Running a load flow¶
You can use the module pypowsybl.loadflow in order to run load flows on networks.
Start by importing the module:
import pypowsybl.network as pn
import pypowsybl.loadflow as lf
Providers¶
We can get the list of supported load flow implementations (so called providers) and default one:
>>> lf.get_provider_names()
['DynaFlow', 'OpenLoadFlow']
>>> lf.get_default_provider()
'OpenLoadFlow'
By default, load flows are based on the OpenLoadFlow implementation, fully described here. OpenLoadFlow supports AC Newton-Raphson and linear DC calculation methods.
You may also use DynaFlow, provided by the Dynawo project. DynaFlow is a new steady-state simulation tool that aims at calculating the steady-state point by using a simplified time-domain simulation. Please see configuration instructions here.
Parameters¶
The most important part before running a load flow is knowing the parameters and change them if needed. Let’s have a look at the default ones:
>>> lf.Parameters()
Parameters(voltage_init_mode=UNIFORM_VALUES, transformer_voltage_control_on=False, use_reactive_limits=True, phase_shifter_regulation_on=False, twt_split_shunt_admittance=False, shunt_compensator_voltage_control_on=False, read_slack_bus=True, write_slack_bus=True, distributed_slack=True, balance_type=PROPORTIONAL_TO_GENERATION_P_MAX, dc_use_transformer_ratio=True, countries_to_balance=[], component_mode=<ComponentMode.MAIN_CONNECTED: 0>, hvdc_ac_emulation=True, dc_power_factor=1.0, dc=False, provider_parameters={})
For more details on each parameter, please refer to the API reference.
All parameters are also fully described in Powsybl load flow parameters documentation.
Parameters specific to a provider¶
Some parameters are not supported by all load flow providers but specific to only one. These specific parameters could be specified in a less typed way than common parameters using the provider_parameters attribute.
Warning
provider_parameters is a dictionary in which all keys and values must be a string, even in case of a numeric value:
string and integer parameters do not bring much challenge:
provider_parameters={'someStringParam' : 'myStringValue', 'someIntegerParam' : '42'}for float (double) parameters, use the dot as decimal separator. E notation is also supported:
provider_parameters={'someDoubleParam' : '1.23', 'someOtherDoubleParam' : '4.56E-2'}for boolean parameters, use either ‘True’, ‘true’, ‘False’, ‘false’:
provider_parameters={'someBooleanParam' : 'true'}for string list parameters, use the comma as a separator:
provider_parameters={'someStringListParam' : 'value1,value2,value3'}
We can list supported parameters specific to default provider using:
>>> lf.get_provider_parameters_names()
['slackBusSelectionMode', 'slackBusesIds', 'lowImpedanceBranchMode', 'voltageRemoteControl', ...]
And get more detailed information about theses parameters, such as parameter description, type, default value if any, possible values if applicable, using:
>>> lf.get_provider_parameters().query('name == "slackBusSelectionMode" or name == "slackBusesIds"')
category_key description type default possible_values
name
slackBusSelectionMode SlackDistribution Slack bus selection mode STRING MOST_MESHED [FIRST, MOST_MESHED, NAME, LARGEST_GENERATOR]
slackBusesIds SlackDistribution Slack bus IDs STRING_LIST
For instance, OLF supports configuration of slack bus from its ID like this:
>>> p = lf.Parameters(provider_parameters={'slackBusSelectionMode' : 'NAME', 'slackBusesIds' : 'VLHV2_0'})
AC Load Flow¶
In order to run an AC loadflow, simply use the run_ac() method:
>>> network = pn.create_eurostag_tutorial_example1_network()
>>> results = lf.run_ac(network, parameters=lf.Parameters(distributed_slack=False))
The result is composed of a list of component results, one for each connected component of the network included in the computation:
>>> results
[ComponentResult(connected_component_num=0, synchronous_component_num=0, status=CONVERGED, status_text=Converged, iteration_count=3, reference_bus_id='VLHV1_0', slack_bus_results=[SlackBusResult(id='VLHV1_0', active_power_mismatch=-606.5596...)], distributed_active_power=0.0)]
Component results provides general information about the loadflow execution: was it successful? How many iterations did it need? What is the remaining active power imbalance? For example, let’s have a look at the imbalance on the main component of the network:
>>> results[0].slack_bus_results[0].active_power_mismatch
-606.5596...
Then, the main output of the loadflow is actually the updated data in the network itself: all voltages and flows are now updated with the computed values. For example you can have a look at the voltage magnitudes (rounded to 2 digits here):
>>> network.get_buses().v_mag.round(2)
id
VLGEN_0 24.50
VLHV1_0 400.62
VLHV2_0 388.33
VLLOAD_0 146.90
Name: v_mag, dtype: float64
DC Load Flow¶
In order to run a DC loadflow, simply use the run_dc() method.
For that example, we will use a distributed slack, with imbalance distributed on generators, proportional to their maximum power. We also choose to ignore transformer ratios in the DC equations:
>>> parameters = lf.Parameters(dc_use_transformer_ratio=False, distributed_slack=True,
... balance_type=lf.BalanceType.PROPORTIONAL_TO_GENERATION_P_MAX)
Then let’s create our test network and run the DC load flow:
>>> network = pn.create_eurostag_tutorial_example1_network()
>>> results = lf.run_dc(network, parameters)
We can finally retrieve the computed flows on lines:
>>> network.get_lines()[['p1', 'p2']]
p1 p2
id
NHV1_NHV2_1 300.0 -300.0
NHV1_NHV2_2 300.0 -300.0
Reports¶
Reports contain detailed computation information. To see those reports, pass a report_node argument to the run command.
>>> report_node = pp.report.ReportNode()
>>> network = pn.create_eurostag_tutorial_example1_network()
>>> results = lf.run_ac(network, parameters, report_node=report_node)
>>> print(report_node)
+
+ Load flow on network 'sim1'
+ Network CC0 SC0
+ Network info
Network has 4 buses and 4 branches
Network balance: active generation=1214.0 MW, active load=600.0 MW, reactive generation=0.0 MVar, reactive load=200.0 MVar
Angle reference bus: VLHV1_0
Slack bus: VLHV1_0
+ Outer loop DistributedSlack
+ Outer loop iteration 1
Slack bus active power (-606.5596837558763 MW) distributed in 1 distribution iteration(s)
+ Outer loop iteration 2
Slack bus active power (-1.8792855272990572 MW) distributed in 1 distribution iteration(s)
Outer loop VoltageMonitoring
Outer loop ReactiveLimits
Outer loop DistributedSlack
Outer loop VoltageMonitoring
Outer loop ReactiveLimits
AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
Asynchronous API¶
- An asynchronous API based on Python’s asyncio has been added for AC loadflow calculations (DC loadflow support will be added in a future release). The following example demonstrates how to:
Load a network
Create two identical variants from the initial state
Run AC loadflow calculations on both variants concurrently
Wait for both results and display their convergence status
Both loadflow calculations are executed in parallel using Python’s asyncio API.
Caution
The network has to be loaded using a special parameter allow_variant_multi_thread_access to True to be able to work on multiple variants of a same network concurrently using different threads.
>>> import asyncio
>>> async def run_2_lf():
... lf1 = lf.run_ac_async(network, "variant1")
... lf2 = lf.run_ac_async(network, "variant2")
... results = await asyncio.gather(lf1, lf2)
... print(results[0][0].status)
... print(results[1][0].status)
>>> network = pn.create_ieee14(allow_variant_multi_thread_access=True)
>>> network.clone_variant("InitialState", "variant1")
>>> network.clone_variant("InitialState", "variant2")
>>> asyncio.run(run_2_lf())
ComponentStatus.CONVERGED
ComponentStatus.CONVERGED