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

Multiple scaling modifications #2666

Merged
merged 24 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
162fac8
Implementation of ProportionalScalable on loads and on generators
rolnico Aug 4, 2023
2488883
Implementation of ProportionalScalable on loads and on generators
rolnico Aug 7, 2023
d37ac44
Merge VariationParameters into existing ScalingParameters + make scal…
rolnico Aug 7, 2023
aae0a7d
Added Exceptions management and Tests
rolnico Aug 7, 2023
f45a773
Moved scaleOnLoads and scaleOnGenerators in Scalable, added STACKING_…
rolnico Aug 8, 2023
d957c35
Modified and moved the reporter, added a default value for a paramete…
rolnico Aug 9, 2023
4a40f89
Added tests for coverage
rolnico Aug 9, 2023
f51a09c
Added tests for coverage
rolnico Aug 9, 2023
b298cad
Merge branch 'main' into multiple_scaling_modifications
annetill Aug 23, 2023
1134ed2
create proportional and then scale rather than having a method doing …
phiedw Sep 4, 2023
b3b2489
removed scalingValue from ScalingParameters
phiedw Sep 4, 2023
794eb69
Reorganized code for Scalables
rolnico Sep 8, 2023
ce4410a
Checkstyle corrections
rolnico Sep 8, 2023
e91acea
Correction of testProportional (float to double)
rolnico Sep 11, 2023
bd21317
deletion of checkPositiveAskedWhenTarget and modification of getCurre…
rolnico Sep 13, 2023
190ed0a
Merge branch 'main' into multiple_scaling_modifications
rolnico Sep 13, 2023
8f14333
Merge branch 'main' into multiple_scaling_modifications
rolnico Sep 18, 2023
2395cc1
Renamed getCurrentPower to getOngoingPower
rolnico Sep 18, 2023
c2c65c0
Merge branch 'main' into multiple_scaling_modifications
rolnico Sep 18, 2023
0f4dd21
Renamed getOngoingPower to getSteadyStatePower
rolnico Sep 18, 2023
922a004
Javadoc corrected
rolnico Sep 18, 2023
84d7497
Merge branch 'main' into multiple_scaling_modifications
annetill Sep 18, 2023
4a995ec
Merge branch 'main' into multiple_scaling_modifications
annetill Sep 19, 2023
375f113
Header correction
rolnico Sep 19, 2023
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: 1 addition & 1 deletion action/action-dsl/src/test/resources/scalable.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ action('testProportional') {
script {
variationValue = 15000
gens = scalables('GEN', 'GEN2', 'GEN3')
variation = proportional([50.0f,20.0f,30.0f], gens)
variation = proportional([50.0d,20.0d,30.0d], gens)
variation.reset(network)
variation.scale(network, variationValue)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,32 @@ public double scale(Network n, double asked, ScalingParameters parameters) {

return done;
}

/**
* Compute the percentage of asked power available for the scale. It takes into account the scaling convention
* specified by the user and the sign of the asked power.
*
* @param network Network on which the scaling is done
* @param asked Asked power (can be positive or negative)
* @param scalingPercentage Percentage of the asked power that shall be distributed to the current injection
* @param scalingConvention Scaling convention (GENERATOR or LOAD)
* @return the percentage of asked power available for the scale on the current injection
*/
double availablePowerInPercentageOfAsked(Network network, double asked, double scalingPercentage, ScalingConvention scalingConvention) {
var generator = network.getGenerator(id);

// In LOAD convention, a positive scale will imply a decrease of generators target power
var askedPower = asked * scalingPercentage / 100;
if (scalingConvention == LOAD) {
askedPower = -askedPower;
}

if (askedPower >= 0) {
var availablePower = Math.min(generator.getMaxP(), maxValue) - generator.getTargetP();
return askedPower > availablePower ? availablePower / askedPower : 100.0;
} else {
var availablePower = Math.max(generator.getMinP(), minValue) - generator.getTargetP();
return askedPower < availablePower ? availablePower / askedPower : 100.0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,42 @@
*/
package com.powsybl.iidm.modification.scalable;

import com.powsybl.iidm.network.Network;
import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static com.powsybl.iidm.modification.scalable.ScalingParameters.Priority.RESPECT_OF_DISTRIBUTION;
import static com.powsybl.iidm.modification.scalable.ScalingParameters.Priority.RESPECT_OF_VOLUME_ASKED;

/**
* Scalable that divides scale proportionally between multiple scalable.
*
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
* @author Sebastien Murgey {@literal <sebastien.murgey at rte-france.com>}
*/
class ProportionalScalable extends AbstractCompoundScalable {
public class ProportionalScalable extends AbstractCompoundScalable {
private static final double EPSILON = 1e-2;
private static final String GENERIC_SCALABLE_CLASS_ERROR = "Unable to create a scalable from %s";
private static final String GENERIC_INCONSISTENCY_ERROR = "Variable %s inconsistent with injection type %s";

public enum DistributionMode {
PROPORTIONAL_TO_TARGETP,
PROPORTIONAL_TO_PMAX,
PROPORTIONAL_TO_DIFF_PMAX_TARGETP,
PROPORTIONAL_TO_DIFF_TARGETP_PMIN,
PROPORTIONAL_TO_P0,
UNIFORM_DISTRIBUTION
}

private static final class ScalablePercentage {
static final class ScalablePercentage {
private final Scalable scalable;
private final float percentage;
private final double percentage;
private double iterationPercentage;

private ScalablePercentage(Scalable scalable, float percentage) {
ScalablePercentage(Scalable scalable, double percentage) {
this.scalable = scalable;
this.percentage = percentage;
this.iterationPercentage = percentage;
Expand All @@ -38,7 +51,7 @@ Scalable getScalable() {
return scalable;
}

float getPercentage() {
double getPercentage() {
return percentage;
}

Expand All @@ -60,20 +73,126 @@ void setIterationPercentage(double iterationPercentage) {
}

private final List<ScalablePercentage> scalablePercentageList;
private double currentGlobalPower = 0;

ProportionalScalable(List<Float> percentages, List<Scalable> scalables) {
ProportionalScalable(List<Double> percentages, List<Scalable> scalables) {
checkPercentages(percentages, scalables);
this.scalablePercentageList = new ArrayList<>();
for (int i = 0; i < scalables.size(); i++) {
this.scalablePercentageList.add(new ScalablePercentage(scalables.get(i), percentages.get(i)));
}
}

public ProportionalScalable(List<? extends Injection> injections, DistributionMode distributionMode) {
// Create the scalable for each injection
List<Scalable> injectionScalables = injections.stream().map(ScalableAdapter::new).collect(Collectors.toList());

// Compute the sum of every individual power
double totalDistribution = computeTotalDistribution(injections, distributionMode);

// Compute the current power value
currentGlobalPower = injections.stream().mapToDouble(Scalable::getCurrentPower).sum();

// In some cases, a regular distribution is equivalent to the nominal distribution :
// - PROPORTIONAL_TO_P0 : if no power is currently configured
// - PROPORTIONAL_TO_TARGETP : if no power is currently configured
// - PROPORTIONAL_TO_DIFF_PMAX_TARGETP : if no power is currently available
// - PROPORTIONAL_TO_DIFF_TARGETP_PMIN : if no power is currently used
DistributionMode finalDistributionMode;
double finalTotalDistribution;
if (totalDistribution == 0.0 &&
(distributionMode == DistributionMode.PROPORTIONAL_TO_P0
|| distributionMode == DistributionMode.PROPORTIONAL_TO_TARGETP
|| distributionMode == DistributionMode.PROPORTIONAL_TO_DIFF_PMAX_TARGETP
|| distributionMode == DistributionMode.PROPORTIONAL_TO_DIFF_TARGETP_PMIN)) {
finalDistributionMode = DistributionMode.UNIFORM_DISTRIBUTION;
finalTotalDistribution = computeTotalDistribution(injections, finalDistributionMode);
} else {
finalDistributionMode = distributionMode;
finalTotalDistribution = totalDistribution;
}

// Compute the percentages for each injection
List<Double> percentages = injections.stream().map(injection -> getIndividualDistribution(injection, finalDistributionMode) * 100.0 / finalTotalDistribution).toList();
checkPercentages(percentages, injectionScalables);

// Create the list of ScalablePercentage
this.scalablePercentageList = new ArrayList<>();
for (int i = 0; i < injectionScalables.size(); i++) {
this.scalablePercentageList.add(new ScalablePercentage(injectionScalables.get(i), percentages.get(i)));
}
}

private double computeTotalDistribution(List<? extends Injection> injections, DistributionMode distributionMode) {
return injections.stream().mapToDouble(injection -> getIndividualDistribution(injection, distributionMode)).sum();
}

private double getIndividualDistribution(Injection<?> injection, DistributionMode distributionMode) {
// Check the injection type
checkInjectionClass(injection);

// Get the injection value according to the distribution mode
return switch (distributionMode) {
case PROPORTIONAL_TO_TARGETP -> getTargetP(injection);
case PROPORTIONAL_TO_P0 -> getP0(injection);
case PROPORTIONAL_TO_PMAX -> getMaxP(injection);
case PROPORTIONAL_TO_DIFF_PMAX_TARGETP -> getMaxP(injection) - getTargetP(injection);
case PROPORTIONAL_TO_DIFF_TARGETP_PMIN -> getTargetP(injection) - getMinP(injection);
case UNIFORM_DISTRIBUTION -> 1;
};
}

private void checkInjectionClass(Injection<?> injection) {
if (!(injection instanceof Generator
|| injection instanceof Load
|| injection instanceof DanglingLine)) {
throw new PowsyblException(String.format(GENERIC_SCALABLE_CLASS_ERROR, injection.getClass()));
}
}

private double getTargetP(Injection<?> injection) {
if (injection instanceof Generator generator) {
return generator.getTargetP();
} else {
throw new PowsyblException(String.format(GENERIC_INCONSISTENCY_ERROR,
"TargetP", injection.getClass()));
}
}

private double getP0(Injection<?> injection) {
if (injection instanceof Load load) {
return load.getP0();
} else if (injection instanceof DanglingLine danglingLine) {
return danglingLine.getP0();
} else {
throw new PowsyblException(String.format(GENERIC_INCONSISTENCY_ERROR,
"P0", injection.getClass()));
}
}

private double getMaxP(Injection<?> injection) {
if (injection instanceof Generator generator) {
return generator.getMaxP();
} else {
throw new PowsyblException(String.format(GENERIC_INCONSISTENCY_ERROR,
"MaxP", injection.getClass()));
}
}

private double getMinP(Injection<?> injection) {
if (injection instanceof Generator generator) {
return generator.getMinP();
} else {
throw new PowsyblException(String.format(GENERIC_INCONSISTENCY_ERROR,
"MinP", injection.getClass()));
}
}

Collection<Scalable> getScalables() {
return scalablePercentageList.stream().map(ScalablePercentage::getScalable).collect(Collectors.toList());
return scalablePercentageList.stream().map(ScalablePercentage::getScalable).toList();
}

private static void checkPercentages(List<Float> percentages, List<Scalable> scalables) {
private static void checkPercentages(List<Double> percentages, List<Scalable> scalables) {
Objects.requireNonNull(percentages);
Objects.requireNonNull(scalables);

Expand All @@ -83,7 +202,7 @@ private static void checkPercentages(List<Float> percentages, List<Scalable> sca
if (scalables.isEmpty()) {
return;
}
if (percentages.stream().anyMatch(p -> Float.isNaN(p))) {
if (percentages.stream().anyMatch(p -> Double.isNaN(p))) {
throw new IllegalArgumentException("There is at least one undefined percentage");
}
double sum = percentages.stream().mapToDouble(Double::valueOf).sum();
Expand Down Expand Up @@ -128,8 +247,7 @@ private double scaleIteration(Network n, double asked, ScalingParameters paramet
Scalable s = scalablePercentage.getScalable();
double iterationPercentage = scalablePercentage.getIterationPercentage();
double askedOnScalable = iterationPercentage / 100 * asked;
double doneOnScalable = 0;
doneOnScalable = s.scale(n, askedOnScalable, parameters);
double doneOnScalable = s.scale(n, askedOnScalable, parameters);
if (Math.abs(doneOnScalable - askedOnScalable) > EPSILON) {
scalablePercentage.setIterationPercentage(0);
}
Expand All @@ -142,17 +260,63 @@ private double scaleIteration(Network n, double asked, ScalingParameters paramet
public double scale(Network n, double asked, ScalingParameters parameters) {
Objects.requireNonNull(n);
Objects.requireNonNull(parameters);

// Check the coherence of the asked value and the scaling type
checkPositiveAskedWhenTarget(asked, parameters);

// Variation asked
double variationAsked = Scalable.getVariationAsked(parameters, asked, currentGlobalPower);
phiedw marked this conversation as resolved.
Show resolved Hide resolved

// Adapt the asked value if needed - only used in VENTILATION mode
phiedw marked this conversation as resolved.
Show resolved Hide resolved
if (parameters.getPriority() == RESPECT_OF_DISTRIBUTION) {
variationAsked = resizeAskedForVentilation(n, variationAsked, parameters);
phiedw marked this conversation as resolved.
Show resolved Hide resolved
}

reinitIterationPercentage();
if (parameters.isIterative()) {
return iterativeScale(n, asked, parameters);
if (parameters.getPriority() == RESPECT_OF_VOLUME_ASKED) {
return iterativeScale(n, variationAsked, parameters);
} else {
return scaleIteration(n, asked, parameters);
return scaleIteration(n, variationAsked, parameters);
}
}

private void checkPositiveAskedWhenTarget(double asked, ScalingParameters scalingParameters) {
if (asked < 0 && scalingParameters.getScalingType() == ScalingParameters.ScalingType.TARGET_P) {
throw new PowsyblException("The asked power value can only be positive when scaling type is TARGET_P");
phiedw marked this conversation as resolved.
Show resolved Hide resolved
}
}

private void reinitIterationPercentage() {
scalablePercentageList.forEach(scalablePercentage -> scalablePercentage.setIterationPercentage(scalablePercentage.getPercentage()));
}

/**
* Compute the power that can be scaled on the network while keeping the ventilation percentages valid.
phiedw marked this conversation as resolved.
Show resolved Hide resolved
* This method is only used if the distribution is not STACKING_UP (cannot happen since it's a
* {@link ProportionalScalable} and if the scaling priority is VENTILATION.
* @param asked power that shall be scaled on the network
* @param network network on which the scaling shall be done
* @return the effective power value that can be safely scaled while keeping the ventilation percentages valid
*/
double resizeAskedForVentilation(Network network, double asked, ScalingParameters scalingParameters) {
AtomicReference<Double> resizingPercentage = new AtomicReference<>(1.0);
scalablePercentageList.forEach(scalablePercentage -> {
if (scalablePercentage.getScalable() instanceof GeneratorScalable generatorScalable) {
resizingPercentage.set(Math.min(
generatorScalable.availablePowerInPercentageOfAsked(network, asked, scalablePercentage.getPercentage(), scalingParameters.getScalingConvention()),
resizingPercentage.get()));
} else if (scalablePercentage.getScalable() instanceof ScalableAdapter scalableAdapter) {
resizingPercentage.set(Math.min(
scalableAdapter.availablePowerInPercentageOfAsked(network, asked, scalablePercentage.getPercentage(), scalingParameters.getScalingConvention()),
resizingPercentage.get()));
} else {
throw new PowsyblException(String.format("VENTILATION mode can only be used with ScalableAdapter, not %s",
phiedw marked this conversation as resolved.
Show resolved Hide resolved
scalablePercentage.getScalable().getClass()));
}
}
phiedw marked this conversation as resolved.
Show resolved Hide resolved
);
return asked * resizingPercentage.get();
}

}

Loading