Running a security analysis

You can use the module pypowsybl.security in order to perform a security analysis on a network. Please check out the examples below.

For detailed documentation of involved classes and methods, please refer to the API reference.

AC security analysis

To perform a security analysis, you need at least a network and a contingency on this network. In the result there are violations detected with the initial loadflow on the network. These violations are collected in pre_contingency_result. The results contain also the violations created by the contingency, they are collected by contingency in post_contingency_results:

>>> network = pp.network.create_eurostag_tutorial_example1_network()
>>> network.update_loads(id='LOAD', p0=800)
>>> security_analysis = pp.security.create_analysis()
>>> security_analysis.add_single_element_contingency('NHV1_NHV2_1', 'First contingency')
>>> result = security_analysis.run_ac(network)
>>> result.pre_contingency_result
PreContingencyResult(, status=CONVERGED, limit_violations=[3])
>>> result.post_contingency_results
{'First contingency': PostContingencyResult(contingency_id='First contingency', status=CONVERGED, limit_violations=[3])}
>>> result.limit_violations
                              subject_name   limit_type limit_name   limit  acceptable_duration  limit_reduction        value side
contingency_id    subject_id
                  NHV1_NHV2_1                   CURRENT  permanent   500.0           2147483647              1.0   623.568946  ONE
                  NHV1_NHV2_2                   CURRENT  permanent   500.0           2147483647              1.0   655.409876  TWO
                  VLHV1                     LOW_VOLTAGE              400.0           2147483647              1.0   398.917401
First contingency NHV1_NHV2_2                   CURRENT        20'  1200.0                   60              1.0  1438.021676  ONE
                  NHV1_NHV2_2                   CURRENT  permanent   500.0           2147483647              1.0  1477.824335  TWO
                  VLHV1                     LOW_VOLTAGE              400.0           2147483647              1.0   392.158685

It is also possible to get a JSON file with the full security analysis results, just by using the export_to_json method, like in the example below :

>>> n = pp.network.create_eurostag_tutorial_example1_network()
>>> sa = pp.security.create_analysis()
>>> sa_result = sa.run_ac(n)
>>> sa_result.export_to_json(str(DATA_DIR.joinpath('json_file_security_analysis.json')))

Adding monitored Elements

This feature is used to get information on different element of the network after the loadflow’s computations. Information can be obtained on buses, branches and three windings transformers.

>>> network = pp.network.create_eurostag_tutorial_example1_with_more_generators_network()
>>> security_analysis = pp.security.create_analysis()
>>> security_analysis.add_single_element_contingency('NHV1_NHV2_1', 'NHV1_NHV2_1')
>>> security_analysis.add_single_element_contingency('GEN', 'GEN')
>>> security_analysis.add_monitored_elements(voltage_level_ids=['VLHV2'])
>>> security_analysis.add_postcontingency_monitored_elements(branch_ids=['NHV1_NHV2_2'], contingency_ids=['NHV1_NHV2_1', 'GEN'])
>>> security_analysis.add_postcontingency_monitored_elements(branch_ids=['NHV1_NHV2_1'], contingency_ids='GEN')
>>> security_analysis.add_precontingency_monitored_elements(branch_ids=['NHV1_NHV2_2'])
>>> results = security_analysis.run_ac(network)
>>> results.bus_results
                                                              v_mag  v_angle
contingency_id operator_strategy_id voltage_level_id bus_id
                                    VLHV2            NHV2   389.95    -3.51
>>> results.branch_results
                                                    p1     q1       i1      p2      q2       i2  flow_transfer
contingency_id operator_strategy_id branch_id
                                    NHV1_NHV2_2 302.44  98.74   456.77 -300.43 -137.19   488.99            NaN
GEN                                 NHV1_NHV2_1 302.44  98.74   456.77 -300.43 -137.19   488.99            NaN
                                    NHV1_NHV2_2 302.44  98.74   456.77 -300.43 -137.19   488.99            NaN
NHV1_NHV2_1                         NHV1_NHV2_2 610.56 334.06 1,008.93 -601.00 -285.38 1,047.83            NaN

It also possible to get flow transfer on monitored branches in case of N-1 branch contingencies:

>>> n = pp.network.create_eurostag_tutorial_example1_network()
>>> sa = pp.security.create_analysis()
>>> sa.add_single_element_contingencies(['NHV1_NHV2_1', 'NHV1_NHV2_2'])
>>> sa.add_monitored_elements(branch_ids=['NHV1_NHV2_1', 'NHV1_NHV2_2'])
>>> sa_result = sa.run_ac(n)
>>> sa_result.branch_results
                                                          p1          q1           i1          p2          q2           i2  flow_transfer
contingency_id operator_strategy_id branch_id
                                    NHV1_NHV2_2  302.444049   98.740275   456.768978 -300.433895 -137.188493   488.992798            NaN
                                    NHV1_NHV2_1  302.444049   98.740275   456.768978 -300.433895 -137.188493   488.992798            NaN
NHV1_NHV2_2                         NHV1_NHV2_1  610.562154  334.056272  1008.928788 -600.996156 -285.379147  1047.825769       1.018761
NHV1_NHV2_1                         NHV1_NHV2_2  610.562154  334.056272  1008.928788 -600.996156 -285.379147  1047.825769       1.018761

Operator strategies and remedial actions

Pypowsybl security analysis support operator strategies and remedial actions definition.

You can define several types of actions by calling the add_XXX_action API. All actions need a unique id to be referenced later at the operator strategy creation stage.

