Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix flow computation of HVDC connected at only one side #965

Merged
merged 24 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
68fa131
Proposal to handle lost of a VSC converter station (ac emulation or n…
annetill Jan 20, 2024
01e3fa7
Fix dc sensi. Bus contingency still missing...
annetill Jan 23, 2024
9df1b92
Clean after sonar analysis.
annetill Jan 23, 2024
b5e099e
Merge branch 'main' into rework-hvdc
geofjamg Jan 24, 2024
79db2b4
Fix
geofjamg Jan 24, 2024
9b7b84d
Clean comment.
annetill Jan 24, 2024
10d1976
Refactoring.
annetill Jan 24, 2024
78dcfe7
Support HVDC disconnection in IIDM network
vidaldid-rte Jan 23, 2024
365e5e4
PR review + typos
vidaldid-rte Jan 24, 2024
0a25a81
Merge branch 'main' into hvdc-loss-in-N
vidaldid-rte Jan 25, 2024
99df3ae
fix typo
vidaldid-rte Jan 26, 2024
063e0b7
backport test from PR 957
vidaldid-rte Jan 26, 2024
d4f86f5
adapt test to factory (in the factory thehvdc pushes power to the gen…
vidaldid-rte Jan 26, 2024
0c1a72b
Fix N state restoration.
annetill Jan 26, 2024
6cbcd3e
Align isolated bus in IIDM with Networks.isIsolatedBusForHvdc and mod…
vidaldid-rte Jan 26, 2024
8b43547
Merge branch 'main' into hvdc-loss-in-N
vidaldid-rte Jan 26, 2024
78a8497
Add a unit test and fix code smells.
annetill Jan 26, 2024
b74239b
Merge branch 'hvdc-loss-in-N' of https://github.com/powsybl/powsybl-o…
annetill Jan 26, 2024
dca7925
Add unit test.
annetill Jan 26, 2024
1ad88db
Merge branch 'main' into hvdc-loss-in-N
annetill Jan 29, 2024
1875d34
Move utility method.
annetill Jan 29, 2024
d930f03
Fix unit test (1).
annetill Jan 29, 2024
e598e51
Clean unit test (2).
annetill Jan 29, 2024
d9193e4
Merge branch 'main' into hvdc-loss-in-N
annetill Jan 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,11 @@ void addLoad(Load load, LfNetworkParameters parameters) {
}

void addLccConverterStation(LccConverterStation lccCs, LfNetworkParameters parameters) {
getOrCreateLfLoad(null, parameters).add(lccCs, parameters);
if (!HvdcConverterStations.isHvdcDanglingInIidm(lccCs, parameters)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of that, we end the calculation with NaN as p for the converter station for LCC only. @jeandemanged I don't like it very much...

// Note: Load is determined statically - contingencies or actions that change an LCC Station connectivity
// will continue to give incorrect result
getOrCreateLfLoad(null, parameters).add(lccCs, parameters);
}
}

protected void add(LfGenerator generator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.util.HvdcUtils;
import com.powsybl.openloadflow.network.LfNetworkParameters;

import java.util.Optional;

Expand Down Expand Up @@ -40,4 +41,28 @@ public static boolean isVsc(Connectable<?> connectable) {
return connectable.getType() == IdentifiableType.HVDC_CONVERTER_STATION
&& ((HvdcConverterStation<?>) connectable).getHvdcType() == HvdcConverterStation.HvdcType.VSC;
}

public static boolean isHvdcDanglingInIidm(HvdcConverterStation<?> station, LfNetworkParameters parameters) {

if (isIsolated(station.getTerminal(), parameters)) {
return true;
} else {
return station.getOtherConverterStation().map(otherConverterStation -> {
Terminal otherTerminal = otherConverterStation.getTerminal();
return isIsolated(otherTerminal, parameters);
}).orElse(true); // it means there is no HVDC line connected to station
}
}

private static boolean isIsolated(Terminal terminal, LfNetworkParameters parameters) {
Bus bus = parameters.isBreakers() ? terminal.getBusBreakerView().getBus() : terminal.getBusView().getBus();
if (bus == null) {
return true;
}

// The criteria should as close as possible to Networks.isIsolatedBusForHvdc - only connected to the station
return bus.getConnectedTerminalStream()
.map(Terminal::getConnectable)
.noneMatch(c -> !(c instanceof HvdcConverterStation<?> || c instanceof BusbarSection));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
import com.powsybl.iidm.network.ReactiveLimits;
import com.powsybl.iidm.network.VscConverterStation;
import com.powsybl.iidm.network.util.HvdcUtils;
import com.powsybl.openloadflow.network.*;

import com.powsybl.openloadflow.network.LfHvdc;
import com.powsybl.openloadflow.network.LfNetwork;
import com.powsybl.openloadflow.network.LfNetworkParameters;
import com.powsybl.openloadflow.network.LfNetworkStateUpdateParameters;
import com.powsybl.openloadflow.network.LfVscConverterStation;
import com.powsybl.openloadflow.util.PerUnit;

import java.util.Objects;
Expand All @@ -27,8 +32,11 @@ public class LfVscConverterStationImpl extends AbstractLfGenerator implements Lf

private LfHvdc hvdc;

private final boolean hvdcDandlingInIidm;

public LfVscConverterStationImpl(VscConverterStation station, LfNetwork network, LfNetworkParameters parameters, LfNetworkLoadingReport report) {
super(network, HvdcUtils.getConverterStationTargetP(station) / PerUnit.SB);
this.hvdcDandlingInIidm = HvdcConverterStations.isHvdcDanglingInIidm(station, parameters);
this.stationRef = Ref.create(station, parameters.isCacheEnabled());
this.lossFactor = station.getLossFactor();

Expand All @@ -55,8 +63,13 @@ 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.isAcEmulation() ? super.getTargetP() : 0;
if (hvdc == null) {
// Because in case one node is not in the LfNetwork, the connectivity of that node is given by IIDM
return hvdcDandlingInIidm ? 0 : super.getTargetP();
} else {
// Because in case of AC emulation, active power is injected by HvdcAcEmulationSideXActiveFlowEquationTerm equations
return hvdc.isAcEmulation() ? 0 : super.getTargetP();
}
}

@Override
Expand Down Expand Up @@ -97,7 +110,7 @@ public void updateState(LfNetworkStateUpdateParameters parameters) {
station.getTerminal()
.setQ(Double.isNaN(calculatedQ) ? -station.getReactivePowerSetpoint() : -calculatedQ * PerUnit.SB);
if (hvdc == null || !hvdc.isAcEmulation()) { // because when AC emulation is activated, update of p is done in LFHvdcImpl
station.getTerminal().setP(-targetP * PerUnit.SB);
station.getTerminal().setP(-getTargetP() * PerUnit.SB);
}
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/powsybl/openloadflow/network/impl/Networks.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.reporter.Reporter;
import com.powsybl.iidm.network.*;
import com.powsybl.openloadflow.graph.GraphConnectivity;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.network.impl.extensions.OverloadManagementSystem;
import com.powsybl.openloadflow.network.impl.extensions.SubstationAutomationSystems;
Expand Down Expand Up @@ -149,6 +150,13 @@ private static void restoreInitialTopology(LfNetwork network, Set<Switch> allSwi
bus.getBranches().stream().filter(b -> !b.isConnectedAtBothSides()).forEach(removedBranches::add);
}
removedBranches.forEach(branch -> branch.setDisabled(true));
for (LfHvdc hvdc : network.getHvdcs()) {
if (isIsolatedBusForHvdc(hvdc.getBus1(), connectivity) || isIsolatedBusForHvdc(hvdc.getBus2(), connectivity)) {
hvdc.setDisabled(true);
hvdc.getConverterStation1().setTargetP(0.0);
hvdc.getConverterStation2().setTargetP(0.0);
}
}
}

private static void addSwitchesOperatedByAutomationSystem(Network network, LfTopoConfig topoConfig, OverloadManagementSystem system) {
Expand Down Expand Up @@ -230,6 +238,20 @@ public static Bus getBus(Terminal terminal, boolean breakers) {
: terminal.getBusView().getBus();
}

public static boolean isIsolatedBusForHvdc(LfBus bus, GraphConnectivity<LfBus, LfBranch> connectivity) {
// used only for hvdc lines.
// this criteria can be improved later depending on use case
return connectivity.getConnectedComponent(bus).size() == 1 && bus.getLoadTargetP() == 0.0
&& bus.getGenerators().stream().noneMatch(LfGeneratorImpl.class::isInstance);
}
Comment on lines +241 to +246
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no SA test covering this


public static boolean isIsolatedBusForHvdc(LfBus bus, Set<LfBus> disabledBuses) {
// used only for hvdc lines for DC sensitivity analysis where we don't have the connectivity.
// this criteria can be improved later depending on use case
return disabledBuses.contains(bus) && bus.getLoadTargetP() == 0.0
&& bus.getGenerators().stream().noneMatch(LfGeneratorImpl.class::isInstance);
}

public static Optional<Terminal> getEquipmentRegulatingTerminal(Network network, String equipmentId) {
Generator generator = network.getGenerator(equipmentId);
if (generator != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ private ContingencyConnectivityLossImpact findBusesAndBranchesImpactedBecauseOfC
}

private boolean checkIsolatedBus(LfBus bus1, LfBus bus2, Set<LfBus> busesToLost, GraphConnectivity<LfBus, LfBranch> connectivity) {
return busesToLost.contains(bus1) && !busesToLost.contains(bus2) && connectivity.getConnectedComponent(bus1).size() == 1;
return busesToLost.contains(bus1) && !busesToLost.contains(bus2) && Networks.isIsolatedBusForHvdc(bus1, connectivity);
}

private static boolean isConnectedAfterContingencySide1(Map<LfBranch, DisabledBranchStatus> branchesToOpen, LfBranch branch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ private void processContingenciesBreakingConnectivity(ConnectivityAnalysisResult
// 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())) {
if (Networks.isIsolatedBusForHvdc(hvdc.getBus1(), disabledBuses) ^ Networks.isIsolatedBusForHvdc(hvdc.getBus2(), disabledBuses)) {
connectivityAnalysisResult.getContingencies().forEach(contingency -> {
contingency.getGeneratorIdsToLose().add(hvdc.getConverterStation1().getId());
contingency.getGeneratorIdsToLose().add(hvdc.getConverterStation2().getId());
Expand Down
63 changes: 63 additions & 0 deletions src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowVscTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.junit.jupiter.api.Test;

import static com.powsybl.openloadflow.util.LoadFlowAssert.*;
import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand Down Expand Up @@ -335,4 +336,66 @@ void testHvdcDirectionChangeAcEmulation() {
assertTrue(pcs1 < 0, "Power delivered by cs1");
assertTrue(Math.abs(pcs2) > Math.abs(pcs1), "Loss at HVDC output");
}

@Test
void testLccOpenAtOneSide() {
Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.LCC);
LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
LoadFlowResult result = loadFlowRunner.run(network);
assertTrue(result.isFullyConverged());

// generator g1 expected to deliver enough power for the load
assertActivePowerEquals(-304.400, network.getGenerator("g1").getTerminal());
assertActivePowerEquals(300.00, network.getLoad("l4").getTerminal());
assertActivePowerEquals(-195.600, network.getHvdcConverterStation("cs2").getTerminal());
assertActivePowerEquals(200.00, network.getHvdcConverterStation("cs3").getTerminal());

Line l34 = network.getLine("l34");
l34.getTerminals().stream().forEach(Terminal::disconnect);
result = loadFlowRunner.run(network);
assertTrue(result.isPartiallyConverged()); // for LCC test, no PV bus in the small component -> FAILED

assertActivePowerEquals(-300.00, network.getGenerator("g1").getTerminal());
assertActivePowerEquals(300.00, network.getLoad("l4").getTerminal());
assertTrue(Double.isNaN(network.getHvdcConverterStation("cs2").getTerminal().getP())); // FIXME
assertTrue(Double.isNaN(network.getHvdcConverterStation("cs3").getTerminal().getP()));
}

@Test
void testVscOpenAtOneSide() {
Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC);
LoadFlowParameters parameters = new LoadFlowParameters()
.setHvdcAcEmulation(false);
LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
LoadFlowResult result = loadFlowRunner.run(network, parameters);
assertTrue(result.isFullyConverged());

// generator g1 expected to deliver enough power for the load
assertActivePowerEquals(-304.400, network.getGenerator("g1").getTerminal());
assertActivePowerEquals(300.00, network.getLoad("l4").getTerminal());
assertActivePowerEquals(-195.600, network.getHvdcConverterStation("cs2").getTerminal());
assertActivePowerEquals(200.00, network.getHvdcConverterStation("cs3").getTerminal());

Line l34 = network.getLine("l34");
l34.getTerminals().stream().forEach(Terminal::disconnect);
result = loadFlowRunner.run(network, parameters);
assertTrue(result.isFullyConverged());

assertActivePowerEquals(-300.00, network.getGenerator("g1").getTerminal());
assertActivePowerEquals(300.00, network.getLoad("l4").getTerminal());
assertActivePowerEquals(0.0, network.getHvdcConverterStation("cs2").getTerminal());
assertActivePowerEquals(0.0, network.getHvdcConverterStation("cs3").getTerminal());
}

@Test
void testHvdcAndGenerator() {
Network network = HvdcNetworkFactory.createWithHvdcAndGenerator();
LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
LoadFlowResult result = loadFlowRunner.run(network, new LoadFlowParameters());
assertTrue(result.isFullyConverged());
assertActivePowerEquals(-1.956, network.getVscConverterStation("cs3").getTerminal());
assertActivePowerEquals(2.0, network.getVscConverterStation("cs4").getTerminal());
assertActivePowerEquals(-2.0, network.getGenerator("g4").getTerminal());
assertActivePowerEquals(-2.047, network.getGenerator("g1").getTerminal());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -644,4 +644,35 @@ public static Network createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.H
createLoad(b4, "l4", 300, 0);
return network;
}

/**
* <pre>
* b1 ----------+
* | |
* b2 -------- b3 - cs3
* hvdc34
* b4 - g4
* </pre>
*
* @return network
*/
public static Network createWithHvdcAndGenerator() {
Network network = Network.create("test", "code");
Bus b1 = createBus(network, "b1");
Bus b2 = createBus(network, "b2");
Bus b3 = createBus(network, "b3");
Bus b4 = createBus(network, "b4");
createLine(network, b1, b2, "l12", 0.1f);
createLine(network, b1, b3, "l13", 0.1f);
createLine(network, b2, b3, "l23", 0.1f);
HvdcConverterStation cs3 = createVsc(b3, "cs3", 1.2d, 0d);
HvdcConverterStation cs4 = createVsc(b4, "cs4", 1.2d, 0d);
createHvdcLine(network, "hvdc34", cs3, cs4, 400, 0.1, 2);
createGenerator(b1, "g1", 1);
createGenerator(b4, "g4", 2);
network.getGenerator("g1").setMaxP(5);
network.getGenerator("g4").setMaxP(5);
createLoad(b2, "d2", 4);
return network;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;

Expand Down Expand Up @@ -261,6 +262,14 @@ void testElements() {
assertEquals("hvdc34", mainNetwork.getHvdc(0).getId());
}

@Test
void testIsolatedForHvdc() {
Network network = HvdcNetworkFactory.createWithHvdcAndGenerator();
List<LfNetwork> lfNetworks = Networks.load(network, new MostMeshedSlackBusSelector());
LfNetwork smallNetwork = lfNetworks.get(1);
assertFalse(Networks.isIsolatedBusForHvdc(smallNetwork.getBusById("b4_vl_0"), Set.of(smallNetwork.getBusById("b4_vl_0"))));
}

@Test
void evaluableGetterAndSetterTest() {
Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.powsybl.openloadflow.util.LoadFlowAssert.DELTA_POWER;
import static com.powsybl.openloadflow.util.LoadFlowAssert.assertReportEquals;
import static org.junit.jupiter.api.Assertions.*;

Expand Down Expand Up @@ -1235,6 +1236,74 @@ void testVSCLossAcEmulation() {
assertEquals(193.822, getOperatorStrategyResult(result, "strategy").getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER);
}

@Test
void testHvdcDisconnectedThenConnectedByStrategy() {
// Hvdc initially disconnected in iidm network
// contingency leads to an action that reconnects the hvdc link
// VSC only
Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC);

// hvdc flow power from generator to load
network.getHvdcLine("hvdc23").setConvertersMode(HvdcLine.ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER);
network.getLine("l12").getTerminals().forEach(Terminal::disconnect);
network.getLine("l14").newCurrentLimits1()
.setPermanentLimit(290)
.add();
network.newLine()
.setId("l14Bis")
.setBus1("b1")
.setConnectableBus1("b1")
.setBus2("b4")
.setConnectableBus2("b4")
.setR(0d)
.setX(0.1d)
.add();

List<Contingency> contingencies = List.of(new Contingency("l14Bis", new BranchContingency("l14Bis")));
List<Action> actions = List.of(new SwitchAction("action1", "s2", false));
List<OperatorStrategy> operatorStrategies = List.of(new OperatorStrategy("strategyL1",
ContingencyContext.specificContingency("l14Bis"),
new AnyViolationCondition(),
List.of("action1")));
List<StateMonitor> monitors = createNetworkMonitors(network);

// with AC emulation first
SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, new SecurityAnalysisParameters(),
operatorStrategies, actions, Reporter.NO_OP);

// No power expected since switch is open and L12 is open
assertEquals(0.0, result.getPreContingencyResult().getNetworkResult().getBranchResult("l34").getP1(), DELTA_POWER);
assertTrue(result.getPreContingencyResult().getLimitViolationsResult().getLimitViolations().isEmpty());

PostContingencyResult postContingencyResult = getPostContingencyResult(result, "l14Bis");
assertEquals(300.0, postContingencyResult.getNetworkResult().getBranchResult("l14").getP1(), DELTA_POWER);
assertFalse(postContingencyResult.getLimitViolationsResult().getLimitViolations().isEmpty()); // "One violation expected for l34"

OperatorStrategyResult operatorStrategyResult = getOperatorStrategyResult(result, "strategyL1");
assertEquals(198.158, operatorStrategyResult.getNetworkResult().getBranchResult("l12Bis").getP1(), DELTA_POWER);
assertEquals(193.822, operatorStrategyResult.getNetworkResult().getBranchResult("l34").getP1(), DELTA_POWER);
assertEquals(106.177, operatorStrategyResult.getNetworkResult().getBranchResult("l14").getP1(), DELTA_POWER);

// without AC emulation
SecurityAnalysisParameters parameters = new SecurityAnalysisParameters();
parameters.getLoadFlowParameters().setHvdcAcEmulation(false);
SecurityAnalysisResult result2 = runSecurityAnalysis(network, contingencies, monitors, parameters,
operatorStrategies, actions, Reporter.NO_OP);

// No power expected since switch is open and L12 is open
assertEquals(0.0, result2.getPreContingencyResult().getNetworkResult().getBranchResult("l34").getP1(), DELTA_POWER);
assertTrue(result2.getPreContingencyResult().getLimitViolationsResult().getLimitViolations().isEmpty());

PostContingencyResult postContingencyResult2 = getPostContingencyResult(result2, "l14Bis");
assertEquals(300.0, postContingencyResult2.getNetworkResult().getBranchResult("l14").getP1(), DELTA_POWER);
assertFalse(postContingencyResult2.getLimitViolationsResult().getLimitViolations().isEmpty()); // "One violation expected for l34"

OperatorStrategyResult operatorStrategyResult2 = getOperatorStrategyResult(result2, "strategyL1");
assertEquals(200.0, operatorStrategyResult2.getNetworkResult().getBranchResult("l12Bis").getP1(), DELTA_POWER);
assertEquals(195.60, operatorStrategyResult2.getNetworkResult().getBranchResult("l34").getP1(), DELTA_POWER);
assertEquals(104.40, operatorStrategyResult2.getNetworkResult().getBranchResult("l14").getP1(), DELTA_POWER);
}

@Test
void testVSCLossSetpoint() {
// contingency leads to the lost of one converter station.
Expand Down