Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for operator strategies in security analysis API #621

Merged
merged 28 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b316029
Support first actions and operator strategy.
obrix Jun 28, 2023
d1ca2a4
Support for operator strategy results.
obrix Jun 28, 2023
baf4119
Remove duplicated test.
obrix Jun 28, 2023
8314622
Add condition support and some tests for Load Action with conditions.
obrix Jul 21, 2023
13412e9
Fix type check.
obrix Aug 2, 2023
4ada531
Handle operator strategy branch result and add a switch action test.
obrix Aug 3, 2023
d94b871
Operator strategy result also for bus.
obrix Aug 3, 2023
bcab580
Try updating doc test.
obrix Aug 3, 2023
e0913ab
Merge branch 'main' into sa_operator_strategies
EtienneLt Aug 29, 2023
35d2a81
Merge branch 'main' into sa_operator_strategies
obrix Nov 14, 2023
c1e7caa
Add missing import.
obrix Nov 14, 2023
839cc9b
Clean.
obrix Nov 14, 2023
d0300fd
Clean.
obrix Nov 14, 2023
e562549
Import missing.
obrix Nov 14, 2023
4fd2da1
Merge branch 'main' into sa_operator_strategies
obrix Jan 12, 2024
69d691b
Fix action description.
obrix Jan 12, 2024
acaee26
Basic user guide.
obrix Jan 15, 2024
3e8a01e
Merge branch 'main' into sa_operator_strategies
obrix Jan 15, 2024
5f588e6
Import conditiontype.
obrix Jan 15, 2024
1219ae3
Remove assert.
obrix Jan 15, 2024
70de010
P result.
obrix Jan 15, 2024
ab118bb
Actually use _operator_strategy_result_repr.
obrix Jan 17, 2024
64296ee
Operator strategy in get_table.
obrix Jan 17, 2024
9998f95
Fix broken limit type conversion.
obrix Jan 17, 2024
7ae2bef
Implement getitem for array indexing.
obrix Jan 18, 2024
a61ede9
Get item for all arrays.
obrix Jan 18, 2024
ef6b107
Add assert on get_table.
obrix Jan 18, 2024
96edeef
Dereference return getitem element.
obrix Jan 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 54 additions & 0 deletions cpp/src/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,31 @@ PYBIND11_MODULE(_pypowsybl, m) {
m.def("add_contingency", &pypowsybl::addContingency, "Add a contingency to a security analysis or sensitivity analysis",
py::arg("analysis_context"), py::arg("contingency_id"), py::arg("elements_ids"));

m.def("add_load_active_power_action", &pypowsybl::addLoadActivePowerAction, "Add a load active power remedial action",
py::arg("analysis_context"), py::arg("action_id"), py::arg("load_id"), py::arg("is_relative"), py::arg("active_power"));

m.def("add_load_reactive_power_action", &pypowsybl::addLoadReactivePowerAction, "Add a load reactive power remedial action",
py::arg("analysis_context"), py::arg("action_id"), py::arg("load_id"), py::arg("is_relative"), py::arg("reactive_power"));

m.def("add_generator_active_power_action", &pypowsybl::addGeneratorActivePowerAction, "Add a generator active power remedial action",
py::arg("analysis_context"), py::arg("action_id"), py::arg("generator_id"), py::arg("is_relative"), py::arg("active_power"));

m.def("add_switch_action", &pypowsybl::addSwitchAction, "Add a switch action",
py::arg("analysis_context"), py::arg("action_id"), py::arg("switch_id"), py::arg("open"));

m.def("add_phase_tap_changer_position_action", &pypowsybl::addPhaseTapChangerPositionAction, "Add a phase tap changer position action",
py::arg("analysis_context"), py::arg("action_id"), py::arg("transformer_id"), py::arg("is_relative"), py::arg("tap_position"));

m.def("add_ratio_tap_changer_position_action", &pypowsybl::addRatioTapChangerPositionAction, "Add a ratio tap changer position action",
py::arg("analysis_context"), py::arg("action_id"), py::arg("transformer_id"), py::arg("is_relative"), py::arg("tap_position"));

m.def("add_shunt_compensator_position_action", &pypowsybl::addShuntCompensatorPositionAction, "Add a shunt compensator position action",
py::arg("analysis_context"), py::arg("action_id"), py::arg("shunt_id"), py::arg("section_count"));

m.def("add_operator_strategy", &pypowsybl::addOperatorStrategy, "Add an operator strategy",
py::arg("analysis_context"), py::arg("operator_strategy_id"), py::arg("contingency_id"), py::arg("action_ids"),
py::arg("condition_type"), py::arg("subject_ids"), py::arg("violation_types"));

py::enum_<pypowsybl::LimitType>(m, "LimitType")
.value("CURRENT", pypowsybl::LimitType::CURRENT)
.value("LOW_VOLTAGE", pypowsybl::LimitType::LOW_VOLTAGE)
Expand All @@ -519,6 +544,22 @@ PYBIND11_MODULE(_pypowsybl, m) {
.value("ONE", pypowsybl::Side::ONE)
.value("TWO", pypowsybl::Side::TWO);

py::enum_<violation_type>(m, "ViolationType")
.value("ACTIVE_POWER", violation_type::ACTIVE_POWER)
.value("APPARENT_POWER", violation_type::APPARENT_POWER)
.value("CURRENT", violation_type::CURRENT)
.value("LOW_VOLTAGE", violation_type::LOW_VOLTAGE)
.value("HIGH_VOLTAGE", violation_type::HIGH_VOLTAGE)
.value("LOW_SHORT_CIRCUIT_CURRENT", violation_type::LOW_SHORT_CIRCUIT_CURRENT)
.value("HIGH_SHORT_CIRCUIT_CURRENT", violation_type::HIGH_SHORT_CIRCUIT_CURRENT)
.value("OTHER", violation_type::OTHER);

py::enum_<condition_type>(m, "ConditionType")
.value("TRUE_CONDITION", condition_type::TRUE_CONDITION)
.value("ALL_VIOLATION_CONDITION", condition_type::ALL_VIOLATION_CONDITION)
.value("ANY_VIOLATION_CONDITION", condition_type::ANY_VIOLATION_CONDITION)
.value("AT_LEAST_ONE_VIOLATION_CONDITION", condition_type::AT_LEAST_ONE_VIOLATION_CONDITION);

py::class_<network_metadata, std::shared_ptr<network_metadata>>(m, "NetworkMetadata")
.def_property_readonly("id", [](const network_metadata& att) {
return att.id;
Expand Down Expand Up @@ -579,6 +620,18 @@ PYBIND11_MODULE(_pypowsybl, m) {
});
bindArray<pypowsybl::PostContingencyResultArray>(m, "PostContingencyResultArray");

py::class_<operator_strategy_result>(m, "OperatorStrategyResult")
.def_property_readonly("operator_strategy_id", [](const operator_strategy_result& r) {
return r.operator_strategy_id;
})
.def_property_readonly("status", [](const operator_strategy_result& r) {
return static_cast<pypowsybl::PostContingencyComputationStatus>(r.status);
})
.def_property_readonly("limit_violations", [](const operator_strategy_result& r) {
return pypowsybl::LimitViolationArray((array *) & r.limit_violations);
});
bindArray<pypowsybl::OperatorStrategyResultArray>(m, "OperatorStrategyResultArray");

py::class_<pre_contingency_result>(m, "PreContingencyResult")
.def_property_readonly("status", [](const pre_contingency_result& r) {
return static_cast<pypowsybl::LoadFlowComponentStatus>(r.status);
Expand Down Expand Up @@ -718,6 +771,7 @@ PYBIND11_MODULE(_pypowsybl, m) {

m.def("get_post_contingency_results", &pypowsybl::getPostContingencyResults, "get post contingency results of a security analysis", py::arg("result"));
m.def("get_pre_contingency_result", &pypowsybl::getPreContingencyResult, "get pre contingency result of a security analysis", py::arg("result"));
m.def("get_operator_strategy_results", &pypowsybl::getOperatorStrategyResults, "get operator strategy results of a security analysis", py::arg("result"));
m.def("get_node_breaker_view_nodes", &pypowsybl::getNodeBreakerViewNodes, "get all nodes for a voltage level", py::arg("network"), py::arg("voltage_level"));
m.def("get_node_breaker_view_internal_connections", &pypowsybl::getNodeBreakerViewInternalConnections,
"get all internal connections for a voltage level", py::arg("network"), py::arg("voltage_level"));
Expand Down
24 changes: 24 additions & 0 deletions cpp/src/pypowsybl-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ typedef struct pre_contingency_result_struct {
array limit_violations;
} pre_contingency_result;

typedef struct operator_strategy_result_struct {
char* operator_strategy_id;
int status;
array limit_violations;
} operator_strategy_result;

typedef enum {
BUS = 0,
LINE,
Expand Down Expand Up @@ -166,6 +172,24 @@ typedef enum {
TWTS3W,
} validation_type;

typedef enum {
ACTIVE_POWER = 0,
APPARENT_POWER,
CURRENT,
LOW_VOLTAGE,
HIGH_VOLTAGE,
LOW_SHORT_CIRCUIT_CURRENT,
HIGH_SHORT_CIRCUIT_CURRENT,
OTHER,
} violation_type;

typedef enum {
TRUE_CONDITION = 0,
ALL_VIOLATION_CONDITION,
ANY_VIOLATION_CONDITION,
AT_LEAST_ONE_VIOLATION_CONDITION,
} condition_type;

typedef enum {
EQUIPMENT = 0,
STEADY_STATE_HYPOTHESIS,
Expand Down
53 changes: 53 additions & 0 deletions cpp/src/pypowsybl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ Array<post_contingency_result>::~Array() {
callJava<>(::freeContingencyResultArrayPointer, delegate_);
}

template<>
Array<operator_strategy_result>::~Array() {
callJava<>(::freeOperatorStrategyResultArrayPointer, delegate_);
}

template<>
Array<limit_violation>::~Array() {
// already freed by contingency_result
Expand Down Expand Up @@ -843,6 +848,50 @@ JavaHandle createSensitivityAnalysis() {
return callJava<JavaHandle>(::createSensitivityAnalysis);
}

void addLoadActivePowerAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& loadId, bool relativeValue, double activePower) {
callJava(::addLoadActivePowerAction, analysisContext, (char*) actionId.data(), (char*) loadId.data(), relativeValue, activePower);
}

void addLoadReactivePowerAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& loadId, bool relativeValue, double reactivePower) {
callJava(::addLoadReactivePowerAction, analysisContext, (char*) actionId.data(), (char*) loadId.data(), relativeValue, reactivePower);
}

void addGeneratorActivePowerAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& generatorId, bool relativeValue, double activePower) {
callJava(::addGeneratorActivePowerAction, analysisContext, (char*) actionId.data(), (char*) generatorId.data(), relativeValue, activePower);
}

void addSwitchAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& switchId, bool open) {
callJava(::addSwitchAction, analysisContext, (char*) actionId.data(), (char*) switchId.data(), open);
}

void addPhaseTapChangerPositionAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& transformerId,
bool isRelative, int tapPosition) {
callJava(::addPhaseTapChangerPositionAction, analysisContext, (char*) actionId.data(), (char*) transformerId.data(), isRelative, tapPosition);
}

void addRatioTapChangerPositionAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& transformerId,
bool isRelative, int tapPosition) {
callJava(::addRatioTapChangerPositionAction, analysisContext, (char*) actionId.data(), (char*) transformerId.data(), isRelative, tapPosition);
}

void addShuntCompensatorPositionAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& shuntId,
int sectionCount) {
callJava(::addShuntCompensatorPositionAction, analysisContext, (char*) actionId.data(), (char*) shuntId.data(), sectionCount);
}

void addOperatorStrategy(const JavaHandle& analysisContext, std::string operatorStrategyId, std::string contingencyId, const std::vector<std::string>& actionsIds,
condition_type conditionType, const std::vector<std::string>& subjectIds, const std::vector<violation_type>& violationTypesFilters) {
ToCharPtrPtr actionsPtr(actionsIds);
ToCharPtrPtr subjectIdsPtr(subjectIds);
std::vector<int> violationTypes;
for(int i = 0; i < violationTypesFilters.size(); ++i) {
violationTypes.push_back(violationTypesFilters[i]);
}
ToIntPtr violationTypesPtr(violationTypes);
callJava(::addOperatorStrategy, analysisContext, (char*) operatorStrategyId.data(), (char*) contingencyId.data(), actionsPtr.get(), actionsIds.size(),
conditionType, subjectIdsPtr.get(), subjectIds.size(), violationTypesPtr.get(), violationTypesFilters.size());
}

::zone* createZone(const std::string& id, const std::vector<std::string>& injectionsIds, const std::vector<double>& injectionsShiftKeys) {
auto z = new ::zone;
z->id = copyStringToCharPtr(id);
Expand Down Expand Up @@ -975,6 +1024,10 @@ PostContingencyResultArray* getPostContingencyResults(const JavaHandle& security
return new PostContingencyResultArray(callJava<array*>(::getPostContingencyResults, securityAnalysisResult));
}

OperatorStrategyResultArray* getOperatorStrategyResults(const JavaHandle& securityAnalysisResult) {
return new OperatorStrategyResultArray(callJava<array*>(::getOperatorStrategyResults, securityAnalysisResult));
}

pre_contingency_result* getPreContingencyResult(const JavaHandle& securityAnalysisResult) {
return callJava<pre_contingency_result*>(::getPreContingencyResult, securityAnalysisResult);
}
Expand Down
20 changes: 20 additions & 0 deletions cpp/src/pypowsybl.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class Array {

typedef Array<loadflow_component_result> LoadFlowComponentResultArray;
typedef Array<post_contingency_result> PostContingencyResultArray;
typedef Array<operator_strategy_result> OperatorStrategyResultArray;
typedef Array<limit_violation> LimitViolationArray;
typedef Array<series> SeriesArray;

Expand Down Expand Up @@ -414,6 +415,23 @@ JavaHandle runSecurityAnalysis(const JavaHandle& securityAnalysisContext, const

JavaHandle createSensitivityAnalysis();

void addLoadActivePowerAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& loadId, bool relativeValue, double activePower);

void addLoadReactivePowerAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& loadId, bool relativeValue, double reactivePower);

void addGeneratorActivePowerAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& generatorId, bool relativeValue, double activePower);

void addSwitchAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& switchId, bool open);

void addPhaseTapChangerPositionAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& transformerId, bool isRelative, int tapPosition);

void addRatioTapChangerPositionAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& transformerId, bool isRelative, int tapPosition);

void addShuntCompensatorPositionAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& shuntId, int sectionCount);

void addOperatorStrategy(const JavaHandle& analysisContext, std::string operatorStrategyId, std::string contingencyId, const std::vector<std::string>& actionsIds,
condition_type conditionType, const std::vector<std::string>& subjectIds, const std::vector<violation_type>& violationTypesFilters);

void setZones(const JavaHandle& sensitivityAnalysisContext, const std::vector<::zone*>& zones);

void addFactorMatrix(const JavaHandle& sensitivityAnalysisContext, std::string matrixId, const std::vector<std::string>& branchesIds,
Expand Down Expand Up @@ -456,6 +474,8 @@ SeriesArray* getLimitViolations(const JavaHandle& securityAnalysisResult);

PostContingencyResultArray* getPostContingencyResults(const JavaHandle& securityAnalysisResult);

OperatorStrategyResultArray* getOperatorStrategyResults(const JavaHandle& securityAnalysisResult);

pre_contingency_result* getPreContingencyResult(const JavaHandle& securityAnalysisResult);

SeriesArray* getBranchResults(const JavaHandle& securityAnalysisResult);
Expand Down
72 changes: 55 additions & 17 deletions docs/user_guide/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Running a security analysis
import pandas as pd
pd.options.display.max_columns = None
pd.options.display.expand_frame_repr = False
from pypowsybl._pypowsybl import ConditionType

You can use the module :mod:`pypowsybl.security` in order to perform a security analysis on a network.
Please check out the examples below.
Expand Down Expand Up @@ -70,18 +71,18 @@ Information can be obtained on buses, branches and three windings transformers.
>>> 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 voltage_level_id bus_id
VLHV2 NHV2 389.95 -3.51
NGEN_NHV1 VLHV2 NHV2 569.04 -1.71
NHV1_NHV2_1 VLHV2 NHV2 366.58 -7.50
v_mag v_angle
contingency_id operator_strategy_id voltage_level_id bus_id
VLHV2 NHV2 389.95 -3.51
NGEN_NHV1 VLHV2 NHV2 569.04 -1.71
NHV1_NHV2_1 VLHV2 NHV2 366.58 -7.50
>>> results.branch_results
p1 q1 i1 p2 q2 i2 flow_transfer
contingency_id branch_id
NHV1_NHV2_2 302.44 98.74 456.77 -300.43 -137.19 488.99 NaN
NGEN_NHV1 NHV1_NHV2_1 301.06 0.00 302.80 -300.19 -116.60 326.75 NaN
NHV1_NHV2_2 301.06 0.00 302.80 -300.19 -116.60 326.75 NaN
NHV1_NHV2_1 NHV1_NHV2_2 610.56 334.06 1,008.93 -601.00 -285.38 1,047.83 NaN
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
NGEN_NHV1 NHV1_NHV2_1 301.06 0.00 302.80 -300.19 -116.60 326.75 NaN
NHV1_NHV2_2 301.06 0.00 302.80 -300.19 -116.60 326.75 NaN
NHV1_NHV2_1 NHV1_NHV2_2 610.56 334.06 1,008.93 -601.00 -285.38 1,047.83 NaN



Expand All @@ -98,13 +99,50 @@ It also possible to get flow transfer on monitored branches in case of N-1 branc
>>> 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 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
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

.. testcleanup:: security.monitored_elements

pd.options.display.float_format = None

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 following example define a switch closing action with id 'SwitchAction' on the switch with id 'S4VL1_BBS_LD6_DISCONNECTOR'.

.. doctest::
:options: +NORMALIZE_WHITESPACE

>>> 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) :

.. doctest::
:options: +NORMALIZE_WHITESPACE

>>> 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=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']
240.00360040333226

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