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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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: + * + * @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.powsybl powsybl-action-ial - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-action-ial - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-action-ial - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-action-ial - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom 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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-cgmes - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-cgmes - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-cgmes - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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 @@ 1 RK - - - 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.powsybl powsybl-cgmes - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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-cgmes com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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-cgmes com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT 4.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.powsybl powsybl-cgmes - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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-cgmes com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-cgmes - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-cgmes - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-contingency - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-contingency - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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 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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom 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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom 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-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT 4.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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-dynamic-simulation - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-dynamic-simulation - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-dynamic-simulation - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom 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.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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-cdf com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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.powsybl powsybl-ieee-cdf - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom 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.powsybl powsybl-iidm - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-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 getReferrers(); } diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/VoltageLevel.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/VoltageLevel.java index 053007a2326..f22d1d884d9 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/VoltageLevel.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/VoltageLevel.java @@ -1355,6 +1355,15 @@ default void remove() { */ BusView getBusView(); + /** + * Convert the topology model to another one. Notice that when converting from a + * node/breaker model to a bus/breaker model, we definitely lost some information as + * we are converting to a simpler topology model. + * + * @param newTopologyKind the new topology model kind + */ + void convertToTopology(TopologyKind newTopologyKind); + /** * Print an ASCII representation of the topology on the standard ouput. */ diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java index bf68d246b1f..7b90cfe67e1 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java @@ -86,6 +86,20 @@ public double getQ() { } } + @Override + public double getI() { + if (useHypothesis(parent)) { + return Math.hypot(getP(), getQ()) / (Math.sqrt(3.) * getV() / 1000); + } + Terminal t = parent.getTerminal(); + Bus b = t.getBusView().getBus(); + if (zeroImpedance(parent)) { + return t.getI(); + } else { + return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideI(parent, false); + } + } + @Override public DanglingLine getDanglingLine() { return parent; diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/SV.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/SV.java index fbecdf10d0e..3c9edfb461c 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/SV.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/SV.java @@ -68,6 +68,10 @@ public double getA() { return a; } + public double getI() { + return Math.hypot(p, q) / (Math.sqrt(3.) * u / 1000); + } + public TwoSides getSide() { return side; } @@ -162,6 +166,14 @@ public double otherSideA(DanglingLine dl, boolean splitShuntAdmittance) { return otherSide(dl, splitShuntAdmittance).getA(); } + public double otherSideI(DanglingLine dl) { + return otherSide(dl).getI(); + } + + public double otherSideI(DanglingLine dl, boolean splitShuntAdmittance) { + return otherSide(dl, splitShuntAdmittance).getI(); + } + private static double getRho(TwoWindingsTransformer twt) { double rho = twt.getRatedU2() / twt.getRatedU1(); if (twt.getRatioTapChanger() != null) { diff --git a/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/SVTest.java b/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/SVTest.java index 3cad5c657be..36ba07adf56 100644 --- a/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/SVTest.java +++ b/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/SVTest.java @@ -59,11 +59,13 @@ void testDanglingLine() { double q1 = -77.444122; double v1 = 118.13329315185547; double a1 = 0.19568365812301636; + double i1 = 726.224579; double p2 = 15.098317; double q2 = 64.333028; double v2 = 138.0; double a2 = 0.0; + double i2 = 276.462893; SV svA1 = new SV(p1, q1, v1, a1, TwoSides.ONE); SV svA2 = svA1.otherSide(dl); @@ -71,6 +73,7 @@ void testDanglingLine() { assertEquals(q2, svA2.getQ(), tol); assertEquals(v2, svA2.getU(), tol); assertEquals(a2, svA2.getA(), tol); + assertEquals(i2, svA2.getI(), tol); SV svB2 = new SV(p2, q2, v2, a2, TwoSides.TWO); SV svB1 = svB2.otherSide(dl); @@ -78,26 +81,31 @@ void testDanglingLine() { assertEquals(q1, svB1.getQ(), tol); assertEquals(v1, svB1.getU(), tol); assertEquals(a1, svB1.getA(), tol); + assertEquals(i1, svB1.getI(), tol); assertEquals(p2, svA1.otherSideP(dl), tol); assertEquals(q2, svA1.otherSideQ(dl), tol); assertEquals(v2, svA1.otherSideU(dl), tol); assertEquals(a2, svA1.otherSideA(dl), tol); + assertEquals(i2, svA1.otherSideI(dl), tol); assertEquals(p1, svB2.otherSideP(dl), tol); assertEquals(q1, svB2.otherSideQ(dl), tol); assertEquals(v1, svB2.otherSideU(dl), tol); assertEquals(a1, svB2.otherSideA(dl), tol); + assertEquals(i1, svB2.otherSideI(dl), tol); assertEquals(p2, svA1.otherSideP(dl, false), tol); assertEquals(q2, svA1.otherSideQ(dl, false), tol); assertEquals(v2, svA1.otherSideU(dl, false), tol); assertEquals(a2, svA1.otherSideA(dl, false), tol); + assertEquals(i2, svA1.otherSideI(dl, false), tol); assertEquals(p1, svB2.otherSideP(dl, false), tol); assertEquals(q1, svB2.otherSideQ(dl, false), tol); assertEquals(v1, svB2.otherSideU(dl, false), tol); assertEquals(a1, svB2.otherSideA(dl, false), tol); + assertEquals(i1, svB2.otherSideI(dl, false), tol); } @Test diff --git a/iidm/iidm-comparator/pom.xml b/iidm/iidm-comparator/pom.xml index 7047cc4cfb0..cac67b2fab4 100644 --- a/iidm/iidm-comparator/pom.xml +++ b/iidm/iidm-comparator/pom.xml @@ -15,7 +15,7 @@ powsybl-iidm com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-comparator diff --git a/iidm/iidm-criteria/pom.xml b/iidm/iidm-criteria/pom.xml index 226de290284..09b290b1a22 100644 --- a/iidm/iidm-criteria/pom.xml +++ b/iidm/iidm-criteria/pom.xml @@ -16,7 +16,7 @@ powsybl-iidm com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-criteria diff --git a/iidm/iidm-extensions/pom.xml b/iidm/iidm-extensions/pom.xml index 05f81fd3cb3..c0e4e164f6c 100644 --- a/iidm/iidm-extensions/pom.xml +++ b/iidm/iidm-extensions/pom.xml @@ -15,7 +15,7 @@ powsybl-iidm com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-extensions diff --git a/iidm/iidm-extensions/src/main/java/com/powsybl/iidm/network/extensions/RemoteReactivePowerControl.java b/iidm/iidm-extensions/src/main/java/com/powsybl/iidm/network/extensions/RemoteReactivePowerControl.java index 81ea14454a1..316b3d6b51b 100644 --- a/iidm/iidm-extensions/src/main/java/com/powsybl/iidm/network/extensions/RemoteReactivePowerControl.java +++ b/iidm/iidm-extensions/src/main/java/com/powsybl/iidm/network/extensions/RemoteReactivePowerControl.java @@ -43,4 +43,6 @@ default String getName() { RemoteReactivePowerControl setTargetQ(double targetQ); RemoteReactivePowerControl setEnabled(boolean enabled); + + RemoteReactivePowerControl setRegulatingTerminal(Terminal regulatingTerminals); } diff --git a/iidm/iidm-geodata/pom.xml b/iidm/iidm-geodata/pom.xml index e4c157b4c17..b76c01a1e45 100644 --- a/iidm/iidm-geodata/pom.xml +++ b/iidm/iidm-geodata/pom.xml @@ -14,7 +14,7 @@ powsybl-iidm com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-geodata diff --git a/iidm/iidm-impl/pom.xml b/iidm/iidm-impl/pom.xml index b94c47af2dd..c95d1f7ba9a 100644 --- a/iidm/iidm-impl/pom.xml +++ b/iidm/iidm-impl/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-iidm - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-impl diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java index 756f6be24a2..22032cb9d09 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java @@ -70,7 +70,7 @@ public void remove() { network.getIndex().remove(this); for (TerminalExt terminal : terminals) { - terminal.removeAsRegulationPoint(); + terminal.getReferrerManager().notifyOfRemoval(); VoltageLevelExt vl = terminal.getVoltageLevel(); vl.getTopologyModel().detach(terminal); } @@ -132,7 +132,7 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { } } - protected void move(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, int node, String voltageLevelId) { + protected void move(TerminalExt oldTerminal, int node, String voltageLevelId) { VoltageLevelExt voltageLevel = getNetwork().getVoltageLevel(voltageLevelId); if (voltageLevel == null) { throw new PowsyblException("Voltage level '" + voltageLevelId + "' not found"); @@ -152,10 +152,10 @@ protected void move(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, int .build(); // detach the terminal from its previous voltage level - attachTerminal(oldTerminal, oldTopologyPoint, voltageLevel, terminalExt); + replaceTerminal(oldTerminal, voltageLevel.getTopologyModel(), terminalExt, true); } - protected void move(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, String busId, boolean connected) { + protected void move(TerminalExt oldTerminal, String busId, boolean connected) { Bus bus = getNetwork().getBusBreakerView().getBus(busId); if (bus == null) { throw new PowsyblException("Bus '" + busId + "' not found"); @@ -175,22 +175,43 @@ protected void move(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, Str .build(); // detach the terminal from its previous voltage level - attachTerminal(oldTerminal, oldTopologyPoint, (VoltageLevelExt) bus.getVoltageLevel(), terminalExt); + replaceTerminal(oldTerminal, ((VoltageLevelExt) bus.getVoltageLevel()).getTopologyModel(), terminalExt, true); } - private void attachTerminal(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, VoltageLevelExt voltageLevel, TerminalExt terminalExt) { + void replaceTerminal(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, TerminalExt newTerminalExt, boolean notify) { + Objects.requireNonNull(oldTerminal); + Objects.requireNonNull(newTerminalExt); + int iSide = terminals.indexOf(oldTerminal); + if (iSide == -1) { + throw new PowsyblException("Terminal to replace not found"); + } + terminals.set(iSide, newTerminalExt); + + if (notify) { + notifyUpdate("terminal" + (iSide + 1), oldTopologyPoint, newTerminalExt.getTopologyPoint()); + } + } + + void replaceTerminal(TerminalExt oldTerminal, TopologyModel newTopologyModel, TerminalExt newTerminalExt, boolean notify) { + Objects.requireNonNull(oldTerminal); + Objects.requireNonNull(newTopologyModel); + Objects.requireNonNull(newTerminalExt); + // first, attach new terminal to connectable and to voltage level of destination, to ensure that the new terminal is valid - terminalExt.setConnectable(this); - voltageLevel.getTopologyModel().attach(terminalExt, false); + newTerminalExt.setConnectable(this); + newTopologyModel.attach(newTerminalExt, false); // then we can detach the old terminal, as we now know that the new terminal is valid + TopologyPoint oldTopologyPoint = oldTerminal.getTopologyPoint(); oldTerminal.getVoltageLevel().getTopologyModel().detach(oldTerminal); // replace the old terminal by the new terminal in the connectable - int iSide = terminals.indexOf(oldTerminal); - terminals.set(iSide, terminalExt); + replaceTerminal(oldTerminal, oldTopologyPoint, newTerminalExt, notify); - notifyUpdate("terminal" + (iSide + 1), oldTopologyPoint, terminalExt.getTopologyPoint()); + // also update terminal referrers + for (Referrer referrer : oldTerminal.getReferrerManager().getReferrers()) { + referrer.onReferencedReplacement(oldTerminal, newTerminalExt); + } } @Override diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java index 837269c396b..6a546dd4046 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java @@ -260,10 +260,17 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { @Override public > boolean removeExtension(Class type) { + return removeExtension(type, true); + } + + public > boolean removeExtension(Class type, boolean cleanup) { E extension = getExtension(type); NetworkListenerList listeners = getNetwork().getListeners(); if (extension != null) { listeners.notifyExtensionBeforeRemoval(extension); + if (cleanup) { + extension.cleanup(); + } removeExtension(type, extension); listeners.notifyExtensionAfterRemoval(this, extension.getName()); return true; diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java new file mode 100644 index 00000000000..6e0bdf483c0 --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java @@ -0,0 +1,33 @@ +/** + * 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.iidm.network.impl; + +import com.powsybl.commons.extensions.AbstractExtension; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Terminal; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public abstract class AbstractIidmExtension> extends AbstractExtension implements Referrer { + + protected AbstractIidmExtension(I extendable) { + super(extendable); + } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + // nothing by default + // this is the place for terminal reference cleanup + } + + @Override + public void onReferencedReplacement(Terminal oldReferenced, Terminal newReferenced) { + // nothing by default + } +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java index bbae95cfad4..aeb04fda519 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java @@ -8,14 +8,13 @@ package com.powsybl.iidm.network.impl; import com.powsybl.commons.exceptions.UncheckedClassCastExceptionException; -import com.powsybl.commons.extensions.AbstractExtension; import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; /** * @author Florian Dupuy {@literal } */ -public abstract class AbstractMultiVariantIdentifiableExtension> extends AbstractExtension implements MultiVariantObject { +public abstract class AbstractMultiVariantIdentifiableExtension> extends AbstractIidmExtension implements MultiVariantObject { public AbstractMultiVariantIdentifiableExtension(T extendable) { super(extendable); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java index a73725bf9ec..8614a6a3c50 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java @@ -104,7 +104,7 @@ protected static void transferExtensions(Network from, Network to, boolean ignor "an extension of this same class already exists in the destination network.", clazz.getName(), from.getId(), to.getId()); } else { - from.removeExtension((Class>) clazz); + ((AbstractIdentifiable) from).removeExtension((Class>) clazz, false); to.addExtension((Class>) clazz, (Extension) e); } }) diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java index d46a9b1a830..84d370a7c74 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java @@ -256,4 +256,8 @@ private void throwIncorrectTapPosition(int tapPosition, int highTapPosition) { + tapPosition + " [" + lowTapPosition + ", " + highTapPosition + "]"); } + + public void remove() { + regulatingPoint.remove(); + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java index 0f7d2f336e1..2e1d8ad0770 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java @@ -8,12 +8,11 @@ package com.powsybl.iidm.network.impl; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.*; import com.powsybl.commons.ref.Ref; +import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.util.SwitchPredicates; import gnu.trove.list.array.TDoubleArrayList; -import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; @@ -33,7 +32,7 @@ abstract class AbstractTerminal implements TerminalExt { protected VoltageLevelExt voltageLevel; - protected final List regulated = new ArrayList<>(); + protected final ReferrerManager referrerManager = new ReferrerManager<>(this); // attributes depending on the variant @@ -94,12 +93,6 @@ public void setVoltageLevel(VoltageLevelExt voltageLevel) { } } - @Override - public void removeAsRegulationPoint() { - regulated.forEach(RegulatingPoint::removeRegulatingTerminal); - regulated.clear(); - } - @Override public double getP() { if (removed) { @@ -250,12 +243,12 @@ public void remove() { } @Override - public void setAsRegulatingPoint(RegulatingPoint rp) { - regulated.add(rp); + public ReferrerManager getReferrerManager() { + return referrerManager; } @Override - public void removeRegulatingPoint(RegulatingPoint rp) { - regulated.remove(rp); + public List getReferrers() { + return referrerManager.getReferrers().stream().map(r -> (Object) r).toList(); } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaBoundaryImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaBoundaryImpl.java index 277bd3e6e7b..dc8fd5b1494 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaBoundaryImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaBoundaryImpl.java @@ -17,13 +17,13 @@ public class AreaBoundaryImpl implements AreaBoundary { - final Area area; + private final Area area; - final Terminal terminal; + private Terminal terminal; - final Boundary boundary; + private final Boundary boundary; - final boolean ac; + private final boolean ac; AreaBoundaryImpl(Area area, Terminal terminal, boolean ac) { this.area = Objects.requireNonNull(area); @@ -68,4 +68,12 @@ public double getP() { public double getQ() { return boundary != null ? boundary.getQ() : terminal.getQ(); } + + void replaceTerminal(Terminal oldTerminal, Terminal newTerminal) { + Objects.requireNonNull(oldTerminal); + Objects.requireNonNull(newTerminal); + if (terminal == oldTerminal) { + terminal = newTerminal; + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java index 89d18793155..4de343b8956 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java @@ -34,31 +34,31 @@ public class AreaImpl extends AbstractIdentifiable implements Area { private final TDoubleArrayList interchangeTarget; - private final class AreaListener extends DefaultNetworkListener { + private final Referrer terminalReferrer = new Referrer<>() { @Override - public void beforeRemoval(Identifiable identifiable) { - if (identifiable instanceof DanglingLine danglingLine) { - // if dangling line removed from network, remove its boundary from this extension - AreaImpl.this.removeAreaBoundary(danglingLine.getBoundary()); - } else if (identifiable instanceof Connectable connectable) { - // if connectable removed from network, remove its terminals from this extension - connectable.getTerminals().forEach(AreaImpl.this::removeAreaBoundary); + public void onReferencedRemoval(Terminal terminal) { + removeAreaBoundary(terminal); + } + + @Override + public void onReferencedReplacement(Terminal oldTerminal, Terminal newTerminal) { + for (AreaBoundary areaBoundary : areaBoundaries) { + ((AreaBoundaryImpl) areaBoundary).replaceTerminal(oldTerminal, newTerminal); } - // When a VoltageLevel is removed, its areas' voltageLevels attributes are directly updated. This is not managed via this listener. } - } + }; - /** - * Must be called whenever the Area is moved to a different root Network when merging and detaching. - * @param fromNetwork previous root network - * @param toNetwork new root network - */ - void moveListener(NetworkImpl fromNetwork, NetworkImpl toNetwork) { - fromNetwork.removeListener(this.areaListener); - toNetwork.addListener(this.areaListener); - } + private final Referrer boundaryReferrer = new Referrer<>() { + @Override + public void onReferencedRemoval(Boundary boundary) { + removeAreaBoundary(boundary); + } - private final NetworkListener areaListener; + @Override + public void onReferencedReplacement(Boundary oldBoundary, Boundary newBoundary) { + // cannot happen + } + }; AreaImpl(Ref ref, Ref subnetworkRef, String id, String name, boolean fictitious, String areaType, double interchangeTarget) { @@ -74,8 +74,6 @@ void moveListener(NetworkImpl fromNetwork, NetworkImpl toNetwork) { for (int i = 0; i < variantArraySize; i++) { this.interchangeTarget.add(interchangeTarget); } - this.areaListener = new AreaListener(); - getNetwork().addListener(this.areaListener); } @Override @@ -229,8 +227,14 @@ public Stream getAreaBoundaryStream() { protected void addAreaBoundary(AreaBoundaryImpl areaBoundary) { Optional terminal = areaBoundary.getTerminal(); Optional boundary = areaBoundary.getBoundary(); - boundary.ifPresent(b -> checkBoundaryNetwork(b.getDanglingLine().getParentNetwork(), "Boundary of DanglingLine" + b.getDanglingLine().getId())); - terminal.ifPresent(t -> checkBoundaryNetwork(t.getConnectable().getParentNetwork(), "Terminal of connectable " + t.getConnectable().getId())); + boundary.ifPresent(b -> { + checkBoundaryNetwork(b.getDanglingLine().getParentNetwork(), "Boundary of DanglingLine" + b.getDanglingLine().getId()); + ((DanglingLineBoundaryImplExt) b).getReferrerManager().register(boundaryReferrer); + }); + terminal.ifPresent(t -> { + checkBoundaryNetwork(t.getConnectable().getParentNetwork(), "Terminal of connectable " + t.getConnectable().getId()); + ((TerminalExt) t).getReferrerManager().register(terminalReferrer); + }); areaBoundaries.add(areaBoundary); } @@ -248,8 +252,11 @@ public void remove() { for (VoltageLevel voltageLevel : new HashSet<>(voltageLevels)) { voltageLevel.removeArea(this); } + for (AreaBoundary areaBoundary : areaBoundaries) { + areaBoundary.getTerminal().ifPresent(t -> ((TerminalExt) t).getReferrerManager().unregister(terminalReferrer)); + areaBoundary.getBoundary().ifPresent(b -> ((DanglingLineBoundaryImplExt) b).getReferrerManager().unregister(boundaryReferrer)); + } network.getListeners().notifyAfterRemoval(id); - network.removeListener(this.areaListener); removed = true; } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusBreakerTopologyModel.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusBreakerTopologyModel.java index 95d1a89dd97..884335d11de 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusBreakerTopologyModel.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusBreakerTopologyModel.java @@ -100,7 +100,8 @@ public Switch add() { } SwitchImpl aSwitch = new SwitchImpl(voltageLevel, id, getName(), isFictitious(), SwitchKind.BREAKER, open, true); - addSwitch(aSwitch, busId1, busId2); + getNetwork().getIndex().checkAndAdd(aSwitch); + addSwitchToTopology(aSwitch, busId1, busId2); getNetwork().getListeners().notifyCreation(aSwitch); return aSwitch; } @@ -797,10 +798,9 @@ private void removeAllBuses() { removedBusesIds.forEach(id -> network.getListeners().notifyAfterRemoval(id)); } - private void addSwitch(SwitchImpl aSwitch, String busId1, String busId2) { + void addSwitchToTopology(SwitchImpl aSwitch, String busId1, String busId2) { int v1 = getVertex(busId1, true); int v2 = getVertex(busId2, true); - getNetwork().getIndex().checkAndAdd(aSwitch); int e = graph.addEdge(v1, v2, aSwitch); switches.put(aSwitch.getId(), e); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusTerminal.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusTerminal.java index 383023ceea8..15509ef1642 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusTerminal.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusTerminal.java @@ -41,7 +41,7 @@ public void moveConnectable(int node, String voltageLevelId) { if (removed) { throw new PowsyblException(UNMODIFIABLE_REMOVED_EQUIPMENT + connectable.id); } - getConnectable().move(BusTerminal.this, getTopologyPoint(), node, voltageLevelId); + getConnectable().move(BusTerminal.this, node, voltageLevelId); } }; @@ -87,7 +87,7 @@ public void moveConnectable(String busId, boolean connected) { if (removed) { throw new PowsyblException(UNMODIFIABLE_REMOVED_EQUIPMENT + connectable.id); } - getConnectable().move(BusTerminal.this, getTopologyPoint(), busId, connected); + getConnectable().move(BusTerminal.this, busId, connected); } }; diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineBoundaryImplExt.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineBoundaryImplExt.java new file mode 100644 index 00000000000..46a0e144960 --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineBoundaryImplExt.java @@ -0,0 +1,32 @@ +/** + * 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.iidm.network.impl; + +import com.powsybl.iidm.network.Boundary; +import com.powsybl.iidm.network.DanglingLine; +import com.powsybl.iidm.network.util.DanglingLineBoundaryImpl; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class DanglingLineBoundaryImplExt extends DanglingLineBoundaryImpl { + + protected final ReferrerManager referrerManager = new ReferrerManager<>(this); + + public DanglingLineBoundaryImplExt(DanglingLine parent) { + super(parent); + } + + public ReferrerManager getReferrerManager() { + return referrerManager; + } + + void remove() { + referrerManager.notifyOfRemoval(); + } +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java index 567dfe44356..a92f8d6a8a9 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java @@ -10,7 +10,6 @@ import com.powsybl.commons.util.trove.TBooleanArrayList; import com.powsybl.iidm.network.*; import com.powsybl.commons.ref.Ref; -import com.powsybl.iidm.network.util.DanglingLineBoundaryImpl; import gnu.trove.list.array.TDoubleArrayList; import java.util.Collection; @@ -259,7 +258,7 @@ void allocateVariantArrayElement(int[] indexes, int sourceIndex) { private final TDoubleArrayList q0; - private final DanglingLineBoundaryImpl boundary; + private final DanglingLineBoundaryImplExt boundary; DanglingLineImpl(Ref network, String id, String name, boolean fictitious, double p0, double q0, double r, double x, double g, double b, String pairingKey, GenerationImpl generation) { super(network, id, name, fictitious); @@ -277,7 +276,7 @@ void allocateVariantArrayElement(int[] indexes, int sourceIndex) { this.b = b; this.pairingKey = pairingKey; this.operationalLimitsGroups = new OperationalLimitsGroupsImpl(this, "limits"); - this.boundary = new DanglingLineBoundaryImpl(this); + this.boundary = new DanglingLineBoundaryImplExt(this); this.generation = generation != null ? generation.attach(this) : null; } @@ -309,6 +308,7 @@ public void remove() { throw new UnsupportedOperationException("Parent tie line " + tieLine.getId() + " should be removed before the child dangling line"); } super.remove(); + boundary.remove(); } void removeTieLine() { diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java index 54afbc6133c..bac5fd0da46 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java @@ -127,7 +127,7 @@ public GeneratorImpl add() { = new GeneratorImpl(getNetworkRef(), id, getName(), isFictitious(), energySource, minP, maxP, - voltageRegulatorOn, regulatingTerminal != null ? regulatingTerminal : terminal, + voltageRegulatorOn, regulatingTerminal, targetP, targetQ, targetV, ratedS, isCondenser); generator.addTerminal(terminal); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java index a5ba5df417f..93a5b337afa 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java @@ -54,7 +54,7 @@ class GeneratorImpl extends AbstractConnectable implements Generator, this.reactiveLimits = new ReactiveLimitsHolderImpl(this, new MinMaxReactiveLimitsImpl(-Double.MAX_VALUE, Double.MAX_VALUE)); this.ratedS = ratedS; int variantArraySize = network.get().getVariantManager().getVariantArraySize(); - regulatingPoint = new RegulatingPoint(id, this::getTerminal, variantArraySize, voltageRegulatorOn, voltageRegulatorOn); + regulatingPoint = new RegulatingPoint(id, this::getTerminal, variantArraySize, voltageRegulatorOn, true); regulatingPoint.setRegulatingTerminal(regulatingTerminal); this.targetP = new TDoubleArrayList(variantArraySize); this.targetQ = new TDoubleArrayList(variantArraySize); @@ -129,7 +129,6 @@ public GeneratorImpl setVoltageRegulatorOn(boolean voltageRegulatorOn) { voltageRegulatorOn, targetV.get(variantIndex), targetQ.get(variantIndex), n.getMinValidationLevel(), n.getReportNodeContext().getReportNode()); boolean oldValue = regulatingPoint.setRegulating(variantIndex, voltageRegulatorOn); - regulatingPoint.setUseVoltageRegulation(voltageRegulatorOn); String variantId = network.get().getVariantManager().getVariantId(variantIndex); n.invalidateValidationLevel(); notifyUpdate("voltageRegulatorOn", variantId, oldValue, voltageRegulatorOn); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java index 344f3e246bf..31c25e9d926 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java @@ -1015,11 +1015,6 @@ private void merge(Network other) { checkMergeability(otherNetwork); - otherNetwork.getAreaStream().forEach(a -> { - AreaImpl area = (AreaImpl) a; - area.moveListener(otherNetwork, this); - }); - // try to find dangling lines couples List lines = new ArrayList<>(); Map> dl1byPairingKey = new HashMap<>(); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeBreakerTopologyModel.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeBreakerTopologyModel.java index e58a76de39c..93873b380a2 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeBreakerTopologyModel.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeBreakerTopologyModel.java @@ -263,15 +263,20 @@ private void traverse(int n, boolean[] encountered, Predicate termin if (!encountered[n]) { final TIntArrayList nodes = new TIntArrayList(1); nodes.add(n); - graph.traverse(n, TraversalType.DEPTH_FIRST, (n1, e, n2) -> { + Traverser traverser = (n1, e, n2) -> { SwitchImpl aSwitch = graph.getEdgeObject(e); if (aSwitch != null && terminate.test(aSwitch)) { return TraverseResult.TERMINATE_PATH; } - nodes.add(n2); + if (!encountered[n2]) { + // We need to check this as the traverser might be called twice with the same n2 but with different edges. + // Note that the "encountered" array is used and maintained inside graph::traverse method, hence we should not update it. + nodes.add(n2); + } return TraverseResult.CONTINUE; - }, encountered); + }; + graph.traverse(n, TraversalType.DEPTH_FIRST, traverser, encountered); // check that the component is a bus String busId = Identifiables.getUniqueId(NAMING_STRATEGY.getId(voltageLevel, nodes), getNetwork().getIndex()::contains); @@ -626,6 +631,16 @@ CalculatedBusTopology getCalculatedBusTopology() { return variants.get().calculatedBusTopology; } + void removeSwitchFromTopology(String switchId, boolean notify) { + Integer e = switches.get(switchId); + if (e == null) { + throw new PowsyblException("Switch '" + switchId + + "' not found in voltage level '" + voltageLevel.getId() + "'"); + } + graph.removeEdge(e, notify); + graph.removeIsolatedVertices(notify); + } + private final VoltageLevelExt.NodeBreakerViewExt nodeBreakerView = new VoltageLevelExt.NodeBreakerViewExt() { private final TIntObjectMap fictitiousP0ByNode = TCollections.synchronizedMap(new TIntObjectHashMap<>()); @@ -873,13 +888,7 @@ public int getSwitchCount() { @Override public void removeSwitch(String switchId) { - Integer e = switches.get(switchId); - if (e == null) { - throw new PowsyblException("Switch '" + switchId - + "' not found in voltage level '" + voltageLevel.getId() + "'"); - } - graph.removeEdge(e); - graph.removeIsolatedVertices(); + NodeBreakerTopologyModel.this.removeSwitchFromTopology(switchId, true); } @Override @@ -1276,8 +1285,8 @@ boolean getDisconnectingSwitches(Terminal terminal, Predicate paths = graph.findAllPaths(node, NodeBreakerTopologyModel::isBusbarSection, SwitchPredicates.IS_OPEN); + // find all paths starting from the current terminal to a terminal that does not contain an open switch + List paths = graph.findAllPaths(node, Objects::nonNull, SwitchPredicates.IS_OPEN); if (paths.isEmpty()) { return false; } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeTerminal.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeTerminal.java index e1acd93912c..da48badb779 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeTerminal.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeTerminal.java @@ -53,7 +53,7 @@ public void moveConnectable(int node, String voltageLevelId) { if (removed) { throw new PowsyblException(UNMODIFIABLE_REMOVED_EQUIPMENT + connectable.id); } - getConnectable().move(NodeTerminal.this, getTopologyPoint(), node, voltageLevelId); + getConnectable().move(NodeTerminal.this, node, voltageLevelId); } }; @@ -89,7 +89,7 @@ public void moveConnectable(String busId, boolean connected) { if (removed) { throw new PowsyblException(UNMODIFIABLE_REMOVED_EQUIPMENT + connectable.id); } - getConnectable().move(NodeTerminal.this, getTopologyPoint(), busId, connected); + getConnectable().move(NodeTerminal.this, busId, connected); } }; diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java index b658fd1aaa3..7aa502c70bf 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java @@ -131,7 +131,7 @@ public PhaseTapChangerImpl setRegulationTerminal(Terminal regulationTerminal) { @Override public void remove() { - regulatingPoint.remove(); + super.remove(); parent.setPhaseTapChanger(null); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java index 98843670406..7fbf01b53d4 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java @@ -180,7 +180,7 @@ public RatioTapChangerImpl setRegulationTerminal(Terminal regulationTerminal) { @Override public void remove() { - regulatingPoint.remove(); + super.remove(); parent.setRatioTapChanger(null); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java new file mode 100644 index 00000000000..88e9bd0003c --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java @@ -0,0 +1,33 @@ +/** + * 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.iidm.network.impl; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public interface Referrer { + + /** + * Called when a referenced object is removed because of a connectable removal. + * Implementations of this method should handle any required cleanup or updates + * necessary when the referenced object is no longer part of the network. + * + * @param removedReferenced The referenced that has been removed from the network. + */ + void onReferencedRemoval(T removedReferenced); + + /** + * Called when a referenced object is replaced with another one. Implementations of this method + * should handle any required updates or transfers necessary when the referenced object is + * replaced. + * + * @param oldReferenced The original referenced object that is being replaced. + * @param newReferenced The new referenced object that is taking the place of the old one. + */ + void onReferencedReplacement(T oldReferenced, T newReferenced); +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ReferrerManager.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ReferrerManager.java new file mode 100644 index 00000000000..e29931e9783 --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ReferrerManager.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.network.impl; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class ReferrerManager { + + private final T referenced; + + private final List> referrers = new CopyOnWriteArrayList<>(); + + public ReferrerManager(T referenced) { + this.referenced = Objects.requireNonNull(referenced); + } + + public List> getReferrers() { + return referrers; + } + + public void register(Referrer referrer) { + referrers.add(Objects.requireNonNull(referrer)); + } + + public void unregister(Referrer referrer) { + referrers.remove(Objects.requireNonNull(referrer)); + } + + public void notifyOfRemoval() { + for (Referrer referrer : referrers) { + referrer.onReferencedRemoval(referenced); + } + } +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java index 8ca037d0711..0301d5a0fef 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java @@ -10,24 +10,24 @@ import com.powsybl.commons.util.trove.TBooleanArrayList; import com.powsybl.iidm.network.Bus; import com.powsybl.iidm.network.StaticVarCompensator; +import com.powsybl.iidm.network.Terminal; import gnu.trove.list.array.TIntArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Objects; import java.util.function.Supplier; /** * @author Miora Vedelago {@literal } */ -class RegulatingPoint implements MultiVariantObject { +class RegulatingPoint implements MultiVariantObject, Referrer { private static final Logger LOG = LoggerFactory.getLogger(RegulatingPoint.class); private final String regulatedEquipmentId; private final Supplier localTerminalSupplier; - private boolean useVoltageRegulation; - private TerminalExt regulatingTerminal = null; + private final boolean useVoltageRegulation; + private TerminalExt regulatingTerminal; // attributes depending on the variant @@ -58,26 +58,23 @@ class RegulatingPoint implements MultiVariantObject { void setRegulatingTerminal(TerminalExt regulatingTerminal) { if (this.regulatingTerminal != null) { - this.regulatingTerminal.removeRegulatingPoint(this); + this.regulatingTerminal.getReferrerManager().unregister(this); + this.regulatingTerminal = null; } - this.regulatingTerminal = regulatingTerminal != null ? regulatingTerminal : localTerminalSupplier.get(); - if (this.regulatingTerminal != null) { - this.regulatingTerminal.setAsRegulatingPoint(this); + if (regulatingTerminal != null) { + this.regulatingTerminal = regulatingTerminal; + this.regulatingTerminal.getReferrerManager().register(this); } } TerminalExt getRegulatingTerminal() { - return regulatingTerminal; + return regulatingTerminal != null ? regulatingTerminal : localTerminalSupplier.get(); } boolean setRegulating(int index, boolean regulating) { return this.regulating.set(index, regulating); } - void setUseVoltageRegulation(boolean useVoltageRegulation) { - this.useVoltageRegulation = useVoltageRegulation; - } - boolean isRegulating(int index) { return regulating.get(index); } @@ -90,35 +87,6 @@ int getRegulationMode(int index) { return regulationMode.get(index); } - void removeRegulatingTerminal() { - Objects.requireNonNull(regulatingTerminal); - TerminalExt localTerminal = localTerminalSupplier.get(); - if (localTerminal != null && useVoltageRegulation) { // if local voltage regulation, we keep the regulating status, and re-locate the regulation at the regulated equipment - Bus bus = regulatingTerminal.getBusView().getBus(); - Bus localBus = localTerminal.getBusView().getBus(); - if (bus != null && bus == localBus) { - LOG.warn("Connectable {} was a local voltage regulation point for {}. Regulation point is re-located at {}.", regulatingTerminal.getConnectable().getId(), - regulatedEquipmentId, regulatedEquipmentId); - regulatingTerminal = localTerminal; - return; - } - } - LOG.warn("Connectable {} was a regulation point for {}. Regulation is deactivated", regulatingTerminal.getConnectable().getId(), regulatedEquipmentId); - regulatingTerminal = localTerminal; - if (regulating != null) { - regulating.fill(0, regulating.size(), false); - } - if (regulationMode != null) { - regulationMode.fill(0, regulationMode.size(), StaticVarCompensator.RegulationMode.OFF.ordinal()); - } - } - - void remove() { - if (regulatingTerminal != null) { - regulatingTerminal.removeRegulatingPoint(this); - } - } - @Override public void extendVariantArraySize(int initVariantArraySize, int number, int sourceIndex) { if (regulating != null) { @@ -163,4 +131,44 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { } } } + + void remove() { + if (regulatingTerminal != null) { + regulatingTerminal.getReferrerManager().unregister(this); + } + } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + TerminalExt oldRegulatingTerminal = regulatingTerminal; + TerminalExt localTerminal = localTerminalSupplier.get(); + if (localTerminal != null && useVoltageRegulation) { // if local voltage regulation, we keep the regulating status, and re-locate the regulation at the regulated equipment + Bus bus = regulatingTerminal.getBusView().getBus(); + Bus localBus = localTerminal.getBusView().getBus(); + if (bus != null && bus == localBus) { + LOG.warn("Connectable {} was a local voltage regulation point for {}. Regulation point is re-located at {}.", regulatingTerminal.getConnectable().getId(), + regulatedEquipmentId, regulatedEquipmentId); + regulatingTerminal = localTerminal; + return; + } else { + regulatingTerminal = null; + } + } else { + regulatingTerminal = null; + } + LOG.warn("Connectable {} was a regulation point for {}. Regulation is deactivated", oldRegulatingTerminal.getConnectable().getId(), regulatedEquipmentId); + if (regulating != null) { + regulating.fill(0, regulating.size(), false); + } + if (regulationMode != null) { + regulationMode.fill(0, regulationMode.size(), StaticVarCompensator.RegulationMode.OFF.ordinal()); + } + } + + @Override + public void onReferencedReplacement(Terminal oldReferenced, Terminal newReferenced) { + if (regulatingTerminal == oldReferenced) { + regulatingTerminal = (TerminalExt) newReferenced; + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java index a83a21c13c4..dd059cab2dd 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java @@ -220,8 +220,7 @@ public ShuntCompensatorImpl add() { network.getMinValidationLevel(), network.getReportNodeContext().getReportNode())); ShuntCompensatorImpl shunt = new ShuntCompensatorImpl(getNetworkRef(), - id, getName(), isFictitious(), modelBuilder.build(), sectionCount, - regulatingTerminal == null ? terminal : regulatingTerminal, + id, getName(), isFictitious(), modelBuilder.build(), sectionCount, regulatingTerminal, voltageRegulatorOn, targetV, targetDeadband); shunt.addTerminal(terminal); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java index af6a95bbb75..5e3a4a7566a 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java @@ -88,8 +88,7 @@ public StaticVarCompensatorImpl add() { network.setValidationLevelIfGreaterThan(ValidationUtil.checkSvcRegulator(this, voltageSetpoint, reactivePowerSetpoint, regulationMode, network.getMinValidationLevel(), network.getReportNodeContext().getReportNode())); StaticVarCompensatorImpl svc = new StaticVarCompensatorImpl(id, name, isFictitious(), bMin, bMax, voltageSetpoint, reactivePowerSetpoint, - regulationMode, regulatingTerminal != null ? regulatingTerminal : terminal, - getNetworkRef()); + regulationMode, regulatingTerminal, getNetworkRef()); svc.addTerminal(terminal); voltageLevel.getTopologyModel().attach(terminal, false); network.getIndex().checkAndAdd(svc); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java index c5835250164..a99f31b2456 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java @@ -136,7 +136,6 @@ public StaticVarCompensatorImpl setRegulationMode(RegulationMode regulationMode) int variantIndex = n.getVariantIndex(); int oldValueOrdinal = regulatingPoint.setRegulationMode(variantIndex, regulationMode != null ? regulationMode.ordinal() : -1); - regulatingPoint.setUseVoltageRegulation(regulationMode == RegulationMode.VOLTAGE); String variantId = n.getVariantManager().getVariantId(variantIndex); n.invalidateValidationLevel(); notifyUpdate("regulationMode", variantId, oldValueOrdinal == -1 ? null : RegulationMode.values()[oldValueOrdinal], regulationMode); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java index 04dad01f24b..52c0cfa5eb5 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java @@ -837,11 +837,6 @@ public Network detach() { detachedNetwork.getVoltageAngleLimitsIndex().put(val.getId(), val); } - detachedNetwork.getAreaStream().forEach(a -> { - AreaImpl area = (AreaImpl) a; - area.moveListener(previousRootNetwork, detachedNetwork); - }); - // We don't control that regulating terminals and phase/ratio regulation terminals are in the same subnetwork // as their network elements (generators, PSTs, ...). It is unlikely that those terminals and their elements // are in different subnetworks but nothing prevents it. For now, we ignore this case, but it may be necessary diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java index 43569f81dad..bbc76742b33 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java @@ -18,7 +18,7 @@ * * @author Geoffroy Jamgotchian {@literal } */ -interface TerminalExt extends Terminal, MultiVariantObject { +public interface TerminalExt extends Terminal, MultiVariantObject { interface BusBreakerViewExt extends BusBreakerView { @@ -55,11 +55,7 @@ interface BusViewExt extends BusView { TopologyPoint getTopologyPoint(); - void removeAsRegulationPoint(); - void remove(); - void setAsRegulatingPoint(RegulatingPoint rp); - - void removeRegulatingPoint(RegulatingPoint rp); + ReferrerManager getReferrerManager(); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java index 3b488a909ae..00a5b76e107 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java @@ -285,4 +285,15 @@ public String getTapChangerAttribute() { protected String getTypeDescription() { return "2 windings transformer"; } + + @Override + public void remove() { + if (ratioTapChanger != null) { + ratioTapChanger.remove(); + } + if (phaseTapChanger != null) { + phaseTapChanger.remove(); + } + super.remove(); + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VoltageLevelImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VoltageLevelImpl.java index 7fc379bc153..88e2c5b3936 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VoltageLevelImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VoltageLevelImpl.java @@ -10,8 +10,8 @@ import com.google.common.collect.Lists; import com.google.common.primitives.Ints; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.*; import com.powsybl.commons.ref.Ref; +import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.util.ShortIdDictionary; import java.io.IOException; @@ -39,7 +39,7 @@ class VoltageLevelImpl extends AbstractIdentifiable implements Vol private double highVoltageLimit; - private final AbstractTopologyModel topologyModel; + private AbstractTopologyModel topologyModel; /** Areas associated to this VoltageLevel, with at most one area for each area type */ private final Set areas = new LinkedHashSet<>(); @@ -618,4 +618,83 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { super.allocateVariantArrayElement(indexes, sourceIndex); topologyModel.allocateVariantArrayElement(indexes, sourceIndex); } + + private void convertToBusBreakerModel() { + BusBreakerTopologyModel newTopologyModel = new BusBreakerTopologyModel(this); + + // remove busbar sections because not needed in a bus/breaker topology + for (BusbarSection bbs : topologyModel.getNodeBreakerView().getBusbarSections()) { + bbs.remove(); + } + + for (Bus bus : topologyModel.getBusBreakerView().getBuses()) { + // no notification, this is just a mutation of a calculation bus to a configured bus + ConfiguredBusImpl configuredBus = new ConfiguredBusImpl(bus.getId(), bus.getOptionalName().orElse(null), bus.isFictitious(), this); + newTopologyModel.addBus(configuredBus); + } + + // transfer retained switches + for (Switch sw : topologyModel.getBusBreakerView().getSwitchStream().toList()) { + String busId1 = topologyModel.getBusBreakerView().getBus1(sw.getId()).getId(); + String busId2 = topologyModel.getBusBreakerView().getBus2(sw.getId()).getId(); + // no notification, this is just a transfer + ((NodeBreakerTopologyModel) topologyModel).removeSwitchFromTopology(sw.getId(), false); + newTopologyModel.addSwitchToTopology((SwitchImpl) sw, busId1, busId2); + } + + // reconnect all connectable to new topology model + // first store all bus/breaker topological infos associated to this terminal because we will start moving + // terminal from old mode to new one, it will modify the old topology model + record TopologyModelInfos(TerminalExt terminal, String connectableBusId, boolean connected) { + } + List oldTopologyModelInfos = new ArrayList<>(); + for (Terminal oldTerminal : topologyModel.getTerminals()) { + Bus connectableBus = oldTerminal.getBusBreakerView().getConnectableBus(); + String connectableBusId = connectableBus == null ? null : connectableBus.getId(); + boolean connected = oldTerminal.isConnected(); + oldTopologyModelInfos.add(new TopologyModelInfos((TerminalExt) oldTerminal, connectableBusId, connected)); + } + + for (var infos : oldTopologyModelInfos) { + TerminalExt oldTerminalExt = infos.terminal(); + + // if there is no way to find a connectable bus, remove the connectable + // an alternative would be to connect them all to a new trash configured bus + if (infos.connectableBusId() == null) { + // here keep the removal notification + oldTerminalExt.getConnectable().remove(); + continue; + } + + AbstractConnectable connectable = oldTerminalExt.getConnectable(); + + // create the new terminal with new type + TerminalExt newTerminalExt = new TerminalBuilder(networkRef, this, oldTerminalExt.getSide()) + .setBus(infos.connected ? infos.connectableBusId() : null) + .setConnectableBus(infos.connectableBusId()) + .build(); + + connectable.replaceTerminal(oldTerminalExt, newTopologyModel, newTerminalExt, false); + } + + // also here keep the notification for remaining switches removal + topologyModel.removeTopology(); + + TopologyKind oldTopologyKind = topologyModel.getTopologyKind(); + topologyModel = newTopologyModel; + + notifyUpdate("topologyKind", oldTopologyKind, TopologyKind.BUS_BREAKER); + } + + @Override + public void convertToTopology(TopologyKind newTopologyKind) { + Objects.requireNonNull(newTopologyKind); + if (newTopologyKind != topologyModel.getTopologyKind()) { + if (newTopologyKind == TopologyKind.NODE_BREAKER) { + throw new PowsyblException("Topology model conversion from bus/breaker to node/breaker not yet supported"); + } else if (newTopologyKind == TopologyKind.BUS_BREAKER) { + convertToBusBreakerModel(); + } + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java index 3c361ab1cdf..a81738f74c6 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java @@ -71,7 +71,7 @@ public VscConverterStationImpl add() { validate(); VscConverterStationImpl converterStation = new VscConverterStationImpl(id, name, isFictitious(), getLossFactor(), getNetworkRef(), - voltageRegulatorOn, reactivePowerSetpoint, voltageSetpoint, regulatingTerminal == null ? terminal : regulatingTerminal); + voltageRegulatorOn, reactivePowerSetpoint, voltageSetpoint, regulatingTerminal); converterStation.addTerminal(terminal); getVoltageLevel().getTopologyModel().attach(terminal, false); network.getIndex().checkAndAdd(converterStation); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java index c2a1704d4f4..166b28678c6 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java @@ -36,7 +36,7 @@ class VscConverterStationImpl extends AbstractHvdcConverterStation implements ReferenceTerminals { - private final class ReferenceTerminalsListener extends DefaultNetworkListener { - @Override - public void beforeRemoval(Identifiable identifiable) { - if (identifiable instanceof Connectable connectable) { - // if connectable removed from network, remove its terminals from this extension - terminalsPerVariant.forEach(referenceTerminals -> connectable.getTerminals().forEach(referenceTerminals::remove)); - } - } - } - - private final NetworkListener referenceTerminalsListener; private final ArrayList> terminalsPerVariant; public ReferenceTerminalsImpl(Network network, Set terminals) { @@ -38,22 +29,51 @@ public ReferenceTerminalsImpl(Network network, Set terminals) { this.terminalsPerVariant = new ArrayList<>( Collections.nCopies(getVariantManagerHolder().getVariantManager().getVariantArraySize(), new LinkedHashSet<>())); setReferenceTerminals(terminals); - this.referenceTerminalsListener = new ReferenceTerminalsListener(); } - @Override - public void setExtendable(Network extendable) { - super.setExtendable(extendable); - if (extendable != null) { - // Add the listener, this will be done both extension creation, but also on extension transfer when merging and detaching. - extendable.getNetwork().addListener(this.referenceTerminalsListener); + private void unregisterReferencedTerminalIfNeeded(int variantIndex) { + // check there is no more same terminal referenced by any variant, unregister it + Set oldTerminals = terminalsPerVariant.get(variantIndex); + for (Terminal oldTerminal : oldTerminals) { + if (terminalsPerVariant.stream() + .flatMap(Collection::stream) + .filter(t -> t == oldTerminal) + .count() == 1) { + ((TerminalExt) oldTerminal).getReferrerManager().unregister(this); + } } } - @Override - protected void cleanup() { - // when extension removed from extendable, remove the listener. This will happen when merging and detaching. - getExtendable().getNetwork().removeListener(this.referenceTerminalsListener); + private void registerReferencedTerminalIfNeeded(Set terminals) { + // if terminal was not already referenced by another variant, register it + for (Terminal terminal : terminals) { + if (terminalsPerVariant.stream() + .flatMap(Collection::stream) + .noneMatch(t -> t == terminal)) { + ((TerminalExt) terminal).getReferrerManager().register(this); + } + } + } + + private void setTerminalsAndUpdateReferences(int variantIndex, Set terminals) { + unregisterReferencedTerminalIfNeeded(variantIndex); + registerReferencedTerminalIfNeeded(terminals); + terminalsPerVariant.set(variantIndex, new LinkedHashSet<>(terminals)); + } + + private void addTerminalsAndUpdateReferences(Set terminals) { + registerReferencedTerminalIfNeeded(terminals); + terminalsPerVariant.add(new LinkedHashSet<>(terminals)); + } + + private void updateTerminalsAndUpdateReferences(int variantIndex, Terminal terminal) { + registerReferencedTerminalIfNeeded(Set.of(terminal)); + terminalsPerVariant.get(variantIndex).add(terminal); + } + + private void removeTerminalsAndUpdateReferences(int variantIndex) { + unregisterReferencedTerminalIfNeeded(variantIndex); + terminalsPerVariant.remove(variantIndex); // remove elements from the top to avoid moves inside the array } @Override @@ -65,12 +85,12 @@ public Set getReferenceTerminals() { public void setReferenceTerminals(Set terminals) { Objects.requireNonNull(terminals); terminals.forEach(t -> checkTerminalInNetwork(t, getExtendable())); - terminalsPerVariant.set(getVariantIndex(), new LinkedHashSet<>(terminals)); + setTerminalsAndUpdateReferences(getVariantIndex(), terminals); } @Override public ReferenceTerminals reset() { - terminalsPerVariant.set(getVariantIndex(), new LinkedHashSet<>()); + setTerminalsAndUpdateReferences(getVariantIndex(), Collections.emptySet()); return this; } @@ -78,7 +98,7 @@ public ReferenceTerminals reset() { public ReferenceTerminals addReferenceTerminal(Terminal terminal) { Objects.requireNonNull(terminal); checkTerminalInNetwork(terminal, getExtendable()); - terminalsPerVariant.get(getVariantIndex()).add(terminal); + updateTerminalsAndUpdateReferences(getVariantIndex(), terminal); return this; } @@ -87,27 +107,27 @@ public void extendVariantArraySize(int initVariantArraySize, int number, int sou terminalsPerVariant.ensureCapacity(terminalsPerVariant.size() + number); Set sourceTerminals = terminalsPerVariant.get(sourceIndex); for (int i = 0; i < number; ++i) { - terminalsPerVariant.add(new LinkedHashSet<>(sourceTerminals)); + addTerminalsAndUpdateReferences(sourceTerminals); } } @Override public void reduceVariantArraySize(int number) { for (int i = 0; i < number; i++) { - terminalsPerVariant.remove(terminalsPerVariant.size() - 1); // remove elements from the top to avoid moves inside the array + removeTerminalsAndUpdateReferences(terminalsPerVariant.size() - 1); // remove elements from the top to avoid moves inside the array } } @Override public void deleteVariantArrayElement(int index) { - terminalsPerVariant.set(index, new LinkedHashSet<>()); + setTerminalsAndUpdateReferences(index, Collections.emptySet()); } @Override public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { Set sourceTerminals = terminalsPerVariant.get(sourceIndex); for (int index : indexes) { - terminalsPerVariant.set(index, new LinkedHashSet<>(sourceTerminals)); + setTerminalsAndUpdateReferences(index, sourceTerminals); } } @@ -127,4 +147,20 @@ private static void checkTerminalInNetwork(Terminal terminal, Network network) { } } } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + for (Set terminals : terminalsPerVariant) { + terminals.remove(removedTerminal); + } + } + + @Override + public void cleanup() { + for (Set terminals : terminalsPerVariant) { + for (Terminal terminal : terminals) { + ((TerminalExt) terminal).getReferrerManager().unregister(this); + } + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java index fe80f5e384b..cb840592d9a 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java @@ -7,6 +7,7 @@ */ package com.powsybl.iidm.network.impl.extensions; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.extensions.AbstractExtensionAdder; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Terminal; @@ -18,11 +19,11 @@ */ public class RemoteReactivePowerControlAdderImpl extends AbstractExtensionAdder implements RemoteReactivePowerControlAdder { - private double targetQ; + private double targetQ = Double.NaN; private Terminal regulatingTerminal; - private boolean enabled; + private boolean enabled = true; protected RemoteReactivePowerControlAdderImpl(final Generator extendable) { super(extendable); @@ -30,6 +31,12 @@ protected RemoteReactivePowerControlAdderImpl(final Generator extendable) { @Override protected RemoteReactivePowerControl createExtension(final Generator extendable) { + if (Double.isNaN(targetQ)) { + throw new PowsyblException("Reactive power target must be set"); + } + if (regulatingTerminal == null) { + throw new PowsyblException("Regulating terminal must be set"); + } return new RemoteReactivePowerControlImpl(extendable, targetQ, regulatingTerminal, enabled); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java index d73f773d934..f3c7a3c5663 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java @@ -7,34 +7,49 @@ */ package com.powsybl.iidm.network.impl.extensions; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.util.trove.TBooleanArrayList; import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.Terminal; import com.powsybl.iidm.network.extensions.RemoteReactivePowerControl; import com.powsybl.iidm.network.impl.AbstractMultiVariantIdentifiableExtension; +import com.powsybl.iidm.network.impl.TerminalExt; import gnu.trove.list.array.TDoubleArrayList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; /** * @author Bertrand Rix {@literal } */ public class RemoteReactivePowerControlImpl extends AbstractMultiVariantIdentifiableExtension implements RemoteReactivePowerControl { - private TDoubleArrayList targetQ; + private static final Logger LOGGER = LoggerFactory.getLogger(RemoteReactivePowerControlImpl.class); + + private final TDoubleArrayList targetQ; - private final Terminal regulatingTerminal; + private Terminal regulatingTerminal; - private TBooleanArrayList enabled; + private final TBooleanArrayList enabled; public RemoteReactivePowerControlImpl(Generator generator, double targetQ, Terminal regulatingTerminal, boolean enabled) { super(generator); int variantArraySize = getVariantManagerHolder().getVariantManager().getVariantArraySize(); this.targetQ = new TDoubleArrayList(); - this.regulatingTerminal = regulatingTerminal; + this.regulatingTerminal = Objects.requireNonNull(regulatingTerminal); this.enabled = new TBooleanArrayList(variantArraySize); for (int i = 0; i < variantArraySize; i++) { this.targetQ.add(targetQ); this.enabled.add(enabled); } + if (regulatingTerminal.getVoltageLevel().getParentNetwork() != getExtendable().getParentNetwork()) { + throw new PowsyblException("Regulating terminal is not in the right Network (" + + regulatingTerminal.getVoltageLevel().getParentNetwork().getId() + " instead of " + + getExtendable().getParentNetwork().getId() + ")"); + } + ((TerminalExt) regulatingTerminal).getReferrerManager().register(this); } @Override @@ -59,6 +74,20 @@ public Terminal getRegulatingTerminal() { return regulatingTerminal; } + @Override + public RemoteReactivePowerControl setRegulatingTerminal(Terminal regulatingTerminal) { + Objects.requireNonNull(regulatingTerminal); + checkRegulatingTerminal(regulatingTerminal, getExtendable().getTerminal().getVoltageLevel().getNetwork()); + this.regulatingTerminal = regulatingTerminal; + return this; + } + + private static void checkRegulatingTerminal(Terminal regulatingTerminal, Network network) { + if (regulatingTerminal != null && regulatingTerminal.getVoltageLevel().getNetwork() != network) { + throw new PowsyblException("regulating terminal is not part of the same network"); + } + } + @Override public boolean isEnabled() { return enabled.get(getVariantIndex()); @@ -92,4 +121,28 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { enabled.set(index, enabled.get(sourceIndex)); } } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + // we cannot set regulating terminal to null because otherwise extension won't be consistent anymore + // we cannot also as for voltage regulation fallback to a local terminal + // so we just remove the extension + LOGGER.warn("Remove 'RemoteReactivePowerControl' extension of generator '{}', because its regulating terminal has been removed", + getExtendable().getId()); + getExtendable().removeExtension(RemoteReactivePowerControl.class); + } + + @Override + public void onReferencedReplacement(Terminal oldReferenced, Terminal newReferenced) { + if (regulatingTerminal == oldReferenced) { + regulatingTerminal = newReferenced; + } + } + + @Override + public void cleanup() { + if (regulatingTerminal != null) { + ((TerminalExt) regulatingTerminal).getReferrerManager().unregister(this); + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java index 17b41c7e93c..e072f714070 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java @@ -12,6 +12,7 @@ import com.powsybl.iidm.network.VoltageLevel; import com.powsybl.iidm.network.extensions.SlackTerminal; import com.powsybl.iidm.network.impl.AbstractMultiVariantIdentifiableExtension; +import com.powsybl.iidm.network.impl.TerminalExt; import java.util.ArrayList; import java.util.Collections; @@ -31,6 +32,37 @@ public class SlackTerminalImpl extends AbstractMultiVariantIdentifiableExtension this.setTerminal(terminal); } + private void unregisterReferencedTerminalIfNeeded(int variantIndex) { + // check there is no more same terminal referenced by any variant, unregister it + Terminal oldTerminal = terminals.get(variantIndex); + if (oldTerminal != null && Collections.frequency(terminals, oldTerminal) == 1) { + ((TerminalExt) oldTerminal).getReferrerManager().unregister(this); + } + } + + private void registerReferencedTerminalIfNeeded(Terminal terminal) { + // if terminal was not already referenced by another variant, register it + if (terminal != null && !terminals.contains(terminal)) { + ((TerminalExt) terminal).getReferrerManager().register(this); + } + } + + private void setTerminalAndUpdateReferences(int variantIndex, Terminal terminal) { + unregisterReferencedTerminalIfNeeded(variantIndex); + registerReferencedTerminalIfNeeded(terminal); + terminals.set(variantIndex, terminal); + } + + private void addTerminalAndUpdateReferences(Terminal terminal) { + registerReferencedTerminalIfNeeded(terminal); + terminals.add(terminal); + } + + private void removeTerminalAndUpdateReferences(int variantIndex) { + unregisterReferencedTerminalIfNeeded(variantIndex); + terminals.remove(variantIndex); + } + @Override public Terminal getTerminal() { return terminals.get(getVariantIndex()); @@ -42,7 +74,7 @@ public SlackTerminal setTerminal(Terminal terminal) { throw new PowsyblException("Terminal given is not in the right VoltageLevel (" + terminal.getVoltageLevel().getId() + " instead of " + getExtendable().getId() + ")"); } - terminals.set(getVariantIndex(), terminal); + setTerminalAndUpdateReferences(getVariantIndex(), terminal); return this; } @@ -56,27 +88,49 @@ public void extendVariantArraySize(int initVariantArraySize, int number, int sou terminals.ensureCapacity(terminals.size() + number); Terminal sourceTerminal = terminals.get(sourceIndex); for (int i = 0; i < number; ++i) { - terminals.add(sourceTerminal); + addTerminalAndUpdateReferences(sourceTerminal); } } @Override public void reduceVariantArraySize(int number) { for (int i = 0; i < number; i++) { - terminals.remove(terminals.size() - 1); // remove elements from the top to avoid moves inside the array + removeTerminalAndUpdateReferences(terminals.size() - 1); // remove elements from the top to avoid moves inside the array } } @Override public void deleteVariantArrayElement(int index) { - terminals.set(index, null); + setTerminalAndUpdateReferences(index, null); } @Override public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { Terminal terminalSource = terminals.get(sourceIndex); for (int index : indexes) { - terminals.set(index, terminalSource); + setTerminalAndUpdateReferences(index, terminalSource); + } + } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + int variantIndex = terminals.indexOf(removedTerminal); + if (variantIndex != -1) { + terminals.set(variantIndex, null); + } + } + + @Override + public void onReferencedReplacement(Terminal oldReferenced, Terminal newReferenced) { + terminals.replaceAll(t -> t == oldReferenced ? newReferenced : t); + } + + @Override + public void cleanup() { + for (Terminal terminal : terminals) { + if (terminal != null) { + ((TerminalExt) terminal).getReferrerManager().unregister(this); + } } } } diff --git a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/TieLineTest.java b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/TieLineTest.java index 83d8fd3c582..7dd6764165c 100644 --- a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/TieLineTest.java +++ b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/TieLineTest.java @@ -57,12 +57,14 @@ void tieLineTest0() { assertTrue(compare(caseSv0.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv0.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv0.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv0.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv0, initialModelCase(TwoSides.TWO, TwoSides.ONE), TwoSides.ONE); assertTrue(compare(caseSv0.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv0.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv0.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv0.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv0.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI())); } @Test @@ -92,12 +94,14 @@ void tieLineTest1() { assertTrue(compare(caseSv1.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv1.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv1.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv1.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv1, initialModelCase(TwoSides.TWO, TwoSides.TWO), TwoSides.TWO); assertTrue(compare(caseSv1.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv1.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv1.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv1.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv1.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI())); } @Test @@ -127,12 +131,14 @@ void tieLineTest2() { assertTrue(compare(caseSv2.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv2.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv2.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv2.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv2, initialModelCase(TwoSides.ONE, TwoSides.ONE), TwoSides.ONE); assertTrue(compare(caseSv2.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv2.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv2.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv2.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv2.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI())); } @Test @@ -162,12 +168,14 @@ void tieLineTest3() { assertTrue(compare(caseSv3.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv3.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv3.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv3.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv3, initialModelCase(TwoSides.ONE, TwoSides.TWO), TwoSides.TWO); assertTrue(compare(caseSv3.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv3.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv3.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv3.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv3.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI())); } @Test @@ -197,12 +205,51 @@ void tieLineWithDifferentNominalVoltageAtEndsTest() { assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.ONE); assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); + } + + @Test + void tieLineWithNaNVoltagesTest() { + + // Line1 from node1 to boundaryNode, Line2 from boundaryNode to node2 + CaseSv caseSv = createCaseNaNVoltages(); + Network n = createNetworkWithTieLineWithDifferentNominalVoltageAtEnds(NetworkFactory.findDefault(), TwoSides.TWO, TwoSides.ONE, caseSv); + TieLine tieLine = n.getTieLine("TWO + ONE"); + + SV sv2 = new SV(tieLine.getDanglingLine1().getTerminal().getP(), tieLine.getDanglingLine1().getTerminal().getQ(), + tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(), + tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(), + TwoSides.ONE).otherSide(tieLine); + SV isv2 = initialSv2(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE); + assertTrue(compare(sv2, caseSv.node2, caseSv.line2, TwoSides.ONE, isv2)); + + SV sv1 = new SV(tieLine.getDanglingLine2().getTerminal().getP(), tieLine.getDanglingLine2().getTerminal().getQ(), + tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getV(), + tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getAngle(), + TwoSides.TWO).otherSide(tieLine); + SV isv1 = initialSv1(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE); + assertTrue(compare(sv1, caseSv.node1, caseSv.line1, TwoSides.TWO, isv1)); + + SV isvHalf1 = initialHalf1SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO); + assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine1().getBoundary().getV(), isvHalf1.getU())); + assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); + assertTrue(compare(getP(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); + assertTrue(compare(getQ(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); + + SV isvHalf2 = initialHalf2SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.ONE); + assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); + assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); + assertTrue(compare(getP(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); + assertTrue(compare(getQ(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); } @Test @@ -553,6 +600,14 @@ private static double getQ(LineSv line, TwoSides boundarySide) { } } + private static double getI(LineSv line, TwoSides boundarySide) { + if (boundarySide == TwoSides.ONE) { + return line.getI1(); + } else { + return line.getI2(); + } + } + // We define an error by value to adjust the case. The error is calculated by difference between // the calculated value with both models, the initial model of the case and the current model of the danglingLine // Errors are due to the danglingLine model (it does not allow shunt admittance at both ends) @@ -595,8 +650,8 @@ private static CaseSv createCase0() { NodeSv nodeBoundary = new NodeSv(1.05913402, Math.toDegrees(-0.01700730)); NodeSv node2 = new NodeSv(1.04546576, Math.toDegrees(-0.04168907)); - LineSv line1 = new LineSv(0.32101578, -0.16210107, -0.26328124, 0.00991455); - LineSv line2 = new LineSv(0.26328124, -0.00991455, -0.21700000, -0.12700000); + LineSv line1 = new LineSv(0.32101578, -0.16210107, -0.26328124, 0.00991455, node1.v, nodeBoundary.v); + LineSv line2 = new LineSv(0.26328124, -0.00991455, -0.21700000, -0.12700000, nodeBoundary.v, node2.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } @@ -606,8 +661,8 @@ private static CaseSv createCase1() { NodeSv nodeBoundary = new NodeSv(1.05916756, Math.toDegrees(-0.01702560)); NodeSv node2 = new NodeSv(1.04216358, Math.toDegrees(-0.03946400)); - LineSv line1 = new LineSv(0.32116645, -0.16274609, -0.26342655, 0.01056498); - LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26342655, -0.01056498); + LineSv line1 = new LineSv(0.32116645, -0.16274609, -0.26342655, 0.01056498, node1.v, nodeBoundary.v); + LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26342655, -0.01056498, node2.v, nodeBoundary.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } @@ -617,8 +672,8 @@ private static CaseSv createCase2() { NodeSv nodeBoundary = new NodeSv(1.05998661, Math.toDegrees(-0.01660626)); NodeSv node2 = new NodeSv(1.04634503, Math.toDegrees(-0.04125738)); - LineSv line1 = new LineSv(-0.26335112, 0.01016197, 0.32106283, -0.16270573); - LineSv line2 = new LineSv(0.26335112, -0.01016197, -0.21700000, -0.12700000); + LineSv line1 = new LineSv(-0.26335112, 0.01016197, 0.32106283, -0.16270573, nodeBoundary.v, node1.v); + LineSv line2 = new LineSv(0.26335112, -0.01016197, -0.21700000, -0.12700000, nodeBoundary.v, node2.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } @@ -628,12 +683,11 @@ private static CaseSv createCase3() { NodeSv nodeBoundary = new NodeSv(1.06002014, Math.toDegrees(-0.01662448)); NodeSv node2 = new NodeSv(1.04304009, Math.toDegrees(-0.03903205)); - LineSv line1 = new LineSv(-0.26349561, 0.01081185, 0.32121215, -0.16335034); - LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26349561, -0.01081185); + LineSv line1 = new LineSv(-0.26349561, 0.01081185, 0.32121215, -0.16335034, nodeBoundary.v, node1.v); + LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26349561, -0.01081185, node2.v, nodeBoundary.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } - // Line1 from nodeBoundary to node1, Line2 from node2 to nodeBoundary // Line1 from node1 to nodeBoundary, Line2 from nodeBoundary to node2 // Different nominal voltage at node1 and node2 private static CaseSv createCaseDifferentNominalVoltageAtEnds() { @@ -641,8 +695,21 @@ private static CaseSv createCaseDifferentNominalVoltageAtEnds() { NodeSv nodeBoundary = new NodeSv(145.42378472578227, Math.toDegrees(-0.02324020)); NodeSv node2 = new NodeSv(231.30269602522478, Math.toDegrees(-0.02818192)); - LineSv line1 = new LineSv(11.729938, -8.196614, -11.713527, 1.301712); - LineSv line2 = new LineSv(11.713527, -1.301712, -11.700000, -6.700000); + LineSv line1 = new LineSv(11.729938, -8.196614, -11.713527, 1.301712, node1.v, nodeBoundary.v); + LineSv line2 = new LineSv(11.713527, -1.301712, -11.700000, -6.700000, nodeBoundary.v, node2.v); + return new CaseSv(node1, node2, nodeBoundary, line1, line2); + } + + // Line1 from node1 to nodeBoundary, Line2 from nodeBoundary to node2 + // Different nominal voltage at node1 and node2 + // NaN values for voltages - simulates DC LF + private static CaseSv createCaseNaNVoltages() { + NodeSv node1 = new NodeSv(Double.NaN, Math.toDegrees(-0.01745197)); + NodeSv nodeBoundary = new NodeSv(Double.NaN, Math.toDegrees(-0.02324020)); + NodeSv node2 = new NodeSv(Double.NaN, Math.toDegrees(-0.02818192)); + + LineSv line1 = new LineSv(11.729938, -8.196614, -11.713527, 1.301712, node1.v, nodeBoundary.v); + LineSv line2 = new LineSv(11.713527, -1.301712, -11.700000, -6.700000, nodeBoundary.v, node2.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } @@ -677,12 +744,24 @@ private static final class LineSv { private final double q1; private final double p2; private final double q2; + private final double v1; + private final double v2; - private LineSv(double p1, double q1, double p2, double q2) { + private LineSv(double p1, double q1, double p2, double q2, double v1, double v2) { this.p1 = p1; this.q1 = q1; this.p2 = p2; this.q2 = q2; + this.v1 = v1; + this.v2 = v2; + } + + private double getI1() { + return Math.hypot(p1, q1) / (Math.sqrt(3.) * v1 / 1000); + } + + private double getI2() { + return Math.hypot(p2, q2) / (Math.sqrt(3.) * v2 / 1000); } } diff --git a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/ConvertTopologyTest.java b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/ConvertTopologyTest.java new file mode 100644 index 00000000000..5f78e6ecdb3 --- /dev/null +++ b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/ConvertTopologyTest.java @@ -0,0 +1,16 @@ +/** + * 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.iidm.network.impl.tck; + +import com.powsybl.iidm.network.tck.AbstractConvertTopologyTest; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class ConvertTopologyTest extends AbstractConvertTopologyTest { +} diff --git a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/ForkConnectDisconnectTest.java b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/ForkConnectDisconnectTest.java new file mode 100644 index 00000000000..159491af1e8 --- /dev/null +++ b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/ForkConnectDisconnectTest.java @@ -0,0 +1,16 @@ +/* + * 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.iidm.network.impl.tck; + +import com.powsybl.iidm.network.tck.AbstractForkConnectDisconnectTest; + +/** + * @author Nicolas Rol {@literal } + */ +class ForkConnectDisconnectTest extends AbstractForkConnectDisconnectTest { +} diff --git a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/TapChangerHolderTest.java b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/TapChangerHolderTest.java new file mode 100644 index 00000000000..0d18b007543 --- /dev/null +++ b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/TapChangerHolderTest.java @@ -0,0 +1,16 @@ +/** + * 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.iidm.network.impl.tck; + +import com.powsybl.iidm.network.tck.AbstractTapChangerHolderTest; + +/** + * @author Benoît Chiquet {@literal } + */ +public class TapChangerHolderTest extends AbstractTapChangerHolderTest { +} diff --git a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java index b244718c921..4a3d8f23b5a 100644 --- a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java +++ b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java @@ -62,6 +62,8 @@ void testOlfRealNetwork() { assertEquals(-dl.getP0(), dl.getBoundary().getP(), tol); assertEquals(-dl.getQ0(), dl.getBoundary().getQ(), tol); + double expectedI = Math.hypot(-dl.getP0(), -dl.getQ0()) / (Math.sqrt(3.) * dl.getBoundary().getV() / 1000); + assertEquals(expectedI, dl.getBoundary().getI(), tol); } @Test @@ -91,6 +93,8 @@ void testDcOlfRealNetwork() { assertEquals(-0.4187543391573424, svDl1other.getA(), tol); assertEquals(-dl.getP0(), dl.getBoundary().getP(), tol); + assertEquals(-dl.getQ0(), dl.getBoundary().getQ(), tol); + assertEquals(Double.NaN, dl.getBoundary().getI()); } private static void svOlfDataToNetwork(Network network) { @@ -288,6 +292,7 @@ void testWithZeroImpedanceDanglingLineWithGeneration() { danglingLine.getTerminal().getBusView().getBus().setV(100.0); assertEquals(298.937, danglingLine.getBoundary().getP(), tol); assertEquals(7.413, danglingLine.getBoundary().getQ(), tol); + assertEquals(1726.444, danglingLine.getBoundary().getI(), tol); assertEquals(100.0, danglingLine.getBoundary().getV(), tol); assertEquals(0.0, danglingLine.getBoundary().getAngle(), tol); } @@ -304,7 +309,25 @@ void testWithZeroImpedanceDanglingLineWithoutGeneration() { danglingLine.getTerminal().getBusView().getBus().setV(100.0); assertEquals(-50.0, danglingLine.getBoundary().getP(), tol); assertEquals(-30.0, danglingLine.getBoundary().getQ(), tol); + assertEquals(336.650, danglingLine.getBoundary().getI(), tol); assertEquals(100.0, danglingLine.getBoundary().getV(), tol); assertEquals(0.0, danglingLine.getBoundary().getAngle(), tol); } + + @Test + void testWithZeroImpedanceDanglingLineWithoutGenerationWithNaNV() { + double tol = 0.001; + Network network = DanglingLineNetworkFactory.create(); + DanglingLine danglingLine = network.getDanglingLine("DL"); + danglingLine.setR(0.0).setX(0.0); + danglingLine.getTerminal().setP(50.0); + danglingLine.getTerminal().setQ(30.0); + danglingLine.getTerminal().getBusView().getBus().setAngle(0.5); + danglingLine.getTerminal().getBusView().getBus().setV(Double.NaN); + assertEquals(-50.0, danglingLine.getBoundary().getP(), tol); + assertEquals(-30.0, danglingLine.getBoundary().getQ(), tol); + assertEquals(Double.NaN, danglingLine.getBoundary().getI()); + assertEquals(Double.NaN, danglingLine.getBoundary().getV()); + assertEquals(Double.NaN, danglingLine.getBoundary().getAngle()); + } } diff --git a/iidm/iidm-modification/pom.xml b/iidm/iidm-modification/pom.xml index c60a37058c8..cb8ea49691f 100644 --- a/iidm/iidm-modification/pom.xml +++ b/iidm/iidm-modification/pom.xml @@ -13,7 +13,7 @@ powsybl-iidm com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-modification diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AreaInterchangeTargetModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AreaInterchangeTargetModification.java new file mode 100644 index 00000000000..0235d649555 --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AreaInterchangeTargetModification.java @@ -0,0 +1,69 @@ +/* + * 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.iidm.modification; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; +import com.powsybl.iidm.network.Area; +import com.powsybl.iidm.network.Network; + +import java.util.Objects; + +/** + * Simple {@link NetworkModification} for an area interchange target update. + * + * @author Bertrand Rix {@literal } + */ +public class AreaInterchangeTargetModification extends AbstractNetworkModification { + + private final String areaId; + + private final double interchangeTarget; + + public AreaInterchangeTargetModification(String areadId, double interchangeTarget) { + this.areaId = Objects.requireNonNull(areadId); + this.interchangeTarget = interchangeTarget; + } + + @Override + public String getName() { + return "AreaInterchangeModification"; + } + + @Override + public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) { + Area area = network.getArea(areaId); + if (area == null) { + logOrThrow(throwException, "Area '" + areaId + "' not found"); + return; + } + area.setInterchangeTarget(interchangeTarget); + } + + @Override + public NetworkModificationImpact hasImpactOnNetwork(Network network) { + impact = DEFAULT_IMPACT; + Area area = network.getArea(areaId); + if (area == null) { + impact = NetworkModificationImpact.CANNOT_BE_APPLIED; + } else if (area.getInterchangeTarget().isPresent() && Math.abs(interchangeTarget - area.getInterchangeTarget().getAsDouble()) < EPSILON + || area.getInterchangeTarget().isEmpty() && Double.isNaN(interchangeTarget)) { + impact = NetworkModificationImpact.NO_IMPACT_ON_NETWORK; + } + return impact; + } + + public String getAreaId() { + return areaId; + } + + public Double getInterchangeTarget() { + return interchangeTarget; + } +} diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PercentChangeLoadModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PercentChangeLoadModification.java new file mode 100644 index 00000000000..45cca3bf4c7 --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PercentChangeLoadModification.java @@ -0,0 +1,75 @@ +/** + * 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.iidm.modification; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; + +import java.util.Objects; + +/** + * {@link NetworkModification} changing the active and reactive powers of a load by defining percentage changes (which could be positive or negative). + * + * @author Benoît Chiquet {@literal } + */ +public class PercentChangeLoadModification extends AbstractNetworkModification { + + private String loadId; + private double q0PercentChange; + private double p0PercentChange; + + public PercentChangeLoadModification(String loadId, double p0PercentChange, double q0PercentChange) { + this.loadId = Objects.requireNonNull(loadId); + if (p0PercentChange < -100) { + throw new PowsyblException("The active power of " + loadId + " cannot decrease by more than 100% (current value: " + p0PercentChange + ")"); + } + if (q0PercentChange < -100) { + throw new PowsyblException("The reactive power of " + loadId + " cannot decrease by more than 100% (current value: " + q0PercentChange + ")"); + } + this.p0PercentChange = p0PercentChange; + this.q0PercentChange = q0PercentChange; + } + + @Override + public String getName() { + return "PercentChangeLoadModification"; + } + + @Override + public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) { + Load load = network.getLoad(loadId); + if (load == null) { + logOrThrow(throwException, "Load '" + loadId + "' not found"); + } else { + double p0 = load.getP0(); + load.setP0(p0 + (p0 * p0PercentChange / 100)); + double q0 = load.getQ0(); + load.setQ0(q0 + (q0 * q0PercentChange / 100)); + } + } + + @Override + public NetworkModificationImpact hasImpactOnNetwork(Network network) { + impact = DEFAULT_IMPACT; + Load load = network.getLoad(loadId); + if (load == null) { + impact = NetworkModificationImpact.CANNOT_BE_APPLIED; + } else if (p0PercentChange == 0 && q0PercentChange == 0) { + impact = NetworkModificationImpact.NO_IMPACT_ON_NETWORK; + } + return impact; + } + + public String getMessageHeader() { + return getName(); + } +} diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformers.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformers.java new file mode 100644 index 00000000000..fac031bcd59 --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformers.java @@ -0,0 +1,570 @@ +/** + * 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.iidm.modification; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; +import com.powsybl.iidm.modification.util.RegulatedTerminalControllers; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.powsybl.iidm.modification.util.ModificationReports.*; +import static com.powsybl.iidm.modification.util.TransformerUtils.*; + +/** + *

This network modification is used to replace 3 twoWindingsTransformers by threeWindingsTransformers.

+ *
    + *
  • BusbarSections and the three TwoWindingsTransformers are the only connectable equipment allowed in the voltageLevel associated with the star bus.
  • + *
  • The three TwoWindingsTransformers must be connected to the star bus.
  • + *
  • The star terminals of the twoWindingsTransformers must not be regulated terminals for any controller.
  • + *
  • Each twoWindingsTransformer is well oriented if the star bus is located at the end 2.
  • + *
  • A new ThreeWindingsTransformer is created for replacing the three TwoWindingsTransformers.
  • + *
  • The following attributes are copied from each twoWindingsTransformer to the new associated leg: + *
      + *
    • Electrical characteristics, ratioTapChangers, and phaseTapChangers. Adjustments are required if the twoWindingsTransformer is not well oriented.
    • + *
    • Only the Operational Loading Limits defined at the non-star end are copied to the leg.
    • + *
    • Active and reactive power at the non-star terminal are copied to the leg terminal.
    • + *
    + *
  • + *
  • Aliases: + *
      + *
    • Aliases for known CGMES identifiers (terminal, transformer end, ratio, and phase tap changer) are copied to the threeWindingsTransformer after adjusting the aliasType.
    • + *
    • Aliases that are not mapped are recorded in the functional log.
    • + *
    + *
  • + *
  • Properties: + *
      + *
    • Voltage and angle of the star bus are added as properties of the threeWindingsTransformer.
    • + *
    • Only the names of the transferred operational limits are copied as properties of the threeWindingsTransformer.
    • + *
    • All the properties of the first twoWindingsTransformer are transferred to the threeWindingsTransformer, + * then those of the second that are not in the first, and finally, the properties of the third that are not in the first two.
    • + *
    • Properties that are not mapped are recorded in the functional log.
    • + *
    + *
  • + *
  • Extensions: + *
      + *
    • Only IIDM extensions are copied: TransformerFortescueData, PhaseAngleClock, and TransformerToBeEstimated.
    • + *
    • CGMES extensions can not be copied, as they cause circular dependencies.
    • + *
    • Extensions that are not copied are recorded in the functional log.
    • + *
    + *
  • + *
  • All the controllers using any of the twoWindingsTransformer terminals as regulated terminal are updated.
  • + *
  • New and removed equipment is also recorded in the functional log.
  • + *
  • Internal connections are created to manage the replacement.
  • + *
+ * + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ + +public class Replace3TwoWindingsTransformersByThreeWindingsTransformers extends AbstractNetworkModification { + + private static final Logger LOG = LoggerFactory.getLogger(Replace3TwoWindingsTransformersByThreeWindingsTransformers.class); + private static final String TWO_WINDINGS_TRANSFORMER = "TwoWindingsTransformer"; + private static final String WITH_FICTITIOUS_TERMINAL_USED_AS_REGULATED_TERMINAL = "with star terminal used as regulated terminal"; + private static final String CGMES_OPERATIONAL_LIMIT_SET = "CGMES.OperationalLimitSet_"; + + private final List transformersToBeReplaced; + + /** + *

Used to replace all 3 twoWindingsTransformers by threeWindingsTransformers.

+ */ + public Replace3TwoWindingsTransformersByThreeWindingsTransformers() { + this.transformersToBeReplaced = null; + } + + /** + *

Used to replace the 3 twoWindingsTransformers defined in the list by threeWindingsTransformers. + * To be selected, at least one of the three transformers must be included in the list.

+ */ + public Replace3TwoWindingsTransformersByThreeWindingsTransformers(List transformersToBeReplaced) { + this.transformersToBeReplaced = Objects.requireNonNull(transformersToBeReplaced); + } + + @Override + public String getName() { + return "Replace3TwoWindingsTransformersByThreeWindingsTransformers"; + } + + @Override + public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) { + RegulatedTerminalControllers regulatedTerminalControllers = new RegulatedTerminalControllers(network); + List twoWindingsTransformers = find3TwoWindingsTransformers(network, transformersToBeReplaced); + twoWindingsTransformers.forEach(twoR -> replace3TwoWindingsTransformerByThreeWindingsTransformer(twoR, regulatedTerminalControllers, throwException, reportNode)); + } + + private static List find3TwoWindingsTransformers(Network network, List transformersToBeReplaced) { + Map> twoWindingTransformersByBus = new HashMap<>(); + network.getTwoWindingsTransformers().forEach(t2w -> { + Bus bus1 = t2w.getTerminal1().getBusView().getBus(); + Bus bus2 = t2w.getTerminal2().getBusView().getBus(); + if (bus1 != null) { + twoWindingTransformersByBus.computeIfAbsent(bus1, k -> new ArrayList<>()).add(t2w); + } + if (bus2 != null) { + twoWindingTransformersByBus.computeIfAbsent(bus2, k -> new ArrayList<>()).add(t2w); + } + }); + return twoWindingTransformersByBus.keySet().stream() + .filter(bus -> isStarBus(bus, twoWindingTransformersByBus.get(bus))) + .sorted(Comparator.comparing(Identifiable::getId)) + .map(bus -> buildTwoR(bus, twoWindingTransformersByBus.get(bus))) + .filter(twoR -> isGoingToBeReplaced(twoR, transformersToBeReplaced)).toList(); + } + + private static boolean isGoingToBeReplaced(TwoR twoR, List transformersToBeReplaced) { + return transformersToBeReplaced == null + || transformersToBeReplaced.contains(twoR.t2w1.getId()) + || transformersToBeReplaced.contains(twoR.t2w2.getId()) + || transformersToBeReplaced.contains(twoR.t2w3.getId()); + } + + private static boolean isStarBus(Bus bus, List t2ws) { + return t2ws.size() == 3 && bus.getConnectedTerminalStream().filter(connectedTerminal -> connectedTerminal.getConnectable().getType() != IdentifiableType.BUSBAR_SECTION).count() == 3; + } + + private static TwoR buildTwoR(Bus starBus, List starBusT2ws) { + List sortedStarBusT2ws = starBusT2ws.stream() + .sorted(Comparator.comparingDouble((TwoWindingsTransformer t2w) -> getNominalV(starBus, t2w)) + .reversed() + .thenComparing(Identifiable::getId)) + .toList(); + + return new TwoR(starBus, sortedStarBusT2ws.get(0), sortedStarBusT2ws.get(1), sortedStarBusT2ws.get(2)); + } + + private static double getNominalV(Bus bus, TwoWindingsTransformer t2w) { + Bus terminalBus = t2w.getTerminal1().getBusView().getBus(); + return terminalBus != null && bus != null && terminalBus.getId().equals(bus.getId()) + ? t2w.getTerminal2().getVoltageLevel().getNominalV() + : t2w.getTerminal1().getVoltageLevel().getNominalV(); + } + + private record TwoR(Bus starBus, TwoWindingsTransformer t2w1, TwoWindingsTransformer t2w2, + TwoWindingsTransformer t2w3) { + } + + // if the twoWindingsTransformer is not well oriented, and it has non-zero shunt admittance (G != 0 or B != 0) + // the obtained model is not equivalent to the initial one as the shunt admittance must be moved to the other side + private void replace3TwoWindingsTransformerByThreeWindingsTransformer(TwoR twoR, RegulatedTerminalControllers regulatedTerminalControllers, boolean throwException, ReportNode reportNode) { + Substation substation = findSubstation(twoR, throwException); + if (substation == null) { + return; + } + if (anyTwoWindingsTransformerStarTerminalDefinedAsRegulatedTerminal(twoR, regulatedTerminalControllers, throwException)) { + return; + } + double ratedU0 = twoR.starBus.getVoltageLevel().getNominalV(); + boolean isWellOrientedT2w1 = isWellOriented(twoR.starBus, twoR.t2w1); + boolean isWellOrientedT2w2 = isWellOriented(twoR.starBus, twoR.t2w2); + boolean isWellOrientedT2w3 = isWellOriented(twoR.starBus, twoR.t2w3); + + ThreeWindingsTransformerAdder t3wAdder = substation.newThreeWindingsTransformer() + .setEnsureIdUnicity(true) + .setId(getId(twoR)) + .setName(getName(twoR)) + .setRatedU0(ratedU0); + + addLeg(t3wAdder.newLeg1(), twoR.t2w1, isWellOrientedT2w1, ratedU0); + addLeg(t3wAdder.newLeg2(), twoR.t2w2, isWellOrientedT2w2, ratedU0); + addLeg(t3wAdder.newLeg3(), twoR.t2w3, isWellOrientedT2w3, ratedU0); + ThreeWindingsTransformer t3w = t3wAdder.add(); + + // t3w is not considered in regulatedTerminalControllers (created later in the model) + setLegData(t3w.getLeg1(), twoR.t2w1, isWellOrientedT2w1, regulatedTerminalControllers, twoR); + setLegData(t3w.getLeg2(), twoR.t2w2, isWellOrientedT2w2, regulatedTerminalControllers, twoR); + setLegData(t3w.getLeg3(), twoR.t2w3, isWellOrientedT2w3, regulatedTerminalControllers, twoR); + + copyStarBusVoltageAndAngle(twoR.starBus, t3w); + List lostProperties = new ArrayList<>(); + lostProperties.addAll(copyProperties(twoR.t2w1, t3w)); + lostProperties.addAll(copyProperties(twoR.t2w2, t3w)); + lostProperties.addAll(copyProperties(twoR.t2w3, t3w)); + + List lostExtensions = copyExtensions(twoR, t3w); + + // copy necessary data before removing + List t2wAliases = new ArrayList<>(); + t2wAliases.addAll(getAliases(twoR.t2w1, "1", getEnd1(isWellOrientedT2w1))); + t2wAliases.addAll(getAliases(twoR.t2w2, "2", getEnd1(isWellOrientedT2w2))); + t2wAliases.addAll(getAliases(twoR.t2w3, "3", getEnd1(isWellOrientedT2w3))); + + String t2w1Id = twoR.t2w1.getId(); + String t2w2Id = twoR.t2w2.getId(); + String t2w3Id = twoR.t2w3.getId(); + String starVoltageId = twoR.starBus.getVoltageLevel().getId(); + List lostLimits = findLostLimits(twoR); + + remove(twoR); + + // after removing + List lostAliases = copyAliases(t2wAliases, t3w); + + // warnings + if (!lostProperties.isEmpty()) { + lostProperties.forEach(propertyR -> LOG.warn("Property '{}' of twoWindingsTransformer '{}' was not transferred", propertyR.propertyName, propertyR.t2wId)); + } + if (!lostExtensions.isEmpty()) { + lostExtensions.forEach(extensionR -> LOG.warn("Extension '{}' of twoWindingsTransformer '{}' was not transferred", extensionR.extensionName, extensionR.t2wId)); + } + if (!lostAliases.isEmpty()) { + lostAliases.forEach(aliasR -> LOG.warn("Alias '{}' '{}' of twoWindingsTransformer '{}' was not transferred", aliasR.alias, aliasR.aliasType, aliasR.t2wId)); + } + if (!lostLimits.isEmpty()) { + lostLimits.forEach(limitsR -> LOG.warn("OperationalLimitsGroup '{}' of twoWindingsTransformer '{}' is lost", limitsR.operationalLimitsGroupName, limitsR.t2wId)); + } + + // report + createReportNode(reportNode, t2w1Id, t2w2Id, t2w3Id, starVoltageId, lostProperties, lostExtensions, lostAliases, lostLimits, t3w.getId()); + } + + private static void addLeg(ThreeWindingsTransformerAdder.LegAdder legAdder, TwoWindingsTransformer t2w, boolean isWellOriented, double ratedU0) { + legAdder.setVoltageLevel(findVoltageLevel(t2w, isWellOriented).getId()) + .setR(findImpedance(t2w.getR(), getStructuralRatio(t2w), isWellOriented)) + .setX(findImpedance(t2w.getX(), getStructuralRatio(t2w), isWellOriented)) + .setG(findAdmittance(t2w.getG(), getStructuralRatio(t2w), isWellOriented)) + .setB(findAdmittance(t2w.getB(), getStructuralRatio(t2w), isWellOriented)) + .setRatedU(getRatedU1(t2w, ratedU0, isWellOriented)); + connectAfterCreatingInternalConnection(legAdder, t2w, isWellOriented); + legAdder.add(); + } + + private static void setLegData(ThreeWindingsTransformer.Leg leg, TwoWindingsTransformer t2w, boolean isWellOriented, RegulatedTerminalControllers regulatedTerminalControllers, TwoR twoR) { + t2w.getOptionalRatioTapChanger().ifPresent(rtc -> copyOrMoveRatioTapChanger(leg.newRatioTapChanger(), rtc, isWellOriented)); + t2w.getOptionalPhaseTapChanger().ifPresent(ptc -> copyOrMovePhaseTapChanger(leg.newPhaseTapChanger(), ptc, isWellOriented)); + + getOperationalLimitsGroups1(t2w, isWellOriented) + .forEach(operationalLimitGroup -> copyOperationalLimitsGroup(leg.newOperationalLimitsGroup(operationalLimitGroup.getId()), operationalLimitGroup)); + + regulatedTerminalControllers.replaceRegulatedTerminal(getTerminal1(t2w, isWellOriented), leg.getTerminal()); + replaceRegulatedTerminal(leg, twoR); + + copyTerminalActiveAndReactivePower(leg.getTerminal(), getTerminal1(t2w, isWellOriented)); + } + + private Substation findSubstation(TwoR twoR, boolean throwException) { + Optional substation = twoR.t2w1.getSubstation(); + if (substation.isEmpty()) { + logOrThrow(throwException, TWO_WINDINGS_TRANSFORMER + "'" + twoR.t2w1.getId() + "' without substation"); + return null; + } else { + return substation.get(); + } + } + + private boolean anyTwoWindingsTransformerStarTerminalDefinedAsRegulatedTerminal(TwoR twoR, RegulatedTerminalControllers regulatedTerminalControllers, boolean throwException) { + if (regulatedTerminalControllers.usedAsRegulatedTerminal(getTerminal2(twoR.t2w1, isWellOriented(twoR.starBus, twoR.t2w1)))) { + logOrThrow(throwException, TWO_WINDINGS_TRANSFORMER + "'" + twoR.t2w1.getId() + "' " + WITH_FICTITIOUS_TERMINAL_USED_AS_REGULATED_TERMINAL); + return true; + } + if (regulatedTerminalControllers.usedAsRegulatedTerminal(getTerminal2(twoR.t2w2, isWellOriented(twoR.starBus, twoR.t2w2)))) { + logOrThrow(throwException, TWO_WINDINGS_TRANSFORMER + "'" + twoR.t2w2.getId() + "' " + WITH_FICTITIOUS_TERMINAL_USED_AS_REGULATED_TERMINAL); + return true; + } + if (regulatedTerminalControllers.usedAsRegulatedTerminal(getTerminal2(twoR.t2w3, isWellOriented(twoR.starBus, twoR.t2w3)))) { + logOrThrow(throwException, TWO_WINDINGS_TRANSFORMER + "'" + twoR.t2w3.getId() + "' " + WITH_FICTITIOUS_TERMINAL_USED_AS_REGULATED_TERMINAL); + return true; + } + return false; + } + + private static String getId(TwoR twoR) { + return twoR.t2w1.getId() + "-" + twoR.t2w2.getId() + "-" + twoR.t2w3.getId(); + } + + private static String getName(TwoR twoR) { + return twoR.t2w1.getNameOrId() + "-" + twoR.t2w2.getNameOrId() + "-" + twoR.t2w3.getNameOrId(); + } + + // is well oriented when the star side is at end2 + private static boolean isWellOriented(Bus starBus, TwoWindingsTransformer t2w) { + return starBus.getId().equals(t2w.getTerminal2().getBusView().getBus().getId()); + } + + private static VoltageLevel findVoltageLevel(TwoWindingsTransformer t2w, boolean isWellOriented) { + return isWellOriented ? t2w.getTerminal1().getVoltageLevel() : t2w.getTerminal2().getVoltageLevel(); + } + + private static double getStructuralRatio(TwoWindingsTransformer twt) { + return twt.getRatedU1() / twt.getRatedU2(); + } + + private static double findImpedance(double impedance, double a, boolean isWellOriented) { + return isWellOriented ? impedance : impedanceConversion(impedance, a); + } + + private static double findAdmittance(double admittance, double a, boolean isWellOriented) { + return isWellOriented ? admittance : admittanceConversion(admittance, a); + } + + private static double getRatedU1(TwoWindingsTransformer t2w, double ratedU0, boolean isWellOriented) { + return isWellOriented ? getStructuralRatio(t2w) * ratedU0 : ratedU0 / getStructuralRatio(t2w); + } + + private static void connectAfterCreatingInternalConnection(ThreeWindingsTransformerAdder.LegAdder legAdder, TwoWindingsTransformer t2w, boolean isWellOriented) { + Terminal terminal = getTerminal1(t2w, isWellOriented); + if (terminal.getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) { + int newNode = terminal.getVoltageLevel().getNodeBreakerView().getMaximumNodeIndex() + 1; + terminal.getVoltageLevel().getNodeBreakerView() + .newInternalConnection() + .setNode1(terminal.getNodeBreakerView().getNode()) + .setNode2(newNode).add(); + legAdder.setNode(newNode); + } else { + legAdder.setConnectableBus(terminal.getBusBreakerView().getConnectableBus().getId()); + Bus bus = terminal.getBusBreakerView().getBus(); + if (bus != null) { + legAdder.setBus(bus.getId()); + } + } + } + + private static void copyOrMoveRatioTapChanger(RatioTapChangerAdder rtcAdder, RatioTapChanger rtc, boolean isWellOriented) { + if (isWellOriented) { + copyAndAddRatioTapChanger(rtcAdder, rtc); + } else { + copyAndMoveAndAddRatioTapChanger(rtcAdder, rtc); + } + } + + private static void copyOrMovePhaseTapChanger(PhaseTapChangerAdder ptcAdder, PhaseTapChanger ptc, boolean isWellOriented) { + if (isWellOriented) { + copyAndAddPhaseTapChanger(ptcAdder, ptc); + } else { + copyAndMoveAndAddPhaseTapChanger(ptcAdder, ptc); + } + } + + private static Collection getOperationalLimitsGroups1(TwoWindingsTransformer t2w, boolean isWellOriented) { + return isWellOriented ? t2w.getOperationalLimitsGroups1() : t2w.getOperationalLimitsGroups2(); + } + + private static Terminal getTerminal1(TwoWindingsTransformer t2w, boolean isWellOriented) { + return isWellOriented ? t2w.getTerminal1() : t2w.getTerminal2(); + } + + private static Terminal getTerminal2(TwoWindingsTransformer t2w, boolean isWellOriented) { + return isWellOriented ? t2w.getTerminal2() : t2w.getTerminal1(); + } + + private static String getEnd1(boolean isWellOriented) { + return isWellOriented ? "1" : "2"; + } + + private static void replaceRegulatedTerminal(ThreeWindingsTransformer.Leg t3wLeg, TwoR twoR) { + t3wLeg.getOptionalRatioTapChanger().ifPresent(rtc -> findNewRegulatedTerminal(rtc.getRegulationTerminal(), t3wLeg.getTransformer(), twoR).ifPresent(rtc::setRegulationTerminal)); + t3wLeg.getOptionalPhaseTapChanger().ifPresent(ptc -> findNewRegulatedTerminal(ptc.getRegulationTerminal(), t3wLeg.getTransformer(), twoR).ifPresent(ptc::setRegulationTerminal)); + } + + private static Optional findNewRegulatedTerminal(Terminal regulatedTerminal, ThreeWindingsTransformer t3w, TwoR twoR) { + if (isRegulatedTerminalInTwoWindingsTransformer(regulatedTerminal, twoR.t2w1)) { + return Optional.of(t3w.getTerminal(ThreeSides.ONE)); + } else if (isRegulatedTerminalInTwoWindingsTransformer(regulatedTerminal, twoR.t2w2)) { + return Optional.of(t3w.getTerminal(ThreeSides.TWO)); + } else if (isRegulatedTerminalInTwoWindingsTransformer(regulatedTerminal, twoR.t2w3)) { + return Optional.of(t3w.getTerminal(ThreeSides.THREE)); + } else { + return Optional.empty(); + } + } + + // we do not check the side, threeWindingsTransformers can only be controlled on the non-star side + private static boolean isRegulatedTerminalInTwoWindingsTransformer(Terminal regulatedTerminal, TwoWindingsTransformer t2w) { + return regulatedTerminal != null && regulatedTerminal.getConnectable().getId().equals(t2w.getId()); + } + + private static void copyStarBusVoltageAndAngle(Bus starBus, ThreeWindingsTransformer t3w) { + if (Double.isFinite(starBus.getV()) && starBus.getV() > 0.0 && Double.isFinite(starBus.getAngle())) { + t3w.setProperty("v", String.valueOf(starBus.getV())); + t3w.setProperty("angle", String.valueOf(starBus.getAngle())); + } + } + + private static List copyProperties(TwoWindingsTransformer t2w, ThreeWindingsTransformer t3w) { + List lostProperties = new ArrayList<>(); + t2w.getPropertyNames().forEach(propertyName -> { + boolean copied = copyProperty(propertyName, t2w.getProperty(propertyName), t3w); + if (!copied) { + lostProperties.add(new PropertyR(t2w.getId(), propertyName)); + } + }); + return lostProperties; + } + + private static boolean copyProperty(String propertyName, String property, ThreeWindingsTransformer t3w) { + boolean copied = true; + if (propertyName.startsWith(CGMES_OPERATIONAL_LIMIT_SET)) { + if (t3w.getLeg1().getOperationalLimitsGroups().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) { + t3w.setProperty(propertyName, property); + } else if (t3w.getLeg2().getOperationalLimitsGroups().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) { + t3w.setProperty(propertyName, property); + } else if (t3w.getLeg3().getOperationalLimitsGroups().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) { + t3w.setProperty(propertyName, property); + } else { + copied = false; + } + } else { + if (t3w.getPropertyNames().contains(propertyName)) { + copied = false; + } else { + t3w.setProperty(propertyName, property); + } + } + return copied; + } + + private record PropertyR(String t2wId, String propertyName) { + } + + // TODO For now, only a few extensions are supported. But a wider mechanism should be developed to support custom extensions. + private static List copyExtensions(TwoR twoR, ThreeWindingsTransformer t3w) { + List extensions = new ArrayList<>(); + extensions.addAll(twoR.t2w1.getExtensions().stream().map(extension -> new ExtensionR(twoR.t2w1.getId(), extension.getName())).toList()); + extensions.addAll(twoR.t2w2.getExtensions().stream().map(extension -> new ExtensionR(twoR.t2w2.getId(), extension.getName())).toList()); + extensions.addAll(twoR.t2w3.getExtensions().stream().map(extension -> new ExtensionR(twoR.t2w3.getId(), extension.getName())).toList()); + + List lostExtensions = new ArrayList<>(); + extensions.stream().map(extensionR -> extensionR.extensionName).collect(Collectors.toSet()).forEach(extensionName -> { + boolean copied = copyExtension(extensionName, twoR, t3w); + if (!copied) { + lostExtensions.addAll(extensions.stream().filter(extensionR -> extensionR.extensionName.equals(extensionName)).toList()); + } + }); + return lostExtensions; + } + + private record ExtensionR(String t2wId, String extensionName) { + } + + private static boolean copyExtension(String extensionName, TwoR twoR, ThreeWindingsTransformer t3w) { + boolean copied = true; + switch (extensionName) { + case "twoWindingsTransformerFortescue" -> + copyAndAddFortescue(t3w.newExtension(ThreeWindingsTransformerFortescueAdder.class), + twoR.t2w1.getExtension(TwoWindingsTransformerFortescue.class), isWellOriented(twoR.starBus, twoR.t2w1), + twoR.t2w2.getExtension(TwoWindingsTransformerFortescue.class), isWellOriented(twoR.starBus, twoR.t2w2), + twoR.t2w3.getExtension(TwoWindingsTransformerFortescue.class), isWellOriented(twoR.starBus, twoR.t2w3)); + case "twoWindingsTransformerPhaseAngleClock" -> + copyAndAddPhaseAngleClock(t3w.newExtension(ThreeWindingsTransformerPhaseAngleClockAdder.class), + twoR.t2w2.getExtension(TwoWindingsTransformerPhaseAngleClock.class), + twoR.t2w3.getExtension(TwoWindingsTransformerPhaseAngleClock.class)); + case "twoWindingsTransformerToBeEstimated" -> + copyAndAddToBeEstimated(t3w.newExtension(ThreeWindingsTransformerToBeEstimatedAdder.class), + twoR.t2w1.getExtension(TwoWindingsTransformerToBeEstimated.class), + twoR.t2w2.getExtension(TwoWindingsTransformerToBeEstimated.class), + twoR.t2w3.getExtension(TwoWindingsTransformerToBeEstimated.class)); + default -> copied = false; + } + return copied; + } + + private static List getAliases(TwoWindingsTransformer t2w, String leg, String end) { + return t2w.getAliases().stream().map(alias -> new AliasR(t2w.getId(), alias, t2w.getAliasType(alias).orElse(""), leg, end)).toList(); + } + + private static List copyAliases(List t2wAliases, ThreeWindingsTransformer t3w) { + List lostAliases = new ArrayList<>(); + t2wAliases.forEach(aliasR -> { + boolean copied = copyAlias(aliasR.alias, aliasR.aliasType, aliasR.leg, aliasR.end, t3w); + if (!copied) { + lostAliases.add(aliasR); + } + }); + return lostAliases; + } + + private static boolean copyAlias(String alias, String aliasType, String leg, String end, ThreeWindingsTransformer t3w) { + boolean copied = true; + if (aliasType.equals("CGMES.TransformerEnd" + end)) { + t3w.addAlias(alias, "CGMES.TransformerEnd" + leg, true); + } else if (aliasType.equals("CGMES.Terminal" + end)) { + t3w.addAlias(alias, "CGMES.Terminal" + leg, true); + } else if (aliasType.equals("CGMES.RatioTapChanger1")) { + t3w.addAlias(alias, "CGMES.RatioTapChanger" + leg, true); + } else if (aliasType.equals("CGMES.PhaseTapChanger1")) { + t3w.addAlias(alias, "CGMES.PhaseTapChanger" + leg, true); + } else { + copied = false; + } + return copied; + } + + private record AliasR(String t2wId, String alias, String aliasType, String leg, String end) { + } + + private static void remove(TwoR twoR) { + VoltageLevel voltageLevel = twoR.starBus.getVoltageLevel(); + twoR.starBus.getConnectedTerminalStream().toList() // toList is required to create a temporary list since the threeWindingsTransformer is removed during the replacement + .forEach(terminal -> terminal.getConnectable().remove()); + voltageLevel.remove(); + } + + private static List findLostLimits(TwoR twoR) { + List lostLimits = new ArrayList<>(); + getOperationalLimitsGroups2(twoR.t2w1, isWellOriented(twoR.starBus, twoR.t2w1)).forEach(operationalLimitsGroup -> lostLimits.add(new LimitsR(twoR.t2w1.getId(), operationalLimitsGroup.getId()))); + getOperationalLimitsGroups2(twoR.t2w2, isWellOriented(twoR.starBus, twoR.t2w2)).forEach(operationalLimitsGroup -> lostLimits.add(new LimitsR(twoR.t2w2.getId(), operationalLimitsGroup.getId()))); + getOperationalLimitsGroups2(twoR.t2w3, isWellOriented(twoR.starBus, twoR.t2w3)).forEach(operationalLimitsGroup -> lostLimits.add(new LimitsR(twoR.t2w3.getId(), operationalLimitsGroup.getId()))); + return lostLimits; + } + + private static Collection getOperationalLimitsGroups2(TwoWindingsTransformer t2w, boolean isWellOriented) { + return isWellOriented ? t2w.getOperationalLimitsGroups2() : t2w.getOperationalLimitsGroups1(); + } + + private record LimitsR(String t2wId, String operationalLimitsGroupName) { + } + + private static void createReportNode(ReportNode reportNode, String t2w1Id, String t2w2Id, String t2w3Id, String starVoltageId, + List lostProperties, List lostExtensions, List lostAliases, + List lostLimits, String t3wId) { + + ReportNode reportNodeReplacement = replace3TwoWindingsTransformersByThreeWindingsTransformersReport(reportNode); + + removedTwoWindingsTransformerReport(reportNodeReplacement, t2w1Id); + removedTwoWindingsTransformerReport(reportNodeReplacement, t2w2Id); + removedTwoWindingsTransformerReport(reportNodeReplacement, t2w3Id); + removedVoltageLevelReport(reportNodeReplacement, starVoltageId); + + if (!lostProperties.isEmpty()) { + Set t2wIds = lostProperties.stream().map(propertyR -> propertyR.t2wId).collect(Collectors.toSet()); + t2wIds.stream().sorted().forEach(t2wId -> { + String properties = String.join(",", lostProperties.stream().filter(propertyR -> propertyR.t2wId.equals(t2wId)).map(propertyR -> propertyR.propertyName).toList()); + lostTwoWindingsTransformerProperties(reportNodeReplacement, properties, t2wId); + }); + } + if (!lostExtensions.isEmpty()) { + Set t2wIds = lostExtensions.stream().map(extensionR -> extensionR.t2wId).collect(Collectors.toSet()); + t2wIds.stream().sorted().forEach(t2wId -> { + String extensions = String.join(",", lostExtensions.stream().filter(extensionR -> extensionR.t2wId.equals(t2wId)).map(extensionR -> extensionR.extensionName).toList()); + lostTwoWindingsTransformerExtensions(reportNodeReplacement, extensions, t2wId); + }); + } + if (!lostAliases.isEmpty()) { + Set t2wIds = lostAliases.stream().map(aliasR -> aliasR.t2wId).collect(Collectors.toSet()); + t2wIds.stream().sorted().forEach(t2wId -> { + String aliases = lostAliases.stream().filter(aliasR -> aliasR.t2wId.equals(t2wId)).map(AliasR::alias).collect(Collectors.joining(",")); + lostTwoWindingsTransformerAliases(reportNodeReplacement, aliases, t2wId); + }); + } + if (!lostLimits.isEmpty()) { + Set t2wIds = lostLimits.stream().map(limitsR -> limitsR.t2wId).collect(Collectors.toSet()); + t2wIds.stream().sorted().forEach(t2wId -> { + String limits = lostLimits.stream().filter(limitsR -> limitsR.t2wId.equals(t2wId)).map(LimitsR::operationalLimitsGroupName).collect(Collectors.joining(",")); + lostTwoWindingsTransformerOperationalLimitsGroups(reportNodeReplacement, limits, t2wId); + }); + } + + createdThreeWindingsTransformerReport(reportNodeReplacement, t3wId); + } +} diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers.java new file mode 100644 index 00000000000..7f65aea0f12 --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers.java @@ -0,0 +1,417 @@ +/** + * 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.iidm.modification; + +import com.powsybl.commons.extensions.Extension; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; +import com.powsybl.iidm.modification.util.RegulatedTerminalControllers; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.powsybl.iidm.modification.util.ModificationReports.*; +import static com.powsybl.iidm.modification.util.TransformerUtils.*; +import static com.powsybl.iidm.modification.util.TransformerUtils.copyAndAddPhaseAngleClock; + +/** + *

This network modification is used to replace threeWindingsTransformers by 3 twoWindingsTransformers.

+ *

For each threeWindingsTransformer to be replaced:

+ *
    + *
  • A new voltage level is created for the star node with nominal voltage of ratedU0.
  • + *
  • Three new TwoWindingsTransformers are created, one for each leg of the removed ThreeWindingsTransformer.
  • + *
  • The following attributes are copied from each leg to the new associated twoWindingsTransformer: + *
      + *
    • Electrical characteristics, ratioTapChangers, and phaseTapChangers. No adjustments are required.
    • + *
    • Operational Loading Limits are copied to the non-star end of the twoWindingsTransformers.
    • + *
    • Active and reactive power at the terminal are copied to the non-star terminal of the twoWindingsTransformer.
    • + *
    + *
  • + *
  • Aliases: + *
      + *
    • Aliases for known CGMES identifiers (terminal, transformer end, ratio, and phase tap changer) are copied to the right twoWindingsTransformer after adjusting the aliasType.
    • + *
    • Aliases that are not mapped are recorded in the functional log.
    • + *
    + *
  • + *
  • Properties: + *
      + *
    • Star bus voltage and angle are set to the bus created for the star node.
    • + *
    • The names of the operationalLimitsSet are copied to the right twoWindingsTransformer.
    • + *
    • The rest of the properties of the threeWindingsTransformer are transferred to all 3 twoWindingsTransformers.
    • + *
    + *
  • + *
  • Extensions: + *
      + *
    • Only IIDM extensions are copied: TransformerFortescueData, PhaseAngleClock, and TransformerToBeEstimated.
    • + *
    • CGMES extensions can not be copied, as they cause circular dependencies.
    • + *
    • Extensions that are not copied are recorded in the functional log.
    • + *
    + *
  • + *
  • All the controllers using any of the threeWindingsTransformer terminals as regulated terminal are updated.
  • + *
  • New and removed equipment is also recorded in the functional log.
  • + *
  • Internal connections are created to manage the replacement.
  • + *
+ * + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +public class ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers extends AbstractNetworkModification { + + private static final Logger LOG = LoggerFactory.getLogger(ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers.class); + private static final String THREE_WINDINGS_TRANSFORMER = "ThreeWindingsTransformer"; + private static final String CGMES_OPERATIONAL_LIMIT_SET = "CGMES.OperationalLimitSet_"; + + private final List transformersToBeReplaced; + + /** + *

Used to replace all threeWindingsTransformers by 3 twoWindingsTransformers.

+ */ + public ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers() { + this.transformersToBeReplaced = null; + } + + /** + *

Used to replace the threeWindingsTransformers included in the list by 3 twoWindingsTransformers.

+ */ + public ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(List transformersToBeReplaced) { + this.transformersToBeReplaced = Objects.requireNonNull(transformersToBeReplaced); + } + + @Override + public String getName() { + return "ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers"; + } + + @Override + public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) { + RegulatedTerminalControllers regulatedTerminalControllers = new RegulatedTerminalControllers(network); + network.getThreeWindingsTransformerStream().filter(t3w -> isGoingToBeReplaced(transformersToBeReplaced, t3w.getId())).toList() // toList is required to create a temporary list since the threeWindingsTransformer is removed during the replacement + .forEach(t3w -> replaceThreeWindingsTransformerBy3TwoWindingsTransformer(t3w, regulatedTerminalControllers, throwException, reportNode)); + } + + private static boolean isGoingToBeReplaced(List transformersToBeReplaced, String t3wId) { + return transformersToBeReplaced == null || transformersToBeReplaced.contains(t3wId); + } + + private void replaceThreeWindingsTransformerBy3TwoWindingsTransformer(ThreeWindingsTransformer t3w, RegulatedTerminalControllers regulatedTerminalControllers, boolean throwException, ReportNode reportNode) { + VoltageLevel starVoltageLevel = createStarVoltageLevel(t3w, throwException); + if (starVoltageLevel == null) { + return; + } + createTopologyInsideStarVoltageLevel(t3w, starVoltageLevel); + + TwoWindingsTransformer t2wLeg1 = createTwoWindingsTransformer(t3w, t3w.getLeg1(), starVoltageLevel); + TwoWindingsTransformer t2wLeg2 = createTwoWindingsTransformer(t3w, t3w.getLeg2(), starVoltageLevel); + TwoWindingsTransformer t2wLeg3 = createTwoWindingsTransformer(t3w, t3w.getLeg3(), starVoltageLevel); + ThreeT2wsR threeT2ws = new ThreeT2wsR(t2wLeg1, t2wLeg2, t2wLeg3); + + regulatedTerminalControllers.replaceRegulatedTerminal(t3w.getLeg1().getTerminal(), threeT2ws.t2wOne.getTerminal1()); + regulatedTerminalControllers.replaceRegulatedTerminal(t3w.getLeg2().getTerminal(), threeT2ws.t2wTwo.getTerminal1()); + regulatedTerminalControllers.replaceRegulatedTerminal(t3w.getLeg3().getTerminal(), threeT2ws.t2wThree.getTerminal1()); + + // t2wLeg1, t2wLeg, and t2wLeg3 are not considered in regulatedTerminalControllers (created later in the model) + replaceRegulatedTerminal(threeT2ws.t2wOne, t3w, threeT2ws); + replaceRegulatedTerminal(threeT2ws.t2wTwo, t3w, threeT2ws); + replaceRegulatedTerminal(threeT2ws.t2wThree, t3w, threeT2ws); + + copyTerminalActiveAndReactivePower(threeT2ws.t2wOne.getTerminal1(), t3w.getLeg1().getTerminal()); + copyTerminalActiveAndReactivePower(threeT2ws.t2wTwo.getTerminal1(), t3w.getLeg2().getTerminal()); + copyTerminalActiveAndReactivePower(threeT2ws.t2wThree.getTerminal1(), t3w.getLeg3().getTerminal()); + + List lostProperties = copyProperties(t3w, threeT2ws, starVoltageLevel); + List lostExtensions = copyExtensions(t3w, threeT2ws); + + // copy necessary data before removing the transformer + String t3wId = t3w.getId(); + List t3wAliases = getAliases(t3w); + t3w.remove(); + + // after removing the threeWindingsTransformer + List lostAliases = copyAliases(t3wAliases, threeT2ws); + + // warnings + if (!lostProperties.isEmpty()) { + lostProperties.forEach(propertyName -> LOG.warn("Property '{}' of threeWindingsTransformer '{}' was not transferred", propertyName, t3wId)); + } + if (!lostExtensions.isEmpty()) { + lostExtensions.forEach(extensionName -> LOG.warn("Extension '{}' of threeWindingsTransformer '{}' was not transferred", extensionName, t3wId)); + } + if (!lostAliases.isEmpty()) { + lostAliases.forEach(aliasR -> LOG.warn("Alias '{}' '{}' of threeWindingsTransformer '{}' was not transferred", aliasR.alias, aliasR.aliasType, t3wId)); + } + + // report + createReportNode(reportNode, t3wId, lostProperties, lostExtensions, lostAliases, starVoltageLevel.getId(), threeT2ws); + } + + // It is a fictitious bus, then we do not set voltage limits + private VoltageLevel createStarVoltageLevel(ThreeWindingsTransformer t3w, boolean throwException) { + Optional substation = t3w.getSubstation(); + if (substation.isEmpty()) { + logOrThrow(throwException, THREE_WINDINGS_TRANSFORMER + "'" + t3w.getId() + "' without substation"); + return null; + } + TopologyKind topologykind = t3w.getLeg1().getTerminal().getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER + && t3w.getLeg2().getTerminal().getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER + && t3w.getLeg3().getTerminal().getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER + ? TopologyKind.BUS_BREAKER : TopologyKind.NODE_BREAKER; + return substation.get().newVoltageLevel() + .setId(t3w.getId() + "-Star-VL") + .setName(t3w.getNameOrId() + "-Star-VL") + .setNominalV(t3w.getRatedU0()) + .setTopologyKind(topologykind) + .add(); + } + + private static void createTopologyInsideStarVoltageLevel(ThreeWindingsTransformer t3w, VoltageLevel starVoltageLevel) { + if (starVoltageLevel.getTopologyKind() == TopologyKind.BUS_BREAKER) { + starVoltageLevel.getBusBreakerView().newBus() + .setId(t3w.getId() + "-Star-Bus") + .setName(t3w.getNameOrId() + "-Star-Bus") + .add(); + } else { + starVoltageLevel.getNodeBreakerView().newInternalConnection().setNode1(1).setNode2(0).add(); + starVoltageLevel.getNodeBreakerView().newInternalConnection().setNode1(2).setNode2(0).add(); + starVoltageLevel.getNodeBreakerView().newInternalConnection().setNode1(3).setNode2(0).add(); + } + } + + private static TwoWindingsTransformer createTwoWindingsTransformer(ThreeWindingsTransformer t3w, ThreeWindingsTransformer.Leg leg, VoltageLevel starVoltageLevel) { + TwoWindingsTransformerAdder t2wAdder = starVoltageLevel.getSubstation().orElseThrow() + .newTwoWindingsTransformer() + .setEnsureIdUnicity(true) + .setId(t3w.getId() + "-Leg" + leg.getSide().getNum()) + .setName(t3w.getNameOrId() + "-Leg" + leg.getSide().getNum()) + .setRatedU1(leg.getRatedU()) + .setRatedU2(starVoltageLevel.getNominalV()) + .setR(leg.getR()) + .setX(leg.getX()) + .setG(leg.getG()) + .setB(leg.getB()) + .setRatedS(leg.getRatedS()) + .setVoltageLevel1(leg.getTerminal().getVoltageLevel().getId()) + .setVoltageLevel2(starVoltageLevel.getId()); + + connect(t2wAdder, getConnectivityLegAfterCreatingInternalConnection(leg), getConnectivityStar(leg.getSide().getNum(), starVoltageLevel)); + TwoWindingsTransformer t2w = t2wAdder.add(); + + leg.getOptionalRatioTapChanger().ifPresent(rtc -> copyAndAddRatioTapChanger(t2w.newRatioTapChanger(), rtc)); + leg.getOptionalPhaseTapChanger().ifPresent(rtc -> copyAndAddPhaseTapChanger(t2w.newPhaseTapChanger(), rtc)); + leg.getOperationalLimitsGroups().forEach(operationalLimitGroup -> copyOperationalLimitsGroup(t2w.newOperationalLimitsGroup1(operationalLimitGroup.getId()), operationalLimitGroup)); + + return t2w; + } + + private static void connect(TwoWindingsTransformerAdder t2wAdder, ConnectivityR connectivityEnd1, ConnectivityR connectivityEnd2) { + if (connectivityEnd1.node != null) { + t2wAdder.setNode1(connectivityEnd1.node); + } else { + t2wAdder.setConnectableBus1(connectivityEnd1.connectableBus.getId()); + if (connectivityEnd1.bus != null) { + t2wAdder.setBus1(connectivityEnd1.bus.getId()); + } + } + + if (connectivityEnd2.node != null) { + t2wAdder.setNode2(connectivityEnd2.node); + } else { + t2wAdder.setConnectableBus2(connectivityEnd2.connectableBus.getId()); + if (connectivityEnd2.bus != null) { + t2wAdder.setBus2(connectivityEnd2.bus.getId()); + } + } + } + + private static ConnectivityR getConnectivityLegAfterCreatingInternalConnection(ThreeWindingsTransformer.Leg leg) { + if (leg.getTerminal().getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) { + int newNode = leg.getTerminal().getVoltageLevel().getNodeBreakerView().getMaximumNodeIndex() + 1; + leg.getTerminal().getVoltageLevel().getNodeBreakerView() + .newInternalConnection() + .setNode1(leg.getTerminal().getNodeBreakerView().getNode()) + .setNode2(newNode).add(); + return new ConnectivityR(newNode, null, null); + } else { + return new ConnectivityR(null, leg.getTerminal().getBusBreakerView().getBus(), leg.getTerminal().getBusBreakerView().getConnectableBus()); + } + } + + private static ConnectivityR getConnectivityStar(int node, VoltageLevel startVoltageLevel) { + if (startVoltageLevel.getTopologyKind() == TopologyKind.NODE_BREAKER) { + return new ConnectivityR(node, null, null); + } else { + Bus bus = startVoltageLevel.getBusBreakerView().getBuses().iterator().next(); + return new ConnectivityR(null, bus, bus); + } + } + + private record ConnectivityR(Integer node, Bus bus, Bus connectableBus) { + } + + private static void replaceRegulatedTerminal(TwoWindingsTransformer t2w, ThreeWindingsTransformer t3w, ThreeT2wsR threeT2ws) { + t2w.getOptionalRatioTapChanger().ifPresent(rtc -> findNewRegulatedTerminal(rtc.getRegulationTerminal(), t3w, threeT2ws).ifPresent(rtc::setRegulationTerminal)); + t2w.getOptionalPhaseTapChanger().ifPresent(ptc -> findNewRegulatedTerminal(ptc.getRegulationTerminal(), t3w, threeT2ws).ifPresent(ptc::setRegulationTerminal)); + } + + private static Optional findNewRegulatedTerminal(Terminal regulatedTerminal, ThreeWindingsTransformer t3w, ThreeT2wsR threeT2ws) { + if (regulatedTerminal != null && regulatedTerminal.getConnectable().getId().equals(t3w.getId())) { + return switch (regulatedTerminal.getSide()) { + case ONE -> Optional.of(threeT2ws.t2wOne.getTerminal1()); + case TWO -> Optional.of(threeT2ws.t2wTwo.getTerminal1()); + case THREE -> Optional.of(threeT2ws.t2wThree.getTerminal1()); + }; + } else { + return Optional.empty(); + } + } + + private static List copyProperties(ThreeWindingsTransformer t3w, ThreeT2wsR threeT2ws, VoltageLevel starVoltageLevel) { + List lostProperties = new ArrayList<>(); + t3w.getPropertyNames().forEach(propertyName -> { + boolean copied = copyProperty(propertyName, t3w.getProperty(propertyName), threeT2ws, starVoltageLevel); + if (!copied) { + lostProperties.add(propertyName); + } + }); + return lostProperties; + } + + private static boolean copyProperty(String propertyName, String property, ThreeT2wsR threeT2ws, VoltageLevel starVoltageLevel) { + boolean copied = true; + if ("v".equals(propertyName)) { + starVoltageLevel.getBusView().getBuses().iterator().next().setV(Double.parseDouble(property)); + } else if ("angle".equals(propertyName)) { + starVoltageLevel.getBusView().getBuses().iterator().next().setAngle(Double.parseDouble(property)); + } else if (propertyName.startsWith(CGMES_OPERATIONAL_LIMIT_SET)) { + if (threeT2ws.t2wOne.getOperationalLimitsGroups1().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) { + threeT2ws.t2wOne.setProperty(propertyName, property); + } else if (threeT2ws.t2wTwo.getOperationalLimitsGroups1().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) { + threeT2ws.t2wTwo.setProperty(propertyName, property); + } else if (threeT2ws.t2wThree.getOperationalLimitsGroups1().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) { + threeT2ws.t2wThree.setProperty(propertyName, property); + } else { + copied = false; + } + } else { + // we copy all other properties on the 3 2wt + threeT2ws.t2wOne.setProperty(propertyName, property); + threeT2ws.t2wTwo.setProperty(propertyName, property); + threeT2ws.t2wThree.setProperty(propertyName, property); + } + return copied; + } + + // TODO For now, only a few extensions are supported. But a wider mechanism should be developed to support custom extensions. + private static List copyExtensions(ThreeWindingsTransformer t3w, ThreeT2wsR threeT2w) { + List lostExtensions = new ArrayList<>(); + t3w.getExtensions().stream().map(Extension::getName).forEach(extensionName -> { + boolean copied = copyExtension(extensionName, t3w, threeT2w); + if (!copied) { + lostExtensions.add(extensionName); + } + }); + return lostExtensions; + } + + private static boolean copyExtension(String extensionName, ThreeWindingsTransformer t3w, ThreeT2wsR threeT2ws) { + boolean copied = true; + switch (extensionName) { + case "threeWindingsTransformerFortescue" -> { + ThreeWindingsTransformerFortescue extension = t3w.getExtension(ThreeWindingsTransformerFortescue.class); + copyAndAddFortescue(threeT2ws.t2wOne.newExtension(TwoWindingsTransformerFortescueAdder.class), extension.getLeg1()); + copyAndAddFortescue(threeT2ws.t2wTwo.newExtension(TwoWindingsTransformerFortescueAdder.class), extension.getLeg2()); + copyAndAddFortescue(threeT2ws.t2wThree.newExtension(TwoWindingsTransformerFortescueAdder.class), extension.getLeg3()); + } + case "threeWindingsTransformerPhaseAngleClock" -> { + ThreeWindingsTransformerPhaseAngleClock extension = t3w.getExtension(ThreeWindingsTransformerPhaseAngleClock.class); + copyAndAddPhaseAngleClock(threeT2ws.t2wTwo.newExtension(TwoWindingsTransformerPhaseAngleClockAdder.class), extension.getPhaseAngleClockLeg2()); + copyAndAddPhaseAngleClock(threeT2ws.t2wThree.newExtension(TwoWindingsTransformerPhaseAngleClockAdder.class), extension.getPhaseAngleClockLeg3()); + } + case "threeWindingsTransformerToBeEstimated" -> { + ThreeWindingsTransformerToBeEstimated extension = t3w.getExtension(ThreeWindingsTransformerToBeEstimated.class); + copyAndAddToBeEstimated(threeT2ws.t2wOne.newExtension(TwoWindingsTransformerToBeEstimatedAdder.class), extension.shouldEstimateRatioTapChanger1(), extension.shouldEstimatePhaseTapChanger1()); + copyAndAddToBeEstimated(threeT2ws.t2wTwo.newExtension(TwoWindingsTransformerToBeEstimatedAdder.class), extension.shouldEstimateRatioTapChanger2(), extension.shouldEstimatePhaseTapChanger2()); + copyAndAddToBeEstimated(threeT2ws.t2wThree.newExtension(TwoWindingsTransformerToBeEstimatedAdder.class), extension.shouldEstimateRatioTapChanger3(), extension.shouldEstimatePhaseTapChanger3()); + } + default -> copied = false; + } + return copied; + } + + private static List getAliases(ThreeWindingsTransformer t3w) { + return t3w.getAliases().stream().map(alias -> new AliasR(alias, t3w.getAliasType(alias).orElse(""))).toList(); + } + + private static List copyAliases(List t3wAliases, ThreeT2wsR threeT2w) { + List lostAliases = new ArrayList<>(); + t3wAliases.forEach(aliasR -> { + boolean copied = copyAlias(aliasR.alias, aliasR.aliasType, threeT2w); + if (!copied) { + lostAliases.add(aliasR); + } + }); + return lostAliases; + } + + private static boolean copyAlias(String alias, String aliasType, ThreeT2wsR threeT2ws) { + return copyLegAlias(alias, aliasType, "1", threeT2ws.t2wOne) + || copyLegAlias(alias, aliasType, "2", threeT2ws.t2wTwo) + || copyLegAlias(alias, aliasType, "3", threeT2ws.t2wThree); + } + + private static boolean copyLegAlias(String alias, String aliasType, String legEnd, TwoWindingsTransformer t2wLeg) { + boolean copied = true; + if (aliasType.equals("CGMES.TransformerEnd" + legEnd)) { + t2wLeg.addAlias(alias, "CGMES.TransformerEnd1", true); + } else if (aliasType.equals("CGMES.Terminal" + legEnd)) { + t2wLeg.addAlias(alias, "CGMES.Terminal1", true); + } else if (aliasType.equals("CGMES.RatioTapChanger" + legEnd)) { + t2wLeg.addAlias(alias, "CGMES.RatioTapChanger1", true); + } else if (aliasType.equals("CGMES.PhaseTapChanger" + legEnd)) { + t2wLeg.addAlias(alias, "CGMES.PhaseTapChanger1", true); + } else { + copied = false; + } + return copied; + } + + private record AliasR(String alias, String aliasType) { + } + + private static void createReportNode(ReportNode reportNode, String t3wId, List lostProperties, List lostExtensions, + List lostAliases, String starVoltageLevelId, ThreeT2wsR threeT2ws) { + + ReportNode reportNodeReplacement = replaceThreeWindingsTransformersBy3TwoWindingsTransformersReport(reportNode); + + removedThreeWindingsTransformerReport(reportNodeReplacement, t3wId); + if (!lostProperties.isEmpty()) { + String properties = String.join(",", lostProperties); + lostThreeWindingsTransformerProperties(reportNodeReplacement, properties, t3wId); + } + if (!lostExtensions.isEmpty()) { + String extensions = String.join(",", lostExtensions); + lostThreeWindingsTransformerExtensions(reportNodeReplacement, extensions, t3wId); + } + if (!lostAliases.isEmpty()) { + String aliases = lostAliases.stream().map(AliasR::alias).collect(Collectors.joining(",")); + lostThreeWindingsTransformerAliases(reportNodeReplacement, aliases, t3wId); + } + + createdVoltageLevelReport(reportNodeReplacement, starVoltageLevelId); + createdTwoWindingsTransformerReport(reportNodeReplacement, threeT2ws.t2wOne.getId()); + createdTwoWindingsTransformerReport(reportNodeReplacement, threeT2ws.t2wTwo.getId()); + createdTwoWindingsTransformerReport(reportNodeReplacement, threeT2ws.t2wThree.getId()); + } + + private record ThreeT2wsR(TwoWindingsTransformer t2wOne, TwoWindingsTransformer t2wTwo, TwoWindingsTransformer t2wThree) { + } +} diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/SetGeneratorToLocalRegulation.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/SetGeneratorToLocalRegulation.java new file mode 100644 index 00000000000..c8849c26e25 --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/SetGeneratorToLocalRegulation.java @@ -0,0 +1,88 @@ +/** + * 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.iidm.modification; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; + +import static com.powsybl.iidm.modification.util.ModificationReports.generatorLocalRegulationReport; + +/** + *

Network modification to set a generator regulation to local instead of remote.

+ *
    + *
  • Generator's RegulatingTerminal is set to the generator's own Terminal.
  • + *
  • TargetV engineering unit value is adapted but the per unit value remains the same.
  • + *
+ * + * @author Romain Courtier {@literal } + */ +public class SetGeneratorToLocalRegulation extends AbstractNetworkModification { + + private final String generatorId; + private static final Logger LOG = LoggerFactory.getLogger(SetGeneratorToLocalRegulation.class); + + public SetGeneratorToLocalRegulation(String generatorId) { + this.generatorId = Objects.requireNonNull(generatorId); + } + + @Override + public String getName() { + return "SetGeneratorToLocalRegulation"; + } + + @Override + public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) { + Generator generator = network.getGenerator(generatorId); + if (generator == null) { + logOrThrow(throwException, "Generator '" + generatorId + "' not found"); + } else if (!generator.getId().equals(generator.getRegulatingTerminal().getConnectable().getId())) { + setLocalRegulation(generator, reportNode); + } + } + + /** + * Change the regulatingTerminal and the targetV of a generator to make it regulate locally. + * @param generator The Generator that should regulate locally. + * @param reportNode The ReportNode for functional logs. + */ + private void setLocalRegulation(Generator generator, ReportNode reportNode) { + // Calculate the (new) local targetV which should be the same value in per unit as the (old) remote targetV + double remoteTargetV = generator.getTargetV(); + double remoteNominalV = generator.getRegulatingTerminal().getVoltageLevel().getNominalV(); + double localNominalV = generator.getTerminal().getVoltageLevel().getNominalV(); + double localTargetV = localNominalV * remoteTargetV / remoteNominalV; + + // Change the regulation (local instead of remote) + generator.setRegulatingTerminal(generator.getTerminal()); + generator.setTargetV(localTargetV); + + // Notify the change + LOG.info("Changed regulation for generator: {} to local instead of remote", generator.getId()); + generatorLocalRegulationReport(reportNode, generator.getId()); + } + + @Override + public NetworkModificationImpact hasImpactOnNetwork(Network network) { + Generator generator = network.getGenerator(generatorId); + if (generator == null) { + impact = NetworkModificationImpact.CANNOT_BE_APPLIED; + } else if (generator.getId().equals(generator.getRegulatingTerminal().getConnectable().getId())) { + impact = NetworkModificationImpact.NO_IMPACT_ON_NETWORK; + } else { + impact = DEFAULT_IMPACT; + } + return impact; + } +} diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/ModificationReports.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/ModificationReports.java index 35cae543424..1583c702e11 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/ModificationReports.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/ModificationReports.java @@ -26,6 +26,9 @@ public final class ModificationReports { private static final String IDENTIFIABLE_ID = "identifiableId"; private static final String IDENTIFIABLE_TYPE = "identifiableType"; private static final String HVDC_LINE_ID = "hvdcLineId"; + private static final String TWO_WINDINGS_TRANSFORMER_ID = "twoWindingsTransformerId"; + private static final String THREE_WINDINGS_TRANSFORMER_ID = "threeWindingsTransformerId"; + private static final String EXTENSIONS = "extensions"; public static final String POSITION_ORDER = "positionOrder"; // INFO @@ -78,6 +81,109 @@ public static void createdLineReport(ReportNode reportNode, String lineId) { .add(); } + public static void createdVoltageLevelReport(ReportNode reportNode, String voltageLevelId) { + reportNode.newReportNode() + .withMessageTemplate("voltageLevelCreated", "VoltageLevel ${voltageLevelId} created") + .withUntypedValue(VOLTAGE_LEVEL_ID, voltageLevelId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void createdTwoWindingsTransformerReport(ReportNode reportNode, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("twoWindingsTransformerCreated", "TwoWindingsTransformer ${twoWindingsTransformerId} created") + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void createdThreeWindingsTransformerReport(ReportNode reportNode, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("threeWindingsTransformerCreated", "ThreeWindingsTransformer ${threeWindingsTransformerId} created") + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void removedTwoWindingsTransformerReport(ReportNode reportNode, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("twoWindingsTransformerRemoved", "TwoWindingsTransformer ${twoWindingsTransformerId} removed") + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void removedThreeWindingsTransformerReport(ReportNode reportNode, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("threeWindingsTransformerRemoved", "ThreeWindingsTransformer ${threeWindingsTransformerId} removed") + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void lostTwoWindingsTransformerAliases(ReportNode reportNode, String aliases, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostTwoWindingsTransformerAliases", "Alias [${aliases}] of twoWindingsTransformer ${twoWindingsTransformerId} will be lost") + .withUntypedValue("aliases", aliases) + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostThreeWindingsTransformerAliases(ReportNode reportNode, String aliases, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostThreeWindingsTransformerAliases", "Alias [${aliases}] of threeWindingsTransformer ${threeWindingsTransformerId} will be lost") + .withUntypedValue("aliases", aliases) + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostTwoWindingsTransformerProperties(ReportNode reportNode, String properties, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostTwoWindingsTransformerProperties", "Property [${properties}] of twoWindingsTransformer ${twoWindingsTransformerId} will be lost") + .withUntypedValue("properties", properties) + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostThreeWindingsTransformerProperties(ReportNode reportNode, String properties, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostThreeWindingsTransformerProperties", "Property [${properties}] of threeWindingsTransformer ${threeWindingsTransformerId} will be lost") + .withUntypedValue("properties", properties) + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostTwoWindingsTransformerExtensions(ReportNode reportNode, String extensions, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostTwoWindingsTransformerExtensions", "Extension [${extensions}] of twoWindingsTransformer ${twoWindingsTransformerId} will be lost") + .withUntypedValue(EXTENSIONS, extensions) + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostThreeWindingsTransformerExtensions(ReportNode reportNode, String extensions, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostThreeWindingsTransformerExtensions", "Extension [${extensions}] of threeWindingsTransformer ${threeWindingsTransformerId} will be lost") + .withUntypedValue(EXTENSIONS, extensions) + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostTwoWindingsTransformerOperationalLimitsGroups(ReportNode reportNode, String limits, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostTwoWindingsTransformerOperationalLimitsGroups", "OperationalLimitsGroups [${limits}] of twoWindingsTransformer ${twoWindingsTransformerId} will be lost") + .withUntypedValue("limits", limits) + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + public static void voltageLevelRemovedReport(ReportNode reportNode, String vlId) { reportNode.newReportNode() .withMessageTemplate("voltageLevelRemoved", "Voltage level ${vlId} removed") @@ -239,7 +345,7 @@ public static void ignoredPositionOrder(ReportNode reportNode, int positionOrder public static void lostDanglingLineExtensions(ReportNode reportNode, String extensions, String danglingLineId) { reportNode.newReportNode() .withMessageTemplate("lostDanglingLineExtensions", "Extension [${extensions}] of dangling line ${danglingLineId} will be lost") - .withUntypedValue("extensions", extensions) + .withUntypedValue(EXTENSIONS, extensions) .withUntypedValue("danglingLineId", danglingLineId) .withSeverity(TypedValue.WARN_SEVERITY) .add(); @@ -248,7 +354,7 @@ public static void lostDanglingLineExtensions(ReportNode reportNode, String exte public static void lostTieLineExtensions(ReportNode reportNode, String extensions, String tieLineId) { reportNode.newReportNode() .withMessageTemplate("lostTieLineExtensions", "Extension [${extensions}] of tie line ${tieLineId} will be lost") - .withUntypedValue("extensions", extensions) + .withUntypedValue(EXTENSIONS, extensions) .withUntypedValue("tieLineId", tieLineId) .withSeverity(TypedValue.WARN_SEVERITY) .add(); @@ -563,54 +669,76 @@ private ModificationReports() { public static void scalingReport(ReportNode reportNode, String type, DistributionMode mode, ScalingType scalingType, double asked, double done) { reportNode.newReportNode() - .withMessageTemplate("scalingApplied", "Successfully scaled on ${identifiableType} using mode ${mode} and type ${type} with a variation value asked of ${asked}. Variation done is ${done}") - .withUntypedValue(IDENTIFIABLE_TYPE, type) - .withUntypedValue("mode", mode.name()) - .withUntypedValue("type", scalingType.name()) - .withUntypedValue("asked", asked) - .withUntypedValue("done", done) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); + .withMessageTemplate("scalingApplied", "Successfully scaled on ${identifiableType} using mode ${mode} and type ${type} with a variation value asked of ${asked}. Variation done is ${done}") + .withUntypedValue(IDENTIFIABLE_TYPE, type) + .withUntypedValue("mode", mode.name()) + .withUntypedValue("type", scalingType.name()) + .withUntypedValue("asked", asked) + .withUntypedValue("done", done) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); } public static void scalingReport(ReportNode reportNode, String type, ScalingType scalingType, double asked, double done) { reportNode.newReportNode() - .withMessageTemplate("scalingApplied", "Successfully scaled on ${identifiableType} using mode STACKING and type ${type} with a variation value asked of ${asked}. Variation done is ${done}") - .withUntypedValue(IDENTIFIABLE_TYPE, type) - .withUntypedValue("type", scalingType.name()) - .withUntypedValue("asked", asked) - .withUntypedValue("done", done) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); + .withMessageTemplate("scalingApplied", "Successfully scaled on ${identifiableType} using mode STACKING and type ${type} with a variation value asked of ${asked}. Variation done is ${done}") + .withUntypedValue(IDENTIFIABLE_TYPE, type) + .withUntypedValue("type", scalingType.name()) + .withUntypedValue("asked", asked) + .withUntypedValue("done", done) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); } public static void connectableConnectionReport(ReportNode reportNode, Identifiable identifiable, boolean connectionSuccessful, ThreeSides side) { String defaultMessage = connectionSuccessful ? - "Connectable ${identifiable} has been connected" : - "Connectable ${identifiable} has NOT been connected"; + "Connectable ${identifiable} has been connected" : + "Connectable ${identifiable} has NOT been connected"; defaultMessage += side == null ? " on each side." : " on side " + side.getNum() + "."; String key = connectionSuccessful ? "connectableConnected" : "connectableNotConnected"; key += side == null ? "" : "Side" + side.getNum(); reportNode.newReportNode() - .withMessageTemplate(key, defaultMessage) - .withUntypedValue("identifiable", identifiable.getId()) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); + .withMessageTemplate(key, defaultMessage) + .withUntypedValue("identifiable", identifiable.getId()) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); } public static void identifiableDisconnectionReport(ReportNode reportNode, Identifiable identifiable, boolean disconnectionSuccessful, boolean isPlanned, ThreeSides side) { String defaultMessage = disconnectionSuccessful ? - "Identifiable ${identifiable} has been disconnected" : - "Identifiable ${identifiable} has NOT been disconnected"; + "Identifiable ${identifiable} has been disconnected" : + "Identifiable ${identifiable} has NOT been disconnected"; defaultMessage += isPlanned ? " (planned disconnection)" : " (unplanned disconnection)"; defaultMessage += side == null ? " on each side." : " on side " + side.getNum() + "."; String key = isPlanned ? "planned" : "unplanned"; key += disconnectionSuccessful ? "IdentifiableDisconnected" : "IdentifiableNotDisconnected"; key += side == null ? "" : "Side" + side.getNum(); reportNode.newReportNode() - .withMessageTemplate(key, defaultMessage) - .withUntypedValue("identifiable", identifiable.getId()) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); + .withMessageTemplate(key, defaultMessage) + .withUntypedValue("identifiable", identifiable.getId()) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static ReportNode replaceThreeWindingsTransformersBy3TwoWindingsTransformersReport(ReportNode reportNode) { + return reportNode.newReportNode() + .withMessageTemplate("replaced-t3w-by-3t2w", "Replaced ThreeWindingsTransformer by 3 TwoWindingsTransformers") + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static ReportNode replace3TwoWindingsTransformersByThreeWindingsTransformersReport(ReportNode reportNode) { + return reportNode.newReportNode() + .withMessageTemplate("replaced-3t2w-by-t3w", "Replaced 3 TwoWindingsTransformers by ThreeWindingsTransformer") + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void generatorLocalRegulationReport(ReportNode reportNode, String generatorId) { + reportNode.newReportNode() + .withMessageTemplate("generatorLocalRegulation", "Changed regulation for generator ${generatorId} to local instead of remote") + .withUntypedValue("generatorId", generatorId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/RegulatedTerminalControllers.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/RegulatedTerminalControllers.java new file mode 100644 index 00000000000..14a3c9f64a6 --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/RegulatedTerminalControllers.java @@ -0,0 +1,238 @@ +/** + * 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.iidm.modification.util; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.extensions.Extension; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.RemoteReactivePowerControl; +import com.powsybl.iidm.network.extensions.SlackTerminal; +import com.powsybl.iidm.network.extensions.VoltageRegulation; + +import java.util.*; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +public class RegulatedTerminalControllers { + + private final Network network; + private final Map>> controllers; + + public RegulatedTerminalControllers(Network network) { + this.network = network; + this.controllers = new HashMap<>(); + findRegulatedTerminalControllers(); + } + + private void findRegulatedTerminalControllers() { + network.getIdentifiables().forEach(identifiable -> { + List regulatedTerminals = findRegulatedTerminals(identifiable); + regulatedTerminals.forEach(regulatedTerminal -> controllers.computeIfAbsent(regulatedTerminal, k -> new ArrayList<>()).add(identifiable)); + }); + } + + private List findRegulatedTerminals(Identifiable identifiable) { + List terminals = findRegulatedTerminalsInModel(identifiable); + terminals.addAll(findRegulatedTerminalsInExtensions(identifiable)); + return terminals; + } + + private List findRegulatedTerminalsInModel(Identifiable identifiable) { + List regulatedTerminals = new ArrayList<>(); + switch (identifiable.getType()) { + case TWO_WINDINGS_TRANSFORMER -> { + TwoWindingsTransformer t2w = (TwoWindingsTransformer) identifiable; + t2w.getOptionalRatioTapChanger().ifPresent(rtc -> add(regulatedTerminals, rtc.getRegulationTerminal())); + t2w.getOptionalPhaseTapChanger().ifPresent(ptc -> add(regulatedTerminals, ptc.getRegulationTerminal())); + } + case THREE_WINDINGS_TRANSFORMER -> { + ThreeWindingsTransformer t3w = (ThreeWindingsTransformer) identifiable; + t3w.getLeg1().getOptionalRatioTapChanger().ifPresent(rtc -> add(regulatedTerminals, rtc.getRegulationTerminal())); + t3w.getLeg1().getOptionalPhaseTapChanger().ifPresent(ptc -> add(regulatedTerminals, ptc.getRegulationTerminal())); + t3w.getLeg2().getOptionalRatioTapChanger().ifPresent(rtc -> add(regulatedTerminals, rtc.getRegulationTerminal())); + t3w.getLeg2().getOptionalPhaseTapChanger().ifPresent(ptc -> add(regulatedTerminals, ptc.getRegulationTerminal())); + t3w.getLeg3().getOptionalRatioTapChanger().ifPresent(rtc -> add(regulatedTerminals, rtc.getRegulationTerminal())); + t3w.getLeg3().getOptionalPhaseTapChanger().ifPresent(ptc -> add(regulatedTerminals, ptc.getRegulationTerminal())); + } + case GENERATOR -> { + Generator generator = (Generator) identifiable; + add(regulatedTerminals, generator.getRegulatingTerminal()); + } + case SHUNT_COMPENSATOR -> { + ShuntCompensator shuntCompensator = (ShuntCompensator) identifiable; + add(regulatedTerminals, shuntCompensator.getRegulatingTerminal()); + } + case STATIC_VAR_COMPENSATOR -> { + StaticVarCompensator staticVarCompensator = (StaticVarCompensator) identifiable; + add(regulatedTerminals, staticVarCompensator.getRegulatingTerminal()); + } + case HVDC_CONVERTER_STATION -> { + HvdcConverterStation hvdcConverterStation = (HvdcConverterStation) identifiable; + if (hvdcConverterStation.getHvdcType() == HvdcConverterStation.HvdcType.VSC) { + VscConverterStation vscConverterStation = (VscConverterStation) hvdcConverterStation; + add(regulatedTerminals, vscConverterStation.getRegulatingTerminal()); + } + } + default -> { + // do nothing + } + } + return regulatedTerminals; + } + + private static void add(List regulatedTerminals, Terminal regulatedTerminal) { + if (regulatedTerminal != null) { + regulatedTerminals.add(newTerminalRef(regulatedTerminal)); + } + } + + private List findRegulatedTerminalsInExtensions(Identifiable identifiable) { + List regulatedTerminals = new ArrayList<>(); + identifiable.getExtensions().stream().map(Extension::getName).forEach(extensionName -> + add(regulatedTerminals, findRegulatedTerminalInExtension(identifiable, extensionName))); + return regulatedTerminals; + } + + private static Terminal findRegulatedTerminalInExtension(Identifiable identifiable, String extensionName) { + switch (extensionName) { + case "voltageRegulation" -> { + Battery battery = (Battery) identifiable; + VoltageRegulation voltageRegulation = battery.getExtension(VoltageRegulation.class); + return voltageRegulation.getRegulatingTerminal(); + } + case "generatorRemoteReactivePowerControl" -> { + Generator generator = (Generator) identifiable; + RemoteReactivePowerControl remoteReactivePowerControl = generator.getExtension(RemoteReactivePowerControl.class); + return remoteReactivePowerControl.getRegulatingTerminal(); + } + case "slackTerminal" -> { + VoltageLevel voltageLevel = (VoltageLevel) identifiable; + SlackTerminal slackTerminal = voltageLevel.getExtension(SlackTerminal.class); + return slackTerminal.getTerminal(); + } + default -> { + return null; + } + } + } + + public boolean usedAsRegulatedTerminal(Terminal regulatedTerminal) { + Objects.requireNonNull(regulatedTerminal); + return controllers.containsKey(newTerminalRef(regulatedTerminal)); + } + + public void replaceRegulatedTerminal(Terminal currentRegulatedTerminal, Terminal newRegulatedTerminal) { + Objects.requireNonNull(currentRegulatedTerminal); + Objects.requireNonNull(newRegulatedTerminal); + TerminalRef currentRegulatedTerminalRef = newTerminalRef(currentRegulatedTerminal); + if (controllers.containsKey(currentRegulatedTerminalRef)) { + controllers.get(currentRegulatedTerminalRef).forEach(identifiable -> replaceRegulatedTerminal(identifiable, currentRegulatedTerminalRef, newRegulatedTerminal)); + } + } + + private static void replaceRegulatedTerminal(Identifiable identifiable, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + switch (identifiable.getType()) { + case TWO_WINDINGS_TRANSFORMER -> + replaceRegulatedTerminalTwoWindingsTransformer((TwoWindingsTransformer) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case THREE_WINDINGS_TRANSFORMER -> + replaceRegulatedTerminalThreeWindingsTransformer((ThreeWindingsTransformer) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case GENERATOR -> + replaceRegulatedTerminalGenerator((Generator) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case SHUNT_COMPENSATOR -> + replaceRegulatedTerminalShuntCompensator((ShuntCompensator) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case STATIC_VAR_COMPENSATOR -> + replaceRegulatedTerminalStaticVarCompensator((StaticVarCompensator) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case HVDC_CONVERTER_STATION -> + replaceRegulatedTerminalHvdcConverterStation((HvdcConverterStation) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case BATTERY -> + replaceRegulatedTerminalBattery((Battery) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case VOLTAGE_LEVEL -> + replaceRegulatedTerminalVoltageLevel((VoltageLevel) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + default -> throw new PowsyblException("unexpected identifiable type: " + identifiable.getType()); + } + } + + private static void replaceRegulatedTerminalTwoWindingsTransformer(TwoWindingsTransformer t2w, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + t2w.getOptionalRatioTapChanger().ifPresent(rtc -> replace(rtc, currentRegulatedTerminal, newRegulatedTerminal)); + t2w.getOptionalPhaseTapChanger().ifPresent(ptc -> replace(ptc, currentRegulatedTerminal, newRegulatedTerminal)); + } + + private static void replaceRegulatedTerminalThreeWindingsTransformer(ThreeWindingsTransformer t3w, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + t3w.getLeg1().getOptionalRatioTapChanger().ifPresent(rtc -> replace(rtc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg1().getOptionalPhaseTapChanger().ifPresent(ptc -> replace(ptc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg2().getOptionalRatioTapChanger().ifPresent(rtc -> replace(rtc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg2().getOptionalPhaseTapChanger().ifPresent(ptc -> replace(ptc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg3().getOptionalRatioTapChanger().ifPresent(rtc -> replace(rtc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg3().getOptionalPhaseTapChanger().ifPresent(ptc -> replace(ptc, currentRegulatedTerminal, newRegulatedTerminal)); + } + + private static void replace(TapChanger tc, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (tc.getRegulationTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(tc.getRegulationTerminal()))) { + tc.setRegulationTerminal(newRegulatedTerminal); + } + } + + private static void replaceRegulatedTerminalGenerator(Generator generator, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (generator.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(generator.getRegulatingTerminal()))) { + generator.setRegulatingTerminal(newRegulatedTerminal); + } else { + RemoteReactivePowerControl remoteReactivePowerControl = generator.getExtension(RemoteReactivePowerControl.class); + if (remoteReactivePowerControl != null + && remoteReactivePowerControl.getRegulatingTerminal() != null + && currentRegulatedTerminal.equals(newTerminalRef(remoteReactivePowerControl.getRegulatingTerminal()))) { + remoteReactivePowerControl.setRegulatingTerminal(newRegulatedTerminal); + } + } + } + + private static void replaceRegulatedTerminalShuntCompensator(ShuntCompensator shuntCompensator, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (shuntCompensator.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(shuntCompensator.getRegulatingTerminal()))) { + shuntCompensator.setRegulatingTerminal(newRegulatedTerminal); + } + } + + private static void replaceRegulatedTerminalStaticVarCompensator(StaticVarCompensator staticVarCompensator, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (staticVarCompensator.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(staticVarCompensator.getRegulatingTerminal()))) { + staticVarCompensator.setRegulatingTerminal(newRegulatedTerminal); + } + } + + private static void replaceRegulatedTerminalHvdcConverterStation(HvdcConverterStation hvdcConverterStation, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (hvdcConverterStation.getHvdcType() == HvdcConverterStation.HvdcType.VSC) { + VscConverterStation vscConverterStation = (VscConverterStation) hvdcConverterStation; + if (vscConverterStation.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(vscConverterStation.getRegulatingTerminal()))) { + vscConverterStation.setRegulatingTerminal(newRegulatedTerminal); + } + } + } + + private static void replaceRegulatedTerminalBattery(Battery battery, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + VoltageRegulation voltageRegulation = battery.getExtension(VoltageRegulation.class); + if (voltageRegulation != null && voltageRegulation.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(voltageRegulation.getRegulatingTerminal()))) { + voltageRegulation.setRegulatingTerminal(newRegulatedTerminal); + } + } + + private static void replaceRegulatedTerminalVoltageLevel(VoltageLevel voltageLevel, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + SlackTerminal slackTerminal = voltageLevel.getExtension(SlackTerminal.class); + if (slackTerminal != null && slackTerminal.getTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(slackTerminal.getTerminal()))) { + slackTerminal.setTerminal(newRegulatedTerminal); + } + } + + private static TerminalRef newTerminalRef(Terminal terminal) { + Objects.requireNonNull(terminal); + return new TerminalRef(terminal.getConnectable().getId(), terminal.getSide()); + } + + // To avoid comparing regulating terminal objects, with custom IIDM implementations could be problematic. + private record TerminalRef(String identifiableId, ThreeSides side) { + } +} diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/TransformerUtils.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/TransformerUtils.java new file mode 100644 index 00000000000..794cabf512e --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/TransformerUtils.java @@ -0,0 +1,233 @@ +/** + * 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.iidm.modification.util; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import com.powsybl.iidm.network.util.LoadingLimitsUtil; +import org.apache.commons.math3.complex.Complex; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +public final class TransformerUtils { + + private TransformerUtils() { + } + + public static void copyAndAddRatioTapChanger(RatioTapChangerAdder rtcAdder, RatioTapChanger rtc) { + copyCommonRatioTapChanger(rtcAdder, rtc); + + rtc.getAllSteps().keySet().stream().sorted().forEach(step -> + rtcAdder.beginStep() + .setR(rtc.getStep(step).getR()) + .setX(rtc.getStep(step).getX()) + .setG(rtc.getStep(step).getG()) + .setB(rtc.getStep(step).getB()) + .setRho(rtc.getStep(step).getRho()) + .endStep()); + rtcAdder.add(); + } + + public static void copyAndMoveAndAddRatioTapChanger(RatioTapChangerAdder rtcAdder, RatioTapChanger rtc) { + copyCommonRatioTapChanger(rtcAdder, rtc); + + rtc.getAllSteps().keySet().stream().sorted().forEach(step -> { + double rho = rtc.getStep(step).getRho(); + double r = rtc.getStep(step).getR(); + double x = rtc.getStep(step).getX(); + double g = rtc.getStep(step).getG(); + double b = rtc.getStep(step).getB(); + + Complex ratio = obtainComplexRatio(1.0 / rho, 0.0); + Complex movedRatio = ratio.reciprocal(); + + double rCorrection = 100 * (impedanceConversion(1 + r / 100, ratio) - 1); + double xCorrection = 100 * (impedanceConversion(1 + x / 100, ratio) - 1); + double gCorrection = 100 * (admittanceConversion(1 + g / 100, ratio) - 1); + double bCorrection = 100 * (admittanceConversion(1 + b / 100, ratio) - 1); + + rtcAdder.beginStep() + .setR(rCorrection) + .setX(xCorrection) + .setG(gCorrection) + .setB(bCorrection) + .setRho(1.0 / movedRatio.abs()) + .endStep(); + }); + rtcAdder.add(); + } + + private static void copyCommonRatioTapChanger(RatioTapChangerAdder rtcAdder, RatioTapChanger rtc) { + rtcAdder.setTapPosition(rtc.getTapPosition()) + .setTargetV(rtc.getTargetV()) + .setLoadTapChangingCapabilities(rtc.hasLoadTapChangingCapabilities()) + .setRegulationMode(rtc.getRegulationMode()) + .setRegulationValue(rtc.getRegulationValue()) + .setLowTapPosition(rtc.getLowTapPosition()) + .setRegulating(rtc.isRegulating()) + .setRegulationTerminal(rtc.getRegulationTerminal()) + .setTargetDeadband(rtc.getTargetDeadband()); + } + + public static void copyAndAddPhaseTapChanger(PhaseTapChangerAdder ptcAdder, PhaseTapChanger ptc) { + copyCommonPhaseTapChanger(ptcAdder, ptc); + + ptc.getAllSteps().keySet().stream().sorted().forEach(step -> + ptcAdder.beginStep() + .setR(ptc.getStep(step).getR()) + .setX(ptc.getStep(step).getX()) + .setG(ptc.getStep(step).getG()) + .setB(ptc.getStep(step).getB()) + .setRho(ptc.getStep(step).getRho()) + .setAlpha(ptc.getStep(step).getAlpha()) + .endStep()); + ptcAdder.add(); + } + + public static void copyAndMoveAndAddPhaseTapChanger(PhaseTapChangerAdder ptcAdder, PhaseTapChanger ptc) { + copyCommonPhaseTapChanger(ptcAdder, ptc); + + ptc.getAllSteps().keySet().stream().sorted().forEach(step -> { + double rho = ptc.getStep(step).getRho(); + double alpha = ptc.getStep(step).getAlpha(); + double r = ptc.getStep(step).getR(); + double x = ptc.getStep(step).getX(); + double g = ptc.getStep(step).getG(); + double b = ptc.getStep(step).getB(); + + Complex ratio = obtainComplexRatio(1.0 / rho, -alpha); + Complex movedRatio = ratio.reciprocal(); + + double rCorrection = 100 * (impedanceConversion(1 + r / 100, ratio) - 1); + double xCorrection = 100 * (impedanceConversion(1 + x / 100, ratio) - 1); + double gCorrection = 100 * (admittanceConversion(1 + g / 100, ratio) - 1); + double bCorrection = 100 * (admittanceConversion(1 + b / 100, ratio) - 1); + + ptcAdder.beginStep() + .setR(rCorrection) + .setX(xCorrection) + .setG(gCorrection) + .setB(bCorrection) + .setRho(1.0 / movedRatio.abs()) + .setAlpha(-Math.toDegrees(movedRatio.getArgument())) + .endStep(); + }); + ptcAdder.add(); + } + + private static void copyCommonPhaseTapChanger(PhaseTapChangerAdder ptcAdder, PhaseTapChanger ptc) { + ptcAdder.setTapPosition(ptc.getTapPosition()) + .setRegulationMode(ptc.getRegulationMode()) + .setRegulationValue(ptc.getRegulationValue()) + .setLowTapPosition(ptc.getLowTapPosition()) + .setRegulationTerminal(ptc.getRegulationTerminal()) + .setTargetDeadband(ptc.getTargetDeadband()) + .setRegulating(ptc.isRegulating()); + } + + private static Complex obtainComplexRatio(double ratio, double angle) { + return new Complex(ratio * Math.cos(Math.toRadians(angle)), ratio * Math.sin(Math.toRadians(angle))); + } + + public static double impedanceConversion(double impedance, Complex a) { + return impedance * a.abs() * a.abs(); + } + + public static double impedanceConversion(double impedance, double a) { + return impedance * a * a; + } + + public static double admittanceConversion(double admittance, Complex a) { + return admittance / (a.abs() * a.abs()); + } + + public static double admittanceConversion(double admittance, double a) { + return admittance / (a * a); + } + + public static void copyOperationalLimitsGroup(OperationalLimitsGroup destination, OperationalLimitsGroup source) { + source.getActivePowerLimits().ifPresent(activePowerLimits -> LoadingLimitsUtil.initializeFromLoadingLimits(destination.newActivePowerLimits(), activePowerLimits).add()); + source.getApparentPowerLimits().ifPresent(apparentPowerLimits -> LoadingLimitsUtil.initializeFromLoadingLimits(destination.newApparentPowerLimits(), apparentPowerLimits).add()); + source.getCurrentLimits().ifPresent(currentLimits -> LoadingLimitsUtil.initializeFromLoadingLimits(destination.newCurrentLimits(), currentLimits).add()); + } + + public static void copyTerminalActiveAndReactivePower(Terminal destinationTerminal, Terminal sourceTerminal) { + destinationTerminal.setP(sourceTerminal.getP()); + destinationTerminal.setQ(sourceTerminal.getQ()); + } + + public static void copyAndAddFortescue(TwoWindingsTransformerFortescueAdder t2wFortescueAdder, ThreeWindingsTransformerFortescue.LegFortescue legFortescue) { + t2wFortescueAdder.withConnectionType1(legFortescue.getConnectionType()) + .withFreeFluxes(legFortescue.isFreeFluxes()) + .withGroundingR1(legFortescue.getGroundingR()) + .withGroundingX1(legFortescue.getGroundingX()) + .withRz(legFortescue.getRz()) + .withXz(legFortescue.getXz()) + .withConnectionType2(WindingConnectionType.Y) + .withGroundingR2(.0) + .withGroundingX2(.0); + t2wFortescueAdder.add(); + } + + public static void copyAndAddFortescue(ThreeWindingsTransformerFortescueAdder t3wFortescue, TwoWindingsTransformerFortescue t2w1Fortescue, boolean isWellOrientedT2w1, TwoWindingsTransformerFortescue t2w2Fortescue, boolean isWellOrientedT2w2, TwoWindingsTransformerFortescue t2w3Fortescue, boolean isWellOrientedT2w3) { + copyFortescueLeg(t3wFortescue.leg1(), t2w1Fortescue, isWellOrientedT2w1); + copyFortescueLeg(t3wFortescue.leg2(), t2w2Fortescue, isWellOrientedT2w2); + copyFortescueLeg(t3wFortescue.leg3(), t2w3Fortescue, isWellOrientedT2w3); + t3wFortescue.add(); + } + + private static void copyFortescueLeg(ThreeWindingsTransformerFortescueAdder.LegFortescueAdder legFortescueAdder, TwoWindingsTransformerFortescue t2wFortescue, boolean isWellOrientedT2w) { + if (t2wFortescue != null) { + legFortescueAdder.withConnectionType(isWellOrientedT2w ? t2wFortescue.getConnectionType1() : t2wFortescue.getConnectionType2()) + .withFreeFluxes(t2wFortescue.isFreeFluxes()) + .withGroundingR(isWellOrientedT2w ? t2wFortescue.getGroundingR1() : t2wFortescue.getGroundingR2()) + .withGroundingX(isWellOrientedT2w ? t2wFortescue.getGroundingX1() : t2wFortescue.getGroundingX2()) + .withRz(t2wFortescue.getRz()) + .withXz(t2wFortescue.getXz()); + } + } + + public static void copyAndAddPhaseAngleClock(TwoWindingsTransformerPhaseAngleClockAdder phaseAngleClockAdder, int phaseAngleClock) { + phaseAngleClockAdder.withPhaseAngleClock(phaseAngleClock); + phaseAngleClockAdder.add(); + } + + public static void copyAndAddPhaseAngleClock(ThreeWindingsTransformerPhaseAngleClockAdder phaseAngleClockAdder, TwoWindingsTransformerPhaseAngleClock t2w2PhaseAngleClock, TwoWindingsTransformerPhaseAngleClock t2w3PhaseAngleClock) { + if (t2w2PhaseAngleClock != null) { + phaseAngleClockAdder.withPhaseAngleClockLeg2(t2w2PhaseAngleClock.getPhaseAngleClock()); + } + if (t2w3PhaseAngleClock != null) { + phaseAngleClockAdder.withPhaseAngleClockLeg3(t2w3PhaseAngleClock.getPhaseAngleClock()); + } + phaseAngleClockAdder.add(); + } + + public static void copyAndAddToBeEstimated(TwoWindingsTransformerToBeEstimatedAdder toBeEstimatedAdder, boolean shouldEstimateRatioTapChanger, boolean shouldEstimatePhaseTapChanger) { + toBeEstimatedAdder.withRatioTapChangerStatus(shouldEstimateRatioTapChanger) + .withPhaseTapChangerStatus(shouldEstimatePhaseTapChanger); + toBeEstimatedAdder.add(); + } + + public static void copyAndAddToBeEstimated(ThreeWindingsTransformerToBeEstimatedAdder toBeEstimatedAdder, TwoWindingsTransformerToBeEstimated t2w1ToBeEstimated, TwoWindingsTransformerToBeEstimated t2w2ToBeEstimated, TwoWindingsTransformerToBeEstimated t2w3ToBeEstimated) { + if (t2w1ToBeEstimated != null) { + toBeEstimatedAdder.withRatioTapChanger1Status(t2w1ToBeEstimated.shouldEstimateRatioTapChanger()) + .withPhaseTapChanger1Status(t2w1ToBeEstimated.shouldEstimatePhaseTapChanger()); + } + if (t2w2ToBeEstimated != null) { + toBeEstimatedAdder.withRatioTapChanger2Status(t2w2ToBeEstimated.shouldEstimateRatioTapChanger()) + .withPhaseTapChanger2Status(t2w2ToBeEstimated.shouldEstimatePhaseTapChanger()); + } + if (t2w3ToBeEstimated != null) { + toBeEstimatedAdder.withRatioTapChanger3Status(t2w3ToBeEstimated.shouldEstimateRatioTapChanger()) + .withPhaseTapChanger3Status(t2w3ToBeEstimated.shouldEstimatePhaseTapChanger()); + } + toBeEstimatedAdder.add(); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/AreaInterchangeModificationTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/AreaInterchangeModificationTest.java new file mode 100644 index 00000000000..cb3e591de58 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/AreaInterchangeModificationTest.java @@ -0,0 +1,81 @@ +/* + * 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.iidm.modification; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.Area; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Bertrand Rix {@literal } + */ +class AreaInterchangeModificationTest { + + private Network network; + private Area area; + + @BeforeEach + public void setUp() { + network = EurostagTutorialExample1Factory.createWithTieLinesAndAreas(); + assertTrue(network.getAreaCount() > 0); + area = network.getArea("ControlArea_A"); + } + + @Test + void testModification() { + AreaInterchangeTargetModification modification = new AreaInterchangeTargetModification(area.getId(), -750); + assertTrue(area.getInterchangeTarget().isPresent()); + assertEquals(-602.6, area.getInterchangeTarget().getAsDouble()); + modification.apply(network); + assertEquals(-750, area.getInterchangeTarget().getAsDouble()); + + AreaInterchangeTargetModification modification2 = new AreaInterchangeTargetModification(area.getId(), Double.NaN); + modification2.apply(network); + assertTrue(area.getInterchangeTarget().isEmpty()); + + AreaInterchangeTargetModification modification3 = new AreaInterchangeTargetModification("AREA_UNKNOWN", 2.0); + PowsyblException exception = assertThrows(PowsyblException.class, () -> modification3.apply(network, true, ReportNode.NO_OP)); + assertEquals("Area 'AREA_UNKNOWN' not found", exception.getMessage()); + } + + @Test + void testModificationGetters() { + AreaInterchangeTargetModification modification = new AreaInterchangeTargetModification(area.getId(), 1.0); + assertEquals(area.getId(), modification.getAreaId()); + assertEquals(1.0, modification.getInterchangeTarget()); + } + + @Test + void testHasImpact() { + AreaInterchangeTargetModification modification1 = new AreaInterchangeTargetModification("AREA_NOT_EXISTING", 2.0); + assertEquals(NetworkModificationImpact.CANNOT_BE_APPLIED, modification1.hasImpactOnNetwork(network)); + + AreaInterchangeTargetModification modification2 = new AreaInterchangeTargetModification(area.getId(), 1.0); + assertEquals(NetworkModificationImpact.HAS_IMPACT_ON_NETWORK, modification2.hasImpactOnNetwork(network)); + + AreaInterchangeTargetModification modification3 = new AreaInterchangeTargetModification(area.getId(), Double.NaN); + assertEquals(NetworkModificationImpact.HAS_IMPACT_ON_NETWORK, modification3.hasImpactOnNetwork(network)); + + AreaInterchangeTargetModification modification4 = new AreaInterchangeTargetModification(area.getId(), -602.6); + assertEquals(NetworkModificationImpact.NO_IMPACT_ON_NETWORK, modification4.hasImpactOnNetwork(network)); + + area.setInterchangeTarget(Double.NaN); + AreaInterchangeTargetModification modification5 = new AreaInterchangeTargetModification(area.getId(), Double.NaN); + assertEquals(NetworkModificationImpact.NO_IMPACT_ON_NETWORK, modification5.hasImpactOnNetwork(network)); + + AreaInterchangeTargetModification modification6 = new AreaInterchangeTargetModification(area.getId(), 1.0); + assertEquals(NetworkModificationImpact.HAS_IMPACT_ON_NETWORK, modification6.hasImpactOnNetwork(network)); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ControlledRegulatingTerminalsTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ControlledRegulatingTerminalsTest.java new file mode 100644 index 00000000000..0585c179115 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ControlledRegulatingTerminalsTest.java @@ -0,0 +1,202 @@ +/** + * 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.iidm.modification; + +import com.powsybl.iidm.modification.util.RegulatedTerminalControllers; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import com.powsybl.iidm.network.test.BatteryNetworkFactory; +import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; +import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory; +import org.junit.jupiter.api.Test; + +import static com.powsybl.iidm.modification.TransformersTestUtils.addPhaseTapChanger; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +class ControlledRegulatingTerminalsTest { + + @Test + void twoWindingsTransformerRtcTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + TwoWindingsTransformer t2w = network.getTwoWindingsTransformer("TWT"); + + assertNotEquals(t2w.getRatioTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + assertNotEquals(t2w.getPhaseTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t2w.getRatioTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t2w.getPhaseTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + assertEquals(t2w.getRatioTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + assertEquals(t2w.getPhaseTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + } + + @Test + void threeWindingsTransformerRtcTest() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + + assertNotEquals(t3w.getLeg2().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertNotEquals(t3w.getLeg3().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg2().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg3().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(t3w.getLeg2().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(t3w.getLeg3().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + } + + @Test + void threeWindingsTransformerPtcLeg1Test() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + addPhaseTapChanger(t3w.getLeg1()); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + assertNotEquals(t3w.getLeg1().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg2().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg1().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg2().getTerminal()); + assertEquals(t3w.getLeg1().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg2().getTerminal()); + } + + @Test + void threeWindingsTransformerPtcLeg2Test() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + addPhaseTapChanger(t3w.getLeg2()); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + assertNotEquals(t3w.getLeg2().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg2().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(t3w.getLeg2().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + } + + @Test + void threeWindingsTransformerPtcLeg3Test() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + addPhaseTapChanger(t3w.getLeg3()); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + assertNotEquals(t3w.getLeg3().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg3().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(t3w.getLeg3().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + } + + @Test + void generatorTest() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + Generator generator = network.getGenerator("GEN_132"); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + + assertNotEquals(generator.getRegulatingTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(generator.getRegulatingTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(generator.getRegulatingTerminal(), t3w.getLeg1().getTerminal()); + } + + @Test + void shuntCompensatorTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + ShuntCompensator shuntCompensator = network.getShuntCompensator("SHUNT"); + TwoWindingsTransformer t2w = network.getTwoWindingsTransformer("TWT"); + + assertNotEquals(shuntCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(shuntCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + assertEquals(shuntCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + } + + @Test + void staticVarCompensatorTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + StaticVarCompensator staticVarCompensator = network.getStaticVarCompensator("SVC"); + TwoWindingsTransformer t2w = network.getTwoWindingsTransformer("TWT"); + + assertNotEquals(staticVarCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(staticVarCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + assertEquals(staticVarCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + } + + @Test + void voltageSourceConverterTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + HvdcConverterStation hvdcConverterStation = network.getHvdcConverterStation("VSC1"); + VscConverterStation vscConverterStation = (VscConverterStation) hvdcConverterStation; + TwoWindingsTransformer t2w = network.getTwoWindingsTransformer("TWT"); + + assertNotEquals(vscConverterStation.getRegulatingTerminal(), t2w.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(vscConverterStation.getRegulatingTerminal(), t2w.getTerminal1()); + assertEquals(vscConverterStation.getRegulatingTerminal(), t2w.getTerminal1()); + } + + @Test + void batteryTest() { + Network network = BatteryNetworkFactory.create(); + + Battery battery = network.getBattery("BAT2"); + VoltageRegulation voltageRegulation = battery.newExtension(VoltageRegulationAdder.class) + .withRegulatingTerminal(battery.getTerminal()) + .withVoltageRegulatorOn(true) + .withTargetV(50.0) + .add(); + + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + Generator generator = network.getGenerator("GEN"); + + assertNotEquals(voltageRegulation.getRegulatingTerminal(), generator.getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(voltageRegulation.getRegulatingTerminal(), generator.getTerminal()); + assertEquals(voltageRegulation.getRegulatingTerminal(), generator.getTerminal()); + } + + @Test + void generatorRemoteReactiveControlTest() { + Network network = BatteryNetworkFactory.create(); + + Generator generator = network.getGenerator("GEN"); + RemoteReactivePowerControl remoteReactivePowerControl = generator.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(100.0) + .withRegulatingTerminal(generator.getTerminal()) + .withEnabled(true) + .add(); + + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + Line line = network.getLine("NHV1_NHV2_1"); + + assertNotEquals(remoteReactivePowerControl.getRegulatingTerminal(), line.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(remoteReactivePowerControl.getRegulatingTerminal(), line.getTerminal1()); + assertEquals(remoteReactivePowerControl.getRegulatingTerminal(), line.getTerminal1()); + } + + @Test + void slackTerminalTest() { + Network network = BatteryNetworkFactory.create(); + + Generator generator = network.getGenerator("GEN"); + VoltageLevel voltageLevel = network.getVoltageLevel("VLGEN"); + SlackTerminal slackTerminal = voltageLevel.newExtension(SlackTerminalAdder.class) + .withTerminal(generator.getTerminal()) + .add(); + + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + Line line = network.getLine("NHV1_NHV2_1"); + + assertNotEquals(slackTerminal.getTerminal(), line.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(slackTerminal.getTerminal(), line.getTerminal1()); + assertEquals(slackTerminal.getTerminal(), line.getTerminal1()); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/PercentChangeLoadModificationTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/PercentChangeLoadModificationTest.java new file mode 100644 index 00000000000..c4b87f8e6db --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/PercentChangeLoadModificationTest.java @@ -0,0 +1,99 @@ +/** + * 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.iidm.modification; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.Test; + +import static com.powsybl.iidm.modification.NetworkModificationImpact.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author Benoît Chiquet {@literal } + */ +class PercentChangeLoadModificationTest { + + @Test + void shouldIncreaseP0() { + Network network = EurostagTutorialExample1Factory.create(); + Load load = network.getLoad("LOAD"); + assertEquals(600.0, load.getP0()); + PercentChangeLoadModification modification = new PercentChangeLoadModification("LOAD", 3.51, 0); + modification.apply(network); + assertEquals(621.06, load.getP0(), 0.001); + } + + @Test + void shouldDecreaseP0() { + Network network = EurostagTutorialExample1Factory.create(); + Load load = network.getLoad("LOAD"); + assertEquals(600.0, load.getP0()); + PercentChangeLoadModification modification = new PercentChangeLoadModification("LOAD", -3.5, 0); + modification.apply(network); + assertEquals(579.0, load.getP0()); + } + + @Test + void shouldIncreaseQ0() { + Network network = EurostagTutorialExample1Factory.create(); + Load load = network.getLoad("LOAD"); + assertEquals(200.0, load.getQ0()); + PercentChangeLoadModification modification = new PercentChangeLoadModification("LOAD", 0, 2.5); + modification.apply(network); + assertEquals(205.0, load.getQ0()); + } + + @Test + void shouldDecreaseQ0() { + Network network = EurostagTutorialExample1Factory.create(); + Load load = network.getLoad("LOAD"); + assertEquals(200.0, load.getQ0()); + PercentChangeLoadModification modification = new PercentChangeLoadModification("LOAD", 0, -2.5); + modification.apply(network); + assertEquals(195.0, load.getQ0()); + } + + @Test + void shouldCreatePercentChangeLoadModificationAtLimit() { + PercentChangeLoadModification modification = new PercentChangeLoadModification("LOAD", -100, -100); + assertEquals("PercentChangeLoadModification", modification.getName()); + } + + @Test + void shouldThrowWhenLoadDecreasesTooMuch() { + assertThrows(PowsyblException.class, () -> new PercentChangeLoadModification("LOAD", -101, 0)); + assertThrows(PowsyblException.class, () -> new PercentChangeLoadModification("LOAD", 0, -101)); + } + + @Test + void shouldThrowWhenLoadNotFound() { + Network network = EurostagTutorialExample1Factory.create(); + PercentChangeLoadModification modification = new PercentChangeLoadModification("LoadNotFound", 2.5, 2.5); + assertThrows(PowsyblException.class, () -> modification.apply(network, true, null)); + } + + @Test + void impactOnNetwork() { + Network network = EurostagTutorialExample1Factory.create(); + assertEquals(NO_IMPACT_ON_NETWORK, new PercentChangeLoadModification("LOAD", 0, 0).hasImpactOnNetwork(network)); + assertEquals(HAS_IMPACT_ON_NETWORK, new PercentChangeLoadModification("LOAD", 3, 0).hasImpactOnNetwork(network)); + assertEquals(HAS_IMPACT_ON_NETWORK, new PercentChangeLoadModification("LOAD", 0, 3).hasImpactOnNetwork(network)); + assertEquals(CANNOT_BE_APPLIED, new PercentChangeLoadModification("LoadNotFound", 3, 2).hasImpactOnNetwork(network)); + } + + @Test + void pctLoadModificationName() { + PercentChangeLoadModification modification = new PercentChangeLoadModification("LOAD", 3.5, 0); + assertEquals("PercentChangeLoadModification", modification.getName()); + } + +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformersTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformersTest.java new file mode 100644 index 00000000000..5d6a1a9dbb8 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformersTest.java @@ -0,0 +1,457 @@ +/** + * 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.iidm.modification; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.test.TestUtil; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.ThreeSides; +import com.powsybl.iidm.network.ThreeWindingsTransformer; +import com.powsybl.iidm.network.TwoWindingsTransformer; +import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory; +import com.powsybl.iidm.network.util.BranchData; +import com.powsybl.iidm.network.util.TwtData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collections; + +import static com.powsybl.iidm.modification.TransformersTestUtils.*; +import static com.powsybl.iidm.modification.TransformersTestUtils.addPhaseTapChanger; +import static com.powsybl.iidm.modification.util.ModificationReports.lostTwoWindingsTransformerExtensions; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +class Replace3TwoWindingsTransformersByThreeWindingsTransformersTest { + private Network network; + private TwoWindingsTransformer t2w1; + private TwoWindingsTransformer t2w2; + private TwoWindingsTransformer t2w3; + + @BeforeEach + public void setUp() { + Network expectedNetwork = createSetUpNetwork(); + assertEquals(3, expectedNetwork.getTwoWindingsTransformerCount()); + t2w1 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg1"); + t2w2 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg2"); + t2w3 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg3"); + network = createSetUpNetwork(); + } + + private static Network createSetUpNetwork() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + return network; + } + + @Test + void replaceBasicTest() { + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + } + + @Test + void replaceSelectedTwoWindingsTransformerOneTest() { + assertTrue(replaceSelectedTwoWindingsTransformerTest("3WT-Leg1")); + } + + @Test + void replaceSelectedTwoWindingsTransformerTwoTest() { + assertTrue(replaceSelectedTwoWindingsTransformerTest("3WT-Leg2")); + } + + @Test + void replaceSelectedTwoWindingsTransformerThreeTest() { + assertTrue(replaceSelectedTwoWindingsTransformerTest("3WT-Leg3")); + } + + private boolean replaceSelectedTwoWindingsTransformerTest(String t2wId) { + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(Collections.singletonList(t2wId)); + replace.apply(network); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + + return true; + } + + @Test + void nonReplacementTest() { + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + + String t2wId = "unknown twoWindingsTransformer"; + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(Collections.singletonList(t2wId)); + replace.apply(network); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + } + + @Test + void replaceRatioTapChangerTest() { + assertNull(t2w1.getRatioTapChanger()); + assertNotNull(t2w2.getRatioTapChanger()); + assertNotNull(t2w3.getRatioTapChanger()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertNull(t3w.getLeg1().getRatioTapChanger()); + assertNotNull(t3w.getLeg2().getRatioTapChanger()); + assertNotNull(t3w.getLeg3().getRatioTapChanger()); + + assertTrue(compareRatioTapChanger(t2w2.getRatioTapChanger(), t3w.getLeg2().getRatioTapChanger())); + assertTrue(compareRatioTapChanger(t2w3.getRatioTapChanger(), t3w.getLeg3().getRatioTapChanger())); + } + + @Test + void replacePhaseTapChangerTest() { + modifyNetworkForPhaseTapChangerTest(); + + assertNotNull(t2w1.getPhaseTapChanger()); + assertNull(t2w2.getPhaseTapChanger()); + assertNull(t2w3.getPhaseTapChanger()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertNotNull(t3w.getLeg1().getPhaseTapChanger()); + assertNull(t3w.getLeg2().getPhaseTapChanger()); + assertNull(t3w.getLeg3().getPhaseTapChanger()); + + assertTrue(comparePhaseTapChanger(t2w1.getPhaseTapChanger(), t3w.getLeg1().getPhaseTapChanger())); + } + + private void modifyNetworkForPhaseTapChangerTest() { + addPhaseTapChanger(t2w1); // modify expected network + addPhaseTapChanger(network.getTwoWindingsTransformer(t2w1.getId())); // modify network + } + + @Test + void replaceLoadingLimitsTest() { + modifyNetworkForLoadingLimitsTest(); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertTrue(compareOperationalLimitsGroups(t2w1.getOperationalLimitsGroups1(), t3w.getLeg1().getOperationalLimitsGroups())); + assertTrue(compareOperationalLimitsGroups(t2w2.getOperationalLimitsGroups1(), t3w.getLeg2().getOperationalLimitsGroups())); + assertTrue(compareOperationalLimitsGroups(t2w3.getOperationalLimitsGroups1(), t3w.getLeg3().getOperationalLimitsGroups())); + } + + private void modifyNetworkForLoadingLimitsTest() { + addLoadingLimitsEnd1(t2w1); + addLoadingLimitsEnd1(t2w2); + addLoadingLimitsEnd1(t2w3); + addLoadingLimitsEnd1(network.getTwoWindingsTransformer(t2w1.getId())); + addLoadingLimitsEnd1(network.getTwoWindingsTransformer(t2w2.getId())); + addLoadingLimitsEnd1(network.getTwoWindingsTransformer(t2w3.getId())); + } + + @Test + void replacePropertiesT2w1Test() { + assertTrue(replacePropertiesTwoWindingsTransformer(t2w1)); + } + + @Test + void replacePropertiesT2w2Test() { + assertTrue(replacePropertiesTwoWindingsTransformer(t2w2)); + } + + @Test + void replacePropertiesT2w3Test() { + assertTrue(replacePropertiesTwoWindingsTransformer(t2w3)); + } + + private boolean replacePropertiesTwoWindingsTransformer(TwoWindingsTransformer t2w) { + modifyNetworkForPropertiesTest(t2w); + network.getTwoWindingsTransformer(t2w.getId()).setProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-summer", "summer"); + network.getTwoWindingsTransformer(t2w.getId()).setProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-winter", "winter"); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + return t3w.getProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-summer").equals("summer") && + t3w.getProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-winter").equals("winter"); + } + + private void modifyNetworkForPropertiesTest(TwoWindingsTransformer t2w) { + addLoadingLimitsEnd1(t2w); + addLoadingLimitsEnd1(network.getTwoWindingsTransformer(t2w.getId())); + } + + @Test + void replaceExtensionsTest() { + modifyNetworkForExtensionsTest(); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertEquals(createTwoWindingsTransformerFortescueToString(t2w1, t2w2, t2w3), createThreeWindingsTransformerFortescueToString(t3w)); + assertEquals(createTwoWindingsTransformerPhaseAngleClockToString(t2w2, t2w3), createThreeWindingsTransformerPhaseAngleClockToString(t3w)); + assertEquals(createTwoWindingsTransformerToBeEstimatedToString(t2w1, t2w2, t2w3), createThreeWindingsTransformerToBeEstimatedToString(t3w)); + } + + private void modifyNetworkForExtensionsTest() { + addExtensions(t2w1, 1); + addExtensions(t2w2, 2); + addExtensions(t2w3, 3); + addExtensions(network.getTwoWindingsTransformer(t2w1.getId()), 1); + addExtensions(network.getTwoWindingsTransformer(t2w2.getId()), 2); + addExtensions(network.getTwoWindingsTransformer(t2w3.getId()), 3); + } + + @Test + void replaceAliasesTest() { + addTwoWindingsTransformerAliases(network.getTwoWindingsTransformer(t2w1.getId())); + addTwoWindingsTransformerAliases(network.getTwoWindingsTransformer(t2w2.getId())); + addTwoWindingsTransformerAliases(network.getTwoWindingsTransformer(t2w3.getId())); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertTrue(compareAliases(t3w, "1", t2w1)); + assertTrue(compareAliases(t3w, "2", t2w2)); + assertTrue(compareAliases(t3w, "3", t2w3)); + } + + private void addTwoWindingsTransformerAliases(TwoWindingsTransformer t2w) { + t2w.addAlias("transformerEnd-" + t2w.getId(), "CGMES.TransformerEnd1"); + t2w.addAlias("terminal-" + t2w.getId(), "CGMES.Terminal1"); + t2w.addAlias("ratioTapChanger-" + t2w.getId(), "CGMES.RatioTapChanger1"); + t2w.addAlias("phaseTapChanger-" + t2w.getId(), "CGMES.PhaseTapChanger1"); + } + + private boolean compareAliases(ThreeWindingsTransformer t3w, String leg, TwoWindingsTransformer t2w) { + return t3w.getAliasFromType("CGMES.TransformerEnd" + leg).orElseThrow().equals("transformerEnd-" + t2w.getId()) + && t3w.getAliasFromType("CGMES.Terminal" + leg).orElseThrow().equals("terminal-" + t2w.getId()) + && t3w.getAliasFromType("CGMES.RatioTapChanger" + leg).orElseThrow().equals("ratioTapChanger-" + t2w.getId()) + && t3w.getAliasFromType("CGMES.PhaseTapChanger" + leg).orElseThrow().equals("phaseTapChanger-" + t2w.getId()); + } + + @Test + void reportNodeTest() throws IOException { + network.getTwoWindingsTransformer(t2w1.getId()).setProperty("t2w1 property1", "t2w1-value1"); + network.getTwoWindingsTransformer(t2w2.getId()).setProperty("t2w2 property1", "t2w2-value1"); + network.getTwoWindingsTransformer(t2w2.getId()).setProperty("t2w2 property2", "t2w2-value2"); + network.getTwoWindingsTransformer(t2w3.getId()).setProperty("t2w3 property1", "t2w3-value1"); + network.getTwoWindingsTransformer(t2w1.getId()).addAlias("t2w1-alias1"); + network.getTwoWindingsTransformer(t2w1.getId()).addAlias("t2w1-alias2"); + network.getTwoWindingsTransformer(t2w2.getId()).addAlias("t2w2-alias1"); + network.getTwoWindingsTransformer(t2w3.getId()).addAlias("t2w3-alias1"); + network.getTwoWindingsTransformer(t2w1.getId()).addAlias("t2w1-alias2"); + addLoadingLimitsEnd2(network.getTwoWindingsTransformer(t2w1.getId())); + addLoadingLimitsEnd2(network.getTwoWindingsTransformer(t2w2.getId())); + ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test reportNode").build(); + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network, reportNode); + + StringWriter sw1 = new StringWriter(); + reportNode.print(sw1); + assertEquals(""" + + test reportNode + + Replaced 3 TwoWindingsTransformers by ThreeWindingsTransformer + TwoWindingsTransformer 3WT-Leg1 removed + TwoWindingsTransformer 3WT-Leg2 removed + TwoWindingsTransformer 3WT-Leg3 removed + Voltage level 3WT-Star-VL, its equipments and the branches it is connected to have been removed + Alias [t2w1-alias2,t2w1-alias1] of twoWindingsTransformer 3WT-Leg1 will be lost + Alias [t2w2-alias1] of twoWindingsTransformer 3WT-Leg2 will be lost + Alias [t2w3-alias1] of twoWindingsTransformer 3WT-Leg3 will be lost + OperationalLimitsGroups [OperationalLimitsGroup-summer-end2,OperationalLimitsGroup-winter-end2] of twoWindingsTransformer 3WT-Leg1 will be lost + OperationalLimitsGroups [OperationalLimitsGroup-summer-end2,OperationalLimitsGroup-winter-end2] of twoWindingsTransformer 3WT-Leg2 will be lost + ThreeWindingsTransformer 3WT-Leg1-3WT-Leg2-3WT-Leg3 created + """, TestUtil.normalizeLineSeparator(sw1.toString())); + } + + @Test + void reportNodeExtensionsTest() throws IOException { + ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test reportNode").build(); + lostTwoWindingsTransformerExtensions(reportNode, "unknownExtension1, unknownExtension2", t2w1.getId()); + + StringWriter sw1 = new StringWriter(); + reportNode.print(sw1); + assertEquals(""" + + test reportNode + Extension [unknownExtension1, unknownExtension2] of twoWindingsTransformer 3WT-Leg1 will be lost + """, TestUtil.normalizeLineSeparator(sw1.toString())); + } + + @Test + void replaceFlowsTest() { + modifyNetworkForFlowsTest(); + assertTrue(checkFlowsTest()); + } + + private void modifyNetworkForFlowsTest() { + addVoltages(t2w1.getTerminal1().getBusView().getBus(), t2w2.getTerminal1().getBusView().getBus(), t2w3.getTerminal1().getBusView().getBus()); + addVoltages(network.getTwoWindingsTransformer(t2w1.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w2.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w3.getId()).getTerminal1().getBusView().getBus()); + } + + @Test + void replaceFlowsRatedU2Test() { + modifyNetworkForFlowsRatedU2Test(); + assertTrue(checkFlowsTest()); + } + + private void modifyNetworkForFlowsRatedU2Test() { + t2w1.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.99); + t2w2.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 1.02); + t2w3.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.98); + + network.getTwoWindingsTransformer(t2w1.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.99); + network.getTwoWindingsTransformer(t2w2.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 1.02); + network.getTwoWindingsTransformer(t2w3.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.98); + + addVoltages(t2w1.getTerminal1().getBusView().getBus(), t2w2.getTerminal1().getBusView().getBus(), t2w3.getTerminal1().getBusView().getBus()); + addVoltages(network.getTwoWindingsTransformer(t2w1.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w2.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w3.getId()).getTerminal1().getBusView().getBus()); + } + + private boolean checkFlowsTest() { + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + TwtData t3wData = new TwtData(t3w, 0.0, false); + setStarBusVoltage(t3wData, t2w1.getTerminal2().getBusView().getBus()); + + BranchData t2w1Data = new BranchData(t2w1, 0.0, false, false); + BranchData t2w2Data = new BranchData(t2w2, 0.0, false, false); + BranchData t2w3Data = new BranchData(t2w3, 0.0, false, false); + + double tol = 0.000001; + assertEquals(t2w1Data.getComputedP1(), t3wData.getComputedP(ThreeSides.ONE), tol); + assertEquals(t2w2Data.getComputedP1(), t3wData.getComputedP(ThreeSides.TWO), tol); + assertEquals(t2w3Data.getComputedP1(), t3wData.getComputedP(ThreeSides.THREE), tol); + return true; + } + + @Test + void replaceFlowsNotWellOrientedTest() { + modifyNetworkForFlowsNotWellOrientedTest(); + assertTrue(checkFlowsTestNotWellOriented()); + } + + private void modifyNetworkForFlowsNotWellOrientedTest() { + t2w1.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.99); + t2w2.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 1.02); + t2w3.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.98); + + network.getTwoWindingsTransformer(t2w1.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.99); + network.getTwoWindingsTransformer(t2w2.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 1.02); + network.getTwoWindingsTransformer(t2w3.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.98); + + addPhaseTapChanger(t2w2); + addPhaseTapChanger(network.getTwoWindingsTransformer(t2w2.getId())); + + String tw2Id = t2w2.getId(); + reOrientedTwoWindingsTransformer(t2w2); + reOrientedTwoWindingsTransformer(network.getTwoWindingsTransformer(tw2Id)); + t2w2 = t2w1.getNetwork().getTwoWindingsTransformer("3WT-Leg2-notWellOriented"); + + addVoltages(t2w1.getTerminal1().getBusView().getBus(), t2w2.getTerminal2().getBusView().getBus(), t2w3.getTerminal1().getBusView().getBus()); + addVoltages(network.getTwoWindingsTransformer(t2w1.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w2.getId()).getTerminal2().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w3.getId()).getTerminal1().getBusView().getBus()); + } + + private boolean checkFlowsTestNotWellOriented() { + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-notWellOriented-3WT-Leg3"); + + TwtData t3wData = new TwtData(t3w, 0.0, false); + setStarBusVoltage(t3wData, t2w1.getTerminal2().getBusView().getBus()); + + BranchData t2w1Data = new BranchData(t2w1, 0.0, false, false); + BranchData t2w2Data = new BranchData(t2w2, 0.0, false, false); + BranchData t2w3Data = new BranchData(t2w3, 0.0, false, false); + + double tol = 0.000001; + assertEquals(t2w1Data.getComputedP1(), t3wData.getComputedP(ThreeSides.ONE), tol); + assertEquals(t2w2Data.getComputedP2(), t3wData.getComputedP(ThreeSides.TWO), tol); + assertEquals(t2w3Data.getComputedP1(), t3wData.getComputedP(ThreeSides.THREE), tol); + return true; + } + + @Test + void replaceNodeBreakerTest() { + modifyNetworkForNodeBreakerTest(); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + } + + private void modifyNetworkForNodeBreakerTest() { + Network expectedNetwork = createThreeWindingsTransformerNodeBreakerNetwork(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replaceExpected = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replaceExpected.apply(expectedNetwork); + t2w1 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg1"); + assertNotNull(t2w1); + t2w2 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg2"); + assertNotNull(t2w2); + t2w3 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg3"); + + network = createThreeWindingsTransformerNodeBreakerNetwork(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + } + + @Test + void getNameTest() { + AbstractNetworkModification networkModification = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + assertEquals("Replace3TwoWindingsTransformersByThreeWindingsTransformers", networkModification.getName()); + } + + @Test + void hasImpactTest() { + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers modification = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + assertEquals(NetworkModificationImpact.HAS_IMPACT_ON_NETWORK, modification.hasImpactOnNetwork(network)); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceThreeWindingsTransformersBy3TwoWindingsTransformersTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceThreeWindingsTransformersBy3TwoWindingsTransformersTest.java new file mode 100644 index 00000000000..48988b0c5c1 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceThreeWindingsTransformersBy3TwoWindingsTransformersTest.java @@ -0,0 +1,337 @@ +/** + * 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.iidm.modification; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.test.TestUtil; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory; +import com.powsybl.iidm.network.util.BranchData; +import com.powsybl.iidm.network.util.TwtData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collections; + +import static com.powsybl.iidm.modification.TransformersTestUtils.*; +import static com.powsybl.iidm.modification.util.ModificationReports.lostThreeWindingsTransformerExtensions; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +class ReplaceThreeWindingsTransformersBy3TwoWindingsTransformersTest { + + private Network network; + private ThreeWindingsTransformer t3w; + + @BeforeEach + public void setUp() { + Network expectedNetwork = ThreeWindingsTransformerNetworkFactory.create(); + assertEquals(1, expectedNetwork.getThreeWindingsTransformerCount()); + t3w = expectedNetwork.getThreeWindingsTransformer("3WT"); + network = ThreeWindingsTransformerNetworkFactory.create(); + } + + @Test + void replaceBasicTest() { + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + } + + @Test + void replaceSelectedThreeWindingsTransformerTest() { + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + + String t3wId = network.getThreeWindingsTransformers().iterator().next().getId(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(Collections.singletonList(t3wId)); + replace.apply(network); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + } + + @Test + void nonReplacementTest() { + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + + String t3wId = "unknown threeWindingsTransformer"; + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(Collections.singletonList(t3wId)); + replace.apply(network); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + } + + @Test + void replaceRatioTapChangerTest() { + assertNull(t3w.getLeg1().getRatioTapChanger()); + assertNotNull(t3w.getLeg2().getRatioTapChanger()); + assertNotNull(t3w.getLeg3().getRatioTapChanger()); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertNull(t2w1.getRatioTapChanger()); + assertNotNull(t2w2.getRatioTapChanger()); + assertNotNull(t2w3.getRatioTapChanger()); + + assertTrue(compareRatioTapChanger(t3w.getLeg2().getRatioTapChanger(), t2w2.getRatioTapChanger())); + assertTrue(compareRatioTapChanger(t3w.getLeg3().getRatioTapChanger(), t2w3.getRatioTapChanger())); + } + + @Test + void replacePhaseTapChangerTest() { + modifyNetworkForPhaseTapChangerTest(); + + assertNull(t3w.getLeg1().getPhaseTapChanger()); + assertNotNull(t3w.getLeg2().getPhaseTapChanger()); + assertNull(t3w.getLeg3().getPhaseTapChanger()); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertNull(t2w1.getPhaseTapChanger()); + assertNotNull(t2w2.getPhaseTapChanger()); + assertNull(t2w3.getPhaseTapChanger()); + + assertTrue(comparePhaseTapChanger(t3w.getLeg2().getPhaseTapChanger(), t2w2.getPhaseTapChanger())); + } + + private void modifyNetworkForPhaseTapChangerTest() { + addPhaseTapChanger(t3w.getLeg2()); // modify expected network + addPhaseTapChanger(network.getThreeWindingsTransformer(t3w.getId()).getLeg2()); // modify network + } + + @Test + void replaceLoadingLimitsTest() { + modifyNetworkForLoadingLimitsTest(); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertTrue(compareOperationalLimitsGroups(t3w.getLeg1().getOperationalLimitsGroups(), t2w1.getOperationalLimitsGroups1())); + assertTrue(compareOperationalLimitsGroups(t3w.getLeg2().getOperationalLimitsGroups(), t2w2.getOperationalLimitsGroups1())); + assertTrue(compareOperationalLimitsGroups(t3w.getLeg3().getOperationalLimitsGroups(), t2w3.getOperationalLimitsGroups1())); + } + + private void modifyNetworkForLoadingLimitsTest() { + addLoadingLimits(t3w.getLeg1()); + addLoadingLimits(t3w.getLeg2()); + addLoadingLimits(t3w.getLeg3()); + addLoadingLimits(network.getThreeWindingsTransformer(t3w.getId()).getLeg1()); + addLoadingLimits(network.getThreeWindingsTransformer(t3w.getId()).getLeg2()); + addLoadingLimits(network.getThreeWindingsTransformer(t3w.getId()).getLeg3()); + } + + @Test + void replacePropertiesTest() { + modifyNetworkForPropertiesTest(); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("v", "132.5"); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("angle", "2.5"); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-summer", "summer"); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-winter", "winter"); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + + assertEquals(132.5, t2w1.getTerminal2().getBusView().getBus().getV()); + assertEquals(2.5, t2w1.getTerminal2().getBusView().getBus().getAngle()); + assertEquals("summer", t2w1.getProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-summer")); + assertEquals("winter", t2w1.getProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-winter")); + } + + private void modifyNetworkForPropertiesTest() { + addLoadingLimits(t3w.getLeg1()); + addLoadingLimits(network.getThreeWindingsTransformer(t3w.getId()).getLeg1()); + } + + @Test + void replaceExtensionsTest() { + modifyNetworkForExtensionsTest(); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertEquals(createThreeWindingsTransformerFortescueToString(t3w), createTwoWindingsTransformerFortescueToString(t2w1, t2w2, t2w3)); + assertEquals(createThreeWindingsTransformerPhaseAngleClockToString(t3w), createTwoWindingsTransformerPhaseAngleClockToString(t2w2, t2w3)); + assertEquals(createThreeWindingsTransformerToBeEstimatedToString(t3w), createTwoWindingsTransformerToBeEstimatedToString(t2w1, t2w2, t2w3)); + } + + private void modifyNetworkForExtensionsTest() { + addExtensions(t3w); + addExtensions(network.getThreeWindingsTransformer(t3w.getId())); + } + + @Test + void replaceAliasesTest() { + addLegAliases("1"); + addLegAliases("2"); + addLegAliases("3"); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertTrue(compareAliases(t2w1, "1")); + assertTrue(compareAliases(t2w2, "2")); + assertTrue(compareAliases(t2w3, "3")); + } + + private void addLegAliases(String leg) { + network.getThreeWindingsTransformer(t3w.getId()).addAlias("transformerEnd-Leg" + leg, "CGMES.TransformerEnd" + leg); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("terminal-Leg" + leg, "CGMES.Terminal" + leg); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("ratioTapChanger-Leg" + leg, "CGMES.RatioTapChanger" + leg); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("phaseTapChanger-Leg" + leg, "CGMES.PhaseTapChanger" + leg); + } + + private boolean compareAliases(TwoWindingsTransformer t2w, String leg) { + return t2w.getAliasFromType("CGMES.TransformerEnd1").orElseThrow().equals("transformerEnd-Leg" + leg) + && t2w.getAliasFromType("CGMES.Terminal1").orElseThrow().equals("terminal-Leg" + leg) + && t2w.getAliasFromType("CGMES.RatioTapChanger1").orElseThrow().equals("ratioTapChanger-Leg" + leg) + && t2w.getAliasFromType("CGMES.PhaseTapChanger1").orElseThrow().equals("phaseTapChanger-Leg" + leg); + } + + @Test + void reportNodeTest() throws IOException { + network.getThreeWindingsTransformer(t3w.getId()).setProperty("t3w property1", "t3w-value1"); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("t3w property2", "t3w-value2"); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("t3w-alias1"); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("t3w-alias2"); + ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test reportNode").build(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network, reportNode); + + StringWriter sw1 = new StringWriter(); + reportNode.print(sw1); + assertEquals(""" + + test reportNode + + Replaced ThreeWindingsTransformer by 3 TwoWindingsTransformers + ThreeWindingsTransformer 3WT removed + Alias [t3w-alias1,t3w-alias2] of threeWindingsTransformer 3WT will be lost + VoltageLevel 3WT-Star-VL created + TwoWindingsTransformer 3WT-Leg1 created + TwoWindingsTransformer 3WT-Leg2 created + TwoWindingsTransformer 3WT-Leg3 created + """, TestUtil.normalizeLineSeparator(sw1.toString())); + } + + @Test + void reportNodeExtensionsTest() throws IOException { + ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test reportNode").build(); + lostThreeWindingsTransformerExtensions(reportNode, "unknownExtension1, unknownExtension2", t3w.getId()); + + StringWriter sw1 = new StringWriter(); + reportNode.print(sw1); + assertEquals(""" + + test reportNode + Extension [unknownExtension1, unknownExtension2] of threeWindingsTransformer 3WT will be lost + """, TestUtil.normalizeLineSeparator(sw1.toString())); + } + + @Test + void replaceFlowsTest() { + modifyNetworkForFlowsTest(); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + TwtData t3wData = new TwtData(t3w, 0.0, false); + setStarBusVoltage(t3wData, t2w1.getTerminal2().getBusView().getBus()); + + BranchData t2w1Data = new BranchData(t2w1, 0.0, false, false); + BranchData t2w2Data = new BranchData(t2w2, 0.0, false, false); + BranchData t2w3Data = new BranchData(t2w3, 0.0, false, false); + + double tol = 0.000001; + assertEquals(t3wData.getComputedP(ThreeSides.ONE), t2w1Data.getComputedP1(), tol); + assertEquals(t3wData.getComputedP(ThreeSides.TWO), t2w2Data.getComputedP1(), tol); + assertEquals(t3wData.getComputedP(ThreeSides.THREE), t2w3Data.getComputedP1(), tol); + } + + private void modifyNetworkForFlowsTest() { + addVoltages(t3w.getLeg1().getTerminal().getBusView().getBus(), t3w.getLeg2().getTerminal().getBusView().getBus(), t3w.getLeg3().getTerminal().getBusView().getBus()); + addVoltages(network.getThreeWindingsTransformer(t3w.getId()).getLeg1().getTerminal().getBusView().getBus(), + network.getThreeWindingsTransformer(t3w.getId()).getLeg2().getTerminal().getBusView().getBus(), + network.getThreeWindingsTransformer(t3w.getId()).getLeg3().getTerminal().getBusView().getBus()); + } + + @Test + void replaceNodeBreakerTest() { + modifyNetworkForNodeBreakerTest(); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + } + + private void modifyNetworkForNodeBreakerTest() { + Network expectedNetwork = createThreeWindingsTransformerNodeBreakerNetwork(); + t3w = expectedNetwork.getThreeWindingsTransformer("3WT"); + network = createThreeWindingsTransformerNodeBreakerNetwork(); + } + + @Test + void getNameTest() { + AbstractNetworkModification networkModification = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + assertEquals("ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers", networkModification.getName()); + } + + @Test + void hasImpactTest() { + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers modification = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + assertEquals(NetworkModificationImpact.HAS_IMPACT_ON_NETWORK, modification.hasImpactOnNetwork(network)); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/SetGeneratorToLocalRegulationTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/SetGeneratorToLocalRegulationTest.java new file mode 100644 index 00000000000..0a45433000f --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/SetGeneratorToLocalRegulationTest.java @@ -0,0 +1,159 @@ +/** + * 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.iidm.modification; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.test.TestUtil; +import com.powsybl.iidm.network.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.time.ZonedDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Romain Courtier {@literal } + */ +class SetGeneratorToLocalRegulationTest { + + Network network; + + @BeforeEach + void setUp() { + network = createTestNetwork(); + } + + @Test + void setLocalRegulationTest() throws IOException { + assertNotNull(network); + Generator gen1 = network.getGenerator("GEN1"); + Generator gen2 = network.getGenerator("GEN2"); + + // Before applying the network modification, + // gen1 regulates remotely at 1.05 pu (420 kV) and gen2 regulates locally at 1.05 pu (21 kV). + assertNotEquals(gen1.getId(), gen1.getRegulatingTerminal().getConnectable().getId()); + assertEquals(420.0, gen1.getTargetV()); + assertEquals(gen2.getId(), gen2.getRegulatingTerminal().getConnectable().getId()); + assertEquals(21.0, gen2.getTargetV()); + + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("rootReportNode", "Set generators to local regulation").build(); + new SetGeneratorToLocalRegulation("GEN1").apply(network, reportNode); + new SetGeneratorToLocalRegulation("GEN2").apply(network, reportNode); + SetGeneratorToLocalRegulation modification = new SetGeneratorToLocalRegulation("WRONG_ID"); + PowsyblException e = assertThrows(PowsyblException.class, () -> modification.apply(network, true, reportNode)); + assertEquals("Generator 'WRONG_ID' not found", e.getMessage()); + + // After applying the network modification, both generators regulate locally at 1.05 pu (21 kV). + assertEquals(gen1.getId(), gen1.getRegulatingTerminal().getConnectable().getId()); + assertEquals(21.0, gen1.getTargetV()); + assertEquals(gen2.getId(), gen2.getRegulatingTerminal().getConnectable().getId()); + assertEquals(21.0, gen2.getTargetV()); + + // Report node has been updated with the change for gen1 (no impact for gen2). + StringWriter sw = new StringWriter(); + reportNode.print(sw); + assertEquals(""" + + Set generators to local regulation + Changed regulation for generator GEN1 to local instead of remote + """, TestUtil.normalizeLineSeparator(sw.toString())); + } + + @Test + void hasImpactTest() { + SetGeneratorToLocalRegulation modification; + + modification = new SetGeneratorToLocalRegulation("WRONG_ID"); + assertEquals(NetworkModificationImpact.CANNOT_BE_APPLIED, modification.hasImpactOnNetwork(network)); + + modification = new SetGeneratorToLocalRegulation("GEN1"); + assertEquals(NetworkModificationImpact.HAS_IMPACT_ON_NETWORK, modification.hasImpactOnNetwork(network)); + + modification = new SetGeneratorToLocalRegulation("GEN2"); + assertEquals(NetworkModificationImpact.NO_IMPACT_ON_NETWORK, modification.hasImpactOnNetwork(network)); + } + + @Test + void getNameTest() { + SetGeneratorToLocalRegulation modification = new SetGeneratorToLocalRegulation("GEN1"); + assertEquals("SetGeneratorToLocalRegulation", modification.getName()); + } + + private Network createTestNetwork() { + Network n = Network.create("test_network", "test"); + n.setCaseDate(ZonedDateTime.parse("2021-12-07T18:45:00.000+02:00")); + Substation st = n.newSubstation() + .setId("ST") + .setCountry(Country.FR) + .add(); + + VoltageLevel vl400 = st.newVoltageLevel() + .setId("VL400") + .setNominalV(400) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + vl400.getNodeBreakerView().newBusbarSection() + .setId("BBS") + .setNode(0) + .add(); + + VoltageLevel vl20 = st.newVoltageLevel() + .setId("VL20") + .setNominalV(20) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + vl20.newGenerator() + .setId("GEN1") + .setNode(3) + .setEnergySource(EnergySource.NUCLEAR) + .setMinP(100) + .setMaxP(200) + .setTargetP(200) + .setVoltageRegulatorOn(true) + .setTargetV(420) + .setRegulatingTerminal(n.getBusbarSection("BBS").getTerminal()) + .add(); + vl20.newGenerator() + .setId("GEN2") + .setNode(4) + .setEnergySource(EnergySource.NUCLEAR) + .setMinP(100) + .setMaxP(200) + .setTargetP(200) + .setVoltageRegulatorOn(true) + .setTargetV(21) + // No regulatingTerminal set == use its own terminal for regulation + .add(); + + st.newTwoWindingsTransformer() + .setId("T2W") + .setName("T2W") + .setR(1.0) + .setX(10.0) + .setG(0.0) + .setB(0.0) + .setRatedU1(20.0) + .setRatedU2(400.0) + .setRatedS(250.0) + .setVoltageLevel1("VL400") + .setVoltageLevel2("VL20") + .setNode1(1) + .setNode2(2) + .add(); + + vl400.getNodeBreakerView().newInternalConnection().setNode1(0).setNode2(1); + vl20.getNodeBreakerView().newInternalConnection().setNode1(2).setNode2(3); + vl20.getNodeBreakerView().newInternalConnection().setNode1(2).setNode2(4); + + return n; + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TransformersTestUtils.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TransformersTestUtils.java new file mode 100644 index 00000000000..f4146e4da92 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TransformersTestUtils.java @@ -0,0 +1,517 @@ +/** + * 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.iidm.modification; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import com.powsybl.iidm.network.util.TwtData; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static com.powsybl.iidm.modification.util.TransformerUtils.copyAndAddPhaseTapChanger; +import static com.powsybl.iidm.modification.util.TransformerUtils.copyAndAddRatioTapChanger; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ + +final class TransformersTestUtils { + + private TransformersTestUtils() { + } + + static void addPhaseTapChanger(TwoWindingsTransformer t2w) { + PhaseTapChangerAdder ptcAdder = t2w.newPhaseTapChanger(); + fillTapChangerAdder(ptcAdder, t2w.getTerminal1()); + ptcAdder.add(); + } + + static void addPhaseTapChanger(ThreeWindingsTransformer.Leg leg) { + PhaseTapChangerAdder ptcAdder = leg.newPhaseTapChanger(); + fillTapChangerAdder(ptcAdder, leg.getTerminal()); + ptcAdder.add(); + } + + private static void fillTapChangerAdder(PhaseTapChangerAdder ptcAdder, Terminal terminal) { + ptcAdder.setLowTapPosition(0) + .setTapPosition(2) + .setRegulationTerminal(terminal) + .setRegulationValue(10.0) + .setRegulationMode(PhaseTapChanger.RegulationMode.FIXED_TAP) + .setTargetDeadband(0.5) + .setRegulating(false) + .beginStep() + .setRho(0.99) + .setAlpha(-2.0) + .setR(1.01) + .setX(1.02) + .setG(1.03) + .setB(1.04) + .endStep() + .beginStep() + .setRho(1.00) + .setAlpha(0.0) + .setR(0.0) + .setX(0.0) + .setG(0.0) + .setB(0.0) + .endStep() + .beginStep() + .setRho(1.01) + .setAlpha(2.0) + .setR(0.99) + .setX(0.98) + .setG(0.97) + .setB(0.96) + .endStep() + .add(); + } + + static void addLoadingLimitsEnd1(TwoWindingsTransformer t2w) { + OperationalLimitsGroup summer = t2w.newOperationalLimitsGroup1("OperationalLimitsGroup-summer"); + OperationalLimitsGroup winter = t2w.newOperationalLimitsGroup1("OperationalLimitsGroup-winter"); + addSummerLoadingLimits(summer); + addWinterLoadingLimits(winter); + } + + static void addLoadingLimitsEnd2(TwoWindingsTransformer t2w) { + OperationalLimitsGroup summer = t2w.newOperationalLimitsGroup2("OperationalLimitsGroup-summer-end2"); + OperationalLimitsGroup winter = t2w.newOperationalLimitsGroup2("OperationalLimitsGroup-winter-end2"); + addSummerLoadingLimits(summer); + addWinterLoadingLimits(winter); + } + + static void addLoadingLimits(ThreeWindingsTransformer.Leg leg) { + OperationalLimitsGroup summer = leg.newOperationalLimitsGroup("OperationalLimitsGroup-summer"); + OperationalLimitsGroup winter = leg.newOperationalLimitsGroup("OperationalLimitsGroup-winter"); + addSummerLoadingLimits(summer); + addWinterLoadingLimits(winter); + } + + private static void addSummerLoadingLimits(OperationalLimitsGroup summer) { + summer.newActivePowerLimits() + .setPermanentLimit(100.0) + .beginTemporaryLimit() + .setName("TemporaryActivePowerLimit-1-summer") + .setAcceptableDuration(2) + .setValue(110.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryActivePowerLimit-2-summer") + .setAcceptableDuration(1) + .setValue(120.0) + .endTemporaryLimit().add(); + summer.newApparentPowerLimits() + .setPermanentLimit(105.0) + .beginTemporaryLimit() + .setName("TemporaryApparentPowerLimit-1") + .setAcceptableDuration(2) + .setValue(115.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryApparentPowerLimit-2-summer") + .setAcceptableDuration(1) + .setValue(125.0) + .endTemporaryLimit().add(); + summer.newCurrentLimits() + .setPermanentLimit(1050.0) + .beginTemporaryLimit() + .setName("TemporaryCurrentLimit-1-summer") + .setAcceptableDuration(2) + .setValue(1150.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryCurrentLimit-2-summer") + .setAcceptableDuration(1) + .setValue(1250.0) + .endTemporaryLimit().add(); + } + + private static void addWinterLoadingLimits(OperationalLimitsGroup winter) { + winter.newActivePowerLimits() + .setPermanentLimit(125.0) + .beginTemporaryLimit() + .setName("TemporaryActivePowerLimit-1-winter") + .setAcceptableDuration(3) + .setValue(135.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryActivePowerLimit-2-winter") + .setAcceptableDuration(2) + .setValue(145.0) + .endTemporaryLimit().add(); + winter.newApparentPowerLimits() + .setPermanentLimit(130.0) + .beginTemporaryLimit() + .setName("TemporaryApparentPowerLimit-1-winter") + .setAcceptableDuration(3) + .setValue(140.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryApparentPowerLimit-2-winter") + .setAcceptableDuration(2) + .setValue(150.0) + .endTemporaryLimit().add(); + winter.newCurrentLimits() + .setPermanentLimit(130.0) + .beginTemporaryLimit() + .setName("TemporaryCurrentLimit-1-winter") + .setAcceptableDuration(3) + .setValue(140.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryCurrentLimit-2-winter") + .setAcceptableDuration(2) + .setValue(150.0) + .endTemporaryLimit().add(); + } + + static boolean compareRatioTapChanger(RatioTapChanger expectedRtc, RatioTapChanger rtc) { + String expected = ratioTapChangerToString(expectedRtc); + String actual = ratioTapChangerToString(rtc); + return expected.equals(actual); + } + + private static String ratioTapChangerToString(RatioTapChanger rtc) { + List strings = new ArrayList<>(); + strings.add(String.valueOf(rtc.getLowTapPosition())); + strings.add(String.valueOf(rtc.getTapPosition())); + strings.add(rtc.getRegulationTerminal().getBusView().getBus().getId()); + strings.add(String.valueOf(rtc.getTargetV())); + strings.add(String.valueOf(rtc.getRegulationValue())); + strings.add(String.valueOf(rtc.getRegulationMode())); + strings.add(String.valueOf(rtc.getTargetDeadband())); + strings.add(String.valueOf(rtc.isRegulating())); + strings.add(String.valueOf(rtc.getStepCount())); + rtc.getAllSteps().forEach((step, rtcStep) -> { + strings.add(String.valueOf(step)); + strings.add(String.valueOf(rtcStep.getRho())); + strings.add(String.valueOf(rtcStep.getR())); + strings.add(String.valueOf(rtcStep.getX())); + strings.add(String.valueOf(rtcStep.getG())); + strings.add(String.valueOf(rtcStep.getB())); + }); + + return String.join(",", strings); + } + + static boolean comparePhaseTapChanger(PhaseTapChanger expectedPtc, PhaseTapChanger ptc) { + String expected = phaseTapChangerToString(expectedPtc); + String actual = phaseTapChangerToString(ptc); + return expected.equals(actual); + } + + private static String phaseTapChangerToString(PhaseTapChanger ptc) { + List strings = new ArrayList<>(); + strings.add(String.valueOf(ptc.getLowTapPosition())); + strings.add(String.valueOf(ptc.getTapPosition())); + strings.add(ptc.getRegulationTerminal().getBusView().getBus().getId()); + strings.add(String.valueOf(ptc.getRegulationValue())); + strings.add(String.valueOf(ptc.getRegulationMode())); + strings.add(String.valueOf(ptc.getTargetDeadband())); + strings.add(String.valueOf(ptc.isRegulating())); + strings.add(String.valueOf(ptc.getStepCount())); + ptc.getAllSteps().forEach((step, rtcStep) -> { + strings.add(String.valueOf(step)); + strings.add(String.valueOf(rtcStep.getRho())); + strings.add(String.valueOf(rtcStep.getAlpha())); + strings.add(String.valueOf(rtcStep.getR())); + strings.add(String.valueOf(rtcStep.getX())); + strings.add(String.valueOf(rtcStep.getG())); + strings.add(String.valueOf(rtcStep.getB())); + }); + + return String.join(",", strings); + } + + static boolean compareOperationalLimitsGroups(Collection expected, Collection actual) { + String expectedString = operationalLimitsToString(expected); + String actualString = operationalLimitsToString(actual); + return expectedString.equals(actualString); + } + + private static String operationalLimitsToString(Collection operationalLimitsGroups) { + List strings = new ArrayList<>(); + operationalLimitsGroups.forEach(operationalLimitGroup -> { + strings.add(operationalLimitGroup.getId()); + operationalLimitGroup.getActivePowerLimits().ifPresent(activePowerLimits -> { + strings.add(activePowerLimits.getLimitType().name()); + addLegLimits(activePowerLimits, strings); + }); + operationalLimitGroup.getApparentPowerLimits().ifPresent(apparentPowerLimits -> { + strings.add(apparentPowerLimits.getLimitType().name()); + addLegLimits(apparentPowerLimits, strings); + }); + operationalLimitGroup.getCurrentLimits().ifPresent(currentLimits -> { + strings.add(currentLimits.getLimitType().name()); + addLegLimits(currentLimits, strings); + }); + }); + return String.join(",", strings); + } + + private static void addLegLimits(LoadingLimits loadingLimits, List strings) { + strings.add(String.valueOf(loadingLimits.getPermanentLimit())); + loadingLimits.getTemporaryLimits().forEach(temporaryLimit -> { + strings.add(temporaryLimit.getName()); + strings.add(String.valueOf(temporaryLimit.getAcceptableDuration())); + strings.add(String.valueOf(temporaryLimit.getValue())); + strings.add(String.valueOf(temporaryLimit.isFictitious())); + }); + } + + static String createThreeWindingsTransformerFortescueToString(ThreeWindingsTransformer t3w) { + List strings = new ArrayList<>(); + ThreeWindingsTransformerFortescue extension = t3w.getExtension(ThreeWindingsTransformerFortescue.class); + if (extension != null) { + addLegFortescue(extension.getLeg1(), strings); + addLegFortescue(extension.getLeg2(), strings); + addLegFortescue(extension.getLeg3(), strings); + } + return String.join(",", strings); + } + + private static void addLegFortescue(ThreeWindingsTransformerFortescue.LegFortescue legFortescue, List strings) { + strings.add(legFortescue.getConnectionType().name()); + strings.add(String.valueOf(legFortescue.isFreeFluxes())); + strings.add(String.valueOf(legFortescue.getGroundingR())); + strings.add(String.valueOf(legFortescue.getGroundingX())); + strings.add(String.valueOf(legFortescue.getRz())); + strings.add(String.valueOf(legFortescue.getXz())); + } + + static String createTwoWindingsTransformerFortescueToString(TwoWindingsTransformer t2w1, TwoWindingsTransformer t2w2, TwoWindingsTransformer t2w3) { + List strings = new ArrayList<>(); + addT2wFortescue(t2w1.getExtension(TwoWindingsTransformerFortescue.class), strings); + addT2wFortescue(t2w2.getExtension(TwoWindingsTransformerFortescue.class), strings); + addT2wFortescue(t2w3.getExtension(TwoWindingsTransformerFortescue.class), strings); + return String.join(",", strings); + } + + private static void addT2wFortescue(TwoWindingsTransformerFortescue t2wFortescue, List strings) { + if (t2wFortescue != null) { + strings.add(t2wFortescue.getConnectionType1().name()); + strings.add(String.valueOf(t2wFortescue.isFreeFluxes())); + strings.add(String.valueOf(t2wFortescue.getGroundingR1())); + strings.add(String.valueOf(t2wFortescue.getGroundingX1())); + strings.add(String.valueOf(t2wFortescue.getRz())); + strings.add(String.valueOf(t2wFortescue.getXz())); + } + } + + static String createThreeWindingsTransformerPhaseAngleClockToString(ThreeWindingsTransformer t3w) { + List strings = new ArrayList<>(); + ThreeWindingsTransformerPhaseAngleClock extension = t3w.getExtension(ThreeWindingsTransformerPhaseAngleClock.class); + if (extension != null) { + strings.add(String.valueOf(extension.getPhaseAngleClockLeg2())); + strings.add(String.valueOf(extension.getPhaseAngleClockLeg3())); + } + return String.join(",", strings); + } + + static String createTwoWindingsTransformerPhaseAngleClockToString(TwoWindingsTransformer t2w2, TwoWindingsTransformer t2w3) { + List strings = new ArrayList<>(); + TwoWindingsTransformerPhaseAngleClock t2w2Extension = t2w2.getExtension(TwoWindingsTransformerPhaseAngleClock.class); + if (t2w2Extension != null) { + strings.add(String.valueOf(t2w2Extension.getPhaseAngleClock())); + } + TwoWindingsTransformerPhaseAngleClock t2w3Extension = t2w3.getExtension(TwoWindingsTransformerPhaseAngleClock.class); + if (t2w3Extension != null) { + strings.add(String.valueOf(t2w3Extension.getPhaseAngleClock())); + } + return String.join(",", strings); + } + + static String createThreeWindingsTransformerToBeEstimatedToString(ThreeWindingsTransformer t3w) { + List strings = new ArrayList<>(); + ThreeWindingsTransformerToBeEstimated extension = t3w.getExtension(ThreeWindingsTransformerToBeEstimated.class); + if (extension != null) { + strings.add(String.valueOf(extension.shouldEstimateRatioTapChanger1())); + strings.add(String.valueOf(extension.shouldEstimatePhaseTapChanger1())); + strings.add(String.valueOf(extension.shouldEstimateRatioTapChanger2())); + strings.add(String.valueOf(extension.shouldEstimatePhaseTapChanger2())); + strings.add(String.valueOf(extension.shouldEstimateRatioTapChanger3())); + strings.add(String.valueOf(extension.shouldEstimatePhaseTapChanger3())); + } + return String.join(",", strings); + } + + static String createTwoWindingsTransformerToBeEstimatedToString(TwoWindingsTransformer t2w1, TwoWindingsTransformer t2w2, TwoWindingsTransformer t2w3) { + List strings = new ArrayList<>(); + addToBeEstimated(t2w1.getExtension(TwoWindingsTransformerToBeEstimated.class), strings); + addToBeEstimated(t2w2.getExtension(TwoWindingsTransformerToBeEstimated.class), strings); + addToBeEstimated(t2w3.getExtension(TwoWindingsTransformerToBeEstimated.class), strings); + return String.join(",", strings); + } + + private static void addToBeEstimated(TwoWindingsTransformerToBeEstimated extension, List strings) { + if (extension != null) { + strings.add(String.valueOf(extension.shouldEstimateRatioTapChanger())); + strings.add(String.valueOf(extension.shouldEstimatePhaseTapChanger())); + } + } + + static void addVoltages(Bus bus1, Bus bus2, Bus bus3) { + bus1.setV(bus1.getVoltageLevel().getNominalV() * 1.01); + bus1.setAngle(2.0); + + bus2.setV(bus2.getVoltageLevel().getNominalV() * 0.99); + bus2.setAngle(4.0); + + bus3.setV(bus3.getVoltageLevel().getNominalV() * 0.98); + bus3.setAngle(3.0); + } + + static void setStarBusVoltage(TwtData twtData, Bus starBus) { + starBus.setV(twtData.getStarU()); + starBus.setAngle(Math.toDegrees(twtData.getStarTheta())); + } + + static void reOrientedTwoWindingsTransformer(TwoWindingsTransformer t2w) { + TwoWindingsTransformer t2wNotWellOriented = t2w.getTerminal1().getVoltageLevel().getSubstation().orElseThrow().newTwoWindingsTransformer() + .setId(t2w.getId() + "-" + "notWellOriented") + .setName(t2w.getNameOrId() + "-" + "notWellOriented") + .setRatedU1(t2w.getRatedU2()) + .setRatedU2(t2w.getRatedU1()) + .setR(t2w.getR()) + .setX(t2w.getX()) + .setG(t2w.getG()) + .setB(t2w.getB()) + .setRatedS(t2w.getRatedS()) + .setVoltageLevel1(t2w.getTerminal2().getVoltageLevel().getId()) + .setConnectableBus1(t2w.getTerminal2().getBusBreakerView().getBus().getId()) + .setBus1(t2w.getTerminal2().getBusBreakerView().getBus().getId()) + .setVoltageLevel2(t2w.getTerminal1().getVoltageLevel().getId()) + .setConnectableBus2(t2w.getTerminal1().getBusBreakerView().getBus().getId()) + .setBus2(t2w.getTerminal1().getBusBreakerView().getBus().getId()) + .add(); + + copyAndAddRatioTapChanger(t2wNotWellOriented.newRatioTapChanger(), t2w.getRatioTapChanger()); + copyAndAddPhaseTapChanger(t2wNotWellOriented.newPhaseTapChanger(), t2w.getPhaseTapChanger()); + + t2w.remove(); + } + + static Network createThreeWindingsTransformerNodeBreakerNetwork() { + Network network = NetworkFactory.findDefault().createNetwork("three-windings-transformer-nodeBreaker", "test"); + network.setCaseDate(ZonedDateTime.parse("2018-03-05T13:30:30.486+01:00")); + Substation substation = network.newSubstation() + .setId("SUBSTATION") + .setCountry(Country.FR) + .add(); + VoltageLevel vl1 = substation.newVoltageLevel() + .setId("VL_132") + .setNominalV(132.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + VoltageLevel vl2 = substation.newVoltageLevel() + .setId("VL_33") + .setNominalV(33.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + VoltageLevel vl3 = substation.newVoltageLevel() + .setId("VL_11") + .setNominalV(11.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + substation.newThreeWindingsTransformer() + .setId("3WT") + .setRatedU0(132.0) + .newLeg1() + .setR(17.424) + .setX(1.7424) + .setG(0.00573921028466483) + .setB(0.000573921028466483) + .setRatedU(132.0) + .setVoltageLevel(vl1.getId()) + .setNode(1) + .add() + .newLeg2() + .setR(1.089) + .setX(0.1089) + .setG(0.0) + .setB(0.0) + .setRatedU(33.0) + .setVoltageLevel(vl2.getId()) + .setNode(1) + .add() + .newLeg3() + .setR(0.121) + .setX(0.0121) + .setG(0.0) + .setB(0.0) + .setRatedU(11.0) + .setVoltageLevel(vl3.getId()) + .setNode(1) + .add() + .add(); + return network; + } + + static void addExtensions(ThreeWindingsTransformer t3w) { + t3w.newExtension(ThreeWindingsTransformerFortescueAdder.class).leg1() + .withConnectionType(WindingConnectionType.Y) + .withFreeFluxes(false) + .withGroundingR(0.1) + .withGroundingX(0.11) + .withRz(0.12) + .withXz(0.121) + .leg2().withConnectionType(WindingConnectionType.Y) + .withFreeFluxes(false) + .withGroundingR(0.2) + .withGroundingX(0.21) + .withRz(0.22) + .withXz(0.221) + .leg3() + .withConnectionType(WindingConnectionType.DELTA) + .withFreeFluxes(true) + .withGroundingR(0.3) + .withGroundingX(0.31) + .withRz(0.32) + .withXz(0.321) + .add(); + t3w.newExtension(ThreeWindingsTransformerPhaseAngleClockAdder.class) + .withPhaseAngleClockLeg2(2) + .withPhaseAngleClockLeg3(6) + .add(); + t3w.newExtension(ThreeWindingsTransformerToBeEstimatedAdder.class) + .withRatioTapChanger1Status(true) + .withPhaseTapChanger1Status(true) + .withRatioTapChanger2Status(true) + .withPhaseTapChanger2Status(true) + .withRatioTapChanger3Status(true) + .withPhaseTapChanger3Status(true) + .add(); + } + + static void addExtensions(TwoWindingsTransformer t2w, int diferenceFactor) { + t2w.newExtension(TwoWindingsTransformerFortescueAdder.class) + .withConnectionType1(WindingConnectionType.Y) + .withFreeFluxes(false) + .withGroundingR1(0.1 * diferenceFactor) + .withGroundingX1(0.11 * diferenceFactor) + .withRz(0.12 * diferenceFactor) + .withXz(0.121 * diferenceFactor) + .withConnectionType2(WindingConnectionType.Y) + .withGroundingR1(0.0) + .withGroundingX1(0.0) + .add(); + t2w.newExtension(TwoWindingsTransformerPhaseAngleClockAdder.class) + .withPhaseAngleClock(2 * diferenceFactor) + .add(); + t2w.newExtension(TwoWindingsTransformerToBeEstimatedAdder.class) + .withRatioTapChangerStatus(true) + .withPhaseTapChangerStatus(true) + .add(); + } +} diff --git a/iidm/iidm-reducer/pom.xml b/iidm/iidm-reducer/pom.xml index 696005eefbf..13e67173c71 100644 --- a/iidm/iidm-reducer/pom.xml +++ b/iidm/iidm-reducer/pom.xml @@ -15,7 +15,7 @@ powsybl-iidm com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-reducer diff --git a/iidm/iidm-scripting/pom.xml b/iidm/iidm-scripting/pom.xml index 9c289b92416..d61a891bc8f 100644 --- a/iidm/iidm-scripting/pom.xml +++ b/iidm/iidm-scripting/pom.xml @@ -15,7 +15,7 @@ powsybl-iidm com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-scripting diff --git a/iidm/iidm-serde/pom.xml b/iidm/iidm-serde/pom.xml index ab6e318b718..5e6b94b5a9f 100644 --- a/iidm/iidm-serde/pom.xml +++ b/iidm/iidm-serde/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-iidm - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-serde diff --git a/iidm/iidm-tck/pom.xml b/iidm/iidm-tck/pom.xml index f5362bae8a7..cf75b28ef5e 100644 --- a/iidm/iidm-tck/pom.xml +++ b/iidm/iidm-tck/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-iidm - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-tck diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractConvertTopologyTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractConvertTopologyTest.java new file mode 100644 index 00000000000..637f15e5197 --- /dev/null +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractConvertTopologyTest.java @@ -0,0 +1,202 @@ +/** + * 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.iidm.network.tck; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.events.RemovalNetworkEvent; +import com.powsybl.iidm.network.events.UpdateNetworkEvent; +import com.powsybl.iidm.network.extensions.SlackTerminal; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public abstract class AbstractConvertTopologyTest { + + private Network network; + private VoltageLevel vl; + private NetworkEventRecorder eventRecorder; + + @BeforeEach + void setUp() { + network = FourSubstationsNodeBreakerFactory.create(); + vl = network.getVoltageLevel("S1VL2"); + for (Switch sw : vl.getSwitches()) { + sw.setRetained(sw.getId().equals("S1VL2_COUPLER")); + } + eventRecorder = new NetworkEventRecorder(); + network.addListener(eventRecorder); + } + + @Test + void testBusBreakerToNodeBreaker() { + Network busBreakerNetwork = EurostagTutorialExample1Factory.create(); + VoltageLevel vlgen = busBreakerNetwork.getVoltageLevel("VLGEN"); + var e = assertThrows(PowsyblException.class, () -> vlgen.convertToTopology(TopologyKind.NODE_BREAKER)); + assertEquals("Topology model conversion from bus/breaker to node/breaker not yet supported", e.getMessage()); + } + + @Test + void testNodeBreakerToBusBreaker() { + var gh1 = network.getGenerator("GH1"); + var busesNbModel = vl.getBusBreakerView().getBusStream().toList(); + assertEquals(2, busesNbModel.size()); + assertEquals(List.of("S1VL2_0", "S1VL2_1"), busesNbModel.stream().map(Identifiable::getId).toList()); + assertEquals(List.of("S1VL2_BBS1", "TWT", "GH1", "GH2", "GH3", "SHUNT"), busesNbModel.get(0).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("S1VL2_BBS2", "VSC1", "LD2", "LD3", "LD4", "LCC1"), busesNbModel.get(1).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals("S1VL2_0", gh1.getRegulatingTerminal().getBusBreakerView().getBus().getId()); + vl.convertToTopology(TopologyKind.BUS_BREAKER); + + var busesBbModel = vl.getBusBreakerView().getBusStream().toList(); + assertEquals(2, busesBbModel.size()); + assertEquals(List.of("S1VL2_0", "S1VL2_1"), busesNbModel.stream().map(Identifiable::getId).toList()); + // compare to initial node/breaker model, only difference is that there is no more busbar sections + assertEquals(List.of("TWT", "GH1", "GH2", "GH3", "SHUNT"), busesBbModel.get(0).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("VSC1", "LD2", "LD3", "LD4", "LCC1"), busesBbModel.get(1).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + // only retained switches have been kept + assertEquals(List.of("S1VL2_COUPLER"), vl.getBusBreakerView().getSwitchStream().map(Identifiable::getId).toList()); + assertEquals(List.of(new RemovalNetworkEvent("S1VL2_BBS1", false), + new RemovalNetworkEvent("S1VL2_BBS1", true), + new RemovalNetworkEvent("S1VL2_BBS2", false), + new RemovalNetworkEvent("S1VL2_BBS2", true), + new RemovalNetworkEvent("S1VL2_BBS1_TWT_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_TWT_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_TWT_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_VSC1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_VSC1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_VSC1_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_GH1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_GH2_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_GH3_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_GH1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_GH2_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_GH3_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_GH1_BREAKER", false), + new RemovalNetworkEvent("S1VL2_GH2_BREAKER", false), + new RemovalNetworkEvent("S1VL2_GH3_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_LD2_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_LD3_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_LD4_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_LD2_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_LD3_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_LD4_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_LD2_BREAKER", false), + new RemovalNetworkEvent("S1VL2_LD3_BREAKER", false), + new RemovalNetworkEvent("S1VL2_LD4_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_SHUNT_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_SHUNT_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_SHUNT_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_LCC1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_LCC1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_LCC1_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_COUPLER_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_COUPLER_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_TWT_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_TWT_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_TWT_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_VSC1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_VSC1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_VSC1_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_GH1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS1_GH2_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS1_GH3_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_GH1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_GH2_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_GH3_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_GH1_BREAKER", true), + new RemovalNetworkEvent("S1VL2_GH2_BREAKER", true), + new RemovalNetworkEvent("S1VL2_GH3_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_LD2_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS1_LD3_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS1_LD4_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_LD2_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_LD3_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_LD4_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_LD2_BREAKER", true), + new RemovalNetworkEvent("S1VL2_LD3_BREAKER", true), + new RemovalNetworkEvent("S1VL2_LD4_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_SHUNT_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_SHUNT_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_SHUNT_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_LCC1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_LCC1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_LCC1_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_COUPLER_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_COUPLER_DISCONNECTOR", true), + new UpdateNetworkEvent("S1VL2", "topologyKind", null, TopologyKind.NODE_BREAKER, TopologyKind.BUS_BREAKER)), + eventRecorder.getEvents()); + + // check regulating terminal has been correctly updated + assertEquals("S1VL2_0", gh1.getRegulatingTerminal().getBusBreakerView().getBus().getId()); + + // check busbar sections have been removed + assertNull(network.getBusbarSection("S1VL2_BBS1")); + assertNull(network.getBusbarSection("S1VL2_BBS2")); + } + + @Test + void testNodeBreakerToBusBreakerOneElementDisconnected() { + var gh2 = network.getGenerator("GH2"); + gh2.disconnect(); + assertEquals(List.of(new UpdateNetworkEvent("S1VL2_GH2_BREAKER", "open", "InitialState", false, true)), eventRecorder.getEvents()); + eventRecorder.reset(); + var busesNbModel = vl.getBusBreakerView().getBusStream().toList(); + assertEquals(3, busesNbModel.size()); + assertEquals(List.of("S1VL2_0", "S1VL2_1", "S1VL2_9"), busesNbModel.stream().map(Identifiable::getId).toList()); + assertEquals(List.of("S1VL2_BBS1", "TWT", "GH1", "GH3", "SHUNT"), busesNbModel.get(0).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("S1VL2_BBS2", "VSC1", "LD2", "LD3", "LD4", "LCC1"), busesNbModel.get(1).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("GH2"), busesNbModel.get(2).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + vl.convertToTopology(TopologyKind.BUS_BREAKER); + assertEquals(69, eventRecorder.getEvents().size()); + + var busesBbModel = vl.getBusBreakerView().getBusStream().toList(); + assertEquals(3, busesBbModel.size()); + assertEquals(List.of("S1VL2_0", "S1VL2_1", "S1VL2_9"), busesNbModel.stream().map(Identifiable::getId).toList()); + // compare to initial node/breaker model, only difference is that there is no more busbar sections + assertEquals(List.of("TWT", "GH1", "GH3", "SHUNT"), busesBbModel.get(0).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("VSC1", "LD2", "LD3", "LD4", "LCC1"), busesBbModel.get(1).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(Collections.emptyList(), busesBbModel.get(2).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals("S1VL2_9", gh2.getTerminal().getBusBreakerView().getConnectableBus().getId()); + assertFalse(gh2.getTerminal().isConnected()); + // only retained switches have been kept + assertEquals(List.of("S1VL2_COUPLER"), vl.getBusBreakerView().getSwitchStream().map(Identifiable::getId).toList()); + } + + @Test + void testNodeBreakerToBusBreakerWithArea() { + var gh2 = network.getGenerator("GH2"); + network.newArea() + .setId("area1") + .setAreaType("fake") + .addAreaBoundary(gh2.getTerminal(), true) + .add(); + var boundary = network.getArea("area1").getAreaBoundaryStream().findFirst().orElseThrow(); + assertEquals(gh2.getTerminal(), boundary.getTerminal().orElse(null)); + vl.convertToTopology(TopologyKind.BUS_BREAKER); + assertEquals(gh2.getTerminal(), boundary.getTerminal().orElse(null)); + } + + @Test + void testWithSlackTerminalExtension() { + var gh2 = network.getGenerator("GH2"); + SlackTerminal.reset(gh2.getTerminal().getVoltageLevel(), gh2.getTerminal()); + var slackTerminal = gh2.getTerminal().getVoltageLevel().getExtension(SlackTerminal.class); + assertEquals(gh2.getTerminal(), slackTerminal.getTerminal()); + vl.convertToTopology(TopologyKind.BUS_BREAKER); + assertEquals(gh2.getTerminal(), slackTerminal.getTerminal()); + } +} diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractForkConnectDisconnectTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractForkConnectDisconnectTest.java new file mode 100644 index 00000000000..5231e5539d3 --- /dev/null +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractForkConnectDisconnectTest.java @@ -0,0 +1,247 @@ +/* + * 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.iidm.network.tck; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.BusbarSectionPositionAdder; +import com.powsybl.iidm.network.util.SwitchPredicates; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; + +/** + * @author Nicolas Rol {@literal } + */ +public abstract class AbstractForkConnectDisconnectTest { + + public Network createNetwork() { + Network network = Network.create("test", "test"); + // Substations + Substation s1 = network.newSubstation() + .setId("S1") + .setCountry(Country.FR) + .add(); + VoltageLevel vl1 = s1.newVoltageLevel() + .setId("VL1") + .setNominalV(1.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + VoltageLevel vl2 = s1.newVoltageLevel() + .setId("VL2") + .setNominalV(1.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + // Busbar sections + BusbarSection bbs1 = vl1.getNodeBreakerView() + .newBusbarSection() + .setId("BBS11") + .setNode(0) + .add(); + bbs1.newExtension(BusbarSectionPositionAdder.class) + .withBusbarIndex(1) + .withSectionIndex(1) + .add(); + BusbarSection bbs2A = vl2.getNodeBreakerView() + .newBusbarSection() + .setId("BBS2A") + .setNode(0) + .add(); + bbs2A.newExtension(BusbarSectionPositionAdder.class) + .withBusbarIndex(1) + .withSectionIndex(1) + .add(); + BusbarSection bbs2B = vl2.getNodeBreakerView() + .newBusbarSection() + .setId("BBS2B") + .setNode(1) + .add(); + bbs2B.newExtension(BusbarSectionPositionAdder.class) + .withBusbarIndex(2) + .withSectionIndex(1) + .add(); + + // Lines + network.newLine() + .setId("L1") + .setName("LINE1") + .setR(1.0) + .setX(2.0) + .setG1(3.0) + .setG2(3.5) + .setB1(4.0) + .setB2(4.5) + .setVoltageLevel1("VL1") + .setVoltageLevel2("VL2") + .setNode1(4) + .setNode2(3) + .add(); + + // Add a second line + network.newLine() + .setId("L2") + .setName("LINE2") + .setR(1.0) + .setX(2.0) + .setG1(3.0) + .setG2(3.5) + .setB1(4.0) + .setB2(4.5) + .setVoltageLevel1("VL1") + .setVoltageLevel2("VL2") + .setNode1(5) + .setNode2(5) + .add(); + + // VL1 - Breakers and disconnectors for L1 and L2 + vl1.getNodeBreakerView().newDisconnector() + .setId("D_L1") + .setNode1(4) + .setNode2(6) + .setOpen(false) + .add(); + vl1.getNodeBreakerView().newDisconnector() + .setId("D_L2") + .setNode1(5) + .setNode2(6) + .setOpen(false) + .add(); + vl1.getNodeBreakerView().newBreaker() + .setId("B_L1_L2") + .setNode1(6) + .setNode2(7) + .setOpen(false) + .setFictitious(false) + .add(); + vl1.getNodeBreakerView().newDisconnector() + .setId("D0") + .setNode1(7) + .setNode2(0) + .setOpen(false) + .add(); + + // VL2 - Breakers and disconnectors for L1 and L2 + vl2.getNodeBreakerView().newBreaker() + .setId("B_L1_VL2") + .setNode1(3) + .setNode2(2) + .setOpen(false) + .setFictitious(false) + .add(); + vl2.getNodeBreakerView().newDisconnector() + .setId("D_L1_BBS2A") + .setNode1(2) + .setNode2(0) + .setOpen(false) + .add(); + vl2.getNodeBreakerView().newDisconnector() + .setId("D_L1_BBS2B") + .setNode1(2) + .setNode2(1) + .setOpen(true) + .add(); + vl2.getNodeBreakerView().newBreaker() + .setId("B_L2_VL2") + .setNode1(5) + .setNode2(4) + .setOpen(false) + .setFictitious(false) + .add(); + vl2.getNodeBreakerView().newDisconnector() + .setId("D_L2_BBS2A") + .setNode1(4) + .setNode2(0) + .setOpen(true) + .add(); + vl2.getNodeBreakerView().newDisconnector() + .setId("D_L2_BBS2B") + .setNode1(4) + .setNode2(1) + .setOpen(false) + .add(); + return network; + } + + @Test + public void fullyClosedTest() { + Network network = createNetwork(); + + // Useful elements + VoltageLevel.NodeBreakerView topo = network.getVoltageLevel("VL1").getNodeBreakerView(); + Line line1 = network.getLine("L1"); + Line line2 = network.getLine("L2"); + Switch disconnector = network.getSwitch("D_L1"); + + // L1 and L2 are fully connected + assertTrue(topo.getOptionalTerminal(4).isPresent()); + assertTerminalsStatus(line1.getTerminals(), true); + assertTrue(topo.getOptionalTerminal(5).isPresent()); + assertTerminalsStatus(line2.getTerminals(), true); + + // Disconnect L1 + line1.disconnect(SwitchPredicates.IS_NON_NULL); + + // check that L1 is disconnected while L2 is still connected + assertTrue(topo.getOptionalTerminal(4).isPresent()); + assertTerminalsStatus(line1.getTerminals(), false); + assertTrue(topo.getOptionalTerminal(5).isPresent()); + assertTerminalsStatus(line2.getTerminals(), true); + + // D_L1 should be open + assertTrue(disconnector.isOpen()); + } + + @Test + public void forkDisconnectedTest() { + Network network = createNetwork(); + + // Useful elements + VoltageLevel.NodeBreakerView topo = network.getVoltageLevel("VL1").getNodeBreakerView(); + Line line1 = network.getLine("L1"); + Line line2 = network.getLine("L2"); + Switch disconnectorL1 = network.getSwitch("D_L1"); + Switch disconnectorL2 = network.getSwitch("D_L2"); + Switch breaker = network.getSwitch("B_L1_L2"); + + // In this case, B_L1_L2 is open + breaker.setOpen(true); + + // L1 and L2 are fully connected + assertTrue(topo.getOptionalTerminal(4).isPresent()); + assertTerminalsStatus(line1.getTerminals(), true); + assertTrue(topo.getOptionalTerminal(5).isPresent()); + assertTerminalsStatus(line2.getTerminals(), true); + + // Disconnect L1 + line1.disconnect(SwitchPredicates.IS_NON_NULL); + + // check that L1 and L2 are disconnected + assertTrue(topo.getOptionalTerminal(4).isPresent()); + assertTerminalsStatus(line1.getTerminals(), false); + assertTrue(topo.getOptionalTerminal(5).isPresent()); + assertTerminalsStatus(List.of(line2.getTerminal1()), false); + assertTerminalsStatus(List.of(line2.getTerminal2()), true); + + // D_L1 should be open but not D_L2 + assertTrue(disconnectorL1.isOpen()); + assertFalse(disconnectorL2.isOpen()); + } + + private void assertTerminalsStatus(List terminals, boolean terminalsShouldBeConnected) { + if (terminalsShouldBeConnected) { + terminals.forEach(terminal -> assertNotNull(terminal.getBusView().getBus())); + terminals.forEach(terminal -> assertTrue(terminal.isConnected())); + } else { + terminals.forEach(terminal -> assertNull(terminal.getBusView().getBus())); + terminals.forEach(terminal -> assertFalse(terminal.isConnected())); + } + } +} diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerInternalConnectionsTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerInternalConnectionsTest.java index 7c142cdf18a..a065b9a163b 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerInternalConnectionsTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerInternalConnectionsTest.java @@ -12,8 +12,8 @@ import com.powsybl.math.graph.TraverseResult; import org.junit.jupiter.api.Test; -import java.util.*; -import java.util.stream.Collectors; +import java.util.HashSet; +import java.util.List; import static com.powsybl.iidm.network.VoltageLevel.NodeBreakerView.InternalConnection; import static org.junit.jupiter.api.Assertions.*; @@ -32,47 +32,28 @@ public void testTraversalInternalConnections() { createNetwork(network, all); VoltageLevel vl = network.getVoltageLevel(S5_10K_V); - assertEquals(6, vl.getNodeBreakerView().getInternalConnectionCount()); + assertEquals(10, vl.getNodeBreakerView().getInternalConnectionCount()); List internalConnections = vl.getNodeBreakerView().getInternalConnectionStream().toList(); - int[] expecteds1 = new int[]{7, 6, 4, 5, 9, 8}; - int[] expecteds2 = new int[]{0, 3, 3, 2, 2, 1}; - assertEquals(expecteds1.length, expecteds2.length); - for (int i = 0; i < expecteds1.length; i++) { - assertEquals(expecteds1[i], internalConnections.get(i).getNode1()); - assertEquals(expecteds2[i], internalConnections.get(i).getNode2()); + int[][] expectedIcNodes = new int[][]{{7, 0}, {6, 3}, {4, 3}, {5, 2}, {9, 2}, {8, 1}, {1, 10}, {3, 11}, {2, 12}, {2, 13}}; + for (int i = 0; i < 10; i++) { + assertEquals(expectedIcNodes[i][0], internalConnections.get(i).getNode1()); + assertEquals(expectedIcNodes[i][1], internalConnections.get(i).getNode2()); } - Iterator nodeIterator7 = vl.getNodeBreakerView().getNodesInternalConnectedTo(7).iterator(); - assertEquals(0, (int) nodeIterator7.next()); - assertFalse(nodeIterator7.hasNext()); + assertEquals(List.of(0), vl.getNodeBreakerView().getNodesInternalConnectedTo(7)); + assertEquals(List.of(5, 9, 12, 13), vl.getNodeBreakerView().getNodesInternalConnectedTo(2)); + assertEquals(List.of(6, 4, 11), vl.getNodeBreakerView().getNodeInternalConnectedToStream(3).boxed().toList()); - Iterator nodeIterator2 = vl.getNodeBreakerView().getNodesInternalConnectedTo(2).iterator(); - assertEquals(5, (int) nodeIterator2.next()); - assertEquals(9, (int) nodeIterator2.next()); - assertFalse(nodeIterator2.hasNext()); + assertEquals(new InternalConnections().add(0, 7), findFirstInternalConnections(vl)); - List nodesInternallyConnectedTo3 = vl.getNodeBreakerView().getNodeInternalConnectedToStream(3).boxed().toList(); - assertEquals(Arrays.asList(6, 4), nodesInternallyConnectedTo3); + // Find the internal connections encountered before encountering a terminal, starting from every node + // Only internal connections connecting two nodes having both a terminal are expected to be missing + InternalConnections icConnectedToAtMostOneTerminal = findInternalConnectionsTraverseStoppingAtTerminals(vl); + InternalConnections expected = new InternalConnections(); + expected.add(7, 0).add(6, 3).add(4, 3).add(5, 2).add(9, 2).add(8, 1); + assertEquals(expected, icConnectedToAtMostOneTerminal); - // Find the first internal connection encountered - InternalConnections firstInternalConnectionFound = findFirstInternalConnections(vl); - assertEquals(new InternalConnections().add(0, 7), firstInternalConnectionFound); - - // Find the internal connections encountered before encountering a terminal - InternalConnections foundStoppingAtTerminals = findInternalConnectionsTraverseStoppingAtTerminals(vl); - // If we stop traversal at terminals - // some internal connections are expected to be missing - InternalConnections expectedMissing = new InternalConnections().add(6, 3).add(9, 2).add(4, 3); - - // Compute all missing connections - Set actualMissing = all.stream() - .filter(c -> !foundStoppingAtTerminals.contains(c)) - .collect(Collectors.toSet()); - assertEquals(expectedMissing, actualMissing); - - InternalConnections actual = findInternalConnections(vl); - InternalConnections expected = all; - assertEquals(expected, actual); + assertEquals(all, findInternalConnections(vl)); } @@ -187,15 +168,23 @@ private void createNetwork(Network network, InternalConnections internalConnecti addInternalConnection(topo, internalConnections, 5, 2); addInternalConnection(topo, internalConnections, 9, 2); addInternalConnection(topo, internalConnections, 8, 1); + addInternalConnection(topo, internalConnections, 1, 10); + addInternalConnection(topo, internalConnections, 3, 11); + addInternalConnection(topo, internalConnections, 2, 12); + addInternalConnection(topo, internalConnections, 2, 13); Substation s4 = network.newSubstation() .setId("S4") .setCountry(Country.FR) .add(); - s4.newVoltageLevel() + VoltageLevel vl2 = s4.newVoltageLevel() .setId("S4 10kV") .setNominalV(10.0) .setTopologyKind(TopologyKind.NODE_BREAKER) .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("NODE40") + .setNode(1) + .add(); network.newLine() .setId("L6") .setVoltageLevel1("S4 10kV") @@ -209,6 +198,12 @@ private void createNetwork(Network network, InternalConnections internalConnecti .setG2(0) .setB2(0) .add(); + vl2.getNodeBreakerView().newSwitch() + .setId("DISCONNECTOR1") + .setNode1(0) + .setNode2(1) + .setKind(SwitchKind.DISCONNECTOR) + .add(); } private void addInternalConnection( diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerTest.java index e33898eaea1..c05049cd5e6 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerTest.java @@ -312,6 +312,33 @@ private static Network createIsolatedLoadNetwork() { return network; } + /** + *
+     *                load
+     *                  |
+     *               ___|___
+     *               |     |
+     *           fd1 x     x fd2
+     * bbs1 _________|__   |
+     *        |            |
+     *        c            |
+     * bbs2 __|____________|__
+     * 
+ */ + private static Network createNetworkWithLoop() { + Network network = Network.create("test", "test"); + Substation substation = network.newSubstation().setId("s").add(); + VoltageLevel vl = substation.newVoltageLevel().setId("vl").setNominalV(400).setTopologyKind(TopologyKind.NODE_BREAKER).add(); + VoltageLevel.NodeBreakerView topology = vl.getNodeBreakerView(); + topology.newBusbarSection().setId("bbs1").setNode(0).add(); + topology.newBusbarSection().setId("bbs2").setNode(1).add(); + topology.newDisconnector().setId("fd1").setNode1(0).setNode2(2).add(); + topology.newDisconnector().setId("fd2").setNode1(1).setNode2(2).add(); + topology.newBreaker().setId("c").setNode1(0).setNode2(1).add(); + vl.newLoad().setId("load").setNode(2).setP0(10).setQ0(3).add(); + return network; + } + @Test public void connectDisconnectRemove() { Network network = createNetwork(); @@ -533,4 +560,19 @@ public void testRemove() { sub.remove(); assertNull(network.getSubstation("S1")); } + + @Test + public void testCalculatedBusTopologyWithLoop() { + Network n = createNetworkWithLoop(); + + Bus busBbv = n.getBusBreakerView().getBus("vl_0"); + assertNotNull(busBbv); + assertEquals(1, n.getBusBreakerView().getBusCount()); + assertEquals(3, busBbv.getConnectedTerminalCount()); + + Bus busBv = n.getBusView().getBus("vl_0"); + assertNotNull(busBv); + assertEquals(1, n.getBusView().getBusStream().count()); + assertEquals(3, busBv.getConnectedTerminalCount()); + } } diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractReactiveCapabilityCurveTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractReactiveCapabilityCurveTest.java index c8fab8e9655..7e989dd5fbe 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractReactiveCapabilityCurveTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractReactiveCapabilityCurveTest.java @@ -10,13 +10,15 @@ import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.ReactiveCapabilityCurve; -import com.powsybl.iidm.network.ReactiveLimitsKind; import com.powsybl.iidm.network.ValidationException; import com.powsybl.iidm.network.test.FictitiousSwitchFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.util.Optional; + +import static com.powsybl.iidm.network.ReactiveLimitsKind.CURVE; import static org.junit.jupiter.api.Assertions.*; public abstract class AbstractReactiveCapabilityCurveTest { @@ -48,7 +50,7 @@ public void testAdder() { .setMinQ(2.0) .endPoint() .add(); - assertEquals(ReactiveLimitsKind.CURVE, reactiveCapabilityCurve.getKind()); + assertEquals(CURVE, reactiveCapabilityCurve.getKind()); assertEquals(100.0, reactiveCapabilityCurve.getMaxP(), 0.0); assertEquals(1.0, reactiveCapabilityCurve.getMinP(), 0.0); assertEquals(3, reactiveCapabilityCurve.getPoints().size()); @@ -134,4 +136,33 @@ public void invalidMinQGreaterThanMaxQ() { assertTrue(e.getMessage().contains("maximum reactive power is expected to be greater than or equal to minimum reactive power")); } + @Test + void shouldCopyDataWhenCreatingReactiveCapabilityCurveFromTemplate() { + ReactiveCapabilityCurve copiedCurve = generator.newReactiveCapabilityCurve() + .beginPoint() + .setP(1) + .setMinQ(5) + .setMaxQ(50) + .endPoint() + .beginPoint() + .setP(2) + .setMinQ(7) + .setMaxQ(70) + .endPoint() + .add(); + Generator anotherGenerator = generator.getNetwork().getGenerator("CC"); + + anotherGenerator.newReactiveCapabilityCurve(copiedCurve) + .add(); + + copiedCurve.getPoints().forEach(copiedPoint -> { + Optional pastedPoint = anotherGenerator.getReactiveLimits(ReactiveCapabilityCurve.class).getPoints().stream() + .filter(point -> point.getP() == copiedPoint.getP()) + .findFirst(); + assertTrue(pastedPoint.isPresent()); + assertEquals(copiedPoint.getMinQ(), pastedPoint.get().getMinQ()); + assertEquals(copiedPoint.getMaxQ(), pastedPoint.get().getMaxQ()); + }); + + } } diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerHolderTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerHolderTest.java new file mode 100644 index 00000000000..191bdb6a3d3 --- /dev/null +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerHolderTest.java @@ -0,0 +1,195 @@ +/** + * 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.iidm.network.tck; + +import com.powsybl.iidm.network.*; +import org.junit.jupiter.api.Test; + +import static com.powsybl.iidm.network.PhaseTapChanger.RegulationMode.FIXED_TAP; +import static com.powsybl.iidm.network.RatioTapChanger.RegulationMode.VOLTAGE; +import static com.powsybl.iidm.network.TopologyKind.BUS_BREAKER; +import static com.powsybl.iidm.network.TwoSides.ONE; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Benoît Chiquet {@literal } + */ +public abstract class AbstractTapChangerHolderTest { + + @Test + void shouldReuseCopiedPhaseTapChangerPropertiesFixedTapExample() { + Network network = exampleNetwork(); + PhaseTapChanger existingPhaseTapChanger = network.getTwoWindingsTransformer("transformer") + .newPhaseTapChanger() + .setTapPosition(1) + .setRegulationValue(12) + .setRegulationMode(FIXED_TAP) + .setLowTapPosition(0) + .setRegulating(false) + .setTargetDeadband(3) + .beginStep().setAlpha(1).setRho(2).setR(3).setG(4).setB(5).setX(6) + .endStep() + .beginStep().setAlpha(2).setRho(3).setR(4).setG(5).setB(6).setX(7) + .endStep() + .beginStep().setAlpha(3).setRho(4).setR(5).setG(6).setB(7).setX(8) + .endStep() + .add(); + + PhaseTapChanger newPhaseTapChanger = network.getTwoWindingsTransformer("transformer2") + .newPhaseTapChanger(existingPhaseTapChanger) + .add(); + + assertEquals(existingPhaseTapChanger.getTapPosition(), newPhaseTapChanger.getTapPosition()); + assertEquals(existingPhaseTapChanger.getLowTapPosition(), newPhaseTapChanger.getLowTapPosition()); + assertEquals(existingPhaseTapChanger.getRegulationValue(), newPhaseTapChanger.getRegulationValue()); + assertEquals(existingPhaseTapChanger.getRegulationMode(), newPhaseTapChanger.getRegulationMode()); + assertEquals(existingPhaseTapChanger.isRegulating(), newPhaseTapChanger.isRegulating()); + assertEquals(existingPhaseTapChanger.getTargetDeadband(), newPhaseTapChanger.getTargetDeadband()); + + newPhaseTapChanger.getAllSteps().forEach((tap, newStep) -> { + PhaseTapChangerStep existingStep = existingPhaseTapChanger.getStep(tap); + assertEquals(existingStep.getAlpha(), newStep.getAlpha()); + assertEquals(existingStep.getRho(), newStep.getRho()); + assertEquals(existingStep.getR(), newStep.getR()); + assertEquals(existingStep.getG(), newStep.getG()); + assertEquals(existingStep.getB(), newStep.getB()); + assertEquals(existingStep.getX(), newStep.getX()); + }); + } + + @Test + void shouldReuseCopiedPhaseTapChangerPropertiesActivePowerControlExample() { + Network network = exampleNetwork(); + PhaseTapChanger existingPhaseTapChanger = network.getTwoWindingsTransformer("transformer") + .newPhaseTapChanger() + .setTapPosition(1) + .setRegulationValue(12) + .setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setRegulationTerminal(network.getTwoWindingsTransformer("transformer").getTerminal(ONE)) + .setLowTapPosition(0) + .setRegulating(false) + .setTargetDeadband(3) + .beginStep().setAlpha(1).setRho(2).setR(3).setG(4).setB(5).setX(6) + .endStep() + .beginStep().setAlpha(2).setRho(3).setR(4).setG(5).setB(6).setX(7) + .endStep() + .beginStep().setAlpha(3).setRho(4).setR(5).setG(6).setB(7).setX(8) + .endStep() + .add(); + + PhaseTapChanger newPhaseTapChanger = network.getTwoWindingsTransformer("transformer2") + .newPhaseTapChanger(existingPhaseTapChanger) + .add(); + + assertEquals(existingPhaseTapChanger.getTapPosition(), newPhaseTapChanger.getTapPosition()); + assertEquals(existingPhaseTapChanger.getLowTapPosition(), newPhaseTapChanger.getLowTapPosition()); + assertEquals(existingPhaseTapChanger.getRegulationValue(), newPhaseTapChanger.getRegulationValue()); + assertEquals(existingPhaseTapChanger.getRegulationMode(), newPhaseTapChanger.getRegulationMode()); + assertEquals(existingPhaseTapChanger.isRegulating(), newPhaseTapChanger.isRegulating()); + assertEquals(existingPhaseTapChanger.getTargetDeadband(), newPhaseTapChanger.getTargetDeadband()); + assertEquals(existingPhaseTapChanger.getRegulationTerminal(), newPhaseTapChanger.getRegulationTerminal()); + + newPhaseTapChanger.getAllSteps().forEach((tap, newStep) -> { + PhaseTapChangerStep existingStep = existingPhaseTapChanger.getStep(tap); + assertEquals(existingStep.getAlpha(), newStep.getAlpha()); + assertEquals(existingStep.getRho(), newStep.getRho()); + assertEquals(existingStep.getR(), newStep.getR()); + assertEquals(existingStep.getG(), newStep.getG()); + assertEquals(existingStep.getB(), newStep.getB()); + assertEquals(existingStep.getX(), newStep.getX()); + }); + } + + @Test + void shouldReuseCopiedRatioTapChangerProperties() { + Network network = exampleNetwork(); + RatioTapChanger existingRatioTapChanger = network.getTwoWindingsTransformer("transformer").newRatioTapChanger() + .setTapPosition(1) + .setTargetV(400) + .setRegulationValue(12) + .setRegulationMode(VOLTAGE) + .setLowTapPosition(0) + .setRegulating(false) + .setLoadTapChangingCapabilities(true) + .setTargetDeadband(3) + .beginStep().setRho(2).setR(3).setG(4).setB(5).setX(6) + .endStep() + .beginStep().setRho(3).setR(4).setG(5).setB(6).setX(7) + .endStep() + .beginStep().setRho(4).setR(5).setG(6).setB(7).setX(8) + .endStep() + .add(); + + RatioTapChanger newRatioTapChanger = network.getTwoWindingsTransformer("transformer2") + .newRatioTapChanger(existingRatioTapChanger) + .add(); + + assertEquals(existingRatioTapChanger.getTapPosition(), newRatioTapChanger.getTapPosition()); + assertEquals(existingRatioTapChanger.getLowTapPosition(), newRatioTapChanger.getLowTapPosition()); + assertEquals(existingRatioTapChanger.getRegulationValue(), newRatioTapChanger.getRegulationValue()); + assertEquals(existingRatioTapChanger.getRegulationMode(), newRatioTapChanger.getRegulationMode()); + assertEquals(existingRatioTapChanger.isRegulating(), newRatioTapChanger.isRegulating()); + assertEquals(existingRatioTapChanger.getTargetDeadband(), newRatioTapChanger.getTargetDeadband()); + assertEquals(existingRatioTapChanger.getRegulationTerminal(), newRatioTapChanger.getRegulationTerminal()); + assertEquals(existingRatioTapChanger.getTargetV(), newRatioTapChanger.getTargetV()); + assertEquals(existingRatioTapChanger.hasLoadTapChangingCapabilities(), newRatioTapChanger.hasLoadTapChangingCapabilities()); + + newRatioTapChanger.getAllSteps().forEach((tap, newStep) -> { + RatioTapChangerStep existingStep = existingRatioTapChanger.getStep(tap); + assertEquals(existingStep.getRho(), newStep.getRho()); + assertEquals(existingStep.getR(), newStep.getR()); + assertEquals(existingStep.getG(), newStep.getG()); + assertEquals(existingStep.getB(), newStep.getB()); + assertEquals(existingStep.getX(), newStep.getX()); + }); + } + + Network exampleNetwork() { + Network network = Network.create("test", "test"); + Substation substation = network.newSubstation() + .setId("substation") + .setCountry(Country.AD) + .add(); + VoltageLevel vl1 = substation.newVoltageLevel() + .setId("vl1") + .setTopologyKind(BUS_BREAKER) + .setName("name") + .setNominalV(225) + .setLowVoltageLimit(200) + .setHighVoltageLimit(250) + .add(); + vl1.getBusBreakerView().newBus().setId("bus1").add(); + VoltageLevel vl2 = substation.newVoltageLevel() + .setId("vl2") + .setTopologyKind(BUS_BREAKER) + .setName("name") + .setNominalV(90) + .setLowVoltageLimit(80) + .setHighVoltageLimit(100) + .add(); + vl2.getBusBreakerView().newBus().setId("bus2").add(); + + substation.newTwoWindingsTransformer() + .setId("transformer") + .setR(17) + .setX(10) + .setBus1("bus1") + .setBus2("bus2") + .add(); + + substation.newTwoWindingsTransformer() + .setId("transformer2") + .setR(12) + .setX(15) + .setBus1("bus1") + .setBus2("bus2") + .add(); + + return network; + } +} diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java index 5f73b06b4c7..681bc8e710f 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java @@ -16,8 +16,6 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.*; public abstract class AbstractTapChangerTest { diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java index 9e414ef43e4..3654f96fa9d 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java @@ -8,17 +8,18 @@ package com.powsybl.iidm.network.tck.extensions; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Terminal; +import com.powsybl.iidm.network.VariantManager; import com.powsybl.iidm.network.extensions.ReferenceTerminals; import com.powsybl.iidm.network.extensions.ReferenceTerminalsAdder; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Set; @@ -91,6 +92,9 @@ public void testVariants() { .withTerminals(Set.of(gh1.getTerminal())) .add(); ReferenceTerminals ext = network.getExtension(ReferenceTerminals.class); + assertEquals(1, gh1.getTerminal().getReferrers().size()); + assertEquals(0, gh2.getTerminal().getReferrers().size()); + assertEquals(0, gh3.getTerminal().getReferrers().size()); // create variants String variant1 = "variant1"; @@ -101,9 +105,11 @@ public void testVariants() { // add gh2 to variant1 variantManager.setWorkingVariant(variant1); ext.addReferenceTerminal(gh2.getTerminal()); + assertEquals(1, gh2.getTerminal().getReferrers().size()); // add gh3 to variant2 variantManager.setWorkingVariant(variant2); ext.addReferenceTerminal(gh3.getTerminal()); + assertEquals(1, gh3.getTerminal().getReferrers().size()); // initial variant unmodified variantManager.setWorkingVariant(INITIAL_VARIANT_ID); @@ -123,6 +129,8 @@ public void testVariants() { // clear variant 1 variantManager.setWorkingVariant(variant1); ext.reset(); + assertEquals(0, gh2.getTerminal().getReferrers().size()); + assertEquals(1, gh3.getTerminal().getReferrers().size()); // check variant 1 empty assertEquals(0, ext.getReferenceTerminals().size()); @@ -281,31 +289,4 @@ public void testRemoveEquipment() { assertEquals(1, ext.getReferenceTerminals().size()); assertTrue(ext.getReferenceTerminals().contains(gh2.getTerminal())); } - - @Test - public void testCleanup() { - Network net = Mockito.spy(EurostagTutorialExample1Factory.create()); - - net.newExtension(ReferenceTerminalsAdder.class) - .withTerminals(Collections.emptySet()) - .add(); - // check listener added - Mockito.verify(net, Mockito.times(1)).addListener(Mockito.any()); - Mockito.verify(net, Mockito.times(0)).removeListener(Mockito.any()); - - // overwrite existing extension - net.newExtension(ReferenceTerminalsAdder.class) - .withTerminals(Collections.emptySet()) - .add(); - // check old listener removed and new listener added - Mockito.verify(net, Mockito.times(2)).addListener(Mockito.any()); - Mockito.verify(net, Mockito.times(1)).removeListener(Mockito.any()); - - // remove extension - net.removeExtension(ReferenceTerminals.class); - // check all clean - Mockito.verify(net, Mockito.times(2)).addListener(Mockito.any()); - Mockito.verify(net, Mockito.times(2)).removeListener(Mockito.any()); - } - } diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java index 9760b527bc9..eb1f0177e3c 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java @@ -174,4 +174,42 @@ public void variantsCloneTest() { assertEquals("Variant index not set", e.getMessage()); } } + + @Test + public void adderTest() { + Network network = createNetwork(); + Generator g = network.getGenerator("g4"); + Line l = network.getLine("l34"); + var adder = g.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(200.0) + .withEnabled(true); + var e = assertThrows(PowsyblException.class, adder::add); + assertEquals("Regulating terminal must be set", e.getMessage()); + adder = g.newExtension(RemoteReactivePowerControlAdder.class) + .withRegulatingTerminal(l.getTerminal(TwoSides.ONE)) + .withEnabled(true); + e = assertThrows(PowsyblException.class, adder::add); + assertEquals("Reactive power target must be set", e.getMessage()); + var extension = g.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(200.0) + .withRegulatingTerminal(l.getTerminal(TwoSides.ONE)) + .add(); + assertTrue(extension.isEnabled()); + } + + @Test + public void terminalRemoveTest() { + Network network = createNetwork(); + Generator g = network.getGenerator("g4"); + Line l = network.getLine("l34"); + g.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(200.0) + .withRegulatingTerminal(l.getTerminal(TwoSides.ONE)) + .withEnabled(true) + .add(); + assertNotNull(g.getExtension(RemoteReactivePowerControl.class)); + l.remove(); + // extension has been removed because regulating terminal is invalid + assertNull(g.getExtension(RemoteReactivePowerControl.class)); + } } diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java index 8d665ba4e3b..774fd2d71f7 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java @@ -206,26 +206,33 @@ public void variantsResetTest() { SlackTerminal stGen = vlgen.getExtension(SlackTerminal.class); assertNotNull(stGen); final Terminal tGen = stGen.getTerminal(); + assertEquals(1, tGen.getReferrers().size()); // Testing that only current variant was set variantManager.setWorkingVariant(INITIAL_VARIANT_ID); assertNull(stGen.getTerminal()); stGen.setTerminal(tGen); + assertEquals(1, tGen.getReferrers().size()); variantManager.setWorkingVariant(variant1); assertNull(stGen.getTerminal()); stGen.setTerminal(tGen); + assertEquals(1, tGen.getReferrers().size()); // Testing the empty property of the slackTerminal variantManager.setWorkingVariant(INITIAL_VARIANT_ID); assertFalse(stGen.setTerminal(null).isEmpty()); + assertEquals(1, tGen.getReferrers().size()); variantManager.setWorkingVariant(variant2); assertFalse(stGen.setTerminal(null).isEmpty()); + assertEquals(1, tGen.getReferrers().size()); variantManager.setWorkingVariant(variant1); assertTrue(stGen.setTerminal(null).isEmpty()); + assertEquals(0, tGen.getReferrers().size()); assertFalse(stGen.setTerminal(tGen).isEmpty()); + assertEquals(1, tGen.getReferrers().size()); // Testing the cleanIfEmpty boolean stGen.setTerminal(null, false); @@ -284,4 +291,17 @@ public void testWithSubnetwork() { assertNull(merged.getVoltageLevel("VL").getExtension(SlackTerminal.class)); // reset assertNotNull(merged.getVoltageLevel("VLHV1").getExtension(SlackTerminal.class)); // untouched } + + @Test + public void removeTerminalConnectableTest() { + Network network = EurostagTutorialExample1Factory.createWithMoreGenerators(); + var vlgen = network.getVoltageLevel("VLGEN"); + var gen2 = network.getGenerator("GEN2"); + var slackTerminal = vlgen.newExtension(SlackTerminalAdder.class) + .withTerminal(gen2.getTerminal()) + .add(); + assertNotNull(slackTerminal.getTerminal()); + gen2.remove(); + assertNull(slackTerminal.getTerminal()); + } } diff --git a/iidm/iidm-test/pom.xml b/iidm/iidm-test/pom.xml index e5210f608ae..3200314d44a 100644 --- a/iidm/iidm-test/pom.xml +++ b/iidm/iidm-test/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-iidm - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-iidm-test diff --git a/iidm/iidm-test/src/main/java/com/powsybl/iidm/network/test/EurostagTutorialExample1Factory.java b/iidm/iidm-test/src/main/java/com/powsybl/iidm/network/test/EurostagTutorialExample1Factory.java index a70455a1968..39b32da1afe 100644 --- a/iidm/iidm-test/src/main/java/com/powsybl/iidm/network/test/EurostagTutorialExample1Factory.java +++ b/iidm/iidm-test/src/main/java/com/powsybl/iidm/network/test/EurostagTutorialExample1Factory.java @@ -18,8 +18,8 @@ */ public final class EurostagTutorialExample1Factory { - private static final String VLGEN = "VLGEN"; - private static final String VLLOAD = "VLLOAD"; + public static final String VLGEN = "VLGEN"; + public static final String VLLOAD = "VLLOAD"; public static final String CASE_DATE = "2018-01-01T11:00:00+01:00"; public static final String DANGLING_LINE_XNODE1_1 = "NHV1_XNODE1"; public static final String DANGLING_LINE_XNODE1_2 = "XNODE1_NHV2"; @@ -1174,7 +1174,8 @@ private static Network addReactiveGenerator(Network network, Terminal terminal) private static Network addRemoteVoltageGenerator(Network network) { network.getGenerator("GEN") - .setRegulatingTerminal(network.getTwoWindingsTransformer(NHV2_NLOAD).getTerminal1()); + .setRegulatingTerminal(network.getTwoWindingsTransformer(NHV2_NLOAD).getTerminal1()) + .setTargetV(399); return network; } diff --git a/iidm/pom.xml b/iidm/pom.xml index b9e91d11865..758892e6b1b 100644 --- a/iidm/pom.xml +++ b/iidm/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom diff --git a/itools-packager/pom.xml b/itools-packager/pom.xml index cb554c17b3f..ca5cccd8937 100644 --- a/itools-packager/pom.xml +++ b/itools-packager/pom.xml @@ -14,7 +14,7 @@ com.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-itools-packager-maven-plugin diff --git a/loadflow/loadflow-api/pom.xml b/loadflow/loadflow-api/pom.xml index 84067c57923..1163a642996 100644 --- a/loadflow/loadflow-api/pom.xml +++ b/loadflow/loadflow-api/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-loadflow - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-loadflow-api diff --git a/loadflow/loadflow-api/src/main/java/com/powsybl/loadflow/LoadFlowParameters.java b/loadflow/loadflow-api/src/main/java/com/powsybl/loadflow/LoadFlowParameters.java index d1a8f04518a..5b64f9668b7 100644 --- a/loadflow/loadflow-api/src/main/java/com/powsybl/loadflow/LoadFlowParameters.java +++ b/loadflow/loadflow-api/src/main/java/com/powsybl/loadflow/LoadFlowParameters.java @@ -149,8 +149,8 @@ protected static void load(LoadFlowParameters parameters, PlatformConfig platfor config.getOptionalBooleanProperty("dc").ifPresent(parameters::setDc); config.getOptionalBooleanProperty("distributedSlack").ifPresent(parameters::setDistributedSlack); config.getOptionalEnumProperty("balanceType", BalanceType.class).ifPresent(parameters::setBalanceType); - config.getOptionalBooleanProperty("dcUseTranformerRatio").ifPresent(parameters::setDcUseTransformerRatio); - config.getOptionalEnumSetProperty("contriesToBalance", Country.class).ifPresent(parameters::setCountriesToBalance); + config.getOptionalBooleanProperty("dcUseTransformerRatio").ifPresent(parameters::setDcUseTransformerRatio); + config.getOptionalEnumSetProperty("countriesToBalance", Country.class).ifPresent(parameters::setCountriesToBalance); config.getOptionalEnumProperty("connectedComponentMode", ConnectedComponentMode.class).ifPresent(parameters::setConnectedComponentMode); config.getOptionalBooleanProperty("hvdcAcEmulation").ifPresent(parameters::setHvdcAcEmulation); config.getOptionalDoubleProperty("dcPowerFactor").ifPresent(parameters::setDcPowerFactor); diff --git a/loadflow/loadflow-api/src/test/java/com/powsybl/loadflow/LoadFlowParametersTest.java b/loadflow/loadflow-api/src/test/java/com/powsybl/loadflow/LoadFlowParametersTest.java index 7b15480dc95..c08c2a3201e 100644 --- a/loadflow/loadflow-api/src/test/java/com/powsybl/loadflow/LoadFlowParametersTest.java +++ b/loadflow/loadflow-api/src/test/java/com/powsybl/loadflow/LoadFlowParametersTest.java @@ -50,7 +50,7 @@ private void checkValues(LoadFlowParameters parameters, LoadFlowParameters.Volta boolean dc, boolean distributedSlack, LoadFlowParameters.BalanceType balanceType, boolean dcUseTransformerRatio, Set countriesToBalance, LoadFlowParameters.ConnectedComponentMode computedConnectedComponent, - boolean hvdcAcEmulation) { + boolean hvdcAcEmulation, double dcPowerFactor) { assertEquals(parameters.getVoltageInitMode(), voltageInitMode); assertEquals(parameters.isTransformerVoltageControlOn(), transformerVoltageControlOn); assertEquals(parameters.isPhaseShifterRegulationOn(), phaseShifterRegulationOn); @@ -66,6 +66,7 @@ private void checkValues(LoadFlowParameters parameters, LoadFlowParameters.Volta assertEquals(parameters.getCountriesToBalance(), countriesToBalance); assertEquals(parameters.getConnectedComponentMode(), computedConnectedComponent); assertEquals(parameters.isHvdcAcEmulation(), hvdcAcEmulation); + assertEquals(parameters.getDcPowerFactor(), dcPowerFactor); } @Test @@ -86,51 +87,8 @@ void testNoConfig() { LoadFlowParameters.DEFAULT_DC_USE_TRANSFORMER_RATIO_DEFAULT, LoadFlowParameters.DEFAULT_COUNTRIES_TO_BALANCE, LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, - LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON); - } - - @Test - void checkConfig() { - boolean transformerVoltageControlOn = true; - boolean noGeneratorReactiveLimits = true; - boolean phaseShifterRegulationOn = true; - boolean twtSplitShuntAdmittance = true; - boolean simulShunt = true; - boolean readSlackBus = true; - boolean writeSlackBus = true; - boolean voltageRemoteControl = true; - boolean dc = true; - boolean distributedSlack = true; - LoadFlowParameters.BalanceType balanceType = LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD; - LoadFlowParameters.VoltageInitMode voltageInitMode = LoadFlowParameters.VoltageInitMode.UNIFORM_VALUES; - boolean dcUseTransformerRatio = true; - Set countriesToBalance = new HashSet<>(); - LoadFlowParameters.ConnectedComponentMode computedConnectedComponent = LoadFlowParameters.ConnectedComponentMode.MAIN; - boolean hvdcAcEmulation = false; - - MapModuleConfig moduleConfig = platformConfig.createModuleConfig("load-flow-default-parameters"); - moduleConfig.setStringProperty("voltageInitMode", "UNIFORM_VALUES"); - moduleConfig.setStringProperty("transformerVoltageControlOn", Boolean.toString(transformerVoltageControlOn)); - moduleConfig.setStringProperty("noGeneratorReactiveLimits", Boolean.toString(noGeneratorReactiveLimits)); - moduleConfig.setStringProperty("phaseShifterRegulationOn", Boolean.toString(phaseShifterRegulationOn)); - moduleConfig.setStringProperty("twtSplitShuntAdmittance", Boolean.toString(twtSplitShuntAdmittance)); - moduleConfig.setStringProperty("simulShunt", Boolean.toString(simulShunt)); - moduleConfig.setStringProperty("readSlackBus", Boolean.toString(readSlackBus)); - moduleConfig.setStringProperty("writeSlackBus", Boolean.toString(writeSlackBus)); - moduleConfig.setStringProperty("voltageRemoteControl", Boolean.toString(voltageRemoteControl)); - moduleConfig.setStringProperty("dc", Boolean.toString(dc)); - moduleConfig.setStringProperty("distributedSlack", Boolean.toString(dc)); - moduleConfig.setStringProperty("balanceType", balanceType.name()); - moduleConfig.setStringProperty("dcUseTransformerRatio", Boolean.toString(dc)); - moduleConfig.setStringListProperty("countriesToBalance", countriesToBalance.stream().map(e -> e.name()).toList()); - moduleConfig.setStringProperty("computedConnectedComponent", computedConnectedComponent.name()); - moduleConfig.setStringProperty("hvdcAcEmulation", Boolean.toString(hvdcAcEmulation)); - - LoadFlowParameters parameters = new LoadFlowParameters(); - LoadFlowParameters.load(parameters, platformConfig); - checkValues(parameters, voltageInitMode, transformerVoltageControlOn, - !noGeneratorReactiveLimits, phaseShifterRegulationOn, twtSplitShuntAdmittance, simulShunt, readSlackBus, writeSlackBus, - dc, distributedSlack, balanceType, dcUseTransformerRatio, countriesToBalance, computedConnectedComponent, hvdcAcEmulation); + LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON, + LoadFlowParameters.DEFAULT_DC_POWER_FACTOR); } @Test @@ -146,7 +104,8 @@ void checkIncompleteConfig() { LoadFlowParameters.DEFAULT_SHUNT_COMPENSATOR_VOLTAGE_CONTROL_ON, LoadFlowParameters.DEFAULT_READ_SLACK_BUS, LoadFlowParameters.DEFAULT_WRITE_SLACK_BUS, LoadFlowParameters.DEFAULT_DC, LoadFlowParameters.DEFAULT_DISTRIBUTED_SLACK, LoadFlowParameters.DEFAULT_BALANCE_TYPE, LoadFlowParameters.DEFAULT_DC_USE_TRANSFORMER_RATIO_DEFAULT, LoadFlowParameters.DEFAULT_COUNTRIES_TO_BALANCE, - LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON); + LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON, + LoadFlowParameters.DEFAULT_DC_POWER_FACTOR); } @Test @@ -159,7 +118,8 @@ void checkDefaultPlatformConfig() { LoadFlowParameters.DEFAULT_SHUNT_COMPENSATOR_VOLTAGE_CONTROL_ON, LoadFlowParameters.DEFAULT_READ_SLACK_BUS, LoadFlowParameters.DEFAULT_WRITE_SLACK_BUS, LoadFlowParameters.DEFAULT_DC, LoadFlowParameters.DEFAULT_DISTRIBUTED_SLACK, LoadFlowParameters.DEFAULT_BALANCE_TYPE, LoadFlowParameters.DEFAULT_DC_USE_TRANSFORMER_RATIO_DEFAULT, LoadFlowParameters.DEFAULT_COUNTRIES_TO_BALANCE, - LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON); + LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON, + LoadFlowParameters.DEFAULT_DC_POWER_FACTOR); } @Test @@ -173,7 +133,8 @@ void checkConstructorByVoltageInitMode() { LoadFlowParameters.DEFAULT_SHUNT_COMPENSATOR_VOLTAGE_CONTROL_ON, LoadFlowParameters.DEFAULT_READ_SLACK_BUS, LoadFlowParameters.DEFAULT_WRITE_SLACK_BUS, LoadFlowParameters.DEFAULT_DC, LoadFlowParameters.DEFAULT_DISTRIBUTED_SLACK, LoadFlowParameters.DEFAULT_BALANCE_TYPE, LoadFlowParameters.DEFAULT_DC_USE_TRANSFORMER_RATIO_DEFAULT, LoadFlowParameters.DEFAULT_COUNTRIES_TO_BALANCE, - LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON); + LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON, + LoadFlowParameters.DEFAULT_DC_POWER_FACTOR); } @Test @@ -195,7 +156,8 @@ void checkConstructorByVoltageInitModeAndTransformerVoltageControlOn() { LoadFlowParameters.DEFAULT_DC_USE_TRANSFORMER_RATIO_DEFAULT, LoadFlowParameters.DEFAULT_COUNTRIES_TO_BALANCE, LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, - LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON); + LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON, + LoadFlowParameters.DEFAULT_DC_POWER_FACTOR); } @Test @@ -216,7 +178,8 @@ void checkConstructorByLoadFlowParameters() { LoadFlowParameters.DEFAULT_DC_USE_TRANSFORMER_RATIO_DEFAULT, LoadFlowParameters.DEFAULT_COUNTRIES_TO_BALANCE, LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, - LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON); + LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON, + LoadFlowParameters.DEFAULT_DC_POWER_FACTOR); LoadFlowParameters parameters1 = new LoadFlowParameters(parameters); parameters1.setDc(true); @@ -236,7 +199,8 @@ void checkConstructorByLoadFlowParameters() { LoadFlowParameters.DEFAULT_DC_USE_TRANSFORMER_RATIO_DEFAULT, LoadFlowParameters.DEFAULT_COUNTRIES_TO_BALANCE, LoadFlowParameters.DEFAULT_CONNECTED_COMPONENT_MODE, - LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON); + LoadFlowParameters.DEFAULT_HVDC_AC_EMULATION_ON, + LoadFlowParameters.DEFAULT_DC_POWER_FACTOR); } @Test @@ -256,6 +220,7 @@ void checkSetters() { Set countriesToBalance = new HashSet<>(); LoadFlowParameters.ConnectedComponentMode computedConnectedComponent = LoadFlowParameters.ConnectedComponentMode.MAIN; boolean hvdcAcEmulation = false; + double dcPowerFactor = 0.95; LoadFlowParameters parameters = new LoadFlowParameters(); LoadFlowParameters.load(parameters, platformConfig); @@ -270,11 +235,12 @@ void checkSetters() { .setDc(dc) .setDistributedSlack(distributedSlack) .setBalanceType(balanceType) - .setHvdcAcEmulation(hvdcAcEmulation); + .setHvdcAcEmulation(hvdcAcEmulation) + .setDcPowerFactor(dcPowerFactor); checkValues(parameters, voltageInitMode, transformerVoltageControlOn, useReactiveLimits, phaseShifterRegulationOn, twtSplitShuntAdmittance, simulShunt, readSlackBus, writeSlackBus, - dc, distributedSlack, balanceType, dcUseTransformerRatio, countriesToBalance, computedConnectedComponent, hvdcAcEmulation); + dc, distributedSlack, balanceType, dcUseTransformerRatio, countriesToBalance, computedConnectedComponent, hvdcAcEmulation, dcPowerFactor); } @Test @@ -294,6 +260,7 @@ void checkClone() { Set countriesToBalance = new HashSet<>(); LoadFlowParameters.ConnectedComponentMode computedConnectedComponent = LoadFlowParameters.ConnectedComponentMode.MAIN; boolean hvdcAcEmulation = false; + double dcPowerFactor = 0.95; LoadFlowParameters parameters = new LoadFlowParameters() .setVoltageInitMode(voltageInitMode) .setTransformerVoltageControlOn(transformerVoltageControlOn) @@ -309,13 +276,14 @@ void checkClone() { .setDcUseTransformerRatio(dcUseTransformerRatio) .setCountriesToBalance(countriesToBalance) .setConnectedComponentMode(computedConnectedComponent) - .setHvdcAcEmulation(hvdcAcEmulation); + .setHvdcAcEmulation(hvdcAcEmulation) + .setDcPowerFactor(dcPowerFactor); LoadFlowParameters parametersCloned = parameters.copy(); checkValues(parametersCloned, parameters.getVoltageInitMode(), parameters.isTransformerVoltageControlOn(), parameters.isUseReactiveLimits(), parameters.isPhaseShifterRegulationOn(), parameters.isTwtSplitShuntAdmittance(), parameters.isShuntCompensatorVoltageControlOn(), parameters.isReadSlackBus(), parameters.isWriteSlackBus(), parameters.isDc(), parameters.isDistributedSlack(), parameters.getBalanceType(), parameters.isDcUseTransformerRatio(), - parameters.getCountriesToBalance(), parameters.getConnectedComponentMode(), parameters.isHvdcAcEmulation()); + parameters.getCountriesToBalance(), parameters.getConnectedComponentMode(), parameters.isHvdcAcEmulation(), parameters.getDcPowerFactor()); } @Test @@ -360,4 +328,95 @@ void testExtensionFromConfig() { assertInstanceOf(DummyExtension.class, parameters.getExtensionByName("dummy-extension")); assertNotNull(parameters.getExtension(DummyExtension.class)); } + + @Test + void checkAllValuesNonDefault() { + LoadFlowParameters.VoltageInitMode voltageInitMode = LoadFlowParameters.VoltageInitMode.DC_VALUES; + boolean transformerVoltageControlOn = true; + boolean useReactiveLimits = false; + boolean phaseShifterRegulationOn = true; + boolean twtSplitShuntAdmittance = true; + boolean shuntCompensatorVoltageControlOn = true; + boolean readSlackBus = false; + boolean writeSlackBus = false; + boolean dc = true; + boolean distributedSlack = false; + LoadFlowParameters.BalanceType balanceType = LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD; + boolean dcUseTransformerRatio = false; + Set countriesToBalance = Set.of(Country.FR); + LoadFlowParameters.ConnectedComponentMode connectedComponentMode = LoadFlowParameters.ConnectedComponentMode.ALL; + boolean hvdcAcEmulation = false; + double dcPowerFactor = 0.95; + + MapModuleConfig moduleConfig = platformConfig.createModuleConfig("load-flow-default-parameters"); + moduleConfig.setStringProperty("voltageInitMode", voltageInitMode.name()); + moduleConfig.setStringProperty("transformerVoltageControlOn", Boolean.toString(transformerVoltageControlOn)); + moduleConfig.setStringProperty("useReactiveLimits", Boolean.toString(useReactiveLimits)); + moduleConfig.setStringProperty("phaseShifterRegulationOn", Boolean.toString(phaseShifterRegulationOn)); + moduleConfig.setStringProperty("twtSplitShuntAdmittance", Boolean.toString(twtSplitShuntAdmittance)); + moduleConfig.setStringProperty("shuntCompensatorVoltageControlOn", Boolean.toString(shuntCompensatorVoltageControlOn)); + moduleConfig.setStringProperty("readSlackBus", Boolean.toString(readSlackBus)); + moduleConfig.setStringProperty("writeSlackBus", Boolean.toString(writeSlackBus)); + moduleConfig.setStringProperty("dc", Boolean.toString(dc)); + moduleConfig.setStringProperty("distributedSlack", Boolean.toString(distributedSlack)); + moduleConfig.setStringProperty("balanceType", balanceType.name()); + moduleConfig.setStringProperty("dcUseTransformerRatio", Boolean.toString(dcUseTransformerRatio)); + moduleConfig.setStringListProperty("countriesToBalance", countriesToBalance.stream().map(Enum::name).toList()); + moduleConfig.setStringProperty("connectedComponentMode", connectedComponentMode.name()); + moduleConfig.setStringProperty("hvdcAcEmulation", Boolean.toString(hvdcAcEmulation)); + moduleConfig.setStringProperty("dcPowerFactor", Double.toString(dcPowerFactor)); + + LoadFlowParameters parameters = new LoadFlowParameters(); + LoadFlowParameters.load(parameters, platformConfig); + checkValues(parameters, voltageInitMode, transformerVoltageControlOn, useReactiveLimits, phaseShifterRegulationOn, + twtSplitShuntAdmittance, shuntCompensatorVoltageControlOn, readSlackBus, writeSlackBus, dc, distributedSlack, + balanceType, dcUseTransformerRatio, countriesToBalance, connectedComponentMode, hvdcAcEmulation, dcPowerFactor); + } + + @Test + void checkSecondaryNamesNonDefault() { + LoadFlowParameters.VoltageInitMode voltageInitMode = LoadFlowParameters.VoltageInitMode.DC_VALUES; + boolean transformerVoltageControlOn = true; + boolean useReactiveLimits = false; + boolean phaseShifterRegulationOn = true; + boolean twtSplitShuntAdmittance = true; + boolean shuntCompensatorVoltageControlOn = true; + boolean readSlackBus = false; + boolean writeSlackBus = false; + boolean dc = true; + boolean distributedSlack = false; + LoadFlowParameters.BalanceType balanceType = LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD; + boolean dcUseTransformerRatio = false; + Set countriesToBalance = Set.of(Country.FR); + LoadFlowParameters.ConnectedComponentMode connectedComponentMode = LoadFlowParameters.ConnectedComponentMode.ALL; + boolean hvdcAcEmulation = false; + double dcPowerFactor = 0.95; + + MapModuleConfig moduleConfig = platformConfig.createModuleConfig("load-flow-default-parameters"); + moduleConfig.setStringProperty("voltageInitMode", voltageInitMode.name()); + moduleConfig.setStringProperty("transformerVoltageControlOn", Boolean.toString(transformerVoltageControlOn)); + // useReactiveLimits can be replaced by noGeneratorReactiveLimits (inverted) + moduleConfig.setStringProperty("noGeneratorReactiveLimits", Boolean.toString(!useReactiveLimits)); + moduleConfig.setStringProperty("phaseShifterRegulationOn", Boolean.toString(phaseShifterRegulationOn)); + // twtSplitShuntAdmittance can be replaced by specificCompatibility + moduleConfig.setStringProperty("specificCompatibility", Boolean.toString(twtSplitShuntAdmittance)); + // shuntCompensatorVoltageControlOn can be replaced by simulShunt + moduleConfig.setStringProperty("simulShunt", Boolean.toString(shuntCompensatorVoltageControlOn)); + moduleConfig.setStringProperty("readSlackBus", Boolean.toString(readSlackBus)); + moduleConfig.setStringProperty("writeSlackBus", Boolean.toString(writeSlackBus)); + moduleConfig.setStringProperty("dc", Boolean.toString(dc)); + moduleConfig.setStringProperty("distributedSlack", Boolean.toString(distributedSlack)); + moduleConfig.setStringProperty("balanceType", balanceType.name()); + moduleConfig.setStringProperty("dcUseTransformerRatio", Boolean.toString(dcUseTransformerRatio)); + moduleConfig.setStringListProperty("countriesToBalance", countriesToBalance.stream().map(Enum::name).toList()); + moduleConfig.setStringProperty("connectedComponentMode", connectedComponentMode.name()); + moduleConfig.setStringProperty("hvdcAcEmulation", Boolean.toString(hvdcAcEmulation)); + moduleConfig.setStringProperty("dcPowerFactor", Double.toString(dcPowerFactor)); + + LoadFlowParameters parameters = new LoadFlowParameters(); + LoadFlowParameters.load(parameters, platformConfig); + checkValues(parameters, voltageInitMode, transformerVoltageControlOn, useReactiveLimits, phaseShifterRegulationOn, + twtSplitShuntAdmittance, shuntCompensatorVoltageControlOn, readSlackBus, writeSlackBus, dc, distributedSlack, + balanceType, dcUseTransformerRatio, countriesToBalance, connectedComponentMode, hvdcAcEmulation, dcPowerFactor); + } } diff --git a/loadflow/loadflow-results-completion/pom.xml b/loadflow/loadflow-results-completion/pom.xml index ca3fa74cab5..a5dda0b1d1d 100644 --- a/loadflow/loadflow-results-completion/pom.xml +++ b/loadflow/loadflow-results-completion/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-loadflow - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-loadflow-results-completion diff --git a/loadflow/loadflow-scripting/pom.xml b/loadflow/loadflow-scripting/pom.xml index b2daf9c30b5..1946bb27d49 100644 --- a/loadflow/loadflow-scripting/pom.xml +++ b/loadflow/loadflow-scripting/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-loadflow - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-loadflow-scripting diff --git a/loadflow/loadflow-validation/pom.xml b/loadflow/loadflow-validation/pom.xml index 56518936b72..a79c7f4a8f9 100644 --- a/loadflow/loadflow-validation/pom.xml +++ b/loadflow/loadflow-validation/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-loadflow - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-loadflow-validation diff --git a/loadflow/pom.xml b/loadflow/pom.xml index 541599c657b..5c52623c6a0 100644 --- a/loadflow/pom.xml +++ b/loadflow/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom diff --git a/math/pom.xml b/math/pom.xml index 910b671e75a..6a043eb76d4 100644 --- a/math/pom.xml +++ b/math/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-math diff --git a/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java b/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java index 077d6cf3f1d..ec39705999b 100644 --- a/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java +++ b/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java @@ -315,17 +315,21 @@ public interface UndirectedGraph { /** * Traverse the entire graph, starting at the specified vertex v. * This method relies on a {@link Traverser} instance to know if the traverse of the graph should continue or stop. - * This method throws a {@link com.powsybl.commons.PowsyblException} if the encountered table size is less than the maximum vertex index. - * At the end of the method, the encountered array contains {@literal true} for all the traversed vertices, {@literal false} otherwise. + * This method throws a {@link com.powsybl.commons.PowsyblException} if the verticesEncountered table size is less than the maximum vertex index. + * At the end of the method, the verticesEncountered array contains {@literal true} for all the traversed vertices, {@literal false} otherwise. * * @param v the vertex index where the traverse has to start. * @param traversalType the type of traversal (breadth-first or depth-first) * @param traverser the {@link Traverser} instance to use to know if the traverse should continue or stop. - * @param encountered the list of traversed vertices. + * @param verticesEncountered the list of traversed vertices - a vertex is considered as traversed: + *
    + *
  • if it is the starting vertex
  • + *
  • if one of its edges has been traversed, with a traverser result {@link TraverseResult#CONTINUE}
  • + *
* @return false if the whole traversing has to stop, meaning that a {@link TraverseResult#TERMINATE_TRAVERSER} * has been returned from the traverser, true otherwise */ - boolean traverse(int v, TraversalType traversalType, Traverser traverser, boolean[] encountered); + boolean traverse(int v, TraversalType traversalType, Traverser traverser, boolean[] verticesEncountered); /** * Traverse the entire graph, starting at the specified vertex v. diff --git a/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java b/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java index 9d504362ff5..7fd5d91f17e 100644 --- a/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java +++ b/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java @@ -507,9 +507,13 @@ private void invalidateAdjacencyList() { adjacencyListCache = null; } - private static void traverseVertex(int v, boolean[] encountered, Deque edgesToTraverse, TIntArrayList[] adjacencyList, TraversalType traversalType) { - encountered[v] = true; - TIntArrayList adjacentEdges = adjacencyList[v]; + private void traverseVertex(int vToTraverse, boolean[] vEncountered, Deque edgesToTraverse, + TIntArrayList[] adjacencyList, TraversalType traversalType) { + if (vEncountered[vToTraverse]) { + return; + } + vEncountered[vToTraverse] = true; + TIntArrayList adjacentEdges = adjacencyList[vToTraverse]; for (int i = 0; i < adjacentEdges.size(); i++) { // For depth-first traversal, we're going to poll the last element added in the deque. Hence, edges have to // be added in reverse order, otherwise the depth-first traversal will be "on the right side" instead of @@ -518,41 +522,53 @@ private static void traverseVertex(int v, boolean[] encountered, Deque case DEPTH_FIRST -> adjacentEdges.size() - i - 1; case BREADTH_FIRST -> i; }; - edgesToTraverse.add(adjacentEdges.getQuick(iEdge)); + + int adjacentEdgeIndex = adjacentEdges.getQuick(iEdge); + boolean flippedEdge = edges.get(adjacentEdgeIndex).v1 != vToTraverse; + edgesToTraverse.add(new DirectedEdge(adjacentEdgeIndex, flippedEdge)); } } + /** + * Record to store which edge has to be traversed and in which direction + * @param index index of the edge within the edges list + * @param flippedDirection if true, edge.getNode2() has already been visited, otherwise it's edge.getNode1() + */ + private record DirectedEdge(int index, boolean flippedDirection) { + } + @Override - public boolean traverse(int v, TraversalType traversalType, Traverser traverser, boolean[] encountered) { + public boolean traverse(int v, TraversalType traversalType, Traverser traverser, boolean[] encounteredVertices) { checkVertex(v); Objects.requireNonNull(traverser); - Objects.requireNonNull(encountered); + Objects.requireNonNull(encounteredVertices); - if (encountered.length < vertices.size()) { + if (encounteredVertices.length < vertices.size()) { throw new PowsyblException("Encountered array is too small"); } + boolean[] encounteredEdges = new boolean[edges.size()]; TIntArrayList[] adjacencyList = getAdjacencyList(); boolean keepGoing = true; - Deque edgesToTraverse = new ArrayDeque<>(); - traverseVertex(v, encountered, edgesToTraverse, adjacencyList, traversalType); + Deque edgesToTraverse = new ArrayDeque<>(); + traverseVertex(v, encounteredVertices, edgesToTraverse, adjacencyList, traversalType); while (!edgesToTraverse.isEmpty() && keepGoing) { - int e = switch (traversalType) { + DirectedEdge directedEdge = switch (traversalType) { case DEPTH_FIRST -> edgesToTraverse.pollLast(); case BREADTH_FIRST -> edgesToTraverse.pollFirst(); }; - Edge edge = edges.get(e); - if (!encountered[edge.getV1()] || !encountered[edge.getV2()]) { - // This means the edge hasn't been traversed yet. - // Nonetheless, by doing so we're missing the edges parallel to an edge already traversed. - boolean flipEdge = encountered[edge.getV2()]; - int vOrigin = flipEdge ? edge.getV2() : edge.getV1(); - int vDest = flipEdge ? edge.getV1() : edge.getV2(); - TraverseResult traverserResult = traverser.traverse(vOrigin, e, vDest); + if (!encounteredEdges[directedEdge.index]) { + encounteredEdges[directedEdge.index] = true; + + Edge edge = edges.get(directedEdge.index); + int vOrigin = directedEdge.flippedDirection ? edge.getV2() : edge.getV1(); + int vDest = directedEdge.flippedDirection ? edge.getV1() : edge.getV2(); + + TraverseResult traverserResult = traverser.traverse(vOrigin, directedEdge.index, vDest); switch (traverserResult) { - case CONTINUE -> traverseVertex(vDest, encountered, edgesToTraverse, adjacencyList, traversalType); + case CONTINUE -> traverseVertex(vDest, encounteredVertices, edgesToTraverse, adjacencyList, traversalType); case TERMINATE_TRAVERSER -> keepGoing = false; // the whole traversing needs to stop case TERMINATE_PATH -> { // Path ends on edge e before reaching vDest, continuing with next edge in the deque diff --git a/math/src/main/java/com/powsybl/math/matrix/DenseMatrix.java b/math/src/main/java/com/powsybl/math/matrix/DenseMatrix.java index 3d5d0a154a0..5878b28b293 100644 --- a/math/src/main/java/com/powsybl/math/matrix/DenseMatrix.java +++ b/math/src/main/java/com/powsybl/math/matrix/DenseMatrix.java @@ -29,6 +29,8 @@ public class DenseMatrix extends AbstractMatrix { public static final int MAX_ELEMENT_COUNT = Integer.MAX_VALUE / Double.BYTES; + public static final DenseMatrix EMPTY = new DenseMatrix(0, 0); + /** * Dense element implementation. * An element in a dense matrix is defined by its row index and column index. @@ -361,6 +363,56 @@ public DenseMatrix transpose() { return new DenseMatrix(transposedRowCount, transposedColumnCount, () -> transposedBuffer); } + /** + * Copy all the values that are in an originalMatrix and paste it in the current DenseMatrix (without allocating new memory spaces) + * The dimensions of both matrices must be the same + */ + public void copyValuesFrom(DenseMatrix originalMatrix) { + if (originalMatrix.getRowCount() == getRowCount() && originalMatrix.getColumnCount() == getColumnCount()) { + for (int columnIndex = 0; columnIndex < originalMatrix.getColumnCount(); columnIndex++) { + for (int rowIndex = 0; rowIndex < originalMatrix.getRowCount(); rowIndex++) { + set(rowIndex, columnIndex, originalMatrix.get(rowIndex, columnIndex)); + } + } + } else { + throw new MatrixException("Incompatible matrix dimensions when copying values. Received (" + originalMatrix.getRowCount() + ", " + originalMatrix.getColumnCount() + ") but expected (" + getRowCount() + ", " + getColumnCount() + ")"); + } + } + + public void resetRow(int row) { + if (row >= 0 && row < getRowCount()) { + for (int j = 0; j < getColumnCount(); j++) { + set(row, j, 0); + } + } else { + throw new IllegalArgumentException("Row value out of bounds. Expected in range [0," + (getRowCount() - 1) + "] but received " + row); + } + } + + public void resetColumn(int column) { + if (column >= 0 && column < getColumnCount()) { + for (int i = 0; i < getRowCount(); i++) { + set(i, column, 0); + } + } else { + throw new IllegalArgumentException("Column value out of bounds. Expected in range [0," + (getColumnCount() - 1) + "] but received " + column); + } + } + + public void removeSmallValues(double epsilonValue) { + if (epsilonValue >= 0) { + for (int i = 0; i < getRowCount(); i++) { + for (int j = 0; j < getColumnCount(); j++) { + if (Math.abs(get(i, j)) < epsilonValue) { + set(i, j, 0.); + } + } + } + } else { + throw new IllegalArgumentException("Argument epsilonValue should be positive but received " + epsilonValue); + } + } + @Override public void print(PrintStream out) { print(out, null, null); diff --git a/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java b/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java index 998534feb17..0ee1284f4ad 100644 --- a/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java +++ b/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java @@ -10,14 +10,13 @@ import com.powsybl.commons.PowsyblException; import gnu.trove.list.array.TIntArrayList; import org.junit.jupiter.api.AfterEach; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.PrintStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -281,6 +280,7 @@ void testGetEdgeObjects() { } /** + *
      *           0
      *           |
      *         ---------
@@ -307,7 +307,7 @@ void testGetEdgeObjects() {
      *  all paths (edge numbers) between vertex 0 and 5:
      *  0, 3, 5
      *  1, 4, 5
-     *  2, 6
+     *  2, 6
*/ @Test void testFindAllPaths() { @@ -327,12 +327,13 @@ void testFindAllPaths() { graph.addEdge(3, 5, null); // 6 List paths = graph.findAllPaths(0, vertex -> vertex != null && "end".equals(vertex.name), null); assertEquals(3, paths.size()); - assertArrayEquals(paths.get(0).toArray(), new int[] {2, 6}); - assertArrayEquals(paths.get(1).toArray(), new int[] {0, 3, 5}); - assertArrayEquals(paths.get(2).toArray(), new int[] {1, 4, 5}); + assertArrayEquals(new int[] {2, 6}, paths.get(0).toArray()); + assertArrayEquals(new int[] {0, 3, 5}, paths.get(1).toArray()); + assertArrayEquals(new int[] {1, 4, 5}, paths.get(2).toArray()); } /** + *
      *           0
      *           |
      *         ---------
@@ -359,10 +360,10 @@ void testFindAllPaths() {
      *  all paths (edge numbers) between vertex 0 and 5:
      *  0, 3, 5
      *  1, 4, 5
-     *  2, 6
+     *  2, 6
*/ @Test - void testPrintGraph() throws IOException { + void testPrintGraph() { graph.addVertex(); graph.addVertex(); graph.addVertex(); @@ -458,7 +459,7 @@ void testRemoveListener() { * ------- * | * 5 - * + * * edges: * 0 <-> 1 : 0 * 0 <-> 2 : 1 @@ -471,7 +472,7 @@ void testRemoveListener() { * all paths (edge numbers) between vertex 0 and 5: * 0, 3, 5 * 1, 4, 5 - * 2, 6 + * 2, 6 */ @Test void testTraverse() { @@ -496,30 +497,117 @@ void testTraverse() { Mockito.when(traverser.traverse(4, 3, 1)).thenReturn(TraverseResult.TERMINATE_PATH); Mockito.when(traverser.traverse(4, 4, 2)).thenReturn(TraverseResult.TERMINATE_PATH); Mockito.when(traverser.traverse(5, 6, 3)).thenReturn(TraverseResult.TERMINATE_PATH); - boolean[] encountered = new boolean[graph.getVertexCount()]; - Arrays.fill(encountered, false); - graph.traverse(5, TraversalType.DEPTH_FIRST, traverser, encountered); + boolean[] vEncountered = new boolean[graph.getVertexCount()]; + graph.traverse(5, TraversalType.DEPTH_FIRST, traverser, vEncountered); // Only vertex 4 and 5 encountered - assertArrayEquals(new boolean[] {false, false, false, false, true, true}, encountered); + assertArrayEquals(new boolean[] {false, false, false, false, true, true}, vEncountered); - Arrays.fill(encountered, false); + Arrays.fill(vEncountered, false); Traverser traverser2 = (v1, e, v2) -> { - encountered[v1] = true; + vEncountered[v1] = true; return v2 == 1 || v2 == 2 || v2 == 3 ? TraverseResult.TERMINATE_PATH : TraverseResult.CONTINUE; }; graph.traverse(4, TraversalType.DEPTH_FIRST, traverser2); // Only vertex 4 and 5 encountered - assertArrayEquals(new boolean[] {false, false, false, false, true, true}, encountered); + assertArrayEquals(new boolean[] {false, false, false, false, true, true}, vEncountered); - Arrays.fill(encountered, false); - Traverser traverser3 = (v1, e, v2) -> { - return v2 == 0 ? TraverseResult.TERMINATE_TRAVERSER : TraverseResult.CONTINUE; - }; + Arrays.fill(vEncountered, false); + Traverser traverser3 = (v1, e, v2) -> v2 == 0 ? TraverseResult.TERMINATE_TRAVERSER : TraverseResult.CONTINUE; - graph.traverse(5, TraversalType.DEPTH_FIRST, traverser3, encountered); + graph.traverse(5, TraversalType.DEPTH_FIRST, traverser3, vEncountered); // Only vertices on first path encountering 0 are encountered - assertArrayEquals(new boolean[] {false, true, false, false, true, true}, encountered); + assertArrayEquals(new boolean[] {false, true, false, false, true, true}, vEncountered); + + Arrays.fill(vEncountered, false); + boolean[] eEncountered = new boolean[graph.getEdgeCount()]; + Traverser traverser4 = (v1, e, v2) -> { + eEncountered[e] = true; + return TraverseResult.CONTINUE; + }; + graph.traverse(5, TraversalType.DEPTH_FIRST, traverser4, vEncountered); + // All vertices and edges are encountered + assertArrayEquals(new boolean[] {true, true, true, true, true, true}, vEncountered); + assertArrayEquals(new boolean[] {true, true, true, true, true, true, true}, eEncountered); + + List breadthFirstexpected = List.of( + new GraphPath(5, 5, 4), + new GraphPath(5, 6, 3), + new GraphPath(4, 3, 1), + new GraphPath(4, 4, 2), + new GraphPath(3, 2, 0), + new GraphPath(1, 0, 0), + new GraphPath(2, 1, 0)); + List depthFirstExpected = List.of( + new GraphPath(5, 5, 4), + new GraphPath(4, 3, 1), + new GraphPath(1, 0, 0), + new GraphPath(0, 1, 2), + new GraphPath(2, 4, 4), + new GraphPath(0, 2, 3), + new GraphPath(3, 6, 5)); + + // Check that all edges and vertices are traversed in the right order when traversing the graph with no stopping point + List pathsBf = new ArrayList<>(); + List pathsDf = new ArrayList<>(); + graph.traverse(5, TraversalType.BREADTH_FIRST, (v1, e, v2) -> { + pathsBf.add(new GraphPath(v1, e, v2)); + return TraverseResult.CONTINUE; + }); + graph.traverse(5, TraversalType.DEPTH_FIRST, (v1, e, v2) -> { + pathsDf.add(new GraphPath(v1, e, v2)); + return TraverseResult.CONTINUE; + }); + assertEquals(breadthFirstexpected, pathsBf); + assertEquals(depthFirstExpected, pathsDf); + + // Check all calls done when traversing the graph with one stopping point at vertex 0 when arriving from 3 + // to ensure the edge 0 and 1 still get traversed even if the destination vertex 0 is already encountered + List pathsWithStoppingPoint = new ArrayList<>(); + graph.traverse(5, TraversalType.BREADTH_FIRST, (v1, e, v2) -> { + pathsWithStoppingPoint.add(new GraphPath(v1, e, v2)); + return v1 == 3 && v2 == 0 ? TraverseResult.TERMINATE_PATH : TraverseResult.CONTINUE; + }); + assertEquals(breadthFirstexpected, pathsWithStoppingPoint); + } + + /** + *
+     *           0
+     *           |
+     *         -----
+     *         |   |
+     *         -----
+     *           |
+     *           1
+     *           |
+     *           2
+     *
+     *  edges:
+     *  0 <-> 1 : 0
+     *  0 <-> 1 : 1
+     *  1 <-> 2 : 2
+ */ + @Test + void testTraverseParallelEdges() { + graph.addVertex(); + graph.addVertex(); + graph.addVertex(); + graph.addEdge(0, 1, null); // 0 + graph.addEdge(1, 0, null); // 1 + graph.addEdge(1, 2, null); // 2 + + List paths = new ArrayList<>(); + graph.traverse(0, TraversalType.BREADTH_FIRST, (v1, e, v2) -> { + paths.add(new GraphPath(v1, e, v2)); + return TraverseResult.CONTINUE; + }); + + List expected = List.of( + new GraphPath(0, 0, 1), + new GraphPath(0, 1, 1), + new GraphPath(1, 2, 2)); + assertEquals(expected, paths); } @Test @@ -574,4 +662,7 @@ void removeIsolatedVertices() { graph.removeIsolatedVertices(); assertEquals(2, graph.getVertexCount()); } + + private record GraphPath(int v1, int e, int v2) { + } } diff --git a/math/src/test/java/com/powsybl/math/matrix/DenseMatrixTest.java b/math/src/test/java/com/powsybl/math/matrix/DenseMatrixTest.java index 8263daf6fe8..0c86ea8b459 100644 --- a/math/src/test/java/com/powsybl/math/matrix/DenseMatrixTest.java +++ b/math/src/test/java/com/powsybl/math/matrix/DenseMatrixTest.java @@ -115,4 +115,61 @@ void testTooManyElementDenseMatrix() { assertEquals("Too many elements for a dense matrix, maximum allowed is 268435455", e.getMessage()); assertEquals(268435455, DenseMatrix.MAX_ELEMENT_COUNT); } + + @Test + void testCopyValuesMatrix() { + DenseMatrix a = new DenseMatrix(2, 1); + a.set(0, 0, 4); + a.set(1, 0, 5); + DenseMatrix b = new DenseMatrix(2, 1); + b.copyValuesFrom(a); + assertEquals(4, b.get(0, 0), EPSILON); + assertEquals(5, b.get(1, 0), EPSILON); + DenseMatrix c = new DenseMatrix(3, 1); + MatrixException e = assertThrows(MatrixException.class, () -> c.copyValuesFrom(a)); + assertEquals("Incompatible matrix dimensions when copying values. Received (2, 1) but expected (3, 1)", e.getMessage()); + } + + @Test + void testResetRow() { + DenseMatrix a = (DenseMatrix) createA(matrixFactory); + a.resetRow(0); + assertEquals(0d, a.get(0, 0), EPSILON); + assertEquals(0d, a.get(1, 0), EPSILON); + assertEquals(2d, a.get(2, 0), EPSILON); + assertEquals(0d, a.get(0, 1), EPSILON); + assertEquals(3d, a.get(1, 1), EPSILON); + assertEquals(0d, a.get(2, 1), EPSILON); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> a.resetRow(3)); + assertEquals("Row value out of bounds. Expected in range [0,2] but received 3", e.getMessage()); + } + + @Test + void testResetColumn() { + DenseMatrix a = (DenseMatrix) createA(matrixFactory); + a.resetColumn(1); + assertEquals(1d, a.get(0, 0), EPSILON); + assertEquals(0d, a.get(1, 0), EPSILON); + assertEquals(2d, a.get(2, 0), EPSILON); + assertEquals(0d, a.get(0, 1), EPSILON); + assertEquals(0d, a.get(1, 1), EPSILON); + assertEquals(0d, a.get(2, 1), EPSILON); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> a.resetColumn(3)); + assertEquals("Column value out of bounds. Expected in range [0,1] but received 3", e.getMessage()); + } + + @Test + void testRemoveSmallValues() { + DenseMatrix a = (DenseMatrix) createA(matrixFactory); + a.removeSmallValues(2d); + assertEquals(0d, a.get(0, 0), EPSILON); + assertEquals(0d, a.get(1, 0), EPSILON); + assertEquals(2d, a.get(2, 0), EPSILON); + assertEquals(0d, a.get(0, 1), EPSILON); + assertEquals(3d, a.get(1, 1), EPSILON); + assertEquals(0d, a.get(2, 1), EPSILON); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> a.removeSmallValues(-1)); + assertEquals("Argument epsilonValue should be positive but received -1.0", e.getMessage()); + } + } diff --git a/matpower/matpower-converter/pom.xml b/matpower/matpower-converter/pom.xml index 7e9d1d1c152..9203916b76b 100644 --- a/matpower/matpower-converter/pom.xml +++ b/matpower/matpower-converter/pom.xml @@ -13,7 +13,7 @@ powsybl-matpower com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-matpower-converter diff --git a/matpower/matpower-model/pom.xml b/matpower/matpower-model/pom.xml index 028318038d9..060995b65f2 100644 --- a/matpower/matpower-model/pom.xml +++ b/matpower/matpower-model/pom.xml @@ -13,7 +13,7 @@ com.powsybl powsybl-matpower - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-matpower-model diff --git a/matpower/pom.xml b/matpower/pom.xml index e7b915e04a0..e15bb9debd3 100644 --- a/matpower/pom.xml +++ b/matpower/pom.xml @@ -13,7 +13,7 @@ powsybl-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom diff --git a/pom.xml b/pom.xml index 5ed3b8d8695..4ab8339e7cb 100644 --- a/pom.xml +++ b/pom.xml @@ -15,12 +15,12 @@ com.powsybl powsybl-parent - 18 + 20 powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom powsybl diff --git a/powerfactory/pom.xml b/powerfactory/pom.xml index 687a4dea97f..8bf4afa5951 100644 --- a/powerfactory/pom.xml +++ b/powerfactory/pom.xml @@ -13,7 +13,7 @@ powsybl-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom diff --git a/powerfactory/powerfactory-converter/pom.xml b/powerfactory/powerfactory-converter/pom.xml index 9652fb61d07..82beaeb9f06 100644 --- a/powerfactory/powerfactory-converter/pom.xml +++ b/powerfactory/powerfactory-converter/pom.xml @@ -13,7 +13,7 @@ powsybl-powerfactory com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-powerfactory-converter diff --git a/powerfactory/powerfactory-db/pom.xml b/powerfactory/powerfactory-db/pom.xml index 3ca54a36fbb..69f57d71f10 100644 --- a/powerfactory/powerfactory-db/pom.xml +++ b/powerfactory/powerfactory-db/pom.xml @@ -13,7 +13,7 @@ powsybl-powerfactory com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-powerfactory-db diff --git a/powerfactory/powerfactory-dgs/pom.xml b/powerfactory/powerfactory-dgs/pom.xml index 1e0e23b4cef..1ebe3ecd5a3 100644 --- a/powerfactory/powerfactory-dgs/pom.xml +++ b/powerfactory/powerfactory-dgs/pom.xml @@ -13,7 +13,7 @@ powsybl-powerfactory com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-powerfactory-dgs diff --git a/powerfactory/powerfactory-model/pom.xml b/powerfactory/powerfactory-model/pom.xml index c2cb2651109..2cabf48ddd8 100644 --- a/powerfactory/powerfactory-model/pom.xml +++ b/powerfactory/powerfactory-model/pom.xml @@ -13,7 +13,7 @@ powsybl-powerfactory com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-powerfactory-model diff --git a/psse/pom.xml b/psse/pom.xml index 806b31e276c..3d772660a83 100644 --- a/psse/pom.xml +++ b/psse/pom.xml @@ -13,7 +13,7 @@ powsybl-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom diff --git a/psse/psse-converter/pom.xml b/psse/psse-converter/pom.xml index 00a92eaf12f..e1c1469cb8b 100644 --- a/psse/psse-converter/pom.xml +++ b/psse/psse-converter/pom.xml @@ -11,7 +11,7 @@ powsybl-psse com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT 4.0.0 diff --git a/psse/psse-model-test/pom.xml b/psse/psse-model-test/pom.xml index 5e2ecf3f4a9..9c0ef1336a2 100644 --- a/psse/psse-model-test/pom.xml +++ b/psse/psse-model-test/pom.xml @@ -13,7 +13,7 @@ powsybl-psse com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-psse-model-test diff --git a/psse/psse-model/pom.xml b/psse/psse-model/pom.xml index 027615c30f9..5e8438393a3 100644 --- a/psse/psse-model/pom.xml +++ b/psse/psse-model/pom.xml @@ -13,7 +13,7 @@ powsybl-psse com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-psse-model diff --git a/scripting-test/pom.xml b/scripting-test/pom.xml index 6a644f928f3..0cbddea5c0b 100644 --- a/scripting-test/pom.xml +++ b/scripting-test/pom.xml @@ -15,7 +15,7 @@ powsybl-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-scripting-test diff --git a/scripting/pom.xml b/scripting/pom.xml index 86f15bcccd0..990789bca32 100644 --- a/scripting/pom.xml +++ b/scripting/pom.xml @@ -15,7 +15,7 @@ powsybl-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-scripting diff --git a/security-analysis/pom.xml b/security-analysis/pom.xml index a99944d9892..27fc13cfe84 100644 --- a/security-analysis/pom.xml +++ b/security-analysis/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom diff --git a/security-analysis/security-analysis-api/pom.xml b/security-analysis/security-analysis-api/pom.xml index bc9f154eaaf..6757c739c60 100644 --- a/security-analysis/security-analysis-api/pom.xml +++ b/security-analysis/security-analysis-api/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-security-analysis - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-security-analysis-api diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java index 63189bc0eff..9375c5af722 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java @@ -7,13 +7,13 @@ */ package com.powsybl.security.extensions; -import com.powsybl.commons.extensions.Extension; +import com.powsybl.commons.extensions.AbstractExtension; import com.powsybl.security.LimitViolation; /** * @author Mathieu Bague {@literal } */ -public class ActivePowerExtension implements Extension { +public class ActivePowerExtension extends AbstractExtension { private LimitViolation limitViolation; diff --git a/security-analysis/security-analysis-default/pom.xml b/security-analysis/security-analysis-default/pom.xml index 2178fd13b8b..ac01918f9ad 100644 --- a/security-analysis/security-analysis-default/pom.xml +++ b/security-analysis/security-analysis-default/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-security-analysis - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-security-analysis-default diff --git a/sensitivity-analysis-api/pom.xml b/sensitivity-analysis-api/pom.xml index 072d0c25309..c12c42c926a 100644 --- a/sensitivity-analysis-api/pom.xml +++ b/sensitivity-analysis-api/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-sensitivity-analysis-api diff --git a/shortcircuit-api/pom.xml b/shortcircuit-api/pom.xml index ee1bd53251c..c09710b2752 100644 --- a/shortcircuit-api/pom.xml +++ b/shortcircuit-api/pom.xml @@ -15,7 +15,7 @@ powsybl-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-shortcircuit-api diff --git a/time-series/pom.xml b/time-series/pom.xml index 79757df30f9..554557ed197 100644 --- a/time-series/pom.xml +++ b/time-series/pom.xml @@ -15,7 +15,7 @@ powsybl-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom diff --git a/time-series/time-series-api/pom.xml b/time-series/time-series-api/pom.xml index 7311622c792..722d3d6cf03 100644 --- a/time-series/time-series-api/pom.xml +++ b/time-series/time-series-api/pom.xml @@ -15,7 +15,7 @@ powsybl-time-series com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-time-series-api diff --git a/time-series/time-series-api/src/main/java/com/powsybl/timeseries/IrregularTimeSeriesIndex.java b/time-series/time-series-api/src/main/java/com/powsybl/timeseries/IrregularTimeSeriesIndex.java index 09b4dbaf806..9a2fef773f2 100644 --- a/time-series/time-series-api/src/main/java/com/powsybl/timeseries/IrregularTimeSeriesIndex.java +++ b/time-series/time-series-api/src/main/java/com/powsybl/timeseries/IrregularTimeSeriesIndex.java @@ -15,10 +15,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.time.Instant; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.stream.Stream; /** @@ -32,6 +29,11 @@ public class IrregularTimeSeriesIndex extends AbstractTimeSeriesIndex { public IrregularTimeSeriesIndex(long[] times) { this.times = Objects.requireNonNull(times); + for (int i = 1; i < times.length; i++) { + if (times[i] <= times[i - 1]) { + throw new IllegalArgumentException("Time list should be sorted and without duplicate values"); + } + } if (times.length == 0) { throw new IllegalArgumentException("Empty time list"); } diff --git a/time-series/time-series-api/src/main/java/com/powsybl/timeseries/TimeSeries.java b/time-series/time-series-api/src/main/java/com/powsybl/timeseries/TimeSeries.java index 458f8fca2ea..3ca3f1d4d4d 100644 --- a/time-series/time-series-api/src/main/java/com/powsybl/timeseries/TimeSeries.java +++ b/time-series/time-series-api/src/main/java/com/powsybl/timeseries/TimeSeries.java @@ -291,27 +291,25 @@ void parseToken(int i, String token) { } void parseLine(String[] tokens) { - for (int i = fixedColumns; i < tokens.length; i++) { - String token = tokens[i] != null ? tokens[i].trim() : ""; - parseToken(i, token); + long time = parseTokenTime(tokens[0]); + if (times.isEmpty() || times.get(times.size() - 1) != time) { + for (int i = fixedColumns; i < tokens.length; i++) { + String token = tokens[i] != null ? tokens[i].trim() : ""; + parseToken(i, token); + } + times.add(time); + } else { + LOGGER.warn("Row with the same time have already been read, the row will be skipped"); } - - parseTokenTime(tokens); } - void parseTokenTime(String[] tokens) { + long parseTokenTime(String token) { TimeFormat timeFormat = timeSeriesCsvConfig.timeFormat(); - switch (timeFormat) { - case DATE_TIME -> times.add(ZonedDateTime.parse(tokens[0]).toInstant().toEpochMilli()); - case FRACTIONS_OF_SECOND -> { - double time = Double.parseDouble(tokens[0]) * 1000; - times.add((long) time); - } - case MILLIS -> { - double millis = Double.parseDouble(tokens[0]); - times.add((long) millis); - } - } + return switch (timeFormat) { + case DATE_TIME -> ZonedDateTime.parse(token).toInstant().toEpochMilli(); + case FRACTIONS_OF_SECOND -> (long) (Double.parseDouble(token) * 1000); + case MILLIS -> (long) Double.parseDouble(token); + }; } void reInit() { diff --git a/time-series/time-series-api/src/test/java/com/powsybl/timeseries/IrregularTimeSeriesIndexTest.java b/time-series/time-series-api/src/test/java/com/powsybl/timeseries/IrregularTimeSeriesIndexTest.java index c4856e47ae1..24a111e806d 100644 --- a/time-series/time-series-api/src/test/java/com/powsybl/timeseries/IrregularTimeSeriesIndexTest.java +++ b/time-series/time-series-api/src/test/java/com/powsybl/timeseries/IrregularTimeSeriesIndexTest.java @@ -65,5 +65,9 @@ void testEquals() { @Test void testContructorError() { assertThrows(IllegalArgumentException.class, IrregularTimeSeriesIndex::create); + long[] duplicates = {0L, 1L, 1L}; + assertThrows(IllegalArgumentException.class, () -> new IrregularTimeSeriesIndex(duplicates)); + long[] unordered = {0L, 2L, 1L}; + assertThrows(IllegalArgumentException.class, () -> new IrregularTimeSeriesIndex(unordered)); } } diff --git a/time-series/time-series-api/src/test/java/com/powsybl/timeseries/TimeSeriesTest.java b/time-series/time-series-api/src/test/java/com/powsybl/timeseries/TimeSeriesTest.java index 45cae27df22..78871f548eb 100644 --- a/time-series/time-series-api/src/test/java/com/powsybl/timeseries/TimeSeriesTest.java +++ b/time-series/time-series-api/src/test/java/com/powsybl/timeseries/TimeSeriesTest.java @@ -61,23 +61,25 @@ private void assertOnParsedTimeSeries(Map> timeSeriesP @Test void testRegularTimeSeriesIndex() { - String csv = String.join(System.lineSeparator(), - "Time;Version;ts1;ts2", - "1970-01-01T01:00:00.000+01:00;1;1.0;", - "1970-01-01T02:00:00.000+01:00;1;;a", - "1970-01-01T03:00:00.000+01:00;1;3.0;b", - "1970-01-01T01:00:00.000+01:00;2;4.0;c", - "1970-01-01T02:00:00.000+01:00;2;5.0;", - "1970-01-01T03:00:00.000+01:00;2;6.0;d") + System.lineSeparator(); - - String csvWithQuotes = String.join(System.lineSeparator(), - "\"Time\";\"Version\";\"ts1\";\"ts2\"", - "\"1970-01-01T01:00:00.000+01:00\";\"1\";\"1.0\";", - "\"1970-01-01T02:00:00.000+01:00\";\"1\";;\"a\"", - "\"1970-01-01T03:00:00.000+01:00\";\"1\";\"3.0\";\"b\"", - "\"1970-01-01T01:00:00.000+01:00\";\"2\";\"4.0\";\"c\"", - "\"1970-01-01T02:00:00.000+01:00\";\"2\";\"5.0\";", - "\"1970-01-01T03:00:00.000+01:00\";\"2\";\"6.0\";\"d\"") + System.lineSeparator(); + String csv = """ + Time;Version;ts1;ts2 + 1970-01-01T01:00:00.000+01:00;1;1.0; + 1970-01-01T02:00:00.000+01:00;1;;a + 1970-01-01T03:00:00.000+01:00;1;3.0;b + 1970-01-01T01:00:00.000+01:00;2;4.0;c + 1970-01-01T02:00:00.000+01:00;2;5.0; + 1970-01-01T03:00:00.000+01:00;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); + + String csvWithQuotes = """ + "Time";"Version";"ts1";"ts2" + "1970-01-01T01:00:00.000+01:00";"1";"1.0"; + "1970-01-01T02:00:00.000+01:00";"1";;"a" + "1970-01-01T03:00:00.000+01:00";"1";"3.0";"b" + "1970-01-01T01:00:00.000+01:00";"2";"4.0";"c" + "1970-01-01T02:00:00.000+01:00";"2";"5.0"; + "1970-01-01T03:00:00.000+01:00";"2";"6.0";"d" + """.replaceAll("\n", System.lineSeparator()); Arrays.asList(csv, csvWithQuotes).forEach(data -> { Map> timeSeriesPerVersion = TimeSeries.parseCsv(data); @@ -88,14 +90,15 @@ void testRegularTimeSeriesIndex() { @Test void testIrregularTimeSeriesIndex() { - String csv = String.join(System.lineSeparator(), - "Time;Version;ts1;ts2", - "1970-01-01T01:00:00.000+01:00;1;1.0;", - "1970-01-01T02:00:00.000+01:00;1;;a", - "1970-01-01T04:00:00.000+01:00;1;3.0;b", - "1970-01-01T01:00:00.000+01:00;2;4.0;c", - "1970-01-01T02:00:00.000+01:00;2;5.0;", - "1970-01-01T04:00:00.000+01:00;2;6.0;d") + System.lineSeparator(); + String csv = """ + Time;Version;ts1;ts2 + 1970-01-01T01:00:00.000+01:00;1;1.0; + 1970-01-01T02:00:00.000+01:00;1;;a + 1970-01-01T04:00:00.000+01:00;1;3.0;b + 1970-01-01T01:00:00.000+01:00;2;4.0;c + 1970-01-01T02:00:00.000+01:00;2;5.0; + 1970-01-01T04:00:00.000+01:00;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); Map> timeSeriesPerVersion = TimeSeries.parseCsv(csv); @@ -104,14 +107,15 @@ void testIrregularTimeSeriesIndex() { @Test void testTimeSeriesNameMissing() { - String csv = String.join(System.lineSeparator(), - "Time;Version;;ts2", - "1970-01-01T01:00:00.000+01:00;1;1.0;", - "1970-01-01T02:00:00.000+01:00;1;;a", - "1970-01-01T04:00:00.000+01:00;1;3.0;b", - "1970-01-01T01:00:00.000+01:00;2;4.0;c", - "1970-01-01T02:00:00.000+01:00;2;5.0;", - "1970-01-01T04:00:00.000+01:00;2;6.0;d") + System.lineSeparator(); + String csv = """ + Time;Version;;ts2 + 1970-01-01T01:00:00.000+01:00;1;1.0; + 1970-01-01T02:00:00.000+01:00;1;;a + 1970-01-01T04:00:00.000+01:00;1;3.0;b + 1970-01-01T01:00:00.000+01:00;2;4.0;c + 1970-01-01T02:00:00.000+01:00;2;5.0; + 1970-01-01T04:00:00.000+01:00;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); Map> timeSeriesPerVersion = TimeSeries.parseCsv(csv); @@ -123,14 +127,35 @@ void testTimeSeriesNameMissing() { @Test void testFractionsOfSecondsRegularTimeSeriesIndex() { - String csv = String.join(System.lineSeparator(), - "Time;Version;ts1;ts2", - "0.000;1;1.0;", - "0.001;1;;a", - "0.002;1;3.0;b", - "0.000;2;4.0;c", - "0.001;2;5.0;", - "0.002;2;6.0;d") + System.lineSeparator(); + String csv = """ + Time;Version;ts1;ts2 + 0.000;1;1.0; + 0.001;1;;a + 0.002;1;3.0;b + 0.000;2;4.0;c + 0.001;2;5.0; + 0.002;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); + + TimeSeriesCsvConfig timeSeriesCsvConfig = new TimeSeriesCsvConfig(';', true, TimeFormat.FRACTIONS_OF_SECOND, true); + Map> timeSeriesPerVersion = TimeSeries.parseCsv(csv, timeSeriesCsvConfig); + + assertOnParsedTimeSeries(timeSeriesPerVersion, RegularTimeSeriesIndex.class); + } + + @Test + void testFractionsOfSecondsRegularTimeSeriesIndexWithDuplicateTime() { + String csv = """ + Time;Version;ts1;ts2 + 0.000;1;1.0; + 0.001;1;;a + 0.0015;1;;b + 0.002;1;3.0;b + 0.000;2;4.0;c + 0.0002;2;4.5;c + 0.001;2;5.0; + 0.002;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); TimeSeriesCsvConfig timeSeriesCsvConfig = new TimeSeriesCsvConfig(';', true, TimeFormat.FRACTIONS_OF_SECOND, true); Map> timeSeriesPerVersion = TimeSeries.parseCsv(csv, timeSeriesCsvConfig); @@ -140,14 +165,15 @@ void testFractionsOfSecondsRegularTimeSeriesIndex() { @Test void testParseCsvBuffered() { - String csv = String.join(System.lineSeparator(), - "Time;Version;ts1;ts2", - "0.000;1;1.0;", - "0.001;1;;a", - "0.002;1;3.0;b", - "0.000;2;4.0;c", - "0.001;2;5.0;", - "0.002;2;6.0;d") + System.lineSeparator(); + String csv = """ + Time;Version;ts1;ts2 + 0.000;1;1.0; + 0.001;1;;a + 0.002;1;3.0;b + 0.000;2;4.0;c + 0.001;2;5.0; + 0.002;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); TimeSeriesCsvConfig timeSeriesCsvConfig = new TimeSeriesCsvConfig(';', true, TimeFormat.FRACTIONS_OF_SECOND, true); try (BufferedReader reader = new BufferedReader(new StringReader(csv))) { @@ -160,14 +186,15 @@ void testParseCsvBuffered() { @Test void testMillisIrregularTimeSeriesIndex() { - String csv = String.join(System.lineSeparator(), - "Time;Version;ts1;ts2", - "0;1;1.0;", - "1;1;;a", - "4;1;3.0;b", - "0;2;4.0;c", - "1;2;5.0;", - "4;2;6.0;d") + System.lineSeparator(); + String csv = """ + Time;Version;ts1;ts2 + 0;1;1.0; + 1;1;;a + 4;1;3.0;b + 0;2;4.0;c + 1;2;5.0; + 4;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); TimeSeriesCsvConfig timeSeriesCsvConfig = new TimeSeriesCsvConfig(';', true, TimeFormat.MILLIS); Map> timeSeriesPerVersion = TimeSeries.parseCsv(csv, timeSeriesCsvConfig); @@ -177,14 +204,15 @@ void testMillisIrregularTimeSeriesIndex() { @Test void testNoVersion() { - String csv = String.join(System.lineSeparator(), - "Time;ts1;ts2", - "1970-01-01T01:00:00.000+01:00;1.0;", - "1970-01-01T02:00:00.000+01:00;;a", - "1970-01-01T03:00:00.000+01:00;3.0;b", - "1970-01-01T04:00:00.000+01:00;4.0;c", - "1970-01-01T05:00:00.000+01:00;5.0;", - "1970-01-01T06:00:00.000+01:00;6.0;d") + System.lineSeparator(); + String csv = """ + Time;ts1;ts2 + 1970-01-01T01:00:00.000+01:00;1.0; + 1970-01-01T02:00:00.000+01:00;;a + 1970-01-01T03:00:00.000+01:00;3.0;b + 1970-01-01T04:00:00.000+01:00;4.0;c + 1970-01-01T05:00:00.000+01:00;5.0; + 1970-01-01T06:00:00.000+01:00;6.0;d + """.replaceAll("\n", System.lineSeparator()); TimeSeriesCsvConfig timeSeriesCsvConfig = new TimeSeriesCsvConfig(';', false, TimeFormat.DATE_TIME); Map> timeSeriesPerVersion = TimeSeries.parseCsv(csv, timeSeriesCsvConfig); @@ -206,14 +234,15 @@ void testNoVersion() { @Test void testVersionedAtDefaultNumberNotStrictCSV() { - String csv = String.join(System.lineSeparator(), - "Time;Version;ts1;ts2", - "0.000;-1;1.0;", - "0.001;-1;;a", - "0.002;-1;3.0;b", - "0.000;2;4.0;c", - "0.001;2;5.0;", - "0.002;2;6.0;d") + System.lineSeparator(); + String csv = """ + Time;Version;ts1;ts2 + 0.000;-1;1.0; + 0.001;-1;;a + 0.002;-1;3.0;b + 0.000;2;4.0;c + 0.001;2;5.0; + 0.002;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); // Reporter ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("reportTestVersionedAtDefaultNumberNotStrictCSV", "Testing reportNode TimeSeriesImport with wrong version number").build(); @@ -233,14 +262,15 @@ void testVersionedAtDefaultNumberNotStrictCSV() { @Test void testVersionedAtDefaultNumberStrictCSV() { - String csv = String.join(System.lineSeparator(), - "Time;Version;ts1;ts2", - "0.000;-1;1.0;", - "0.001;-1;;a", - "0.002;-1;3.0;b", - "0.000;2;4.0;c", - "0.001;2;5.0;", - "0.002;2;6.0;d") + System.lineSeparator(); + String csv = """ + Time;Version;ts1;ts2 + 0.000;-1;1.0; + 0.001;-1;;a + 0.002;-1;3.0;b + 0.000;2;4.0;c + 0.001;2;5.0; + 0.002;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); // Reporter ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("reportTestVersionedAtDefaultNumberNotStrictCSV", "Testing reportNode TimeSeriesImport with wrong version number").build(); @@ -272,56 +302,62 @@ void testErrors() { String emptyCsv = ""; assertThatCode(() -> TimeSeries.parseCsv(emptyCsv)).hasMessage("CSV header is missing").isInstanceOf(TimeSeriesException.class); - String badHeaderNoTime = String.join(System.lineSeparator(), - "NoTime;ts1;ts2", - "1970-01-01T01:00:00.000+01:00;1.0;", - "1970-01-01T02:00:00.000+01:00;;a", - "1970-01-01T03:00:00.000+01:00;3.0;b", - "1970-01-01T04:00:00.000+01:00;4.0;c", - "1970-01-01T05:00:00.000+01:00;5.0;", - "1970-01-01T06:00:00.000+01:00;6.0;d") + System.lineSeparator(); + String badHeaderNoTime = """ + NoTime;ts1;ts2 + 1970-01-01T01:00:00.000+01:00;1.0; + 1970-01-01T02:00:00.000+01:00;;a + 1970-01-01T03:00:00.000+01:00;3.0;b + 1970-01-01T04:00:00.000+01:00;4.0;c + 1970-01-01T05:00:00.000+01:00;5.0; + 1970-01-01T06:00:00.000+01:00;6.0;d + """.replaceAll("\n", System.lineSeparator()); assertThatCode(() -> TimeSeries.parseCsv(badHeaderNoTime, timeSeriesCsvConfig)).hasMessage("Bad CSV header, should be \ntime;...").isInstanceOf(TimeSeriesException.class); - String badHeaderNoVersion = String.join(System.lineSeparator(), - "Time;NoVersion;ts1;ts2", - "1970-01-01T01:00:00.000+01:00;1;1.0;", - "1970-01-01T02:00:00.000+01:00;1;;a", - "1970-01-01T03:00:00.000+01:00;1;3.0;b", - "1970-01-01T01:00:00.000+01:00;2;4.0;c", - "1970-01-01T02:00:00.000+01:00;2;5.0;", - "1970-01-01T03:00:00.000+01:00;2;6.0;d") + System.lineSeparator(); + String badHeaderNoVersion = """ + Time;NoVersion;ts1;ts2 + 1970-01-01T01:00:00.000+01:00;1;1.0; + 1970-01-01T02:00:00.000+01:00;1;;a + 1970-01-01T03:00:00.000+01:00;1;3.0;b + 1970-01-01T01:00:00.000+01:00;2;4.0;c + 1970-01-01T02:00:00.000+01:00;2;5.0; + 1970-01-01T03:00:00.000+01:00;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); assertThatCode(() -> TimeSeries.parseCsv(badHeaderNoVersion)).hasMessage("Bad CSV header, should be \ntime;version;...").isInstanceOf(TimeSeriesException.class); - String duplicates = String.join(System.lineSeparator(), - "Time;Version;ts1;ts1", - "1970-01-01T01:00:00.000+01:00;1;1.0;", - "1970-01-01T02:00:00.000+01:00;1;;a", - "1970-01-01T03:00:00.000+01:00;1;3.0;b", - "1970-01-01T01:00:00.000+01:00;2;4.0;c", - "1970-01-01T02:00:00.000+01:00;2;5.0;", - "1970-01-01T03:00:00.000+01:00;2;6.0;d") + System.lineSeparator(); + String duplicates = """ + Time;Version;ts1;ts1 + 1970-01-01T01:00:00.000+01:00;1;1.0; + 1970-01-01T02:00:00.000+01:00;1;;a + 1970-01-01T03:00:00.000+01:00;1;3.0;b + 1970-01-01T01:00:00.000+01:00;2;4.0;c + 1970-01-01T02:00:00.000+01:00;2;5.0; + 1970-01-01T03:00:00.000+01:00;2;6.0;d + """.replaceAll("\n", System.lineSeparator()); assertThatCode(() -> TimeSeries.parseCsv(duplicates)).hasMessageContaining("Bad CSV header, there are duplicates in time series names").isInstanceOf(TimeSeriesException.class); - String noData = String.join(System.lineSeparator(), - "Time;Version", - "1970-01-01T01:00:00.000+01:00;1", - "1970-01-01T02:00:00.000+01:00;1", - "1970-01-01T03:00:00.000+01:00;1", - "1970-01-01T01:00:00.000+01:00;2", - "1970-01-01T02:00:00.000+01:00;2", - "1970-01-01T03:00:00.000+01:00;2") + System.lineSeparator(); + String noData = """ + Time;Version + 1970-01-01T01:00:00.000+01:00;1 + 1970-01-01T02:00:00.000+01:00;1 + 1970-01-01T03:00:00.000+01:00;1 + 1970-01-01T01:00:00.000+01:00;2 + 1970-01-01T02:00:00.000+01:00;2 + 1970-01-01T03:00:00.000+01:00;2 + """.replaceAll("\n", System.lineSeparator()); assertThatCode(() -> TimeSeries.parseCsv(noData)).hasMessageContaining("Bad CSV header, should be \ntime;version;...").isInstanceOf(TimeSeriesException.class); - String onlyOneTime = String.join(System.lineSeparator(), - "Time;ts1", - "1970-01-01T03:00:00.000+01:00;2.0") + System.lineSeparator(); + String onlyOneTime = """ + Time;ts1 + 1970-01-01T03:00:00.000+01:00;2.0 + """.replaceAll("\n", System.lineSeparator()); assertThatCode(() -> TimeSeries.parseCsv(onlyOneTime, timeSeriesCsvConfig)).hasMessageContaining("At least 2 rows are expected").isInstanceOf(TimeSeriesException.class); - String unexpectedTokens = String.join(System.lineSeparator(), - "Time;ts1;ts2", - "1970-01-01T01:00:00.000+01:00;1.0;3.2", - "1970-01-01T02:00:00.000+01:00;2.0", - "1970-01-01T03:00:00.000+01:00;2.0;1.0") + System.lineSeparator(); + String unexpectedTokens = """ + Time;ts1;ts2 + 1970-01-01T01:00:00.000+01:00;1.0;3.2 + 1970-01-01T02:00:00.000+01:00;2.0 + 1970-01-01T03:00:00.000+01:00;2.0;1.0 + """.replaceAll("\n", System.lineSeparator()); assertThatCode(() -> TimeSeries.parseCsv(unexpectedTokens, timeSeriesCsvConfig)).hasMessageContaining("Columns of line 1 are inconsistent with header").isInstanceOf(TimeSeriesException.class); Path path = Path.of("wrongPath.csv"); diff --git a/time-series/time-series-dsl/pom.xml b/time-series/time-series-dsl/pom.xml index 2907bd22ee1..8b47ec6eccf 100644 --- a/time-series/time-series-dsl/pom.xml +++ b/time-series/time-series-dsl/pom.xml @@ -15,7 +15,7 @@ powsybl-time-series com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-time-series-dsl diff --git a/tools-test/pom.xml b/tools-test/pom.xml index 118e0113309..a6ae0ee6f0c 100644 --- a/tools-test/pom.xml +++ b/tools-test/pom.xml @@ -15,7 +15,7 @@ powsybl-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-tools-test diff --git a/tools/pom.xml b/tools/pom.xml index 4d5529b5cc0..9fc9a4cdd47 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -15,7 +15,7 @@ powsybl-core com.powsybl - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-tools diff --git a/triple-store/pom.xml b/triple-store/pom.xml index 692ad361a43..e24baa20edf 100644 --- a/triple-store/pom.xml +++ b/triple-store/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom diff --git a/triple-store/triple-store-api/pom.xml b/triple-store/triple-store-api/pom.xml index 0b8f8af55f0..00f8ccda714 100644 --- a/triple-store/triple-store-api/pom.xml +++ b/triple-store/triple-store-api/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-triple-store - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-triple-store-api diff --git a/triple-store/triple-store-impl-rdf4j/pom.xml b/triple-store/triple-store-impl-rdf4j/pom.xml index 5518a1e6655..12507fa84b3 100644 --- a/triple-store/triple-store-impl-rdf4j/pom.xml +++ b/triple-store/triple-store-impl-rdf4j/pom.xml @@ -11,7 +11,7 @@ com.powsybl powsybl-triple-store - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-triple-store-impl-rdf4j diff --git a/triple-store/triple-store-test/pom.xml b/triple-store/triple-store-test/pom.xml index c55ab47305d..5ca72f10015 100644 --- a/triple-store/triple-store-test/pom.xml +++ b/triple-store/triple-store-test/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-triple-store - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-triple-store-test diff --git a/ucte/pom.xml b/ucte/pom.xml index 2475ae4ed6d..c32a449e969 100644 --- a/ucte/pom.xml +++ b/ucte/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-core - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT pom diff --git a/ucte/ucte-converter/pom.xml b/ucte/ucte-converter/pom.xml index 2efb6cdcacd..ebaef92d200 100644 --- a/ucte/ucte-converter/pom.xml +++ b/ucte/ucte-converter/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-ucte - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-ucte-converter diff --git a/ucte/ucte-network/pom.xml b/ucte/ucte-network/pom.xml index 36daacb7ed9..11fee760cd5 100644 --- a/ucte/ucte-network/pom.xml +++ b/ucte/ucte-network/pom.xml @@ -15,7 +15,7 @@ com.powsybl powsybl-ucte - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-ucte-network diff --git a/ucte/ucte-util/pom.xml b/ucte/ucte-util/pom.xml index 6422b37c9cd..8b1215a3982 100644 --- a/ucte/ucte-util/pom.xml +++ b/ucte/ucte-util/pom.xml @@ -7,7 +7,7 @@ com.powsybl powsybl-ucte - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT powsybl-ucte-util