diff --git a/dynawaltz-dsl/src/main/groovy/com/powsybl/dynawaltz/dsl/events/EventDisconnectionGroovyExtension.groovy b/dynawaltz-dsl/src/main/groovy/com/powsybl/dynawaltz/dsl/events/EventDisconnectionGroovyExtension.groovy index 4fbab033e..20dbe9e8c 100644 --- a/dynawaltz-dsl/src/main/groovy/com/powsybl/dynawaltz/dsl/events/EventDisconnectionGroovyExtension.groovy +++ b/dynawaltz-dsl/src/main/groovy/com/powsybl/dynawaltz/dsl/events/EventDisconnectionGroovyExtension.groovy @@ -12,7 +12,6 @@ import com.powsybl.dynamicsimulation.EventModel import com.powsybl.dynamicsimulation.groovy.EventModelGroovyExtension import com.powsybl.dynawaltz.dsl.AbstractPureDynamicGroovyExtension import com.powsybl.dynawaltz.dsl.DslEquipment -import com.powsybl.dynawaltz.dsl.DslVariousEquipment import com.powsybl.dynawaltz.dsl.builders.AbstractEventModelBuilder import com.powsybl.dynawaltz.models.events.AbstractEventModel import com.powsybl.dynawaltz.models.events.EventHvdcDisconnection diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzContext.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzContext.java index 84fb404b9..f715a1e12 100644 --- a/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzContext.java +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/DynaWaltzContext.java @@ -19,6 +19,8 @@ import com.powsybl.dynawaltz.xml.MacroStaticReference; import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import java.util.function.Function; @@ -31,7 +33,9 @@ */ public class DynaWaltzContext { + protected static final Logger LOGGER = LoggerFactory.getLogger(DynaWaltzContext.class); private static final String MODEL_ID_EXCEPTION = "The model identified by the static id %s does not match the expected model (%s)"; + private static final String MODEL_ID_LOG = "The model identified by the static id {} does not match the expected model ({})"; private final Network network; private final String workingVariantId; @@ -111,27 +115,46 @@ public T getDynamicModel(String staticId, Class clazz) { } public T getDynamicModel(Identifiable equipment, Class connectableClass) { + return getDynamicModel(equipment, connectableClass, true); + } + + public T getDynamicModel(Identifiable equipment, Class connectableClass, boolean throwException) { BlackBoxModel bbm = staticIdBlackBoxModelMap.get(equipment.getId()); if (bbm == null) { - return defaultModelsHandler.getDefaultModel(equipment, connectableClass); + return defaultModelsHandler.getDefaultModel(equipment, connectableClass, throwException); } if (connectableClass.isInstance(bbm)) { return connectableClass.cast(bbm); } - throw new PowsyblException(String.format(MODEL_ID_EXCEPTION, equipment.getId(), connectableClass.getSimpleName())); + if (throwException) { + throw new PowsyblException(String.format(MODEL_ID_EXCEPTION, equipment.getId(), connectableClass.getSimpleName())); + } else { + LOGGER.warn(MODEL_ID_LOG, equipment.getId(), connectableClass.getSimpleName()); + return null; + } } - public T getPureDynamicModel(String dynamicId, Class connectableClass) { + public T getPureDynamicModel(String dynamicId, Class connectableClass, boolean throwException) { BlackBoxModel bbm = dynamicModels.stream() .filter(dm -> dynamicId.equals(dm.getDynamicModelId())) - .findFirst() - .orElseThrow(() -> { - throw new PowsyblException("Pure dynamic model " + dynamicId + " not found"); - }); + .findFirst().orElse(null); + if (bbm == null) { + if (throwException) { + throw new PowsyblException("Pure dynamic model " + dynamicId + " not found"); + } else { + LOGGER.warn("Pure dynamic model {} not found", dynamicId); + return null; + } + } if (connectableClass.isInstance(bbm)) { return connectableClass.cast(bbm); } - throw new PowsyblException(String.format(MODEL_ID_EXCEPTION, dynamicId, connectableClass.getSimpleName())); + if (throwException) { + throw new PowsyblException(String.format(MODEL_ID_EXCEPTION, dynamicId, connectableClass.getSimpleName())); + } else { + LOGGER.warn(MODEL_ID_LOG, dynamicId, connectableClass.getSimpleName()); + return null; + } } private EquipmentBlackBoxModel mergeDuplicateStaticId(EquipmentBlackBoxModel bbm1, EquipmentBlackBoxModel bbm2) { diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/AbstractBlackBoxModel.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/AbstractBlackBoxModel.java index 23ce3aad0..f80a3e15a 100644 --- a/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/AbstractBlackBoxModel.java +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/AbstractBlackBoxModel.java @@ -101,12 +101,31 @@ protected final void createMacroConnections(Identifiable eq createMacroConnections(connectedModel, varConnectionsSupplier.apply(connectedModel), context, connectFromAttributes); } + protected final boolean createMacroConnectionsOrSkip(Identifiable equipment, Class modelClass, Function> varConnectionsSupplier, DynaWaltzContext context, MacroConnectAttribute... connectFromAttributes) { + T connectedModel = context.getDynamicModel(equipment, modelClass, false); + if (connectedModel != null) { + createMacroConnections(connectedModel, varConnectionsSupplier.apply(connectedModel), context, connectFromAttributes); + return false; + } + return true; + } + protected void createMacroConnections(Identifiable equipment, Class modelClass, Function> varConnectionsSupplier, DynaWaltzContext context) { T connectedModel = context.getDynamicModel(equipment, modelClass); String macroConnectorId = context.addMacroConnector(getName(), connectedModel.getName(), varConnectionsSupplier.apply(connectedModel)); context.addMacroConnect(macroConnectorId, getMacroConnectFromAttributes(), connectedModel.getMacroConnectToAttributes()); } + protected boolean createMacroConnectionsOrSkip(Identifiable equipment, Class modelClass, Function> varConnectionsSupplier, DynaWaltzContext context) { + T connectedModel = context.getDynamicModel(equipment, modelClass, false); + if (connectedModel != null) { + String macroConnectorId = context.addMacroConnector(getName(), connectedModel.getName(), varConnectionsSupplier.apply(connectedModel)); + context.addMacroConnect(macroConnectorId, getMacroConnectFromAttributes(), connectedModel.getMacroConnectToAttributes()); + return false; + } + return true; + } + /** * Suffixes MacroConnector id with side name */ @@ -137,12 +156,6 @@ protected void createMacroConnections(Identifiable equipmen context.addMacroConnect(macroConnectorId, getMacroConnectFromAttributes(), connectedModel.getMacroConnectToAttributes()); } - protected void createPureDynamicMacroConnections(String dynamicId, Class modelClass, Function> varConnectionsSupplier, DynaWaltzContext context) { - T connectedModel = context.getPureDynamicModel(dynamicId, modelClass); - String macroConnectorId = context.addMacroConnector(getName(), connectedModel.getName(), varConnectionsSupplier.apply(connectedModel)); - context.addMacroConnect(macroConnectorId, getMacroConnectFromAttributes(), connectedModel.getMacroConnectToAttributes()); - } - protected final void createMacroConnections(Identifiable equipment, Class modelClass, BiFunction> varConnectionsSupplier, DynaWaltzContext context, String parametrizedName, MacroConnectAttribute... connectFromAttributes) { T connectedModel = context.getDynamicModel(equipment, modelClass); String macroConnectorId = context.addMacroConnector(getName(), connectedModel.getName(), parametrizedName, varConnectionsSupplier.apply(connectedModel, parametrizedName)); diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/automatons/TapChangerAutomaton.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/automatons/TapChangerAutomaton.java index e7de3bcab..ee986c2bc 100644 --- a/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/automatons/TapChangerAutomaton.java +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/automatons/TapChangerAutomaton.java @@ -14,7 +14,11 @@ import com.powsybl.dynawaltz.models.loads.LoadWithTransformers; import com.powsybl.dynawaltz.models.transformers.TapChangerModel; import com.powsybl.iidm.network.Load; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import java.util.List; import java.util.Objects; @@ -23,9 +27,19 @@ */ public class TapChangerAutomaton extends AbstractPureDynamicBlackBoxModel implements TapChangerModel { + private static final Logger LOGGER = LoggerFactory.getLogger(TapChangerAutomaton.class); + private final Load load; private final TransformerSide side; + private ConnectionState connection = ConnectionState.NOT_SET; + + private enum ConnectionState { + CONNECTED, + NOT_CONNECTED, + NOT_SET + } + public TapChangerAutomaton(String dynamicModelId, String parameterSetId, Load load, TransformerSide side) { super(dynamicModelId, parameterSetId); this.load = Objects.requireNonNull(load); @@ -48,7 +62,15 @@ public String getLib() { @Override public void createMacroConnections(DynaWaltzContext context) { - createMacroConnections(load, LoadWithTransformers.class, this::getVarConnectionsWith, context); + if (ConnectionState.NOT_SET == connection) { + boolean isSkipped = createMacroConnectionsOrSkip(load, LoadWithTransformers.class, this::getVarConnectionsWith, context); + if (isSkipped) { + connection = ConnectionState.NOT_CONNECTED; + LOGGER.warn("TapChangerAutomaton {} load does not possess a transformer, the automaton will be skipped", getDynamicModelId()); + } else { + connection = ConnectionState.CONNECTED; + } + } } private List getVarConnectionsWith(LoadWithTransformers connected) { @@ -59,4 +81,18 @@ private List getVarConnectionsWith(LoadWithTransformers connected public List getTapChangerBlockerVarConnections() { return List.of(new VarConnection(getTapChangerBlockingVarName(side), "tapChanger_locked")); } + + @Override + public void write(XMLStreamWriter writer, DynaWaltzContext context) throws XMLStreamException { + if (ConnectionState.CONNECTED == connection) { + super.write(writer, context); + } + } + + public boolean isConnected(DynaWaltzContext context) { + if (ConnectionState.NOT_SET == connection) { + createMacroConnections(context); + } + return ConnectionState.CONNECTED == connection; + } } diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/automatons/TapChangerBlockingAutomaton.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/automatons/TapChangerBlockingAutomaton.java index bd461d551..fe571f859 100644 --- a/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/automatons/TapChangerBlockingAutomaton.java +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/automatons/TapChangerBlockingAutomaton.java @@ -18,7 +18,11 @@ import com.powsybl.iidm.network.IdentifiableType; import com.powsybl.iidm.network.Load; import com.powsybl.iidm.network.TwoWindingsTransformer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import java.util.*; /** @@ -26,6 +30,7 @@ */ public class TapChangerBlockingAutomaton extends AbstractPureDynamicBlackBoxModel { + private static final Logger LOGGER = LoggerFactory.getLogger(TapChangerBlockingAutomaton.class); private static final Set COMPATIBLE_EQUIPMENTS = EnumSet.of(IdentifiableType.LOAD, IdentifiableType.TWO_WINDINGS_TRANSFORMER); private static final int MAX_MEASUREMENTS = 5; @@ -33,6 +38,7 @@ public class TapChangerBlockingAutomaton extends AbstractPureDynamicBlackBoxMode private final List loadsWithTransformer; private final List tapChangerAutomatonIds; private final List uMeasurements; + private boolean isConnected = true; public TapChangerBlockingAutomaton(String dynamicModelId, String parameterSetId, List transformers, List loadsWithTransformer, List tapChangerAutomatonIds, List uMeasurements) { super(dynamicModelId, parameterSetId); @@ -78,16 +84,27 @@ public void createMacroConnections(DynaWaltzContext context) { for (TwoWindingsTransformer transformer : transformers) { createMacroConnections(transformer, TapChangerModel.class, this::getVarConnectionsWith, context); } + int skippedTapChangers = 0; for (Load load : loadsWithTransformer) { - createMacroConnections(load, TapChangerModel.class, this::getVarConnectionsWith, context); + boolean isSkipped = createMacroConnectionsOrSkip(load, TapChangerModel.class, this::getVarConnectionsWith, context); + if (isSkipped) { + skippedTapChangers++; + } } for (String id : tapChangerAutomatonIds) { - createPureDynamicMacroConnections(id, TapChangerModel.class, this::getVarConnectionsWith, context); + if (createTcaMacroConnectionsOrSkip(id, context)) { + skippedTapChangers++; + } } - int i = 1; - for (Bus bus : uMeasurements) { - createMacroConnections(bus, BusModel.class, this::getVarConnectionsWith, context, MeasurementPoint.of(i)); - i++; + if (!transformers.isEmpty() || skippedTapChangers < (loadsWithTransformer.size() + tapChangerAutomatonIds.size())) { + int i = 1; + for (Bus bus : uMeasurements) { + createMacroConnections(bus, BusModel.class, this::getVarConnectionsWith, context, MeasurementPoint.of(i)); + i++; + } + } else { + isConnected = false; + LOGGER.warn("None of TapChangerBlockingAutomaton {} equipments are TapChangerModel, the automaton will be skipped", getDynamicModelId()); } } @@ -100,4 +117,21 @@ private List getVarConnectionsWith(BusModel connected, String suf .map(uImpinVarName -> List.of(new VarConnection("tapChangerBlocking_UMonitored" + suffix, uImpinVarName))) .orElse(Collections.emptyList()); } + + private boolean createTcaMacroConnectionsOrSkip(String dynamicId, DynaWaltzContext context) { + TapChangerAutomaton connectedModel = context.getPureDynamicModel(dynamicId, TapChangerAutomaton.class, false); + if (connectedModel != null && connectedModel.isConnected(context)) { + String macroConnectorId = context.addMacroConnector(getName(), connectedModel.getName(), getVarConnectionsWith(connectedModel)); + context.addMacroConnect(macroConnectorId, getMacroConnectFromAttributes(), connectedModel.getMacroConnectToAttributes()); + return false; + } + return true; + } + + @Override + public void write(XMLStreamWriter writer, DynaWaltzContext context) throws XMLStreamException { + if (isConnected) { + super.write(writer, context); + } + } } diff --git a/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/defaultmodels/DefaultModelsHandler.java b/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/defaultmodels/DefaultModelsHandler.java index 51fd6668f..77df92ffc 100644 --- a/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/defaultmodels/DefaultModelsHandler.java +++ b/dynawaltz/src/main/java/com/powsybl/dynawaltz/models/defaultmodels/DefaultModelsHandler.java @@ -26,6 +26,8 @@ import com.powsybl.dynawaltz.models.transformers.TransformerModel; import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.IdentifiableType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.EnumMap; import java.util.Map; @@ -36,6 +38,8 @@ */ public class DefaultModelsHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultModelsHandler.class); + private final Map> powSyBlTypeToModel = new EnumMap<>(IdentifiableType.class); private final Map, DefaultModelFactory> factoryMap; @@ -68,6 +72,10 @@ public T getDefaultModel(String staticId, Class clazz) { } public T getDefaultModel(Identifiable equipment, Class connectableClass) { + return getDefaultModel(equipment, connectableClass, true); + } + + public T getDefaultModel(Identifiable equipment, Class connectableClass, boolean throwException) { Class equipmentClass = powSyBlTypeToModel.get(equipment.getType()); if (equipmentClass == null) { @@ -79,8 +87,18 @@ public T getDefaultModel(Identifiable equipment, Class c if (connectableClass.isInstance(defaultModel)) { return connectableClass.cast(defaultModel); } - throw new PowsyblException("Default model " + defaultModel.getClass().getSimpleName() + " does not implement " + connectableClass.getSimpleName() + " interface"); + if (throwException) { + throw new PowsyblException("Default model " + defaultModel.getClass().getSimpleName() + " does not implement " + connectableClass.getSimpleName() + " interface"); + } else { + LOGGER.warn("Default model {} does not implement {} interface", defaultModel.getClass().getSimpleName(), connectableClass.getSimpleName()); + return null; + } + } + if (throwException) { + throw new PowsyblException("Default model not implemented for " + equipmentClass.getSimpleName()); + } else { + LOGGER.warn("Default model not implemented for {}", equipmentClass.getSimpleName()); + return null; } - throw new PowsyblException("Default model not implemented for " + equipmentClass.getSimpleName()); } } diff --git a/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/EmptyTapChangerAutomatonXmlTest.java b/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/EmptyTapChangerAutomatonXmlTest.java new file mode 100644 index 000000000..0d44b493a --- /dev/null +++ b/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/EmptyTapChangerAutomatonXmlTest.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2023, 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.dynawaltz.xml; + +import com.powsybl.dynawaltz.models.automatons.TapChangerAutomaton; +import com.powsybl.dynawaltz.models.loads.LoadAlphaBeta; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +import javax.xml.stream.XMLStreamException; +import java.io.IOException; + +/** + * @author Laurent Issertial + */ +class EmptyTapChangerAutomatonXmlTest extends AbstractDynamicModelXmlTest { + + @Override + protected void setupNetwork() { + network = EurostagTutorialExample1Factory.create(); + } + + @Override + protected void addDynamicModels() { + dynamicModels.add(new LoadAlphaBeta("BBM_LOAD", network.getLoad("LOAD"), "LAB", "LoadAlphaBeta")); + dynamicModels.add(new TapChangerAutomaton("BBM_TC", "tc", network.getLoad("LOAD"))); + } + + @Test + void writeModel() throws SAXException, IOException, XMLStreamException { + DydXml.write(tmpDir, context); + ParametersXml.write(tmpDir, context); + validate("dyd.xsd", "tap_changer_empty_dyd.xml", tmpDir.resolve(DynaWaltzConstants.DYD_FILENAME)); + } +} diff --git a/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/EmptyTapChangerBlockingAutomatonXmlTest.java b/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/EmptyTapChangerBlockingAutomatonXmlTest.java new file mode 100644 index 000000000..31bd1c129 --- /dev/null +++ b/dynawaltz/src/test/java/com/powsybl/dynawaltz/xml/EmptyTapChangerBlockingAutomatonXmlTest.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2023, 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.dynawaltz.xml; + +import com.powsybl.dynawaltz.models.automatons.TapChangerAutomaton; +import com.powsybl.dynawaltz.models.automatons.TapChangerBlockingAutomaton; +import com.powsybl.dynawaltz.models.loads.LoadOneTransformer; +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +/** + * @author Laurent Issertial + */ +class EmptyTapChangerBlockingAutomatonXmlTest extends AbstractDynamicModelXmlTest { + + @Override + protected void setupNetwork() { + network = EurostagTutorialExample1Factory.create(); + VoltageLevel vlload = network.getVoltageLevel("VLLOAD"); + Bus nload = network.getBusBreakerView().getBus("NLOAD"); + vlload.newLoad().setId("LOAD2").setBus(nload.getId()).setConnectableBus(nload.getId()).setP0(600.0).setQ0(200.0).add(); + } + + @Override + protected void addDynamicModels() { + dynamicModels.add(new LoadOneTransformer("BBM_LOAD", network.getLoad("LOAD"), "lot")); + dynamicModels.add(new TapChangerBlockingAutomaton("BBM_TapChangerBlocking", "TapChangerPar", + Collections.emptyList(), + List.of(network.getLoad("LOAD")), + List.of("GEN", "LOAD", "BBM_TC"), + List.of(network.getBusBreakerView().getBus("NHV1")))); + dynamicModels.add(new TapChangerAutomaton("BBM_TC", "tc", network.getLoad("LOAD2"))); + } + + @Test + void writeModel() throws SAXException, IOException, XMLStreamException { + DydXml.write(tmpDir, context); + ParametersXml.write(tmpDir, context); + validate("dyd.xsd", "tap_changer_blocking_empty_dyd.xml", tmpDir.resolve(DynaWaltzConstants.DYD_FILENAME)); + } +} diff --git a/dynawaltz/src/test/resources/tap_changer_blocking_empty_dyd.xml b/dynawaltz/src/test/resources/tap_changer_blocking_empty_dyd.xml new file mode 100644 index 000000000..d7c86adfc --- /dev/null +++ b/dynawaltz/src/test/resources/tap_changer_blocking_empty_dyd.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/dynawaltz/src/test/resources/tap_changer_empty_dyd.xml b/dynawaltz/src/test/resources/tap_changer_empty_dyd.xml new file mode 100644 index 000000000..f8a5c52df --- /dev/null +++ b/dynawaltz/src/test/resources/tap_changer_empty_dyd.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + +