diff --git a/docs/loadflow/parameters.md b/docs/loadflow/parameters.md index 84b1cd92a3..fed134ed79 100644 --- a/docs/loadflow/parameters.md +++ b/docs/loadflow/parameters.md @@ -396,6 +396,22 @@ If the user specifies only a sub-list of priorities, this sub-list is completed order defined by default. Thus, if the user specifies only `["TRANSFORMER"]`, it will be completed to `["TRANSFORMER", "GENERATOR", "SHUNT"]`. +**transformerVoltageControlUseInitialTapPosition** +This parameter is only used if the transformer voltage control is enabled and of mode `AFTER_GENERATOR_VOLTAGE_CONTROL`. +If this parameter is set to `true`, transformers of the same voltage control (meaning controlling the same bus) are merged +and the algorithm tries to maintain the initial difference between rhos. If the voltage at controlled bus is closed to the +target voltage at initial step of the outer loop, the algorithm blocks transformers at their initial tap positions. +The default value of this parameter is `false`. + +**generatorVoltageControlMinNominalVoltage** +This parameter is only used if the transformer voltage control is enabled and of mode `AFTER_GENERATOR_VOLTAGE_CONTROL`. +In this mode, at the first step of the outer loop, the transformers that controlled voltage are disabled, only generators +are participating to voltage control. At second step, generators controlling voltage at a controlled bus above +`generatorVoltageControlMinNominalVoltage` have their voltage control disabled. This parameter overrides an automatic +detection of the minimal nominal voltage based on an analysis of nominal voltages controlled by transformers. The default +value of this parameter is $-1$: with this value the parameter is ignored and the outer loop relies only on the automatic +detection. + **fictitiousGeneratorVoltageControlCheckMode** Specifies the active power checks exemption for fictitious generators voltage control. @@ -410,6 +426,7 @@ The `fictitiousGeneratorVoltageControlCheckMode` option controls whether the abo The default mode is `FORCED`. + ## Configuration file example See below an extract of a config file that could help: diff --git a/src/main/java/com/powsybl/openloadflow/AbstractAcOuterLoopConfig.java b/src/main/java/com/powsybl/openloadflow/AbstractAcOuterLoopConfig.java index a5fd28de62..a18c41551e 100644 --- a/src/main/java/com/powsybl/openloadflow/AbstractAcOuterLoopConfig.java +++ b/src/main/java/com/powsybl/openloadflow/AbstractAcOuterLoopConfig.java @@ -55,12 +55,15 @@ protected static Optional createMonitoringVoltageOuterLoop(OpenLoad return Optional.empty(); } - protected static Optional createTransformerVoltageControlOuterLoop(LoadFlowParameters parameters, OpenLoadFlowParameters.TransformerVoltageControlMode controlMode, - int incrementalTransformerVoltageControlOuterLoopMaxTapShift, double generatorVoltageControlMinNominalVoltage) { + protected static Optional createTransformerVoltageControlOuterLoop(LoadFlowParameters parameters, + boolean useInitialTapPosition, + OpenLoadFlowParameters.TransformerVoltageControlMode controlMode, + int incrementalTransformerVoltageControlOuterLoopMaxTapShift, + double generatorVoltageControlMinNominalVoltage) { if (parameters.isTransformerVoltageControlOn()) { AcOuterLoop outerLoop = switch (controlMode) { case WITH_GENERATOR_VOLTAGE_CONTROL -> new SimpleTransformerVoltageControlOuterLoop(); - case AFTER_GENERATOR_VOLTAGE_CONTROL -> new TransformerVoltageControlOuterLoop(generatorVoltageControlMinNominalVoltage); + case AFTER_GENERATOR_VOLTAGE_CONTROL -> new TransformerVoltageControlOuterLoop(useInitialTapPosition, generatorVoltageControlMinNominalVoltage); case INCREMENTAL_VOLTAGE_CONTROL -> new IncrementalTransformerVoltageControlOuterLoop(incrementalTransformerVoltageControlOuterLoopMaxTapShift); }; return Optional.of(outerLoop); @@ -70,6 +73,7 @@ protected static Optional createTransformerVoltageControlOuterLoop( protected static Optional createTransformerVoltageControlOuterLoop(LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt) { return createTransformerVoltageControlOuterLoop(parameters, + parametersExt.isTransformerVoltageControlUseInitialTapPosition(), parametersExt.getTransformerVoltageControlMode(), parametersExt.getIncrementalTransformerRatioTapControlOuterLoopMaxTapShift(), parametersExt.getGeneratorVoltageControlMinNominalVoltage()); diff --git a/src/main/java/com/powsybl/openloadflow/ExplicitAcOuterLoopConfig.java b/src/main/java/com/powsybl/openloadflow/ExplicitAcOuterLoopConfig.java index a770c2c2c6..294a2227b2 100644 --- a/src/main/java/com/powsybl/openloadflow/ExplicitAcOuterLoopConfig.java +++ b/src/main/java/com/powsybl/openloadflow/ExplicitAcOuterLoopConfig.java @@ -45,6 +45,7 @@ private static Optional createOuterLoop(String name, LoadFlowParame case IncrementalShuntVoltageControlOuterLoop.NAME -> createShuntVoltageControlOuterLoop(parameters, OpenLoadFlowParameters.ShuntVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); case IncrementalTransformerVoltageControlOuterLoop.NAME -> createTransformerVoltageControlOuterLoop(parameters, + parametersExt.isTransformerVoltageControlUseInitialTapPosition(), OpenLoadFlowParameters.TransformerVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL, parametersExt.getIncrementalTransformerRatioTapControlOuterLoopMaxTapShift(), parametersExt.getGeneratorVoltageControlMinNominalVoltage()); @@ -56,10 +57,12 @@ private static Optional createOuterLoop(String name, LoadFlowParame case ShuntVoltageControlOuterLoop.NAME -> createShuntVoltageControlOuterLoop(parameters, OpenLoadFlowParameters.ShuntVoltageControlMode.WITH_GENERATOR_VOLTAGE_CONTROL); case SimpleTransformerVoltageControlOuterLoop.NAME -> createTransformerVoltageControlOuterLoop(parameters, + parametersExt.isTransformerVoltageControlUseInitialTapPosition(), OpenLoadFlowParameters.TransformerVoltageControlMode.WITH_GENERATOR_VOLTAGE_CONTROL, parametersExt.getIncrementalTransformerRatioTapControlOuterLoopMaxTapShift(), parametersExt.getGeneratorVoltageControlMinNominalVoltage()); case TransformerVoltageControlOuterLoop.NAME -> createTransformerVoltageControlOuterLoop(parameters, + parametersExt.isTransformerVoltageControlUseInitialTapPosition(), OpenLoadFlowParameters.TransformerVoltageControlMode.AFTER_GENERATOR_VOLTAGE_CONTROL, parametersExt.getIncrementalTransformerRatioTapControlOuterLoopMaxTapShift(), parametersExt.getGeneratorVoltageControlMinNominalVoltage()); diff --git a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java index b242477d4e..63752a2cd5 100644 --- a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java +++ b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java @@ -257,6 +257,8 @@ public enum FictitiousGeneratorVoltageControlCheckMode { public static final String VOLTAGE_TARGET_PRIORITIES_PARAM_NAME = "voltageTargetPriorities"; + public static final String TRANSFORMER_VOLTAGE_CONTROL_USE_INITIAL_TAP_POSITION_PARAM_NAME = "transformerVoltageControlUseInitialTapPosition"; + public static final String GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_PARAM_NAME = "generatorVoltageControlMinNominalVoltage"; public static final String FICTITIOUS_GENERATOR_VOLTAGE_CONTROL_CHECK_MODE = "fictitiousGeneratorVoltageControlCheckMode"; @@ -331,6 +333,7 @@ public static > List getEnumPossibleValues(Class en new Parameter(REFERENCE_BUS_SELECTION_MODE_PARAM_NAME, ParameterType.STRING, "Reference bus selection mode", ReferenceBusSelector.DEFAULT_MODE.name(), getEnumPossibleValues(ReferenceBusSelectionMode.class)), new Parameter(WRITE_REFERENCE_TERMINALS_PARAM_NAME, ParameterType.BOOLEAN, "Write Reference Terminals", WRITE_REFERENCE_TERMINALS_DEFAULT_VALUE), new Parameter(VOLTAGE_TARGET_PRIORITIES_PARAM_NAME, ParameterType.STRING_LIST, "Voltage target priorities for voltage controls", LfNetworkParameters.VOLTAGE_CONTROL_PRIORITIES_DEFAULT_VALUE, getEnumPossibleValues(VoltageControl.Type.class)), + new Parameter(TRANSFORMER_VOLTAGE_CONTROL_USE_INITIAL_TAP_POSITION_PARAM_NAME, ParameterType.BOOLEAN, "Maintain initial tap position if possible", LfNetworkParameters.TRANSFORMER_VOLTAGE_CONTROL_USE_INITIAL_TAP_POSITION_DEFAULT_VALUE), new Parameter(GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_PARAM_NAME, ParameterType.DOUBLE, "Nominal voltage under which generator voltage controls are disabled during transformer voltage control outer loop of mode AFTER_GENERATOR_VOLTAGE_CONTROL, < 0 means automatic detection", OpenLoadFlowParameters.GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_DEFAULT_VALUE), 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)) ); @@ -504,6 +507,8 @@ public enum ReactiveRangeCheckMode { private List voltageTargetPriorities = LfNetworkParameters.VOLTAGE_CONTROL_PRIORITIES_DEFAULT_VALUE; + private boolean transformerVoltageControlUseInitialTapPosition = LfNetworkParameters.TRANSFORMER_VOLTAGE_CONTROL_USE_INITIAL_TAP_POSITION_DEFAULT_VALUE; + private double generatorVoltageControlMinNominalVoltage = GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_DEFAULT_VALUE; private FictitiousGeneratorVoltageControlCheckMode fictitiousGeneratorVoltageControlCheckMode = FICTITIOUS_GENERATOR_VOLTAGE_CONTROL_CHECK_MODE_DEFAULT_VALUE; @@ -1172,6 +1177,15 @@ public OpenLoadFlowParameters setVoltageTargetPriorities(List voltageTar return this; } + public boolean isTransformerVoltageControlUseInitialTapPosition() { + return transformerVoltageControlUseInitialTapPosition; + } + + public OpenLoadFlowParameters setTransformerVoltageControlUseInitialTapPosition(boolean transformerVoltageControlUseInitialTapPosition) { + this.transformerVoltageControlUseInitialTapPosition = transformerVoltageControlUseInitialTapPosition; + return this; + } + /** * Only if transformer voltage control is active and with mode `AFTER_GENERATOR_VOLTAGE_CONTROL`. Set the nominal * voltage under which the generator voltage control are disabled during outer loop. This parameter overrides the @@ -1269,6 +1283,7 @@ public static OpenLoadFlowParameters load(PlatformConfig platformConfig) { .setReferenceBusSelectionMode(config.getEnumProperty(REFERENCE_BUS_SELECTION_MODE_PARAM_NAME, ReferenceBusSelectionMode.class, ReferenceBusSelector.DEFAULT_MODE)) .setWriteReferenceTerminals(config.getBooleanProperty(WRITE_REFERENCE_TERMINALS_PARAM_NAME, WRITE_REFERENCE_TERMINALS_DEFAULT_VALUE)) .setVoltageTargetPriorities(config.getStringListProperty(VOLTAGE_TARGET_PRIORITIES_PARAM_NAME, LfNetworkParameters.VOLTAGE_CONTROL_PRIORITIES_DEFAULT_VALUE)) + .setTransformerVoltageControlUseInitialTapPosition(config.getBooleanProperty(TRANSFORMER_VOLTAGE_CONTROL_USE_INITIAL_TAP_POSITION_PARAM_NAME, LfNetworkParameters.TRANSFORMER_VOLTAGE_CONTROL_USE_INITIAL_TAP_POSITION_DEFAULT_VALUE)) .setGeneratorVoltageControlMinNominalVoltage(config.getDoubleProperty(GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_PARAM_NAME, GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_DEFAULT_VALUE))); return parameters; } @@ -1418,6 +1433,8 @@ public OpenLoadFlowParameters update(Map properties) { .ifPresent(prop -> this.setWriteReferenceTerminals(Boolean.parseBoolean(prop))); Optional.ofNullable(properties.get(VOLTAGE_TARGET_PRIORITIES_PARAM_NAME)) .ifPresent(prop -> this.setVoltageTargetPriorities(parseStringListProp(prop))); + Optional.ofNullable(properties.get(TRANSFORMER_VOLTAGE_CONTROL_USE_INITIAL_TAP_POSITION_PARAM_NAME)) + .ifPresent(prop -> this.setTransformerVoltageControlUseInitialTapPosition(Boolean.parseBoolean(prop))); Optional.ofNullable(properties.get(GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_PARAM_NAME)) .ifPresent(prop -> this.setGeneratorVoltageControlMinNominalVoltage(Double.parseDouble(prop))); Optional.ofNullable(properties.get(FICTITIOUS_GENERATOR_VOLTAGE_CONTROL_CHECK_MODE)) @@ -1492,6 +1509,7 @@ public Map toMap() { map.put(REFERENCE_BUS_SELECTION_MODE_PARAM_NAME, referenceBusSelectionMode); map.put(WRITE_REFERENCE_TERMINALS_PARAM_NAME, writeReferenceTerminals); map.put(VOLTAGE_TARGET_PRIORITIES_PARAM_NAME, voltageTargetPriorities); + map.put(TRANSFORMER_VOLTAGE_CONTROL_USE_INITIAL_TAP_POSITION_PARAM_NAME, transformerVoltageControlUseInitialTapPosition); map.put(GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_PARAM_NAME, generatorVoltageControlMinNominalVoltage); map.put(FICTITIOUS_GENERATOR_VOLTAGE_CONTROL_CHECK_MODE, fictitiousGeneratorVoltageControlCheckMode); return map; @@ -1870,6 +1888,7 @@ public static boolean equals(LoadFlowParameters parameters1, LoadFlowParameters extension1.getMaxSusceptanceMismatch() == extension2.getMaxSusceptanceMismatch() && extension1.getNewtonRaphsonStoppingCriteriaType() == extension2.getNewtonRaphsonStoppingCriteriaType() && Objects.equals(extension1.getVoltageTargetPriorities(), extension2.getVoltageTargetPriorities()) && + extension1.isTransformerVoltageControlUseInitialTapPosition() == extension2.isTransformerVoltageControlUseInitialTapPosition() && extension1.getGeneratorVoltageControlMinNominalVoltage() == extension2.getGeneratorVoltageControlMinNominalVoltage() && extension1.getFictitiousGeneratorVoltageControlCheckMode() == extension2.getFictitiousGeneratorVoltageControlCheckMode(); } @@ -1962,6 +1981,7 @@ public static LoadFlowParameters clone(LoadFlowParameters parameters) { .setNewtonRaphsonStoppingCriteriaType(extension.getNewtonRaphsonStoppingCriteriaType()) .setReferenceBusSelectionMode(extension.getReferenceBusSelectionMode()) .setVoltageTargetPriorities(extension.getVoltageTargetPriorities()) + .setTransformerVoltageControlUseInitialTapPosition(extension.isTransformerVoltageControlUseInitialTapPosition()) .setGeneratorVoltageControlMinNominalVoltage(extension.getGeneratorVoltageControlMinNominalVoltage()) .setFictitiousGeneratorVoltageControlCheckMode(extension.getFictitiousGeneratorVoltageControlCheckMode()); diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/AbstractTransformerVoltageControlOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/AbstractTransformerVoltageControlOuterLoop.java index a521ad8691..a23df8655c 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/outerloop/AbstractTransformerVoltageControlOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/AbstractTransformerVoltageControlOuterLoop.java @@ -7,9 +7,9 @@ */ package com.powsybl.openloadflow.ac.outerloop; -import com.powsybl.openloadflow.ac.AcOuterLoopContext; import com.powsybl.openloadflow.lf.outerloop.OuterLoopStatus; import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfNetwork; import com.powsybl.openloadflow.network.PiModel; import com.powsybl.openloadflow.network.TransformerVoltageControl; import com.powsybl.openloadflow.network.VoltageControl; @@ -32,9 +32,9 @@ public String getType() { return TYPE; } - protected OuterLoopStatus roundVoltageRatios(AcOuterLoopContext context) { + protected OuterLoopStatus roundVoltageRatios(LfNetwork network) { OuterLoopStatus status = OuterLoopStatus.STABLE; - for (LfBranch controllerBranch : context.getNetwork().getControllerElements(VoltageControl.Type.TRANSFORMER)) { + for (LfBranch controllerBranch : network.getControllerElements(VoltageControl.Type.TRANSFORMER)) { controllerBranch.setVoltageControlEnabled(false); // round the rho shift to the closest tap diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/SimpleTransformerVoltageControlOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/SimpleTransformerVoltageControlOuterLoop.java index 21b177089c..9da7b11772 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/outerloop/SimpleTransformerVoltageControlOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/SimpleTransformerVoltageControlOuterLoop.java @@ -40,7 +40,7 @@ public void initialize(AcOuterLoopContext context) { public OuterLoopResult check(AcOuterLoopContext context, ReportNode reportNode) { OuterLoopStatus status = OuterLoopStatus.STABLE; if (context.getIteration() == 0) { - status = roundVoltageRatios(context); + status = roundVoltageRatios(context.getNetwork()); } return new OuterLoopResult(this, status); } diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/TransformerVoltageControlOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/TransformerVoltageControlOuterLoop.java index 22d78b1ce2..42c069e97d 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/outerloop/TransformerVoltageControlOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/TransformerVoltageControlOuterLoop.java @@ -9,16 +9,15 @@ import com.powsybl.commons.report.ReportNode; import com.powsybl.openloadflow.ac.AcOuterLoopContext; +import com.powsybl.openloadflow.ac.outerloop.tap.GeneratorVoltageControlManager; +import com.powsybl.openloadflow.ac.outerloop.tap.TransformerRatioManager; import com.powsybl.openloadflow.lf.outerloop.OuterLoopResult; import com.powsybl.openloadflow.lf.outerloop.OuterLoopStatus; import com.powsybl.openloadflow.network.LfBranch; import com.powsybl.openloadflow.network.LfBus; +import com.powsybl.openloadflow.network.LfNetwork; import com.powsybl.openloadflow.network.TransformerVoltageControl; import com.powsybl.openloadflow.network.VoltageControl; -import org.apache.commons.lang3.mutable.MutableObject; - -import java.util.ArrayList; -import java.util.List; /** * @author Anne Tilloy {@literal } @@ -27,69 +26,41 @@ public class TransformerVoltageControlOuterLoop extends AbstractTransformerVolta public static final String NAME = "TransformerVoltageControl"; + private enum Step { + INITIAL, + CONTROL, + COMPLETE + } + private final double maxControlledNominalVoltageOverride; private static final class ContextData { - private double maxControlledNominalVoltage = Double.MIN_VALUE; - - private final List busesWithVoltageControlDisabled = new ArrayList<>(); - - private double getMaxControlledNominalVoltage() { - return maxControlledNominalVoltage; - } + private TransformerRatioManager transformerRatioManager; - private void setMaxControlledNominalVoltage(double maxControlledNominalVoltage) { - this.maxControlledNominalVoltage = maxControlledNominalVoltage; - } + private GeneratorVoltageControlManager generatorVoltageControlManager; - private List getBusesWithVoltageControlDisabled() { - return busesWithVoltageControlDisabled; - } + private Step step = Step.INITIAL; } - public TransformerVoltageControlOuterLoop(double maxControlledNominalVoltageOverride) { - this.maxControlledNominalVoltageOverride = maxControlledNominalVoltageOverride; - } + private final boolean useInitialTapPosition; - private static boolean isTransformerVoltageControlsValidForMaxControlledNominalVoltageCalculation(TransformerVoltageControl transformerVoltageControl) { - // are removed from this automatic algorithm the transformer voltage control that are between two nominal - // voltages equivalents. - if (transformerVoltageControl != null) { - for (LfBranch branch : transformerVoltageControl.getControllerElements()) { - if (!branch.isConnectedAtBothSides() - || branch.getBus1().getNominalV() == branch.getBus2().getNominalV()) { - return false; - } - } - return true; - } - return false; - } - - private static double calculateMaxControlledNominalVoltage(AcOuterLoopContext context) { - double maxControlledNominalVoltage = Double.MIN_VALUE; - for (LfBus bus : context.getNetwork().getBuses()) { - if (!bus.isDisabled() - && bus.isTransformerVoltageControlled() - && isTransformerVoltageControlsValidForMaxControlledNominalVoltageCalculation(bus.getTransformerVoltageControl().orElse(null))) { - maxControlledNominalVoltage = Math.max(maxControlledNominalVoltage, bus.getNominalV()); - } - } - return maxControlledNominalVoltage; + public TransformerVoltageControlOuterLoop(boolean useInitialTapPosition, double maxControlledNominalVoltageOverride) { + this.useInitialTapPosition = useInitialTapPosition; + this.maxControlledNominalVoltageOverride = maxControlledNominalVoltageOverride; } @Override public void initialize(AcOuterLoopContext context) { - context.setData(new ContextData()); + ContextData contextData = new ContextData(); + context.setData(contextData); + // All transformer voltage control are disabled for the first equation system resolution. for (LfBranch controllerBranch : context.getNetwork().getControllerElements(VoltageControl.Type.TRANSFORMER)) { controllerBranch.setVoltageControlEnabled(false); } - // all transformer voltage control are disabled for the first equation system resolution. - double maxControlledNominalVoltage = maxControlledNominalVoltageOverride < 0 ? calculateMaxControlledNominalVoltage(context) : maxControlledNominalVoltageOverride; - ((ContextData) context.getData()).setMaxControlledNominalVoltage(maxControlledNominalVoltage); + contextData.generatorVoltageControlManager = new GeneratorVoltageControlManager(context.getNetwork(), maxControlledNominalVoltageOverride); } @Override @@ -99,62 +70,87 @@ public String getName() { @Override public OuterLoopResult check(AcOuterLoopContext context, ReportNode reportNode) { - final MutableObject status = new MutableObject<>(OuterLoopStatus.STABLE); var contextData = (ContextData) context.getData(); - // At first outer loop iteration, the voltage control of generators that controlled at nominal voltage of - // the set controlledNominalVoltages are disabled. - // The transformer voltage controls are enabled. - if (context.getIteration() == 0) { - firstOuterLoop(context, contextData, status); - } - - // At second outer loop iteration, the transformers are rounded. The generator voltage controls that were - // disabled previously are enabled. - if (context.getIteration() == 1) { - secondOuterLoop(context, status, contextData); - } + return switch (contextData.step) { + case INITIAL -> initStep(context.getNetwork(), contextData); + case CONTROL -> controlStep(context.getNetwork(), contextData); + case COMPLETE -> new OuterLoopResult(this, OuterLoopStatus.STABLE); + }; - return new OuterLoopResult(this, status.getValue()); } - private static void firstOuterLoop(AcOuterLoopContext context, ContextData contextData, MutableObject status) { - double maxControlledNominalVoltage = contextData.getMaxControlledNominalVoltage(); - for (LfBus bus : context.getNetwork().getControlledBuses(VoltageControl.Type.GENERATOR)) { - if (bus.getNominalV() <= maxControlledNominalVoltage) { - var voltageControl = bus.getGeneratorVoltageControl().orElseThrow(); - voltageControl.getMergedControllerElements().forEach(controllerBus -> { - if (controllerBus.isGeneratorVoltageControlEnabled()) { - controllerBus.setGenerationTargetQ(controllerBus.getQ().eval()); - controllerBus.setGeneratorVoltageControlEnabled(false); - contextData.getBusesWithVoltageControlDisabled().add(controllerBus); - } - }); - status.setValue(OuterLoopStatus.UNSTABLE); - } - } - for (LfBranch branch : context.getNetwork().getControllerElements(VoltageControl.Type.TRANSFORMER)) { - branch.getVoltageControl().ifPresent(voltageControl -> { + /** + * At first outer loop iteration, the voltage control of generators that controlled under the max controlled nominal + * voltage are disabled (automatic detection or parameter). + * The transformer voltage controls are enabled and their continuous ratio is computed. + * @return a stable status if all taps are already tuned for tension control, unstable otherwise + */ + private OuterLoopResult initStep(LfNetwork network, ContextData contextData) { + boolean needRun = false; + for (LfBranch branch : network.getControllerElements(VoltageControl.Type.TRANSFORMER)) { + if (branch.getVoltageControl().isPresent()) { + TransformerVoltageControl voltageControl = branch.getVoltageControl().orElseThrow(); double targetV = voltageControl.getTargetValue(); double v = voltageControl.getControlledBus().getV(); double diffV = targetV - v; double halfTargetDeadband = getHalfTargetDeadband(voltageControl); if (Math.abs(diffV) > halfTargetDeadband && branch.isConnectedAtBothSides()) { branch.setVoltageControlEnabled(true); - status.setValue(OuterLoopStatus.UNSTABLE); + needRun = true; } - }); + } + } + + contextData.transformerRatioManager = new TransformerRatioManager(network, useInitialTapPosition); + + if (!needRun) { + contextData.step = Step.COMPLETE; + return new OuterLoopResult(this, OuterLoopStatus.STABLE); + } + contextData.generatorVoltageControlManager.disableGeneratorVoltageControlsUnderMaxControlledNominalVoltage(network); + + network.fixTransformerVoltageControls(); + + contextData.step = Step.CONTROL; + return new OuterLoopResult(this, OuterLoopStatus.UNSTABLE); + } + + /** + * During control step, transformers with ratio outside of range are rounded. We iterate until all transformers + * with continuous ratio are within their operating range, rounded then and switch them to COMPLETE. + */ + private OuterLoopResult controlStep(LfNetwork network, ContextData contextData) { + boolean outOfBoundTap = false; + + for (LfBranch controllerBranch : network.getControllerElements(VoltageControl.Type.TRANSFORMER)) { + if (contextData.transformerRatioManager.roundR1ToExtremeTapPosition(controllerBranch)) { + outOfBoundTap = true; + } + } + + if (!outOfBoundTap) { + updateContinuousRatio(network, contextData); + + roundVoltageRatios(network); + contextData.generatorVoltageControlManager.enableGeneratorVoltageControlsUnderMaxControlledNominalVoltage(); + + contextData.step = Step.COMPLETE; } - context.getNetwork().fixTransformerVoltageControls(); + // In any case, the loop must run again. + return new OuterLoopResult(this, OuterLoopStatus.UNSTABLE); } - private void secondOuterLoop(AcOuterLoopContext context, MutableObject status, ContextData contextData) { - status.setValue(roundVoltageRatios(context)); - for (LfBus controllerBus : contextData.getBusesWithVoltageControlDisabled()) { - controllerBus.setGenerationTargetQ(0); - controllerBus.setGeneratorVoltageControlEnabled(true); - status.setValue(OuterLoopStatus.UNSTABLE); + private void updateContinuousRatio(LfNetwork network, ContextData contextData) { + for (LfBus bus : network.getControlledBuses(VoltageControl.Type.TRANSFORMER)) { + bus.getTransformerVoltageControl() + .filter(voltageControl -> voltageControl.getMergeStatus() == VoltageControl.MergeStatus.MAIN) + .ifPresent(voltageControl -> + voltageControl.getMergedControllerElements().stream() + .filter(b -> !b.isDisabled()) + .filter(LfBranch::isVoltageControlEnabled) + .forEach(b -> contextData.transformerRatioManager.updateContinuousRatio(b))); } } } diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/tap/GeneratorVoltageControlManager.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/tap/GeneratorVoltageControlManager.java new file mode 100644 index 0000000000..e5e10a82a0 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/tap/GeneratorVoltageControlManager.java @@ -0,0 +1,113 @@ +/** + * 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.ac.outerloop.tap; + +import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfBus; +import com.powsybl.openloadflow.network.LfNetwork; +import com.powsybl.openloadflow.network.LfVscConverterStation; +import com.powsybl.openloadflow.network.TransformerVoltageControl; +import com.powsybl.openloadflow.network.VoltageControl; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Didier Vidal {@literal } + */ +public class GeneratorVoltageControlManager { + + private final double minNominalVoltageLimit; + private final List disabledControllerBuses = new ArrayList<>(); + + public GeneratorVoltageControlManager(LfNetwork network, double limitOverride) { + this.minNominalVoltageLimit = limitOverride < 0 ? computeDefaultMinNominalVoltageLimit(network) : limitOverride; + } + + private static double computeDefaultMinNominalVoltageLimit(LfNetwork network) { + double defaultMinNominalVoltage = Double.MIN_VALUE; + for (LfBus bus : network.getBuses()) { + if (!bus.isDisabled() + && bus.isTransformerVoltageControlled() + && isTransformerVoltageControlsValidForMaxControlledNominalVoltageCalculation(bus.getTransformerVoltageControl().orElse(null))) { + defaultMinNominalVoltage = Math.max(defaultMinNominalVoltage, bus.getNominalV()); + } + } + return defaultMinNominalVoltage; + } + + private static boolean isTransformerVoltageControlsValidForMaxControlledNominalVoltageCalculation(TransformerVoltageControl transformerVoltageControl) { + // are removed from this automatic algorithm the transformer voltage control that are between two nominal + // voltages equivalents. + if (transformerVoltageControl != null) { + for (LfBranch branch : transformerVoltageControl.getControllerElements()) { + if (!branch.isConnectedAtBothSides() + || branch.getBus1().getNominalV() == branch.getBus2().getNominalV()) { + return false; + } + } + return true; + } + return false; + } + + /** + * Disables the voltage control of generators if controlled bus nominal voltage is under the limit. + */ + public void disableGeneratorVoltageControlsUnderMaxControlledNominalVoltage(LfNetwork network) { + // Clear in case someone wants to call this multiple time in instance lifecycle + // although this class as not designed nor tested with this use case in mind. + disabledControllerBuses.clear(); + for (LfBus bus : network.getControlledBuses(VoltageControl.Type.GENERATOR)) { + if (bus.getNominalV() <= minNominalVoltageLimit) { + var voltageControl = bus.getGeneratorVoltageControl().orElseThrow(); + for (LfBus controllerBus : voltageControl.getMergedControllerElements()) { + if (controllerBus.isGeneratorVoltageControlEnabled() && !hasStepUpTransformers(controllerBus, minNominalVoltageLimit)) { + controllerBus.setGenerationTargetQ(controllerBus.getQ().eval()); + controllerBus.setGeneratorVoltageControlEnabled(false); + disabledControllerBuses.add(controllerBus); + } + } + } + } + } + + /** + * Enables the voltage control of generators if controlled bus nominal voltage is under the limit. + */ + public void enableGeneratorVoltageControlsUnderMaxControlledNominalVoltage() { + for (LfBus controllerBus : disabledControllerBuses) { + controllerBus.setGenerationTargetQ(0); + controllerBus.setGeneratorVoltageControlEnabled(true); + } + } + + /** + * True if a controller bus: + * - has a VSC converter station; + * - has step-up transformers converting voltage to a bus with nominal voltage higher than the voltage limit. A step-up + * transformer has no tap changing capabilities. + */ + private boolean hasStepUpTransformers(LfBus bus, double limit) { + if (bus.getGenerators().stream().anyMatch(LfVscConverterStation.class::isInstance)) { + return true; + } + if (bus.getBranches().stream().anyMatch(b -> b.getVoltageControl().isPresent())) { + return false; + } + + double startingNominalVoltage = bus.getNominalV(); + double minConnectedVoltageLevel = bus.getBranches().stream() + .filter(b -> b.isConnectedAtBothSides()) + .mapToDouble(b -> Math.max(b.getBus1().getNominalV(), b.getBus2().getNominalV())) + .min() + .orElse(-1); + // All branches should be step-up transformers arriving to a voltage higher than limit + return minConnectedVoltageLevel > startingNominalVoltage && minConnectedVoltageLevel > limit; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/tap/TransformerRatioManager.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/tap/TransformerRatioManager.java new file mode 100644 index 0000000000..2884464bdf --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/tap/TransformerRatioManager.java @@ -0,0 +1,140 @@ +/** + * 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.ac.outerloop.tap; + +import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfBus; +import com.powsybl.openloadflow.network.LfNetwork; +import com.powsybl.openloadflow.network.PiModel; +import com.powsybl.openloadflow.network.VoltageControl; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Didier Vidal {@literal } + */ +public class TransformerRatioManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(TransformerRatioManager.class); + + private final boolean useInitialTapPosition; + + public record SharedControl(double maxR1, double minR1, double initialR1) { + + public SharedControl(PiModel piModel) { + this(piModel.getMaxR1(), piModel.getMinR1(), piModel.getR1()); + } + + public SharedControl(double maxR1, double minR1, double a, double b, int count) { + this(maxR1, minR1, (minR1 * a + maxR1 * b) / count); + } + } + + private final Map> sharedControlByBranchId = new HashMap<>(); + + /** + * Initializes the ratios. In particular maxR1, minR1 and initialR1. + * If 'useInitialTapPosition' is true then maxR1, minR1 and initialR1 are the average value of the transformers of the same + * control otherwise the transformer individual values. + */ + public TransformerRatioManager(LfNetwork network, boolean useInitialTapPosition) { + this.useInitialTapPosition = useInitialTapPosition; + for (LfBus bus : network.getControlledBuses(VoltageControl.Type.TRANSFORMER)) { + bus.getTransformerVoltageControl() + .filter(voltageControl -> voltageControl.getMergeStatus() == VoltageControl.MergeStatus.MAIN) + .ifPresent(voltageControl -> { + var controllerBranches = voltageControl.getMergedControllerElements().stream() + .filter(b -> !b.isDisabled()) + .filter(LfBranch::isVoltageControlEnabled) + .toList(); + if (!controllerBranches.isEmpty()) { + if (useInitialTapPosition) { + // shared behavior + double a = 0; + double b = 0; + for (LfBranch branch : controllerBranches) { + PiModel piModel = branch.getPiModel(); + double r1 = piModel.getR1(); + double maxR1 = piModel.getMaxR1(); + double minR1 = piModel.getMinR1(); + if (maxR1 != minR1) { + a += (maxR1 - r1) / (maxR1 - minR1); + b += (r1 - minR1) / (maxR1 - minR1); + } else { + a += 1; + b += 1; + } + } + SharedControl sharedControl = new SharedControl( + controllerBranches.stream().mapToDouble(branch -> branch.getPiModel().getMaxR1()).average().orElseThrow(), + controllerBranches.stream().mapToDouble(branch -> branch.getPiModel().getMinR1()).average().orElseThrow(), + a, + b, + controllerBranches.size()); + controllerBranches.forEach(branch -> sharedControlByBranchId.put(branch.getId(), Pair.of(branch.getPiModel().getR1(), sharedControl))); + } else { + // individual behavior + controllerBranches.forEach(branch -> sharedControlByBranchId.put(branch.getId(), Pair.of(branch.getPiModel().getR1(), new SharedControl(branch.getPiModel())))); + } + } + }); + } + } + + /** + * If 'useInitialTapPosition' is true, for transformers of the same control, we try to keep the initial difference + * between individual ratios. This algorithm maintains the individual tap positions if the voltage is correct with initial + * state. It can also be seen as an approximate simulation of transformers acting with independent automation systems. + * Assumes that all transformers of the same control have the same ratio (should be maintained by + * ACEquationSystemCreator.createR1DistributionEquations). + * If 'useInitialTapPosition' is false, the transformer is not modified and keeps its computed R1. + */ + public void updateContinuousRatio(LfBranch branch) { + if (useInitialTapPosition) { + double individualInitialR1 = sharedControlByBranchId.get(branch.getId()).getLeft(); + SharedControl sharedControl = sharedControlByBranchId.get(branch.getId()).getRight(); + double maxR1 = sharedControl.maxR1(); + double minR1 = sharedControl.minR1(); + double initialR1 = sharedControl.initialR1(); + double computedR1 = branch.getPiModel().getR1(); // equations provide the same R1 for all branches + double updatedR1 = computedR1 >= initialR1 ? + individualInitialR1 + (computedR1 - initialR1) * (branch.getPiModel().getMaxR1() - individualInitialR1) / (maxR1 - initialR1) + : + individualInitialR1 - (initialR1 - computedR1) * (individualInitialR1 - branch.getPiModel().getMinR1()) / (initialR1 - minR1); + branch.getPiModel().setR1(updatedR1); + } + } + + /** + * Rounds the transformer at min or max tap position if the ratio of transformers of the same shared control is above the max + * (or below the min) of the group. + * @return true if the transformer has been frozen and false it voltage control remains disabled + */ + public boolean roundR1ToExtremeTapPosition(LfBranch branch) { + if (branch.isVoltageControlEnabled() && !branch.isDisabled()) { + // round the rho shift to the closest tap + PiModel piModel = branch.getPiModel(); + double r1 = piModel.getR1(); + SharedControl sharedControl = sharedControlByBranchId.get(branch.getId()).getRight(); + double minR1 = sharedControl.minR1(); + double maxR1 = sharedControl.maxR1(); + if (r1 < minR1 || r1 > maxR1) { + LOGGER.info("Transformer {} with voltage control frozen: rounded at extreme tap position", branch.getId()); + piModel.setR1(r1 > maxR1 ? maxR1 : minR1); + piModel.roundR1ToClosestTap(); + branch.setVoltageControlEnabled(false); + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/LfNetworkParameters.java b/src/main/java/com/powsybl/openloadflow/network/LfNetworkParameters.java index 7c9b9e878b..b7de3a51f2 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfNetworkParameters.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfNetworkParameters.java @@ -64,6 +64,8 @@ public class LfNetworkParameters { public static final List VOLTAGE_CONTROL_PRIORITIES_DEFAULT_VALUE = VoltageControl.VOLTAGE_CONTROL_PRIORITIES; + public static final boolean TRANSFORMER_VOLTAGE_CONTROL_USE_INITIAL_TAP_POSITION_DEFAULT_VALUE = false; + private boolean generatorVoltageRemoteControl = true; private boolean minImpedance = false; diff --git a/src/main/java/com/powsybl/openloadflow/network/PiModel.java b/src/main/java/com/powsybl/openloadflow/network/PiModel.java index fda71a76e5..4d430f6d1b 100644 --- a/src/main/java/com/powsybl/openloadflow/network/PiModel.java +++ b/src/main/java/com/powsybl/openloadflow/network/PiModel.java @@ -44,6 +44,10 @@ public interface PiModel { double getR1(); + double getMinR1(); + + double getMaxR1(); + double getContinuousR1(); double getA1(); diff --git a/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java b/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java index 8dfffa517e..56b6ada3db 100644 --- a/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java +++ b/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java @@ -33,10 +33,16 @@ public class PiModelArray implements PiModel { private LfBranch branch; + private final double minR1; + + private final double maxR1; + public PiModelArray(List models, int lowTapPosition, int tapPosition) { this.models = Objects.requireNonNull(models); this.lowTapPosition = lowTapPosition; tapPositionIndex = tapPosition - lowTapPosition; + minR1 = this.models.stream().mapToDouble(PiModel::getMinR1).min().orElseThrow(); + maxR1 = this.models.stream().mapToDouble(PiModel::getMaxR1).max().orElseThrow(); } List getModels() { @@ -390,6 +396,16 @@ public PiModel setTapPosition(int tapPosition) { return this; } + @Override + public double getMinR1() { + return minR1; + } + + @Override + public double getMaxR1() { + return maxR1; + } + @Override public Range getTapPositionRange() { return Range.of(lowTapPosition, lowTapPosition + models.size() - 1); diff --git a/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java b/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java index 510481599d..9d4376a616 100644 --- a/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java +++ b/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java @@ -110,6 +110,16 @@ public double getR1() { return r1; } + @Override + public double getMinR1() { + return getR1(); + } + + @Override + public double getMaxR1() { + return getR1(); + } + @Override public double getContinuousR1() { return getR1(); diff --git a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java index 545d318541..c79f42720d 100644 --- a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java +++ b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java @@ -384,7 +384,7 @@ void testCloneParameters() { @Test void testToString() { OpenLoadFlowParameters parameters = new OpenLoadFlowParameters(); - assertEquals("OpenLoadFlowParameters(slackBusSelectionMode=MOST_MESHED, slackBusesIds=[], slackDistributionFailureBehavior=LEAVE_ON_SLACK_BUS, voltageRemoteControl=true, lowImpedanceBranchMode=REPLACE_BY_ZERO_IMPEDANCE_LINE, loadPowerFactorConstant=false, plausibleActivePowerLimit=5000.0, newtonRaphsonStoppingCriteriaType=UNIFORM_CRITERIA, slackBusPMaxMismatch=1.0, maxActivePowerMismatch=0.01, maxReactivePowerMismatch=0.01, maxVoltageMismatch=1.0E-4, maxAngleMismatch=1.0E-5, maxRatioMismatch=1.0E-5, maxSusceptanceMismatch=1.0E-4, voltagePerReactivePowerControl=false, generatorReactivePowerRemoteControl=false, transformerReactivePowerControl=false, maxNewtonRaphsonIterations=15, maxOuterLoopIterations=20, newtonRaphsonConvEpsPerEq=1.0E-4, voltageInitModeOverride=NONE, transformerVoltageControlMode=WITH_GENERATOR_VOLTAGE_CONTROL, shuntVoltageControlMode=WITH_GENERATOR_VOLTAGE_CONTROL, minPlausibleTargetVoltage=0.8, maxPlausibleTargetVoltage=1.2, minRealisticVoltage=0.5, maxRealisticVoltage=2.0, reactiveRangeCheckMode=MAX, lowImpedanceThreshold=1.0E-8, networkCacheEnabled=false, svcVoltageMonitoring=true, stateVectorScalingMode=NONE, maxSlackBusCount=1, debugDir=null, incrementalTransformerRatioTapControlOuterLoopMaxTapShift=3, secondaryVoltageControl=false, reactiveLimitsMaxPqPvSwitch=3, phaseShifterControlMode=CONTINUOUS_WITH_DISCRETISATION, alwaysUpdateNetwork=false, mostMeshedSlackBusSelectorMaxNominalVoltagePercentile=95.0, reportedFeatures=[], slackBusCountryFilter=[], actionableSwitchesIds=[], actionableTransformersIds=[], asymmetrical=false, minNominalVoltageTargetVoltageCheck=20.0, reactivePowerDispatchMode=Q_EQUAL_PROPORTION, outerLoopNames=null, useActiveLimits=true, disableVoltageControlOfGeneratorsOutsideActivePowerLimits=false, lineSearchStateVectorScalingMaxIteration=10, lineSearchStateVectorScalingStepFold=1.3333333333333333, maxVoltageChangeStateVectorScalingMaxDv=0.1, maxVoltageChangeStateVectorScalingMaxDphi=0.17453292519943295, linePerUnitMode=IMPEDANCE, useLoadModel=false, dcApproximationType=IGNORE_R, simulateAutomationSystems=false, acSolverType=NEWTON_RAPHSON, maxNewtonKrylovIterations=100, newtonKrylovLineSearch=false, referenceBusSelectionMode=FIRST_SLACK, writeReferenceTerminals=true, voltageTargetPriorities=[GENERATOR, TRANSFORMER, SHUNT], generatorVoltageControlMinNominalVoltage=-1.0, fictitiousGeneratorVoltageControlCheckMode=FORCED)", + assertEquals("OpenLoadFlowParameters(slackBusSelectionMode=MOST_MESHED, slackBusesIds=[], slackDistributionFailureBehavior=LEAVE_ON_SLACK_BUS, voltageRemoteControl=true, lowImpedanceBranchMode=REPLACE_BY_ZERO_IMPEDANCE_LINE, loadPowerFactorConstant=false, plausibleActivePowerLimit=5000.0, newtonRaphsonStoppingCriteriaType=UNIFORM_CRITERIA, slackBusPMaxMismatch=1.0, maxActivePowerMismatch=0.01, maxReactivePowerMismatch=0.01, maxVoltageMismatch=1.0E-4, maxAngleMismatch=1.0E-5, maxRatioMismatch=1.0E-5, maxSusceptanceMismatch=1.0E-4, voltagePerReactivePowerControl=false, generatorReactivePowerRemoteControl=false, transformerReactivePowerControl=false, maxNewtonRaphsonIterations=15, maxOuterLoopIterations=20, newtonRaphsonConvEpsPerEq=1.0E-4, voltageInitModeOverride=NONE, transformerVoltageControlMode=WITH_GENERATOR_VOLTAGE_CONTROL, shuntVoltageControlMode=WITH_GENERATOR_VOLTAGE_CONTROL, minPlausibleTargetVoltage=0.8, maxPlausibleTargetVoltage=1.2, minRealisticVoltage=0.5, maxRealisticVoltage=2.0, reactiveRangeCheckMode=MAX, lowImpedanceThreshold=1.0E-8, networkCacheEnabled=false, svcVoltageMonitoring=true, stateVectorScalingMode=NONE, maxSlackBusCount=1, debugDir=null, incrementalTransformerRatioTapControlOuterLoopMaxTapShift=3, secondaryVoltageControl=false, reactiveLimitsMaxPqPvSwitch=3, phaseShifterControlMode=CONTINUOUS_WITH_DISCRETISATION, alwaysUpdateNetwork=false, mostMeshedSlackBusSelectorMaxNominalVoltagePercentile=95.0, reportedFeatures=[], slackBusCountryFilter=[], actionableSwitchesIds=[], actionableTransformersIds=[], asymmetrical=false, minNominalVoltageTargetVoltageCheck=20.0, reactivePowerDispatchMode=Q_EQUAL_PROPORTION, outerLoopNames=null, useActiveLimits=true, disableVoltageControlOfGeneratorsOutsideActivePowerLimits=false, lineSearchStateVectorScalingMaxIteration=10, lineSearchStateVectorScalingStepFold=1.3333333333333333, maxVoltageChangeStateVectorScalingMaxDv=0.1, maxVoltageChangeStateVectorScalingMaxDphi=0.17453292519943295, linePerUnitMode=IMPEDANCE, useLoadModel=false, dcApproximationType=IGNORE_R, simulateAutomationSystems=false, acSolverType=NEWTON_RAPHSON, maxNewtonKrylovIterations=100, newtonKrylovLineSearch=false, referenceBusSelectionMode=FIRST_SLACK, writeReferenceTerminals=true, voltageTargetPriorities=[GENERATOR, TRANSFORMER, SHUNT], transformerVoltageControlUseInitialTapPosition=false, generatorVoltageControlMinNominalVoltage=-1.0, fictitiousGeneratorVoltageControlCheckMode=FORCED)", parameters.toString()); } diff --git a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java index 7af81066bd..b5eb3cc21d 100644 --- a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java +++ b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java @@ -87,7 +87,7 @@ void testGetExtendedVoltageInitializer() { @Test void specificParametersTest() { OpenLoadFlowProvider provider = new OpenLoadFlowProvider(); - assertEquals(67, provider.getSpecificParameters().size()); + assertEquals(68, provider.getSpecificParameters().size()); LoadFlowParameters parameters = new LoadFlowParameters(); provider.loadSpecificParameters(Collections.emptyMap()) @@ -110,7 +110,7 @@ void testCreateMapFromSpecificParameters() { OpenLoadFlowParameters parametersExt = new OpenLoadFlowParameters(); OpenLoadFlowProvider provider = new OpenLoadFlowProvider(); Map map = provider.createMapFromSpecificParameters(parametersExt); - assertEquals(67, map.size()); + assertEquals(68, map.size()); assertEquals(provider.getSpecificParameters().size(), map.size()); } diff --git a/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowTransformerVoltageControlTest.java b/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowTransformerVoltageControlTest.java index 5dec0a8ff6..1eb15fe68a 100644 --- a/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowTransformerVoltageControlTest.java +++ b/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowTransformerVoltageControlTest.java @@ -186,6 +186,128 @@ void voltageControlT2wtTest5() { assertVoltageEquals(33.989, t2wt.getTerminal2().getBusView().getBus()); assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); assertEquals(2, t2wt2.getRatioTapChanger().getTapPosition()); + + // Now test in stable mode with AfterVoltageControl + // dealign transformers + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(3) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(34.0); + LoadFlowParameters stableParams = parameters.copy(); + stableParams.getExtension(OpenLoadFlowParameters.class).setTransformerVoltageControlUseInitialTapPosition(true); + stableParams.getExtension(OpenLoadFlowParameters.class).setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.AFTER_GENERATOR_VOLTAGE_CONTROL); + result = loadFlowRunner.run(network, stableParams); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.267, bus2); + assertVoltageEquals(33.989, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(1, t2wt2.getRatioTapChanger().getTapPosition()); + + // still stable mode but with movement needed + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(3) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(34.0); + + result = loadFlowRunner.run(network, stableParams); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.267, bus2); + assertVoltageEquals(33.989, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(1, t2wt2.getRatioTapChanger().getTapPosition()); + + // stable mode but transfo removed fromtuning because of tht limit + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(3) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(34.0); + + stableParams.getExtension(OpenLoadFlowParameters.class).setGeneratorVoltageControlMinNominalVoltage(150); // Above G1 voltage level + result = loadFlowRunner.run(network, stableParams); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.223, bus2); + assertVoltageEquals(32.228, t2wt.getTerminal2().getBusView().getBus()); // no voltage control + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(0, t2wt2.getRatioTapChanger().getTapPosition()); // No change expected + + // generator now included in tht limit + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(3) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(34.0); + + stableParams.getExtension(OpenLoadFlowParameters.class).setGeneratorVoltageControlMinNominalVoltage(90); // Below G1 voltage level + result = loadFlowRunner.run(network, stableParams); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.267, bus2); + assertVoltageEquals(33.989, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(1, t2wt2.getRatioTapChanger().getTapPosition()); + + } + + @Test + void voltageControlT2wtTestRationAtLimit() { + LoadFlowParameters stableParams = parameters.copy(); + stableParams.setTransformerVoltageControlOn(true); + stableParams.getExtension(OpenLoadFlowParameters.class).setTransformerVoltageControlUseInitialTapPosition(true); + stableParams.getExtension(OpenLoadFlowParameters.class).setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.AFTER_GENERATOR_VOLTAGE_CONTROL); + stableParams.getExtension(OpenLoadFlowParameters.class).setMinPlausibleTargetVoltage(0.5); // Keep unusual voltage target to force ratio to tap changer limit + + // Move group to its limit + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); // recreate the network (strange bug in server built otherwise) + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(3) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(26.0); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(26.0); + + System.out.println("build on server fails here"); + LoadFlowResult result = loadFlowRunner.run(network, stableParams); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.283, bus2); + assertVoltageEquals(28.71, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(0, t2wt2.getRatioTapChanger().getTapPosition()); } @Test @@ -987,8 +1109,9 @@ void testGeneratorVoltageControlMinNominalVoltage() { assertVoltageEquals(230.060, b4); assertVoltageEquals(92.050, b6); // we force the min nominal voltage control to 225kV... - // 3 transformer voltage controls have been disabled because no PV buses on not controlled side connected component - parametersExt.setGeneratorVoltageControlMinNominalVoltage(225.0); + // 3 transformer voltage controls have been disabled because no PV buses (even behind a transfo) + // on not controlled side connected component + parametersExt.setGeneratorVoltageControlMinNominalVoltage(230.0); twt1.getRatioTapChanger().setTapPosition(0); twt2.getRatioTapChanger().setTapPosition(0); twt3.getRatioTapChanger().setTapPosition(0); diff --git a/src/test/resources/debug-parameters.json b/src/test/resources/debug-parameters.json index 04ce33b452..3dbd07f73c 100644 --- a/src/test/resources/debug-parameters.json +++ b/src/test/resources/debug-parameters.json @@ -85,6 +85,7 @@ "referenceBusSelectionMode" : "FIRST_SLACK", "writeReferenceTerminals" : true, "voltageTargetPriorities" : [ "GENERATOR", "TRANSFORMER", "SHUNT" ], + "transformerVoltageControlUseInitialTapPosition" : false, "generatorVoltageControlMinNominalVoltage" : -1.0, "fictitiousGeneratorVoltageControlCheckMode" : "FORCED" }