BalanceTypeGuesser.java
/**
* Copyright (c) 2018, RTE (http://www.rte-france.com)
* 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.loadflow.validation;
import static java.util.Comparator.comparingDouble;
import java.util.AbstractMap.SimpleEntry;
import java.util.Objects;
import java.util.stream.Stream;
import com.powsybl.iidm.network.Generator;
import com.powsybl.iidm.network.Network;
/**
*
* On creation, tries to guess the balancing method used by the computation, from the differences between
* the computed power outputs and initial target power.
* This is necessary since the balancing method actually used is not known.
*
* @author Massimo Ferraro {@literal <massimo.ferraro@techrain.eu>}
*/
public class BalanceTypeGuesser {
private String slackCandidate;
private double maxActivePowerDifference = 0;
/**
* Compute balancing ratio assuming balancing proportional to P max.
*/
private final KComputation kMaxComputation = new KComputation();
/**
* Compute balancing ratio assuming balancing proportional to target P.
*/
private final KComputation kTargetComputation = new KComputation();
/**
* Compute balancing ratio assuming balancing proportional to headroom.
*/
private final KComputation kHeadroomComputation = new KComputation();
private BalanceType balanceType;
private String slack;
public BalanceTypeGuesser() {
this.balanceType = BalanceType.NONE;
}
/**
* Tries to guess the balancing method for the provided network.
*
* @param threshold: Under this threshold, a power deviation will not be seen as significant (not taken into account in the ratio computation).
*/
public BalanceTypeGuesser(Network network, double threshold) {
Objects.requireNonNull(network);
guess(network, threshold);
}
private void guess(Network network, double threshold) {
// Number of generators that significantly moved
int movedGeneratorsCount = network.getGeneratorStream().mapToInt(generator -> isMovedGenerator(generator, threshold)).sum();
if (movedGeneratorsCount > 0) {
this.balanceType = getBalanceType(kMaxComputation.getVarK(), kTargetComputation.getVarK(), kHeadroomComputation.getVarK());
} else {
this.balanceType = BalanceType.NONE;
this.slack = slackCandidate;
}
}
private BalanceType getBalanceType(double varKMax, double varKTarget, double varKHeadroom) {
return Stream.of(new SimpleEntry<>(BalanceType.PROPORTIONAL_TO_GENERATION_P_MAX, varKMax),
new SimpleEntry<>(BalanceType.PROPORTIONAL_TO_GENERATION_P, varKTarget),
new SimpleEntry<>(BalanceType.PROPORTIONAL_TO_GENERATION_HEADROOM, varKHeadroom))
.min(comparingDouble(SimpleEntry::getValue))
.map(SimpleEntry::getKey)
.orElse(BalanceType.NONE);
}
private int isMovedGenerator(Generator generator, double threshold) {
double p = -generator.getTerminal().getP();
double targetP = generator.getTargetP();
double maxP = generator.getMaxP();
double minP = generator.getMinP();
if (Math.abs(p - targetP) > maxActivePowerDifference) {
slackCandidate = generator.getId();
maxActivePowerDifference = Math.abs(p - targetP);
}
if (ValidationUtils.boundedWithin(Math.max(threshold, minP), maxP - threshold, targetP, 0)
&& ValidationUtils.boundedWithin(Math.max(0, minP), maxP, p, -threshold)
&& maxP >= threshold
&& Math.abs(p - targetP) >= threshold) {
kMaxComputation.addGeneratorValues(p, targetP, maxP);
kTargetComputation.addGeneratorValues(p, targetP, targetP);
kHeadroomComputation.addGeneratorValues(p, targetP, maxP - targetP);
return 1;
}
return 0;
}
/**
* The balance type identified as most likely (leading to the smallest variance around theoretical values).
*/
public BalanceType getBalanceType() {
return balanceType;
}
/**
* The identified slack generator, if any.
*/
public String getSlack() {
return slack;
}
/**
* The balancing ratio value, assuming balancing proportional to P max.
*/
public double getKMax() {
return kMaxComputation.getK();
}
/**
* The balancing ratio value, assuming balancing proportional to target P.
*/
public double getKTarget() {
return kTargetComputation.getK();
}
/**
* The balancing ratio value, assuming balancing proportional to P headroom.
*/
public double getKHeadroom() {
return kHeadroomComputation.getK();
}
}