diff --git a/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractHvdcAcEmulationFlowEquationTerm.java b/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractHvdcAcEmulationFlowEquationTerm.java index 1778de9d10..8c7f539a05 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractHvdcAcEmulationFlowEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractHvdcAcEmulationFlowEquationTerm.java @@ -14,12 +14,15 @@ import com.powsybl.openloadflow.network.LfHvdc; import java.util.List; +import java.util.Objects; /** * @author Anne Tilloy */ public abstract class AbstractHvdcAcEmulationFlowEquationTerm extends AbstractNamedEquationTerm { + protected final LfHvdc hvdc; + protected final Variable ph1Var; protected final Variable ph2Var; @@ -30,17 +33,16 @@ public abstract class AbstractHvdcAcEmulationFlowEquationTerm extends AbstractNa protected final double p0; - protected final LfHvdc hvdc; - protected final double lossFactor1; protected final double lossFactor2; protected AbstractHvdcAcEmulationFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfBus bus2, VariableSet variableSet) { + super(!Objects.requireNonNull(hvdc).isDisabled()); + this.hvdc = hvdc; ph1Var = variableSet.getVariable(bus1.getNum(), AcVariableType.BUS_PHI); ph2Var = variableSet.getVariable(bus2.getNum(), AcVariableType.BUS_PHI); variables = List.of(ph1Var, ph2Var); - this.hvdc = hvdc; k = hvdc.getDroop() * 180 / Math.PI; p0 = hvdc.getP0(); lossFactor1 = hvdc.getConverterStation1().getLossFactor() / 100; diff --git a/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractShuntCompensatorEquationTerm.java b/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractShuntCompensatorEquationTerm.java index 26cd7df585..82683b339a 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractShuntCompensatorEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractShuntCompensatorEquationTerm.java @@ -25,7 +25,8 @@ public abstract class AbstractShuntCompensatorEquationTerm extends AbstractNamed protected final Variable vVar; protected AbstractShuntCompensatorEquationTerm(LfShunt shunt, LfBus bus, VariableSet variableSet) { - this.shunt = Objects.requireNonNull(shunt); + super(!Objects.requireNonNull(shunt).isDisabled()); + this.shunt = shunt; Objects.requireNonNull(bus); Objects.requireNonNull(variableSet); vVar = variableSet.getVariable(bus.getNum(), AcVariableType.BUS_V); diff --git a/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystem.java b/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystem.java index 9cbb8f376e..934d1e6ef0 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystem.java +++ b/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystem.java @@ -26,13 +26,13 @@ private AcEquationSystem() { private static void createBusEquation(LfBus bus, EquationSystem equationSystem, AcEquationSystemCreationParameters creationParameters) { - var p = equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_P); + var p = equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_P); bus.setP(p); - var q = equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_Q); + var q = equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_Q); bus.setQ(q); if (bus.isSlack()) { - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_PHI) + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_PHI) .addTerm(equationSystem.getVariable(bus.getNum(), AcVariableType.BUS_PHI) .createTerm()); p.setActive(false); @@ -50,7 +50,7 @@ private static void createBusEquation(LfBus bus, // deactivated if (!equationSystem.hasEquation(bus.getNum(), AcEquationType.BUS_TARGET_V)) { EquationTerm vTerm = equationSystem.getVariable(bus.getNum(), AcVariableType.BUS_V).createTerm(); - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_V) + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_V) .addTerm(vTerm) .setActive(false); bus.setCalculatedV(vTerm); @@ -94,18 +94,18 @@ private static void createLocalVoltageControlEquation(LfBus bus, // we only support one generator controlling voltage with a non zero slope at a bus. // equation is: V + slope * qSVC = targetV // which is modeled here with: V + slope * (sum_branch qBranch) = TargetV - slope * qLoads + slope * qGenerators - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_V_WITH_SLOPE) + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_V_WITH_SLOPE) .addTerm(vTerm) .addTerms(createReactiveTerms(bus, equationSystem.getVariableSet(), creationParameters) .stream() .map(term -> term.multiply(slope)) .collect(Collectors.toList())); } else { - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_V) + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_V) .addTerm(vTerm); } - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_Q); + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_Q); } private static void createReactivePowerControlBranchEquation(LfBranch branch, LfBus bus1, LfBus bus2, EquationSystem equationSystem, @@ -115,11 +115,11 @@ private static void createReactivePowerControlBranchEquation(LfBranch branch, Lf EquationTerm q = rpc.getControlledSide() == ReactivePowerControl.ControlledSide.ONE ? new ClosedBranchSide1ReactiveFlowEquationTerm(branch, bus1, bus2, equationSystem.getVariableSet(), deriveA1, deriveR1) : new ClosedBranchSide2ReactiveFlowEquationTerm(branch, bus1, bus2, equationSystem.getVariableSet(), deriveA1, deriveR1); - equationSystem.createEquation(branch.getNum(), AcEquationType.BRANCH_TARGET_Q) + equationSystem.createEquation(branch, AcEquationType.BRANCH_TARGET_Q) .addTerm(q); // if bus has both voltage and remote reactive power controls, then only voltage control has been kept - equationSystem.createEquation(rpc.getControllerBus().getNum(), AcEquationType.BUS_TARGET_Q); + equationSystem.createEquation(rpc.getControllerBus(), AcEquationType.BUS_TARGET_Q); updateReactivePowerControlBranchEquations(rpc, equationSystem); }); @@ -139,15 +139,15 @@ public static void updateReactivePowerControlBranchEquations(ReactivePowerContro private static void createShuntEquations(LfBus bus, EquationSystem equationSystem) { bus.getShunt().ifPresent(shunt -> { ShuntCompensatorReactiveFlowEquationTerm q = new ShuntCompensatorReactiveFlowEquationTerm(shunt, bus, equationSystem.getVariableSet(), false); - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_Q).addTerm(q); + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_Q).addTerm(q); ShuntCompensatorActiveFlowEquationTerm p = new ShuntCompensatorActiveFlowEquationTerm(shunt, bus, equationSystem.getVariableSet()); - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_P).addTerm(p); + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_P).addTerm(p); }); bus.getControllerShunt().ifPresent(shunt -> { ShuntCompensatorReactiveFlowEquationTerm q = new ShuntCompensatorReactiveFlowEquationTerm(shunt, bus, equationSystem.getVariableSet(), shunt.hasVoltageControlCapability()); - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_Q).addTerm(q); + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_Q).addTerm(q); ShuntCompensatorActiveFlowEquationTerm p = new ShuntCompensatorActiveFlowEquationTerm(shunt, bus, equationSystem.getVariableSet()); - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_P).addTerm(p); + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_P).addTerm(p); }); } @@ -158,12 +158,12 @@ private static void createRemoteVoltageControlEquations(VoltageControl voltageCo // create voltage equation at voltage controlled bus EquationTerm vTerm = equationSystem.getVariable(controlledBus.getNum(), AcVariableType.BUS_V).createTerm(); - equationSystem.createEquation(controlledBus.getNum(), AcEquationType.BUS_TARGET_V) + equationSystem.createEquation(controlledBus, AcEquationType.BUS_TARGET_V) .addTerm(vTerm); controlledBus.setCalculatedV(vTerm); for (LfBus controllerBus : voltageControl.getControllerBuses()) { - equationSystem.createEquation(controllerBus.getNum(), AcEquationType.BUS_TARGET_Q); + equationSystem.createEquation(controllerBus, AcEquationType.BUS_TARGET_Q); // create reactive power distribution equations at voltage controller buses @@ -172,7 +172,7 @@ private static void createRemoteVoltageControlEquations(VoltageControl voltageCo // 0 = qPercent_i * sum_j(q_j) - q_i // which can be rewritten in a more simple way // 0 = (qPercent_i - 1) * q_i + qPercent_i * sum_j(q_j) where j are all the voltage controller buses except i - Equation zero = equationSystem.createEquation(controllerBus.getNum(), AcEquationType.DISTR_Q) + Equation zero = equationSystem.createEquation(controllerBus, AcEquationType.DISTR_Q) .addTerms(createReactiveTerms(controllerBus, equationSystem.getVariableSet(), creationParameters).stream() .map(term -> term.multiply(() -> controllerBus.getRemoteVoltageControlReactivePercent() - 1)) .collect(Collectors.toList())); @@ -326,7 +326,7 @@ private static void createNonImpedantBranch(LfBranch branch, LfBus bus1, LfBus b .createTerm(); EquationTerm bus2vTerm = equationSystem.getVariable(bus2.getNum(), AcVariableType.BUS_V) .createTerm(); - equationSystem.createEquation(branch.getNum(), AcEquationType.ZERO_V) + equationSystem.createEquation(branch, AcEquationType.ZERO_V) .addTerm(vTerm) .addTerm(bus2vTerm.multiply(-rho)); bus1.setCalculatedV(vTerm); @@ -344,9 +344,9 @@ private static void createNonImpedantBranch(LfBranch branch, LfBus bus1, LfBus b // create an inactive dummy reactive power target equation set to zero that could be activated // on case of switch opening - equationSystem.createEquation(branch.getNum(), AcEquationType.DUMMY_TARGET_Q) + equationSystem.createEquation(branch, AcEquationType.DUMMY_TARGET_Q) .addTerm(dummyQ.createTerm()) - .setActive(false); + .setActive(branch.isDisabled()); // inverted logic } else { // nothing to do in case of v1 and v2 are found, we just have to ensure // target v are equals. @@ -357,7 +357,7 @@ private static void createNonImpedantBranch(LfBranch branch, LfBus bus1, LfBus b if (!(hasPhi1 && hasPhi2)) { // create voltage angle coupling equation // alpha = phi1 - phi2 - equationSystem.createEquation(branch.getNum(), AcEquationType.ZERO_PHI) + equationSystem.createEquation(branch, AcEquationType.ZERO_PHI) .addTerm(equationSystem.getVariable(bus1.getNum(), AcVariableType.BUS_PHI).createTerm()) .addTerm(equationSystem.getVariable(bus2.getNum(), AcVariableType.BUS_PHI).createTerm() .minus()); @@ -376,9 +376,9 @@ private static void createNonImpedantBranch(LfBranch branch, LfBus bus1, LfBus b // create an inactive dummy active power target equation set to zero that could be activated // on case of switch opening - equationSystem.createEquation(branch.getNum(), AcEquationType.DUMMY_TARGET_P) + equationSystem.createEquation(branch, AcEquationType.DUMMY_TARGET_P) .addTerm(dummyP.createTerm()) - .setActive(false); + .setActive(branch.isDisabled()); // inverted logic } else { throw new IllegalStateException("Cannot happen because only there is one slack bus per model"); } @@ -390,7 +390,7 @@ private static void createTransformerPhaseControlEquations(LfBranch branch, LfBu EquationTerm a1 = equationSystem.getVariable(branch.getNum(), AcVariableType.BRANCH_ALPHA1) .createTerm(); branch.setA1(a1); - equationSystem.createEquation(branch.getNum(), AcEquationType.BRANCH_TARGET_ALPHA1) + equationSystem.createEquation(branch, AcEquationType.BRANCH_TARGET_ALPHA1) .addTerm(a1); } @@ -404,7 +404,7 @@ private static void createTransformerPhaseControlEquations(LfBranch branch, LfBu EquationTerm p = phaseControl.getControlledSide() == DiscretePhaseControl.ControlledSide.ONE ? new ClosedBranchSide1ActiveFlowEquationTerm(branch, bus1, bus2, equationSystem.getVariableSet(), deriveA1, deriveR1) : new ClosedBranchSide2ActiveFlowEquationTerm(branch, bus1, bus2, equationSystem.getVariableSet(), deriveA1, deriveR1); - equationSystem.createEquation(branch.getNum(), AcEquationType.BRANCH_TARGET_P) + equationSystem.createEquation(branch, AcEquationType.BRANCH_TARGET_P) .addTerm(p) .setActive(false); // by default BRANCH_TARGET_ALPHA1 is active and BRANCH_TARGET_P inactive } @@ -440,7 +440,7 @@ private static void createTransformerVoltageControlEquations(LfBus bus, Equation // create voltage target equation at controlled bus EquationTerm vTerm = equationSystem.getVariable(bus.getNum(), AcVariableType.BUS_V) .createTerm(); - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_V).addTerm(vTerm); + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_V).addTerm(vTerm); bus.setCalculatedV(vTerm); // add transformer ratio distribution equations @@ -448,7 +448,7 @@ private static void createTransformerVoltageControlEquations(LfBus bus, Equation // we also create an equation per controller that will be used later to maintain R1 variable constant for (LfBranch controllerBranch : voltageControl.getControllers()) { - equationSystem.createEquation(controllerBranch.getNum(), AcEquationType.BRANCH_TARGET_RHO1) + equationSystem.createEquation(controllerBranch, AcEquationType.BRANCH_TARGET_RHO1) .addTerm(equationSystem.getVariable(controllerBranch.getNum(), AcVariableType.BRANCH_RHO1).createTerm()); } @@ -467,7 +467,7 @@ public static void createR1DistributionEquations(List controllerBranch // 0 = (1 / controller_count - 1) * r1_i + sum_j(r1_j) / controller_count where j are all the controller branches except i EquationTerm r1 = equationSystem.getVariable(controllerBranch.getNum(), AcVariableType.BRANCH_RHO1) .createTerm(); - Equation zero = equationSystem.createEquation(controllerBranch.getNum(), AcEquationType.DISTR_RHO) + Equation zero = equationSystem.createEquation(controllerBranch, AcEquationType.DISTR_RHO) .addTerm(r1.multiply(() -> 1d / controllerBranches.stream().filter(b -> !b.isDisabled()).count() - 1)); for (LfBranch otherControllerBranch : controllerBranches) { if (otherControllerBranch != controllerBranch) { @@ -541,7 +541,7 @@ private static void createShuntVoltageControlEquations(LfBus bus, EquationSystem .ifPresent(voltageControl -> { EquationTerm vTerm = equationSystem.getVariable(bus.getNum(), AcVariableType.BUS_V) .createTerm(); - equationSystem.createEquation(bus.getNum(), AcEquationType.BUS_TARGET_V).addTerm(vTerm); + equationSystem.createEquation(bus, AcEquationType.BUS_TARGET_V).addTerm(vTerm); bus.setCalculatedV(vTerm); // add shunt distribution equations @@ -550,7 +550,7 @@ private static void createShuntVoltageControlEquations(LfBus bus, EquationSystem for (LfShunt controllerShunt : voltageControl.getControllers()) { // we also create an equation that will be used later to maintain B variable constant // this equation is now inactive - equationSystem.createEquation(controllerShunt.getNum(), AcEquationType.SHUNT_TARGET_B) + equationSystem.createEquation(controllerShunt, AcEquationType.SHUNT_TARGET_B) .addTerm(equationSystem.getVariable(controllerShunt.getNum(), AcVariableType.SHUNT_B).createTerm()); } @@ -568,7 +568,7 @@ public static void createShuntSusceptanceDistributionEquations(List con // 0 = (1 / controller_count - 1) * b_i + sum_j(b_j) / controller_count where j are all the controller buses except i EquationTerm shuntB = equationSystem.getVariable(controllerShunt.getNum(), AcVariableType.SHUNT_B) .createTerm(); - Equation zero = equationSystem.createEquation(controllerShunt.getNum(), AcEquationType.DISTR_SHUNT_B) + Equation zero = equationSystem.createEquation(controllerShunt, AcEquationType.DISTR_SHUNT_B) .addTerm(shuntB.multiply(() -> 1d / controllerShunts.stream().filter(b -> !b.isDisabled()).count() - 1)); for (LfShunt otherControllerShunt : controllerShunts) { if (otherControllerShunt != controllerShunt) { diff --git a/src/main/java/com/powsybl/openloadflow/equations/AbstractBranchEquationTerm.java b/src/main/java/com/powsybl/openloadflow/equations/AbstractBranchEquationTerm.java index d71d0e454a..b8ccc56120 100644 --- a/src/main/java/com/powsybl/openloadflow/equations/AbstractBranchEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/equations/AbstractBranchEquationTerm.java @@ -19,7 +19,8 @@ public abstract class AbstractBranchEquationTerm & Quantity, E protected final LfBranch branch; protected AbstractBranchEquationTerm(LfBranch branch) { - this.branch = Objects.requireNonNull(branch); + super(!Objects.requireNonNull(branch).isDisabled()); + this.branch = branch; } @Override diff --git a/src/main/java/com/powsybl/openloadflow/equations/AbstractBusEquationTerm.java b/src/main/java/com/powsybl/openloadflow/equations/AbstractBusEquationTerm.java index 1f107e6e97..6f0529df9b 100644 --- a/src/main/java/com/powsybl/openloadflow/equations/AbstractBusEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/equations/AbstractBusEquationTerm.java @@ -19,7 +19,8 @@ public abstract class AbstractBusEquationTerm & Quantity, E ex protected final LfBus bus; protected AbstractBusEquationTerm(LfBus bus) { - this.bus = Objects.requireNonNull(bus); + super(!Objects.requireNonNull(bus).isDisabled()); + this.bus = bus; } @Override diff --git a/src/main/java/com/powsybl/openloadflow/equations/AbstractEquationTerm.java b/src/main/java/com/powsybl/openloadflow/equations/AbstractEquationTerm.java index 66fa2e8b9a..884d00febe 100644 --- a/src/main/java/com/powsybl/openloadflow/equations/AbstractEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/equations/AbstractEquationTerm.java @@ -17,12 +17,20 @@ public abstract class AbstractEquationTerm & Quantity, E exten private Equation equation; - private boolean active = true; + private boolean active; protected StateVector sv; protected EquationTerm self = this; + protected AbstractEquationTerm() { + this(true); + } + + protected AbstractEquationTerm(boolean active) { + this.active = active; + } + @Override public void setStateVector(StateVector sv) { this.sv = Objects.requireNonNull(sv); diff --git a/src/main/java/com/powsybl/openloadflow/equations/AbstractNamedEquationTerm.java b/src/main/java/com/powsybl/openloadflow/equations/AbstractNamedEquationTerm.java index 5842709846..3a918e9743 100644 --- a/src/main/java/com/powsybl/openloadflow/equations/AbstractNamedEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/equations/AbstractNamedEquationTerm.java @@ -15,6 +15,10 @@ */ public abstract class AbstractNamedEquationTerm & Quantity, E extends Enum & Quantity> extends AbstractEquationTerm { + protected AbstractNamedEquationTerm(boolean active) { + super(active); + } + protected abstract String getName(); @Override diff --git a/src/main/java/com/powsybl/openloadflow/equations/Equation.java b/src/main/java/com/powsybl/openloadflow/equations/Equation.java index d399ebce3f..8a898a06a1 100644 --- a/src/main/java/com/powsybl/openloadflow/equations/Equation.java +++ b/src/main/java/com/powsybl/openloadflow/equations/Equation.java @@ -66,11 +66,12 @@ public boolean isActive() { return active; } - public void setActive(boolean active) { + public Equation setActive(boolean active) { if (active != this.active) { this.active = active; equationSystem.notifyEquationChange(this, active ? EquationEventType.EQUATION_ACTIVATED : EquationEventType.EQUATION_DEACTIVATED); } + return this; } public Equation addTerm(EquationTerm term) { diff --git a/src/main/java/com/powsybl/openloadflow/equations/EquationSystem.java b/src/main/java/com/powsybl/openloadflow/equations/EquationSystem.java index c48b4d6c41..6d4c2a3236 100644 --- a/src/main/java/com/powsybl/openloadflow/equations/EquationSystem.java +++ b/src/main/java/com/powsybl/openloadflow/equations/EquationSystem.java @@ -8,6 +8,7 @@ import com.powsybl.commons.PowsyblException; import com.powsybl.openloadflow.network.ElementType; +import com.powsybl.openloadflow.network.LfElement; import com.powsybl.openloadflow.network.LfNetwork; import org.apache.commons.lang3.tuple.Pair; @@ -101,6 +102,21 @@ public > T getEquationTerm(ElementType elementType, .orElseThrow(() -> new PowsyblException("Equation term not found")); } + public Equation createEquation(LfElement element, E type) { + Objects.requireNonNull(element); + Objects.requireNonNull(type); + if (element.getType() != type.getElementType()) { + throw new PowsyblException("Incorrect equation type: " + type); + } + Pair p = Pair.of(element.getNum(), type); + Equation equation = equations.get(p); + if (equation == null) { + equation = addEquation(p) + .setActive(!element.isDisabled()); + } + return equation; + } + public Equation createEquation(int num, E type) { Pair p = Pair.of(num, type); Equation equation = equations.get(p); diff --git a/src/main/java/com/powsybl/openloadflow/network/BranchState.java b/src/main/java/com/powsybl/openloadflow/network/BranchState.java index fb841f45e0..7fb2c50bb4 100644 --- a/src/main/java/com/powsybl/openloadflow/network/BranchState.java +++ b/src/main/java/com/powsybl/openloadflow/network/BranchState.java @@ -11,16 +11,21 @@ */ public class BranchState extends ElementState { - private final double a1; - private final double r1; + private double a1 = Double.NaN; + private double r1 = Double.NaN; private final boolean phaseControlEnabled; private final boolean voltageControlEnabled; + private Integer tapPosition; public BranchState(LfBranch branch) { super(branch); PiModel piModel = branch.getPiModel(); - a1 = piModel.getA1(); - r1 = piModel.getR1(); + if (piModel instanceof PiModelArray) { + tapPosition = piModel.getTapPosition(); + } else { + a1 = piModel.getA1(); + r1 = piModel.getR1(); + } phaseControlEnabled = branch.isPhaseControlEnabled(); voltageControlEnabled = branch.isVoltageControlEnabled(); } @@ -29,8 +34,15 @@ public BranchState(LfBranch branch) { public void restore() { super.restore(); PiModel piModel = element.getPiModel(); - piModel.setA1(a1); - piModel.setR1(r1); + if (tapPosition != null) { + piModel.setTapPosition(tapPosition); + } + if (!Double.isNaN(a1)) { + piModel.setA1(a1); + } + if (!Double.isNaN(r1)) { + piModel.setR1(r1); + } element.setPhaseControlEnabled(phaseControlEnabled); element.setVoltageControlEnabled(voltageControlEnabled); } diff --git a/src/main/java/com/powsybl/openloadflow/network/LfAction.java b/src/main/java/com/powsybl/openloadflow/network/LfAction.java new file mode 100644 index 0000000000..55cf76d7c2 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/LfAction.java @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.openloadflow.network; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.openloadflow.graph.GraphConnectivity; +import com.powsybl.security.action.Action; +import com.powsybl.security.action.LineConnectionAction; +import com.powsybl.security.action.PhaseTapChangerTapPositionAction; +import com.powsybl.security.action.SwitchAction; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * @author Anne Tilloy + */ +public class LfAction { + + private static final class TapPositionChange { + + private final LfBranch branch; + + private final int value; + + private final boolean relative; + + private TapPositionChange(LfBranch branch, int value, boolean relative) { + this.branch = Objects.requireNonNull(branch); + this.value = value; + this.relative = relative; + } + + public LfBranch getBranch() { + return branch; + } + + public int getValue() { + return value; + } + + public boolean isRelative() { + return relative; + } + } + + private final String id; + + private LfBranch disabledBranch; // switch to open + + private LfBranch enabledBranch; // switch to close + + private TapPositionChange tapPositionChange; + + public LfAction(Action action, LfNetwork network) { + this.id = Objects.requireNonNull(action.getId()); + Objects.requireNonNull(network); + LfBranch branch; + switch (action.getType()) { + case SwitchAction.NAME: + SwitchAction switchAction = (SwitchAction) action; + branch = network.getBranchById(switchAction.getSwitchId()); + checkBranch(branch, switchAction.getSwitchId()); + if (switchAction.isOpen()) { + disabledBranch = branch; + } else { + enabledBranch = branch; + } + break; + case LineConnectionAction.NAME: + LineConnectionAction lineConnectionAction = (LineConnectionAction) action; + branch = network.getBranchById(lineConnectionAction.getLineId()); + checkBranch(branch, lineConnectionAction.getLineId()); + if (lineConnectionAction.isOpenSide1() && lineConnectionAction.isOpenSide2()) { + disabledBranch = branch; + } else { + throw new UnsupportedOperationException("Line connection action: only open line at both sides is supported yet."); + } + break; + case PhaseTapChangerTapPositionAction.NAME: + PhaseTapChangerTapPositionAction phaseTapChangerTapPositionAction = (PhaseTapChangerTapPositionAction) action; + branch = network.getBranchById(phaseTapChangerTapPositionAction.getTransformerId()); // only two windings transformer for the moment. + checkBranch(branch, phaseTapChangerTapPositionAction.getTransformerId()); // how to check that it is really a phase tap changer? + if (branch.getPiModel() instanceof SimplePiModel) { + throw new UnsupportedOperationException("Phase tap changer tap connection action: only one tap in the branch {" + phaseTapChangerTapPositionAction.getTransformerId() + "}"); + } else { + tapPositionChange = new TapPositionChange(branch, phaseTapChangerTapPositionAction.getValue(), phaseTapChangerTapPositionAction.isRelativeValue()); + } + break; + default: + throw new UnsupportedOperationException("Unsupported action type: " + action.getType()); + } + } + + public String getId() { + return id; + } + + public LfBranch getDisabledBranch() { + return disabledBranch; + } + + public LfBranch getEnabledBranch() { + return enabledBranch; + } + + public static void apply(List actions, LfNetwork network, LfContingency contingency) { + Objects.requireNonNull(actions); + Objects.requireNonNull(network); + + // first process connectivity part of actions + updateConnectivity(actions, network, contingency); + + // then process remaining changes of actions + for (LfAction action : actions) { + action.apply(); + } + } + + private static void updateConnectivity(List actions, LfNetwork network, LfContingency contingency) { + GraphConnectivity connectivity = network.getConnectivity(); + + // re-update connectivity according to post contingency state (revert after LfContingency apply) + connectivity.startTemporaryChanges(); + contingency.getDisabledBranches().forEach(connectivity::removeEdge); + + // update connectivity according to post action state + connectivity.startTemporaryChanges(); + for (LfAction action : actions) { + action.updateConnectivity(connectivity); + } + + // add to action description buses and branches that won't be part of the main connected + // component in post action state. + Set removedBuses = connectivity.getVerticesRemovedFromMainComponent(); + removedBuses.forEach(bus -> bus.setDisabled(true)); + Set removedBranches = new HashSet<>(connectivity.getEdgesRemovedFromMainComponent()); + // we should manage branches open at one side. + for (LfBus bus : removedBuses) { + bus.getBranches().stream().filter(b -> !b.isConnectedAtBothSides()).forEach(removedBranches::add); + } + removedBranches.forEach(branch -> branch.setDisabled(true)); + + // add to action description buses and branches that will be part of the main connected + // component in post action state. + Set addedBuses = connectivity.getVerticesAddedToMainComponent(); + addedBuses.forEach(bus -> bus.setDisabled(false)); + Set addedBranches = new HashSet<>(connectivity.getEdgesAddedToMainComponent()); + // we should manage branches open at one side. + for (LfBus bus : addedBuses) { + bus.getBranches().stream().filter(b -> !b.isConnectedAtBothSides()).forEach(addedBranches::add); + } + addedBranches.forEach(branch -> branch.setDisabled(false)); + + // reset connectivity to discard post contingency connectivity and post action connectivity + connectivity.undoTemporaryChanges(); + connectivity.undoTemporaryChanges(); + } + + public void updateConnectivity(GraphConnectivity connectivity) { + if (disabledBranch != null && disabledBranch.getBus1() != null && disabledBranch.getBus2() != null) { + connectivity.removeEdge(disabledBranch); + } + if (enabledBranch != null) { + connectivity.addEdge(enabledBranch.getBus1(), enabledBranch.getBus2(), enabledBranch); + } + } + + public void apply() { + if (tapPositionChange != null) { + LfBranch branch = tapPositionChange.getBranch(); + int tapPosition = branch.getPiModel().getTapPosition(); + int value = tapPositionChange.getValue(); + int newTapPosition = tapPositionChange.isRelative() ? tapPosition + value : value; + branch.getPiModel().setTapPosition(newTapPosition); + } + } + + private static void checkBranch(LfBranch branch, String branchId) { + if (branch == null) { + throw new PowsyblException("Branch " + branchId + " not found in the network"); + } + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/LfNetwork.java b/src/main/java/com/powsybl/openloadflow/network/LfNetwork.java index fc6e7b78de..1febf6fa31 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfNetwork.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfNetwork.java @@ -546,6 +546,7 @@ public GraphConnectivity getConnectivity() { getBranches().stream() .filter(b -> b.getBus1() != null && b.getBus2() != null) .forEach(b -> connectivity.addEdge(b.getBus1(), b.getBus2(), b)); + connectivity.setMainComponentVertex(getSlackBus()); } return connectivity; } diff --git a/src/main/java/com/powsybl/openloadflow/network/PiModel.java b/src/main/java/com/powsybl/openloadflow/network/PiModel.java index 58093d9aa8..cb9d4dd5e7 100644 --- a/src/main/java/com/powsybl/openloadflow/network/PiModel.java +++ b/src/main/java/com/powsybl/openloadflow/network/PiModel.java @@ -81,4 +81,8 @@ enum AllowedDirection { boolean setMinZ(double minZ, boolean dc); void setBranch(LfBranch branch); + + int getTapPosition(); + + PiModel setTapPosition(int tapPosition); } diff --git a/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java b/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java index 13166f99ba..9a40d61f4f 100644 --- a/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java +++ b/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java @@ -252,4 +252,17 @@ public boolean setMinZ(double minZ, boolean dc) { public void setBranch(LfBranch branch) { this.branch = Objects.requireNonNull(branch); } + + @Override + public int getTapPosition() { + return this.tapPosition; + } + + @Override + public PiModel setTapPosition(int tapPosition) { + this.tapPosition = tapPosition; + r1 = Double.NaN; + a1 = Double.NaN; + return this; + } } diff --git a/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java b/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java index 0f01008de6..ef4c179f89 100644 --- a/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java +++ b/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java @@ -24,6 +24,8 @@ public class SimplePiModel implements PiModel { private double r1 = 1; private double a1 = 0; + private static final String NO_TAP_POSITION_ERROR = "No tap position change in simple Pi model implementation"; + @Override public double getR() { return r; @@ -139,12 +141,12 @@ public void roundR1ToClosestTap() { @Override public boolean updateTapPositionA1(Direction direction) { - throw new IllegalStateException("No tap position change in simple Pi model implementation"); + throw new IllegalStateException(NO_TAP_POSITION_ERROR); } @Override public Optional updateTapPositionR1(double deltaR1, int maxTapShift, AllowedDirection allowedDirection) { - throw new IllegalStateException("No tap position change in simple Pi model implementation"); + throw new IllegalStateException(NO_TAP_POSITION_ERROR); } private void rescaleZ(double z) { @@ -174,4 +176,14 @@ public boolean setMinZ(double minZ, boolean dc) { public void setBranch(LfBranch branch) { // nothing to set } + + @Override + public int getTapPosition() { + throw new IllegalStateException(NO_TAP_POSITION_ERROR); + } + + @Override + public PiModel setTapPosition(int tapPosition) { + throw new IllegalStateException(NO_TAP_POSITION_ERROR); + } } diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/Networks.java b/src/main/java/com/powsybl/openloadflow/network/impl/Networks.java index aab9866296..75ebc62de8 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/Networks.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/Networks.java @@ -115,19 +115,26 @@ public static List load(Network network, LfNetworkParameters paramete } public static List load(Network network, LfNetworkParameters networkParameters, - Set switchesToOpen, Reporter reporter) { - if (switchesToOpen.isEmpty()) { + Set switchesToOpen, Set switchesToClose, Reporter reporter) { + if (switchesToOpen.isEmpty() && switchesToClose.isEmpty()) { return load(network, networkParameters, reporter); } else { String tmpVariantId = "olf-tmp-" + UUID.randomUUID(); + String variantId = network.getVariantManager().getWorkingVariantId(); network.getVariantManager().cloneVariant(network.getVariantManager().getWorkingVariantId(), tmpVariantId); + network.getVariantManager().setWorkingVariant(tmpVariantId); try { network.getSwitchStream().filter(sw -> sw.getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) .forEach(sw -> sw.setRetained(false)); - switchesToOpen.forEach(sw -> sw.setRetained(true)); + switchesToOpen.stream().filter(sw -> sw.getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) + .forEach(sw -> sw.setRetained(true)); + switchesToClose.stream().filter(sw -> sw.getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) + .forEach(sw -> sw.setRetained(true)); + switchesToClose.forEach(sw -> sw.setOpen(false)); // in order to be present in the network. return load(network, networkParameters, reporter); } finally { network.getVariantManager().removeVariant(tmpVariantId); + network.getVariantManager().setWorkingVariant(variantId); } } } diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/PropagatedContingency.java b/src/main/java/com/powsybl/openloadflow/network/impl/PropagatedContingency.java index 15709f7783..9f39164655 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/PropagatedContingency.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/PropagatedContingency.java @@ -279,7 +279,6 @@ private static Identifiable getIdentifiable(Network network, ContingencyEleme public Optional toLfContingency(LfNetwork network) { // update connectivity with triggered branches of this network GraphConnectivity connectivity = network.getConnectivity(); - connectivity.setMainComponentVertex(network.getSlackBus()); connectivity.startTemporaryChanges(); branchIdsToOpen.stream() .map(network::getBranchById) diff --git a/src/main/java/com/powsybl/openloadflow/network/util/PreviousValueVoltageInitializer.java b/src/main/java/com/powsybl/openloadflow/network/util/PreviousValueVoltageInitializer.java index 890a9bd48c..bd6986e258 100644 --- a/src/main/java/com/powsybl/openloadflow/network/util/PreviousValueVoltageInitializer.java +++ b/src/main/java/com/powsybl/openloadflow/network/util/PreviousValueVoltageInitializer.java @@ -15,6 +15,18 @@ */ public class PreviousValueVoltageInitializer implements VoltageInitializer { + private final UniformValueVoltageInitializer defaultVoltageInitializer = new UniformValueVoltageInitializer(); + + private final boolean defaultToUniformValue; + + public PreviousValueVoltageInitializer() { + this(false); + } + + public PreviousValueVoltageInitializer(boolean defaultToUniformValue) { + this.defaultToUniformValue = defaultToUniformValue; + } + @Override public void prepare(LfNetwork network) { // nothing to do @@ -24,7 +36,11 @@ public void prepare(LfNetwork network) { public double getMagnitude(LfBus bus) { double v = bus.getV(); if (Double.isNaN(v)) { - throw new PowsyblException("Voltage magnitude is undefined for bus '" + bus.getId() + "'"); + if (defaultToUniformValue) { + return defaultVoltageInitializer.getMagnitude(bus); + } else { + throw new PowsyblException("Voltage magnitude is undefined for bus '" + bus.getId() + "'"); + } } return v; } @@ -33,7 +49,11 @@ public double getMagnitude(LfBus bus) { public double getAngle(LfBus bus) { double angle = bus.getAngle(); if (Double.isNaN(angle)) { - throw new PowsyblException("Voltage angle is undefined for bus '" + bus.getId() + "'"); + if (defaultToUniformValue) { + return defaultVoltageInitializer.getAngle(bus); + } else { + throw new PowsyblException("Voltage angle is undefined for bus '" + bus.getId() + "'"); + } } return angle; } diff --git a/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java index a4503c87d8..9e59f8f3b0 100644 --- a/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java @@ -17,8 +17,10 @@ import com.powsybl.openloadflow.network.LfBus; import com.powsybl.security.SecurityAnalysisParameters; import com.powsybl.security.SecurityAnalysisReport; +import com.powsybl.security.action.Action; import com.powsybl.security.monitor.StateMonitor; import com.powsybl.security.monitor.StateMonitorIndex; +import com.powsybl.security.strategy.OperatorStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,16 +55,17 @@ protected AbstractSecurityAnalysis(Network network, MatrixFactory matrixFactory, } public CompletableFuture run(String workingVariantId, SecurityAnalysisParameters securityAnalysisParameters, - ContingenciesProvider contingenciesProvider, ComputationManager computationManager) { + ContingenciesProvider contingenciesProvider, ComputationManager computationManager, + List operatorStrategies, List actions) { Objects.requireNonNull(workingVariantId); Objects.requireNonNull(securityAnalysisParameters); Objects.requireNonNull(contingenciesProvider); return CompletableFutureTask.runAsync(() -> { network.getVariantManager().setWorkingVariant(workingVariantId); - return runSync(workingVariantId, securityAnalysisParameters, contingenciesProvider, computationManager); + return runSync(workingVariantId, securityAnalysisParameters, contingenciesProvider, computationManager, operatorStrategies, actions); }, computationManager.getExecutor()); } abstract SecurityAnalysisReport runSync(String workingVariantId, SecurityAnalysisParameters securityAnalysisParameters, ContingenciesProvider contingenciesProvider, - ComputationManager computationManager); + ComputationManager computationManager, List operatorStrategies, List actions); } diff --git a/src/main/java/com/powsybl/openloadflow/sa/AcSecurityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sa/AcSecurityAnalysis.java index 1f1bb0c53f..bb51f66fb4 100644 --- a/src/main/java/com/powsybl/openloadflow/sa/AcSecurityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sa/AcSecurityAnalysis.java @@ -7,10 +7,12 @@ package com.powsybl.openloadflow.sa; import com.google.common.base.Stopwatch; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.reporter.Reporter; import com.powsybl.computation.ComputationManager; import com.powsybl.contingency.ContingenciesProvider; import com.powsybl.contingency.Contingency; +import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.Switch; import com.powsybl.loadflow.LoadFlowParameters; @@ -28,15 +30,22 @@ import com.powsybl.openloadflow.network.util.ActivePowerDistribution; import com.powsybl.openloadflow.network.util.PreviousValueVoltageInitializer; import com.powsybl.openloadflow.util.Reports; -import com.powsybl.security.LimitViolationsResult; -import com.powsybl.security.SecurityAnalysisParameters; -import com.powsybl.security.SecurityAnalysisReport; -import com.powsybl.security.SecurityAnalysisResult; +import com.powsybl.security.*; +import com.powsybl.security.action.Action; +import com.powsybl.security.action.SwitchAction; +import com.powsybl.security.condition.AllViolationCondition; +import com.powsybl.security.condition.AnyViolationCondition; +import com.powsybl.security.condition.AtLeastOneViolationCondition; +import com.powsybl.security.condition.TrueCondition; import com.powsybl.security.monitor.StateMonitor; +import com.powsybl.security.results.NetworkResult; +import com.powsybl.security.results.OperatorStrategyResult; import com.powsybl.security.results.PostContingencyResult; +import com.powsybl.security.strategy.OperatorStrategy; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * @author Geoffroy Jamgotchian @@ -54,7 +63,7 @@ private static SecurityAnalysisResult createNoResult() { @Override SecurityAnalysisReport runSync(String workingVariantId, SecurityAnalysisParameters securityAnalysisParameters, ContingenciesProvider contingenciesProvider, - ComputationManager computationManager) { + ComputationManager computationManager, List operatorStrategies, List actions) { var saReporter = Reports.createAcSecurityAnalysis(reporter, network.getId()); Stopwatch stopwatch = Stopwatch.createStarted(); @@ -75,12 +84,14 @@ SecurityAnalysisReport runSync(String workingVariantId, SecurityAnalysisParamete lfParameters.isShuntCompensatorVoltageControlOn(), lfParameters.getBalanceType() == LoadFlowParameters.BalanceType.PROPORTIONAL_TO_CONFORM_LOAD, lfParameters.isHvdcAcEmulation(), securityAnalysisParametersExt.isContingencyPropagation()); - boolean breakers = !allSwitchesToOpen.isEmpty(); - + // try for find all switches to be operated as actions. + Set allSwitchesToClose = new HashSet<>(); + findAllSwitchesToOperate(network, actions, allSwitchesToClose, allSwitchesToOpen); + boolean breakers = !(allSwitchesToOpen.isEmpty() && allSwitchesToClose.isEmpty()); AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network, lfParameters, lfParametersExt, matrixFactory, connectivityFactory, saReporter, breakers, false); // create networks including all necessary switches - List lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), allSwitchesToOpen, saReporter); + List lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), allSwitchesToOpen, allSwitchesToClose, saReporter); // run simulation on largest network SecurityAnalysisResult result; @@ -89,7 +100,9 @@ SecurityAnalysisReport runSync(String workingVariantId, SecurityAnalysisParamete } else { LfNetwork largestNetwork = lfNetworks.get(0); if (largestNetwork.isValid()) { - result = runSimulations(largestNetwork, propagatedContingencies, acParameters, securityAnalysisParameters); + Map lfActionsById = createLfActions(largestNetwork, actions); + Map> operatorStrategiesByContingencyId = indexOperatorStrategiesByContingencyId(propagatedContingencies, operatorStrategies); + result = runSimulations(largestNetwork, propagatedContingencies, acParameters, securityAnalysisParameters, operatorStrategiesByContingencyId, lfActionsById, allSwitchesToClose); } else { result = createNoResult(); } @@ -110,8 +123,62 @@ public static void distributedMismatch(LfNetwork network, double mismatch, LoadF } } + private static void findAllSwitchesToOperate(Network network, List actions, Set allSwitchesToClose, Set allSwitchesToOpen) { + actions.stream().filter(action -> action.getType().equals(SwitchAction.NAME)) + .forEach(action -> { + String switchId = ((SwitchAction) action).getSwitchId(); + Switch sw = network.getSwitch(switchId); + boolean toOpen = ((SwitchAction) action).isOpen(); + if (sw.isOpen() && !toOpen) { // the switch is open and the action will close it. + allSwitchesToClose.add(sw); + } else if (!sw.isOpen() && toOpen) { // the switch is closed and the action will open it. + allSwitchesToOpen.add(sw); + } + }); + } + + private static Map createLfActions(LfNetwork network, List actions) { + return actions.stream().collect(Collectors.toMap(Action::getId, action -> new LfAction(action, network))); + } + + private static Map> indexOperatorStrategiesByContingencyId(List propagatedContingencies, + List operatorStrategies) { + Set contingencyIds = propagatedContingencies.stream().map(propagatedContingency -> propagatedContingency.getContingency().getId()).collect(Collectors.toSet()); + Map> operatorStrategiesByContingencyId = new HashMap<>(); + for (OperatorStrategy operatorStrategy : operatorStrategies) { + if (contingencyIds.contains(operatorStrategy.getContingencyId())) { + operatorStrategiesByContingencyId.computeIfAbsent(operatorStrategy.getContingencyId(), key -> new ArrayList<>()).add(operatorStrategy); + } else { + throw new PowsyblException("An operator strategy associated to contingency '" + operatorStrategy.getContingencyId() + + "' but this contingency is not present in the list of contingencies"); + } + } + return operatorStrategiesByContingencyId; + } + + private static void restoreInitialTopology(LfNetwork network, Set allSwitchesToClose) { + if (allSwitchesToClose.isEmpty()) { + return; + } + var connectivity = network.getConnectivity(); + connectivity.startTemporaryChanges(); + allSwitchesToClose.stream().map(Identifiable::getId).forEach(id -> { + LfBranch branch = network.getBranchById(id); + connectivity.removeEdge(branch); + }); + Set removedBuses = connectivity.getVerticesRemovedFromMainComponent(); + removedBuses.forEach(bus -> bus.setDisabled(true)); + Set removedBranches = new HashSet<>(connectivity.getEdgesRemovedFromMainComponent()); + // we should manage branches open at one side. + for (LfBus bus : removedBuses) { + bus.getBranches().stream().filter(b -> !b.isConnectedAtBothSides()).forEach(removedBranches::add); + } + removedBranches.forEach(branch -> branch.setDisabled(true)); + } + private SecurityAnalysisResult runSimulations(LfNetwork network, List propagatedContingencies, AcLoadFlowParameters acParameters, - SecurityAnalysisParameters securityAnalysisParameters) { + SecurityAnalysisParameters securityAnalysisParameters, Map> operatorStrategiesByContingencyId, + Map lfActionById, Set allSwitchesToClose) { LoadFlowParameters loadFlowParameters = securityAnalysisParameters.getLoadFlowParameters(); OpenLoadFlowParameters openLoadFlowParameters = OpenLoadFlowParameters.get(loadFlowParameters); OpenSecurityAnalysisParameters openSecurityAnalysisParameters = OpenSecurityAnalysisParameters.getOrDefault(securityAnalysisParameters); @@ -123,6 +190,7 @@ private SecurityAnalysisResult runSimulations(LfNetwork network, List postContingencyResults = new ArrayList<>(); var preContingencyNetworkResult = new PreContingencyNetworkResult(network, monitorIndex, createResultExtension); + List operatorStrategyResults = new ArrayList<>(); // only run post-contingency simulations if pre-contingency simulation is ok if (preContingencyComputationOk) { @@ -161,6 +230,29 @@ private SecurityAnalysisResult runSimulations(LfNetwork network, List operatorStrategiesForThisContingency = operatorStrategiesByContingencyId.get(lfContingency.getId()); + if (operatorStrategiesForThisContingency != null) { + // we have at least an operator strategy for this contingency. + if (operatorStrategiesForThisContingency.size() == 1) { + runActionSimulation(network, context, + operatorStrategiesForThisContingency.get(0), preContingencyLimitViolationManager, + securityAnalysisParameters.getIncreasedViolationsParameters(), postContingencyResult.getLimitViolationsResult(), lfActionById, + createResultExtension, lfContingency) + .ifPresent(operatorStrategyResults::add); + } else { + // save post contingency state for later restoration after action + NetworkState postContingencyNetworkState = NetworkState.save(network); + for (OperatorStrategy operatorStrategy : operatorStrategiesForThisContingency) { + runActionSimulation(network, context, + operatorStrategy, preContingencyLimitViolationManager, + securityAnalysisParameters.getIncreasedViolationsParameters(), postContingencyResult.getLimitViolationsResult(), lfActionById, + createResultExtension, lfContingency) + .ifPresent(operatorStrategyResults::add); + postContingencyNetworkState.restore(); + } + } + } + if (contingencyIt.hasNext()) { // restore base state networkState.restore(); @@ -174,7 +266,7 @@ private SecurityAnalysisResult runSimulations(LfNetwork network, List runActionSimulation(LfNetwork network, AcLoadFlowContext context, OperatorStrategy operatorStrategy, + LimitViolationManager preContingencyLimitViolationManager, + SecurityAnalysisParameters.IncreasedViolationsParameters violationsParameters, + LimitViolationsResult postContingencyLimitViolations, Map lfActionById, + boolean createResultExtension, LfContingency contingency) { + OperatorStrategyResult operatorStrategyResult = null; + + if (checkCondition(operatorStrategy, postContingencyLimitViolations)) { + LOGGER.info("Start operator strategy {} after contingency '{}' simulation on network {}", operatorStrategy.getId(), + operatorStrategy.getContingencyId(), network); + + List operatorStrategyLfActions = operatorStrategy.getActionIds().stream() + .map(id -> { + LfAction lfAction = lfActionById.get(id); + if (lfAction == null) { + throw new PowsyblException("Action '" + id + "' of operator strategy '" + operatorStrategy.getId() + "' not found"); + } + return lfAction; + }) + .collect(Collectors.toList()); + + LfAction.apply(operatorStrategyLfActions, network, contingency); + + Stopwatch stopwatch = Stopwatch.createStarted(); + + // restart LF on post contingency and post actions equation system + context.getParameters().setVoltageInitializer(new PreviousValueVoltageInitializer(true)); + AcLoadFlowResult postActionsLoadFlowResult = new AcloadFlowEngine(context) + .run(); + + boolean postActionsComputationOk = postActionsLoadFlowResult.getNewtonRaphsonStatus() == NewtonRaphsonStatus.CONVERGED; + var postActionsViolationManager = new LimitViolationManager(preContingencyLimitViolationManager, violationsParameters); + var postActionsNetworkResult = new PreContingencyNetworkResult(network, monitorIndex, createResultExtension); + + if (postActionsComputationOk) { + // update network result + postActionsNetworkResult.update(); + + // detect violations + postActionsViolationManager.detectViolations(network); + } + + stopwatch.stop(); + + LOGGER.info("Operator strategy {} after contingency '{}' simulation done on network {} in {} ms", operatorStrategy.getId(), + operatorStrategy.getContingencyId(), network, stopwatch.elapsed(TimeUnit.MILLISECONDS)); + + operatorStrategyResult = new OperatorStrategyResult(operatorStrategy, + new LimitViolationsResult(postActionsComputationOk, + postActionsViolationManager.getLimitViolations()), + new NetworkResult(postActionsNetworkResult.getBranchResults(), + postActionsNetworkResult.getBusResults(), + postActionsNetworkResult.getThreeWindingsTransformerResults())); + } + + return Optional.ofNullable(operatorStrategyResult); + } + + private boolean checkCondition(OperatorStrategy operatorStrategy, LimitViolationsResult limitViolationsResult) { + Set limitViolationEquipmentIds = limitViolationsResult.getLimitViolations().stream() + .map(LimitViolation::getSubjectId) + .collect(Collectors.toSet()); + switch (operatorStrategy.getCondition().getType()) { + case TrueCondition.NAME: + return true; + case AnyViolationCondition.NAME: + return !limitViolationEquipmentIds.isEmpty(); + case AtLeastOneViolationCondition.NAME: { + AtLeastOneViolationCondition atLeastCondition = (AtLeastOneViolationCondition) operatorStrategy.getCondition(); + Set commonEquipmentIds = atLeastCondition.getViolationIds().stream() + .distinct() + .filter(limitViolationEquipmentIds::contains) + .collect(Collectors.toSet()); + return !commonEquipmentIds.isEmpty(); + } + case AllViolationCondition.NAME: { + AllViolationCondition allCondition = (AllViolationCondition) operatorStrategy.getCondition(); + Set commonEquipmentIds = allCondition.getViolationIds().stream() + .distinct() + .filter(limitViolationEquipmentIds::contains) + .collect(Collectors.toSet()); + return commonEquipmentIds.equals(new HashSet<>(allCondition.getViolationIds())); + } + default: + throw new UnsupportedOperationException("Unsupported condition type: " + operatorStrategy.getCondition().getType()); + } + } } diff --git a/src/main/java/com/powsybl/openloadflow/sa/DcSecurityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sa/DcSecurityAnalysis.java index 1caddbd80c..72da2138c4 100644 --- a/src/main/java/com/powsybl/openloadflow/sa/DcSecurityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sa/DcSecurityAnalysis.java @@ -18,11 +18,13 @@ import com.powsybl.openloadflow.network.LfBus; import com.powsybl.openloadflow.sensi.OpenSensitivityAnalysisProvider; import com.powsybl.security.*; +import com.powsybl.security.action.Action; import com.powsybl.security.detectors.DefaultLimitViolationDetector; import com.powsybl.security.detectors.LoadingLimitType; import com.powsybl.security.monitor.StateMonitor; import com.powsybl.security.results.BranchResult; import com.powsybl.security.results.PostContingencyResult; +import com.powsybl.security.strategy.OperatorStrategy; import com.powsybl.sensitivity.*; import org.apache.commons.lang3.tuple.Pair; @@ -37,7 +39,7 @@ protected DcSecurityAnalysis(Network network, MatrixFactory matrixFactory, Graph @Override SecurityAnalysisReport runSync(String workingVariantId, SecurityAnalysisParameters securityAnalysisParameters, ContingenciesProvider contingenciesProvider, - ComputationManager computationManager) { + ComputationManager computationManager, List operatorStrategies, List actions) { // load contingencies List contingencies = contingenciesProvider.getContingencies(network); diff --git a/src/main/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisProvider.java b/src/main/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisProvider.java index 8c070188a9..fbfe14e81c 100644 --- a/src/main/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisProvider.java +++ b/src/main/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisProvider.java @@ -18,6 +18,7 @@ import com.powsybl.math.matrix.SparseMatrixFactory; import com.powsybl.openloadflow.graph.EvenShiloachGraphDecrementalConnectivityFactory; import com.powsybl.openloadflow.graph.GraphConnectivityFactory; +import com.powsybl.openloadflow.graph.NaiveGraphConnectivityFactory; import com.powsybl.openloadflow.network.LfBranch; import com.powsybl.openloadflow.network.LfBus; import com.powsybl.openloadflow.util.PowsyblOpenLoadFlowVersion; @@ -27,6 +28,8 @@ import com.powsybl.security.interceptors.SecurityAnalysisInterceptor; import com.powsybl.security.monitor.StateMonitor; import com.powsybl.security.strategy.OperatorStrategy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -37,6 +40,8 @@ @AutoService(SecurityAnalysisProvider.class) public class OpenSecurityAnalysisProvider implements SecurityAnalysisProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(OpenSecurityAnalysisProvider.class); + private final MatrixFactory matrixFactory; private final GraphConnectivityFactory connectivityFactory; @@ -58,20 +63,33 @@ public CompletableFuture run(Network network, String wor List stateMonitors, Reporter reporter) { Objects.requireNonNull(network); Objects.requireNonNull(workingVariantId); + Objects.requireNonNull(limitViolationDetector); Objects.requireNonNull(computationManager); Objects.requireNonNull(securityAnalysisParameters); Objects.requireNonNull(contingenciesProvider); + Objects.requireNonNull(interceptors); + Objects.requireNonNull(operatorStrategies); + Objects.requireNonNull(actions); Objects.requireNonNull(stateMonitors); Objects.requireNonNull(reporter); + // FIXME implement a fast incremental connectivity algorithm + GraphConnectivityFactory selectedConnectivityFactory; + if (operatorStrategies.isEmpty()) { + selectedConnectivityFactory = connectivityFactory; + } else { + LOGGER.warn("Naive (and slow!!!) connectivity algorithm has been selected because at least one operator strategy is configured"); + selectedConnectivityFactory = new NaiveGraphConnectivityFactory<>(LfBus::getNum); + } + AbstractSecurityAnalysis securityAnalysis; if (securityAnalysisParameters.getLoadFlowParameters().isDc()) { - securityAnalysis = new DcSecurityAnalysis(network, matrixFactory, connectivityFactory, stateMonitors, reporter); + securityAnalysis = new DcSecurityAnalysis(network, matrixFactory, selectedConnectivityFactory, stateMonitors, reporter); } else { - securityAnalysis = new AcSecurityAnalysis(network, matrixFactory, connectivityFactory, stateMonitors, reporter); + securityAnalysis = new AcSecurityAnalysis(network, matrixFactory, selectedConnectivityFactory, stateMonitors, reporter); } - return securityAnalysis.run(workingVariantId, securityAnalysisParameters, contingenciesProvider, computationManager); + return securityAnalysis.run(workingVariantId, securityAnalysisParameters, contingenciesProvider, computationManager, operatorStrategies, actions); } @Override diff --git a/src/main/java/com/powsybl/openloadflow/sensi/AcSensitivityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sensi/AcSensitivityAnalysis.java index 9d87b54786..c72ea272e2 100644 --- a/src/main/java/com/powsybl/openloadflow/sensi/AcSensitivityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sensi/AcSensitivityAnalysis.java @@ -216,7 +216,7 @@ public void analyse(Network network, List contingencies, .setMaxPlausibleTargetVoltage(lfParametersExt.getMaxPlausibleTargetVoltage()); // create networks including all necessary switches - List lfNetworks = Networks.load(network, lfNetworkParameters, allSwitchesToOpen, reporter); + List lfNetworks = Networks.load(network, lfNetworkParameters, allSwitchesToOpen, Collections.emptySet(), reporter); LfNetwork lfNetwork = lfNetworks.get(0); checkContingencies(lfNetwork, contingencies); checkLoadFlowParameters(lfParameters); diff --git a/src/main/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysis.java index 763491f717..73d58608d3 100644 --- a/src/main/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysis.java @@ -580,7 +580,6 @@ private static List computeConnectivityData(LfNetwor Map, ConnectivityAnalysisResult> connectivityAnalysisResults = new LinkedHashMap<>(); GraphConnectivity connectivity = lfNetwork.getConnectivity(); - connectivity.setMainComponentVertex(lfNetwork.getSlackBus()); for (Map.Entry, List> e : contingenciesByGroupOfElementsBreakingConnectivity.entrySet()) { Set breakingConnectivityCandidates = e.getKey(); List contingencyList = e.getValue(); @@ -856,7 +855,7 @@ public void analyse(Network network, List contingencies, .setMinPlausibleTargetVoltage(lfParametersExt.getMinPlausibleTargetVoltage()) .setMaxPlausibleTargetVoltage(lfParametersExt.getMaxPlausibleTargetVoltage()); // create networks including all necessary switches - List lfNetworks = Networks.load(network, lfNetworkParameters, allSwitchesToOpen, reporter); + List lfNetworks = Networks.load(network, lfNetworkParameters, allSwitchesToOpen, Collections.emptySet(), reporter); LfNetwork lfNetwork = lfNetworks.get(0); checkContingencies(lfNetwork, contingencies); checkLoadFlowParameters(lfParameters); diff --git a/src/test/java/com/powsybl/openloadflow/ac/AcEquationsTest.java b/src/test/java/com/powsybl/openloadflow/ac/AcEquationsTest.java index 6294129c04..73517f6a70 100644 --- a/src/test/java/com/powsybl/openloadflow/ac/AcEquationsTest.java +++ b/src/test/java/com/powsybl/openloadflow/ac/AcEquationsTest.java @@ -97,6 +97,7 @@ private static double[] eval(EquationTerm term, void branchTest() { var branch = Mockito.mock(LfBranch.class, ANSWER); Mockito.doReturn(0).when(branch).getNum(); + Mockito.doReturn(false).when(branch).isDisabled(); PiModel piModel = Mockito.mock(PiModel.class, ANSWER); Mockito.doReturn(piModel).when(branch).getPiModel(); Mockito.doReturn(R).when(piModel).getR(); @@ -188,6 +189,7 @@ void branchTest() { void shuntTest() { var shunt = Mockito.mock(LfShunt.class, new RuntimeExceptionAnswer()); Mockito.doReturn(0).when(shunt).getNum(); + Mockito.doReturn(false).when(shunt).isDisabled(); var bus = Mockito.mock(LfBus.class, ANSWER); Mockito.doReturn(0).when(bus).getNum(); @@ -210,6 +212,7 @@ void shuntTest() { void hvdcTest() { var hvdc = Mockito.mock(LfHvdc.class, new RuntimeExceptionAnswer()); Mockito.doReturn(0).when(hvdc).getNum(); + Mockito.doReturn(false).when(hvdc).isDisabled(); Mockito.doReturn(DROOP).when(hvdc).getDroop(); Mockito.doReturn(P_0).when(hvdc).getP0(); LfVscConverterStationImpl station1 = Mockito.mock(LfVscConverterStationImpl.class, new RuntimeExceptionAnswer()); diff --git a/src/test/java/com/powsybl/openloadflow/network/AbstractLoadFlowNetworkFactory.java b/src/test/java/com/powsybl/openloadflow/network/AbstractLoadFlowNetworkFactory.java index 8c3e13cd96..8187beb070 100644 --- a/src/test/java/com/powsybl/openloadflow/network/AbstractLoadFlowNetworkFactory.java +++ b/src/test/java/com/powsybl/openloadflow/network/AbstractLoadFlowNetworkFactory.java @@ -43,6 +43,13 @@ protected static Bus createBus(Network network, String substationId, String id, .add(); } + protected static Bus createOtherBus(Network network, String id, String voltageLevelId) { + VoltageLevel vl = network.getVoltageLevel(voltageLevelId); + return vl.getBusBreakerView().newBus() + .setId(id) + .add(); + } + protected static Generator createGenerator(Bus b, String id, double p) { return createGenerator(b, id, p, 1); } @@ -111,6 +118,15 @@ protected static Line createLine(Network network, Bus b1, Bus b2, String id, dou .add(); } + protected static Switch createSwitch(Network network, Bus b1, Bus b2, String id) { + return network.getVoltageLevel(b1.getVoltageLevel().getId()).getBusBreakerView().newSwitch() + .setId(id) + .setBus1(b1.getId()) + .setBus2(b2.getId()) + .setOpen(false) + .add(); + } + protected static TwoWindingsTransformer createTransformer(Network network, String substationId, Bus b1, Bus b2, String id, double x, double rho) { return network.getSubstation(substationId).newTwoWindingsTransformer() .setId(id) diff --git a/src/test/java/com/powsybl/openloadflow/network/ConnectedComponentNetworkFactory.java b/src/test/java/com/powsybl/openloadflow/network/ConnectedComponentNetworkFactory.java index ae3a782773..563efecd16 100644 --- a/src/test/java/com/powsybl/openloadflow/network/ConnectedComponentNetworkFactory.java +++ b/src/test/java/com/powsybl/openloadflow/network/ConnectedComponentNetworkFactory.java @@ -708,4 +708,40 @@ public static Network createSubComp() { return network; } + + /** + *
+     * b1 ----------+
+     * |            |
+     * b2 -------- b3
+     *              |
+     * b5 -------- b4
+     * |            |
+     * b6 ----------+
+     * 
+ * + * @return network + */ + public static Network createTwoCcLinkedBySwitches() { + Network network = Network.create("test", "code"); + Bus b1 = createBus(network, "b1"); + Bus b2 = createBus(network, "b2"); + Bus b3 = createBus(network, "b3"); + Bus b4 = createOtherBus(network, "b4", "b3_vl"); + Bus b5 = createOtherBus(network, "b5", "b2_vl"); + Bus b6 = createBus(network, "b6"); + createLine(network, b1, b2, "l12", 0.1f); + createLine(network, b1, b3, "l13", 0.1f); + createLine(network, b2, b3, "l23", 0.1f); + createSwitch(network, b3, b4, "s34"); + createLine(network, b4, b5, "l45", 0.1f); + createLine(network, b4, b6, "l46", 0.1f); + createLine(network, b5, b6, "l56", 0.1f); + createSwitch(network, b2, b5, "s25"); + createGenerator(b1, "g1", 3); + createGenerator(b5, "g3", 1); + createLoad(b4, "d4", 3); + createLoad(b2, "d2", 1); + return network; + } } diff --git a/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java b/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java new file mode 100644 index 0000000000..119e74e7c9 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.openloadflow.sa; + +import com.powsybl.commons.AbstractConverterTest; +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.reporter.Reporter; +import com.powsybl.contingency.Contingency; +import com.powsybl.contingency.LoadContingency; +import com.powsybl.iidm.network.Network; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.ac.outerloop.AcLoadFlowParameters; +import com.powsybl.openloadflow.graph.NaiveGraphConnectivityFactory; +import com.powsybl.openloadflow.network.LfAction; +import com.powsybl.openloadflow.network.LfBus; +import com.powsybl.openloadflow.network.LfNetwork; +import com.powsybl.openloadflow.network.NodeBreakerNetworkFactory; +import com.powsybl.openloadflow.network.impl.Networks; +import com.powsybl.openloadflow.network.impl.PropagatedContingency; +import com.powsybl.security.action.SwitchAction; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Anne Tilloy + */ +class LfActionTest extends AbstractConverterTest { + + @Override + @BeforeEach + public void setUp() throws IOException { + super.setUp(); + } + + @Override + @AfterEach + public void tearDown() throws IOException { + super.tearDown(); + } + + @Test + void test() { + Network network = NodeBreakerNetworkFactory.create(); + SwitchAction switchAction = new SwitchAction("switchAction", "C", true); + var matrixFactory = new DenseMatrixFactory(); + AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network, + new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), Reporter.NO_OP, true, false); + List lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), Set.of(network.getSwitch("C")), Collections.emptySet(), Reporter.NO_OP); + LfNetwork lfNetwork = lfNetworks.get(0); + LfAction lfAction = new LfAction(switchAction, lfNetwork); + String loadId = "LOAD"; + Contingency contingency = new Contingency(loadId, new LoadContingency("LD")); + PropagatedContingency propagatedContingency = PropagatedContingency.createList(network, + Collections.singletonList(contingency), new HashSet<>(), false, false, false, true).get(0); + propagatedContingency.toLfContingency(lfNetwork).ifPresent(lfContingency -> { + LfAction.apply(List.of(lfAction), lfNetwork, lfContingency); + assertTrue(lfNetwork.getBranchById("C").isDisabled()); + assertEquals("C", lfAction.getDisabledBranch().getId()); + assertNull(lfAction.getEnabledBranch()); + }); + + SwitchAction switchAction2 = new SwitchAction("switchAction", "S", true); + assertThrows(PowsyblException.class, () -> new LfAction(switchAction2, lfNetwork), "Branch S not found in the network"); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisGraphTest.java b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisGraphTest.java index 95997e2281..23bf4e022f 100644 --- a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisGraphTest.java +++ b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisGraphTest.java @@ -158,7 +158,7 @@ List> getLoadFlowContingencies(GraphConnectivityFactory lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), allSwitchesToOpen, Reporter.NO_OP); + List lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), allSwitchesToOpen, Collections.emptySet(), Reporter.NO_OP); // run simulation on each network start = System.currentTimeMillis(); diff --git a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java index c7ab058f26..d0da7b0ede 100644 --- a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java +++ b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java @@ -18,6 +18,7 @@ import com.powsybl.iidm.network.extensions.LoadDetailAdder; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; +import com.powsybl.iidm.xml.test.MetrixTutorialSixBusesFactory; import com.powsybl.loadflow.LoadFlow; import com.powsybl.loadflow.LoadFlowParameters; import com.powsybl.loadflow.LoadFlowResult; @@ -27,13 +28,23 @@ import com.powsybl.openloadflow.OpenLoadFlowProvider; import com.powsybl.openloadflow.graph.EvenShiloachGraphDecrementalConnectivityFactory; import com.powsybl.openloadflow.graph.GraphConnectivityFactory; +import com.powsybl.openloadflow.graph.NaiveGraphConnectivityFactory; import com.powsybl.openloadflow.network.*; import com.powsybl.openloadflow.network.impl.OlfBranchResult; import com.powsybl.openloadflow.util.LoadFlowAssert; import com.powsybl.security.*; +import com.powsybl.security.action.Action; +import com.powsybl.security.action.LineConnectionAction; +import com.powsybl.security.action.PhaseTapChangerTapPositionAction; +import com.powsybl.security.action.SwitchAction; +import com.powsybl.security.condition.AllViolationCondition; +import com.powsybl.security.condition.AnyViolationCondition; +import com.powsybl.security.condition.AtLeastOneViolationCondition; +import com.powsybl.security.condition.TrueCondition; import com.powsybl.security.detectors.DefaultLimitViolationDetector; import com.powsybl.security.monitor.StateMonitor; import com.powsybl.security.results.*; +import com.powsybl.security.strategy.OperatorStrategy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -151,6 +162,26 @@ private SecurityAnalysisResult runSecurityAnalysis(Network network, List contingencies, List monitors, + SecurityAnalysisParameters saParameters, List operatorStrategies, + List actions, Reporter reporter) { + ContingenciesProvider provider = n -> contingencies; + SecurityAnalysisReport report = securityAnalysisProvider.run(network, + network.getVariantManager().getWorkingVariantId(), + new DefaultLimitViolationDetector(), + new LimitViolationFilter(), + computationManager, + saParameters, + provider, + Collections.emptyList(), + operatorStrategies, + actions, + monitors, + reporter) + .join(); + return report.getResult(); + } + private SecurityAnalysisResult runSecurityAnalysis(Network network, List contingencies, List monitors) { return runSecurityAnalysis(network, contingencies, monitors, new LoadFlowParameters()); } @@ -196,11 +227,22 @@ private static Optional getOptionalPostContingencyResult( .findFirst(); } + private static Optional getOptionalOperatorStrategyResult(SecurityAnalysisResult result, String operatorStrategyId) { + return result.getOperatorStrategyResults().stream() + .filter(r -> r.getOperatorStrategy().getId().equals(operatorStrategyId)) + .findFirst(); + } + private static PostContingencyResult getPostContingencyResult(SecurityAnalysisResult result, String contingencyId) { return getOptionalPostContingencyResult(result, contingencyId) .orElseThrow(); } + private static OperatorStrategyResult getOperatorStrategyResult(SecurityAnalysisResult result, String operatorStrategyId) { + return getOptionalOperatorStrategyResult(result, operatorStrategyId) + .orElseThrow(); + } + private static void assertAlmostEquals(BusResult expected, BusResult actual, double epsilon) { assertEquals(expected.getVoltageLevelId(), actual.getVoltageLevelId()); assertEquals(expected.getBusId(), actual.getBusId()); @@ -1715,4 +1757,262 @@ void testBranchOpenAtOneSideLoss() { assertTrue(result.getPreContingencyResult().getLimitViolationsResult().isComputationOk()); assertTrue(result.getPostContingencyResults().get(0).getLimitViolationsResult().isComputationOk()); } + + @Test + void testSecurityAnalysisWithOperatorStrategy() { + MatrixFactory matrixFactory = new DenseMatrixFactory(); + GraphConnectivityFactory connectivityFactory = new NaiveGraphConnectivityFactory<>(LfBus::getNum); + securityAnalysisProvider = new OpenSecurityAnalysisProvider(matrixFactory, connectivityFactory); + + Network network = NodeBreakerNetworkFactory.create3Bars(); + network.getSwitch("C1").setOpen(true); + network.getSwitch("C2").setOpen(true); + network.getLineStream().forEach(line -> { + if (line.getCurrentLimits1().isPresent()) { + line.getCurrentLimits1().orElseThrow().setPermanentLimit(310); + } + if (line.getCurrentLimits2().isPresent()) { + line.getCurrentLimits2().orElseThrow().setPermanentLimit(310); + } + }); + + List contingencies = Stream.of("L1", "L3", "L2") + .map(id -> new Contingency(id, new BranchContingency(id))) + .collect(Collectors.toList()); + + List actions = List.of(new SwitchAction("action1", "C1", false), + new SwitchAction("action3", "C2", false)); + + List operatorStrategies = List.of(new OperatorStrategy("strategyL1", "L1", new TrueCondition(), List.of("action1")), + new OperatorStrategy("strategyL3", "L3", new TrueCondition(), List.of("action3")), + new OperatorStrategy("strategyL2", "L2", new TrueCondition(), List.of("action1", "action3"))); + + List monitors = createAllBranchesMonitors(network); + + LoadFlowParameters parameters = new LoadFlowParameters(); + parameters.setDistributedSlack(false); + setSlackBusId(parameters, "VL2_0"); + SecurityAnalysisParameters securityAnalysisParameters = new SecurityAnalysisParameters(); + securityAnalysisParameters.setLoadFlowParameters(parameters); + + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, securityAnalysisParameters, + operatorStrategies, actions, Reporter.NO_OP); + assertEquals(578.3, result.getPreContingencyResult().getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(0.0, result.getPreContingencyResult().getNetworkResult().getBranchResult("L2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(292.0, result.getPreContingencyResult().getNetworkResult().getBranchResult("L3").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(0.0, getPostContingencyResult(result, "L1").getNetworkResult().getBranchResult("L2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(318.3, getPostContingencyResult(result, "L1").getNetworkResult().getBranchResult("L3").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(583.5, getOperatorStrategyResult(result, "strategyL1").getNetworkResult().getBranchResult("L2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(302.0, getOperatorStrategyResult(result, "strategyL1").getNetworkResult().getBranchResult("L3").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(0.0, getPostContingencyResult(result, "L3").getNetworkResult().getBranchResult("L2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(431.0, getPostContingencyResult(result, "L3").getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(302.2, getOperatorStrategyResult(result, "strategyL3").getNetworkResult().getBranchResult("L2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(583.5, getOperatorStrategyResult(result, "strategyL3").getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(583.5, getPostContingencyResult(result, "L2").getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(302.2, getPostContingencyResult(result, "L2").getNetworkResult().getBranchResult("L3").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(441.5, getOperatorStrategyResult(result, "strategyL2").getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(441.5, getOperatorStrategyResult(result, "strategyL2").getNetworkResult().getBranchResult("L3").getI1(), LoadFlowAssert.DELTA_I); + } + + @Test + void testSecurityAnalysisWithOperatorStrategy2() { + MatrixFactory matrixFactory = new DenseMatrixFactory(); + GraphConnectivityFactory connectivityFactory = new NaiveGraphConnectivityFactory<>(LfBus::getNum); + securityAnalysisProvider = new OpenSecurityAnalysisProvider(matrixFactory, connectivityFactory); + + Network network = NodeBreakerNetworkFactory.create3Bars(); + network.getSwitch("C1").setOpen(true); + network.getSwitch("C2").setOpen(true); + network.getLine("L1").getCurrentLimits1().orElseThrow().setPermanentLimit(580.0); + network.getLine("L1").getCurrentLimits2().orElseThrow().setPermanentLimit(580.0); + network.getLine("L2").getCurrentLimits1().orElseThrow().setPermanentLimit(500.0); + network.getLine("L2").getCurrentLimits2().orElseThrow().setPermanentLimit(500.0); + + List contingencies = Stream.of("L1", "L3", "L2") + .map(id -> new Contingency(id, new BranchContingency(id))) + .collect(Collectors.toList()); + + List actions = List.of(new SwitchAction("action1", "C1", false), + new SwitchAction("action3", "C2", false)); + + List operatorStrategies = List.of(new OperatorStrategy("strategyL1", "L1", new AnyViolationCondition(), List.of("action1")), + new OperatorStrategy("strategyL3", "L3", new AnyViolationCondition(), List.of("action3")), + new OperatorStrategy("strategyL2_1", "L2", new AtLeastOneViolationCondition(List.of("L1")), List.of("action1", "action3")), + new OperatorStrategy("strategyL2_2", "L2", new AllViolationCondition(List.of("L1")), List.of("action1", "action3"))); + + List monitors = createAllBranchesMonitors(network); + + LoadFlowParameters parameters = new LoadFlowParameters(); + parameters.setDistributedSlack(false); + setSlackBusId(parameters, "VL2_0"); + SecurityAnalysisParameters securityAnalysisParameters = new SecurityAnalysisParameters(); + securityAnalysisParameters.setLoadFlowParameters(parameters); + + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, securityAnalysisParameters, + operatorStrategies, actions, Reporter.NO_OP); + assertEquals(578.3, result.getPreContingencyResult().getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(0.0, result.getPreContingencyResult().getNetworkResult().getBranchResult("L2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(292.0, result.getPreContingencyResult().getNetworkResult().getBranchResult("L3").getI1(), LoadFlowAssert.DELTA_I); + // L1 contingency + assertEquals(0.0, getPostContingencyResult(result, "L1").getNetworkResult().getBranchResult("L2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(318.3, getPostContingencyResult(result, "L1").getNetworkResult().getBranchResult("L3").getI1(), LoadFlowAssert.DELTA_I); + assertTrue(getPostContingencyResult(result, "L1").getLimitViolationsResult().getLimitViolations().isEmpty()); + assertTrue(getOptionalOperatorStrategyResult(result, "strategyL1").isEmpty()); + // L3 contingency + assertEquals(0.0, getPostContingencyResult(result, "L3").getNetworkResult().getBranchResult("L2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(431.0, getPostContingencyResult(result, "L3").getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + assertFalse(getPostContingencyResult(result, "L3").getLimitViolationsResult().getLimitViolations().isEmpty()); // HIGH_VOLTAGE + assertFalse(getOptionalOperatorStrategyResult(result, "strategyL3").isEmpty()); + // L2 contingency + assertEquals(583.5, getPostContingencyResult(result, "L2").getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(302.2, getPostContingencyResult(result, "L2").getNetworkResult().getBranchResult("L3").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(441.5, getOperatorStrategyResult(result, "strategyL2_1").getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(441.5, getOperatorStrategyResult(result, "strategyL2_2").getNetworkResult().getBranchResult("L1").getI1(), LoadFlowAssert.DELTA_I); + } + + @Test + void testSecurityAnalysisWithOperatorStrategy3() { + MatrixFactory matrixFactory = new DenseMatrixFactory(); + GraphConnectivityFactory connectivityFactory = new NaiveGraphConnectivityFactory<>(LfBus::getNum); + securityAnalysisProvider = new OpenSecurityAnalysisProvider(matrixFactory, connectivityFactory); + + Network network = NodeBreakerNetworkFactory.create3Bars(); + network.getSwitch("C1").setOpen(true); + network.getSwitch("C2").setOpen(true); + network.getLine("L1").getCurrentLimits1().orElseThrow().setPermanentLimit(580.0); + network.getLine("L1").getCurrentLimits2().orElseThrow().setPermanentLimit(580.0); + network.getLine("L2").getCurrentLimits1().orElseThrow().setPermanentLimit(500.0); + network.getLine("L2").getCurrentLimits2().orElseThrow().setPermanentLimit(500.0); + + List contingencies = Stream.of("L3", "L2") + .map(id -> new Contingency(id, new BranchContingency(id))) + .collect(Collectors.toList()); + + List actions = List.of(new SwitchAction("action1", "C1", false), + new SwitchAction("action3", "C2", false)); + + List operatorStrategies = List.of(new OperatorStrategy("strategyL3", "L3", new AllViolationCondition(List.of("VL1", "VL2")), List.of("action3")), + new OperatorStrategy("strategyL2", "L2", new AtLeastOneViolationCondition(List.of("L1", "L3")), List.of("action1", "action3"))); + + List monitors = createAllBranchesMonitors(network); + + LoadFlowParameters parameters = new LoadFlowParameters(); + parameters.setDistributedSlack(false); + setSlackBusId(parameters, "VL2_0"); + SecurityAnalysisParameters securityAnalysisParameters = new SecurityAnalysisParameters(); + securityAnalysisParameters.setLoadFlowParameters(parameters); + + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, securityAnalysisParameters, + operatorStrategies, actions, Reporter.NO_OP); + // L3 contingency + assertFalse(getOptionalOperatorStrategyResult(result, "strategyL3").isEmpty()); + // L2 contingency + assertFalse(getOptionalOperatorStrategyResult(result, "strategyL2").isEmpty()); + } + + @Test + void testWithSeveralConnectedComponents() { + MatrixFactory matrixFactory = new DenseMatrixFactory(); + GraphConnectivityFactory connectivityFactory = new NaiveGraphConnectivityFactory<>(LfBus::getNum); + securityAnalysisProvider = new OpenSecurityAnalysisProvider(matrixFactory, connectivityFactory); + + Network network = ConnectedComponentNetworkFactory.createTwoCcLinkedBySwitches(); + + List contingencies = Stream.of("s25") + .map(id -> new Contingency(id, new SwitchContingency(id))) + .collect(Collectors.toList()); + + List actions = List.of(new SwitchAction("action1", "s34", true)); + + List operatorStrategies = List.of(new OperatorStrategy("strategyS25", "s25", new TrueCondition(), List.of("action1"))); + + List monitors = createAllBranchesMonitors(network); + + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, new SecurityAnalysisParameters(), + operatorStrategies, actions, Reporter.NO_OP); + assertEquals(1.255, result.getPreContingencyResult().getNetworkResult().getBranchResult("l12").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(1.745, result.getPreContingencyResult().getNetworkResult().getBranchResult("l13").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(0.502, result.getPreContingencyResult().getNetworkResult().getBranchResult("l23").getP1(), LoadFlowAssert.DELTA_POWER); + // s25 contingency + assertEquals(1.332, getPostContingencyResult(result, "s25").getNetworkResult().getBranchResult("l12").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(1.667, getPostContingencyResult(result, "s25").getNetworkResult().getBranchResult("l13").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(0.335, getPostContingencyResult(result, "s25").getNetworkResult().getBranchResult("l23").getP1(), LoadFlowAssert.DELTA_POWER); + // strategyS25 operator strategy + assertEquals(0.666, getOperatorStrategyResult(result, "strategyS25").getNetworkResult().getBranchResult("l12").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(0.333, getOperatorStrategyResult(result, "strategyS25").getNetworkResult().getBranchResult("l13").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(-0.333, getOperatorStrategyResult(result, "strategyS25").getNetworkResult().getBranchResult("l23").getP1(), LoadFlowAssert.DELTA_POWER); + } + + @Test + void testMetrixTutorial() { + MatrixFactory matrixFactory = new DenseMatrixFactory(); + GraphConnectivityFactory connectivityFactory = new NaiveGraphConnectivityFactory<>(LfBus::getNum); + securityAnalysisProvider = new OpenSecurityAnalysisProvider(matrixFactory, connectivityFactory); + + Network network = MetrixTutorialSixBusesFactory.create(); + network.getGenerator("SO_G2").setTargetP(628); + + SecurityAnalysisParameters securityAnalysisParameters = new SecurityAnalysisParameters(); + LoadFlowParameters parameters = new LoadFlowParameters(); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + parameters.setHvdcAcEmulation(false); + securityAnalysisParameters.setLoadFlowParameters(parameters); + + List contingencies = List.of(new Contingency("S_SO_1", new BranchContingency("S_SO_1"))); + + List monitors = createAllBranchesMonitors(network); + + List actions = List.of(new SwitchAction("openSwitchS0", "SOO1_SOO1_DJ_OMN", true), + new LineConnectionAction("openLineSSO2", "S_SO_2", true, true), + new PhaseTapChangerTapPositionAction("pst", "NE_NO_1", false, 1), // PST at tap position 17. + new PhaseTapChangerTapPositionAction("pst2", "NE_NO_1", true, -16)); + List operatorStrategies = List.of(new OperatorStrategy("strategy1", "S_SO_1", new AllViolationCondition(List.of("S_SO_2")), List.of("openSwitchS0")), + new OperatorStrategy("strategy2", "S_SO_1", new AllViolationCondition(List.of("S_SO_2")), List.of("openLineSSO2")), + new OperatorStrategy("strategy3", "S_SO_1", new TrueCondition(), List.of("pst")), + new OperatorStrategy("strategy4", "S_SO_1", new TrueCondition(), List.of("pst2"))); + + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, securityAnalysisParameters, + operatorStrategies, actions, Reporter.NO_OP); + assertEquals(271.99, result.getPreContingencyResult().getNetworkResult().getBranchResult("S_SO_2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(504.40, getPostContingencyResult(result, "S_SO_1").getNetworkResult().getBranchResult("S_SO_2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(298.70, getOperatorStrategyResult(result, "strategy1").getNetworkResult().getBranchResult("S_SO_2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(488.99, getOperatorStrategyResult(result, "strategy2").getNetworkResult().getBranchResult("SO_NO_1").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(499.984, getOperatorStrategyResult(result, "strategy3").getNetworkResult().getBranchResult("S_SO_2").getI1(), LoadFlowAssert.DELTA_I); + assertEquals(499.984, getOperatorStrategyResult(result, "strategy4").getNetworkResult().getBranchResult("S_SO_2").getI1(), LoadFlowAssert.DELTA_I); + + network.getGenerator("SO_G2").setTargetP(628); + network.getLine("S_SO_1").getTerminal1().disconnect(); + network.getLine("S_SO_1").getTerminal2().disconnect(); + + LoadFlowParameters parameters2 = new LoadFlowParameters(); + parameters2.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + parameters2.setHvdcAcEmulation(false); + + LoadFlow.run(network, parameters); + assertEquals(504.40, network.getLine("S_SO_2").getTerminal1().getI(), LoadFlowAssert.DELTA_I); + + network.getTwoWindingsTransformer("NE_NO_1").getPhaseTapChanger().setTapPosition(1); + LoadFlow.run(network, parameters); + assertEquals(499.989, network.getLine("S_SO_2").getTerminal1().getI(), LoadFlowAssert.DELTA_I); + } + + @Test + void testBranchOpenAtOneSideRecovery() { + MatrixFactory matrixFactory = new DenseMatrixFactory(); + GraphConnectivityFactory connectivityFactory = new NaiveGraphConnectivityFactory<>(LfBus::getNum); + securityAnalysisProvider = new OpenSecurityAnalysisProvider(matrixFactory, connectivityFactory); + + var network = ConnectedComponentNetworkFactory.createTwoCcLinkedBySwitches(); + network.getLine("l46").getTerminal1().disconnect(); + network.getSwitch("s25").setOpen(true); + network.getSwitch("s34").setOpen(true); + List contingencies = List.of(new Contingency("line", new BranchContingency("l12"))); + List actions = List.of(new SwitchAction("closeSwitch", "s25", false)); + List operatorStrategies = List.of(new OperatorStrategy("strategy", "line", new TrueCondition(), List.of("closeSwitch"))); + List monitors = createAllBranchesMonitors(network); + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, new SecurityAnalysisParameters(), + operatorStrategies, actions, Reporter.NO_OP); + assertEquals(-2.996, getOperatorStrategyResult(result, "strategy").getNetworkResult().getBranchResult("l23").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(-3.000, getOperatorStrategyResult(result, "strategy").getNetworkResult().getBranchResult("l45").getP1(), LoadFlowAssert.DELTA_POWER); + } }