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 limits handling in slack distribution on generators #1041

Merged
merged 6 commits into from
Jul 1, 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 @@ -56,7 +56,8 @@ public void initialize(AcOuterLoopContext context) {
@Override
public OuterLoopResult check(AcOuterLoopContext context, ReportNode reportNode) {
double slackBusActivePowerMismatch = context.getLastSolverResult().getSlackBusActivePowerMismatch();
boolean shouldDistributeSlack = Math.abs(slackBusActivePowerMismatch) > slackBusPMaxMismatch / PerUnit.SB;
double absMismatch = Math.abs(slackBusActivePowerMismatch);
boolean shouldDistributeSlack = absMismatch > slackBusPMaxMismatch / PerUnit.SB && absMismatch > ActivePowerDistribution.P_RESIDUE_EPS;
Comment on lines +59 to +60
Copy link
Member Author

@jeandemanged jeandemanged Jun 25, 2024

Choose a reason for hiding this comment

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

ℹ️ Note to reviewers: This part could be a separate BugFix PR... let me know...

if slackBusPMaxMismatch is small in comparison with ActivePowerDistribution.P_RESIDUE_EPS, the outerloop becomes unstable because 1/ ActivePowerDistribution will not distribute anything and 2/ Outerloop says something has to be distributed

Copy link
Member

Choose a reason for hiding this comment

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

Thank you for catching this! I think we can let it in this PR.


if (!shouldDistributeSlack) {
LOGGER.debug("Already balanced");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
public class BusDcState extends ElementState<LfBus> {

private final Map<String, Double> generatorsTargetP;
private final Map<String, Double> generatorsInitialTargetP;
private final Map<String, Boolean> participatingGenerators;
private final Map<String, Boolean> disablingStatusGenerators;
private final List<LoadDcState> loadStates;
Expand Down Expand Up @@ -45,6 +46,7 @@ protected void restore(LfLoad load) {
public BusDcState(LfBus bus) {
super(bus);
this.generatorsTargetP = bus.getGenerators().stream().collect(Collectors.toMap(LfGenerator::getId, LfGenerator::getTargetP));
this.generatorsInitialTargetP = bus.getGenerators().stream().collect(Collectors.toMap(LfGenerator::getId, LfGenerator::getInitialTargetP));
this.participatingGenerators = bus.getGenerators().stream().collect(Collectors.toMap(LfGenerator::getId, LfGenerator::isParticipating));
this.disablingStatusGenerators = bus.getGenerators().stream().collect(Collectors.toMap(LfGenerator::getId, LfGenerator::isDisabled));
loadStates = bus.getLoads().stream().map(load -> createLoadState().save(load)).toList();
Expand All @@ -58,6 +60,7 @@ protected LoadDcState createLoadState() {
public void restore() {
super.restore();
element.getGenerators().forEach(g -> g.setTargetP(generatorsTargetP.get(g.getId())));
element.getGenerators().forEach(g -> g.setInitialTargetP(generatorsInitialTargetP.get(g.getId())));
element.getGenerators().forEach(g -> g.setParticipating(participatingGenerators.get(g.getId())));
element.getGenerators().forEach(g -> g.setDisabled(disablingStatusGenerators.get(g.getId())));
for (int i = 0; i < loadStates.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ private void applyGeneratorChange(LfNetworkParameters networkParameters) {
if (!generator.isDisabled()) {
double newTargetP = generatorChange.isRelative() ? generator.getTargetP() + generatorChange.activePowerValue() : generatorChange.activePowerValue();
generator.setTargetP(newTargetP);
Copy link
Member

Choose a reason for hiding this comment

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

I was about to merge the PR but I have a doubt, why you don't change these lines too in a LfContingency:

        Set<LfBus> generatorBuses = new HashSet<>();
        for (LfGenerator generator : lostGenerators) {
            **generator.setTargetP(0);** -> initial targetP to zero too ?
            LfBus bus = generator.getBus();
            generatorBuses.add(bus);
            generator.setParticipating(false);
            generator.setDisabled(true);

Copy link
Member

Choose a reason for hiding this comment

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

Checked: indeed the generator.setParticipating(false); solved this issue.

Copy link
Member Author

@jeandemanged jeandemanged Jul 1, 2024

Choose a reason for hiding this comment

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

indeed, but adding setInitialTargetP(0) would improve code clarity and won't harm, I am adding it.

generator.setInitialTargetP(newTargetP);
if (!AbstractLfGenerator.checkActivePowerControl(generator.getId(), generator.getTargetP(), generator.getMinP(), generator.getMaxP(),
networkParameters.getPlausibleActivePowerLimit(), networkParameters.isUseActiveLimits(), null)) {
generator.setParticipating(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import java.io.UncheckedIOException;
import java.io.Writer;
import java.util.*;
import java.util.stream.Collectors;

/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
Expand Down Expand Up @@ -61,8 +60,8 @@ public LfContingency(String id, int index, int createdSynchronousComponentsCount
for (LfBus bus : disabledNetwork.getBuses()) {
disconnectedLoadActivePower += bus.getLoadTargetP();
disconnectedGenerationActivePower += bus.getGenerationTargetP();
disconnectedElementIds.addAll(bus.getGenerators().stream().map(LfGenerator::getId).collect(Collectors.toList()));
disconnectedElementIds.addAll(bus.getLoads().stream().flatMap(l -> l.getOriginalIds().stream()).collect(Collectors.toList()));
disconnectedElementIds.addAll(bus.getGenerators().stream().map(LfGenerator::getId).toList());
disconnectedElementIds.addAll(bus.getLoads().stream().flatMap(l -> l.getOriginalIds().stream()).toList());
bus.getControllerShunt().ifPresent(shunt -> disconnectedElementIds.addAll(shunt.getOriginalIds()));
bus.getShunt().ifPresent(shunt -> disconnectedElementIds.addAll(shunt.getOriginalIds()));
}
Expand All @@ -75,7 +74,7 @@ public LfContingency(String id, int index, int createdSynchronousComponentsCount
disconnectedGenerationActivePower += generator.getTargetP();
disconnectedElementIds.add(generator.getOriginalId());
}
disconnectedElementIds.addAll(disabledNetwork.getBranches().stream().map(LfBranch::getId).collect(Collectors.toList()));
disconnectedElementIds.addAll(disabledNetwork.getBranches().stream().map(LfBranch::getId).toList());
// FIXME: shuntsShift has to be included in the disconnected elements.
}

Expand Down Expand Up @@ -156,14 +155,15 @@ public void apply(LoadFlowParameters.BalanceType balanceType) {
Set<LfBus> generatorBuses = new HashSet<>();
for (LfGenerator generator : lostGenerators) {
generator.setTargetP(0);
generator.setInitialTargetP(0);
LfBus bus = generator.getBus();
generatorBuses.add(bus);
generator.setParticipating(false);
generator.setDisabled(true);
if (generator.getGeneratorControlType() != LfGenerator.GeneratorControlType.OFF) {
generator.setGeneratorControlType(LfGenerator.GeneratorControlType.OFF);
bus.getGeneratorVoltageControl().ifPresent(vc -> vc.updateReactiveKeys());
bus.getGeneratorReactivePowerControl().ifPresent(rc -> rc.updateReactiveKeys());
bus.getGeneratorVoltageControl().ifPresent(GeneratorVoltageControl::updateReactiveKeys);
bus.getGeneratorReactivePowerControl().ifPresent(GeneratorReactivePowerControl::updateReactiveKeys);
} else {
bus.setGenerationTargetQ(bus.getGenerationTargetQ() - generator.getTargetQ());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ static double qToK(LfGenerator generator, double q) {

double getInitialTargetP();

void setInitialTargetP(double initialTargetP);

void setInitialTargetPToTargetP();

double getTargetP();

void setTargetP(double targetP);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ protected NetworkState(LfNetwork network, List<BusState> busStates, List<BranchS
public static NetworkState save(LfNetwork network) {
Objects.requireNonNull(network);
LOGGER.trace("Saving network state");
network.getBuses().stream().flatMap(b -> b.getGenerators().stream()).forEach(LfGenerator::setInitialTargetPToTargetP);
List<BusState> busStates = ElementState.save(network.getBuses(), BusState::save);
List<BranchState> branchStates = ElementState.save(network.getBranches(), BranchState::save);
List<HvdcState> hvdcStates = ElementState.save(network.getHvdcs(), HvdcState::save);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public double getInitialTargetP() {
return initialTargetP;
}

public void setInitialTargetP(double initialTargetP) {
this.initialTargetP = initialTargetP;
}

public void setInitialTargetPToTargetP() {
initialTargetP = targetP;
}

public double getTargetP() {
return targetP;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ public double getTargetP() {
}
}

@Override
public void setInitialTargetP(double initialTargetP) {
// no-op
}

@Override
public void setInitialTargetPToTargetP() {
// no-op
}

@Override
public double getLossFactor() {
return lossFactor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
Expand All @@ -25,15 +28,13 @@ public final class ActivePowerDistribution {
*/
public static final double P_RESIDUE_EPS = Math.pow(10, -5);

public record StepResult(double done, boolean movedBuses) { }

public interface Step {

String getElementType();

List<ParticipatingElement> getParticipatingElements(Collection<LfBus> buses);

StepResult run(List<ParticipatingElement> participatingElements, int iteration, double remainingMismatch);
double run(List<ParticipatingElement> participatingElements, int iteration, double remainingMismatch);
}

public record Result(int iteration, double remainingMismatch, boolean movedBuses) { }
Expand All @@ -54,25 +55,26 @@ public Result run(LfNetwork network, double activePowerMismatch) {

public Result run(Collection<LfBus> buses, double activePowerMismatch) {
List<ParticipatingElement> participatingElements = step.getParticipatingElements(buses);
final Map<ParticipatingElement, Double> initialP = participatingElements.stream()
.collect(Collectors.toUnmodifiableMap(Function.identity(), ParticipatingElement::getTargetP));

int iteration = 0;
double remainingMismatch = activePowerMismatch;
boolean movedBuses = false;
while (!participatingElements.isEmpty()
&& Math.abs(remainingMismatch) > P_RESIDUE_EPS) {

if (ParticipatingElement.participationFactorNorm(participatingElements) > 0.0) {
StepResult stepResult = step.run(participatingElements, iteration, remainingMismatch);
remainingMismatch -= stepResult.done();
if (stepResult.movedBuses()) {
movedBuses = true;
}
double done = step.run(participatingElements, iteration, remainingMismatch);
Copy link
Member

Choose a reason for hiding this comment

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

"powerDistributed" might be a clearer name than "done", don't you agree?

remainingMismatch -= done;
} else {
break;
}
iteration++;
}

final boolean movedBuses = initialP.entrySet().stream()
.anyMatch(e -> Math.abs(e.getKey().getTargetP() - e.getValue()) > P_RESIDUE_EPS);

return new Result(iteration, remainingMismatch, movedBuses);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -55,16 +56,27 @@ public List<ParticipatingElement> getParticipatingElements(Collection<LfBus> bus
.flatMap(bus -> bus.getGenerators().stream())
.filter(generator -> isParticipating(generator) && getParticipationFactor(generator) != 0)
.map(generator -> new ParticipatingElement(generator, getParticipationFactor(generator)))
.collect(Collectors.toList());
.collect(Collectors.toCollection(LinkedList::new));
}

@Override
public ActivePowerDistribution.StepResult run(List<ParticipatingElement> participatingElements, int iteration, double remainingMismatch) {
public double run(List<ParticipatingElement> participatingElements, int iteration, double remainingMismatch) {
// normalize participation factors at each iteration start as some
// generators might have reach a limit and have been discarded
ParticipatingElement.normalizeParticipationFactors(participatingElements);

double done = 0d;
Copy link
Member

Choose a reason for hiding this comment

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

Same, another name than "done" might be better (even if the name "done" was not introduced in this PR).

double mismatch = remainingMismatch;
if (iteration == 0) {
// "undo" everything from targetP to go back to initialP
for (ParticipatingElement participatingGenerator : participatingElements) {
LfGenerator generator = (LfGenerator) participatingGenerator.getElement();
done += generator.getInitialTargetP() - generator.getTargetP();
mismatch -= generator.getInitialTargetP() - generator.getTargetP();
generator.setTargetP(generator.getInitialTargetP());
}
}
Comment on lines +69 to +78
Copy link
Member Author

@jeandemanged jeandemanged Jun 25, 2024

Choose a reason for hiding this comment

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

ℹ️ Note to reviewers

This is the way I found to fix the issue AND also avoiding a massive refactor: today ActivePowerDistribution is completely stateless. Making ActivePowerDistribution aware of a "starting point" would require more work / refactorings...

Note also that such refactoring's will need to be addressed for future developments planned in #979 and powsybl-entsoe/# - planned end of this year / Q4-2024 on our side.
Indeed, imagine a merit order rebalancing, the starting point is crucial, otherwise you would end up with both generators from upward list moved and generators from downward list moved ...


int modifiedBuses = 0;
int generatorsAtMax = 0;
int generatorsAtMin = 0;
Expand All @@ -85,12 +97,12 @@ public ActivePowerDistribution.StepResult run(List<ParticipatingElement> partici
minP = Math.max(minP, 0);
}

double newTargetP = targetP + remainingMismatch * factor;
if (remainingMismatch > 0 && newTargetP > maxP) {
double newTargetP = targetP + mismatch * factor;
if (mismatch > 0 && newTargetP > maxP) {
newTargetP = maxP;
generatorsAtMax++;
it.remove();
} else if (remainingMismatch < 0 && newTargetP < minP) {
} else if (mismatch < 0 && newTargetP < minP) {
newTargetP = minP;
generatorsAtMin++;
it.remove();
Expand All @@ -106,10 +118,10 @@ public ActivePowerDistribution.StepResult run(List<ParticipatingElement> partici
}

LOGGER.debug("{} MW / {} MW distributed at iteration {} to {} generators ({} at max power, {} at min power)",
done * PerUnit.SB, remainingMismatch * PerUnit.SB, iteration, modifiedBuses,
done * PerUnit.SB, mismatch * PerUnit.SB, iteration, modifiedBuses,
generatorsAtMax, generatorsAtMin);

return new ActivePowerDistribution.StepResult(done, modifiedBuses != 0);
return done;
}

private double getParticipationFactor(LfGenerator generator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -42,15 +43,15 @@ public List<ParticipatingElement> getParticipatingElements(Collection<LfBus> bus
.filter(bus -> bus.isParticipating() && !bus.isDisabled() && !bus.isFictitious())
.flatMap(bus -> bus.getLoads().stream())
.map(load -> new ParticipatingElement(load, getParticipationFactor(load)))
.collect(Collectors.toList());
.collect(Collectors.toCollection(LinkedList::new));
}

private double getParticipationFactor(LfLoad load) {
return load.getAbsVariableTargetP();
}

@Override
public ActivePowerDistribution.StepResult run(List<ParticipatingElement> participatingElements, int iteration, double remainingMismatch) {
public double run(List<ParticipatingElement> participatingElements, int iteration, double remainingMismatch) {
// normalize participation factors at each iteration start as some
// loads might have reach zero and have been discarded.
ParticipatingElement.normalizeParticipationFactors(participatingElements);
Expand Down Expand Up @@ -84,7 +85,7 @@ public ActivePowerDistribution.StepResult run(List<ParticipatingElement> partici
LOGGER.debug("{} MW / {} MW distributed at iteration {} to {} buses ({} at min consumption)",
-done * PerUnit.SB, -remainingMismatch * PerUnit.SB, iteration, modifiedBuses, loadsAtMin);

return new ActivePowerDistribution.StepResult(done, modifiedBuses != 0);
return done;
}

private static void ensurePowerFactorConstant(LfLoad load, double newLoadTargetP) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ public double getFactor() {
return factor;
}

public double getTargetP() {
if (element instanceof LfGenerator generator) {
return generator.getTargetP();
} else if (element instanceof LfLoad load) {
return load.getTargetP();
} else {
return Double.NaN;
}
}

public static double participationFactorNorm(List<ParticipatingElement> participatingElements) {
return participatingElements.stream()
.mapToDouble(participatingGenerator -> participatingGenerator.factor)
Expand Down
Loading