diff --git a/java/pypowsybl/pom.xml b/java/pypowsybl/pom.xml index 1287f88cff..0c4ae19dc9 100644 --- a/java/pypowsybl/pom.xml +++ b/java/pypowsybl/pom.xml @@ -292,6 +292,18 @@ com.powsybl powsybl-dynawo-simulation + + com.powsybl + powsybl-dynawo-extensions-api + + + com.powsybl + powsybl-dynawo-extensions-impl + + + com.powsybl + powsybl-dynawo-extensions-serde + com.powsybl powsybl-entsoe-commons diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeAdder.java new file mode 100644 index 0000000000..eb0dbf62cc --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeAdder.java @@ -0,0 +1,69 @@ +/** + * 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.dynamic.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.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 java.util.Collections; +import java.util.List; + +/** + * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@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(GeneratorConnectionLevel.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/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java new file mode 100644 index 0000000000..2eed4e3a63 --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.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.dynamic.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.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.Network; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@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, for dynamic simulation only, 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 -> ext.getExtendable().getId()) + .enums("level", GeneratorConnectionLevel.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/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java new file mode 100644 index 0000000000..322c759b5f --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java @@ -0,0 +1,73 @@ +/** + * 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.dynamic.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.dynawo.extensions.api.generator.SynchronizedGeneratorPropertiesAdder; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; + +import java.util.Collections; +import java.util.List; + +/** + * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@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/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java new file mode 100644 index 0000000000..9f119b229c --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java @@ -0,0 +1,88 @@ +/** + * 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.dynamic.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.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.Network; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@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, for dynamic simulation only, 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 -> 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(); + } + + @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/dynamic/extensions/SynchronousGeneratorPropertiesDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeAdder.java new file mode 100644 index 0000000000..2dcb800ad9 --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeAdder.java @@ -0,0 +1,107 @@ +/** + * 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.dynamic.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.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 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.strings("numberOfWindings"), + SeriesMetadata.strings("governor"), + SeriesMetadata.strings("voltageRegulator"), + SeriesMetadata.strings("pss"), + SeriesMetadata.booleans("auxiliaries"), + SeriesMetadata.booleans("internalTransformer"), + SeriesMetadata.strings("rpcl"), + SeriesMetadata.strings("uva"), + SeriesMetadata.booleans("aggregated"), + SeriesMetadata.booleans("qlim") + ); + + @Override + public List> getMetadata() { + return Collections.singletonList(METADATA); + } + + private static class SynchronousGeneratorPropertiesSeries { + + private final StringSeries id; + 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 StringSeries rpcl; + private final StringSeries uva; + private final IntSeries aggregated; + private final IntSeries qlim; + + SynchronousGeneratorPropertiesSeries(UpdatingDataframe dataframe) { + this.id = dataframe.getStrings("id"); + 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.getStrings("rpcl"); + this.uva = dataframe.getStrings("uva"); + this.aggregated = dataframe.getInts("aggregated"); + 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, 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.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(); + } + } + + @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/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java new file mode 100644 index 0000000000..875d142187 --- /dev/null +++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java @@ -0,0 +1,104 @@ +/** + * 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.dynamic.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.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.Network; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * @author Gautier Bureau {@literal } + * @author Laurent Issertial {@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, for dynamic simulation only, about the characteristics of a synchronous generator", + "index : id (str), " + + "numberOfWindings (str), " + + "governor (str), " + + "voltageRegulator (str), " + + "pss (str), " + + "auxiliaries (bool), " + + "internalTransformer (bool), " + + "rpcl (str), " + + "uva (str), " + + "aggregated (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 -> 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(); + } + + @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/dynamic/extensions/DynamicExtensionAddersTest.java b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java new file mode 100644 index 0000000000..a52ce8bfe1 --- /dev/null +++ b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java @@ -0,0 +1,115 @@ +/** + * 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.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 } + */ +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 addIntColumn(DefaultUpdatingDataframe dataframe, String column, int... value) { + dataframe.addSeries(column, false, new TestIntSeries(value)); + } +} 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 f163238d74..e97f13dd52 100644 --- a/tests/test_network_extensions.py +++ b/tests/test_network_extensions.py @@ -588,7 +588,6 @@ def test_batteries_voltage_regulation(): network.remove_extensions('voltageRegulation', ['BAT']) assert network.get_extensions('voltageRegulation').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' @@ -640,3 +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 (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)'