The supported actions in PyPowsybl are listed here:

  • switch, to open or close a switch

  • phase_tap_changer_position, to change the tap position of a phase tap changer

  • ratio_tap_changer_position, to change the tap position of a ratio tap changer

  • load_active_power, to change the active power of a load

  • load_reactive_power, to change the reactive power of a load

  • shunt_compensator_position, to change the section of a shunt compensator

  • generator_active_power, to modify the generator active power

  • terminals_connection, to connect/disconnect one or multiple sides of a network element

The following example defines a switch closing action with id ‘SwitchAction’ on the switch with id ‘S4VL1_BBS_LD6_DISCONNECTOR’.

>>> n = pp.network.create_four_substations_node_breaker_network()
>>> sa = pp.security.create_analysis()
>>> sa.add_switch_action(action_id='SwitchAction', switch_id='S4VL1_BBS_LD6_DISCONNECTOR', open=False)

To enable the application of the action you need to define an operator strategy and add the action to it. An operator strategy is a set of actions to be applied after the simulation of a contingency. It is defined with an unique id, a reference to the id of the contingency, a list action ids and a condition. The following operator strategy define the application of the switch action ‘SwitchAction’ after ‘Breaker contingency’ with the ‘True’ condition (always applied):

>>> n = pp.network.create_four_substations_node_breaker_network()
>>> sa = pp.security.create_analysis()
>>> sa.add_single_element_contingency(element_id='S4VL1_BBS_LD6_DISCONNECTOR', contingency_id='Breaker contingency')
>>> sa.add_switch_action(action_id='SwitchAction', switch_id='S4VL1_BBS_LD6_DISCONNECTOR', open=False)
>>> sa.add_operator_strategy(operator_strategy_id='OperatorStrategy1', contingency_id='Breaker contingency', action_ids=['SwitchAction'], condition_type=pp.security.ConditionType.TRUE_CONDITION)
>>> sa.add_monitored_elements(branch_ids=['LINE_S3S4'])
>>> sa_result = sa.run_ac(n)
>>> df = sa_result.branch_results
>>> #Get the detailed results post operator strategy
>>> df.loc['Breaker contingency', 'OperatorStrategy1', 'LINE_S3S4']['p1'].item()
240.00360040333226

Results for the post remedial action state are available in the branch results indexed with the operator strategy unique id.

Adding limit reductions to the analysis

Limit reductions can be added to a security analysis in order to detect limit violations on lower values of operational limits, using the add_limit_reductions method.

The following example reduces by a factor of 0.8 all limits on the network for the security analysis:

>>> n = pp.network.create_eurostag_tutorial_example1_network()
>>> sa = pp.security.create_analysis()
>>> sa.add_single_element_contingency('NHV1_NHV2_1', 'First contingency')
>>> sa.add_limit_reductions(limit_type='CURRENT', permanent=True, temporary=True, value=0.8)
>>> sa_result = sa.run_ac(n)
>>> sa_result.limit_violations["limit_reduction"].unique()
[0.8]

Limit reductions can also be more selective. They can only be applied to certain network elements using the country, min_voltage and max_voltage parameters, or to certain temporary limits using the min_temporary_duration and max_temporary_duration parameters (if temporary=True). For example, the following method adds a limit reduction of 0.9 on all temporary current limits with minimal acceptable duration of 300s, on all network elements in France with nominal voltage between 90 and 225kV :

sa..add_limit_reductions(limit_type='CURRENT', permanent=False, temporary=True, value=0.9, min_temporary_duration=300, country='FR', min_voltage=90, max_voltage=225)

Warning

If multiple reductions are applicable to the same limits (eg: 0.9 on all limits and 0.8 on all temporary limits), only the last one in addition order will be applied for the security analysis. If a dataframe was used to create the limit reductions, the order of the lines is the addition order.

Adding input data from JSON files

It is possible to add the input data of a security analysis using JSON files. The contingencies can be added this way, using the add_contingencies_from_json_file method.

An example of a valid JSON contingency file is the following :

{
  "type" : "default",
  "version" : "1.0",
  "name" : "list",
  "contingencies" : [ {
    "id" : "contingency",
    "elements" : [ {
      "id" : "NHV1_NHV2_1",
      "type" : "BRANCH"
    }, {
      "id" : "NHV1_NHV2_2",
      "type" : "BRANCH"
    } ]
  }, {
    "id" : "contingency2",
    "elements" : [ {
      "id" : "GEN",
      "type" : "GENERATOR"
    } ]
  } ]
}

From now on, it is possible to add the remedial actions using JSON files too, using the add_actions_from_json_file method. The following example is a valid JSON file input for this method :

{
  "version" : "1.0",
  "actions" : [ {
    "type" : "SWITCH",
    "id" : "id1",
    "switchId" : "S1VL2_LCC1_BREAKER",
    "open" : true
  }, {
    "type" : "SWITCH",
    "id" : "id2",
    "switchId" : "S1VL2_BBS2_COUPLER_DISCONNECTOR",
    "open" : true
  }]
}

Additionally, you can add operator strategies from JSON data, using the add_operator_strategies_from_json_file method. The following example is a valid JSON file input for this method :

{
  "version" : "1.1",
  "operatorStrategies" : [ {
    "id" : "id1",
    "contingencyContextType" : "SPECIFIC",
    "contingencyId" : "contingency",
    "conditionalActions" : [ {
      "id" : "stage1",
      "condition" : {
        "type" : "TRUE_CONDITION"
      },
      "actionIds" : [ "id1", "id2" ]
    } ]
  }]
}