Getting started with Roseau Load Flow¶
Make sure you have followed the installation instructions.
In this tutorial you will learn how to:
Creating a network¶
An electrical network can be built by assembling basic elements described in the Models section. The following is a summary of the available elements:
Buses:
Bus
: A basic multiphase electrical bus.
Branches:
Line
: A line connects two buses. The physical parameters of the line, like its impedance, are defined by aLineParameters
object.Switch
: A basic switch element.Transformer
: A generic transformer. The physical parameters of the transformer like its impedance and winding configuration are defined by aTransformerParameters
object.
Loads:
The ZIP load model is available via the following classes:
ImpedanceLoad
: A constant impedance (Z) load: \(S = V^2 \times \overline{Z}\), \(S\) is proportional to \(V^2\).CurrentLoad
A constant current (I) load: \(S = V \times \overline{I}\), \(S\) is proportional to \(V^1\).PowerLoad
: A constant power (P) load: \(S = \mathrm{constant}\), \(S\) is proportional to \(V^0\).
A power load can be made flexible (controllable) by using the following class:
FlexibleParameter
: This object defines the parameters of the flexible load’s control (Maximum power, projection, type, etc.)
Note that flexible loads are an advanced feature that most users don’t need. They are explained in details here.
Sources:
VoltageSource
: Represents an infinite power source with a constant voltage.
Others:
Ground
: A ground acts as a perfect conductor. If two elements are connected to the ground, the potentials at the connection points are always equal.PotentialRef
: A potential reference sets the reference of potentials in the network. It can be connected to buses or grounds.
Let’s use some of these elements to build the following network with a voltage source, a simple line and a constant power load. This network is a low voltage network (threephase + neutral wire).
>>> import numpy as np
... import roseau.load_flow as rlf
>>> # Nominal phasetoneutral voltage (typical european voltage)
... un = 400 / np.sqrt(3) # In Volts
... # Optional network limits (for analyzing network violations)
... u_min = 0.9 * un # V
... u_max = 1.1 * un # V
... i_max = 500.0 # A
>>> # Create two buses (notice the phases of LV buses include the neutral, as in typical LV networks)
... source_bus = rlf.Bus(id="sb", phases="abcn", min_voltage=u_min, max_voltage=u_max)
... load_bus = rlf.Bus(id="lb", phases="abcn", min_voltage=u_min, max_voltage=u_max)
>>> # Define the reference of potentials to be the earth's potential
... # First define the "Ground" object representing the earth
... ground = rlf.Ground(id="gnd")
... # second, fix the potential of the ground at 0 V
... pref = rlf.PotentialRef(id="pref", element=ground)
... # third, ground the neutral wire of the source bus by connecting it to the Ground element
... ground.connect(source_bus, phase="n")
>>> # Create a threephase voltage source at the first bus
... # (voltages are phasetoneutral because the source's phases include a neutral)
... vs = rlf.VoltageSource(id="vs", bus=source_bus, voltages=un)
>>> # Add a 30kW load at the second bus (balanced load, 10 kW per phase)
... load = rlf.PowerLoad(id="load", bus=load_bus, powers=[10e3 + 0j, 10e3, 10e3]) # In VA
>>> # Add a 2 km line between the source bus and the load bus with R = 0.1 Ohm/km and X = 0
... lp = rlf.LineParameters("lp", z_line=(0.1 + 0.0j) * np.eye(4), max_current=i_max)
... line = rlf.Line(id="line", bus1=source_bus, bus2=load_bus, parameters=lp, length=2.0)
Notice how the phases of the load, line and source are not explicitly given. They are inferred to be
"abcn"
from the buses they are connected to. You can also explicitly declare the phases of these
elements. For example, to create a deltaconnected source instead, you can explicitly set its phases
to "abc"
:
>>> vs_delta = rlf.VoltageSource(
... id="vs_delta",
... bus=source_bus,
... voltages=un * np.sqrt(3),
... phases="abc", # < delta source
... )
Note the use of un * np.sqrt(3)
as the source voltages as they now represent the phasetophase
voltages. This is because everywhere in roseauloadflow
, the voltages
of an element depend on
the element’s phases
. Voltages of elements connected in a Star (wye) configuration (elements
that have a neutral connection indicated by the presence of the 'n'
character in their phases
attribute) are the phasetoneutral voltages. Voltages of elements connected in a Delta
configuration (elements that do not have a neutral connection indicated by the absence of the
'n'
char from their phases
attribute) are the phasetophase voltages. To see between which
phases the voltage is defined, you can use the voltage_phases
property of the element.
>>> vs.voltage_phases
['an', 'bn', 'cn']
>>> vs_delta.voltage_phases
['ab', 'bc', 'ca']
When creating balanced loads, you can, as a shortcut, pass a single scalar value to the powers
argument. This value will be used equally for all phases. For example, the load definition above
can be simplified as follows:
>>> load = rlf.PowerLoad(
... id="load", bus=load_bus, powers=10e3 + 0j
... ) # 10kW per phase, 30kW total
At this point, all the basic elements of the network have been defined and connected. Now,
everything can be encapsulated in an ElectricalNetwork
object, but first, some important
notes on the Ground
and PotentialRef
elements:
Important
The Ground
element does not have a fixed potential as one would expect from a real ground
connection. The ground element here merely represents a “perfect conductor”, equivalent to a
singleconductor line with zero impedance. The potential reference, 0 Volts, is defined by the
PotentialRef
element that itself can be connected to any bus or ground in the network. This
gives more flexibility for the user to define the potential reference of their network.
A PotentialRef
defines the potential reference for the network. It is a mandatory reference
for the load flow resolution to be welldefined. A network MUST have one and only one potential
reference per a galvanically isolated section.
When in doubt, define the ground and potential reference similar to the example above.
An ElectricalNetwork
object can now be created using the from_element
constructor. The source
bus source_bus
is given to this constructor. All the elements connected to this bus are
automatically included into the network.
>>> en = rlf.ElectricalNetwork.from_element(source_bus)
Solving a load flow¶
A license is required. You can use the free but limited licence key or get a personal and unlimited key by contacting us at contact@roseautechnologies.com. Once you have your license key, you can activate it by following the instructions in the License activation page.
Afterwards, the load flow can be solved by calling the solve_load_flow
method of the ElectricalNetwork
>>> en.solve_load_flow()
(2, 1.8595619621919468e07)
It returns the number of iterations performed by the NewtonRaphson solver, and the residual error after convergence. Here, the load flow converged in 2 iterations with a residual error of \(1.86 \times 10^{7}\).
Getting the results¶
The results are now available for all elements of the network. Results can be accessed through
special properties prefixed with res_
on each element. For instance, the potentials
of the load_bus
can be accessed using the property load_bus.res_potentials
. It contains 4
values representing the potentials of its phases a
, b
, c
and n
(neutral). The potentials
are returned as complex numbers. Calling abs(load_bus.res_potentials)
returns their magnitude
(in Volts) and np.angle(load_bus.res_potentials)
returns their angle (phase shift) in radians.
Roseau Load Flow uses Pint Quantity
objects to present
unitaware data to the user. Most input data (load powers, source voltages, etc.) are expected
to be either given in SI units or using the pint Quantity interface for nonSI units (example below).
Look at the documentation of a method to see its default units.
In the following example, we create a load with powers expressed in kVA:
load = rlf.PowerLoad(
id="load", bus=load_bus, phases="abcn", powers=rlf.Q_([10, 10, 10], "kVA")
)
The results returned by the res_
properties are also Quantity
objects.
Available results¶
The available results depend on the type of element. The following table lists the available results for each element type:
Element type 
Available results 

















