diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/IncrementalShuntVoltageControlOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/IncrementalShuntVoltageControlOuterLoop.java index 7477a43ccb..d0cfb58b50 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/outerloop/IncrementalShuntVoltageControlOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/IncrementalShuntVoltageControlOuterLoop.java @@ -23,8 +23,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Comparator; -import java.util.List; +import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -47,8 +47,17 @@ public String getName() { return NAME; } - public static List getControlledBuses(IncrementalContextData contextData) { - return IncrementalContextData.getControlledBuses(contextData.getCandidateControlledBuses(), VoltageControl.Type.SHUNT); + public static List getControlledBusesOutOfDeadband(IncrementalContextData contextData) { + return IncrementalContextData.getControlledBuses(contextData.getCandidateControlledBuses(), VoltageControl.Type.SHUNT).stream() + .filter(bus -> isOutOfDeadband(bus.getShuntVoltageControl().orElseThrow())) + .collect(Collectors.toList()); + } + + public static List getControllerElementsOutOfDeadband(List controlledBusesOutOfDeadband) { + return controlledBusesOutOfDeadband.stream() + .flatMap(bus -> bus.getShuntVoltageControl().orElseThrow().getMergedControllerElements().stream()) + .filter(Predicate.not(LfShunt::isDisabled)) + .collect(Collectors.toList()); } public static List getControllerElements(IncrementalContextData contextData) { @@ -94,7 +103,10 @@ private static int[] createControllerShuntIndex(LfNetwork network, List private static DenseMatrix calculateSensitivityValues(List controllerShunts, int[] controllerShuntIndex, EquationSystem equationSystem, JacobianMatrix j) { - DenseMatrix rhs = new DenseMatrix(equationSystem.getIndex().getSortedEquationsToSolve().size(), controllerShunts.size()); + int nRows = equationSystem.getIndex().getSortedEquationsToSolve().size(); + int nColumns = controllerShunts.size(); + + DenseMatrix rhs = new DenseMatrix(nRows, nColumns); for (LfShunt controllerShunt : controllerShunts) { equationSystem.getEquation(controllerShunt.getNum(), AcEquationType.SHUNT_TARGET_B) .ifPresent(equation -> rhs.set(equation.getColumn(), controllerShuntIndex[controllerShunt.getNum()], 1d)); @@ -148,6 +160,27 @@ private void adjustB(ShuntVoltageControl voltageControl, List sortedCon } } + private static double getDiffV(ShuntVoltageControl voltageControl) { + double targetV = voltageControl.getControlledBus().getHighestPriorityMainVoltageControl().orElseThrow().getTargetValue(); + double v = voltageControl.getControlledBus().getV(); + return targetV - v; + } + + private static boolean isOutOfDeadband(ShuntVoltageControl voltageControl) { + double diffV = getDiffV(voltageControl); + double halfTargetDeadband = getHalfTargetDeadband(voltageControl); + boolean outOfDeadband = Math.abs(diffV) > halfTargetDeadband; + if (outOfDeadband) { + List controllers = voltageControl.getMergedControllerElements().stream() + .filter(shunt -> !shunt.isDisabled()) + .collect(Collectors.toList()); + LOGGER.trace("Controlled bus '{}' ({} controllers) is outside of its deadband (half is {} kV) and could need a voltage adjustment of {} kV", + voltageControl.getControlledBus().getId(), controllers.size(), halfTargetDeadband * voltageControl.getControlledBus().getNominalV(), + diffV * voltageControl.getControlledBus().getNominalV()); + } + return outOfDeadband; + } + @Override public OuterLoopStatus check(AcOuterLoopContext context, Reporter reporter) { MutableObject status = new MutableObject<>(OuterLoopStatus.STABLE); @@ -156,20 +189,27 @@ public OuterLoopStatus check(AcOuterLoopContext context, Reporter reporter) { AcLoadFlowContext loadFlowContext = context.getLoadFlowContext(); var contextData = (IncrementalContextData) context.getData(); - List controllerShunts = getControllerElements(contextData); - SensitivityContext sensitivityContext = new SensitivityContext(network, controllerShunts, + // check which shunts are not within their deadbands + List controlledBusesOutOfDeadband = getControlledBusesOutOfDeadband(contextData); + List controllerShuntsOutOfDeadband = getControllerElementsOutOfDeadband(controlledBusesOutOfDeadband); + + // all shunts are within their deadbands + if (controllerShuntsOutOfDeadband.isEmpty()) { + return status.getValue(); + } + + SensitivityContext sensitivityContext = new SensitivityContext(network, controllerShuntsOutOfDeadband, loadFlowContext.getEquationSystem(), loadFlowContext.getJacobianMatrix()); - getControlledBuses(contextData) - .forEach(controlledBus -> { - ShuntVoltageControl voltageControl = controlledBus.getShuntVoltageControl().orElseThrow(); - double diffV = controlledBus.getHighestPriorityMainVoltageControl().orElseThrow().getTargetValue() - voltageControl.getControlledBus().getV(); - List sortedControllers = voltageControl.getMergedControllerElements().stream() - .filter(shunt -> !shunt.isDisabled()) - .sorted(Comparator.comparingDouble(LfShunt::getBMagnitude).reversed()) - .collect(Collectors.toList()); - adjustB(voltageControl, sortedControllers, controlledBus, contextData, sensitivityContext, diffV, status); - }); + controlledBusesOutOfDeadband.forEach(controlledBus -> { + ShuntVoltageControl voltageControl = controlledBus.getShuntVoltageControl().orElseThrow(); + double diffV = getDiffV(voltageControl); + List sortedControllers = voltageControl.getMergedControllerElements().stream() + .filter(shunt -> !shunt.isDisabled()) + .sorted(Comparator.comparingDouble(LfShunt::getBMagnitude).reversed()) + .collect(Collectors.toList()); + adjustB(voltageControl, sortedControllers, controlledBus, contextData, sensitivityContext, diffV, status); + }); return status.getValue(); } diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/IncrementalTransformerVoltageControlOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/IncrementalTransformerVoltageControlOuterLoop.java index f5fc81097e..0d0fce14ca 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/outerloop/IncrementalTransformerVoltageControlOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/IncrementalTransformerVoltageControlOuterLoop.java @@ -26,10 +26,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -56,8 +54,17 @@ public String getName() { return NAME; } - public static List getControlledBuses(IncrementalContextData contextData) { - return IncrementalContextData.getControlledBuses(contextData.getCandidateControlledBuses(), VoltageControl.Type.TRANSFORMER); + public static List getControlledBusesOutOfDeadband(IncrementalContextData contextData) { + return IncrementalContextData.getControlledBuses(contextData.getCandidateControlledBuses(), VoltageControl.Type.TRANSFORMER).stream() + .filter(bus -> isOutOfDeadband(bus.getTransformerVoltageControl().orElseThrow())) + .collect(Collectors.toList()); + } + + public static List getControllerElementsOutOfDeadband(List controlledBusesOutOfDeadband) { + return controlledBusesOutOfDeadband.stream() + .flatMap(bus -> bus.getTransformerVoltageControl().orElseThrow().getMergedControllerElements().stream()) + .filter(Predicate.not(LfBranch::isDisabled)) + .collect(Collectors.toList()); } public static List getControllerElements(IncrementalContextData contextData) { @@ -93,7 +100,10 @@ public SensitivityContext(LfNetwork network, List controllerBranches, private static DenseMatrix calculateSensitivityValues(List controllerBranches, int[] controllerBranchIndex, EquationSystem equationSystem, JacobianMatrix j) { - DenseMatrix rhs = new DenseMatrix(equationSystem.getIndex().getSortedEquationsToSolve().size(), controllerBranches.size()); + int nRows = equationSystem.getIndex().getSortedEquationsToSolve().size(); + int nColumns = controllerBranches.size(); + + DenseMatrix rhs = new DenseMatrix(nRows, nColumns); for (LfBranch controllerBranch : controllerBranches) { equationSystem.getEquation(controllerBranch.getNum(), AcEquationType.BRANCH_TARGET_RHO1) .ifPresent(equation -> rhs.set(equation.getColumn(), controllerBranchIndex[controllerBranch.getNum()], 1d)); @@ -191,11 +201,26 @@ private boolean adjustWithSeveralControllers(List controllerBranches, } private static double getDiffV(TransformerVoltageControl voltageControl) { - double targetV = voltageControl.getTargetValue(); + double targetV = voltageControl.getControlledBus().getHighestPriorityMainVoltageControl().orElseThrow().getTargetValue(); double v = voltageControl.getControlledBus().getV(); return targetV - v; } + private static boolean isOutOfDeadband(TransformerVoltageControl voltageControl) { + double diffV = getDiffV(voltageControl); + double halfTargetDeadband = getHalfTargetDeadband(voltageControl); + boolean outOfDeadband = Math.abs(diffV) > halfTargetDeadband; + if (outOfDeadband) { + List controllers = voltageControl.getMergedControllerElements().stream() + .filter(b -> !b.isDisabled()) + .collect(Collectors.toList()); + LOGGER.trace("Controlled bus '{}' ({} controllers) is outside of its deadband (half is {} kV) and could need a voltage adjustment of {} kV", + voltageControl.getControlledBus().getId(), controllers.size(), halfTargetDeadband * voltageControl.getControlledBus().getNominalV(), + diffV * voltageControl.getControlledBus().getNominalV()); + } + return outOfDeadband; + } + @Override public OuterLoopStatus check(AcOuterLoopContext context, Reporter reporter) { MutableObject status = new MutableObject<>(OuterLoopStatus.STABLE); @@ -204,49 +229,49 @@ public OuterLoopStatus check(AcOuterLoopContext context, Reporter reporter) { AcLoadFlowContext loadFlowContext = context.getLoadFlowContext(); var contextData = (IncrementalContextData) context.getData(); - List controllerBranches = getControllerElements(contextData); - SensitivityContext sensitivityContext = new SensitivityContext(network, controllerBranches, + // filter out buses/branches which are outside their deadbands + List controlledBusesOutOfDeadband = getControlledBusesOutOfDeadband(contextData); + List controllerBranchesOutOfDeadband = getControllerElementsOutOfDeadband(controlledBusesOutOfDeadband); + + // all branches are within their deadbands + if (controllerBranchesOutOfDeadband.isEmpty()) { + return status.getValue(); + } + + SensitivityContext sensitivityContext = new SensitivityContext(network, controllerBranchesOutOfDeadband, loadFlowContext.getEquationSystem(), loadFlowContext.getJacobianMatrix()); // for synthetics logs - List controlledBusesOutsideOfDeadband = new ArrayList<>(); List controlledBusesAdjusted = new ArrayList<>(); List controlledBusesWithAllItsControllersToLimit = new ArrayList<>(); - List controlledBuses = getControlledBuses(contextData); - - controlledBuses.forEach(controlledBus -> { + controlledBusesOutOfDeadband.forEach(controlledBus -> { TransformerVoltageControl voltageControl = controlledBus.getTransformerVoltageControl().orElseThrow(); double diffV = getDiffV(voltageControl); double halfTargetDeadband = getHalfTargetDeadband(voltageControl); - if (Math.abs(diffV) > halfTargetDeadband) { - controlledBusesOutsideOfDeadband.add(controlledBus.getId()); - List controllers = voltageControl.getMergedControllerElements().stream() - .filter(b -> !b.isDisabled()) - .collect(Collectors.toList()); - LOGGER.trace("Controlled bus '{}' ({} controllers) is outside of its deadband (half is {} kV) and could need a voltage adjustment of {} kV", - controlledBus.getId(), controllers.size(), halfTargetDeadband * controlledBus.getNominalV(), diffV * controlledBus.getNominalV()); - boolean adjusted; - if (controllers.size() == 1) { - adjusted = adjustWithOneController(controllers.get(0), controlledBus, contextData, sensitivityContext, diffV, controlledBusesWithAllItsControllersToLimit); - } else { - adjusted = adjustWithSeveralControllers(controllers, controlledBus, contextData, sensitivityContext, diffV, halfTargetDeadband, controlledBusesWithAllItsControllersToLimit); - } - if (adjusted) { - controlledBusesAdjusted.add(controlledBus.getId()); - status.setValue(OuterLoopStatus.UNSTABLE); - } + List controllers = voltageControl.getMergedControllerElements().stream() + .filter(b -> !b.isDisabled()) + .collect(Collectors.toList()); + boolean adjusted; + if (controllers.size() == 1) { + adjusted = adjustWithOneController(controllers.get(0), controlledBus, contextData, sensitivityContext, diffV, controlledBusesWithAllItsControllersToLimit); + } else { + adjusted = adjustWithSeveralControllers(controllers, controlledBus, contextData, sensitivityContext, diffV, halfTargetDeadband, controlledBusesWithAllItsControllersToLimit); + } + if (adjusted) { + controlledBusesAdjusted.add(controlledBus.getId()); + status.setValue(OuterLoopStatus.UNSTABLE); } }); - if (!controlledBusesOutsideOfDeadband.isEmpty() && LOGGER.isInfoEnabled()) { - Map largestMismatches = controlledBuses.stream() + if (!controlledBusesOutOfDeadband.isEmpty() && LOGGER.isInfoEnabled()) { + Map largestMismatches = controlledBusesOutOfDeadband.stream() .map(controlledBus -> Pair.of(controlledBus.getId(), Math.abs(getDiffV(controlledBus.getTransformerVoltageControl().orElseThrow()) * controlledBus.getNominalV()))) .sorted((p1, p2) -> Double.compare(p2.getRight(), p1.getRight())) .limit(3) // 3 largest .collect(Collectors.toMap(Pair::getLeft, Pair::getRight, (key1, key2) -> key1, LinkedHashMap::new)); LOGGER.info("{} controlled bus voltages are outside of their target deadband, largest ones are: {}", - controlledBusesOutsideOfDeadband.size(), largestMismatches); + controlledBusesOutOfDeadband.size(), largestMismatches); } if (!controlledBusesAdjusted.isEmpty()) { LOGGER.info("{} controlled bus voltages have been adjusted by changing at least one tap", diff --git a/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowTransformerControlTest.java b/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowTransformerControlTest.java index 776b31acf1..57082db96c 100644 --- a/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowTransformerControlTest.java +++ b/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowTransformerControlTest.java @@ -363,6 +363,85 @@ void remoteVoltageControlT2wtTest2() { assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); } + @Test + void testIncrementalVoltageControlWithGenerator() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + Substation substation = network.newSubstation() + .setId("SUBSTATION4") + .setCountry(Country.FR) + .add(); + VoltageLevel vl4 = substation.newVoltageLevel() + .setId("VL_4") + .setNominalV(33.0) + .setLowVoltageLimit(0) + .setHighVoltageLimit(100) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + Bus bus4 = vl4.getBusBreakerView().newBus() + .setId("BUS_4") + .add(); + vl4.newLoad() + .setId("LOAD_4") + .setBus("BUS_4") + .setP0(2.) + .setQ0(0.5) + .add(); + + Line line34 = network.newLine() + .setId("LINE_34") + .setBus1("BUS_3") + .setBus2("BUS_4") + .setR(1.05) + .setX(10.0) + .setG1(0.0000005) + .add(); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(line34.getTerminal2()) + .setTargetV(30.0); + + Generator g4 = vl4.newGenerator() + .setId("GEN_4") + .setBus("BUS_4") + .setMinP(0.0) + .setMaxP(30) + .setTargetP(5) + .setTargetV(33) + .setVoltageRegulatorOn(true) + .add(); + + // Generator reactive capability is enough to hold voltage target + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isOk()); + assertVoltageEquals(33, bus4); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + assertReactivePowerEquals(-7.110, g4.getTerminal()); + + g4.newMinMaxReactiveLimits().setMinQ(-3.5).setMaxQ(3.5).add(); + // Generator reactive capability is not enough to hold voltage target and rtc is deactivated + t2wt.getRatioTapChanger().setRegulating(false); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isOk()); + assertVoltageEquals(31.032, bus4); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + assertReactivePowerEquals(-3.5, g4.getTerminal()); + + // Generator reactive capability is not enough to hold voltage alone but with rtc it is ok + t2wt.getRatioTapChanger().setRegulating(true); + LoadFlowResult result3 = loadFlowRunner.run(network, parameters); + assertTrue(result3.isOk()); + assertVoltageEquals(33, bus4); + assertEquals(1, t2wt.getRatioTapChanger().getTapPosition()); + assertReactivePowerEquals(-1.172, g4.getTerminal()); + } + @Test void nonSupportedVoltageControlT2wtTest() { selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt());