From 2141f90d68fccb0dc044321db5d06c30eb45c6f2 Mon Sep 17 00:00:00 2001 From: Gautier Bureau Date: Wed, 29 Oct 2025 14:02:25 +0100 Subject: [PATCH 1/7] Add synchronous generator extension. Signed-off-by: Gautier Bureau --- ...eneratorConnectionLevelDataframeAdder.java | 68 +++++++++++ ...ratorConnectionLevelDataframeProvider.java | 84 ++++++++++++++ ...izedGeneratorPropertiesDataframeAdder.java | 72 ++++++++++++ ...dGeneratorPropertiesDataframeProvider.java | 85 ++++++++++++++ ...nousGeneratorPropertiesDataframeAdder.java | 108 ++++++++++++++++++ ...sGeneratorPropertiesDataframeProvider.java | 103 +++++++++++++++++ .../adders/NetworkElementAddersTest.java | 80 +++++++++++++ tests/test_network_extensions.py | 88 ++++++++++++++ 8 files changed, 688 insertions(+) create mode 100644 java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeAdder.java create mode 100644 java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeProvider.java create mode 100644 java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java create mode 100644 java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java create mode 100644 java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeAdder.java create mode 100644 java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeProvider.java diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeAdder.java new file mode 100644 index 0000000000..38e27eaf19 --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeAdder.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2022, 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.extensions; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.dataframe.SeriesMetadata; +import com.powsybl.dataframe.network.adders.AbstractSimpleAdder; +import com.powsybl.dataframe.network.adders.SeriesUtils; +import com.powsybl.dataframe.update.StringSeries; +import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.GeneratorConnectionLevelAdder; +import com.powsybl.iidm.network.extensions.GeneratorConnectionLevelType; + +import java.util.Collections; +import java.util.List; + +/** + * @author Gautier Bureau {@literal } + */ +public class GeneratorConnectionLevelDataframeAdder extends AbstractSimpleAdder { + + private static final List METADATA = List.of( + SeriesMetadata.stringIndex("id"), + SeriesMetadata.strings("level") + ); + + @Override + public List> getMetadata() { + return Collections.singletonList(METADATA); + } + + private static class GeneratorConnectionLevelSeries { + + private final StringSeries id; + private final StringSeries level; + + GeneratorConnectionLevelSeries(UpdatingDataframe dataframe) { + this.id = dataframe.getStrings("id"); + this.level = dataframe.getStrings("level"); + } + + void create(Network network, int row) { + String generatorId = this.id.get(row); + Generator g = network.getGenerator(generatorId); + if (g == null) { + throw new PowsyblException("Invalid generator id : could not find " + generatorId); + } + var adder = g.newExtension(GeneratorConnectionLevelAdder.class); + SeriesUtils.applyIfPresent(level, row, l -> adder.withLevel(GeneratorConnectionLevelType.valueOf(l))); + adder.add(); + } + } + + @Override + public void addElements(Network network, UpdatingDataframe dataframe) { + GeneratorConnectionLevelSeries series = new GeneratorConnectionLevelSeries(dataframe); + for (int row = 0; row < dataframe.getRowCount(); row++) { + series.create(network, row); + } + } +} diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeProvider.java new file mode 100644 index 0000000000..063e45ed58 --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeProvider.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2021-2022, 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.extensions; + +import com.google.auto.service.AutoService; +import com.powsybl.commons.PowsyblException; +import com.powsybl.dataframe.network.ExtensionInformation; +import com.powsybl.dataframe.network.NetworkDataframeMapper; +import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder; +import com.powsybl.dataframe.network.adders.NetworkElementAdder; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.GeneratorConnectionLevel; +import com.powsybl.iidm.network.extensions.GeneratorConnectionLevelType; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * @author Gautier Bureau {@literal } + */ +@AutoService(NetworkExtensionDataframeProvider.class) +public class GeneratorConnectionLevelDataframeProvider extends AbstractSingleDataframeNetworkExtension { + + @Override + public String getExtensionName() { + return GeneratorConnectionLevel.NAME; + } + + @Override + public ExtensionInformation getExtensionInformation() { + return new ExtensionInformation(GeneratorConnectionLevel.NAME, + "Provides information about the characteristics of a Synchronized generator", + "index : id (str), " + + "level (str)"); + } + + private Stream itemsStream(Network network) { + return network.getGeneratorStream() + .map(g -> (GeneratorConnectionLevel) g.getExtension(GeneratorConnectionLevel.class)) + .filter(Objects::nonNull); + } + + private GeneratorConnectionLevel getOrThrow(Network network, String id) { + Generator gen = network.getGenerator(id); + if (gen == null) { + throw new PowsyblException("Generator '" + id + "' not found"); + } + GeneratorConnectionLevel sgp = gen.getExtension(GeneratorConnectionLevel.class); + if (sgp == null) { + throw new PowsyblException("Generator '" + id + "' has no GeneratorConnectionLevel extension"); + } + return sgp; + } + + @Override + public NetworkDataframeMapper createMapper() { + return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, this::getOrThrow) + .stringsIndex("id", ext -> ((Identifiable) ext.getExtendable()).getId()) + .enums("level", GeneratorConnectionLevelType.class, GeneratorConnectionLevel::getLevel, GeneratorConnectionLevel::setLevel) + .build(); + } + + @Override + public void removeExtensions(Network network, List ids) { + ids.stream().filter(Objects::nonNull) + .map(network::getGenerator) + .filter(Objects::nonNull) + .forEach(g -> g.removeExtension(GeneratorConnectionLevel.class)); + } + + @Override + public NetworkElementAdder createAdder() { + return new GeneratorConnectionLevelDataframeAdder(); + } + +} diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java new file mode 100644 index 0000000000..08d5a6d44a --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2022, 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.extensions; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.dataframe.SeriesMetadata; +import com.powsybl.dataframe.network.adders.AbstractSimpleAdder; +import com.powsybl.dataframe.network.adders.SeriesUtils; +import com.powsybl.dataframe.update.IntSeries; +import com.powsybl.dataframe.update.StringSeries; +import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.SynchronizedGeneratorPropertiesAdder; + +import java.util.Collections; +import java.util.List; + +/** + * @author Gautier Bureau {@literal } + */ +public class SynchronizedGeneratorPropertiesDataframeAdder extends AbstractSimpleAdder { + + private static final List METADATA = List.of( + SeriesMetadata.stringIndex("id"), + SeriesMetadata.strings("type"), + SeriesMetadata.booleans("rpcl2") + ); + + @Override + public List> getMetadata() { + return Collections.singletonList(METADATA); + } + + private static class SynchronizedGeneratorPropertiesSeries { + + private final StringSeries id; + private final StringSeries type; + private final IntSeries rpcl2; + + SynchronizedGeneratorPropertiesSeries(UpdatingDataframe dataframe) { + this.id = dataframe.getStrings("id"); + this.type = dataframe.getStrings("type"); + this.rpcl2 = dataframe.getInts("rpcl2"); + } + + void create(Network network, int row) { + String generatorId = this.id.get(row); + Generator g = network.getGenerator(generatorId); + if (g == null) { + throw new PowsyblException("Invalid generator id : could not find " + generatorId); + } + var adder = g.newExtension(SynchronizedGeneratorPropertiesAdder.class); + SeriesUtils.applyIfPresent(type, row, adder::withType); + SeriesUtils.applyBooleanIfPresent(rpcl2, row, adder::withRpcl2); + adder.add(); + } + } + + @Override + public void addElements(Network network, UpdatingDataframe dataframe) { + SynchronizedGeneratorPropertiesSeries series = new SynchronizedGeneratorPropertiesSeries(dataframe); + for (int row = 0; row < dataframe.getRowCount(); row++) { + series.create(network, row); + } + } +} diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java new file mode 100644 index 0000000000..ae8bdfbb0b --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2021-2022, 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.extensions; + +import com.google.auto.service.AutoService; +import com.powsybl.commons.PowsyblException; +import com.powsybl.dataframe.network.ExtensionInformation; +import com.powsybl.dataframe.network.NetworkDataframeMapper; +import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder; +import com.powsybl.dataframe.network.adders.NetworkElementAdder; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.SynchronizedGeneratorProperties; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * @author Gautier Bureau {@literal } + */ +@AutoService(NetworkExtensionDataframeProvider.class) +public class SynchronizedGeneratorPropertiesDataframeProvider extends AbstractSingleDataframeNetworkExtension { + + @Override + public String getExtensionName() { + return SynchronizedGeneratorProperties.NAME; + } + + @Override + public ExtensionInformation getExtensionInformation() { + return new ExtensionInformation(SynchronizedGeneratorProperties.NAME, + "Provides information about the characteristics of a Synchronized generator", + "index : id (str), " + + "type (str), " + + "rpcl2 (bool)"); + } + + private Stream itemsStream(Network network) { + return network.getGeneratorStream() + .map(g -> (SynchronizedGeneratorProperties) g.getExtension(SynchronizedGeneratorProperties.class)) + .filter(Objects::nonNull); + } + + private SynchronizedGeneratorProperties getOrThrow(Network network, String id) { + Generator gen = network.getGenerator(id); + if (gen == null) { + throw new PowsyblException("Generator '" + id + "' not found"); + } + SynchronizedGeneratorProperties sgp = gen.getExtension(SynchronizedGeneratorProperties.class); + if (sgp == null) { + throw new PowsyblException("Generator '" + id + "' has no SynchronizedGeneratorProperties extension"); + } + return sgp; + } + + @Override + public NetworkDataframeMapper createMapper() { + return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, this::getOrThrow) + .stringsIndex("id", ext -> ((Identifiable) ext.getExtendable()).getId()) + .strings("type", sgp -> String.valueOf(sgp.getType()), (sgp, type) -> sgp.setType(type)) + .booleans("rpcl2", SynchronizedGeneratorProperties::getRpcl2, SynchronizedGeneratorProperties::setRpcl2) + .build(); + } + + @Override + public void removeExtensions(Network network, List ids) { + ids.stream().filter(Objects::nonNull) + .map(network::getGenerator) + .filter(Objects::nonNull) + .forEach(g -> g.removeExtension(SynchronizedGeneratorProperties.class)); + } + + @Override + public NetworkElementAdder createAdder() { + return new SynchronizedGeneratorPropertiesDataframeAdder(); + } + +} diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeAdder.java new file mode 100644 index 0000000000..580430fed7 --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeAdder.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2022, 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.extensions; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.dataframe.SeriesMetadata; +import com.powsybl.dataframe.network.adders.AbstractSimpleAdder; +import com.powsybl.dataframe.network.adders.SeriesUtils; +import com.powsybl.dataframe.update.IntSeries; +import com.powsybl.dataframe.update.StringSeries; +import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.SynchronousGeneratorPropertiesAdder; + +import java.util.Collections; +import java.util.List; + +/** + * @author Gautier Bureau {@literal } + */ +public class SynchronousGeneratorPropertiesDataframeAdder extends AbstractSimpleAdder { + + private static final List METADATA = List.of( + SeriesMetadata.stringIndex("id"), + SeriesMetadata.ints("numberOfWindings"), + SeriesMetadata.strings("governor"), + SeriesMetadata.strings("voltageRegulator"), + SeriesMetadata.strings("pss"), + SeriesMetadata.booleans("auxiliaries"), + SeriesMetadata.booleans("internalTransformer"), + SeriesMetadata.booleans("rpcl"), + SeriesMetadata.booleans("rpcl2"), + SeriesMetadata.strings("uva"), + SeriesMetadata.booleans("fictitious"), + SeriesMetadata.booleans("qlim") + ); + + @Override + public List> getMetadata() { + return Collections.singletonList(METADATA); + } + + private static class SynchronousGeneratorPropertiesSeries { + + private final StringSeries id; + private final IntSeries numberOfWindings; + private final StringSeries governor; + private final StringSeries voltageRegulator; + private final StringSeries pss; + private final IntSeries auxiliaries; + private final IntSeries internalTransformer; + private final IntSeries rpcl; + private final IntSeries rpcl2; + private final StringSeries uva; + private final IntSeries fictitious; + private final IntSeries qlim; + + SynchronousGeneratorPropertiesSeries(UpdatingDataframe dataframe) { + this.id = dataframe.getStrings("id"); + this.numberOfWindings = dataframe.getInts("numberOfWindings"); + this.governor = dataframe.getStrings("governor"); + this.voltageRegulator = dataframe.getStrings("voltageRegulator"); + this.pss = dataframe.getStrings("pss"); + this.auxiliaries = dataframe.getInts("auxiliaries"); + this.internalTransformer = dataframe.getInts("internalTransformer"); + this.rpcl = dataframe.getInts("rpcl"); + this.rpcl2 = dataframe.getInts("rpcl2"); + this.uva = dataframe.getStrings("uva"); + this.fictitious = dataframe.getInts("fictitious"); + this.qlim = dataframe.getInts("qlim"); + } + + void create(Network network, int row) { + String generatorId = this.id.get(row); + Generator g = network.getGenerator(generatorId); + if (g == null) { + throw new PowsyblException("Invalid generator id : could not find " + generatorId); + } + var adder = g.newExtension(SynchronousGeneratorPropertiesAdder.class); + SeriesUtils.applyIfPresent(numberOfWindings, row, adder::withNumberOfWindings); + SeriesUtils.applyIfPresent(governor, row, adder::withGovernor); + SeriesUtils.applyIfPresent(voltageRegulator, row, adder::withVoltageRegulator); + SeriesUtils.applyIfPresent(pss, row, adder::withPss); + SeriesUtils.applyBooleanIfPresent(auxiliaries, row, adder::withAuxiliaries); + SeriesUtils.applyBooleanIfPresent(internalTransformer, row, adder::withInternalTransformer); + SeriesUtils.applyBooleanIfPresent(rpcl, row, adder::withRpcl); + SeriesUtils.applyBooleanIfPresent(rpcl2, row, adder::withRpcl2); + SeriesUtils.applyIfPresent(uva, row, adder::withUva); + SeriesUtils.applyBooleanIfPresent(fictitious, row, adder::withFictitious); + SeriesUtils.applyBooleanIfPresent(qlim, row, adder::withQlim); + adder.add(); + } + } + + @Override + public void addElements(Network network, UpdatingDataframe dataframe) { + SynchronousGeneratorPropertiesSeries series = new SynchronousGeneratorPropertiesSeries(dataframe); + for (int row = 0; row < dataframe.getRowCount(); row++) { + series.create(network, row); + } + } +} diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeProvider.java new file mode 100644 index 0000000000..83ae97fed7 --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeProvider.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2021-2022, 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.extensions; + +import com.google.auto.service.AutoService; +import com.powsybl.commons.PowsyblException; +import com.powsybl.dataframe.network.ExtensionInformation; +import com.powsybl.dataframe.network.NetworkDataframeMapper; +import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder; +import com.powsybl.dataframe.network.adders.NetworkElementAdder; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.SynchronousGeneratorProperties; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * @author Gautier Bureau {@literal } + */ +@AutoService(NetworkExtensionDataframeProvider.class) +public class SynchronousGeneratorPropertiesDataframeProvider extends AbstractSingleDataframeNetworkExtension { + + @Override + public String getExtensionName() { + return SynchronousGeneratorProperties.NAME; + } + + @Override + public ExtensionInformation getExtensionInformation() { + return new ExtensionInformation(SynchronousGeneratorProperties.NAME, + "Provides information about the characteristics of a synchronous generator", + "index : id (str), " + + "numberOfWindings (int), " + + "governor (str), " + + "voltageRegulator (str), " + + "pss (str), " + + "auxiliaries (bool), " + + "internalTransformer (bool), " + + "rpcl (bool), " + + "rpcl2 (bool), " + + "uva (str), " + + "fictitious (bool), " + + "qlim (bool)"); + } + + private Stream itemsStream(Network network) { + return network.getGeneratorStream() + .map(g -> (SynchronousGeneratorProperties) g.getExtension(SynchronousGeneratorProperties.class)) + .filter(Objects::nonNull); + } + + private SynchronousGeneratorProperties getOrThrow(Network network, String id) { + Generator gen = network.getGenerator(id); + if (gen == null) { + throw new PowsyblException("Generator '" + id + "' not found"); + } + SynchronousGeneratorProperties sgp = gen.getExtension(SynchronousGeneratorProperties.class); + if (sgp == null) { + throw new PowsyblException("Generator '" + id + "' has no SynchronousGeneratorProperties extension"); + } + return sgp; + } + + @Override + public NetworkDataframeMapper createMapper() { + return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, this::getOrThrow) + .stringsIndex("id", ext -> ((Identifiable) ext.getExtendable()).getId()) + .ints("numberOfWindings", SynchronousGeneratorProperties::getNumberOfWindings, SynchronousGeneratorProperties::setNumberOfWindings) + .strings("governor", sgp -> String.valueOf(sgp.getGovernor()), (sgp, governor) -> sgp.setGovernor(governor)) + .strings("voltageRegulator", sgp -> String.valueOf(sgp.getVoltageRegulator()), (sgp, voltageRegulator) -> sgp.setVoltageRegulator(voltageRegulator)) + .strings("pss", sgp -> String.valueOf(sgp.getPss()), (sgp, pss) -> sgp.setPss(pss)) + .booleans("auxiliaries", SynchronousGeneratorProperties::getAuxiliaries, SynchronousGeneratorProperties::setAuxiliaries) + .booleans("internalTransformer", SynchronousGeneratorProperties::getInternalTransformer, SynchronousGeneratorProperties::setInternalTransformer) + .booleans("rpcl", SynchronousGeneratorProperties::getRpcl, SynchronousGeneratorProperties::setRpcl) + .booleans("rpcl2", SynchronousGeneratorProperties::getRpcl2, SynchronousGeneratorProperties::setRpcl2) + .strings("uva", sgp -> String.valueOf(sgp.getUva()), (sgp, uva) -> sgp.setUva(uva)) + .booleans("fictitious", SynchronousGeneratorProperties::getFictitious, SynchronousGeneratorProperties::setFictitious) + .booleans("qlim", SynchronousGeneratorProperties::getQlim, SynchronousGeneratorProperties::setQlim) + .build(); + } + + @Override + public void removeExtensions(Network network, List ids) { + ids.stream().filter(Objects::nonNull) + .map(network::getGenerator) + .filter(Objects::nonNull) + .forEach(g -> g.removeExtension(SynchronousGeneratorProperties.class)); + } + + @Override + public NetworkElementAdder createAdder() { + return new SynchronousGeneratorPropertiesDataframeAdder(); + } + +} diff --git a/java/pypowsybl/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java b/java/pypowsybl/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java index 2cfe13b88e..a3eab133a0 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java @@ -746,4 +746,84 @@ void dcGround() { NetworkElementAdders.addElements(DataframeElementType.DC_GROUND, network.getSubnetwork("VscAsymmetricalMonopole"), singletonList(dataframe)); assertEquals(3, network.getDcGroundCount()); } + + @Test + void synchronousGeneratorPropertiesExtension() { + var network = EurostagTutorialExample1Factory.create(); + String genId = "GEN"; + SynchronousGeneratorProperties extension = network.getGenerator(genId).getExtension(SynchronousGeneratorProperties.class); + assertNull(extension); + int numberOfWindings = 3; + String governor = "Proportional"; + String voltageRegulator = "Proportional"; + String pss = ""; + String uva = ""; + + DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); + addStringColumn(dataframe, "id", genId); + addIntColumn(dataframe, "numberOfWindings", numberOfWindings); + addStringColumn(dataframe, "governor", governor); + addStringColumn(dataframe, "voltageRegulator", voltageRegulator); + addStringColumn(dataframe, "pss", pss); + addIntColumn(dataframe, "auxiliaries", 1); + addIntColumn(dataframe, "internalTransformer", 0); + addIntColumn(dataframe, "rpcl", 1); + addIntColumn(dataframe, "rpcl2", 0); + addStringColumn(dataframe, "uva", uva); + addIntColumn(dataframe, "fictitious", 0); + addIntColumn(dataframe, "qlim", 0); + NetworkElementAdders.addExtensions("synchronousGeneratorProperties", network, singletonList(dataframe)); + + extension = network.getGenerator(genId).getExtension(SynchronousGeneratorProperties.class); + assertNotNull(extension); + assertEquals(numberOfWindings, extension.getNumberOfWindings()); + assertEquals(governor, extension.getGovernor()); + assertEquals(voltageRegulator, extension.getVoltageRegulator()); + assertEquals(pss, extension.getPss()); + assertTrue(extension.getAuxiliaries()); + assertFalse(extension.getInternalTransformer()); + assertTrue(extension.getRpcl()); + assertFalse(extension.getRpcl2()); + assertEquals(uva, extension.getUva()); + assertFalse(extension.getFictitious()); + assertFalse(extension.getQlim()); + } + + @Test + void synchronizedGeneratorPropertiesExtension() { + var network = EurostagTutorialExample1Factory.create(); + String genId = "GEN"; + SynchronizedGeneratorProperties extension = network.getGenerator(genId).getExtension(SynchronizedGeneratorProperties.class); + assertNull(extension); + String type = "PV"; + + DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); + addStringColumn(dataframe, "id", genId); + addStringColumn(dataframe, "type", type); + addIntColumn(dataframe, "rpcl2", 0); + NetworkElementAdders.addExtensions("synchronizedGeneratorProperties", network, singletonList(dataframe)); + + extension = network.getGenerator(genId).getExtension(SynchronizedGeneratorProperties.class); + assertNotNull(extension); + assertEquals(type, extension.getType()); + assertFalse(extension.getRpcl2()); + } + + @Test + void generatorConnectionLevelExtension() { + var network = EurostagTutorialExample1Factory.create(); + String genId = "GEN"; + GeneratorConnectionLevel extension = network.getGenerator(genId).getExtension(GeneratorConnectionLevel.class); + assertNull(extension); + String level = "TSO"; + + DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); + addStringColumn(dataframe, "id", genId); + addStringColumn(dataframe, "level", level); + NetworkElementAdders.addExtensions("generatorConnectionLevel", network, singletonList(dataframe)); + + extension = network.getGenerator(genId).getExtension(GeneratorConnectionLevel.class); + assertNotNull(extension); + assertEquals(GeneratorConnectionLevelType.valueOf(level), extension.getLevel()); + } } diff --git a/tests/test_network_extensions.py b/tests/test_network_extensions.py index f163238d74..f7fdf6ede8 100644 --- a/tests/test_network_extensions.py +++ b/tests/test_network_extensions.py @@ -588,6 +588,91 @@ def test_batteries_voltage_regulation(): network.remove_extensions('voltageRegulation', ['BAT']) assert network.get_extensions('voltageRegulation').empty +def test_synchronous_generator_properties(): + n = pn.create_four_substations_node_breaker_network() + extension_name = 'synchronousGeneratorProperties' + element_id = 'GH1' + + extensions = n.get_extensions(extension_name) + assert extensions.empty + + n.create_extensions(extension_name, id=element_id, numberOfWindings=3, + governor="Proportional", voltageRegulator="Proportional", pss="", + auxiliaries=True, internalTransformer=False, rpcl=True, rpcl2=False, + uva="", fictitious=False, qlim=False) + e = n.get_extensions(extension_name).loc[element_id] + assert e.numberOfWindings == 3 + assert e.governor == "Proportional" + assert e.voltageRegulator == "Proportional" + assert e.pss == "" + assert e.auxiliaries == True + assert e.internalTransformer == False + assert e.rpcl == True + assert e.rpcl2 == False + assert e.uva == "" + assert e.fictitious == False + assert e.qlim == False + + n.update_extensions(extension_name, id=element_id, numberOfWindings=4, + governor="ProportionalIntegral", voltageRegulator="ProportionalIntegral", pss="Pss", + auxiliaries=False, internalTransformer=True, rpcl=False, rpcl2=True, + uva="local", fictitious=True, qlim=True) + e = n.get_extensions(extension_name).loc[element_id] + assert e.numberOfWindings == 4 + assert e.governor == "ProportionalIntegral" + assert e.voltageRegulator == "ProportionalIntegral" + assert e.pss == "Pss" + assert e.auxiliaries == False + assert e.internalTransformer == True + assert e.rpcl == False + assert e.rpcl2 == True + assert e.uva == "local" + assert e.fictitious == True + assert e.qlim == True + + n.remove_extensions(extension_name, [element_id]) + assert n.get_extensions(extension_name).empty + +def test_synchronized_generator_properties(): + n = pn.create_four_substations_node_breaker_network() + extension_name = 'synchronizedGeneratorProperties' + element_id = 'GH1' + + extensions = n.get_extensions(extension_name) + assert extensions.empty + + n.create_extensions(extension_name, id=element_id, + type="PV", rpcl2=True) + e = n.get_extensions(extension_name).loc[element_id] + assert e.type == "PV" + assert e.rpcl2 == True + + n.update_extensions(extension_name, id=element_id, type="PfQ", rpcl2=False) + e = n.get_extensions(extension_name).loc[element_id] + assert e.type == "PfQ" + assert e.rpcl2 == False + + n.remove_extensions(extension_name, [element_id]) + assert n.get_extensions(extension_name).empty + +def test_synchronized_generator_properties(): + n = pn.create_four_substations_node_breaker_network() + extension_name = 'generatorConnectionLevel' + element_id = 'GH1' + + extensions = n.get_extensions(extension_name) + assert extensions.empty + + n.create_extensions(extension_name, id=element_id, level='TSO') + e = n.get_extensions(extension_name).loc[element_id] + assert e.level == 'TSO' + + n.update_extensions(extension_name, id=element_id, level='DSO') + e = n.get_extensions(extension_name).loc[element_id] + assert e.level == 'DSO' + + n.remove_extensions(extension_name, [element_id]) + assert n.get_extensions(extension_name).empty def test_get_extensions_information(): extensions_information = pypowsybl.network.get_extensions_information() @@ -640,3 +725,6 @@ def test_get_extensions_information(): assert extensions_information.loc['voltagePerReactivePowerControl']['attributes'] == 'index : id (str), slope (float)' assert extensions_information.loc['voltageRegulation']['detail'] == 'it allows to specify the voltage regulation mode for batteries' assert extensions_information.loc['voltageRegulation']['attributes'] == 'index : id (str), voltage_regulator_on (bool), target_v (float), regulated_element_id (str)' + assert extensions_information.loc['synchronousGeneratorProperties']['attributes'] == 'index : id (str), numberOfWindings (int), governor (str), voltageRegulator (str), pss (str), auxiliaries (bool), internalTransformer (bool), rpcl (bool), rpcl2 (bool), uva (str), fictitious (bool), qlim (bool)' + assert extensions_information.loc['synchronizedGeneratorProperties']['attributes'] == 'index : id (str), type (str), rpcl2 (bool)' + assert extensions_information.loc['generatorConnectionLevel']['attributes'] == 'index : id (str), level (str)' From 98865a50cdf02ae1000c087aeda3414ff8df0b98 Mon Sep 17 00:00:00 2001 From: lisrte Date: Fri, 12 Dec 2025 00:09:03 +0100 Subject: [PATCH 2/7] Fix extensions and move them to their package Signed-off-by: lisrte --- ...eneratorConnectionLevelDataframeAdder.java | 9 +- ...ratorConnectionLevelDataframeProvider.java | 13 +- ...izedGeneratorPropertiesDataframeAdder.java | 5 +- ...dGeneratorPropertiesDataframeProvider.java | 15 ++- ...nousGeneratorPropertiesDataframeAdder.java | 37 +++--- ...sGeneratorPropertiesDataframeProvider.java | 39 +++--- .../DynamicExtensionAddersTest.java | 120 ++++++++++++++++++ .../adders/NetworkElementAddersTest.java | 80 ------------ tests/test_dynamic.py | 86 +++++++++++++ tests/test_network_extensions.py | 88 +------------ 10 files changed, 269 insertions(+), 223 deletions(-) rename java/pypowsybl/src/main/java/com/powsybl/dataframe/{network => dynamic}/extensions/GeneratorConnectionLevelDataframeAdder.java (85%) rename java/pypowsybl/src/main/java/com/powsybl/dataframe/{network => dynamic}/extensions/GeneratorConnectionLevelDataframeProvider.java (82%) rename java/pypowsybl/src/main/java/com/powsybl/dataframe/{network => dynamic}/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java (92%) rename java/pypowsybl/src/main/java/com/powsybl/dataframe/{network => dynamic}/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java (83%) rename java/pypowsybl/src/main/java/com/powsybl/dataframe/{network => dynamic}/extensions/SynchronousGeneratorPropertiesDataframeAdder.java (74%) rename java/pypowsybl/src/main/java/com/powsybl/dataframe/{network => dynamic}/extensions/SynchronousGeneratorPropertiesDataframeProvider.java (68%) create mode 100644 java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeAdder.java similarity index 85% rename from java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeAdder.java rename to java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeAdder.java index 38e27eaf19..eb0dbf62cc 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeAdder.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeAdder.java @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dataframe.network.extensions; +package com.powsybl.dataframe.dynamic.extensions; import com.powsybl.commons.PowsyblException; import com.powsybl.dataframe.SeriesMetadata; @@ -13,16 +13,17 @@ import com.powsybl.dataframe.network.adders.SeriesUtils; import com.powsybl.dataframe.update.StringSeries; import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.dynawo.extensions.api.generator.connection.GeneratorConnectionLevel; +import com.powsybl.dynawo.extensions.api.generator.connection.GeneratorConnectionLevelAdder; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.extensions.GeneratorConnectionLevelAdder; -import com.powsybl.iidm.network.extensions.GeneratorConnectionLevelType; import java.util.Collections; import java.util.List; /** * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@literal } */ public class GeneratorConnectionLevelDataframeAdder extends AbstractSimpleAdder { @@ -53,7 +54,7 @@ void create(Network network, int row) { throw new PowsyblException("Invalid generator id : could not find " + generatorId); } var adder = g.newExtension(GeneratorConnectionLevelAdder.class); - SeriesUtils.applyIfPresent(level, row, l -> adder.withLevel(GeneratorConnectionLevelType.valueOf(l))); + SeriesUtils.applyIfPresent(level, row, l -> adder.withLevel(GeneratorConnectionLevel.GeneratorConnectionLevelType.valueOf(l))); adder.add(); } } diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java similarity index 82% rename from java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeProvider.java rename to java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java index 063e45ed58..3aebaca6b1 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/GeneratorConnectionLevelDataframeProvider.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dataframe.network.extensions; +package com.powsybl.dataframe.dynamic.extensions; import com.google.auto.service.AutoService; import com.powsybl.commons.PowsyblException; @@ -13,11 +13,11 @@ import com.powsybl.dataframe.network.NetworkDataframeMapper; import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder; import com.powsybl.dataframe.network.adders.NetworkElementAdder; +import com.powsybl.dataframe.network.extensions.AbstractSingleDataframeNetworkExtension; +import com.powsybl.dataframe.network.extensions.NetworkExtensionDataframeProvider; +import com.powsybl.dynawo.extensions.api.generator.connection.GeneratorConnectionLevel; import com.powsybl.iidm.network.Generator; -import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.extensions.GeneratorConnectionLevel; -import com.powsybl.iidm.network.extensions.GeneratorConnectionLevelType; import java.util.List; import java.util.Objects; @@ -25,6 +25,7 @@ /** * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@literal } */ @AutoService(NetworkExtensionDataframeProvider.class) public class GeneratorConnectionLevelDataframeProvider extends AbstractSingleDataframeNetworkExtension { @@ -63,8 +64,8 @@ private GeneratorConnectionLevel getOrThrow(Network network, String id) { @Override public NetworkDataframeMapper createMapper() { return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, this::getOrThrow) - .stringsIndex("id", ext -> ((Identifiable) ext.getExtendable()).getId()) - .enums("level", GeneratorConnectionLevelType.class, GeneratorConnectionLevel::getLevel, GeneratorConnectionLevel::setLevel) + .stringsIndex("id", ext -> ext.getExtendable().getId()) + .enums("level", GeneratorConnectionLevel.GeneratorConnectionLevelType.class, GeneratorConnectionLevel::getLevel, GeneratorConnectionLevel::setLevel) .build(); } diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java similarity index 92% rename from java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java rename to java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java index 08d5a6d44a..322c759b5f 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dataframe.network.extensions; +package com.powsybl.dataframe.dynamic.extensions; import com.powsybl.commons.PowsyblException; import com.powsybl.dataframe.SeriesMetadata; @@ -14,15 +14,16 @@ import com.powsybl.dataframe.update.IntSeries; import com.powsybl.dataframe.update.StringSeries; import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.dynawo.extensions.api.generator.SynchronizedGeneratorPropertiesAdder; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.extensions.SynchronizedGeneratorPropertiesAdder; import java.util.Collections; import java.util.List; /** * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@literal } */ public class SynchronizedGeneratorPropertiesDataframeAdder extends AbstractSimpleAdder { diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java similarity index 83% rename from java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java rename to java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java index ae8bdfbb0b..733ddba5a7 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dataframe.network.extensions; +package com.powsybl.dataframe.dynamic.extensions; import com.google.auto.service.AutoService; import com.powsybl.commons.PowsyblException; @@ -13,10 +13,12 @@ import com.powsybl.dataframe.network.NetworkDataframeMapper; import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder; import com.powsybl.dataframe.network.adders.NetworkElementAdder; +import com.powsybl.dataframe.network.extensions.AbstractSingleDataframeNetworkExtension; +import com.powsybl.dataframe.network.extensions.NetworkExtensionDataframeProvider; +import com.powsybl.dynawo.extensions.api.generator.RpclType; +import com.powsybl.dynawo.extensions.api.generator.SynchronizedGeneratorProperties; import com.powsybl.iidm.network.Generator; -import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.extensions.SynchronizedGeneratorProperties; import java.util.List; import java.util.Objects; @@ -24,6 +26,7 @@ /** * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@literal } */ @AutoService(NetworkExtensionDataframeProvider.class) public class SynchronizedGeneratorPropertiesDataframeProvider extends AbstractSingleDataframeNetworkExtension { @@ -63,9 +66,9 @@ private SynchronizedGeneratorProperties getOrThrow(Network network, String id) { @Override public NetworkDataframeMapper createMapper() { return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, this::getOrThrow) - .stringsIndex("id", ext -> ((Identifiable) ext.getExtendable()).getId()) - .strings("type", sgp -> String.valueOf(sgp.getType()), (sgp, type) -> sgp.setType(type)) - .booleans("rpcl2", SynchronizedGeneratorProperties::getRpcl2, SynchronizedGeneratorProperties::setRpcl2) + .stringsIndex("id", ext -> ext.getExtendable().getId()) + .strings("type", sgp -> String.valueOf(sgp.getType()), SynchronizedGeneratorProperties::setType) + .booleans("rpcl2", SynchronizedGeneratorProperties::isRpcl2, (sgp, b) -> sgp.setRpcl(b ? RpclType.RPCL2 : RpclType.NONE)) .build(); } diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeAdder.java similarity index 74% rename from java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeAdder.java rename to java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeAdder.java index 580430fed7..2dcb800ad9 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeAdder.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeAdder.java @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dataframe.network.extensions; +package com.powsybl.dataframe.dynamic.extensions; import com.powsybl.commons.PowsyblException; import com.powsybl.dataframe.SeriesMetadata; @@ -14,30 +14,32 @@ import com.powsybl.dataframe.update.IntSeries; import com.powsybl.dataframe.update.StringSeries; import com.powsybl.dataframe.update.UpdatingDataframe; +import com.powsybl.dynawo.extensions.api.generator.RpclType; +import com.powsybl.dynawo.extensions.api.generator.SynchronousGeneratorProperties; +import com.powsybl.dynawo.extensions.api.generator.SynchronousGeneratorPropertiesAdder; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.extensions.SynchronousGeneratorPropertiesAdder; import java.util.Collections; import java.util.List; /** * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@literal } */ public class SynchronousGeneratorPropertiesDataframeAdder extends AbstractSimpleAdder { private static final List METADATA = List.of( SeriesMetadata.stringIndex("id"), - SeriesMetadata.ints("numberOfWindings"), + SeriesMetadata.strings("numberOfWindings"), SeriesMetadata.strings("governor"), SeriesMetadata.strings("voltageRegulator"), SeriesMetadata.strings("pss"), SeriesMetadata.booleans("auxiliaries"), SeriesMetadata.booleans("internalTransformer"), - SeriesMetadata.booleans("rpcl"), - SeriesMetadata.booleans("rpcl2"), + SeriesMetadata.strings("rpcl"), SeriesMetadata.strings("uva"), - SeriesMetadata.booleans("fictitious"), + SeriesMetadata.booleans("aggregated"), SeriesMetadata.booleans("qlim") ); @@ -49,30 +51,28 @@ public List> getMetadata() { private static class SynchronousGeneratorPropertiesSeries { private final StringSeries id; - private final IntSeries numberOfWindings; + private final StringSeries numberOfWindings; private final StringSeries governor; private final StringSeries voltageRegulator; private final StringSeries pss; private final IntSeries auxiliaries; private final IntSeries internalTransformer; - private final IntSeries rpcl; - private final IntSeries rpcl2; + private final StringSeries rpcl; private final StringSeries uva; - private final IntSeries fictitious; + private final IntSeries aggregated; private final IntSeries qlim; SynchronousGeneratorPropertiesSeries(UpdatingDataframe dataframe) { this.id = dataframe.getStrings("id"); - this.numberOfWindings = dataframe.getInts("numberOfWindings"); + this.numberOfWindings = dataframe.getStrings("numberOfWindings"); this.governor = dataframe.getStrings("governor"); this.voltageRegulator = dataframe.getStrings("voltageRegulator"); this.pss = dataframe.getStrings("pss"); this.auxiliaries = dataframe.getInts("auxiliaries"); this.internalTransformer = dataframe.getInts("internalTransformer"); - this.rpcl = dataframe.getInts("rpcl"); - this.rpcl2 = dataframe.getInts("rpcl2"); + this.rpcl = dataframe.getStrings("rpcl"); this.uva = dataframe.getStrings("uva"); - this.fictitious = dataframe.getInts("fictitious"); + this.aggregated = dataframe.getInts("aggregated"); this.qlim = dataframe.getInts("qlim"); } @@ -83,16 +83,15 @@ void create(Network network, int row) { throw new PowsyblException("Invalid generator id : could not find " + generatorId); } var adder = g.newExtension(SynchronousGeneratorPropertiesAdder.class); - SeriesUtils.applyIfPresent(numberOfWindings, row, adder::withNumberOfWindings); + SeriesUtils.applyIfPresent(numberOfWindings, row, w -> adder.withNumberOfWindings(SynchronousGeneratorProperties.Windings.valueOf(w))); SeriesUtils.applyIfPresent(governor, row, adder::withGovernor); SeriesUtils.applyIfPresent(voltageRegulator, row, adder::withVoltageRegulator); SeriesUtils.applyIfPresent(pss, row, adder::withPss); SeriesUtils.applyBooleanIfPresent(auxiliaries, row, adder::withAuxiliaries); SeriesUtils.applyBooleanIfPresent(internalTransformer, row, adder::withInternalTransformer); - SeriesUtils.applyBooleanIfPresent(rpcl, row, adder::withRpcl); - SeriesUtils.applyBooleanIfPresent(rpcl2, row, adder::withRpcl2); - SeriesUtils.applyIfPresent(uva, row, adder::withUva); - SeriesUtils.applyBooleanIfPresent(fictitious, row, adder::withFictitious); + SeriesUtils.applyIfPresent(rpcl, row, r -> adder.withRpcl(RpclType.valueOf(r))); + SeriesUtils.applyIfPresent(uva, row, u -> adder.withUva(SynchronousGeneratorProperties.Uva.valueOf(u))); + SeriesUtils.applyBooleanIfPresent(aggregated, row, adder::withAggregated); SeriesUtils.applyBooleanIfPresent(qlim, row, adder::withQlim); adder.add(); } diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java similarity index 68% rename from java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeProvider.java rename to java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java index 83ae97fed7..ba0e66f7b9 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/network/extensions/SynchronousGeneratorPropertiesDataframeProvider.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * SPDX-License-Identifier: MPL-2.0 */ -package com.powsybl.dataframe.network.extensions; +package com.powsybl.dataframe.dynamic.extensions; import com.google.auto.service.AutoService; import com.powsybl.commons.PowsyblException; @@ -13,10 +13,12 @@ import com.powsybl.dataframe.network.NetworkDataframeMapper; import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder; import com.powsybl.dataframe.network.adders.NetworkElementAdder; +import com.powsybl.dataframe.network.extensions.AbstractSingleDataframeNetworkExtension; +import com.powsybl.dataframe.network.extensions.NetworkExtensionDataframeProvider; +import com.powsybl.dynawo.extensions.api.generator.RpclType; +import com.powsybl.dynawo.extensions.api.generator.SynchronousGeneratorProperties; import com.powsybl.iidm.network.Generator; -import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.extensions.SynchronousGeneratorProperties; import java.util.List; import java.util.Objects; @@ -24,6 +26,7 @@ /** * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@literal } */ @AutoService(NetworkExtensionDataframeProvider.class) public class SynchronousGeneratorPropertiesDataframeProvider extends AbstractSingleDataframeNetworkExtension { @@ -38,16 +41,15 @@ public ExtensionInformation getExtensionInformation() { return new ExtensionInformation(SynchronousGeneratorProperties.NAME, "Provides information about the characteristics of a synchronous generator", "index : id (str), " + - "numberOfWindings (int), " + + "numberOfWindings (str), " + "governor (str), " + "voltageRegulator (str), " + "pss (str), " + "auxiliaries (bool), " + "internalTransformer (bool), " + - "rpcl (bool), " + - "rpcl2 (bool), " + + "rpcl (str), " + "uva (str), " + - "fictitious (bool), " + + "aggregated (bool), " + "qlim (bool)"); } @@ -72,18 +74,17 @@ private SynchronousGeneratorProperties getOrThrow(Network network, String id) { @Override public NetworkDataframeMapper createMapper() { return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, this::getOrThrow) - .stringsIndex("id", ext -> ((Identifiable) ext.getExtendable()).getId()) - .ints("numberOfWindings", SynchronousGeneratorProperties::getNumberOfWindings, SynchronousGeneratorProperties::setNumberOfWindings) - .strings("governor", sgp -> String.valueOf(sgp.getGovernor()), (sgp, governor) -> sgp.setGovernor(governor)) - .strings("voltageRegulator", sgp -> String.valueOf(sgp.getVoltageRegulator()), (sgp, voltageRegulator) -> sgp.setVoltageRegulator(voltageRegulator)) - .strings("pss", sgp -> String.valueOf(sgp.getPss()), (sgp, pss) -> sgp.setPss(pss)) - .booleans("auxiliaries", SynchronousGeneratorProperties::getAuxiliaries, SynchronousGeneratorProperties::setAuxiliaries) - .booleans("internalTransformer", SynchronousGeneratorProperties::getInternalTransformer, SynchronousGeneratorProperties::setInternalTransformer) - .booleans("rpcl", SynchronousGeneratorProperties::getRpcl, SynchronousGeneratorProperties::setRpcl) - .booleans("rpcl2", SynchronousGeneratorProperties::getRpcl2, SynchronousGeneratorProperties::setRpcl2) - .strings("uva", sgp -> String.valueOf(sgp.getUva()), (sgp, uva) -> sgp.setUva(uva)) - .booleans("fictitious", SynchronousGeneratorProperties::getFictitious, SynchronousGeneratorProperties::setFictitious) - .booleans("qlim", SynchronousGeneratorProperties::getQlim, SynchronousGeneratorProperties::setQlim) + .stringsIndex("id", ext -> ext.getExtendable().getId()) + .enums("numberOfWindings", SynchronousGeneratorProperties.Windings.class, SynchronousGeneratorProperties::getNumberOfWindings, SynchronousGeneratorProperties::setNumberOfWindings) + .strings("governor", sgp -> String.valueOf(sgp.getGovernor()), SynchronousGeneratorProperties::setGovernor) + .strings("voltageRegulator", sgp -> String.valueOf(sgp.getVoltageRegulator()), SynchronousGeneratorProperties::setVoltageRegulator) + .strings("pss", sgp -> String.valueOf(sgp.getPss()), SynchronousGeneratorProperties::setPss) + .booleans("auxiliaries", SynchronousGeneratorProperties::isAuxiliaries, SynchronousGeneratorProperties::setAuxiliaries) + .booleans("internalTransformer", SynchronousGeneratorProperties::isInternalTransformer, SynchronousGeneratorProperties::setInternalTransformer) + .enums("rpcl", RpclType.class, SynchronousGeneratorProperties::getRpcl, SynchronousGeneratorProperties::setRpcl) + .enums("uva", SynchronousGeneratorProperties.Uva.class, SynchronousGeneratorProperties::getUva, SynchronousGeneratorProperties::setUva) + .booleans("aggregated", SynchronousGeneratorProperties::isAggregated, SynchronousGeneratorProperties::setAggregated) + .booleans("qlim", SynchronousGeneratorProperties::isQlim, SynchronousGeneratorProperties::setQlim) .build(); } diff --git a/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java new file mode 100644 index 0000000000..996b44e390 --- /dev/null +++ b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2025, 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.dynamic.extensions; + +import com.powsybl.dataframe.network.adders.NetworkElementAdders; +import com.powsybl.dataframe.update.DefaultUpdatingDataframe; +import com.powsybl.dataframe.update.TestDoubleSeries; +import com.powsybl.dataframe.update.TestIntSeries; +import com.powsybl.dataframe.update.TestStringSeries; +import com.powsybl.dynawo.extensions.api.generator.RpclType; +import com.powsybl.dynawo.extensions.api.generator.SynchronizedGeneratorProperties; +import com.powsybl.dynawo.extensions.api.generator.SynchronousGeneratorProperties; +import com.powsybl.dynawo.extensions.api.generator.connection.GeneratorConnectionLevel; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.Test; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Laurent Issertial {@literal } + */ +public class DynamicExtensionAddersTest { + + @Test + void synchronousGeneratorPropertiesExtension() { + var network = EurostagTutorialExample1Factory.create(); + String genId = "GEN"; + SynchronousGeneratorProperties extension = network.getGenerator(genId).getExtension(SynchronousGeneratorProperties.class); + assertNull(extension); + SynchronousGeneratorProperties.Windings numberOfWindings = SynchronousGeneratorProperties.Windings.FOUR_WINDINGS; + String governor = "Proportional"; + String voltageRegulator = "Proportional"; + String pss = ""; + RpclType rpcl = RpclType.RPCL1; + SynchronousGeneratorProperties.Uva uva = SynchronousGeneratorProperties.Uva.DISTANT; + + DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); + addStringColumn(dataframe, "id", genId); + addStringColumn(dataframe, "numberOfWindings", String.valueOf(numberOfWindings)); + addStringColumn(dataframe, "governor", governor); + addStringColumn(dataframe, "voltageRegulator", voltageRegulator); + addStringColumn(dataframe, "pss", pss); + addIntColumn(dataframe, "auxiliaries", 1); + addIntColumn(dataframe, "internalTransformer", 0); + addStringColumn(dataframe, "rpcl", String.valueOf(rpcl)); + addStringColumn(dataframe, "uva", String.valueOf(uva)); + addIntColumn(dataframe, "fictitious", 0); + addIntColumn(dataframe, "qlim", 0); + NetworkElementAdders.addExtensions("synchronousGeneratorProperties", network, singletonList(dataframe)); + + extension = network.getGenerator(genId).getExtension(SynchronousGeneratorProperties.class); + assertNotNull(extension); + assertEquals(numberOfWindings, extension.getNumberOfWindings()); + assertEquals(governor, extension.getGovernor()); + assertEquals(voltageRegulator, extension.getVoltageRegulator()); + assertEquals(pss, extension.getPss()); + assertTrue(extension.isAuxiliaries()); + assertFalse(extension.isInternalTransformer()); + assertEquals(rpcl, extension.getRpcl()); + assertEquals(uva, extension.getUva()); + assertFalse(extension.isAggregated()); + assertFalse(extension.isQlim()); + } + + @Test + void synchronizedGeneratorPropertiesExtension() { + var network = EurostagTutorialExample1Factory.create(); + String genId = "GEN"; + SynchronizedGeneratorProperties extension = network.getGenerator(genId).getExtension(SynchronizedGeneratorProperties.class); + assertNull(extension); + String type = "PV"; + + DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); + addStringColumn(dataframe, "id", genId); + addStringColumn(dataframe, "type", type); + addIntColumn(dataframe, "rpcl2", 0); + NetworkElementAdders.addExtensions("synchronizedGeneratorProperties", network, singletonList(dataframe)); + + extension = network.getGenerator(genId).getExtension(SynchronizedGeneratorProperties.class); + assertNotNull(extension); + assertEquals(type, extension.getType()); + assertFalse(extension.isRpcl2()); + } + + @Test + void generatorConnectionLevelExtension() { + var network = EurostagTutorialExample1Factory.create(); + String genId = "GEN"; + GeneratorConnectionLevel extension = network.getGenerator(genId).getExtension(GeneratorConnectionLevel.class); + assertNull(extension); + String level = "TSO"; + + DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); + addStringColumn(dataframe, "id", genId); + addStringColumn(dataframe, "level", level); + NetworkElementAdders.addExtensions("generatorConnectionLevel", network, singletonList(dataframe)); + + extension = network.getGenerator(genId).getExtension(GeneratorConnectionLevel.class); + assertNotNull(extension); + assertEquals(GeneratorConnectionLevel.GeneratorConnectionLevelType.valueOf(level), extension.getLevel()); + } + + private void addStringColumn(DefaultUpdatingDataframe dataframe, String column, String... value) { + dataframe.addSeries(column, false, new TestStringSeries(value)); + } + + private void addDoubleColumn(DefaultUpdatingDataframe dataframe, String column, double... value) { + dataframe.addSeries(column, false, new TestDoubleSeries(value)); + } + + private void addIntColumn(DefaultUpdatingDataframe dataframe, String column, int... value) { + dataframe.addSeries(column, false, new TestIntSeries(value)); + } +} diff --git a/java/pypowsybl/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java b/java/pypowsybl/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java index a3eab133a0..2cfe13b88e 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/dataframe/network/adders/NetworkElementAddersTest.java @@ -746,84 +746,4 @@ void dcGround() { NetworkElementAdders.addElements(DataframeElementType.DC_GROUND, network.getSubnetwork("VscAsymmetricalMonopole"), singletonList(dataframe)); assertEquals(3, network.getDcGroundCount()); } - - @Test - void synchronousGeneratorPropertiesExtension() { - var network = EurostagTutorialExample1Factory.create(); - String genId = "GEN"; - SynchronousGeneratorProperties extension = network.getGenerator(genId).getExtension(SynchronousGeneratorProperties.class); - assertNull(extension); - int numberOfWindings = 3; - String governor = "Proportional"; - String voltageRegulator = "Proportional"; - String pss = ""; - String uva = ""; - - DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); - addStringColumn(dataframe, "id", genId); - addIntColumn(dataframe, "numberOfWindings", numberOfWindings); - addStringColumn(dataframe, "governor", governor); - addStringColumn(dataframe, "voltageRegulator", voltageRegulator); - addStringColumn(dataframe, "pss", pss); - addIntColumn(dataframe, "auxiliaries", 1); - addIntColumn(dataframe, "internalTransformer", 0); - addIntColumn(dataframe, "rpcl", 1); - addIntColumn(dataframe, "rpcl2", 0); - addStringColumn(dataframe, "uva", uva); - addIntColumn(dataframe, "fictitious", 0); - addIntColumn(dataframe, "qlim", 0); - NetworkElementAdders.addExtensions("synchronousGeneratorProperties", network, singletonList(dataframe)); - - extension = network.getGenerator(genId).getExtension(SynchronousGeneratorProperties.class); - assertNotNull(extension); - assertEquals(numberOfWindings, extension.getNumberOfWindings()); - assertEquals(governor, extension.getGovernor()); - assertEquals(voltageRegulator, extension.getVoltageRegulator()); - assertEquals(pss, extension.getPss()); - assertTrue(extension.getAuxiliaries()); - assertFalse(extension.getInternalTransformer()); - assertTrue(extension.getRpcl()); - assertFalse(extension.getRpcl2()); - assertEquals(uva, extension.getUva()); - assertFalse(extension.getFictitious()); - assertFalse(extension.getQlim()); - } - - @Test - void synchronizedGeneratorPropertiesExtension() { - var network = EurostagTutorialExample1Factory.create(); - String genId = "GEN"; - SynchronizedGeneratorProperties extension = network.getGenerator(genId).getExtension(SynchronizedGeneratorProperties.class); - assertNull(extension); - String type = "PV"; - - DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); - addStringColumn(dataframe, "id", genId); - addStringColumn(dataframe, "type", type); - addIntColumn(dataframe, "rpcl2", 0); - NetworkElementAdders.addExtensions("synchronizedGeneratorProperties", network, singletonList(dataframe)); - - extension = network.getGenerator(genId).getExtension(SynchronizedGeneratorProperties.class); - assertNotNull(extension); - assertEquals(type, extension.getType()); - assertFalse(extension.getRpcl2()); - } - - @Test - void generatorConnectionLevelExtension() { - var network = EurostagTutorialExample1Factory.create(); - String genId = "GEN"; - GeneratorConnectionLevel extension = network.getGenerator(genId).getExtension(GeneratorConnectionLevel.class); - assertNull(extension); - String level = "TSO"; - - DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1); - addStringColumn(dataframe, "id", genId); - addStringColumn(dataframe, "level", level); - NetworkElementAdders.addExtensions("generatorConnectionLevel", network, singletonList(dataframe)); - - extension = network.getGenerator(genId).getExtension(GeneratorConnectionLevel.class); - assertNotNull(extension); - assertEquals(GeneratorConnectionLevelType.valueOf(level), extension.getLevel()); - } } diff --git a/tests/test_dynamic.py b/tests/test_dynamic.py index f8087769ea..1090ff0c43 100644 --- a/tests/test_dynamic.py +++ b/tests/test_dynamic.py @@ -8,6 +8,7 @@ import pypowsybl.dynamic as dyn import pytest import pandas as pd +import pypowsybl.network as pn @pytest.fixture(autouse=True) def set_up(): @@ -165,3 +166,88 @@ def test_parameters(): assert 100.0 == parameters.stop_time assert 'IDA'== parameters.provider_parameters['solver.type'] assert '1e-5' == parameters.provider_parameters['precision'] + + +def test_synchronous_generator_properties(): + n = pn.create_four_substations_node_breaker_network() + extension_name = 'synchronousGeneratorProperties' + element_id = 'GH1' + + extensions = n.get_extensions(extension_name) + assert extensions.empty + + n.create_extensions(extension_name, id=element_id, numberOfWindings="THREE_WINDINGS", + governor="Proportional", voltageRegulator="Proportional", pss="", + auxiliaries=True, internalTransformer=False, rpcl="RPCL1", + aggregated=False, qlim=False) + e = n.get_extensions(extension_name).loc[element_id] + assert e.numberOfWindings == "THREE_WINDINGS" + assert e.governor == "Proportional" + assert e.voltageRegulator == "Proportional" + assert e.pss == "" + assert e.auxiliaries == True + assert e.internalTransformer == False + assert e.rpcl == "RPCL1" + assert e.uva == "LOCAL" + assert e.aggregated == False + assert e.qlim == False + + n.update_extensions(extension_name, id=element_id, numberOfWindings="FOUR_WINDINGS", + governor="ProportionalIntegral", voltageRegulator="ProportionalIntegral", pss="Pss", + auxiliaries=False, internalTransformer=True, rpcl="RPCL2", + uva="DISTANT", aggregated=True, qlim=True) + e = n.get_extensions(extension_name).loc[element_id] + assert e.numberOfWindings == "FOUR_WINDINGS" + assert e.governor == "ProportionalIntegral" + assert e.voltageRegulator == "ProportionalIntegral" + assert e.pss == "Pss" + assert e.auxiliaries == False + assert e.internalTransformer == True + assert e.rpcl == "RPCL2" + assert e.uva == "DISTANT" + assert e.aggregated == True + assert e.qlim == True + + n.remove_extensions(extension_name, [element_id]) + assert n.get_extensions(extension_name).empty + +def test_synchronized_generator_properties(): + n = pn.create_four_substations_node_breaker_network() + extension_name = 'synchronizedGeneratorProperties' + element_id = 'GH1' + + extensions = n.get_extensions(extension_name) + assert extensions.empty + + n.create_extensions(extension_name, id=element_id, + type="PV", rpcl2=True) + e = n.get_extensions(extension_name).loc[element_id] + assert e.type == "PV" + assert e.rpcl2 == True + + n.update_extensions(extension_name, id=element_id, type="PfQ", rpcl2=False) + e = n.get_extensions(extension_name).loc[element_id] + assert e.type == "PfQ" + assert e.rpcl2 == False + + n.remove_extensions(extension_name, [element_id]) + assert n.get_extensions(extension_name).empty + +def test_generator_connection_level_properties(): + n = pn.create_four_substations_node_breaker_network() + extension_name = 'generatorConnectionLevel' + element_id = 'GH1' + + extensions = n.get_extensions(extension_name) + assert extensions.empty + + n.create_extensions(extension_name, id=element_id, level='TSO') + e = n.get_extensions(extension_name).loc[element_id] + assert e.level == 'TSO' + + n.update_extensions(extension_name, id=element_id, level='DSO') + e = n.get_extensions(extension_name).loc[element_id] + assert e.level == 'DSO' + + n.remove_extensions(extension_name, [element_id]) + assert n.get_extensions(extension_name).empty diff --git a/tests/test_network_extensions.py b/tests/test_network_extensions.py index f7fdf6ede8..e97f13dd52 100644 --- a/tests/test_network_extensions.py +++ b/tests/test_network_extensions.py @@ -588,92 +588,6 @@ def test_batteries_voltage_regulation(): network.remove_extensions('voltageRegulation', ['BAT']) assert network.get_extensions('voltageRegulation').empty -def test_synchronous_generator_properties(): - n = pn.create_four_substations_node_breaker_network() - extension_name = 'synchronousGeneratorProperties' - element_id = 'GH1' - - extensions = n.get_extensions(extension_name) - assert extensions.empty - - n.create_extensions(extension_name, id=element_id, numberOfWindings=3, - governor="Proportional", voltageRegulator="Proportional", pss="", - auxiliaries=True, internalTransformer=False, rpcl=True, rpcl2=False, - uva="", fictitious=False, qlim=False) - e = n.get_extensions(extension_name).loc[element_id] - assert e.numberOfWindings == 3 - assert e.governor == "Proportional" - assert e.voltageRegulator == "Proportional" - assert e.pss == "" - assert e.auxiliaries == True - assert e.internalTransformer == False - assert e.rpcl == True - assert e.rpcl2 == False - assert e.uva == "" - assert e.fictitious == False - assert e.qlim == False - - n.update_extensions(extension_name, id=element_id, numberOfWindings=4, - governor="ProportionalIntegral", voltageRegulator="ProportionalIntegral", pss="Pss", - auxiliaries=False, internalTransformer=True, rpcl=False, rpcl2=True, - uva="local", fictitious=True, qlim=True) - e = n.get_extensions(extension_name).loc[element_id] - assert e.numberOfWindings == 4 - assert e.governor == "ProportionalIntegral" - assert e.voltageRegulator == "ProportionalIntegral" - assert e.pss == "Pss" - assert e.auxiliaries == False - assert e.internalTransformer == True - assert e.rpcl == False - assert e.rpcl2 == True - assert e.uva == "local" - assert e.fictitious == True - assert e.qlim == True - - n.remove_extensions(extension_name, [element_id]) - assert n.get_extensions(extension_name).empty - -def test_synchronized_generator_properties(): - n = pn.create_four_substations_node_breaker_network() - extension_name = 'synchronizedGeneratorProperties' - element_id = 'GH1' - - extensions = n.get_extensions(extension_name) - assert extensions.empty - - n.create_extensions(extension_name, id=element_id, - type="PV", rpcl2=True) - e = n.get_extensions(extension_name).loc[element_id] - assert e.type == "PV" - assert e.rpcl2 == True - - n.update_extensions(extension_name, id=element_id, type="PfQ", rpcl2=False) - e = n.get_extensions(extension_name).loc[element_id] - assert e.type == "PfQ" - assert e.rpcl2 == False - - n.remove_extensions(extension_name, [element_id]) - assert n.get_extensions(extension_name).empty - -def test_synchronized_generator_properties(): - n = pn.create_four_substations_node_breaker_network() - extension_name = 'generatorConnectionLevel' - element_id = 'GH1' - - extensions = n.get_extensions(extension_name) - assert extensions.empty - - n.create_extensions(extension_name, id=element_id, level='TSO') - e = n.get_extensions(extension_name).loc[element_id] - assert e.level == 'TSO' - - n.update_extensions(extension_name, id=element_id, level='DSO') - e = n.get_extensions(extension_name).loc[element_id] - assert e.level == 'DSO' - - n.remove_extensions(extension_name, [element_id]) - assert n.get_extensions(extension_name).empty - def test_get_extensions_information(): extensions_information = pypowsybl.network.get_extensions_information() assert extensions_information.loc['cgmesMetadataModels']['detail'] == 'Provides information about CGMES metadata models' @@ -725,6 +639,6 @@ def test_get_extensions_information(): assert extensions_information.loc['voltagePerReactivePowerControl']['attributes'] == 'index : id (str), slope (float)' assert extensions_information.loc['voltageRegulation']['detail'] == 'it allows to specify the voltage regulation mode for batteries' assert extensions_information.loc['voltageRegulation']['attributes'] == 'index : id (str), voltage_regulator_on (bool), target_v (float), regulated_element_id (str)' - assert extensions_information.loc['synchronousGeneratorProperties']['attributes'] == 'index : id (str), numberOfWindings (int), governor (str), voltageRegulator (str), pss (str), auxiliaries (bool), internalTransformer (bool), rpcl (bool), rpcl2 (bool), uva (str), fictitious (bool), qlim (bool)' + assert extensions_information.loc['synchronousGeneratorProperties']['attributes'] == 'index : id (str), numberOfWindings (str), governor (str), voltageRegulator (str), pss (str), auxiliaries (bool), internalTransformer (bool), rpcl (str), uva (str), aggregated (bool), qlim (bool)' assert extensions_information.loc['synchronizedGeneratorProperties']['attributes'] == 'index : id (str), type (str), rpcl2 (bool)' assert extensions_information.loc['generatorConnectionLevel']['attributes'] == 'index : id (str), level (str)' From e23502f52c6402588bf6078ce46628c8328a536e Mon Sep 17 00:00:00 2001 From: lisrte Date: Fri, 12 Dec 2025 00:09:37 +0100 Subject: [PATCH 3/7] Add dynawo extension dependencies Signed-off-by: lisrte --- java/pypowsybl/pom.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/java/pypowsybl/pom.xml b/java/pypowsybl/pom.xml index 1287f88cff..b915beeeba 100644 --- a/java/pypowsybl/pom.xml +++ b/java/pypowsybl/pom.xml @@ -292,6 +292,21 @@ com.powsybl powsybl-dynawo-simulation + + com.powsybl + powsybl-dynawo-extensions-api + 3.1.0 + + + com.powsybl + powsybl-dynawo-extensions-impl + 3.1.0 + + + com.powsybl + powsybl-dynawo-extensions-serde + 3.1.0 + com.powsybl powsybl-entsoe-commons From 07a13aa0d8ecdec22b891d970ad994ffd21f9c11 Mon Sep 17 00:00:00 2001 From: lisrte Date: Mon, 5 Jan 2026 12:44:03 +0100 Subject: [PATCH 4/7] Fix code smells Signed-off-by: lisrte --- .../dynamic/extensions/DynamicExtensionAddersTest.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java index 996b44e390..db58103ce6 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java @@ -25,7 +25,7 @@ /** * @author Laurent Issertial {@literal } */ -public class DynamicExtensionAddersTest { +class DynamicExtensionAddersTest { @Test void synchronousGeneratorPropertiesExtension() { @@ -110,10 +110,6 @@ private void addStringColumn(DefaultUpdatingDataframe dataframe, String column, dataframe.addSeries(column, false, new TestStringSeries(value)); } - private void addDoubleColumn(DefaultUpdatingDataframe dataframe, String column, double... value) { - dataframe.addSeries(column, false, new TestDoubleSeries(value)); - } - private void addIntColumn(DefaultUpdatingDataframe dataframe, String column, int... value) { dataframe.addSeries(column, false, new TestIntSeries(value)); } From 731e8a797ae04c2980e87c163430c3ad3d6e86ec Mon Sep 17 00:00:00 2001 From: lisrte Date: Tue, 6 Jan 2026 10:36:09 +0100 Subject: [PATCH 5/7] Fix checkstyle Signed-off-by: lisrte --- .../dataframe/dynamic/extensions/DynamicExtensionAddersTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java index db58103ce6..a52ce8bfe1 100644 --- a/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java +++ b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java @@ -9,7 +9,6 @@ import com.powsybl.dataframe.network.adders.NetworkElementAdders; import com.powsybl.dataframe.update.DefaultUpdatingDataframe; -import com.powsybl.dataframe.update.TestDoubleSeries; import com.powsybl.dataframe.update.TestIntSeries; import com.powsybl.dataframe.update.TestStringSeries; import com.powsybl.dynawo.extensions.api.generator.RpclType; From b540ac50577f3e15f6cc7bb71af5a956d81d5f76 Mon Sep 17 00:00:00 2001 From: lisrte Date: Tue, 6 Jan 2026 11:07:07 +0100 Subject: [PATCH 6/7] Remove powsybl-dynawo-extensions version Signed-off-by: lisrte --- java/pypowsybl/pom.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/java/pypowsybl/pom.xml b/java/pypowsybl/pom.xml index b915beeeba..0c4ae19dc9 100644 --- a/java/pypowsybl/pom.xml +++ b/java/pypowsybl/pom.xml @@ -295,17 +295,14 @@ com.powsybl powsybl-dynawo-extensions-api - 3.1.0 com.powsybl powsybl-dynawo-extensions-impl - 3.1.0 com.powsybl powsybl-dynawo-extensions-serde - 3.1.0 com.powsybl From da40e910f3813eaeea3a33e96d69bfe54eddc92c Mon Sep 17 00:00:00 2001 From: lisrte Date: Wed, 7 Jan 2026 15:40:05 +0100 Subject: [PATCH 7/7] Modify ExtensionInformation Signed-off-by: lisrte --- .../extensions/GeneratorConnectionLevelDataframeProvider.java | 2 +- .../SynchronizedGeneratorPropertiesDataframeProvider.java | 2 +- .../SynchronousGeneratorPropertiesDataframeProvider.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java index 3aebaca6b1..2eed4e3a63 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java @@ -38,7 +38,7 @@ public String getExtensionName() { @Override public ExtensionInformation getExtensionInformation() { return new ExtensionInformation(GeneratorConnectionLevel.NAME, - "Provides information about the characteristics of a Synchronized generator", + "Provides information, for dynamic simulation only, about the characteristics of a Synchronized generator", "index : id (str), " + "level (str)"); } diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java index 733ddba5a7..9f119b229c 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java @@ -39,7 +39,7 @@ public String getExtensionName() { @Override public ExtensionInformation getExtensionInformation() { return new ExtensionInformation(SynchronizedGeneratorProperties.NAME, - "Provides information about the characteristics of a Synchronized generator", + "Provides information, for dynamic simulation only, about the characteristics of a Synchronized generator", "index : id (str), " + "type (str), " + "rpcl2 (bool)"); diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java index ba0e66f7b9..875d142187 100644 --- a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java @@ -39,7 +39,7 @@ public String getExtensionName() { @Override public ExtensionInformation getExtensionInformation() { return new ExtensionInformation(SynchronousGeneratorProperties.NAME, - "Provides information about the characteristics of a synchronous generator", + "Provides information, for dynamic simulation only, about the characteristics of a synchronous generator", "index : id (str), " + "numberOfWindings (str), " + "governor (str), " +