¹ res_flexible_powers
is only available for flexible loads (PowerLoad
s with flexible_params
). You’ll see
an example on the usage of flexible loads in the “Flexible Loads” page.
Getting results per object¶
In order to get the potentials or voltages of a bus, use the res_potentials
or res_voltages
properties of buses as follows:
>>> load_bus.res_potentials
array([ 2.21928183e+022.60536682e18j, 1.10964092e+021.92195445e+02j,
1.10964092e+02+1.92195445e+02j, 2.68637675e156.67652444e17j]) <Unit('volt')>
As the results are pint quantities, they can be converted to different units easily.
>>> abs(load_bus.res_voltages).to("kV") # Get a Quantity in kV
array([0.22192818, 0.22192818, 0.22192818]) <Unit('kilovolt')>
>>> abs(load_bus.res_voltages).m_as("kV") # Get the magnitude in kV
array([0.22192818, 0.22192818, 0.22192818])
>>> abs(load_bus.res_voltages).m # Get the default magnitude (Volts)
array([221.928183, 221.928183, 221.928183])
The currents of the line are available using the res_currents
property of the line
object.
It contains two arrays:
the first is the current flowing from the first bus of the line to the second bus of the line. It contains 4 values: one per phase and the neutral current.
the second is the current flowing from the second bus of the line to the first bus of the line.
Here, the sum of these currents is 0 as we have chosen a simple line model, i.e, a line with only series impedance elements without shunt components. If shunt components were modelled, the sum would have been nonzero.
>>> line.res_currents
(array([ 4.50596216e+01+1.30268341e17j, 2.25298108e+013.90227770e+01j,
2.25298108e+01+3.90227770e+01j, 1.34318838e14+3.33826222e16j]) <Unit('ampere')>,
array([4.50596216e+011.30268341e17j, 2.25298108e+01+3.90227770e+01j,
2.25298108e+013.90227770e+01j, 1.34318838e143.33826222e16j]) <Unit('ampere')>)
Dataframe network results¶
The results can also be retrieved for the entire network using res_
properties of the
ElectricalNetwork
instance as pandas DataFrames.
Available results for the network are:
res_buses
: Buses potentials indexed by (bus id, phase)res_buses_voltages
: Buses voltages and voltage limits indexed by (bus id, voltage phase²)res_transformers
: Transformers currents, powers, potentials, and power limits indexed by (transformer id, phase)res_lines
: Lines currents, powers, potentials, series losses, series currents, and current limits indexed by (line id, phase)res_switches
: Switches currents, powers, and potentials indexed by (switch id, phase)res_loads
: Loads currents, powers, and potentials indexed by (load id, phase)res_loads_voltages
: Loads voltages indexed by (load id, voltage phase)res_loads_flexible_powers
: Loads flexible powers (only for flexible loads) indexed by (load id, voltage phase)res_sources
: Sources currents, powers, and potentials indexed by (source id, phase)res_grounds
: Grounds potentials indexed by ground idres_potential_refs
: Potential references currents indexed by potential ref id (always zero for a successful load flow)
² a “voltage phase” is a composite phase like an
or ab
All the results are complex numbers. You can always access the magnitude of the results using
the abs
function and the angle in radians using the np.angle
function. For instance,
abs(network.res_loads)
gives you the magnitude of the loads’ results in SI units.
Below are the results of the load flow for en
, rounded to 2 decimal places:
>>> en.res_buses
bus_id 
phase 
potential 

