diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/SensitivityComputer.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/SensitivityComputer.java index 13ee81bddb..780948126a 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/SensitivityComputer.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/SensitivityComputer.java @@ -63,6 +63,7 @@ public static final class SensitivityComputerBuilder { private Set loopFlowCnecs; private AppliedRemedialActions appliedRemedialActions; private Instant outageInstant; + private Set extraInjectionIds; public SensitivityComputerBuilder withToolProvider(ToolProvider toolProvider) { this.toolProvider = toolProvider; @@ -114,6 +115,11 @@ public SensitivityComputerBuilder withOutageInstant(Instant outageInstant) { return this; } + public SensitivityComputerBuilder withExtraInjections(Set extraInjectionIds) { + this.extraInjectionIds = extraInjectionIds; + return this; + } + public SensitivityComputer build() { Objects.requireNonNull(toolProvider); Objects.requireNonNull(flowCnecs); @@ -128,7 +134,8 @@ public SensitivityComputer build() { computePtdfs, computeLoopFlows, appliedRemedialActions, - outageInstant); + outageInstant, + extraInjectionIds); BranchResultAdapterImpl.BranchResultAdpaterBuilder builder = BranchResultAdapterImpl.create(); if (loopFlowComputation != null) { builder.withCommercialFlowsResults(loopFlowComputation, loopFlowCnecs); diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/ToolProvider.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/ToolProvider.java index 6e3d88845f..fc8090d8e9 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/ToolProvider.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/ToolProvider.java @@ -21,7 +21,9 @@ import com.powsybl.openrao.raoapi.parameters.RaoParameters; import com.powsybl.openrao.raoapi.parameters.LoopFlowParameters; import com.powsybl.openrao.raoapi.parameters.RelativeMarginsParameters; +import com.powsybl.openrao.searchtreerao.marmot.Marmot; import com.powsybl.openrao.sensitivityanalysis.AppliedRemedialActions; +import com.powsybl.openrao.sensitivityanalysis.InjectionSensitivityProvider; import com.powsybl.openrao.sensitivityanalysis.SystematicSensitivityInterface; import com.powsybl.glsk.commons.ZonalData; import com.powsybl.glsk.commons.ZonalDataImpl; @@ -83,7 +85,7 @@ public SystematicSensitivityInterface getSystematicSensitivityInterface(Set> rangeActions, boolean computePtdfs, boolean computeLoopFlows, Instant outageInstant) { - return getSystematicSensitivityInterface(cnecs, rangeActions, computePtdfs, computeLoopFlows, null, outageInstant); + return getSystematicSensitivityInterface(cnecs, rangeActions, computePtdfs, computeLoopFlows, null, outageInstant, null); } public SystematicSensitivityInterface getSystematicSensitivityInterface(Set cnecs, @@ -91,7 +93,8 @@ public SystematicSensitivityInterface getSystematicSensitivityInterface(Set extraInjectionIds) { SystematicSensitivityInterface.SystematicSensitivityInterfaceBuilder builder = SystematicSensitivityInterface.builder() .withSensitivityProviderName(getSensitivityProvider(raoParameters)) @@ -100,6 +103,16 @@ public SystematicSensitivityInterface getSystematicSensitivityInterface(Set injectionIds = Marmot.getScenarioRepo().getInjectionIds(); + if (!injectionIds.isEmpty()) { + builder.withSensitivityProvider(new InjectionSensitivityProvider(cnecs, injectionIds, Collections.singleton(Unit.MEGAWATT))); + } + } + if (!getSensitivityWithLoadFlowParameters(raoParameters).getLoadFlowParameters().isDc()) { builder.withLoadflow(cnecs, Collections.singleton(Unit.AMPERE)); } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/ShiftedFlowResult.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/ShiftedFlowResult.java new file mode 100644 index 0000000000..eb98d694fc --- /dev/null +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/ShiftedFlowResult.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms; + +import com.powsybl.iidm.network.TwoSides; +import com.powsybl.openrao.commons.OpenRaoException; +import com.powsybl.openrao.commons.Unit; +import com.powsybl.openrao.data.crac.api.Instant; +import com.powsybl.openrao.data.crac.api.State; +import com.powsybl.openrao.data.crac.api.cnec.FlowCnec; +import com.powsybl.openrao.data.raoresult.api.ComputationStatus; +import com.powsybl.openrao.searchtreerao.result.api.FlowResult; +import com.powsybl.openrao.searchtreerao.result.api.SensitivityResult; + +import java.util.Map; + +/** + * @author Peter Mitri {@literal } + */ +public class ShiftedFlowResult implements FlowResult { + private FlowResult initFlowResult; + private Map shiftedInjections; + private SensitivityResult sensitivityResult; + + public ShiftedFlowResult(FlowResult initFlowResult, Map shiftedInjections, SensitivityResult sensitivityResult) { + this.initFlowResult = initFlowResult; + this.shiftedInjections = shiftedInjections; + this.sensitivityResult = sensitivityResult; + } + + @Override + public double getFlow(FlowCnec flowCnec, TwoSides side, Unit unit) { + double flow = initFlowResult.getFlow(flowCnec, side, unit); + for (String shiftedInjection : this.shiftedInjections.keySet()) { + double sensi = sensitivityResult.getSensitivityValue(flowCnec, side, shiftedInjection, unit); + flow += sensi * shiftedInjections.get(shiftedInjection); + } + return flow; + } + + @Override + public double getFlow(FlowCnec flowCnec, TwoSides side, Unit unit, Instant optimizedInstant) { + throw new OpenRaoException("Not implemented"); + } + + @Override + public double getMargin(FlowCnec flowCnec, Unit unit) { + throw new OpenRaoException("Not implemented"); + } + + @Override + public double getCommercialFlow(FlowCnec flowCnec, TwoSides side, Unit unit) { + // throw new OpenRaoException("Not implemented"); + return initFlowResult.getCommercialFlow(flowCnec, side, unit); + } + + @Override + public double getPtdfZonalSum(FlowCnec flowCnec, TwoSides side) { + return initFlowResult.getPtdfZonalSum(flowCnec, side); + } + + @Override + public Map> getPtdfZonalSums() { + return initFlowResult.getPtdfZonalSums(); + } + + @Override + public ComputationStatus getComputationStatus() { + return initFlowResult.getComputationStatus(); + } + + @Override + public ComputationStatus getComputationStatus(State state) { + return initFlowResult.getComputationStatus(state); + } +} diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MaxLoopFlowFiller.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MaxLoopFlowFiller.java index 021ad15257..79e8a90b7f 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MaxLoopFlowFiller.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MaxLoopFlowFiller.java @@ -28,6 +28,8 @@ import java.util.Set; import java.util.TreeSet; +import static com.powsybl.openrao.commons.Unit.MEGAWATT; + /** * @author Pengbo Wang {@literal } */ @@ -116,6 +118,7 @@ private void buildLoopFlowConstraintsAndUpdateObjectiveFunction(LinearProblem li side, Optional.ofNullable(timestamp) ); + double referenceFlow = flowResult.getFlow(cnec, side, MEGAWATT); // build constraint which defines the loopFlow : // - MaxLoopFlow + commercialFlow <= flowVariable + loopflowViolationVariable <= POSITIVE_INF @@ -132,6 +135,7 @@ private void buildLoopFlowConstraintsAndUpdateObjectiveFunction(LinearProblem li ); positiveLoopflowViolationConstraint.setCoefficient(flowVariable, 1); positiveLoopflowViolationConstraint.setCoefficient(loopflowViolationVariable, 1.0); + positiveLoopflowViolationConstraint.setIsLazy(referenceFlow >= -loopFlowUpperBound + flowResult.getCommercialFlow(cnec, side, Unit.MEGAWATT)); OpenRaoMPConstraint negativeLoopflowViolationConstraint = linearProblem.addMaxLoopFlowConstraint( -linearProblem.infinity(), @@ -143,6 +147,7 @@ private void buildLoopFlowConstraintsAndUpdateObjectiveFunction(LinearProblem li ); negativeLoopflowViolationConstraint.setCoefficient(flowVariable, 1); negativeLoopflowViolationConstraint.setCoefficient(loopflowViolationVariable, -1); + negativeLoopflowViolationConstraint.setIsLazy(referenceFlow <= loopFlowUpperBound + flowResult.getCommercialFlow(cnec, side, Unit.MEGAWATT)); //update objective function with loopflowViolationCost linearProblem.getObjective().setCoefficient(loopflowViolationVariable, loopFlowViolationCost / cnec.getMonitoredSides().size()); diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MaxMinMarginFiller.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MaxMinMarginFiller.java index d5798dc960..4ae0ef8421 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MaxMinMarginFiller.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MaxMinMarginFiller.java @@ -20,10 +20,7 @@ import com.powsybl.openrao.searchtreerao.result.api.SensitivityResult; import java.time.OffsetDateTime; -import java.util.Comparator; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; import static com.powsybl.openrao.commons.Unit.MEGAWATT; @@ -61,7 +58,7 @@ public void fill(LinearProblem linearProblem, FlowResult flowResult, Sensitivity } // build constraints - buildMinimumMarginConstraints(linearProblem, validFlowCnecs); + buildMinimumMarginConstraints(linearProblem, validFlowCnecs, flowResult); if (costOptimization) { addMinMarginShiftedViolationConstraint(linearProblem); } @@ -121,10 +118,12 @@ private void buildMinimumMarginVariable(LinearProblem linearProblem, Set validFlowCnecs) { + private void buildMinimumMarginConstraints(LinearProblem linearProblem, Set validFlowCnecs, FlowResult flowResult) { OpenRaoMPVariable minimumMarginVariable = linearProblem.getMinimumMarginVariable(Optional.ofNullable(timestamp)); + List allMarginConstraints = new ArrayList<>(); validFlowCnecs.forEach(cnec -> cnec.getMonitoredSides().forEach(side -> { + OpenRaoMPVariable flowVariable = linearProblem.getFlowVariable(cnec, side, Optional.ofNullable(timestamp)); Optional minFlow; @@ -132,19 +131,29 @@ private void buildMinimumMarginConstraints(LinearProblem linearProblem, Set= minFlow.get()); + allMarginConstraints.add(minimumMarginNegative); } if (maxFlow.isPresent()) { OpenRaoMPConstraint minimumMarginPositive = linearProblem.addMinimumMarginConstraint(-linearProblem.infinity(), maxFlow.get(), cnec, side, LinearProblem.MarginExtension.ABOVE_THRESHOLD, Optional.ofNullable(timestamp)); minimumMarginPositive.setCoefficient(minimumMarginVariable, unitConversionCoefficient); minimumMarginPositive.setCoefficient(flowVariable, 1); + minimumMarginPositive.setIsLazy(referenceFlow <= maxFlow.get()); + allMarginConstraints.add(minimumMarginPositive); } })); + + // Make sure at least one constraint is not lazy, otherwise XPRESS finds the problem to be dual infeasible + if (allMarginConstraints.stream().allMatch(OpenRaoMPConstraint::isLazy)) { + allMarginConstraints.stream().findAny().ifPresent(constraint -> constraint.setIsLazy(false)); + } } /** diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MnecFiller.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MnecFiller.java index e2ae2f4295..fac883e5b8 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MnecFiller.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/fillers/MnecFiller.java @@ -59,7 +59,7 @@ public MnecFiller(FlowResult initialFlowResult, public void fill(LinearProblem linearProblem, FlowResult flowResult, SensitivityResult sensitivityResult, RangeActionActivationResult rangeActionActivationResult) { Set validMonitoredCnecs = FillersUtil.getFlowCnecsComputationStatusOk(monitoredCnecs, sensitivityResult); buildMarginViolationVariable(linearProblem, validMonitoredCnecs); - buildMnecMarginConstraints(linearProblem, validMonitoredCnecs); + buildMnecMarginConstraints(linearProblem, validMonitoredCnecs, flowResult); fillObjectiveWithMnecPenaltyCost(linearProblem, validMonitoredCnecs); } @@ -74,12 +74,13 @@ private void buildMarginViolationVariable(LinearProblem linearProblem, Set validMonitoredCnecs) { + private void buildMnecMarginConstraints(LinearProblem linearProblem, Set validMonitoredCnecs, FlowResult flowResult) { validMonitoredCnecs.forEach(mnec -> mnec.getMonitoredSides().forEach(side -> { double mnecInitialFlowInMW = initialFlowResult.getFlow(mnec, side, unit) * RaoUtil.getFlowUnitMultiplier(mnec, side, unit, MEGAWATT); OpenRaoMPVariable flowVariable = linearProblem.getFlowVariable(mnec, side, Optional.ofNullable(timestamp)); OpenRaoMPVariable mnecViolationVariable = linearProblem.getMnecViolationVariable(mnec, side, Optional.ofNullable(timestamp)); + double referenceFlow = flowResult.getFlow(mnec, side, unit) * RaoUtil.getFlowUnitMultiplier(mnec, side, unit, MEGAWATT); Optional maxFlow = mnec.getUpperBound(side, MEGAWATT); if (maxFlow.isPresent()) { @@ -87,6 +88,7 @@ private void buildMnecMarginConstraints(LinearProblem linearProblem, Set minFlow = mnec.getLowerBound(side, MEGAWATT); @@ -95,6 +97,7 @@ private void buildMnecMarginConstraints(LinearProblem linearProblem, Set= lb); } } )); diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/LinearProblemIdGenerator.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/LinearProblemIdGenerator.java index e57b5e0f40..488f5702c1 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/LinearProblemIdGenerator.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/LinearProblemIdGenerator.java @@ -61,14 +61,29 @@ public final class LinearProblemIdGenerator { private static final String GENERATOR_POWER_GRADIENT_CONSTRAINT = "generatorpowergradientconstraint"; private static final String MIN_MARGIN_SHIFTED_VIOLATION = "minmarginshiftedviolation"; private static final DateTimeFormatter DATE_TIME_FORMATER = DateTimeFormatter.ofPattern("yyyyMMddHHmm"); + private static String PREFIX = null; private LinearProblemIdGenerator() { // Should not be instantiated } + public static void setPrefix(String prefix) { + PREFIX = prefix; + } + private static String formatName(Optional timestamp, String... substrings) { + return LinearProblemIdGenerator.formatName(timestamp, false, substrings); + } + + private static String formatName(Optional timestamp, boolean scenarioDependent, String... substrings) { String name = String.join(SEPARATOR, substrings).replace("__", "_"); // remove empty strings - return timestamp.map(time -> name + SEPARATOR + time.format(DATE_TIME_FORMATER)).orElse(name); + if (scenarioDependent && PREFIX != null) { + name = PREFIX + SEPARATOR + name; + } + if (timestamp.isPresent()) { + name = name + SEPARATOR + timestamp.get().format(DATE_TIME_FORMATER); + } + return name; } private static String formatName(String... substrings) { @@ -76,11 +91,11 @@ private static String formatName(String... substrings) { } public static String flowVariableId(FlowCnec flowCnec, TwoSides side, Optional timestamp) { - return formatName(timestamp, flowCnec.getId(), side.toString().toLowerCase(), FLOW, VARIABLE_SUFFIX); + return formatName(timestamp, true, flowCnec.getId(), side.toString().toLowerCase(), FLOW, VARIABLE_SUFFIX); } public static String flowConstraintId(FlowCnec flowCnec, TwoSides side, Optional timestamp) { - return formatName(timestamp, flowCnec.getId(), side.toString().toLowerCase(), FLOW, CONSTRAINT_SUFFIX); + return formatName(timestamp, true, flowCnec.getId(), side.toString().toLowerCase(), FLOW, CONSTRAINT_SUFFIX); } public static String rangeActionSetpointVariableId(RangeAction rangeAction, State state) { @@ -140,12 +155,8 @@ public static String pstGroupTapConstraintId(RangeAction rangeAction, State s return formatName(rangeAction.getId(), state.getId(), rangeAction.getGroupId().orElseThrow(), VIRTUAL_TAP, CONSTRAINT_SUFFIX); } - public static String absoluteRangeActionVariationVariableId(RangeAction rangeAction, State state) { - return formatName(rangeAction.getId(), state.getId(), ABSOLUTE_VARIATION, VARIABLE_SUFFIX); - } - public static String minimumMarginConstraintId(FlowCnec flowCnec, TwoSides side, LinearProblem.MarginExtension belowOrAboveThreshold, Optional timestamp) { - return formatName(timestamp, flowCnec.getId(), side.toString().toLowerCase(), MIN_MARGIN, belowOrAboveThreshold.toString().toLowerCase(), CONSTRAINT_SUFFIX); + return formatName(timestamp, true, flowCnec.getId(), side.toString().toLowerCase(), MIN_MARGIN, belowOrAboveThreshold.toString().toLowerCase(), CONSTRAINT_SUFFIX); } public static String minimumMarginVariableId(Optional timestamp) { @@ -157,7 +168,7 @@ public static String minimumRelativeMarginVariableId(Optional ti } public static String minimumRelativeMarginConstraintId(FlowCnec flowCnec, TwoSides side, LinearProblem.MarginExtension belowOrAboveThreshold, Optional timestamp) { - return formatName(timestamp, flowCnec.getId(), side.toString().toLowerCase(), MIN_RELATIVE_MARGIN, belowOrAboveThreshold.toString().toLowerCase(), CONSTRAINT_SUFFIX); + return formatName(timestamp, true, flowCnec.getId(), side.toString().toLowerCase(), MIN_RELATIVE_MARGIN, belowOrAboveThreshold.toString().toLowerCase(), CONSTRAINT_SUFFIX); } public static String minimumRelativeMarginSignBinaryVariableId(Optional timestamp) { @@ -173,27 +184,27 @@ public static String minimumRelativeMarginSetToZeroConstraintId(Optional timestamp) { - return formatName(timestamp, flowCnec.getId(), side.toString().toLowerCase(), MAX_LOOPFLOW, lbOrUb.toString().toLowerCase(), CONSTRAINT_SUFFIX); + return formatName(timestamp, true, flowCnec.getId(), side.toString().toLowerCase(), MAX_LOOPFLOW, lbOrUb.toString().toLowerCase(), CONSTRAINT_SUFFIX); } public static String loopflowViolationVariableId(FlowCnec flowCnec, TwoSides side, Optional timestamp) { - return formatName(timestamp, flowCnec.getId(), side.toString().toLowerCase(), LOOPFLOWVIOLATION, VARIABLE_SUFFIX); + return formatName(timestamp, true, flowCnec.getId(), side.toString().toLowerCase(), LOOPFLOWVIOLATION, VARIABLE_SUFFIX); } public static String mnecViolationVariableId(FlowCnec mnec, TwoSides side, Optional timestamp) { - return formatName(timestamp, mnec.getId(), side.toString().toLowerCase(), MNEC_VIOLATION, VARIABLE_SUFFIX); + return formatName(timestamp, true, mnec.getId(), side.toString().toLowerCase(), MNEC_VIOLATION, VARIABLE_SUFFIX); } public static String mnecFlowConstraintId(FlowCnec mnec, TwoSides side, LinearProblem.MarginExtension belowOrAboveThreshold, Optional timestamp) { - return formatName(timestamp, mnec.getId(), side.toString().toLowerCase(), MNEC_FLOW, belowOrAboveThreshold.toString().toLowerCase(), CONSTRAINT_SUFFIX); + return formatName(timestamp, true, mnec.getId(), side.toString().toLowerCase(), MNEC_FLOW, belowOrAboveThreshold.toString().toLowerCase(), CONSTRAINT_SUFFIX); } public static String optimizeCnecBinaryVariableId(FlowCnec flowCnec, TwoSides side, Optional timestamp) { - return formatName(timestamp, flowCnec.getId(), side.toString().toLowerCase(), OPTIMIZE_CNEC, VARIABLE_SUFFIX); + return formatName(timestamp, true, flowCnec.getId(), side.toString().toLowerCase(), OPTIMIZE_CNEC, VARIABLE_SUFFIX); } public static String dontOptimizeCnecConstraintId(FlowCnec flowCnec, TwoSides side, LinearProblem.MarginExtension belowOrAboveThreshold, Optional timestamp) { - return formatName(timestamp, flowCnec.getId(), side.toString().toLowerCase(), OPTIMIZE_CNEC + belowOrAboveThreshold.toString().toLowerCase(), CONSTRAINT_SUFFIX); + return formatName(timestamp, true, flowCnec.getId(), side.toString().toLowerCase(), OPTIMIZE_CNEC + belowOrAboveThreshold.toString().toLowerCase(), CONSTRAINT_SUFFIX); } public static String maxRaConstraintId(State state) { diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/OpenRaoMPConstraint.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/OpenRaoMPConstraint.java index a046f05dc0..28231146c3 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/OpenRaoMPConstraint.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/OpenRaoMPConstraint.java @@ -50,4 +50,12 @@ public void setUb(double ub) { public void setBounds(double lb, double ub) { mpConstraint.setBounds(OpenRaoMPSolver.roundDouble(lb), OpenRaoMPSolver.roundDouble(ub)); } + + public boolean isLazy() { + return mpConstraint.isLazy(); + } + + public void setIsLazy(boolean isLazy) { + mpConstraint.setIsLazy(isLazy); + } } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/OpenRaoMPSolver.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/OpenRaoMPSolver.java index 8e56cc3e5a..0b27b61da0 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/OpenRaoMPSolver.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/algorithms/linearproblem/OpenRaoMPSolver.java @@ -79,8 +79,8 @@ public SearchTreeRaoRangeActionsOptimizationParameters.Solver getSolver() { private MPSolver.OptimizationProblemType getOrToolsProblemType(SearchTreeRaoRangeActionsOptimizationParameters.Solver solver) { Objects.requireNonNull(solver); return switch (solver) { - case CBC -> MPSolver.OptimizationProblemType.CBC_MIXED_INTEGER_PROGRAMMING; - case SCIP -> MPSolver.OptimizationProblemType.SCIP_MIXED_INTEGER_PROGRAMMING; + case CBC -> MPSolver.OptimizationProblemType.XPRESS_MIXED_INTEGER_PROGRAMMING; + case SCIP -> MPSolver.OptimizationProblemType.XPRESS_MIXED_INTEGER_PROGRAMMING; case XPRESS -> MPSolver.OptimizationProblemType.XPRESS_MIXED_INTEGER_PROGRAMMING; default -> throw new OpenRaoException(String.format("unknown solver %s in RAO parameters", solver)); }; @@ -133,7 +133,7 @@ public OpenRaoMPVariable makeBoolVar(String name) { private OpenRaoMPVariable makeVar(double lb, double ub, boolean integer, String name) { if (hasVariable(name)) { - throw new OpenRaoException(String.format("Variable %s already exists", name)); + return variables.get(name); } double roundedLb = roundDouble(lb); double roundedUb = roundDouble(ub); @@ -144,7 +144,7 @@ private OpenRaoMPVariable makeVar(double lb, double ub, boolean integer, String public OpenRaoMPConstraint makeConstraint(double lb, double ub, String name) { if (hasConstraint(name)) { - throw new OpenRaoException(String.format("Constraint %s already exists", name)); + return constraints.get(name); } else { double roundedLb = roundDouble(lb); double roundedUb = roundDouble(ub); diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/inputs/IteratingLinearOptimizerInput.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/inputs/IteratingLinearOptimizerInput.java index 81f55f81b1..897600b142 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/inputs/IteratingLinearOptimizerInput.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/linearoptimisation/inputs/IteratingLinearOptimizerInput.java @@ -19,6 +19,8 @@ import com.powsybl.openrao.sensitivityanalysis.AppliedRemedialActions; import com.powsybl.iidm.network.Network; +import java.util.Map; + /** * @author Baptiste Seguinot {@literal } */ @@ -31,7 +33,11 @@ public record IteratingLinearOptimizerInput(Network network, OptimizationPerimet RangeActionActivationResult raActivationFromParentLeaf, NetworkActionsResult appliedNetworkActionsInPrimaryState, ObjectiveFunction objectiveFunction, ToolProvider toolProvider, - Instant outageInstant) { + Instant outageInstant, + Map initialFlowResultPerScenario, + Map prePerimeterFlowResultPerScenario, + Map preOptimizationFlowResultPerScenario, + Map preOptimizationSensitivityResultPerScenario) { public static IteratingLinearOptimizerInputBuilder create() { return new IteratingLinearOptimizerInputBuilder(); @@ -42,9 +48,13 @@ public static class IteratingLinearOptimizerInputBuilder { private OptimizationPerimeter optimizationPerimeter; private FlowResult initialFlowResult; private FlowResult prePerimeterFlowResult; + private Map initialFlowResultPerScenario; + private Map prePerimeterFlowResultPerScenario; + private Map preOptimizationFlowResultPerScenario; private RangeActionSetpointResult prePerimeterSetpoints; private FlowResult preOptimizationFlowResult; private SensitivityResult preOptimizationSensitivityResult; + private Map preOptimizationSensitivityResultPerScenario; private AppliedRemedialActions preOptimizationAppliedRemedialActions; private RangeActionActivationResult raActivationFromParentLeaf; private NetworkActionsResult appliedNetworkActionsInPrimaryState; @@ -72,6 +82,16 @@ public IteratingLinearOptimizerInputBuilder withPrePerimeterFlowResult(FlowResul return this; } + public IteratingLinearOptimizerInputBuilder withInitialFlowResultPerScenario(Map initialFlowResult) { + this.initialFlowResultPerScenario = initialFlowResult; + return this; + } + + public IteratingLinearOptimizerInputBuilder withPrePerimeterFlowResultPerScenario(Map prePerimeterFlowResult) { + this.prePerimeterFlowResultPerScenario = prePerimeterFlowResult; + return this; + } + public IteratingLinearOptimizerInputBuilder withPrePerimeterSetpoints(RangeActionSetpointResult prePerimeterSetpoints) { this.prePerimeterSetpoints = prePerimeterSetpoints; return this; @@ -82,11 +102,21 @@ public IteratingLinearOptimizerInputBuilder withPreOptimizationFlowResult(FlowRe return this; } + public IteratingLinearOptimizerInputBuilder withPreOptimizationFlowResultPerScenario(Map preOptimizationFlowResult) { + this.preOptimizationFlowResultPerScenario = preOptimizationFlowResult; + return this; + } + public IteratingLinearOptimizerInputBuilder withPreOptimizationSensitivityResult(SensitivityResult preOptimizationSensitivityResult) { this.preOptimizationSensitivityResult = preOptimizationSensitivityResult; return this; } + public IteratingLinearOptimizerInputBuilder withPreOptimizationSensitivityResultPerScenario(Map preOptimizationSensitivityResult) { + this.preOptimizationSensitivityResultPerScenario = preOptimizationSensitivityResult; + return this; + } + public IteratingLinearOptimizerInputBuilder withPreOptimizationAppliedRemedialActions(AppliedRemedialActions preOptimizationAppliedRemedialActions) { this.preOptimizationAppliedRemedialActions = preOptimizationAppliedRemedialActions; return this; @@ -130,7 +160,11 @@ public IteratingLinearOptimizerInput build() { appliedNetworkActionsInPrimaryState, objectiveFunction, toolProvider, - outageInstant); + outageInstant, + initialFlowResultPerScenario, + prePerimeterFlowResultPerScenario, + preOptimizationFlowResultPerScenario, + preOptimizationSensitivityResultPerScenario); } } } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/InterTemporalIteratingLinearOptimizerInput.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/InterTemporalIteratingLinearOptimizerInput.java index bd8a6794e4..660703c32d 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/InterTemporalIteratingLinearOptimizerInput.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/InterTemporalIteratingLinearOptimizerInput.java @@ -18,5 +18,5 @@ * @author Thomas Bouquet {@literal } * @author Godelaine de Montmorillon {@literal } */ -public record InterTemporalIteratingLinearOptimizerInput(TemporalData iteratingLinearOptimizerInputs, ObjectiveFunction objectiveFunction, Set generatorConstraints) { +public record InterTemporalIteratingLinearOptimizerInput(TemporalData iteratingLinearOptimizerInputs, RobustObjectiveFunction objectiveFunction, Set generatorConstraints) { } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/Marmot.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/Marmot.java index 211a3f5756..3fea3c8d38 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/Marmot.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/Marmot.java @@ -8,6 +8,7 @@ package com.powsybl.openrao.searchtreerao.marmot; import com.google.auto.service.AutoService; +import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; import com.powsybl.openrao.commons.TemporalData; import com.powsybl.openrao.commons.TemporalDataImpl; @@ -26,7 +27,6 @@ import com.powsybl.openrao.raoapi.parameters.extensions.SearchTreeRaoRangeActionsOptimizationParameters; import com.powsybl.openrao.raoapi.parameters.extensions.SearchTreeRaoRelativeMarginsParameters; import com.powsybl.openrao.searchtreerao.commons.ToolProvider; -import com.powsybl.openrao.searchtreerao.commons.objectivefunction.ObjectiveFunction; import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.OptimizationPerimeter; import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.PreventiveOptimizationPerimeter; import com.powsybl.openrao.searchtreerao.commons.parameters.RangeActionLimitationParameters; @@ -35,6 +35,9 @@ import com.powsybl.openrao.searchtreerao.linearoptimisation.parameters.IteratingLinearOptimizerParameters; import com.powsybl.openrao.searchtreerao.marmot.results.GlobalFlowResult; import com.powsybl.openrao.searchtreerao.marmot.results.GlobalLinearOptimizationResult; +import com.powsybl.openrao.searchtreerao.marmot.scenariobuilder.GeneratorTargetPNetworkVariation; +import com.powsybl.openrao.searchtreerao.marmot.scenariobuilder.NetworkVariation; +import com.powsybl.openrao.searchtreerao.marmot.scenariobuilder.ScenarioRepo; import com.powsybl.openrao.searchtreerao.result.api.*; import com.powsybl.openrao.searchtreerao.marmot.results.InterTemporalRaoResultImpl; import com.powsybl.openrao.searchtreerao.result.api.FlowResult; @@ -48,6 +51,7 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import static com.powsybl.openrao.commons.logs.OpenRaoLoggerProvider.TECHNICAL_LOGS; import static com.powsybl.openrao.searchtreerao.commons.RaoLogger.logCost; @@ -69,8 +73,69 @@ public class Marmot implements InterTemporalRaoProvider { private static final String MIP_SCENARIO = "MipScenario"; private static final String MIN_MARGIN_VIOLATION_EVALUATOR = "min-margin-violation-evaluator"; + private static ScenarioRepo SCENARIO_REPO = null; // TODO this is a temp singleton ; make this cleaner + + public static ScenarioRepo getScenarioRepo() { + return SCENARIO_REPO; + } + + private static List createNetworkVariationsForGenerator(String genId, TemporalData networks) { + List variations = new ArrayList<>(); + // Reference + TemporalData valuesRef = new TemporalDataImpl<>(); + for (OffsetDateTime ts : networks.getTimestamps()) { + valuesRef.put(ts, networks.getData(ts).orElseThrow().getGenerator(genId).getTargetP()); + } + variations.add(new GeneratorTargetPNetworkVariation(genId + "_ref", genId, valuesRef)); + // MinP + TemporalData valuesMin = new TemporalDataImpl<>(); + for (OffsetDateTime ts : networks.getTimestamps()) { + valuesMin.put(ts, networks.getData(ts).orElseThrow().getGenerator(genId).getMinP()); + } + variations.add(new GeneratorTargetPNetworkVariation(genId + "_min_P", genId, valuesMin)); + // MaxP + TemporalData valuesMax = new TemporalDataImpl<>(); + for (OffsetDateTime ts : networks.getTimestamps()) { + valuesMax.put(ts, networks.getData(ts).orElseThrow().getGenerator(genId).getMaxP()); + } + variations.add(new GeneratorTargetPNetworkVariation(genId + "_max_P", genId, valuesMax)); + + return variations; + } + + private static ScenarioRepo createScenarios(TemporalData networks) { + // TODO this should be done by various new input file imports + Map> networkVariations = new HashMap<>(); + Set networkGenerators = networks.getData(networks.getTimestamps().get(0)).orElseThrow().getGeneratorStream().map(Identifiable::getId).filter(id -> !id.contains("_RA_")).collect(Collectors.toSet()); + for (String genId : networkGenerators) { + networkVariations.put(genId, createNetworkVariationsForGenerator(genId, networks)); + } + ScenarioRepo scenarioRepo = new ScenarioRepo(networkVariations.values().stream().flatMap(Collection::stream).collect(Collectors.toSet())); + int nScenarios = 10; + Random rand = new Random(); + // Add reference scenario + scenarioRepo.addScenario("REFERENCE", Set.of()); + while (scenarioRepo.getNumberOfScenarios() < nScenarios) { + Set scenarioVariations = new HashSet<>(); + for (String genId : networkGenerators) { + NetworkVariation randomVariation = networkVariations.get(genId).get(rand.nextInt(networkVariations.get(genId).size())); + scenarioVariations.add(randomVariation.getId()); + } + //String scenarioId = scenarioVariations.stream().sorted().collect(Collectors.joining(" + ")); + String scenarioId = "scn_" + scenarioRepo.getNumberOfScenarios(); + scenarioRepo.addScenario(scenarioId, scenarioVariations); + } + + return scenarioRepo; + } + @Override public CompletableFuture run(InterTemporalRaoInputWithNetworkPaths interTemporalRaoInputWithNetworkPaths, RaoParameters raoParameters) { + // TODO make this cleaner + InterTemporalRaoInput tmpInterTemporalRaoInput = importNetworksFromInterTemporalRaoInputWithNetworkPaths(interTemporalRaoInputWithNetworkPaths); + TemporalData networks = tmpInterTemporalRaoInput.getRaoInputs().map(RaoInput::getNetwork); + SCENARIO_REPO = createScenarios(networks); + // 1. Run independent RAOs to compute optimal preventive topological remedial actions TECHNICAL_LOGS.info("[MARMOT] ----- Topological optimization [start]"); TemporalData> consideredCnecs = new TemporalDataImpl<>(); @@ -78,7 +143,7 @@ public CompletableFuture run(InterTemporalRaoInputWithNe TECHNICAL_LOGS.info("[MARMOT] ----- Topological optimization [end]"); // 2. Get the initial results from the various independent results to avoid recomputing them - TemporalData initialResults = buildInitialResults(topologicalOptimizationResults); + Map> initialResults = runInitialLoadFlow(tmpInterTemporalRaoInput.getRaoInputs(), raoParameters, false); // TODO : Add intertemporal constraint check if none violated then return boolean noInterTemporalConstraint = interTemporalRaoInputWithNetworkPaths.getGeneratorConstraints().isEmpty(); @@ -86,12 +151,16 @@ public CompletableFuture run(InterTemporalRaoInputWithNe // 3. Apply independent topological remedial actions (and preventive range actions if there are no inter-temporal constraints) InterTemporalRaoInput interTemporalRaoInput = importNetworksFromInterTemporalRaoInputWithNetworkPaths(interTemporalRaoInputWithNetworkPaths); TECHNICAL_LOGS.info("[MARMOT] Applying optimal topological actions on networks"); - ObjectiveFunction fullObjectiveFunction = buildGlobalObjectiveFunction(interTemporalRaoInput.getRaoInputs().map(RaoInput::getCrac), new GlobalFlowResult(initialResults), raoParameters); + Map initialGlobalFlowResults = new HashMap<>(); + for (String scenario : SCENARIO_REPO.getScenarios()) { + initialGlobalFlowResults.put(scenario, new GlobalFlowResult(initialResults.get(scenario))); + } + RobustObjectiveFunction fullObjectiveFunction = buildGlobalObjectiveFunction(interTemporalRaoInput.getRaoInputs().map(RaoInput::getCrac), initialGlobalFlowResults, raoParameters); LinearOptimizationResult initialObjectiveFunctionResult = getInitialObjectiveFunctionResult(initialResults, fullObjectiveFunction); // 4. Evaluate objective function after independent optimizations TECHNICAL_LOGS.info("[MARMOT] Evaluating global result after independent optimizations"); - TemporalData postTopologicalActionsResults = topologicalOptimizationResults.map( + /*TemporalData postTopologicalActionsResults = topologicalOptimizationResults.map( raoResult -> ((FastRaoResultImpl) raoResult).getFinalResult() ); TemporalData initialSetpointResults = getInitialSetpointResults(topologicalOptimizationResults, interTemporalRaoInput.getRaoInputs()); @@ -100,7 +169,10 @@ public CompletableFuture run(InterTemporalRaoInputWithNe postTopologicalActionsResults, fullObjectiveFunction, topologicalOptimizationResults, - interTemporalRaoInput.getRaoInputs().map(individualRaoInput -> individualRaoInput.getCrac().getPreventiveState())); + interTemporalRaoInput.getRaoInputs().map(individualRaoInput -> individualRaoInput.getCrac().getPreventiveState()));*/ + // TODO : reactivate topo optimization + LinearOptimizationResult postTopologicalOptimizationResult = initialObjectiveFunctionResult; + TemporalData initialSetpointResults = getInitialSetpointResults(topologicalOptimizationResults, interTemporalRaoInput.getRaoInputs()); // if no inter-temporal constraints are defined, the results can be returned if (noInterTemporalConstraint) { @@ -121,7 +193,7 @@ public CompletableFuture run(InterTemporalRaoInputWithNe replaceFastRaoResultsWithLightVersions(topologicalOptimizationResults); //TODO: loop - TemporalData loadFlowResults; + Map> loadFlowResults; GlobalLinearOptimizationResult linearOptimizationResults; GlobalLinearOptimizationResult fullResults; int counter = 1; @@ -134,11 +206,15 @@ public CompletableFuture run(InterTemporalRaoInputWithNe // Run post topo sensitivity analysis on all timestamps ON CONSIDERED CNECS ONLY (which is why we do it every loop) TECHNICAL_LOGS.info("[MARMOT] Systematic inter-temporal sensitivity analysis [start]"); - TemporalData postTopoResults = runAllSensitivityAnalysesBasedOnInitialResult(interTemporalRaoInput.getRaoInputs(), curativeRemedialActions, initialResults, raoParameters, consideredCnecs); + // TODO reactivate topo optimization + for (String scenario : SCENARIO_REPO.getScenarios()) { + + } + Map> postTopoResults = runInitialLoadFlow(tmpInterTemporalRaoInput.getRaoInputs(), raoParameters, true); TECHNICAL_LOGS.info("[MARMOT] Systematic inter-temporal sensitivity analysis [end]"); // Build objective function with ONLY THE CONSIDERED CNECS - ObjectiveFunction filteredObjectiveFunction = buildFilteredObjectiveFunction(interTemporalRaoInput.getRaoInputs().map(RaoInput::getCrac), new GlobalFlowResult(initialResults), raoParameters, consideredCnecs); + RobustObjectiveFunction filteredObjectiveFunction = buildFilteredObjectiveFunction(interTemporalRaoInput.getRaoInputs().map(RaoInput::getCrac), initialGlobalFlowResults, raoParameters, consideredCnecs); // Create and iteratively solve MIP to find optimal range actions' set-points FOR THE CONSIDERED CNECS TECHNICAL_LOGS.info("[MARMOT] ----- Global range actions optimization [start] for iteration {}", counter); @@ -150,7 +226,15 @@ public CompletableFuture run(InterTemporalRaoInputWithNe // Create a global result with the flows on ALL cnecs and the actions applied during MIP TemporalData rangeActionActivationResultTemporalData = linearOptimizationResults.getRangeActionActivationResultTemporalData(); - fullResults = new GlobalLinearOptimizationResult(loadFlowResults, loadFlowResults.map(PrePerimeterResult::getSensitivityResult), rangeActionActivationResultTemporalData, preventiveTopologicalActions, fullObjectiveFunction, LinearProblemStatus.OPTIMAL); + Map> sensitivityResults = new HashMap<>(); + for (String scenario : loadFlowResults.keySet()) { + sensitivityResults.put(scenario, loadFlowResults.get(scenario).map(PrePerimeterResult::getSensitivityResult)); + } + Map> casted = new HashMap<>(); + for (String scenario : loadFlowResults.keySet()) { + casted.put(scenario, loadFlowResults.get(scenario).map(FlowResult.class::cast)); + } + fullResults = new GlobalLinearOptimizationResult(casted, sensitivityResults, rangeActionActivationResultTemporalData, preventiveTopologicalActions, fullObjectiveFunction, LinearProblemStatus.OPTIMAL); logCost("[MARMOT] next iteration of MIP: ", fullResults, raoParameters, 10); counter++; @@ -159,7 +243,9 @@ public CompletableFuture run(InterTemporalRaoInputWithNe // 7. Merge topological and linear result TECHNICAL_LOGS.info("[MARMOT] Merging topological and linear remedial action results"); - InterTemporalRaoResultImpl interTemporalRaoResult = mergeTopologicalAndLinearOptimizationResults(interTemporalRaoInput.getRaoInputs(), initialResults, initialObjectiveFunctionResult, fullResults, topologicalOptimizationResults, raoParameters); + // Selecting reference and hoping it works + // TODO make this cleaner + InterTemporalRaoResultImpl interTemporalRaoResult = mergeTopologicalAndLinearOptimizationResults(interTemporalRaoInput.getRaoInputs(), initialResults.get("REFERENCE"), initialObjectiveFunctionResult, fullResults, topologicalOptimizationResults, raoParameters); // 8. Log initial and final results logCost("[MARMOT] Before topological optimizations: ", initialObjectiveFunctionResult, raoParameters, 10); @@ -185,7 +271,7 @@ private TemporalData getInitialSetpointResults(Tempor return initialSetpointResults; } - private boolean shouldContinueAndAddCnecs(TemporalData loadFlowResults, TemporalData> consideredCnecs) { + private boolean shouldContinueAndAddCnecs(Map> loadFlowResults, TemporalData> consideredCnecs) { int cnecsToAddPerVirtualCostName = 20; double minRelativeImprovementOnMargin = 0.1; double marginWindowToConsider = 5.0; @@ -199,83 +285,87 @@ private boolean shouldContinueAndAddCnecs(TemporalData loadF return shouldContinue.get(); } - private static void updateShouldContinue(TemporalData loadFlowResults, TemporalData> consideredCnecs, double minRelativeImprovementOnMargin, AtomicBoolean shouldContinue) { - loadFlowResults.getTimestamps().forEach(timestamp -> { - PrePerimeterResult loadFlowResult = loadFlowResults.getData(timestamp).orElseThrow(); - Set previousCnecs = consideredCnecs.getData(timestamp).orElseThrow(); - - // for margin violation - need to compare to min improvement on margin - // ordered list of cnecs with an overload - List worstCnecsForMarginViolation = loadFlowResult.getCostlyElements(MIN_MARGIN_VIOLATION_EVALUATOR, Integer.MAX_VALUE); - double worstConsideredMargin = worstCnecsForMarginViolation.stream() - .filter(previousCnecs::contains) - .findFirst() - .map(cnec -> loadFlowResult.getMargin(cnec, Unit.MEGAWATT)) - .orElse(0.); - double worstMarginOfAll = worstCnecsForMarginViolation.stream() - .findFirst() - .map(cnec -> loadFlowResult.getMargin(cnec, Unit.MEGAWATT)) - .orElse(0.); - // if worst overload > worst considered overload *( 1 + minImprovementOnLoad) - if (worstMarginOfAll < worstConsideredMargin * (1 + minRelativeImprovementOnMargin) - 1e-6) { - shouldContinue.set(true); - } + private static void updateShouldContinue(Map> loadFlowResults, TemporalData> consideredCnecs, double minRelativeImprovementOnMargin, AtomicBoolean shouldContinue) { + for (TemporalData scenarioLoadFlowResult : loadFlowResults.values()) { + scenarioLoadFlowResult.getTimestamps().forEach(timestamp -> { + PrePerimeterResult loadFlowResult = scenarioLoadFlowResult.getData(timestamp).orElseThrow(); + Set previousCnecs = consideredCnecs.getData(timestamp).orElseThrow(); + + // for margin violation - need to compare to min improvement on margin + // ordered list of cnecs with an overload + List worstCnecsForMarginViolation = loadFlowResult.getCostlyElements(MIN_MARGIN_VIOLATION_EVALUATOR, Integer.MAX_VALUE); + double worstConsideredMargin = worstCnecsForMarginViolation.stream() + .filter(previousCnecs::contains) + .findFirst() + .map(cnec -> loadFlowResult.getMargin(cnec, Unit.MEGAWATT)) + .orElse(0.); + double worstMarginOfAll = worstCnecsForMarginViolation.stream() + .findFirst() + .map(cnec -> loadFlowResult.getMargin(cnec, Unit.MEGAWATT)) + .orElse(0.); + // if worst overload > worst considered overload *( 1 + minImprovementOnLoad) + if (worstMarginOfAll < worstConsideredMargin * (1 + minRelativeImprovementOnMargin) - 1e-6) { + shouldContinue.set(true); + } - // for other violations - just check if cnec was considered - loadFlowResult.getVirtualCostNames().stream() - .filter(vcName -> !vcName.equals(MIN_MARGIN_VIOLATION_EVALUATOR)) - .forEach(vcName -> { - Optional worstCnec = loadFlowResult.getCostlyElements(vcName, 1).stream().findFirst(); - if (worstCnec.isPresent() && !previousCnecs.contains(worstCnec.get())) { - shouldContinue.set(true); - } - }); - }); + // for other violations - just check if cnec was considered + loadFlowResult.getVirtualCostNames().stream() + .filter(vcName -> !vcName.equals(MIN_MARGIN_VIOLATION_EVALUATOR)) + .forEach(vcName -> { + Optional worstCnec = loadFlowResult.getCostlyElements(vcName, 1).stream().findFirst(); + if (worstCnec.isPresent() && !previousCnecs.contains(worstCnec.get())) { + shouldContinue.set(true); + } + }); + }); + } } - private static void updateConsideredCnecs(TemporalData loadFlowResults, TemporalData> consideredCnecs, double marginWindowToConsider, int cnecsToAddPerVirtualCostName) { + private static void updateConsideredCnecs(Map> loadFlowResults, TemporalData> consideredCnecs, double marginWindowToConsider, int cnecsToAddPerVirtualCostName) { List addedCnecsForLogging = new ArrayList<>(); - loadFlowResults.getTimestamps().forEach(timestamp -> { - PrePerimeterResult loadFlowResult = loadFlowResults.getData(timestamp).orElseThrow(); - Set previousIterationCnecs = consideredCnecs.getData(timestamp).orElseThrow(); - Set nextIterationCnecs = new HashSet<>(previousIterationCnecs); - - double worstConsideredMargin = loadFlowResult.getCostlyElements(MIN_MARGIN_VIOLATION_EVALUATOR, Integer.MAX_VALUE) - .stream() - .filter(previousIterationCnecs::contains) - .findFirst() - .map(cnec -> loadFlowResult.getMargin(cnec, Unit.MEGAWATT)) - .orElse(0.); - - loadFlowResult.getVirtualCostNames().forEach(vcName -> { - LoggingAddedCnecs currentLoggingAddedCnecs = new LoggingAddedCnecs(timestamp, vcName, new ArrayList<>(), new HashMap<>()); - int addedCnecsForVcName = 0; - - // for min margin violation take all cnecs - if (vcName.equals(MIN_MARGIN_VIOLATION_EVALUATOR)) { - for (FlowCnec cnec : loadFlowResult.getCostlyElements(vcName, Integer.MAX_VALUE)) { - if (loadFlowResult.getMargin(cnec, Unit.MEGAWATT) > worstConsideredMargin + marginWindowToConsider && addedCnecsForVcName > cnecsToAddPerVirtualCostName) { - // stop if out of window and already added enough - break; - } else if (!previousIterationCnecs.contains(cnec)) { - // if in window or not added enough yet, add - nextIterationCnecs.add(cnec); - addedCnecsForVcName++; - currentLoggingAddedCnecs.addCnec(cnec.getId(), loadFlowResult.getMargin(cnec, Unit.MEGAWATT)); + for (TemporalData scenarioLoadFlowResult : loadFlowResults.values()) { + scenarioLoadFlowResult.getTimestamps().forEach(timestamp -> { + PrePerimeterResult loadFlowResult = scenarioLoadFlowResult.getData(timestamp).orElseThrow(); + Set previousIterationCnecs = consideredCnecs.getData(timestamp).orElseThrow(); + Set nextIterationCnecs = new HashSet<>(previousIterationCnecs); + + double worstConsideredMargin = loadFlowResult.getCostlyElements(MIN_MARGIN_VIOLATION_EVALUATOR, Integer.MAX_VALUE) + .stream() + .filter(previousIterationCnecs::contains) + .findFirst() + .map(cnec -> loadFlowResult.getMargin(cnec, Unit.MEGAWATT)) + .orElse(0.); + + loadFlowResult.getVirtualCostNames().forEach(vcName -> { + LoggingAddedCnecs currentLoggingAddedCnecs = new LoggingAddedCnecs(timestamp, vcName, new ArrayList<>(), new HashMap<>()); + int addedCnecsForVcName = 0; + + // for min margin violation take all cnecs + if (vcName.equals(MIN_MARGIN_VIOLATION_EVALUATOR)) { + for (FlowCnec cnec : loadFlowResult.getCostlyElements(vcName, Integer.MAX_VALUE)) { + if (loadFlowResult.getMargin(cnec, Unit.MEGAWATT) > worstConsideredMargin + marginWindowToConsider && addedCnecsForVcName > cnecsToAddPerVirtualCostName) { + // stop if out of window and already added enough + break; + } else if (!previousIterationCnecs.contains(cnec)) { + // if in window or not added enough yet, add + nextIterationCnecs.add(cnec); + addedCnecsForVcName++; + currentLoggingAddedCnecs.addCnec(cnec.getId(), loadFlowResult.getMargin(cnec, Unit.MEGAWATT)); + } } - } - } else if (loadFlowResult.getVirtualCost(vcName) > 1e-6) { - for (FlowCnec cnec : loadFlowResult.getCostlyElements(vcName, Integer.MAX_VALUE)) { - if (!previousIterationCnecs.contains(cnec)) { - nextIterationCnecs.add(cnec); - currentLoggingAddedCnecs.addCnec(cnec.getId()); + } else if (loadFlowResult.getVirtualCost(vcName) > 1e-6) { + for (FlowCnec cnec : loadFlowResult.getCostlyElements(vcName, Integer.MAX_VALUE)) { + if (!previousIterationCnecs.contains(cnec)) { + nextIterationCnecs.add(cnec); + currentLoggingAddedCnecs.addCnec(cnec.getId()); + } } } - } - addedCnecsForLogging.add(currentLoggingAddedCnecs); + addedCnecsForLogging.add(currentLoggingAddedCnecs); + }); + consideredCnecs.put(timestamp, nextIterationCnecs); }); - consideredCnecs.put(timestamp, nextIterationCnecs); - }); + } logCnecs(addedCnecsForLogging); } @@ -295,7 +385,8 @@ private static void logCnecs(List addedCnecsForLogging) { TECHNICAL_LOGS.info(logMessage.toString()); } - record LoggingAddedCnecs(OffsetDateTime offsetDateTime, String vcName, List addedCnecs, Map margins) { + record LoggingAddedCnecs(OffsetDateTime offsetDateTime, String vcName, List addedCnecs, + Map margins) { private void addCnec(String cnec) { addedCnecs.add(cnec); } @@ -306,25 +397,61 @@ private void addCnec(String cnec, double margin) { } } - private static TemporalData applyActionsAndRunFullLoadflow(TemporalData raoInputs, TemporalData curativeRemedialActions, LinearOptimizationResult filteredResult, TemporalData initialResults, RaoParameters raoParameters) { - TemporalData prePerimeterResults = new TemporalDataImpl<>(); - raoInputs.getDataPerTimestamp().forEach((timestamp, raoInput) -> { - // duplicate the postTopoScenario variant and switch to the new clone - raoInput.getNetwork().getVariantManager().cloneVariant(POST_TOPO_SCENARIO, "PostPreventiveScenario", true); - raoInput.getNetwork().getVariantManager().setWorkingVariant("PostPreventiveScenario"); - State preventiveState = raoInput.getCrac().getPreventiveState(); - raoInput.getCrac().getRangeActions(preventiveState).forEach(rangeAction -> - rangeAction.apply(raoInput.getNetwork(), filteredResult.getOptimizedSetpoint(rangeAction, preventiveState)) - ); - prePerimeterResults.put(timestamp, runInitialPrePerimeterSensitivityAnalysisWithoutRangeActions( - raoInputs.getData(timestamp).orElseThrow(), - curativeRemedialActions.getData(timestamp).orElseThrow(), - initialResults.getData(timestamp).orElseThrow(), - raoParameters)); - // switch back to the postTopoScenario to avoid keeping applied range actions when entering the MIP - raoInput.getNetwork().getVariantManager().setWorkingVariant(POST_TOPO_SCENARIO); - }); - return prePerimeterResults; + private static Map> applyActionsAndRunFullLoadflow(TemporalData raoInputs, TemporalData curativeRemedialActions, LinearOptimizationResult filteredResult, Map> initialResults, RaoParameters raoParameters) { + Map> results = new HashMap<>(); + for (String scenario : SCENARIO_REPO.getScenarios()) { + TemporalData prePerimeterResults = new TemporalDataImpl<>(); + raoInputs.getDataPerTimestamp().forEach((timestamp, raoInput) -> { + // duplicate the postTopoScenario variant and switch to the new clone + raoInput.getNetwork().getVariantManager().cloneVariant(POST_TOPO_SCENARIO, "PostPreventiveScenario", true); + raoInput.getNetwork().getVariantManager().setWorkingVariant("PostPreventiveScenario"); + SCENARIO_REPO.applyScenario(scenario, raoInput.getNetwork(), timestamp); + State preventiveState = raoInput.getCrac().getPreventiveState(); + raoInput.getCrac().getRangeActions(preventiveState).forEach(rangeAction -> + rangeAction.apply(raoInput.getNetwork(), filteredResult.getOptimizedSetpoint(rangeAction, preventiveState)) + ); + prePerimeterResults.put(timestamp, runInitialPrePerimeterSensitivityAnalysisWithoutRangeActions( + raoInputs.getData(timestamp).orElseThrow(), + curativeRemedialActions.getData(timestamp).orElseThrow(), + initialResults.get(scenario).getData(timestamp).orElseThrow(), + raoParameters)); + // switch back to the postTopoScenario to avoid keeping applied range actions when entering the MIP + raoInput.getNetwork().getVariantManager().setWorkingVariant(POST_TOPO_SCENARIO); + }); + results.put(scenario, prePerimeterResults); + } + return results; + } + + + private static Map> runInitialLoadFlow(TemporalData raoInputs, RaoParameters raoParameters, boolean withRangeActions) { + Map> results = new HashMap<>(); + for (String scenario : SCENARIO_REPO.getScenarios()) { + TemporalData prePerimeterResults = new TemporalDataImpl<>(); + raoInputs.getDataPerTimestamp().forEach((timestamp, raoInput) -> { + // duplicate the postTopoScenario variant and switch to the new clone + String initialVariant = raoInput.getNetwork().getVariantManager().getWorkingVariantId(); + String tmpVariant = "InitialLoadFlowVariant"; + raoInput.getNetwork().getVariantManager().cloneVariant(initialVariant, tmpVariant, true); + raoInput.getNetwork().getVariantManager().setWorkingVariant(tmpVariant); + SCENARIO_REPO.applyScenario(scenario, raoInput.getNetwork(), timestamp); + // TODO apply topological results here ? + Set> rangeActions = Set.of(); + if (withRangeActions) { + Crac crac = raoInput.getCrac(); + State preventiveState = crac.getPreventiveState(); + rangeActions = crac.getRangeActions(preventiveState); + } + prePerimeterResults.put(timestamp, runInitialPrePerimeterSensitivityAnalysisWithRangeActions( + raoInputs.getData(timestamp).orElseThrow(), + raoParameters, + rangeActions)); + raoInput.getNetwork().getVariantManager().setWorkingVariant(initialVariant); + raoInput.getNetwork().getVariantManager().removeVariant(tmpVariant); + }); + results.put(scenario, prePerimeterResults); + } + return results; } private void replaceFastRaoResultsWithLightVersions(TemporalData topologicalOptimizationResults) { @@ -370,13 +497,6 @@ private static void applyPreventiveTopologicalActionsOnNetworks(TemporalData buildInitialResults(TemporalData topologicalOptimizationResults) { - TemporalData initialResults = new TemporalDataImpl<>(); - topologicalOptimizationResults.getDataPerTimestamp().forEach((timestamp, raoResult) -> - initialResults.put(timestamp, ((FastRaoResultImpl) raoResult).getInitialResult())); - return initialResults; - } - private static TemporalData runAllSensitivityAnalysesBasedOnInitialResult(TemporalData raoInputs, TemporalData curativeRemedialActions, TemporalData initialFlowResults, RaoParameters raoParameters, TemporalData> consideredCnecs) { TemporalData prePerimeterResults = new TemporalDataImpl<>(); raoInputs.getTimestamps().forEach(timestamp -> { @@ -400,25 +520,38 @@ private static TemporalData getPreventiveTopologicalAction return new TemporalDataImpl<>(preventiveTopologicalActions); } - private static GlobalLinearOptimizationResult optimizeLinearRemedialActions(InterTemporalRaoInput raoInput, TemporalData initialResults, TemporalData initialSetpoints, TemporalData postTopologicalActionsResults, RaoParameters parameters, TemporalData preventiveTopologicalActions, TemporalData curativeRemedialActions, TemporalData> consideredCnecs, ObjectiveFunction objectiveFunction) { + private static GlobalLinearOptimizationResult optimizeLinearRemedialActions(InterTemporalRaoInput raoInput, Map> initialResults, TemporalData initialSetpoints, Map> postTopologicalActionsResults, RaoParameters parameters, TemporalData preventiveTopologicalActions, TemporalData curativeRemedialActions, TemporalData> consideredCnecs, RobustObjectiveFunction objectiveFunction) { // -- Build IteratingLinearOptimizerInterTemporalInput TemporalData optimizationPerimeterPerTimestamp = computeOptimizationPerimetersPerTimestamp(raoInput.getRaoInputs().map(RaoInput::getCrac), consideredCnecs); // no objective function defined in individual IteratingLinearOptimizerInputs as it is global Map linearOptimizerInputPerTimestamp = new HashMap<>(); - raoInput.getRaoInputs().getTimestamps().forEach(timestamp -> linearOptimizerInputPerTimestamp.put(timestamp, IteratingLinearOptimizerInput.create() + // Sending reference flow results, the iterating optimizer will shift them using sensi + // TODO : make it cleaner by sending real loadflow results + raoInput.getRaoInputs().getTimestamps().forEach(timestamp -> { + Map initialResultsPerScenario = new HashMap<>(); + Map preoptimResultsPerScenario = new HashMap<>(); + Map sensiResultsPerScenario = new HashMap<>(); + for (String scenario : initialResults.keySet()) { + initialResultsPerScenario.put(scenario, initialResults.get(scenario).getData(timestamp).orElseThrow()); + preoptimResultsPerScenario.put(scenario, postTopologicalActionsResults.get(scenario).getData(timestamp).orElseThrow()); + sensiResultsPerScenario.put(scenario, postTopologicalActionsResults.get(scenario).getData(timestamp).orElseThrow()); + } + linearOptimizerInputPerTimestamp.put(timestamp, IteratingLinearOptimizerInput.create() .withNetwork(raoInput.getRaoInputs().getData(timestamp).orElseThrow().getNetwork()) .withOptimizationPerimeter(optimizationPerimeterPerTimestamp.getData(timestamp).orElseThrow()) - .withInitialFlowResult(initialResults.getData(timestamp).orElseThrow()) - .withPrePerimeterFlowResult(initialResults.getData(timestamp).orElseThrow()) - .withPreOptimizationFlowResult(postTopologicalActionsResults.getData(timestamp).orElseThrow()) + .withInitialFlowResultPerScenario(initialResultsPerScenario) + .withPrePerimeterFlowResultPerScenario(initialResultsPerScenario) + .withPreOptimizationFlowResultPerScenario(preoptimResultsPerScenario) .withPrePerimeterSetpoints(initialSetpoints.getData(timestamp).orElseThrow()) - .withPreOptimizationSensitivityResult(postTopologicalActionsResults.getData(timestamp).orElseThrow()) + .withPreOptimizationSensitivityResultPerScenario(sensiResultsPerScenario) .withPreOptimizationAppliedRemedialActions(curativeRemedialActions.getData(timestamp).orElseThrow()) .withToolProvider(ToolProvider.buildFromRaoInputAndParameters(raoInput.getRaoInputs().getData(timestamp).orElseThrow(), parameters)) .withOutageInstant(raoInput.getRaoInputs().getData(timestamp).orElseThrow().getCrac().getOutageInstant()) .withAppliedNetworkActionsInPrimaryState(preventiveTopologicalActions.getData(timestamp).orElseThrow()) - .build())); + .build()); + } + ); InterTemporalIteratingLinearOptimizerInput interTemporalLinearOptimizerInput = new InterTemporalIteratingLinearOptimizerInput(new TemporalDataImpl<>(linearOptimizerInputPerTimestamp), objectiveFunction, raoInput.getGeneratorConstraints()); // Build parameters @@ -441,7 +574,8 @@ private static GlobalLinearOptimizationResult optimizeLinearRemedialActions(Inte parameters.getExtension(OpenRaoSearchTreeParameters.class).getLoopFlowParameters().ifPresent(linearOptimizerParametersBuilder::withLoopFlowParametersExtension); IteratingLinearOptimizerParameters linearOptimizerParameters = linearOptimizerParametersBuilder.build(); - return InterTemporalIteratingLinearOptimizer.optimize(interTemporalLinearOptimizerInput, linearOptimizerParameters); + //return InterTemporalIteratingLinearOptimizer.optimize(interTemporalLinearOptimizerInput, linearOptimizerParameters); + return RobustInterTemporalIteratingLinearOptimizer.optimize(interTemporalLinearOptimizerInput, linearOptimizerParameters, SCENARIO_REPO); } private static TemporalData computeOptimizationPerimetersPerTimestamp(TemporalData cracs, TemporalData> consideredCnecs) { @@ -471,11 +605,13 @@ private static InterTemporalRaoResultImpl mergeTopologicalAndLinearOptimizationR raoParameters)); } - private static ObjectiveFunction buildGlobalObjectiveFunction(TemporalData cracs, FlowResult globalInitialFlowResult, RaoParameters raoParameters) { + private static RobustObjectiveFunction buildGlobalObjectiveFunction(TemporalData cracs, Map globalInitialFlowResult, RaoParameters raoParameters) { Set allFlowCnecs = new HashSet<>(); cracs.map(MarmotUtils::getPreventivePerimeterCnecs).getDataPerTimestamp().values().forEach(allFlowCnecs::addAll); Set allOptimizedStates = new HashSet<>(cracs.map(Crac::getPreventiveState).getDataPerTimestamp().values()); - return ObjectiveFunction.build(allFlowCnecs, + return RobustObjectiveFunction.build( + SCENARIO_REPO, + allFlowCnecs, new HashSet<>(), // no loop flows for now globalInitialFlowResult, globalInitialFlowResult, // always building from preventive so prePerimeter = initial @@ -484,12 +620,13 @@ private static ObjectiveFunction buildGlobalObjectiveFunction(TemporalData allOptimizedStates); } - private static ObjectiveFunction buildFilteredObjectiveFunction(TemporalData cracs, FlowResult globalInitialFlowResult, RaoParameters raoParameters, TemporalData> consideredCnecs) { + private static RobustObjectiveFunction buildFilteredObjectiveFunction(TemporalData cracs, Map globalInitialFlowResult, RaoParameters raoParameters, TemporalData> consideredCnecs) { Set flatConsideredCnecs = new HashSet<>(); consideredCnecs.getDataPerTimestamp().values().forEach(flatConsideredCnecs::addAll); Set allOptimizedStates = new HashSet<>(cracs.map(Crac::getPreventiveState).getDataPerTimestamp().values()); - return ObjectiveFunction.build( + return RobustObjectiveFunction.build( + SCENARIO_REPO, flatConsideredCnecs, new HashSet<>(), // no loop flows for now globalInitialFlowResult, @@ -499,16 +636,32 @@ private static ObjectiveFunction buildFilteredObjectiveFunction(TemporalData prePerimeterResults, ObjectiveFunction objectiveFunction) { - TemporalData rangeActionActivationResults = prePerimeterResults.map(RangeActionActivationResultImpl::new); + private LinearOptimizationResult getInitialObjectiveFunctionResult(Map> prePerimeterResults, RobustObjectiveFunction objectiveFunction) { + TemporalData rangeActionActivationResults = prePerimeterResults.values().stream().findAny().orElseThrow().map(RangeActionActivationResultImpl::new); // all RA activations should be the same TemporalData networkActionsResults = new TemporalDataImpl<>(); - return new GlobalLinearOptimizationResult(prePerimeterResults.map(PrePerimeterResult::getFlowResult), prePerimeterResults.map(PrePerimeterResult::getSensitivityResult), rangeActionActivationResults, networkActionsResults, objectiveFunction, LinearProblemStatus.OPTIMAL); + Map> sensitivityResults = new HashMap<>(); + for (String scenario : prePerimeterResults.keySet()) { + sensitivityResults.put(scenario, prePerimeterResults.get(scenario).map(PrePerimeterResult::getSensitivityResult)); + } + Map> casted = new HashMap<>(); + for (String scenario : prePerimeterResults.keySet()) { + casted.put(scenario, prePerimeterResults.get(scenario).map(FlowResult.class::cast)); + } + return new GlobalLinearOptimizationResult(casted, sensitivityResults, rangeActionActivationResults, networkActionsResults, objectiveFunction, LinearProblemStatus.OPTIMAL); } - private LinearOptimizationResult getPostTopologicalOptimizationResult(TemporalData allInitialSetPoints, TemporalData prePerimeterResults, ObjectiveFunction objectiveFunction, TemporalData topologicalOptimizationResults, TemporalData preventiveStates) { + private LinearOptimizationResult getPostTopologicalOptimizationResult(TemporalData allInitialSetPoints, Map> prePerimeterResults, RobustObjectiveFunction objectiveFunction, TemporalData topologicalOptimizationResults, TemporalData preventiveStates) { TemporalData rangeActionActivationResults = getRangeActionActivationResults(allInitialSetPoints, topologicalOptimizationResults, preventiveStates); TemporalData networkActionsResults = getNetworkActionActivationResults(topologicalOptimizationResults, preventiveStates); - return new GlobalLinearOptimizationResult(prePerimeterResults.map(PrePerimeterResult::getFlowResult), prePerimeterResults.map(PrePerimeterResult::getSensitivityResult), rangeActionActivationResults, networkActionsResults, objectiveFunction, LinearProblemStatus.OPTIMAL); + Map> sensitivityResults = new HashMap<>(); + for (String scenario : prePerimeterResults.keySet()) { + sensitivityResults.put(scenario, prePerimeterResults.get(scenario).map(PrePerimeterResult::getSensitivityResult)); + } + Map> casted = new HashMap<>(); + for (String scenario : prePerimeterResults.keySet()) { + casted.put(scenario, prePerimeterResults.get(scenario).map(FlowResult.class::cast)); + } + return new GlobalLinearOptimizationResult(casted, sensitivityResults, rangeActionActivationResults, networkActionsResults, objectiveFunction, LinearProblemStatus.OPTIMAL); } private static TemporalData getRangeActionActivationResults(TemporalData allInitialSetPoints, TemporalData topologicalOptimizationResults, TemporalData preventiveStates) { diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/MarmotUtils.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/MarmotUtils.java index 79f731acfb..d2b35fd3ed 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/MarmotUtils.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/MarmotUtils.java @@ -68,6 +68,18 @@ public static PrePerimeterResult runInitialPrePerimeterSensitivityAnalysisWithou ).runBasedOnInitialResults(network, initialResult, null, curativeRemedialActions); } + public static PrePerimeterResult runInitialPrePerimeterSensitivityAnalysisWithRangeActions(RaoInput raoInput, RaoParameters raoParameters, Set> rangeActions) { + Crac crac = raoInput.getCrac(); + Network network = raoInput.getNetwork(); + return new PrePerimeterSensitivityAnalysis( + crac, + crac.getFlowCnecs(), // want results on all cnecs + rangeActions, + raoParameters, + ToolProvider.buildFromRaoInputAndParameters(raoInput, raoParameters) + ).runInitialSensitivityAnalysis(network); + } + public static TemporalData getAppliedRemedialActionsInCurative(TemporalData inputs, TemporalData raoResults) { TemporalData curativeRemedialActions = new TemporalDataImpl<>(); inputs.getTimestamps().forEach(timestamp -> { diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/InterTemporalIteratingLinearOptimizer.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/RobustInterTemporalIteratingLinearOptimizer.java similarity index 82% rename from ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/InterTemporalIteratingLinearOptimizer.java rename to ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/RobustInterTemporalIteratingLinearOptimizer.java index d8cb04a114..8f5d97eeec 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/InterTemporalIteratingLinearOptimizer.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/RobustInterTemporalIteratingLinearOptimizer.java @@ -17,7 +17,6 @@ import com.powsybl.openrao.data.raoresult.api.ComputationStatus; import com.powsybl.openrao.raoapi.parameters.extensions.SearchTreeRaoRangeActionsOptimizationParameters; import com.powsybl.openrao.searchtreerao.commons.SensitivityComputer; -import com.powsybl.openrao.searchtreerao.commons.objectivefunction.ObjectiveFunction; import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.GlobalOptimizationPerimeter; import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.OptimizationPerimeter; import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.BestTapFinder; @@ -27,9 +26,11 @@ import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.ProblemFiller; import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.linearproblem.LinearProblem; import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.linearproblem.LinearProblemBuilder; +import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.linearproblem.LinearProblemIdGenerator; import com.powsybl.openrao.searchtreerao.linearoptimisation.inputs.IteratingLinearOptimizerInput; import com.powsybl.openrao.searchtreerao.linearoptimisation.parameters.IteratingLinearOptimizerParameters; import com.powsybl.openrao.searchtreerao.marmot.results.GlobalLinearOptimizationResult; +import com.powsybl.openrao.searchtreerao.marmot.scenariobuilder.ScenarioRepo; import com.powsybl.openrao.searchtreerao.result.api.*; import com.powsybl.openrao.searchtreerao.result.impl.LinearProblemResult; import com.powsybl.openrao.searchtreerao.result.impl.RangeActionActivationResultImpl; @@ -37,11 +38,7 @@ import org.apache.commons.lang3.tuple.Pair; import java.time.OffsetDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import static com.powsybl.openrao.commons.logs.OpenRaoLoggerProvider.*; @@ -51,16 +48,28 @@ * @author Thomas Bouquet {@literal } * @author Godelaine de Montmorillon {@literal } */ -public final class InterTemporalIteratingLinearOptimizer { +public final class RobustInterTemporalIteratingLinearOptimizer { - private InterTemporalIteratingLinearOptimizer() { + private RobustInterTemporalIteratingLinearOptimizer() { } - public static GlobalLinearOptimizationResult optimize(InterTemporalIteratingLinearOptimizerInput input, IteratingLinearOptimizerParameters parameters) { + public static GlobalLinearOptimizationResult optimize(InterTemporalIteratingLinearOptimizerInput input, IteratingLinearOptimizerParameters parameters, ScenarioRepo scenarioRepo) { // 1. Initialize best result using input data + Map> preOptimizationFlowResults = new HashMap<>(); + Map> preOptimizationSensiResults = new HashMap<>(); + for (String scenario : scenarioRepo.getScenarios()) { + TemporalData flowResults = new TemporalDataImpl<>(); + TemporalData sensiResults = new TemporalDataImpl<>(); + for (OffsetDateTime ts : input.iteratingLinearOptimizerInputs().getTimestamps()) { + flowResults.put(ts, input.iteratingLinearOptimizerInputs().getData(ts).orElseThrow().preOptimizationFlowResultPerScenario().get(scenario)); + sensiResults.put(ts, input.iteratingLinearOptimizerInputs().getData(ts).orElseThrow().preOptimizationSensitivityResultPerScenario().get(scenario)); + } + preOptimizationFlowResults.put(scenario, flowResults); + preOptimizationSensiResults.put(scenario, sensiResults); + } GlobalLinearOptimizationResult bestResult = createInitialResult( - input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::preOptimizationFlowResult), - input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::preOptimizationSensitivityResult), + preOptimizationFlowResults, + preOptimizationSensiResults, input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::prePerimeterSetpoints).map(RangeActionActivationResultImpl::new), input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::appliedNetworkActionsInPrimaryState), input.objectiveFunction() @@ -72,17 +81,43 @@ public static GlobalLinearOptimizationResult optimize(InterTemporalIteratingLine // 2. Initialize linear problem using input data TemporalData> problemFillers = getProblemFillersPerTimestamp(input, parameters); List interTemporalProblemFillers = getInterTemporalProblemFillers(input); - LinearProblem linearProblem = buildLinearProblem(problemFillers, interTemporalProblemFillers, parameters); - fillLinearProblem( - linearProblem, - problemFillers, - interTemporalProblemFillers, - input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::preOptimizationFlowResult), - input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::preOptimizationSensitivityResult), - input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::prePerimeterSetpoints)); + LinearProblem linearProblem = buildLinearProblem(problemFillers, parameters); + + + //TemporalData sensitivityResults = input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::preOptimizationSensitivityResult); + //TemporalData networks = input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::network); + for (String scenarioId : scenarioRepo.getScenarios()) { + TemporalData preOptimizationFlowResult = new TemporalDataImpl<>(); + /*TemporalData initPreOptimizationFlowResult = input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::preOptimizationFlowResult); + Map> shifts = scenarioRepo.computeShifts(scenarioId, networks); + for (OffsetDateTime timestamp : initPreOptimizationFlowResult.getTimestamps()) { + Map shiftedInjections = new HashMap<>(); + for (String injectionId : shifts.keySet()) { + shiftedInjections.put(injectionId, shifts.get(injectionId).getData(timestamp).orElse(0.)); + } + preOptimizationFlowResult.put(timestamp, new ShiftedFlowResult(initPreOptimizationFlowResult.getData(timestamp).orElseThrow(), shiftedInjections, sensitivityResults.getData(timestamp).orElseThrow())); + }*/ + + TemporalData sensitivityResults = new TemporalDataImpl<>(); + for (OffsetDateTime timestamp : input.iteratingLinearOptimizerInputs().getTimestamps()) { + sensitivityResults.put(timestamp, input.iteratingLinearOptimizerInputs().getData(timestamp).orElseThrow().preOptimizationSensitivityResultPerScenario().get(scenarioId)); + preOptimizationFlowResult.put(timestamp, input.iteratingLinearOptimizerInputs().getData(timestamp).orElseThrow().preOptimizationFlowResultPerScenario().get(scenarioId)); + } + + LinearProblemIdGenerator.setPrefix("scenario(" + scenarioId + ")"); + fillLinearProblem( + linearProblem, + problemFillers, + interTemporalProblemFillers, + preOptimizationFlowResult, + sensitivityResults, + input.iteratingLinearOptimizerInputs().map(IteratingLinearOptimizerInput::prePerimeterSetpoints)); + } // 3. Iterate - for (int iteration = 1; iteration <= parameters.getMaxNumberOfIterations(); iteration++) { + // TODO support multiple iterations + // for now we can't update the MIP + for (int iteration = 1; iteration <= 1; iteration++) { // a. Solve linear problem LinearProblemStatus solveStatus = solveLinearProblem(linearProblem, iteration); // b. Check linear problem status and return best result if not FEASIBLE not OPTIMAL @@ -123,7 +158,7 @@ public static GlobalLinearOptimizationResult optimize(InterTemporalIteratingLine // e. Run sensitivity analyses with new set-points Map newSensitivityComputers = new HashMap<>(); for (OffsetDateTime timestamp : rangeActionActivationPerTimestamp.getTimestamps()) { - newSensitivityComputers.put(timestamp, runSensitivityAnalysis(sensitivityComputers.getData(timestamp).orElse(null), iteration, rangeActionActivationPerTimestamp.getData(timestamp).orElseThrow(), input.iteratingLinearOptimizerInputs().getData(timestamp).orElseThrow(), parameters)); + newSensitivityComputers.put(timestamp, runSensitivityAnalysis(sensitivityComputers.getData(timestamp).orElse(null), iteration, rangeActionActivationPerTimestamp.getData(timestamp).orElseThrow(), input.iteratingLinearOptimizerInputs().getData(timestamp).orElseThrow(), parameters, scenarioRepo)); } if (newSensitivityComputers.values().stream().anyMatch(sensitivityComputer -> sensitivityComputer.getSensitivityResult().getSensitivityStatus() == ComputationStatus.FAILURE)) { @@ -174,7 +209,7 @@ private static Set filterPreventiveInjectionRangeAction(Se return rangeActions.stream().filter(InjectionRangeAction.class::isInstance).map(InjectionRangeAction.class::cast).collect(Collectors.toSet()); } - private static LinearProblem buildLinearProblem(TemporalData> problemFillers, List interTemporalProblemFillers, IteratingLinearOptimizerParameters parameters) { + private static LinearProblem buildLinearProblem(TemporalData> problemFillers, IteratingLinearOptimizerParameters parameters) { LinearProblemBuilder linearProblemBuilder = LinearProblem.create() .withSolver(parameters.getSolverParameters().getSolver()) .withRelativeMipGap(parameters.getSolverParameters().getRelativeMipGap()) @@ -182,7 +217,6 @@ private static LinearProblem buildLinearProblem(TemporalData // add problem fillers for each timestamp and inter-temporal timestamps problemFillers.getDataPerTimestamp().values().forEach(problemFillerOfTimestamp -> problemFillerOfTimestamp.forEach(linearProblemBuilder::withProblemFiller)); - interTemporalProblemFillers.forEach(linearProblemBuilder::withProblemFiller); return linearProblemBuilder.build(); } @@ -207,6 +241,9 @@ private static void updateLinearProblemBetweenMipIterations(LinearProblem linear } private static void updateLinearProblemBetweenSensiComputations(LinearProblem linearProblem, TemporalData> problemFillers, List interTemporalProblemFillers, LinearOptimizationResult optimizationResult) { + return; + // TODO + /* linearProblem.reset(); List timestamps = problemFillers.getTimestamps(); timestamps.forEach(timestamp -> { @@ -214,6 +251,8 @@ private static void updateLinearProblemBetweenSensiComputations(LinearProblem li problemFillersForTimestamp.forEach(problemFiller -> problemFiller.fill(linearProblem, optimizationResult, optimizationResult, optimizationResult)); }); interTemporalProblemFillers.forEach(problemFiller -> problemFiller.fill(linearProblem, null, null, null)); + + */ } private static LinearProblemStatus solveLinearProblem(LinearProblem linearProblem, int iteration) { @@ -225,30 +264,31 @@ private static LinearProblemStatus solveLinearProblem(LinearProblem linearProble // Sensitivity analysis - private static SensitivityComputer runSensitivityAnalysis(SensitivityComputer sensitivityComputer, int iteration, RangeActionActivationResult currentRangeActionActivationResult, IteratingLinearOptimizerInput input, IteratingLinearOptimizerParameters parameters) { + private static SensitivityComputer runSensitivityAnalysis(SensitivityComputer sensitivityComputer, int iteration, RangeActionActivationResult currentRangeActionActivationResult, IteratingLinearOptimizerInput input, IteratingLinearOptimizerParameters parameters, ScenarioRepo scenarioRepo) { SensitivityComputer tmpSensitivityComputer = sensitivityComputer; // TODO: if we want to force 2P, shoud always be global if (input.optimizationPerimeter() instanceof GlobalOptimizationPerimeter) { AppliedRemedialActions appliedRemedialActionsInSecondaryStates = IteratingLinearOptimizer.applyRangeActions(currentRangeActionActivationResult, input); - tmpSensitivityComputer = createSensitivityComputer(appliedRemedialActionsInSecondaryStates, input, parameters); + tmpSensitivityComputer = createSensitivityComputer(appliedRemedialActionsInSecondaryStates, input, parameters, scenarioRepo); } else { IteratingLinearOptimizer.applyRangeActions(currentRangeActionActivationResult, input); if (tmpSensitivityComputer == null) { // first iteration, do not need to be updated afterwards - tmpSensitivityComputer = createSensitivityComputer(input.preOptimizationAppliedRemedialActions(), input, parameters); + tmpSensitivityComputer = createSensitivityComputer(input.preOptimizationAppliedRemedialActions(), input, parameters, scenarioRepo); } } runSensitivityAnalysis(tmpSensitivityComputer, input.network(), iteration); return tmpSensitivityComputer; } - private static SensitivityComputer createSensitivityComputer(AppliedRemedialActions appliedRemedialActions, IteratingLinearOptimizerInput input, IteratingLinearOptimizerParameters parameters) { + private static SensitivityComputer createSensitivityComputer(AppliedRemedialActions appliedRemedialActions, IteratingLinearOptimizerInput input, IteratingLinearOptimizerParameters parameters, ScenarioRepo scenarioRepo) { SensitivityComputer.SensitivityComputerBuilder builder = SensitivityComputer.create() .withCnecs(input.optimizationPerimeter().getFlowCnecs()) .withRangeActions(input.optimizationPerimeter().getRangeActions()) .withAppliedRemedialActions(appliedRemedialActions) .withToolProvider(input.toolProvider()) - .withOutageInstant(input.outageInstant()); + .withOutageInstant(input.outageInstant()) + .withExtraInjections(scenarioRepo.getInjectionIds()); if (parameters.isRaoWithLoopFlowLimitation() && parameters.getLoopFlowParametersExtension().getPtdfApproximation().shouldUpdatePtdfWithPstChange()) { builder.withCommercialFlowsResults(input.toolProvider().getLoopFlowComputation(), input.optimizationPerimeter().getLoopFlowCnecs()); @@ -274,17 +314,23 @@ private static void runSensitivityAnalysis(SensitivityComputer sensitivityComput } // Result management - private static GlobalLinearOptimizationResult createInitialResult(TemporalData flowResults, TemporalData sensitivityResults, TemporalData rangeActionActivations, TemporalData preventiveTopologicalActions, ObjectiveFunction objectiveFunction) { + private static GlobalLinearOptimizationResult createInitialResult(Map> flowResults, Map> sensitivityResults, TemporalData rangeActionActivations, TemporalData preventiveTopologicalActions, RobustObjectiveFunction objectiveFunction) { return new GlobalLinearOptimizationResult(flowResults, sensitivityResults, rangeActionActivations, preventiveTopologicalActions, objectiveFunction, LinearProblemStatus.OPTIMAL); } - private static GlobalLinearOptimizationResult createResultFromData(TemporalData sensitivityComputers, TemporalData networks, TemporalData rangeActionActivation, TemporalData preventiveTopologicalActions, ObjectiveFunction objectiveFunction) { - Map flowResults = new HashMap<>(); - for (OffsetDateTime timestamp : sensitivityComputers.getTimestamps()) { - FlowResult flowResult = sensitivityComputers.getData(timestamp).orElseThrow().getBranchResult(networks.getData(timestamp).orElseThrow()); - flowResults.put(timestamp, flowResult); + private static GlobalLinearOptimizationResult createResultFromData(TemporalData sensitivityComputers, TemporalData networks, TemporalData rangeActionActivation, TemporalData preventiveTopologicalActions, RobustObjectiveFunction objectiveFunction) { + Map> scenarizedFlowResults = new HashMap<>(); + Map> scenarizedSensitivityResults = new HashMap<>(); + for (String scenario : Marmot.getScenarioRepo().getScenarios()) { + Map flowResults = new HashMap<>(); + for (OffsetDateTime timestamp : sensitivityComputers.getTimestamps()) { + FlowResult flowResult = sensitivityComputers.getData(timestamp).orElseThrow().getBranchResult(networks.getData(timestamp).orElseThrow()); + flowResults.put(timestamp, flowResult); + } + scenarizedFlowResults.put(scenario, new TemporalDataImpl<>(flowResults)); + scenarizedSensitivityResults.put(scenario, sensitivityComputers.map(SensitivityComputer::getSensitivityResult)); } - return new GlobalLinearOptimizationResult(new TemporalDataImpl<>(flowResults), sensitivityComputers.map(SensitivityComputer::getSensitivityResult), rangeActionActivation, preventiveTopologicalActions, objectiveFunction, LinearProblemStatus.OPTIMAL); + return new GlobalLinearOptimizationResult(scenarizedFlowResults, scenarizedSensitivityResults, rangeActionActivation, preventiveTopologicalActions, objectiveFunction, LinearProblemStatus.OPTIMAL); } // Set-point rounding diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/RobustObjectiveFunction.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/RobustObjectiveFunction.java new file mode 100644 index 0000000000..0d059e2e39 --- /dev/null +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/RobustObjectiveFunction.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.powsybl.openrao.searchtreerao.marmot; + +import com.powsybl.openrao.data.crac.api.State; +import com.powsybl.openrao.data.crac.api.cnec.FlowCnec; +import com.powsybl.openrao.raoapi.parameters.RaoParameters; +import com.powsybl.openrao.searchtreerao.commons.objectivefunction.ObjectiveFunction; +import com.powsybl.openrao.searchtreerao.marmot.scenariobuilder.ScenarioRepo; +import com.powsybl.openrao.searchtreerao.result.api.FlowResult; +import com.powsybl.openrao.searchtreerao.result.api.ObjectiveFunctionResult; +import com.powsybl.openrao.searchtreerao.result.api.RemedialActionActivationResult; + +import java.util.*; + +import static com.powsybl.openrao.commons.logs.OpenRaoLoggerProvider.TECHNICAL_LOGS; + +public class RobustObjectiveFunction { + private final Map objectiveFunctionPerScenario; + + public RobustObjectiveFunction(Map objectiveFunctionPerScenario) { + this.objectiveFunctionPerScenario = objectiveFunctionPerScenario; + } + + public ObjectiveFunctionResult evaluate(Map flowResults, RemedialActionActivationResult remedialActionActivationResult) { + ObjectiveFunctionResult worstResult = null; + String worstScenario = null; + for (String scenario : objectiveFunctionPerScenario.keySet()) { + ObjectiveFunctionResult result = objectiveFunctionPerScenario.get(scenario).evaluate(flowResults.get(scenario), remedialActionActivationResult); + if ((worstResult == null) || (result.getCost() > worstResult.getCost())) { + worstResult = result; + worstScenario = scenario; + } + } + TECHNICAL_LOGS.info(String.format("Worst scenario: %s, cost = %.2f", worstScenario, worstResult.getCost())); + return worstResult; + } + + public static RobustObjectiveFunction buildForInitialSensitivityComputation(ScenarioRepo scenarioRepo, Set flowCnecs, RaoParameters raoParameters, Set optimizedStates) { + Map objectiveFunctionPerScenario = new HashMap<>(); + for (String scenario : scenarioRepo.getScenarios()) { + ObjectiveFunction objectiveFunction = ObjectiveFunction.buildForInitialSensitivityComputation( + flowCnecs, raoParameters, optimizedStates + ); + objectiveFunctionPerScenario.put(scenario, objectiveFunction); + } + return new RobustObjectiveFunction(objectiveFunctionPerScenario); + } + + public static RobustObjectiveFunction build(ScenarioRepo scenarioRepo, + Set flowCnecs, + Set loopFlowCnecs, + Map initialFlowResult, + Map prePerimeterFlowResult, + Set operatorsNotToOptimizeInCurative, + RaoParameters raoParameters, + Set optimizedStates + ) { + Map objectiveFunctionPerScenario = new HashMap<>(); + for (String scenario : scenarioRepo.getScenarios()) { + ObjectiveFunction objectiveFunction = ObjectiveFunction.build( + flowCnecs, loopFlowCnecs, initialFlowResult.get(scenario), prePerimeterFlowResult.get(scenario), operatorsNotToOptimizeInCurative, raoParameters, optimizedStates + ); + objectiveFunctionPerScenario.put(scenario, objectiveFunction); + } + return new RobustObjectiveFunction(objectiveFunctionPerScenario); + } +} diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/results/GlobalLinearOptimizationResult.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/results/GlobalLinearOptimizationResult.java index 8bdbffdae1..3c97d9c9d0 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/results/GlobalLinearOptimizationResult.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/results/GlobalLinearOptimizationResult.java @@ -17,17 +17,12 @@ import com.powsybl.openrao.data.crac.api.rangeaction.PstRangeAction; import com.powsybl.openrao.data.crac.api.rangeaction.RangeAction; import com.powsybl.openrao.data.raoresult.api.ComputationStatus; -import com.powsybl.openrao.searchtreerao.commons.objectivefunction.ObjectiveFunction; -import com.powsybl.openrao.searchtreerao.result.api.FlowResult; -import com.powsybl.openrao.searchtreerao.result.api.LinearOptimizationResult; -import com.powsybl.openrao.searchtreerao.result.api.LinearProblemStatus; -import com.powsybl.openrao.searchtreerao.result.api.NetworkActionsResult; -import com.powsybl.openrao.searchtreerao.result.api.ObjectiveFunctionResult; -import com.powsybl.openrao.searchtreerao.result.api.RangeActionActivationResult; -import com.powsybl.openrao.searchtreerao.result.api.SensitivityResult; +import com.powsybl.openrao.searchtreerao.marmot.RobustObjectiveFunction; +import com.powsybl.openrao.searchtreerao.result.api.*; import com.powsybl.sensitivity.SensitivityVariableSet; import java.time.OffsetDateTime; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -39,15 +34,18 @@ * @author Godelaine de Montmorillon {@literal } */ public class GlobalLinearOptimizationResult implements LinearOptimizationResult { - private final GlobalFlowResult globalFlowResult; - private final GlobalSensitivityResult globalSensitivityResult; + private final Map globalFlowResult; + private final GlobalSensitivityResult globalSensitivityResult; // TODO make it a map ? private final GlobalRangeActionActivationResult globalRangeActionActivationResult; private final ObjectiveFunctionResult globalObjectiveFunctionResult; private LinearProblemStatus status; - public GlobalLinearOptimizationResult(TemporalData flowResults, TemporalData sensitivityResults, TemporalData rangeActionActivationResults, TemporalData preventiveTopologicalActions, ObjectiveFunction objectiveFunction, LinearProblemStatus status) { - this.globalFlowResult = new GlobalFlowResult(flowResults); - this.globalSensitivityResult = new GlobalSensitivityResult(sensitivityResults); + public GlobalLinearOptimizationResult(Map> flowResults, Map> sensitivityResults, TemporalData rangeActionActivationResults, TemporalData preventiveTopologicalActions, RobustObjectiveFunction objectiveFunction, LinearProblemStatus status) { + this.globalFlowResult = new HashMap<>(); + for (String scenario : flowResults.keySet()) { + this.globalFlowResult.put(scenario, new GlobalFlowResult(flowResults.get(scenario))); // TODO make this cleaner + } + this.globalSensitivityResult = new GlobalSensitivityResult(sensitivityResults.values().stream().findAny().orElseThrow()); // TODO ? this.globalRangeActionActivationResult = new GlobalRangeActionActivationResult(rangeActionActivationResults); this.globalObjectiveFunctionResult = objectiveFunction.evaluate(globalFlowResult, new GlobalRemedialActionActivationResult(rangeActionActivationResults, preventiveTopologicalActions)); this.status = status; @@ -62,7 +60,7 @@ public TemporalData getRangeActionActivationResultT } public FlowResult getFlowResult(OffsetDateTime timestamp) { - return globalFlowResult.getIndividualResult(timestamp); + return ((GlobalFlowResult) globalFlowResult.values().stream().findAny().orElseThrow()).getIndividualResult(timestamp); // TODO } public SensitivityResult getSensitivityResult(OffsetDateTime timestamp) { @@ -75,32 +73,32 @@ public RangeActionActivationResult getRangeActionActivationResult(OffsetDateTime @Override public double getFlow(FlowCnec flowCnec, TwoSides side, Unit unit) { - return globalFlowResult.getFlow(flowCnec, side, unit); + return globalFlowResult.values().stream().findAny().orElseThrow().getFlow(flowCnec, side, unit); // TODO } @Override public double getFlow(FlowCnec flowCnec, TwoSides side, Unit unit, Instant optimizedInstant) { - return globalFlowResult.getFlow(flowCnec, side, unit, optimizedInstant); + return globalFlowResult.values().stream().findAny().orElseThrow().getFlow(flowCnec, side, unit, optimizedInstant); // TODO } @Override public double getMargin(FlowCnec flowCnec, Unit unit) { - return globalFlowResult.getMargin(flowCnec, unit); + return globalFlowResult.values().stream().findAny().orElseThrow().getMargin(flowCnec, unit); // TODO } @Override public double getCommercialFlow(FlowCnec flowCnec, TwoSides side, Unit unit) { - return globalFlowResult.getCommercialFlow(flowCnec, side, unit); + return globalFlowResult.values().stream().findAny().orElseThrow().getCommercialFlow(flowCnec, side, unit); // TODO } @Override public double getPtdfZonalSum(FlowCnec flowCnec, TwoSides side) { - return globalFlowResult.getPtdfZonalSum(flowCnec, side); + return globalFlowResult.values().stream().findAny().orElseThrow().getPtdfZonalSum(flowCnec, side); // TODO } @Override public Map> getPtdfZonalSums() { - return globalFlowResult.getPtdfZonalSums(); + return globalFlowResult.values().stream().findAny().orElseThrow().getPtdfZonalSums(); // TODO } public void setStatus(LinearProblemStatus status) { @@ -226,4 +224,9 @@ public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, RangeAction< public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityVariableSet linearGlsk, Unit unit) { return globalSensitivityResult.getSensitivityValue(flowCnec, side, linearGlsk, unit); } + + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + return globalSensitivityResult.getSensitivityValue(flowCnec, side, variableId, unit); + } } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/results/GlobalSensitivityResult.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/results/GlobalSensitivityResult.java index 9696880ff1..9cccb21710 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/results/GlobalSensitivityResult.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/results/GlobalSensitivityResult.java @@ -59,4 +59,9 @@ public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, RangeAction< public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityVariableSet linearGlsk, Unit unit) { return MarmotUtils.getDataFromState(resultPerTimestamp, flowCnec.getState()).getSensitivityValue(flowCnec, side, linearGlsk, unit); } + + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + return MarmotUtils.getDataFromState(resultPerTimestamp, flowCnec.getState()).getSensitivityValue(flowCnec, side, variableId, unit); + } } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/AbstractNetworkVariation.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/AbstractNetworkVariation.java new file mode 100644 index 0000000000..e8d4cea0a7 --- /dev/null +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/AbstractNetworkVariation.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.powsybl.openrao.searchtreerao.marmot.scenariobuilder; + +import com.powsybl.openrao.commons.TemporalData; + +/** + * @author Peter Mitri {@literal } + */ +public abstract class AbstractNetworkVariation implements NetworkVariation { + protected String id; + protected String networkElementId; + protected TemporalData values; + + protected AbstractNetworkVariation(String id, String networkElementId, TemporalData values) { + this.id = id; + this.networkElementId = networkElementId; + this.values = values; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getNetworkElementId() { + return networkElementId; + } +} diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/GeneratorTargetPNetworkVariation.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/GeneratorTargetPNetworkVariation.java new file mode 100644 index 0000000000..6dca784ba7 --- /dev/null +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/GeneratorTargetPNetworkVariation.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.powsybl.openrao.searchtreerao.marmot.scenariobuilder; + +import com.powsybl.action.GeneratorActionBuilder; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.openrao.commons.OpenRaoException; +import com.powsybl.openrao.commons.TemporalData; +import com.powsybl.openrao.commons.TemporalDataImpl; + +import java.time.OffsetDateTime; + + +/** + * @author Peter Mitri {@literal } + */ +public class GeneratorTargetPNetworkVariation extends AbstractNetworkVariation implements NetworkVariation { + + public GeneratorTargetPNetworkVariation(String id, String networkElementId, TemporalData values) { + super(id, networkElementId, values); + } + + @Override + public TemporalData computeShifts(TemporalData networks) { + return inspect(networks, false); + } + + @Override + public TemporalData apply(TemporalData networks) { + return inspect(networks, true); + } + + @Override + public void apply(Network network, OffsetDateTime timestamp) { + new GeneratorActionBuilder() + .withId("id") + .withGeneratorId(networkElementId) + .withActivePowerRelativeValue(false) + .withActivePowerValue(values.getData(timestamp).orElseThrow()) + .build() + .toModification() + .apply(network, true, ReportNode.NO_OP); + } + + private TemporalData inspect(TemporalData networks, boolean apply) { + if (!networks.getTimestamps().equals(values.getTimestamps())) { + throw new OpenRaoException("Networks and values have different timestamps"); + } + TemporalData shifts = new TemporalDataImpl<>(); + for (OffsetDateTime ts : networks.getTimestamps()) { + Network network = networks.getData(ts).orElseThrow(); + Generator generator = network.getGenerator(networkElementId); + if (generator == null) { + throw new OpenRaoException(String.format("Network at timestamp %s does not contain generator %s.", ts, networkElementId)); + } + shifts.put(ts, values.getData(ts).orElseThrow() - generator.getTargetP()); + if (apply) { + apply(network, ts); + } + } + return shifts; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return this.id.equals(((GeneratorTargetPNetworkVariation) o).id); + } +} diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/LoadP0NetworkVariation.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/LoadP0NetworkVariation.java new file mode 100644 index 0000000000..f0812c2d0a --- /dev/null +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/LoadP0NetworkVariation.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.powsybl.openrao.searchtreerao.marmot.scenariobuilder; + +import com.powsybl.action.LoadActionBuilder; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; +import com.powsybl.openrao.commons.OpenRaoException; +import com.powsybl.openrao.commons.TemporalData; +import com.powsybl.openrao.commons.TemporalDataImpl; + +import java.time.OffsetDateTime; + + +/** + * @author Peter Mitri {@literal } + */ +public class LoadP0NetworkVariation extends AbstractNetworkVariation implements NetworkVariation { + public LoadP0NetworkVariation(String id, String networkElementId, TemporalData values) { + super(id, networkElementId, values); + } + + @Override + public TemporalData computeShifts(TemporalData networks) { + return inspect(networks, false); + } + + @Override + public TemporalData apply(TemporalData networks) { + return inspect(networks, true); + } + + @Override + public void apply(Network network, OffsetDateTime timestamp) { + new LoadActionBuilder() + .withId("id") + .withLoadId(networkElementId) + .withRelativeValue(false) + .withActivePowerValue(values.getData(timestamp).orElseThrow()) // TODO : multiply by -1 like in InjectionRangeActionImpl ? + .build() + .toModification() + .apply(network, true, ReportNode.NO_OP); + } + + private TemporalData inspect(TemporalData networks, boolean apply) { + if (!networks.getTimestamps().equals(values.getTimestamps())) { + throw new OpenRaoException("Networks and values have different timestamps"); + } + TemporalData shifts = new TemporalDataImpl<>(); + for (OffsetDateTime ts : networks.getTimestamps()) { + Network network = networks.getData(ts).orElseThrow(); + Load load = network.getLoad(networkElementId); + if (load == null) { + throw new OpenRaoException(String.format("Network at timestamp %s does not contain load %s.", ts, networkElementId)); + } + shifts.put(ts, values.getData(ts).orElseThrow() - load.getP0()); + if (apply) { + apply(network, ts); + } + } + return shifts; + } +} diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/NetworkVariation.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/NetworkVariation.java new file mode 100644 index 0000000000..942f441e00 --- /dev/null +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/NetworkVariation.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.powsybl.openrao.searchtreerao.marmot.scenariobuilder; + +import com.powsybl.iidm.network.Network; +import com.powsybl.openrao.commons.TemporalData; + +import java.time.OffsetDateTime; + +/** + * @author Peter Mitri {@literal } + */ +public interface NetworkVariation { + String getId(); + String getNetworkElementId(); // TODO I don't like this very much, but it's needed to compute shifts + TemporalData computeShifts(TemporalData networks); + TemporalData apply(TemporalData networks); + void apply(Network network, OffsetDateTime timestamp); +} diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/ScenarioRepo.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/ScenarioRepo.java new file mode 100644 index 0000000000..52c97ffad4 --- /dev/null +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/marmot/scenariobuilder/ScenarioRepo.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.powsybl.openrao.searchtreerao.marmot.scenariobuilder; + +import com.powsybl.iidm.network.Network; +import com.powsybl.openrao.commons.OpenRaoException; +import com.powsybl.openrao.commons.TemporalData; + +import java.time.OffsetDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Peter Mitri {@literal } + */ +public class ScenarioRepo { + private Map networkVariations; + private Map> scenarios; + + public ScenarioRepo(Set networkVariations) { + this.networkVariations = new HashMap<>(); + for (NetworkVariation networkVariation : networkVariations) { + this.networkVariations.put(networkVariation.getId(), networkVariation); + } + this.scenarios = new HashMap<>(); + } + + public void addScenario(String id, Set networkVariationIds) { + if (scenarios.containsKey(id)) { + throw new OpenRaoException("Scenario with id " + id + " already exists"); + } + if (networkVariationIds.stream().anyMatch(networkVariationId -> !networkVariations.containsKey(networkVariationId))) { + throw new OpenRaoException("At least one network variation was not declared."); + } + Set networkVariationsSet = networkVariationIds.stream().map(networkVariations::get).collect(Collectors.toSet()); + if (!scenarios.containsValue(networkVariationsSet)) { + scenarios.put(id, networkVariationsSet); + } + } + + public Set getScenarios() { + return scenarios.keySet(); + } + + public Map> applyScenario(String scenarioId, TemporalData networks) { + Map> shifts = new HashMap<>(); + scenarios.get(scenarioId).forEach(networkVariation -> shifts.put(networkVariation.getNetworkElementId(), networkVariation.apply(networks))); + return shifts; + } + + public void applyScenario(String scenarioId, Network network, OffsetDateTime timestamp) { + scenarios.get(scenarioId).forEach(networkVariation -> networkVariation.apply(network, timestamp)); + } + + public Set getInjectionIds() { + // TODO make this cleaner, more generic. Also base on scenarios, because networkVariations can contain unused values + return networkVariations.values().stream().map(NetworkVariation::getNetworkElementId).collect(Collectors.toSet()); + } + + public Map> computeShifts(String scenarioId, TemporalData networks) { + Map> shifts = new HashMap<>(); + scenarios.get(scenarioId).forEach(networkVariation -> shifts.put(networkVariation.getNetworkElementId(), networkVariation.computeShifts(networks))); + return shifts; + } + + public int getNumberOfScenarios() { + return scenarios.size(); + } +} diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/api/SensitivityResult.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/api/SensitivityResult.java index 8ac3ca6c14..50b7cd8c1e 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/api/SensitivityResult.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/api/SensitivityResult.java @@ -31,4 +31,6 @@ public interface SensitivityResult { double getSensitivityValue(FlowCnec flowCnec, TwoSides side, RangeAction rangeAction, Unit unit); double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityVariableSet linearGlsk, Unit unit); + + double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit); } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/AutomatonPerimeterResultImpl.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/AutomatonPerimeterResultImpl.java index 80efd8ad42..61412b2305 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/AutomatonPerimeterResultImpl.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/AutomatonPerimeterResultImpl.java @@ -215,6 +215,11 @@ public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityV return postAutomatonSensitivityAnalysisOutput.getSensitivityValue(flowCnec, side, linearGlsk, unit); } + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + return postAutomatonSensitivityAnalysisOutput.getSensitivityValue(flowCnec, side, variableId, unit); + } + private void checkState(State state) { if (!state.equals(optimizedState)) { throw new OpenRaoException("State should be " + optimizedState.getId() + " but was " + state.getId()); diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/CurativeWithSecondPraoResult.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/CurativeWithSecondPraoResult.java index 85bcd5e031..c4558e7e00 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/CurativeWithSecondPraoResult.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/CurativeWithSecondPraoResult.java @@ -272,4 +272,10 @@ public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityV checkCnec(flowCnec); return postCraSensitivitySensitivityResult.getSensitivityValue(flowCnec, side, linearGlsk, unit); } + + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + checkCnec(flowCnec); + return postCraSensitivitySensitivityResult.getSensitivityValue(flowCnec, side, variableId, unit); + } } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/IteratingLinearOptimizationResultImpl.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/IteratingLinearOptimizationResultImpl.java index 842df17813..06fd743153 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/IteratingLinearOptimizationResultImpl.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/IteratingLinearOptimizationResultImpl.java @@ -208,13 +208,18 @@ public Set getContingencies() { } @Override - public double getSensitivityValue(FlowCnec branchCnec, TwoSides side, RangeAction rangeAction, Unit unit) { - return sensitivityResult.getSensitivityValue(branchCnec, side, rangeAction, unit); + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, RangeAction rangeAction, Unit unit) { + return sensitivityResult.getSensitivityValue(flowCnec, side, rangeAction, unit); } @Override - public double getSensitivityValue(FlowCnec branchCnec, TwoSides side, SensitivityVariableSet linearGlsk, Unit unit) { - return sensitivityResult.getSensitivityValue(branchCnec, side, linearGlsk, unit); + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityVariableSet linearGlsk, Unit unit) { + return sensitivityResult.getSensitivityValue(flowCnec, side, linearGlsk, unit); + } + + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + return sensitivityResult.getSensitivityValue(flowCnec, side, variableId, unit); } @Override diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/OptimizationResultImpl.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/OptimizationResultImpl.java index bed6f7bf7c..0346c7d428 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/OptimizationResultImpl.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/OptimizationResultImpl.java @@ -191,6 +191,11 @@ public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityV return sensitivityResult.getSensitivityValue(flowCnec, side, linearGlsk, unit); } + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + return sensitivityResult.getSensitivityValue(flowCnec, side, variableId, unit); + } + @Override public void excludeCnecs(Set cnecsToExclude) { objectiveFunctionResult.excludeCnecs(cnecsToExclude); diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/PrePerimeterSensitivityResultImpl.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/PrePerimeterSensitivityResultImpl.java index 6a4368d7de..4221ce2937 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/PrePerimeterSensitivityResultImpl.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/PrePerimeterSensitivityResultImpl.java @@ -80,6 +80,11 @@ public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityV return sensitivityResult.getSensitivityValue(flowCnec, side, linearGlsk, unit); } + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + return sensitivityResult.getSensitivityValue(flowCnec, side, variableId, unit); + } + @Override public double getFlow(FlowCnec flowCnec, TwoSides side, Unit unit) { return flowResult.getFlow(flowCnec, side, unit); diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/SensitivityResultImpl.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/SensitivityResultImpl.java index 2e34287976..f8e4b7351d 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/SensitivityResultImpl.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/SensitivityResultImpl.java @@ -70,4 +70,13 @@ public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityV throw new OpenRaoException(format("Unknown unit for sensitivity value on linear GLSK : %s.", unit)); } } + + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + if (unit == Unit.MEGAWATT) { + return systematicSensitivityResult.getSensitivityOnFlow(variableId, flowCnec, side); + } else { + throw new OpenRaoException(format("Unknown unit for sensitivity value : %s.", unit)); + } + } } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/SkippedOptimizationResultImpl.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/SkippedOptimizationResultImpl.java index 798da4b6f3..e41ab12b31 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/SkippedOptimizationResultImpl.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/SkippedOptimizationResultImpl.java @@ -75,6 +75,11 @@ public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityV throw new OpenRaoException(SHOULD_NOT_BE_USED); } + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + throw new OpenRaoException(SHOULD_NOT_BE_USED); + } + @Override public double getFlow(FlowCnec flowCnec, TwoSides side, Unit unit) { throw new OpenRaoException(SHOULD_NOT_BE_USED); diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/searchtree/algorithms/Leaf.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/searchtree/algorithms/Leaf.java index cce00997db..09b851e187 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/searchtree/algorithms/Leaf.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/searchtree/algorithms/Leaf.java @@ -663,6 +663,17 @@ public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, SensitivityV } } + @Override + public double getSensitivityValue(FlowCnec flowCnec, TwoSides side, String variableId, Unit unit) { + if (status == Status.EVALUATED) { + return preOptimSensitivityResult.getSensitivityValue(flowCnec, side, variableId, unit); + } else if (status == Status.OPTIMIZED) { + return postOptimResult.getSensitivityValue(flowCnec, side, variableId, unit); + } else { + throw new OpenRaoException(NO_RESULTS_AVAILABLE); + } + } + /** * Releases data used in optimization to make leaf lighter */ diff --git a/sensitivity-analysis/src/main/java/com/powsybl/openrao/sensitivityanalysis/InjectionSensitivityProvider.java b/sensitivity-analysis/src/main/java/com/powsybl/openrao/sensitivityanalysis/InjectionSensitivityProvider.java new file mode 100644 index 0000000000..3be1169a35 --- /dev/null +++ b/sensitivity-analysis/src/main/java/com/powsybl/openrao/sensitivityanalysis/InjectionSensitivityProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.powsybl.openrao.sensitivityanalysis; + +import com.powsybl.contingency.Contingency; +import com.powsybl.contingency.ContingencyContext; +import com.powsybl.iidm.network.Network; +import com.powsybl.openrao.commons.Unit; +import com.powsybl.openrao.data.crac.api.cnec.FlowCnec; +import com.powsybl.openrao.data.crac.api.rangeaction.HvdcRangeAction; +import com.powsybl.sensitivity.*; + +import java.util.*; + +/** + * @author Peter Mitri {@literal } + */ +public class InjectionSensitivityProvider extends AbstractSimpleSensitivityProvider { + private final Set injectionIds; + + public InjectionSensitivityProvider(Set cnecs, Set injectionIds, Set requestedUnits) { + super(cnecs, requestedUnits); + this.injectionIds = injectionIds; + } + + @Override + public List getBasecaseFactors(Network network) { + List sensiFactors = new ArrayList<>(); + for (String genId : injectionIds) { + for (FlowCnec cnec : this.cnecs) { + sensiFactors.add(new SensitivityFactor(SensitivityFunctionType.BRANCH_ACTIVE_POWER_1, cnec.getNetworkElement().getId(), SensitivityVariableType.INJECTION_ACTIVE_POWER, genId, false, ContingencyContext.none())); + sensiFactors.add(new SensitivityFactor(SensitivityFunctionType.BRANCH_ACTIVE_POWER_2, cnec.getNetworkElement().getId(), SensitivityVariableType.INJECTION_ACTIVE_POWER, genId, false, ContingencyContext.none())); + } + } + return sensiFactors; + } + + @Override + public List getContingencyFactors(Network network, List contingencies) { + // TODO + return List.of(); + } + + @Override + public List getVariableSets() { + return List.of(); + } + + @Override + public Map getHvdcs() { + return Map.of(); + } +} diff --git a/tests/src/test/java/com/powsybl/openrao/tests/steps/InterTemporalRaoSteps.java b/tests/src/test/java/com/powsybl/openrao/tests/steps/InterTemporalRaoSteps.java index 2a7c401c7b..78c84a0804 100644 --- a/tests/src/test/java/com/powsybl/openrao/tests/steps/InterTemporalRaoSteps.java +++ b/tests/src/test/java/com/powsybl/openrao/tests/steps/InterTemporalRaoSteps.java @@ -216,6 +216,7 @@ public static void iExportMarmotResults(String outputPath) throws IOException { properties.put("inter-temporal-rao-result.export.filename-template", "'RAO_RESULT_'yyyy-MM-dd'T'HH:mm:ss'.json'"); properties.put("inter-temporal-rao-result.export.summary-filename", "summary.json"); interTemporalRaoResult.write(zipOutputStream, interTemporalRaoInput.getRaoInputs().map(RaoInputWithNetworkPaths::getCrac), properties); + zipOutputStream.close(); } @When("I export RefProg after redispatching to {string} based on raoResults zip {string}") diff --git a/tests/src/test/java/com/powsybl/openrao/tests/steps/RaoSteps.java b/tests/src/test/java/com/powsybl/openrao/tests/steps/RaoSteps.java index e145b94fa4..edc8dc4002 100644 --- a/tests/src/test/java/com/powsybl/openrao/tests/steps/RaoSteps.java +++ b/tests/src/test/java/com/powsybl/openrao/tests/steps/RaoSteps.java @@ -39,13 +39,8 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.util.Comparator; -import java.util.Objects; -import java.util.Properties; -import java.util.Set; +import java.io.*; +import java.util.*; import java.util.stream.Collectors; import static com.powsybl.openrao.raoapi.parameters.extensions.LoadFlowAndSensitivityParameters.getSensitivityWithLoadFlowParameters; @@ -680,8 +675,8 @@ private Pair getWorstCnec(Unit unit, boolean relative) { double margin; //Filter flow cnecs from failed perimeters Set failedStates = crac.getStates().stream() - .filter(state -> raoResult.getComputationStatus(state).equals(ComputationStatus.FAILURE)) - .collect(Collectors.toSet()); + .filter(state -> raoResult.getComputationStatus(state).equals(ComputationStatus.FAILURE)) + .collect(Collectors.toSet()); for (FlowCnec flowCnec : flowCnecs) { if (failedStates.contains(flowCnec.getState())) { continue; @@ -721,4 +716,13 @@ private Pair getWorstCnec(Unit unit, boolean relative) { public void getOptimizationSteps(String string) { assertEquals(string, raoResult.getExecutionDetails()); } + + @When("I export RAO result to {string}") + public void iExportRAOResultTo(String path) throws IOException { + OutputStream os = new FileOutputStream(path); + Properties properties = new Properties(); + properties.setProperty("rao-result.export.json.flows-in-megawatts", "true"); + raoResult.write("JSON", CommonTestData.getCracCreationContext(), properties, os); + os.close(); + } }