diff --git a/action-ial/action-ial-dsl/src/main/java/com/powsybl/action/ial/dsl/modification/ScriptNetworkModification.java b/action-ial/action-ial-dsl/src/main/java/com/powsybl/action/ial/dsl/modification/ScriptNetworkModification.java index c03eddfeb94..7a31b08b54e 100644 --- a/action-ial/action-ial-dsl/src/main/java/com/powsybl/action/ial/dsl/modification/ScriptNetworkModification.java +++ b/action-ial/action-ial-dsl/src/main/java/com/powsybl/action/ial/dsl/modification/ScriptNetworkModification.java @@ -36,4 +36,10 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE ComputationManager computationManager, ReportNode reportNode) { script.call(network, computationManager); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + // TODO: what do we do here? + return true; + } } diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Connectable.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Connectable.java index 7650d123b21..a0c24fd4052 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Connectable.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Connectable.java @@ -30,9 +30,13 @@ public interface Connectable> extends Identifiable { boolean connect(Predicate isTypeSwitchToOperate, ThreeSides side); + boolean connect(Predicate isTypeSwitchToOperate, ThreeSides side, boolean dryRun); + boolean disconnect(); boolean disconnect(Predicate isSwitchOpenable); boolean disconnect(Predicate isSwitchOpenable, ThreeSides side); + + boolean disconnect(Predicate isSwitchOpenable, ThreeSides side, boolean dryRun); } 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 7025a5084bf..a2f1593cb19 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 @@ -205,11 +205,16 @@ public boolean connect(Predicate isTypeSwitchToOperate) { @Override public boolean connect(Predicate isTypeSwitchToOperate, ThreeSides side) { + return connect(isTypeSwitchToOperate, side, false); + } + @Override + public boolean connect(Predicate isTypeSwitchToOperate, ThreeSides side, boolean dryRun) { return ConnectDisconnectUtil.connectAllTerminals( this, getTerminals(side), isTypeSwitchToOperate, + dryRun, getNetwork().getReportNodeContext().getReportNode()); } @@ -225,10 +230,16 @@ public boolean disconnect(Predicate isSwitchOpenable) { @Override public boolean disconnect(Predicate isSwitchOpenable, ThreeSides side) { + return disconnect(isSwitchOpenable, side, false); + } + + @Override + public boolean disconnect(Predicate isSwitchOpenable, ThreeSides side, boolean dryRun) { return ConnectDisconnectUtil.disconnectAllTerminals( this, getTerminals(side), isSwitchOpenable, + dryRun, getNetwork().getReportNodeContext().getReportNode()); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ConnectDisconnectUtil.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ConnectDisconnectUtil.java index e178a176e2e..455f41fd280 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ConnectDisconnectUtil.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ConnectDisconnectUtil.java @@ -39,8 +39,26 @@ private ConnectDisconnectUtil() { * @param reportNode report node * @return {@code true} if all the specified terminals have been connected, else {@code false}. */ - static boolean connectAllTerminals(Identifiable identifiable, List terminals, Predicate isTypeSwitchToOperate, ReportNode reportNode) { + static boolean connectAllTerminals(Identifiable identifiable, List terminals, + Predicate isTypeSwitchToOperate, ReportNode reportNode) { + return connectAllTerminals(identifiable, terminals, isTypeSwitchToOperate, false, reportNode); + } + /** + * Connect the specified terminals. It will connect all the specified terminals or none if at least one cannot be + * connected. + * @param identifiable network element to connect. It can be a connectable, a tie line or an HVDC line + * @param terminals list of the terminals that should be connected. For a connectable, it should be its own + * terminals, while for a tie line (respectively an HVDC line) it should be the terminals of the + * underlying dangling lines (respectively converter stations) + * @param isTypeSwitchToOperate type of switches that can be operated + * @param dryRun is it a dry run? If {@code true}, no terminal will be effectively connected. + * @param reportNode report node + * @return {@code true} if all the specified terminals have been connected (or can be connected, when + * {@code dryRun} is {@code true}), else {@code false}. + */ + static boolean connectAllTerminals(Identifiable identifiable, List terminals, + Predicate isTypeSwitchToOperate, boolean dryRun, ReportNode reportNode) { // Booleans boolean isAlreadyConnected = true; boolean isNowConnected = true; @@ -79,6 +97,11 @@ static boolean connectAllTerminals(Identifiable identifiable, List identifiable, List identifiable, List terminals, Predicate isSwitchOpenable, ReportNode reportNode) { + static boolean disconnectAllTerminals(Identifiable identifiable, List terminals, + Predicate isSwitchOpenable, ReportNode reportNode) { + return disconnectAllTerminals(identifiable, terminals, isSwitchOpenable, false, reportNode); + } + + /** + * Disconnect the specified terminals. It will disconnect all the specified terminals or none if at least one cannot + * be disconnected. + * @param identifiable network element to disconnect. It can be a connectable, a tie line or an HVDC line + * @param terminals list of the terminals that should be connected. For a connectable, it should be its own + * terminals, while for a tie line (respectively an HVDC line) it should be the terminals of the + * underlying dangling lines (respectively converter stations) + * @param isSwitchOpenable type of switches that can be operated + * @param dryRun is it a dry run? If {@code true}, no terminal will be effectively disconnected. + * @param reportNode report node + * @return {@code true} if all the specified terminals have been disconnected (or can be disconnected, when + * {@code dryRun} is {@code true}), else {@code false}. + */ + static boolean disconnectAllTerminals(Identifiable identifiable, List terminals, + Predicate isSwitchOpenable, boolean dryRun, ReportNode reportNode) { // Booleans boolean isAlreadyDisconnected = true; boolean isNowDisconnected = true; @@ -140,6 +182,11 @@ static boolean disconnectAllTerminals(Identifiable identifiable, List${project.groupId} powsybl-loadflow-api + + ${project.groupId} + powsybl-iidm-serde + @@ -95,11 +99,6 @@ powsybl-iidm-test test - - ${project.groupId} - powsybl-iidm-serde - test - diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractDisconnection.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractDisconnection.java index cb0531fa984..b8c6d518522 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractDisconnection.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractDisconnection.java @@ -8,6 +8,8 @@ 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.network.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,6 +44,35 @@ public abstract class AbstractDisconnection extends AbstractNetworkModification this.side = side; } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + Connectable connectable = network.getConnectable(identifiableId); + if (connectable == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "AbstractDisconnection", + "Identifiable '" + identifiableId + "' not found"); + } else if (!connectable.disconnect(openableSwitches, side, true)) { + // TODO : differenciate the cases where the identifiable does not exist, where it is already disconnected, where it cannot be disconnected, etc. + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "AbstractDisconnection", + "Disconnection failed"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + // TODO: see TODO in applyDryRun + return true; + } + public void applyModification(Network network, boolean isPlanned, boolean throwException, ReportNode reportNode) { // Add the reportNode to the network reportNode context network.getReportNodeContext().pushReportNode(reportNode); diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractNetworkModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractNetworkModification.java index 92645e58f2d..f6db80182e3 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractNetworkModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractNetworkModification.java @@ -9,11 +9,12 @@ import com.powsybl.commons.PowsyblException; import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.report.TypedValue; import com.powsybl.computation.ComputationManager; import com.powsybl.computation.local.LocalComputationManager; import com.powsybl.iidm.modification.topology.DefaultNamingStrategy; import com.powsybl.iidm.modification.topology.NamingStrategy; -import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +24,7 @@ public abstract class AbstractNetworkModification implements NetworkModification { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractNetworkModification.class); + protected boolean dryRunConclusive = true; @Override public void apply(Network network) { @@ -79,6 +81,83 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE apply(network, namingStrategy, throwException, LocalComputationManager.getDefault(), reportNode); } + @Override + public boolean dryRun(Network network) { + return dryRun(network, new DefaultNamingStrategy(), LocalComputationManager.getDefault(), ReportNode.NO_OP); + } + + @Override + public boolean dryRun(Network network, ReportNode reportNode) { + return dryRun(network, new DefaultNamingStrategy(), LocalComputationManager.getDefault(), reportNode); + } + + @Override + public boolean dryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + String templateKey = "networkModificationDryRun"; + String messageTemplate = "Dry-run: Checking if a network modification can be applied on network '${networkNameOrId}'"; + ReportNode dryRunReportNode = reportNode.newReportNode() + .withMessageTemplate(templateKey, messageTemplate) + .withUntypedValue("networkNameOrId", network.getNameOrId()) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + return applyDryRun(network, namingStrategy, computationManager, dryRunReportNode); + } + + protected abstract boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode); + + @Override + public boolean hasImpactOnNetwork() { + return true; + } + + @Override + public boolean isLocalDryRunPossible() { + return false; + } + + protected static void reportOnInconclusiveDryRun(ReportNode reportNode, String networkModification, String cause) { + reportNode.newReportNode() + .withMessageTemplate("networkModificationsDryRun-failure", + "Dry-run failed for ${networkModification}. The issue is: ${dryRunError}") + .withUntypedValue("dryRunError", cause) + .withUntypedValue("networkModification", networkModification) + .add(); + } + + protected static boolean checkVoltageLevel(Identifiable identifiable, ReportNode reportNode, String networkModification, boolean dryRunConclusive) { + boolean localDryRunConclusive = dryRunConclusive; + VoltageLevel vl = null; + if (identifiable instanceof Bus bus) { + vl = bus.getVoltageLevel(); + } else if (identifiable instanceof BusbarSection bbs) { + vl = bbs.getTerminal().getVoltageLevel(); + } else { + localDryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + networkModification, + "Unexpected type of identifiable " + identifiable.getId() + ": " + identifiable.getType()); + } + if (vl == null) { + localDryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + networkModification, + "Voltage level is null"); + } + return localDryRunConclusive; + } + + protected static boolean checkLine(Network network, String lineId, ReportNode reportNode, String networkModification, boolean dryRunConclusive) { + boolean localDryRunConclusive = dryRunConclusive; + Line line = network.getLine(lineId); + if (line == null) { + localDryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + networkModification, + String.format("Line %s is not found", lineId)); + } + return localDryRunConclusive; + } + /** * Utility during apply functions, logs or throw the message. * diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractPhaseShifterModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractPhaseShifterModification.java new file mode 100644 index 00000000000..85cde837816 --- /dev/null +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractPhaseShifterModification.java @@ -0,0 +1,62 @@ +/* + * 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.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.PhaseTapChanger; +import com.powsybl.iidm.network.TwoWindingsTransformer; + +import java.util.Objects; + +/** + * @author Nicolas Rol {@literal } + */ +public abstract class AbstractPhaseShifterModification extends AbstractNetworkModification { + + protected final String phaseShifterId; + private static final String TRANSFORMER_NOT_FOUND = "Transformer '%s' not found"; + private static final String NOT_A_PHASE_SHIFTER = "Transformer '%s' is not a phase shifter"; + + protected AbstractPhaseShifterModification(String phaseShifterId) { + this.phaseShifterId = Objects.requireNonNull(phaseShifterId); + } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + TwoWindingsTransformer phaseShifter = network.getTwoWindingsTransformer(phaseShifterId); + if (phaseShifter == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "AbstractPhaseShifterModification", + String.format(TRANSFORMER_NOT_FOUND, phaseShifterId)); + } else if (!phaseShifter.hasPhaseTapChanger()) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "AbstractPhaseShifterModification", + String.format(NOT_A_PHASE_SHIFTER, phaseShifterId)); + } + return dryRunConclusive; + } + + protected PhaseTapChanger getPhaseTapChanger(Network network) { + Objects.requireNonNull(network); + TwoWindingsTransformer phaseShifter = network.getTwoWindingsTransformer(phaseShifterId); + if (phaseShifter == null) { + throw new PowsyblException(String.format(TRANSFORMER_NOT_FOUND, phaseShifterId)); + } + PhaseTapChanger phaseTapChanger = phaseShifter.getPhaseTapChanger(); + if (phaseTapChanger == null) { + throw new PowsyblException(String.format(NOT_A_PHASE_SHIFTER, phaseShifterId)); + } + return phaseTapChanger; + } +} diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractSetpointModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractSetpointModification.java index 740982f18fc..08b4cc2c3c2 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractSetpointModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/AbstractSetpointModification.java @@ -58,6 +58,27 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (getNetworkElement(network, elementId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "AbstractSetpointModification", + "Connectable '" + elementId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + public abstract String getElementName(); protected abstract void setVoltageSetpoint(T networkElement, Double voltageSetpoint); diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/BatteryModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/BatteryModification.java index cc44c62673d..5e53ffbfe76 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/BatteryModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/BatteryModification.java @@ -15,6 +15,8 @@ import java.util.Objects; +import static com.powsybl.iidm.modification.util.ModificationReports.notFoundBatteryReport; + /** * Simple {@link NetworkModification} for a battery. * @@ -37,6 +39,7 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE ReportNode reportNode) { Battery battery = network.getBattery(batteryId); if (battery == null) { + notFoundBatteryReport(reportNode, batteryId); logOrThrow(throwException, "Battery '" + batteryId + "' not found"); return; } @@ -48,6 +51,27 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getBattery(batteryId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "BatteryModification", + "Battery '" + batteryId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + public String getBatteryId() { return batteryId; } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/CloseSwitch.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/CloseSwitch.java index 2670a2b7cb3..a63366e647c 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/CloseSwitch.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/CloseSwitch.java @@ -35,4 +35,25 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } sw.setOpen(false); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getSwitch(switchId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "CloseSwitch", + "Switch '" + switchId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ConnectGenerator.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ConnectGenerator.java index a943e2c3562..5dc749b3ace 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ConnectGenerator.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ConnectGenerator.java @@ -41,6 +41,35 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE connect(g); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + Generator g = network.getGenerator(generatorId); + if (g == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "ConnectGenerator", + "Generator '" + generatorId + "' not found"); + } else if (g.getTerminal() == null) { + // TODO: can the generator's terminal be null here? + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "ConnectGenerator", + "Terminal for the generator '" + generatorId + "' is null"); + + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + static void connect(Generator g) { Objects.requireNonNull(g); diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ConnectableConnection.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ConnectableConnection.java index 4a3d56e752b..d3c50bb8ccc 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ConnectableConnection.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ConnectableConnection.java @@ -100,4 +100,33 @@ private void connectIdentifiable(Identifiable identifiable, Network network, } connectableConnectionReport(reportNode, identifiable, hasBeenConnected, side); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + Connectable connectable = network.getConnectable(connectableId); + if (connectable == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "ConnectableConnection", + "Identifiable '" + connectableId + "' not found"); + } else if (!connectable.connect(isTypeSwitchToOperate, side, true)) { + // TODO : differenciate the cases where the connectable does not exist, where it is already connected, where it cannot be connected, etc. + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "ConnectableConnection", + "Connection failed"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + // TODO: see TODO in applyDryRun + return true; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/DanglingLineModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/DanglingLineModification.java index 148e949b9e0..424824e01cc 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/DanglingLineModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/DanglingLineModification.java @@ -54,4 +54,25 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE getP0().ifPresent(value -> danglingLine.setP0((isRelativeValue() ? danglingLine.getP0() : 0) + value)); getQ0().ifPresent(value -> danglingLine.setQ0((isRelativeValue() ? danglingLine.getQ0() : 0) + value)); } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getDanglingLine(getDanglingLineId()) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "DanglingLineModification", + "Dangling line '" + getDanglingLineId() + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/GeneratorModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/GeneratorModification.java index 197d1da467b..b45ee36763e 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/GeneratorModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/GeneratorModification.java @@ -68,6 +68,27 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getGenerator(generatorId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "GeneratorModification", + "Generator '" + generatorId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private void applyTargetP(Generator g, boolean skipOtherConnectionChange) { Double newTargetP; if (modifs.getTargetP() != null) { diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/HvdcLineModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/HvdcLineModification.java index 129861e667a..9c3765a4e9c 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/HvdcLineModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/HvdcLineModification.java @@ -26,6 +26,7 @@ public class HvdcLineModification extends AbstractNetworkModification { private static final Logger LOG = LoggerFactory.getLogger(HvdcLineModification.class); + private static final String NETWORK_MODIFICATION_NAME = "HvdcLineModification"; private final String hvdcId; private final Boolean acEmulationEnabled; private final Double activePowerSetpoint; @@ -49,7 +50,7 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE ReportNode reportNode) { HvdcLine hvdcLine = network.getHvdcLine(hvdcId); if (hvdcLine == null) { - logOrThrow(throwException, "HvdcLine '" + hvdcId + "' not found"); + logOrThrow(throwException, "Hvdc line '" + hvdcId + "' not found"); return; } if (activePowerSetpoint != null) { @@ -69,6 +70,54 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE applyToHvdcAngleDroopActivePowerControlExtension(hvdcLine); } + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + HvdcLine hvdcLine = network.getHvdcLine(hvdcId); + if (hvdcLine == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "Hvdc line '" + hvdcId + "' not found"); + } + if (activePowerSetpoint == null && Boolean.TRUE.equals(relativeValue)) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "Relative value is set to true but it will not be applied since active power setpoint is undefined (null)"); + } + if (hvdcLine != null && hvdcLine.getExtension(HvdcAngleDroopActivePowerControl.class) == null) { + if (acEmulationEnabled != null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("AV emulation enable is defined but it will not be applied since the hvdc line %s does not have a HvdcAngleDroopActivePowerControl extension", hvdcId)); + } + if (p0 != null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("P0 is defined but it will not be applied since the hvdc line %s does not have a HvdcAngleDroopActivePowerControl extension", hvdcId)); + } + if (droop != null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("Droop is defined but it will not be applied since the hvdc line %s does not have a HvdcAngleDroopActivePowerControl extension", hvdcId)); + } + } + return dryRunConclusive; + } + private void applyToHvdcAngleDroopActivePowerControlExtension(HvdcLine hvdcLine) { HvdcAngleDroopActivePowerControl hvdcAngleDroopActivePowerControl = hvdcLine.getExtension(HvdcAngleDroopActivePowerControl.class); if (acEmulationEnabled != null) { diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/LoadModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/LoadModification.java index 39afaea5e93..d95628a1925 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/LoadModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/LoadModification.java @@ -54,4 +54,25 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE getP0().ifPresent(value -> load.setP0((isRelativeValue() ? load.getP0() : 0) + value)); getQ0().ifPresent(value -> load.setQ0((isRelativeValue() ? load.getQ0() : 0) + value)); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getLoad(getLoadId()) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "LoadModification", + "Load '" + getLoadId() + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/NetworkModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/NetworkModification.java index ec43522e086..3c57e907cf6 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/NetworkModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/NetworkModification.java @@ -60,4 +60,23 @@ public interface NetworkModification { * in case of error. */ void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode); + + boolean dryRun(Network network); + + boolean dryRun(Network network, ReportNode reportNode); + + /** + * Test the application of the modification to the given network by checking prerequisites. + */ + boolean dryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode); + + /** + * Tells if a network modification may have an impact on another network modification that would be applied afterwards + */ + boolean hasImpactOnNetwork(); + + /** + * Tells if all the prerequisites of the network modification can be checked in the local dry run + */ + boolean isLocalDryRunPossible(); } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/NetworkModificationList.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/NetworkModificationList.java index 118a065c240..09e291da281 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/NetworkModificationList.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/NetworkModificationList.java @@ -7,11 +7,17 @@ */ package com.powsybl.iidm.modification; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.report.TypedValue; import com.powsybl.computation.ComputationManager; +import com.powsybl.computation.local.LocalComputationManager; +import com.powsybl.iidm.modification.topology.DefaultNamingStrategy; import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.serde.NetworkSerDe; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -36,4 +42,91 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE ComputationManager computationManager, ReportNode reportNode) { modificationList.forEach(modification -> modification.apply(network, namingStrategy, throwException, computationManager, reportNode)); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, + ComputationManager computationManager, ReportNode reportNode) { + if (!hasImpactOnNetwork() && isLocalDryRunPossible()) { + dryRunConclusive = getFlattenedModificationList().stream().allMatch(modification -> modification.dryRun(network, namingStrategy, computationManager, reportNode)); + if (!dryRunConclusive) { + reportOnInconclusiveDryRun(reportNode, + "NetworkModificationList", + "At least one dry run on a network modification from the list failed"); + } + } else { + dryRunConclusive = fullDryRun(network, namingStrategy, computationManager, reportNode); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + List flatModificationList = getFlattenedModificationList(); + + // Empty list + if (flatModificationList.isEmpty()) { + return false; + } + + // Check if any network modification from the list - except the last element - has an impact on the network + return flatModificationList.subList(0, flatModificationList.size() - 1) + .stream() + .anyMatch(NetworkModification::hasImpactOnNetwork); + } + + @Override + public boolean isLocalDryRunPossible() { + return modificationList.stream().allMatch(NetworkModification::isLocalDryRunPossible); + } + + private List getFlattenedModificationList() { + List flatModificationList = new ArrayList<>(); + modificationList.forEach(networkModification -> { + if (networkModification instanceof NetworkModificationList networkModificationList) { + flatModificationList.addAll(networkModificationList.getFlattenedModificationList()); + } else { + flatModificationList.add(networkModification); + } + }); + return flatModificationList; + } + + public boolean fullDryRun(Network network) { + return fullDryRun(network, ReportNode.NO_OP); + } + + public boolean fullDryRun(Network network, ReportNode reportNode) { + return fullDryRun(network, new DefaultNamingStrategy(), LocalComputationManager.getDefault(), reportNode); + } + + public boolean fullDryRun(Network network, NamingStrategy namingStrategy, + ComputationManager computationManager, ReportNode reportNode) { + String templateKey = "networkModificationsDryRun"; + String messageTemplate = "Dry-run: Checking if network modifications can be applied on network ${networkNameOrId}"; + ReportNode dryRunReportNode = reportNode.newReportNode() + .withMessageTemplate(templateKey, messageTemplate) + .withUntypedValue("networkNameOrId", network.getNameOrId()) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + try { + //TODO The following copy performs an XML export/import. It will be more performant to change it to the BIN format. + Network dryRunNetwork = NetworkSerDe.copy(network); + dryRunNetwork.setName(network.getNameOrId() + "_Dry-run"); + apply(dryRunNetwork, namingStrategy, true, computationManager, dryRunReportNode); + } catch (PowsyblException powsyblException) { + dryRunReportNode.newReportNode() + .withMessageTemplate("networkModificationsDryRun-failure", + "Dry-run failed. Error message is: ${dryRunError}") + .withUntypedValue("dryRunError", powsyblException.getMessage()) + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + return false; + } + dryRunReportNode.newReportNode() + .withMessageTemplate("networkModificationsDryRun-success", + "DRY-RUN: Network modifications can successfully be applied on network ${networkNameOrId}") + .withSeverity(TypedValue.INFO_SEVERITY) + .add(); + return true; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/OpenSwitch.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/OpenSwitch.java index a4fbed4ba65..74cb6f54716 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/OpenSwitch.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/OpenSwitch.java @@ -36,4 +36,25 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } sw.setOpen(true); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getSwitch(switchId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "OpenSwitch", + "Switch '" + switchId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterOptimizeTap.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterOptimizeTap.java index 513934f5036..308c879e89f 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterOptimizeTap.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterOptimizeTap.java @@ -11,18 +11,15 @@ import com.powsybl.computation.ComputationManager; import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Network; - -import java.util.Objects; +import com.powsybl.iidm.network.TwoWindingsTransformer; /** * @author Geoffroy Jamgotchian {@literal } */ -public class PhaseShifterOptimizeTap extends AbstractNetworkModification { - - private final String phaseShifterId; +public class PhaseShifterOptimizeTap extends AbstractPhaseShifterModification { public PhaseShifterOptimizeTap(String phaseShifterId) { - this.phaseShifterId = Objects.requireNonNull(phaseShifterId); + super(phaseShifterId); } @Override @@ -31,4 +28,27 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE new LoadFlowBasedPhaseShifterOptimizer(computationManager) .findMaximalFlowTap(network, phaseShifterId); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + // TODO: should we run the loadflow or not? If not, delete this method + TwoWindingsTransformer phaseShifter = network.getTwoWindingsTransformer(phaseShifterId); + if (phaseShifter == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "AbstractPhaseShifterModification", + String.format("Transformer %s not found", phaseShifterId)); + } else if (!phaseShifter.hasPhaseTapChanger()) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "AbstractPhaseShifterModification", + String.format("Transformer %s is not a phase shifter", phaseShifterId)); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterSetAsFixedTap.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterSetAsFixedTap.java index 5b0a677291f..2bea7a0eef3 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterSetAsFixedTap.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterSetAsFixedTap.java @@ -7,23 +7,20 @@ */ package com.powsybl.iidm.modification; -import com.powsybl.commons.PowsyblException; import com.powsybl.commons.report.ReportNode; import com.powsybl.computation.ComputationManager; import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.PhaseTapChanger; -import com.powsybl.iidm.network.TwoWindingsTransformer; import java.util.Objects; -public class PhaseShifterSetAsFixedTap extends AbstractNetworkModification { +public class PhaseShifterSetAsFixedTap extends AbstractPhaseShifterModification { - private final String phaseShifterId; private final int tapPosition; public PhaseShifterSetAsFixedTap(String phaseShifterId, int tapPosition) { - this.phaseShifterId = Objects.requireNonNull(phaseShifterId); + super(phaseShifterId); this.tapPosition = tapPosition; } @@ -31,15 +28,19 @@ public PhaseShifterSetAsFixedTap(String phaseShifterId, int tapPosition) { public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) { Objects.requireNonNull(network); - TwoWindingsTransformer phaseShifter = network.getTwoWindingsTransformer(phaseShifterId); - if (phaseShifter == null) { - throw new PowsyblException("Transformer '" + phaseShifterId + "' not found"); - } - if (!phaseShifter.hasPhaseTapChanger()) { - throw new PowsyblException("Transformer '" + phaseShifterId + "' is not a phase shifter"); - } - phaseShifter.getPhaseTapChanger().setTapPosition(tapPosition); - phaseShifter.getPhaseTapChanger().setRegulating(false); - phaseShifter.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.FIXED_TAP); + PhaseTapChanger phaseTapChanger = getPhaseTapChanger(network); + phaseTapChanger.setTapPosition(tapPosition); + phaseTapChanger.setRegulating(false); + phaseTapChanger.setRegulationMode(PhaseTapChanger.RegulationMode.FIXED_TAP); + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterShiftTap.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterShiftTap.java index 5200b21ce8b..d7165207721 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterShiftTap.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/PhaseShifterShiftTap.java @@ -7,26 +7,23 @@ */ package com.powsybl.iidm.modification; -import com.powsybl.commons.PowsyblException; import com.powsybl.commons.report.ReportNode; import com.powsybl.computation.ComputationManager; import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.PhaseTapChanger; -import com.powsybl.iidm.network.TwoWindingsTransformer; import java.util.Objects; /** * @author Hamou AMROUN {@literal } */ -public class PhaseShifterShiftTap extends AbstractNetworkModification { +public class PhaseShifterShiftTap extends AbstractPhaseShifterModification { - private final String phaseShifterId; private final int tapDelta; public PhaseShifterShiftTap(String phaseShifterId, int tapDelta) { - this.phaseShifterId = Objects.requireNonNull(phaseShifterId); + super(phaseShifterId); this.tapDelta = tapDelta; } @@ -38,14 +35,7 @@ public int getTapDelta() { public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) { Objects.requireNonNull(network); - TwoWindingsTransformer phaseShifter = network.getTwoWindingsTransformer(phaseShifterId); - if (phaseShifter == null) { - throw new PowsyblException("Transformer '" + phaseShifterId + "' not found"); - } - PhaseTapChanger phaseTapChanger = phaseShifter.getPhaseTapChanger(); - if (phaseTapChanger == null) { - throw new PowsyblException("Transformer '" + phaseShifterId + "' is not a phase shifter"); - } + PhaseTapChanger phaseTapChanger = getPhaseTapChanger(network); adjustTapPosition(phaseTapChanger); phaseTapChanger.setRegulating(false); phaseTapChanger.setRegulationMode(PhaseTapChanger.RegulationMode.FIXED_TAP); @@ -55,4 +45,14 @@ private void adjustTapPosition(PhaseTapChanger phaseTapChanger) { phaseTapChanger.setTapPosition(Math.min(Math.max(phaseTapChanger.getTapPosition() + tapDelta, phaseTapChanger.getLowTapPosition()), phaseTapChanger.getHighTapPosition())); } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ReplaceTieLinesByLines.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ReplaceTieLinesByLines.java index a563b278323..00a27fc56a5 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ReplaceTieLinesByLines.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ReplaceTieLinesByLines.java @@ -111,6 +111,16 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + return true; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private static void warningAboutExtensions(DanglingLine dl1, DanglingLine dl2, TieLine tl, ReportNode reportNode) { String dl1Id = dl1.getId(); String dl2Id = dl2.getId(); diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ShuntCompensatorModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ShuntCompensatorModification.java index ca5e64628e4..99154e8b744 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ShuntCompensatorModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ShuntCompensatorModification.java @@ -59,6 +59,27 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getShuntCompensator(shuntCompensatorId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "ShuntCompensatorModification", + "ShuntCompensator '" + shuntCompensatorId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private static void setTargetV(ShuntCompensator shuntCompensator) { if (shuntCompensator.isVoltageRegulatorOn()) { VoltageRegulationUtils.getTargetVForRegulatingElement(shuntCompensator.getNetwork(), shuntCompensator.getRegulatingTerminal().getBusView().getBus(), diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ThreeWindingsTransformerModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ThreeWindingsTransformerModification.java index 1aa4147d954..2e822ca2846 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ThreeWindingsTransformerModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/ThreeWindingsTransformerModification.java @@ -45,6 +45,27 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getThreeWindingsTransformer(transformerId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "ThreeWindingsTransformerModification", + "ThreeWindingsTransformer '" + transformerId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private static double calculateNewRatedU(double ratedU, double ratedU0, double newRatedU0) { return ratedU * newRatedU0 / ratedU0; } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tapchanger/AbstractTapPositionModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tapchanger/AbstractTapPositionModification.java index 7344a3077a0..15a59fb6eb5 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tapchanger/AbstractTapPositionModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tapchanger/AbstractTapPositionModification.java @@ -69,6 +69,23 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + // TODO: fix this + if (network.getTwoWindingsTransformer(getTransformerId()) == null && network.getThreeWindingsTransformer(getTransformerId()) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "AbstractTapPositionModification", + TRANSFORMER_STR + getTransformerId() + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + /** * @param isTapHolder predicate to test if the leg has the correct Tap * @return The leg either indicated in the constructor, or the unique one matching the predicate, null otherwise diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/AbstractCreateConnectableFeederBays.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/AbstractCreateConnectableFeederBays.java index d23801968dc..beb422cc7d2 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/AbstractCreateConnectableFeederBays.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/AbstractCreateConnectableFeederBays.java @@ -31,6 +31,7 @@ abstract class AbstractCreateConnectableFeederBays extends AbstractNetworkModification { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCreateConnectableFeederBays.class); + private static final String NETWORK_MODIFICATION_NAME = "AbstractCreateConnectableFeederBays"; protected final int[] sides; @@ -76,6 +77,46 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE createExtensionAndTopology(connectable, network, namingStrategy, reportNode); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + for (int side : sides) { + // Get the busOrBusbarSection corresponding to the side parameter + String busOrBusbarSectionId = getBusOrBusbarSectionId(side); + Identifiable busOrBusbarSection = network.getIdentifiable(busOrBusbarSectionId); + + if (busOrBusbarSection == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("Bus or busbar section '%s' not found", busOrBusbarSectionId)); + } else if (busOrBusbarSection instanceof BusbarSection bbs) { + if (getPositionOrder(side) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "Position order is null for attachment in node-breaker voltage level " + bbs.getTerminal().getVoltageLevel().getId()); + } else if (getPositionOrder(side) < 0) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "Position order is negative for attachment in node-breaker voltage level " + bbs.getTerminal().getVoltageLevel().getId() + ": " + getPositionOrder(side)); + } + } else if (!(busOrBusbarSection instanceof Bus)) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("Unsupported type %s for identifiable %s", busOrBusbarSection.getType(), busOrBusbarSectionId)); + } + // TODO: should we go further and check the values in the Adder (as it is done in adder.add())? + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private boolean checkOrders(int side, VoltageLevel voltageLevel, ReportNode reportNode, boolean throwException) { TopologyKind topologyKind = voltageLevel.getTopologyKind(); Integer positionOrder = getPositionOrder(side); diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/AbstractLineConnectionModification.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/AbstractLineConnectionModification.java index 0776e7fbe5d..995eeab66cd 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/AbstractLineConnectionModification.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/AbstractLineConnectionModification.java @@ -9,6 +9,7 @@ import com.powsybl.commons.PowsyblException; import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; import com.powsybl.iidm.modification.AbstractNetworkModification; import com.powsybl.iidm.modification.util.ModificationLogs; import com.powsybl.iidm.network.*; @@ -16,12 +17,14 @@ import java.util.Objects; -import static com.powsybl.iidm.modification.util.ModificationReports.*; +import static com.powsybl.iidm.modification.util.ModificationReports.undefinedPercent; +import static com.powsybl.iidm.modification.util.ModificationReports.unexpectedIdentifiableType; /** * @author Miora Vedelago {@literal } */ abstract class AbstractLineConnectionModification> extends AbstractNetworkModification { + private static final String NETWORK_MODIFICATION_NAME = "AbstractLineConnectionModification"; protected final String bbsOrBusId; @@ -125,6 +128,31 @@ protected boolean failChecks(Network network, boolean throwException, ReportNode return voltageLevel == null; } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + Identifiable identifiable = network.getIdentifiable(bbsOrBusId); + if (identifiable == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("Bus or busbar section '%s' not found", bbsOrBusId)); + } else { + dryRunConclusive = checkVoltageLevel(identifiable, reportNode, NETWORK_MODIFICATION_NAME, dryRunConclusive); + } + if (Double.isNaN(positionPercent)) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "Percent should not be undefined"); + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private static VoltageLevel getVoltageLevel(Identifiable identifiable, boolean throwException, ReportNode reportNode, Logger logger) { if (identifiable instanceof Bus bus) { return bus.getVoltageLevel(); diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/CreateCouplingDevice.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/CreateCouplingDevice.java index b1a7c528ab8..b2f069bb29f 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/CreateCouplingDevice.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/CreateCouplingDevice.java @@ -32,6 +32,7 @@ public class CreateCouplingDevice extends AbstractNetworkModification { private static final Logger LOGGER = LoggerFactory.getLogger(CreateCouplingDevice.class); + private static final String NETWORK_MODIFICATION_NAME = "CreateCouplingDevice"; private final String busOrBbsId1; @@ -113,6 +114,37 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE newCouplingDeviceAddedReport(reportNode, voltageLevel1.getId(), busOrBbsId1, busOrBbsId2); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + Identifiable busOrBbs1 = network.getIdentifiable(busOrBbsId1); + Identifiable busOrBbs2 = network.getIdentifiable(busOrBbsId2); + if (busOrBbs1 == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("Bus or busbar section %s not found", busOrBbsId1)); + } + if (busOrBbs2 == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("Bus or busbar section %s not found", busOrBbsId2)); + } else if (busOrBbs2 == busOrBbs1) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("No coupling device can be created on a same bus or busbar section (%s)", busOrBbsId1)); + } + dryRunConclusive = checkVoltageLevel(busOrBbs1, reportNode, NETWORK_MODIFICATION_NAME, dryRunConclusive); + dryRunConclusive = checkVoltageLevel(busOrBbs2, reportNode, NETWORK_MODIFICATION_NAME, dryRunConclusive); + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + /** * Apply the modification on the two specified busbar sections */ diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/CreateVoltageLevelTopology.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/CreateVoltageLevelTopology.java index 2fc8d6c2750..1d670fdec98 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/CreateVoltageLevelTopology.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/CreateVoltageLevelTopology.java @@ -31,6 +31,7 @@ public class CreateVoltageLevelTopology extends AbstractNetworkModification { private static final Logger LOG = LoggerFactory.getLogger(CreateVoltageLevelTopology.class); + private static final String NETWORK_MODIFICATION_NAME = "CreateVoltageLevelTopology"; private final String voltageLevelId; @@ -57,12 +58,19 @@ public class CreateVoltageLevelTopology extends AbstractNetworkModification { this.switchKinds = switchKinds; } - private static boolean checkCountAttributes(Integer count, String type, int min, ReportNode reportNode, boolean throwException) { + private static boolean checkCountAttributes(Integer count, String type, int min, ReportNode reportNode, + boolean throwException, boolean isDryRun) { if (count < min) { LOG.error("{} must be >= {}", type, min); - countLowerThanMin(reportNode, type, min); - if (throwException) { - throw new PowsyblException(type + " must be >= " + min); + if (isDryRun) { + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("%s must be >= %d", type, min)); + } else { + countLowerThanMin(reportNode, type, min); + if (throwException) { + throw new PowsyblException(type + " must be >= " + min); + } } return false; } @@ -70,11 +78,11 @@ private static boolean checkCountAttributes(Integer count, String type, int min, } private boolean checkCountAttributes(int lowBusOrBusbarIndex, int alignedBusesOrBusbarCount, int lowSectionIndex, - int sectionCount, boolean throwException, ReportNode reportNode) { - return checkCountAttributes(lowBusOrBusbarIndex, "low busbar index", 0, reportNode, throwException) && - checkCountAttributes(alignedBusesOrBusbarCount, "busbar count", 1, reportNode, throwException) && - checkCountAttributes(lowSectionIndex, "low section index", 0, reportNode, throwException) && - checkCountAttributes(sectionCount, "section count", 1, reportNode, throwException); + int sectionCount, boolean throwException, ReportNode reportNode, boolean isDryRun) { + return checkCountAttributes(lowBusOrBusbarIndex, "low busbar index", 0, reportNode, throwException, isDryRun) && + checkCountAttributes(alignedBusesOrBusbarCount, "busbar count", 1, reportNode, throwException, isDryRun) && + checkCountAttributes(lowSectionIndex, "low section index", 0, reportNode, throwException, isDryRun) && + checkCountAttributes(sectionCount, "section count", 1, reportNode, throwException, isDryRun); } private static boolean checkSwitchKinds(List switchKinds, int sectionCount, ReportNode reportNode, boolean throwException) { @@ -133,7 +141,7 @@ public List getSwitchKinds() { @Override public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) { //checks - if (!checkCountAttributes(lowBusOrBusbarIndex, alignedBusesOrBusbarCount, lowSectionIndex, sectionCount, throwException, reportNode)) { + if (!checkCountAttributes(lowBusOrBusbarIndex, alignedBusesOrBusbarCount, lowSectionIndex, sectionCount, throwException, reportNode, false)) { return; } @@ -170,6 +178,46 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE createdNewSymmetricalTopology(reportNode, voltageLevelId, alignedBusesOrBusbarCount, sectionCount); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + // Checks on the attributes + dryRunConclusive = checkCountAttributes(lowBusOrBusbarIndex, alignedBusesOrBusbarCount, lowSectionIndex, sectionCount, false, reportNode, true); + + // Get the voltage level + VoltageLevel voltageLevel = network.getVoltageLevel(voltageLevelId); + if (voltageLevel == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "Voltage level is null"); + } else if (voltageLevel.getTopologyKind() != TopologyKind.BUS_BREAKER) { + if (switchKinds.size() != sectionCount - 1) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "Unexpected switch kinds count (" + switchKinds.size() + "). Should be " + (sectionCount - 1)); + } + if (switchKinds.contains(null)) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "All switch kinds must be defined"); + } + if (switchKinds.stream().anyMatch(kind -> kind != SwitchKind.DISCONNECTOR && kind != SwitchKind.BREAKER)) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "Switch kinds must be DISCONNECTOR or BREAKER"); + } + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private void createBusbarSections(VoltageLevel voltageLevel, NamingStrategy namingStrategy) { int node = 0; for (int sectionNum = lowSectionIndex; sectionNum < lowSectionIndex + sectionCount; sectionNum++) { diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveFeederBay.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveFeederBay.java index 85185aede04..81e09b1bce9 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveFeederBay.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveFeederBay.java @@ -62,6 +62,29 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE LOGGER.info("Connectable {} removed", connectableId); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + Connectable connectable = network.getConnectable(connectableId); + if (connectable == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "RemoveFeederBay", + String.format("Connectable not found: %s", connectableId)); + } + if (connectable instanceof BusbarSection) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "RemoveFeederBay", + String.format("BusbarSection connectables are not allowed as RemoveFeederBay input: %s", connectableId)); + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private Graph createGraphFromTerminal(Terminal terminal) { Graph graph = new Pseudograph<>(Object.class); int node = terminal.getNodeBreakerView().getNode(); @@ -220,14 +243,6 @@ private static Integer getOppositeNode(Graph graph, int node, O } private boolean checkConnectable(boolean throwException, ReportNode reportNode, Connectable connectable) { - if (connectable instanceof BusbarSection) { - LOGGER.error("BusbarSection connectables are not allowed as RemoveFeederBay input: {}", connectableId); - removeFeederBayBusbarSectionReport(reportNode, connectableId); - if (throwException) { - throw new PowsyblException("BusbarSection connectables are not allowed as RemoveFeederBay input: " + connectableId); - } - return false; - } if (connectable == null) { LOGGER.error("Connectable {} not found", connectableId); notFoundConnectableReport(reportNode, connectableId); @@ -236,6 +251,14 @@ private boolean checkConnectable(boolean throwException, ReportNode reportNode, } return false; } + if (connectable instanceof BusbarSection) { + LOGGER.error("BusbarSection connectables are not allowed as RemoveFeederBay input: {}", connectableId); + removeFeederBayBusbarSectionReport(reportNode, connectableId); + if (throwException) { + throw new PowsyblException("BusbarSection connectables are not allowed as RemoveFeederBay input: " + connectableId); + } + return false; + } return true; } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveHvdcLine.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveHvdcLine.java index 92e4fd6c9b8..ad37516a824 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveHvdcLine.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveHvdcLine.java @@ -69,6 +69,22 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE } } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getHvdcLine(hvdcLineId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "RemoveHvdcLine", + "HvdcLine '" + hvdcLineId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private static ShuntCompensator getShuntCompensator(String id, Network network, boolean throwException, ReportNode reportNode) { ShuntCompensator sc = network.getShuntCompensator(id); if (sc == null) { diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveSubstation.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveSubstation.java index af8ff4935d9..837112e473f 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveSubstation.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveSubstation.java @@ -52,5 +52,21 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE removedSubstationReport(reportNode, substationId); LOGGER.info("Substation {} and its voltage levels have been removed", substationId); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getSubstation(substationId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "RemoveSubstation", + "Substation '" + substationId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveVoltageLevel.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveVoltageLevel.java index 9907c744353..db9271e6647 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveVoltageLevel.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RemoveVoltageLevel.java @@ -65,4 +65,20 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE LOGGER.info("Voltage level {}, its equipments and the branches it is connected to have been removed", voltageLevelId); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getVoltageLevel(voltageLevelId) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "RemoveVoltageLevel", + "Voltage level '" + voltageLevelId + "' not found"); + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/ReplaceTeePointByVoltageLevelOnLine.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/ReplaceTeePointByVoltageLevelOnLine.java index eed6a960dac..d13803fc1fb 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/ReplaceTeePointByVoltageLevelOnLine.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/ReplaceTeePointByVoltageLevelOnLine.java @@ -59,6 +59,7 @@ public class ReplaceTeePointByVoltageLevelOnLine extends AbstractNetworkModifica private static final String LINE_NOT_FOUND_REPORT_MESSAGE = "Line %s is not found"; private static final String LINE_NOT_FOUND_LOG_MESSAGE = "Line {} is not found"; private static final String LINE_REMOVED_LOG_MESSAGE = "Line {} removed"; + private static final String NETWORK_MODIFICATION_NAME = "CreateCouplingDevice"; /** * Constructor. @@ -218,6 +219,48 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE removeVoltageLevelAndSubstation(teePoint, reportNode); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + Line tpLine1 = network.getLine(teePointLine1Id); + Line tpLine2 = network.getLine(teePointLine2Id); + Line tpLineToRemove = network.getLine(teePointLineToRemoveId); + if (tpLine1 == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format(LINE_NOT_FOUND_REPORT_MESSAGE, teePointLine1Id)); + } + if (tpLine2 == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format(LINE_NOT_FOUND_REPORT_MESSAGE, teePointLine2Id)); + } + if (tpLineToRemove == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format(LINE_NOT_FOUND_REPORT_MESSAGE, teePointLineToRemoveId)); + } + + // tee point is the voltage level in common with tpLine1, tpLine2 and tpLineToRemove + if (tpLine1 != null && tpLine2 != null && tpLineToRemove != null) { + VoltageLevel teePoint = TopologyModificationUtils.findTeePoint(tpLine1, tpLine2, tpLineToRemove); + if (teePoint == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("No common voltage level found for %s, %s and %s", teePointLine1Id, teePointLine2Id, teePointLineToRemoveId)); + } + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + private boolean createTopology(LineAdder newLine1Adder, LineAdder newLine2Adder, VoltageLevel tappedVoltageLevel, NamingStrategy namingStrategy, ReportNode reportNode, boolean throwException) { TopologyKind topologyKind = tappedVoltageLevel.getTopologyKind(); if (topologyKind == TopologyKind.BUS_BREAKER) { diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RevertConnectVoltageLevelOnLine.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RevertConnectVoltageLevelOnLine.java index 187dc9eccd4..1d215a626ac 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RevertConnectVoltageLevelOnLine.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RevertConnectVoltageLevelOnLine.java @@ -49,6 +49,8 @@ public class RevertConnectVoltageLevelOnLine extends AbstractNetworkModification private static final Logger LOG = LoggerFactory.getLogger(RevertConnectVoltageLevelOnLine.class); + private static final String NETWORK_MODIFICATION_NAME = "RevertConnectVoltageLevelOnLine"; + private final String line1Id; private final String line2Id; @@ -161,6 +163,36 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE removeVoltageLevelAndSubstation(commonVl, reportNode); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + dryRunConclusive = checkLine(network, line1Id, reportNode, NETWORK_MODIFICATION_NAME, dryRunConclusive); + dryRunConclusive = checkLine(network, line2Id, reportNode, NETWORK_MODIFICATION_NAME, dryRunConclusive); + + // Check and find the voltage level in common + if (dryRunConclusive) { + // Since dryRunConclusive is true, the lines cannot be null so no check is needed + Line line1 = network.getLine(line1Id); + Line line2 = network.getLine(line2Id); + Set vlIds = new HashSet<>(); + vlIds.add(line1.getTerminal1().getVoltageLevel().getId()); + vlIds.add(line1.getTerminal2().getVoltageLevel().getId()); + vlIds.add(line2.getTerminal1().getVoltageLevel().getId()); + vlIds.add(line2.getTerminal2().getVoltageLevel().getId()); + if (vlIds.size() != 3) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("Lines %s and %s should have one and only one voltage level in common at their extremities", line1Id, line2Id)); + } + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + public String getLine1Id() { return line1Id; } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RevertCreateLineOnLine.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RevertCreateLineOnLine.java index 2229ceb924e..19624acdcb2 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RevertCreateLineOnLine.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/topology/RevertCreateLineOnLine.java @@ -52,6 +52,7 @@ public class RevertCreateLineOnLine extends AbstractNetworkModification { private static final String LINE_NOT_FOUND_REPORT_MESSAGE = "Line %s is not found"; private static final String LINE_REMOVED_MESSAGE = "Line {} removed"; + private static final String NETWORK_MODIFICATION_NAME = "RevertCreateLineOnLine"; /** * Constructor. @@ -182,6 +183,33 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE removeVoltageLevelAndSubstation(tappedVoltageLevel, reportNode); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + dryRunConclusive = checkLine(network, lineToBeMerged1Id, reportNode, NETWORK_MODIFICATION_NAME, dryRunConclusive); + dryRunConclusive = checkLine(network, lineToBeMerged2Id, reportNode, NETWORK_MODIFICATION_NAME, dryRunConclusive); + + if (dryRunConclusive) { + Line lineToBeMerged1 = network.getLine(lineToBeMerged1Id); + Line lineToBeMerged2 = network.getLine(lineToBeMerged2Id); + Line lineToBeDeleted = network.getLine(lineToBeDeletedId); + + // tee point is the voltage level in common with lineToBeMerged1, lineToBeMerged2 and lineToBeDeleted + VoltageLevel teePoint = TopologyModificationUtils.findTeePoint(lineToBeMerged1, lineToBeMerged2, lineToBeDeleted); + if (teePoint == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("No common voltage level found for %s, %s and %s", lineToBeMerged1, lineToBeMerged2, lineToBeDeleted)); + } + } + return dryRunConclusive; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + public String getLineToBeMerged1Id() { return lineToBeMerged1Id; } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/AbstractTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/AbstractTripping.java index b97406427a1..fcac5a1e31a 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/AbstractTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/AbstractTripping.java @@ -47,6 +47,16 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE terminalsToDisconnect.forEach(Terminal::disconnect); } + @Override + public boolean hasImpactOnNetwork() { + return false; + } + + @Override + public boolean isLocalDryRunPossible() { + return true; + } + public void traverseDoubleSidedEquipment(String voltageLevelId, Terminal terminal1, Terminal terminal2, Set switchesToOpen, Set terminalsToDisconnect, Set traversedTerminals, String equipmentType) { if (voltageLevelId != null) { if (voltageLevelId.equals(terminal1.getVoltageLevel().getId())) { diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BatteryTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BatteryTripping.java index 94a5f6b5c87..601a6e64e62 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BatteryTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BatteryTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Battery; import com.powsybl.iidm.network.Network; @@ -29,4 +32,15 @@ protected Battery getInjection(Network network) { return injection; } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getBattery(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "BatteryTripping", + "Battery '" + id + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BranchTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BranchTripping.java index 5bc75883d83..5736be72821 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BranchTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BranchTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Branch; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.Switch; @@ -28,6 +31,8 @@ public class BranchTripping extends AbstractTripping { private final BiFunction> supplier; + private static final String NETWORK_MODIFICATION_NAME = "BranchTripping"; + public BranchTripping(String branchId) { this(branchId, null); } @@ -42,6 +47,32 @@ protected BranchTripping(String branchId, String voltageLevelId, BiFunction branch = supplier.apply(network, id); + if (branch == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("Branch %s not found", id)); + } + if (voltageLevelId == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "voltageLevelId should not be null"); + } + if (branch != null && voltageLevelId != null + && !voltageLevelId.equals(branch.getTerminal1().getVoltageLevel().getId()) + && !voltageLevelId.equals(branch.getTerminal2().getVoltageLevel().getId())) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("Branch %s is not connected to voltage level %s", id, voltageLevelId)); + } + return dryRunConclusive; + } + protected String getVoltageLevelId() { return voltageLevelId; } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BusTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BusTripping.java index 5b0a0754f52..db51bb7e20a 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BusTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BusTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.*; import java.util.Objects; @@ -35,4 +38,15 @@ public void traverse(Network network, Set switchesToOpen, Set TrippingTopologyTraverser.traverse(t, switchesToOpen, terminalsToDisconnect, traversedTerminals); } } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getBusBreakerView().getBus(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "BusTripping", + "Bus '" + id + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BusbarSectionTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BusbarSectionTripping.java index 35e9c2ddc63..506c70a6c19 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BusbarSectionTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/BusbarSectionTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.BusbarSection; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.Switch; @@ -36,4 +39,15 @@ public void traverse(Network network, Set switchesToOpen, Set TrippingTopologyTraverser.traverse(busbarSection.getTerminal(), switchesToOpen, terminalsToDisconnect, traversedTerminals); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getBusbarSection(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "BusbarSectionTripping", + "BusbarSection '" + id + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/DanglingLineTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/DanglingLineTripping.java index 307b8339161..b69c8eb331a 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/DanglingLineTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/DanglingLineTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.DanglingLine; import com.powsybl.iidm.network.Network; @@ -29,4 +32,15 @@ protected DanglingLine getInjection(Network network) { return injection; } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getDanglingLine(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "DanglingLineTripping", + "DanglingLine '" + id + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/GeneratorTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/GeneratorTripping.java index 4dccd16faeb..5e48357a9b7 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/GeneratorTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/GeneratorTripping.java @@ -9,6 +9,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Network; @@ -32,4 +35,15 @@ protected Generator getInjection(Network network) { return injection; } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getGenerator(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "GeneratorTripping", + "Generator '" + id + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/HvdcLineTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/HvdcLineTripping.java index bc95199dd6d..1f434d166b7 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/HvdcLineTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/HvdcLineTripping.java @@ -8,10 +8,10 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.HvdcLine; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.Switch; -import com.powsybl.iidm.network.Terminal; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; +import com.powsybl.iidm.network.*; import java.util.Objects; import java.util.Set; @@ -23,6 +23,8 @@ public class HvdcLineTripping extends AbstractTripping { private final String voltageLevelId; + private static final String NETWORK_MODIFICATION_NAME = "HvdcLineTripping"; + public HvdcLineTripping(String hvdcLineId) { this(hvdcLineId, null); } @@ -46,4 +48,30 @@ public void traverse(Network network, Set switchesToOpen, Set traverseDoubleSidedEquipment(voltageLevelId, terminal1, terminal2, switchesToOpen, terminalsToDisconnect, traversedTerminals, hvdcLine.getType().name()); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + HvdcLine hvdcLine = network.getHvdcLine(id); + if (hvdcLine == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("HvdcLine %s not found", id)); + } + if (voltageLevelId == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "voltageLevelId should not be null"); + } + if (hvdcLine != null && voltageLevelId != null + && !voltageLevelId.equals(hvdcLine.getConverterStation1().getTerminal().getVoltageLevel().getId()) + && !voltageLevelId.equals(hvdcLine.getConverterStation2().getTerminal().getVoltageLevel().getId())) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("HvdcLine %s is not connected to voltage level %s", id, voltageLevelId)); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/LoadTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/LoadTripping.java index 95641faef2c..9dae3ad1924 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/LoadTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/LoadTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Load; import com.powsybl.iidm.network.Network; @@ -29,4 +32,15 @@ protected Load getInjection(Network network) { return injection; } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getLoad(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "LoadTripping", + "Load '" + id + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/ShuntCompensatorTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/ShuntCompensatorTripping.java index 84cf8d2c2e1..a144d7ba8a1 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/ShuntCompensatorTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/ShuntCompensatorTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.ShuntCompensator; @@ -29,4 +32,15 @@ protected ShuntCompensator getInjection(Network network) { return injection; } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getShuntCompensator(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "ShuntCompensatorTripping", + "ShuntCompensator '" + id + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/StaticVarCompensatorTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/StaticVarCompensatorTripping.java index be057463440..b66c83d99c6 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/StaticVarCompensatorTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/StaticVarCompensatorTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.StaticVarCompensator; @@ -29,4 +32,15 @@ protected StaticVarCompensator getInjection(Network network) { return injection; } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getStaticVarCompensator(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "StaticVarCompensatorTripping", + "StaticVarCompensator '" + id + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/SwitchTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/SwitchTripping.java index 9cc53362d97..e6d2ad4aae5 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/SwitchTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/SwitchTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.*; import java.util.Objects; @@ -33,4 +36,15 @@ public void traverse(Network network, Set switchesToOpen, Set switchesToOpen.add(aSwitch); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getSwitch(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "SwitchTripping", + "Switch '" + id + "' not found"); + } + return dryRunConclusive; + } } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/ThreeWindingsTransformerTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/ThreeWindingsTransformerTripping.java index 6324937120b..ad9caaf85f2 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/ThreeWindingsTransformerTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/ThreeWindingsTransformerTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.Switch; import com.powsybl.iidm.network.Terminal; @@ -38,6 +41,17 @@ public void traverse(Network network, Set switchesToOpen, Set TrippingTopologyTraverser.traverse(twt3.getLeg3().getTerminal(), switchesToOpen, terminalsToDisconnect, traversedTerminals); } + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + if (network.getThreeWindingsTransformer(id) == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + "ThreeWindingsTransformerTripping", + "ThreeWindingsTransformer '" + id + "' not found"); + } + return dryRunConclusive; + } + protected PowsyblException createNotFoundException() { return new PowsyblException("ThreeWindingsTransformer '" + id + "' not found"); } diff --git a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/TieLineTripping.java b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/TieLineTripping.java index 5513a226ccc..a98ece90c1f 100644 --- a/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/TieLineTripping.java +++ b/iidm/iidm-modification/src/main/java/com/powsybl/iidm/modification/tripping/TieLineTripping.java @@ -8,6 +8,9 @@ package com.powsybl.iidm.modification.tripping; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.modification.topology.NamingStrategy; import com.powsybl.iidm.network.*; import java.util.Objects; @@ -20,6 +23,8 @@ public class TieLineTripping extends AbstractTripping { private final String voltageLevelId; + private static final String NETWORK_MODIFICATION_NAME = "TieLineTripping"; + public TieLineTripping(String tieLineId) { this(tieLineId, null); } @@ -43,4 +48,30 @@ public void traverse(Network network, Set switchesToOpen, Set traverseDoubleSidedEquipment(voltageLevelId, terminal1, terminal2, switchesToOpen, terminalsToDisconnect, traversedTerminals, tieLine.getType().name()); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + TieLine tieLine = network.getTieLine(id); + if (tieLine == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("TieLine %s not found", id)); + } + if (voltageLevelId == null) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + "voltageLevelId should not be null"); + } + if (tieLine != null && voltageLevelId != null + && !voltageLevelId.equals(tieLine.getDanglingLine1().getTerminal().getVoltageLevel().getId()) + && !voltageLevelId.equals(tieLine.getDanglingLine2().getTerminal().getVoltageLevel().getId())) { + dryRunConclusive = false; + reportOnInconclusiveDryRun(reportNode, + NETWORK_MODIFICATION_NAME, + String.format("TieLine %s is not connected to voltage level %s", id, voltageLevelId)); + } + return dryRunConclusive; + } } 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 35cae543424..3456b2c380a 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 @@ -450,6 +450,22 @@ public static void notFoundBusbarSectionInVoltageLevelReport(ReportNode reportNo .add(); } + public static void notFoundBatteryReport(ReportNode reportNode, String batteryId) { + reportNode.newReportNode() + .withMessageTemplate("notFoundBattery", "Battery '${batteryId}' not found") + .withUntypedValue("batteryId", batteryId) + .withSeverity(TypedValue.ERROR_SEVERITY) + .add(); + } + + public static void notFoundSwitchReport(ReportNode reportNode, String switchId) { + reportNode.newReportNode() + .withMessageTemplate("notFoundSwitch", "Switch '${switchId}' not found") + .withUntypedValue("switchId", switchId) + .withSeverity(TypedValue.ERROR_SEVERITY) + .add(); + } + public static void noCouplingDeviceOnSameBusOrBusbarSection(ReportNode reportNode, String busbarSectionId) { reportNode.newReportNode() .withMessageTemplate("noCouplingDeviceOnSameBusOrBusbarSection", "No coupling device can be created on a same bus or busbar section (${busOrBbsId}).") diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/BatteryModificationTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/BatteryModificationTest.java new file mode 100644 index 00000000000..dcdef848190 --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/BatteryModificationTest.java @@ -0,0 +1,88 @@ +/** + * 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.iidm.modification; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.Battery; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.BatteryNetworkFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Nicolas PIERRE {@literal } + */ +class BatteryModificationTest { + + private Network network; + private Battery battery; + + @BeforeEach + public void setUp() { + network = BatteryNetworkFactory.create(); + assertTrue(network.getBatteryCount() > 0); + battery = network.getBatteries().iterator().next(); + battery.setTargetQ(0); + } + + @Test + void testBatteryModification() { + BatteryModification batteryModification = new BatteryModification(battery.getId(), 1.0, null); + assertEquals(9999.99, battery.getTargetP()); + batteryModification.apply(network); + assertEquals(1., battery.getTargetP()); + + BatteryModification batteryModification2 = new BatteryModification(battery.getId(), null, 2.0); + assertEquals(0, battery.getTargetQ()); + batteryModification2.apply(network); + assertEquals(2., battery.getTargetQ()); + + BatteryModification batteryModification3 = new BatteryModification("BAT_NOT_EXISTING", null, 2.0); + PowsyblException exception = assertThrows(PowsyblException.class, () -> batteryModification3.apply(network, true, ReportNode.NO_OP)); + assertEquals("Battery 'BAT_NOT_EXISTING' not found", exception.getMessage()); + + // With no exception thrown + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("test", "test") + .build(); + batteryModification3.apply(network, false, reportNode); + assertEquals("Battery 'BAT_NOT_EXISTING' not found", + reportNode.getChildren().get(0).getMessage()); + } + + @Test + void testBatteryModificationGetters() { + BatteryModification batteryModification = new BatteryModification(battery.getId(), 1.0, null); + assertEquals(battery.getId(), batteryModification.getBatteryId()); + assertEquals(1.0, batteryModification.getTargetP()); + assertNull(batteryModification.getTargetQ()); + } + + @Test + void testDryRun() { + // Passing dryRun + BatteryModification batteryModificationPassing = new BatteryModification(battery.getId(), 1.0, null); + assertTrue(batteryModificationPassing.dryRun(network)); + + // Useful methods for dry run + assertFalse(batteryModificationPassing.hasImpactOnNetwork()); + assertTrue(batteryModificationPassing.isLocalDryRunPossible()); + + // Failing dryRun + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + BatteryModification batteryModificationFailing = new BatteryModification("BAT_NOT_EXISTING", 1.0, null); + assertFalse(batteryModificationFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for BatteryModification. The issue is: Battery 'BAT_NOT_EXISTING' not found", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ConnectGeneratorTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ConnectGeneratorTest.java index f1169a426dd..41ca4a8e057 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ConnectGeneratorTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ConnectGeneratorTest.java @@ -7,14 +7,15 @@ */ package com.powsybl.iidm.modification; +import com.powsybl.commons.report.ReportNode; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; /** * @author Olivier Perrin {@literal } @@ -101,4 +102,52 @@ void testConnectGeneratorWithNoNetworkInformation() { assertTrue(g2.getTerminal().isConnected()); assertEquals(g2.getRegulatingTerminal().getBusView().getBus().getV(), g2.getTargetV(), 0.01); } + + @Test + void testDryRunConnection() { + // Passing dryRun + ConnectGenerator connectGenerator = new ConnectGenerator(g2.getId()); + assertTrue(connectGenerator.dryRun(network)); + + // Useful methods for dry run + assertFalse(connectGenerator.hasImpactOnNetwork()); + assertTrue(connectGenerator.isLocalDryRunPossible()); + + // Failing dryRun + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + ConnectGenerator connectGeneratorFailing = new ConnectGenerator("GEN_NOT_EXISTING"); + assertFalse(connectGeneratorFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for ConnectGenerator. The issue is: Generator 'GEN_NOT_EXISTING' not found", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + } + + @Test + void testDryRunModification() { + g3.setVoltageRegulatorOn(false); + g2.setVoltageRegulatorOn(false); + g2.setRegulatingTerminal(g3.getTerminal()); + g2.setTargetV(Double.NaN); + GeneratorModification.Modifs modifs = new GeneratorModification.Modifs(); + modifs.setVoltageRegulatorOn(true); // no targetV provided! + modifs.setConnected(true); + + // Passing dryRun + GeneratorModification generatorModification = new GeneratorModification(g2.getId(), modifs); + assertTrue(generatorModification.dryRun(network)); + + // Useful methods for dry run + assertFalse(generatorModification.hasImpactOnNetwork()); + assertTrue(generatorModification.isLocalDryRunPossible()); + + // Failing dryRun + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + GeneratorModification connectGeneratorFailing = new GeneratorModification("GEN_NOT_EXISTING", modifs); + assertFalse(connectGeneratorFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for GeneratorModification. The issue is: Generator 'GEN_NOT_EXISTING' not found", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + } } diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ConnectionAndDisconnectionsTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ConnectionAndDisconnectionsTest.java index 9e42d4ae7af..127caa04ce2 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ConnectionAndDisconnectionsTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ConnectionAndDisconnectionsTest.java @@ -441,6 +441,81 @@ void testConnectionNoConnection() throws IOException { testReportNode(reportNode, "/reportNode/connectable-not-connected.txt"); } + @Test + void testDryRunConnection() { + // Network creation + Network network = createNetwork(); + + // Connection - Passing dryRun + ConnectableConnection connection = new ConnectableConnectionBuilder() + .withConnectableId("L2") + .withFictitiousSwitchesOperable(true) + .withOnlyBreakersOperable(false) + .build(); + assertTrue(connection.dryRun(network)); + + // Useful methods for dry run + assertFalse(connection.hasImpactOnNetwork()); + assertTrue(connection.isLocalDryRunPossible()); + + // CloseSwitch - Failing dryRun + ReportNode reportNodeCloseSwitch = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + ConnectableConnection connectionFailing = new ConnectableConnectionBuilder() + .withConnectableId("dummy") + .withFictitiousSwitchesOperable(false) + .withOnlyBreakersOperable(true) + .build(); + assertFalse(connectionFailing.dryRun(network, reportNodeCloseSwitch)); + assertEquals("Dry-run failed for ConnectableConnection. The issue is: Identifiable 'dummy' not found", + reportNodeCloseSwitch.getChildren().get(0).getChildren().get(0).getMessage()); + connectionFailing = new ConnectableConnectionBuilder() + .withConnectableId("L2") + .withFictitiousSwitchesOperable(false) + .withOnlyBreakersOperable(true) + .build(); + assertFalse(connectionFailing.dryRun(network, reportNodeCloseSwitch)); + assertEquals("Dry-run failed for ConnectableConnection. The issue is: Connection failed", + reportNodeCloseSwitch.getChildren().get(1).getChildren().get(0).getMessage()); + } + + @Test + void testDryRunDisconnection() { + // Network creation + Network network = createNetwork(); + + // Disconnection - Passing dryRun + UnplannedDisconnection unplannedDisconnection = new UnplannedDisconnectionBuilder() + .withConnectableId("L1") + .withFictitiousSwitchesOperable(true) + .build(); + assertTrue(unplannedDisconnection.dryRun(network)); + + // Useful methods for dry run + assertFalse(unplannedDisconnection.hasImpactOnNetwork()); + assertTrue(unplannedDisconnection.isLocalDryRunPossible()); + + // Disconnection - Failing dryRun + ReportNode reportNodeCloseSwitch = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + UnplannedDisconnection unplannedDisconnectionFailing = new UnplannedDisconnectionBuilder() + .withConnectableId("dummy") + .withFictitiousSwitchesOperable(false) + .build(); + assertFalse(unplannedDisconnectionFailing.dryRun(network, reportNodeCloseSwitch)); + assertEquals("Dry-run failed for AbstractDisconnection. The issue is: Identifiable 'dummy' not found", + reportNodeCloseSwitch.getChildren().get(0).getChildren().get(0).getMessage()); + unplannedDisconnectionFailing = new UnplannedDisconnectionBuilder() + .withConnectableId("L1") + .withFictitiousSwitchesOperable(false) + .build(); + assertFalse(unplannedDisconnectionFailing.dryRun(network, reportNodeCloseSwitch)); + assertEquals("Dry-run failed for AbstractDisconnection. The issue is: Disconnection failed", + reportNodeCloseSwitch.getChildren().get(1).getChildren().get(0).getMessage()); + } + @Test void testTieLine() { Network network = createNetwork(); diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/DanglingLineModificationTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/DanglingLineModificationTest.java index b1293e06430..de9879fcb66 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/DanglingLineModificationTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/DanglingLineModificationTest.java @@ -8,13 +8,14 @@ package com.powsybl.iidm.modification; +import com.powsybl.commons.report.ReportNode; import com.powsybl.iidm.network.DanglingLine; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; /** * @author Pauline Jean-Marie {@literal } @@ -47,4 +48,24 @@ void modifyQ0Relatively() { modification.apply(network); assertEquals(6.0, danglingLine.getQ0()); } + + @Test + void testDryRun() { + // Passing dryRun + DanglingLineModification modification = new DanglingLineModification("NHV1_XNODE1", false, 5.0, null); + assertTrue(modification.dryRun(network)); + + // Useful methods for dry run + assertFalse(modification.hasImpactOnNetwork()); + assertTrue(modification.isLocalDryRunPossible()); + + // Failing dryRun + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + DanglingLineModification modificationFailing = new DanglingLineModification("DANGLING_LINE_NOT_EXISTING", false, 5.0, null); + assertFalse(modificationFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for DanglingLineModification. The issue is: Dangling line 'DANGLING_LINE_NOT_EXISTING' not found", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + } } diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/HvdcLineModificationTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/HvdcLineModificationTest.java new file mode 100644 index 00000000000..5424785cefc --- /dev/null +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/HvdcLineModificationTest.java @@ -0,0 +1,131 @@ +/* + * 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.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.HvdcLine; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControl; +import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControlAdder; +import com.powsybl.iidm.network.test.HvdcTestNetwork; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Nicolas Rol {@literal } + */ +class HvdcLineModificationTest { + + private Network network; + private HvdcLine l; + private static final double EPSILON = Math.pow(10, -15); + + @BeforeEach + public void setUp() { + network = HvdcTestNetwork.createLcc(); + l = network.getHvdcLine("L"); + } + + @Test + void testModificationFailure() { + HvdcLineModification hvdcLineModification = new HvdcLineModification("dummy", true, null, null, null, null, null); + PowsyblException exception = assertThrows(PowsyblException.class, () -> hvdcLineModification.apply(network, true, ReportNode.NO_OP)); + assertEquals("Hvdc line 'dummy' not found", exception.getMessage()); + } + + @Test + void testModification() { + HvdcLineModification hvdcLineModification; + + // ActivePowerSetpoint - relative value null + assertEquals(280.0, l.getActivePowerSetpoint(), EPSILON); + hvdcLineModification = new HvdcLineModification(l.getId(), null, 300.0, null, null, null, null); + hvdcLineModification.apply(network, true, ReportNode.NO_OP); + assertEquals(300.0, l.getActivePowerSetpoint(), EPSILON); + + // ActivePowerSetpoint - relative value false + hvdcLineModification = new HvdcLineModification(l.getId(), null, 200.0, null, null, null, false); + hvdcLineModification.apply(network, true, ReportNode.NO_OP); + assertEquals(200.0, l.getActivePowerSetpoint(), EPSILON); + + // ActivePowerSetpoint - relative value true + hvdcLineModification = new HvdcLineModification(l.getId(), null, 80.0, null, null, null, true); + hvdcLineModification.apply(network, true, ReportNode.NO_OP); + assertEquals(280.0, l.getActivePowerSetpoint(), EPSILON); + + // Relative value null - Nothing happens + hvdcLineModification = new HvdcLineModification(l.getId(), null, null, null, null, null, null); + hvdcLineModification.apply(network, true, ReportNode.NO_OP); + assertEquals(280.0, l.getActivePowerSetpoint(), EPSILON); + + // Relative value false - Nothing happens + hvdcLineModification = new HvdcLineModification(l.getId(), null, null, null, null, null, false); + hvdcLineModification.apply(network, true, ReportNode.NO_OP); + assertEquals(280.0, l.getActivePowerSetpoint(), EPSILON); + + // Relative value true - Nothing happens + hvdcLineModification = new HvdcLineModification(l.getId(), null, null, null, null, null, true); + hvdcLineModification.apply(network, true, ReportNode.NO_OP); + assertEquals(280.0, l.getActivePowerSetpoint(), EPSILON); + + // Converter mode + assertEquals(HvdcLine.ConvertersMode.SIDE_1_INVERTER_SIDE_2_RECTIFIER, l.getConvertersMode()); + hvdcLineModification = new HvdcLineModification(l.getId(), null, null, HvdcLine.ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER, null, null, null); + hvdcLineModification.apply(network, true, ReportNode.NO_OP); + assertEquals(HvdcLine.ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER, l.getConvertersMode()); + + // HvdcAngleDroopActivePowerControl inactive - Nothing happens + hvdcLineModification = new HvdcLineModification(l.getId(), true, null, null, 50.0, 12.0, null); + hvdcLineModification.apply(network, true, ReportNode.NO_OP); + + // HvdcAngleDroopActivePowerControl active + HvdcAngleDroopActivePowerControl hvdcAngleDroopActivePowerControl = l.newExtension(HvdcAngleDroopActivePowerControlAdder.class).add(); + assertFalse(hvdcAngleDroopActivePowerControl.isEnabled()); + assertEquals(0.0, hvdcAngleDroopActivePowerControl.getP0(), EPSILON); + assertEquals(0.0, hvdcAngleDroopActivePowerControl.getDroop(), EPSILON); + hvdcLineModification = new HvdcLineModification(l.getId(), true, null, null, 50.0, 12.0, null); + hvdcLineModification.apply(network, true, ReportNode.NO_OP); + assertTrue(hvdcAngleDroopActivePowerControl.isEnabled()); + assertEquals(12.0, hvdcAngleDroopActivePowerControl.getP0(), EPSILON); + assertEquals(50.0, hvdcAngleDroopActivePowerControl.getDroop(), EPSILON); + } + + @Test + void testDryRunModification() { + HvdcLineModification hvdcLineModification; + + // Failing dryRun + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + hvdcLineModification = new HvdcLineModification("LINE_NOT_EXISTING", null, 300.0, null, null, null, null); + assertFalse(hvdcLineModification.dryRun(network, reportNode)); + assertEquals("Dry-run failed for HvdcLineModification. The issue is: Hvdc line 'LINE_NOT_EXISTING' not found", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + + // Failing dryRun + hvdcLineModification = new HvdcLineModification(l.getId(), true, null, null, 50.0, 12.0, true); + assertFalse(hvdcLineModification.dryRun(network, reportNode)); + assertEquals("Dry-run failed for HvdcLineModification. The issue is: Relative value is set to true but it will not be applied since active power setpoint is undefined (null)", + reportNode.getChildren().get(1).getChildren().get(0).getMessage()); + assertEquals("Dry-run failed for HvdcLineModification. The issue is: AV emulation enable is defined but it will not be applied since the hvdc line L does not have a HvdcAngleDroopActivePowerControl extension", + reportNode.getChildren().get(1).getChildren().get(1).getMessage()); + assertEquals("Dry-run failed for HvdcLineModification. The issue is: P0 is defined but it will not be applied since the hvdc line L does not have a HvdcAngleDroopActivePowerControl extension", + reportNode.getChildren().get(1).getChildren().get(2).getMessage()); + assertEquals("Dry-run failed for HvdcLineModification. The issue is: Droop is defined but it will not be applied since the hvdc line L does not have a HvdcAngleDroopActivePowerControl extension", + reportNode.getChildren().get(1).getChildren().get(3).getMessage()); + + // Passing dryRun + l.newExtension(HvdcAngleDroopActivePowerControlAdder.class).add(); + hvdcLineModification = new HvdcLineModification(l.getId(), true, null, null, 50.0, 12.0, null); + assertTrue(hvdcLineModification.dryRun(network)); + } +} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/LoadModificationTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/LoadModificationTest.java index 1f464acb73f..15c7f6c24b6 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/LoadModificationTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/LoadModificationTest.java @@ -8,13 +8,14 @@ package com.powsybl.iidm.modification; +import com.powsybl.commons.report.ReportNode; import com.powsybl.iidm.network.Load; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; /** * @author Pauline Jean-Marie {@literal } @@ -45,4 +46,24 @@ void modifyP0Relatively() { modification.apply(network); assertEquals(580.0, load.getP0()); } + + @Test + void testDryRun() { + // CloseSwitch - Passing dryRun + LoadModification modification = new LoadModification("LOAD", false, null, 100.0); + assertTrue(modification.dryRun(network)); + + // Useful methods for dry run + assertFalse(modification.hasImpactOnNetwork()); + assertTrue(modification.isLocalDryRunPossible()); + + // CloseSwitch - Failing dryRun + ReportNode reportNodeCloseSwitch = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + LoadModification failingModification = new LoadModification("dummy", false, null, 100.0); + assertFalse(failingModification.dryRun(network, reportNodeCloseSwitch)); + assertEquals("Dry-run failed for LoadModification. The issue is: Load 'dummy' not found", + reportNodeCloseSwitch.getChildren().get(0).getChildren().get(0).getMessage()); + } } diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/NetworkModificationListTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/NetworkModificationListTest.java index 9054a5a1328..7eb37443615 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/NetworkModificationListTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/NetworkModificationListTest.java @@ -7,12 +7,23 @@ */ package com.powsybl.iidm.modification; +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.test.TestUtil; +import com.powsybl.iidm.modification.topology.RemoveFeederBay; +import com.powsybl.iidm.modification.topology.RemoveFeederBayBuilder; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; import com.powsybl.iidm.modification.tripping.BranchTripping; import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.io.StringWriter; + import static org.junit.jupiter.api.Assertions.assertFalse; /** @@ -20,18 +31,64 @@ */ class NetworkModificationListTest { + private Network network; + + @BeforeEach + void init() { + network = EurostagTutorialExample1Factory.create(); + } + @Test void test() { - Network network = EurostagTutorialExample1Factory.create(); assertTrue(network.getLine("NHV1_NHV2_1").getTerminal1().isConnected()); assertTrue(network.getLine("NHV1_NHV2_1").getTerminal2().isConnected()); BranchTripping tripping1 = new BranchTripping("NHV1_NHV2_1", "VLHV1"); BranchTripping tripping2 = new BranchTripping("NHV1_NHV2_1", "VLHV2"); NetworkModificationList modificationList = new NetworkModificationList(tripping1, tripping2); + + boolean dryRunIsOk = Assertions.assertDoesNotThrow(() -> modificationList.fullDryRun(network)); + assertTrue(dryRunIsOk); modificationList.apply(network); assertFalse(network.getLine("NHV1_NHV2_1").getTerminal1().isConnected()); assertFalse(network.getLine("NHV1_NHV2_1").getTerminal2().isConnected()); } + + @Test + void applicationFailureTest() throws IOException { + String lineId = "NHV1_NHV2_1"; + assertTrue(network.getLine(lineId).getTerminal1().isConnected()); + assertTrue(network.getLine(lineId).getTerminal2().isConnected()); + + // Operation list: + // 1. Remove a line; + // 2. Open it. + // The second operation could not be performed because of the effect of the first + RemoveFeederBay removal = new RemoveFeederBayBuilder().withConnectableId(lineId).build(); + BranchTripping tripping = new BranchTripping(lineId, "VLHV1"); + NetworkModificationList task = new NetworkModificationList(removal, tripping); + + ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test reportNode").build(); + boolean dryRunIsOk = Assertions.assertDoesNotThrow(() -> task.fullDryRun(network, reportNode)); + // The full dry-run returns that a problem was encountered and that the full NetworkModificationList could not be performed. + // No operation was applied on the network. + assertFalse(dryRunIsOk); + assertNotNull(network.getLine("NHV1_NHV2_1")); + assertTrue(network.getLine("NHV1_NHV2_1").getTerminal1().isConnected()); + + StringWriter sw1 = new StringWriter(); + reportNode.print(sw1); + assertEquals(""" + + test reportNode + + Dry-run: Checking if network modifications can be applied on network sim1 + Connectable NHV1_NHV2_1 removed + Dry-run failed. Error message is: Branch 'NHV1_NHV2_1' not found + """, TestUtil.normalizeLineSeparator(sw1.toString())); + + // If we ignore the dry-run result and try to apply the NetworkModificationList, an exception is thrown and + // the network is in an "unstable" state. + Assertions.assertThrows(PowsyblException.class, () -> task.apply(network), "Branch '" + lineId + "' not found"); + assertNull(network.getLine("NHV1_NHV2_1")); + } } diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceTieLinesByLinesTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceTieLinesByLinesTest.java index b018add9c63..a9e995fbeda 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceTieLinesByLinesTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ReplaceTieLinesByLinesTest.java @@ -19,6 +19,8 @@ import java.time.ZonedDateTime; import java.util.Properties; +import static org.junit.jupiter.api.Assertions.assertTrue; + /** * @author Miora Vedelago {@literal } */ @@ -88,4 +90,17 @@ public String getName() { return "foo"; } } + + @Test + void testDryRun() { + Network network = createDummyNodeBreakerNetwork(); + + // ReplaceTieLinesByLines - Passing dryRun + ReplaceTieLinesByLines networkModification = new ReplaceTieLinesByLines(); + assertTrue(networkModification.dryRun(network)); + + // Useful methods for dry run + assertTrue(networkModification.hasImpactOnNetwork()); + assertTrue(networkModification.isLocalDryRunPossible()); + } } diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ShuntCompensatorModificationTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ShuntCompensatorModificationTest.java index 6ea4aed847a..2f7da371cc9 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ShuntCompensatorModificationTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ShuntCompensatorModificationTest.java @@ -116,4 +116,24 @@ void testConnectShuntCorrectSetPointWithNoRegulatingElmt() { Assertions.assertTrue(shunt.getTerminal().isConnected()); Assertions.assertEquals(2.0, shunt.getTargetV(), 0.1); } + + @Test + void testDryRun() { + // Passing dryRun + ShuntCompensatorModification shuntCompensatorModification = new ShuntCompensatorModification(shunt.getId(), null, null); + assertTrue(shuntCompensatorModification.dryRun(network)); + + // Useful methods for dry run + assertFalse(shuntCompensatorModification.hasImpactOnNetwork()); + assertTrue(shuntCompensatorModification.isLocalDryRunPossible()); + + // Failing dryRun + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + ShuntCompensatorModification modificationFailing = new ShuntCompensatorModification("SHUNT_NOT_EXISTING", null, null); + assertFalse(modificationFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for ShuntCompensatorModification. The issue is: ShuntCompensator 'SHUNT_NOT_EXISTING' not found", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + } } diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/SwitchModificationsTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/SwitchModificationsTest.java index ddf21fd246f..03c898c8450 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/SwitchModificationsTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/SwitchModificationsTest.java @@ -7,6 +7,8 @@ */ package com.powsybl.iidm.modification; +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.Switch; @@ -20,33 +22,71 @@ class SwitchModificationsTest { private final Network network = NetworkTest1Factory.create(); + private final String existingSwitchId = "generator1Breaker1"; + private final String notExistingSwitchId = "dummy"; @Test void test() { - String switchId = "generator1Breaker1"; - Switch sw = network.getSwitch(switchId); + Switch sw = network.getSwitch(existingSwitchId); Generator generator = network.getGenerator("generator1"); assertFalse(sw.isOpen()); assertTrue(generator.getTerminal().isConnected()); - new OpenSwitch(switchId).apply(network); + new OpenSwitch(existingSwitchId).apply(network); assertTrue(sw.isOpen()); assertFalse(generator.getTerminal().isConnected()); - new CloseSwitch(switchId).apply(network); + new CloseSwitch(existingSwitchId).apply(network); assertFalse(sw.isOpen()); assertTrue(generator.getTerminal().isConnected()); } @Test void testInvalidOpenSwitch() { - assertThrows(RuntimeException.class, () -> new OpenSwitch("dummy").apply(network)); + OpenSwitch openSwitch = new OpenSwitch(notExistingSwitchId); + PowsyblException exception = assertThrows(PowsyblException.class, () -> openSwitch.apply(network)); + assertEquals("Switch 'dummy' not found", exception.getMessage()); } @Test void testInvalidCloseSwitch() { - assertThrows(RuntimeException.class, () -> new CloseSwitch("dummy").apply(network)); + CloseSwitch closeSwitch = new CloseSwitch(notExistingSwitchId); + PowsyblException exception = assertThrows(PowsyblException.class, () -> closeSwitch.apply(network)); + assertEquals("Switch 'dummy' not found", exception.getMessage()); + } + + @Test + void testDryRun() { + // CloseSwitch - Passing dryRun + CloseSwitch closeSwitchPassing = new CloseSwitch(existingSwitchId); + assertTrue(closeSwitchPassing.dryRun(network)); + + // Useful methods for dry run + assertFalse(closeSwitchPassing.hasImpactOnNetwork()); + assertTrue(closeSwitchPassing.isLocalDryRunPossible()); + + // CloseSwitch - Failing dryRun + ReportNode reportNodeCloseSwitch = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + CloseSwitch closeSwitchFailing = new CloseSwitch(notExistingSwitchId); + assertFalse(closeSwitchFailing.dryRun(network, reportNodeCloseSwitch)); + assertEquals("Dry-run failed for CloseSwitch. The issue is: Switch 'dummy' not found", + reportNodeCloseSwitch.getChildren().get(0).getChildren().get(0).getMessage()); + + // OpenSwitch - Passing dryRun + OpenSwitch openSwitchPassing = new OpenSwitch(existingSwitchId); + assertTrue(openSwitchPassing.dryRun(network)); + + // OpenSwitch - Failing dryRun + ReportNode reportNodeOpenSwitch = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + OpenSwitch openSwitchFailing = new OpenSwitch(notExistingSwitchId); + assertFalse(openSwitchFailing.dryRun(network, reportNodeOpenSwitch)); + assertEquals("Dry-run failed for OpenSwitch. The issue is: Switch 'dummy' not found", + reportNodeOpenSwitch.getChildren().get(0).getChildren().get(0).getMessage()); } } diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TestBatteryModification.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TestBatteryModification.java deleted file mode 100644 index 30d965b5baf..00000000000 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TestBatteryModification.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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.iidm.modification; - -import com.powsybl.iidm.network.Battery; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.test.BatteryNetworkFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Nicolas PIERRE {@literal } - */ -class TestBatteryModification { - - private Network network; - private Battery battery; - - @BeforeEach - public void setUp() { - network = BatteryNetworkFactory.create(); - assertTrue(network.getBatteryCount() > 0); - battery = network.getBatteries().iterator().next(); - battery.setTargetQ(0); - } - - @Test - void testBatteryModification() { - BatteryModification batteryModification = new BatteryModification(battery.getId(), 1.0, null); - assertEquals(9999.99, battery.getTargetP()); - batteryModification.apply(network); - assertEquals(1., battery.getTargetP()); - - BatteryModification batteryModification2 = new BatteryModification(battery.getId(), null, 2.0); - assertEquals(0, battery.getTargetQ()); - batteryModification2.apply(network); - assertEquals(2., battery.getTargetQ()); - } -} diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TestThreeWindingsTransformerModification.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ThreeWindingsTransformerModificationTest.java similarity index 75% rename from iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TestThreeWindingsTransformerModification.java rename to iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ThreeWindingsTransformerModificationTest.java index 4b84c7ec7f6..a16ecd0b588 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/TestThreeWindingsTransformerModification.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/ThreeWindingsTransformerModificationTest.java @@ -22,7 +22,7 @@ * @author Luma Zamarreño {@literal } * @author José Antonio Marqués {@literal } */ -class TestThreeWindingsTransformerModification { +class ThreeWindingsTransformerModificationTest { private Network network; private ThreeWindingsTransformer t3wt; @@ -72,4 +72,24 @@ void testGetters() { assertEquals(t3wt.getId(), t3wtModification.getTransformerId()); assertEquals(135, t3wtModification.getRatedU0()); } + + @Test + void testDryRun() { + // Passing dryRun + ThreeWindingsTransformerModification t3wtModification = new ThreeWindingsTransformerModification(t3wt.getId(), 135.0); + assertTrue(t3wtModification.dryRun(network)); + + // Useful methods for dry run + assertFalse(t3wtModification.hasImpactOnNetwork()); + assertTrue(t3wtModification.isLocalDryRunPossible()); + + // Failing dryRun + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + ThreeWindingsTransformerModification modificationFailing = new ThreeWindingsTransformerModification("TWT_NOT_EXISTING", 135.0); + assertFalse(modificationFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for ThreeWindingsTransformerModification. The issue is: ThreeWindingsTransformer 'TWT_NOT_EXISTING' not found", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + } } diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/topology/CreateFeederBayTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/topology/CreateFeederBayTest.java index 2ba221bfe82..49bd7036c85 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/topology/CreateFeederBayTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/topology/CreateFeederBayTest.java @@ -496,4 +496,72 @@ void testCreateLoadWithReportNodeWithoutExtensions() throws IOException { .apply(network, reportNode); testReportNode(reportNode, "/reportNode/create-load-NB-without-extensions-report.txt"); } + + @Test + void testDryRun() { + Network network = Network.read("testNetworkNodeBreaker.xiidm", getClass().getResourceAsStream("/testNetworkNodeBreaker.xiidm")); + GeneratorAdder generatorAdder = network.getVoltageLevel("vl1").newGenerator() + .setId("newGenerator") + .setVoltageRegulatorOn(true) + .setMaxP(9999) + .setMinP(-9999) + .setTargetV(25.5) + .setTargetP(600) + .setTargetQ(300) + .setRatedS(10) + .setEnergySource(EnergySource.NUCLEAR) + .setEnsureIdUnicity(true); + NetworkModification modification = new CreateFeederBayBuilder() + .withInjectionAdder(generatorAdder) + .withBusOrBusbarSectionId("bbs1") + .withInjectionPositionOrder(71) + .withInjectionDirection(BOTTOM) + .build(); + assertTrue(modification.dryRun(network)); + + // Useful methods for dry run + assertTrue(modification.hasImpactOnNetwork()); + assertTrue(modification.isLocalDryRunPossible()); + + // Failing dry run + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + CreateFeederBay modificationFailing = new CreateFeederBayBuilder() + .withInjectionAdder(generatorAdder) + .withBusOrBusbarSectionId("dummy") + .withInjectionPositionOrder(71) + .withInjectionDirection(BOTTOM) + .build(); + assertFalse(modificationFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for AbstractCreateConnectableFeederBays. The issue is: Bus or busbar section 'dummy' not found", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + + // Failing dry run + reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + modificationFailing = new CreateFeederBayBuilder() + .withInjectionAdder(generatorAdder) + .withBusOrBusbarSectionId("bbs1") + .withInjectionDirection(BOTTOM) + .build(); + assertFalse(modificationFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for AbstractCreateConnectableFeederBays. The issue is: Position order is null for attachment in node-breaker voltage level vl1", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + + // Failing dry run + reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + modificationFailing = new CreateFeederBayBuilder() + .withInjectionAdder(generatorAdder) + .withBusOrBusbarSectionId("bbs1") + .withInjectionPositionOrder(-5) + .withInjectionDirection(BOTTOM) + .build(); + assertFalse(modificationFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for AbstractCreateConnectableFeederBays. The issue is: Position order is negative for attachment in node-breaker voltage level vl1: -5", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + } } diff --git a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/topology/CreateLineOnLineTest.java b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/topology/CreateLineOnLineTest.java index 69d169a2f75..f73bdd9d7f1 100644 --- a/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/topology/CreateLineOnLineTest.java +++ b/iidm/iidm-modification/src/test/java/com/powsybl/iidm/modification/topology/CreateLineOnLineTest.java @@ -215,4 +215,48 @@ private static LineAdder createLineAdder(Line line, Network network) { .setB2(line.getB2()) .setG2(line.getG2()); } + + @Test + void testDryRun() { + Network network = createNbNetworkWithBusbarSection(); + Line line = network.getLine("CJ"); + LineAdder adder = createLineAdder(line, network); + NetworkModification modification = new CreateLineOnLineBuilder() + .withBusbarSectionOrBusId(BBS) + .withLine(line) + .withLineAdder(adder) + .build(); + assertTrue(modification.dryRun(network)); + + // Useful methods for dry run + assertTrue(modification.hasImpactOnNetwork()); + assertTrue(modification.isLocalDryRunPossible()); + + // Failing dry run + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + NetworkModification modificationFailing = new CreateLineOnLineBuilder() + .withBusbarSectionOrBusId("dummy") + .withLine(line) + .withLineAdder(adder) + .build(); + assertFalse(modificationFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for AbstractLineConnectionModification. The issue is: Bus or busbar section 'dummy' not found", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + + // Failing dry run + reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("", "") + .build(); + modificationFailing = new CreateLineOnLineBuilder() + .withBusbarSectionOrBusId(BBS) + .withLine(line) + .withPositionPercent(Double.NaN) + .withLineAdder(adder) + .build(); + assertFalse(modificationFailing.dryRun(network, reportNode)); + assertEquals("Dry-run failed for AbstractLineConnectionModification. The issue is: Percent should not be undefined", + reportNode.getChildren().get(0).getChildren().get(0).getMessage()); + } } diff --git a/security-analysis/security-analysis-default/src/test/java/com/powsybl/security/impl/SecurityAnalysisTest.java b/security-analysis/security-analysis-default/src/test/java/com/powsybl/security/impl/SecurityAnalysisTest.java index 2271f5c0e2a..c459255c655 100644 --- a/security-analysis/security-analysis-default/src/test/java/com/powsybl/security/impl/SecurityAnalysisTest.java +++ b/security-analysis/security-analysis-default/src/test/java/com/powsybl/security/impl/SecurityAnalysisTest.java @@ -66,6 +66,11 @@ public void apply(Network network, NamingStrategy namingStrategy, boolean throwE network.getLine("NHV1_NHV2_1").getTerminal2().setP(600.0); ((Bus) network.getIdentifiable("NHV2")).setV(380.0).setAngle(-0.10); } + + @Override + protected boolean applyDryRun(Network network, NamingStrategy namingStrategy, ComputationManager computationManager, ReportNode reportNode) { + return true; + } } private FileSystem fileSystem;