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

[WIP] Automatic fix of incompatible target voltages #1115

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 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
39 changes: 31 additions & 8 deletions src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ public enum FictitiousGeneratorVoltageControlCheckMode {

public static final String AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME = "areaInterchangePMaxMismatch";

public static final String FIX_TARGET_VOLTAGE_INCOMPATIBILITY_PARAM_NAME = "fixTargetVoltageIncompatibility";

Copy link
Collaborator

Choose a reason for hiding this comment

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

To be documented.
And maybe addiotional parameters given like TARGET_VOLTAGE_PLAUSIBILITY_THRESHOLD

However in my test networks, increasing CONTROLLED_BUS_NEIGHBORS_EXPLORATION_DEPTH had a terrible algotirhmic cost and didn't improve quality (defined as more convergence)

Copy link
Member Author

Choose a reason for hiding this comment

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

"And maybe addiotional parameters given like TARGET_VOLTAGE_PLAUSIBILITY_THRESHOLD" => yes
for CONTROLLED_BUS_NEIGHBORS_EXPLORATION_DEPTH indeed it should not be configurable, a value of 2 or 3 is probably enough.

public static <E extends Enum<E>> List<Object> getEnumPossibleValues(Class<E> enumClass) {
return EnumSet.allOf(enumClass).stream().map(Enum::name).collect(Collectors.toList());
}
Expand Down Expand Up @@ -413,7 +415,8 @@ public static <E extends Enum<E>> List<Object> getEnumPossibleValues(Class<E> en
new Parameter(FICTITIOUS_GENERATOR_VOLTAGE_CONTROL_CHECK_MODE, ParameterType.STRING, "Specifies fictitious generators active power checks exemption for voltage control", OpenLoadFlowParameters.FICTITIOUS_GENERATOR_VOLTAGE_CONTROL_CHECK_MODE_DEFAULT_VALUE.name(), getEnumPossibleValues(FictitiousGeneratorVoltageControlCheckMode.class), ParameterScope.FUNCTIONAL, GENERATOR_VOLTAGE_CONTROL_CATEGORY_KEY),
new Parameter(AREA_INTERCHANGE_CONTROL_PARAM_NAME, ParameterType.BOOLEAN, "Area interchange control", AREA_INTERCHANGE_CONTROL_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, SLACK_DISTRIBUTION_CATEGORY_KEY),
new Parameter(AREA_INTERCHANGE_CONTROL_AREA_TYPE_PARAM_NAME, ParameterType.STRING, "Area type for area interchange control", LfNetworkParameters.AREA_INTERCHANGE_CONTROL_AREA_TYPE_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, SLACK_DISTRIBUTION_CATEGORY_KEY),
new Parameter(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, ParameterType.DOUBLE, "Area interchange max active power mismatch", AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, SLACK_DISTRIBUTION_CATEGORY_KEY)
new Parameter(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, ParameterType.DOUBLE, "Area interchange max active power mismatch", AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, SLACK_DISTRIBUTION_CATEGORY_KEY),
new Parameter(FIX_TARGET_VOLTAGE_INCOMPATIBILITY_PARAM_NAME, ParameterType.BOOLEAN, "Automatically fix incompatible target voltages", LfNetworkParameters.FIX_TARGET_VOLTAGE_INCOMPATIBILITY_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, VOLTAGE_CONTROLS_CATEGORY_KEY)
);

public enum VoltageInitModeOverride {
Expand Down Expand Up @@ -597,6 +600,8 @@ public enum ReactiveRangeCheckMode {

private double areaInterchangePMaxMismatch = AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE;

private boolean fixTargetVoltageIncompatibility = LfNetworkParameters.FIX_TARGET_VOLTAGE_INCOMPATIBILITY_DEFAULT_VALUE;

public static double checkParameterValue(double parameterValue, boolean condition, String parameterName) {
if (!condition) {
throw new IllegalArgumentException("Invalid value for parameter " + parameterName + ": " + parameterValue);
Expand Down Expand Up @@ -1322,6 +1327,15 @@ public OpenLoadFlowParameters setAreaInterchangePMaxMismatch(double areaIntercha
return this;
}

public boolean isFixTargetVoltageIncompatibility() {
return fixTargetVoltageIncompatibility;
}

public OpenLoadFlowParameters setFixTargetVoltageIncompatibility(boolean fixTargetVoltageIncompatibility) {
this.fixTargetVoltageIncompatibility = fixTargetVoltageIncompatibility;
return this;
}

public static OpenLoadFlowParameters load() {
return load(PlatformConfig.defaultConfig());
}
Expand Down Expand Up @@ -1400,7 +1414,8 @@ public static OpenLoadFlowParameters load(PlatformConfig platformConfig) {
.setGeneratorVoltageControlMinNominalVoltage(config.getDoubleProperty(GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_PARAM_NAME, GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_DEFAULT_VALUE))
.setAreaInterchangeControl(config.getBooleanProperty(AREA_INTERCHANGE_CONTROL_PARAM_NAME, AREA_INTERCHANGE_CONTROL_DEFAULT_VALUE))
.setAreaInterchangeControlAreaType(config.getStringProperty(AREA_INTERCHANGE_CONTROL_AREA_TYPE_PARAM_NAME, LfNetworkParameters.AREA_INTERCHANGE_CONTROL_AREA_TYPE_DEFAULT_VALUE))
.setAreaInterchangePMaxMismatch(config.getDoubleProperty(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE)));
.setAreaInterchangePMaxMismatch(config.getDoubleProperty(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE))
.setFixTargetVoltageIncompatibility(config.getBooleanProperty(FIX_TARGET_VOLTAGE_INCOMPATIBILITY_PARAM_NAME, LfNetworkParameters.FIX_TARGET_VOLTAGE_INCOMPATIBILITY_DEFAULT_VALUE)));
return parameters;
}

Expand Down Expand Up @@ -1561,11 +1576,13 @@ public OpenLoadFlowParameters update(Map<String, String> properties) {
.ifPresent(this::setAreaInterchangeControlAreaType);
Optional.ofNullable(properties.get(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME))
.ifPresent(prop -> this.setAreaInterchangePMaxMismatch(Double.parseDouble(prop)));
Optional.ofNullable(properties.get(FIX_TARGET_VOLTAGE_INCOMPATIBILITY_PARAM_NAME))
.ifPresent(prop -> this.setFixTargetVoltageIncompatibility(Boolean.parseBoolean(prop)));
return this;
}

public Map<String, Object> toMap() {
Map<String, Object> map = new LinkedHashMap<>(71);
Map<String, Object> map = new LinkedHashMap<>(72);
map.put(SLACK_BUS_SELECTION_MODE_PARAM_NAME, slackBusSelectionMode);
map.put(SLACK_BUSES_IDS_PARAM_NAME, slackBusesIds);
map.put(SLACK_DISTRIBUTION_FAILURE_BEHAVIOR_PARAM_NAME, slackDistributionFailureBehavior);
Expand Down Expand Up @@ -1637,6 +1654,7 @@ public Map<String, Object> toMap() {
map.put(AREA_INTERCHANGE_CONTROL_PARAM_NAME, areaInterchangeControl);
map.put(AREA_INTERCHANGE_CONTROL_AREA_TYPE_PARAM_NAME, areaInterchangeControlAreaType);
map.put(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, areaInterchangePMaxMismatch);
map.put(FIX_TARGET_VOLTAGE_INCOMPATIBILITY_PARAM_NAME, fixTargetVoltageIncompatibility);
return map;
}

Expand Down Expand Up @@ -1751,7 +1769,8 @@ static VoltageInitializer getExtendedVoltageInitializer(LoadFlowParameters param
}

static LfNetworkParameters getNetworkParameters(LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt,
SlackBusSelector slackBusSelector, GraphConnectivityFactory<LfBus, LfBranch> connectivityFactory,
SlackBusSelector slackBusSelector, MatrixFactory matrixFactory,
GraphConnectivityFactory<LfBus, LfBranch> connectivityFactory,
boolean breakers) {
return new LfNetworkParameters()
.setSlackBusSelector(slackBusSelector)
Expand Down Expand Up @@ -1793,7 +1812,9 @@ static LfNetworkParameters getNetworkParameters(LoadFlowParameters parameters, O
.setVoltageTargetPriorities(parametersExt.getVoltageTargetPriorities())
.setFictitiousGeneratorVoltageControlCheckMode(parametersExt.getFictitiousGeneratorVoltageControlCheckMode())
.setAreaInterchangeControl(parametersExt.isAreaInterchangeControl())
.setAreaInterchangeControlAreaType(parametersExt.getAreaInterchangeControlAreaType());
.setAreaInterchangeControlAreaType(parametersExt.getAreaInterchangeControlAreaType())
.setFixTargetVoltageIncompatibility(parametersExt.isFixTargetVoltageIncompatibility())
.setMatrixFactory(matrixFactory);
}

public static AcLoadFlowParameters createAcParameters(Network network, LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt,
Expand Down Expand Up @@ -1837,7 +1858,7 @@ public static AcLoadFlowParameters createAcParameters(LoadFlowParameters paramet
SlackBusSelector slackBusSelector = SlackBusSelector.fromMode(parametersExt.getSlackBusSelectionMode(), parametersExt.getSlackBusesIds(),
parametersExt.getPlausibleActivePowerLimit(), parametersExt.getMostMeshedSlackBusSelectorMaxNominalVoltagePercentile(), parametersExt.getSlackBusCountryFilter());

var networkParameters = getNetworkParameters(parameters, parametersExt, slackBusSelector, connectivityFactory, breakers);
var networkParameters = getNetworkParameters(parameters, parametersExt, slackBusSelector, matrixFactory, connectivityFactory, breakers);

var equationSystemCreationParameters = new AcEquationSystemCreationParameters(forceA1Var);

Expand Down Expand Up @@ -2046,7 +2067,8 @@ public static boolean equals(LoadFlowParameters parameters1, LoadFlowParameters
extension1.getFictitiousGeneratorVoltageControlCheckMode() == extension2.getFictitiousGeneratorVoltageControlCheckMode() &&
extension1.isAreaInterchangeControl() == extension2.isAreaInterchangeControl() &&
Objects.equals(extension1.getAreaInterchangeControlAreaType(), extension2.getAreaInterchangeControlAreaType()) &&
extension1.getAreaInterchangePMaxMismatch() == extension2.getAreaInterchangePMaxMismatch();
extension1.getAreaInterchangePMaxMismatch() == extension2.getAreaInterchangePMaxMismatch() &&
extension1.isFixTargetVoltageIncompatibility() == extension2.isFixTargetVoltageIncompatibility();
}

public static LoadFlowParameters clone(LoadFlowParameters parameters) {
Expand Down Expand Up @@ -2142,7 +2164,8 @@ public static LoadFlowParameters clone(LoadFlowParameters parameters) {
.setFictitiousGeneratorVoltageControlCheckMode(extension.getFictitiousGeneratorVoltageControlCheckMode())
.setAreaInterchangeControl(extension.isAreaInterchangeControl())
.setAreaInterchangeControlAreaType(extension.getAreaInterchangeControlAreaType())
.setAreaInterchangePMaxMismatch(extension.getAreaInterchangePMaxMismatch());
.setAreaInterchangePMaxMismatch(extension.getAreaInterchangePMaxMismatch())
.setFixTargetVoltageIncompatibility(extension.isFixTargetVoltageIncompatibility());

if (extension2 != null) {
parameters2.addExtension(OpenLoadFlowParameters.class, extension2);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* Copyright (c) 2024, 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.openloadflow;

import com.google.common.base.Stopwatch;
import com.powsybl.openloadflow.adm.AdmittanceEquationSystem;
import com.powsybl.openloadflow.adm.AdmittanceMatrix;
import com.powsybl.openloadflow.equations.VariableSet;
import com.powsybl.openloadflow.network.LfBranch;
import com.powsybl.openloadflow.network.LfBus;
import com.powsybl.openloadflow.network.LfNetwork;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.tuple.Pair;
import org.jgrapht.Graph;
import org.jgrapht.graph.Pseudograph;
import org.jgrapht.traverse.BreadthFirstIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
public class TargetVoltageCompatibilityChecker {

private static final Logger LOGGER = LoggerFactory.getLogger(TargetVoltageCompatibilityChecker.class);

private final LfNetwork network;

vidaldid-rte marked this conversation as resolved.
Show resolved Hide resolved
public TargetVoltageCompatibilityChecker(LfNetwork network) {
this.network = Objects.requireNonNull(network);
}

private static Graph<LfBus, LfBranch> createGraph(LfNetwork lfNetwork) {
Graph<LfBus, LfBranch> graph = new Pseudograph<>(LfBranch.class);
for (LfBranch branch : lfNetwork.getBranches()) {
LfBus bus1 = branch.getBus1();
LfBus bus2 = branch.getBus2();
if (bus1 != null && bus2 != null && !branch.isDisabled()) {
if (!graph.containsVertex(bus1)) {
graph.addVertex(bus1);
}
if (!graph.containsVertex(bus2)) {
graph.addVertex(bus2);
}
graph.addEdge(bus1, bus2, branch);
}
}
return graph;
}

private static Set<LfBus> exploreNeighbors(Graph<LfBus, LfBranch> graph, LfBus bus, int maxDepth) {
var it = new BreadthFirstIterator<>(graph, bus);
Set<LfBus> neighbors = new HashSet<>();
while (it.hasNext()) {
LfBus b = it.next();
int currentDepth = it.getDepth(b);
if (currentDepth > maxDepth) {
break;
}
if (b != bus) {
neighbors.add(b);
}
}
return neighbors;
}

public List<Pair<LfBus, LfBus>> check(TargetVoltageCompatibilityCheckerParameters parameters) {
Stopwatch stopwatch = Stopwatch.createStarted();

Graph<LfBus, LfBranch> graph = createGraph(network);

Set<LfBus> controlledBuses = network.getBuses().stream()
.filter(bus -> !bus.isDisabled()
&& bus.isVoltageControlled()
&& bus.getVoltageControls().stream().anyMatch(vc -> !vc.isDisabled()))
.collect(Collectors.toSet());

List<Pair<LfBus, LfBus>> incompatibleControlledBuses = new ArrayList<>();

var ySystem = AdmittanceEquationSystem.create(network, new VariableSet<>());
try (var y = AdmittanceMatrix.create(ySystem, parameters.getMatrixFactory())) {
for (LfBus controlledBus : controlledBuses) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't you compute the impedance between each pair of bus twice ? if you iterate on all buses then on all neighbours ?

Also, would it accerlerate things if there was an option to check a pair only if at least one of the two buses is remotely controlled ? [This is where we have convergence issues]

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, we are checking potentially twice. It could be improved indeed.

Set<LfBus> neighborControlledBuses = exploreNeighbors(graph, controlledBus, parameters.getControlledBusNeighborsExplorationDepth())
.stream().filter(controlledBuses::contains)
.collect(Collectors.toSet());
if (!neighborControlledBuses.isEmpty()) {
for (LfBus neighborControlledBus : neighborControlledBuses) {
double z = y.getZ(controlledBus, neighborControlledBus).abs();
double dv = Math.abs(controlledBus.getHighestPriorityTargetV().orElseThrow() - neighborControlledBus.getHighestPriorityTargetV().orElseThrow());
double targetVoltagePlausibility = dv / z;
if (targetVoltagePlausibility > parameters.getTargetVoltagePlausibilityThreshold()) {
incompatibleControlledBuses.add(Pair.of(controlledBus, neighborControlledBus));
}
}
}
}
}

LOGGER.debug("Target voltage compatibility checked in {} ms", stopwatch.elapsed().toMillis());

return incompatibleControlledBuses;
}

public void fix(TargetVoltageCompatibilityCheckerParameters parameters) {
List<Pair<LfBus, LfBus>> incompatibleControlledBuses = check(parameters);
// some buses could be part of multiple target v incompatible buses couples
// fix the most referenced ones
Map<LfBus, MutableInt> incompatibleControlledBusRefCount = new HashMap<>();
for (Pair<LfBus, LfBus> p : incompatibleControlledBuses) {
incompatibleControlledBusRefCount.computeIfAbsent(p.getLeft(), k -> new MutableInt(0)).increment();
incompatibleControlledBusRefCount.computeIfAbsent(p.getRight(), k -> new MutableInt(0)).increment();
}
Set<LfBus> fixedControlledBuses = new HashSet<>();
for (Pair<LfBus, LfBus> p : incompatibleControlledBuses) {
LfBus controlledBus1 = p.getLeft();
LfBus controlledBus2 = p.getRight();
if (fixedControlledBuses.contains(controlledBus1) || fixedControlledBuses.contains(controlledBus2)) {
continue;
}
LfBus controlledBusToFix = incompatibleControlledBusRefCount.get(controlledBus1).intValue() > incompatibleControlledBusRefCount.get(controlledBus2).intValue()
? controlledBus1 : controlledBus2;
for (var voltageControl : controlledBusToFix.getVoltageControls()) {
LOGGER.warn("Controlled buses '{}' and '{}' have incompatible target voltages ({} and {}): disable voltage control of '{}'",
controlledBus1.getId(), controlledBus2.getId(), controlledBus1.getHighestPriorityTargetV().orElseThrow() * controlledBus1.getNominalV(),
controlledBus2.getHighestPriorityTargetV().orElseThrow() * controlledBus2.getNominalV(), controlledBusToFix.getId());
voltageControl.setDisabled(true);
fixedControlledBuses.add(controlledBusToFix);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright (c) 2024, 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.openloadflow;

import com.powsybl.math.matrix.MatrixFactory;

import java.util.Objects;

/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
public class TargetVoltageCompatibilityCheckerParameters {

public static final int CONTROLLED_BUS_NEIGHBORS_EXPLORATION_DEPTH_DEFAULT_VALUE = 2;

public static final double TARGET_VOLTAGE_PLAUSIBILITY_THRESHOLD_DEFAULT_VALUE = 10;

private MatrixFactory matrixFactory;

private int controlledBusNeighborsExplorationDepth = CONTROLLED_BUS_NEIGHBORS_EXPLORATION_DEPTH_DEFAULT_VALUE;

private double targetVoltagePlausibilityThreshold = TARGET_VOLTAGE_PLAUSIBILITY_THRESHOLD_DEFAULT_VALUE;

public MatrixFactory getMatrixFactory() {
return matrixFactory;
}

public void setMatrixFactory(MatrixFactory matrixFactory) {
this.matrixFactory = Objects.requireNonNull(matrixFactory);
}

public TargetVoltageCompatibilityCheckerParameters(MatrixFactory matrixFactory) {
this.matrixFactory = Objects.requireNonNull(matrixFactory);
}

public int getControlledBusNeighborsExplorationDepth() {
return controlledBusNeighborsExplorationDepth;
}

public TargetVoltageCompatibilityCheckerParameters setControlledBusNeighborsExplorationDepth(int controlledBusNeighborsExplorationDepth) {
this.controlledBusNeighborsExplorationDepth = controlledBusNeighborsExplorationDepth;
return this;
}

public double getTargetVoltagePlausibilityThreshold() {
return targetVoltagePlausibilityThreshold;
}

public TargetVoltageCompatibilityCheckerParameters setTargetVoltagePlausibilityThreshold(double targetVoltagePlausibilityThreshold) {
this.targetVoltagePlausibilityThreshold = targetVoltagePlausibilityThreshold;
return this;
}

public String toString() {
return "TargetVoltageCompatibilityCheckerParameters(" +
"controlledBusNeighborsExplorationDepth=" + controlledBusNeighborsExplorationDepth +
", targetVoltagePlausibilityThreshold=" + targetVoltagePlausibilityThreshold +
", matrixFactory=" + matrixFactory.getClass().getSimpleName() +
")";
}
}
Loading