sb 
a 
230.94+0j 
sb 
b 
115.47200j 
sb 
c 
115.47+200j 
sb 
n 
0j 
lb 
a 
221.930j 
lb 
b 
110.96192.2j 
lb 
c 
110.96+192.2j 
lb 
n 
0j 
>>> en.res_buses_voltages
bus_id 
phase 
voltage 
min_voltage 
max_voltage 
violated 

sb 
an 
230.94+0j 
207.846 
254.034 
False 
sb 
bn 
115.47200j 
207.846 
254.034 
False 
sb 
cn 
115.47+200j 
207.846 
254.034 
False 
lb 
an 
221.930j 
207.846 
254.034 
False 
lb 
bn 
110.96192.2j 
207.846 
254.034 
False 
lb 
cn 
110.96+192.2j 
207.846 
254.034 
False 
>>> en.res_lines
line_id 
phase 
current1 
current2 
power1 
power2 
potential1 
potential2 
series_losses 
series_current 
max_current 
violated 

line 
a 
45.060j 
45.06+0j 
10406.07+0j 
100000j 
230.94+0j 
221.93+0j 
406.070j 
45.060j 
500 
False 
line 
b 
22.5339.02j 
22.53+39.02j 
10406.07+0j 
100000j 
115.47200j 
110.96192.2j 
406.070j 
22.5339.02j 
500 
False 
line 
c 
22.53+39.02j 
22.5339.02j 
10406.070j 
10000+0j 
115.47+200j 
110.96+192.2j 
406.07+0j 
22.53+39.02j 
500 
False 
line 
n 
00j 
0j 
0+0j 
0j 
0j 
0j 
0j 
00j 
500 
False 
>>> en.res_transformers # empty as the network does not contain transformers
transformer_id 
phase 
current1 
current2 
power1 
power2 
potential1 
potential2 
max_power 
violated 

