diff --git a/src/main/java/com/powsybl/openloadflow/AbstractAcOuterLoopConfig.java b/src/main/java/com/powsybl/openloadflow/AbstractAcOuterLoopConfig.java index a5fd28de62..27e733e578 100644 --- a/src/main/java/com/powsybl/openloadflow/AbstractAcOuterLoopConfig.java +++ b/src/main/java/com/powsybl/openloadflow/AbstractAcOuterLoopConfig.java @@ -112,6 +112,13 @@ protected static Optional createPhaseControlOuterLoop(LoadFlowParam return createPhaseControlOuterLoop(parameters, parametersExt.getPhaseShifterControlMode()); } + protected static Optional createAcHvdcAcEmulationOuterLoop(LoadFlowParameters parameters) { + if (parameters.isHvdcAcEmulation()) { + return Optional.of(new AcHvdcAcEmulationOuterLoop()); + } + return Optional.empty(); + } + protected static Optional createAutomationSystemOuterLoop(OpenLoadFlowParameters parametersExt) { if (parametersExt.isSimulateAutomationSystems()) { return Optional.of(new AutomationSystemOuterLoop()); diff --git a/src/main/java/com/powsybl/openloadflow/DefaultAcOuterLoopConfig.java b/src/main/java/com/powsybl/openloadflow/DefaultAcOuterLoopConfig.java index d100fe82ec..7d2b853f31 100644 --- a/src/main/java/com/powsybl/openloadflow/DefaultAcOuterLoopConfig.java +++ b/src/main/java/com/powsybl/openloadflow/DefaultAcOuterLoopConfig.java @@ -23,6 +23,8 @@ public List configure(LoadFlowParameters parameters, OpenLoadFlowPa List outerLoops = new ArrayList<>(5); // primary frequency control createDistributedSlackOuterLoop(parameters, parametersExt).ifPresent(outerLoops::add); + // AC emulation + createAcHvdcAcEmulationOuterLoop(parameters).ifPresent(outerLoops::add); // secondary voltage control createSecondaryVoltageControlOuterLoop(parametersExt).ifPresent(outerLoops::add); // primary voltage control diff --git a/src/main/java/com/powsybl/openloadflow/ExplicitAcOuterLoopConfig.java b/src/main/java/com/powsybl/openloadflow/ExplicitAcOuterLoopConfig.java index a770c2c2c6..031175ccce 100644 --- a/src/main/java/com/powsybl/openloadflow/ExplicitAcOuterLoopConfig.java +++ b/src/main/java/com/powsybl/openloadflow/ExplicitAcOuterLoopConfig.java @@ -35,7 +35,8 @@ public class ExplicitAcOuterLoopConfig extends AbstractAcOuterLoopConfig { SimpleTransformerVoltageControlOuterLoop.NAME, TransformerVoltageControlOuterLoop.NAME, AutomationSystemOuterLoop.NAME, - IncrementalTransformerReactivePowerControlOuterLoop.NAME); + IncrementalTransformerReactivePowerControlOuterLoop.NAME, + AcHvdcAcEmulationOuterLoop.NAME); private static Optional createOuterLoop(String name, LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt) { return switch (name) { @@ -65,6 +66,7 @@ private static Optional createOuterLoop(String name, LoadFlowParame parametersExt.getGeneratorVoltageControlMinNominalVoltage()); case AutomationSystemOuterLoop.NAME -> createAutomationSystemOuterLoop(parametersExt); case IncrementalTransformerReactivePowerControlOuterLoop.NAME -> createTransformerReactivePowerControlOuterLoop(parametersExt); + case AcHvdcAcEmulationOuterLoop.NAME -> createAcHvdcAcEmulationOuterLoop(parameters); default -> throw new PowsyblException("Unknown outer loop '" + name + "'"); }; } 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 466e293869..c680ffaad1 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractHvdcAcEmulationFlowEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/ac/equations/AbstractHvdcAcEmulationFlowEquationTerm.java @@ -43,29 +43,18 @@ protected AbstractHvdcAcEmulationFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfBus ph1Var = variableSet.getVariable(bus1.getNum(), AcVariableType.BUS_PHI); ph2Var = variableSet.getVariable(bus2.getNum(), AcVariableType.BUS_PHI); variables = List.of(ph1Var, ph2Var); - k = hvdc.getDroop() * 180 / Math.PI; - p0 = hvdc.getP0(); + k = hvdc.getAcEmulationControl().getDroop() * 180 / Math.PI; + p0 = hvdc.getAcEmulationControl().getP0(); lossFactor1 = hvdc.getConverterStation1().getLossFactor() / 100; lossFactor2 = hvdc.getConverterStation2().getLossFactor() / 100; - pMaxFromCS1toCS2 = hvdc.getPMaxFromCS1toCS2(); - pMaxFromCS2toCS1 = hvdc.getPMaxFromCS2toCS1(); + pMaxFromCS1toCS2 = hvdc.getAcEmulationControl().getPMaxFromCS1toCS2(); + pMaxFromCS2toCS1 = hvdc.getAcEmulationControl().getPMaxFromCS2toCS1(); } protected double rawP(double p0, double k, double ph1, double ph2) { return p0 + k * (ph1 - ph2); } - protected double boundedP(double rawP) { - // If there is a maximal active power - // it is applied at the entry of the controller VSC station - // on the AC side of the network. - if (rawP >= 0) { - return Math.min(rawP, pMaxFromCS1toCS2); - } else { - return Math.max(rawP, -pMaxFromCS2toCS1); - } - } - protected double ph1() { return sv.get(ph1Var.getRow()); } diff --git a/src/main/java/com/powsybl/openloadflow/ac/equations/HvdcAcEmulationSide1ActiveFlowEquationTerm.java b/src/main/java/com/powsybl/openloadflow/ac/equations/HvdcAcEmulationSide1ActiveFlowEquationTerm.java index 0b58fc63e0..6b6399766d 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/equations/HvdcAcEmulationSide1ActiveFlowEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/ac/equations/HvdcAcEmulationSide1ActiveFlowEquationTerm.java @@ -7,6 +7,7 @@ */ package com.powsybl.openloadflow.ac.equations; +import com.powsybl.iidm.network.TwoSides; import com.powsybl.openloadflow.equations.Variable; import com.powsybl.openloadflow.equations.VariableSet; import com.powsybl.openloadflow.network.LfBus; @@ -23,31 +24,29 @@ public HvdcAcEmulationSide1ActiveFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfBus super(hvdc, bus1, bus2, variableSet); } - private double p1(double ph1, double ph2) { - double rawP = rawP(p0, k, ph1, ph2); - double boundedP = boundedP(rawP); - return (isController(rawP) ? 1 : getVscLossMultiplier()) * boundedP; - } - - private static boolean isController(double rawP) { - return rawP >= 0; + private double getSide1LossMultiplier() { + return element.getAcEmulationControl().getFeedingSide() == TwoSides.ONE ? 1 : getVscLossMultiplier(); } - private boolean isInOperatingRange(double rawP) { - return rawP < pMaxFromCS1toCS2 && rawP > -pMaxFromCS2toCS1; + private double p1(double ph1, double ph2) { + double boundedP = switch (element.getAcEmulationControl().getAcEmulationStatus()) { + case FREE -> rawP(p0, k, ph1, ph2); + case BOUNDED -> element.getAcEmulationControl().getFeedingSide() == TwoSides.ONE ? pMaxFromCS1toCS2 : -pMaxFromCS2toCS1; + default -> 0; + }; + return getSide1LossMultiplier() * boundedP; } - protected double dp1dph1(double ph1, double ph2) { - double rawP = rawP(p0, k, ph1, ph2); - if (isInOperatingRange(rawP)) { - return (isController(rawP) ? 1 : getVscLossMultiplier()) * k; + protected double dp1dph1() { + if (element.getAcEmulationControl().getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.FREE) { + return getSide1LossMultiplier() * k; } else { return 0; } } - protected double dp1dph2(double ph1, double ph2) { - return -dp1dph1(ph1, ph2); + protected double dp1dph2() { + return -dp1dph1(); } @Override @@ -59,9 +58,9 @@ public double eval() { public double der(Variable variable) { Objects.requireNonNull(variable); if (variable.equals(ph1Var)) { - return dp1dph1(ph1(), ph2()); + return dp1dph1(); } else if (variable.equals(ph2Var)) { - return dp1dph2(ph1(), ph2()); + return dp1dph2(); } else { throw new IllegalStateException("Unknown variable: " + variable); } diff --git a/src/main/java/com/powsybl/openloadflow/ac/equations/HvdcAcEmulationSide2ActiveFlowEquationTerm.java b/src/main/java/com/powsybl/openloadflow/ac/equations/HvdcAcEmulationSide2ActiveFlowEquationTerm.java index 501f7eca1d..f596c939f1 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/equations/HvdcAcEmulationSide2ActiveFlowEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/ac/equations/HvdcAcEmulationSide2ActiveFlowEquationTerm.java @@ -7,6 +7,7 @@ */ package com.powsybl.openloadflow.ac.equations; +import com.powsybl.iidm.network.TwoSides; import com.powsybl.openloadflow.equations.Variable; import com.powsybl.openloadflow.equations.VariableSet; import com.powsybl.openloadflow.network.LfBus; @@ -23,31 +24,29 @@ public HvdcAcEmulationSide2ActiveFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfBus super(hvdc, bus1, bus2, variableSet); } - private double p2(double ph1, double ph2) { - double rawP = rawP(p0, k, ph1, ph2); - double boundedP = boundedP(rawP); - return -(isController(rawP) ? 1 : getVscLossMultiplier()) * boundedP; - } - - private boolean isController(double rawP) { - return rawP < 0; + private double getSide2LossMultiplier() { + return element.getAcEmulationControl().getFeedingSide() == TwoSides.TWO ? 1 : getVscLossMultiplier(); } - private boolean isInOperatingRange(double rawP) { - return rawP < pMaxFromCS2toCS1 && rawP > -pMaxFromCS1toCS2; + private double p2(double ph1, double ph2) { + double boundedP = switch (element.getAcEmulationControl().getAcEmulationStatus()) { + case FREE -> -rawP(p0, k, ph1, ph2); + case BOUNDED -> element.getAcEmulationControl().getFeedingSide() == TwoSides.TWO ? pMaxFromCS2toCS1 : -pMaxFromCS1toCS2; + default -> 0; + }; + return getSide2LossMultiplier() * boundedP; } - private double dp2dph1(double ph1, double ph2) { - double rawP = rawP(p0, k, ph1, ph2); - if (isInOperatingRange(rawP)) { - return -(isController(rawP) ? 1 : getVscLossMultiplier()) * k; + private double dp2dph1() { + if (element.getAcEmulationControl().getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.FREE) { + return -getSide2LossMultiplier() * k; } else { return 0; } } - private double dp2dph2(double ph1, double ph2) { - return -dp2dph1(ph1, ph2); + private double dp2dph2() { + return -dp2dph1(); } @Override @@ -59,9 +58,9 @@ public double eval() { public double der(Variable variable) { Objects.requireNonNull(variable); if (variable.equals(ph1Var)) { - return dp2dph1(ph1(), ph2()); + return dp2dph1(); } else if (variable.equals(ph2Var)) { - return dp2dph2(ph1(), ph2()); + return dp2dph2(); } else { throw new IllegalStateException("Unknown variable: " + variable); } diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/AcHvdcAcEmulationOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/AcHvdcAcEmulationOuterLoop.java new file mode 100644 index 0000000000..834d463a71 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/AcHvdcAcEmulationOuterLoop.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2023, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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.openloadflow.ac.outerloop; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.TwoSides; +import com.powsybl.openloadflow.ac.AcLoadFlowContext; +import com.powsybl.openloadflow.ac.AcLoadFlowParameters; +import com.powsybl.openloadflow.ac.AcOuterLoopContext; +import com.powsybl.openloadflow.ac.equations.AcEquationType; +import com.powsybl.openloadflow.ac.equations.AcVariableType; +import com.powsybl.openloadflow.lf.outerloop.AbstractHvdcAcEmulationOuterLoop; +import com.powsybl.openloadflow.lf.outerloop.OuterLoopResult; +import com.powsybl.openloadflow.lf.outerloop.OuterLoopStatus; +import com.powsybl.openloadflow.network.LfHvdc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hadrien Godard {@literal } + */ +public class AcHvdcAcEmulationOuterLoop + extends AbstractHvdcAcEmulationOuterLoop + implements AcOuterLoop { + + private static final Logger LOGGER = LoggerFactory.getLogger(AcHvdcAcEmulationOuterLoop.class); + public static final String NAME = "AcHvdcAcEmulation"; + + @Override + public String getName() { + return NAME; + } + + private boolean checkFeedingSide(LfHvdc hvdc, ContextData contextData) { + String hvdcId = hvdc.getId(); + LfHvdc.AcEmulationControl acEmulationControl = hvdc.getAcEmulationControl(); + + if (acEmulationControl.getFeedingSide() == TwoSides.ONE) { + if (hvdc.getP1().eval() < 0) { + // Switch feeding side + LOGGER.trace("Switching feeding side from One to Two for Hvdc: {}", hvdcId); + contextData.incrementFeedingSideSwitchCount(hvdcId); + hvdc.updateFeedingSide(TwoSides.TWO); + if (contextData.getFeedingSideSwitchCount(hvdcId) == MAX_FEEDING_SIDE_SWITCH) { + LOGGER.debug("Two many feeding side switches (flow blocked to 0 MW) for Hvdc: {}", hvdcId); + hvdc.updateAcEmulationStatus(LfHvdc.AcEmulationControl.AcEmulationStatus.NULL); + } + return true; + } + } else { + if (hvdc.getP2().eval() < 0) { + // Switch feeding side + LOGGER.trace("Switching feeding side from Two to One for Hvdc: {}", hvdcId); + contextData.incrementFeedingSideSwitchCount(hvdcId); + hvdc.updateFeedingSide(TwoSides.ONE); + if (contextData.getFeedingSideSwitchCount(hvdcId) == MAX_FEEDING_SIDE_SWITCH) { + LOGGER.debug("Two many feeding side switches (flow blocked to 0 MW) for Hvdc: {}", hvdcId); + hvdc.updateAcEmulationStatus(LfHvdc.AcEmulationControl.AcEmulationStatus.NULL); + } + return true; + } + } + return false; + } + + private boolean checkMode(LfHvdc hvdc, ContextData contextData) { + String hvdcId = hvdc.getId(); + LfHvdc.AcEmulationControl acEmulationControl = hvdc.getAcEmulationControl(); + + // Check for mode switch between FREE and BOUNDED + if (acEmulationControl.getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.FREE) { + // Check Pmax + if (acEmulationControl.getFeedingSide() == TwoSides.ONE) { + if (hvdc.getP1().eval() > acEmulationControl.getPMaxFromCS1toCS2()) { + // Switch mode + LOGGER.trace("Bound Hvdc flow to Pmax from CS1 to CS2 for Hvdc: {}", hvdcId); + contextData.incrementModeSwitchCount(hvdcId); + hvdc.updateAcEmulationStatus(LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED); + if (contextData.getModeSwitchCount(hvdcId) == MAX_MODE_SWITCH) { + LOGGER.debug("Two many mode switches (flow blocked to Pmax from CS1 to CS2) for Hvdc: {}", hvdcId); + } + return true; + } + } else { + if (hvdc.getP2().eval() > acEmulationControl.getPMaxFromCS2toCS1()) { + // Switch mode + LOGGER.trace("Bound Hvdc flow to Pmax from CS2 to CS1 for Hvdc: {}", hvdcId); + contextData.incrementModeSwitchCount(hvdcId); + hvdc.updateAcEmulationStatus(LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED); + if (contextData.getModeSwitchCount(hvdcId) == MAX_MODE_SWITCH) { + LOGGER.debug("Two many mode switches (flow blocked to Pmax from CS2 to CS1) for Hvdc: {}", hvdcId); + } + return true; + } + } + } + + // Check for mode switch between BOUNDED and FREE + if (acEmulationControl.getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED) { + if (acEmulationControl.getFeedingSide() == TwoSides.ONE) { + if (computeRawP1(hvdc) < acEmulationControl.getPMaxFromCS1toCS2()) { + // Switch mode + LOGGER.trace("Set free the Ac Emulation mode for Hvdc: {}", hvdcId); + hvdc.updateAcEmulationStatus(LfHvdc.AcEmulationControl.AcEmulationStatus.FREE); + return true; + } + } else { + if (computeRawP2(hvdc) < acEmulationControl.getPMaxFromCS2toCS1()) { + // Switch mode + LOGGER.trace("Set free the Ac Emulation mode for Hvdc: {}", hvdcId); + hvdc.updateAcEmulationStatus(LfHvdc.AcEmulationControl.AcEmulationStatus.FREE); + return true; + } + } + } + return false; + } + + @Override + public OuterLoopResult check(AcOuterLoopContext context, ReportNode reportNode) { + OuterLoopStatus status = OuterLoopStatus.STABLE; + ContextData contextData = (ContextData) context.getData(); + + for (LfHvdc hvdc : context.getNetwork().getHvdcs()) { + if (!hvdc.isAcEmulation() || hvdc.getBus1().isDisabled() || hvdc.getBus2().isDisabled() || hvdc.isDisabled()) { + continue; + } + String hvdcId = hvdc.getId(); + if (contextData.getFeedingSideSwitchCount(hvdcId) < MAX_FEEDING_SIDE_SWITCH && contextData.getModeSwitchCount(hvdcId) < MAX_MODE_SWITCH) { + // First check the feeding side + if (checkFeedingSide(hvdc, contextData)) { + status = OuterLoopStatus.UNSTABLE; + } + + // Second check for Pmax values + if (checkMode(hvdc, contextData)) { + status = OuterLoopStatus.UNSTABLE; + } + } + } + + return new OuterLoopResult(this, status); + } +} diff --git a/src/main/java/com/powsybl/openloadflow/dc/DcHvdcAcEmulationOuterLoop.java b/src/main/java/com/powsybl/openloadflow/dc/DcHvdcAcEmulationOuterLoop.java new file mode 100644 index 0000000000..db3bb7458c --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/dc/DcHvdcAcEmulationOuterLoop.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2023, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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.openloadflow.dc; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.TwoSides; +import com.powsybl.openloadflow.dc.equations.DcEquationType; +import com.powsybl.openloadflow.dc.equations.DcVariableType; +import com.powsybl.openloadflow.lf.outerloop.AbstractHvdcAcEmulationOuterLoop; +import com.powsybl.openloadflow.lf.outerloop.OuterLoopResult; +import com.powsybl.openloadflow.lf.outerloop.OuterLoopStatus; +import com.powsybl.openloadflow.network.LfHvdc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hadrien Godard {@literal } + */ +public class DcHvdcAcEmulationOuterLoop extends AbstractHvdcAcEmulationOuterLoop { + + private static final Logger LOGGER = LoggerFactory.getLogger(DcHvdcAcEmulationOuterLoop.class); + public static final String NAME = "DcHvdcAcEmulation"; + + @Override + public String getName() { + return NAME; + } + + private boolean checkMode(LfHvdc hvdc, ContextData contextData) { + String hvdcId = hvdc.getId(); + LfHvdc.AcEmulationControl acEmulationControl = hvdc.getAcEmulationControl(); + + // Check for mode switch between FREE and BOUNDED + if (acEmulationControl.getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.FREE) { + // Check Pmax + if (hvdc.getP1().eval() > acEmulationControl.getPMaxFromCS1toCS2()) { + hvdc.updateFeedingSide(TwoSides.ONE); + // Switch mode + LOGGER.trace("Bound Hvdc flow to Pmax from CS1 to CS2 for Hvdc: {}", hvdcId); + contextData.incrementModeSwitchCount(hvdcId); + hvdc.updateAcEmulationStatus(LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED); + if (contextData.getModeSwitchCount(hvdcId) == MAX_MODE_SWITCH) { + LOGGER.debug("Two many mode switches (flow blocked to Pmax from CS1 to CS2) for Hvdc: {}", hvdcId); + } + return true; + } + if (hvdc.getP2().eval() > acEmulationControl.getPMaxFromCS2toCS1()) { + hvdc.updateFeedingSide(TwoSides.TWO); + // Switch mode + LOGGER.trace("Bound Hvdc flow to Pmax from CS2 to CS1 for Hvdc: {}", hvdcId); + contextData.incrementModeSwitchCount(hvdcId); + hvdc.updateAcEmulationStatus(LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED); + if (contextData.getModeSwitchCount(hvdcId) == MAX_MODE_SWITCH) { + LOGGER.debug("Two many mode switches (flow blocked to Pmax from CS2 to CS1) for Hvdc: {}", hvdcId); + } + return true; + } + } + + // Check for mode switch between BOUNDED and FREE + if (acEmulationControl.getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED) { + if (computeRawP1(hvdc) < acEmulationControl.getPMaxFromCS1toCS2() && computeRawP2(hvdc) < acEmulationControl.getPMaxFromCS2toCS1()) { + // Switch mode + LOGGER.trace("Set free the Ac Emulation mode for Hvdc: {}", hvdcId); + hvdc.updateAcEmulationStatus(LfHvdc.AcEmulationControl.AcEmulationStatus.FREE); + return true; + } + } + return false; + } + + @Override + public OuterLoopResult check(DcOuterLoopContext context, ReportNode reportNode) { + OuterLoopStatus status = OuterLoopStatus.STABLE; + ContextData contextData = (ContextData) context.getData(); + + for (LfHvdc hvdc : context.getNetwork().getHvdcs()) { + if (!hvdc.isAcEmulation() || hvdc.getBus1().isDisabled() || hvdc.getBus2().isDisabled() || hvdc.isDisabled()) { + continue; + } + String hvdcId = hvdc.getId(); + if (contextData.getModeSwitchCount(hvdcId) < MAX_MODE_SWITCH) { + // Check for Pmax values + if (checkMode(hvdc, contextData)) { + status = OuterLoopStatus.UNSTABLE; + } + } + } + + return new OuterLoopResult(this, status); + } +} diff --git a/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java b/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java index 00e0808673..3cea650ad3 100644 --- a/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java +++ b/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java @@ -21,6 +21,7 @@ import com.powsybl.openloadflow.network.LfBus; import com.powsybl.openloadflow.network.LfNetwork; import com.powsybl.openloadflow.network.LfNetworkLoader; +import com.powsybl.openloadflow.network.LfNetworkParameters; import com.powsybl.openloadflow.network.util.ActivePowerDistribution; import com.powsybl.openloadflow.network.util.UniformValueVoltageInitializer; import com.powsybl.openloadflow.network.util.VoltageInitializer; @@ -69,20 +70,10 @@ public static void initStateVector(LfNetwork network, EquationSystem v : equationSystem.getIndex().getSortedVariablesToFind()) { switch (v.getType()) { - case BUS_PHI: - x[v.getRow()] = initializer.getAngle(network.getBus(v.getElementNum())); - break; - - case BRANCH_ALPHA1: - x[v.getRow()] = network.getBranch(v.getElementNum()).getPiModel().getA1(); - break; - - case DUMMY_P: - x[v.getRow()] = 0; - break; - - default: - throw new IllegalStateException("Unknown variable type " + v.getType()); + case BUS_PHI -> x[v.getRow()] = initializer.getAngle(network.getBus(v.getElementNum())); + case BRANCH_ALPHA1 -> x[v.getRow()] = network.getBranch(v.getElementNum()).getPiModel().getA1(); + case DUMMY_P -> x[v.getRow()] = 0; + default -> throw new IllegalStateException("Unknown variable type " + v.getType()); } } equationSystem.getStateVector().set(x); @@ -110,36 +101,59 @@ public static void updateNetwork(LfNetwork network, EquationSystem targetVector = context.getTargetVector(); - // outer loop initialization + // outer loops initialization DcIncrementalPhaseControlOuterLoop phaseShifterControlOuterLoop = new DcIncrementalPhaseControlOuterLoop(); - DcOuterLoopContext outerLoopContext = new DcOuterLoopContext(network); + DcOuterLoopContext phaseControlOuterLoopContext = new DcOuterLoopContext(network); if (parameters.getNetworkParameters().isPhaseControl()) { - phaseShifterControlOuterLoop.initialize(outerLoopContext); + phaseShifterControlOuterLoop.initialize(phaseControlOuterLoopContext); + } + DcHvdcAcEmulationOuterLoop acEmulationOuterLoop = new DcHvdcAcEmulationOuterLoop(); + DcOuterLoopContext acEmulationLoopContext = new DcOuterLoopContext(network); + if (parameters.getNetworkParameters().isHvdcAcEmulation()) { + acEmulationOuterLoop.initialize(acEmulationLoopContext); } initStateVector(network, equationSystem, new UniformValueVoltageInitializer()); @@ -188,9 +207,9 @@ public DcLoadFlowResult run() { equationSystem.getStateVector().set(targetVectorArray); updateNetwork(network, equationSystem, targetVectorArray); - // continue with PST active power control outer loop only if first linear system solution has succeeded - if (success && parameters.getNetworkParameters().isPhaseControl()) { - success = runPhaseControlOuterLoop(phaseShifterControlOuterLoop, outerLoopContext); + // continue with outer loops only if first linear system solution has succeeded + if (success) { + success = runOuterLoops(phaseShifterControlOuterLoop, phaseControlOuterLoopContext, acEmulationOuterLoop, acEmulationLoopContext, parameters.getNetworkParameters()); } // set all calculated voltages to NaN diff --git a/src/main/java/com/powsybl/openloadflow/dc/DcOuterLoopContext.java b/src/main/java/com/powsybl/openloadflow/dc/DcOuterLoopContext.java index 6a8e959f90..e5db5f170d 100644 --- a/src/main/java/com/powsybl/openloadflow/dc/DcOuterLoopContext.java +++ b/src/main/java/com/powsybl/openloadflow/dc/DcOuterLoopContext.java @@ -31,4 +31,8 @@ public int getIteration() { public void setIteration(int iteration) { this.iteration = iteration; } + + public void incrementIteration() { + this.iteration = iteration + 1; + } } diff --git a/src/main/java/com/powsybl/openloadflow/dc/equations/AbstractHvdcAcEmulationDcFlowEquationTerm.java b/src/main/java/com/powsybl/openloadflow/dc/equations/AbstractHvdcAcEmulationDcFlowEquationTerm.java index 6626c16c5c..2307895300 100644 --- a/src/main/java/com/powsybl/openloadflow/dc/equations/AbstractHvdcAcEmulationDcFlowEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/dc/equations/AbstractHvdcAcEmulationDcFlowEquationTerm.java @@ -26,6 +26,9 @@ public abstract class AbstractHvdcAcEmulationDcFlowEquationTerm extends Abstract protected final List> variables; protected final LfHvdc hvdc; protected final double k; + protected final double p0; + protected final double pMaxFromCS1toCS2; + protected final double pMaxFromCS2toCS1; protected AbstractHvdcAcEmulationDcFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfBus bus2, VariableSet variableSet) { super(hvdc); @@ -33,7 +36,10 @@ protected AbstractHvdcAcEmulationDcFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfB ph2Var = variableSet.getVariable(bus2.getNum(), DcVariableType.BUS_PHI); variables = List.of(ph1Var, ph2Var); this.hvdc = hvdc; - k = this.hvdc.getDroop() * 180 / Math.PI; + k = this.hvdc.getAcEmulationControl().getDroop() * 180 / Math.PI; + p0 = hvdc.getAcEmulationControl().getP0(); + pMaxFromCS1toCS2 = hvdc.getAcEmulationControl().getPMaxFromCS1toCS2(); + pMaxFromCS2toCS1 = hvdc.getAcEmulationControl().getPMaxFromCS2toCS1(); } @Override @@ -41,6 +47,10 @@ public List> getVariables() { return variables; } + protected double rawP(double p0, double k, double ph1, double ph2) { + return p0 + k * (ph1 - ph2); + } + protected double ph1() { return ph1(sv); } diff --git a/src/main/java/com/powsybl/openloadflow/dc/equations/HvdcAcEmulationSide1DCFlowEquationTerm.java b/src/main/java/com/powsybl/openloadflow/dc/equations/HvdcAcEmulationSide1DCFlowEquationTerm.java index ef20ed1742..6dc88fdd5a 100644 --- a/src/main/java/com/powsybl/openloadflow/dc/equations/HvdcAcEmulationSide1DCFlowEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/dc/equations/HvdcAcEmulationSide1DCFlowEquationTerm.java @@ -7,6 +7,7 @@ */ package com.powsybl.openloadflow.dc.equations; +import com.powsybl.iidm.network.TwoSides; import com.powsybl.openloadflow.equations.Variable; import com.powsybl.openloadflow.equations.VariableSet; import com.powsybl.openloadflow.network.LfBus; @@ -28,15 +29,23 @@ protected String getName() { @Override public double eval() { - return k * (ph1() - ph2()) + hvdc.getP0(); + if (element.getAcEmulationControl().getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED) { + return element.getAcEmulationControl().getFeedingSide() == TwoSides.ONE ? pMaxFromCS1toCS2 : -pMaxFromCS2toCS1; + } else { // free + return rawP(p0, k, ph1(), ph2()); + } } @Override public double der(Variable variable) { + double der = 0; + if (element.getAcEmulationControl().getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.FREE) { + der = k; + } if (variable.equals(ph1Var)) { - return k; + return der; } else if (variable.equals(ph2Var)) { - return -k; + return -der; } else { throw new IllegalStateException("Unknown variable: " + variable); } @@ -49,6 +58,10 @@ public boolean hasRhs() { @Override public double rhs() { - return hvdc.getP0(); + if (element.getAcEmulationControl().getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED) { + return element.getAcEmulationControl().getFeedingSide() == TwoSides.ONE ? pMaxFromCS1toCS2 : -pMaxFromCS2toCS1; + } else { // free + return p0; + } } } diff --git a/src/main/java/com/powsybl/openloadflow/dc/equations/HvdcAcEmulationSide2DCFlowEquationTerm.java b/src/main/java/com/powsybl/openloadflow/dc/equations/HvdcAcEmulationSide2DCFlowEquationTerm.java index dec88b3517..074fd430aa 100644 --- a/src/main/java/com/powsybl/openloadflow/dc/equations/HvdcAcEmulationSide2DCFlowEquationTerm.java +++ b/src/main/java/com/powsybl/openloadflow/dc/equations/HvdcAcEmulationSide2DCFlowEquationTerm.java @@ -7,6 +7,7 @@ */ package com.powsybl.openloadflow.dc.equations; +import com.powsybl.iidm.network.TwoSides; import com.powsybl.openloadflow.equations.Variable; import com.powsybl.openloadflow.equations.VariableSet; import com.powsybl.openloadflow.network.LfBus; @@ -28,15 +29,23 @@ protected String getName() { @Override public double eval() { - return k * (ph2() - ph1()) - hvdc.getP0(); + if (element.getAcEmulationControl().getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED) { + return element.getAcEmulationControl().getFeedingSide() == TwoSides.TWO ? pMaxFromCS2toCS1 : -pMaxFromCS1toCS2; + } else { // free + return -rawP(p0, k, ph1(), ph2()); + } } @Override public double der(Variable variable) { - if (variable.equals(ph1Var)) { - return -k; - } else if (variable.equals(ph2Var)) { - return k; + double der = 0; + if (element.getAcEmulationControl().getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.FREE) { + der = k; + } + if (variable.equals(ph2Var)) { + return der; + } else if (variable.equals(ph1Var)) { + return -der; } else { throw new IllegalStateException("Unknown variable: " + variable); } @@ -49,6 +58,10 @@ public boolean hasRhs() { @Override public double rhs() { - return -hvdc.getP0(); + if (element.getAcEmulationControl().getAcEmulationStatus() == LfHvdc.AcEmulationControl.AcEmulationStatus.BOUNDED) { + return element.getAcEmulationControl().getFeedingSide() == TwoSides.TWO ? pMaxFromCS2toCS1 : -pMaxFromCS1toCS2; + } else { // free + return -p0; + } } } diff --git a/src/main/java/com/powsybl/openloadflow/equations/TargetVector.java b/src/main/java/com/powsybl/openloadflow/equations/TargetVector.java index 3632ac3f37..b20f930034 100644 --- a/src/main/java/com/powsybl/openloadflow/equations/TargetVector.java +++ b/src/main/java/com/powsybl/openloadflow/equations/TargetVector.java @@ -7,6 +7,7 @@ */ package com.powsybl.openloadflow.equations; +import com.powsybl.iidm.network.TwoSides; import com.powsybl.openloadflow.network.*; import java.util.List; @@ -77,6 +78,16 @@ public void onDisableChange(LfElement element, boolean disabled) { } } } + + @Override + public void onHvdcAcEmulationStatusChange(LfHvdc hvdc, LfHvdc.AcEmulationControl.AcEmulationStatus acEmulationStatus) { + invalidateValues(); + } + + @Override + public void onHvdcAcEmulationFeedingSideChange(LfHvdc hvdc, TwoSides side) { + invalidateValues(); + } }; public TargetVector(LfNetwork network, EquationSystem equationSystem, Initializer initializer) { diff --git a/src/main/java/com/powsybl/openloadflow/lf/outerloop/AbstractHvdcAcEmulationOuterLoop.java b/src/main/java/com/powsybl/openloadflow/lf/outerloop/AbstractHvdcAcEmulationOuterLoop.java new file mode 100644 index 0000000000..c9c65c6e34 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/lf/outerloop/AbstractHvdcAcEmulationOuterLoop.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2023, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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.openloadflow.lf.outerloop; + +/** + * @author Hadrien Godard {@literal } + */ + +import com.powsybl.openloadflow.equations.Quantity; +import com.powsybl.openloadflow.lf.AbstractLoadFlowParameters; +import com.powsybl.openloadflow.lf.LoadFlowContext; +import com.powsybl.openloadflow.network.LfHvdc; +import org.apache.commons.lang3.mutable.MutableInt; + +import java.util.HashMap; +import java.util.Map; + +public abstract class AbstractHvdcAcEmulationOuterLoop & Quantity, + E extends Enum & Quantity, + P extends AbstractLoadFlowParameters, + C extends LoadFlowContext, + O extends OuterLoopContext> implements OuterLoop { + + protected static final int MAX_MODE_SWITCH = 2; + protected static final int MAX_FEEDING_SIDE_SWITCH = 2; + + protected static final class ContextData { + private final Map modeSwitchCount = new HashMap<>(); + private final Map feedingSideSwitchCount = new HashMap<>(); + + public void incrementModeSwitchCount(String hvdcId) { + modeSwitchCount.computeIfAbsent(hvdcId, k -> new MutableInt(0)) + .increment(); + } + + public void incrementFeedingSideSwitchCount(String hvdcId) { + feedingSideSwitchCount.computeIfAbsent(hvdcId, k -> new MutableInt(0)) + .increment(); + } + + public int getModeSwitchCount(String hvdcId) { + MutableInt counter = modeSwitchCount.get(hvdcId); + if (counter == null) { + return 0; + } + return counter.getValue(); + } + + public int getFeedingSideSwitchCount(String hvdcId) { + MutableInt counter = feedingSideSwitchCount.get(hvdcId); + if (counter == null) { + return 0; + } + return counter.getValue(); + } + } + + protected double computeRawP1(LfHvdc hvdc) { + double ph1 = hvdc.getBus1().getAngle(); + double ph2 = hvdc.getBus2().getAngle(); + double p0 = hvdc.getAcEmulationControl().getP0(); + double k = hvdc.getAcEmulationControl().getDroop(); + return p0 + k * (ph1 - ph2); + } + + protected double computeRawP2(LfHvdc hvdc) { + return -computeRawP1(hvdc); + } + + public void initialize(OuterLoopContext context) { + context.setData(new ContextData()); + } + +} diff --git a/src/main/java/com/powsybl/openloadflow/network/AbstractLfNetworkListener.java b/src/main/java/com/powsybl/openloadflow/network/AbstractLfNetworkListener.java index e4aa63bde3..bd5d55f2b5 100644 --- a/src/main/java/com/powsybl/openloadflow/network/AbstractLfNetworkListener.java +++ b/src/main/java/com/powsybl/openloadflow/network/AbstractLfNetworkListener.java @@ -115,4 +115,14 @@ public void onSlackBusChange(LfBus bus, boolean slack) { public void onReferenceBusChange(LfBus bus, boolean reference) { // empty } + + @Override + public void onHvdcAcEmulationStatusChange(LfHvdc hvdc, LfHvdc.AcEmulationControl.AcEmulationStatus acEmulationStatus) { + // empty + } + + @Override + public void onHvdcAcEmulationFeedingSideChange(LfHvdc hvdc, TwoSides side) { + // empty + } } diff --git a/src/main/java/com/powsybl/openloadflow/network/LfHvdc.java b/src/main/java/com/powsybl/openloadflow/network/LfHvdc.java index 4b07bafd52..6f6530be6f 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfHvdc.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfHvdc.java @@ -7,13 +7,69 @@ */ package com.powsybl.openloadflow.network; +import com.powsybl.iidm.network.TwoSides; import com.powsybl.openloadflow.util.Evaluable; +import com.powsybl.openloadflow.util.PerUnit; /** * @author Anne Tilloy {@literal } */ public interface LfHvdc extends LfElement { + class AcEmulationControl { + public enum AcEmulationStatus { + FREE, + BOUNDED, + NULL; + } + private final double droop; + private final double p0; + private final double pMaxFromCS1toCS2; + private final double pMaxFromCS2toCS1; + private AcEmulationStatus acEmulationStatus = AcEmulationStatus.FREE; + private TwoSides feedingSide; + + public AcEmulationControl(double droop, double p0, double pMaxFromCS1toCS2, double pMaxFromCS2toCS1) { + this.droop = droop; + this.p0 = p0; + this.pMaxFromCS1toCS2 = pMaxFromCS1toCS2; + this.pMaxFromCS2toCS1 = pMaxFromCS2toCS1; + this.feedingSide = (p0 >= 0) ? TwoSides.ONE : TwoSides.TWO; + } + + public double getDroop() { + return droop / PerUnit.SB; + } + + public double getP0() { + return p0 / PerUnit.SB; + } + + public double getPMaxFromCS1toCS2() { + return pMaxFromCS1toCS2 / PerUnit.SB; + } + + public double getPMaxFromCS2toCS1() { + return pMaxFromCS2toCS1 / PerUnit.SB; + } + + public AcEmulationStatus getAcEmulationStatus() { + return acEmulationStatus; + } + + public TwoSides getFeedingSide() { + return feedingSide; + } + + public void setAcEmulationStatus(AcEmulationStatus status) { + acEmulationStatus = status; + } + + public void setFeedingSide(TwoSides side) { + feedingSide = side; + } + } + LfBus getBus1(); LfBus getBus2(); @@ -28,10 +84,6 @@ public interface LfHvdc extends LfElement { Evaluable getP2(); - double getDroop(); - - double getP0(); - boolean isAcEmulation(); void setAcEmulation(boolean acEmulation); @@ -44,9 +96,11 @@ public interface LfHvdc extends LfElement { void setConverterStation2(LfVscConverterStation converterStation2); - void updateState(); + AcEmulationControl getAcEmulationControl(); + + void updateAcEmulationStatus(AcEmulationControl.AcEmulationStatus acEmulationStatus); - double getPMaxFromCS1toCS2(); + void updateFeedingSide(TwoSides side); - double getPMaxFromCS2toCS1(); + void updateState(); } diff --git a/src/main/java/com/powsybl/openloadflow/network/LfNetworkListener.java b/src/main/java/com/powsybl/openloadflow/network/LfNetworkListener.java index d6faf9a32b..a029b7cec7 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfNetworkListener.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfNetworkListener.java @@ -55,4 +55,8 @@ public interface LfNetworkListener { void onSlackBusChange(LfBus bus, boolean slack); void onReferenceBusChange(LfBus bus, boolean reference); + + void onHvdcAcEmulationStatusChange(LfHvdc hvdc, LfHvdc.AcEmulationControl.AcEmulationStatus acEmulationStatus); + + void onHvdcAcEmulationFeedingSideChange(LfHvdc hvdc, TwoSides side); } diff --git a/src/main/java/com/powsybl/openloadflow/network/LfNetworkListenerTracer.java b/src/main/java/com/powsybl/openloadflow/network/LfNetworkListenerTracer.java index ee7eb7d54a..8ee2246c5a 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfNetworkListenerTracer.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfNetworkListenerTracer.java @@ -167,4 +167,16 @@ public void onReferenceBusChange(LfBus bus, boolean reference) { LOGGER.trace("onReferenceBusChange(bus={}, reference={})", bus, reference); delegate.onReferenceBusChange(bus, reference); } + + @Override + public void onHvdcAcEmulationStatusChange(LfHvdc hvdc, LfHvdc.AcEmulationControl.AcEmulationStatus acEmulationStatus) { + LOGGER.trace("onHvdcAcEmulationStatusChange(hvdc={}, status={})", hvdc, acEmulationStatus); + delegate.onHvdcAcEmulationStatusChange(hvdc, acEmulationStatus); + } + + @Override + public void onHvdcAcEmulationFeedingSideChange(LfHvdc hvdc, TwoSides side) { + LOGGER.trace("onHvdcAcEmulationFeedingSideChange(hvdc={}, side={})", hvdc, side); + delegate.onHvdcAcEmulationFeedingSideChange(hvdc, side); + } } diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/LfHvdcImpl.java b/src/main/java/com/powsybl/openloadflow/network/impl/LfHvdcImpl.java index ffbe307e63..3dfc91cdf9 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/LfHvdcImpl.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/LfHvdcImpl.java @@ -8,6 +8,7 @@ package com.powsybl.openloadflow.network.impl; import com.powsybl.iidm.network.HvdcLine; +import com.powsybl.iidm.network.TwoSides; import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControl; import com.powsybl.iidm.network.extensions.HvdcOperatorActivePowerRange; import com.powsybl.openloadflow.network.*; @@ -33,19 +34,13 @@ public class LfHvdcImpl extends AbstractElement implements LfHvdc { private Evaluable p2 = NAN; - private double droop = Double.NaN; - - private double p0 = Double.NaN; - private LfVscConverterStation converterStation1; private LfVscConverterStation converterStation2; private boolean acEmulation; - private final double pMaxFromCS1toCS2; - - private final double pMaxFromCS2toCS1; + AcEmulationControl acEmulationControl; public LfHvdcImpl(String id, LfBus bus1, LfBus bus2, LfNetwork network, HvdcLine hvdcLine, boolean acEmulation) { super(network); @@ -53,18 +48,12 @@ public LfHvdcImpl(String id, LfBus bus1, LfBus bus2, LfNetwork network, HvdcLine this.bus1 = bus1; this.bus2 = bus2; HvdcAngleDroopActivePowerControl droopControl = hvdcLine.getExtension(HvdcAngleDroopActivePowerControl.class); + HvdcOperatorActivePowerRange powerRange = hvdcLine.getExtension(HvdcOperatorActivePowerRange.class); + double pMaxFromCS1toCS2 = (powerRange != null) ? powerRange.getOprFromCS1toCS2() : hvdcLine.getMaxP(); + double pMaxFromCS2toCS1 = (powerRange != null) ? powerRange.getOprFromCS2toCS1() : hvdcLine.getMaxP(); this.acEmulation = acEmulation && droopControl != null && droopControl.isEnabled(); if (this.acEmulation) { - droop = droopControl.getDroop(); - p0 = droopControl.getP0(); - } - HvdcOperatorActivePowerRange powerRange = hvdcLine.getExtension(HvdcOperatorActivePowerRange.class); - if (powerRange != null) { - pMaxFromCS1toCS2 = powerRange.getOprFromCS1toCS2(); - pMaxFromCS2toCS1 = powerRange.getOprFromCS2toCS1(); - } else { - pMaxFromCS2toCS1 = hvdcLine.getMaxP(); - pMaxFromCS1toCS2 = hvdcLine.getMaxP(); + acEmulationControl = new AcEmulationControl(droopControl.getDroop(), droopControl.getP0(), pMaxFromCS1toCS2, pMaxFromCS2toCS1); } } @@ -123,16 +112,6 @@ public Evaluable getP2() { return this.p2; } - @Override - public double getDroop() { - return droop / PerUnit.SB; - } - - @Override - public double getP0() { - return p0 / PerUnit.SB; - } - @Override public boolean isAcEmulation() { return acEmulation; @@ -166,20 +145,31 @@ public void setConverterStation2(LfVscConverterStation converterStation2) { } @Override - public void updateState() { - if (acEmulation) { - ((LfVscConverterStationImpl) converterStation1).getStation().getTerminal().setP(p1.eval() * PerUnit.SB); - ((LfVscConverterStationImpl) converterStation2).getStation().getTerminal().setP(p2.eval() * PerUnit.SB); + public AcEmulationControl getAcEmulationControl() { + return acEmulationControl; + } + + @Override + public void updateAcEmulationStatus(AcEmulationControl.AcEmulationStatus acEmulationStatus) { + acEmulationControl.setAcEmulationStatus(acEmulationStatus); + for (LfNetworkListener listener : network.getListeners()) { + listener.onHvdcAcEmulationStatusChange(this, acEmulationStatus); } } @Override - public double getPMaxFromCS1toCS2() { - return Double.isNaN(pMaxFromCS1toCS2) ? Double.MAX_VALUE : pMaxFromCS1toCS2 / PerUnit.SB; + public void updateFeedingSide(TwoSides side) { + acEmulationControl.setFeedingSide(side); + for (LfNetworkListener listener : network.getListeners()) { + listener.onHvdcAcEmulationFeedingSideChange(this, side); + } } @Override - public double getPMaxFromCS2toCS1() { - return Double.isNaN(pMaxFromCS1toCS2) ? Double.MAX_VALUE : pMaxFromCS2toCS1 / PerUnit.SB; + public void updateState() { + if (acEmulation) { + ((LfVscConverterStationImpl) converterStation1).getStation().getTerminal().setP(p1.eval() * PerUnit.SB); + ((LfVscConverterStationImpl) converterStation2).getStation().getTerminal().setP(p2.eval() * PerUnit.SB); + } } } diff --git a/src/test/java/com/powsybl/openloadflow/EquationsTest.java b/src/test/java/com/powsybl/openloadflow/EquationsTest.java index c0b9d10bec..225c59da27 100644 --- a/src/test/java/com/powsybl/openloadflow/EquationsTest.java +++ b/src/test/java/com/powsybl/openloadflow/EquationsTest.java @@ -8,6 +8,7 @@ package com.powsybl.openloadflow; import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.TwoSides; import com.powsybl.math.matrix.DenseMatrix; import com.powsybl.openloadflow.ac.equations.*; import com.powsybl.openloadflow.dc.equations.ClosedBranchSide1DcFlowEquationTerm; @@ -17,6 +18,7 @@ import com.powsybl.openloadflow.equations.*; import com.powsybl.openloadflow.network.*; import com.powsybl.openloadflow.network.impl.LfVscConverterStationImpl; +import com.powsybl.openloadflow.util.PerUnit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -64,6 +66,14 @@ public Object answer(InvocationOnMock invocation) { private static final double LOSS_FACTOR_2 = 0.02400453453002384; private static final double G_SHUNT = 0.0000372472384299244; + private static LfHvdc.AcEmulationControl buildAcEmulationControl() { + LfHvdc.AcEmulationControl acEmulationControl = new LfHvdc.AcEmulationControl(DROOP * PerUnit.SB, P_0 * PerUnit.SB, Double.MAX_VALUE, Double.MAX_VALUE); + acEmulationControl.setFeedingSide(TwoSides.TWO); + return acEmulationControl; + } + + private static final LfHvdc.AcEmulationControl AC_EMULATION_CONTROL = buildAcEmulationControl(); + private static & Quantity, E extends Enum & Quantity> double[] eval(EquationTerm term, List> variables, StateVector sv) { term.setStateVector(sv); double[] values = new double[variables.size() + 2]; @@ -255,10 +265,7 @@ 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(); - Mockito.doReturn(Double.MAX_VALUE).when(hvdc).getPMaxFromCS1toCS2(); - Mockito.doReturn(Double.MAX_VALUE).when(hvdc).getPMaxFromCS2toCS1(); + Mockito.doReturn(AC_EMULATION_CONTROL).when(hvdc).getAcEmulationControl(); LfVscConverterStationImpl station1 = Mockito.mock(LfVscConverterStationImpl.class, new RuntimeExceptionAnswer()); LfVscConverterStationImpl station2 = Mockito.mock(LfVscConverterStationImpl.class, new RuntimeExceptionAnswer()); Mockito.doReturn(station1).when(hvdc).getConverterStation1(); diff --git a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java index 166ee44393..6d83c27c98 100644 --- a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java +++ b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java @@ -395,7 +395,7 @@ void testExplicitOuterLoopsParameter() { OpenLoadFlowParameters parametersExt = new OpenLoadFlowParameters() .setSecondaryVoltageControl(true); - assertEquals(List.of("DistributedSlack", "SecondaryVoltageControl", "VoltageMonitoring", "ReactiveLimits", "ShuntVoltageControl"), OpenLoadFlowParameters.createOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getType).toList()); + assertEquals(List.of("DistributedSlack", "AcHvdcAcEmulation", "SecondaryVoltageControl", "VoltageMonitoring", "ReactiveLimits", "ShuntVoltageControl"), OpenLoadFlowParameters.createOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getType).toList()); parametersExt.setOuterLoopNames(List.of("ReactiveLimits", "SecondaryVoltageControl")); assertEquals(List.of("ReactiveLimits", "SecondaryVoltageControl"), OpenLoadFlowParameters.createOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getType).toList()); @@ -408,7 +408,7 @@ void testExplicitOuterLoopsParameter() { e = assertThrows(PowsyblException.class, () -> OpenLoadFlowParameters.createOuterLoops(parameters, parametersExt)); assertEquals("Unknown outer loop 'Foo'", e.getMessage()); - assertEquals("Ordered explicit list of outer loop names, supported outer loops are IncrementalPhaseControl, DistributedSlack, IncrementalShuntVoltageControl, IncrementalTransformerVoltageControl, VoltageMonitoring, PhaseControl, ReactiveLimits, SecondaryVoltageControl, ShuntVoltageControl, SimpleTransformerVoltageControl, TransformerVoltageControl, AutomationSystem, IncrementalTransformerReactivePowerControl", + assertEquals("Ordered explicit list of outer loop names, supported outer loops are IncrementalPhaseControl, DistributedSlack, IncrementalShuntVoltageControl, IncrementalTransformerVoltageControl, VoltageMonitoring, PhaseControl, ReactiveLimits, SecondaryVoltageControl, ShuntVoltageControl, SimpleTransformerVoltageControl, TransformerVoltageControl, AutomationSystem, IncrementalTransformerReactivePowerControl, AcHvdcAcEmulation", OpenLoadFlowParameters.SPECIFIC_PARAMETERS.stream().filter(p -> p.getName().equals(OpenLoadFlowParameters.OUTER_LOOP_NAMES_PARAM_NAME)).findFirst().orElseThrow().getDescription()); } diff --git a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java index 762f4d34a9..5552907371 100644 --- a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java +++ b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java @@ -59,7 +59,7 @@ void testDcParameters() { void testAcParameters() { Network network = Mockito.mock(Network.class); AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network, new LoadFlowParameters().setReadSlackBus(true), new OpenLoadFlowParameters(), new DenseMatrixFactory(), new EvenShiloachGraphDecrementalConnectivityFactory<>(), false, false); - assertEquals("AcLoadFlowParameters(networkParameters=LfNetworkParameters(slackBusSelector=NetworkSlackBusSelector, connectivityFactory=EvenShiloachGraphDecrementalConnectivityFactory, generatorVoltageRemoteControl=true, minImpedance=false, twtSplitShuntAdmittance=false, breakers=false, plausibleActivePowerLimit=5000.0, computeMainConnectedComponentOnly=true, countriesToBalance=[], distributedOnConformLoad=false, phaseControl=false, transformerVoltageControl=false, voltagePerReactivePowerControl=false, generatorReactivePowerRemoteControl=false, transformerReactivePowerControl=false, loadFlowModel=AC, reactiveLimits=true, hvdcAcEmulation=true, minPlausibleTargetVoltage=0.8, maxPlausibleTargetVoltage=1.2, loaderPostProcessorSelection=[], reactiveRangeCheckMode=MAX, lowImpedanceThreshold=1.0E-8, svcVoltageMonitoring=true, maxSlackBusCount=1, debugDir=null, secondaryVoltageControl=false, cacheEnabled=false, asymmetrical=false, minNominalVoltageTargetVoltageCheck=20.0, linePerUnitMode=IMPEDANCE, useLoadModel=false, simulateAutomationSystems=false, referenceBusSelector=ReferenceBusFirstSlackSelector, voltageTargetPriorities=[GENERATOR, TRANSFORMER, SHUNT]), equationSystemCreationParameters=AcEquationSystemCreationParameters(forceA1Var=false), newtonRaphsonParameters=NewtonRaphsonParameters(maxIterations=15, minRealisticVoltage=0.5, maxRealisticVoltage=2.0, stoppingCriteria=DefaultNewtonRaphsonStoppingCriteria, stateVectorScalingMode=NONE, alwaysUpdateNetwork=false, lineSearchStateVectorScalingMaxIteration=10, lineSearchStateVectorScalingStepFold=1.3333333333333333, maxVoltageChangeStateVectorScalingMaxDv=0.1, maxVoltageChangeStateVectorScalingMaxDphi=0.17453292519943295), newtonKrylovParameters=NewtonKrylovParameters(maxIterations=100, lineSearch=false), outerLoops=[DistributedSlackOuterLoop, MonitoringVoltageOuterLoop, ReactiveLimitsOuterLoop], maxOuterLoopIterations=20, matrixFactory=DenseMatrixFactory, voltageInitializer=UniformValueVoltageInitializer, asymmetrical=false, slackDistributionFailureBehavior=LEAVE_ON_SLACK_BUS, solverFactory=NewtonRaphsonFactory, detailedReport=false)", + assertEquals("AcLoadFlowParameters(networkParameters=LfNetworkParameters(slackBusSelector=NetworkSlackBusSelector, connectivityFactory=EvenShiloachGraphDecrementalConnectivityFactory, generatorVoltageRemoteControl=true, minImpedance=false, twtSplitShuntAdmittance=false, breakers=false, plausibleActivePowerLimit=5000.0, computeMainConnectedComponentOnly=true, countriesToBalance=[], distributedOnConformLoad=false, phaseControl=false, transformerVoltageControl=false, voltagePerReactivePowerControl=false, generatorReactivePowerRemoteControl=false, transformerReactivePowerControl=false, loadFlowModel=AC, reactiveLimits=true, hvdcAcEmulation=true, minPlausibleTargetVoltage=0.8, maxPlausibleTargetVoltage=1.2, loaderPostProcessorSelection=[], reactiveRangeCheckMode=MAX, lowImpedanceThreshold=1.0E-8, svcVoltageMonitoring=true, maxSlackBusCount=1, debugDir=null, secondaryVoltageControl=false, cacheEnabled=false, asymmetrical=false, minNominalVoltageTargetVoltageCheck=20.0, linePerUnitMode=IMPEDANCE, useLoadModel=false, simulateAutomationSystems=false, referenceBusSelector=ReferenceBusFirstSlackSelector, voltageTargetPriorities=[GENERATOR, TRANSFORMER, SHUNT]), equationSystemCreationParameters=AcEquationSystemCreationParameters(forceA1Var=false), newtonRaphsonParameters=NewtonRaphsonParameters(maxIterations=15, minRealisticVoltage=0.5, maxRealisticVoltage=2.0, stoppingCriteria=DefaultNewtonRaphsonStoppingCriteria, stateVectorScalingMode=NONE, alwaysUpdateNetwork=false, lineSearchStateVectorScalingMaxIteration=10, lineSearchStateVectorScalingStepFold=1.3333333333333333, maxVoltageChangeStateVectorScalingMaxDv=0.1, maxVoltageChangeStateVectorScalingMaxDphi=0.17453292519943295), newtonKrylovParameters=NewtonKrylovParameters(maxIterations=100, lineSearch=false), outerLoops=[DistributedSlackOuterLoop, AcHvdcAcEmulationOuterLoop, MonitoringVoltageOuterLoop, ReactiveLimitsOuterLoop], maxOuterLoopIterations=20, matrixFactory=DenseMatrixFactory, voltageInitializer=UniformValueVoltageInitializer, asymmetrical=false, slackDistributionFailureBehavior=LEAVE_ON_SLACK_BUS, solverFactory=NewtonRaphsonFactory, detailedReport=false)", acParameters.toString()); } diff --git a/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowVscTest.java b/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowVscTest.java index 2620a9b960..0cc6679a6f 100644 --- a/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowVscTest.java +++ b/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowVscTest.java @@ -275,7 +275,7 @@ void testHvdcPowerAcEmulation() { @Test void testHvdcDirectionChangeAcEmulation() { - Network network = HvdcNetworkFactory.createHvdcInAcEmulationInSymetricNetwork(); + Network network = HvdcNetworkFactory.createHvdcInAcEmulationInSymmetricNetwork(); LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); LoadFlowResult result = loadFlowRunner.run(network, new LoadFlowParameters()); assertTrue(result.isFullyConverged()); diff --git a/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java b/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java index bd4a5f586d..d39cf4bce8 100644 --- a/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java +++ b/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java @@ -391,4 +391,13 @@ void testDcApproxIgnoreG() { assertEquals(307.436, line2.getTerminal1().getP(), 0.01); assertEquals(-307.436, line2.getTerminal2().getP(), 0.01); } + + @Test + void testDcAcEmulation() { + Network network = HvdcNetworkFactory.createHvdcInAcEmulation3BusesNetwork(); + network.getHvdcLine("hvdc23").setMaxP(2.); + loadFlowRunner.run(network, parameters); + assertEquals(2.0, network.getHvdcLine("hvdc23").getConverterStation1().getTerminal().getP(), 0.01); + assertEquals(1.0, network.getLine("l13").getTerminal1().getP(), 0.01); + } } diff --git a/src/test/java/com/powsybl/openloadflow/network/HvdcNetworkFactory.java b/src/test/java/com/powsybl/openloadflow/network/HvdcNetworkFactory.java index da5f0cae3b..09efe67a8b 100644 --- a/src/test/java/com/powsybl/openloadflow/network/HvdcNetworkFactory.java +++ b/src/test/java/com/powsybl/openloadflow/network/HvdcNetworkFactory.java @@ -582,7 +582,7 @@ public static Network createWithHvdcInAcEmulation() { * Initially, g1 is on. g2 is off. * @return */ - public static Network createHvdcInAcEmulationInSymetricNetwork() { + public static Network createHvdcInAcEmulationInSymmetricNetwork() { Network network = Network.create("test", "code"); Bus b1 = createBus(network, "b1"); Bus b2 = createBus(network, "b2"); @@ -603,6 +603,39 @@ public static Network createHvdcInAcEmulationInSymetricNetwork() { return network; } + /** + * ------------l12--------------- + * | | + * g1--b1--b2---vsc1--hvdc12--vsc2----b3--g3 + * | | + * l1 l3 + * + * Initially, g1 is on. g2 is off. + * @return + */ + public static Network createHvdcInAcEmulation3BusesNetwork() { + Network network = Network.create("test", "code"); + Bus b1 = createBus(network, "b1"); + Bus b2 = createBus(network, "b2"); + Bus b3 = createBus(network, "b3"); + createGenerator(b1, "g1", 5).setMaxP(10); + createGenerator(b3, "g3", 0).setMaxP(10); + createLoad(b1, "l1", 3); + createLoad(b3, "l3", 3); + createLine(network, b1, b2, "l12", 0.001f); + createLine(network, b1, b3, "l13", 0.1f); + + HvdcConverterStation cs1 = createVsc(b2, "cs2", 1.2d, 0d); + HvdcConverterStation cs2 = createVsc(b3, "cs3", 1.2d, 0d); + createHvdcLine(network, "hvdc23", cs1, cs2, 400, 0.1, 2) + .newExtension(HvdcAngleDroopActivePowerControlAdder.class) + .withDroop(1) + .withP0(0) + .withEnabled(true) + .add(); + return network; + } + /** *
      *     g1 - b1 -- l12 -- b2 -- hvdc23 -- b3 -- l34 -- b4 - l4
diff --git a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java
index 558c244ab1..4a03c7a4e7 100644
--- a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java
+++ b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java
@@ -1302,11 +1302,11 @@ void testHvdcAcEmulation() {
         SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, parameters);
 
         PreContingencyResult preContingencyResult = result.getPreContingencyResult();
-        assertEquals(-1.768, preContingencyResult.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER);
+        assertEquals(-1.766, preContingencyResult.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER);
 
         // post-contingency tests
         PostContingencyResult g1ContingencyResult = getPostContingencyResult(result, "g1");
-        assertEquals(-2.783, g1ContingencyResult.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER);
+        assertEquals(-2.780, g1ContingencyResult.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER);
 
         List contingencies2 = new ArrayList<>();
         contingencies2.add(Contingency.hvdcLine("hvdc34"));
@@ -1315,10 +1315,10 @@ void testHvdcAcEmulation() {
 
         // post-contingency tests
         PostContingencyResult hvdcContingencyResult = getPostContingencyResult(result2, "hvdc34");
-        assertEquals(-2.000, hvdcContingencyResult.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER);
+        assertEquals(-1.998, hvdcContingencyResult.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER);
 
         PostContingencyResult g1ContingencyResult2 = getPostContingencyResult(result, "g1");
-        assertEquals(-2.783, g1ContingencyResult2.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER);
+        assertEquals(-2.780, g1ContingencyResult2.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER);
     }
 
     @Test
diff --git a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java
index e61b270e67..4b9af0e11f 100644
--- a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java
+++ b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java
@@ -1279,7 +1279,7 @@ void testWrongShuntAction() {
 
     @Test
     void testVSCLossAcEmulation() {
-        // contingency leads to the lost of one converter station.
+        // contingency leads to the loss of one converter station.
         // contingency leads to zero active power transmission in the hvdc line.
         // but other converter station keeps its voltage control capability.
         // remedial action re-enables the ac emulation of the hvdc line.
diff --git a/src/test/resources/controllerShuntAlreadyInVoltageControlReport.txt b/src/test/resources/controllerShuntAlreadyInVoltageControlReport.txt
index 98f4429245..64ac125cc5 100644
--- a/src/test/resources/controllerShuntAlreadyInVoltageControlReport.txt
+++ b/src/test/resources/controllerShuntAlreadyInVoltageControlReport.txt
@@ -8,10 +8,12 @@
             Angle reference bus: vl2_0
             Slack bus: vl2_0
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          Outer loop ShuntVoltageControl
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          Outer loop ShuntVoltageControl
diff --git a/src/test/resources/detailedNrReportSecurityAnalysis.txt b/src/test/resources/detailedNrReportSecurityAnalysis.txt
index 8a323d826b..7ef1bba19f 100644
--- a/src/test/resources/detailedNrReportSecurityAnalysis.txt
+++ b/src/test/resources/detailedNrReportSecurityAnalysis.txt
@@ -66,6 +66,7 @@
                      Bus V: 0.995 pu, 8.551939024446748E-22 rad
                      Bus injection: 603.7693866643697 MW, 121.9404027609291 MVar
             Outer loop DistributedSlack
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
@@ -161,9 +162,11 @@
                      Bus Id: VL1_0 (nominalVoltage=400.0kV)
                      Bus V: 0.995 pu, 2.3836834369535747E-26 rad
                      Bus injection: 608.3346737740034 MW, 234.0235926831469 MVar
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             Outer loop DistributedSlack
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
diff --git a/src/test/resources/esgTutoReport.txt b/src/test/resources/esgTutoReport.txt
index 9714db7f95..bb0817bab5 100644
--- a/src/test/resources/esgTutoReport.txt
+++ b/src/test/resources/esgTutoReport.txt
@@ -9,9 +9,11 @@
          + Outer loop DistributedSlack
             + Outer loop iteration 1
                Slack bus active power (-1.4404045651219555 MW) distributed in 1 distribution iteration(s)
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
@@ -22,6 +24,7 @@
             Network balance: active generation=607.0 MW, active load=600.0 MW, reactive generation=0.0 MVar, reactive load=200.0 MVar
             Angle reference bus: VLLOAD_0
             Slack bus: VLLOAD_0
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
diff --git a/src/test/resources/esgTutoReportDetailedNrReportLf.txt b/src/test/resources/esgTutoReportDetailedNrReportLf.txt
index b0ddfeed51..5987aae5da 100644
--- a/src/test/resources/esgTutoReportDetailedNrReportLf.txt
+++ b/src/test/resources/esgTutoReportDetailedNrReportLf.txt
@@ -218,12 +218,14 @@
                   Bus Id: VLGEN_0 (nominalVoltage=24.0kV)
                   Bus V: 1.0208333333333333 pu, 0.04059595665621846 rad
                   Bus injection: 605.5584900201209 MW, 225.2825348319027 MVar
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          + Outer loop IncrementalTransformerVoltageControl
             + Outer loop iteration 2
                1 voltage-controlled buses are outside of their target deadbands
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          + Outer loop IncrementalTransformerVoltageControl
diff --git a/src/test/resources/esgTutoReportDetailedNrReportSensi.txt b/src/test/resources/esgTutoReportDetailedNrReportSensi.txt
index 6904bf38c8..5c0658679b 100644
--- a/src/test/resources/esgTutoReportDetailedNrReportSensi.txt
+++ b/src/test/resources/esgTutoReportDetailedNrReportSensi.txt
@@ -64,6 +64,7 @@
                   Bus Id: VLGEN_0 (nominalVoltage=24.0kV)
                   Bus V: 1.0208333333333333 pu, 0.04069359966489529 rad
                   Bus injection: 607.0001563589107 MW, 225.40357009583855 MVar
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
diff --git a/src/test/resources/generatorVoltageControlDiscarded.txt b/src/test/resources/generatorVoltageControlDiscarded.txt
index a42c147f6a..88d6c0ff2c 100644
--- a/src/test/resources/generatorVoltageControlDiscarded.txt
+++ b/src/test/resources/generatorVoltageControlDiscarded.txt
@@ -8,6 +8,7 @@
             Angle reference bus: b1_vl_0
             Slack bus: b1_vl_0
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          Outer loop SimpleTransformerVoltageControl
diff --git a/src/test/resources/generatorsConnectedToSameBusNotControllingSameBusReport.txt b/src/test/resources/generatorsConnectedToSameBusNotControllingSameBusReport.txt
index 366ec047f1..c61fea8acd 100644
--- a/src/test/resources/generatorsConnectedToSameBusNotControllingSameBusReport.txt
+++ b/src/test/resources/generatorsConnectedToSameBusNotControllingSameBusReport.txt
@@ -7,5 +7,6 @@
             Network balance: active generation=608.0 MW, active load=600.0 MW, reactive generation=0.0 MVar, reactive load=200.0 MVar
             Angle reference bus: VLGEN_0
             Slack bus: VLGEN_0
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
diff --git a/src/test/resources/multipleConnectedComponentsAcReport.txt b/src/test/resources/multipleConnectedComponentsAcReport.txt
index a7170efdb2..f014d774bd 100644
--- a/src/test/resources/multipleConnectedComponentsAcReport.txt
+++ b/src/test/resources/multipleConnectedComponentsAcReport.txt
@@ -7,6 +7,7 @@
             Angle reference bus: b1_vl_0
             Slack bus: b1_vl_0
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
diff --git a/src/test/resources/multipleConnectedComponentsDcReport.txt b/src/test/resources/multipleConnectedComponentsDcReport.txt
index f25d69cdc5..296bab6ba5 100644
--- a/src/test/resources/multipleConnectedComponentsDcReport.txt
+++ b/src/test/resources/multipleConnectedComponentsDcReport.txt
@@ -6,6 +6,7 @@
             Network balance: active generation=3.0 MW, active load=2.0 MW, reactive generation=0.0 MVar, reactive load=0.0 MVar
             Angle reference bus: b1_vl_0
             Slack bus: b1_vl_0
+         Outer loop DcHvdcAcEmulation
          DC load flow completed (status=true)
       + Network CC1 SC1
          + Network info
@@ -13,5 +14,6 @@
             Network balance: active generation=2.0 MW, active load=4.0 MW, reactive generation=0.0 MVar, reactive load=0.0 MVar
             Angle reference bus: b5_vl_0
             Slack bus: b5_vl_0
+         Outer loop DcHvdcAcEmulation
          DC load flow completed (status=true)
       No calculation will be done on 1 network(s) that have no generators
diff --git a/src/test/resources/saReport.txt b/src/test/resources/saReport.txt
index eed341c721..7b430696a8 100644
--- a/src/test/resources/saReport.txt
+++ b/src/test/resources/saReport.txt
@@ -10,9 +10,11 @@
             + Outer loop DistributedSlack
                + Outer loop iteration 1
                   Slack bus active power (-1.4404045651219555 MW) distributed in 1 distribution iteration(s)
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             Outer loop DistributedSlack
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
@@ -20,9 +22,11 @@
             + Outer loop DistributedSlack
                + Outer loop iteration 1
                   Slack bus active power (5.803741102800952 MW) distributed in 1 distribution iteration(s)
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             Outer loop DistributedSlack
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
@@ -30,9 +34,11 @@
             + Outer loop DistributedSlack
                + Outer loop iteration 1
                   Slack bus active power (5.803741102800952 MW) distributed in 1 distribution iteration(s)
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             Outer loop DistributedSlack
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
@@ -43,9 +49,11 @@
             + Outer loop DistributedSlack
                + Outer loop iteration 1
                   Slack bus active power (-5.4942194604343655 MW) distributed in 1 distribution iteration(s)
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             Outer loop DistributedSlack
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
diff --git a/src/test/resources/saReportOperatorStrategies.txt b/src/test/resources/saReportOperatorStrategies.txt
index c2833fb62e..9686039660 100644
--- a/src/test/resources/saReportOperatorStrategies.txt
+++ b/src/test/resources/saReportOperatorStrategies.txt
@@ -7,30 +7,37 @@
             Angle reference bus: VL2_0
             Slack bus: VL2_0
          + Pre-contingency simulation
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
          + Post-contingency simulation 'L1'
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
             + Operator strategy simulation 'strategyL1'
+               Outer loop AcHvdcAcEmulation
                Outer loop VoltageMonitoring
                Outer loop ReactiveLimits
                AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
          + Post-contingency simulation 'L3'
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
             + Operator strategy simulation 'strategyL3'
+               Outer loop AcHvdcAcEmulation
                Outer loop VoltageMonitoring
                Outer loop ReactiveLimits
                AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
          + Post-contingency simulation 'L2'
+            Outer loop AcHvdcAcEmulation
             Outer loop VoltageMonitoring
             Outer loop ReactiveLimits
             AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
             + Operator strategy simulation 'strategyL2'
+               Outer loop AcHvdcAcEmulation
                Outer loop VoltageMonitoring
                Outer loop ReactiveLimits
                AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
diff --git a/src/test/resources/sharedGeneratorRemoteReactivePowerControlReport.txt b/src/test/resources/sharedGeneratorRemoteReactivePowerControlReport.txt
index 9e9e4fd45e..893c4bba65 100644
--- a/src/test/resources/sharedGeneratorRemoteReactivePowerControlReport.txt
+++ b/src/test/resources/sharedGeneratorRemoteReactivePowerControlReport.txt
@@ -7,5 +7,6 @@
             Network balance: active generation=3.0 MW, active load=5.0 MW, reactive generation=0.0 MVar, reactive load=0.0 MVar
             Angle reference bus: b1_vl_0
             Slack bus: b1_vl_0
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)
diff --git a/src/test/resources/shuntVoltageControlDiscarded.txt b/src/test/resources/shuntVoltageControlDiscarded.txt
index 3e3ff71efe..0296e2f43b 100644
--- a/src/test/resources/shuntVoltageControlDiscarded.txt
+++ b/src/test/resources/shuntVoltageControlDiscarded.txt
@@ -8,10 +8,12 @@
             Angle reference bus: vl2_0
             Slack bus: vl2_0
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          Outer loop ShuntVoltageControl
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          Outer loop ShuntVoltageControl
diff --git a/src/test/resources/shuntVoltageControlOuterLoopReport.txt b/src/test/resources/shuntVoltageControlOuterLoopReport.txt
index 4347afc3f7..bd8ab5f265 100644
--- a/src/test/resources/shuntVoltageControlOuterLoopReport.txt
+++ b/src/test/resources/shuntVoltageControlOuterLoopReport.txt
@@ -53,6 +53,7 @@
                   Bus V: 0.975 pu, 0.0010125504427887688 rad
                   Bus injection: 101.36618622358611 MW, 150.64984395641986 MVar
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          + Outer loop IncrementalShuntVoltageControl
@@ -105,6 +106,7 @@
                   Bus V: 0.975 pu, 0.002014826586019094 rad
                   Bus injection: 101.3664080925296 MW, -2.165165450231683 MVar
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          Outer loop IncrementalShuntVoltageControl
diff --git a/src/test/resources/transformerReactivePowerControlOuterLoopReport.txt b/src/test/resources/transformerReactivePowerControlOuterLoopReport.txt
index 327991105f..844c674434 100644
--- a/src/test/resources/transformerReactivePowerControlOuterLoopReport.txt
+++ b/src/test/resources/transformerReactivePowerControlOuterLoopReport.txt
@@ -97,6 +97,7 @@
                   Bus Id: VL_1_0 (nominalVoltage=132.0kV)
                   Bus V: 1.0227272727272727 pu, 0.011752202697884006 rad
                   Bus injection: 22.111404488893672 MW, 7.616219016989096 MVar
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          + Outer loop IncrementalTransformerReactivePowerControl
@@ -244,12 +245,14 @@
                   Bus Id: VL_1_0 (nominalVoltage=132.0kV)
                   Bus V: 1.0227272727272727 pu, 0.013083707387261096 rad
                   Bus injection: 24.491420280899572 MW, 7.284141028025306 MVar
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          + Outer loop IncrementalTransformerReactivePowerControl
             + Outer loop iteration 5
                1 reactive power-controlled branches are outside of their target deadbands
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          + Outer loop IncrementalTransformerReactivePowerControl
diff --git a/src/test/resources/transformerVoltageControlDiscarded.txt b/src/test/resources/transformerVoltageControlDiscarded.txt
index c3d7024b31..bb6a8cc51d 100644
--- a/src/test/resources/transformerVoltageControlDiscarded.txt
+++ b/src/test/resources/transformerVoltageControlDiscarded.txt
@@ -10,10 +10,12 @@
          + Outer loop DistributedSlack
             + Outer loop iteration 1
                Slack bus active power (-2.8882610545475753 MW) distributed in 1 distribution iteration(s)
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          Outer loop SimpleTransformerVoltageControl
          Outer loop DistributedSlack
+         Outer loop AcHvdcAcEmulation
          Outer loop VoltageMonitoring
          Outer loop ReactiveLimits
          Outer loop SimpleTransformerVoltageControl