diff --git a/cgmes/cgmes-conformity/src/main/java/com/powsybl/cgmes/conformity/CgmesConformity1Catalog.java b/cgmes/cgmes-conformity/src/main/java/com/powsybl/cgmes/conformity/CgmesConformity1Catalog.java index 77dafc06140..a32013acffc 100644 --- a/cgmes/cgmes-conformity/src/main/java/com/powsybl/cgmes/conformity/CgmesConformity1Catalog.java +++ b/cgmes/cgmes-conformity/src/main/java/com/powsybl/cgmes/conformity/CgmesConformity1Catalog.java @@ -1087,6 +1087,11 @@ public static CgmesModel expectedMicroGridType4BE() { "fd227658-0e1b-4ecd-952a-c6b0307b1ea11", "ff466d18-e4f5-439b-a50a-daec2fa41e2c", "ff466d18-e4f5-439b-a50a-daec2fa41e2c1"); + m.shuntCompensatorsPoints("46e3d51d-0a41-4e3f-8ce5-63e7bb165b73", + "7dc75c5a-74cc-434c-a125-860960b6ed35", + "89e965d7-0348-4dc1-98d4-be3bf8891fad", + "8b93ca77-3cc3-4c82-8524-2f8a13513e20", + "ca954c3a-5194-49eb-9097-10c77cea36b9"); Set tlremove = new HashSet<>(Arrays.asList( "acbd4688-6393-4b43-a9f4-27d8c3f8c309", "1c8440dc-e65d-4337-9d3e-7558062228da1", diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java index 311efbc54fc..e5369dbd89a 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java @@ -423,6 +423,7 @@ private void addParametersToContext(CgmesExportContext context, Properties param .setExportTransformersWithHighestVoltageAtEnd1(Parameter.readBoolean(getFormat(), params, EXPORT_TRANSFORMERS_WITH_HIGHEST_VOLTAGE_AT_END1_PARAMETER, defaultValueConfig)) .setExportLoadFlowStatus(Parameter.readBoolean(getFormat(), params, EXPORT_LOAD_FLOW_STATUS_PARAMETER, defaultValueConfig)) .setExportAllLimitsGroup(Parameter.readBoolean(getFormat(), params, EXPORT_ALL_LIMITS_GROUP_PARAMETER, defaultValueConfig)) + .setExportGeneratorsInLocalRegulationMode(Parameter.readBoolean(getFormat(), params, EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_PARAMETER, defaultValueConfig)) .setMaxPMismatchConverged(Parameter.readDouble(getFormat(), params, MAX_P_MISMATCH_CONVERGED_PARAMETER, defaultValueConfig)) .setMaxQMismatchConverged(Parameter.readDouble(getFormat(), params, MAX_Q_MISMATCH_CONVERGED_PARAMETER, defaultValueConfig)) .setExportSvInjectionsForSlacks(Parameter.readBoolean(getFormat(), params, EXPORT_SV_INJECTIONS_FOR_SLACKS_PARAMETER, defaultValueConfig)) @@ -544,6 +545,7 @@ public String getFormat() { public static final String EXPORT_TRANSFORMERS_WITH_HIGHEST_VOLTAGE_AT_END1 = "iidm.export.cgmes.export-transformers-with-highest-voltage-at-end1"; public static final String EXPORT_LOAD_FLOW_STATUS = "iidm.export.cgmes.export-load-flow-status"; public static final String EXPORT_ALL_LIMITS_GROUP = "iidm.export.cgmes.export-all-limits-group"; + public static final String EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE = "iidm.export.cgmes.export-generators-in-local-regulation-mode"; public static final String MAX_P_MISMATCH_CONVERGED = "iidm.export.cgmes.max-p-mismatch-converged"; public static final String MAX_Q_MISMATCH_CONVERGED = "iidm.export.cgmes.max-q-mismatch-converged"; public static final String EXPORT_SV_INJECTIONS_FOR_SLACKS = "iidm.export.cgmes.export-sv-injections-for-slacks"; @@ -637,7 +639,12 @@ public String getFormat() { EXPORT_ALL_LIMITS_GROUP, ParameterType.BOOLEAN, "True to export all OperationalLimitsGroup, False to export only the selected group", - CgmesExportContext.EXPORT_LOAD_FLOW_STATUS_DEFAULT_VALUE); + CgmesExportContext.EXPORT_ALL_LIMITS_GROUP_DEFAULT_VALUE); + private static final Parameter EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_PARAMETER = new Parameter( + EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE, + ParameterType.BOOLEAN, + "True to export voltage regulating generators in local regulation mode, False to keep their regulation mode unchanged.", + CgmesExportContext.EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_DEFAULT_VALUE); private static final Parameter MAX_P_MISMATCH_CONVERGED_PARAMETER = new Parameter( MAX_P_MISMATCH_CONVERGED, ParameterType.DOUBLE, diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java index 1e0920d8c7c..628f4a8d15d 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java @@ -1105,4 +1105,5 @@ public Config setCreateFictitiousVoltageLevelsForEveryNode(boolean b) { public static final String PROPERTY_CGMES_GOVERNOR_SCD = CGMES_PREFIX_ALIAS_PROPERTIES + "governorSCD"; public static final String PROPERTY_CGMES_SYNCHRONOUS_MACHINE_TYPE = CGMES_PREFIX_ALIAS_PROPERTIES + "synchronousMachineType"; public static final String PROPERTY_CGMES_SYNCHRONOUS_MACHINE_OPERATING_MODE = CGMES_PREFIX_ALIAS_PROPERTIES + "synchronousMachineOperatingMode"; + public static final String PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS = CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.OPERATIONAL_LIMIT_SET + "_identifiers"; } diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/OperationalLimitConversion.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/OperationalLimitConversion.java index 33ea40fccd5..65def7f741d 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/OperationalLimitConversion.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/elements/OperationalLimitConversion.java @@ -8,15 +8,21 @@ package com.powsybl.cgmes.conversion.elements; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.powsybl.cgmes.conversion.Context; -import com.powsybl.cgmes.conversion.Conversion; import com.powsybl.cgmes.model.CgmesNames; +import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.*; import com.powsybl.triplestore.api.PropertyBag; import java.util.Optional; import java.util.function.Supplier; +import static com.powsybl.cgmes.conversion.Conversion.PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS; + /** * @author Luma Zamarreño {@literal } */ @@ -28,7 +34,6 @@ public class OperationalLimitConversion extends AbstractIdentifiedObjectConversi private static final String OPERATIONAL_LIMIT_SUBCLASS = "OperationalLimitSubclass"; private static final String OPERATIONAL_LIMIT_SET_ID = "OperationalLimitSet"; private static final String OPERATIONAL_LIMIT_SET_NAME = "OperationalLimitSetName"; - private static final String PROPERTY_PREFIX = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.OPERATIONAL_LIMIT_SET + "_"; private static final String PERMANENT_LIMIT = "Permanent Limit"; private static final String TEMPORARY_LIMIT = "Temporary Limit"; @@ -81,6 +86,30 @@ private void setVoltageLevelForVoltageLimit(Terminal terminal) { } } + /** + * Store the CGMES OperationalLimitSet id/name pair in a property of the identifiable. + * If the property already exists, meaning it has been created for another limit set of that identifiable, + * then append the id/name pair to the property value (which actually represents a serialized json). + * @param identifiable The Branch, DanglingLine, ThreeWindingsTransformer where the limit set id/name are stored. + * @param limitSetId The OperationalLimitSet id to store. + * @param limitSetName The OperationalLimitSet name to store. + */ + private void storeOperationalLimitSetIdentifiers(Identifiable identifiable, String limitSetId, String limitSetName) { + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node; + if (identifiable.hasProperty(PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS)) { + node = mapper.readTree(identifiable.getProperty(PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS)); + } else { + node = mapper.createObjectNode(); + } + ((ObjectNode) node).put(limitSetId, limitSetName); + identifiable.setProperty(PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS, mapper.writeValueAsString(node)); + } catch (JsonProcessingException e) { + throw new PowsyblException(e.getMessage(), e); + } + } + /** * Create the LoadingLimitsAdder for the given branch + side and the given limit set + subclass. * @param terminalNumber The side of the branch to which the OperationalLimit applies. @@ -92,12 +121,12 @@ private void setVoltageLevelForVoltageLimit(Terminal terminal) { private void createLimitsAdder(int terminalNumber, String limitSubClass, String limitSetId, String limitSetName, Branch b) { if (terminalNumber == 1) { OperationalLimitsGroup limitsGroup = b.getOperationalLimitsGroup1(limitSetId).orElseGet(() -> { - b.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName); + storeOperationalLimitSetIdentifiers(b, limitSetId, limitSetName); return b.newOperationalLimitsGroup1(limitSetId); }); loadingLimitsAdder1 = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass); } else if (terminalNumber == 2) { OperationalLimitsGroup limitsGroup = b.getOperationalLimitsGroup2(limitSetId).orElseGet(() -> { - b.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName); + storeOperationalLimitSetIdentifiers(b, limitSetId, limitSetName); return b.newOperationalLimitsGroup2(limitSetId); }); loadingLimitsAdder2 = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass); } else { @@ -114,7 +143,7 @@ private void createLimitsAdder(int terminalNumber, String limitSubClass, String */ private void createLimitsAdder(String limitSubClass, String limitSetId, String limitSetName, DanglingLine dl) { OperationalLimitsGroup limitsGroup = dl.getOperationalLimitsGroup(limitSetId).orElseGet(() -> { - dl.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName); + storeOperationalLimitSetIdentifiers(dl, limitSetId, limitSetName); return dl.newOperationalLimitsGroup(limitSetId); }); loadingLimitsAdder = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass); } @@ -130,17 +159,17 @@ private void createLimitsAdder(String limitSubClass, String limitSetId, String l private void createLimitsAdder(int terminalNumber, String limitSubClass, String limitSetId, String limitSetName, ThreeWindingsTransformer twt) { if (terminalNumber == 1) { OperationalLimitsGroup limitsGroup = twt.getLeg1().getOperationalLimitsGroup(limitSetId).orElseGet(() -> { - twt.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName); + storeOperationalLimitSetIdentifiers(twt, limitSetId, limitSetName); return twt.getLeg1().newOperationalLimitsGroup(limitSetId); }); loadingLimitsAdder = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass); } else if (terminalNumber == 2) { OperationalLimitsGroup limitsGroup = twt.getLeg2().getOperationalLimitsGroup(limitSetId).orElseGet(() -> { - twt.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName); + storeOperationalLimitSetIdentifiers(twt, limitSetId, limitSetName); return twt.getLeg2().newOperationalLimitsGroup(limitSetId); }); loadingLimitsAdder = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass); } else if (terminalNumber == 3) { OperationalLimitsGroup limitsGroup = twt.getLeg3().getOperationalLimitsGroup(limitSetId).orElseGet(() -> { - twt.setProperty(PROPERTY_PREFIX + limitSetId, limitSetName); + storeOperationalLimitSetIdentifiers(twt, limitSetId, limitSetName); return twt.getLeg3().newOperationalLimitsGroup(limitSetId); }); loadingLimitsAdder = context.loadingLimitsMapping().getLoadingLimitsAdder(limitsGroup, limitSubClass); } else { diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/CgmesExportContext.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/CgmesExportContext.java index a4f8f6df99f..409b0376e1f 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/CgmesExportContext.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/CgmesExportContext.java @@ -74,6 +74,7 @@ public class CgmesExportContext { public static final boolean ENCODE_IDS_DEFAULT_VALUE = true; public static final boolean EXPORT_LOAD_FLOW_STATUS_DEFAULT_VALUE = true; public static final boolean EXPORT_ALL_LIMITS_GROUP_DEFAULT_VALUE = true; + public static final boolean EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_DEFAULT_VALUE = false; // From QoCDC 3.3.1 rules IGMConvergence, KirchhoffsFirstLaw, ... that refer to SV_INJECTION_LIMIT=0.1 public static final double MAX_P_MISMATCH_CONVERGED_DEFAULT_VALUE = 0.1; public static final double MAX_Q_MISMATCH_CONVERGED_DEFAULT_VALUE = 0.1; @@ -88,6 +89,7 @@ public class CgmesExportContext { private boolean exportTransformersWithHighestVoltageAtEnd1 = EXPORT_TRANSFORMERS_WITH_HIGHEST_VOLTAGE_AT_END1_DEFAULT_VALUE; private boolean exportLoadFlowStatus = EXPORT_LOAD_FLOW_STATUS_DEFAULT_VALUE; private boolean exportAllLimitsGroup = EXPORT_ALL_LIMITS_GROUP_DEFAULT_VALUE; + private boolean exportGeneratorsInLocalRegulationMode = EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE_DEFAULT_VALUE; private double maxPMismatchConverged = MAX_P_MISMATCH_CONVERGED_DEFAULT_VALUE; private double maxQMismatchConverged = MAX_Q_MISMATCH_CONVERGED_DEFAULT_VALUE; private boolean isExportSvInjectionsForSlacks = EXPORT_SV_INJECTIONS_FOR_SLACKS_DEFAULT_VALUE; @@ -619,6 +621,15 @@ public CgmesExportContext setExportAllLimitsGroup(boolean exportAllLimitsGroup) return this; } + public boolean isExportGeneratorsInLocalRegulationMode() { + return exportGeneratorsInLocalRegulationMode; + } + + public CgmesExportContext setExportGeneratorsInLocalRegulationMode(boolean exportGeneratorsInLocalRegulationMode) { + this.exportGeneratorsInLocalRegulationMode = exportGeneratorsInLocalRegulationMode; + return this; + } + public double getMaxPMismatchConverged() { return maxPMismatchConverged; } diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/EquipmentExport.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/EquipmentExport.java index ca89b0f6326..1ecfdecb705 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/EquipmentExport.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/EquipmentExport.java @@ -7,6 +7,9 @@ */ package com.powsybl.cgmes.conversion.export; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.cgmes.conversion.CgmesExport; import com.powsybl.cgmes.conversion.Conversion; import com.powsybl.cgmes.conversion.naming.NamingStrategy; @@ -382,7 +385,15 @@ private static void writeGenerators(Network network, Map mapTe String cgmesOriginalClass = generator.getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS, CgmesNames.SYNCHRONOUS_MACHINE); RemoteReactivePowerControl rrpc = generator.getExtension(RemoteReactivePowerControl.class); String mode = CgmesExportUtil.getGeneratorRegulatingControlMode(generator, rrpc); - Terminal regulatingTerminal = mode.equals(RegulatingControlEq.REGULATING_CONTROL_VOLTAGE) ? generator.getRegulatingTerminal() : rrpc.getRegulatingTerminal(); + Terminal regulatingTerminal; + if (mode.equals(RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER)) { + regulatingTerminal = rrpc.getRegulatingTerminal(); + } else if (context.isExportGeneratorsInLocalRegulationMode()) { + regulatingTerminal = generator.getTerminal(); + } else { + regulatingTerminal = generator.getRegulatingTerminal(); + } + String regulatingControlId; switch (cgmesOriginalClass) { case CgmesNames.EQUIVALENT_INJECTION: String reactiveCapabilityCurveId = writeReactiveCapabilityCurve(generator, cimNamespace, writer, context); @@ -393,7 +404,7 @@ private static void writeGenerators(Network network, Map mapTe cimNamespace, writer, context); break; case CgmesNames.EXTERNAL_NETWORK_INJECTION: - String regulatingControlId = RegulatingControlEq.writeRegulatingControlEq(generator, exportedTerminalId(mapTerminal2Id, regulatingTerminal), regulatingControlsWritten, mode, cimNamespace, writer, context); + regulatingControlId = RegulatingControlEq.writeRegulatingControlEq(generator, exportedTerminalId(mapTerminal2Id, regulatingTerminal), regulatingControlsWritten, mode, cimNamespace, writer, context); ExternalNetworkInjectionEq.write(context.getNamingStrategy().getCgmesId(generator), generator.getNameOrId(), context.getNamingStrategy().getCgmesId(generator.getTerminal().getVoltageLevel()), obtainGeneratorGovernorScd(generator), generator.getMaxP(), obtainMaxQ(generator), generator.getMinP(), obtainMinQ(generator), @@ -1203,10 +1214,16 @@ private static void writeLimitsGroup(Identifiable identifiable, OperationalLi // Write the OperationalLimitSet String operationalLimitSetId; String operationalLimitSetName; - String propertyKey = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.OPERATIONAL_LIMIT_SET + "_" + limitsGroup.getId(); - if (identifiable.hasProperty(propertyKey)) { + if (identifiable.hasProperty(Conversion.PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS)) { operationalLimitSetId = limitsGroup.getId(); - operationalLimitSetName = identifiable.getProperty(propertyKey); + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode propertyNode = mapper.readTree(identifiable.getProperty(Conversion.PROPERTY_OPERATIONAL_LIMIT_SET_IDENTIFIERS)); + JsonNode limitsGroupNode = propertyNode.get(operationalLimitSetId); + operationalLimitSetName = limitsGroupNode != null ? limitsGroupNode.textValue() : operationalLimitSetId; + } catch (JsonProcessingException e) { + operationalLimitSetName = operationalLimitSetId; + } } else { operationalLimitSetId = context.getNamingStrategy().getCgmesId(ref(terminalId), ref(limitsGroup.getId()), OPERATIONAL_LIMIT_SET); operationalLimitSetName = limitsGroup.getId(); diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/SteadyStateHypothesisExport.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/SteadyStateHypothesisExport.java index f0aa90ddd5e..3e994d5370d 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/SteadyStateHypothesisExport.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/export/SteadyStateHypothesisExport.java @@ -389,6 +389,14 @@ private static void addRegulatingControlView(Generator g, Map.*?(.*?)"; + assertEquals("SPRING", getFirstMatch(xmlFile, Pattern.compile(regex.replace("NUM", "1"), Pattern.DOTALL))); + assertEquals("SPRING", getFirstMatch(xmlFile, Pattern.compile(regex.replace("NUM", "2"), Pattern.DOTALL))); + assertEquals("WINTER", getFirstMatch(xmlFile, Pattern.compile(regex.replace("NUM", "3"), Pattern.DOTALL))); } } diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/EquipmentExportTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/EquipmentExportTest.java index 3a4883e42b4..5a9977b2829 100644 --- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/EquipmentExportTest.java +++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/EquipmentExportTest.java @@ -1318,6 +1318,14 @@ void generatorRegulatingControlEQTest() throws IOException { eq = getEQ(network, baseName, tmpDir, exportParams); testRcEqRcWithAttribute(eq, "_GEN_RC", "_NHV2_NLOAD_PT_T_1", "voltage"); + // Generator with remote voltage regulation exported in local regulation mode + Properties exportInLocalRegulationModeParams = new Properties(); + exportInLocalRegulationModeParams.put(CgmesExport.PROFILES, "EQ"); + exportInLocalRegulationModeParams.put(CgmesExport.EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE, true); + network = EurostagTutorialExample1Factory.createWithRemoteVoltageGenerator(); + eq = getEQ(network, baseName, tmpDir, exportInLocalRegulationModeParams); + testRcEqRcWithAttribute(eq, "_GEN_RC", "_GEN_SM_T_1", "voltage"); + // Generator with local reactive network = EurostagTutorialExample1Factory.createWithLocalReactiveGenerator(); eq = getEQ(network, baseName, tmpDir, exportParams); diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/SteadyStateHypothesisExportTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/SteadyStateHypothesisExportTest.java index bf5b4c2c7dd..43102c5c030 100644 --- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/SteadyStateHypothesisExportTest.java +++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/SteadyStateHypothesisExportTest.java @@ -681,10 +681,18 @@ void generatorRegulatingControlSSHTest() throws IOException { // Generator remote voltage network = EurostagTutorialExample1Factory.createWithRemoteVoltageGenerator(); ssh = getSSH(network, baseName, tmpDir, exportParams); - testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "24.5", "k"); + testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "399", "k"); network.getGenerator("GEN").setVoltageRegulatorOn(false); ssh = getSSH(network, baseName, tmpDir, exportParams); - testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "false", "0", "24.5", "k"); + testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "false", "0", "399", "k"); + + // Generator with remote voltage regulation exported in local regulation mode + Properties exportInLocalRegulationModeParams = new Properties(); + exportInLocalRegulationModeParams.put(CgmesExport.PROFILES, "SSH"); + exportInLocalRegulationModeParams.put(CgmesExport.EXPORT_GENERATORS_IN_LOCAL_REGULATION_MODE, true); + network = EurostagTutorialExample1Factory.createWithRemoteVoltageGenerator(); + ssh = getSSH(network, baseName, tmpDir, exportInLocalRegulationModeParams); + testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "25.2", "k"); // Generator with local reactive network = EurostagTutorialExample1Factory.createWithLocalReactiveGenerator(); @@ -719,16 +727,16 @@ void generatorRegulatingControlSSHTest() throws IOException { // Generator with remote reactive and voltage network = EurostagTutorialExample1Factory.createWithRemoteReactiveAndVoltageGenerators(); ssh = getSSH(network, baseName, tmpDir, exportParams); - testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "24.5", "k"); + testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "399", "k"); network.getGenerator("GEN").setVoltageRegulatorOn(false); ssh = getSSH(network, baseName, tmpDir, exportParams); testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "200", "M"); network.getGenerator("GEN").getExtension(RemoteReactivePowerControl.class).setEnabled(false); ssh = getSSH(network, baseName, tmpDir, exportParams); - testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "false", "0", "24.5", "k"); + testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "false", "0", "399", "k"); network.getGenerator("GEN").setVoltageRegulatorOn(true); ssh = getSSH(network, baseName, tmpDir, exportParams); - testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "24.5", "k"); + testRcEqRcWithAttribute(ssh, "_GEN_RC", "false", "true", "0", "399", "k"); // Generator without control network = EurostagTutorialExample1Factory.createWithoutControl(); diff --git a/cgmes/cgmes-model-test/src/main/java/com/powsybl/cgmes/model/test/CgmesModelTester.java b/cgmes/cgmes-model-test/src/main/java/com/powsybl/cgmes/model/test/CgmesModelTester.java index 1d3ebdb2e91..81a84e427b2 100644 --- a/cgmes/cgmes-model-test/src/main/java/com/powsybl/cgmes/model/test/CgmesModelTester.java +++ b/cgmes/cgmes-model-test/src/main/java/com/powsybl/cgmes/model/test/CgmesModelTester.java @@ -89,6 +89,7 @@ private void testCompare(CgmesModel expected, CgmesModel actual) { testPropertyBags(expected.phaseTapChangers(), actual.phaseTapChangers()); testPropertyBags(expected.energyConsumers(), actual.energyConsumers()); testPropertyBags(expected.shuntCompensators(), actual.shuntCompensators()); + testPropertyBags(expected.nonlinearShuntCompensatorPoints(), actual.nonlinearShuntCompensatorPoints()); testPropertyBags(expected.staticVarCompensators(), actual.staticVarCompensators()); testPropertyBags(expected.synchronousMachinesGenerators(), actual.synchronousMachinesGenerators()); testPropertyBags(expected.synchronousMachinesCondensers(), actual.synchronousMachinesCondensers()); diff --git a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/AbstractCgmesModel.java b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/AbstractCgmesModel.java index 82b1c963ac0..6006049b616 100644 --- a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/AbstractCgmesModel.java +++ b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/AbstractCgmesModel.java @@ -36,6 +36,14 @@ public Properties getProperties() { return this.properties; } + @Override + public PropertyBags nonlinearShuntCompensatorPoints(String shuntId) { + if (cachedGroupedShuntCompensatorPoints == null) { + cachedGroupedShuntCompensatorPoints = computeGroupedShuntCompensatorPoints(); + } + return cachedGroupedShuntCompensatorPoints.getOrDefault(shuntId, new PropertyBags()); + } + @Override public Map groupedTransformerEnds() { if (cachedGroupedTransformerEnds == null) { @@ -161,6 +169,17 @@ private CgmesContainer container(CgmesTerminal t, boolean nodeBreaker) { return (containerId == null) ? null : container(containerId); } + private Map computeGroupedShuntCompensatorPoints() { + Map groupedShuntCompensatorPoints = new HashMap<>(); + nonlinearShuntCompensatorPoints() + .forEach(point -> { + String shuntCompensator = point.getId("Shunt"); + groupedShuntCompensatorPoints.computeIfAbsent(shuntCompensator, bag -> new PropertyBags()) + .add(point); + }); + return groupedShuntCompensatorPoints; + } + private Map computeGroupedTransformerEnds() { // Alternative implementation: // instead of sorting after building each list, @@ -291,6 +310,7 @@ public void read(ReadOnlyDataSource ds, ReportNode reportNode) { } protected void invalidateCaches() { + cachedGroupedShuntCompensatorPoints = null; cachedGroupedTransformerEnds = null; powerTransformerRatioTapChanger = null; powerTransformerPhaseTapChanger = null; @@ -308,6 +328,7 @@ protected void invalidateCaches() { private String baseName; // Caches + private Map cachedGroupedShuntCompensatorPoints; private Map cachedGroupedTransformerEnds; private Map cachedTerminals; private Map cachedContainers; diff --git a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/CgmesModel.java b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/CgmesModel.java index 1312f12762c..0f1bb6928b4 100644 --- a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/CgmesModel.java +++ b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/CgmesModel.java @@ -109,7 +109,18 @@ default PropertyBags fullModels() { PropertyBags equivalentShunts(); - PropertyBags nonlinearShuntCompensatorPoints(String id); + /** + * Query all NonlinearShuntCompensatorPoint in the CgmesModel. + * @return A {@link PropertyBags} with the shunt compensators points properties. + */ + PropertyBags nonlinearShuntCompensatorPoints(); + + /** + * Query the NonlinearShuntCompensatorPoint associated to the given NonlinearShuntCompensator. + * @param shuntId The id of the NonlinearShuntCompensator. + * @return A {@link PropertyBags} with the given shunt compensator's points properties. + */ + PropertyBags nonlinearShuntCompensatorPoints(String shuntId); PropertyBags staticVarCompensators(); diff --git a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/InMemoryCgmesModel.java b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/InMemoryCgmesModel.java index 69c279d00b4..17f9d4844ec 100644 --- a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/InMemoryCgmesModel.java +++ b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/InMemoryCgmesModel.java @@ -49,6 +49,7 @@ public final class InMemoryCgmesModel implements CgmesModel { private PropertyBags energyConsumers; private PropertyBags energySources; private PropertyBags shuntCompensators; + private PropertyBags shuntCompensatorPoints; private PropertyBags staticVarCompensators; private PropertyBags equivalentShunts; private PropertyBags synchronousMachinesGenerators; @@ -90,6 +91,7 @@ public InMemoryCgmesModel() { energyConsumers = new PropertyBags(); energySources = new PropertyBags(); shuntCompensators = new PropertyBags(); + shuntCompensatorPoints = new PropertyBags(); equivalentShunts = new PropertyBags(); staticVarCompensators = new PropertyBags(); synchronousMachinesGenerators = new PropertyBags(); @@ -225,6 +227,11 @@ public InMemoryCgmesModel shuntCompensators(String... ids) { return this; } + public InMemoryCgmesModel shuntCompensatorsPoints(String... ids) { + fakeObjectsFromIdentifiers("NonlinearShuntCompensatorPoint", ids, shuntCompensatorPoints); + return this; + } + public InMemoryCgmesModel staticVarCompensators(String... ids) { fakeObjectsFromIdentifiers("StaticVarCompensator", ids, staticVarCompensators); return this; @@ -431,15 +438,21 @@ public PropertyBags shuntCompensators() { } @Override - public PropertyBags equivalentShunts() { - return equivalentShunts; + public PropertyBags nonlinearShuntCompensatorPoints() { + return shuntCompensatorPoints; } @Override - public PropertyBags nonlinearShuntCompensatorPoints(String scId) { + public PropertyBags nonlinearShuntCompensatorPoints(String shuntId) { + // FakeCgmesModel does not provide grouped shunt compensator points return new PropertyBags(); } + @Override + public PropertyBags equivalentShunts() { + return equivalentShunts; + } + @Override public PropertyBags staticVarCompensators() { return staticVarCompensators; diff --git a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/triplestore/CgmesModelTripleStore.java b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/triplestore/CgmesModelTripleStore.java index 08290fac08e..7915a17dfe8 100644 --- a/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/triplestore/CgmesModelTripleStore.java +++ b/cgmes/cgmes-model/src/main/java/com/powsybl/cgmes/model/triplestore/CgmesModelTripleStore.java @@ -498,9 +498,8 @@ public PropertyBags equivalentShunts() { } @Override - public PropertyBags nonlinearShuntCompensatorPoints(String scId) { - Objects.requireNonNull(scId); - return namedQuery("nonlinearShuntCompensatorPoints", scId); + public PropertyBags nonlinearShuntCompensatorPoints() { + return namedQuery("nonlinearShuntCompensatorPoints"); } @Override diff --git a/cgmes/cgmes-model/src/main/resources/CIM16.sparql b/cgmes/cgmes-model/src/main/resources/CIM16.sparql index 5b7ec762727..bae7ca1b6db 100644 --- a/cgmes/cgmes-model/src/main/resources/CIM16.sparql +++ b/cgmes/cgmes-model/src/main/resources/CIM16.sparql @@ -826,8 +826,7 @@ WHERE { cim:NonlinearShuntCompensatorPoint.NonlinearShuntCompensator ?Shunt ; cim:NonlinearShuntCompensatorPoint.sectionNumber ?sectionNumber ; cim:NonlinearShuntCompensatorPoint.b ?b ; - cim:NonlinearShuntCompensatorPoint.g ?g . - FILTER REGEX ( STR (?Shunt), "{0}") + cim:NonlinearShuntCompensatorPoint.g ?g . } # query: synchronousMachinesGenerators diff --git a/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java b/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java index f091e829e3e..bf4ab14edb3 100644 --- a/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java +++ b/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java @@ -32,15 +32,11 @@ public void setExtendable(T extendable) { if (extendable != null && this.extendable != null && this.extendable != extendable) { throw new PowsyblException("Extension is already associated to the extendable " + this.extendable); } - if (extendable == null) { - cleanup(); - } this.extendable = extendable; } - /** - * Method called when the extension is removed from its holder. - * Can be used for e.g. resource cleanup. - */ - protected void cleanup() { } + @Override + public void cleanup() { + // nothing by default + } } diff --git a/commons/src/main/java/com/powsybl/commons/extensions/Extension.java b/commons/src/main/java/com/powsybl/commons/extensions/Extension.java index ce30898628b..c56f23591bd 100644 --- a/commons/src/main/java/com/powsybl/commons/extensions/Extension.java +++ b/commons/src/main/java/com/powsybl/commons/extensions/Extension.java @@ -33,4 +33,11 @@ public interface Extension { * @throws com.powsybl.commons.PowsyblException if this extension is already held. */ void setExtendable(T extendable); + + /** + * Method called just before the extension is removed from its holder. + * Can be used for e.g. resource cleanup. + */ + default void cleanup() { + } } diff --git a/contingency/contingency-api/src/main/java/com/powsybl/contingency/Contingency.java b/contingency/contingency-api/src/main/java/com/powsybl/contingency/Contingency.java index 4399587cfe4..90204681f07 100644 --- a/contingency/contingency-api/src/main/java/com/powsybl/contingency/Contingency.java +++ b/contingency/contingency-api/src/main/java/com/powsybl/contingency/Contingency.java @@ -112,8 +112,8 @@ public boolean isValid(Network network) { case LOAD -> checkLoadContingency(this, (LoadContingency) element, network); case BUS -> checkBusContingency(this, (BusContingency) element, network); case TIE_LINE -> checkTieLineContingency(this, (TieLineContingency) element, network); + case SWITCH -> checkSwitchContingency(this, (SwitchContingency) element, network); case BATTERY -> checkBatteryContingency(this, (BatteryContingency) element, network); - default -> throw new IllegalStateException("Unknown contingency element type " + element.getType()); }; } if (!valid) { @@ -263,6 +263,15 @@ private static boolean checkTieLineContingency(Contingency contingency, TieLineC return true; } + private static boolean checkSwitchContingency(Contingency contingency, SwitchContingency element, Network network) { + Switch switchElement = network.getSwitch(element.getId()); + if (switchElement == null) { + LOGGER.warn("Switch '{}' of contingency '{}' not found", element.getId(), contingency.getId()); + return false; + } + return true; + } + public static ContingencyBuilder builder(String id) { return new ContingencyBuilder(id); } diff --git a/contingency/contingency-api/src/main/java/com/powsybl/contingency/json/ContingencyElementDeserializer.java b/contingency/contingency-api/src/main/java/com/powsybl/contingency/json/ContingencyElementDeserializer.java index bbccb7da4ec..e46c7ea1f5a 100644 --- a/contingency/contingency-api/src/main/java/com/powsybl/contingency/json/ContingencyElementDeserializer.java +++ b/contingency/contingency-api/src/main/java/com/powsybl/contingency/json/ContingencyElementDeserializer.java @@ -56,9 +56,10 @@ public ContingencyElement deserialize(JsonParser parser, DeserializationContext case TWO_WINDINGS_TRANSFORMER -> new TwoWindingsTransformerContingency(id, voltageLevelId); case THREE_WINDINGS_TRANSFORMER -> new ThreeWindingsTransformerContingency(id); case LOAD -> new LoadContingency(id); + case SWITCH -> new SwitchContingency(id); + case BATTERY -> new BatteryContingency(id); case BUS -> new BusContingency(id); case TIE_LINE -> new TieLineContingency(id, voltageLevelId); - default -> throw new IllegalStateException("Unexpected ContingencyElementType value: " + type); }; } diff --git a/contingency/contingency-api/src/test/java/com/powsybl/contingency/SwitchContingencyTest.java b/contingency/contingency-api/src/test/java/com/powsybl/contingency/SwitchContingencyTest.java new file mode 100644 index 00000000000..51b5ed545fe --- /dev/null +++ b/contingency/contingency-api/src/test/java/com/powsybl/contingency/SwitchContingencyTest.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.contingency; + +import com.google.common.testing.EqualsTester; +import com.powsybl.iidm.modification.tripping.SwitchTripping; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Switch; +import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Etienne Lesot {@literal } + */ +class SwitchContingencyTest { + @Test + void test() { + var switchContingency = new SwitchContingency("switch"); + assertEquals("switch", switchContingency.getId()); + assertEquals(ContingencyElementType.SWITCH, switchContingency.getType()); + + assertNotNull(switchContingency.toModification()); + assertInstanceOf(SwitchTripping.class, switchContingency.toModification()); + + new EqualsTester() + .addEqualityGroup(new SwitchContingency("foo"), new SwitchContingency("foo")) + .addEqualityGroup(new SwitchContingency("bar"), new SwitchContingency("bar")) + .testEquals(); + } + + @Test + void testContingencyElement() { + Network network = FourSubstationsNodeBreakerFactory.create(); + Switch networkSwitch = network.getSwitch("S1VL1_LD1_BREAKER"); + assertNotNull(networkSwitch); + ContingencyElement element = ContingencyElement.of(networkSwitch); + assertNotNull(element); + assertInstanceOf(SwitchContingency.class, element); + assertEquals("S1VL1_LD1_BREAKER", element.getId()); + assertEquals(ContingencyElementType.SWITCH, element.getType()); + } + + @Test + void testContingencyElementNotFound() { + Network network = FourSubstationsNodeBreakerFactory.create(); + ContingencyElement element = new SwitchContingency("id"); + Contingency contingency = new Contingency("contingencyId", "contingencyName", List.of(element)); + assertFalse(contingency.isValid(network)); + ContingencyElement element2 = new SwitchContingency("S1VL1_LD1_BREAKER"); + Contingency contingency2 = new Contingency("contingencyId2", "contingencyName2", List.of(element2)); + assertTrue(contingency2.isValid(network)); + } +} diff --git a/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java b/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java index 894ec5c4c30..ca19654a6cd 100644 --- a/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java +++ b/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java @@ -14,7 +14,7 @@ import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializerProvider; import com.google.auto.service.AutoService; -import com.powsybl.commons.extensions.Extension; +import com.powsybl.commons.extensions.AbstractExtension; import com.powsybl.commons.extensions.ExtensionJsonSerializer; import com.powsybl.commons.json.JsonUtil; import com.powsybl.commons.test.AbstractSerDeTest; @@ -22,7 +22,7 @@ import com.powsybl.contingency.contingency.list.ContingencyList; import com.powsybl.contingency.contingency.list.DefaultContingencyList; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.iidm.network.test.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,6 +35,7 @@ import java.util.List; import java.util.Objects; +import static com.powsybl.contingency.ContingencyElementType.*; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -49,7 +50,11 @@ void setup() throws IOException { super.setUp(); Files.copy(getClass().getResourceAsStream("/contingencies.json"), fileSystem.getPath("/contingencies.json")); + Files.copy(getClass().getResourceAsStream("/contingenciesBatteries.json"), fileSystem.getPath("/contingenciesBatteries.json")); Files.copy(getClass().getResourceAsStream("/contingenciesWithOptionalName.json"), fileSystem.getPath("/contingenciesWithOptionalName.json")); + Files.copy(getClass().getResourceAsStream("/contingenciesWithSeveralElements.json"), fileSystem.getPath("/contingenciesWithSeveralElements.json")); + Files.copy(getClass().getResourceAsStream("/contingenciesWith3wt.json"), fileSystem.getPath("/contingenciesWith3wt.json")); + Files.copy(getClass().getResourceAsStream("/contingenciesWithDlAndTl.json"), fileSystem.getPath("/contingenciesWithDlAndTl.json")); } private static Contingency create() { @@ -134,6 +139,89 @@ void readJsonList() throws IOException { roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingencies.json"); } + @Test + void readJsonListWithBatteryContingency() throws IOException { + Network network = BatteryNetworkFactory.create(); + + ContingencyList contingencyList = ContingencyList.load(fileSystem.getPath("/contingenciesBatteries.json")); + assertEquals("list", contingencyList.getName()); + + List contingencies = contingencyList.getContingencies(network); + assertEquals(4, contingencies.size()); + assertEquals("contingency", contingencies.get(0).getId()); + assertEquals(2, contingencies.get(0).getElements().size()); + assertEquals("contingency2", contingencies.get(1).getId()); + assertEquals(1, contingencies.get(1).getElements().size()); + assertEquals("contingency3", contingencies.get(2).getId()); + assertEquals(1, contingencies.get(2).getElements().size()); + assertEquals("contingency4", contingencies.get(3).getId()); + assertEquals(1, contingencies.get(3).getElements().size()); + + roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesBatteries.json"); + } + + @Test + void readJsonListWithSwitchHvdcSvcShuntBbsTwtContingency() throws IOException { + Network network = FourSubstationsNodeBreakerFactory.create(); + + ContingencyList contingencyList = ContingencyList.load(fileSystem.getPath("/contingenciesWithSeveralElements.json")); + assertEquals("list", contingencyList.getName()); + + List contingencies = contingencyList.getContingencies(network); + assertEquals(6, contingencies.size()); + assertEquals("contingency", contingencies.get(0).getId()); + assertEquals(1, contingencies.get(0).getElements().size()); + assertEquals(HVDC_LINE, contingencies.get(0).getElements().get(0).getType()); + assertEquals("contingency2", contingencies.get(1).getId()); + assertEquals(1, contingencies.get(1).getElements().size()); + assertEquals(TWO_WINDINGS_TRANSFORMER, contingencies.get(1).getElements().get(0).getType()); + assertEquals("contingency3", contingencies.get(2).getId()); + assertEquals(1, contingencies.get(2).getElements().size()); + assertEquals(SHUNT_COMPENSATOR, contingencies.get(2).getElements().get(0).getType()); + assertEquals("contingency4", contingencies.get(3).getId()); + assertEquals(1, contingencies.get(3).getElements().size()); + assertEquals(STATIC_VAR_COMPENSATOR, contingencies.get(3).getElements().get(0).getType()); + assertEquals("contingency5", contingencies.get(4).getId()); + assertEquals(1, contingencies.get(4).getElements().size()); + assertEquals(BUSBAR_SECTION, contingencies.get(4).getElements().get(0).getType()); + assertEquals("contingency6", contingencies.get(5).getId()); + assertEquals(1, contingencies.get(5).getElements().size()); + assertEquals(SWITCH, contingencies.get(5).getElements().get(0).getType()); + + roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesWithSeveralElements.json"); + } + + @Test + void readJsonListWithTwt3Contingency() throws IOException { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + ContingencyList contingencyList = ContingencyList.load(fileSystem.getPath("/contingenciesWith3wt.json")); + assertEquals("list", contingencyList.getName()); + + List contingencies = contingencyList.getContingencies(network); + assertEquals(1, contingencies.size()); + assertEquals("contingency", contingencies.get(0).getId()); + assertEquals(1, contingencies.get(0).getElements().size()); + assertEquals(THREE_WINDINGS_TRANSFORMER, contingencies.get(0).getElements().get(0).getType()); + roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesWith3wt.json"); + } + + @Test + void readJsonListWithTieLineAndDanglingLineContingency() throws IOException { + Network network = EurostagTutorialExample1Factory.createWithTieLine(); + ContingencyList contingencyList = ContingencyList.load(fileSystem.getPath("/contingenciesWithDlAndTl.json")); + assertEquals("list", contingencyList.getName()); + + List contingencies = contingencyList.getContingencies(network); + assertEquals(2, contingencies.size()); + assertEquals("contingency", contingencies.get(0).getId()); + assertEquals(1, contingencies.get(0).getElements().size()); + assertEquals(DANGLING_LINE, contingencies.get(0).getElements().get(0).getType()); + assertEquals("contingency2", contingencies.get(1).getId()); + assertEquals(1, contingencies.get(1).getElements().size()); + assertEquals(TIE_LINE, contingencies.get(1).getElements().get(0).getType()); + roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesWithDlAndTl.json"); + } + @Test void readNotDefaultJsonList() throws IOException { Files.copy(getClass().getResourceAsStream("/identifierContingencyList.json"), fileSystem.getPath("/identifierContingencyList.json")); @@ -161,7 +249,7 @@ void readJsonListContingenciesWithOptionalName() throws IOException { roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesWithOptionalName.json"); } - static class DummyExtension implements Extension { + static class DummyExtension extends AbstractExtension { private Contingency contingency; diff --git a/contingency/contingency-api/src/test/resources/contingenciesBatteries.json b/contingency/contingency-api/src/test/resources/contingenciesBatteries.json new file mode 100644 index 00000000000..219e6b5ba06 --- /dev/null +++ b/contingency/contingency-api/src/test/resources/contingenciesBatteries.json @@ -0,0 +1,33 @@ +{ + "type" : "default", + "version" : "1.0", + "name" : "list", + "contingencies" : [ { + "id" : "contingency", + "elements" : [ { + "id" : "NHV1_NHV2_1", + "type" : "BRANCH" + }, { + "id" : "NHV1_NHV2_2", + "type" : "BRANCH" + } ] + }, { + "id" : "contingency2", + "elements" : [ { + "id" : "GEN", + "type" : "GENERATOR" + } ] + }, { + "id" : "contingency3", + "elements" : [ { + "id" : "BAT", + "type" : "BATTERY" + } ] + }, { + "id" : "contingency4", + "elements" : [ { + "id" : "LOAD", + "type" : "LOAD" + } ] + } ] +} \ No newline at end of file diff --git a/contingency/contingency-api/src/test/resources/contingenciesWith3wt.json b/contingency/contingency-api/src/test/resources/contingenciesWith3wt.json new file mode 100644 index 00000000000..7d12233c60b --- /dev/null +++ b/contingency/contingency-api/src/test/resources/contingenciesWith3wt.json @@ -0,0 +1,12 @@ +{ + "type" : "default", + "version" : "1.0", + "name" : "list", + "contingencies" : [ { + "id" : "contingency", + "elements" : [ { + "id" : "3WT", + "type" : "THREE_WINDINGS_TRANSFORMER" + } ] + } ] +} \ No newline at end of file diff --git a/contingency/contingency-api/src/test/resources/contingenciesWithDlAndTl.json b/contingency/contingency-api/src/test/resources/contingenciesWithDlAndTl.json new file mode 100644 index 00000000000..862d346b530 --- /dev/null +++ b/contingency/contingency-api/src/test/resources/contingenciesWithDlAndTl.json @@ -0,0 +1,18 @@ +{ + "type" : "default", + "version" : "1.0", + "name" : "list", + "contingencies" : [ { + "id" : "contingency", + "elements" : [ { + "id" : "NHV1_XNODE1", + "type" : "DANGLING_LINE" + } ] + }, { + "id" : "contingency2", + "elements" : [ { + "id" : "NHV1_NHV2_1", + "type" : "TIE_LINE" + } ] + } ] +} \ No newline at end of file diff --git a/contingency/contingency-api/src/test/resources/contingenciesWithSeveralElements.json b/contingency/contingency-api/src/test/resources/contingenciesWithSeveralElements.json new file mode 100644 index 00000000000..fcccdfa370b --- /dev/null +++ b/contingency/contingency-api/src/test/resources/contingenciesWithSeveralElements.json @@ -0,0 +1,42 @@ +{ + "type" : "default", + "version" : "1.0", + "name" : "list", + "contingencies" : [ { + "id" : "contingency", + "elements" : [ { + "id" : "HVDC1", + "type" : "HVDC_LINE" + } ] + }, { + "id" : "contingency2", + "elements" : [ { + "id" : "TWT", + "type" : "TWO_WINDINGS_TRANSFORMER" + } ] + }, { + "id" : "contingency3", + "elements" : [ { + "id" : "SHUNT", + "type" : "SHUNT_COMPENSATOR" + } ] + }, { + "id" : "contingency4", + "elements" : [ { + "id" : "SVC", + "type" : "STATIC_VAR_COMPENSATOR" + } ] + }, { + "id" : "contingency5", + "elements" : [ { + "id" : "S1VL1_BBS", + "type" : "BUSBAR_SECTION" + } ] + }, { + "id" : "contingency6", + "elements" : [ { + "id" : "S1VL1_LD1_BREAKER", + "type" : "SWITCH" + } ] + } ] +} \ No newline at end of file diff --git a/contingency/contingency-dsl/src/main/groovy/com/powsybl/contingency/dsl/ContingencyDslLoader.groovy b/contingency/contingency-dsl/src/main/groovy/com/powsybl/contingency/dsl/ContingencyDslLoader.groovy index 6c215d9e917..29308102f66 100644 --- a/contingency/contingency-dsl/src/main/groovy/com/powsybl/contingency/dsl/ContingencyDslLoader.groovy +++ b/contingency/contingency-dsl/src/main/groovy/com/powsybl/contingency/dsl/ContingencyDslLoader.groovy @@ -78,6 +78,16 @@ class ContingencyDslLoader extends DslLoader { builder.addDanglingLine(equipment) } else if (identifiable instanceof ThreeWindingsTransformer) { builder.addThreeWindingsTransformer(equipment) + } else if (identifiable instanceof TieLine) { + builder.addTieLine(equipment) + } else if (identifiable instanceof Bus) { + builder.addBus(equipment) + } else if (identifiable instanceof Battery) { + builder.addBattery(equipment) + } else if (identifiable instanceof Load) { + builder.addLoad(equipment) + } else if (identifiable instanceof Switch) { + builder.addSwitch(equipment) } else { LOGGER.warn("Equipment type {} not supported in contingencies", identifiable.getClass().name) valid = false diff --git a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ContingencyElementTypesTest.java b/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ContingencyElementTypesTest.java new file mode 100644 index 00000000000..6f8ee69bb02 --- /dev/null +++ b/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ContingencyElementTypesTest.java @@ -0,0 +1,169 @@ +/** + * Copyright (c) 2020,2021, RTE (http://www.rte-france.com) + * Copyright (c) 2024, Artelys (http://www.artelys.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.contingency.dsl; + +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import com.powsybl.contingency.*; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +/** + * @author Yichen TANG {@literal } + * @author Sebastien Murgey {@literal } + * @author Damien Jeandemange {@literal } + */ +class ContingencyElementTypesTest { + + private FileSystem fileSystem; + + private Path dslFile; + + @BeforeEach + void setUp() { + fileSystem = Jimfs.newFileSystem(Configuration.unix()); + dslFile = fileSystem.getPath("/test.dsl"); + } + + @AfterEach + void tearDown() throws Exception { + fileSystem.close(); + } + + private void test(Network network, String contingencyId, String equipmentId, Class contingencyElementClass) { + try { + Files.writeString(dslFile, String.format(""" + contingency('%s') { + equipments '%s' + }""", contingencyId, equipmentId), + StandardCharsets.UTF_8); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + List contingencies = new GroovyDslContingenciesProvider(dslFile) + .getContingencies(network); + assertEquals(1, contingencies.size()); + Contingency contingency = contingencies.get(0); + assertEquals(contingencyId, contingency.getId()); + assertEquals(0, contingency.getExtensions().size()); + assertEquals(1, contingency.getElements().size()); + ContingencyElement element = contingency.getElements().get(0); + assertInstanceOf(contingencyElementClass, element); + assertEquals(equipmentId, element.getId()); + } + + @Test + void generatorTest() { + Network network = EurostagTutorialExample1Factory.create(); + test(network, "GEN_CONTINGENCY", "GEN", GeneratorContingency.class); + } + + @Test + void loadTest() { + Network network = EurostagTutorialExample1Factory.create(); + test(network, "LOAD_CONTINGENCY", "LOAD", LoadContingency.class); + } + + @Test + void twoWindingsTransformerTest() { + Network network = EurostagTutorialExample1Factory.create(); + test(network, "2WT_CONTINGENCY", "NGEN_NHV1", TwoWindingsTransformerContingency.class); + } + + @Test + void threeWindingsTransformerTest() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + test(network, "3WT_CONTINGENCY", "3WT", ThreeWindingsTransformerContingency.class); + } + + @Test + void lineTest() { + Network network = EurostagTutorialExample1Factory.create(); + test(network, "LINE_CONTINGENCY", "NHV1_NHV2_1", LineContingency.class); + } + + @Test + void tieLineTest() { + Network network = EurostagTutorialExample1Factory.createWithTieLine(); + test(network, "TIELINE_CONTINGENCY", "NHV1_NHV2_1", TieLineContingency.class); + } + + @Test + void danglingLineTest() { + Network network = DanglingLineNetworkFactory.create(); + test(network, "DL_CONTINGENCY", "DL", DanglingLineContingency.class); + } + + @Test + void shuntCompensatorTest() { + Network network = EurostagTutorialExample1Factory.create(); + network.getVoltageLevel("VLLOAD") + .newShuntCompensator() + .setId("SC") + .setConnectableBus("NLOAD") + .setBus("NLOAD") + .setSectionCount(1) + .newLinearModel() + .setBPerSection(1e-5) + .setMaximumSectionCount(1) + .add() + .add(); + test(network, "SC_CONTINGENCY", "SC", ShuntCompensatorContingency.class); + } + + @Test + void busTest() { + Network network = EurostagTutorialExample1Factory.create(); + test(network, "BUS_CONTINGENCY", "NLOAD", BusContingency.class); + } + + @Test + void busbarSectionTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + test(network, "BBS_CONTINGENCY", "S1VL1_BBS", BusbarSectionContingency.class); + } + + @Test + void svcTest() { + Network network = SvcTestCaseFactory.create(); + test(network, "SVC_CONTINGENCY", "SVC2", StaticVarCompensatorContingency.class); + } + + @Test + void switchTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + test(network, "SWITCH_CONTINGENCY", "S1VL1_LD1_BREAKER", SwitchContingency.class); + } + + @Test + void hvdcTest() { + Network network = HvdcTestNetwork.createLcc(); + test(network, "HVDC_LINE_CONTINGENCY", "L", HvdcLineContingency.class); + } + + @Test + void batteryTest() { + Network network = BatteryNetworkFactory.create(); + test(network, "BAT_CONTINGENCY", "BAT", BatteryContingency.class); + } +} diff --git a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/DanglingLineContingencyScriptTest.java b/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/DanglingLineContingencyScriptTest.java deleted file mode 100644 index 95e28aeb3c3..00000000000 --- a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/DanglingLineContingencyScriptTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2020, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * SPDX-License-Identifier: MPL-2.0 - */ -package com.powsybl.contingency.dsl; - -import com.google.common.jimfs.Configuration; -import com.google.common.jimfs.Jimfs; -import com.powsybl.contingency.Contingency; -import com.powsybl.contingency.ContingencyElement; -import com.powsybl.contingency.DanglingLineContingency; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.test.DanglingLineNetworkFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Sebastien Murgey {@literal } - */ -class DanglingLineContingencyScriptTest { - - private FileSystem fileSystem; - - private Path dslFile; - - private Network network; - - @BeforeEach - void setUp() { - fileSystem = Jimfs.newFileSystem(Configuration.unix()); - dslFile = fileSystem.getPath("/test.dsl"); - network = DanglingLineNetworkFactory.create(); - } - - @AfterEach - void tearDown() throws Exception { - fileSystem.close(); - } - - private void writeToDslFile(String... lines) throws IOException { - try (Writer writer = Files.newBufferedWriter(dslFile, StandardCharsets.UTF_8)) { - writer.write(String.join(System.lineSeparator(), lines)); - } - } - - @Test - void test() throws IOException { - writeToDslFile("contingency('DL_CONTINGENCY') {", - " equipments 'DL'", - "}"); - List contingencies = new GroovyDslContingenciesProvider(dslFile) - .getContingencies(network); - assertEquals(1, contingencies.size()); - Contingency contingency = contingencies.get(0); - assertEquals("DL_CONTINGENCY", contingency.getId()); - assertEquals(0, contingency.getExtensions().size()); - assertEquals(1, contingency.getElements().size()); - ContingencyElement element = contingency.getElements().iterator().next(); - assertTrue(element instanceof DanglingLineContingency); - assertEquals("DL", element.getId()); - } -} diff --git a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ThreeWindingsTransformerContingencyScriptTest.java b/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ThreeWindingsTransformerContingencyScriptTest.java deleted file mode 100644 index 4b20fab7ba8..00000000000 --- a/contingency/contingency-dsl/src/test/java/com/powsybl/contingency/dsl/ThreeWindingsTransformerContingencyScriptTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2021, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * SPDX-License-Identifier: MPL-2.0 - */ -package com.powsybl.contingency.dsl; - -import com.google.common.jimfs.Configuration; -import com.google.common.jimfs.Jimfs; -import com.powsybl.contingency.Contingency; -import com.powsybl.contingency.ContingencyElement; -import com.powsybl.contingency.ThreeWindingsTransformerContingency; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Yichen TANG {@literal } - */ -class ThreeWindingsTransformerContingencyScriptTest { - - private FileSystem fileSystem; - - private Path dslFile; - - private Network network; - - @BeforeEach - void setUp() { - fileSystem = Jimfs.newFileSystem(Configuration.unix()); - dslFile = fileSystem.getPath("/test.dsl"); - network = ThreeWindingsTransformerNetworkFactory.create(); - } - - @AfterEach - void tearDown() throws Exception { - fileSystem.close(); - } - - private void writeToDslFile(String... lines) throws IOException { - try (Writer writer = Files.newBufferedWriter(dslFile, StandardCharsets.UTF_8)) { - writer.write(String.join(System.lineSeparator(), lines)); - } - } - - @Test - void test() throws IOException { - writeToDslFile("contingency('3WT_CONTINGENCY') {", - " equipments '3WT'", - "}"); - List contingencies = new GroovyDslContingenciesProvider(dslFile) - .getContingencies(network); - assertEquals(1, contingencies.size()); - Contingency contingency = contingencies.get(0); - assertEquals("3WT_CONTINGENCY", contingency.getId()); - assertEquals(0, contingency.getExtensions().size()); - assertEquals(1, contingency.getElements().size()); - ContingencyElement element = contingency.getElements().iterator().next(); - assertTrue(element instanceof ThreeWindingsTransformerContingency); - assertEquals("3WT", element.getId()); - } -} diff --git a/docs/grid_exchange_formats/cgmes/export.md b/docs/grid_exchange_formats/cgmes/export.md index 233a0f4f745..b6510362016 100644 --- a/docs/grid_exchange_formats/cgmes/export.md +++ b/docs/grid_exchange_formats/cgmes/export.md @@ -391,6 +391,11 @@ This property is set to `true` by default. Optional property that defines whether all OperationalLimitsGroup should be exported, or only the selected (active) ones. This property is set to `true` by default, which means all groups are exported (not only the active ones). +**iidm.export.cgmes.export-generators-in-local-regulation-mode** +Optional property that allows to export voltage regulating generators in local regulation mode. This doesn't concern reactive power regulating generators. +If set to true, the generator's regulating terminal is set to the generator's own terminal and the target voltage is rescaled accordingly. +This property is set to `false` by default. + **iidm.export.cgmes.max-p-mismatch-converged** Optional property that defines the threshold below which a bus is considered to be balanced for the load flow status of the `TopologicalIsland` in active power. If the sum of all the active power of the terminals connected to the bus is greater than this threshold, then the load flow is considered to be divergent. Its default value is `0.1`, and it should be used only if the `iidm.export.cgmes.export-load-flow-status` property is set to `true`. diff --git a/docs/simulation/security/contingency-dsl.md b/docs/simulation/security/contingency-dsl.md index 542c67464f0..94a844df548 100644 --- a/docs/simulation/security/contingency-dsl.md +++ b/docs/simulation/security/contingency-dsl.md @@ -1,17 +1,26 @@ # Contingency DSL -The contingency DSL is a domain specific language written in groovy for the creation of a contingency list, used in [security analyses](./index.md) or [sensitivity analyses](../sensitivity/index). At the moment, it's possible to simulate the loss of a generator, a static VAR compensator, a shunt, a power line, a power transformer, an HVDC line or a busbar section. + +The contingency DSL is a domain specific language written in groovy for the creation of a contingency list, used +in [security analyses](./index.md) or [sensitivity analyses](../sensitivity/index). ## N-1 contingency -A N-1 contingency is a contingency that triggers a single piece of equipment at a time. + +An N-1 contingency is a contingency that triggers a single piece of equipment at a time. + ```groovy contingency('contingencyID') { equipments 'equipmentID' } ``` -where the `contingencyID` is the identifier of the contingency and the `equipmentID` is the identifier of a supported piece of equipment. If the equipment doesn't exist or is not supported, an error will occur. + +where the `contingencyID` is the identifier of the contingency and the `equipmentID` is the identifier of a supported +piece of equipment. If the equipment doesn't exist or is not supported, an error will occur. ## N-K contingency -A N-K contingency is a contingency that triggers several equipments at a time. The syntax is the same as for the N-1 contingencies, except that you have to pass a list of equipments' IDs. + +An N-K contingency is a contingency that triggers several equipments at a time. The syntax is the same as for the N-1 +contingencies, except that you have to pass a list of equipments' IDs. + ```groovy contingency('contingencyID') { equipments 'equipment1', 'equipment2' @@ -19,7 +28,10 @@ contingency('contingencyID') { ``` ## Manual contingency list -A manual contingency list is a set of contingencies that are explicitly defined. In the following example, the list contains two contingencies that trigger respectively the equipment `equipment1` and `equipment2`: + +A manual contingency list is a set of contingencies that are explicitly defined. In the following example, the list +contains two contingencies that trigger respectively the equipment `equipment1` and `equipment2`: + ```groovy contingency('contingency1') { equipments 'equipment1' @@ -31,9 +43,14 @@ contingency('contingency2') { ``` ## Automatic contingency list -As the DSL is written in Groovy, it's possible to write a more complex script. For example, it's possible to iterate over the equipment of the network to generate the contingency list. The network is accessible using the `network` variable. -The following example creates a N-1 contingency for each line of the network. We use the ID of the lines as identifier for the contingencies. +As the DSL is written in Groovy, it's possible to write a more complex script. For example, it's possible to iterate +over the equipment of the network to generate the contingency list. The network is accessible using the `network` +variable. + +The following example creates an N-1 contingency for each line of the network. We use the ID of the lines as identifier +for the contingencies. + ```groovy for (l in network.lines) { contingency(l.id) { @@ -43,6 +60,7 @@ for (l in network.lines) { ``` It's also possible to filter the lines, for example, to consider on the boundary lines: + ```groovy import com.powsybl.iidm.network.Country @@ -58,6 +76,7 @@ for (l in network.lines) { ``` The following example creates a list of contingencies for all 380 kV lines: + ```groovy for (l in network.lines) { nominalVoltage1 = l.terminal1.voltageLevel.nominalV @@ -70,62 +89,71 @@ for (l in network.lines) { } ``` -In the following example, we use the [Stream API](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/stream/package-summary.html) to create a list of contingencies with the 3 first 225 kV french lines: +In the following example, we use +the [Stream API](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/stream/package-summary.html) to +create a list of contingencies with the 3 first 225 kV french lines: + ```groovy import com.powsybl.iidm.network.Country network.lineStream - .filter({l -> l.terminal1.voltageLevel.substation.country == Country.FR}) - .filter({l -> l.terminal2.voltageLevel.substation.country == Country.FR}) - .filter({l -> l.terminal1.voltageLevel.nominalV == 225}) - .filter({l -> l.terminal2.voltageLevel.nominalV == 225}) - .limit(3) - .forEach({l -> - contingency(l.id) { - equipments l.id - } - }) + .filter({ l -> l.terminal1.voltageLevel.substation.country == Country.FR }) + .filter({ l -> l.terminal2.voltageLevel.substation.country == Country.FR }) + .filter({ l -> l.terminal1.voltageLevel.nominalV == 225 }) + .filter({ l -> l.terminal2.voltageLevel.nominalV == 225 }) + .limit(3) + .forEach({ l -> + contingency(l.id) { + equipments l.id + } + }) ``` -The following example creates a list of contingencies with the 3 first French nuclear generators with a maximum power greater than 1000 MW. +The following example creates a list of contingencies with the 3 first French nuclear generators with a maximum power +greater than 1000 MW. + ```groovy import com.powsybl.iidm.network.Country import com.powsybl.iidm.network.EnergySource network.generatorStream - .filter({g -> g.terminal.voltageLevel.substation.country == Country.FR}) - .filter({g -> g.energySource == EnergySource.NUCLEAR}) - .filter({g -> g.maxP > 1000}) - .limit(3) - .forEach({g -> - contingency(g.id) { - equipments g.id - } - }) + .filter({ g -> g.terminal.voltageLevel.substation.country == Country.FR }) + .filter({ g -> g.energySource == EnergySource.NUCLEAR }) + .filter({ g -> g.maxP > 1000 }) + .limit(3) + .forEach({ g -> + contingency(g.id) { + equipments g.id + } + }) ``` ## Configuration + To provide a contingency list using this DSL, you have to add the following lines to your configuration file: **YAML configuration:** + ```yaml componentDefaultConfig: - ContingenciesProviderFactory: com.powsybl.contingency.dsl.GroovyDslContingenciesProviderFactory + ContingenciesProviderFactory: com.powsybl.contingency.dsl.GroovyDslContingenciesProviderFactory groovy-dsl-contingencies: - dsl-file: /path/to/contingencies.groovy + dsl-file: /path/to/contingencies.groovy ``` **XML configuration:** + ```xml com.powsybl.contingency.dsl.GroovyDslContingenciesProviderFactory - /path/to/contingencies.groovy + /path/to/contingencies.groovy ``` ## Going further + - [Action DSL](action-dsl.md): Lean how to write scripts for security analyses with remedial actions diff --git a/docs/simulation/security/index.md b/docs/simulation/security/index.md index 11c9d8c0476..8209612700c 100644 --- a/docs/simulation/security/index.md +++ b/docs/simulation/security/index.md @@ -9,20 +9,35 @@ action-dsl.md limit-reductions.md ``` -The security analysis is a simulation that checks violations on a network. These checks can be done on the base case or after a contingency, with or without remedial actions. A security analysis can monitor network states, in pre-contingency state, after a contingency and after a remedial action. +The security analysis is a simulation that checks violations on a network. These checks can be done on the base case or +after a contingency, with or without remedial actions. A security analysis can monitor network states, in +pre-contingency state, after a contingency and after a remedial action. -There is a violation if the computed value is greater than the maximum allowed value. Depending on the equipment, the violations can have different types: -- Current, active power and apparent power: this kind of violation can be detected on a branch (line, two-winding transformer, tie line) or on a three-winding transformer, if the computed value is greater than its [permanent limit](../../grid_model/additional.md#loading-limits) or one of its [temporary limits](../../grid_model/additional.md#loading-limits). -- Voltage: this kind of violation can be detected on a bus or a bus bar section, if the computed voltage is out of the low-high voltage limits of a [voltage level](../../grid_model/network_subnetwork.md#voltage-level). -- Voltage angle: this kind of violation can be detected if the voltage angle difference between the buses associated to two terminals is out of the low-high voltage angle limits defined in the network. +There is a violation if the computed value is greater than the maximum allowed value. Depending on the equipment, the +violations can have different types: + +- Current, active power and apparent power: this kind of violation can be detected on a branch (line, two-winding + transformer, tie line) or on a three-winding transformer, if the computed value is greater than + its [permanent limit](../../grid_model/additional.md#loading-limits) or one of + its [temporary limits](../../grid_model/additional.md#loading-limits). +- Voltage: this kind of violation can be detected on a bus or a bus bar section, if the computed voltage is out of the + low-high voltage limits of a [voltage level](../../grid_model/network_subnetwork.md#voltage-level). +- Voltage angle: this kind of violation can be detected if the voltage angle difference between the buses associated to + two terminals is out of the low-high voltage angle limits defined in the network. ## Inputs ### Network -The first input of the security analysis is a network. As this simulation is based on a [load flow](../loadflow/index) engine for a list of contingencies, this network should converge in the pre-contingency state. + +The first input of the security analysis is a network. As this simulation is based on a [load flow](../loadflow/index) +engine for a list of contingencies, this network should converge in the pre-contingency state. ### Contingencies -The security analysis needs a list of contingencies as an input. When contingencies are provided, the violations are detected on the network at pre-contingency state, but also after applying each contingency. The supported elementary contingencies are: + +The security analysis needs a list of contingencies as an input. When contingencies are provided, the violations are +detected on the network at pre-contingency state, but also after applying each contingency. The supported elementary +contingencies are: + - Generator contingency - Static var compensator contingency - Load contingency @@ -30,12 +45,19 @@ The security analysis needs a list of contingencies as an input. When contingenc - Busbar section contingency for node/breaker topologies - Line, two-winding transformer and tie line contingencies (branch contingency) - Three-winding transformer contingency +- Dangling line contingency - HVDC line contingency +- Battery contingency +- Shunt Compensator contingency -A contingency is made of contingency elements. A contingency can trigger one element at a time (N-1) or several elements at a time (N-K). Bus bar and bus contingencies are special N-K contingencies as they trigger all the equipments connected to a given bus bar section. +A contingency is made of contingency elements. A contingency can trigger one element at a time (N-1) or several elements +at a time (N-K). Bus bar and bus contingencies are special N-K contingencies as they trigger all the equipments +connected to a given bus bar section. ### Remedial actions + Remedial actions are actions that are applied when limit violations occur. Supported actions are: + - Open or close a switch - Open or close a terminal - Change the tap of a tap changer (phase or ratio) @@ -44,36 +66,62 @@ Remedial actions are actions that are applied when limit violations occur. Suppo - Change the regulation status of a tap changer - Change `targetP`, `targetQ`, regulation status and `targetV` of a generator - Change the regulation mode of a static var compensator and its associated set point. -- Enabled or disabled AC emulation for HVDC line (with the possibility to change `P0` and `droop` for AC emulation and active power set point and converter mode for set point operating mode) +- Enabled or disabled AC emulation for HVDC line (with the possibility to change `P0` and `droop` for AC emulation and + active power set point and converter mode for set point operating mode) - Change the interchange target of an area by specifying a new interchange target in MW. Remedial actions can be *preventive* or *curative*: -- preventive: these actions are implemented before the violation occurs, for example if the flow of a monitored line is between `90%` and `100%`. Use contingency context `NONE` for that. -- curative: these actions are implemented after a violation occurs, for example, if the flow of the monitored line is greater than `100%`. + +- preventive: these actions are implemented before the violation occurs, for example if the flow of a monitored line is + between `90%` and `100%`. Use contingency context `NONE` for that. +- curative: these actions are implemented after a violation occurs, for example, if the flow of the monitored line is + greater than `100%`. ### Conditions + Actions are applied if a condition is met. The conditions can be diversified and extended in the future: + - True condition: meaning that the list of actions is applied. -- All violations condition on a list of elements: meaning that the list of actions is applied only if all elements provided are overloaded. -- At least one violation condition: meaning that the list of actions is applied only if a violation occurs on the network. -- Any violation condition on a list of elements: meaning that the list of actions is applied if one or more elements provided are overloaded. +- All violations condition on a list of elements: meaning that the list of actions is applied only if all elements + provided are overloaded. +- At least one violation condition: meaning that the list of actions is applied only if a violation occurs on the + network. +- Any violation condition on a list of elements: meaning that the list of actions is applied if one or more elements + provided are overloaded. ### Operator strategies -An operator strategy is applied in pre-contingency or after a contingency, depending on the contingency context provided. A contingency context can be a pre-contingency state only (`NONE`), a post-contingency state (on a specific contingency (`SPECIFIC`) or on every contingency (`ONLY_CONTINGENCIES`)) or both pre-contingency and post-contingency states (`ALL`). + +An operator strategy is applied in pre-contingency or after a contingency, depending on the contingency context +provided. A contingency context can be a pre-contingency state only (`NONE`), a post-contingency state (on a specific +contingency (`SPECIFIC`) or on every contingency (`ONLY_CONTINGENCIES`)) or both pre-contingency and post-contingency +states (`ALL`). An operator strategy groups a condition and a list of remedial actions. ### State monitors -A stateMonitor allows getting information about branch, bus and three-winding transformers on the network after a security analysis computation. Contingency context allows to specify if the information asked are about pre-contingency state or post-contingency state with a contingency id or both. For example: -- If we want information about a branch after security analysis on contingency `c1`, the contingencyContext will contain the contingencyId `c1`, contextType `SPECIFIC` and the state monitor will contain the id of the branch. -- If we want information about a branch in pre-contingency state, the contingencyContext will contain a null contingencyId, contextType `NONE` and the state monitor will contain the id of the branch. -- If we want information about a branch in pre-contingency state and after security analysis on contingency `c1`, the contingencyContext will contain contingencyId `c1`, contextType `ALL` and the state monitor will contain the id of the branch. + +A stateMonitor allows getting information about branch, bus and three-winding transformers on the network after a +security analysis computation. Contingency context allows to specify if the information asked are about pre-contingency +state or post-contingency state with a contingency id or both. For example: + +- If we want information about a branch after security analysis on contingency `c1`, the contingencyContext will contain + the contingencyId `c1`, contextType `SPECIFIC` and the state monitor will contain the id of the branch. +- If we want information about a branch in pre-contingency state, the contingencyContext will contain a null + contingencyId, contextType `NONE` and the state monitor will contain the id of the branch. +- If we want information about a branch in pre-contingency state and after security analysis on contingency `c1`, the + contingencyContext will contain contingencyId `c1`, contextType `ALL` and the state monitor will contain the id of the + branch. ### Limit reductions -Limit reductions can be specified in order to detect when a specific limit is **nearly** reached, without having to artificially modify the limit itself. -For instance, with a limit reduction set to 95% for a limit of 1000 MW, the security analysis will flag a limit violation for any value exceeding 950 MW. -Each limit reduction has its own criteria specifying for which limits and under what conditions it should be applied. These criteria can include: +Limit reductions can be specified in order to detect when a specific limit is **nearly** reached, without having to +artificially modify the limit itself. +For instance, with a limit reduction set to 95% for a limit of 1000 MW, the security analysis will flag a limit +violation for any value exceeding 950 MW. + +Each limit reduction has its own criteria specifying for which limits and under what conditions it should be applied. +These criteria can include: + - the type of limit (current, active power or apparent power); - the use case (for monitoring only or also for applying remedial actions); - the contingency context (pre-contingency, after a specific contingency or after all contingencies, etc.); @@ -85,25 +133,40 @@ You can find more details about limit reductions [here](./limit-reductions). ## Outputs ### Pre-contingency results -The violations are detected on the network at state N, meaning before a contingency occurred. This determines a reference for the simulation. For each violation, we get the ID of the overloaded equipment, the limit type (`CURRENT`, `ACTIVE_POWER`, `APPARENT_POWER`, `LOW_VOLTAGE` or `HIGH_VOLTAGE`, `LOW_VOLTAGE_ANGLE` or `HIGH_VOLTAGE_ANGLE`), the acceptable value and the computed value. For branches and three-winding transformers, we also have the side where the violation has been detected. -The pre-contingency results also contain the network results based on given state monitors. A network result groups branch results, bus results and three-winding transformer results. All elementary results are fully extendable. +The violations are detected on the network at state N, meaning before a contingency occurred. This determines a +reference for the simulation. For each violation, we get the ID of the overloaded equipment, the limit +type (`CURRENT`, `ACTIVE_POWER`, `APPARENT_POWER`, `LOW_VOLTAGE` or `HIGH_VOLTAGE`, `LOW_VOLTAGE_ANGLE` +or `HIGH_VOLTAGE_ANGLE`), the acceptable value and the computed value. For branches and three-winding transformers, we +also have the side where the violation has been detected. + +The pre-contingency results also contain the network results based on given state monitors. A network result groups +branch results, bus results and three-winding transformer results. All elementary results are fully extendable. ### Post-contingency results -The post-contingency results contain the complete list of the contingencies that have been simulated, and for each of them the violations detected. To limit information to the user, only new violations or worsened violations can be listed. + +The post-contingency results contain the complete list of the contingencies that have been simulated, and for each of +them the violations detected. To limit information to the user, only new violations or worsened violations can be +listed. The post-contingency results also contain the network results based on given state monitors. ### Operator strategy results -The post-contingency results contain the complete list of the contingencies that have been simulated, and for each of them the violations detected in order to check if remedial actions were efficient. + +The post-contingency results contain the complete list of the contingencies that have been simulated, and for each of +them the violations detected in order to check if remedial actions were efficient. The operator strategy results also contain the network results based on given state monitors. ### Extensions -The results of a security analysis are extendable, meaning you can have additional information attached to the network, the contingencies or the violations. + +The results of a security analysis are extendable, meaning you can have additional information attached to the network, +the contingencies or the violations. ### Example + The following example is a result of a security analysis with remedial action, exported in JSON: + ```json { "version" : "1.4", @@ -285,5 +348,6 @@ The following example is a result of a security analysis with remedial action, e ``` ## Going further + - Different implementations are available to run security analyses on [page](implementations.md). - [Run a security analysis through an iTools command](../../user/itools/security-analysis.md): Learn how to perform a security analysis from the command line. diff --git a/docs/simulation/shortcircuit/parameters.md b/docs/simulation/shortcircuit/parameters.md index 6f53f70e9c4..0c312cd46ac 100644 --- a/docs/simulation/shortcircuit/parameters.md +++ b/docs/simulation/shortcircuit/parameters.md @@ -106,25 +106,31 @@ This property defines the voltage profile that should be used for the calculatio **voltage-ranges** This property specifies a path to a JSON file containing the voltage ranges and associated coefficients to be used when `initial-voltage-profile-mode` is set to `CONFIGURED`. -The JSON file must contain a list of voltage ranges and coefficients. Then, for each nominal voltage in the network that belongs to the range, the given coefficient is applied to calculate the voltage to be used -in the calculation. All the coefficients should be between 0.8 and 1.2. +The JSON file must contain a list of voltage ranges and for each range, coefficients and/or voltages. +Then, for each nominal voltage in the network that belongs to the range, the coefficient is applied to calculate the voltage to be used +in the calculation. All the coefficients should be between 0.8 and 1.2. They are optional, and if they are missing for a range, 1 will be used. +The voltage attribute of the voltage range defines the nominal voltage of all the voltage levels in the network that are in the range. +It is also optional, and if it is not defined, then the nominal voltage already in the network will be used. Here is an example of this JSON file: ````json [ { "minimumNominalVoltage": 350.0, "maximumNominalVoltage": 400.0, - "voltageRangeCoefficient": 1.1 + "voltageRangeCoefficient": 1.1, + "voltage": 380.0 }, { "minimumNominalVoltage": 215.0, "maximumNominalVoltage": 235.0, - "voltageRangeCoefficient": 1.2 + "voltageRangeCoefficient": 1.2, + "voltage": 225.0 }, { "minimumNominalVoltage": 80.0, "maximumNominalVoltage": 150.0, - "voltageRangeCoefficient": 1.05 + "voltageRangeCoefficient": 1.05, + "voltage": 105.0 } ] ```` diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Boundary.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Boundary.java index 660f7f3ddac..8a257551f5d 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Boundary.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Boundary.java @@ -32,6 +32,11 @@ public interface Boundary { */ double getQ(); + /** + * Get the current in A at the fictitious boundary terminal. + */ + double getI(); + /** * Get the danglingLine the boundary is associated to. */ diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ShuntCompensatorNonLinearModelAdder.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ShuntCompensatorNonLinearModelAdder.java index 727c80f492c..2a1420cd972 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ShuntCompensatorNonLinearModelAdder.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/ShuntCompensatorNonLinearModelAdder.java @@ -20,7 +20,7 @@ interface SectionAdder { SectionAdder setB(double b); /*** - * Set the accumulated conductance is S when the section to be added is the last section in service. + * Set the accumulated conductance in S when the section to be added is the last section in service. * If the accumulated conductance is undefined, its conductance per section is considered equal to 0: * it is equal to the accumulated conductance of the previous section. */ diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java index 47ab9a2cfd3..8da19d0c3cc 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java @@ -11,6 +11,7 @@ import com.powsybl.math.graph.TraversalType; import com.powsybl.math.graph.TraverseResult; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -252,4 +253,11 @@ static Terminal getTerminal(Identifiable identifiable, ThreeSides side) { } ThreeSides getSide(); + + /** + * Retrieves a list of objects that refer to the terminal. + * + * @return a list of referrer objects. + */ + List getReferrers(); } diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java index bf68d246b1f..7b90cfe67e1 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java @@ -86,6 +86,20 @@ public double getQ() { } } + @Override + public double getI() { + if (useHypothesis(parent)) { + return Math.hypot(getP(), getQ()) / (Math.sqrt(3.) * getV() / 1000); + } + Terminal t = parent.getTerminal(); + Bus b = t.getBusView().getBus(); + if (zeroImpedance(parent)) { + return t.getI(); + } else { + return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideI(parent, false); + } + } + @Override public DanglingLine getDanglingLine() { return parent; diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/SV.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/SV.java index fbecdf10d0e..3c9edfb461c 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/SV.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/SV.java @@ -68,6 +68,10 @@ public double getA() { return a; } + public double getI() { + return Math.hypot(p, q) / (Math.sqrt(3.) * u / 1000); + } + public TwoSides getSide() { return side; } @@ -162,6 +166,14 @@ public double otherSideA(DanglingLine dl, boolean splitShuntAdmittance) { return otherSide(dl, splitShuntAdmittance).getA(); } + public double otherSideI(DanglingLine dl) { + return otherSide(dl).getI(); + } + + public double otherSideI(DanglingLine dl, boolean splitShuntAdmittance) { + return otherSide(dl, splitShuntAdmittance).getI(); + } + private static double getRho(TwoWindingsTransformer twt) { double rho = twt.getRatedU2() / twt.getRatedU1(); if (twt.getRatioTapChanger() != null) { diff --git a/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/SVTest.java b/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/SVTest.java index 3cad5c657be..36ba07adf56 100644 --- a/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/SVTest.java +++ b/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/SVTest.java @@ -59,11 +59,13 @@ void testDanglingLine() { double q1 = -77.444122; double v1 = 118.13329315185547; double a1 = 0.19568365812301636; + double i1 = 726.224579; double p2 = 15.098317; double q2 = 64.333028; double v2 = 138.0; double a2 = 0.0; + double i2 = 276.462893; SV svA1 = new SV(p1, q1, v1, a1, TwoSides.ONE); SV svA2 = svA1.otherSide(dl); @@ -71,6 +73,7 @@ void testDanglingLine() { assertEquals(q2, svA2.getQ(), tol); assertEquals(v2, svA2.getU(), tol); assertEquals(a2, svA2.getA(), tol); + assertEquals(i2, svA2.getI(), tol); SV svB2 = new SV(p2, q2, v2, a2, TwoSides.TWO); SV svB1 = svB2.otherSide(dl); @@ -78,26 +81,31 @@ void testDanglingLine() { assertEquals(q1, svB1.getQ(), tol); assertEquals(v1, svB1.getU(), tol); assertEquals(a1, svB1.getA(), tol); + assertEquals(i1, svB1.getI(), tol); assertEquals(p2, svA1.otherSideP(dl), tol); assertEquals(q2, svA1.otherSideQ(dl), tol); assertEquals(v2, svA1.otherSideU(dl), tol); assertEquals(a2, svA1.otherSideA(dl), tol); + assertEquals(i2, svA1.otherSideI(dl), tol); assertEquals(p1, svB2.otherSideP(dl), tol); assertEquals(q1, svB2.otherSideQ(dl), tol); assertEquals(v1, svB2.otherSideU(dl), tol); assertEquals(a1, svB2.otherSideA(dl), tol); + assertEquals(i1, svB2.otherSideI(dl), tol); assertEquals(p2, svA1.otherSideP(dl, false), tol); assertEquals(q2, svA1.otherSideQ(dl, false), tol); assertEquals(v2, svA1.otherSideU(dl, false), tol); assertEquals(a2, svA1.otherSideA(dl, false), tol); + assertEquals(i2, svA1.otherSideI(dl, false), tol); assertEquals(p1, svB2.otherSideP(dl, false), tol); assertEquals(q1, svB2.otherSideQ(dl, false), tol); assertEquals(v1, svB2.otherSideU(dl, false), tol); assertEquals(a1, svB2.otherSideA(dl, false), tol); + assertEquals(i1, svB2.otherSideI(dl, false), tol); } @Test diff --git a/iidm/iidm-extensions/src/main/java/com/powsybl/iidm/network/extensions/RemoteReactivePowerControl.java b/iidm/iidm-extensions/src/main/java/com/powsybl/iidm/network/extensions/RemoteReactivePowerControl.java index 81ea14454a1..316b3d6b51b 100644 --- a/iidm/iidm-extensions/src/main/java/com/powsybl/iidm/network/extensions/RemoteReactivePowerControl.java +++ b/iidm/iidm-extensions/src/main/java/com/powsybl/iidm/network/extensions/RemoteReactivePowerControl.java @@ -43,4 +43,6 @@ default String getName() { RemoteReactivePowerControl setTargetQ(double targetQ); RemoteReactivePowerControl setEnabled(boolean enabled); + + RemoteReactivePowerControl setRegulatingTerminal(Terminal regulatingTerminals); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java index 756f6be24a2..4a98b6c6f07 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java @@ -70,7 +70,7 @@ public void remove() { network.getIndex().remove(this); for (TerminalExt terminal : terminals) { - terminal.removeAsRegulationPoint(); + terminal.getReferrerManager().notifyOfRemoval(); VoltageLevelExt vl = terminal.getVoltageLevel(); vl.getTopologyModel().detach(terminal); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java index 837269c396b..6a546dd4046 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java @@ -260,10 +260,17 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { @Override public > boolean removeExtension(Class type) { + return removeExtension(type, true); + } + + public > boolean removeExtension(Class type, boolean cleanup) { E extension = getExtension(type); NetworkListenerList listeners = getNetwork().getListeners(); if (extension != null) { listeners.notifyExtensionBeforeRemoval(extension); + if (cleanup) { + extension.cleanup(); + } removeExtension(type, extension); listeners.notifyExtensionAfterRemoval(this, extension.getName()); return true; diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java new file mode 100644 index 00000000000..f7a91350642 --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.network.impl; + +import com.powsybl.commons.extensions.AbstractExtension; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Terminal; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public abstract class AbstractIidmExtension> extends AbstractExtension implements Referrer { + + protected AbstractIidmExtension(I extendable) { + super(extendable); + } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + // nothing by default + // this is the place for terminal reference cleanup + } +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java index bbae95cfad4..aeb04fda519 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java @@ -8,14 +8,13 @@ package com.powsybl.iidm.network.impl; import com.powsybl.commons.exceptions.UncheckedClassCastExceptionException; -import com.powsybl.commons.extensions.AbstractExtension; import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; /** * @author Florian Dupuy {@literal } */ -public abstract class AbstractMultiVariantIdentifiableExtension> extends AbstractExtension implements MultiVariantObject { +public abstract class AbstractMultiVariantIdentifiableExtension> extends AbstractIidmExtension implements MultiVariantObject { public AbstractMultiVariantIdentifiableExtension(T extendable) { super(extendable); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java index a73725bf9ec..8614a6a3c50 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java @@ -104,7 +104,7 @@ protected static void transferExtensions(Network from, Network to, boolean ignor "an extension of this same class already exists in the destination network.", clazz.getName(), from.getId(), to.getId()); } else { - from.removeExtension((Class>) clazz); + ((AbstractIdentifiable) from).removeExtension((Class>) clazz, false); to.addExtension((Class>) clazz, (Extension) e); } }) diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java index d46a9b1a830..84d370a7c74 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java @@ -256,4 +256,8 @@ private void throwIncorrectTapPosition(int tapPosition, int highTapPosition) { + tapPosition + " [" + lowTapPosition + ", " + highTapPosition + "]"); } + + public void remove() { + regulatingPoint.remove(); + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java index 0f7d2f336e1..2e1d8ad0770 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java @@ -8,12 +8,11 @@ package com.powsybl.iidm.network.impl; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.*; import com.powsybl.commons.ref.Ref; +import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.util.SwitchPredicates; import gnu.trove.list.array.TDoubleArrayList; -import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; @@ -33,7 +32,7 @@ abstract class AbstractTerminal implements TerminalExt { protected VoltageLevelExt voltageLevel; - protected final List regulated = new ArrayList<>(); + protected final ReferrerManager referrerManager = new ReferrerManager<>(this); // attributes depending on the variant @@ -94,12 +93,6 @@ public void setVoltageLevel(VoltageLevelExt voltageLevel) { } } - @Override - public void removeAsRegulationPoint() { - regulated.forEach(RegulatingPoint::removeRegulatingTerminal); - regulated.clear(); - } - @Override public double getP() { if (removed) { @@ -250,12 +243,12 @@ public void remove() { } @Override - public void setAsRegulatingPoint(RegulatingPoint rp) { - regulated.add(rp); + public ReferrerManager getReferrerManager() { + return referrerManager; } @Override - public void removeRegulatingPoint(RegulatingPoint rp) { - regulated.remove(rp); + public List getReferrers() { + return referrerManager.getReferrers().stream().map(r -> (Object) r).toList(); } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java index 89d18793155..123f451ee98 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java @@ -34,31 +34,9 @@ public class AreaImpl extends AbstractIdentifiable implements Area { private final TDoubleArrayList interchangeTarget; - private final class AreaListener extends DefaultNetworkListener { - @Override - public void beforeRemoval(Identifiable identifiable) { - if (identifiable instanceof DanglingLine danglingLine) { - // if dangling line removed from network, remove its boundary from this extension - AreaImpl.this.removeAreaBoundary(danglingLine.getBoundary()); - } else if (identifiable instanceof Connectable connectable) { - // if connectable removed from network, remove its terminals from this extension - connectable.getTerminals().forEach(AreaImpl.this::removeAreaBoundary); - } - // When a VoltageLevel is removed, its areas' voltageLevels attributes are directly updated. This is not managed via this listener. - } - } + private final Referrer terminalReferrer = this::removeAreaBoundary; - /** - * Must be called whenever the Area is moved to a different root Network when merging and detaching. - * @param fromNetwork previous root network - * @param toNetwork new root network - */ - void moveListener(NetworkImpl fromNetwork, NetworkImpl toNetwork) { - fromNetwork.removeListener(this.areaListener); - toNetwork.addListener(this.areaListener); - } - - private final NetworkListener areaListener; + private final Referrer boundaryReferrer = this::removeAreaBoundary; AreaImpl(Ref ref, Ref subnetworkRef, String id, String name, boolean fictitious, String areaType, double interchangeTarget) { @@ -74,8 +52,6 @@ void moveListener(NetworkImpl fromNetwork, NetworkImpl toNetwork) { for (int i = 0; i < variantArraySize; i++) { this.interchangeTarget.add(interchangeTarget); } - this.areaListener = new AreaListener(); - getNetwork().addListener(this.areaListener); } @Override @@ -229,8 +205,14 @@ public Stream getAreaBoundaryStream() { protected void addAreaBoundary(AreaBoundaryImpl areaBoundary) { Optional terminal = areaBoundary.getTerminal(); Optional boundary = areaBoundary.getBoundary(); - boundary.ifPresent(b -> checkBoundaryNetwork(b.getDanglingLine().getParentNetwork(), "Boundary of DanglingLine" + b.getDanglingLine().getId())); - terminal.ifPresent(t -> checkBoundaryNetwork(t.getConnectable().getParentNetwork(), "Terminal of connectable " + t.getConnectable().getId())); + boundary.ifPresent(b -> { + checkBoundaryNetwork(b.getDanglingLine().getParentNetwork(), "Boundary of DanglingLine" + b.getDanglingLine().getId()); + ((DanglingLineBoundaryImplExt) b).getReferrerManager().register(boundaryReferrer); + }); + terminal.ifPresent(t -> { + checkBoundaryNetwork(t.getConnectable().getParentNetwork(), "Terminal of connectable " + t.getConnectable().getId()); + ((TerminalExt) t).getReferrerManager().register(terminalReferrer); + }); areaBoundaries.add(areaBoundary); } @@ -248,8 +230,11 @@ public void remove() { for (VoltageLevel voltageLevel : new HashSet<>(voltageLevels)) { voltageLevel.removeArea(this); } + for (AreaBoundary areaBoundary : areaBoundaries) { + areaBoundary.getTerminal().ifPresent(t -> ((TerminalExt) t).getReferrerManager().unregister(terminalReferrer)); + areaBoundary.getBoundary().ifPresent(b -> ((DanglingLineBoundaryImplExt) b).getReferrerManager().unregister(boundaryReferrer)); + } network.getListeners().notifyAfterRemoval(id); - network.removeListener(this.areaListener); removed = true; } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineBoundaryImplExt.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineBoundaryImplExt.java new file mode 100644 index 00000000000..46a0e144960 --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineBoundaryImplExt.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.network.impl; + +import com.powsybl.iidm.network.Boundary; +import com.powsybl.iidm.network.DanglingLine; +import com.powsybl.iidm.network.util.DanglingLineBoundaryImpl; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class DanglingLineBoundaryImplExt extends DanglingLineBoundaryImpl { + + protected final ReferrerManager referrerManager = new ReferrerManager<>(this); + + public DanglingLineBoundaryImplExt(DanglingLine parent) { + super(parent); + } + + public ReferrerManager getReferrerManager() { + return referrerManager; + } + + void remove() { + referrerManager.notifyOfRemoval(); + } +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java index 567dfe44356..a92f8d6a8a9 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java @@ -10,7 +10,6 @@ import com.powsybl.commons.util.trove.TBooleanArrayList; import com.powsybl.iidm.network.*; import com.powsybl.commons.ref.Ref; -import com.powsybl.iidm.network.util.DanglingLineBoundaryImpl; import gnu.trove.list.array.TDoubleArrayList; import java.util.Collection; @@ -259,7 +258,7 @@ void allocateVariantArrayElement(int[] indexes, int sourceIndex) { private final TDoubleArrayList q0; - private final DanglingLineBoundaryImpl boundary; + private final DanglingLineBoundaryImplExt boundary; DanglingLineImpl(Ref network, String id, String name, boolean fictitious, double p0, double q0, double r, double x, double g, double b, String pairingKey, GenerationImpl generation) { super(network, id, name, fictitious); @@ -277,7 +276,7 @@ void allocateVariantArrayElement(int[] indexes, int sourceIndex) { this.b = b; this.pairingKey = pairingKey; this.operationalLimitsGroups = new OperationalLimitsGroupsImpl(this, "limits"); - this.boundary = new DanglingLineBoundaryImpl(this); + this.boundary = new DanglingLineBoundaryImplExt(this); this.generation = generation != null ? generation.attach(this) : null; } @@ -309,6 +308,7 @@ public void remove() { throw new UnsupportedOperationException("Parent tie line " + tieLine.getId() + " should be removed before the child dangling line"); } super.remove(); + boundary.remove(); } void removeTieLine() { diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java index 54afbc6133c..bac5fd0da46 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java @@ -127,7 +127,7 @@ public GeneratorImpl add() { = new GeneratorImpl(getNetworkRef(), id, getName(), isFictitious(), energySource, minP, maxP, - voltageRegulatorOn, regulatingTerminal != null ? regulatingTerminal : terminal, + voltageRegulatorOn, regulatingTerminal, targetP, targetQ, targetV, ratedS, isCondenser); generator.addTerminal(terminal); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java index a5ba5df417f..93a5b337afa 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java @@ -54,7 +54,7 @@ class GeneratorImpl extends AbstractConnectable implements Generator, this.reactiveLimits = new ReactiveLimitsHolderImpl(this, new MinMaxReactiveLimitsImpl(-Double.MAX_VALUE, Double.MAX_VALUE)); this.ratedS = ratedS; int variantArraySize = network.get().getVariantManager().getVariantArraySize(); - regulatingPoint = new RegulatingPoint(id, this::getTerminal, variantArraySize, voltageRegulatorOn, voltageRegulatorOn); + regulatingPoint = new RegulatingPoint(id, this::getTerminal, variantArraySize, voltageRegulatorOn, true); regulatingPoint.setRegulatingTerminal(regulatingTerminal); this.targetP = new TDoubleArrayList(variantArraySize); this.targetQ = new TDoubleArrayList(variantArraySize); @@ -129,7 +129,6 @@ public GeneratorImpl setVoltageRegulatorOn(boolean voltageRegulatorOn) { voltageRegulatorOn, targetV.get(variantIndex), targetQ.get(variantIndex), n.getMinValidationLevel(), n.getReportNodeContext().getReportNode()); boolean oldValue = regulatingPoint.setRegulating(variantIndex, voltageRegulatorOn); - regulatingPoint.setUseVoltageRegulation(voltageRegulatorOn); String variantId = network.get().getVariantManager().getVariantId(variantIndex); n.invalidateValidationLevel(); notifyUpdate("voltageRegulatorOn", variantId, oldValue, voltageRegulatorOn); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java index 344f3e246bf..31c25e9d926 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java @@ -1015,11 +1015,6 @@ private void merge(Network other) { checkMergeability(otherNetwork); - otherNetwork.getAreaStream().forEach(a -> { - AreaImpl area = (AreaImpl) a; - area.moveListener(otherNetwork, this); - }); - // try to find dangling lines couples List lines = new ArrayList<>(); Map> dl1byPairingKey = new HashMap<>(); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java index b658fd1aaa3..7aa502c70bf 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java @@ -131,7 +131,7 @@ public PhaseTapChangerImpl setRegulationTerminal(Terminal regulationTerminal) { @Override public void remove() { - regulatingPoint.remove(); + super.remove(); parent.setPhaseTapChanger(null); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java index 98843670406..7fbf01b53d4 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java @@ -180,7 +180,7 @@ public RatioTapChangerImpl setRegulationTerminal(Terminal regulationTerminal) { @Override public void remove() { - regulatingPoint.remove(); + super.remove(); parent.setRatioTapChanger(null); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java new file mode 100644 index 00000000000..e9e29e36c4d --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.network.impl; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public interface Referrer { + + /** + * Called when a referenced object is removed because of a connectable removal. + * Implementations of this method should handle any required cleanup or updates + * necessary when the referenced object is no longer part of the network. + * + * @param removedReferenced The referenced that has been removed from the network. + */ + void onReferencedRemoval(T removedReferenced); +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ReferrerManager.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ReferrerManager.java new file mode 100644 index 00000000000..e29931e9783 --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ReferrerManager.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.network.impl; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class ReferrerManager { + + private final T referenced; + + private final List> referrers = new CopyOnWriteArrayList<>(); + + public ReferrerManager(T referenced) { + this.referenced = Objects.requireNonNull(referenced); + } + + public List> getReferrers() { + return referrers; + } + + public void register(Referrer referrer) { + referrers.add(Objects.requireNonNull(referrer)); + } + + public void unregister(Referrer referrer) { + referrers.remove(Objects.requireNonNull(referrer)); + } + + public void notifyOfRemoval() { + for (Referrer referrer : referrers) { + referrer.onReferencedRemoval(referenced); + } + } +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java index 8ca037d0711..54a69e96ecd 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java @@ -10,24 +10,24 @@ import com.powsybl.commons.util.trove.TBooleanArrayList; import com.powsybl.iidm.network.Bus; import com.powsybl.iidm.network.StaticVarCompensator; +import com.powsybl.iidm.network.Terminal; import gnu.trove.list.array.TIntArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Objects; import java.util.function.Supplier; /** * @author Miora Vedelago {@literal } */ -class RegulatingPoint implements MultiVariantObject { +class RegulatingPoint implements MultiVariantObject, Referrer { private static final Logger LOG = LoggerFactory.getLogger(RegulatingPoint.class); private final String regulatedEquipmentId; private final Supplier localTerminalSupplier; - private boolean useVoltageRegulation; - private TerminalExt regulatingTerminal = null; + private final boolean useVoltageRegulation; + private TerminalExt regulatingTerminal; // attributes depending on the variant @@ -58,26 +58,23 @@ class RegulatingPoint implements MultiVariantObject { void setRegulatingTerminal(TerminalExt regulatingTerminal) { if (this.regulatingTerminal != null) { - this.regulatingTerminal.removeRegulatingPoint(this); + this.regulatingTerminal.getReferrerManager().unregister(this); + this.regulatingTerminal = null; } - this.regulatingTerminal = regulatingTerminal != null ? regulatingTerminal : localTerminalSupplier.get(); - if (this.regulatingTerminal != null) { - this.regulatingTerminal.setAsRegulatingPoint(this); + if (regulatingTerminal != null) { + this.regulatingTerminal = regulatingTerminal; + this.regulatingTerminal.getReferrerManager().register(this); } } TerminalExt getRegulatingTerminal() { - return regulatingTerminal; + return regulatingTerminal != null ? regulatingTerminal : localTerminalSupplier.get(); } boolean setRegulating(int index, boolean regulating) { return this.regulating.set(index, regulating); } - void setUseVoltageRegulation(boolean useVoltageRegulation) { - this.useVoltageRegulation = useVoltageRegulation; - } - boolean isRegulating(int index) { return regulating.get(index); } @@ -90,35 +87,6 @@ int getRegulationMode(int index) { return regulationMode.get(index); } - void removeRegulatingTerminal() { - Objects.requireNonNull(regulatingTerminal); - TerminalExt localTerminal = localTerminalSupplier.get(); - if (localTerminal != null && useVoltageRegulation) { // if local voltage regulation, we keep the regulating status, and re-locate the regulation at the regulated equipment - Bus bus = regulatingTerminal.getBusView().getBus(); - Bus localBus = localTerminal.getBusView().getBus(); - if (bus != null && bus == localBus) { - LOG.warn("Connectable {} was a local voltage regulation point for {}. Regulation point is re-located at {}.", regulatingTerminal.getConnectable().getId(), - regulatedEquipmentId, regulatedEquipmentId); - regulatingTerminal = localTerminal; - return; - } - } - LOG.warn("Connectable {} was a regulation point for {}. Regulation is deactivated", regulatingTerminal.getConnectable().getId(), regulatedEquipmentId); - regulatingTerminal = localTerminal; - if (regulating != null) { - regulating.fill(0, regulating.size(), false); - } - if (regulationMode != null) { - regulationMode.fill(0, regulationMode.size(), StaticVarCompensator.RegulationMode.OFF.ordinal()); - } - } - - void remove() { - if (regulatingTerminal != null) { - regulatingTerminal.removeRegulatingPoint(this); - } - } - @Override public void extendVariantArraySize(int initVariantArraySize, int number, int sourceIndex) { if (regulating != null) { @@ -163,4 +131,37 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { } } } + + void remove() { + if (regulatingTerminal != null) { + regulatingTerminal.getReferrerManager().unregister(this); + } + } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + TerminalExt oldRegulatingTerminal = regulatingTerminal; + TerminalExt localTerminal = localTerminalSupplier.get(); + if (localTerminal != null && useVoltageRegulation) { // if local voltage regulation, we keep the regulating status, and re-locate the regulation at the regulated equipment + Bus bus = regulatingTerminal.getBusView().getBus(); + Bus localBus = localTerminal.getBusView().getBus(); + if (bus != null && bus == localBus) { + LOG.warn("Connectable {} was a local voltage regulation point for {}. Regulation point is re-located at {}.", regulatingTerminal.getConnectable().getId(), + regulatedEquipmentId, regulatedEquipmentId); + regulatingTerminal = localTerminal; + return; + } else { + regulatingTerminal = null; + } + } else { + regulatingTerminal = null; + } + LOG.warn("Connectable {} was a regulation point for {}. Regulation is deactivated", oldRegulatingTerminal.getConnectable().getId(), regulatedEquipmentId); + if (regulating != null) { + regulating.fill(0, regulating.size(), false); + } + if (regulationMode != null) { + regulationMode.fill(0, regulationMode.size(), StaticVarCompensator.RegulationMode.OFF.ordinal()); + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java index a83a21c13c4..dd059cab2dd 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java @@ -220,8 +220,7 @@ public ShuntCompensatorImpl add() { network.getMinValidationLevel(), network.getReportNodeContext().getReportNode())); ShuntCompensatorImpl shunt = new ShuntCompensatorImpl(getNetworkRef(), - id, getName(), isFictitious(), modelBuilder.build(), sectionCount, - regulatingTerminal == null ? terminal : regulatingTerminal, + id, getName(), isFictitious(), modelBuilder.build(), sectionCount, regulatingTerminal, voltageRegulatorOn, targetV, targetDeadband); shunt.addTerminal(terminal); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java index af6a95bbb75..5e3a4a7566a 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java @@ -88,8 +88,7 @@ public StaticVarCompensatorImpl add() { network.setValidationLevelIfGreaterThan(ValidationUtil.checkSvcRegulator(this, voltageSetpoint, reactivePowerSetpoint, regulationMode, network.getMinValidationLevel(), network.getReportNodeContext().getReportNode())); StaticVarCompensatorImpl svc = new StaticVarCompensatorImpl(id, name, isFictitious(), bMin, bMax, voltageSetpoint, reactivePowerSetpoint, - regulationMode, regulatingTerminal != null ? regulatingTerminal : terminal, - getNetworkRef()); + regulationMode, regulatingTerminal, getNetworkRef()); svc.addTerminal(terminal); voltageLevel.getTopologyModel().attach(terminal, false); network.getIndex().checkAndAdd(svc); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java index c5835250164..a99f31b2456 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java @@ -136,7 +136,6 @@ public StaticVarCompensatorImpl setRegulationMode(RegulationMode regulationMode) int variantIndex = n.getVariantIndex(); int oldValueOrdinal = regulatingPoint.setRegulationMode(variantIndex, regulationMode != null ? regulationMode.ordinal() : -1); - regulatingPoint.setUseVoltageRegulation(regulationMode == RegulationMode.VOLTAGE); String variantId = n.getVariantManager().getVariantId(variantIndex); n.invalidateValidationLevel(); notifyUpdate("regulationMode", variantId, oldValueOrdinal == -1 ? null : RegulationMode.values()[oldValueOrdinal], regulationMode); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java index 04dad01f24b..52c0cfa5eb5 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java @@ -837,11 +837,6 @@ public Network detach() { detachedNetwork.getVoltageAngleLimitsIndex().put(val.getId(), val); } - detachedNetwork.getAreaStream().forEach(a -> { - AreaImpl area = (AreaImpl) a; - area.moveListener(previousRootNetwork, detachedNetwork); - }); - // We don't control that regulating terminals and phase/ratio regulation terminals are in the same subnetwork // as their network elements (generators, PSTs, ...). It is unlikely that those terminals and their elements // are in different subnetworks but nothing prevents it. For now, we ignore this case, but it may be necessary diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java index 43569f81dad..bbc76742b33 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java @@ -18,7 +18,7 @@ * * @author Geoffroy Jamgotchian {@literal } */ -interface TerminalExt extends Terminal, MultiVariantObject { +public interface TerminalExt extends Terminal, MultiVariantObject { interface BusBreakerViewExt extends BusBreakerView { @@ -55,11 +55,7 @@ interface BusViewExt extends BusView { TopologyPoint getTopologyPoint(); - void removeAsRegulationPoint(); - void remove(); - void setAsRegulatingPoint(RegulatingPoint rp); - - void removeRegulatingPoint(RegulatingPoint rp); + ReferrerManager getReferrerManager(); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java index 3b488a909ae..00a5b76e107 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java @@ -285,4 +285,15 @@ public String getTapChangerAttribute() { protected String getTypeDescription() { return "2 windings transformer"; } + + @Override + public void remove() { + if (ratioTapChanger != null) { + ratioTapChanger.remove(); + } + if (phaseTapChanger != null) { + phaseTapChanger.remove(); + } + super.remove(); + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java index 3c361ab1cdf..a81738f74c6 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java @@ -71,7 +71,7 @@ public VscConverterStationImpl add() { validate(); VscConverterStationImpl converterStation = new VscConverterStationImpl(id, name, isFictitious(), getLossFactor(), getNetworkRef(), - voltageRegulatorOn, reactivePowerSetpoint, voltageSetpoint, regulatingTerminal == null ? terminal : regulatingTerminal); + voltageRegulatorOn, reactivePowerSetpoint, voltageSetpoint, regulatingTerminal); converterStation.addTerminal(terminal); getVoltageLevel().getTopologyModel().attach(terminal, false); network.getIndex().checkAndAdd(converterStation); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java index c2a1704d4f4..166b28678c6 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java @@ -36,7 +36,7 @@ class VscConverterStationImpl extends AbstractHvdcConverterStation implements ReferenceTerminals { - private final class ReferenceTerminalsListener extends DefaultNetworkListener { - @Override - public void beforeRemoval(Identifiable identifiable) { - if (identifiable instanceof Connectable connectable) { - // if connectable removed from network, remove its terminals from this extension - terminalsPerVariant.forEach(referenceTerminals -> connectable.getTerminals().forEach(referenceTerminals::remove)); - } - } - } - - private final NetworkListener referenceTerminalsListener; private final ArrayList> terminalsPerVariant; public ReferenceTerminalsImpl(Network network, Set terminals) { @@ -38,22 +29,51 @@ public ReferenceTerminalsImpl(Network network, Set terminals) { this.terminalsPerVariant = new ArrayList<>( Collections.nCopies(getVariantManagerHolder().getVariantManager().getVariantArraySize(), new LinkedHashSet<>())); setReferenceTerminals(terminals); - this.referenceTerminalsListener = new ReferenceTerminalsListener(); } - @Override - public void setExtendable(Network extendable) { - super.setExtendable(extendable); - if (extendable != null) { - // Add the listener, this will be done both extension creation, but also on extension transfer when merging and detaching. - extendable.getNetwork().addListener(this.referenceTerminalsListener); + private void unregisterReferencedTerminalIfNeeded(int variantIndex) { + // check there is no more same terminal referenced by any variant, unregister it + Set oldTerminals = terminalsPerVariant.get(variantIndex); + for (Terminal oldTerminal : oldTerminals) { + if (terminalsPerVariant.stream() + .flatMap(Collection::stream) + .filter(t -> t == oldTerminal) + .count() == 1) { + ((TerminalExt) oldTerminal).getReferrerManager().unregister(this); + } } } - @Override - protected void cleanup() { - // when extension removed from extendable, remove the listener. This will happen when merging and detaching. - getExtendable().getNetwork().removeListener(this.referenceTerminalsListener); + private void registerReferencedTerminalIfNeeded(Set terminals) { + // if terminal was not already referenced by another variant, register it + for (Terminal terminal : terminals) { + if (terminalsPerVariant.stream() + .flatMap(Collection::stream) + .noneMatch(t -> t == terminal)) { + ((TerminalExt) terminal).getReferrerManager().register(this); + } + } + } + + private void setTerminalsAndUpdateReferences(int variantIndex, Set terminals) { + unregisterReferencedTerminalIfNeeded(variantIndex); + registerReferencedTerminalIfNeeded(terminals); + terminalsPerVariant.set(variantIndex, new LinkedHashSet<>(terminals)); + } + + private void addTerminalsAndUpdateReferences(Set terminals) { + registerReferencedTerminalIfNeeded(terminals); + terminalsPerVariant.add(new LinkedHashSet<>(terminals)); + } + + private void updateTerminalsAndUpdateReferences(int variantIndex, Terminal terminal) { + registerReferencedTerminalIfNeeded(Set.of(terminal)); + terminalsPerVariant.get(variantIndex).add(terminal); + } + + private void removeTerminalsAndUpdateReferences(int variantIndex) { + unregisterReferencedTerminalIfNeeded(variantIndex); + terminalsPerVariant.remove(variantIndex); // remove elements from the top to avoid moves inside the array } @Override @@ -65,12 +85,12 @@ public Set getReferenceTerminals() { public void setReferenceTerminals(Set terminals) { Objects.requireNonNull(terminals); terminals.forEach(t -> checkTerminalInNetwork(t, getExtendable())); - terminalsPerVariant.set(getVariantIndex(), new LinkedHashSet<>(terminals)); + setTerminalsAndUpdateReferences(getVariantIndex(), terminals); } @Override public ReferenceTerminals reset() { - terminalsPerVariant.set(getVariantIndex(), new LinkedHashSet<>()); + setTerminalsAndUpdateReferences(getVariantIndex(), Collections.emptySet()); return this; } @@ -78,7 +98,7 @@ public ReferenceTerminals reset() { public ReferenceTerminals addReferenceTerminal(Terminal terminal) { Objects.requireNonNull(terminal); checkTerminalInNetwork(terminal, getExtendable()); - terminalsPerVariant.get(getVariantIndex()).add(terminal); + updateTerminalsAndUpdateReferences(getVariantIndex(), terminal); return this; } @@ -87,27 +107,27 @@ public void extendVariantArraySize(int initVariantArraySize, int number, int sou terminalsPerVariant.ensureCapacity(terminalsPerVariant.size() + number); Set sourceTerminals = terminalsPerVariant.get(sourceIndex); for (int i = 0; i < number; ++i) { - terminalsPerVariant.add(new LinkedHashSet<>(sourceTerminals)); + addTerminalsAndUpdateReferences(sourceTerminals); } } @Override public void reduceVariantArraySize(int number) { for (int i = 0; i < number; i++) { - terminalsPerVariant.remove(terminalsPerVariant.size() - 1); // remove elements from the top to avoid moves inside the array + removeTerminalsAndUpdateReferences(terminalsPerVariant.size() - 1); // remove elements from the top to avoid moves inside the array } } @Override public void deleteVariantArrayElement(int index) { - terminalsPerVariant.set(index, new LinkedHashSet<>()); + setTerminalsAndUpdateReferences(index, Collections.emptySet()); } @Override public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { Set sourceTerminals = terminalsPerVariant.get(sourceIndex); for (int index : indexes) { - terminalsPerVariant.set(index, new LinkedHashSet<>(sourceTerminals)); + setTerminalsAndUpdateReferences(index, sourceTerminals); } } @@ -127,4 +147,20 @@ private static void checkTerminalInNetwork(Terminal terminal, Network network) { } } } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + for (Set terminals : terminalsPerVariant) { + terminals.remove(removedTerminal); + } + } + + @Override + public void cleanup() { + for (Set terminals : terminalsPerVariant) { + for (Terminal terminal : terminals) { + ((TerminalExt) terminal).getReferrerManager().unregister(this); + } + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java index fe80f5e384b..cb840592d9a 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java @@ -7,6 +7,7 @@ */ package com.powsybl.iidm.network.impl.extensions; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.extensions.AbstractExtensionAdder; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Terminal; @@ -18,11 +19,11 @@ */ public class RemoteReactivePowerControlAdderImpl extends AbstractExtensionAdder implements RemoteReactivePowerControlAdder { - private double targetQ; + private double targetQ = Double.NaN; private Terminal regulatingTerminal; - private boolean enabled; + private boolean enabled = true; protected RemoteReactivePowerControlAdderImpl(final Generator extendable) { super(extendable); @@ -30,6 +31,12 @@ protected RemoteReactivePowerControlAdderImpl(final Generator extendable) { @Override protected RemoteReactivePowerControl createExtension(final Generator extendable) { + if (Double.isNaN(targetQ)) { + throw new PowsyblException("Reactive power target must be set"); + } + if (regulatingTerminal == null) { + throw new PowsyblException("Regulating terminal must be set"); + } return new RemoteReactivePowerControlImpl(extendable, targetQ, regulatingTerminal, enabled); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java index d73f773d934..cd88cec163e 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java @@ -7,34 +7,49 @@ */ package com.powsybl.iidm.network.impl.extensions; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.util.trove.TBooleanArrayList; import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.Terminal; import com.powsybl.iidm.network.extensions.RemoteReactivePowerControl; import com.powsybl.iidm.network.impl.AbstractMultiVariantIdentifiableExtension; +import com.powsybl.iidm.network.impl.TerminalExt; import gnu.trove.list.array.TDoubleArrayList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; /** * @author Bertrand Rix {@literal } */ public class RemoteReactivePowerControlImpl extends AbstractMultiVariantIdentifiableExtension implements RemoteReactivePowerControl { - private TDoubleArrayList targetQ; + private static final Logger LOGGER = LoggerFactory.getLogger(RemoteReactivePowerControlImpl.class); + + private final TDoubleArrayList targetQ; - private final Terminal regulatingTerminal; + private Terminal regulatingTerminal; - private TBooleanArrayList enabled; + private final TBooleanArrayList enabled; public RemoteReactivePowerControlImpl(Generator generator, double targetQ, Terminal regulatingTerminal, boolean enabled) { super(generator); int variantArraySize = getVariantManagerHolder().getVariantManager().getVariantArraySize(); this.targetQ = new TDoubleArrayList(); - this.regulatingTerminal = regulatingTerminal; + this.regulatingTerminal = Objects.requireNonNull(regulatingTerminal); this.enabled = new TBooleanArrayList(variantArraySize); for (int i = 0; i < variantArraySize; i++) { this.targetQ.add(targetQ); this.enabled.add(enabled); } + if (regulatingTerminal.getVoltageLevel().getParentNetwork() != getExtendable().getParentNetwork()) { + throw new PowsyblException("Regulating terminal is not in the right Network (" + + regulatingTerminal.getVoltageLevel().getParentNetwork().getId() + " instead of " + + getExtendable().getParentNetwork().getId() + ")"); + } + ((TerminalExt) regulatingTerminal).getReferrerManager().register(this); } @Override @@ -59,6 +74,20 @@ public Terminal getRegulatingTerminal() { return regulatingTerminal; } + @Override + public RemoteReactivePowerControl setRegulatingTerminal(Terminal regulatingTerminal) { + Objects.requireNonNull(regulatingTerminal); + checkRegulatingTerminal(regulatingTerminal, getExtendable().getTerminal().getVoltageLevel().getNetwork()); + this.regulatingTerminal = regulatingTerminal; + return this; + } + + private static void checkRegulatingTerminal(Terminal regulatingTerminal, Network network) { + if (regulatingTerminal != null && regulatingTerminal.getVoltageLevel().getNetwork() != network) { + throw new PowsyblException("regulating terminal is not part of the same network"); + } + } + @Override public boolean isEnabled() { return enabled.get(getVariantIndex()); @@ -92,4 +121,21 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { enabled.set(index, enabled.get(sourceIndex)); } } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + // we cannot set regulating terminal to null because otherwise extension won't be consistent anymore + // we cannot also as for voltage regulation fallback to a local terminal + // so we just remove the extension + LOGGER.warn("Remove 'RemoteReactivePowerControl' extension of generator '{}', because its regulating terminal has been removed", + getExtendable().getId()); + getExtendable().removeExtension(RemoteReactivePowerControl.class); + } + + @Override + public void cleanup() { + if (regulatingTerminal != null) { + ((TerminalExt) regulatingTerminal).getReferrerManager().unregister(this); + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java index 17b41c7e93c..f33f56abbcf 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java @@ -12,6 +12,7 @@ import com.powsybl.iidm.network.VoltageLevel; import com.powsybl.iidm.network.extensions.SlackTerminal; import com.powsybl.iidm.network.impl.AbstractMultiVariantIdentifiableExtension; +import com.powsybl.iidm.network.impl.TerminalExt; import java.util.ArrayList; import java.util.Collections; @@ -31,6 +32,37 @@ public class SlackTerminalImpl extends AbstractMultiVariantIdentifiableExtension this.setTerminal(terminal); } + private void unregisterReferencedTerminalIfNeeded(int variantIndex) { + // check there is no more same terminal referenced by any variant, unregister it + Terminal oldTerminal = terminals.get(variantIndex); + if (oldTerminal != null && Collections.frequency(terminals, oldTerminal) == 1) { + ((TerminalExt) oldTerminal).getReferrerManager().unregister(this); + } + } + + private void registerReferencedTerminalIfNeeded(Terminal terminal) { + // if terminal was not already referenced by another variant, register it + if (terminal != null && !terminals.contains(terminal)) { + ((TerminalExt) terminal).getReferrerManager().register(this); + } + } + + private void setTerminalAndUpdateReferences(int variantIndex, Terminal terminal) { + unregisterReferencedTerminalIfNeeded(variantIndex); + registerReferencedTerminalIfNeeded(terminal); + terminals.set(variantIndex, terminal); + } + + private void addTerminalAndUpdateReferences(Terminal terminal) { + registerReferencedTerminalIfNeeded(terminal); + terminals.add(terminal); + } + + private void removeTerminalAndUpdateReferences(int variantIndex) { + unregisterReferencedTerminalIfNeeded(variantIndex); + terminals.remove(variantIndex); + } + @Override public Terminal getTerminal() { return terminals.get(getVariantIndex()); @@ -42,7 +74,7 @@ public SlackTerminal setTerminal(Terminal terminal) { throw new PowsyblException("Terminal given is not in the right VoltageLevel (" + terminal.getVoltageLevel().getId() + " instead of " + getExtendable().getId() + ")"); } - terminals.set(getVariantIndex(), terminal); + setTerminalAndUpdateReferences(getVariantIndex(), terminal); return this; } @@ -56,27 +88,44 @@ public void extendVariantArraySize(int initVariantArraySize, int number, int sou terminals.ensureCapacity(terminals.size() + number); Terminal sourceTerminal = terminals.get(sourceIndex); for (int i = 0; i < number; ++i) { - terminals.add(sourceTerminal); + addTerminalAndUpdateReferences(sourceTerminal); } } @Override public void reduceVariantArraySize(int number) { for (int i = 0; i < number; i++) { - terminals.remove(terminals.size() - 1); // remove elements from the top to avoid moves inside the array + removeTerminalAndUpdateReferences(terminals.size() - 1); // remove elements from the top to avoid moves inside the array } } @Override public void deleteVariantArrayElement(int index) { - terminals.set(index, null); + setTerminalAndUpdateReferences(index, null); } @Override public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { Terminal terminalSource = terminals.get(sourceIndex); for (int index : indexes) { - terminals.set(index, terminalSource); + setTerminalAndUpdateReferences(index, terminalSource); + } + } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + int variantIndex = terminals.indexOf(removedTerminal); + if (variantIndex != -1) { + terminals.set(variantIndex, null); + } + } + + @Override + public void cleanup() { + for (Terminal terminal : terminals) { + if (terminal != null) { + ((TerminalExt) terminal).getReferrerManager().unregister(this); + } } } } diff --git a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/TieLineTest.java b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/TieLineTest.java index 83d8fd3c582..7dd6764165c 100644 --- a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/TieLineTest.java +++ b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/TieLineTest.java @@ -57,12 +57,14 @@ void tieLineTest0() { assertTrue(compare(caseSv0.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv0.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv0.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv0.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv0, initialModelCase(TwoSides.TWO, TwoSides.ONE), TwoSides.ONE); assertTrue(compare(caseSv0.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv0.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv0.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv0.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv0.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI())); } @Test @@ -92,12 +94,14 @@ void tieLineTest1() { assertTrue(compare(caseSv1.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv1.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv1.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv1.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv1, initialModelCase(TwoSides.TWO, TwoSides.TWO), TwoSides.TWO); assertTrue(compare(caseSv1.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv1.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv1.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv1.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv1.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI())); } @Test @@ -127,12 +131,14 @@ void tieLineTest2() { assertTrue(compare(caseSv2.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv2.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv2.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv2.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv2, initialModelCase(TwoSides.ONE, TwoSides.ONE), TwoSides.ONE); assertTrue(compare(caseSv2.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv2.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv2.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv2.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv2.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI())); } @Test @@ -162,12 +168,14 @@ void tieLineTest3() { assertTrue(compare(caseSv3.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv3.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv3.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv3.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv3, initialModelCase(TwoSides.ONE, TwoSides.TWO), TwoSides.TWO); assertTrue(compare(caseSv3.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv3.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv3.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv3.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv3.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI())); } @Test @@ -197,12 +205,51 @@ void tieLineWithDifferentNominalVoltageAtEndsTest() { assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); assertTrue(compare(getP(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); assertTrue(compare(getQ(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); SV isvHalf2 = initialHalf2SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.ONE); assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); assertTrue(compare(getP(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); assertTrue(compare(getQ(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); + } + + @Test + void tieLineWithNaNVoltagesTest() { + + // Line1 from node1 to boundaryNode, Line2 from boundaryNode to node2 + CaseSv caseSv = createCaseNaNVoltages(); + Network n = createNetworkWithTieLineWithDifferentNominalVoltageAtEnds(NetworkFactory.findDefault(), TwoSides.TWO, TwoSides.ONE, caseSv); + TieLine tieLine = n.getTieLine("TWO + ONE"); + + SV sv2 = new SV(tieLine.getDanglingLine1().getTerminal().getP(), tieLine.getDanglingLine1().getTerminal().getQ(), + tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(), + tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(), + TwoSides.ONE).otherSide(tieLine); + SV isv2 = initialSv2(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE); + assertTrue(compare(sv2, caseSv.node2, caseSv.line2, TwoSides.ONE, isv2)); + + SV sv1 = new SV(tieLine.getDanglingLine2().getTerminal().getP(), tieLine.getDanglingLine2().getTerminal().getQ(), + tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getV(), + tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getAngle(), + TwoSides.TWO).otherSide(tieLine); + SV isv1 = initialSv1(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE); + assertTrue(compare(sv1, caseSv.node1, caseSv.line1, TwoSides.TWO, isv1)); + + SV isvHalf1 = initialHalf1SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO); + assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine1().getBoundary().getV(), isvHalf1.getU())); + assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA())); + assertTrue(compare(getP(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP())); + assertTrue(compare(getQ(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ())); + assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); + + SV isvHalf2 = initialHalf2SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.ONE); + assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU())); + assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA())); + assertTrue(compare(getP(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP())); + assertTrue(compare(getQ(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ())); + assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI())); } @Test @@ -553,6 +600,14 @@ private static double getQ(LineSv line, TwoSides boundarySide) { } } + private static double getI(LineSv line, TwoSides boundarySide) { + if (boundarySide == TwoSides.ONE) { + return line.getI1(); + } else { + return line.getI2(); + } + } + // We define an error by value to adjust the case. The error is calculated by difference between // the calculated value with both models, the initial model of the case and the current model of the danglingLine // Errors are due to the danglingLine model (it does not allow shunt admittance at both ends) @@ -595,8 +650,8 @@ private static CaseSv createCase0() { NodeSv nodeBoundary = new NodeSv(1.05913402, Math.toDegrees(-0.01700730)); NodeSv node2 = new NodeSv(1.04546576, Math.toDegrees(-0.04168907)); - LineSv line1 = new LineSv(0.32101578, -0.16210107, -0.26328124, 0.00991455); - LineSv line2 = new LineSv(0.26328124, -0.00991455, -0.21700000, -0.12700000); + LineSv line1 = new LineSv(0.32101578, -0.16210107, -0.26328124, 0.00991455, node1.v, nodeBoundary.v); + LineSv line2 = new LineSv(0.26328124, -0.00991455, -0.21700000, -0.12700000, nodeBoundary.v, node2.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } @@ -606,8 +661,8 @@ private static CaseSv createCase1() { NodeSv nodeBoundary = new NodeSv(1.05916756, Math.toDegrees(-0.01702560)); NodeSv node2 = new NodeSv(1.04216358, Math.toDegrees(-0.03946400)); - LineSv line1 = new LineSv(0.32116645, -0.16274609, -0.26342655, 0.01056498); - LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26342655, -0.01056498); + LineSv line1 = new LineSv(0.32116645, -0.16274609, -0.26342655, 0.01056498, node1.v, nodeBoundary.v); + LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26342655, -0.01056498, node2.v, nodeBoundary.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } @@ -617,8 +672,8 @@ private static CaseSv createCase2() { NodeSv nodeBoundary = new NodeSv(1.05998661, Math.toDegrees(-0.01660626)); NodeSv node2 = new NodeSv(1.04634503, Math.toDegrees(-0.04125738)); - LineSv line1 = new LineSv(-0.26335112, 0.01016197, 0.32106283, -0.16270573); - LineSv line2 = new LineSv(0.26335112, -0.01016197, -0.21700000, -0.12700000); + LineSv line1 = new LineSv(-0.26335112, 0.01016197, 0.32106283, -0.16270573, nodeBoundary.v, node1.v); + LineSv line2 = new LineSv(0.26335112, -0.01016197, -0.21700000, -0.12700000, nodeBoundary.v, node2.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } @@ -628,12 +683,11 @@ private static CaseSv createCase3() { NodeSv nodeBoundary = new NodeSv(1.06002014, Math.toDegrees(-0.01662448)); NodeSv node2 = new NodeSv(1.04304009, Math.toDegrees(-0.03903205)); - LineSv line1 = new LineSv(-0.26349561, 0.01081185, 0.32121215, -0.16335034); - LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26349561, -0.01081185); + LineSv line1 = new LineSv(-0.26349561, 0.01081185, 0.32121215, -0.16335034, nodeBoundary.v, node1.v); + LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26349561, -0.01081185, node2.v, nodeBoundary.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } - // Line1 from nodeBoundary to node1, Line2 from node2 to nodeBoundary // Line1 from node1 to nodeBoundary, Line2 from nodeBoundary to node2 // Different nominal voltage at node1 and node2 private static CaseSv createCaseDifferentNominalVoltageAtEnds() { @@ -641,8 +695,21 @@ private static CaseSv createCaseDifferentNominalVoltageAtEnds() { NodeSv nodeBoundary = new NodeSv(145.42378472578227, Math.toDegrees(-0.02324020)); NodeSv node2 = new NodeSv(231.30269602522478, Math.toDegrees(-0.02818192)); - LineSv line1 = new LineSv(11.729938, -8.196614, -11.713527, 1.301712); - LineSv line2 = new LineSv(11.713527, -1.301712, -11.700000, -6.700000); + LineSv line1 = new LineSv(11.729938, -8.196614, -11.713527, 1.301712, node1.v, nodeBoundary.v); + LineSv line2 = new LineSv(11.713527, -1.301712, -11.700000, -6.700000, nodeBoundary.v, node2.v); + return new CaseSv(node1, node2, nodeBoundary, line1, line2); + } + + // Line1 from node1 to nodeBoundary, Line2 from nodeBoundary to node2 + // Different nominal voltage at node1 and node2 + // NaN values for voltages - simulates DC LF + private static CaseSv createCaseNaNVoltages() { + NodeSv node1 = new NodeSv(Double.NaN, Math.toDegrees(-0.01745197)); + NodeSv nodeBoundary = new NodeSv(Double.NaN, Math.toDegrees(-0.02324020)); + NodeSv node2 = new NodeSv(Double.NaN, Math.toDegrees(-0.02818192)); + + LineSv line1 = new LineSv(11.729938, -8.196614, -11.713527, 1.301712, node1.v, nodeBoundary.v); + LineSv line2 = new LineSv(11.713527, -1.301712, -11.700000, -6.700000, nodeBoundary.v, node2.v); return new CaseSv(node1, node2, nodeBoundary, line1, line2); } @@ -677,12 +744,24 @@ private static final class LineSv { private final double q1; private final double p2; private final double q2; + private final double v1; + private final double v2; - private LineSv(double p1, double q1, double p2, double q2) { + private LineSv(double p1, double q1, double p2, double q2, double v1, double v2) { this.p1 = p1; this.q1 = q1; this.p2 = p2; this.q2 = q2; + this.v1 = v1; + this.v2 = v2; + } + + private double getI1() { + return Math.hypot(p1, q1) / (Math.sqrt(3.) * v1 / 1000); + } + + private double getI2() { + return Math.hypot(p2, q2) / (Math.sqrt(3.) * v2 / 1000); } } diff --git a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java index b244718c921..4a3d8f23b5a 100644 --- a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java +++ b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java @@ -62,6 +62,8 @@ void testOlfRealNetwork() { assertEquals(-dl.getP0(), dl.getBoundary().getP(), tol); assertEquals(-dl.getQ0(), dl.getBoundary().getQ(), tol); + double expectedI = Math.hypot(-dl.getP0(), -dl.getQ0()) / (Math.sqrt(3.) * dl.getBoundary().getV() / 1000); + assertEquals(expectedI, dl.getBoundary().getI(), tol); } @Test @@ -91,6 +93,8 @@ void testDcOlfRealNetwork() { assertEquals(-0.4187543391573424, svDl1other.getA(), tol); assertEquals(-dl.getP0(), dl.getBoundary().getP(), tol); + assertEquals(-dl.getQ0(), dl.getBoundary().getQ(), tol); + assertEquals(Double.NaN, dl.getBoundary().getI()); } private static void svOlfDataToNetwork(Network network) { @@ -288,6 +292,7 @@ void testWithZeroImpedanceDanglingLineWithGeneration() { danglingLine.getTerminal().getBusView().getBus().setV(100.0); assertEquals(298.937, danglingLine.getBoundary().getP(), tol); assertEquals(7.413, danglingLine.getBoundary().getQ(), tol); + assertEquals(1726.444, danglingLine.getBoundary().getI(), tol); assertEquals(100.0, danglingLine.getBoundary().getV(), tol); assertEquals(0.0, danglingLine.getBoundary().getAngle(), tol); } @@ -304,7 +309,25 @@ void testWithZeroImpedanceDanglingLineWithoutGeneration() { danglingLine.getTerminal().getBusView().getBus().setV(100.0); assertEquals(-50.0, danglingLine.getBoundary().getP(), tol); assertEquals(-30.0, danglingLine.getBoundary().getQ(), tol); + assertEquals(336.650, danglingLine.getBoundary().getI(), tol); assertEquals(100.0, danglingLine.getBoundary().getV(), tol); assertEquals(0.0, danglingLine.getBoundary().getAngle(), tol); } + + @Test + void testWithZeroImpedanceDanglingLineWithoutGenerationWithNaNV() { + double tol = 0.001; + Network network = DanglingLineNetworkFactory.create(); + DanglingLine danglingLine = network.getDanglingLine("DL"); + danglingLine.setR(0.0).setX(0.0); + danglingLine.getTerminal().setP(50.0); + danglingLine.getTerminal().setQ(30.0); + danglingLine.getTerminal().getBusView().getBus().setAngle(0.5); + danglingLine.getTerminal().getBusView().getBus().setV(Double.NaN); + assertEquals(-50.0, danglingLine.getBoundary().getP(), tol); + assertEquals(-30.0, danglingLine.getBoundary().getQ(), tol); + assertEquals(Double.NaN, danglingLine.getBoundary().getI()); + assertEquals(Double.NaN, danglingLine.getBoundary().getV()); + assertEquals(Double.NaN, danglingLine.getBoundary().getAngle()); + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformers.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformers.java new file mode 100644 index 00000000000..fac031bcd59 --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformers.java @@ -0,0 +1,570 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.modification; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; +import com.powsybl.iidm.modification.util.RegulatedTerminalControllers; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.powsybl.iidm.modification.util.ModificationReports.*; +import static com.powsybl.iidm.modification.util.TransformerUtils.*; + +/** + *

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

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

Used to replace all 3 twoWindingsTransformers by threeWindingsTransformers.

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

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

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

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

+ *

For each threeWindingsTransformer to be replaced:

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

Used to replace all threeWindingsTransformers by 3 twoWindingsTransformers.

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

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

+ */ + public ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(List transformersToBeReplaced) { + this.transformersToBeReplaced = Objects.requireNonNull(transformersToBeReplaced); + } + + @Override + public String getName() { + return "ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers"; + } + + @Override + public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) { + RegulatedTerminalControllers regulatedTerminalControllers = new RegulatedTerminalControllers(network); + network.getThreeWindingsTransformerStream().filter(t3w -> isGoingToBeReplaced(transformersToBeReplaced, t3w.getId())).toList() // toList is required to create a temporary list since the threeWindingsTransformer is removed during the replacement + .forEach(t3w -> replaceThreeWindingsTransformerBy3TwoWindingsTransformer(t3w, regulatedTerminalControllers, throwException, reportNode)); + } + + private static boolean isGoingToBeReplaced(List transformersToBeReplaced, String t3wId) { + return transformersToBeReplaced == null || transformersToBeReplaced.contains(t3wId); + } + + private void replaceThreeWindingsTransformerBy3TwoWindingsTransformer(ThreeWindingsTransformer t3w, RegulatedTerminalControllers regulatedTerminalControllers, boolean throwException, ReportNode reportNode) { + VoltageLevel starVoltageLevel = createStarVoltageLevel(t3w, throwException); + if (starVoltageLevel == null) { + return; + } + createTopologyInsideStarVoltageLevel(t3w, starVoltageLevel); + + TwoWindingsTransformer t2wLeg1 = createTwoWindingsTransformer(t3w, t3w.getLeg1(), starVoltageLevel); + TwoWindingsTransformer t2wLeg2 = createTwoWindingsTransformer(t3w, t3w.getLeg2(), starVoltageLevel); + TwoWindingsTransformer t2wLeg3 = createTwoWindingsTransformer(t3w, t3w.getLeg3(), starVoltageLevel); + ThreeT2wsR threeT2ws = new ThreeT2wsR(t2wLeg1, t2wLeg2, t2wLeg3); + + regulatedTerminalControllers.replaceRegulatedTerminal(t3w.getLeg1().getTerminal(), threeT2ws.t2wOne.getTerminal1()); + regulatedTerminalControllers.replaceRegulatedTerminal(t3w.getLeg2().getTerminal(), threeT2ws.t2wTwo.getTerminal1()); + regulatedTerminalControllers.replaceRegulatedTerminal(t3w.getLeg3().getTerminal(), threeT2ws.t2wThree.getTerminal1()); + + // t2wLeg1, t2wLeg, and t2wLeg3 are not considered in regulatedTerminalControllers (created later in the model) + replaceRegulatedTerminal(threeT2ws.t2wOne, t3w, threeT2ws); + replaceRegulatedTerminal(threeT2ws.t2wTwo, t3w, threeT2ws); + replaceRegulatedTerminal(threeT2ws.t2wThree, t3w, threeT2ws); + + copyTerminalActiveAndReactivePower(threeT2ws.t2wOne.getTerminal1(), t3w.getLeg1().getTerminal()); + copyTerminalActiveAndReactivePower(threeT2ws.t2wTwo.getTerminal1(), t3w.getLeg2().getTerminal()); + copyTerminalActiveAndReactivePower(threeT2ws.t2wThree.getTerminal1(), t3w.getLeg3().getTerminal()); + + List lostProperties = copyProperties(t3w, threeT2ws, starVoltageLevel); + List lostExtensions = copyExtensions(t3w, threeT2ws); + + // copy necessary data before removing the transformer + String t3wId = t3w.getId(); + List t3wAliases = getAliases(t3w); + t3w.remove(); + + // after removing the threeWindingsTransformer + List lostAliases = copyAliases(t3wAliases, threeT2ws); + + // warnings + if (!lostProperties.isEmpty()) { + lostProperties.forEach(propertyName -> LOG.warn("Property '{}' of threeWindingsTransformer '{}' was not transferred", propertyName, t3wId)); + } + if (!lostExtensions.isEmpty()) { + lostExtensions.forEach(extensionName -> LOG.warn("Extension '{}' of threeWindingsTransformer '{}' was not transferred", extensionName, t3wId)); + } + if (!lostAliases.isEmpty()) { + lostAliases.forEach(aliasR -> LOG.warn("Alias '{}' '{}' of threeWindingsTransformer '{}' was not transferred", aliasR.alias, aliasR.aliasType, t3wId)); + } + + // report + createReportNode(reportNode, t3wId, lostProperties, lostExtensions, lostAliases, starVoltageLevel.getId(), threeT2ws); + } + + // It is a fictitious bus, then we do not set voltage limits + private VoltageLevel createStarVoltageLevel(ThreeWindingsTransformer t3w, boolean throwException) { + Optional substation = t3w.getSubstation(); + if (substation.isEmpty()) { + logOrThrow(throwException, THREE_WINDINGS_TRANSFORMER + "'" + t3w.getId() + "' without substation"); + return null; + } + TopologyKind topologykind = t3w.getLeg1().getTerminal().getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER + && t3w.getLeg2().getTerminal().getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER + && t3w.getLeg3().getTerminal().getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER + ? TopologyKind.BUS_BREAKER : TopologyKind.NODE_BREAKER; + return substation.get().newVoltageLevel() + .setId(t3w.getId() + "-Star-VL") + .setName(t3w.getNameOrId() + "-Star-VL") + .setNominalV(t3w.getRatedU0()) + .setTopologyKind(topologykind) + .add(); + } + + private static void createTopologyInsideStarVoltageLevel(ThreeWindingsTransformer t3w, VoltageLevel starVoltageLevel) { + if (starVoltageLevel.getTopologyKind() == TopologyKind.BUS_BREAKER) { + starVoltageLevel.getBusBreakerView().newBus() + .setId(t3w.getId() + "-Star-Bus") + .setName(t3w.getNameOrId() + "-Star-Bus") + .add(); + } else { + starVoltageLevel.getNodeBreakerView().newInternalConnection().setNode1(1).setNode2(0).add(); + starVoltageLevel.getNodeBreakerView().newInternalConnection().setNode1(2).setNode2(0).add(); + starVoltageLevel.getNodeBreakerView().newInternalConnection().setNode1(3).setNode2(0).add(); + } + } + + private static TwoWindingsTransformer createTwoWindingsTransformer(ThreeWindingsTransformer t3w, ThreeWindingsTransformer.Leg leg, VoltageLevel starVoltageLevel) { + TwoWindingsTransformerAdder t2wAdder = starVoltageLevel.getSubstation().orElseThrow() + .newTwoWindingsTransformer() + .setEnsureIdUnicity(true) + .setId(t3w.getId() + "-Leg" + leg.getSide().getNum()) + .setName(t3w.getNameOrId() + "-Leg" + leg.getSide().getNum()) + .setRatedU1(leg.getRatedU()) + .setRatedU2(starVoltageLevel.getNominalV()) + .setR(leg.getR()) + .setX(leg.getX()) + .setG(leg.getG()) + .setB(leg.getB()) + .setRatedS(leg.getRatedS()) + .setVoltageLevel1(leg.getTerminal().getVoltageLevel().getId()) + .setVoltageLevel2(starVoltageLevel.getId()); + + connect(t2wAdder, getConnectivityLegAfterCreatingInternalConnection(leg), getConnectivityStar(leg.getSide().getNum(), starVoltageLevel)); + TwoWindingsTransformer t2w = t2wAdder.add(); + + leg.getOptionalRatioTapChanger().ifPresent(rtc -> copyAndAddRatioTapChanger(t2w.newRatioTapChanger(), rtc)); + leg.getOptionalPhaseTapChanger().ifPresent(rtc -> copyAndAddPhaseTapChanger(t2w.newPhaseTapChanger(), rtc)); + leg.getOperationalLimitsGroups().forEach(operationalLimitGroup -> copyOperationalLimitsGroup(t2w.newOperationalLimitsGroup1(operationalLimitGroup.getId()), operationalLimitGroup)); + + return t2w; + } + + private static void connect(TwoWindingsTransformerAdder t2wAdder, ConnectivityR connectivityEnd1, ConnectivityR connectivityEnd2) { + if (connectivityEnd1.node != null) { + t2wAdder.setNode1(connectivityEnd1.node); + } else { + t2wAdder.setConnectableBus1(connectivityEnd1.connectableBus.getId()); + if (connectivityEnd1.bus != null) { + t2wAdder.setBus1(connectivityEnd1.bus.getId()); + } + } + + if (connectivityEnd2.node != null) { + t2wAdder.setNode2(connectivityEnd2.node); + } else { + t2wAdder.setConnectableBus2(connectivityEnd2.connectableBus.getId()); + if (connectivityEnd2.bus != null) { + t2wAdder.setBus2(connectivityEnd2.bus.getId()); + } + } + } + + private static ConnectivityR getConnectivityLegAfterCreatingInternalConnection(ThreeWindingsTransformer.Leg leg) { + if (leg.getTerminal().getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) { + int newNode = leg.getTerminal().getVoltageLevel().getNodeBreakerView().getMaximumNodeIndex() + 1; + leg.getTerminal().getVoltageLevel().getNodeBreakerView() + .newInternalConnection() + .setNode1(leg.getTerminal().getNodeBreakerView().getNode()) + .setNode2(newNode).add(); + return new ConnectivityR(newNode, null, null); + } else { + return new ConnectivityR(null, leg.getTerminal().getBusBreakerView().getBus(), leg.getTerminal().getBusBreakerView().getConnectableBus()); + } + } + + private static ConnectivityR getConnectivityStar(int node, VoltageLevel startVoltageLevel) { + if (startVoltageLevel.getTopologyKind() == TopologyKind.NODE_BREAKER) { + return new ConnectivityR(node, null, null); + } else { + Bus bus = startVoltageLevel.getBusBreakerView().getBuses().iterator().next(); + return new ConnectivityR(null, bus, bus); + } + } + + private record ConnectivityR(Integer node, Bus bus, Bus connectableBus) { + } + + private static void replaceRegulatedTerminal(TwoWindingsTransformer t2w, ThreeWindingsTransformer t3w, ThreeT2wsR threeT2ws) { + t2w.getOptionalRatioTapChanger().ifPresent(rtc -> findNewRegulatedTerminal(rtc.getRegulationTerminal(), t3w, threeT2ws).ifPresent(rtc::setRegulationTerminal)); + t2w.getOptionalPhaseTapChanger().ifPresent(ptc -> findNewRegulatedTerminal(ptc.getRegulationTerminal(), t3w, threeT2ws).ifPresent(ptc::setRegulationTerminal)); + } + + private static Optional findNewRegulatedTerminal(Terminal regulatedTerminal, ThreeWindingsTransformer t3w, ThreeT2wsR threeT2ws) { + if (regulatedTerminal != null && regulatedTerminal.getConnectable().getId().equals(t3w.getId())) { + return switch (regulatedTerminal.getSide()) { + case ONE -> Optional.of(threeT2ws.t2wOne.getTerminal1()); + case TWO -> Optional.of(threeT2ws.t2wTwo.getTerminal1()); + case THREE -> Optional.of(threeT2ws.t2wThree.getTerminal1()); + }; + } else { + return Optional.empty(); + } + } + + private static List copyProperties(ThreeWindingsTransformer t3w, ThreeT2wsR threeT2ws, VoltageLevel starVoltageLevel) { + List lostProperties = new ArrayList<>(); + t3w.getPropertyNames().forEach(propertyName -> { + boolean copied = copyProperty(propertyName, t3w.getProperty(propertyName), threeT2ws, starVoltageLevel); + if (!copied) { + lostProperties.add(propertyName); + } + }); + return lostProperties; + } + + private static boolean copyProperty(String propertyName, String property, ThreeT2wsR threeT2ws, VoltageLevel starVoltageLevel) { + boolean copied = true; + if ("v".equals(propertyName)) { + starVoltageLevel.getBusView().getBuses().iterator().next().setV(Double.parseDouble(property)); + } else if ("angle".equals(propertyName)) { + starVoltageLevel.getBusView().getBuses().iterator().next().setAngle(Double.parseDouble(property)); + } else if (propertyName.startsWith(CGMES_OPERATIONAL_LIMIT_SET)) { + if (threeT2ws.t2wOne.getOperationalLimitsGroups1().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) { + threeT2ws.t2wOne.setProperty(propertyName, property); + } else if (threeT2ws.t2wTwo.getOperationalLimitsGroups1().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) { + threeT2ws.t2wTwo.setProperty(propertyName, property); + } else if (threeT2ws.t2wThree.getOperationalLimitsGroups1().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) { + threeT2ws.t2wThree.setProperty(propertyName, property); + } else { + copied = false; + } + } else { + // we copy all other properties on the 3 2wt + threeT2ws.t2wOne.setProperty(propertyName, property); + threeT2ws.t2wTwo.setProperty(propertyName, property); + threeT2ws.t2wThree.setProperty(propertyName, property); + } + return copied; + } + + // TODO For now, only a few extensions are supported. But a wider mechanism should be developed to support custom extensions. + private static List copyExtensions(ThreeWindingsTransformer t3w, ThreeT2wsR threeT2w) { + List lostExtensions = new ArrayList<>(); + t3w.getExtensions().stream().map(Extension::getName).forEach(extensionName -> { + boolean copied = copyExtension(extensionName, t3w, threeT2w); + if (!copied) { + lostExtensions.add(extensionName); + } + }); + return lostExtensions; + } + + private static boolean copyExtension(String extensionName, ThreeWindingsTransformer t3w, ThreeT2wsR threeT2ws) { + boolean copied = true; + switch (extensionName) { + case "threeWindingsTransformerFortescue" -> { + ThreeWindingsTransformerFortescue extension = t3w.getExtension(ThreeWindingsTransformerFortescue.class); + copyAndAddFortescue(threeT2ws.t2wOne.newExtension(TwoWindingsTransformerFortescueAdder.class), extension.getLeg1()); + copyAndAddFortescue(threeT2ws.t2wTwo.newExtension(TwoWindingsTransformerFortescueAdder.class), extension.getLeg2()); + copyAndAddFortescue(threeT2ws.t2wThree.newExtension(TwoWindingsTransformerFortescueAdder.class), extension.getLeg3()); + } + case "threeWindingsTransformerPhaseAngleClock" -> { + ThreeWindingsTransformerPhaseAngleClock extension = t3w.getExtension(ThreeWindingsTransformerPhaseAngleClock.class); + copyAndAddPhaseAngleClock(threeT2ws.t2wTwo.newExtension(TwoWindingsTransformerPhaseAngleClockAdder.class), extension.getPhaseAngleClockLeg2()); + copyAndAddPhaseAngleClock(threeT2ws.t2wThree.newExtension(TwoWindingsTransformerPhaseAngleClockAdder.class), extension.getPhaseAngleClockLeg3()); + } + case "threeWindingsTransformerToBeEstimated" -> { + ThreeWindingsTransformerToBeEstimated extension = t3w.getExtension(ThreeWindingsTransformerToBeEstimated.class); + copyAndAddToBeEstimated(threeT2ws.t2wOne.newExtension(TwoWindingsTransformerToBeEstimatedAdder.class), extension.shouldEstimateRatioTapChanger1(), extension.shouldEstimatePhaseTapChanger1()); + copyAndAddToBeEstimated(threeT2ws.t2wTwo.newExtension(TwoWindingsTransformerToBeEstimatedAdder.class), extension.shouldEstimateRatioTapChanger2(), extension.shouldEstimatePhaseTapChanger2()); + copyAndAddToBeEstimated(threeT2ws.t2wThree.newExtension(TwoWindingsTransformerToBeEstimatedAdder.class), extension.shouldEstimateRatioTapChanger3(), extension.shouldEstimatePhaseTapChanger3()); + } + default -> copied = false; + } + return copied; + } + + private static List getAliases(ThreeWindingsTransformer t3w) { + return t3w.getAliases().stream().map(alias -> new AliasR(alias, t3w.getAliasType(alias).orElse(""))).toList(); + } + + private static List copyAliases(List t3wAliases, ThreeT2wsR threeT2w) { + List lostAliases = new ArrayList<>(); + t3wAliases.forEach(aliasR -> { + boolean copied = copyAlias(aliasR.alias, aliasR.aliasType, threeT2w); + if (!copied) { + lostAliases.add(aliasR); + } + }); + return lostAliases; + } + + private static boolean copyAlias(String alias, String aliasType, ThreeT2wsR threeT2ws) { + return copyLegAlias(alias, aliasType, "1", threeT2ws.t2wOne) + || copyLegAlias(alias, aliasType, "2", threeT2ws.t2wTwo) + || copyLegAlias(alias, aliasType, "3", threeT2ws.t2wThree); + } + + private static boolean copyLegAlias(String alias, String aliasType, String legEnd, TwoWindingsTransformer t2wLeg) { + boolean copied = true; + if (aliasType.equals("CGMES.TransformerEnd" + legEnd)) { + t2wLeg.addAlias(alias, "CGMES.TransformerEnd1", true); + } else if (aliasType.equals("CGMES.Terminal" + legEnd)) { + t2wLeg.addAlias(alias, "CGMES.Terminal1", true); + } else if (aliasType.equals("CGMES.RatioTapChanger" + legEnd)) { + t2wLeg.addAlias(alias, "CGMES.RatioTapChanger1", true); + } else if (aliasType.equals("CGMES.PhaseTapChanger" + legEnd)) { + t2wLeg.addAlias(alias, "CGMES.PhaseTapChanger1", true); + } else { + copied = false; + } + return copied; + } + + private record AliasR(String alias, String aliasType) { + } + + private static void createReportNode(ReportNode reportNode, String t3wId, List lostProperties, List lostExtensions, + List lostAliases, String starVoltageLevelId, ThreeT2wsR threeT2ws) { + + ReportNode reportNodeReplacement = replaceThreeWindingsTransformersBy3TwoWindingsTransformersReport(reportNode); + + removedThreeWindingsTransformerReport(reportNodeReplacement, t3wId); + if (!lostProperties.isEmpty()) { + String properties = String.join(",", lostProperties); + lostThreeWindingsTransformerProperties(reportNodeReplacement, properties, t3wId); + } + if (!lostExtensions.isEmpty()) { + String extensions = String.join(",", lostExtensions); + lostThreeWindingsTransformerExtensions(reportNodeReplacement, extensions, t3wId); + } + if (!lostAliases.isEmpty()) { + String aliases = lostAliases.stream().map(AliasR::alias).collect(Collectors.joining(",")); + lostThreeWindingsTransformerAliases(reportNodeReplacement, aliases, t3wId); + } + + createdVoltageLevelReport(reportNodeReplacement, starVoltageLevelId); + createdTwoWindingsTransformerReport(reportNodeReplacement, threeT2ws.t2wOne.getId()); + createdTwoWindingsTransformerReport(reportNodeReplacement, threeT2ws.t2wTwo.getId()); + createdTwoWindingsTransformerReport(reportNodeReplacement, threeT2ws.t2wThree.getId()); + } + + private record ThreeT2wsR(TwoWindingsTransformer t2wOne, TwoWindingsTransformer t2wTwo, TwoWindingsTransformer t2wThree) { + } +} diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/ModificationReports.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/ModificationReports.java index 0a868e88686..1583c702e11 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/ModificationReports.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/ModificationReports.java @@ -26,6 +26,9 @@ public final class ModificationReports { private static final String IDENTIFIABLE_ID = "identifiableId"; private static final String IDENTIFIABLE_TYPE = "identifiableType"; private static final String HVDC_LINE_ID = "hvdcLineId"; + private static final String TWO_WINDINGS_TRANSFORMER_ID = "twoWindingsTransformerId"; + private static final String THREE_WINDINGS_TRANSFORMER_ID = "threeWindingsTransformerId"; + private static final String EXTENSIONS = "extensions"; public static final String POSITION_ORDER = "positionOrder"; // INFO @@ -78,6 +81,109 @@ public static void createdLineReport(ReportNode reportNode, String lineId) { .add(); } + public static void createdVoltageLevelReport(ReportNode reportNode, String voltageLevelId) { + reportNode.newReportNode() + .withMessageTemplate("voltageLevelCreated", "VoltageLevel ${voltageLevelId} created") + .withUntypedValue(VOLTAGE_LEVEL_ID, voltageLevelId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void createdTwoWindingsTransformerReport(ReportNode reportNode, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("twoWindingsTransformerCreated", "TwoWindingsTransformer ${twoWindingsTransformerId} created") + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void createdThreeWindingsTransformerReport(ReportNode reportNode, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("threeWindingsTransformerCreated", "ThreeWindingsTransformer ${threeWindingsTransformerId} created") + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void removedTwoWindingsTransformerReport(ReportNode reportNode, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("twoWindingsTransformerRemoved", "TwoWindingsTransformer ${twoWindingsTransformerId} removed") + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void removedThreeWindingsTransformerReport(ReportNode reportNode, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("threeWindingsTransformerRemoved", "ThreeWindingsTransformer ${threeWindingsTransformerId} removed") + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static void lostTwoWindingsTransformerAliases(ReportNode reportNode, String aliases, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostTwoWindingsTransformerAliases", "Alias [${aliases}] of twoWindingsTransformer ${twoWindingsTransformerId} will be lost") + .withUntypedValue("aliases", aliases) + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostThreeWindingsTransformerAliases(ReportNode reportNode, String aliases, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostThreeWindingsTransformerAliases", "Alias [${aliases}] of threeWindingsTransformer ${threeWindingsTransformerId} will be lost") + .withUntypedValue("aliases", aliases) + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostTwoWindingsTransformerProperties(ReportNode reportNode, String properties, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostTwoWindingsTransformerProperties", "Property [${properties}] of twoWindingsTransformer ${twoWindingsTransformerId} will be lost") + .withUntypedValue("properties", properties) + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostThreeWindingsTransformerProperties(ReportNode reportNode, String properties, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostThreeWindingsTransformerProperties", "Property [${properties}] of threeWindingsTransformer ${threeWindingsTransformerId} will be lost") + .withUntypedValue("properties", properties) + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostTwoWindingsTransformerExtensions(ReportNode reportNode, String extensions, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostTwoWindingsTransformerExtensions", "Extension [${extensions}] of twoWindingsTransformer ${twoWindingsTransformerId} will be lost") + .withUntypedValue(EXTENSIONS, extensions) + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostThreeWindingsTransformerExtensions(ReportNode reportNode, String extensions, String threeWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostThreeWindingsTransformerExtensions", "Extension [${extensions}] of threeWindingsTransformer ${threeWindingsTransformerId} will be lost") + .withUntypedValue(EXTENSIONS, extensions) + .withUntypedValue(THREE_WINDINGS_TRANSFORMER_ID, threeWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + + public static void lostTwoWindingsTransformerOperationalLimitsGroups(ReportNode reportNode, String limits, String twoWindingsTransformerId) { + reportNode.newReportNode() + .withMessageTemplate("lostTwoWindingsTransformerOperationalLimitsGroups", "OperationalLimitsGroups [${limits}] of twoWindingsTransformer ${twoWindingsTransformerId} will be lost") + .withUntypedValue("limits", limits) + .withUntypedValue(TWO_WINDINGS_TRANSFORMER_ID, twoWindingsTransformerId) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + public static void voltageLevelRemovedReport(ReportNode reportNode, String vlId) { reportNode.newReportNode() .withMessageTemplate("voltageLevelRemoved", "Voltage level ${vlId} removed") @@ -239,7 +345,7 @@ public static void ignoredPositionOrder(ReportNode reportNode, int positionOrder public static void lostDanglingLineExtensions(ReportNode reportNode, String extensions, String danglingLineId) { reportNode.newReportNode() .withMessageTemplate("lostDanglingLineExtensions", "Extension [${extensions}] of dangling line ${danglingLineId} will be lost") - .withUntypedValue("extensions", extensions) + .withUntypedValue(EXTENSIONS, extensions) .withUntypedValue("danglingLineId", danglingLineId) .withSeverity(TypedValue.WARN_SEVERITY) .add(); @@ -248,7 +354,7 @@ public static void lostDanglingLineExtensions(ReportNode reportNode, String exte public static void lostTieLineExtensions(ReportNode reportNode, String extensions, String tieLineId) { reportNode.newReportNode() .withMessageTemplate("lostTieLineExtensions", "Extension [${extensions}] of tie line ${tieLineId} will be lost") - .withUntypedValue("extensions", extensions) + .withUntypedValue(EXTENSIONS, extensions) .withUntypedValue("tieLineId", tieLineId) .withSeverity(TypedValue.WARN_SEVERITY) .add(); @@ -563,55 +669,69 @@ private ModificationReports() { public static void scalingReport(ReportNode reportNode, String type, DistributionMode mode, ScalingType scalingType, double asked, double done) { reportNode.newReportNode() - .withMessageTemplate("scalingApplied", "Successfully scaled on ${identifiableType} using mode ${mode} and type ${type} with a variation value asked of ${asked}. Variation done is ${done}") - .withUntypedValue(IDENTIFIABLE_TYPE, type) - .withUntypedValue("mode", mode.name()) - .withUntypedValue("type", scalingType.name()) - .withUntypedValue("asked", asked) - .withUntypedValue("done", done) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); + .withMessageTemplate("scalingApplied", "Successfully scaled on ${identifiableType} using mode ${mode} and type ${type} with a variation value asked of ${asked}. Variation done is ${done}") + .withUntypedValue(IDENTIFIABLE_TYPE, type) + .withUntypedValue("mode", mode.name()) + .withUntypedValue("type", scalingType.name()) + .withUntypedValue("asked", asked) + .withUntypedValue("done", done) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); } public static void scalingReport(ReportNode reportNode, String type, ScalingType scalingType, double asked, double done) { reportNode.newReportNode() - .withMessageTemplate("scalingApplied", "Successfully scaled on ${identifiableType} using mode STACKING and type ${type} with a variation value asked of ${asked}. Variation done is ${done}") - .withUntypedValue(IDENTIFIABLE_TYPE, type) - .withUntypedValue("type", scalingType.name()) - .withUntypedValue("asked", asked) - .withUntypedValue("done", done) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); + .withMessageTemplate("scalingApplied", "Successfully scaled on ${identifiableType} using mode STACKING and type ${type} with a variation value asked of ${asked}. Variation done is ${done}") + .withUntypedValue(IDENTIFIABLE_TYPE, type) + .withUntypedValue("type", scalingType.name()) + .withUntypedValue("asked", asked) + .withUntypedValue("done", done) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); } public static void connectableConnectionReport(ReportNode reportNode, Identifiable identifiable, boolean connectionSuccessful, ThreeSides side) { String defaultMessage = connectionSuccessful ? - "Connectable ${identifiable} has been connected" : - "Connectable ${identifiable} has NOT been connected"; + "Connectable ${identifiable} has been connected" : + "Connectable ${identifiable} has NOT been connected"; defaultMessage += side == null ? " on each side." : " on side " + side.getNum() + "."; String key = connectionSuccessful ? "connectableConnected" : "connectableNotConnected"; key += side == null ? "" : "Side" + side.getNum(); reportNode.newReportNode() - .withMessageTemplate(key, defaultMessage) - .withUntypedValue("identifiable", identifiable.getId()) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); + .withMessageTemplate(key, defaultMessage) + .withUntypedValue("identifiable", identifiable.getId()) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); } public static void identifiableDisconnectionReport(ReportNode reportNode, Identifiable identifiable, boolean disconnectionSuccessful, boolean isPlanned, ThreeSides side) { String defaultMessage = disconnectionSuccessful ? - "Identifiable ${identifiable} has been disconnected" : - "Identifiable ${identifiable} has NOT been disconnected"; + "Identifiable ${identifiable} has been disconnected" : + "Identifiable ${identifiable} has NOT been disconnected"; defaultMessage += isPlanned ? " (planned disconnection)" : " (unplanned disconnection)"; defaultMessage += side == null ? " on each side." : " on side " + side.getNum() + "."; String key = isPlanned ? "planned" : "unplanned"; key += disconnectionSuccessful ? "IdentifiableDisconnected" : "IdentifiableNotDisconnected"; key += side == null ? "" : "Side" + side.getNum(); reportNode.newReportNode() - .withMessageTemplate(key, defaultMessage) - .withUntypedValue("identifiable", identifiable.getId()) - .withSeverity(TypedValue.INFO_SEVERITY) - .add(); + .withMessageTemplate(key, defaultMessage) + .withUntypedValue("identifiable", identifiable.getId()) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static ReportNode replaceThreeWindingsTransformersBy3TwoWindingsTransformersReport(ReportNode reportNode) { + return reportNode.newReportNode() + .withMessageTemplate("replaced-t3w-by-3t2w", "Replaced ThreeWindingsTransformer by 3 TwoWindingsTransformers") + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + } + + public static ReportNode replace3TwoWindingsTransformersByThreeWindingsTransformersReport(ReportNode reportNode) { + return reportNode.newReportNode() + .withMessageTemplate("replaced-3t2w-by-t3w", "Replaced 3 TwoWindingsTransformers by ThreeWindingsTransformer") + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); } public static void generatorLocalRegulationReport(ReportNode reportNode, String generatorId) { diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/RegulatedTerminalControllers.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/RegulatedTerminalControllers.java new file mode 100644 index 00000000000..14a3c9f64a6 --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/RegulatedTerminalControllers.java @@ -0,0 +1,238 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.modification.util; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.extensions.Extension; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.RemoteReactivePowerControl; +import com.powsybl.iidm.network.extensions.SlackTerminal; +import com.powsybl.iidm.network.extensions.VoltageRegulation; + +import java.util.*; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +public class RegulatedTerminalControllers { + + private final Network network; + private final Map>> controllers; + + public RegulatedTerminalControllers(Network network) { + this.network = network; + this.controllers = new HashMap<>(); + findRegulatedTerminalControllers(); + } + + private void findRegulatedTerminalControllers() { + network.getIdentifiables().forEach(identifiable -> { + List regulatedTerminals = findRegulatedTerminals(identifiable); + regulatedTerminals.forEach(regulatedTerminal -> controllers.computeIfAbsent(regulatedTerminal, k -> new ArrayList<>()).add(identifiable)); + }); + } + + private List findRegulatedTerminals(Identifiable identifiable) { + List terminals = findRegulatedTerminalsInModel(identifiable); + terminals.addAll(findRegulatedTerminalsInExtensions(identifiable)); + return terminals; + } + + private List findRegulatedTerminalsInModel(Identifiable identifiable) { + List regulatedTerminals = new ArrayList<>(); + switch (identifiable.getType()) { + case TWO_WINDINGS_TRANSFORMER -> { + TwoWindingsTransformer t2w = (TwoWindingsTransformer) identifiable; + t2w.getOptionalRatioTapChanger().ifPresent(rtc -> add(regulatedTerminals, rtc.getRegulationTerminal())); + t2w.getOptionalPhaseTapChanger().ifPresent(ptc -> add(regulatedTerminals, ptc.getRegulationTerminal())); + } + case THREE_WINDINGS_TRANSFORMER -> { + ThreeWindingsTransformer t3w = (ThreeWindingsTransformer) identifiable; + t3w.getLeg1().getOptionalRatioTapChanger().ifPresent(rtc -> add(regulatedTerminals, rtc.getRegulationTerminal())); + t3w.getLeg1().getOptionalPhaseTapChanger().ifPresent(ptc -> add(regulatedTerminals, ptc.getRegulationTerminal())); + t3w.getLeg2().getOptionalRatioTapChanger().ifPresent(rtc -> add(regulatedTerminals, rtc.getRegulationTerminal())); + t3w.getLeg2().getOptionalPhaseTapChanger().ifPresent(ptc -> add(regulatedTerminals, ptc.getRegulationTerminal())); + t3w.getLeg3().getOptionalRatioTapChanger().ifPresent(rtc -> add(regulatedTerminals, rtc.getRegulationTerminal())); + t3w.getLeg3().getOptionalPhaseTapChanger().ifPresent(ptc -> add(regulatedTerminals, ptc.getRegulationTerminal())); + } + case GENERATOR -> { + Generator generator = (Generator) identifiable; + add(regulatedTerminals, generator.getRegulatingTerminal()); + } + case SHUNT_COMPENSATOR -> { + ShuntCompensator shuntCompensator = (ShuntCompensator) identifiable; + add(regulatedTerminals, shuntCompensator.getRegulatingTerminal()); + } + case STATIC_VAR_COMPENSATOR -> { + StaticVarCompensator staticVarCompensator = (StaticVarCompensator) identifiable; + add(regulatedTerminals, staticVarCompensator.getRegulatingTerminal()); + } + case HVDC_CONVERTER_STATION -> { + HvdcConverterStation hvdcConverterStation = (HvdcConverterStation) identifiable; + if (hvdcConverterStation.getHvdcType() == HvdcConverterStation.HvdcType.VSC) { + VscConverterStation vscConverterStation = (VscConverterStation) hvdcConverterStation; + add(regulatedTerminals, vscConverterStation.getRegulatingTerminal()); + } + } + default -> { + // do nothing + } + } + return regulatedTerminals; + } + + private static void add(List regulatedTerminals, Terminal regulatedTerminal) { + if (regulatedTerminal != null) { + regulatedTerminals.add(newTerminalRef(regulatedTerminal)); + } + } + + private List findRegulatedTerminalsInExtensions(Identifiable identifiable) { + List regulatedTerminals = new ArrayList<>(); + identifiable.getExtensions().stream().map(Extension::getName).forEach(extensionName -> + add(regulatedTerminals, findRegulatedTerminalInExtension(identifiable, extensionName))); + return regulatedTerminals; + } + + private static Terminal findRegulatedTerminalInExtension(Identifiable identifiable, String extensionName) { + switch (extensionName) { + case "voltageRegulation" -> { + Battery battery = (Battery) identifiable; + VoltageRegulation voltageRegulation = battery.getExtension(VoltageRegulation.class); + return voltageRegulation.getRegulatingTerminal(); + } + case "generatorRemoteReactivePowerControl" -> { + Generator generator = (Generator) identifiable; + RemoteReactivePowerControl remoteReactivePowerControl = generator.getExtension(RemoteReactivePowerControl.class); + return remoteReactivePowerControl.getRegulatingTerminal(); + } + case "slackTerminal" -> { + VoltageLevel voltageLevel = (VoltageLevel) identifiable; + SlackTerminal slackTerminal = voltageLevel.getExtension(SlackTerminal.class); + return slackTerminal.getTerminal(); + } + default -> { + return null; + } + } + } + + public boolean usedAsRegulatedTerminal(Terminal regulatedTerminal) { + Objects.requireNonNull(regulatedTerminal); + return controllers.containsKey(newTerminalRef(regulatedTerminal)); + } + + public void replaceRegulatedTerminal(Terminal currentRegulatedTerminal, Terminal newRegulatedTerminal) { + Objects.requireNonNull(currentRegulatedTerminal); + Objects.requireNonNull(newRegulatedTerminal); + TerminalRef currentRegulatedTerminalRef = newTerminalRef(currentRegulatedTerminal); + if (controllers.containsKey(currentRegulatedTerminalRef)) { + controllers.get(currentRegulatedTerminalRef).forEach(identifiable -> replaceRegulatedTerminal(identifiable, currentRegulatedTerminalRef, newRegulatedTerminal)); + } + } + + private static void replaceRegulatedTerminal(Identifiable identifiable, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + switch (identifiable.getType()) { + case TWO_WINDINGS_TRANSFORMER -> + replaceRegulatedTerminalTwoWindingsTransformer((TwoWindingsTransformer) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case THREE_WINDINGS_TRANSFORMER -> + replaceRegulatedTerminalThreeWindingsTransformer((ThreeWindingsTransformer) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case GENERATOR -> + replaceRegulatedTerminalGenerator((Generator) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case SHUNT_COMPENSATOR -> + replaceRegulatedTerminalShuntCompensator((ShuntCompensator) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case STATIC_VAR_COMPENSATOR -> + replaceRegulatedTerminalStaticVarCompensator((StaticVarCompensator) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case HVDC_CONVERTER_STATION -> + replaceRegulatedTerminalHvdcConverterStation((HvdcConverterStation) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case BATTERY -> + replaceRegulatedTerminalBattery((Battery) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + case VOLTAGE_LEVEL -> + replaceRegulatedTerminalVoltageLevel((VoltageLevel) identifiable, currentRegulatedTerminal, newRegulatedTerminal); + default -> throw new PowsyblException("unexpected identifiable type: " + identifiable.getType()); + } + } + + private static void replaceRegulatedTerminalTwoWindingsTransformer(TwoWindingsTransformer t2w, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + t2w.getOptionalRatioTapChanger().ifPresent(rtc -> replace(rtc, currentRegulatedTerminal, newRegulatedTerminal)); + t2w.getOptionalPhaseTapChanger().ifPresent(ptc -> replace(ptc, currentRegulatedTerminal, newRegulatedTerminal)); + } + + private static void replaceRegulatedTerminalThreeWindingsTransformer(ThreeWindingsTransformer t3w, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + t3w.getLeg1().getOptionalRatioTapChanger().ifPresent(rtc -> replace(rtc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg1().getOptionalPhaseTapChanger().ifPresent(ptc -> replace(ptc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg2().getOptionalRatioTapChanger().ifPresent(rtc -> replace(rtc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg2().getOptionalPhaseTapChanger().ifPresent(ptc -> replace(ptc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg3().getOptionalRatioTapChanger().ifPresent(rtc -> replace(rtc, currentRegulatedTerminal, newRegulatedTerminal)); + t3w.getLeg3().getOptionalPhaseTapChanger().ifPresent(ptc -> replace(ptc, currentRegulatedTerminal, newRegulatedTerminal)); + } + + private static void replace(TapChanger tc, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (tc.getRegulationTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(tc.getRegulationTerminal()))) { + tc.setRegulationTerminal(newRegulatedTerminal); + } + } + + private static void replaceRegulatedTerminalGenerator(Generator generator, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (generator.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(generator.getRegulatingTerminal()))) { + generator.setRegulatingTerminal(newRegulatedTerminal); + } else { + RemoteReactivePowerControl remoteReactivePowerControl = generator.getExtension(RemoteReactivePowerControl.class); + if (remoteReactivePowerControl != null + && remoteReactivePowerControl.getRegulatingTerminal() != null + && currentRegulatedTerminal.equals(newTerminalRef(remoteReactivePowerControl.getRegulatingTerminal()))) { + remoteReactivePowerControl.setRegulatingTerminal(newRegulatedTerminal); + } + } + } + + private static void replaceRegulatedTerminalShuntCompensator(ShuntCompensator shuntCompensator, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (shuntCompensator.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(shuntCompensator.getRegulatingTerminal()))) { + shuntCompensator.setRegulatingTerminal(newRegulatedTerminal); + } + } + + private static void replaceRegulatedTerminalStaticVarCompensator(StaticVarCompensator staticVarCompensator, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (staticVarCompensator.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(staticVarCompensator.getRegulatingTerminal()))) { + staticVarCompensator.setRegulatingTerminal(newRegulatedTerminal); + } + } + + private static void replaceRegulatedTerminalHvdcConverterStation(HvdcConverterStation hvdcConverterStation, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + if (hvdcConverterStation.getHvdcType() == HvdcConverterStation.HvdcType.VSC) { + VscConverterStation vscConverterStation = (VscConverterStation) hvdcConverterStation; + if (vscConverterStation.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(vscConverterStation.getRegulatingTerminal()))) { + vscConverterStation.setRegulatingTerminal(newRegulatedTerminal); + } + } + } + + private static void replaceRegulatedTerminalBattery(Battery battery, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + VoltageRegulation voltageRegulation = battery.getExtension(VoltageRegulation.class); + if (voltageRegulation != null && voltageRegulation.getRegulatingTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(voltageRegulation.getRegulatingTerminal()))) { + voltageRegulation.setRegulatingTerminal(newRegulatedTerminal); + } + } + + private static void replaceRegulatedTerminalVoltageLevel(VoltageLevel voltageLevel, TerminalRef currentRegulatedTerminal, Terminal newRegulatedTerminal) { + SlackTerminal slackTerminal = voltageLevel.getExtension(SlackTerminal.class); + if (slackTerminal != null && slackTerminal.getTerminal() != null && currentRegulatedTerminal.equals(newTerminalRef(slackTerminal.getTerminal()))) { + slackTerminal.setTerminal(newRegulatedTerminal); + } + } + + private static TerminalRef newTerminalRef(Terminal terminal) { + Objects.requireNonNull(terminal); + return new TerminalRef(terminal.getConnectable().getId(), terminal.getSide()); + } + + // To avoid comparing regulating terminal objects, with custom IIDM implementations could be problematic. + private record TerminalRef(String identifiableId, ThreeSides side) { + } +} diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/TransformerUtils.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/TransformerUtils.java new file mode 100644 index 00000000000..794cabf512e --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/util/TransformerUtils.java @@ -0,0 +1,233 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.modification.util; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import com.powsybl.iidm.network.util.LoadingLimitsUtil; +import org.apache.commons.math3.complex.Complex; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +public final class TransformerUtils { + + private TransformerUtils() { + } + + public static void copyAndAddRatioTapChanger(RatioTapChangerAdder rtcAdder, RatioTapChanger rtc) { + copyCommonRatioTapChanger(rtcAdder, rtc); + + rtc.getAllSteps().keySet().stream().sorted().forEach(step -> + rtcAdder.beginStep() + .setR(rtc.getStep(step).getR()) + .setX(rtc.getStep(step).getX()) + .setG(rtc.getStep(step).getG()) + .setB(rtc.getStep(step).getB()) + .setRho(rtc.getStep(step).getRho()) + .endStep()); + rtcAdder.add(); + } + + public static void copyAndMoveAndAddRatioTapChanger(RatioTapChangerAdder rtcAdder, RatioTapChanger rtc) { + copyCommonRatioTapChanger(rtcAdder, rtc); + + rtc.getAllSteps().keySet().stream().sorted().forEach(step -> { + double rho = rtc.getStep(step).getRho(); + double r = rtc.getStep(step).getR(); + double x = rtc.getStep(step).getX(); + double g = rtc.getStep(step).getG(); + double b = rtc.getStep(step).getB(); + + Complex ratio = obtainComplexRatio(1.0 / rho, 0.0); + Complex movedRatio = ratio.reciprocal(); + + double rCorrection = 100 * (impedanceConversion(1 + r / 100, ratio) - 1); + double xCorrection = 100 * (impedanceConversion(1 + x / 100, ratio) - 1); + double gCorrection = 100 * (admittanceConversion(1 + g / 100, ratio) - 1); + double bCorrection = 100 * (admittanceConversion(1 + b / 100, ratio) - 1); + + rtcAdder.beginStep() + .setR(rCorrection) + .setX(xCorrection) + .setG(gCorrection) + .setB(bCorrection) + .setRho(1.0 / movedRatio.abs()) + .endStep(); + }); + rtcAdder.add(); + } + + private static void copyCommonRatioTapChanger(RatioTapChangerAdder rtcAdder, RatioTapChanger rtc) { + rtcAdder.setTapPosition(rtc.getTapPosition()) + .setTargetV(rtc.getTargetV()) + .setLoadTapChangingCapabilities(rtc.hasLoadTapChangingCapabilities()) + .setRegulationMode(rtc.getRegulationMode()) + .setRegulationValue(rtc.getRegulationValue()) + .setLowTapPosition(rtc.getLowTapPosition()) + .setRegulating(rtc.isRegulating()) + .setRegulationTerminal(rtc.getRegulationTerminal()) + .setTargetDeadband(rtc.getTargetDeadband()); + } + + public static void copyAndAddPhaseTapChanger(PhaseTapChangerAdder ptcAdder, PhaseTapChanger ptc) { + copyCommonPhaseTapChanger(ptcAdder, ptc); + + ptc.getAllSteps().keySet().stream().sorted().forEach(step -> + ptcAdder.beginStep() + .setR(ptc.getStep(step).getR()) + .setX(ptc.getStep(step).getX()) + .setG(ptc.getStep(step).getG()) + .setB(ptc.getStep(step).getB()) + .setRho(ptc.getStep(step).getRho()) + .setAlpha(ptc.getStep(step).getAlpha()) + .endStep()); + ptcAdder.add(); + } + + public static void copyAndMoveAndAddPhaseTapChanger(PhaseTapChangerAdder ptcAdder, PhaseTapChanger ptc) { + copyCommonPhaseTapChanger(ptcAdder, ptc); + + ptc.getAllSteps().keySet().stream().sorted().forEach(step -> { + double rho = ptc.getStep(step).getRho(); + double alpha = ptc.getStep(step).getAlpha(); + double r = ptc.getStep(step).getR(); + double x = ptc.getStep(step).getX(); + double g = ptc.getStep(step).getG(); + double b = ptc.getStep(step).getB(); + + Complex ratio = obtainComplexRatio(1.0 / rho, -alpha); + Complex movedRatio = ratio.reciprocal(); + + double rCorrection = 100 * (impedanceConversion(1 + r / 100, ratio) - 1); + double xCorrection = 100 * (impedanceConversion(1 + x / 100, ratio) - 1); + double gCorrection = 100 * (admittanceConversion(1 + g / 100, ratio) - 1); + double bCorrection = 100 * (admittanceConversion(1 + b / 100, ratio) - 1); + + ptcAdder.beginStep() + .setR(rCorrection) + .setX(xCorrection) + .setG(gCorrection) + .setB(bCorrection) + .setRho(1.0 / movedRatio.abs()) + .setAlpha(-Math.toDegrees(movedRatio.getArgument())) + .endStep(); + }); + ptcAdder.add(); + } + + private static void copyCommonPhaseTapChanger(PhaseTapChangerAdder ptcAdder, PhaseTapChanger ptc) { + ptcAdder.setTapPosition(ptc.getTapPosition()) + .setRegulationMode(ptc.getRegulationMode()) + .setRegulationValue(ptc.getRegulationValue()) + .setLowTapPosition(ptc.getLowTapPosition()) + .setRegulationTerminal(ptc.getRegulationTerminal()) + .setTargetDeadband(ptc.getTargetDeadband()) + .setRegulating(ptc.isRegulating()); + } + + private static Complex obtainComplexRatio(double ratio, double angle) { + return new Complex(ratio * Math.cos(Math.toRadians(angle)), ratio * Math.sin(Math.toRadians(angle))); + } + + public static double impedanceConversion(double impedance, Complex a) { + return impedance * a.abs() * a.abs(); + } + + public static double impedanceConversion(double impedance, double a) { + return impedance * a * a; + } + + public static double admittanceConversion(double admittance, Complex a) { + return admittance / (a.abs() * a.abs()); + } + + public static double admittanceConversion(double admittance, double a) { + return admittance / (a * a); + } + + public static void copyOperationalLimitsGroup(OperationalLimitsGroup destination, OperationalLimitsGroup source) { + source.getActivePowerLimits().ifPresent(activePowerLimits -> LoadingLimitsUtil.initializeFromLoadingLimits(destination.newActivePowerLimits(), activePowerLimits).add()); + source.getApparentPowerLimits().ifPresent(apparentPowerLimits -> LoadingLimitsUtil.initializeFromLoadingLimits(destination.newApparentPowerLimits(), apparentPowerLimits).add()); + source.getCurrentLimits().ifPresent(currentLimits -> LoadingLimitsUtil.initializeFromLoadingLimits(destination.newCurrentLimits(), currentLimits).add()); + } + + public static void copyTerminalActiveAndReactivePower(Terminal destinationTerminal, Terminal sourceTerminal) { + destinationTerminal.setP(sourceTerminal.getP()); + destinationTerminal.setQ(sourceTerminal.getQ()); + } + + public static void copyAndAddFortescue(TwoWindingsTransformerFortescueAdder t2wFortescueAdder, ThreeWindingsTransformerFortescue.LegFortescue legFortescue) { + t2wFortescueAdder.withConnectionType1(legFortescue.getConnectionType()) + .withFreeFluxes(legFortescue.isFreeFluxes()) + .withGroundingR1(legFortescue.getGroundingR()) + .withGroundingX1(legFortescue.getGroundingX()) + .withRz(legFortescue.getRz()) + .withXz(legFortescue.getXz()) + .withConnectionType2(WindingConnectionType.Y) + .withGroundingR2(.0) + .withGroundingX2(.0); + t2wFortescueAdder.add(); + } + + public static void copyAndAddFortescue(ThreeWindingsTransformerFortescueAdder t3wFortescue, TwoWindingsTransformerFortescue t2w1Fortescue, boolean isWellOrientedT2w1, TwoWindingsTransformerFortescue t2w2Fortescue, boolean isWellOrientedT2w2, TwoWindingsTransformerFortescue t2w3Fortescue, boolean isWellOrientedT2w3) { + copyFortescueLeg(t3wFortescue.leg1(), t2w1Fortescue, isWellOrientedT2w1); + copyFortescueLeg(t3wFortescue.leg2(), t2w2Fortescue, isWellOrientedT2w2); + copyFortescueLeg(t3wFortescue.leg3(), t2w3Fortescue, isWellOrientedT2w3); + t3wFortescue.add(); + } + + private static void copyFortescueLeg(ThreeWindingsTransformerFortescueAdder.LegFortescueAdder legFortescueAdder, TwoWindingsTransformerFortescue t2wFortescue, boolean isWellOrientedT2w) { + if (t2wFortescue != null) { + legFortescueAdder.withConnectionType(isWellOrientedT2w ? t2wFortescue.getConnectionType1() : t2wFortescue.getConnectionType2()) + .withFreeFluxes(t2wFortescue.isFreeFluxes()) + .withGroundingR(isWellOrientedT2w ? t2wFortescue.getGroundingR1() : t2wFortescue.getGroundingR2()) + .withGroundingX(isWellOrientedT2w ? t2wFortescue.getGroundingX1() : t2wFortescue.getGroundingX2()) + .withRz(t2wFortescue.getRz()) + .withXz(t2wFortescue.getXz()); + } + } + + public static void copyAndAddPhaseAngleClock(TwoWindingsTransformerPhaseAngleClockAdder phaseAngleClockAdder, int phaseAngleClock) { + phaseAngleClockAdder.withPhaseAngleClock(phaseAngleClock); + phaseAngleClockAdder.add(); + } + + public static void copyAndAddPhaseAngleClock(ThreeWindingsTransformerPhaseAngleClockAdder phaseAngleClockAdder, TwoWindingsTransformerPhaseAngleClock t2w2PhaseAngleClock, TwoWindingsTransformerPhaseAngleClock t2w3PhaseAngleClock) { + if (t2w2PhaseAngleClock != null) { + phaseAngleClockAdder.withPhaseAngleClockLeg2(t2w2PhaseAngleClock.getPhaseAngleClock()); + } + if (t2w3PhaseAngleClock != null) { + phaseAngleClockAdder.withPhaseAngleClockLeg3(t2w3PhaseAngleClock.getPhaseAngleClock()); + } + phaseAngleClockAdder.add(); + } + + public static void copyAndAddToBeEstimated(TwoWindingsTransformerToBeEstimatedAdder toBeEstimatedAdder, boolean shouldEstimateRatioTapChanger, boolean shouldEstimatePhaseTapChanger) { + toBeEstimatedAdder.withRatioTapChangerStatus(shouldEstimateRatioTapChanger) + .withPhaseTapChangerStatus(shouldEstimatePhaseTapChanger); + toBeEstimatedAdder.add(); + } + + public static void copyAndAddToBeEstimated(ThreeWindingsTransformerToBeEstimatedAdder toBeEstimatedAdder, TwoWindingsTransformerToBeEstimated t2w1ToBeEstimated, TwoWindingsTransformerToBeEstimated t2w2ToBeEstimated, TwoWindingsTransformerToBeEstimated t2w3ToBeEstimated) { + if (t2w1ToBeEstimated != null) { + toBeEstimatedAdder.withRatioTapChanger1Status(t2w1ToBeEstimated.shouldEstimateRatioTapChanger()) + .withPhaseTapChanger1Status(t2w1ToBeEstimated.shouldEstimatePhaseTapChanger()); + } + if (t2w2ToBeEstimated != null) { + toBeEstimatedAdder.withRatioTapChanger2Status(t2w2ToBeEstimated.shouldEstimateRatioTapChanger()) + .withPhaseTapChanger2Status(t2w2ToBeEstimated.shouldEstimatePhaseTapChanger()); + } + if (t2w3ToBeEstimated != null) { + toBeEstimatedAdder.withRatioTapChanger3Status(t2w3ToBeEstimated.shouldEstimateRatioTapChanger()) + .withPhaseTapChanger3Status(t2w3ToBeEstimated.shouldEstimatePhaseTapChanger()); + } + toBeEstimatedAdder.add(); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ControlledRegulatingTerminalsTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ControlledRegulatingTerminalsTest.java new file mode 100644 index 00000000000..0585c179115 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ControlledRegulatingTerminalsTest.java @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.modification; + +import com.powsybl.iidm.modification.util.RegulatedTerminalControllers; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import com.powsybl.iidm.network.test.BatteryNetworkFactory; +import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; +import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory; +import org.junit.jupiter.api.Test; + +import static com.powsybl.iidm.modification.TransformersTestUtils.addPhaseTapChanger; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +class ControlledRegulatingTerminalsTest { + + @Test + void twoWindingsTransformerRtcTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + TwoWindingsTransformer t2w = network.getTwoWindingsTransformer("TWT"); + + assertNotEquals(t2w.getRatioTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + assertNotEquals(t2w.getPhaseTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t2w.getRatioTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t2w.getPhaseTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + assertEquals(t2w.getRatioTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + assertEquals(t2w.getPhaseTapChanger().getRegulationTerminal(), t2w.getTerminal2()); + } + + @Test + void threeWindingsTransformerRtcTest() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + + assertNotEquals(t3w.getLeg2().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertNotEquals(t3w.getLeg3().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg2().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg3().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(t3w.getLeg2().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(t3w.getLeg3().getRatioTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + } + + @Test + void threeWindingsTransformerPtcLeg1Test() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + addPhaseTapChanger(t3w.getLeg1()); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + assertNotEquals(t3w.getLeg1().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg2().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg1().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg2().getTerminal()); + assertEquals(t3w.getLeg1().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg2().getTerminal()); + } + + @Test + void threeWindingsTransformerPtcLeg2Test() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + addPhaseTapChanger(t3w.getLeg2()); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + assertNotEquals(t3w.getLeg2().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg2().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(t3w.getLeg2().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + } + + @Test + void threeWindingsTransformerPtcLeg3Test() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + addPhaseTapChanger(t3w.getLeg3()); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + assertNotEquals(t3w.getLeg3().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(t3w.getLeg3().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(t3w.getLeg3().getPhaseTapChanger().getRegulationTerminal(), t3w.getLeg1().getTerminal()); + } + + @Test + void generatorTest() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + Generator generator = network.getGenerator("GEN_132"); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT"); + + assertNotEquals(generator.getRegulatingTerminal(), t3w.getLeg1().getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(generator.getRegulatingTerminal(), t3w.getLeg1().getTerminal()); + assertEquals(generator.getRegulatingTerminal(), t3w.getLeg1().getTerminal()); + } + + @Test + void shuntCompensatorTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + ShuntCompensator shuntCompensator = network.getShuntCompensator("SHUNT"); + TwoWindingsTransformer t2w = network.getTwoWindingsTransformer("TWT"); + + assertNotEquals(shuntCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(shuntCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + assertEquals(shuntCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + } + + @Test + void staticVarCompensatorTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + StaticVarCompensator staticVarCompensator = network.getStaticVarCompensator("SVC"); + TwoWindingsTransformer t2w = network.getTwoWindingsTransformer("TWT"); + + assertNotEquals(staticVarCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(staticVarCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + assertEquals(staticVarCompensator.getRegulatingTerminal(), t2w.getTerminal1()); + } + + @Test + void voltageSourceConverterTest() { + Network network = FourSubstationsNodeBreakerFactory.create(); + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + + HvdcConverterStation hvdcConverterStation = network.getHvdcConverterStation("VSC1"); + VscConverterStation vscConverterStation = (VscConverterStation) hvdcConverterStation; + TwoWindingsTransformer t2w = network.getTwoWindingsTransformer("TWT"); + + assertNotEquals(vscConverterStation.getRegulatingTerminal(), t2w.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(vscConverterStation.getRegulatingTerminal(), t2w.getTerminal1()); + assertEquals(vscConverterStation.getRegulatingTerminal(), t2w.getTerminal1()); + } + + @Test + void batteryTest() { + Network network = BatteryNetworkFactory.create(); + + Battery battery = network.getBattery("BAT2"); + VoltageRegulation voltageRegulation = battery.newExtension(VoltageRegulationAdder.class) + .withRegulatingTerminal(battery.getTerminal()) + .withVoltageRegulatorOn(true) + .withTargetV(50.0) + .add(); + + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + Generator generator = network.getGenerator("GEN"); + + assertNotEquals(voltageRegulation.getRegulatingTerminal(), generator.getTerminal()); + controlledRegulatingTerminals.replaceRegulatedTerminal(voltageRegulation.getRegulatingTerminal(), generator.getTerminal()); + assertEquals(voltageRegulation.getRegulatingTerminal(), generator.getTerminal()); + } + + @Test + void generatorRemoteReactiveControlTest() { + Network network = BatteryNetworkFactory.create(); + + Generator generator = network.getGenerator("GEN"); + RemoteReactivePowerControl remoteReactivePowerControl = generator.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(100.0) + .withRegulatingTerminal(generator.getTerminal()) + .withEnabled(true) + .add(); + + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + Line line = network.getLine("NHV1_NHV2_1"); + + assertNotEquals(remoteReactivePowerControl.getRegulatingTerminal(), line.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(remoteReactivePowerControl.getRegulatingTerminal(), line.getTerminal1()); + assertEquals(remoteReactivePowerControl.getRegulatingTerminal(), line.getTerminal1()); + } + + @Test + void slackTerminalTest() { + Network network = BatteryNetworkFactory.create(); + + Generator generator = network.getGenerator("GEN"); + VoltageLevel voltageLevel = network.getVoltageLevel("VLGEN"); + SlackTerminal slackTerminal = voltageLevel.newExtension(SlackTerminalAdder.class) + .withTerminal(generator.getTerminal()) + .add(); + + RegulatedTerminalControllers controlledRegulatingTerminals = new RegulatedTerminalControllers(network); + Line line = network.getLine("NHV1_NHV2_1"); + + assertNotEquals(slackTerminal.getTerminal(), line.getTerminal1()); + controlledRegulatingTerminals.replaceRegulatedTerminal(slackTerminal.getTerminal(), line.getTerminal1()); + assertEquals(slackTerminal.getTerminal(), line.getTerminal1()); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformersTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformersTest.java new file mode 100644 index 00000000000..5d6a1a9dbb8 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/Replace3TwoWindingsTransformersByThreeWindingsTransformersTest.java @@ -0,0 +1,457 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.modification; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.test.TestUtil; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.ThreeSides; +import com.powsybl.iidm.network.ThreeWindingsTransformer; +import com.powsybl.iidm.network.TwoWindingsTransformer; +import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory; +import com.powsybl.iidm.network.util.BranchData; +import com.powsybl.iidm.network.util.TwtData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collections; + +import static com.powsybl.iidm.modification.TransformersTestUtils.*; +import static com.powsybl.iidm.modification.TransformersTestUtils.addPhaseTapChanger; +import static com.powsybl.iidm.modification.util.ModificationReports.lostTwoWindingsTransformerExtensions; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +class Replace3TwoWindingsTransformersByThreeWindingsTransformersTest { + private Network network; + private TwoWindingsTransformer t2w1; + private TwoWindingsTransformer t2w2; + private TwoWindingsTransformer t2w3; + + @BeforeEach + public void setUp() { + Network expectedNetwork = createSetUpNetwork(); + assertEquals(3, expectedNetwork.getTwoWindingsTransformerCount()); + t2w1 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg1"); + t2w2 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg2"); + t2w3 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg3"); + network = createSetUpNetwork(); + } + + private static Network createSetUpNetwork() { + Network network = ThreeWindingsTransformerNetworkFactory.create(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + return network; + } + + @Test + void replaceBasicTest() { + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + } + + @Test + void replaceSelectedTwoWindingsTransformerOneTest() { + assertTrue(replaceSelectedTwoWindingsTransformerTest("3WT-Leg1")); + } + + @Test + void replaceSelectedTwoWindingsTransformerTwoTest() { + assertTrue(replaceSelectedTwoWindingsTransformerTest("3WT-Leg2")); + } + + @Test + void replaceSelectedTwoWindingsTransformerThreeTest() { + assertTrue(replaceSelectedTwoWindingsTransformerTest("3WT-Leg3")); + } + + private boolean replaceSelectedTwoWindingsTransformerTest(String t2wId) { + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(Collections.singletonList(t2wId)); + replace.apply(network); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + + return true; + } + + @Test + void nonReplacementTest() { + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + + String t2wId = "unknown twoWindingsTransformer"; + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(Collections.singletonList(t2wId)); + replace.apply(network); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + } + + @Test + void replaceRatioTapChangerTest() { + assertNull(t2w1.getRatioTapChanger()); + assertNotNull(t2w2.getRatioTapChanger()); + assertNotNull(t2w3.getRatioTapChanger()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertNull(t3w.getLeg1().getRatioTapChanger()); + assertNotNull(t3w.getLeg2().getRatioTapChanger()); + assertNotNull(t3w.getLeg3().getRatioTapChanger()); + + assertTrue(compareRatioTapChanger(t2w2.getRatioTapChanger(), t3w.getLeg2().getRatioTapChanger())); + assertTrue(compareRatioTapChanger(t2w3.getRatioTapChanger(), t3w.getLeg3().getRatioTapChanger())); + } + + @Test + void replacePhaseTapChangerTest() { + modifyNetworkForPhaseTapChangerTest(); + + assertNotNull(t2w1.getPhaseTapChanger()); + assertNull(t2w2.getPhaseTapChanger()); + assertNull(t2w3.getPhaseTapChanger()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertNotNull(t3w.getLeg1().getPhaseTapChanger()); + assertNull(t3w.getLeg2().getPhaseTapChanger()); + assertNull(t3w.getLeg3().getPhaseTapChanger()); + + assertTrue(comparePhaseTapChanger(t2w1.getPhaseTapChanger(), t3w.getLeg1().getPhaseTapChanger())); + } + + private void modifyNetworkForPhaseTapChangerTest() { + addPhaseTapChanger(t2w1); // modify expected network + addPhaseTapChanger(network.getTwoWindingsTransformer(t2w1.getId())); // modify network + } + + @Test + void replaceLoadingLimitsTest() { + modifyNetworkForLoadingLimitsTest(); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertTrue(compareOperationalLimitsGroups(t2w1.getOperationalLimitsGroups1(), t3w.getLeg1().getOperationalLimitsGroups())); + assertTrue(compareOperationalLimitsGroups(t2w2.getOperationalLimitsGroups1(), t3w.getLeg2().getOperationalLimitsGroups())); + assertTrue(compareOperationalLimitsGroups(t2w3.getOperationalLimitsGroups1(), t3w.getLeg3().getOperationalLimitsGroups())); + } + + private void modifyNetworkForLoadingLimitsTest() { + addLoadingLimitsEnd1(t2w1); + addLoadingLimitsEnd1(t2w2); + addLoadingLimitsEnd1(t2w3); + addLoadingLimitsEnd1(network.getTwoWindingsTransformer(t2w1.getId())); + addLoadingLimitsEnd1(network.getTwoWindingsTransformer(t2w2.getId())); + addLoadingLimitsEnd1(network.getTwoWindingsTransformer(t2w3.getId())); + } + + @Test + void replacePropertiesT2w1Test() { + assertTrue(replacePropertiesTwoWindingsTransformer(t2w1)); + } + + @Test + void replacePropertiesT2w2Test() { + assertTrue(replacePropertiesTwoWindingsTransformer(t2w2)); + } + + @Test + void replacePropertiesT2w3Test() { + assertTrue(replacePropertiesTwoWindingsTransformer(t2w3)); + } + + private boolean replacePropertiesTwoWindingsTransformer(TwoWindingsTransformer t2w) { + modifyNetworkForPropertiesTest(t2w); + network.getTwoWindingsTransformer(t2w.getId()).setProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-summer", "summer"); + network.getTwoWindingsTransformer(t2w.getId()).setProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-winter", "winter"); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + return t3w.getProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-summer").equals("summer") && + t3w.getProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-winter").equals("winter"); + } + + private void modifyNetworkForPropertiesTest(TwoWindingsTransformer t2w) { + addLoadingLimitsEnd1(t2w); + addLoadingLimitsEnd1(network.getTwoWindingsTransformer(t2w.getId())); + } + + @Test + void replaceExtensionsTest() { + modifyNetworkForExtensionsTest(); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertEquals(createTwoWindingsTransformerFortescueToString(t2w1, t2w2, t2w3), createThreeWindingsTransformerFortescueToString(t3w)); + assertEquals(createTwoWindingsTransformerPhaseAngleClockToString(t2w2, t2w3), createThreeWindingsTransformerPhaseAngleClockToString(t3w)); + assertEquals(createTwoWindingsTransformerToBeEstimatedToString(t2w1, t2w2, t2w3), createThreeWindingsTransformerToBeEstimatedToString(t3w)); + } + + private void modifyNetworkForExtensionsTest() { + addExtensions(t2w1, 1); + addExtensions(t2w2, 2); + addExtensions(t2w3, 3); + addExtensions(network.getTwoWindingsTransformer(t2w1.getId()), 1); + addExtensions(network.getTwoWindingsTransformer(t2w2.getId()), 2); + addExtensions(network.getTwoWindingsTransformer(t2w3.getId()), 3); + } + + @Test + void replaceAliasesTest() { + addTwoWindingsTransformerAliases(network.getTwoWindingsTransformer(t2w1.getId())); + addTwoWindingsTransformerAliases(network.getTwoWindingsTransformer(t2w2.getId())); + addTwoWindingsTransformerAliases(network.getTwoWindingsTransformer(t2w3.getId())); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + assertTrue(compareAliases(t3w, "1", t2w1)); + assertTrue(compareAliases(t3w, "2", t2w2)); + assertTrue(compareAliases(t3w, "3", t2w3)); + } + + private void addTwoWindingsTransformerAliases(TwoWindingsTransformer t2w) { + t2w.addAlias("transformerEnd-" + t2w.getId(), "CGMES.TransformerEnd1"); + t2w.addAlias("terminal-" + t2w.getId(), "CGMES.Terminal1"); + t2w.addAlias("ratioTapChanger-" + t2w.getId(), "CGMES.RatioTapChanger1"); + t2w.addAlias("phaseTapChanger-" + t2w.getId(), "CGMES.PhaseTapChanger1"); + } + + private boolean compareAliases(ThreeWindingsTransformer t3w, String leg, TwoWindingsTransformer t2w) { + return t3w.getAliasFromType("CGMES.TransformerEnd" + leg).orElseThrow().equals("transformerEnd-" + t2w.getId()) + && t3w.getAliasFromType("CGMES.Terminal" + leg).orElseThrow().equals("terminal-" + t2w.getId()) + && t3w.getAliasFromType("CGMES.RatioTapChanger" + leg).orElseThrow().equals("ratioTapChanger-" + t2w.getId()) + && t3w.getAliasFromType("CGMES.PhaseTapChanger" + leg).orElseThrow().equals("phaseTapChanger-" + t2w.getId()); + } + + @Test + void reportNodeTest() throws IOException { + network.getTwoWindingsTransformer(t2w1.getId()).setProperty("t2w1 property1", "t2w1-value1"); + network.getTwoWindingsTransformer(t2w2.getId()).setProperty("t2w2 property1", "t2w2-value1"); + network.getTwoWindingsTransformer(t2w2.getId()).setProperty("t2w2 property2", "t2w2-value2"); + network.getTwoWindingsTransformer(t2w3.getId()).setProperty("t2w3 property1", "t2w3-value1"); + network.getTwoWindingsTransformer(t2w1.getId()).addAlias("t2w1-alias1"); + network.getTwoWindingsTransformer(t2w1.getId()).addAlias("t2w1-alias2"); + network.getTwoWindingsTransformer(t2w2.getId()).addAlias("t2w2-alias1"); + network.getTwoWindingsTransformer(t2w3.getId()).addAlias("t2w3-alias1"); + network.getTwoWindingsTransformer(t2w1.getId()).addAlias("t2w1-alias2"); + addLoadingLimitsEnd2(network.getTwoWindingsTransformer(t2w1.getId())); + addLoadingLimitsEnd2(network.getTwoWindingsTransformer(t2w2.getId())); + ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test reportNode").build(); + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network, reportNode); + + StringWriter sw1 = new StringWriter(); + reportNode.print(sw1); + assertEquals(""" + + test reportNode + + Replaced 3 TwoWindingsTransformers by ThreeWindingsTransformer + TwoWindingsTransformer 3WT-Leg1 removed + TwoWindingsTransformer 3WT-Leg2 removed + TwoWindingsTransformer 3WT-Leg3 removed + Voltage level 3WT-Star-VL, its equipments and the branches it is connected to have been removed + Alias [t2w1-alias2,t2w1-alias1] of twoWindingsTransformer 3WT-Leg1 will be lost + Alias [t2w2-alias1] of twoWindingsTransformer 3WT-Leg2 will be lost + Alias [t2w3-alias1] of twoWindingsTransformer 3WT-Leg3 will be lost + OperationalLimitsGroups [OperationalLimitsGroup-summer-end2,OperationalLimitsGroup-winter-end2] of twoWindingsTransformer 3WT-Leg1 will be lost + OperationalLimitsGroups [OperationalLimitsGroup-summer-end2,OperationalLimitsGroup-winter-end2] of twoWindingsTransformer 3WT-Leg2 will be lost + ThreeWindingsTransformer 3WT-Leg1-3WT-Leg2-3WT-Leg3 created + """, TestUtil.normalizeLineSeparator(sw1.toString())); + } + + @Test + void reportNodeExtensionsTest() throws IOException { + ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test reportNode").build(); + lostTwoWindingsTransformerExtensions(reportNode, "unknownExtension1, unknownExtension2", t2w1.getId()); + + StringWriter sw1 = new StringWriter(); + reportNode.print(sw1); + assertEquals(""" + + test reportNode + Extension [unknownExtension1, unknownExtension2] of twoWindingsTransformer 3WT-Leg1 will be lost + """, TestUtil.normalizeLineSeparator(sw1.toString())); + } + + @Test + void replaceFlowsTest() { + modifyNetworkForFlowsTest(); + assertTrue(checkFlowsTest()); + } + + private void modifyNetworkForFlowsTest() { + addVoltages(t2w1.getTerminal1().getBusView().getBus(), t2w2.getTerminal1().getBusView().getBus(), t2w3.getTerminal1().getBusView().getBus()); + addVoltages(network.getTwoWindingsTransformer(t2w1.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w2.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w3.getId()).getTerminal1().getBusView().getBus()); + } + + @Test + void replaceFlowsRatedU2Test() { + modifyNetworkForFlowsRatedU2Test(); + assertTrue(checkFlowsTest()); + } + + private void modifyNetworkForFlowsRatedU2Test() { + t2w1.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.99); + t2w2.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 1.02); + t2w3.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.98); + + network.getTwoWindingsTransformer(t2w1.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.99); + network.getTwoWindingsTransformer(t2w2.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 1.02); + network.getTwoWindingsTransformer(t2w3.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.98); + + addVoltages(t2w1.getTerminal1().getBusView().getBus(), t2w2.getTerminal1().getBusView().getBus(), t2w3.getTerminal1().getBusView().getBus()); + addVoltages(network.getTwoWindingsTransformer(t2w1.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w2.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w3.getId()).getTerminal1().getBusView().getBus()); + } + + private boolean checkFlowsTest() { + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-3WT-Leg3"); + + TwtData t3wData = new TwtData(t3w, 0.0, false); + setStarBusVoltage(t3wData, t2w1.getTerminal2().getBusView().getBus()); + + BranchData t2w1Data = new BranchData(t2w1, 0.0, false, false); + BranchData t2w2Data = new BranchData(t2w2, 0.0, false, false); + BranchData t2w3Data = new BranchData(t2w3, 0.0, false, false); + + double tol = 0.000001; + assertEquals(t2w1Data.getComputedP1(), t3wData.getComputedP(ThreeSides.ONE), tol); + assertEquals(t2w2Data.getComputedP1(), t3wData.getComputedP(ThreeSides.TWO), tol); + assertEquals(t2w3Data.getComputedP1(), t3wData.getComputedP(ThreeSides.THREE), tol); + return true; + } + + @Test + void replaceFlowsNotWellOrientedTest() { + modifyNetworkForFlowsNotWellOrientedTest(); + assertTrue(checkFlowsTestNotWellOriented()); + } + + private void modifyNetworkForFlowsNotWellOrientedTest() { + t2w1.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.99); + t2w2.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 1.02); + t2w3.setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.98); + + network.getTwoWindingsTransformer(t2w1.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.99); + network.getTwoWindingsTransformer(t2w2.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 1.02); + network.getTwoWindingsTransformer(t2w3.getId()).setRatedU2(t2w1.getTerminal2().getVoltageLevel().getNominalV() * 0.98); + + addPhaseTapChanger(t2w2); + addPhaseTapChanger(network.getTwoWindingsTransformer(t2w2.getId())); + + String tw2Id = t2w2.getId(); + reOrientedTwoWindingsTransformer(t2w2); + reOrientedTwoWindingsTransformer(network.getTwoWindingsTransformer(tw2Id)); + t2w2 = t2w1.getNetwork().getTwoWindingsTransformer("3WT-Leg2-notWellOriented"); + + addVoltages(t2w1.getTerminal1().getBusView().getBus(), t2w2.getTerminal2().getBusView().getBus(), t2w3.getTerminal1().getBusView().getBus()); + addVoltages(network.getTwoWindingsTransformer(t2w1.getId()).getTerminal1().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w2.getId()).getTerminal2().getBusView().getBus(), + network.getTwoWindingsTransformer(t2w3.getId()).getTerminal1().getBusView().getBus()); + } + + private boolean checkFlowsTestNotWellOriented() { + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + ThreeWindingsTransformer t3w = network.getThreeWindingsTransformer("3WT-Leg1-3WT-Leg2-notWellOriented-3WT-Leg3"); + + TwtData t3wData = new TwtData(t3w, 0.0, false); + setStarBusVoltage(t3wData, t2w1.getTerminal2().getBusView().getBus()); + + BranchData t2w1Data = new BranchData(t2w1, 0.0, false, false); + BranchData t2w2Data = new BranchData(t2w2, 0.0, false, false); + BranchData t2w3Data = new BranchData(t2w3, 0.0, false, false); + + double tol = 0.000001; + assertEquals(t2w1Data.getComputedP1(), t3wData.getComputedP(ThreeSides.ONE), tol); + assertEquals(t2w2Data.getComputedP2(), t3wData.getComputedP(ThreeSides.TWO), tol); + assertEquals(t2w3Data.getComputedP1(), t3wData.getComputedP(ThreeSides.THREE), tol); + return true; + } + + @Test + void replaceNodeBreakerTest() { + modifyNetworkForNodeBreakerTest(); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + + Replace3TwoWindingsTransformersByThreeWindingsTransformers replace = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + replace.apply(network); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + } + + private void modifyNetworkForNodeBreakerTest() { + Network expectedNetwork = createThreeWindingsTransformerNodeBreakerNetwork(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replaceExpected = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replaceExpected.apply(expectedNetwork); + t2w1 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg1"); + assertNotNull(t2w1); + t2w2 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg2"); + assertNotNull(t2w2); + t2w3 = expectedNetwork.getTwoWindingsTransformer("3WT-Leg3"); + + network = createThreeWindingsTransformerNodeBreakerNetwork(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + } + + @Test + void getNameTest() { + AbstractNetworkModification networkModification = new Replace3TwoWindingsTransformersByThreeWindingsTransformers(); + assertEquals("Replace3TwoWindingsTransformersByThreeWindingsTransformers", networkModification.getName()); + } + + @Test + void hasImpactTest() { + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers modification = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + assertEquals(NetworkModificationImpact.HAS_IMPACT_ON_NETWORK, modification.hasImpactOnNetwork(network)); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceThreeWindingsTransformersBy3TwoWindingsTransformersTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceThreeWindingsTransformersBy3TwoWindingsTransformersTest.java new file mode 100644 index 00000000000..48988b0c5c1 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceThreeWindingsTransformersBy3TwoWindingsTransformersTest.java @@ -0,0 +1,337 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.modification; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.test.TestUtil; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory; +import com.powsybl.iidm.network.util.BranchData; +import com.powsybl.iidm.network.util.TwtData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collections; + +import static com.powsybl.iidm.modification.TransformersTestUtils.*; +import static com.powsybl.iidm.modification.util.ModificationReports.lostThreeWindingsTransformerExtensions; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ +class ReplaceThreeWindingsTransformersBy3TwoWindingsTransformersTest { + + private Network network; + private ThreeWindingsTransformer t3w; + + @BeforeEach + public void setUp() { + Network expectedNetwork = ThreeWindingsTransformerNetworkFactory.create(); + assertEquals(1, expectedNetwork.getThreeWindingsTransformerCount()); + t3w = expectedNetwork.getThreeWindingsTransformer("3WT"); + network = ThreeWindingsTransformerNetworkFactory.create(); + } + + @Test + void replaceBasicTest() { + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + } + + @Test + void replaceSelectedThreeWindingsTransformerTest() { + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + + String t3wId = network.getThreeWindingsTransformers().iterator().next().getId(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(Collections.singletonList(t3wId)); + replace.apply(network); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + } + + @Test + void nonReplacementTest() { + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + + String t3wId = "unknown threeWindingsTransformer"; + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(Collections.singletonList(t3wId)); + replace.apply(network); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + } + + @Test + void replaceRatioTapChangerTest() { + assertNull(t3w.getLeg1().getRatioTapChanger()); + assertNotNull(t3w.getLeg2().getRatioTapChanger()); + assertNotNull(t3w.getLeg3().getRatioTapChanger()); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertNull(t2w1.getRatioTapChanger()); + assertNotNull(t2w2.getRatioTapChanger()); + assertNotNull(t2w3.getRatioTapChanger()); + + assertTrue(compareRatioTapChanger(t3w.getLeg2().getRatioTapChanger(), t2w2.getRatioTapChanger())); + assertTrue(compareRatioTapChanger(t3w.getLeg3().getRatioTapChanger(), t2w3.getRatioTapChanger())); + } + + @Test + void replacePhaseTapChangerTest() { + modifyNetworkForPhaseTapChangerTest(); + + assertNull(t3w.getLeg1().getPhaseTapChanger()); + assertNotNull(t3w.getLeg2().getPhaseTapChanger()); + assertNull(t3w.getLeg3().getPhaseTapChanger()); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertNull(t2w1.getPhaseTapChanger()); + assertNotNull(t2w2.getPhaseTapChanger()); + assertNull(t2w3.getPhaseTapChanger()); + + assertTrue(comparePhaseTapChanger(t3w.getLeg2().getPhaseTapChanger(), t2w2.getPhaseTapChanger())); + } + + private void modifyNetworkForPhaseTapChangerTest() { + addPhaseTapChanger(t3w.getLeg2()); // modify expected network + addPhaseTapChanger(network.getThreeWindingsTransformer(t3w.getId()).getLeg2()); // modify network + } + + @Test + void replaceLoadingLimitsTest() { + modifyNetworkForLoadingLimitsTest(); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertTrue(compareOperationalLimitsGroups(t3w.getLeg1().getOperationalLimitsGroups(), t2w1.getOperationalLimitsGroups1())); + assertTrue(compareOperationalLimitsGroups(t3w.getLeg2().getOperationalLimitsGroups(), t2w2.getOperationalLimitsGroups1())); + assertTrue(compareOperationalLimitsGroups(t3w.getLeg3().getOperationalLimitsGroups(), t2w3.getOperationalLimitsGroups1())); + } + + private void modifyNetworkForLoadingLimitsTest() { + addLoadingLimits(t3w.getLeg1()); + addLoadingLimits(t3w.getLeg2()); + addLoadingLimits(t3w.getLeg3()); + addLoadingLimits(network.getThreeWindingsTransformer(t3w.getId()).getLeg1()); + addLoadingLimits(network.getThreeWindingsTransformer(t3w.getId()).getLeg2()); + addLoadingLimits(network.getThreeWindingsTransformer(t3w.getId()).getLeg3()); + } + + @Test + void replacePropertiesTest() { + modifyNetworkForPropertiesTest(); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("v", "132.5"); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("angle", "2.5"); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-summer", "summer"); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-winter", "winter"); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + + assertEquals(132.5, t2w1.getTerminal2().getBusView().getBus().getV()); + assertEquals(2.5, t2w1.getTerminal2().getBusView().getBus().getAngle()); + assertEquals("summer", t2w1.getProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-summer")); + assertEquals("winter", t2w1.getProperty("CGMES.OperationalLimitSet_" + "OperationalLimitsGroup-winter")); + } + + private void modifyNetworkForPropertiesTest() { + addLoadingLimits(t3w.getLeg1()); + addLoadingLimits(network.getThreeWindingsTransformer(t3w.getId()).getLeg1()); + } + + @Test + void replaceExtensionsTest() { + modifyNetworkForExtensionsTest(); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertEquals(createThreeWindingsTransformerFortescueToString(t3w), createTwoWindingsTransformerFortescueToString(t2w1, t2w2, t2w3)); + assertEquals(createThreeWindingsTransformerPhaseAngleClockToString(t3w), createTwoWindingsTransformerPhaseAngleClockToString(t2w2, t2w3)); + assertEquals(createThreeWindingsTransformerToBeEstimatedToString(t3w), createTwoWindingsTransformerToBeEstimatedToString(t2w1, t2w2, t2w3)); + } + + private void modifyNetworkForExtensionsTest() { + addExtensions(t3w); + addExtensions(network.getThreeWindingsTransformer(t3w.getId())); + } + + @Test + void replaceAliasesTest() { + addLegAliases("1"); + addLegAliases("2"); + addLegAliases("3"); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + assertTrue(compareAliases(t2w1, "1")); + assertTrue(compareAliases(t2w2, "2")); + assertTrue(compareAliases(t2w3, "3")); + } + + private void addLegAliases(String leg) { + network.getThreeWindingsTransformer(t3w.getId()).addAlias("transformerEnd-Leg" + leg, "CGMES.TransformerEnd" + leg); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("terminal-Leg" + leg, "CGMES.Terminal" + leg); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("ratioTapChanger-Leg" + leg, "CGMES.RatioTapChanger" + leg); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("phaseTapChanger-Leg" + leg, "CGMES.PhaseTapChanger" + leg); + } + + private boolean compareAliases(TwoWindingsTransformer t2w, String leg) { + return t2w.getAliasFromType("CGMES.TransformerEnd1").orElseThrow().equals("transformerEnd-Leg" + leg) + && t2w.getAliasFromType("CGMES.Terminal1").orElseThrow().equals("terminal-Leg" + leg) + && t2w.getAliasFromType("CGMES.RatioTapChanger1").orElseThrow().equals("ratioTapChanger-Leg" + leg) + && t2w.getAliasFromType("CGMES.PhaseTapChanger1").orElseThrow().equals("phaseTapChanger-Leg" + leg); + } + + @Test + void reportNodeTest() throws IOException { + network.getThreeWindingsTransformer(t3w.getId()).setProperty("t3w property1", "t3w-value1"); + network.getThreeWindingsTransformer(t3w.getId()).setProperty("t3w property2", "t3w-value2"); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("t3w-alias1"); + network.getThreeWindingsTransformer(t3w.getId()).addAlias("t3w-alias2"); + ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test reportNode").build(); + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network, reportNode); + + StringWriter sw1 = new StringWriter(); + reportNode.print(sw1); + assertEquals(""" + + test reportNode + + Replaced ThreeWindingsTransformer by 3 TwoWindingsTransformers + ThreeWindingsTransformer 3WT removed + Alias [t3w-alias1,t3w-alias2] of threeWindingsTransformer 3WT will be lost + VoltageLevel 3WT-Star-VL created + TwoWindingsTransformer 3WT-Leg1 created + TwoWindingsTransformer 3WT-Leg2 created + TwoWindingsTransformer 3WT-Leg3 created + """, TestUtil.normalizeLineSeparator(sw1.toString())); + } + + @Test + void reportNodeExtensionsTest() throws IOException { + ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test reportNode").build(); + lostThreeWindingsTransformerExtensions(reportNode, "unknownExtension1, unknownExtension2", t3w.getId()); + + StringWriter sw1 = new StringWriter(); + reportNode.print(sw1); + assertEquals(""" + + test reportNode + Extension [unknownExtension1, unknownExtension2] of threeWindingsTransformer 3WT will be lost + """, TestUtil.normalizeLineSeparator(sw1.toString())); + } + + @Test + void replaceFlowsTest() { + modifyNetworkForFlowsTest(); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + TwoWindingsTransformer t2w1 = network.getTwoWindingsTransformer("3WT-Leg1"); + TwoWindingsTransformer t2w2 = network.getTwoWindingsTransformer("3WT-Leg2"); + TwoWindingsTransformer t2w3 = network.getTwoWindingsTransformer("3WT-Leg3"); + + TwtData t3wData = new TwtData(t3w, 0.0, false); + setStarBusVoltage(t3wData, t2w1.getTerminal2().getBusView().getBus()); + + BranchData t2w1Data = new BranchData(t2w1, 0.0, false, false); + BranchData t2w2Data = new BranchData(t2w2, 0.0, false, false); + BranchData t2w3Data = new BranchData(t2w3, 0.0, false, false); + + double tol = 0.000001; + assertEquals(t3wData.getComputedP(ThreeSides.ONE), t2w1Data.getComputedP1(), tol); + assertEquals(t3wData.getComputedP(ThreeSides.TWO), t2w2Data.getComputedP1(), tol); + assertEquals(t3wData.getComputedP(ThreeSides.THREE), t2w3Data.getComputedP1(), tol); + } + + private void modifyNetworkForFlowsTest() { + addVoltages(t3w.getLeg1().getTerminal().getBusView().getBus(), t3w.getLeg2().getTerminal().getBusView().getBus(), t3w.getLeg3().getTerminal().getBusView().getBus()); + addVoltages(network.getThreeWindingsTransformer(t3w.getId()).getLeg1().getTerminal().getBusView().getBus(), + network.getThreeWindingsTransformer(t3w.getId()).getLeg2().getTerminal().getBusView().getBus(), + network.getThreeWindingsTransformer(t3w.getId()).getLeg3().getTerminal().getBusView().getBus()); + } + + @Test + void replaceNodeBreakerTest() { + modifyNetworkForNodeBreakerTest(); + + assertEquals(3, network.getVoltageLevelCount()); + assertEquals(1, network.getThreeWindingsTransformerCount()); + assertEquals(0, network.getTwoWindingsTransformerCount()); + + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers replace = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + replace.apply(network); + + assertEquals(4, network.getVoltageLevelCount()); + assertEquals(0, network.getThreeWindingsTransformerCount()); + assertEquals(3, network.getTwoWindingsTransformerCount()); + } + + private void modifyNetworkForNodeBreakerTest() { + Network expectedNetwork = createThreeWindingsTransformerNodeBreakerNetwork(); + t3w = expectedNetwork.getThreeWindingsTransformer("3WT"); + network = createThreeWindingsTransformerNodeBreakerNetwork(); + } + + @Test + void getNameTest() { + AbstractNetworkModification networkModification = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + assertEquals("ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers", networkModification.getName()); + } + + @Test + void hasImpactTest() { + ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers modification = new ReplaceThreeWindingsTransformersBy3TwoWindingsTransformers(); + assertEquals(NetworkModificationImpact.HAS_IMPACT_ON_NETWORK, modification.hasImpactOnNetwork(network)); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TransformersTestUtils.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TransformersTestUtils.java new file mode 100644 index 00000000000..f4146e4da92 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TransformersTestUtils.java @@ -0,0 +1,517 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.modification; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import com.powsybl.iidm.network.util.TwtData; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static com.powsybl.iidm.modification.util.TransformerUtils.copyAndAddPhaseTapChanger; +import static com.powsybl.iidm.modification.util.TransformerUtils.copyAndAddRatioTapChanger; + +/** + * @author Luma Zamarreño {@literal } + * @author José Antonio Marqués {@literal } + */ + +final class TransformersTestUtils { + + private TransformersTestUtils() { + } + + static void addPhaseTapChanger(TwoWindingsTransformer t2w) { + PhaseTapChangerAdder ptcAdder = t2w.newPhaseTapChanger(); + fillTapChangerAdder(ptcAdder, t2w.getTerminal1()); + ptcAdder.add(); + } + + static void addPhaseTapChanger(ThreeWindingsTransformer.Leg leg) { + PhaseTapChangerAdder ptcAdder = leg.newPhaseTapChanger(); + fillTapChangerAdder(ptcAdder, leg.getTerminal()); + ptcAdder.add(); + } + + private static void fillTapChangerAdder(PhaseTapChangerAdder ptcAdder, Terminal terminal) { + ptcAdder.setLowTapPosition(0) + .setTapPosition(2) + .setRegulationTerminal(terminal) + .setRegulationValue(10.0) + .setRegulationMode(PhaseTapChanger.RegulationMode.FIXED_TAP) + .setTargetDeadband(0.5) + .setRegulating(false) + .beginStep() + .setRho(0.99) + .setAlpha(-2.0) + .setR(1.01) + .setX(1.02) + .setG(1.03) + .setB(1.04) + .endStep() + .beginStep() + .setRho(1.00) + .setAlpha(0.0) + .setR(0.0) + .setX(0.0) + .setG(0.0) + .setB(0.0) + .endStep() + .beginStep() + .setRho(1.01) + .setAlpha(2.0) + .setR(0.99) + .setX(0.98) + .setG(0.97) + .setB(0.96) + .endStep() + .add(); + } + + static void addLoadingLimitsEnd1(TwoWindingsTransformer t2w) { + OperationalLimitsGroup summer = t2w.newOperationalLimitsGroup1("OperationalLimitsGroup-summer"); + OperationalLimitsGroup winter = t2w.newOperationalLimitsGroup1("OperationalLimitsGroup-winter"); + addSummerLoadingLimits(summer); + addWinterLoadingLimits(winter); + } + + static void addLoadingLimitsEnd2(TwoWindingsTransformer t2w) { + OperationalLimitsGroup summer = t2w.newOperationalLimitsGroup2("OperationalLimitsGroup-summer-end2"); + OperationalLimitsGroup winter = t2w.newOperationalLimitsGroup2("OperationalLimitsGroup-winter-end2"); + addSummerLoadingLimits(summer); + addWinterLoadingLimits(winter); + } + + static void addLoadingLimits(ThreeWindingsTransformer.Leg leg) { + OperationalLimitsGroup summer = leg.newOperationalLimitsGroup("OperationalLimitsGroup-summer"); + OperationalLimitsGroup winter = leg.newOperationalLimitsGroup("OperationalLimitsGroup-winter"); + addSummerLoadingLimits(summer); + addWinterLoadingLimits(winter); + } + + private static void addSummerLoadingLimits(OperationalLimitsGroup summer) { + summer.newActivePowerLimits() + .setPermanentLimit(100.0) + .beginTemporaryLimit() + .setName("TemporaryActivePowerLimit-1-summer") + .setAcceptableDuration(2) + .setValue(110.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryActivePowerLimit-2-summer") + .setAcceptableDuration(1) + .setValue(120.0) + .endTemporaryLimit().add(); + summer.newApparentPowerLimits() + .setPermanentLimit(105.0) + .beginTemporaryLimit() + .setName("TemporaryApparentPowerLimit-1") + .setAcceptableDuration(2) + .setValue(115.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryApparentPowerLimit-2-summer") + .setAcceptableDuration(1) + .setValue(125.0) + .endTemporaryLimit().add(); + summer.newCurrentLimits() + .setPermanentLimit(1050.0) + .beginTemporaryLimit() + .setName("TemporaryCurrentLimit-1-summer") + .setAcceptableDuration(2) + .setValue(1150.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryCurrentLimit-2-summer") + .setAcceptableDuration(1) + .setValue(1250.0) + .endTemporaryLimit().add(); + } + + private static void addWinterLoadingLimits(OperationalLimitsGroup winter) { + winter.newActivePowerLimits() + .setPermanentLimit(125.0) + .beginTemporaryLimit() + .setName("TemporaryActivePowerLimit-1-winter") + .setAcceptableDuration(3) + .setValue(135.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryActivePowerLimit-2-winter") + .setAcceptableDuration(2) + .setValue(145.0) + .endTemporaryLimit().add(); + winter.newApparentPowerLimits() + .setPermanentLimit(130.0) + .beginTemporaryLimit() + .setName("TemporaryApparentPowerLimit-1-winter") + .setAcceptableDuration(3) + .setValue(140.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryApparentPowerLimit-2-winter") + .setAcceptableDuration(2) + .setValue(150.0) + .endTemporaryLimit().add(); + winter.newCurrentLimits() + .setPermanentLimit(130.0) + .beginTemporaryLimit() + .setName("TemporaryCurrentLimit-1-winter") + .setAcceptableDuration(3) + .setValue(140.0) + .endTemporaryLimit() + .beginTemporaryLimit() + .setName("TemporaryCurrentLimit-2-winter") + .setAcceptableDuration(2) + .setValue(150.0) + .endTemporaryLimit().add(); + } + + static boolean compareRatioTapChanger(RatioTapChanger expectedRtc, RatioTapChanger rtc) { + String expected = ratioTapChangerToString(expectedRtc); + String actual = ratioTapChangerToString(rtc); + return expected.equals(actual); + } + + private static String ratioTapChangerToString(RatioTapChanger rtc) { + List strings = new ArrayList<>(); + strings.add(String.valueOf(rtc.getLowTapPosition())); + strings.add(String.valueOf(rtc.getTapPosition())); + strings.add(rtc.getRegulationTerminal().getBusView().getBus().getId()); + strings.add(String.valueOf(rtc.getTargetV())); + strings.add(String.valueOf(rtc.getRegulationValue())); + strings.add(String.valueOf(rtc.getRegulationMode())); + strings.add(String.valueOf(rtc.getTargetDeadband())); + strings.add(String.valueOf(rtc.isRegulating())); + strings.add(String.valueOf(rtc.getStepCount())); + rtc.getAllSteps().forEach((step, rtcStep) -> { + strings.add(String.valueOf(step)); + strings.add(String.valueOf(rtcStep.getRho())); + strings.add(String.valueOf(rtcStep.getR())); + strings.add(String.valueOf(rtcStep.getX())); + strings.add(String.valueOf(rtcStep.getG())); + strings.add(String.valueOf(rtcStep.getB())); + }); + + return String.join(",", strings); + } + + static boolean comparePhaseTapChanger(PhaseTapChanger expectedPtc, PhaseTapChanger ptc) { + String expected = phaseTapChangerToString(expectedPtc); + String actual = phaseTapChangerToString(ptc); + return expected.equals(actual); + } + + private static String phaseTapChangerToString(PhaseTapChanger ptc) { + List strings = new ArrayList<>(); + strings.add(String.valueOf(ptc.getLowTapPosition())); + strings.add(String.valueOf(ptc.getTapPosition())); + strings.add(ptc.getRegulationTerminal().getBusView().getBus().getId()); + strings.add(String.valueOf(ptc.getRegulationValue())); + strings.add(String.valueOf(ptc.getRegulationMode())); + strings.add(String.valueOf(ptc.getTargetDeadband())); + strings.add(String.valueOf(ptc.isRegulating())); + strings.add(String.valueOf(ptc.getStepCount())); + ptc.getAllSteps().forEach((step, rtcStep) -> { + strings.add(String.valueOf(step)); + strings.add(String.valueOf(rtcStep.getRho())); + strings.add(String.valueOf(rtcStep.getAlpha())); + strings.add(String.valueOf(rtcStep.getR())); + strings.add(String.valueOf(rtcStep.getX())); + strings.add(String.valueOf(rtcStep.getG())); + strings.add(String.valueOf(rtcStep.getB())); + }); + + return String.join(",", strings); + } + + static boolean compareOperationalLimitsGroups(Collection expected, Collection actual) { + String expectedString = operationalLimitsToString(expected); + String actualString = operationalLimitsToString(actual); + return expectedString.equals(actualString); + } + + private static String operationalLimitsToString(Collection operationalLimitsGroups) { + List strings = new ArrayList<>(); + operationalLimitsGroups.forEach(operationalLimitGroup -> { + strings.add(operationalLimitGroup.getId()); + operationalLimitGroup.getActivePowerLimits().ifPresent(activePowerLimits -> { + strings.add(activePowerLimits.getLimitType().name()); + addLegLimits(activePowerLimits, strings); + }); + operationalLimitGroup.getApparentPowerLimits().ifPresent(apparentPowerLimits -> { + strings.add(apparentPowerLimits.getLimitType().name()); + addLegLimits(apparentPowerLimits, strings); + }); + operationalLimitGroup.getCurrentLimits().ifPresent(currentLimits -> { + strings.add(currentLimits.getLimitType().name()); + addLegLimits(currentLimits, strings); + }); + }); + return String.join(",", strings); + } + + private static void addLegLimits(LoadingLimits loadingLimits, List strings) { + strings.add(String.valueOf(loadingLimits.getPermanentLimit())); + loadingLimits.getTemporaryLimits().forEach(temporaryLimit -> { + strings.add(temporaryLimit.getName()); + strings.add(String.valueOf(temporaryLimit.getAcceptableDuration())); + strings.add(String.valueOf(temporaryLimit.getValue())); + strings.add(String.valueOf(temporaryLimit.isFictitious())); + }); + } + + static String createThreeWindingsTransformerFortescueToString(ThreeWindingsTransformer t3w) { + List strings = new ArrayList<>(); + ThreeWindingsTransformerFortescue extension = t3w.getExtension(ThreeWindingsTransformerFortescue.class); + if (extension != null) { + addLegFortescue(extension.getLeg1(), strings); + addLegFortescue(extension.getLeg2(), strings); + addLegFortescue(extension.getLeg3(), strings); + } + return String.join(",", strings); + } + + private static void addLegFortescue(ThreeWindingsTransformerFortescue.LegFortescue legFortescue, List strings) { + strings.add(legFortescue.getConnectionType().name()); + strings.add(String.valueOf(legFortescue.isFreeFluxes())); + strings.add(String.valueOf(legFortescue.getGroundingR())); + strings.add(String.valueOf(legFortescue.getGroundingX())); + strings.add(String.valueOf(legFortescue.getRz())); + strings.add(String.valueOf(legFortescue.getXz())); + } + + static String createTwoWindingsTransformerFortescueToString(TwoWindingsTransformer t2w1, TwoWindingsTransformer t2w2, TwoWindingsTransformer t2w3) { + List strings = new ArrayList<>(); + addT2wFortescue(t2w1.getExtension(TwoWindingsTransformerFortescue.class), strings); + addT2wFortescue(t2w2.getExtension(TwoWindingsTransformerFortescue.class), strings); + addT2wFortescue(t2w3.getExtension(TwoWindingsTransformerFortescue.class), strings); + return String.join(",", strings); + } + + private static void addT2wFortescue(TwoWindingsTransformerFortescue t2wFortescue, List strings) { + if (t2wFortescue != null) { + strings.add(t2wFortescue.getConnectionType1().name()); + strings.add(String.valueOf(t2wFortescue.isFreeFluxes())); + strings.add(String.valueOf(t2wFortescue.getGroundingR1())); + strings.add(String.valueOf(t2wFortescue.getGroundingX1())); + strings.add(String.valueOf(t2wFortescue.getRz())); + strings.add(String.valueOf(t2wFortescue.getXz())); + } + } + + static String createThreeWindingsTransformerPhaseAngleClockToString(ThreeWindingsTransformer t3w) { + List strings = new ArrayList<>(); + ThreeWindingsTransformerPhaseAngleClock extension = t3w.getExtension(ThreeWindingsTransformerPhaseAngleClock.class); + if (extension != null) { + strings.add(String.valueOf(extension.getPhaseAngleClockLeg2())); + strings.add(String.valueOf(extension.getPhaseAngleClockLeg3())); + } + return String.join(",", strings); + } + + static String createTwoWindingsTransformerPhaseAngleClockToString(TwoWindingsTransformer t2w2, TwoWindingsTransformer t2w3) { + List strings = new ArrayList<>(); + TwoWindingsTransformerPhaseAngleClock t2w2Extension = t2w2.getExtension(TwoWindingsTransformerPhaseAngleClock.class); + if (t2w2Extension != null) { + strings.add(String.valueOf(t2w2Extension.getPhaseAngleClock())); + } + TwoWindingsTransformerPhaseAngleClock t2w3Extension = t2w3.getExtension(TwoWindingsTransformerPhaseAngleClock.class); + if (t2w3Extension != null) { + strings.add(String.valueOf(t2w3Extension.getPhaseAngleClock())); + } + return String.join(",", strings); + } + + static String createThreeWindingsTransformerToBeEstimatedToString(ThreeWindingsTransformer t3w) { + List strings = new ArrayList<>(); + ThreeWindingsTransformerToBeEstimated extension = t3w.getExtension(ThreeWindingsTransformerToBeEstimated.class); + if (extension != null) { + strings.add(String.valueOf(extension.shouldEstimateRatioTapChanger1())); + strings.add(String.valueOf(extension.shouldEstimatePhaseTapChanger1())); + strings.add(String.valueOf(extension.shouldEstimateRatioTapChanger2())); + strings.add(String.valueOf(extension.shouldEstimatePhaseTapChanger2())); + strings.add(String.valueOf(extension.shouldEstimateRatioTapChanger3())); + strings.add(String.valueOf(extension.shouldEstimatePhaseTapChanger3())); + } + return String.join(",", strings); + } + + static String createTwoWindingsTransformerToBeEstimatedToString(TwoWindingsTransformer t2w1, TwoWindingsTransformer t2w2, TwoWindingsTransformer t2w3) { + List strings = new ArrayList<>(); + addToBeEstimated(t2w1.getExtension(TwoWindingsTransformerToBeEstimated.class), strings); + addToBeEstimated(t2w2.getExtension(TwoWindingsTransformerToBeEstimated.class), strings); + addToBeEstimated(t2w3.getExtension(TwoWindingsTransformerToBeEstimated.class), strings); + return String.join(",", strings); + } + + private static void addToBeEstimated(TwoWindingsTransformerToBeEstimated extension, List strings) { + if (extension != null) { + strings.add(String.valueOf(extension.shouldEstimateRatioTapChanger())); + strings.add(String.valueOf(extension.shouldEstimatePhaseTapChanger())); + } + } + + static void addVoltages(Bus bus1, Bus bus2, Bus bus3) { + bus1.setV(bus1.getVoltageLevel().getNominalV() * 1.01); + bus1.setAngle(2.0); + + bus2.setV(bus2.getVoltageLevel().getNominalV() * 0.99); + bus2.setAngle(4.0); + + bus3.setV(bus3.getVoltageLevel().getNominalV() * 0.98); + bus3.setAngle(3.0); + } + + static void setStarBusVoltage(TwtData twtData, Bus starBus) { + starBus.setV(twtData.getStarU()); + starBus.setAngle(Math.toDegrees(twtData.getStarTheta())); + } + + static void reOrientedTwoWindingsTransformer(TwoWindingsTransformer t2w) { + TwoWindingsTransformer t2wNotWellOriented = t2w.getTerminal1().getVoltageLevel().getSubstation().orElseThrow().newTwoWindingsTransformer() + .setId(t2w.getId() + "-" + "notWellOriented") + .setName(t2w.getNameOrId() + "-" + "notWellOriented") + .setRatedU1(t2w.getRatedU2()) + .setRatedU2(t2w.getRatedU1()) + .setR(t2w.getR()) + .setX(t2w.getX()) + .setG(t2w.getG()) + .setB(t2w.getB()) + .setRatedS(t2w.getRatedS()) + .setVoltageLevel1(t2w.getTerminal2().getVoltageLevel().getId()) + .setConnectableBus1(t2w.getTerminal2().getBusBreakerView().getBus().getId()) + .setBus1(t2w.getTerminal2().getBusBreakerView().getBus().getId()) + .setVoltageLevel2(t2w.getTerminal1().getVoltageLevel().getId()) + .setConnectableBus2(t2w.getTerminal1().getBusBreakerView().getBus().getId()) + .setBus2(t2w.getTerminal1().getBusBreakerView().getBus().getId()) + .add(); + + copyAndAddRatioTapChanger(t2wNotWellOriented.newRatioTapChanger(), t2w.getRatioTapChanger()); + copyAndAddPhaseTapChanger(t2wNotWellOriented.newPhaseTapChanger(), t2w.getPhaseTapChanger()); + + t2w.remove(); + } + + static Network createThreeWindingsTransformerNodeBreakerNetwork() { + Network network = NetworkFactory.findDefault().createNetwork("three-windings-transformer-nodeBreaker", "test"); + network.setCaseDate(ZonedDateTime.parse("2018-03-05T13:30:30.486+01:00")); + Substation substation = network.newSubstation() + .setId("SUBSTATION") + .setCountry(Country.FR) + .add(); + VoltageLevel vl1 = substation.newVoltageLevel() + .setId("VL_132") + .setNominalV(132.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + VoltageLevel vl2 = substation.newVoltageLevel() + .setId("VL_33") + .setNominalV(33.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + VoltageLevel vl3 = substation.newVoltageLevel() + .setId("VL_11") + .setNominalV(11.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + substation.newThreeWindingsTransformer() + .setId("3WT") + .setRatedU0(132.0) + .newLeg1() + .setR(17.424) + .setX(1.7424) + .setG(0.00573921028466483) + .setB(0.000573921028466483) + .setRatedU(132.0) + .setVoltageLevel(vl1.getId()) + .setNode(1) + .add() + .newLeg2() + .setR(1.089) + .setX(0.1089) + .setG(0.0) + .setB(0.0) + .setRatedU(33.0) + .setVoltageLevel(vl2.getId()) + .setNode(1) + .add() + .newLeg3() + .setR(0.121) + .setX(0.0121) + .setG(0.0) + .setB(0.0) + .setRatedU(11.0) + .setVoltageLevel(vl3.getId()) + .setNode(1) + .add() + .add(); + return network; + } + + static void addExtensions(ThreeWindingsTransformer t3w) { + t3w.newExtension(ThreeWindingsTransformerFortescueAdder.class).leg1() + .withConnectionType(WindingConnectionType.Y) + .withFreeFluxes(false) + .withGroundingR(0.1) + .withGroundingX(0.11) + .withRz(0.12) + .withXz(0.121) + .leg2().withConnectionType(WindingConnectionType.Y) + .withFreeFluxes(false) + .withGroundingR(0.2) + .withGroundingX(0.21) + .withRz(0.22) + .withXz(0.221) + .leg3() + .withConnectionType(WindingConnectionType.DELTA) + .withFreeFluxes(true) + .withGroundingR(0.3) + .withGroundingX(0.31) + .withRz(0.32) + .withXz(0.321) + .add(); + t3w.newExtension(ThreeWindingsTransformerPhaseAngleClockAdder.class) + .withPhaseAngleClockLeg2(2) + .withPhaseAngleClockLeg3(6) + .add(); + t3w.newExtension(ThreeWindingsTransformerToBeEstimatedAdder.class) + .withRatioTapChanger1Status(true) + .withPhaseTapChanger1Status(true) + .withRatioTapChanger2Status(true) + .withPhaseTapChanger2Status(true) + .withRatioTapChanger3Status(true) + .withPhaseTapChanger3Status(true) + .add(); + } + + static void addExtensions(TwoWindingsTransformer t2w, int diferenceFactor) { + t2w.newExtension(TwoWindingsTransformerFortescueAdder.class) + .withConnectionType1(WindingConnectionType.Y) + .withFreeFluxes(false) + .withGroundingR1(0.1 * diferenceFactor) + .withGroundingX1(0.11 * diferenceFactor) + .withRz(0.12 * diferenceFactor) + .withXz(0.121 * diferenceFactor) + .withConnectionType2(WindingConnectionType.Y) + .withGroundingR1(0.0) + .withGroundingX1(0.0) + .add(); + t2w.newExtension(TwoWindingsTransformerPhaseAngleClockAdder.class) + .withPhaseAngleClock(2 * diferenceFactor) + .add(); + t2w.newExtension(TwoWindingsTransformerToBeEstimatedAdder.class) + .withRatioTapChangerStatus(true) + .withPhaseTapChangerStatus(true) + .add(); + } +} diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerInternalConnectionsTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerInternalConnectionsTest.java index 7c142cdf18a..a065b9a163b 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerInternalConnectionsTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractNodeBreakerInternalConnectionsTest.java @@ -12,8 +12,8 @@ import com.powsybl.math.graph.TraverseResult; import org.junit.jupiter.api.Test; -import java.util.*; -import java.util.stream.Collectors; +import java.util.HashSet; +import java.util.List; import static com.powsybl.iidm.network.VoltageLevel.NodeBreakerView.InternalConnection; import static org.junit.jupiter.api.Assertions.*; @@ -32,47 +32,28 @@ public void testTraversalInternalConnections() { createNetwork(network, all); VoltageLevel vl = network.getVoltageLevel(S5_10K_V); - assertEquals(6, vl.getNodeBreakerView().getInternalConnectionCount()); + assertEquals(10, vl.getNodeBreakerView().getInternalConnectionCount()); List internalConnections = vl.getNodeBreakerView().getInternalConnectionStream().toList(); - int[] expecteds1 = new int[]{7, 6, 4, 5, 9, 8}; - int[] expecteds2 = new int[]{0, 3, 3, 2, 2, 1}; - assertEquals(expecteds1.length, expecteds2.length); - for (int i = 0; i < expecteds1.length; i++) { - assertEquals(expecteds1[i], internalConnections.get(i).getNode1()); - assertEquals(expecteds2[i], internalConnections.get(i).getNode2()); + int[][] expectedIcNodes = new int[][]{{7, 0}, {6, 3}, {4, 3}, {5, 2}, {9, 2}, {8, 1}, {1, 10}, {3, 11}, {2, 12}, {2, 13}}; + for (int i = 0; i < 10; i++) { + assertEquals(expectedIcNodes[i][0], internalConnections.get(i).getNode1()); + assertEquals(expectedIcNodes[i][1], internalConnections.get(i).getNode2()); } - Iterator nodeIterator7 = vl.getNodeBreakerView().getNodesInternalConnectedTo(7).iterator(); - assertEquals(0, (int) nodeIterator7.next()); - assertFalse(nodeIterator7.hasNext()); + assertEquals(List.of(0), vl.getNodeBreakerView().getNodesInternalConnectedTo(7)); + assertEquals(List.of(5, 9, 12, 13), vl.getNodeBreakerView().getNodesInternalConnectedTo(2)); + assertEquals(List.of(6, 4, 11), vl.getNodeBreakerView().getNodeInternalConnectedToStream(3).boxed().toList()); - Iterator nodeIterator2 = vl.getNodeBreakerView().getNodesInternalConnectedTo(2).iterator(); - assertEquals(5, (int) nodeIterator2.next()); - assertEquals(9, (int) nodeIterator2.next()); - assertFalse(nodeIterator2.hasNext()); + assertEquals(new InternalConnections().add(0, 7), findFirstInternalConnections(vl)); - List nodesInternallyConnectedTo3 = vl.getNodeBreakerView().getNodeInternalConnectedToStream(3).boxed().toList(); - assertEquals(Arrays.asList(6, 4), nodesInternallyConnectedTo3); + // Find the internal connections encountered before encountering a terminal, starting from every node + // Only internal connections connecting two nodes having both a terminal are expected to be missing + InternalConnections icConnectedToAtMostOneTerminal = findInternalConnectionsTraverseStoppingAtTerminals(vl); + InternalConnections expected = new InternalConnections(); + expected.add(7, 0).add(6, 3).add(4, 3).add(5, 2).add(9, 2).add(8, 1); + assertEquals(expected, icConnectedToAtMostOneTerminal); - // Find the first internal connection encountered - InternalConnections firstInternalConnectionFound = findFirstInternalConnections(vl); - assertEquals(new InternalConnections().add(0, 7), firstInternalConnectionFound); - - // Find the internal connections encountered before encountering a terminal - InternalConnections foundStoppingAtTerminals = findInternalConnectionsTraverseStoppingAtTerminals(vl); - // If we stop traversal at terminals - // some internal connections are expected to be missing - InternalConnections expectedMissing = new InternalConnections().add(6, 3).add(9, 2).add(4, 3); - - // Compute all missing connections - Set actualMissing = all.stream() - .filter(c -> !foundStoppingAtTerminals.contains(c)) - .collect(Collectors.toSet()); - assertEquals(expectedMissing, actualMissing); - - InternalConnections actual = findInternalConnections(vl); - InternalConnections expected = all; - assertEquals(expected, actual); + assertEquals(all, findInternalConnections(vl)); } @@ -187,15 +168,23 @@ private void createNetwork(Network network, InternalConnections internalConnecti addInternalConnection(topo, internalConnections, 5, 2); addInternalConnection(topo, internalConnections, 9, 2); addInternalConnection(topo, internalConnections, 8, 1); + addInternalConnection(topo, internalConnections, 1, 10); + addInternalConnection(topo, internalConnections, 3, 11); + addInternalConnection(topo, internalConnections, 2, 12); + addInternalConnection(topo, internalConnections, 2, 13); Substation s4 = network.newSubstation() .setId("S4") .setCountry(Country.FR) .add(); - s4.newVoltageLevel() + VoltageLevel vl2 = s4.newVoltageLevel() .setId("S4 10kV") .setNominalV(10.0) .setTopologyKind(TopologyKind.NODE_BREAKER) .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("NODE40") + .setNode(1) + .add(); network.newLine() .setId("L6") .setVoltageLevel1("S4 10kV") @@ -209,6 +198,12 @@ private void createNetwork(Network network, InternalConnections internalConnecti .setG2(0) .setB2(0) .add(); + vl2.getNodeBreakerView().newSwitch() + .setId("DISCONNECTOR1") + .setNode1(0) + .setNode2(1) + .setKind(SwitchKind.DISCONNECTOR) + .add(); } private void addInternalConnection( diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java index 5f73b06b4c7..681bc8e710f 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java @@ -16,8 +16,6 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.*; public abstract class AbstractTapChangerTest { diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java index 9e414ef43e4..3654f96fa9d 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java @@ -8,17 +8,18 @@ package com.powsybl.iidm.network.tck.extensions; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Terminal; +import com.powsybl.iidm.network.VariantManager; import com.powsybl.iidm.network.extensions.ReferenceTerminals; import com.powsybl.iidm.network.extensions.ReferenceTerminalsAdder; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Set; @@ -91,6 +92,9 @@ public void testVariants() { .withTerminals(Set.of(gh1.getTerminal())) .add(); ReferenceTerminals ext = network.getExtension(ReferenceTerminals.class); + assertEquals(1, gh1.getTerminal().getReferrers().size()); + assertEquals(0, gh2.getTerminal().getReferrers().size()); + assertEquals(0, gh3.getTerminal().getReferrers().size()); // create variants String variant1 = "variant1"; @@ -101,9 +105,11 @@ public void testVariants() { // add gh2 to variant1 variantManager.setWorkingVariant(variant1); ext.addReferenceTerminal(gh2.getTerminal()); + assertEquals(1, gh2.getTerminal().getReferrers().size()); // add gh3 to variant2 variantManager.setWorkingVariant(variant2); ext.addReferenceTerminal(gh3.getTerminal()); + assertEquals(1, gh3.getTerminal().getReferrers().size()); // initial variant unmodified variantManager.setWorkingVariant(INITIAL_VARIANT_ID); @@ -123,6 +129,8 @@ public void testVariants() { // clear variant 1 variantManager.setWorkingVariant(variant1); ext.reset(); + assertEquals(0, gh2.getTerminal().getReferrers().size()); + assertEquals(1, gh3.getTerminal().getReferrers().size()); // check variant 1 empty assertEquals(0, ext.getReferenceTerminals().size()); @@ -281,31 +289,4 @@ public void testRemoveEquipment() { assertEquals(1, ext.getReferenceTerminals().size()); assertTrue(ext.getReferenceTerminals().contains(gh2.getTerminal())); } - - @Test - public void testCleanup() { - Network net = Mockito.spy(EurostagTutorialExample1Factory.create()); - - net.newExtension(ReferenceTerminalsAdder.class) - .withTerminals(Collections.emptySet()) - .add(); - // check listener added - Mockito.verify(net, Mockito.times(1)).addListener(Mockito.any()); - Mockito.verify(net, Mockito.times(0)).removeListener(Mockito.any()); - - // overwrite existing extension - net.newExtension(ReferenceTerminalsAdder.class) - .withTerminals(Collections.emptySet()) - .add(); - // check old listener removed and new listener added - Mockito.verify(net, Mockito.times(2)).addListener(Mockito.any()); - Mockito.verify(net, Mockito.times(1)).removeListener(Mockito.any()); - - // remove extension - net.removeExtension(ReferenceTerminals.class); - // check all clean - Mockito.verify(net, Mockito.times(2)).addListener(Mockito.any()); - Mockito.verify(net, Mockito.times(2)).removeListener(Mockito.any()); - } - } diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java index 9760b527bc9..eb1f0177e3c 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java @@ -174,4 +174,42 @@ public void variantsCloneTest() { assertEquals("Variant index not set", e.getMessage()); } } + + @Test + public void adderTest() { + Network network = createNetwork(); + Generator g = network.getGenerator("g4"); + Line l = network.getLine("l34"); + var adder = g.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(200.0) + .withEnabled(true); + var e = assertThrows(PowsyblException.class, adder::add); + assertEquals("Regulating terminal must be set", e.getMessage()); + adder = g.newExtension(RemoteReactivePowerControlAdder.class) + .withRegulatingTerminal(l.getTerminal(TwoSides.ONE)) + .withEnabled(true); + e = assertThrows(PowsyblException.class, adder::add); + assertEquals("Reactive power target must be set", e.getMessage()); + var extension = g.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(200.0) + .withRegulatingTerminal(l.getTerminal(TwoSides.ONE)) + .add(); + assertTrue(extension.isEnabled()); + } + + @Test + public void terminalRemoveTest() { + Network network = createNetwork(); + Generator g = network.getGenerator("g4"); + Line l = network.getLine("l34"); + g.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(200.0) + .withRegulatingTerminal(l.getTerminal(TwoSides.ONE)) + .withEnabled(true) + .add(); + assertNotNull(g.getExtension(RemoteReactivePowerControl.class)); + l.remove(); + // extension has been removed because regulating terminal is invalid + assertNull(g.getExtension(RemoteReactivePowerControl.class)); + } } diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java index 8d665ba4e3b..774fd2d71f7 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java @@ -206,26 +206,33 @@ public void variantsResetTest() { SlackTerminal stGen = vlgen.getExtension(SlackTerminal.class); assertNotNull(stGen); final Terminal tGen = stGen.getTerminal(); + assertEquals(1, tGen.getReferrers().size()); // Testing that only current variant was set variantManager.setWorkingVariant(INITIAL_VARIANT_ID); assertNull(stGen.getTerminal()); stGen.setTerminal(tGen); + assertEquals(1, tGen.getReferrers().size()); variantManager.setWorkingVariant(variant1); assertNull(stGen.getTerminal()); stGen.setTerminal(tGen); + assertEquals(1, tGen.getReferrers().size()); // Testing the empty property of the slackTerminal variantManager.setWorkingVariant(INITIAL_VARIANT_ID); assertFalse(stGen.setTerminal(null).isEmpty()); + assertEquals(1, tGen.getReferrers().size()); variantManager.setWorkingVariant(variant2); assertFalse(stGen.setTerminal(null).isEmpty()); + assertEquals(1, tGen.getReferrers().size()); variantManager.setWorkingVariant(variant1); assertTrue(stGen.setTerminal(null).isEmpty()); + assertEquals(0, tGen.getReferrers().size()); assertFalse(stGen.setTerminal(tGen).isEmpty()); + assertEquals(1, tGen.getReferrers().size()); // Testing the cleanIfEmpty boolean stGen.setTerminal(null, false); @@ -284,4 +291,17 @@ public void testWithSubnetwork() { assertNull(merged.getVoltageLevel("VL").getExtension(SlackTerminal.class)); // reset assertNotNull(merged.getVoltageLevel("VLHV1").getExtension(SlackTerminal.class)); // untouched } + + @Test + public void removeTerminalConnectableTest() { + Network network = EurostagTutorialExample1Factory.createWithMoreGenerators(); + var vlgen = network.getVoltageLevel("VLGEN"); + var gen2 = network.getGenerator("GEN2"); + var slackTerminal = vlgen.newExtension(SlackTerminalAdder.class) + .withTerminal(gen2.getTerminal()) + .add(); + assertNotNull(slackTerminal.getTerminal()); + gen2.remove(); + assertNull(slackTerminal.getTerminal()); + } } diff --git a/iidm/iidm-test/src/main/java/com/powsybl/iidm/network/test/EurostagTutorialExample1Factory.java b/iidm/iidm-test/src/main/java/com/powsybl/iidm/network/test/EurostagTutorialExample1Factory.java index af9bd6307c0..39b32da1afe 100644 --- a/iidm/iidm-test/src/main/java/com/powsybl/iidm/network/test/EurostagTutorialExample1Factory.java +++ b/iidm/iidm-test/src/main/java/com/powsybl/iidm/network/test/EurostagTutorialExample1Factory.java @@ -1174,7 +1174,8 @@ private static Network addReactiveGenerator(Network network, Terminal terminal) private static Network addRemoteVoltageGenerator(Network network) { network.getGenerator("GEN") - .setRegulatingTerminal(network.getTwoWindingsTransformer(NHV2_NLOAD).getTerminal1()); + .setRegulatingTerminal(network.getTwoWindingsTransformer(NHV2_NLOAD).getTerminal1()) + .setTargetV(399); return network; } diff --git a/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java b/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java index 077d6cf3f1d..bea1cad423f 100644 --- a/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java +++ b/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java @@ -315,17 +315,17 @@ public interface UndirectedGraph { /** * Traverse the entire graph, starting at the specified vertex v. * This method relies on a {@link Traverser} instance to know if the traverse of the graph should continue or stop. - * This method throws a {@link com.powsybl.commons.PowsyblException} if the encountered table size is less than the maximum vertex index. - * At the end of the method, the encountered array contains {@literal true} for all the traversed vertices, {@literal false} otherwise. + * This method throws a {@link com.powsybl.commons.PowsyblException} if the verticesEncountered table size is less than the maximum vertex index. + * At the end of the method, the verticesEncountered array contains {@literal true} for all the traversed vertices, {@literal false} otherwise. * * @param v the vertex index where the traverse has to start. * @param traversalType the type of traversal (breadth-first or depth-first) * @param traverser the {@link Traverser} instance to use to know if the traverse should continue or stop. - * @param encountered the list of traversed vertices. + * @param verticesEncountered the list of traversed vertices. * @return false if the whole traversing has to stop, meaning that a {@link TraverseResult#TERMINATE_TRAVERSER} * has been returned from the traverser, true otherwise */ - boolean traverse(int v, TraversalType traversalType, Traverser traverser, boolean[] encountered); + boolean traverse(int v, TraversalType traversalType, Traverser traverser, boolean[] verticesEncountered); /** * Traverse the entire graph, starting at the specified vertex v. diff --git a/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java b/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java index 9d504362ff5..7fd5d91f17e 100644 --- a/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java +++ b/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java @@ -507,9 +507,13 @@ private void invalidateAdjacencyList() { adjacencyListCache = null; } - private static void traverseVertex(int v, boolean[] encountered, Deque edgesToTraverse, TIntArrayList[] adjacencyList, TraversalType traversalType) { - encountered[v] = true; - TIntArrayList adjacentEdges = adjacencyList[v]; + private void traverseVertex(int vToTraverse, boolean[] vEncountered, Deque edgesToTraverse, + TIntArrayList[] adjacencyList, TraversalType traversalType) { + if (vEncountered[vToTraverse]) { + return; + } + vEncountered[vToTraverse] = true; + TIntArrayList adjacentEdges = adjacencyList[vToTraverse]; for (int i = 0; i < adjacentEdges.size(); i++) { // For depth-first traversal, we're going to poll the last element added in the deque. Hence, edges have to // be added in reverse order, otherwise the depth-first traversal will be "on the right side" instead of @@ -518,41 +522,53 @@ private static void traverseVertex(int v, boolean[] encountered, Deque case DEPTH_FIRST -> adjacentEdges.size() - i - 1; case BREADTH_FIRST -> i; }; - edgesToTraverse.add(adjacentEdges.getQuick(iEdge)); + + int adjacentEdgeIndex = adjacentEdges.getQuick(iEdge); + boolean flippedEdge = edges.get(adjacentEdgeIndex).v1 != vToTraverse; + edgesToTraverse.add(new DirectedEdge(adjacentEdgeIndex, flippedEdge)); } } + /** + * Record to store which edge has to be traversed and in which direction + * @param index index of the edge within the edges list + * @param flippedDirection if true, edge.getNode2() has already been visited, otherwise it's edge.getNode1() + */ + private record DirectedEdge(int index, boolean flippedDirection) { + } + @Override - public boolean traverse(int v, TraversalType traversalType, Traverser traverser, boolean[] encountered) { + public boolean traverse(int v, TraversalType traversalType, Traverser traverser, boolean[] encounteredVertices) { checkVertex(v); Objects.requireNonNull(traverser); - Objects.requireNonNull(encountered); + Objects.requireNonNull(encounteredVertices); - if (encountered.length < vertices.size()) { + if (encounteredVertices.length < vertices.size()) { throw new PowsyblException("Encountered array is too small"); } + boolean[] encounteredEdges = new boolean[edges.size()]; TIntArrayList[] adjacencyList = getAdjacencyList(); boolean keepGoing = true; - Deque edgesToTraverse = new ArrayDeque<>(); - traverseVertex(v, encountered, edgesToTraverse, adjacencyList, traversalType); + Deque edgesToTraverse = new ArrayDeque<>(); + traverseVertex(v, encounteredVertices, edgesToTraverse, adjacencyList, traversalType); while (!edgesToTraverse.isEmpty() && keepGoing) { - int e = switch (traversalType) { + DirectedEdge directedEdge = switch (traversalType) { case DEPTH_FIRST -> edgesToTraverse.pollLast(); case BREADTH_FIRST -> edgesToTraverse.pollFirst(); }; - Edge edge = edges.get(e); - if (!encountered[edge.getV1()] || !encountered[edge.getV2()]) { - // This means the edge hasn't been traversed yet. - // Nonetheless, by doing so we're missing the edges parallel to an edge already traversed. - boolean flipEdge = encountered[edge.getV2()]; - int vOrigin = flipEdge ? edge.getV2() : edge.getV1(); - int vDest = flipEdge ? edge.getV1() : edge.getV2(); - TraverseResult traverserResult = traverser.traverse(vOrigin, e, vDest); + if (!encounteredEdges[directedEdge.index]) { + encounteredEdges[directedEdge.index] = true; + + Edge edge = edges.get(directedEdge.index); + int vOrigin = directedEdge.flippedDirection ? edge.getV2() : edge.getV1(); + int vDest = directedEdge.flippedDirection ? edge.getV1() : edge.getV2(); + + TraverseResult traverserResult = traverser.traverse(vOrigin, directedEdge.index, vDest); switch (traverserResult) { - case CONTINUE -> traverseVertex(vDest, encountered, edgesToTraverse, adjacencyList, traversalType); + case CONTINUE -> traverseVertex(vDest, encounteredVertices, edgesToTraverse, adjacencyList, traversalType); case TERMINATE_TRAVERSER -> keepGoing = false; // the whole traversing needs to stop case TERMINATE_PATH -> { // Path ends on edge e before reaching vDest, continuing with next edge in the deque diff --git a/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java b/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java index 998534feb17..0ee1284f4ad 100644 --- a/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java +++ b/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java @@ -10,14 +10,13 @@ import com.powsybl.commons.PowsyblException; import gnu.trove.list.array.TIntArrayList; import org.junit.jupiter.api.AfterEach; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.PrintStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -281,6 +280,7 @@ void testGetEdgeObjects() { } /** + *
      *           0
      *           |
      *         ---------
@@ -307,7 +307,7 @@ void testGetEdgeObjects() {
      *  all paths (edge numbers) between vertex 0 and 5:
      *  0, 3, 5
      *  1, 4, 5
-     *  2, 6
+     *  2, 6
*/ @Test void testFindAllPaths() { @@ -327,12 +327,13 @@ void testFindAllPaths() { graph.addEdge(3, 5, null); // 6 List paths = graph.findAllPaths(0, vertex -> vertex != null && "end".equals(vertex.name), null); assertEquals(3, paths.size()); - assertArrayEquals(paths.get(0).toArray(), new int[] {2, 6}); - assertArrayEquals(paths.get(1).toArray(), new int[] {0, 3, 5}); - assertArrayEquals(paths.get(2).toArray(), new int[] {1, 4, 5}); + assertArrayEquals(new int[] {2, 6}, paths.get(0).toArray()); + assertArrayEquals(new int[] {0, 3, 5}, paths.get(1).toArray()); + assertArrayEquals(new int[] {1, 4, 5}, paths.get(2).toArray()); } /** + *
      *           0
      *           |
      *         ---------
@@ -359,10 +360,10 @@ void testFindAllPaths() {
      *  all paths (edge numbers) between vertex 0 and 5:
      *  0, 3, 5
      *  1, 4, 5
-     *  2, 6
+     *  2, 6
*/ @Test - void testPrintGraph() throws IOException { + void testPrintGraph() { graph.addVertex(); graph.addVertex(); graph.addVertex(); @@ -458,7 +459,7 @@ void testRemoveListener() { * ------- * | * 5 - * + * * edges: * 0 <-> 1 : 0 * 0 <-> 2 : 1 @@ -471,7 +472,7 @@ void testRemoveListener() { * all paths (edge numbers) between vertex 0 and 5: * 0, 3, 5 * 1, 4, 5 - * 2, 6 + * 2, 6 */ @Test void testTraverse() { @@ -496,30 +497,117 @@ void testTraverse() { Mockito.when(traverser.traverse(4, 3, 1)).thenReturn(TraverseResult.TERMINATE_PATH); Mockito.when(traverser.traverse(4, 4, 2)).thenReturn(TraverseResult.TERMINATE_PATH); Mockito.when(traverser.traverse(5, 6, 3)).thenReturn(TraverseResult.TERMINATE_PATH); - boolean[] encountered = new boolean[graph.getVertexCount()]; - Arrays.fill(encountered, false); - graph.traverse(5, TraversalType.DEPTH_FIRST, traverser, encountered); + boolean[] vEncountered = new boolean[graph.getVertexCount()]; + graph.traverse(5, TraversalType.DEPTH_FIRST, traverser, vEncountered); // Only vertex 4 and 5 encountered - assertArrayEquals(new boolean[] {false, false, false, false, true, true}, encountered); + assertArrayEquals(new boolean[] {false, false, false, false, true, true}, vEncountered); - Arrays.fill(encountered, false); + Arrays.fill(vEncountered, false); Traverser traverser2 = (v1, e, v2) -> { - encountered[v1] = true; + vEncountered[v1] = true; return v2 == 1 || v2 == 2 || v2 == 3 ? TraverseResult.TERMINATE_PATH : TraverseResult.CONTINUE; }; graph.traverse(4, TraversalType.DEPTH_FIRST, traverser2); // Only vertex 4 and 5 encountered - assertArrayEquals(new boolean[] {false, false, false, false, true, true}, encountered); + assertArrayEquals(new boolean[] {false, false, false, false, true, true}, vEncountered); - Arrays.fill(encountered, false); - Traverser traverser3 = (v1, e, v2) -> { - return v2 == 0 ? TraverseResult.TERMINATE_TRAVERSER : TraverseResult.CONTINUE; - }; + Arrays.fill(vEncountered, false); + Traverser traverser3 = (v1, e, v2) -> v2 == 0 ? TraverseResult.TERMINATE_TRAVERSER : TraverseResult.CONTINUE; - graph.traverse(5, TraversalType.DEPTH_FIRST, traverser3, encountered); + graph.traverse(5, TraversalType.DEPTH_FIRST, traverser3, vEncountered); // Only vertices on first path encountering 0 are encountered - assertArrayEquals(new boolean[] {false, true, false, false, true, true}, encountered); + assertArrayEquals(new boolean[] {false, true, false, false, true, true}, vEncountered); + + Arrays.fill(vEncountered, false); + boolean[] eEncountered = new boolean[graph.getEdgeCount()]; + Traverser traverser4 = (v1, e, v2) -> { + eEncountered[e] = true; + return TraverseResult.CONTINUE; + }; + graph.traverse(5, TraversalType.DEPTH_FIRST, traverser4, vEncountered); + // All vertices and edges are encountered + assertArrayEquals(new boolean[] {true, true, true, true, true, true}, vEncountered); + assertArrayEquals(new boolean[] {true, true, true, true, true, true, true}, eEncountered); + + List breadthFirstexpected = List.of( + new GraphPath(5, 5, 4), + new GraphPath(5, 6, 3), + new GraphPath(4, 3, 1), + new GraphPath(4, 4, 2), + new GraphPath(3, 2, 0), + new GraphPath(1, 0, 0), + new GraphPath(2, 1, 0)); + List depthFirstExpected = List.of( + new GraphPath(5, 5, 4), + new GraphPath(4, 3, 1), + new GraphPath(1, 0, 0), + new GraphPath(0, 1, 2), + new GraphPath(2, 4, 4), + new GraphPath(0, 2, 3), + new GraphPath(3, 6, 5)); + + // Check that all edges and vertices are traversed in the right order when traversing the graph with no stopping point + List pathsBf = new ArrayList<>(); + List pathsDf = new ArrayList<>(); + graph.traverse(5, TraversalType.BREADTH_FIRST, (v1, e, v2) -> { + pathsBf.add(new GraphPath(v1, e, v2)); + return TraverseResult.CONTINUE; + }); + graph.traverse(5, TraversalType.DEPTH_FIRST, (v1, e, v2) -> { + pathsDf.add(new GraphPath(v1, e, v2)); + return TraverseResult.CONTINUE; + }); + assertEquals(breadthFirstexpected, pathsBf); + assertEquals(depthFirstExpected, pathsDf); + + // Check all calls done when traversing the graph with one stopping point at vertex 0 when arriving from 3 + // to ensure the edge 0 and 1 still get traversed even if the destination vertex 0 is already encountered + List pathsWithStoppingPoint = new ArrayList<>(); + graph.traverse(5, TraversalType.BREADTH_FIRST, (v1, e, v2) -> { + pathsWithStoppingPoint.add(new GraphPath(v1, e, v2)); + return v1 == 3 && v2 == 0 ? TraverseResult.TERMINATE_PATH : TraverseResult.CONTINUE; + }); + assertEquals(breadthFirstexpected, pathsWithStoppingPoint); + } + + /** + *
+     *           0
+     *           |
+     *         -----
+     *         |   |
+     *         -----
+     *           |
+     *           1
+     *           |
+     *           2
+     *
+     *  edges:
+     *  0 <-> 1 : 0
+     *  0 <-> 1 : 1
+     *  1 <-> 2 : 2
+ */ + @Test + void testTraverseParallelEdges() { + graph.addVertex(); + graph.addVertex(); + graph.addVertex(); + graph.addEdge(0, 1, null); // 0 + graph.addEdge(1, 0, null); // 1 + graph.addEdge(1, 2, null); // 2 + + List paths = new ArrayList<>(); + graph.traverse(0, TraversalType.BREADTH_FIRST, (v1, e, v2) -> { + paths.add(new GraphPath(v1, e, v2)); + return TraverseResult.CONTINUE; + }); + + List expected = List.of( + new GraphPath(0, 0, 1), + new GraphPath(0, 1, 1), + new GraphPath(1, 2, 2)); + assertEquals(expected, paths); } @Test @@ -574,4 +662,7 @@ void removeIsolatedVertices() { graph.removeIsolatedVertices(); assertEquals(2, graph.getVertexCount()); } + + private record GraphPath(int v1, int e, int v2) { + } } diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java index 63189bc0eff..9375c5af722 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java @@ -7,13 +7,13 @@ */ package com.powsybl.security.extensions; -import com.powsybl.commons.extensions.Extension; +import com.powsybl.commons.extensions.AbstractExtension; import com.powsybl.security.LimitViolation; /** * @author Mathieu Bague {@literal } */ -public class ActivePowerExtension implements Extension { +public class ActivePowerExtension extends AbstractExtension { private LimitViolation limitViolation;