From bb6e8c3855dc3b5851349747094562640865a031 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 26 Feb 2025 16:42:07 +0100 Subject: [PATCH 01/71] [failed] added java and python impl for reading json contingency list function Signed-off-by: Naledi EL CHEIKH --- .gitignore | 5 ++- cpp/pypowsybl-cpp/bindings.cpp | 3 ++ java/pypowsybl/pom.xml | 5 +++ .../contingency/ContingencyContainer.java | 2 ++ .../contingency/ContingencyContainerImpl.java | 4 +++ .../security/SecurityAnalysisCFunctions.java | 36 +++++++++++++++++++ .../python/security/SecurityAnalysisTest.java | 12 +++++++ .../security/impl/contingency_container.py | 11 ++++++ 8 files changed, 77 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a05cf1d357..6483088f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,7 @@ tests/_trial_temp/* *.orig # Documentation generated files (autosummary) -docs/reference/api/ \ No newline at end of file +docs/reference/api/ + +# Test files +**/contingencies.json \ No newline at end of file diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index c402f12600..3d06e4572a 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -701,6 +701,9 @@ 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("read_json_contingency", &pypowsybl::readJsonContingency, "Add contingencies from JSON file.", + py::arg("analysis_context"), py::arg("path_to_json_file"); + 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")); diff --git a/java/pypowsybl/pom.xml b/java/pypowsybl/pom.xml index f86a13285c..0725db8609 100644 --- a/java/pypowsybl/pom.xml +++ b/java/pypowsybl/pom.xml @@ -410,5 +410,10 @@ open-rao-rao-result-json ${powsybl-open-rao.version} + + org.json + json + 20190722 + diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java index d80df991bb..eff9437903 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java @@ -15,4 +15,6 @@ public interface ContingencyContainer { void addContingency(String contingencyId, List elementIds); + + void readJsonContingency(String pathToJsonFile); } diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index fcbb6866dc..f5618a4289 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -29,6 +29,10 @@ public void addContingency(String contingencyId, List elementIds) { elementIdsByContingencyId.put(contingencyId, elementIds); } + @Override + public void readJsonContingency(String pathToJsonFile) { + } + private static ContingencyElement createContingencyElement(Network network, String elementId) { Identifiable identifiable = network.getIdentifiable(elementId); if (identifiable == null) { diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index 4e8fbb051d..f2e2b37786 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -35,9 +35,15 @@ import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; import org.graalvm.nativeimage.c.type.CIntPointer; +//import org.json.JSONArray; +//import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +//import java.io.IOException; +//import java.nio.file.Files; +//import java.nio.file.Paths; +//import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; @@ -136,6 +142,36 @@ public static void addContingency(IsolateThread thread, ObjectHandle contingency }); } + // en cours +// @CEntryPoint(name = "readJsonContingency") +// public static void readJsonContingency(String jsonFilePath, ObjectHandle contingencyContainerHandle) { +// String jsonData; +// ContingencyContainer contingencyContainer = ObjectHandles.getGlobal().get(contingencyContainerHandle); +// try { +// jsonData = new String(Files.readAllBytes(Paths.get(jsonFilePath))); +// } catch (IOException e) { +// throw new RuntimeException(e); +// } +// +// JSONObject json = new JSONObject(jsonData); +// System.out.println("JSON " + jsonData); +// +// String jsonFileContingencyIds = json.getString("id"); +// System.out.println("JSON contingencies: " + jsonFileContingencyIds); +// +// JSONArray tempElements = json.getJSONArray("elements"); +// System.out.println("JSON Data: " + json); +// JSONArray tempElementIds = json.getJSONArray("id"); +// System.out.println("Temp elements ids " + tempElementIds); +// +// List jsonFileElementsIds = new ArrayList<>(); +// for (int i = 0; i < tempElements.length(); i++) { +// jsonFileElementsIds.add(tempElements.getString(i)); +// } +// System.out.println(jsonFileElementsIds); +// contingencyContainer.addContingency(jsonFileContingencyIds, jsonFileElementsIds); +// } + private static void setPostContingencyResultInSecurityAnalysisResultPointer(PyPowsyblApiHeader.PostContingencyResultPointer contingencyPtr, PostContingencyResult postContingencyResult) { contingencyPtr.setContingencyId(CTypeUtil.toCharPtr(postContingencyResult.getContingency().getId())); contingencyPtr.setStatus(postContingencyResult.getStatus().ordinal()); diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index 773599868a..95bcd1e9b2 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -80,4 +80,16 @@ void testSecurityAnalysis() { Assertions.assertThat(series.get(1).getStrings()) .containsExactly("NHV1_NHV2_2", "VLHV1"); } + + @Test + void addContingencyFromJsonFile() { + Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); + SecurityAnalysisContext analysisContext = new SecurityAnalysisContext(); + analysisContext.addContingency("First contingency", Collections.singletonList("NHV1_NHV2_1")); + SecurityAnalysisResult result = analysisContext.run(network, new SecurityAnalysisParameters(), "OpenLoadFlow", ReportNode.NO_OP); + + System.out.println("List contingency :" + result.getPostContingencyResults().stream().toList().get(0).toString()); + + analysisContext.readJsonContingency("/home/elcheikhnal/projects/pypowsybl/contingencies.json"); + } } diff --git a/pypowsybl/security/impl/contingency_container.py b/pypowsybl/security/impl/contingency_container.py index 8d6d45b949..1ac3a07288 100644 --- a/pypowsybl/security/impl/contingency_container.py +++ b/pypowsybl/security/impl/contingency_container.py @@ -4,6 +4,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # SPDX-License-Identifier: MPL-2.0 # + from typing import List, Callable from pypowsybl import _pypowsybl @@ -47,3 +48,13 @@ def add_single_element_contingencies(self, elements_ids: List[str], for element_id in elements_ids: contingency_id = contingency_id_provider(element_id) if contingency_id_provider else element_id _pypowsybl.add_contingency(self._handle, contingency_id, [element_id]) + + def add_contingencies_from_json_file(self, path_to_json_file: str ) -> None: + """ + Add contingencies from JSON file. + + Args: + path_to_json_file: The path to the JSON file to add to the contingency list. + """ + + _pypowsybl.add_contingency(self._handle, read_json_contingency(path_to_json_file)) \ No newline at end of file From 53327840da56126efaec929c2dc8609be768b2ab Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 6 Mar 2025 13:05:03 +0100 Subject: [PATCH 02/71] new function for reading json files in contingency container impl Signed-off-by: Naledi EL CHEIKH --- .../contingency/ContingencyContainer.java | 5 +- .../contingency/ContingencyContainerImpl.java | 15 +++++- .../security/SecurityAnalysisCFunctions.java | 49 ++++++------------- .../contingency/ContingencyContainerTest.java | 28 +++++++++++ .../python/security/SecurityAnalysisTest.java | 12 ----- 5 files changed, 60 insertions(+), 49 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java index eff9437903..bfbabd73b2 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java @@ -7,6 +7,9 @@ */ package com.powsybl.python.contingency; +import com.powsybl.iidm.network.Network; + +import java.nio.file.Path; import java.util.List; /** @@ -16,5 +19,5 @@ public interface ContingencyContainer { void addContingency(String contingencyId, List elementIds); - void readJsonContingency(String pathToJsonFile); + void readJsonContingency(Path pathToJsonFile, Network network); } diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index f5618a4289..92b2d776e4 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -9,8 +9,10 @@ import com.powsybl.commons.PowsyblException; import com.powsybl.contingency.*; +import com.powsybl.contingency.contingency.list.ContingencyList; import com.powsybl.iidm.network.*; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -30,7 +32,18 @@ public void addContingency(String contingencyId, List elementIds) { } @Override - public void readJsonContingency(String pathToJsonFile) { + public void readJsonContingency(Path pathToJsonFile, Network network) { + ContingencyList list; + + try { + list = ContingencyList.load(pathToJsonFile); + } catch (Exception e) { + throw new PowsyblException(e); + } + + for (Contingency contingency : list.getContingencies(network)) { + addContingency(contingency.getId(), contingency.getElements().stream().map(ContingencyElement::getId).collect(Collectors.toList())); + } } private static ContingencyElement createContingencyElement(Network network, String elementId) { diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index f2e2b37786..0c2c9c3f6d 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -35,15 +35,12 @@ import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; import org.graalvm.nativeimage.c.type.CIntPointer; -//import org.json.JSONArray; -//import org.json.JSONObject; +import org.graalvm.nativeimage.c.type.CTypeConversion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -//import java.io.IOException; -//import java.nio.file.Files; -//import java.nio.file.Paths; -//import java.util.ArrayList; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Objects; import java.util.Set; @@ -142,35 +139,17 @@ public static void addContingency(IsolateThread thread, ObjectHandle contingency }); } - // en cours -// @CEntryPoint(name = "readJsonContingency") -// public static void readJsonContingency(String jsonFilePath, ObjectHandle contingencyContainerHandle) { -// String jsonData; -// ContingencyContainer contingencyContainer = ObjectHandles.getGlobal().get(contingencyContainerHandle); -// try { -// jsonData = new String(Files.readAllBytes(Paths.get(jsonFilePath))); -// } catch (IOException e) { -// throw new RuntimeException(e); -// } -// -// JSONObject json = new JSONObject(jsonData); -// System.out.println("JSON " + jsonData); -// -// String jsonFileContingencyIds = json.getString("id"); -// System.out.println("JSON contingencies: " + jsonFileContingencyIds); -// -// JSONArray tempElements = json.getJSONArray("elements"); -// System.out.println("JSON Data: " + json); -// JSONArray tempElementIds = json.getJSONArray("id"); -// System.out.println("Temp elements ids " + tempElementIds); -// -// List jsonFileElementsIds = new ArrayList<>(); -// for (int i = 0; i < tempElements.length(); i++) { -// jsonFileElementsIds.add(tempElements.getString(i)); -// } -// System.out.println(jsonFileElementsIds); -// contingencyContainer.addContingency(jsonFileContingencyIds, jsonFileElementsIds); -// } + @CEntryPoint(name = "readJsonContingency") + public static void readJsonContingency(IsolateThread thread, CCharPointer jsonFilePath, ObjectHandle contingencyContainerHandle, ObjectHandle networkHandle, + PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { + doCatch(exceptionHandlerPtr, () -> { + ContingencyContainer contingencyContainer = ObjectHandles.getGlobal().get(contingencyContainerHandle); + String stringPath = CTypeUtil.toString(jsonFilePath); + Path path = Paths.get(stringPath); + Network network = ObjectHandles.getGlobal().get(networkHandle); + contingencyContainer.readJsonContingency(path, network); + }); + } private static void setPostContingencyResultInSecurityAnalysisResultPointer(PyPowsyblApiHeader.PostContingencyResultPointer contingencyPtr, PostContingencyResult postContingencyResult) { contingencyPtr.setContingencyId(CTypeUtil.toCharPtr(postContingencyResult.getContingency().getId())); diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 36880cb961..17cea1cdd6 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -7,20 +7,32 @@ */ package com.powsybl.python.contingency; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import com.powsybl.commons.PowsyblException; import com.powsybl.contingency.*; +import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.test.*; + import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; import java.util.List; +import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; /** * @author Yichen TANG {@literal } */ class ContingencyContainerTest { + protected FileSystem fileSystem; + @Test void testContingencyConverter() { var container1 = new ContingencyContainerImpl(); @@ -89,4 +101,20 @@ void testContingencyConverter() { .hasOnlyOneElementSatisfying(c -> assertThat(c.getElements().get(0)).isInstanceOf(BatteryContingency.class)); } + @Test + void testToAddContingencyFromJsonFile() throws IOException { + ContingencyContainerImpl container = new ContingencyContainerImpl(); + Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); + fileSystem = Jimfs.newFileSystem(Configuration.unix()); + + Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/contingencies.json")), fileSystem.getPath("/contingencies.json")); + container.readJsonContingency(fileSystem.getPath("/contingencies.json"), network); + List contingencies = container.createContingencies(network); + + assertFalse(contingencies.isEmpty()); + assertEquals(2, contingencies.size()); + assertEquals("contingency", contingencies.get(0).getId()); + assertEquals("contingency2", contingencies.get(1).getId()); + assertThrows(PowsyblException.class, () -> container.readJsonContingency(fileSystem.getPath("/notExistingContingencies.json"), network)); + } } diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index 95bcd1e9b2..773599868a 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -80,16 +80,4 @@ void testSecurityAnalysis() { Assertions.assertThat(series.get(1).getStrings()) .containsExactly("NHV1_NHV2_2", "VLHV1"); } - - @Test - void addContingencyFromJsonFile() { - Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); - SecurityAnalysisContext analysisContext = new SecurityAnalysisContext(); - analysisContext.addContingency("First contingency", Collections.singletonList("NHV1_NHV2_1")); - SecurityAnalysisResult result = analysisContext.run(network, new SecurityAnalysisParameters(), "OpenLoadFlow", ReportNode.NO_OP); - - System.out.println("List contingency :" + result.getPostContingencyResults().stream().toList().get(0).toString()); - - analysisContext.readJsonContingency("/home/elcheikhnal/projects/pypowsybl/contingencies.json"); - } } From 9014a4daae583925cc29a8acb5d5bbd3fce71918 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 6 Mar 2025 14:10:06 +0100 Subject: [PATCH 03/71] fix import Signed-off-by: Naledi EL CHEIKH --- .../powsybl/python/security/SecurityAnalysisCFunctions.java | 1 - pypowsybl/_pypowsybl.pyi | 4 ++++ pypowsybl/security/impl/contingency_container.py | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index 0c2c9c3f6d..b43f236b1c 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -35,7 +35,6 @@ import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; import org.graalvm.nativeimage.c.type.CIntPointer; -import org.graalvm.nativeimage.c.type.CTypeConversion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index ed79dd08be..c8e4566299 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -3,6 +3,8 @@ from typing import ClassVar, Dict, Iterator, List, Sequence, Optional, Union from numpy import ndarray +from pypowsybl.network import Network + class ArrayStruct: def __init__(self) -> None: ... @@ -796,6 +798,7 @@ class RaoComputationStatus: def name(self) -> str: ... def add_contingency(analysis_context: JavaHandle, contingency_id: str, elements_ids: List[str]) -> None: ... +def read_json_contingency(analysis_context: JavaHandle, path_to_json_file: str, network: Network) -> None: ... def add_monitored_elements(security_analysis_context: JavaHandle, contingency_context_type: ContingencyContextType, branch_ids: List[str], voltage_level_ids: List[str], three_windings_transformer_ids: List[str], contingency_ids: List[str]) -> None: ... def add_load_active_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, active_power: float) -> None: ... def add_load_reactive_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, reactive_power: float) -> None: ... @@ -1195,3 +1198,4 @@ def update_grid2op_integer_value(backend: JavaHandle, value_type: Grid2opUpdateI def check_grid2op_isolated_and_disconnected_injections(backend: JavaHandle) -> bool: ... def run_grid2op_loadflow(backend: JavaHandle, dc: bool, parameters: LoadFlowParameters) -> LoadFlowComponentResultArray: ... + diff --git a/pypowsybl/security/impl/contingency_container.py b/pypowsybl/security/impl/contingency_container.py index 1ac3a07288..162acd2dbb 100644 --- a/pypowsybl/security/impl/contingency_container.py +++ b/pypowsybl/security/impl/contingency_container.py @@ -7,6 +7,7 @@ from typing import List, Callable from pypowsybl import _pypowsybl +from pypowsybl.network import Network class ContingencyContainer: @@ -49,12 +50,13 @@ def add_single_element_contingencies(self, elements_ids: List[str], contingency_id = contingency_id_provider(element_id) if contingency_id_provider else element_id _pypowsybl.add_contingency(self._handle, contingency_id, [element_id]) - def add_contingencies_from_json_file(self, path_to_json_file: str ) -> None: + def add_contingencies_from_json_file(self, path_to_json_file: str, network: Network) -> None: """ Add contingencies from JSON file. Args: path_to_json_file: The path to the JSON file to add to the contingency list. + network: The network on which the contingencies will be added. """ - _pypowsybl.add_contingency(self._handle, read_json_contingency(path_to_json_file)) \ No newline at end of file + _pypowsybl.read_json_contingency(self._handle, path_to_json_file, network) \ No newline at end of file From 8cc01de79de1dfcd8f0a58a2aef962128c0c80a0 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 10 Mar 2025 10:22:52 +0100 Subject: [PATCH 04/71] new add contingency from json file implementation Signed-off-by: Naledi EL CHEIKH --- cpp/pypowsybl-cpp/bindings.cpp | 2 +- .../contingency/ContingencyContainer.java | 4 +-- .../contingency/ContingencyContainerImpl.java | 34 ++++++++++--------- .../security/SecurityAnalysisCFunctions.java | 7 ++-- .../contingency/ContingencyContainerTest.java | 8 +++-- pypowsybl/_pypowsybl.pyi | 2 +- .../security/impl/contingency_container.py | 2 +- 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index 3d06e4572a..1798adedc9 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -701,7 +701,7 @@ 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("read_json_contingency", &pypowsybl::readJsonContingency, "Add contingencies from JSON file.", + m.def("add_contingency_from_json_file", &pypowsybl::addContingencyFromJsonFile, "Add contingencies from JSON file.", py::arg("analysis_context"), py::arg("path_to_json_file"); m.def("add_load_active_power_action", &pypowsybl::addLoadActivePowerAction, "Add a load active power remedial action", diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java index bfbabd73b2..b385ae4dcd 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainer.java @@ -7,8 +7,6 @@ */ package com.powsybl.python.contingency; -import com.powsybl.iidm.network.Network; - import java.nio.file.Path; import java.util.List; @@ -19,5 +17,5 @@ public interface ContingencyContainer { void addContingency(String contingencyId, List elementIds); - void readJsonContingency(Path pathToJsonFile, Network network); + void addContingencyFromJsonFile(Path pathToJsonFile); } diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index 92b2d776e4..de74f993ff 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -12,11 +12,9 @@ import com.powsybl.contingency.contingency.list.ContingencyList; import com.powsybl.iidm.network.*; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; /** @@ -25,6 +23,7 @@ public class ContingencyContainerImpl implements ContingencyContainer { private final Map> elementIdsByContingencyId = new HashMap<>(); + private Path pathToContingencyJsonFile = null; @Override public void addContingency(String contingencyId, List elementIds) { @@ -32,18 +31,8 @@ public void addContingency(String contingencyId, List elementIds) { } @Override - public void readJsonContingency(Path pathToJsonFile, Network network) { - ContingencyList list; - - try { - list = ContingencyList.load(pathToJsonFile); - } catch (Exception e) { - throw new PowsyblException(e); - } - - for (Contingency contingency : list.getContingencies(network)) { - addContingency(contingency.getId(), contingency.getElements().stream().map(ContingencyElement::getId).collect(Collectors.toList())); - } + public void addContingencyFromJsonFile(Path pathToJsonFile) { + pathToContingencyJsonFile = pathToJsonFile; } private static ContingencyElement createContingencyElement(Network network, String elementId) { @@ -83,6 +72,18 @@ private static ContingencyElement createContingencyElement(Network network, Stri } protected List createContingencies(Network network) { + if (pathToContingencyJsonFile != null) { + ContingencyList contingenciesList; + List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); + + contingenciesList = ContingencyList.load(pathToContingencyJsonFile); + + for (Contingency contingency : contingenciesList.getContingencies(network)) { + contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); + } + return contingencies; + } + List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { String contingencyId = e.getKey(); @@ -92,6 +93,7 @@ protected List createContingencies(Network network) { .collect(Collectors.toList()); contingencies.add(new Contingency(contingencyId, elements)); } + return contingencies; } } diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index b43f236b1c..12dbe7d041 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -138,15 +138,14 @@ public static void addContingency(IsolateThread thread, ObjectHandle contingency }); } - @CEntryPoint(name = "readJsonContingency") - public static void readJsonContingency(IsolateThread thread, CCharPointer jsonFilePath, ObjectHandle contingencyContainerHandle, ObjectHandle networkHandle, + @CEntryPoint(name = "addContingencyFromJsonFile") + public static void addContingencyFromJsonFile(IsolateThread thread, CCharPointer jsonFilePath, ObjectHandle contingencyContainerHandle, PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { doCatch(exceptionHandlerPtr, () -> { ContingencyContainer contingencyContainer = ObjectHandles.getGlobal().get(contingencyContainerHandle); String stringPath = CTypeUtil.toString(jsonFilePath); Path path = Paths.get(stringPath); - Network network = ObjectHandles.getGlobal().get(networkHandle); - contingencyContainer.readJsonContingency(path, network); + contingencyContainer.addContingencyFromJsonFile(path); }); } diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 17cea1cdd6..4a0ee569f7 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -108,13 +108,17 @@ void testToAddContingencyFromJsonFile() throws IOException { fileSystem = Jimfs.newFileSystem(Configuration.unix()); Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/contingencies.json")), fileSystem.getPath("/contingencies.json")); - container.readJsonContingency(fileSystem.getPath("/contingencies.json"), network); + container.addContingencyFromJsonFile(fileSystem.getPath("/contingencies.json")); List contingencies = container.createContingencies(network); + assertFalse(contingencies.isEmpty()); assertEquals(2, contingencies.size()); assertEquals("contingency", contingencies.get(0).getId()); assertEquals("contingency2", contingencies.get(1).getId()); - assertThrows(PowsyblException.class, () -> container.readJsonContingency(fileSystem.getPath("/notExistingContingencies.json"), network)); + assertThrows(NullPointerException.class, () -> { + Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/notExistingContingencies.json")), fileSystem.getPath("/notExistingContingencies.json")); + container.addContingencyFromJsonFile(fileSystem.getPath("/notExistingContingencies.json")); + }); } } diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index c8e4566299..534582775f 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -798,7 +798,7 @@ class RaoComputationStatus: def name(self) -> str: ... def add_contingency(analysis_context: JavaHandle, contingency_id: str, elements_ids: List[str]) -> None: ... -def read_json_contingency(analysis_context: JavaHandle, path_to_json_file: str, network: Network) -> None: ... +def add_contingency_from_json_file(analysis_context: JavaHandle, path_to_json_file: str, network: Network) -> None: ... def add_monitored_elements(security_analysis_context: JavaHandle, contingency_context_type: ContingencyContextType, branch_ids: List[str], voltage_level_ids: List[str], three_windings_transformer_ids: List[str], contingency_ids: List[str]) -> None: ... def add_load_active_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, active_power: float) -> None: ... def add_load_reactive_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, reactive_power: float) -> None: ... diff --git a/pypowsybl/security/impl/contingency_container.py b/pypowsybl/security/impl/contingency_container.py index 162acd2dbb..786742b9c1 100644 --- a/pypowsybl/security/impl/contingency_container.py +++ b/pypowsybl/security/impl/contingency_container.py @@ -59,4 +59,4 @@ def add_contingencies_from_json_file(self, path_to_json_file: str, network: Netw network: The network on which the contingencies will be added. """ - _pypowsybl.read_json_contingency(self._handle, path_to_json_file, network) \ No newline at end of file + _pypowsybl.add_contingency_from_json_file(self._handle, path_to_json_file, network) \ No newline at end of file From a8ba55cf42bb35cfb7735f5f85bf9f2a31c07b55 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 10 Mar 2025 13:47:13 +0100 Subject: [PATCH 05/71] fix add contingency from json file implementation Signed-off-by: Naledi EL CHEIKH --- cpp/pypowsybl-cpp/bindings.cpp | 2 +- .../powsybl/python/contingency/ContingencyContainerImpl.java | 1 - .../powsybl/python/contingency/ContingencyContainerTest.java | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index 1798adedc9..21f0bc7cd0 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -702,7 +702,7 @@ PYBIND11_MODULE(_pypowsybl, m) { py::arg("analysis_context"), py::arg("contingency_id"), py::arg("elements_ids")); m.def("add_contingency_from_json_file", &pypowsybl::addContingencyFromJsonFile, "Add contingencies from JSON file.", - py::arg("analysis_context"), py::arg("path_to_json_file"); + py::arg("analysis_context"), py::arg("path_to_json_file")); 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")); diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index de74f993ff..7fc01fbea2 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -12,7 +12,6 @@ import com.powsybl.contingency.contingency.list.ContingencyList; import com.powsybl.iidm.network.*; -import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 4a0ee569f7..7c0e0a6c58 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -9,7 +9,6 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; -import com.powsybl.commons.PowsyblException; import com.powsybl.contingency.*; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.test.*; @@ -111,7 +110,6 @@ void testToAddContingencyFromJsonFile() throws IOException { container.addContingencyFromJsonFile(fileSystem.getPath("/contingencies.json")); List contingencies = container.createContingencies(network); - assertFalse(contingencies.isEmpty()); assertEquals(2, contingencies.size()); assertEquals("contingency", contingencies.get(0).getId()); From 4bf16895dfa68210b14e9417df7f827989ea7988 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 12 Mar 2025 11:13:11 +0100 Subject: [PATCH 06/71] fixed add contingency from json file implementation Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 4 ++++ cpp/powsybl-cpp/powsybl-cpp.h | 2 ++ pypowsybl/security/impl/contingency_container.py | 5 ++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index 953e625af1..824a833945 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -759,6 +759,10 @@ void addContingency(const JavaHandle& analysisContext, const std::string& contin PowsyblCaller::get()->callJava(::addContingency, analysisContext, (char*) contingencyId.data(), elementIdPtr.get(), elementsIds.size()); } +void addContingencyFromJsonFile(const JavaHandle& analysisContext, const std::string& jsonFilePath) { + PowsyblCaller::get()->callJava<(::addContingencyFromJsonFile, analysisContext, (char*) jsonFilePath.data()); +} + JavaHandle runSecurityAnalysis(const JavaHandle& securityAnalysisContext, const JavaHandle& network, const SecurityAnalysisParameters& parameters, const std::string& provider, bool dc, JavaHandle* reportNode) { auto c_parameters = parameters.to_c_struct(); diff --git a/cpp/powsybl-cpp/powsybl-cpp.h b/cpp/powsybl-cpp/powsybl-cpp.h index 2a6ee859c1..c2bd5e4dfb 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.h +++ b/cpp/powsybl-cpp/powsybl-cpp.h @@ -591,6 +591,8 @@ JavaHandle createSecurityAnalysis(); void addContingency(const JavaHandle& analysisContext, const std::string& contingencyId, const std::vector& elementsIds); +void addContingencyFromJsonFile(const JavaHandle& analysisContext, const std::string& jsonFilePath); + JavaHandle runSecurityAnalysis(const JavaHandle& securityAnalysisContext, const JavaHandle& network, const SecurityAnalysisParameters& parameters, const std::string& provider, bool dc, JavaHandle* reportNode); JavaHandle createSensitivityAnalysis(); diff --git a/pypowsybl/security/impl/contingency_container.py b/pypowsybl/security/impl/contingency_container.py index 786742b9c1..23ceb74c63 100644 --- a/pypowsybl/security/impl/contingency_container.py +++ b/pypowsybl/security/impl/contingency_container.py @@ -50,13 +50,12 @@ def add_single_element_contingencies(self, elements_ids: List[str], contingency_id = contingency_id_provider(element_id) if contingency_id_provider else element_id _pypowsybl.add_contingency(self._handle, contingency_id, [element_id]) - def add_contingencies_from_json_file(self, path_to_json_file: str, network: Network) -> None: + def add_contingencies_from_json_file(self, path_to_json_file: str) -> None: """ Add contingencies from JSON file. Args: path_to_json_file: The path to the JSON file to add to the contingency list. - network: The network on which the contingencies will be added. """ - _pypowsybl.add_contingency_from_json_file(self._handle, path_to_json_file, network) \ No newline at end of file + _pypowsybl.add_contingency_from_json_file(self._handle, path_to_json_file) \ No newline at end of file From 058e7d3d190b16b2f3700f842fbb567eec6a79e9 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 12 Mar 2025 14:32:18 +0100 Subject: [PATCH 07/71] fixed add contingency from json file implementation Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 2 +- .../com/powsybl/python/security/SecurityAnalysisCFunctions.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index 824a833945..4c2b702986 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -760,7 +760,7 @@ void addContingency(const JavaHandle& analysisContext, const std::string& contin } void addContingencyFromJsonFile(const JavaHandle& analysisContext, const std::string& jsonFilePath) { - PowsyblCaller::get()->callJava<(::addContingencyFromJsonFile, analysisContext, (char*) jsonFilePath.data()); + PowsyblCaller::get()->callJava(::addContingencyFromJsonFile, analysisContext, (char*) jsonFilePath.data()); } JavaHandle runSecurityAnalysis(const JavaHandle& securityAnalysisContext, const JavaHandle& network, const SecurityAnalysisParameters& parameters, diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index 12dbe7d041..c8a1bbed64 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -139,7 +139,7 @@ public static void addContingency(IsolateThread thread, ObjectHandle contingency } @CEntryPoint(name = "addContingencyFromJsonFile") - public static void addContingencyFromJsonFile(IsolateThread thread, CCharPointer jsonFilePath, ObjectHandle contingencyContainerHandle, + public static void addContingencyFromJsonFile(IsolateThread thread, ObjectHandle contingencyContainerHandle, CCharPointer jsonFilePath, PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { doCatch(exceptionHandlerPtr, () -> { ContingencyContainer contingencyContainer = ObjectHandles.getGlobal().get(contingencyContainerHandle); From 759dc0e0c3507acc13e094c6c8c11838a247cbb6 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 12 Mar 2025 15:10:28 +0100 Subject: [PATCH 08/71] fixed venv Signed-off-by: Naledi EL CHEIKH --- cpp/lib/pybind11 | 1 + 1 file changed, 1 insertion(+) create mode 160000 cpp/lib/pybind11 diff --git a/cpp/lib/pybind11 b/cpp/lib/pybind11 new file mode 160000 index 0000000000..3e9dfa2866 --- /dev/null +++ b/cpp/lib/pybind11 @@ -0,0 +1 @@ +Subproject commit 3e9dfa2866941655c56877882565e7577de6fc7b From 4bc688bd7f7c30ff3782d044db31a19209079b38 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 12 Mar 2025 15:35:23 +0100 Subject: [PATCH 09/71] fixed error handling Signed-off-by: Naledi EL CHEIKH --- .../contingency/ContingencyContainerImpl.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index 7fc01fbea2..e95e1b94b6 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -71,7 +71,7 @@ private static ContingencyElement createContingencyElement(Network network, Stri } protected List createContingencies(Network network) { - if (pathToContingencyJsonFile != null) { + if (pathToContingencyJsonFile == null) { ContingencyList contingenciesList; List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); @@ -81,18 +81,22 @@ protected List createContingencies(Network network) { contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); } return contingencies; - } + } else if (elementIdsByContingencyId.isEmpty()){ + List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); + for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { + String contingencyId = e.getKey(); + List elementIds = e.getValue(); + List elements = elementIds.stream() + .map(elementId -> createContingencyElement(network, elementId)) + .collect(Collectors.toList()); + contingencies.add(new Contingency(contingencyId, elements)); + } - List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); - for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { - String contingencyId = e.getKey(); - List elementIds = e.getValue(); - List elements = elementIds.stream() - .map(elementId -> createContingencyElement(network, elementId)) - .collect(Collectors.toList()); - contingencies.add(new Contingency(contingencyId, elements)); + return contingencies; + } else { + throw new PowsyblException("Can't find contingencies"); } - return contingencies; + } } From c0c0d4530ef8159c23ddd378d1fd0dcb243ecf7a Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 12 Mar 2025 15:37:54 +0100 Subject: [PATCH 10/71] fixed error handling Signed-off-by: Naledi EL CHEIKH --- .../powsybl/python/contingency/ContingencyContainerImpl.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index e95e1b94b6..949af86911 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -81,7 +81,7 @@ protected List createContingencies(Network network) { contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); } return contingencies; - } else if (elementIdsByContingencyId.isEmpty()){ + } else if (elementIdsByContingencyId.isEmpty()) { List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { String contingencyId = e.getKey(); @@ -96,7 +96,5 @@ protected List createContingencies(Network network) { } else { throw new PowsyblException("Can't find contingencies"); } - - } } From 2de47df13af44af586d8833e635511418a4d318f Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 12 Mar 2025 16:23:49 +0100 Subject: [PATCH 11/71] fixed add contingency impl and error handling Signed-off-by: Naledi EL CHEIKH --- cpp/lib/pybind11 | 1 - .../contingency/ContingencyContainerImpl.java | 43 ++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) delete mode 160000 cpp/lib/pybind11 diff --git a/cpp/lib/pybind11 b/cpp/lib/pybind11 deleted file mode 160000 index 3e9dfa2866..0000000000 --- a/cpp/lib/pybind11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3e9dfa2866941655c56877882565e7577de6fc7b diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index 949af86911..f2e65296a5 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -71,30 +71,33 @@ private static ContingencyElement createContingencyElement(Network network, Stri } protected List createContingencies(Network network) { - if (pathToContingencyJsonFile == null) { - ContingencyList contingenciesList; - List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); + try { + if (pathToContingencyJsonFile == null) { + ContingencyList contingenciesList; + List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); - contingenciesList = ContingencyList.load(pathToContingencyJsonFile); + contingenciesList = ContingencyList.load(pathToContingencyJsonFile); - for (Contingency contingency : contingenciesList.getContingencies(network)) { - contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); - } - return contingencies; - } else if (elementIdsByContingencyId.isEmpty()) { - List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); - for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { - String contingencyId = e.getKey(); - List elementIds = e.getValue(); - List elements = elementIds.stream() - .map(elementId -> createContingencyElement(network, elementId)) - .collect(Collectors.toList()); - contingencies.add(new Contingency(contingencyId, elements)); + for (Contingency contingency : contingenciesList.getContingencies(network)) { + contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); + } + return contingencies; + + } else { + List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); + for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { + String contingencyId = e.getKey(); + List elementIds = e.getValue(); + List elements = elementIds.stream() + .map(elementId -> createContingencyElement(network, elementId)) + .collect(Collectors.toList()); + contingencies.add(new Contingency(contingencyId, elements)); + } + return contingencies; } - return contingencies; - } else { - throw new PowsyblException("Can't find contingencies"); + } catch (NullPointerException e) { + throw new PowsyblException("Can't find contingencies", e); } } } From 5a04d95995edc01547400e7754d8edc1044ed7dd Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 13 Mar 2025 10:33:32 +0100 Subject: [PATCH 12/71] fix error in implementation Signed-off-by: Naledi EL CHEIKH --- .../powsybl/python/contingency/ContingencyContainerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index f2e65296a5..ffa343d8a7 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -72,7 +72,7 @@ private static ContingencyElement createContingencyElement(Network network, Stri protected List createContingencies(Network network) { try { - if (pathToContingencyJsonFile == null) { + if (pathToContingencyJsonFile != null) { ContingencyList contingenciesList; List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); From fdcb324805d082486a380e337065a1bb3c91e7eb Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 13 Mar 2025 11:36:30 +0100 Subject: [PATCH 13/71] fix error in implementation Signed-off-by: Naledi EL CHEIKH --- .../contingency/ContingencyContainerImpl.java | 39 ++++++++----------- .../contingency/ContingencyContainerTest.java | 4 +- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index ffa343d8a7..56a0a6b2c0 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -71,33 +71,26 @@ private static ContingencyElement createContingencyElement(Network network, Stri } protected List createContingencies(Network network) { - try { - if (pathToContingencyJsonFile != null) { - ContingencyList contingenciesList; - List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); + List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); - contingenciesList = ContingencyList.load(pathToContingencyJsonFile); + if (pathToContingencyJsonFile != null) { + ContingencyList contingenciesList; + contingenciesList = ContingencyList.load(pathToContingencyJsonFile); - for (Contingency contingency : contingenciesList.getContingencies(network)) { - contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); - } - return contingencies; - - } else { - List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); - for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { - String contingencyId = e.getKey(); - List elementIds = e.getValue(); - List elements = elementIds.stream() - .map(elementId -> createContingencyElement(network, elementId)) - .collect(Collectors.toList()); - contingencies.add(new Contingency(contingencyId, elements)); - } - return contingencies; + for (Contingency contingency : contingenciesList.getContingencies(network)) { + contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); } + } - } catch (NullPointerException e) { - throw new PowsyblException("Can't find contingencies", e); + for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { + String contingencyId = e.getKey(); + List elementIds = e.getValue(); + List elements = elementIds.stream() + .map(elementId -> createContingencyElement(network, elementId)) + .collect(Collectors.toList()); + contingencies.add(new Contingency(contingencyId, elements)); } + + return contingencies; } } diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 7c0e0a6c58..6fb10f058b 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -106,7 +106,7 @@ void testToAddContingencyFromJsonFile() throws IOException { Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); fileSystem = Jimfs.newFileSystem(Configuration.unix()); - Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/contingencies.json")), fileSystem.getPath("/contingencies.json")); + Files.copy(getClass().getResourceAsStream("/contingencies.json"), fileSystem.getPath("/contingencies.json")); container.addContingencyFromJsonFile(fileSystem.getPath("/contingencies.json")); List contingencies = container.createContingencies(network); @@ -115,7 +115,7 @@ void testToAddContingencyFromJsonFile() throws IOException { assertEquals("contingency", contingencies.get(0).getId()); assertEquals("contingency2", contingencies.get(1).getId()); assertThrows(NullPointerException.class, () -> { - Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/notExistingContingencies.json")), fileSystem.getPath("/notExistingContingencies.json")); + Files.copy(getClass().getResourceAsStream("/notExistingContingencies.json"), fileSystem.getPath("/notExistingContingencies.json")); container.addContingencyFromJsonFile(fileSystem.getPath("/notExistingContingencies.json")); }); } From fdde7d405b99a4c01f284caa115f510840ea4eca Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 13 Mar 2025 13:15:09 +0100 Subject: [PATCH 14/71] fix error in test for json contingencies Signed-off-by: Naledi EL CHEIKH --- .../com/powsybl/python/contingency/ContingencyContainerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 6fb10f058b..5e7ff47ebb 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -19,7 +19,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.util.List; -import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; From 6e312f72e7b3be12e7afe70adfa0e2c356535894 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 13 Mar 2025 17:07:30 +0100 Subject: [PATCH 15/71] update of add json contingencies function with error handling Signed-off-by: Naledi EL CHEIKH --- .../contingency/ContingencyContainerImpl.java | 19 +++++++++++++------ .../contingency/ContingencyContainerTest.java | 8 +++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index 56a0a6b2c0..57a27f2c8c 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -12,6 +12,7 @@ import com.powsybl.contingency.contingency.list.ContingencyList; import com.powsybl.iidm.network.*; +import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; @@ -31,7 +32,12 @@ public void addContingency(String contingencyId, List elementIds) { @Override public void addContingencyFromJsonFile(Path pathToJsonFile) { - pathToContingencyJsonFile = pathToJsonFile; + if (Files.exists(pathToJsonFile)) { + pathToContingencyJsonFile = pathToJsonFile; + } else { + throw new PowsyblException("Can't find JSON file: " + pathToJsonFile); + } + } private static ContingencyElement createContingencyElement(Network network, String elementId) { @@ -72,15 +78,17 @@ private static ContingencyElement createContingencyElement(Network network, Stri protected List createContingencies(Network network) { List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); - - if (pathToContingencyJsonFile != null) { - ContingencyList contingenciesList; + ContingencyList contingenciesList; + try { contingenciesList = ContingencyList.load(pathToContingencyJsonFile); + } catch (NullPointerException e) { + throw new PowsyblException("Can't find JSON file.", e); + } for (Contingency contingency : contingenciesList.getContingencies(network)) { contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); } - } + for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { String contingencyId = e.getKey(); @@ -90,7 +98,6 @@ protected List createContingencies(Network network) { .collect(Collectors.toList()); contingencies.add(new Contingency(contingencyId, elements)); } - return contingencies; } } diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 5e7ff47ebb..6fea6737ce 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -9,6 +9,7 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; +import com.powsybl.commons.PowsyblException; import com.powsybl.contingency.*; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.test.*; @@ -113,9 +114,10 @@ void testToAddContingencyFromJsonFile() throws IOException { assertEquals(2, contingencies.size()); assertEquals("contingency", contingencies.get(0).getId()); assertEquals("contingency2", contingencies.get(1).getId()); - assertThrows(NullPointerException.class, () -> { - Files.copy(getClass().getResourceAsStream("/notExistingContingencies.json"), fileSystem.getPath("/notExistingContingencies.json")); + + assertThrows(PowsyblException.class, () -> { container.addContingencyFromJsonFile(fileSystem.getPath("/notExistingContingencies.json")); + container.createContingencies(network); }); } -} +} \ No newline at end of file From 00328dd0e4ec1750ac43bbcf7ca63a91278501ba Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 13 Mar 2025 17:08:59 +0100 Subject: [PATCH 16/71] update of add json contingencies function with error handling Signed-off-by: Naledi EL CHEIKH --- .../contingency/ContingencyContainerImpl.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index 57a27f2c8c..53328ac85d 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -78,17 +78,19 @@ private static ContingencyElement createContingencyElement(Network network, Stri protected List createContingencies(Network network) { List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); - ContingencyList contingenciesList; - try { - contingenciesList = ContingencyList.load(pathToContingencyJsonFile); - } catch (NullPointerException e) { - throw new PowsyblException("Can't find JSON file.", e); - } + + if (pathToContingencyJsonFile != null) { + ContingencyList contingenciesList; + try { + contingenciesList = ContingencyList.load(pathToContingencyJsonFile); + } catch (NullPointerException e) { + throw new PowsyblException("Can't find JSON file.", e); + } for (Contingency contingency : contingenciesList.getContingencies(network)) { contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); } - + } for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { String contingencyId = e.getKey(); From b22a043f2b02fff0e7fe722ab5d92e16a039f0ee Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 13 Mar 2025 17:14:13 +0100 Subject: [PATCH 17/71] fix checkstyle violation Signed-off-by: Naledi EL CHEIKH --- .../powsybl/python/contingency/ContingencyContainerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 6fea6737ce..ab815ea5e2 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -120,4 +120,4 @@ void testToAddContingencyFromJsonFile() throws IOException { container.createContingencies(network); }); } -} \ No newline at end of file +} From 2344c34031671ae2a1c4a405d5eb3714c4d4a63f Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 24 Mar 2025 10:40:25 +0100 Subject: [PATCH 18/71] test with objects.requirenonnull Signed-off-by: Naledi EL CHEIKH --- .../python/contingency/ContingencyContainerTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index ab815ea5e2..7d29b68afd 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -20,6 +20,7 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.util.List; +import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -106,7 +107,7 @@ void testToAddContingencyFromJsonFile() throws IOException { Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); fileSystem = Jimfs.newFileSystem(Configuration.unix()); - Files.copy(getClass().getResourceAsStream("/contingencies.json"), fileSystem.getPath("/contingencies.json")); + Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/contingencies.json")), fileSystem.getPath("/contingencies.json")); container.addContingencyFromJsonFile(fileSystem.getPath("/contingencies.json")); List contingencies = container.createContingencies(network); @@ -119,5 +120,8 @@ void testToAddContingencyFromJsonFile() throws IOException { container.addContingencyFromJsonFile(fileSystem.getPath("/notExistingContingencies.json")); container.createContingencies(network); }); + + assertThrows(NullPointerException.class, () -> + Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/notExistingContingencies.json")), fileSystem.getPath("/notExistingContingencies.json"))); } } From 6b7b48a5341e04a244ddf71c47eebc8ad5e85a80 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 24 Mar 2025 11:23:44 +0100 Subject: [PATCH 19/71] test with try catch path initializer Signed-off-by: Naledi EL CHEIKH --- .../contingency/ContingencyContainerImpl.java | 7 +------ .../contingency/ContingencyContainerTest.java | 14 +++++++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index 53328ac85d..3fbb4d0c78 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -37,7 +37,6 @@ public void addContingencyFromJsonFile(Path pathToJsonFile) { } else { throw new PowsyblException("Can't find JSON file: " + pathToJsonFile); } - } private static ContingencyElement createContingencyElement(Network network, String elementId) { @@ -81,11 +80,7 @@ protected List createContingencies(Network network) { if (pathToContingencyJsonFile != null) { ContingencyList contingenciesList; - try { - contingenciesList = ContingencyList.load(pathToContingencyJsonFile); - } catch (NullPointerException e) { - throw new PowsyblException("Can't find JSON file.", e); - } + contingenciesList = ContingencyList.load(pathToContingencyJsonFile); for (Contingency contingency : contingenciesList.getContingencies(network)) { contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 7d29b68afd..6b2fba562b 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -15,12 +15,13 @@ import com.powsybl.iidm.network.test.*; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.Files; import java.util.List; -import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -32,6 +33,7 @@ class ContingencyContainerTest { protected FileSystem fileSystem; + private static final Logger LOGGER = LoggerFactory.getLogger(ContingencyContainerTest.class); @Test void testContingencyConverter() { @@ -107,7 +109,12 @@ void testToAddContingencyFromJsonFile() throws IOException { Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); fileSystem = Jimfs.newFileSystem(Configuration.unix()); - Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/contingencies.json")), fileSystem.getPath("/contingencies.json")); + try { + Files.copy(getClass().getResourceAsStream("/contingencies.json"), fileSystem.getPath("/contingencies.json")); + } catch (NullPointerException e) { + LOGGER.error("JSON file required doesn't exist"); + } + container.addContingencyFromJsonFile(fileSystem.getPath("/contingencies.json")); List contingencies = container.createContingencies(network); @@ -120,8 +127,5 @@ void testToAddContingencyFromJsonFile() throws IOException { container.addContingencyFromJsonFile(fileSystem.getPath("/notExistingContingencies.json")); container.createContingencies(network); }); - - assertThrows(NullPointerException.class, () -> - Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/notExistingContingencies.json")), fileSystem.getPath("/notExistingContingencies.json"))); } } From 599a5f7c96d1e28df5a633ac79f646eab26ed54c Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 24 Mar 2025 11:30:03 +0100 Subject: [PATCH 20/71] test with try catch path initializer Signed-off-by: Naledi EL CHEIKH --- .../powsybl/python/contingency/ContingencyContainerTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 6b2fba562b..39be6b38f8 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -111,11 +111,12 @@ void testToAddContingencyFromJsonFile() throws IOException { try { Files.copy(getClass().getResourceAsStream("/contingencies.json"), fileSystem.getPath("/contingencies.json")); - } catch (NullPointerException e) { + container.addContingencyFromJsonFile(fileSystem.getPath("/contingencies.json")); + + } catch (Exception e) { LOGGER.error("JSON file required doesn't exist"); } - container.addContingencyFromJsonFile(fileSystem.getPath("/contingencies.json")); List contingencies = container.createContingencies(network); assertFalse(contingencies.isEmpty()); From 21b666d0c3e5388c6822d7b22c7db7c559c60bb3 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 24 Mar 2025 11:35:53 +0100 Subject: [PATCH 21/71] add test file contingencies.json Signed-off-by: Naledi EL CHEIKH --- .../contingency/ContingencyContainerTest.java | 10 ++------- .../src/test/resources/contingencies.json | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 java/pypowsybl/src/test/resources/contingencies.json diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index 39be6b38f8..ffa3227b8e 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -109,14 +109,8 @@ void testToAddContingencyFromJsonFile() throws IOException { Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); fileSystem = Jimfs.newFileSystem(Configuration.unix()); - try { - Files.copy(getClass().getResourceAsStream("/contingencies.json"), fileSystem.getPath("/contingencies.json")); - container.addContingencyFromJsonFile(fileSystem.getPath("/contingencies.json")); - - } catch (Exception e) { - LOGGER.error("JSON file required doesn't exist"); - } - + Files.copy(getClass().getResourceAsStream("/contingencies.json"), fileSystem.getPath("/contingencies.json")); + container.addContingencyFromJsonFile(fileSystem.getPath("/contingencies.json")); List contingencies = container.createContingencies(network); assertFalse(contingencies.isEmpty()); diff --git a/java/pypowsybl/src/test/resources/contingencies.json b/java/pypowsybl/src/test/resources/contingencies.json new file mode 100644 index 0000000000..fc6abd1d65 --- /dev/null +++ b/java/pypowsybl/src/test/resources/contingencies.json @@ -0,0 +1,21 @@ +{ + "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" + } ] + } ] +} \ No newline at end of file From 6939e17d4cae16ff7c4bf299ccff21a82a941902 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 24 Mar 2025 11:52:26 +0100 Subject: [PATCH 22/71] deleted unused field Signed-off-by: Naledi EL CHEIKH --- .gitignore | 3 --- .../python/contingency/ContingencyContainerTest.java | 8 -------- 2 files changed, 11 deletions(-) diff --git a/.gitignore b/.gitignore index 6483088f7c..d493fc0cea 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,3 @@ tests/_trial_temp/* # Documentation generated files (autosummary) docs/reference/api/ - -# Test files -**/contingencies.json \ No newline at end of file diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index ffa3227b8e..b8a62fa259 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -15,8 +15,6 @@ import com.powsybl.iidm.network.test.*; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.FileSystem; @@ -33,7 +31,6 @@ class ContingencyContainerTest { protected FileSystem fileSystem; - private static final Logger LOGGER = LoggerFactory.getLogger(ContingencyContainerTest.class); @Test void testContingencyConverter() { @@ -117,10 +114,5 @@ void testToAddContingencyFromJsonFile() throws IOException { assertEquals(2, contingencies.size()); assertEquals("contingency", contingencies.get(0).getId()); assertEquals("contingency2", contingencies.get(1).getId()); - - assertThrows(PowsyblException.class, () -> { - container.addContingencyFromJsonFile(fileSystem.getPath("/notExistingContingencies.json")); - container.createContingencies(network); - }); } } From 5fc4b635fe88cefb223efa56fb95ab0acbf64164 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 24 Mar 2025 11:52:48 +0100 Subject: [PATCH 23/71] deleted unused field Signed-off-by: Naledi EL CHEIKH --- .../com/powsybl/python/contingency/ContingencyContainerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java index b8a62fa259..3793ec8239 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/contingency/ContingencyContainerTest.java @@ -9,7 +9,6 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; -import com.powsybl.commons.PowsyblException; import com.powsybl.contingency.*; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.test.*; From eeadfd4284bdc5d580926f18d92ac90c3a6d2234 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 24 Mar 2025 15:19:09 +0100 Subject: [PATCH 24/71] fixed python impl of add_contingency_from_json_file function Signed-off-by: Naledi EL CHEIKH --- pypowsybl/_pypowsybl.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 534582775f..38bb81e935 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -798,7 +798,7 @@ class RaoComputationStatus: def name(self) -> str: ... def add_contingency(analysis_context: JavaHandle, contingency_id: str, elements_ids: List[str]) -> None: ... -def add_contingency_from_json_file(analysis_context: JavaHandle, path_to_json_file: str, network: Network) -> None: ... +def add_contingency_from_json_file(analysis_context: JavaHandle, path_to_json_file: str) -> None: ... def add_monitored_elements(security_analysis_context: JavaHandle, contingency_context_type: ContingencyContextType, branch_ids: List[str], voltage_level_ids: List[str], three_windings_transformer_ids: List[str], contingency_ids: List[str]) -> None: ... def add_load_active_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, active_power: float) -> None: ... def add_load_reactive_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, reactive_power: float) -> None: ... From c6cab0cc8b67284e14e48989378c57bef71f4ec8 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 24 Mar 2025 16:17:20 +0100 Subject: [PATCH 25/71] add python tests Signed-off-by: Naledi EL CHEIKH --- pypowsybl/security/impl/contingency_container.py | 2 -- tests/test_security_analysis.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pypowsybl/security/impl/contingency_container.py b/pypowsybl/security/impl/contingency_container.py index 23ceb74c63..f749f10f3a 100644 --- a/pypowsybl/security/impl/contingency_container.py +++ b/pypowsybl/security/impl/contingency_container.py @@ -7,8 +7,6 @@ from typing import List, Callable from pypowsybl import _pypowsybl -from pypowsybl.network import Network - class ContingencyContainer: def __init__(self, handle: _pypowsybl.JavaHandle): diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index f3c78f36fc..c4ab057649 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -407,3 +407,13 @@ def test_tie_line_contingency(): sa.add_single_element_contingency('NHV1_NHV2_1', 'tie line contingency') sa_result = sa.run_ac(n) assert 'tie line contingency' in sa_result.post_contingency_results.keys() + +def test_add_contingencies_from_json_file(): + n = pp.network.create_eurostag_tutorial_example1_network() + sa = pp.security.create_analysis() + sa.add_contingencies_from_json_file("/contingencies.json") + sa_result = sa.run_ac(n) + assert 'contingency' in sa_result.post_contingency_results.keys() + assert 'contingency2' in sa_result.post_contingency_results.keys() + + From a493ec8cb89e1096cc4b6f4a5cfeafe4b50fd79b Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 26 Mar 2025 17:02:03 +0100 Subject: [PATCH 26/71] rewrite of the python impl of the add_contingency_from_json_file function Signed-off-by: Naledi EL CHEIKH --- pypowsybl/_pypowsybl.pyi | 3 ++- pypowsybl/security/impl/contingency_container.py | 6 +++--- tests/test_security_analysis.py | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 38bb81e935..5356b972db 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -1,4 +1,5 @@ from logging import Logger +from pathlib import Path from typing import ClassVar, Dict, Iterator, List, Sequence, Optional, Union from numpy import ndarray @@ -798,7 +799,7 @@ class RaoComputationStatus: def name(self) -> str: ... def add_contingency(analysis_context: JavaHandle, contingency_id: str, elements_ids: List[str]) -> None: ... -def add_contingency_from_json_file(analysis_context: JavaHandle, path_to_json_file: str) -> None: ... +def add_contingency_from_json_file(analysis_context: JavaHandle, path_to_json_file: Path) -> None: ... def add_monitored_elements(security_analysis_context: JavaHandle, contingency_context_type: ContingencyContextType, branch_ids: List[str], voltage_level_ids: List[str], three_windings_transformer_ids: List[str], contingency_ids: List[str]) -> None: ... def add_load_active_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, active_power: float) -> None: ... def add_load_reactive_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, reactive_power: float) -> None: ... diff --git a/pypowsybl/security/impl/contingency_container.py b/pypowsybl/security/impl/contingency_container.py index f749f10f3a..b58e1b5092 100644 --- a/pypowsybl/security/impl/contingency_container.py +++ b/pypowsybl/security/impl/contingency_container.py @@ -4,7 +4,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # SPDX-License-Identifier: MPL-2.0 # - +from pathlib import Path from typing import List, Callable from pypowsybl import _pypowsybl @@ -48,12 +48,12 @@ def add_single_element_contingencies(self, elements_ids: List[str], contingency_id = contingency_id_provider(element_id) if contingency_id_provider else element_id _pypowsybl.add_contingency(self._handle, contingency_id, [element_id]) - def add_contingencies_from_json_file(self, path_to_json_file: str) -> None: + def add_contingencies_from_json_file(self, path_to_json_file: Path) -> None: """ Add contingencies from JSON file. Args: - path_to_json_file: The path to the JSON file to add to the contingency list. + path_to_json_file: The path to the JSON file from which we extract the contingency list. """ _pypowsybl.add_contingency_from_json_file(self._handle, path_to_json_file) \ No newline at end of file diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index c4ab057649..35af6bd852 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -11,6 +11,7 @@ import pypowsybl.report as rp from pypowsybl._pypowsybl import ConditionType import re +from pathlib import Path @pytest.fixture(autouse=True) @@ -411,7 +412,8 @@ def test_tie_line_contingency(): def test_add_contingencies_from_json_file(): n = pp.network.create_eurostag_tutorial_example1_network() sa = pp.security.create_analysis() - sa.add_contingencies_from_json_file("/contingencies.json") + str_path = "/contingencies.json" + sa.add_contingencies_from_json_file(Path(str_path)) sa_result = sa.run_ac(n) assert 'contingency' in sa_result.post_contingency_results.keys() assert 'contingency2' in sa_result.post_contingency_results.keys() From fbae2c70f464b518b15cb03745ebc528ec6812b8 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 27 Mar 2025 16:37:21 +0100 Subject: [PATCH 27/71] rewrite python tests Signed-off-by: Naledi EL CHEIKH --- .../contingency/ContingencyContainerImpl.java | 19 ++++++++++--------- pypowsybl/_pypowsybl.pyi | 2 +- .../security/impl/contingency_container.py | 2 +- tests/test_security_analysis.py | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java index 3fbb4d0c78..1294904003 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/contingency/ContingencyContainerImpl.java @@ -32,11 +32,7 @@ public void addContingency(String contingencyId, List elementIds) { @Override public void addContingencyFromJsonFile(Path pathToJsonFile) { - if (Files.exists(pathToJsonFile)) { - pathToContingencyJsonFile = pathToJsonFile; - } else { - throw new PowsyblException("Can't find JSON file: " + pathToJsonFile); - } + pathToContingencyJsonFile = pathToJsonFile; } private static ContingencyElement createContingencyElement(Network network, String elementId) { @@ -79,12 +75,17 @@ protected List createContingencies(Network network) { List contingencies = new ArrayList<>(elementIdsByContingencyId.size()); if (pathToContingencyJsonFile != null) { - ContingencyList contingenciesList; - contingenciesList = ContingencyList.load(pathToContingencyJsonFile); + if (Files.exists(pathToContingencyJsonFile)) { + ContingencyList contingenciesList; + contingenciesList = ContingencyList.load(pathToContingencyJsonFile); - for (Contingency contingency : contingenciesList.getContingencies(network)) { - contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); + for (Contingency contingency : contingenciesList.getContingencies(network)) { + contingencies.add(new Contingency(contingency.getId(), contingency.getElements())); + } + } else { + throw new PowsyblException("File not found: " + pathToContingencyJsonFile); } + } for (Map.Entry> e : elementIdsByContingencyId.entrySet()) { diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 5356b972db..28540469d6 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -799,7 +799,7 @@ class RaoComputationStatus: def name(self) -> str: ... def add_contingency(analysis_context: JavaHandle, contingency_id: str, elements_ids: List[str]) -> None: ... -def add_contingency_from_json_file(analysis_context: JavaHandle, path_to_json_file: Path) -> None: ... +def add_contingency_from_json_file(analysis_context: JavaHandle, path_to_json_file: str) -> None: ... def add_monitored_elements(security_analysis_context: JavaHandle, contingency_context_type: ContingencyContextType, branch_ids: List[str], voltage_level_ids: List[str], three_windings_transformer_ids: List[str], contingency_ids: List[str]) -> None: ... def add_load_active_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, active_power: float) -> None: ... def add_load_reactive_power_action(security_analysis_context: JavaHandle, action_id: str, load_id: str, is_relative: bool, reactive_power: float) -> None: ... diff --git a/pypowsybl/security/impl/contingency_container.py b/pypowsybl/security/impl/contingency_container.py index b58e1b5092..1b99257d74 100644 --- a/pypowsybl/security/impl/contingency_container.py +++ b/pypowsybl/security/impl/contingency_container.py @@ -48,7 +48,7 @@ def add_single_element_contingencies(self, elements_ids: List[str], contingency_id = contingency_id_provider(element_id) if contingency_id_provider else element_id _pypowsybl.add_contingency(self._handle, contingency_id, [element_id]) - def add_contingencies_from_json_file(self, path_to_json_file: Path) -> None: + def add_contingencies_from_json_file(self, path_to_json_file: str) -> None: """ Add contingencies from JSON file. diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index 35af6bd852..a2fd3dea99 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -413,7 +413,7 @@ def test_add_contingencies_from_json_file(): n = pp.network.create_eurostag_tutorial_example1_network() sa = pp.security.create_analysis() str_path = "/contingencies.json" - sa.add_contingencies_from_json_file(Path(str_path)) + sa.add_contingencies_from_json_file(str_path) sa_result = sa.run_ac(n) assert 'contingency' in sa_result.post_contingency_results.keys() assert 'contingency2' in sa_result.post_contingency_results.keys() From 42177ab1560b528f6f21a3974019e82d16e64b2e Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 31 Mar 2025 13:08:54 +0200 Subject: [PATCH 28/71] successful test to add contingencies from json file Signed-off-by: Naledi EL CHEIKH --- tests/test_security_analysis.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index a2fd3dea99..a2ec7b56ef 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -11,7 +11,10 @@ import pypowsybl.report as rp from pypowsybl._pypowsybl import ConditionType import re -from pathlib import Path +import pathlib + +TEST_DIR = pathlib.Path(__file__).parent +DATA_DIR = TEST_DIR.parent.joinpath('data') @pytest.fixture(autouse=True) @@ -412,8 +415,7 @@ def test_tie_line_contingency(): def test_add_contingencies_from_json_file(): n = pp.network.create_eurostag_tutorial_example1_network() sa = pp.security.create_analysis() - str_path = "/contingencies.json" - sa.add_contingencies_from_json_file(str_path) + sa.add_contingencies_from_json_file(str(DATA_DIR.joinpath('contingencies.json'))) sa_result = sa.run_ac(n) assert 'contingency' in sa_result.post_contingency_results.keys() assert 'contingency2' in sa_result.post_contingency_results.keys() From ee6374a3ddefd685f622426ab1b4534d5a98899c Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 31 Mar 2025 13:55:27 +0200 Subject: [PATCH 29/71] data file contingencies.json Signed-off-by: Naledi EL CHEIKH --- data/contingencies.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 data/contingencies.json diff --git a/data/contingencies.json b/data/contingencies.json new file mode 100644 index 0000000000..fc6abd1d65 --- /dev/null +++ b/data/contingencies.json @@ -0,0 +1,21 @@ +{ + "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" + } ] + } ] +} \ No newline at end of file From c22c26854948b9be67918effc3ed7bbcc7c07ef8 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 2 Apr 2025 11:05:23 +0200 Subject: [PATCH 30/71] add impl and test of addActionFromJsonFile and addOperatorStrategyFromJsonFile Signed-off-by: Naledi EL CHEIKH --- .../security/SecurityAnalysisCFunctions.java | 20 ++ .../security/SecurityAnalysisContext.java | 24 +++ .../python/security/SecurityAnalysisTest.java | 26 +++ .../test/resources/ActionFileTestV1.0.json | 193 ++++++++++++++++++ .../OperatorStrategyFileTestV1.0.json | 75 +++++++ 5 files changed, 338 insertions(+) create mode 100644 java/pypowsybl/src/test/resources/ActionFileTestV1.0.json create mode 100644 java/pypowsybl/src/test/resources/OperatorStrategyFileTestV1.0.json diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index c8a1bbed64..0871890066 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -460,6 +460,17 @@ public static void addShuntCompensatorPositionAction(IsolateThread thread, Objec }); } + @CEntryPoint(name = "addActionFromJsonFile") + public static void addActionFromJsonFile(IsolateThread thread, ObjectHandle securityAnalysisContextHandle, + CCharPointer jsonFilePath, PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { + doCatch(exceptionHandlerPtr, () -> { + SecurityAnalysisContext analysisContext = ObjectHandles.getGlobal().get(securityAnalysisContextHandle); + String stringPath = CTypeUtil.toString(jsonFilePath); + Path path = Paths.get(stringPath); + analysisContext.addActionsFromJsonFile(path); + }); + } + @CEntryPoint(name = "addOperatorStrategy") public static void addOperatorStrategy(IsolateThread thread, ObjectHandle securityAnalysisContextHandle, CCharPointer operationStrategyId, CCharPointer contingencyId, @@ -482,6 +493,15 @@ public static void addOperatorStrategy(IsolateThread thread, ObjectHandle securi }); } + @CEntryPoint(name = "addOperatorStrategyFromJsonFile") + public static void addOperatorStrategyFromJsonFile(IsolateThread thread, ObjectHandle securityAnalysisContextHandle, + CCharPointer jsonFilePath, PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { + SecurityAnalysisContext analysisContext = ObjectHandles.getGlobal().get(securityAnalysisContextHandle); + String stringPath = CTypeUtil.toString(jsonFilePath); + Path path = Paths.get(stringPath); + analysisContext.addOperatorStrategiesFromJsonFile(path); + } + private static Condition buildCondition(PyPowsyblApiHeader.ConditionType conditionType, CCharPointerPointer subjectIds, int subjectIdsCount, CIntPointer violationTypes, int violationTypesCount) { diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisContext.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisContext.java index 4f7f2131fb..596e034b42 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisContext.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisContext.java @@ -8,6 +8,7 @@ package com.powsybl.python.security; import com.powsybl.action.Action; +import com.powsybl.action.ActionList; import com.powsybl.commons.report.ReportNode; import com.powsybl.contingency.ContingenciesProvider; import com.powsybl.iidm.network.Network; @@ -16,7 +17,10 @@ import com.powsybl.security.*; import com.powsybl.security.monitor.StateMonitor; import com.powsybl.security.strategy.OperatorStrategy; +import com.powsybl.security.strategy.OperatorStrategyList; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -45,6 +49,26 @@ SecurityAnalysisResult run(Network network, SecurityAnalysisParameters securityA return report.getResult(); } + void addActionsFromJsonFile(Path path) { + if (Files.exists(path)) { + ActionList actionList; + actionList = ActionList.readJsonFile(path); + actions.addAll(actionList.getActions()); + } else { + throw new SecurityException("No actions found in " + path); + } + } + + void addOperatorStrategiesFromJsonFile(Path path) { + if (Files.exists(path)) { + OperatorStrategyList operatorStrategyList; + operatorStrategyList = OperatorStrategyList.read(path); + operatorStrategies.addAll(operatorStrategyList.getOperatorStrategies()); + } else { + throw new SecurityException("No operatorStrategies found in " + path); + } + } + void addAction(Action action) { actions.add(action); } diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index 773599868a..9ce6fc8306 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -7,6 +7,10 @@ */ package com.powsybl.python.security; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import com.powsybl.action.Action; +import com.powsybl.action.ActionList; import com.powsybl.commons.report.ReportNode; import com.powsybl.contingency.ContingencyContext; import com.powsybl.contingency.ContingencyContextType; @@ -23,16 +27,25 @@ import org.assertj.core.data.Offset; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.atomicReferenceArray; +import static org.ejml.EjmlUnitTests.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; /** * @author Etienne Lesot {@literal } */ class SecurityAnalysisTest { + protected FileSystem fileSystem; + @Test void testStateMonitors() { SecurityAnalysisContext analysisContext = new SecurityAnalysisContext(); @@ -80,4 +93,17 @@ void testSecurityAnalysis() { Assertions.assertThat(series.get(1).getStrings()) .containsExactly("NHV1_NHV2_2", "VLHV1"); } + + @Test + void testToAddActionsAndOperatorStrategiesFromJsonFile() throws IOException { + SecurityAnalysisContext analysisContext = new SecurityAnalysisContext(); + Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); + fileSystem = Jimfs.newFileSystem(Configuration.unix()); + + Files.copy(getClass().getResourceAsStream("/ActionFileTestV1.0.json"), fileSystem.getPath("/ActionFileTestV1.0.json")); + analysisContext.addActionsFromJsonFile(fileSystem.getPath("/ActionFileTestV1.0.json")); + + Files.copy(getClass().getResourceAsStream("/OperatorStrategyFileTestV1.0.json"), fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); + analysisContext.addActionsFromJsonFile(fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); + } } diff --git a/java/pypowsybl/src/test/resources/ActionFileTestV1.0.json b/java/pypowsybl/src/test/resources/ActionFileTestV1.0.json new file mode 100644 index 0000000000..856876582a --- /dev/null +++ b/java/pypowsybl/src/test/resources/ActionFileTestV1.0.json @@ -0,0 +1,193 @@ +{ + "version" : "1.0", + "actions" : [ { + "type" : "SWITCH", + "id" : "id1", + "switchId" : "switchId1", + "open" : true + }, { + "type" : "MULTIPLE_ACTIONS", + "id" : "id2", + "actions" : [ { + "type" : "SWITCH", + "id" : "id3", + "switchId" : "switchId2", + "open" : true + } ] + }, { + "type" : "TERMINALS_CONNECTION", + "id" : "id3", + "elementId" : "lineId3", + "open" : true + }, { + "type" : "TERMINALS_CONNECTION", + "id" : "id4", + "elementId" : "lineId4", + "open" : false + }, { + "type" : "PHASE_TAP_CHANGER_TAP_POSITION", + "id" : "id5", + "transformerId" : "transformerId1", + "value" : 5, + "relativeValue" : true, + "side" : "TWO" + }, { + "type" : "PHASE_TAP_CHANGER_TAP_POSITION", + "id" : "id6", + "transformerId" : "transformerId2", + "value" : 12, + "relativeValue" : false + }, { + "type" : "PHASE_TAP_CHANGER_TAP_POSITION", + "id" : "id7", + "transformerId" : "transformerId3", + "value" : -5, + "relativeValue" : true, + "side" : "ONE" + }, { + "type" : "PHASE_TAP_CHANGER_TAP_POSITION", + "id" : "id8", + "transformerId" : "transformerId3", + "value" : 2, + "relativeValue" : false, + "side" : "THREE" + }, { + "type" : "GENERATOR", + "id" : "id9", + "generatorId" : "generatorId1", + "activePowerRelativeValue" : true, + "activePowerValue" : 100.0 + }, { + "type" : "GENERATOR", + "id" : "id10", + "generatorId" : "generatorId2", + "voltageRegulatorOn" : true, + "targetV" : 225.0 + }, { + "type" : "GENERATOR", + "id" : "id11", + "generatorId" : "generatorId2", + "voltageRegulatorOn" : false, + "targetQ" : 400.0 + }, { + "type" : "LOAD", + "id" : "id12", + "loadId" : "loadId1", + "relativeValue" : false, + "activePowerValue" : 50.0 + }, { + "type" : "LOAD", + "id" : "id13", + "loadId" : "loadId1", + "relativeValue" : true, + "reactivePowerValue" : 5.0 + }, { + "type" : "DANGLING_LINE", + "id" : "id17", + "danglingLineId" : "dlId1", + "relativeValue" : true, + "reactivePowerValue" : 5.0 + }, { + "type" : "RATIO_TAP_CHANGER_TAP_POSITION", + "id" : "id14", + "transformerId" : "transformerId4", + "value" : 2, + "relativeValue" : false, + "side" : "THREE" + }, { + "type" : "RATIO_TAP_CHANGER_TAP_POSITION", + "id" : "id15", + "transformerId" : "transformerId5", + "value" : 1, + "relativeValue" : true + }, { + "type" : "RATIO_TAP_CHANGER_REGULATION", + "id" : "id16", + "transformerId" : "transformerId5", + "regulating" : true, + "side" : "THREE" + }, { + "type" : "PHASE_TAP_CHANGER_REGULATION", + "id" : "id17", + "transformerId" : "transformerId5", + "regulating" : true, + "side" : "ONE", + "regulationMode" : "ACTIVE_POWER_CONTROL", + "regulationValue" : 10.0 + }, { + "type" : "PHASE_TAP_CHANGER_REGULATION", + "id" : "id18", + "transformerId" : "transformerId6", + "regulating" : false, + "side" : "ONE" + }, { + "type" : "PHASE_TAP_CHANGER_REGULATION", + "id" : "id19", + "transformerId" : "transformerId6", + "regulating" : true, + "side" : "ONE", + "regulationMode" : "ACTIVE_POWER_CONTROL", + "regulationValue" : 15.0 + }, { + "type" : "RATIO_TAP_CHANGER_REGULATION", + "id" : "id20", + "transformerId" : "transformerId5", + "regulating" : true, + "targetV" : 90.0 + }, { + "type" : "RATIO_TAP_CHANGER_REGULATION", + "id" : "id21", + "transformerId" : "transformerId5", + "regulating" : false, + "side" : "THREE" + }, { + "type" : "HVDC", + "id" : "id22", + "hvdcId" : "hvdc1", + "acEmulationEnabled" : false + }, { + "type" : "HVDC", + "id" : "id23", + "hvdcId" : "hvdc2", + "acEmulationEnabled" : true + }, { + "type" : "HVDC", + "id" : "id24", + "hvdcId" : "hvdc2", + "acEmulationEnabled" : true, + "converterMode" : "SIDE_1_RECTIFIER_SIDE_2_INVERTER", + "droop" : 121.0, + "p0" : 42.0, + "relativeValue" : false + }, { + "type" : "HVDC", + "id" : "id25", + "hvdcId" : "hvdc1", + "acEmulationEnabled" : false, + "activePowerSetpoint" : 12.0, + "relativeValue" : true + }, { + "type" : "SHUNT_COMPENSATOR_POSITION", + "id" : "id22", + "shuntCompensatorId" : "shuntId1", + "sectionCount" : 5 + }, { + "type" : "STATIC_VAR_COMPENSATOR", + "id" : "id23", + "staticVarCompensatorId" : "svc", + "regulationMode" : "VOLTAGE", + "voltageSetpoint" : 56.0 + }, { + "type" : "STATIC_VAR_COMPENSATOR", + "id" : "id24", + "staticVarCompensatorId" : "svc", + "regulationMode" : "REACTIVE_POWER", + "reactivePowerSetpoint" : 120.0 + }, { + "type" : "TERMINALS_CONNECTION", + "id" : "id4", + "elementId" : "transformerId25", + "side" : "THREE", + "open" : true + } ] +} \ No newline at end of file diff --git a/java/pypowsybl/src/test/resources/OperatorStrategyFileTestV1.0.json b/java/pypowsybl/src/test/resources/OperatorStrategyFileTestV1.0.json new file mode 100644 index 0000000000..88be6ac884 --- /dev/null +++ b/java/pypowsybl/src/test/resources/OperatorStrategyFileTestV1.0.json @@ -0,0 +1,75 @@ +{ + "version" : "1.1", + "operatorStrategies" : [ { + "id" : "id1", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId1", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "TRUE_CONDITION" + }, + "actionIds" : [ "actionId1", "actionId2", "actionId3" ] + } ] + }, { + "id" : "id2", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId2", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ANY_VIOLATION_CONDITION" + }, + "actionIds" : [ "actionId4" ] + } ] + }, { + "id" : "id3", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId1", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ANY_VIOLATION_CONDITION", + "filters" : [ "CURRENT" ] + }, + "actionIds" : [ "actionId1", "actionId3" ] + } ] + }, { + "id" : "id4", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId3", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ANY_VIOLATION_CONDITION", + "filters" : [ "LOW_VOLTAGE" ] + }, + "actionIds" : [ "actionId1", "actionId2", "actionId4" ] + } ] + }, { + "id" : "id5", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId4", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ALL_VIOLATION", + "filters" : [ "HIGH_VOLTAGE" ], + "violationIds" : [ "violation1", "violation2" ] + }, + "actionIds" : [ "actionId1", "actionId5" ] + } ] + }, { + "id" : "id6", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId5", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ALL_VIOLATION", + "violationIds" : [ "violation1", "violation2" ] + }, + "actionIds" : [ "actionId3" ] + } ] + } ] +} \ No newline at end of file From 6797cba9e082bed436c5e2d597a2959a6b52149a Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 2 Apr 2025 11:19:38 +0200 Subject: [PATCH 31/71] fix typo Signed-off-by: Naledi EL CHEIKH --- .../com/powsybl/python/security/SecurityAnalysisTest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index 9ce6fc8306..d536c0b629 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -9,8 +9,6 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; -import com.powsybl.action.Action; -import com.powsybl.action.ActionList; import com.powsybl.commons.report.ReportNode; import com.powsybl.contingency.ContingencyContext; import com.powsybl.contingency.ContingencyContextType; @@ -34,10 +32,6 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.atomicReferenceArray; -import static org.ejml.EjmlUnitTests.assertEquals; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertFalse; /** * @author Etienne Lesot {@literal } @@ -104,6 +98,6 @@ void testToAddActionsAndOperatorStrategiesFromJsonFile() throws IOException { analysisContext.addActionsFromJsonFile(fileSystem.getPath("/ActionFileTestV1.0.json")); Files.copy(getClass().getResourceAsStream("/OperatorStrategyFileTestV1.0.json"), fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); - analysisContext.addActionsFromJsonFile(fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); + analysisContext.addOperatorStrategiesFromJsonFile(fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); } } From 9893a56865751406009dba98d117960f3dd1d710 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 2 Apr 2025 15:41:57 +0200 Subject: [PATCH 32/71] python impl of both function Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 9 + cpp/powsybl-cpp/powsybl-cpp.h | 4 + cpp/pypowsybl-cpp/bindings.cpp | 6 + data/ActionFileTestV1.0.json | 193 ++++++++++++++++++ data/OperatorStrategyFileTestV1.0.json | 75 +++++++ .../security/SecurityAnalysisCFunctions.java | 4 +- .../security/SecurityAnalysisContext.java | 4 +- .../python/security/SecurityAnalysisTest.java | 4 +- pypowsybl/_pypowsybl.pyi | 3 +- pypowsybl/security/impl/security.py | 20 ++ 10 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 data/ActionFileTestV1.0.json create mode 100644 data/OperatorStrategyFileTestV1.0.json diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index 4c2b702986..63e91dc392 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -817,6 +817,15 @@ void addOperatorStrategy(const JavaHandle& analysisContext, std::string operator conditionType, subjectIdsPtr.get(), subjectIds.size(), violationTypesPtr.get(), violationTypesFilters.size()); } +void addActionFromJsonFile(const JavaHandle& analysisContext, const std::string jsonFilePath) { + PowsyblCaller::get()->callJava(::addActionFromJsonFile, analysisContext, (char*) jsonFilePath.data()); +} + +void addOperatorStrategyFromJsonFile(const JavaHandle& analysisContext, const std::string jsonFilePath) { + PowsyblCaller::get()->callJava(::addOperatorStrategyFromJsonFile, analysisContext, (char*) jsonFilePath.data()); +} + + ::zone* createZone(const std::string& id, const std::vector& injectionsIds, const std::vector& injectionsShiftKeys) { auto z = new ::zone; z->id = copyStringToCharPtr(id); diff --git a/cpp/powsybl-cpp/powsybl-cpp.h b/cpp/powsybl-cpp/powsybl-cpp.h index c2bd5e4dfb..6476ab0f70 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.h +++ b/cpp/powsybl-cpp/powsybl-cpp.h @@ -614,6 +614,10 @@ void addShuntCompensatorPositionAction(const JavaHandle& analysisContext, const void addOperatorStrategy(const JavaHandle& analysisContext, std::string operatorStrategyId, std::string contingencyId, const std::vector& actionsIds, condition_type conditionType, const std::vector& subjectIds, const std::vector& violationTypesFilters); +void addActionFromJsonFile(const JavaHandle& analysisContext, const std::string& jsonFilePath); + +void addOperatorStrategyFromJsonFile(const JavaHandle& analysisContext, const std::string& jsonFilePath); + void setZones(const JavaHandle& sensitivityAnalysisContext, const std::vector<::zone*>& zones); void addFactorMatrix(const JavaHandle& sensitivityAnalysisContext, std::string matrixId, const std::vector& branchesIds, diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index 21f0bc7cd0..1934eb2178 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -729,6 +729,12 @@ PYBIND11_MODULE(_pypowsybl, m) { 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")); + m.def("add_action_from_json_file", &pypowsybl::addActionFromJsonFile, "Add actions from JSON file.", + py::arg("analysis_context"), py::arg("path_to_json_file")); + + m.def("add_operator_strategy_from_json_file", &pypowsybl::addOperatorStrategyFromJsonFile, "Add operator strategies from JSON file.", + py::arg("analysis_context"), py::arg("path_to_json_file")); + py::enum_(m, "LimitType") .value("ACTIVE_POWER", pypowsybl::LimitType::ACTIVE_POWER) .value("APPARENT_POWER", pypowsybl::LimitType::APPARENT_POWER) diff --git a/data/ActionFileTestV1.0.json b/data/ActionFileTestV1.0.json new file mode 100644 index 0000000000..856876582a --- /dev/null +++ b/data/ActionFileTestV1.0.json @@ -0,0 +1,193 @@ +{ + "version" : "1.0", + "actions" : [ { + "type" : "SWITCH", + "id" : "id1", + "switchId" : "switchId1", + "open" : true + }, { + "type" : "MULTIPLE_ACTIONS", + "id" : "id2", + "actions" : [ { + "type" : "SWITCH", + "id" : "id3", + "switchId" : "switchId2", + "open" : true + } ] + }, { + "type" : "TERMINALS_CONNECTION", + "id" : "id3", + "elementId" : "lineId3", + "open" : true + }, { + "type" : "TERMINALS_CONNECTION", + "id" : "id4", + "elementId" : "lineId4", + "open" : false + }, { + "type" : "PHASE_TAP_CHANGER_TAP_POSITION", + "id" : "id5", + "transformerId" : "transformerId1", + "value" : 5, + "relativeValue" : true, + "side" : "TWO" + }, { + "type" : "PHASE_TAP_CHANGER_TAP_POSITION", + "id" : "id6", + "transformerId" : "transformerId2", + "value" : 12, + "relativeValue" : false + }, { + "type" : "PHASE_TAP_CHANGER_TAP_POSITION", + "id" : "id7", + "transformerId" : "transformerId3", + "value" : -5, + "relativeValue" : true, + "side" : "ONE" + }, { + "type" : "PHASE_TAP_CHANGER_TAP_POSITION", + "id" : "id8", + "transformerId" : "transformerId3", + "value" : 2, + "relativeValue" : false, + "side" : "THREE" + }, { + "type" : "GENERATOR", + "id" : "id9", + "generatorId" : "generatorId1", + "activePowerRelativeValue" : true, + "activePowerValue" : 100.0 + }, { + "type" : "GENERATOR", + "id" : "id10", + "generatorId" : "generatorId2", + "voltageRegulatorOn" : true, + "targetV" : 225.0 + }, { + "type" : "GENERATOR", + "id" : "id11", + "generatorId" : "generatorId2", + "voltageRegulatorOn" : false, + "targetQ" : 400.0 + }, { + "type" : "LOAD", + "id" : "id12", + "loadId" : "loadId1", + "relativeValue" : false, + "activePowerValue" : 50.0 + }, { + "type" : "LOAD", + "id" : "id13", + "loadId" : "loadId1", + "relativeValue" : true, + "reactivePowerValue" : 5.0 + }, { + "type" : "DANGLING_LINE", + "id" : "id17", + "danglingLineId" : "dlId1", + "relativeValue" : true, + "reactivePowerValue" : 5.0 + }, { + "type" : "RATIO_TAP_CHANGER_TAP_POSITION", + "id" : "id14", + "transformerId" : "transformerId4", + "value" : 2, + "relativeValue" : false, + "side" : "THREE" + }, { + "type" : "RATIO_TAP_CHANGER_TAP_POSITION", + "id" : "id15", + "transformerId" : "transformerId5", + "value" : 1, + "relativeValue" : true + }, { + "type" : "RATIO_TAP_CHANGER_REGULATION", + "id" : "id16", + "transformerId" : "transformerId5", + "regulating" : true, + "side" : "THREE" + }, { + "type" : "PHASE_TAP_CHANGER_REGULATION", + "id" : "id17", + "transformerId" : "transformerId5", + "regulating" : true, + "side" : "ONE", + "regulationMode" : "ACTIVE_POWER_CONTROL", + "regulationValue" : 10.0 + }, { + "type" : "PHASE_TAP_CHANGER_REGULATION", + "id" : "id18", + "transformerId" : "transformerId6", + "regulating" : false, + "side" : "ONE" + }, { + "type" : "PHASE_TAP_CHANGER_REGULATION", + "id" : "id19", + "transformerId" : "transformerId6", + "regulating" : true, + "side" : "ONE", + "regulationMode" : "ACTIVE_POWER_CONTROL", + "regulationValue" : 15.0 + }, { + "type" : "RATIO_TAP_CHANGER_REGULATION", + "id" : "id20", + "transformerId" : "transformerId5", + "regulating" : true, + "targetV" : 90.0 + }, { + "type" : "RATIO_TAP_CHANGER_REGULATION", + "id" : "id21", + "transformerId" : "transformerId5", + "regulating" : false, + "side" : "THREE" + }, { + "type" : "HVDC", + "id" : "id22", + "hvdcId" : "hvdc1", + "acEmulationEnabled" : false + }, { + "type" : "HVDC", + "id" : "id23", + "hvdcId" : "hvdc2", + "acEmulationEnabled" : true + }, { + "type" : "HVDC", + "id" : "id24", + "hvdcId" : "hvdc2", + "acEmulationEnabled" : true, + "converterMode" : "SIDE_1_RECTIFIER_SIDE_2_INVERTER", + "droop" : 121.0, + "p0" : 42.0, + "relativeValue" : false + }, { + "type" : "HVDC", + "id" : "id25", + "hvdcId" : "hvdc1", + "acEmulationEnabled" : false, + "activePowerSetpoint" : 12.0, + "relativeValue" : true + }, { + "type" : "SHUNT_COMPENSATOR_POSITION", + "id" : "id22", + "shuntCompensatorId" : "shuntId1", + "sectionCount" : 5 + }, { + "type" : "STATIC_VAR_COMPENSATOR", + "id" : "id23", + "staticVarCompensatorId" : "svc", + "regulationMode" : "VOLTAGE", + "voltageSetpoint" : 56.0 + }, { + "type" : "STATIC_VAR_COMPENSATOR", + "id" : "id24", + "staticVarCompensatorId" : "svc", + "regulationMode" : "REACTIVE_POWER", + "reactivePowerSetpoint" : 120.0 + }, { + "type" : "TERMINALS_CONNECTION", + "id" : "id4", + "elementId" : "transformerId25", + "side" : "THREE", + "open" : true + } ] +} \ No newline at end of file diff --git a/data/OperatorStrategyFileTestV1.0.json b/data/OperatorStrategyFileTestV1.0.json new file mode 100644 index 0000000000..88be6ac884 --- /dev/null +++ b/data/OperatorStrategyFileTestV1.0.json @@ -0,0 +1,75 @@ +{ + "version" : "1.1", + "operatorStrategies" : [ { + "id" : "id1", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId1", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "TRUE_CONDITION" + }, + "actionIds" : [ "actionId1", "actionId2", "actionId3" ] + } ] + }, { + "id" : "id2", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId2", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ANY_VIOLATION_CONDITION" + }, + "actionIds" : [ "actionId4" ] + } ] + }, { + "id" : "id3", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId1", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ANY_VIOLATION_CONDITION", + "filters" : [ "CURRENT" ] + }, + "actionIds" : [ "actionId1", "actionId3" ] + } ] + }, { + "id" : "id4", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId3", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ANY_VIOLATION_CONDITION", + "filters" : [ "LOW_VOLTAGE" ] + }, + "actionIds" : [ "actionId1", "actionId2", "actionId4" ] + } ] + }, { + "id" : "id5", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId4", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ALL_VIOLATION", + "filters" : [ "HIGH_VOLTAGE" ], + "violationIds" : [ "violation1", "violation2" ] + }, + "actionIds" : [ "actionId1", "actionId5" ] + } ] + }, { + "id" : "id6", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId5", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ALL_VIOLATION", + "violationIds" : [ "violation1", "violation2" ] + }, + "actionIds" : [ "actionId3" ] + } ] + } ] +} \ No newline at end of file diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index 0871890066..0323112a2a 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -467,7 +467,7 @@ public static void addActionFromJsonFile(IsolateThread thread, ObjectHandle secu SecurityAnalysisContext analysisContext = ObjectHandles.getGlobal().get(securityAnalysisContextHandle); String stringPath = CTypeUtil.toString(jsonFilePath); Path path = Paths.get(stringPath); - analysisContext.addActionsFromJsonFile(path); + analysisContext.addActionFromJsonFile(path); }); } @@ -499,7 +499,7 @@ public static void addOperatorStrategyFromJsonFile(IsolateThread thread, ObjectH SecurityAnalysisContext analysisContext = ObjectHandles.getGlobal().get(securityAnalysisContextHandle); String stringPath = CTypeUtil.toString(jsonFilePath); Path path = Paths.get(stringPath); - analysisContext.addOperatorStrategiesFromJsonFile(path); + analysisContext.addOperatorStrategyFromJsonFile(path); } private static Condition buildCondition(PyPowsyblApiHeader.ConditionType conditionType, diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisContext.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisContext.java index 596e034b42..def3055199 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisContext.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisContext.java @@ -49,7 +49,7 @@ SecurityAnalysisResult run(Network network, SecurityAnalysisParameters securityA return report.getResult(); } - void addActionsFromJsonFile(Path path) { + void addActionFromJsonFile(Path path) { if (Files.exists(path)) { ActionList actionList; actionList = ActionList.readJsonFile(path); @@ -59,7 +59,7 @@ void addActionsFromJsonFile(Path path) { } } - void addOperatorStrategiesFromJsonFile(Path path) { + void addOperatorStrategyFromJsonFile(Path path) { if (Files.exists(path)) { OperatorStrategyList operatorStrategyList; operatorStrategyList = OperatorStrategyList.read(path); diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index d536c0b629..e74d55d8e0 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -95,9 +95,9 @@ void testToAddActionsAndOperatorStrategiesFromJsonFile() throws IOException { fileSystem = Jimfs.newFileSystem(Configuration.unix()); Files.copy(getClass().getResourceAsStream("/ActionFileTestV1.0.json"), fileSystem.getPath("/ActionFileTestV1.0.json")); - analysisContext.addActionsFromJsonFile(fileSystem.getPath("/ActionFileTestV1.0.json")); + analysisContext.addActionFromJsonFile(fileSystem.getPath("/ActionFileTestV1.0.json")); Files.copy(getClass().getResourceAsStream("/OperatorStrategyFileTestV1.0.json"), fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); - analysisContext.addOperatorStrategiesFromJsonFile(fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); + analysisContext.addOperatorStrategyFromJsonFile(fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); } } diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 28540469d6..16c995c8b9 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -809,6 +809,8 @@ def add_phase_tap_changer_position_action(security_analysis_context: JavaHandle, def add_ratio_tap_changer_position_action(security_analysis_context: JavaHandle, action_id: str, transformer_id: str, is_relative: bool, tap_position: int, side: Side) -> None: ... def add_shunt_compensator_position_action(security_analysis_context: JavaHandle, action_id: str, shunt_id: str, section: int) -> None: ... def add_operator_strategy(security_analysis_context: JavaHandle, operator_strategy_id: str, contingency_id: str, action_ids: List[str], condition_type: ConditionType, violation_subject_ids: List[str], violation_types: List[ViolationType]) -> None: ... +def add_action_from_json_file(security_analysis_context: JavaHandle, path_to_json_file: str) -> None: ... +def add_operator_strategy_from_json_file(security_analysis_context: JavaHandle, path_to_json_file: str) -> None: ... def clone_variant(network: JavaHandle, src: str, variant: str, may_overwrite: bool) -> None: ... def create_dataframe(columns_values: list, columns_names: List[str], columns_types: List[int], is_index: List[bool]) -> Dataframe: ... def create_element(network: JavaHandle, dataframes: List[Optional[Dataframe]], element_type: ElementType) -> None: ... @@ -1199,4 +1201,3 @@ def update_grid2op_integer_value(backend: JavaHandle, value_type: Grid2opUpdateI def check_grid2op_isolated_and_disconnected_injections(backend: JavaHandle) -> bool: ... def run_grid2op_loadflow(backend: JavaHandle, dc: bool, parameters: LoadFlowParameters) -> LoadFlowComponentResultArray: ... - diff --git a/pypowsybl/security/impl/security.py b/pypowsybl/security/impl/security.py index f32c8f76bc..9f56a23981 100644 --- a/pypowsybl/security/impl/security.py +++ b/pypowsybl/security/impl/security.py @@ -246,3 +246,23 @@ def add_operator_strategy(self, operator_strategy_id: str, contingency_id: str, if violation_subject_ids is None: violation_subject_ids = [] _pypowsybl.add_operator_strategy(self._handle, operator_strategy_id, contingency_id, action_ids, condition_type, violation_subject_ids, violation_types) + + def add_actions_from_json_file(self, path_to_json_file: str) -> None: + """ + Add any kinds of actions by reading them from a JSON file. + + Args: + path_to_json_file: the path to the JSON file in which we extract the actions' data. + """ + + _pypowsybl.add_action_from_json_file(self._handle, path_to_json_file) + + def add_operator_strategies_from_json_file(self, path_to_json_file: str) -> None: + """ + Add operator strategies by reading them from a JSON file. + + Args: + path_to_json_file: the path to the JSON file in which we extract the operator strategies' data. + """ + + _pypowsybl.add_operator_strategy_from_json_file(self._handle, path_to_json_file) \ No newline at end of file From 3d72bd8cb216828764cd04e2d9c8f0c1087da032 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 3 Apr 2025 10:43:54 +0200 Subject: [PATCH 33/71] python tests Signed-off-by: Naledi EL CHEIKH --- tests/test_security_analysis.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index a2ec7b56ef..6bf90318bf 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -420,4 +420,13 @@ def test_add_contingencies_from_json_file(): assert 'contingency' in sa_result.post_contingency_results.keys() assert 'contingency2' in sa_result.post_contingency_results.keys() +def test_add_actions_from_json_file(): + n = pp.network.create_eurostag_tutorial_example1_network() + sa = pp.security.create_analysis() + sa.add_actions_from_json_file(str(DATA_DIR.joinpath('ActionFileTestV1.0.json'))) + sa_result = sa.run_ac(n) + df = sa_result.branch_results + assert 'MULTIPLE_ACTIONS' in df.keys() + assert 'PHASE_TAP_CHANGER_TAP_POSITION' in df.keys() + From e9ad5931d9419968c8e808c42cfa74cc3aeba152 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 3 Apr 2025 13:50:52 +0200 Subject: [PATCH 34/71] fix powsybl-cpp method Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 4 ++-- .../com/powsybl/python/security/SecurityAnalysisTest.java | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index 63e91dc392..1d5bcbd1d2 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -817,11 +817,11 @@ void addOperatorStrategy(const JavaHandle& analysisContext, std::string operator conditionType, subjectIdsPtr.get(), subjectIds.size(), violationTypesPtr.get(), violationTypesFilters.size()); } -void addActionFromJsonFile(const JavaHandle& analysisContext, const std::string jsonFilePath) { +void addActionFromJsonFile(const JavaHandle& analysisContext, const std::string& jsonFilePath) { PowsyblCaller::get()->callJava(::addActionFromJsonFile, analysisContext, (char*) jsonFilePath.data()); } -void addOperatorStrategyFromJsonFile(const JavaHandle& analysisContext, const std::string jsonFilePath) { +void addOperatorStrategyFromJsonFile(const JavaHandle& analysisContext, const std::string& jsonFilePath) { PowsyblCaller::get()->callJava(::addOperatorStrategyFromJsonFile, analysisContext, (char*) jsonFilePath.data()); } diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index e74d55d8e0..f5a20d057a 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -91,7 +91,6 @@ void testSecurityAnalysis() { @Test void testToAddActionsAndOperatorStrategiesFromJsonFile() throws IOException { SecurityAnalysisContext analysisContext = new SecurityAnalysisContext(); - Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); fileSystem = Jimfs.newFileSystem(Configuration.unix()); Files.copy(getClass().getResourceAsStream("/ActionFileTestV1.0.json"), fileSystem.getPath("/ActionFileTestV1.0.json")); From 8b58fe856c8773a706332312f30ffede9fe0b736 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 3 Apr 2025 15:02:33 +0200 Subject: [PATCH 35/71] clean unused imports Signed-off-by: Naledi EL CHEIKH --- .gitignore | 3 --- java/pypowsybl/pom.xml | 5 ----- pypowsybl/_pypowsybl.pyi | 4 ---- pypowsybl/security/impl/contingency_container.py | 1 - 4 files changed, 13 deletions(-) diff --git a/.gitignore b/.gitignore index d493fc0cea..3a4b6e9e32 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,3 @@ build/* tests/_trial_temp/* *.orig - -# Documentation generated files (autosummary) -docs/reference/api/ diff --git a/java/pypowsybl/pom.xml b/java/pypowsybl/pom.xml index 0725db8609..f86a13285c 100644 --- a/java/pypowsybl/pom.xml +++ b/java/pypowsybl/pom.xml @@ -410,10 +410,5 @@ open-rao-rao-result-json ${powsybl-open-rao.version} - - org.json - json - 20190722 - diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 16c995c8b9..c9aa084c55 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -1,11 +1,7 @@ from logging import Logger -from pathlib import Path from typing import ClassVar, Dict, Iterator, List, Sequence, Optional, Union - from numpy import ndarray -from pypowsybl.network import Network - class ArrayStruct: def __init__(self) -> None: ... diff --git a/pypowsybl/security/impl/contingency_container.py b/pypowsybl/security/impl/contingency_container.py index 1b99257d74..fcbca4c5e4 100644 --- a/pypowsybl/security/impl/contingency_container.py +++ b/pypowsybl/security/impl/contingency_container.py @@ -4,7 +4,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # SPDX-License-Identifier: MPL-2.0 # -from pathlib import Path from typing import List, Callable from pypowsybl import _pypowsybl From 442a81372ad30386a84a9b21b3bba73eaba9618a Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 3 Apr 2025 16:51:02 +0200 Subject: [PATCH 36/71] changing network Signed-off-by: Naledi EL CHEIKH --- tests/test_security_analysis.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index 6bf90318bf..9662f8b05a 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -421,12 +421,10 @@ def test_add_contingencies_from_json_file(): assert 'contingency2' in sa_result.post_contingency_results.keys() def test_add_actions_from_json_file(): - n = pp.network.create_eurostag_tutorial_example1_network() + n = pp.network.create_four_substations_node_breaker_network() sa = pp.security.create_analysis() sa.add_actions_from_json_file(str(DATA_DIR.joinpath('ActionFileTestV1.0.json'))) sa_result = sa.run_ac(n) df = sa_result.branch_results assert 'MULTIPLE_ACTIONS' in df.keys() assert 'PHASE_TAP_CHANGER_TAP_POSITION' in df.keys() - - From ab44563ebdacfd4424602f1c22afae1b8355fe6a Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 7 Apr 2025 13:28:08 +0200 Subject: [PATCH 37/71] add tests for operator strategies and documentation Signed-off-by: Naledi EL CHEIKH --- data/ActionFileTestV1.0.json | 177 +------------------------ data/OperatorStrategyFileTestV1.0.json | 49 ------- docs/user_guide/security.rst | 90 ++++++++++++- tests/test_security_analysis.py | 8 ++ 4 files changed, 98 insertions(+), 226 deletions(-) diff --git a/data/ActionFileTestV1.0.json b/data/ActionFileTestV1.0.json index 856876582a..bab7fc38de 100644 --- a/data/ActionFileTestV1.0.json +++ b/data/ActionFileTestV1.0.json @@ -14,180 +14,5 @@ "switchId" : "switchId2", "open" : true } ] - }, { - "type" : "TERMINALS_CONNECTION", - "id" : "id3", - "elementId" : "lineId3", - "open" : true - }, { - "type" : "TERMINALS_CONNECTION", - "id" : "id4", - "elementId" : "lineId4", - "open" : false - }, { - "type" : "PHASE_TAP_CHANGER_TAP_POSITION", - "id" : "id5", - "transformerId" : "transformerId1", - "value" : 5, - "relativeValue" : true, - "side" : "TWO" - }, { - "type" : "PHASE_TAP_CHANGER_TAP_POSITION", - "id" : "id6", - "transformerId" : "transformerId2", - "value" : 12, - "relativeValue" : false - }, { - "type" : "PHASE_TAP_CHANGER_TAP_POSITION", - "id" : "id7", - "transformerId" : "transformerId3", - "value" : -5, - "relativeValue" : true, - "side" : "ONE" - }, { - "type" : "PHASE_TAP_CHANGER_TAP_POSITION", - "id" : "id8", - "transformerId" : "transformerId3", - "value" : 2, - "relativeValue" : false, - "side" : "THREE" - }, { - "type" : "GENERATOR", - "id" : "id9", - "generatorId" : "generatorId1", - "activePowerRelativeValue" : true, - "activePowerValue" : 100.0 - }, { - "type" : "GENERATOR", - "id" : "id10", - "generatorId" : "generatorId2", - "voltageRegulatorOn" : true, - "targetV" : 225.0 - }, { - "type" : "GENERATOR", - "id" : "id11", - "generatorId" : "generatorId2", - "voltageRegulatorOn" : false, - "targetQ" : 400.0 - }, { - "type" : "LOAD", - "id" : "id12", - "loadId" : "loadId1", - "relativeValue" : false, - "activePowerValue" : 50.0 - }, { - "type" : "LOAD", - "id" : "id13", - "loadId" : "loadId1", - "relativeValue" : true, - "reactivePowerValue" : 5.0 - }, { - "type" : "DANGLING_LINE", - "id" : "id17", - "danglingLineId" : "dlId1", - "relativeValue" : true, - "reactivePowerValue" : 5.0 - }, { - "type" : "RATIO_TAP_CHANGER_TAP_POSITION", - "id" : "id14", - "transformerId" : "transformerId4", - "value" : 2, - "relativeValue" : false, - "side" : "THREE" - }, { - "type" : "RATIO_TAP_CHANGER_TAP_POSITION", - "id" : "id15", - "transformerId" : "transformerId5", - "value" : 1, - "relativeValue" : true - }, { - "type" : "RATIO_TAP_CHANGER_REGULATION", - "id" : "id16", - "transformerId" : "transformerId5", - "regulating" : true, - "side" : "THREE" - }, { - "type" : "PHASE_TAP_CHANGER_REGULATION", - "id" : "id17", - "transformerId" : "transformerId5", - "regulating" : true, - "side" : "ONE", - "regulationMode" : "ACTIVE_POWER_CONTROL", - "regulationValue" : 10.0 - }, { - "type" : "PHASE_TAP_CHANGER_REGULATION", - "id" : "id18", - "transformerId" : "transformerId6", - "regulating" : false, - "side" : "ONE" - }, { - "type" : "PHASE_TAP_CHANGER_REGULATION", - "id" : "id19", - "transformerId" : "transformerId6", - "regulating" : true, - "side" : "ONE", - "regulationMode" : "ACTIVE_POWER_CONTROL", - "regulationValue" : 15.0 - }, { - "type" : "RATIO_TAP_CHANGER_REGULATION", - "id" : "id20", - "transformerId" : "transformerId5", - "regulating" : true, - "targetV" : 90.0 - }, { - "type" : "RATIO_TAP_CHANGER_REGULATION", - "id" : "id21", - "transformerId" : "transformerId5", - "regulating" : false, - "side" : "THREE" - }, { - "type" : "HVDC", - "id" : "id22", - "hvdcId" : "hvdc1", - "acEmulationEnabled" : false - }, { - "type" : "HVDC", - "id" : "id23", - "hvdcId" : "hvdc2", - "acEmulationEnabled" : true - }, { - "type" : "HVDC", - "id" : "id24", - "hvdcId" : "hvdc2", - "acEmulationEnabled" : true, - "converterMode" : "SIDE_1_RECTIFIER_SIDE_2_INVERTER", - "droop" : 121.0, - "p0" : 42.0, - "relativeValue" : false - }, { - "type" : "HVDC", - "id" : "id25", - "hvdcId" : "hvdc1", - "acEmulationEnabled" : false, - "activePowerSetpoint" : 12.0, - "relativeValue" : true - }, { - "type" : "SHUNT_COMPENSATOR_POSITION", - "id" : "id22", - "shuntCompensatorId" : "shuntId1", - "sectionCount" : 5 - }, { - "type" : "STATIC_VAR_COMPENSATOR", - "id" : "id23", - "staticVarCompensatorId" : "svc", - "regulationMode" : "VOLTAGE", - "voltageSetpoint" : 56.0 - }, { - "type" : "STATIC_VAR_COMPENSATOR", - "id" : "id24", - "staticVarCompensatorId" : "svc", - "regulationMode" : "REACTIVE_POWER", - "reactivePowerSetpoint" : 120.0 - }, { - "type" : "TERMINALS_CONNECTION", - "id" : "id4", - "elementId" : "transformerId25", - "side" : "THREE", - "open" : true - } ] + }] } \ No newline at end of file diff --git a/data/OperatorStrategyFileTestV1.0.json b/data/OperatorStrategyFileTestV1.0.json index 88be6ac884..b4945cab7c 100644 --- a/data/OperatorStrategyFileTestV1.0.json +++ b/data/OperatorStrategyFileTestV1.0.json @@ -22,54 +22,5 @@ }, "actionIds" : [ "actionId4" ] } ] - }, { - "id" : "id3", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId1", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ANY_VIOLATION_CONDITION", - "filters" : [ "CURRENT" ] - }, - "actionIds" : [ "actionId1", "actionId3" ] - } ] - }, { - "id" : "id4", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId3", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ANY_VIOLATION_CONDITION", - "filters" : [ "LOW_VOLTAGE" ] - }, - "actionIds" : [ "actionId1", "actionId2", "actionId4" ] - } ] - }, { - "id" : "id5", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId4", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ALL_VIOLATION", - "filters" : [ "HIGH_VOLTAGE" ], - "violationIds" : [ "violation1", "violation2" ] - }, - "actionIds" : [ "actionId1", "actionId5" ] - } ] - }, { - "id" : "id6", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId5", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ALL_VIOLATION", - "violationIds" : [ "violation1", "violation2" ] - }, - "actionIds" : [ "actionId3" ] - } ] } ] } \ No newline at end of file diff --git a/docs/user_guide/security.rst b/docs/user_guide/security.rst index b4ba3670c3..15ef2483fb 100644 --- a/docs/user_guide/security.rst +++ b/docs/user_guide/security.rst @@ -130,4 +130,92 @@ The following operator strategy define the application of the switch action 'Swi >>> 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. \ No newline at end of file +Results for the post remedial action state are available in the branch results indexed with the operator strategy unique id. + +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 : + +.. code-block:: JSON + + { + "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 : + +.. code-block:: JSON + + { + "version" : "1.0", + "actions" : [ { + "type" : "SWITCH", + "id" : "id1", + "switchId" : "switchId1", + "open" : true + }, { + "type" : "MULTIPLE_ACTIONS", + "id" : "id2", + "actions" : [ { + "type" : "SWITCH", + "id" : "id3", + "switchId" : "switchId2", + "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 : + +.. code-block:: JSON + + { + "version" : "1.1", + "operatorStrategies" : [ { + "id" : "id1", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId1", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "TRUE_CONDITION" + }, + "actionIds" : [ "actionId1", "actionId2", "actionId3" ] + } ] + }, { + "id" : "id2", + "contingencyContextType" : "SPECIFIC", + "contingencyId" : "contingencyId2", + "conditionalActions" : [ { + "id" : "stage1", + "condition" : { + "type" : "ANY_VIOLATION_CONDITION" + }, + "actionIds" : [ "actionId4" ] + } ] + } ] + } \ No newline at end of file diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index 9662f8b05a..e24c7ce055 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -428,3 +428,11 @@ def test_add_actions_from_json_file(): df = sa_result.branch_results assert 'MULTIPLE_ACTIONS' in df.keys() assert 'PHASE_TAP_CHANGER_TAP_POSITION' in df.keys() + +def test_add_operator_strategies_from_json_file(): + n = pp.network.create_four_substations_node_breaker_network() + sa = pp.security.create_analysis() + sa.add_operator_strategies_from_json_file(str(DATA_DIR.joinpath('OperatorStrategyFileTestV1.0.json'))) + sa_result = sa.run_ac(n) + df = sa_result.branch_results + From 3de29c2e6c1fcee247e14dff25618071989b6ccd Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Mon, 7 Apr 2025 17:48:19 +0200 Subject: [PATCH 38/71] changed test files Signed-off-by: Naledi EL CHEIKH --- data/ActionFileTestV1.0.json | 12 ++++-------- data/OperatorStrategyFileTestV1.0.json | 8 ++++---- tests/test_security_analysis.py | 10 +++++----- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/data/ActionFileTestV1.0.json b/data/ActionFileTestV1.0.json index bab7fc38de..22922891b0 100644 --- a/data/ActionFileTestV1.0.json +++ b/data/ActionFileTestV1.0.json @@ -3,16 +3,12 @@ "actions" : [ { "type" : "SWITCH", "id" : "id1", - "switchId" : "switchId1", + "switchId" : "S1VL2_LCC1_BREAKER", "open" : true }, { - "type" : "MULTIPLE_ACTIONS", + "type" : "SWITCH", "id" : "id2", - "actions" : [ { - "type" : "SWITCH", - "id" : "id3", - "switchId" : "switchId2", - "open" : true - } ] + "switchId" : "S1VL2_BBS2_COUPLER_DISCONNECTOR", + "open" : true }] } \ No newline at end of file diff --git a/data/OperatorStrategyFileTestV1.0.json b/data/OperatorStrategyFileTestV1.0.json index b4945cab7c..4059a43d28 100644 --- a/data/OperatorStrategyFileTestV1.0.json +++ b/data/OperatorStrategyFileTestV1.0.json @@ -3,24 +3,24 @@ "operatorStrategies" : [ { "id" : "id1", "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId1", + "contingencyId" : "BE-G2_contingency", "conditionalActions" : [ { "id" : "stage1", "condition" : { "type" : "TRUE_CONDITION" }, - "actionIds" : [ "actionId1", "actionId2", "actionId3" ] + "actionIds" : [ "S1VL2_LCC1_BREAKER", "S1VL2_BBS2_COUPLER_DISCONNECTOR" ] } ] }, { "id" : "id2", "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId2", + "contingencyId" : "Line contingency", "conditionalActions" : [ { "id" : "stage1", "condition" : { "type" : "ANY_VIOLATION_CONDITION" }, - "actionIds" : [ "actionId4" ] + "actionIds" : [ "S4VL1_BBS_LINES3S4_DISCONNECTOR" ] } ] } ] } \ No newline at end of file diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index e24c7ce055..cbe2e904f7 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -424,15 +424,15 @@ def test_add_actions_from_json_file(): n = pp.network.create_four_substations_node_breaker_network() sa = pp.security.create_analysis() sa.add_actions_from_json_file(str(DATA_DIR.joinpath('ActionFileTestV1.0.json'))) - sa_result = sa.run_ac(n) - df = sa_result.branch_results - assert 'MULTIPLE_ACTIONS' in df.keys() - assert 'PHASE_TAP_CHANGER_TAP_POSITION' in df.keys() + sa.run_dc(n) def test_add_operator_strategies_from_json_file(): n = pp.network.create_four_substations_node_breaker_network() sa = pp.security.create_analysis() + sa.add_single_element_contingency('550ebe0d-f2b2-48c1-991f-cebea43a21aa', 'BE-G2_contingency') + sa.add_single_element_contingency('NHV1_NHV2_1', 'Line contingency') + sa.add_operator_strategies_from_json_file(str(DATA_DIR.joinpath('OperatorStrategyFileTestV1.0.json'))) - sa_result = sa.run_ac(n) + sa_result = sa.run_dc(n) df = sa_result.branch_results From 6a39902d0b4b69d2d813b16e8884dc2228c077bd Mon Sep 17 00:00:00 2001 From: jeandemanged Date: Thu, 27 Feb 2025 09:23:29 +0100 Subject: [PATCH 39/71] Fix Network.create_operational_limits docstring (#956) Signed-off-by: Damien Jeandemange Signed-off-by: Naledi EL CHEIKH --- pypowsybl/network/impl/network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pypowsybl/network/impl/network.py b/pypowsybl/network/impl/network.py index 22da5e7635..8f60755f60 100644 --- a/pypowsybl/network/impl/network.py +++ b/pypowsybl/network/impl/network.py @@ -4913,7 +4913,6 @@ def create_operational_limits(self, df: DataFrame = None, **kwargs: ArrayLike) - Valid attributes are: - **element_id**: the ID of the network element on which we want to create new limits - THREE_WINDINGS_TRANSFORMER, DANGLING_LINE) - **side**: the side of the network element where we want to create new limits (ONE, TWO, THREE) - **name**: the name of the limit - **type**: the type of limit to be created (CURRENT, APPARENT_POWER, ACTIVE_POWER) From edb749062d8a98db66ad0bbcf3d1421b01bcd1f1 Mon Sep 17 00:00:00 2001 From: Geoffroy Jamgotchian Date: Thu, 6 Mar 2025 10:46:21 +0100 Subject: [PATCH 40/71] Clean import config creation (#965) Signed-off-by: Geoffroy Jamgotchian Signed-off-by: Naledi EL CHEIKH --- .../com/powsybl/python/network/NetworkCFunctions.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java index 66703970bd..8f339c1b4f 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java @@ -153,11 +153,9 @@ private static void freeNetworkMetadata(NetworkMetadataPointer networkMetadataPo } private static ImportConfig createImportConfig(CCharPointerPointer postProcessorsPtrPtr, int postProcessorsCount) { - // FIXME to clean when a addPostProcessors will be added to core - List postProcessors = new ArrayList<>(); - postProcessors.addAll(ImportConfig.load().getPostProcessors()); - postProcessors.addAll(toStringList(postProcessorsPtrPtr, postProcessorsCount)); - return new ImportConfig(postProcessors.stream().distinct().toList()); + var importConfig = ImportConfig.load(); + importConfig.addPostProcessors(toStringList(postProcessorsPtrPtr, postProcessorsCount)); + return importConfig; } @CEntryPoint(name = "loadNetwork") From c15c6268554d2bd76869c28977f2460199dbcec7 Mon Sep 17 00:00:00 2001 From: HugoKulesza <94374655+HugoKulesza@users.noreply.github.com> Date: Fri, 7 Mar 2025 10:02:36 +0100 Subject: [PATCH 41/71] Add supported extensions to documentation (#949) Signed-off-by: Hugo KULESZA Signed-off-by: Naledi EL CHEIKH --- docs/user_guide/network.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/user_guide/network.rst b/docs/user_guide/network.rst index 85e321805f..fa2bc75586 100644 --- a/docs/user_guide/network.rst +++ b/docs/user_guide/network.rst @@ -32,6 +32,9 @@ The supported formats are the following: >>> pp.network.get_import_formats() ['BIIDM', 'CGMES', 'IEEE-CDF', 'JIIDM', 'MATPOWER', 'POWER-FACTORY', 'PSS/E', 'UCTE', 'XIIDM'] + >>> pp.network.get_import_supported_extensions() + ['RAW', 'RAWX', 'UCT', 'biidm', 'bin', 'dgs', 'iidm', 'jiidm', 'json', 'mat', 'raw', 'rawx', 'txt', 'uct', 'xiidm', 'xml'] + .. Note:: From 0a3119b9a74823f89ea50636f09a7e6937be003a Mon Sep 17 00:00:00 2001 From: HugoKulesza <94374655+HugoKulesza@users.noreply.github.com> Date: Fri, 7 Mar 2025 11:00:34 +0100 Subject: [PATCH 42/71] Fix NPE + clarify error message for wrong extension names (#952) Signed-off-by: Hugo KULESZA Signed-off-by: Naledi EL CHEIKH --- ...ondaryVoltageControlDataframeProvider.java | 20 +++++++++++++------ .../python/network/NetworkCFunctions.java | 17 ++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SecondaryVoltageControlDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SecondaryVoltageControlDataframeProvider.java index 5ae9170b52..3ca8240318 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SecondaryVoltageControlDataframeProvider.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SecondaryVoltageControlDataframeProvider.java @@ -31,6 +31,8 @@ @AutoService(NetworkExtensionDataframeProvider.class) public class SecondaryVoltageControlDataframeProvider implements NetworkExtensionDataframeProvider { + private static final String NO_EXTENSION_MESSAGE = "No SecondaryVoltageControl extension found on network "; + @Override public String getExtensionName() { return SecondaryVoltageControl.NAME; @@ -49,14 +51,20 @@ public List getExtensionTableNames() { } private Stream zonesStream(Network network) { - return network.getExtension(SecondaryVoltageControl.class) - .getControlZones().stream(); + SecondaryVoltageControl ext = network.getExtension(SecondaryVoltageControl.class); + if (ext == null) { + throw new PowsyblException(NO_EXTENSION_MESSAGE + network.getId()); + } + return ext.getControlZones().stream(); } private Stream unitsStream(Network network) { List units = new ArrayList<>(); - network.getExtension(SecondaryVoltageControl.class) - .getControlZones() + SecondaryVoltageControl ext = network.getExtension(SecondaryVoltageControl.class); + if (ext == null) { + throw new PowsyblException(NO_EXTENSION_MESSAGE + network.getId()); + } + ext.getControlZones() .forEach(zone -> { units.addAll(zone.getControlUnits() .stream() @@ -73,7 +81,7 @@ private static class ControlZoneGetter implements BaseDataframeMapperBuilder.Ite public ControlZone getItem(Network network, UpdatingDataframe updatingDataframe, int lineNumber) { SecondaryVoltageControl ext = network.getExtension(SecondaryVoltageControl.class); if (ext == null) { - throw new PowsyblException("Network " + network.getId() + " has no SecondaryVoltageControl extension."); + throw new PowsyblException(NO_EXTENSION_MESSAGE + network.getId()); } String name = updatingDataframe.getStringValue("name", lineNumber).orElse(null); ControlZone zone = ext.getControlZones().stream() @@ -95,7 +103,7 @@ private static class ControlUnitGetter implements BaseDataframeMapperBuilder.Ite public ControlUnitWithZone getItem(Network network, UpdatingDataframe updatingDataframe, int lineNumber) { SecondaryVoltageControl ext = network.getExtension(SecondaryVoltageControl.class); if (ext == null) { - throw new PowsyblException("Network " + network.getId() + " has no SecondaryVoltageControl extension."); + throw new PowsyblException(NO_EXTENSION_MESSAGE + network.getId()); } String id = updatingDataframe.getStringValue("unit_id", lineNumber).orElse(null); diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java index 8f339c1b4f..455004898e 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java @@ -468,11 +468,19 @@ public static ArrayPointer createNetworkElemen Network network = ObjectHandles.getGlobal().get(networkHandle); return Dataframes.createCDataframe(mapper, network, new DataframeFilter(), NetworkDataframeContext.DEFAULT); } else { - throw new PowsyblException("extension " + name + " not found"); + throw new PowsyblException(errorMessageForWrongExtensionName(name, tableName)); } }); } + private static String errorMessageForWrongExtensionName(String name, String tableName) { + String message = "No extension named " + name + " available"; + if (tableName != null) { + message = "No table " + tableName + " for extension " + name + " available"; + } + return message; + } + @CEntryPoint(name = "getExtensionsNames") public static ArrayPointer getExtensionsNames(IsolateThread thread, ExceptionHandlerPointer exceptionHandlerPtr) { return doCatch(exceptionHandlerPtr, () -> createCharPtrArray(List.copyOf(NetworkExtensions.getExtensionsNames()))); @@ -794,10 +802,7 @@ public static void updateNetworkElementsExtensionsWithSeries(IsolateThread threa UpdatingDataframe updatingDataframe = createDataframe(dataframe); mapper.updateSeries(network, updatingDataframe, NetworkDataframeContext.DEFAULT); } else { - if (tableName != null) { - throw new PowsyblException("table " + tableName + " of extension " + name + " not found"); - } - throw new PowsyblException("extension " + name + " not found"); + throw new PowsyblException(errorMessageForWrongExtensionName(name, tableName)); } }); } @@ -827,7 +832,7 @@ public static DataframeMetadataPointer getExtensionSeriesMetadata(IsolateThread List seriesMetadata = mapper.getSeriesMetadata(); return CTypeUtil.createSeriesMetadata(seriesMetadata); } else { - throw new PowsyblException("extension " + name + " not found"); + throw new PowsyblException(errorMessageForWrongExtensionName(name, tableName)); } }); } From 01adbe995d2fc837e337c4487b38a50e336da604 Mon Sep 17 00:00:00 2001 From: Geoffroy Jamgotchian Date: Mon, 10 Mar 2025 10:34:03 +0100 Subject: [PATCH 43/71] Topovec is wrong when elements are lost by propagation (#966) Signed-off-by: Geoffroy Jamgotchian Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 4 +- cpp/powsybl-cpp/powsybl-cpp.h | 2 +- cpp/pypowsybl-cpp/bindings.cpp | 2 +- .../com/powsybl/python/grid2op/Backend.java | 101 +++++++++++++++--- .../python/grid2op/Grid2opCFunctions.java | 4 +- pypowsybl/_pypowsybl.pyi | 2 +- pypowsybl/grid2op/impl/backend.py | 8 +- tests/test_grid2op.py | 20 ++++ 8 files changed, 120 insertions(+), 23 deletions(-) diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index 1d5bcbd1d2..ddca825bb2 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -1693,8 +1693,8 @@ JavaHandle createDefaultRaoParameters() { return pypowsybl::PowsyblCaller::get()->callJava(::createDefaultRaoParameters); } -JavaHandle createGrid2opBackend(const JavaHandle& networkHandle, bool considerOpenBranchReactiveFlow, int busesPerVoltageLevel, bool connectAllElementsToFirstBus) { - return pypowsybl::PowsyblCaller::get()->callJava(::createGrid2opBackend, networkHandle, considerOpenBranchReactiveFlow, busesPerVoltageLevel, connectAllElementsToFirstBus); +JavaHandle createGrid2opBackend(const JavaHandle& networkHandle, bool considerOpenBranchReactiveFlow, bool checkIsolatedAndDisconnectedInjections, int busesPerVoltageLevel, bool connectAllElementsToFirstBus) { + return pypowsybl::PowsyblCaller::get()->callJava(::createGrid2opBackend, networkHandle, considerOpenBranchReactiveFlow, checkIsolatedAndDisconnectedInjections, busesPerVoltageLevel, connectAllElementsToFirstBus); } void freeGrid2opBackend(const JavaHandle& backendHandle) { diff --git a/cpp/powsybl-cpp/powsybl-cpp.h b/cpp/powsybl-cpp/powsybl-cpp.h index 6476ab0f70..0a34140238 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.h +++ b/cpp/powsybl-cpp/powsybl-cpp.h @@ -861,7 +861,7 @@ JavaHandle getRaoResult(const JavaHandle& raoContext); RaoComputationStatus getRaoResultStatus(const JavaHandle& raoResult); JavaHandle createDefaultRaoParameters(); -JavaHandle createGrid2opBackend(const JavaHandle& networkHandle, bool considerOpenBranchReactiveFlow, int busesPerVoltageLevel, bool connectAllElementsToFirstBus); +JavaHandle createGrid2opBackend(const JavaHandle& networkHandle, bool considerOpenBranchReactiveFlow, bool checkIsolatedAndDisconnectedInjections, int busesPerVoltageLevel, bool connectAllElementsToFirstBus); void freeGrid2opBackend(const JavaHandle& backendHandle); std::vector getGrid2opStringValue(const JavaHandle& backendHandle, Grid2opStringValueType valueType); array* getGrid2opIntegerValue(const JavaHandle& backendHandle, Grid2opIntegerValueType valueType); diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index 1934eb2178..a1af8b113e 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -1218,7 +1218,7 @@ PYBIND11_MODULE(_pypowsybl, m) { .value("UPDATE_BRANCH_BUS2", Grid2opUpdateIntegerValueType::UPDATE_BRANCH_BUS2); m.def("create_grid2op_backend", &pypowsybl::createGrid2opBackend, "Create a Grid2op backend", py::arg("network"), - py::arg("consider_open_branch_reactive_flow"), py::arg("buses_per_voltage_level"), py::arg("connect_all_elements_to_first_bus")); + py::arg("consider_open_branch_reactive_flow"), py::arg("check_isolated_and_disconnected_injections"), py::arg("buses_per_voltage_level"), py::arg("connect_all_elements_to_first_bus")); m.def("free_grid2op_backend", &pypowsybl::freeGrid2opBackend, "Free a Grid2op backend", py::arg("backend")); m.def("get_grid2op_string_value", &pypowsybl::getGrid2opStringValue, "From a Grid2op backend get a string value vector", py::arg("backend"), py::arg("value_type")); m.def("get_grid2op_integer_value", &::pyGetGrid2opIntegerValue, "From a Grid2op backend get a integer value vector", py::arg("backend"), py::arg("value_type")); diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Backend.java b/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Backend.java index 8b692c153c..59f278bccb 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Backend.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Backend.java @@ -38,12 +38,14 @@ public class Backend implements Closeable { private final Network network; private final boolean considerOpenBranchReactiveFlow; + private final boolean checkIsolatedAndDisconnectedInjections; private final List voltageLevels; private final ArrayPointer voltageLevelName; private final Bus[] buses; private final double[] busV; + private Map busIdToGlobalNum; private final List loads; private final ArrayPointer loadName; @@ -88,21 +90,28 @@ public class Backend implements Closeable { private final int[] branchBusGlobalNum1; private final int[] branchBusGlobalNum2; + record TopoChange(Terminal terminal, String newBusId, boolean connected) { + } + private int[] loadTopoVectPosition; private int[] generatorTopoVectPosition; private int[] branchTopoVectPosition1; private int[] branchTopoVectPosition2; private ArrayPointer topoVect; + private final List topoChanges = new ArrayList<>(); + private final LoadFlowProvider loadFlowProvider = LoadFlowProvider.findAll().stream() .filter(p -> p.getName().equals("OpenLoadFlow")) .findFirst() .orElseThrow(); private final LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(loadFlowProvider); - public Backend(Network network, boolean considerOpenBranchReactiveFlow, int busesPerVoltageLevel, boolean connectAllElementsToFirstBus) { + public Backend(Network network, boolean considerOpenBranchReactiveFlow, boolean checkIsolatedAndDisconnectedInjections, + int busesPerVoltageLevel, boolean connectAllElementsToFirstBus) { this.network = Objects.requireNonNull(network); this.considerOpenBranchReactiveFlow = considerOpenBranchReactiveFlow; + this.checkIsolatedAndDisconnectedInjections = checkIsolatedAndDisconnectedInjections; prepareNetwork(network, busesPerVoltageLevel); @@ -119,9 +128,10 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, int buse int busCount = network.getBusBreakerView().getBusCount(); buses = new Bus[busCount]; busV = new double[busCount]; - Map busIdToGlobalNum = new HashMap<>(busCount); + busIdToGlobalNum = new HashMap<>(busCount); for (int voltageLevelNum = 0; voltageLevelNum < voltageLevels.size(); voltageLevelNum++) { VoltageLevel voltageLevel = voltageLevels.get(voltageLevelNum); + // process all buses including ones not in main CC becuase an element might be reconnected afterward List localBuses = voltageLevel.getBusBreakerView().getBusStream().toList(); for (int i = 0; i < localBuses.size(); i++) { Bus localBus = localBuses.get(i); @@ -143,7 +153,7 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, int buse for (int i = 0; i < loads.size(); i++) { Load load = loads.get(i); loadToVoltageLevelNum.getPtr().write(i, voltageLevelIdToNum.get(load.getTerminal().getVoltageLevel().getId())); - Bus bus = load.getTerminal().getBusBreakerView().getBus(); + Bus bus = getBus(load.getTerminal()); loadBusGlobalNum[i] = bus == null ? -1 : busIdToGlobalNum.get(bus.getId()); } @@ -158,7 +168,7 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, int buse for (int i = 0; i < generators.size(); i++) { Generator generator = generators.get(i); generatorToVoltageLevelNum.getPtr().write(i, voltageLevelIdToNum.get(generator.getTerminal().getVoltageLevel().getId())); - Bus bus = generator.getTerminal().getBusBreakerView().getBus(); + Bus bus = getBus(generator.getTerminal()); generatorBusGlobalNum[i] = bus == null ? -1 : busIdToGlobalNum.get(bus.getId()); } @@ -174,7 +184,7 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, int buse for (int i = 0; i < shunts.size(); i++) { ShuntCompensator shunt = shunts.get(i); shuntToVoltageLevelNum.getPtr().write(i, voltageLevelIdToNum.get(shunt.getTerminal().getVoltageLevel().getId())); - Bus bus = shunt.getTerminal().getBusBreakerView().getBus(); + Bus bus = getBus(shunt.getTerminal()); shuntBusGlobalNum[i] = bus == null ? -1 : busIdToGlobalNum.get(bus.getId()); shuntBusLocalNum.getPtr().write(i, globalToLocalBusNum(shuntBusGlobalNum[i])); } @@ -199,8 +209,8 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, int buse Branch branch = branches.get(i); branchToVoltageLevelNum1.getPtr().write(i, voltageLevelIdToNum.get(branch.getTerminal1().getVoltageLevel().getId())); branchToVoltageLevelNum2.getPtr().write(i, voltageLevelIdToNum.get(branch.getTerminal2().getVoltageLevel().getId())); - Bus bus1 = branch.getTerminal1().getBusBreakerView().getBus(); - Bus bus2 = branch.getTerminal2().getBusBreakerView().getBus(); + Bus bus1 = getBus(branch.getTerminal1()); + Bus bus2 = getBus(branch.getTerminal2()); branchBusGlobalNum1[i] = bus1 == null ? -1 : busIdToGlobalNum.get(bus1.getId()); branchBusGlobalNum2[i] = bus2 == null ? -1 : busIdToGlobalNum.get(bus2.getId()); branchPermanentLimitA.getPtr().write(i, branch.getCurrentLimits1().map(LoadingLimits::getPermanentLimit) @@ -218,6 +228,11 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, int buse } } + private static Bus getBus(Terminal t) { + var bus = t.getBusBreakerView().getBus(); + return bus != null && bus.isInMainConnectedComponent() ? bus : null; + } + private void connectAllElementsToFirstBus() { LOGGER.debug("Connecting all elements to first bus of their voltage level..."); for (int i = 0; i < loads.size(); i++) { @@ -347,6 +362,48 @@ private void updateTopoVect(int[] topoVectPosition, int[] busGlobalNum) { } } + private void ensureTopoVectIsUpToDate() { + if (!topoChanges.isEmpty()) { + // apply changes on IIDM + for (var topoChange : topoChanges) { + if (topoChange.newBusId != null) { + topoChange.terminal.getBusBreakerView().setConnectableBus(topoChange.newBusId); + } + if (topoChange.connected) { + topoChange.terminal.connect(); + } else { + topoChange.terminal.disconnect(); + } + } + // some buses might have moved in or out of main CC, so we need to re-update all bus global nums + for (int i = 0; i < loads.size(); i++) { + Load load = loads.get(i); + Bus bus = getBus(load.getTerminal()); + loadBusGlobalNum[i] = bus == null ? -1 : busIdToGlobalNum.get(bus.getId()); + } + for (int i = 0; i < generators.size(); i++) { + Generator generator = generators.get(i); + Bus bus = getBus(generator.getTerminal()); + generatorBusGlobalNum[i] = bus == null ? -1 : busIdToGlobalNum.get(bus.getId()); + } + for (int i = 0; i < shunts.size(); i++) { + ShuntCompensator shunt = shunts.get(i); + Bus bus = getBus(shunt.getTerminal()); + shuntBusGlobalNum[i] = bus == null ? -1 : busIdToGlobalNum.get(bus.getId()); + shuntBusLocalNum.getPtr().write(i, globalToLocalBusNum(shuntBusGlobalNum[i])); + } + for (int i = 0; i < branches.size(); i++) { + Branch branch = branches.get(i); + Bus bus1 = getBus(branch.getTerminal1()); + Bus bus2 = getBus(branch.getTerminal2()); + branchBusGlobalNum1[i] = bus1 == null ? -1 : busIdToGlobalNum.get(bus1.getId()); + branchBusGlobalNum2[i] = bus2 == null ? -1 : busIdToGlobalNum.get(bus2.getId()); + } + // then we can update topo vect + updateTopoVect(); + } + } + public void updateTopoVect() { updateTopoVect(loadTopoVectPosition, loadBusGlobalNum); updateTopoVect(generatorTopoVectPosition, generatorBusGlobalNum); @@ -479,7 +536,10 @@ public ArrayPointer getIntegerValue(Grid2opCFunctions.Grid2opIntege case BRANCH_VOLTAGE_LEVEL_NUM_1 -> branchToVoltageLevelNum1; case BRANCH_VOLTAGE_LEVEL_NUM_2 -> branchToVoltageLevelNum2; case SHUNT_LOCAL_BUS -> shuntBusLocalNum; - case TOPO_VECT -> topoVect; + case TOPO_VECT -> { + ensureTopoVectIsUpToDate(); + yield topoVect; + } }; } @@ -588,9 +648,14 @@ private boolean updateTopo(String label, int i, Terminal t, int localBusNum, int int oldLocalBusNum = globalToLocalBusNum(oldGlobalBusNum); LOGGER.trace("Disconnect {} from bus {}", label, oldLocalBusNum); } - t.disconnect(); - xBusGlobalNum[i] = -1; - return true; + if (checkIsolatedAndDisconnectedInjections) { + t.disconnect(); + xBusGlobalNum[i] = -1; + return true; + } else { + topoChanges.add(new TopoChange(t, null, false)); + return false; + } } } else { int globalBusNum = localToGlobalBusNum(xToVoltageLevelNum.getPtr().read(i), localBusNum); @@ -605,10 +670,15 @@ private boolean updateTopo(String label, int i, Terminal t, int localBusNum, int } } String newBusId = buses[globalBusNum].getId(); - t.getBusBreakerView().setConnectableBus(newBusId); - t.connect(); - xBusGlobalNum[i] = globalBusNum; - return true; + if (checkIsolatedAndDisconnectedInjections) { + t.getBusBreakerView().setConnectableBus(newBusId); + t.connect(); + xBusGlobalNum[i] = globalBusNum; + return true; + } else { + topoChanges.add(new TopoChange(t, newBusId, true)); + return false; + } } } return false; @@ -756,6 +826,7 @@ public boolean checkIsolatedAndDisconnectedInjections() { public LoadFlowResult runLoadFlow(LoadFlowParameters parameters) { checkIsolatedAndDisconnectedInjections(); + ensureTopoVectIsUpToDate(); LoadFlowResult result = loadFlowRunner.run(network, parameters); updateState(); return result; diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Grid2opCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Grid2opCFunctions.java index d8217a8bc7..a9d0ed698f 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Grid2opCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Grid2opCFunctions.java @@ -131,11 +131,11 @@ public enum Grid2opUpdateIntegerValueType { @CEntryPoint(name = "createGrid2opBackend") public static ObjectHandle createBackend(IsolateThread thread, ObjectHandle networkHandle, boolean considerOpenBranchReactiveFlow, - int busesPerVoltageLevel, boolean connectAllElementsToFirstBus, + boolean checkIsolatedAndDisconnectedInjections, int busesPerVoltageLevel, boolean connectAllElementsToFirstBus, ExceptionHandlerPointer exceptionHandlerPtr) { return doCatch(exceptionHandlerPtr, () -> { Network network = ObjectHandles.getGlobal().get(networkHandle); - Backend backend = new Backend(network, considerOpenBranchReactiveFlow, busesPerVoltageLevel, connectAllElementsToFirstBus); + Backend backend = new Backend(network, considerOpenBranchReactiveFlow, checkIsolatedAndDisconnectedInjections, busesPerVoltageLevel, connectAllElementsToFirstBus); return ObjectHandles.getGlobal().create(backend); }); } diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index c9aa084c55..76c2271152 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -1180,7 +1180,7 @@ class Grid2opUpdateIntegerValueType: @property def name(self) -> str: ... -def create_grid2op_backend(backend: JavaHandle, consider_open_branch_reactive_flow: bool, buses_per_voltage_level: int, connect_all_elements_to_first_bus: bool) -> JavaHandle: ... +def create_grid2op_backend(backend: JavaHandle, consider_open_branch_reactive_flow: bool, check_isolated_and_disconnected_injections: bool, buses_per_voltage_level: int, connect_all_elements_to_first_bus: bool) -> JavaHandle: ... def free_grid2op_backend(backend: JavaHandle) -> None: ... diff --git a/pypowsybl/grid2op/impl/backend.py b/pypowsybl/grid2op/impl/backend.py index 49a86fa335..a21360cb90 100644 --- a/pypowsybl/grid2op/impl/backend.py +++ b/pypowsybl/grid2op/impl/backend.py @@ -22,14 +22,17 @@ class Backend: def __init__(self, network: Network, consider_open_branch_reactive_flow: bool = False, + check_isolated_and_disconnected_injections: bool = True, buses_per_voltage_level: int = 2, connect_all_elements_to_first_bus: bool = True): self._network = network self._consider_open_branch_reactive_flow = consider_open_branch_reactive_flow + self._check_isolated_and_disconnected_injections = check_isolated_and_disconnected_injections self._buses_per_voltage_level = buses_per_voltage_level self._connect_all_elements_to_first_bus = connect_all_elements_to_first_bus self._handle = _pypowsybl.create_grid2op_backend(self._network._handle, - self._connect_all_elements_to_first_bus, + self._consider_open_branch_reactive_flow, + self._check_isolated_and_disconnected_injections, self._buses_per_voltage_level, self._connect_all_elements_to_first_bus) @@ -52,16 +55,19 @@ def __exit__(self, exc_type: Optional[Type[BaseException]], def __getstate__(self) -> Dict[str, Any]: return {'xiidm': self._network.save_to_binary_buffer('XIIDM', {}), 'consider_open_branch_reactive_flow': self._consider_open_branch_reactive_flow, + 'check_isolated_and_disconnected_injections': self._check_isolated_and_disconnected_injections, 'buses_per_voltage_level': self._buses_per_voltage_level, 'connect_all_elements_to_first_bus': self._connect_all_elements_to_first_bus} def __setstate__(self, state: Dict[str, Any]) -> None: self._network = Network(_pypowsybl.load_network_from_binary_buffers([state['xiidm'].getbuffer()], {}, [], None)) self._consider_open_branch_reactive_flow = state['consider_open_branch_reactive_flow'] + self._check_isolated_and_disconnected_injections = state['check_isolated_and_disconnected_injections'] self._buses_per_voltage_level = state['buses_per_voltage_level'] self._connect_all_elements_to_first_bus = state['connect_all_elements_to_first_bus'] self._handle = _pypowsybl.create_grid2op_backend(self._network._handle, self._connect_all_elements_to_first_bus, + self._check_isolated_and_disconnected_injections, self._buses_per_voltage_level, self._connect_all_elements_to_first_bus) diff --git a/tests/test_grid2op.py b/tests/test_grid2op.py index 0e1c9f75c0..844ec0a1a5 100644 --- a/tests/test_grid2op.py +++ b/tests/test_grid2op.py @@ -113,3 +113,23 @@ def test_backend_copy(): with open(data_file, 'rb') as f: with pickle.load(f) as backend2: npt.assert_allclose(np.array([630.0]), backend2.get_double_value(grid2op.DoubleValueType.LOAD_P), rtol=TOLERANCE, atol=TOLERANCE) + + +def test_backend_disconnection_issue(): + n = pp.network.create_ieee14() + pp.loadflow.run_ac(n) + with grid2op.Backend(n, check_isolated_and_disconnected_injections=False) as backend: + npt.assert_array_equal(np.array([1] * 56), # all is connected to bus 1 + backend.get_integer_value(grid2op.IntegerValueType.TOPO_VECT)) + npt.assert_array_equal(np.array(['L1-2-1', 'L1-5-1', 'L2-3-1', 'L2-4-1', 'L2-5-1', 'L3-4-1', 'L4-5-1', 'L6-11-1', 'L6-12-1', 'L6-13-1', 'L7-8-1', 'L7-9-1', 'L9-10-1', 'L9-14-1', 'L10-11-1', 'L12-13-1', 'L13-14-1', 'T4-7-1', 'T4-9-1', 'T5-6-1']), + backend.get_string_value(grid2op.StringValueType.BRANCH_NAME)) + # disconnect L7-8-1 + backend.update_integer_value(grid2op.UpdateIntegerValueType.UPDATE_BRANCH_BUS1, np.array([1] * 10 + [-1] + [1] * 9), np.array([False] * 10 + [True] + [False] * 9)) + backend.update_integer_value(grid2op.UpdateIntegerValueType.UPDATE_BRANCH_BUS2, np.array([1] * 10 + [-1] + [1] * 9), np.array([False] * 10 + [True] + [False] * 9)) + backend.run_pf() + # we can see than L7-8-1 is disconnected at both side but also generator at bus 8 + npt.assert_array_equal(np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, + -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1]), + backend.get_integer_value(grid2op.IntegerValueType.TOPO_VECT)) From 39f2dacb65c7c52e47c81d9efe98084dd6d6fb79 Mon Sep 17 00:00:00 2001 From: HugoKulesza <94374655+HugoKulesza@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:24:47 +0100 Subject: [PATCH 44/71] Change minimum version of cmake to reflect use of cmake_path (#967) Signed-off-by: Hugo KULESZA Signed-off-by: Naledi EL CHEIKH --- README.md | 2 +- cpp/CMakeLists.txt | 2 +- cpp/powsybl-cpp/CMakeLists.txt | 2 +- cpp/pypowsybl-cpp/CMakeLists.txt | 2 +- cpp/pypowsybl-java/CMakeLists.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 274ff997c5..e467d9d8df 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ That section is intended for developers who wish to build pypowsybl from the sou Requirements: - Maven >= 3.1 -- Cmake >= 3.14 +- Cmake >= 3.20 - C++11 compiler - Python >= 3.8 for Linux, Windows and MacOS (amd64 and arm64) - [Oracle GraalVM Java 17](https://www.graalvm.org/downloads/) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 534e3a210e..76c8937978 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -4,7 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.20) project(pypowsybl) set(CMAKE_CXX_STANDARD 17) diff --git a/cpp/powsybl-cpp/CMakeLists.txt b/cpp/powsybl-cpp/CMakeLists.txt index 9d741b8d02..f2fc9f0166 100644 --- a/cpp/powsybl-cpp/CMakeLists.txt +++ b/cpp/powsybl-cpp/CMakeLists.txt @@ -4,7 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.20) project(powsybl-cpp) set(CMAKE_CXX_STANDARD 17) diff --git a/cpp/pypowsybl-cpp/CMakeLists.txt b/cpp/pypowsybl-cpp/CMakeLists.txt index ec5e014236..5265f8f0f3 100644 --- a/cpp/pypowsybl-cpp/CMakeLists.txt +++ b/cpp/pypowsybl-cpp/CMakeLists.txt @@ -4,7 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.20) project(pypowsybl-cpp) # Enable static linkage to prevent any future runtime binary compatibility issue diff --git a/cpp/pypowsybl-java/CMakeLists.txt b/cpp/pypowsybl-java/CMakeLists.txt index 74bfb0d05a..8500d3f7cd 100644 --- a/cpp/pypowsybl-java/CMakeLists.txt +++ b/cpp/pypowsybl-java/CMakeLists.txt @@ -4,7 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.20) project(pypowsybl-java) set(CMAKE_CXX_STANDARD 17) From 7091096107913f6b5640d39011b9ce9638152a2d Mon Sep 17 00:00:00 2001 From: alicecaron Date: Mon, 10 Mar 2025 15:03:30 +0100 Subject: [PATCH 45/71] List supported actions in SA documentation (#957) Signed-off-by: CARON Alice Signed-off-by: Naledi EL CHEIKH --- docs/user_guide/security.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/user_guide/security.rst b/docs/user_guide/security.rst index 15ef2483fb..a8d4074064 100644 --- a/docs/user_guide/security.rst +++ b/docs/user_guide/security.rst @@ -99,9 +99,21 @@ 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'. + +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 + +The following example defines a switch closing action with id 'SwitchAction' on the switch with id 'S4VL1_BBS_LD6_DISCONNECTOR'. .. doctest:: :options: +NORMALIZE_WHITESPACE From aff268c3e5f723082916c9561de577abdfcd7273 Mon Sep 17 00:00:00 2001 From: alicecaron Date: Tue, 11 Mar 2025 08:41:12 +0100 Subject: [PATCH 46/71] Add "Build the doc" section in the readme (#941) * Add "Build the doc" section in the readme Signed-off-by: CARON Alice * Handle feedbacks Signed-off-by: CARON Alice --------- Signed-off-by: CARON Alice Co-authored-by: Florian Dupuy <66690739+flo-dup@users.noreply.github.com> Co-authored-by: HugoKulesza <94374655+HugoKulesza@users.noreply.github.com> Signed-off-by: Naledi EL CHEIKH --- README.md | 24 ++---------------------- docs/README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 docs/README.md diff --git a/README.md b/README.md index e467d9d8df..c0c5e4a768 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ GraalVM to compile Java code to a native library. Latest version of the documentation with API reference and many code samples is [here](https://pypowsybl.readthedocs.io/). +To contribute to the documentation follow the instructions in the [documentation README](https://github.com/powsybl/pypowsybl/blob/main/docs/README.md) page. + ## Notebooks Notebooks demonstrating PyPowSyBl features can be found in this [repository](https://github.com/powsybl/pypowsybl-notebooks). @@ -133,25 +135,3 @@ To run linting inspection with `pylint`: ```bash pylint pypowsybl ``` - -## Contribute to documentation - -To run the tests included in the documentation: - -```bash -cd docs/ -make doctest -``` - -And then, to build the documentation: - -```bash -make html -``` - -Web pages are generated in repository _build/html/ for preview before opening a pull request. -You can for example open it with firefox browser: - -```bash -firefox _build/html/index.html -``` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..6ae165d2a6 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,49 @@ +# PyPowSyBl documentation + +These are the documentation sources for PyPowSyBl features. + +Please keep them up to date with your developments. +They are published on pypowsybl.readthedocs.io and pull requests are built and previewed automatically. + +## Run + +To run the tests included in the documentation: + +```bash +make doctest +``` + +## Build the documentation + +When modifying the website content, you can easily preview the result on your PC. + +Navigate to the `docs` directory of the project and run the following commands: +~~~bash +cd docs +~~~ +Install the requirements the first time: +~~~bash +pip install -r requirements.txt +~~~ +Build the documentation: +~~~bash +sphinx-build -a . _build/html +~~~ +Or +~~~bash +make html +~~~ +Or to build the documentation in latex format: +~~~bash +make latexpdf +~~~ + +## Preview the result + +For html format, web pages are generated in repository `_build/html` and can be previewed opening a pull request. +You can for example open it with firefox browser: + +```bash +firefox _build/html/index.html +``` + From 0bf8b42b33354fcef2e40c0e446c0c2b9a5971a4 Mon Sep 17 00:00:00 2001 From: Geoffroy Jamgotchian Date: Fri, 14 Mar 2025 09:14:50 +0100 Subject: [PATCH 47/71] Expose voltage angles to Grid2op backend (#968) Signed-off-by: Geoffroy Jamgotchian Signed-off-by: Naledi EL CHEIKH --- cpp/pypowsybl-cpp/bindings.cpp | 5 +++ cpp/pypowsybl-java/powsybl-api.h | 5 +++ .../com/powsybl/python/grid2op/Backend.java | 36 +++++++++++++++++++ .../python/grid2op/Grid2opCFunctions.java | 5 +++ pypowsybl/_pypowsybl.pyi | 5 +++ tests/test_grid2op.py | 7 ++++ 6 files changed, 63 insertions(+) diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index a1af8b113e..b3c4b79e1e 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -1188,18 +1188,23 @@ PYBIND11_MODULE(_pypowsybl, m) { .value("LOAD_P", Grid2opDoubleValueType::LOAD_P) .value("LOAD_Q", Grid2opDoubleValueType::LOAD_Q) .value("LOAD_V", Grid2opDoubleValueType::LOAD_V) + .value("LOAD_ANGLE", Grid2opDoubleValueType::LOAD_ANGLE) .value("GENERATOR_P", Grid2opDoubleValueType::GENERATOR_P) .value("GENERATOR_Q", Grid2opDoubleValueType::GENERATOR_Q) .value("GENERATOR_V", Grid2opDoubleValueType::GENERATOR_V) + .value("GENERATOR_ANGLE", Grid2opDoubleValueType::GENERATOR_ANGLE) .value("SHUNT_P", Grid2opDoubleValueType::SHUNT_P) .value("SHUNT_Q", Grid2opDoubleValueType::SHUNT_Q) .value("SHUNT_V", Grid2opDoubleValueType::SHUNT_V) + .value("SHUNT_ANGLE", Grid2opDoubleValueType::SHUNT_ANGLE) .value("BRANCH_P1", Grid2opDoubleValueType::BRANCH_P1) .value("BRANCH_P2", Grid2opDoubleValueType::BRANCH_P2) .value("BRANCH_Q1", Grid2opDoubleValueType::BRANCH_Q1) .value("BRANCH_Q2", Grid2opDoubleValueType::BRANCH_Q2) .value("BRANCH_V1", Grid2opDoubleValueType::BRANCH_V1) .value("BRANCH_V2", Grid2opDoubleValueType::BRANCH_V2) + .value("BRANCH_ANGLE1", Grid2opDoubleValueType::BRANCH_ANGLE1) + .value("BRANCH_ANGLE2", Grid2opDoubleValueType::BRANCH_ANGLE2) .value("BRANCH_I1", Grid2opDoubleValueType::BRANCH_I1) .value("BRANCH_I2", Grid2opDoubleValueType::BRANCH_I2) .value("BRANCH_PERMANENT_LIMIT_A", Grid2opDoubleValueType::BRANCH_PERMANENT_LIMIT_A); diff --git a/cpp/pypowsybl-java/powsybl-api.h b/cpp/pypowsybl-java/powsybl-api.h index c8f7bad594..ffd54878f9 100644 --- a/cpp/pypowsybl-java/powsybl-api.h +++ b/cpp/pypowsybl-java/powsybl-api.h @@ -494,18 +494,23 @@ typedef enum { LOAD_P = 0, LOAD_Q, LOAD_V, + LOAD_ANGLE, GENERATOR_P, GENERATOR_Q, GENERATOR_V, + GENERATOR_ANGLE, SHUNT_P, SHUNT_Q, SHUNT_V, + SHUNT_ANGLE, BRANCH_P1, BRANCH_P2, BRANCH_Q1, BRANCH_Q2, BRANCH_V1, BRANCH_V2, + BRANCH_ANGLE1, + BRANCH_ANGLE2, BRANCH_I1, BRANCH_I2, BRANCH_PERMANENT_LIMIT_A, diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Backend.java b/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Backend.java index 59f278bccb..ae3f2b46d0 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Backend.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Backend.java @@ -45,6 +45,7 @@ public class Backend implements Closeable { private final Bus[] buses; private final double[] busV; + private final double[] busAngle; private Map busIdToGlobalNum; private final List loads; @@ -53,6 +54,7 @@ public class Backend implements Closeable { private final ArrayPointer loadP; private final ArrayPointer loadQ; private final ArrayPointer loadV; + private final ArrayPointer loadAngle; private final int[] loadBusGlobalNum; private final List generators; @@ -61,6 +63,7 @@ public class Backend implements Closeable { private final ArrayPointer generatorP; private final ArrayPointer generatorQ; private final ArrayPointer generatorV; + private final ArrayPointer generatorAngle; private final int[] generatorBusGlobalNum; private final List shunts; @@ -69,6 +72,7 @@ public class Backend implements Closeable { private final ArrayPointer shuntP; private final ArrayPointer shuntQ; private final ArrayPointer shuntV; + private final ArrayPointer shuntAngle; private final int[] shuntBusGlobalNum; private final ArrayPointer shuntBusLocalNum; @@ -84,6 +88,8 @@ public class Backend implements Closeable { private final ArrayPointer branchQ2; private final ArrayPointer branchV1; private final ArrayPointer branchV2; + private final ArrayPointer branchAngle1; + private final ArrayPointer branchAngle2; private final ArrayPointer branchI1; private final ArrayPointer branchI2; private final ArrayPointer branchPermanentLimitA; @@ -128,6 +134,7 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, boolean int busCount = network.getBusBreakerView().getBusCount(); buses = new Bus[busCount]; busV = new double[busCount]; + busAngle = new double[busCount]; busIdToGlobalNum = new HashMap<>(busCount); for (int voltageLevelNum = 0; voltageLevelNum < voltageLevels.size(); voltageLevelNum++) { VoltageLevel voltageLevel = voltageLevels.get(voltageLevelNum); @@ -149,6 +156,7 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, boolean loadP = createDoubleArrayPointer(loads.size()); loadQ = createDoubleArrayPointer(loads.size()); loadV = createDoubleArrayPointer(loads.size()); + loadAngle = createDoubleArrayPointer(loads.size()); loadBusGlobalNum = new int[loads.size()]; for (int i = 0; i < loads.size(); i++) { Load load = loads.get(i); @@ -164,6 +172,7 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, boolean generatorP = createDoubleArrayPointer(generators.size()); generatorQ = createDoubleArrayPointer(generators.size()); generatorV = createDoubleArrayPointer(generators.size()); + generatorAngle = createDoubleArrayPointer(generators.size()); generatorBusGlobalNum = new int[generators.size()]; for (int i = 0; i < generators.size(); i++) { Generator generator = generators.get(i); @@ -179,6 +188,7 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, boolean shuntP = createDoubleArrayPointer(shunts.size()); shuntQ = createDoubleArrayPointer(shunts.size()); shuntV = createDoubleArrayPointer(shunts.size()); + shuntAngle = createDoubleArrayPointer(shunts.size()); shuntBusGlobalNum = new int[shunts.size()]; shuntBusLocalNum = createIntArrayPointer(shunts.size()); for (int i = 0; i < shunts.size(); i++) { @@ -200,6 +210,8 @@ public Backend(Network network, boolean considerOpenBranchReactiveFlow, boolean branchQ2 = createDoubleArrayPointer(branches.size()); branchV1 = createDoubleArrayPointer(branches.size()); branchV2 = createDoubleArrayPointer(branches.size()); + branchAngle1 = createDoubleArrayPointer(branches.size()); + branchAngle2 = createDoubleArrayPointer(branches.size()); branchI1 = createDoubleArrayPointer(branches.size()); branchI2 = createDoubleArrayPointer(branches.size()); branchPermanentLimitA = createDoubleArrayPointer(branches.size()); @@ -436,6 +448,7 @@ private void updateBuses() { Bus bus = buses[i]; if (bus != null) { busV[i] = fixNan(bus.getV()); + busAngle[i] = fixNan(Math.toRadians(bus.getAngle())); } } } @@ -448,6 +461,14 @@ private double getV(int i, int[] xBusGlobalNum) { return busV[globalNum]; } + private double getAngle(int i, int[] xBusGlobalNum) { + int globalNum = xBusGlobalNum[i]; + if (globalNum == -1) { + return 0.0; + } + return busAngle[globalNum]; + } + private double getP(Terminal t, int i, int[] xBusGlobalNum) { int globalNum = xBusGlobalNum[i]; if (globalNum == -1) { @@ -479,6 +500,7 @@ private void updateLoads() { loadP.getPtr().write(i, getP(terminal, i, loadBusGlobalNum)); loadQ.getPtr().write(i, getQ(terminal, i, loadBusGlobalNum)); loadV.getPtr().write(i, getV(i, loadBusGlobalNum)); + loadAngle.getPtr().write(i, getAngle(i, loadBusGlobalNum)); } } @@ -489,6 +511,7 @@ private void updateGenerators() { generatorP.getPtr().write(i, -getP(terminal, i, generatorBusGlobalNum)); // grid2op convention generatorQ.getPtr().write(i, -getQ(terminal, i, generatorBusGlobalNum)); // grid2op convention generatorV.getPtr().write(i, getV(i, generatorBusGlobalNum)); + generatorAngle.getPtr().write(i, getAngle(i, generatorBusGlobalNum)); } } @@ -499,6 +522,7 @@ private void updateShunts() { shuntP.getPtr().write(i, getP(terminal, i, shuntBusGlobalNum)); shuntQ.getPtr().write(i, getQ(terminal, i, shuntBusGlobalNum)); shuntV.getPtr().write(i, getV(i, shuntBusGlobalNum)); + shuntAngle.getPtr().write(i, getAngle(i, shuntBusGlobalNum)); } } @@ -513,6 +537,8 @@ private void updateBranches() { branchQ2.getPtr().write(i, getQ(terminal2, i, branchBusGlobalNum2)); branchV1.getPtr().write(i, getV(i, branchBusGlobalNum1)); branchV2.getPtr().write(i, getV(i, branchBusGlobalNum2)); + branchAngle1.getPtr().write(i, getAngle(i, branchBusGlobalNum1)); + branchAngle2.getPtr().write(i, getAngle(i, branchBusGlobalNum2)); branchI1.getPtr().write(i, getI(terminal1, i, branchBusGlobalNum1)); branchI2.getPtr().write(i, getI(terminal2, i, branchBusGlobalNum2)); } @@ -548,18 +574,23 @@ public ArrayPointer getDoubleValue(Grid2opCFunctions.Grid2opDoub case LOAD_P -> loadP; case LOAD_Q -> loadQ; case LOAD_V -> loadV; + case LOAD_ANGLE -> loadAngle; case GENERATOR_P -> generatorP; case GENERATOR_Q -> generatorQ; case GENERATOR_V -> generatorV; + case GENERATOR_ANGLE -> generatorAngle; case SHUNT_P -> shuntP; case SHUNT_Q -> shuntQ; case SHUNT_V -> shuntV; + case SHUNT_ANGLE -> shuntAngle; case BRANCH_P1 -> branchP1; case BRANCH_P2 -> branchP2; case BRANCH_Q1 -> branchQ1; case BRANCH_Q2 -> branchQ2; case BRANCH_V1 -> branchV1; case BRANCH_V2 -> branchV2; + case BRANCH_ANGLE1 -> branchAngle1; + case BRANCH_ANGLE2 -> branchAngle2; case BRANCH_I1 -> branchI1; case BRANCH_I2 -> branchI2; case BRANCH_PERMANENT_LIMIT_A -> branchPermanentLimitA; @@ -841,18 +872,21 @@ public void close() { PyPowsyblApiHeader.freeArrayPointer(loadP); PyPowsyblApiHeader.freeArrayPointer(loadQ); PyPowsyblApiHeader.freeArrayPointer(loadV); + PyPowsyblApiHeader.freeArrayPointer(loadAngle); Util.freeCharPtrArray(generatorName); PyPowsyblApiHeader.freeArrayPointer(generatorToVoltageLevelNum); PyPowsyblApiHeader.freeArrayPointer(generatorP); PyPowsyblApiHeader.freeArrayPointer(generatorQ); PyPowsyblApiHeader.freeArrayPointer(generatorV); + PyPowsyblApiHeader.freeArrayPointer(generatorAngle); Util.freeCharPtrArray(shuntName); PyPowsyblApiHeader.freeArrayPointer(shuntToVoltageLevelNum); PyPowsyblApiHeader.freeArrayPointer(shuntP); PyPowsyblApiHeader.freeArrayPointer(shuntQ); PyPowsyblApiHeader.freeArrayPointer(shuntV); + PyPowsyblApiHeader.freeArrayPointer(shuntAngle); PyPowsyblApiHeader.freeArrayPointer(shuntBusLocalNum); Util.freeCharPtrArray(branchName); @@ -864,6 +898,8 @@ public void close() { PyPowsyblApiHeader.freeArrayPointer(branchQ2); PyPowsyblApiHeader.freeArrayPointer(branchV1); PyPowsyblApiHeader.freeArrayPointer(branchV2); + PyPowsyblApiHeader.freeArrayPointer(branchAngle1); + PyPowsyblApiHeader.freeArrayPointer(branchAngle2); PyPowsyblApiHeader.freeArrayPointer(branchI1); PyPowsyblApiHeader.freeArrayPointer(branchI2); diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Grid2opCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Grid2opCFunctions.java index a9d0ed698f..8492d430d9 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Grid2opCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/grid2op/Grid2opCFunctions.java @@ -77,18 +77,23 @@ public enum Grid2opDoubleValueType { LOAD_P, LOAD_Q, LOAD_V, + LOAD_ANGLE, GENERATOR_P, GENERATOR_Q, GENERATOR_V, + GENERATOR_ANGLE, SHUNT_P, SHUNT_Q, SHUNT_V, + SHUNT_ANGLE, BRANCH_P1, BRANCH_P2, BRANCH_Q1, BRANCH_Q2, BRANCH_V1, BRANCH_V2, + BRANCH_ANGLE1, + BRANCH_ANGLE2, BRANCH_I1, BRANCH_I2, BRANCH_PERMANENT_LIMIT_A; diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 76c2271152..e2c2b5cb84 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -1119,18 +1119,23 @@ class Grid2opDoubleValueType: LOAD_P: ClassVar[Grid2opDoubleValueType] = ... LOAD_Q: ClassVar[Grid2opDoubleValueType] = ... LOAD_V: ClassVar[Grid2opDoubleValueType] = ... + LOAD_ANGLE: ClassVar[Grid2opDoubleValueType] = ... GENERATOR_P: ClassVar[Grid2opDoubleValueType] = ... GENERATOR_Q: ClassVar[Grid2opDoubleValueType] = ... GENERATOR_V: ClassVar[Grid2opDoubleValueType] = ... + GENERATOR_ANGLE: ClassVar[Grid2opDoubleValueType] = ... SHUNT_P: ClassVar[Grid2opDoubleValueType] = ... SHUNT_Q: ClassVar[Grid2opDoubleValueType] = ... SHUNT_V: ClassVar[Grid2opDoubleValueType] = ... + SHUNT_ANGLE: ClassVar[Grid2opDoubleValueType] = ... BRANCH_P1: ClassVar[Grid2opDoubleValueType] = ... BRANCH_P2: ClassVar[Grid2opDoubleValueType] = ... BRANCH_Q1: ClassVar[Grid2opDoubleValueType] = ... BRANCH_Q2: ClassVar[Grid2opDoubleValueType] = ... BRANCH_V1: ClassVar[Grid2opDoubleValueType] = ... BRANCH_V2: ClassVar[Grid2opDoubleValueType] = ... + BRANCH_ANGLE1: ClassVar[Grid2opDoubleValueType] = ... + BRANCH_ANGLE2: ClassVar[Grid2opDoubleValueType] = ... BRANCH_I1: ClassVar[Grid2opDoubleValueType] = ... BRANCH_I2: ClassVar[Grid2opDoubleValueType] = ... BRANCH_PERMANENT_LIMIT_A: ClassVar[Grid2opDoubleValueType] = ... diff --git a/tests/test_grid2op.py b/tests/test_grid2op.py index 844ec0a1a5..4645093aad 100644 --- a/tests/test_grid2op.py +++ b/tests/test_grid2op.py @@ -42,14 +42,17 @@ def test_backend(): npt.assert_allclose(np.array([600.0]), backend.get_double_value(grid2op.DoubleValueType.LOAD_P), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([200.0]), backend.get_double_value(grid2op.DoubleValueType.LOAD_Q), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([147.578618]), backend.get_double_value(grid2op.DoubleValueType.LOAD_V), rtol=TOLERANCE, atol=TOLERANCE) + npt.assert_allclose(np.array([-0.167804]), backend.get_double_value(grid2op.DoubleValueType.LOAD_ANGLE), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([302.78, 302.78]), backend.get_double_value(grid2op.DoubleValueType.GENERATOR_P), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([112.641, 112.641]), backend.get_double_value(grid2op.DoubleValueType.GENERATOR_Q), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([24.5, 24.5]), backend.get_double_value(grid2op.DoubleValueType.GENERATOR_V), rtol=TOLERANCE, atol=TOLERANCE) + npt.assert_allclose(np.array([0.040596, 0.040596]), backend.get_double_value(grid2op.DoubleValueType.GENERATOR_ANGLE), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_P), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_Q), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_V), rtol=TOLERANCE, atol=TOLERANCE) + npt.assert_allclose(np.array([]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_ANGLE), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([302.444, 302.444, 605.561, 600.867]), backend.get_double_value(grid2op.DoubleValueType.BRANCH_P1), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([-300.433, -300.433, -604.893, -600.]), backend.get_double_value(grid2op.DoubleValueType.BRANCH_P2), rtol=TOLERANCE, atol=TOLERANCE) @@ -57,6 +60,8 @@ def test_backend(): npt.assert_allclose(np.array([-137.188, -137.188, -197.48, -200.0]), backend.get_double_value(grid2op.DoubleValueType.BRANCH_Q2), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([402.142, 402.142, 24.5, 389.952]), backend.get_double_value(grid2op.DoubleValueType.BRANCH_V1), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([389.952, 389.952, 402.142, 147.578]), backend.get_double_value(grid2op.DoubleValueType.BRANCH_V2), rtol=TOLERANCE, atol=TOLERANCE) + npt.assert_allclose(np.array([0.0, 0.0, 0.040596, -0.061197]), backend.get_double_value(grid2op.DoubleValueType.BRANCH_ANGLE1), rtol=TOLERANCE, atol=TOLERANCE) + npt.assert_allclose(np.array([-0.061197, -0.061197, 0.0, -0.167804]), backend.get_double_value(grid2op.DoubleValueType.BRANCH_ANGLE2), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([456.768, 456.768, 15225.756, 977.985]), backend.get_double_value(grid2op.DoubleValueType.BRANCH_I1), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([488.992, 488.992, 913.545, 2474.263]), backend.get_double_value(grid2op.DoubleValueType.BRANCH_I2), rtol=TOLERANCE, atol=TOLERANCE) @@ -88,6 +93,7 @@ def test_backend_ieee14(): npt.assert_allclose(np.array([0.0]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_P), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([-21.184]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_Q), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([12.671]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_V), rtol=TOLERANCE, atol=TOLERANCE) + npt.assert_allclose(np.array([-0.260726]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_ANGLE), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([141.075, 136.35, 137.385, 137.634, 12.84, 12.671, 12.611, 12.682, 12.662, 12.604, 12.426]), backend.get_double_value(grid2op.DoubleValueType.LOAD_V), rtol=TOLERANCE, atol=TOLERANCE) backend.update_integer_value(grid2op.UpdateIntegerValueType.UPDATE_SHUNT_BUS, np.array([-1]), np.array([True])) @@ -95,6 +101,7 @@ def test_backend_ieee14(): backend.run_pf() npt.assert_allclose(np.array([0.0]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_Q), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([0.0]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_V), rtol=TOLERANCE, atol=TOLERANCE) + npt.assert_allclose(np.array([0.0]), backend.get_double_value(grid2op.DoubleValueType.SHUNT_ANGLE), rtol=TOLERANCE, atol=TOLERANCE) npt.assert_allclose(np.array([141.075, 136.35, 136.9, 137.313, 12.84, 12.398, 12.385, 12.567, 12.641, 12.564, 12.252]), backend.get_double_value(grid2op.DoubleValueType.LOAD_V), rtol=TOLERANCE, atol=TOLERANCE) From d6be59fe61e1874090a88b526157a4d6534d341b Mon Sep 17 00:00:00 2001 From: Lisrte Date: Tue, 18 Mar 2025 14:58:07 +0100 Subject: [PATCH 48/71] Nullable specific parameters category key (#969) Signed-off-by: lisrte Signed-off-by: Naledi EL CHEIKH --- .../java/com/powsybl/python/commons/Util.java | 21 +++++++++++--- .../python/loadflow/LoadFlowCFunctions.java | 2 +- .../python/loadflow/LoadFlowCUtils.java | 16 ----------- .../dataframe/DataframeMapperBuilderTest.java | 28 +++++++++++++++++++ 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/commons/Util.java b/java/pypowsybl/src/main/java/com/powsybl/python/commons/Util.java index 5359824b68..08a9eaf304 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/commons/Util.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/commons/Util.java @@ -8,8 +8,12 @@ package com.powsybl.python.commons; import com.powsybl.commons.datasource.CompressionFormat; +import com.powsybl.commons.parameters.Parameter; +import com.powsybl.commons.parameters.ParameterType; import com.powsybl.contingency.ContingencyContextType; import com.powsybl.dataframe.DataframeElementType; +import com.powsybl.dataframe.DataframeMapper; +import com.powsybl.dataframe.DataframeMapperBuilder; import com.powsybl.dataframe.SeriesDataType; import com.powsybl.dataframe.network.modifications.DataframeNetworkModificationType; import com.powsybl.dynamicsimulation.DynamicSimulationResult; @@ -21,7 +25,6 @@ import com.powsybl.openreac.parameters.input.algo.OpenReacSolverLogLevel; import com.powsybl.openreac.parameters.input.algo.ReactiveSlackBusesMode; import com.powsybl.openreac.parameters.output.OpenReacStatus; -import com.powsybl.python.commons.PyPowsyblApiHeader.*; import com.powsybl.python.dataframe.CDataframeHandler; import com.powsybl.security.LimitViolationType; import com.powsybl.sensitivity.SensitivityFunctionType; @@ -38,9 +41,7 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.function.BooleanSupplier; import java.util.function.IntSupplier; import java.util.function.LongSupplier; @@ -53,6 +54,18 @@ */ public final class Util { + public static final DataframeMapper, Void> SPECIFIC_PARAMETERS_MAPPER = new DataframeMapperBuilder, Parameter, Void>() + .itemsProvider(parameters -> parameters.stream() + .sorted(Comparator.comparing(Parameter::getCategoryKey, Comparator.nullsLast(Comparator.naturalOrder())).thenComparing(Parameter::getName)) + .toList()) + .stringsIndex("name", Parameter::getName) + .strings("category_key", p -> Objects.toString(p.getCategoryKey(), "")) + .strings("description", Parameter::getDescription) + .enums("type", ParameterType.class, Parameter::getType) + .strings("default", p -> Objects.toString(p.getDefaultValue(), "")) + .strings("possible_values", p -> p.getPossibleValues() == null ? "" : p.getPossibleValues().toString()) + .build(); + private Util() { } diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/loadflow/LoadFlowCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/loadflow/LoadFlowCFunctions.java index 6c81b66d8e..ff71bdef6f 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/loadflow/LoadFlowCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/loadflow/LoadFlowCFunctions.java @@ -199,7 +199,7 @@ static PyPowsyblApiHeader.ArrayPointer createL return doCatch(exceptionHandlerPtr, () -> { String providerName = CTypeUtil.toString(providerNamePtr); LoadFlowProvider provider = LoadFlowCUtils.getLoadFlowProvider(providerName); - return Dataframes.createCDataframe(LoadFlowCUtils.SPECIFIC_PARAMETERS_MAPPER, provider); + return Dataframes.createCDataframe(Util.SPECIFIC_PARAMETERS_MAPPER, provider.getSpecificParameters()); }); } diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/loadflow/LoadFlowCUtils.java b/java/pypowsybl/src/main/java/com/powsybl/python/loadflow/LoadFlowCUtils.java index c323b2e3f5..de29e49df6 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/loadflow/LoadFlowCUtils.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/loadflow/LoadFlowCUtils.java @@ -9,10 +9,6 @@ import com.powsybl.commons.PowsyblException; import com.powsybl.commons.extensions.Extension; -import com.powsybl.commons.parameters.Parameter; -import com.powsybl.commons.parameters.ParameterType; -import com.powsybl.dataframe.DataframeMapper; -import com.powsybl.dataframe.DataframeMapperBuilder; import com.powsybl.iidm.network.Country; import com.powsybl.loadflow.LoadFlowParameters; import com.powsybl.loadflow.LoadFlowProvider; @@ -21,9 +17,7 @@ import com.powsybl.python.commons.PyPowsyblConfiguration; import org.graalvm.nativeimage.UnmanagedMemory; -import java.util.Comparator; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; /** @@ -31,16 +25,6 @@ */ public final class LoadFlowCUtils { - static final DataframeMapper SPECIFIC_PARAMETERS_MAPPER = new DataframeMapperBuilder() - .itemsProvider(provider -> provider.getSpecificParameters().stream().sorted(Comparator.comparing(Parameter::getCategoryKey).thenComparing(Parameter::getName)).toList()) - .stringsIndex("name", Parameter::getName) - .strings("category_key", p -> Objects.toString(p.getCategoryKey(), "")) - .strings("description", Parameter::getDescription) - .enums("type", ParameterType.class, Parameter::getType) - .strings("default", p -> Objects.toString(p.getDefaultValue(), "")) - .strings("possible_values", p -> p.getPossibleValues() == null ? "" : p.getPossibleValues().toString()) - .build(); - private LoadFlowCUtils() { } diff --git a/java/pypowsybl/src/test/java/com/powsybl/dataframe/DataframeMapperBuilderTest.java b/java/pypowsybl/src/test/java/com/powsybl/dataframe/DataframeMapperBuilderTest.java index c880aa7801..c414ac0fa1 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/dataframe/DataframeMapperBuilderTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/dataframe/DataframeMapperBuilderTest.java @@ -8,19 +8,25 @@ package com.powsybl.dataframe; import com.google.common.base.Functions; +import com.powsybl.commons.parameters.Parameter; +import com.powsybl.commons.parameters.ParameterScope; +import com.powsybl.commons.parameters.ParameterType; import com.powsybl.dataframe.DataframeFilter.AttributeFilterType; import com.powsybl.dataframe.impl.DefaultDataframeHandler; +import com.powsybl.dataframe.impl.Series; import com.powsybl.dataframe.update.DefaultUpdatingDataframe; import com.powsybl.dataframe.update.TestDoubleSeries; import com.powsybl.dataframe.update.TestStringSeries; import com.powsybl.dataframe.update.UpdatingDataframe; import com.powsybl.dataframe.update.TestIntSeries; +import com.powsybl.python.network.Dataframes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.*; import java.util.stream.Collectors; +import static com.powsybl.python.commons.Util.SPECIFIC_PARAMETERS_MAPPER; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -302,4 +308,26 @@ void testFilterAttributes() { .containsExactly("id", "str", "color"); } + + @Test + void testSpecificParametersDataframesMapper() { + List parameters = List.of( + new Parameter("PARAM1", ParameterType.STRING, "Parameter 1", "empty"), + new Parameter(List.of("PARAM2", "PARAMETER 2"), ParameterType.STRING, "Parameter 2", + "KO", List.of("OK", "KO"), ParameterScope.FUNCTIONAL, "Test2"), + new Parameter("PARAM3", ParameterType.INTEGER, "Parameter 3", 0, + List.of(0, 1, 2, 3), ParameterScope.FUNCTIONAL, "Test1"), + new Parameter("PARAM4", ParameterType.DOUBLE, "Parameter 4", 0.0)); + List series = Dataframes.createSeries(SPECIFIC_PARAMETERS_MAPPER, parameters); + assertThat(series) + .extracting(Series::getName) + .containsExactly("name", "category_key", "description", "type", "default", "possible_values"); + assertThat(series).satisfiesExactly( + index -> assertThat(index.getStrings()).containsExactly("PARAM3", "PARAM2", "PARAM1", "PARAM4"), + cat -> assertThat(cat.getStrings()).containsExactly("Test1", "Test2", "", ""), + desc -> assertThat(desc.getStrings()).containsExactly("Parameter 3", "Parameter 2", "Parameter 1", "Parameter 4"), + type -> assertThat(type.getStrings()).containsExactly("INTEGER", "STRING", "STRING", "DOUBLE"), + def -> assertThat(def.getStrings()).containsExactly("0", "KO", "empty", "0.0"), + val -> assertThat(val.getStrings()).containsExactly("[0, 1, 2, 3]", "[OK, KO]", "", "")); + } } From 50f4823fcdaf77054a76b86376de48c7ffc48724 Mon Sep 17 00:00:00 2001 From: Christian Biasuzzi Date: Fri, 21 Mar 2025 15:27:28 +0100 Subject: [PATCH 49/71] adds a NadProfile param to the get NAD and write NAD methods (#942) * adds a branch_labels dataframe param inside the NadProfile * allows setting the branch labels from the dataframe * allows setting the direction of the arrows, from the dataframe * allows null columns, in the labels dataframe * forces the svg parameter EdgeNameDisplayed when the label datraframe is set Signed-off-by: Christian Biasuzzi Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 8 +- cpp/powsybl-cpp/powsybl-cpp.h | 4 +- cpp/pypowsybl-cpp/bindings.cpp | 6 +- docs/user_guide/network_visualization.rst | 26 +++++- .../python/network/NetworkCFunctions.java | 87 ++++++++++++++++++- pypowsybl/_pypowsybl.pyi | 4 +- pypowsybl/network/__init__.py | 1 + pypowsybl/network/impl/nad_profile.py | 36 ++++++++ pypowsybl/network/impl/network.py | 15 +++- tests/test_network.py | 25 +++++- 10 files changed, 194 insertions(+), 18 deletions(-) create mode 100644 pypowsybl/network/impl/nad_profile.py diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index ddca825bb2..14086986d0 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -722,11 +722,11 @@ std::vector getMatrixMultiSubstationSvgAndMetadata(const JavaHandle return svgAndMetadata.get(); } -void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions) { +void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, dataframe* branch_labels) { auto c_parameters = parameters.to_c_struct(); ToCharPtrPtr voltageLevelIdPtr(voltageLevelIds); PowsyblCaller::get()->callJava(::writeNetworkAreaDiagramSvg, network, (char*) svgFile.data(), (char*) metadataFile.data(), - voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions); + voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels); } std::string getNetworkAreaDiagramSvg(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters) { @@ -735,10 +735,10 @@ std::string getNetworkAreaDiagramSvg(const JavaHandle& network, const std::vecto return toString(PowsyblCaller::get()->callJava(::getNetworkAreaDiagramSvg, network, voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get())); } -std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions) { +std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, dataframe* branch_labels) { auto c_parameters = parameters.to_c_struct(); ToCharPtrPtr voltageLevelIdPtr(voltageLevelIds); - auto svgAndMetadataArrayPtr = PowsyblCaller::get()->callJava(::getNetworkAreaDiagramSvgAndMetadata, network, voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions); + auto svgAndMetadataArrayPtr = PowsyblCaller::get()->callJava(::getNetworkAreaDiagramSvgAndMetadata, network, voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels); ToStringVector svgAndMetadata(svgAndMetadataArrayPtr); return svgAndMetadata.get(); } diff --git a/cpp/powsybl-cpp/powsybl-cpp.h b/cpp/powsybl-cpp/powsybl-cpp.h index 0a34140238..7b35528292 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.h +++ b/cpp/powsybl-cpp/powsybl-cpp.h @@ -579,11 +579,11 @@ std::vector getMatrixMultiSubstationSvgAndMetadata(const JavaHandle std::vector getSingleLineDiagramComponentLibraryNames(); -void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions); +void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, dataframe* branch_labels); std::string getNetworkAreaDiagramSvg(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters); -std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions); +std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, dataframe* branch_labels); std::vector getNetworkAreaDiagramDisplayedVoltageLevels(const JavaHandle& network, const std::vector& voltageLevelIds, int depth); diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index b3c4b79e1e..4728e5936d 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -685,13 +685,15 @@ PYBIND11_MODULE(_pypowsybl, m) { m.def("write_network_area_diagram_svg", &pypowsybl::writeNetworkAreaDiagramSvg, "Write network area diagram SVG", py::arg("network"), py::arg("svg_file"), py::arg("metadata_file"), py::arg("voltage_level_ids"), - py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters"), py::arg("fixed_positions")); + py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters"), py::arg("fixed_positions"), + py::arg("branch_labels")); m.def("get_network_area_diagram_svg", &pypowsybl::getNetworkAreaDiagramSvg, "Get network area diagram SVG as a string", py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters")); m.def("get_network_area_diagram_svg_and_metadata", &pypowsybl::getNetworkAreaDiagramSvgAndMetadata, "Get network area diagram SVG and its metadata as a list of strings", - py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters"), py::arg("fixed_positions")); + py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters"), py::arg("fixed_positions"), + py::arg("branch_labels")); m.def("get_network_area_diagram_displayed_voltage_levels", &pypowsybl::getNetworkAreaDiagramDisplayedVoltageLevels, "Get network area diagram displayed voltage level", py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth")); diff --git a/docs/user_guide/network_visualization.rst b/docs/user_guide/network_visualization.rst index daa581b61f..17205f5d38 100644 --- a/docs/user_guide/network_visualization.rst +++ b/docs/user_guide/network_visualization.rst @@ -314,7 +314,31 @@ We can generate a network area diagram using fixed positions, defined in a dataf The optional parameter fixed_positions can also be set in the write_network_area_diagram function. Note that positions for elements not included in the dataframe are computed using the current layout algorithm. - + + +We can further customize the NAD diagram using the NadProfile. For example, to set the labels of the NAD branches and the arrows direction, using a dataframe: + +.. code-block:: python + + >>> import pandas as pd + >>> network = pp.network.create_four_substations_node_breaker_network() + >>> labels_df = pd.DataFrame.from_records(index='id', columns=['id', 'side1', 'middle', 'side2', 'arrow1', 'arrow2'], + data=[ + ('LINE_S2S3', 'L1_1', 'L1', 'L1_2', 'IN', 'IN'), + ('LINE_S3S4', 'L2_1', 'L2', 'L2_2', 'OUT', 'IN'), + ('TWT', 'TWT1_1', 'TWT1', 'TWT1_2', None, 'OUT') + ]) + >>> diagram_profile=pp.network.NadProfile(branch_labels=labels_df) + >>> pars=pp.network.NadParameters(edge_name_displayed=True) + >>> network.get_network_area_diagram(voltage_level_ids='S1VL1', depth=2, nad_parameters=pars, nad_profile=diagram_profile) + + In the dataframe: + - id is the branch id + - side1 and side2 define the labels along the two branch's edges + - middle defines the branch's label + - arrow1 and arrow2 define the direction of the arrows at the ends of the branch: 'IN' or 'OUT'. None (or an empty string) does not display the arrow. + + The optional parameter nad_profile can also be set in the write_network_area_diagram function. Network area diagram using geographical data -------------------------------------------- diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java index 455004898e..493e971e76 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java @@ -35,8 +35,14 @@ import com.powsybl.iidm.reducer.*; import com.powsybl.nad.NadParameters; import com.powsybl.nad.layout.*; +import com.powsybl.nad.model.BranchEdge; +import com.powsybl.nad.model.Edge; +import com.powsybl.nad.model.Graph; import com.powsybl.nad.model.Point; +import com.powsybl.nad.svg.EdgeInfo; import com.powsybl.nad.svg.SvgParameters; +import com.powsybl.nad.svg.iidm.DefaultLabelProvider; +import com.powsybl.nad.svg.iidm.LabelProviderFactory; import com.powsybl.python.commons.CTypeUtil; import com.powsybl.python.commons.Directives; import com.powsybl.python.commons.PyPowsyblApiHeader; @@ -1117,7 +1123,8 @@ public static PyPowsyblApiHeader.ArrayPointer getSingleLine public static void writeNetworkAreaDiagramSvg(IsolateThread thread, ObjectHandle networkHandle, CCharPointer svgFile, CCharPointer metadataFile, CCharPointerPointer voltageLevelIdsPointer, int voltageLevelIdCount, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, NadParametersPointer nadParametersPointer, - DataframePointer fixedPositions, ExceptionHandlerPointer exceptionHandlerPtr) { + DataframePointer fixedPositions, DataframePointer branchLabels, + ExceptionHandlerPointer exceptionHandlerPtr) { doCatch(exceptionHandlerPtr, () -> { Network network = ObjectHandles.getGlobal().get(networkHandle); String svgFileStr = CTypeUtil.toString(svgFile); @@ -1125,6 +1132,7 @@ public static void writeNetworkAreaDiagramSvg(IsolateThread thread, ObjectHandle List voltageLevelIds = toStringList(voltageLevelIdsPointer, voltageLevelIdCount); NadParameters nadParameters = convertNadParameters(nadParametersPointer, network); applyFixedPositions(fixedPositions, nadParameters); + applyCustomLabels(branchLabels, nadParameters); NetworkAreaDiagramUtil.writeSvg(network, voltageLevelIds, depth, svgFileStr, metadataFileStr, highNominalVoltageBound, lowNominalVoltageBound, nadParameters); }); } @@ -1192,16 +1200,91 @@ private static void applyFixedPositions(DataframePointer fixedPositions, NadPara } } + record CustomBranchLabels(String side1, String middle, String side2, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2) { + } + + private static String getValueFromSeriesOrNull(StringSeries series, int row) { + return (series != null) ? series.get(row) : null; + } + + private static EdgeInfo.Direction getDirectionFromSeriesOrNull(StringSeries series, int row) { + if (series == null) { + return null; + } + String dir = series.get(row); + return (dir != null && !dir.isEmpty()) ? EdgeInfo.Direction.valueOf(dir) : null; + } + + private static Map getNadCustomBranchLabels(int rowCount, StringSeries idSeries, + StringSeries side1Label, StringSeries middleLabel, + StringSeries side2Label, StringSeries arrow1, + StringSeries arrow2) { + Map nadCustomBranchLabels = new HashMap<>(); + for (int i = 0; i < rowCount; i++) { + String id = idSeries.get(i); + CustomBranchLabels labels = new CustomBranchLabels( + getValueFromSeriesOrNull(side1Label, i), + getValueFromSeriesOrNull(middleLabel, i), + getValueFromSeriesOrNull(side2Label, i), + getDirectionFromSeriesOrNull(arrow1, i), + getDirectionFromSeriesOrNull(arrow2, i) + ); + nadCustomBranchLabels.put(id, labels); + } + return nadCustomBranchLabels; + } + + private static LabelProviderFactory getCustomLabelProviderFactory(Map branchLabels) { + return (network, svgParameters) -> new DefaultLabelProvider(network, svgParameters) { + @Override + public Optional getEdgeInfo(Graph graph, BranchEdge edge, BranchEdge.Side side) { + CustomBranchLabels bl = branchLabels.get(edge.getEquipmentId()); + String label = null; + EdgeInfo.Direction arrowDirection = null; + if (bl != null) { + label = side == BranchEdge.Side.ONE ? bl.side1 : bl.side2; + arrowDirection = side == BranchEdge.Side.ONE ? bl.arrow1 : bl.arrow2; + } + return Optional.of(new EdgeInfo("ActivePower", arrowDirection, null, label)); + } + + @Override + public String getLabel(Edge edge) { + CustomBranchLabels bl = branchLabels.get(edge.getEquipmentId()); + return (bl != null) ? bl.middle : null; + } + }; + } + + private static void applyCustomLabels(DataframePointer customLabels, NadParameters nadParameters) { + UpdatingDataframe customLabelsDataframe = createDataframe(customLabels); + if (customLabelsDataframe != null) { + + //when the custom dataframe is defined, the displaying of the edge name is forced + nadParameters.getSvgParameters().setEdgeNameDisplayed(true); + + StringSeries idSeries = customLabelsDataframe.getStrings("id"); + int rowCount = customLabelsDataframe.getRowCount(); + + Map branchLabels = getNadCustomBranchLabels(rowCount, idSeries, customLabelsDataframe.getStrings("side1"), + customLabelsDataframe.getStrings("middle"), customLabelsDataframe.getStrings("side2"), + customLabelsDataframe.getStrings("arrow1"), customLabelsDataframe.getStrings("arrow2")); + + nadParameters.setLabelProviderFactory(getCustomLabelProviderFactory(branchLabels)); + } + } + @CEntryPoint(name = "getNetworkAreaDiagramSvgAndMetadata") public static ArrayPointer getNetworkAreaDiagramSvgAndMetadata(IsolateThread thread, ObjectHandle networkHandle, CCharPointerPointer voltageLevelIdsPointer, int voltageLevelIdCount, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, NadParametersPointer nadParametersPointer, - DataframePointer fixedPositions, ExceptionHandlerPointer exceptionHandlerPtr) { + DataframePointer fixedPositions, DataframePointer branchLabels, ExceptionHandlerPointer exceptionHandlerPtr) { return doCatch(exceptionHandlerPtr, () -> { Network network = ObjectHandles.getGlobal().get(networkHandle); List voltageLevelIds = toStringList(voltageLevelIdsPointer, voltageLevelIdCount); NadParameters nadParameters = convertNadParameters(nadParametersPointer, network); applyFixedPositions(fixedPositions, nadParameters); + applyCustomLabels(branchLabels, nadParameters); List svgAndMeta = NetworkAreaDiagramUtil.getSvgAndMetadata(network, voltageLevelIds, depth, highNominalVoltageBound, lowNominalVoltageBound, nadParameters); return createCharPtrArray(svgAndMeta); }); diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index e2c2b5cb84..205596e973 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -834,7 +834,7 @@ def get_security_analysis_provider_parameters_names(provider: str) -> List[str]: def get_sensitivity_analysis_provider_parameters_names(provider: str) -> List[str]: ... def get_limit_violations(result: JavaHandle) -> SeriesArray: ... def get_network_area_diagram_svg(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters) -> str: ... -def get_network_area_diagram_svg_and_metadata(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe]) -> List[str]: ... +def get_network_area_diagram_svg_and_metadata(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe]) -> List[str]: ... def get_network_area_diagram_displayed_voltage_levels(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int) -> List[str]: ... def get_network_elements_ids(network: JavaHandle, element_type: ElementType, nominal_voltages: List[float], countries: List[str], main_connected_component: bool, main_synchronous_component: bool, not_connected_to_same_bus_at_both_sides: bool) -> List[str]: ... def get_network_export_formats() -> List[str]: ... @@ -895,7 +895,7 @@ def update_connectable_status(arg0: JavaHandle, arg1: str, arg2: bool) -> bool: def update_network_elements_with_series(network: JavaHandle, array: Dataframe, element_type: ElementType, per_unit: bool, nominal_apparent_power: float) -> None: ... def update_switch_position(arg0: JavaHandle, arg1: str, arg2: bool) -> bool: ... def validate(network: JavaHandle) -> ValidationLevel: ... -def write_network_area_diagram_svg(network: JavaHandle, svg_file: str, metadata_file: str, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe]) -> None: ... +def write_network_area_diagram_svg(network: JavaHandle, svg_file: str, metadata_file: str, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe]) -> None: ... def write_single_line_diagram_svg(network: JavaHandle, container_id: str, svg_file: str, metadata_file: str, parameters: SldParameters) -> None: ... def write_matrix_multi_substation_single_line_diagram_svg(network: JavaHandle, matrix_ids: List[List[str]], svg_file: str, metadata_file: str, parameters: SldParameters) -> None: ... def add_network_element_properties(network: JavaHandle, dataframe: Dataframe) -> None: ... diff --git a/pypowsybl/network/__init__.py b/pypowsybl/network/__init__.py index 60c1c27c9f..6a24e0e11e 100644 --- a/pypowsybl/network/__init__.py +++ b/pypowsybl/network/__init__.py @@ -16,6 +16,7 @@ from .impl.sld_parameters import SldParameters from .impl.nad_parameters import NadLayoutType, EdgeInfoType from .impl.nad_parameters import NadParameters +from .impl.nad_profile import NadProfile from .impl.layout_parameters import LayoutParameters from .impl.network_creation_util import ( create_empty, diff --git a/pypowsybl/network/impl/nad_profile.py b/pypowsybl/network/impl/nad_profile.py new file mode 100644 index 0000000000..12f80398e6 --- /dev/null +++ b/pypowsybl/network/impl/nad_profile.py @@ -0,0 +1,36 @@ +# Copyright (c) 2025, RTE (http://www.rte-france.com) +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# SPDX-License-Identifier: MPL-2.0 +# +from typing import Optional + +from pandas import DataFrame + +import pypowsybl._pypowsybl as _pp +from pypowsybl.utils import _create_c_dataframe + +class NadProfile: + """ + This class represents parameters to customize a network area diagram (e.g., labels).""" + + _nad_branch_labels_metadata=[_pp.SeriesMetadata('id',0,True,False,False), + _pp.SeriesMetadata('side1',0,False,False,False), + _pp.SeriesMetadata('middle',0,False,False,False), + _pp.SeriesMetadata('side2',0,False,False,False), + _pp.SeriesMetadata('arrow1',0,False,False,False), + _pp.SeriesMetadata('arrow2',0,False,False,False)] + + def __init__(self, branch_labels: Optional[DataFrame] = None): + self._branch_labels = branch_labels + + @property + def branch_labels(self) -> Optional[DataFrame]: + """branch_labels""" + return self._branch_labels + + + def _create_nad_branch_labels_c_dataframe(self) -> Optional[_pp.Dataframe]: + return None if self._branch_labels is None else _create_c_dataframe(self._branch_labels.fillna(''), + NadProfile._nad_branch_labels_metadata) diff --git a/pypowsybl/network/impl/network.py b/pypowsybl/network/impl/network.py index 8f60755f60..1eec412c9c 100644 --- a/pypowsybl/network/impl/network.py +++ b/pypowsybl/network/impl/network.py @@ -43,6 +43,7 @@ from .node_breaker_topology import NodeBreakerTopology from .sld_parameters import SldParameters from .nad_parameters import NadParameters +from .nad_profile import NadProfile from .svg import Svg from .util import create_data_frame_from_series_array, ParamsDict @@ -369,7 +370,8 @@ def write_network_area_diagram(self, svg_file: PathOrStr, voltage_level_ids: Uni low_nominal_voltage_bound: float = -1, nad_parameters: NadParameters = None, metadata_file: PathOrStr = None, - fixed_positions: Optional[DataFrame] = None) -> None: + fixed_positions: Optional[DataFrame] = None, + nad_profile: NadProfile = None) -> None: """ Create a network area diagram in SVG format and write it to a file. @@ -382,6 +384,7 @@ def write_network_area_diagram(self, svg_file: PathOrStr, voltage_level_ids: Uni low_nominal_voltage_bound: low bound to filter voltage level according to nominal voltage nad_parameters: parameters for network area diagram fixed_positions: optional dataframe used to set fixed coordinates for diagram elements. Positions for elements not specified in the dataframe will be computed using the current layout. + nad_profile: parameters to customize the network area diagram """ svg_file = path_to_str(svg_file) if voltage_level_ids is None: @@ -391,7 +394,8 @@ def write_network_area_diagram(self, svg_file: PathOrStr, voltage_level_ids: Uni nad_p = nad_parameters._to_c_parameters() if nad_parameters is not None else _pp.NadParameters() # pylint: disable=protected-access _pp.write_network_area_diagram_svg(self._handle, svg_file, '' if metadata_file is None else path_to_str(metadata_file), voltage_level_ids, depth, high_nominal_voltage_bound, low_nominal_voltage_bound, nad_p, - None if fixed_positions is None else self._create_nad_positions_c_dataframe(fixed_positions)) + None if fixed_positions is None else self._create_nad_positions_c_dataframe(fixed_positions), + None if nad_profile is None else nad_profile._create_nad_branch_labels_c_dataframe()) # pylint: disable=protected-access def _create_nad_positions_c_dataframe(self, df: DataFrame) -> _pp.Dataframe: nad_positions_metadata=[_pp.SeriesMetadata('id',0,True,False,False), @@ -405,7 +409,8 @@ def _create_nad_positions_c_dataframe(self, df: DataFrame) -> _pp.Dataframe: def get_network_area_diagram(self, voltage_level_ids: Union[str, List[str]] = None, depth: int = 0, high_nominal_voltage_bound: float = -1, low_nominal_voltage_bound: float = -1, - nad_parameters: NadParameters = None, fixed_positions: Optional[DataFrame] = None) -> Svg: + nad_parameters: NadParameters = None, fixed_positions: Optional[DataFrame] = None, + nad_profile: NadProfile = None) -> Svg: """ Create a network area diagram. @@ -416,6 +421,7 @@ def get_network_area_diagram(self, voltage_level_ids: Union[str, List[str]] = No low_nominal_voltage_bound: low bound to filter voltage level according to nominal voltage nad_parameters: parameters for network area diagram fixed_positions: optional dataframe used to set fixed coordinates for diagram elements. Positions for elements not specified in the dataframe will be computed using the current layout. + nad_profile: parameters to customize the network area diagram Returns: the network area diagram @@ -427,7 +433,8 @@ def get_network_area_diagram(self, voltage_level_ids: Union[str, List[str]] = No nad_p = nad_parameters._to_c_parameters() if nad_parameters is not None else _pp.NadParameters() # pylint: disable=protected-access svg_and_metadata: List[str] = _pp.get_network_area_diagram_svg_and_metadata(self._handle, voltage_level_ids, depth, high_nominal_voltage_bound, low_nominal_voltage_bound, - nad_p, None if fixed_positions is None else self._create_nad_positions_c_dataframe(fixed_positions)) + nad_p, None if fixed_positions is None else self._create_nad_positions_c_dataframe(fixed_positions), + None if nad_profile is None else nad_profile._create_nad_branch_labels_c_dataframe()) # pylint: disable=protected-access return Svg(svg_and_metadata[0], svg_and_metadata[1]) diff --git a/tests/test_network.py b/tests/test_network.py index b0e742a82b..984f34bdc3 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -27,7 +27,7 @@ import pypowsybl.report as rp import util from pypowsybl import PyPowsyblError -from pypowsybl.network import ValidationLevel, SldParameters, NadLayoutType, NadParameters, LayoutParameters, EdgeInfoType +from pypowsybl.network import ValidationLevel, SldParameters, NadLayoutType, NadParameters, LayoutParameters, EdgeInfoType, NadProfile TEST_DIR = pathlib.Path(__file__).parent DATA_DIR = TEST_DIR.parent / 'data' @@ -1178,6 +1178,29 @@ def test_nad_fixed_positions(): assert exists(fixed_positions_svg_file) +def test_nad_profile(): + diagram_profile = NadProfile() + assert not diagram_profile.branch_labels + diagram_profile = NadProfile(branch_labels=None) + assert not diagram_profile.branch_labels + n = pp.network.create_ieee14() + branch_labels_df = pd.DataFrame.from_records(index='id', + data=[{'id': 'L1-5-1', 'side1': 'S1_1', 'middle': 'MIDDLE_1', 'side2': 'S2_1', 'arrow1': 'IN', 'arrow2': 'IN'}, + {'id': 'L2-5-1', 'side1': 'S1_2', 'middle': 'MIDDLE_2', 'side2': 'S2_2', 'arrow1': 'OUT', 'arrow2': 'OUT'}]) + diagram_profile=NadProfile(branch_labels=branch_labels_df) + assert isinstance(diagram_profile.branch_labels, pd.DataFrame) + pars=pp.network.NadParameters(edge_name_displayed=True) + nad1=n.get_network_area_diagram(voltage_level_ids='VL1', depth=1, nad_parameters=pars, nad_profile=diagram_profile) + assert re.search('.* 0 + + with tempfile.TemporaryDirectory() as tmp_dir_name: + tmp_dir_path = pathlib.Path(tmp_dir_name) + branch_labels_svg_file = tmp_dir_path.joinpath('test_branch_labels.svg') + n.write_network_area_diagram(branch_labels_svg_file, voltage_level_ids='VL1', depth=1, nad_parameters=pars, nad_profile=diagram_profile) + assert exists(branch_labels_svg_file) + + def test_current_limits(): network = pp.network.create_eurostag_tutorial_example1_network() with pytest.warns(DeprecationWarning, match=re.escape("get_current_limits is deprecated, use get_operational_limits instead")): From 0718eb507aa0d2a2577cefd08c9dc38e03fe345f Mon Sep 17 00:00:00 2001 From: Geoffroy Jamgotchian Date: Thu, 27 Mar 2025 14:07:26 +0100 Subject: [PATCH 50/71] Add DiscreteMeasurements extension dataframe (#975) Signed-off-by: Geoffroy Jamgotchian Signed-off-by: Naledi EL CHEIKH --- .../DiscreteMeasurementsDataframeAdder.java | 116 ++++++++++++++ ...DiscreteMeasurementsDataframeProvider.java | 145 ++++++++++++++++++ .../MeasurementsDataframeProvider.java | 2 +- tests/test_network_extensions.py | 16 +- 4 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/DiscreteMeasurementsDataframeAdder.java create mode 100644 java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/DiscreteMeasurementsDataframeProvider.java diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/DiscreteMeasurementsDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/DiscreteMeasurementsDataframeAdder.java new file mode 100644 index 0000000000..b5666a21ff --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/DiscreteMeasurementsDataframeAdder.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.dataframe.network.extensions; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.dataframe.SeriesMetadata; +import com.powsybl.dataframe.network.adders.AbstractSimpleAdder; +import com.powsybl.dataframe.network.adders.SeriesUtils; +import com.powsybl.dataframe.update.IntSeries; +import com.powsybl.dataframe.update.StringSeries; +import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.DiscreteMeasurement; +import com.powsybl.iidm.network.extensions.DiscreteMeasurementAdder; +import com.powsybl.iidm.network.extensions.DiscreteMeasurements; +import com.powsybl.iidm.network.extensions.DiscreteMeasurementsAdder; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class DiscreteMeasurementsDataframeAdder extends AbstractSimpleAdder { + + private static final List METADATA = List.of( + SeriesMetadata.stringIndex(DiscreteMeasurementsDataframeProvider.ELEMENT_ID), + SeriesMetadata.strings(DiscreteMeasurementsDataframeProvider.ID), + SeriesMetadata.strings(DiscreteMeasurementsDataframeProvider.TYPE), + SeriesMetadata.strings(DiscreteMeasurementsDataframeProvider.TAP_CHANGER), + SeriesMetadata.strings(DiscreteMeasurementsDataframeProvider.VALUE_TYPE), + SeriesMetadata.strings(DiscreteMeasurementsDataframeProvider.VALUE), + SeriesMetadata.booleans(DiscreteMeasurementsDataframeProvider.VALID) + ); + + private static class DiscreteMeasurementsSeries { + private final StringSeries ids; + private final StringSeries elementIds; + private final StringSeries types; + private final StringSeries tapChangers; + private final StringSeries valueTypes; + private final StringSeries values; + private final IntSeries valids; + private final Map> measurementsByElementId = new HashMap<>(); + + DiscreteMeasurementsSeries(UpdatingDataframe dataframe) { + this.elementIds = dataframe.getStrings(DiscreteMeasurementsDataframeProvider.ELEMENT_ID); + this.ids = dataframe.getStrings(DiscreteMeasurementsDataframeProvider.ID); + this.types = dataframe.getStrings(DiscreteMeasurementsDataframeProvider.TYPE); + this.tapChangers = dataframe.getStrings(DiscreteMeasurementsDataframeProvider.TAP_CHANGER); + this.valueTypes = dataframe.getStrings(DiscreteMeasurementsDataframeProvider.VALUE_TYPE); + this.values = dataframe.getStrings(DiscreteMeasurementsDataframeProvider.VALUE); + this.valids = dataframe.getInts(DiscreteMeasurementsDataframeProvider.VALID); + } + + void create(int row) { + String elementId = this.elementIds.get(row); + DiscreteMeasurements measurements = measurementsByElementId.get(elementId); + DiscreteMeasurementAdder adder = measurements.newDiscreteMeasurement(); + SeriesUtils.applyIfPresent(ids, row, adder::setId); + SeriesUtils.applyIfPresent(types, row, type -> adder.setType(DiscreteMeasurement.Type.valueOf(type))); + SeriesUtils.applyIfPresent(tapChangers, row, tapChanger -> adder.setTapChanger(DiscreteMeasurement.TapChanger.valueOf(tapChanger))); + SeriesUtils.applyIfPresent(values, row, value -> { + switch (DiscreteMeasurement.ValueType.valueOf(valueTypes.get(row))) { + case BOOLEAN -> adder.setValue(Boolean.parseBoolean(value)); + case INT -> adder.setValue(Integer.parseInt(value)); + case STRING -> adder.setValue(value); + } + }); + SeriesUtils.applyBooleanIfPresent(valids, row, adder::setValid); + adder.add(); + } + + @SuppressWarnings("unchecked") + > DiscreteMeasurements createDiscreteMeasurement(Identifiable identifiable) { + DiscreteMeasurementsAdder adder = identifiable.newExtension(DiscreteMeasurementsAdder.class); + return adder.add(); + } + + void removeAndInitialize(Network network, int row) { + String elementId = this.elementIds.get(row); + if (!measurementsByElementId.containsKey(elementId)) { + Identifiable identifiable = network.getIdentifiable(elementId); + if (identifiable == null) { + throw new PowsyblException("Invalid element id '" + elementId + "'"); + } + DiscreteMeasurementsDataframeProvider.removeDiscreteMeasurements(identifiable); + measurementsByElementId.put(elementId, createDiscreteMeasurement(identifiable)); + } + } + } + + @Override + public void addElements(Network network, UpdatingDataframe dataframe) { + DiscreteMeasurementsSeries series = new DiscreteMeasurementsSeries(dataframe); + for (int row = 0; row < dataframe.getRowCount(); row++) { + series.removeAndInitialize(network, row); + } + for (int row = 0; row < dataframe.getRowCount(); row++) { + series.create(row); + } + } + + @Override + public List> getMetadata() { + return Collections.singletonList(METADATA); + } +} diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/DiscreteMeasurementsDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/DiscreteMeasurementsDataframeProvider.java new file mode 100644 index 0000000000..28cfe476a3 --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/DiscreteMeasurementsDataframeProvider.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.dataframe.network.extensions; + +import com.google.auto.service.AutoService; +import com.powsybl.dataframe.network.ExtensionInformation; +import com.powsybl.dataframe.network.NetworkDataframeMapper; +import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder; +import com.powsybl.dataframe.network.adders.NetworkElementAdder; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.DiscreteMeasurement; +import com.powsybl.iidm.network.extensions.DiscreteMeasurements; +import org.jgrapht.alg.util.Pair; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +@AutoService(NetworkExtensionDataframeProvider.class) +public class DiscreteMeasurementsDataframeProvider extends AbstractSingleDataframeNetworkExtension { + + public static final String ELEMENT_ID = "element_id"; + public static final String ID = "id"; + public static final String TYPE = "type"; + public static final String TAP_CHANGER = "tap_changer"; + public static final String VALUE_TYPE = "value_type"; + public static final String VALUE = "value"; + public static final String VALID = "valid"; + + @Override + public String getExtensionName() { + return DiscreteMeasurements.NAME; + } + + @Override + public ExtensionInformation getExtensionInformation() { + return new ExtensionInformation(DiscreteMeasurements.NAME, "Provides discrete measurements about a specific equipment", + "index : element_id (str),id (str), type (str), tap_changer (str), value_type (str), value (str), valid (bool)"); + } + + @Override + public NetworkDataframeMapper createMapper() { + return NetworkDataframeMapperBuilder.ofStream(this::itemsStream) + .stringsIndex(ELEMENT_ID, DiscreteMeasurementInformation::getElementId) + .strings(ID, DiscreteMeasurementInformation::getId) + .strings(TYPE, info -> info.getType().toString()) + .strings(TAP_CHANGER, info -> info.getTapChanger() == null ? null : info.getTapChanger().toString()) + .strings(VALUE_TYPE, info -> info.getValueType().toString()) + .strings(VALUE, DiscreteMeasurementInformation::getValue) + .booleans(VALID, DiscreteMeasurementInformation::isValid) + .build(); + } + + @SuppressWarnings("unchecked") + static void removeDiscreteMeasurements(Identifiable identifiable) { + identifiable.removeExtension(DiscreteMeasurements.class); + } + + @Override + public void removeExtensions(Network network, List ids) { + ids.stream().filter(Objects::nonNull) + .map(network::getIdentifiable) + .filter(Objects::nonNull) + .forEach(DiscreteMeasurementsDataframeProvider::removeDiscreteMeasurements); + } + + static > DiscreteMeasurements getDiscreteMeasurements(Identifiable identifiable) { + return identifiable.getExtension(DiscreteMeasurements.class); + } + + private Stream itemsStream(Network network) { + return network.getIdentifiables().stream() + .map(identifiable -> Pair.of(identifiable.getId(), getDiscreteMeasurements(identifiable))) + .filter(pair -> pair.getSecond() != null) + .flatMap(pair -> pair.getSecond().getDiscreteMeasurements().stream() + .map(measurement -> new DiscreteMeasurementInformation(measurement, pair.getFirst()))); + } + + @Override + public NetworkElementAdder createAdder() { + return new DiscreteMeasurementsDataframeAdder(); + } + + private static class DiscreteMeasurementInformation { + private final String elementId; + private final String id; + private final DiscreteMeasurement.Type type; + private final DiscreteMeasurement.TapChanger tapChanger; + private final DiscreteMeasurement.ValueType valueType; + private final String value; + private final boolean valid; + + public DiscreteMeasurementInformation(DiscreteMeasurement measurement, String elementId) { + this.elementId = elementId; + this.id = measurement.getId(); + this.type = measurement.getType(); + this.tapChanger = measurement.getTapChanger(); + this.valueType = measurement.getValueType(); + // FIXME make object directly available in DiscreteMeasurement interface + this.value = switch (measurement.getValueType()) { + case BOOLEAN -> Boolean.toString(measurement.getValueAsBoolean()); + case INT -> Integer.toString(measurement.getValueAsInt()); + case STRING -> measurement.getValueAsString(); + }; + this.valid = measurement.isValid(); + } + + public String getId() { + return id; + } + + public String getElementId() { + return elementId; + } + + public DiscreteMeasurement.Type getType() { + return type; + } + + public DiscreteMeasurement.TapChanger getTapChanger() { + return tapChanger; + } + + public DiscreteMeasurement.ValueType getValueType() { + return valueType; + } + + public String getValue() { + return value; + } + + public boolean isValid() { + return valid; + } + } +} diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/MeasurementsDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/MeasurementsDataframeProvider.java index b2b5db784f..1de5e01217 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/MeasurementsDataframeProvider.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/MeasurementsDataframeProvider.java @@ -44,7 +44,7 @@ public String getExtensionName() { @Override public ExtensionInformation getExtensionInformation() { - return new ExtensionInformation(Measurements.NAME, "Provides measurement about a specific equipment", + return new ExtensionInformation(Measurements.NAME, "Provides measurements about a specific equipment", "index : element_id (str),id (str), type (str), standard_deviation (float), value (float), valid (bool)"); } diff --git a/tests/test_network_extensions.py b/tests/test_network_extensions.py index 0290413835..93d6c7a380 100644 --- a/tests/test_network_extensions.py +++ b/tests/test_network_extensions.py @@ -222,6 +222,20 @@ def test_measurements(): assert n.get_extensions(extension_name).empty +def test_discrete_measurements(): + n = pn.create_four_substations_node_breaker_network() + extension_name = 'discreteMeasurements' + extensions = n.get_extensions(extension_name) + assert extensions.empty + n.create_extensions(extension_name, id='TWT_TAP_TS', element_id='TWT', type='TAP_POSITION', + tap_changer='PHASE_TAP_CHANGER', value_type='INT', value='17', valid=True) + extensions = n.get_extensions(extension_name) + expected = pd.DataFrame(index=pd.Series(name='element_id', data=['TWT']), + columns=['id', 'type', 'tap_changer', 'value_type', 'value', 'valid'], + data=[['TWT_TAP_TS', 'TAP_POSITION', 'PHASE_TAP_CHANGER', 'INT', '17', True]]) + pd.testing.assert_frame_equal(expected, extensions, check_dtype=False) + + def test_injection_observability(): n = pn.create_four_substations_node_breaker_network() extension_name = 'injectionObservability' @@ -533,7 +547,7 @@ def test_get_extensions_information(): assert extensions_information.loc['cgmesMetadataModels']['attributes'] == ('index : id (str), cgmes_subset (str), description (str), ' \ 'version (int), modeling_authority_set (str), profiles (str), ' \ 'dependent_on (str), supersedes (str) ') - assert extensions_information.loc['measurements']['detail'] == 'Provides measurement about a specific equipment' + assert extensions_information.loc['measurements']['detail'] == 'Provides measurements about a specific equipment' assert extensions_information.loc['measurements']['attributes'] == 'index : element_id (str),id (str), type (str), ' \ 'standard_deviation (float), value (float), valid (bool)' assert extensions_information.loc['branchObservability']['detail'] == 'Provides information about the observability of a branch' From 8373d3362645a2594b2ecb0836a1336789c5540d Mon Sep 17 00:00:00 2001 From: alicecaron Date: Mon, 31 Mar 2025 10:37:35 +0200 Subject: [PATCH 51/71] Add documentation about RAO beta implementation (#958) Signed-off-by: CARON Alice Signed-off-by: Naledi EL CHEIKH --- docs/reference/index.rst | 1 + docs/reference/rao.rst | 39 +++++++++++++++++++++++++++++++++++ docs/user_guide/index.rst | 1 + docs/user_guide/rao.rst | 43 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 docs/reference/rao.rst create mode 100644 docs/user_guide/rao.rst diff --git a/docs/reference/index.rst b/docs/reference/index.rst index f53780087c..fe87c97378 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -9,6 +9,7 @@ of pypowsybl classes and methods. network loadflow + rao security sensitivity flowdecomposition diff --git a/docs/reference/rao.rst b/docs/reference/rao.rst new file mode 100644 index 0000000000..563a563e56 --- /dev/null +++ b/docs/reference/rao.rst @@ -0,0 +1,39 @@ +Remedial action optimizer (RAO) +==================================== + +.. module:: pypowsybl.rao + +The RAO module allows to optimize remedial actions for electrical power systems (coordinated capacity calculation, local security analysis and coordinated security analysis). + +Run a RAO +----------------------- + +You can run a RAO using the following methods: + +.. autosummary:: + :nosignatures: + :toctree: api/ + + +Parameters +---------- + +The execution of the RAO can be customized using file parameters. + +.. autosummary:: + :nosignatures: + :toctree: api/ + + Parameters + +Results +------- + +When the RAO is completed, you can inspect its results: + +.. autosummary:: + :nosignatures: + :toctree: api/ + + RaoResult + diff --git a/docs/user_guide/index.rst b/docs/user_guide/index.rst index 4f020ab8d1..ec785b1b3a 100644 --- a/docs/user_guide/index.rst +++ b/docs/user_guide/index.rst @@ -12,6 +12,7 @@ relying as much as possible on practical examples. network_visualization per_unit loadflow + rao security sensitivity logging diff --git a/docs/user_guide/rao.rst b/docs/user_guide/rao.rst new file mode 100644 index 0000000000..32dbc1c243 --- /dev/null +++ b/docs/user_guide/rao.rst @@ -0,0 +1,43 @@ +Running a RAO +=========================== + +The RAO is currently in **beta** version. + +You can use the module :mod:`pypowsybl.rao` in order to perform a remedial actions optimization on a network. + +For detailed documentation of involved classes and methods, please refer to the :mod:`API reference `. + +For detailed documentation of the Powsybl OpenRAO please refer to the `PowSyBl RAO documentation `_. + +Inputs for a RAO +---------------- +To run a RAO you need: + +- a network in a PyPowsybl supported exchange format +- a CRAC file (Contingency list, Remedial Actions and additional Constraints) in json +- optionally a GLSK file (Generation and Load Shift Keys) in json +- optionally a parameters file, in json, allowing to override the RAO parameters + +Here is a code example of how to configure and run the RAO: + +.. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> import pypowsybl as pp + >>> from pypowsybl.rao import Parameters as RaoParameters + >>> + >>> network = pp.network.load(str(DATA_DIR.joinpath("rao/rao_network.uct"))) + >>> parameters = RaoParameters() + >>> parameters.load_from_file_source(str(DATA_DIR.joinpath("rao/rao_parameters.json"))) + >>> rao_runner = pp.rao.create_rao() + >>> rao_runner.set_crac_file_source(network, str(DATA_DIR.joinpath("rao/rao_crac.json"))) + >>> rao_runner.set_glsk_file_source(network, str(DATA_DIR.joinpath("rao/rao_glsk.xml"))) + >>> rao_result = rao_runner.run(network, parameters) + >>> rao_result.status() + + + +Outputs of a RAO +---------------- + +The RAO result is readable in a `RaoResult` object that can be serialized in json. It contains the optimal list of remedial actions to be applied in both basecase and after contingencies provided in the input CRAC file. From eaaae6ac4cb1a7db182ee4e055fff2b055312dee Mon Sep 17 00:00:00 2001 From: Nicolas Rol Date: Thu, 3 Apr 2025 13:00:49 +0200 Subject: [PATCH 52/71] Switch snapshot ci to new app and rework notification design (#938) * rework CI snapshot * add print * fix jobs name * format results * change references from integration to snapshot Signed-off-by: Nicolas Rol Signed-off-by: Naledi EL CHEIKH --- .github/workflows/scripts/build_module.sh | 8 +- .../scripts/check_snapshot_branch.sh | 14 +- .github/workflows/snapshot-ci.yml | 463 ++++++++++++++---- 3 files changed, 375 insertions(+), 110 deletions(-) diff --git a/.github/workflows/scripts/build_module.sh b/.github/workflows/scripts/build_module.sh index 7c1673ed94..0d712940bf 100755 --- a/.github/workflows/scripts/build_module.sh +++ b/.github/workflows/scripts/build_module.sh @@ -2,15 +2,15 @@ MODULE_NAME=$1 COMMAND=$2 +CURRENT_OS=$3 +CURRENT_PYTHON=$4 echo "Building $MODULE_NAME..." $COMMAND BUILD_EXIT=$? - if [ $BUILD_EXIT -ne 0 ]; then - echo "❌ $MODULE_NAME build FAILED" >> $BUILD_STATUS + printf 'BUILD_RESULT=%s;%s;%s;failure\n' "$CURRENT_OS" "$CURRENT_PYTHON" "$MODULE_NAME" >> "$GITHUB_OUTPUT" else - echo "✅ $MODULE_NAME build SUCCESS" >> $BUILD_STATUS + printf 'BUILD_RESULT=%s;%s;%s;success\n' "$CURRENT_OS" "$CURRENT_PYTHON" "$MODULE_NAME" >> "$GITHUB_OUTPUT" fi - exit $BUILD_EXIT \ No newline at end of file diff --git a/.github/workflows/scripts/check_snapshot_branch.sh b/.github/workflows/scripts/check_snapshot_branch.sh index 41c3712cc4..3a914670a2 100755 --- a/.github/workflows/scripts/check_snapshot_branch.sh +++ b/.github/workflows/scripts/check_snapshot_branch.sh @@ -3,13 +3,15 @@ repo=$1 core_version=$2 -SNAPSHOT_BRANCH=$(git ls-remote --heads "$repo" | grep -E "refs/heads/$(echo $core_version | grep -q SNAPSHOT && echo "$core_version" || echo "$core_version-SNAPSHOT")" | sed 's/.*refs\/heads\///') +# Add "-SNAPSHOT" to powsybl-core version if not already there +core_snapshot_version=$(echo "$core_version" | grep -q SNAPSHOT && echo "$core_version" || echo "$core_version-SNAPSHOT") + +# Find if an CI snapshot branch exists +SNAPSHOT_BRANCH=$(git ls-remote --heads "$repo" | grep -E "refs/heads/ci/core-$core_snapshot_version" | sed 's/.*refs\/heads\///') if [ -n "$SNAPSHOT_BRANCH" ]; then echo "SNAPSHOT VERSION EXIST: $SNAPSHOT_BRANCH" - echo "SNAPSHOT_BRANCH=$SNAPSHOT_BRANCH" >> $GITHUB_ENV + echo "SNAPSHOT_BRANCH=$SNAPSHOT_BRANCH" >> "$GITHUB_ENV" else echo "No SNAPSHOT branch found" - echo "SNAPSHOT_BRANCH=main" >> $GITHUB_ENV -fi - - + echo "SNAPSHOT_BRANCH=main" >> "$GITHUB_ENV" +fi \ No newline at end of file diff --git a/.github/workflows/snapshot-ci.yml b/.github/workflows/snapshot-ci.yml index d82834956a..5a5141b1ae 100644 --- a/.github/workflows/snapshot-ci.yml +++ b/.github/workflows/snapshot-ci.yml @@ -5,23 +5,26 @@ on: schedule: - cron: '0 3 * * *' -env: - BUILD_STATUS: ${{ github.workspace }}/build_status.txt - jobs: build_pypowsybl: name: Build ${{ matrix.config.name }} ${{ matrix.python.name }} wheel runs-on: ${{ matrix.config.os }} outputs: - build_status_output: ${{ steps.build_status_step.outputs.build_status_output }} #Output job with build_status.txt content created in Read Build Status step + core-version: ${{ env.CORE_VERSION }} + olf-version: ${{ env.OLF_VERSION }} + diagram-version: ${{ env.DIAGRAM_VERSION }} + entsoe-version: ${{ env.ENTSOE_VERSION }} + openrao-version: ${{ env.OPENRAO_VERSION }} + dependencies-version: ${{ env.DEPENDENCIES_VERSION }} + pypowsybl-branch: ${{ env.SNAPSHOT_BRANCH }} strategy: matrix: config: - - { name: ubuntu, os: ubuntu-20.04} + - { name: ubuntu, os: ubuntu-20.04 } - { name: darwin, os: macos-13, macosx_deployment_target: "10.16", bdist_wheel_args: "--plat-name macosx-11.0-x86_64" } - - { name: darwin-arm64, os: macos-14, macosx_deployment_target: "11", bdist_wheel_args: "--plat-name macosx-11.0-arm64"} - - { name: windows, os: windows-2022} + - { name: darwin-arm64, os: macos-14, macosx_deployment_target: "11", bdist_wheel_args: "--plat-name macosx-11.0-arm64" } + - { name: windows, os: windows-2022 } python: - { name: cp38, version: '3.8' } - { name: cp39, version: '3.9' } @@ -35,7 +38,7 @@ jobs: steps: - #SETUP PYTHON + # Setup Python - name: Set up Python ${{ matrix.python.version }} uses: actions/setup-python@v5 with: @@ -43,7 +46,7 @@ jobs: - name: Install Python dependencies run: python -m pip install --upgrade pip - #SETUP GRAALVM + # Setup GraalVM - name: Setup GraalVM uses: graalvm/setup-graalvm@v1 with: @@ -51,7 +54,7 @@ jobs: distribution: 'graalvm' github-token: ${{ secrets.GITHUB_TOKEN }} - #DEFINE SCRIPTS PATH + # Define scripts path - name: Set up script path run: | SCRIPTS_PATH="${GITHUB_WORKSPACE}/scripts/.github/workflows/scripts" @@ -60,151 +63,234 @@ jobs: fi echo "SCRIPTS_PATH=$SCRIPTS_PATH" >> $GITHUB_ENV - #BUILD CORE + # Build powsybl-core on main branch - name: Checkout core sources - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: powsybl/powsybl-core ref: main path: powsybl-core - - name: Build CORE + + - name: Build powsybl-core run: mvn -batch-mode --no-transfer-progress clean install -DskipTests working-directory: ./powsybl-core - - name: get CORE_VERSION + + - name: Get powsybl-core version run: echo "CORE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV working-directory: ./powsybl-core - #CHECKOUT SCRIPT - #The script check_snapshot_branch.sh is located in the workflow folder of the pypowsybl repository - #It is necessary for checking out the integration branch if it exists (pypowsybl include) + # Checkout script + # The script check_snapshot_branch.sh is located in the workflow folder of the repository + # It is necessary for checking out the integration branch if it exists - name: Checkout script - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: sparse-checkout: | .github sparse-checkout-cone-mode: false path: scripts - #BUILD OPENLOADFLOW - - name: Checking for openloadflow snapshot branch - run : ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/powsybl-open-loadflow.git" ${{ env.CORE_VERSION }} - - name: Checkout openloadflow sources - uses: actions/checkout@v4 + # Build Open-Loadflow + - name: Checking for powsybl-open-loadflow snapshot branch + run: ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/powsybl-open-loadflow.git" ${{ env.CORE_VERSION }} + + - name: Checkout powsybl-open-loadflow sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: powsybl/powsybl-open-loadflow ref: ${{ env.SNAPSHOT_BRANCH }} path: powsybl-open-loadflow - - name: Change core version + + - name: Change core version in pom.xml run: mvn versions:set-property -Dproperty=powsybl-core.version -DnewVersion=${{ env.CORE_VERSION}} -DgenerateBackupPoms=false working-directory: ./powsybl-open-loadflow - - name: Build LOADFLOW + + - name: Build powsybl-open-loadflow + id: build_olf continue-on-error: true - run: ${{ env.SCRIPTS_PATH }}/build_module.sh "LOADFLOW" "mvn -batch-mode --no-transfer-progress clean install" + run: ${{ env.SCRIPTS_PATH }}/build_module.sh "powsybl-open-loadflow" "mvn -batch-mode --no-transfer-progress clean install" ${{ matrix.config.name }} ${{ matrix.python.name }} working-directory: ./powsybl-open-loadflow - - name: Get LOADFLOW_VERSION - run: echo "LOADFLOW_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV + + - name: Store job result + if: always() + run: | + echo "${{ steps.build_olf.outputs.BUILD_RESULT }}" >> job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + + - name: Print file + run: | + echo "=============================================" + cat job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + echo "=============================================" + + - name: Get OLF_VERSION + run: echo "OLF_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV working-directory: ./powsybl-open-loadflow - #BUILD DIAGRAM + # Build powsybl-diagram - name: Checking for diagram snapshot branch - run : ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/powsybl-diagram.git" ${{ env.CORE_VERSION }} + run: ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/powsybl-diagram.git" ${{ env.CORE_VERSION }} + - name: Checkout diagram sources - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: powsybl/powsybl-diagram ref: ${{ env.SNAPSHOT_BRANCH }} path: powsybl-diagram - - name: Change core version + + - name: Change core version in pom.xml run: mvn versions:set-property -Dproperty=powsybl-core.version -DnewVersion=${{ env.CORE_VERSION}} -DgenerateBackupPoms=false working-directory: ./powsybl-diagram - - name: Build DIAGRAM + + - name: Build powsybl-diagram + id: build_diagram continue-on-error: true - run: ${{ env.SCRIPTS_PATH }}/build_module.sh "DIAGRAM" "mvn -batch-mode --no-transfer-progress clean install" + run: ${{ env.SCRIPTS_PATH }}/build_module.sh "powsybl-diagram" "mvn -batch-mode --no-transfer-progress clean install" ${{ matrix.config.name }} ${{ matrix.python.name }} working-directory: ./powsybl-diagram + + - name: Store job result + if: always() + run: | + echo "${{ steps.build_diagram.outputs.BUILD_RESULT }}" >> job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + + - name: Print file + run: | + echo "=============================================" + cat job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + echo "=============================================" + - name: Get DIAGRAM_VERSION version run: echo "DIAGRAM_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV working-directory: ./powsybl-diagram - #BUILD ENTSOE - - name: Checking for entsoe snapshot branch - run : ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/powsybl-entsoe.git" ${{ env.CORE_VERSION }} - - name: Checkout entsoe sources - uses: actions/checkout@v4 + # Build powsybl-entsoe + - name: Checking for powsybl-entsoe snapshot branch + run: ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/powsybl-entsoe.git" ${{ env.CORE_VERSION }} + + - name: Checkout powsybl-entsoe sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: powsybl/powsybl-entsoe ref: ${{ env.SNAPSHOT_BRANCH }} path: powsybl-entsoe - - name: Change core/loadflow version + + - name: Change core/loadflow version in pom.xml run: | mvn versions:set-property -Dproperty=powsyblcore.version -DnewVersion=${{ env.CORE_VERSION}} -DgenerateBackupPoms=false - mvn versions:set-property -Dproperty=powsyblopenloadflow.version -DnewVersion=${{ env.LOADFLOW_VERSION}} -DgenerateBackupPoms=false + mvn versions:set-property -Dproperty=powsyblopenloadflow.version -DnewVersion=${{ env.OLF_VERSION}} -DgenerateBackupPoms=false working-directory: ./powsybl-entsoe - - name: Build ENTSOE + + - name: Build powsybl-entsoe + id: build_entsoe continue-on-error: true - run: ${{ env.SCRIPTS_PATH }}/build_module.sh "ENTSOE" "mvn -batch-mode --no-transfer-progress clean install" + run: ${{ env.SCRIPTS_PATH }}/build_module.sh "powsybl-entsoe" "mvn -batch-mode --no-transfer-progress clean install" ${{ matrix.config.name }} ${{ matrix.python.name }} working-directory: ./powsybl-entsoe + + - name: Store job result + if: always() + run: | + echo "${{ steps.build_entsoe.outputs.BUILD_RESULT }}" >> job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + + - name: Print file + run: | + echo "=============================================" + cat job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + echo "=============================================" + - name: Get ENTSOE_VERSION run: echo "ENTSOE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV working-directory: ./powsybl-entsoe - #BUILD OPENRAO - - name: Checking for openrao snapshot branch - run : ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/powsybl-open-rao.git" ${{ env.CORE_VERSION }} - - name: Checkout openrao sources - uses: actions/checkout@v4 + # Build powsybl-open-rao + - name: Checking for powsybl-open-rao snapshot branch + run: ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/powsybl-open-rao.git" ${{ env.CORE_VERSION }} + + - name: Checkout powsybl-open-rao sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: powsybl/powsybl-open-rao ref: ${{ env.SNAPSHOT_BRANCH }} path: powsybl-openrao - - name: Change core/entsoe/loadflow version + + - name: Change core/entsoe/loadflow version in pom.xml run: | mvn versions:set-property -Dproperty=powsybl.core.version -DnewVersion=${{ env.CORE_VERSION}} -DgenerateBackupPoms=false mvn versions:set-property -Dproperty=powsybl.entsoe.version -DnewVersion=${{ env.ENTSOE_VERSION}} -DgenerateBackupPoms=false - mvn versions:set-property -Dproperty=powsybl.openloadflow.version -DnewVersion=${{ env.LOADFLOW_VERSION}} -DgenerateBackupPoms=false + mvn versions:set-property -Dproperty=powsybl.openloadflow.version -DnewVersion=${{ env.OLF_VERSION}} -DgenerateBackupPoms=false working-directory: ./powsybl-openrao - - name: Build OPENRAO + + - name: Build powsybl-open-rao + id: build_rao continue-on-error: true - run: ${{ env.SCRIPTS_PATH }}/build_module.sh "OPENRAO" "mvn -batch-mode --no-transfer-progress clean install" + run: ${{ env.SCRIPTS_PATH }}/build_module.sh "powsybl-open-rao" "mvn -batch-mode --no-transfer-progress clean install" ${{ matrix.config.name }} ${{ matrix.python.name }} working-directory: ./powsybl-openrao + + - name: Store job result + if: always() + run: | + echo "${{ steps.build_rao.outputs.BUILD_RESULT }}" >> job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + + - name: Print file + run: | + echo "=============================================" + cat job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + echo "=============================================" + - name: Get OPENRAO_VERSION run: echo "OPENRAO_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV working-directory: ./powsybl-openrao - #BUILD DYNAWO - - name: Checking for dynawo snapshot branch + # Build powsybl-dynawo + - name: Checking for powsybl-dynawo snapshot branch run: ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/powsybl-dynawo.git" ${{ env.CORE_VERSION }} - - name: Checkout dynawo sources - uses: actions/checkout@v4 + + - name: Checkout powsybl-dynawo sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: powsybl/powsybl-dynawo ref: ${{ env.SNAPSHOT_BRANCH }} path: powsybl-dynawo - - name: Change core version + + - name: Change core version in pom.xml run: mvn versions:set-property -Dproperty=powsybl-core.version -DnewVersion=${{ env.CORE_VERSION}} -DgenerateBackupPoms=false working-directory: ./powsybl-dynawo - - name: Build DYNAWO + + - name: Build powsybl-dynawo + id: build_dynawo continue-on-error: true - run: ${{ env.SCRIPTS_PATH }}/build_module.sh "DYNAWO" "mvn -batch-mode --no-transfer-progress clean install" + run: ${{ env.SCRIPTS_PATH }}/build_module.sh "powsybl-dynawo" "mvn -batch-mode --no-transfer-progress clean install" ${{ matrix.config.name }} ${{ matrix.python.name }} working-directory: ./powsybl-dynawo + + - name: Store job result + if: always() + run: | + echo "${{ steps.build_dynawo.outputs.BUILD_RESULT }}" >> job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + + - name: Print file + run: | + echo "=============================================" + cat job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + echo "=============================================" + - name: Get DYNAWO_VERSION run: echo "DYNAWO_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV working-directory: ./powsybl-dynawo - #CHECKOUT_PYPOWSYBL_DEPENCIES + # Checkout powsybl-dependencies - name: Checkout powsybl-dependencies sources - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: powsybl/powsybl-dependencies ref: main path: powsybl-dependencies + - name: Get DEPENDENCIES_VERSION run: echo "DEPENDENCIES_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV working-directory: ./powsybl-dependencies - #UPDATE/INSTALL_PYPOWSYBL_DEPENCIES - - name: Update dependencies versions + # Update or install powsybl-dependencies + - name: Update powsybl-dependencies versions run: | - mvn versions:set-property -Dproperty=powsybl-open-loadflow.version -DnewVersion=$LOADFLOW_VERSION -DgenerateBackupPoms=false + mvn versions:set-property -Dproperty=powsybl-open-loadflow.version -DnewVersion=$OLF_VERSION -DgenerateBackupPoms=false mvn versions:set-property -Dproperty=powsybl-core.version -DnewVersion=$CORE_VERSION -DgenerateBackupPoms=false mvn versions:set-property -Dproperty=powsybl-diagram.version -DnewVersion=$DIAGRAM_VERSION -DgenerateBackupPoms=false mvn versions:set-property -Dproperty=powsybl-dynawo.version -DnewVersion=$DYNAWO_VERSION -DgenerateBackupPoms=false @@ -217,73 +303,250 @@ jobs: run: mvn -batch-mode --no-transfer-progress clean install working-directory: ./powsybl-dependencies - #BUILD PYPOWSYBL - - name: Checking dor pypowsybl snapshot branch + # Build pypowsybl + - name: Checking for pypowsybl snapshot branch run: ${{ env.SCRIPTS_PATH }}/check_snapshot_branch.sh "https://github.com/powsybl/pypowsybl.git" ${{ env.CORE_VERSION }} + - name: Checkout pypowsybl - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: powsybl/pypowsybl ref: ${{ env.SNAPSHOT_BRANCH }} path: pypowsybl submodules: true - - name: update java/pom.xml + + - name: Update java/pom.xml run: mvn versions:set-property -Dproperty=powsybl-dependencies.version -DnewVersion=$DEPENDENCIES_VERSION -DgenerateBackupPoms=false working-directory: ./pypowsybl/java + - name: Install requirement.txt run: pip3 install -r requirements.txt working-directory: ./pypowsybl + - name: Build wheel run: python3 setup.py bdist_wheel working-directory: ./pypowsybl + - name: Install wheel run: python -m pip install dist/*.whl --user working-directory: ./pypowsybl - - name: check pypowsybl versions + + - name: Check pypowsybl versions working-directory: ./pypowsybl/tests run: python3 basic_import_test.py + - name: Run tests working-directory: ./pypowsybl/tests run: pytest + - name: Upload wheel - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # 4.6.0 with: name: pypowsybl-wheel-${{ matrix.config.name }}-${{ matrix.python.name }} path: ./pypowsybl/dist/*.whl - - name: Record Job Name - if: failure() + - name: Store job result + if: always() run: | - echo "Failed job : Build ${{ matrix.config.name }} ${{ matrix.python.name }} wheel" >> $BUILD_STATUS + echo "${{ matrix.config.name }};${{ matrix.python.name }};wheels;${{ job.status }}" >> job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt - - name: Read Build Status - if: always() - id: build_status_step + - name: Print file run: | - echo "=== BUILD STATUS REPORT ===" - cat $BUILD_STATUS - echo "==========================" - echo "build_status_output<> $GITHUB_OUTPUT - cat $BUILD_STATUS >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + echo "=============================================" + cat job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt + echo "=============================================" + - name: Upload job result + if: always() + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # 4.6.0 + with: + name: job-results_${{ matrix.config.name }}-${{ matrix.python.name }} + path: job_result_${{ matrix.config.name }}-${{ matrix.python.name }}.txt - #SLACK NOTIFICATION ON FAILURE + # Slack notification notify_slack: - needs: build_pypowsybl - runs-on: ubuntu-latest - if: failure() - steps: - - name: Send Slack Notification - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - text: | - :x: *Failed workflow ${{ github.workflow }}* - *Failure details:* - ``` - ${{ needs.build_pypowsybl.outputs.build_status_output }} - ``` - See logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + needs: build_pypowsybl + runs-on: ubuntu-latest + if: always() + steps: + - name: Download job results + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + + - name: Combine job results + run: | + for dir in job-results_*; do + cat "$dir"/* >> combined_job_results.txt + done + echo "===== Step Result =====" + cat combined_job_results.txt + echo "=======================" + + - name: Determine workflow status + id: workflow_status + run: | + if grep -q "failure" combined_job_results.txt; then + echo "icon=❌" >> $GITHUB_OUTPUT + echo "status=Failed" >> $GITHUB_OUTPUT + else + echo "icon=✅" >> $GITHUB_OUTPUT + echo "status=Successful" >> $GITHUB_OUTPUT + fi + + - name: Format job results + id: format_results + run: | + declare -A success_modules failure_modules seen_modules + module_order=() + os_set=() + python_set=() + + while IFS=';' read -r os python module status; do + # Ignore empty or bad formated lines + if [[ -z "$os" || -z "$python" || -z "$module" || -z "$status" ]]; then + continue + fi + + # List the modules seen (to keep the order) + if [[ -z "${seen_modules[$module]}" ]]; then + module_order+=("$module") + seen_modules["$module"]=1 + fi + + # List the OS and Python versions + if [[ ! " ${os_set[*]} " =~ " ${os} " ]]; then + os_set+=("$os") + fi + if [[ ! " ${python_set[*]} " =~ " ${python} " ]]; then + python_set+=("$python") + fi + + # List the build status + if [[ "$status" == "success" ]]; then + success_modules["$module"]+="$os+$python " + else + failure_modules["$module"]+="$os+$python " + fi + done < combined_job_results.txt + + # Format the notification message + formatted="" + all_success_modules=() + for module in "${module_order[@]}"; do + if [[ -n "${failure_modules[$module]}" ]]; then + failures=$(echo "${failure_modules[$module]}" | sed 's/ $//') + failed_all_os=true + failed_all_python=true + for os in "${os_set[@]}"; do + if [[ ! "${failure_modules[$module]}" =~ $os ]]; then + failed_all_os=false + break + fi + done + + for python in "${python_set[@]}"; do + if [[ ! "${failure_modules[$module]}" =~ $python ]]; then + failed_all_python=false + break + fi + done + + if [[ "$module" == "wheels" && "$failed_all_os" == true && "$failed_all_python" == true ]]; then + formatted+=":x: Build *$module* on all OS and for all Python versions"$'\\n' + elif [[ "$failed_all_os" == true ]]; then + formatted+=":x: Build *$module* on all OS"$'\\n' + else + formatted+=":x: Build *$module* on $failures"$'\\n' + fi + elif [[ -n "${success_modules[$module]}" ]]; then + all_success_modules+=("$module") + fi + done + + if [ ${#all_success_modules[@]} -gt 0 ]; then + success_list="" + for module in "${all_success_modules[@]}"; do + if [ -z "$success_list" ]; then + success_list="$module" + else + success_list="$success_list, $module" + fi + done + formatted=":white_check_mark: Build *$success_list*"$'\\n'"$formatted" + fi + + formatted=$(echo -n "$formatted" | sed '$ s/\n$//') + + echo "formatted_results=${formatted}" >> $GITHUB_OUTPUT + + - name: Prepare Slack payload + id: prepare_payload + run: | + if [ "${{ steps.workflow_status.outputs.status }}" == "Successful" ]; then + echo 'payload<> $GITHUB_OUTPUT + echo '{ + "attachments": [{ + "color": "#319f4b", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "${{ steps.workflow_status.outputs.icon }} *${{ steps.workflow_status.outputs.status }} workflow: Snapshot-CI on *\n\nBranch built: ${{ needs.build_pypowsybl.outputs.pypowsybl-branch }}\nPowSyBl-Core version used: ${{ needs.build_pypowsybl.outputs.core-version }}\n\nSee logs on " + } + } + ] + }] + }' >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + else + echo 'payload<> $GITHUB_OUTPUT + echo '{ + "attachments": [{ + "color": "#f64538", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "${{ steps.workflow_status.outputs.icon }} *${{ steps.workflow_status.outputs.status }} workflow: Snapshot-CI on * (branch built: ${{ needs.build_pypowsybl.outputs.pypowsybl-branch }})" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Workflow details:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "${{ steps.format_results.outputs.formatted_results }}" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "@channel - See logs on " + } + } + ] + }] + }' >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + fi + + - name: Send Slack Notification + uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 # v3.16.2 + if: ${{ steps.workflow_status.outputs.status != 'Successful' || github.event_name == 'workflow_dispatch' }} + with: + author_name: 'pypowsybl on GitHub' + status: custom + custom_payload: ${{ steps.prepare_payload.outputs.payload }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_POWSYBL_WEBHOOK_URL }} From bad6a9abe32ff59561774b95e94889d5387edecb Mon Sep 17 00:00:00 2001 From: Christian Biasuzzi Date: Thu, 3 Apr 2025 14:51:41 +0200 Subject: [PATCH 53/71] Adds more customization parameters to the NadProfile (#960) Signed-off-by: Christian Biasuzzi Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 10 +- cpp/powsybl-cpp/powsybl-cpp.h | 6 +- cpp/pypowsybl-cpp/bindings.cpp | 4 +- docs/user_guide/network_visualization.rst | 57 ++++-- .../python/network/NetworkCFunctions.java | 169 ++++++++++++++++-- pypowsybl/_pypowsybl.pyi | 4 +- pypowsybl/network/impl/nad_profile.py | 49 ++++- pypowsybl/network/impl/network.py | 11 +- tests/test_network.py | 45 ++++- 9 files changed, 309 insertions(+), 46 deletions(-) diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index 14086986d0..af7b2777be 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -722,11 +722,12 @@ std::vector getMatrixMultiSubstationSvgAndMetadata(const JavaHandle return svgAndMetadata.get(); } -void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, dataframe* branch_labels) { +void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, + dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions) { auto c_parameters = parameters.to_c_struct(); ToCharPtrPtr voltageLevelIdPtr(voltageLevelIds); PowsyblCaller::get()->callJava(::writeNetworkAreaDiagramSvg, network, (char*) svgFile.data(), (char*) metadataFile.data(), - voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels); + voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels, three_wt_labels, bus_descriptions, vl_descriptions); } std::string getNetworkAreaDiagramSvg(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters) { @@ -735,10 +736,11 @@ std::string getNetworkAreaDiagramSvg(const JavaHandle& network, const std::vecto return toString(PowsyblCaller::get()->callJava(::getNetworkAreaDiagramSvg, network, voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get())); } -std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, dataframe* branch_labels) { +std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, + dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions) { auto c_parameters = parameters.to_c_struct(); ToCharPtrPtr voltageLevelIdPtr(voltageLevelIds); - auto svgAndMetadataArrayPtr = PowsyblCaller::get()->callJava(::getNetworkAreaDiagramSvgAndMetadata, network, voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels); + auto svgAndMetadataArrayPtr = PowsyblCaller::get()->callJava(::getNetworkAreaDiagramSvgAndMetadata, network, voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels, three_wt_labels, bus_descriptions, vl_descriptions); ToStringVector svgAndMetadata(svgAndMetadataArrayPtr); return svgAndMetadata.get(); } diff --git a/cpp/powsybl-cpp/powsybl-cpp.h b/cpp/powsybl-cpp/powsybl-cpp.h index 7b35528292..fae1cd5899 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.h +++ b/cpp/powsybl-cpp/powsybl-cpp.h @@ -579,11 +579,13 @@ std::vector getMatrixMultiSubstationSvgAndMetadata(const JavaHandle std::vector getSingleLineDiagramComponentLibraryNames(); -void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, dataframe* branch_labels); +void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, + dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions); std::string getNetworkAreaDiagramSvg(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters); -std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, dataframe* branch_labels); +std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, + dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions); std::vector getNetworkAreaDiagramDisplayedVoltageLevels(const JavaHandle& network, const std::vector& voltageLevelIds, int depth); diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index 4728e5936d..b72874d088 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -686,14 +686,14 @@ PYBIND11_MODULE(_pypowsybl, m) { m.def("write_network_area_diagram_svg", &pypowsybl::writeNetworkAreaDiagramSvg, "Write network area diagram SVG", py::arg("network"), py::arg("svg_file"), py::arg("metadata_file"), py::arg("voltage_level_ids"), py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters"), py::arg("fixed_positions"), - py::arg("branch_labels")); + py::arg("branch_labels"), py::arg("three_wt_labels"), py::arg("bus_descriptions"), py::arg("vl_descriptions")); m.def("get_network_area_diagram_svg", &pypowsybl::getNetworkAreaDiagramSvg, "Get network area diagram SVG as a string", py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters")); m.def("get_network_area_diagram_svg_and_metadata", &pypowsybl::getNetworkAreaDiagramSvgAndMetadata, "Get network area diagram SVG and its metadata as a list of strings", py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters"), py::arg("fixed_positions"), - py::arg("branch_labels")); + py::arg("branch_labels"), py::arg("three_wt_labels"), py::arg("bus_descriptions"), py::arg("vl_descriptions")); m.def("get_network_area_diagram_displayed_voltage_levels", &pypowsybl::getNetworkAreaDiagramDisplayedVoltageLevels, "Get network area diagram displayed voltage level", py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth")); diff --git a/docs/user_guide/network_visualization.rst b/docs/user_guide/network_visualization.rst index 17205f5d38..6958e2648d 100644 --- a/docs/user_guide/network_visualization.rst +++ b/docs/user_guide/network_visualization.rst @@ -306,17 +306,22 @@ We can generate a network area diagram using fixed positions, defined in a dataf ]) >>> nad = network.get_network_area_diagram(fixed_positions=pos_df) - In the dataframe: - - id is the equipment id for the node - - x, y define the position for the node - - legend_shift_x, legend_shift_y define the legend box top-left position (relative to the node position) - - legend_connection_shift_x, legend_connection_shift_y define the legend box side endpoint position (relative to the node position) for the segment connecting a node and its legend box +In the dataframe: - The optional parameter fixed_positions can also be set in the write_network_area_diagram function. - Note that positions for elements not included in the dataframe are computed using the current layout algorithm. +- id is the equipment id for the node +- x, y define the position for the node +- legend_shift_x, legend_shift_y define the legend box top-left position (relative to the node position) +- legend_connection_shift_x, legend_connection_shift_y define the legend box side endpoint position (relative to the node position) for the segment connecting a node and its legend box +The optional parameter fixed_positions can also be set in the write_network_area_diagram function. +Note that positions for elements not included in the dataframe are computed using the current layout algorithm. -We can further customize the NAD diagram using the NadProfile. For example, to set the labels of the NAD branches and the arrows direction, using a dataframe: + +We can further customize the NAD diagram using the NadProfile. For example, to set + - the labels for the branches, and the arrows direction + - the VL and BUS descriptions in the VL info boxes + +by using dataframes: .. code-block:: python @@ -328,17 +333,47 @@ We can further customize the NAD diagram using the NadProfile. For example, to s ('LINE_S3S4', 'L2_1', 'L2', 'L2_2', 'OUT', 'IN'), ('TWT', 'TWT1_1', 'TWT1', 'TWT1_2', None, 'OUT') ]) - >>> diagram_profile=pp.network.NadProfile(branch_labels=labels_df) + >>> vl_descriptions_df=pd.DataFrame.from_records(index='id', + data=[ + {'id': 'S1VL1', 'type': 'HEADER', 'description': 'VL A'}, + {'id': 'S1VL1', 'type': 'FOOTER', 'description': 'VL A footer'}, + {'id': 'S1VL2', 'type': 'HEADER', 'description': 'VL B'}, + {'id': 'S2VL1', 'type': 'HEADER', 'description': 'VL C'}, + {'id': 'S3VL1', 'type': 'HEADER', 'description': 'VL D'}, + {'id': 'S3VL1', 'type': 'FOOTER', 'description': 'VL D footer'} + ]) + >>> bus_descriptions_df=pd.DataFrame.from_records(index='id', + data=[ + {'id': 'S1VL1_0', 'description': 'BUS A'}, + {'id': 'S1VL2_0', 'description': 'BUS B'}, + {'id': 'S2VL1_0', 'description': 'BUS C'}, + {'id': 'S3VL1_0', 'description': 'BUS D'} + ]) + >>> diagram_profile=pp.network.NadProfile(branch_labels=labels_df, vl_descriptions=vl_descriptions_df, bus_descriptions=bus_descriptions_df) >>> pars=pp.network.NadParameters(edge_name_displayed=True) >>> network.get_network_area_diagram(voltage_level_ids='S1VL1', depth=2, nad_parameters=pars, nad_profile=diagram_profile) - In the dataframe: +In the branch_labels dataframe parameter: - id is the branch id - side1 and side2 define the labels along the two branch's edges - middle defines the branch's label - arrow1 and arrow2 define the direction of the arrows at the ends of the branch: 'IN' or 'OUT'. None (or an empty string) does not display the arrow. - The optional parameter nad_profile can also be set in the write_network_area_diagram function. +In the vl_descriptions dataframe parameter: + - id is the VL id + - type: 'HEADER' or 'FOOTER' determines if the descrtiption appears above or below the bus description, in the VL info box + - description define a label for the VL. Entries with the same VL id are displayed sequentially as multiple rows + +In the bus_descriptions dataframe parameter: + - id is the BUS id + - description define a label for the BUS + +An additional three_wt_labels dataframe parameter can be used to set the labels and the arrows direction for three winding transformers: + - id is the three winding transformer id + - side1, side2, and side3 define the labels along the three winding transformer legs + - arrow1, arrow2, and arrow3 define the direction of the arrows at the ends of the three winding transformer legs: 'IN' or 'OUT'. None (or an empty string) does not display the arrow. + +The optional parameter nad_profile can also be set in the write_network_area_diagram function. Network area diagram using geographical data -------------------------------------------- diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java index 493e971e76..101431c37a 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java @@ -31,14 +31,12 @@ import com.powsybl.dataframe.update.DoubleSeries; import com.powsybl.dataframe.update.StringSeries; import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.*; import com.powsybl.iidm.reducer.*; import com.powsybl.nad.NadParameters; import com.powsybl.nad.layout.*; -import com.powsybl.nad.model.BranchEdge; -import com.powsybl.nad.model.Edge; -import com.powsybl.nad.model.Graph; -import com.powsybl.nad.model.Point; +import com.powsybl.nad.model.*; import com.powsybl.nad.svg.EdgeInfo; import com.powsybl.nad.svg.SvgParameters; import com.powsybl.nad.svg.iidm.DefaultLabelProvider; @@ -74,6 +72,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.*; +import java.util.stream.IntStream; import java.util.zip.ZipOutputStream; import static com.powsybl.nad.svg.SvgParameters.EdgeInfoEnum.*; @@ -1123,7 +1122,8 @@ public static PyPowsyblApiHeader.ArrayPointer getSingleLine public static void writeNetworkAreaDiagramSvg(IsolateThread thread, ObjectHandle networkHandle, CCharPointer svgFile, CCharPointer metadataFile, CCharPointerPointer voltageLevelIdsPointer, int voltageLevelIdCount, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, NadParametersPointer nadParametersPointer, - DataframePointer fixedPositions, DataframePointer branchLabels, + DataframePointer fixedPositions, DataframePointer branchLabels, DataframePointer threeWtLabels, + DataframePointer busDescriptions, DataframePointer vlDescriptions, ExceptionHandlerPointer exceptionHandlerPtr) { doCatch(exceptionHandlerPtr, () -> { Network network = ObjectHandles.getGlobal().get(networkHandle); @@ -1132,7 +1132,7 @@ public static void writeNetworkAreaDiagramSvg(IsolateThread thread, ObjectHandle List voltageLevelIds = toStringList(voltageLevelIdsPointer, voltageLevelIdCount); NadParameters nadParameters = convertNadParameters(nadParametersPointer, network); applyFixedPositions(fixedPositions, nadParameters); - applyCustomLabels(branchLabels, nadParameters); + applyCustomLabels(branchLabels, threeWtLabels, busDescriptions, vlDescriptions, nadParameters); NetworkAreaDiagramUtil.writeSvg(network, voltageLevelIds, depth, svgFileStr, metadataFileStr, highNominalVoltageBound, lowNominalVoltageBound, nadParameters); }); } @@ -1203,6 +1203,9 @@ private static void applyFixedPositions(DataframePointer fixedPositions, NadPara record CustomBranchLabels(String side1, String middle, String side2, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2) { } + record CustomThreeWtLabels(String side1, String side2, String side3, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2, EdgeInfo.Direction arrow3) { + } + private static String getValueFromSeriesOrNull(StringSeries series, int row) { return (series != null) ? series.get(row) : null; } @@ -1234,7 +1237,88 @@ private static Map getNadCustomBranchLabels(int rowC return nadCustomBranchLabels; } - private static LabelProviderFactory getCustomLabelProviderFactory(Map branchLabels) { + private static Map getNadCustomThreeWtLabels(UpdatingDataframe threeWtLabelsDataframe) { + int rowCount = threeWtLabelsDataframe.getRowCount(); + StringSeries idS = threeWtLabelsDataframe.getStrings("id"); + StringSeries side1S = threeWtLabelsDataframe.getStrings("side1"); + StringSeries side2S = threeWtLabelsDataframe.getStrings("side2"); + StringSeries side3S = threeWtLabelsDataframe.getStrings("side3"); + StringSeries arrow1S = threeWtLabelsDataframe.getStrings("arrow1"); + StringSeries arrow2S = threeWtLabelsDataframe.getStrings("arrow2"); + StringSeries arrow3S = threeWtLabelsDataframe.getStrings("arrow3"); + + Map nadCustomThreeWtLabels = new HashMap<>(); + for (int i = 0; i < rowCount; i++) { + String id = idS.get(i); + CustomThreeWtLabels labels = new CustomThreeWtLabels( + getValueFromSeriesOrNull(side1S, i), + getValueFromSeriesOrNull(side2S, i), + getValueFromSeriesOrNull(side3S, i), + getDirectionFromSeriesOrNull(arrow1S, i), + getDirectionFromSeriesOrNull(arrow2S, i), + getDirectionFromSeriesOrNull(arrow3S, i) + ); + nadCustomThreeWtLabels.put(id, labels); + } + return nadCustomThreeWtLabels; + } + + private static Map getNadCustomBusDescriptions(int rowCount, StringSeries idS, StringSeries descriptionS) { + Map nadCustomDescriptions = new HashMap<>(); + for (int i = 0; i < rowCount; i++) { + String id = idS.get(i); + String description = descriptionS.get(i); + nadCustomDescriptions.put(id, description); + } + return nadCustomDescriptions; + } + + public record VlInfo(Map> headers, Map> footers) { + + } + + public static VlInfo getNadCustomVlInfos(int rowCount, StringSeries ids, + StringSeries types, StringSeries descriptions) { + Map> headers = new LinkedHashMap<>(); + Map> footers = new LinkedHashMap<>(); + + IntStream.range(0, rowCount) + .forEach(i -> { + String id = ids.get(i); + String description = descriptions.get(i); + String type = types.get(i); + + Map> targetMap = type.equals("HEADER") ? headers : footers; + targetMap.computeIfAbsent(id, k -> new ArrayList<>()).add(description); + }); + + return new VlInfo(headers, footers); + } + + private static EdgeInfo getThreeWtEdgeInfo(CustomThreeWtLabels threeWtLabels, ThreeWtEdge.Side edgeSide) { + String labelSide = null; + EdgeInfo.Direction arrowDirection = null; + if (threeWtLabels != null) { + switch (edgeSide) { + case ONE -> { + labelSide = threeWtLabels.side1; + arrowDirection = threeWtLabels.arrow1; + } + case TWO -> { + labelSide = threeWtLabels.side2; + arrowDirection = threeWtLabels.arrow2; + } + case THREE -> { + labelSide = threeWtLabels.side3; + arrowDirection = threeWtLabels.arrow3; + } + } + } + return new EdgeInfo("ActivePower", arrowDirection, null, labelSide); + } + + private static LabelProviderFactory getCustomLabelProviderFactory(Map branchLabels, Map threeWtLabels, + Map busDescriptions, Map> vlDescriptions, Map> vlDetails) { return (network, svgParameters) -> new DefaultLabelProvider(network, svgParameters) { @Override public Optional getEdgeInfo(Graph graph, BranchEdge edge, BranchEdge.Side side) { @@ -1248,29 +1332,75 @@ public Optional getEdgeInfo(Graph graph, BranchEdge edge, BranchEdge.S return Optional.of(new EdgeInfo("ActivePower", arrowDirection, null, label)); } + @Override + public Optional getEdgeInfo(Graph graph, ThreeWtEdge edge) { + return Optional.of(getThreeWtEdgeInfo(threeWtLabels.get(edge.getEquipmentId()), edge.getSide())); + } + @Override public String getLabel(Edge edge) { CustomBranchLabels bl = branchLabels.get(edge.getEquipmentId()); return (bl != null) ? bl.middle : null; } + + @Override + public String getBusDescription(BusNode busNode) { + return busDescriptions.get(busNode.getEquipmentId()); + } + + @Override + public List getVoltageLevelDescription(VoltageLevelNode voltageLevelNode) { + return vlDescriptions.getOrDefault(voltageLevelNode.getEquipmentId(), Collections.emptyList()); + } + + @Override + public List getVoltageLevelDetails(VoltageLevelNode vlNode) { + return vlDetails.getOrDefault(vlNode.getEquipmentId(), Collections.emptyList()); + } }; } - private static void applyCustomLabels(DataframePointer customLabels, NadParameters nadParameters) { + private static void applyCustomLabels(DataframePointer customLabels, DataframePointer threeWtLabels, DataframePointer busDescriptions, DataframePointer vlDescriptions, NadParameters nadParameters) { UpdatingDataframe customLabelsDataframe = createDataframe(customLabels); - if (customLabelsDataframe != null) { + UpdatingDataframe threeWtLabelsDataframe = createDataframe(threeWtLabels); + UpdatingDataframe busDescriptionsDataframe = createDataframe(busDescriptions); + UpdatingDataframe customVlDescriptionsDataframe = createDataframe(vlDescriptions); + if (customLabelsDataframe != null || threeWtLabelsDataframe != null || busDescriptionsDataframe != null || customVlDescriptionsDataframe != null) { + Map branchLabels = Collections.emptyMap(); + if (customLabelsDataframe != null) { + //when the custom dataframe is defined, the displaying of the edge name is forced + nadParameters.getSvgParameters().setEdgeNameDisplayed(true); + branchLabels = getNadCustomBranchLabels(customLabelsDataframe.getRowCount(), customLabelsDataframe.getStrings("id"), + customLabelsDataframe.getStrings("side1"), + customLabelsDataframe.getStrings("middle"), customLabelsDataframe.getStrings("side2"), + customLabelsDataframe.getStrings("arrow1"), customLabelsDataframe.getStrings("arrow2")); + } - //when the custom dataframe is defined, the displaying of the edge name is forced - nadParameters.getSvgParameters().setEdgeNameDisplayed(true); + Map customThreeWtLabels = (threeWtLabelsDataframe != null) ? getNadCustomThreeWtLabels(threeWtLabelsDataframe) : Collections.emptyMap(); - StringSeries idSeries = customLabelsDataframe.getStrings("id"); - int rowCount = customLabelsDataframe.getRowCount(); + Map customBusDescriptions = Collections.emptyMap(); + if (busDescriptionsDataframe != null) { + //when the custom dataframe is defined, the displaying of the bus legend section is forced + nadParameters.getSvgParameters().setBusLegend(true); + customBusDescriptions = getNadCustomBusDescriptions(busDescriptionsDataframe.getRowCount(), + busDescriptionsDataframe.getStrings("id"), + busDescriptionsDataframe.getStrings("description")); + } - Map branchLabels = getNadCustomBranchLabels(rowCount, idSeries, customLabelsDataframe.getStrings("side1"), - customLabelsDataframe.getStrings("middle"), customLabelsDataframe.getStrings("side2"), - customLabelsDataframe.getStrings("arrow1"), customLabelsDataframe.getStrings("arrow2")); + Map> customVlDescriptions = Collections.emptyMap(); + Map> customVlDetails = Collections.emptyMap(); + if (customVlDescriptionsDataframe != null) { + //when the custom dataframe is defined, the displaying of the vl details section is forced + nadParameters.getSvgParameters().setVoltageLevelDetails(true); + VlInfo vlInfo = getNadCustomVlInfos(customVlDescriptionsDataframe.getRowCount(), + customVlDescriptionsDataframe.getStrings("id"), + customVlDescriptionsDataframe.getStrings("type"), + customVlDescriptionsDataframe.getStrings("description")); + customVlDescriptions = vlInfo.headers(); + customVlDetails = vlInfo.footers(); + } - nadParameters.setLabelProviderFactory(getCustomLabelProviderFactory(branchLabels)); + nadParameters.setLabelProviderFactory(getCustomLabelProviderFactory(branchLabels, customThreeWtLabels, customBusDescriptions, customVlDescriptions, customVlDetails)); } } @@ -1278,13 +1408,14 @@ private static void applyCustomLabels(DataframePointer customLabels, NadParamete public static ArrayPointer getNetworkAreaDiagramSvgAndMetadata(IsolateThread thread, ObjectHandle networkHandle, CCharPointerPointer voltageLevelIdsPointer, int voltageLevelIdCount, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, NadParametersPointer nadParametersPointer, - DataframePointer fixedPositions, DataframePointer branchLabels, ExceptionHandlerPointer exceptionHandlerPtr) { + DataframePointer fixedPositions, DataframePointer branchLabels, DataframePointer threeWtLabels, DataframePointer busDescriptions, + DataframePointer vlDescriptions, ExceptionHandlerPointer exceptionHandlerPtr) { return doCatch(exceptionHandlerPtr, () -> { Network network = ObjectHandles.getGlobal().get(networkHandle); List voltageLevelIds = toStringList(voltageLevelIdsPointer, voltageLevelIdCount); NadParameters nadParameters = convertNadParameters(nadParametersPointer, network); applyFixedPositions(fixedPositions, nadParameters); - applyCustomLabels(branchLabels, nadParameters); + applyCustomLabels(branchLabels, threeWtLabels, busDescriptions, vlDescriptions, nadParameters); List svgAndMeta = NetworkAreaDiagramUtil.getSvgAndMetadata(network, voltageLevelIds, depth, highNominalVoltageBound, lowNominalVoltageBound, nadParameters); return createCharPtrArray(svgAndMeta); }); diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 205596e973..5a2b7565d5 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -834,7 +834,7 @@ def get_security_analysis_provider_parameters_names(provider: str) -> List[str]: def get_sensitivity_analysis_provider_parameters_names(provider: str) -> List[str]: ... def get_limit_violations(result: JavaHandle) -> SeriesArray: ... def get_network_area_diagram_svg(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters) -> str: ... -def get_network_area_diagram_svg_and_metadata(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe]) -> List[str]: ... +def get_network_area_diagram_svg_and_metadata(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe], three_wt_labels: Optional[Dataframe], bus_descriptions: Optional[Dataframe], vl_descriptions: Optional[Dataframe]) -> List[str]: ... def get_network_area_diagram_displayed_voltage_levels(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int) -> List[str]: ... def get_network_elements_ids(network: JavaHandle, element_type: ElementType, nominal_voltages: List[float], countries: List[str], main_connected_component: bool, main_synchronous_component: bool, not_connected_to_same_bus_at_both_sides: bool) -> List[str]: ... def get_network_export_formats() -> List[str]: ... @@ -895,7 +895,7 @@ def update_connectable_status(arg0: JavaHandle, arg1: str, arg2: bool) -> bool: def update_network_elements_with_series(network: JavaHandle, array: Dataframe, element_type: ElementType, per_unit: bool, nominal_apparent_power: float) -> None: ... def update_switch_position(arg0: JavaHandle, arg1: str, arg2: bool) -> bool: ... def validate(network: JavaHandle) -> ValidationLevel: ... -def write_network_area_diagram_svg(network: JavaHandle, svg_file: str, metadata_file: str, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe]) -> None: ... +def write_network_area_diagram_svg(network: JavaHandle, svg_file: str, metadata_file: str, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe], three_wt_labels: Optional[Dataframe], bus_descriptions: Optional[Dataframe], vl_descriptions: Optional[Dataframe]) -> None: ... def write_single_line_diagram_svg(network: JavaHandle, container_id: str, svg_file: str, metadata_file: str, parameters: SldParameters) -> None: ... def write_matrix_multi_substation_single_line_diagram_svg(network: JavaHandle, matrix_ids: List[List[str]], svg_file: str, metadata_file: str, parameters: SldParameters) -> None: ... def add_network_element_properties(network: JavaHandle, dataframe: Dataframe) -> None: ... diff --git a/pypowsybl/network/impl/nad_profile.py b/pypowsybl/network/impl/nad_profile.py index 12f80398e6..13a4fab998 100644 --- a/pypowsybl/network/impl/nad_profile.py +++ b/pypowsybl/network/impl/nad_profile.py @@ -13,7 +13,7 @@ class NadProfile: """ - This class represents parameters to customize a network area diagram (e.g., labels).""" + This class represents parameters to customize a network area diagram (e.g., labels on branches).""" _nad_branch_labels_metadata=[_pp.SeriesMetadata('id',0,True,False,False), _pp.SeriesMetadata('side1',0,False,False,False), @@ -22,15 +22,60 @@ class NadProfile: _pp.SeriesMetadata('arrow1',0,False,False,False), _pp.SeriesMetadata('arrow2',0,False,False,False)] - def __init__(self, branch_labels: Optional[DataFrame] = None): + _nad_three_wt_metadata=[_pp.SeriesMetadata('id',0,True,False,False), + _pp.SeriesMetadata('side1',0,False,False,False), + _pp.SeriesMetadata('side2',0,False,False,False), + _pp.SeriesMetadata('side3',0,False,False,False), + _pp.SeriesMetadata('arrow1',0,False,False,False), + _pp.SeriesMetadata('arrow2',0,False,False,False), + _pp.SeriesMetadata('arrow3',0,False,False,False)] + + _nad_descriptions_metadata=[_pp.SeriesMetadata('id',0,True,False,False), + _pp.SeriesMetadata('type',0,False,False,False), + _pp.SeriesMetadata('description',0,False,False,False)] + + _nad_bus_descriptions_metadata=[_pp.SeriesMetadata('id',0,True,False,False), + _pp.SeriesMetadata('description',0,False,False,False)] + + def __init__(self, branch_labels: Optional[DataFrame] = None, three_wt_labels: Optional[DataFrame] = None, + bus_descriptions: Optional[DataFrame] = None, vl_descriptions: Optional[DataFrame] = None): self._branch_labels = branch_labels + self._three_wt_labels = three_wt_labels + self._bus_descriptions = bus_descriptions + self._vl_descriptions = vl_descriptions @property def branch_labels(self) -> Optional[DataFrame]: """branch_labels""" return self._branch_labels + @property + def three_wt_labels(self) -> Optional[DataFrame]: + """three_wt_labels""" + return self._three_wt_labels + + @property + def bus_descriptions(self) -> Optional[DataFrame]: + """bus_description""" + return self._bus_descriptions + + @property + def vl_descriptions(self) -> Optional[DataFrame]: + """vl_descriptions""" + return self._vl_descriptions def _create_nad_branch_labels_c_dataframe(self) -> Optional[_pp.Dataframe]: return None if self._branch_labels is None else _create_c_dataframe(self._branch_labels.fillna(''), NadProfile._nad_branch_labels_metadata) + + def _create_nad_three_wt_labels_c_dataframe(self) -> Optional[_pp.Dataframe]: + return None if self._three_wt_labels is None else _create_c_dataframe(self._three_wt_labels.fillna(''), + NadProfile._nad_three_wt_metadata) + + def _create_nad_bus_descriptions_c_dataframe(self) -> Optional[_pp.Dataframe]: + return None if self._bus_descriptions is None else _create_c_dataframe(self._bus_descriptions.fillna(''), + NadProfile._nad_bus_descriptions_metadata) + + def _create_nad_vl_descriptions_c_dataframe(self) -> Optional[_pp.Dataframe]: + return None if self._vl_descriptions is None else _create_c_dataframe(self._vl_descriptions.fillna(''), + NadProfile._nad_descriptions_metadata) diff --git a/pypowsybl/network/impl/network.py b/pypowsybl/network/impl/network.py index 1eec412c9c..b0388f2731 100644 --- a/pypowsybl/network/impl/network.py +++ b/pypowsybl/network/impl/network.py @@ -395,7 +395,11 @@ def write_network_area_diagram(self, svg_file: PathOrStr, voltage_level_ids: Uni _pp.write_network_area_diagram_svg(self._handle, svg_file, '' if metadata_file is None else path_to_str(metadata_file), voltage_level_ids, depth, high_nominal_voltage_bound, low_nominal_voltage_bound, nad_p, None if fixed_positions is None else self._create_nad_positions_c_dataframe(fixed_positions), - None if nad_profile is None else nad_profile._create_nad_branch_labels_c_dataframe()) # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_branch_labels_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_three_wt_labels_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_bus_descriptions_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_vl_descriptions_c_dataframe()) # pylint: disable=protected-access + def _create_nad_positions_c_dataframe(self, df: DataFrame) -> _pp.Dataframe: nad_positions_metadata=[_pp.SeriesMetadata('id',0,True,False,False), @@ -434,7 +438,10 @@ def get_network_area_diagram(self, voltage_level_ids: Union[str, List[str]] = No svg_and_metadata: List[str] = _pp.get_network_area_diagram_svg_and_metadata(self._handle, voltage_level_ids, depth, high_nominal_voltage_bound, low_nominal_voltage_bound, nad_p, None if fixed_positions is None else self._create_nad_positions_c_dataframe(fixed_positions), - None if nad_profile is None else nad_profile._create_nad_branch_labels_c_dataframe()) # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_branch_labels_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_three_wt_labels_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_bus_descriptions_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_vl_descriptions_c_dataframe()) # pylint: disable=protected-access return Svg(svg_and_metadata[0], svg_and_metadata[1]) diff --git a/tests/test_network.py b/tests/test_network.py index 984f34bdc3..3f4316f492 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -1184,11 +1184,27 @@ def test_nad_profile(): diagram_profile = NadProfile(branch_labels=None) assert not diagram_profile.branch_labels n = pp.network.create_ieee14() - branch_labels_df = pd.DataFrame.from_records(index='id', + branch_labels_df = pd.DataFrame.from_records(index='id', data=[{'id': 'L1-5-1', 'side1': 'S1_1', 'middle': 'MIDDLE_1', 'side2': 'S2_1', 'arrow1': 'IN', 'arrow2': 'IN'}, {'id': 'L2-5-1', 'side1': 'S1_2', 'middle': 'MIDDLE_2', 'side2': 'S2_2', 'arrow1': 'OUT', 'arrow2': 'OUT'}]) - diagram_profile=NadProfile(branch_labels=branch_labels_df) + vl_descriptions_df=pd.DataFrame.from_records(index='id', + data=[ + {'id': 'VL1', 'type': 'HEADER', 'description': 'VL1 header'}, + {'id': 'VL1', 'type': 'HEADER', 'description': 'VL1 header2'}, + {'id': 'VL1', 'type': 'FOOTER', 'description': 'VL1 footer'}, + {'id': 'VL2', 'type': 'HEADER', 'description': 'VL2 header'}, + {'id': 'VL5', 'type': 'FOOTER', 'description': 'VL5 footer'} + ]) + bus_descriptions_df = pd.DataFrame.from_records(index='id', + data=[ + {'id': 'VL1_0', 'description': 'VL1 bus'}, + {'id': 'VL2_0', 'description': 'VL2 bus'}, + {'id': 'VL5_0', 'description': 'VL3 bus'} + ]) + diagram_profile=NadProfile(branch_labels=branch_labels_df, vl_descriptions=vl_descriptions_df, bus_descriptions=bus_descriptions_df) assert isinstance(diagram_profile.branch_labels, pd.DataFrame) + assert isinstance(diagram_profile.vl_descriptions, pd.DataFrame) + assert isinstance(diagram_profile.bus_descriptions, pd.DataFrame) pars=pp.network.NadParameters(edge_name_displayed=True) nad1=n.get_network_area_diagram(voltage_level_ids='VL1', depth=1, nad_parameters=pars, nad_profile=diagram_profile) assert re.search('.* 0 + def test_current_limits(): network = pp.network.create_eurostag_tutorial_example1_network() From 36dcb8ce66e59dd8266da3de89b0f00fd68eeb6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Marszal?= Date: Thu, 3 Apr 2025 16:33:21 +0200 Subject: [PATCH 54/71] Fix subscripts in per unit documentation (#962) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Marszal Signed-off-by: Naledi EL CHEIKH --- docs/user_guide/per_unit.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user_guide/per_unit.rst b/docs/user_guide/per_unit.rst index 7408a01fe7..29c3131d0e 100644 --- a/docs/user_guide/per_unit.rst +++ b/docs/user_guide/per_unit.rst @@ -36,7 +36,7 @@ Resistance R for network elements with only one nominal voltage : -.. math:: \frac{S_n}{V_nominal^2} R +.. math:: \frac{S_n}{V_{nominal}^2} R with Sn the nominal apparent power For two winding transformers, the nominal voltage is the nominal voltage of the side 2 @@ -49,7 +49,7 @@ Reactance X for network elements with only one nominal voltage : -.. math:: \frac{S_n}{V_nominal^2} X +.. math:: \frac{S_n}{V_{nominal}^2} X with Sn the nominal apparent power For two winding transformers, the nominal voltage is the nominal voltage of the side 2 From c6f1ffc8c4506d7f94e1066c761ea5519a895cca Mon Sep 17 00:00:00 2001 From: Geoffroy Jamgotchian Date: Fri, 4 Apr 2025 10:01:11 +0200 Subject: [PATCH 55/71] Update to PowSyBl dependencies 2025.0.0 (#974) Signed-off-by: Geoffroy Jamgotchian Co-authored-by: lisrte Signed-off-by: Naledi EL CHEIKH --- cpp/pypowsybl-cpp/bindings.cpp | 2 +- cpp/pypowsybl-java/powsybl-api.h | 2 +- data/rao/rao_parameters.json | 150 ++++++++------- docs/reference/dynamic.rst | 2 +- docs/user_guide/dynamic.rst | 1 - integration_tests/test_dynawo.py | 11 +- java/pom.xml | 5 +- java/pypowsybl/pom.xml | 6 - .../AbstractAutomationSystemSeries.java | 6 + .../adders/AbstractDynamicModelSeries.java | 2 - .../adders/AbstractEquipmentAdder.java | 1 - .../adders/AbstractEquipmentSeries.java | 1 - .../dynamic/adders/DynamicMappingHandler.java | 2 +- ...woLevelOverloadManagementSystemAdder.java} | 18 +- .../python/commons/PyPowsyblApiHeader.java | 2 +- .../powsybl-commons/resource-config.json | 6 +- .../adders/DynamicModelsAdderTest.java | 7 +- java/pypowsybl/src/test/resources/nad.svg | 173 +++++++++--------- pypowsybl/_pypowsybl.pyi | 2 +- pypowsybl/dynamic/impl/model_mapping.py | 63 +++---- pypowsybl/network/impl/util.py | 2 +- tests/test_dynamic.py | 40 ++-- tests/test_loadflow.py | 11 +- tests/test_network.py | 20 +- tests/test_rao.py | 8 +- tests/test_security_analysis.py | 4 +- tests/test_sensitivity_analysis.py | 2 +- 27 files changed, 273 insertions(+), 276 deletions(-) rename java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/{DynamicTwoLevelsOverloadManagementSystemAdder.java => DynamicTwoLevelOverloadManagementSystemAdder.java} (79%) diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index b72874d088..1aab2ecf30 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -205,7 +205,7 @@ void dynamicSimulationBindings(py::module_& m) { .value("BASE_BUS", DynamicMappingType::BASE_BUS) .value("INFINITE_BUS", DynamicMappingType::INFINITE_BUS) .value("OVERLOAD_MANAGEMENT_SYSTEM", DynamicMappingType::OVERLOAD_MANAGEMENT_SYSTEM) - .value("TWO_LEVELS_OVERLOAD_MANAGEMENT_SYSTEM", DynamicMappingType::TWO_LEVELS_OVERLOAD_MANAGEMENT_SYSTEM) + .value("TWO_LEVEL_OVERLOAD_MANAGEMENT_SYSTEM", DynamicMappingType::TWO_LEVEL_OVERLOAD_MANAGEMENT_SYSTEM) .value("UNDER_VOLTAGE", DynamicMappingType::UNDER_VOLTAGE) .value("PHASE_SHIFTER_I", DynamicMappingType::PHASE_SHIFTER_I) .value("PHASE_SHIFTER_P", DynamicMappingType::PHASE_SHIFTER_P) diff --git a/cpp/pypowsybl-java/powsybl-api.h b/cpp/pypowsybl-java/powsybl-api.h index ffd54878f9..425c4c837c 100644 --- a/cpp/pypowsybl-java/powsybl-api.h +++ b/cpp/pypowsybl-java/powsybl-api.h @@ -391,7 +391,7 @@ typedef enum { BASE_BUS, INFINITE_BUS, OVERLOAD_MANAGEMENT_SYSTEM, - TWO_LEVELS_OVERLOAD_MANAGEMENT_SYSTEM, + TWO_LEVEL_OVERLOAD_MANAGEMENT_SYSTEM, UNDER_VOLTAGE, PHASE_SHIFTER_I, PHASE_SHIFTER_P, diff --git a/data/rao/rao_parameters.json b/data/rao/rao_parameters.json index e1b202c360..4bc24c32ff 100644 --- a/data/rao/rao_parameters.json +++ b/data/rao/rao_parameters.json @@ -1,85 +1,97 @@ { - "version" : "2.4", + "version" : "3.0", "objective-function" : { - "type" : "MAX_MIN_RELATIVE_MARGIN_IN_MEGAWATT", - "forbid-cost-increase" : false, - "curative-min-obj-improvement" : 0.0, - "preventive-stop-criterion" : "MIN_OBJECTIVE", - "curative-stop-criterion" : "MIN_OBJECTIVE" + "type" : "MAX_MIN_MARGIN", + "unit" : "MW", + "enforce-curative-security" : false }, "range-actions-optimization" : { - "max-mip-iterations" : 15, - "pst-penalty-cost" : 0.01, - "pst-sensitivity-threshold" : 1.0E-6, - "pst-model" : "CONTINUOUS", - "hvdc-penalty-cost" : 0.001, - "hvdc-sensitivity-threshold" : 1.0E-6, - "injection-ra-penalty-cost" : 0.001, - "injection-ra-sensitivity-threshold" : 1.0E-6, - "linear-optimization-solver" : { - "solver" : "CBC", - "relative-mip-gap" : 1.0E-4, - "solver-specific-parameters" : null - } + "pst-ra-min-impact-threshold" : 0.01, + "hvdc-ra-min-impact-threshold" : 0.001, + "injection-ra-min-impact-threshold" : 0.001 }, "topological-actions-optimization" : { - "max-preventive-search-tree-depth" : 2147483647, - "max-auto-search-tree-depth" : 2147483647, - "max-curative-search-tree-depth" : 2147483647, - "predefined-combinations" : [ ], "relative-minimum-impact-threshold" : 0.0, - "absolute-minimum-impact-threshold" : 1.0, - "skip-actions-far-from-most-limiting-element" : false, - "max-number-of-boundaries-for-skipping-actions" : 2 - }, - "multi-threading" : { - "contingency-scenarios-in-parallel" : 1, - "preventive-leaves-in-parallel" : 1, - "curative-leaves-in-parallel" : 1 - }, - "second-preventive-rao" : { - "execution-condition" : "DISABLED", - "re-optimize-curative-range-actions" : false, - "hint-from-first-preventive-rao" : false + "absolute-minimum-impact-threshold" : 1.0 }, "not-optimized-cnecs" : { "do-not-optimize-curative-cnecs-for-tsos-without-cras" : false }, - "load-flow-and-sensitivity-computation" : { - "load-flow-provider" : "OpenLoadFlow", - "sensitivity-provider" : "OpenLoadFlow", - "sensitivity-failure-overcost" : 10000.0, - "sensitivity-parameters" : { - "version" : "1.0", - "load-flow-parameters" : { - "version" : "1.9", - "voltageInitMode" : "UNIFORM_VALUES", - "transformerVoltageControlOn" : false, - "phaseShifterRegulationOn" : false, - "useReactiveLimits" : true, - "twtSplitShuntAdmittance" : false, - "shuntCompensatorVoltageControlOn" : false, - "readSlackBus" : true, - "writeSlackBus" : true, - "dc" : false, - "distributedSlack" : true, - "balanceType" : "PROPORTIONAL_TO_GENERATION_P", - "dcUseTransformerRatio" : true, - "countriesToBalance" : [ ], - "connectedComponentMode" : "MAIN", - "hvdcAcEmulation" : true - } - } + "mnec-parameters" : { + "acceptable-margin-decrease" : 50.0 + }, + "loop-flow-parameters" : { + "acceptable-increase" : 0.0, + "countries" : [ ] }, "extensions" : { - "mnec-parameters" : { - "acceptable-margin-decrease" : 50.0, - "violation-cost" : 10.0, - "constraint-adjustment-coefficient" : 0.0 - }, - "relative-margins-parameters" : { - "ptdf-boundaries" : [ "{FR}-{BE}", "{FR}-{DE}", "{BE}-{NL}", "{NL}-{DE}" ], - "ptdf-sum-lower-bound" : 0.01 + "open-rao-search-tree-parameters" : { + "objective-function" : { + "curative-min-obj-improvement" : 10000.0 + }, + "range-actions-optimization" : { + "max-mip-iterations" : 10, + "pst-sensitivity-threshold" : 1.0E-6, + "pst-model" : "APPROXIMATED_INTEGERS", + "hvdc-sensitivity-threshold" : 1.0E-6, + "injection-ra-sensitivity-threshold" : 1.0E-6, + "linear-optimization-solver" : { + "solver" : "CBC", + "relative-mip-gap" : 1.0E-4, + "solver-specific-parameters" : null + } + }, + "topological-actions-optimization" : { + "max-preventive-search-tree-depth" : 2147483647, + "max-auto-search-tree-depth" : 2147483647, + "max-curative-search-tree-depth" : 2147483647, + "predefined-combinations" : [ ], + "skip-actions-far-from-most-limiting-element" : false, + "max-number-of-boundaries-for-skipping-actions" : 2 + }, + "second-preventive-rao" : { + "execution-condition" : "DISABLED", + "re-optimize-curative-range-actions" : false, + "hint-from-first-preventive-rao" : false + }, + "load-flow-and-sensitivity-computation" : { + "load-flow-provider" : "OpenLoadFlow", + "sensitivity-provider" : "OpenLoadFlow", + "sensitivity-failure-overcost" : 10000.0, + "sensitivity-parameters" : { + "version" : "1.0", + "load-flow-parameters" : { + "version" : "1.9", + "voltageInitMode" : "UNIFORM_VALUES", + "transformerVoltageControlOn" : false, + "phaseShifterRegulationOn" : false, + "useReactiveLimits" : true, + "twtSplitShuntAdmittance" : false, + "shuntCompensatorVoltageControlOn" : false, + "readSlackBus" : true, + "writeSlackBus" : true, + "dc" : true, + "distributedSlack" : true, + "balanceType" : "PROPORTIONAL_TO_GENERATION_P", + "dcUseTransformerRatio" : true, + "countriesToBalance" : [ ], + "connectedComponentMode" : "MAIN", + "hvdcAcEmulation" : true + } + } + }, + "multi-threading" : { + "available-cpus" : 1 + }, + "mnec-parameters" : { + "violation-cost" : 10.0, + "constraint-adjustment-coefficient" : 0.0 + }, + "loop-flow-parameters" : { + "ptdf-approximation" : "UPDATE_PTDF_WITH_TOPO", + "constraint-adjustment-coefficient" : 5.0, + "violation-cost" : 100.0 + } } } } \ No newline at end of file diff --git a/docs/reference/dynamic.rst b/docs/reference/dynamic.rst index d298dced75..5f8225289a 100644 --- a/docs/reference/dynamic.rst +++ b/docs/reference/dynamic.rst @@ -30,7 +30,7 @@ ModelMapping ModelMapping.add_base_bus ModelMapping.add_infinite_bus ModelMapping.add_overload_management_system - ModelMapping.add_two_levels_overload_management_system + ModelMapping.add_two_level_overload_management_system ModelMapping.add_under_voltage_automation_system ModelMapping.add_phase_shifter_i_automation_system ModelMapping.add_phase_shifter_p_automation_system diff --git a/docs/user_guide/dynamic.rst b/docs/user_guide/dynamic.rst index a324781344..b524adbed3 100644 --- a/docs/user_guide/dynamic.rst +++ b/docs/user_guide/dynamic.rst @@ -72,7 +72,6 @@ To run a Dynawo simulation: model_mapping = dyn.ModelMapping() model_mapping.add_base_load(static_id='LOAD', parameter_set_id='LAB', - dynamic_model_id='DM_LOAD', model_name='LoadAlphaBeta') # and so on # events mapping diff --git a/integration_tests/test_dynawo.py b/integration_tests/test_dynawo.py index 251257984a..637b7e633a 100644 --- a/integration_tests/test_dynawo.py +++ b/integration_tests/test_dynawo.py @@ -21,12 +21,11 @@ def test_simulation(): report_node = rp.Reporter() model_mapping = dyn.ModelMapping() - model_mapping.add_base_load(static_id='B3-L', parameter_set_id='LAB', dynamic_model_id='BBM_LOAD', model_name='LoadAlphaBeta') + model_mapping.add_base_load(static_id='B3-L', parameter_set_id='LAB', model_name='LoadAlphaBeta') generator_mapping_df = pd.DataFrame( index=pd.Series(name='static_id', data=['B6-G', 'B8-G']), data={ - 'dynamic_model_id': ['BBM_GEN6', 'BBM_GEN8'], 'parameter_set_id': ['GSTWPR_GEN____6_SM', 'GSTWPR_GEN____8_SM'], 'model_name': 'GeneratorSynchronousThreeWindingsProportionalRegulations' } @@ -38,7 +37,7 @@ def test_simulation(): event_mapping.add_active_power_variation(static_id='B3-L', start_time=4, delta_p=0.02) variables_mapping = dyn.OutputVariableMapping() - variables_mapping.add_dynamic_model_curves('BBM_GEN6', ['generator_PGen', 'generator_QGen', 'generator_UStatorPu']) + variables_mapping.add_dynamic_model_curves('B6-G', ['generator_PGen', 'generator_QGen', 'generator_UStatorPu']) variables_mapping.add_standard_model_final_state_values('B3', 'Upu_value') sim = dyn.Simulation() @@ -47,8 +46,8 @@ def test_simulation(): assert report_node assert DynamicSimulationStatus.SUCCESS == res.status() assert "" == res.status_text() - assert 'BBM_GEN6_generator_PGen' in res.curves() - assert 'BBM_GEN6_generator_QGen' in res.curves() - assert 'BBM_GEN6_generator_UStatorPu' in res.curves() + assert 'B6-G_generator_PGen' in res.curves() + assert 'B6-G_generator_QGen' in res.curves() + assert 'B6-G_generator_UStatorPu' in res.curves() assert False == res.final_state_values().loc['NETWORK_B3_Upu_value'].empty assert False == res.timeline().empty diff --git a/java/pom.xml b/java/pom.xml index 27755af72c..f2e0afb454 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -68,9 +68,8 @@ 17 - 2024.4.1 - 6.2.1 - 0.10.0 + 2025.0.0 + 0.12.0 diff --git a/java/pypowsybl/pom.xml b/java/pypowsybl/pom.xml index f86a13285c..37ad9d8790 100644 --- a/java/pypowsybl/pom.xml +++ b/java/pypowsybl/pom.xml @@ -383,32 +383,26 @@ com.powsybl open-rao-rao-api - ${powsybl-open-rao.version} com.powsybl open-rao-crac-api - ${powsybl-open-rao.version} com.powsybl open-rao-crac-impl - ${powsybl-open-rao.version} com.powsybl open-rao-crac-io-json - ${powsybl-open-rao.version} com.powsybl open-rao-search-tree-rao - ${powsybl-open-rao.version} com.powsybl open-rao-rao-result-json - ${powsybl-open-rao.version} diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractAutomationSystemSeries.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractAutomationSystemSeries.java index c5d9e6c20f..d327a16198 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractAutomationSystemSeries.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractAutomationSystemSeries.java @@ -7,9 +7,12 @@ */ package com.powsybl.dataframe.dynamic.adders; +import com.powsybl.dataframe.dynamic.PersistentStringSeries; +import com.powsybl.dataframe.update.StringSeries; import com.powsybl.dataframe.update.UpdatingDataframe; import com.powsybl.dynawo.models.automationsystems.AbstractAutomationSystemModelBuilder; +import static com.powsybl.dataframe.dynamic.adders.DynamicModelDataframeConstants.DYNAMIC_MODEL_ID; import static com.powsybl.dataframe.network.adders.SeriesUtils.applyIfPresent; /** @@ -17,8 +20,11 @@ */ abstract class AbstractAutomationSystemSeries> extends AbstractDynamicModelSeries { + protected final StringSeries dynamicModelIds; + AbstractAutomationSystemSeries(UpdatingDataframe dataframe) { super(dataframe); + this.dynamicModelIds = PersistentStringSeries.copyOf(dataframe, DYNAMIC_MODEL_ID); } protected void applyOnBuilder(int row, T builder) { diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractDynamicModelSeries.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractDynamicModelSeries.java index 5485d616a6..36263fed2d 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractDynamicModelSeries.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractDynamicModelSeries.java @@ -24,12 +24,10 @@ */ abstract class AbstractDynamicModelSeries> implements DynamicModelSeries { - protected final StringSeries dynamicModelIds; protected final StringSeries parameterSetIds; protected final StringSeries modelsNames; AbstractDynamicModelSeries(UpdatingDataframe dataframe) { - this.dynamicModelIds = PersistentStringSeries.copyOf(dataframe, DYNAMIC_MODEL_ID); this.parameterSetIds = PersistentStringSeries.copyOf(dataframe, PARAMETER_SET_ID); this.modelsNames = PersistentStringSeries.copyOf(dataframe, MODEL_NAME); } diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractEquipmentAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractEquipmentAdder.java index 5c64a79658..e1a4860cd8 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractEquipmentAdder.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractEquipmentAdder.java @@ -22,7 +22,6 @@ abstract class AbstractEquipmentAdder extends AbstractSimpleDynamicModelAdder { protected static final List EQUIPMENT_METADATA = List.of( SeriesMetadata.stringIndex(STATIC_ID), SeriesMetadata.strings(PARAMETER_SET_ID), - SeriesMetadata.strings(DYNAMIC_MODEL_ID), SeriesMetadata.strings(MODEL_NAME)); @Override diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractEquipmentSeries.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractEquipmentSeries.java index 5e133274cb..3d3e55a86b 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractEquipmentSeries.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/AbstractEquipmentSeries.java @@ -31,6 +31,5 @@ abstract class AbstractEquipmentSeries, B extends Equi protected void applyOnBuilder(int row, B builder) { applyIfPresent(staticIds, row, builder::staticId); applyIfPresent(parameterSetIds, row, builder::parameterSetId); - applyIfPresent(dynamicModelIds, row, builder::dynamicModelId); } } diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicMappingHandler.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicMappingHandler.java index 2dcc0eb839..665e74a645 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicMappingHandler.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicMappingHandler.java @@ -44,7 +44,7 @@ public final class DynamicMappingHandler { Map.entry(DynamicMappingType.INFINITE_BUS, new InfiniteBusAdder()), // Automation systems Map.entry(DynamicMappingType.OVERLOAD_MANAGEMENT_SYSTEM, new DynamicOverloadManagementSystemAdder()), - Map.entry(DynamicMappingType.TWO_LEVELS_OVERLOAD_MANAGEMENT_SYSTEM, new DynamicTwoLevelsOverloadManagementSystemAdder()), + Map.entry(DynamicMappingType.TWO_LEVEL_OVERLOAD_MANAGEMENT_SYSTEM, new DynamicTwoLevelOverloadManagementSystemAdder()), Map.entry(DynamicMappingType.PHASE_SHIFTER_I, new PhaseShifterIAdder()), Map.entry(DynamicMappingType.PHASE_SHIFTER_P, new PhaseShifterPAdder()), Map.entry(DynamicMappingType.PHASE_SHIFTER_BLOCKING_I, new PhaseShifterBlockingIAdder()), diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicTwoLevelsOverloadManagementSystemAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicTwoLevelOverloadManagementSystemAdder.java similarity index 79% rename from java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicTwoLevelsOverloadManagementSystemAdder.java rename to java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicTwoLevelOverloadManagementSystemAdder.java index f1f8c0ab1b..c9050a55ad 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicTwoLevelsOverloadManagementSystemAdder.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/adders/DynamicTwoLevelOverloadManagementSystemAdder.java @@ -13,7 +13,7 @@ import com.powsybl.dataframe.update.StringSeries; import com.powsybl.dataframe.update.UpdatingDataframe; import com.powsybl.dynawo.builders.ModelInfo; -import com.powsybl.dynawo.models.automationsystems.overloadmanagments.DynamicTwoLevelsOverloadManagementSystemBuilder; +import com.powsybl.dynawo.models.automationsystems.overloadmanagments.DynamicTwoLevelOverloadManagementSystemBuilder; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.TwoSides; @@ -27,7 +27,7 @@ /** * @author Laurent Issertial {@literal } */ -public class DynamicTwoLevelsOverloadManagementSystemAdder extends AbstractSimpleDynamicModelAdder { +public class DynamicTwoLevelOverloadManagementSystemAdder extends AbstractSimpleDynamicModelAdder { protected static final List METADATA = List.of( SeriesMetadata.stringIndex(DYNAMIC_MODEL_ID), @@ -46,10 +46,10 @@ public List> getMetadata() { @Override public Collection getSupportedModels() { - return DynamicTwoLevelsOverloadManagementSystemBuilder.getSupportedModelInfos(); + return DynamicTwoLevelOverloadManagementSystemBuilder.getSupportedModelInfos(); } - private static class OverloadManagementSystemSeries extends AbstractAutomationSystemSeries { + private static class OverloadManagementSystemSeries extends AbstractAutomationSystemSeries { private final StringSeries controlledBranch; private final StringSeries iMeasurement1; @@ -67,7 +67,7 @@ private static class OverloadManagementSystemSeries extends AbstractAutomationSy } @Override - protected void applyOnBuilder(int row, DynamicTwoLevelsOverloadManagementSystemBuilder builder) { + protected void applyOnBuilder(int row, DynamicTwoLevelOverloadManagementSystemBuilder builder) { super.applyOnBuilder(row, builder); applyIfPresent(controlledBranch, row, builder::controlledBranch); applyIfPresent(iMeasurement1, row, builder::iMeasurement1); @@ -77,13 +77,13 @@ protected void applyOnBuilder(int row, DynamicTwoLevelsOverloadManagementSystemB } @Override - protected DynamicTwoLevelsOverloadManagementSystemBuilder createBuilder(Network network, ReportNode reportNode) { - return DynamicTwoLevelsOverloadManagementSystemBuilder.of(network, reportNode); + protected DynamicTwoLevelOverloadManagementSystemBuilder createBuilder(Network network, ReportNode reportNode) { + return DynamicTwoLevelOverloadManagementSystemBuilder.of(network, reportNode); } @Override - protected DynamicTwoLevelsOverloadManagementSystemBuilder createBuilder(Network network, String modelName, ReportNode reportNode) { - return DynamicTwoLevelsOverloadManagementSystemBuilder.of(network, modelName, reportNode); + protected DynamicTwoLevelOverloadManagementSystemBuilder createBuilder(Network network, String modelName, ReportNode reportNode) { + return DynamicTwoLevelOverloadManagementSystemBuilder.of(network, modelName, reportNode); } } diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java b/java/pypowsybl/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java index 7288fa51d4..4d9f73e0cf 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java @@ -1205,7 +1205,7 @@ public enum DynamicMappingType { BASE_BUS, INFINITE_BUS, OVERLOAD_MANAGEMENT_SYSTEM, - TWO_LEVELS_OVERLOAD_MANAGEMENT_SYSTEM, + TWO_LEVEL_OVERLOAD_MANAGEMENT_SYSTEM, UNDER_VOLTAGE, PHASE_SHIFTER_I, PHASE_SHIFTER_P, diff --git a/java/pypowsybl/src/main/resources/META-INF/native-image/com.powsybl/powsybl-commons/resource-config.json b/java/pypowsybl/src/main/resources/META-INF/native-image/com.powsybl/powsybl-commons/resource-config.json index 8992ab37e8..657cde6039 100644 --- a/java/pypowsybl/src/main/resources/META-INF/native-image/com.powsybl/powsybl-commons/resource-config.json +++ b/java/pypowsybl/src/main/resources/META-INF/native-image/com.powsybl/powsybl-commons/resource-config.json @@ -4,6 +4,10 @@ {"pattern":"\\QMETA-INF/services/com.powsybl.commons.config.PlatformConfigProvider\\E"}, {"pattern":"\\QMETA-INF/services/com.powsybl.commons.extensions.ExtensionAdderProvider\\E"}, {"pattern":"\\QMETA-INF/services/com.powsybl.commons.extensions.ExtensionXmlSerializer\\E"}, - {"pattern":"\\Qbase-voltages.yml\\E"} + {"pattern":"\\Qbase-voltages.yml\\E"}, + {"pattern":"\\Qcom/powsybl/commons/reports.properties\\E"}, + {"pattern":"\\Qcom/powsybl/commons/reports_en_US.properties\\E"}, + {"pattern":"\\Qcom/powsybl/commons/reports_fr.properties\\E"}, + {"pattern":"\\Qcom/powsybl/commons/reports_fr_FR.properties\\E"} ]} } diff --git a/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/adders/DynamicModelsAdderTest.java b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/adders/DynamicModelsAdderTest.java index 305b4755ca..51a2c96687 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/adders/DynamicModelsAdderTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/adders/DynamicModelsAdderTest.java @@ -55,15 +55,14 @@ void setup() { void testEquipmentsAdder(PyPowsyblApiHeader.DynamicMappingType mappingType, Network network, String staticId) { String expectedModelName = DynamicMappingHandler.getSupportedModels(mappingType).stream().findFirst().orElse(""); dataframe.addSeries(STATIC_ID, true, createTwoRowsSeries(staticId)); - dataframe.addSeries(DYNAMIC_MODEL_ID, false, createTwoRowsSeries("BBM" + staticId)); dataframe.addSeries(PARAMETER_SET_ID, false, createTwoRowsSeries("eq_par")); dataframe.addSeries(MODEL_NAME, false, new TestStringSeries(expectedModelName, "")); DynamicMappingHandler.addElements(mappingType, dynamicModelsSupplier, List.of(dataframe)); assertThat(dynamicModelsSupplier.get(network)).satisfiesExactly( - model1 -> assertThat(model1).hasFieldOrPropertyWithValue("dynamicModelId", "BBM" + staticId) + model1 -> assertThat(model1).hasFieldOrPropertyWithValue("dynamicModelId", staticId) .hasFieldOrPropertyWithValue("lib", expectedModelName), - model2 -> assertThat(model2).hasFieldOrPropertyWithValue("dynamicModelId", "BBM" + staticId)); + model2 -> assertThat(model2).hasFieldOrPropertyWithValue("dynamicModelId", staticId)); } @ParameterizedTest(name = "{0}") @@ -172,7 +171,7 @@ static Stream automationSystemProvider() { df.addSeries(I_MEASUREMENT, false, createTwoRowsSeries(lineId)); df.addSeries(I_MEASUREMENT_SIDE, false, createTwoRowsSeries(TwoSides.ONE.toString())); }), - Arguments.of(TWO_LEVELS_OVERLOAD_MANAGEMENT_SYSTEM, + Arguments.of(TWO_LEVEL_OVERLOAD_MANAGEMENT_SYSTEM, (Consumer) df -> { String lineId = "NGEN_NHV1"; df.addSeries(CONTROLLED_BRANCH, false, createTwoRowsSeries(lineId)); diff --git a/java/pypowsybl/src/test/resources/nad.svg b/java/pypowsybl/src/test/resources/nad.svg index 2078cfd7e5..2e2759192d 100644 --- a/java/pypowsybl/src/test/resources/nad.svg +++ b/java/pypowsybl/src/test/resources/nad.svg @@ -95,7 +95,12 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} .nad-branch-edges .nad-overload .nad-edge-path {animation: line-blink 3s infinite} .nad-vl-nodes .nad-overvoltage {animation: node-over-blink 3s infinite} .nad-vl-nodes .nad-undervoltage {animation: node-under-blink 3s infinite} - +.nad-highlight {stroke-width: 25; opacity: 0.2; fill: none;} +.nad-highlight-0 {stroke: #e6e600;} +.nad-highlight-1 {stroke: #b300b3;} +.nad-highlight-2 {stroke: #2eb82e;} +.nad-highlight-3 {stroke: #e67300;} +.nad-highlight-4 {stroke: #0000ff;} @keyframes line-blink { 0%, 80%, 100% {stroke: var(--nad-vl-color, black); stroke-width: 5} 40% {stroke: #FFEB3B; stroke-width: 15} @@ -156,8 +161,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -168,8 +173,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -182,8 +187,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -194,8 +199,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -208,8 +213,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -220,8 +225,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -234,8 +239,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -246,8 +251,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -260,8 +265,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -272,8 +277,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -286,8 +291,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -298,8 +303,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -312,8 +317,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -324,8 +329,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -338,8 +343,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -350,8 +355,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -364,8 +369,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -376,8 +381,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -390,8 +395,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -402,8 +407,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -416,8 +421,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -428,8 +433,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -442,8 +447,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -454,8 +459,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -468,8 +473,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -480,8 +485,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -494,8 +499,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -506,8 +511,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -520,8 +525,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -532,8 +537,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -546,8 +551,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -558,8 +563,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -569,15 +574,15 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - + - - + + @@ -588,8 +593,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -599,15 +604,15 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - + - - + + @@ -618,8 +623,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -629,15 +634,15 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - + - - + + @@ -648,8 +653,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -662,8 +667,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + @@ -674,8 +679,8 @@ path.nad-arrow-in:not(.nad-state-in .nad-arrow-in) {visibility: hidden} - - + + diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 5a2b7565d5..a9bf97c86d 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -642,7 +642,7 @@ class DynamicMappingType: BASE_BUS: ClassVar[DynamicMappingType] = ... INFINITE_BUS: ClassVar[DynamicMappingType] = ... OVERLOAD_MANAGEMENT_SYSTEM: ClassVar[DynamicMappingType] = ... - TWO_LEVELS_OVERLOAD_MANAGEMENT_SYSTEM: ClassVar[DynamicMappingType] = ... + TWO_LEVEL_OVERLOAD_MANAGEMENT_SYSTEM: ClassVar[DynamicMappingType] = ... UNDER_VOLTAGE: ClassVar[DynamicMappingType] = ... PHASE_SHIFTER_I: ClassVar[DynamicMappingType] = ... PHASE_SHIFTER_P: ClassVar[DynamicMappingType] = ... diff --git a/pypowsybl/dynamic/impl/model_mapping.py b/pypowsybl/dynamic/impl/model_mapping.py index b979e80fa5..b8bdbcc6a0 100644 --- a/pypowsybl/dynamic/impl/model_mapping.py +++ b/pypowsybl/dynamic/impl/model_mapping.py @@ -40,7 +40,6 @@ def add_base_load(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -50,7 +49,6 @@ def add_base_load(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: model_mapping.add_base_load(static_id='LOAD', parameter_set_id='lab', - dynamic_model_id='DM_LOAD', model_name='LoadPQ') """ self._add_all_dynamic_mappings(DynamicMappingType.BASE_LOAD, [df], **kwargs) @@ -72,7 +70,7 @@ def add_load_one_transformer(self, df: DataFrame = None, **kwargs: ArrayLike) -> - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -82,7 +80,6 @@ def add_load_one_transformer(self, df: DataFrame = None, **kwargs: ArrayLike) -> model_mapping.add_load_one_transformer(static_id='LOAD', parameter_set_id='lt', - dynamic_model_id='DM_LT', model_name='LoadOneTransformer') """ self._add_all_dynamic_mappings(DynamicMappingType.LOAD_ONE_TRANSFORMER, [df], **kwargs) @@ -104,7 +101,7 @@ def add_load_one_transformer_tap_changer(self, df: DataFrame = None, **kwargs: A - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -114,7 +111,6 @@ def add_load_one_transformer_tap_changer(self, df: DataFrame = None, **kwargs: A model_mapping.add_load_one_transformer_tap_changer(static_id='LOAD', parameter_set_id='lt_tc', - dynamic_model_id='DM_LT_TC', model_name='LoadOneTransformerTapChanger') """ self._add_all_dynamic_mappings(DynamicMappingType.LOAD_ONE_TRANSFORMER_TAP_CHANGER, [df], **kwargs) @@ -136,7 +132,7 @@ def add_load_two_transformers(self, df: DataFrame = None, **kwargs: ArrayLike) - - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -146,7 +142,6 @@ def add_load_two_transformers(self, df: DataFrame = None, **kwargs: ArrayLike) - model_mapping.add_load_two_transformers(static_id='LOAD', parameter_set_id='ltt', - dynamic_model_id='DM_LTT', model_name='LoadTwoTransformers') """ self._add_all_dynamic_mappings(DynamicMappingType.LOAD_TWO_TRANSFORMERS, [df], **kwargs) @@ -168,7 +163,7 @@ def add_load_two_transformers_tap_changers(self, df: DataFrame = None, **kwargs: - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -178,7 +173,6 @@ def add_load_two_transformers_tap_changers(self, df: DataFrame = None, **kwargs: model_mapping.add_load_two_transformers_tap_changers(static_id='LOAD', parameter_set_id='ltt_tc', - dynamic_model_id='DM_LTT_TC', model_name='LoadTwoTransformersTapChangers') """ self._add_all_dynamic_mappings(DynamicMappingType.LOAD_TWO_TRANSFORMERS_TAP_CHANGERS, [df], **kwargs) @@ -200,7 +194,7 @@ def add_base_generator(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -210,7 +204,6 @@ def add_base_generator(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: model_mapping.add_base_generator(static_id='GEN', parameter_set_id='gen', - dynamic_model_id='DM_GEN', model_name='GeneratorFictitious') """ self._add_all_dynamic_mappings(DynamicMappingType.BASE_GENERATOR, [df], **kwargs) @@ -232,7 +225,7 @@ def add_synchronized_generator(self, df: DataFrame = None, **kwargs: ArrayLike) - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -242,7 +235,6 @@ def add_synchronized_generator(self, df: DataFrame = None, **kwargs: ArrayLike) model_mapping.add_synchronized_generator(static_id='GEN', parameter_set_id='sgen', - dynamic_model_id='DM_SYNCH_GEN', model_name='GeneratorPVFixed') """ self._add_all_dynamic_mappings(DynamicMappingType.SYNCHRONIZED_GENERATOR, [df], **kwargs) @@ -264,7 +256,7 @@ def add_synchronous_generator(self, df: DataFrame = None, **kwargs: ArrayLike) - - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -274,7 +266,6 @@ def add_synchronous_generator(self, df: DataFrame = None, **kwargs: ArrayLike) - model_mapping.add_synchronous_generator(static_id='GEN', parameter_set_id='ssgen', - dynamic_model_id='DM_SYNCHRONOUS_GEN', model_name='GeneratorSynchronousThreeWindings') """ self._add_all_dynamic_mappings(DynamicMappingType.SYNCHRONOUS_GENERATOR, [df], **kwargs) @@ -296,7 +287,7 @@ def add_wecc(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -306,7 +297,6 @@ def add_wecc(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: model_mapping.add_wecc(static_id='GEN', parameter_set_id='wecc', - dynamic_model_id='DM_WECC', model_name='WT4BWeccCurrentSource') """ self._add_all_dynamic_mappings(DynamicMappingType.WECC, [df], **kwargs) @@ -328,7 +318,7 @@ def add_grid_forming_converter(self, df: DataFrame = None, **kwargs: ArrayLike) - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -338,7 +328,6 @@ def add_grid_forming_converter(self, df: DataFrame = None, **kwargs: ArrayLike) model_mapping.add_grid_forming_converter(static_id='GEN', parameter_set_id='gf', - dynamic_model_id='DM_GF', model_name='GridFormingConverterMatchingControl') """ self._add_all_dynamic_mappings(DynamicMappingType.GRID_FORMING_CONVERTER, [df], **kwargs) @@ -360,7 +349,7 @@ def add_signal_n_generator(self, df: DataFrame = None, **kwargs: ArrayLike) -> N - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -370,7 +359,6 @@ def add_signal_n_generator(self, df: DataFrame = None, **kwargs: ArrayLike) -> N model_mapping.add_signal_n_generator(static_id='GEN', parameter_set_id='signal_n', - dynamic_model_id='DM_SIGNAL_N', model_name='GeneratorPVSignalN') """ self._add_all_dynamic_mappings(DynamicMappingType.SIGNAL_N_GENERATOR, [df], **kwargs) @@ -392,7 +380,7 @@ def add_hvdc_p(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -402,7 +390,6 @@ def add_hvdc_p(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: model_mapping.add_hvdc_p(static_id='HVDC_LINE', parameter_set_id='hvdc_p', - dynamic_model_id='DM_HVDC_P', model_name='HvdcPV') """ self._add_all_dynamic_mappings(DynamicMappingType.HVDC_P, [df], **kwargs) @@ -424,7 +411,7 @@ def add_hvdc_vsc(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -434,7 +421,6 @@ def add_hvdc_vsc(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: model_mapping.add_hvdc_vsc(static_id='HVDC_LINE', parameter_set_id='hvdc_vsc', - dynamic_model_id='DM_HVDC_VSC', model_name='HvdcVSCDanglingP') """ self._add_all_dynamic_mappings(DynamicMappingType.HVDC_VSC, [df], **kwargs) @@ -456,7 +442,7 @@ def add_base_transformer(self, df: DataFrame = None, **kwargs: ArrayLike) -> Non - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -466,7 +452,6 @@ def add_base_transformer(self, df: DataFrame = None, **kwargs: ArrayLike) -> Non model_mapping.add_base_transformer(static_id='TFO', parameter_set_id='tfo', - dynamic_model_id='DM_TFO', model_name='TransformerFixedRatio') """ self._add_all_dynamic_mappings(DynamicMappingType.BASE_TRANSFORMER, [df], **kwargs) @@ -488,7 +473,7 @@ def add_base_static_var_compensator(self, df: DataFrame = None, **kwargs: ArrayL - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -498,7 +483,6 @@ def add_base_static_var_compensator(self, df: DataFrame = None, **kwargs: ArrayL model_mapping.add_base_static_var_compensator(static_id='SVARC', parameter_set_id='svarc', - dynamic_model_id='DM_SVARC', model_name='StaticVarCompensatorPV') """ self._add_all_dynamic_mappings(DynamicMappingType.BASE_STATIC_VAR_COMPENSATOR, [df], **kwargs) @@ -520,7 +504,7 @@ def add_base_line(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -530,7 +514,6 @@ def add_base_line(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: mmodel_mapping.add_base_line(static_id='LINE', parameter_set_id='l', - dynamic_model_id='DM_LINE', model_name='Line') """ self._add_all_dynamic_mappings(DynamicMappingType.BASE_LINE, [df], **kwargs) @@ -552,7 +535,7 @@ def add_base_bus(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -562,7 +545,6 @@ def add_base_bus(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: model_mapping.add_base_bus(static_id='BUS', parameter_set_id='bus', - dynamic_model_id='DM_BUS', model_name='Bus') """ self._add_all_dynamic_mappings(DynamicMappingType.BASE_BUS, [df], **kwargs) @@ -584,7 +566,7 @@ def add_infinite_bus(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: - **static_id**: id of the network element to map - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - - **dynamic_model_id**: id of the model mapping the network element (if none the static id will be used) + - **model_name**: name of the model used for the mapping (if none the default model will be used) Examples: @@ -594,7 +576,6 @@ def add_infinite_bus(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: model_mapping.add_infinite_bus(static_id='BUS', parameter_set_id='inf_bus', - dynamic_model_id='DM_INF_BUS', model_name='InfiniteBus') """ self._add_all_dynamic_mappings(DynamicMappingType.INFINITE_BUS, [df], **kwargs) @@ -635,9 +616,9 @@ def add_overload_management_system(self, df: DataFrame = None, **kwargs: ArrayLi """ self._add_all_dynamic_mappings(DynamicMappingType.OVERLOAD_MANAGEMENT_SYSTEM, [df], **kwargs) - def add_two_levels_overload_management_system(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: + def add_two_level_overload_management_system(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: """ - Add a dynamic two levels overload management system (not link to a network element) + Add a dynamic two level overload management system (not link to a network element) :Args: df: Attributes as a dataframe. @@ -650,7 +631,7 @@ def add_two_levels_overload_management_system(self, df: DataFrame = None, **kwar Valid attributes are: - - **dynamic_model_id**: id of the two levels overload management system + - **dynamic_model_id**: id of the two level overload management system - **parameter_set_id**: id of the parameter for this model given in the dynawo configuration - **controlled_branch**: id of the branch controlled by the automation system - **i_measurement_1**: id of the first branch used for the current intensity measurement @@ -664,7 +645,7 @@ def add_two_levels_overload_management_system(self, df: DataFrame = None, **kwar .. code-block:: python - model_mapping.add_two_levels_overload_management_system(dynamic_model_id='DM_TOV', + model_mapping.add_two_level_overload_management_system(dynamic_model_id='DM_TOV', parameter_set_id='tov', controlled_branch= 'LINE1', i_measurement_1='LINE1', @@ -673,7 +654,7 @@ def add_two_levels_overload_management_system(self, df: DataFrame = None, **kwar i_measurement_2_side='ONE', model_name='TwoLevelsOverloadManagementSystem') """ - self._add_all_dynamic_mappings(DynamicMappingType.TWO_LEVELS_OVERLOAD_MANAGEMENT_SYSTEM, [df], **kwargs) + self._add_all_dynamic_mappings(DynamicMappingType.TWO_LEVEL_OVERLOAD_MANAGEMENT_SYSTEM, [df], **kwargs) def add_under_voltage_automation_system(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: """ diff --git a/pypowsybl/network/impl/util.py b/pypowsybl/network/impl/util.py index ea7b2be2bb..c1fd95f233 100644 --- a/pypowsybl/network/impl/util.py +++ b/pypowsybl/network/impl/util.py @@ -76,7 +76,7 @@ def get_import_parameters(fmt: str) -> DataFrame: >>> parameters = pp.network.get_import_parameters('PSS/E') >>> parameters.index.tolist() - ['psse.import.ignore-base-voltage'] + ['psse.import.ignore-base-voltage', 'psse.import.ignore-node-breaker-topology'] >>> parameters['description']['psse.import.ignore-base-voltage'] 'Ignore base voltage specified in the file' >>> parameters['type']['psse.import.ignore-base-voltage'] diff --git a/tests/test_dynamic.py b/tests/test_dynamic.py index a539eb389d..ca2482b35d 100644 --- a/tests/test_dynamic.py +++ b/tests/test_dynamic.py @@ -18,28 +18,28 @@ def set_up(): def test_add_mapping(): model_mapping = dyn.ModelMapping() # Equipments - model_mapping.add_base_load(static_id='LOAD', parameter_set_id='lab', dynamic_model_id='DM_LOAD', model_name='LoadPQ') - model_mapping.add_load_one_transformer(static_id='LOAD', parameter_set_id='lt', dynamic_model_id='DM_LT', model_name='LoadOneTransformer') - model_mapping.add_load_one_transformer_tap_changer(static_id='LOAD', parameter_set_id='lt_tc', dynamic_model_id='DM_LT_TC', model_name='LoadOneTransformerTapChanger') - model_mapping.add_load_two_transformers(static_id='LOAD', parameter_set_id='ltt', dynamic_model_id='DM_LTT', model_name='LoadTwoTransformers') - model_mapping.add_load_two_transformers_tap_changers(static_id='LOAD', parameter_set_id='ltt_tc', dynamic_model_id='DM_LTT_TC', model_name='LoadTwoTransformersTapChangers') - model_mapping.add_base_generator(static_id='GEN', parameter_set_id='gen', dynamic_model_id='DM_GEN', model_name='GeneratorFictitious') - model_mapping.add_synchronized_generator(static_id='GEN', parameter_set_id='sgen', dynamic_model_id='DM_SYNCH_GEN', model_name='GeneratorPVFixed') - model_mapping.add_synchronous_generator(static_id='GEN', parameter_set_id='ssgen', dynamic_model_id='DM_SYNCHRONOUS_GEN', model_name='GeneratorSynchronousThreeWindings') - model_mapping.add_wecc(static_id='GEN', parameter_set_id='wecc', dynamic_model_id='DM_WECC', model_name='WT4BWeccCurrentSource') - model_mapping.add_grid_forming_converter(static_id='GEN', parameter_set_id='gf', dynamic_model_id='DM_GF', model_name='GridFormingConverterMatchingControl') - model_mapping.add_signal_n_generator(static_id='GEN', parameter_set_id='signal_n', dynamic_model_id='DM_SIGNAL_N', model_name='GeneratorPVSignalN') - model_mapping.add_hvdc_p(static_id='HVDC_LINE', parameter_set_id='hvdc_p', dynamic_model_id='DM_HVDC_P', model_name='HvdcPV') - model_mapping.add_hvdc_vsc(static_id='HVDC_LINE', parameter_set_id='hvdc_vsc', dynamic_model_id='DM_HVDC_VSC', model_name='HvdcVSCDanglingP') - model_mapping.add_base_transformer(static_id='TFO', parameter_set_id='tfo', dynamic_model_id='DM_TFO', model_name='TransformerFixedRatio') - model_mapping.add_base_static_var_compensator(static_id='SVARC', parameter_set_id='svarc', dynamic_model_id='DM_SVARC', model_name='StaticVarCompensatorPV') - model_mapping.add_base_line(static_id='LINE', parameter_set_id='l', dynamic_model_id='DM_LINE', model_name='Line') - model_mapping.add_base_bus(static_id='BUS', parameter_set_id='bus', dynamic_model_id='DM_BUS', model_name='Bus') - model_mapping.add_infinite_bus(static_id='BUS', parameter_set_id='inf_bus', dynamic_model_id='DM_INF_BUS', model_name='InfiniteBus') + model_mapping.add_base_load(static_id='LOAD', parameter_set_id='lab', model_name='LoadPQ') + model_mapping.add_load_one_transformer(static_id='LOAD', parameter_set_id='lt', model_name='LoadOneTransformer') + model_mapping.add_load_one_transformer_tap_changer(static_id='LOAD', parameter_set_id='lt_tc', model_name='LoadOneTransformerTapChanger') + model_mapping.add_load_two_transformers(static_id='LOAD', parameter_set_id='ltt', model_name='LoadTwoTransformers') + model_mapping.add_load_two_transformers_tap_changers(static_id='LOAD', parameter_set_id='ltt_tc', model_name='LoadTwoTransformersTapChangers') + model_mapping.add_base_generator(static_id='GEN', parameter_set_id='gen', model_name='GeneratorFictitious') + model_mapping.add_synchronized_generator(static_id='GEN', parameter_set_id='sgen', model_name='GeneratorPVFixed') + model_mapping.add_synchronous_generator(static_id='GEN', parameter_set_id='ssgen', model_name='GeneratorSynchronousThreeWindings') + model_mapping.add_wecc(static_id='GEN', parameter_set_id='wecc', model_name='WT4BWeccCurrentSource') + model_mapping.add_grid_forming_converter(static_id='GEN', parameter_set_id='gf', model_name='GridFormingConverterMatchingControl') + model_mapping.add_signal_n_generator(static_id='GEN', parameter_set_id='signal_n', model_name='GeneratorPVSignalN') + model_mapping.add_hvdc_p(static_id='HVDC_LINE', parameter_set_id='hvdc_p', model_name='HvdcPV') + model_mapping.add_hvdc_vsc(static_id='HVDC_LINE', parameter_set_id='hvdc_vsc', model_name='HvdcVSCDanglingP') + model_mapping.add_base_transformer(static_id='TFO', parameter_set_id='tfo', model_name='TransformerFixedRatio') + model_mapping.add_base_static_var_compensator(static_id='SVARC', parameter_set_id='svarc', model_name='StaticVarCompensatorPV') + model_mapping.add_base_line(static_id='LINE', parameter_set_id='l', model_name='Line') + model_mapping.add_base_bus(static_id='BUS', parameter_set_id='bus', model_name='Bus') + model_mapping.add_infinite_bus(static_id='BUS', parameter_set_id='inf_bus', model_name='InfiniteBus') # Dynamic automation systems model_mapping.add_overload_management_system(dynamic_model_id='DM_OV', parameter_set_id='ov', controlled_branch='LINE1', i_measurement='LINE2', i_measurement_side='TWO', model_name='OverloadManagementSystem') - model_mapping.add_two_levels_overload_management_system(dynamic_model_id='DM_TOV', parameter_set_id='tov', + model_mapping.add_two_level_overload_management_system(dynamic_model_id='DM_TOV', parameter_set_id='tov', controlled_branch= 'LINE1', i_measurement_1='LINE1', i_measurement_1_side='TWO', i_measurement_2='LINE2', i_measurement_2_side='ONE', @@ -58,7 +58,7 @@ def test_add_mapping(): model_mapping.add_base_load(static_id='LOAD', parameter_set_id='lab') # Equipment model from Supported models model_name = model_mapping.get_supported_models(dyn.DynamicMappingType.BASE_LOAD)[0] - model_mapping.add_base_load(static_id='LOAD', parameter_set_id='lab', dynamic_model_id='DM_LOAD', model_name=model_name) + model_mapping.add_base_load(static_id='LOAD', parameter_set_id='lab', model_name=model_name) def test_dynamic_dataframe(): diff --git a/tests/test_loadflow.py b/tests/test_loadflow.py index 130bea3984..1576168416 100644 --- a/tests/test_loadflow.py +++ b/tests/test_loadflow.py @@ -61,9 +61,9 @@ def test_run_lf_ac_2slacks(): sbr0 = results[0].slack_bus_results[0] sbr1 = results[0].slack_bus_results[1] assert 'VL4_0' == sbr0.id - assert abs(-0.56 - sbr0.active_power_mismatch) < 0.01 + assert abs(sbr0.active_power_mismatch) < 0.01 assert 'VL2_0' == sbr1.id - assert abs(-0.56 - sbr1.active_power_mismatch) < 0.01 + assert abs(sbr1.active_power_mismatch) < 0.01 def test_run_lf_dc(): @@ -278,11 +278,14 @@ def test_get_provider_parameters_names(): 'fictitiousGeneratorVoltageControlCheckMode', 'areaInterchangeControl', 'areaInterchangeControlAreaType', - 'areaInterchangePMaxMismatch'] + 'areaInterchangePMaxMismatch', + 'voltageRemoteControlRobustMode', + 'forceTargetQInReactiveLimits', + 'disableInconsistentVoltageControls'] def test_get_provider_parameters(): specific_parameters = pp.loadflow.get_provider_parameters('OpenLoadFlow') - assert 71 == len(specific_parameters) + assert 74 == len(specific_parameters) assert 'Slack bus selection mode' == specific_parameters['description']['slackBusSelectionMode'] assert 'STRING' == specific_parameters['type']['slackBusSelectionMode'] assert 'MOST_MESHED' == specific_parameters['default']['slackBusSelectionMode'] diff --git a/tests/test_network.py b/tests/test_network.py index 3f4316f492..2cf500935b 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -164,8 +164,8 @@ def test_get_import_supported_extensions(): def test_get_import_parameters(): parameters = pp.network.get_import_parameters('PSS/E') - assert 1 == len(parameters) - assert ['psse.import.ignore-base-voltage'] == parameters.index.tolist() + assert 2 == len(parameters) + assert ['psse.import.ignore-base-voltage', 'psse.import.ignore-node-breaker-topology'] == parameters.index.tolist() assert 'Ignore base voltage specified in the file' == parameters['description']['psse.import.ignore-base-voltage'] assert 'BOOLEAN' == parameters['type']['psse.import.ignore-base-voltage'] assert 'false' == parameters['default']['psse.import.ignore-base-voltage'] @@ -912,13 +912,13 @@ def test_ratio_tap_changers(): def test_ratio_tap_changers_3_windings(): n = pp.network.create_micro_grid_be_network() - expected = pd.DataFrame(index=pd.Series(name='id', data=['e482b89a-fa84-4ea9-8e70-a83d44790957', - 'b94318f6-6d24-4f56-96b9-df2531ad6543', + expected = pd.DataFrame(index=pd.Series(name='id', data=['b94318f6-6d24-4f56-96b9-df2531ad6543', + 'e482b89a-fa84-4ea9-8e70-a83d44790957', '84ed55f4-61f5-4d9d-8755-bba7b877a246']), columns=['side', 'tap', 'low_tap', 'high_tap', 'step_count', 'on_load', 'regulating', 'target_v', 'target_deadband', 'regulating_bus_id'], - data=[['', 14, 1, 33, 33, True, True, 10.815, 0.5, '4ba71b59-ee2f-450b-9f7d-cc2f1cc5e386_0'], - ['', 10, 1, 25, 25, True, False, 0.0, 0.5, '8bbd7e74-ae20-4dce-8780-c20f8e18c2e0_0'], + data=[['', 10, 1, 25, 25, True, False, 0.0, 0.5, '8bbd7e74-ae20-4dce-8780-c20f8e18c2e0_0'], + ['', 14, 1, 33, 33, True, True, 10.815, 0.5, '4ba71b59-ee2f-450b-9f7d-cc2f1cc5e386_0'], ['TWO', 17, 1, 33, 33, True, False, 0.0, 0.5, 'b10b171b-3bc5-4849-bb1f-61ed9ea1ec7c_0']]) pd.testing.assert_frame_equal(expected, n.get_ratio_tap_changers(), check_dtype=False, atol=1e-2) @@ -928,13 +928,13 @@ def test_ratio_tap_changers_3_windings(): data=[[9, 16.7, True]]) n.update_ratio_tap_changers(update) - expected = pd.DataFrame(index=pd.Series(name='id', data=['e482b89a-fa84-4ea9-8e70-a83d44790957', - 'b94318f6-6d24-4f56-96b9-df2531ad6543', + expected = pd.DataFrame(index=pd.Series(name='id', data=['b94318f6-6d24-4f56-96b9-df2531ad6543', + 'e482b89a-fa84-4ea9-8e70-a83d44790957', '84ed55f4-61f5-4d9d-8755-bba7b877a246']), columns=['side', 'tap', 'low_tap', 'high_tap', 'step_count', 'on_load', 'regulating', 'target_v', 'target_deadband', 'regulating_bus_id'], - data=[['', 14, 1, 33, 33, True, True, 10.815, 0.5, '4ba71b59-ee2f-450b-9f7d-cc2f1cc5e386_0'], - ['', 10, 1, 25, 25, True, False, 0.0, 0.5, '8bbd7e74-ae20-4dce-8780-c20f8e18c2e0_0'], + data=[['', 10, 1, 25, 25, True, False, 0.0, 0.5, '8bbd7e74-ae20-4dce-8780-c20f8e18c2e0_0'], + ['', 14, 1, 33, 33, True, True, 10.815, 0.5, '4ba71b59-ee2f-450b-9f7d-cc2f1cc5e386_0'], ['TWO', 9, 1, 33, 33, True, True, 16.7, 0.5, 'b10b171b-3bc5-4849-bb1f-61ed9ea1ec7c_0']]) pd.testing.assert_frame_equal(expected, n.get_ratio_tap_changers(), check_dtype=False, atol=1e-2) diff --git a/tests/test_rao.py b/tests/test_rao.py index 238418fd57..877fa0369a 100644 --- a/tests/test_rao.py +++ b/tests/test_rao.py @@ -18,15 +18,15 @@ def test_default_rao_parameters(): parameters = RaoParameters() json_param = parameters.to_json() - assert json_param['version'] == '2.4' - assert json_param['objective-function']['type'] == 'MAX_MIN_MARGIN_IN_MEGAWATT' + assert json_param['version'] == '3.0' + assert json_param['objective-function']['type'] == 'SECURE_FLOW' def test_rao_parameters(): parameters = RaoParameters() parameters.load_from_file_source(DATA_DIR.joinpath("rao/rao_parameters.json")) json_param = parameters.to_json() - assert json_param['range-actions-optimization']['max-mip-iterations'] == 15 - assert json_param['objective-function']['type'] == 'MAX_MIN_RELATIVE_MARGIN_IN_MEGAWATT' + assert json_param['range-actions-optimization']['pst-ra-min-impact-threshold'] == 0.01 + assert json_param['objective-function']['type'] == 'MAX_MIN_MARGIN' def test_rao_from_files(): network = pp.network.load(DATA_DIR.joinpath("rao/rao_network.uct")) diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index cbe2e904f7..d6a239776a 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -245,8 +245,8 @@ def test_security_analysis_parameters(): def test_provider_parameters_names(): - assert pp.security.get_provider_parameters_names() == ['createResultExtension', 'contingencyPropagation', 'threadCount', 'dcFastMode'] - assert pp.security.get_provider_parameters_names('OpenLoadFlow') == ['createResultExtension', 'contingencyPropagation', 'threadCount', 'dcFastMode'] + assert pp.security.get_provider_parameters_names() == ['createResultExtension', 'contingencyPropagation', 'threadCount', 'dcFastMode', 'contingencyActivePowerLossDistribution'] + assert pp.security.get_provider_parameters_names('OpenLoadFlow') == ['createResultExtension', 'contingencyPropagation', 'threadCount', 'dcFastMode', 'contingencyActivePowerLossDistribution'] with pytest.raises(pp.PyPowsyblError, match='No security analysis provider for name \'unknown\''): pp.security.get_provider_parameters_names('unknown') diff --git a/tests/test_sensitivity_analysis.py b/tests/test_sensitivity_analysis.py index cb21da8e75..7ca7e0cb43 100644 --- a/tests/test_sensitivity_analysis.py +++ b/tests/test_sensitivity_analysis.py @@ -252,7 +252,7 @@ def test_provider_parameters(): n = pp.network.create_eurostag_tutorial_example1_network() analysis = pp.sensitivity.create_ac_analysis() analysis.add_branch_flow_factor_matrix(['NHV1_NHV2_1'], ['GEN']) - with pytest.raises(pp.PyPowsyblError, match='Load flow ended with status MAX_ITERATION_REACHED'): + with pytest.raises(pp.PyPowsyblError, match='Initial load flow of base situation ended with solver status MAX_ITERATION_REACHED'): analysis.run(n, parameters) # does not throw result = analysis.run(n) From 6d86170fed4d19959f0f0762ea99e60ae006a566 Mon Sep 17 00:00:00 2001 From: HugoKulesza <94374655+HugoKulesza@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:11:50 +0200 Subject: [PATCH 56/71] Adding terminals connection action (#953) Signed-off-by: Hugo KULESZA Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 6 +++ cpp/powsybl-cpp/powsybl-cpp.h | 3 ++ cpp/pypowsybl-cpp/bindings.cpp | 3 ++ docs/user_guide/security.rst | 1 + .../security/SecurityAnalysisCFunctions.java | 41 ++++++------------- pypowsybl/_pypowsybl.pyi | 1 + pypowsybl/security/impl/security.py | 12 ++++++ tests/test_security_analysis.py | 31 +++----------- 8 files changed, 45 insertions(+), 53 deletions(-) diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index af7b2777be..f7929bc8df 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -806,6 +806,12 @@ void addShuntCompensatorPositionAction(const JavaHandle& analysisContext, const PowsyblCaller::get()->callJava(::addShuntCompensatorPositionAction, analysisContext, (char*) actionId.data(), (char*) shuntId.data(), sectionCount); } +void addTerminalsConnectionAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& elementId, + ThreeSide side, bool opening) { + PowsyblCaller::get()->callJava(::addTerminalsConnectionAction, analysisContext, (char*) actionId.data(), (char*) elementId.data(), + side, opening); +} + void addOperatorStrategy(const JavaHandle& analysisContext, std::string operatorStrategyId, std::string contingencyId, const std::vector& actionsIds, condition_type conditionType, const std::vector& subjectIds, const std::vector& violationTypesFilters) { ToCharPtrPtr actionsPtr(actionsIds); diff --git a/cpp/powsybl-cpp/powsybl-cpp.h b/cpp/powsybl-cpp/powsybl-cpp.h index fae1cd5899..488c02f2b8 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.h +++ b/cpp/powsybl-cpp/powsybl-cpp.h @@ -613,6 +613,9 @@ void addRatioTapChangerPositionAction(const JavaHandle& analysisContext, const s void addShuntCompensatorPositionAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& shuntId, int sectionCount); +void addTerminalsConnectionAction(const JavaHandle& analysisContext, const std::string& actionId, const std::string& elementId, + ThreeSide side, bool opening); + void addOperatorStrategy(const JavaHandle& analysisContext, std::string operatorStrategyId, std::string contingencyId, const std::vector& actionsIds, condition_type conditionType, const std::vector& subjectIds, const std::vector& violationTypesFilters); diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index 1aab2ecf30..f67bd0fb9a 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -727,6 +727,9 @@ PYBIND11_MODULE(_pypowsybl, m) { 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_terminals_connection_action", &pypowsybl::addTerminalsConnectionAction, "Add a terminals connection action", + py::arg("analysis_context"), py::arg("action_id"), py::arg("element_id"), py::arg("side"), py::arg("open")); + 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")); diff --git a/docs/user_guide/security.rst b/docs/user_guide/security.rst index a8d4074064..08c7196be8 100644 --- a/docs/user_guide/security.rst +++ b/docs/user_guide/security.rst @@ -112,6 +112,7 @@ The supported actions in PyPowsybl are listed here: - `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'. diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index 0323112a2a..4016dff177 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -38,8 +38,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.Objects; import java.util.Set; @@ -138,17 +136,6 @@ public static void addContingency(IsolateThread thread, ObjectHandle contingency }); } - @CEntryPoint(name = "addContingencyFromJsonFile") - public static void addContingencyFromJsonFile(IsolateThread thread, ObjectHandle contingencyContainerHandle, CCharPointer jsonFilePath, - PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { - doCatch(exceptionHandlerPtr, () -> { - ContingencyContainer contingencyContainer = ObjectHandles.getGlobal().get(contingencyContainerHandle); - String stringPath = CTypeUtil.toString(jsonFilePath); - Path path = Paths.get(stringPath); - contingencyContainer.addContingencyFromJsonFile(path); - }); - } - private static void setPostContingencyResultInSecurityAnalysisResultPointer(PyPowsyblApiHeader.PostContingencyResultPointer contingencyPtr, PostContingencyResult postContingencyResult) { contingencyPtr.setContingencyId(CTypeUtil.toCharPtr(postContingencyResult.getContingency().getId())); contingencyPtr.setStatus(postContingencyResult.getStatus().ordinal()); @@ -460,14 +447,21 @@ public static void addShuntCompensatorPositionAction(IsolateThread thread, Objec }); } - @CEntryPoint(name = "addActionFromJsonFile") - public static void addActionFromJsonFile(IsolateThread thread, ObjectHandle securityAnalysisContextHandle, - CCharPointer jsonFilePath, PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { + @CEntryPoint(name = "addTerminalsConnectionAction") + public static void addTerminalsConnectionAction(IsolateThread thread, ObjectHandle securityAnalysisContextHandle, + CCharPointer actionId, CCharPointer elementId, PyPowsyblApiHeader.ThreeSideType side, + boolean opening, PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { doCatch(exceptionHandlerPtr, () -> { SecurityAnalysisContext analysisContext = ObjectHandles.getGlobal().get(securityAnalysisContextHandle); - String stringPath = CTypeUtil.toString(jsonFilePath); - Path path = Paths.get(stringPath); - analysisContext.addActionFromJsonFile(path); + String actionIdStr = CTypeUtil.toString(actionId); + String elementIdStr = CTypeUtil.toString(elementId); + TerminalsConnectionActionBuilder builder = new TerminalsConnectionActionBuilder(); + TerminalsConnectionAction action = builder.withId(actionIdStr) + .withNetworkElementId(elementIdStr) + .withSide(Util.convert(side)) + .withOpen(opening) + .build(); + analysisContext.addAction(action); }); } @@ -493,15 +487,6 @@ public static void addOperatorStrategy(IsolateThread thread, ObjectHandle securi }); } - @CEntryPoint(name = "addOperatorStrategyFromJsonFile") - public static void addOperatorStrategyFromJsonFile(IsolateThread thread, ObjectHandle securityAnalysisContextHandle, - CCharPointer jsonFilePath, PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { - SecurityAnalysisContext analysisContext = ObjectHandles.getGlobal().get(securityAnalysisContextHandle); - String stringPath = CTypeUtil.toString(jsonFilePath); - Path path = Paths.get(stringPath); - analysisContext.addOperatorStrategyFromJsonFile(path); - } - private static Condition buildCondition(PyPowsyblApiHeader.ConditionType conditionType, CCharPointerPointer subjectIds, int subjectIdsCount, CIntPointer violationTypes, int violationTypesCount) { diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index a9bf97c86d..481406c05a 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -804,6 +804,7 @@ def add_switch_action(security_analysis_context: JavaHandle, action_id: str, swi def add_phase_tap_changer_position_action(security_analysis_context: JavaHandle, action_id: str, transformer_id: str, is_relative: bool, tap_position: int, side: Side) -> None: ... def add_ratio_tap_changer_position_action(security_analysis_context: JavaHandle, action_id: str, transformer_id: str, is_relative: bool, tap_position: int, side: Side) -> None: ... def add_shunt_compensator_position_action(security_analysis_context: JavaHandle, action_id: str, shunt_id: str, section: int) -> None: ... +def add_terminals_connection_action(security_analysis_context: JavaHandle, action_id: str, element_id: str, side: Side, opening: bool) -> None: ... def add_operator_strategy(security_analysis_context: JavaHandle, operator_strategy_id: str, contingency_id: str, action_ids: List[str], condition_type: ConditionType, violation_subject_ids: List[str], violation_types: List[ViolationType]) -> None: ... def add_action_from_json_file(security_analysis_context: JavaHandle, path_to_json_file: str) -> None: ... def add_operator_strategy_from_json_file(security_analysis_context: JavaHandle, path_to_json_file: str) -> None: ... diff --git a/pypowsybl/security/impl/security.py b/pypowsybl/security/impl/security.py index 9f56a23981..e5f0d6f703 100644 --- a/pypowsybl/security/impl/security.py +++ b/pypowsybl/security/impl/security.py @@ -228,6 +228,18 @@ def add_shunt_compensator_position_action(self, action_id: str, shunt_id: str, s """ _pypowsybl.add_shunt_compensator_position_action(self._handle, action_id, shunt_id, section) + def add_terminals_connection_action(self, action_id: str, element_id: str, side: Side = Side.NONE, opening: bool = True) -> None: + """ Add a terminals connection action, connecting/disconnecting one or multiple sides of a network element + + Args: + action_id: unique ID for the action + element_id: network element identifier + side: The side of the element to modify (all if side=None) + opening: True to open the terminals, False otherwise + """ + _pypowsybl.add_terminals_connection_action(self._handle, action_id, element_id, side, opening) + + def add_operator_strategy(self, operator_strategy_id: str, contingency_id: str, action_ids: List[str], condition_type: ConditionType = ConditionType.TRUE_CONDITION, violation_subject_ids: List[str] = None, violation_types: List[ViolationType] = None) -> None: diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index d6a239776a..93b44d9033 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -11,10 +11,6 @@ import pypowsybl.report as rp from pypowsybl._pypowsybl import ConditionType import re -import pathlib - -TEST_DIR = pathlib.Path(__file__).parent -DATA_DIR = TEST_DIR.parent.joinpath('data') @pytest.fixture(autouse=True) @@ -412,27 +408,12 @@ def test_tie_line_contingency(): sa_result = sa.run_ac(n) assert 'tie line contingency' in sa_result.post_contingency_results.keys() -def test_add_contingencies_from_json_file(): +def test_terminal_connection_action(): n = pp.network.create_eurostag_tutorial_example1_network() sa = pp.security.create_analysis() - sa.add_contingencies_from_json_file(str(DATA_DIR.joinpath('contingencies.json'))) - sa_result = sa.run_ac(n) - assert 'contingency' in sa_result.post_contingency_results.keys() - assert 'contingency2' in sa_result.post_contingency_results.keys() - -def test_add_actions_from_json_file(): - n = pp.network.create_four_substations_node_breaker_network() - sa = pp.security.create_analysis() - sa.add_actions_from_json_file(str(DATA_DIR.joinpath('ActionFileTestV1.0.json'))) - sa.run_dc(n) - -def test_add_operator_strategies_from_json_file(): - n = pp.network.create_four_substations_node_breaker_network() - sa = pp.security.create_analysis() - sa.add_single_element_contingency('550ebe0d-f2b2-48c1-991f-cebea43a21aa', 'BE-G2_contingency') sa.add_single_element_contingency('NHV1_NHV2_1', 'Line contingency') - - sa.add_operator_strategies_from_json_file(str(DATA_DIR.joinpath('OperatorStrategyFileTestV1.0.json'))) - sa_result = sa.run_dc(n) - df = sa_result.branch_results - + sa.add_terminals_connection_action(action_id="Disconnection", element_id='NHV1_NHV2_2', opening=True) + sa.add_operator_strategy('OperatorStrategy1', 'Line contingency', ['Disconnection']) + sa_result = sa.run_ac(n) + assert 'Line contingency' in sa_result.post_contingency_results.keys() + assert 'OperatorStrategy1' in sa_result.operator_strategy_results.keys() From 86058616ddae475a27952a3e93bc4cebf53b1875 Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo <93923177+So-Fras@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:30:22 +0200 Subject: [PATCH 57/71] Add voltage_level_details parameter (#963) Signed-off-by: Sophie Frasnedo Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 2 ++ cpp/powsybl-cpp/powsybl-cpp.h | 1 + cpp/pypowsybl-cpp/bindings.cpp | 3 ++- cpp/pypowsybl-java/powsybl-api.h | 1 + docs/user_guide/network_visualization.rst | 5 ++++- .../powsybl/python/commons/PyPowsyblApiHeader.java | 6 ++++++ .../powsybl/python/network/NetworkCFunctions.java | 1 + pypowsybl/_pypowsybl.pyi | 1 + pypowsybl/network/impl/nad_parameters.py | 10 +++++++++- tests/test_network.py | 13 +++++++++---- 10 files changed, 36 insertions(+), 7 deletions(-) diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index f7929bc8df..cb1082e7da 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -1268,6 +1268,7 @@ NadParameters::NadParameters(nad_parameters* src) { scaling_factor = src->scaling_factor; radius_factor = src->radius_factor; edge_info_displayed = static_cast(src->edge_info_displayed); + voltage_level_details = (bool) src->voltage_level_details; } void SldParameters::sld_to_c_struct(sld_parameters& res) const { @@ -1298,6 +1299,7 @@ void NadParameters::nad_to_c_struct(nad_parameters& res) const { res.scaling_factor = scaling_factor; res.radius_factor = radius_factor; res.edge_info_displayed = (int) edge_info_displayed; + res.voltage_level_details = (unsigned char) voltage_level_details; } std::shared_ptr SldParameters::to_c_struct() const { diff --git a/cpp/powsybl-cpp/powsybl-cpp.h b/cpp/powsybl-cpp/powsybl-cpp.h index 488c02f2b8..8bb760428f 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.h +++ b/cpp/powsybl-cpp/powsybl-cpp.h @@ -436,6 +436,7 @@ class NadParameters { int scaling_factor; double radius_factor; EdgeInfoType edge_info_displayed; + bool voltage_level_details; }; //=======short-circuit analysis========== diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index f67bd0fb9a..227bc5ff01 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -664,7 +664,8 @@ PYBIND11_MODULE(_pypowsybl, m) { .def_readwrite("layout_type", &pypowsybl::NadParameters::layout_type) .def_readwrite("scaling_factor", &pypowsybl::NadParameters::scaling_factor) .def_readwrite("radius_factor", &pypowsybl::NadParameters::radius_factor) - .def_readwrite("edge_info_displayed",&pypowsybl::NadParameters::edge_info_displayed); + .def_readwrite("edge_info_displayed",&pypowsybl::NadParameters::edge_info_displayed) + .def_readwrite("voltage_level_details", &pypowsybl::NadParameters::voltage_level_details); m.def("write_single_line_diagram_svg", &pypowsybl::writeSingleLineDiagramSvg, "Write single line diagram SVG", py::arg("network"), py::arg("container_id"), py::arg("svg_file"), py::arg("metadata_file"), py::arg("sld_parameters")); diff --git a/cpp/pypowsybl-java/powsybl-api.h b/cpp/pypowsybl-java/powsybl-api.h index 425c4c837c..91972036d0 100644 --- a/cpp/pypowsybl-java/powsybl-api.h +++ b/cpp/pypowsybl-java/powsybl-api.h @@ -369,6 +369,7 @@ typedef struct nad_parameters_struct { int scaling_factor; double radius_factor; int edge_info_displayed; + unsigned char voltage_level_details; } nad_parameters; typedef enum { diff --git a/docs/user_guide/network_visualization.rst b/docs/user_guide/network_visualization.rst index 6958e2648d..e8dbbce658 100644 --- a/docs/user_guide/network_visualization.rst +++ b/docs/user_guide/network_visualization.rst @@ -269,7 +269,7 @@ Network-area diagrams can be customized through NadParameters: >>> from pypowsybl.network import NadParameters >>> network = pp.network.create_ieee14() - >>> nad = network.get_network_area_diagram('VL6', nad_parameters=NadParameters(edge_name_displayed=True, id_displayed=True, edge_info_along_edge=False, power_value_precision=1, angle_value_precision=0, current_value_precision=1, voltage_value_precision=0, bus_legend=False, substation_description_displayed=True, edge_info_displayed=EdgeInfoType.REACTIVE_POWER)) + >>> nad = network.get_network_area_diagram('VL6', nad_parameters=NadParameters(edge_name_displayed=True, id_displayed=True, edge_info_along_edge=False, power_value_precision=1, angle_value_precision=0, current_value_precision=1, voltage_value_precision=0, bus_legend=False, substation_description_displayed=True, edge_info_displayed=EdgeInfoType.REACTIVE_POWER, voltage_level_details=False)) - edge_name_displayed: if true, names along lines and transformer legs are displayed (default value false) - id_displayed: if true, the equipment ids are displayed. If false, the equipment names are displayed (if a name is null, then the id is displayed) (default value false) @@ -281,6 +281,9 @@ Network-area diagrams can be customized through NadParameters: - bus_legend: if true, angle and voltage values associated to a voltage level are displayed in a text box. If false, only the voltage level name is displayed (default value true) - substation_description_displayed: if true, the substation name is added to the voltage level info on the diagram (default value false) - edge_info_displayed: type of info displayed (EdgeInfoType.ACTIVE_POWER(default),EdgeInfoType.REACTIVE_POWER or EdgeInfoType.CURRENT) + - voltage_level_details: if true, additional information about voltage levels is displayed in text boxes. The content of the additional information is determined by the label provider that is used. + + In order to get a list of the displayed voltage levels from an input voltage level (or an input list of voltage levels) and a depth: diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java b/java/pypowsybl/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java index 4d9f73e0cf..536d677f8d 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java @@ -1182,6 +1182,12 @@ public interface NadParametersPointer extends PointerBase { @CField("edge_info_displayed") int getEdgeInfoDisplayed(); + + @CField("voltage_level_details") + void setVoltageLevelDetails(boolean isVoltageLevelDetails); + + @CField("voltage_level_details") + boolean isVoltageLevelDetails(); } @CEnum("DynamicMappingType") diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java index 101431c37a..4ce23aef28 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java @@ -973,6 +973,7 @@ public static void copyToCNadParameters(NadParameters parameters, NadParametersP cParameters.setBusLegend(parameters.getSvgParameters().isBusLegend()); cParameters.setSubstationDescriptionDisplayed(parameters.getSvgParameters().isSubstationDescriptionDisplayed()); cParameters.setEdgeInfoDisplayed(edgeInfo); + cParameters.setVoltageLevelDetails(parameters.getSvgParameters().isVoltageLevelDetails()); } @CEntryPoint(name = "createNadParameters") diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 481406c05a..ccd2394cbf 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -410,6 +410,7 @@ class NadParameters: scaling_factor: int radius_factor: float edge_info_displayed: EdgeInfoType + voltage_level_details: bool def __init__(self) -> None: ... class SlackBusResult: diff --git a/pypowsybl/network/impl/nad_parameters.py b/pypowsybl/network/impl/nad_parameters.py index d0b39ad99c..d106af75c2 100644 --- a/pypowsybl/network/impl/nad_parameters.py +++ b/pypowsybl/network/impl/nad_parameters.py @@ -17,7 +17,7 @@ def __init__(self, edge_name_displayed: bool = False, id_displayed: bool = False current_value_precision: int = 0, voltage_value_precision: int = 1, bus_legend: bool = True, substation_description_displayed: bool = False, layout_type: NadLayoutType = NadLayoutType.FORCE_LAYOUT, scaling_factor: int = 150000, radius_factor: float = 150.0, - edge_info_displayed: EdgeInfoType = EdgeInfoType.ACTIVE_POWER): + edge_info_displayed: EdgeInfoType = EdgeInfoType.ACTIVE_POWER, voltage_level_details: bool = True): self._edge_name_displayed = edge_name_displayed self._edge_info_along_edge = edge_info_along_edge self._id_displayed = id_displayed @@ -31,6 +31,7 @@ def __init__(self, edge_name_displayed: bool = False, id_displayed: bool = False self._scaling_factor = scaling_factor self._radius_factor = radius_factor self._edge_info_displayed = edge_info_displayed + self._voltage_level_details = voltage_level_details @property def edge_name_displayed(self) -> bool: @@ -97,6 +98,11 @@ def edge_info_displayed(self) -> EdgeInfoType: """edge_info_displayed""" return self._edge_info_displayed + @property + def voltage_level_details(self) -> bool: + """voltage_level_details""" + return self._voltage_level_details + def _to_c_parameters(self) -> _pp.NadParameters: c_parameters = _pp.NadParameters() c_parameters.edge_name_displayed = self._edge_name_displayed @@ -112,6 +118,7 @@ def _to_c_parameters(self) -> _pp.NadParameters: c_parameters.scaling_factor = self._scaling_factor c_parameters.radius_factor = self._radius_factor c_parameters.edge_info_displayed = self._edge_info_displayed + c_parameters.voltage_level_details = self._voltage_level_details return c_parameters def __repr__(self) -> str: @@ -129,4 +136,5 @@ def __repr__(self) -> str: f", scaling_factor={self._scaling_factor}" \ f", radius_factor={self._radius_factor}" \ f", edge_info_displayed={self._edge_info_displayed}" \ + f", voltage_level_details={self._voltage_level_details}" \ f")" diff --git a/tests/test_network.py b/tests/test_network.py index 2cf500935b..20e580c814 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -1123,7 +1123,8 @@ def test_nad(): voltage_value_precision=0, bus_legend=False, substation_description_displayed=True, - edge_info_displayed=EdgeInfoType.CURRENT + edge_info_displayed=EdgeInfoType.CURRENT, + voltage_level_details=False )) assert re.search('.* Date: Mon, 7 Apr 2025 08:49:27 +0200 Subject: [PATCH 58/71] Fail early if crac is not valid (#979) Signed-off-by: Bertrand Rix Signed-off-by: Naledi EL CHEIKH --- .../src/main/java/com/powsybl/python/rao/RaoCFunctions.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/rao/RaoCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/rao/RaoCFunctions.java index 74eb6f1f86..bc106776c3 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/rao/RaoCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/rao/RaoCFunctions.java @@ -86,7 +86,11 @@ public static void setCracBufferedSource(IsolateThread thread, ObjectHandle netw InputStream streamedCrac = new ByteArrayInputStream(binaryBufferToBytes(bufferCrac)); try { Crac crac = Crac.read("crac.json", streamedCrac, network); - raoContext.setCrac(crac); + if (crac != null) { + raoContext.setCrac(crac); + } else { + throw new PowsyblException("Error while reading json crac, please enable detailed log for more information."); + } } catch (IOException e) { throw new PowsyblException("Cannot read provided crac data : " + e.getMessage()); } From 8645d40d600af2bb6166d96d5138ad15085ed3f0 Mon Sep 17 00:00:00 2001 From: Christian Biasuzzi Date: Mon, 7 Apr 2025 09:29:35 +0200 Subject: [PATCH 59/71] extends NadProfile with style customizations (#976) Signed-off-by: Christian Biasuzzi Signed-off-by: Naledi EL CHEIKH --- cpp/powsybl-cpp/powsybl-cpp.cpp | 8 +- cpp/powsybl-cpp/powsybl-cpp.h | 4 +- cpp/pypowsybl-cpp/bindings.cpp | 5 +- docs/user_guide/network_visualization.rst | 34 ++- .../python/network/NetworkCFunctions.java | 240 +++++++++++------- .../resource-config.json | 1 + pypowsybl/_pypowsybl.pyi | 4 +- pypowsybl/network/impl/nad_profile.py | 58 ++++- pypowsybl/network/impl/network.py | 11 +- 9 files changed, 256 insertions(+), 109 deletions(-) diff --git a/cpp/powsybl-cpp/powsybl-cpp.cpp b/cpp/powsybl-cpp/powsybl-cpp.cpp index cb1082e7da..0dc282890b 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.cpp +++ b/cpp/powsybl-cpp/powsybl-cpp.cpp @@ -723,11 +723,11 @@ std::vector getMatrixMultiSubstationSvgAndMetadata(const JavaHandle } void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, - dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions) { + dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions, dataframe* bus_node_styles, dataframe* edge_styles, dataframe* three_wt_styles) { auto c_parameters = parameters.to_c_struct(); ToCharPtrPtr voltageLevelIdPtr(voltageLevelIds); PowsyblCaller::get()->callJava(::writeNetworkAreaDiagramSvg, network, (char*) svgFile.data(), (char*) metadataFile.data(), - voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels, three_wt_labels, bus_descriptions, vl_descriptions); + voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels, three_wt_labels, bus_descriptions, vl_descriptions, bus_node_styles, edge_styles, three_wt_styles); } std::string getNetworkAreaDiagramSvg(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters) { @@ -737,10 +737,10 @@ std::string getNetworkAreaDiagramSvg(const JavaHandle& network, const std::vecto } std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, - dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions) { + dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions, dataframe* bus_node_styles, dataframe* edge_styles, dataframe* three_wt_styles) { auto c_parameters = parameters.to_c_struct(); ToCharPtrPtr voltageLevelIdPtr(voltageLevelIds); - auto svgAndMetadataArrayPtr = PowsyblCaller::get()->callJava(::getNetworkAreaDiagramSvgAndMetadata, network, voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels, three_wt_labels, bus_descriptions, vl_descriptions); + auto svgAndMetadataArrayPtr = PowsyblCaller::get()->callJava(::getNetworkAreaDiagramSvgAndMetadata, network, voltageLevelIdPtr.get(), voltageLevelIds.size(), depth, highNominalVoltageBound, lowNominalVoltageBound, c_parameters.get(), fixed_positions, branch_labels, three_wt_labels, bus_descriptions, vl_descriptions, bus_node_styles, edge_styles, three_wt_styles); ToStringVector svgAndMetadata(svgAndMetadataArrayPtr); return svgAndMetadata.get(); } diff --git a/cpp/powsybl-cpp/powsybl-cpp.h b/cpp/powsybl-cpp/powsybl-cpp.h index 8bb760428f..7e9292d699 100644 --- a/cpp/powsybl-cpp/powsybl-cpp.h +++ b/cpp/powsybl-cpp/powsybl-cpp.h @@ -581,12 +581,12 @@ std::vector getMatrixMultiSubstationSvgAndMetadata(const JavaHandle std::vector getSingleLineDiagramComponentLibraryNames(); void writeNetworkAreaDiagramSvg(const JavaHandle& network, const std::string& svgFile, const std::string& metadataFile, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, - dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions); + dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions, dataframe* bus_node_styles, dataframe* edge_styles, dataframe* three_wt_styles); std::string getNetworkAreaDiagramSvg(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters); std::vector getNetworkAreaDiagramSvgAndMetadata(const JavaHandle& network, const std::vector& voltageLevelIds, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, const NadParameters& parameters, dataframe* fixed_positions, - dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions); + dataframe* branch_labels, dataframe* three_wt_labels, dataframe* bus_descriptions, dataframe* vl_descriptions, dataframe* bus_node_styles, dataframe* edge_styles, dataframe* three_wt_styles); std::vector getNetworkAreaDiagramDisplayedVoltageLevels(const JavaHandle& network, const std::vector& voltageLevelIds, int depth); diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index 227bc5ff01..7c3fe546ff 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -687,14 +687,15 @@ PYBIND11_MODULE(_pypowsybl, m) { m.def("write_network_area_diagram_svg", &pypowsybl::writeNetworkAreaDiagramSvg, "Write network area diagram SVG", py::arg("network"), py::arg("svg_file"), py::arg("metadata_file"), py::arg("voltage_level_ids"), py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters"), py::arg("fixed_positions"), - py::arg("branch_labels"), py::arg("three_wt_labels"), py::arg("bus_descriptions"), py::arg("vl_descriptions")); + py::arg("branch_labels"), py::arg("three_wt_labels"), py::arg("bus_descriptions"), py::arg("vl_descriptions"), + py::arg("bus_node_styles"), py::arg("edge_styles"), py::arg("three_wt_styles")); m.def("get_network_area_diagram_svg", &pypowsybl::getNetworkAreaDiagramSvg, "Get network area diagram SVG as a string", py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters")); m.def("get_network_area_diagram_svg_and_metadata", &pypowsybl::getNetworkAreaDiagramSvgAndMetadata, "Get network area diagram SVG and its metadata as a list of strings", py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth"), py::arg("high_nominal_voltage_bound"), py::arg("low_nominal_voltage_bound"), py::arg("nad_parameters"), py::arg("fixed_positions"), - py::arg("branch_labels"), py::arg("three_wt_labels"), py::arg("bus_descriptions"), py::arg("vl_descriptions")); + py::arg("branch_labels"), py::arg("three_wt_labels"), py::arg("bus_descriptions"), py::arg("vl_descriptions"), py::arg("bus_node_styles"), py::arg("edge_styles"), py::arg("three_wt_styles")); m.def("get_network_area_diagram_displayed_voltage_levels", &pypowsybl::getNetworkAreaDiagramDisplayedVoltageLevels, "Get network area diagram displayed voltage level", py::arg("network"), py::arg("voltage_level_ids"), py::arg("depth")); diff --git a/docs/user_guide/network_visualization.rst b/docs/user_guide/network_visualization.rst index e8dbbce658..9c58559693 100644 --- a/docs/user_guide/network_visualization.rst +++ b/docs/user_guide/network_visualization.rst @@ -352,7 +352,20 @@ by using dataframes: {'id': 'S2VL1_0', 'description': 'BUS C'}, {'id': 'S3VL1_0', 'description': 'BUS D'} ]) - >>> diagram_profile=pp.network.NadProfile(branch_labels=labels_df, vl_descriptions=vl_descriptions_df, bus_descriptions=bus_descriptions_df) + >>> bus_node_style_df = pd.DataFrame.from_records(index='id', + data=[ + {'id': 'S1VL1_0', 'fill': 'red', 'edge': 'black', 'edge-width': '4px'}, + {'id': 'S1VL2_0', 'fill': 'blue', 'edge': 'black', 'edge-width': '4px'}, + {'id': 'S2VL1_0', 'fill': 'yellow', 'edge': 'black', 'edge-width': '4px'}, + ]) + >>> edge_style_df = pd.DataFrame.from_records(index='id', + data=[ + {'id': 'LINE_S2S3', 'edge1': 'blue', 'width1': '16px', 'dash1': '12,12' ,'edge2': 'blue', 'width2': '16px', 'dash2': '12,12'}, + {'id': 'LINE_S3S4', 'edge1': 'green', 'width1': '3px', 'edge2': 'green', 'width2': '3px'}, + {'id': 'TWT' , 'edge1': 'yellow', 'width1': '4px', 'edge2': 'blue', 'width2': '4px'}, + ]) + >>> diagram_profile=pp.network.NadProfile(branch_labels=labels_df, vl_descriptions=vl_descriptions_df, bus_descriptions=bus_descriptions_df, + bus_node_styles=bus_node_style_df, edge_styles=edge_style_df) >>> pars=pp.network.NadParameters(edge_name_displayed=True) >>> network.get_network_area_diagram(voltage_level_ids='S1VL1', depth=2, nad_parameters=pars, nad_profile=diagram_profile) @@ -371,11 +384,30 @@ In the bus_descriptions dataframe parameter: - id is the BUS id - description define a label for the BUS +In the bus_node_styles dataframe parameter: + - id is the BUS id + - fill is the fill color for the node + - edge is the edge color for the node + - width is the width of the edge for the node + +In the edge_styles dataframe parameter: + - id is the branch id + - edge1, width1 and dash1 is the color, the width and the dash pattern for the first branch's edge + - edge2, width2 and dash2 is the color, the width and the dash pattern for the second branch's edge + +The dash pattern string specifies the lengths of alternating dashes and gaps in the edge, separated by commas and/or spaces + An additional three_wt_labels dataframe parameter can be used to set the labels and the arrows direction for three winding transformers: - id is the three winding transformer id - side1, side2, and side3 define the labels along the three winding transformer legs - arrow1, arrow2, and arrow3 define the direction of the arrows at the ends of the three winding transformer legs: 'IN' or 'OUT'. None (or an empty string) does not display the arrow. +Similarly to the edge_styles, the three_wt_styles parameter can be used to set the style for the three winding transformers: + - id is the three winding transformer id + - edge1, width1 and dash1 is the color, the width and the dash pattern for the first transformer's leg + - edge2, width2 and dash2 is the color, the width and the dash pattern for the second transformer's leg + - edge3, width3 and dash3 is the color, the width and the dash pattern for the third transformer's leg + The optional parameter nad_profile can also be set in the write_network_area_diagram function. Network area diagram using geographical data diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java index 4ce23aef28..f5d7dead9b 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/network/NetworkCFunctions.java @@ -37,10 +37,10 @@ import com.powsybl.nad.NadParameters; import com.powsybl.nad.layout.*; import com.powsybl.nad.model.*; +import com.powsybl.nad.svg.CustomLabelProvider; +import com.powsybl.nad.svg.CustomStyleProvider; import com.powsybl.nad.svg.EdgeInfo; import com.powsybl.nad.svg.SvgParameters; -import com.powsybl.nad.svg.iidm.DefaultLabelProvider; -import com.powsybl.nad.svg.iidm.LabelProviderFactory; import com.powsybl.python.commons.CTypeUtil; import com.powsybl.python.commons.Directives; import com.powsybl.python.commons.PyPowsyblApiHeader; @@ -1124,8 +1124,8 @@ public static void writeNetworkAreaDiagramSvg(IsolateThread thread, ObjectHandle CCharPointerPointer voltageLevelIdsPointer, int voltageLevelIdCount, int depth, double highNominalVoltageBound, double lowNominalVoltageBound, NadParametersPointer nadParametersPointer, DataframePointer fixedPositions, DataframePointer branchLabels, DataframePointer threeWtLabels, - DataframePointer busDescriptions, DataframePointer vlDescriptions, - ExceptionHandlerPointer exceptionHandlerPtr) { + DataframePointer busDescriptions, DataframePointer vlDescriptions, DataframePointer busNodeStyles, + DataframePointer edgeStyles, DataframePointer threeWtStyles, ExceptionHandlerPointer exceptionHandlerPtr) { doCatch(exceptionHandlerPtr, () -> { Network network = ObjectHandles.getGlobal().get(networkHandle); String svgFileStr = CTypeUtil.toString(svgFile); @@ -1134,6 +1134,7 @@ public static void writeNetworkAreaDiagramSvg(IsolateThread thread, ObjectHandle NadParameters nadParameters = convertNadParameters(nadParametersPointer, network); applyFixedPositions(fixedPositions, nadParameters); applyCustomLabels(branchLabels, threeWtLabels, busDescriptions, vlDescriptions, nadParameters); + applyCustomStyles(busNodeStyles, edgeStyles, threeWtStyles, nadParameters); NetworkAreaDiagramUtil.writeSvg(network, voltageLevelIds, depth, svgFileStr, metadataFileStr, highNominalVoltageBound, lowNominalVoltageBound, nadParameters); }); } @@ -1201,16 +1202,18 @@ private static void applyFixedPositions(DataframePointer fixedPositions, NadPara } } - record CustomBranchLabels(String side1, String middle, String side2, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2) { - } - - record CustomThreeWtLabels(String side1, String side2, String side3, EdgeInfo.Direction arrow1, EdgeInfo.Direction arrow2, EdgeInfo.Direction arrow3) { - } - private static String getValueFromSeriesOrNull(StringSeries series, int row) { return (series != null) ? series.get(row) : null; } + private static String getNonEmptyValueFromSeries(StringSeries series, int row) { + if (series == null) { + return null; + } + String str = series.get(row); + return str != null && !str.isBlank() ? str : null; + } + private static EdgeInfo.Direction getDirectionFromSeriesOrNull(StringSeries series, int row) { if (series == null) { return null; @@ -1219,14 +1222,14 @@ private static EdgeInfo.Direction getDirectionFromSeriesOrNull(StringSeries seri return (dir != null && !dir.isEmpty()) ? EdgeInfo.Direction.valueOf(dir) : null; } - private static Map getNadCustomBranchLabels(int rowCount, StringSeries idSeries, - StringSeries side1Label, StringSeries middleLabel, - StringSeries side2Label, StringSeries arrow1, - StringSeries arrow2) { - Map nadCustomBranchLabels = new HashMap<>(); + private static Map getNadCustomBranchLabels(int rowCount, StringSeries idSeries, + StringSeries side1Label, StringSeries middleLabel, + StringSeries side2Label, StringSeries arrow1, + StringSeries arrow2) { + Map nadCustomBranchLabels = new HashMap<>(); for (int i = 0; i < rowCount; i++) { String id = idSeries.get(i); - CustomBranchLabels labels = new CustomBranchLabels( + CustomLabelProvider.BranchLabels labels = new CustomLabelProvider.BranchLabels( getValueFromSeriesOrNull(side1Label, i), getValueFromSeriesOrNull(middleLabel, i), getValueFromSeriesOrNull(side2Label, i), @@ -1238,7 +1241,7 @@ private static Map getNadCustomBranchLabels(int rowC return nadCustomBranchLabels; } - private static Map getNadCustomThreeWtLabels(UpdatingDataframe threeWtLabelsDataframe) { + private static Map getNadCustomThreeWtLabels(UpdatingDataframe threeWtLabelsDataframe) { int rowCount = threeWtLabelsDataframe.getRowCount(); StringSeries idS = threeWtLabelsDataframe.getStrings("id"); StringSeries side1S = threeWtLabelsDataframe.getStrings("side1"); @@ -1248,10 +1251,10 @@ private static Map getNadCustomThreeWtLabels(Updati StringSeries arrow2S = threeWtLabelsDataframe.getStrings("arrow2"); StringSeries arrow3S = threeWtLabelsDataframe.getStrings("arrow3"); - Map nadCustomThreeWtLabels = new HashMap<>(); + Map nadCustomThreeWtLabels = new HashMap<>(); for (int i = 0; i < rowCount; i++) { String id = idS.get(i); - CustomThreeWtLabels labels = new CustomThreeWtLabels( + CustomLabelProvider.ThreeWtLabels labels = new CustomLabelProvider.ThreeWtLabels( getValueFromSeriesOrNull(side1S, i), getValueFromSeriesOrNull(side2S, i), getValueFromSeriesOrNull(side3S, i), @@ -1296,78 +1299,13 @@ public static VlInfo getNadCustomVlInfos(int rowCount, StringSeries ids, return new VlInfo(headers, footers); } - private static EdgeInfo getThreeWtEdgeInfo(CustomThreeWtLabels threeWtLabels, ThreeWtEdge.Side edgeSide) { - String labelSide = null; - EdgeInfo.Direction arrowDirection = null; - if (threeWtLabels != null) { - switch (edgeSide) { - case ONE -> { - labelSide = threeWtLabels.side1; - arrowDirection = threeWtLabels.arrow1; - } - case TWO -> { - labelSide = threeWtLabels.side2; - arrowDirection = threeWtLabels.arrow2; - } - case THREE -> { - labelSide = threeWtLabels.side3; - arrowDirection = threeWtLabels.arrow3; - } - } - } - return new EdgeInfo("ActivePower", arrowDirection, null, labelSide); - } - - private static LabelProviderFactory getCustomLabelProviderFactory(Map branchLabels, Map threeWtLabels, - Map busDescriptions, Map> vlDescriptions, Map> vlDetails) { - return (network, svgParameters) -> new DefaultLabelProvider(network, svgParameters) { - @Override - public Optional getEdgeInfo(Graph graph, BranchEdge edge, BranchEdge.Side side) { - CustomBranchLabels bl = branchLabels.get(edge.getEquipmentId()); - String label = null; - EdgeInfo.Direction arrowDirection = null; - if (bl != null) { - label = side == BranchEdge.Side.ONE ? bl.side1 : bl.side2; - arrowDirection = side == BranchEdge.Side.ONE ? bl.arrow1 : bl.arrow2; - } - return Optional.of(new EdgeInfo("ActivePower", arrowDirection, null, label)); - } - - @Override - public Optional getEdgeInfo(Graph graph, ThreeWtEdge edge) { - return Optional.of(getThreeWtEdgeInfo(threeWtLabels.get(edge.getEquipmentId()), edge.getSide())); - } - - @Override - public String getLabel(Edge edge) { - CustomBranchLabels bl = branchLabels.get(edge.getEquipmentId()); - return (bl != null) ? bl.middle : null; - } - - @Override - public String getBusDescription(BusNode busNode) { - return busDescriptions.get(busNode.getEquipmentId()); - } - - @Override - public List getVoltageLevelDescription(VoltageLevelNode voltageLevelNode) { - return vlDescriptions.getOrDefault(voltageLevelNode.getEquipmentId(), Collections.emptyList()); - } - - @Override - public List getVoltageLevelDetails(VoltageLevelNode vlNode) { - return vlDetails.getOrDefault(vlNode.getEquipmentId(), Collections.emptyList()); - } - }; - } - private static void applyCustomLabels(DataframePointer customLabels, DataframePointer threeWtLabels, DataframePointer busDescriptions, DataframePointer vlDescriptions, NadParameters nadParameters) { UpdatingDataframe customLabelsDataframe = createDataframe(customLabels); UpdatingDataframe threeWtLabelsDataframe = createDataframe(threeWtLabels); UpdatingDataframe busDescriptionsDataframe = createDataframe(busDescriptions); UpdatingDataframe customVlDescriptionsDataframe = createDataframe(vlDescriptions); if (customLabelsDataframe != null || threeWtLabelsDataframe != null || busDescriptionsDataframe != null || customVlDescriptionsDataframe != null) { - Map branchLabels = Collections.emptyMap(); + final Map branchLabels; if (customLabelsDataframe != null) { //when the custom dataframe is defined, the displaying of the edge name is forced nadParameters.getSvgParameters().setEdgeNameDisplayed(true); @@ -1375,21 +1313,25 @@ private static void applyCustomLabels(DataframePointer customLabels, DataframePo customLabelsDataframe.getStrings("side1"), customLabelsDataframe.getStrings("middle"), customLabelsDataframe.getStrings("side2"), customLabelsDataframe.getStrings("arrow1"), customLabelsDataframe.getStrings("arrow2")); + } else { + branchLabels = Collections.emptyMap(); } - Map customThreeWtLabels = (threeWtLabelsDataframe != null) ? getNadCustomThreeWtLabels(threeWtLabelsDataframe) : Collections.emptyMap(); + Map customThreeWtLabels = (threeWtLabelsDataframe != null) ? getNadCustomThreeWtLabels(threeWtLabelsDataframe) : Collections.emptyMap(); - Map customBusDescriptions = Collections.emptyMap(); + final Map customBusDescriptions; if (busDescriptionsDataframe != null) { //when the custom dataframe is defined, the displaying of the bus legend section is forced nadParameters.getSvgParameters().setBusLegend(true); customBusDescriptions = getNadCustomBusDescriptions(busDescriptionsDataframe.getRowCount(), busDescriptionsDataframe.getStrings("id"), busDescriptionsDataframe.getStrings("description")); + } else { + customBusDescriptions = Collections.emptyMap(); } - Map> customVlDescriptions = Collections.emptyMap(); - Map> customVlDetails = Collections.emptyMap(); + final Map> customVlDescriptions; + final Map> customVlDetails; if (customVlDescriptionsDataframe != null) { //when the custom dataframe is defined, the displaying of the vl details section is forced nadParameters.getSvgParameters().setVoltageLevelDetails(true); @@ -1399,24 +1341,134 @@ private static void applyCustomLabels(DataframePointer customLabels, DataframePo customVlDescriptionsDataframe.getStrings("description")); customVlDescriptions = vlInfo.headers(); customVlDetails = vlInfo.footers(); + } else { + customVlDescriptions = Collections.emptyMap(); + customVlDetails = Collections.emptyMap(); + } + nadParameters.setLabelProviderFactory((network, svgParameters) -> + new CustomLabelProvider(branchLabels, customThreeWtLabels, customBusDescriptions, customVlDescriptions, customVlDetails)); + } + } + + private static Map getNadCustomBusStyles(int rowCount, StringSeries idS, StringSeries fillS, StringSeries edgeS, StringSeries edgeWidthS) { + Map customBusNodeStyles = new HashMap<>(); + for (int i = 0; i < rowCount; i++) { + String id = idS.get(i); + CustomStyleProvider.BusNodeStyles busStyles = new CustomStyleProvider.BusNodeStyles( + getNonEmptyValueFromSeries(fillS, i), + getNonEmptyValueFromSeries(edgeS, i), + getNonEmptyValueFromSeries(edgeWidthS, i) + ); + customBusNodeStyles.put(id, busStyles); + } + return customBusNodeStyles; + } + + private static Map getNadCustomEdgeStyles(UpdatingDataframe customEdgeStylesDataframe) { + int rowCount = customEdgeStylesDataframe.getRowCount(); + StringSeries idS = customEdgeStylesDataframe.getStrings("id"); + StringSeries edge1S = customEdgeStylesDataframe.getStrings("edge1"); + StringSeries width1S = customEdgeStylesDataframe.getStrings("width1"); + StringSeries dash1S = customEdgeStylesDataframe.getStrings("dash1"); + StringSeries edge2S = customEdgeStylesDataframe.getStrings("edge2"); + StringSeries width2S = customEdgeStylesDataframe.getStrings("width2"); + StringSeries dash2S = customEdgeStylesDataframe.getStrings("dash2"); + + Map customEdgeStyles = new HashMap<>(); + for (int i = 0; i < rowCount; i++) { + String id = idS.get(i); + CustomStyleProvider.EdgeStyles busStyles = new CustomStyleProvider.EdgeStyles( + getNonEmptyValueFromSeries(edge1S, i), + getNonEmptyValueFromSeries(width1S, i), + getNonEmptyValueFromSeries(dash1S, i), + getNonEmptyValueFromSeries(edge2S, i), + getNonEmptyValueFromSeries(width2S, i), + getNonEmptyValueFromSeries(dash2S, i) + ); + customEdgeStyles.put(id, busStyles); + } + return customEdgeStyles; + } + + private static Map getNadCustomThreeWtStyles(UpdatingDataframe customThreeWtStylesDataframe) { + StringSeries edge3S = customThreeWtStylesDataframe.getStrings("edge3"); + StringSeries width3S = customThreeWtStylesDataframe.getStrings("width3"); + StringSeries dash3S = customThreeWtStylesDataframe.getStrings("dash3"); + StringSeries edge2S = customThreeWtStylesDataframe.getStrings("edge2"); + StringSeries width2S = customThreeWtStylesDataframe.getStrings("width2"); + StringSeries dash2S = customThreeWtStylesDataframe.getStrings("dash2"); + StringSeries edge1S = customThreeWtStylesDataframe.getStrings("edge1"); + StringSeries width1S = customThreeWtStylesDataframe.getStrings("width1"); + StringSeries dash1S = customThreeWtStylesDataframe.getStrings("dash1"); + StringSeries idS = customThreeWtStylesDataframe.getStrings("id"); + int rowCount = customThreeWtStylesDataframe.getRowCount(); + + Map customThreeWtStyles = new HashMap<>(); + for (int i = 0; i < rowCount; i++) { + String id = idS.get(i); + CustomStyleProvider.ThreeWtStyles threeWtStyles = new CustomStyleProvider.ThreeWtStyles( + getNonEmptyValueFromSeries(edge1S, i), + getNonEmptyValueFromSeries(width1S, i), + getNonEmptyValueFromSeries(dash1S, i), + getNonEmptyValueFromSeries(edge2S, i), + getNonEmptyValueFromSeries(width2S, i), + getNonEmptyValueFromSeries(dash2S, i), + getNonEmptyValueFromSeries(edge3S, i), + getNonEmptyValueFromSeries(width3S, i), + getNonEmptyValueFromSeries(dash3S, i) + ); + customThreeWtStyles.put(id, threeWtStyles); + } + return customThreeWtStyles; + } + + private static void applyCustomStyles(DataframePointer busNodeStyles, DataframePointer edgeStyles, DataframePointer threeWtStyles, NadParameters nadParameters) { + UpdatingDataframe customBusNodeStylesDataframe = createDataframe(busNodeStyles); + UpdatingDataframe customEdgeStylesDataframe = createDataframe(edgeStyles); + UpdatingDataframe customThreeWtStylesDataframe = createDataframe(threeWtStyles); + if (customBusNodeStylesDataframe != null || customEdgeStylesDataframe != null || customThreeWtStylesDataframe != null) { + final Map busStyles; + if (customBusNodeStylesDataframe != null) { + busStyles = getNadCustomBusStyles(customBusNodeStylesDataframe.getRowCount(), customBusNodeStylesDataframe.getStrings("id"), + customBusNodeStylesDataframe.getStrings("fill"), + customBusNodeStylesDataframe.getStrings("edge"), + customBusNodeStylesDataframe.getStrings("edge-width")); + } else { + busStyles = Collections.emptyMap(); } - nadParameters.setLabelProviderFactory(getCustomLabelProviderFactory(branchLabels, customThreeWtLabels, customBusDescriptions, customVlDescriptions, customVlDetails)); + final Map edgesStyles; + if (customEdgeStylesDataframe != null) { + edgesStyles = getNadCustomEdgeStyles(customEdgeStylesDataframe); + } else { + edgesStyles = Collections.emptyMap(); + } + + Map threeWtsStyles; + if (customThreeWtStylesDataframe != null) { + threeWtsStyles = getNadCustomThreeWtStyles(customThreeWtStylesDataframe); + } else { + threeWtsStyles = Collections.emptyMap(); + } + + nadParameters.setStyleProviderFactory(network -> new CustomStyleProvider(busStyles, edgesStyles, threeWtsStyles)); } } @CEntryPoint(name = "getNetworkAreaDiagramSvgAndMetadata") public static ArrayPointer getNetworkAreaDiagramSvgAndMetadata(IsolateThread thread, ObjectHandle networkHandle, CCharPointerPointer voltageLevelIdsPointer, - int voltageLevelIdCount, int depth, double highNominalVoltageBound, - double lowNominalVoltageBound, NadParametersPointer nadParametersPointer, - DataframePointer fixedPositions, DataframePointer branchLabels, DataframePointer threeWtLabels, DataframePointer busDescriptions, - DataframePointer vlDescriptions, ExceptionHandlerPointer exceptionHandlerPtr) { + int voltageLevelIdCount, int depth, double highNominalVoltageBound, + double lowNominalVoltageBound, NadParametersPointer nadParametersPointer, + DataframePointer fixedPositions, DataframePointer branchLabels, DataframePointer threeWtLabels, DataframePointer busDescriptions, + DataframePointer vlDescriptions, DataframePointer busNodeStyles, DataframePointer edgeStyles, + DataframePointer threeWtStyles, ExceptionHandlerPointer exceptionHandlerPtr) { return doCatch(exceptionHandlerPtr, () -> { Network network = ObjectHandles.getGlobal().get(networkHandle); List voltageLevelIds = toStringList(voltageLevelIdsPointer, voltageLevelIdCount); NadParameters nadParameters = convertNadParameters(nadParametersPointer, network); applyFixedPositions(fixedPositions, nadParameters); applyCustomLabels(branchLabels, threeWtLabels, busDescriptions, vlDescriptions, nadParameters); + applyCustomStyles(busNodeStyles, edgeStyles, threeWtStyles, nadParameters); List svgAndMeta = NetworkAreaDiagramUtil.getSvgAndMetadata(network, voltageLevelIds, depth, highNominalVoltageBound, lowNominalVoltageBound, nadParameters); return createCharPtrArray(svgAndMeta); }); diff --git a/java/pypowsybl/src/main/resources/META-INF/native-image/com.powsybl/powsybl-network-area-diagram/resource-config.json b/java/pypowsybl/src/main/resources/META-INF/native-image/com.powsybl/powsybl-network-area-diagram/resource-config.json index b0ed4b620a..95c008c2b9 100644 --- a/java/pypowsybl/src/main/resources/META-INF/native-image/com.powsybl/powsybl-network-area-diagram/resource-config.json +++ b/java/pypowsybl/src/main/resources/META-INF/native-image/com.powsybl/powsybl-network-area-diagram/resource-config.json @@ -1,6 +1,7 @@ { "resources":{ "includes":[ + {"pattern":"\\QcustomStyle.css\\E"}, {"pattern":"\\QnominalStyle.css\\E"}, {"pattern":"\\QtopologicalStyle.css\\E"} ]} diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index ccd2394cbf..70a8b20cc4 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -836,7 +836,7 @@ def get_security_analysis_provider_parameters_names(provider: str) -> List[str]: def get_sensitivity_analysis_provider_parameters_names(provider: str) -> List[str]: ... def get_limit_violations(result: JavaHandle) -> SeriesArray: ... def get_network_area_diagram_svg(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters) -> str: ... -def get_network_area_diagram_svg_and_metadata(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe], three_wt_labels: Optional[Dataframe], bus_descriptions: Optional[Dataframe], vl_descriptions: Optional[Dataframe]) -> List[str]: ... +def get_network_area_diagram_svg_and_metadata(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe], three_wt_labels: Optional[Dataframe], bus_descriptions: Optional[Dataframe], vl_descriptions: Optional[Dataframe], bus_node_styles: Optional[Dataframe], edge_styles: Optional[Dataframe], three_wt_styles: Optional[Dataframe]) -> List[str]: ... def get_network_area_diagram_displayed_voltage_levels(network: JavaHandle, voltage_level_ids: Union[str, List[str]], depth: int) -> List[str]: ... def get_network_elements_ids(network: JavaHandle, element_type: ElementType, nominal_voltages: List[float], countries: List[str], main_connected_component: bool, main_synchronous_component: bool, not_connected_to_same_bus_at_both_sides: bool) -> List[str]: ... def get_network_export_formats() -> List[str]: ... @@ -897,7 +897,7 @@ def update_connectable_status(arg0: JavaHandle, arg1: str, arg2: bool) -> bool: def update_network_elements_with_series(network: JavaHandle, array: Dataframe, element_type: ElementType, per_unit: bool, nominal_apparent_power: float) -> None: ... def update_switch_position(arg0: JavaHandle, arg1: str, arg2: bool) -> bool: ... def validate(network: JavaHandle) -> ValidationLevel: ... -def write_network_area_diagram_svg(network: JavaHandle, svg_file: str, metadata_file: str, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe], three_wt_labels: Optional[Dataframe], bus_descriptions: Optional[Dataframe], vl_descriptions: Optional[Dataframe]) -> None: ... +def write_network_area_diagram_svg(network: JavaHandle, svg_file: str, metadata_file: str, voltage_level_ids: Union[str, List[str]], depth: int, high_nominal_voltage_bound: float, low_nominal_voltage_bound: float, nad_parameters: NadParameters, fixed_positions: Optional[Dataframe], branch_labels: Optional[Dataframe], three_wt_labels: Optional[Dataframe], bus_descriptions: Optional[Dataframe], vl_descriptions: Optional[Dataframe], bus_node_styles: Optional[Dataframe], edge_styles: Optional[Dataframe], three_wt_styles: Optional[Dataframe]) -> None: ... def write_single_line_diagram_svg(network: JavaHandle, container_id: str, svg_file: str, metadata_file: str, parameters: SldParameters) -> None: ... def write_matrix_multi_substation_single_line_diagram_svg(network: JavaHandle, matrix_ids: List[List[str]], svg_file: str, metadata_file: str, parameters: SldParameters) -> None: ... def add_network_element_properties(network: JavaHandle, dataframe: Dataframe) -> None: ... diff --git a/pypowsybl/network/impl/nad_profile.py b/pypowsybl/network/impl/nad_profile.py index 13a4fab998..8228574874 100644 --- a/pypowsybl/network/impl/nad_profile.py +++ b/pypowsybl/network/impl/nad_profile.py @@ -37,12 +37,42 @@ class NadProfile: _nad_bus_descriptions_metadata=[_pp.SeriesMetadata('id',0,True,False,False), _pp.SeriesMetadata('description',0,False,False,False)] + _nad_bus_node_styles_metadata=[_pp.SeriesMetadata('id',0,True,False,False), + _pp.SeriesMetadata('fill',0,False,False,False), + _pp.SeriesMetadata('edge',0,False,False,False), + _pp.SeriesMetadata('edge-width',0,False,False,False)] + + _nad_edge_styles_metadata=[_pp.SeriesMetadata('id',0,True,False,False), + _pp.SeriesMetadata('edge1',0,False,False,False), + _pp.SeriesMetadata('width1',0,False,False,False), + _pp.SeriesMetadata('dash1',0,False,False,False), + _pp.SeriesMetadata('edge2',0,False,False,False), + _pp.SeriesMetadata('width2',0,False,False,False), + _pp.SeriesMetadata('dash2',0,False,False,False)] + + _nad_three_wt_styles_metadata=[_pp.SeriesMetadata('id',0,True,False,False), + _pp.SeriesMetadata('edge1',0,False,False,False), + _pp.SeriesMetadata('width1',0,False,False,False), + _pp.SeriesMetadata('dash1',0,False,False,False), + _pp.SeriesMetadata('edge2',0,False,False,False), + _pp.SeriesMetadata('width2',0,False,False,False), + _pp.SeriesMetadata('dash2',0,False,False,False), + _pp.SeriesMetadata('edge3',0,False,False,False), + _pp.SeriesMetadata('width3',0,False,False,False), + _pp.SeriesMetadata('dash3',0,False,False,False)] + + def __init__(self, branch_labels: Optional[DataFrame] = None, three_wt_labels: Optional[DataFrame] = None, - bus_descriptions: Optional[DataFrame] = None, vl_descriptions: Optional[DataFrame] = None): + bus_descriptions: Optional[DataFrame] = None, vl_descriptions: Optional[DataFrame] = None, + bus_node_styles: Optional[DataFrame] = None, edge_styles: Optional[DataFrame] = None, + three_wt_styles: Optional[DataFrame] = None): self._branch_labels = branch_labels self._three_wt_labels = three_wt_labels self._bus_descriptions = bus_descriptions self._vl_descriptions = vl_descriptions + self._bus_node_styles = bus_node_styles + self._edge_styles = edge_styles + self._three_wt_styles = three_wt_styles @property def branch_labels(self) -> Optional[DataFrame]: @@ -64,6 +94,21 @@ def vl_descriptions(self) -> Optional[DataFrame]: """vl_descriptions""" return self._vl_descriptions + @property + def bus_node_styles(self) -> Optional[DataFrame]: + """bus_node_styles""" + return self._bus_node_styles + + @property + def edge_styles(self) -> Optional[DataFrame]: + """edge_styles""" + return self._edge_styles + + @property + def three_wt_styles(self) -> Optional[DataFrame]: + """three_wt_styles""" + return self._three_wt_styles + def _create_nad_branch_labels_c_dataframe(self) -> Optional[_pp.Dataframe]: return None if self._branch_labels is None else _create_c_dataframe(self._branch_labels.fillna(''), NadProfile._nad_branch_labels_metadata) @@ -79,3 +124,14 @@ def _create_nad_bus_descriptions_c_dataframe(self) -> Optional[_pp.Dataframe]: def _create_nad_vl_descriptions_c_dataframe(self) -> Optional[_pp.Dataframe]: return None if self._vl_descriptions is None else _create_c_dataframe(self._vl_descriptions.fillna(''), NadProfile._nad_descriptions_metadata) + + def _create_nad_bus_node_styles_c_dataframe(self) -> Optional[_pp.Dataframe]: + return None if self._bus_node_styles is None else _create_c_dataframe(self._bus_node_styles.fillna(''), + NadProfile._nad_bus_node_styles_metadata) + + def _create_nad_edge_styles_c_dataframe(self) -> Optional[_pp.Dataframe]: + return None if self._edge_styles is None else _create_c_dataframe(self._edge_styles.fillna(''), + NadProfile._nad_edge_styles_metadata) + def _create_nad_three_wt_styles_c_dataframe(self) -> Optional[_pp.Dataframe]: + return None if self._three_wt_styles is None else _create_c_dataframe(self._three_wt_styles.fillna(''), + NadProfile._nad_three_wt_styles_metadata) diff --git a/pypowsybl/network/impl/network.py b/pypowsybl/network/impl/network.py index b0388f2731..6ae46227d6 100644 --- a/pypowsybl/network/impl/network.py +++ b/pypowsybl/network/impl/network.py @@ -398,8 +398,10 @@ def write_network_area_diagram(self, svg_file: PathOrStr, voltage_level_ids: Uni None if nad_profile is None else nad_profile._create_nad_branch_labels_c_dataframe(), # pylint: disable=protected-access None if nad_profile is None else nad_profile._create_nad_three_wt_labels_c_dataframe(), # pylint: disable=protected-access None if nad_profile is None else nad_profile._create_nad_bus_descriptions_c_dataframe(), # pylint: disable=protected-access - None if nad_profile is None else nad_profile._create_nad_vl_descriptions_c_dataframe()) # pylint: disable=protected-access - + None if nad_profile is None else nad_profile._create_nad_vl_descriptions_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_bus_node_styles_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_edge_styles_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_three_wt_styles_c_dataframe()) # pylint: disable=protected-access def _create_nad_positions_c_dataframe(self, df: DataFrame) -> _pp.Dataframe: nad_positions_metadata=[_pp.SeriesMetadata('id',0,True,False,False), @@ -441,7 +443,10 @@ def get_network_area_diagram(self, voltage_level_ids: Union[str, List[str]] = No None if nad_profile is None else nad_profile._create_nad_branch_labels_c_dataframe(), # pylint: disable=protected-access None if nad_profile is None else nad_profile._create_nad_three_wt_labels_c_dataframe(), # pylint: disable=protected-access None if nad_profile is None else nad_profile._create_nad_bus_descriptions_c_dataframe(), # pylint: disable=protected-access - None if nad_profile is None else nad_profile._create_nad_vl_descriptions_c_dataframe()) # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_vl_descriptions_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_bus_node_styles_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_edge_styles_c_dataframe(), # pylint: disable=protected-access + None if nad_profile is None else nad_profile._create_nad_three_wt_styles_c_dataframe()) # pylint: disable=protected-access return Svg(svg_and_metadata[0], svg_and_metadata[1]) From 84e46abb9d55c30d3940630ae9461a18cca1c5d5 Mon Sep 17 00:00:00 2001 From: Naledi <151443525+nao1345678@users.noreply.github.com> Date: Mon, 7 Apr 2025 10:28:04 +0200 Subject: [PATCH 60/71] Add JSON file content to ContingencyContainer (#959) Signed-off-by: Naledi EL CHEIKH --- .../python/security/SecurityAnalysisCFunctions.java | 13 +++++++++++++ pypowsybl/_pypowsybl.pyi | 2 +- tests/test_security_analysis.py | 12 ++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index 4016dff177..96c7b3d751 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -38,6 +38,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Objects; import java.util.Set; @@ -136,6 +138,17 @@ public static void addContingency(IsolateThread thread, ObjectHandle contingency }); } + @CEntryPoint(name = "addContingencyFromJsonFile") + public static void addContingencyFromJsonFile(IsolateThread thread, ObjectHandle contingencyContainerHandle, CCharPointer jsonFilePath, + PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { + doCatch(exceptionHandlerPtr, () -> { + ContingencyContainer contingencyContainer = ObjectHandles.getGlobal().get(contingencyContainerHandle); + String stringPath = CTypeUtil.toString(jsonFilePath); + Path path = Paths.get(stringPath); + contingencyContainer.addContingencyFromJsonFile(path); + }); + } + private static void setPostContingencyResultInSecurityAnalysisResultPointer(PyPowsyblApiHeader.PostContingencyResultPointer contingencyPtr, PostContingencyResult postContingencyResult) { contingencyPtr.setContingencyId(CTypeUtil.toCharPtr(postContingencyResult.getContingency().getId())); contingencyPtr.setStatus(postContingencyResult.getStatus().ordinal()); diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 70a8b20cc4..7a91d4f66e 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -2,7 +2,6 @@ from logging import Logger from typing import ClassVar, Dict, Iterator, List, Sequence, Optional, Union from numpy import ndarray - class ArrayStruct: def __init__(self) -> None: ... @@ -1204,3 +1203,4 @@ def update_grid2op_integer_value(backend: JavaHandle, value_type: Grid2opUpdateI def check_grid2op_isolated_and_disconnected_injections(backend: JavaHandle) -> bool: ... def run_grid2op_loadflow(backend: JavaHandle, dc: bool, parameters: LoadFlowParameters) -> LoadFlowComponentResultArray: ... + diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index 93b44d9033..f67f7da1d9 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -11,6 +11,10 @@ import pypowsybl.report as rp from pypowsybl._pypowsybl import ConditionType import re +import pathlib + +TEST_DIR = pathlib.Path(__file__).parent +DATA_DIR = TEST_DIR.parent.joinpath('data') @pytest.fixture(autouse=True) @@ -408,6 +412,14 @@ def test_tie_line_contingency(): sa_result = sa.run_ac(n) assert 'tie line contingency' in sa_result.post_contingency_results.keys() +def test_add_contingencies_from_json_file(): + n = pp.network.create_eurostag_tutorial_example1_network() + sa = pp.security.create_analysis() + sa.add_contingencies_from_json_file(str(DATA_DIR.joinpath('contingencies.json'))) + sa_result = sa.run_ac(n) + assert 'contingency' in sa_result.post_contingency_results.keys() + assert 'contingency2' in sa_result.post_contingency_results.keys() + def test_terminal_connection_action(): n = pp.network.create_eurostag_tutorial_example1_network() sa = pp.security.create_analysis() From 0019d17e23e5d7d2668f4353b12d54fa9782b0f3 Mon Sep 17 00:00:00 2001 From: Hugo KULESZA Date: Mon, 7 Apr 2025 12:37:58 +0200 Subject: [PATCH 61/71] Bump to v1.11.0 Signed-off-by: Hugo KULESZA Signed-off-by: Naledi EL CHEIKH --- java/pom.xml | 2 +- java/pypowsybl/pom.xml | 4 ++-- pypowsybl/__init__.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index f2e0afb454..a391466e95 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -22,7 +22,7 @@ pypowsybl-parent pom - 1.11.0-SNAPSHOT + 1.11.0 PyPowSyBl Java part parent project https://www.powsybl.org diff --git a/java/pypowsybl/pom.xml b/java/pypowsybl/pom.xml index 37ad9d8790..bab57f0a90 100644 --- a/java/pypowsybl/pom.xml +++ b/java/pypowsybl/pom.xml @@ -15,14 +15,14 @@ com.powsybl pypowsybl-parent - 1.11.0-SNAPSHOT + 1.11.0 pypowsybl A C interface to powsybl, for pypowsybl implementation jar - 1.11.0-SNAPSHOT + 1.11.0 4.4 diff --git a/pypowsybl/__init__.py b/pypowsybl/__init__.py index 988805b0b2..97d4fbaad5 100644 --- a/pypowsybl/__init__.py +++ b/pypowsybl/__init__.py @@ -23,7 +23,7 @@ ) from pypowsybl.network import per_unit_view -__version__ = '1.11.0.dev1' +__version__ = '1.11.0' # set JVM java.library.path to pypowsybl module installation directory to be able to load native libraries _pypowsybl.set_java_library_path(_os.path.dirname(_inspect.getfile(_pypowsybl))) From 0bf09d8aece840dbdf61e5156fa7fd6b0dcac093 Mon Sep 17 00:00:00 2001 From: Hugo KULESZA Date: Mon, 7 Apr 2025 12:42:16 +0200 Subject: [PATCH 62/71] Bump to v1.12.0.dev1 Signed-off-by: Hugo KULESZA Signed-off-by: Naledi EL CHEIKH --- java/pom.xml | 2 +- java/pypowsybl/pom.xml | 4 ++-- pypowsybl/__init__.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index a391466e95..312cbe7649 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -22,7 +22,7 @@ pypowsybl-parent pom - 1.11.0 + 1.12.0-SNAPSHOT PyPowSyBl Java part parent project https://www.powsybl.org diff --git a/java/pypowsybl/pom.xml b/java/pypowsybl/pom.xml index bab57f0a90..50293cfc6b 100644 --- a/java/pypowsybl/pom.xml +++ b/java/pypowsybl/pom.xml @@ -15,14 +15,14 @@ com.powsybl pypowsybl-parent - 1.11.0 + 1.12.0-SNAPSHOT pypowsybl A C interface to powsybl, for pypowsybl implementation jar - 1.11.0 + 1.12.0-SNAPSHOT 4.4 diff --git a/pypowsybl/__init__.py b/pypowsybl/__init__.py index 97d4fbaad5..babd1cdb47 100644 --- a/pypowsybl/__init__.py +++ b/pypowsybl/__init__.py @@ -23,7 +23,7 @@ ) from pypowsybl.network import per_unit_view -__version__ = '1.11.0' +__version__ = '1.12.0.dev1' # set JVM java.library.path to pypowsybl module installation directory to be able to load native libraries _pypowsybl.set_java_library_path(_os.path.dirname(_inspect.getfile(_pypowsybl))) From 52fadb4cc3da45d947d6137d9ae5900de3e5bb65 Mon Sep 17 00:00:00 2001 From: HugoKulesza <94374655+HugoKulesza@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:31:57 +0200 Subject: [PATCH 63/71] fix upload of wheels (force upgrade packaging) (#981) Signed-off-by: Hugo KULESZA Signed-off-by: Naledi EL CHEIKH --- .github/workflows/full-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/full-ci.yml b/.github/workflows/full-ci.yml index 83c97cd973..13fa580822 100644 --- a/.github/workflows/full-ci.yml +++ b/.github/workflows/full-ci.yml @@ -255,7 +255,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') shell: bash run: | - python -m pip install --user --upgrade twine + python -m pip install --user --upgrade twine packaging python -m twine upload --username __token__ --password ${{ secrets.PYPI_TOKEN }} download/*/*.whl build: From 9bbad751fa6b1a5ef214af7aed6516dbb5f2ff01 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 9 Apr 2025 11:18:09 +0200 Subject: [PATCH 64/71] fix syntax error Signed-off-by: Naledi EL CHEIKH --- .../python/security/SecurityAnalysisCFunctions.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index 96c7b3d751..8ced7c9c0b 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -460,6 +460,17 @@ public static void addShuntCompensatorPositionAction(IsolateThread thread, Objec }); } + @CEntryPoint(name = "addActionFromJsonFile") + public static void addActionFromJsonFile(IsolateThread thread, ObjectHandle securityAnalysisContextHandle, + CCharPointer jsonFilePath, PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { + doCatch(exceptionHandlerPtr, () -> { + SecurityAnalysisContext analysisContext = ObjectHandles.getGlobal().get(securityAnalysisContextHandle); + String stringPath = CTypeUtil.toString(jsonFilePath); + Path path = Paths.get(stringPath); + analysisContext.addActionFromJsonFile(path); + }); + } + @CEntryPoint(name = "addTerminalsConnectionAction") public static void addTerminalsConnectionAction(IsolateThread thread, ObjectHandle securityAnalysisContextHandle, CCharPointer actionId, CCharPointer elementId, PyPowsyblApiHeader.ThreeSideType side, From 6a50f50e3d841dbf4b52541714da9e7cba3b029f Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 10 Apr 2025 10:37:11 +0200 Subject: [PATCH 65/71] changed test files and python test Signed-off-by: Naledi EL CHEIKH --- data/OperatorStrategyFileTestV1.0.json | 17 +++----------- docs/user_guide/security.rst | 31 +++++++------------------- tests/test_security_analysis.py | 9 ++++++++ 3 files changed, 20 insertions(+), 37 deletions(-) diff --git a/data/OperatorStrategyFileTestV1.0.json b/data/OperatorStrategyFileTestV1.0.json index 4059a43d28..38edc7071a 100644 --- a/data/OperatorStrategyFileTestV1.0.json +++ b/data/OperatorStrategyFileTestV1.0.json @@ -3,24 +3,13 @@ "operatorStrategies" : [ { "id" : "id1", "contingencyContextType" : "SPECIFIC", - "contingencyId" : "BE-G2_contingency", + "contingencyId" : "contingency", "conditionalActions" : [ { "id" : "stage1", "condition" : { "type" : "TRUE_CONDITION" }, - "actionIds" : [ "S1VL2_LCC1_BREAKER", "S1VL2_BBS2_COUPLER_DISCONNECTOR" ] + "actionIds" : [ "id1", "id2" ] } ] - }, { - "id" : "id2", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "Line contingency", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ANY_VIOLATION_CONDITION" - }, - "actionIds" : [ "S4VL1_BBS_LINES3S4_DISCONNECTOR" ] - } ] - } ] + }] } \ No newline at end of file diff --git a/docs/user_guide/security.rst b/docs/user_guide/security.rst index 08c7196be8..959b94a3ac 100644 --- a/docs/user_guide/security.rst +++ b/docs/user_guide/security.rst @@ -186,17 +186,13 @@ The following example is a valid JSON file input for this method : "actions" : [ { "type" : "SWITCH", "id" : "id1", - "switchId" : "switchId1", + "switchId" : "S1VL2_LCC1_BREAKER", "open" : true }, { - "type" : "MULTIPLE_ACTIONS", + "type" : "SWITCH", "id" : "id2", - "actions" : [ { - "type" : "SWITCH", - "id" : "id3", - "switchId" : "switchId2", - "open" : true - } ] + "switchId" : "S1VL2_BBS2_COUPLER_DISCONNECTOR", + "open" : true }] } @@ -211,24 +207,13 @@ The following example is a valid JSON file input for this method : "operatorStrategies" : [ { "id" : "id1", "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId1", + "contingencyId" : "contingency", "conditionalActions" : [ { "id" : "stage1", "condition" : { "type" : "TRUE_CONDITION" }, - "actionIds" : [ "actionId1", "actionId2", "actionId3" ] - } ] - }, { - "id" : "id2", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId2", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ANY_VIOLATION_CONDITION" - }, - "actionIds" : [ "actionId4" ] + "actionIds" : [ "id1", "id2" ] } ] - } ] - } \ No newline at end of file + }] + } diff --git a/tests/test_security_analysis.py b/tests/test_security_analysis.py index f67f7da1d9..58b6b87a5a 100644 --- a/tests/test_security_analysis.py +++ b/tests/test_security_analysis.py @@ -420,6 +420,15 @@ def test_add_contingencies_from_json_file(): assert 'contingency' in sa_result.post_contingency_results.keys() assert 'contingency2' in sa_result.post_contingency_results.keys() +def test_add_operator_strategies_and_actions_from_json_file(): + n = pp.network.create_four_substations_node_breaker_network() + sa = pp.security.create_analysis() + sa.add_single_element_contingency('LINE_S2S3', 'contingency') + sa.add_actions_from_json_file(str(DATA_DIR.joinpath('ActionFileTestV1.0.json'))) + sa.add_operator_strategies_from_json_file(str(DATA_DIR.joinpath('OperatorStrategyFileTestV1.0.json'))) + sa_result = sa.run_dc(n) + assert 'id1' in sa_result.operator_strategy_results.keys() + def test_terminal_connection_action(): n = pp.network.create_eurostag_tutorial_example1_network() sa = pp.security.create_analysis() From 28e9ee9a915ea618198063dc23868d77320c7599 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 10 Apr 2025 11:37:52 +0200 Subject: [PATCH 66/71] deleted tests for c functions Signed-off-by: Naledi EL CHEIKH --- .../python/security/SecurityAnalysisTest.java | 13 +- .../test/resources/ActionFileTestV1.0.json | 187 +----------------- .../OperatorStrategyFileTestV1.0.json | 66 +------ 3 files changed, 8 insertions(+), 258 deletions(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index f5a20d057a..2187701dcd 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -32,6 +32,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; /** * @author Etienne Lesot {@literal } @@ -87,16 +88,4 @@ void testSecurityAnalysis() { Assertions.assertThat(series.get(1).getStrings()) .containsExactly("NHV1_NHV2_2", "VLHV1"); } - - @Test - void testToAddActionsAndOperatorStrategiesFromJsonFile() throws IOException { - SecurityAnalysisContext analysisContext = new SecurityAnalysisContext(); - fileSystem = Jimfs.newFileSystem(Configuration.unix()); - - Files.copy(getClass().getResourceAsStream("/ActionFileTestV1.0.json"), fileSystem.getPath("/ActionFileTestV1.0.json")); - analysisContext.addActionFromJsonFile(fileSystem.getPath("/ActionFileTestV1.0.json")); - - Files.copy(getClass().getResourceAsStream("/OperatorStrategyFileTestV1.0.json"), fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); - analysisContext.addOperatorStrategyFromJsonFile(fileSystem.getPath("/OperatorStrategyFileTestV1.0.json")); - } } diff --git a/java/pypowsybl/src/test/resources/ActionFileTestV1.0.json b/java/pypowsybl/src/test/resources/ActionFileTestV1.0.json index 856876582a..eb133aaab5 100644 --- a/java/pypowsybl/src/test/resources/ActionFileTestV1.0.json +++ b/java/pypowsybl/src/test/resources/ActionFileTestV1.0.json @@ -3,191 +3,12 @@ "actions" : [ { "type" : "SWITCH", "id" : "id1", - "switchId" : "switchId1", + "switchId" : "S1VL1_LD1_BREAKER", "open" : true }, { - "type" : "MULTIPLE_ACTIONS", + "type" : "SWITCH", "id" : "id2", - "actions" : [ { - "type" : "SWITCH", - "id" : "id3", - "switchId" : "switchId2", - "open" : true - } ] - }, { - "type" : "TERMINALS_CONNECTION", - "id" : "id3", - "elementId" : "lineId3", - "open" : true - }, { - "type" : "TERMINALS_CONNECTION", - "id" : "id4", - "elementId" : "lineId4", - "open" : false - }, { - "type" : "PHASE_TAP_CHANGER_TAP_POSITION", - "id" : "id5", - "transformerId" : "transformerId1", - "value" : 5, - "relativeValue" : true, - "side" : "TWO" - }, { - "type" : "PHASE_TAP_CHANGER_TAP_POSITION", - "id" : "id6", - "transformerId" : "transformerId2", - "value" : 12, - "relativeValue" : false - }, { - "type" : "PHASE_TAP_CHANGER_TAP_POSITION", - "id" : "id7", - "transformerId" : "transformerId3", - "value" : -5, - "relativeValue" : true, - "side" : "ONE" - }, { - "type" : "PHASE_TAP_CHANGER_TAP_POSITION", - "id" : "id8", - "transformerId" : "transformerId3", - "value" : 2, - "relativeValue" : false, - "side" : "THREE" - }, { - "type" : "GENERATOR", - "id" : "id9", - "generatorId" : "generatorId1", - "activePowerRelativeValue" : true, - "activePowerValue" : 100.0 - }, { - "type" : "GENERATOR", - "id" : "id10", - "generatorId" : "generatorId2", - "voltageRegulatorOn" : true, - "targetV" : 225.0 - }, { - "type" : "GENERATOR", - "id" : "id11", - "generatorId" : "generatorId2", - "voltageRegulatorOn" : false, - "targetQ" : 400.0 - }, { - "type" : "LOAD", - "id" : "id12", - "loadId" : "loadId1", - "relativeValue" : false, - "activePowerValue" : 50.0 - }, { - "type" : "LOAD", - "id" : "id13", - "loadId" : "loadId1", - "relativeValue" : true, - "reactivePowerValue" : 5.0 - }, { - "type" : "DANGLING_LINE", - "id" : "id17", - "danglingLineId" : "dlId1", - "relativeValue" : true, - "reactivePowerValue" : 5.0 - }, { - "type" : "RATIO_TAP_CHANGER_TAP_POSITION", - "id" : "id14", - "transformerId" : "transformerId4", - "value" : 2, - "relativeValue" : false, - "side" : "THREE" - }, { - "type" : "RATIO_TAP_CHANGER_TAP_POSITION", - "id" : "id15", - "transformerId" : "transformerId5", - "value" : 1, - "relativeValue" : true - }, { - "type" : "RATIO_TAP_CHANGER_REGULATION", - "id" : "id16", - "transformerId" : "transformerId5", - "regulating" : true, - "side" : "THREE" - }, { - "type" : "PHASE_TAP_CHANGER_REGULATION", - "id" : "id17", - "transformerId" : "transformerId5", - "regulating" : true, - "side" : "ONE", - "regulationMode" : "ACTIVE_POWER_CONTROL", - "regulationValue" : 10.0 - }, { - "type" : "PHASE_TAP_CHANGER_REGULATION", - "id" : "id18", - "transformerId" : "transformerId6", - "regulating" : false, - "side" : "ONE" - }, { - "type" : "PHASE_TAP_CHANGER_REGULATION", - "id" : "id19", - "transformerId" : "transformerId6", - "regulating" : true, - "side" : "ONE", - "regulationMode" : "ACTIVE_POWER_CONTROL", - "regulationValue" : 15.0 - }, { - "type" : "RATIO_TAP_CHANGER_REGULATION", - "id" : "id20", - "transformerId" : "transformerId5", - "regulating" : true, - "targetV" : 90.0 - }, { - "type" : "RATIO_TAP_CHANGER_REGULATION", - "id" : "id21", - "transformerId" : "transformerId5", - "regulating" : false, - "side" : "THREE" - }, { - "type" : "HVDC", - "id" : "id22", - "hvdcId" : "hvdc1", - "acEmulationEnabled" : false - }, { - "type" : "HVDC", - "id" : "id23", - "hvdcId" : "hvdc2", - "acEmulationEnabled" : true - }, { - "type" : "HVDC", - "id" : "id24", - "hvdcId" : "hvdc2", - "acEmulationEnabled" : true, - "converterMode" : "SIDE_1_RECTIFIER_SIDE_2_INVERTER", - "droop" : 121.0, - "p0" : 42.0, - "relativeValue" : false - }, { - "type" : "HVDC", - "id" : "id25", - "hvdcId" : "hvdc1", - "acEmulationEnabled" : false, - "activePowerSetpoint" : 12.0, - "relativeValue" : true - }, { - "type" : "SHUNT_COMPENSATOR_POSITION", - "id" : "id22", - "shuntCompensatorId" : "shuntId1", - "sectionCount" : 5 - }, { - "type" : "STATIC_VAR_COMPENSATOR", - "id" : "id23", - "staticVarCompensatorId" : "svc", - "regulationMode" : "VOLTAGE", - "voltageSetpoint" : 56.0 - }, { - "type" : "STATIC_VAR_COMPENSATOR", - "id" : "id24", - "staticVarCompensatorId" : "svc", - "regulationMode" : "REACTIVE_POWER", - "reactivePowerSetpoint" : 120.0 - }, { - "type" : "TERMINALS_CONNECTION", - "id" : "id4", - "elementId" : "transformerId25", - "side" : "THREE", + "switchId" : "S1VL2_BBS2_COUPLER_DISCONNECTOR", "open" : true - } ] + }] } \ No newline at end of file diff --git a/java/pypowsybl/src/test/resources/OperatorStrategyFileTestV1.0.json b/java/pypowsybl/src/test/resources/OperatorStrategyFileTestV1.0.json index 88be6ac884..7e2687d0ba 100644 --- a/java/pypowsybl/src/test/resources/OperatorStrategyFileTestV1.0.json +++ b/java/pypowsybl/src/test/resources/OperatorStrategyFileTestV1.0.json @@ -3,73 +3,13 @@ "operatorStrategies" : [ { "id" : "id1", "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId1", + "contingencyId" : "First contingency", "conditionalActions" : [ { "id" : "stage1", "condition" : { "type" : "TRUE_CONDITION" }, - "actionIds" : [ "actionId1", "actionId2", "actionId3" ] + "actionIds" : [ "id1", "id2" ] } ] - }, { - "id" : "id2", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId2", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ANY_VIOLATION_CONDITION" - }, - "actionIds" : [ "actionId4" ] - } ] - }, { - "id" : "id3", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId1", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ANY_VIOLATION_CONDITION", - "filters" : [ "CURRENT" ] - }, - "actionIds" : [ "actionId1", "actionId3" ] - } ] - }, { - "id" : "id4", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId3", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ANY_VIOLATION_CONDITION", - "filters" : [ "LOW_VOLTAGE" ] - }, - "actionIds" : [ "actionId1", "actionId2", "actionId4" ] - } ] - }, { - "id" : "id5", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId4", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ALL_VIOLATION", - "filters" : [ "HIGH_VOLTAGE" ], - "violationIds" : [ "violation1", "violation2" ] - }, - "actionIds" : [ "actionId1", "actionId5" ] - } ] - }, { - "id" : "id6", - "contingencyContextType" : "SPECIFIC", - "contingencyId" : "contingencyId5", - "conditionalActions" : [ { - "id" : "stage1", - "condition" : { - "type" : "ALL_VIOLATION", - "violationIds" : [ "violation1", "violation2" ] - }, - "actionIds" : [ "actionId3" ] - } ] - } ] + }] } \ No newline at end of file From b7a0dbd9433f0137b6a404570752273559dff5e3 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 10 Apr 2025 11:44:35 +0200 Subject: [PATCH 67/71] deleted unused imports Signed-off-by: Naledi EL CHEIKH --- .../com/powsybl/python/security/SecurityAnalysisTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index 2187701dcd..e4f03690c7 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -7,8 +7,6 @@ */ package com.powsybl.python.security; -import com.google.common.jimfs.Configuration; -import com.google.common.jimfs.Jimfs; import com.powsybl.commons.report.ReportNode; import com.powsybl.contingency.ContingencyContext; import com.powsybl.contingency.ContingencyContextType; @@ -25,14 +23,11 @@ import org.assertj.core.data.Offset; import org.junit.jupiter.api.Test; -import java.io.IOException; import java.nio.file.FileSystem; -import java.nio.file.Files; import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; /** * @author Etienne Lesot {@literal } From 457b12fbb0dc5639eca3e30f11a0e12610bd6930 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Thu, 10 Apr 2025 13:05:26 +0200 Subject: [PATCH 68/71] add deleted function impl add operator strategy from json fie Signed-off-by: Naledi EL CHEIKH --- .../python/security/SecurityAnalysisCFunctions.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java index 8ced7c9c0b..23dad8ecf0 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java +++ b/java/pypowsybl/src/main/java/com/powsybl/python/security/SecurityAnalysisCFunctions.java @@ -511,6 +511,17 @@ public static void addOperatorStrategy(IsolateThread thread, ObjectHandle securi }); } + @CEntryPoint(name = "addOperatorStrategyFromJsonFile") + public static void addOperatorStrategyFromJsonFile(IsolateThread thread, ObjectHandle securityAnalysisContextHandle, + CCharPointer jsonFilePath, PyPowsyblApiHeader.ExceptionHandlerPointer exceptionHandlerPtr) { + doCatch(exceptionHandlerPtr, () -> { + SecurityAnalysisContext analysisContext = ObjectHandles.getGlobal().get(securityAnalysisContextHandle); + String stringPath = CTypeUtil.toString(jsonFilePath); + Path path = Paths.get(stringPath); + analysisContext.addOperatorStrategyFromJsonFile(path); + }); + } + private static Condition buildCondition(PyPowsyblApiHeader.ConditionType conditionType, CCharPointerPointer subjectIds, int subjectIdsCount, CIntPointer violationTypes, int violationTypesCount) { From 2873cbd9f663c17d540540e3b67c6889840b13e9 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 16 Apr 2025 15:23:12 +0200 Subject: [PATCH 69/71] add files to gitignore Signed-off-by: Naledi EL CHEIKH --- .gitignore | 3 +++ .../java/com/powsybl/python/security/SecurityAnalysisTest.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3a4b6e9e32..d493fc0cea 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,6 @@ build/* tests/_trial_temp/* *.orig + +# Documentation generated files (autosummary) +docs/reference/api/ diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index e4f03690c7..125543e725 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -20,8 +20,8 @@ import com.powsybl.security.monitor.StateMonitor; import com.powsybl.security.results.BranchResult; import org.assertj.core.api.Assertions; -import org.assertj.core.data.Offset; import org.junit.jupiter.api.Test; +import org.assertj.core.data.Offset; import java.nio.file.FileSystem; import java.util.Collections; From 8b624c8c84637dd25395c1d1c1b33ca3959338e9 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 16 Apr 2025 15:29:44 +0200 Subject: [PATCH 70/71] delete unused import Signed-off-by: Naledi EL CHEIKH --- .../java/com/powsybl/python/security/SecurityAnalysisTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index 125543e725..c2717bd690 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test; import org.assertj.core.data.Offset; -import java.nio.file.FileSystem; import java.util.Collections; import java.util.List; @@ -34,8 +33,6 @@ */ class SecurityAnalysisTest { - protected FileSystem fileSystem; - @Test void testStateMonitors() { SecurityAnalysisContext analysisContext = new SecurityAnalysisContext(); From de83cb708a0e489f07802776cb6bd6870136f2f1 Mon Sep 17 00:00:00 2001 From: Naledi EL CHEIKH Date: Wed, 16 Apr 2025 16:08:11 +0200 Subject: [PATCH 71/71] delete unused import Signed-off-by: Naledi EL CHEIKH --- .../java/com/powsybl/python/security/SecurityAnalysisTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java index c2717bd690..022f1dc7cd 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/python/security/SecurityAnalysisTest.java @@ -22,7 +22,6 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.assertj.core.data.Offset; - import java.util.Collections; import java.util.List;