From 3e8d9b7e1732d02d20ba3b2fc78fd6f84c72c885 Mon Sep 17 00:00:00 2001 From: Coline Piloquet <55250145+colinepiloquet@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:01:22 +0200 Subject: [PATCH] Three-winding transformers creation (#739) Signed-off-by: Coline PILOQUET --- docs/reference/network.rst | 1 + docs/user_guide/network.rst | 40 ++++- .../network/adders/NetworkElementAdders.java | 1 + .../adders/PhaseTapChangerDataframeAdder.java | 50 +++++-- .../adders/RatioTapChangerDataframeAdder.java | 50 +++++-- ...hreeWindingsTransformerDataframeAdder.java | 71 +++++++++ .../ThreeWindingsTransformerSeries.java | 140 ++++++++++++++++++ .../adders/NetworkElementAddersTest.java | 39 +++++ pypowsybl/network/impl/network.py | 91 +++++++++++- tests/test_network_elements_creation.py | 103 ++++++++++++- 10 files changed, 553 insertions(+), 33 deletions(-) create mode 100644 java/src/main/java/com/powsybl/dataframe/network/adders/ThreeWindingsTransformerDataframeAdder.java create mode 100644 java/src/main/java/com/powsybl/dataframe/network/adders/ThreeWindingsTransformerSeries.java diff --git a/docs/reference/network.rst b/docs/reference/network.rst index da680a34e7..9cb2cb70a6 100644 --- a/docs/reference/network.rst +++ b/docs/reference/network.rst @@ -148,6 +148,7 @@ Network elements can be created or removed using the following methods: :nosignatures: Network.create_2_windings_transformers + Network.create_3_windings_transformers Network.create_batteries Network.create_busbar_sections Network.create_buses diff --git a/docs/user_guide/network.rst b/docs/user_guide/network.rst index 7326493e43..fa8e13ddd3 100644 --- a/docs/user_guide/network.rst +++ b/docs/user_guide/network.rst @@ -320,7 +320,7 @@ Let's now create some buses inside those voltage levels: network.create_buses(id=['B1', 'B2'], voltage_level_id=['VL1', 'VL2']) -Let's connect thoses buses with a line: +Let's connect these buses with a line: .. testcode:: @@ -347,6 +347,44 @@ You can now run a loadflow to check our network actually works ! >>> str(res[0].status) 'ComponentStatus.CONVERGED' +Now let's see how to add a three-winding transformer to the network. First, let's add two voltage levels and their associated buses to the substation `S1`. + +.. testcode:: + + voltage_levels = pd.DataFrame.from_records(index='id', data=[ + {'substation_id': 'S1', 'id': 'VL3', 'topology_kind': 'BUS_BREAKER', 'nominal_v': 225}, + {'substation_id': 'S1', 'id': 'VL4', 'topology_kind': 'BUS_BREAKER', 'nominal_v': 90}, + ]) + network.create_voltage_levels(voltage_levels) + network.create_buses(id=['B3', 'B4'], voltage_level_id=['VL3', 'VL4']) + +Now let's add a three-winding transformer between VL1, VL2 and VL3: + +.. testcode:: + + network.create_3_windings_transformers(id='T1', rated_u0 = 225, voltage_level1_id='VL1', bus1_id='B1', + voltage_level2_id='VL3', bus2_id='B3', + voltage_level3_id='VL4', bus3_id='B4', + b1=1e-6, g1=1e-6, r1=0.5, x1=10, rated_u1=400, + b2=1e-6, g2=1e-6, r2=0.5, x2=10, rated_u2=225, + b3=1e-6, g3=1e-6, r3=0.5, x3=10, rated_u3=90) + +You can add a ratio tap changer on the leg 1 of the three-winding transformer with: + +.. testcode:: + + rtc_df = pd.DataFrame.from_records( + index='id', + columns=['id', 'target_deadband', 'target_v', 'on_load', 'low_tap', 'tap', 'side'], + data=[('T1', 2, 200, False, 0, 1, 'ONE')]) + steps_df = pd.DataFrame.from_records( + index='id', + columns=['id', 'b', 'g', 'r', 'x', 'rho'], + data=[('T1', 2, 2, 1, 1, 0.5), + ('T1', 2, 2, 1, 1, 0.5), + ('T1', 2, 2, 1, 1, 0.8)]) + network.create_ratio_tap_changers(rtc_df, steps_df) + For more details and examples about network elements creations, please refer to the API reference :doc:`documentation `. diff --git a/java/src/main/java/com/powsybl/dataframe/network/adders/NetworkElementAdders.java b/java/src/main/java/com/powsybl/dataframe/network/adders/NetworkElementAdders.java index 3566b18e7f..aa4d5021e7 100644 --- a/java/src/main/java/com/powsybl/dataframe/network/adders/NetworkElementAdders.java +++ b/java/src/main/java/com/powsybl/dataframe/network/adders/NetworkElementAdders.java @@ -29,6 +29,7 @@ public final class NetworkElementAdders { Map.entry(LINE, new LineDataframeAdder()), Map.entry(STATIC_VAR_COMPENSATOR, new SvcDataframeAdder()), Map.entry(TWO_WINDINGS_TRANSFORMER, new TwtDataframeAdder()), + Map.entry(THREE_WINDINGS_TRANSFORMER, new ThreeWindingsTransformerDataframeAdder()), Map.entry(LOAD, new LoadDataframeAdder()), Map.entry(VSC_CONVERTER_STATION, new VscStationDataframeAdder()), Map.entry(LCC_CONVERTER_STATION, new LccStationDataframeAdder()), diff --git a/java/src/main/java/com/powsybl/dataframe/network/adders/PhaseTapChangerDataframeAdder.java b/java/src/main/java/com/powsybl/dataframe/network/adders/PhaseTapChangerDataframeAdder.java index 4804e88fa2..6b5ca2bbb5 100644 --- a/java/src/main/java/com/powsybl/dataframe/network/adders/PhaseTapChangerDataframeAdder.java +++ b/java/src/main/java/com/powsybl/dataframe/network/adders/PhaseTapChangerDataframeAdder.java @@ -32,7 +32,8 @@ public class PhaseTapChangerDataframeAdder implements NetworkElementAdder { SeriesMetadata.booleans("regulating"), SeriesMetadata.strings("regulated_side"), SeriesMetadata.ints("low_tap"), - SeriesMetadata.ints("tap") + SeriesMetadata.ints("tap"), + SeriesMetadata.strings("side") ); private static final List STEPS_METADATA = List.of( @@ -71,6 +72,7 @@ private static class PhaseTapChangerSeries { private final DoubleSeries targetValues; private final IntSeries regulating; private final StringSeries regulatedSide; + private final StringSeries sides; private final Map stepsIndexes; private final DoubleSeries g; @@ -89,6 +91,7 @@ private static class PhaseTapChangerSeries { this.targetValues = tapChangersDf.getDoubles("target_value"); this.regulating = tapChangersDf.getInts("regulating"); this.regulatedSide = tapChangersDf.getStrings("regulated_side"); + this.sides = tapChangersDf.getStrings("side"); this.stepsIndexes = getStepsIndexes(stepsDf); this.g = stepsDf.getDoubles("g"); @@ -101,20 +104,33 @@ private static class PhaseTapChangerSeries { void create(Network network, int row) { String transformerId = ids.get(row); - TwoWindingsTransformer transformer = network.getTwoWindingsTransformer(transformerId); - if (transformer == null) { - throw new PowsyblException("Transformer " + transformerId + " does not exist."); + TwoWindingsTransformer twoWindingsTransformer = network.getTwoWindingsTransformer(transformerId); + PhaseTapChangerAdder adder; + if (twoWindingsTransformer == null) { + ThreeWindingsTransformer threeWindingsTransformer = network.getThreeWindingsTransformer(transformerId); + if (threeWindingsTransformer == null) { + throw new PowsyblException("Transformer " + transformerId + " does not exist."); + } + if (sides == null) { + throw new PowsyblException("Side is missing: cannot add a ratio tap changer on a three-winding transformer."); + } + ThreeSides side = ThreeSides.valueOf(sides.get(row)); + adder = threeWindingsTransformer.getLeg(side).newPhaseTapChanger(); + if (regulatedSide != null) { + setRegulatedSide(threeWindingsTransformer, adder, regulatedSide.get(row)); + } + } else { + adder = twoWindingsTransformer.newPhaseTapChanger(); + if (regulatedSide != null) { + setRegulatedSide(twoWindingsTransformer, adder, regulatedSide.get(row)); + } } - PhaseTapChangerAdder adder = transformer.newPhaseTapChanger(); applyIfPresent(regulationModes, row, PhaseTapChanger.RegulationMode.class, adder::setRegulationMode); applyIfPresent(targetDeadband, row, adder::setTargetDeadband); applyIfPresent(targetValues, row, adder::setRegulationValue); applyIfPresent(lowTaps, row, adder::setLowTapPosition); applyIfPresent(taps, row, adder::setTapPosition); applyBooleanIfPresent(regulating, row, adder::setRegulating); - if (regulatedSide != null) { - setRegulatedSide(transformer, adder, regulatedSide.get(row)); - } TIntArrayList steps = stepsIndexes.get(transformerId); if (steps != null) { @@ -132,14 +148,20 @@ void create(Network network, int row) { } adder.add(); } - } - private static void setRegulatedSide(TwoWindingsTransformer transformer, PhaseTapChangerAdder adder, String regulatedSideStr) { - if (regulatedSideStr.isEmpty()) { - return; + private static void setRegulatedSide(ThreeWindingsTransformer transformer, PhaseTapChangerAdder adder, String regulatedSideStr) { + if (!regulatedSideStr.isEmpty()) { + ThreeSides regulatedSide = ThreeSides.valueOf(regulatedSideStr); + adder.setRegulationTerminal(transformer.getTerminal(regulatedSide)); + } + } + + private static void setRegulatedSide(TwoWindingsTransformer transformer, PhaseTapChangerAdder adder, String regulatedSideStr) { + if (!regulatedSideStr.isEmpty()) { + TwoSides regulatedSide = TwoSides.valueOf(regulatedSideStr); + adder.setRegulationTerminal(transformer.getTerminal(regulatedSide)); + } } - TwoSides regulatedSide = TwoSides.valueOf(regulatedSideStr); - adder.setRegulationTerminal(transformer.getTerminal(regulatedSide)); } /** diff --git a/java/src/main/java/com/powsybl/dataframe/network/adders/RatioTapChangerDataframeAdder.java b/java/src/main/java/com/powsybl/dataframe/network/adders/RatioTapChangerDataframeAdder.java index 83c10fecfd..89435df534 100644 --- a/java/src/main/java/com/powsybl/dataframe/network/adders/RatioTapChangerDataframeAdder.java +++ b/java/src/main/java/com/powsybl/dataframe/network/adders/RatioTapChangerDataframeAdder.java @@ -32,7 +32,8 @@ public class RatioTapChangerDataframeAdder implements NetworkElementAdder { SeriesMetadata.doubles("target_v"), SeriesMetadata.doubles("target_deadband"), SeriesMetadata.booleans("regulating"), - SeriesMetadata.strings("regulated_side") + SeriesMetadata.strings("regulated_side"), + SeriesMetadata.strings("side") ); private static final List STEPS_METADATA = List.of( @@ -58,6 +59,7 @@ private static class RatioTapChangerSeries { private final DoubleSeries targetDeadband; private final IntSeries regulating; private final StringSeries regulatedSide; + private final StringSeries sides; private final Map stepsIndexes; private final DoubleSeries g; @@ -75,6 +77,7 @@ private static class RatioTapChangerSeries { this.targetDeadband = tapChangersDf.getDoubles("target_deadband"); this.regulating = tapChangersDf.getInts("regulating"); this.regulatedSide = tapChangersDf.getStrings("regulated_side"); + this.sides = tapChangersDf.getStrings("side"); this.stepsIndexes = getStepsIndexes(stepsDf); this.g = stepsDf.getDoubles("g"); @@ -86,20 +89,33 @@ private static class RatioTapChangerSeries { void create(Network network, int row) { String transformerId = ids.get(row); - TwoWindingsTransformer transformer = network.getTwoWindingsTransformer(transformerId); - if (transformer == null) { - throw new PowsyblException("Transformer " + transformerId + " does not exist."); + RatioTapChangerAdder adder; + TwoWindingsTransformer twoWindingsTransformer = network.getTwoWindingsTransformer(transformerId); + if (twoWindingsTransformer == null) { + ThreeWindingsTransformer threeWindingsTransformer = network.getThreeWindingsTransformer(transformerId); + if (threeWindingsTransformer == null) { + throw new PowsyblException("Transformer " + transformerId + " does not exist."); + } + if (sides == null) { + throw new PowsyblException("Side is missing: cannot add a ratio tap changer on a three-winding transformer."); + } + ThreeSides side = ThreeSides.valueOf(sides.get(row)); + adder = threeWindingsTransformer.getLeg(side).newRatioTapChanger(); + if (regulatedSide != null) { + setRegulatedSide(threeWindingsTransformer, adder, regulatedSide.get(row)); + } + } else { + adder = twoWindingsTransformer.newRatioTapChanger(); + if (regulatedSide != null) { + setRegulatedSide(twoWindingsTransformer, adder, regulatedSide.get(row)); + } } - RatioTapChangerAdder adder = transformer.newRatioTapChanger(); applyIfPresent(targetDeadband, row, adder::setTargetDeadband); applyIfPresent(targetV, row, adder::setTargetV); applyBooleanIfPresent(onLoad, row, adder::setLoadTapChangingCapabilities); applyIfPresent(lowTaps, row, adder::setLowTapPosition); applyIfPresent(taps, row, adder::setTapPosition); applyBooleanIfPresent(regulating, row, adder::setRegulating); - if (regulatedSide != null) { - setRegulatedSide(transformer, adder, regulatedSide.get(row)); - } TIntArrayList steps = stepsIndexes.get(transformerId); if (steps != null) { @@ -116,14 +132,20 @@ void create(Network network, int row) { } adder.add(); } - } - private static void setRegulatedSide(TwoWindingsTransformer transformer, RatioTapChangerAdder adder, String regulatedSideStr) { - if (regulatedSideStr.isEmpty()) { - return; + private static void setRegulatedSide(TwoWindingsTransformer transformer, RatioTapChangerAdder adder, String regulatedSideStr) { + if (!regulatedSideStr.isEmpty()) { + TwoSides regulatedSide = TwoSides.valueOf(regulatedSideStr); + adder.setRegulationTerminal(transformer.getTerminal(regulatedSide)); + } + } + + private static void setRegulatedSide(ThreeWindingsTransformer transformer, RatioTapChangerAdder adder, String regulatedSideStr) { + if (!regulatedSideStr.isEmpty()) { + ThreeSides regulatedSide = ThreeSides.valueOf(regulatedSideStr); + adder.setRegulationTerminal(transformer.getTerminal(regulatedSide)); + } } - TwoSides regulatedSide = TwoSides.valueOf(regulatedSideStr); - adder.setRegulationTerminal(transformer.getTerminal(regulatedSide)); } @Override diff --git a/java/src/main/java/com/powsybl/dataframe/network/adders/ThreeWindingsTransformerDataframeAdder.java b/java/src/main/java/com/powsybl/dataframe/network/adders/ThreeWindingsTransformerDataframeAdder.java new file mode 100644 index 0000000000..95151aac21 --- /dev/null +++ b/java/src/main/java/com/powsybl/dataframe/network/adders/ThreeWindingsTransformerDataframeAdder.java @@ -0,0 +1,71 @@ +/** + * 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.dataframe.network.adders; + +import com.powsybl.dataframe.SeriesMetadata; +import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.iidm.network.Network; + +import java.util.Collections; +import java.util.List; + +/** + * @author Coline Piloquet + */ +public class ThreeWindingsTransformerDataframeAdder extends AbstractSimpleAdder { + + private static final List METADATA = List.of( + SeriesMetadata.stringIndex("id"), + SeriesMetadata.strings("name"), + SeriesMetadata.doubles("rated_u0"), + SeriesMetadata.doubles("r1"), + SeriesMetadata.doubles("x1"), + SeriesMetadata.doubles("g1"), + SeriesMetadata.doubles("b1"), + SeriesMetadata.doubles("rated_u1"), + SeriesMetadata.doubles("rated_s1"), + SeriesMetadata.strings("voltage_level1_id"), + SeriesMetadata.ints("node1"), + SeriesMetadata.strings("bus1_id"), + SeriesMetadata.strings("connectable_bus1_id"), + SeriesMetadata.doubles("r2"), + SeriesMetadata.doubles("x2"), + SeriesMetadata.doubles("g2"), + SeriesMetadata.doubles("b2"), + SeriesMetadata.doubles("rated_u2"), + SeriesMetadata.doubles("rated_s2"), + SeriesMetadata.strings("voltage_level2_id"), + SeriesMetadata.ints("node2"), + SeriesMetadata.strings("bus2_id"), + SeriesMetadata.strings("connectable_bus2_id"), + SeriesMetadata.doubles("r3"), + SeriesMetadata.doubles("x3"), + SeriesMetadata.doubles("g3"), + SeriesMetadata.doubles("b3"), + SeriesMetadata.doubles("rated_u3"), + SeriesMetadata.doubles("rated_s3"), + SeriesMetadata.strings("voltage_level3_id"), + SeriesMetadata.ints("node3"), + SeriesMetadata.strings("bus3_id"), + SeriesMetadata.strings("connectable_bus3_id") + ); + + @Override + public List> getMetadata() { + return Collections.singletonList(METADATA); + } + + @Override + public void addElements(Network network, UpdatingDataframe dataframe) { + ThreeWindingsTransformerSeries series = new ThreeWindingsTransformerSeries(dataframe); + for (int row = 0; row < dataframe.getRowCount(); row++) { + series.create(network, row).add(); + } + } + +} diff --git a/java/src/main/java/com/powsybl/dataframe/network/adders/ThreeWindingsTransformerSeries.java b/java/src/main/java/com/powsybl/dataframe/network/adders/ThreeWindingsTransformerSeries.java new file mode 100644 index 0000000000..6ebbc571be --- /dev/null +++ b/java/src/main/java/com/powsybl/dataframe/network/adders/ThreeWindingsTransformerSeries.java @@ -0,0 +1,140 @@ +/** + * 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.dataframe.network.adders; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.dataframe.update.DoubleSeries; +import com.powsybl.dataframe.update.IntSeries; +import com.powsybl.dataframe.update.StringSeries; +import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Substation; +import com.powsybl.iidm.network.ThreeWindingsTransformerAdder; +import com.powsybl.iidm.network.VoltageLevel; + +import static com.powsybl.dataframe.network.adders.NetworkUtils.getVoltageLevelOrThrow; +import static com.powsybl.dataframe.network.adders.SeriesUtils.applyIfPresent; + +/** + * @author Coline Piloquet + */ +public class ThreeWindingsTransformerSeries extends IdentifiableSeries { + private static final String COULD_NOT_CREATE_TRANSFORMER = "Could not create transformer "; + private static final String NO_SUBSTATION = ": no substation."; + + private final DoubleSeries ratedU0; + private final DoubleSeries r1; + private final DoubleSeries x1; + private final DoubleSeries g1; + private final DoubleSeries b1; + private final DoubleSeries ratedU1; + private final DoubleSeries ratedS1; + private final DoubleSeries r2; + private final DoubleSeries x2; + private final DoubleSeries g2; + private final DoubleSeries b2; + private final DoubleSeries ratedU2; + private final DoubleSeries ratedS2; + private final DoubleSeries r3; + private final DoubleSeries x3; + private final DoubleSeries g3; + private final DoubleSeries b3; + private final DoubleSeries ratedU3; + private final DoubleSeries ratedS3; + + private final StringSeries voltageLevels1; + private final StringSeries connectableBuses1; + private final StringSeries buses1; + private final IntSeries nodes1; + private final StringSeries voltageLevels2; + private final StringSeries connectableBuses2; + private final StringSeries buses2; + private final IntSeries nodes2; + private final StringSeries voltageLevels3; + private final StringSeries connectableBuses3; + private final StringSeries buses3; + private final IntSeries nodes3; + + ThreeWindingsTransformerSeries(UpdatingDataframe dataframe) { + super(dataframe); + this.ratedU0 = dataframe.getDoubles("rated_u0"); + this.r1 = dataframe.getDoubles("r1"); + this.x1 = dataframe.getDoubles("x1"); + this.g1 = dataframe.getDoubles("g1"); + this.b1 = dataframe.getDoubles("b1"); + this.ratedU1 = dataframe.getDoubles("rated_u1"); + this.ratedS1 = dataframe.getDoubles("rated_s1"); + this.r2 = dataframe.getDoubles("r2"); + this.x2 = dataframe.getDoubles("x2"); + this.g2 = dataframe.getDoubles("g2"); + this.b2 = dataframe.getDoubles("b2"); + this.ratedU2 = dataframe.getDoubles("rated_u2"); + this.ratedS2 = dataframe.getDoubles("rated_s2"); + this.r3 = dataframe.getDoubles("r3"); + this.x3 = dataframe.getDoubles("x3"); + this.g3 = dataframe.getDoubles("g3"); + this.b3 = dataframe.getDoubles("b3"); + this.ratedU3 = dataframe.getDoubles("rated_u3"); + this.ratedS3 = dataframe.getDoubles("rated_s3"); + this.voltageLevels1 = dataframe.getStrings("voltage_level1_id"); + this.connectableBuses1 = dataframe.getStrings("connectable_bus1_id"); + this.buses1 = dataframe.getStrings("bus1_id"); + this.nodes1 = dataframe.getInts("node1"); + this.voltageLevels2 = dataframe.getStrings("voltage_level2_id"); + this.connectableBuses2 = dataframe.getStrings("connectable_bus2_id"); + this.buses2 = dataframe.getStrings("bus2_id"); + this.nodes2 = dataframe.getInts("node2"); + this.voltageLevels3 = dataframe.getStrings("voltage_level3_id"); + this.connectableBuses3 = dataframe.getStrings("connectable_bus3_id"); + this.buses3 = dataframe.getStrings("bus3_id"); + this.nodes3 = dataframe.getInts("node3"); + } + + ThreeWindingsTransformerAdder create(Network network, int row) { + String id = ids.get(row); + VoltageLevel vl1 = getVoltageLevelOrThrow(network, voltageLevels1.get(row)); + VoltageLevel vl2 = getVoltageLevelOrThrow(network, voltageLevels2.get(row)); + VoltageLevel vl3 = getVoltageLevelOrThrow(network, voltageLevels3.get(row)); + + Substation s1 = vl1.getSubstation().orElseThrow(() -> new PowsyblException(COULD_NOT_CREATE_TRANSFORMER + id + NO_SUBSTATION)); + Substation s2 = vl2.getSubstation().orElseThrow(() -> new PowsyblException(COULD_NOT_CREATE_TRANSFORMER + id + NO_SUBSTATION)); + Substation s3 = vl3.getSubstation().orElseThrow(() -> new PowsyblException(COULD_NOT_CREATE_TRANSFORMER + id + NO_SUBSTATION)); + if (!(s1 == s2 && s1 == s3)) { + throw new PowsyblException(COULD_NOT_CREATE_TRANSFORMER + id + ": all voltage levels must be on the same substation"); + } + var adder = s1.newThreeWindingsTransformer(); + setIdentifiableAttributes(adder, row); + applyIfPresent(ratedU0, row, adder::setRatedU0); + ThreeWindingsTransformerAdder.LegAdder leg1 = adder.newLeg1(); + ThreeWindingsTransformerAdder.LegAdder leg2 = adder.newLeg2(); + ThreeWindingsTransformerAdder.LegAdder leg3 = adder.newLeg3(); + createLeg(row, leg1, voltageLevels1, connectableBuses1, buses1, nodes1, r1, x1, g1, b1, ratedU1, ratedS1); + createLeg(row, leg2, voltageLevels2, connectableBuses2, buses2, nodes2, r2, x2, g2, b2, ratedU2, ratedS2); + createLeg(row, leg3, voltageLevels3, connectableBuses3, buses3, nodes3, r3, x3, g3, b3, ratedU3, ratedS3); + leg1.add(); + leg2.add(); + leg3.add(); + + return adder; + } + + private void createLeg(int row, ThreeWindingsTransformerAdder.LegAdder leg, StringSeries voltageLevels, StringSeries connectableBuses, + StringSeries buses, IntSeries nodes, DoubleSeries r, DoubleSeries x, DoubleSeries g, DoubleSeries b, + DoubleSeries ratedU, DoubleSeries ratedS) { + applyIfPresent(voltageLevels, row, leg::setVoltageLevel); + applyIfPresent(connectableBuses, row, leg::setConnectableBus); + applyIfPresent(buses, row, leg::setBus); + applyIfPresent(nodes, row, leg::setNode); + applyIfPresent(r, row, leg::setR); + applyIfPresent(x, row, leg::setX); + applyIfPresent(g, row, leg::setG); + applyIfPresent(b, row, leg::setB); + applyIfPresent(ratedU, row, leg::setRatedU); + applyIfPresent(ratedS, row, leg::setRatedS); + } +} diff --git a/java/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java b/java/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java index 097df1ac90..915f16b3b2 100644 --- a/java/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java +++ b/java/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java @@ -441,4 +441,43 @@ void secondaryVoltageControlExtension() { extension = network.getExtension(SecondaryVoltageControl.class); assertNotNull(extension); } + + @Test + void threeWindingTransformer() { + var network = EurostagTutorialExample1Factory.create(); + DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); + addStringColumn(dataframe, "id", "test"); + addStringColumn(dataframe, "substation_id", "P1"); + addStringColumn(dataframe, "name", "l3"); + addStringColumn(dataframe, "bus1_id", "NGEN"); + addStringColumn(dataframe, "bus2_id", "NHV1"); + addStringColumn(dataframe, "bus3_id", "NHV1"); + addStringColumn(dataframe, "voltage_level1_id", "VLGEN"); + addStringColumn(dataframe, "voltage_level2_id", "VLHV1"); + addStringColumn(dataframe, "voltage_level3_id", "VLHV1"); + addStringColumn(dataframe, "connectable_bus1_id", "NGEN"); + addStringColumn(dataframe, "connectable_bus2_id", "NHV1"); + addStringColumn(dataframe, "connectable_bus3_id", "NHV1"); + addDoubleColumn(dataframe, "rated_u1", 4.0); + addDoubleColumn(dataframe, "rated_u2", 4.0); + addDoubleColumn(dataframe, "rated_u3", 4.0); + addDoubleColumn(dataframe, "rated_s1", 4.0); + addDoubleColumn(dataframe, "rated_s2", 4.0); + addDoubleColumn(dataframe, "rated_s3", 4.0); + addDoubleColumn(dataframe, "r1", 4.0); + addDoubleColumn(dataframe, "r2", 4.0); + addDoubleColumn(dataframe, "r3", 4.0); + addDoubleColumn(dataframe, "x1", 4.0); + addDoubleColumn(dataframe, "x2", 4.0); + addDoubleColumn(dataframe, "x3", 4.0); + addDoubleColumn(dataframe, "g1", 4.0); + addDoubleColumn(dataframe, "g2", 4.0); + addDoubleColumn(dataframe, "g3", 4.0); + addDoubleColumn(dataframe, "b1", 4.0); + addDoubleColumn(dataframe, "b2", 4.0); + addDoubleColumn(dataframe, "b3", 4.0); + addDoubleColumn(dataframe, "rated_u0", 4.0); + NetworkElementAdders.addElements(DataframeElementType.THREE_WINDINGS_TRANSFORMER, network, singletonList(dataframe)); + assertEquals(1, network.getThreeWindingsTransformerCount()); + } } diff --git a/pypowsybl/network/impl/network.py b/pypowsybl/network/impl/network.py index 482c22b940..112fd64c40 100644 --- a/pypowsybl/network/impl/network.py +++ b/pypowsybl/network/impl/network.py @@ -28,7 +28,7 @@ import pandas as pd import pypowsybl._pypowsybl as _pp -from pypowsybl._pypowsybl import ElementType, ValidationLevel +from pypowsybl._pypowsybl import ElementType, ValidationLevel, Side from pypowsybl.utils import ( _adapt_df_or_kwargs, _create_c_dataframe, @@ -3893,6 +3893,86 @@ def create_2_windings_transformers(self, df: DataFrame = None, **kwargs: ArrayLi """ return self._create_elements(ElementType.TWO_WINDINGS_TRANSFORMER, [df], **kwargs) + def create_3_windings_transformers(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: + """ + Creates three-winding transformers. + + Args: + df: Attributes as a dataframe. + kwargs: Attributes as keyword arguments. + + Notes: + + Data may be provided as a dataframe or as keyword arguments. + In the latter case, all arguments must have the same length. + + For each side of the transformer, either a node ID (voltage level in node-breaker topology), bus ID ( + voltage level in bus-breaker topology) or connectable bus ID (voltage level in bus-breaker topology, + the transformer is created associated but disconnected from this bus) should be specified. + + Valid attributes are: + + - **id**: the identifier of the new transformer + - **rated_u0**: the rated voltage at the star bus + - **voltage_level1_id**: the voltage level where the new transformer will be connected on side 1. + The voltage level must already exist. + - **bus1_id**: the bus where the new transformer will be connected on side 1, + if the voltage level has a bus-breaker topology kind. + - **connectable_bus1_id**: the bus to which the transformer can be connected on side 1, if the voltage level + has a bus-breaker topology kind. The transformer is created disconnected from this bus. + - **node1**: the node where the new transformer will be connected on side 1, + if the voltage level has a node-breaker topology kind. + - **voltage_level2_id**: the voltage level where the new transformer will be connected on side 2. + The voltage level must already exist. + - **bus2_id**: the bus where the new transformer will be connected on side 2, + if the voltage level has a bus-breaker topology kind. + - **connectable_bus2_id**: the bus to which the transformer can be connected on side 2, if the voltage level + has a bus-breaker topology kind. The transformer is created disconnected from this bus. + - **node2**: the node where the new transformer will be connected on side 2, + if the voltage level has a node-breaker topology kind. + - **voltage_level3_id**: the voltage level where the new transformer will be connected on side 3. + The voltage level must already exist. + - **bus3_id**: the bus where the new transformer will be connected on side 3, + if the voltage level has a bus-breaker topology kind. + - **connectable_bus3_id**: the bus to which the transformer can be connected on side 3, if the voltage level + has a bus-breaker topology kind. The transformer is created disconnected from this bus. + - **node3**: the node where the new transformer will be connected on side 3, + if the voltage level has a node-breaker topology kind. + - **name**: an optional human-readable name + - **rated_u1**: nominal voltage of the side 1 of the transformer + - **rated_u2**: nominal voltage of the side 2 of the transformer + - **rated_u3**: nominal voltage of the side 3 of the transformer + - **rated_s1**: optionally, nominal power of the side 1 of the transformer + - **rated_s2**: optionally, nominal power of the side 2 of the transformer + - **rated_s3**: optionally, nominal power of the side 3 of the transformer + - **r1**: the resistance of the side 1, in Ohm + - **r2**: the resistance of the side 2, in Ohm + - **r3**: the resistance of the side 3, in Ohm + - **x1**: the reactance of the side 1, in Ohm + - **x2**: the reactance of the side 2, in Ohm + - **x3**: the reactance of the side 3, in Ohm + - **b1**: the shunt susceptance of the side 1, in S + - **b2**: the shunt susceptance of the side 2, in S + - **b3**: the shunt susceptance of the side 3, in S + - **g1**: the shunt conductance of the side 1, in S + - **g2**: the shunt conductance of the side 2, in S + - **g3**: the shunt conductance of the side 3, in S + + + Examples: + Using keyword arguments: + + .. code-block:: python + + network.create_3_windings_transformers(id='T-1', rated_u0 = 225, voltage_level1_id='VL1', bus1_id='B1', + voltage_level2_id='VL2', bus2_id='B2', + voltage_level3_id='VL3', bus3_id='B3', + b1=1e-6, g1=1e-6, r1=0.5, x1=10, rated_u1=400, rated_s1=100, + b2=1e-6, g2=1e-6, r2=0.5, x2=10, rated_u2=225, rated_s2=100, + b3=1e-6, g3=1e-6, r3=0.5, x3=10, rated_u3=90, rated_s3=100) + """ + return self._create_elements(ElementType.THREE_WINDINGS_TRANSFORMER, [df], **kwargs) + def create_shunt_compensators(self, shunt_df: DataFrame, linear_model_df: Optional[DataFrame] = None, non_linear_model_df: Optional[DataFrame] = None) -> None: @@ -4099,7 +4179,9 @@ def create_ratio_tap_changers(self, rtc_df: DataFrame, steps_df: DataFrame) -> N - **target_v**: the target voltage, in kV - **target_deadband**: the target voltage regulation deadband, in kV - **regulating**: true if the tap changer should regulate voltage - - **regulated_side**: the side where voltage is regulated (ONE or TWO) + - **regulated_side**: the side where voltage is regulated (ONE or TWO if two-winding transformer, ONE, TWO + or THREE if three-winding transformer) + - **side**: Side of the tap changer (only for three-winding transformers) Valid attributes for the steps dataframe are: @@ -4127,6 +4209,7 @@ def create_ratio_tap_changers(self, rtc_df: DataFrame, steps_df: DataFrame) -> N ('NGEN_NHV1', 2, 2, 1, 1, 0.8)]) network.create_ratio_tap_changers(rtc_df, steps_df) """ + return self._create_elements(ElementType.RATIO_TAP_CHANGER, [rtc_df, steps_df]) def create_phase_tap_changers(self, ptc_df: DataFrame, steps_df: DataFrame) -> None: @@ -4153,7 +4236,9 @@ def create_phase_tap_changers(self, ptc_df: DataFrame, steps_df: DataFrame) -> N - **regulation_mode**: the regulation mode (CURRENT_LIMITER, ACTIVE_POWER_CONTROL, FIXED_TAP) - **target_deadband**: the regulation deadband - **regulating**: true if the tap changer should regulate - - **regulated_side**: the side where the current or active power is regulated (ONE or TWO) + - **regulated_side**: the side where the current or active power is regulated (ONE or TWO if two-winding + transformer, ONE, TWO or THREE if three-winding transformer) + - **side**: Side of the tap changer (only for three-winding transformers) Valid attributes for the steps dataframe are: diff --git a/tests/test_network_elements_creation.py b/tests/test_network_elements_creation.py index 3a1553e5c4..239375bccf 100644 --- a/tests/test_network_elements_creation.py +++ b/tests/test_network_elements_creation.py @@ -938,4 +938,105 @@ def test_deprecated_ucte_xnode_code_dataframe(): index='id')) assert 'DL_TEST' in network.get_dangling_lines().index assert 'DL_TEST2' in network.get_dangling_lines().index - assert 'ucte_xnode_code' in network.get_dangling_lines().columns \ No newline at end of file + assert 'ucte_xnode_code' in network.get_dangling_lines().columns + + +def test_3_windings_transformers_creation(): + n = pn.create_eurostag_tutorial_example1_network() + df = pd.DataFrame.from_records(index='id', data=[{ + 'id': 'VLTEST', + 'substation_id': 'P1', + 'high_voltage_limit': 250, + 'low_voltage_limit': 200, + 'nominal_v': 225, + 'topology_kind': 'BUS_BREAKER' + }]) + n.create_voltage_levels(df) + n.create_buses(pd.DataFrame(index=['BUS_TEST'], + columns=['voltage_level_id'], + data=[['VLTEST']])) + n.create_3_windings_transformers(id='TWT_TEST', rated_u0=225, voltage_level1_id='VLHV1', bus1_id='NHV1', + voltage_level2_id='VLTEST', bus2_id='BUS_TEST', + voltage_level3_id='VLGEN', bus3_id='NGEN', + b1=1e-6, g1=1e-6, r1=0.5, x1=10, rated_u1=380, rated_s1=100, + b2=1e-6, g2=1e-6, r2=0.5, x2=10, rated_u2=225, rated_s2=100, + b3=1e-6, g3=1e-6, r3=0.5, x3=10, rated_u3=24, rated_s3=100) + + transformer = n.get_3_windings_transformers().loc['TWT_TEST'] + assert transformer.rated_u0 == 225 + assert transformer.r1 == 0.5 + assert transformer.x1 == 10 + assert transformer.g1 == 1e-6 + assert transformer.b1 == 1e-6 + assert transformer.rated_u1 == 380.0 + assert transformer.rated_s1 == 100 + assert transformer.voltage_level1_id == 'VLHV1' + assert transformer.bus1_id == 'VLHV1_0' + assert transformer.connected1 == True + assert transformer.r2 == 0.5 + assert transformer.x2 == 10 + assert transformer.g2 == 1e-6 + assert transformer.b2 == 1e-6 + assert transformer.rated_u2 == 225.0 + assert transformer.rated_s2 == 100 + assert transformer.voltage_level2_id == 'VLTEST' + assert transformer.bus2_id == 'VLTEST_0' + assert transformer.connected2 == True + assert transformer.r3 == 0.5 + assert transformer.x3 == 10 + assert transformer.g3 == 1e-6 + assert transformer.b3 == 1e-6 + assert transformer.rated_u3 == 24.0 + assert transformer.rated_s3 == 100 + assert transformer.voltage_level3_id == 'VLGEN' + assert transformer.bus3_id == 'VLGEN_0' + assert transformer.connected3 == True + + #Add ratio tap changer + rtc_df = pd.DataFrame.from_records( + index='id', + columns=['id', 'target_deadband', 'target_v', 'on_load', 'low_tap', 'tap', 'side'], + data=[('TWT_TEST', 2, 200, False, 0, 1, 'ONE')]) + steps_df = pd.DataFrame.from_records( + index='id', + columns=['id', 'b', 'g', 'r', 'x', 'rho'], + data=[('TWT_TEST', 2, 2, 1, 1, 0.5), + ('TWT_TEST', 2, 2, 1, 1, 0.5), + ('TWT_TEST', 2, 2, 1, 1, 0.8)]) + n.create_ratio_tap_changers(rtc_df, steps_df) + transformer = n.get_3_windings_transformers().loc['TWT_TEST'] + assert transformer.ratio_tap_position1 == 1 + + #Add phase tap changer + ptc_df = pd.DataFrame.from_records( + index='id', columns=['id', 'target_deadband', 'regulation_mode', 'low_tap', 'tap', 'side'], + data=[('TWT_TEST', 2, 'CURRENT_LIMITER', 0, 1, 'TWO')]) + steps_df = pd.DataFrame.from_records( + index='id', columns=['id', 'b', 'g', 'r', 'x', 'rho', 'alpha'], + data=[('TWT_TEST', 2, 2, 1, 1, 0.5, 0.1), + ('TWT_TEST', 2, 2, 1, 1, 0.4, 0.2), + ('TWT_TEST', 2, 2, 1, 1, 0.5, 0.1)]) + n.create_phase_tap_changers(ptc_df, steps_df) + transformer = n.get_3_windings_transformers().loc['TWT_TEST'] + assert transformer.phase_tap_position2 == 1 + + #Add some limits + n.create_operational_limits(pd.DataFrame.from_records(index='element_id', data=[ + {'element_id': 'TWT_TEST', 'name': 'permanent_limit', 'element_type': 'THREE_WINDINGS_TRANSFORMER', + 'side': 'ONE', + 'type': 'APPARENT_POWER', 'value': 600, + 'acceptable_duration': np.Inf, 'is_fictitious': False}, + {'element_id': 'TWT_TEST', 'name': '1\'', 'element_type': 'THREE_WINDINGS_TRANSFORMER', 'side': 'ONE', + 'type': 'APPARENT_POWER', 'value': 1000, + 'acceptable_duration': 60, 'is_fictitious': False}, + {'element_id': 'TWT_TEST', 'name': 'permanent_limit', 'element_type': 'THREE_WINDINGS_TRANSFORMER', + 'side': 'ONE', + 'type': 'ACTIVE_POWER', 'value': 400, + 'acceptable_duration': np.Inf, 'is_fictitious': False}, + {'element_id': 'TWT_TEST', 'name': '1\'', 'element_type': 'THREE_WINDINGS_TRANSFORMER', 'side': 'ONE', + 'type': 'ACTIVE_POWER', 'value': 700, + 'acceptable_duration': 60, 'is_fictitious': False} + ])) + operational_limits = n.get_operational_limits().loc['TWT_TEST'] + assert operational_limits.shape[0] == 4 +