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

Fast restart for secondary voltage control #966

Merged
merged 7 commits into from
Jan 26, 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
54 changes: 54 additions & 0 deletions src/main/java/com/powsybl/openloadflow/NetworkCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
*/
package com.powsybl.openloadflow;

import com.powsybl.commons.extensions.Extension;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.ControlUnit;
import com.powsybl.iidm.network.extensions.ControlZone;
import com.powsybl.iidm.network.extensions.PilotPoint;
import com.powsybl.iidm.network.extensions.SecondaryVoltageControl;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.openloadflow.ac.AcLoadFlowContext;
import com.powsybl.openloadflow.ac.AcLoadFlowResult;
Expand Down Expand Up @@ -246,6 +251,55 @@ && onShuntUpdate(shunt, attribute)) {
}
}

@Override
public void onExtensionUpdate(Extension<?> extension, String attribute, Object oldValue, Object newValue) {
if (contexts == null || pause) {
return;
}

boolean[] done = new boolean[1];
if ("secondaryVoltageControl".equals(extension.getName())) {
SecondaryVoltageControl svc = (SecondaryVoltageControl) extension;
onSecondaryVoltageControlExtensionUpdate(svc, attribute, newValue, done);
}

if (!done[0]) {
reset();
}
}

private void onSecondaryVoltageControlExtensionUpdate(SecondaryVoltageControl svc, String attribute, Object newValue, boolean[] done) {
if ("pilotPointTargetV".equals(attribute)) {
PilotPoint.TargetVoltageEvent event = (PilotPoint.TargetVoltageEvent) newValue;
ControlZone controlZone = svc.getControlZone(event.controlZoneName()).orElseThrow();
for (AcLoadFlowContext context : contexts) {
LfNetwork lfNetwork = context.getNetwork();
lfNetwork.getSecondaryVoltageControl(controlZone.getName())
.ifPresent(lfSvc -> {
lfSvc.setTargetValue(event.value() / lfSvc.getPilotBus().getNominalV());
context.setNetworkUpdated(true);
done[0] = true;
});
}
} else if ("controlUnitParticipate".equals(attribute)) {
ControlUnit.ParticipateEvent event = (ControlUnit.ParticipateEvent) newValue;
ControlZone controlZone = svc.getControlZone(event.controlZoneName()).orElseThrow();
for (AcLoadFlowContext context : contexts) {
LfNetwork lfNetwork = context.getNetwork();
lfNetwork.getSecondaryVoltageControl(controlZone.getName())
.ifPresent(lfSvc -> {
if (event.value()) {
lfSvc.getParticipatingControlUnitIds().add(event.controlUnitId());
} else {
lfSvc.getParticipatingControlUnitIds().remove(event.controlUnitId());
}
context.setNetworkUpdated(true);
done[0] = true;
});
}
}
}

private void onPropertyChange() {
// nothing to do there could not have any impact on LF calculation
}
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/powsybl/openloadflow/network/LfNetwork.java
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,13 @@ public List<LfSecondaryVoltageControl> getSecondaryVoltageControls() {
return secondaryVoltageControls;
}

public Optional<LfSecondaryVoltageControl> getSecondaryVoltageControl(String controlZoneName) {
Objects.requireNonNull(controlZoneName);
return secondaryVoltageControls.stream()
.filter(lfSvc -> lfSvc.getZoneName().equals(controlZoneName))
.findFirst();
}

