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 secondary voltage control in case of generator remote voltage control #1165

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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 @@ -21,7 +21,6 @@
import com.powsybl.openloadflow.lf.outerloop.OuterLoopStatus;
import com.powsybl.openloadflow.network.*;
import org.apache.commons.lang3.mutable.MutableDouble;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -65,22 +64,20 @@ private static Map<Integer, Integer> buildBusIndex(List<LfBus> buses) {

static class SensitivityContext {

private final Map<Integer, Integer> busNumToSensiColumn;
private final Map<Integer, Integer> controlledBusIndex;

private final DenseMatrix sensitivities;

SensitivityContext(Map<Integer, Integer> busNumToSensiColumn, DenseMatrix sensitivities) {
this.busNumToSensiColumn = Objects.requireNonNull(busNumToSensiColumn);
SensitivityContext(Map<Integer, Integer> controlledBusIndex, DenseMatrix sensitivities) {
this.controlledBusIndex = Objects.requireNonNull(controlledBusIndex);
this.sensitivities = Objects.requireNonNull(sensitivities);
}

static SensitivityContext create(List<LfBus> controlledBuses, AcLoadFlowContext context) {
var busNumToSensiColumn = buildBusIndex(controlledBuses);

DenseMatrix sensitivities = calculateSensitivityValues(controlledBuses, busNumToSensiColumn, context.getEquationSystem(),
static SensitivityContext create(List<LfBus> controlledBuses, Map<Integer, Integer> controlledBusIndex, AcLoadFlowContext context) {
DenseMatrix sensitivities = calculateSensitivityValues(controlledBuses, controlledBusIndex, context.getEquationSystem(),
context.getJacobianMatrix());

return new SensitivityContext(busNumToSensiColumn, sensitivities);
return new SensitivityContext(controlledBusIndex, sensitivities);
}

private static DenseMatrix calculateSensitivityValues(List<LfBus> controlledBuses, Map<Integer, Integer> busNumToSensiColumn,
Expand Down Expand Up @@ -124,7 +121,7 @@ private static EquationTerm<AcEquationType, AcEquationType> getQ(LfShunt shunt)
* Calculate controlled bus voltage to controller bus reactive power injection sensitivity.
*/
double calculateSensiQ(LfBus controllerBus, LfBus controlledBus) {
int controlledBusSensiColumn = busNumToSensiColumn.get(controlledBus.getNum());
int controlledBusSensiColumn = controlledBusIndex.get(controlledBus.getNum());

MutableDouble sq = new MutableDouble();
for (LfBranch branch : controllerBus.getBranches()) {
Expand All @@ -147,7 +144,7 @@ private static EquationTerm<AcEquationType, AcEquationType> getQ(LfShunt shunt)
* Calculate controlled buses voltage to pilot bus voltage sensitivities.
*/
double calculateSensiVpp(LfBus controlledBus, LfBus pilotBus) {
int controlledBusSensiColumn = busNumToSensiColumn.get(controlledBus.getNum());
int controlledBusSensiColumn = controlledBusIndex.get(controlledBus.getNum());
return getCalculatedV(pilotBus).calculateSensi(sensitivities, controlledBusSensiColumn);
}
}
Expand All @@ -162,12 +159,12 @@ private static double calculateK(LfBus controllerBus) {
return qToK(q, controllerBus);
}

private static DenseMatrix createA(List<LfSecondaryVoltageControl> secondaryVoltageControls, Map<Integer, Integer> controllerBusIndex) {
int n = controllerBusIndex.size();
DenseMatrix a = new DenseMatrix(n, n);
private static DenseMatrix createA(List<LfSecondaryVoltageControl> secondaryVoltageControls,
List<LfBus> allControllerBuses, Map<Integer, Integer> controllerBusIndex) {
DenseMatrix a = new DenseMatrix(allControllerBuses.size(), allControllerBuses.size());
// build a block in the global matrix for each of the secondary voltage control
for (LfSecondaryVoltageControl secondaryVoltageControl : secondaryVoltageControls) {
List<LfBus> controllerBuses = secondaryVoltageControl.getControllerBuses();
List<LfBus> controllerBuses = secondaryVoltageControl.getEnabledControllerBuses();
int nControl = controllerBuses.size();
for (LfBus controllerBusI : controllerBuses) {
for (LfBus controllerBusJ : controllerBuses) {
Expand All @@ -181,35 +178,33 @@ private static DenseMatrix createA(List<LfSecondaryVoltageControl> secondaryVolt
}

private static DenseMatrix createK0(List<LfBus> controllerBuses, Map<Integer, Integer> controllerBusIndex) {
int n = controllerBuses.size();
DenseMatrix k0 = new DenseMatrix(n, 1);
DenseMatrix k0 = new DenseMatrix(controllerBuses.size(), 1);
for (LfBus controllerBus : controllerBuses) {
int i = controllerBusIndex.get(controllerBus.getNum());
k0.set(i, 0, calculateK(controllerBus));
}
return k0;
}

private static DenseMatrix createJk(SensitivityContext sensitivityContext, List<LfBus> controllerBuses, Map<Integer, Integer> controllerBusIndex) {
int n = controllerBuses.size();
DenseMatrix jK = new DenseMatrix(n, n);
for (LfBus controllerBusI : controllerBuses) {
private static DenseMatrix createJk(SensitivityContext sensitivityContext,
List<LfBus> controllerBuses, List<LfBus> controlledBuses,
Map<Integer, Integer> controllerBusIndex, Map<Integer, Integer> controlledBusIndex) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

very subtle difference between controllerBusIndex and controlledBusIndex
Is it possible to use something with more visual constrast ?
For example controllingBusIndex / controlledBusIndex

DenseMatrix jK = new DenseMatrix(controlledBuses.size(), controllerBuses.size());
for (LfBus controlledBusI : controlledBuses) {
for (LfBus controllerBusJ : controllerBuses) {
int i = controllerBusIndex.get(controllerBusI.getNum());
int i = controlledBusIndex.get(controlledBusI.getNum());
int j = controllerBusIndex.get(controllerBusJ.getNum());
LfBus controlledBus2 = controllerBusJ.getGeneratorVoltageControl().orElseThrow().getControlledBus();
jK.set(i, j, sensitivityContext.calculateSensiK(controllerBusI, controlledBus2));
jK.set(i, j, sensitivityContext.calculateSensiK(controllerBusJ, controlledBusI));
}
}
return jK;
}

private static DenseMatrix createJvpp(SensitivityContext sensitivityContext, LfBus pilotBus, List<LfBus> controllerBuses, Map<Integer, Integer> controllerBusIndex) {
int n = controllerBuses.size();
DenseMatrix jVpp = new DenseMatrix(n, 1);
for (LfBus controllerBus : controllerBuses) {
int i = controllerBusIndex.get(controllerBus.getNum());
LfBus controlledBus = controllerBus.getGeneratorVoltageControl().orElseThrow().getControlledBus();
private static DenseMatrix createJvpp(SensitivityContext sensitivityContext, LfBus pilotBus,
List<LfBus> controlledBuses, Map<Integer, Integer> controlledBusIndex) {
DenseMatrix jVpp = new DenseMatrix(controlledBuses.size(), 1);
for (LfBus controlledBus : controlledBuses) {
int i = controlledBusIndex.get(controlledBus.getNum());
jVpp.set(i, 0, sensitivityContext.calculateSensiVpp(controlledBus, pilotBus));
}
return jVpp;
Expand All @@ -231,7 +226,7 @@ private static double calculatePilotPointDv(LfSecondaryVoltageControl secondaryV
}

private Optional<List<String>> processSecondaryVoltageControl(List<LfSecondaryVoltageControl> secondaryVoltageControls,
SensitivityContext sensitivityContext) {
AcLoadFlowContext loadFlowContext) {
List<String> adjustedZoneNames = new ArrayList<>();

for (LfSecondaryVoltageControl secondaryVoltageControl : secondaryVoltageControls) {
Expand All @@ -257,25 +252,37 @@ private Optional<List<String>> processSecondaryVoltageControl(List<LfSecondaryVo
.flatMap(control -> control.getEnabledControllerBuses().stream())
.toList();

List<LfBus> allControlledBuses = allControllerBuses.stream()
.map(b -> b.getGeneratorVoltageControl().orElseThrow().getControlledBus())
.distinct()
.toList();

var controllerBusIndex = buildBusIndex(allControllerBuses);
var controlledBusIndex = buildBusIndex(allControlledBuses);

// compute target voltage sensitivities for all controlled buses
SensitivityContext sensitivityContext = SensitivityContext.create(allControlledBuses, controlledBusIndex, loadFlowContext);

DenseMatrix a = createA(secondaryVoltageControls, controllerBusIndex);
DenseMatrix a = createA(secondaryVoltageControls, allControllerBuses, controllerBusIndex);

DenseMatrix k0 = createK0(allControllerBuses, controllerBusIndex);

DenseMatrix rhs = a.times(k0, -1);

DenseMatrix jK = createJk(sensitivityContext, allControllerBuses, controllerBusIndex);

DenseMatrix jK = createJk(sensitivityContext, allControllerBuses, allControlledBuses, controllerBusIndex, controlledBusIndex).transpose();
DenseMatrix b = a.times(jK);

// replace last row for each of the secondary voltage control block
for (LfSecondaryVoltageControl secondaryVoltageControl : secondaryVoltageControls) {
List<LfBus> controllerBuses = secondaryVoltageControl.getControllerBuses();
LfBus lastControllerBus = controllerBuses.get(controllerBuses.size() - 1);
int i = controllerBusIndex.get(lastControllerBus.getNum());

DenseMatrix jVpp = createJvpp(sensitivityContext, secondaryVoltageControl.getPilotBus(), allControllerBuses, controllerBusIndex);
List<LfBus> controllerBuses = secondaryVoltageControl.getEnabledControllerBuses();
List<LfBus> controlledBuses = controllerBuses.stream()
.map(bus -> bus.getGeneratorVoltageControl().orElseThrow().getControlledBus())
.distinct()
.toList();
LfBus lastControlledBus = controlledBuses.get(controlledBuses.size() - 1);
int i = controlledBusIndex.get(lastControlledBus.getNum());

DenseMatrix jVpp = createJvpp(sensitivityContext, secondaryVoltageControl.getPilotBus(), allControlledBuses, controlledBusIndex);
for (int j = 0; j < b.getColumnCount(); j++) {
b.set(i, j, jVpp.get(j, 0));
}
Expand All @@ -292,10 +299,9 @@ private Optional<List<String>> processSecondaryVoltageControl(List<LfSecondaryVo

// compute new controlled bus target voltages
Map<LfBus, Double> newControlledBusTargetV = new HashMap<>(allControllerBuses.size());
for (LfBus controllerBus : allControllerBuses) {
int i = controllerBusIndex.get(controllerBus.getNum());
var vc = controllerBus.getGeneratorVoltageControl().orElseThrow();
LfBus controlledBus = vc.getControlledBus();
for (LfBus controlledBus : allControlledBuses) {
int i = controlledBusIndex.get(controlledBus.getNum());
var vc = controlledBus.getGeneratorVoltageControl().orElseThrow();
double newTargetV = vc.getTargetValue() + dv.get(i, 0);
newControlledBusTargetV.put(controlledBus, newTargetV);
}
Expand All @@ -306,13 +312,7 @@ private Optional<List<String>> processSecondaryVoltageControl(List<LfSecondaryVo
double newTargetV = e.getValue();
return !VoltageControl.isTargetVoltagePlausible(newTargetV, minPlausibleTargetVoltage, maxPlausibleTargetVoltage);
})
.map(e -> {
// convert target to Kv for better display
LfBus controlledBus = e.getKey();
double newTargetV = e.getValue();
return Pair.of(controlledBus, newTargetV * controlledBus.getNominalV());
})
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (notPlausibleNewControlledBusTargetV.isEmpty()) {
for (var e : newControlledBusTargetV.entrySet()) {
LfBus controlledBus = e.getKey();
Expand Down Expand Up @@ -369,15 +369,9 @@ public OuterLoopResult check(AcOuterLoopContext context, ReportNode reportNode)
return new OuterLoopResult(this, OuterLoopStatus.STABLE);
}

// compute target voltage sensitivities for all controlled buses
List<LfBus> allControlledBuses = secondaryVoltageControls.stream()
.flatMap(control -> control.getControlledBuses().stream())
.toList();
SensitivityContext sensitivityContext = SensitivityContext.create(allControlledBuses, context.getLoadFlowContext());

OuterLoopStatus status = OuterLoopStatus.STABLE;

List<String> adjustedZoneNames = processSecondaryVoltageControl(secondaryVoltageControls, sensitivityContext).orElse(null);
List<String> adjustedZoneNames = processSecondaryVoltageControl(secondaryVoltageControls, context.getLoadFlowContext()).orElse(null);
if (adjustedZoneNames == null) {
status = OuterLoopStatus.FAILED;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
package com.powsybl.openloadflow.network;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

Expand Down Expand Up @@ -80,4 +81,23 @@ public void updateReactiveKeys() {
controllerBus.setRemoteControlReactivePercent(reactiveKeysSum == 0 ? 0 : reactiveKeys[i] / reactiveKeysSum);
}
}

/**
* Returns itself in case of local control, split into several local voltage controls in case of remote control.
*/
public List<GeneratorVoltageControl> split() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The method name is misleading.
It appears as a getter, but in reality the method may transform the control if it is remote.

Maybe something like
toLocalVoltageControls ?

if (isLocalControl()) {
return List.of(this);
} else {
List<GeneratorVoltageControl> generatorVoltageControls = new ArrayList<>(controllerElements.size());
// create one (local) generator control per controller bus and remove this one
controlledBus.setGeneratorVoltageControl(null);
for (LfBus controllerBus : controllerElements) {
var generatorVoltageControl = new GeneratorVoltageControl(controllerBus, targetPriority, targetValue);
generatorVoltageControl.addControllerElement(controllerBus);
Copy link
Collaborator

@vidaldid-rte vidaldid-rte Jan 9, 2025

Choose a reason for hiding this comment

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

moving targetV pu of remote bus to targetV pu at local bus can change a lot the network configuration if the transformer between local and remote has ratedUi different from nominalVi, which is quite frequent.

generatorVoltageControls.add(generatorVoltageControl);
}
return generatorVoltageControls;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void removeParticipatingControlUnit(String id) {
public Set<GeneratorVoltageControl> getGeneratorVoltageControls() {
return generatorVoltageControls.stream()
.filter(this::hasAtLeastOneParticipatingControlUnit) // only keep voltage controls where there is at list one enabled control unit
.collect(Collectors.toSet());
.collect(Collectors.toCollection(LinkedHashSet::new));
}

private boolean hasAtLeastOneParticipatingControlUnit(GeneratorVoltageControl vc) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,15 @@ public Optional<GeneratorVoltageControl> getGeneratorVoltageControl() {

@Override
public void setGeneratorVoltageControl(GeneratorVoltageControl generatorVoltageControl) {
this.generatorVoltageControl = Objects.requireNonNull(generatorVoltageControl);
if (hasGeneratorVoltageControllerCapability()) {
this.generatorVoltageControlEnabled = true;
} else if (!isGeneratorVoltageControlled()) {
throw new PowsyblException("Setting inconsistent voltage control to bus " + getId());
this.generatorVoltageControl = generatorVoltageControl;
if (generatorVoltageControl != null) {
if (hasGeneratorVoltageControllerCapability()) {
this.generatorVoltageControlEnabled = true;
} else if (!isGeneratorVoltageControlled()) {
throw new PowsyblException("Setting inconsistent voltage control to bus " + getId());
}
} else {
this.generatorVoltageControlEnabled = false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1105,11 +1105,20 @@ private static void createSecondaryVoltageControls(Network network, LfNetworkPar
// filter missing control units and find corresponding primary voltage control, controlled bus
Set<GeneratorVoltageControl> generatorVoltageControls = findControlZoneGeneratorVoltageControl(network, parameters, lfNetwork, controlZone);
if (!generatorVoltageControls.isEmpty()) {
// remove remote control for generators that belongs to a secondary voltage control zone because
// - it does not make sens to mix generator remote control plus pilot point remote control
// - it cannot work in case of generator shared voltage control (no way to align K of generators
// for a given shared voltage control as a unique controlled bus cannot help to align reactive
// power of generators)
Set<GeneratorVoltageControl> splitGeneratorVoltageControls = generatorVoltageControls.stream()
.flatMap(vc -> vc.split().stream())
.collect(Collectors.toCollection(LinkedHashSet::new));

Set<String> participatingControlUnitIds = controlZone.getControlUnits().stream()
.filter(ControlUnit::isParticipate)
.map(ControlUnit::getId).collect(Collectors.toSet());
var lfSvc = new LfSecondaryVoltageControl(controlZone.getName(), lfPilotBus, targetV,
participatingControlUnitIds, generatorVoltageControls);
participatingControlUnitIds, splitGeneratorVoltageControls);
lfNetwork.addSecondaryVoltageControl(lfSvc);
}
}
Expand Down
Loading
Loading