diff --git a/action-api/pom.xml b/action-api/pom.xml
index 1c0426531ff..b84d679f2cc 100644
--- a/action-api/pom.xml
+++ b/action-api/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-action-api
diff --git a/action-api/src/main/java/com/powsybl/action/AreaInterchangeTargetAction.java b/action-api/src/main/java/com/powsybl/action/AreaInterchangeTargetAction.java
new file mode 100644
index 00000000000..088ce646313
--- /dev/null
+++ b/action-api/src/main/java/com/powsybl/action/AreaInterchangeTargetAction.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/)
+ * 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.action;
+
+import com.powsybl.iidm.modification.AreaInterchangeTargetModification;
+import com.powsybl.iidm.modification.NetworkModification;
+
+import java.util.Objects;
+
+/**
+ * An action to:
+ *
+ *
Change the interchange target of an area by specifying a new interchange target in MW.
+ *
+ * @author Bertrand Rix {@literal }
+ */
+public class AreaInterchangeTargetAction extends AbstractAction {
+
+ public static final String NAME = "AREA_INTERCHANGE_TARGET_ACTION";
+
+ private final String areaId;
+ private final double interchangeTarget;
+
+ public AreaInterchangeTargetAction(String id, String areaId, double interchangeTarget) {
+ super(id);
+ this.areaId = Objects.requireNonNull(areaId);
+ this.interchangeTarget = interchangeTarget;
+ }
+
+ public double getInterchangeTarget() {
+ return interchangeTarget;
+ }
+
+ public String getAreaId() {
+ return areaId;
+ }
+
+ @Override
+ public String getType() {
+ return NAME;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ AreaInterchangeTargetAction that = (AreaInterchangeTargetAction) o;
+ return Objects.equals(areaId, that.areaId) && (interchangeTarget == that.interchangeTarget || Double.isNaN(interchangeTarget) && Double.isNaN(that.interchangeTarget));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), interchangeTarget, areaId);
+ }
+
+ @Override
+ public NetworkModification toModification() {
+ return new AreaInterchangeTargetModification(areaId, interchangeTarget);
+ }
+}
diff --git a/action-api/src/main/java/com/powsybl/action/AreaInterchangeTargetActionBuilder.java b/action-api/src/main/java/com/powsybl/action/AreaInterchangeTargetActionBuilder.java
new file mode 100644
index 00000000000..fac2beb7c51
--- /dev/null
+++ b/action-api/src/main/java/com/powsybl/action/AreaInterchangeTargetActionBuilder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/)
+ * 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.action;
+
+/**
+ * @author Bertrand Rix {@literal }
+ */
+public class AreaInterchangeTargetActionBuilder implements ActionBuilder {
+
+ private String id;
+
+ private String areaId;
+
+ private double target = Double.NaN;
+
+ @Override
+ public String getType() {
+ return AreaInterchangeTargetAction.NAME;
+ }
+
+ @Override
+ public AreaInterchangeTargetActionBuilder withId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public AreaInterchangeTargetActionBuilder withNetworkElementId(String elementId) {
+ this.areaId = elementId;
+ return this;
+ }
+
+ public AreaInterchangeTargetActionBuilder withTarget(double target) {
+ this.target = target;
+ return this;
+ }
+
+ public AreaInterchangeTargetActionBuilder withAreaId(String areaId) {
+ this.withNetworkElementId(areaId);
+ return this;
+ }
+
+ @Override
+ public AreaInterchangeTargetAction build() {
+ return new AreaInterchangeTargetAction(id, areaId, target);
+ }
+}
diff --git a/action-api/src/main/java/com/powsybl/action/PercentChangeLoadAction.java b/action-api/src/main/java/com/powsybl/action/PercentChangeLoadAction.java
new file mode 100644
index 00000000000..b1bcb593bbc
--- /dev/null
+++ b/action-api/src/main/java/com/powsybl/action/PercentChangeLoadAction.java
@@ -0,0 +1,97 @@
+/**
+ * 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.action;
+
+import com.powsybl.iidm.modification.NetworkModification;
+import com.powsybl.iidm.modification.PercentChangeLoadModification;
+
+import java.util.Objects;
+
+/**
+ * An action to:
+ *
+ *
change the P0 of a load, by specifying its percentage change (which could be positive or negative).
+ *
describe the impact of this change on the Q0 of a load, by specifying the qModificationStrategy.
+ *
+ *
+ * This action is useful to specify changes that should be applied on a load when its actual active power is unknown.
+ *
+ *
+ * @author Benoît Chiquet {@literal }
+ */
+public class PercentChangeLoadAction extends AbstractAction {
+
+ public static final String NAME = "PCT_LOAD_CHANGE";
+ private String loadId;
+ private Double p0PercentChange;
+ private QModificationStrategy qModificationStrategy;
+
+ /**
+ * @param id the id of the action.
+ * @param loadId the id of the load on which the action would be applied.
+ * @param p0PercentChange the percentage that will be added to P0. Negative values describe load reduction.
+ * @param qModificationStrategy the way this change impacts Q0.
+ */
+ PercentChangeLoadAction(String id, String loadId, Double p0PercentChange, QModificationStrategy qModificationStrategy) {
+ super(id);
+ this.loadId = loadId;
+ this.p0PercentChange = p0PercentChange;
+ this.qModificationStrategy = qModificationStrategy;
+ }
+
+ public enum QModificationStrategy {
+ CONSTANT_Q,
+ CONSTANT_PQ_RATIO
+ }
+
+ @Override
+ public String getType() {
+ return NAME;
+ }
+
+ public Double getP0PercentChange() {
+ return this.p0PercentChange;
+ }
+
+ public String getLoadId() {
+ return this.loadId;
+ }
+
+ public QModificationStrategy getQModificationStrategy() {
+ return this.qModificationStrategy;
+ }
+
+ @Override
+ public NetworkModification toModification() {
+ double q0PercentChange = switch (qModificationStrategy) {
+ case CONSTANT_Q -> 0d;
+ case CONSTANT_PQ_RATIO -> p0PercentChange;
+ };
+ return new PercentChangeLoadModification(loadId, p0PercentChange, q0PercentChange);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ PercentChangeLoadAction that = (PercentChangeLoadAction) o;
+ return Objects.equals(loadId, that.loadId) && Objects.equals(p0PercentChange, that.p0PercentChange) && qModificationStrategy == that.qModificationStrategy;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), loadId, p0PercentChange, qModificationStrategy);
+ }
+}
diff --git a/action-api/src/main/java/com/powsybl/action/PercentChangeLoadActionBuilder.java b/action-api/src/main/java/com/powsybl/action/PercentChangeLoadActionBuilder.java
new file mode 100644
index 00000000000..ea07c4a1bf8
--- /dev/null
+++ b/action-api/src/main/java/com/powsybl/action/PercentChangeLoadActionBuilder.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.action;
+
+import com.powsybl.action.PercentChangeLoadAction.QModificationStrategy;
+
+import java.util.Objects;
+
+/**
+ * @author Benoît Chiquet {@literal }
+ */
+public class PercentChangeLoadActionBuilder implements ActionBuilder {
+ private String id;
+ private String loadId;
+ private double p0PercentChange;
+ private QModificationStrategy qModificationStrategy;
+
+ @Override
+ public String getType() {
+ return PercentChangeLoadAction.NAME;
+ }
+
+ @Override
+ public PercentChangeLoadActionBuilder withId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public PercentChangeLoadActionBuilder withNetworkElementId(String elementId) {
+ this.loadId = elementId;
+ return this;
+ }
+
+ public PercentChangeLoadActionBuilder withLoadId(String loadId) {
+ return this.withNetworkElementId(loadId);
+ }
+
+ @Override
+ public Action build() {
+ if (p0PercentChange < -100) {
+ throw new IllegalArgumentException("The active power can't be reduced by more than 100%.");
+ }
+ return new PercentChangeLoadAction(Objects.requireNonNull(id), Objects.requireNonNull(loadId), p0PercentChange, Objects.requireNonNull(qModificationStrategy));
+ }
+
+ public PercentChangeLoadActionBuilder withP0PercentChange(double p0PercentChange) {
+ this.p0PercentChange = p0PercentChange;
+ return this;
+ }
+
+ public PercentChangeLoadActionBuilder withQModificationStrategy(QModificationStrategy strategy) {
+ this.qModificationStrategy = strategy;
+ return this;
+ }
+}
diff --git a/action-api/src/main/java/com/powsybl/action/json/ActionJsonModule.java b/action-api/src/main/java/com/powsybl/action/json/ActionJsonModule.java
index 6e53d795e9a..57ed1c3c9dd 100644
--- a/action-api/src/main/java/com/powsybl/action/json/ActionJsonModule.java
+++ b/action-api/src/main/java/com/powsybl/action/json/ActionJsonModule.java
@@ -53,6 +53,7 @@ TerminalsConnectionAction.NAME, new TerminalsConnectionActionSerializer(),
registerActionBuilderType(RatioTapChangerRegulationAction.class, RatioTapChangerRegulationActionBuilder.class, RatioTapChangerRegulationAction.NAME,
new RatioTapChangerRegulationActionSerializer(), new RatioTapChangerRegulationActionBuilderBuilderDeserializer());
registerActionBuilderType(LoadAction.class, LoadActionBuilder.class, LoadAction.NAME, new LoadActionSerializer(), new LoadActionBuilderBuilderDeserializer());
+ registerActionBuilderType(PercentChangeLoadAction.class, PercentChangeLoadActionBuilder.class, PercentChangeLoadAction.NAME, new PercentChangeLoadActionSerializer(), new PercentChangeLoadActionBuilderDeserializer());
registerActionBuilderType(DanglingLineAction.class, DanglingLineActionBuilder.class, DanglingLineAction.NAME, new DanglingLineActionSerializer(), new DanglingLineActionBuilderBuilderDeserializer());
registerActionBuilderType(HvdcAction.class, HvdcActionBuilder.class, HvdcAction.NAME, new HvdcActionSerializer(), new HvdcActionBuilderDeserializer());
registerActionBuilderType(GeneratorAction.class, GeneratorActionBuilder.class, GeneratorAction.NAME,
@@ -63,5 +64,7 @@ TerminalsConnectionAction.NAME, new TerminalsConnectionActionSerializer(),
registerActionBuilderType(StaticVarCompensatorAction.class, StaticVarCompensatorActionBuilder.class,
StaticVarCompensatorAction.NAME, new StaticVarCompensatorActionSerializer(),
new StaticVarCompensatorActionBuilderDeserializer());
+ registerActionBuilderType(AreaInterchangeTargetAction.class, AreaInterchangeTargetActionBuilder.class, AreaInterchangeTargetAction.NAME,
+ new AreaInterchangeTargetActionSerializer(), new AreaInterchangeTargetActionDeserializer());
}
}
diff --git a/action-api/src/main/java/com/powsybl/action/json/AreaInterchangeTargetActionDeserializer.java b/action-api/src/main/java/com/powsybl/action/json/AreaInterchangeTargetActionDeserializer.java
new file mode 100644
index 00000000000..c4167f290c5
--- /dev/null
+++ b/action-api/src/main/java/com/powsybl/action/json/AreaInterchangeTargetActionDeserializer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/)
+ * 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.action.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.powsybl.action.AreaInterchangeTargetAction;
+import com.powsybl.action.AreaInterchangeTargetActionBuilder;
+import com.powsybl.commons.json.JsonUtil;
+
+import java.io.IOException;
+
+/**
+ * @author Bertrand Rix {@literal }
+ */
+public class AreaInterchangeTargetActionDeserializer extends StdDeserializer {
+
+ protected AreaInterchangeTargetActionDeserializer() {
+ super(AreaInterchangeTargetActionBuilder.class);
+ }
+
+ @Override
+ public AreaInterchangeTargetActionBuilder deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+ AreaInterchangeTargetActionBuilder builder = new AreaInterchangeTargetActionBuilder();
+ JsonUtil.parsePolymorphicObject(jsonParser, name -> {
+ switch (name) {
+ case "type":
+ if (!AreaInterchangeTargetAction.NAME.equals(jsonParser.nextTextValue())) {
+ throw JsonMappingException.from(jsonParser, "Expected type " + AreaInterchangeTargetAction.NAME);
+ }
+ return true;
+ case "id":
+ builder.withId(jsonParser.nextTextValue());
+ return true;
+ case "areaId":
+ builder.withAreaId(jsonParser.nextTextValue());
+ return true;
+ case "interchangeTarget":
+ jsonParser.nextToken();
+ builder.withTarget(jsonParser.getValueAsDouble());
+ return true;
+ default:
+ return false;
+ }
+ });
+ return builder;
+ }
+}
diff --git a/action-api/src/main/java/com/powsybl/action/json/AreaInterchangeTargetActionSerializer.java b/action-api/src/main/java/com/powsybl/action/json/AreaInterchangeTargetActionSerializer.java
new file mode 100644
index 00000000000..9d62cc71720
--- /dev/null
+++ b/action-api/src/main/java/com/powsybl/action/json/AreaInterchangeTargetActionSerializer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/)
+ * 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.action.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.powsybl.action.AreaInterchangeTargetAction;
+
+import java.io.IOException;
+
+/**
+ * @author Bertrand Rix {@literal }
+ */
+public class AreaInterchangeTargetActionSerializer extends StdSerializer {
+
+ AreaInterchangeTargetActionSerializer() {
+ super(AreaInterchangeTargetAction.class);
+ }
+
+ @Override
+ public void serialize(AreaInterchangeTargetAction action, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+ jsonGenerator.writeStartObject();
+ jsonGenerator.writeStringField("type", action.getType());
+ jsonGenerator.writeStringField("id", action.getId());
+ jsonGenerator.writeStringField("areaId", action.getAreaId());
+ if (!Double.isNaN(action.getInterchangeTarget())) {
+ jsonGenerator.writeNumberField("interchangeTarget", action.getInterchangeTarget());
+ }
+ jsonGenerator.writeEndObject();
+ }
+}
diff --git a/action-api/src/main/java/com/powsybl/action/json/PercentChangeLoadActionBuilderDeserializer.java b/action-api/src/main/java/com/powsybl/action/json/PercentChangeLoadActionBuilderDeserializer.java
new file mode 100644
index 00000000000..ea34283ed9d
--- /dev/null
+++ b/action-api/src/main/java/com/powsybl/action/json/PercentChangeLoadActionBuilderDeserializer.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.action.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.powsybl.action.PercentChangeLoadAction;
+import com.powsybl.action.PercentChangeLoadActionBuilder;
+import com.powsybl.commons.json.JsonUtil;
+
+import java.io.IOException;
+
+/**
+ * @author Benoît Chiquet {@literal }
+ */
+public class PercentChangeLoadActionBuilderDeserializer extends StdDeserializer {
+ public PercentChangeLoadActionBuilderDeserializer() {
+ super(PercentChangeLoadActionBuilder.class);
+ }
+
+ @Override
+ public PercentChangeLoadActionBuilder deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+ PercentChangeLoadActionBuilder builder = new PercentChangeLoadActionBuilder();
+ JsonUtil.parsePolymorphicObject(jsonParser, name -> {
+ switch (name) {
+ case "type":
+ if (!PercentChangeLoadAction.NAME.equals(jsonParser.nextTextValue())) {
+ throw JsonMappingException.from(jsonParser, "Expected type " + PercentChangeLoadAction.NAME);
+ }
+ return true;
+ case "id":
+ builder.withId(jsonParser.nextTextValue());
+ return true;
+ case "loadId":
+ builder.withLoadId(jsonParser.nextTextValue());
+ return true;
+ case "p0PercentChange":
+ jsonParser.nextToken();
+ builder.withP0PercentChange(jsonParser.getValueAsDouble());
+ return true;
+ case "qModificationStrategy":
+ builder.withQModificationStrategy(PercentChangeLoadAction.QModificationStrategy.valueOf(jsonParser.nextTextValue()));
+ return true;
+ default:
+ return false;
+ }
+ });
+ return builder;
+ }
+}
diff --git a/action-api/src/main/java/com/powsybl/action/json/PercentChangeLoadActionSerializer.java b/action-api/src/main/java/com/powsybl/action/json/PercentChangeLoadActionSerializer.java
new file mode 100644
index 00000000000..4d8b47c10fd
--- /dev/null
+++ b/action-api/src/main/java/com/powsybl/action/json/PercentChangeLoadActionSerializer.java
@@ -0,0 +1,36 @@
+/**
+ * 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.action.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.powsybl.action.PercentChangeLoadAction;
+
+import java.io.IOException;
+
+/**
+ * @author Benoît Chiquet {@literal }
+ */
+public class PercentChangeLoadActionSerializer extends StdSerializer {
+
+ public PercentChangeLoadActionSerializer() {
+ super(PercentChangeLoadAction.class);
+ }
+
+ @Override
+ public void serialize(PercentChangeLoadAction action, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+ jsonGenerator.writeStartObject();
+ jsonGenerator.writeStringField("type", action.getType());
+ jsonGenerator.writeStringField("id", action.getId());
+ jsonGenerator.writeStringField("loadId", action.getLoadId());
+ jsonGenerator.writeNumberField("p0PercentChange", action.getP0PercentChange());
+ jsonGenerator.writeStringField("qModificationStrategy", action.getQModificationStrategy().toString());
+ jsonGenerator.writeEndObject();
+ }
+}
diff --git a/action-api/src/test/java/com/powsybl/action/ActionBuilderTest.java b/action-api/src/test/java/com/powsybl/action/ActionBuilderTest.java
index 80146f083f1..b0075b900f4 100644
--- a/action-api/src/test/java/com/powsybl/action/ActionBuilderTest.java
+++ b/action-api/src/test/java/com/powsybl/action/ActionBuilderTest.java
@@ -10,6 +10,8 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
/**
* @author Etienne Lesot {@literal }
*/
@@ -19,14 +21,26 @@ class ActionBuilderTest {
void shuntCompensatorPositionActionBuilderTest() {
ShuntCompensatorPositionActionBuilder shuntCompensatorPositionActionBuilder1 = new ShuntCompensatorPositionActionBuilder()
.withId("actionId").withShuntCompensatorId("shuntCompensatorId");
- String message1 = Assertions.assertThrows(IllegalArgumentException.class, shuntCompensatorPositionActionBuilder1::build)
+ String message1 = assertThrows(IllegalArgumentException.class, shuntCompensatorPositionActionBuilder1::build)
.getMessage();
Assertions.assertEquals("sectionCount is undefined", message1);
ShuntCompensatorPositionActionBuilder shuntCompensatorPositionActionBuilder2 = new ShuntCompensatorPositionActionBuilder()
.withId("actionId").withShuntCompensatorId("shuntCompensatorId").withSectionCount(-1);
- String message2 = Assertions.assertThrows(IllegalArgumentException.class, shuntCompensatorPositionActionBuilder2::build)
+ String message2 = assertThrows(IllegalArgumentException.class, shuntCompensatorPositionActionBuilder2::build)
.getMessage();
Assertions.assertEquals("sectionCount should be positive for a shunt compensator", message2);
}
+
+ @Test
+ void pctLoadActionBuilderShouldCheckPctNotAbove100() {
+ PercentChangeLoadActionBuilder actionBuilder = new PercentChangeLoadActionBuilder()
+ .withId("actionId")
+ .withLoadId("myLoad")
+ .withQModificationStrategy(PercentChangeLoadAction.QModificationStrategy.CONSTANT_Q)
+ .withP0PercentChange(-101);
+ String message = assertThrows(IllegalArgumentException.class, actionBuilder::build)
+ .getMessage();
+ Assertions.assertEquals("The active power can't be reduced by more than 100%.", message);
+ }
}
diff --git a/action-api/src/test/java/com/powsybl/action/ApplyActionToNetworkTest.java b/action-api/src/test/java/com/powsybl/action/ApplyActionToNetworkTest.java
index 08a26805e3f..a7b6b79767d 100644
--- a/action-api/src/test/java/com/powsybl/action/ApplyActionToNetworkTest.java
+++ b/action-api/src/test/java/com/powsybl/action/ApplyActionToNetworkTest.java
@@ -21,6 +21,8 @@
import com.powsybl.iidm.network.test.*;
import org.junit.jupiter.api.Test;
+import static com.powsybl.action.PercentChangeLoadAction.QModificationStrategy.CONSTANT_PQ_RATIO;
+import static com.powsybl.action.PercentChangeLoadAction.QModificationStrategy.CONSTANT_Q;
import static org.junit.jupiter.api.Assertions.*;
/**
@@ -137,6 +139,32 @@ void loadAction() {
assertEquals(580.0, load.getP0());
}
+ @Test
+ void pctLoadActionShouldNotModifyQ0WhenConstantQ() {
+ Network network = EurostagTutorialExample1Factory.create();
+ Load load = network.getLoad("LOAD");
+ assertEquals(600.0, load.getP0());
+ assertEquals(200.0, load.getQ0());
+ PercentChangeLoadAction action = (PercentChangeLoadAction) new PercentChangeLoadActionBuilder()
+ .withId("id").withLoadId("LOAD").withP0PercentChange(-10d).withQModificationStrategy(CONSTANT_Q).build();
+ action.toModification().apply(network);
+ assertEquals(540.0, load.getP0());
+ assertEquals(200.0, load.getQ0());
+ }
+
+ @Test
+ void pctLoadActionShouldPreservePQRatioWhenConstantPQRatio() {
+ Network network = EurostagTutorialExample1Factory.create();
+ Load load = network.getLoad("LOAD");
+ assertEquals(600.0, load.getP0());
+ assertEquals(200.0, load.getQ0());
+ PercentChangeLoadAction action = (PercentChangeLoadAction) new PercentChangeLoadActionBuilder()
+ .withId("id").withLoadId("LOAD").withP0PercentChange(-10d).withQModificationStrategy(CONSTANT_PQ_RATIO).build();
+ action.toModification().apply(network);
+ assertEquals(540.0, load.getP0());
+ assertEquals(180.0, load.getQ0());
+ }
+
@Test
void shuntCompensatorAction() {
Network network = EurostagTutorialExample1Factory.createWithMultipleConnectedComponents();
diff --git a/action-api/src/test/java/com/powsybl/action/EqualsActionTest.java b/action-api/src/test/java/com/powsybl/action/EqualsActionTest.java
index 818af157d59..317749367d2 100644
--- a/action-api/src/test/java/com/powsybl/action/EqualsActionTest.java
+++ b/action-api/src/test/java/com/powsybl/action/EqualsActionTest.java
@@ -17,6 +17,9 @@
import java.util.List;
+import static com.powsybl.action.PercentChangeLoadAction.QModificationStrategy.CONSTANT_PQ_RATIO;
+import static com.powsybl.action.PercentChangeLoadAction.QModificationStrategy.CONSTANT_Q;
+
/**
* @author Pauline JEAN-MARIE {@literal }
*/
@@ -167,6 +170,21 @@ void loadAction() {
.testEquals();
}
+ @Test
+ void pctLoadAction() {
+ PercentChangeLoadAction action1 = new PercentChangeLoadAction("id", "load", -2d, CONSTANT_Q);
+ PercentChangeLoadAction action2 = new PercentChangeLoadAction("id", "load", -2d, CONSTANT_Q);
+ PercentChangeLoadAction action3 = new PercentChangeLoadAction("id", "load", -2d, CONSTANT_PQ_RATIO);
+ PercentChangeLoadAction action4 = new PercentChangeLoadAction("id", "load", -3d, CONSTANT_Q);
+ PercentChangeLoadAction action5 = new PercentChangeLoadAction("id2", "load", -2d, CONSTANT_Q);
+ new EqualsTester()
+ .addEqualityGroup(action1, action2)
+ .addEqualityGroup(action3)
+ .addEqualityGroup(action4)
+ .addEqualityGroup(action5)
+ .testEquals();
+ }
+
@Test
void shuntCompensatorAction() {
ShuntCompensatorPositionAction action1 = new ShuntCompensatorPositionActionBuilder().withId("id")
@@ -546,4 +564,46 @@ void multipleActionsAction() {
.testEquals();
}
+ @Test
+ void interchangeTargetAction() {
+ AreaInterchangeTargetAction action1 = new AreaInterchangeTargetActionBuilder().withId("id")
+ .withAreaId("area1")
+ .withTarget(1.0)
+ .build();
+ AreaInterchangeTargetAction action2 = new AreaInterchangeTargetActionBuilder().withId("id")
+ .withAreaId("area1")
+ .withTarget(1.0)
+ .build();
+ AreaInterchangeTargetAction action3 = new AreaInterchangeTargetActionBuilder().withId("id2")
+ .withAreaId("area1")
+ .withTarget(1.0)
+ .build();
+ AreaInterchangeTargetAction action4 = new AreaInterchangeTargetActionBuilder().withId("id")
+ .withAreaId("area2")
+ .withTarget(1.0)
+ .build();
+ AreaInterchangeTargetAction action5 = new AreaInterchangeTargetActionBuilder().withId("id")
+ .withAreaId("area1")
+ .withTarget(2.0)
+ .build();
+
+ AreaInterchangeTargetAction action6 = new AreaInterchangeTargetActionBuilder().withId("id")
+ .withAreaId("area1")
+ .withTarget(Double.NaN)
+ .build();
+
+ AreaInterchangeTargetAction action7 = new AreaInterchangeTargetActionBuilder().withId("id")
+ .withAreaId("area1")
+ .withTarget(Double.NaN)
+ .build();
+
+ new EqualsTester()
+ .addEqualityGroup(action1, action2)
+ .addEqualityGroup(action3)
+ .addEqualityGroup(action4)
+ .addEqualityGroup(action5)
+ .addEqualityGroup(action6, action7)
+ .testEquals();
+ }
+
}
diff --git a/action-api/src/test/java/com/powsybl/action/json/JsonActionTest.java b/action-api/src/test/java/com/powsybl/action/json/JsonActionTest.java
index 465fed9a2f5..385c2b74c4b 100644
--- a/action-api/src/test/java/com/powsybl/action/json/JsonActionTest.java
+++ b/action-api/src/test/java/com/powsybl/action/json/JsonActionTest.java
@@ -14,9 +14,9 @@
import java.io.*;
import java.util.*;
+import static com.powsybl.action.PercentChangeLoadAction.QModificationStrategy.CONSTANT_Q;
import static com.powsybl.iidm.network.HvdcLine.ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER;
import static org.junit.jupiter.api.Assertions.*;
-import static org.junit.jupiter.api.Assertions.assertThrows;
public class JsonActionTest extends AbstractSerDeTest {
@@ -36,6 +36,7 @@ void actionRoundTrip() throws IOException {
actions.add(new GeneratorActionBuilder().withId("id11").withGeneratorId("generatorId2").withVoltageRegulatorOn(false).withTargetQ(400.0).build());
actions.add(new LoadActionBuilder().withId("id12").withLoadId("loadId1").withRelativeValue(false).withActivePowerValue(50.0).build());
actions.add(new LoadActionBuilder().withId("id13").withLoadId("loadId1").withRelativeValue(true).withReactivePowerValue(5.0).build());
+ actions.add(new PercentChangeLoadActionBuilder().withId("id26").withLoadId("loadId1").withP0PercentChange(5.0).withQModificationStrategy(CONSTANT_Q).build());
actions.add(new DanglingLineActionBuilder().withId("id17").withDanglingLineId("dlId1").withRelativeValue(true).withReactivePowerValue(5.0).build());
actions.add(new RatioTapChangerTapPositionAction("id14", "transformerId4", false, 2, ThreeSides.THREE));
actions.add(new RatioTapChangerTapPositionAction("id15", "transformerId5", true, 1));
@@ -83,6 +84,8 @@ void actionRoundTrip() throws IOException {
.withStaticVarCompensatorId("svc").withRegulationMode(StaticVarCompensator.RegulationMode.REACTIVE_POWER)
.withReactivePowerSetpoint(120.0).build());
actions.add(new TerminalsConnectionAction("id4", "transformerId25", ThreeSides.THREE, true)); // only one side.
+ actions.add(new AreaInterchangeTargetAction("id99", "AreaA", 101.0));
+ actions.add(new AreaInterchangeTargetAction("idDisabledTarget", "AreaA", Double.NaN));
ActionList actionList = new ActionList(actions);
roundTripTest(actionList, ActionList::writeJsonFile, ActionList::readJsonFile, "/ActionFileTest.json");
}
diff --git a/action-api/src/test/resources/ActionFileTest.json b/action-api/src/test/resources/ActionFileTest.json
index 549c1c0e510..94181ba1505 100644
--- a/action-api/src/test/resources/ActionFileTest.json
+++ b/action-api/src/test/resources/ActionFileTest.json
@@ -81,6 +81,12 @@
"loadId" : "loadId1",
"relativeValue" : true,
"reactivePowerValue" : 5.0
+ }, {
+ "type" : "PCT_LOAD_CHANGE",
+ "id" : "id26",
+ "loadId" : "loadId1",
+ "p0PercentChange" : 5.0,
+ "qModificationStrategy" : "CONSTANT_Q"
}, {
"type" : "DANGLING_LINE",
"id" : "id17",
@@ -189,5 +195,14 @@
"elementId" : "transformerId25",
"side" : "THREE",
"open" : true
+ }, {
+ "type" : "AREA_INTERCHANGE_TARGET_ACTION",
+ "id" : "id99",
+ "areaId" : "AreaA",
+ "interchangeTarget" : 101.0
+ }, {
+ "type" : "AREA_INTERCHANGE_TARGET_ACTION",
+ "id" : "idDisabledTarget",
+ "areaId" : "AreaA"
} ]
}
\ No newline at end of file
diff --git a/action-api/src/test/resources/ActionFileTestV1.0.json b/action-api/src/test/resources/ActionFileTestV1.0.json
index 856876582a4..2bec92daca9 100644
--- a/action-api/src/test/resources/ActionFileTestV1.0.json
+++ b/action-api/src/test/resources/ActionFileTestV1.0.json
@@ -81,6 +81,12 @@
"loadId" : "loadId1",
"relativeValue" : true,
"reactivePowerValue" : 5.0
+ }, {
+ "type" : "PCT_LOAD_CHANGE",
+ "id" : "id26",
+ "loadId" : "loadId1",
+ "p0PercentChange" : 5.0,
+ "qModificationStrategy" : "CONSTANT_Q"
}, {
"type" : "DANGLING_LINE",
"id" : "id17",
@@ -189,5 +195,14 @@
"elementId" : "transformerId25",
"side" : "THREE",
"open" : true
+ }, {
+ "type" : "AREA_INTERCHANGE_TARGET_ACTION",
+ "id" : "id99",
+ "areaId" : "AreaA",
+ "interchangeTarget" : 101.0
+ }, {
+ "type" : "AREA_INTERCHANGE_TARGET_ACTION",
+ "id" : "idDisabledTarget",
+ "areaId" : "AreaA"
} ]
}
\ No newline at end of file
diff --git a/action-ial/action-ial-dsl-spi/pom.xml b/action-ial/action-ial-dsl-spi/pom.xml
index cac4122ef5c..00d8b4225da 100644
--- a/action-ial/action-ial-dsl-spi/pom.xml
+++ b/action-ial/action-ial-dsl-spi/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-action-ial
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-action-ial-dsl-spi
diff --git a/action-ial/action-ial-dsl/pom.xml b/action-ial/action-ial-dsl/pom.xml
index e3177b05e52..ba30e248b28 100644
--- a/action-ial/action-ial-dsl/pom.xml
+++ b/action-ial/action-ial-dsl/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-action-ial
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-action-ial-dsl
diff --git a/action-ial/action-ial-simulator/pom.xml b/action-ial/action-ial-simulator/pom.xml
index ff5af699fa5..b6c6df5002c 100644
--- a/action-ial/action-ial-simulator/pom.xml
+++ b/action-ial/action-ial-simulator/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-action-ial
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-action-ial-simulator
diff --git a/action-ial/action-ial-util/pom.xml b/action-ial/action-ial-util/pom.xml
index 1f496842fe7..37475bd4b86 100644
--- a/action-ial/action-ial-util/pom.xml
+++ b/action-ial/action-ial-util/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-action-ial
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-action-ial-util
diff --git a/action-ial/pom.xml b/action-ial/pom.xml
index 0329bcaff63..d8539aaa3e9 100644
--- a/action-ial/pom.xml
+++ b/action-ial/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpom
diff --git a/ampl-converter/pom.xml b/ampl-converter/pom.xml
index c4d66573145..5a1e26c0afb 100644
--- a/ampl-converter/pom.xml
+++ b/ampl-converter/pom.xml
@@ -14,7 +14,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-ampl-converter
diff --git a/ampl-converter/src/main/java/com/powsybl/ampl/converter/AmplConstants.java b/ampl-converter/src/main/java/com/powsybl/ampl/converter/AmplConstants.java
index 1a8f7646b2a..d54b1b4553b 100644
--- a/ampl-converter/src/main/java/com/powsybl/ampl/converter/AmplConstants.java
+++ b/ampl-converter/src/main/java/com/powsybl/ampl/converter/AmplConstants.java
@@ -41,7 +41,7 @@ private AmplConstants() {
public static final String NUM = "num";
public static final String BUS = "bus";
public static final String P0 = "p0 (MW)";
- public static final String Q0 = "q0 (MW)";
+ public static final String Q0 = "q0 (MVar)";
public static final String ID = "id";
// End column headers
diff --git a/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/AmplExportVersion.java b/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/AmplExportVersion.java
index 17b385463eb..a4e0abc726d 100644
--- a/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/AmplExportVersion.java
+++ b/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/AmplExportVersion.java
@@ -25,7 +25,8 @@
public enum AmplExportVersion {
V1_0("1.0", BasicAmplExporter::new),
- V1_1("1.1", ExtendedAmplExporter::new);
+ V1_1("1.1", ExtendedAmplExporter::new),
+ V1_2("1.2", ExtendedAmplExporterV2::new);
public interface Factory {
AmplColumnsExporter create(AmplExportConfig config, Network network, StringToIntMapper mapper,
@@ -64,6 +65,6 @@ public static AmplExportVersion fromExporterId(String exporterId) {
}
public static AmplExportVersion defaultVersion() {
- return V1_1;
+ return V1_2;
}
}
diff --git a/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/BasicAmplExporter.java b/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/BasicAmplExporter.java
index 06267f37b52..e7e71ca40c6 100644
--- a/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/BasicAmplExporter.java
+++ b/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/BasicAmplExporter.java
@@ -279,7 +279,7 @@ public List getBatteriesColumns() {
new Column(CON_BUS),
new Column(SUBSTATION),
new Column(P0),
- new Column(Q0),
+ new Column("q0 (MW)"), // Wrong unit, fixed in v1.2
new Column(MINP),
new Column(MAXP),
new Column(MIN_Q_MAX_P),
@@ -446,20 +446,32 @@ public void writeHvdcToFormatter(TableFormatter formatter, HvdcLine hvdcLine) th
HvdcConverterStation.HvdcType type = hvdcLine.getConverterStation1().getHvdcType();
AmplSubset subset = type.equals(
HvdcConverterStation.HvdcType.VSC) ? AmplSubset.VSC_CONVERTER_STATION : AmplSubset.LCC_CONVERTER_STATION;
- formatter.writeCell(variantIndex)
- .writeCell(num)
- .writeCell(type.equals(HvdcConverterStation.HvdcType.VSC) ? 1 : 2)
- .writeCell(mapper.getInt(subset, hvdcLine.getConverterStation1().getId()))
- .writeCell(mapper.getInt(subset, hvdcLine.getConverterStation2().getId()))
- .writeCell(hvdcLine.getR())
- .writeCell(hvdcLine.getNominalV())
- .writeCell(hvdcLine.getConvertersMode().name())
- .writeCell(hvdcLine.getActivePowerSetpoint())
- .writeCell(hvdcLine.getMaxP())
- .writeCell(faultNum)
- .writeCell(actionNum)
- .writeCell(id)
- .writeCell(hvdcLine.getNameOrId());
+ TableFormatterHelper formatterHelper = new TableFormatterHelper(formatter);
+ formatterHelper.addCell(variantIndex)
+ .addCell(num)
+ .addCell(type.equals(HvdcConverterStation.HvdcType.VSC) ? 1 : 2)
+ .addCell(mapper.getInt(subset, hvdcLine.getConverterStation1().getId()))
+ .addCell(mapper.getInt(subset, hvdcLine.getConverterStation2().getId()))
+ .addCell(hvdcLine.getR())
+ .addCell(hvdcLine.getNominalV())
+ .addCell(hvdcLine.getConvertersMode().name())
+ .addCell(hvdcLine.getActivePowerSetpoint())
+ .addCell(hvdcLine.getMaxP())
+ .addCell(faultNum)
+ .addCell(actionNum)
+ .addCell(id)
+ .addCell(hvdcLine.getNameOrId());
+
+ // Add cells if necessary
+ addAdditionalCellsHvdcLine(formatterHelper, hvdcLine);
+
+ // Write the cells
+ formatterHelper.write();
+ }
+
+ public void addAdditionalCellsHvdcLine(TableFormatterHelper formatterHelper,
+ HvdcLine hvdcLine) {
+ // Nothing to do here
}
@Override
@@ -473,19 +485,31 @@ public void writeLccConverterStationToFormatter(TableFormatter formatter,
int num = mapper.getInt(AmplSubset.LCC_CONVERTER_STATION, lccStation.getId());
- formatter.writeCell(variantIndex)
- .writeCell(num)
- .writeCell(busNum)
- .writeCell(conBusNum != -1 ? conBusNum : busNum)
- .writeCell(vlNum)
- .writeCell(lccStation.getLossFactor())
- .writeCell(lccStation.getPowerFactor())
- .writeCell(faultNum)
- .writeCell(actionNum)
- .writeCell(lccStation.getId())
- .writeCell(lccStation.getNameOrId())
- .writeCell(t.getP())
- .writeCell(t.getQ());
+ TableFormatterHelper formatterHelper = new TableFormatterHelper(formatter);
+ formatterHelper.addCell(variantIndex)
+ .addCell(num)
+ .addCell(busNum)
+ .addCell(conBusNum != -1 ? conBusNum : busNum)
+ .addCell(vlNum)
+ .addCell(lccStation.getLossFactor())
+ .addCell(lccStation.getPowerFactor())
+ .addCell(faultNum)
+ .addCell(actionNum)
+ .addCell(lccStation.getId())
+ .addCell(lccStation.getNameOrId())
+ .addCell(t.getP())
+ .addCell(t.getQ());
+
+ // Add cells if necessary
+ addAdditionalCellsLccConverterStation(formatterHelper, lccStation);
+
+ // Write the cells
+ formatterHelper.write();
+ }
+
+ public void addAdditionalCellsLccConverterStation(TableFormatterHelper formatterHelper,
+ LccConverterStation lccStation) {
+ // Nothing to do here
}
@Override
diff --git a/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/ExtendedAmplExporterV2.java b/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/ExtendedAmplExporterV2.java
new file mode 100644
index 00000000000..979bed70a2a
--- /dev/null
+++ b/ampl-converter/src/main/java/com/powsybl/ampl/converter/version/ExtendedAmplExporterV2.java
@@ -0,0 +1,106 @@
+/**
+ * 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.ampl.converter.version;
+
+import com.powsybl.ampl.converter.AmplExportConfig;
+import com.powsybl.ampl.converter.AmplSubset;
+import com.powsybl.commons.io.table.Column;
+import com.powsybl.commons.io.table.TableFormatterHelper;
+import com.powsybl.commons.util.StringToIntMapper;
+import com.powsybl.iidm.network.*;
+import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControl;
+import com.powsybl.iidm.network.util.HvdcUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.powsybl.ampl.converter.AmplConstants.*;
+
+/**
+ * @author Pierre ARVY {@literal }
+ */
+public class ExtendedAmplExporterV2 extends ExtendedAmplExporter {
+
+ private static final int BATTERY_Q0_COLUMN_INDEX = 6;
+ private static final int GENERATOR_IS_CONDENSER_COLUMN_INDEX = 16;
+ private static final int LCC_TARGET_Q_COLUMN_INDEX = 5;
+ private static final int HVDC_AC_EMULATION_COLUMN_INDEX = 8;
+ private static final int HVDC_P_OFFSET_COLUMN_INDEX = 10;
+ private static final int HVDC_K_COLUMN_INDEX = 11;
+
+ public ExtendedAmplExporterV2(AmplExportConfig config,
+ Network network,
+ StringToIntMapper mapper,
+ int variantIndex, int faultNum, int actionNum) {
+ super(config, network, mapper, variantIndex, faultNum, actionNum);
+ }
+
+ @Override
+ public List getBatteriesColumns() {
+ List batteriesColumns = new ArrayList<>(super.getBatteriesColumns());
+ // fix unit of q0 column
+ batteriesColumns.set(BATTERY_Q0_COLUMN_INDEX, new Column(Q0));
+ return batteriesColumns;
+ }
+
+ @Override
+ public List getGeneratorsColumns() {
+ List generatorsColumns = new ArrayList<>(super.getGeneratorsColumns());
+ // add column to indicate if generator is a condenser
+ generatorsColumns.add(GENERATOR_IS_CONDENSER_COLUMN_INDEX, new Column("condenser"));
+ return generatorsColumns;
+ }
+
+ @Override
+ public List getLccConverterStationsColumns() {
+ List lccColumns = new ArrayList<>(super.getLccConverterStationsColumns());
+ // add columns for load target Q of converter station
+ lccColumns.add(LCC_TARGET_Q_COLUMN_INDEX, new Column(Q0));
+ return lccColumns;
+ }
+
+ @Override
+ public List getHvdcLinesColumns() {
+ List hvdcColumns = new ArrayList<>(super.getHvdcLinesColumns());
+ // add columns for AC emulation
+ hvdcColumns.add(HVDC_AC_EMULATION_COLUMN_INDEX, new Column("ac emul."));
+ hvdcColumns.add(HVDC_P_OFFSET_COLUMN_INDEX, new Column("P offset (MW)"));
+ hvdcColumns.add(HVDC_K_COLUMN_INDEX, new Column("k (MW/rad)"));
+ return hvdcColumns;
+ }
+
+ @Override
+ public void addAdditionalCellsGenerator(TableFormatterHelper formatterHelper, Generator gen) {
+ super.addAdditionalCellsGenerator(formatterHelper, gen);
+ formatterHelper.addCell(gen.isCondenser(), GENERATOR_IS_CONDENSER_COLUMN_INDEX);
+ }
+
+ @Override
+ public void addAdditionalCellsLccConverterStation(TableFormatterHelper formatterHelper,
+ LccConverterStation lccStation) {
+ double loadTargetQ = HvdcUtils.getLccConverterStationLoadTargetQ(lccStation);
+ formatterHelper.addCell(loadTargetQ, LCC_TARGET_Q_COLUMN_INDEX);
+ }
+
+ @Override
+ public void addAdditionalCellsHvdcLine(TableFormatterHelper formatterHelper,
+ HvdcLine hvdcLine) {
+ boolean isEnabled = false;
+ double p0 = Double.NaN;
+ double k = Double.NaN;
+ HvdcAngleDroopActivePowerControl droopControl = hvdcLine.getExtension(HvdcAngleDroopActivePowerControl.class);
+ if (droopControl != null) {
+ isEnabled = droopControl.isEnabled();
+ p0 = droopControl.getP0();
+ k = droopControl.getDroop() * 180 / Math.PI; // export MW/rad as voltage angles are exported in rad
+ }
+ formatterHelper.addCell(isEnabled, HVDC_AC_EMULATION_COLUMN_INDEX);
+ formatterHelper.addCell(p0, HVDC_P_OFFSET_COLUMN_INDEX);
+ formatterHelper.addCell(k, HVDC_K_COLUMN_INDEX);
+ }
+}
diff --git a/ampl-converter/src/test/java/com/powsybl/ampl/converter/AbstractAmplExporterTest.java b/ampl-converter/src/test/java/com/powsybl/ampl/converter/AbstractAmplExporterTest.java
new file mode 100644
index 00000000000..4200e22569c
--- /dev/null
+++ b/ampl-converter/src/test/java/com/powsybl/ampl/converter/AbstractAmplExporterTest.java
@@ -0,0 +1,46 @@
+/**
+ * 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.ampl.converter;
+
+import com.powsybl.commons.datasource.MemDataSource;
+import com.powsybl.commons.test.AbstractSerDeTest;
+import org.junit.jupiter.api.BeforeEach;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import static com.powsybl.commons.test.ComparisonUtils.assertTxtEquals;
+
+/**
+ * @author Geoffroy Jamgotchian {@literal }
+ * @author Pierre ARVY {@literal }
+ */
+abstract class AbstractAmplExporterTest extends AbstractSerDeTest {
+
+ MemDataSource dataSource;
+ AmplExporter exporter;
+ Properties properties;
+
+ protected void assertEqualsToRef(MemDataSource dataSource, String suffix, String refFileName) throws IOException {
+ try (InputStream actual = new ByteArrayInputStream(dataSource.getData(suffix, "txt"))) {
+ assertTxtEquals(getClass().getResourceAsStream("/" + refFileName), actual);
+ }
+ }
+
+ @Override
+ @BeforeEach
+ public void setUp() throws IOException {
+ super.setUp();
+ dataSource = new MemDataSource();
+ exporter = new AmplExporter();
+ properties = new Properties();
+ }
+
+}
diff --git a/ampl-converter/src/test/java/com/powsybl/ampl/converter/AmplNetworkWriterTest.java b/ampl-converter/src/test/java/com/powsybl/ampl/converter/AmplNetworkWriterTest.java
index f2fec589d23..da0b5bfd413 100644
--- a/ampl-converter/src/test/java/com/powsybl/ampl/converter/AmplNetworkWriterTest.java
+++ b/ampl-converter/src/test/java/com/powsybl/ampl/converter/AmplNetworkWriterTest.java
@@ -7,7 +7,6 @@
*/
package com.powsybl.ampl.converter;
-import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.commons.datasource.DataSource;
import com.powsybl.commons.datasource.MemDataSource;
import com.powsybl.iidm.network.*;
@@ -15,32 +14,20 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.util.Properties;
-import static com.powsybl.commons.test.ComparisonUtils.assertTxtEquals;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Geoffroy Jamgotchian {@literal }
*/
-class AmplNetworkWriterTest extends AbstractSerDeTest {
-
- Properties properties;
-
- private void assertEqualsToRef(MemDataSource dataSource, String suffix, String refFileName) throws IOException {
- try (InputStream actual = new ByteArrayInputStream(dataSource.getData(suffix, "txt"))) {
- assertTxtEquals(getClass().getResourceAsStream("/" + refFileName), actual);
- }
- }
+class AmplNetworkWriterTest extends AbstractAmplExporterTest {
@Override
@BeforeEach
public void setUp() throws IOException {
super.setUp();
- properties = new Properties();
properties.put("iidm.export.ampl.export-version", "1.0");
}
@@ -274,7 +261,7 @@ void writeHeadersWithUnknownVersion() {
Exception e = assertThrows(IllegalArgumentException.class, () -> export(network, properties, dataSource));
- assertTrue(e.getMessage().contains("Value V1_0 of parameter iidm.export.ampl.export-version is not contained in possible values [1.0, 1.1]"));
+ assertTrue(e.getMessage().contains("Value V1_0 of parameter iidm.export.ampl.export-version is not contained in possible values [1.0, 1.1, 1.2]"));
}
@Test
diff --git a/ampl-converter/src/test/java/com/powsybl/ampl/converter/ExtendedAmplExporterTest.java b/ampl-converter/src/test/java/com/powsybl/ampl/converter/ExtendedAmplExporterTest.java
index 352bc050956..ac9d4f708c9 100644
--- a/ampl-converter/src/test/java/com/powsybl/ampl/converter/ExtendedAmplExporterTest.java
+++ b/ampl-converter/src/test/java/com/powsybl/ampl/converter/ExtendedAmplExporterTest.java
@@ -7,44 +7,25 @@
*/
package com.powsybl.ampl.converter;
-import com.powsybl.commons.datasource.MemDataSource;
-import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.SlackTerminalAdder;
import com.powsybl.iidm.network.test.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-
-import static com.powsybl.commons.test.ComparisonUtils.assertTxtEquals;
/**
* @author Nicolas PIERRE {@literal }
* @author Pierre ARVY {@literal }
*/
-class ExtendedAmplExporterTest extends AbstractSerDeTest {
-
- MemDataSource dataSource;
- AmplExporter exporter;
- Properties properties;
-
- private void assertEqualsToRef(MemDataSource dataSource, String suffix, String refFileName) throws IOException {
- try (InputStream actual = new ByteArrayInputStream(dataSource.getData(suffix, "txt"))) {
- assertTxtEquals(getClass().getResourceAsStream("/" + refFileName), actual);
- }
- }
+class ExtendedAmplExporterTest extends AbstractAmplExporterTest {
@Override
@BeforeEach
public void setUp() throws IOException {
super.setUp();
- dataSource = new MemDataSource();
- exporter = new AmplExporter();
- properties = new Properties();
+ properties.put("iidm.export.ampl.export-version", "1.1");
}
@Test
diff --git a/ampl-converter/src/test/java/com/powsybl/ampl/converter/ExtendedAmplExporterV2Test.java b/ampl-converter/src/test/java/com/powsybl/ampl/converter/ExtendedAmplExporterV2Test.java
new file mode 100644
index 00000000000..7d17b632a52
--- /dev/null
+++ b/ampl-converter/src/test/java/com/powsybl/ampl/converter/ExtendedAmplExporterV2Test.java
@@ -0,0 +1,121 @@
+/**
+ * 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.ampl.converter;
+
+import com.powsybl.iidm.network.Network;
+import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControlAdder;
+import com.powsybl.iidm.network.test.BatteryNetworkFactory;
+import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
+import com.powsybl.iidm.network.test.HvdcTestNetwork;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+/**
+ * @author Pierre ARVY {@literal }
+ */
+class ExtendedAmplExporterV2Test extends AbstractAmplExporterTest {
+
+ @Test
+ void testNoModifiedExports() throws IOException {
+ Network network = EurostagTutorialExample1Factory.createWithMoreGenerators();
+
+ exporter.export(network, properties, dataSource);
+
+ // no modification compared to ampl exporter v1.0
+ assertEqualsToRef(dataSource, "_network_substations", "inputs/eurostag-tutorial-example1-substations.txt");
+ assertEqualsToRef(dataSource, "_network_rtc", "inputs/eurostag-tutorial-example1-rtc.txt");
+ assertEqualsToRef(dataSource, "_network_ptc", "inputs/eurostag-tutorial-example1-ptc.txt");
+ assertEqualsToRef(dataSource, "_network_loads", "inputs/eurostag-tutorial-example1-loads.txt");
+ assertEqualsToRef(dataSource, "_network_limits", "inputs/eurostag-tutorial-example1-limits.txt");
+ }
+
+ @Test
+ void testQ0UnitColumnBatteries() throws IOException {
+ Network network = BatteryNetworkFactory.create();
+
+ exporter.export(network, properties, dataSource);
+
+ assertEqualsToRef(dataSource, "_network_batteries", "inputs/extended_exporter_v2/battery-q0-unit-column.txt");
+ }
+
+ @Test
+ void testIsCondenserExportGenerators() throws IOException {
+ Network network = EurostagTutorialExample1Factory.createWithMoreGenerators();
+ network.getVoltageLevel("VLGEN").newGenerator()
+ .setId("GEN3")
+ .setBus("NGEN")
+ .setConnectableBus("NGEN")
+ .setMinP(-9999.99)
+ .setMaxP(9999.99)
+ .setVoltageRegulatorOn(true)
+ .setRegulatingTerminal(network.getLoad("LOAD").getTerminal())
+ .setTargetV(152.5)
+ .setTargetP(607.0)
+ .setTargetQ(301.0)
+ .setCondenser(true)
+ .add();
+
+ exporter.export(network, properties, dataSource);
+
+ assertEqualsToRef(dataSource, "_network_generators",
+ "inputs/extended_exporter_v2/eurostag-tutorial-example1-generators-is-condenser.txt");
+ }
+
+ @Test
+ void testLccLoadTargetQ() throws IOException {
+ Network network = HvdcTestNetwork.createLcc();
+
+ exporter.export(network, properties, dataSource);
+
+ // Check hvdc line has null parameter for ac emulation
+ assertEqualsToRef(dataSource, "_network_hvdc", "inputs/extended_exporter_v2/hvdc-ac-emul-lcc-test-case.txt");
+
+ // Check target Q has been added to LCC converter station table
+ assertEqualsToRef(dataSource, "_network_lcc_converter_stations", "inputs/extended_exporter_v2/lcc-load-target-q-test-case.txt");
+ }
+
+ @Test
+ void testHvdcNoAcEmulation() throws IOException {
+ Network network = HvdcTestNetwork.createVsc();
+
+ exporter.export(network, properties, dataSource);
+
+ // Check that export is the same as for basic AMPL exporter
+ assertEqualsToRef(dataSource, "_network_vsc_converter_stations", "inputs/vsc-test-case.txt");
+
+ // Check hvdc line has null parameter for ac emulation
+ assertEqualsToRef(dataSource, "_network_hvdc", "inputs/extended_exporter_v2/hvdc-vsc-test-case.txt");
+ }
+
+ @Test
+ void testHvdcAcEmulation() throws IOException {
+ Network network = HvdcTestNetwork.createVsc();
+ network.getHvdcLine("L").newExtension(HvdcAngleDroopActivePowerControlAdder.class)
+ .withP0(200.0f)
+ .withDroop(0.9f)
+ .withEnabled(true)
+ .add();
+
+ exporter.export(network, properties, dataSource);
+
+ // Check that export is the same as for basic AMPL exporter
+ assertEqualsToRef(dataSource, "_network_vsc_converter_stations", "inputs/vsc-test-case.txt");
+
+ // Check ac emulation parameters of the hvdc line
+ assertEqualsToRef(dataSource, "_network_hvdc", "inputs/extended_exporter_v2/hvdc-ac-emul-vsc-test-case.txt");
+ }
+
+ @Test
+ void writeHeadersWithVersion12() throws IOException {
+ Network network = Network.create("dummy_network", "test");
+ exporter.export(network, properties, dataSource);
+ assertEqualsToRef(dataSource, "_headers", "inputs/extended_exporter_v2/headers.txt");
+ }
+
+}
diff --git a/ampl-converter/src/test/resources/inputs/extended_exporter_v2/battery-q0-unit-column.txt b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/battery-q0-unit-column.txt
new file mode 100644
index 00000000000..64c1bfc93fb
--- /dev/null
+++ b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/battery-q0-unit-column.txt
@@ -0,0 +1,4 @@
+#Batteries (fictitious/InitialState)
+#"variant" "num" "bus" "con. bus" "substation" "p0 (MW)" "q0 (MVar)" "minP (MW)" "maxP (MW)" "minQmaxP (MVar)" "minQ0 (MVar)" "minQminP (MVar)" "maxQmaxP (MVar)" "maxQ0 (MVar)" "maxQminP (MVar)" "fault" "curative" "id" "description" "P (MW)" "Q (MVar)"
+1 1 2 2 2 9999.99 9999.99 -9999.99 9999.99 -9999.99 -9999.99 -9999.99 9999.99 9999.99 9999.99 0 0 "BAT" "BAT" -605.000 -225.000
+1 2 2 2 2 100.000 200.000 -200.000 200.000 -54.5500 -59.3000 -59.3000 46.2500 60.0000 60.0000 0 0 "BAT2" "BAT2" -605.000 -225.000
diff --git a/ampl-converter/src/test/resources/inputs/extended_exporter_v2/eurostag-tutorial-example1-generators-is-condenser.txt b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/eurostag-tutorial-example1-generators-is-condenser.txt
new file mode 100644
index 00000000000..4f489e6465f
--- /dev/null
+++ b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/eurostag-tutorial-example1-generators-is-condenser.txt
@@ -0,0 +1,5 @@
+#Generators (sim1/InitialState)
+#"variant" "num" "bus" "con. bus" "substation" "minP (MW)" "maxP (MW)" "minQmaxP (MVar)" "minQ0 (MVar)" "minQminP (MVar)" "maxQmaxP (MVar)" "maxQ0 (MVar)" "maxQminP (MVar)" "v regul." "v regul. bus" "targetV (pu)" "condenser" "targetP (MW)" "targetQ (MVar)" "fault" "curative" "id" "description" "P (MW)" "Q (MVar)"
+1 1 1 1 1 -9999.99 9999.99 -9999.99 -9999.99 -9999.99 9999.99 9999.99 9999.99 true 1 1.02083 false 607.000 301.000 0 0 "GEN" "GEN" -99999.0 -99999.0
+1 2 1 1 1 -9999.99 9999.99 4.00000 6.00000 6.00000 5.00000 7.00000 7.00000 true 1 1.02083 false 607.000 301.000 0 0 "GEN2" "GEN2" -99999.0 -99999.0
+1 3 1 1 1 -9999.99 9999.99 -1.79769e+308 -1.79769e+308 -1.79769e+308 1.79769e+308 1.79769e+308 1.79769e+308 true 4 1.01667 true 607.000 301.000 0 0 "GEN3" "GEN3" -99999.0 -99999.0
diff --git a/ampl-converter/src/test/resources/inputs/extended_exporter_v2/headers.txt b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/headers.txt
new file mode 100644
index 00000000000..ef7df68cb0f
--- /dev/null
+++ b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/headers.txt
@@ -0,0 +1 @@
+version 1.2
diff --git a/ampl-converter/src/test/resources/inputs/extended_exporter_v2/hvdc-ac-emul-lcc-test-case.txt b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/hvdc-ac-emul-lcc-test-case.txt
new file mode 100644
index 00000000000..ad929ff3a19
--- /dev/null
+++ b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/hvdc-ac-emul-lcc-test-case.txt
@@ -0,0 +1,3 @@
+#HVDC lines (hvdctest/InitialState)
+#"variant" "num" "type" "converterStation1" "converterStation2" "r (ohm)" "nomV (KV)" "convertersMode" "ac emul." "targetP (MW)" "P offset (MW)" "k (MW/rad)" "maxP (MW)" "fault" "curative" "id" "description"
+1 1 2 1 2 1.00000 400.000 "SIDE_1_INVERTER_SIDE_2_RECTIFIER" false 280.000 -99999.0 -99999.0 300.000 0 0 "L" "HVDC"
diff --git a/ampl-converter/src/test/resources/inputs/extended_exporter_v2/hvdc-ac-emul-vsc-test-case.txt b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/hvdc-ac-emul-vsc-test-case.txt
new file mode 100644
index 00000000000..f07d41ef807
--- /dev/null
+++ b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/hvdc-ac-emul-vsc-test-case.txt
@@ -0,0 +1,3 @@
+#HVDC lines (hvdctest/InitialState)
+#"variant" "num" "type" "converterStation1" "converterStation2" "r (ohm)" "nomV (KV)" "convertersMode" "ac emul." "targetP (MW)" "P offset (MW)" "k (MW/rad)" "maxP (MW)" "fault" "curative" "id" "description"
+1 1 1 1 2 1.00000 400.000 "SIDE_1_INVERTER_SIDE_2_RECTIFIER" true 280.000 200.000 51.5662 300.000 0 0 "L" "HVDC"
diff --git a/ampl-converter/src/test/resources/inputs/extended_exporter_v2/hvdc-vsc-test-case.txt b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/hvdc-vsc-test-case.txt
new file mode 100644
index 00000000000..7aed156ff8e
--- /dev/null
+++ b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/hvdc-vsc-test-case.txt
@@ -0,0 +1,3 @@
+#HVDC lines (hvdctest/InitialState)
+#"variant" "num" "type" "converterStation1" "converterStation2" "r (ohm)" "nomV (KV)" "convertersMode" "ac emul." "targetP (MW)" "P offset (MW)" "k (MW/rad)" "maxP (MW)" "fault" "curative" "id" "description"
+1 1 1 1 2 1.00000 400.000 "SIDE_1_INVERTER_SIDE_2_RECTIFIER" false 280.000 -99999.0 -99999.0 300.000 0 0 "L" "HVDC"
diff --git a/ampl-converter/src/test/resources/inputs/extended_exporter_v2/lcc-load-target-q-test-case.txt b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/lcc-load-target-q-test-case.txt
new file mode 100644
index 00000000000..e17116f517a
--- /dev/null
+++ b/ampl-converter/src/test/resources/inputs/extended_exporter_v2/lcc-load-target-q-test-case.txt
@@ -0,0 +1,4 @@
+#LCC Converter Stations (hvdctest/InitialState)
+#"variant" "num" "bus" "con. bus" "substation" "q0 (MVar)" "lossFactor (%PDC)" "powerFactor" "fault" "curative" "id" "description" "P (MW)" "Q (MVar)"
+1 1 1 1 1 473.542 1.10000 0.500000 0 0 "C1" "Converter1" 100.000 50.0000
+1 2 2 2 2 373.333 1.10000 0.600000 0 0 "C2" "Converter2" 75.0000 25.0000
diff --git a/ampl-executor/pom.xml b/ampl-executor/pom.xml
index fe2f62c3abe..6c4eec43c5f 100644
--- a/ampl-executor/pom.xml
+++ b/ampl-executor/pom.xml
@@ -15,7 +15,7 @@ this
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-ampl-executor
diff --git a/cgmes/cgmes-completion/pom.xml b/cgmes/cgmes-completion/pom.xml
index 011fb861b8e..0517dcf89ad 100644
--- a/cgmes/cgmes-completion/pom.xml
+++ b/cgmes/cgmes-completion/pom.xml
@@ -16,7 +16,7 @@
com.powsyblpowsybl-cgmes
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes-completion
diff --git a/cgmes/cgmes-conformity/pom.xml b/cgmes/cgmes-conformity/pom.xml
index 90a29b56fbc..004965a179d 100644
--- a/cgmes/cgmes-conformity/pom.xml
+++ b/cgmes/cgmes-conformity/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-cgmes
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes-conformity
diff --git a/cgmes/cgmes-conformity/src/main/java/com/powsybl/cgmes/conformity/CgmesConformity1Catalog.java b/cgmes/cgmes-conformity/src/main/java/com/powsybl/cgmes/conformity/CgmesConformity1Catalog.java
index 77dafc06140..a32013acffc 100644
--- a/cgmes/cgmes-conformity/src/main/java/com/powsybl/cgmes/conformity/CgmesConformity1Catalog.java
+++ b/cgmes/cgmes-conformity/src/main/java/com/powsybl/cgmes/conformity/CgmesConformity1Catalog.java
@@ -1087,6 +1087,11 @@ public static CgmesModel expectedMicroGridType4BE() {
"fd227658-0e1b-4ecd-952a-c6b0307b1ea11",
"ff466d18-e4f5-439b-a50a-daec2fa41e2c",
"ff466d18-e4f5-439b-a50a-daec2fa41e2c1");
+ m.shuntCompensatorsPoints("46e3d51d-0a41-4e3f-8ce5-63e7bb165b73",
+ "7dc75c5a-74cc-434c-a125-860960b6ed35",
+ "89e965d7-0348-4dc1-98d4-be3bf8891fad",
+ "8b93ca77-3cc3-4c82-8524-2f8a13513e20",
+ "ca954c3a-5194-49eb-9097-10c77cea36b9");
Set tlremove = new HashSet<>(Arrays.asList(
"acbd4688-6393-4b43-a9f4-27d8c3f8c309",
"1c8440dc-e65d-4337-9d3e-7558062228da1",
diff --git a/cgmes/cgmes-conversion/pom.xml b/cgmes/cgmes-conversion/pom.xml
index d40391ee8d7..faa9465306f 100644
--- a/cgmes/cgmes-conversion/pom.xml
+++ b/cgmes/cgmes-conversion/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-cgmes
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes-conversion
diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java
index 311efbc54fc..e5369dbd89a 100644
--- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java
+++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java
@@ -423,6 +423,7 @@ private void addParametersToContext(CgmesExportContext context, Properties param
.setExportTransformersWithHighestVoltageAtEnd1(Parameter.readBoolean(getFormat(), params, EXPORT_TRANSFORMERS_WITH_HIGHEST_VOLTAGE_AT_END1_PARAMETER, defaultValueConfig))
.setExportLoadFlowStatus(Parameter.readBoolean(getFormat(), params, EXPORT_LOAD_FLOW_STATUS_PARAMETER, defaultValueConfig))
.setExportAllLimitsGroup(Parameter.readBoolean(getFormat(), params, EXPORT_ALL_LIMITS_GROUP_PARAMETER, defaultValueConfig))
+ .setExportGeneratorsInLocalRegulationMode(Parameter.readBoolean(getFormat(), params, EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_PARAMETER, defaultValueConfig))
.setMaxPMismatchConverged(Parameter.readDouble(getFormat(), params, MAX_P_MISMATCH_CONVERGED_PARAMETER, defaultValueConfig))
.setMaxQMismatchConverged(Parameter.readDouble(getFormat(), params, MAX_Q_MISMATCH_CONVERGED_PARAMETER, defaultValueConfig))
.setExportSvInjectionsForSlacks(Parameter.readBoolean(getFormat(), params, EXPORT_SV_INJECTIONS_FOR_SLACKS_PARAMETER, defaultValueConfig))
@@ -544,6 +545,7 @@ public String getFormat() {
public static final String EXPORT_TRANSFORMERS_WITH_HIGHEST_VOLTAGE_AT_END1 = "iidm.export.cgmes.export-transformers-with-highest-voltage-at-end1";
public static final String EXPORT_LOAD_FLOW_STATUS = "iidm.export.cgmes.export-load-flow-status";
public static final String EXPORT_ALL_LIMITS_GROUP = "iidm.export.cgmes.export-all-limits-group";
+ public static final String EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE = "iidm.export.cgmes.export-generators-in-local-regulation-mode";
public static final String MAX_P_MISMATCH_CONVERGED = "iidm.export.cgmes.max-p-mismatch-converged";
public static final String MAX_Q_MISMATCH_CONVERGED = "iidm.export.cgmes.max-q-mismatch-converged";
public static final String EXPORT_SV_INJECTIONS_FOR_SLACKS = "iidm.export.cgmes.export-sv-injections-for-slacks";
@@ -637,7 +639,12 @@ public String getFormat() {
EXPORT_ALL_LIMITS_GROUP,
ParameterType.BOOLEAN,
"True to export all OperationalLimitsGroup, False to export only the selected group",
- CgmesExportContext.EXPORT_LOAD_FLOW_STATUS_DEFAULT_VALUE);
+ CgmesExportContext.EXPORT_ALL_LIMITS_GROUP_DEFAULT_VALUE);
+ private static final Parameter EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_PARAMETER = new Parameter(
+ EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE,
+ ParameterType.BOOLEAN,
+ "True to export voltage regulating generators in local regulation mode, False to keep their regulation mode unchanged.",
+ CgmesExportContext.EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_DEFAULT_VALUE);
private static final Parameter MAX_P_MISMATCH_CONVERGED_PARAMETER = new Parameter(
MAX_P_MISMATCH_CONVERGED,
ParameterType.DOUBLE,
diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java
index f41b2311f89..628f4a8d15d 100644
--- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java
+++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java
@@ -1099,9 +1099,11 @@ public Config setCreateFictitiousVoltageLevelsForEveryNode(boolean b) {
public static final String PROPERTY_IS_EQUIVALENT_SHUNT = CGMES_PREFIX_ALIAS_PROPERTIES + "isEquivalentShunt";
public static final String PROPERTY_HYDRO_PLANT_STORAGE_TYPE = CGMES_PREFIX_ALIAS_PROPERTIES + "hydroPlantStorageKind";
public static final String PROPERTY_FOSSIL_FUEL_TYPE = CGMES_PREFIX_ALIAS_PROPERTIES + "fuelType";
+ public static final String PROPERTY_WIND_GEN_UNIT_TYPE = CGMES_PREFIX_ALIAS_PROPERTIES + "windGenUnitType";
public static final String PROPERTY_CGMES_ORIGINAL_CLASS = CGMES_PREFIX_ALIAS_PROPERTIES + "originalClass";
public static final String PROPERTY_BUSBAR_SECTION_TERMINALS = CGMES_PREFIX_ALIAS_PROPERTIES + "busbarSectionTerminals";
public static final String PROPERTY_CGMES_GOVERNOR_SCD = CGMES_PREFIX_ALIAS_PROPERTIES + "governorSCD";
public static final String PROPERTY_CGMES_SYNCHRONOUS_MACHINE_TYPE = CGMES_PREFIX_ALIAS_PROPERTIES + "synchronousMachineType";
public static final String PROPERTY_CGMES_SYNCHRONOUS_MACHINE_OPERATING_MODE = CGMES_PREFIX_ALIAS_PROPERTIES + "synchronousMachineOperatingMode";
+ public static final String PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS = CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.OPERATIONAL_LIMIT_SET + "_identifiers";
}
diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/OperationalLimitConversion.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/OperationalLimitConversion.java
index 33ea40fccd5..65def7f741d 100644
--- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/OperationalLimitConversion.java
+++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/OperationalLimitConversion.java
@@ -8,15 +8,21 @@
package com.powsybl.cgmes.conversion.elements;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.powsybl.cgmes.conversion.Context;
-import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.cgmes.model.CgmesNames;
+import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.*;
import com.powsybl.triplestore.api.PropertyBag;
import java.util.Optional;
import java.util.function.Supplier;
+import static com.powsybl.cgmes.conversion.Conversion.PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS;
+
/**
* @author Luma Zamarreño {@literal }
*/
@@ -28,7 +34,6 @@ public class OperationalLimitConversion extends AbstractIdentifiedObjectConversi
private static final String OPERATIONAL_LIMIT_SUBCLASS = "OperationalLimitSubclass";
private static final String OPERATIONAL_LIMIT_SET_ID = "OperationalLimitSet";
private static final String OPERATIONAL_LIMIT_SET_NAME = "OperationalLimitSetName";
- private static final String PROPERTY_PREFIX = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.OPERATIONAL_LIMIT_SET + "_";
private static final String PERMANENT_LIMIT = "Permanent Limit";
private static final String TEMPORARY_LIMIT = "Temporary Limit";
@@ -81,6 +86,30 @@ private void setVoltageLevelForVoltageLimit(Terminal terminal) {
}
}
+ /**
+ * Store the CGMES OperationalLimitSet id/name pair in a property of the identifiable.
+ * If the property already exists, meaning it has been created for another limit set of that identifiable,
+ * then append the id/name pair to the property value (which actually represents a serialized json).
+ * @param identifiable The Branch, DanglingLine, ThreeWindingsTransformer where the limit set id/name are stored.
+ * @param limitSetId The OperationalLimitSet id to store.
+ * @param limitSetName The OperationalLimitSet name to store.
+ */
+ private void storeOperationalLimitSetIdentifiers(Identifiable> identifiable, String limitSetId, String limitSetName) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode node;
+ if (identifiable.hasProperty(PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS)) {
+ node = mapper.readTree(identifiable.getProperty(PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS));
+ } else {
+ node = mapper.createObjectNode();
+ }
+ ((ObjectNode) node).put(limitSetId, limitSetName);
+ identifiable.setProperty(PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS, mapper.writeValueAsString(node));
+ } catch (JsonProcessingException e) {
+ throw new PowsyblException(e.getMessage(), e);
+ }
+ }
+
/**
* Create the LoadingLimitsAdder for the given branch + side and the given limit set + subclass.
* @param terminalNumber The side of the branch to which the OperationalLimit applies.
@@ -92,12 +121,12 @@ private void setVoltageLevelForVoltageLimit(Terminal terminal) {
private void createLimitsAdder(int terminalNumber, String limitSubClass, String limitSetId, String limitSetName, Branch> b) {
if (terminalNumber == 1) {
OperationalLimitsGroup limitsGroup = b.getOperationalLimitsGroup1(limitSetId).orElseGet(() -> {
- b.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName);
+ storeOperationalLimitSetIdentifiers(b, limitSetId, limitSetName);
return b.newOperationalLimitsGroup1(limitSetId); });
loadingLimitsAdder1 = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass);
} else if (terminalNumber == 2) {
OperationalLimitsGroup limitsGroup = b.getOperationalLimitsGroup2(limitSetId).orElseGet(() -> {
- b.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName);
+ storeOperationalLimitSetIdentifiers(b, limitSetId, limitSetName);
return b.newOperationalLimitsGroup2(limitSetId); });
loadingLimitsAdder2 = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass);
} else {
@@ -114,7 +143,7 @@ private void createLimitsAdder(int terminalNumber, String limitSubClass, String
*/
private void createLimitsAdder(String limitSubClass, String limitSetId, String limitSetName, DanglingLine dl) {
OperationalLimitsGroup limitsGroup = dl.getOperationalLimitsGroup(limitSetId).orElseGet(() -> {
- dl.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName);
+ storeOperationalLimitSetIdentifiers(dl, limitSetId, limitSetName);
return dl.newOperationalLimitsGroup(limitSetId); });
loadingLimitsAdder = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass);
}
@@ -130,17 +159,17 @@ private void createLimitsAdder(String limitSubClass, String limitSetId, String l
private void createLimitsAdder(int terminalNumber, String limitSubClass, String limitSetId, String limitSetName, ThreeWindingsTransformer twt) {
if (terminalNumber == 1) {
OperationalLimitsGroup limitsGroup = twt.getLeg1().getOperationalLimitsGroup(limitSetId).orElseGet(() -> {
- twt.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName);
+ storeOperationalLimitSetIdentifiers(twt, limitSetId, limitSetName);
return twt.getLeg1().newOperationalLimitsGroup(limitSetId); });
loadingLimitsAdder = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass);
} else if (terminalNumber == 2) {
OperationalLimitsGroup limitsGroup = twt.getLeg2().getOperationalLimitsGroup(limitSetId).orElseGet(() -> {
- twt.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName);
+ storeOperationalLimitSetIdentifiers(twt, limitSetId, limitSetName);
return twt.getLeg2().newOperationalLimitsGroup(limitSetId); });
loadingLimitsAdder = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass);
} else if (terminalNumber == 3) {
OperationalLimitsGroup limitsGroup = twt.getLeg3().getOperationalLimitsGroup(limitSetId).orElseGet(() -> {
- twt.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName);
+ storeOperationalLimitSetIdentifiers(twt, limitSetId, limitSetName);
return twt.getLeg3().newOperationalLimitsGroup(limitSetId); });
loadingLimitsAdder = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass);
} else {
diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/SwitchConversion.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/SwitchConversion.java
index 60b3ef99b7b..4410b2c9465 100644
--- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/SwitchConversion.java
+++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/SwitchConversion.java
@@ -118,6 +118,8 @@ private SwitchKind kind() {
return SwitchKind.DISCONNECTOR;
} else if (type.contains("loadbreak")) {
return SwitchKind.LOAD_BREAK_SWITCH;
+ } else if (type.contains("jumper")) {
+ return SwitchKind.DISCONNECTOR;
}
return SwitchKind.BREAKER;
}
diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/SynchronousMachineConversion.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/SynchronousMachineConversion.java
index 9f44b011066..f161afed3e2 100644
--- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/SynchronousMachineConversion.java
+++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/SynchronousMachineConversion.java
@@ -30,7 +30,8 @@ public class SynchronousMachineConversion extends AbstractReactiveLimitsOwnerCon
public SynchronousMachineConversion(PropertyBag sm, Context context) {
super(CgmesNames.SYNCHRONOUS_MACHINE, sm, context);
String type = p.getLocal("type");
- isCondenser = type != null && type.endsWith("Kind.condenser");
+ // CIM14 uses Type.condenser, CIM16 and CIM100 use Kind.condenser
+ isCondenser = type != null && type.endsWith(".condenser");
}
@Override
@@ -61,6 +62,7 @@ public void convert() {
.setRatedS(ratedS);
identify(adder);
connect(adder);
+ adder.setCondenser(isCondenser);
Generator g = adder.add();
addAliasesAndProperties(g);
convertedTerminals(g.getTerminal());
@@ -121,6 +123,10 @@ private static void addSpecificGeneratingUnitProperties(Generator generator, Pro
if (!fossilFuelType.isEmpty()) {
generator.setProperty(Conversion.PROPERTY_FOSSIL_FUEL_TYPE, fossilFuelType);
}
+ String windGenUnitType = p.getLocal("windGenUnitType");
+ if (windGenUnitType != null) {
+ generator.setProperty(Conversion.PROPERTY_WIND_GEN_UNIT_TYPE, windGenUnitType.replace("WindGenUnitKind.", ""));
+ }
}
private EnergySource energySourceFromGeneratingUnitType() {
diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/CgmesExportContext.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/CgmesExportContext.java
index a4f8f6df99f..409b0376e1f 100644
--- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/CgmesExportContext.java
+++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/CgmesExportContext.java
@@ -74,6 +74,7 @@ public class CgmesExportContext {
public static final boolean ENCODE_IDS_DEFAULT_VALUE = true;
public static final boolean EXPORT_LOAD_FLOW_STATUS_DEFAULT_VALUE = true;
public static final boolean EXPORT_ALL_LIMITS_GROUP_DEFAULT_VALUE = true;
+ public static final boolean EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_DEFAULT_VALUE = false;
// From QoCDC 3.3.1 rules IGMConvergence, KirchhoffsFirstLaw, ... that refer to SV_INJECTION_LIMIT=0.1
public static final double MAX_P_MISMATCH_CONVERGED_DEFAULT_VALUE = 0.1;
public static final double MAX_Q_MISMATCH_CONVERGED_DEFAULT_VALUE = 0.1;
@@ -88,6 +89,7 @@ public class CgmesExportContext {
private boolean exportTransformersWithHighestVoltageAtEnd1 = EXPORT_TRANSFORMERS_WITH_HIGHEST_VOLTAGE_AT_END1_DEFAULT_VALUE;
private boolean exportLoadFlowStatus = EXPORT_LOAD_FLOW_STATUS_DEFAULT_VALUE;
private boolean exportAllLimitsGroup = EXPORT_ALL_LIMITS_GROUP_DEFAULT_VALUE;
+ private boolean exportGeneratorsInLocalRegulationMode = EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_DEFAULT_VALUE;
private double maxPMismatchConverged = MAX_P_MISMATCH_CONVERGED_DEFAULT_VALUE;
private double maxQMismatchConverged = MAX_Q_MISMATCH_CONVERGED_DEFAULT_VALUE;
private boolean isExportSvInjectionsForSlacks = EXPORT_SV_INJECTIONS_FOR_SLACKS_DEFAULT_VALUE;
@@ -619,6 +621,15 @@ public CgmesExportContext setExportAllLimitsGroup(boolean exportAllLimitsGroup)
return this;
}
+ public boolean isExportGeneratorsInLocalRegulationMode() {
+ return exportGeneratorsInLocalRegulationMode;
+ }
+
+ public CgmesExportContext setExportGeneratorsInLocalRegulationMode(boolean exportGeneratorsInLocalRegulationMode) {
+ this.exportGeneratorsInLocalRegulationMode = exportGeneratorsInLocalRegulationMode;
+ return this;
+ }
+
public double getMaxPMismatchConverged() {
return maxPMismatchConverged;
}
diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/EquipmentExport.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/EquipmentExport.java
index c9ec4f180c9..1ecfdecb705 100644
--- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/EquipmentExport.java
+++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/EquipmentExport.java
@@ -7,6 +7,9 @@
*/
package com.powsybl.cgmes.conversion.export;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.powsybl.cgmes.conversion.CgmesExport;
import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.cgmes.conversion.naming.NamingStrategy;
@@ -382,7 +385,15 @@ private static void writeGenerators(Network network, Map mapTe
String cgmesOriginalClass = generator.getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS, CgmesNames.SYNCHRONOUS_MACHINE);
RemoteReactivePowerControl rrpc = generator.getExtension(RemoteReactivePowerControl.class);
String mode = CgmesExportUtil.getGeneratorRegulatingControlMode(generator, rrpc);
- Terminal regulatingTerminal = mode.equals(RegulatingControlEq.REGULATING_CONTROL_VOLTAGE) ? generator.getRegulatingTerminal() : rrpc.getRegulatingTerminal();
+ Terminal regulatingTerminal;
+ if (mode.equals(RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER)) {
+ regulatingTerminal = rrpc.getRegulatingTerminal();
+ } else if (context.isExportGeneratorsInLocalRegulationMode()) {
+ regulatingTerminal = generator.getTerminal();
+ } else {
+ regulatingTerminal = generator.getRegulatingTerminal();
+ }
+ String regulatingControlId;
switch (cgmesOriginalClass) {
case CgmesNames.EQUIVALENT_INJECTION:
String reactiveCapabilityCurveId = writeReactiveCapabilityCurve(generator, cimNamespace, writer, context);
@@ -393,7 +404,7 @@ private static void writeGenerators(Network network, Map mapTe
cimNamespace, writer, context);
break;
case CgmesNames.EXTERNAL_NETWORK_INJECTION:
- String regulatingControlId = RegulatingControlEq.writeRegulatingControlEq(generator, exportedTerminalId(mapTerminal2Id, regulatingTerminal), regulatingControlsWritten, mode, cimNamespace, writer, context);
+ regulatingControlId = RegulatingControlEq.writeRegulatingControlEq(generator, exportedTerminalId(mapTerminal2Id, regulatingTerminal), regulatingControlsWritten, mode, cimNamespace, writer, context);
ExternalNetworkInjectionEq.write(context.getNamingStrategy().getCgmesId(generator), generator.getNameOrId(),
context.getNamingStrategy().getCgmesId(generator.getTerminal().getVoltageLevel()),
obtainGeneratorGovernorScd(generator), generator.getMaxP(), obtainMaxQ(generator), generator.getMinP(), obtainMinQ(generator),
@@ -448,13 +459,14 @@ private static > void writeSynchro
if (generatingUnit != null && !generatingUnitsWritten.contains(generatingUnit)) {
String hydroPowerPlantId = generatingUnitWriteHydroPowerPlantAndFossilFuel(i, cimNamespace, energySource, generatingUnit, writer, context);
+ String windGenUnitType = i.getProperty(Conversion.PROPERTY_WIND_GEN_UNIT_TYPE, "onshore"); // considered onshore if property missing
// We have not preserved the names of generating units
// We name generating units based on the first machine found
String generatingUnitName = "GU_" + i.getNameOrId();
GeneratingUnitEq.write(generatingUnit, generatingUnitName, energySource, minP, maxP, targetP, cimNamespace, writeInitialP,
i.getTerminal().getVoltageLevel().getSubstation().map(s -> context.getNamingStrategy().getCgmesId(s)).orElse(null),
- hydroPowerPlantId, writer, context);
+ hydroPowerPlantId, windGenUnitType, writer, context);
generatingUnitsWritten.add(generatingUnit);
}
}
@@ -1202,10 +1214,16 @@ private static void writeLimitsGroup(Identifiable> identifiable, OperationalLi
// Write the OperationalLimitSet
String operationalLimitSetId;
String operationalLimitSetName;
- String propertyKey = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.OPERATIONAL_LIMIT_SET + "_" + limitsGroup.getId();
- if (identifiable.hasProperty(propertyKey)) {
+ if (identifiable.hasProperty(Conversion.PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS)) {
operationalLimitSetId = limitsGroup.getId();
- operationalLimitSetName = identifiable.getProperty(propertyKey);
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode propertyNode = mapper.readTree(identifiable.getProperty(Conversion.PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS));
+ JsonNode limitsGroupNode = propertyNode.get(operationalLimitSetId);
+ operationalLimitSetName = limitsGroupNode != null ? limitsGroupNode.textValue() : operationalLimitSetId;
+ } catch (JsonProcessingException e) {
+ operationalLimitSetName = operationalLimitSetId;
+ }
} else {
operationalLimitSetId = context.getNamingStrategy().getCgmesId(ref(terminalId), ref(limitsGroup.getId()), OPERATIONAL_LIMIT_SET);
operationalLimitSetName = limitsGroup.getId();
diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/SteadyStateHypothesisExport.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/SteadyStateHypothesisExport.java
index f0aa90ddd5e..3e994d5370d 100644
--- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/SteadyStateHypothesisExport.java
+++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/SteadyStateHypothesisExport.java
@@ -389,6 +389,14 @@ private static void addRegulatingControlView(Generator g, Map1<"));
}
}
diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/GeneratorConversionTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/GeneratorConversionTest.java
new file mode 100644
index 00000000000..7da3645869b
--- /dev/null
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/GeneratorConversionTest.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2024, Artelys (http://www.artelys.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.cgmes.conversion.test;
+
+import com.powsybl.cgmes.conversion.Conversion;
+import com.powsybl.iidm.network.*;
+import org.junit.jupiter.api.Test;
+
+import static com.powsybl.cgmes.conversion.test.ConversionUtil.readCgmesResources;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author Damien Jeandemange {@literal }
+ */
+class GeneratorConversionTest {
+
+ @Test
+ void generatingUnitTypes() {
+ Network network = readCgmesResources("/", "GeneratingUnitTypes.xml");
+ assertEquals(EnergySource.OTHER, network.getGenerator("gu_sm").getEnergySource());
+ assertEquals(EnergySource.THERMAL, network.getGenerator("tgu_sm").getEnergySource());
+ assertEquals(EnergySource.HYDRO, network.getGenerator("hgu_sm").getEnergySource());
+ assertEquals(EnergySource.NUCLEAR, network.getGenerator("ngu_sm").getEnergySource());
+ assertEquals(EnergySource.WIND, network.getGenerator("offshore_wgu_sm").getEnergySource());
+ assertEquals("offshore", network.getGenerator("offshore_wgu_sm").getProperty(Conversion.PROPERTY_WIND_GEN_UNIT_TYPE));
+ assertEquals(EnergySource.WIND, network.getGenerator("onshore_wgu_sm").getEnergySource());
+ assertEquals("onshore", network.getGenerator("onshore_wgu_sm").getProperty(Conversion.PROPERTY_WIND_GEN_UNIT_TYPE));
+ assertEquals(EnergySource.SOLAR, network.getGenerator("sgu_sm").getEnergySource());
+ }
+}
diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/OperationalLimitsGroupTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/OperationalLimitsGroupTest.java
index e778289b383..eee40e23bb4 100644
--- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/OperationalLimitsGroupTest.java
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/OperationalLimitsGroupTest.java
@@ -9,6 +9,7 @@
package com.powsybl.cgmes.conversion.test;
import com.powsybl.cgmes.conversion.CgmesExport;
+import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.iidm.network.*;
import org.junit.jupiter.api.Test;
@@ -18,6 +19,7 @@
import java.util.*;
import java.util.regex.Pattern;
+import static com.powsybl.cgmes.conversion.test.ConversionUtil.getFirstMatch;
import static com.powsybl.cgmes.conversion.test.ConversionUtil.getUniqueMatches;
import static org.junit.jupiter.api.Assertions.*;
@@ -51,6 +53,11 @@ void importMultipleLimitsGroupsOnSameLineEndTest() {
// When an end has only 1 set, this set gets selected, otherwise none is
assertTrue(line.getSelectedOperationalLimitsGroup1().isPresent());
assertFalse(line.getSelectedOperationalLimitsGroup2().isPresent());
+
+ // The CGMES id/name have been correctly imported
+ String propertyValue = """
+ {"OLS_1":"SPRING","OLS_2":"SPRING","OLS_3":"WINTER"}""";
+ assertEquals(propertyValue, line.getProperty(Conversion.PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS));
}
@Test
@@ -62,25 +69,25 @@ void exportSelectedLimitsGroupTest() throws IOException {
exportParams.put(CgmesExport.EXPORT_ALL_LIMITS_GROUP, false);
exportParams.put(CgmesExport.PROFILES, List.of("EQ"));
network.write("CGMES", exportParams, tmpDir.resolve("ExportSelectedLimitsGroup.xml"));
- String exportSelectedLimitsGroupXml = Files.readString(tmpDir.resolve("ExportSelectedLimitsGroup_EQ.xml"));
+ String xmlFile = Files.readString(tmpDir.resolve("ExportSelectedLimitsGroup_EQ.xml"));
// There is 1 set on side 1 which is selected, and there are 2 sets on side 2 but none of them is selected
- assertEquals(1, getUniqueMatches(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_SET).size());
- assertEquals(3, getUniqueMatches(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_TYPE).size());
- assertEquals(0, getUniqueMatches(exportSelectedLimitsGroupXml, ACTIVE_POWER_LIMIT).size());
- assertEquals(3, getUniqueMatches(exportSelectedLimitsGroupXml, CURRENT_LIMIT).size());
+ assertEquals(1, getUniqueMatches(xmlFile, OPERATIONAL_LIMIT_SET).size());
+ assertEquals(3, getUniqueMatches(xmlFile, OPERATIONAL_LIMIT_TYPE).size());
+ assertEquals(0, getUniqueMatches(xmlFile, ACTIVE_POWER_LIMIT).size());
+ assertEquals(3, getUniqueMatches(xmlFile, CURRENT_LIMIT).size());
// Manually select one of the limits group on side 2 and export again
Line line = network.getLine("Line");
line.setSelectedOperationalLimitsGroup2("OLS_2");
network.write("CGMES", exportParams, tmpDir.resolve("ExportSelectedLimitsGroup.xml"));
- exportSelectedLimitsGroupXml = Files.readString(tmpDir.resolve("ExportSelectedLimitsGroup_EQ.xml"));
+ xmlFile = Files.readString(tmpDir.resolve("ExportSelectedLimitsGroup_EQ.xml"));
// That makes 1 set selected on each side = 2 in total
- assertEquals(2, getUniqueMatches(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_SET).size());
- assertEquals(3, getUniqueMatches(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_TYPE).size());
- assertEquals(0, getUniqueMatches(exportSelectedLimitsGroupXml, ACTIVE_POWER_LIMIT).size());
- assertEquals(6, getUniqueMatches(exportSelectedLimitsGroupXml, CURRENT_LIMIT).size());
+ assertEquals(2, getUniqueMatches(xmlFile, OPERATIONAL_LIMIT_SET).size());
+ assertEquals(3, getUniqueMatches(xmlFile, OPERATIONAL_LIMIT_TYPE).size());
+ assertEquals(0, getUniqueMatches(xmlFile, ACTIVE_POWER_LIMIT).size());
+ assertEquals(6, getUniqueMatches(xmlFile, CURRENT_LIMIT).size());
}
@Test
@@ -92,13 +99,19 @@ void exportAllLimitsGroupTest() throws IOException {
exportParams.put(CgmesExport.EXPORT_ALL_LIMITS_GROUP, true);
exportParams.put(CgmesExport.PROFILES, List.of("EQ"));
network.write("CGMES", exportParams, tmpDir.resolve("ExportAllLimitsGroup.xml"));
- String exportAllLimitsGroupXml = Files.readString(tmpDir.resolve("ExportAllLimitsGroup_EQ.xml"));
+ String xmlFile = Files.readString(tmpDir.resolve("ExportAllLimitsGroup_EQ.xml"));
// All 3 OperationalLimitsGroup are exported, even though only 2 are selected
- assertEquals(3, getUniqueMatches(exportAllLimitsGroupXml, OPERATIONAL_LIMIT_SET).size());
- assertEquals(3, getUniqueMatches(exportAllLimitsGroupXml, OPERATIONAL_LIMIT_TYPE).size());
- assertEquals(3, getUniqueMatches(exportAllLimitsGroupXml, ACTIVE_POWER_LIMIT).size());
- assertEquals(9, getUniqueMatches(exportAllLimitsGroupXml, CURRENT_LIMIT).size());
+ assertEquals(3, getUniqueMatches(xmlFile, OPERATIONAL_LIMIT_SET).size());
+ assertEquals(3, getUniqueMatches(xmlFile, OPERATIONAL_LIMIT_TYPE).size());
+ assertEquals(3, getUniqueMatches(xmlFile, ACTIVE_POWER_LIMIT).size());
+ assertEquals(9, getUniqueMatches(xmlFile, CURRENT_LIMIT).size());
+
+ // The CGMES id/name have been correctly exported
+ String regex = ".*?(.*?)";
+ assertEquals("SPRING", getFirstMatch(xmlFile, Pattern.compile(regex.replace("NUM", "1"), Pattern.DOTALL)));
+ assertEquals("SPRING", getFirstMatch(xmlFile, Pattern.compile(regex.replace("NUM", "2"), Pattern.DOTALL)));
+ assertEquals("WINTER", getFirstMatch(xmlFile, Pattern.compile(regex.replace("NUM", "3"), Pattern.DOTALL)));
}
}
diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/SwitchConversionTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/SwitchConversionTest.java
new file mode 100644
index 00000000000..60812d3034d
--- /dev/null
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/SwitchConversionTest.java
@@ -0,0 +1,37 @@
+/**
+ * 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.cgmes.conversion.test;
+
+import com.powsybl.cgmes.conversion.Conversion;
+import com.powsybl.commons.test.AbstractSerDeTest;
+import com.powsybl.iidm.network.Network;
+import com.powsybl.iidm.network.Switch;
+import com.powsybl.iidm.network.SwitchKind;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Florian Dupuy {@literal }
+ */
+
+class SwitchConversionTest extends AbstractSerDeTest {
+
+ @Test
+ void jumperImportTest() {
+ Network network = Network.read("jumperTest.xml", getClass().getResourceAsStream("/jumperTest.xml"));
+
+ Switch aswitch = network.getSwitch("Jumper");
+ assertEquals(SwitchKind.DISCONNECTOR, aswitch.getKind());
+ assertEquals("Jumper", aswitch.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "switchType"));
+ assertEquals("opened jumper", aswitch.getNameOrId());
+ assertTrue(aswitch.isOpen());
+ assertFalse(aswitch.isRetained());
+ }
+}
diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/cim14/Cim14SmallCasesConversionTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/cim14/Cim14SmallCasesConversionTest.java
index 271fb300103..c14a6327bbc 100644
--- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/cim14/Cim14SmallCasesConversionTest.java
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/cim14/Cim14SmallCasesConversionTest.java
@@ -14,8 +14,13 @@
import com.powsybl.cgmes.conversion.test.network.compare.ComparisonConfig;
import com.powsybl.cgmes.model.CgmesModel;
import com.powsybl.cgmes.model.test.Cim14SmallCasesCatalog;
+import com.powsybl.commons.datasource.ReadOnlyDataSource;
+import com.powsybl.commons.datasource.ResourceDataSource;
+import com.powsybl.commons.datasource.ResourceSet;
+import com.powsybl.iidm.network.Generator;
import com.powsybl.iidm.network.Importers;
import com.powsybl.iidm.network.Network;
+import com.powsybl.iidm.network.impl.NetworkFactoryImpl;
import com.powsybl.triplestore.api.TripleStoreFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -26,6 +31,7 @@
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Luma Zamarreño {@literal }
@@ -95,5 +101,17 @@ void m7busesNoSequenceNumbers() {
});
}
+ @Test
+ void condenser() {
+ ReadOnlyDataSource ds = new ResourceDataSource("condenser",
+ new ResourceSet("/cim14/condenser", "condenser_EQ.xml", "condenser_TP.xml"));
+ Network network = new CgmesImport().importData(ds, new NetworkFactoryImpl(), new Properties());
+ assertEquals(1, network.getGeneratorCount());
+ Generator c = network.getGenerator("CONDENSER_1");
+ assertEquals(0, c.getMinP());
+ assertEquals(0, c.getMaxP());
+ assertTrue(c.isCondenser());
+ }
+
private static ConversionTester tester;
}
diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/conformity/modified/CgmesConformity1ModifiedConversionTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/conformity/modified/CgmesConformity1ModifiedConversionTest.java
index b9001de9f75..c85bf88011b 100644
--- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/conformity/modified/CgmesConformity1ModifiedConversionTest.java
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/conformity/modified/CgmesConformity1ModifiedConversionTest.java
@@ -503,6 +503,7 @@ void microBEFixedMinPMaxP() {
Generator generator = network.getGenerator("3a3b27be-b18b-4385-b557-6735d733baf0");
assertEquals(50.0, generator.getMinP(), 0.0);
assertEquals(200.0, generator.getMaxP(), 0.0);
+ assertFalse(generator.isCondenser());
}
@Test
diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/EquipmentExportTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/EquipmentExportTest.java
index 42f4c34dda4..5a9977b2829 100644
--- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/EquipmentExportTest.java
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/EquipmentExportTest.java
@@ -58,6 +58,8 @@
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
+import static com.powsybl.cgmes.conversion.test.ConversionUtil.writeCgmesProfile;
+import static com.powsybl.cgmes.conversion.test.ConversionUtil.getFirstMatch;
import static org.junit.jupiter.api.Assertions.*;
/**
@@ -1316,6 +1318,14 @@ void generatorRegulatingControlEQTest() throws IOException {
eq = getEQ(network, baseName, tmpDir, exportParams);
testRcEqRcWithAttribute(eq, "_GEN_RC", "_NHV2_NLOAD_PT_T_1", "voltage");
+ // Generator with remote voltage regulation exported in local regulation mode
+ Properties exportInLocalRegulationModeParams = new Properties();
+ exportInLocalRegulationModeParams.put(CgmesExport.PROFILES, "EQ");
+ exportInLocalRegulationModeParams.put(CgmesExport.EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE, true);
+ network = EurostagTutorialExample1Factory.createWithRemoteVoltageGenerator();
+ eq = getEQ(network, baseName, tmpDir, exportInLocalRegulationModeParams);
+ testRcEqRcWithAttribute(eq, "_GEN_RC", "_GEN_SM_T_1", "voltage");
+
// Generator with local reactive
network = EurostagTutorialExample1Factory.createWithLocalReactiveGenerator();
eq = getEQ(network, baseName, tmpDir, exportParams);
@@ -1831,4 +1841,127 @@ private Network prepareNetworkForEQComparison(Network network) {
return network;
}
+
+ private static Network allGeneratingUnitTypesNetwork() {
+ Network network = NetworkFactory.findDefault().createNetwork("network", "test");
+ Substation substation1 = network.newSubstation()
+ .setId("substation1")
+ .setCountry(Country.FR)
+ .setTso("TSO1")
+ .setGeographicalTags("region1")
+ .add();
+ VoltageLevel voltageLevel1 = substation1.newVoltageLevel()
+ .setId("voltageLevel1")
+ .setNominalV(400)
+ .setTopologyKind(TopologyKind.NODE_BREAKER)
+ .add();
+ VoltageLevel.NodeBreakerView topology1 = voltageLevel1.getNodeBreakerView();
+ topology1.newBusbarSection()
+ .setId("voltageLevel1BusbarSection1")
+ .setNode(0)
+ .add();
+ voltageLevel1.newGenerator()
+ .setId("other")
+ .setNode(1)
+ .setMinP(0.0)
+ .setMaxP(100.0)
+ .setTargetP(25.0)
+ .setTargetQ(10.0)
+ .setVoltageRegulatorOn(false)
+ .add();
+ voltageLevel1.newGenerator()
+ .setId("nuclear")
+ .setNode(2)
+ .setMinP(0.0)
+ .setMaxP(100.0)
+ .setTargetP(25.0)
+ .setTargetQ(10.0)
+ .setVoltageRegulatorOn(false)
+ .setEnergySource(EnergySource.NUCLEAR)
+ .add();
+ voltageLevel1.newGenerator()
+ .setId("thermal")
+ .setNode(3)
+ .setMinP(0.0)
+ .setMaxP(100.0)
+ .setTargetP(25.0)
+ .setTargetQ(10.0)
+ .setVoltageRegulatorOn(false)
+ .setEnergySource(EnergySource.THERMAL)
+ .add();
+ voltageLevel1.newGenerator()
+ .setId("hydro")
+ .setNode(4)
+ .setMinP(0.0)
+ .setMaxP(100.0)
+ .setTargetP(25.0)
+ .setTargetQ(10.0)
+ .setVoltageRegulatorOn(false)
+ .setEnergySource(EnergySource.HYDRO)
+ .add();
+ voltageLevel1.newGenerator()
+ .setId("solar")
+ .setNode(5)
+ .setMinP(0.0)
+ .setMaxP(100.0)
+ .setTargetP(25.0)
+ .setTargetQ(10.0)
+ .setVoltageRegulatorOn(false)
+ .setEnergySource(EnergySource.SOLAR)
+ .add();
+ Generator windOnshore = voltageLevel1.newGenerator()
+ .setId("wind_onshore")
+ .setNode(6)
+ .setMinP(0.0)
+ .setMaxP(100.0)
+ .setTargetP(25.0)
+ .setTargetQ(10.0)
+ .setVoltageRegulatorOn(false)
+ .setEnergySource(EnergySource.WIND)
+ .add();
+ Generator windOffshore = voltageLevel1.newGenerator()
+ .setId("wind_offshore")
+ .setNode(7)
+ .setMinP(0.0)
+ .setMaxP(100.0)
+ .setTargetP(25.0)
+ .setTargetQ(10.0)
+ .setVoltageRegulatorOn(false)
+ .setEnergySource(EnergySource.WIND)
+ .add();
+ topology1.newInternalConnection().setNode1(0).setNode2(1).add();
+ topology1.newInternalConnection().setNode1(0).setNode2(2).add();
+ topology1.newInternalConnection().setNode1(0).setNode2(3).add();
+ topology1.newInternalConnection().setNode1(0).setNode2(4).add();
+ topology1.newInternalConnection().setNode1(0).setNode2(5).add();
+ topology1.newInternalConnection().setNode1(0).setNode2(6).add();
+ topology1.newInternalConnection().setNode1(0).setNode2(7).add();
+ windOnshore.setProperty(Conversion.PROPERTY_WIND_GEN_UNIT_TYPE, "onshore");
+ windOffshore.setProperty(Conversion.PROPERTY_WIND_GEN_UNIT_TYPE, "offshore");
+ return network;
+ }
+
+ @Test
+ void generatingUnitTypesTest() throws IOException {
+ Network network = allGeneratingUnitTypesNetwork();
+
+ // Export as cgmes
+ String eqXml = writeCgmesProfile(network, "EQ", tmpDir);
+
+ assertTrue(eqXml.contains(""));
+ assertTrue(eqXml.contains(""));
+ assertTrue(eqXml.contains(""));
+ assertTrue(eqXml.contains(""));
+ assertTrue(eqXml.contains(""));
+ assertTrue(eqXml.contains(""));
+ assertTrue(eqXml.contains(""));
+
+ String sPattern = ".*?" +
+ "";
+
+ Pattern onshorePattern = Pattern.compile(sPattern.replace("${rdfId}", "_wind_onshore_WGU"), Pattern.DOTALL);
+ assertEquals("onshore", getFirstMatch(eqXml, onshorePattern));
+ Pattern offshorePattern = Pattern.compile(sPattern.replace("${rdfId}", "_wind_offshore_WGU"), Pattern.DOTALL);
+ assertEquals("offshore", getFirstMatch(eqXml, offshorePattern));
+ }
}
diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/SteadyStateHypothesisExportTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/SteadyStateHypothesisExportTest.java
index bf5b4c2c7dd..43102c5c030 100644
--- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/SteadyStateHypothesisExportTest.java
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/SteadyStateHypothesisExportTest.java
@@ -681,10 +681,18 @@ void generatorRegulatingControlSSHTest() throws IOException {
// Generator remote voltage
network = EurostagTutorialExample1Factory.createWithRemoteVoltageGenerator();
ssh = getSSH(network, baseName, tmpDir, exportParams);
- testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "24.5", "k");
+ testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "399", "k");
network.getGenerator("GEN").setVoltageRegulatorOn(false);
ssh = getSSH(network, baseName, tmpDir, exportParams);
- testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "false", "0", "24.5", "k");
+ testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "false", "0", "399", "k");
+
+ // Generator with remote voltage regulation exported in local regulation mode
+ Properties exportInLocalRegulationModeParams = new Properties();
+ exportInLocalRegulationModeParams.put(CgmesExport.PROFILES, "SSH");
+ exportInLocalRegulationModeParams.put(CgmesExport.EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE, true);
+ network = EurostagTutorialExample1Factory.createWithRemoteVoltageGenerator();
+ ssh = getSSH(network, baseName, tmpDir, exportInLocalRegulationModeParams);
+ testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "25.2", "k");
// Generator with local reactive
network = EurostagTutorialExample1Factory.createWithLocalReactiveGenerator();
@@ -719,16 +727,16 @@ void generatorRegulatingControlSSHTest() throws IOException {
// Generator with remote reactive and voltage
network = EurostagTutorialExample1Factory.createWithRemoteReactiveAndVoltageGenerators();
ssh = getSSH(network, baseName, tmpDir, exportParams);
- testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "24.5", "k");
+ testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "399", "k");
network.getGenerator("GEN").setVoltageRegulatorOn(false);
ssh = getSSH(network, baseName, tmpDir, exportParams);
testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "200", "M");
network.getGenerator("GEN").getExtension(RemoteReactivePowerControl.class).setEnabled(false);
ssh = getSSH(network, baseName, tmpDir, exportParams);
- testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "false", "0", "24.5", "k");
+ testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "false", "0", "399", "k");
network.getGenerator("GEN").setVoltageRegulatorOn(true);
ssh = getSSH(network, baseName, tmpDir, exportParams);
- testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "24.5", "k");
+ testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "399", "k");
// Generator without control
network = EurostagTutorialExample1Factory.createWithoutControl();
diff --git a/cgmes/cgmes-conversion/src/test/resources/GeneratingUnitTypes.xml b/cgmes/cgmes-conversion/src/test/resources/GeneratingUnitTypes.xml
new file mode 100644
index 00000000000..d0ec5c9ffbf
--- /dev/null
+++ b/cgmes/cgmes-conversion/src/test/resources/GeneratingUnitTypes.xml
@@ -0,0 +1,180 @@
+
+
+ 2024-10-18T00:00:00Z
+ 2024-10-18T00:00:00Z
+ Generating Unit Types test
+ http://iec.ch/TC57/ns/CIM/Operation-EU/3.0
+ http://iec.ch/TC57/ns/CIM/CoreEquipment-EU/3.0
+ http://powsybl.org
+ 1
+
+
+ 400
+ 400 kV
+ Base voltage for 400 kV
+
+
+ GeographicalRegion
+ _gr
+
+
+
+ SubGeographicalRegion
+ _sgr
+
+
+
+ Substation
+
+
+
+
+ VoltageLevel
+
+
+ ConnectivityNode
+
+
+
+
+
+ SynchronousMachine
+
+
+ -100
+ 100
+
+
+ GeneratingUnit
+ 0
+ 100
+ 0
+
+
+
+
+ 1
+
+
+
+
+ SynchronousMachine
+
+
+ -100
+ 100
+
+
+ GeneratingUnit
+ 0
+ 100
+ 0
+
+
+
+
+ 1
+
+
+
+
+ SynchronousMachine
+
+
+ -100
+ 100
+
+
+ GeneratingUnit
+ 0
+ 100
+ 0
+
+
+
+
+ 1
+
+
+
+
+ SynchronousMachine
+
+
+ -100
+ 100
+
+
+ GeneratingUnit
+ 0
+ 100
+ 0
+
+
+
+
+ 1
+
+
+
+
+ SynchronousMachine
+
+
+ -100
+ 100
+
+
+ GeneratingUnit
+ 0
+ 100
+ 0
+
+
+
+
+ 1
+
+
+
+
+ SynchronousMachine
+
+
+ -100
+ 100
+
+
+ GeneratingUnit
+ 0
+ 100
+ 0
+
+
+
+
+
+ 1
+
+
+
+
+ SynchronousMachine
+
+
+ -100
+ 100
+
+
+ GeneratingUnit
+ 0
+ 100
+ 0
+
+
+
+
+
+ 1
+
+
diff --git a/cgmes/cgmes-conversion/src/test/resources/groundTest.xml b/cgmes/cgmes-conversion/src/test/resources/groundTest.xml
index a8a9f11d941..7cb32dda91e 100644
--- a/cgmes/cgmes-conversion/src/test/resources/groundTest.xml
+++ b/cgmes/cgmes-conversion/src/test/resources/groundTest.xml
@@ -92,10 +92,6 @@
1RK
-
-
- AAP
-
diff --git a/cgmes/cgmes-conversion/src/test/resources/jumperTest.xml b/cgmes/cgmes-conversion/src/test/resources/jumperTest.xml
new file mode 100644
index 00000000000..f1d0c36059e
--- /dev/null
+++ b/cgmes/cgmes-conversion/src/test/resources/jumperTest.xml
@@ -0,0 +1,78 @@
+
+
+
+ 2023-01-01T00:00:00Z
+ 2023-01-01T00:00:00Z
+ Test Jumper import
+ 1
+ http://iec.ch/TC57/ns/CIM/CoreEquipment-EU/3.0
+ http://iec.ch/TC57/ns/CIM/Operation-EU/3.0
+ http://powsybl.org
+
+
+ Region
+
+
+
+ SubRegion
+
+
+ 67.5
+ 67.5
+
+
+
+ Substation
+
+
+
+
+ VoltageLevel
+
+
+
+ BBS
+
+
+
+
+ 1
+ BBS Terminal
+
+
+
+ BBS ConnectivityNode
+
+
+ Load
+
+
+
+
+
+ 1
+ Load Terminal
+
+
+
+ Load ConnectivityNode
+
+
+ true
+ false
+
+ opened jumper
+
+
+
+
+ 1
+ Jumper Terminal1
+
+
+
+
+ 2
+ Jumper Terminal2
+
+
diff --git a/cgmes/cgmes-extensions/pom.xml b/cgmes/cgmes-extensions/pom.xml
index 3eb7290e3b8..ba7ef1bec44 100644
--- a/cgmes/cgmes-extensions/pom.xml
+++ b/cgmes/cgmes-extensions/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-cgmes
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes-extensions
diff --git a/cgmes/cgmes-gl/pom.xml b/cgmes/cgmes-gl/pom.xml
index ce8c8bf9877..6d13879412e 100644
--- a/cgmes/cgmes-gl/pom.xml
+++ b/cgmes/cgmes-gl/pom.xml
@@ -14,7 +14,7 @@
powsybl-cgmescom.powsybl
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes-gl
diff --git a/cgmes/cgmes-measurements/pom.xml b/cgmes/cgmes-measurements/pom.xml
index 5b7275292ba..64858d349dd 100644
--- a/cgmes/cgmes-measurements/pom.xml
+++ b/cgmes/cgmes-measurements/pom.xml
@@ -13,7 +13,7 @@
powsybl-cgmescom.powsybl
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOT4.0.0
diff --git a/cgmes/cgmes-model-alternatives/pom.xml b/cgmes/cgmes-model-alternatives/pom.xml
index fa8b674e24d..209eacc0d35 100644
--- a/cgmes/cgmes-model-alternatives/pom.xml
+++ b/cgmes/cgmes-model-alternatives/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-cgmes
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes-model-alternatives
diff --git a/cgmes/cgmes-model-test/pom.xml b/cgmes/cgmes-model-test/pom.xml
index 3bdd0c7c5e7..934e334493d 100644
--- a/cgmes/cgmes-model-test/pom.xml
+++ b/cgmes/cgmes-model-test/pom.xml
@@ -15,7 +15,7 @@
powsybl-cgmescom.powsybl
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes-model-test
diff --git a/cgmes/cgmes-model-test/src/main/java/com/powsybl/cgmes/model/test/CgmesModelTester.java b/cgmes/cgmes-model-test/src/main/java/com/powsybl/cgmes/model/test/CgmesModelTester.java
index 1d3ebdb2e91..81a84e427b2 100644
--- a/cgmes/cgmes-model-test/src/main/java/com/powsybl/cgmes/model/test/CgmesModelTester.java
+++ b/cgmes/cgmes-model-test/src/main/java/com/powsybl/cgmes/model/test/CgmesModelTester.java
@@ -89,6 +89,7 @@ private void testCompare(CgmesModel expected, CgmesModel actual) {
testPropertyBags(expected.phaseTapChangers(), actual.phaseTapChangers());
testPropertyBags(expected.energyConsumers(), actual.energyConsumers());
testPropertyBags(expected.shuntCompensators(), actual.shuntCompensators());
+ testPropertyBags(expected.nonlinearShuntCompensatorPoints(), actual.nonlinearShuntCompensatorPoints());
testPropertyBags(expected.staticVarCompensators(), actual.staticVarCompensators());
testPropertyBags(expected.synchronousMachinesGenerators(), actual.synchronousMachinesGenerators());
testPropertyBags(expected.synchronousMachinesCondensers(), actual.synchronousMachinesCondensers());
diff --git a/cgmes/cgmes-model-test/src/main/resources/cim14/condenser/condenser_EQ.xml b/cgmes/cgmes-model-test/src/main/resources/cim14/condenser/condenser_EQ.xml
new file mode 100644
index 00000000000..27e23c5def2
--- /dev/null
+++ b/cgmes/cgmes-model-test/src/main/resources/cim14/condenser/condenser_EQ.xml
@@ -0,0 +1,53 @@
+
+
+
+ Region 1
+
+
+
+ SubRegion 1
+
+
+ false
+ 110
+ 110 kV
+
+
+
+ Substation 1
+
+
+
+
+ 110
+
+
+ 1
+
+ T1
+
+
+ 0
+ 0
+ 0
+
+ G1
+
+
+ 0
+ -999
+ 0
+ 1
+
+
+ 0
+
+ 999
+
+ CONDENSER 1
+
+
+ IEC61970CIM14v02
+ 2009-05-10
+
+
diff --git a/cgmes/cgmes-model-test/src/main/resources/cim14/condenser/condenser_TP.xml b/cgmes/cgmes-model-test/src/main/resources/cim14/condenser/condenser_TP.xml
new file mode 100644
index 00000000000..5dc066970db
--- /dev/null
+++ b/cgmes/cgmes-model-test/src/main/resources/cim14/condenser/condenser_TP.xml
@@ -0,0 +1,16 @@
+
+
+
+ true
+
+
+
+
+
+
+ TN 1
+
+
+
+
+
diff --git a/cgmes/cgmes-model-test/src/test/java/com/powsybl/cgmes/model/test/cim14/Cim14SmallCasesTest.java b/cgmes/cgmes-model-test/src/test/java/com/powsybl/cgmes/model/test/cim14/Cim14SmallCasesTest.java
index 58feda9bc52..18f7b0abd3b 100644
--- a/cgmes/cgmes-model-test/src/test/java/com/powsybl/cgmes/model/test/cim14/Cim14SmallCasesTest.java
+++ b/cgmes/cgmes-model-test/src/test/java/com/powsybl/cgmes/model/test/cim14/Cim14SmallCasesTest.java
@@ -8,10 +8,16 @@
package com.powsybl.cgmes.model.test.cim14;
+import com.powsybl.cgmes.model.CgmesModel;
+import com.powsybl.cgmes.model.CgmesModelFactory;
import com.powsybl.cgmes.model.CgmesOnDataSource;
import com.powsybl.cgmes.model.test.CgmesModelTester;
import com.powsybl.cgmes.model.GridModelReferenceResources;
import com.powsybl.cgmes.model.test.Cim14SmallCasesCatalog;
+import com.powsybl.commons.datasource.ReadOnlyDataSource;
+import com.powsybl.commons.datasource.ResourceDataSource;
+import com.powsybl.commons.datasource.ResourceSet;
+import com.powsybl.triplestore.api.TripleStoreFactory;
import org.junit.jupiter.api.Test;
import java.io.IOException;
@@ -19,8 +25,7 @@
import java.util.HashSet;
import java.util.Set;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
/**
* @author Luma Zamarreño {@literal }
@@ -44,6 +49,14 @@ void small1PlusInvalidFileContent() throws IOException {
new CgmesModelTester(t).test();
}
+ @Test
+ void condenser() {
+ ReadOnlyDataSource ds = new ResourceDataSource("condenser",
+ new ResourceSet("/cim14/condenser", "condenser_EQ.xml"));
+ CgmesModel actual = CgmesModelFactory.create(ds, TripleStoreFactory.defaultImplementation());
+ assertEquals(1, actual.synchronousMachinesCondensers().size());
+ }
+
@Test
void m7Buses() {
new CgmesModelTester(Cim14SmallCasesCatalog.m7buses()).test();
diff --git a/cgmes/cgmes-model/pom.xml b/cgmes/cgmes-model/pom.xml
index 134cc0d5c66..3ed67af4ea5 100644
--- a/cgmes/cgmes-model/pom.xml
+++ b/cgmes/cgmes-model/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-cgmes
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes-model
diff --git a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/AbstractCgmesModel.java b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/AbstractCgmesModel.java
index 82b1c963ac0..6006049b616 100644
--- a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/AbstractCgmesModel.java
+++ b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/AbstractCgmesModel.java
@@ -36,6 +36,14 @@ public Properties getProperties() {
return this.properties;
}
+ @Override
+ public PropertyBags nonlinearShuntCompensatorPoints(String shuntId) {
+ if (cachedGroupedShuntCompensatorPoints == null) {
+ cachedGroupedShuntCompensatorPoints = computeGroupedShuntCompensatorPoints();
+ }
+ return cachedGroupedShuntCompensatorPoints.getOrDefault(shuntId, new PropertyBags());
+ }
+
@Override
public Map groupedTransformerEnds() {
if (cachedGroupedTransformerEnds == null) {
@@ -161,6 +169,17 @@ private CgmesContainer container(CgmesTerminal t, boolean nodeBreaker) {
return (containerId == null) ? null : container(containerId);
}
+ private Map computeGroupedShuntCompensatorPoints() {
+ Map groupedShuntCompensatorPoints = new HashMap<>();
+ nonlinearShuntCompensatorPoints()
+ .forEach(point -> {
+ String shuntCompensator = point.getId("Shunt");
+ groupedShuntCompensatorPoints.computeIfAbsent(shuntCompensator, bag -> new PropertyBags())
+ .add(point);
+ });
+ return groupedShuntCompensatorPoints;
+ }
+
private Map computeGroupedTransformerEnds() {
// Alternative implementation:
// instead of sorting after building each list,
@@ -291,6 +310,7 @@ public void read(ReadOnlyDataSource ds, ReportNode reportNode) {
}
protected void invalidateCaches() {
+ cachedGroupedShuntCompensatorPoints = null;
cachedGroupedTransformerEnds = null;
powerTransformerRatioTapChanger = null;
powerTransformerPhaseTapChanger = null;
@@ -308,6 +328,7 @@ protected void invalidateCaches() {
private String baseName;
// Caches
+ private Map cachedGroupedShuntCompensatorPoints;
private Map cachedGroupedTransformerEnds;
private Map cachedTerminals;
private Map cachedContainers;
diff --git a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/CgmesModel.java b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/CgmesModel.java
index 1312f12762c..0f1bb6928b4 100644
--- a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/CgmesModel.java
+++ b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/CgmesModel.java
@@ -109,7 +109,18 @@ default PropertyBags fullModels() {
PropertyBags equivalentShunts();
- PropertyBags nonlinearShuntCompensatorPoints(String id);
+ /**
+ * Query all NonlinearShuntCompensatorPoint in the CgmesModel.
+ * @return A {@link PropertyBags} with the shunt compensators points properties.
+ */
+ PropertyBags nonlinearShuntCompensatorPoints();
+
+ /**
+ * Query the NonlinearShuntCompensatorPoint associated to the given NonlinearShuntCompensator.
+ * @param shuntId The id of the NonlinearShuntCompensator.
+ * @return A {@link PropertyBags} with the given shunt compensator's points properties.
+ */
+ PropertyBags nonlinearShuntCompensatorPoints(String shuntId);
PropertyBags staticVarCompensators();
diff --git a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/InMemoryCgmesModel.java b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/InMemoryCgmesModel.java
index 69c279d00b4..17f9d4844ec 100644
--- a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/InMemoryCgmesModel.java
+++ b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/InMemoryCgmesModel.java
@@ -49,6 +49,7 @@ public final class InMemoryCgmesModel implements CgmesModel {
private PropertyBags energyConsumers;
private PropertyBags energySources;
private PropertyBags shuntCompensators;
+ private PropertyBags shuntCompensatorPoints;
private PropertyBags staticVarCompensators;
private PropertyBags equivalentShunts;
private PropertyBags synchronousMachinesGenerators;
@@ -90,6 +91,7 @@ public InMemoryCgmesModel() {
energyConsumers = new PropertyBags();
energySources = new PropertyBags();
shuntCompensators = new PropertyBags();
+ shuntCompensatorPoints = new PropertyBags();
equivalentShunts = new PropertyBags();
staticVarCompensators = new PropertyBags();
synchronousMachinesGenerators = new PropertyBags();
@@ -225,6 +227,11 @@ public InMemoryCgmesModel shuntCompensators(String... ids) {
return this;
}
+ public InMemoryCgmesModel shuntCompensatorsPoints(String... ids) {
+ fakeObjectsFromIdentifiers("NonlinearShuntCompensatorPoint", ids, shuntCompensatorPoints);
+ return this;
+ }
+
public InMemoryCgmesModel staticVarCompensators(String... ids) {
fakeObjectsFromIdentifiers("StaticVarCompensator", ids, staticVarCompensators);
return this;
@@ -431,15 +438,21 @@ public PropertyBags shuntCompensators() {
}
@Override
- public PropertyBags equivalentShunts() {
- return equivalentShunts;
+ public PropertyBags nonlinearShuntCompensatorPoints() {
+ return shuntCompensatorPoints;
}
@Override
- public PropertyBags nonlinearShuntCompensatorPoints(String scId) {
+ public PropertyBags nonlinearShuntCompensatorPoints(String shuntId) {
+ // FakeCgmesModel does not provide grouped shunt compensator points
return new PropertyBags();
}
+ @Override
+ public PropertyBags equivalentShunts() {
+ return equivalentShunts;
+ }
+
@Override
public PropertyBags staticVarCompensators() {
return staticVarCompensators;
diff --git a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/triplestore/CgmesModelTripleStore.java b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/triplestore/CgmesModelTripleStore.java
index 08290fac08e..7915a17dfe8 100644
--- a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/triplestore/CgmesModelTripleStore.java
+++ b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/triplestore/CgmesModelTripleStore.java
@@ -498,9 +498,8 @@ public PropertyBags equivalentShunts() {
}
@Override
- public PropertyBags nonlinearShuntCompensatorPoints(String scId) {
- Objects.requireNonNull(scId);
- return namedQuery("nonlinearShuntCompensatorPoints", scId);
+ public PropertyBags nonlinearShuntCompensatorPoints() {
+ return namedQuery("nonlinearShuntCompensatorPoints");
}
@Override
diff --git a/cgmes/cgmes-model/src/main/resources/CIM100.sparql b/cgmes/cgmes-model/src/main/resources/CIM100.sparql
index 19e355b9414..ba668498e7a 100644
--- a/cgmes/cgmes-model/src/main/resources/CIM100.sparql
+++ b/cgmes/cgmes-model/src/main/resources/CIM100.sparql
@@ -57,7 +57,14 @@ WHERE {
cim:RotatingMachine.GeneratingUnit ?GeneratingUnit .
# Some test cases of condensers have a (wrong) association with a generating unit,
# So we explicitly check for the type of the synchronous machine
- FILTER (!REGEX(STR(?type), "Kind.condenser"))
+ VALUES ?type {
+ cim:SynchronousMachineKind.generator
+ cim:SynchronousMachineKind.generatorOrCondenser
+ cim:SynchronousMachineKind.motor
+ cim:SynchronousMachineKind.generatorOrMotor
+ cim:SynchronousMachineKind.motorOrCondenser
+ cim:SynchronousMachineKind.generatorOrCondenserOrMotor
+ }
OPTIONAL {
?SynchronousMachine cim:RotatingMachine.ratedS ?ratedS
}
@@ -71,6 +78,9 @@ WHERE {
?HydroPowerPlant cim:HydroPowerPlant.hydroPlantStorageType ?hydroPlantStorageType
}
}
+ OPTIONAL {
+ ?GeneratingUnit cim:WindGeneratingUnit.windGenUnitType ?windGenUnitType
+ }
OPTIONAL {
SELECT ?GeneratingUnit (group_concat(?fossilFuelType;separator=";") as ?fossilFuelTypeList)
WHERE {
@@ -131,3 +141,37 @@ WHERE {
OPTIONAL {?OperationalLimit cim:VoltageLimit.value ?value }
}}
}
+
+# query: switches
+SELECT *
+WHERE {
+{ GRAPH ?graph {
+ ?Switch
+ a ?type ;
+ cim:Equipment.EquipmentContainer ?EquipmentContainer .
+ VALUES ?type { cim:Switch cim:Breaker cim:Disconnector cim:LoadBreakSwitch cim:ProtectedSwitch cim:GroundDisconnector cim:Jumper } .
+ OPTIONAL {
+ ?Switch cim:IdentifiedObject.name ?name ;
+ }
+ OPTIONAL {
+ ?Switch cim:Switch.retained ?retained
+ }
+ OPTIONAL {
+ ?Switch cim:Switch.normalOpen ?normalOpen
+ }
+ ?Terminal1
+ a cim:Terminal ;
+ cim:Terminal.ConductingEquipment ?Switch .
+ OPTIONAL { ?Terminal1 cim:ACDCTerminal.sequenceNumber ?seq1 }
+ ?Terminal2
+ a cim:Terminal ;
+ cim:Terminal.ConductingEquipment ?Switch .
+ OPTIONAL { ?Terminal2 cim:ACDCTerminal.sequenceNumber ?seq2 }
+ FILTER ( bound(?seq1) && ?seq1 = "1" && bound(?seq2) && ?seq2 = "2"
+ || !bound(?seq1) && !bound(?seq2) && str(?Terminal1) < str(?Terminal2) )
+}}
+OPTIONAL { GRAPH ?graphSSH {
+ ?Switch cim:Switch.open ?open
+}}
+}
+
diff --git a/cgmes/cgmes-model/src/main/resources/CIM14.sparql b/cgmes/cgmes-model/src/main/resources/CIM14.sparql
index d6b510ea199..2f49406b1c2 100644
--- a/cgmes/cgmes-model/src/main/resources/CIM14.sparql
+++ b/cgmes/cgmes-model/src/main/resources/CIM14.sparql
@@ -341,7 +341,41 @@ WHERE {
a cim:SynchronousMachine ;
cim:SynchronousMachine.MemberOf_GeneratingUnit ?GeneratingUnit ;
cim:SynchronousMachine.ratedS ?ratedS ;
- cim:Equipment.MemberOf_EquipmentContainer ?VoltageLevel .
+ cim:Equipment.MemberOf_EquipmentContainer ?VoltageLevel ;
+ cim:SynchronousMachine.type ?type .
+ VALUES ?type {
+ cim:SynchronousMachineType.generator
+ cim:SynchronousMachineType.generator_or_condenser
+ }
+ OPTIONAL {
+ ?SynchronousMachine
+ cim:SynchronousMachine.minQ ?minQ ;
+ cim:SynchronousMachine.maxQ ?maxQ
+ }
+ ?GeneratingUnit
+ a ?generatingUnitType ;
+ cim:GeneratingUnit.minOperatingP ?minP ;
+ cim:GeneratingUnit.maxOperatingP ?maxP ;
+ cim:IdentifiedObject.name ?name .
+ ?Terminal cim:Terminal.ConductingEquipment ?SynchronousMachine .
+ OPTIONAL { ?SynchronousMachine cim:RegulatingCondEq.RegulatingControl ?RegulatingControl }
+ BIND ( "true" AS ?controlEnabled )
+}}
+}
+
+# query: synchronousMachinesCondensers
+SELECT *
+WHERE {
+{ GRAPH ?graph {
+ ?SynchronousMachine
+ a cim:SynchronousMachine ;
+ cim:SynchronousMachine.MemberOf_GeneratingUnit ?GeneratingUnit ;
+ cim:SynchronousMachine.ratedS ?ratedS ;
+ cim:Equipment.MemberOf_EquipmentContainer ?VoltageLevel ;
+ cim:SynchronousMachine.type ?type .
+ VALUES ?type {
+ cim:SynchronousMachineType.condenser
+ }
OPTIONAL {
?SynchronousMachine
cim:SynchronousMachine.minQ ?minQ ;
diff --git a/cgmes/cgmes-model/src/main/resources/CIM16.sparql b/cgmes/cgmes-model/src/main/resources/CIM16.sparql
index 5ec42873e9e..bae7ca1b6db 100644
--- a/cgmes/cgmes-model/src/main/resources/CIM16.sparql
+++ b/cgmes/cgmes-model/src/main/resources/CIM16.sparql
@@ -826,8 +826,7 @@ WHERE {
cim:NonlinearShuntCompensatorPoint.NonlinearShuntCompensator ?Shunt ;
cim:NonlinearShuntCompensatorPoint.sectionNumber ?sectionNumber ;
cim:NonlinearShuntCompensatorPoint.b ?b ;
- cim:NonlinearShuntCompensatorPoint.g ?g .
- FILTER REGEX ( STR (?Shunt), "{0}")
+ cim:NonlinearShuntCompensatorPoint.g ?g .
}
# query: synchronousMachinesGenerators
@@ -862,6 +861,9 @@ WHERE {
?HydroPowerPlant cim:HydroPowerPlant.hydroPlantStorageType ?hydroPlantStorageType
}
}
+ OPTIONAL {
+ ?GeneratingUnit cim:WindGeneratingUnit.windGenUnitType ?windGenUnitType
+ }
OPTIONAL {
SELECT ?GeneratingUnit (group_concat(?fossilFuelType;separator=";") as ?fossilFuelTypeList)
WHERE {
diff --git a/cgmes/cgmes-shortcircuit/pom.xml b/cgmes/cgmes-shortcircuit/pom.xml
index 777fc4b691d..68c41972f29 100644
--- a/cgmes/cgmes-shortcircuit/pom.xml
+++ b/cgmes/cgmes-shortcircuit/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-cgmes
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes-shortcircuit
diff --git a/cgmes/pom.xml b/cgmes/pom.xml
index 1caaf3d9c27..1a3773068dc 100644
--- a/cgmes/pom.xml
+++ b/cgmes/pom.xml
@@ -13,7 +13,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cgmes
diff --git a/cim-anonymiser/pom.xml b/cim-anonymiser/pom.xml
index f8863833832..a0b1af2f068 100644
--- a/cim-anonymiser/pom.xml
+++ b/cim-anonymiser/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-cim-anonymiser
diff --git a/commons-test/pom.xml b/commons-test/pom.xml
index 72c77057407..667ba7d746b 100644
--- a/commons-test/pom.xml
+++ b/commons-test/pom.xml
@@ -15,7 +15,7 @@
powsybl-corecom.powsybl
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-commons-test
diff --git a/commons/pom.xml b/commons/pom.xml
index f1e3433c500..eca1ae214e0 100644
--- a/commons/pom.xml
+++ b/commons/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-commons
diff --git a/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java b/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java
index f091e829e3e..bf4ab14edb3 100644
--- a/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java
+++ b/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java
@@ -32,15 +32,11 @@ public void setExtendable(T extendable) {
if (extendable != null && this.extendable != null && this.extendable != extendable) {
throw new PowsyblException("Extension is already associated to the extendable " + this.extendable);
}
- if (extendable == null) {
- cleanup();
- }
this.extendable = extendable;
}
- /**
- * Method called when the extension is removed from its holder.
- * Can be used for e.g. resource cleanup.
- */
- protected void cleanup() { }
+ @Override
+ public void cleanup() {
+ // nothing by default
+ }
}
diff --git a/commons/src/main/java/com/powsybl/commons/extensions/Extension.java b/commons/src/main/java/com/powsybl/commons/extensions/Extension.java
index ce30898628b..c56f23591bd 100644
--- a/commons/src/main/java/com/powsybl/commons/extensions/Extension.java
+++ b/commons/src/main/java/com/powsybl/commons/extensions/Extension.java
@@ -33,4 +33,11 @@ public interface Extension {
* @throws com.powsybl.commons.PowsyblException if this extension is already held.
*/
void setExtendable(T extendable);
+
+ /**
+ * Method called just before the extension is removed from its holder.
+ * Can be used for e.g. resource cleanup.
+ */
+ default void cleanup() {
+ }
}
diff --git a/computation-local-test/pom.xml b/computation-local-test/pom.xml
index 93dacc3c765..47d2a8f6579 100644
--- a/computation-local-test/pom.xml
+++ b/computation-local-test/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-computation-local-test
diff --git a/computation-local/pom.xml b/computation-local/pom.xml
index 45baba96c3a..561cdd3d405 100644
--- a/computation-local/pom.xml
+++ b/computation-local/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-computation-local
diff --git a/computation/pom.xml b/computation/pom.xml
index f5ff0059ca5..472f97c8fb9 100644
--- a/computation/pom.xml
+++ b/computation/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-computation
diff --git a/config-classic/pom.xml b/config-classic/pom.xml
index 8866c09df09..f2cb30e6735 100644
--- a/config-classic/pom.xml
+++ b/config-classic/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-config-classic
diff --git a/config-test/pom.xml b/config-test/pom.xml
index 454330edbd8..40040b9023c 100644
--- a/config-test/pom.xml
+++ b/config-test/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-config-test
diff --git a/contingency/contingency-api/pom.xml b/contingency/contingency-api/pom.xml
index 80384b629ab..218a3ff815c 100644
--- a/contingency/contingency-api/pom.xml
+++ b/contingency/contingency-api/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-contingency
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-contingency-api
diff --git a/contingency/contingency-api/src/main/java/com/powsybl/contingency/AbstractSidedContingency.java b/contingency/contingency-api/src/main/java/com/powsybl/contingency/AbstractSidedContingency.java
index ce31096e06e..875df25714c 100644
--- a/contingency/contingency-api/src/main/java/com/powsybl/contingency/AbstractSidedContingency.java
+++ b/contingency/contingency-api/src/main/java/com/powsybl/contingency/AbstractSidedContingency.java
@@ -12,7 +12,7 @@
/**
* @author Geoffroy Jamgotchian {@literal }
*/
-public abstract class AbstractSidedContingency implements ContingencyElement {
+public abstract class AbstractSidedContingency implements SidedContingencyElement {
protected final String id;
@@ -32,6 +32,7 @@ public String getId() {
return id;
}
+ @Override
public String getVoltageLevelId() {
return voltageLevelId;
}
diff --git a/contingency/contingency-api/src/main/java/com/powsybl/contingency/Contingency.java b/contingency/contingency-api/src/main/java/com/powsybl/contingency/Contingency.java
index 4399587cfe4..90204681f07 100644
--- a/contingency/contingency-api/src/main/java/com/powsybl/contingency/Contingency.java
+++ b/contingency/contingency-api/src/main/java/com/powsybl/contingency/Contingency.java
@@ -112,8 +112,8 @@ public boolean isValid(Network network) {
case LOAD -> checkLoadContingency(this, (LoadContingency) element, network);
case BUS -> checkBusContingency(this, (BusContingency) element, network);
case TIE_LINE -> checkTieLineContingency(this, (TieLineContingency) element, network);
+ case SWITCH -> checkSwitchContingency(this, (SwitchContingency) element, network);
case BATTERY -> checkBatteryContingency(this, (BatteryContingency) element, network);
- default -> throw new IllegalStateException("Unknown contingency element type " + element.getType());
};
}
if (!valid) {
@@ -263,6 +263,15 @@ private static boolean checkTieLineContingency(Contingency contingency, TieLineC
return true;
}
+ private static boolean checkSwitchContingency(Contingency contingency, SwitchContingency element, Network network) {
+ Switch switchElement = network.getSwitch(element.getId());
+ if (switchElement == null) {
+ LOGGER.warn("Switch '{}' of contingency '{}' not found", element.getId(), contingency.getId());
+ return false;
+ }
+ return true;
+ }
+
public static ContingencyBuilder builder(String id) {
return new ContingencyBuilder(id);
}
diff --git a/contingency/contingency-api/src/main/java/com/powsybl/contingency/SidedContingencyElement.java b/contingency/contingency-api/src/main/java/com/powsybl/contingency/SidedContingencyElement.java
new file mode 100644
index 00000000000..a185cda11bb
--- /dev/null
+++ b/contingency/contingency-api/src/main/java/com/powsybl/contingency/SidedContingencyElement.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.contingency;
+
+import com.powsybl.iidm.network.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.function.Function;
+
+/**
+ * @author Laurent Issertial {@literal }
+ */
+public interface SidedContingencyElement extends ContingencyElement {
+
+ Logger LOGGER = LoggerFactory.getLogger(SidedContingencyElement.class);
+
+ String getVoltageLevelId();
+
+ static TwoSides getContingencySide(Network network, SidedContingencyElement element) {
+ String voltageLevelId = element.getVoltageLevelId();
+ if (voltageLevelId != null) {
+ Function terminalSupplier = getTerminalSupplier(network, element);
+ if (terminalSupplier != null) {
+ if (voltageLevelId.equals(terminalSupplier.apply(TwoSides.ONE).getVoltageLevel().getId())) {
+ return TwoSides.ONE;
+ } else if (voltageLevelId.equals(terminalSupplier.apply(TwoSides.TWO).getVoltageLevel().getId())) {
+ return TwoSides.TWO;
+ } else {
+ LOGGER.warn("Voltage id '{}' of contingency '{}' not found", voltageLevelId, element.getId());
+ }
+ } else {
+ LOGGER.warn("Id of contingency '{}' not found", element.getId());
+ }
+ }
+ return null;
+ }
+
+ private static Function getTerminalSupplier(Network network, SidedContingencyElement element) {
+ return switch (element.getType()) {
+ case BRANCH -> getBranchTerminalSupplier(network, element.getId());
+ case HVDC_LINE -> getHvdcLineTerminalSupplier(network, element.getId());
+ case LINE -> getLineTerminalSupplier(network, element.getId());
+ case TIE_LINE -> getTieLineTerminalSupplier(network, element.getId());
+ case TWO_WINDINGS_TRANSFORMER -> getTransformerTerminalSupplier(network, element.getId());
+ default -> null;
+ };
+ }
+
+ private static Function getBranchTerminalSupplier(Network network, String id) {
+ Branch> eq = network.getBranch(id);
+ return eq != null ? eq::getTerminal : null;
+ }
+
+ private static Function getLineTerminalSupplier(Network network, String id) {
+ Line eq = network.getLine(id);
+ return eq != null ? eq::getTerminal : null;
+ }
+
+ private static Function getTieLineTerminalSupplier(Network network, String id) {
+ TieLine eq = network.getTieLine(id);
+ return eq != null ? eq::getTerminal : null;
+ }
+
+ private static Function getTransformerTerminalSupplier(Network network, String id) {
+ TwoWindingsTransformer eq = network.getTwoWindingsTransformer(id);
+ return eq != null ? eq::getTerminal : null;
+ }
+
+ private static Function getHvdcLineTerminalSupplier(Network network, String id) {
+ HvdcLine eq = network.getHvdcLine(id);
+ return eq != null ? s -> eq.getConverterStation(s).getTerminal() : null;
+ }
+}
diff --git a/contingency/contingency-api/src/main/java/com/powsybl/contingency/json/ContingencyElementDeserializer.java b/contingency/contingency-api/src/main/java/com/powsybl/contingency/json/ContingencyElementDeserializer.java
index bbccb7da4ec..e46c7ea1f5a 100644
--- a/contingency/contingency-api/src/main/java/com/powsybl/contingency/json/ContingencyElementDeserializer.java
+++ b/contingency/contingency-api/src/main/java/com/powsybl/contingency/json/ContingencyElementDeserializer.java
@@ -56,9 +56,10 @@ public ContingencyElement deserialize(JsonParser parser, DeserializationContext
case TWO_WINDINGS_TRANSFORMER -> new TwoWindingsTransformerContingency(id, voltageLevelId);
case THREE_WINDINGS_TRANSFORMER -> new ThreeWindingsTransformerContingency(id);
case LOAD -> new LoadContingency(id);
+ case SWITCH -> new SwitchContingency(id);
+ case BATTERY -> new BatteryContingency(id);
case BUS -> new BusContingency(id);
case TIE_LINE -> new TieLineContingency(id, voltageLevelId);
- default -> throw new IllegalStateException("Unexpected ContingencyElementType value: " + type);
};
}
diff --git a/contingency/contingency-api/src/test/java/com/powsybl/contingency/SidedContingencyTest.java b/contingency/contingency-api/src/test/java/com/powsybl/contingency/SidedContingencyTest.java
new file mode 100644
index 00000000000..0c56fd54924
--- /dev/null
+++ b/contingency/contingency-api/src/test/java/com/powsybl/contingency/SidedContingencyTest.java
@@ -0,0 +1,81 @@
+/**
+ * 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.contingency;
+
+import com.powsybl.iidm.network.Network;
+import com.powsybl.iidm.network.TwoSides;
+import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
+import com.powsybl.iidm.network.test.HvdcTestNetwork;
+import org.junit.jupiter.api.BeforeAll;
+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.util.stream.Stream;
+
+import static com.powsybl.iidm.network.test.EurostagTutorialExample1Factory.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * @author Laurent Issertial {@literal }
+ */
+public class SidedContingencyTest {
+
+ private static Network eurostagNetwork;
+
+ @BeforeAll
+ public static void setup() {
+ eurostagNetwork = EurostagTutorialExample1Factory.create();
+ }
+
+ @ParameterizedTest(name = "{1}")
+ @MethodSource("sidedElementProvider")
+ void testSideGetter(Network network, SidedContingencyElement element, TwoSides expectedSide) {
+ assertEquals(expectedSide, SidedContingencyElement.getContingencySide(network, element));
+ }
+
+ @Test
+ void testIdNotFound() {
+ SidedContingencyElement element = new BranchContingency("WRONG_ID", VLHV2);
+ assertNull(SidedContingencyElement.getContingencySide(eurostagNetwork, element));
+ }
+
+ @Test
+ void testVoltageIdNotFound() {
+ SidedContingencyElement element = new BranchContingency(NHV1_NHV2_2, "WRONG_ID");
+ assertNull(SidedContingencyElement.getContingencySide(eurostagNetwork, element));
+ }
+
+ @Test
+ void testNullVoltageId() {
+ SidedContingencyElement element = new BranchContingency(NHV1_NHV2_2);
+ assertNull(SidedContingencyElement.getContingencySide(eurostagNetwork, element));
+ }
+
+ private static Stream sidedElementProvider() {
+ return Stream.of(
+ Arguments.of(eurostagNetwork,
+ new TwoWindingsTransformerContingency(NGEN_NHV1, VLGEN),
+ TwoSides.ONE),
+ Arguments.of(eurostagNetwork,
+ new LineContingency(NHV1_NHV2_1, VLHV1),
+ TwoSides.ONE),
+ Arguments.of(eurostagNetwork,
+ new BranchContingency(NHV1_NHV2_2, VLHV2),
+ TwoSides.TWO),
+ Arguments.of(EurostagTutorialExample1Factory.createWithTieLine(),
+ new TieLineContingency(NHV1_NHV2_1, VLHV2),
+ TwoSides.TWO),
+ Arguments.of(HvdcTestNetwork.createVsc(),
+ new HvdcLineContingency("L", "VL2"),
+ TwoSides.TWO)
+ );
+ }
+}
diff --git a/contingency/contingency-api/src/test/java/com/powsybl/contingency/SwitchContingencyTest.java b/contingency/contingency-api/src/test/java/com/powsybl/contingency/SwitchContingencyTest.java
new file mode 100644
index 00000000000..51b5ed545fe
--- /dev/null
+++ b/contingency/contingency-api/src/test/java/com/powsybl/contingency/SwitchContingencyTest.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.contingency;
+
+import com.google.common.testing.EqualsTester;
+import com.powsybl.iidm.modification.tripping.SwitchTripping;
+import com.powsybl.iidm.network.Network;
+import com.powsybl.iidm.network.Switch;
+import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @author Etienne Lesot {@literal }
+ */
+class SwitchContingencyTest {
+ @Test
+ void test() {
+ var switchContingency = new SwitchContingency("switch");
+ assertEquals("switch", switchContingency.getId());
+ assertEquals(ContingencyElementType.SWITCH, switchContingency.getType());
+
+ assertNotNull(switchContingency.toModification());
+ assertInstanceOf(SwitchTripping.class, switchContingency.toModification());
+
+ new EqualsTester()
+ .addEqualityGroup(new SwitchContingency("foo"), new SwitchContingency("foo"))
+ .addEqualityGroup(new SwitchContingency("bar"), new SwitchContingency("bar"))
+ .testEquals();
+ }
+
+ @Test
+ void testContingencyElement() {
+ Network network = FourSubstationsNodeBreakerFactory.create();
+ Switch networkSwitch = network.getSwitch("S1VL1_LD1_BREAKER");
+ assertNotNull(networkSwitch);
+ ContingencyElement element = ContingencyElement.of(networkSwitch);
+ assertNotNull(element);
+ assertInstanceOf(SwitchContingency.class, element);
+ assertEquals("S1VL1_LD1_BREAKER", element.getId());
+ assertEquals(ContingencyElementType.SWITCH, element.getType());
+ }
+
+ @Test
+ void testContingencyElementNotFound() {
+ Network network = FourSubstationsNodeBreakerFactory.create();
+ ContingencyElement element = new SwitchContingency("id");
+ Contingency contingency = new Contingency("contingencyId", "contingencyName", List.of(element));
+ assertFalse(contingency.isValid(network));
+ ContingencyElement element2 = new SwitchContingency("S1VL1_LD1_BREAKER");
+ Contingency contingency2 = new Contingency("contingencyId2", "contingencyName2", List.of(element2));
+ assertTrue(contingency2.isValid(network));
+ }
+}
diff --git a/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java b/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java
index 894ec5c4c30..ca19654a6cd 100644
--- a/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java
+++ b/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java
@@ -14,7 +14,7 @@
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.google.auto.service.AutoService;
-import com.powsybl.commons.extensions.Extension;
+import com.powsybl.commons.extensions.AbstractExtension;
import com.powsybl.commons.extensions.ExtensionJsonSerializer;
import com.powsybl.commons.json.JsonUtil;
import com.powsybl.commons.test.AbstractSerDeTest;
@@ -22,7 +22,7 @@
import com.powsybl.contingency.contingency.list.ContingencyList;
import com.powsybl.contingency.contingency.list.DefaultContingencyList;
import com.powsybl.iidm.network.Network;
-import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
+import com.powsybl.iidm.network.test.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -35,6 +35,7 @@
import java.util.List;
import java.util.Objects;
+import static com.powsybl.contingency.ContingencyElementType.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
@@ -49,7 +50,11 @@ void setup() throws IOException {
super.setUp();
Files.copy(getClass().getResourceAsStream("/contingencies.json"), fileSystem.getPath("/contingencies.json"));
+ Files.copy(getClass().getResourceAsStream("/contingenciesBatteries.json"), fileSystem.getPath("/contingenciesBatteries.json"));
Files.copy(getClass().getResourceAsStream("/contingenciesWithOptionalName.json"), fileSystem.getPath("/contingenciesWithOptionalName.json"));
+ Files.copy(getClass().getResourceAsStream("/contingenciesWithSeveralElements.json"), fileSystem.getPath("/contingenciesWithSeveralElements.json"));
+ Files.copy(getClass().getResourceAsStream("/contingenciesWith3wt.json"), fileSystem.getPath("/contingenciesWith3wt.json"));
+ Files.copy(getClass().getResourceAsStream("/contingenciesWithDlAndTl.json"), fileSystem.getPath("/contingenciesWithDlAndTl.json"));
}
private static Contingency create() {
@@ -134,6 +139,89 @@ void readJsonList() throws IOException {
roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingencies.json");
}
+ @Test
+ void readJsonListWithBatteryContingency() throws IOException {
+ Network network = BatteryNetworkFactory.create();
+
+ ContingencyList contingencyList = ContingencyList.load(fileSystem.getPath("/contingenciesBatteries.json"));
+ assertEquals("list", contingencyList.getName());
+
+ List contingencies = contingencyList.getContingencies(network);
+ assertEquals(4, contingencies.size());
+ assertEquals("contingency", contingencies.get(0).getId());
+ assertEquals(2, contingencies.get(0).getElements().size());
+ assertEquals("contingency2", contingencies.get(1).getId());
+ assertEquals(1, contingencies.get(1).getElements().size());
+ assertEquals("contingency3", contingencies.get(2).getId());
+ assertEquals(1, contingencies.get(2).getElements().size());
+ assertEquals("contingency4", contingencies.get(3).getId());
+ assertEquals(1, contingencies.get(3).getElements().size());
+
+ roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesBatteries.json");
+ }
+
+ @Test
+ void readJsonListWithSwitchHvdcSvcShuntBbsTwtContingency() throws IOException {
+ Network network = FourSubstationsNodeBreakerFactory.create();
+
+ ContingencyList contingencyList = ContingencyList.load(fileSystem.getPath("/contingenciesWithSeveralElements.json"));
+ assertEquals("list", contingencyList.getName());
+
+ List contingencies = contingencyList.getContingencies(network);
+ assertEquals(6, contingencies.size());
+ assertEquals("contingency", contingencies.get(0).getId());
+ assertEquals(1, contingencies.get(0).getElements().size());
+ assertEquals(HVDC_LINE, contingencies.get(0).getElements().get(0).getType());
+ assertEquals("contingency2", contingencies.get(1).getId());
+ assertEquals(1, contingencies.get(1).getElements().size());
+ assertEquals(TWO_WINDINGS_TRANSFORMER, contingencies.get(1).getElements().get(0).getType());
+ assertEquals("contingency3", contingencies.get(2).getId());
+ assertEquals(1, contingencies.get(2).getElements().size());
+ assertEquals(SHUNT_COMPENSATOR, contingencies.get(2).getElements().get(0).getType());
+ assertEquals("contingency4", contingencies.get(3).getId());
+ assertEquals(1, contingencies.get(3).getElements().size());
+ assertEquals(STATIC_VAR_COMPENSATOR, contingencies.get(3).getElements().get(0).getType());
+ assertEquals("contingency5", contingencies.get(4).getId());
+ assertEquals(1, contingencies.get(4).getElements().size());
+ assertEquals(BUSBAR_SECTION, contingencies.get(4).getElements().get(0).getType());
+ assertEquals("contingency6", contingencies.get(5).getId());
+ assertEquals(1, contingencies.get(5).getElements().size());
+ assertEquals(SWITCH, contingencies.get(5).getElements().get(0).getType());
+
+ roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesWithSeveralElements.json");
+ }
+
+ @Test
+ void readJsonListWithTwt3Contingency() throws IOException {
+ Network network = ThreeWindingsTransformerNetworkFactory.create();
+ ContingencyList contingencyList = ContingencyList.load(fileSystem.getPath("/contingenciesWith3wt.json"));
+ assertEquals("list", contingencyList.getName());
+
+ List contingencies = contingencyList.getContingencies(network);
+ assertEquals(1, contingencies.size());
+ assertEquals("contingency", contingencies.get(0).getId());
+ assertEquals(1, contingencies.get(0).getElements().size());
+ assertEquals(THREE_WINDINGS_TRANSFORMER, contingencies.get(0).getElements().get(0).getType());
+ roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesWith3wt.json");
+ }
+
+ @Test
+ void readJsonListWithTieLineAndDanglingLineContingency() throws IOException {
+ Network network = EurostagTutorialExample1Factory.createWithTieLine();
+ ContingencyList contingencyList = ContingencyList.load(fileSystem.getPath("/contingenciesWithDlAndTl.json"));
+ assertEquals("list", contingencyList.getName());
+
+ List contingencies = contingencyList.getContingencies(network);
+ assertEquals(2, contingencies.size());
+ assertEquals("contingency", contingencies.get(0).getId());
+ assertEquals(1, contingencies.get(0).getElements().size());
+ assertEquals(DANGLING_LINE, contingencies.get(0).getElements().get(0).getType());
+ assertEquals("contingency2", contingencies.get(1).getId());
+ assertEquals(1, contingencies.get(1).getElements().size());
+ assertEquals(TIE_LINE, contingencies.get(1).getElements().get(0).getType());
+ roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesWithDlAndTl.json");
+ }
+
@Test
void readNotDefaultJsonList() throws IOException {
Files.copy(getClass().getResourceAsStream("/identifierContingencyList.json"), fileSystem.getPath("/identifierContingencyList.json"));
@@ -161,7 +249,7 @@ void readJsonListContingenciesWithOptionalName() throws IOException {
roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesWithOptionalName.json");
}
- static class DummyExtension implements Extension {
+ static class DummyExtension extends AbstractExtension {
private Contingency contingency;
diff --git a/contingency/contingency-api/src/test/resources/contingenciesBatteries.json b/contingency/contingency-api/src/test/resources/contingenciesBatteries.json
new file mode 100644
index 00000000000..219e6b5ba06
--- /dev/null
+++ b/contingency/contingency-api/src/test/resources/contingenciesBatteries.json
@@ -0,0 +1,33 @@
+{
+ "type" : "default",
+ "version" : "1.0",
+ "name" : "list",
+ "contingencies" : [ {
+ "id" : "contingency",
+ "elements" : [ {
+ "id" : "NHV1_NHV2_1",
+ "type" : "BRANCH"
+ }, {
+ "id" : "NHV1_NHV2_2",
+ "type" : "BRANCH"
+ } ]
+ }, {
+ "id" : "contingency2",
+ "elements" : [ {
+ "id" : "GEN",
+ "type" : "GENERATOR"
+ } ]
+ }, {
+ "id" : "contingency3",
+ "elements" : [ {
+ "id" : "BAT",
+ "type" : "BATTERY"
+ } ]
+ }, {
+ "id" : "contingency4",
+ "elements" : [ {
+ "id" : "LOAD",
+ "type" : "LOAD"
+ } ]
+ } ]
+}
\ No newline at end of file
diff --git a/contingency/contingency-api/src/test/resources/contingenciesWith3wt.json b/contingency/contingency-api/src/test/resources/contingenciesWith3wt.json
new file mode 100644
index 00000000000..7d12233c60b
--- /dev/null
+++ b/contingency/contingency-api/src/test/resources/contingenciesWith3wt.json
@@ -0,0 +1,12 @@
+{
+ "type" : "default",
+ "version" : "1.0",
+ "name" : "list",
+ "contingencies" : [ {
+ "id" : "contingency",
+ "elements" : [ {
+ "id" : "3WT",
+ "type" : "THREE_WINDINGS_TRANSFORMER"
+ } ]
+ } ]
+}
\ No newline at end of file
diff --git a/contingency/contingency-api/src/test/resources/contingenciesWithDlAndTl.json b/contingency/contingency-api/src/test/resources/contingenciesWithDlAndTl.json
new file mode 100644
index 00000000000..862d346b530
--- /dev/null
+++ b/contingency/contingency-api/src/test/resources/contingenciesWithDlAndTl.json
@@ -0,0 +1,18 @@
+{
+ "type" : "default",
+ "version" : "1.0",
+ "name" : "list",
+ "contingencies" : [ {
+ "id" : "contingency",
+ "elements" : [ {
+ "id" : "NHV1_XNODE1",
+ "type" : "DANGLING_LINE"
+ } ]
+ }, {
+ "id" : "contingency2",
+ "elements" : [ {
+ "id" : "NHV1_NHV2_1",
+ "type" : "TIE_LINE"
+ } ]
+ } ]
+}
\ No newline at end of file
diff --git a/contingency/contingency-api/src/test/resources/contingenciesWithSeveralElements.json b/contingency/contingency-api/src/test/resources/contingenciesWithSeveralElements.json
new file mode 100644
index 00000000000..fcccdfa370b
--- /dev/null
+++ b/contingency/contingency-api/src/test/resources/contingenciesWithSeveralElements.json
@@ -0,0 +1,42 @@
+{
+ "type" : "default",
+ "version" : "1.0",
+ "name" : "list",
+ "contingencies" : [ {
+ "id" : "contingency",
+ "elements" : [ {
+ "id" : "HVDC1",
+ "type" : "HVDC_LINE"
+ } ]
+ }, {
+ "id" : "contingency2",
+ "elements" : [ {
+ "id" : "TWT",
+ "type" : "TWO_WINDINGS_TRANSFORMER"
+ } ]
+ }, {
+ "id" : "contingency3",
+ "elements" : [ {
+ "id" : "SHUNT",
+ "type" : "SHUNT_COMPENSATOR"
+ } ]
+ }, {
+ "id" : "contingency4",
+ "elements" : [ {
+ "id" : "SVC",
+ "type" : "STATIC_VAR_COMPENSATOR"
+ } ]
+ }, {
+ "id" : "contingency5",
+ "elements" : [ {
+ "id" : "S1VL1_BBS",
+ "type" : "BUSBAR_SECTION"
+ } ]
+ }, {
+ "id" : "contingency6",
+ "elements" : [ {
+ "id" : "S1VL1_LD1_BREAKER",
+ "type" : "SWITCH"
+ } ]
+ } ]
+}
\ No newline at end of file
diff --git a/contingency/contingency-dsl/pom.xml b/contingency/contingency-dsl/pom.xml
index 2a19c476a41..098d014c05e 100644
--- a/contingency/contingency-dsl/pom.xml
+++ b/contingency/contingency-dsl/pom.xml
@@ -14,7 +14,7 @@
com.powsyblpowsybl-contingency
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-contingency-dsl
diff --git a/contingency/contingency-dsl/src/main/groovy/com/powsybl/contingency/dsl/ContingencyDslLoader.groovy b/contingency/contingency-dsl/src/main/groovy/com/powsybl/contingency/dsl/ContingencyDslLoader.groovy
index 6c215d9e917..29308102f66 100644
--- a/contingency/contingency-dsl/src/main/groovy/com/powsybl/contingency/dsl/ContingencyDslLoader.groovy
+++ b/contingency/contingency-dsl/src/main/groovy/com/powsybl/contingency/dsl/ContingencyDslLoader.groovy
@@ -78,6 +78,16 @@ class ContingencyDslLoader extends DslLoader {
builder.addDanglingLine(equipment)
} else if (identifiable instanceof ThreeWindingsTransformer) {
builder.addThreeWindingsTransformer(equipment)
+ } else if (identifiable instanceof TieLine) {
+ builder.addTieLine(equipment)
+ } else if (identifiable instanceof Bus) {
+ builder.addBus(equipment)
+ } else if (identifiable instanceof Battery) {
+ builder.addBattery(equipment)
+ } else if (identifiable instanceof Load) {
+ builder.addLoad(equipment)
+ } else if (identifiable instanceof Switch) {
+ builder.addSwitch(equipment)
} else {
LOGGER.warn("Equipment type {} not supported in contingencies", identifiable.getClass().name)
valid = false
diff --git a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ContingencyElementTypesTest.java b/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ContingencyElementTypesTest.java
new file mode 100644
index 00000000000..6f8ee69bb02
--- /dev/null
+++ b/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ContingencyElementTypesTest.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright (c) 2020,2021, RTE (http://www.rte-france.com)
+ * Copyright (c) 2024, Artelys (http://www.artelys.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.contingency.dsl;
+
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
+import com.powsybl.contingency.*;
+import com.powsybl.iidm.network.Network;
+import com.powsybl.iidm.network.test.*;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+/**
+ * @author Yichen TANG {@literal }
+ * @author Sebastien Murgey {@literal }
+ * @author Damien Jeandemange {@literal }
+ */
+class ContingencyElementTypesTest {
+
+ private FileSystem fileSystem;
+
+ private Path dslFile;
+
+ @BeforeEach
+ void setUp() {
+ fileSystem = Jimfs.newFileSystem(Configuration.unix());
+ dslFile = fileSystem.getPath("/test.dsl");
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ fileSystem.close();
+ }
+
+ private void test(Network network, String contingencyId, String equipmentId, Class extends ContingencyElement> contingencyElementClass) {
+ try {
+ Files.writeString(dslFile, String.format("""
+ contingency('%s') {
+ equipments '%s'
+ }""", contingencyId, equipmentId),
+ StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ List contingencies = new GroovyDslContingenciesProvider(dslFile)
+ .getContingencies(network);
+ assertEquals(1, contingencies.size());
+ Contingency contingency = contingencies.get(0);
+ assertEquals(contingencyId, contingency.getId());
+ assertEquals(0, contingency.getExtensions().size());
+ assertEquals(1, contingency.getElements().size());
+ ContingencyElement element = contingency.getElements().get(0);
+ assertInstanceOf(contingencyElementClass, element);
+ assertEquals(equipmentId, element.getId());
+ }
+
+ @Test
+ void generatorTest() {
+ Network network = EurostagTutorialExample1Factory.create();
+ test(network, "GEN_CONTINGENCY", "GEN", GeneratorContingency.class);
+ }
+
+ @Test
+ void loadTest() {
+ Network network = EurostagTutorialExample1Factory.create();
+ test(network, "LOAD_CONTINGENCY", "LOAD", LoadContingency.class);
+ }
+
+ @Test
+ void twoWindingsTransformerTest() {
+ Network network = EurostagTutorialExample1Factory.create();
+ test(network, "2WT_CONTINGENCY", "NGEN_NHV1", TwoWindingsTransformerContingency.class);
+ }
+
+ @Test
+ void threeWindingsTransformerTest() {
+ Network network = ThreeWindingsTransformerNetworkFactory.create();
+ test(network, "3WT_CONTINGENCY", "3WT", ThreeWindingsTransformerContingency.class);
+ }
+
+ @Test
+ void lineTest() {
+ Network network = EurostagTutorialExample1Factory.create();
+ test(network, "LINE_CONTINGENCY", "NHV1_NHV2_1", LineContingency.class);
+ }
+
+ @Test
+ void tieLineTest() {
+ Network network = EurostagTutorialExample1Factory.createWithTieLine();
+ test(network, "TIELINE_CONTINGENCY", "NHV1_NHV2_1", TieLineContingency.class);
+ }
+
+ @Test
+ void danglingLineTest() {
+ Network network = DanglingLineNetworkFactory.create();
+ test(network, "DL_CONTINGENCY", "DL", DanglingLineContingency.class);
+ }
+
+ @Test
+ void shuntCompensatorTest() {
+ Network network = EurostagTutorialExample1Factory.create();
+ network.getVoltageLevel("VLLOAD")
+ .newShuntCompensator()
+ .setId("SC")
+ .setConnectableBus("NLOAD")
+ .setBus("NLOAD")
+ .setSectionCount(1)
+ .newLinearModel()
+ .setBPerSection(1e-5)
+ .setMaximumSectionCount(1)
+ .add()
+ .add();
+ test(network, "SC_CONTINGENCY", "SC", ShuntCompensatorContingency.class);
+ }
+
+ @Test
+ void busTest() {
+ Network network = EurostagTutorialExample1Factory.create();
+ test(network, "BUS_CONTINGENCY", "NLOAD", BusContingency.class);
+ }
+
+ @Test
+ void busbarSectionTest() {
+ Network network = FourSubstationsNodeBreakerFactory.create();
+ test(network, "BBS_CONTINGENCY", "S1VL1_BBS", BusbarSectionContingency.class);
+ }
+
+ @Test
+ void svcTest() {
+ Network network = SvcTestCaseFactory.create();
+ test(network, "SVC_CONTINGENCY", "SVC2", StaticVarCompensatorContingency.class);
+ }
+
+ @Test
+ void switchTest() {
+ Network network = FourSubstationsNodeBreakerFactory.create();
+ test(network, "SWITCH_CONTINGENCY", "S1VL1_LD1_BREAKER", SwitchContingency.class);
+ }
+
+ @Test
+ void hvdcTest() {
+ Network network = HvdcTestNetwork.createLcc();
+ test(network, "HVDC_LINE_CONTINGENCY", "L", HvdcLineContingency.class);
+ }
+
+ @Test
+ void batteryTest() {
+ Network network = BatteryNetworkFactory.create();
+ test(network, "BAT_CONTINGENCY", "BAT", BatteryContingency.class);
+ }
+}
diff --git a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/DanglingLineContingencyScriptTest.java b/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/DanglingLineContingencyScriptTest.java
deleted file mode 100644
index 95e28aeb3c3..00000000000
--- a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/DanglingLineContingencyScriptTest.java
+++ /dev/null
@@ -1,77 +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/.
- * SPDX-License-Identifier: MPL-2.0
- */
-package com.powsybl.contingency.dsl;
-
-import com.google.common.jimfs.Configuration;
-import com.google.common.jimfs.Jimfs;
-import com.powsybl.contingency.Contingency;
-import com.powsybl.contingency.ContingencyElement;
-import com.powsybl.contingency.DanglingLineContingency;
-import com.powsybl.iidm.network.Network;
-import com.powsybl.iidm.network.test.DanglingLineNetworkFactory;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * @author Sebastien Murgey {@literal }
- */
-class DanglingLineContingencyScriptTest {
-
- private FileSystem fileSystem;
-
- private Path dslFile;
-
- private Network network;
-
- @BeforeEach
- void setUp() {
- fileSystem = Jimfs.newFileSystem(Configuration.unix());
- dslFile = fileSystem.getPath("/test.dsl");
- network = DanglingLineNetworkFactory.create();
- }
-
- @AfterEach
- void tearDown() throws Exception {
- fileSystem.close();
- }
-
- private void writeToDslFile(String... lines) throws IOException {
- try (Writer writer = Files.newBufferedWriter(dslFile, StandardCharsets.UTF_8)) {
- writer.write(String.join(System.lineSeparator(), lines));
- }
- }
-
- @Test
- void test() throws IOException {
- writeToDslFile("contingency('DL_CONTINGENCY') {",
- " equipments 'DL'",
- "}");
- List contingencies = new GroovyDslContingenciesProvider(dslFile)
- .getContingencies(network);
- assertEquals(1, contingencies.size());
- Contingency contingency = contingencies.get(0);
- assertEquals("DL_CONTINGENCY", contingency.getId());
- assertEquals(0, contingency.getExtensions().size());
- assertEquals(1, contingency.getElements().size());
- ContingencyElement element = contingency.getElements().iterator().next();
- assertTrue(element instanceof DanglingLineContingency);
- assertEquals("DL", element.getId());
- }
-}
diff --git a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ThreeWindingsTransformerContingencyScriptTest.java b/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ThreeWindingsTransformerContingencyScriptTest.java
deleted file mode 100644
index 4b20fab7ba8..00000000000
--- a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ThreeWindingsTransformerContingencyScriptTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Copyright (c) 2021, 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.contingency.dsl;
-
-import com.google.common.jimfs.Configuration;
-import com.google.common.jimfs.Jimfs;
-import com.powsybl.contingency.Contingency;
-import com.powsybl.contingency.ContingencyElement;
-import com.powsybl.contingency.ThreeWindingsTransformerContingency;
-import com.powsybl.iidm.network.Network;
-import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * @author Yichen TANG {@literal }
- */
-class ThreeWindingsTransformerContingencyScriptTest {
-
- private FileSystem fileSystem;
-
- private Path dslFile;
-
- private Network network;
-
- @BeforeEach
- void setUp() {
- fileSystem = Jimfs.newFileSystem(Configuration.unix());
- dslFile = fileSystem.getPath("/test.dsl");
- network = ThreeWindingsTransformerNetworkFactory.create();
- }
-
- @AfterEach
- void tearDown() throws Exception {
- fileSystem.close();
- }
-
- private void writeToDslFile(String... lines) throws IOException {
- try (Writer writer = Files.newBufferedWriter(dslFile, StandardCharsets.UTF_8)) {
- writer.write(String.join(System.lineSeparator(), lines));
- }
- }
-
- @Test
- void test() throws IOException {
- writeToDslFile("contingency('3WT_CONTINGENCY') {",
- " equipments '3WT'",
- "}");
- List contingencies = new GroovyDslContingenciesProvider(dslFile)
- .getContingencies(network);
- assertEquals(1, contingencies.size());
- Contingency contingency = contingencies.get(0);
- assertEquals("3WT_CONTINGENCY", contingency.getId());
- assertEquals(0, contingency.getExtensions().size());
- assertEquals(1, contingency.getElements().size());
- ContingencyElement element = contingency.getElements().iterator().next();
- assertTrue(element instanceof ThreeWindingsTransformerContingency);
- assertEquals("3WT", element.getId());
- }
-}
diff --git a/contingency/pom.xml b/contingency/pom.xml
index 36cd53d21fb..74b07a97dbc 100644
--- a/contingency/pom.xml
+++ b/contingency/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpom
diff --git a/distribution-core/pom.xml b/distribution-core/pom.xml
index 8900ca8011c..65cb2c582c8 100644
--- a/distribution-core/pom.xml
+++ b/distribution-core/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpom
diff --git a/docs/grid_exchange_formats/ampl/export.md b/docs/grid_exchange_formats/ampl/export.md
index 6f8bb055daf..9f942862abd 100644
--- a/docs/grid_exchange_formats/ampl/export.md
+++ b/docs/grid_exchange_formats/ampl/export.md
@@ -8,8 +8,9 @@ At the moment, there are:
- The `BasicAmplExporter` (associated with the `AmplExportVersion` `V1_0`);
- The `ExtendedAmplExporter` (associated with the `AmplExportVersion` `V1_1`) that inherits from the `BasicAmplExporter`.
+- The `ExtendedAmplExporterV2` (associated with the `AmplExportVersion` `V1_2`) that inherits from the `ExtendedAmplExporter`.
-The default version is the `V1_1`.
+The default version is the `V1_2`.
Exporters define the information written in text files and fed to AMPL regarding:
@@ -38,6 +39,15 @@ This exporter adds the following information to the `BasicAmplExporter`:
- `r`, `g` and `b` in tap tables as it is already done for `x`;
- The regulating bus id for generators and static VAR compensators that are in voltage regulation mode.
+### The `ExtendedAmplExporterV2`
+
+This exporter adds the following information to the `ExtendedAmplExporter`:
+
+- In the generator tables, a boolean indicating if the generator is a condenser;
+- In LCC converter station tables, the load target Q of the converter station;
+- In HVDC line tables, the AC emulation parameters, along with a boolean to indicate whether AC emulation is active.
+
+This exporter also corrects the unit of the load target Q (MVar) in the battery tables.
(ampl-export-options)=
## Options
diff --git a/docs/grid_exchange_formats/cgmes/export.md b/docs/grid_exchange_formats/cgmes/export.md
index 233a0f4f745..b6510362016 100644
--- a/docs/grid_exchange_formats/cgmes/export.md
+++ b/docs/grid_exchange_formats/cgmes/export.md
@@ -391,6 +391,11 @@ This property is set to `true` by default.
Optional property that defines whether all OperationalLimitsGroup should be exported, or only the selected (active) ones.
This property is set to `true` by default, which means all groups are exported (not only the active ones).
+**iidm.export.cgmes.export-generators-in-local-regulation-mode**
+Optional property that allows to export voltage regulating generators in local regulation mode. This doesn't concern reactive power regulating generators.
+If set to true, the generator's regulating terminal is set to the generator's own terminal and the target voltage is rescaled accordingly.
+This property is set to `false` by default.
+
**iidm.export.cgmes.max-p-mismatch-converged**
Optional property that defines the threshold below which a bus is considered to be balanced for the load flow status of the `TopologicalIsland` in active power. If the sum of all the active power of the terminals connected to the bus is greater than this threshold, then the load flow is considered to be divergent. Its default value is `0.1`, and it should be used only if the `iidm.export.cgmes.export-load-flow-status` property is set to `true`.
diff --git a/docs/grid_exchange_formats/cgmes/import.md b/docs/grid_exchange_formats/cgmes/import.md
index 786c0c4415b..17ed7a10318 100644
--- a/docs/grid_exchange_formats/cgmes/import.md
+++ b/docs/grid_exchange_formats/cgmes/import.md
@@ -218,7 +218,7 @@ If the `EquivalentBranch` is mapped to a PowSyBl [`DanglingLine`](../../grid_mod
- `Q0` is copied from CGMES `Q` of the terminal at boundary side
(cgmes-asynchronous-machine-import)=
-### AsychronousMachine
+### AsynchronousMachine
Asynchronous machines represent rotating machines whose shaft rotates asynchronously with the electrical field.
It can be motor or generator; no distinction is made for the conversion of these two types.
@@ -241,7 +241,7 @@ A `SynchronousMachine` is mapped to a PowSyBl [`Generator`](../../grid_model/net
- If it is a `HydroGeneratingUnit`, `EnergySource` is `HYDRO`
- If it is a `NuclearGeneratingUnit`, `EnergySource` is `NUCLEAR`
- If it is a `ThermalGeneratingUnit`, `EnergySource` is `THERMAL`
- - If it is a `WindGeneratingUnit`, `EnergySource` is `WIND`
+ - If it is a `WindGeneratingUnit`, `EnergySource` is `WIND`. Additionally, the `WindGeneratingUnit.windGenUnitType` value (`onshore` or `offshore`) is stored as an iIDM property `CGMES.windGenUnitType` of the generator.
- If it is a `SolarGeneratingUnit`, `EnergySource` is `SOLAR`
- Else, `EnergySource` is `OTHER`
- `TargetP`/`TargetQ` are set from `SSH` or `SV` values depending on which are defined. CGMES values for `p`/`q` are given with load sign convention, so a change in sign is applied when copying them to `TargetP`/`TargetQ`. If undefined, `TargetP` is set from CGMES `GeneratingUnit.initialP` from the `GeneratingUnit` associated to the `SynchronousMachine` and `TargetQ` is set to `0`.
diff --git a/docs/grid_exchange_formats/iidm/index.md b/docs/grid_exchange_formats/iidm/index.md
index ea04c69f517..2b1d6aca91d 100644
--- a/docs/grid_exchange_formats/iidm/index.md
+++ b/docs/grid_exchange_formats/iidm/index.md
@@ -6,7 +6,7 @@ import.md
export.md
```
-The [IIDM (**i**Tesla **I**nternal **D**ata **M**odel)](../../grid_model/index.md) format was designed during the [iTesla project](http://www.itesla-project.eu).
+The [IIDM (**i**Tesla **I**nternal **D**ata **M**odel)](../../grid_model/index.md) format was designed during the iTesla project.
IIDM is the internal format used in Powsybl because it is designed for running simulations.
Several exchange formats result from this internal format:
diff --git a/docs/simulation/security/contingency-dsl.md b/docs/simulation/security/contingency-dsl.md
index 542c67464f0..94a844df548 100644
--- a/docs/simulation/security/contingency-dsl.md
+++ b/docs/simulation/security/contingency-dsl.md
@@ -1,17 +1,26 @@
# Contingency DSL
-The contingency DSL is a domain specific language written in groovy for the creation of a contingency list, used in [security analyses](./index.md) or [sensitivity analyses](../sensitivity/index). At the moment, it's possible to simulate the loss of a generator, a static VAR compensator, a shunt, a power line, a power transformer, an HVDC line or a busbar section.
+
+The contingency DSL is a domain specific language written in groovy for the creation of a contingency list, used
+in [security analyses](./index.md) or [sensitivity analyses](../sensitivity/index).
## N-1 contingency
-A N-1 contingency is a contingency that triggers a single piece of equipment at a time.
+
+An N-1 contingency is a contingency that triggers a single piece of equipment at a time.
+
```groovy
contingency('contingencyID') {
equipments 'equipmentID'
}
```
-where the `contingencyID` is the identifier of the contingency and the `equipmentID` is the identifier of a supported piece of equipment. If the equipment doesn't exist or is not supported, an error will occur.
+
+where the `contingencyID` is the identifier of the contingency and the `equipmentID` is the identifier of a supported
+piece of equipment. If the equipment doesn't exist or is not supported, an error will occur.
## N-K contingency
-A N-K contingency is a contingency that triggers several equipments at a time. The syntax is the same as for the N-1 contingencies, except that you have to pass a list of equipments' IDs.
+
+An N-K contingency is a contingency that triggers several equipments at a time. The syntax is the same as for the N-1
+contingencies, except that you have to pass a list of equipments' IDs.
+
```groovy
contingency('contingencyID') {
equipments 'equipment1', 'equipment2'
@@ -19,7 +28,10 @@ contingency('contingencyID') {
```
## Manual contingency list
-A manual contingency list is a set of contingencies that are explicitly defined. In the following example, the list contains two contingencies that trigger respectively the equipment `equipment1` and `equipment2`:
+
+A manual contingency list is a set of contingencies that are explicitly defined. In the following example, the list
+contains two contingencies that trigger respectively the equipment `equipment1` and `equipment2`:
+
```groovy
contingency('contingency1') {
equipments 'equipment1'
@@ -31,9 +43,14 @@ contingency('contingency2') {
```
## Automatic contingency list
-As the DSL is written in Groovy, it's possible to write a more complex script. For example, it's possible to iterate over the equipment of the network to generate the contingency list. The network is accessible using the `network` variable.
-The following example creates a N-1 contingency for each line of the network. We use the ID of the lines as identifier for the contingencies.
+As the DSL is written in Groovy, it's possible to write a more complex script. For example, it's possible to iterate
+over the equipment of the network to generate the contingency list. The network is accessible using the `network`
+variable.
+
+The following example creates an N-1 contingency for each line of the network. We use the ID of the lines as identifier
+for the contingencies.
+
```groovy
for (l in network.lines) {
contingency(l.id) {
@@ -43,6 +60,7 @@ for (l in network.lines) {
```
It's also possible to filter the lines, for example, to consider on the boundary lines:
+
```groovy
import com.powsybl.iidm.network.Country
@@ -58,6 +76,7 @@ for (l in network.lines) {
```
The following example creates a list of contingencies for all 380 kV lines:
+
```groovy
for (l in network.lines) {
nominalVoltage1 = l.terminal1.voltageLevel.nominalV
@@ -70,62 +89,71 @@ for (l in network.lines) {
}
```
-In the following example, we use the [Stream API](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/stream/package-summary.html) to create a list of contingencies with the 3 first 225 kV french lines:
+In the following example, we use
+the [Stream API](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/stream/package-summary.html) to
+create a list of contingencies with the 3 first 225 kV french lines:
+
```groovy
import com.powsybl.iidm.network.Country
network.lineStream
- .filter({l -> l.terminal1.voltageLevel.substation.country == Country.FR})
- .filter({l -> l.terminal2.voltageLevel.substation.country == Country.FR})
- .filter({l -> l.terminal1.voltageLevel.nominalV == 225})
- .filter({l -> l.terminal2.voltageLevel.nominalV == 225})
- .limit(3)
- .forEach({l ->
- contingency(l.id) {
- equipments l.id
- }
- })
+ .filter({ l -> l.terminal1.voltageLevel.substation.country == Country.FR })
+ .filter({ l -> l.terminal2.voltageLevel.substation.country == Country.FR })
+ .filter({ l -> l.terminal1.voltageLevel.nominalV == 225 })
+ .filter({ l -> l.terminal2.voltageLevel.nominalV == 225 })
+ .limit(3)
+ .forEach({ l ->
+ contingency(l.id) {
+ equipments l.id
+ }
+ })
```
-The following example creates a list of contingencies with the 3 first French nuclear generators with a maximum power greater than 1000 MW.
+The following example creates a list of contingencies with the 3 first French nuclear generators with a maximum power
+greater than 1000 MW.
+
```groovy
import com.powsybl.iidm.network.Country
import com.powsybl.iidm.network.EnergySource
network.generatorStream
- .filter({g -> g.terminal.voltageLevel.substation.country == Country.FR})
- .filter({g -> g.energySource == EnergySource.NUCLEAR})
- .filter({g -> g.maxP > 1000})
- .limit(3)
- .forEach({g ->
- contingency(g.id) {
- equipments g.id
- }
- })
+ .filter({ g -> g.terminal.voltageLevel.substation.country == Country.FR })
+ .filter({ g -> g.energySource == EnergySource.NUCLEAR })
+ .filter({ g -> g.maxP > 1000 })
+ .limit(3)
+ .forEach({ g ->
+ contingency(g.id) {
+ equipments g.id
+ }
+ })
```
## Configuration
+
To provide a contingency list using this DSL, you have to add the following lines to your configuration file:
**YAML configuration:**
+
```yaml
componentDefaultConfig:
- ContingenciesProviderFactory: com.powsybl.contingency.dsl.GroovyDslContingenciesProviderFactory
+ ContingenciesProviderFactory: com.powsybl.contingency.dsl.GroovyDslContingenciesProviderFactory
groovy-dsl-contingencies:
- dsl-file: /path/to/contingencies.groovy
+ dsl-file: /path/to/contingencies.groovy
```
**XML configuration:**
+
```xml
com.powsybl.contingency.dsl.GroovyDslContingenciesProviderFactory
- /path/to/contingencies.groovy
+ /path/to/contingencies.groovy
```
## Going further
+
- [Action DSL](action-dsl.md): Lean how to write scripts for security analyses with remedial actions
diff --git a/docs/simulation/security/index.md b/docs/simulation/security/index.md
index 2de6edf9cfb..8209612700c 100644
--- a/docs/simulation/security/index.md
+++ b/docs/simulation/security/index.md
@@ -9,20 +9,35 @@ action-dsl.md
limit-reductions.md
```
-The security analysis is a simulation that checks violations on a network. These checks can be done on the base case or after a contingency, with or without remedial actions. A security analysis can monitor network states, in pre-contingency state, after a contingency and after a remedial action.
+The security analysis is a simulation that checks violations on a network. These checks can be done on the base case or
+after a contingency, with or without remedial actions. A security analysis can monitor network states, in
+pre-contingency state, after a contingency and after a remedial action.
-There is a violation if the computed value is greater than the maximum allowed value. Depending on the equipment, the violations can have different types:
-- Current, active power and apparent power: this kind of violation can be detected on a branch (line, two-winding transformer, tie line) or on a three-winding transformer, if the computed value is greater than its [permanent limit](../../grid_model/additional.md#loading-limits) or one of its [temporary limits](../../grid_model/additional.md#loading-limits).
-- Voltage: this kind of violation can be detected on a bus or a bus bar section, if the computed voltage is out of the low-high voltage limits of a [voltage level](../../grid_model/network_subnetwork.md#voltage-level).
-- Voltage angle: this kind of violation can be detected if the voltage angle difference between the buses associated to two terminals is out of the low-high voltage angle limits defined in the network.
+There is a violation if the computed value is greater than the maximum allowed value. Depending on the equipment, the
+violations can have different types:
+
+- Current, active power and apparent power: this kind of violation can be detected on a branch (line, two-winding
+ transformer, tie line) or on a three-winding transformer, if the computed value is greater than
+ its [permanent limit](../../grid_model/additional.md#loading-limits) or one of
+ its [temporary limits](../../grid_model/additional.md#loading-limits).
+- Voltage: this kind of violation can be detected on a bus or a bus bar section, if the computed voltage is out of the
+ low-high voltage limits of a [voltage level](../../grid_model/network_subnetwork.md#voltage-level).
+- Voltage angle: this kind of violation can be detected if the voltage angle difference between the buses associated to
+ two terminals is out of the low-high voltage angle limits defined in the network.
## Inputs
### Network
-The first input of the security analysis is a network. As this simulation is based on a [load flow](../loadflow/index) engine for a list of contingencies, this network should converge in the pre-contingency state.
+
+The first input of the security analysis is a network. As this simulation is based on a [load flow](../loadflow/index)
+engine for a list of contingencies, this network should converge in the pre-contingency state.
### Contingencies
-The security analysis needs a list of contingencies as an input. When contingencies are provided, the violations are detected on the network at pre-contingency state, but also after applying each contingency. The supported elementary contingencies are:
+
+The security analysis needs a list of contingencies as an input. When contingencies are provided, the violations are
+detected on the network at pre-contingency state, but also after applying each contingency. The supported elementary
+contingencies are:
+
- Generator contingency
- Static var compensator contingency
- Load contingency
@@ -30,49 +45,83 @@ The security analysis needs a list of contingencies as an input. When contingenc
- Busbar section contingency for node/breaker topologies
- Line, two-winding transformer and tie line contingencies (branch contingency)
- Three-winding transformer contingency
+- Dangling line contingency
- HVDC line contingency
+- Battery contingency
+- Shunt Compensator contingency
-A contingency is made of contingency elements. A contingency can trigger one element at a time (N-1) or several elements at a time (N-K). Bus bar and bus contingencies are special N-K contingencies as they trigger all the equipments connected to a given bus bar section.
+A contingency is made of contingency elements. A contingency can trigger one element at a time (N-1) or several elements
+at a time (N-K). Bus bar and bus contingencies are special N-K contingencies as they trigger all the equipments
+connected to a given bus bar section.
### Remedial actions
+
Remedial actions are actions that are applied when limit violations occur. Supported actions are:
+
- Open or close a switch
- Open or close a terminal
- Change the tap of a tap changer (phase or ratio)
-- Change the active and/or reactive power of a load
+- Change the active and/or reactive power of a load (by setting the values, or defining changes in value or percentage)
- Change the section of a shunt compensator
- Change the regulation status of a tap changer
- Change `targetP`, `targetQ`, regulation status and `targetV` of a generator
- Change the regulation mode of a static var compensator and its associated set point.
-- Enabled or disabled AC emulation for HVDC line (with the possibility to change `P0` and `droop` for AC emulation and active power set point and converter mode for set point operating mode)
+- Enabled or disabled AC emulation for HVDC line (with the possibility to change `P0` and `droop` for AC emulation and
+ active power set point and converter mode for set point operating mode)
+- Change the interchange target of an area by specifying a new interchange target in MW.
Remedial actions can be *preventive* or *curative*:
-- preventive: these actions are implemented before the violation occurs, for example if the flow of a monitored line is between `90%` and `100%`. Use contingency context `NONE` for that.
-- curative: these actions are implemented after a violation occurs, for example, if the flow of the monitored line is greater than `100%`.
+
+- preventive: these actions are implemented before the violation occurs, for example if the flow of a monitored line is
+ between `90%` and `100%`. Use contingency context `NONE` for that.
+- curative: these actions are implemented after a violation occurs, for example, if the flow of the monitored line is
+ greater than `100%`.
### Conditions
+
Actions are applied if a condition is met. The conditions can be diversified and extended in the future:
+
- True condition: meaning that the list of actions is applied.
-- All violations condition on a list of elements: meaning that the list of actions is applied only if all elements provided are overloaded.
-- At least one violation condition: meaning that the list of actions is applied only if a violation occurs on the network.
-- Any violation condition on a list of elements: meaning that the list of actions is applied if one or more elements provided are overloaded.
+- All violations condition on a list of elements: meaning that the list of actions is applied only if all elements
+ provided are overloaded.
+- At least one violation condition: meaning that the list of actions is applied only if a violation occurs on the
+ network.
+- Any violation condition on a list of elements: meaning that the list of actions is applied if one or more elements
+ provided are overloaded.
### Operator strategies
-An operator strategy is applied in pre-contingency or after a contingency, depending on the contingency context provided. A contingency context can be a pre-contingency state only (`NONE`), a post-contingency state (on a specific contingency (`SPECIFIC`) or on every contingency (`ONLY_CONTINGENCIES`)) or both pre-contingency and post-contingency states (`ALL`).
+
+An operator strategy is applied in pre-contingency or after a contingency, depending on the contingency context
+provided. A contingency context can be a pre-contingency state only (`NONE`), a post-contingency state (on a specific
+contingency (`SPECIFIC`) or on every contingency (`ONLY_CONTINGENCIES`)) or both pre-contingency and post-contingency
+states (`ALL`).
An operator strategy groups a condition and a list of remedial actions.
### State monitors
-A stateMonitor allows getting information about branch, bus and three-winding transformers on the network after a security analysis computation. Contingency context allows to specify if the information asked are about pre-contingency state or post-contingency state with a contingency id or both. For example:
-- If we want information about a branch after security analysis on contingency `c1`, the contingencyContext will contain the contingencyId `c1`, contextType `SPECIFIC` and the state monitor will contain the id of the branch.
-- If we want information about a branch in pre-contingency state, the contingencyContext will contain a null contingencyId, contextType `NONE` and the state monitor will contain the id of the branch.
-- If we want information about a branch in pre-contingency state and after security analysis on contingency `c1`, the contingencyContext will contain contingencyId `c1`, contextType `ALL` and the state monitor will contain the id of the branch.
+
+A stateMonitor allows getting information about branch, bus and three-winding transformers on the network after a
+security analysis computation. Contingency context allows to specify if the information asked are about pre-contingency
+state or post-contingency state with a contingency id or both. For example:
+
+- If we want information about a branch after security analysis on contingency `c1`, the contingencyContext will contain
+ the contingencyId `c1`, contextType `SPECIFIC` and the state monitor will contain the id of the branch.
+- If we want information about a branch in pre-contingency state, the contingencyContext will contain a null
+ contingencyId, contextType `NONE` and the state monitor will contain the id of the branch.
+- If we want information about a branch in pre-contingency state and after security analysis on contingency `c1`, the
+ contingencyContext will contain contingencyId `c1`, contextType `ALL` and the state monitor will contain the id of the
+ branch.
### Limit reductions
-Limit reductions can be specified in order to detect when a specific limit is **nearly** reached, without having to artificially modify the limit itself.
-For instance, with a limit reduction set to 95% for a limit of 1000 MW, the security analysis will flag a limit violation for any value exceeding 950 MW.
-Each limit reduction has its own criteria specifying for which limits and under what conditions it should be applied. These criteria can include:
+Limit reductions can be specified in order to detect when a specific limit is **nearly** reached, without having to
+artificially modify the limit itself.
+For instance, with a limit reduction set to 95% for a limit of 1000 MW, the security analysis will flag a limit
+violation for any value exceeding 950 MW.
+
+Each limit reduction has its own criteria specifying for which limits and under what conditions it should be applied.
+These criteria can include:
+
- the type of limit (current, active power or apparent power);
- the use case (for monitoring only or also for applying remedial actions);
- the contingency context (pre-contingency, after a specific contingency or after all contingencies, etc.);
@@ -84,25 +133,40 @@ You can find more details about limit reductions [here](./limit-reductions).
## Outputs
### Pre-contingency results
-The violations are detected on the network at state N, meaning before a contingency occurred. This determines a reference for the simulation. For each violation, we get the ID of the overloaded equipment, the limit type (`CURRENT`, `ACTIVE_POWER`, `APPARENT_POWER`, `LOW_VOLTAGE` or `HIGH_VOLTAGE`, `LOW_VOLTAGE_ANGLE` or `HIGH_VOLTAGE_ANGLE`), the acceptable value and the computed value. For branches and three-winding transformers, we also have the side where the violation has been detected.
-The pre-contingency results also contain the network results based on given state monitors. A network result groups branch results, bus results and three-winding transformer results. All elementary results are fully extendable.
+The violations are detected on the network at state N, meaning before a contingency occurred. This determines a
+reference for the simulation. For each violation, we get the ID of the overloaded equipment, the limit
+type (`CURRENT`, `ACTIVE_POWER`, `APPARENT_POWER`, `LOW_VOLTAGE` or `HIGH_VOLTAGE`, `LOW_VOLTAGE_ANGLE`
+or `HIGH_VOLTAGE_ANGLE`), the acceptable value and the computed value. For branches and three-winding transformers, we
+also have the side where the violation has been detected.
+
+The pre-contingency results also contain the network results based on given state monitors. A network result groups
+branch results, bus results and three-winding transformer results. All elementary results are fully extendable.
### Post-contingency results
-The post-contingency results contain the complete list of the contingencies that have been simulated, and for each of them the violations detected. To limit information to the user, only new violations or worsened violations can be listed.
+
+The post-contingency results contain the complete list of the contingencies that have been simulated, and for each of
+them the violations detected. To limit information to the user, only new violations or worsened violations can be
+listed.
The post-contingency results also contain the network results based on given state monitors.
### Operator strategy results
-The post-contingency results contain the complete list of the contingencies that have been simulated, and for each of them the violations detected in order to check if remedial actions were efficient.
+
+The post-contingency results contain the complete list of the contingencies that have been simulated, and for each of
+them the violations detected in order to check if remedial actions were efficient.
The operator strategy results also contain the network results based on given state monitors.
### Extensions
-The results of a security analysis are extendable, meaning you can have additional information attached to the network, the contingencies or the violations.
+
+The results of a security analysis are extendable, meaning you can have additional information attached to the network,
+the contingencies or the violations.
### Example
+
The following example is a result of a security analysis with remedial action, exported in JSON:
+
```json
{
"version" : "1.4",
@@ -284,5 +348,6 @@ The following example is a result of a security analysis with remedial action, e
```
## Going further
+
- Different implementations are available to run security analyses on [page](implementations.md).
- [Run a security analysis through an iTools command](../../user/itools/security-analysis.md): Learn how to perform a security analysis from the command line.
diff --git a/docs/simulation/shortcircuit/parameters.md b/docs/simulation/shortcircuit/parameters.md
index 6f53f70e9c4..0c312cd46ac 100644
--- a/docs/simulation/shortcircuit/parameters.md
+++ b/docs/simulation/shortcircuit/parameters.md
@@ -106,25 +106,31 @@ This property defines the voltage profile that should be used for the calculatio
**voltage-ranges**
This property specifies a path to a JSON file containing the voltage ranges and associated coefficients to be used when `initial-voltage-profile-mode` is set to `CONFIGURED`.
-The JSON file must contain a list of voltage ranges and coefficients. Then, for each nominal voltage in the network that belongs to the range, the given coefficient is applied to calculate the voltage to be used
-in the calculation. All the coefficients should be between 0.8 and 1.2.
+The JSON file must contain a list of voltage ranges and for each range, coefficients and/or voltages.
+Then, for each nominal voltage in the network that belongs to the range, the coefficient is applied to calculate the voltage to be used
+in the calculation. All the coefficients should be between 0.8 and 1.2. They are optional, and if they are missing for a range, 1 will be used.
+The voltage attribute of the voltage range defines the nominal voltage of all the voltage levels in the network that are in the range.
+It is also optional, and if it is not defined, then the nominal voltage already in the network will be used.
Here is an example of this JSON file:
````json
[
{
"minimumNominalVoltage": 350.0,
"maximumNominalVoltage": 400.0,
- "voltageRangeCoefficient": 1.1
+ "voltageRangeCoefficient": 1.1,
+ "voltage": 380.0
},
{
"minimumNominalVoltage": 215.0,
"maximumNominalVoltage": 235.0,
- "voltageRangeCoefficient": 1.2
+ "voltageRangeCoefficient": 1.2,
+ "voltage": 225.0
},
{
"minimumNominalVoltage": 80.0,
"maximumNominalVoltage": 150.0,
- "voltageRangeCoefficient": 1.05
+ "voltageRangeCoefficient": 1.05,
+ "voltage": 105.0
}
]
````
diff --git a/dsl/pom.xml b/dsl/pom.xml
index 574327e0649..abf96b46cb0 100644
--- a/dsl/pom.xml
+++ b/dsl/pom.xml
@@ -13,7 +13,7 @@
powsybl-corecom.powsybl
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOT4.0.0
diff --git a/dynamic-security-analysis/pom.xml b/dynamic-security-analysis/pom.xml
index aa7ff6c76e4..80600070846 100644
--- a/dynamic-security-analysis/pom.xml
+++ b/dynamic-security-analysis/pom.xml
@@ -13,7 +13,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-dynamic-security-analysis
diff --git a/dynamic-simulation/dynamic-simulation-api/pom.xml b/dynamic-simulation/dynamic-simulation-api/pom.xml
index 3002e490708..c797860843b 100644
--- a/dynamic-simulation/dynamic-simulation-api/pom.xml
+++ b/dynamic-simulation/dynamic-simulation-api/pom.xml
@@ -13,7 +13,7 @@
com.powsyblpowsybl-dynamic-simulation
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-dynamic-simulation-api
diff --git a/dynamic-simulation/dynamic-simulation-dsl/pom.xml b/dynamic-simulation/dynamic-simulation-dsl/pom.xml
index 691ab834404..b77e4849834 100644
--- a/dynamic-simulation/dynamic-simulation-dsl/pom.xml
+++ b/dynamic-simulation/dynamic-simulation-dsl/pom.xml
@@ -13,7 +13,7 @@
com.powsyblpowsybl-dynamic-simulation
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-dynamic-simulation-dsl
diff --git a/dynamic-simulation/dynamic-simulation-tool/pom.xml b/dynamic-simulation/dynamic-simulation-tool/pom.xml
index dd058b77c4f..06b62122aa4 100644
--- a/dynamic-simulation/dynamic-simulation-tool/pom.xml
+++ b/dynamic-simulation/dynamic-simulation-tool/pom.xml
@@ -13,7 +13,7 @@
com.powsyblpowsybl-dynamic-simulation
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-dynamic-simulation-tool
diff --git a/dynamic-simulation/pom.xml b/dynamic-simulation/pom.xml
index 8c664882f54..7d3aed81a58 100644
--- a/dynamic-simulation/pom.xml
+++ b/dynamic-simulation/pom.xml
@@ -13,7 +13,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpom
diff --git a/entsoe-util/pom.xml b/entsoe-util/pom.xml
index 903f1f659ea..42448bba4a5 100644
--- a/entsoe-util/pom.xml
+++ b/entsoe-util/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-core
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-entsoe-util
diff --git a/ieee-cdf/ieee-cdf-converter/pom.xml b/ieee-cdf/ieee-cdf-converter/pom.xml
index 9ef09a841a1..f111775cb5e 100644
--- a/ieee-cdf/ieee-cdf-converter/pom.xml
+++ b/ieee-cdf/ieee-cdf-converter/pom.xml
@@ -13,7 +13,7 @@
powsybl-ieee-cdfcom.powsybl
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-ieee-cdf-converter
diff --git a/ieee-cdf/ieee-cdf-model/pom.xml b/ieee-cdf/ieee-cdf-model/pom.xml
index a5167e0cb1e..44597b50220 100644
--- a/ieee-cdf/ieee-cdf-model/pom.xml
+++ b/ieee-cdf/ieee-cdf-model/pom.xml
@@ -13,7 +13,7 @@
com.powsyblpowsybl-ieee-cdf
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-ieee-cdf-model
diff --git a/ieee-cdf/pom.xml b/ieee-cdf/pom.xml
index 6063fb78ac6..a0267921e56 100644
--- a/ieee-cdf/pom.xml
+++ b/ieee-cdf/pom.xml
@@ -13,7 +13,7 @@
powsybl-corecom.powsybl
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpom
diff --git a/iidm/iidm-api/pom.xml b/iidm/iidm-api/pom.xml
index 956230f2ee7..58e7743376f 100644
--- a/iidm/iidm-api/pom.xml
+++ b/iidm/iidm-api/pom.xml
@@ -15,7 +15,7 @@
com.powsyblpowsybl-iidm
- 6.6.0-SNAPSHOT
+ 6.7.0-SNAPSHOTpowsybl-iidm-api
diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Boundary.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Boundary.java
index 660f7f3ddac..8a257551f5d 100644
--- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Boundary.java
+++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Boundary.java
@@ -32,6 +32,11 @@ public interface Boundary {
*/
double getQ();
+ /**
+ * Get the current in A at the fictitious boundary terminal.
+ */
+ double getI();
+
/**
* Get the danglingLine the boundary is associated to.
*/
diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/PhaseTapChangerHolder.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/PhaseTapChangerHolder.java
index f7852e37ad5..186169b90b7 100644
--- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/PhaseTapChangerHolder.java
+++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/PhaseTapChangerHolder.java
@@ -10,7 +10,6 @@
import java.util.Optional;
/**
- *
* @author Geoffroy Jamgotchian {@literal }
*/
public interface PhaseTapChangerHolder {
@@ -21,6 +20,33 @@ public interface PhaseTapChangerHolder {
*/
PhaseTapChangerAdder newPhaseTapChanger();
+ /**
+ * Get a builder to create and associate a phase tap changer to the
+ * transformer, initialized with the values of an existing ratio tap changer.
+ */
+ default PhaseTapChangerAdder newPhaseTapChanger(PhaseTapChanger phaseTapChanger) {
+ PhaseTapChangerAdder adder = this.newPhaseTapChanger()
+ .setRegulationTerminal(phaseTapChanger.getRegulationTerminal())
+ .setRegulationMode(phaseTapChanger.getRegulationMode())
+ .setRegulationValue(phaseTapChanger.getRegulationValue())
+ .setLowTapPosition(phaseTapChanger.getLowTapPosition())
+ .setTapPosition(phaseTapChanger.getTapPosition())
+ .setRegulating(phaseTapChanger.isRegulating())
+ .setTargetDeadband(phaseTapChanger.getTargetDeadband());
+ for (int tapPosition = phaseTapChanger.getLowTapPosition(); tapPosition <= phaseTapChanger.getHighTapPosition(); tapPosition++) {
+ PhaseTapChangerStep step = phaseTapChanger.getStep(tapPosition);
+ adder.beginStep()
+ .setAlpha(step.getAlpha())
+ .setRho(step.getRho())
+ .setB(step.getB())
+ .setG(step.getG())
+ .setX(step.getX())
+ .setR(step.getR())
+ .endStep();
+ }
+ return adder;
+ }
+
/**
* Get the phase tap changer.
*
Could return null if the transfomer is not associated to
@@ -36,7 +62,7 @@ default Optional getOptionalPhaseTapChanger() {
}
/**
- * Check if a phase tap changer is present
+ * Check if a phase tap changer is present
*/
default boolean hasPhaseTapChanger() {
return getPhaseTapChanger() != null;
diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/RatioTapChangerHolder.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/RatioTapChangerHolder.java
index ecbbbc280b2..117e87c4b95 100644
--- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/RatioTapChangerHolder.java
+++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/RatioTapChangerHolder.java
@@ -21,6 +21,34 @@ public interface RatioTapChangerHolder {
*/
RatioTapChangerAdder newRatioTapChanger();
+ /**
+ * Get a builder to create and associate a ratio tap changer to the
+ * transformer, initialized with the values of an existing ratio tap changer.
+ */
+ default RatioTapChangerAdder newRatioTapChanger(RatioTapChanger ratioTapChanger) {
+ RatioTapChangerAdder adder = this.newRatioTapChanger()
+ .setRegulationTerminal(ratioTapChanger.getRegulationTerminal())
+ .setRegulationMode(ratioTapChanger.getRegulationMode())
+ .setRegulationValue(ratioTapChanger.getRegulationValue())
+ .setLoadTapChangingCapabilities(ratioTapChanger.hasLoadTapChangingCapabilities())
+ .setTargetV(ratioTapChanger.getTargetV())
+ .setLowTapPosition(ratioTapChanger.getLowTapPosition())
+ .setTapPosition(ratioTapChanger.getTapPosition())
+ .setRegulating(ratioTapChanger.isRegulating())
+ .setTargetDeadband(ratioTapChanger.getTargetDeadband());
+ for (int tapPosition = ratioTapChanger.getLowTapPosition(); tapPosition <= ratioTapChanger.getHighTapPosition(); tapPosition++) {
+ RatioTapChangerStep step = ratioTapChanger.getStep(tapPosition);
+ adder.beginStep()
+ .setRho(step.getRho())
+ .setB(step.getB())
+ .setG(step.getG())
+ .setX(step.getX())
+ .setR(step.getR())
+ .endStep();
+ }
+ return adder;
+ }
+
/**
* Get the ratio tap changer.
*
Could return null if the leg is not associated to a ratio
diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ReactiveLimitsHolder.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ReactiveLimitsHolder.java
index 465209243ad..a98f45eb940 100644
--- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ReactiveLimitsHolder.java
+++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ReactiveLimitsHolder.java
@@ -30,4 +30,19 @@ public interface ReactiveLimitsHolder {
* to this generator.
*/
MinMaxReactiveLimitsAdder newMinMaxReactiveLimits();
+
+ /**
+ * Get a builder to create and associate a new reactive capability curve
+ * to this generator based on an existing curve.
+ */
+ default ReactiveCapabilityCurveAdder newReactiveCapabilityCurve(ReactiveCapabilityCurve copiedCurve) {
+ ReactiveCapabilityCurveAdder adder = newReactiveCapabilityCurve();
+ copiedCurve.getPoints().forEach(copiedPoint ->
+ adder.beginPoint()
+ .setP(copiedPoint.getP())
+ .setMinQ(copiedPoint.getMinQ())
+ .setMaxQ(copiedPoint.getMaxQ())
+ .endPoint());
+ return adder;
+ }
}
diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ShuntCompensatorNonLinearModelAdder.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ShuntCompensatorNonLinearModelAdder.java
index 727c80f492c..2a1420cd972 100644
--- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ShuntCompensatorNonLinearModelAdder.java
+++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ShuntCompensatorNonLinearModelAdder.java
@@ -20,7 +20,7 @@ interface SectionAdder {
SectionAdder setB(double b);
/***
- * Set the accumulated conductance is S when the section to be added is the last section in service.
+ * Set the accumulated conductance in S when the section to be added is the last section in service.
* If the accumulated conductance is undefined, its conductance per section is considered equal to 0:
* it is equal to the accumulated conductance of the previous section.
*/
diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java
index 47ab9a2cfd3..8da19d0c3cc 100644
--- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java
+++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java
@@ -11,6 +11,7 @@
import com.powsybl.math.graph.TraversalType;
import com.powsybl.math.graph.TraverseResult;
+import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
@@ -252,4 +253,11 @@ static Terminal getTerminal(Identifiable> identifiable, ThreeSides side) {
}
ThreeSides getSide();
+
+ /**
+ * Retrieves a list of objects that refer to the terminal.
+ *
+ * @return a list of referrer objects.
+ */
+ List