Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019, RTE (http://www.rte-france.com)
/*
* Copyright (c) 2019-2025, 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/.
Expand Down Expand Up @@ -39,6 +39,8 @@ public abstract class AbstractLfGenerator extends AbstractLfInjection implements

protected double targetV = Double.NaN;

private boolean switchedToLocalVoltageRegulation = false;

protected GeneratorControlType generatorControlType = GeneratorControlType.OFF;

protected String controlledBusId;
Expand Down Expand Up @@ -131,6 +133,11 @@ public double getTargetV() {
return targetV;
}

public boolean switchToLocalVoltageControl() {
switchedToLocalVoltageRegulation = true;
return false;
}

@Override
public GeneratorControlType getGeneratorControlType() {
return generatorControlType;
Expand Down Expand Up @@ -229,7 +236,7 @@ public void setCalculatedQ(double calculatedQ) {

@Override
public LfBus getControlledBus() {
return network.getBusById(controlledBusId);
return switchedToLocalVoltageRegulation ? bus : network.getBusById(controlledBusId);
}

protected void setVoltageControl(double targetV, Terminal terminal, Terminal regulatingTerminal, LfNetworkParameters parameters,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019, RTE (http://www.rte-france.com)
/*
* Copyright (c) 2019-2025, 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/.
Expand Down Expand Up @@ -255,4 +255,16 @@ protected boolean checkIfGeneratorStartedForVoltageControl(LfNetworkParameters p
protected boolean checkIfGeneratorIsInsideActivePowerLimitsForVoltageControl(LfNetworkParameters parameters, LfNetworkLoadingReport report) {
return forceVoltageControl || super.checkIfGeneratorIsInsideActivePowerLimitsForVoltageControl(parameters, report);
}

@Override
public boolean switchToLocalVoltageControl() {
super.switchToLocalVoltageControl();
if (!Double.isNaN(generatorRef.get().getEquivalentLocalTargetV())) {
targetV = generatorRef.get().getEquivalentLocalTargetV() / getBus().getNominalV();
return true;
} else {
// keep the same targetV in perUnit
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
}
}

private static void createVoltageControls(List<LfBus> lfBuses, LfNetworkParameters parameters, LfNetworkLoadingReport report) {

Check failure on line 86 in src/main/java/com/powsybl/openloadflow/network/impl/LfNetworkLoaderImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=com.powsybl%3Apowsybl-open-loadflow&issues=AZrZh5D9u-mtKnwcujlZ&open=AZrZh5D9u-mtKnwcujlZ&pullRequest=1295
List<GeneratorVoltageControl> voltageControls = new ArrayList<>();

// set controller -> controlled link
Expand Down Expand Up @@ -111,6 +111,11 @@
}
voltageControlGenerators.addAll(voltageMonitoringGenerators);

// If remote voltage control is off, move remote voltage control generators to local control
if (!parameters.isGeneratorVoltageRemoteControl()) {
switchGeneratorsToLocalVoltageControl(voltageControlGenerators, controllerBus, report);
}

if (!voltageControlGenerators.isEmpty()) {
checkAndCreateVoltageControl(controllerBus, voltageControls, voltageControlGenerators, parameters, report);
}
Expand All @@ -121,7 +126,28 @@
}
}

private static void checkAndCreateVoltageControl(LfBus controllerBus, List<GeneratorVoltageControl> voltageControls, List<LfGenerator> voltageControlGenerators, LfNetworkParameters parameters, LfNetworkLoadingReport report) {
private static void switchGeneratorsToLocalVoltageControl(List<LfGenerator> voltageControlGenerators,
LfBus controllerBus,
LfNetworkLoadingReport report) {
for (LfGenerator g : voltageControlGenerators) {
if (g.getControlledBus() != g.getBus()) {
LfBus remoteControlledBus = g.getControlledBus();
if (g instanceof AbstractLfGenerator gen && !gen.switchToLocalVoltageControl()) {
report.rescaledRemoteVoltageControls += 1;
double remoteTargetV = g.getTargetV() * remoteControlledBus.getNominalV();
double localTargetV = g.getTargetV() * controllerBus.getNominalV();
LOGGER.warn("Remote voltage control is not activated and no local target is defined for generator {}. The voltage target of {} with remote control is rescaled from {} to {}",
g.getId(), controllerBus.getId(), remoteTargetV, localTargetV);
}
}
}
}

private static void checkAndCreateVoltageControl(LfBus controllerBus,
List<GeneratorVoltageControl> voltageControls,
List<LfGenerator> voltageControlGenerators,
LfNetworkParameters parameters,
LfNetworkLoadingReport report) {
LfGenerator lfGenerator0 = voltageControlGenerators.get(0);
LfBus controlledBus = lfGenerator0.getControlledBus();
double controllerTargetV = lfGenerator0.getTargetV();
Expand All @@ -142,18 +168,11 @@
if (inconsistentTargetVoltages) {
report.generatorsDiscardedFromVoltageControlBecauseInconsistentTargetVoltages += voltageControlGenerators.size();
}
} else if (parameters.isGeneratorVoltageRemoteControl() || controlledBus == controllerBus) {
} else {
// if consistent, creating voltage control
controlledBus.getGeneratorVoltageControl().ifPresentOrElse(
vc -> updateGeneratorVoltageControl(vc, controllerBus, controllerTargetV),
() -> createGeneratorVoltageControl(controlledBus, controllerBus, controllerTargetV, voltageControls, parameters));
} else {
// if voltage remote control deactivated and remote control, set local control instead
LOGGER.warn("Remote voltage control is not activated. The voltage target of {} with remote control is rescaled from {} to {}",
controllerBus.getId(), controllerTargetV, controllerTargetV * controllerBus.getNominalV() / controlledBus.getNominalV());
controlledBus.getGeneratorVoltageControl().ifPresentOrElse(
vc -> updateGeneratorVoltageControl(vc, controllerBus, controllerTargetV), // updating only to check targetV uniqueness
() -> createGeneratorVoltageControl(controllerBus, controllerBus, controllerTargetV, voltageControls, parameters));
}
}

Expand Down Expand Up @@ -1068,6 +1087,12 @@
lfNetwork, report.shuntsWithInconsistentTargetVoltage);
}

if (report.rescaledRemoteVoltageControls > 0) {
Reports.reportRescaledRemoteVoltageControls(reportNode, report.rescaledRemoteVoltageControls);
LOGGER.warn("Network {}: {} remote voltage controls have no backup local targetV and have been rescaled",
lfNetwork, report.rescaledRemoteVoltageControls);
}

if (parameters.getDebugDir() != null) {
Path debugDir = DebugUtil.getDebugDir(parameters.getDebugDir());
String dateStr = ZonedDateTime.now().format(DATE_TIME_FORMAT);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019, RTE (http://www.rte-france.com)
/*
* Copyright (c) 2019-2025, 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/.
Expand Down Expand Up @@ -47,4 +47,6 @@ public class LfNetworkLoadingReport {
int transformersWithInconsistentTargetVoltage = 0;

int shuntsWithInconsistentTargetVoltage = 0;

int rescaledRemoteVoltageControls = 0;
}
8 changes: 8 additions & 0 deletions src/main/java/com/powsybl/openloadflow/util/Reports.java
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,14 @@ public static void reportShuntsDiscardedFromVoltageControlBecauseTargetVIsIncons
.add();
}

public static void reportRescaledRemoteVoltageControls(ReportNode reportNode, int rescaledRemoteVoltageControls) {
reportNode.newReportNode()
.withMessageTemplate("olf.rescaledRemoteVoltageControls")
.withUntypedValue("count", rescaledRemoteVoltageControls)
.withSeverity(TypedValue.WARN_SEVERITY)
.add();
}

public static void reportAcLfCompleteWithSuccess(ReportNode reportNode, String solverStatus, String outerloopStatus) {
reportNode.newReportNode()
.withMessageTemplate("olf.acLfCompleteWithSuccess")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ olf.PvToPqMinRealisticV = Switch bus '${busId}' PV -> PQ, q set to ${targetQ} =
olf.reactiveControllerBusesToPqBuses = ${remoteReactivePowerControllerBusToPqCount} bus(es) with remote reactive power controller switched PQ
olf.reactiveControllerBusesToPqMaxQ = Remote reactive power controller bus '${busId}' -> PQ, q=${busQ} > maxQ=${maxQ}
olf.reactiveControllerBusesToPqMinQ = Remote reactive power controller bus '${busId}' -> PQ, q=${busQ} < minQ=${minQ}
olf.rescaledRemoteVoltageControls = ${count} generators configured for remote voltage control have no equivalent local targetV and have been rescaled
olf.residualDistributionMismatch = Remaining residual slack bus active power mismatch after active power distribution, ${mismatch} MW remains
olf.sensitivityAnalysis = Sensitivity analysis on network '${networkId}'
olf.shuntsDiscardedFromVoltageControlBecauseTargetVIsInconsistent = ${impactedShuntCount} shunt compensators have been discarded from voltage control because targetV is inconsistent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ olf.PvToPqMinRealisticV = Passage PV-PQ du noeud
olf.reactiveControllerBusesToPqBuses = ${remoteReactivePowerControllerBusToPqCount} noeuds électriques avec régleur de puissance réactive ŕ distance passé(s) en PQ
olf.reactiveControllerBusesToPqMaxQ = Réglage de la puissance réactive distante du noeud électrique '${busId}' -> PQ, q=${busQ} > maxQ=${maxQ}
olf.reactiveControllerBusesToPqMinQ = Noeud électrique ${busId} régleur de puissance réactive ŕ distance -> PQ, q=${busQ} < minQ=${minQ}
olf.rescaledRemoteVoltageControls = ${count} groupes en contrôle de tension distante n'ont pas de consigne équivalente locale et passent en réglage local par mise ŕ l'échelle de la tension nominale locale.
olf.residualDistributionMismatch = Puissance active résiduelle au noeud bilan aprčs compensation, ${mismatch} MW restants
olf.sensitivityAnalysis = Analyse de sensibilité sur le réseau '${networkId}'
olf.shuntsDiscardedFromVoltageControlBecauseTargetVIsInconsistent = ${impactedShuntCount} moyens de compensation statique ont été mis hors réglage de tension car leur tension de consigne (targetV) est incohérente.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
/**
* Copyright (c) 2019, RTE (http://www.rte-france.com)
/*
* Copyright (c) 2019-2025, 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.ac;

import com.powsybl.commons.report.ReportNode;
import com.powsybl.commons.test.PowsyblTestReportResourceBundle;
import com.powsybl.iidm.network.*;
import com.powsybl.loadflow.LoadFlow;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.loadflow.LoadFlowResult;
import com.powsybl.loadflow.LoadFlowRunParameters;
import com.powsybl.math.matrix.DenseMatrixFactory;
import com.powsybl.openloadflow.OpenLoadFlowParameters;
import com.powsybl.openloadflow.OpenLoadFlowProvider;
import com.powsybl.openloadflow.util.LoadFlowAssert;
import com.powsybl.openloadflow.util.report.PowsyblOpenLoadFlowReportResourceBundle;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;

import static org.junit.jupiter.api.Assertions.*;

/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
Expand Down Expand Up @@ -92,10 +98,106 @@ void setUp() {
}

@Test
void test() {
LoadFlowResult result = loadFlowRunner.run(network, parameters);
void test() throws IOException {
ReportNode reportNode = ReportNode.newRootReportNode()
.withResourceBundles(PowsyblOpenLoadFlowReportResourceBundle.BASE_NAME, PowsyblTestReportResourceBundle.TEST_BASE_NAME)
.withMessageTemplate("testReport")
.build();
LoadFlowRunParameters rp = new LoadFlowRunParameters()
.setReportNode(reportNode)
.setParameters(parameters);
LoadFlowResult result = loadFlowRunner.run(network, rp);
assertTrue(result.isFullyConverged());
LoadFlowAssert.assertVoltageEquals(20.67, b1); // check local targetV has been correctly rescaled
LoadFlowAssert.assertVoltageEquals(20.67, b1); // check local targetV has been correctly rescaled 20.67=413.4/400*20
LoadFlowAssert.assertVoltageEquals(395.927, b2);
LoadFlowAssert.assertReportEquals("/targetVRescaleReport.txt", reportNode);
}

@Test
void testLocalTargetV() {
network.getGenerator("g1").setTargetV(413.4);
parameters.getExtension(OpenLoadFlowParameters.class).setVoltageRemoteControl(true);

LoadFlowResult result = loadFlowRunner.run(network, parameters);

assertTrue(result.isFullyConverged());
LoadFlowAssert.assertVoltageEquals(413.4, b2);
LoadFlowAssert.assertVoltageEquals(21.55, b1);

// Set the backup local target v and run without remote voltage control and check that the same result is obtained
network.getGenerator("g1").setTargetV(413.4, 21.5535);
parameters.getExtension(OpenLoadFlowParameters.class).setVoltageRemoteControl(false);

result = loadFlowRunner.run(network, parameters);

assertTrue(result.isFullyConverged());
LoadFlowAssert.assertVoltageEquals(413.4, b2);
LoadFlowAssert.assertVoltageEquals(21.55, b1);

// Change a bit the local target V anc verify that it is honored
network.getGenerator("g1").setTargetV(413.4, 21.0);

parameters.getExtension(OpenLoadFlowParameters.class).setVoltageRemoteControl(false);

result = loadFlowRunner.run(network, parameters);

assertTrue(result.isFullyConverged());
LoadFlowAssert.assertVoltageEquals(402.45, b2);
LoadFlowAssert.assertVoltageEquals(21.0, b1); // The local target V is maintained
}

@Test
void testInconsistentLocalTargetV() {
Generator g2 = network.getVoltageLevel("vl1")
.newGenerator()
.setId("g2")
.setBus("b1")
.setEnergySource(EnergySource.THERMAL)
.setMinP(0)
.setMaxP(200)
.setTargetP(100)
.setTargetV(413.4)
.setVoltageRegulatorOn(true)
.setRegulatingTerminal(network.getLoad("l2").getTerminal())
.add();
network.getGenerator("g1").setTargetV(413.4, 21.0);
g2.setTargetV(413.4, 21.0);

parameters.getExtension(OpenLoadFlowParameters.class).setVoltageRemoteControl(false);

LoadFlowResult result = loadFlowRunner.run(network, parameters);

assertTrue(result.isFullyConverged());
LoadFlowAssert.assertVoltageEquals(402.45, b2);
LoadFlowAssert.assertVoltageEquals(21.0, b1); // The local target V is maintained

// Set inconsistent local targets
network.getGenerator("g1").setTargetV(413.4, 20.9);
g2.setTargetV(413.4, 21.1);

result = loadFlowRunner.run(network, parameters);

assertTrue(result.isFullyConverged());
LoadFlowAssert.assertVoltageEquals(400.48, b2);
LoadFlowAssert.assertVoltageEquals(20.9, b1); // The local target V of first generator found is maintained

parameters.getExtension(OpenLoadFlowParameters.class).setDisableInconsistentVoltageControls(true);
network.getGenerator("g1").setTargetQ(10);
g2.setTargetQ(10);
result = loadFlowRunner.run(network, parameters);

// The groups have been disabled from voltage control
assertSame(LoadFlowResult.ComponentResult.Status.NO_CALCULATION, result.getComponentResults().getFirst().getStatus());
assertEquals("Network has no generator with voltage control enabled", result.getComponentResults().getFirst().getStatusText());

// set consistent targets and run with disableInconsistentVoltage mode
network.getGenerator("g1").setTargetV(413.4, 21);
g2.setTargetV(413.4, 21);

result = loadFlowRunner.run(network, parameters);

assertTrue(result.isFullyConverged());
LoadFlowAssert.assertVoltageEquals(402.45, b2);
LoadFlowAssert.assertVoltageEquals(21.0, b1); // The local target V is maintained
}
}
13 changes: 13 additions & 0 deletions src/test/resources/targetVRescaleReport.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
+ Test Report
+ Load flow on network 'GeneratorRemoteControlLocalRescaleTest'
+ Network CC0 SC0
1 generators configured for remote voltage control have no equivalent local targetV and have been rescaled
+ Network info
Network has 2 buses and 1 branches
Network balance: active generation=100 MW, active load=99.9 MW, reactive generation=0 MVar, reactive load=80 MVar
Angle reference bus: vl2_0
Slack bus: vl2_0
Outer loop DistributedSlack
Outer loop VoltageMonitoring
Outer loop ReactiveLimits
AC load flow completed successfully (solverStatus=CONVERGED, outerloopStatus=STABLE)