We load the simple example network from the create_network tutorial from the pandapower.networks module:
import pandapower as pp
import pandapower.networks
net = pandapower.networks.example_simple()
net
This pandapower network includes the following parameter tables: - bus (7 elements) - load (1 element) - sgen (1 element) - gen (1 element) - switch (8 elements) - shunt (1 element) - ext_grid (1 element) - line (4 elements) - trafo (1 element)
Runing a loadflow adds seperate result table with the prefix 'res_':
pp.runpp(net)
net
This pandapower network includes the following parameter tables: - bus (7 elements) - load (1 element) - sgen (1 element) - gen (1 element) - switch (8 elements) - shunt (1 element) - ext_grid (1 element) - line (4 elements) - trafo (1 element) and the following results tables: - res_bus (7 elements) - res_line (4 elements) - res_trafo (1 element) - res_ext_grid (1 element) - res_load (1 element) - res_sgen (1 element) - res_shunt (1 element) - res_gen (1 element) - res_switch (8 elements)
These results tables are pandas datafarmes with the same index as the element table. For example, the bus table contains all bus voltages and summed bus power injections:
net.res_bus
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
0 | 1.020000 | 0.000000 | 6.741115 | 7.146883 |
1 | 1.020830 | 0.032414 | 0.000000 | 0.000000 |
2 | 1.020830 | 0.032414 | 0.000000 | -1.000409 |
3 | 1.024562 | 1.802848 | 0.000000 | 0.000000 |
4 | 1.024562 | 1.802848 | 0.000000 | 0.000000 |
5 | 1.030000 | 1.870455 | -6.000000 | -3.421910 |
6 | 1.023205 | 1.952224 | -0.800000 | 2.900000 |
We can now use pandas functionality to analyse the loadflow results, for example to get the minimum voltage in the medium voltage level:
net.res_bus[net.bus.vn_kv==20.].vm_pu.min()
1.023205339154807
or the maxium voltage at a bus with load or generation:
load_or_generation_buses = set(net.load.bus.values) | set(net.sgen.bus.values) | set(net.gen.bus.values)
net.res_bus.vm_pu.loc[list(load_or_generation_buses)].max()
C:\Users\mmilovic\AppData\Local\Temp\ipykernel_16060\2520291681.py:2: FutureWarning: Passing a set as an indexer is deprecated and will raise in a future version. Use a list instead. net.res_bus.vm_pu.loc[load_or_generation_buses].max()
1.03
For more on how to use pandas for data analysis in pandapower, see the tutorial on data analysis.
Each element (except the switch) has its own result table with results tailored to the specific element. Here, we just show each table. For parameters definitions, see the documentation of the datastructure.
net.res_bus
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
0 | 1.020000 | 0.000000 | 6.741115 | 7.146883 |
1 | 1.020830 | 0.032414 | 0.000000 | 0.000000 |
2 | 1.020830 | 0.032414 | 0.000000 | -1.000409 |
3 | 1.024562 | 1.802848 | 0.000000 | 0.000000 |
4 | 1.024562 | 1.802848 | 0.000000 | 0.000000 |
5 | 1.030000 | 1.870455 | -6.000000 | -3.421910 |
6 | 1.023205 | 1.952224 | -0.800000 | 2.900000 |
net.res_ext_grid
p_mw | q_mvar | |
---|---|---|
0 | -6.741115 | -7.146883 |
net.res_line
p_from_mw | q_from_mvar | p_to_mw | q_to_mvar | pl_mw | ql_mvar | i_from_ka | i_to_ka | i_ka | vm_from_pu | va_from_degree | vm_to_pu | va_to_degree | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -6.741115e+00 | -7.146883 | 6.744162e+00 | 1.454505e+00 | 3.046845e-03 | -5.692378 | 0.050554 | 3.547266e-02 | 0.050554 | 1.020000 | 0.000000 | 1.020830 | 0.032414 | 8.597638 |
1 | -5.972390e+00 | -3.481625 | 6.000000e+00 | 3.426342e+00 | 2.761024e-02 | -0.055283 | 0.194780 | 1.936478e-01 | 0.194780 | 1.024562 | 1.802848 | 1.030000 | 1.870455 | 46.266070 |
2 | 2.406266e-08 | -0.004433 | 2.925897e-14 | 9.551979e-16 | 2.406269e-08 | -0.004433 | 0.000124 | 8.204641e-16 | 0.000124 | 1.030000 | 1.870455 | 1.030007 | 1.869833 | 0.059160 |
3 | 8.000000e-01 | -2.900000 | -7.936182e-01 | 2.805738e+00 | 6.381811e-03 | -0.094262 | 0.084873 | 8.215450e-02 | 0.084873 | 1.023205 | 1.952224 | 1.024562 | 1.802848 | 20.159919 |
net.res_trafo
p_hv_mw | q_hv_mvar | p_lv_mw | q_lv_mvar | pl_mw | ql_mvar | i_hv_ka | i_lv_ka | vm_hv_pu | va_hv_degree | vm_lv_pu | va_lv_degree | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -6.744162 | -0.454096 | 6.766008 | 0.675887 | 0.021846 | 0.221791 | 0.034754 | 0.191584 | 1.02083 | 0.032414 | 1.024562 | 1.802848 | 26.546681 |
net.res_load
p_mw | q_mvar | |
---|---|---|
0 | 1.2 | 2.4 |
net.res_sgen
p_mw | q_mvar | |
---|---|---|
0 | 2.0 | -0.5 |
net.res_gen
p_mw | q_mvar | va_degree | vm_pu | |
---|---|---|---|---|
0 | 6.0 | 3.42191 | 1.870455 | 1.03 |
net.res_shunt
p_mw | q_mvar | vm_pu | |
---|---|---|---|
0 | 0.0 | -1.000409 | 1.02083 |
Maybe you wondered why even though there is a voltage angle of 50 degrees defined for the external grid:
net.ext_grid.va_degree
0 50.0 Name: va_degree, dtype: float64
and a shift of 150° over the HV/MV transformer:
net.trafo.shift_degree
0 150.0 Name: shift_degree, dtype: float64
the voltage angles are all close to zero:
pp.runpp(net)
net.res_bus.va_degree
0 0.000000 1 0.032414 2 0.032414 3 1.802848 4 1.802848 5 1.870455 6 1.952224 Name: va_degree, dtype: float64
That is because the standard parameter for calculate_voltage_angles is False, which means voltage angles at external grids and transformer shifts are ignored by default. In a radial network, the absolute voltage angle shifts do not have an influence on the power flow, which is why they are disabled by default. In meshed networks however, where multiple external grids are galvanically coupled, it is always necessary to calculate the voltage angles.
Suppose we want to calculate the correct voltage angles and set calculate_voltage_angles to True:
pp.runpp(net, calculate_voltage_angles=True)
Now the power flow does not converge. This can happen with large angle shifts. The solution is to use a initialization with a DC loadflow instead of a flat start, which is default behaviour:
pp.runpp(net, calculate_voltage_angles=True, init="dc")
Now, we can see that all voltage angles are correctly calculated:
net.res_bus.va_degree
0 50.000000 1 50.032414 2 50.032414 3 -98.197152 4 -98.197152 5 -98.129545 6 -98.047776 Name: va_degree, dtype: float64
If we already have a solution, we can also initialize the loadflow with the voltage values from the last loadflow:
pp.runpp(net, calculate_voltage_angles=True, init="results")
net.res_bus.va_degree
0 50.000000 1 50.032414 2 50.032414 3 -98.197152 4 -98.197152 5 -98.129545 6 -98.047776 Name: va_degree, dtype: float64
The power flow converges and yields correct results where a flat start power flow would have failed.
Initializing with previous results can save convergence time in cases where multiple power flows with simliar input parameters are carried out consecutively, such as in quasi-static time series simulations.
The parameter "trafo_model" can be used to switch between a 'pi' and a 't' transformer model:
pp.runpp(net, trafo_model="t")
net.res_trafo
p_hv_mw | q_hv_mvar | p_lv_mw | q_lv_mvar | pl_mw | ql_mvar | i_hv_ka | i_lv_ka | vm_hv_pu | va_hv_degree | vm_lv_pu | va_lv_degree | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -6.744162 | -0.454096 | 6.766008 | 0.675887 | 0.021846 | 0.221791 | 0.034754 | 0.191584 | 1.02083 | 0.032414 | 1.024562 | 1.802848 | 26.546681 |
pp.runpp(net, trafo_model="pi")
net.res_trafo
p_hv_mw | q_hv_mvar | p_lv_mw | q_lv_mvar | pl_mw | ql_mvar | i_hv_ka | i_lv_ka | vm_hv_pu | va_hv_degree | vm_lv_pu | va_lv_degree | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -6.744159 | -0.45401 | 6.766008 | 0.675798 | 0.021849 | 0.221788 | 0.034754 | 0.191584 | 1.020829 | 0.032414 | 1.024562 | 1.802822 | 26.546646 |
For a definition of the different transformer model see the power flow model documentation of the transformer element.
The transformer loading can either be calculated in relation to the rated current:
pp.runpp(net, trafo_loading="current")
net.res_trafo
p_hv_mw | q_hv_mvar | p_lv_mw | q_lv_mvar | pl_mw | ql_mvar | i_hv_ka | i_lv_ka | vm_hv_pu | va_hv_degree | vm_lv_pu | va_lv_degree | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -6.744162 | -0.454096 | 6.766008 | 0.675887 | 0.021846 | 0.221791 | 0.034754 | 0.191584 | 1.02083 | 0.032414 | 1.024562 | 1.802848 | 26.546681 |
or to the rated power of the transformer:
pp.runpp(net, trafo_loading="power")
net.res_trafo
p_hv_mw | q_hv_mvar | p_lv_mw | q_lv_mvar | pl_mw | ql_mvar | i_hv_ka | i_lv_ka | vm_hv_pu | va_hv_degree | vm_lv_pu | va_lv_degree | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -6.744162 | -0.454096 | 6.766008 | 0.675887 | 0.021846 | 0.221791 | 0.034754 | 0.191584 | 1.02083 | 0.032414 | 1.024562 | 1.802848 | 27.198731 |
The transformer loading does not have an influence on other power flow results besides the loading_percent parameter.
The generator has reactive power limits of -3/+3 Mvar:
net.gen
name | bus | p_mw | vm_pu | sn_mva | min_q_mvar | max_q_mvar | scaling | slack | in_service | slack_weight | type | power_station_trafo | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | generator | 5 | 6.0 | 1.03 | NaN | -3.0 | 3.0 | 1.0 | False | True | 0.0 | None | NaN |
which are however exceeded in the power flow results, because the enforce_q_lims option defaults to False:
pp.runpp(net)
net.res_gen
p_mw | q_mvar | va_degree | vm_pu | |
---|---|---|---|---|
0 | 6.0 | 3.42191 | 1.870455 | 1.03 |
If the enforce_q_lims parameter is set to True, the reactive power limit is complied with, while the voltage deviates from the voltage set point of the generator:
pp.runpp(net, enforce_q_lims=True)
net.res_gen
p_mw | q_mvar | va_degree | vm_pu | |
---|---|---|---|---|
0 | 6.0 | 3.0 | 1.893699 | 1.02776 |
If you want to know what to do when a power flow does not converge, continue with the diagnostic tutorial.
There are 5 algorithms available for solving the power flow problem:
Each algorithm can be selected by passing corresponding string {"nr", "bfsw", "gs", "fdbx", "fdxb"} to the parameter algorithm.
For example, if you want to use the Backward/Forward sweep algorithm:
pp.runpp(net, algorithm="bfsw")
Or in the case of Gauss-Seidel:
pp.runpp(net, algorithm="gs")
If power flow is run without setting the algorithm parameter, Newton-Raphson will be used as the default algorithm option.
pp.runpp(net)
There is also possibility to select maximum number of iterations that will be used for the specific algorithm.
In the following example max_iteration is set to 10, which is obviously not enough for the Gauss-Seidel to converge:
pp.runpp(net, algorithm="gs", max_iteration=10)
--------------------------------------------------------------------------- LoadflowNotConverged Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_16060\2341567813.py in <module> ----> 1 pp.runpp(net, algorithm="gs", max_iteration=10) ~\Documents\Python Scripts\pandapower\pandapower\run.py in runpp(net, algorithm, calculate_voltage_angles, init, max_iteration, tolerance_mva, trafo_model, trafo_loading, enforce_q_lims, check_connectivity, voltage_depend_loads, consider_line_temperature, run_control, distributed_slack, tdpf, tdpf_delay_s, **kwargs) 239 _check_bus_index_and_print_warning_if_high(net) 240 _check_gen_index_and_print_warning_if_high(net) --> 241 _powerflow(net, **kwargs) 242 243 ~\Documents\Python Scripts\pandapower\pandapower\powerflow.py in _powerflow(net, **kwargs) 84 result = _run_pf_algorithm(ppci, net["_options"], **kwargs) 85 # read the results (=ppci with results) to net ---> 86 _ppci_to_net(result, net) 87 88 ~\Documents\Python Scripts\pandapower\pandapower\powerflow.py in _ppci_to_net(result, net) 186 algorithm = net["_options"]["algorithm"] 187 max_iteration = net["_options"]["max_iteration"] --> 188 raise LoadflowNotConverged("Power Flow {0} did not converge after " 189 "{1} iterations!".format(algorithm, max_iteration)) 190 else: LoadflowNotConverged: Power Flow gs did not converge after 10 iterations!
It is possible to set user options that override the pandapower default parameters for one specific network. For the example network, the voltage angles are not calculated by default:
pp.runpp(net)
net.res_bus
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
0 | 1.020000 | 0.000000 | 6.741115 | 7.146883 |
1 | 1.020830 | 0.032414 | 0.000000 | 0.000000 |
2 | 1.020830 | 0.032414 | 0.000000 | -1.000409 |
3 | 1.024562 | 1.802848 | 0.000000 | 0.000000 |
4 | 1.024562 | 1.802848 | 0.000000 | 0.000000 |
5 | 1.030000 | 1.870455 | -6.000000 | -3.421910 |
6 | 1.023205 | 1.952224 | -0.800000 | 2.900000 |
We now set the option calculate_voltage_angles to True with the set_user_pf_options function:
pp.set_user_pf_options(net, calculate_voltage_angles=True, init="dc")
If we run another power flow without specifing parameters, the voltage angles are calculated:
pp.runpp(net)
net.res_bus
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
0 | 1.020000 | 50.000000 | 6.741115 | 7.146883 |
1 | 1.020830 | 50.032414 | 0.000000 | 0.000000 |
2 | 1.020830 | 50.032414 | 0.000000 | -1.000409 |
3 | 1.024562 | -98.197152 | 0.000000 | 0.000000 |
4 | 1.024562 | -98.197152 | 0.000000 | 0.000000 |
5 | 1.030000 | -98.129545 | -6.000000 | -3.421910 |
6 | 1.023205 | -98.047776 | -0.800000 | 2.900000 |
This change in standard behaviour is only valid for this one network.
When a parameter is specified directly in the runpp function, it overrides the user option:
pp.runpp(net, calculate_voltage_angles=False)
net.res_bus
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
0 | 1.020000 | 0.000000 | 6.741115 | 7.146883 |
1 | 1.020830 | 0.032414 | 0.000000 | 0.000000 |
2 | 1.020830 | 0.032414 | 0.000000 | -1.000409 |
3 | 1.024562 | 1.802848 | 0.000000 | 0.000000 |
4 | 1.024562 | 1.802848 | 0.000000 | 0.000000 |
5 | 1.030000 | 1.870455 | -6.000000 | -3.421910 |
6 | 1.023205 | 1.952224 | -0.800000 | 2.900000 |
The hierarchy for power flow options is therefore: 1. Arguments passed to runpp 2. User Options 3. runpp default parameters Note however that there is a small exception for this rule that you have to deal with: When setting your own user options and then trying to override them with the same value as the runpp default parameter, this will not be recongnized within the powerflow and the user option value is used.