private static boolean filterSecondaryVoltageControl(LfSecondaryVoltageControl secondaryVoltageControl) {
return !secondaryVoltageControl.getPilotBus().isDisabled();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
Expand All @@ -20,14 +21,18 @@ public class LfSecondaryVoltageControl {

private final LfBus pilotBus;

private final Set<String> participatingControlUnitIds;

private final Set<GeneratorVoltageControl> generatorVoltageControls;

private final double targetValue;
private double targetValue;

public LfSecondaryVoltageControl(String zoneName, LfBus pilotBus, double targetValue, Set<GeneratorVoltageControl> generatorVoltageControls) {
public LfSecondaryVoltageControl(String zoneName, LfBus pilotBus, double targetValue, Set<String> participatingControlUnitIds,
Set<GeneratorVoltageControl> generatorVoltageControls) {
this.zoneName = Objects.requireNonNull(zoneName);
this.pilotBus = Objects.requireNonNull(pilotBus);
this.targetValue = targetValue;
this.participatingControlUnitIds = Objects.requireNonNull(participatingControlUnitIds);
this.generatorVoltageControls = Objects.requireNonNull(generatorVoltageControls);
}

Expand All @@ -39,12 +44,33 @@ public double getTargetValue() {
return targetValue;
}

public void setTargetValue(double targetValue) {
this.targetValue = targetValue;
}

public LfBus getPilotBus() {
return pilotBus;
}

public Set<String> getParticipatingControlUnitIds() {
return participatingControlUnitIds;
}

public Set<GeneratorVoltageControl> getGeneratorVoltageControls() {
return generatorVoltageControls;
return generatorVoltageControls.stream()
.filter(this::hasAtLeastOneParticipatingControlUnit) // only keep voltage controls where there is at list one enabled control unit
.collect(Collectors.toSet());
}

private boolean hasAtLeastOneParticipatingControlUnit(GeneratorVoltageControl vc) {
for (var controllerElement : vc.getMergedControllerElements()) {
for (LfGenerator generator : controllerElement.getGenerators()) {
if (participatingControlUnitIds.contains(generator.getId())) {
return true;
}
}
}
return false;
}

private static List<LfBus> findControllerBuses(LfBus controlledBus) {
Expand All @@ -55,7 +81,7 @@ private static List<LfBus> findControllerBuses(LfBus controlledBus) {
}

public List<LfBus> getControlledBuses() {
return generatorVoltageControls.stream()
return getGeneratorVoltageControls().stream()
.filter(voltageControl -> voltageControl.isVisible() && voltageControl.getMergeStatus() == VoltageControl.MergeStatus.MAIN)
.map(VoltageControl::getControlledBus)
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,6 @@ private static void checkControlZonesAreDisjoints(LfNetwork lfNetwork) {

private static Set<GeneratorVoltageControl> findControlZoneGeneratorVoltageControl(Network network, LfNetworkParameters parameters, LfNetwork lfNetwork, ControlZone controlZone) {
return controlZone.getControlUnits().stream()
.filter(ControlUnit::isParticipate)
.flatMap(controlUnit -> Networks.getEquipmentRegulatingTerminal(network, controlUnit.getId()).stream())
.flatMap(regulatingTerminal -> {
Connectable<?> connectable = regulatingTerminal.getConnectable();
Expand Down Expand Up @@ -929,7 +928,11 @@ private static void createSecondaryVoltageControls(Network network, LfNetworkPar
controlZone.getControlUnits().size(), controlZone.getName(), generatorVoltageControls.size(),
generatorVoltageControls.stream().map(VoltageControl::getControlledBus).map(LfElement::getId).toList());
if (!generatorVoltageControls.isEmpty()) {
var lfSvc = new LfSecondaryVoltageControl(controlZone.getName(), lfPilotBus, targetV, generatorVoltageControls);
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);
lfNetwork.addSecondaryVoltageControl(lfSvc);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
*/
package com.powsybl.openloadflow.ac;

import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory;
import com.powsybl.iidm.network.Bus;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.VariantManagerConstants;
import com.powsybl.iidm.network.extensions.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory;
import com.powsybl.loadflow.LoadFlow;
Expand All @@ -18,15 +20,12 @@
import com.powsybl.openloadflow.NetworkCache;
import com.powsybl.openloadflow.OpenLoadFlowParameters;
import com.powsybl.openloadflow.OpenLoadFlowProvider;
import com.powsybl.openloadflow.network.EurostagFactory;
import com.powsybl.openloadflow.network.HvdcNetworkFactory;
import com.powsybl.openloadflow.network.NodeBreakerNetworkFactory;
import com.powsybl.openloadflow.network.ShuntNetworkFactory;
import com.powsybl.openloadflow.network.VoltageControlNetworkFactory;
import com.powsybl.openloadflow.network.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Set;

import static com.powsybl.openloadflow.util.LoadFlowAssert.*;
Expand Down Expand Up @@ -452,4 +451,78 @@ void fixCacheInvalidationWhenUpdatingTapPosition() {
assertTrue(result.isFullyConverged());
assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts());
}

@Test
void testSecondaryVoltageControl() {
parametersExt.setSecondaryVoltageControl(true);
var network = IeeeCdfNetworkFactory.create14();
network.newExtension(SecondaryVoltageControlAdder.class)
.newControlZone()
.withName("z1")
.newPilotPoint()
.withTargetV(12.7)
.withBusbarSectionsOrBusesIds(List.of("B10"))
.add()
.newControlUnit()
.withId("B6-G")
.add()
.newControlUnit()
.withId("B8-G")
.add()
.add()
.add();
SecondaryVoltageControl control = network.getExtension(SecondaryVoltageControl.class);
ControlZone z1 = control.getControlZone("z1").orElseThrow();
PilotPoint pilotPoint = z1.getPilotPoint();
LoadFlowResult result = loadFlowRunner.run(network, parameters);
assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus());
assertEquals(5, result.getComponentResults().get(0).getIterationCount());
var b10 = network.getBusBreakerView().getBus("B10");
assertVoltageEquals(12.7, b10);
assertReactivePowerEquals(-17.826, network.getGenerator("B6-G").getTerminal());
assertReactivePowerEquals(-17.827, network.getGenerator("B8-G").getTerminal());

// update pilot point target voltage
pilotPoint.setTargetV(12.5);
assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated

result = loadFlowRunner.run(network, parameters);
assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus());
assertEquals(3, result.getComponentResults().get(0).getIterationCount());
assertVoltageEquals(12.5, b10);
assertReactivePowerEquals(-11.832, network.getGenerator("B6-G").getTerminal());
assertReactivePowerEquals(-11.832, network.getGenerator("B8-G").getTerminal());

ControlUnit b6g = z1.getControlUnit("B6-G").orElseThrow();
b6g.setParticipate(false);
assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated

result = loadFlowRunner.run(network, parameters);
assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus());
assertEquals(1, result.getComponentResults().get(0).getIterationCount());
// there is no re-run of secondary voltage control outer loop, this is expected as pilot point has already reached
// its target voltage and remaining control unit is necessarily aligned.
assertVoltageEquals(12.5, b10);
assertReactivePowerEquals(-11.832, network.getGenerator("B6-G").getTerminal());
assertReactivePowerEquals(-11.832, network.getGenerator("B8-G").getTerminal());

pilotPoint.setTargetV(12.7);
assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated
result = loadFlowRunner.run(network, parameters);
assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus());
assertEquals(4, result.getComponentResults().get(0).getIterationCount());
assertVoltageEquals(12.613, b10); // we cannot reach back to 12.7 Kv with only one control unit
assertReactivePowerEquals(-6.771, network.getGenerator("B6-G").getTerminal());
assertReactivePowerEquals(-24.0, network.getGenerator("B8-G").getTerminal());

// get b6 generator back
b6g.setParticipate(true);
assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated
result = loadFlowRunner.run(network, parameters);
assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus());
assertEquals(4, result.getComponentResults().get(0).getIterationCount());
assertVoltageEquals(12.7, b10); // we can reach now 12.7 Kv with the 2 control units
assertReactivePowerEquals(-17.826, network.getGenerator("B6-G").getTerminal());
assertReactivePowerEquals(-17.826, network.getGenerator("B8-G").getTerminal());
}
}