diff --git a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java index e4b3e2d50b..e0532931cf 100644 --- a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java +++ b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java @@ -29,6 +29,7 @@ import com.powsybl.openloadflow.dc.equations.DcApproximationType; import com.powsybl.openloadflow.dc.equations.DcEquationSystemCreationParameters; import com.powsybl.openloadflow.graph.GraphConnectivityFactory; +import com.powsybl.openloadflow.graph.NaiveGraphConnectivityFactory; import com.powsybl.openloadflow.lf.AbstractLoadFlowParameters; import com.powsybl.openloadflow.network.*; import com.powsybl.openloadflow.network.util.PreviousValueVoltageInitializer; @@ -276,6 +277,8 @@ public enum FictitiousGeneratorVoltageControlCheckMode { public static final String AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME = "areaInterchangePMaxMismatch"; + public static final String FIX_REMOTE_VOLTAGE_TARGET_PARAM_NAME = "fixRemoteTargetVoltage"; + public static > List getEnumPossibleValues(Class enumClass) { return EnumSet.allOf(enumClass).stream().map(Enum::name).collect(Collectors.toList()); } @@ -413,7 +416,8 @@ public static > List getEnumPossibleValues(Class en 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), ParameterScope.FUNCTIONAL, GENERATOR_VOLTAGE_CONTROL_CATEGORY_KEY), new Parameter(AREA_INTERCHANGE_CONTROL_PARAM_NAME, ParameterType.BOOLEAN, "Area interchange control", AREA_INTERCHANGE_CONTROL_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, SLACK_DISTRIBUTION_CATEGORY_KEY), new Parameter(AREA_INTERCHANGE_CONTROL_AREA_TYPE_PARAM_NAME, ParameterType.STRING, "Area type for area interchange control", LfNetworkParameters.AREA_INTERCHANGE_CONTROL_AREA_TYPE_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, SLACK_DISTRIBUTION_CATEGORY_KEY), - new Parameter(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, ParameterType.DOUBLE, "Area interchange max active power mismatch", AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, SLACK_DISTRIBUTION_CATEGORY_KEY) + new Parameter(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, ParameterType.DOUBLE, "Area interchange max active power mismatch", AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, SLACK_DISTRIBUTION_CATEGORY_KEY), + new Parameter(FIX_REMOTE_VOLTAGE_TARGET_PARAM_NAME, ParameterType.BOOLEAN, "Automatically fix problematic remote voltage targets", AcLoadFlowParameters.FIX_REMOTE_VOLTAGE_TARGET_DEFAULT_VALUE, ParameterScope.FUNCTIONAL, VOLTAGE_CONTROLS_CATEGORY_KEY) ); public enum VoltageInitModeOverride { @@ -597,6 +601,8 @@ public enum ReactiveRangeCheckMode { private double areaInterchangePMaxMismatch = AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE; + private boolean fixRemoteVoltageTarget = AcLoadFlowParameters.FIX_REMOTE_VOLTAGE_TARGET_DEFAULT_VALUE; + public static double checkParameterValue(double parameterValue, boolean condition, String parameterName) { if (!condition) { throw new IllegalArgumentException("Invalid value for parameter " + parameterName + ": " + parameterValue); @@ -1322,6 +1328,15 @@ public OpenLoadFlowParameters setAreaInterchangePMaxMismatch(double areaIntercha return this; } + public boolean isFixRemoteVoltageTarget() { + return fixRemoteVoltageTarget; + } + + public OpenLoadFlowParameters setFixRemoteVoltageTarget(boolean fixRemoteVoltageTarget) { + this.fixRemoteVoltageTarget = fixRemoteVoltageTarget; + return this; + } + public static OpenLoadFlowParameters load() { return load(PlatformConfig.defaultConfig()); } @@ -1400,7 +1415,8 @@ public static OpenLoadFlowParameters load(PlatformConfig platformConfig) { .setGeneratorVoltageControlMinNominalVoltage(config.getDoubleProperty(GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_PARAM_NAME, GENERATOR_VOLTAGE_CONTROL_MIN_NOMINAL_VOLTAGE_DEFAULT_VALUE)) .setAreaInterchangeControl(config.getBooleanProperty(AREA_INTERCHANGE_CONTROL_PARAM_NAME, AREA_INTERCHANGE_CONTROL_DEFAULT_VALUE)) .setAreaInterchangeControlAreaType(config.getStringProperty(AREA_INTERCHANGE_CONTROL_AREA_TYPE_PARAM_NAME, LfNetworkParameters.AREA_INTERCHANGE_CONTROL_AREA_TYPE_DEFAULT_VALUE)) - .setAreaInterchangePMaxMismatch(config.getDoubleProperty(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE))); + .setAreaInterchangePMaxMismatch(config.getDoubleProperty(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, AREA_INTERCHANGE_P_MAX_MISMATCH_DEFAULT_VALUE)) + .setFixRemoteVoltageTarget(config.getBooleanProperty(FIX_REMOTE_VOLTAGE_TARGET_PARAM_NAME, AcLoadFlowParameters.FIX_REMOTE_VOLTAGE_TARGET_DEFAULT_VALUE))); return parameters; } @@ -1561,11 +1577,13 @@ public OpenLoadFlowParameters update(Map properties) { .ifPresent(this::setAreaInterchangeControlAreaType); Optional.ofNullable(properties.get(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME)) .ifPresent(prop -> this.setAreaInterchangePMaxMismatch(Double.parseDouble(prop))); + Optional.ofNullable(properties.get(FIX_REMOTE_VOLTAGE_TARGET_PARAM_NAME)) + .ifPresent(prop -> this.setFixRemoteVoltageTarget(Boolean.parseBoolean(prop))); return this; } public Map toMap() { - Map map = new LinkedHashMap<>(71); + Map map = new LinkedHashMap<>(72); map.put(SLACK_BUS_SELECTION_MODE_PARAM_NAME, slackBusSelectionMode); map.put(SLACK_BUSES_IDS_PARAM_NAME, slackBusesIds); map.put(SLACK_DISTRIBUTION_FAILURE_BEHAVIOR_PARAM_NAME, slackDistributionFailureBehavior); @@ -1637,6 +1655,7 @@ public Map toMap() { map.put(AREA_INTERCHANGE_CONTROL_PARAM_NAME, areaInterchangeControl); map.put(AREA_INTERCHANGE_CONTROL_AREA_TYPE_PARAM_NAME, areaInterchangeControlAreaType); map.put(AREA_INTERCHANGE_P_MAX_MISMATCH_PARAM_NAME, areaInterchangePMaxMismatch); + map.put(FIX_REMOTE_VOLTAGE_TARGET_PARAM_NAME, fixRemoteVoltageTarget); return map; } @@ -1751,7 +1770,8 @@ static VoltageInitializer getExtendedVoltageInitializer(LoadFlowParameters param } static LfNetworkParameters getNetworkParameters(LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt, - SlackBusSelector slackBusSelector, GraphConnectivityFactory connectivityFactory, + SlackBusSelector slackBusSelector, MatrixFactory matrixFactory, + GraphConnectivityFactory connectivityFactory, boolean breakers) { return new LfNetworkParameters() .setSlackBusSelector(slackBusSelector) @@ -1837,7 +1857,7 @@ public static AcLoadFlowParameters createAcParameters(LoadFlowParameters paramet SlackBusSelector slackBusSelector = SlackBusSelector.fromMode(parametersExt.getSlackBusSelectionMode(), parametersExt.getSlackBusesIds(), parametersExt.getPlausibleActivePowerLimit(), parametersExt.getMostMeshedSlackBusSelectorMaxNominalVoltagePercentile(), parametersExt.getSlackBusCountryFilter()); - var networkParameters = getNetworkParameters(parameters, parametersExt, slackBusSelector, connectivityFactory, breakers); + var networkParameters = getNetworkParameters(parameters, parametersExt, slackBusSelector, matrixFactory, connectivityFactory, breakers); var equationSystemCreationParameters = new AcEquationSystemCreationParameters(forceA1Var); @@ -1877,7 +1897,8 @@ public static AcLoadFlowParameters createAcParameters(LoadFlowParameters paramet .setVoltageInitializer(voltageInitializer) .setAsymmetrical(parametersExt.isAsymmetrical()) .setSlackDistributionFailureBehavior(parametersExt.getSlackDistributionFailureBehavior()) - .setSolverFactory(solverFactory); + .setSolverFactory(solverFactory) + .setFixRemoteVoltageTarget(parametersExt.isFixRemoteVoltageTarget()); } public static DcLoadFlowParameters createDcParameters(Network network, LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt, @@ -1941,6 +1962,14 @@ public static DcLoadFlowParameters createDcParameters(LoadFlowParameters paramet .setMaxOuterLoopIterations(parametersExt.getMaxOuterLoopIterations()); } + static GraphConnectivityFactory getConnectivityFactory(OpenLoadFlowParameters parametersExt, + GraphConnectivityFactory defaultConnectivityFactory) { + return parametersExt.isNetworkCacheEnabled() && !parametersExt.getActionableSwitchesIds().isEmpty() + || parametersExt.isSimulateAutomationSystems() + ? new NaiveGraphConnectivityFactory<>(LfBus::getNum) + : defaultConnectivityFactory; + } + public static boolean equals(LoadFlowParameters parameters1, LoadFlowParameters parameters2) { Objects.requireNonNull(parameters1); Objects.requireNonNull(parameters2); @@ -2046,7 +2075,8 @@ public static boolean equals(LoadFlowParameters parameters1, LoadFlowParameters extension1.getFictitiousGeneratorVoltageControlCheckMode() == extension2.getFictitiousGeneratorVoltageControlCheckMode() && extension1.isAreaInterchangeControl() == extension2.isAreaInterchangeControl() && Objects.equals(extension1.getAreaInterchangeControlAreaType(), extension2.getAreaInterchangeControlAreaType()) && - extension1.getAreaInterchangePMaxMismatch() == extension2.getAreaInterchangePMaxMismatch(); + extension1.getAreaInterchangePMaxMismatch() == extension2.getAreaInterchangePMaxMismatch() && + extension1.isFixRemoteVoltageTarget() == extension2.isFixRemoteVoltageTarget(); } public static LoadFlowParameters clone(LoadFlowParameters parameters) { @@ -2142,7 +2172,8 @@ public static LoadFlowParameters clone(LoadFlowParameters parameters) { .setFictitiousGeneratorVoltageControlCheckMode(extension.getFictitiousGeneratorVoltageControlCheckMode()) .setAreaInterchangeControl(extension.isAreaInterchangeControl()) .setAreaInterchangeControlAreaType(extension.getAreaInterchangeControlAreaType()) - .setAreaInterchangePMaxMismatch(extension.getAreaInterchangePMaxMismatch()); + .setAreaInterchangePMaxMismatch(extension.getAreaInterchangePMaxMismatch()) + .setFixRemoteVoltageTarget(extension.isFixRemoteVoltageTarget()); if (extension2 != null) { parameters2.addExtension(OpenLoadFlowParameters.class, extension2); diff --git a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowProvider.java b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowProvider.java index ebcbc39813..d21a178ed2 100644 --- a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowProvider.java +++ b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowProvider.java @@ -32,7 +32,6 @@ import com.powsybl.openloadflow.dc.DcLoadFlowResult; import com.powsybl.openloadflow.graph.EvenShiloachGraphDecrementalConnectivityFactory; import com.powsybl.openloadflow.graph.GraphConnectivityFactory; -import com.powsybl.openloadflow.graph.NaiveGraphConnectivityFactory; import com.powsybl.openloadflow.lf.AbstractLoadFlowResult; import com.powsybl.openloadflow.lf.outerloop.OuterLoop; import com.powsybl.openloadflow.network.*; @@ -93,13 +92,6 @@ public String getVersion() { return new PowsyblCoreVersion().getMavenProjectVersion(); } - private GraphConnectivityFactory getConnectivityFactory(OpenLoadFlowParameters parametersExt) { - return parametersExt.isNetworkCacheEnabled() && !parametersExt.getActionableSwitchesIds().isEmpty() - || parametersExt.isSimulateAutomationSystems() - ? new NaiveGraphConnectivityFactory<>(LfBus::getNum) - : connectivityFactory; - } - private void updateAcState(Network network, LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt, AcLoadFlowResult result, AcLoadFlowParameters acParameters, boolean atLeastOneComponentHasToBeUpdated) { if (parametersExt.isNetworkCacheEnabled()) { @@ -134,7 +126,7 @@ private void updateAcState(Network network, LoadFlowParameters parameters, OpenL } private LoadFlowResult runAc(Network network, LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt, ReportNode reportNode) { - GraphConnectivityFactory selectedConnectivityFactory = getConnectivityFactory(parametersExt); + GraphConnectivityFactory selectedConnectivityFactory = OpenLoadFlowParameters.getConnectivityFactory(parametersExt, connectivityFactory); AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network, parameters, parametersExt, matrixFactory, selectedConnectivityFactory); acParameters.setDetailedReport(parametersExt.getReportedFeatures().contains(OpenLoadFlowParameters.ReportedFeatures.NEWTON_RAPHSON_LOAD_FLOW)); diff --git a/src/main/java/com/powsybl/openloadflow/RemoteVoltageTargetCheckResult.java b/src/main/java/com/powsybl/openloadflow/RemoteVoltageTargetCheckResult.java new file mode 100644 index 0000000000..f375f97fb1 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/RemoteVoltageTargetCheckResult.java @@ -0,0 +1,37 @@ +/** + * 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; + +import com.powsybl.openloadflow.network.LfBus; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class RemoteVoltageTargetCheckResult { + + public record IncompatibleTarget(LfBus controlledBus1, LfBus controlledBus2, double targetVoltagePlausibilityIndicator) { + } + + public record UnrealisticTarget(LfBus controllerBus, double estimatedDvController) { + } + + private final List incompatibleTargets = new ArrayList<>(); + + private final List unrealisticTargets = new ArrayList<>(); + + public List getIncompatibleTargets() { + return incompatibleTargets; + } + + public List getUnrealisticTargets() { + return unrealisticTargets; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/RemoteVoltageTargetChecker.java b/src/main/java/com/powsybl/openloadflow/RemoteVoltageTargetChecker.java new file mode 100644 index 0000000000..c476d1984c --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/RemoteVoltageTargetChecker.java @@ -0,0 +1,270 @@ +/** + * 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; + +import com.google.common.base.Stopwatch; +import com.powsybl.iidm.network.Network; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.math.matrix.DenseMatrix; +import com.powsybl.math.matrix.MatrixFactory; +import com.powsybl.math.matrix.SparseMatrixFactory; +import com.powsybl.openloadflow.ac.AcJacobianMatrix; +import com.powsybl.openloadflow.ac.AcLoadFlowParameters; +import com.powsybl.openloadflow.ac.equations.AcEquationSystemCreator; +import com.powsybl.openloadflow.ac.equations.AcEquationType; +import com.powsybl.openloadflow.ac.equations.AcVariableType; +import com.powsybl.openloadflow.ac.solver.AcSolverUtil; +import com.powsybl.openloadflow.adm.AdmittanceEquationSystem; +import com.powsybl.openloadflow.adm.AdmittanceMatrix; +import com.powsybl.openloadflow.equations.EquationSystem; +import com.powsybl.openloadflow.equations.EquationTerm; +import com.powsybl.openloadflow.equations.JacobianMatrix; +import com.powsybl.openloadflow.equations.VariableSet; +import com.powsybl.openloadflow.graph.EvenShiloachGraphDecrementalConnectivityFactory; +import com.powsybl.openloadflow.graph.GraphConnectivityFactory; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.impl.LfNetworkLoaderImpl; +import com.powsybl.openloadflow.network.util.UniformValueVoltageInitializer; +import org.apache.commons.lang3.mutable.MutableInt; +import org.jgrapht.Graph; +import org.jgrapht.graph.Pseudograph; +import org.jgrapht.traverse.BreadthFirstIterator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class RemoteVoltageTargetChecker { + + private static final Logger LOGGER = LoggerFactory.getLogger(RemoteVoltageTargetChecker.class); + + private final LfNetwork network; + + private final EquationSystem equationSystem; + + private final JacobianMatrix j; + + public RemoteVoltageTargetChecker(LfNetwork network, + EquationSystem equationSystem, + JacobianMatrix j) { + this.network = Objects.requireNonNull(network); + this.equationSystem = Objects.requireNonNull(equationSystem); + this.j = Objects.requireNonNull(j); + } + + public static Set findElementsToDiscardFromVoltageControl(Network network, LoadFlowParameters parameters) { + return findElementsToDiscardFromVoltageControl(network, parameters, new SparseMatrixFactory()); + } + + public static Set findElementsToDiscardFromVoltageControl(Network network, LoadFlowParameters parameters, MatrixFactory matrixFactory) { + Objects.requireNonNull(network); + Objects.requireNonNull(parameters); + Set elementIds = new TreeSet<>(); + OpenLoadFlowParameters parametersExt = OpenLoadFlowParameters.get(parameters); + GraphConnectivityFactory selectedConnectivityFactory = OpenLoadFlowParameters.getConnectivityFactory(parametersExt, new EvenShiloachGraphDecrementalConnectivityFactory<>()); + AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network, parameters, parametersExt, matrixFactory, selectedConnectivityFactory); + for (LfNetwork lfNetwork : LfNetwork.load(network, new LfNetworkLoaderImpl(), acParameters.getNetworkParameters())) { + var equationSystem = new AcEquationSystemCreator(lfNetwork, acParameters.getEquationSystemCreationParameters()) + .create(); + try (var j = new AcJacobianMatrix(equationSystem, matrixFactory, lfNetwork)) { + var result = new RemoteVoltageTargetChecker(lfNetwork, equationSystem, j) + .check(new RemoteVoltageTargetCheckerParameters(acParameters.getMatrixFactory())); + for (var incompatibleTarget : result.getIncompatibleTargets()) { + LfBus controlledBus1 = incompatibleTarget.controlledBus1(); + LfBus controlledBus2 = incompatibleTarget.controlledBus2(); + for (LfBus controlledBus : List.of(controlledBus1, controlledBus2)) { + controlledBus.getGeneratorVoltageControl() + .ifPresent(vc -> elementIds.addAll(vc.getControllerElements().stream() + .flatMap(c -> c.getGenerators().stream()) + .map(LfGenerator::getOriginalId) + .toList())); + controlledBus.getShuntVoltageControl() + .ifPresent(vc -> elementIds.addAll(vc.getControllerElements().stream() + .flatMap(s -> s.getOriginalIds().stream()) + .toList())); + controlledBus.getTransformerVoltageControl() + .ifPresent(vc -> elementIds.addAll(vc.getControllerElements().stream() + .flatMap(t -> t.getOriginalIds().stream()) + .toList())); + } + } + for (var unrealisticTarget : result.getUnrealisticTargets()) { + elementIds.addAll(unrealisticTarget.controllerBus().getGenerators().stream().map(LfGenerator::getOriginalId).toList()); + } + } + } + return elementIds; + } + + private static Graph createGraph(LfNetwork lfNetwork) { + Graph graph = new Pseudograph<>(LfBranch.class); + for (LfBranch branch : lfNetwork.getBranches()) { + LfBus bus1 = branch.getBus1(); + LfBus bus2 = branch.getBus2(); + if (bus1 != null && bus2 != null && !branch.isDisabled()) { + if (!graph.containsVertex(bus1)) { + graph.addVertex(bus1); + } + if (!graph.containsVertex(bus2)) { + graph.addVertex(bus2); + } + graph.addEdge(bus1, bus2, branch); + } + } + return graph; + } + + private static Set exploreNeighbors(Graph graph, LfBus bus, int maxDepth) { + var it = new BreadthFirstIterator<>(graph, bus); + Set neighbors = new HashSet<>(); + while (it.hasNext()) { + LfBus b = it.next(); + int currentDepth = it.getDepth(b); + if (currentDepth > maxDepth) { + break; + } + if (b != bus) { + neighbors.add(b); + } + } + return neighbors; + } + + private void checkIncompatibleTargets(RemoteVoltageTargetCheckerParameters parameters, + Set controlledBuses, + RemoteVoltageTargetCheckResult result) { + Graph graph = createGraph(network); + + var ySystem = AdmittanceEquationSystem.create(network, new VariableSet<>()); + try (var y = AdmittanceMatrix.create(ySystem, parameters.getMatrixFactory())) { + for (LfBus controlledBus : controlledBuses) { + Set neighborControlledBuses = exploreNeighbors(graph, controlledBus, parameters.getControlledBusNeighborsExplorationDepth()) + .stream().filter(controlledBuses::contains) + .collect(Collectors.toSet()); + if (!neighborControlledBuses.isEmpty()) { + for (LfBus neighborControlledBus : neighborControlledBuses) { + double z = y.getZ(controlledBus, neighborControlledBus).abs(); + double dv = Math.abs(controlledBus.getHighestPriorityTargetV().orElseThrow() - neighborControlledBus.getHighestPriorityTargetV().orElseThrow()); + double targetVoltagePlausibilityIndicator = dv / z; + if (targetVoltagePlausibilityIndicator > parameters.getTargetVoltagePlausibilityIndicatorThreshold()) { + result.getIncompatibleTargets().add(new RemoteVoltageTargetCheckResult.IncompatibleTarget(controlledBus, neighborControlledBus, targetVoltagePlausibilityIndicator)); + } + } + } + } + } + } + + @SuppressWarnings("unchecked") + private EquationTerm getCalculatedV(LfBus controllerBus) { + return (EquationTerm) controllerBus.getCalculatedV(); + } + + private void checkUnrealisticTargets(RemoteVoltageTargetCheckerParameters parameters, + List generatorControlledBuses, + RemoteVoltageTargetCheckResult result) { + AcSolverUtil.initStateVector(network, equationSystem, new UniformValueVoltageInitializer()); + + // calculate target voltage to calculated voltage sensibilities + var busNumToSensiColumn = LfBus.buildIndex(new ArrayList<>(generatorControlledBuses)); + DenseMatrix rhs = new DenseMatrix(equationSystem.getIndex().getSortedEquationsToSolve().size(), generatorControlledBuses.size()); + for (LfBus controlledBus : generatorControlledBuses) { + equationSystem.getEquation(controlledBus.getNum(), AcEquationType.BUS_TARGET_V) + .ifPresent(equation -> rhs.set(equation.getColumn(), busNumToSensiColumn.get(controlledBus.getNum()), 1d)); + } + j.solveTransposed(rhs); + + for (LfBus controlledBus : generatorControlledBuses) { + int controlledBusSensiColumn = busNumToSensiColumn.get(controlledBus.getNum()); + GeneratorVoltageControl voltageControl = controlledBus.getGeneratorVoltageControl().orElseThrow(); + if (!voltageControl.isLocalControl()) { + for (LfBus controllerBus : voltageControl.getControllerElements()) { + var term = getCalculatedV(controllerBus); + double sensiVv = term.calculateSensi(rhs, controlledBusSensiColumn); + // this is the targe voltage shift from a normal value (1 pu) + double dvControlled = voltageControl.getTargetValue() - 1.0; + // thanks to the sensitivity, compute the corresponding controller bus side + // resulting calculated voltage + double estimatedDvController = dvControlled * sensiVv; + // check if not too far from 1 pu + if (Math.abs(estimatedDvController) > parameters.getControllerBusAcceptableVoltageDrop()) { + result.getUnrealisticTargets().add(new RemoteVoltageTargetCheckResult.UnrealisticTarget(controllerBus, estimatedDvController)); + } + } + } + } + } + + public RemoteVoltageTargetCheckResult check(RemoteVoltageTargetCheckerParameters parameters) { + Stopwatch stopwatch = Stopwatch.createStarted(); + + RemoteVoltageTargetCheckResult result = new RemoteVoltageTargetCheckResult(); + + // controlled buses whatever the voltage control type it is + Set controlledBuses = network.getBuses().stream() + .filter(bus -> !bus.isDisabled() + && bus.isVoltageControlled() + && bus.getVoltageControls().stream().anyMatch(vc -> !vc.isDisabled())) + .collect(Collectors.toSet()); + checkIncompatibleTargets(parameters, controlledBuses, result); + + // for this check we only keep generator voltage controls + List generatorControlledBuses = controlledBuses.stream() + .filter(LfBus::isGeneratorVoltageControlled) + .toList(); + checkUnrealisticTargets(parameters, generatorControlledBuses, result); + + LOGGER.debug("Remote voltage targets checked in {} ms", stopwatch.elapsed().toMillis()); + + return result; + } + + public void fix(RemoteVoltageTargetCheckerParameters parameters) { + RemoteVoltageTargetCheckResult result = check(parameters); + + // some buses could be part of multiple target v incompatible buses couples + // fix the most referenced ones + Map incompatibleControlledBusRefCount = new HashMap<>(); + for (RemoteVoltageTargetCheckResult.IncompatibleTarget incompatibleTarget : result.getIncompatibleTargets()) { + incompatibleControlledBusRefCount.computeIfAbsent(incompatibleTarget.controlledBus1(), k -> new MutableInt(0)).increment(); + incompatibleControlledBusRefCount.computeIfAbsent(incompatibleTarget.controlledBus2(), k -> new MutableInt(0)).increment(); + } + Set fixedControlledBuses = new HashSet<>(); + for (RemoteVoltageTargetCheckResult.IncompatibleTarget incompatibleTarget : result.getIncompatibleTargets()) { + LfBus controlledBus1 = incompatibleTarget.controlledBus1(); + LfBus controlledBus2 = incompatibleTarget.controlledBus2(); + if (fixedControlledBuses.contains(controlledBus1) || fixedControlledBuses.contains(controlledBus2)) { + continue; + } + LfBus controlledBusToFix = incompatibleControlledBusRefCount.get(controlledBus1).intValue() > incompatibleControlledBusRefCount.get(controlledBus2).intValue() + ? controlledBus1 : controlledBus2; + for (VoltageControl voltageControl : controlledBusToFix.getVoltageControls()) { + LOGGER.warn("Controlled buses '{}' and '{}' have incompatible target voltages (plausibility indicator: {}): disable controller elements {}", + controlledBus1.getId(), controlledBus2.getId(), incompatibleTarget.targetVoltagePlausibilityIndicator(), + voltageControl.getControllerElements().stream().map(LfElement::getId).toList()); + for (var controllerElement : voltageControl.getControllerElements()) { + controllerElement.setVoltageControlEnabled(false); + } + fixedControlledBuses.add(controlledBusToFix); + } + } + + for (RemoteVoltageTargetCheckResult.UnrealisticTarget unrealisticTarget : result.getUnrealisticTargets()) { + LfBus controllerBus = unrealisticTarget.controllerBus(); + LfBus controlledBus = controllerBus.getGeneratorVoltageControl().orElseThrow().getControlledBus(); + LOGGER.warn("Controlled bus '{}' has an unrealistic target voltage {} Kv, causing a severe controller bus '{}' voltage drop (estimated at {} pu): disable controller bus", + controlledBus.getId(), controlledBus.getHighestPriorityTargetV().orElseThrow() * controlledBus.getNominalV(), + controllerBus.getId(), unrealisticTarget.estimatedDvController()); + controllerBus.setGeneratorVoltageControlEnabled(false); + } + } +} diff --git a/src/main/java/com/powsybl/openloadflow/RemoteVoltageTargetCheckerParameters.java b/src/main/java/com/powsybl/openloadflow/RemoteVoltageTargetCheckerParameters.java new file mode 100644 index 0000000000..f635d1f6f1 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/RemoteVoltageTargetCheckerParameters.java @@ -0,0 +1,80 @@ +/** + * 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; + +import com.powsybl.math.matrix.MatrixFactory; + +import java.util.Objects; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class RemoteVoltageTargetCheckerParameters { + + public static final int CONTROLLED_BUS_NEIGHBORS_EXPLORATION_DEPTH_DEFAULT_VALUE = 2; + + public static final double TARGET_VOLTAGE_PLAUSIBILITY_THRESHOLD_DEFAULT_VALUE = 10; + + public static final double CONTROLLER_BUS_ACCEPTABLE_VOLTAGE_SHIFT_DEFAULT_VALUE = 0.8; + + private MatrixFactory matrixFactory; + + private int controlledBusNeighborsExplorationDepth = CONTROLLED_BUS_NEIGHBORS_EXPLORATION_DEPTH_DEFAULT_VALUE; + + private double targetVoltagePlausibilityIndicatorThreshold = TARGET_VOLTAGE_PLAUSIBILITY_THRESHOLD_DEFAULT_VALUE; + + private double controllerBusAcceptableVoltageDrop = CONTROLLER_BUS_ACCEPTABLE_VOLTAGE_SHIFT_DEFAULT_VALUE; // shift from 1 pu + + public RemoteVoltageTargetCheckerParameters(MatrixFactory matrixFactory) { + this.matrixFactory = Objects.requireNonNull(matrixFactory); + } + + public MatrixFactory getMatrixFactory() { + return matrixFactory; + } + + public void setMatrixFactory(MatrixFactory matrixFactory) { + this.matrixFactory = Objects.requireNonNull(matrixFactory); + } + + public int getControlledBusNeighborsExplorationDepth() { + return controlledBusNeighborsExplorationDepth; + } + + public RemoteVoltageTargetCheckerParameters setControlledBusNeighborsExplorationDepth(int controlledBusNeighborsExplorationDepth) { + this.controlledBusNeighborsExplorationDepth = controlledBusNeighborsExplorationDepth; + return this; + } + + public double getTargetVoltagePlausibilityIndicatorThreshold() { + return targetVoltagePlausibilityIndicatorThreshold; + } + + public RemoteVoltageTargetCheckerParameters setTargetVoltagePlausibilityIndicatorThreshold(double targetVoltagePlausibilityIndicatorThreshold) { + this.targetVoltagePlausibilityIndicatorThreshold = targetVoltagePlausibilityIndicatorThreshold; + return this; + } + + public double getControllerBusAcceptableVoltageDrop() { + return controllerBusAcceptableVoltageDrop; + } + + public RemoteVoltageTargetCheckerParameters setControllerBusAcceptableVoltageDrop(double controllerBusAcceptableVoltageDrop) { + this.controllerBusAcceptableVoltageDrop = controllerBusAcceptableVoltageDrop; + return this; + } + + public String toString() { + return "TargetVoltageCompatibilityCheckerParameters(" + + "controlledBusNeighborsExplorationDepth=" + controlledBusNeighborsExplorationDepth + + ", targetVoltagePlausibilityIndicatorThreshold=" + targetVoltagePlausibilityIndicatorThreshold + + ", controllerBusAcceptableVoltageDrop=" + controllerBusAcceptableVoltageDrop + + ", matrixFactory=" + matrixFactory.getClass().getSimpleName() + + ")"; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/ac/AcLoadFlowParameters.java b/src/main/java/com/powsybl/openloadflow/ac/AcLoadFlowParameters.java index 8d639b80d7..d9489b3759 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/AcLoadFlowParameters.java +++ b/src/main/java/com/powsybl/openloadflow/ac/AcLoadFlowParameters.java @@ -42,12 +42,16 @@ public class AcLoadFlowParameters extends AbstractLoadFlowParameters + * @author Geoffroy Jamgotchian + */ +public final class AdmittanceEquationSystem { + + private static final double B_EPSILON = 1e-8; + + private final EquationSystem equationSystem; + + private AdmittanceEquationSystem(EquationSystem equationSystem) { + this.equationSystem = Objects.requireNonNull(equationSystem); + } + + public EquationSystem getEquationSystem() { + return equationSystem; + } + + //Equations are created based on the branches connections + private static void createBranchEquation(VariableSet variableSet, EquationSystem equationSystem, + LfBranch branch, LfBus bus1, LfBus bus2) { + if (bus1 != null && bus2 != null) { + // Equation system Y*V = I (expressed in cartesian coordinates x,y) + equationSystem.createEquation(bus1.getNum(), AdmittanceEquationType.BUS_ADM_IX) + .addTerm(new AdmittanceEquationTermBranchI1x(branch, bus1, bus2, variableSet)); + + equationSystem.createEquation(bus1.getNum(), AdmittanceEquationType.BUS_ADM_IY) + .addTerm(new AdmittanceEquationTermBranchI1y(branch, bus1, bus2, variableSet)); + + equationSystem.createEquation(bus2.getNum(), AdmittanceEquationType.BUS_ADM_IX) + .addTerm(new AdmittanceEquationTermBranchI2x(branch, bus1, bus2, variableSet)); + + equationSystem.createEquation(bus2.getNum(), AdmittanceEquationType.BUS_ADM_IY) + .addTerm(new AdmittanceEquationTermBranchI2y(branch, bus1, bus2, variableSet)); + } + } + + private static void createBranchEquations(Collection branches, VariableSet variableSet, EquationSystem equationSystem) { + for (LfBranch branch : branches) { + LfBus bus1 = branch.getBus1(); + LfBus bus2 = branch.getBus2(); + createBranchEquation(variableSet, equationSystem, branch, bus1, bus2); + } + } + + private static double getShuntB(LfBus bus) { + LfShunt shunt = bus.getShunt().orElse(null); + double b = 0; + if (shunt != null) { + b += shunt.getB(); + } + LfShunt controllerShunt = bus.getControllerShunt().orElse(null); + if (controllerShunt != null) { + b += controllerShunt.getB(); + } + return b; + } + + private static void createShuntEquations(Collection buses, VariableSet variableSet, EquationSystem equationSystem) { + for (LfBus bus : buses) { + double b = getShuntB(bus); + if (Math.abs(b) > B_EPSILON) { + equationSystem.createEquation(bus.getNum(), AdmittanceEquationType.BUS_ADM_IX) + .addTerm(new AdmittanceEquationTermShunt(bus, variableSet, 0, b, true)); + equationSystem.createEquation(bus.getNum(), AdmittanceEquationType.BUS_ADM_IY) + .addTerm(new AdmittanceEquationTermShunt(bus, variableSet, 0, b, false)); + } + } + } + + public static AdmittanceEquationSystem create(LfNetwork network, VariableSet variableSet) { + return create(network.getBuses(), network.getBranches(), variableSet); + } + + public static AdmittanceEquationSystem create(Collection buses, Collection branches, VariableSet variableSet) { + EquationSystem equationSystem = new EquationSystem<>(); + + createBranchEquations(branches, variableSet, equationSystem); + createShuntEquations(buses, variableSet, equationSystem); + + return new AdmittanceEquationSystem(equationSystem); + } +} diff --git a/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI1x.java b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI1x.java new file mode 100644 index 0000000000..438b566bd1 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI1x.java @@ -0,0 +1,58 @@ +/** + * 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.adm; + +import com.powsybl.openloadflow.equations.Variable; +import com.powsybl.openloadflow.equations.VariableSet; +import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfBus; + +/** + * i1x = (g1 + g12) * v1x - (b1 + b12) * v1y - g12 * v2x + b12 * v2y + * + * @author Jean-Baptiste Heyberger + * @author Geoffroy Jamgotchian + */ +public class AdmittanceEquationTermBranchI1x extends AbstractAdmittanceEquationTerm { + + private final double g12; + + private final double b12; + + private final double g1g12sum; + + private final double b1b12sum; + + public AdmittanceEquationTermBranchI1x(LfBranch branch, LfBus bus1, LfBus bus2, VariableSet variableSet) { + super(branch, bus1, bus2, variableSet); + g12 = rho * zInvSquare * (r * cosA + x * sinA); + b12 = -rho * zInvSquare * (x * cosA + r * sinA); + g1g12sum = rho * rho * (gPi1 + r * zInvSquare); + b1b12sum = rho * rho * (bPi1 - x * zInvSquare); + } + + @Override + public double der(Variable variable) { + if (variable.equals(v1xVar)) { + return g1g12sum; + } else if (variable.equals(v2xVar)) { + return -g12; + } else if (variable.equals(v1yVar)) { + return -b1b12sum; + } else if (variable.equals(v2yVar)) { + return b12; + } else { + throw new IllegalArgumentException("Unknown variable " + variable); + } + } + + @Override + protected String getName() { + return "adm_ix1"; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI1y.java b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI1y.java new file mode 100644 index 0000000000..1aa5b79491 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI1y.java @@ -0,0 +1,58 @@ +/** + * 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.adm; + +import com.powsybl.openloadflow.equations.Variable; +import com.powsybl.openloadflow.equations.VariableSet; +import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfBus; + +/** + * i1y = (b1 + b12) * v1x + (g1 + g12) * v1y - b12 * v2x - g12 * v2y + * + * @author Jean-Baptiste Heyberger + * @author Geoffroy Jamgotchian + */ +public class AdmittanceEquationTermBranchI1y extends AbstractAdmittanceEquationTerm { + + private final double g12; + + private final double b12; + + private final double g1g12sum; + + private final double b1b12sum; + + public AdmittanceEquationTermBranchI1y(LfBranch branch, LfBus bus1, LfBus bus2, VariableSet variableSet) { + super(branch, bus1, bus2, variableSet); + g12 = rho * zInvSquare * (r * cosA + x * sinA); + b12 = -rho * zInvSquare * (x * cosA + r * sinA); + g1g12sum = rho * rho * (gPi1 + r * zInvSquare); + b1b12sum = rho * rho * (bPi1 - x * zInvSquare); + } + + @Override + public double der(Variable variable) { + if (variable.equals(v1xVar)) { + return b1b12sum; + } else if (variable.equals(v2xVar)) { + return -b12; + } else if (variable.equals(v1yVar)) { + return g1g12sum; + } else if (variable.equals(v2yVar)) { + return -g12; + } else { + throw new IllegalArgumentException("Unknown variable " + variable); + } + } + + @Override + protected String getName() { + return "adm_i1y"; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI2x.java b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI2x.java new file mode 100644 index 0000000000..de291cc663 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI2x.java @@ -0,0 +1,59 @@ +/** + * 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.adm; + +import com.powsybl.openloadflow.equations.Variable; +import com.powsybl.openloadflow.equations.VariableSet; +import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfBus; + +/** + * i2x = -g21 * v1x + b21 * v1y + (g2 + g21) * v2x - (b2 + b21) * v2y + * + * @author Jean-Baptiste Heyberger + * @author Geoffroy Jamgotchian + */ +public class AdmittanceEquationTermBranchI2x extends AbstractAdmittanceEquationTerm { + + private final double g21; + + private final double b21; + + private final double g2g21sum; + + private final double b2b21sum; + + public AdmittanceEquationTermBranchI2x(LfBranch branch, LfBus bus1, LfBus bus2, VariableSet variableSet) { + super(branch, bus1, bus2, variableSet); + double g12 = rho * zInvSquare * (r * cosA + x * sinA); + g21 = g12; + b21 = rho * zInvSquare * (r * sinA - x * cosA); + g2g21sum = r * zInvSquare + gPi2; + b2b21sum = -x * zInvSquare + bPi2; + } + + @Override + public double der(Variable variable) { + if (variable.equals(v1xVar)) { + return -g21; + } else if (variable.equals(v2xVar)) { + return g2g21sum; + } else if (variable.equals(v1yVar)) { + return b21; + } else if (variable.equals(v2yVar)) { + return -b2b21sum; + } else { + throw new IllegalArgumentException("Unknown variable " + variable); + } + } + + @Override + protected String getName() { + return "adm_i2x"; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI2y.java b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI2y.java new file mode 100644 index 0000000000..abeb2d5f52 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermBranchI2y.java @@ -0,0 +1,59 @@ +/** + * 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.adm; + +import com.powsybl.openloadflow.equations.Variable; +import com.powsybl.openloadflow.equations.VariableSet; +import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfBus; + +/** + * i2y = -b21 * v1x - g21 * v1y + (b2 + b21) * v2x + (g2 + g21) * v2y + * + * @author Jean-Baptiste Heyberger + * @author Geoffroy Jamgotchian + */ +public class AdmittanceEquationTermBranchI2y extends AbstractAdmittanceEquationTerm { + + private final double g21; + + private final double b21; + + private final double g2g21sum; + + private final double b2b21sum; + + public AdmittanceEquationTermBranchI2y(LfBranch branch, LfBus bus1, LfBus bus2, VariableSet variableSet) { + super(branch, bus1, bus2, variableSet); + double g12 = rho * zInvSquare * (r * cosA + x * sinA); + g21 = g12; + b21 = rho * zInvSquare * (r * sinA - x * cosA); + g2g21sum = r * zInvSquare + gPi2; + b2b21sum = -x * zInvSquare + bPi2; + } + + @Override + public double der(Variable variable) { + if (variable.equals(v1xVar)) { + return -b21; + } else if (variable.equals(v2xVar)) { + return b2b21sum; + } else if (variable.equals(v1yVar)) { + return -g21; + } else if (variable.equals(v2yVar)) { + return g2g21sum; + } else { + throw new IllegalArgumentException("Unknown variable " + variable); + } + } + + @Override + protected String getName() { + return "adm_i2y"; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermShunt.java b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermShunt.java new file mode 100644 index 0000000000..a5787e65a0 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationTermShunt.java @@ -0,0 +1,78 @@ +/** + * 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.adm; + +import com.powsybl.openloadflow.equations.AbstractElementEquationTerm; +import com.powsybl.openloadflow.equations.Variable; +import com.powsybl.openloadflow.equations.VariableSet; +import com.powsybl.openloadflow.network.LfBus; + +import java.util.List; +import java.util.Objects; + +/** + * v1x v1y + * | | + * i1x - [ g -b ] + * i2y - [ b g ] + * + * @author Jean-Baptiste Heyberger + * @author Geoffroy Jamgotchian + */ +public class AdmittanceEquationTermShunt extends AbstractElementEquationTerm { + + protected final Variable v1xVar; + + protected final Variable v1yVar; + + protected final List> variables; + + protected double g; + + protected double b; + + protected boolean real; + + public AdmittanceEquationTermShunt(LfBus bus, VariableSet variableSet, double g, double b, boolean real) { + super(bus); + Objects.requireNonNull(variableSet); + + v1xVar = variableSet.getVariable(bus.getNum(), AdmittanceVariableType.BUS_ADM_VX); + v1yVar = variableSet.getVariable(bus.getNum(), AdmittanceVariableType.BUS_ADM_VY); + variables = List.of(v1xVar, v1yVar); + + this.g = g; + this.b = b; + this.real = real; + } + + public List> getVariables() { + return variables; + } + + @Override + public double eval() { + throw new UnsupportedOperationException("Not needed"); + } + + @Override + public double der(Variable variable) { + if (variable.equals(v1xVar)) { + return real ? g : b; + } else if (variable.equals(v1yVar)) { + return real ? -b : g; + } else { + throw new IllegalArgumentException("Unknown variable " + variable); + } + } + + @Override + protected String getName() { + return "adm_shunt"; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationType.java b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationType.java new file mode 100644 index 0000000000..7bb6f69bb5 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceEquationType.java @@ -0,0 +1,35 @@ +/** + * 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.adm; + +import com.powsybl.openloadflow.equations.Quantity; +import com.powsybl.openloadflow.network.ElementType; + +/** + * @author Geoffroy Jamgotchian + */ +public enum AdmittanceEquationType implements Quantity { + BUS_ADM_IX("ix"), // real part + BUS_ADM_IY("iy"); // imaginary part + + private final String symbol; + + AdmittanceEquationType(String symbol) { + this.symbol = symbol; + } + + @Override + public String getSymbol() { + return symbol; + } + + @Override + public ElementType getElementType() { + return ElementType.BUS; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/adm/AdmittanceMatrix.java b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceMatrix.java new file mode 100644 index 0000000000..501580c48c --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceMatrix.java @@ -0,0 +1,94 @@ +/** + * 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.adm; + +import com.powsybl.math.matrix.DenseMatrix; +import com.powsybl.math.matrix.LUDecomposition; +import com.powsybl.math.matrix.Matrix; +import com.powsybl.math.matrix.MatrixFactory; +import com.powsybl.openloadflow.equations.EquationSystem; +import com.powsybl.openloadflow.equations.JacobianMatrix; +import com.powsybl.openloadflow.network.LfBus; +import org.apache.commons.math3.complex.Complex; + +import java.util.Objects; + +/** + * @author Geoffroy Jamgotchian + */ +public class AdmittanceMatrix implements AutoCloseable { + + private final EquationSystem equationSystem; + + private final Matrix matrix; + + private LUDecomposition lu; + + private DenseMatrix e; // impedance extractor + + public AdmittanceMatrix(EquationSystem equationSystem, Matrix matrix) { + this.equationSystem = Objects.requireNonNull(equationSystem); + this.matrix = Objects.requireNonNull(matrix); + } + + public static AdmittanceMatrix create(AdmittanceEquationSystem ySystem, MatrixFactory matrixFactory) { + Objects.requireNonNull(matrixFactory); + try (var j = new JacobianMatrix<>(ySystem.getEquationSystem(), matrixFactory)) { + return new AdmittanceMatrix(ySystem.getEquationSystem(), j.getMatrix()); + } + } + + public Matrix getMatrix() { + return matrix; + } + + /** + * Get equivalent impedance between 2 buses. + */ + public Complex getZ(LfBus bus1, LfBus bus2) { + Objects.requireNonNull(bus1); + Objects.requireNonNull(bus2); + var i1xEq = equationSystem.getEquation(bus1.getNum(), AdmittanceEquationType.BUS_ADM_IX).orElseThrow(); + var i1yEq = equationSystem.getEquation(bus1.getNum(), AdmittanceEquationType.BUS_ADM_IY).orElseThrow(); + var i2xEq = equationSystem.getEquation(bus2.getNum(), AdmittanceEquationType.BUS_ADM_IX).orElseThrow(); + var i2yEq = equationSystem.getEquation(bus2.getNum(), AdmittanceEquationType.BUS_ADM_IY).orElseThrow(); + if (e == null) { + e = new DenseMatrix(matrix.getRowCount(), 4); + } + e.reset(); + e.set(i1xEq.getColumn(), 0, 1.0); + e.set(i1yEq.getColumn(), 1, 1.0); + e.set(i2xEq.getColumn(), 2, 1.0); + e.set(i2yEq.getColumn(), 3, 1.0); + if (lu == null) { + lu = matrix.decomposeLU(); + } + lu.solveTransposed(e); + double z12x = e.get(i1xEq.getColumn(), 2); + double z12y = e.get(i1yEq.getColumn(), 2); + double z21x = e.get(i2xEq.getColumn(), 0); + double z21y = e.get(i2yEq.getColumn(), 0); + double z11x = e.get(i1xEq.getColumn(), 0); + double z11y = e.get(i1yEq.getColumn(), 0); + double z22x = e.get(i2xEq.getColumn(), 2); + double z22y = e.get(i2yEq.getColumn(), 2); + Complex z12 = new Complex(z12x, z12y); + Complex z21 = new Complex(z21x, z21y); + Complex z11 = new Complex(z11x, z11y); + Complex z22 = new Complex(z22x, z22y); + // z = (z11 * z22 - z12 * z21) / z12 + return (z11.multiply(z22).subtract(z12.multiply(z21))).divide(z12); + } + + @Override + public void close() { + if (lu != null) { + lu.close(); + } + } +} diff --git a/src/main/java/com/powsybl/openloadflow/adm/AdmittanceVariableType.java b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceVariableType.java new file mode 100644 index 0000000000..7746dcd4ac --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/adm/AdmittanceVariableType.java @@ -0,0 +1,35 @@ +/** + * 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.adm; + +import com.powsybl.openloadflow.equations.Quantity; +import com.powsybl.openloadflow.network.ElementType; + +/** + * @author Geoffroy Jamgotchian + */ +public enum AdmittanceVariableType implements Quantity { + BUS_ADM_VX("vx"), // real part + BUS_ADM_VY("vy"); // imaginary part + + private final String symbol; + + AdmittanceVariableType(String symbol) { + this.symbol = symbol; + } + + @Override + public String getSymbol() { + return symbol; + } + + @Override + public ElementType getElementType() { + return ElementType.BUS; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/AbstractElement.java b/src/main/java/com/powsybl/openloadflow/network/AbstractElement.java index 6306b2de99..46f87abb8d 100644 --- a/src/main/java/com/powsybl/openloadflow/network/AbstractElement.java +++ b/src/main/java/com/powsybl/openloadflow/network/AbstractElement.java @@ -71,6 +71,11 @@ public void removeEvaluable(Evaluable evaluable) { // nothing by default } + @Override + public void setVoltageControlEnabled(boolean enabled) { + // nothing byt default + } + @Override public String toString() { return getId(); diff --git a/src/main/java/com/powsybl/openloadflow/network/LfBus.java b/src/main/java/com/powsybl/openloadflow/network/LfBus.java index 60d6355728..a4dd2e1bc0 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfBus.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfBus.java @@ -18,6 +18,15 @@ */ public interface LfBus extends LfElement { + static Map buildIndex(List buses) { + Map busIndex = new LinkedHashMap<>(); + for (int i = 0; i < buses.size(); i++) { + var bus = buses.get(i); + busIndex.put(bus.getNum(), i); + } + return busIndex; + } + enum QLimitType { MIN_Q, MAX_Q diff --git a/src/main/java/com/powsybl/openloadflow/network/LfElement.java b/src/main/java/com/powsybl/openloadflow/network/LfElement.java index c54bd54f7a..673a76ba83 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfElement.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfElement.java @@ -35,4 +35,6 @@ public interface LfElement extends PropertyBag { LfNetwork getNetwork(); void removeEvaluable(Evaluable evaluable); + + void setVoltageControlEnabled(boolean enabled); } diff --git a/src/main/java/com/powsybl/openloadflow/network/PiModel.java b/src/main/java/com/powsybl/openloadflow/network/PiModel.java index 4d430f6d1b..e816ec57c4 100644 --- a/src/main/java/com/powsybl/openloadflow/network/PiModel.java +++ b/src/main/java/com/powsybl/openloadflow/network/PiModel.java @@ -8,6 +8,7 @@ package com.powsybl.openloadflow.network; import org.apache.commons.lang3.Range; +import org.apache.commons.math3.complex.Complex; import java.util.Optional; @@ -68,6 +69,8 @@ public interface PiModel { Optional updateTapPositionToReachNewA1(double deltaA1, int maxTapShift, AllowedDirection allowedDirection); + Optional getCutZ(double minZ, LoadFlowModel loadFlowModel); + boolean setMinZ(double minZ, LoadFlowModel loadFlowModel); void setBranch(LfBranch branch); diff --git a/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java b/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java index 56b6ada3db..c0043b77de 100644 --- a/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java +++ b/src/main/java/com/powsybl/openloadflow/network/PiModelArray.java @@ -8,6 +8,7 @@ package com.powsybl.openloadflow.network; import org.apache.commons.lang3.Range; +import org.apache.commons.math3.complex.Complex; import java.util.List; import java.util.Objects; @@ -358,6 +359,11 @@ public Optional updateTapPositionToReachNewA1(double deltaA1, int max return direction; } + @Override + public Optional getCutZ(double minZ, LoadFlowModel loadFlowModel) { + return getModel().getCutZ(minZ, loadFlowModel); + } + @Override public boolean setMinZ(double minZ, LoadFlowModel loadFlowModel) { boolean done = false; diff --git a/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java b/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java index 9d4376a616..65cac8e16a 100644 --- a/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java +++ b/src/main/java/com/powsybl/openloadflow/network/SimplePiModel.java @@ -9,7 +9,9 @@ import net.jafama.FastMath; import org.apache.commons.lang3.Range; +import org.apache.commons.math3.complex.Complex; +import java.util.Objects; import java.util.Optional; /** @@ -171,27 +173,32 @@ public Optional updateTapPositionToReachNewA1(double deltaA1, int max throw new IllegalStateException(NO_TAP_POSITION_ERROR); } - private void rescaleZ(double z) { - double ksi = getKsi(); - r = z * FastMath.sin(ksi); - x = z * FastMath.cos(ksi); - } - @Override - public boolean setMinZ(double minZ, LoadFlowModel loadFlowModel) { + public Optional getCutZ(double minZ, LoadFlowModel loadFlowModel) { + Objects.requireNonNull(loadFlowModel); if (loadFlowModel == LoadFlowModel.DC) { if (FastMath.abs(this.x) < minZ) { - this.x = minZ; - return true; + return Optional.of(Complex.valueOf(0, minZ)); } } else { double z = getZ(); if (z < minZ) { - rescaleZ(minZ); - return true; + double ksi = getKsi(); + double newR = minZ * FastMath.sin(ksi); + double newX = minZ * FastMath.cos(ksi); + return Optional.of(Complex.valueOf(newR, newX)); } } - return false; + return Optional.empty(); + } + + @Override + public boolean setMinZ(double minZ, LoadFlowModel loadFlowModel) { + return getCutZ(minZ, loadFlowModel).map(z -> { + SimplePiModel.this.r = z.getReal(); + SimplePiModel.this.x = z.getImaginary(); + return true; + }).orElse(false); } @Override diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java b/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java index 5300c52fd0..bf6f5eee84 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java @@ -261,6 +261,11 @@ public void setGeneratorVoltageControlEnabled(boolean generatorVoltageControlEna } } + @Override + public void setVoltageControlEnabled(boolean enabled) { + setGeneratorVoltageControlEnabled(enabled); + } + private static LfLoadModel createLfLoadModel(LoadModel loadModel, LfNetworkParameters parameters) { if (!parameters.isUseLoadModel() || loadModel == null) { return null; diff --git a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java index e0743a0126..9452f1d1fe 100644 --- a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java +++ b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowParametersTest.java @@ -396,7 +396,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], transformerVoltageControlUseInitialTapPosition=false, generatorVoltageControlMinNominalVoltage=-1.0, fictitiousGeneratorVoltageControlCheckMode=FORCED, areaInterchangeControl=false, areaInterchangeControlAreaType=ControlArea, areaInterchangePMaxMismatch=2.0)", + 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, areaInterchangeControl=false, areaInterchangeControlAreaType=ControlArea, areaInterchangePMaxMismatch=2.0, fixRemoteTargetVoltage=false)", parameters.toString()); } @@ -452,7 +452,7 @@ void testVoltageTargetPrioritiesParameter() { assertEquals("Unknown Voltage Control Type: Foo", e.getMessage()); parametersExt.setVoltageTargetPriorities(List.of("SHUNT")); - LfNetworkParameters lfNetworkParameters = OpenLoadFlowParameters.getNetworkParameters(parameters, parametersExt, new FirstSlackBusSelector(), new EvenShiloachGraphDecrementalConnectivityFactory<>(), false); + LfNetworkParameters lfNetworkParameters = OpenLoadFlowParameters.getNetworkParameters(parameters, parametersExt, new FirstSlackBusSelector(), new DenseMatrixFactory(), new EvenShiloachGraphDecrementalConnectivityFactory<>(), false); assertEquals(0, lfNetworkParameters.getVoltageTargetPriority(VoltageControl.Type.SHUNT)); // user-provided assertEquals(1, lfNetworkParameters.getVoltageTargetPriority(VoltageControl.Type.GENERATOR)); // filled from default assertEquals(2, lfNetworkParameters.getVoltageTargetPriority(VoltageControl.Type.TRANSFORMER)); // filled from default diff --git a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java index 0b2c1a31b4..af1991a304 100644 --- a/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java +++ b/src/test/java/com/powsybl/openloadflow/OpenLoadFlowProviderTest.java @@ -59,12 +59,12 @@ void testDcParameters() { void testAcParameters() { Network network = Mockito.mock(Network.class); AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network, new LoadFlowParameters().setReadSlackBus(true), new OpenLoadFlowParameters(), new DenseMatrixFactory(), new EvenShiloachGraphDecrementalConnectivityFactory<>(), false, false); - assertEquals("AcLoadFlowParameters(networkParameters=LfNetworkParameters(slackBusSelector=NetworkSlackBusSelector, connectivityFactory=EvenShiloachGraphDecrementalConnectivityFactory, generatorVoltageRemoteControl=true, minImpedance=false, twtSplitShuntAdmittance=false, breakers=false, plausibleActivePowerLimit=5000.0, computeMainConnectedComponentOnly=true, countriesToBalance=[], distributedOnConformLoad=false, phaseControl=false, transformerVoltageControl=false, voltagePerReactivePowerControl=false, generatorReactivePowerRemoteControl=false, transformerReactivePowerControl=false, loadFlowModel=AC, reactiveLimits=true, hvdcAcEmulation=true, minPlausibleTargetVoltage=0.8, maxPlausibleTargetVoltage=1.2, loaderPostProcessorSelection=[], reactiveRangeCheckMode=MAX, lowImpedanceThreshold=1.0E-8, svcVoltageMonitoring=true, maxSlackBusCount=1, debugDir=null, secondaryVoltageControl=false, cacheEnabled=false, asymmetrical=false, minNominalVoltageTargetVoltageCheck=20.0, linePerUnitMode=IMPEDANCE, useLoadModel=false, simulateAutomationSystems=false, referenceBusSelector=ReferenceBusFirstSlackSelector, voltageTargetPriorities=[GENERATOR, TRANSFORMER, SHUNT], fictitiousGeneratorVoltageControlCheckMode=FORCED, areaInterchangeControl=false, areaInterchangeControlAreaType=ControlArea), equationSystemCreationParameters=AcEquationSystemCreationParameters(forceA1Var=false), newtonRaphsonParameters=NewtonRaphsonParameters(maxIterations=15, minRealisticVoltage=0.5, maxRealisticVoltage=2.0, stoppingCriteria=DefaultNewtonRaphsonStoppingCriteria, stateVectorScalingMode=NONE, alwaysUpdateNetwork=false, lineSearchStateVectorScalingMaxIteration=10, lineSearchStateVectorScalingStepFold=1.3333333333333333, maxVoltageChangeStateVectorScalingMaxDv=0.1, maxVoltageChangeStateVectorScalingMaxDphi=0.17453292519943295), newtonKrylovParameters=NewtonKrylovParameters(maxIterations=100, lineSearch=false), outerLoops=[DistributedSlackOuterLoop, MonitoringVoltageOuterLoop, ReactiveLimitsOuterLoop], maxOuterLoopIterations=20, matrixFactory=DenseMatrixFactory, voltageInitializer=UniformValueVoltageInitializer, asymmetrical=false, slackDistributionFailureBehavior=LEAVE_ON_SLACK_BUS, solverFactory=NewtonRaphsonFactory, detailedReport=false)", - acParameters.toString()); + assertEquals("AcLoadFlowParameters(networkParameters=LfNetworkParameters(slackBusSelector=NetworkSlackBusSelector, connectivityFactory=EvenShiloachGraphDecrementalConnectivityFactory, generatorVoltageRemoteControl=true, minImpedance=false, twtSplitShuntAdmittance=false, breakers=false, plausibleActivePowerLimit=5000.0, computeMainConnectedComponentOnly=true, countriesToBalance=[], distributedOnConformLoad=false, phaseControl=false, transformerVoltageControl=false, voltagePerReactivePowerControl=false, generatorReactivePowerRemoteControl=false, transformerReactivePowerControl=false, loadFlowModel=AC, reactiveLimits=true, hvdcAcEmulation=true, minPlausibleTargetVoltage=0.8, maxPlausibleTargetVoltage=1.2, loaderPostProcessorSelection=[], reactiveRangeCheckMode=MAX, lowImpedanceThreshold=1.0E-8, svcVoltageMonitoring=true, maxSlackBusCount=1, debugDir=null, secondaryVoltageControl=false, cacheEnabled=false, asymmetrical=false, minNominalVoltageTargetVoltageCheck=20.0, linePerUnitMode=IMPEDANCE, useLoadModel=false, simulateAutomationSystems=false, referenceBusSelector=ReferenceBusFirstSlackSelector, voltageTargetPriorities=[GENERATOR, TRANSFORMER, SHUNT], fictitiousGeneratorVoltageControlCheckMode=FORCED, areaInterchangeControl=false, areaInterchangeControlAreaType=ControlArea), equationSystemCreationParameters=AcEquationSystemCreationParameters(forceA1Var=false), newtonRaphsonParameters=NewtonRaphsonParameters(maxIterations=15, minRealisticVoltage=0.5, maxRealisticVoltage=2.0, stoppingCriteria=DefaultNewtonRaphsonStoppingCriteria, stateVectorScalingMode=NONE, alwaysUpdateNetwork=false, lineSearchStateVectorScalingMaxIteration=10, lineSearchStateVectorScalingStepFold=1.3333333333333333, maxVoltageChangeStateVectorScalingMaxDv=0.1, maxVoltageChangeStateVectorScalingMaxDphi=0.17453292519943295), newtonKrylovParameters=NewtonKrylovParameters(maxIterations=100, lineSearch=false), outerLoops=[DistributedSlackOuterLoop, MonitoringVoltageOuterLoop, ReactiveLimitsOuterLoop], maxOuterLoopIterations=20, matrixFactory=DenseMatrixFactory, voltageInitializer=UniformValueVoltageInitializer, asymmetrical=false, slackDistributionFailureBehavior=LEAVE_ON_SLACK_BUS, solverFactory=NewtonRaphsonFactory, detailedReport=false, fixRemoteTargetVoltage=false)", + acParameters.toString()); } private static VoltageInitializer getExtendedVoltageInitializer(Network network, LoadFlowParameters parameters, OpenLoadFlowParameters parametersExt) { - LfNetworkParameters networkParameters = OpenLoadFlowParameters.getNetworkParameters(parameters, parametersExt, new FirstSlackBusSelector(), new EvenShiloachGraphDecrementalConnectivityFactory<>(), false); + LfNetworkParameters networkParameters = OpenLoadFlowParameters.getNetworkParameters(parameters, parametersExt, new FirstSlackBusSelector(), new DenseMatrixFactory(), new EvenShiloachGraphDecrementalConnectivityFactory<>(), false); return OpenLoadFlowParameters.getExtendedVoltageInitializer(parameters, parametersExt, networkParameters, new DenseMatrixFactory()); } @@ -87,7 +87,7 @@ void testGetExtendedVoltageInitializer() { @Test void specificParametersTest() { OpenLoadFlowProvider provider = new OpenLoadFlowProvider(); - assertEquals(71, provider.getSpecificParameters().size()); + assertEquals(72, 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(71, map.size()); + assertEquals(72, map.size()); assertEquals(provider.getSpecificParameters().size(), map.size()); } diff --git a/src/test/java/com/powsybl/openloadflow/adm/AdmittanceMatrixTest.java b/src/test/java/com/powsybl/openloadflow/adm/AdmittanceMatrixTest.java new file mode 100644 index 0000000000..ddca2cfca9 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/adm/AdmittanceMatrixTest.java @@ -0,0 +1,57 @@ +/** + * 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.adm; + +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.math.matrix.DenseMatrix; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.equations.VariableSet; +import com.powsybl.openloadflow.network.FirstSlackBusSelector; +import com.powsybl.openloadflow.network.LfBus; +import com.powsybl.openloadflow.network.LfNetwork; +import com.powsybl.openloadflow.network.impl.LfNetworkLoaderImpl; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class AdmittanceMatrixTest { + + @Test + void test() { + Network network = EurostagTutorialExample1Factory.create(); + LfNetwork lfNetwork = LfNetwork.load(network, new LfNetworkLoaderImpl(), new FirstSlackBusSelector()).get(0); + var ySystem = AdmittanceEquationSystem.create(lfNetwork, new VariableSet<>()); + try (var y = AdmittanceMatrix.create(ySystem, new DenseMatrixFactory())) { + var yRef = new DenseMatrix(8, 8, new double[] { + 3.4570637119113563, -144.0028305895698, -3.2842105263157895, 136.80268906009132, 0.0, 0.0, 0.0, 0.0, + 144.0028305895698, 3.4570637119113563, -136.80268906009132, -3.2842105263157895, 0.0, 0.0, 0.0, 0.0, + -3.2842105263157895, 136.80268906009132, 11.010710382513661, -216.20298481473702, -7.890710382513662, 86.79781420765028, 0.0, 0.0, + -136.80268906009132, -3.2842105263157895, 216.20298481473702, 11.010710382513661, -86.79781420765028, -7.890710382513662, 0.0, 0.0, + 0.0, 0.0, -7.890710382513662, 86.79781420765028, 8.540588654886903, -141.94049103976127, -0.649012633744856, 55.6258682851227, + 0.0, 0.0, -86.79781420765028, -7.890710382513662, 141.94049103976127, 8.540588654886903, -55.6258682851227, -0.649012633744856, + 0.0, 0.0, 0.0, 0.0, -0.649012633744856, 55.6258682851227, 0.6481481481481483, -55.55177456269486, + 0.0, 0.0, 0.0, 0.0, -55.6258682851227, -0.649012633744856, 55.55177456269486, 0.6481481481481483 + }).transpose(); + assertEquals(yRef, y.getMatrix()); + + LfBus ngen = lfNetwork.getBusById("VLGEN_0"); + LfBus nhv1 = lfNetwork.getBusById("VLHV1_0"); + LfBus nhv2 = lfNetwork.getBusById("VLHV2_0"); + LfBus nload = lfNetwork.getBusById("VLLOAD_0"); + + assertEquals(136.842, 1.0 / y.getZ(ngen, nhv1).abs(), 1e-3); + assertEquals(87.155, 1.0 / y.getZ(nhv1, nhv2).abs(), 1e-3); + assertEquals(55.629, 1.0 / y.getZ(nhv2, nload).abs(), 1e-3); + assertEquals(28.582, 1.0 / y.getZ(ngen, nload).abs(), 1e-3); + } + } +} diff --git a/src/test/resources/debug-parameters.json b/src/test/resources/debug-parameters.json index 4c83e814d3..7aeb6bb8cd 100644 --- a/src/test/resources/debug-parameters.json +++ b/src/test/resources/debug-parameters.json @@ -90,7 +90,8 @@ "fictitiousGeneratorVoltageControlCheckMode" : "FORCED", "areaInterchangeControl" : false, "areaInterchangeControlAreaType" : "ControlArea", - "areaInterchangePMaxMismatch" : 2.0 + "areaInterchangePMaxMismatch" : 2.0, + "fixRemoteVoltageTarget" : false } } },