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

fictive load on isolated bus keeps hvdc isolated #1120

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions src/main/java/com/powsybl/openloadflow/network/LfBus.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ enum QLimitType {

double getLoadTargetP();

double getNonFictitiousLoadTargetP();

double getLoadTargetQ();

void invalidateGenerationTargetP();
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/powsybl/openloadflow/network/LfLoad.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public interface LfLoad extends PropertyBag {

double getTargetP();

double getNonFictitiousLoadTargetP();

void setTargetP(double targetP);

double getTargetQ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ void addLoad(Load load, LfNetworkParameters parameters) {
}

void addLccConverterStation(LccConverterStation lccCs, LfNetworkParameters parameters) {
if (!HvdcConverterStations.isHvdcDanglingInIidm(lccCs, parameters)) {
if (!HvdcConverterStations.isHvdcDanglingInIidm(lccCs)) {
// 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);
Expand Down Expand Up @@ -412,6 +412,13 @@ public double getLoadTargetP() {
.sum();
}

@Override
public double getNonFictitiousLoadTargetP() {
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure there is no performance impact to compute this on the fly ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The laternative I see would be

  • to compute incrementally this value for all buses in each event modifying the loads.
    Seems complex and gain is not obvious
  • to cache it at bus creation, ignoring deconnection events, actions etc..
    Might cause bugs

Here it is computed;
at each restore, for each HVDC station
at contingency propagation for each HVDC
and you pay an iteration price if the bus has several loads.

This being said I'll run some mesures of this cost on a large network.

Copy link
Collaborator Author

@vidaldid-rte vidaldid-rte Nov 19, 2024

Choose a reason for hiding this comment

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

It has ZERO performance impact the way it is used today for this new usage.
The method was never called in a sensitivity analysis of a large network with 100 contingencies.
The reason is that restoreInitialTopology that will trigger the call for each HVDC link is called in very rare and targeted occasions - when some switches are moved. So I suggest to keep it simple stupid at this time.

Copy link
Member

Choose a reason for hiding this comment

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

Indeed it seems to be only called at Lf Network loading so only one time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

And only if an HVDC's station bus is disabled....

return loads.stream()
.mapToDouble(load -> load.getNonFictitiousLoadTargetP() * load.getLoadModel().flatMap(lm -> lm.getExpTermP(0).map(LfLoadModel.ExpTerm::c)).orElse(1d))
.sum();
}

@Override
public double getLoadTargetQ() {
return loads.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

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

import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -45,27 +44,31 @@ public static boolean isVsc(Identifiable<?> identifiable) {
&& ((HvdcConverterStation<?>) identifiable).getHvdcType() == HvdcConverterStation.HvdcType.VSC;
}

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

if (isIsolated(station.getTerminal(), parameters)) {
if (isIsolated(station.getTerminal())) {
return true;
} else {
return station.getOtherConverterStation().map(otherConverterStation -> {
Terminal otherTerminal = otherConverterStation.getTerminal();
return isIsolated(otherTerminal, parameters);
return isIsolated(otherTerminal);
}).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();
private static boolean isIsolated(Terminal terminal) {
Bus bus = terminal.getBusView().getBus();
Copy link
Member

Choose a reason for hiding this comment

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

I would rather agree on this change, but has it been motivated by something failing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No. Just the fact that when I wrote it first I didn't fully understand the difference betwwen BusView and BusBreakerVew

Copy link
Member

Choose a reason for hiding this comment

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

So maybe we should revert this change and think about it in another one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh la la... So it can breaks something in a topology I have seen where HVDC disconnection matters. I'll show you next time we meet.

Copy link
Member

Choose a reason for hiding this comment

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

So in that case, you just need to add a unit test that shows the issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done. That was a bit trickier that I thought - and the bug is more seldom that I though because:

  • the breaker mode is activated in very rare occasions
  • the criteria is only used for an HVDC part that is not in the LfNetwork
    But the bug existed.

if (bus == null) {
return true;
}

// The criteria should as close as possible to Networks.isIsolatedBusForHvdc - only connected to the station
// The criteria should as close as possible to Networks.isIsolatedBusForHvdc - only connected to the station or a fictitious load
return bus.getConnectedTerminalStream()
.map(Terminal::getConnectable)
.noneMatch(c -> !(c instanceof HvdcConverterStation<?> || c instanceof BusbarSection));
.noneMatch(c -> !(c instanceof HvdcConverterStation<?> || c instanceof BusbarSection || isFictitiousLoad(c)));
}

private static boolean isFictitiousLoad(Connectable<?> c) {
return c instanceof Load load && LfLoadImpl.isLoadFictitious(load);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,28 @@ private static double getPowerFactor(Load load) {
* Returns true if the load does not participate to slack distribution
*/
public static boolean isLoadNotParticipating(Load load) {
// Fictitious loads that do not participate to slack distribution.
return isLoadFictitious(load);
}

/**
* Returns whether the load is tagged fictitious in the grid model
*/
public static boolean isLoadFictitious(Load load) {
// Fictitious loads that do not participate to slack distribution.
return load.isFictitious() || LoadType.FICTITIOUS.equals(load.getLoadType());
}

@Override
public double getNonFictitiousLoadTargetP() {
return loadsRefs.values().stream()
.map(Ref::get)
.filter(Objects::nonNull)
.filter(l -> !isLoadFictitious(l))
.mapToDouble(Load::getP0)
.sum();
}

@Override
public Evaluable getP() {
return p;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class LfVscConverterStationImpl extends AbstractLfGenerator implements Lf

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,14 +269,14 @@ public static Bus getBus(Terminal terminal, boolean breakers) {
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
return connectivity.getConnectedComponent(bus).size() == 1 && bus.getNonFictitiousLoadTargetP() == 0.0
&& bus.getGenerators().stream().noneMatch(LfGeneratorImpl.class::isInstance);
}

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
return disabledBuses.contains(bus) && bus.getNonFictitiousLoadTargetP() == 0.0
&& bus.getGenerators().stream().noneMatch(LfGeneratorImpl.class::isInstance);
}

Expand Down
31 changes: 26 additions & 5 deletions src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowVscTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.powsybl.openloadflow.network.HvdcNetworkFactory;
import com.powsybl.openloadflow.network.SlackBusSelectionMode;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static com.powsybl.openloadflow.util.LoadFlowAssert.*;
import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals;
Expand Down Expand Up @@ -450,8 +452,9 @@ void testVscVoltageControlWithZeroTargetP() {
assertVoltageEquals(vcs3, network.getVscConverterStation("cs3").getTerminal().getBusView().getBus());
}

@Test
void testVscVoltageControlWithOneSideDisconnected() {
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testVscVoltageControlWithOneSideDisconnected(boolean withFictiveLoad) {
Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC);
LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
// Set specific voltage setPoints to the stations
Expand All @@ -460,15 +463,33 @@ void testVscVoltageControlWithOneSideDisconnected() {
network.getVscConverterStation("cs2").setVoltageSetpoint(vcs2);
network.getVscConverterStation("cs3").setVoltageSetpoint(vcs3);

if (withFictiveLoad) {
// Add a fictive load to the bus that will be disconnected
network.getBusBreakerView().getBus("b3").getVoltageLevel().newLoad()
.setId("fictiveLoad")
.setBus("b3")
.setConnectableBus("b3")
.setP0(5)
.setQ0(2)
.setFictitious(true)
.add();
}

LoadFlowParameters p = new LoadFlowParameters();
LoadFlowResult result = loadFlowRunner.run(network, p);

assertTrue(result.isFullyConverged());
// Just test that the HVDC is open - no need for more precision
assertTrue(network.getVscConverterStation("cs2").getTerminal().getP() > 100);
assertVoltageEquals(vcs2, network.getVscConverterStation("cs2").getTerminal().getBusView().getBus());

// Disconnect line at HVDCoutput
Line l34 = network.getLine("l34");
l34.getTerminals().stream().forEach(Terminal::disconnect);

LoadFlowParameters p = new LoadFlowParameters();

// without AC emulation
p.setHvdcAcEmulation(false);
LoadFlowResult result = loadFlowRunner.run(network, p);
result = loadFlowRunner.run(network, p);

assertTrue(result.isFullyConverged());
assertActivePowerEquals(0, network.getVscConverterStation("cs2").getTerminal());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,56 @@ public static Network createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.H
return network;
}

/**
* <pre>
* g1 - b1 b2 -- hvdc23 -- b3 -- l34 -- b4 - l4
* | | | |
* | s2 (closed) s3 (closed) l45
* | | | |
* C|---l12------ b7--l7 b5---l56-b6-g6
*
* </pre>
* @return
*/
public static Network createHvdcAndSwitch(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);
Bus b5 = createBus(network, "b5", 400);
Bus b6 = createBus(network, "b6", 400);
Bus b7 = b3.getVoltageLevel().getBusBreakerView().newBus().setId("b7").add();

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(false);
createSwitch(network, b3, b7, "s3").setOpen(false);
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, b4, b5, "l45", 0.1f);
createLine(network, b5, b6, "l56", 0.1f);
createLoad(b4, "l4", 300, 0);
createLoad(b7, "l7", 50, 0);
createGenerator(b6, "g6", 400, 400);
return network;
}

/**
* <pre>
* b1 ----------+
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import com.powsybl.security.results.PreContingencyResult;
import com.powsybl.security.strategy.OperatorStrategy;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.IOException;
import java.util.ArrayList;
Expand Down Expand Up @@ -1409,6 +1411,46 @@ void testVSCLossAcEmulation() {
assertEquals(193.799, getOperatorStrategyResult(result, "strategy").getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER);
}

@Test
void testVSCConnectivityWithSwitch() {
// The goal is to test the AC connectivity when the LF parameter is in breaker mode
// This is achieved by running an AS with a contingency on a switch

Network network = HvdcNetworkFactory.createHvdcAndSwitch(HvdcConverterStation.HvdcType.VSC);

LoadFlowParameters parameters = new LoadFlowParameters();
parameters.setConnectedComponentMode(LoadFlowParameters.ConnectedComponentMode.ALL);

// Disconect l12 - the HVDC line should still transfer power in N
network.getLine("l12").getTerminal1().disconnect();
network.getLine("l12").getTerminal2().disconnect();

runLoadFlow(network, parameters);

assertEquals(-250, network.getBranch("l34").getTerminal1().getP(), LoadFlowAssert.DELTA_POWER);
assertEquals(200.0, network.getHvdcConverterStation("cs3").getTerminal().getP(), LoadFlowAssert.DELTA_POWER);

network.getSwitch("s3").setOpen(true);
runLoadFlow(network, parameters);

assertEquals(-200, network.getBranch("l34").getTerminal1().getP(), LoadFlowAssert.DELTA_POWER);
assertEquals(200, network.getHvdcConverterStation("cs3").getTerminal().getP(), LoadFlowAssert.DELTA_POWER);

// Now reconnect the switch and replay the scenario in SA
network.getSwitch("s3").setOpen(false);

List<StateMonitor> monitors = createNetworkMonitors(network);
List<Contingency> contingencies = List.of(new Contingency("c_s3", new SwitchContingency("s3")));

SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, new SecurityAnalysisParameters().setLoadFlowParameters(parameters),
Collections.emptyList(), Collections.emptyList(), ReportNode.NO_OP);

// HVDC is on in N
assertEquals(-250, result.getPreContingencyResult().getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER);
// HVDC is still on - no flow to l7
assertEquals(-200, getPostContingencyResult(result, "c_s3").getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER);
}

@Test
void testHvdcDisconnectedThenConnectedByStrategy() {
// Hvdc initially disconnected in iidm network
Expand Down Expand Up @@ -1477,13 +1519,25 @@ void testHvdcDisconnectedThenConnectedByStrategy() {
assertEquals(104.40, operatorStrategyResult2.getNetworkResult().getBranchResult("l14").getP1(), DELTA_POWER);
}

@Test
void testVSCLossSetpoint() {
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testVSCLossSetpoint(boolean withFictiveLoad) {
// 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);
if (withFictiveLoad) {
network.getBusBreakerView().getBus("b2").getVoltageLevel()
.newLoad()
.setId("fictiveLoad")
.setP0(10)
.setQ0(0)
.setBus("b2")
.setConnectableBus("b2")
.setFictitious(true)
.add();
}
List<Contingency> contingencies = List.of(new Contingency("contingency", new LineContingency("l12")));
List<Action> actions = List.of(new SwitchAction("action", "s2", false));
List<OperatorStrategy> operatorStrategies = List.of(new OperatorStrategy("strategy",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import com.powsybl.openloadflow.util.LoadFlowAssert;
import com.powsybl.sensitivity.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -2459,9 +2461,21 @@ void testBusContingency() {
assertEquals(600.0, result.getBranchFlow1FunctionReferenceValue("NGEN", "NGEN_NHV1"), LoadFlowAssert.DELTA_POWER);
}

@Test
void testVSCLoss() {
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testVSCLoss(boolean withFictiveLoad) {
Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC);
if (withFictiveLoad) {
network.getBusBreakerView().getBus("b2").getVoltageLevel()
.newLoad()
.setId("fictiveLoad")
.setP0(5)
.setQ0(1)
.setBus("b2")
.setConnectableBus("b2")
.setFictitious(true)
.add();
}
List<Contingency> contingencies = List.of(new Contingency("contingency", new LineContingency("l12")),
new Contingency("bus", new BusContingency("b2")));
List<SensitivityFactor> factors = List.of(createBranchFlowPerInjectionIncrease("l34", "l4"));
Expand Down