Skip to content

Define a Grid from scratch

Santiago Peñate Vera edited this page Sep 30, 2018 · 5 revisions

This example shows how to define a circuit from scratch using GridCal as a library.

Lynn 5 buses

Define the circuit

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')

Define the buses

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)

Add the loads

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)))

Add the generators

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)

Add the lines

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))

Run a power flow simulation

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]