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

Avoid slack distribution to fictitious loads #1028

Merged
merged 38 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
caf81cb
add isFictitious to LfLoad and filter fictitious loads from slack dis…
caioluke May 10, 2024
b3c8530
test slack distribution with fictitious load
caioluke May 10, 2024
9c471ef
Merge branch 'main' into add_isfictitious_to_loads
caioluke May 15, 2024
2603c81
check if load is fictitious through LoadType
caioluke May 15, 2024
77629e8
Merge branch 'main' into add_isfictitious_to_loads
caioluke May 16, 2024
a47906e
Merge branch 'main' into add_isfictitious_to_loads
caioluke Jun 3, 2024
bae40cb
Merge branch 'main' into add_isfictitious_to_loads
caioluke Jun 4, 2024
245c097
fix
caioluke Jun 4, 2024
1a11956
refacto method isLoadFictitious
caioluke Jun 4, 2024
5045ef7
Test more configuration with second unit test.
annetill Jun 4, 2024
4086b62
add SA test
caioluke Jun 6, 2024
cf9268b
fix typo: loose -> lose
caioluke Jun 10, 2024
63c2e6c
Merge branch 'main' into add_isfictitious_to_loads
caioluke Aug 28, 2024
64852e4
fix typo: loose -> lose
caioluke Aug 28, 2024
dd32089
Merge branch 'main' into add_isfictitious_to_loads
caioluke Aug 30, 2024
4bb287c
Merge branch 'main' into add_isfictitious_to_loads
caioluke Sep 2, 2024
0e2d9c2
Merge branch 'main' into add_isfictitious_to_loads
caioluke Sep 4, 2024
9f63eae
changes in LfContingency
caioluke Sep 4, 2024
97fc66f
changes in LfAction
caioluke Sep 4, 2024
c394912
Merge branch 'main' into add_isfictitious_to_loads
caioluke Sep 5, 2024
b75cdb3
Merge branch 'main' into add_isfictitious_to_loads
caioluke Sep 6, 2024
04d54b7
changes after comment
caioluke Sep 10, 2024
cf08e18
changes after comment cont.
caioluke Sep 10, 2024
963db91
TODO add breaking test
caioluke Sep 13, 2024
65dfe2f
wip breaking test
caioluke Sep 13, 2024
ce3b1a2
Merge branch 'main' into add_isfictitious_to_loads
caioluke Sep 16, 2024
ca1c237
use isLoadFictitious from LfLoadImpl everywhere possible
caioluke Sep 16, 2024
53a73fc
fix behaviour
caioluke Sep 16, 2024
9eda95b
add test with conform load
caioluke Sep 16, 2024
380c90d
Merge remote-tracking branch 'origin/add_isfictitious_to_loads' into …
caioluke Sep 16, 2024
5cf6d41
refactoring
vidaldid-rte Sep 17, 2024
712ab42
rename method and add doc
caioluke Sep 17, 2024
ec765b0
harmonize concepts - passive -> notParticipating to be like groups
vidaldid-rte Sep 18, 2024
ff1cee1
typo
vidaldid-rte Sep 18, 2024
dd5f96b
Small clean.
annetill Sep 18, 2024
c449a9c
Fix wording.
annetill Sep 18, 2024
30452c1
Small renaming.
annetill Sep 19, 2024
411a27d
Use map for both loadsRefs and loadsAbsVariableTargetP to ensure cons…
vidaldid-rte Sep 19, 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
2 changes: 2 additions & 0 deletions docs/loadflow/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ $$
If `balanceType` equals to `PROPORTIONAL_TO_LOAD`, the power factor remains constant scaling the global $P0$ and $Q0$ of the load.
If `balanceType` equals to `PROPORTIONAL_TO_CONFORM_LOAD`, the power factor remains constant scaling only the variable parts. Thus, we fully rely on [load detail extension](inv:powsyblcore:*:*:#load-detail-extension).

In both cases, slack is not distributed to fictitious loads. A load can be fictitious by setting its boolean attribute `isFictitious` or by having a `loadType` equal to `LoadType.FICTITIOUS`.

The default value for `loadPowerFactorConstant` property is `false`.

**slackBusPMaxMismatch**
Expand Down
28 changes: 8 additions & 20 deletions src/main/java/com/powsybl/openloadflow/network/LfAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public final class LfAction {
private record TapPositionChange(LfBranch branch, int value, boolean isRelative) {
}

private record LoadShift(String loadId, LfLoad load, PowerShift powerShift) {
private record LoadShift(String loadId, LfLoad lfLoad, PowerShift powerShift) {
}
Copy link
Collaborator

@vidaldid-rte vidaldid-rte Sep 9, 2024

Choose a reason for hiding this comment

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

You should not keep directly iidm.network objects in memory since fast restart is implemented (#635)
If you need to keep the Load in a record, use a WeakRef instead.
Otherwise, this will prevent the GC from deleting a Network if the fast restart mode is on.

See LfBatteryImpl for an example.
Ref are created using com.powsybl.openloadflow.network.impl.Ref

Copy link
Member Author

Choose a reason for hiding this comment

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

I see, thanks for the explanation
Since I don't need a lot of information about the load, only it's id and if it's fictitious or not, I kept only those informations instead of creating a Ref


private record GeneratorChange(LfGenerator generator, double activePowerValue, boolean isRelative) {
Expand Down Expand Up @@ -136,21 +136,9 @@ private static Optional<LfAction> create(LoadAction action, LfNetwork lfNetwork,
Terminal terminal = load.getTerminal();
Bus bus = Networks.getBus(terminal, breakers);
if (bus != null) {
double activePowerShift = 0;
double reactivePowerShift = 0;
OptionalDouble activePowerValue = action.getActivePowerValue();
OptionalDouble reactivePowerValue = action.getReactivePowerValue();
if (activePowerValue.isPresent()) {
activePowerShift = action.isRelativeValue() ? activePowerValue.getAsDouble() : activePowerValue.getAsDouble() - load.getP0();
}
if (reactivePowerValue.isPresent()) {
reactivePowerShift = action.isRelativeValue() ? reactivePowerValue.getAsDouble() : reactivePowerValue.getAsDouble() - load.getQ0();
}
// In case of a power shift, we suppose that the shift on a load P0 is exactly the same on the variable active power
// of P0 that could be described in a LoadDetail extension.
PowerShift powerShift = new PowerShift(activePowerShift / PerUnit.SB, activePowerShift / PerUnit.SB, reactivePowerShift / PerUnit.SB);
LfLoad lfLoad = lfNetwork.getLoadById(load.getId());
if (lfLoad != null) {
PowerShift powerShift = PowerShift.createPowerShift(load, action);
return Optional.of(new LfAction(action.getId(), null, null, null, new LoadShift(load.getId(), lfLoad, powerShift), null, null, null));
}
}
Expand Down Expand Up @@ -318,13 +306,13 @@ private void applyTapPositionChange() {
}

private void applyLoadShift() {
LfLoad load = loadShift.load();
if (!load.isOriginalLoadDisabled(loadShift.loadId())) {
String loadId = loadShift.loadId();
LfLoad lfLoad = loadShift.lfLoad();
if (!lfLoad.isOriginalLoadDisabled(loadId)) {
PowerShift shift = loadShift.powerShift();
load.setTargetP(load.getTargetP() + shift.getActive());
load.setTargetQ(load.getTargetQ() + shift.getReactive());
load.setAbsVariableTargetP(load.getAbsVariableTargetP()
+ Math.signum(shift.getActive()) * Math.abs(shift.getVariableActive()));
lfLoad.setTargetP(lfLoad.getTargetP() + shift.getActive());
lfLoad.setTargetQ(lfLoad.getTargetQ() + shift.getReactive());
lfLoad.setAbsVariableTargetP(lfLoad.getAbsVariableTargetP() + Math.signum(shift.getActive()) * Math.abs(shift.getVariableActive()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public void apply(LoadFlowParameters.BalanceType balanceType) {
LfLoad load = e.getKey();
LfLostLoad lostLoad = e.getValue();
PowerShift shift = lostLoad.getPowerShift();
load.setTargetP(load.getTargetP() - getUpdatedLoadP0(load, balanceType, shift.getActive(), shift.getVariableActive()));
load.setTargetP(load.getTargetP() - getUpdatedLoadP0(load, balanceType, shift.getActive(), shift.getVariableActive(), lostLoad.getNotParticipatingLoadP0()));
load.setTargetQ(load.getTargetQ() - shift.getReactive());
load.setAbsVariableTargetP(load.getAbsVariableTargetP() - Math.abs(shift.getVariableActive()));
lostLoad.getOriginalIds().forEach(loadId -> load.setOriginalLoadDisabled(loadId, true));
Copy link
Collaborator

@vidaldid-rte vidaldid-rte Sep 9, 2024

Choose a reason for hiding this comment

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

Why do you need to do somthing special for fictive load in setAbsVariableTargetP for fictive loads since the getAbsVariableTargetP is harcoded for fictive load , by this new code::
private double getAbsVariableTargetP(Load load) {
if (isLoadFictitious(load)) {
return 0.0;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Ha, you are right.
I didn't realize that.
Reverted back to what it was.

Expand Down Expand Up @@ -189,16 +189,17 @@ public void apply(LoadFlowParameters.BalanceType balanceType) {
}
}

private static double getUpdatedLoadP0(LfLoad load, LoadFlowParameters.BalanceType balanceType, double initialP0, double initialVariableActivePower) {
private static double getUpdatedLoadP0(LfLoad lfLoad, LoadFlowParameters.BalanceType balanceType, double initialP0, double initialVariableActivePower, double notParticipatingLoadP0) {
double factor = 0;
if (load.getOriginalLoadCount() > 0) {
double loadVariableTargetP = lfLoad.getAbsVariableTargetP();
if (loadVariableTargetP != 0 && lfLoad.getOriginalLoadCount() > 0) {
if (balanceType == LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD) {
factor = Math.abs(initialP0) / load.getAbsVariableTargetP();
factor = (Math.abs(initialP0) - Math.abs(notParticipatingLoadP0)) / loadVariableTargetP;
} else if (balanceType == LoadFlowParameters.BalanceType.PROPORTIONAL_TO_CONFORM_LOAD) {
factor = initialVariableActivePower / load.getAbsVariableTargetP();
factor = initialVariableActivePower / loadVariableTargetP;
}
}
return initialP0 + (load.getTargetP() - load.getInitialTargetP()) * factor;
return initialP0 + (lfLoad.getTargetP() - lfLoad.getInitialTargetP()) * factor;
}

public Set<LfBus> getLoadAndGeneratorBuses() {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/powsybl/openloadflow/network/LfLoad.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public interface LfLoad extends PropertyBag {

LfBus getBus();

boolean isOriginalLoadNotParticipating(String originalId);

Optional<LfLoadModel> getLoadModel();

double getInitialTargetP();
Expand All @@ -44,7 +46,7 @@ public interface LfLoad extends PropertyBag {

List<String> getOriginalIds();

double getOriginalLoadCount();
int getOriginalLoadCount();

boolean isOriginalLoadDisabled(String originalId);

Expand Down
18 changes: 17 additions & 1 deletion src/main/java/com/powsybl/openloadflow/network/LfLostLoad.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
public class LfLostLoad {

private final PowerShift powerShift = new PowerShift();

private final Set<String> ids = new LinkedHashSet<>();
private double notParticipatingLoadP0 = 0.0;

public PowerShift getPowerShift() {
return powerShift;
Expand All @@ -30,6 +30,22 @@ public Set<String> getOriginalIds() {
return ids;
}

/**
* Updates the contribution of loads that do not participate to slack distribution
*/
public void updateNotParticipatingLoadP0(LfLoad load, String originalLoadId, PowerShift powerShift) {
if (load.isOriginalLoadNotParticipating(originalLoadId)) {
notParticipatingLoadP0 += Math.abs(powerShift.getActive());
}
}

/**
* Returns the contribution of loads that do not participate to slack distribution
*/
public double getNotParticipatingLoadP0() {
return notParticipatingLoadP0;
}

@Override
public String toString() {
return "LfLostLoad(" +
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/powsybl/openloadflow/network/PowerShift.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
*/
package com.powsybl.openloadflow.network;

import com.powsybl.action.LoadAction;
import com.powsybl.iidm.network.Load;
import com.powsybl.openloadflow.network.impl.LfLoadImpl;
import com.powsybl.openloadflow.util.PerUnit;

import java.util.Objects;

/**
Expand Down Expand Up @@ -56,4 +61,30 @@ public String toString() {
+ variableActive + ", "
+ reactive + ")";
}

/**
* Returns the power shift for a complete loss of a load
*/
public static PowerShift createPowerShift(Load load, boolean slackDistributionOnConformLoad) {
double variableActivePower = Math.abs(LfLoadImpl.getAbsVariableTargetPPerUnit(load, slackDistributionOnConformLoad));
return new PowerShift(load.getP0() / PerUnit.SB,
variableActivePower,
load.getQ0() / PerUnit.SB); // ensurePowerFactorConstant is not supported.
}

/**
* Returns the power shift for a load action.
*/
public static PowerShift createPowerShift(Load load, LoadAction loadAction) {
double activePowerShift = loadAction.getActivePowerValue().stream().map(a -> loadAction.isRelativeValue() ? a : a - load.getP0()).findAny().orElse(0);
double reactivePowerShift = loadAction.getReactivePowerValue().stream().map(r -> loadAction.isRelativeValue() ? r : r - load.getQ0()).findAny().orElse(0);

// In case of a power shift, we suppose that the shift on a load P0 is exactly the same on the variable active power
// of P0 that could be described in a LoadDetail extension.
// Note that fictitious loads have a zero variable active power shift.
double variableActivePower = LfLoadImpl.isLoadNotParticipating(load) ? 0.0 : activePowerShift;
return new PowerShift(activePowerShift / PerUnit.SB,
variableActivePower / PerUnit.SB,
reactivePowerShift / PerUnit.SB);
}
}
52 changes: 36 additions & 16 deletions src/main/java/com/powsybl/openloadflow/network/impl/LfLoadImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.powsybl.iidm.network.DanglingLine;
import com.powsybl.iidm.network.LccConverterStation;
import com.powsybl.iidm.network.Load;
import com.powsybl.iidm.network.LoadType;
import com.powsybl.iidm.network.extensions.LoadDetail;
import com.powsybl.iidm.network.util.HvdcUtils;
import com.powsybl.openloadflow.network.*;
Expand All @@ -29,15 +30,15 @@ public class LfLoadImpl extends AbstractLfInjection implements LfLoad {

private final LfLoadModel loadModel;

private final List<Ref<Load>> loadsRefs = new ArrayList<>();
private final Map<String, Ref<Load>> loadsRefs = new HashMap<>();

private final List<Ref<LccConverterStation>> lccCsRefs = new ArrayList<>();

private double targetQ = 0;

private boolean ensurePowerFactorConstantByLoad = false;

private final List<Double> loadsAbsVariableTargetP = new ArrayList<>();
private final HashMap<String, Double> loadsAbsVariableTargetP = new HashMap<>();
Copy link
Member Author

Choose a reason for hiding this comment

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

Do we need to define the object as the implementation class? @vidaldid-rte

Map<String, Double> instead of HashMap<String, Double>

Copy link
Collaborator

@vidaldid-rte vidaldid-rte Sep 20, 2024

Choose a reason for hiding this comment

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

You mean define it as a Map ? It avoids some pedantic discussions with Sonar lovers so why not, although it does not really matter in a private field definition.
There is still an issue with that - in theory it slows down iterations on the array (that happen frequently) because whe would break the processor cache here. I don't know how much it matters in real perf.
But we may revert this change and instead add commentarys that it is important to update the array and the map in sync because the code relies on the order.


private double absVariableTargetP = 0;

Expand All @@ -63,7 +64,7 @@ public String getId() {

@Override
public List<String> getOriginalIds() {
return Stream.concat(loadsRefs.stream().map(r -> r.get().getId()),
return Stream.concat(loadsRefs.values().stream().map(r -> r.get().getId()),
lccCsRefs.stream().map(r -> r.get().getId()))
.toList();
}
Expand All @@ -73,13 +74,21 @@ public LfBus getBus() {
return bus;
}

@Override
public boolean isOriginalLoadNotParticipating(String originalLoadId) {
if (loadsRefs.get(originalLoadId) == null) {
return false;
}
return isLoadNotParticipating(loadsRefs.get(originalLoadId).get());
}

@Override
public Optional<LfLoadModel> getLoadModel() {
return Optional.ofNullable(loadModel);
}

void add(Load load, LfNetworkParameters parameters) {
loadsRefs.add(Ref.create(load, parameters.isCacheEnabled()));
loadsRefs.put(load.getId(), Ref.create(load, parameters.isCacheEnabled()));
loadsDisablingStatus.put(load.getId(), false);
double p0 = load.getP0();
double q0 = load.getQ0();
Expand All @@ -97,8 +106,8 @@ void add(Load load, LfNetworkParameters parameters) {
if (p0 < 0 || hasVariableActivePower || reactiveOnlyLoad) {
ensurePowerFactorConstantByLoad = true;
}
double absTargetP = getAbsVariableTargetP(load);
loadsAbsVariableTargetP.add(absTargetP);
double absTargetP = getAbsVariableTargetPPerUnit(load, distributedOnConformLoad);
loadsAbsVariableTargetP.put(load.getId(), absTargetP);
absVariableTargetP += absTargetP;
}

Expand Down Expand Up @@ -158,7 +167,10 @@ public void setAbsVariableTargetP(double absVariableTargetP) {
this.absVariableTargetP = absVariableTargetP;
}

private double getAbsVariableTargetP(Load load) {
public static double getAbsVariableTargetPPerUnit(Load load, boolean distributedOnConformLoad) {
if (isLoadNotParticipating(load)) {
return 0.0;
}
double varP;
if (distributedOnConformLoad) {
varP = load.getExtension(LoadDetail.class) == null ? 0 : load.getExtension(LoadDetail.class).getVariableActivePower();
Expand All @@ -169,17 +181,17 @@ private double getAbsVariableTargetP(Load load) {
}

@Override
public double getOriginalLoadCount() {
public int getOriginalLoadCount() {
return loadsRefs.size();
}

private double getParticipationFactor(int i) {
private double getParticipationFactor(String originalLoadId) {
// FIXME
// After a load contingency or a load action, only the global variable targetP is updated.
// The list loadsAbsVariableTargetP never changes. It is not an issue for security analysis as the network is
// never updated. Excepted if loadPowerFactorConstant is true, the new targetQ could be wrong after a load contingency
// or a load action.
return absVariableTargetP != 0 ? loadsAbsVariableTargetP.get(i) / absVariableTargetP : 0;
return absVariableTargetP != 0 ? loadsAbsVariableTargetP.get(originalLoadId) / absVariableTargetP : 0;
}

private double calculateP() {
Expand All @@ -199,9 +211,9 @@ public void updateState(boolean loadPowerFactorConstant, boolean breakers) {
double pv = p == EvaluableConstants.NAN ? 1 : calculateP() / targetP; // extract part of p that is dependent to voltage
double qv = q == EvaluableConstants.NAN ? 1 : calculateQ() / targetQ;
double diffLoadTargetP = targetP - initialTargetP;
for (int i = 0; i < loadsRefs.size(); i++) {
Load load = loadsRefs.get(i).get();
double diffP0 = diffLoadTargetP * getParticipationFactor(i) * PerUnit.SB;
for (Ref<Load> refLoad : loadsRefs.values()) {
Load load = refLoad.get();
double diffP0 = diffLoadTargetP * getParticipationFactor(load.getId()) * PerUnit.SB;
double updatedP0 = load.getP0() + diffP0;
double updatedQ0 = load.getQ0() + (loadPowerFactorConstant ? getPowerFactor(load) * diffP0 : 0.0);
load.getTerminal()
Expand All @@ -223,9 +235,9 @@ public void updateState(boolean loadPowerFactorConstant, boolean breakers) {
@Override
public double calculateNewTargetQ(double diffTargetP) {
double newLoadTargetQ = 0;
for (int i = 0; i < loadsRefs.size(); i++) {
Load load = loadsRefs.get(i).get();
double updatedQ0 = load.getQ0() / PerUnit.SB + getPowerFactor(load) * diffTargetP * getParticipationFactor(i);
for (Ref<Load> refLoad : loadsRefs.values()) {
Load load = refLoad.get();
double updatedQ0 = load.getQ0() / PerUnit.SB + getPowerFactor(load) * diffTargetP * getParticipationFactor(load.getId());
newLoadTargetQ += updatedQ0;
}
return newLoadTargetQ;
Expand Down Expand Up @@ -255,6 +267,14 @@ private static double getPowerFactor(Load load) {
return load.getP0() != 0 ? load.getQ0() / load.getP0() : 1;
}

/**
* 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 load.isFictitious() || LoadType.FICTITIOUS.equals(load.getLoadType());
}

@Override
public Evaluable getP() {
return p;
Expand Down
Loading