diff --git a/docs/dynamic_simulation/curves-dsl.md b/docs/dynamic_simulation/curves-dsl.md index 96c4e0c54..216d70b5c 100644 --- a/docs/dynamic_simulation/curves-dsl.md +++ b/docs/dynamic_simulation/curves-dsl.md @@ -1,7 +1,7 @@ # Curves DSL -The curves domain specific language allow a user to configure the curves Dynawo will export at the end of the simulation. This DSL defines the `curve` and the `curves` keywords. +The curves domain specific language allow a user to configure the curves Dynawo will export at the end of the simulation. This DSL defines the `curve` keywords. -The `curve` keyword create a single curve for a dynamic model. One identifies a dynamic model by its ID, the same as the one used in the [Dynamic Models DSL](dynamic-models-dsl). The variable to plot is identified by its name. +The `curve` keyword combined with the `variable` field create a single curve for a dynamic model. One identifies a dynamic model by its ID, the same as the one used in the [Dynamic Models DSL](dynamic-models-dsl). The variable to plot is identified by its name. ```groovy curve { dynamicModelId load.id @@ -17,7 +17,7 @@ curve { } ``` -If you want to plot several variables of the same dynamic model, you can use the `curves` keyword that permit limiting boilerplate code in the script. +If you want to plot several variables of the same dynamic model, you can use the `variables` field that permit limiting boilerplate code in the script. ``` // This: curve { @@ -30,7 +30,7 @@ curve { } // is equivalent to: -curves { +curve { dynamicModelId load.id variables "load_PPu", "load_QPu" } diff --git a/dynawaltz-dsl/src/main/groovy/com/powsybl/dynawaltz/dsl/DynaWaltzCurveGroovyExtension.groovy b/dynawaltz-dsl/src/main/groovy/com/powsybl/dynawaltz/dsl/DynaWaltzCurveGroovyExtension.groovy index 36b0f4fc1..855c9902a 100644 --- a/dynawaltz-dsl/src/main/groovy/com/powsybl/dynawaltz/dsl/DynaWaltzCurveGroovyExtension.groovy +++ b/dynawaltz-dsl/src/main/groovy/com/powsybl/dynawaltz/dsl/DynaWaltzCurveGroovyExtension.groovy @@ -8,15 +8,12 @@ package com.powsybl.dynawaltz.dsl import com.google.auto.service.AutoService import com.powsybl.commons.report.ReportNode -import com.powsybl.dsl.DslException import com.powsybl.dynamicsimulation.Curve import com.powsybl.dynamicsimulation.groovy.CurveGroovyExtension - -import com.powsybl.dynawaltz.DynaWaltzCurve import com.powsybl.dynawaltz.DynaWaltzProvider +import com.powsybl.dynawaltz.curves.DynawoCurvesBuilder import java.util.function.Consumer - /** * An implementation of {@link CurveGroovyExtension} that adds the
curve
keyword to the DSL * @@ -25,80 +22,20 @@ import java.util.function.Consumer @AutoService(CurveGroovyExtension.class) class DynaWaltzCurveGroovyExtension implements CurveGroovyExtension { - /** - * A curve for
DynaWaltz
can be defined in DSL using {@code staticId} and {@code variable} or {@code dynamicModelId} and {@code variable}. - * Definition with {@code staticId} and {@code variable} are used when no explicit dynamic component exists (buses). - *
DynaWaltz
expects {@code dynamicModelId} = “NETWORK” for these variables. - */ - static class CurvesSpec { - String dynamicModelId - String staticId - String[] variables - - void dynamicModelId(String dynamicModelId) { - this.dynamicModelId = dynamicModelId - } - - void staticId(String staticId) { - this.staticId = staticId - } - - void variables(String[] variables) { - this.variables = variables - } - - void variable(String variable) { - this.variables = [variable] - } - } - @Override String getName() { DynaWaltzProvider.NAME } - DynaWaltzCurve dynawoCurve(CurvesSpec curveSpec, Consumer consumer) { - - if (curveSpec.staticId && curveSpec.dynamicModelId) { - throw new DslException("Both staticId and dynamicModelId are defined") - } - if (!curveSpec.variables) { - throw new DslException("'variables' field is not set") - } - if (curveSpec.variables.length == 0) { - throw new DslException("'variables' field is empty") - } - - for (String variable : curveSpec.variables) { - if (curveSpec.staticId) { - consumer.accept(new DynaWaltzCurve("NETWORK", curveSpec.staticId + "_" + variable)) - } else { - consumer.accept(new DynaWaltzCurve(curveSpec.dynamicModelId, variable)) - } - } - } - @Override void load(Binding binding, Consumer consumer, ReportNode reportNode) { - binding.curve = { Closure closure -> - def cloned = closure.clone() - CurvesSpec curveSpec = new CurvesSpec() - - cloned.delegate = curveSpec - cloned() - - dynawoCurve(curveSpec, consumer) - } - - binding.curves = { Closure closure -> + Closure closure = { Closure closure -> def cloned = closure.clone() - CurvesSpec curvesSpec = new CurvesSpec() - - cloned.delegate = curvesSpec + DynawoCurvesBuilder curvesBuilder = new DynawoCurvesBuilder(reportNode) + cloned.delegate = curvesBuilder cloned() - - dynawoCurve(curvesSpec, consumer) + curvesBuilder.add(consumer) } + binding.curve = closure } - } diff --git a/dynawaltz-dsl/src/test/java/com/powsybl/dynawaltz/dsl/DynaWaltzGroovyCurvesSupplierTest.java b/dynawaltz-dsl/src/test/java/com/powsybl/dynawaltz/dsl/DynaWaltzGroovyCurvesSupplierTest.java index cd04ca96e..2b540613b 100644 --- a/dynawaltz-dsl/src/test/java/com/powsybl/dynawaltz/dsl/DynaWaltzGroovyCurvesSupplierTest.java +++ b/dynawaltz-dsl/src/test/java/com/powsybl/dynawaltz/dsl/DynaWaltzGroovyCurvesSupplierTest.java @@ -8,13 +8,12 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; -import com.powsybl.dsl.DslException; import com.powsybl.dynamicsimulation.Curve; import com.powsybl.dynamicsimulation.CurvesSupplier; import com.powsybl.dynamicsimulation.groovy.CurveGroovyExtension; import com.powsybl.dynamicsimulation.groovy.GroovyCurvesSupplier; import com.powsybl.dynamicsimulation.groovy.GroovyExtension; -import com.powsybl.dynawaltz.DynaWaltzCurve; +import com.powsybl.dynawaltz.curves.DynawoCurve; import com.powsybl.dynawaltz.DynaWaltzProvider; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Load; @@ -23,9 +22,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; 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; import java.io.IOException; import java.nio.file.FileSystem; @@ -33,14 +29,13 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; /** * @author Marcos de Miguel {@literal } */ -class DynaWaltzGroovyCurvesSupplierTest { +class DynaWaltzGroovyCurvesSupplierTest extends AbstractModelSupplierTest { private FileSystem fileSystem; private Network network; @@ -51,9 +46,6 @@ void setup() throws IOException { network = EurostagTutorialExample1Factory.create(); Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/curves.groovy")), fileSystem.getPath("/curves.groovy")); - Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/curves_dynamicModelId_staticId.groovy")), fileSystem.getPath("/curves_dynamicModelId_staticId.groovy")); - Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/curves_variable.groovy")), fileSystem.getPath("/curves_variable.groovy")); - Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/curves_variables.groovy")), fileSystem.getPath("/curves_variables.groovy")); } @AfterEach @@ -70,23 +62,6 @@ void test() { curves.forEach(this::validateCurve); } - @ParameterizedTest(name = "{1}") - @MethodSource("provideFileError") - void testScriptError(String fileName, String error) { - List extensions = validateGroovyExtension(); - CurvesSupplier supplier = new GroovyCurvesSupplier(fileSystem.getPath(fileName), extensions); - DslException exception = assertThrows(DslException.class, () -> supplier.get(network)); - assertEquals(error, exception.getMessage()); - } - - private static Stream provideFileError() { - return Stream.of( - Arguments.of("/curves_dynamicModelId_staticId.groovy", "Both staticId and dynamicModelId are defined"), - Arguments.of("/curves_variable.groovy", "'variables' field is not set"), - Arguments.of("/curves_variables.groovy", "'variables' field is not set") - ); - } - private List validateGroovyExtension() { List extensions = GroovyExtension.find(CurveGroovyExtension.class, DynaWaltzProvider.NAME); assertEquals(1, extensions.size()); @@ -95,8 +70,8 @@ private List validateGroovyExtension() { } private void validateCurve(Curve curve) { - assertEquals(DynaWaltzCurve.class, curve.getClass()); - DynaWaltzCurve curveImpl = (DynaWaltzCurve) curve; + assertEquals(DynawoCurve.class, curve.getClass()); + DynawoCurve curveImpl = (DynawoCurve) curve; if (curveImpl.getModelId().equals("NETWORK")) { assertTrue(Arrays.asList("NGEN_Upu_value", "NHV1_Upu_value", "NHV2_Upu_value", "NLOAD_Upu_value").contains(curveImpl.getVariable())); } else if (network.getIdentifiable(curveImpl.getModelId()) instanceof Generator) { diff --git a/dynawaltz-dsl/src/test/resources/curves.groovy b/dynawaltz-dsl/src/test/resources/curves.groovy index 9e4de3021..49c11a42d 100644 --- a/dynawaltz-dsl/src/test/resources/curves.groovy +++ b/dynawaltz-dsl/src/test/resources/curves.groovy @@ -17,7 +17,7 @@ for (Bus bus : network.busBreakerView.buses) { } for (Generator gen : network.generators) { - curves { + curve { dynamicModelId gen.id variables "generator_omegaPu", "generator_PGen", "generator_UStatorPU", "voltageRegulator_UcEfdP", "voltageRegulator_EfdPu" } diff --git a/dynawaltz-dsl/src/test/resources/curves_dynamicModelId_staticId.groovy b/dynawaltz-dsl/src/test/resources/curves_dynamicModelId_staticId.groovy deleted file mode 100644 index 24d5c61f5..000000000 --- a/dynawaltz-dsl/src/test/resources/curves_dynamicModelId_staticId.groovy +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2020, 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/. - */ - -import com.powsybl.iidm.network.Bus - -for (Bus bus : network.busBreakerView.buses) { - curve { - dynamicModelId "NETWORK" - staticId bus.id - variable "Upu_value" - } -} diff --git a/dynawaltz-dsl/src/test/resources/curves_variable.groovy b/dynawaltz-dsl/src/test/resources/curves_variable.groovy deleted file mode 100644 index a37d35c66..000000000 --- a/dynawaltz-dsl/src/test/resources/curves_variable.groovy +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2020, 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/. - */ - -import com.powsybl.iidm.network.Bus - -for (Bus bus : network.busBreakerView.buses) { - curve { - staticId bus.id - } -} diff --git a/dynawaltz-dsl/src/test/resources/curves_variables.groovy b/dynawaltz-dsl/src/test/resources/curves_variables.groovy deleted file mode 100644 index 7c293e4df..000000000 --- a/dynawaltz-dsl/src/test/resources/curves_variables.groovy +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2020, 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/. - */ - -import com.powsybl.iidm.network.Generator - -for (Generator gen : network.generators) { - curves { - dynamicModelId gen.id - } -} diff --git a/dynawaltz-dsl/src/test/resources/ieee14-disconnectline/powsybl-inputs/curves.groovy b/dynawaltz-dsl/src/test/resources/ieee14-disconnectline/powsybl-inputs/curves.groovy index 7bf0f8979..e7acd795b 100644 --- a/dynawaltz-dsl/src/test/resources/ieee14-disconnectline/powsybl-inputs/curves.groovy +++ b/dynawaltz-dsl/src/test/resources/ieee14-disconnectline/powsybl-inputs/curves.groovy @@ -17,7 +17,7 @@ for (Bus bus : network.busBreakerView.buses) { } for (Generator gen : network.generators) { - curves { + curve { dynamicModelId gen.id variables "generator_omegaPu", "generator_PGen", "generator_QGen", "generator_UStatorPu", "voltageRegulator_EfdPu" } diff --git a/dynawaltz-dsl/src/test/resources/ieee14-macroconnects/powsybl-inputs/curves.groovy b/dynawaltz-dsl/src/test/resources/ieee14-macroconnects/powsybl-inputs/curves.groovy index ae2e1aadd..a0e03268a 100644 --- a/dynawaltz-dsl/src/test/resources/ieee14-macroconnects/powsybl-inputs/curves.groovy +++ b/dynawaltz-dsl/src/test/resources/ieee14-macroconnects/powsybl-inputs/curves.groovy @@ -17,7 +17,7 @@ for (Bus bus : network.busBreakerView.buses) { } for (Generator gen : network.generators) { - curves { + curve { dynamicModelId gen.id variables "generator_omegaPu", "generator_PGen", "generator_QGen", "generator_UStatorPu", "voltageRegulator_EfdPu" } diff --git a/dynawaltz-dsl/src/test/resources/ieee14-overloadmanagementsystem/powsybl-inputs/curves.groovy b/dynawaltz-dsl/src/test/resources/ieee14-overloadmanagementsystem/powsybl-inputs/curves.groovy index 2bfb3a04a..bc49f3dfa 100644 --- a/dynawaltz-dsl/src/test/resources/ieee14-overloadmanagementsystem/powsybl-inputs/curves.groovy +++ b/dynawaltz-dsl/src/test/resources/ieee14-overloadmanagementsystem/powsybl-inputs/curves.groovy @@ -17,38 +17,38 @@ for (Bus bus : network.busBreakerView.buses) { } for (Generator gen : network.generators) { - curves { + curve { dynamicModelId gen.id variables "generator_omegaPu", "generator_PGen", "generator_QGen", "generator_UStatorPu", "voltageRegulator_EfdPu" } } -curves { +curve { dynamicModelId "_LOAD___2_EC" variables "load_PPu", "load_QPu" } -curves { +curve { staticId "_BUS____2-BUS____4-1_AC" variables "iSide2", "state" } -curves { +curve { dynamicModelId "CLA_2_4" variables "currentLimitAutomaton_order", "currentLimitAutomaton_IMax" } -curves { +curve { staticId "_BUS____1-BUS____5-1_AC" variables "iSide2", "state" } -curves { +curve { staticId "_BUS____2-BUS____5-1_AC" variables "iSide2", "state" } -curves { +curve { dynamicModelId "CLA_2_5" variables "currentLimitAutomaton_order", "currentLimitAutomaton_IMax" } diff --git a/dynawaltz-dsl/src/test/resources/ieee57-disconnectgenerator/powsybl-inputs/curves.groovy b/dynawaltz-dsl/src/test/resources/ieee57-disconnectgenerator/powsybl-inputs/curves.groovy index fcec6a943..8d5dee563 100644 --- a/dynawaltz-dsl/src/test/resources/ieee57-disconnectgenerator/powsybl-inputs/curves.groovy +++ b/dynawaltz-dsl/src/test/resources/ieee57-disconnectgenerator/powsybl-inputs/curves.groovy @@ -19,7 +19,7 @@ for (Bus bus : network.busBreakerView.buses) { } for (Generator gen : network.generators) { - curves { + curve { dynamicModelId gen.id variables "generator_omegaPu", "generator_PGen", "generator_QGen", "generator_UStatorPu" } diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzContext.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzContext.java index 7db4b9572..96290715f 100644 --- a/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzContext.java +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzContext.java @@ -10,6 +10,7 @@ import com.powsybl.commons.report.ReportNode; import com.powsybl.dynamicsimulation.Curve; import com.powsybl.dynamicsimulation.DynamicSimulationParameters; +import com.powsybl.dynawaltz.curves.DynawoCurve; import com.powsybl.dynawaltz.models.AbstractPureDynamicBlackBoxModel; import com.powsybl.dynawaltz.models.BlackBoxModel; import com.powsybl.dynawaltz.models.EquipmentBlackBoxModel; @@ -54,7 +55,7 @@ public class DynaWaltzContext { private final List dynamicModels; private final List eventModels; private final Map staticIdBlackBoxModelMap; - private final List curves; + private final List curves; private final Map macroStaticReferences = new LinkedHashMap<>(); private final List macroConnectList = new ArrayList<>(); private final Map macroConnectorsMap = new LinkedHashMap<>(); @@ -97,7 +98,10 @@ public DynaWaltzContext(Network network, String workingVariantId, List e.setEquipmentHasDynamicModel(this)); - this.curves = Objects.requireNonNull(curves); + this.curves = Objects.requireNonNull(curves).stream() + .filter(DynawoCurve.class::isInstance) + .map(DynawoCurve.class::cast) + .toList(); this.frequencySynchronizer = setupFrequencySynchronizer(dynamicModels.stream().anyMatch(AbstractBus.class::isInstance) ? SetPoint::new : OmegaRef::new); this.macroConnectionsAdder = new MacroConnectionsAdder(this::getDynamicModel, this::getPureDynamicModel, @@ -256,8 +260,8 @@ public List getBlackBoxEventModels() { return eventModels; } - public List getCurves() { - return Collections.unmodifiableList(curves); + public List getCurves() { + return curves; } public boolean withCurves() { diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/builders/BuilderReports.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/builders/BuilderReports.java index 0e399d6fc..a9f210f25 100644 --- a/dynawaltz/src/main/java/com/powsybl/dynawaltz/builders/BuilderReports.java +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/builders/BuilderReports.java @@ -54,6 +54,14 @@ public static void reportModelInstantiationFailure(ReportNode reportNode, String .add(); } + public static void reportCurveInstantiationFailure(ReportNode reportNode, String id) { + reportNode.newReportNode() + .withMessageTemplate("curveInstantiationError", "Curve ${id} cannot be instantiated") + .withUntypedValue("id", id) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + public static void reportFieldReplacement(ReportNode reportNode, String fieldName, String replacementName, String replacement) { reportNode.newReportNode() .withMessageTemplate("fieldReplacement", "'${fieldName}' field is not set, ${replacementName} ${replacement} will be used instead") @@ -129,4 +137,12 @@ public static void reportFieldSetWithWrongEquipment(ReportNode reportNode, Strin .add(); } + public static void reportFieldConflict(ReportNode reportNode, String firstFieldName, String secondFieldName) { + reportNode.newReportNode() + .withMessageTemplate("fieldConflict", "Both '${firstFieldName}' and '${secondFieldName}' are defined, '${firstFieldName}' will be used") + .withUntypedValue("firstFieldName", firstFieldName) + .withUntypedValue("secondFieldName", secondFieldName) + .withSeverity(TypedValue.TRACE_SEVERITY) + .add(); + } } diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzCurve.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/curves/DynawoCurve.java similarity index 83% rename from dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzCurve.java rename to dynawaltz/src/main/java/com/powsybl/dynawaltz/curves/DynawoCurve.java index ebcc7bd2e..82e955b0b 100644 --- a/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzCurve.java +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/curves/DynawoCurve.java @@ -4,8 +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.dynawaltz; +package com.powsybl.dynawaltz.curves; import com.powsybl.dynamicsimulation.Curve; @@ -14,12 +13,12 @@ /** * @author Mathieu Bague {@literal } */ -public class DynaWaltzCurve implements Curve { +public class DynawoCurve implements Curve { private final String dynamicModelId; private final String variable; - public DynaWaltzCurve(String dynamicModelId, String variable) { + DynawoCurve(String dynamicModelId, String variable) { this.dynamicModelId = Objects.requireNonNull(dynamicModelId); this.variable = Objects.requireNonNull(variable); } diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/curves/DynawoCurvesBuilder.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/curves/DynawoCurvesBuilder.java new file mode 100644 index 000000000..26d9437a4 --- /dev/null +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/curves/DynawoCurvesBuilder.java @@ -0,0 +1,104 @@ +/** + * 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.dynawaltz.curves; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.dynamicsimulation.Curve; +import com.powsybl.dynawaltz.builders.BuilderReports; + +import java.util.*; +import java.util.function.Consumer; + +/** + * A curve for
Dynawo
can be defined using {@code staticId} and {@code variable} or {@code dynamicModelId} and {@code variable}. + * Definition with {@code staticId} and {@code variable} are used when no explicit dynamic component exists. + *
Dynawo
expects {@code dynamicModelId} = “NETWORK” for these variables. + * @author Laurent Issertial {@literal } + */ +public class DynawoCurvesBuilder { + + private static final String DEFAULT_DYNAMIC_MODEL_ID = "NETWORK"; + + private final ReportNode reportNode; + private boolean isInstantiable = true; + private String dynamicModelId; + private String staticId; + private List variables; + + public DynawoCurvesBuilder(ReportNode reportNode) { + this.reportNode = reportNode; + } + + public DynawoCurvesBuilder() { + this(ReportNode.NO_OP); + } + + public DynawoCurvesBuilder dynamicModelId(String dynamicModelId) { + this.dynamicModelId = dynamicModelId; + return this; + } + + public DynawoCurvesBuilder staticId(String staticId) { + this.staticId = staticId; + return this; + } + + public DynawoCurvesBuilder variables(String... variables) { + this.variables = List.of(variables); + return this; + } + + public DynawoCurvesBuilder variables(List variables) { + this.variables = variables; + return this; + } + + public DynawoCurvesBuilder variable(String variable) { + this.variables = List.of(variable); + return this; + } + + private String getId() { + return dynamicModelId != null ? dynamicModelId : staticId; + } + + private void checkData() { + if (staticId != null && dynamicModelId != null) { + BuilderReports.reportFieldConflict(reportNode, "dynamicModelId", "staticId"); + } + if (variables == null) { + BuilderReports.reportFieldNotSet(reportNode, "variables"); + isInstantiable = false; + } else if (variables.isEmpty()) { + BuilderReports.reportEmptyList(reportNode, "variables"); + isInstantiable = false; + } + } + + private boolean isInstantiable() { + checkData(); + if (!isInstantiable) { + BuilderReports.reportCurveInstantiationFailure(reportNode, getId()); + } + return isInstantiable; + } + + public void add(Consumer curveConsumer) { + if (isInstantiable()) { + boolean hasDynamicModelId = dynamicModelId != null; + String id = hasDynamicModelId ? dynamicModelId : DEFAULT_DYNAMIC_MODEL_ID; + variables.forEach(v -> curveConsumer.accept(new DynawoCurve(id, hasDynamicModelId ? v : staticId + "_" + v))); + } + } + + public List build() { + List curves = new ArrayList<>(); + add(curves::add); + return curves; + } +} diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/suppliers/curves/CurvesJsonDeserializer.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/suppliers/curves/CurvesJsonDeserializer.java new file mode 100644 index 000000000..a1b3d63e8 --- /dev/null +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/suppliers/curves/CurvesJsonDeserializer.java @@ -0,0 +1,69 @@ +/** + * 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.dynawaltz.suppliers.curves; + +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.dynamicsimulation.Curve; +import com.powsybl.dynawaltz.curves.DynawoCurvesBuilder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * @author Laurent Issertial {@literal } + */ +public class CurvesJsonDeserializer extends StdDeserializer> { + + private final transient Supplier builderConstructor; + + public CurvesJsonDeserializer(Supplier builderConstructor) { + super(List.class); + this.builderConstructor = builderConstructor; + } + + public CurvesJsonDeserializer() { + this(DynawoCurvesBuilder::new); + } + + @Override + public List deserialize(JsonParser parser, DeserializationContext context) { + List modelConfigList = new ArrayList<>(); + JsonUtil.parseObject(parser, name -> { + if (name.equals("curves")) { + JsonUtil.parseObjectArray(parser, modelConfigList::add, this::parseCurvesBuilder); + return true; + } + return false; + }); + return modelConfigList.stream() + .flatMap(b -> b.build().stream()) + .filter(Objects::nonNull) + .toList(); + } + + private DynawoCurvesBuilder parseCurvesBuilder(JsonParser parser) { + DynawoCurvesBuilder curvesBuilder = builderConstructor.get(); + JsonUtil.parseObject(parser, name -> { + boolean handled = true; + switch (name) { + case "dynamicModelId" -> curvesBuilder.dynamicModelId(parser.nextTextValue()); + case "staticId" -> curvesBuilder.staticId(parser.nextTextValue()); + case "variable" -> curvesBuilder.variable(parser.nextTextValue()); + case "variables" -> curvesBuilder.variables(JsonUtil.parseStringArray(parser)); + default -> handled = false; + } + return handled; + }); + return curvesBuilder; + } +} diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/xml/CurvesXml.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/xml/CurvesXml.java index 3fe6aec83..29d22f363 100644 --- a/dynawaltz/src/main/java/com/powsybl/dynawaltz/xml/CurvesXml.java +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/xml/CurvesXml.java @@ -6,9 +6,8 @@ */ package com.powsybl.dynawaltz.xml; -import com.powsybl.dynamicsimulation.Curve; import com.powsybl.dynawaltz.DynaWaltzContext; -import com.powsybl.dynawaltz.DynaWaltzCurve; +import com.powsybl.dynawaltz.curves.DynawoCurve; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -33,8 +32,7 @@ public static void write(Path workingDir, DynaWaltzContext context) throws IOExc @Override public void write(XMLStreamWriter writer, DynaWaltzContext context) throws XMLStreamException { - for (Curve curve : context.getCurves()) { - DynaWaltzCurve dynCurve = (DynaWaltzCurve) curve; + for (DynawoCurve dynCurve : context.getCurves()) { writer.writeEmptyElement(DYN_URI, "curve"); writer.writeAttribute("model", dynCurve.getModelId()); writer.writeAttribute("variable", dynCurve.getVariable()); diff --git a/dynawaltz/src/test/java/com/powsybl/dynawaltz/DynaWaltzCurveTest.java b/dynawaltz/src/test/java/com/powsybl/dynawaltz/DynaWaltzCurveTest.java deleted file mode 100644 index 24c3a9fb3..000000000 --- a/dynawaltz/src/test/java/com/powsybl/dynawaltz/DynaWaltzCurveTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2020, 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.dynawaltz; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * @author Marcos de Miguel {@literal } - */ -class DynaWaltzCurveTest { - - @Test - void test() { - DynaWaltzCurve curve = new DynaWaltzCurve("busId", "variable"); - - assertEquals("busId", curve.getModelId()); - assertEquals("variable", curve.getVariable()); - } -} diff --git a/dynawaltz/src/test/java/com/powsybl/dynawaltz/DynaWaltzProviderTest.java b/dynawaltz/src/test/java/com/powsybl/dynawaltz/DynaWaltzProviderTest.java index 68e334a32..31ecd3ccc 100644 --- a/dynawaltz/src/test/java/com/powsybl/dynawaltz/DynaWaltzProviderTest.java +++ b/dynawaltz/src/test/java/com/powsybl/dynawaltz/DynaWaltzProviderTest.java @@ -14,6 +14,7 @@ import com.powsybl.computation.local.LocalComputationConfig; import com.powsybl.computation.local.LocalComputationManager; import com.powsybl.dynamicsimulation.*; +import com.powsybl.dynawaltz.curves.DynawoCurvesBuilder; import com.powsybl.dynawo.commons.DynawoConstants; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.Substation; @@ -46,6 +47,7 @@ class DynaWaltzProviderTest extends AbstractSerDeTest { private DynaWaltzConfig config; @BeforeEach + @Override public void setUp() throws IOException { super.setUp(); config = DynaWaltzConfig.load(); @@ -54,7 +56,7 @@ public void setUp() throws IOException { public static class CurvesSupplierMock implements CurvesSupplier { @Override public List get(Network network, ReportNode reportNode) { - return Collections.singletonList(new DynaWaltzCurve("bus", "uPu")); + return new DynawoCurvesBuilder().dynamicModelId("bus").variable("uPu").build(); } } diff --git a/dynawaltz/src/test/java/com/powsybl/dynawaltz/builders/CurvesBuilderTest.java b/dynawaltz/src/test/java/com/powsybl/dynawaltz/builders/CurvesBuilderTest.java new file mode 100644 index 000000000..189862a8e --- /dev/null +++ b/dynawaltz/src/test/java/com/powsybl/dynawaltz/builders/CurvesBuilderTest.java @@ -0,0 +1,102 @@ +package com.powsybl.dynawaltz.builders; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.test.TestUtil; +import com.powsybl.dynamicsimulation.Curve; +import com.powsybl.dynawaltz.curves.DynawoCurve; +import com.powsybl.dynawaltz.curves.DynawoCurvesBuilder; +import org.junit.jupiter.api.BeforeEach; +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; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CurvesBuilderTest { + + private ReportNode reporter; + + @BeforeEach + void setup() { + reporter = ReportNode.newRootReportNode().withMessageTemplate("builderTests", "Builder tests").build(); + } + + @Test + void buildFromDynamicId() { + List curveList = new DynawoCurvesBuilder() + .dynamicModelId("BBM_GEN") + .variable("generator_omegaPu") + .build(); + assertEquals(1, curveList.size()); + DynawoCurve curve = (DynawoCurve) curveList.get(0); + assertEquals("BBM_GEN", curve.getModelId()); + assertEquals("generator_omegaPu", curve.getVariable()); + } + + @Test + void buildFromStaticId() { + List curveList = new DynawoCurvesBuilder() + .staticId("GEN") + .variables("generator_omegaPu", "generator_PGen") + .build(); + assertEquals(2, curveList.size()); + DynawoCurve curve = (DynawoCurve) curveList.get(0); + assertEquals("NETWORK", curve.getModelId()); + assertEquals("GEN_generator_omegaPu", curve.getVariable()); + } + + @ParameterizedTest(name = "{1}") + @MethodSource("provideBuilderError") + void testScriptError(Function builderFunction, boolean isInstantiable, String report) throws IOException { + boolean hasInstance = !builderFunction.apply(reporter).build().isEmpty(); + assertEquals(isInstantiable, hasInstance); + checkReportNode(report); + } + + private static Stream provideBuilderError() { + return Stream.of( + Arguments.of((Function) r -> + new DynawoCurvesBuilder(r) + .staticId("GEN") + .dynamicModelId("BBM_GEN") + .variable("uPu"), + true, + """ + + Builder tests + Both 'dynamicModelId' and 'staticId' are defined, 'dynamicModelId' will be used + """), + Arguments.of((Function) r -> + new DynawoCurvesBuilder(r) + .staticId("GEN"), + false, + """ + + Builder tests + 'variables' field is not set + Curve GEN cannot be instantiated + """), + Arguments.of((Function) r -> + new DynawoCurvesBuilder(r) + .staticId("GEN") + .variables(), + false, + """ + + Builder tests + 'variables' list is empty + Curve GEN cannot be instantiated + """) + ); + } + + private void checkReportNode(String report) throws IOException { + StringWriter sw = new StringWriter(); + reporter.print(sw); + assertEquals(report, TestUtil.normalizeLineSeparator(sw.toString())); + } +} diff --git a/dynawaltz/src/test/java/com/powsybl/dynawaltz/suppliers/DynawoCurvesJsonDeserializerTest.java b/dynawaltz/src/test/java/com/powsybl/dynawaltz/suppliers/DynawoCurvesJsonDeserializerTest.java new file mode 100644 index 000000000..83f3cdd58 --- /dev/null +++ b/dynawaltz/src/test/java/com/powsybl/dynawaltz/suppliers/DynawoCurvesJsonDeserializerTest.java @@ -0,0 +1,44 @@ +/** + * 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.dynawaltz.suppliers; + +import com.powsybl.dynamicsimulation.Curve; +import com.powsybl.dynawaltz.curves.DynawoCurvesBuilder; +import com.powsybl.dynawaltz.suppliers.curves.CurvesJsonDeserializer; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Laurent Issertial {@literal } + */ +class DynawoCurvesJsonDeserializerTest { + + @Test + void testCurvesSupplier() throws IOException { + try (InputStream is = getClass().getResourceAsStream("/suppliers/curves.json")) { + List curves = new SupplierJsonDeserializer<>(new CurvesJsonDeserializer()).deserialize(is); + assertThat(curves).usingRecursiveFieldByFieldElementComparatorOnFields() + .containsExactlyInAnyOrderElementsOf(getExpectedCurves()); + } + } + + private static List getExpectedCurves() { + List curves = new ArrayList<>(); + new DynawoCurvesBuilder().dynamicModelId("BBM_GEN").variables("voltageRegulator_EfdPu").add(curves::add); + new DynawoCurvesBuilder().staticId("BUS").variables("Upu_value").add(curves::add); + new DynawoCurvesBuilder().dynamicModelId("BBM_GEN2").variables("generator_omegaPu", "generator_PGen", "generator_UStatorPU").add(curves::add); + new DynawoCurvesBuilder().staticId("LOAD").variables("load_PPu", "load_QPu").add(curves::add); + return curves; + } +} diff --git a/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/CurvesXmlTest.java b/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/CurvesXmlTest.java index e3b3e9559..eecc62f9e 100644 --- a/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/CurvesXmlTest.java +++ b/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/CurvesXmlTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.Test; import org.xml.sax.SAXException; -import javax.xml.stream.XMLStreamException; import java.io.IOException; /** @@ -21,7 +20,7 @@ class CurvesXmlTest extends DynaWaltzTestUtil { @Test - void writeCurve() throws SAXException, IOException, XMLStreamException { + void writeCurve() throws SAXException, IOException { DynamicSimulationParameters parameters = DynamicSimulationParameters.load(); DynaWaltzParameters dynawoParameters = DynaWaltzParameters.load(); DynaWaltzContext context = new DynaWaltzContext(network, network.getVariantManager().getWorkingVariantId(), dynamicModels, eventModels, curves, parameters, dynawoParameters); diff --git a/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/DynaWaltzTestUtil.java b/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/DynaWaltzTestUtil.java index b57d27a61..47fe34d6c 100644 --- a/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/DynaWaltzTestUtil.java +++ b/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/DynaWaltzTestUtil.java @@ -8,7 +8,7 @@ import com.powsybl.commons.test.AbstractSerDeTest; import com.powsybl.dynamicsimulation.Curve; -import com.powsybl.dynawaltz.DynaWaltzCurve; +import com.powsybl.dynawaltz.curves.DynawoCurvesBuilder; import com.powsybl.dynawaltz.models.BlackBoxModel; import com.powsybl.dynawaltz.models.automationsystems.overloadmanagments.DynamicOverloadManagementSystemBuilder; import com.powsybl.dynawaltz.models.events.EventDisconnectionBuilder; @@ -55,17 +55,17 @@ void setup() { network = createEurostagTutorialExample1WithMoreLoads(); curves = new ArrayList<>(); - network.getBusBreakerView().getBusStream().forEach(b -> curves.add(new DynaWaltzCurve("NETWORK", b.getId() + "_Upu_value"))); + network.getBusBreakerView().getBusStream().forEach(b -> new DynawoCurvesBuilder() + .staticId(b.getId()) + .variables("Upu_value") + .add(curves::add)); // A curve is made up of the id of the dynamic model and the variable to plot. // The static id of the generator is used as the id of the dynamic model (dynamicModelId). - network.getGeneratorStream().forEach(g -> { - curves.add(new DynaWaltzCurve(g.getId(), "generator_omegaPu")); - curves.add(new DynaWaltzCurve(g.getId(), "generator_PGen")); - curves.add(new DynaWaltzCurve(g.getId(), "generator_UStatorPu")); - curves.add(new DynaWaltzCurve(g.getId(), "voltageRegulator_UcEfdP")); - curves.add(new DynaWaltzCurve(g.getId(), "voltageRegulator_EfdPu")); - }); + network.getGeneratorStream().forEach(g -> new DynawoCurvesBuilder() + .dynamicModelId(g.getId()) + .variables("generator_omegaPu", "generator_PGen", "generator_UStatorPu", "voltageRegulator_UcEfdP", "voltageRegulator_EfdPu") + .add(curves::add)); // Dynamic Models dynamicModels = new ArrayList<>(); diff --git a/dynawaltz/src/test/resources/suppliers/curves.json b/dynawaltz/src/test/resources/suppliers/curves.json new file mode 100644 index 000000000..3b89f124c --- /dev/null +++ b/dynawaltz/src/test/resources/suppliers/curves.json @@ -0,0 +1,20 @@ +{ + "curves":[ + { + "dynamicModelId": "BBM_GEN", + "variable": "voltageRegulator_EfdPu" + }, + { + "staticId": "BUS", + "variable": "Upu_value" + }, + { + "dynamicModelId": "BBM_GEN2", + "variables": ["generator_omegaPu", "generator_PGen", "generator_UStatorPU"] + }, + { + "staticId": "LOAD", + "variables": ["load_PPu", "load_QPu"] + } + ] +} \ No newline at end of file diff --git a/dynawo-integration-tests/src/test/resources/ieee14/disconnectline/curves.groovy b/dynawo-integration-tests/src/test/resources/ieee14/disconnectline/curves.groovy index 7bf0f8979..e7acd795b 100644 --- a/dynawo-integration-tests/src/test/resources/ieee14/disconnectline/curves.groovy +++ b/dynawo-integration-tests/src/test/resources/ieee14/disconnectline/curves.groovy @@ -17,7 +17,7 @@ for (Bus bus : network.busBreakerView.buses) { } for (Generator gen : network.generators) { - curves { + curve { dynamicModelId gen.id variables "generator_omegaPu", "generator_PGen", "generator_QGen", "generator_UStatorPu", "voltageRegulator_EfdPu" } diff --git a/dynawo-integration-tests/src/test/resources/smib/curves.groovy b/dynawo-integration-tests/src/test/resources/smib/curves.groovy index b74db84ac..f6989320f 100644 --- a/dynawo-integration-tests/src/test/resources/smib/curves.groovy +++ b/dynawo-integration-tests/src/test/resources/smib/curves.groovy @@ -8,19 +8,19 @@ import com.powsybl.iidm.network.Line -curves { +curve { dynamicModelId "sm" variables "generator_cePu", "generator_PePu", "generator_cmPu", "generator_PmPu", "generator_ufPu", "generator_efdPu", "generator_omegaPu", "generator_theta", "generator_PGenPu", "generator_QGenPu", "generator_PGen", "generator_QGen", "generator_UPu", "generator_IStatorPu", "generator_IRotorPu", "generator_UStatorPu", "generator_QStatorPu", "generator_thetaInternal", "governor_PmRefPu" } for (Line line : network.lines) { - curves { + curve { dynamicModelId line.id variables "line_P1Pu", "line_P2Pu", "line_Q1Pu", "line_Q2Pu" } } -curves { +curve { dynamicModelId "tfo" variables "transformer_terminal1_V_re", "transformer_terminal1_V_im", "transformer_terminal2_V_re", "transformer_terminal2_V_im", "transformer_terminal1_i_re", "transformer_terminal1_i_im", "transformer_terminal2_i_re", "transformer_terminal2_i_im" } \ No newline at end of file