diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 909d4fa1b..b4ab378d2 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -18,25 +18,37 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v1 + uses: actions/checkout@v4 + with: + path: main + + - name: Checkout powsybl-core branch + uses: actions/checkout@v4 + with: + repository: powsybl/powsybl-core + ref: refs/heads/scalable_access + path: powsybl-core - name: Set up JDK 17 uses: actions/setup-java@v1 with: java-version: 17 + - name: Build and install powsybl-core with Maven + run: mvn --batch-mode -DskipTests=true --file ./powsybl-core/pom.xml install + - name: Build with Maven if: matrix.os == 'ubuntu-latest' - run: mvn --batch-mode -Pintegration-tests,jacoco install + run: mvn --batch-mode -Pintegration-tests,jacoco --file ./main/pom.xml install - name: Build with Maven if: matrix.os != 'ubuntu-latest' - run: mvn --batch-mode install + run: mvn --batch-mode --file ./main/pom.xml install - name: Run SonarCloud analysis if: matrix.os == 'ubuntu-latest' run: > - mvn --batch-mode -DskipTests sonar:sonar + mvn --batch-mode -DskipTests --file ./main/pom.xml sonar:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=powsybl-ci-github -Dsonar.projectKey=com.powsybl:powsybl-dynawo diff --git a/dynaflow/src/main/java/com/powsybl/dynaflow/results/AbstractXmlAggregatedResultParser.java b/dynaflow/src/main/java/com/powsybl/dynaflow/results/AbstractXmlAggregatedResultParser.java new file mode 100644 index 000000000..579e850d8 --- /dev/null +++ b/dynaflow/src/main/java/com/powsybl/dynaflow/results/AbstractXmlAggregatedResultParser.java @@ -0,0 +1,53 @@ +/** + * 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.dynaflow.results; + +import com.powsybl.commons.exceptions.UncheckedXmlStreamException; +import com.powsybl.commons.xml.XmlUtil; +import com.powsybl.dynawo.commons.AbstractXmlParser; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * @author Laurent Issertial {@literal } + */ +public abstract class AbstractXmlAggregatedResultParser extends AbstractXmlParser { + + protected static final String STATUS = "status"; + private static final String ID = "id"; + private static final String TIME = "time"; + + protected static boolean readScenarioResult(String elementName, XMLStreamReader xmlReader, Consumer scenarioConsumer) { + if (elementName.equals("scenarioResults")) { + String id = xmlReader.getAttributeValue(null, ID); + String status = xmlReader.getAttributeValue(null, STATUS); + List failedCriteria = new ArrayList<>(); + XmlUtil.readSubElements(xmlReader, subElementName -> readFailedCriterion(subElementName, xmlReader, failedCriteria::add)); + ResultsUtil.createScenarioResult(id, status, failedCriteria).ifPresent(scenarioConsumer); + return true; + } + return false; + } + + protected static void readFailedCriterion(String elementName, XMLStreamReader xmlReader, Consumer resultConsumer) { + try { + if (elementName.equals("criterionNonRespected")) { + String description = xmlReader.getAttributeValue(null, ID); + String time = xmlReader.getAttributeValue(null, TIME); + XmlUtil.readEndElementOrThrow(xmlReader); + ResultsUtil.createFailedCriterion(description, time).ifPresent(resultConsumer); + } + } catch (XMLStreamException e) { + throw new UncheckedXmlStreamException(e); + } + } +} diff --git a/dynaflow/src/main/java/com/powsybl/dynaflow/results/ResultsUtil.java b/dynaflow/src/main/java/com/powsybl/dynaflow/results/ResultsUtil.java index a642c6f2a..840d2994d 100644 --- a/dynaflow/src/main/java/com/powsybl/dynaflow/results/ResultsUtil.java +++ b/dynaflow/src/main/java/com/powsybl/dynaflow/results/ResultsUtil.java @@ -35,7 +35,7 @@ public static PostContingencyComputationStatus convertToPostStatus(Status status }; } - static Optional createScenarioResult(String id, String status, List failedCriteria) { + public static Optional createScenarioResult(String id, String status, List failedCriteria) { if (id == null || status == null || failedCriteria == null) { LOGGER.warn("Inconsistent scenario result entry (id: '{}', status: '{}', failedCriteria: '{}')", id, status, failedCriteria); } else { @@ -56,7 +56,7 @@ static Optional createScenarioResult(String id, String status, L return Optional.empty(); } - static Optional createFailedCriterion(String description, String time) { + public static Optional createFailedCriterion(String description, String time) { if (description == null || time == null) { LOGGER.warn("Inconsistent failed criterion entry (description: '{}', time: '{}')", description, time); } else { diff --git a/dynaflow/src/main/java/com/powsybl/dynaflow/results/XmlScenarioResultParser.java b/dynaflow/src/main/java/com/powsybl/dynaflow/results/XmlScenarioResultParser.java index 5d21cddd0..ce75706cd 100644 --- a/dynaflow/src/main/java/com/powsybl/dynaflow/results/XmlScenarioResultParser.java +++ b/dynaflow/src/main/java/com/powsybl/dynaflow/results/XmlScenarioResultParser.java @@ -7,25 +7,17 @@ */ package com.powsybl.dynaflow.results; -import com.powsybl.commons.exceptions.UncheckedXmlStreamException; import com.powsybl.commons.xml.XmlUtil; -import com.powsybl.dynawo.commons.AbstractXmlParser; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; -import java.util.ArrayList; -import java.util.List; import java.util.function.Consumer; /** * @author Laurent Issertial {@literal } */ -public final class XmlScenarioResultParser extends AbstractXmlParser { - - private static final String STATUS = "status"; - private static final String ID = "id"; - private static final String TIME = "time"; +public final class XmlScenarioResultParser extends AbstractXmlAggregatedResultParser { @Override protected void read(XMLStreamReader xmlReader, Consumer consumer) throws XMLStreamException { @@ -35,27 +27,4 @@ protected void read(XMLStreamReader xmlReader, Consumer consumer } XmlUtil.readSubElements(xmlReader, elementName -> readScenarioResult(elementName, xmlReader, consumer)); } - - public static void readScenarioResult(String elementName, XMLStreamReader xmlReader, Consumer scenarioConsumer) { - if (elementName.equals("scenarioResults")) { - String id = xmlReader.getAttributeValue(null, ID); - String status = xmlReader.getAttributeValue(null, STATUS); - List failedCriteria = new ArrayList<>(); - XmlUtil.readSubElements(xmlReader, subElementName -> readFailedCriterion(subElementName, xmlReader, failedCriteria::add)); - ResultsUtil.createScenarioResult(id, status, failedCriteria).ifPresent(scenarioConsumer); - } - } - - public static void readFailedCriterion(String elementName, XMLStreamReader xmlReader, Consumer resultConsumer) { - try { - if (elementName.equals("criterionNonRespected")) { - String description = xmlReader.getAttributeValue(null, ID); - String time = xmlReader.getAttributeValue(null, TIME); - XmlUtil.readEndElementOrThrow(xmlReader); - ResultsUtil.createFailedCriterion(description, time).ifPresent(resultConsumer); - } - } catch (XMLStreamException e) { - throw new UncheckedXmlStreamException(e); - } - } } diff --git a/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/DynawoSecurityAnalysisTest.java b/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/DynawoSecurityAnalysisTest.java index 60f936f72..0c0e3236d 100644 --- a/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/DynawoSecurityAnalysisTest.java +++ b/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/DynawoSecurityAnalysisTest.java @@ -17,10 +17,10 @@ import com.powsybl.dynamicsimulation.groovy.GroovyExtension; import com.powsybl.dynawo.DynawoSimulationParameters; import com.powsybl.dynawo.DynawoSimulationProvider; +import com.powsybl.dynawo.algorithms.DynawoAlgorithmsConfig; import com.powsybl.dynawo.parameters.ParametersSet; import com.powsybl.dynawo.xml.ParametersXml; -import com.powsybl.dynawo.security.DynawoAlgorithmsConfig; import com.powsybl.dynawo.security.DynawoSecurityAnalysisProvider; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.VariantManagerConstants; @@ -71,7 +71,7 @@ void testIeee14DSA(String criteriaPath, List contingencies, String Network network = Network.read(new ResourceDataSource("IEEE14", new ResourceSet("/ieee14", "IEEE14.iidm"))); GroovyDynamicModelsSupplier dynamicModelsSupplier = new GroovyDynamicModelsSupplier( - getResourceAsStream("/ieee14/dynamic-security-analysis/dynamicModels.groovy"), + getResourceAsStream("/ieee14/dynamicModels.groovy"), GroovyExtension.find(DynamicModelGroovyExtension.class, DynawoSimulationProvider.NAME)); List modelsParameters = ParametersXml.load(getResourceAsStream("/ieee14/models.par")); diff --git a/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/DynawoSimulationTest.java b/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/DynawoSimulationTest.java index 9d18241ed..279a09e60 100644 --- a/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/DynawoSimulationTest.java +++ b/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/DynawoSimulationTest.java @@ -72,7 +72,7 @@ void testIeee14() { assertEquals(27, result.getCurves().size()); DoubleTimeSeries ts1 = result.getCurve("_GEN____1_SM_generator_UStatorPu"); assertEquals("_GEN____1_SM_generator_UStatorPu", ts1.getMetadata().getName()); - assertEquals(192, ts1.toArray().length); + assertEquals(258, ts1.toArray().length); assertEquals(14, result.getFinalStateValues().size()); assertEquals(1.046227, result.getFinalStateValues().get("NETWORK__BUS___10_TN_Upu_value")); List timeLine = result.getTimeLine(); diff --git a/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/MarginCalculationTest.java b/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/MarginCalculationTest.java new file mode 100644 index 000000000..6ba30c9c9 --- /dev/null +++ b/dynawo-integration-tests/src/test/java/com/powsybl/dynawo/it/MarginCalculationTest.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2022, 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/. + */ +package com.powsybl.dynawo.it; + +import com.powsybl.commons.datasource.ResourceDataSource; +import com.powsybl.commons.datasource.ResourceSet; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.contingency.Contingency; +import com.powsybl.dynaflow.results.ScenarioResult; +import com.powsybl.dynaflow.results.Status; +import com.powsybl.dynamicsimulation.groovy.DynamicModelGroovyExtension; +import com.powsybl.dynamicsimulation.groovy.GroovyDynamicModelsSupplier; +import com.powsybl.dynamicsimulation.groovy.GroovyExtension; +import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.DynawoSimulationProvider; +import com.powsybl.dynawo.algorithms.DynawoAlgorithmsConfig; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.margincalculation.MarginCalculationProvider; +import com.powsybl.dynawo.margincalculation.MarginCalculationRunParameters; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariationBuilder; +import com.powsybl.dynawo.margincalculation.loadsvariation.supplier.LoadsVariationSupplier; +import com.powsybl.dynawo.margincalculation.results.LoadIncreaseResult; +import com.powsybl.dynawo.parameters.ParametersSet; +import com.powsybl.dynawo.xml.ParametersXml; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VariantManagerConstants; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Laurent Issertial + */ +class MarginCalculationTest extends AbstractDynawoTest { + + private MarginCalculationProvider provider; + + private MarginCalculationParameters parameters; + + private DynawoSimulationParameters dynawoSimulationParameters; + + @Override + @BeforeEach + void setUp() throws Exception { + super.setUp(); + provider = new MarginCalculationProvider(new DynawoAlgorithmsConfig(Path.of("/dynaflow-launcher"), true)); + dynawoSimulationParameters = new DynawoSimulationParameters(); + parameters = MarginCalculationParameters.builder() + .setStartTime(0) + .setLoadIncreaseStartTime(10) + .setLoadIncreaseStopTime(70) + .setMarginCalculationStartTime(100) + .setContingenciesStartTime(110) + .setStopTime(200) + .setDynawoParameters(dynawoSimulationParameters) + .build(); + } + + @ParameterizedTest + @MethodSource("provideSimulationParameter") + void testIeee14MC(String criteriaPath, List contingencies) { + Network network = Network.read(new ResourceDataSource("IEEE14", new ResourceSet("/ieee14", "IEEE14.iidm"))); + + GroovyDynamicModelsSupplier dynamicModelsSupplier = new GroovyDynamicModelsSupplier( + getResourceAsStream("/ieee14/dynamicModels.groovy"), + GroovyExtension.find(DynamicModelGroovyExtension.class, DynawoSimulationProvider.NAME)); + + List modelsParameters = ParametersXml.load(getResourceAsStream("/ieee14/models.par")); + ParametersSet networkParameters = ParametersXml.load(getResourceAsStream("/ieee14/network.par"), "8"); + ParametersSet solverParameters = ParametersXml.load(getResourceAsStream("/ieee14/solvers.par"), "2"); + dynawoSimulationParameters.setModelsParameters(modelsParameters) + .setNetworkParameters(networkParameters) + .setSolverParameters(solverParameters) + .setSolverType(DynawoSimulationParameters.SolverType.IDA) + .setCriteriaFilePath(Path.of(Objects.requireNonNull(getClass() + .getResource(criteriaPath)).getPath())); + + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("mc_test", "Margin calculation integration test") + .build(); + + MarginCalculationRunParameters runParameters = new MarginCalculationRunParameters() + .setComputationManager(computationManager) + .setMarginCalculationParameters(parameters) + .setReportNode(reportNode); + + LoadsVariationSupplier loadsVariationSupplier = (n, r) -> List.of( + new LoadsVariationBuilder(n, r) + .loads("_LOAD___3_EC", "_LOAD___6_EC", "_LOAD___9_EC") + .variationValue(10) + .build()); + + List results = provider.run(network, VariantManagerConstants.INITIAL_VARIANT_ID, + dynamicModelsSupplier, n -> contingencies, loadsVariationSupplier, runParameters) + .join() + .getResults(); + + List expectedResults = contingencies.stream() + .map(c -> new ScenarioResult(c.getId(), Status.CONVERGENCE)) + .toList(); + assertThat(results).containsExactly( + new LoadIncreaseResult(0, Status.CONVERGENCE, expectedResults), + new LoadIncreaseResult(50, Status.CONVERGENCE, expectedResults), + new LoadIncreaseResult(75, Status.CONVERGENCE, expectedResults), + new LoadIncreaseResult(88, Status.CONVERGENCE, expectedResults), + new LoadIncreaseResult(94, Status.CONVERGENCE, expectedResults), + new LoadIncreaseResult(97, Status.CONVERGENCE, expectedResults), + new LoadIncreaseResult(99, Status.CONVERGENCE, expectedResults)); + } + + private static Stream provideSimulationParameter() { + return Stream.of( + Arguments.of("/ieee14/dynamic-security-analysis/convergence/criteria.crt", + List.of(Contingency.line("_BUS____1-BUS____5-1_AC", "_BUS____5_VL"), + Contingency.generator("_GEN____2_SM"))) + ); + } +} diff --git a/dynawo-integration-tests/src/test/resources/ieee14/dynamicModels.groovy b/dynawo-integration-tests/src/test/resources/ieee14/dynamicModels.groovy new file mode 100644 index 000000000..93892b2c6 --- /dev/null +++ b/dynawo-integration-tests/src/test/resources/ieee14/dynamicModels.groovy @@ -0,0 +1,31 @@ +package ieee14 +/** + * Copyright (c) 2024, 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 + */ + +import com.powsybl.iidm.network.Load + +for (Load load : network.loads) { + LoadAlphaBeta { + staticId load.id + parameterSetId "LAB" + } +} + +for (gen in ["_GEN____1_SM", "_GEN____2_SM", "_GEN____3_SM"]) { + GeneratorSynchronousFourWindingsProportionalRegulations { + staticId gen + parameterSetId "GSFWPR" + gen + } +} + +for (gen in ["_GEN____6_SM", "_GEN____8_SM"]) { + GeneratorSynchronousThreeWindingsProportionalRegulations { + staticId gen + parameterSetId "GSTWPR" + gen + } +} \ No newline at end of file diff --git a/dynawo-integration-tests/src/test/resources/ieee14/margin-calculation/load_variations.json b/dynawo-integration-tests/src/test/resources/ieee14/margin-calculation/load_variations.json new file mode 100644 index 000000000..bd61a0178 --- /dev/null +++ b/dynawo-integration-tests/src/test/resources/ieee14/margin-calculation/load_variations.json @@ -0,0 +1,12 @@ +{ + "variations": [ + { + "loadsIds": ["_LOAD___3_EC_", "_LOAD___6_EC_", "_LOAD___9_EC_"], + "variationValue": 20 + }, + { + "loadsIds": ["LOAD3"], + "variationValue": 10.2 + } + ] +} \ No newline at end of file diff --git a/dynawo-security-analysis/pom.xml b/dynawo-security-analysis/pom.xml index 683813844..21f77addf 100644 --- a/dynawo-security-analysis/pom.xml +++ b/dynawo-security-analysis/pom.xml @@ -35,6 +35,10 @@ ${project.groupId} powsybl-dynaflow + + com.powsybl + powsybl-contingency-dsl + diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/AbstractDynawoAlgorithmsHandler.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/AbstractDynawoAlgorithmsHandler.java new file mode 100644 index 000000000..5f390f4db --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/AbstractDynawoAlgorithmsHandler.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2024, 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.dynawo.algorithms; + +import com.powsybl.commons.exceptions.UncheckedXmlStreamException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.AbstractExecutionHandler; +import com.powsybl.computation.Command; +import com.powsybl.computation.CommandExecution; +import com.powsybl.dynawo.DynawoFilesUtils; +import com.powsybl.dynawo.DynawoSimulationContext; +import com.powsybl.dynawo.commons.DynawoUtil; +import com.powsybl.iidm.network.Network; + +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.List; + +import static com.powsybl.dynawo.DynawoFilesUtils.deleteExistingFile; +import static com.powsybl.dynawo.commons.DynawoConstants.*; +import static com.powsybl.dynawo.commons.DynawoUtil.getCommandExecutions; + +/** + * @author Laurent Issertial {@literal } + */ +public abstract class AbstractDynawoAlgorithmsHandler extends AbstractExecutionHandler { + + protected final S context; + protected final Command command; + protected final Network network; + protected final ReportNode reportNode; + + protected AbstractDynawoAlgorithmsHandler(S context, Command command, ReportNode reportNode) { + this.context = context; + this.command = command; + this.network = context.getNetwork(); + this.reportNode = reportNode; + } + + @Override + public List before(Path workingDir) throws IOException { + network.getVariantManager().setWorkingVariant(context.getWorkingVariantId()); + deleteExistingFile(workingDir.resolve(OUTPUTS_FOLDER), FINAL_STATE_FOLDER, OUTPUT_IIDM_FILENAME); + writeInputFiles(workingDir); + return getCommandExecutions(command); + } + + private void writeInputFiles(Path workingDir) { + try { + DynawoUtil.writeIidm(network, workingDir.resolve(NETWORK_FILENAME)); + DynawoFilesUtils.writeInputFiles(workingDir, context); + writeMultipleJobs(workingDir); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (XMLStreamException e) { + throw new UncheckedXmlStreamException(e); + } + } + + protected abstract void writeMultipleJobs(Path workingDir) throws XMLStreamException, IOException; +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/ContingencyEventModels.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/ContingencyEventModels.java similarity index 96% rename from dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/ContingencyEventModels.java rename to dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/ContingencyEventModels.java index 4c06a7b5f..210257f0b 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/ContingencyEventModels.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/ContingencyEventModels.java @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dynawo.security; +package com.powsybl.dynawo.algorithms; import com.powsybl.contingency.Contingency; import com.powsybl.dynawo.models.BlackBoxModel; diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/ContingencyEventModelsFactory.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/ContingencyEventModelsFactory.java similarity index 92% rename from dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/ContingencyEventModelsFactory.java rename to dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/ContingencyEventModelsFactory.java index 33a1ef451..63bf9d8c9 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/ContingencyEventModelsFactory.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/ContingencyEventModelsFactory.java @@ -5,9 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dynawo.security; +package com.powsybl.dynawo.algorithms; -import com.powsybl.commons.PowsyblException; import com.powsybl.commons.report.ReportNode; import com.powsybl.contingency.Contingency; import com.powsybl.contingency.ContingencyElement; @@ -25,6 +24,9 @@ import java.util.*; +import static com.powsybl.dynawo.algorithms.DynawoAlgorithmsReports.createContingencyVoltageIdNotFoundReportNode; +import static com.powsybl.dynawo.algorithms.DynawoAlgorithmsReports.createNotSupportedContingencyTypeReportNode; + /** * @author Laurent Issertial */ @@ -84,14 +86,14 @@ private static BlackBoxModel createContingencyEventModel(ContingencyElement elem if (side != null) { builder.disconnectOnly(side); } else { - DynamicSecurityAnalysisReports.createContingencyVoltageIdNotFoundReportNode(reportNode, + createContingencyVoltageIdNotFoundReportNode(reportNode, sidedElement.getId(), sidedElement.getVoltageLevelId()); return null; } } BlackBoxModel bbm = builder.build(); if (bbm == null) { - throw new PowsyblException("Contingency element " + element.getType() + " not supported"); + createNotSupportedContingencyTypeReportNode(reportNode, element.getType().toString()); } if (bbm instanceof ContextDependentEvent cde) { cde.setEquipmentHasDynamicModel(context); diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsCommandUtil.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsCommandUtil.java new file mode 100644 index 000000000..acd623cb1 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsCommandUtil.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2024, 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.dynawo.algorithms; + +import com.powsybl.computation.Command; +import com.powsybl.computation.SimpleCommandBuilder; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.powsybl.dynawo.algorithms.xml.AlgorithmsConstants.MULTIPLE_JOBS_FILENAME; +import static com.powsybl.dynawo.commons.DynawoConstants.AGGREGATED_RESULTS; + +/** + * @author Laurent Issertial {@literal } + */ +public final class DynawoAlgorithmsCommandUtil { + + private DynawoAlgorithmsCommandUtil() { + } + + public static Command getCommand(DynawoAlgorithmsConfig config, String mode, String id) { + List args = Arrays.asList( + mode, + "--input", MULTIPLE_JOBS_FILENAME, + "--output", AGGREGATED_RESULTS); + return new SimpleCommandBuilder() + .id(id) + .program(config.getProgram()) + .args(args) + .build(); + } + + public static Command getVersionCommand(DynawoAlgorithmsConfig config) { + List args = Collections.singletonList("--version"); + return new SimpleCommandBuilder() + .id("dynawo_version") + .program(config.getProgram()) + .args(args) + .build(); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoAlgorithmsConfig.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsConfig.java similarity index 97% rename from dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoAlgorithmsConfig.java rename to dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsConfig.java index 88960f1d0..c13d8c6e9 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoAlgorithmsConfig.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsConfig.java @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dynawo.security; +package com.powsybl.dynawo.algorithms; import com.powsybl.commons.config.ModuleConfig; import com.powsybl.commons.config.PlatformConfig; diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsReports.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsReports.java new file mode 100644 index 000000000..7e2b99824 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsReports.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2024, 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.dynawo.algorithms; + +import com.powsybl.commons.report.ReportNode; + +/** + * @author Laurent Issertial {@literal } + */ +public final class DynawoAlgorithmsReports { + + private DynawoAlgorithmsReports() { + } + + public static ReportNode createContingencyVoltageIdNotFoundReportNode(ReportNode reportNode, String contingencyId, String voltageLevelId) { + return reportNode.newReportNode() + .withMessageTemplate("contingencyVlIdNotFound", "Voltage id '${voltageLevelId}' of contingency '${contingencyId}' not found, contingency will be skipped") + .withUntypedValue("voltageLevelId", voltageLevelId) + .withUntypedValue("contingencyId", contingencyId) + .add(); + } + + public static ReportNode createNotSupportedContingencyTypeReportNode(ReportNode reportNode, String contingencyType) { + return reportNode.newReportNode() + .withMessageTemplate("contingencyNotSupported", "Contingency element '${contingencyType}' not supported, contingency will be skipped") + .withUntypedValue("contingencyType", contingencyType) + .add(); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/AlgorithmsConstants.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/AlgorithmsConstants.java new file mode 100644 index 000000000..179d5d0e0 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/AlgorithmsConstants.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024, 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.dynawo.algorithms.xml; + +/** + * @author Laurent Issertial {@literal } + */ +public final class AlgorithmsConstants { + + public static final String MULTIPLE_JOBS_FILENAME = "multiple_jobs.xml"; + + private AlgorithmsConstants() { + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/ContingenciesDydXml.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/ContingenciesDydXml.java similarity index 67% rename from dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/ContingenciesDydXml.java rename to dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/ContingenciesDydXml.java index 1d1239bb6..ae14f4fad 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/ContingenciesDydXml.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/ContingenciesDydXml.java @@ -5,19 +5,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dynawo.security.xml; +package com.powsybl.dynawo.algorithms.xml; -import com.powsybl.dynawo.DynawoSimulationContext; import com.powsybl.dynawo.models.BlackBoxModel; import com.powsybl.dynawo.models.macroconnections.MacroConnect; import com.powsybl.dynawo.models.macroconnections.MacroConnector; -import com.powsybl.dynawo.security.ContingencyEventModels; -import com.powsybl.dynawo.security.SecurityAnalysisContext; +import com.powsybl.dynawo.algorithms.ContingencyEventModels; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import java.io.IOException; import java.nio.file.Path; +import java.util.List; import java.util.Objects; /** @@ -28,15 +27,15 @@ public final class ContingenciesDydXml { private ContingenciesDydXml() { } - public static void write(Path workingDir, SecurityAnalysisContext context) throws IOException, XMLStreamException { + public static void write(Path workingDir, List eventModels) throws IOException, XMLStreamException { Objects.requireNonNull(workingDir); - for (ContingencyEventModels model : context.getContingencyEventModels()) { + for (ContingencyEventModels model : eventModels) { Path file = workingDir.resolve(createDydFileName(model)); - XmlUtil.write(file, context, "dynamicModelsArchitecture", ContingenciesDydXml::writeEvent, model); + XmlUtil.write(file, "dynamicModelsArchitecture", ContingenciesDydXml::writeEvent, model); } } - private static void writeEvent(XMLStreamWriter writer, DynawoSimulationContext context, ContingencyEventModels model) throws XMLStreamException { + private static void writeEvent(XMLStreamWriter writer, ContingencyEventModels model) throws XMLStreamException { for (BlackBoxModel ev : model.eventModels()) { ev.write(writer, ContingenciesParXml.createParFileName(model)); } diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/ContingenciesParXml.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/ContingenciesParXml.java similarity index 74% rename from dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/ContingenciesParXml.java rename to dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/ContingenciesParXml.java index 985dd81b1..1fc8795ac 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/ContingenciesParXml.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/ContingenciesParXml.java @@ -5,13 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dynawo.security.xml; +package com.powsybl.dynawo.algorithms.xml; import com.powsybl.dynawo.xml.ParametersXml; -import com.powsybl.dynawo.security.ContingencyEventModels; -import com.powsybl.dynawo.security.SecurityAnalysisContext; +import com.powsybl.dynawo.algorithms.ContingencyEventModels; import java.nio.file.Path; +import java.util.List; import java.util.Objects; import static com.powsybl.dynawo.xml.DynawoSimulationXmlConstants.DYN_PREFIX; @@ -24,9 +24,9 @@ public final class ContingenciesParXml { private ContingenciesParXml() { } - public static void write(Path workingDir, SecurityAnalysisContext context) { + public static void write(Path workingDir, List eventModels) { Objects.requireNonNull(workingDir); - for (ContingencyEventModels model : context.getContingencyEventModels()) { + for (ContingencyEventModels model : eventModels) { ParametersXml.write(model.eventParameters(), createParFileName(model), workingDir, DYN_PREFIX); } } diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/XmlUtil.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/XmlUtil.java similarity index 67% rename from dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/XmlUtil.java rename to dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/XmlUtil.java index ba94242b3..72e5a2b46 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/XmlUtil.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/algorithms/xml/XmlUtil.java @@ -5,11 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dynawo.security.xml; +package com.powsybl.dynawo.algorithms.xml; import com.powsybl.dynawo.xml.XmlStreamWriterFactory; -import com.powsybl.dynawo.security.ContingencyEventModels; -import com.powsybl.dynawo.security.SecurityAnalysisContext; +import com.powsybl.dynawo.algorithms.ContingencyEventModels; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -19,6 +18,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; +import java.util.function.Consumer; import static com.powsybl.dynawo.xml.DynawoSimulationXmlConstants.DYN_PREFIX; import static com.powsybl.dynawo.xml.DynawoSimulationXmlConstants.DYN_URI; @@ -28,22 +28,16 @@ */ public final class XmlUtil { - @FunctionalInterface - public interface XmlDynawoContingenciesWriter { - void writeContingencies(XMLStreamWriter writer, SecurityAnalysisContext context) throws XMLStreamException; - } - @FunctionalInterface public interface XmlDynawoEventWriter { - void writeEvent(XMLStreamWriter writer, SecurityAnalysisContext context, ContingencyEventModels model) throws XMLStreamException; + void writeEvent(XMLStreamWriter writer, ContingencyEventModels model) throws XMLStreamException; } private XmlUtil() { } - public static void write(Path file, SecurityAnalysisContext context, String elementName, XmlDynawoEventWriter xmlDynawoEventWriter, ContingencyEventModels model) throws IOException, XMLStreamException { + public static void write(Path file, String elementName, XmlDynawoEventWriter xmlDynawoEventWriter, ContingencyEventModels model) throws IOException, XMLStreamException { Objects.requireNonNull(file); - Objects.requireNonNull(context); Objects.requireNonNull(elementName); Objects.requireNonNull(xmlDynawoEventWriter); @@ -55,7 +49,7 @@ public static void write(Path file, SecurityAnalysisContext context, String elem xmlWriter.writeStartElement(DYN_URI, elementName); xmlWriter.writeNamespace(DYN_PREFIX, DYN_URI); - xmlDynawoEventWriter.writeEvent(xmlWriter, context, model); + xmlDynawoEventWriter.writeEvent(xmlWriter, model); xmlWriter.writeEndElement(); xmlWriter.writeEndDocument(); @@ -65,11 +59,10 @@ public static void write(Path file, SecurityAnalysisContext context, String elem } } - public static void write(Path file, SecurityAnalysisContext context, String elementName, XmlDynawoContingenciesWriter xmlDynawoWriter) throws IOException, XMLStreamException { + public static void write(Path file, String elementName, Consumer xmlStreamWriterConsumer) throws IOException, XMLStreamException { Objects.requireNonNull(file); - Objects.requireNonNull(context); Objects.requireNonNull(elementName); - Objects.requireNonNull(xmlDynawoWriter); + Objects.requireNonNull(xmlStreamWriterConsumer); try (Writer writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { XMLStreamWriter xmlWriter = XmlStreamWriterFactory.newInstance(writer); @@ -78,7 +71,7 @@ public static void write(Path file, SecurityAnalysisContext context, String elem xmlWriter.writeStartElement(elementName); xmlWriter.writeNamespace("", DYN_URI); - xmlDynawoWriter.writeContingencies(xmlWriter, context); + xmlStreamWriterConsumer.accept(xmlWriter); xmlWriter.writeEndElement(); xmlWriter.writeEndDocument(); diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculation.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculation.java new file mode 100644 index 000000000..2bb98d977 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculation.java @@ -0,0 +1,156 @@ +/** + * 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.dynawo.margincalculation; + +import com.powsybl.commons.Versionable; +import com.powsybl.contingency.ContingenciesProvider; +import com.powsybl.dynamicsimulation.DynamicModelsSupplier; +import com.powsybl.dynawo.margincalculation.loadsvariation.supplier.LoadsVariationSupplier; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; +import com.powsybl.iidm.network.Network; + +import java.util.concurrent.CompletableFuture; + +/** + * Margin calculation main API. It is a utility class (so with only static methods) used as an entry point for running + * a margin calculation (Dynawo being the only implementation). + * + * @author Laurent Issertial {@literal } + */ +public final class MarginCalculation { + + private MarginCalculation() { + throw new IllegalStateException("Utility class should not been instantiated"); + } + + /** + * A margin calculation runner is responsible for providing convenient methods on top of {@link MarginCalculationProvider}: + * several variants of synchronous and asynchronous run with default parameters. + */ + public static final class Runner implements Versionable { + + private final MarginCalculationProvider provider; + + public Runner() { + provider = new MarginCalculationProvider(); + } + + public CompletableFuture runAsync(Network network, String workingVariantId, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier, + MarginCalculationRunParameters runParameters) { + return provider.run(network, workingVariantId, dynamicModelsSupplier, contingenciesProvider, + loadsVariationSupplier, runParameters); + } + + public CompletableFuture runAsync(Network network, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier, + MarginCalculationRunParameters runParameters) { + return provider.run(network, network.getVariantManager().getWorkingVariantId(), dynamicModelsSupplier, contingenciesProvider, + loadsVariationSupplier, runParameters); + } + + public CompletableFuture runAsync(Network network, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier) { + return provider.run(network, network.getVariantManager().getWorkingVariantId(), dynamicModelsSupplier, contingenciesProvider, + loadsVariationSupplier, MarginCalculationRunParameters.getDefault()); + } + + public MarginCalculationResult run(Network network, String workingVariantId, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier, + MarginCalculationRunParameters runParameters) { + return runAsync(network, workingVariantId, dynamicModelsSupplier, contingenciesProvider, loadsVariationSupplier, + runParameters).join(); + } + + public MarginCalculationResult run(Network network, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier, + MarginCalculationRunParameters runParameters) { + return runAsync(network, dynamicModelsSupplier, contingenciesProvider, loadsVariationSupplier, + runParameters).join(); + } + + public MarginCalculationResult run(Network network, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier) { + return runAsync(network, dynamicModelsSupplier, contingenciesProvider, loadsVariationSupplier).join(); + } + + @Override + public String getName() { + return provider.getName(); + } + + @Override + public String getVersion() { + return provider.getVersion(); + } + } + + public static Runner getRunner() { + return new Runner(); + } + + public static CompletableFuture runAsync(Network network, String workingVariantId, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier, + MarginCalculationRunParameters runParameters) { + return new Runner().runAsync(network, workingVariantId, dynamicModelsSupplier, contingenciesProvider, + loadsVariationSupplier, runParameters); + } + + public static CompletableFuture runAsync(Network network, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier, + MarginCalculationRunParameters runParameters) { + return new Runner().runAsync(network, dynamicModelsSupplier, contingenciesProvider, loadsVariationSupplier, runParameters); + } + + public static CompletableFuture runAsync(Network network, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier) { + return new Runner().runAsync(network, dynamicModelsSupplier, contingenciesProvider, loadsVariationSupplier); + } + + public static MarginCalculationResult run(Network network, String workingVariantId, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier, + MarginCalculationRunParameters runParameters) { + return new Runner().run(network, workingVariantId, dynamicModelsSupplier, contingenciesProvider, + loadsVariationSupplier, runParameters); + } + + public static MarginCalculationResult run(Network network, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier, + MarginCalculationRunParameters runParameters) { + return new Runner().run(network, dynamicModelsSupplier, contingenciesProvider, loadsVariationSupplier, runParameters); + } + + public static MarginCalculationResult run(Network network, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier) { + return new Runner().run(network, dynamicModelsSupplier, contingenciesProvider, loadsVariationSupplier); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationContext.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationContext.java new file mode 100644 index 000000000..fa540d77c --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationContext.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.contingency.Contingency; +import com.powsybl.dynamicsimulation.DynamicSimulationParameters; +import com.powsybl.dynawo.DynawoSimulationConstants; +import com.powsybl.dynawo.Phase2Config; +import com.powsybl.dynawo.commons.DynawoConstants; +import com.powsybl.dynawo.commons.DynawoVersion; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadVariationAreaAutomationSystem; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsProportionalScalable; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.dynawo.DynawoSimulationContext; +import com.powsybl.dynawo.models.BlackBoxModel; +import com.powsybl.dynawo.models.loads.AbstractLoad; +import com.powsybl.dynawo.models.macroconnections.MacroConnect; +import com.powsybl.dynawo.models.macroconnections.MacroConnector; +import com.powsybl.dynawo.algorithms.ContingencyEventModels; +import com.powsybl.dynawo.algorithms.ContingencyEventModelsFactory; +import com.powsybl.dynawo.xml.DydDataSupplier; +import com.powsybl.iidm.modification.scalable.Scalable; +import com.powsybl.iidm.modification.scalable.ScalingParameters; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Network; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +/** + * @author Laurent Issertial + */ +public class MarginCalculationContext extends DynawoSimulationContext { + + private final MarginCalculationParameters marginCalculationParameters; + private final List contingencyEventModels; + private final LoadVariationAreaAutomationSystem loadVariationArea; + private final List loadVariationMacroConnectList = new ArrayList<>(); + private final Map loadVariationMacroConnectorsMap = new LinkedHashMap<>(); + + public MarginCalculationContext(Network network, String workingVariantId, + List dynamicModels, + MarginCalculationParameters parameters, + List contingencies, + List loadsVariations) { + this(network, workingVariantId, dynamicModels, parameters, contingencies, + loadsVariations, DynawoConstants.VERSION_MIN, ReportNode.NO_OP); + } + + public MarginCalculationContext(Network network, String workingVariantId, + List dynamicModels, + MarginCalculationParameters parameters, + List contingencies, + List loadsVariations, + DynawoVersion currentVersion, + ReportNode reportNode) { + super(network, workingVariantId, dynamicModels, Collections.emptyList(), Collections.emptyList(), + new DynamicSimulationParameters(parameters.getStartTime(), parameters.getMarginCalculationStartTime()), + parameters.getDynawoParameters(), + configurePhase2(parameters, loadsVariations), + currentVersion, reportNode); + this.marginCalculationParameters = parameters; + double contingenciesStartTime = parameters.getContingenciesStartTime(); + this.contingencyEventModels = ContingencyEventModelsFactory + .createFrom(contingencies, this, macroConnectionsAdder, contingenciesStartTime, reportNode); + this.loadVariationArea = new LoadVariationAreaAutomationSystem(loadsVariations, + parameters.getLoadIncreaseStartTime(), + parameters.getLoadIncreaseStopTime(), + configureScaling(network)); + + macroConnectionsAdder.setMacroConnectorAdder(loadVariationMacroConnectorsMap::computeIfAbsent); + macroConnectionsAdder.setMacroConnectAdder(loadVariationMacroConnectList::add); + loadVariationArea.createMacroConnections(macroConnectionsAdder); + loadVariationArea.createDynamicModelParameters(this, getDynamicModelsParameters()::add); + } + + public MarginCalculationParameters getMarginCalculationParameters() { + return marginCalculationParameters; + } + + public List getContingencyEventModels() { + return contingencyEventModels; + } + + public DydDataSupplier getLoadVariationAreaDydData() { + return new DydDataSupplier() { + + @Override + public List getBlackBoxDynamicModels() { + return List.of(loadVariationArea); + } + + @Override + public Collection getMacroConnectors() { + return loadVariationMacroConnectorsMap.values(); + } + + @Override + public List getMacroConnectList() { + return loadVariationMacroConnectList; + } + + @Override + public String getParFileName() { + return DynawoSimulationConstants.getSimulationParFile(getNetwork()); + } + }; + } + + private static Phase2Config configurePhase2(MarginCalculationParameters parameters, List loadsVariations) { + return switch (parameters.getLoadModelsRule()) { + case ALL_LOADS -> new Phase2Config(parameters.getStopTime(), AbstractLoad.class::isInstance); + case TARGETED_LOADS -> { + Set loadIds = loadsVariations.stream() + .flatMap(l -> l.loads().stream()) + .map(Identifiable::getId) + .collect(Collectors.toSet()); + yield new Phase2Config(parameters.getStopTime(), + bbm -> bbm instanceof AbstractLoad eBbm && loadIds.contains(eBbm.getStaticId())); + } + }; + } + + private static BiConsumer configureScaling(Network network) { + ScalingParameters scalingParameters = new ScalingParameters() + .setScalingConvention(Scalable.ScalingConvention.LOAD) + .setConstantPowerFactor(true); + return (s, v) -> s.scale(network, v, scalingParameters); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationHandler.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationHandler.java new file mode 100644 index 000000000..8d0a9732f --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationHandler.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.Command; +import com.powsybl.computation.ExecutionReport; +import com.powsybl.dynawo.algorithms.AbstractDynawoAlgorithmsHandler; +import com.powsybl.dynawo.algorithms.xml.ContingenciesDydXml; +import com.powsybl.dynawo.algorithms.xml.ContingenciesParXml; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; +import com.powsybl.dynawo.margincalculation.results.XmlMarginCalculationResultParser; +import com.powsybl.dynawo.margincalculation.xml.MultipleJobsXml; +import com.powsybl.dynawo.xml.DydXml; +import com.powsybl.dynawo.xml.JobsXml; + +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.nio.file.Path; + +import static com.powsybl.dynawo.DynawoSimulationConstants.PHASE_2_DYD_FILENAME; +import static com.powsybl.dynawo.commons.DynawoConstants.AGGREGATED_RESULTS; + +/** + * @author Laurent Issertial {@literal } + */ +public class MarginCalculationHandler extends AbstractDynawoAlgorithmsHandler { + + public MarginCalculationHandler(MarginCalculationContext context, Command command, ReportNode reportNode) { + super(context, command, reportNode); + } + + @Override + public MarginCalculationResult after(Path workingDir, ExecutionReport report) throws IOException { + super.after(workingDir, report); + context.getNetwork().getVariantManager().setWorkingVariant(context.getWorkingVariantId()); + Path resultsFile = workingDir.resolve(AGGREGATED_RESULTS); + new XmlMarginCalculationResultParser().parse(resultsFile); + return new MarginCalculationResult(new XmlMarginCalculationResultParser().parse(resultsFile)); + } + + @Override + protected void writeMultipleJobs(Path workingDir) throws XMLStreamException, IOException { + MultipleJobsXml.write(workingDir, context); + ContingenciesDydXml.write(workingDir, context.getContingencyEventModels()); + ContingenciesParXml.write(workingDir, context.getContingencyEventModels()); + JobsXml.writePhase2(workingDir, context); + if (context.getPhase2DydData().isPresent()) { + DydXml.write(workingDir, PHASE_2_DYD_FILENAME, context.getPhase2DydData().get()); + } + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationParameters.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationParameters.java new file mode 100644 index 000000000..eb72c1981 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationParameters.java @@ -0,0 +1,263 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation; + +import com.powsybl.commons.config.ModuleConfig; +import com.powsybl.commons.config.PlatformConfig; +import com.powsybl.dynawo.DynawoSimulationParameters; + +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.util.Optional; + +/** + * @author Laurent Issertial + */ +public final class MarginCalculationParameters { + + public static final double DEFAULT_START_TIME = 0; + public static final double DEFAULT_STOP_TIME = 200; + public static final double DEFAULT_MARGIN_CALCULATION_START_TIME = 100; + public static final double DEFAULT_LOAD_INCREASE_START_TIME = 10; + public static final double DEFAULT_LOAD_INCREASE_STOP_TIME = 50; + public static final double DEFAULT_CONTINGENCIES_START_TIME = 120; + public static final CalculationType DEFAULT_CALCULATION_TYPE = CalculationType.GLOBAL_MARGIN; + public static final int DEFAULT_ACCURACY = 2; + public static final LoadModelsRule DEFAULT_LOAD_MODELS_RULE = LoadModelsRule.ALL_LOADS; + + public enum CalculationType { + GLOBAL_MARGIN, + LOCAL_MARGIN + } + + /** + * Indicates how to handle load dynamic models in the first phase + */ + public enum LoadModelsRule { + /** + * Remove every specific loads dynamic models + */ + ALL_LOADS, + /** + * Remove dynamic models on loads affected by the margin calculation + */ + TARGETED_LOADS + } + + public static class Builder { + + private double startTime = DEFAULT_START_TIME; + private double stopTime = DEFAULT_STOP_TIME; + private double marginCalculationStartTime = DEFAULT_MARGIN_CALCULATION_START_TIME; + private double loadIncreaseStartTime = DEFAULT_LOAD_INCREASE_START_TIME; + private double loadIncreaseStopTime = DEFAULT_LOAD_INCREASE_STOP_TIME; + private double contingenciesStartTime = DEFAULT_CONTINGENCIES_START_TIME; + private CalculationType calculationType = DEFAULT_CALCULATION_TYPE; + private int accuracy = DEFAULT_ACCURACY; + private LoadModelsRule loadModelsRule = DEFAULT_LOAD_MODELS_RULE; + private DynawoSimulationParameters dynawoParameters = new DynawoSimulationParameters(); + + /** + * Set dynamic simulation start time, must be greater than 0 + */ + public Builder setStartTime(double startTime) { + this.startTime = startTime; + return this; + } + + /** + * Set dynamic simulation stop time, must be after {@link #startTime} + */ + public Builder setStopTime(double stopTime) { + this.stopTime = stopTime; + return this; + } + + /** + * Set margin calculation phase start time, must be between {@link #startTime} and {@link #stopTime} + */ + public Builder setMarginCalculationStartTime(double marginCalculationStartTime) { + this.marginCalculationStartTime = marginCalculationStartTime; + return this; + } + + /** + * Set load increase start time, must be between {@link #startTime} and {@link #marginCalculationStartTime} + */ + public Builder setLoadIncreaseStartTime(double loadIncreaseStartTime) { + this.loadIncreaseStartTime = loadIncreaseStartTime; + return this; + } + + /** + * Set load increase stop time, must be after {@link #loadIncreaseStartTime} + */ + public Builder setLoadIncreaseStopTime(double loadIncreaseStopTime) { + this.loadIncreaseStopTime = loadIncreaseStopTime; + return this; + } + + /** + * Set contingencies start time, must be between {@link #marginCalculationStartTime} and {@link #stopTime} + */ + public Builder setContingenciesStartTime(double contingenciesStartTime) { + this.contingenciesStartTime = contingenciesStartTime; + return this; + } + + public Builder setCalculationType(CalculationType calculationType) { + this.calculationType = calculationType; + return this; + } + + public Builder setAccuracy(int accuracy) { + this.accuracy = accuracy; + return this; + } + + public Builder setLoadModelsRule(LoadModelsRule loadModelsRule) { + this.loadModelsRule = loadModelsRule; + return this; + } + + public Builder setDynawoParameters(DynawoSimulationParameters dynawoParameters) { + this.dynawoParameters = dynawoParameters; + return this; + } + + public MarginCalculationParameters build() { + //TODO add values to exception + if (startTime < 0) { + throw new IllegalStateException("Start time should be zero or positive"); + } + if (stopTime <= startTime) { + throw new IllegalStateException("Stop time should be greater than start time"); + } + if (marginCalculationStartTime <= startTime || marginCalculationStartTime >= stopTime) { + throw new IllegalStateException("Margin calculation start time should be between start and stop time"); + } + if (contingenciesStartTime <= marginCalculationStartTime || contingenciesStartTime >= stopTime) { + throw new IllegalStateException("Contingencies start time should be between margin calculation start time and stop time"); + } + if (loadIncreaseStartTime <= startTime) { + throw new IllegalStateException("Load increase start time should be greater start time"); + } + if (loadIncreaseStopTime <= loadIncreaseStartTime || loadIncreaseStopTime >= marginCalculationStartTime) { + throw new IllegalStateException("Load increase stop time should be between load increase start time and margin calculation start time"); + } + return new MarginCalculationParameters(startTime, stopTime, marginCalculationStartTime, loadIncreaseStartTime, + loadIncreaseStopTime, contingenciesStartTime, calculationType, accuracy, loadModelsRule, dynawoParameters); + } + } + + /** + * Creates a builder for MarginCalculationParameters with default values + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Loads parameters from the default platform configuration. + */ + public static MarginCalculationParameters load() { + return load(PlatformConfig.defaultConfig()); + } + + /** + * Loads parameters from a provided platform configuration. + */ + public static MarginCalculationParameters load(PlatformConfig platformConfig) { + return load(platformConfig, FileSystems.getDefault()); + } + + public static MarginCalculationParameters load(PlatformConfig platformConfig, FileSystem fileSystem) { + Builder builder = builder(); + Optional config = platformConfig.getOptionalModuleConfig("margin-calculation-default-parameters"); + config.ifPresent(c -> { + c.getOptionalDoubleProperty("startTime").ifPresent(builder::setStartTime); + c.getOptionalDoubleProperty("stopTime").ifPresent(builder::setStopTime); + c.getOptionalDoubleProperty("margin-calculation-start-time").ifPresent(builder::setMarginCalculationStartTime); + c.getOptionalDoubleProperty("loadIncrease.startTime").ifPresent(builder::setLoadIncreaseStartTime); + c.getOptionalDoubleProperty("loadIncrease.stopTime").ifPresent(builder::setLoadIncreaseStopTime); + c.getOptionalDoubleProperty("contingencies-start-time").ifPresent(builder::setContingenciesStartTime); + c.getOptionalEnumProperty("calculation-type", CalculationType.class).ifPresent(builder::setCalculationType); + c.getOptionalIntProperty("accuracy").ifPresent(builder::setAccuracy); + c.getOptionalEnumProperty("load-models-rule", LoadModelsRule.class).ifPresent(builder::setLoadModelsRule); + }); + builder.setDynawoParameters(DynawoSimulationParameters.load(platformConfig, fileSystem)); + return builder.build(); + } + + private final double startTime; + private final double stopTime; + private final double marginCalculationStartTime; + private final double loadIncreaseStartTime; + private final double loadIncreaseStopTime; + private final double contingenciesStartTime; + private final CalculationType calculationType; + private final int accuracy; + private final LoadModelsRule loadModelsRule; + private final DynawoSimulationParameters dynawoParameters; + + private MarginCalculationParameters(double startTime, double stopTime, double marginCalculationStartTime, + double loadIncreaseStartTime, double loadIncreaseStopTime, + double contingenciesStartTime, CalculationType calculationType, + int accuracy, LoadModelsRule loadModelsRule, DynawoSimulationParameters dynawoParameters) { + this.startTime = startTime; + this.stopTime = stopTime; + this.marginCalculationStartTime = marginCalculationStartTime; + this.loadIncreaseStartTime = loadIncreaseStartTime; + this.loadIncreaseStopTime = loadIncreaseStopTime; + this.contingenciesStartTime = contingenciesStartTime; + this.calculationType = calculationType; + this.accuracy = accuracy; + this.loadModelsRule = loadModelsRule; + this.dynawoParameters = dynawoParameters; + } + + public double getStartTime() { + return startTime; + } + + public double getStopTime() { + return stopTime; + } + + public double getMarginCalculationStartTime() { + return marginCalculationStartTime; + } + + public double getLoadIncreaseStartTime() { + return loadIncreaseStartTime; + } + + public double getLoadIncreaseStopTime() { + return loadIncreaseStopTime; + } + + public double getContingenciesStartTime() { + return contingenciesStartTime; + } + + public CalculationType getCalculationType() { + return calculationType; + } + + public int getAccuracy() { + return accuracy; + } + + public LoadModelsRule getLoadModelsRule() { + return loadModelsRule; + } + + public DynawoSimulationParameters getDynawoParameters() { + return dynawoParameters; + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationProvider.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationProvider.java new file mode 100644 index 000000000..af6260b2f --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationProvider.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation; + +import com.powsybl.commons.Versionable; +import com.powsybl.commons.config.PlatformConfig; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ExecutionEnvironment; +import com.powsybl.contingency.ContingenciesProvider; +import com.powsybl.dynamicsimulation.DynamicModelsSupplier; +import com.powsybl.dynawo.DynawoSimulationProvider; +import com.powsybl.dynawo.commons.DynawoUtil; +import com.powsybl.dynawo.commons.DynawoVersion; +import com.powsybl.dynawo.commons.PowsyblDynawoVersion; +import com.powsybl.dynawo.margincalculation.loadsvariation.supplier.LoadsVariationSupplier; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; +import com.powsybl.dynawo.models.utils.BlackBoxSupplierUtils; +import com.powsybl.dynawo.algorithms.DynawoAlgorithmsConfig; +import com.powsybl.iidm.network.Network; + +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import static com.powsybl.dynawo.DynawoSimulationConfig.DYNAWO_LAUNCHER_PROGRAM_NAME; +import static com.powsybl.dynawo.algorithms.DynawoAlgorithmsCommandUtil.getCommand; +import static com.powsybl.dynawo.algorithms.DynawoAlgorithmsCommandUtil.getVersionCommand; + +/** + * @author Laurent Issertial {@literal } + */ +public class MarginCalculationProvider implements Versionable { + + private static final String WORKING_DIR_PREFIX = "dynawo_mc_"; + private final DynawoAlgorithmsConfig config; + + public MarginCalculationProvider() { + this(PlatformConfig.defaultConfig()); + } + + public MarginCalculationProvider(PlatformConfig platformConfig) { + this(DynawoAlgorithmsConfig.load(platformConfig)); + } + + public MarginCalculationProvider(DynawoAlgorithmsConfig config) { + this.config = Objects.requireNonNull(config); + } + + public CompletableFuture run(Network network, String workingVariantId, + DynamicModelsSupplier dynamicModelsSupplier, + ContingenciesProvider contingenciesProvider, + LoadsVariationSupplier loadsVariationSupplier, + MarginCalculationRunParameters runParameters) { + + ReportNode mcReportNode = MarginCalculationReports.createMarginCalculationReportNode(runParameters.getReportNode(), network.getId()); + network.getVariantManager().setWorkingVariant(workingVariantId); + ExecutionEnvironment execEnv = new ExecutionEnvironment(Collections.emptyMap(), WORKING_DIR_PREFIX, config.isDebug()); + DynawoVersion currentVersion = DynawoUtil.requireDynaMinVersion(execEnv, runParameters.getComputationManager(), getVersionCommand(config), DYNAWO_LAUNCHER_PROGRAM_NAME, false); + MarginCalculationParameters parameters = runParameters.getMarginCalculationParameters(); + MarginCalculationContext context = new MarginCalculationContext(network, workingVariantId, + BlackBoxSupplierUtils.getBlackBoxModelList(dynamicModelsSupplier, network, mcReportNode), + parameters, + contingenciesProvider.getContingencies(network), + loadsVariationSupplier.getLoadsVariations(network, mcReportNode), + currentVersion, + mcReportNode); + + return runParameters.getComputationManager().execute(execEnv, + new MarginCalculationHandler(context, getCommand(config, "MC", "dynawo_dynamic_mc"), mcReportNode)); + } + + @Override + public String getName() { + return DynawoSimulationProvider.NAME; + } + + @Override + public String getVersion() { + return new PowsyblDynawoVersion().getMavenProjectVersion(); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationReports.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationReports.java new file mode 100644 index 000000000..de812b811 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationReports.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.report.TypedValue; + +/** + * @author Laurent Issertial {@literal } + */ +public final class MarginCalculationReports { + + private MarginCalculationReports() { + } + + public static ReportNode createMarginCalculationReportNode(ReportNode reportNode, String networkId) { + return reportNode.newReportNode() + .withMessageTemplate("mc", "Dynawo margin calculation on network '${networkId}'") + .withUntypedValue("networkId", networkId) + .add(); + } + + public static void reportLoadVariationInstantiationFailure(ReportNode reportNode) { + reportNode.newReportNode() + .withMessageTemplate("loadsVariationInstantiationError", "LoadVariation cannot be instantiated") + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationRunParameters.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationRunParameters.java new file mode 100644 index 000000000..87fbbf486 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/MarginCalculationRunParameters.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.computation.local.LocalComputationManager; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * @author Laurent Issertial {@literal } + */ +public class MarginCalculationRunParameters { + + private static final Supplier DEFAULT_MC_PARAMETERS_SUPPLIER = MarginCalculationParameters::load; + private static final Supplier DEFAULT_COMPUTATION_MANAGER_SUPPLIER = LocalComputationManager::getDefault; + + private MarginCalculationParameters marginCalculationParameters; + private ComputationManager computationManager; + private ReportNode reportNode = ReportNode.NO_OP; + + /** + * Returns a {@link MarginCalculationRunParameters} instance with default value on each field. + * @return the MarginCalculationRunParameters instance. + */ + public static MarginCalculationRunParameters getDefault() { + return new MarginCalculationRunParameters() + .setMarginCalculationParameters(DEFAULT_MC_PARAMETERS_SUPPLIER.get()) + .setComputationManager(DEFAULT_COMPUTATION_MANAGER_SUPPLIER.get()); + } + + /** + * {@link MarginCalculationParameters} getter
+ * If null, sets the field to its default value with {@link #DEFAULT_MC_PARAMETERS_SUPPLIER} before returning it. + */ + public MarginCalculationParameters getMarginCalculationParameters() { + if (marginCalculationParameters == null) { + setMarginCalculationParameters(DEFAULT_MC_PARAMETERS_SUPPLIER.get()); + } + return marginCalculationParameters; + } + + /** + * {@link ComputationManager} getter
+ * If null, sets the field to its default value with {@link #DEFAULT_COMPUTATION_MANAGER_SUPPLIER} before returning it. + */ + public ComputationManager getComputationManager() { + if (computationManager == null) { + setComputationManager(DEFAULT_COMPUTATION_MANAGER_SUPPLIER.get()); + } + return computationManager; + } + + public ReportNode getReportNode() { + return reportNode; + } + + /** + * Sets the security analysis parameters, see {@link MarginCalculationRunParameters}. + */ + public MarginCalculationRunParameters setMarginCalculationParameters(MarginCalculationParameters marginCalculationParameters) { + Objects.requireNonNull(marginCalculationParameters, "Margin calculation parameters should not be null"); + this.marginCalculationParameters = marginCalculationParameters; + return this; + } + + /** + * Sets the computationManager handling command execution. + */ + public MarginCalculationRunParameters setComputationManager(ComputationManager computationManager) { + Objects.requireNonNull(computationManager, "ComputationManager should not be null"); + this.computationManager = computationManager; + return this; + } + + /** + * Sets the reportNode used for functional logs, see {@link ReportNode} + */ + public MarginCalculationRunParameters setReportNode(ReportNode reportNode) { + Objects.requireNonNull(reportNode, "ReportNode should not be null"); + this.reportNode = reportNode; + return this; + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/JsonMarginCalculationParameters.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/JsonMarginCalculationParameters.java new file mode 100644 index 000000000..ccccf4e1a --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/JsonMarginCalculationParameters.java @@ -0,0 +1,85 @@ +/** + * 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.dynawo.margincalculation.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.powsybl.commons.json.JsonUtil; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +/** + * Provides methods to read and write MarginCalculationParameters from and to JSON. + * + * @author Laurent Issertial {@literal } + */ +public class JsonMarginCalculationParameters { + + /** + * Reads parameters from a JSON file (will NOT rely on platform config). + */ + public static MarginCalculationParameters read(Path jsonFile) { + Objects.requireNonNull(jsonFile); + + try (InputStream is = Files.newInputStream(jsonFile)) { + return read(is); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Reads parameters from a JSON file (will NOT rely on platform config). + */ + public static MarginCalculationParameters read(InputStream jsonStream) { + try { + ObjectMapper objectMapper = createObjectMapper(); + return objectMapper.readerFor(MarginCalculationParameters.class).readValue(jsonStream); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Writes parameters as JSON to a file. + */ + public static void write(MarginCalculationParameters parameters, Path jsonFile) { + Objects.requireNonNull(jsonFile); + + try (OutputStream outputStream = Files.newOutputStream(jsonFile)) { + write(parameters, outputStream); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Writes parameters as JSON to an output stream. + */ + public static void write(MarginCalculationParameters parameters, OutputStream outputStream) { + try { + ObjectMapper objectMapper = createObjectMapper(); + ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter(); + writer.writeValue(outputStream, parameters); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static ObjectMapper createObjectMapper() { + return JsonUtil.createObjectMapper() + .registerModule(new MarginCalculationParametersJsonModule()); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationParametersDeserializer.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationParametersDeserializer.java new file mode 100644 index 000000000..d90b49a72 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationParametersDeserializer.java @@ -0,0 +1,59 @@ +/** + * 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.dynawo.margincalculation.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.powsybl.dynawo.json.DynawoSimulationParametersSerializer; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; + +import java.io.IOException; + +/** + * @author Laurent Issertial {@literal } + */ +public class MarginCalculationParametersDeserializer extends StdDeserializer { + + MarginCalculationParametersDeserializer() { + super(MarginCalculationParameters.class); + } + + @Override + public MarginCalculationParameters deserialize(JsonParser parser, DeserializationContext context) throws IOException { + + MarginCalculationParameters.Builder builder = MarginCalculationParameters.builder(); + while (parser.nextToken() != JsonToken.END_OBJECT) { + switch (parser.currentName()) { + case "version" -> parser.nextToken(); + case "startTime" -> builder.setStartTime(parser.getValueAsDouble()); + case "stopTime" -> builder.setStopTime(parser.getValueAsDouble()); + case "marginCalculationStartTime" -> builder.setMarginCalculationStartTime(parser.getValueAsDouble()); + case "loadIncreaseStartTime" -> builder.setLoadIncreaseStartTime(parser.getValueAsDouble()); + case "loadIncreaseStopTime" -> builder.setLoadIncreaseStopTime(parser.getValueAsDouble()); + case "contingenciesStartTime" -> builder.setContingenciesStartTime(parser.getValueAsDouble()); + case "calculationType" -> { + parser.nextToken(); + builder.setCalculationType(parser.readValueAs(MarginCalculationParameters.CalculationType.class)); + } + case "accuracy" -> builder.setAccuracy(parser.getValueAsInt()); + case "loadModelsRule" -> { + parser.nextToken(); + builder.setLoadModelsRule(parser.readValueAs(MarginCalculationParameters.LoadModelsRule.class)); + } + case "dynawoParameters" -> { + parser.nextToken(); + builder.setDynawoParameters(new DynawoSimulationParametersSerializer().deserialize(parser, context)); + } + default -> throw new IllegalStateException("Unexpected field: " + parser.currentName()); + } + } + return builder.build(); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationParametersJsonModule.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationParametersJsonModule.java new file mode 100644 index 000000000..48ec02af8 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationParametersJsonModule.java @@ -0,0 +1,22 @@ +/** + * 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.dynawo.margincalculation.json; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; + +/** + * @author Laurent Issertial {@literal } + */ +public class MarginCalculationParametersJsonModule extends SimpleModule { + + public MarginCalculationParametersJsonModule() { + addDeserializer(MarginCalculationParameters.class, new MarginCalculationParametersDeserializer()); + addSerializer(MarginCalculationParameters.class, new MarginCalculationParametersSerializer()); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationParametersSerializer.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationParametersSerializer.java new file mode 100644 index 000000000..b36fca14b --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationParametersSerializer.java @@ -0,0 +1,43 @@ +/** + * 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.dynawo.margincalculation.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.powsybl.dynawo.json.DynawoSimulationParametersSerializer; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; + +import java.io.IOException; + +/** + * @author Laurent Issertial {@literal } + */ +public class MarginCalculationParametersSerializer extends StdSerializer { + + MarginCalculationParametersSerializer() { + super(MarginCalculationParameters.class); + } + + @Override + public void serialize(MarginCalculationParameters parameters, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeNumberField("startTime", parameters.getStartTime()); + jsonGenerator.writeNumberField("stopTime", parameters.getStopTime()); + jsonGenerator.writeNumberField("marginCalculationStartTime", parameters.getMarginCalculationStartTime()); + jsonGenerator.writeNumberField("loadIncreaseStartTime", parameters.getLoadIncreaseStartTime()); + jsonGenerator.writeNumberField("loadIncreaseStopTime", parameters.getLoadIncreaseStopTime()); + jsonGenerator.writeNumberField("contingenciesStartTime", parameters.getContingenciesStartTime()); + jsonGenerator.writeStringField("calculationType", parameters.getCalculationType().toString()); + jsonGenerator.writeNumberField("accuracy", parameters.getAccuracy()); + jsonGenerator.writeStringField("loadModelsRule", parameters.getLoadModelsRule().toString()); + jsonGenerator.writeFieldName("dynawoParameters"); + new DynawoSimulationParametersSerializer().serialize(parameters.getDynawoParameters(), jsonGenerator, serializerProvider); + jsonGenerator.writeEndObject(); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationResultDeserializer.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationResultDeserializer.java new file mode 100644 index 000000000..a7c8dcd26 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationResultDeserializer.java @@ -0,0 +1,144 @@ +/** + * 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.dynawo.margincalculation.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.powsybl.commons.json.JsonUtil; +import com.powsybl.dynaflow.results.FailedCriterion; +import com.powsybl.dynaflow.results.ScenarioResult; +import com.powsybl.dynaflow.results.Status; +import com.powsybl.dynawo.margincalculation.results.LoadIncreaseResult; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author Laurent Issertial {@literal } + */ +public class MarginCalculationResultDeserializer extends StdDeserializer { + + MarginCalculationResultDeserializer() { + super(MarginCalculationResult.class); + } + + public static MarginCalculationResult read(InputStream is) throws IOException { + Objects.requireNonNull(is); + ObjectMapper objectMapper = JsonUtil.createObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addDeserializer(MarginCalculationResult.class, new MarginCalculationResultDeserializer()); + objectMapper.registerModule(module); + return objectMapper.readValue(is, MarginCalculationResult.class); + } + + public static MarginCalculationResult read(Path jsonFile) { + Objects.requireNonNull(jsonFile); + try (InputStream is = Files.newInputStream(jsonFile)) { + return read(is); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public MarginCalculationResult deserialize(JsonParser parser, DeserializationContext ctx) throws IOException { + List loadIncreaseResults = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_OBJECT) { + switch (parser.currentName()) { + case "version" -> parser.nextToken(); // skip + case "loadIncreases" -> { + parser.nextToken(); + deserializeLoadIncreaseResults(parser, loadIncreaseResults); + } + default -> throw getUnexpectedFieldException(parser); + } + } + return new MarginCalculationResult(loadIncreaseResults); + } + + private void deserializeLoadIncreaseResults(JsonParser parser, List loadIncreaseResults) throws IOException { + while (parser.nextToken() != JsonToken.END_ARRAY) { + double loadLevel = 0; + Status status = null; + List failedCriteria = new ArrayList<>(); + List scenarioResults = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_OBJECT) { + switch (parser.currentName()) { + case "loadLevel" -> loadLevel = parser.getValueAsDouble(); + case "status" -> { + parser.nextToken(); + status = parser.readValueAs(Status.class); + } + case "failedCriteria" -> { + parser.nextToken(); + deserializeFailedCriteria(parser, failedCriteria); + } + case "scenarioResults" -> { + parser.nextToken(); + deserializeScenarioResults(parser, scenarioResults); + } + default -> throw getUnexpectedFieldException(parser); + } + } + loadIncreaseResults.add(new LoadIncreaseResult(loadLevel, status, scenarioResults, failedCriteria)); + } + } + + private void deserializeFailedCriteria(JsonParser parser, List failedCriteria) throws IOException { + while (parser.nextToken() != JsonToken.END_ARRAY) { + String description = null; + double time = 0; + while (parser.nextToken() != JsonToken.END_OBJECT) { + switch (parser.currentName()) { + case "description" -> description = parser.getValueAsString(); + case "time" -> time = parser.getValueAsDouble(); + default -> throw getUnexpectedFieldException(parser); + } + } + failedCriteria.add(new FailedCriterion(description, time)); + } + } + + private void deserializeScenarioResults(JsonParser parser, List scenarioResults) throws IOException { + while (parser.nextToken() != JsonToken.END_ARRAY) { + String id = null; + Status status = null; + List failedCriteria = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_OBJECT) { + switch (parser.currentName()) { + case "id" -> id = parser.getValueAsString(); + case "status" -> { + parser.nextToken(); + status = parser.readValueAs(Status.class); + } + case "failedCriteria" -> { + parser.nextToken(); + deserializeFailedCriteria(parser, failedCriteria); + } + default -> throw getUnexpectedFieldException(parser); + } + } + scenarioResults.add(new ScenarioResult(id, status, failedCriteria)); + } + } + + private static IllegalStateException getUnexpectedFieldException(JsonParser parser) throws IOException { + return new IllegalStateException("Unexpected field: " + parser.currentName()); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationResultSerializer.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationResultSerializer.java new file mode 100644 index 000000000..4432eb0e8 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationResultSerializer.java @@ -0,0 +1,106 @@ +/** + * 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.dynawo.margincalculation.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.powsybl.commons.json.JsonUtil; +import com.powsybl.dynaflow.results.FailedCriterion; +import com.powsybl.dynaflow.results.ScenarioResult; +import com.powsybl.dynamicsimulation.DynamicSimulationResult; +import com.powsybl.dynamicsimulation.json.DynamicSimulationResultSerializer; +import com.powsybl.dynawo.margincalculation.results.LoadIncreaseResult; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +/** + * @author Laurent Issertial {@literal } + */ +public class MarginCalculationResultSerializer extends StdSerializer { + + private static final String VERSION = "1.0"; + + MarginCalculationResultSerializer() { + super(MarginCalculationResult.class); + } + + public static void write(MarginCalculationResult result, Path jsonFile) { + Objects.requireNonNull(result); + Objects.requireNonNull(jsonFile); + try { + try (OutputStream os = Files.newOutputStream(jsonFile)) { + ObjectMapper objectMapper = JsonUtil.createObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addSerializer(MarginCalculationResult.class, new MarginCalculationResultSerializer()); + objectMapper.registerModule(module); + ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter(); + writer.writeValue(os, result); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void serialize(MarginCalculationResult result, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("version", VERSION); + jsonGenerator.writeFieldName("loadIncreases"); + jsonGenerator.writeStartArray(); + for (LoadIncreaseResult loadIncrease : result.getResults()) { + jsonGenerator.writeStartObject(); + jsonGenerator.writeNumberField("loadLevel", loadIncrease.loadLevel()); + jsonGenerator.writeStringField("status", loadIncrease.status().toString()); + writeFailedCriteria(loadIncrease.failedCriteria(), jsonGenerator); + jsonGenerator.writeFieldName("scenarioResults"); + jsonGenerator.writeStartArray(); + for (ScenarioResult scenario : loadIncrease.scenarioResults()) { + writeScenarioResult(scenario, jsonGenerator); + } + jsonGenerator.writeEndArray(); + jsonGenerator.writeEndObject(); + } + jsonGenerator.writeEndArray(); + jsonGenerator.writeEndObject(); + } + + private void writeFailedCriteria(List failedCriteria, JsonGenerator jsonGenerator) throws IOException { + jsonGenerator.writeFieldName("failedCriteria"); + jsonGenerator.writeStartArray(); + for (FailedCriterion criterion : failedCriteria) { + writeFailedCriterion(criterion, jsonGenerator); + } + jsonGenerator.writeEndArray(); + } + + private void writeFailedCriterion(FailedCriterion failedCriterion, JsonGenerator jsonGenerator) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("description", failedCriterion.description()); + jsonGenerator.writeNumberField("time", failedCriterion.time()); + jsonGenerator.writeEndObject(); + } + + private void writeScenarioResult(ScenarioResult scenarioResult, JsonGenerator jsonGenerator) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("id", scenarioResult.id()); + jsonGenerator.writeStringField("status", scenarioResult.status().toString()); + writeFailedCriteria(scenarioResult.failedCriteria(), jsonGenerator); + jsonGenerator.writeEndObject(); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/CalculatedLoadScalable.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/CalculatedLoadScalable.java new file mode 100644 index 000000000..04709879e --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/CalculatedLoadScalable.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.loadsvariation; + +import com.powsybl.iidm.modification.scalable.LoadScalable; +import com.powsybl.iidm.network.Load; + +/** + * The scaled P and Q are not set in the given load but kept in properties, contrary to {@link LoadScalable} + * @author Laurent Issertial {@literal } + */ +public class CalculatedLoadScalable extends LoadScalable implements CalculatedPower { + + private Double calculatedP0 = Double.NaN; + private Double calculatedQ0 = Double.NaN; + + CalculatedLoadScalable(String id) { + super(id); + } + + @Override + protected void setP0(Load l, double value) { + calculatedP0 = value; + } + + @Override + protected double getP0(Load l) { + return calculatedP0.isNaN() ? l.getP0() : calculatedP0; + } + + @Override + protected void setQ0(Load l, double value) { + calculatedQ0 = value; + } + + @Override + protected double getQ0(Load l) { + return calculatedQ0.isNaN() ? l.getQ0() : calculatedQ0; + } + + @Override + public Double getCalculatedP0() { + return calculatedP0; + } + + @Override + public Double getCalculatedQ0() { + return calculatedQ0; + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/CalculatedPower.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/CalculatedPower.java new file mode 100644 index 000000000..06379688e --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/CalculatedPower.java @@ -0,0 +1,18 @@ +/** + * 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.dynawo.margincalculation.loadsvariation; + +/** + * @author Laurent Issertial {@literal } + */ +public interface CalculatedPower { + + Double getCalculatedP0(); + + Double getCalculatedQ0(); +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadVariationAreaAutomationSystem.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadVariationAreaAutomationSystem.java new file mode 100644 index 000000000..7fd4e8e57 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadVariationAreaAutomationSystem.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.loadsvariation; + +import com.powsybl.dynawo.DynawoSimulationContext; +import com.powsybl.dynawo.builders.ModelConfig; +import com.powsybl.dynawo.models.AbstractPureDynamicBlackBoxModel; +import com.powsybl.dynawo.models.VarConnection; +import com.powsybl.dynawo.models.loads.DefaultControllableLoadModel; +import com.powsybl.dynawo.models.macroconnections.MacroConnectAttribute; +import com.powsybl.dynawo.models.macroconnections.MacroConnectionsAdder; +import com.powsybl.dynawo.parameters.ParametersSet; +import com.powsybl.iidm.network.Load; + +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static com.powsybl.dynawo.parameters.ParameterType.DOUBLE; +import static com.powsybl.dynawo.parameters.ParameterType.INT; + +/** + * @author Laurent Issertial {@literal } + */ +public class LoadVariationAreaAutomationSystem extends AbstractPureDynamicBlackBoxModel { + + private static final String ID = "LOAD_VARIATION_AREA"; + private static final String PAR_ID = "loadVarArea"; + private static final ModelConfig MODEL_CONFIG = new ModelConfig("DYNModelVariationArea"); + + private final List loadsVariations; + private final double loadIncreaseStartTime; + private final double loadIncreaseStopTime; + private final BiConsumer scalingConfig; + + public LoadVariationAreaAutomationSystem(List loadsVariations, double loadIncreaseStartTime, + double loadIncreaseStopTime, + BiConsumer scalingConfig) { + super(ID, PAR_ID, MODEL_CONFIG); + this.loadsVariations = Objects.requireNonNull(loadsVariations); + this.loadIncreaseStartTime = loadIncreaseStartTime; + this.loadIncreaseStopTime = loadIncreaseStopTime; + this.scalingConfig = scalingConfig; + } + + @Override + public void createMacroConnections(MacroConnectionsAdder adder) { + int index = 0; + for (LoadsVariation lv : loadsVariations) { + for (Load load : lv.loads()) { + adder.createMacroConnections(this, load, DefaultControllableLoadModel.class, this::getVarConnectionsWith, MacroConnectAttribute.ofIndex1(index)); + index++; + } + } + } + + private List getVarConnectionsWith(DefaultControllableLoadModel connected) { + return List.of(new VarConnection("DeltaPc_load_@INDEX@", connected.getDeltaPVarName()), + new VarConnection("DeltaQc_load_@INDEX@", connected.getDeltaQVarName())); + } + + @Override + public void createDynamicModelParameters(DynawoSimulationContext context, Consumer parametersAdder) { + ParametersSet paramSet = new ParametersSet(getParameterSetId()); + int index = 0; + for (LoadsVariation lv : loadsVariations) { + LoadsProportionalScalable proportionalScalable = new LoadsProportionalScalable(lv.loads()); + scalingConfig.accept(proportionalScalable, lv.variationValue()); + for (CalculatedPower load : proportionalScalable.getLoadScalable()) { + paramSet.addParameter("deltaP_load_" + index, DOUBLE, String.valueOf(load.getCalculatedP0())); + paramSet.addParameter("deltaQ_load_" + index, DOUBLE, String.valueOf(load.getCalculatedQ0())); + index++; + } + } + paramSet.addParameter("nbLoads", INT, String.valueOf(index)); + paramSet.addParameter("startTime", DOUBLE, String.valueOf(loadIncreaseStartTime)); + paramSet.addParameter("stopTime", DOUBLE, String.valueOf(loadIncreaseStopTime)); + parametersAdder.accept(paramSet); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadsProportionalScalable.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadsProportionalScalable.java new file mode 100644 index 000000000..6ddac163d --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadsProportionalScalable.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.loadsvariation; + +import com.powsybl.iidm.modification.scalable.ProportionalScalable; +import com.powsybl.iidm.network.Load; + +import java.util.List; + +import static com.powsybl.iidm.modification.scalable.ProportionalScalable.DistributionMode.PROPORTIONAL_TO_P0; + +/** + * @author Laurent Issertial {@literal } + */ +public class LoadsProportionalScalable extends ProportionalScalable { + + public LoadsProportionalScalable(List loads) { + super(loads, i -> + new CalculatedLoadScalable(i.getId()), PROPORTIONAL_TO_P0, -Double.MAX_VALUE, Double.MAX_VALUE); + } + + public List getLoadScalable() { + return getScalables().stream().map(CalculatedPower.class::cast).toList(); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadsVariation.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadsVariation.java new file mode 100644 index 000000000..f381d726a --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadsVariation.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.loadsvariation; + +import com.powsybl.iidm.network.Load; + +import java.util.List; + +/** + * @author Laurent Issertial + */ +public record LoadsVariation(List loads, double variationValue) { +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadsVariationBuilder.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadsVariationBuilder.java new file mode 100644 index 000000000..6bc2d3097 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/LoadsVariationBuilder.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.loadsvariation; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.dynawo.builders.BuilderEquipmentsList; +import com.powsybl.dynawo.builders.BuilderReports; +import com.powsybl.dynawo.margincalculation.MarginCalculationReports; +import com.powsybl.iidm.network.IdentifiableType; +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; + +import java.util.Collection; +import java.util.Objects; + +/** + * @author Laurent Issertial {@literal } + */ +public class LoadsVariationBuilder { + + private final Network network; + private final ReportNode reportNode; + private boolean isInstantiable = true; + + private final BuilderEquipmentsList loads; + private double variationValue = Double.NaN; + + public LoadsVariationBuilder(Network network, ReportNode reportNode) { + this.network = Objects.requireNonNull(network); + this.reportNode = Objects.requireNonNull(reportNode); + this.loads = new BuilderEquipmentsList<>(IdentifiableType.LOAD, "loads"); + } + + public LoadsVariationBuilder loads(String... loadIds) { + loads.addEquipments(loadIds, this::getConnectedLoad); + return this; + } + + public LoadsVariationBuilder loads(Collection loadIds) { + loads.addEquipments(loadIds, this::getConnectedLoad); + return this; + } + + private Load getConnectedLoad(String loadId) { + Load load = network.getLoad(loadId); + return load != null && load.getTerminal().isConnected() ? load : null; + } + + public LoadsVariationBuilder variationValue(double variationValue) { + this.variationValue = variationValue; + return this; + } + + protected void checkData() { + isInstantiable = loads.checkEquipmentData(reportNode); + if (Double.isNaN(variationValue)) { + BuilderReports.reportFieldNotSet(reportNode, "variationValue"); + isInstantiable = false; + } + } + + private boolean isInstantiable() { + checkData(); + if (!isInstantiable) { + MarginCalculationReports.reportLoadVariationInstantiationFailure(reportNode); + } + return isInstantiable; + } + + public LoadsVariation build() { + return isInstantiable() ? new LoadsVariation(loads.getEquipments(), variationValue) : null; + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationJsonDeserializer.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationJsonDeserializer.java new file mode 100644 index 000000000..2c2d67b2b --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationJsonDeserializer.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.loadsvariation.supplier; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.powsybl.commons.json.JsonUtil; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariationBuilder; +import com.powsybl.dynawo.suppliers.SupplierJsonDeserializer; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * @author Laurent Issertial {@literal } + */ +public class LoadsVariationJsonDeserializer extends StdDeserializer> { + + private final transient Supplier builderConstructor; + + public LoadsVariationJsonDeserializer(Supplier builderConstructor) { + super(List.class); + this.builderConstructor = builderConstructor; + } + + @Override + public List deserialize(JsonParser parser, DeserializationContext context) { + List modelConfigList = new ArrayList<>(); + JsonUtil.parseObject(parser, name -> { + if (name.equals("variations")) { + JsonUtil.parseObjectArray(parser, modelConfigList::add, this::parseLoadsVariationBuilder); + return true; + } + return false; + }); + return modelConfigList.stream() + .map(LoadsVariationBuilder::build) + .filter(Objects::nonNull) + .toList(); + } + + private LoadsVariationBuilder parseLoadsVariationBuilder(JsonParser parser) { + LoadsVariationBuilder loadsVariationBuilder = builderConstructor.get(); + JsonUtil.parseObject(parser, name -> { + boolean handled = true; + switch (name) { + case "loadsIds" -> loadsVariationBuilder.loads(JsonUtil.parseStringArray(parser)); + case "variationValue" -> { + parser.nextToken(); + loadsVariationBuilder.variationValue(parser.getDoubleValue()); + } + default -> handled = false; + } + return handled; + }); + return loadsVariationBuilder; + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationSupplier.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationSupplier.java new file mode 100644 index 000000000..494f45232 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationSupplier.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.loadsvariation.supplier; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.iidm.network.Network; + +import java.util.List; + +/** + * @author Laurent Issertial {@literal } + */ +public interface LoadsVariationSupplier { + + List getLoadsVariations(Network network, ReportNode reportNode); + + default List getLoadsVariations(Network network) { + return getLoadsVariations(network, ReportNode.NO_OP); + } + +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationSupplierUtils.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationSupplierUtils.java new file mode 100644 index 000000000..41dcf9f60 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationSupplierUtils.java @@ -0,0 +1,25 @@ +/** + * 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.dynawo.margincalculation.loadsvariation.supplier; + +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariationBuilder; +import com.powsybl.dynawo.suppliers.SupplierJsonDeserializer; + +import java.nio.file.Path; + +/** + * @author Laurent Issertial {@literal } + */ +public class LoadsVariationSupplierUtils { + + public static LoadsVariationSupplier getLoadsVariationSupplierForJson(Path loadVariationsPath) { + return (n, r) -> new SupplierJsonDeserializer<>( + new LoadsVariationJsonDeserializer(() -> new LoadsVariationBuilder(n, r))) + .deserialize(loadVariationsPath); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/LoadIncreaseResult.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/LoadIncreaseResult.java new file mode 100644 index 000000000..8e7976d58 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/LoadIncreaseResult.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.results; + +import com.powsybl.dynaflow.results.FailedCriterion; +import com.powsybl.dynaflow.results.ScenarioResult; +import com.powsybl.dynaflow.results.Status; + +import java.util.Collections; +import java.util.List; + +/** + * @author Laurent Issertial {@literal } + */ +public record LoadIncreaseResult(double loadLevel, Status status, + List scenarioResults, List failedCriteria) { + + public LoadIncreaseResult(double loadLevel, Status status, List scenarioResults) { + this(loadLevel, status, scenarioResults, Collections.emptyList()); + } + + public LoadIncreaseResult(double loadLevel, Status status) { + this(loadLevel, status, Collections.emptyList(), Collections.emptyList()); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/LoadIncreaseResultsUtil.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/LoadIncreaseResultsUtil.java new file mode 100644 index 000000000..88536b66f --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/LoadIncreaseResultsUtil.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.results; + +import com.powsybl.dynaflow.results.FailedCriterion; +import com.powsybl.dynaflow.results.ScenarioResult; +import com.powsybl.dynaflow.results.Status; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Optional; + +/** + * @author Laurent Issertial {@literal } + */ +public final class LoadIncreaseResultsUtil { + + private LoadIncreaseResultsUtil() { + } + + private static final Logger LOGGER = LoggerFactory.getLogger(LoadIncreaseResultsUtil.class); + + static Optional createLoadIncreaseResult(String loadLevel, String status, List scenarioResults, + List failedCriteria) { + if (loadLevel == null || status == null || failedCriteria == null || scenarioResults == null) { + LOGGER.warn("Inconsistent load increase result entry (loadLevel: '{}', status: '{}', scenarioResults: '{}', failedCriteria: '{}')", + loadLevel, status, scenarioResults, failedCriteria); + } else { + try { + double loadLevelD = Double.parseDouble(loadLevel); + Status statusE = Status.valueOf(status); + switch (statusE) { + case CONVERGENCE -> { + if (scenarioResults.isEmpty()) { + LOGGER.warn("LoadIncreaseResult with {} status should have scenario results", status); + return Optional.empty(); + } + if (!failedCriteria.isEmpty()) { + LOGGER.warn("LoadIncreaseResult with {} status should not have failed criteria", status); + return Optional.empty(); + } + } + case CRITERIA_NON_RESPECTED -> { + if (!scenarioResults.isEmpty()) { + LOGGER.warn("LoadIncreaseResult with {} status should not have scenario results", status); + return Optional.empty(); + } + if (failedCriteria.isEmpty()) { + LOGGER.warn("LoadIncreaseResult with {} status should have failed criteria", status); + return Optional.empty(); + } + } + case DIVERGENCE, EXECUTION_PROBLEM -> { + if (!scenarioResults.isEmpty()) { + LOGGER.warn("LoadIncreaseResult with {} status should not have scenario results", status); + return Optional.empty(); + } + if (!failedCriteria.isEmpty()) { + LOGGER.warn("LoadIncreaseResult with {} status should not have failed criteria", status); + return Optional.empty(); + } + } + } + return Optional.of(new LoadIncreaseResult(loadLevelD, statusE, scenarioResults, failedCriteria)); + } catch (NumberFormatException e) { + logInconsistentEntry("loadLevel", loadLevel); + } catch (IllegalArgumentException e) { + logInconsistentEntry("status", status); + } + } + return Optional.empty(); + } + + private static void logInconsistentEntry(String fieldName, String message) { + LOGGER.warn("Inconsistent {} entry '{}'", fieldName, message); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/MarginCalculationResult.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/MarginCalculationResult.java new file mode 100644 index 000000000..49255e9c2 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/MarginCalculationResult.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.results; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * @author Laurent Issertial {@literal } + */ +public class MarginCalculationResult { + + private final List loadIncreaseResults; + //TODO remove ? + private byte[] logBytes; + + public static MarginCalculationResult empty() { + return new MarginCalculationResult(Collections.emptyList()); + } + + public MarginCalculationResult(List loadIncreaseResults) { + this.loadIncreaseResults = Objects.requireNonNull(loadIncreaseResults); + } + + public List getResults() { + return loadIncreaseResults; + } + + /** + * Gets log file in bytes. + * @return an Optional describing the zip bytes + */ + public Optional getLogBytes() { + return Optional.ofNullable(logBytes); + } + + public MarginCalculationResult setLogBytes(byte[] logBytes) { + this.logBytes = logBytes; + return this; + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/XmlMarginCalculationResultParser.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/XmlMarginCalculationResultParser.java new file mode 100644 index 000000000..bb0d87214 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/results/XmlMarginCalculationResultParser.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.results; + +import com.powsybl.commons.xml.XmlUtil; +import com.powsybl.dynaflow.results.AbstractXmlAggregatedResultParser; +import com.powsybl.dynaflow.results.FailedCriterion; +import com.powsybl.dynaflow.results.ScenarioResult; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * @author Laurent Issertial {@literal } + */ +public final class XmlMarginCalculationResultParser extends AbstractXmlAggregatedResultParser { + + private static final String LOAD_LEVEL = "loadLevel"; + + @Override + protected void read(XMLStreamReader xmlReader, Consumer consumer) throws XMLStreamException { + int state = xmlReader.next(); + while (state == XMLStreamConstants.COMMENT) { + state = xmlReader.next(); + } + XmlUtil.readSubElements(xmlReader, elementName -> readLoadIncreaseResult(elementName, xmlReader, consumer)); + } + + private static void readLoadIncreaseResult(String elementName, XMLStreamReader xmlReader, Consumer resultConsumer) { + if (elementName.equals("loadIncreaseResults")) { + String loadLevel = xmlReader.getAttributeValue(null, LOAD_LEVEL); + String status = xmlReader.getAttributeValue(null, STATUS); + List scenarioResults = new ArrayList<>(); + List failedCriteria = new ArrayList<>(); + XmlUtil.readSubElements(xmlReader, subElementName -> readLoadIncreaseResultSubElements(subElementName, xmlReader, scenarioResults::add, failedCriteria::add)); + LoadIncreaseResultsUtil.createLoadIncreaseResult(loadLevel, status, scenarioResults, failedCriteria).ifPresent(resultConsumer); + } + } + + private static void readLoadIncreaseResultSubElements(String elementName, XMLStreamReader xmlReader, Consumer scenarioConsumer, Consumer criterionConsumer) { + if (!readScenarioResult(elementName, xmlReader, scenarioConsumer)) { + readFailedCriterion(elementName, xmlReader, criterionConsumer); + } + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/xml/MarginCalculationConstant.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/xml/MarginCalculationConstant.java new file mode 100644 index 000000000..945ec7e50 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/xml/MarginCalculationConstant.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.xml; + +/** + * @author Laurent Issertial + */ +public final class MarginCalculationConstant { + + public static final String LOAD_VARIATION_AREA_FILENAME = "load_variation_area.dyd"; + + private MarginCalculationConstant() { + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/xml/MultipleJobsXml.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/xml/MultipleJobsXml.java new file mode 100644 index 000000000..d3d9ff0a1 --- /dev/null +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/margincalculation/xml/MultipleJobsXml.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2023, 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.dynawo.margincalculation.xml; + +import com.powsybl.dynawo.algorithms.xml.XmlUtil; +import com.powsybl.dynawo.margincalculation.MarginCalculationContext; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.algorithms.ContingencyEventModels; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +import static com.powsybl.dynawo.DynawoSimulationConstants.JOBS_FILENAME; +import static com.powsybl.dynawo.DynawoSimulationConstants.PHASE_2_JOBS_FILENAME; +import static com.powsybl.dynawo.algorithms.xml.AlgorithmsConstants.MULTIPLE_JOBS_FILENAME; +import static com.powsybl.dynawo.margincalculation.xml.MarginCalculationConstant.LOAD_VARIATION_AREA_FILENAME; + +/** + * @author Laurent Issertial + */ +public final class MultipleJobsXml { + + private MultipleJobsXml() { + } + + public static void write(Path workingDir, MarginCalculationContext context) throws IOException, XMLStreamException { + Objects.requireNonNull(workingDir); + Path file = workingDir.resolve(MULTIPLE_JOBS_FILENAME); + XmlUtil.write(file, "multipleJobs", w -> writeMarginCalculation(w, context)); + } + + private static void writeMarginCalculation(XMLStreamWriter writer, MarginCalculationContext context) { + try { + MarginCalculationParameters parameters = context.getMarginCalculationParameters(); + writer.writeStartElement("marginCalculation"); + writer.writeAttribute("calculationType", parameters.getCalculationType().toString()); + writer.writeAttribute("accuracy", Integer.toString(parameters.getAccuracy())); + writeScenarios(writer, context.getContingencyEventModels()); + writer.writeEmptyElement("loadIncrease"); + writer.writeAttribute("id", LOAD_VARIATION_AREA_FILENAME); + writer.writeAttribute("jobsFile", JOBS_FILENAME); + writer.writeEndElement(); + } catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + + private static void writeScenarios(XMLStreamWriter writer, List models) throws XMLStreamException { + writer.writeStartElement("scenarios"); + writer.writeAttribute("jobsFile", PHASE_2_JOBS_FILENAME); + for (ContingencyEventModels model : models) { + writeScenario(writer, model.getId()); + } + writer.writeEndElement(); + } + + private static void writeScenario(XMLStreamWriter writer, String id) throws XMLStreamException { + writer.writeEmptyElement("scenario"); + writer.writeAttribute("id", id); + writer.writeAttribute("dydFile", id + ".dyd"); + } +} diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynamicSecurityAnalysisReports.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynamicSecurityAnalysisReports.java index ed00ca1da..591141ef3 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynamicSecurityAnalysisReports.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynamicSecurityAnalysisReports.java @@ -23,12 +23,4 @@ public static ReportNode createDynamicSecurityAnalysisReportNode(ReportNode repo .withUntypedValue("networkId", networkId) .add(); } - - public static ReportNode createContingencyVoltageIdNotFoundReportNode(ReportNode reportNode, String contingencyId, String voltageLevelId) { - return reportNode.newReportNode() - .withMessageTemplate("contingencyVlIdNotFound", "Voltage id '${voltageLevelId}' of contingency '${contingencyId}' not found, contingency will be skipped") - .withUntypedValue("voltageLevelId", voltageLevelId) - .withUntypedValue("contingencyId", contingencyId) - .add(); - } } diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoSecurityAnalysisHandler.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoSecurityAnalysisHandler.java index af8568ef0..a5d915e0c 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoSecurityAnalysisHandler.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoSecurityAnalysisHandler.java @@ -7,20 +7,15 @@ */ package com.powsybl.dynawo.security; -import com.powsybl.commons.exceptions.UncheckedXmlStreamException; import com.powsybl.commons.report.ReportNode; -import com.powsybl.computation.AbstractExecutionHandler; import com.powsybl.computation.Command; -import com.powsybl.computation.CommandExecution; import com.powsybl.computation.ExecutionReport; import com.powsybl.dynaflow.results.ContingencyResultsUtils; -import com.powsybl.dynawo.DynawoFilesUtils; -import com.powsybl.dynawo.commons.DynawoUtil; +import com.powsybl.dynawo.algorithms.AbstractDynawoAlgorithmsHandler; +import com.powsybl.dynawo.algorithms.xml.ContingenciesDydXml; +import com.powsybl.dynawo.algorithms.xml.ContingenciesParXml; import com.powsybl.dynawo.commons.NetworkResultsUpdater; -import com.powsybl.dynawo.security.xml.ContingenciesDydXml; -import com.powsybl.dynawo.security.xml.ContingenciesParXml; import com.powsybl.dynawo.security.xml.MultipleJobsXml; -import com.powsybl.iidm.network.Network; import com.powsybl.iidm.serde.NetworkSerDe; import com.powsybl.security.LimitViolationFilter; import com.powsybl.security.SecurityAnalysisReport; @@ -28,45 +23,27 @@ import javax.xml.stream.XMLStreamException; import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import static com.powsybl.dynaflow.results.ContingencyResultsUtils.createSecurityAnalysisResult; -import static com.powsybl.dynawo.DynawoFilesUtils.deleteExistingFile; import static com.powsybl.dynawo.commons.DynawoConstants.*; -import static com.powsybl.dynawo.commons.DynawoUtil.getCommandExecutions; /** * @author Laurent Issertial */ -public final class DynawoSecurityAnalysisHandler extends AbstractExecutionHandler { +public final class DynawoSecurityAnalysisHandler extends AbstractDynawoAlgorithmsHandler { - private final SecurityAnalysisContext context; - private final Command command; - private final Network network; private final LimitViolationFilter violationFilter; private final List interceptors; - private final ReportNode reportNode; public DynawoSecurityAnalysisHandler(SecurityAnalysisContext context, Command command, LimitViolationFilter violationFilter, List interceptors, ReportNode reportNode) { - this.context = context; - this.network = context.getNetwork(); - this.command = command; + super(context, command, reportNode); this.violationFilter = violationFilter; this.interceptors = interceptors; - this.reportNode = reportNode; - } - - @Override - public List before(Path workingDir) throws IOException { - network.getVariantManager().setWorkingVariant(context.getWorkingVariantId()); - deleteExistingFile(workingDir.resolve(OUTPUTS_FOLDER), FINAL_STATE_FOLDER, OUTPUT_IIDM_FILENAME); - writeInputFiles(workingDir); - return getCommandExecutions(command); } @Override @@ -81,17 +58,10 @@ public SecurityAnalysisReport after(Path workingDir, ExecutionReport report) thr return new SecurityAnalysisReport(createSecurityAnalysisResult(network, violationFilter, workingDir, context.getContingencies())); } - private void writeInputFiles(Path workingDir) { - try { - DynawoUtil.writeIidm(network, workingDir.resolve(NETWORK_FILENAME)); - DynawoFilesUtils.writeInputFiles(workingDir, context); - MultipleJobsXml.write(workingDir, context); - ContingenciesDydXml.write(workingDir, context); - ContingenciesParXml.write(workingDir, context); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (XMLStreamException e) { - throw new UncheckedXmlStreamException(e); - } + @Override + protected void writeMultipleJobs(Path workingDir) throws XMLStreamException, IOException { + MultipleJobsXml.write(workingDir, context); + ContingenciesDydXml.write(workingDir, context.getContingencyEventModels()); + ContingenciesParXml.write(workingDir, context.getContingencyEventModels()); } } diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoSecurityAnalysisProvider.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoSecurityAnalysisProvider.java index 273bc7817..4b049e528 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoSecurityAnalysisProvider.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/DynawoSecurityAnalysisProvider.java @@ -18,9 +18,9 @@ import com.powsybl.dynamicsimulation.DynamicModelsSupplier; import com.powsybl.dynawo.DynawoSimulationParameters; import com.powsybl.dynawo.DynawoSimulationProvider; +import com.powsybl.dynawo.algorithms.DynawoAlgorithmsConfig; import com.powsybl.dynawo.commons.DynawoVersion; import com.powsybl.dynawo.models.utils.BlackBoxSupplierUtils; -import com.powsybl.dynawo.DynawoSimulationConstants; import com.powsybl.dynawo.commons.DynawoUtil; import com.powsybl.dynawo.commons.PowsyblDynawoVersion; import com.powsybl.iidm.network.Network; @@ -38,6 +38,8 @@ import java.util.concurrent.CompletableFuture; import static com.powsybl.dynawo.DynawoSimulationConfig.DYNAWO_LAUNCHER_PROGRAM_NAME; +import static com.powsybl.dynawo.algorithms.xml.AlgorithmsConstants.MULTIPLE_JOBS_FILENAME; +import static com.powsybl.dynawo.commons.DynawoConstants.AGGREGATED_RESULTS; /** * @author Laurent Issertial @@ -88,7 +90,8 @@ public CompletableFuture run(Network network, String wor parameters, DynawoSimulationParameters.load(parameters.getDynamicSimulationParameters()), contingencies, - currentVersion); + currentVersion, + dsaReportNode); return runParameters.getComputationManager().execute(execEnv, new DynawoSecurityAnalysisHandler(context, getCommand(config), runParameters.getFilter(), runParameters.getInterceptors(), dsaReportNode)); } @@ -106,8 +109,8 @@ public String getVersion() { public static Command getCommand(DynawoAlgorithmsConfig config) { List args = Arrays.asList( "SA", - "--input", DynawoSimulationConstants.MULTIPLE_JOBS_FILENAME, - "--output", DynawoSimulationConstants.AGGREGATED_RESULTS); + "--input", MULTIPLE_JOBS_FILENAME, + "--output", AGGREGATED_RESULTS); return new SimpleCommandBuilder() .id("dynawo_dynamic_sa") .program(config.getProgram()) diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/SecurityAnalysisContext.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/SecurityAnalysisContext.java index ffb0ea57c..d3b60f74a 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/SecurityAnalysisContext.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/SecurityAnalysisContext.java @@ -11,6 +11,8 @@ import com.powsybl.contingency.Contingency; import com.powsybl.dynawo.DynawoSimulationContext; import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.algorithms.ContingencyEventModels; +import com.powsybl.dynawo.algorithms.ContingencyEventModelsFactory; import com.powsybl.dynawo.commons.DynawoConstants; import com.powsybl.dynawo.commons.DynawoVersion; import com.powsybl.dynawo.models.BlackBoxModel; @@ -32,7 +34,7 @@ public SecurityAnalysisContext(Network network, String workingVariantId, DynamicSecurityAnalysisParameters parameters, DynawoSimulationParameters dynawoSimulationParameters, List contingencies) { - this(network, workingVariantId, dynamicModels, parameters, dynawoSimulationParameters, contingencies, DynawoConstants.VERSION_MIN); + this(network, workingVariantId, dynamicModels, parameters, dynawoSimulationParameters, contingencies, DynawoConstants.VERSION_MIN, ReportNode.NO_OP); } public SecurityAnalysisContext(Network network, String workingVariantId, @@ -40,12 +42,13 @@ public SecurityAnalysisContext(Network network, String workingVariantId, DynamicSecurityAnalysisParameters parameters, DynawoSimulationParameters dynawoSimulationParameters, List contingencies, - DynawoVersion currentVersion) { + DynawoVersion currentVersion, + ReportNode reportNode) { super(network, workingVariantId, dynamicModels, List.of(), Collections.emptyList(), - parameters.getDynamicSimulationParameters(), dynawoSimulationParameters, currentVersion, ReportNode.NO_OP); + parameters.getDynamicSimulationParameters(), dynawoSimulationParameters, currentVersion, reportNode); double contingenciesStartTime = parameters.getDynamicContingenciesParameters().getContingenciesStartTime(); this.contingencies = contingencies; - this.contingencyEventModels = ContingencyEventModelsFactory.createFrom(contingencies, this, macroConnectionsAdder, contingenciesStartTime, ReportNode.NO_OP); + this.contingencyEventModels = ContingencyEventModelsFactory.createFrom(contingencies, this, macroConnectionsAdder, contingenciesStartTime, reportNode); } public List getContingencies() { diff --git a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/MultipleJobsXml.java b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/MultipleJobsXml.java index 8bf978e69..3e66064d0 100644 --- a/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/MultipleJobsXml.java +++ b/dynawo-security-analysis/src/main/java/com/powsybl/dynawo/security/xml/MultipleJobsXml.java @@ -7,7 +7,8 @@ */ package com.powsybl.dynawo.security.xml; -import com.powsybl.dynawo.security.ContingencyEventModels; +import com.powsybl.dynawo.algorithms.xml.XmlUtil; +import com.powsybl.dynawo.algorithms.ContingencyEventModels; import com.powsybl.dynawo.security.SecurityAnalysisContext; import javax.xml.stream.XMLStreamException; @@ -17,7 +18,7 @@ import java.util.Objects; import static com.powsybl.dynawo.DynawoSimulationConstants.JOBS_FILENAME; -import static com.powsybl.dynawo.DynawoSimulationConstants.MULTIPLE_JOBS_FILENAME; +import static com.powsybl.dynawo.algorithms.xml.AlgorithmsConstants.MULTIPLE_JOBS_FILENAME; /** * @author Laurent Issertial @@ -30,17 +31,20 @@ private MultipleJobsXml() { public static void write(Path workingDir, SecurityAnalysisContext context) throws IOException, XMLStreamException { Objects.requireNonNull(workingDir); Path file = workingDir.resolve(MULTIPLE_JOBS_FILENAME); - - XmlUtil.write(file, context, "multipleJobs", MultipleJobsXml::writeContingencies); + XmlUtil.write(file, "multipleJobs", w -> writeContingencies(w, context)); } - private static void writeContingencies(XMLStreamWriter writer, SecurityAnalysisContext context) throws XMLStreamException { - writer.writeStartElement("scenarios"); - writer.writeAttribute("jobsFile", JOBS_FILENAME); - for (ContingencyEventModels model : context.getContingencyEventModels()) { - writeScenario(writer, model.getId()); + private static void writeContingencies(XMLStreamWriter writer, SecurityAnalysisContext context) { + try { + writer.writeStartElement("scenarios"); + writer.writeAttribute("jobsFile", JOBS_FILENAME); + for (ContingencyEventModels model : context.getContingencyEventModels()) { + writeScenario(writer, model.getId()); + } + writer.writeEndElement(); + } catch (XMLStreamException e) { + throw new RuntimeException(e); } - writer.writeEndElement(); } private static void writeScenario(XMLStreamWriter writer, String id) throws XMLStreamException { diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/xml/ContingenciesXmlTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/algorithms/ContingenciesXmlTest.java similarity index 87% rename from dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/xml/ContingenciesXmlTest.java rename to dynawo-security-analysis/src/test/java/com/powsybl/dynawo/algorithms/ContingenciesXmlTest.java index 39a44c951..fafecfdc3 100644 --- a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/xml/ContingenciesXmlTest.java +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/algorithms/ContingenciesXmlTest.java @@ -5,10 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dynawo.security.xml; +package com.powsybl.dynawo.algorithms; import com.powsybl.contingency.Contingency; import com.powsybl.dynamicsimulation.DynamicSimulationParameters; +import com.powsybl.dynawo.algorithms.xml.ContingenciesDydXml; +import com.powsybl.dynawo.algorithms.xml.ContingenciesParXml; import com.powsybl.dynawo.xml.DynawoTestUtil; import com.powsybl.dynawo.DynawoSimulationParameters; import com.powsybl.dynawo.security.SecurityAnalysisContext; @@ -39,12 +41,11 @@ void writeDyds() throws SAXException, IOException, XMLStreamException { .build()); SecurityAnalysisContext context = new SecurityAnalysisContext(network, network.getVariantManager().getWorkingVariantId(), dynamicModels, parameters, dynawoSimulationParameters, contingencies); - ContingenciesDydXml.write(tmpDir, context); - ContingenciesParXml.write(tmpDir, context); + ContingenciesDydXml.write(tmpDir, context.getContingencyEventModels()); + ContingenciesParXml.write(tmpDir, context.getContingencyEventModels()); validate("dyd.xsd", "LOAD.xml", tmpDir.resolve("LOAD.dyd")); validate("dyd.xsd", "DisconnectLineGenerator.xml", tmpDir.resolve("DisconnectLineGenerator.dyd")); validate("parameters.xsd", "LOAD_par.xml", tmpDir.resolve("LOAD.par")); validate("parameters.xsd", "DisconnectLineGenerator_par.xml", tmpDir.resolve("DisconnectLineGenerator.par")); } - } diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/ContingencyEventModelsTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/algorithms/ContingencyEventModelsTest.java similarity index 96% rename from dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/ContingencyEventModelsTest.java rename to dynawo-security-analysis/src/test/java/com/powsybl/dynawo/algorithms/ContingencyEventModelsTest.java index d046092b8..d6a3104f6 100644 --- a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/ContingencyEventModelsTest.java +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/algorithms/ContingencyEventModelsTest.java @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dynawo.security; +package com.powsybl.dynawo.algorithms; import com.powsybl.commons.report.ReportNode; import com.powsybl.contingency.Contingency; @@ -51,7 +51,8 @@ void test() { Contingency.load("LOAD"), Contingency.generator("GEN"), Contingency.line(NHV1_NHV2_1, VLHV1), - Contingency.branch(NHV1_NHV2_2, "WRONG_ID")); + Contingency.branch(NHV1_NHV2_2, "WRONG_ID"), + Contingency.battery("BATTERY")); List contingencyEvents = ContingencyEventModelsFactory.createFrom(contingencies, context, macroConnectionsAdder, 2, ReportNode.NO_OP); diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/DynawoAlgorithmsConfigTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsConfigTest.java similarity index 97% rename from dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/DynawoAlgorithmsConfigTest.java rename to dynawo-security-analysis/src/test/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsConfigTest.java index 939abbc89..0341b1dab 100644 --- a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/DynawoAlgorithmsConfigTest.java +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/algorithms/DynawoAlgorithmsConfigTest.java @@ -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/. */ -package com.powsybl.dynawo.security; +package com.powsybl.dynawo.algorithms; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/MarginCalculationParametersTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/MarginCalculationParametersTest.java new file mode 100644 index 000000000..507a4aa37 --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/MarginCalculationParametersTest.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author Laurent Issertial + */ +public class MarginCalculationParametersTest { + + @ParameterizedTest(name = "{6}") + @MethodSource("provideMarginCalculationTimes") + void testExceptions(double startTime, double stopTime, double marginCalculationStartTime, double loadIncreaseStartTime, + double loadIncreaseStopTime, double contingenciesStartTime, String message) { + MarginCalculationParameters.Builder builder = MarginCalculationParameters.builder() + .setStartTime(startTime) + .setStopTime(stopTime) + .setMarginCalculationStartTime(marginCalculationStartTime) + .setLoadIncreaseStartTime(loadIncreaseStartTime) + .setLoadIncreaseStopTime(loadIncreaseStopTime) + .setContingenciesStartTime(contingenciesStartTime); + Exception e = assertThrows(IllegalStateException.class, builder::build); + assertEquals(message, e.getMessage()); + } + + private static Stream provideMarginCalculationTimes() { + return Stream.of( + Arguments.of(-1, 200, 100, 20, 70, 150, "Start time should be zero or positive"), + Arguments.of(10, 5, 100, 20, 70, 150, "Stop time should be greater than start time"), + Arguments.of(10, 200, 5, 20, 70, 150, "Margin calculation start time should be between start and stop time"), + Arguments.of(10, 200, 205, 20, 70, 150, "Margin calculation start time should be between start and stop time"), + Arguments.of(10, 200, 100, 20, 70, 90, "Contingencies start time should be between margin calculation start time and stop time"), + Arguments.of(10, 200, 100, 20, 70, 210, "Contingencies start time should be between margin calculation start time and stop time"), + Arguments.of(10, 200, 100, 5, 70, 150, "Load increase start time should be greater start time"), + Arguments.of(10, 200, 100, 20, 15, 150, "Load increase stop time should be between load increase start time and margin calculation start time"), + Arguments.of(10, 200, 100, 20, 110, 150, "Load increase stop time should be between load increase start time and margin calculation start time") + ); + } +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/json/JsonMarginCalculationParametersTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/json/JsonMarginCalculationParametersTest.java new file mode 100644 index 000000000..10672f808 --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/json/JsonMarginCalculationParametersTest.java @@ -0,0 +1,68 @@ +/** + * 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.dynawo.margincalculation.json; + +import com.powsybl.commons.config.InMemoryPlatformConfig; +import com.powsybl.commons.test.AbstractSerDeTest; +import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author Laurent Issertial {@literal } + */ +class JsonMarginCalculationParametersTest extends AbstractSerDeTest { + + private static final String USER_HOME = "/home/user/"; + + private InMemoryPlatformConfig platformConfig; + + @BeforeEach + @Override + public void setUp() throws IOException { + super.setUp(); + platformConfig = new InMemoryPlatformConfig(fileSystem); + Files.createDirectories(fileSystem.getPath(USER_HOME)); + copyFile("/parametersSet/models.par", DynawoSimulationParameters.DEFAULT_INPUT_PARAMETERS_FILE); + copyFile("/parametersSet/network.par", DynawoSimulationParameters.DEFAULT_INPUT_NETWORK_PARAMETERS_FILE); + copyFile("/parametersSet/solvers.par", DynawoSimulationParameters.DEFAULT_INPUT_SOLVER_PARAMETERS_FILE); + } + + private void copyFile(String name, String parametersFile) throws IOException { + Path path = platformConfig.getConfigDir() + .map(cd -> cd.resolve(fileSystem.getPath(parametersFile))) + .orElse(fileSystem.getPath(parametersFile)); + Objects.requireNonNull(getClass().getResourceAsStream(name)) + .transferTo(Files.newOutputStream(path)); + } + + @Test + void roundTrip() throws IOException { + MarginCalculationParameters parameters = MarginCalculationParameters.builder() + .setDynawoParameters(DynawoSimulationParameters.load(platformConfig)) + .build(); + roundTripTest(parameters, JsonMarginCalculationParameters::write, JsonMarginCalculationParameters::read, "/MarginCalculationParameters.json"); + } + + @Test + void readError() throws IOException { + try (var is = getClass().getResourceAsStream("/MarginCalculationParametersError.json")) { + IllegalStateException e = assertThrows(IllegalStateException.class, () -> JsonMarginCalculationParameters.read(is)); + assertEquals("Unexpected field: unknownParameter", e.getMessage()); + } + } +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationResultJsonTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationResultJsonTest.java new file mode 100644 index 000000000..ff016706f --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/json/MarginCalculationResultJsonTest.java @@ -0,0 +1,59 @@ +/** + * 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.dynawo.margincalculation.json; + +import com.powsybl.commons.test.AbstractSerDeTest; +import com.powsybl.dynaflow.results.FailedCriterion; +import com.powsybl.dynaflow.results.ScenarioResult; +import com.powsybl.dynaflow.results.Status; +import com.powsybl.dynawo.margincalculation.results.LoadIncreaseResult; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author Laurent Issertial {@literal } + */ +class MarginCalculationResultJsonTest extends AbstractSerDeTest { + + private static MarginCalculationResult create() { + return new MarginCalculationResult(List.of( + new LoadIncreaseResult(100.0, + Status.CRITERIA_NON_RESPECTED, + Collections.emptyList(), + Collections.singletonList(new FailedCriterion("Failed crit1", 23.2))), + new LoadIncreaseResult(75.0, + Status.DIVERGENCE, + List.of( + new ScenarioResult("cont1", Status.CONVERGENCE), + new ScenarioResult("cont2", Status.CRITERIA_NON_RESPECTED, + Collections.singletonList(new FailedCriterion("Failed crit", 3.3))) + )), + new LoadIncreaseResult(50.0, Status.CONVERGENCE))); + } + + @Test + void roundTripTest() throws IOException { + roundTripTest(create(), MarginCalculationResultSerializer::write, MarginCalculationResultDeserializer::read, "/MarginCalculationResult.json"); + } + + @Test + void handleErrorTest() throws IOException { + try (var is = getClass().getResourceAsStream("/MarginCalculationResultError.json")) { + IllegalStateException e = assertThrows(IllegalStateException.class, () -> MarginCalculationResultDeserializer.read(is)); + assertEquals("Unexpected field: err", e.getMessage()); + } + } + +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationJsonDeserializerTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationJsonDeserializerTest.java new file mode 100644 index 000000000..4e35ba011 --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/loadsvariation/supplier/LoadsVariationJsonDeserializerTest.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.loadsvariation.supplier; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariationBuilder; +import com.powsybl.dynawo.suppliers.SupplierJsonDeserializer; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Laurent Issertial {@literal } + */ +class LoadsVariationJsonDeserializerTest { + + @Test + void testLoadVariationSupplier() throws IOException { + Network network = EurostagTutorialExample1Factory.createWithMultipleConnectedComponents(); + try (InputStream is = getClass().getResourceAsStream("/load_variations.json")) { + List loadsVariations = new SupplierJsonDeserializer<>( + new LoadsVariationJsonDeserializer(() -> new LoadsVariationBuilder(network, ReportNode.NO_OP))) + .deserialize(is); + assertThat(loadsVariations).usingRecursiveFieldByFieldElementComparatorOnFields() + .containsExactlyInAnyOrderElementsOf(getExpectedLoadsVariations(network)); + } + } + + private static List getExpectedLoadsVariations(Network network) { + return List.of( + new LoadsVariationBuilder(network, ReportNode.NO_OP) + .loads("LOAD", "LOAD2") + .variationValue(20) + .build()); + } +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/results/XmlResultParserTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/results/XmlResultParserTest.java new file mode 100644 index 000000000..26c4f8e69 --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/results/XmlResultParserTest.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2024, 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.dynawo.margincalculation.results; + +import com.powsybl.dynaflow.results.FailedCriterion; +import com.powsybl.dynaflow.results.ScenarioResult; +import org.junit.jupiter.api.Test; + +import javax.xml.stream.XMLStreamException; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static com.powsybl.dynaflow.results.Status.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Laurent Issertial {@literal } + */ +class XmlResultParserTest { + + @Test + void test() throws XMLStreamException { + InputStreamReader xml = new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("/result.xml"))); + List result = new XmlMarginCalculationResultParser().parse(xml); + + assertThat(result).containsExactly(new LoadIncreaseResult(100, CRITERIA_NON_RESPECTED, Collections.emptyList(), + List.of(new FailedCriterion("total load power = 295.381MW", 70.0))), + new LoadIncreaseResult(50, CONVERGENCE, + List.of(new ScenarioResult("DisconnectLine", CONVERGENCE), + new ScenarioResult("DisconnectGroup", CRITERIA_NON_RESPECTED, + List.of(new FailedCriterion("total load power = 276.374MW", 185.0))), + new ScenarioResult("DisconnectGenerator", DIVERGENCE)), + Collections.emptyList()), + new LoadIncreaseResult(60, DIVERGENCE), + new LoadIncreaseResult(70, EXECUTION_PROBLEM)); + } + + @Test + void parseFromPath() throws URISyntaxException { + List result = new ArrayList<>(); + Path path = Path.of(Objects.requireNonNull(getClass().getResource("/result.xml")).toURI()); + new XmlMarginCalculationResultParser().parse(path, result::add); + assertEquals(4, result.size()); + } + + @Test + void testInconsistentFile() throws XMLStreamException { + InputStreamReader xml = new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("/wrongResult.xml"))); + List result = new XmlMarginCalculationResultParser().parse(xml); + assertEquals(0, result.size()); + } +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/tool/MarginCalculationTool.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/tool/MarginCalculationTool.java new file mode 100644 index 000000000..c5a8b0794 --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/tool/MarginCalculationTool.java @@ -0,0 +1,273 @@ +/** + * 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.dynawo.margincalculation.tool; + +import com.google.auto.service.AutoService; +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.io.table.*; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.contingency.ContingenciesProvider; +import com.powsybl.contingency.dsl.GroovyDslContingenciesProviderFactory; +import com.powsybl.dynaflow.results.FailedCriterion; +import com.powsybl.dynaflow.results.ScenarioResult; +import com.powsybl.dynamicsimulation.DynamicModelsSupplier; +import com.powsybl.dynamicsimulation.groovy.DynamicSimulationSupplierFactory; +import com.powsybl.dynawo.margincalculation.MarginCalculation; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.margincalculation.MarginCalculationRunParameters; +import com.powsybl.dynawo.margincalculation.json.JsonMarginCalculationParameters; +import com.powsybl.dynawo.margincalculation.json.MarginCalculationResultSerializer; +import com.powsybl.dynawo.margincalculation.loadsvariation.supplier.LoadsVariationSupplier; +import com.powsybl.dynawo.margincalculation.loadsvariation.supplier.LoadsVariationSupplierUtils; +import com.powsybl.dynawo.margincalculation.results.LoadIncreaseResult; +import com.powsybl.dynawo.margincalculation.results.MarginCalculationResult; +import com.powsybl.iidm.network.ImportConfig; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.tools.ConversionToolUtils; +import com.powsybl.tools.Command; +import com.powsybl.tools.Tool; +import com.powsybl.tools.ToolRunningContext; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.file.Path; +import java.util.List; +import java.util.Properties; + +/** + * @author Laurent Issertial {@literal } + */ +@AutoService(Tool.class) +//TODO add tests +public class MarginCalculationTool implements Tool { + + private static final String CASE_FILE = "case-file"; + private static final String DYNAMIC_MODELS_FILE = "dynamic-models-file"; + private static final String CONTINGENCIES_FILE = "contingencies-file"; + private static final String LOAD_VARIATIONS_FILE = "load-variations-file"; + private static final String PARAMETERS_FILE = "parameters-file"; + private static final String OUTPUT_FILE = "output-file"; + private static final String OUTPUT_LOG_FILE = "output-log-file"; + + @Override + public Command getCommand() { + return new Command() { + @Override + public String getName() { + return "margin-calculation"; + } + + @Override + public String getTheme() { + return "Computation"; + } + + @Override + public String getDescription() { + return "Run margin calculation"; + } + + @Override + public Options getOptions() { + return new Options() + .addOption(Option.builder().longOpt(CASE_FILE) + .desc("the case path") + .hasArg() + .argName("FILE") + .required() + .build()) + .addOption(Option.builder().longOpt(DYNAMIC_MODELS_FILE) + .desc("dynamic models description as a Groovy file: defines the dynamic models to be associated to chosen equipments of the network") + .hasArg() + .argName("FILE") + .required() + .build()) + .addOption(Option.builder().longOpt(CONTINGENCIES_FILE) + .desc("contingencies description as a Groovy file") + .hasArg() + .argName("FILE") + .required() + .build()) + .addOption(Option.builder().longOpt(LOAD_VARIATIONS_FILE) + .desc("load variations description as a JSON file") + .hasArg() + .argName("FILE") + .required() + .build()) + .addOption(Option.builder().longOpt(PARAMETERS_FILE) + .desc("margin calculation parameters as a JSON file") + .hasArg() + .argName("FILE") + .build()) + .addOption(Option.builder().longOpt(OUTPUT_FILE) + .desc("margin calculation results output path") + .hasArg() + .argName("FILE") + .build()) + .addOption(Option.builder().longOpt(OUTPUT_LOG_FILE) + .desc("margin calculation logs output path") + .hasArg() + .argName("FILE") + .build()) + .addOption(ConversionToolUtils.createImportParametersFileOption()) + .addOption(ConversionToolUtils.createImportParameterOption()); + } + + @Override + public String getUsageFooter() { + return null; + } + }; + } + + @Override + public void run(CommandLine line, ToolRunningContext context) throws Exception { + + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("marginCalculationTool", "Margin Calculation Tool") + .build(); + Path caseFile = context.getFileSystem().getPath(line.getOptionValue(CASE_FILE)); + // process a single network: output-file/output-format options available + + context.getOutputStream().println("Loading network '" + caseFile + "'"); + Properties inputParams = ConversionToolUtils.readProperties(line, ConversionToolUtils.OptionType.IMPORT, context); + Network network = Network.read(caseFile, context.getShortTimeExecutionComputationManager(), ImportConfig.load(), inputParams); + if (network == null) { + throw new PowsyblException("Case '" + caseFile + "' not found"); + } + + MarginCalculation.Runner runner = MarginCalculation.getRunner(); + Path dydFile = context.getFileSystem().getPath(line.getOptionValue(DYNAMIC_MODELS_FILE)); + DynamicModelsSupplier dynamicModelsSupplier = DynamicSimulationSupplierFactory.createDynamicModelsSupplier(dydFile, runner.getName()); + Path contingenciesFile = context.getFileSystem().getPath(line.getOptionValue(CONTINGENCIES_FILE)); + ContingenciesProvider contingenciesProvider = new GroovyDslContingenciesProviderFactory().create(contingenciesFile); + Path loadVariationsFile = context.getFileSystem().getPath(line.getOptionValue(LOAD_VARIATIONS_FILE)); + LoadsVariationSupplier loadsVariationSupplier = LoadsVariationSupplierUtils.getLoadsVariationSupplierForJson(loadVariationsFile); + MarginCalculationParameters parameters = line.hasOption(PARAMETERS_FILE) ? + JsonMarginCalculationParameters.read(context.getFileSystem().getPath(line.getOptionValue(PARAMETERS_FILE))) + : MarginCalculationParameters.builder().build(); + MarginCalculationRunParameters runParameters = new MarginCalculationRunParameters() + .setMarginCalculationParameters(parameters) + .setComputationManager(context.getShortTimeExecutionComputationManager()) + .setReportNode(reportNode); + + MarginCalculationResult result = runner.run(network, dynamicModelsSupplier, contingenciesProvider, loadsVariationSupplier, runParameters); + //Results + Path outputLogFile = line.hasOption(OUTPUT_LOG_FILE) ? context.getFileSystem().getPath(line.getOptionValue(OUTPUT_LOG_FILE)) : null; + if (outputLogFile != null) { + exportLog(reportNode, context, outputLogFile); + } else { + printLog(reportNode, context); + } + Path outputFile = line.hasOption(OUTPUT_FILE) ? context.getFileSystem().getPath(line.getOptionValue(OUTPUT_FILE)) : null; + if (outputFile != null) { + exportResult(result, context, outputFile); + } else { + printResult(result, context); + } + } + + private void printLog(ReportNode reportNode, ToolRunningContext context) throws IOException { + Writer writer = new OutputStreamWriter(context.getOutputStream()); + reportNode.print(writer); + writer.flush(); + } + + private void exportLog(ReportNode reportNode, ToolRunningContext context, Path outputLogFile) throws IOException { + context.getOutputStream().println("Writing logs to '" + outputLogFile + "'"); + reportNode.print(outputLogFile); + } + + private void printResult(MarginCalculationResult result, ToolRunningContext context) { + Writer writer = new OutputStreamWriter(context.getOutputStream()); + AsciiTableFormatterFactory asciiTableFormatterFactory = new AsciiTableFormatterFactory(); + printDynamicSimulationResult(result, writer, asciiTableFormatterFactory, TableFormatterConfig.load()); + } + + private void exportResult(MarginCalculationResult result, ToolRunningContext context, Path outputFile) { + context.getOutputStream().println("Writing results to '" + outputFile + "'"); + MarginCalculationResultSerializer.write(result, outputFile); + } + + private void printDynamicSimulationResult(MarginCalculationResult results, Writer writer, + TableFormatterFactory formatterFactory, + TableFormatterConfig formatterConfig) { + try (TableFormatter formatter = formatterFactory.create(writer, + "dynamic simulation results", + formatterConfig, + new Column("Result"))) { + for (LoadIncreaseResult result : results.getResults()) { + formatter.writeCell(result.loadLevel()); + formatter.writeCell(result.status().toString()); + + List failedCriteria = result.failedCriteria(); + if (failedCriteria.isEmpty()) { + formatter.writeEmptyCells(2); + } else { + formatter.writeCell("Failed criteria (%s)".formatted(failedCriteria.size())); + formatter.writeEmptyCell(); + } + + List scenarioResults = result.scenarioResults(); + if (scenarioResults.isEmpty()) { + formatter.writeEmptyCells(4); + } else { + formatter.writeCell("Scenarios (%s)".formatted(scenarioResults.size())); + formatter.writeEmptyCells(3); + } + + for (FailedCriterion criterion : failedCriteria) { + formatter.writeEmptyCells(2); + formatter.writeCell(criterion.description()); + formatter.writeCell(criterion.time()); + formatter.writeEmptyCells(4); + } + + for (ScenarioResult scenarioResult : scenarioResults) { + formatter.writeEmptyCells(4); + formatter.writeCell(scenarioResult.id()); + formatter.writeCell(scenarioResult.status().toString()); + formatter.writeEmptyCells(2); + List scenarioCriteria = scenarioResult.failedCriteria(); + if (scenarioCriteria.isEmpty()) { + formatter.writeEmptyCells(2); + } else { + formatter.writeCell("Scenario failed criteria (%s)".formatted(scenarioCriteria.size())); + formatter.writeEmptyCell(); + } + for (FailedCriterion criterion : failedCriteria) { + formatter.writeEmptyCells(6); + formatter.writeCell(criterion.description()); + formatter.writeCell(criterion.time()); + } + } + + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static Column[] getColumns() { + return new Column[]{ + new Column("Load level"), + new Column("Status"), + new Column("Failed criteria"), + new Column("Failed criteria time"), + new Column("Scenarios"), + new Column("Scenarios Status"), + new Column("Scenarios failed criteria"), + new Column("Scenarios failed criteria time") + }; + } +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/LoadVariationAreaXmlTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/LoadVariationAreaXmlTest.java new file mode 100644 index 000000000..687bc9024 --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/LoadVariationAreaXmlTest.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2023, 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.dynawo.margincalculation.xml; + +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.dynawo.margincalculation.MarginCalculationContext; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.xml.DydXml; +import com.powsybl.dynawo.xml.AbstractDynamicModelXmlTest; +import com.powsybl.dynawo.xml.ParametersXml; +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static com.powsybl.dynawo.margincalculation.xml.MarginCalculationConstant.LOAD_VARIATION_AREA_FILENAME; + +/** + * @author Laurent Issertial + */ +class LoadVariationAreaXmlTest extends AbstractDynamicModelXmlTest { + + @Override + protected void setupNetwork() { + network = EurostagTutorialExample1Factory.createWithMultipleConnectedComponents(); + network.getLoad("LOAD").setP0(400); + network.getLoad("LOAD2").setP0(200); + Load load3 = network.getLoad("LOAD3"); + load3.setP0(100); + load3.getTerminal().connect(); + } + + @Override + protected void addDynamicModels() { + // no models + } + + @Override + protected void setupDynawoContext() { + MarginCalculationParameters parameters = MarginCalculationParameters.load(); + List loadsVariationList = List.of( + new LoadsVariation(List.of(network.getLoad("LOAD")), 20), + new LoadsVariation(List.of(network.getLoad("LOAD2"), network.getLoad("LOAD3")), 30)); + context = new MarginCalculationContext(network, network.getVariantManager().getWorkingVariantId(), + dynamicModels, parameters, Collections.emptyList(), loadsVariationList); + } + + @Test + void writeDyd() throws SAXException, IOException { + DydXml.write(tmpDir, LOAD_VARIATION_AREA_FILENAME, ((MarginCalculationContext) context).getLoadVariationAreaDydData()); + ParametersXml.write(tmpDir, context); + validate("dyd.xsd", "load_variation_area_dyd.xml", tmpDir.resolve(LOAD_VARIATION_AREA_FILENAME)); + validate("parameters.xsd", "load_variation_area_par.xml", tmpDir.resolve(context.getSimulationParFile())); + } +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/MultiplesJobsXmlTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/MultiplesJobsXmlTest.java new file mode 100644 index 000000000..cfe59738b --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/MultiplesJobsXmlTest.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2023, 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.dynawo.margincalculation.xml; + +import com.powsybl.contingency.Contingency; +import com.powsybl.dynawo.margincalculation.MarginCalculationContext; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.dynawo.xml.AbstractDynamicModelXmlTest; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.util.List; + +import static com.powsybl.dynawo.algorithms.xml.AlgorithmsConstants.MULTIPLE_JOBS_FILENAME; + +/** + * @author Laurent Issertial + */ +class MultiplesJobsXmlTest extends AbstractDynamicModelXmlTest { + + @Override + protected void setupNetwork() { + network = EurostagTutorialExample1Factory.createWithMultipleConnectedComponents(); + } + + @Override + protected void addDynamicModels() { + // no models + } + + @Override + protected void setupDynawoContext() { + List contingencies = List.of( + Contingency.load("LOAD"), + Contingency.builder("DisconnectLineGenerator") + .addLine("NHV1_NHV2_1") + .addGenerator("GEN2") + .build()); + MarginCalculationParameters parameters = MarginCalculationParameters.load(); + List loadsVariationList = List.of( + new LoadsVariation(List.of(network.getLoad("LOAD"), network.getLoad("LOAD2")), 10)); + context = new MarginCalculationContext(network, network.getVariantManager().getWorkingVariantId(), + dynamicModels, parameters, contingencies, loadsVariationList); + } + + @Test + void writeMultiplesJobs() throws SAXException, IOException, XMLStreamException { + MultipleJobsXml.write(tmpDir, (MarginCalculationContext) context); + validate("multipleJobs.xsd", "multipleJobs_mc.xml", tmpDir.resolve(MULTIPLE_JOBS_FILENAME)); + } +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/Phase2GlobalXmlTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/Phase2GlobalXmlTest.java new file mode 100644 index 000000000..839d03d05 --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/Phase2GlobalXmlTest.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2023, 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.dynawo.margincalculation.xml; + +import com.powsybl.dynawo.margincalculation.MarginCalculationContext; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.dynawo.models.generators.BaseGeneratorBuilder; +import com.powsybl.dynawo.models.loads.BaseLoadBuilder; +import com.powsybl.dynawo.xml.AbstractDynamicModelXmlTest; +import com.powsybl.dynawo.xml.DydXml; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static com.powsybl.dynawo.DynawoSimulationConstants.DYD_FILENAME; +import static com.powsybl.dynawo.DynawoSimulationConstants.PHASE_2_DYD_FILENAME; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Laurent Issertial + */ +class Phase2GlobalXmlTest extends AbstractDynamicModelXmlTest { + + @Override + protected void setupNetwork() { + network = EurostagTutorialExample1Factory.createWithMultipleConnectedComponents(); + } + + @Override + protected void addDynamicModels() { + dynamicModels.add( + BaseGeneratorBuilder.of(network) + .staticId("GEN") + .parameterSetId("gen") + .build()); + dynamicModels.add( + BaseLoadBuilder.of(network) + .staticId("LOAD") + .parameterSetId("lab") + .build()); + dynamicModels.add( + BaseLoadBuilder.of(network) + .staticId("LOAD2") + .parameterSetId("lab") + .build()); + } + + @Override + protected void setupDynawoContext() { + MarginCalculationParameters parameters = MarginCalculationParameters.load(); + List loadsVariationList = List.of( + new LoadsVariation(List.of(network.getLoad("LOAD2"), network.getLoad("LOAD3")), 30)); + context = new MarginCalculationContext(network, network.getVariantManager().getWorkingVariantId(), + dynamicModels, parameters, Collections.emptyList(), loadsVariationList); + } + + @Test + void writeDyd() throws SAXException, IOException { + DydXml.write(tmpDir, context); + assertThat(context.getPhase2DydData()).isPresent(); + DydXml.write(tmpDir, PHASE_2_DYD_FILENAME, context.getPhase2DydData().get()); + validate("dyd.xsd", "phase1_global_dyd.xml", tmpDir.resolve(DYD_FILENAME)); + validate("dyd.xsd", "phase2_global_dyd.xml", tmpDir.resolve(PHASE_2_DYD_FILENAME)); + } +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/Phase2TargetedXmlTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/Phase2TargetedXmlTest.java new file mode 100644 index 000000000..595b026f2 --- /dev/null +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/margincalculation/xml/Phase2TargetedXmlTest.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2023, 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.dynawo.margincalculation.xml; + +import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.margincalculation.MarginCalculationContext; +import com.powsybl.dynawo.margincalculation.MarginCalculationParameters; +import com.powsybl.dynawo.margincalculation.loadsvariation.LoadsVariation; +import com.powsybl.dynawo.models.generators.BaseGeneratorBuilder; +import com.powsybl.dynawo.models.loads.BaseLoadBuilder; +import com.powsybl.dynawo.xml.AbstractDynamicModelXmlTest; +import com.powsybl.dynawo.xml.DydXml; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static com.powsybl.dynawo.DynawoSimulationConstants.DYD_FILENAME; +import static com.powsybl.dynawo.DynawoSimulationConstants.PHASE_2_DYD_FILENAME; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Laurent Issertial + */ +class Phase2TargetedXmlTest extends AbstractDynamicModelXmlTest { + + @Override + protected void setupNetwork() { + network = EurostagTutorialExample1Factory.createWithMultipleConnectedComponents(); + } + + @Override + protected void addDynamicModels() { + dynamicModels.add( + BaseGeneratorBuilder.of(network) + .staticId("GEN") + .parameterSetId("gen") + .build()); + dynamicModels.add( + BaseLoadBuilder.of(network) + .staticId("LOAD") + .parameterSetId("lab") + .build()); + dynamicModels.add( + BaseLoadBuilder.of(network) + .staticId("LOAD2") + .parameterSetId("lab") + .build()); + } + + @Override + protected void setupDynawoContext() { + MarginCalculationParameters parameters = MarginCalculationParameters.builder() + .setLoadModelsRule(MarginCalculationParameters.LoadModelsRule.TARGETED_LOADS) + .setDynawoParameters(DynawoSimulationParameters.load()) + .build(); + List loadsVariationList = List.of( + new LoadsVariation(List.of(network.getLoad("LOAD2"), network.getLoad("LOAD3")), 30)); + context = new MarginCalculationContext(network, network.getVariantManager().getWorkingVariantId(), + dynamicModels, parameters, Collections.emptyList(), loadsVariationList); + } + + @Test + void writeDyd() throws SAXException, IOException { + DydXml.write(tmpDir, context); + assertThat(context.getPhase2DydData()).isPresent(); + DydXml.write(tmpDir, PHASE_2_DYD_FILENAME, context.getPhase2DydData().get()); + validate("dyd.xsd", "phase1_targeted_dyd.xml", tmpDir.resolve(DYD_FILENAME)); + validate("dyd.xsd", "phase2_targeted_dyd.xml", tmpDir.resolve(PHASE_2_DYD_FILENAME)); + } +} diff --git a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/xml/MultiplesJobsXmlTest.java b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/xml/MultiplesJobsXmlTest.java index 7aac2307a..21dd15dfb 100644 --- a/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/xml/MultiplesJobsXmlTest.java +++ b/dynawo-security-analysis/src/test/java/com/powsybl/dynawo/security/xml/MultiplesJobsXmlTest.java @@ -10,7 +10,6 @@ import com.powsybl.contingency.Contingency; import com.powsybl.dynawo.DynawoSimulationParameters; import com.powsybl.dynawo.security.SecurityAnalysisContext; -import com.powsybl.dynawo.DynawoSimulationConstants; import com.powsybl.dynawo.xml.DynawoTestUtil; import com.powsybl.security.dynamic.DynamicSecurityAnalysisParameters; import org.junit.jupiter.api.Test; @@ -20,6 +19,8 @@ import java.io.IOException; import java.util.List; +import static com.powsybl.dynawo.algorithms.xml.AlgorithmsConstants.MULTIPLE_JOBS_FILENAME; + /** * @author Laurent Issertial */ @@ -38,7 +39,7 @@ void writeMultiplesJobs() throws SAXException, IOException, XMLStreamException { SecurityAnalysisContext context = new SecurityAnalysisContext(network, network.getVariantManager().getWorkingVariantId(), dynamicModels, parameters, dynawoSimulationParameters, contingencies); MultipleJobsXml.write(tmpDir, context); - validate("multipleJobs.xsd", "multipleJobs.xml", tmpDir.resolve(DynawoSimulationConstants.MULTIPLE_JOBS_FILENAME)); + validate("multipleJobs.xsd", "multipleJobs_sa.xml", tmpDir.resolve(MULTIPLE_JOBS_FILENAME)); } } diff --git a/dynawo-security-analysis/src/test/resources/MarginCalculationParameters.json b/dynawo-security-analysis/src/test/resources/MarginCalculationParameters.json new file mode 100644 index 000000000..58a0b27f2 --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/MarginCalculationParameters.json @@ -0,0 +1,64 @@ +{ + "startTime" : 0.0, + "stopTime" : 200.0, + "marginCalculationStartTime" : 100.0, + "loadIncreaseStartTime" : 10.0, + "loadIncreaseStopTime" : 50.0, + "contingenciesStartTime" : 120.0, + "calculationType" : "GLOBAL_MARGIN", + "accuracy" : 2, + "loadModelsRule" : "ALL_LOADS", + "dynawoParameters" : { + "networkParameters" : { + "id" : "1", + "parameters" : { }, + "references" : [ ] + }, + "solverParameters" : { + "id" : "1", + "parameters" : { + "order" : { + "name" : "order", + "type" : "INT", + "value" : "1" + }, + "absAccuracy" : { + "name" : "absAccuracy", + "type" : "DOUBLE", + "value" : "1e-4" + } + }, + "references" : [ ] + }, + "solverType" : "SIM", + "mergeLoads" : false, + "useModelSimplifiers" : false, + "dumpFileParameters" : { + "exportDumpFile" : false, + "useDumpFile" : false, + "dumpFileFolder" : null, + "dumpFile" : null + }, + "precision" : 1.0E-6, + "timelineExportMode" : "TXT", + "logLevelFilter" : "INFO", + "specificLogs" : [ ], + "criteriaFilePath" : null, + "modelsParameters" : [ { + "id" : "test", + "parameters" : { + "boolean" : { + "name" : "boolean", + "type" : "BOOL", + "value" : "true" + }, + "string" : { + "name" : "string", + "type" : "STRING", + "value" : "aString" + } + }, + "references" : [ ] + } ] + } +} \ No newline at end of file diff --git a/dynawo-security-analysis/src/test/resources/MarginCalculationParametersError.json b/dynawo-security-analysis/src/test/resources/MarginCalculationParametersError.json new file mode 100644 index 000000000..6365f3ac1 --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/MarginCalculationParametersError.json @@ -0,0 +1,6 @@ +{ + "version" : "1.0", + "startTime" : 0, + "stopTime" : 1, + "unknownParameter": "" +} \ No newline at end of file diff --git a/dynawo-security-analysis/src/test/resources/MarginCalculationResult.json b/dynawo-security-analysis/src/test/resources/MarginCalculationResult.json new file mode 100644 index 000000000..78e45210c --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/MarginCalculationResult.json @@ -0,0 +1,33 @@ +{ + "version" : "1.0", + "loadIncreases" : [ { + "loadLevel" : 100.0, + "status" : "CRITERIA_NON_RESPECTED", + "failedCriteria" : [ { + "description" : "Failed crit1", + "time" : 23.2 + } ], + "scenarioResults" : [ ] + }, { + "loadLevel" : 75.0, + "status" : "DIVERGENCE", + "failedCriteria" : [ ], + "scenarioResults" : [ { + "id" : "cont1", + "status" : "CONVERGENCE", + "failedCriteria" : [ ] + }, { + "id" : "cont2", + "status" : "CRITERIA_NON_RESPECTED", + "failedCriteria" : [ { + "description" : "Failed crit", + "time" : 3.3 + } ] + } ] + }, { + "loadLevel" : 50.0, + "status" : "CONVERGENCE", + "failedCriteria" : [ ], + "scenarioResults" : [ ] + } ] +} \ No newline at end of file diff --git a/dynawo-security-analysis/src/test/resources/MarginCalculationResultError.json b/dynawo-security-analysis/src/test/resources/MarginCalculationResultError.json new file mode 100644 index 000000000..b58e465cc --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/MarginCalculationResultError.json @@ -0,0 +1,4 @@ +{ + "version" : "1.0", + "err" : "4" +} \ No newline at end of file diff --git a/dynawo-security-analysis/src/test/resources/load_variation_area_dyd.xml b/dynawo-security-analysis/src/test/resources/load_variation_area_dyd.xml new file mode 100644 index 000000000..04389c11f --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/load_variation_area_dyd.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/load_variation_area_par.xml b/dynawo-security-analysis/src/test/resources/load_variation_area_par.xml new file mode 100644 index 000000000..519be206e --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/load_variation_area_par.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/load_variations.json b/dynawo-security-analysis/src/test/resources/load_variations.json new file mode 100644 index 000000000..907574f83 --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/load_variations.json @@ -0,0 +1,12 @@ +{ + "variations": [ + { + "loadsIds": ["LOAD", "LOAD2"], + "variationValue": 20 + }, + { + "loadsIds": ["LOAD3"], + "variationValue": 10.2 + } + ] +} \ No newline at end of file diff --git a/dynawo-security-analysis/src/test/resources/multipleJobs_mc.xml b/dynawo-security-analysis/src/test/resources/multipleJobs_mc.xml new file mode 100644 index 000000000..74a121647 --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/multipleJobs_mc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/multipleJobs.xml b/dynawo-security-analysis/src/test/resources/multipleJobs_sa.xml similarity index 100% rename from dynawo-security-analysis/src/test/resources/multipleJobs.xml rename to dynawo-security-analysis/src/test/resources/multipleJobs_sa.xml diff --git a/dynawo-security-analysis/src/test/resources/parametersSet/models.par b/dynawo-security-analysis/src/test/resources/parametersSet/models.par new file mode 100644 index 000000000..be611df2b --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/parametersSet/models.par @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/parametersSet/network.par b/dynawo-security-analysis/src/test/resources/parametersSet/network.par new file mode 100644 index 000000000..e3ed13644 --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/parametersSet/network.par @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/parametersSet/solvers.par b/dynawo-security-analysis/src/test/resources/parametersSet/solvers.par new file mode 100644 index 000000000..f15ad1128 --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/parametersSet/solvers.par @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/phase1_global_dyd.xml b/dynawo-security-analysis/src/test/resources/phase1_global_dyd.xml new file mode 100644 index 000000000..a6778e060 --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/phase1_global_dyd.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/phase1_targeted_dyd.xml b/dynawo-security-analysis/src/test/resources/phase1_targeted_dyd.xml new file mode 100644 index 000000000..e407da502 --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/phase1_targeted_dyd.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/phase2_global_dyd.xml b/dynawo-security-analysis/src/test/resources/phase2_global_dyd.xml new file mode 100644 index 000000000..cf449d13a --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/phase2_global_dyd.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/phase2_targeted_dyd.xml b/dynawo-security-analysis/src/test/resources/phase2_targeted_dyd.xml new file mode 100644 index 000000000..7f038b589 --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/phase2_targeted_dyd.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/result.xml b/dynawo-security-analysis/src/test/resources/result.xml new file mode 100644 index 000000000..c9ffafe1c --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/result.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/dynawo-security-analysis/src/test/resources/wrongResult.xml b/dynawo-security-analysis/src/test/resources/wrongResult.xml new file mode 100644 index 000000000..bd106412a --- /dev/null +++ b/dynawo-security-analysis/src/test/resources/wrongResult.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationConstants.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationConstants.java index e75fe6a99..831b5544f 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationConstants.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationConstants.java @@ -6,6 +6,8 @@ */ package com.powsybl.dynawo; +import com.powsybl.iidm.network.Network; + /** * @author Marcos de Miguel {@literal } */ @@ -27,14 +29,18 @@ public final class DynawoSimulationConstants { public static final String FSV_OUTPUT_FILENAME = "finalStateValues.csv"; - public static final String MULTIPLE_JOBS_FILENAME = "multiple_jobs.xml"; - - public static final String AGGREGATED_RESULTS = "aggregatedResults.xml"; - public static final String LOGS_FOLDER = "logs"; public static final String LOGS_FILENAME = "dynawo.log"; + public static final String PHASE_2_DYD_FILENAME = "phase_2_powsybl_dynawo.dyd"; + + public static final String PHASE_2_JOBS_FILENAME = "phase_2.jobs"; + private DynawoSimulationConstants() { } + + public static String getSimulationParFile(Network network) { + return network.getId() + ".par"; + } } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationContext.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationContext.java index 4652d2592..f2b45ced3 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationContext.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationContext.java @@ -25,6 +25,7 @@ import com.powsybl.dynawo.models.macroconnections.MacroConnectionsAdder; import com.powsybl.dynawo.models.macroconnections.MacroConnector; import com.powsybl.dynawo.parameters.ParametersSet; +import com.powsybl.dynawo.xml.DydDataSupplier; import com.powsybl.dynawo.xml.MacroStaticReference; import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; @@ -41,7 +42,7 @@ * @author Marcos de Miguel {@literal } * @author Laurent Issertial {@literal } */ -public class DynawoSimulationContext { +public class DynawoSimulationContext implements DydDataSupplier { protected static final Logger LOGGER = LoggerFactory.getLogger(DynawoSimulationContext.class); private static final String MODEL_ID_EXCEPTION = "The model identified by the static id %s does not match the expected model (%s)"; @@ -62,30 +63,47 @@ public class DynawoSimulationContext { private final FrequencySynchronizerModel frequencySynchronizer; private final List dynamicModelsParameters = new ArrayList<>(); protected final MacroConnectionsAdder macroConnectionsAdder; + private final Phase2Config phase2Config; + private Phase2Models phase2Models; + + public DynawoSimulationContext(Network network, String workingVariantId, List dynamicModels, List eventModels, + List outputVariables, DynamicSimulationParameters parameters, DynawoSimulationParameters dynawoSimulationParameters, + DynawoVersion currentVersion, ReportNode reportNode) { + this(network, workingVariantId, dynamicModels, eventModels, outputVariables, parameters, dynawoSimulationParameters, null, currentVersion, reportNode); + } public DynawoSimulationContext(Network network, String workingVariantId, List dynamicModels, List eventModels, List outputVariables, DynamicSimulationParameters parameters, DynawoSimulationParameters dynawoSimulationParameters) { - this(network, workingVariantId, dynamicModels, eventModels, outputVariables, parameters, dynawoSimulationParameters, DynawoConstants.VERSION_MIN, ReportNode.NO_OP); + this(network, workingVariantId, dynamicModels, eventModels, outputVariables, parameters, dynawoSimulationParameters, null, DynawoConstants.VERSION_MIN, ReportNode.NO_OP); } public DynawoSimulationContext(Network network, String workingVariantId, List dynamicModels, List eventModels, List outputVariables, DynamicSimulationParameters parameters, DynawoSimulationParameters dynawoSimulationParameters, - DynawoVersion currentVersion, ReportNode reportNode) { - + Phase2Config phase2Config, DynawoVersion currentVersion, ReportNode reportNode) { ReportNode contextReportNode = DynawoSimulationReports.createDynawoSimulationContextReportNode(reportNode); DynawoVersion dynawoVersion = Objects.requireNonNull(currentVersion); this.network = Objects.requireNonNull(network); this.workingVariantId = Objects.requireNonNull(workingVariantId); this.parameters = Objects.requireNonNull(parameters); this.dynawoSimulationParameters = Objects.requireNonNull(dynawoSimulationParameters); + this.phase2Config = phase2Config; Stream uniqueIdsDynamicModels = Objects.requireNonNull(dynamicModels).stream() .filter(distinctByDynamicId(contextReportNode) .and(distinctByStaticId(contextReportNode) .and(supportedVersion(dynawoVersion, contextReportNode)))); - this.dynamicModels = dynawoSimulationParameters.isUseModelSimplifiers() - ? simplifyModels(uniqueIdsDynamicModels, contextReportNode).toList() - : uniqueIdsDynamicModels.toList(); + if (dynawoSimulationParameters.isUseModelSimplifiers()) { + uniqueIdsDynamicModels = simplifyModels(uniqueIdsDynamicModels, contextReportNode); + } + + List phase2DynamicModels = List.of(); + if (phase2Config != null) { + Map> splitModels = uniqueIdsDynamicModels.collect(Collectors.partitioningBy(phase2Config.phase2ModelsPredicate())); + this.dynamicModels = splitModels.get(false); + phase2DynamicModels = splitModels.get(true); + } else { + this.dynamicModels = uniqueIdsDynamicModels.toList(); + } this.eventModels = Objects.requireNonNull(eventModels).stream() .filter(distinctByDynamicId(contextReportNode) @@ -111,11 +129,12 @@ public DynawoSimulationContext(Network network, String workingVariantId, List { macroStaticReferences.computeIfAbsent(bbm.getName(), k -> new MacroStaticReference(k, bbm.getVarsMapping())); bbm.createMacroConnections(macroConnectionsAdder); bbm.createDynamicModelParameters(this, dynamicModelsParameters::add); - } + }); ParametersSet networkParameters = getDynawoSimulationParameters().getNetworkParameters(); for (BlackBoxModel bbem : eventModels) { @@ -123,6 +142,14 @@ public DynawoSimulationContext(Network network, String workingVariantId, List !macroStaticReferences.containsKey(bbm.getName()), + n -> !macroConnectorsMap.containsKey(n)); + phase2Models.getBlackBoxDynamicModels().forEach(bbm -> bbm.createDynamicModelParameters(this, dynamicModelsParameters::add)); + } } private Stream simplifyModels(Stream inputBbm, ReportNode reportNode) { @@ -142,13 +169,14 @@ private FrequencySynchronizerModel setupFrequencySynchronizer() { List frequencySynchronizedModels = filterDynamicModels(FrequencySynchronizedModel.class); boolean hasSpecificBuses = dynamicModels.stream().anyMatch(AbstractBus.class::isInstance); boolean hasSignalNModel = !signalNModels.isEmpty(); + String defaultParFile = DynawoSimulationConstants.getSimulationParFile(getNetwork()); if (!frequencySynchronizedModels.isEmpty() && hasSignalNModel) { throw new PowsyblException("Signal N and frequency synchronized generators cannot be used with one another"); } if (hasSignalNModel) { - return new SignalN(signalNModels); + return new SignalN(signalNModels, defaultParFile); } - return hasSpecificBuses ? new SetPoint(frequencySynchronizedModels) : new OmegaRef(frequencySynchronizedModels); + return hasSpecificBuses ? new SetPoint(frequencySynchronizedModels, defaultParFile) : new OmegaRef(frequencySynchronizedModels, defaultParFile); } private List filterDynamicModels(Class modelClass) { @@ -166,14 +194,19 @@ public String getWorkingVariantId() { return workingVariantId; } - public DynamicSimulationParameters getParameters() { - return parameters; + public double getStartTime(boolean isPhase2) { + return isPhase2 ? parameters.getStopTime() : parameters.getStartTime(); + } + + public double getStopTime(boolean isPhase2) { + return isPhase2 ? phase2Config.phase2stopTime() : parameters.getStopTime(); } public DynawoSimulationParameters getDynawoSimulationParameters() { return dynawoSimulationParameters; } + @Override public Collection getMacroStaticReferences() { return macroStaticReferences.values(); } @@ -259,10 +292,12 @@ public boolean hasDynamicModel(Identifiable equipment) { return staticIdBlackBoxModelMap.containsKey(equipment.getId()); } + @Override public List getMacroConnectList() { return macroConnectList; } + @Override public Collection getMacroConnectors() { return macroConnectorsMap.values(); } @@ -279,6 +314,7 @@ public Stream getBlackBoxDynamicModelStream() { return Stream.concat(getInputBlackBoxDynamicModelStream(), Stream.of(frequencySynchronizer)); } + @Override public List getBlackBoxDynamicModels() { return getBlackBoxDynamicModelStream().toList(); } @@ -287,6 +323,7 @@ public Stream getBlackBoxEventModelStream() { return eventModels.stream(); } + @Override public List getBlackBoxEventModels() { return eventModels; } @@ -314,4 +351,8 @@ public List getDynamicModelsParameters() { public String getSimulationParFile() { return getNetwork().getId() + ".par"; } + + public Optional getPhase2DydData() { + return Optional.ofNullable(phase2Models); + } } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationParameters.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationParameters.java index 69e739e32..698b73498 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationParameters.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/DynawoSimulationParameters.java @@ -274,7 +274,11 @@ public Set getSpecificLogs() { } public DynawoSimulationParameters setSpecificLogs(Set specificLogs) { - this.specificLogs = EnumSet.copyOf(specificLogs); + if (specificLogs.isEmpty()) { + this.specificLogs = EnumSet.noneOf(SpecificLog.class); + } else { + this.specificLogs = EnumSet.copyOf(specificLogs); + } return this; } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/Phase2Config.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/Phase2Config.java new file mode 100644 index 000000000..e28e3c512 --- /dev/null +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/Phase2Config.java @@ -0,0 +1,21 @@ +/** + * 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.dynawo; + +import com.powsybl.dynawo.models.BlackBoxModel; + +import java.util.function.Predicate; + +/** + * Configures the dynamic simulation phase 2 + * @param phase2stopTime Simulation phase 2 stop time, start time will be equal to phase 1 stop time + * @param phase2ModelsPredicate Discriminate models used only during phase 2 + * @author Laurent Issertial {@literal } + */ +public record Phase2Config(double phase2stopTime, Predicate phase2ModelsPredicate) { +} diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/Phase2Models.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/Phase2Models.java new file mode 100644 index 000000000..7410b953d --- /dev/null +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/Phase2Models.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2024, 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.dynawo; + +import com.powsybl.dynawo.models.BlackBoxModel; +import com.powsybl.dynawo.models.macroconnections.MacroConnect; +import com.powsybl.dynawo.models.macroconnections.MacroConnectionsAdder; +import com.powsybl.dynawo.models.macroconnections.MacroConnector; +import com.powsybl.dynawo.xml.DydDataSupplier; +import com.powsybl.dynawo.xml.MacroStaticReference; + +import java.util.*; +import java.util.function.Predicate; + +/** + * @author Laurent Issertial {@literal } + */ +public class Phase2Models implements DydDataSupplier { + + private final List dynamicModels; + private final Map macroStaticReferences = new LinkedHashMap<>(); + private final List macroConnectList = new ArrayList<>(); + private final Map macroConnectorsMap = new LinkedHashMap<>(); + + public Phase2Models(List dynamicModels, MacroConnectionsAdder macroConnectionsAdder, + Predicate macroStaticDuplicatePredicate, Predicate macroConnectorDuplicatePredicate) { + this.dynamicModels = dynamicModels; + macroConnectionsAdder.setMacroConnectorAdder((n, f) -> { + if (macroConnectorDuplicatePredicate.test(n)) { + macroConnectorsMap.computeIfAbsent(n, f); + } + }); + macroConnectionsAdder.setMacroConnectAdder(macroConnectList::add); + for (BlackBoxModel bbm : dynamicModels) { + if (macroStaticDuplicatePredicate.test(bbm)) { + macroStaticReferences.computeIfAbsent(bbm.getName(), k -> new MacroStaticReference(k, bbm.getVarsMapping())); + } + bbm.createMacroConnections(macroConnectionsAdder); + } + } + + @Override + public List getBlackBoxDynamicModels() { + return dynamicModels; + } + + @Override + public Collection getMacroConnectors() { + return macroConnectorsMap.values(); + } + + @Override + public Collection getMacroStaticReferences() { + return macroStaticReferences.values(); + } + + @Override + public List getMacroConnectList() { + return macroConnectList; + } +} diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/AbstractBlackBoxModel.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/AbstractBlackBoxModel.java index 694546c24..03c10bdab 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/AbstractBlackBoxModel.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/AbstractBlackBoxModel.java @@ -75,18 +75,18 @@ public List getMacroConnectToAttributes() { } @Override - public String getParFile(DynawoSimulationContext context) { - return DynawoSimulationParameters.MODELS_OUTPUT_PARAMETERS_FILE; + public void write(XMLStreamWriter writer) throws XMLStreamException { + write(writer, getDefaultParFile()); } @Override - public List getVarsMapping() { - return Collections.emptyList(); + public String getDefaultParFile() { + return DynawoSimulationParameters.MODELS_OUTPUT_PARAMETERS_FILE; } @Override - public void write(XMLStreamWriter writer, DynawoSimulationContext context) throws XMLStreamException { - write(writer, getParFile(context)); + public List getVarsMapping() { + return Collections.emptyList(); } protected void writeDynamicAttributes(XMLStreamWriter writer, String parFileName) throws XMLStreamException { diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/BlackBoxModel.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/BlackBoxModel.java index 4218eb33c..46722915d 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/BlackBoxModel.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/BlackBoxModel.java @@ -36,9 +36,9 @@ public interface BlackBoxModel extends Model { List getMacroConnectFromAttributes(); - String getParFile(DynawoSimulationContext context); + String getDefaultParFile(); - void write(XMLStreamWriter writer, DynawoSimulationContext context) throws XMLStreamException; + void write(XMLStreamWriter writer) throws XMLStreamException; void write(XMLStreamWriter writer, String parFileName) throws XMLStreamException; diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/automationsystems/phaseshifters/PhaseShifterBlockingIAutomationSystem.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/automationsystems/phaseshifters/PhaseShifterBlockingIAutomationSystem.java index a3afc73dd..686523f33 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/automationsystems/phaseshifters/PhaseShifterBlockingIAutomationSystem.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/automationsystems/phaseshifters/PhaseShifterBlockingIAutomationSystem.java @@ -7,7 +7,6 @@ */ package com.powsybl.dynawo.models.automationsystems.phaseshifters; -import com.powsybl.dynawo.DynawoSimulationContext; import com.powsybl.dynawo.DynawoSimulationReports; import com.powsybl.dynawo.builders.ModelConfig; import com.powsybl.dynawo.models.AbstractPureDynamicBlackBoxModel; @@ -55,9 +54,9 @@ protected List getVarConnectionsWith(TransformerModel connected) } @Override - public void write(XMLStreamWriter writer, DynawoSimulationContext context) throws XMLStreamException { + public void write(XMLStreamWriter writer, String parFileName) throws XMLStreamException { if (isConnected) { - super.write(writer, context); + super.write(writer, parFileName); } } } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/AbstractEvent.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/AbstractEvent.java index 244a333c2..22505202a 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/AbstractEvent.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/AbstractEvent.java @@ -8,6 +8,7 @@ package com.powsybl.dynawo.models.events; import com.powsybl.dynamicsimulation.EventModel; +import com.powsybl.dynawo.DynawoSimulationConstants; import com.powsybl.dynawo.DynawoSimulationContext; import com.powsybl.dynawo.builders.EventModelInfo; import com.powsybl.dynawo.builders.VersionInterval; @@ -72,11 +73,6 @@ public double getStartTime() { return startTime; } - @Override - public String getParFile(DynawoSimulationContext context) { - return context.getSimulationParFile(); - } - @Override public void createDynamicModelParameters(DynawoSimulationContext context, Consumer parametersAdder) { ParametersSet paramSet = new ParametersSet(getParameterSetId()); @@ -84,5 +80,10 @@ public void createDynamicModelParameters(DynawoSimulationContext context, Consum parametersAdder.accept(paramSet); } + @Override + public String getDefaultParFile() { + return DynawoSimulationConstants.getSimulationParFile(getEquipment().getNetwork()); + } + protected abstract void createEventSpecificParameters(ParametersSet paramSet); } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/ControllableEquipment.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/ControllableEquipment.java deleted file mode 100644 index dc76a2853..000000000 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/ControllableEquipment.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.powsybl.dynawo.models.events; - -import com.powsybl.dynawo.models.Model; - -public interface ControllableEquipment extends Model { - - String getDeltaPVarName(); -} diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/ControllableEquipmentModel.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/ControllableEquipmentModel.java new file mode 100644 index 000000000..106e34f6d --- /dev/null +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/ControllableEquipmentModel.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2023, 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.dynawo.models.events; + +import com.powsybl.dynawo.models.Model; + +/** + * @author Laurent Issertial {@literal } + */ +public interface ControllableEquipmentModel extends Model { + + String getDeltaPVarName(); +} diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/EventActivePowerVariation.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/EventActivePowerVariation.java index 06debacc6..694ee9258 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/EventActivePowerVariation.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/events/EventActivePowerVariation.java @@ -53,7 +53,7 @@ public String getName() { return EventActivePowerVariation.class.getSimpleName(); } - private List getVarConnectionsWith(ControllableEquipment connected) { + private List getVarConnectionsWith(ControllableEquipmentModel connected) { return List.of(TRUE == equipmentHasDynamicModel.getValue() ? new VarConnection("step_step_value", connected.getDeltaPVarName()) : new VarConnection("event_state1", connected.getDeltaPVarName())); } @@ -62,7 +62,7 @@ private List getVarConnectionsWith(ControllableEquipment connecte public void createMacroConnections(MacroConnectionsAdder adder) { adder.createMacroConnections(this, getEquipment(), - ControllableEquipment.class, + ControllableEquipmentModel.class, this::getVarConnectionsWith); } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/AbstractFrequencySynchronizer.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/AbstractFrequencySynchronizer.java index e4fed2be3..d0dfb4bf1 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/AbstractFrequencySynchronizer.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/AbstractFrequencySynchronizer.java @@ -7,7 +7,6 @@ */ package com.powsybl.dynawo.models.frequencysynchronizers; -import com.powsybl.dynawo.DynawoSimulationContext; import com.powsybl.dynawo.builders.ModelConfig; import com.powsybl.dynawo.models.AbstractPureDynamicBlackBoxModel; @@ -21,10 +20,12 @@ public abstract class AbstractFrequencySynchronizer extends AbstractPureDynamicB private static final String FREQUENCY_SYNCHRONIZER_ID = "FREQ_SYNC"; private static final String FREQUENCY_SYNCHRONIZER_PARAMETER_SET_ID = "FREQ_SYNC_PAR"; protected final List synchronizedEquipments; + private final String defaultParFile; - protected AbstractFrequencySynchronizer(List synchronizedEquipments, ModelConfig modelConfig) { + protected AbstractFrequencySynchronizer(List synchronizedEquipments, ModelConfig modelConfig, String defaultParFile) { super(FREQUENCY_SYNCHRONIZER_ID, FREQUENCY_SYNCHRONIZER_PARAMETER_SET_ID, modelConfig); this.synchronizedEquipments = synchronizedEquipments; + this.defaultParFile = defaultParFile; } @Override @@ -33,7 +34,7 @@ public boolean isEmpty() { } @Override - public String getParFile(DynawoSimulationContext context) { - return context.getSimulationParFile(); + public String getDefaultParFile() { + return defaultParFile; } } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/OmegaRef.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/OmegaRef.java index 7c2e28a3a..2761ac7ad 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/OmegaRef.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/OmegaRef.java @@ -35,8 +35,10 @@ */ public class OmegaRef extends AbstractFrequencySynchronizer { - public OmegaRef(List synchronizedEquipments) { - super(synchronizedEquipments, new ModelConfig("DYNModelOmegaRef")); + private static final ModelConfig MODEL_CONFIG = new ModelConfig("DYNModelOmegaRef"); + + public OmegaRef(List synchronizedEquipments, String defaultParFile) { + super(synchronizedEquipments, MODEL_CONFIG, defaultParFile); } @Override diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/SetPoint.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/SetPoint.java index 31fc719e5..981433d1e 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/SetPoint.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/SetPoint.java @@ -26,8 +26,10 @@ */ public class SetPoint extends AbstractFrequencySynchronizer { - public SetPoint(List synchronizedEquipments) { - super(synchronizedEquipments, new ModelConfig("SetPoint")); + private static final ModelConfig MODEL_CONFIG = new ModelConfig("SetPoint"); + + public SetPoint(List synchronizedEquipments, String defaultParFile) { + super(synchronizedEquipments, MODEL_CONFIG, defaultParFile); } @Override diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/SignalN.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/SignalN.java index 0745a8557..2890f1546 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/SignalN.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/frequencysynchronizers/SignalN.java @@ -26,10 +26,12 @@ public class SignalN extends AbstractPureDynamicBlackBoxModel implements Frequen private static final String SIGNAL_N_ID = "Signal_N"; private static final ModelConfig MODEL_CONFIG = new ModelConfig("SignalN"); private final List signalNEquipments; + private final String defaultParFile; - public SignalN(List signalNEquipments) { + public SignalN(List signalNEquipments, String defaultParFile) { super(SIGNAL_N_ID, "", MODEL_CONFIG); this.signalNEquipments = signalNEquipments; + this.defaultParFile = defaultParFile; } @Override @@ -60,4 +62,9 @@ protected void writeDynamicAttributes(XMLStreamWriter writer, String parFileName writer.writeAttribute("id", getDynamicModelId()); writer.writeAttribute("lib", getLib()); } + + @Override + public String getDefaultParFile() { + return defaultParFile; + } } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/DefaultGenerator.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/DefaultGenerator.java index 54d001179..20777b8fa 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/DefaultGenerator.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/DefaultGenerator.java @@ -8,12 +8,12 @@ package com.powsybl.dynawo.models.generators; import com.powsybl.dynawo.models.defaultmodels.AbstractInjectionDefaultModel; -import com.powsybl.dynawo.models.events.ControllableEquipment; +import com.powsybl.dynawo.models.events.ControllableEquipmentModel; /** * @author Laurent Issertial {@literal } */ -public class DefaultGenerator extends AbstractInjectionDefaultModel implements GeneratorModel, ControllableEquipment { +public class DefaultGenerator extends AbstractInjectionDefaultModel implements GeneratorModel, ControllableEquipmentModel { public DefaultGenerator(String staticId) { super(staticId); diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/SynchronizedGeneratorControllable.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/SynchronizedGeneratorControllable.java index 6959569b1..be7d84345 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/SynchronizedGeneratorControllable.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/SynchronizedGeneratorControllable.java @@ -8,13 +8,13 @@ package com.powsybl.dynawo.models.generators; import com.powsybl.dynawo.builders.ModelConfig; -import com.powsybl.dynawo.models.events.ControllableEquipment; +import com.powsybl.dynawo.models.events.ControllableEquipmentModel; import com.powsybl.iidm.network.Generator; /** * @author Laurent Issertial {@literal } */ -public class SynchronizedGeneratorControllable extends SynchronizedGenerator implements ControllableEquipment { +public class SynchronizedGeneratorControllable extends SynchronizedGenerator implements ControllableEquipmentModel { protected SynchronizedGeneratorControllable(String dynamicModelId, Generator generator, String parameterSetId, ModelConfig modelConfig) { super(dynamicModelId, generator, parameterSetId, modelConfig); diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/SynchronousGeneratorControllable.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/SynchronousGeneratorControllable.java index 29ef4e29b..aefdabc0f 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/SynchronousGeneratorControllable.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/generators/SynchronousGeneratorControllable.java @@ -8,13 +8,13 @@ package com.powsybl.dynawo.models.generators; import com.powsybl.dynawo.builders.ModelConfig; -import com.powsybl.dynawo.models.events.ControllableEquipment; +import com.powsybl.dynawo.models.events.ControllableEquipmentModel; import com.powsybl.iidm.network.Generator; /** * @author Laurent Issertial {@literal } */ -public class SynchronousGeneratorControllable extends SynchronousGenerator implements ControllableEquipment { +public class SynchronousGeneratorControllable extends SynchronousGenerator implements ControllableEquipmentModel { protected SynchronousGeneratorControllable(String dynamicModelId, Generator generator, String parameterSetId, ModelConfig modelConfig, EnumGeneratorComponent generatorComponent) { super(dynamicModelId, generator, parameterSetId, modelConfig, generatorComponent); diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/BaseLoadControllable.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/BaseLoadControllable.java index f9de46f40..f6f69cf3b 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/BaseLoadControllable.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/BaseLoadControllable.java @@ -8,13 +8,13 @@ package com.powsybl.dynawo.models.loads; import com.powsybl.dynawo.builders.ModelConfig; -import com.powsybl.dynawo.models.events.ControllableEquipment; +import com.powsybl.dynawo.models.events.ControllableEquipmentModel; import com.powsybl.iidm.network.Load; /** * @author Laurent Issertial {@literal } */ -public class BaseLoadControllable extends BaseLoad implements ControllableEquipment { +public class BaseLoadControllable extends BaseLoad implements ControllableEquipmentModel { protected BaseLoadControllable(String dynamicModelId, Load load, String parameterSetId, ModelConfig modelConfig) { super(dynamicModelId, load, parameterSetId, modelConfig); diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/DefaultControllableLoadModel.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/DefaultControllableLoadModel.java new file mode 100644 index 000000000..c81450759 --- /dev/null +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/DefaultControllableLoadModel.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2024, 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.dynawo.models.loads; + +import com.powsybl.dynawo.models.events.ControllableEquipmentModel; + +/** + * @author Laurent Issertial {@literal } + */ +public interface DefaultControllableLoadModel extends ControllableEquipmentModel { + + String getDeltaQVarName(); +} diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/DefaultLoad.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/DefaultLoad.java index 1384a52da..94680f1b4 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/DefaultLoad.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/models/loads/DefaultLoad.java @@ -8,12 +8,11 @@ package com.powsybl.dynawo.models.loads; import com.powsybl.dynawo.models.defaultmodels.AbstractInjectionDefaultModel; -import com.powsybl.dynawo.models.events.ControllableEquipment; /** * @author Laurent Issertial {@literal } */ -public class DefaultLoad extends AbstractInjectionDefaultModel implements ControllableEquipment { +public class DefaultLoad extends AbstractInjectionDefaultModel implements DefaultControllableLoadModel { public DefaultLoad(String staticId) { super(staticId); @@ -28,4 +27,9 @@ public String getName() { public String getDeltaPVarName() { return "@NAME@_DeltaPc"; } + + @Override + public String getDeltaQVarName() { + return "@NAME@_DeltaQc"; + } } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/AbstractXmlDynawoSimulationWriter.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/AbstractXmlDynawoSimulationWriter.java index e3c2478cd..a181a9c65 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/AbstractXmlDynawoSimulationWriter.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/AbstractXmlDynawoSimulationWriter.java @@ -8,7 +8,6 @@ package com.powsybl.dynawo.xml; import com.powsybl.commons.exceptions.UncheckedXmlStreamException; -import com.powsybl.dynawo.DynawoSimulationContext; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -25,7 +24,7 @@ /** * @author Florian Dupuy {@literal } */ -public abstract class AbstractXmlDynawoSimulationWriter implements XmlDynawoSimulationWriter { +abstract class AbstractXmlDynawoSimulationWriter { private final String xmlFileName; private final String xmlRootName; @@ -35,10 +34,9 @@ protected AbstractXmlDynawoSimulationWriter(String xmlFileName, String xmlRootNa this.xmlRootName = Objects.requireNonNull(xmlRootName); } - @Override - public void createXmlFileFromContext(Path workingDir, DynawoSimulationContext context) throws IOException { + public void createXmlFileFromDataSupplier(Path workingDir, T dataSupplier) throws IOException { Objects.requireNonNull(workingDir); - Objects.requireNonNull(context); + Objects.requireNonNull(dataSupplier); Path file = workingDir.resolve(xmlFileName); try (Writer writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { @@ -49,7 +47,7 @@ public void createXmlFileFromContext(Path workingDir, DynawoSimulationContext co xmlWriter.writeStartElement(DYN_URI, xmlRootName); xmlWriter.writeNamespace(DYN_PREFIX, DYN_URI); - write(xmlWriter, context); + write(xmlWriter, dataSupplier); xmlWriter.writeEndElement(); xmlWriter.writeEndDocument(); @@ -61,5 +59,5 @@ public void createXmlFileFromContext(Path workingDir, DynawoSimulationContext co } } - abstract void write(XMLStreamWriter writer, DynawoSimulationContext dynawoSimulationContext) throws XMLStreamException; + abstract void write(XMLStreamWriter writer, T dataSupplier) throws XMLStreamException; } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/DydDataSupplier.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/DydDataSupplier.java new file mode 100644 index 000000000..df5878dfe --- /dev/null +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/DydDataSupplier.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2024, 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.dynawo.xml; + +import com.powsybl.dynawo.models.BlackBoxModel; +import com.powsybl.dynawo.models.macroconnections.MacroConnect; +import com.powsybl.dynawo.models.macroconnections.MacroConnector; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * @author Laurent Issertial + */ +public interface DydDataSupplier { + + default List getBlackBoxDynamicModels() { + return Collections.emptyList(); + } + + default List getBlackBoxEventModels() { + return Collections.emptyList(); + } + + Collection getMacroConnectors(); + + default Collection getMacroStaticReferences() { + return Collections.emptySet(); + } + + List getMacroConnectList(); + + default String getParFileName() { + return null; + } +} diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/DydXml.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/DydXml.java index 330ac5ed2..b3dd53bdc 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/DydXml.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/DydXml.java @@ -7,7 +7,6 @@ package com.powsybl.dynawo.xml; -import com.powsybl.dynawo.DynawoSimulationContext; import com.powsybl.dynawo.models.BlackBoxModel; import com.powsybl.dynawo.models.macroconnections.MacroConnect; import com.powsybl.dynawo.models.macroconnections.MacroConnector; @@ -22,32 +21,50 @@ /** * @author Mathieu Bague {@literal } */ -public final class DydXml extends AbstractXmlDynawoSimulationWriter { +public final class DydXml extends AbstractXmlDynawoSimulationWriter { + + private DydXml(String fileName) { + super(fileName, "dynamicModelsArchitecture"); + } private DydXml() { - super(DYD_FILENAME, "dynamicModelsArchitecture"); + this(DYD_FILENAME); } - public static void write(Path workingDir, DynawoSimulationContext context) throws IOException { - new DydXml().createXmlFileFromContext(workingDir, context); + public static void write(Path workingDir, DydDataSupplier dataSupplier) throws IOException { + new DydXml().createXmlFileFromDataSupplier(workingDir, dataSupplier); + } + + public static void write(Path workingDir, String fileName, DydDataSupplier dataSupplier) throws IOException { + new DydXml(fileName).createXmlFileFromDataSupplier(workingDir, dataSupplier); } @Override - public void write(XMLStreamWriter writer, DynawoSimulationContext context) throws XMLStreamException { + public void write(XMLStreamWriter writer, DydDataSupplier dataSupplier) throws XMLStreamException { // loop over the values of the map indexed by dynamicIds to write only once objects with the same dynamicId - for (BlackBoxModel model : context.getBlackBoxDynamicModels()) { - model.write(writer, context); - } - for (BlackBoxModel model : context.getBlackBoxEventModels()) { - model.write(writer, context); + String parFileName = dataSupplier.getParFileName(); + if (parFileName == null || parFileName.isEmpty()) { + for (BlackBoxModel model : dataSupplier.getBlackBoxDynamicModels()) { + model.write(writer); + } + for (BlackBoxModel model : dataSupplier.getBlackBoxEventModels()) { + model.write(writer); + } + } else { + for (BlackBoxModel model : dataSupplier.getBlackBoxDynamicModels()) { + model.write(writer, dataSupplier.getParFileName()); + } + for (BlackBoxModel model : dataSupplier.getBlackBoxEventModels()) { + model.write(writer, dataSupplier.getParFileName()); + } } - for (MacroConnector macroConnector : context.getMacroConnectors()) { + for (MacroConnector macroConnector : dataSupplier.getMacroConnectors()) { macroConnector.write(writer); } - for (MacroStaticReference macroStaticReference : context.getMacroStaticReferences()) { + for (MacroStaticReference macroStaticReference : dataSupplier.getMacroStaticReferences()) { macroStaticReference.write(writer); } - for (MacroConnect macroConnect : context.getMacroConnectList()) { + for (MacroConnect macroConnect : dataSupplier.getMacroConnectList()) { macroConnect.write(writer); } } diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/JobsXml.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/JobsXml.java index 0d0fa9f2f..992142e08 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/JobsXml.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/JobsXml.java @@ -6,7 +6,6 @@ */ package com.powsybl.dynawo.xml; -import com.powsybl.dynamicsimulation.DynamicSimulationParameters; import com.powsybl.dynawo.DumpFileParameters; import com.powsybl.dynawo.DynawoSimulationContext; import com.powsybl.dynawo.DynawoSimulationParameters; @@ -20,6 +19,7 @@ import java.util.Optional; import static com.powsybl.dynawo.DynawoSimulationConstants.*; +import static com.powsybl.dynawo.DynawoSimulationConstants.DYD_FILENAME; import static com.powsybl.dynawo.commons.DynawoConstants.NETWORK_FILENAME; import static com.powsybl.dynawo.commons.DynawoConstants.OUTPUTS_FOLDER; import static com.powsybl.dynawo.xml.DynawoSimulationXmlConstants.DYN_URI; @@ -27,16 +27,22 @@ /** * @author Marcos de Miguel {@literal } */ -public final class JobsXml extends AbstractXmlDynawoSimulationWriter { +public final class JobsXml extends AbstractXmlDynawoSimulationWriter { private static final String EXPORT_MODE = "exportMode"; + private final boolean isPhase2; - private JobsXml() { - super(JOBS_FILENAME, "jobs"); + private JobsXml(String xmlFileName, boolean isPhase2) { + super(xmlFileName, "jobs"); + this.isPhase2 = isPhase2; } public static void write(Path workingDir, DynawoSimulationContext context) throws IOException { - new JobsXml().createXmlFileFromContext(workingDir, context); + new JobsXml(JOBS_FILENAME, false).createXmlFileFromDataSupplier(workingDir, context); + } + + public static void writePhase2(Path workingDir, DynawoSimulationContext context) throws IOException { + new JobsXml(PHASE_2_JOBS_FILENAME, true).createXmlFileFromDataSupplier(workingDir, context); } @Override @@ -45,8 +51,8 @@ public void write(XMLStreamWriter writer, DynawoSimulationContext context) throw writer.writeStartElement(DYN_URI, "job"); writer.writeAttribute("name", "Job"); writeSolver(writer, parameters); - writeModeler(writer, parameters); - writeSimulation(writer, parameters, context.getParameters()); + writeModeler(writer, parameters, isPhase2 && context.getPhase2DydData().isPresent()); + writeSimulation(writer, parameters, context.getStartTime(isPhase2), context.getStopTime(isPhase2)); writeOutput(writer, context); writer.writeEndElement(); } @@ -58,7 +64,7 @@ private static void writeSolver(XMLStreamWriter writer, DynawoSimulationParamete writer.writeAttribute("parId", parameters.getSolverParameters().getId()); } - private static void writeModeler(XMLStreamWriter writer, DynawoSimulationParameters parameters) throws XMLStreamException { + private static void writeModeler(XMLStreamWriter writer, DynawoSimulationParameters parameters, boolean phase2) throws XMLStreamException { writer.writeStartElement(DYN_URI, "modeler"); writer.writeAttribute("compileDir", "outputs/compilation"); @@ -69,6 +75,10 @@ private static void writeModeler(XMLStreamWriter writer, DynawoSimulationParamet writer.writeEmptyElement(DYN_URI, "dynModels"); writer.writeAttribute("dydFile", DYD_FILENAME); + if (phase2) { + writer.writeEmptyElement(DYN_URI, "dynModels"); + writer.writeAttribute("dydFile", PHASE_2_DYD_FILENAME); + } DumpFileParameters dumpFileParameters = parameters.getDumpFileParameters(); if (dumpFileParameters.useDumpFile()) { @@ -85,15 +95,15 @@ private static void writeModeler(XMLStreamWriter writer, DynawoSimulationParamet writer.writeEndElement(); } - private static void writeSimulation(XMLStreamWriter writer, DynawoSimulationParameters parameters, DynamicSimulationParameters dynamicSimulationParameters) throws XMLStreamException { + private static void writeSimulation(XMLStreamWriter writer, DynawoSimulationParameters parameters, double startTime, double stopTime) throws XMLStreamException { Optional criteriaFileName = parameters.getCriteriaFileName(); if (criteriaFileName.isPresent()) { writer.writeStartElement(DYN_URI, "simulation"); } else { writer.writeEmptyElement(DYN_URI, "simulation"); } - writer.writeAttribute("startTime", Double.toString(dynamicSimulationParameters.getStartTime())); - writer.writeAttribute("stopTime", Double.toString(dynamicSimulationParameters.getStopTime())); + writer.writeAttribute("startTime", Double.toString(startTime)); + writer.writeAttribute("stopTime", Double.toString(stopTime)); writer.writeAttribute("precision", Double.toString(parameters.getPrecision())); if (criteriaFileName.isPresent()) { writer.writeEmptyElement(DYN_URI, "criteria"); diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/OutputVariablesXml.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/OutputVariablesXml.java index 2df3504fb..b5147f66c 100644 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/OutputVariablesXml.java +++ b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/OutputVariablesXml.java @@ -21,7 +21,7 @@ /** * @author Marcos de Miguel {@literal } */ -public final class OutputVariablesXml extends AbstractXmlDynawoSimulationWriter { +public final class OutputVariablesXml extends AbstractXmlDynawoSimulationWriter { private final OutputVariable.OutputType outputType; private final String xmlElementName; @@ -34,12 +34,12 @@ private OutputVariablesXml(String xmlFileName, String xmlRootName, String xmlEle public static void writeCurve(Path workingDir, DynawoSimulationContext context) throws IOException { new OutputVariablesXml(CRV_FILENAME, "curvesInput", "curve", OutputVariable.OutputType.CURVE) - .createXmlFileFromContext(workingDir, context); + .createXmlFileFromDataSupplier(workingDir, context); } public static void writeFsv(Path workingDir, DynawoSimulationContext context) throws IOException { new OutputVariablesXml(FSV_FILENAME, "finalStateValuesInput", "finalStateValue", OutputVariable.OutputType.FINAL_STATE) - .createXmlFileFromContext(workingDir, context); + .createXmlFileFromDataSupplier(workingDir, context); } @Override diff --git a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/XmlDynawoSimulationWriter.java b/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/XmlDynawoSimulationWriter.java deleted file mode 100644 index 34d10b845..000000000 --- a/dynawo-simulation/src/main/java/com/powsybl/dynawo/xml/XmlDynawoSimulationWriter.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2023, 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.dynawo.xml; - -import com.powsybl.dynawo.DynawoSimulationContext; - -import java.io.IOException; -import java.nio.file.Path; - -/** - * @author Florian Dupuy {@literal } - */ -@FunctionalInterface -public interface XmlDynawoSimulationWriter { - void createXmlFileFromContext(Path workingDir, DynawoSimulationContext context) throws IOException; -} diff --git a/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/AbstractDynamicModelXmlTest.java b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/AbstractDynamicModelXmlTest.java index a207ef663..1778b71e2 100644 --- a/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/AbstractDynamicModelXmlTest.java +++ b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/AbstractDynamicModelXmlTest.java @@ -77,7 +77,7 @@ public void validate(String schemaDefinition, String expectedResourceName, Path assertTxtEquals(expected, actual); } - void setupDynawoContext() { + protected void setupDynawoContext() { DynamicSimulationParameters parameters = DynamicSimulationParameters.load(); DynawoSimulationParameters dynawoParameters = DynawoSimulationParameters.load(); context = new DynawoSimulationContext(network, network.getVariantManager().getWorkingVariantId(), dynamicModels, eventModels, outputVariables, parameters, dynawoParameters, DynawoConstants.VERSION_MIN, reportNode); diff --git a/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/AbstractParametrizedDynamicModelXmlTest.java b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/AbstractParametrizedDynamicModelXmlTest.java index 821101470..e91007a93 100644 --- a/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/AbstractParametrizedDynamicModelXmlTest.java +++ b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/AbstractParametrizedDynamicModelXmlTest.java @@ -14,6 +14,7 @@ import com.powsybl.dynamicsimulation.OutputVariable; import com.powsybl.dynawo.DynawoSimulationContext; import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.Phase2Config; import com.powsybl.dynawo.commons.DynawoConstants; import com.powsybl.dynawo.models.BlackBoxModel; import com.powsybl.iidm.network.Network; @@ -61,10 +62,15 @@ public void validate(String schemaDefinition, String expectedResourceName, Path assertTxtEquals(expected, actual); } - protected void setupDynawoContext() { + void setupDynawoContext() { + setupDynawoContext(null); + } + + protected void setupDynawoContext(Phase2Config phase2Config) { DynamicSimulationParameters parameters = DynamicSimulationParameters.load(); DynawoSimulationParameters dynawoParameters = DynawoSimulationParameters.load(); - context = new DynawoSimulationContext(network, network.getVariantManager().getWorkingVariantId(), dynamicModels, eventModels, outputVariables, parameters, dynawoParameters, DynawoConstants.VERSION_MIN, reportNode); + context = new DynawoSimulationContext(network, network.getVariantManager().getWorkingVariantId(), dynamicModels, + eventModels, outputVariables, parameters, dynawoParameters, phase2Config, DynawoConstants.VERSION_MIN, reportNode); } protected void checkReport(String report) throws IOException { diff --git a/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/ContextPhase2XmlTest.java b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/ContextPhase2XmlTest.java new file mode 100644 index 000000000..fb284fb2c --- /dev/null +++ b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/ContextPhase2XmlTest.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2024, 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.dynawo.xml; + +import com.powsybl.dynawo.Phase2Config; +import com.powsybl.dynawo.models.BlackBoxModel; +import com.powsybl.dynawo.models.loads.BaseLoadBuilder; +import com.powsybl.dynawo.models.shunts.BaseShuntBuilder; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static com.powsybl.dynawo.DynawoSimulationConstants.DYD_FILENAME; +import static com.powsybl.dynawo.DynawoSimulationConstants.PHASE_2_DYD_FILENAME; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Laurent Issertial {@literal } + */ +@ExtendWith(CustomParameterResolver.class) +class ContextPhase2XmlTest extends AbstractParametrizedDynamicModelXmlTest { + + @BeforeEach + void setup(String dydName, String phase2DydName, Predicate phase2ModelsPredicate) { + setupNetwork(); + addDynamicModels(); + setupDynawoContext(new Phase2Config(200, phase2ModelsPredicate)); + } + + protected void setupNetwork() { + network = EurostagTutorialExample1Factory.createWithMultipleConnectedComponents(); + } + + protected void addDynamicModels() { + dynamicModels.add(BaseShuntBuilder.of(network) + .staticId("SHUNT") + .parameterSetId("sh") + .build()); + dynamicModels.add(BaseLoadBuilder.of(network) + .staticId("LOAD") + .parameterSetId("lab") + .build()); + dynamicModels.add(BaseLoadBuilder.of(network) + .staticId("LOAD2") + .parameterSetId("lab") + .build()); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("provideBbm") + void writePhase2Models(String dydName, String phase2DydName, Predicate phase2ModelsPredicate) throws SAXException, IOException { + DydXml.write(tmpDir, context); + validate("dyd.xsd", dydName, tmpDir.resolve(DYD_FILENAME)); + assertThat(context.getPhase2DydData()).isPresent(); + DydXml.write(tmpDir, PHASE_2_DYD_FILENAME, context.getPhase2DydData().get()); + validate("dyd.xsd", phase2DydName, tmpDir.resolve(PHASE_2_DYD_FILENAME)); + } + + private static Stream provideBbm() { + return Stream.of( + Arguments.of("no_loads_phase_1_dyd.xml", "no_loads_phase_2_dyd.xml", + (Predicate) bbm -> bbm.getLib().equalsIgnoreCase("LoadAlphaBeta")), + Arguments.of("specific_load_phase_1_dyd.xml", "specific_load_phase_2_dyd.xml", + (Predicate) bbm -> bbm.getDynamicModelId().equalsIgnoreCase("LOAD"))); + } +} diff --git a/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/JobsXmlTest.java b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/JobsXmlTest.java index 24121c1fe..656f1ac29 100644 --- a/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/JobsXmlTest.java +++ b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/JobsXmlTest.java @@ -6,11 +6,11 @@ */ package com.powsybl.dynawo.xml; +import com.powsybl.commons.report.ReportNode; import com.powsybl.dynamicsimulation.DynamicSimulationParameters; -import com.powsybl.dynawo.DumpFileParameters; -import com.powsybl.dynawo.DynawoSimulationConstants; -import com.powsybl.dynawo.DynawoSimulationContext; -import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.*; +import com.powsybl.dynawo.commons.DynawoConstants; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -46,4 +46,17 @@ private static Stream provideParameters() { .setCriteriaFilePath(Path.of("criteria.crt"))) ); } + + @Test + void writePhase2Job() throws SAXException, IOException { + DynamicSimulationParameters parameters = DynamicSimulationParameters.load(); + DynawoSimulationParameters dynawoParameters = DynawoSimulationParameters.load(); + DynawoSimulationContext context = new DynawoSimulationContext(network, network.getVariantManager().getWorkingVariantId(), + dynamicModels, eventModels, outputVariables, parameters, dynawoParameters, + new Phase2Config(200, bbm -> bbm.getDynamicModelId().equalsIgnoreCase("BBM_LOAD2")), + DynawoConstants.VERSION_MIN, ReportNode.NO_OP); + + JobsXml.writePhase2(tmpDir, context); + validate("jobs.xsd", "jobsWithPhase2.xml", tmpDir.resolve(DynawoSimulationConstants.PHASE_2_JOBS_FILENAME)); + } } diff --git a/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/MixedFreqSynchSignalNXmlTest.java b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/MixedFreqSynchSignalNXmlTest.java index d7f02b1fc..45a6bbfd9 100644 --- a/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/MixedFreqSynchSignalNXmlTest.java +++ b/dynawo-simulation/src/test/java/com/powsybl/dynawo/xml/MixedFreqSynchSignalNXmlTest.java @@ -41,7 +41,7 @@ protected void addDynamicModels() { } @Override - void setupDynawoContext() { + protected void setupDynawoContext() { // empty to call super in test } diff --git a/dynawo-simulation/src/test/resources/jobsWithPhase2.xml b/dynawo-simulation/src/test/resources/jobsWithPhase2.xml new file mode 100644 index 000000000..617ad6a39 --- /dev/null +++ b/dynawo-simulation/src/test/resources/jobsWithPhase2.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynawo-simulation/src/test/resources/no_loads_phase_1_dyd.xml b/dynawo-simulation/src/test/resources/no_loads_phase_1_dyd.xml new file mode 100644 index 000000000..fae160f24 --- /dev/null +++ b/dynawo-simulation/src/test/resources/no_loads_phase_1_dyd.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/dynawo-simulation/src/test/resources/no_loads_phase_2_dyd.xml b/dynawo-simulation/src/test/resources/no_loads_phase_2_dyd.xml new file mode 100644 index 000000000..cf449d13a --- /dev/null +++ b/dynawo-simulation/src/test/resources/no_loads_phase_2_dyd.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/dynawo-simulation/src/test/resources/specific_load_phase_1_dyd.xml b/dynawo-simulation/src/test/resources/specific_load_phase_1_dyd.xml new file mode 100644 index 000000000..f830b9577 --- /dev/null +++ b/dynawo-simulation/src/test/resources/specific_load_phase_1_dyd.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dynawo-simulation/src/test/resources/specific_load_phase_2_dyd.xml b/dynawo-simulation/src/test/resources/specific_load_phase_2_dyd.xml new file mode 100644 index 000000000..77740e1aa --- /dev/null +++ b/dynawo-simulation/src/test/resources/specific_load_phase_2_dyd.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pom.xml b/pom.xml index 3e4f7e22c..ea042ee37 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ 17 - 6.6.0 + 6.7.0-SNAPSHOT 4.0.14 0.3.2 2.17.1