>>> en.res_switches # empty as the network does not contain switches
switch_id 
phase 
current1 
current2 
power1 
power2 
potential1 
potential2 

>>> en.res_loads
load_id 
phase 
type 
current 
power 
potential 

load 
a 
power 
45.06+0j 
100000j 
221.930j 
load 
b 
power 
22.5339.02j 
100000j 
110.96192.2j 
load 
c 
power 
22.53+39.02j 
10000+0j 
110.96+192.2j 
load 
n 
power 
0j 
0j 
0j 
>>> en.res_loads_voltages
load_id 
phase 
type 
voltage 

load 
an 
power 
221.93+0j 
load 
bn 
power 
110.96192.2j 
load 
cn 
power 
110.96+192.2j 
>>> en.res_sources
source_id 
phase 
current 
power 
potential 

vs 
a 
45.060j 
10406.07+0j 
230.94+0j 
vs 
b 
22.53+39.02j 
10406.070j 
115.47200j 
vs 
c 
22.5339.02j 
10406.07+0j 
115.47+200j 
vs 
n 
0j 
0j 
0j 
>>> en.res_grounds
ground_id 
potential 

gnd 
0j 
>>> en.res_potential_refs
potential_ref_id 
current 

pref 
0j 
Using the transform
method of data frames, the results can easily be converted from complex values
to magnitude and angle values (radians).
>>> en.res_buses_voltages["voltage"].transform([np.abs, np.angle])
bus_id 
phase 
absolute 
angle 

sb 
an 
230.94 
0 
sb 
bn 
230.94 
2.0944 
sb 
cn 
230.94 
2.0944 
lb 
an 
221.928 
2.89102e19 
lb 
bn 
221.928 
2.0944 
lb 
cn 
221.928 
2.0944 
Or, if you prefer degrees:
>>> import functools as ft
... en.res_buses_voltages["voltage"].transform([np.abs, ft.partial(np.angle, deg=True)])
bus_id 
phase 
absolute 
angle 

sb 
an 
230.94 
0 
sb 
bn 
230.94 
120 
sb 
cn 
230.94 
120 
lb 
an 
221.928 
1.65643e17 
lb 
bn 
221.928 
120 
lb 
cn 
221.928 
120 
Analyzing the results and detecting violations¶
In the example network above, min_voltage
and max_voltage
arguments were passed to the Bus
constructor and max_current
was passed to the LineParameters
constructor. These arguments
define the limits of the network that can be used to check if the network is in a valid state
or not. Note that these limits have no effect on the load flow calculation.
If you set min_voltage
or max_voltage
on a bus, the res_violated
property will tell you if
the voltage limits are violated or not at this bus. Here, the voltage limits are not violated.
>>> load_bus.res_violated
False
Similarly, if you set max_current
on a line, the res_violated
property will tell you if the
current loading of the line in any phase exceeds the limit. Here, the current limit is not violated.
>>> line.res_violated
False
The power limit of the transformer can be defined using the max_power
argument of the
TransformerParameters
. Transformers also have a res_violated
property that indicates if the
power loading of the transformer exceeds the limit.
The data frame results on the electrical network also include a violated
column that indicates if
the limits are violated or not for the corresponding element.
Tip
You can use the Bus.propagate_limits()
method to
propagate the limits from a bus to buses connected to it galvanically (i.e. via lines or switches).
Updating elements of the network¶
Network elements can be updated. Here, the load’s power values are changed to create an unbalanced situation.
>>> load.powers = rlf.Q_(
... [15, 0, 0], "kVA"
... ) # < 15 kW on phase "a", 0 W on phases "b" and "c"
>>> en.solve_load_flow()
(3, 1.686343545e07)
>>> load_bus.res_potentials
array([ 216.02252269 +0.j, 115.47005384200.j, 115.47005384+200.j, 14.91758499 +0.j]) <Unit('volt')>
Notice how the unbalance manifested in the neutral’s potential of the bus no longer being close to 0 V.
More information on modifying the network elements can be found in the Modifying a network page.
Saving/loading the network¶
An electrical network can be written to a JSON file, for later analysis or for sharing with others,
using the to_json()
method.
>>> en.to_json("my_network.json")
For more information see this section on network JSON serialization.