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

Ac emulation outerloop #1048

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7b3b61c
AcEmulationControl
Hadrien-Godard May 27, 2024
14ea87e
Continuing AcEmulationControl
Hadrien-Godard May 27, 2024
e334a54
Merge remote-tracking branch 'origin/main' into ac_emulation_outerloop
Hadrien-Godard Jun 3, 2024
5bee2ba
AcEmulationControl end
Hadrien-Godard Jun 3, 2024
2384e46
AcEmulationOuterLoop
Hadrien-Godard Jun 3, 2024
ec34c48
Merge remote-tracking branch 'origin/main' into ac_emulation_outerloop
Hadrien-Godard Jun 7, 2024
4cf7a20
HvdcAcEmulationSide1ActiveFlowEquationTerm
Hadrien-Godard Jun 7, 2024
52edfd9
Continuing outerloop
Hadrien-Godard Jun 7, 2024
2aaba91
End outerloop
Hadrien-Godard Jun 10, 2024
93d7de0
Merge branch 'main' into ac_emulation_outerloop
Hadrien-Godard Jun 14, 2024
448652a
Fix typos
Hadrien-Godard Jun 14, 2024
25ad3a8
Fix tests
Hadrien-Godard Jun 14, 2024
1c22515
Fix tests
Hadrien-Godard Jun 14, 2024
39bf64a
Reporter
Hadrien-Godard Jun 14, 2024
7e30920
Reporter test fix
Hadrien-Godard Jun 14, 2024
8b596e8
Fix
Hadrien-Godard Jun 14, 2024
2f44f84
Tests ok
Hadrien-Godard Jun 14, 2024
510192e
Fix
Hadrien-Godard Jun 14, 2024
eb70309
DcLoadFlow
Hadrien-Godard Jun 14, 2024
5897654
Refacto
Hadrien-Godard Jun 14, 2024
305a6a4
Test
Hadrien-Godard Jun 14, 2024
5d43d95
Listeners
Hadrien-Godard Jun 14, 2024
d295f9c
New test
Hadrien-Godard Jun 14, 2024
c174274
Merge branch 'main' into ac_emulation_outerloop
Hadrien-Godard Jun 24, 2024
198c4e1
Rename
Hadrien-Godard Jun 24, 2024
2521db6
Merge remote-tracking branch 'origin/main' into ac_emulation_outerloop
Hadrien-Godard Jun 24, 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 @@ -112,6 +112,13 @@ protected static Optional<AcOuterLoop> createPhaseControlOuterLoop(LoadFlowParam
return createPhaseControlOuterLoop(parameters, parametersExt.getPhaseShifterControlMode());
}

protected static Optional<AcOuterLoop> createAcEmulationOuterLoop(LoadFlowParameters parameters) {
if (parameters.isHvdcAcEmulation()) {
return Optional.of(new AcEmulationOuterLoop());
}
return Optional.empty();
}

protected static Optional<AcOuterLoop> createAutomationSystemOuterLoop(OpenLoadFlowParameters parametersExt) {
if (parametersExt.isSimulateAutomationSystems()) {
return Optional.of(new AutomationSystemOuterLoop());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public List<AcOuterLoop> configure(LoadFlowParameters parameters, OpenLoadFlowPa
createTransformerReactivePowerControlOuterLoop(parametersExt).ifPresent(outerLoops::add);
// shunt compensator voltage control
createShuntVoltageControlOuterLoop(parameters, parametersExt).ifPresent(outerLoops::add);
// AC emulation
createAcEmulationOuterLoop(parameters).ifPresent(outerLoops::add);
// automation system
Hadrien-Godard marked this conversation as resolved.
Show resolved Hide resolved
createAutomationSystemOuterLoop(parametersExt).ifPresent(outerLoops::add);
return outerLoops;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public class ExplicitAcOuterLoopConfig extends AbstractAcOuterLoopConfig {
SimpleTransformerVoltageControlOuterLoop.NAME,
TransformerVoltageControlOuterLoop.NAME,
AutomationSystemOuterLoop.NAME,
IncrementalTransformerReactivePowerControlOuterLoop.NAME);
IncrementalTransformerReactivePowerControlOuterLoop.NAME,
AcEmulationOuterLoop.NAME);

private static Optional<AcOuterLoop> createOuterLoop(String name, LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt) {
return switch (name) {
Expand Down Expand Up @@ -65,6 +66,7 @@ private static Optional<AcOuterLoop> createOuterLoop(String name, LoadFlowParame
parametersExt.getGeneratorVoltageControlMinNominalVoltage());
case AutomationSystemOuterLoop.NAME -> createAutomationSystemOuterLoop(parametersExt);
case IncrementalTransformerReactivePowerControlOuterLoop.NAME -> createTransformerReactivePowerControlOuterLoop(parametersExt);
case AcEmulationOuterLoop.NAME -> createAcEmulationOuterLoop(parameters);
default -> throw new PowsyblException("Unknown outer loop '" + name + "'");
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -59,9 +58,9 @@ public double eval() {
public double der(Variable<AcVariableType> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -59,9 +58,9 @@ public double eval() {
public double der(Variable<AcVariableType> 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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.AbstractAcEmulationOuterLoop;
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 <hadrien.godard at artelys.com>}
*/
public class AcEmulationOuterLoop
extends AbstractAcEmulationOuterLoop<AcVariableType, AcEquationType, AcLoadFlowParameters, AcLoadFlowContext, AcOuterLoopContext>
Hadrien-Godard marked this conversation as resolved.
Show resolved Hide resolved
implements AcOuterLoop {

private static final Logger LOGGER = LoggerFactory.getLogger(AcEmulationOuterLoop.class);
public static final String NAME = "AcEmulation";

@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);
Copy link
Contributor

Choose a reason for hiding this comment

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

I have a problem with this NULL status.
In the end it would produce a solution with status FULLY CONVERGED and where the HVDC transports no power.
But it may not represent the reality and be misleading. And cause users of the simulator to make wrong decisions.

For example the flip fop may result from changes of Phase Tap changers, or other automates, that changes the active power routes. It is not necessarily that the active power in the HVDC has small oscillations around 0 and can be neglected.

Have you found examples where small endless oscillations happen here ? Wouldn't a dead angle zone for which P is 0 be better ?

Copy link
Member Author

Choose a reason for hiding this comment

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

The idea here is that, if the HVDC is changing its feeding side multiple times during the LF algorithm, then its flow should be very close to 0. In realistic case, the feeding side should never change at all during iterations.

We do not have any cases where endless oscillations around zero occur, but in theory, without this security, it may happen. It is mainly because of the discontinuity of the derivative from k to -k around 0.

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;
Copy link
Contributor

Choose a reason for hiding this comment

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

What you really want to know here is whether the hvdc link is active. That is if the hvdc in AC emulation mode and the P1 and P2 equations terms are active.
This condition here looks like a code duplication and may create problems in the future if we need to change the conditions and notifications that activate/deactivate P1 and P2 for LfHDVC.
Why not making getP1 and getP2 returning an EquationTerm instead of an evaluable and checking their active status ?
That would ensure consistency.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok I will try that

}
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);
}
}
Loading