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

Honnor HVDCOperatorActivePowerRange as well as Pmax of hvdcLine in AC emulation #1005

Merged
merged 6 commits into from
Mar 29, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public abstract class AbstractHvdcAcEmulationFlowEquationTerm extends AbstractEl

protected final double lossFactor2;

protected final double pMaxFromCS1toCS2;

protected final double pMaxFromCS2toCS1;

protected AbstractHvdcAcEmulationFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfBus bus2, VariableSet<AcVariableType> variableSet) {
super(hvdc);
ph1Var = variableSet.getVariable(bus1.getNum(), AcVariableType.BUS_PHI);
Expand All @@ -42,6 +46,23 @@ protected AbstractHvdcAcEmulationFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfBus
p0 = hvdc.getP0();
lossFactor1 = hvdc.getConverterStation1().getLossFactor() / 100;
lossFactor2 = hvdc.getConverterStation2().getLossFactor() / 100;
pMaxFromCS1toCS2 = hvdc.getPMaxFromCS1toCS2();
pMaxFromCS2toCS1 = hvdc.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() {
Expand All @@ -52,7 +73,7 @@ protected double ph2() {
return sv.get(ph2Var.getRow());
}

protected static double getLossMultiplier(double lossFactor1, double lossFactor2) {
protected double getVscLossMultiplier() {
return (1 - lossFactor1) * (1 - lossFactor2);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,45 @@ public HvdcAcEmulationSide1ActiveFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfBus
super(hvdc, bus1, bus2, variableSet);
}

private static double p1(double p0, double k, double lossFactor1, double lossFactor2, double ph1, double ph2) {
return (isController(p0, k, ph1, ph2) ? 1 : getLossMultiplier(lossFactor1, lossFactor2)) * (p0 + k * (ph1 - ph2));
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 p0, double k, double ph1, double ph2) {
return (p0 + k * (ph1 - ph2)) >= 0;
private static boolean isController(double rawP) {
return rawP >= 0;
}

private static double dp1dph1(double p0, double k, double lossFactor1, double lossFactor2, double ph1, double ph2) {
return (isController(p0, k, ph1, ph2) ? 1 : getLossMultiplier(lossFactor1, lossFactor2)) * k;
private boolean isInOperatingRange(double rawP) {
return rawP < pMaxFromCS1toCS2 && rawP > -pMaxFromCS2toCS1;
}

private static double dp1dph2(double p0, double k, double lossFactor1, double lossFactor2, double ph1, double ph2) {
return -dp1dph1(p0, k, lossFactor1, lossFactor2, ph1, ph2);
protected double dp1dph1(double ph1, double ph2) {
double rawP = rawP(p0, k, ph1, ph2);
if (isInOperatingRange(rawP)) {
return (isController(rawP) ? 1 : getVscLossMultiplier()) * k;
} else {
return 0;
}
}

protected double dp1dph2(double ph1, double ph2) {
return -dp1dph1(ph1, ph2);
}

@Override
public double eval() {
return p1(p0, k, lossFactor1, lossFactor2, ph1(), ph2());
return p1(ph1(), ph2());
}

@Override
public double der(Variable<AcVariableType> variable) {
Objects.requireNonNull(variable);
if (variable.equals(ph1Var)) {
return dp1dph1(p0, k, lossFactor1, lossFactor2, ph1(), ph2());
return dp1dph1(ph1(), ph2());
} else if (variable.equals(ph2Var)) {
return dp1dph2(p0, k, lossFactor1, lossFactor2, ph1(), ph2());
return dp1dph2(ph1(), ph2());
} else {
throw new IllegalStateException("Unknown variable: " + variable);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,45 @@ public HvdcAcEmulationSide2ActiveFlowEquationTerm(LfHvdc hvdc, LfBus bus1, LfBus
super(hvdc, bus1, bus2, variableSet);
}

private static double p2(double p0, double k, double lossFactor1, double lossFactor2, double ph1, double ph2) {
return -(isController(p0, k, ph1, ph2) ? 1 : getLossMultiplier(lossFactor1, lossFactor2)) * (p0 + k * (ph1 - ph2));
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 static boolean isController(double p0, double k, double ph1, double ph2) {
return (p0 + k * (ph1 - ph2)) < 0;
private boolean isController(double rawP) {
return rawP < 0;
}

private static double dp2dph1(double p0, double k, double lossFactor1, double lossFactor2, double ph1, double ph2) {
return -(isController(p0, k, ph1, ph2) ? 1 : getLossMultiplier(lossFactor1, lossFactor2)) * k;
private boolean isInOperatingRange(double rawP) {
return rawP < pMaxFromCS2toCS1 && rawP > -pMaxFromCS1toCS2;
}

private static double dp2dph2(double p0, double k, double lossFactor1, double lossFactor2, double ph1, double ph2) {
return -dp2dph1(p0, k, lossFactor1, lossFactor2, ph1, ph2);
private double dp2dph1(double ph1, double ph2) {
double rawP = rawP(p0, k, ph1, ph2);
if (isInOperatingRange(rawP)) {
return -(isController(rawP) ? 1 : getVscLossMultiplier()) * k;
} else {
return 0;
}
}

private double dp2dph2(double ph1, double ph2) {
return -dp2dph1(ph1, ph2);
}

@Override
public double eval() {
return p2(p0, k, lossFactor1, lossFactor2, ph1(), ph2());
return p2(ph1(), ph2());
}

@Override
public double der(Variable<AcVariableType> variable) {
Objects.requireNonNull(variable);
if (variable.equals(ph1Var)) {
return dp2dph1(p0, k, lossFactor1, lossFactor2, ph1(), ph2());
return dp2dph1(ph1(), ph2());
} else if (variable.equals(ph2Var)) {
return dp2dph2(p0, k, lossFactor1, lossFactor2, ph1(), ph2());
return dp2dph2(ph1(), ph2());
} else {
throw new IllegalStateException("Unknown variable: " + variable);
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/powsybl/openloadflow/network/LfHvdc.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ public interface LfHvdc extends LfElement {
void setConverterStation2(LfVscConverterStation converterStation2);

void updateState();

double getPMaxFromCS1toCS2();

double getPMaxFromCS2toCS1();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
*/
package com.powsybl.openloadflow.network.impl;

import com.powsybl.iidm.network.HvdcLine;
import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControl;
import com.powsybl.iidm.network.extensions.HvdcOperatorActivePowerRange;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.util.Evaluable;
import com.powsybl.openloadflow.util.PerUnit;
Expand Down Expand Up @@ -40,16 +42,28 @@ public class LfHvdcImpl extends AbstractElement implements LfHvdc {

private boolean acEmulation;

public LfHvdcImpl(String id, LfBus bus1, LfBus bus2, LfNetwork network, HvdcAngleDroopActivePowerControl control,
boolean acEmulation) {
private final double pMaxFromCS1toCS2;

private final double pMaxFromCS2toCS1;

public LfHvdcImpl(String id, LfBus bus1, LfBus bus2, LfNetwork network, HvdcLine hvdcLine, boolean acEmulation) {
super(network);
this.id = Objects.requireNonNull(id);
this.bus1 = bus1;
this.bus2 = bus2;
this.acEmulation = acEmulation && control != null && control.isEnabled();
HvdcAngleDroopActivePowerControl droopControl = hvdcLine.getExtension(HvdcAngleDroopActivePowerControl.class);
this.acEmulation = acEmulation && droopControl != null && droopControl.isEnabled();
if (this.acEmulation) {
droop = control.getDroop();
p0 = control.getP0();
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();
}
}

Expand Down Expand Up @@ -157,4 +171,14 @@ public void updateState() {
((LfVscConverterStationImpl) converterStation2).getStation().getTerminal().setP(p2.eval() * PerUnit.SB);
}
}

@Override
public double getPMaxFromCS1toCS2() {
return Double.isNaN(pMaxFromCS1toCS2) ? Double.MAX_VALUE : pMaxFromCS1toCS2 / PerUnit.SB;
}

@Override
public double getPMaxFromCS2toCS1() {
return Double.isNaN(pMaxFromCS1toCS2) ? Double.MAX_VALUE : pMaxFromCS2toCS1 / PerUnit.SB;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -487,9 +487,8 @@ private static void createBranches(List<LfBus> lfBuses, LfNetwork lfNetwork, LfT
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 lfHvdc = new LfHvdcImpl(hvdcLine.getId(), lfBus1, lfBus2, lfNetwork, hvdcLine, parameters.isHvdcAcEmulation());
lfHvdc.setConverterStation1(cs1);
lfHvdc.setConverterStation2(cs2);
lfNetwork.addHvdc(lfHvdc);
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/com/powsybl/openloadflow/EquationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ void hvdcTest() {
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();
LfVscConverterStationImpl station1 = Mockito.mock(LfVscConverterStationImpl.class, new RuntimeExceptionAnswer());
LfVscConverterStationImpl station2 = Mockito.mock(LfVscConverterStationImpl.class, new RuntimeExceptionAnswer());
Mockito.doReturn(station1).when(hvdc).getConverterStation1();
Expand Down
61 changes: 61 additions & 0 deletions src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowVscTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControl;
import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControlAdder;
import com.powsybl.iidm.network.extensions.HvdcOperatorActivePowerRangeAdder;
import com.powsybl.loadflow.LoadFlow;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.loadflow.LoadFlowResult;
Expand Down Expand Up @@ -468,4 +469,64 @@ void testVscVoltageControlWithOneSideDisconnected() {
assertActivePowerEquals(0, network.getVscConverterStation("cs2").getTerminal());
assertVoltageEquals(vcs2, network.getVscConverterStation("cs2").getTerminal().getBusView().getBus());
}

@Test
void testAcEmuWithOperationalLimits() {
Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC);
// without limit p=195
network.getHvdcLine("hvdc23")
.newExtension(HvdcOperatorActivePowerRangeAdder.class)
.withOprFromCS2toCS1(180)
.withOprFromCS1toCS2(170)
.add();

LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
LoadFlowParameters p = new LoadFlowParameters();
p.setHvdcAcEmulation(true);
LoadFlowResult result = loadFlowRunner.run(network, p);

Choose a reason for hiding this comment

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

Is there a test with HvdcAngleDroopActivePowerControl but no HvdcOperatorActivePowerRange extension?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@rosiereflo Yes. All other tests with HVDC in AC emulation. For example testHvdcDirectionChangeAcEmulation

Copy link
Member

Choose a reason for hiding this comment

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

Do we have to take into account the active power range extension if the hvdc is operated in set point ?

Copy link
Collaborator Author

@vidaldid-rte vidaldid-rte Mar 28, 2024

Choose a reason for hiding this comment

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

Good question @annetill
My approach was to consider that an extension has priority.
This is consistent with the fact that the AC emulation takes priority on both the mode (side of inverter/rectifier) and active power setPoint.
But there is nature for conflict because data is duplicated and we need to agree whether giving priority on the extension is what we want or not.
@rosiereflo

EDIT: My comment was actually on the fact that poweRange has priority over hvdcLine.pmax (I had read Anne's point too quicky)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also the fix did not cover the case where AC Emlulation is off.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But I have not seen consistency check between p and pmax in HvdcLineAdder

Copy link
Member

Choose a reason for hiding this comment

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

In OLF, I have noticed that we can have a generator with a targetP over Pmax, it is not forbidden but maybe it should... for VSC converter station, no check either. Any idea @geofjamg ?

Copy link
Member

Choose a reason for hiding this comment

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

For generators it is very common to have a targetP outside Pmin/Pmax on real data.
Usual expected behavior for these is just to leave them where they are outside Pmin/Pmax and don't change them at all, e.g. exclude them from slack distribution.
I would be against enforcing targetP within Pmin/Pmax for generators in iIDM. Same for reactive power.
For HVDC I don't have any strong opinion though... There aren't that much, and AC emulation is ultra specific to few links.

Copy link
Member

@annetill annetill Mar 28, 2024

Choose a reason for hiding this comment

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

So we can say that it is the user's responsibility and not the purpose of this fix, related to hvdc in AC emulation.


assertTrue(result.isFullyConverged());

// Active flow capped at limit. Output has losses (due to VSC stations)
assertEquals(170, network.getHvdcConverterStation("cs2").getTerminal().getP(), DELTA_POWER);
assertEquals(-166.280, network.getHvdcConverterStation("cs3").getTerminal().getP(), DELTA_POWER);

// now invert power direction
HvdcAngleDroopActivePowerControl activePowerControl = network.getHvdcLine("hvdc23").getExtension(HvdcAngleDroopActivePowerControl.class);
activePowerControl.setP0(-activePowerControl.getP0());
result = loadFlowRunner.run(network, p);
assertTrue(result.isFullyConverged());

// Active flow capped at other direction's limit. Output has losses (due to VSC stations)
assertEquals(-176.062, network.getHvdcConverterStation("cs2").getTerminal().getP(), DELTA_POWER);
assertEquals(180, network.getHvdcConverterStation("cs3").getTerminal().getP(), DELTA_POWER);
}

@Test
void testAcEmuAndPMax() {
Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC);
// without limit p=195
network.getHvdcLine("hvdc23")
.setMaxP(170);

LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
LoadFlowParameters p = new LoadFlowParameters();
p.setHvdcAcEmulation(true);
LoadFlowResult result = loadFlowRunner.run(network, p);

assertTrue(result.isFullyConverged());

// Active flow capped at limit. Output has losses (due to VSC stations)
assertActivePowerEquals(170, network.getHvdcConverterStation("cs2").getTerminal());
assertActivePowerEquals(-166.280, network.getHvdcConverterStation("cs3").getTerminal());

// now invert power direction
HvdcAngleDroopActivePowerControl activePowerControl = network.getHvdcLine("hvdc23").getExtension(HvdcAngleDroopActivePowerControl.class);
activePowerControl.setP0(-activePowerControl.getP0());
result = loadFlowRunner.run(network, p);
assertTrue(result.isFullyConverged());

assertActivePowerEquals(-166.280, network.getHvdcConverterStation("cs2").getTerminal());
assertActivePowerEquals(170, network.getHvdcConverterStation("cs3").getTerminal());
}
}