diff --git a/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystemCreator.java b/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystemCreator.java index 13ea6a1ca8..08424e3dfb 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystemCreator.java +++ b/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystemCreator.java @@ -930,7 +930,7 @@ static void updateBranchEquations(LfBranch branch) { private static void createHvdcAcEmulationEquations(LfHvdc hvdc, EquationSystem equationSystem) { EquationTerm p1 = null; EquationTerm p2 = null; - if (hvdc.getBus1() != null && hvdc.getBus2() != null) { + if (hvdc.getBus1() != null && hvdc.getBus2() != null && hvdc.isAcEmulation()) { p1 = new HvdcAcEmulationSide1ActiveFlowEquationTerm(hvdc, hvdc.getBus1(), hvdc.getBus2(), equationSystem.getVariableSet()); p2 = new HvdcAcEmulationSide2ActiveFlowEquationTerm(hvdc, hvdc.getBus1(), hvdc.getBus2(), equationSystem.getVariableSet()); } else { diff --git a/src/main/java/com/powsybl/openloadflow/network/HvdcState.java b/src/main/java/com/powsybl/openloadflow/network/HvdcState.java index 412f225b1c..9f0f95ea4e 100644 --- a/src/main/java/com/powsybl/openloadflow/network/HvdcState.java +++ b/src/main/java/com/powsybl/openloadflow/network/HvdcState.java @@ -11,11 +11,20 @@ */ public class HvdcState extends ElementState { + private boolean acEmulation; + public HvdcState(LfHvdc hvdc) { super(hvdc); + this.acEmulation = hvdc.isAcEmulation(); } public static HvdcState save(LfHvdc hvdc) { return new HvdcState(hvdc); } + + @Override + public void restore() { + super.restore(); + element.setAcEmulation(acEmulation); + } } diff --git a/src/main/java/com/powsybl/openloadflow/network/LfAction.java b/src/main/java/com/powsybl/openloadflow/network/LfAction.java index 8306346c4b..6a60c99028 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfAction.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfAction.java @@ -365,9 +365,10 @@ public void apply(LfNetworkParameters networkParameters) { } if (hvdc != null) { - hvdc.setDisabled(true); - hvdc.getConverterStation1().setTargetP(-hvdc.getP1().eval()); - hvdc.getConverterStation2().setTargetP(-hvdc.getP2().eval()); + hvdc.setAcEmulation(false); + hvdc.setDisabled(true); // for equations only, but should be hidden + hvdc.getConverterStation1().setTargetP(-hvdc.getP1().eval()); // override + hvdc.getConverterStation2().setTargetP(-hvdc.getP2().eval()); // override } } } diff --git a/src/main/java/com/powsybl/openloadflow/network/LfBus.java b/src/main/java/com/powsybl/openloadflow/network/LfBus.java index 126a64703d..9ff7c019a6 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfBus.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfBus.java @@ -149,6 +149,8 @@ default double getHighVoltageLimit() { void addBranch(LfBranch branch); + List getHvdcs(); + void addHvdc(LfHvdc hvdc); void updateState(LfNetworkStateUpdateParameters parameters); diff --git a/src/main/java/com/powsybl/openloadflow/network/LfContingency.java b/src/main/java/com/powsybl/openloadflow/network/LfContingency.java index 05707e2ec6..5744eadaeb 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfContingency.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfContingency.java @@ -41,8 +41,10 @@ public class LfContingency { private final Set disconnectedElementIds; + private final Set hvdcsWithoutPower; + public LfContingency(String id, int index, int createdSynchronousComponentsCount, DisabledNetwork disabledNetwork, Map shuntsShift, - Map lostLoads, Set lostGenerators) { + Map lostLoads, Set lostGenerators, Set hvdcsWithoutPower) { this.id = Objects.requireNonNull(id); this.index = index; this.createdSynchronousComponentsCount = createdSynchronousComponentsCount; @@ -50,6 +52,7 @@ public LfContingency(String id, int index, int createdSynchronousComponentsCount this.shuntsShift = Objects.requireNonNull(shuntsShift); this.lostLoads = Objects.requireNonNull(lostLoads); this.lostGenerators = Objects.requireNonNull(lostGenerators); + this.hvdcsWithoutPower = Objects.requireNonNull(hvdcsWithoutPower); this.disconnectedLoadActivePower = 0.0; this.disconnectedGenerationActivePower = 0.0; this.disconnectedElementIds = new HashSet<>(); @@ -179,6 +182,10 @@ public void apply(LoadFlowParameters.BalanceType balanceType) { bus.setGeneratorReactivePowerControlEnabled(false); } } + for (LfHvdc hvdc : hvdcsWithoutPower) { + hvdc.getConverterStation1().setTargetP(0.0); + hvdc.getConverterStation2().setTargetP(0.0); + } } private static double getUpdatedLoadP0(LfLoad load, LoadFlowParameters.BalanceType balanceType, double initialP0, double initialVariableActivePower) { diff --git a/src/main/java/com/powsybl/openloadflow/network/LfHvdc.java b/src/main/java/com/powsybl/openloadflow/network/LfHvdc.java index 1a2427bd8f..3dc109cdcc 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfHvdc.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfHvdc.java @@ -17,6 +17,8 @@ public interface LfHvdc extends LfElement { LfBus getBus2(); + LfBus getOtherBus(LfBus bus); + void setP1(Evaluable p1); Evaluable getP1(); @@ -29,6 +31,10 @@ public interface LfHvdc extends LfElement { double getP0(); + boolean isAcEmulation(); + + void setAcEmulation(boolean acEmulation); + LfVscConverterStation getConverterStation1(); LfVscConverterStation getConverterStation2(); diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java b/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java index de8a843d26..37e2451385 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java @@ -482,6 +482,11 @@ public void addBranch(LfBranch branch) { branches.add(Objects.requireNonNull(branch)); } + @Override + public List getHvdcs() { + return hvdcs; + } + @Override public void addHvdc(LfHvdc hvdc) { hvdcs.add(Objects.requireNonNull(hvdc)); @@ -711,6 +716,14 @@ public void setDisabled(boolean disabled) { if (controllerShunt != null) { controllerShunt.setDisabled(disabled); } + for (LfHvdc hvdc : hvdcs) { + if (disabled) { + hvdc.setDisabled(true); + } else if (!hvdc.getOtherBus(this).isDisabled()) { + // if both buses enabled only + hvdc.setDisabled(false); + } + } } @Override 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 806444a133..0ee0d3660f 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/LfHvdcImpl.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/LfHvdcImpl.java @@ -30,22 +30,27 @@ public class LfHvdcImpl extends AbstractElement implements LfHvdc { private Evaluable p2 = NAN; - private final double droop; + private double droop = Double.NaN; - private final double p0; + private double p0 = Double.NaN; private LfVscConverterStation converterStation1; private LfVscConverterStation converterStation2; - public LfHvdcImpl(String id, LfBus bus1, LfBus bus2, LfNetwork network, HvdcAngleDroopActivePowerControl control) { + private boolean acEmulation; + + public LfHvdcImpl(String id, LfBus bus1, LfBus bus2, LfNetwork network, HvdcAngleDroopActivePowerControl control, + boolean acEmulation) { super(network); this.id = Objects.requireNonNull(id); this.bus1 = bus1; this.bus2 = bus2; - Objects.requireNonNull(control); - droop = control.getDroop(); - p0 = control.getP0(); + this.acEmulation = acEmulation && control != null && control.isEnabled(); + if (this.acEmulation) { + droop = control.getDroop(); + p0 = control.getP0(); + } } @Override @@ -68,6 +73,21 @@ public LfBus getBus2() { return this.bus2; } + @Override + public LfBus getOtherBus(LfBus bus) { + return bus.equals(bus1) ? bus2 : bus1; + } + + @Override + public void setDisabled(boolean disabled) { + super.setDisabled(disabled); // for AC emulation equations only. + if (!acEmulation && !disabled) { + // re-active power transmission to initial target values. + converterStation1.setTargetP(converterStation1.getInitialTargetP()); + converterStation2.setTargetP(converterStation2.getInitialTargetP()); + } + } + @Override public void setP1(Evaluable p1) { this.p1 = Objects.requireNonNull(p1); @@ -98,6 +118,16 @@ public double getP0() { return p0 / PerUnit.SB; } + @Override + public boolean isAcEmulation() { + return acEmulation; + } + + @Override + public void setAcEmulation(boolean acEmulation) { + this.acEmulation = acEmulation; + } + @Override public LfVscConverterStation getConverterStation1() { return converterStation1; @@ -122,7 +152,9 @@ public void setConverterStation2(LfVscConverterStation converterStation2) { @Override public void updateState() { - ((LfVscConverterStationImpl) converterStation1).getStation().getTerminal().setP(p1.eval() * PerUnit.SB); - ((LfVscConverterStationImpl) converterStation2).getStation().getTerminal().setP(p2.eval() * PerUnit.SB); + if (acEmulation) { + ((LfVscConverterStationImpl) converterStation1).getStation().getTerminal().setP(p1.eval() * PerUnit.SB); + ((LfVscConverterStationImpl) converterStation2).getStation().getTerminal().setP(p2.eval() * PerUnit.SB); + } } } diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/LfNetworkLoaderImpl.java b/src/main/java/com/powsybl/openloadflow/network/impl/LfNetworkLoaderImpl.java index 447ba769e4..7576096a1f 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/LfNetworkLoaderImpl.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/LfNetworkLoaderImpl.java @@ -482,23 +482,19 @@ private static void createBranches(List lfBuses, LfNetwork lfNetwork, LfT } } - if (parameters.isHvdcAcEmulation()) { - for (HvdcLine hvdcLine : loadingContext.hvdcLineSet) { - HvdcAngleDroopActivePowerControl control = hvdcLine.getExtension(HvdcAngleDroopActivePowerControl.class); - if (control != null && control.isEnabled()) { - LfBus lfBus1 = getLfBus(hvdcLine.getConverterStation1().getTerminal(), lfNetwork, parameters.isBreakers()); - LfBus lfBus2 = getLfBus(hvdcLine.getConverterStation2().getTerminal(), lfNetwork, parameters.isBreakers()); - LfVscConverterStationImpl cs1 = (LfVscConverterStationImpl) lfNetwork.getGeneratorById(hvdcLine.getConverterStation1().getId()); - LfVscConverterStationImpl cs2 = (LfVscConverterStationImpl) lfNetwork.getGeneratorById(hvdcLine.getConverterStation2().getId()); - if (cs1 != null && cs2 != null) { - LfHvdc lfHvdc = new LfHvdcImpl(hvdcLine.getId(), lfBus1, lfBus2, lfNetwork, control); - lfHvdc.setConverterStation1((LfVscConverterStationImpl) lfNetwork.getGeneratorById(hvdcLine.getConverterStation1().getId())); - lfHvdc.setConverterStation2((LfVscConverterStationImpl) lfNetwork.getGeneratorById(hvdcLine.getConverterStation2().getId())); - lfNetwork.addHvdc(lfHvdc); - } else { - LOGGER.warn("Hvdc line '{}' in AC emulation but converter stations are not in the same synchronous component: operated using active set point.", hvdcLine.getId()); - } - } + for (HvdcLine hvdcLine : loadingContext.hvdcLineSet) { + LfBus lfBus1 = getLfBus(hvdcLine.getConverterStation1().getTerminal(), lfNetwork, parameters.isBreakers()); + LfBus lfBus2 = getLfBus(hvdcLine.getConverterStation2().getTerminal(), lfNetwork, parameters.isBreakers()); + LfVscConverterStationImpl cs1 = (LfVscConverterStationImpl) lfNetwork.getGeneratorById(hvdcLine.getConverterStation1().getId()); + LfVscConverterStationImpl cs2 = (LfVscConverterStationImpl) lfNetwork.getGeneratorById(hvdcLine.getConverterStation2().getId()); + HvdcAngleDroopActivePowerControl control = hvdcLine.getExtension(HvdcAngleDroopActivePowerControl.class); + if (cs1 != null && cs2 != null) { + LfHvdc lfHvdc = new LfHvdcImpl(hvdcLine.getId(), lfBus1, lfBus2, lfNetwork, control, parameters.isHvdcAcEmulation()); + lfHvdc.setConverterStation1(cs1); + lfHvdc.setConverterStation2(cs2); + lfNetwork.addHvdc(lfHvdc); + } else { + LOGGER.warn("The converter stations of hvdc line {} are not in the same synchronous component: no hvdc link created to model active power flow.", hvdcLine.getId()); } } } diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/LfVscConverterStationImpl.java b/src/main/java/com/powsybl/openloadflow/network/impl/LfVscConverterStationImpl.java index 2c721dac15..89da0feb97 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/LfVscConverterStationImpl.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/LfVscConverterStationImpl.java @@ -25,7 +25,7 @@ public class LfVscConverterStationImpl extends AbstractLfGenerator implements Lf private final double lossFactor; - private LfHvdc hvdc; // set only when AC emulation is activated + private LfHvdc hvdc; public LfVscConverterStationImpl(VscConverterStation station, LfNetwork network, LfNetworkParameters parameters, LfNetworkLoadingReport report) { super(network, HvdcUtils.getConverterStationTargetP(station) / PerUnit.SB); @@ -56,7 +56,7 @@ public void setHvdc(LfHvdc hvdc) { @Override public double getTargetP() { // because in case of AC emulation, active power is injected by HvdcAcEmulationSideXActiveFlowEquationTerm equations - return (hvdc == null || hvdc.isDisabled()) ? super.getTargetP() : 0; + return hvdc == null || !hvdc.isAcEmulation() ? super.getTargetP() : 0; } @Override @@ -96,7 +96,7 @@ public void updateState(LfNetworkStateUpdateParameters parameters) { var station = getStation(); station.getTerminal() .setQ(Double.isNaN(calculatedQ) ? -station.getReactivePowerSetpoint() : -calculatedQ * PerUnit.SB); - if (hvdc == null) { // because when AC emulation is activated, update of p is done in LFHvdcImpl + if (hvdc == null || !hvdc.isAcEmulation()) { // because when AC emulation is activated, update of p is done in LFHvdcImpl station.getTerminal().setP(-targetP * PerUnit.SB); } } 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 8e7d53d25e..06647b693d 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/PropagatedContingency.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/PropagatedContingency.java @@ -10,7 +10,6 @@ import com.powsybl.contingency.Contingency; import com.powsybl.contingency.ContingencyElement; import com.powsybl.iidm.network.*; -import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControl; import com.powsybl.iidm.network.extensions.LoadDetail; import com.powsybl.iidm.network.util.HvdcUtils; import com.powsybl.openloadflow.graph.GraphConnectivity; @@ -43,7 +42,7 @@ public class PropagatedContingency { private final Map branchIdsToOpen = new LinkedHashMap<>(); - private final Set hvdcIdsToOpen = new HashSet<>(); // for HVDC in AC emulation + private final Set hvdcIdsToOpen = new HashSet<>(); private final Set generatorIdsToLose = new HashSet<>(); @@ -67,14 +66,6 @@ public Map getBranchIdsToOpen() { return branchIdsToOpen; } - public Set getSwitchesToOpen() { - return switchesToOpen; - } - - public Set getHvdcIdsToOpen() { - return hvdcIdsToOpen; - } - public Set getGeneratorIdsToLose() { return generatorIdsToLose; } @@ -83,10 +74,6 @@ public Map getLoadIdsToLoose() { return loadIdsToLoose; } - public Map getShuntIdsToShift() { - return shuntIdsToShift; - } - public PropagatedContingency(Contingency contingency, int index, Set switchesToOpen, Set terminalsToDisconnect, Set busIdsToLose) { this.contingency = Objects.requireNonNull(contingency); @@ -248,13 +235,11 @@ private void complete(LfTopoConfig topoConfig, PropagatedContingencyCreationPara break; case HVDC_CONVERTER_STATION: + // in case of a hvdc contingency, both converter station will go through this case. + // in case of the lost of one VSC converter station only, the transmission of active power is stopped + // but the other converter station, if present, keeps it voltage control if present. HvdcConverterStation station = (HvdcConverterStation) connectable; - HvdcAngleDroopActivePowerControl control = station.getHvdcLine().getExtension(HvdcAngleDroopActivePowerControl.class); - if (control != null && control.isEnabled() && creationParameters.isHvdcAcEmulation()) { - hvdcIdsToOpen.add(station.getHvdcLine().getId()); - } - // FIXME - // the other converter station should be considered to if in the same synchronous component (hvdc setpoint mode). + hvdcIdsToOpen.add(station.getHvdcLine().getId()); if (connectable instanceof VscConverterStation) { generatorIdsToLose.add(connectable.getId()); } else { @@ -400,7 +385,7 @@ private Map findBranchToOpenDirectlyImpactedByCo return branchesToOpen; } - record ContingencyConnectivityLossImpact(boolean ok, int createdSynchronousComponents, Set busesToLost) { + record ContingencyConnectivityLossImpact(boolean ok, int createdSynchronousComponents, Set busesToLost, Set hvdcsWithoutPower) { } private ContingencyConnectivityLossImpact findBusesAndBranchesImpactedBecauseOfConnectivityLoss(LfNetwork network, Map branchesToOpen) { @@ -417,7 +402,7 @@ private ContingencyConnectivityLossImpact findBusesAndBranchesImpactedBecauseOfC // If a contingency leads to an isolated slack bus, this bus is considered as the main component. // In that case, we have an issue with a different number of variables and equations. LOGGER.error("Contingency '{}' leads to an isolated slack bus: not supported", contingency.getId()); - return new ContingencyConnectivityLossImpact(false, 0, Collections.emptySet()); + return new ContingencyConnectivityLossImpact(false, 0, Collections.emptySet(), Collections.emptySet()); } // add to contingency description buses and branches that won't be part of the main connected @@ -425,13 +410,27 @@ private ContingencyConnectivityLossImpact findBusesAndBranchesImpactedBecauseOfC int createdSynchronousComponents = connectivity.getNbConnectedComponents() - 1; Set busesToLost = connectivity.getVerticesRemovedFromMainComponent(); - return new ContingencyConnectivityLossImpact(true, createdSynchronousComponents, busesToLost); + // as we know here the connectivity after contingency, we have to reset active power flow of a hvdc line + // if one bus of the line is lost. + Set hvdcsWithoutFlow = new HashSet<>(); + for (LfHvdc hvdcLine : network.getHvdcs()) { + if (checkIsolatedBus(hvdcLine.getBus1(), hvdcLine.getBus2(), busesToLost, connectivity) + || checkIsolatedBus(hvdcLine.getBus2(), hvdcLine.getBus1(), busesToLost, connectivity)) { + hvdcsWithoutFlow.add(hvdcLine); + } + } + + return new ContingencyConnectivityLossImpact(true, createdSynchronousComponents, busesToLost, hvdcsWithoutFlow); } finally { // reset connectivity to discard triggered elements connectivity.undoTemporaryChanges(); } } + private boolean checkIsolatedBus(LfBus bus1, LfBus bus2, Set busesToLost, GraphConnectivity connectivity) { + return busesToLost.contains(bus1) && !busesToLost.contains(bus2) && connectivity.getConnectedComponent(bus1).size() == 1; + } + private static boolean isConnectedAfterContingencySide1(Map branchesToOpen, LfBranch branch) { DisabledBranchStatus status = branchesToOpen.get(branch); return status == null || status == DisabledBranchStatus.SIDE_2; @@ -509,9 +508,6 @@ public Optional toLfContingency(LfNetwork network) { .collect(Collectors.toCollection(LinkedHashSet::new)); for (LfHvdc hvdcLine : network.getHvdcs()) { - // FIXME - // if we loose a bus with a converter station, the other converter station should be considered to if in the - // same synchronous component (hvdc setpoint mode). if (busesToLost.contains(hvdcLine.getBus1()) || busesToLost.contains(hvdcLine.getBus2())) { lostHvdcs.add(hvdcLine); } @@ -522,11 +518,14 @@ public Optional toLfContingency(LfNetwork network) { && shunts.isEmpty() && loads.isEmpty() && generators.isEmpty() - && lostHvdcs.isEmpty()) { + && lostHvdcs.isEmpty() + && connectivityLossImpact.hvdcsWithoutPower().isEmpty()) { LOGGER.debug("Contingency '{}' has no impact", contingency.getId()); return Optional.empty(); } - return Optional.of(new LfContingency(contingency.getId(), index, connectivityLossImpact.createdSynchronousComponents, new DisabledNetwork(busesToLost, branchesToOpen, lostHvdcs), shunts, loads, generators)); + return Optional.of(new LfContingency(contingency.getId(), index, connectivityLossImpact.createdSynchronousComponents, + new DisabledNetwork(busesToLost, branchesToOpen, lostHvdcs), shunts, loads, generators, + connectivityLossImpact.hvdcsWithoutPower())); } } diff --git a/src/main/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysis.java index c11744ef5d..0e93eb33b8 100644 --- a/src/main/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysis.java @@ -794,6 +794,17 @@ private void processContingenciesBreakingConnectivity(ConnectivityAnalysisResult Set disabledBuses = connectivityAnalysisResult.getDisabledBuses(); Set partialDisabledBranches = connectivityAnalysisResult.getPartialDisabledBranches(); + // as we are processing contingencies with connectivity break, we have to reset active power flow of a hvdc line + // if one bus of the line is lost. + for (LfHvdc hvdc : loadFlowContext.getNetwork().getHvdcs()) { + if (disabledBuses.contains(hvdc.getBus1()) ^ disabledBuses.contains(hvdc.getBus2())) { + connectivityAnalysisResult.getContingencies().forEach(contingency -> { + contingency.getGeneratorIdsToLose().add(hvdc.getConverterStation1().getId()); + contingency.getGeneratorIdsToLose().add(hvdc.getConverterStation2().getId()); + }); + } + } + // null and unused if slack bus is not distributed List participatingElementsForThisConnectivity = participatingElements; boolean rhsChanged = false; // true if the disabled buses change the slack distribution, or the GLSK diff --git a/src/test/java/com/powsybl/openloadflow/network/HvdcNetworkFactory.java b/src/test/java/com/powsybl/openloadflow/network/HvdcNetworkFactory.java index fe58c46326..2a925e004e 100644 --- a/src/test/java/com/powsybl/openloadflow/network/HvdcNetworkFactory.java +++ b/src/test/java/com/powsybl/openloadflow/network/HvdcNetworkFactory.java @@ -601,4 +601,47 @@ public static Network createHvdcInAcEmulationInSymetricNetwork() { .add(); return network; } + + /** + *
+     *     g1 - b1 -- l12 -- b2 -- hvdc23 -- b3 -- l34 -- b4 - l4
+     *          |            |                            |
+     *          |           s2 (open)                     |
+     *          |            |                            |
+     *          |---l12Bis --                             |
+     *          |                                         |
+     *          ---------------------l14-------------------
+     * 
+ * @return + */ + public static Network createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType type) { + Network network = Network.create("test", "code"); + Bus b1 = createBus(network, "b1", 400); + Bus b2 = createBus(network, "b2", 400); + Bus b2Bis = b2.getVoltageLevel().getBusBreakerView().newBus().setId("b2Bis").add(); + Bus b3 = createBus(network, "b3", 400); + Bus b4 = createBus(network, "b4", 400); + createGenerator(b1, "g1", 400, 400); + createLine(network, b1, b2, "l12", 0.1f); + createLine(network, b1, b2Bis, "l12Bis", 0.1f); + createSwitch(network, b2, b2Bis, "s2").setOpen(true); + HvdcConverterStation cs2 = switch (type) { + case LCC -> createLcc(b2, "cs2"); + case VSC -> createVsc(b2, "cs2", 400, 0); + }; + HvdcConverterStation cs3 = switch (type) { + case LCC -> createLcc(b3, "cs3"); + case VSC -> createVsc(b3, "cs3", 400, 0); + }; + createHvdcLine(network, "hvdc23", cs2, cs3, 400, 0.1, 200) + .newExtension(HvdcAngleDroopActivePowerControlAdder.class) + .withDroop(180) + .withP0(200) + .withEnabled(true) + .add(); + createLine(network, b3, b4, "l34", 0.1f); + createLine(network, b1, b4, "l14", 0.1f); + createLoad(b4, "l4", 300, 0); + return network; + } } diff --git a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java index 1e4a43a8ae..792e7db581 100644 --- a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java +++ b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java @@ -1311,6 +1311,36 @@ void testHvdcAcEmulation() { assertEquals(-2.783, g1ContingencyResult2.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER); } + @Test + void testHvdcSetpoint() { + Network network = HvdcNetworkFactory.createWithHvdcInAcEmulation(); + network.getHvdcLine("hvdc34").newExtension(HvdcAngleDroopActivePowerControlAdder.class) + .withDroop(180) + .withP0(0.f) + .withEnabled(true) + .add(); + network.getGeneratorStream().forEach(generator -> generator.setMaxP(10)); + + LoadFlowParameters parameters = new LoadFlowParameters(); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_P_MAX) + .setHvdcAcEmulation(false); // will be operated in set point + OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED); + + List monitors = createAllBranchesMonitors(network); + + List contingencies = List.of(new Contingency("hvdc34", new HvdcLineContingency("hvdc34"))); + + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, parameters); + + PreContingencyResult preContingencyResult = result.getPreContingencyResult(); + assertEquals(-0.021, preContingencyResult.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER); + + // post-contingency tests + PostContingencyResult g1ContingencyResult = getPostContingencyResult(result, "hvdc34"); + assertEquals(-1.998, g1ContingencyResult.getNetworkResult().getBranchResult("l25").getP1(), LoadFlowAssert.DELTA_POWER); + } + @Test void testContingencyOnHvdcLcc() { Network network = HvdcNetworkFactory.createTwoCcLinkedByAHvdcWithGenerators(); diff --git a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java index e0aff5296d..df13a2f2d0 100644 --- a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java +++ b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java @@ -1213,4 +1213,48 @@ void testActionOnRetainedRtc() { assertEquals(network.getBusBreakerView().getBus("BUS_2").getV(), getOperatorStrategyResult(result, "strategy").getNetworkResult().getBusResult("BUS_2").getV(), LoadFlowAssert.DELTA_POWER); } + + @Test + void testVSCLossAcEmulation() { + // contingency leads to the lost 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. + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC); + List contingencies = List.of(new Contingency("contingency", new LineContingency("l12"))); + List actions = List.of(new SwitchAction("action", "s2", false)); + List operatorStrategies = List.of(new OperatorStrategy("strategy", + ContingencyContext.specificContingency("contingency"), + new TrueCondition(), + List.of("action"))); + List monitors = createNetworkMonitors(network); + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, new SecurityAnalysisParameters(), + operatorStrategies, actions, Reporter.NO_OP); + assertEquals(193.822, result.getPreContingencyResult().getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(0.0, getPostContingencyResult(result, "contingency").getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(193.822, getOperatorStrategyResult(result, "strategy").getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); + } + + @Test + void testVSCLossSetpoint() { + // contingency leads to the lost 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 power transmission of the hvdc line. + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC); + List contingencies = List.of(new Contingency("contingency", new LineContingency("l12"))); + List actions = List.of(new SwitchAction("action", "s2", false)); + List operatorStrategies = List.of(new OperatorStrategy("strategy", + ContingencyContext.specificContingency("contingency"), + new TrueCondition(), + List.of("action"))); + List monitors = createNetworkMonitors(network); + SecurityAnalysisParameters securityAnalysisParameters = new SecurityAnalysisParameters(); + securityAnalysisParameters.getLoadFlowParameters().setHvdcAcEmulation(false); + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, securityAnalysisParameters, + operatorStrategies, actions, Reporter.NO_OP); + assertEquals(-200.0, result.getPreContingencyResult().getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(-0.0, getPostContingencyResult(result, "contingency").getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(-200.0, getOperatorStrategyResult(result, "strategy").getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); + } } diff --git a/src/test/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysisContingenciesTest.java b/src/test/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysisContingenciesTest.java index ab3fb278d3..9fe1526f0b 100644 --- a/src/test/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysisContingenciesTest.java +++ b/src/test/java/com/powsybl/openloadflow/sensi/DcSensitivityAnalysisContingenciesTest.java @@ -2457,4 +2457,17 @@ void testBusContingency() { assertEquals(300.0, result.getBranchFlow1FunctionReferenceValue("NGEN", "NHV1_NHV2_1"), LoadFlowAssert.DELTA_POWER); assertEquals(600.0, result.getBranchFlow1FunctionReferenceValue("NGEN", "NGEN_NHV1"), LoadFlowAssert.DELTA_POWER); } + + @Test + void testVSCLoss() { + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC); + List contingencies = List.of(new Contingency("contingency", new LineContingency("l12")), + new Contingency("bus", new BusContingency("b2"))); + List factors = List.of(createBranchFlowPerInjectionIncrease("l34", "l4")); + SensitivityAnalysisParameters sensiParameters = createParameters(true, "b1", true); + SensitivityAnalysisResult result = sensiRunner.run(network, factors, contingencies, Collections.emptyList(), sensiParameters); + assertEquals(-200.0, result.getBranchFlow1FunctionReferenceValue("l34"), LoadFlowAssert.DELTA_POWER); + assertEquals(0.0, result.getBranchFlow1FunctionReferenceValue("contingency", "l34"), LoadFlowAssert.DELTA_POWER); + assertEquals(0.0, result.getBranchFlow1FunctionReferenceValue("bus", "l34"), LoadFlowAssert.DELTA_POWER); + } }