diff --git a/cpp/pypowsybl-cpp/bindings.cpp b/cpp/pypowsybl-cpp/bindings.cpp index 7f1b99715..839db133e 100644 --- a/cpp/pypowsybl-cpp/bindings.cpp +++ b/cpp/pypowsybl-cpp/bindings.cpp @@ -306,6 +306,7 @@ PYBIND11_MODULE(_pypowsybl, m) { .value("NON_LINEAR_SHUNT_COMPENSATOR_SECTION", element_type::NON_LINEAR_SHUNT_COMPENSATOR_SECTION) .value("LINEAR_SHUNT_COMPENSATOR_SECTION", element_type::LINEAR_SHUNT_COMPENSATOR_SECTION) .value("DANGLING_LINE", element_type::DANGLING_LINE) + .value("DANGLING_LINE_GENERATION", element_type::DANGLING_LINE_GENERATION) .value("TIE_LINE", element_type::TIE_LINE) .value("LCC_CONVERTER_STATION", element_type::LCC_CONVERTER_STATION) .value("VSC_CONVERTER_STATION", element_type::VSC_CONVERTER_STATION) diff --git a/cpp/pypowsybl-java/powsybl-api.h b/cpp/pypowsybl-java/powsybl-api.h index 1648f1203..6860da0fb 100644 --- a/cpp/pypowsybl-java/powsybl-api.h +++ b/cpp/pypowsybl-java/powsybl-api.h @@ -146,6 +146,7 @@ typedef enum { NON_LINEAR_SHUNT_COMPENSATOR_SECTION, LINEAR_SHUNT_COMPENSATOR_SECTION, DANGLING_LINE, + DANGLING_LINE_GENERATION, TIE_LINE, LCC_CONVERTER_STATION, VSC_CONVERTER_STATION, diff --git a/java/src/main/java/com/powsybl/dataframe/DataframeElementType.java b/java/src/main/java/com/powsybl/dataframe/DataframeElementType.java index 86df03575..c754cf649 100644 --- a/java/src/main/java/com/powsybl/dataframe/DataframeElementType.java +++ b/java/src/main/java/com/powsybl/dataframe/DataframeElementType.java @@ -23,6 +23,7 @@ public enum DataframeElementType { NON_LINEAR_SHUNT_COMPENSATOR_SECTION, LINEAR_SHUNT_COMPENSATOR_SECTION, DANGLING_LINE, + DANGLING_LINE_GENERATION, TIE_LINE, LCC_CONVERTER_STATION, VSC_CONVERTER_STATION, diff --git a/java/src/main/java/com/powsybl/dataframe/network/NetworkDataframes.java b/java/src/main/java/com/powsybl/dataframe/network/NetworkDataframes.java index 5ed634269..eb5c24514 100644 --- a/java/src/main/java/com/powsybl/dataframe/network/NetworkDataframes.java +++ b/java/src/main/java/com/powsybl/dataframe/network/NetworkDataframes.java @@ -70,6 +70,7 @@ private static Map createMappers() mappers.put(DataframeElementType.NON_LINEAR_SHUNT_COMPENSATOR_SECTION, shuntsNonLinear()); mappers.put(DataframeElementType.LINEAR_SHUNT_COMPENSATOR_SECTION, linearShuntsSections()); mappers.put(DataframeElementType.DANGLING_LINE, danglingLines()); + mappers.put(DataframeElementType.DANGLING_LINE_GENERATION, danglingLinesGeneration()); mappers.put(DataframeElementType.TIE_LINE, tieLines()); mappers.put(DataframeElementType.LCC_CONVERTER_STATION, lccs()); mappers.put(DataframeElementType.VSC_CONVERTER_STATION, vscs()); @@ -653,6 +654,25 @@ static NetworkDataframeMapper danglingLines() { .build(); } + static NetworkDataframeMapper danglingLinesGeneration() { + return NetworkDataframeMapperBuilder.ofStream(network -> network.getDanglingLineStream().filter(dl -> Optional.ofNullable(dl.getGeneration()).isPresent()), + getOrThrow(Network::getDanglingLine, "Dangling line with generation")) + .stringsIndex("id", DanglingLine::getId) + .doubles("min_p", (dl, context) -> perUnitPQ(context, dl.getGeneration().getMinP()), + (dl, minP, context) -> dl.getGeneration().setMinP(unPerUnitPQ(context, minP))) + .doubles("max_p", (dl, context) -> perUnitPQ(context, dl.getGeneration().getMaxP()), + (dl, maxP, context) -> dl.getGeneration().setMaxP(unPerUnitPQ(context, maxP))) + .doubles("target_p", (dl, context) -> perUnitPQ(context, dl.getGeneration().getTargetP()), + (dl, targetP, context) -> dl.getGeneration().setTargetP(unPerUnitPQ(context, targetP))) + .doubles("target_q", (dl, context) -> perUnitPQ(context, dl.getGeneration().getTargetQ()), + (dl, targetQ, context) -> dl.getGeneration().setTargetQ(unPerUnitPQ(context, targetQ))) + .doubles("target_v", (dl, context) -> perUnitV(context, dl.getGeneration().getTargetV(), dl.getTerminal()), + (dl, targetV, context) -> dl.getGeneration().setTargetV(unPerUnitV(context, targetV, dl.getTerminal()))) + .booleans("voltage_regulator_on", dl -> dl.getGeneration().isVoltageRegulationOn(), + (dl, voltageRegulatorOn) -> dl.getGeneration().setVoltageRegulationOn(voltageRegulatorOn)) + .build(); + } + static NetworkDataframeMapper tieLines() { return NetworkDataframeMapperBuilder.ofStream(Network::getTieLineStream, getOrThrow(Network::getTieLine, "Tie line")) .stringsIndex("id", TieLine::getId) @@ -1350,6 +1370,5 @@ private static Stream> areaBoundariesData(Network netwo .flatMap(area -> area.getAreaBoundaryStream() .map(areaBoundary -> Pair.of(area, areaBoundary))); } - } diff --git a/java/src/main/java/com/powsybl/dataframe/network/adders/DanglingLineDataframeAdder.java b/java/src/main/java/com/powsybl/dataframe/network/adders/DanglingLineDataframeAdder.java index 04d6669c9..649b4275b 100644 --- a/java/src/main/java/com/powsybl/dataframe/network/adders/DanglingLineDataframeAdder.java +++ b/java/src/main/java/com/powsybl/dataframe/network/adders/DanglingLineDataframeAdder.java @@ -7,20 +7,24 @@ */ package com.powsybl.dataframe.network.adders; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.report.ReportNode; import com.powsybl.dataframe.SeriesMetadata; 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.modification.topology.CreateFeederBay; +import com.powsybl.iidm.modification.topology.CreateFeederBayBuilder; import com.powsybl.iidm.network.DanglingLineAdder; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.iidm.network.extensions.ConnectablePosition; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import static com.powsybl.dataframe.network.adders.NetworkUtils.getVoltageLevelOrThrowWithBusOrBusbarSectionId; +import static com.powsybl.dataframe.network.adders.SeriesUtils.applyBooleanIfPresent; import static com.powsybl.dataframe.network.adders.SeriesUtils.applyIfPresent; /** @@ -28,7 +32,7 @@ * @author Etienne Lesot {@literal } * @author Sylvain Leclerc {@literal } */ -public class DanglingLineDataframeAdder extends AbstractSimpleAdder { +public class DanglingLineDataframeAdder implements NetworkElementAdder { private static final List METADATA = List.of( SeriesMetadata.stringIndex("id"), @@ -46,9 +50,19 @@ public class DanglingLineDataframeAdder extends AbstractSimpleAdder { SeriesMetadata.strings("pairing_key") ); + private static final List GENERATION_METADATA = List.of( + SeriesMetadata.stringIndex("id"), + SeriesMetadata.doubles("min_p"), + SeriesMetadata.doubles("max_p"), + SeriesMetadata.doubles("target_p"), + SeriesMetadata.doubles("target_q"), + SeriesMetadata.doubles("target_v"), + SeriesMetadata.booleans("voltage_regulator_on") + ); + @Override public List> getMetadata() { - return Collections.singletonList(METADATA); + return List.of(METADATA, GENERATION_METADATA); } private static class DanglingLineSeries extends InjectionSeries { @@ -63,7 +77,15 @@ private static class DanglingLineSeries extends InjectionSeries { private final StringSeries busOrBusbarSections; private final StringSeries pairingKey; - DanglingLineSeries(UpdatingDataframe dataframe) { + private final Map generationIndexes; + private final DoubleSeries minP; + private final DoubleSeries maxP; + private final DoubleSeries targetP; + private final DoubleSeries targetQ; + private final DoubleSeries targetV; + private final IntSeries voltageRegulatorOn; + + DanglingLineSeries(UpdatingDataframe dataframe, UpdatingDataframe generationDataframe) { super(dataframe); this.voltageLevels = dataframe.getStrings("voltage_level_id"); this.p0 = dataframe.getDoubles("p0"); @@ -74,6 +96,24 @@ private static class DanglingLineSeries extends InjectionSeries { this.b = dataframe.getDoubles("b"); this.busOrBusbarSections = dataframe.getStrings("bus_or_busbar_section_id"); this.pairingKey = dataframe.getStrings("pairing_key"); + + if (generationDataframe != null && generationDataframe.getRowCount() > 0) { + this.minP = generationDataframe.getDoubles("min_p"); + this.maxP = generationDataframe.getDoubles("max_p"); + this.targetP = generationDataframe.getDoubles("target_p"); + this.targetQ = generationDataframe.getDoubles("target_q"); + this.targetV = generationDataframe.getDoubles("target_v"); + this.voltageRegulatorOn = generationDataframe.getInts("voltage_regulator_on"); + this.generationIndexes = getGenerationIndexes(generationDataframe); + } else { + this.minP = null; + this.maxP = null; + this.targetP = null; + this.targetQ = null; + this.targetV = null; + this.voltageRegulatorOn = null; + this.generationIndexes = null; + } } Optional createAdder(Network network, int row, boolean throwException) { @@ -89,21 +129,91 @@ Optional createAdder(Network network, int row, boolean throwE applyIfPresent(g, row, adder::setG); applyIfPresent(b, row, adder::setB); applyIfPresent(pairingKey, row, adder::setPairingKey); + addGenerationIfPresent(adder, row); return Optional.of(adder); } else { return Optional.empty(); } } + + private void addGenerationIfPresent(DanglingLineAdder adder, int row) { + if (generationIndexes == null) { + return; + } + String id = ids.get(row); + Integer generationRow = generationIndexes.get(id); + if (generationRow != null) { + DanglingLineAdder.GenerationAdder genAdder = adder.newGeneration(); + applyIfPresent(minP, generationRow, genAdder::setMinP); + applyIfPresent(maxP, generationRow, genAdder::setMaxP); + applyIfPresent(targetP, generationRow, genAdder::setTargetP); + applyIfPresent(targetQ, generationRow, genAdder::setTargetQ); + applyIfPresent(targetV, generationRow, genAdder::setTargetV); + applyBooleanIfPresent(voltageRegulatorOn, generationRow, genAdder::setVoltageRegulationOn); + genAdder.add(); + } + } + + /** + * Mapping shunt ID --> index of line in dataframe + */ + private static Map getGenerationIndexes(UpdatingDataframe generationDf) { + StringSeries ids = generationDf.getStrings("id"); + if (ids == null) { + throw new PowsyblException("Dangling line generation dataframe: id is not set"); + } + Map indexes = new HashMap<>(); + for (int generationIndex = 0; generationIndex < generationDf.getRowCount(); generationIndex++) { + String danglingLineId = ids.get(generationIndex); + indexes.put(danglingLineId, generationIndex); + } + return indexes; + } + + void create(Network network, int row, boolean throwException) { + Optional adder = createAdder(network, row, throwException); + adder.ifPresent(DanglingLineAdder::add); + } + + void createWithBay(Network network, int row, UpdatingDataframe primaryDataframe, boolean throwException, ReportNode reportNode) { + Optional adder = createAdder(network, row, throwException); + adder.ifPresent(presentAdder -> addWithBay(network, row, primaryDataframe, presentAdder, throwException, reportNode)); + } + + void addWithBay(Network network, int row, UpdatingDataframe dataframe, DanglingLineAdder adder, boolean throwException, ReportNode reportNode) { + String busOrBusbarSectionId = busOrBusbarSections.get(row); + OptionalInt injectionPositionOrder = dataframe.getIntValue("position_order", row); + ConnectablePosition.Direction direction = ConnectablePosition.Direction.valueOf(dataframe.getStringValue("direction", row).orElse("BOTTOM")); + CreateFeederBayBuilder builder = new CreateFeederBayBuilder() + .withInjectionAdder(adder) + .withBusOrBusbarSectionId(busOrBusbarSectionId) + .withInjectionDirection(direction); + if (injectionPositionOrder.isPresent()) { + builder.withInjectionPositionOrder(injectionPositionOrder.getAsInt()); + } + CreateFeederBay modification = builder.build(); + modification.apply(network, throwException, reportNode == null ? ReportNode.NO_OP : reportNode); + } } @Override - public void addElements(Network network, UpdatingDataframe dataframe, AdditionStrategy additionStrategy, boolean throwException, ReportNode reportNode) { - DanglingLineSeries series = new DanglingLineSeries(dataframe); + public void addElements(Network network, List dataframes) { + UpdatingDataframe dataframe = dataframes.get(0); + UpdatingDataframe generationDataframe = dataframes.get(1); + DanglingLineSeries series = new DanglingLineSeries(dataframe, generationDataframe); for (int row = 0; row < dataframe.getRowCount(); row++) { - Optional adder = series.createAdder(network, row, throwException); - if (adder.isPresent()) { - additionStrategy.add(network, dataframe, adder.get(), row, throwException, reportNode); - } + series.create(network, row, true); } } + + @Override + public void addElementsWithBay(Network network, List dataframes, boolean throwException, ReportNode reportNode) { + UpdatingDataframe dataframe = dataframes.get(0); + UpdatingDataframe generationDataframe = dataframes.get(1); + DanglingLineSeries series = new DanglingLineSeries(dataframe, generationDataframe); + for (int row = 0; row < dataframe.getRowCount(); row++) { + series.createWithBay(network, row, dataframe, true, reportNode); + } + } + } diff --git a/java/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java b/java/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java index 24797b538..01e86cad8 100644 --- a/java/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java +++ b/java/src/main/java/com/powsybl/python/commons/PyPowsyblApiHeader.java @@ -602,6 +602,7 @@ public enum ElementType { NON_LINEAR_SHUNT_COMPENSATOR_SECTION, LINEAR_SHUNT_COMPENSATOR_SECTION, DANGLING_LINE, + DANGLING_LINE_GENERATION, TIE_LINE, LCC_CONVERTER_STATION, VSC_CONVERTER_STATION, diff --git a/java/src/main/java/com/powsybl/python/commons/Util.java b/java/src/main/java/com/powsybl/python/commons/Util.java index 3e0127e2b..d92a7b60d 100644 --- a/java/src/main/java/com/powsybl/python/commons/Util.java +++ b/java/src/main/java/com/powsybl/python/commons/Util.java @@ -194,6 +194,7 @@ public static PyPowsyblApiHeader.ElementType convert(DataframeElementType type) case BATTERY -> PyPowsyblApiHeader.ElementType.BATTERY; case SHUNT_COMPENSATOR -> PyPowsyblApiHeader.ElementType.SHUNT_COMPENSATOR; case DANGLING_LINE -> PyPowsyblApiHeader.ElementType.DANGLING_LINE; + case DANGLING_LINE_GENERATION -> PyPowsyblApiHeader.ElementType.DANGLING_LINE_GENERATION; case TIE_LINE -> PyPowsyblApiHeader.ElementType.TIE_LINE; case LCC_CONVERTER_STATION -> PyPowsyblApiHeader.ElementType.LCC_CONVERTER_STATION; case VSC_CONVERTER_STATION -> PyPowsyblApiHeader.ElementType.VSC_CONVERTER_STATION; @@ -239,6 +240,7 @@ public static DataframeElementType convert(PyPowsyblApiHeader.ElementType type) case BATTERY -> DataframeElementType.BATTERY; case SHUNT_COMPENSATOR -> DataframeElementType.SHUNT_COMPENSATOR; case DANGLING_LINE -> DataframeElementType.DANGLING_LINE; + case DANGLING_LINE_GENERATION -> DataframeElementType.DANGLING_LINE_GENERATION; case TIE_LINE -> DataframeElementType.TIE_LINE; case LCC_CONVERTER_STATION -> DataframeElementType.LCC_CONVERTER_STATION; case VSC_CONVERTER_STATION -> DataframeElementType.VSC_CONVERTER_STATION; diff --git a/java/src/test/java/com/powsybl/dataframe/network/NetworkDataframesTest.java b/java/src/test/java/com/powsybl/dataframe/network/NetworkDataframesTest.java index 0260d38b8..71da98e78 100644 --- a/java/src/test/java/com/powsybl/dataframe/network/NetworkDataframesTest.java +++ b/java/src/test/java/com/powsybl/dataframe/network/NetworkDataframesTest.java @@ -333,9 +333,19 @@ void danglingLines() { .extracting(Series::getName) .containsExactly("id", "name", "r", "x", "g", "b", "p0", "q0", "p", "q", "i", "boundary_p", "boundary_q", "boundary_v_mag", "boundary_v_angle", - "voltage_level_id", "bus_id", "bus_breaker_bus_id", "node", "connected", - "pairing_key", "ucte_xnode_code", "paired", "fictitious", "tie_line_id", - "selected_limits_group"); + "voltage_level_id", "bus_id", "bus_breaker_bus_id", "node", "connected", "pairing_key", + "ucte_xnode_code", "paired", "fictitious", "tie_line_id", "selected_limits_group"); + } + + @Test + void danglingLinesGeneration() { + Network network = EurostagTutorialExample1Factory.create(); + List series = createDataFrame(DANGLING_LINE_GENERATION, network); + + assertThat(series) + .extracting(Series::getName) + .containsExactly("id", "min_p", "max_p", "target_p", "target_q", + "target_v", "voltage_regulator_on"); } @Test 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 ee73938b4..fab18636a 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 @@ -140,10 +140,43 @@ void danglingLine() { addDoubleColumn(dataframe, "b", Math.pow(10, -6) * 4); addDoubleColumn(dataframe, "p0", 102d); addDoubleColumn(dataframe, "q0", 151d); - NetworkElementAdders.addElements(DataframeElementType.DANGLING_LINE, network, singletonList(dataframe)); + var emptyGenerationDataframe = new DefaultUpdatingDataframe(0); + NetworkElementAdders.addElements(DataframeElementType.DANGLING_LINE, network, List.of(dataframe, emptyGenerationDataframe)); assertEquals(2, network.getDanglingLineCount()); } + @Test + void danglingLineWithGeneration() { + var network = DanglingLineNetworkFactory.create(); + var dataframe = new DefaultUpdatingDataframe(1); + addStringColumn(dataframe, "id", "dl2"); + addStringColumn(dataframe, "name", "name-dl2"); + addStringColumn(dataframe, "connectable_bus_id", "BUS"); + addStringColumn(dataframe, "bus_id", "BUS"); + addStringColumn(dataframe, "voltage_level_id", "VL"); + addDoubleColumn(dataframe, "r", 0.6d); + addDoubleColumn(dataframe, "x", 1d); + addDoubleColumn(dataframe, "g", Math.pow(10, -6)); + addDoubleColumn(dataframe, "b", Math.pow(10, -6) * 4); + addDoubleColumn(dataframe, "p0", 102d); + addDoubleColumn(dataframe, "q0", 151d); + + var generationDataframe = new DefaultUpdatingDataframe(1); + addStringColumn(generationDataframe, "id", "dl2"); + addDoubleColumn(generationDataframe, "min_p", 0); + addDoubleColumn(generationDataframe, "max_p", 200d); + addDoubleColumn(generationDataframe, "target_p", 102d); + addDoubleColumn(generationDataframe, "target_q", 151d); + addDoubleColumn(generationDataframe, "target_v", 100d); + addIntColumn(generationDataframe, "voltage_regulator_on", 1); + NetworkElementAdders.addElements(DataframeElementType.DANGLING_LINE, network, List.of(dataframe, generationDataframe)); + + assertEquals(2, network.getDanglingLineCount()); + DanglingLine dl = network.getDanglingLine("dl2"); + assertTrue(Optional.ofNullable(dl.getGeneration()).isPresent()); + assertTrue(dl.getGeneration().isVoltageRegulationOn()); + } + @Test void busbar() { var network = HvdcTestNetwork.createBase(); diff --git a/pypowsybl/_pypowsybl.pyi b/pypowsybl/_pypowsybl.pyi index 9642e21d0..21a135d10 100644 --- a/pypowsybl/_pypowsybl.pyi +++ b/pypowsybl/_pypowsybl.pyi @@ -243,6 +243,7 @@ class ElementType: BUS_FROM_BUS_BREAKER_VIEW: ClassVar[ElementType] = ... BUSBAR_SECTION: ClassVar[ElementType] = ... DANGLING_LINE: ClassVar[ElementType] = ... + DANGLING_LINE_GENERATION: ClassVar[ElementType] = ... TIE_LINE: ClassVar[ElementType] = ... GENERATOR: ClassVar[ElementType] = ... HVDC_LINE: ClassVar[ElementType] = ... diff --git a/pypowsybl/network/impl/network.py b/pypowsybl/network/impl/network.py index dc0aa61d5..413b2dde5 100644 --- a/pypowsybl/network/impl/network.py +++ b/pypowsybl/network/impl/network.py @@ -1321,7 +1321,7 @@ def get_dangling_lines(self, all_attributes: bool = False, attributes: List[str] .. code-block:: python - net = pp.network._create_dangling_lines_network() + net = pp.network.create_dangling_lines_network() net.get_dangling_lines() will output something like: @@ -1335,7 +1335,7 @@ def get_dangling_lines(self, all_attributes: bool = False, attributes: List[str] .. code-block:: python - net = pp.network._create_dangling_lines_network() + net = pp.network.create_dangling_lines_network() net.get_dangling_lines(all_attributes=True) will output something like: @@ -1349,7 +1349,7 @@ def get_dangling_lines(self, all_attributes: bool = False, attributes: List[str] .. code-block:: python - net = pp.network._create_dangling_lines_network() + net = pp.network.create_dangling_lines_network() net.get_dangling_lines(attributes=['p','q','i','voltage_level_id','bus_id','connected']) will output something like: @@ -1363,6 +1363,35 @@ def get_dangling_lines(self, all_attributes: bool = False, attributes: List[str] """ return self.get_elements(ElementType.DANGLING_LINE, all_attributes, attributes, **kwargs) + def get_dangling_lines_generation(self, all_attributes: bool = False, attributes: List[str] = None, + **kwargs: ArrayLike) -> DataFrame: + r""" + Get a dataframe of dangling lines generation part. + + Args: + all_attributes: flag for including all attributes in the dataframe, default is false + attributes: attributes to include in the dataframe. The 2 parameters are mutually exclusive. + If no parameter is specified, the dataframe will include the default attributes. + kwargs: the data to be selected, as named arguments. + + Returns: + A dataframe of dangling lines generation part. + + Notes: + The resulting dataframe, depending on the parameters, will include the following columns: + + - **min_p**: Minimum active power output of the dangling line's generation part + - **max_p**: Maximum active power output of the dangling line's generation part + - **target_p**: Active power target of the generation part + - **target_q**: Reactive power target of the generation part + - **target_v**: Voltage target of the generation part + - **voltage_regulator_on**: ``True`` if the generation part regulates voltage + + This dataframe is indexed by the id of the dangling lines + + """ + return self.get_elements(ElementType.DANGLING_LINE_GENERATION, all_attributes, attributes, **kwargs) + def get_tie_lines(self, all_attributes: bool = False, attributes: List[str] = None, **kwargs: ArrayLike) -> DataFrame: r""" @@ -2853,6 +2882,39 @@ def update_dangling_lines(self, df: DataFrame = None, **kwargs: ArrayLike) -> No """ return self._update_elements(ElementType.DANGLING_LINE, df, **kwargs) + def update_dangling_lines_generation(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: + """ + Update dangling lines generation part with data provided as a :class:`~pandas.DataFrame` or as named arguments. + + Args: + df: the data to be updated, as a dataframe. + kwargs: the data to be updated, as named arguments. + Arguments can be single values or any type of sequence. + In the case of sequences, all arguments must have the same length. + + Notes: + Attributes that can be updated are: + + - `min_p` + - `max_p` + - `target_p` + - `target_q` + - `target_v` + - `voltage_regulator_on` + + See Also: + :meth:`get_dangling_lines_generation` + + Examples: + Some examples using keyword arguments: + + .. code-block:: python + + network.update_dangling_lines_generation(id='DL', voltage_regulator_on=True, target_v=225) + network.update_dangling_lines_generation(id=['DL', 'DL2'], voltage_regulator_on=[True, True], target_v=[225, 400]) + """ + return self._update_elements(ElementType.DANGLING_LINE_GENERATION, df, **kwargs) + def update_vsc_converter_stations(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: """ Update VSC converter stations with data provided as a :class:`~pandas.DataFrame` or as named arguments. @@ -4008,17 +4070,18 @@ def create_batteries(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: """ return self._create_elements(ElementType.BATTERY, [df], **kwargs) - def create_dangling_lines(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: + def create_dangling_lines(self, df: DataFrame = None, generation_df: DataFrame = pd.DataFrame(), **kwargs: ArrayLike) -> None: """ Creates dangling lines. Args: df: Attributes as a dataframe. + generation_df: Attributes of the dangling lines optional generation part, only as a dataframe kwargs: Attributes as keyword arguments. Notes: - Data may be provided as a dataframe or as keyword arguments. + General dangling line data may be provided as a dataframe or as keyword arguments. In the latter case, all arguments must have the same length. Valid attributes are: @@ -4042,6 +4105,17 @@ def create_dangling_lines(self, df: DataFrame = None, **kwargs: ArrayLike) -> No - **pairing-key**: the optional pairing key associated to the dangling line, to be used for creating tie lines. - **ucte-x-node-code**: deprecated, use pairing-key instead. + Dangling line generation information must be provided as a dataframe. + Valid attributes are: + + - **id**: Identifier of the dangling line that contains this generation part + - **min_p**: Minimum active power output of the dangling line's generation part + - **max_p**: Maximum active power output of the dangling line's generation part + - **target_p**: Active power target of the generation part + - **target_q**: Reactive power target of the generation part + - **target_v**: Voltage target of the generation part + - **voltage_regulator_on**: ``True`` if the generation part regulates voltage + Examples: Using keyword arguments: @@ -4060,7 +4134,7 @@ def create_dangling_lines(self, df: DataFrame = None, **kwargs: ArrayLike) -> No warnings.warn(ucte_xnode_code_str + " is deprecated, use pairing_key", DeprecationWarning) kwargs['pairing_key'] = ucte_x_node_code kwargs.pop(ucte_xnode_code_str) - return self._create_elements(ElementType.DANGLING_LINE, [df], **kwargs) + return self._create_elements(ElementType.DANGLING_LINE, [df, generation_df], **kwargs) def create_lcc_converter_stations(self, df: DataFrame = None, **kwargs: ArrayLike) -> None: """ diff --git a/pypowsybl/network/impl/network_element_modification_util.py b/pypowsybl/network/impl/network_element_modification_util.py index 7dc409e89..b2422df7e 100644 --- a/pypowsybl/network/impl/network_element_modification_util.py +++ b/pypowsybl/network/impl/network_element_modification_util.py @@ -297,14 +297,16 @@ def create_generator_bay(network: Network, df: DataFrame = None, raise_exception return _create_feeder_bay(network, [df], ElementType.GENERATOR, raise_exception, reporter, report_node, **kwargs) -def create_dangling_line_bay(network: Network, df: DataFrame = None, raise_exception: bool = True, - reporter: ReportNode = None, report_node: ReportNode = None, **kwargs: ArrayLike) -> None: +def create_dangling_line_bay(network: Network, df: DataFrame = None, generation_df: DataFrame = pd.DataFrame(), + raise_exception: bool = True, reporter: ReportNode = None, report_node: ReportNode = None, + **kwargs: ArrayLike) -> None: """ Creates a dangling line, connects it to the network on a given bus or busbar section and creates the associated topology. Args: network: the network to which we want to add the dangling line df: Attributes as a dataframe. + generation_df: Optional dangling lines' generation part, only as a dataframe raise_exception: optionally, whether the calculation should throw exceptions. In any case, errors will be logged. Default is True. reporter: deprecated, use report_node instead @@ -318,7 +320,7 @@ def create_dangling_line_bay(network: Network, df: DataFrame = None, raise_excep busbar section with an open disconnector. If the voltage level is bus/breaker, the dangling line is just connected to the bus. - Valid attributes are: + Valid attributes for dangling line dataframe or named arguments are: - **id**: the identifier of the new line - **name**: an optional human-readable name @@ -332,8 +334,18 @@ def create_dangling_line_bay(network: Network, df: DataFrame = None, raise_excep - **position_order**: in node/breaker, the order of the dangling line, will fill the ConnectablePosition extension - **direction**: optionally, in node/breaker, the direction of the dangling line, will fill the ConnectablePosition extension, default is BOTTOM. + Dangling line generation information must be provided as a dataframe. + Valid attributes are: + + - **id**: Identifier of the dangling line that contains this generation part + - **min_p**: Minimum active power output of the dangling line's generation part + - **max_p**: Maximum active power output of the dangling line's generation part + - **target_p**: Active power target of the generation part + - **target_q**: Reactive power target of the generation part + - **target_v**: Voltage target of the generation part + - **voltage_regulator_on**: ``True`` if the generation part regulates voltage """ - return _create_feeder_bay(network, [df], ElementType.DANGLING_LINE, raise_exception, reporter, report_node, + return _create_feeder_bay(network, [df, generation_df], ElementType.DANGLING_LINE, raise_exception, reporter, report_node, **kwargs) diff --git a/tests/test_network.py b/tests/test_network.py index e489b9d1a..31151db37 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -6,6 +6,7 @@ # import copy import datetime +import math import os import pathlib import re @@ -13,7 +14,6 @@ import unittest import io import zipfile -from netrc import netrc from os.path import exists import matplotlib.pyplot as plt @@ -1227,6 +1227,34 @@ def test_dangling_lines(): pd.testing.assert_frame_equal(expected, dangling_lines, check_dtype=False, atol=1e-2) +def test_dangling_line_generation(): + n = util.create_dangling_lines_network() + df = n.get_dangling_lines_generation() + assert df.empty + + wrong_generation_df = pd.DataFrame(index=pd.Series(name='id', data=['DL2_wrong']), columns=["target_v", "voltage_regulator_on"], + data=[[225, True]]) + with pytest.raises(PyPowsyblError) as context: + n.create_dangling_lines(generation_df=wrong_generation_df, id='DL2_wrong', voltage_level_id='VL', bus_id='BUS', + p0=100, q0=100, r=0, x=0, g=0, b=0) + assert "invalid value (NaN) for active power setpoint" in str(context) + + generation_df = pd.DataFrame(index=pd.Series(name='id', data=['DL2']), + columns=["min_p", "max_p", "target_p", "target_v", "voltage_regulator_on"], + data= [[0, 100, 100, 225, True]]) + n.create_dangling_lines(generation_df=generation_df, id='DL2', voltage_level_id='VL', bus_id='BUS', + p0=100, q0=100, r=0, x=0, g=0, b=0) + df2 = n.get_dangling_lines_generation() + assert df2['voltage_regulator_on']['DL2'] + assert math.isnan(df2['target_q']['DL2']) + + n.update_dangling_lines_generation(pd.DataFrame(index=['DL2'], columns=['target_q', 'voltage_regulator_on'], + data=[[100, False]])) + df3 = n.get_dangling_lines_generation() + assert not df3['voltage_regulator_on']['DL2'] + assert df3['target_q']['DL2']==100 + + def test_batteries(): n = util.create_battery_network() df = n.get_batteries(all_attributes=True)