-
Notifications
You must be signed in to change notification settings - Fork 94
Define a Grid from scratch
This example shows how to define a circuit from scratch using GridCal as a library.
A circuit contains all the grid information regardless of the islands formed or the amount of devices
from GridCal.Engine.All import *
grid = MultiCircuit(name='lynn 5 bus')
I will define this bus with all the properties so you see
bus1 = Bus(name='Bus1',
vnom=10, # Nominal voltage in kV
vmin=0.9, # Bus minimum voltage in per unit
vmax=1.1, # Bus maximum voltage in per unit
xpos=0, # Bus x position in pixels
ypos=0, # Bus y position in pixels
height=0, # Bus height in pixels
width=0, # Bus width in pixels
active=True, # Is the bus active?
is_slack=False, # Is this bus a slack bus?
area='Defualt', # Area (for grouping purposes only)
zone='Default', # Zone (for grouping purposes only)
substation='Default' # Substation (for grouping purposes only)
)
The rest of the buses are defined with the default parameters
bus2 = Bus(name='Bus2')
bus3 = Bus(name='Bus3')
bus4 = Bus(name='Bus4')
bus5 = Bus(name='Bus5')
Add the bus objects to the circuit
grid.add_bus(bus1)
grid.add_bus(bus2)
grid.add_bus(bus3)
grid.add_bus(bus4)
grid.add_bus(bus5)
In GridCal, the loads, generators etc. are stored within each bus object: We'll define the first load completely
l2 = Load(name='Load',
impedance=complex(0, 0), # Impedance of the ZIP model in MVA at the nominal voltage
current=complex(0, 0), # Current of the ZIP model in MVA at the nominal voltage
power=complex(40, 20), # Power of the ZIP model in MVA
impedance_prof=None, # Impedance profile
current_prof=None, # Current profile
power_prof=None, # Power profile
active=True, # Is active?
mttf=0.0, # Mean time to failure
mttr=0.0 # Mean time to recovery
)
grid.add_load(bus2, l2)
Define the others with the default parameters
grid.add_load(bus3, Load(power=complex(25, 15)))
grid.add_load(bus4, Load(power=complex(40, 20)))
grid.add_load(bus5, Load(power=complex(50, 20)))
In GridCal you do not need to set a Slack bus specifically. Instead you can just add Generators to the buses and GridCal will pick the largest generator if no slack bus has been defined.
g1 = ControlledGenerator(name='gen',
active_power=0.0, # Active power in MW, since this generator is used to set the slack , is 0
voltage_module=1.0, # Voltage set point to control
Qmin=-9999, # minimum reactive power in MVAr
Qmax=9999, # Maximum reactive power in MVAr
Snom=9999, # Nominal power in MVA
power_prof=None, # power profile
vset_prof=None, # voltage set point profile
active=True # Is active?
)
grid.add_controlled_generator(bus1, g1)
GridCal implements a generic Pi-model of the lines, transformer, switches, etc. So whenever you want to define a line or transformer you just call the Branch object and then define the object type tag.
br1 = Branch(bus_from=bus1,
bus_to=bus2,
name='Line 1-2',
r=0.05, # resistance of the pi model in per unit
x=0.11, # reactance of the pi model in per unit
g=1e-20, # conductance of the pi model in per unit
b=0.02, # susceptance of the pi model in per unit
rate=50, # Rate in MVA
tap=1.0, # Tap value (value close to 1)
shift_angle=0, # Tap angle in radians
active=True, # is the branch active?
mttf=0, # Mean time to failure
mttr=0, # Mean time to recovery
branch_type=BranchType.Line, # Branch type tag
length=1, # Length in km (to be used with templates)
type_obj=BranchTemplate() # Branch template (The default one is void)
)
grid.add_branch(br1)
grid.add_branch(Branch(bus1, bus3, name='Line 1-3', r=0.05, x=0.11, b=0.02, rate=50))
grid.add_branch(Branch(bus1, bus5, name='Line 1-5', r=0.03, x=0.08, b=0.02, rate=80))
grid.add_branch(Branch(bus2, bus3, name='Line 2-3', r=0.04, x=0.09, b=0.02, rate=3))
grid.add_branch(Branch(bus2, bus5, name='Line 2-5', r=0.04, x=0.09, b=0.02, rate=10))
grid.add_branch(Branch(bus3, bus4, name='Line 3-4', r=0.06, x=0.13, b=0.03, rate=30))
grid.add_branch(Branch(bus4, bus5, name='Line 4-5', r=0.04, x=0.09, b=0.02, rate=30))
We need to specify power flow options:
pf_options = PowerFlowOptions(solver_type=SolverType.NR, # Base method to use
verbose=False, # Verbose option where available
tolerance=1e-6, # power error in p.u.
max_iter=25, # maximum iteration number
control_q=True # if to control the reactive power
)
Declare and execute the power flow simulation:
pf = PowerFlow(grid, pf_options)
pf.run()
Once executed, let's compose a nice DataFrame
with the voltage results:
headers = ['Vm (p.u.)', 'Va (Deg)', 'Vre', 'Vim']
Vm = np.abs(pf.results.voltage)
Va = np.angle(pf.results.voltage, deg=True)
Vre = pf.results.voltage.real
Vim = pf.results.voltage.imag
data = np.c_[Vm, Va, Vre, Vim]
v_df = pd.DataFrame(data=data, columns=headers, index=grid.bus_names)
print('\n', v_df)
Let's do the same for the branch results:
headers = ['Loading (%)', 'Current(p.u.)', 'Power (MVA)']
loading = np.abs(pf.results.loading) * 100
current = np.abs(pf.results.Ibranch)
power = np.abs(pf.results.Sbranch)
data = np.c_[loading, current, power]
br_df = pd.DataFrame(data=data, columns=headers, index=grid.branch_names)
print('\n', br_df)
Finally let's display the execution metrics:
print('\nError:', pf.results.error)
print('Slapsed time (s):', pf.results.elapsed)
The displayed results are:
Vm (p.u.) | Va (Deg) | Vre | Vim | |
---|---|---|---|---|
Bus1 | 1 | 0 | 1 | 0 |
Bus2 | 0.955306 | -2.40484 | 0.954465 | -0.0400846 |
Bus3 | 0.954818 | -2.3638 | 0.954005 | -0.0393809 |
Bus4 | 0.933334 | -3.64979 | 0.931441 | -0.0594139 |
Bus5 | 0.953394 | -2.68896 | 0.952344 | -0.0447275 |
Loading (%) | Current(p.u.) | Power (MVA) | |
---|---|---|---|
Line 1-2 | 99.6103 | 0.498051 | 49.8051 |
Line 1-3 | 99.3956 | 0.496978 | 49.6978 |
Line 1-5 | 95.0725 | 0.76058 | 76.058 |
Line 2-3 | 55.5197 | 0.0174441 | 1.66559 |
Line 2-5 | 50.6269 | 0.0529954 | 5.06269 |
Line 3-4 | 65.5591 | 0.205984 | 19.6677 |
Line 4-5 | 81.0433 | 0.255015 | 24.313 |
Error: [1.8965826464878432e-08]
Slapsed time (s): [0.01304483413696289]