Welcome to GridCal’s documentation!

GridCal (abbreviation of Grid Calculator) is a power systems software that has been used for research and power systems consultancy since 2015.

GridCal aims at being a fully usable software product by end users, while allowing power users to tweak it and extend it at will. GridCal is designed as a software library with a graphical user interface, and because of this you can use GridCal as a conventional software, or you may embed it into your own programs.

You may also power systems theory used in GridCal along with interesting information in the author’s simulation guide.

About GridCal

GridCal is a research oriented power systems software.

Research oriented? How? Well, it is a fruit of research. It is designed to be modular. As a researcher I found that the available software (not even talking about commercial options) are hard to expand or adapt to achieve complex simulations. GridCal is designed to allow you to build and reuse modules, which eventually will boost your productivity and the possibilities that are at hand.

I have made other projects (even another open source one: fPotencia in C++). I believe that this project encapsulates my half life of programming experience and the curiosity I have developed for power systems.

So, I do really hope you enjoy it, and if you have comments, suggestions or just want to collaborate, do not hesitate to contact.

Cheers,

Santiago

Getting Started

Installation

GridCal is designed to work with Python 3.6 and onwards, hence retro-compatibility is not guaranteed.

Standalone setup

You can install GridCal as a separated standalone program without having to bother about setting up python.

GridCal for windows x64

GridCal for linux x64

Remember to update the program to the latest version once installed. You’ll find an update script in the installation folder.

Python Package Installation

GridCal is multiplatform and it will work in Linux, Windows and OSX.

The recommended way to install GridCal if you have a python distribution already is to open a console and type:

  • Windows: pip install GridCal
  • OSX / Linux: pip3 install GridCal

You must have Python 3.6 or higher installed to work with the GUI.

Check out the video on how to install Python and GridCal on Windows 10.

Manual package installation

Sometimes pip does not download the lattest version for some reason. In those cases, follow this link and download the latest GridCal file: GridCal-x.xx.tar.gz.

From a console install the file manually:

  • Windows: pip install GridCal-x.xx.tar.gz
  • OSX / Linux: pip3 install GridCal-x.xx.tar.gz

Installation from GitHub

To install the development version of GridCal that lives under src, open a console and type:

python3 -m pip install -e 'git+git://github.com/SanPen/GridCal.git#egg=GridCal&subdirectory=src'

Installing GridCal from GitHub, pip can still freeze the version using a commit hash:

python -m pip install -e 'git+git://github.com/SanPen/GridCal.git@5c4dcb96998ae882412b5fee977cf0cff7a40d3c#egg=GridCal&subdirectory=UnderDevelopment'

Here 5c4dcb96998ae882412b5fee977cf0cff7a40d3c is the git version.

PySide2 vs. PyQt5

Now GridCal has been completely ported to PySide2. The reason for this is because PySide2 has been endorsed by Qt as the main python wrapper for the Qt Library and therefore it is expected to have the best support.

However there are plenty of other libraries that depend of PyQt5 which is an alternative wrapper for the Qt framework (and used to be the best one though)

After some test, I can tell you that if GridCal does not work and you installed Python via the Anaconda distribution, go to your anaconda main folder and remove the file qt.conf. No other real solution out is there really.

Video tutorial

If you want to start using GridCal with the user interface, the best way might be this tutorial:

_images/video_tutorial.png

There are many other features not shown in the tutorial, but the video guides you through the basics with an example

Running GridCal

You must have Python 3.6 or higher installed to work with the GUI. From a Python console:

from GridCal.ExecuteGridCal import run
run()

To see a walk through the user interface go to Graphical User Interface.

Windows

To run GridCal with GUI directly from a system console, type:

python -c "from GridCal.ExecuteGridCal import run; run()"

You can embed this command in a shortcut file, and have a windows shortcut in the desktop for GridCal.

Linux / OSX

To run GridCal with GUI directly from a system console, type:

python3 -c "from GridCal.ExecuteGridCal import run; run()"

Using GridCal as a library

You can use the calculation engine directly or from other applications:

from GridCal.Engine import *

This will provide access to all the objects in the internal engine of GridCal. Refer to the API Reference and the examples for more details.

Power systems scripting

GridCal is a Python library for power systems simulation. As such you can seamlessly use the graphical user interface (GUI) or use GridCal as a library to program your own software. Using GridCal as a library might be useful when automatizing tasks or when you need to build a custom study or new tool.

5-node grid creation script

This example creates the five-node grid from the fantastic book “Power System Load Flow Analysis” and runs a power flow. After the power flow is executed, the results are printed on the console.

from GridCal.Engine import *

np.set_printoptions(precision=4)
grid = MultiCircuit()

# Add the buses and the generators and loads attached
bus1 = Bus('Bus 1', vnom=20)
# bus1.is_slack = True
grid.add_bus(bus1)

gen1 = Generator('Slack Generator', voltage_module=1.0)
grid.add_generator(bus1, gen1)

bus2 = Bus('Bus 2', vnom=20)
grid.add_bus(bus2)
grid.add_load(bus2, Load('load 2', P=40, Q=20))

bus3 = Bus('Bus 3', vnom=20)
grid.add_bus(bus3)
grid.add_load(bus3, Load('load 3', P=25, Q=15))

bus4 = Bus('Bus 4', vnom=20)
grid.add_bus(bus4)
grid.add_load(bus4, Load('load 4', P=40, Q=20))

bus5 = Bus('Bus 5', vnom=20)
grid.add_bus(bus5)
grid.add_load(bus5, Load('load 5', P=50, Q=20))


# add branches (Lines in this case)
grid.add_branch(Branch(bus1, bus2, 'line 1-2', r=0.05, x=0.11, b=0.02))

grid.add_branch(Branch(bus1, bus3, 'line 1-3', r=0.05, x=0.11, b=0.02))

grid.add_branch(Branch(bus1, bus5, 'line 1-5', r=0.03, x=0.08, b=0.02))

grid.add_branch(Branch(bus2, bus3, 'line 2-3', r=0.04, x=0.09, b=0.02))

grid.add_branch(Branch(bus2, bus5, 'line 2-5', r=0.04, x=0.09, b=0.02))

grid.add_branch(Branch(bus3, bus4, 'line 3-4', r=0.06, x=0.13, b=0.03))

grid.add_branch(Branch(bus4, bus5, 'line 4-5', r=0.04, x=0.09, b=0.02))


options = PowerFlowOptions(SolverType.NR, verbose=False)
power_flow = PowerFlow(grid, options)
power_flow.run()

print('\n\n', grid.name)
print('\t|V|:', abs(power_flow.results.voltage))
print('\t|Sbranch|:', abs(power_flow.results.Sbranch))
print('\t|loading|:', abs(power_flow.results.loading) * 100)
print('\terr:', power_flow.results.error)
print('\tConv:', power_flow.results.converged)

grid.plot_graph()
plt.show()
Other examples

Examples are included in Tutorials folder of the GitHub repository. In addition, the tests under src/tests may serve as valuable examples.

Lastly, the GitHub wiki includes a few more examples.

API reference

GridCal uses an object oriented approach for all the data and simulation management. However the object orientation is very inefficient when used in numerical computation, that is why there are compile() functions that extract the information out of the objects and turn this information into vectors, matrices and DataFrames in order to have efficient numerical computations. After having been involved in quite some number-crunching software developments, I have found this approach to be the best compromise between efficiency and code scalability and maintainability.

The whole idea can be summarized as:

Object oriented structures -> intermediate objects holding arrays -> Numerical modules

GridCal package

Subpackages

GridCal.Engine package
GridCal.Engine.maketrans()

Return a translation table usable for str.translate().

If there is only one argument, it must be a dictionary mapping Unicode ordinals (integers) or characters to Unicode ordinals, strings or None. Character keys will be then converted to ordinals. If there are two arguments, they must be strings of equal length, and in the resulting dictionary, each character in x will be mapped to the character at the same position in y. If there is a third argument, it must be a string, whose characters will be mapped to None in the result.

Subpackages
GridCal.Engine.Core package
Submodules
GridCal.Engine.Core.calculation_inputs module
class GridCal.Engine.Core.calculation_inputs.CalculationInputs(nbus, nbr, ntime, nbat, nctrlgen)

Bases: object

nbus (int): Number of buses nbr (int): Number of branches ntime (int): Number of time steps nbat (int): Number of batteries nctrlgen (int): Number of voltage controlled generators

build_linear_ac_sys_mat()

Get the AC linear approximation matrices :return:

compile_types(types_new=None)

Compile the types :param types_new: new array of types to consider :return: Nothing

compute_branch_results(V)

Compute the branch magnitudes from the voltages :param V: Voltage vector solution in p.u. :return: CalculationResults instance with all the grid magnitudes

consolidate()

Compute the magnitudes that cannot be computed vector-wise

get_island(bus_idx, branch_idx, gen_idx, bat_idx)

Get a sub-island :param bus_idx: bus indices of the island :param branch_idx: branch indices of the island :return: CalculationInputs instance

get_structure(structure_type)

Get a DataFrame with the input.

Arguments:

structure_type (str): ‘Vbus’, ‘Sbus’, ‘Ibus’, ‘Ybus’, ‘Yshunt’, ‘Yseries’ or ‘Types’

Returns:

pandas DataFrame
print(bus_names)

print in console :return:

re_calc_admittance_matrices(tap_mod)

Recalculate the admittance matrices as the tap changes :param tap_mod: tap modules per bus :return: Nothing, the matrices are changed in-place

trim_profiles(time_idx)

Trims the profiles with the passed time indices and stores those time indices for later :param time_idx: array of time indices

GridCal.Engine.Core.csc_graph module
class GridCal.Engine.Core.csc_graph.Graph(adj)

Bases: object

find_islands()

Method to get the islands of a graph This is the non-recursive version :return: islands list where each element is a list of the node indices of the island

GridCal.Engine.Core.multi_circuit module
class GridCal.Engine.Core.multi_circuit.MultiCircuit(name='')

Bases: object

The concept of circuit should be easy enough to understand. It represents a set of nodes (buses) and branches (lines, transformers or other impedances).

The MultiCircuit class is the main object in GridCal. It represents a circuit that may contain islands. It is important to understand that a circuit split in two or more islands cannot be simulated as is, because the admittance matrix would be singular. The solution to this is to split the circuit in island-circuits. Therefore MultiCircuit identifies the islands and creates individual Circuit objects for each of them.

GridCal uses an object oriented approach for the data management. This allows to group the data in a smart way. In GridCal there are only two types of object directly declared in a Circuit or MultiCircuit object. These are the Bus and the Branch. The branches connect the buses and the buses contain all the other possible devices like loads, generators, batteries, etc. This simplifies enormously the management of element when adding, associating and deleting.

from GridCal.Engine.Core.multi_circuit import MultiCircuit
grid = MultiCircuit(name="My grid")
add_battery(bus: GridCal.Engine.Devices.bus.Bus, api_obj=None)

Add a Battery object to a Bus.

Arguments:

bus (Bus): Bus object

api_obj (Battery): Battery object

add_branch(obj: GridCal.Engine.Devices.branch.Branch)

Add a Branch object to the grid.

Arguments:

obj (Branch): Branch object
add_bus(obj: GridCal.Engine.Devices.bus.Bus)

Add a Bus object to the grid.

Arguments:

obj (Bus): Bus object
add_generator(bus: GridCal.Engine.Devices.bus.Bus, api_obj=None)

Add a (controlled) Generator object to a Bus.

Arguments:

bus (Bus): Bus object

api_obj (Generator): Generator object

add_load(bus: GridCal.Engine.Devices.bus.Bus, api_obj=None)

Add a Load object to a Bus.

Arguments:

bus (Bus): Bus object

api_obj (Load): Load object

add_overhead_line(obj: GridCal.Engine.Devices.tower.Tower)

Add overhead line (tower) template to the collection :param obj: Tower instance

add_sequence_line(obj: GridCal.Engine.Devices.sequence_line.SequenceLineType)

Add sequence line to the collection :param obj: SequenceLineType instance

add_shunt(bus: GridCal.Engine.Devices.bus.Bus, api_obj=None)

Add a Shunt object to a Bus.

Arguments:

bus (Bus): Bus object

api_obj (Shunt): Shunt object

add_static_generator(bus: GridCal.Engine.Devices.bus.Bus, api_obj=None)

Add a StaticGenerator object to a Bus.

Arguments:

bus (Bus): Bus object

api_obj (StaticGenerator): StaticGenerator object

add_transformer_type(obj: GridCal.Engine.Devices.transformer.TransformerType)

Add transformer template :param obj: TransformerType instance

add_underground_line(obj: GridCal.Engine.Devices.underground_line.UndergroundLineType)

Add underground line :param obj: UndergroundLineType instance

add_wire(obj: GridCal.Engine.Devices.wire.Wire)

Add Wire to the collection :param obj: Wire instance

apply_all_branch_types()

Apply all the branch types

apply_lp_profiles()

Apply the LP results as device profiles.

assign_circuit(circ)

Assign a circuit object to this object.

Arguments:

circ (MultiCircuit): MultiCircuit object
build_graph()

Returns a networkx DiGraph object of the grid.

clear()

Clear the multi-circuit (remove the bus and branch objects)

compile(use_opf_vals=False, opf_time_series_results=None, logger=[]) → GridCal.Engine.Core.numerical_circuit.NumericalCircuit

Compile the circuit assets into an equivalent circuit that only contains matrices and vectors for calculation. This method returns the numerical circuit, but it also assigns it to self.numerical_circuit, which is used when the MultiCircuit object is passed onto a simulation driver, for example:

from GridCal.Engine import *

grid = MultiCircuit()
grid.load_file("grid.xlsx")
grid.compile()

options = PowerFlowOptions()

power_flow = PowerFlowMP(grid, options)
power_flow.run()

Arguments:

use_opf_vals (bool, False): Use OPF results as inputs

opf_time_series_results (list, None): OPF results to be used as inputs

logger (list, []): Message log

Returns:

self.numerical_circuit (NumericalCircuit): Compiled numerical circuit of the grid
copy()

Returns a deep (true) copy of this circuit.

create_profiles(steps, step_length, step_unit, time_base: datetime.datetime = datetime.datetime(2019, 8, 5, 19, 47, 35, 371775))

Set the default profiles in all the objects enabled to have profiles.

Arguments:

steps (int): Number of time steps

step_length (int): Time length (1, 2, 15, …)

step_unit (str): Unit of the time step (“h”, “m” or “s”)

time_base (datetime, datetime.now()): Date to start from

delete_branch(obj: GridCal.Engine.Devices.branch.Branch)

Delete a Branch object from the grid.

Arguments:

obj (Branch): Branch object
delete_bus(obj: GridCal.Engine.Devices.bus.Bus)

Delete a Bus object from the grid.

Arguments:

obj (Bus): Bus object
delete_overhead_line(i)

Delete tower from the collection :param i: index

delete_sequence_line(i)

Delete sequence line from the collection :param i: index

delete_transformer_type(i)

Delete transformer type from the colection :param i: index

delete_underground_line(i)

Delete underground line :param i: index

delete_wire(i)

Delete wire from the collection :param i: index

device_type_name_dict = None

self.type_name = ‘Shunt’

self.properties_with_profile = [‘Y’]

dispatch()

Dispatch either load or generation using a simple equalised share rule of the shedding to be done.

export_pf(file_name, power_flow_results)

Export power flow results to file.

Arguments:

file_name (str): Excel file name
export_profiles(file_name)

Export object profiles to file.

Arguments:

file_name (str): Excel file name
format_profiles(index)

Format the pandas profiles in place using a time index.

Arguments:

index: Time profile
get_Jacobian(sparse=False)

Returns the grid Jacobian matrix.

Arguments:

sparse (bool, False): Return the matrix in CSR sparse format (True) or as full matrix (False)
get_batteries()

Returns a list of Battery objects in the grid.

get_battery_capacities()

Returns a list of Battery capacities.

get_battery_names()

Returns a list of Battery names.

get_catalogue_dict(branches_only=False)

Returns a dictionary with the catalogue types and the associated list of objects.

Arguments:

branches_only (bool, False): Only branch types
get_catalogue_dict_by_name(type_class=None)
get_controlled_generator_names()

Returns a list of Generator names.

get_elements_by_type(element_type: GridCal.Engine.Devices.meta_devices.DeviceType)

Get set of elements and their parent nodes :param element_type: DeviceTYpe instance :return: List of elements, it raises an exception if the elements are unknown

get_generators()

Returns a list of Generator objects in the grid.

get_json_dict(id)

Returns a JSON dictionary of the MultiCircuit instance with the following values: id, type, phases, name, Sbase, comments.

Arguments:

id: Arbitrary identifier
get_load_names()

Returns a list of Load names.

get_loads()

Returns a list of Load objects in the grid.

get_node_elements_by_type(element_type: GridCal.Engine.Devices.meta_devices.DeviceType)

Get set of elements and their parent nodes.

Arguments:

element_type (str): Element type, either “Load”, “StaticGenerator”, “Generator”, “Battery” or “Shunt”

Returns:

List of elements, list of matching parent buses
get_shunt_names()

Returns a list of Shunt names.

get_shunts()

Returns a list of Shunt objects in the grid.

get_static_generators()

Returns a list of StaticGenerator objects in the grid.

get_static_generators_names()

Returns a list of StaticGenerator names.

plot_graph(ax=None)

Plot the grid. :param ax: Matplotlib axis object :return:

save_calculation_objects(file_path)

Save all the calculation objects of all the grids.

Arguments:

file_path (str): Path to file
set_power(S)

Set the power array in the circuits.

Arguments:

S (list): Array of power values in MVA for all the nodes in all the islands
set_state(t)

Set the profiles state at the index t as the default values.

GridCal.Engine.Core.numerical_circuit module
class GridCal.Engine.Core.numerical_circuit.NumericalCircuit(n_bus, n_br, n_ld, n_gen, n_sta_gen, n_batt, n_sh, n_time, Sbase)

Bases: object

R_corrected(t=None)

Returns temperature corrected resistances (numpy array) based on a formula provided by: NFPA 70-2005, National Electrical Code, Table 8, footnote #2; and https://en.wikipedia.org/wiki/Electrical_resistivity_and_conductivity#Linear_approximation (version of 2019-01-03 at 15:20 EST).

compute(add_storage=True, add_generation=True, apply_temperature=False, branch_tolerance_mode=<BranchImpedanceMode.Specified: 0>) → List[GridCal.Engine.Core.calculation_inputs.CalculationInputs]

Compute the cross connectivity matrices to determine the circuit connectivity towards the calculation. Additionally, compute the calculation matrices. :param add_storage: :param add_generation: :param apply_temperature: :param branch_tolerance_mode: :return: list of CalculationInputs instances where each one is a circuit island

compute_ts(add_storage=True, add_generation=True, apply_temperature=False, branch_tolerance_mode=<BranchImpedanceMode.Specified: 0>) → Dict[int, List[GridCal.Engine.Core.calculation_inputs.CalculationInputs]]

Compute the cross connectivity matrices to determine the circuit connectivity towards the calculation. Additionally, compute the calculation matrices. :param add_storage: :param add_generation: :param apply_temperature: :param branch_tolerance_mode: :return: dictionary of lists of CalculationInputs instances where each one is a circuit island

get_B(apply_temperature=False)
Parameters:apply_temperature
Returns:
get_different_states()

Get a dictionary of different connectivity states :return: dictionary of states {master state index -> list of states associated}

get_raw_circuit(add_generation, add_storage) → GridCal.Engine.Core.calculation_inputs.CalculationInputs
Parameters:
  • add_generation
  • add_storage
Returns:

plot(stop=True)

Plot the grid as a graph :param stop: stop the execution while displaying

power_flow_post_process(V, only_power=False, t=0)

Compute the power flows trough the branches for the complete circuit taking into account the islands @param V: Voltage solution array for the circuit buses @param only_power: compute only the power injection @return: Sbranch (MVA), Ibranch (p.u.), loading (p.u.), losses (MVA), Sbus(MVA)

print(islands_only=False)

print the connectivity matrices :return:

GridCal.Engine.Core.numerical_circuit.calc_connectivity(branch_active, C_branch_bus_f, C_branch_bus_t, apply_temperature, R_corrected, R, X, G, B, branch_tolerance_mode: GridCal.Engine.basic_structures.BranchImpedanceMode, impedance_tolerance, tap_mod, tap_ang, tap_t, tap_f, Ysh)

Build all the admittance related objects :param branch_active: array of branch active :param C_branch_bus_f: branch-bus from connectivity matrix :param C_branch_bus_t: branch-bus to connectivity matrix :param apply_temperature: apply temperature correction? :param R_corrected: Use the corrected resistance? :param R: array of resistance :param X: array of reactance :param G: array of conductance :param B: array of susceptance :param branch_tolerance_mode: branch tolerance mode (enum: BranchImpedanceMode) :param impedance_tolerance: impedance tolerance :param tap_mod: tap modules array :param tap_ang: tap angles array :param tap_t: virtual tap to array :param tap_f: virtual tap from array :param Ysh: shunt admittance injections :return: Ybus: Admittance matrix

Yf: Admittance matrix of the from buses Yt: Admittance matrix of the to buses B1: Fast decoupled B’ matrix B2: Fast decoupled B’’ matrix Yseries: Admittance matrix of the series elements Ys: array of series admittances GBc: array of shunt conductances Cf: Branch-bus from connectivity matrix Ct: Branch-to from connectivity matrix C_bus_bus: Adjacency matrix C_branch_bus: branch-bus connectivity matrix islands: List of islands bus indices (each list element is a list of bus indices of the island)
GridCal.Engine.Core.numerical_circuit.calc_islands(circuit: GridCal.Engine.Core.calculation_inputs.CalculationInputs, C_bus_bus, C_branch_bus, C_gen_bus, C_batt_bus, nbus, nbr, time_idx=None) → List[GridCal.Engine.Core.calculation_inputs.CalculationInputs]

Partition the circuit in islands for the designated time intervals :param circuit: CalculationInputs instance with all the data regardless of the islands and the branch states :param C_bus_bus: bus-bus connectivity matrix :param C_branch_bus: branch-bus connectivity matrix :param C_gen_bus: gen-bus connectivity matrix :param C_batt_bus: battery-bus connectivity matrix :param nbus: number of buses :param nbr: number of branches :param time_idx: array with the time indices where this set of islands belongs to

(if None all the time series are kept)
Returns:list of CalculationInputs instances
GridCal.Engine.Core.numerical_circuit.get_branches_of_the_island(island, C_branch_bus)

Get the branch indices of the island :param island: array of bus indices of the island :param C_branch_bus: connectivity matrix of the branches and the buses :return: array of indices of the branches

GridCal.Engine.Devices package
Submodules
GridCal.Engine.Devices.battery module
class GridCal.Engine.Devices.battery.Battery(name='batt', active_power=0.0, power_factor=0.8, voltage_module=1.0, is_controlled=True, Qmin=-9999, Qmax=9999, Snom=9999, Enom=9999, p_min=-9999, p_max=9999, op_cost=1.0, power_prof=None, power_factor_prof=None, vset_prof=None, active=True, Sbase=100, enabled_dispatch=True, mttf=0.0, mttr=0.0, charge_efficiency=0.9, discharge_efficiency=0.9, max_soc=0.99, min_soc=0.3, soc=0.8, charge_per_cycle=0.1, discharge_per_cycle=0.1)

Bases: GridCal.Engine.Devices.generator.Generator

Battery (voltage controlled and dispatchable).

Arguments:

name (str, “batt”): Name of the battery

active_power (float, 0.0): Active power in MW

power_factor (float, 0.8): Power factor

voltage_module (float, 1.0): Voltage setpoint in per unit

is_controlled (bool, True): Is the unit voltage controlled (if so, the connection bus becomes a PV bus)

Qmin (float, -9999): Minimum reactive power in MVAr

Qmax (float, 9999): Maximum reactive power in MVAr

Snom (float, 9999): Nominal apparent power in MVA

Enom (float, 9999): Nominal energy capacity in MWh

p_min (float, -9999): Minimum dispatchable power in MW

p_max (float, 9999): Maximum dispatchable power in MW

op_cost (float, 1.0): Operational cost in Eur (or other currency) per MW

power_prof (DataFrame, None): Pandas DataFrame with the active power profile in MW

power_factor_prof (DataFrame, None): Pandas DataFrame with the power factor profile

vset_prof (DataFrame, None): Pandas DataFrame with the voltage setpoint profile in per unit

active (bool, True): Is the battery active?

Sbase (float, 100): Base apparent power in MVA

enabled_dispatch (bool, True): Is the battery enabled for OPF?

mttf (float, 0.0): Mean time to failure in hours

mttr (float, 0.0): Mean time to recovery in hours

charge_efficiency (float, 0.9): Efficiency when charging

discharge_efficiency (float, 0.9): Efficiency when discharging

max_soc (float, 0.99): Maximum state of charge

min_soc (float, 0.3): Minimum state of charge

soc (float, 0.8): Current state of charge

charge_per_cycle (float, 0.1): Per unit of power to take per cycle when charging

discharge_per_cycle (float, 0.1): Per unit of power to deliver per cycle when discharging

copy()

Make a copy of this object Returns: Battery instance

get_json_dict(id, bus_dict)

Get json dictionary :param id: ID: Id for this object :param bus_dict: Dictionary of buses [object] -> ID :return: json-compatible dictionary

get_processed_at(t, dt, store_values=True)

Get the processed power at the time index t :param t: time index :param dt: time step in hours :param store_values: store the values? :return: active power processed by the battery control in MW

initialize_arrays(index, arr=None, arr_in_pu=False)

Create power profile based on index :param index: time index associated :param arr: array of values :param arr_in_pu: is the array in per unit?

process(P, dt, charge_if_needed=False)

process a cycle in the battery :param P: proposed power in MW :param dt: time increment in hours :param charge_if_needed: True / False :param store_values: Store the values into the internal arrays? :return: Amount of power actually processed in MW

reset()

Set the battery to its initial state

GridCal.Engine.Devices.branch module
class GridCal.Engine.Devices.branch.Branch(bus_from: GridCal.Engine.Devices.bus.Bus, bus_to: GridCal.Engine.Devices.bus.Bus, name='Branch', r=1e-20, x=1e-20, g=1e-20, b=1e-20, rate=1.0, tap=1.0, shift_angle=0, active=True, tolerance=0, cost=0.0, mttf=0, mttr=0, r_fault=0.0, x_fault=0.0, fault_pos=0.5, branch_type: GridCal.Engine.Devices.types.BranchType = <BranchType.Line: ('line', )>, length=1, vset=1.0, temp_base=20, temp_oper=20, alpha=0.0033, bus_to_regulated=False, template=<GridCal.Engine.Devices.branch.BranchTemplate object>)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

The Branch class represents the connections between nodes (i.e. buses) in GridCal. A branch is an element (cable, line, capacitor, transformer, etc.) with an electrical impedance. The basic Branch class includes basic electrical attributes for most passive elements, but other device types may be passed to the Branch constructor to configure it as a specific type.

For example, a transformer may be created with the following code:

from GridCal.Engine.Core.multi_circuit import MultiCircuit
from GridCal.Engine.Devices import *
from GridCal.Engine.Devices.types import *

# Create grid
grid = MultiCircuit()

# Create buses
POI = Bus(name="POI",
          vnom=100, #kV
          is_slack=True)
grid.add_bus(POI)

B_C3 = Bus(name="B_C3",
           vnom=10) #kV
grid.add_bus(B_C3)

# Create transformer types
SS = TransformerType(name="SS",
                     hv_nominal_voltage=100, # kV
                     lv_nominal_voltage=10, # kV
                     nominal_power=100, # MVA
                     copper_losses=10000, # kW
                     iron_losses=125, # kW
                     no_load_current=0.5, # %
                     short_circuit_voltage=8) # %
grid.add_transformer_type(SS)

# Create transformer
X_C3 = Branch(bus_from=POI,
              bus_to=B_C3,
              name="X_C3",
              branch_type=BranchType.Transformer,
              template=SS,
              )

# Add transformer to grid
grid.add_branch(X_C3)

Refer to the GridCal.Engine.Devices.branch.TapChanger class for an example using a voltage regulator.

Arguments:

bus_from (GridCal.Engine.Devices.bus module): “From” bus object

bus_to (GridCal.Engine.Devices.bus module): “To” bus object

name (str, “Branch”): Name of the branch

r (float, 1e-20): Branch resistance in per unit

x (float, 1e-20): Branch reactance in per unit

g (float, 1e-20): Branch shunt conductance in per unit

b (float, 1e-20): Branch shunt susceptance in per unit

rate (float, 1.0): Branch rate in MVA

tap (float, 1.0): Branch tap module

shift_angle (int, 0): Tap shift angle in radians

active (bool, True): Is the branch active?

tolerance (float, 0): Tolerance specified for the branch impedance in %

mttf (float, 0.0): Mean time to failure in hours

mttr (float, 0.0): Mean time to recovery in hours

r_fault (float, 0.0): Mid-line fault resistance in per unit (SC only)

x_fault (float, 0.0): Mid-line fault reactance in per unit (SC only)

fault_pos (float, 0.0): Mid-line fault position in per unit (0.0 = bus_from, 0.5 = middle, 1.0 = bus_to)

branch_type (BranchType, BranchType.Line): Device type enumeration (ex.: GridCal.Engine.Devices.transformer.TransformerType)

length (float, 0.0): Length of the branch in km

vset (float, 1.0): Voltage set-point of the voltage controlled bus in per unit

temp_base (float, 20.0): Base temperature at which r is measured in °C

temp_oper (float, 20.0): Operating temperature in °C

alpha (float, 0.0033): Thermal constant of the material in °C

bus_to_regulated (bool, False): Is the bus_to voltage regulated by this branch?

template (BranchTemplate, BranchTemplate()): Basic branch template

R_corrected

Returns a temperature corrected resistance based on a formula provided by: NFPA 70-2005, National Electrical Code, Table 8, footnote #2; and https://en.wikipedia.org/wiki/Electrical_resistivity_and_conductivity#Linear_approximation (version of 2019-01-03 at 15:20 EST).

apply_tap_changer(tap_changer: GridCal.Engine.Devices.branch.TapChanger)

Apply a new tap changer

Argument:

tap_changer (GridCal.Engine.Devices.branch.TapChanger): Tap changer object
apply_template(obj, Sbase, logger=[])

Apply a branch template to this object

Arguments:

obj: TransformerType or Tower object

Sbase (float): Nominal power in MVA

logger (list, []): Log list

branch_type_converter(val_string)

function to convert the branch type string into the BranchType :param val_string: :return: branch type conversion

copy(bus_dict=None)

Returns a copy of the branch @return: A new with the same content as this

get_json_dict(id, bus_dict)

Get json dictionary :param id: ID: Id for this object :param bus_dict: Dictionary of buses [object] -> ID :return:

get_save_data()

Return the data that matches the edit_headers :return:

get_virtual_taps()

Get the branch virtual taps

The virtual taps generate when a transformer nominal winding voltage differs from the bus nominal voltage.

Returns:

tap_f (float, 1.0): Virtual tap at the from side

tap_t (float, 1.0): Virtual tap at the to side

plot_profiles(time_series=None, my_index=0, show_fig=True)

Plot the time series results of this object :param time_series: TimeSeries Instance :param my_index: index of this object in the simulation :param show_fig: Show the figure?

tap_down()

Move the tap changer one position up

tap_up()

Move the tap changer one position up

class GridCal.Engine.Devices.branch.BranchTemplate(name='BranchTemplate', tpe=<BranchType.Branch: ('branch', )>)

Bases: object

get_save_data()
class GridCal.Engine.Devices.branch.BranchTypeConverter(tpe: GridCal.Engine.Devices.types.BranchType)

Bases: object

class GridCal.Engine.Devices.branch.TapChanger(taps_up=5, taps_down=5, max_reg=1.1, min_reg=0.9)

Bases: object

The TapChanger class defines a transformer’s tap changer, either onload or offload. It needs to be attached to a predefined transformer (i.e. a Branch object).

The following example shows how to attach a tap changer to a transformer tied to a voltage regulated GridCal.Engine.Devices.bus module:

from GridCal.Engine.Core.multi_circuit import MultiCircuit
from GridCal.Engine.devices import *
from GridCal.Engine.device_types import *

# Create grid
grid = MultiCircuit()

# Create buses
POI = Bus(name="POI",
          vnom=100, #kV
          is_slack=True)
grid.add_bus(POI)

B_C3 = Bus(name="B_C3",
           vnom=10) #kV
grid.add_bus(B_C3)

# Create transformer types
SS = TransformerType(name="SS",
                     hv_nominal_voltage=100, # kV
                     lv_nominal_voltage=10, # kV
                     nominal_power=100, # MVA
                     copper_losses=10000, # kW
                     iron_losses=125, # kW
                     no_load_current=0.5, # %
                     short_circuit_voltage=8) # %
grid.add_transformer_type(SS)

# Create transformer
X_C3 = Branch(bus_from=POI,
              bus_to=B_C3,
              name="X_C3",
              branch_type=BranchType.Transformer,
              template=SS,
              bus_to_regulated=True,
              vset=1.05)

# Attach tap changer
X_C3.tap_changer = TapChanger(taps_up=16, taps_down=16, max_reg=1.1, min_reg=0.9)
X_C3.tap_changer.set_tap(X_C3.tap_module)

# Add transformer to grid
grid.add_branch(X_C3)

Arguments:

taps_up (int, 5): Number of taps position up

taps_down (int, 5): Number of tap positions down

max_reg (float, 1.1): Maximum regulation up i.e 1.1 -> +10%

min_reg (float, 0.9): Maximum regulation down i.e 0.9 -> -10%

Additional Properties:

tap (int, 0): Current tap position
get_tap()

Get the tap voltage regulation module

set_tap(tap_module)

Set the integer tap position corresponding to a tap value

Attribute:

tap_module (float): Tap module centered around 1.0
tap_down()

Go to the next upper tap position

tap_up()

Go to the next upper tap position

GridCal.Engine.Devices.bus module
class GridCal.Engine.Devices.bus.Bus(name='Bus', vnom=10, vmin=0.9, vmax=1.1, r_fault=0.0, x_fault=0.0, xpos=0, ypos=0, height=0, width=0, active=True, is_slack=False, area='Default', zone='Default', substation='Default')

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

The Bus object is the container of all the possible devices that can be attached to a bus bar or substation. Such objects can be loads, voltage controlled generators, static generators, batteries, shunt elements, etc.

Arguments:

name (str, “Bus”): Name of the bus

vnom (float, 10.0): Nominal voltage in kV

vmin (float, 0.9): Minimum per unit voltage

vmax (float, 1.1): Maximum per unit voltage

r_fault (float, 0.0): Resistance of the fault in per unit (SC only)

x_fault (float, 0.0): Reactance of the fault in per unit (SC only)

xpos (int, 0): X position in pixels (GUI only)

ypos (int, 0): Y position in pixels (GUI only)

height (int, 0): Height of the graphic object (GUI only)

width (int, 0): Width of the graphic object (GUI only)

active (bool, True): Is the bus active?

is_slack (bool, False): Is this bus a slack bus?

area (str, “Default”): Name of the area

zone (str, “Default”): Name of the zone

substation (str, “Default”): Name of the substation

Additional Properties:

Qmin_sum (float, 0): Minimum reactive power of this bus (inferred from the devices)

Qmax_sum (float, 0): Maximum reactive power of this bus (inferred from the devices)

loads (list, list()): List of loads attached to this bus

controlled_generators (list, list()): List of controlled generators attached to this bus

shunts (list, list()): List of shunts attached to this bus

batteries (list, list()): List of batteries attached to this bus

static_generators (list, list()): List of static generators attached to this bus

measurements (list, list()): List of measurements

apply_lp_profiles(Sbase)

Sets the lp solution to the regular generators profile

copy()

Deep copy of this object :return: New instance of this object

create_profiles(index)

Delete all profiles

delete_profiles()

Delete all profiles

determine_bus_type()

Infer the bus type from the devices attached to it @return: Nothing

get_fault_impedance()

Get the fault impedance :return: complex value of fault impedance

get_json_dict(id)

Return Json-like dictionary :return: Dictionary

initialize_lp_profiles()

Dimension the LP var profiles :return: Nothing

merge(other_bus)

Add the elements of the “Other bus” to this bus :param other_bus: Another instance of Bus

plot_profiles(time_profile, ax_load=None, ax_voltage=None, time_series_driver=None, my_index=0)

plot the profiles of this bus :param time_profile: Master profile of time steps (stored in the MultiCircuit) :param time_series_driver: time series driver :param ax_load: Load axis, if not provided one will be created :param ax_voltage: Voltage axis, if not provided one will be created :param my_index: index of this object in the time series results

retrieve_graphic_position()

Get the position set by the graphic object into this object’s variables :return: Nothing

set_profile_values(t)

Set the default values from the profiles at time index t :param t: profile time index

set_state(t)

Set the profiles state of the objects in this bus to the value given in the profiles at the index t :param t: index of the profile :return: Nothing

GridCal.Engine.Devices.generator module
class GridCal.Engine.Devices.generator.Generator(name='gen', active_power=0.0, power_factor=0.8, voltage_module=1.0, is_controlled=True, Qmin=-9999, Qmax=9999, Snom=9999, power_prof=None, power_factor_prof=None, vset_prof=None, Cost_prof=None, active=True, p_min=0.0, p_max=9999.0, op_cost=1.0, Sbase=100, enabled_dispatch=True, mttf=0.0, mttr=0.0)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

Voltage controlled generator. This generators supports several reactive power control modes (see GridCal.Engine.Simulations.PowerFlow.power_flow_driver.ReactivePowerControlMode) to regulate the voltage on its GridCal.Engine.Devices.bus module during power flow simulations.

Arguments:

name (str, “gen”): Name of the generator

active_power (float, 0.0): Active power in MW

power_factor (float, 0.8): Power factor

voltage_module (float, 1.0): Voltage setpoint in per unit

is_controlled (bool, True): Is the generator voltage controlled?

Qmin (float, -9999): Minimum reactive power in MVAr

Qmax (float, 9999): Maximum reactive power in MVAr

Snom (float, 9999): Nominal apparent power in MVA

power_prof (DataFrame, None): Pandas DataFrame with the active power profile in MW

power_factor_prof (DataFrame, None): Pandas DataFrame with the power factor profile

vset_prof (DataFrame, None): Pandas DataFrame with the voltage setpoint profile in per unit

active (bool, True): Is the generator active?

p_min (float, 0.0): Minimum dispatchable power in MW

p_max (float, 9999): Maximum dispatchable power in MW

op_cost (float, 1.0): Operational cost in Eur (or other currency) per MW

Sbase (float, 100): Nominal apparent power in MVA

enabled_dispatch (bool, True): Is the generator enabled for OPF?

mttf (float, 0.0): Mean time to failure in hours

mttr (float, 0.0): Mean time to recovery in hours

copy()

Make a deep copy of this object :return: Copy of this object

get_json_dict(id, bus_dict)

Get json dictionary :param id: ID: Id for this object :param bus_dict: Dictionary of buses [object] -> ID :return: json-compatible dictionary

GridCal.Engine.Devices.load module
class GridCal.Engine.Devices.load.Load(name='Load', G=0.0, B=0.0, Ir=0.0, Ii=0.0, P=0.0, Q=0.0, cost=0.0, G_prof=None, B_prof=None, Ir_prof=None, Ii_prof=None, P_prof=None, Q_prof=None, active=True, mttf=0.0, mttr=0.0)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

The load object implements the so-called ZIP model, in which the load can be represented by a combination of power (P), current(I), and impedance (Z).

The sign convention is: Positive to act as a load, negative to act as a generator.

Arguments:

name (str, “Load”): Name of the load

G (float, 0.0): Conductance in equivalent MW

B (float, 0.0): Susceptance in equivalent MVAr

Ir (float, 0.0): Real current in equivalent MW

Ii (float, 0.0): Imaginary current in equivalent MVAr

P (float, 0.0): Active power in MW

Q (float, 0.0): Reactive power in MVAr

G_prof (DataFrame, None): Pandas DataFrame with the conductance profile in equivalent MW

B_prof (DataFrame, None): Pandas DataFrame with the susceptance profile in equivalent MVAr

Ir_prof (DataFrame, None): Pandas DataFrame with the real current profile in equivalent MW

Ii_prof (DataFrame, None): Pandas DataFrame with the imaginary current profile in equivalent MVAr

P_prof (DataFrame, None): Pandas DataFrame with the active power profile in equivalent MW

Q_prof (DataFrame, None): Pandas DataFrame with the reactive power profile in equivalent MVAr

active (bool, True): Is the load active?

mttf (float, 0.0): Mean time to failure in hours

mttr (float, 0.0): Mean time to recovery in hours

copy()
get_json_dict(id, bus_dict)

Get json dictionary :param id: ID: Id for this object :param bus_dict: Dictionary of buses [object] -> ID :return:

GridCal.Engine.Devices.measurement module
class GridCal.Engine.Devices.measurement.Measurement(value, uncertainty, mtype: GridCal.Engine.Devices.measurement.MeasurementType)

Bases: object

class GridCal.Engine.Devices.measurement.MeasurementType

Bases: enum.Enum

An enumeration.

Iflow = 'Current module flow'
Pflow = ('Active power flow',)
Pinj = ('Active power injection',)
Qflow = ('Reactive power flow',)
Qinj = ('Reactive power injection',)
Vmag = ('Voltage magnitude',)
GridCal.Engine.Devices.meta_devices module
class GridCal.Engine.Devices.meta_devices.DeviceType

Bases: enum.Enum

An enumeration.

BatteryDevice = 'Battery'
BranchDevice = 'Branch'
BusDevice = 'Bus'
GeneratorDevice = 'Generator'
LoadDevice = 'Load'
SequenceLineDevice = 'Sequence line'
ShuntDevice = 'Shunt'
StaticGeneratorDevice = 'Static Generator'
TowerDevice = 'Tower'
TransformerTypeDevice = 'Transformer type'
UnderGroundLineDevice = 'Underground line'
WireDevice = 'Wire'
class GridCal.Engine.Devices.meta_devices.EditableDevice(name, active, device_type: GridCal.Engine.Devices.meta_devices.DeviceType, editable_headers: Dict[str, GridCal.Engine.Devices.meta_devices.GCProp], non_editable_attributes: List[str], properties_with_profile: Dict[str, Optional[Any]])

Bases: object

create_profile(magnitude, index, arr=None, arr_in_pu=False)

Create power profile based on index :param magnitude: name of the property :param index: pandas time index :param arr: array of values to set :param arr_in_pu: is the array in per-unit?

create_profiles(index)

Create the load object default profiles Args: :param index: pandas time index

delete_profiles()

Delete the object profiles (set all to None)

ensure_profiles_exist(index)

It might be that when loading the GridCal Model has properties that the file has not. Those properties must be initialized as well :param index: Time series index (timestamps)

get_headers() → List[AnyStr]

Return a list of headers

get_save_data()

Return the data that matches the edit_headers :return:

resize_profiles(index, time_frame: GridCal.Engine.Devices.meta_devices.TimeFrame)

Resize the profiles in this object :param index: pandas time index :param time_frame: Time frame to use (Short term, Long term)

set_profile_values(t)

Set the profile values at t :param t: time index (integer)

class GridCal.Engine.Devices.meta_devices.GCProp(units, tpe, definition, profile_name='')

Bases: object

class GridCal.Engine.Devices.meta_devices.TimeFrame

Bases: enum.Enum

An enumeration.

Continuous = 'Continuous'
GridCal.Engine.Devices.sequence_line module
class GridCal.Engine.Devices.sequence_line.SequenceLineType(name='SequenceLine', rating=1, R=0, X=0, G=0, B=0, R0=0, X0=0, G0=0, B0=0, tpe=<BranchType.Line: ('line', )>)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

GridCal.Engine.Devices.shunt module
class GridCal.Engine.Devices.shunt.Shunt(name='shunt', G=0.0, B=0.0, G_prof=None, B_prof=None, active=True, mttf=0.0, mttr=0.0)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

Arguments:

name (str, “shunt”): Name of the shunt

G (float, 0.0): Conductance in MW at 1 p.u. voltage

B (float, 0.0): Susceptance in MW at 1 p.u. voltage

G_prof (DataFrame, None): Pandas DataFrame with the conductance profile in MW at 1 p.u. voltage

B_prof (DataFrame, None): Pandas DataFrame with the susceptance profile in MW at 1 p.u. voltage

active (bool, True): Is the shunt active?

mttf (float, 0.0): Mean time to failure in hours

mttr (float, 0.0): Mean time to recovery in hours

copy()

Copy of this object :return: a copy of this object

get_json_dict(id, bus_dict)

Get json dictionary :param id: ID: Id for this object :param bus_dict: Dictionary of buses [object] -> ID :return:

GridCal.Engine.Devices.static_generator module
class GridCal.Engine.Devices.static_generator.StaticGenerator(name='StaticGen', P=0.0, Q=0.0, P_prof=None, Q_prof=None, active=True, mttf=0.0, mttr=0.0)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

Arguments:

name (str, “StaticGen”): Name of the static generator

P (float, 0.0): Active power in MW

Q (float, 0.0): Reactive power in MVAr

P_prof (DataFrame, None): Pandas DataFrame with the active power profile in MW

Q_prof (DataFrame, None): Pandas DataFrame with the reactive power profile in MVAr

active (bool, True): Is the static generator active?

mttf (float, 0.0): Mean time to failure in hours

mttr (float, 0.0): Mean time to recovery in hours

copy()

Deep copy of this object :return:

get_json_dict(id, bus_dict)

Get json dictionary :param id: ID: Id for this object :param bus_dict: Dictionary of buses [object] -> ID :return:

GridCal.Engine.Devices.tower module
class GridCal.Engine.Devices.tower.Tower(parent=None, edit_callback=None, name='Tower', tpe=<BranchType.Branch: ('branch', )>)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

check(logger=[])

Check that the wires configuration make sense :return:

compute()

Compute the tower matrices :return:

compute_rating()

Compute the sum of the wires max current in A :return: max current iof the tower in A

get_save_data(dta_list=[])

store the tower data into dta_list in a SQL-like fashion to avoid 3D like structures :param dta_list: list to append the data to :return: nothing

get_save_headers()

Return the tower header + wire header :return:

get_wire_properties()

Get the wire properties in a list :return: list of properties (list of lists)

is_used(wire)
Parameters:wire
Returns:
plot(ax=None)

Plot wires position :param ax: Axis object

y_shunt()

positive sequence shunt admittance in S per unit of length

z_series()

positive sequence series impedance in Ohm per unit of length

GridCal.Engine.Devices.tower.abc_2_seq(mat)

Convert to sequence components Args:

mat:

Returns:

GridCal.Engine.Devices.tower.calc_y_matrix(wires: list, f=50, rho=100)

Impedance matrix :param wires: list of wire objects :param f: system frequency (Hz) :param rho: earth resistivity :return: 4 by 4 impedance matrix where the order of the phases is: N, A, B, C

GridCal.Engine.Devices.tower.calc_z_matrix(wires: list, f=50, rho=100)

Impedance matrix :param wires: list of wire objects :param f: system frequency (Hz) :param rho: earth resistivity :return: 4 by 4 impedance matrix where the order of the phases is: N, A, B, C

GridCal.Engine.Devices.tower.get_D_ij(xi, yi, xj, yj)

Distance module between the wire i and the image of the wire j :param xi: x position of the wire i :param yi: y position of the wire i :param xj: x position of the wire j :param yj: y position of the wire j :return: Distance module between the wire i and the image of the wire j

GridCal.Engine.Devices.tower.get_d_ij(xi, yi, xj, yj)

Distance module between wires :param xi: x position of the wire i :param yi: y position of the wire i :param xj: x position of the wire j :param yj: y position of the wire j :return: distance module

GridCal.Engine.Devices.tower.kron_reduction(mat, keep, embed)

Perform the Kron reduction :param mat: primitive matrix :param keep: indices to keep :param embed: indices to remove / embed :return:

GridCal.Engine.Devices.tower.wire_bundling(phases_set, primitive, phases_vector)

Algorithm to bundle wires per phase :param phases_set: set of phases (list with unique occurrences of each phase values, i.e. [0, 1, 2, 3]) :param primitive: Primitive matrix to reduce by bundling wires :param phases_vector: Vector that contains the phase of each wire :return: reduced primitive matrix, corresponding phases

GridCal.Engine.Devices.tower.z_ii(r_i, x_i, h_i, gmr_i, f, rho)

Self impedance Formula 4.3 from ATP-EMTP theory book :param r_i: wire resistance :param x_i: wire reactance :param h_i: wire vertical position (m) :param gmr_i: wire geometric mean radius (m) :param f: system frequency (Hz) :param rho: earth resistivity (Ohm / m^3) :return: self impedance in Ohm / m

GridCal.Engine.Devices.tower.z_ij(x_i, x_j, h_i, h_j, d_ij, f, rho)

Mutual impedance Formula 4.4 from ATP-EMTP theory book :param x_i: wire i horizontal position (m) :param x_j: wire j horizontal position (m) :param h_i: wire i vertical position (m) :param h_j: wire j vertical position (m) :param d_ij: Distance module between the wires i and j :param f: system frequency (Hz) :param rho: earth resistivity (Ohm / m^3) :return: mutual impedance in Ohm / m

GridCal.Engine.Devices.transformer module
class GridCal.Engine.Devices.transformer.TransformerType(hv_nominal_voltage=0, lv_nominal_voltage=0, nominal_power=0.001, copper_losses=0, iron_losses=0, no_load_current=0, short_circuit_voltage=0, gr_hv1=0.5, gx_hv1=0.5, name='TransformerType', tpe=<BranchType.Transformer: ('transformer', )>)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

Arguments:

hv_nominal_voltage (float, 0.0): Primary side nominal voltage in kV (tied to the Branch’s bus_from)

lv_nominal_voltage (float, 0.0): Secondary side nominal voltage in kV (tied to the Branch’s bus_to)

nominal_power (float, 0.0): Transformer nominal apparent power in MVA

copper_losses (float, 0.0): Copper losses in kW (also known as short circuit power)

iron_losses (float, 0.0): Iron losses in kW (also known as no-load power)

no_load_current (float, 0.0): No load current in %

short_circuit_voltage (float, 0.0): Short circuit voltage in %

gr_hv1 (float, 0.5): Resistive contribution to the primary side in per unit (at the Branch’s bus_from)

gx_hv1 (float, 0.5): Reactive contribution to the primary side in per unit (at the Branch’s bus_from)

name (str, “TransformerType”): Name of the type

tpe (BranchType, BranchType.Transformer): Device type enumeration

get_impedances()

Compute the branch parameters of a transformer from the short circuit test values.

Returns:

zs (complex): Series impedance in per unit

zsh (complex): Shunt impedance in per unit

GridCal.Engine.Devices.types module
class GridCal.Engine.Devices.types.BranchType

Bases: enum.Enum

An enumeration.

Branch = ('branch',)
Line = ('line',)
Reactance = ('reactance',)
Switch = 'switch'
Transformer = ('transformer',)
GridCal.Engine.Devices.underground_line module
class GridCal.Engine.Devices.underground_line.UndergroundLineType(name='UndergroundLine', rating=1, R=0, X=0, G=0, B=0, R0=0, X0=0, G0=0, B0=0)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

y_shunt()

positive sequence shunt admittance in S per unit of length

z_series()

positive sequence series impedance in Ohm per unit of length

GridCal.Engine.Devices.wire module
class GridCal.Engine.Devices.wire.Wire(name='', xpos=0, ypos=0, gmr=0.01, r=0.01, x=0.0, max_current=1, phase=0)

Bases: GridCal.Engine.Devices.meta_devices.EditableDevice

copy()

Copy of the wire :return:

class GridCal.Engine.Devices.wire.WiresCollection(parent=None)

Bases: PySide2.QtCore.QAbstractTableModel

add(wire: GridCal.Engine.Devices.wire.Wire)

Add wire :param wire: :return:

columnCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
data(self, index:PySide2.QtCore.QModelIndex, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
delete(index)

Delete wire :param index: :return:

flags(self, index:PySide2.QtCore.QModelIndex) → PySide2.QtCore.Qt.ItemFlags
headerData(self, section:int, orientation:PySide2.QtCore.Qt.Orientation, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
is_used(name)

checks if the name is used

parent(self) → PySide2.QtCore.QObject

parent(self, child:PySide2.QtCore.QModelIndex) -> PySide2.QtCore.QModelIndex

rowCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
setData(index, value, role=PySide2.QtCore.Qt.ItemDataRole.DisplayRole)

Set data by simple editor (whatever text) :param index: :param value: :param role:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Engine.IO package
Submodules
GridCal.Engine.IO.cim_parser module
class GridCal.Engine.IO.cim_parser.ACLineSegment(id, tpe)

Bases: GridCal.Engine.IO.cim_parser.GeneralContainer

class GridCal.Engine.IO.cim_parser.CIMCircuit

Bases: object

static check_type(xml, class_types, starter='<cim:', ender='</cim:')

Checks if we are starting an object of the predefined types :param xml: some text :param class_types: list of CIM types :param starter string to add prior to the class when opening an object :param ender string to add prior to a class when closing an object :return: start_recording, end_recording, the found type or None if no one was found

clear()

Clear the circuit

find_references(recognised={})

Replaces the references of the classes given :return:

parse_file(file_name, classes_=None)

Parse CIM file and add all the recognised objects :param file_name: file name or path :return:

class GridCal.Engine.IO.cim_parser.CIMExport(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit)

Bases: object

save(file_name)

Save XML CIM version of a grid Args:

file_name: file path
class GridCal.Engine.IO.cim_parser.CIMImport

Bases: object

add_node_terminal_relation(connectivity_node, terminal)

Add the relation between a Connectivity Node and a Terminal :param terminal: :param connectivity_node: :return:

any_in_dict(dict, keys)
get_elements(dict, keys)
load_cim_file(equipment_file, topology_file=None)

Load CIM file :param equipment_file: Main CIM file :param topology_file: Secondary CIM file that may contain the terminals-connectivity node relations

try_properties(dictionary, properties, defaults=None)
Parameters:
  • dictionary
  • properties
Returns:

class GridCal.Engine.IO.cim_parser.ConformLoad(id, tpe)

Bases: GridCal.Engine.IO.cim_parser.GeneralContainer

class GridCal.Engine.IO.cim_parser.GeneralContainer(id, tpe, resources=[], class_replacements={})

Bases: object

get_xml(level=0)

Returns an XML representation of the object Args:

level:

Returns:

merge(other)

Merge the properties of this object with another :param other: GeneralContainer instance

parse_line(line)

Parse xml line that eligibly belongs to this object :param line: xml text line

print()
class GridCal.Engine.IO.cim_parser.PowerTransformer(id, tpe)

Bases: GridCal.Engine.IO.cim_parser.GeneralContainer

class GridCal.Engine.IO.cim_parser.SynchronousMachine(id, tpe)

Bases: GridCal.Engine.IO.cim_parser.GeneralContainer

class GridCal.Engine.IO.cim_parser.Winding(id, tpe)

Bases: GridCal.Engine.IO.cim_parser.GeneralContainer

GridCal.Engine.IO.cim_parser.index_find(string, start, end)

version of substring that matches :param string: string :param start: string to start splitting :param end: string to end splitting :return: string between start and end

GridCal.Engine.IO.dgs_parser module

# This file is part of GridCal. # # GridCal is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # GridCal is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GridCal. If not, see <http://www.gnu.org/licenses/>.

GridCal.Engine.IO.dgs_parser.data_to_grid_object(data, pos_dict, codification='utf-8')

Turns the read data dictionary into a GridCal MultiCircuit object Args:

data: Dictionary of data read from a DGS file pos_dict: Dictionary of objects and their positions read from a DGS file

Returns: GridCal MultiCircuit object

GridCal.Engine.IO.dgs_parser.dgs_to_circuit(filename)
GridCal.Engine.IO.dgs_parser.read_DGS(filename)

Read a DigSilent Power Factory .dgs file and return a dictionary with the data Args:

filename: File name or path
Returns: Dictionary of data where the keys are the object types and the values
are the data of the objects of the key object type
GridCal.Engine.IO.dpx_parser module
GridCal.Engine.IO.dpx_parser.load_dpx(file_name, contraction_factor=1000)

Read DPX file :param file_name: file name :return: MultiCircuit

GridCal.Engine.IO.dpx_parser.read_dpx_data(file_name)

Read the DPX file into a structured dictionary :param file_name: :return:

GridCal.Engine.IO.dpx_parser.reformat(val)

Pick string and give it format :param val: string value :return: int, float or string

GridCal.Engine.IO.dpx_parser.repack(data_structures, logger=[], verbose=False)

Pack the values as DataFrames with headers where available :param data_structures: Raw data structures :param logger: logger (inherited) :return:

GridCal.Engine.IO.excel_interface module
GridCal.Engine.IO.excel_interface.check_names(names)

Check that the names are allowed :param names: :return:

GridCal.Engine.IO.excel_interface.create_data_frames(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit)

Pack the circuit information into tables (DataFrames) :param circuit: MultiCircuit instance :return: dictionary of DataFrames

GridCal.Engine.IO.excel_interface.get_allowed_sheets(circuit=<GridCal.Engine.Core.multi_circuit.MultiCircuit object>)
Parameters:circuit
Returns:
GridCal.Engine.IO.excel_interface.get_objects_dictionary(circuit=<GridCal.Engine.Core.multi_circuit.MultiCircuit object>)
Parameters:circuit
Returns:
GridCal.Engine.IO.excel_interface.interpret_excel_v3(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, data)

Interpret the file version 3 In this file version there are no complex numbers saved :param circuit: :param data: Dictionary with the excel file sheet labels and the corresponding DataFrame :return: Nothing, just applies the loaded data to this MultiCircuit instance

GridCal.Engine.IO.excel_interface.interprete_excel_v2(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, data)

Interpret the file version 2 :param circuit: :param data: Dictionary with the excel file sheet labels and the corresponding DataFrame :return: Nothing, just applies the loaded data to this MultiCircuit instance

GridCal.Engine.IO.excel_interface.load_from_xls(filename)

Loads the excel file content to a dictionary for parsing the data

GridCal.Engine.IO.excel_interface.save_excel(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, file_path)

Save the circuit information in excel format :param circuit: MultiCircuit instance :param file_path: path to the excel file :return: logger with information

GridCal.Engine.IO.file_handler module
class GridCal.Engine.IO.file_handler.FileOpen(file_name)

Bases: object

open(text_func=None, progress_func=None)

Load GridCal compatible file :param text_func: pointer to function that prints the names :param progress_func: pointer to function that prints the progress 0~100 :return: logger with information

class GridCal.Engine.IO.file_handler.FileOpenThread(file_name)

Bases: PySide2.QtCore.QThread

cancel()
done_signal = <PySide2.QtCore.Signal object>
progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

run the file open procedure

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Engine.IO.file_handler.FileSave(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, file_name, text_func=None, progress_func=None)

Bases: object

save()

Save the file in the corresponding format :return: logger with information

save_cim()

Save the circuit information in CIM format :return: logger with information

save_excel()

Save the circuit information in excel format :return: logger with information

save_json()

Save the circuit information in json format :return:logger with information

save_zip()

Save the circuit information in zip format :return: logger with information

class GridCal.Engine.IO.file_handler.FileSaveThread(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, file_name)

Bases: PySide2.QtCore.QThread

cancel()
done_signal = <PySide2.QtCore.Signal object>
progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

run the file save procedure @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Engine.IO.h5_interface module
GridCal.Engine.IO.h5_interface.load_dict_from_hdf5(filename)
Parameters:filename
Returns:
GridCal.Engine.IO.h5_interface.open_h5(file_path)
GridCal.Engine.IO.h5_interface.recursively_load_dict_contents_from_group(h5file, path)
Parameters:
  • h5file
  • path
Returns:

GridCal.Engine.IO.h5_interface.recursively_save_dict_contents_to_group(h5file, path, dic)
Parameters:
  • h5file
  • path
  • dic
Returns:

GridCal.Engine.IO.h5_interface.save_dict_to_hdf5(dic, filename)
Parameters:
  • dic
  • filename
Returns:

GridCal.Engine.IO.h5_interface.save_h5(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, file_path)

Save the circuit information in excel format :param circuit: MultiCircuit instance :param file_path: path to the excel file :return: logger with information

GridCal.Engine.IO.ipa_parser module
GridCal.Engine.IO.ipa_parser.load_iPA(file_name)
GridCal.Engine.IO.json_parser module
GridCal.Engine.IO.json_parser.parse_json(file_name)

Parse JSON file into Circuit :param file_name: :return: GridCal MultiCircuit

GridCal.Engine.IO.json_parser.parse_json_data(data)

Parse JSON structure into GridCal MultiCircuit :param data: JSON structure (list of dictionaries) :return: GridCal MultiCircuit

GridCal.Engine.IO.json_parser.save_json_file(file_path, circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit)

Save JSON file :param file_path: file path :param circuit: GridCal MultiCircuit element

GridCal.Engine.IO.matpower_branch_definitions module
GridCal.Engine.IO.matpower_branch_definitions.branch_headers = ['fbus', 'tbus', 'r', 'x', 'b', 'rateA', 'rateB', 'rateC', 'ratio', 'angle', 'status', 'angmin', 'angmax', 'Pf', 'Qf', 'Pt', 'Qt', 'Mu_Sf', 'Mu_St', 'Mu_AngMin', 'Mu_AngMax', 'Current', 'Loading', 'Losses', 'Original_index']

Defines constants for named column indices to dcline matrix.

Some examples of usage, after defining the constants using the line above, are:

ppc.dcline(4, c[‘BR_STATUS’]) = 0 take branch 4 out of service

The index, name and meaning of each column of the branch matrix is given below:

columns 1-17 must be included in input matrix (in case file)
1 F_BUS f, “from” bus number 2 T_BUS t, “to” bus number 3 BR_STATUS initial branch status, 1 - in service, 0 - out of service 4 PF MW flow at “from” bus (“from” -> “to”) 5 PT MW flow at “to” bus (“from” -> “to”) 6 QF MVAr injection at “from” bus (“from” -> “to”) 7 QT MVAr injection at “to” bus (“from” -> “to”) 8 VF voltage setpoint at “from” bus (p.u.) 9 VT voltage setpoint at “to” bus (p.u.)

10 PMIN lower limit on PF (MW flow at “from” end) 11 PMAX upper limit on PF (MW flow at “from” end) 12 QMINF lower limit on MVAr injection at “from” bus 13 QMAXF upper limit on MVAr injection at “from” bus 14 QMINT lower limit on MVAr injection at “to” bus 15 QMAXT upper limit on MVAr injection at “to” bus 16 LOSS0 constant term of linear loss function (MW) 17 LOSS1 linear term of linear loss function (MW/MW)

(loss = LOSS0 + LOSS1 * PF)

columns 18-23 are added to matrix after OPF solution they are typically not present in the input matrix

(assume OPF objective function has units, u)

18 MU_PMIN Kuhn-Tucker multiplier on lower flow lim at “from” bus (u/MW) 19 MU_PMAX Kuhn-Tucker multiplier on upper flow lim at “from” bus (u/MW) 20 MU_QMINF Kuhn-Tucker multiplier on lower VAr lim at “from” bus (u/MVAr) 21 MU_QMAXF Kuhn-Tucker multiplier on upper VAr lim at “from” bus (u/MVAr) 22 MU_QMINT Kuhn-Tucker multiplier on lower VAr lim at “to” bus (u/MVAr) 23 MU_QMAXT Kuhn-Tucker multiplier on upper VAr lim at “to” bus (u/MVAr)

@see: L{toggle_dcline}

GridCal.Engine.IO.matpower_branch_definitions.get_transformer_impedances(HV_nominal_voltage, LV_nominal_voltage, Nominal_power, Copper_losses, Iron_losses, No_load_current, Short_circuit_voltage, GR_hv1, GX_hv1)

Compute the branch parameters of a transformer from the short circuit test values @param HV_nominal_voltage: High voltage side nominal voltage (kV) @param LV_nominal_voltage: Low voltage side nominal voltage (kV) @param Nominal_power: Transformer nominal power (MVA) @param Copper_losses: Copper losses (kW) @param Iron_losses: Iron Losses (kW) @param No_load_current: No load current (%) @param Short_circuit_voltage: Short circuit voltage (%) @param GR_hv1: @param GX_hv1: @return:

leakage_impedance: Series impedance magnetizing_impedance: Shunt impedance
GridCal.Engine.IO.matpower_bus_definitions module
GridCal.Engine.IO.matpower_bus_definitions.bustypes(bus, gen, storage, Sbus, storage_dispatch_mode=<StorageDispatchMode.no_dispatch: (0, )>)

Builds index lists of each type of bus (C{REF}, C{PV}, C{PQ}).

Generators with “out-of-service” status are treated as L{PQ} buses with zero generation (regardless of C{Pg}/C{Qg} values in gen). Expects C{bus} and C{gen} have been converted to use internal consecutive bus numbering.

@param bus: bus data @param gen: generator data @return: index lists of each bus type

@author: Ray Zimmerman (PSERC Cornell)

GridCal.Engine.IO.matpower_gen_definitions module
GridCal.Engine.IO.matpower_gen_definitions.gen_headers = ['bus', 'Pg', 'Qg', 'Qmax', 'Qmin', 'Vg', 'mBase', 'status', 'Pmax', 'Pmin', 'Pc1', 'Pc2', 'Qc1min', 'Qc1max', 'Qc2min', 'Qc2max', 'ramp_agc', 'ramp_10', 'ramp_30', 'ramp_q', 'apf', 'MU_PMAX', 'MU_PMIN', 'MU_QMAX', 'MU_QMIN', 'Dispatchable', 'Fix_power']

Defines constants for named column indices to gencost matrix.

Some examples of usage, after defining the constants using the line above, are:

start = gencost[3, STARTUP]       # get startup cost of generator 4
gencost[2, [MODEL, NCOST:COST+2]] = [POLYNOMIAL, 2, 30, 0]
# set the cost of generator 2 to a linear function COST = 30 * Pg

The index, name and meaning of each column of the gencost matrix is given below:

columns 1-5
  1. C{MODEL} cost model, 1 - piecewise linear, 2 - polynomial
  2. C{STARTUP} startup cost in US dollars
  3. C{SHUTDOWN} shutdown cost in US dollars

4. C{NCOST} number of cost coefficients to follow for polynomial cost function, or number of data points for piecewise linear 5. C{COST} 1st column of cost parameters cost data defining total cost function For polynomial cost (highest order coeff first):

e.g. cn, ..., c1, c0

where the polynomial is C{c0 + c1*P + … + cn*P^n} For piecewise linear cost:

x0, y0, x1, y1, x2, y2, ...

where C{x0 < x1 < x2 < …} and the points C{(x0,y0), (x1,y1), (x2,y2), …} are the end- and break-points of the total cost function.

additional constants, used to assign/compare values in the C{MODEL} column
  1. C{PW_LINEAR} piecewise linear generator cost model
  2. C{POLYNOMIAL} polynomial generator cost model

@author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln

GridCal.Engine.IO.matpower_parser module

# This file is part of GridCal. # # GridCal is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # GridCal is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GridCal. If not, see <http://www.gnu.org/licenses/>.

GridCal.Engine.IO.matpower_parser.find_between(s, first, last)

Find sting between two sub-strings Args:

s: Main string first: first sub-string last: second sub-string

Example find_between(‘[Hello]’, ‘[‘, ‘]’) -> returns ‘Hello’ Returns:

String between the first and second sub-strings, if any was found otherwise returns an empty string
GridCal.Engine.IO.matpower_parser.interpret_data_v1(circuit, data)

Pass the loaded table-like data to the structures @param data: Data dictionary @return:

GridCal.Engine.IO.matpower_parser.parse_matpower_file(filename, export=False)
Args:
filename: export:

Returns:

GridCal.Engine.IO.matpower_parser.parse_matpower_file_old(filename, export=False)

Converts a MatPower file to GridCal basic dictionary @param filename: @return:

GridCal.Engine.IO.matpower_parser.txt2mat(txt, line_splitter=';', col_splitter='\t', to_float=True)
Args:
txt: line_splitter: col_splitter:

Returns:

GridCal.Engine.IO.matpower_storage_definitions module
class GridCal.Engine.IO.matpower_storage_definitions.StorageDispatchMode

Bases: enum.Enum

An enumeration.

dispatch_pv = 2
dispatch_vd = (1,)
no_dispatch = (0,)
GridCal.Engine.IO.psse_parser module
class GridCal.Engine.IO.psse_parser.PSSeArea(data, version, logger: list)

Bases: object

class GridCal.Engine.IO.psse_parser.PSSeBranch(data, version, logger: list)

Bases: object

get_object(psse_bus_dict, logger: list)

Return GridCal branch object Args:

psse_bus_dict: Dictionary that relates PSSe bus indices with GridCal Bus objects
Returns:
Gridcal Branch object
class GridCal.Engine.IO.psse_parser.PSSeBus(data, version, logger: list)

Bases: object

class GridCal.Engine.IO.psse_parser.PSSeGenerator(data, version, logger: list)

Bases: object

get_object(logger: list)

Return GridCal Load object Returns:

Gridcal Load object
class GridCal.Engine.IO.psse_parser.PSSeGrid(data)

Bases: object

BASFRQ = None

Case Identification Data Bus Data Load Data Fixed Bus Shunt Data Generator Data Non-Transformer Branch Data Transformer Data Area Interchange Data Two-Terminal DC Transmission Line Data Voltage Source Converter (VSC) DC Transmission Line Data Transformer Impedance Correction Tables Multi-Terminal DC Transmission Line Data Multi-Section Line Grouping Data Zone Data Interarea Transfer Data Owner Data FACTS Device Data Switched Shunt Data GNE Device Data Induction Machine Data Q Record

IC = None

Case Identification Data Bus Data Load Data Fixed Bus Shunt Data Generator Data Non-Transformer Branch Data Transformer Data Area Interchange Data Two-Terminal DC Transmission Line Data Voltage Source Converter (VSC) DC Transmission Line Data Transformer Impedance Correction Tables Multi-Terminal DC Transmission Line Data Multi-Section Line Grouping Data Zone Data Interarea Transfer Data Owner Data FACTS Device Data Switched Shunt Data GNE Device Data Induction Machine Data Q Record

NXFRAT = None

Case Identification Data Bus Data Load Data Fixed Bus Shunt Data Generator Data Non-Transformer Branch Data Transformer Data Area Interchange Data Two-Terminal DC Transmission Line Data Voltage Source Converter (VSC) DC Transmission Line Data Transformer Impedance Correction Tables Multi-Terminal DC Transmission Line Data Multi-Section Line Grouping Data Zone Data Interarea Transfer Data Owner Data FACTS Device Data Switched Shunt Data GNE Device Data Induction Machine Data Q Record

REV = None

Case Identification Data Bus Data Load Data Fixed Bus Shunt Data Generator Data Non-Transformer Branch Data Transformer Data Area Interchange Data Two-Terminal DC Transmission Line Data Voltage Source Converter (VSC) DC Transmission Line Data Transformer Impedance Correction Tables Multi-Terminal DC Transmission Line Data Multi-Section Line Grouping Data Zone Data Interarea Transfer Data Owner Data FACTS Device Data Switched Shunt Data GNE Device Data Induction Machine Data Q Record

SBASE = None

Case Identification Data Bus Data Load Data Fixed Bus Shunt Data Generator Data Non-Transformer Branch Data Transformer Data Area Interchange Data Two-Terminal DC Transmission Line Data Voltage Source Converter (VSC) DC Transmission Line Data Transformer Impedance Correction Tables Multi-Terminal DC Transmission Line Data Multi-Section Line Grouping Data Zone Data Interarea Transfer Data Owner Data FACTS Device Data Switched Shunt Data GNE Device Data Induction Machine Data Q Record

XFRRAT = None

Case Identification Data Bus Data Load Data Fixed Bus Shunt Data Generator Data Non-Transformer Branch Data Transformer Data Area Interchange Data Two-Terminal DC Transmission Line Data Voltage Source Converter (VSC) DC Transmission Line Data Transformer Impedance Correction Tables Multi-Terminal DC Transmission Line Data Multi-Section Line Grouping Data Zone Data Interarea Transfer Data Owner Data FACTS Device Data Switched Shunt Data GNE Device Data Induction Machine Data Q Record

get_circuit(logger: list)

Return GridCal circuit Returns:

class GridCal.Engine.IO.psse_parser.PSSeInductionMachine(data, version, logger: list)

Bases: object

get_object(logger: list)

Return GridCal Load object Returns:

Gridcal Load object
class GridCal.Engine.IO.psse_parser.PSSeLoad(data, version, logger: list)

Bases: object

get_object(bus: GridCal.Engine.Devices.bus.Bus, logger: list)

Return GridCal Load object Returns:

Gridcal Load object
class GridCal.Engine.IO.psse_parser.PSSeParser(file_name)

Bases: object

parse_psse() -> (<class 'GridCal.Engine.Core.multi_circuit.MultiCircuit'>, typing.List[typing.AnyStr])
Parser implemented according to:
  • POM section 4.1.1 Power Flow Raw Data File Contents (v.29)
  • POM section 5.2.1 (v.33)
  • POM section 5.2.1 (v.32)

Returns: MultiCircuit, List[str]

read_and_split()

Read the text file and split it into sections :return:

class GridCal.Engine.IO.psse_parser.PSSeShunt(data, version, logger: list)

Bases: object

get_object(bus: GridCal.Engine.Devices.bus.Bus, logger: list)

Return GridCal Load object Returns:

Gridcal Load object
class GridCal.Engine.IO.psse_parser.PSSeTransformer(data, version, logger: list)

Bases: object

NOMV2 = None

I,J,K,CKT,CW,CZ,CM,MAG1,MAG2,NMETR,’NAME’,STAT,O1,F1,…,O4,F4 R1-2,X1-2,SBASE1-2,R2-3,X2-3,SBASE2-3,R3-1,X3-1,SBASE3-1,VMSTAR,ANSTAR

WINDV1,NOMV1,ANG1,RATA1,RATB1,RATC1,COD,CONT,RMA,RMI,VMA,VMI,NTP,TAB,CR,CX

WINDV2,NOMV2,ANG2,RATA2,RATB2,RATC2

WINDV3,NOMV3,ANG3,RATA3,RATB3,RATC3

WINDV2 = None

I,J,K,CKT,CW,CZ,CM,MAG1,MAG2,NMETR,’NAME’,STAT,O1,F1,…,O4,F4 R1-2,X1-2,SBASE1-2,R2-3,X2-3,SBASE2-3,R3-1,X3-1,SBASE3-1,VMSTAR,ANSTAR

WINDV1,NOMV1,ANG1,RATA1,RATB1,RATC1,COD,CONT,RMA,RMI,VMA,VMI,NTP,TAB,CR,CX

WINDV2,NOMV2,ANG2,RATA2,RATB2,RATC2

WINDV3,NOMV3,ANG3,RATA3,RATB3,RATC3

get_object(psse_bus_dict, logger: list)

Return GridCal branch object Args:

psse_bus_dict: Dictionary that relates PSSe bus indices with GridCal Bus objects
Returns:
Gridcal Branch object
windings = None

I,J,K,CKT,CW,CZ,CM,MAG1,MAG2,NMETR,’NAME’,STAT,O1,F1,…,O4,F4,VECGRP R1-2,X1-2,SBASE1-2,R2-3,X2-3,SBASE2-3,R3-1,X3-1,SBASE3-1,VMSTAR,ANSTAR WINDV1,NOMV1,ANG1,RATA1,RATB1,RATC1,COD1,CONT1,RMA1,RMI1,VMA1,VMI1,NTP1,TAB1,CR1,CX1,CNXA1 WINDV2,NOMV2,ANG2,RATA2,RATB2,RATC2,COD2,CONT2,RMA2,RMI2,VMA2,VMI2,NTP2,TAB2,CR2,CX2,CNXA2 WINDV3,NOMV3,ANG3,RATA3,RATB3,RATC3,COD3,CONT3,RMA3,RMI3,VMA3,VMI3,NTP3,TAB3,CR3,CX3,CNXA3

class GridCal.Engine.IO.psse_parser.PSSeTwoTerminalDCLine(data, version, logger: list)

Bases: object

get_object(psse_bus_dict, logger: list)

GEt equivalent object :param psse_bus_dict: :param logger: :return:

class GridCal.Engine.IO.psse_parser.PSSeVscDCLine(data, version, logger: list)

Bases: object

get_object(psse_bus_dict, logger: list)

GEt equivalent object :param psse_bus_dict: :param logger: :return:

class GridCal.Engine.IO.psse_parser.PSSeZone(data, version, logger: list)

Bases: object

GridCal.Engine.IO.psse_parser.interpret_line(line, splitter=', ')

Split text into arguments and parse each of them to an appropriate format (int, float or string) Args:

line: splitter:

Returns: list of arguments

GridCal.Engine.IO.psse_parser.process_raw_file(root_folder, destination_folder, fname)

process a .raw file :param root_folder: folder :param fname: file name (in the folder) :return: nothing

GridCal.Engine.IO.sqlite_interface module
GridCal.Engine.IO.sqlite_interface.open_sqlite(file_path)
GridCal.Engine.IO.sqlite_interface.save_sqlite(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, file_path)

Save the circuit information in excel format :param circuit: MultiCircuit instance :param file_path: path to the excel file :return: logger with information

GridCal.Engine.IO.zip_interface module
GridCal.Engine.IO.zip_interface.open_data_frames_from_zip(file_name_zip, text_func=None, progress_func=None)

Open the csv files from a zip file :param file_name_zip: name of the zip file :param text_func: pointer to function that prints the names :param progress_func: pointer to function that prints the progress 0~100 :return: list of DataFrames

GridCal.Engine.IO.zip_interface.save_data_frames_to_zip(dfs: Dict[str, pandas.core.frame.DataFrame], filename_zip='file.zip', text_func=None, progress_func=None)

Save a list of DataFrames to a zip file without saving to disk the csv files :param dfs: dictionary of pandas dataFrames {name: DataFrame} :param filename_zip: file name where to save all :param text_func: pointer to function that prints the names :param progress_func: pointer to function that prints the progress 0~100

GridCal.Engine.Replacements package
Submodules
GridCal.Engine.Replacements.mpiserve module
GridCal.Engine.Replacements.poap_controller module

Modified to fit GridCal’s purposes when using PySOT

class GridCal.Engine.Replacements.poap_controller.BaseWorkerThread(controller)

Bases: threading.Thread

Worker base class for use with the thread controller.

The BaseWorkerThread class has a run routine that actually handles the worker event loop, and a set of helper routines for dispatching messages into the worker event loop (usually from the controller) and dispatching messages to the controller (usually from the worker).

add_message(message)

Send message to be executed at the controller.

add_worker()

Add worker back to the work queue.

eval(record)

Start evaluation.

Args:
record: Function evaluation record
finish_cancelled(record)

Finish recording cancelled on a record and add ourselves back.

finish_killed(record)

Finish recording killed on a record and add ourselves back.

finish_success(record, value)

Finish successful work on a record and add ourselves back.

handle_eval(record)

Process an eval request.

handle_kill(record)

Process a kill request

handle_terminate()

Handle any cleanup on a terminate request

kill(record)

Send kill message to worker.

Args:
record: Function evaluation record
run()

Run requests as long as we get them.

terminate()

Send termination message to worker.

NB: if the worker is not running in a daemon thread, a call to the terminate method only returns after the the thread has terminated.

class GridCal.Engine.Replacements.poap_controller.BasicWorkerThread(controller, objective)

Bases: GridCal.Engine.Replacements.poap_controller.BaseWorkerThread

Basic worker for use with the thread controller.

The BasicWorkerThread calls a Python objective function when asked to do an evaluation. This is concurrent, but only results in parallelism if the objective function implementation itself allows parallelism (e.g. because it communicates with an external entity via a pipe, socket, or whatever).

handle_eval(record)

Process an eval request.

class GridCal.Engine.Replacements.poap_controller.Controller

Bases: object

Base class for controller.

Attributes:
strategy: Strategy for choosing optimization actions. fevals: Database of function evaluations. feval_callbacks: List of callbacks to execute on new eval record term_callbacks: List of callbacks to execute on termination
add_feval_callback(callback)

Add a callback for notification on new fevals.

add_term_callback(callback)

Add a callback for cleanup on termination.

add_timer(timeout, callback)

Add a task to be executed after a timeout (e.g. for monitoring).

Args:
timeout: Time to wait before execution callback: Function to call when timeout elapses
best_point(merit=None, filter=None)

Return the best point in the database satisfying some criterion.

Args:
merit: Function to minimize (default is r.value) filter: Predicate to use for filtering candidates
Returns:
Record minimizing merit() and satisfying filter(); or None if nothing satisfies the filter
call_term_callbacks()

Call termination callbacks.

can_work()

Return whether we can currently perform work.

new_feval(params, extra_args=None)

Add a function evaluation record to the database.

In addition to adding the record with status ‘pending’, we run the feval_callbacks on the new record.

Args:
params: Parameters to the objective function
Returns:
New EvalRecord object
ping()

Tell controller to consult strategies when possible (if asynchronous)

remove_feval_callback(callback)

Remove a callback from the feval callback list.

remove_term_callback(callback)

Remove a callback from the term callback list.

class GridCal.Engine.Replacements.poap_controller.Monitor(controller)

Bases: object

Monitor events observed by a controller.

The monitor object provides hooks to monitor the progress of an optimization run by a controller. Users should inherit from Monitor and add custom version of the methods

on_new_feval(self, record) on_update(self, record) on_complete(self, record) on_kill(self, record) on_cancel(self, record) on_terminate(self)
on_cancel(record)

Handle record cancelled

on_complete(record)

Handle feval completion

on_kill(record)

Handle record killed

on_new_feval(record)

Handle new function evaluation request.

on_terminate()

Handle termination.

on_update(record)

Handle feval update.

class GridCal.Engine.Replacements.poap_controller.ProcessWorkerThread(controller)

Bases: GridCal.Engine.Replacements.poap_controller.BaseWorkerThread

Subprocess worker for use with the thread controller.

The ProcessWorkerThread is meant for use as a base class. Implementations that inherit from ProcessWorkerThread should define a handle_eval method that sets the process field so that it can be interrupted if needed. This allows use of blocking communication primitives while at the same time allowing interruption.

kill(record)

Send kill message.

terminate()

Send termination message.

class GridCal.Engine.Replacements.poap_controller.ScriptedController

Bases: GridCal.Engine.Replacements.poap_controller.Controller

Run a test script of actions from the controller.

The ScriptedController is meant to test that a strategy adheres to an expected sequence of proposed actions in a given scenario.

Attributes:
strategy: Strategy for choosing optimization actions. fevals: Database of function evaluations
accept_eval(args=None, pred=None, skip=False)

Assert next proposal is an eval, which we accept.

Args:
args: expected evaluation args (if not None) pred: test predicate to run on proposal (if not None) skip: if True, skip over all None proposals
Returns:
proposal record
accept_kill(r=None, skip=False)

Assert next proposal is a kill, which we accept.

Args:
r: record to be killed. skip: if True, skip over all None proposals
accept_terminate(skip=False)

Assert next proposal is a kill, which we accept.

Args:
skip: if True, skip over all None proposals
add_timer(timeout, callback)

Add timer.

can_work()

Return True if worker available.

check_eval(proposal, args=None, pred=None)

Check whether a proposal is an expected eval proposal.

Args:
proposal: proposal to check args: expected evaluation args (if not None) pred: test predicate to run on proposal (if not None)
check_kill(proposal, r=None)

Check whether a proposal is an expected kill proposal.

Args:
proposal: proposal to check r: record to be killed (or None if no check)
check_terminate(proposal)

Check whether a proposal is an expected terminate proposal.

Args:
proposal: proposal to check
no_proposal()

Assert that next proposed action is None.

proposal(skip=False)

Return strategy proposal.

Args:
skip: if True, skip over all None proposals
reject_eval(args=None, pred=None, skip=False)

Assert next proposal is an eval, which we reject.

Args:
args: expected evaluation args (if not None) pred: test predicate to run on proposal (if not None) skip: if True, skip over all None proposals
reject_kill(r=None, skip=False)

Assert next proposal is a kill, which we reject.

Args:
r: record to be killed. skip: if True, skip over all None proposals
reject_terminate(skip=False)

Assert next proposal is a terminate, which we reject.

Args:
skip: if True, skip over all None proposals
set_worker(v)

Set worker availability status.

terminate()

Terminate the script.

class GridCal.Engine.Replacements.poap_controller.SerialController(objective, skip=False)

Bases: GridCal.Engine.Replacements.poap_controller.Controller

Serial optimization controller.

Attributes:
strategy: Strategy for choosing optimization actions. objective: Objective function fevals: Database of function evaluations skip: if True, skip over “None” proposals
run(merit=None, filter=None, reraise=True, stop_at=False, stop_value=0)

Run the optimization and return the best value.

Args:

merit: Function to minimize (default is r.value) filter: Predicate to use for filtering candidates reraise: Flag indicating whether exceptions in the

objective function evaluations should be re-raised, terminating the optimization.
Returns:
Record minimizing merit() and satisfying filter(); or None if nothing satisfies the filter
class GridCal.Engine.Replacements.poap_controller.SimTeamController(objective, delay, workers)

Bases: GridCal.Engine.Replacements.poap_controller.Controller

Simulated parallel optimization controller.

Run events in simulated time. If two events are scheduled at the same time, we prioritize by when the event was added to the queue.

Attributes:
strategy: Strategy for choosing optimization actions. objective: Objective function delay: Time delay function workers: Number of workers available fevals: Database of function evaluations time: Current simulated time time_events: Time-stamped event heap
add_timer(timeout, event)

Add a task to be executed after a timeout (e.g. for monitoring).

Args:
timeout: Time to wait before execution callback: Function to call when timeout elapses
advance_time()

Advance time to the next event.

can_work()

Check if there are workers available.

kill_work(proposal)

Submit a kill event.

run(merit=None, filter=None)

Run the optimization and return the best value.

Args:
merit: Function to minimize (default is r.value) filter: Predicate to use for filtering candidates
Returns:
Record minimizing merit() and satisfying filter(); or None if nothing satisfies the filter
submit_work(proposal)

Submit a work event.

class GridCal.Engine.Replacements.poap_controller.ThreadController

Bases: GridCal.Engine.Replacements.poap_controller.Controller

Thread-based optimization controller.

The optimizer dispatches work to a queue of workers. Each worker has methods of the form

worker.eval(record) worker.kill(record)

These methods are asynchronous: they start a function evaluation or termination, but do not necessarily complete it. The worker must respond to eval requests, but may ignore kill requests. On eval requests, the worker should either attempt the evaluation or mark the record as killed. The worker sends status updates back to the controller in terms of lambdas (executed at the controller) that update the relevant record. When the worker becomes available again, it should use add_worker to add itself back to the queue.

Attributes:
strategy: Strategy for choosing optimization actions. fevals: Database of function evaluations workers: Queue of available worker threads messages: Queue of messages from workers
add_message(message=None)

Queue up a message.

Args:
message: callback function with no arguments or None (default)
if None, a dummy message is queued to ping the controller
add_timer(timeout, callback)

Add a task to be executed after a timeout (e.g. for monitoring).

Args:
timeout: Time to wait before execution callback: Function to call when timeout elapses
add_worker(worker)

Add a worker and queue a ‘wake-up’ message.

Args:
worker: a worker thread object
can_work()

Claim we can work if a worker is available.

launch_worker(worker, daemon=False)

Launch and take ownership of a new worker thread.

Args:

worker: a worker thread object daemon: if True, the worker is launched in a daemon thread

(default is False)
ping()

Tell controller to consult strategies when possible

run(merit=None, filter=None)

Run the optimization and return the best value.

Args:
merit: Function to minimize (default is r.value) filter: Predicate to use for filtering candidates
Returns:
Record minimizing merit() and satisfying filter(); or None if nothing satisfies the filter
GridCal.Engine.Replacements.strategy module
class GridCal.Engine.Replacements.strategy.AddArgStrategy(strategy, extra_args=None)

Bases: GridCal.Engine.Replacements.strategy.BaseStrategy

Add extra arguments to evaluation proposals.

Decorates evaluation proposals with extra arguments. These may reflect termination criteria, advice to workers about which of several methods to use, etc. The extra arguments can be static (via the extra_args attribute), or they may be returned from the get_extra_args function, which child classes can override. The strategy overwrites any existing list of extra arguments, so it is probably a mistake to compose strategies in a way that involves multiple objects of this type.

Attributes:
strategy: Parent strategy extra_args: Extra arguments to be added
add_extra_args(record)

Add any extra arguments to an eval record.

Args:
record: Record to be decorated
on_reply_accept(proposal)

Handle proposal acceptance.

propose_action()
class GridCal.Engine.Replacements.strategy.BaseStrategy

Bases: object

Base strategy class.

The BaseStrategy class provides some support for the basic callback flow common to most strategies: handlers are called when an evaluation is accepted or rejected; and if an evaluation proposal is accepted, the record is decorated so that an on_update handler is called for subsequent updates.

Note that not all strategies follow this pattern – the only requirement for a strategy is that it implement propose_action.

decorate_proposal(proposal)

Add a callback to an existing proposal (for nested strategies).

on_complete(record)

Process completed record.

on_kill(record)

Process killed or cancelled record.

on_kill_reply(proposal)

Default handling of kill proposal.

on_kill_reply_accept(proposal)

Handle proposal acceptance.

on_kill_reply_reject(proposal)

Handle proposal rejection.

on_reply(proposal)

Default handling of eval proposal.

on_reply_accept(proposal)

Handle proposal acceptance.

on_reply_reject(proposal)

Handle proposal rejection.

on_terminate_reply(proposal)

Default handling of terminate proposal.

on_terminate_reply_accept(proposal)

Handle proposal acceptance.

on_terminate_reply_reject(proposal)

Handle proposal rejection.

on_update(record)

Process update.

propose_eval(*args)

Generate an eval proposal with a callback to on_reply.

propose_kill(r)

Generate a kill proposal with a callback to on_reply_kill.

propose_terminate()

Generate a terminate proposal with a callback to on_terminate.

class GridCal.Engine.Replacements.strategy.ChaosMonkeyStrategy(controller, strategy, mtbf=1)

Bases: object

Randomly kill running function evaluations.

The ChaosMonkeyStrategy kills function evaluations at random from among all active function evaluations. Attacks are associated with a Poisson process where the mean time between failures is specified at startup. Useful mostly for testing the resilience of strategies to premature termination of their evaluations. The controller may decide to ignore the proposed kill actions, in which case the monkey’s attacks are ultimately futile.

on_new_feval(record)

On every feval, add a callback.

on_timer()
on_update(record)

On every completion, remove from list

propose_action()
class GridCal.Engine.Replacements.strategy.CheckWorkerStrategy(controller, strategy)

Bases: object

Preemptively kill eval proposals when there are no workers.

A strategy like the fixed sampler is simple-minded, and will propose a function evaluation even if the controller has no workers available to carry it out. This wrapper strategy intercepts proposals from a wrapped strategy like the fixed sampler, and only submits evaluation proposals if workers are available.

propose_action()

Generate filtered action proposal.

class GridCal.Engine.Replacements.strategy.CoroutineBatchStrategy(coroutine, rvalue=<function CoroutineBatchStrategy.<lambda>>)

Bases: GridCal.Engine.Replacements.strategy.BaseStrategy

Event-driven to synchronous parallel adapter using coroutines.

The coroutine strategy runs a synchronous parallel optimization algorithm in a Python coroutine, with which the strategy communicates via send/yield directives. The optimization coroutine yields batches of parameters (as lists) at which it would like function values; the strategy then requests the function evaluation and returns the records associated with those function evaluations when all have been completed.

NB: Within a given batch, function evaluations are attempted in the reverse of the order in which they are specified. This should make no difference for most things (the batch is not assumed to have any specific order), but it is significant for testing.

Attributes:
coroutine: Optimizer coroutine retry: Retry strategy for in-flight actions results: Completion records to be returned to the coroutine
on_complete(record)

Return or re-request on completion or cancellation.

on_reply_accept(proposal)

If proposal accepted, wait for result.

propose_action()

If we have a pending request, propose it.

start_batch(xs)

Start evaluation of a batch of points.

class GridCal.Engine.Replacements.strategy.CoroutineStrategy(coroutine, rvalue=<function CoroutineStrategy.<lambda>>)

Bases: GridCal.Engine.Replacements.strategy.BaseStrategy

Event-driven to serial adapter using coroutines.

The coroutine strategy runs a standard sequential optimization algorithm in a Python coroutine, with which the strategy communicates via send/yield directives. The optimization coroutine yields the parameters at which it would like function values; the strategy then requests the function evaluation and returns the value with a send command when it becomes available.

Attributes:
coroutine: Optimizer coroutine rvalue: Function to map records to values retry: Retry manager
on_complete(record)

Return point to coroutine

propose_action()

If we have a pending request, propose it.

class GridCal.Engine.Replacements.strategy.EvalRecord(params, extra_args=None, status='pending')

Bases: object

Represent progress of a function evaluation.

An evaluation record includes the evaluation point, status of the evaluation, and a list of callbacks. The status may be pending, running, killed (deliberately), cancelled (due to a crash or failure) or completed. Once the function evaluation is completed, the value is stored as an attribute in the evaluation record. Other information, such as gradients, Hessians, or function values used to compute constraints, may also be stored in the record.

The evaluation record callbacks are triggered on any relevant event, including not only status changes but also intermediate results. The nature of these intermediate results (lower bounds, initial estimates, etc) is somewhat application-dependent.

NB: Callbacks that handle record updates should be insensitive to order: it is important not to modify attributes that might be used by later callbacks (e.g. the proposal.accepted flag).

Attributes:
params: Evaluation point for the function status: Status of the evaluation (pending, running, killed, completed) value: Return value (if completed) callbacks: Functions to call on status updates
add_callback(callback)

Add a callback for update events.

cancel()

Change status to ‘cancelled’ and execute callbacks.

complete(value)

Change status to ‘completed’ and execute callbacks.

is_cancelled

Check whether evaluation was cancelled

is_completed

Check for successful completion of evaluation

is_done

Check whether the status indicates the evaluation is finished.

is_killed

Check whether evaluation is killed

is_pending

Check if status is pending.

is_running

Check if status is running.

kill()

Change status to ‘killed’ and execute callbacks.

remove_callback(callback)

Remove a callback subscriber.

running()

Change status to ‘running’ and execute callbacks.

status
update(**kwargs)

Update fields and execute callbacks.

update_dict(dict)

Update fields and execute callbacks.

Args:
dict: Dictionary of attributes to set on the record
class GridCal.Engine.Replacements.strategy.FixedSampleStrategy(points)

Bases: GridCal.Engine.Replacements.strategy.BaseStrategy

Sample at a fixed set of points.

The fixed sampling strategy is appropriate for any non-adaptive sampling scheme. Since the strategy is non-adaptive, we can employ as many available workers as we have points to process. We keep trying any evaluation that fails, and suggest termination only when all evaluations are complete. The points in the experimental design can be provided as any iterable object (e.g. a list or a generator function). One can use a generator for an infinite sequence if the fixed sampling strategy is used in combination with a strategy that provides a termination criterion.

propose_action()

Propose an action based on outstanding points.

class GridCal.Engine.Replacements.strategy.InputStrategy(controller, strategy)

Bases: GridCal.Engine.Replacements.strategy.BaseStrategy

Insert requests from the outside world (e.g. from a GUI).

eval(params, retry=True)

Request a new function evaluation

kill(record, retry=False)

Request a function evaluation be killed

propose_action()
terminate(retry=True)

Request termination of the optimization

class GridCal.Engine.Replacements.strategy.MaxEvalStrategy(controller, max_counter)

Bases: object

Recommend termination of the iteration after some number of evals.

Recommends termination after observing some number of completed function evaluations. We allow more than the requisite number of evaluations to be started, since there’s always the possibility that one of our outstanding evaluations won’t finish.

Attributes:
counter: Number of completed evals max_counter: Max number of evals
on_new_feval(record)

On every feval, add a callback.

on_update(record)

On every completion, increment the counter.

propose_action()

Propose termination once the eval counter is high enough.

class GridCal.Engine.Replacements.strategy.MultiStartStrategy(controller, strategies)

Bases: object

Merge several strategies by taking the first valid eval proposal.

This strategy is similar to the SimpleMergedStrategy, except that we only terminate once all the worker strategies have voted to terminate.

Attributes:
controller: Controller object used to determine whether we can eval strategies: Prioritized list of strategies
propose_action()

Go through strategies in order and choose the first valid action. Terminate iff all workers vote to terminate.

class GridCal.Engine.Replacements.strategy.PromiseStrategy(rvalue=<function PromiseStrategy.<lambda>>, block=True)

Bases: GridCal.Engine.Replacements.strategy.BaseStrategy

Provides a promise-based asynchronous evaluation interface.

A promise (aka a future) is a common concurrent programming abstraction. A caller requests an asynchronous evaluation, and the call returns immediately with a promise object. The caller can check the promise object to see if it has a value ready, or wait for the value to become ready. The callee can set the value when it is ready.

Attributes:
proposalq: Queue of proposed actions caused by optimizer. valueq: Queue of function values from proposed actions. rvalue: Function to extract return value from a record block: Flag whether to block on proposal pop
class Promise(valueq)

Bases: object

Evaluation promise.

Properties:
value: Promised value. Block on read if not ready.
ready()

Check whether the value is ready (at consumer)

value

Wait on the value (at consumer)

blocking_eval(*args)

Request a function evaluation and block until done.

blocking_evals(xs)

Request a list of function evaluations.

on_complete(record)

Send the value to the consumer via the promise.

on_kill(record)

Re-submit the proposal with the same promise.

on_reply_accept(proposal)

Make sure we copy over the promise to the feval record.

on_reply_reject(proposal)

Re-submit the proposal with the same promise.

on_terminate()

Throw an exception at the consumer if still running on termination

on_terminate_reply_reject(proposal)

Re-submit the termination proposal.

promise_eval(*args)

Request a function evaluation and return a promise object.

propose_action()

Provide proposals from the queue.

terminate()

Request termination.

class GridCal.Engine.Replacements.strategy.Proposal(action, *args)

Bases: object

Represent a proposed action.

We currently recognize three types of proposals: evaluation requests (“eval”), requests to stop an evaluation (“kill”), and requests to terminate the optimization (“terminate”). When an evaluation proposal is accepted, the controller adds an evaluation record to the proposal before notifying subscribers.

NB: Callbacks that handle proposal accept/reject should be insensitive to order: it is important not to modify attributes that might be used by later callbacks (e.g. the proposal.accepted flag).

Attributes:
action: String describing the action args: Tuple of arguments to pass to the action accepted: Flag set on accept/reject decision callbacks: Functions to call on accept/reject of action record: Evaluation record for accepted evaluation proposals
accept()

Mark the action as accepted and execute callbacks.

add_callback(callback)

Add a callback subscriber to the action.

copy()

Make a copy of the proposal.

reject()

Mark action as rejected and execute callbacks.

remove_callback(callback)

Remove a callback subscriber.

class GridCal.Engine.Replacements.strategy.RetryStrategy

Bases: GridCal.Engine.Replacements.strategy.BaseStrategy

Retry strategy class.

The RetryStrategy class manages a queue of proposals to be retried, either because they were rejected or because they correspond to a function evaluation that was killed.

If a proposal is submitted to a retry strategy and it has already been marked with the .retry attribute, we do not attempt to manage retries, as another object is assumed to have taken responsibility for retrying the proposal on failure. This prevents us from having redundant retries.

When a proposal is submitted to the retry class, we make a copy (the proposal draft) for safe-keeping. If at any point it is necessary to resubmit, we submit the draft copy to the retry class. The draft will have any modifications or callbacks that were added before the proposal reached this strategy, and none of those that were added afterward (including those added afterward by the RetryStrategy itself). The intent is that retried proposals inserted into the strategy hierarchy at the place where they were recorded should require no special treatment to avoid adding multiple copies of callbacks (for example).

Attributes:
proposals: Queue of outstanding proposals num_eval_pending: Number of pending evaluations num_eval_running: Number of running evaluations num_eval_outstanding: Number of outstanding evaluations
empty()

Check if the queue is empty

get()

Pop a proposal from the queue.

num_eval_outstanding

Number of outstanding function evaluations.

on_complete(record)

Clean up after completed eval+retry

on_kill(record)

Resubmit proposal for killed or cancelled eval+retry

on_kill_reply_reject(proposal)

Resubmit rejected kill+retry proposal

on_reply_accept(proposal)

Process accepted eval+retry proposal

on_reply_reject(proposal)

Resubmit rejected eval+retry proposal

on_terminate_reply_reject(proposal)

Resubmit rejected termination+retry proposal

put(proposal)

Put a non-retry proposal in the queue.

rput(proposal)

Put a retry proposal in the queue.

exception GridCal.Engine.Replacements.strategy.RunTerminatedException

Bases: Exception

class GridCal.Engine.Replacements.strategy.SimpleMergedStrategy(controller, strategies)

Bases: object

Merge several strategies by taking the first valid proposal from them.

The simplest combination strategy is to keep a list of several possible strategies in some priority order, query them for proposals in priority order, and pass on the first plausible proposal to the controller.

Attributes:
controller: Controller object used to determine whether we can eval strategies: Prioritized list of strategies
propose_action()

Go through strategies in order and choose the first valid action.

class GridCal.Engine.Replacements.strategy.ThreadStrategy(controller, optimizer, rvalue=<function ThreadStrategy.<lambda>>)

Bases: GridCal.Engine.Replacements.strategy.PromiseStrategy

Event-driven to serial adapter using threads.

The thread strategy runs a standard sequential optimization algorithm in a separate thread of control, with which the strategy communicates via promises. The optimizer thread intercepts function evaluation requests and completion and turns them into proposals for the strategy, which it places in a proposal queue. The optimizer then waits for the requested values (if any) to appear in the reply queue.

Attributes:
optimizer: Optimizer function (takes objective as an argument) proposalq: Queue of proposed actions caused by optimizer. valueq: Queue of function values from proposed actions. thread: Thread in which the optimizer runs. proposal: Proposal that the strategy is currently requesting. rvalue: Function mapping records to values
class OptimizerThread(strategy, optimizer)

Bases: threading.Thread

run()

Method representing the thread’s activity.

You may override this method in a subclass. The standard run() method invokes the callable object passed to the object’s constructor as the target argument, if any, with sequential and keyword arguments taken from the args and kwargs arguments, respectively.

on_terminate()

Throw an exception at the consumer if still running on termination

GridCal.Engine.Replacements.tcpserve module
class GridCal.Engine.Replacements.tcpserve.ProcessSocketWorker(sockname, retries=0)

Bases: GridCal.Engine.Replacements.tcpserve.SocketWorker

Socket worker that runs an evaluation in a subprocess

The ProcessSocketWorker is a base class for simulations that run a simulation in an external subprocess. This class provides functionality just to allow graceful termination of the external simulations.

Attributes:
process: Handle for external subprocess
kill(record_id)

Kill a function evaluation

kill_process()

Kill the child process

terminate()

Terminate the worker

class GridCal.Engine.Replacements.tcpserve.SimpleSocketWorker(objective, sockname, retries=0)

Bases: GridCal.Engine.Replacements.tcpserve.SocketWorker

Simple socket worker that runs a local objective function

The SimpleSocketWorker is a socket worker that runs a local Python function and returns the result. It is probably mostly useful for testing – the ProcessSocketWorker is a better option for external simulations.

eval(record_id, params)

Evaluate the function and send back a result.

If the function evaluation crashes, we send back a ‘cancel’ request for the record. If, on the other hand, there is a problem with calling send, we probably want to let the worker error out.

Args:
record_id: Feval record identifier used by server/controller params: Parameters sent to the function to be evaluated
class GridCal.Engine.Replacements.tcpserve.SocketWorker(sockname, retries=0)

Bases: object

Base class for workers that connect to SocketServer interface

The socket server interface is a server to which workers can connect. The socket worker is the client to that interface. It connects to a given TCP/IP port, then attempts to do work on the controller’s behalf.

Attributes:
running: True if the socket is active sock: Worker TCP socket
eval(record_id, params)

Compute a function value

kill(record_id)

Kill a function evaluation

marshall(*args)

Marshall data to wire format

run(loop=True)

Main loop

send(*args)

Send a message to the controller

terminate()

Terminate the worker

unmarshall(data)

Convert data from wire format back to Python tuple

class GridCal.Engine.Replacements.tcpserve.SocketWorkerHandler(request, client_address, server)

Bases: socketserver.BaseRequestHandler

Manage a remote worker for a thread controller.

The SocketWorkerHandler is a request handler for incoming workers. It implements the socketserver request handler interface, and also the worker interface expected by the ThreadController.

eval(record)

Send an evaluation request to remote worker

handle()

Main event loop called from SocketServer

kill(record)

Send a kill request to a remote worker

terminate()

Send a termination request to a remote worker

class GridCal.Engine.Replacements.tcpserve.ThreadedTCPServer(sockname=('localhost', 0), strategy=None, handlers={})

Bases: socketserver.ThreadingMixIn, socketserver.TCPServer, object

SocketServer interface for workers to connect to controller.

The socket server interface lets workers connect to a given TCP/IP port and exchange updates with the controller.

The server sends messages of the form

(‘eval’, record_id, args, extra_args) (‘eval’, record_id, args) (‘kill’, record_id) (‘terminate’)

The default messages received are

(‘running’, record_id) (‘kill’, record_id) (‘cancel’, record_id) (‘complete’, record_id, value)

The set of handlers can also be extended with a dictionary of named callbacks to be invoked whenever a record update comes in. For example, to set a lower bound field, we might use the handler

def set_lb(rec, value):
rec.lb = value

handlers = {‘lb’ : set_lb }

This is useful for adding new types of updates without mucking around in the EvalRecord implementation.

Attributes:
controller: ThreadController that manages the optimization handlers: dictionary of specialized message handlers strategy: redirects to the controller strategy
marshall(*args)

Convert an argument list to wire format.

run(merit=<function ThreadedTCPServer.<lambda>>, filter=None)
sockname
strategy
unmarshall(data)

Convert wire format back to Python arg list.

GridCal.Engine.Simulations package
GridCal.Engine.Simulations.maketrans()

Return a translation table usable for str.translate().

If there is only one argument, it must be a dictionary mapping Unicode ordinals (integers) or characters to Unicode ordinals, strings or None. Character keys will be then converted to ordinals. If there are two arguments, they must be strings of equal length, and in the resulting dictionary, each character in x will be mapped to the character at the same position in y. If there is a third argument, it must be a string, whose characters will be mapped to None in the result.

Subpackages
GridCal.Engine.Simulations.ContinuationPowerFlow package
Submodules
GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow module
class GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.VCParametrization

Bases: enum.Enum

An enumeration.

ArcLength = 'Arc Length'
Natural = 'Natural'
PseudoArcLength = 'Pseudo Arc Length'
class GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.VCStopAt

Bases: enum.Enum

An enumeration.

Full = 'Full curve'
Nose = 'Nose'
GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.continuation_nr(Ybus, Ibus_base, Ibus_target, Sbus_base, Sbus_target, V, pv, pq, step, approximation_order: GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.VCParametrization, adapt_step, step_min, step_max, error_tol=0.001, tol=1e-06, max_it=20, stop_at=<VCStopAt.Nose: 'Nose'>, verbose=False, call_back_fx=None)

Runs a full AC continuation power flow using a normalized tangent predictor and selected approximation_order scheme. :param Ybus: Admittance matrix :param Ibus_base: :param Ibus_target: :param Sbus_base: Power array of the base solvable case :param Sbus_target: Power array of the case to be solved :param V: Voltage array of the base solved case :param pv: Array of pv indices :param pq: Array of pq indices :param step: Adaptation step :param approximation_order: order of the approximation {Natural, Arc, Pseudo arc} :param adapt_step: use adaptive step size? :param step_min: minimum step size :param step_max: maximum step size :param error_tol: Error tolerance :param tol: Solutions tolerance :param max_it: Maximum iterations :param stop_at: Value of Lambda to stop at. It can be a number or {‘NOSE’, ‘FULL’} :param verbose: Display additional intermediate information? :param call_back_fx: Function to call on every iteration passing the lambda parameter :return: Voltage_series: List of all the voltage solutions from the base to the target

Lambda_series: Lambda values used in the continuation
Ported from MATPOWER

Copyright (c) 1996-2015 by Power System Engineering Research Center (PSERC) by Ray Zimmerman, PSERC Cornell, Shrirang Abhyankar, Argonne National Laboratory, and Alexander Flueck, IIT

$Id: runcpf.m 2644 2015-03-11 19:34:22Z ray $

This file is part of MATPOWER. Covered by the 3-clause BSD License (see LICENSE file for details). See http://www.pserc.cornell.edu/matpower/ for more info.

GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.corrector(Ybus, Ibus, Sbus, V0, pv, pq, lam0, Sxfr, Vprv, lamprv, z, step, parametrization, tol, max_it, verbose)

Solves the corrector step of a continuation power flow using a full Newton method with selected parametrization scheme.

solves for bus voltages and lambda given the full system admittance matrix (for all buses), the complex bus power injection vector (for all buses), the initial vector of complex bus voltages, and column vectors with the lists of bus indices for the swing bus, PV buses, and PQ buses, respectively. The bus voltage vector contains the set point for generator (including ref bus) buses, and the reference angle of the swing bus, as well as an initial guess for remaining magnitudes and angles.

Uses default options if this parameter is not given. Returns the final complex voltages, a flag which indicates whether it converged or not, the number of iterations performed, and the final lambda.
Parameters:
  • Ybus – Admittance matrix (CSC sparse)
  • Ibus – Bus current injections
  • Sbus – Bus power injections
  • V0 – Bus initial voltages
  • pv – list of pv nodes
  • pq – list of pq nodes
  • lam0 – initial value of lambda (loading parameter)
  • Sxfr – [delP+j*delQ] transfer/loading vector for all buses
  • Vprv – final complex V corrector solution from previous continuation step
  • lamprv – final lambda corrector solution from previous continuation step
  • z – normalized predictor for all buses
  • step – continuation step size
  • parametrization
  • tol
  • max_it
  • verbose
Returns:

V, CONVERGED, I, LAM

GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.corrector_new(Ybus, Ibus, Sbus, V0, pv, pq, lam0, Sxfr, Vprv, lamprv, z, step, parametrization, tol, max_it, verbose, max_it_internal=10)

Solves the corrector step of a continuation power flow using a full Newton method with selected parametrization scheme.

solves for bus voltages and lambda given the full system admittance matrix (for all buses), the complex bus power injection vector (for all buses), the initial vector of complex bus voltages, and column vectors with the lists of bus indices for the swing bus, PV buses, and PQ buses, respectively. The bus voltage vector contains the set point for generator (including ref bus) buses, and the reference angle of the swing bus, as well as an initial guess for remaining magnitudes and angles.

Uses default options if this parameter is not given. Returns the final complex voltages, a flag which indicates whether it converged or not, the number of iterations performed, and the final lambda.
Parameters:
  • Ybus – Admittance matrix (CSC sparse)
  • Ibus – Bus current injections
  • Sbus – Bus power injections
  • V0 – Bus initial voltages
  • pv – list of pv nodes
  • pq – list of pq nodes
  • lam0 – initial value of lambda (loading parameter)
  • Sxfr – [delP+j*delQ] transfer/loading vector for all buses
  • Vprv – final complex V corrector solution from previous continuation step
  • lamprv – final lambda corrector solution from previous continuation step
  • z – normalized predictor for all buses
  • step – continuation step size
  • parametrization
  • tol
  • max_it
  • verbose
Returns:

V, CONVERGED, I, LAM

GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.cpf_p(parametrization: GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.VCParametrization, step, z, V, lam, V_prev, lamprv, pv, pq, pvpq)

Computes the value of the Current Parametrization Function :param parametrization: Value of option (1: Natural, 2:Arc-length, 3: pseudo arc-length) :param step: continuation step size :param z: normalized tangent prediction vector from previous step :param V: complex bus voltage vector at current solution :param lam: scalar lambda value at current solution :param V_prev: complex bus voltage vector at previous solution :param lamprv: scalar lambda value at previous solution :param pv: vector of indices of PV buses :param pq: vector of indices of PQ buses :param pvpq: vector of indices of PQ and PV buses :return: value of the parametrization function at the current point

GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.cpf_p_jac(parametrization: GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.VCParametrization, z, V, lam, Vprv, lamprv, pv, pq, pvpq)

Computes partial derivatives of Current Parametrization Function (CPF). :param parametrization: :param z: normalized tangent prediction vector from previous step :param V: complex bus voltage vector at current solution :param lam: scalar lambda value at current solution :param Vprv: complex bus voltage vector at previous solution :param lamprv: scalar lambda value at previous solution :param pv: vector of indices of PV buses :param pq: vector of indices of PQ buses :param pvpq: vector of indices of PQ and PV buses :return: partial of parametrization function w.r.t. voltages

partial of parametrization function w.r.t. lambda
GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.predictor(V, Ibus, lam, Ybus, Sxfr, pv, pq, step, z, Vprv, lamprv, parametrization: GridCal.Engine.Simulations.ContinuationPowerFlow.continuation_power_flow.VCParametrization)

Computes a prediction (approximation) to the next solution of the continuation power flow using a normalized tangent predictor. :param V: complex bus voltage vector at current solution :param Ibus: :param lam: scalar lambda value at current solution :param Ybus: complex bus admittance matrix :param Sxfr: complex vector of scheduled transfers (difference between bus injections in base and target cases) :param pv: vector of indices of PV buses :param pq: vector of indices of PQ buses :param step: continuation step length :param z: normalized tangent prediction vector from previous step :param Vprv: complex bus voltage vector at previous solution :param lamprv: scalar lambda value at previous solution :param parametrization: Value of cpf parametrization option. :return: V0 : predicted complex bus voltage vector

LAM0 : predicted lambda continuation parameter Z : the normalized tangent prediction vector
GridCal.Engine.Simulations.ContinuationPowerFlow.voltage_collapse_driver module
class GridCal.Engine.Simulations.ContinuationPowerFlow.voltage_collapse_driver.VoltageCollapse(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.ContinuationPowerFlow.voltage_collapse_driver.VoltageCollapseOptions, inputs: GridCal.Engine.Simulations.ContinuationPowerFlow.voltage_collapse_driver.VoltageCollapseInput)

Bases: PySide2.QtCore.QThread

cancel()
done_signal = <PySide2.QtCore.Signal object>
get_steps()

List of steps

progress_callback(l)

Send progress report :param l: lambda value :return: None

progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

run the voltage collapse simulation @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Engine.Simulations.ContinuationPowerFlow.voltage_collapse_driver.VoltageCollapseInput(Sbase, Vbase, Starget)

Bases: object

class GridCal.Engine.Simulations.ContinuationPowerFlow.voltage_collapse_driver.VoltageCollapseOptions(step=0.01, approximation_order=<VCParametrization.Natural: 'Natural'>, adapt_step=True, step_min=0.0001, step_max=0.2, error_tol=0.001, tol=1e-06, max_it=20, stop_at=<VCStopAt.Nose: 'Nose'>, verbose=False)

Bases: object

class GridCal.Engine.Simulations.ContinuationPowerFlow.voltage_collapse_driver.VoltageCollapseResults(nbus, nbr)

Bases: object

apply_from_island(voltage_collapse_res, pf_res: GridCal.Engine.Simulations.PowerFlow.power_flow_results.PowerFlowResults, bus_original_idx, branch_original_idx, nbus_full)

Apply the results of an island to this VoltageCollapseResults instance :param voltage_collapse_res: VoltageCollapseResults instance of the island :param pf_res: Power flow results instance :param bus_original_idx: indices of the buses in the complete grid :param branch_original_idx: indices of the branches in the complete grid :param nbus_full: total number of buses in the complete grid

get_results_dict()

Returns a dictionary with the results sorted in a dictionary :return: dictionary of 2D numpy arrays (probably of complex numbers)

plot(result_type=<ResultTypes.BusVoltage: ('Bus voltage', <DeviceType.BusDevice: 'Bus'>)>, ax=None, indices=None, names=None)

Plot the results :param result_type: :param ax: :param indices: :param names: :return:

save(fname)

Export as pickle

GridCal.Engine.Simulations.Dynamics package
Submodules
GridCal.Engine.Simulations.Dynamics.dynamic_modules module
class GridCal.Engine.Simulations.Dynamics.dynamic_modules.DiffEqSolver

Bases: enum.Enum

An enumeration.

EULER = (1,)
RUNGE_KUTTA = 2
class GridCal.Engine.Simulations.Dynamics.dynamic_modules.DoubleCageAsynchronousMotor(H, Rr, Xr, Rs, Xs, a, Xm, Rr2, Xr2, MVA_Rating, Sbase, bus_idx, fn=50)

Bases: object

Double Cage Asynchronous Machine Model

Model equations based on section 15.2.5 of: Milano, F., “Power System Modelling and Scripting”, Springer-Verlag, 2010

calc_currents(vt)

Calculate machine current injections (in network reference frame)

calc_tmech(s)

Calculate mechanical load torque (with a quadratic load model)

check_diffs()

Check if differential equations are zero (on initialisation)

get_yg()

Get the generator admittance :return: shunt admittance

initialise(vt0, S0)

Initialise machine signals and states based on load flow voltage and complex power injection NOTE: currently only initialised at standstill

solve_step(Edp, Eqp, Edpp, Eqpp)

Solve machine differential equations for the next stage in the integration step

class GridCal.Engine.Simulations.Dynamics.dynamic_modules.DynamicModels

Bases: enum.Enum

An enumeration.

AsynchronousDoubleCageMotor = 6
AsynchronousSingleCageMotor = (5,)
ExternalGrid = (4,)
NoModel = (0,)
SynchronousGeneratorOrder4 = (1,)
SynchronousGeneratorOrder6 = (2,)
VoltageSourceConverter = (3,)
class GridCal.Engine.Simulations.Dynamics.dynamic_modules.ExternalGrid(Xdp, H, fn, bus_idx)

Bases: object

External Grid Model Class Grid is modelled as a constant voltage behind a transient reactance and two differential equations representing the swing equations.

calc_currents(vt)

Solve grid current injections (in network reference frame)

function(Eqp, Edp, omega)

Solve machine differential equations for the next stage in the integration step

get_yg()

Return the shunt admittance Returns:

initialise(vt0, S0)

Initialise grid emf based on load flow voltage and grid current injection

class GridCal.Engine.Simulations.Dynamics.dynamic_modules.SingleCageAsynchronousMotor(H, Rr, Xr, Rs, Xs, a, Xm, MVA_Rating, Sbase, bus_idx, fn=50)

Bases: object

Single Cage Asynchronous Motor Model

Model equations based on section 15.2.4 of: Milano, F., “Power System Modelling and Scripting”, Springer-Verlag, 2010

calc_currents(vt)

Calculate machine current injections (in network reference frame)

calc_tmech(s)

Calculate mechanical load torque (with a quadratic load model) :param s: slip :return:

check_diffs()

Check if differential equations are zero (on initialisation)

function(Edp, Eqp)

Solve machine differential equations for the next stage in the integration step

get_yg()

Get the generator admittance :return: shunt admittance

initialise(vt0, S0)

Initialise machine signals and states based on load flow voltage and complex power injection NOTE: currently only initialised at standstill

class GridCal.Engine.Simulations.Dynamics.dynamic_modules.SynchronousMachineOrder4(H, Ra, Xd, Xdp, Xdpp, Xq, Xqp, Xqpp, Td0p, Tq0p, base_mva, Sbase, bus_idx, fn=50, speed_volt=False, solver=<DiffEqSolver.RUNGE_KUTTA: 2>)

Bases: object

4th Order Synchronous Machine Model https://wiki.openelectrical.org/index.php?title=Synchronous_Machine_Models#4th_Order_.28Two-Axis.29_Model Copyright (C) 2014-2015 Julius Susanto. All rights reserved.

typical values: Ra = 0.0 Xa = 0.0 Xd = 1.68 Xq = 1.61 Xdp = 0.32 Xqp = 0.32 Xdpp = 0.2 Xqpp = 0.2 Td0p = 5.5 Tq0p = 4.60375 Td0pp = 0.0575 Tq0pp = 0.0575 H = 2

calc_currents(Vbus, Ibus)

Calculate machine current injections (in network reference frame) :param vt: complex initial voltage :return:

check_diffs()

Check if differential equations are zero (on initialisation)

function(h, Eqp, Edp, omega)

Compute the magnitude’s derivatives :param Eqp: :param Edp: :param omega: :return:

get_yg()

Get the generator admittance :return: shunt admittance

initialise(vt0, S0)

Initialise machine signals and states based on load flow voltage and complex power injection :param vt0: complex initial voltage :param S0: complex initial power :return:

solve(h)

Solve using Runge-Kutta Args:

h: step size

Returns: self.Eqp, self.Edp, self.omega, self.delta

class GridCal.Engine.Simulations.Dynamics.dynamic_modules.SynchronousMachineOrder6SauerPai(H, Ra, Xa, Xd, Xdp, Xdpp, Xq, Xqp, Xqpp, Td0p, Tq0p, Td0pp, Tq0pp, base_mva, Sbase, bus_idx, fn=50, speed_volt=False)

Bases: object

PYPOWER-Dynamics 6th Order Synchronous Machine Model Based on Sauer-Pai model Sauer, P.W., Pai, M. A., “Power System Dynamics and Stability”, Stipes Publishing, 2006

calc_currents(vt)

Calculate machine current injections (in network reference frame)

check_diffs()

Check if differential equations are zero (on initialisation)

function(Eqp, Edp, omega)

Solve machine differential equations for the next stage in the integration step :param Eqp: :param Edp: :param omega: :return:

get_yg()

Get the generator admittance :return: shunt admittance

initialise(vt0, S0)

Initialise machine signals and states based on load flow voltage and complex power injection

class GridCal.Engine.Simulations.Dynamics.dynamic_modules.TransientStabilityEvents

Bases: object

add(t, evt_type, obj, param)

Add elements :param t: time in seconds :param evt_type: event type :param obj: object selected :param param: extra parameters

remove_at(i)

Remove the elements at a position :param i: index

class GridCal.Engine.Simulations.Dynamics.dynamic_modules.TransientStabilityResults

Bases: object

plot(result_type, ax=None, indices=None, names=None, LINEWIDTH=2)

Plot the results :param result_type: :param ax: :param indices: :param names: :return:

class GridCal.Engine.Simulations.Dynamics.dynamic_modules.VoltageSourceConverterAverage(Rl, Xl, fn, bus_idx)

Bases: object

Voltage Source Converter Model Class Average model of a VSC in voltage-control mode (i.e. controlled voltage source behind an impedance). Copyright (C) 2014-2015 Julius Susanto. All rights reserved.

calc_currents(vt)

Solve grid current injections (in network reference frame) :param vt: complex voltage :return:

function(h, d)

Solve machine differential equations for the next stage in the integration step :param h: solve step in seconds

get_yg()

Get the generator admittance :return: shunt admittance

initialise(vt0, S0)

Initialise converter emf based on load flow voltage and grid current injection :param vt0: complex voltage :param S0: complex power

GridCal.Engine.Simulations.Dynamics.dynamic_modules.dynamic_simulation(n, Vbus, Sbus, Ybus, Sbase, fBase, t_sim, h, dynamic_devices=[], bus_indices=[], callback=None)

Dynamic transient simulation of a power system Args:

n: number of nodes Vbus: Ybus: Sbase: fBase: base frequency i.e. 50Hz t_sim: h: dynamic_devices: objects of each machine bus_indices:

Returns:

GridCal.Engine.Simulations.Dynamics.transient_stability_driver module
class GridCal.Engine.Simulations.Dynamics.transient_stability_driver.TransientStability(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.Dynamics.transient_stability_driver.TransientStabilityOptions, pf_res: GridCal.Engine.Simulations.PowerFlow.power_flow_results.PowerFlowResults)

Bases: PySide2.QtCore.QThread

done_signal = <PySide2.QtCore.Signal object>
get_steps()

Get time steps list of strings

progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

Run transient stability

staticMetaObject = <PySide2.QtCore.QMetaObject object>
status(txt, progress)

Emit status :param txt: text to display :param progress: progress 0-100

class GridCal.Engine.Simulations.Dynamics.transient_stability_driver.TransientStabilityOptions(h=0.001, t_sim=15, max_err=0.0001, max_iter=25)

Bases: object

GridCal.Engine.Simulations.OPF package
GridCal.Engine.Simulations.OPF.maketrans()

Return a translation table usable for str.translate().

If there is only one argument, it must be a dictionary mapping Unicode ordinals (integers) or characters to Unicode ordinals, strings or None. Character keys will be then converted to ordinals. If there are two arguments, they must be strings of equal length, and in the resulting dictionary, each character in x will be mapped to the character at the same position in y. If there is a third argument, it must be a string, whose characters will be mapped to None in the result.

Submodules
GridCal.Engine.Simulations.OPF.ac_opf module

This file implements a DC-OPF for time series That means that solves the OPF problem for a complete time series at once

class GridCal.Engine.Simulations.OPF.ac_opf.AcOpf(numerical_circuit: GridCal.Engine.Core.numerical_circuit.NumericalCircuit)

Bases: GridCal.Engine.Simulations.OPF.opf_templates.Opf

formulate()

Formulate the AC OPF time series in the non-sequential fashion (all to the solver at once) :return: PuLP Problem instance

get_voltage()

return the complex voltages (time, device) :return: 2D array

GridCal.Engine.Simulations.OPF.ac_opf.add_ac_nodal_power_balance(numerical_circuit, problem: GridCal.ThirdParty.pulp.pulp.LpProblem, dvm, dva, P, Q)

Add the nodal power balance :param numerical_circuit: NumericalCircuit instance :param problem: LpProblem instance :param dva: Voltage angles LpVars (n, nt) :param P: Power injection at the buses LpVars (n, nt) :return: Nothing, the restrictions are added to the problem

GridCal.Engine.Simulations.OPF.ac_opf.add_branch_loading_restriction(problem: GridCal.ThirdParty.pulp.pulp.LpProblem, theta_f, theta_t, Bseries, Fmax, FSlack1, FSlack2)

Add the branch loading restrictions :param problem: LpProblem instance :param theta_f: voltage angles at the “from” side of the branches (m, nt) :param theta_t: voltage angles at the “to” side of the branches (m, nt) :param Bseries: Array of branch susceptances (m) :param Fmax: Array of branch ratings (m, nt) :param FSlack1: Array of branch loading slack variables in the from-to sense :param FSlack2: Array of branch loading slack variables in the to-from sense :return: Nothing

GridCal.Engine.Simulations.OPF.ac_opf.get_objective_function(Pg, Pb, LSlack, FSlack1, FSlack2, cost_g, cost_b, cost_l, cost_br)

Add the objective function to the problem :param Pg: generator LpVars (ng, nt) :param Pb: batteries LpVars (nb, nt) :param LSlack: Load slack LpVars (nl, nt) :param FSlack1: Branch overload slack1 (m, nt) :param FSlack2: Branch overload slack2 (m, nt) :param cost_g: Cost of the generators (ng, nt) :param cost_b: Cost of the batteries (nb, nt) :param cost_l: Cost of the loss of load (nl, nt) :param cost_br: Cost of the overload (m, nt) :return: Nothing, just assign the objective function

GridCal.Engine.Simulations.OPF.ac_opf.get_power_injections(C_bus_gen, Pg, C_bus_bat, Pb, C_bus_load, PlSlack, QlSlack, Pl, Ql)

Create the power injections per bus :param C_bus_gen: Bus-Generators sparse connectivity matrix (n, ng) :param Pg: generator LpVars (ng, nt) :param C_bus_bat: Bus-Batteries sparse connectivity matrix (n, nb) :param Pb: Batteries LpVars (nb, nt) :param C_bus_load: Bus-Load sparse connectivity matrix (n, nl) :param PlSlack: Load (real) slack LpVars (nl, nt) :param QlSlack: Load (imag) slack LpVars (nl, nt) :param Pl: Load values (nl, nt) :return: Power injection at the buses (n, nt)

GridCal.Engine.Simulations.OPF.ac_opf.maketrans()

Return a translation table usable for str.translate().

If there is only one argument, it must be a dictionary mapping Unicode ordinals (integers) or characters to Unicode ordinals, strings or None. Character keys will be then converted to ordinals. If there are two arguments, they must be strings of equal length, and in the resulting dictionary, each character in x will be mapped to the character at the same position in y. If there is a third argument, it must be a string, whose characters will be mapped to None in the result.

GridCal.Engine.Simulations.OPF.ac_opf.set_fix_generation(problem, Pg, P_fix, enabled_for_dispatch)

Set the generation fixed at the non dispatchable generators :param problem: LP problem instance :param Pg: Array of generation variables :param P_fix: Array of fixed generation values :param enabled_for_dispatch: array of “enables” for dispatching generators :return: Nothing

GridCal.Engine.Simulations.OPF.dc_opf module

This file implements a DC-OPF for time series That means that solves the OPF problem for a complete time series at once

class GridCal.Engine.Simulations.OPF.dc_opf.DcOpf(numerical_circuit: GridCal.Engine.Core.numerical_circuit.NumericalCircuit)

Bases: GridCal.Engine.Simulations.OPF.opf_templates.Opf

formulate()

Formulate the AC OPF time series in the non-sequential fashion (all to the solver at once) :return: PuLP Problem instance

GridCal.Engine.Simulations.OPF.dc_opf.add_branch_loading_restriction(problem: GridCal.ThirdParty.pulp.pulp.LpProblem, theta_f, theta_t, Bseries, rating, FSlack1, FSlack2)

Add the branch loading restrictions :param problem: LpProblem instance :param theta_f: voltage angles at the “from” side of the branches (m) :param theta_t: voltage angles at the “to” side of the branches (m) :param Bseries: Array of branch susceptances (m) :param rating: Array of branch ratings (m) :param FSlack1: Array of branch loading slack variables in the from-to sense :param FSlack2: Array of branch loading slack variables in the to-from sense :return: load_f and load_t arrays (LP+float)

GridCal.Engine.Simulations.OPF.dc_opf.add_dc_nodal_power_balance(numerical_circuit, problem: GridCal.ThirdParty.pulp.pulp.LpProblem, theta, P)

Add the nodal power balance :param numerical_circuit: NumericalCircuit instance :param problem: LpProblem instance :param theta: Voltage angles LpVars (n, nt) :param P: Power injection at the buses LpVars (n, nt) :return: Nothing, the restrictions are added to the problem

GridCal.Engine.Simulations.OPF.dc_opf.add_objective_function(Pg, Pb, LSlack, FSlack1, FSlack2, cost_g, cost_b, cost_l, cost_br)

Add the objective function to the problem :param Pg: generator LpVars (ng, nt) :param Pb: batteries LpVars (nb, nt) :param LSlack: Load slack LpVars (nl, nt) :param FSlack1: Branch overload slack1 (m, nt) :param FSlack2: Branch overload slack2 (m, nt) :param cost_g: Cost of the generators (ng, nt) :param cost_b: Cost of the batteries (nb, nt) :param cost_l: Cost of the loss of load (nl, nt) :param cost_br: Cost of the overload (m, nt) :return: Nothing, just assign the objective function

GridCal.Engine.Simulations.OPF.dc_opf.get_power_injections(C_bus_gen, Pg, C_bus_bat, Pb, C_bus_load, LSlack, Pl)

Create the power injections per bus :param C_bus_gen: Bus-Generators sparse connectivity matrix (n, ng) :param Pg: generator LpVars (ng, nt) :param C_bus_bat: Bus-Batteries sparse connectivity matrix (n, nb) :param Pb: Batteries LpVars (nb, nt) :param C_bus_load: Bus-Load sparse connectivity matrix (n, nl) :param LSlack: Load slack LpVars (nl, nt) :param Pl: Load values (nl, nt) :return: Power injection at the buses (n, nt)

GridCal.Engine.Simulations.OPF.dc_opf.maketrans()

Return a translation table usable for str.translate().

If there is only one argument, it must be a dictionary mapping Unicode ordinals (integers) or characters to Unicode ordinals, strings or None. Character keys will be then converted to ordinals. If there are two arguments, they must be strings of equal length, and in the resulting dictionary, each character in x will be mapped to the character at the same position in y. If there is a third argument, it must be a string, whose characters will be mapped to None in the result.

GridCal.Engine.Simulations.OPF.dc_opf.set_fix_generation(problem, Pg, P_fix, enabled_for_dispatch)

Set the generation fixed at the non dispatchable generators :param problem: LP problem instance :param Pg: Array of generation variables :param P_fix: Array of fixed generation values :param enabled_for_dispatch: array of “enables” for dispatching generators :return: Nothing

GridCal.Engine.Simulations.OPF.nelder_mead_opf module
class GridCal.Engine.Simulations.OPF.nelder_mead_opf.AcOpfNelderMead(multi_circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions, verbose=False, break_at_value=True, good_enough_value=0)

Bases: object

build_solvers()
f_obj(x)
get_batteries_power()
get_branch_flows()
get_controlled_generation()
get_generation_shedding()
get_load_shedding()
get_loading()
get_overloads()
get_voltage()
set_default_state()

Set the default loading state

set_state(load_power, static_gen_power, controlled_gen_power, storage_power, Emin=None, Emax=None, E=None, dt=0, force_batteries_to_charge=False, bat_idx=None, battery_loading_pu=0.01)
set_state_at(t, force_batteries_to_charge=False, bat_idx=None, battery_loading_pu=0.01, Emin=None, Emax=None, E=None, dt=0)

Set the problem state at at time index Args:

t: force_batteries_to_charge: bat_idx: battery_loading_pu: Emin: Emax: E: dt:

Returns:

solve(verbose=False)
GridCal.Engine.Simulations.OPF.nelder_mead_opf.nelder_mead(objective_function, x_start, step=0.1, no_improve_thr=1e-05, no_improv_break=10, max_iter=0, alpha=1.0, gamma=2.0, rho=-0.5, sigma=0.5, break_at_value=False, good_enough_value=0.0, init_res=[], callback=None)
Args:
objective_function: function to optimize, must return a scalar score
and operate over a numpy array of the same dimensions as x_start

x_start: initial position LB: lower bounds (can be None) UB: upper bound (can be None) step: look-around radius in initial step no_improve_thr, no_improv_break: break after no_improv_break iterations with an improvement lower than no_improv_thr max_iter: always break after this number of iterations. Set it to 0 to loop indefinitely. alpha: gamma: rho: sigma:

Returns: xsol, fsol

GridCal.Engine.Simulations.OPF.nelder_mead_opf.nelder_mead_test()
GridCal.Engine.Simulations.OPF.opf_driver module
class GridCal.Engine.Simulations.OPF.opf_driver.OptimalPowerFlow(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.OPF.opf_driver.OptimalPowerFlowOptions)

Bases: PySide2.QtCore.QThread

cancel()
done_signal = <PySide2.QtCore.Signal object>
get_steps()

Get time steps list of strings

opf()

Run a power flow for every circuit @return: OptimalPowerFlowResults object

progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()
Returns:
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Engine.Simulations.OPF.opf_driver.OptimalPowerFlowOptions(verbose=False, solver: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.SolverType = <SolverType.DC_OPF: 'Linear DC OPF'>, grouping: GridCal.Engine.basic_structures.TimeGrouping = <TimeGrouping.NoGrouping: 'No grouping'>, mip_solver=<MIPSolvers.CBC: 'CBC'>, faster_less_accurate=False, power_flow_options=None, bus_types=None)

Bases: object

GridCal.Engine.Simulations.OPF.opf_results module
class GridCal.Engine.Simulations.OPF.opf_results.OptimalPowerFlowResults(Sbus=None, voltage=None, load_shedding=None, generation_shedding=None, battery_power=None, controlled_generation_power=None, Sbranch=None, overloads=None, loading=None, losses=None, converged=None, bus_types=None)

Bases: object

OPF results.

Arguments:

Sbus: bus power injections

voltage: bus voltages

load_shedding: load shedding values

Sbranch: branch power values

overloads: branch overloading values

loading: branch loading values

losses: branch losses

converged: converged?

copy()

Return a copy of this @return:

initialize(n, m)

Initialize the arrays @param n: number of buses @param m: number of branches @return:

plot(result_type, ax=None, indices=None, names=None)

Plot the results :param result_type: type of results (string) :param ax: matplotlib axis object :param indices: element indices :param names: element names :return: DataFrame of the results (or None if the result was not understood)

GridCal.Engine.Simulations.OPF.opf_time_series_driver module
class GridCal.Engine.Simulations.OPF.opf_time_series_driver.OptimalPowerFlowTimeSeries(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.OPF.opf_driver.OptimalPowerFlowOptions, start_=0, end_=None)

Bases: PySide2.QtCore.QThread

cancel()
done_signal = <PySide2.QtCore.Signal object>
get_steps()

Get time steps list of strings

opf(start_, end_, remote=False, batteries_energy_0=None)

Run a power flow for every circuit :param start_: start index :param end_: end index :param remote: is this function being called from the time series? :param batteries_energy_0: initial state of the batteries, if None the default values are taken :return: OptimalPowerFlowResults object

opf_by_groups()

Run the OPF by groups

progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
reset_results()

Clears the results

run()
Returns:
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Engine.Simulations.OPF.opf_time_series_driver.OptimalPowerFlowTimeSeriesResults(n, m, nt, ngen=0, nbat=0, nload=0, time=None)

Bases: object

init_object_results(ngen, nbat)

declare the generator results. This is done separately since these results are known at the end of the simulation :param ngen: number of generators :param nbat: number of batteries

plot(result_type, ax=None, indices=None, names=None)

Plot the results :param result_type: :param ax: :param indices: :param names: :return:

set_at(t, res: GridCal.Engine.Simulations.OPF.opf_results.OptimalPowerFlowResults)

Set the results :param t: time index :param res: OptimalPowerFlowResults instance

GridCal.Engine.Simulations.Optimization package
Submodules
GridCal.Engine.Simulations.Optimization.optimization_driver module
class GridCal.Engine.Simulations.Optimization.optimization_driver.Optimize(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions, max_iter=1000)

Bases: PySide2.QtCore.QThread

cancel()

Cancel the simulation

done_signal = <PySide2.QtCore.Signal object>
plot(ax=None)

Plot the optimization convergence

progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

Run the optimization @return: Nothing

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Engine.Simulations.Optimization.optimization_driver.VoltageOptimizationProblem(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions, max_iter=1000, callback=None)

Bases: pySOT.optimization_problems.OptimizationProblem

Variables:
  • dim – Number of dimensions
  • lb – Lower variable bounds
  • ub – Upper variable bounds
  • int_var – Integer variables
  • cont_var – Continuous variables
  • min – Global minimum value
  • minimum – Global minimizer
  • info – String with problem info
eval(x)

Evaluate the Ackley function at x

Parameters:x (numpy.array) – Data point
Returns:Value at x
Return type:float
GridCal.Engine.Simulations.PowerFlow package
Submodules
GridCal.Engine.Simulations.PowerFlow.fast_decoupled_power_flow module
GridCal.Engine.Simulations.PowerFlow.fast_decoupled_power_flow.FDPF(Vbus, Sbus, Ibus, Ybus, B1, B2, pq, pv, pqpv, tol=1e-09, max_it=100)

Fast decoupled power flow Args:

Vbus: Sbus: Ibus: Ybus: B1: B2: pq: pv: pqpv: tol:

Returns:

GridCal.Engine.Simulations.PowerFlow.helm_power_flow module

Method implemented from the article: Online voltage stability assessment for load areas based on the holomorphic embedding method by Chengxi Liu, Bin Wang, Fengkai Hu, Kai Sun and Claus Leth Bak

Implemented by Santiago Peñate Vera 2018 This implementation computes W[n] for all the buses outside the system matrix leading to better results

GridCal.Engine.Simulations.PowerFlow.helm_power_flow.assign_solution(x, bus_idx, pvpos, pv, nbus)

Assign the solution vector to the appropriate coefficients :param x: solution vector :param bus_idx: array from 0..nbus-1 :param nbus2: two times the number of buses (integer) :param pvpos: array from 0..npv :return: Array of:

  • voltage coefficients
  • reactive power

of order n

GridCal.Engine.Simulations.PowerFlow.helm_power_flow.calc_W(n, V, W)

Calculation of the inverse coefficients W. @param n: Order of the coefficients @param V: Structure of voltage coefficients (Ncoeff x nbus elements) @param W: Structure of inverse voltage coefficients (Ncoeff x nbus elements) @return: Array of inverse voltage coefficients for the order n

GridCal.Engine.Simulations.PowerFlow.helm_power_flow.get_rhs(n, V, W, Q, Vbus, Vst, Sbus, Pbus, nsys, nbus2, pv, pq, pvpos)

Right hand side :param n: order of the coefficients :param V: Voltage coefficients (order, all buses) :param W: Inverse voltage coefficients (order, pv buses) :param Q: Reactive power coefficients (order, pv buses) :param Vbus: Initial bus estimate (only used to pick the PV buses set voltage) :param Vst: Start voltage due to slack injections :param Pbus: Active power injections (all the buses) :param nsys: number of rows or cols in the system matrix A :param nbus2: two times the number of buses :param pv: list of pv indices in the grid :param pvpos: array from 0..npv :return: right hand side vector to solve the coefficients of order n

GridCal.Engine.Simulations.PowerFlow.helm_power_flow.helm(Vbus, Sbus, Ybus, pq, pv, ref, pqpv, tol=1e-09, max_coefficient_count=30)

Helm Method :param Vbus: voltages array :param Sbus: Power injections array :param Ibus: Currents injection array :param Ybus: System admittance matrix :param pq: list of pq node indices :param pv: list of pv node indices :param ref: list of slack node indices :param pqpv: list of pq and pv node indices sorted :param tol: tolerance :return: Voltage array and the power mismatch

GridCal.Engine.Simulations.PowerFlow.helm_power_flow.pade_approximation(n, an, s=1)

Computes the n/2 pade approximant of the series an at the approximation point s

Arguments:
an: coefficient matrix, (number of coefficients, number of series) n: order of the series s: point of approximation
Returns:
pade approximation at s
GridCal.Engine.Simulations.PowerFlow.helm_power_flow.prepare_system_matrices(Ybus, Vbus, bus_idx, pqpv, pq, pv, ref)

Prepare the system matrices :param Ybus: :param Vbus: :param pqpv: :param ref: :return:

GridCal.Engine.Simulations.PowerFlow.helm_power_flow.res_2_df(V, Sbus, tpe)

Create dataframe to display the results nicely :param V: Voltage complex vector :param Sbus: Power complex vector :param tpe: Types :return: Pandas DataFrame

GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow module
GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.ContinuousNR(Ybus, Sbus, V0, Ibus, pv, pq, tol, max_it=15)

Solves the power flow using a full Newton’s method with the backtrack improvement algorithm Args:

Ybus: Admittance matrix Sbus: Array of nodal power injections V0: Array of nodal voltages (initial solution) Ibus: Array of nodal current injections pv: Array with the indices of the PV buses pq: Array with the indices of the PQ buses tol: Tolerance max_it: Maximum number of iterations robust: Boolean variable for the use of the Iwamoto optimal step factor.
Returns:
Voltage solution, converged?, error, calculated power injections

@author: Ray Zimmerman (PSERC Cornell) @Author: Santiago Penate Vera

GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.F(V, Ybus, S, I, pq, pv)
Parameters:
  • V
  • Ybus
  • S
  • I
  • pq
  • pv
  • pvpq
Returns:

GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.IwamotoNR(Ybus, Sbus, V0, Ibus, pv, pq, tol, max_it=15, robust=False)

Solves the power flow using a full Newton’s method with the Iwamoto optimal step factor. Args:

Ybus: Admittance matrix Sbus: Array of nodal power injections V0: Array of nodal voltages (initial solution) Ibus: Array of nodal current injections pv: Array with the indices of the PV buses pq: Array with the indices of the PQ buses tol: Tolerance max_it: Maximum number of iterations robust: Boolean variable for the use of the Iwamoto optimal step factor.
Returns:
Voltage solution, converged?, error, calculated power injections

@author: Ray Zimmerman (PSERC Cornell) @Author: Santiago Penate Vera

GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.Jacobian(Ybus, V, Ibus, pq, pvpq)

Computes the system Jacobian matrix Args:

Ybus: Admittance matrix V: Array of nodal voltages Ibus: Array of nodal current injections pq: Array with the indices of the PQ buses pvpq: Array with the indices of the PV and PQ buses
Returns:
The system Jacobian matrix
GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.Jacobian_I(Ybus, V, pq, pvpq)

Computes the system Jacobian matrix Args:

Ybus: Admittance matrix V: Array of nodal voltages pq: Array with the indices of the PQ buses pvpq: Array with the indices of the PV and PQ buses
Returns:
The system Jacobian matrix in current equations
GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.LevenbergMarquardtPF(Ybus, Sbus, V0, Ibus, pv, pq, tol, max_it=50)

Solves the power flow problem by the Levenberg-Marquardt power flow algorithm. It is usually better than Newton-Raphson, but it takes an order of magnitude more time to converge. Args:

Ybus: Admittance matrix Sbus: Array of nodal power injections V0: Array of nodal voltages (initial solution) Ibus: Array of nodal current injections pv: Array with the indices of the PV buses pq: Array with the indices of the PQ buses tol: Tolerance max_it: Maximum number of iterations
Returns:
Voltage solution, converged?, error, calculated power injections

@Author: Santiago Peñate Vera

GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.NR_I_LS(Ybus, Sbus_sp, V0, Ibus_sp, pv, pq, tol, max_it=15)

Solves the power flow using a full Newton’s method in current equations with current mismatch with line search Args:

Ybus: Admittance matrix Sbus_sp: Array of nodal specified power injections V0: Array of nodal voltages (initial solution) Ibus_sp: Array of nodal specified current injections pv: Array with the indices of the PV buses pq: Array with the indices of the PQ buses tol: Tolerance max_it: Maximum number of iterations
Returns:
Voltage solution, converged?, error, calculated power injections

@Author: Santiago Penate Vera

GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.NR_LS(Ybus, Sbus, V0, Ibus, pv, pq, tol, max_it=15)

Solves the power flow using a full Newton’s method with the backtrack improvement algorithm Args:

Ybus: Admittance matrix Sbus: Array of nodal power injections V0: Array of nodal voltages (initial solution) Ibus: Array of nodal current injections pv: Array with the indices of the PV buses pq: Array with the indices of the PQ buses tol: Tolerance max_it: Maximum number of iterations robust: Boolean variable for the use of the Iwamoto optimal step factor.
Returns:
Voltage solution, converged?, error, calculated power injections

@author: Ray Zimmerman (PSERC Cornell) @Author: Santiago Penate Vera

GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.dSbus_dV(Ybus, V, I)

Computes partial derivatives of power injection w.r.t. voltage. :param Ybus: Admittance matrix :param V: Bus voltages array :param I: Bus current injections array :return:

GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.fx(x, Ybus, S, I, pq, pv, pvpq, j1, j2, j3, j4, j5, j6, Va, Vm)
Parameters:
  • x
  • Ybus
  • S
  • I
  • pq
  • pv
  • pvpq
Returns:

GridCal.Engine.Simulations.PowerFlow.jacobian_based_power_flow.mu(Ybus, Ibus, J, incS, dV, dx, pvpq, pq)

Calculate the Iwamoto acceleration parameter as described in: “A Load Flow Calculation Method for Ill-Conditioned Power Systems” by Iwamoto, S. and Tamura, Y.” Args:

Ybus: Admittance matrix J: Jacobian matrix incS: mismatch vector dV: voltage increment (in complex form) dx: solution vector as calculated dx = solve(J, incS) pvpq: array of the pq and pv indices pq: array of the pq indices
Returns:
the Iwamoto’s optimal multiplier for ill conditioned systems
GridCal.Engine.Simulations.PowerFlow.linearized_power_flow module
GridCal.Engine.Simulations.PowerFlow.linearized_power_flow.dcpf(Ybus, Sbus, Ibus, V0, ref, pvpq, pq, pv)

Solves a DC power flow. :param Ybus: Normal circuit admittance matrix :param Sbus: Complex power injections at all the nodes :param Ibus: Complex current injections at all the nodes :param V0: Array of complex seed voltage (it contains the ref voltages) :param ref: array of the indices of the slack nodes :param pvpq: array of the indices of the non-slack nodes :param pq: array of the indices of the pq nodes :param pv: array of the indices of the pv nodes :return:

Complex voltage solution Converged: Always true Solution error Computed power injections given the found solution
GridCal.Engine.Simulations.PowerFlow.linearized_power_flow.lacpf(Y, Ys, S, I, Vset, pq, pv)

Linearized AC Load Flow

form the article:

Linearized AC Load Flow Applied to Analysis in Electric Power Systems
by: P. Rossoni, W. M da Rosa and E. A. Belati
Args:
Y: Admittance matrix Ys: Admittance matrix of the series elements S: Power injections vector of all the nodes Vset: Set voltages of all the nodes (used for the slack and PV nodes) pq: list of indices of the pq nodes pv: list of indices of the pv nodes

Returns: Voltage vector, converged?, error, calculated power and elapsed time

GridCal.Engine.Simulations.PowerFlow.power_flow_driver module
class GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlow(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions)

Bases: PySide2.QtCore.QRunnable

Power flow wrapper to use with Qt

cancel()
get_steps()
run()

Pack run_pf for the QThread :return:

run_pf(circuit: GridCal.Engine.Core.calculation_inputs.CalculationInputs, Vbus, Sbus, Ibus)

Run a power flow for every circuit @return:

class GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowMP(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions)

Bases: object

Power flow without QT to use with multi processing.

Arguments:

grid (MultiCircuit): Electrical grid to run the power flow in

options (PowerFlowOptions): Power flow options to use

cancel()
static compile_types(Sbus, types=None, logger=[])

Compile the types.

static control_q_direct(V, Vset, Q, Qmax, Qmin, types, original_types, verbose)

Change the buses type in order to control the generators reactive power.

Arguments:

pq (list): array of pq indices

pv (list): array of pq indices

ref (list): array of pq indices

V (list): array of voltages (all buses)

Vset (list): Array of set points (all buses)

Q (list): Array of reactive power (all buses)

types (list): Array of types (all buses)

original_types (list): Types as originally intended (all buses)

verbose (bool): output messages via the console

Returns:

Vnew (list): New voltage values

Qnew (list): New reactive power values

types_new (list): Modified types array

any_control_issue (bool): Was there any control issue?

ON PV-PQ BUS TYPE SWITCHING LOGIC IN POWER FLOW COMPUTATION Jinquan Zhao

  1. Bus i is a PQ bus in the previous iteration and its reactive power was fixed at its lower limit:

    If its voltage magnitude Vi ≥ Viset, then

    it is still a PQ bus at current iteration and set Qi = Qimin .

    If Vi < Viset , then

    compare Qi with the upper and lower limits.

    If Qi ≥ Qimax , then

    it is still a PQ bus but set Qi = Qimax .

    If Qi ≤ Qimin , then

    it is still a PQ bus and set Qi = Qimin .

    If Qimin < Qi < Qi max , then

    it is switched to PV bus, set Vinew = Viset.

  2. Bus i is a PQ bus in the previous iteration and its reactive power was fixed at its upper limit:

    If its voltage magnitude Vi ≤ Viset , then:

    bus i still a PQ bus and set Q i = Q i max.

    If Vi > Viset , then

    Compare between Qi and its upper/lower limits

    If Qi ≥ Qimax , then

    it is still a PQ bus and set Q i = Qimax .

    If Qi ≤ Qimin , then

    it is still a PQ bus but let Qi = Qimin in current iteration.

    If Qimin < Qi < Qimax , then

    it is switched to PV bus and set Vinew = Viset

  3. Bus i is a PV bus in the previous iteration.

    Compare Q i with its upper and lower limits.

    If Qi ≥ Qimax , then

    it is switched to PQ and set Qi = Qimax .

    If Qi ≤ Qimin , then

    it is switched to PQ and set Qi = Qimin .

    If Qi min < Qi < Qimax , then

    it is still a PV bus.

control_q_iterative(V, Vset, Q, Qmax, Qmin, types, original_types, verbose, k)

Change the buses type in order to control the generators reactive power using iterative changes in Q to reach Vset.

Arguments:

V (list): array of voltages (all buses)

Vset (list): Array of set points (all buses)

Q (list): Array of reactive power (all buses)

Qmin (list): Array of minimal reactive power (all buses)

Qmax (list): Array of maximal reactive power (all buses)

types (list): Array of types (all buses)

original_types (list): Types as originally intended (all buses)

verbose (list): output messages via the console

k (float, 30): Steepness factor

Return:

Qnew (list): New reactive power values

types_new (list): Modified types array

any_control_issue (bool): Was there any control issue?

static control_taps_direct(voltage, T, bus_to_regulated_idx, tap_position, tap_module, min_tap, max_tap, tap_inc_reg_up, tap_inc_reg_down, vset, verbose=False)

Change the taps and compute the continuous tap magnitude.

Arguments:

voltage (list): array of bus voltages solution

T (list): array of indices of the “to” buses of each branch

bus_to_regulated_idx (list): array with the indices of the branches that regulate the bus “to”

tap_position (list): array of branch tap positions

tap_module (list): array of branch tap modules

min_tap (list): array of minimum tap positions

max_tap (list): array of maximum tap positions

tap_inc_reg_up (list): array of tap increment when regulating up

tap_inc_reg_down (list): array of tap increment when regulating down

vset (list): array of set voltages to control

Returns:

stable (bool): Is the system stable (i.e.: are controllers stable)?

tap_magnitude (list): Tap module at each bus in per unit

tap_position (list): Tap position at each bus

control_taps_iterative(voltage, T, bus_to_regulated_idx, tap_position, tap_module, min_tap, max_tap, tap_inc_reg_up, tap_inc_reg_down, vset, verbose=False)

Change the taps and compute the continuous tap magnitude.

Arguments:

voltage (list): array of bus voltages solution

T (list): array of indices of the “to” buses of each branch

bus_to_regulated_idx (list): array with the indices of the branches that regulate the bus “to”

tap_position (list): array of branch tap positions

tap_module (list): array of branch tap modules

min_tap (list): array of minimum tap positions

max_tap (list): array of maximum tap positions

tap_inc_reg_up (list): array of tap increment when regulating up

tap_inc_reg_down (list): array of tap increment when regulating down

vset (list): array of set voltages to control

Returns:

stable (bool): Is the system stable (i.e.: are controllers stable)?

tap_magnitude (list): Tap module at each bus in per unit

tap_position (list): Tap position at each bus

static get_q_increment(V1, V2, k)

Logistic function to get the Q increment gain using the difference between the current voltage (V1) and the target voltage (V2).

The gain varies between 0 (at V1 = V2) and inf (at V2 - V1 = inf).

The default steepness factor k was set through trial an error. Other values may be specified as a PowerFlowOptions.

Arguments:

V1 (float): Current voltage

V2 (float): Target voltage

k (float, 30): Steepness factor

Returns:

Q increment gain
static power_flow_post_process(calculation_inputs: GridCal.Engine.Core.calculation_inputs.CalculationInputs, V, only_power=False)

Compute the power flows trough the branches.

Arguments:

calculation_inputs: instance of Circuit

V: Voltage solution array for the circuit buses

only_power: compute only the power injection

Returns:

Sbranch (MVA), Ibranch (p.u.), loading (p.u.), losses (MVA), Sbus(MVA)
run()

Run a power flow for a circuit (wrapper for run_pf).

Returns:

PowerFlowResults instance (self.results)
run_multi_island(calculation_inputs, Vbus, Sbus, Ibus)

Power flow execution for optimization purposes.

Arguments:

calculation_inputs:

Vbus:

Sbus:

Ibus:

Returns:

PowerFlowResults instance
run_pf(circuit: GridCal.Engine.Core.calculation_inputs.CalculationInputs, Vbus, Sbus, Ibus)

Run a power flow for a circuit. In most cases, the run method should be used instead.

Arguments:

circuit (CalculationInputs): CalculationInputs instance

Vbus (list): Initial voltage at each bus in complex per unit

Sbus (list): Power injection at each bus in complex MVA

Ibus (list): Current injection at each bus in complex amperes

Returns:

single_power_flow(circuit: GridCal.Engine.Core.calculation_inputs.CalculationInputs, solver_type: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.SolverType, voltage_solution, Sbus, Ibus)

Run a power flow simulation for a single circuit using the selected outer loop controls. This method shouldn’t be called directly.

Arguments:

circuit: CalculationInputs instance

solver_type: type of power flow to use first

voltage_solution: vector of initial voltages

Sbus: vector of power injections

Ibus: vector of current injections

Return:

PowerFlowResults instance
static solve(solver_type, V0, Sbus, Ibus, Ybus, Yseries, B1, B2, pq, pv, ref, pqpv, tolerance, max_iter)

Run a power flow simulation using the selected method (no outer loop controls).

solver_type:

V0: Voltage solution vector

Sbus: Power injections vector

Ibus: Current injections vector

Ybus: Admittance matrix

Yseries: Series elements’ Admittance matrix

B1: B’ for the fast decoupled method

B2: B’’ for the fast decoupled method

pq: list of pq nodes

pv: list of pv nodes

ref: list of slack nodes

pqpv: list of pq and pv nodes

tolerance: power error tolerance

max_iter: maximum iterations

Returns:

V0 (Voltage solution), converged (converged?), normF (error in power), Scalc (Computed bus power), iterations, elapsed
static tap_down(tap, min_tap)

Go to the next upper tap position

static tap_up(tap, max_tap)

Go to the next upper tap position

class GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions(solver_type: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.SolverType = <SolverType.NR: 'Newton Raphson'>, retry_with_other_methods=True, verbose=False, initialize_with_existing_solution=True, tolerance=1e-06, max_iter=25, max_outer_loop_iter=100, control_q=<ReactivePowerControlMode.NoControl: 'NoControl'>, control_taps=<TapsControlMode.NoControl: 'NoControl'>, multi_core=False, dispatch_storage=False, control_p=False, apply_temperature_correction=False, branch_impedance_tolerance_mode=<BranchImpedanceMode.Specified: 0>, q_steepness_factor=30)

Bases: object

Power flow options class; its object is used as an argument for the PowerFlowMP constructor.

Arguments:

solver_type (SolverType, SolverType.NR): Solver type

retry_with_other_methods (bool, True): Use a battery of methods to tackle the problem if the main solver fails

verbose (bool, False): Print additional details in the logger

initialize_with_existing_solution (bool, True): To be detailed

tolerance (float, 1e-6): Solution tolerance for the power flow numerical methods

max_iter (int, 25): Maximum number of iterations for the power flow numerical method

max_outer_loop_iter (int, 100): Maximum number of iterations for the controls outer loop

control_q (ReactivePowerControlMode, ReactivePowerControlMode.NoControl): Control mode for the PV nodes reactive power limits

control_taps (TapsControlMode, TapsControlMode.NoControl): Control mode for the transformer taps equipped with a voltage regulator (as part of the outer loop)

multi_core (bool, False): Use multi-core processing? applicable for time series

dispatch_storage (bool, False): Dispatch storage?

control_p (bool, False): Control active power (optimization dispatch)

apply_temperature_correction (bool, False): Apply the temperature correction to the resistance of the branches?

branch_impedance_tolerance_mode (BranchImpedanceMode, BranchImpedanceMode.Specified): Type of modification of the branches impedance

q_steepness_factor (float, 30): Steepness factor k for the ReactivePowerControlMode iterative control

class GridCal.Engine.Simulations.PowerFlow.power_flow_driver.ReactivePowerControlMode

Bases: enum.Enum

The ReactivePowerControlMode offers 3 modes to control how Generator objects supply reactive power:

NoControl: In this mode, the generators don’t try to regulate the voltage at their bus.

Direct: In this mode, the generators try to regulate the voltage at their bus. GridCal does so by applying the following algorithm in an outer control loop. For grids with numerous generators tied to the same system, for example wind farms, this control method sometimes fails with some generators not trying hard enough*. In this case, the simulation converges but the voltage controlled buses do not reach their target voltage, while their generator(s) haven’t reached their reactive power limit. In this case, the slower Iterative control mode may be used (see below).

ON PV-PQ BUS TYPE SWITCHING LOGIC IN POWER FLOW COMPUTATION Jinquan Zhao

  1. Bus i is a PQ bus in the previous iteration and its reactive power was fixed at its lower limit:

    If its voltage magnitude Vi >= Viset, then

    it is still a PQ bus at current iteration and set Qi = Qimin .

    If Vi < Viset , then

    compare Qi with the upper and lower limits.

    If Qi >= Qimax , then

    it is still a PQ bus but set Qi = Qimax .

    If Qi <= Qimin , then

    it is still a PQ bus and set Qi = Qimin .

    If Qimin < Qi < Qi max , then

    it is switched to PV bus, set Vinew = Viset.

  2. Bus i is a PQ bus in the previous iteration and its reactive power was fixed at its upper limit:

    If its voltage magnitude Vi <= Viset , then:

    bus i still a PQ bus and set Q i = Q i max.

    If Vi > Viset , then

    Compare between Qi and its upper/lower limits

    If Qi >= Qimax , then

    it is still a PQ bus and set Q i = Qimax .

    If Qi <= Qimin , then

    it is still a PQ bus but let Qi = Qimin in current iteration.

    If Qimin < Qi < Qimax , then

    it is switched to PV bus and set Vinew = Viset

  3. Bus i is a PV bus in the previous iteration.

    Compare Q i with its upper and lower limits.

    If Qi >= Qimax , then

    it is switched to PQ and set Qi = Qimax .

    If Qi <= Qimin , then

    it is switched to PQ and set Qi = Qimin .

    If Qi min < Qi < Qimax , then

    it is still a PV bus.

Iterative: As mentioned above, the Direct control mode may not yield satisfying results in some isolated cases. The Direct control mode tries to jump to the final solution in a single or few iterations, but in grids where a significant change in reactive power at one generator has a significant impact on other generators, additional iterations may be required to reach a satisfying solution.

Instead of trying to jump to the final solution, the Iterative mode raises or lowers each generator’s reactive power incrementally. The increment is determined using a logistic function based on the difference between the current bus voltage its target voltage. The steepness factor k of the logistic function was determined through trial and error, with the intent of reducing the number of iterations while avoiding instability. Other values may be specified in PowerFlowOptions.

The Q_{Increment} in per unit is determined by:

Q_{Increment} = 2 * \left[\frac{1}{1 + e^{-k|V_2 - V_1|}}-0.5\right]

Where:

k = 30 (by default)
Direct = 'Direct'
Iterative = 'Iterative'
NoControl = 'NoControl'
class GridCal.Engine.Simulations.PowerFlow.power_flow_driver.SolverType

Bases: enum.Enum

Refer to the Power Flow section for details about the different algorithms supported by GridCal.

AC_OPF = 'Linear AC OPF'
CONTINUATION_NR = 'Continuation-Newton-Raphson'
DC = 'Linear DC'
DC_OPF = 'Linear DC OPF'
DYCORS_OPF = 'DYCORS OPF'
FASTDECOUPLED = 'Fast decoupled'
GAUSS = 'Gauss-Seidel'
GA_OPF = 'Genetic Algorithm OPF'
HELM = 'Holomorphic Embedding'
HELMZ = 'HELM-Z'
IWAMOTO = 'Iwamoto-Newton-Raphson'
LACPF = 'Linear AC'
LM = 'Levenberg-Marquardt'
NELDER_MEAD_OPF = 'Nelder Mead OPF'
NR = 'Newton Raphson'
NRFD_BX = 'Fast decoupled BX'
NRFD_XB = 'Fast decoupled XB'
NRI = 'Newton-Raphson in current'
ZBUS = 'Z-Gauss-Seidel'
class GridCal.Engine.Simulations.PowerFlow.power_flow_driver.TapsControlMode

Bases: enum.Enum

The TapsControlMode offers 3 modes to control how transformerstap changer regulate voltage on their regulated bus:

NoControl: In this mode, the transformers don’t try to regulate the voltage at their bus.

Direct: In this mode, the transformers try to regulate the voltage at their bus. GridCal does so by jumping straight to the tap that corresponds to the desired transformation ratio, or the highest or lowest tap if the desired ratio is outside of the tap range.

This behavior may fail in certain cases, especially if both the TapControlMode and ReactivePowerControlMode are set to Direct. In this case, the simulation converges but the voltage controlled buses do not reach their target voltage, while their generator(s) haven’t reached their reactive power limit. When this happens, the slower Iterative control mode may be used (see below).

Iterative: As mentioned above, the Direct control mode may not yield satisfying results in some isolated cases. The Direct control mode tries to jump to the final solution in a single or few iterations, but in grids where a significant change of tap at one transformer has a significant impact on other transformers, additional iterations may be required to reach a satisfying solution.

Instead of trying to jump to the final solution, the Iterative mode raises or lowers each transformer’s tap incrementally.

Direct = 'Direct'
Iterative = 'Iterative'
NoControl = 'NoControl'
GridCal.Engine.Simulations.PowerFlow.power_flow_driver.power_flow_worker(t, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions, circuit: GridCal.Engine.Core.calculation_inputs.CalculationInputs, Vbus, Sbus, Ibus, return_dict)
Power flow worker to schedule parallel power flows
**t: execution index **options: power flow options **circuit: circuit **Vbus: Voltages to initialize **Sbus: Power injections **Ibus: Current injections **return_dict: parallel module dictionary in wich to return the values
Returns:
GridCal.Engine.Simulations.PowerFlow.power_flow_driver.power_flow_worker_args(args)

Power flow worker to schedule parallel power flows

args -> t, options: PowerFlowOptions, circuit: Circuit, Vbus, Sbus, Ibus, return_dict

**t: execution index **options: power flow options **circuit: circuit **Vbus: Voltages to initialize **Sbus: Power injections **Ibus: Current injections **return_dict: parallel module dictionary in wich to return the values
Returns:
GridCal.Engine.Simulations.PowerFlow.power_flow_results module
class GridCal.Engine.Simulations.PowerFlow.power_flow_results.PowerFlowResults(Sbus=None, voltage=None, Sbranch=None, Ibranch=None, loading=None, losses=None, tap_module=None, flow_direction=None, Vbranch=None, error=None, converged=None, Qpv=None, battery_power_inc=None, inner_it=None, outer_it=None, elapsed=None, methods=None, bus_types=None)

Bases: object

A PowerFlowResults object is create as an attribute of the PowerFlowMP (as PowerFlowMP.results) when the power flow is run. It provides access to the simulation results through its class attributes.

Attributes:

Sbus (list): Power at each bus in complex per unit

voltage (list): Voltage at each bus in complex per unit

Sbranch (list): Power through each branch in complex MVA

Ibranch (list): Current through each branch in complex per unit

loading (list): Loading of each branch in per unit

losses (list): Losses in each branch in complex MVA

tap_module (list): Computed tap module at each branch in per unit

flow_direction (list): Flow direction at each branch

Vbranch (list): Voltage increment at each branch

error (float): Power flow computed error

converged (bool): Did the power flow converge?

Qpv (list): Reactive power at each PV node in per unit

inner_it (int): Number of inner iterations

outer_it (int): Number of outer iterations

elapsed (float): Simulation duration in seconds

methods (list): Power flow methods used

apply_from_island(results, b_idx, br_idx)

Apply results from another island circuit to the circuit results represented here.

Arguments:

results: PowerFlowResults

b_idx: bus original indices

br_idx: branch original indices

check_limits(F, T, Vmax, Vmin, wo=1, wv1=1, wv2=1)

Check the grid violations on the whole circuit

Arguments:

F:

T:

Vmax:

Vmin:

wo:

wv1:

wv2:

Returns:

Summation of the deviations
copy()

Return a copy of this @return:

export_all()

Exports all the results to DataFrames.

Returns:

Bus results, Branch reuslts
get_convergence_report()
get_report_dataframe(island_idx=0)

Get a DataFrame containing the convergence report.

Arguments:

island_idx: (optional) island index

Returns:

DataFrame
initialize(n, m)

Initialize the arrays @param n: number of buses @param m: number of branches @return:

plot(result_type: GridCal.Engine.Simulations.result_types.ResultTypes, ax=None, indices=None, names=None)

Plot the results.

Arguments:

result_type: ResultTypes

ax: matplotlib axis

indices: Indices f the array to plot (indices of the elements)

names: Names of the elements

Returns:

DataFrame
GridCal.Engine.Simulations.PowerFlow.time_Series_input module
class GridCal.Engine.Simulations.PowerFlow.time_Series_input.TimeSeriesInput(s_profile: pandas.core.frame.DataFrame = None, i_profile: pandas.core.frame.DataFrame = None, y_profile: pandas.core.frame.DataFrame = None)

Bases: object

apply_from_island(res, bus_original_idx, branch_original_idx, nbus_full, nbranch_full)
Parameters:
  • res – TimeSeriesInput
  • bus_original_idx
  • branch_original_idx
  • nbus_full
  • nbranch_full
Returns:

compile()

Generate time-consistent arrays @return:

copy()
get_at(t)

Returns the necessary values @param t: time index @return:

get_from_buses(bus_idx)

@param bus_idx: @return:

GridCal.Engine.Simulations.PowerFlow.time_series_driver module
class GridCal.Engine.Simulations.PowerFlow.time_series_driver.TimeSeries(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions, use_opf_vals=False, opf_time_series_results=None, start_=0, end_=None)

Bases: PySide2.QtCore.QThread

cancel()

Cancel the simulation

done_signal = <PySide2.QtCore.Signal object>
get_steps()

Get time steps list of strings

progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

Run the time series simulation @return:

run_multi_thread() → GridCal.Engine.Simulations.PowerFlow.time_series_driver.TimeSeriesResults

Run multi thread time series :return: TimeSeriesResults instance

run_single_thread() → GridCal.Engine.Simulations.PowerFlow.time_series_driver.TimeSeriesResults

Run single thread time series :return: TimeSeriesResults instance

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Engine.Simulations.PowerFlow.time_series_driver.TimeSeriesResults(n, m, nt, start, end, time=None)

Bases: GridCal.Engine.Simulations.PowerFlow.power_flow_results.PowerFlowResults

analyze()

Analyze the results @return:

apply_from_island(results, b_idx, br_idx, index, grid_idx)

Apply results from another island circuit to the circuit results represented here @param results: PowerFlowResults @param b_idx: bus original indices @param br_idx: branch original indices @return:

get_results_dict()

Returns a dictionary with the results sorted in a dictionary :return: dictionary of 2D numpy arrays (probably of complex numbers)

static merge_if(df, arr, ind, cols)

@param df: @param arr: @param ind: @param cols: @return:

plot(result_type: GridCal.Engine.Simulations.result_types.ResultTypes, ax=None, indices=None, names=None)

Plot the results :param result_type: :param ax: :param indices: :param names: :return:

save(fname)

Export as pickle

set_at(t, results: GridCal.Engine.Simulations.PowerFlow.power_flow_results.PowerFlowResults)

Set the results at the step t @param t: time index @param results: PowerFlowResults instance

GridCal.Engine.Simulations.ShortCircuit package
Submodules
GridCal.Engine.Simulations.ShortCircuit.short_circuit module
GridCal.Engine.Simulations.ShortCircuit.short_circuit.short_circuit_3p(bus_idx, Zbus, Vbus, Zf, baseMVA)

Executes a 3-phase balanced short circuit study Args:

bus_idx: Index of the bus at which the short circuit is being studied Zbus: Inverse of the admittance matrix Vbus: Voltages of the buses in the steady state Zf: Fault impedance array

Returns: Voltages after the short circuit (p.u.), Short circuit power in MVA

GridCal.Engine.Simulations.ShortCircuit.short_circuit_driver module
class GridCal.Engine.Simulations.ShortCircuit.short_circuit_driver.ShortCircuit(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.ShortCircuit.short_circuit_driver.ShortCircuitOptions, pf_results: GridCal.Engine.Simulations.PowerFlow.power_flow_results.PowerFlowResults)

Bases: PySide2.QtCore.QRunnable

cancel()
static compile_zf(grid)
static compute_branch_results(calculation_inputs: GridCal.Engine.Core.calculation_inputs.CalculationInputs, V)

Compute the power flows trough the branches @param calculation_inputs: instance of Circuit @param V: Voltage solution array for the circuit buses @return: Sbranch, Ibranch, loading, losses

get_steps()

Get time steps list of strings

run()

Run a power flow for every circuit @return:

single_short_circuit(calculation_inputs: GridCal.Engine.Core.calculation_inputs.CalculationInputs, Vpf, Zf)

Run a power flow simulation for a single circuit @param calculation_inputs: @param Vpf: Power flow voltage vector applicable to the island @param Zf: Short circuit impedance vector applicable to the island @return: short circuit results

static split_branch(branch: GridCal.Engine.Devices.branch.Branch, fault_position, r_fault, x_fault)

Split a branch by a given distance :param branch: Branch of a circuit :param fault_position: per unit distance measured from the “from” bus (0 ~ 1) :param r_fault: Fault resistance in p.u. :param x_fault: Fault reactance in p.u. :return: the two new branches and the mid short circuited bus

class GridCal.Engine.Simulations.ShortCircuit.short_circuit_driver.ShortCircuitOptions(bus_index=[], branch_index=[], branch_fault_locations=[], branch_fault_impedance=[], branch_impedance_tolerance_mode=<BranchImpedanceMode.Specified: 0>, verbose=False)

Bases: object

class GridCal.Engine.Simulations.ShortCircuit.short_circuit_driver.ShortCircuitResults(Sbus=None, voltage=None, Sbranch=None, Ibranch=None, loading=None, losses=None, SCpower=None, error=None, converged=None, Qpv=None)

Bases: GridCal.Engine.Simulations.PowerFlow.power_flow_results.PowerFlowResults

apply_from_island(results, b_idx, br_idx)

Apply results from another island circuit to the circuit results represented here @param results: PowerFlowResults @param b_idx: bus original indices @param br_idx: branch original indices @return:

copy()

Return a copy of this @return:

initialize(n, m)

Initialize the arrays @param n: number of buses @param m: number of branches @return:

plot(result_type, ax=None, indices=None, names=None)

Plot the results Args:

result_type: ax: indices: names:

Returns:

GridCal.Engine.Simulations.StateEstimation package
Submodules
GridCal.Engine.Simulations.StateEstimation.state_estimation module
GridCal.Engine.Simulations.StateEstimation.state_estimation.Jacobian_SE(Ybus, Yf, Yt, V, f, t, inputs, pvpq)
Parameters:
  • Ybus
  • Yf
  • Yt
  • V
  • f
  • t
  • inputs – instance of StateEstimationInput
  • pvpq
Returns:

GridCal.Engine.Simulations.StateEstimation.state_estimation.dIbr_dV(Yf, Yt, V)

Computes partial derivatives of branch currents w.r.t. voltage :param Yf: :param Yt: :param V: :return:

GridCal.Engine.Simulations.StateEstimation.state_estimation.dSbr_dV(Yf, Yt, V, f, t)
Parameters:
  • Yf
  • Yt
  • V
  • f
  • t
Returns:

GridCal.Engine.Simulations.StateEstimation.state_estimation.dSbus_dV(Ybus, V)
Parameters:
  • Ybus
  • V
Returns:

GridCal.Engine.Simulations.StateEstimation.state_estimation.solve_se_lm(Ybus, Yf, Yt, f, t, se_input, ref, pq, pv)

Solve the state estimation problem using the Levenberg-Marquadt method :param Ybus: :param Yf: :param Yt: :param f: array with the from bus indices of all the branches :param t: array with the to bus indices of all the branches :param inputs: state estimation imput instance (contains the measurements) :param ref: :param pq: :param pv: :return:

GridCal.Engine.Simulations.StateEstimation.state_stimation_driver module
class GridCal.Engine.Simulations.StateEstimation.state_stimation_driver.StateEstimation(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit)

Bases: PySide2.QtCore.QRunnable

static collect_measurements(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, bus_idx, branch_idx)

Form the input from the circuit measurements :return: nothing, the input object is stored in this class

run()

Run state estimation :return:

class GridCal.Engine.Simulations.StateEstimation.state_stimation_driver.StateEstimationInput

Bases: object

clear()

Clear

consolidate()

consolidate the measurements into “measurements” and “sigma” :return: measurements, sigma

class GridCal.Engine.Simulations.StateEstimation.state_stimation_driver.StateEstimationResults(Sbus=None, voltage=None, Sbranch=None, Ibranch=None, loading=None, losses=None, error=None, converged=None, Qpv=None)

Bases: GridCal.Engine.Simulations.PowerFlow.power_flow_results.PowerFlowResults

GridCal.Engine.Simulations.Stochastic package
Submodules
GridCal.Engine.Simulations.Stochastic.blackout_driver module
class GridCal.Engine.Simulations.Stochastic.blackout_driver.CascadeType

Bases: enum.Enum

An enumeration.

LatinHypercube = 1
PowerFlow = (0,)
class GridCal.Engine.Simulations.Stochastic.blackout_driver.Cascading(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions, triggering_idx=None, max_additional_islands=1, cascade_type_: GridCal.Engine.Simulations.Stochastic.blackout_driver.CascadeType = <CascadeType.LatinHypercube: 1>, n_lhs_samples_=1000)

Bases: PySide2.QtCore.QThread

cancel()

Cancel the simulation :return:

done_signal = <PySide2.QtCore.Signal object>
get_failed_idx()

Return the array of all failed branches Returns:

array of all failed branches
get_table()

Get DataFrame of the failed elements :return: DataFrame

perform_step_run()

Perform only one step cascading Returns:

Nothing
progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
static remove_elements(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, loading_vector, idx=None)

Remove branches based on loading Returns:

Nothing
static remove_probability_based(numerical_circuit: GridCal.Engine.Core.numerical_circuit.NumericalCircuit, results: GridCal.Engine.Simulations.Stochastic.monte_carlo_results.MonteCarloResults, max_val, min_prob)

Remove branches based on their chance of overload :param numerical_circuit: :param results: :param max_val: :param min_prob: :return: list of indices actually removed

run()

Run the monte carlo simulation @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Engine.Simulations.Stochastic.blackout_driver.CascadingReportElement(removed_idx, pf_results, criteria)

Bases: object

class GridCal.Engine.Simulations.Stochastic.blackout_driver.CascadingResults(cascade_type: GridCal.Engine.Simulations.Stochastic.blackout_driver.CascadeType)

Bases: object

get_failed_idx()

Return the array of all failed branches Returns:

array of all failed branches
get_table()

Get DataFrame of the failed elements :return: DataFrame

plot()
GridCal.Engine.Simulations.Stochastic.lhs_driver module
class GridCal.Engine.Simulations.Stochastic.lhs_driver.LatinHypercubeSampling(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions, sampling_points=1000)

Bases: PySide2.QtCore.QThread

cancel()

Cancel the simulation

done_signal = <PySide2.QtCore.Signal object>
get_steps()

Get time steps list of strings

progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

Run the monte carlo simulation @return:

run_multi_thread()

Run the monte carlo simulation @return:

run_single_thread()

Run the monte carlo simulation @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Engine.Simulations.Stochastic.monte_carlo_driver module
class GridCal.Engine.Simulations.Stochastic.monte_carlo_driver.MonteCarlo(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions, mc_tol=0.001, batch_size=100, max_mc_iter=10000)

Bases: PySide2.QtCore.QThread

cancel()

Cancel the simulation :return:

done_signal = <PySide2.QtCore.Signal object>
get_steps()

Get time steps list of strings

progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

Run the monte carlo simulation @return:

run_multi_thread()

Run the monte carlo simulation @return:

run_single_thread()

Run the monte carlo simulation @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Engine.Simulations.Stochastic.monte_carlo_driver.make_monte_carlo_input(numerical_input_island: GridCal.Engine.Core.calculation_inputs.CalculationInputs)

Generate a monte carlo input instance :param numerical_input_island: :return:

GridCal.Engine.Simulations.Stochastic.monte_carlo_input module
class GridCal.Engine.Simulations.Stochastic.monte_carlo_input.MonteCarloInput(n, Scdf, Icdf, Ycdf)

Bases: object

get_at(x)

Get samples at x Args:

x: values in [0, 1] to sample the CDF

Returns: Time series object

GridCal.Engine.Simulations.Stochastic.monte_carlo_results module
class GridCal.Engine.Simulations.Stochastic.monte_carlo_results.MonteCarloResults(n, m, p=0)

Bases: object

append_batch(mcres)

Append a batch (a MonteCarloResults object) to this object @param mcres: MonteCarloResults object @return:

compile()

Compiles the final Monte Carlo values by running an online mean and @return:

get_index_loading_cdf(max_val=1.0)

Find the elements where the CDF is greater or equal to a velue :param max_val: value to compare :return: indices, associated probability

get_results_dict()

Returns a dictionary with the results sorted in a dictionary :return: dictionary of 2D numpy arrays (probably of complex numbers)

get_voltage_sum()

Return the voltage summation @return:

open(fname)

open pickle Args:

fname: file name

Returns: true if succeeded, false otherwise

plot(result_type: GridCal.Engine.Simulations.result_types.ResultTypes, ax=None, indices=None, names=None)

Plot the results :param result_type: :param ax: :param indices: :param names: :return:

query_voltage(power_array)

Fantastic function that allows to query the voltage from the sampled points without having to run power flows Args:

power_array: power injections vector

Returns: Interpolated voltages vector

save(fname)

Export as pickle

GridCal.Engine.Simulations.Stochastic.reliability_driver module
class GridCal.Engine.Simulations.Stochastic.reliability_driver.ReliabilityStudy(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, pf_options: GridCal.Engine.Simulations.PowerFlow.power_flow_driver.PowerFlowOptions)

Bases: PySide2.QtCore.QThread

cancel()
done_signal = <PySide2.QtCore.Signal object>
progress_callback(l)

Send progress report :param l: lambda value :return: None

progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

run the voltage collapse simulation @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Engine.Simulations.Stochastic.reliability_driver.get_failure_time(mttf)

Get an array of possible failure times :param mttf: mean time to failure

GridCal.Engine.Simulations.Stochastic.reliability_driver.get_reliability_events(horizon, mttf, mttr, tpe: GridCal.Engine.Devices.meta_devices.DeviceType)

Get random fail-repair events until a given time horizon in hours :param horizon: maximum horizon in hours :return: list of events, Each event tuple has: (time in hours, element index, activation state (True/False))

GridCal.Engine.Simulations.Stochastic.reliability_driver.get_reliability_scenario(nc: GridCal.Engine.Core.numerical_circuit.NumericalCircuit, horizon=10000)

Get reliability events Args:

nc: numerical circuit instance horizon: time horizon in hours

Returns: dictionary of events Each event tuple has: (time in hours, element index, activation state (True/False))

GridCal.Engine.Simulations.Stochastic.reliability_driver.get_repair_time(mttr)

Get an array of possible repair times :param mttr: mean time to recovery

GridCal.Engine.Simulations.Stochastic.reliability_driver.run_events(nc: GridCal.Engine.Core.numerical_circuit.NumericalCircuit, events_list: list)
GridCal.Engine.Simulations.Topology package
Submodules
GridCal.Engine.Simulations.Topology.topology_driver module
class GridCal.Engine.Simulations.Topology.topology_driver.TopologyReduction(grid: GridCal.Engine.Core.multi_circuit.MultiCircuit, branch_indices)

Bases: PySide2.QtCore.QThread

cancel()

Cancel the simulation :return:

done_signal = <PySide2.QtCore.Signal object>
progress_signal = <PySide2.QtCore.Signal object>
progress_text = <PySide2.QtCore.Signal object>
run()

Run the monte carlo simulation @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Engine.Simulations.Topology.topology_driver.TopologyReductionOptions(rx_criteria=False, rx_threshold=1e-05, selected_types=<BranchType.Branch: ('branch', )>)

Bases: object

GridCal.Engine.Simulations.Topology.topology_driver.get_branches_of_bus(B, j)

Get the indices of the branches connected to the bus j :param B: Branch-bus CSC matrix :param j: bus index :return: list of branches in the bus

GridCal.Engine.Simulations.Topology.topology_driver.reduce_buses(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, buses_to_reduce: List[GridCal.Engine.Devices.bus.Bus])

Reduce the uses in the grid This function removes the buses but whenever a bus is removed, the devices connected to it are inherited by the bus of higher voltage that is connected. If the bus is isolated, those devices are lost. :param circuit: MultiCircuit instance :param buses_to_reduce: list of Bus objects :return: Nothing

GridCal.Engine.Simulations.Topology.topology_driver.reduce_grid_brute(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, removed_br_idx)

Remove the first branch found to be removed. this function is meant to be called until it returns false Args:

circuit: Circuit to modify in-place removed_br_idx: branch index

Returns: Nothing

GridCal.Engine.Simulations.Topology.topology_driver.select_branches_to_reduce(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, rx_criteria=True, rx_threshold=1e-05, selected_types=<BranchType.Branch: ('branch', )>)

Find branches to remove Args:

circuit: Circuit to modify in-place rx_criteria: use the r+x threshold to select branches? rx_threshold: r+x threshold selected_types: branch types to select
Submodules
GridCal.Engine.Simulations.result_types module
class GridCal.Engine.Simulations.result_types.ResultTypes

Bases: enum.Enum

An enumeration.

BatteryEnergy = ('Battery energy', <DeviceType.BatteryDevice: 'Battery'>)
BatteryPower = ('Battery power', <DeviceType.BatteryDevice: 'Battery'>)
BranchAngles = ('Branch voltage angles', <DeviceType.BranchDevice: 'Branch'>)
BranchCurrent = ('Branch current', <DeviceType.BranchDevice: 'Branch'>)
BranchCurrentAverage = ('Branch current avg', <DeviceType.BranchDevice: 'Branch'>)
BranchCurrentCDF = ('Branch current CDF', <DeviceType.BranchDevice: 'Branch'>)
BranchCurrentStd = ('Branch current std', <DeviceType.BranchDevice: 'Branch'>)
BranchLoading = ('Branch loading', <DeviceType.BranchDevice: 'Branch'>)
BranchLoadingAverage = ('Branch loading avg', <DeviceType.BranchDevice: 'Branch'>)
BranchLoadingCDF = ('Branch loading CDF', <DeviceType.BranchDevice: 'Branch'>)
BranchLoadingStd = ('Branch loading std', <DeviceType.BranchDevice: 'Branch'>)
BranchLosses = ('Branch losses', <DeviceType.BranchDevice: 'Branch'>)
BranchLossesAverage = ('Branch losses avg', <DeviceType.BranchDevice: 'Branch'>)
BranchLossesCDF = ('Branch losses CDF', <DeviceType.BranchDevice: 'Branch'>)
BranchLossesStd = ('Branch losses std', <DeviceType.BranchDevice: 'Branch'>)
BranchOverloads = ('Branch overloads', <DeviceType.BranchDevice: 'Branch'>)
BranchPower = ('Branch power', <DeviceType.BranchDevice: 'Branch'>)
BranchVoltage = ('Branch voltage drop', <DeviceType.BranchDevice: 'Branch'>)
BusActivePower = ('Bus active power', <DeviceType.BusDevice: 'Bus'>)
BusPower = ('Bus power', <DeviceType.BusDevice: 'Bus'>)
BusPowerCDF = ('Bus power CDF', <DeviceType.BusDevice: 'Bus'>)
BusReactivePower = ('Bus reactive power', <DeviceType.BusDevice: 'Bus'>)
BusShortCircuitPower = ('Bus short circuit power', <DeviceType.BusDevice: 'Bus'>)
BusVoltage = ('Bus voltage', <DeviceType.BusDevice: 'Bus'>)
BusVoltageAngle = ('Bus voltage angle', <DeviceType.BusDevice: 'Bus'>)
BusVoltageAverage = ('Bus voltage avg', <DeviceType.BusDevice: 'Bus'>)
BusVoltageCDF = ('Bus voltage CDF', <DeviceType.BusDevice: 'Bus'>)
BusVoltageModule = ('Bus voltage module', <DeviceType.BusDevice: 'Bus'>)
BusVoltagePolar = ('Bus voltage (polar)', <DeviceType.BusDevice: 'Bus'>)
BusVoltageStd = ('Bus voltage std', <DeviceType.BusDevice: 'Bus'>)
ControlledGeneratorPower = ('Controlled generator power', <DeviceType.GeneratorDevice: 'Generator'>)
ControlledGeneratorShedding = ('Controlled generator shedding', <DeviceType.GeneratorDevice: 'Generator'>)
LoadShedding = ('Load shedding', <DeviceType.LoadDevice: 'Load'>)
ShadowPrices = ('Bus shadow prices', <DeviceType.BusDevice: 'Bus'>)
SimulationError = ('Error', <DeviceType.BusDevice: 'Bus'>)
class GridCal.Engine.Simulations.result_types.SimulationTypes

Bases: enum.Enum

An enumeration.

Cascade_run = 'Cascade'
LatinHypercube_run = 'Latin Hypercube'
MonteCarlo_run = 'Monte Carlo'
OPFTimeSeries_run = 'OPF Time series'
OPF_run = 'Optimal power flow'
PowerFlow_run = 'power flow'
ShortCircuit_run = 'Short circuit'
TimeSeries_run = 'Time series power flow'
TopologyReduction_run = 'Topology reduction'
TransientStability_run = 'Transient stability'
VoltageCollapse_run = 'Voltage collapse'
Submodules
GridCal.Engine.basic_structures module
class GridCal.Engine.basic_structures.BranchImpedanceMode

Bases: enum.Enum

An enumeration.

Lower = 2
Specified = 0
Upper = 1
class GridCal.Engine.basic_structures.BusMode

Bases: enum.Enum

An enumeration.

NONE = (4,)
PQ = (1,)
PV = (2,)
REF = (3,)
STO_DISPATCH = 5
class GridCal.Engine.basic_structures.CDF(data)

Bases: object

Inverse Cumulative density function of a given array of data

get_at(prob)

Samples a number of uniform distributed points and returns the corresponding probability values given the CDF. @param prob: probability from 0 to 1 @return: Corresponding CDF value

get_sample(npoints=1)

Samples a number of uniform distributed points and returns the corresponding probability values given the CDF. @param npoints: Number of points to sample, 1 by default @return: Corresponding probabilities

plot(ax=None)

Plots the CFD @param ax: MatPlotLib axis to plot into @return:

class GridCal.Engine.basic_structures.MIPSolvers

Bases: enum.Enum

An enumeration.

CBC = 'CBC'
CPLEX = 'CPLEX'
GUROBI = 'Gurobi'
XPRESS = 'Xpress'
class GridCal.Engine.basic_structures.StatisticalCharacterization(gen_P, load_P, load_Q)

Bases: object

Object to store the statistical characterization It is useful because the statistical characterizations can be: - not grouped - grouped by day - grouped by hour

get_sample(load_enabled_idx, gen_enabled_idx, npoints=1)

Returns a 2D array containing for load and generation profiles, shape (time, load) The profile is sampled from the original data CDF functions

@param npoints: number of sampling points @return: PG: generators profile S: loads profile

plot(ax)

Plot this statistical characterization @param ax: matplotlib index @return:

class GridCal.Engine.basic_structures.TimeGrouping

Bases: enum.Enum

An enumeration.

Daily = 'Daily'
Hourly = 'Hourly'
Monthly = 'Monthly'
NoGrouping = 'No grouping'
Weekly = 'Weekly'
GridCal.Engine.basic_structures.classify_by_day(t: pandas.core.indexes.datetimes.DatetimeIndex)

Passes an array of TimeStamps to an array of arrays of indices classified by day of the year @param t: Pandas time Index array @return: list of lists of integer indices

GridCal.Engine.basic_structures.classify_by_hour(t: pandas.core.indexes.datetimes.DatetimeIndex)

Passes an array of TimeStamps to an array of arrays of indices classified by hour of the year @param t: Pandas time Index array @return: list of lists of integer indices

GridCal.Engine.basic_structures.get_time_groups(t_array: pandas.core.indexes.datetimes.DatetimeIndex, grouping: GridCal.Engine.basic_structures.TimeGrouping)

Get the indices delimiting a number of groups :param t_array: DatetimeIndex object containing dates :param grouping: TimeGrouping value :return: list of indices that determine the partitions

GridCal.Engine.grid_analysis module
class GridCal.Engine.grid_analysis.TimeSeriesResultsAnalysis(numerical_circuit: GridCal.Engine.Core.numerical_circuit.NumericalCircuit, results: GridCal.Engine.Simulations.PowerFlow.time_series_driver.TimeSeriesResults)

Bases: object

GridCal.Engine.plot_config module
GridCal.Gui package
Subpackages
GridCal.Gui.Analysis package
Submodules
GridCal.Gui.Analysis.AnalysisDialogue module
class GridCal.Gui.Analysis.AnalysisDialogue.GridAnalysisGUI(parent=None, object_types=[], circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit = None)

Bases: PySide2.QtWidgets.QDialog

analyze_click()

Analyze all the circuit data

analyze_object_type(object_type)

Analyze all

msg(text, title='Warning')

Message box :param text: Text to display :param title: Name of the window

object_type_selected()

On click-plot Returns:

plot_analysis(object_type, fig=None)

PLot data + histogram Args:

object_type: fig:
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.Analysis.AnalysisDialogue.GridErrorLog(parent=None)

Bases: PySide2.QtCore.QAbstractTableModel

add(object_type, element_name, element_index, severity, property, message)
clear()

Delete all logs

columnCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
data(self, index:PySide2.QtCore.QModelIndex, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
headerData(self, section:int, orientation:PySide2.QtCore.Qt.Orientation, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
rowCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.Analysis.AnalysisDialogue.PandasModel(data, parent=None)

Bases: PySide2.QtCore.QAbstractTableModel

Class to populate a Qt table view with a pandas data frame

columnCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
data(self, index:PySide2.QtCore.QModelIndex, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
headerData(self, section:int, orientation:PySide2.QtCore.Qt.Orientation, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
rowCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Gui.Analysis.AnalysisDialogue.get_list_model(iterable)

get Qt list model from a simple iterable :param iterable: :return: List model

GridCal.Gui.Analysis.gui module
class GridCal.Gui.Analysis.gui.Ui_Dialog

Bases: object

retranslateUi(Dialog)
setupUi(Dialog)
GridCal.Gui.Analysis.matplotlibwidget module
class GridCal.Gui.Analysis.matplotlibwidget.MatplotlibWidget(parent=None)

Bases: PySide2.QtWidgets.QWidget

clear(force=False)

Clear the interface Args:

force: Remove the object and create a new one (brute force)

Returns:

get_axis()
get_figure()
plot(x, y, title='', xlabel='', ylabel='')

Plot series Args:

x: X values y: Y values title: Title xlabel: Label for X ylabel: Label for Y

Returns:

redraw()

Redraw the interface Returns:

setTitle(text)

Sets the figure title

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.Analysis.matplotlibwidget.MplCanvas

Bases: matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg

pan_factory(ax)

Mouse pan handler

rec_zoom()
setTitle(text)

Sets the figure title

set_graph_mode()

Sets the borders to nicely display graphs

set_last_zoom()
staticMetaObject = <PySide2.QtCore.QMetaObject object>
zoom_factory(ax, base_scale=1.2)

Mouse zoom handler

GridCal.Gui.Analysis.update_gui_file module

Script to update correctly the main GUI (.py) file from the Qt design (.ui) file

GridCal.Gui.Main package
Submodules
GridCal.Gui.Main.GridCalMain module
GridCal.Gui.Main.MainWindow module
GridCal.Gui.Main.icons_rc module
GridCal.Gui.Main.icons_rc.qCleanupResources()
GridCal.Gui.Main.icons_rc.qInitResources()
GridCal.Gui.Main.matplotlibwidget module
GridCal.Gui.Main.update_gui_file module

Script to update correctly the main GUI (.py) file from the Qt design (.ui) file

GridCal.Gui.ProfilesInput package
Submodules
GridCal.Gui.ProfilesInput.excel_dialog module
class GridCal.Gui.ProfilesInput.excel_dialog.ExcelDialog(parent=None, excel_file=None)

Bases: PySide2.QtWidgets.QDialog

accepted_action()
rejected_action()
staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Gui.ProfilesInput.excel_sheet_selection module
class GridCal.Gui.ProfilesInput.excel_sheet_selection.Ui_ExcelSelectionDialog

Bases: object

retranslateUi(ExcelSelectionDialog)
setupUi(ExcelSelectionDialog)
GridCal.Gui.ProfilesInput.gui module
class GridCal.Gui.ProfilesInput.gui.Ui_Dialog

Bases: object

retranslateUi(Dialog)
setupUi(Dialog)
GridCal.Gui.ProfilesInput.icons_rc module
GridCal.Gui.ProfilesInput.icons_rc.qCleanupResources()
GridCal.Gui.ProfilesInput.icons_rc.qInitResources()
GridCal.Gui.ProfilesInput.matplotlibwidget module
class GridCal.Gui.ProfilesInput.matplotlibwidget.MatplotlibWidget(parent=None)

Bases: PySide2.QtWidgets.QWidget

clear(force=False)

Clear the interface Args:

force: Remove the object and create a new one (brute force)

Returns:

get_axis()
get_figure()
plot(x, y, title='', xlabel='', ylabel='')

Plot series Args:

x: X values y: Y values title: Title xlabel: Label for X ylabel: Label for Y

Returns:

redraw()

Redraw the interface Returns:

setTitle(text)

Sets the figure title

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.ProfilesInput.matplotlibwidget.MplCanvas

Bases: matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg

pan_factory(ax)

Mouse pan handler

rec_zoom()
setTitle(text)

Sets the figure title

set_graph_mode()

Sets the borders to nicely display graphs

set_last_zoom()
staticMetaObject = <PySide2.QtCore.QMetaObject object>
zoom_factory(ax, base_scale=1.2)

Mouse zoom handler

GridCal.Gui.ProfilesInput.profile_dialogue module
class GridCal.Gui.ProfilesInput.profile_dialogue.MultiplierType

Bases: enum.Enum

An enumeration.

Cosfi = 2
Mult = (1,)
class GridCal.Gui.ProfilesInput.profile_dialogue.PandasModel(data, parent=None)

Bases: PySide2.QtCore.QAbstractTableModel

Class to populate a Qt table view with a pandas data frame

columnCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
data(self, index:PySide2.QtCore.QModelIndex, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
headerData(self, section:int, orientation:PySide2.QtCore.Qt.Orientation, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
rowCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.ProfilesInput.profile_dialogue.ProfileInputGUI(parent=None, list_of_objects=[], magnitudes=[''])

Bases: PySide2.QtWidgets.QDialog

assignation_table_double_click()

Set the selected profile into the clicked slot

Performs an automatic link between the sources and the objectives based on the names

display_associations()

@return:

do_it()

Close. The data has to be queried later to the object by the parent by calling get_association_data

get_association_data()

Return a dictionary with the data association @return:

get_multiplier()

Gets the necessary multiplier to pass the profile units to Mega Remember that the power units in GridCal are the MVA

get_profile(parent=None, labels=None, alsoQ=None)

Return ths assigned profiles @return:

Array of profiles assigned to the input objectives Array specifying which objectives are not assigned
import_profile()

Select a file to be loaded

Links the selected origin with all the destinations

Links the selected origin with the selected destinations

make_association(source_idx, obj_idx, scale=None, cosfi=None, mult=None, col_idx=None)

Makes an association in the associations table

msg(text, title='Warning')

Message box :param text: Text to display :param title: Name of the window

static normalize_string(s)

Normalizes a string

objectives_list_double_click()

Link source to objective when the objective item is double clicked :return:

print_profile()

prints the profile clicked on the table @return:

Random link

set_multiplier(type)

Set the table multipliers

sources_list_double_click()

When an item in the sources list is double clicked, plot the series :return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Gui.ProfilesInput.profile_dialogue.get_list_model(iterable)

get Qt list model from a simple iterable :param iterable: :return: List model

GridCal.Gui.ProfilesInput.update_gui_file module

Script to update correctly the main GUI (.py) file from the Qt design (.ui) file

GridCal.Gui.TowerBuilder package
Submodules
GridCal.Gui.TowerBuilder.LineBuilderDialogue module
class GridCal.Gui.TowerBuilder.LineBuilderDialogue.TowerBuilderGUI(parent=None, tower=None, wires_catalogue=[])

Bases: PySide2.QtWidgets.QDialog

add_wire_to_collection()

Add new wire to collection :return:

add_wire_to_tower()

Add wire to tower :return:

compute()
Returns:
delete_wire_from_collection()

Delete wire from the collection :return:

delete_wire_from_tower()

Delete wire from the tower :return:

example_1()
example_2()
msg(text, title='Warning')

Message box :param text: Text to display :param title: Name of the window

name_changed()

Change name :return:

plot()

PLot the tower distribution

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Gui.TowerBuilder.gui module
class GridCal.Gui.TowerBuilder.gui.Ui_Dialog

Bases: object

retranslateUi(Dialog)
setupUi(Dialog)
GridCal.Gui.TowerBuilder.icons_rc module
GridCal.Gui.TowerBuilder.icons_rc.qCleanupResources()
GridCal.Gui.TowerBuilder.icons_rc.qInitResources()
GridCal.Gui.TowerBuilder.matplotlibwidget module
class GridCal.Gui.TowerBuilder.matplotlibwidget.MatplotlibWidget(parent=None)

Bases: PySide2.QtWidgets.QWidget

clear(force=False)

Clear the interface Args:

force: Remove the object and create a new one (brute force)

Returns:

get_axis()
get_figure()
plot(x, y, title='', xlabel='', ylabel='')

Plot series Args:

x: X values y: Y values title: Title xlabel: Label for X ylabel: Label for Y

Returns:

redraw()

Redraw the interface Returns:

setTitle(text)

Sets the figure title

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.TowerBuilder.matplotlibwidget.MplCanvas

Bases: matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg

pan_factory(ax)

Mouse pan handler

rec_zoom()
setTitle(text)

Sets the figure title

set_graph_mode()

Sets the borders to nicely display graphs

set_last_zoom()
staticMetaObject = <PySide2.QtCore.QMetaObject object>
zoom_factory(ax, base_scale=1.2)

Mouse zoom handler

GridCal.Gui.TowerBuilder.test_ module
class GridCal.Gui.TowerBuilder.test_.TowerBuilderGUI(parent=None)

Bases: PySide2.QtWidgets.QDialog

add_wire_to_collection()

Add new wire to collection :return:

delete_wire_from_collection()

Delete wire from the collection :return:

msg(text, title='Warning')

Message box :param text: Text to display :param title: Name of the window

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.TowerBuilder.test_.Wire(name, x, y, gmr, r)

Bases: object

class GridCal.Gui.TowerBuilder.test_.WiresCollection(parent=None)

Bases: PySide2.QtCore.QAbstractTableModel

add(wire: GridCal.Gui.TowerBuilder.test_.Wire)

Add wire :param wire: :return:

columnCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
data(self, index:PySide2.QtCore.QModelIndex, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
delete(index)

Delete wire :param index: :return:

headerData(self, section:int, orientation:PySide2.QtCore.Qt.Orientation, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
parent(self) → PySide2.QtCore.QObject

parent(self, child:PySide2.QtCore.QModelIndex) -> PySide2.QtCore.QModelIndex

rowCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
setData(index, value, role=PySide2.QtCore.Qt.ItemDataRole.DisplayRole)

Set data by simple editor (whatever text) :param index: :param value: :param role:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Gui.TowerBuilder.tower_model module
class GridCal.Gui.TowerBuilder.tower_model.TowerModel(parent=None, edit_callback=None, tower: GridCal.Engine.Devices.tower.Tower = None)

Bases: PySide2.QtCore.QAbstractTableModel

add(wire: GridCal.Engine.Devices.wire.Wire)

Add wire :param wire: :return:

columnCount(parent=None)
Parameters:parent
Returns:
data(index, role=PySide2.QtCore.Qt.ItemDataRole.DisplayRole)
Parameters:
  • index
  • role
Returns:

delete(index)

Delete wire :param index: :return:

delete_by_name(wire: GridCal.Engine.Devices.wire.Wire)

Delete wire by name :param wire: Wire object

flags(index)
Parameters:index
Returns:
headerData(p_int, orientation, role)
Parameters:
  • p_int
  • orientation
  • role
Returns:

is_used(wire: GridCal.Engine.Devices.wire.Wire)
Parameters:wire
Returns:
rowCount(parent=None)
Parameters:parent
Returns:
setData(index, value, role=PySide2.QtCore.Qt.ItemDataRole.DisplayRole)

Set data by simple editor (whatever text) :param index: :param value: :param role:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Gui.TowerBuilder.update_gui_file module

Script to update correctly the main GUI (.py) file from the Qt design (.ui) file

Submodules
GridCal.Gui.ConsoleWidget module
class GridCal.Gui.ConsoleWidget.ConsoleWidget(customBanner=None, *args, **kwargs)

Bases: qtconsole.rich_jupyter_widget.RichJupyterWidget

Convenience class for a live IPython console widget. We can replace the standard banner using the customBanner argument

clear()

Clears the terminal

execute_command(command)

Execute a command in the frame of the console widget

print_text(text)

Prints some plain text to the console

push_vars(variableDict)

Given a dictionary containing name / value pairs, push those variables to the IPython console widget

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Gui.GeneralDialogues module
class GridCal.Gui.GeneralDialogues.ElementsDialogue(name, elements: [])

Bases: PySide2.QtWidgets.QDialog

Selected elements dialogue window

accept_click()
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GeneralDialogues.LogsDialogue(name, logs: [])

Bases: PySide2.QtWidgets.QDialog

New profile dialogue window

accept_click()
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GeneralDialogues.NewProfilesStructureDialogue

Bases: PySide2.QtWidgets.QDialog

New profile dialogue window

accept_click()
get_values()
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GeneralDialogues.ProfileTypes

Bases: enum.Enum

An enumeration.

Generators = 2
Loads = (1,)
GridCal.Gui.GeneralDialogues.get_list_model(lst, checks=False)

Pass a list to a list model

GridCal.Gui.GridEditorWidget module
class GridCal.Gui.GridEditorWidget.BatteryGraphicItem(parent, api_obj, diagramScene)

Bases: PySide2.QtWidgets.QGraphicsItemGroup

contextMenuEvent(event)

Display context menu @param event: @return:

enable_disable_toggle()

@return:

mousePressEvent(QGraphicsSceneMouseEvent)

mouse press: display the editor :param QGraphicsSceneMouseEvent: :return:

plot()

Plot API objects profiles

remove()

Remove this element @return:

set_enable(val=True)

Set the enable value, graphically and in the API @param val: @return:

update_line(pos)

Update the line that joins the parent and this object :param pos: position of this object

class GridCal.Gui.GridEditorWidget.BranchGraphicItem(fromPort, toPort, diagramScene, width=5, branch: GridCal.Engine.Devices.branch.Branch = None)

Bases: PySide2.QtWidgets.QGraphicsLineItem

add_to_templates()

Open the appropriate editor dialogue :return:

contextMenuEvent(event)

Show context menu @param event: @return:

edit()

Open the appropriate editor dialogue :return:

enable_disable_toggle()

@return:

make_reactance_symbol()

Make the reactance symbol :return:

make_switch_symbol()

Mathe the switch symbol :return:

make_transformer_symbol()

create the transformer simbol :return:

mouseDoubleClickEvent(event)

On double click, edit :param event: :return:

mousePressEvent(QGraphicsSceneMouseEvent)

mouse press: display the editor :param QGraphicsSceneMouseEvent: :return:

plot_profiles()

Plot the time series profiles @return:

redraw()

Redraw the line with the given positions @return:

reduce()

Reduce this branch

remove()

Remove this object in the diagram and the API @return:

remove_symbol()
remove_widget()

Remove this object in the diagram @return:

setBeginPos(pos1)

Set the starting position @param pos1: @return:

setEndPos(endpos)

Set the starting position @param endpos: @return:

setFromPort(fromPort)

Set the From terminal in a connection @param fromPort: @return:

setToPort(toPort)

Set the To terminal in a connection @param toPort: @return:

setToolTipText(toolTip: str)

Set branch tool tip text Args:

toolTip: text
set_enable(val=True)

Set the enable value, graphically and in the API @param val: @return:

set_pen(pen)

Set pen to all objects Args:

pen:
tap_down()

Set one tap down

tap_up()

Set one tap up

update_symbol()

Make the branch symbol :return:

class GridCal.Gui.GridEditorWidget.BusGraphicItem(diagramScene, name='Untitled', parent=None, index=0, editor=None, bus: GridCal.Engine.Devices.bus.Bus = None, pos: PySide2.QtCore.QPoint = None)

Bases: PySide2.QtWidgets.QGraphicsRectItem

Represents a block in the diagram Has an x and y and width and height width and height can only be adjusted with a tip in the lower right corner.

  • in and output ports
  • parameters
  • description
adapt()

Set the bus width according to the label text

add_battery(api_obj=None)

Returns:

add_big_marker(color=PySide2.QtCore.Qt.GlobalColor.red)

Add a big marker to the bus Args:

color: Qt Color ot the marker
add_generator(api_obj=None)

Returns:

add_load(api_obj=None)

Returns:

add_shunt(api_obj=None)

Returns:

add_static_generator(api_obj=None)

Returns:

arrange_children()

This function sorts the load and generators icons Returns:

Nothing
change_size(w, h)

Resize block function @param w: @param h: @return:

contextMenuEvent(event)

Display context menu @param event: @return:

create_children_icons()

Create the icons of the elements that are attached to the API bus object Returns:

Nothing
delete_all_connections()
delete_big_marker()

Delete the big marker

enable_disable_sc()

Returns:

enable_disable_toggle()

Toggle bus element state @return:

merge(other_bus_graphic)
mouseDoubleClickEvent(event)

Mouse double click :param event: event object

mouseMoveEvent(event: PySide2.QtWidgets.QGraphicsSceneMouseEvent)

On mouse move of this object… Args:

event: QGraphicsSceneMouseEvent inherited
mousePressEvent(event)

mouse press: display the editor :param QGraphicsSceneMouseEvent: :return:

plot_profiles()

@return:

reduce()

Reduce this bus :return:

remove()

Remove this element @return:

set_tile_color(brush)

Set the color of the title Args:

brush: Qt Color
update()

Update the object :return:

class GridCal.Gui.GridEditorWidget.Circle(parent)

Bases: GridCal.Gui.GridEditorWidget.LineUpdateMixin, PySide2.QtWidgets.QGraphicsEllipseItem

class GridCal.Gui.GridEditorWidget.DiagramScene(parent=None, circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit = None)

Bases: PySide2.QtWidgets.QGraphicsScene

mouseMoveEvent(mouseEvent)

@param mouseEvent: @return:

mouseReleaseEvent(mouseEvent)

@param mouseEvent: @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GridEditorWidget.EditorGraphicsView(scene, parent=None, editor=None, lat0=42, lon0=55, zoom=3)

Bases: PySide2.QtWidgets.QGraphicsView

adapt_map_size()
add_bus(bus: GridCal.Engine.Devices.bus.Bus, explode_factor=1.0)

Add bus Args:

bus: GridCal Bus object explode_factor: factor to position the node
dragEnterEvent(event)

@param event: @return:

dragMoveEvent(event)

Move element @param event: @return:

dropEvent(event)

Create an element @param event: @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
view_map(flag=True)
Parameters:flag
Returns:
wheelEvent(event)

Zoom @param event: @return:

class GridCal.Gui.GridEditorWidget.GeneralItem

Bases: object

contextMenuEvent(event)
delete_all_connections()
editParameters()
remove_()

@return:

rotate(angle)
rotate_clockwise()
rotate_counterclockwise()
class GridCal.Gui.GridEditorWidget.GeneratorGraphicItem(parent, api_obj, diagramScene)

Bases: PySide2.QtWidgets.QGraphicsItemGroup

contextMenuEvent(event)

Display context menu @param event: @return:

enable_disable_toggle()

@return:

mousePressEvent(QGraphicsSceneMouseEvent)

mouse press: display the editor :param QGraphicsSceneMouseEvent: :return:

plot()

Plot API objects profiles

remove()

Remove this element @return:

set_enable(val=True)

Set the enable value, graphically and in the API @param val: @return:

update_line(pos)

Update the line that joins the parent and this object :param pos: position of this object

class GridCal.Gui.GridEditorWidget.GridEditor(circuit: GridCal.Engine.Core.multi_circuit.MultiCircuit, lat0=42, lon0=55, zoom=3)

Bases: PySide2.QtWidgets.QSplitter

add_branch(branch)

Add branch to the schematic :param branch: Branch object

auto_layout()

Automatic layout of the nodes

bigger_nodes()

Expand the grid @return:

center_nodes()

Center the view in the nodes @return: Nothing

export(filename, w=1920, h=1080)

Save the grid to a png file :return:

sceneMouseMoveEvent(event)

@param event: @return:

sceneMouseReleaseEvent(event)

Finalize the branch creation if its drawing ends in a terminal @param event: @return:

schematic_from_api(explode_factor=1.0)

Generate schematic from the API :param explode_factor: factor to separate the nodes :return: Nothing

set_limits(min_x, max_x, min_y, max_y, margin_factor=0.1)

Set the picture limits :param min_x: Minimum x value of the buses location :param max_x: Maximum x value of the buses location :param min_y: Minimum y value of the buses location :param max_y: Maximum y value of the buses location :param margin_factor: factor of separation between the buses

smaller_nodes()

Contract the grid @return:

startConnection(port: GridCal.Gui.GridEditorWidget.TerminalItem)

Start the branch creation @param port: @return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GridEditorWidget.HandleItem(parent=None)

Bases: PySide2.QtWidgets.QGraphicsEllipseItem

A handle that can be moved by the mouse: Element to resize the boxes

itemChange(change, value)

@param change: @param value: @return:

class GridCal.Gui.GridEditorWidget.LibraryModel(parent=None)

Bases: PySide2.QtGui.QStandardItemModel

Items model to host the draggable icons

mimeData(idxs)

@param idxs: @return:

mimeTypes()

@return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GridEditorWidget.LineEditor(branch: GridCal.Engine.Devices.branch.Branch, Sbase=100)

Bases: PySide2.QtWidgets.QDialog

accept_click()

Set the values :return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GridEditorWidget.LineUpdateMixin(parent)

Bases: object

itemChange(change, value)
class GridCal.Gui.GridEditorWidget.LoadGraphicItem(parent, api_obj, diagramScene)

Bases: PySide2.QtWidgets.QGraphicsItemGroup

contextMenuEvent(event)

Display context menu @param event: @return:

enable_disable_toggle()

@return:

mousePressEvent(QGraphicsSceneMouseEvent)

mouse press: display the editor :param QGraphicsSceneMouseEvent: :return:

plot()
remove()

Remove this element @return:

set_enable(val=True)

Set the enable value, graphically and in the API @param val: @return:

update_line(pos)

Update the line that joins the parent and this object :param pos: position of this object

class GridCal.Gui.GridEditorWidget.MapWidget(scene: PySide2.QtWidgets.QGraphicsScene, view: PySide2.QtWidgets.QGraphicsView, lat0=42, lon0=55, zoom=3)

Bases: PySide2.QtWidgets.QGraphicsRectItem

change_size(w, h)

Resize block function @param w: @param h: @return:

load_map(lat0=42, lon0=55, zoom=3)

Load a map image into the widget :param lat0: :param lon0: :param zoom: 1~14

paint(painter, option, widget=None)

Action that happens on widget repaint :param painter: :param option: :param widget:

repaint()

Reload with the last parameters

class GridCal.Gui.GridEditorWidget.ObjectFactory

Bases: object

get_box()

@return:

get_circle()

@return:

class GridCal.Gui.GridEditorWidget.ParameterDialog(parent=None)

Bases: PySide2.QtWidgets.QDialog

OK()
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GridEditorWidget.Polygon(parent)

Bases: GridCal.Gui.GridEditorWidget.LineUpdateMixin, PySide2.QtWidgets.QGraphicsPolygonItem

class GridCal.Gui.GridEditorWidget.QLine(parent)

Bases: GridCal.Gui.GridEditorWidget.LineUpdateMixin, PySide2.QtWidgets.QGraphicsLineItem

class GridCal.Gui.GridEditorWidget.ShuntGraphicItem(parent, api_obj, diagramScene)

Bases: PySide2.QtWidgets.QGraphicsItemGroup

contextMenuEvent(event)

Display context menu @param event: @return:

enable_disable_toggle()

@return:

mousePressEvent(QGraphicsSceneMouseEvent)

mouse press: display the editor :param QGraphicsSceneMouseEvent: :return:

plot()

Plot API objects profiles

remove()

Remove this element @return:

set_enable(val=True)

Set the enable value, graphically and in the API @param val: @return:

update_line(pos)

Update the line that joins the parent and this object :param pos: position of this object

class GridCal.Gui.GridEditorWidget.Square(parent)

Bases: GridCal.Gui.GridEditorWidget.LineUpdateMixin, PySide2.QtWidgets.QGraphicsRectItem

class GridCal.Gui.GridEditorWidget.StaticGeneratorGraphicItem(parent, api_obj, diagramScene)

Bases: PySide2.QtWidgets.QGraphicsItemGroup

contextMenuEvent(event)

Display context menu @param event: @return:

enable_disable_toggle()

@return:

mousePressEvent(QGraphicsSceneMouseEvent)

mouse press: display the editor :param QGraphicsSceneMouseEvent: :return:

plot()

Plot API objects profiles

remove()

Remove this element @return:

set_enable(val=True)

Set the enable value, graphically and in the API @param val: @return:

update_line(pos)

Update the line that joins the parent and this object :param pos: position of this object

class GridCal.Gui.GridEditorWidget.TerminalItem(name, editor=None, parent=None, h=10, w=10)

Bases: PySide2.QtWidgets.QGraphicsRectItem

Represents a connection point to a subsystem

itemChange(change, value)

@param change: @param value: This is a QPointF object with the coordinates of the upper left corner of the TerminalItem @return:

mousePressEvent(event)

Start a connection Args:

event:

Returns:

process_callbacks(value)
remove_all_connections()

Removes all the terminal connections Returns:

class GridCal.Gui.GridEditorWidget.TransformerEditor(branch: GridCal.Engine.Devices.branch.Branch, Sbase=100)

Bases: PySide2.QtWidgets.QDialog

accept_click()

Create transformer type and get the impedances :return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Gui.GuiFunctions module
class GridCal.Gui.GuiFunctions.BranchObjectModel(objects, editable_headers, parent=None, editable=False, non_editable_attributes=[], transposed=False, check_unique=[], catalogue_dict={})

Bases: GridCal.Gui.GuiFunctions.ObjectsModel

set_delegates()

Set the cell editor types depending on the attribute_types array :return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GuiFunctions.ComboDelegate(parent, objects, object_names)

Bases: PySide2.QtWidgets.QItemDelegate

commitData = <PySide2.QtCore.Signal object>

A delegate that places a fully functioning QComboBox in every cell of the column to which it’s applied

createEditor(self, parent:PySide2.QtWidgets.QWidget, option:PySide2.QtWidgets.QStyleOptionViewItem, index:PySide2.QtCore.QModelIndex) → PySide2.QtWidgets.QWidget
currentIndexChanged()
setEditorData(self, editor:PySide2.QtWidgets.QWidget, index:PySide2.QtCore.QModelIndex)
setModelData(self, editor:PySide2.QtWidgets.QWidget, model:PySide2.QtCore.QAbstractItemModel, index:PySide2.QtCore.QModelIndex)
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GuiFunctions.ComplexDelegate(parent)

Bases: PySide2.QtWidgets.QItemDelegate

commitData = <PySide2.QtCore.Signal object>

A delegate that places a fully functioning Complex Editor in every cell of the column to which it’s applied

createEditor(parent, option, index)
Parameters:
  • parent
  • option
  • index
Returns:

returnPressed()
Returns:
setEditorData(editor, index)
Parameters:
  • editor
  • index
Returns:

setModelData(editor, model, index)
Parameters:
  • editor
  • model
  • index
Returns:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GuiFunctions.EnumModel(list_of_enums)

Bases: PySide2.QtCore.QAbstractListModel

data(self, index:PySide2.QtCore.QModelIndex, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
rowCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GuiFunctions.FloatDelegate(parent, min_=-9999, max_=9999)

Bases: PySide2.QtWidgets.QItemDelegate

commitData = <PySide2.QtCore.Signal object>

A delegate that places a fully functioning QDoubleSpinBox in every cell of the column to which it’s applied

createEditor(self, parent:PySide2.QtWidgets.QWidget, option:PySide2.QtWidgets.QStyleOptionViewItem, index:PySide2.QtCore.QModelIndex) → PySide2.QtWidgets.QWidget
returnPressed()
setEditorData(self, editor:PySide2.QtWidgets.QWidget, index:PySide2.QtCore.QModelIndex)
setModelData(self, editor:PySide2.QtWidgets.QWidget, model:PySide2.QtCore.QAbstractItemModel, index:PySide2.QtCore.QModelIndex)
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GuiFunctions.MeasurementsModel(circuit)

Bases: PySide2.QtCore.QAbstractListModel

data(self, index:PySide2.QtCore.QModelIndex, role:int=PySide2.QtCore.Qt.ItemDataRole.DisplayRole) → typing.Any
rowCount(self, parent:PySide2.QtCore.QModelIndex=Invalid(PySide2.QtCore.QModelIndex)) → int
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GuiFunctions.ObjectHistory(max_undo_states=100)

Bases: object

add_state(action_name, data: dict)

Add an undo state :param action_name: name of the action that was performed :param data: dictionary {column index -> profile array}

can_redo()

is it possible to redo? :return: True / False

can_undo()

Is it possible to undo? :return: True / False

redo()

Re-do table :return: table instance

undo()

Un-do table :return: table instance

class GridCal.Gui.GuiFunctions.ObjectsModel(objects, editable_headers, parent=None, editable=False, non_editable_attributes=[], transposed=False, check_unique=[])

Bases: PySide2.QtCore.QAbstractTableModel

Class to populate a Qt table view with the properties of objects

attr_taken(attr, val)

Checks if the attribute value is taken :param attr: :param val: :return:

columnCount(parent=None)

Get number of columns :param parent: :return:

copy_to_column(index)

Copy the value pointed by the index to all the other cells in the column :param index: QModelIndex instance :return:

data(index, role=None)

Get the data to display :param index: :param role: :return:

data_with_type(index)

Get the data to display :param index: :return:

flags(index)

Get the display mode :param index: :return:

headerData(p_int, orientation, role)

Get the headers to display :param p_int: :param orientation: :param role: :return:

rowCount(parent=None)

Get number of rows :param parent: :return:

setData(index, value, role=None)

Set data by simple editor (whatever text) :param index: :param value: :param role: :return:

set_delegates()

Set the cell editor types depending on the attribute_types array :return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
update()

update table

class GridCal.Gui.GuiFunctions.PandasModel(data: pandas.core.frame.DataFrame, parent=None, editable=False, editable_min_idx=-1, decimals=6)

Bases: PySide2.QtCore.QAbstractTableModel

Class to populate a Qt table view with a pandas data frame

columnCount(parent=None)
Parameters:parent
Returns:
copy_to_clipboard(mode=None)

Copy profiles to clipboard Args:

mode: ‘real’, ‘imag’, ‘abs’
copy_to_column(row, col)

Copies one value to all the column @param row: Row of the value @param col: Column of the value @return: Nothing

data(index, role=PySide2.QtCore.Qt.ItemDataRole.DisplayRole)
Parameters:
  • index
  • role
Returns:

flags(self, index:PySide2.QtCore.QModelIndex) → PySide2.QtCore.Qt.ItemFlags
get_data(mode=None)
Args:
mode: ‘real’, ‘imag’, ‘abs’

Returns: index, columns, data

headerData(p_int, orientation, role)
Parameters:
  • p_int
  • orientation
  • role
Returns:

rowCount(parent=None)
Parameters:parent
Returns:
save_to_excel(file_name, mode)
Args:
file_name: mode: ‘real’, ‘imag’, ‘abs’

Returns:

setData(index, value, role=PySide2.QtCore.Qt.ItemDataRole.DisplayRole)
Parameters:
  • index
  • value
  • role
Returns:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GuiFunctions.ProfilesModel(multi_circuit, device_type: GridCal.Engine.Devices.meta_devices.DeviceType, magnitude, format, parent, max_undo_states=100)

Bases: PySide2.QtCore.QAbstractTableModel

Class to populate a Qt table view with profiles from objects

add_state(columns, action_name='')

Compile data of an action and store the data in the undo history :param columns: list of column indices changed :param action_name: name of the action :return: None

columnCount(parent=None)

Get number of columns :param parent: :return:

copy_to_clipboard()

Copy profiles to clipboard

data(index, role=PySide2.QtCore.Qt.ItemDataRole.DisplayRole)

Get the data to display :param index: :param role: :return:

flags(index)

Get the display mode :param index: :return:

headerData(p_int, orientation, role)

Get the headers to display :param p_int: :param orientation: :param role: :return:

paste_from_clipboard(row_idx=0, col_idx=0)
Args:
row_idx: col_idx:
redo()

Re-do table changes

restore(data: dict)

Set profiles data from undo history :param data: dictionary comming from the history :return:

rowCount(parent=None)

Get number of rows :param parent: :return:

setData(index, value, role=PySide2.QtCore.Qt.ItemDataRole.DisplayRole)

Set data by simple editor (whatever text) :param index: :param value: :param role: :return:

set_delegates()

Set the cell editor types depending on the attribute_types array :return:

staticMetaObject = <PySide2.QtCore.QMetaObject object>
undo()

Un-do table changes

update()
class GridCal.Gui.GuiFunctions.TextDelegate(parent)

Bases: PySide2.QtWidgets.QItemDelegate

commitData = <PySide2.QtCore.Signal object>

A delegate that places a fully functioning QLineEdit in every cell of the column to which it’s applied

createEditor(self, parent:PySide2.QtWidgets.QWidget, option:PySide2.QtWidgets.QStyleOptionViewItem, index:PySide2.QtCore.QModelIndex) → PySide2.QtWidgets.QWidget
returnPressed()
setEditorData(self, editor:PySide2.QtWidgets.QWidget, index:PySide2.QtCore.QModelIndex)
setModelData(self, editor:PySide2.QtWidgets.QWidget, model:PySide2.QtCore.QAbstractItemModel, index:PySide2.QtCore.QModelIndex)
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class GridCal.Gui.GuiFunctions.TreeDelegate(parent, data={})

Bases: PySide2.QtWidgets.QItemDelegate

commitData = <PySide2.QtCore.Signal object>

A delegate that places a fully functioning QComboBox in every cell of the column to which it’s applied

createEditor(self, parent:PySide2.QtWidgets.QWidget, option:PySide2.QtWidgets.QStyleOptionViewItem, index:PySide2.QtCore.QModelIndex) → PySide2.QtWidgets.QWidget
double_click()
setEditorData(self, editor:PySide2.QtWidgets.QWidget, index:PySide2.QtCore.QModelIndex)
setModelData(self, editor:PySide2.QtWidgets.QWidget, model:PySide2.QtCore.QAbstractItemModel, index:PySide2.QtCore.QModelIndex)
staticMetaObject = <PySide2.QtCore.QMetaObject object>
GridCal.Gui.GuiFunctions.get_checked_indices(mdl: <PySide2.QtGui.QStandardItemModel object at 0x7f2aea374088>)

Get a list of the selected indices in a QStandardItemModel :param mdl: :return:

GridCal.Gui.GuiFunctions.get_list_model(lst, checks=False)

Pass a list to a list model

Submodules

GridCal.ExecuteGridCal module

Theory behind GridCal

GridCal has a great deal of numerical algorithms with little innovations that make this program possible. The featured sections try to describe these innovations such that you can extend on them.

The assets manager: from objects to matrices

One of the key things that make GridCal special is that the devices of the electrical grids are handled by an object-oriented asset manager. This means that the real-life objects like generators are objects in GridCal too, instead of a row in a database or table. This makes the handling of the information very flexible and maintainable. If I had to pinpoint the biggest advantage of GridCal it would be this one.

Most (if not all) other open source grid calculation tools use tables as input; The advantage of a table based management of the information is that it is more or less easy to compose the calculation matrices from those tables. The disadvantage is that maintaining and extending a table-based model is hard. GridCal started out being table-based too, but soon this approach was disregarded because of the limitations this design imposes.

This object oriented approach and the structure used in GridCal seems to make it harder for most electrical engineers when getting started developing with GridCal. It is hard, because the structure might seem over-engineered, however it is this structure what enables seamless multi-island calculations and the easy handling of time series, so it is key to explain “why” and “how”. In this section I’ll explain the information “compilation” procedure used in GridCal, hoping that this makes GridCal more approachable for future contributors.

Structure

The structure used to compile the information might seem convoluted and unnecessary. Experience has proven that this structure is very fast and it scales well (it is future-proof). Bear in mind that GridCal is not only a calculation library, but it tries to make it as simple as possible for the user to input data, hence there is a careful equilibrium between usability and performance.

Compilation process

Objects compilation process

MultiCircuit

The multi-circuit, or asset manager is the main object in GridCal. It represents a group of objects conforming an electrical grid

NumericalCircuit

The numerical circuit contains the information of the objects in 1-dimensional arrays (vectors) ready to compute the topology of the grid and the calculation matrices and vectors. It also contains the connectivity matrices between objects.

This module is the one doing all the heavy lifting; For the computations ahead we will need to have a number of admittance-based matrices. We will compose matrices and vectors for the complete circuit, and then split those per circuit island (CalculationInputs objects). Remember that the calculation matrices are only valid for island circuits, this is because multi-island circuits present singular admittance matrices that lead to no numerical solution by their own.

Element bus aggregations

Element bus aggregations

The logic is to have a vector of magnitudes of an object type (i.e. all the active power power for all the generators) and a connectivity matrix that relates the generators with the buses of the grid. Multiplying the connectivity matrix by the element magnitudes vector we obtain the buses magnitudes vector that we need for calculation.

Power, current and voltage vectors

Power injections complex array:

[S_{l} ]= [C_{l,bus}]^\top \cdot [load_S]

[S_{g}]= [C_{g,bus}]^\top \cdot [generation_S]

[S_{bus}] = [S_{g}]  - [S_{l}]

Current complex injections array:

[I_{bus}] = - [C_{l,bus}]^\top \cdot [load_I]

Where:

Magnitude Dimensions Description
[S_{l}] #bus, 1 Array of complex power injections due to the load (will have a negative sign). Size: number of buses.
[C_{l,bus}] #load, #bus Connectivity of loads and buses.
[load_S] #load, 1 Array of complex load power values
[S_{g}] #bus, 1 Connectivity of loads and buses.
[C_{g,bus}] #generators, #bus Connectivity of generators and buses.
[generation_S] #generators, #1 Array of generators power injections.
[S_{bus}] #bus, 1 Nodal power injections array (positive: generation, negative: load).
[load_I] #load, 1 Array of complex load current values
[I_bus] #bus, 1 Nodal current injections array (positive: generation, negative: load).
Admittance matrix

The calculation of the admittance matrix in GridCal is completely vectorized. It features

Put together the branch magnitudes to composes the Series admittance the shunt admittance and the tap shift.

[Ys] = \frac{1}{[R] + j \cdot [X]}

[GBc] = [G] + j \cdot [B]

[tap] = [tap_{module}] \cdot e^{j \cdot [tap_{angle}]}

Where:

Magnitude Dimensions Units Description
[Ys] #branch, 1 p.u. Array of branch series admittances.
[GBc] #branch, 1 p.u. Array of branch shunt admittances.
[tap] #branch, 1 p.u. Array of branch complex tap shifts.
[R] #branch, 1 p.u. Array of branch resistance.
[X] #branch, 1 p.u. Array of branch reactances.
[G] #branch, 1 p.u. Array of branch conductances.
[B] #branch, 1 p.u. Array of branch susceptances.
[tap_{module}] #branch, 1 p.u. Array of tap modules.
[tap_{angle}] #branch, 1 Radians Array of tap shift angles.

Compute the branch primitives:

[Y_{tt}] = \frac{\frac{[Ys] + [GBc]}{2}}{[tap_t] \cdot [tap_t]}

[Y_{ff}] = \frac{\frac{[Ys] + [GBc]}{2}}{[tap_f] \cdot [tap_f] \cdot [tap] \cdot [tap]^*}

[Y_{ft}] = - \frac{Ys}{[tap_f] \cdot [tap_t] \cdot [tap]^*}

[Y_{tf}] = - \frac{Ys}{[tap_t] \cdot [tap_f] \cdot [tap]}

Magnitude Dimensions Description

[Y_{ff}], [Y_{tt}],

[Y_{ft}], [Y_{tf}]

#branch, 1 Arrays of the bus connected admittances from-from, to-to, from-to and to-from
[tap_f], [tap_t] #branch, 1 Array of tap modules that appear due to the voltage difference rating from transformers and the bus rating at the “from” and “to” side of a transformer branch.

Compose the “from”, “to” and complete admittance matrices:

[Y_{sh}]= [C_{s,bus}]^\top \cdot [shunt_Y] + [C_{l,bus}]^\top \cdot [load_Y]

[Y_f] = diag([Y_{ff}]) \times [C_f] + diag([Y_{ft}]) \times [C_t]

[Y_t] = diag([Y_{tf}]) \times [C_f] + diag([Y_{tt}]) \times [C_t]

[Y_{bus}] = [C_f]^\top \times [Y_f] + [C_t]^\top \times Y_t + diag([Y_{sh}])

Where:

Magnitude Dimensions Description
[Y_{sh}] #bus, #bus Diagonal sparse matrix of the shunt admittances due to the load admittance component and the shunt admittances.
[C_{s,bus}] #shunt, #bus Connectivity of shunts and buses.
[shunt_Y] #shunt, 1 Array of complex admittances from the shunt devices.
[C_{l,bus}] #load, #bus Connectivity of loads and buses.
[load_Y] #load, 1 Array of complex admittances from the load devices.
[C_f], [C_t] #branch, #bus Connectivity matrices of branches and “from” and “to” buses.
[Y_f], [Y_t] #branch, #bus Admittance matrices of branches and “from” and “to” buses.
[Y_{bus}] #bus, #bus Circuit admittance matrix.

A snippet from the code where the admittances are computed:

# form the connectivity matrices with the states applied
states_dia = diags(self.branch_states)
Cf = states_dia * self.C_branch_bus_f
Ct = states_dia * self.C_branch_bus_t

# use the specified of the temperature-corrected resistance
if apply_temperature:
    R = self.R_corrected
else:
    R = self.R

# modify the branches impedance with the lower, upper tolerance values
if branch_tolerance_mode == BranchImpedanceMode.Lower:
    R *= (1 - self.impedance_tolerance / 100.0)
elif branch_tolerance_mode == BranchImpedanceMode.Upper:
    R *= (1 + self.impedance_tolerance / 100.0)
else:
    pass

Ys = 1.0 / (R + 1.0j * self.X)
GBc = self.G + 1.0j * self.B
tap = self.tap_mod * np.exp(1.0j * self.tap_ang)

# branch primitives in vector form
Ytt = (Ys + GBc / 2.0) / (self.tap_t * self.tap_t)
Yff = (Ys + GBc / 2.0) / (self.tap_f * self.tap_f * tap * np.conj(tap))
Yft = - Ys / (self.tap_f * self.tap_t * np.conj(tap))
Ytf = - Ys / (self.tap_t * self.tap_f * tap)

# form the admittance matrices
Yf = diags(Yff) * Cf + diags(Yft) * Ct
Yt = diags(Ytf) * Cf + diags(Ytt) * Ct
Ybus = csc_matrix(Cf.T * Yf + Ct.T * Yt + diags(Ysh))
Adjacency matrix

The computation of the circuit adjacency matrix from matrices that we need anyway for the admittance matrix computation is a very efficient way of dealing with the topological computation. First we establish the total branch-bus connectivity matrix:

[C_{branch-bus}] = [C_f] + [C_t]

Then we compute the bus-bus connectivity matrix, which is the graph adjacency matrix:

[A] = [C_{branch-bus}]^\top \times [C_{branch-bus}]

Islands detection

The admittance matrix of a circuit with more than one island is singular. Therefore, the circuit has to be split in sub-circuits in order to be solved. The suggested algorithm to find the islands of a circuit is the Depth First Search algorithm (DFS).

Previously it was already determined that the circuit complete graph is given by the Bus-Bus connectivity matrix [C_{bus, bus}]. This matrix is also known as the node adjacency matrix. For algorithmic purposes we will call it the adjacency matrix A. As a side note, the matrix A is a sparse matrix.

For algorithmic purposes, A is chosen to be a CSC sparse matrix. This is important because the following algorithm uses the CSC sparse structure to find the adjacent elements of a node.

The following function implements the non-recursive (hence faster) version of the DFS algorithm, which traverses the bus-bus connectivity matrix (also known as the adjacent graph matrix)

def find_islands(A):
    """
    Method to get the islands of a graph
    This is the non-recursive version
    :param: A: Circuit adjacency sparse matrix in CSC format
    :return: islands list where each element is a list of the node indices of the island
    """

    # Mark all the vertices as not visited
    visited = np.zeros(self.node_number, dtype=bool)

    # storage structure for the islands (list of lists)
    islands = list()

    # set the island index
    island_idx = 0

    # go though all the vertices...
    for node in range(self.node_number):

        # if the node has not been visited...
        if not visited[node]:

            # add new island, because the recursive process has already
            # visited all the island connected to v

            islands.append(list())

            # -------------------------------------------------------------------------
            # DFS: store all the reachable vertices into the island from current
            #      vertex "node".

            # declare a stack with the initial node to visit (node)
            stack = list()
            stack.append(node)

            while len(stack) > 0:

                # pick the first element of the stack
                v = stack.pop(0)

                # if v has not been visited...
                if not visited[v]:

                    # mark as visited
                    visited[v] = True

                    # add element to the island
                    islands[island_idx].append(v)

                    # Add the neighbours of v to the stack
                    start = A.indptr[v]
                    end = A.indptr[v + 1]
                    for i in range(start, end):
                        k = A.indices[i]  # get the column index in the CSC scheme
                        if not visited[k]:
                            stack.append(k)
                        else:
                            pass
                else:
                    pass
            # -----------------------------------------------------------------------

            # increase the islands index, because all the other connected vertices
            # have been visited
            island_idx += 1

        else:
            pass

    # sort the islands to maintain raccord
    for island in islands:
        island.sort()

    return islands

The function returns a list (island) where each element is a list of the node indices of the island. These are used to slice the previously computed arrays so that each array slice is copied to the apropriate instance of CalculationInputs.

CalculationInputs

This object contains the calculation arrays already split by island. Hence this object contains information such as the island admittance matrix, the power injections and any other numerical array that the solvers may need.

Universal Branch Model

This section describes the branch model implemented in GridCal. This branch model is a positive sequence model that has been formulated such that it is state of the art.

π model of a branch

π model of a branch

To define the π branch model we need to specify the following magnitudes:

Where:

Magnitude Units Description
R p.u. Resistance of the equivalent branch model.
X p.u. Reactance of the equivalent branch model.
G p.u. Shunt conductance of the equivalent branch model.
B p.u. Shunt susceptance of the equivalent branch model.
|tap| p.u. Transformer tap module. This value indicates the internal voltage regulation and it is around 1. i.e. 0.98, or 1.05.
\delta radians Phase shift angle.
tap_f p.u. Virtual tap that appears because the difference of bus HV rating and the transformer HV rating.
tap_t p.u. Virtual tap that appears because the difference of bus LV rating and the transformer LV rating.

GridCal computes tap_f and tap_t automatically from the values. Also bear in mind that the sense in which the transformer is connected matters. This is dealt with automatically as well.

The basic complex magnitudes are:

Y_s = \frac{1}{R + j \cdot X}

Y_{sh} = G + j \cdot B

tap = |tap| \cdot e^{j \cdot \delta}

tap_f = V_{HV} / V_{bus, HV}

tap_t = V_{LV} / V_{bus, LV}

The compete formulation of the branch primitives for the admittance matrix is:

Y_{tt} = \frac{Y_s + Y_{sh}}{2 \cdot tap_t^2}

Y_{ff} = \frac{Y_s + Y_{sh}}{2 \cdot tap_f^2 \cdot tap \cdot tap^*}

Y_{ft} = - \frac{Y_s}{tap_f \cdot tap_t \cdot tap^*}

Y_{tf} = - \frac{Y_s}{tap_t \cdot tap_f \cdot tap}

In GridCal the primitives of all the branches are computed at once in a matrix fashion, but for didactic purposes the non-matrix formulas are included here.

Temperature correction

The general branch model of gridCal features correction of the resistance due to the temperature. This feature is most applicable to lines. Usually the wires’ catalogue resistance is measured at 20ºC. To account for corrections GridCal

R' = R \cdot (1 + \alpha \cdot \Delta t)

Where \alpha is a parameter that depends of the material of the wires anf \Delta t is the temperature difference between the base and the operational temperatures.

For example

Material Test temperature (ºC) \alpha (1/ºC)
Copper 20 0.004041
Copper 75 0.00323
Annealed copper 20 0.00393
Aluminum 20 0.004308
Aluminum 75 0.00330

Embedded Tap changer

The general branch model features a discrete tap changer to be able to regulate the |tap| parameter manually and automatically from the power flow routines in a realistic way.

Transformer definition from SC test values

The transformers are modeled as π branches too. In order to get the series impedance and shunt admittance of the transformer to match the branch model, it is advised to transform the specification sheet values of the device into the desired values. The values to take from the specs sheet are:

  • S_n: Nominal power in MVA.
  • U_{hv}: Voltage at the high-voltage side in kV.
  • U_{lv}: Voltage at the low-voltage side in kV.
  • U_{sc}: Short circuit voltage in %.
  • P_{cu}: Copper losses in kW.
  • I_0: No load current in %.
  • GX_{hv1}: Reactance contribution to the HV side. Value from 0 to 1.
  • GR_{hv1}: Resistance contribution to the HV side Value from 0 to 1.

Then, the series and shunt impedances are computed as follows:

  • Nominal impedance HV (Ohm): Zn_{hv} = U_{hv}^2 / S_n
  • Nominal impedance LV (Ohm): Zn_{lv} = U_{lv}^2 / S_n
  • Short circuit impedance (p.u.): z_{sc} = U_{sc} / 100
  • Short circuit resistance (p.u.): r_{sc} = \frac{P_{cu} / 1000}{S_n}
  • Short circuit reactance (p.u.): x_{sc} = \sqrt{z_{sc}^2 - r_{sc} ^2}
  • HV resistance (p.u.): r_{cu,hv} = r_{sc} \cdot GR_{hv1}
  • LV resistance (p.u.): r_{cu,lv} = r_{sc} \cdot (1 - GR_{hv1})
  • HV shunt reactance (p.u.): xs_{hv} = x_{sc} \cdot GX_{hv1}
  • LV shunt reactance (p.u.): xs_{lv} = x_{sc} \cdot (1 - GX_{hv1})

If P_{fe} > 0 and I0 > 0, then:

  • Shunt resistance (p.u.): r_{fe} = \frac{Sn}{P_{fe} / 1000}
  • Magnetization impedance (p.u.): z_m = \frac{1}{I_0 / 100}
  • Magnetization reactance (p.u.): x_m = \frac{1}{\sqrt{\frac{1}{z_m^2} - \frac{1}{r_{fe}^2}}}
  • Magnetizing resistance (p.u.): r_m = \sqrt{x_m^2 - z_m^2}

else:

  • Magnetization reactance (p.u.): x_m = 0
  • Magnetizing resistance (p.u.): r_m = 0

The final complex calculated parameters in per unit are:

  • Magnetizing impedance (or series impedance): z_{series} = Z_m = r_{m} +j \cdot x_m
  • Leakage impedance (or shunt impedance): Z_l = r_{sc} + j \cdot x_{sc}
  • Shunt admittance: y_{shunt} = 1 / Z_l

Inverse definition of SC values from π model

In GridCal I found the need to find the short circuit values (P_{cu}, V_{sc}, r_{fe}, I0) from the branch values (R, X, G, B). Hence the following formulas:

z_{sc} = \sqrt{R^2 + X^2}

V_{sc} = 100 \cdot z_{sc}

P_{cu} = R \cdot S_n \cdot 1000

zl = 1 / (G + j B)

r_{fe} = zl.real

xm = zl.imag

I0 = 100 \cdot \sqrt{1 / r_{fe}^2 + 1 / xm^2}

Power Flow

The following subsections include theory about the power flow algorithms supported by GridCal. For control modes (both reactive power control and transformer OLTC control), refer to the Power Flow Driver API Reference.

Newton-Raphson

All the explanations on this section are implemented here.

Canonical Newton-Raphson

The Newton-Raphson method is the standard power flow method tough at schools. GridCal implements slight but important modifications of this method that turns it into a more robust, industry-standard algorithm. The Newton-Raphson method is the first order Taylor approximation of the power flow equation.

The expression to update the voltage solution in the Newton-Raphson algorithm is the following:

\textbf{V}_{t+1} = \textbf{V}_t + \textbf{J}^{-1}(\textbf{S}_0 - \textbf{S}_{calc})

Where:

  • \textbf{V}_t: Voltage vector at the iteration t (current voltage).
  • \textbf{V}_{t+1}: Voltage vector at the iteration t+1 (new voltage).
  • \textbf{J}: Jacobian matrix.
  • \textbf{S}_0: Specified power vector.
  • \textbf{S}_{calc}: Calculated power vector using \textbf{V}_t.

In matrix form we get:

\begin{bmatrix}
\textbf{J}_{11} & \textbf{J}_{12} \\
\textbf{J}_{21} & \textbf{J}_{22} \\
\end{bmatrix}
\times
\begin{bmatrix}
\Delta\theta\\
\Delta|V|\\
\end{bmatrix}
=
\begin{bmatrix}
\Delta \textbf{P}\\
\Delta \textbf{Q}\\
\end{bmatrix}

Jacobian in power equations

The Jacobian matrix is the derivative of the power flow equation for a given voltage set of values.

\textbf{J} =
\begin{bmatrix}
\textbf{J}_{11} & \textbf{J}_{12} \\
\textbf{J}_{21} & \textbf{J}_{22} \\
\end{bmatrix}

Where:

  • J11 = Re\left(\frac{\partial \textbf{S}}{\partial \theta}[pvpq, pvpq]\right)
  • J12 = Re\left(\frac{\partial \textbf{S}}{\partial |V|}[pvpq, pq]\right)
  • J21 = Im\left(\frac{\partial \textbf{S}}{\partial \theta}[pq, pvpq]\right)
  • J22 = Im\left(\frac{\partial \textbf{S}}{\partial |V|}[pq pq]\right)

Where:

  • \textbf{S} = \textbf{V} \cdot \left(\textbf{I} + \textbf{Y}_{bus} \times \textbf{V} \right)^*

Here we introduced two complex-valued derivatives:

  • \frac{\partial S}{\partial |V|} = V_{diag} \cdot \left(Y_{bus} \times V_{diag,norm} \right)^* + I_{diag}^* \cdot V_{diag,norm}
  • \frac{\partial S}{\partial \theta} =  1j \cdot V_{diag} \cdot \left(I_{diag} - Y_{bus} \times V_{diag} \right)^*

Where:

  • V_{diag}: Diagonal matrix formed by a voltage solution.
  • V_{diag,norm}: Diagonal matrix formed by a voltage solution, where every voltage is divided by its module.
  • I_{diag}: Diagonal matrix formed by the current injections.
  • Y_{bus}: And of course, this is the circuit full admittance matrix.

This Jacobian form can be used for other methods.

Newton-Raphson-Iwamoto

In 1982 S. Iwamoto and Y. Tamura present a method [1] where the Jacobian matrix J is only computed at the beginning, and the iteration control parameter µ is computed on every iteration. In GridCal, J and µ are computed on every iteration getting a more robust method on the expense of a greater computational effort.

The Iwamoto and Tamura’s modification to the Newton-Raphson algorithm consist in finding an optimal acceleration parameter µ that determines the length of the iteration step such that, the very iteration step does not affect negatively the solution process, which is one of the main drawbacks of the Newton-Raphson method:

\textbf{V}_{t+1} = \textbf{V}_t + \mu \cdot \textbf{J}^{-1}\times (\textbf{S}_0 - \textbf{S}_{calc})

Here µ is the Iwamoto optimal step size parameter. To compute the parameter µ we must do the following:

\textbf{J'} = Jacobian(\textbf{Y}, \textbf{dV})

The matrix \textbf{J'} is the Jacobian matrix computed using the voltage derivative numerically computed as the voltage increment \textbf{dV}= \textbf{V}_{t} - \textbf{V}_{t-1} (voltage difference between the current and the previous iteration).

\textbf{dx} = \textbf{J}^{-1} \times  (\textbf{S}_0 - \textbf{S}_{calc})

\textbf{a} = \textbf{S}_0 - \textbf{S}_{calc}

\textbf{b} = \textbf{J} \times \textbf{dx}

\textbf{c} = \frac{1}{2} \textbf{dx} \cdot (\textbf{J'} \times \textbf{dx})

g_0 = -\textbf{a} \cdot \textbf{b}

g_1 = \textbf{b} \cdot \textbf{b} + 2  \textbf{a} \cdot \textbf{c}

g_2 = -3  \textbf{b} \cdot \textbf{c}

g_3 = 2  \textbf{c} \cdot \textbf{c}

G(x) = g_0 + g_1 \cdot x + g_2 \cdot x^2 + g_3 \cdot x^3

µ = solve(G(x), x_0=1)

There will be three solutions to the polynomial G(x). Only the last solution will be real, and therefore it is the only valid value for µ. The polynomial can be solved numerically using 1 as the seed.

[1]Iwamoto, S., and Y. Tamura. “A load flow calculation method for ill-conditioned power systems.”IEEE transactions on power apparatus and systems 4 (1981): 1736-1743.
Newton-Raphson in Current Equations

Newton-Raphson in current equations is similar to the regular Newton-Raphson algorithm presented before, but the mismatch is computed with the current instead of power.

The Jacobian is then:

J=
\left[
\begin{array}{cc}
Re\left\{\left[\frac{\partial I}{\partial \theta}\right]\right\}_{(pqpv, pqpv)} &
Re\left\{\left[\frac{\partial I}{\partial Vm}\right]\right\}_{(pqpv, pq)} \\
Im\left\{\left[\frac{\partial I}{\partial \theta}\right]\right\}_{(pq, pqpv)} &
Im\left\{\left[\frac{\partial I}{\partial Vm}\right]\right\}_{(pq,pq)}
\end{array}
\right]

Where:

\left[\frac{\partial I}{\partial Vm}\right] = [Y] \times [E_{diag}]

\left[\frac{\partial I}{\partial \theta}\right] = 1j \cdot [Y] \times [V_{diag}]

The mismatch is computed as increments of current:

F = \left[
\begin{array}{c}
 Re\{\Delta I\} \\
 Im\{\Delta I\}
\end{array}
\right]

Where:

[\Delta I] = \left( \frac{S_{specified}}{V} \right)^*  - ([Y] \times [V] - [I^{specified}])

The steps of the algorithm are equal to the the algorithm presented in Newton-Raphson.

Levenberg-Marquardt

The Levenberg-Marquardt iterative method is often used to solve non-linear least squares problems. in those problems one reduces the calculated error by iteratively solving a system of equations that provides the increment to add to the solution in order to decrease the solution error. So conceptually it applies to the power flow problem.

Set the initial values:

  • \nu = 2
  • f_{prev} = 10^9
  • ComputeH = true

In every iteration:

  • Compute the jacobian matrix if ComputeH is true:

\textbf{H} = Jacobian(\textbf{Y}, \textbf{V})

  • Compute the mismatch in the same order as the jacobian:

\textbf{S}_{calc} = \textbf{V} (\textbf{Y} \cdot \textbf{V} - \textbf{I})^*

\textbf{m} = \textbf{S}_{calc} - \textbf{S}

\textbf{dz} = [ Re(\textbf{m}_{pv}), Re(\textbf{m}_{pq}), Im(\textbf{m}_{pq})]

  • Compute the auxiliary jacobian transformations:

\textbf{H}_1 = \textbf{H}^\top

\textbf{H}_2 = \textbf{H}_1 \cdot \textbf{H}

  • Compute the first value of \lambda (only in the first iteration):

\lambda = 10^{-3} Max(Diag(\textbf{H}_2))

  • Compute the system Matrix:

\textbf{A} = \textbf{H}_2 + \lambda \cdot Identity

  • Compute the linear system right hand side:

\textbf{rhs} = \textbf{H}_1 \cdot \textbf{dz}

  • Solve the system increment:

\textbf{dx} = Solve(\textbf{A}, \textbf{rhs})

  • Compute the objective function:

f = 0.5 \cdot \textbf{dz} \cdot \textbf{dz}^\top

  • Compute the decision function:

\rho = \frac{f_{prev}-f}{0.5 \cdot \textbf{dx}^\top \cdot (\lambda \textbf{dx} + \textbf{rhs})}

  • Update values:

    If \rho > 0

    • ComputeH = true
    • \lambda = \lambda \cdot max(1/3, 1- (2 \cdot \rho -1)^3)
    • \nu = 2
    • Update the voltage solution using \textbf{dx}.

    Else

    • ComputeH = false
    • \lambda = \lambda \cdot \nu
    • \nu = \nu \cdot 2
  • Compute the convergence:

converged = ||dx, \infty|| < tolerance

  • f_{prev} = f

As you can see it takes more steps than Newton-Raphson. It is a slower method, but it works better for ill-conditioned and large grids.

DC approximation

The so called direct current power flow (or just DC power flow) is a convenient oversimplification of the power flow procedure.

It assumes that in any branch the reactive part of the impedance is much larger than the resistive part, hence the resistive part is neglected, and that all the voltages modules are the nominal per unit values. This is, |v|=1 for load nodes and |v|=v_{set} for the generator nodes, where $v_{set}$ is the generator set point value.

In order to compute the DC approximation we must perform a transformation. The slack nodes are removed from the grid, and their influence is maintained by introducing equivalent currents in all the nodes. The equivalent admittance matrix (\textbf{Y}_{red}) is obtained by removing the rows and columns corresponding to the slack nodes. Likewise the removed elements conform the (\textbf{Y}_{slack}) matrix.

Matrix reduction (VD: Slack, PV: Voltage controlled, PQ: Power controlled)

Matrix reduction (VD: Slack, PV: Voltage controlled, PQ: Power controlled)

\textbf{P} = real(\textbf{S}_{red}) + (- imag(\textbf{Y}_{slack}) \cdot angle(\textbf{V}_{slack}) + real(\textbf{I}_{red})) \cdot |\textbf{V}_{red}|

The previous equation computes the DC power injections as the sum of the different factors mentioned:

  • real(\textbf{S}_{red}): Real part of the reduced power injections.
  • imag(\textbf{Y}_{slack}) \cdot angle(\textbf{V}_{slack}) \cdot |v_{red}|: Currents that appear by removing the slack nodes while keeping their influence, multiplied by the voltage module to obtain power.
  • real(\textbf{I}_{red}) \cdot |v_{red}|: Real part of the grid reduced current injections, multiplied by the voltage module to obtain power.

Once the power injections are computed, the new voltage angles are obtained by:

\textbf{V}_{angles} = imag(\textbf{Y}_{red})^{-1} \times \textbf{P}

The new voltage is then:

\textbf{V}_{red} = |\textbf{V}_{red}| \cdot e^{1j \cdot  \textbf{V}_{angles}}

This solution does usually produces a large power mismatch. That is to be expected because the method is an oversimplification with no iterative convergence criteria, just a straight forward set of operations.

Linear AC Power Flow

Following the formulation presented in Linearized AC Load Flow Applied to Analysis in Electric Power Systems [1], we obtain a way to solve circuits in one shot (without iterations) and with quite positive results for a linear approximation.

\begin{bmatrix}
A_{11} & A_{12} \\
A_{21} & A_{22} \\
\end{bmatrix}
\times
\begin{bmatrix}
\Delta \theta\\
\Delta |V|\\
\end{bmatrix}
=
\begin{bmatrix}
Rhs_1\\
Rhs_2\\
\end{bmatrix}

Where:

  • A_{11} = -Im\left(Y_{series}[pqpv, pqpv]\right)
  • A_{12} = Re\left(Y_{bus}[pqpv, pq]\right)
  • A_{21} = -Im\left(Y_{series}[pq, pqpv]\right)
  • A_{22} = -Re\left(Y_{bus}[pq, pq]\right)
  • Rhs_1 = P[pqpv]
  • Rhs_2 = Q[pq]

Here, Y_{bus} is the normal circuit admittance matrix and Y_{series} is the admittance matrix formed with only series elements of the \pi model, this is neglecting all the shunt admittances.

Solving the vector [\Delta \theta + 0, \Delta |V| + 1] we get \theta for the pq and pv nodes and |V| for the pq nodes.

For equivalence with the paper:

  • -B' = -Im(Y_{series}[pqpv, pqpv])
  • G = Re(Y_{bus}[pqpv, pq])
  • -G' = -Im(Y_{series}[pq, pqpv])
  • -B = -Re(Y_{bus}[pq, pq])
[1]Rossoni, P. / Moreti da Rosa, W. / Antonio Belati, E., Linearized AC Load Flow Applied to Analysis in Electric Power Systems, IEEE Latin America Transactions, 14, 9; 4048-4053, 2016

Holomorphic Embedding

First introduced by Antonio Trias in 2012 [1], promises to be a non-divergent power flow method. Trias originally developed a version with no voltage controlled nodes (PV), in which the convergence properties are excellent (With this software try to solve any grid without PV nodes to check this affirmation).

The version programmed in GridCal has been adapted from the master thesis of Muthu Kumar Subramanian at the Arizona State University (ASU) [2]. This version includes a formulation of the voltage controlled nodes. My experience indicates that the introduction of the PV control deteriorates the convergence properties of the holomorphic embedding method. However, in many cases, it is the best approximation to a solution. especially when Newton-Raphson does not provide one.

GridCal’s integration contains a vectorized version of the algorithm. This means that the execution in python is much faster than a previous version that uses loops.

Concepts

All the power flow algorithms until the HELM method was introduced were iterative and recursive. The helm method is iterative but not recursive. A simple way to think of this is that traditional power flow methods are exploratory, while the HELM method is a planned journey. In theory the HELM method is superior, but in practice the numerical degeneration makes it less ideal.

The fundamental idea of the recursive algorithms is that given a voltage initial point (1 p.u. at every node, usually) the algorithm explores the surroundings of the initial point until a suitable voltage solution is reached or no solution at all is found because the initial point is supposed to be far from the solution.

On the HELM methods, we form a curve that departures from a known mathematically exact solution that is obtained from solving the grid with no power injections. This is possible because with no power injections, the grid equations become linear and straight forward to solve. The arriving point of the curve is the solution that we want to achieve. That curve is best approximated by a Padè approximation. To compute the Padè approximation we need to compute the coefficients of the unknown variables, in our case the voltages (and possibly the reactive powers at the PV nodes).

The HELM formulation consists in the derivation of formulas that enable the calculation of the coefficients of the series that describes the curve from the mathematically know solution to the unknown solution. Once the coefficients are obtained, the Padè approximation computes the voltage solution at the end of the curve, providing the desired voltage solution. The more coefficients we compute the more exact the solution is (this is true until the numerical precision limit is reached).

All this sounds very strange, but it works ;)

If you want to get familiar with this concept, you should read about the homotopy concept. In practice the continuation power flow does the same as the HELM algorithm, it takes a known solution and changes the loading factors until a solution for another state is reached.

Fundamentals

The fundamental equation that defines the power flow problem is:

\textbf{S} = \textbf{V} \times (\textbf{Y} \times \textbf{V})^*

Most usefully represented like this:

{\textbf{Y} \times \textbf{V}} = \left(\frac{\textbf{S}}{\textbf{V}}\right)^*

The holomorphic embedding is to insert a “travelling” parameter \alpha, such that for \alpha=0 we have an mathematically exact solution of the problem (but not the solution we’re looking for…), and for \alpha=1 we have the solution we’re looking for. The other thing to do is to represent the variables to be computed as McLaurin series. Let’s go step by step.

For \alpha=0 we say that S=0, in this way the base equation becomes linear, and its solution is mathematically exact. But for that to be useful in our case we need to split the admittance matrix Y into Y_{series} and Y_{shunt}. Y_{shunt} is a diagonal matrix, so it can be expressed as a vector instead (no need for matrix-vector product).

\textbf{Y}_{series} \times \textbf{V} = \left(\frac{\textbf{S}}{\textbf{V}}\right)^* - \textbf{Y}_{shunt} \textbf{V}

This is what will allow us to find the zero “state” in the holomorphic series calculation. For \alpha=1 we say that S=S, so we don’t know the voltage solution, however we can determine a path to get there:

{\textbf{Y }\times \textbf{V}( \alpha )} = \left(\frac{ \alpha\textbf{S}}{\textbf{V}( \alpha )}\right)^* - \alpha \textbf{Y}_{shunt} \times \textbf{V}( \alpha ) = \frac{ \alpha\textbf{S}^*}{\textbf{V}( \alpha )^*} - \alpha \textbf{Y}_{shunt} \textbf{V}( \alpha )

Wait, what?? did you just made this stuff up??, well so far my reasoning is:

  • The voltage \textbf{V} is what I have to convert into a series, and the
    series depend of \alpha, so it makes sense to say that \textbf{V}, as it is dependent of \alpha, becomes \textbf{V}(\alpha).
  • Regarding the \alpha that multiplies \textbf{S}, the amount of power
    (\alpha \textbf{S}) is what I vary during the travel from \alpha=0 to \alpha=1, so that is why S has to be accompanied by the traveling parameter \alpha.
  • In my opinion the \alpha \textbf{Y}_{shunt} is to provoke the first
    voltage coefficients to be one. \textbf{Y}_{series} \times \textbf{V}[0] = 0, makes V[0]=1. This is essential for later steps (is a condition to be able to use Padè).

The series are expressed as McLaurin equations:

V(\alpha) = \sum_{n}^{\infty} V_n \alpha ^n

Theorem - Holomorphicity check There’s still something to do. The magnitude \left(\textbf{V}( \alpha )\right)^* has to be converted into \left(\textbf{V}( \alpha^* )\right)^*. This is done in order to make the function be holomorphic. The holomorphicity condition is tested by the Cauchy-Riemann condition, this is \partial \textbf{V} / \partial \alpha^* = 0, let’s check that:

\partial \left(\textbf{V}( \alpha )^*\right) / \partial \alpha^*  = \partial \left(\sum_{n}^{\infty} V_n^* (\alpha ^n)^*\right) / \partial \alpha^*  = \sum_{n}^{\infty} \alpha ^n V_n^* (\alpha ^{n-1})^*

Which is not zero, obviously. Now with the proposed change:

\partial \left( \textbf{V}( \alpha^* )\right)^* / \partial \alpha^*  = \partial \left(\sum_{n}^{\infty} \textbf{V}_n^* \alpha ^n \right) / \partial \alpha^*  = 0

Yay!, now we’re mathematically happy, since this stuff has no effect in practice because our $alpha$ is not going to be a complex parameter, but for sake of being correct the equation is now:

{\textbf{Y}_{series}\times \textbf{V}( \alpha )} = \frac{ \alpha\textbf{S}^*}{\textbf{V}^*( \alpha^* )} - \alpha \textbf{Y}_{shunt} \textbf{V}( \alpha )

(End of Theorem)

The fact that \textbf{V}^*( \alpha^* ) is dividing is problematic. We need to express it as its inverse so it multiplies instead of divide.

\frac{1}{\textbf{V}( \alpha)} =
\textbf{W}( \alpha ) \longrightarrow \textbf{W}( \alpha ) \textbf{V}( \alpha) = 1
\longrightarrow \sum_{n=0}^{\infty}{\textbf{W}_n \alpha^n}
\sum_{n=0}^{\infty}{\textbf{V}_n \alpha^n} = 1

Expanding the series and identifying terms of \alpha we obtain the expression to compute the inverse voltage series coefficients:

\textbf{W}_n =
\left\{
    \begin{array}{ll}
        \frac{1}{\textbf{V}_0}, \quad n=0 \\
        -\frac{{\sum_{k=0}^{n}\textbf{W}_k \textbf{V}_{n-k}}}{\textbf{V}_0}, \quad n>0
    \end{array}
\right.

Now, this equation becomes:

{\textbf{Y}_{series}\times \textbf{V}( \alpha )} =
\alpha\textbf{S}^* \cdot \textbf{W}( \alpha)^*
- \alpha \textbf{Y}_{shunt} \textbf{V}( \alpha )

Substituting the series by their McLaurin expressions:

{\textbf{Y}_{series}\times \sum_{n=0}^{\infty}{\textbf{V}_n \alpha^n}} = \alpha\textbf{S}^* \left(\sum_{n=0}^{\infty}{\textbf{W}_n \alpha^n}\right)^*  - \alpha \textbf{Y}_{shunt} \sum_{n=0}^{\infty}{\textbf{V}_n \alpha^n}

Expanding the series an identifying terms of \alpha we obtain the expression for the voltage coefficients:

\textbf{V}_n =
\left\{
    \begin{array}{ll}
        {0}, \quad n=0\\
        {\textbf{S}^* \textbf{W}^*_{n-1} - Y_{shunt} \textbf{V}_{n-1} }, \quad n>0
    \end{array}
\right.

This is the HELM fundamental formula derivation for a grid with no voltage controlled nodes (no PV nodes). Once a sufficient number of coefficients are obtained, we still need to use the Padè approximation to get voltage values out of the series.

In the previous formulas, the number of the bus has not been explicitly detailed. All the \textbf{V} and the \textbf{W} are matrices of dimension n \times nbus (number of coefficients by number of buses in the grid) This structures are depicted in the figure Coefficients Structure. For instance \textbf{V}_n is the n^{th} row of the coefficients structure \textbf{V}.

Coefficients Structure

Coefficients Structure

Padè approximation

The McLaurinV equation provides us with an expression to obtain the voltage from the coefficients, knowing that for \alpha=1 we get the final voltage results. So, why do we need any further operation?, and what is this Padè thing?

Well, it is true that the McLaurinV equation provides an approximation of the voltage by means of a series (this is similar to a Taylor approximation), but in practice, the approximation might provide a wrong value for a given number of coefficients. The Padè approximation accelerates the convergence of any given series, so that you get a more accurate result with less coefficients. This means that for the same series of voltage coefficients, using the McLaurinV equation could give a completely wrong result, whereas by applying Padè to those coefficients one could obtain a fairly accurate result.

The Padè approximation is a rational approximation of a function. In our case the function is \textbf{V}(\alpha), represented by the coefficients structure \textbf{V}. The approximation is valid over a small domain of the function, in our case the domain is \alpha=[0,1]. The method requires the function to be continuous and differentiable for \alpha=0. Hence the Cauchy-Riemann condition. And yes, our function meets this condition, we tested it before.

GridCal implements two algorithms that perform the Padè approximation; The Padè canonical algorithm, and Wynn’s Padè approximation.

Padè approximation algorithm

The canonical Padè algorithm for our problem is described by:

Voltage\_value\_approximation = \frac{P_N(\alpha)}{Q_M(\alpha)} \quad \forall \alpha \in [0,1]

Here N=M=n/2, where n is the number of available voltage coefficients, which has to be an even number to be exactly divisible by 2. P and Q are polynomials which coefficients p_i and q_i must be computed. It turns out that if we make the first term of Q_M(\alpha) be q_0=1, the function to be approximated is given by the McLaurin expression (What a happy coincidence!)

P_N(\alpha) = p_0 + p_1\alpha + p_2\alpha^2 + ... + p_N\alpha^N

Q_M(\alpha) = 1 + q_1\alpha + q_2\alpha^2 + ... + q_M\alpha^M

The problem now boils down to find the coefficients q_i and p_i. This is done by solving two systems of equations. The first one to find q_i which does not depend on p_i, and the second one to get p_i which does depend on q_i.

First linear system: The only unknowns are the q_i coefficients.

\begin{matrix}
q_M V_{N-M+1} + q_{M-1}V_{N-M+2}+...+q_1V_N = 0\\
q_M V_{N-M+2} + q_{M-1}V_{N-M+3}+...+q_1V_{N+1} = 0\\
...\\
q_M V_{N} + q_{M-1}V_{N+1}+...+q_1V_{N+M+1} + V_{N+M} = 0\\
\end{matrix}

Second linear System: The only unknowns are the p_i coefficients.

\begin{matrix}
V_0 - p_0=0\\
q_1V_0 + V_1  p_1=0\\
q_2V_0 + q_1V_1+V_2-p_2=0\\
q_3V_0 + q_2V_1 + q_1V_2 + V_3 - p_3 = 0\\
...\\
q_MV_{N-M} + q_{M-1}V_{N-M+1} + ... + +V_N - p_N=0
\end{matrix}

Once the coefficients are there, you would have defined completely the polynomials P_N(\alpha) and Q_M(\alpha), and it is only a matter of evaluating the Padè approximation equation for \alpha=1.

This process is done for every column of coefficients \textbf{V}=\{V_0, V_1,V_2,V_3, ...,V_n\} of the structure depicted in the coefficients structure figure. This means that we have to perform a Padè approximation for every node, using the one columns of the voltage coefficients per Padé approximation.

Wynn’s Padè approximation algorithm

Wynn published a paper in 1969 [4] where he proposed a simple calculation method to obtain the Padè approximation. This method is based on a table. Weniger in 1989 publishes his thesis [5] where a faster version of Wynn’s algorithm is provided in Fortran code.

That very Fortran piece of code has been translated into Python and included in GridCal.

One of the advantages of this method over the canonical Padè approximation implementation is that it can be used for every iteration. In the beginning I thought it would be faster but it turns out that it is not faster since the amount of computation increases with the number of coefficients, whereas with the canonical implementation the order of the matrices does not grow dramatically and it is executed the half of the times.

On top of that my experience shows that the canonical implementation provides a more consistent convergence.

Anyway, both implementations are there to be used in the code.

Formulation with PV nodes

The section Fundamentals introduces the canonical HELM algorithm. That algorithm does not include the formulation of PV nodes. Other articles published on the subject feature PV formulations that work more or less to some degree. The formulation below is a formulation corrected by myself from a formulation contained here [3], which does not work as published, hence the correction.

Embedding

The following embedding equations are proposed instead of the canonical HELM equations from section Fundamentals.

For Slack nodes:

V(\alpha) = V^{SP} \quad \forall \alpha=0

For PQ nodes:

\left\{
\begin{array}{ll}
\textbf{Y} \times \textbf{V}(\alpha) = 0 \quad \quad \quad \quad \forall \alpha=0\\
{\textbf{Y} \times \textbf{V}(\alpha) = \frac{\alpha \textbf{S}}{\textbf{V}^*(\alpha^*)}} \quad \forall \alpha>0
\end{array}
\right.

For PV nodes:

\left\{
\begin{array}{ll}
{\textbf{Y} \times \textbf{V}(\alpha) = \frac{ \textbf{S}}{\textbf{V}^*(\alpha^*)}} \quad \forall \alpha=0\\
{\textbf{Y} \times \textbf{V}(\alpha) = \frac{ \textbf{S} - j \textbf{Q}(\alpha)}{\textbf{V}^*(\alpha^*)}} \quad \forall \alpha>0
\end{array}
\right.

\left\{
\begin{array}{ll}
V(\alpha)V^*(\alpha^*) = |V_0|^2\quad \quad \quad \quad \forall \alpha=0\\
V(\alpha)V^*(\alpha^*) = |V_0|^2 + (|V^{SP}|^2-|V_0|^2) \quad \forall \alpha>0
\end{array}
\right.

This embedding translates into the following formulation:

Step 1

The formulas are adapted to exemplify a 3-bus system where the bus1 is a slack, the bus 2 is PV and the bus 3 is PQ. This follows the example of the Appendix A of [3].

Compute the initial no-load solution (n=0):

\begin{bmatrix}
1 & 0 & 0 & 0 & 0 & 0\\
0 & 1 & 0 & 0 & 0 & 0\\
G_{21} & -B_{21} & G_{22} & -B_{22} & G_{23} & -B_{23}\\
B_{21} & G_{21}  & B_{22} & G_{22}  & B_{23} & G_{23}\\
G_{31} & -B_{31} & G_{32} & -B_{32} & G_{33} & -B_{33}\\
B_{31} & G_{31}  & B_{32} & G_{32}  & B_{33} & G_{33}\\
\end{bmatrix}
\times
\begin{bmatrix}
V[n]_{re, 1}\\
V[n]_{im, 1}\\
V[n]_{re, 2}\\
V[n]_{im, 2}\\
V[n]_{re, 3}\\
V[n]_{im, 3}\\
\end{bmatrix}
=
\begin{bmatrix}
V^{SP}_{re, 1}\\
V^{SP}_{im, 1}\\
0\\
0\\
0\\
0\\
\end{bmatrix}
\quad \forall n = 0

Form the solution vector \textbf{V}[n] you can compute the buses calculated power and then get the reactive power at the PV nodes to initialize \textbf{Q}[0]:

\textbf{S} = \textbf{V}[0] \cdot (\textbf{Y}_{bus} \times \textbf{V}[0])^*

\textbf{Q}_i[0] = imag(\textbf{S}_{i}) \quad \forall i \in PV

The initial inverse voltage coefficients \textbf{W}[0] are obtained by:

W_i[0] = \frac{1}{V_i[0]}  \quad \forall i \in N

This step is entirely equivalent to find the no load solution using the Z-Matrix reduction.

Step 2

Construct the system of equations to solve the coefficients of order greater than zero (n>0). Note that the matrix is the same as constructed for the previous step, but adding a column and a row for each PV node to account for the reactive power coefficients. In our 3-bus example, there is only one PV node, so we add only one column and one row.

\begin{bmatrix}
1 & 0 & 0 & 0 & 0 & 0 & 0\\
0 & 1 & 0 & 0 & 0 & 0 & 0\\
G_{21} & -B_{21} & G_{22} & -B_{22} & G_{23} & -B_{23} & W[0]_{im}\\
B_{21} & G_{21}  & B_{22} & G_{22}  & B_{23} & G_{23} & W[0]_{re}\\
G_{31} & -B_{31} & G_{32} & -B_{32} & G_{33} & -B_{33} & 0\\
B_{31} & G_{31}  & B_{32} & G_{32}  & B_{33} & G_{33} & 0\\
0 & 0 & V[0]_{re} & V[0]_{im} & 0 & 0 & 0\\
\end{bmatrix}
\times
\begin{bmatrix}
V[n]_{re, 1}\\
V[n]_{im, 1}\\
V[n]_{re, 2}\\
V[n]_{im, 2}\\
V[n]_{re, 3}\\
V[n]_{im, 3}\\
Q_2[n]\\
\end{bmatrix}
=
\begin{bmatrix}
0\\
0\\
f2_{re}\\
f2_{im}\\
f1_{re}\\
f1_{im}\\
\epsilon[n]\\
\end{bmatrix}
\quad \forall n > 0

Where:

f1 = S^*_i \cdot W^*_i[n-1] \quad \forall i \in PQ

f2 = P_i \cdot W^*_i[n-1] + conv(n, Q_i, W^*_i) \quad \forall i \in PV

\epsilon[n] = \delta_{n1} \cdot \frac{1}{2} \left(|V_i^SP|^2 - |V_i[0]|^2\right) - \frac{1}{2} conv(n, V_i, V_i^*)  \quad \forall i \in PV, n > 0

The convolution conv is defined as:

conv(n, A, B) = \sum_{m=0}^{n-1} A[m] \cdot B[n-m]

The system matrix (A_{sys}) is the same for all the orders of n>0, therefore we only build it once, and we factorize it to solve the subsequent coefficients.

After the voltages \textbf{V}[n] and the reactive power at the PV nodes Q[n] is obtained solving the linear system (this equation), we must solve the inverse voltage coefficients of order n for all the buses:

W_i[n] = \frac{- {\sum_{m=0}^{n}W_i[m] \cdot V_i[n-m]} }{V_i[0]} \quad  \forall i \in N, n>0

Step 3

Repeat step 2 until a sufficiently low error is achieved or a maximum number of iterations (coefficients).

The error is computed by comparing the calculated power \textbf{S} (eq Scalc) with the specified power injections \textbf{S}^{SP}:

mismatch = \textbf{S} - \textbf{S}^{SP}

error = |mismatch|_\infty = max(abs(mismatch))

[1]Trias, Antonio. “The holomorphic embedding load flow method.” Power and Energy Society General Meeting, 2012 IEEE. IEEE, 2012.
[2]Subramanian, Muthu Kumar. Application of holomorphic embedding to the power-flow problem. Diss. Arizona State University, 2014.
[3](1, 2) Liu, Chengxi, et al. “A multi-dimensional holomorphic embedding method to solve AC power flows.” IEEE Access 5 (2017): 25270-25285.
[4]Wynn, P. “The epsilon algorithm and operational formulas of numerical analysis.” Mathematics of Computation 15.74 (1961): 151-158.
[5]Weniger, Ernst Joachim. “Nonlinear sequence transformations for the acceleration of convergence and the summation of divergent series.” Computer Physics Reports 10.5-6 (1989): 189-371.

Post Power Flow (Loading and Losses)

As we have seen, the power flow routines compute the voltage at every bus of an island grid. However we do not get from those routines the “power flow” values, this is the power that flows through the branches of the grid. In this section I show how the post power flow values are computed.

First we compute the branches per unit currents:

{\textbf{I}_f = \textbf{Y}_f \times \textbf{V}}

{\textbf{I}_t = \textbf{Y}_t \times \textbf{V}}

These are matrix-vector multiplications. The result is the per unit currents flowing through a branch seen from the from bus or from the to bus.

Then we compute the power values:

{\textbf{S}_f = \textbf{V}_f \cdot \textbf{I}_f^*}

{\textbf{S}_t = \textbf{V}_t \cdot \textbf{I}_t^*}

These are element-wise multiplications, resulting in the per unit power flowing through a branch seen from the from bus or from the to bus.

Now we can compute the losses in MVA as:

{\textbf{losses} = |\textbf{S}_f - \textbf{S}_t| \cdot Sbase}

And also the branches loading in per unit as:

{\textbf{loading} = \frac{max(|\textbf{S}_f|, |\textbf{S}_t|) \cdot Sbase}{ \textbf{rate}}}

The variables are:

  • \textbf{Y}_f, \textbf{Y}_t: From and To bus-branch admittance matrices
  • \textbf{I}_f: Array of currents at the from buses in p.u.
  • \textbf{I}_t: Array of currents at the to buses in p.u.
  • \textbf{S}_f: Array of powers at the from buses in p.u.
  • \textbf{S}_t: Array of powers at the to buses in p.u.
  • \textbf{V}_f: Array of voltages at the from buses in p.u.
  • \textbf{V}_t: Array of voltages at the to buses in p.u.
  • \textbf{rate}: Array of branch ratings in MVA.

Continuation power flow

The continuation power flow is a technique that traces a trajectory from a base situation given to a combination of power S_0 and voltage V_0, to another situation determined by another combination of power S'. When the final power situation is undefined, then the algorithm continues until the Jacobian is singular, tracing the voltage collapse curve.

The method uses a predictor-corrector technique to trace this trajectory.

Predictor

System Message: WARNING/2 (\begin{bmatrix} J & \frac{\partial F}{\partial \lambda} \\ \frac{\partial P}{\partial V} & \frac{\partial P}{\partial \lambda} \\ \end{bmatrix} \times \begin{bmatrix} \Delta\theta\\ \Delta|V|\\ \lambda \end{bmatrix} = \begin{bmatrix} 0^\hat \\ 0^\hat \\ 1\\ \end{bmatrix})

latex exited with error [stdout] This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=latex) restricted \write18 enabled. entering extended mode (./math.tex LaTeX2e <2017-04-15> Babel <3.18> and hyphenation patterns for 84 language(s) loaded. (/usr/share/texlive/texmf-dist/tex/latex/base/article.cls Document Class: article 2014/09/29 v1.4h Standard LaTeX document class (/usr/share/texlive/texmf-dist/tex/latex/base/size12.clo)) (/usr/share/texlive/texmf-dist/tex/latex/base/inputenc.sty (/usr/share/texlive/texmf-dist/tex/latex/ucs/utf8x.def)) (/usr/share/texlive/texmf-dist/tex/latex/ucs/ucs.sty (/usr/share/texlive/texmf-dist/tex/latex/ucs/data/uni-global.def)) (/usr/share/texlive/texmf-dist/tex/latex/amsmath/amsmath.sty For additional information on amsmath, use the `?' option. (/usr/share/texlive/texmf-dist/tex/latex/amsmath/amstext.sty (/usr/share/texlive/texmf-dist/tex/latex/amsmath/amsgen.sty)) (/usr/share/texlive/texmf-dist/tex/latex/amsmath/amsbsy.sty) (/usr/share/texlive/texmf-dist/tex/latex/amsmath/amsopn.sty)) (/usr/share/texlive/texmf-dist/tex/latex/amscls/amsthm.sty) (/usr/share/texlive/texmf-dist/tex/latex/amsfonts/amssymb.sty (/usr/share/texlive/texmf-dist/tex/latex/amsfonts/amsfonts.sty)) (/usr/share/texlive/texmf-dist/tex/latex/anyfontsize/anyfontsize.sty) (/usr/share/texlive/texmf-dist/tex/latex/tools/bm.sty) (./math.aux) (/usr/share/texlive/texmf-dist/tex/latex/ucs/ucsencs.def) (/usr/share/texlive/texmf-dist/tex/latex/amsfonts/umsa.fd) (/usr/share/texlive/texmf-dist/tex/latex/amsfonts/umsb.fd) ! Missing { inserted. <to be read again> \gdef l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} ! Extra }, or forgotten $. <inserted text> } l.29 \end{bmatrix}\end{split} ! Missing } inserted. <inserted text> } l.29 \end{bmatrix}\end{split} (That makes 100 errors; please try again.) No pages of output. Transcript written on math.log.
Corrector

Optimal power flow

Linear optimal power flow

General indices and dimensions

Variable Description
n Number of nodes
m Number of branches
ng Number of generators
nb Number of batteries
nl Number of loads
pqpv Vector of node indices of the the PQ and PV buses.
vd Vector of node indices of the the Slack (or VD) buses.
Objective function

The objective function minimizes the cost of generation plus all the slack variables set in the problem.

min: \quad f = \sum_g cost_g \cdot Pg_g \\
             + \sum_b cost_b \cdot Pb_b  \\
             + \sum_l cost_l \cdot LSlack_l \\
             + \sum_b Fslack1_b + Fslack2_b \\

Power injections

This equation is not a restriction but the computation of the power injections (fix and LP variables) that are injected per node, such that the vector P is dimensionally coherent with the number of buses.

P = C\_bus\_gen \times Pg  \\
  + C\_bus\_bat \times Pb  \\
  - C\_bus\_load \times (LSlack + Load)

Variable Description Dimensions Type Units
P Vector of active power per node. n Float + LP p.u.
C\_bus\_gen Bus-Generators connectivity matrix. n, ng int 1/0
Pg Vector of generators active power. ng LP p.u.
C\_bus\_bat Bus-Batteries connectivity matrix. nb int 1/0
Pb Vector of batteries active power. nb LP p.u.
C\_bus\_load Bus-Generators connectivity matrix. n, nl int 1/0
Load Vector of active power loads. nl Float p.u.
LSlack Vector of active power load slack variables. nl LP p.u.
Nodal power balance

These two restrictions are set as hard equality constraints because we want the electrical balance to be fulfilled.

Note that this formulation splits the slack nodes from the non-slack nodes. This is faithful to the original DC power flow formulation which allows for implicit losses computation.

Equilibrium at the non slack nodes.

B_{pqpv, pqpv} \times \theta_{pqpv} = P_{pqpv}

Equilibrium at the slack nodes.

B_{vd, :} \times \theta = P_{vd}

Variable Description Dimensions Type Units
B Matrix of susceptances. Ideally if the imaginary part of Ybus. n, n Float p.u.
P Vector of active power per node. n Float + LP p.u.
\theta Vector of generators voltage angles. n LP radians.
Branch loading restriction

Something else that we need to do is to check that the branch flows respect the established limits. Note that because of the linear simplifications, the computed solution in active power might actually be dangerous for the grid. That is why a real power flow should counter check the OPF solution.

First we compute the arrays of nodal voltage angles for each of the “from” and “to” sides of each branch. This is not a restriction but a simple calculation to aid the next restrictions that apply per branch.

\theta_{from} = C\_branch\_bus\_{from} \times \theta

\theta_{to} = C\_branch\_bus\_{to} \times \theta

Now, these are restrictions that define that the “from->to” and the “to->from” flows must respect the branch rating.

B_{series} \cdot \left( \theta_{from} - \theta_{to} \right) \leq F_{max} + F_{slack1}

B_{series} \cdot \left( \theta_{to} - \theta_{from} \right) \leq F_{max} + F_{slack2}

Another restriction that we may impose is that the loading slacks must be equal, since they represent the extra line capacity required to transport the power in both senses of the transportation.

F_{slack1} = F_{slack2}

Variable Description Dimensions Type Units
B_{series}

Vector of series susceptances of the branches.

Can be computed as Im\left(\frac{1}{r + j \cdot x}\right)

m Float p.u.
C\_branch\_bus_{from} Branch-Bus connectivity matrix at the “from” end of the branches. m, n int 1/0
C\_branch\_bus_{to} Branch-Bus connectivity matrix at the “to” end of the branches. m, n int 1/0
\theta_{from} Vector of bus voltage angles at the “from” end of the branches. m LP radians.
\theta_{to} Vector of bus voltage angles at the “to” end of the branches. m LP radians.
\theta Vector of bus voltage angles. n LP radians.
F_{max} Vector of branch ratings. m Float p.u.
F_{slack1} Vector of branch rating slacks in the from->to sense. m LP p.u.
F_{slack2} Vector of branch rating slacks in the to->from sense. m LP p.u.

Linear DC optimal power flow time series

General indices and dimensions

Variable Description
n Number of nodes
m Number of branches
ng Number of generators
nb Number of batteries
nl Number of loads
nt Number of time steps
pqpv Vector of node indices of the the PQ and PV buses.
vd Vector of node indices of the the Slack (or VD) buses.
Objective function

The objective function minimizes the cost of generation plus all the slack variables set in the problem.

min: \quad f = \sum_t^{nt}  \sum_g^{ng} cost_g \cdot Pg_{g,t} \\
             + \sum_t^{nt}  \sum_b^{nb} cost_b \cdot Pb_{b, t}  \\
             + \sum_t^{nt}  \sum_l^{nl} cost_l \cdot LSlack_{l, t} \\
             + \sum_t^{nt}  \sum_i^{m} Fslack1_{i,t} + Fslack2_{i,t} \\

Power injections

This equation is not a restriction but the computation of the power injections (fix and LP variables) that are injected per node, such that the vector P is dimensionally coherent with the number of buses.

P = C\_bus\_gen \times Pg  \\
    + C\_bus\_bat \times Pb  \\
    - C\_bus\_load \times (LSlack + Load)

Variable Description Dimensions Type Units
P Matrix of active power per node and time step. n, nt Float + LP p.u.
C\_bus\_gen Bus-Generators connectivity matrix. n, ng int 1/0
Pg Matrix of generators active power per time step. ng, nt LP p.u.
C\_bus\_bat Bus-Batteries connectivity matrix. nb int 1/0
Pb Matrix of batteries active power per time step. nb, nt LP p.u.
C\_bus\_load Bus-Generators connectivity matrix. n, nl int 1/0
Load Matrix of active power loads per time step. nl, nt Float p.u.
LSlack Matrix of active power load slack variables per time step. nl, nt LP p.u.
Nodal power balance

These two restrictions are set as hard equality constraints because we want the electrical balance to be fulfilled.

Note that this formulation splits the slack nodes from the non-slack nodes. This is faithful to the original DC power flow formulation which allows for implicit losses computation.

Equilibrium at the non slack nodes.

B_{(pqpv, pqpv)} \times \theta_{(pqpv, :)} = P_{(pqpv, :)}

Equilibrium at the slack nodes.

B_{(vd, :)} \times \theta = P_{(vd, :)}

Remember to set the slack-node voltage angles to zero! Otherwise the generator power will no be used by the solver to provide voltage values.

\theta_{(vd, :)} = 0

Variable Description Dimensions Type Units
B Matrix of susceptances. Ideally if the imaginary part of Ybus. n, n Float p.u.
P Matrix of active power per node and per time step. n, nt Float + LP p.u.
\theta Matrix of generators voltage angles per node and per time step. n, nt LP radians.
Branch loading restriction

Something else that we need to do is to check that the branch flows respect the established limits. Note that because of the linear simplifications, the computed solution in active power might actually be dangerous for the grid. That is why a real power flow should counter check the OPF solution.

First we compute the arrays of nodal voltage angles for each of the “from” and “to” sides of each branch. This is not a restriction but a simple calculation to aid the next restrictions that apply per branch.

\theta_{from} = C\_branch\_bus\_{from} \times \theta

\theta_{to} = C\_branch\_bus\_{to} \times \theta

Now, these are restrictions that define that the “from->to” and the “to->from” flows must respect the branch rating.

B_{series} \cdot \left( \theta_{from} - \theta_{to} \right) \leq F_{max} + F_{slack1}

B_{series} \cdot \left( \theta_{to} - \theta_{from} \right) \leq F_{max} + F_{slack2}

Another restriction that we may impose is that the loading slacks must be equal, since they represent the extra line capacity required to transport the power in both senses of the transportation.

F_{slack1} = F_{slack2}

Variable Description Dimensions Type Units
B_{series}

Vector of series susceptances of the branches.

Can be computed as Im\left(\frac{1}{r + j \cdot x}\right)

m Float p.u.
C\_branch\_bus_{from} Branch-Bus connectivity matrix at the “from” end of the branches. m, n int 1/0
C\_branch\_bus_{to} Branch-Bus connectivity matrix at the “to” end of the branches. m, n int 1/0
\theta_{from} Matrix of bus voltage angles at the “from” end of the branches per bus and time step. m, nt LP radians.
\theta_{to} Matrix of bus voltage angles at the “to” end of the branches per bus and time step. m, nt LP radians.
\theta Matrix of bus voltage angles per bus and time step. n, nt LP radians.
F_{max} Matrix of branch ratings per branch and time step. m, nt Float p.u.
F_{slack1} Matrix of branch rating slacks in the from->to sense per branch and time step. m, nt LP p.u.
F_{slack2} Matrix of branch rating slacks in the to->from sense per branch and time step. m, nt LP p.u.
Battery discharge restrictions

The first value of the batteries’ energy is the initial state of charge (SoC_0) times the battery capacity.

E_0 = SoC_0 \cdot Capacity

The capacity in the subsequent time steps is the previous capacity minus the power dispatched. Note that the convention is that the positive power is discharged by the battery and the negative power values represent the power charged by the battery.

E_t = E_{t-1} - \frac{\Delta_t \cdot Pb}{Efficiency} \quad \quad \forall t \in \{ 1, nt-1 \}

The batteries’ energy has to be kept within the batteries’ operative ranges.

SoC_{min} \cdot Capacity \leq E_t \leq SoC_{max} \cdot Capacity \quad \forall t \in \{ 0, nt-1 \}

Variable Description Dimensions Type Units
E Matrix of energy stored in the batteries. nb, nt LP p.u.
SoC_0 Vector of initial states of charge. nb Float p.u.
SoC_{max} Vector of maximum states of charge. nb Float p.u.
SoC_{min} Vector of minimum states of charge. nb Float p.u.
Capacity Vector of battery capacities. nb Float h \left(\frac{MWh}{MW \quad base} \right)
\Delta_t Time increment in the interval [t-1, t]. 1 Float
Pb Vector of battery power injections. nb LP p.u.
Efficiency Vector of Battery efficiency for charge and discharge. nb Float p.u.

AC Linear optimal power flow time series

General indices and dimensions

Variable Description
n Number of nodes
m Number of branches
ng Number of generators
nb Number of batteries
nl Number of loads
nt Number of time steps
pqpv Vector of node indices of the the PQ and PV buses.
vd Vector of node indices of the the Slack (or VD) buses.
Objective function

The objective function minimizes the cost of generation plus all the slack variables set in the problem.

min: \quad f = \sum_t^{nt}  \sum_g^{ng} cost_g \cdot Pg_{g,t} \\
             + \sum_t^{nt}  \sum_b^{nb} cost_b \cdot Pb_{b, t}  \\
             + \sum_t^{nt}  \sum_l^{nl} cost_l \cdot LSlack_{l, t} \\
             + \sum_t^{nt}  \sum_i^{m} Fslack1_{i,t} + Fslack2_{i,t} \\

Power injections

This equation is not a restriction but the computation of the power injections (fix and LP variables) that are injected per node, such that the vector P is dimensionally coherent with the number of buses.

P = - C\_bus\_load \times (LSlack + P_{load}) \\
    + C\_bus\_gen \times Pg  \\
    + C\_bus\_bat \times Pb

Q = - C\_bus\_load \times (LSlack + Q_{load})

Variable Description Dimensions Type Units
P Matrix of active power per node and time step. n, nt Float + LP p.u.
C\_bus\_gen Bus-Generators connectivity matrix. n, ng int 1/0
Pg Matrix of generators active power per time step. ng, nt LP p.u.
C\_bus\_bat Bus-Batteries connectivity matrix. nb int 1/0
Pb Matrix of batteries active power per time step. nb, nt LP p.u.
C\_bus\_load Bus-Generators connectivity matrix. n, nl int 1/0
P_{load} Matrix of active power loads per time step. nl, nt Float p.u.
Q_{load} Matrix of reactive power loads per time step. nl, nt Float p.u.
LSlack Matrix of active power load slack variables per time step. nl, nt LP p.u.
Nodal power balance

This is the OPF based on the formulation presented in Linearized AC Load Flow Applied to Analysis in Electric Power Systems [1], we obtain a way to solve circuits in one shot (without iterations) and with quite positive results for a linear approximation.

\begin{bmatrix}
-Bs_{pqpv, pqpv} & G_{pqpv, pq} \\
-Gs_{pq, pqpv} & -B_{pq,pq} \\
\end{bmatrix}
\times
\begin{bmatrix}
\Delta \theta_{pqpv}\\
\Delta Vm_{pq}\\
\end{bmatrix}
=
\begin{bmatrix}
P_{pqpv}\\
Q_{pq}\\
\end{bmatrix}

This matrix equation turns into two sets of restrictions:

-Bs_{pqpv, pqpv} \times  \Delta \theta_{pqpv} +  G_{pqpv, pq} \times \Delta Vm_{pq} = P_{pqpv}

-Gs_{pq, pqpv} \times \Delta \theta_{pqpv} - B_{pq,pq} \times \Delta Vm_{pq} = Q_{pq}

An additional set of restrictions is needed in order to include the slack nodes in the formulation. These are not required for the power flow formulation but they are in the optimal power flow one that is presented.

-Bs_{vd, :} \times  \Delta \theta +  G_{vd, :} \times \Delta Vm = P_{vd}

Remember to set the slack-node voltage angles and module increments and the PV node voltage module increments to zero! Otherwise the generator’s power will no be used by the solver to provide voltage values.

\Delta\theta_{(vd, :)} = 0

\Delta Vm_{(vdpv, :)} = 0

Variable Description Dimensions Type Units
B Matrix of susceptances. Im\{Y\}. n, n Float p.u.
Bs Matrix of susceptances of the series elements. Im \left\{Y_{series} \right\}. n, n Float p.u.
G Matrix of conductances. Re\{Y\}. n, n Float p.u.
Gs Matrix of conductances of the series elements. Re \left\{Y_{series} \right\}. n, n Float p.u.
P Matrix of active power per node and per time step. n, nt Float + LP p.u.
Q Matrix of reactive power per node and per time step. n, nt Float + LP p.u.
\Delta \theta Matrix of generators voltage angles increment per node and per time step. n, nt LP radians.
\Delta Vm Matrix of generators voltage module increments per node and per time step. n, nt LP p.u.
Branch loading restriction

Something else that we need to do is to check that the branch flows respect the established limits. Note that because of the linear simplifications, the computed solution in active power might actually be dangerous for the grid. That is why a real power flow should counter check the OPF solution.

First we compute the arrays of nodal voltage angles for each of the “from” and “to” sides of each branch. This is not a restriction but a simple calculation to aid the next restrictions that apply per branch.

\theta_{from} = C\_branch\_bus\_{from} \times \theta

\theta_{to} = C\_branch\_bus\_{to} \times \theta

Now, these are restrictions that define that the “from->to” and the “to->from” flows must respect the branch rating.

B_{series} \cdot \left( \theta_{from} - \theta_{to} \right) \leq F_{max} + F_{slack1}

B_{series} \cdot \left( \theta_{to} - \theta_{from} \right) \leq F_{max} + F_{slack2}

Another restriction that we may impose is that the loading slacks must be equal, since they represent the extra line capacity required to transport the power in both senses of the transportation.

F_{slack1} = F_{slack2}

Variable Description Dimensions Type Units
B_{series}

Vector of series susceptances of the branches.

Can be computed as Im\left(\frac{1}{r + j \cdot x}\right)

m Float p.u.
C\_branch\_bus_{from} Branch-Bus connectivity matrix at the “from” end of the branches. m, n int 1/0
C\_branch\_bus_{to} Branch-Bus connectivity matrix at the “to” end of the branches. m, n int 1/0
\theta_{from} Matrix of bus voltage angles at the “from” end of the branches per bus and time step. m, nt LP radians.
\theta_{to} Matrix of bus voltage angles at the “to” end of the branches per bus and time step. m, nt LP radians.
\theta Matrix of bus voltage angles per bus and time step. n, nt LP radians.
F_{max} Matrix of branch ratings per branch and time step. m, nt Float p.u.
F_{slack1} Matrix of branch rating slacks in the from->to sense per branch and time step. m, nt LP p.u.
F_{slack2} Matrix of branch rating slacks in the to->from sense per branch and time step. m, nt LP p.u.
Battery discharge restrictions

The first value of the batteries’ energy is the initial state of charge (SoC_0) times the battery capacity.

E_0 = SoC_0 \cdot Capacity

The capacity in the subsequent time steps is the previous capacity minus the power dispatched. Note that the convention is that the positive power is discharged by the battery and the negative power values represent the power charged by the battery.

E_t = E_{t-1} - \frac{\Delta_t \cdot Pb}{Efficiency} \quad \quad \forall t \in \{ 1, nt-1 \}

The batteries’ energy has to be kept within the batteries’ operative ranges.

SoC_{min} \cdot Capacity \leq E_t \leq SoC_{max} \cdot Capacity \quad \forall t \in \{ 0, nt-1 \}

Variable Description Dimensions Type Units
E Matrix of energy stored in the batteries. nb, nt LP p.u.
SoC_0 Vector of initial states of charge. nb Float p.u.
SoC_{max} Vector of maximum states of charge. nb Float p.u.
SoC_{min} Vector of minimum states of charge. nb Float p.u.
Capacity Vector of battery capacities. nb Float h \left(\frac{MWh}{MW \quad base} \right)
\Delta_t Time increment in the interval [t-1, t]. 1 Float
Pb Vector of battery power injections. nb LP p.u.
Efficiency Vector of Battery efficiency for charge and discharge. nb Float p.u.
[1]Rossoni, P. / Moreti da Rosa, W. / Antonio Belati, E., Linearized AC Load Flow Applied to Analysis in Electric Power Systems, IEEE Latin America Transactions, 14, 9; 4048-4053, 2016

Short Circuit

3-Phase Short Circuit

First, declare an array of zeros of size equal to the number of nodes in the circuit.

\textbf{I} = \{0, 0, 0, 0, ..., 0\}

Then, compute the short circuit current at the selected bus i and assign that value in the i^{th} position of the array \textbf{I}.

\textbf{I}_i = - \frac{\textbf{V}_{pre-failure, i}}{\textbf{Z}_{i, i} + z_f}

Then, compute the voltage increment for all the circuit nodes as:

\Delta \textbf{V} = \textbf{Z} \times \textbf{I}

Finally, define the voltage at all the nodes as:

\textbf{V}_{post-failure} = \textbf{V}_{pre-failure} + \Delta \textbf{V}

Magnitudes:

  • \textbf{I}: Array of fault currents at the system nodes.
  • \textbf{V}_{pre-failure}: Array of system voltages prior to the failure. This is obtained from the power flow study.
  • z_f: Impedance of the failure itself. This is a given value, although you can set it to zero if you don’t know.
  • \textbf{Z}: system impedance matrix. Obtained as the inverse of the complete system admittance matrix.

Graphical User Interface

The user interface of GridCal is written using the Qt graphical interface framework. This allows GridCal to be multi-platform and to have sufficient performance to handle thousands of graphical items in the editor, to be responsive and plenty of other attributes of consumer software.

GridCal user interface.

GridCal user interface representing a zoom of a ~600 node distribution grid.

The graphical user interface (GUI) makes extensive use of tooltip texts. These area yellow tags that appear when you hover the mouse cursor over a button from the interface. The tooltip texts are meant to be explanatory so that reading a manual is not really needed unless you need the technical details. Nevertheless this guide hopes to guide you through the GUI well enough so that the program is usable.

Model view

The model view is where all the editing is done.

Schematic editor

The schematic view is where you construct the grid in GridCal. The usage is quite simple:

  • Drag & Drop the buses from the left upper side into the main panel.
  • Click on a bus bar (black line) and drag the link into another bus bar, a branch will be created.
    • Branch types can be selected (branch, transformer, line, switch, reactance).
    • If you double click a branch and the type is Line or Transformer, a simplified editor will pop up.
  • To add loads, generators, shunts, etc… just right click on bus and select from the context menu.
  • The context menu from the buses allow plenty of operations such as view the bus profiles (extended is time series results are present) or setting a bus as a short circuit point.

The schematic objects are coloured based on the results of the latest simulation.

Schematic view and editor

When more than one simulation is available (i.e. power flow and power flow time series) the schematic editor is augmented with a bar that allows you to select the appropriate simulation colouring of the grid. When the simulation has a time component, like time series or voltage collapse, the bar will allow you to visualize each individual step of the simulation and navigate through them.

Schematic view and editor extended

Tabular editor

Some times is far more practical to edit the objects in bulk. For that, GridCal features the tabular view of the objects. All the static properties of the objects can be edited here. For the properties with time series we have the “time events” tab.

GridCal objects tabular editor.

There are some filtering options that can be performed; The first thing is to set the property to filter by in the drop down menu. Then in the text box you can type a filter criteria. The available criteria area the following:

Symbol Description Example
< Less than <200, <foo
> Greater than >200, >bar
<= Less or equal to <=200, <=foo
>= Greater or equal to >=200, >=bar
= Exactly equal. =node or =600
!= Different from. !=node or !=600
* The field contains this. This command is only available for strings. *bus

For strings the comparison does not take into account the case.

If the branch objects are selected, then it is possible to extend the view with the catalogue of template elements available to assign to each branch. When a template is assigned to a branch, some properties are affected by the template. The properties affected are the resistance (R), reactance (X), Conductance (G), Susceptance (B) and the branch rating (Rate).

GridCal objects tabular editor 2.

Grid analysis and diagnostic

GridCal features an analysis and diagnostics tool (F8) that allows to inspect at a glance the main magnitudes of the grid objects. For instance if there were outliers in the branches resistance, it would be evident from the histogram charts.

GridCal objects analysis.

A detailed table of common problems is provided in the diagnostics tab. This allows you to go back to the tabular editor and fix the issues found.

GridCal objects diagnostic.

Templates

The branch templates are defined here. The templates are designed to ease the process of defining the properties of the branch objects.

  • Wires: A wire is not strictly a branch, but it is required to be able to define an overhead line.
  • Overhead lines: It is a composition of wires bundled by phase (A:1, B:2, C:3, Neutral:0) that represents an overhead line. The overhead lines can be further edited using the Overhead Line Editor (see below)
  • Underground lines: Underground lines are defined with the zero sequence and positive sequence parameters.
  • Sequence lines: Generic sequence lines are defined with the zero sequence and positive sequence parameters.
  • Transformers: The three phase transformers are defined with the short circuit study parameters.

Visit the theory section to learn more about these models.

GridCal device type templates editor.

Overhead line editor

The overhead line editor allows you to define an overhead line in any way you want, bundling many wires per phase if you need and including the neutral. The equations for this functionality are taken from the EMTP theory book.

GridCal overhead lines editor.

Z: This tab shows the series impedance matrices with the reduced neutral (3x3) and without the reduced neutral (4x4) if the neutral wire is present.

Y: This tab shows the shunt admittance matrices with the reduced neutral (3x3) and without the reduced neutral (4x4) if the neutral wire is present.

Time series

This screen allows you to visualize, create and manipulate the profiles of the various magnitudes of the program.

GridCal time series tabular editor.

The time series is what make GridCal what it is. To handle time series efficiently by design is what made me design this program.

Profiles importer

From the time series you can access the time series importer. This is a program to read excel and csv files from which to import the profiles. Each column of the imported file is treated as an individual profile. The imported profiles can be normalized and scaled. Each profile can be assigned in a number of ways to the objects for which the profiles are being imported.

GridCal time series import interface.

Linking methods:

  • Automatically based on the profile name and the object’s names.
  • Random links between profiles and objects; Each object is assigned with a random profile.
  • Assign the selected profile to all objects.
  • Assign the selected profile to the selected objects.

Array viewer

The array viewer is an utility to inspect the array-like objects that are being passed to the numerical methods. These are arranged per island of the circuit.

GridCal compiled arrays for calculation viewer.

Comments editor

Simple text box where to write comments about the project.

GridCal model comments editor.

Results

The results view is where ou can visualize the results for all the available simulations. This feature stands out from the commercial power systems software where to simply view the results is not standarized or simple.

GridCal results graphical viewer.

Tabular view

The tabular view of the results displays the same information as the graphical view but numerically such that you can copy it to a spreadsheet software, or save them for later use.

GridCal results tabular viewer.

Console

The console in GridCal is a very nice addition that allows some degree of automation within the graphical user interface. The console is a normal python console (embedded in a python program!) where the circuit declared in the user interface (app) is accessible (App.circuit).

GridCal python console (python from within python!).

Some logs from the simulations will be displayed here. Apart from this any python command or operation that you can perform with scripts can be done here.

Settings

The general settings are:

Base power
GridCal works with the magnitudes in per unit. In the per unit system the base magnitude is set in advance. This is the base value of the power in MVA. It is advised not to be changed.
Frequency
The value of the frequency of the grid in Hertz (Hz).
Use multiprocessing
For simulations that can be run in parallel, the software allows to use all the processing power by launching simulations ina parallel. This is only available for UNIX systems due to the way parallelism is implemented in the windows versions of python.
Export visualization
Factor of resolution when exporting the schematic. This is a multiplier of the resolution 1080 x 1920 pixels.
Plotting style
Matplotlib plotting style.

Power flow

GridCal power flow settings.
Solver

The power flow solver to use.

  • Newton-Raphson in power:
  • Newton-Raphson in current:
  • Newton-Raphson-Iwamoto:
  • Levenberg-Marquardt:
  • Fast-Decoupled:
  • Holomorphic-Embedding:
  • Linear AC approximation:
  • DC approximation:

All these solvers are covered in the theory section.

Retry with other methods is failed:
This option tries other numerical solvers to try to find a power flow solution. This option is relevant because different numerical algorithms may be more suited to certain grid configurations. In general the Newton-Raphson implementation in GridCal includes back-tracing and other innovations that make it a very competitive method to consider by default.
Automatic precision
The precision to use for the numerical solvers depends on the magnitude of the power injections. If we are dealing with hundreds of MW, the precision may be 1e-3, but if we are dealing with Watts, the precision has to be greater. The automatic precision checks the loading for a suitable precision such that the results are fine.
Precision
Exponent of the numerical precision. i.e. 4 corresponds to 1e-4 MW in p.u. of precision
Numerical method max. iterations
Number of “inner” iterations of the numerical method before terminating.
Outer loop max. iterations
Number of “outer loop” iterations to figure out the values of the set controls.
Reactive power control mode

This is the mode of reactive power control for the generators that are set in PV mode.

  • No control: The reactive power limits are not enforced.
  • Direct: The classic pq-pv switching algorithm.
  • Iterative: An iterative algorithm that uses the power flow as objective function to find suitable reactive power limits.
Q steepness factor (iterative ctrl.)
Steepness factor for the iterative reactive power control.

Transformer taps control mode

  • No control: The transformer voltage taps control is not enforced.
  • Direct:
  • Iterative:
Apply temperature correction
When selected the branches apply the correction of the resistance due to the temperature.
Apply impedance tolerances
???

Optimal power flow

GridCal Optimal power flow settings.
Solver

Optimal power flow solver to use

DC OPF: classic optimal power flow mixing active power with lines reactance. AC OPF: Innovative linear AC optimal power flow based on the AC linear power flow implemented in GridCal.

Load shedding
This option activates the load shedding slack. It is possible to assign an arbitrary weight to this slack.
Generation shedding
This option activated the generation shedding slack. It is possible to assign an arbitrary weight to this slack.
Show the real associated values
Compute a power flow with the OPF results and show that as the OPF results.
Control batteries
Control the batteries state of charge when running the optimization in time series.

Voltage stability

GridCal voltage collapse settings.
Max. Iterations
Number of iteration to perform at each voltage stability (predictor-corrector) stage.
Stop at

Point of the curve to top at

  • Nose: Stop at the voltage collapse point
  • Full: Trace the full curve.
Use alpha target from the base situation
The voltage collapse (stability) simulation is a “travel” from a base situation towards a “final” one. When this mode is selected the final situation is a linear combination of the base situation. All the power values are multiplied by the same number.
Use departure and target points from time series
When this option is selected the base and the target points are given by time series points. This allows that the base and the final situations to have non even relationships while evolving from the base situation to the target situation.

Stochastic power flow

GridCal stochastic power flow settings.
Precision
Monte carlo standard deviation to achieve. The number represents the exponent of the precision. i.e. 3 corresponds to 1e-3
Max. Iterations
Maximum iterations for Monte Carlo sampling if the simulation does not achieve the selected standard deviation.
Samples
Number of samples for the latin hypercube sampling.
Additional islands until stop
When simulating the blackout cascading, this is the number of islands that determine the stop of a simulation

Topology

GridCal topology processor settings.
Select branch types to reduce
The topological reduction is a top feature of GridCal. With it you can remove the influence of the redundant branches. This is specially relevant when you are provided with grids that have thousands of switches and connection branches that add no simulation value. Those can be removed in a very smart way.
Filter by r+x under threshold
This feature establishes if to topologically remove branches whose resistance + reactance is lower than a threshold. The threshold is given by the exponent number. i.e. 5 corresponds to r+x < 1e-5.
Automatic layout algorithm
Another nice feature in GridCal is the ability to sort bus bar locations according to a graph algorithm. This is especially useful when you are provided with a grid that has no schematic, where the graphical representation depict all the bus bars in the same place.
Ask before applying
Raise a question before applying the graph layout algorithm.
Node expansion factor
The nodes in GridCal can be expanded (far from each other) or shrink (closer) this parameter set the “explosion” factor that determines how far from each other shall the nodes become.
Branch rating factor
For the branch automatic rating, this is the rate multiplier.
Override values
If selected any non-zero rate is overridden by the calculated value.

Development

Contributing

You have found a bug in GridCal or have a suggestion for a new functionality? Then get in touch with us by opening up an issue on the issue board to discuss possible new developments with the community and the maintainers.

Setup your git repository

Note: The following setup is just a suggestion of how to setup your repository and is supposed to make contributing easier, especially for newcomers. If you have a different setup that you are more comfortable with, you do not have to adopt this setup.

If you want to contribute for the first time, you can set up your environment like this:

  • If you have not done it yet: install git and create a GitHub account;
  • Create a fork of the official GridCal repository by clicking on “Fork” in the official repository;
  • Clone the forked repository to your local machine: git clone https://github.com/YOUR-USERNAME/GridCal.git
  • Copy the following configuration at the bottom of to the gridcal/.git/config file (the .git folder is hidden, so you might have to enable showing hidden folders) and insert your github username:
[remote "origin"]
    url = https://github.com/YOUR-USERNAME/GridCal.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    pushurl = https://github.com/YOUR-USERNAME/GridCal.git
[remote "upstream"]
    url = https://github.com/SanPen/GridCal.git
    fetch = +refs/heads/*:refs/remotes/upstream/*
[branch "master"]
    remote = origin
    merge = refs/heads/master

The master branch is now configured to automatically track the official GridCal master branch. So if you are on the master branch and use:

git fetch upstream
git merge upstream/master

…your local repository will be updated with the newest changes in the official GridCal repository.

Since you cannot push directly to the official GridCal repository, if you are on master and do:

git push

…your push is by default routed to your own fork instead of the official GridCal repository with the setting as defined above.

Contribute

All contributions to the GridCal repository are made through pull requests to the master branch. You can either submit a pull request from the develop branch of your fork or create a special feature branch that you keep the changes on. A feature branch is the way to go if you have multiple issues that you are working on in parallel and want to submit with seperate pull requests. If you only have small, one-time changes to submit, you can also use the master branch to submit your pull request.

If you wish to discuss a contribution before the pull request is ready to be officially submitted, create an issue in the official repository and link to your own fork. Do not create pull requests that are not ready to be merged!

Note: The following guide assumes the remotes are set up as described above. If you have a different setup, you will have to adapt the commands accordingly.

Test Suite

GridCal uses pytest for automatic software testing.

If you make changes to GridCal that you plan to submit, first make sure that all tests are still passing. You can do this locally with:

pytest

If you have added new functionality, you should also add a new function that tests this functionality. pytest automatically detects all functions in the src/tests folder that start with test_ and are located in a file that also starts with test_ as relevant test cases.

Testing with pytest

Unit test (for pytest) are included in src/tests. As defined in pytest.ini, all files matching test_*.py are executed by running:

pytest

Files matching *_test.py are not executed; they were not formatted specifically for pytest but were mostly done for manual testing and documentation purposes.

Additional tests should be developped for each new and existing feature. pytest should be run before each commit to prevent easily detectable bugs.

Change log

This section describes the changes introduced at each Version.

* Short releases indicate the fix of a critical bug.

* Notice that some versions skip numbers. This is not an error, this is because the stupid policy of pypi to not allow to correct packages. Hence if something goes wrong, you need to re-upload with a new Version number.

Version 3.5.3

  • Added voltage angle in the power flow results and time series power flow results. About time!
  • Removed warnings from the power flow driver. Now the warnings are stored in a log and displayed in the GUI.

Version 3.5.2

  • Removed pulp dependency in the generator objects (forced a critical update)
  • Added some icons in the GUI

Version 3.5.1

  • Simplified and unified the OPF interfaces.
  • Added AC-liner OPF time series as a non-sequential algorithm.
  • Added shadow prices to the non-sequential OPF.
  • Added the handling of dispatchable non dispatchable generators to the OPF.
  • Fixed bug with the OPF offset when starting at a index other than 0.
  • Fixed bug with time grouping that repeated the last index.
  • Fixed bug with the delegates setting for the boolean values

Version 3.5.0 (commemorating the 100 GitHub stars)

  • Added pulp as an embedded dependency, and updated its CBC solver with a custom compiled one from the latest sources.
  • Fixed some bug related to the OPF storage and results display in non-sequential mode.

Version 3.4.2

  • Fixed branch saving code (hopefully forever)
  • Fixed the loading of some properties that were missing.
  • Fixed the non-sequential OPF.

Version 3.4.1

  • Added branch voltage and angle drops in the power flow and power flow time series simulations.
  • Added cost profiles for the use in the OPF programs.
  • Fixed critical bug when applying profile to snapshot.
  • Fixed pySide related bug when converting dates.
  • Fixed ui bug when setting values in the profiles manually.

Version 3.4.0

  • Now when highlighting the selection, the buses on the schematic are selected. This feature allows to move buses in bulk after any selection kind.
  • Added feature to highlight buses based on any numeric property from the grid objects.
  • Added “master” delete from the schematic. Now any selection of buses from the schematic can be deleted at once.

Version 3.3.9

  • Improved object filtering.
  • Fixed critical bug involving the change to setuptools.

Version 3.3.7

  • Added filtering capabilities to the object browser.
  • Added Bus reduction.
  • Added bus highlight based on the object filtering.

Version 3.3.6

  • Continued to improved PSS/e .raw support.
  • Fixed the bug caused by PySide2 with the excel sheet selection window.

Version 3.3.5

  • Greatly improved PSS/e .raw file import support.

Version 3.3.4

  • The tower names are displayed correctly now.
  • Completely switched from PyQt5 to PySide2.
  • Added support for PSS/e RAW file format Version 29.
  • Overall bug fix.

Version 3.3.0

  • Now the branches and the buses have activation profiles. This allows to run time series where the topology changes. Only available for time series for the moment.
  • The branches now allow to profile their temperature. This allows to change the resistance to explore heat effects.
  • Added undo / redo to the profiles editor. This improves usability quite a bit.
  • Added csv files into zip files as the GridCal default format. This allows to use the same logic as with the excel files but with much faster saving and loading times. Especially suited for large grids with large profiles.
  • Added error logging for the power flow time series.
  • Massive refactoring of the the files in the program structure, hoping to provide a more intuitive interface.
  • Replace the internal profiles from Pandas DataFrames to numpy arrays. This makes the processing simpler and more robust.
  • Added rating to cables.
  • Changed the TransformerType inner property names to shorter ones.
  • Plenty of bug fixes.

This documentation is generated using Sphinx and autodoc.