From d7d94731a8a7f3b752facdeaa4d621d1a300de40 Mon Sep 17 00:00:00 2001 From: HugoKulesza <94374655+HugoKulesza@users.noreply.github.com> Date: Tue, 17 Jan 2023 13:53:49 +0100 Subject: [PATCH] Allowing to modify load flow validation parameters (#553) Signed-off-by: HugoKulesza --- cpp/src/bindings.cpp | 43 ++++-- cpp/src/pypowsybl-api.h | 26 +++- cpp/src/pypowsybl.cpp | 92 ++++++++++--- cpp/src/pypowsybl.h | 47 +++++-- docs/reference/loadflow.rst | 10 ++ .../python/commons/PyPowsyblApiHeader.java | 75 ++++++++++- .../LoadFlowValidationCFunctions.java | 88 +++++++++++- .../validation/LoadFlowValidationTest.java | 4 +- pypowsybl/_pypowsybl.pyi | 22 ++- pypowsybl/loadflow.py | 126 +++++++++++++++++- pypowsybl/security.py | 4 +- pypowsybl/sensitivity.py | 4 +- tests/test_loadflow.py | 73 ++++++++-- tests/test_network_extensions.py | 2 +- 14 files changed, 536 insertions(+), 80 deletions(-) diff --git a/cpp/src/bindings.cpp b/cpp/src/bindings.cpp index e28c2e559..d59a9e954 100644 --- a/cpp/src/bindings.cpp +++ b/cpp/src/bindings.cpp @@ -252,26 +252,26 @@ PYBIND11_MODULE(_pypowsybl, m) { .value("SOLVER_FAILED", pypowsybl::PostContingencyComputationStatus::SOLVER_FAILED, "The loadflow numerical solver has failed.") .value("NO_IMPACT", pypowsybl::PostContingencyComputationStatus::NO_IMPACT, "The contingency has no impact."); - py::class_(m, "LoadFlowComponentResult", "Loadflow result for one connected component of the network.") - .def_property_readonly("connected_component_num", [](const load_flow_component_result& r) { + py::class_(m, "LoadFlowComponentResult", "Loadflow result for one connected component of the network.") + .def_property_readonly("connected_component_num", [](const loadflow_component_result& r) { return r.connected_component_num; }) - .def_property_readonly("synchronous_component_num", [](const load_flow_component_result& r) { + .def_property_readonly("synchronous_component_num", [](const loadflow_component_result& r) { return r.synchronous_component_num; }) - .def_property_readonly("status", [](const load_flow_component_result& r) { + .def_property_readonly("status", [](const loadflow_component_result& r) { return static_cast(r.status); }) - .def_property_readonly("iteration_count", [](const load_flow_component_result& r) { + .def_property_readonly("iteration_count", [](const loadflow_component_result& r) { return r.iteration_count; }) - .def_property_readonly("slack_bus_id", [](const load_flow_component_result& r) { + .def_property_readonly("slack_bus_id", [](const loadflow_component_result& r) { return r.slack_bus_id; }) - .def_property_readonly("slack_bus_active_power_mismatch", [](const load_flow_component_result& r) { + .def_property_readonly("slack_bus_active_power_mismatch", [](const loadflow_component_result& r) { return r.slack_bus_active_power_mismatch; }) - .def_property_readonly("distributed_active_power", [](const load_flow_component_result& r) { + .def_property_readonly("distributed_active_power", [](const loadflow_component_result& r) { return r.distributed_active_power; }); @@ -295,7 +295,7 @@ PYBIND11_MODULE(_pypowsybl, m) { py::enum_(m, "ConnectedComponentMode", "Define which connected components to run on.") .value("ALL", pypowsybl::ConnectedComponentMode::ALL, "Run on all connected components") .value("MAIN", pypowsybl::ConnectedComponentMode::MAIN, "Run only on the main connected component"); - + py::class_>(m, "ArrayStruct") .def(py::init()); @@ -319,9 +319,23 @@ PYBIND11_MODULE(_pypowsybl, m) { .def_readwrite("provider_parameters_keys", &pypowsybl::LoadFlowParameters::provider_parameters_keys) .def_readwrite("provider_parameters_values", &pypowsybl::LoadFlowParameters::provider_parameters_values); + py::class_(m, "LoadFlowValidationParameters") + .def(py::init(&pypowsybl::createValidationConfig)) + .def_readwrite("threshold", &pypowsybl::LoadFlowValidationParameters::threshold) + .def_readwrite("verbose", &pypowsybl::LoadFlowValidationParameters::verbose) + .def_readwrite("loadflow_name", &pypowsybl::LoadFlowValidationParameters::loadflow_name) + .def_readwrite("epsilon_x", &pypowsybl::LoadFlowValidationParameters::epsilon_x) + .def_readwrite("apply_reactance_correction", &pypowsybl::LoadFlowValidationParameters::apply_reactance_correction) + .def_readwrite("loadflow_parameters", &pypowsybl::LoadFlowValidationParameters::loadflow_parameters) + .def_readwrite("ok_missing_values", &pypowsybl::LoadFlowValidationParameters::ok_missing_values) + .def_readwrite("no_requirement_if_reactive_bound_inversion", &pypowsybl::LoadFlowValidationParameters::no_requirement_if_reactive_bound_inversion) + .def_readwrite("compare_results", &pypowsybl::LoadFlowValidationParameters::compare_results) + .def_readwrite("check_main_component_only", &pypowsybl::LoadFlowValidationParameters::check_main_component_only) + .def_readwrite("no_requirement_if_setpoint_outside_power_bounds", &pypowsybl::LoadFlowValidationParameters::no_requirement_if_setpoint_outside_power_bounds); + py::class_(m, "SecurityAnalysisParameters") .def(py::init(&pypowsybl::createSecurityAnalysisParameters)) - .def_readwrite("load_flow_parameters", &pypowsybl::SecurityAnalysisParameters::load_flow_parameters) + .def_readwrite("loadflow_parameters", &pypowsybl::SecurityAnalysisParameters::loadflow_parameters) .def_readwrite("flow_proportional_threshold", &pypowsybl::SecurityAnalysisParameters::flow_proportional_threshold) .def_readwrite("low_voltage_proportional_threshold", &pypowsybl::SecurityAnalysisParameters::low_voltage_proportional_threshold) .def_readwrite("low_voltage_absolute_threshold", &pypowsybl::SecurityAnalysisParameters::low_voltage_absolute_threshold) @@ -332,14 +346,15 @@ PYBIND11_MODULE(_pypowsybl, m) { py::class_(m, "SensitivityAnalysisParameters") .def(py::init(&pypowsybl::createSensitivityAnalysisParameters)) - .def_readwrite("load_flow_parameters", &pypowsybl::SensitivityAnalysisParameters::load_flow_parameters) + .def_readwrite("loadflow_parameters", &pypowsybl::SensitivityAnalysisParameters::loadflow_parameters) .def_readwrite("provider_parameters_keys", &pypowsybl::SensitivityAnalysisParameters::provider_parameters_keys) .def_readwrite("provider_parameters_values", &pypowsybl::SensitivityAnalysisParameters::provider_parameters_values); - m.def("run_load_flow", &pypowsybl::runLoadFlow, "Run a load flow", py::call_guard(), + m.def("run_loadflow", &pypowsybl::runLoadFlow, "Run a load flow", py::call_guard(), py::arg("network"), py::arg("dc"), py::arg("parameters"), py::arg("provider"), py::arg("reporter")); - m.def("run_load_flow_validation", &pypowsybl::runLoadFlowValidation, "Run a load flow validation", py::arg("network"), py::arg("validation_type")); + m.def("run_loadflow_validation", &pypowsybl::runLoadFlowValidation, "Run a load flow validation", py::arg("network"), + py::arg("validation_type"), py::arg("validation_parameters")); py::class_(m, "LayoutParameters") .def(py::init(&pypowsybl::createLayoutParameters)) @@ -644,7 +659,7 @@ PYBIND11_MODULE(_pypowsybl, m) { py::arg("flow_decomposition_context"), py::arg("default_xnec_provider")); m.def("run_flow_decomposition", &pypowsybl::runFlowDecomposition, "Run flow decomposition on a network", - py::call_guard(), py::arg("flow_decomposition_context"), py::arg("network"), py::arg("flow_decomposition_parameters"), py::arg("load_flow_parameters")); + py::call_guard(), py::arg("flow_decomposition_context"), py::arg("network"), py::arg("flow_decomposition_parameters"), py::arg("loadflow_parameters")); py::class_(m, "FlowDecompositionParameters") .def(py::init(&pypowsybl::createFlowDecompositionParameters)) diff --git a/cpp/src/pypowsybl-api.h b/cpp/src/pypowsybl-api.h index 30029b705..322719bca 100644 --- a/cpp/src/pypowsybl-api.h +++ b/cpp/src/pypowsybl-api.h @@ -25,7 +25,7 @@ typedef struct network_metadata_struct { int forecast_distance; } network_metadata; -typedef struct load_flow_component_result_struct { +typedef struct loadflow_component_result_struct { int connected_component_num; int synchronous_component_num; int status; @@ -33,9 +33,9 @@ typedef struct load_flow_component_result_struct { char* slack_bus_id; double slack_bus_active_power_mismatch; double distributed_active_power; -} load_flow_component_result; +} loadflow_component_result; -typedef struct load_flow_parameters_struct { +typedef struct loadflow_parameters_struct { int voltage_init_mode; unsigned char transformer_voltage_control_on; unsigned char no_generator_reactive_limits; @@ -54,10 +54,24 @@ typedef struct load_flow_parameters_struct { int provider_parameters_keys_count; char** provider_parameters_values; int provider_parameters_values_count; -} load_flow_parameters; +} loadflow_parameters; + +typedef struct loadflow_validation_parameters_struct { + double threshold; + double epsilon_x; + unsigned char verbose; + char* loadflow_name; + struct loadflow_parameters_struct loadflow_parameters; + unsigned char apply_reactance_correction; + unsigned char ok_missing_values; + unsigned char no_requirement_if_reactive_bound_inversion; + unsigned char compare_results; + unsigned char check_main_component_only; + unsigned char no_requirement_if_setpoint_outside_power_bounds; +} loadflow_validation_parameters; typedef struct security_analysis_parameters_struct { - struct load_flow_parameters_struct load_flow_parameters; + struct loadflow_parameters_struct loadflow_parameters; double flow_proportional_threshold; double low_voltage_proportional_threshold; double low_voltage_absolute_threshold; @@ -70,7 +84,7 @@ typedef struct security_analysis_parameters_struct { } security_analysis_parameters; typedef struct sensitivity_analysis_parameters_struct { - struct load_flow_parameters_struct load_flow_parameters; + struct loadflow_parameters_struct loadflow_parameters; char** provider_parameters_keys; int provider_parameters_keys_count; char** provider_parameters_values; diff --git a/cpp/src/pypowsybl.cpp b/cpp/src/pypowsybl.cpp index b14a9a29d..fa6d3a1b6 100644 --- a/cpp/src/pypowsybl.cpp +++ b/cpp/src/pypowsybl.cpp @@ -117,7 +117,7 @@ JavaHandle::JavaHandle(void* handle): } template<> -Array::~Array() { +Array::~Array() { callJava<>(::freeLoadFlowComponentResultPointer, delegate_); } @@ -288,13 +288,13 @@ void copyCharPtrPtrToVector(char** src, int count, std::vector& des std::copy(src, src + count, std::back_inserter(dest)); } -void deleteLoadFlowParameters(load_flow_parameters* ptr) { +void deleteLoadFlowParameters(loadflow_parameters* ptr) { pypowsybl::deleteCharPtrPtr(ptr->countries_to_balance, ptr->countries_to_balance_count); pypowsybl::deleteCharPtrPtr(ptr->provider_parameters_keys, ptr->provider_parameters_keys_count); pypowsybl::deleteCharPtrPtr(ptr->provider_parameters_values, ptr->provider_parameters_values_count); } -LoadFlowParameters::LoadFlowParameters(load_flow_parameters* src) { +LoadFlowParameters::LoadFlowParameters(loadflow_parameters* src) { voltage_init_mode = static_cast(src->voltage_init_mode); transformer_voltage_control_on = (bool) src->transformer_voltage_control_on; no_generator_reactive_limits = (bool) src->no_generator_reactive_limits; @@ -312,7 +312,7 @@ LoadFlowParameters::LoadFlowParameters(load_flow_parameters* src) { copyCharPtrPtrToVector(src->provider_parameters_values, src->provider_parameters_values_count, provider_parameters_values); } -void LoadFlowParameters::load_to_c_struct(load_flow_parameters& res) const { +void LoadFlowParameters::load_to_c_struct(loadflow_parameters& res) const { res.voltage_init_mode = voltage_init_mode; res.transformer_voltage_control_on = (unsigned char) transformer_voltage_control_on; res.no_generator_reactive_limits = (unsigned char) no_generator_reactive_limits; @@ -333,24 +333,66 @@ void LoadFlowParameters::load_to_c_struct(load_flow_parameters& res) const { res.provider_parameters_values_count = provider_parameters_values.size(); } -std::shared_ptr LoadFlowParameters::to_c_struct() const { - load_flow_parameters* res = new load_flow_parameters(); +std::shared_ptr LoadFlowParameters::to_c_struct() const { + loadflow_parameters* res = new loadflow_parameters(); load_to_c_struct(*res); //Memory has been allocated here on C side, we need to clean it up on C side (not java side) - return std::shared_ptr(res, [](load_flow_parameters* ptr){ + return std::shared_ptr(res, [](loadflow_parameters* ptr){ deleteLoadFlowParameters(ptr); delete ptr; }); } +void deleteLoadFlowValidationParameters(loadflow_validation_parameters* ptr) { + deleteLoadFlowParameters(&ptr->loadflow_parameters); +} + +LoadFlowValidationParameters::LoadFlowValidationParameters(loadflow_validation_parameters* src): + loadflow_parameters(&src->loadflow_parameters) +{ + threshold = (double) src->threshold; + verbose = (bool) src->verbose; + loadflow_name = toString(src->loadflow_name); + epsilon_x = (double) src->epsilon_x; + apply_reactance_correction = (bool) src->apply_reactance_correction; + ok_missing_values = (bool) src->ok_missing_values; + no_requirement_if_reactive_bound_inversion = (bool) src->no_requirement_if_reactive_bound_inversion; + compare_results = (bool) src->compare_results; + check_main_component_only = (bool) src->check_main_component_only; + no_requirement_if_setpoint_outside_power_bounds = (bool) src->no_requirement_if_setpoint_outside_power_bounds; +} + +void LoadFlowValidationParameters::load_to_c_struct(loadflow_validation_parameters& res) const { + res.threshold = threshold; + res.verbose = (unsigned char) verbose; + res.loadflow_name = copyStringToCharPtr(loadflow_name); + res.epsilon_x = epsilon_x; + res.apply_reactance_correction = (unsigned char) apply_reactance_correction; + res.ok_missing_values = (unsigned char) ok_missing_values; + res.no_requirement_if_reactive_bound_inversion = (unsigned char) no_requirement_if_reactive_bound_inversion; + res.compare_results = (unsigned char) compare_results; + res.check_main_component_only = (unsigned char) check_main_component_only; + res.no_requirement_if_setpoint_outside_power_bounds = (unsigned char) no_requirement_if_setpoint_outside_power_bounds; +} + +std::shared_ptr LoadFlowValidationParameters::to_c_struct() const { + loadflow_validation_parameters* res = new loadflow_validation_parameters(); + load_to_c_struct(*res); + //Memory has been allocated here on C side, we need to clean it up on C side (not java side) + return std::shared_ptr(res, [](loadflow_validation_parameters* ptr){ + deleteLoadFlowValidationParameters(ptr); + delete ptr; + }); +} + void deleteSecurityAnalysisParameters(security_analysis_parameters* ptr) { - deleteLoadFlowParameters(&ptr->load_flow_parameters); + deleteLoadFlowParameters(&ptr->loadflow_parameters); pypowsybl::deleteCharPtrPtr(ptr->provider_parameters_keys, ptr->provider_parameters_keys_count); pypowsybl::deleteCharPtrPtr(ptr->provider_parameters_values, ptr->provider_parameters_values_count); } SecurityAnalysisParameters::SecurityAnalysisParameters(security_analysis_parameters* src): - load_flow_parameters(&src->load_flow_parameters) + loadflow_parameters(&src->loadflow_parameters) { flow_proportional_threshold = (double) src->flow_proportional_threshold; low_voltage_proportional_threshold = (double) src->low_voltage_proportional_threshold; @@ -363,7 +405,7 @@ SecurityAnalysisParameters::SecurityAnalysisParameters(security_analysis_paramet std::shared_ptr SecurityAnalysisParameters::to_c_struct() const { security_analysis_parameters* res = new security_analysis_parameters(); - load_flow_parameters.load_to_c_struct(res->load_flow_parameters); + loadflow_parameters.load_to_c_struct(res->loadflow_parameters); res->flow_proportional_threshold = (double) flow_proportional_threshold; res->low_voltage_proportional_threshold = (double) low_voltage_proportional_threshold; res->low_voltage_absolute_threshold = (double) low_voltage_absolute_threshold; @@ -381,13 +423,13 @@ std::shared_ptr SecurityAnalysisParameters::to_c_s } void deleteSensitivityAnalysisParameters(sensitivity_analysis_parameters* ptr) { - deleteLoadFlowParameters(&ptr->load_flow_parameters); + deleteLoadFlowParameters(&ptr->loadflow_parameters); pypowsybl::deleteCharPtrPtr(ptr->provider_parameters_keys, ptr->provider_parameters_keys_count); pypowsybl::deleteCharPtrPtr(ptr->provider_parameters_values, ptr->provider_parameters_values_count); } SensitivityAnalysisParameters::SensitivityAnalysisParameters(sensitivity_analysis_parameters* src): - load_flow_parameters(&src->load_flow_parameters) + loadflow_parameters(&src->loadflow_parameters) { copyCharPtrPtrToVector(src->provider_parameters_keys, src->provider_parameters_keys_count, provider_parameters_keys); copyCharPtrPtrToVector(src->provider_parameters_values, src->provider_parameters_values_count, provider_parameters_values); @@ -395,7 +437,7 @@ SensitivityAnalysisParameters::SensitivityAnalysisParameters(sensitivity_analysi std::shared_ptr SensitivityAnalysisParameters::to_c_struct() const { sensitivity_analysis_parameters* res = new sensitivity_analysis_parameters(); - load_flow_parameters.load_to_c_struct(res->load_flow_parameters); + loadflow_parameters.load_to_c_struct(res->loadflow_parameters); res->provider_parameters_keys = pypowsybl::copyVectorStringToCharPtrPtr(provider_parameters_keys); res->provider_parameters_keys_count = provider_parameters_keys.size(); res->provider_parameters_values = pypowsybl::copyVectorStringToCharPtrPtr(provider_parameters_values); @@ -623,14 +665,23 @@ std::vector getNetworkElementsIds(const JavaHandle& network, elemen } LoadFlowParameters* createLoadFlowParameters() { - load_flow_parameters* parameters_ptr = callJava(::createLoadFlowParameters); - auto parameters = std::shared_ptr(parameters_ptr, [](load_flow_parameters* ptr){ + loadflow_parameters* parameters_ptr = callJava(::createLoadFlowParameters); + auto parameters = std::shared_ptr(parameters_ptr, [](loadflow_parameters* ptr){ //Memory has been allocated on java side, we need to clean it up on java side callJava(::freeLoadFlowParameters, ptr); }); return new LoadFlowParameters(parameters.get()); } +LoadFlowValidationParameters* createValidationConfig() { + loadflow_validation_parameters* parameters_ptr = callJava(::createValidationConfig); + auto parameters = std::shared_ptr(parameters_ptr, [](loadflow_validation_parameters* ptr){ + //Memory has been allocated on java side, we need to clean it up on java side + callJava(::freeValidationConfig, ptr); + }); + return new LoadFlowValidationParameters(parameters.get()); +} + SecurityAnalysisParameters* createSecurityAnalysisParameters() { security_analysis_parameters* parameters_ptr = callJava(::createSecurityAnalysisParameters); @@ -655,8 +706,9 @@ LoadFlowComponentResultArray* runLoadFlow(const JavaHandle& network, bool dc, co callJava(::runLoadFlow, network, dc, c_parameters.get(), (char *) provider.data(), (reporter == nullptr) ? nullptr : *reporter)); } -SeriesArray* runLoadFlowValidation(const JavaHandle& network, validation_type validationType) { - return new SeriesArray(callJava(::runLoadFlowValidation, network, validationType)); +SeriesArray* runLoadFlowValidation(const JavaHandle& network, validation_type validationType, const LoadFlowValidationParameters& loadflow_validation_parameters) { + auto c_validation_parameters = loadflow_validation_parameters.to_c_struct(); + return new SeriesArray(callJava(::runLoadFlowValidation, network, validationType, c_validation_parameters.get())); } void writeSingleLineDiagramSvg(const JavaHandle& network, const std::string& containerId, const std::string& svgFile, const std::string& metadataFile, const LayoutParameters& parameters) { @@ -1097,10 +1149,10 @@ void addAdditionalXnecProviderForFlowDecomposition(const JavaHandle& flowDecompo callJava(::addAdditionalXnecProviderForFlowDecomposition, flowDecompositionContext, defaultXnecProvider); } -SeriesArray* runFlowDecomposition(const JavaHandle& flowDecompositionContext, const JavaHandle& network, const FlowDecompositionParameters& flow_decomposition_parameters, const LoadFlowParameters& load_flow_parameters) { +SeriesArray* runFlowDecomposition(const JavaHandle& flowDecompositionContext, const JavaHandle& network, const FlowDecompositionParameters& flow_decomposition_parameters, const LoadFlowParameters& loadflow_parameters) { auto c_flow_decomposition_parameters = flow_decomposition_parameters.to_c_struct(); - auto c_load_flow_parameters = load_flow_parameters.to_c_struct(); - return new SeriesArray(callJava(::runFlowDecomposition, flowDecompositionContext, network, c_flow_decomposition_parameters.get(), c_load_flow_parameters.get())); + auto c_loadflow_parameters = loadflow_parameters.to_c_struct(); + return new SeriesArray(callJava(::runFlowDecomposition, flowDecompositionContext, network, c_flow_decomposition_parameters.get(), c_loadflow_parameters.get())); } FlowDecompositionParameters* createFlowDecompositionParameters() { diff --git a/cpp/src/pypowsybl.h b/cpp/src/pypowsybl.h index dbb8ca932..753e743bf 100644 --- a/cpp/src/pypowsybl.h +++ b/cpp/src/pypowsybl.h @@ -67,7 +67,7 @@ class Array { array* delegate_; }; -typedef Array LoadFlowComponentResultArray; +typedef Array LoadFlowComponentResultArray; typedef Array PostContingencyResultArray; typedef Array LimitViolationArray; typedef Array SeriesArray; @@ -138,6 +138,16 @@ enum DefaultXnecProvider { INTERCONNECTIONS, }; +enum OutputWriter { + CSV = 0, + CSV_MULTILINE, +}; + +enum XnecSelectionStrategy { + ONLY_INTERCONNECTIONS = 0, + INTERCONNECTION_OR_ZONE_TO_ZONE_PTDF_GT_5PC, +}; + class SeriesMetadata { public: SeriesMetadata(const std::string& name, int type, bool isIndex, bool isModifiable, bool isDefault): @@ -165,9 +175,9 @@ class SeriesMetadata { class LoadFlowParameters { public: - LoadFlowParameters(load_flow_parameters* src); - std::shared_ptr to_c_struct() const; - void load_to_c_struct(load_flow_parameters& params) const; + LoadFlowParameters(loadflow_parameters* src); + std::shared_ptr to_c_struct() const; + void load_to_c_struct(loadflow_parameters& params) const; VoltageInitMode voltage_init_mode; bool transformer_voltage_control_on; @@ -186,12 +196,31 @@ class LoadFlowParameters { std::vector provider_parameters_values; }; +class LoadFlowValidationParameters { +public: + LoadFlowValidationParameters(loadflow_validation_parameters* src); + std::shared_ptr to_c_struct() const; + void load_to_c_struct(loadflow_validation_parameters& params) const; + + LoadFlowParameters loadflow_parameters; + double threshold; + bool verbose; + std::string loadflow_name; + double epsilon_x; + bool apply_reactance_correction; + bool ok_missing_values; + bool no_requirement_if_reactive_bound_inversion; + bool compare_results; + bool check_main_component_only; + bool no_requirement_if_setpoint_outside_power_bounds; +}; + class SecurityAnalysisParameters { public: SecurityAnalysisParameters(security_analysis_parameters* src); std::shared_ptr to_c_struct() const; - LoadFlowParameters load_flow_parameters; + LoadFlowParameters loadflow_parameters; double flow_proportional_threshold; double low_voltage_proportional_threshold; double low_voltage_absolute_threshold; @@ -206,7 +235,7 @@ class SensitivityAnalysisParameters { SensitivityAnalysisParameters(sensitivity_analysis_parameters* src); std::shared_ptr to_c_struct() const; - LoadFlowParameters load_flow_parameters; + LoadFlowParameters loadflow_parameters; std::vector provider_parameters_keys; std::vector provider_parameters_values; }; @@ -305,6 +334,8 @@ LoadFlowParameters* createLoadFlowParameters(); std::vector getLoadFlowProviderParametersNames(const std::string& loadFlowProvider); +LoadFlowValidationParameters* createValidationConfig(); + SecurityAnalysisParameters* createSecurityAnalysisParameters(); std::vector getSecurityAnalysisProviderParametersNames(const std::string& securityAnalysisProvider); @@ -319,7 +350,7 @@ void reduceNetwork(const JavaHandle& network, const double v_min, const double v LoadFlowComponentResultArray* runLoadFlow(const JavaHandle& network, bool dc, const LoadFlowParameters& parameters, const std::string& provider, JavaHandle* reporter); -SeriesArray* runLoadFlowValidation(const JavaHandle& network, validation_type validationType); +SeriesArray* runLoadFlowValidation(const JavaHandle& network, validation_type validationType, const LoadFlowValidationParameters& validationParameters); void writeSingleLineDiagramSvg(const JavaHandle& network, const std::string& containerId, const std::string& svgFile, const std::string& metadataFile, const LayoutParameters& parameters); @@ -474,7 +505,7 @@ void addPostcontingencyMonitoredElementsForFlowDecomposition(const JavaHandle& f void addAdditionalXnecProviderForFlowDecomposition(const JavaHandle& flowDecompositionContext, DefaultXnecProvider defaultXnecProvider); -SeriesArray* runFlowDecomposition(const JavaHandle& flowDecompositionContext, const JavaHandle& network, const FlowDecompositionParameters& flow_decomposition_parameters, const LoadFlowParameters& load_flow_parameters); +SeriesArray* runFlowDecomposition(const JavaHandle& flowDecompositionContext, const JavaHandle& network, const FlowDecompositionParameters& flow_decomposition_parameters, const LoadFlowParameters& loadflow_parameters); FlowDecompositionParameters* createFlowDecompositionParameters(); diff --git a/docs/reference/loadflow.rst b/docs/reference/loadflow.rst index f3f1be83c..18a8f014d 100644 --- a/docs/reference/loadflow.rst +++ b/docs/reference/loadflow.rst @@ -72,6 +72,16 @@ Some enum classes are used in results: ComponentStatus +Parameters to validate loadflow +------------------------------- + +The validation of a loadflow can be customized using loadflow validation parameters. + +.. autosummary:: + :toctree: api/ + :nosignatures: + + ValidationParameters Validate loadflow results ------------------------- diff --git a/java/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java b/java/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java index 2e040e7a6..239bad93e 100644 --- a/java/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java +++ b/java/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java @@ -108,7 +108,7 @@ public interface NetworkMetadataPointer extends PointerBase { void setForecastDistance(int forecastDistance); } - @CStruct("load_flow_component_result") + @CStruct("loadflow_component_result") public interface LoadFlowComponentResultPointer extends PointerBase { @CField("connected_component_num") @@ -156,7 +156,7 @@ public interface LoadFlowComponentResultPointer extends PointerBase { LoadFlowComponentResultPointer addressOf(int index); } - @CStruct("load_flow_parameters") + @CStruct("loadflow_parameters") public interface LoadFlowParametersPointer extends PointerBase { @CField("voltage_init_mode") @@ -268,10 +268,77 @@ public interface LoadFlowParametersPointer extends PointerBase { void setProviderParametersValuesCount(int providerParametersKeysCount); } + @CStruct("loadflow_validation_parameters") + public interface LoadFlowValidationParametersPointer extends PointerBase { + + @CField("threshold") + double getThreshold(); + + @CField("threshold") + void setThreshold(double threshold); + + @CField("verbose") + boolean isVerbose(); + + @CField("verbose") + void setVerbose(boolean verbose); + + @CField("loadflow_name") + CCharPointer getLoadFlowName(); + + @CField("loadflow_name") + void setLoadFlowName(CCharPointer loadFlowName); + + @CField("epsilon_x") + double getEpsilonX(); + + @CField("epsilon_x") + void setEpsilonX(double epsilonX); + + @CField("apply_reactance_correction") + boolean isApplyReactanceCorrection(); + + @CField("apply_reactance_correction") + void setApplyReactanceCorrection(boolean applyReactanceCorrection); + + @CFieldAddress("loadflow_parameters") + LoadFlowParametersPointer getLoadFlowParameters(); + + @CField("ok_missing_values") + boolean isOkMissingValues(); + + @CField("ok_missing_values") + void setOkMissingValues(boolean okMissingValues); + + @CField("no_requirement_if_reactive_bound_inversion") + boolean isNoRequirementIfReactiveBoundInversion(); + + @CField("no_requirement_if_reactive_bound_inversion") + void setNoRequirementIfReactiveBoundInversion(boolean noRequirementIfReactiveBoundInversion); + + @CField("compare_results") + boolean isCompareResults(); + + @CField("compare_results") + void setCompareResults(boolean compareResults); + + @CField("check_main_component_only") + boolean isCheckMainComponentOnly(); + + @CField("check_main_component_only") + void setCheckMainComponentOnly(boolean checkMainComponentOnly); + + @CField("no_requirement_if_setpoint_outside_power_bounds") + boolean isNoRequirementIfSetpointOutsidePowerBounds(); + + @CField("no_requirement_if_setpoint_outside_power_bounds") + void setNoRequirementIfSetpointOutsidePowerBounds(boolean noRequirementIfSetpointOutsidePowerBounds); + } + @CStruct("security_analysis_parameters") public interface SecurityAnalysisParametersPointer extends PointerBase { - @CFieldAddress("load_flow_parameters") + @CFieldAddress("loadflow_parameters") LoadFlowParametersPointer getLoadFlowParameters(); @CField("flow_proportional_threshold") @@ -332,7 +399,7 @@ public interface SecurityAnalysisParametersPointer extends PointerBase { @CStruct("sensitivity_analysis_parameters") public interface SensitivityAnalysisParametersPointer extends PointerBase { - @CFieldAddress("load_flow_parameters") + @CFieldAddress("loadflow_parameters") LoadFlowParametersPointer getLoadFlowParameters(); @CField("provider_parameters_keys") diff --git a/java/src/main/java/com/powsybl/python/loadflow/validation/LoadFlowValidationCFunctions.java b/java/src/main/java/com/powsybl/python/loadflow/validation/LoadFlowValidationCFunctions.java index 9b8b3611f..ead7e6c4e 100644 --- a/java/src/main/java/com/powsybl/python/loadflow/validation/LoadFlowValidationCFunctions.java +++ b/java/src/main/java/com/powsybl/python/loadflow/validation/LoadFlowValidationCFunctions.java @@ -22,6 +22,11 @@ import org.graalvm.nativeimage.ObjectHandles; import org.graalvm.nativeimage.c.CContext; import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.UnmanagedMemory; +import org.graalvm.nativeimage.c.struct.SizeOf; +import com.powsybl.python.commons.CTypeUtil; +import com.powsybl.python.loadflow.LoadFlowCUtils; +import com.powsybl.python.loadflow.LoadFlowCFunctions; import static com.powsybl.python.commons.PyPowsyblApiHeader.*; import static com.powsybl.python.commons.Util.doCatch; @@ -39,15 +44,19 @@ private LoadFlowValidationCFunctions() { @CEntryPoint(name = "runLoadFlowValidation") public static ArrayPointer createLoadFlowValidationSeriesArray(IsolateThread thread, ObjectHandle networkHandle, - PyPowsyblApiHeader.ValidationType validationType, ExceptionHandlerPointer exceptionHandlerPtr) { + PyPowsyblApiHeader.ValidationType validationType, + PyPowsyblApiHeader.LoadFlowValidationParametersPointer loadFlowValidationParametersPtr, + ExceptionHandlerPointer exceptionHandlerPtr) { return doCatch(exceptionHandlerPtr, () -> { Network network = ObjectHandles.getGlobal().get(networkHandle); - return createLoadFlowValidationSeriesArray(network, validationType); + return createLoadFlowValidationSeriesArray(network, validationType, loadFlowValidationParametersPtr); }); } - static ArrayPointer createLoadFlowValidationSeriesArray(Network network, PyPowsyblApiHeader.ValidationType validationType) { - InMemoryValidationWriter writer = createLoadFlowValidationWriter(network, validationType); + static ArrayPointer createLoadFlowValidationSeriesArray(Network network, PyPowsyblApiHeader.ValidationType validationType, + PyPowsyblApiHeader.LoadFlowValidationParametersPointer loadFlowValidationParametersPtr) { + ValidationConfig validationConfig = createValidationConfig(loadFlowValidationParametersPtr); + InMemoryValidationWriter writer = createLoadFlowValidationWriter(network, validationType, validationConfig); return createCDataFrame(writer, validationType); } @@ -59,9 +68,14 @@ ValidationConfig.VALIDATION_OUTPUT_WRITER_DEFAULT, new LoadFlowParameters(), Val ValidationConfig.CHECK_MAIN_COMPONENT_ONLY_DEFAULT, ValidationConfig.NO_REQUIREMENT_IF_SETPOINT_OUTSIDE_POWERS_BOUNDS); } - // package scope for unit testing static InMemoryValidationWriter createLoadFlowValidationWriter(Network network, PyPowsyblApiHeader.ValidationType validationType) { - ValidationConfig validationConfig = PyPowsyblConfiguration.isReadConfig() ? ValidationConfig.load() : defaultValidationConfig(); + ValidationConfig validationConfig = defaultValidationConfig(); + return createLoadFlowValidationWriter(network, validationType, validationConfig); + } + + // package scope for unit testing + static InMemoryValidationWriter createLoadFlowValidationWriter(Network network, PyPowsyblApiHeader.ValidationType validationType, + ValidationConfig validationConfig) { InMemoryValidationWriter writer = new InMemoryValidationWriter(); switch (validationType) { case FLOWS: @@ -111,4 +125,66 @@ private static ArrayPointer createCDataFrame(InMemoryValidationWr throw new PowsyblException("Validation '" + validationType + "' not supported"); } } + + @CEntryPoint(name = "createValidationConfig") + public static LoadFlowValidationParametersPointer createValidationConfig(IsolateThread thread, ExceptionHandlerPointer exceptionHandlerPtr) { + return doCatch(exceptionHandlerPtr, () -> convertToLoadFlowValidationParametersPointer(createValidationConfig())); + } + + public static void copyToCLoadFlowValidationParameters(ValidationConfig parameters, LoadFlowValidationParametersPointer cParameters) { + cParameters.setThreshold(parameters.getThreshold()); + cParameters.setVerbose(parameters.isVerbose()); + if (parameters.getLoadFlowName().isPresent()) { + cParameters.setLoadFlowName(CTypeUtil.toCharPtr(parameters.getLoadFlowName().get())); + } else { + cParameters.setLoadFlowName(CTypeUtil.toCharPtr("")); + } + cParameters.setEpsilonX(parameters.getEpsilonX()); + cParameters.setApplyReactanceCorrection(parameters.applyReactanceCorrection()); + LoadFlowCFunctions.copyToCLoadFlowParameters(parameters.getLoadFlowParameters(), cParameters.getLoadFlowParameters()); + cParameters.setOkMissingValues(parameters.areOkMissingValues()); + cParameters.setNoRequirementIfReactiveBoundInversion(parameters.isNoRequirementIfReactiveBoundInversion()); + cParameters.setCompareResults(parameters.isCompareResults()); + cParameters.setCheckMainComponentOnly(parameters.isCheckMainComponentOnly()); + cParameters.setNoRequirementIfSetpointOutsidePowerBounds(parameters.isNoRequirementIfSetpointOutsidePowerBounds()); + } + + public static LoadFlowValidationParametersPointer convertToLoadFlowValidationParametersPointer(ValidationConfig parameters) { + LoadFlowValidationParametersPointer paramsPtr = UnmanagedMemory.calloc(SizeOf.get(LoadFlowValidationParametersPointer.class)); + copyToCLoadFlowValidationParameters(parameters, paramsPtr); + return paramsPtr; + } + + @CEntryPoint(name = "freeValidationConfig") + public static void freeValidationConfig(IsolateThread thread, LoadFlowValidationParametersPointer loadFlowValidationParametersPtr, + ExceptionHandlerPointer exceptionHandlerPtr) { + doCatch(exceptionHandlerPtr, () -> { + freeLoadFlowValidationParametersPointer(loadFlowValidationParametersPtr); + }); + } + + public static void freeLoadFlowValidationParametersPointer(LoadFlowValidationParametersPointer loadFlowValidationParametersPtr) { + LoadFlowCUtils.freeLoadFlowParametersContent(loadFlowValidationParametersPtr.getLoadFlowParameters()); + UnmanagedMemory.free(loadFlowValidationParametersPtr); + } + + public static ValidationConfig createValidationConfig() { + return PyPowsyblConfiguration.isReadConfig() ? ValidationConfig.load() : defaultValidationConfig(); + } + + private static ValidationConfig createValidationConfig(PyPowsyblApiHeader.LoadFlowValidationParametersPointer loadFlowValidationParametersPtr) { + ValidationConfig validationConfig = createValidationConfig(); + validationConfig.setThreshold(loadFlowValidationParametersPtr.getThreshold()); + validationConfig.setVerbose(loadFlowValidationParametersPtr.isVerbose()); + validationConfig.setLoadFlowName(CTypeUtil.toString(loadFlowValidationParametersPtr.getLoadFlowName())); + validationConfig.setEpsilonX(loadFlowValidationParametersPtr.getEpsilonX()); + validationConfig.setApplyReactanceCorrection(loadFlowValidationParametersPtr.isApplyReactanceCorrection()); + validationConfig.setLoadFlowParameters(LoadFlowCUtils.convertLoadFlowParameters(false, loadFlowValidationParametersPtr.getLoadFlowParameters())); + validationConfig.setOkMissingValues(loadFlowValidationParametersPtr.isOkMissingValues()); + validationConfig.setNoRequirementIfReactiveBoundInversion(loadFlowValidationParametersPtr.isNoRequirementIfReactiveBoundInversion()); + validationConfig.setCompareResults(loadFlowValidationParametersPtr.isCompareResults()); + validationConfig.setCheckMainComponentOnly(loadFlowValidationParametersPtr.isCheckMainComponentOnly()); + validationConfig.setNoRequirementIfSetpointOutsidePowerBounds(loadFlowValidationParametersPtr.isNoRequirementIfSetpointOutsidePowerBounds()); + return validationConfig; + } } diff --git a/java/src/test/java/com/powsybl/python/loadflow/validation/LoadFlowValidationTest.java b/java/src/test/java/com/powsybl/python/loadflow/validation/LoadFlowValidationTest.java index 3716061c9..ff1570892 100644 --- a/java/src/test/java/com/powsybl/python/loadflow/validation/LoadFlowValidationTest.java +++ b/java/src/test/java/com/powsybl/python/loadflow/validation/LoadFlowValidationTest.java @@ -13,6 +13,7 @@ import com.powsybl.iidm.network.Network; import com.powsybl.loadflow.LoadFlow; import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.validation.ValidationConfig; import com.powsybl.python.commons.PyPowsyblApiHeader; import com.powsybl.python.network.Dataframes; import org.assertj.core.api.Assertions; @@ -33,7 +34,8 @@ void test() { final Network network = IeeeCdfNetworkFactory.create9(); LoadFlow.Runner runner = LoadFlow.find("OpenLoadFlow"); runner.run(network, new LoadFlowParameters()); - InMemoryValidationWriter busWriter = LoadFlowValidationCFunctions.createLoadFlowValidationWriter(network, PyPowsyblApiHeader.ValidationType.BUSES); + ValidationConfig validationConfig = LoadFlowValidationCFunctions.createValidationConfig(); + InMemoryValidationWriter busWriter = LoadFlowValidationCFunctions.createLoadFlowValidationWriter(network, PyPowsyblApiHeader.ValidationType.BUSES, validationConfig); Assertions.assertThat(Dataframes.createSeries(Validations.busValidationsMapper(), busWriter.getBusData())) .extracting(Series::getName) .contains("id", "incoming_p"); diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 700da2b0f..1b1dde4d7 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -257,8 +257,22 @@ class LoadFlowParameters: provider_parameters_values: List[str] def __init__(self) -> None: ... +class LoadFlowValidationParameters: + threshold: float + verbose: bool + loadflow_name: str + epsilon_x: float + apply_reactance_correction: bool + loadflow_parameters: LoadFlowParameters + ok_missing_values: bool + no_requirement_if_reactive_bound_inversion: bool + compare_results: bool + check_main_component_only: bool + no_requirement_if_setpoint_outside_power_bounds: bool + def __init__(self) -> None: ... + class SecurityAnalysisParameters: - load_flow_parameters: LoadFlowParameters + loadflow_parameters: LoadFlowParameters flow_proportional_threshold: float low_voltage_proportional_threshold: float low_voltage_absolute_threshold: float @@ -269,7 +283,7 @@ class SecurityAnalysisParameters: def __init__(self) -> None: ... class SensitivityAnalysisParameters: - load_flow_parameters: LoadFlowParameters + loadflow_parameters: LoadFlowParameters provider_parameters_keys: List[str] provider_parameters_values: List[str] def __init__(self) -> None: ... @@ -476,8 +490,8 @@ def merge(arg0: JavaHandle, arg1: List[JavaHandle]) -> None: ... def reduce_network(network: JavaHandle, v_min: float, v_max: float, ids: List[str], vls: List[str], depths: List[int], with_dangling_lines: bool) -> None: ... def remove_elements(network: JavaHandle, element_ids: List[str]) -> None: ... def remove_variant(network: JavaHandle, variant: str) -> None: ... -def run_load_flow(network: JavaHandle, dc: bool, parameters: LoadFlowParameters, provider: str, report: Optional[JavaHandle]) -> LoadFlowComponentResultArray: ... -def run_load_flow_validation(network: JavaHandle, validation_type: ValidationType) -> SeriesArray: ... +def run_loadflow(network: JavaHandle, dc: bool, parameters: LoadFlowParameters, provider: str, report: Optional[JavaHandle]) -> LoadFlowComponentResultArray: ... +def run_loadflow_validation(network: JavaHandle, validation_type: ValidationType, validation_parameters: LoadFlowValidationParameters) -> SeriesArray: ... def run_security_analysis(security_analysis_context: JavaHandle, network: JavaHandle, parameters: SecurityAnalysisParameters, provider: str, dc: bool, report: Optional[JavaHandle]) -> JavaHandle: ... def run_sensitivity_analysis(sensitivity_analysis_context: JavaHandle, network: JavaHandle, dc: bool, parameters: SensitivityAnalysisParameters, provider: str, report: Optional[JavaHandle]) -> JavaHandle: ... def set_branch_flow_factor_matrix(sensitivity_analysis_context: JavaHandle, branches_ids: List[str], variables_ids: List[str]) -> None: ... diff --git a/pypowsybl/loadflow.py b/pypowsybl/loadflow.py index 2b7c7b4cb..1c1f9aab3 100644 --- a/pypowsybl/loadflow.py +++ b/pypowsybl/loadflow.py @@ -262,7 +262,7 @@ def run_ac(network: _Network, parameters: Parameters = None, provider: str = '', A list of component results, one for each component of the network. """ p = parameters._to_c_parameters() if parameters is not None else _pypowsybl.LoadFlowParameters() - return [ComponentResult(res) for res in _pypowsybl.run_load_flow(network._handle, False, p, provider, None if reporter is None else reporter._reporter_model)] # pylint: disable=protected-access + return [ComponentResult(res) for res in _pypowsybl.run_loadflow(network._handle, False, p, provider, None if reporter is None else reporter._reporter_model)] # pylint: disable=protected-access def run_dc(network: _Network, parameters: Parameters = None, provider: str = '', reporter: _Reporter = None) -> _List[ComponentResult]: @@ -279,7 +279,7 @@ def run_dc(network: _Network, parameters: Parameters = None, provider: str = '', A list of component results, one for each component of the network. """ p = parameters._to_c_parameters() if parameters is not None else _pypowsybl.LoadFlowParameters() - return [ComponentResult(res) for res in _pypowsybl.run_load_flow(network._handle, True, p, provider, None if reporter is None else reporter._reporter_model)] # pylint: disable=protected-access + return [ComponentResult(res) for res in _pypowsybl.run_loadflow(network._handle, True, p, provider, None if reporter is None else reporter._reporter_model)] # pylint: disable=protected-access ValidationType.ALL = [ValidationType.BUSES, ValidationType.FLOWS, ValidationType.GENERATORS, ValidationType.SHUNTS, @@ -287,6 +287,121 @@ def run_dc(network: _Network, parameters: Parameters = None, provider: str = '', _OptionalDf = _Optional[_DataFrame] +class ValidationParameters: # pylint: disable=too-few-public-methods + """ + Parameters for a loadflow validation. + + All parameters are first read from you configuration file, then overridden with + the constructor arguments. + + .. currentmodule:: pypowsybl.loadflow + + Args: + threshold: Define the margin used for values comparison. + The default value is ``0``. + verbose: Define whether the load flow validation should run in verbose or quiet mode. + loadflow_name: Implementation name to use for running the load flow. + epsilon_x: Value used to correct the reactance in flows validation. + The default value is ``0.1``. + apply_reactance_correction: Define whether small reactance values have to be fixed to epsilon_x or not. + The default value is ``False``. + loadflow_parameters: Parameters that are common to loadflow and loadflow validation. + ok_missing_values: Define whether the validation checks fail if some parameters of connected components have NaN values or not. + The default value is ``False``. + no_requirement_if_reactive_bound_inversion: Define whether the validation checks fail if there is a reactive + bounds inversion (maxQ < minQ) or not. + The default value is ``False``. + compare_results: Should be set to ``True`` to compare the results of 2 validations, i.e. print output files with + data of both ones. + The default value is ``False``. + check_main_component_only: Define whether the validation checks are done only on the equiments in the main + connected component or in all components. + The default value is ``True``. + no_requirement_if_setpoint_outside_power_bounds: Define whether the validation checks fail if there is a + setpoint outside the active power bounds (targetP < minP or targetP > maxP) or not. + The default value is ``False``. + """ + + def __init__(self, threshold: float = None, + verbose: bool = None, + loadflow_name: str = None, + epsilon_x: float = None, + apply_reactance_correction: bool = None, + loadflow_parameters: Parameters = None, + ok_missing_values: bool = None, + no_requirement_if_reactive_bound_inversion: bool = None, + compare_results: bool = None, + check_main_component_only: bool = None, + no_requirement_if_setpoint_outside_power_bounds: bool = None): + self._init_with_default_values() + if threshold is not None: + self.threshold = threshold + if verbose is not None: + self.verbose = verbose + if loadflow_name is not None: + self.loadflow_name = loadflow_name + if epsilon_x is not None: + self.epsilon_x = epsilon_x + if apply_reactance_correction is not None: + self.apply_reactance_correction = apply_reactance_correction + if loadflow_parameters is not None: + self.loadflow_parameters = loadflow_parameters + if ok_missing_values is not None: + self.ok_missing_values = ok_missing_values + if no_requirement_if_reactive_bound_inversion is not None: + self.no_requirement_if_reactive_bound_inversion = no_requirement_if_reactive_bound_inversion + if compare_results is not None: + self.compare_results = compare_results + if check_main_component_only is not None: + self.check_main_component_only = check_main_component_only + if no_requirement_if_setpoint_outside_power_bounds is not None: + self.no_requirement_if_setpoint_outside_power_bounds = no_requirement_if_setpoint_outside_power_bounds + + def _init_with_default_values(self) -> None: + self._init_from_c(_pypowsybl.LoadFlowValidationParameters()) + + def _init_from_c(self, c_parameters: _pypowsybl.LoadFlowValidationParameters) -> None: + self.threshold = c_parameters.threshold + self.verbose = c_parameters.verbose + self.loadflow_name = c_parameters.loadflow_name + self.epsilon_x = c_parameters.epsilon_x + self.apply_reactance_correction = c_parameters.apply_reactance_correction + self.loadflow_parameters = _parameters_from_c(c_parameters.loadflow_parameters) + self.ok_missing_values = c_parameters.ok_missing_values + self.no_requirement_if_reactive_bound_inversion = c_parameters.no_requirement_if_reactive_bound_inversion + self.compare_results = c_parameters.compare_results + self.check_main_component_only = c_parameters.check_main_component_only + self.no_requirement_if_setpoint_outside_power_bounds = c_parameters.no_requirement_if_setpoint_outside_power_bounds + + def _to_c_parameters(self) -> _pypowsybl.LoadFlowValidationParameters: + c_parameters = _pypowsybl.LoadFlowValidationParameters() + c_parameters.threshold = self.threshold + c_parameters.verbose = self.verbose + c_parameters.loadflow_name = self.loadflow_name + c_parameters.epsilon_x = self.epsilon_x + c_parameters.apply_reactance_correction = self.apply_reactance_correction + c_parameters.loadflow_parameters = self.loadflow_parameters._to_c_parameters() + c_parameters.ok_missing_values = self.ok_missing_values + c_parameters.no_requirement_if_reactive_bound_inversion = self.no_requirement_if_reactive_bound_inversion + c_parameters.compare_results = self.compare_results + c_parameters.check_main_component_only = self.check_main_component_only + c_parameters.no_requirement_if_setpoint_outside_power_bounds = self.no_requirement_if_setpoint_outside_power_bounds + return c_parameters + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(" \ + f"threshold={self.threshold}" \ + f", verbose={self.verbose!r}" \ + f", loadflow_name={self.loadflow_name!r}" \ + f", epsilon_x={self.epsilon_x!r}" \ + f", apply_reactance_correction={self.apply_reactance_correction!r}" \ + f", loadflow_parameters={self.loadflow_parameters!r}" \ + f", ok_missing_values={self.ok_missing_values!r}" \ + f", no_requirement_if_reactive_bound_inversion={self.no_requirement_if_reactive_bound_inversion}" \ + f", compare_results={self.compare_results!r}" \ + f", no_requirement_if_setpoint_outside_power_bounds={self.no_requirement_if_setpoint_outside_power_bounds}" \ + f")" + class ValidationResult: """ @@ -368,22 +483,25 @@ def valid(self) -> bool: return self._valid -def run_validation(network: _Network, validation_types: _List[ValidationType] = None) -> ValidationResult: +def run_validation(network: _Network, validation_types: _List[ValidationType] = None, + validation_parameters: ValidationParameters = None) -> ValidationResult: """ Checks that the network data are consistent with AC loadflow equations. Args: network: The network to be checked. validation_types: The types of data to be checked. If None, all types will be checked. + validation_parameters: The parameters to run the validation with. Returns: The validation result. """ if validation_types is None: validation_types = ValidationType.ALL + validation_config = validation_parameters._to_c_parameters() if validation_parameters is not None else _pypowsybl.LoadFlowValidationParameters() res_by_type = {} for validation_type in validation_types: - series_array = _pypowsybl.run_load_flow_validation(network._handle, validation_type) + series_array = _pypowsybl.run_loadflow_validation(network._handle, validation_type, validation_config) res_by_type[validation_type] = _create_data_frame_from_series_array(series_array) return ValidationResult(buses=res_by_type.get(ValidationType.BUSES, None), diff --git a/pypowsybl/security.py b/pypowsybl/security.py index 5a3f9573f..15a3b6975 100644 --- a/pypowsybl/security.py +++ b/pypowsybl/security.py @@ -137,7 +137,7 @@ def increased_violations(self) -> IncreasedViolationsParameters: def _init_with_default_values(self) -> None: default_parameters = _pypowsybl.SecurityAnalysisParameters() - self.load_flow_parameters = pypowsybl.loadflow._parameters_from_c(default_parameters.load_flow_parameters) + self.load_flow_parameters = pypowsybl.loadflow._parameters_from_c(default_parameters.loadflow_parameters) self._increased_violations = IncreasedViolationsParameters(default_parameters.flow_proportional_threshold, default_parameters.low_voltage_proportional_threshold, default_parameters.low_voltage_absolute_threshold, @@ -148,7 +148,7 @@ def _init_with_default_values(self) -> None: def _to_c_parameters(self) -> _pypowsybl.SecurityAnalysisParameters: c_parameters = _pypowsybl.SecurityAnalysisParameters() - c_parameters.load_flow_parameters = self.load_flow_parameters._to_c_parameters() + c_parameters.loadflow_parameters = self.load_flow_parameters._to_c_parameters() c_parameters.flow_proportional_threshold = self.increased_violations.flow_proportional_threshold c_parameters.low_voltage_proportional_threshold = self.increased_violations.low_voltage_proportional_threshold c_parameters.low_voltage_absolute_threshold = self.increased_violations.low_voltage_absolute_threshold diff --git a/pypowsybl/sensitivity.py b/pypowsybl/sensitivity.py index 550c822ae..108f8a57f 100644 --- a/pypowsybl/sensitivity.py +++ b/pypowsybl/sensitivity.py @@ -160,13 +160,13 @@ def __init__(self, load_flow_parameters: pypowsybl.loadflow.Parameters = None, def _init_with_default_values(self) -> None: default_parameters = _pypowsybl.SensitivityAnalysisParameters() - self.load_flow_parameters = pypowsybl.loadflow._parameters_from_c(default_parameters.load_flow_parameters) + self.load_flow_parameters = pypowsybl.loadflow._parameters_from_c(default_parameters.loadflow_parameters) self.provider_parameters = dict( zip(default_parameters.provider_parameters_keys, default_parameters.provider_parameters_values)) def _to_c_parameters(self) -> _pypowsybl.SensitivityAnalysisParameters: c_parameters = _pypowsybl.SensitivityAnalysisParameters() - c_parameters.load_flow_parameters = self.load_flow_parameters._to_c_parameters() + c_parameters.loadflow_parameters = self.load_flow_parameters._to_c_parameters() c_parameters.provider_parameters_keys = list(self.provider_parameters.keys()) c_parameters.provider_parameters_values = list(self.provider_parameters.values()) return c_parameters diff --git a/tests/test_loadflow.py b/tests/test_loadflow.py index d40bffbd7..18351ef3a 100644 --- a/tests/test_loadflow.py +++ b/tests/test_loadflow.py @@ -42,7 +42,7 @@ def test_run_lf(): assert 0 == results[0].connected_component_num assert 0 == results[0].synchronous_component_num assert 'VL1_0' == results[0].slack_bus_id - assert round(abs(0.5-results[0].slack_bus_active_power_mismatch), 1) == 0 + assert round(abs(0.5 - results[0].slack_bus_active_power_mismatch), 1) == 0 assert 7 == results[0].iteration_count parameters = lf.Parameters(distributed_slack=False) @@ -88,12 +88,12 @@ def test_validation(): pp.loadflow.run_ac(n) validation = pp.loadflow.run_validation(n, [ValidationType.FLOWS, ValidationType.GENERATORS, ValidationType.BUSES]) - assert abs(-232.4-validation.generators['p']['B1-G']) < 0.1 - assert abs(-47.8-validation.buses['incoming_p']['VL4_0']) < 0.1 - assert abs(157.8-validation.branch_flows['p1']['L1-2-1']) < 0.1 + assert abs(-232.4 - validation.generators['p']['B1-G']) < 0.1 + assert abs(-47.8 - validation.buses['incoming_p']['VL4_0']) < 0.1 + assert abs(157.8 - validation.branch_flows['p1']['L1-2-1']) < 0.1 assert not validation.valid n2 = pp.network.create_four_substations_node_breaker_network() - pp.loadflow.run_ac(n) + pp.loadflow.run_ac(n2) validation2 = pp.loadflow.run_validation(n2, [ValidationType.SVCS]) assert 1 == len(validation2.svcs) assert validation2.svcs['validated']['SVC'] @@ -103,7 +103,7 @@ def test_twt_validation(): n = pp.network.create_eurostag_tutorial_example1_network() pp.loadflow.run_ac(n) validation = pp.loadflow.run_validation(n, [ValidationType.TWTS]) - assert abs(-10.421382-validation.twts['error']['NHV2_NLOAD']) < 0.00001 + assert abs(-10.421382 - validation.twts['error']['NHV2_NLOAD']) < 0.00001 assert validation.valid @@ -120,6 +120,63 @@ def test_validation_all(): assert validation.twts is not None +def test_validation_parameters_get(): + # Testing setting independently every attributes + attributes = { + 'threshold': [0.1, 0.2], + 'verbose': [True, False], + 'loadflow_name': ['loadFlow1', 'loadFLow2'], + 'epsilon_x': [0.1, 0.2], + 'apply_reactance_correction': [True, False], + 'ok_missing_values': [True, False], + 'no_requirement_if_reactive_bound_inversion': [True, False], + 'compare_results': [True, False], + 'check_main_component_only': [True, False], + 'no_requirement_if_setpoint_outside_power_bounds': [True, False] + } + + for attribute, values in attributes.items(): + for value in values: + parameters = lf.ValidationParameters(**dict([(attribute, value)])) + assert value == getattr(parameters, attribute) + + +def test_validation_parameters_set(): + attributes = { + 'threshold': [0.1, 0.2], + 'verbose': [True, False], + 'loadflow_name': ['loadFlow1', 'loadFLow2'], + 'epsilon_x': [0.1, 0.2], + 'apply_reactance_correction': [True, False], + 'ok_missing_values': [True, False], + 'no_requirement_if_reactive_bound_inversion': [True, False], + 'compare_results': [True, False], + 'check_main_component_only': [True, False], + 'no_requirement_if_setpoint_outside_power_bounds': [True, False] + } + for attribute, values in attributes.items(): + for value in values: + parameters = lf.ValidationParameters() + setattr(parameters, attribute, value) + assert value == getattr(parameters, attribute) + + +def test_validation_parameters_not_valid(): + n = pp.network.create_four_substations_node_breaker_network() + pp.loadflow.run_ac(n) + parameters = lf.ValidationParameters() + validation = pp.loadflow.run_validation(n, validation_parameters=parameters) + assert not validation.branch_flows['validated']['LINE_S2S3'] + + +def test_validation_parameters_valid(): + n = pp.network.create_four_substations_node_breaker_network() + pp.loadflow.run_ac(n) + parameters = lf.ValidationParameters(threshold=0.1) + validation = pp.loadflow.run_validation(n, validation_parameters=parameters) + assert validation.branch_flows['validated']['LINE_S2S3'] + + def test_provider_names(): assert 'OpenLoadFlow' in pp.loadflow.get_provider_names() assert 'DynaFlow' in pp.loadflow.get_provider_names() @@ -167,7 +224,7 @@ def test_run_lf_with_report(): reporter = rp.Reporter() report1 = str(reporter) assert len(report1) > 0 - pp.loadflow.run_ac(n, reporter = reporter) + pp.loadflow.run_ac(n, reporter=reporter) report2 = str(reporter) assert len(report2) > len(report1) json_report = reporter.to_json() @@ -175,6 +232,6 @@ def test_run_lf_with_report(): json.loads(json_report) n2 = pp.network.create_eurostag_tutorial_example1_network() - pp.loadflow.run_ac(n2, reporter = reporter) + pp.loadflow.run_ac(n2, reporter=reporter) report3 = str(reporter) assert len(report3) > len(report2) diff --git a/tests/test_network_extensions.py b/tests/test_network_extensions.py index 5ea23f294..32df9bc73 100644 --- a/tests/test_network_extensions.py +++ b/tests/test_network_extensions.py @@ -422,7 +422,7 @@ def test_slack_terminal_bus_breaker(): # but powsybl-core works this way assert e.bus_id == 'VLHV1_0' # the corresponding "bus view" bus -def test_slack_terminal_bus_breaker(): +def test_coordinated_reactive_control(): n = pn.create_four_substations_node_breaker_network() extension_name = 'coordinatedReactiveControl' n.create_extensions(extension_name, generator_id='GH1', q_percent=0.8)