diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/FlowCnecSorting.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/FlowCnecSorting.java index 3685b1988a..51b3c1c6e9 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/FlowCnecSorting.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/FlowCnecSorting.java @@ -31,4 +31,12 @@ public static List sortByMargin(Set flowCnecs, Unit unit, Ma .sorted(Comparator.comparingDouble(flowCnec -> marginEvaluator.getMargin(flowResult, flowCnec, unit))) .toList(); } + + public static List sortByNegativeMargin(Set flowCnecs, Unit unit, MarginEvaluator marginEvaluator, FlowResult flowResult) { + return flowCnecs.stream() + .filter(Cnec::isOptimized) + .filter(flowCnec -> marginEvaluator.getMargin(flowResult, flowCnec, unit) < 0) + .sorted(Comparator.comparingDouble(flowCnec -> marginEvaluator.getMargin(flowResult, flowCnec, unit))) + .toList(); + } } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/RaoLogger.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/RaoLogger.java index 40bdcb1d58..c5a52e09f7 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/RaoLogger.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/RaoLogger.java @@ -71,10 +71,35 @@ public static void logSensitivityAnalysisResults(String prefix, numberOfLoggedLimitingElements); } + public static void logObjectiveFunctionResult(String prefix, + ObjectiveFunctionResult objectiveFunctionResult, + PrePerimeterResult sensitivityAnalysisResult, + RaoParameters raoParameters, + int numberOfLoggedLimitingElements) { + + if (!BUSINESS_LOGS.isInfoEnabled()) { + return; + } + + Map virtualCostDetailed = getVirtualCostDetailed(objectiveFunctionResult); + + BUSINESS_LOGS.info(prefix + "cost = {} (functional: {}, virtual: {}{})", + formatDoubleBasedOnMargin(objectiveFunctionResult.getCost(), -objectiveFunctionResult.getCost()), + formatDoubleBasedOnMargin(objectiveFunctionResult.getFunctionalCost(), -objectiveFunctionResult.getCost()), + formatDoubleBasedOnMargin(objectiveFunctionResult.getVirtualCost(), -objectiveFunctionResult.getCost()), + virtualCostDetailed.isEmpty() ? "" : " " + virtualCostDetailed); + + RaoLogger.logMostLimitingElementsResults(BUSINESS_LOGS, + sensitivityAnalysisResult, + raoParameters.getObjectiveFunctionParameters().getType(), + raoParameters.getObjectiveFunctionParameters().getUnit(), + numberOfLoggedLimitingElements); + } + public static void logRangeActions(OpenRaoLogger logger, Leaf leaf, - OptimizationPerimeter - optimizationContext, String prefix) { + OptimizationPerimeter optimizationContext, + String prefix) { boolean globalPstOptimization = optimizationContext instanceof GlobalOptimizationPerimeter; diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/marginevaluator/MarginEvaluatorWithMarginDecreaseUnoptimizedCnecs.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/marginevaluator/MarginEvaluatorWithMarginDecreaseUnoptimizedCnecs.java index 58565ca43c..a38aa6acad 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/marginevaluator/MarginEvaluatorWithMarginDecreaseUnoptimizedCnecs.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/marginevaluator/MarginEvaluatorWithMarginDecreaseUnoptimizedCnecs.java @@ -50,7 +50,7 @@ public double getMargin(FlowResult flowResult, FlowCnec flowCnec, TwoSides side, } private double computeMargin(FlowCnec flowCnec, double newMargin, double prePerimeterMargin) { - if (countriesNotToOptimize.contains(flowCnec.getOperator()) && newMargin > prePerimeterMargin - .0001 * Math.abs(prePerimeterMargin)) { + if (countriesNotToOptimize.contains(flowCnec.getOperator()) && newMargin > prePerimeterMargin - .0001 * Math.abs(prePerimeterMargin) && flowCnec.getState().getInstant().isCurative()) { return Double.MAX_VALUE; } return newMargin; diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/objectivefunctionevaluator/MinMarginViolationEvaluator.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/objectivefunctionevaluator/MinMarginViolationEvaluator.java index d90a04a935..79bea77063 100644 --- a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/objectivefunctionevaluator/MinMarginViolationEvaluator.java +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/commons/objectivefunctionevaluator/MinMarginViolationEvaluator.java @@ -8,11 +8,20 @@ package com.powsybl.openrao.searchtreerao.commons.objectivefunctionevaluator; import com.powsybl.openrao.commons.Unit; +import com.powsybl.openrao.data.crac.api.State; import com.powsybl.openrao.data.crac.api.cnec.FlowCnec; +import com.powsybl.openrao.searchtreerao.commons.FlowCnecSorting; +import com.powsybl.openrao.searchtreerao.commons.costevaluatorresult.CostEvaluatorResult; +import com.powsybl.openrao.searchtreerao.commons.costevaluatorresult.MaxCostEvaluatorResult; import com.powsybl.openrao.searchtreerao.commons.marginevaluator.MarginEvaluator; import com.powsybl.openrao.searchtreerao.result.api.FlowResult; +import com.powsybl.openrao.searchtreerao.result.api.RemedialActionActivationResult; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; + +import static com.powsybl.openrao.searchtreerao.commons.objectivefunctionevaluator.CostEvaluatorUtils.groupFlowCnecsPerState; /** * @author Thomas Bouquet {@literal } @@ -33,4 +42,11 @@ public String getName() { protected double computeCostForState(FlowResult flowResult, Set flowCnecsOfState) { return Math.max(0, super.computeCostForState(flowResult, flowCnecsOfState)) * OVERLOAD_PENALTY; } + + @Override + public CostEvaluatorResult evaluate(FlowResult flowResult, RemedialActionActivationResult remedialActionActivationResult) { + Map> flowCnecsPerState = groupFlowCnecsPerState(flowCnecs); + Map costPerState = flowCnecsPerState.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> computeCostForState(flowResult, entry.getValue()))); + return new MaxCostEvaluatorResult(costPerState, FlowCnecSorting.sortByNegativeMargin(flowCnecs, unit, marginEvaluator, flowResult)); + } } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/fastrao/FastRao.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/fastrao/FastRao.java new file mode 100644 index 0000000000..16434663db --- /dev/null +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/fastrao/FastRao.java @@ -0,0 +1,470 @@ +/* + * Copyright (c) 2020, 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.fastrao; + +import com.google.auto.service.AutoService; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VariantManager; +import com.powsybl.openrao.commons.OpenRaoException; +import com.powsybl.openrao.commons.Unit; +import com.powsybl.openrao.data.crac.api.*; +import com.powsybl.openrao.data.crac.api.cnec.FlowCnec; +import com.powsybl.openrao.data.crac.api.parameters.CracCreationParameters; +import com.powsybl.openrao.data.crac.io.json.JsonExport; +import com.powsybl.openrao.data.crac.io.json.JsonImport; +import com.powsybl.openrao.data.raoresult.api.ComputationStatus; +import com.powsybl.openrao.data.raoresult.api.RaoResult; +import com.powsybl.openrao.raoapi.RaoInput; +import com.powsybl.openrao.raoapi.RaoProvider; +import com.powsybl.openrao.raoapi.parameters.RaoParameters; +import com.powsybl.openrao.raoapi.parameters.extensions.OpenRaoSearchTreeParameters; +import com.powsybl.openrao.searchtreerao.castor.algorithm.CastorFullOptimization; +import com.powsybl.openrao.searchtreerao.castor.algorithm.PrePerimeterSensitivityAnalysis; +import com.powsybl.openrao.searchtreerao.castor.algorithm.StateTree; +import com.powsybl.openrao.searchtreerao.commons.RaoLogger; +import com.powsybl.openrao.searchtreerao.commons.RaoUtil; +import com.powsybl.openrao.searchtreerao.commons.SensitivityComputer; +import com.powsybl.openrao.searchtreerao.commons.ToolProvider; +import com.powsybl.openrao.searchtreerao.commons.objectivefunction.ObjectiveFunction; +import com.powsybl.openrao.searchtreerao.result.api.*; +import com.powsybl.openrao.searchtreerao.result.impl.*; +import com.powsybl.openrao.sensitivityanalysis.AppliedRemedialActions; +import com.powsybl.openrao.util.AbstractNetworkPool; + +import java.io.*; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static com.powsybl.openrao.commons.logs.OpenRaoLoggerProvider.BUSINESS_LOGS; + +/** + * @author Joris Mancini {@literal } + * @author Philippe Edwards {@literal } + * @author Peter Mitri {@literal } + * @author Godelaine De-Montmorillon {@literal } + * @author Baptiste Seguinot {@literal } + */ +@AutoService(RaoProvider.class) +public class FastRao implements RaoProvider { + private static final String FAST_RAO = "FastRao"; + private static final int NUMBER_LOGGED_ELEMENTS_DURING_RAO = 2; + private static final int NUMBER_LOGGED_ELEMENTS_END_RAO = 10; + private static final int NUMBER_OF_CNECS_TO_ADD = 20; + private static final boolean ADD_UNSECURE_CNECS = false; + private static final double MARGIN_LIMIT = 5; + // Do not store any big object in this class as it is a static RaoProvider + // Objects stored in memory will not be released at the end of the RAO run + + @Override + public String getName() { + return FAST_RAO; + } + + @Override + public String getVersion() { + return "1.0.0"; + } + + @Override + public CompletableFuture run(RaoInput raoInput, RaoParameters parameters) { + return run(raoInput, parameters, null); + } + + @Override + public CompletableFuture run(RaoInput raoInput, RaoParameters parameters, Instant targetEndInstant) { + return CompletableFuture.completedFuture(launchFilteredRao(raoInput, parameters, targetEndInstant, new HashSet<>())); + } + + public static RaoResult launchFilteredRao(RaoInput raoInput, RaoParameters parameters, Instant targetEndInstant, Set consideredCnecs) { + RaoUtil.initData(raoInput, parameters); + + try { + // 1. Retrieve input data + Crac crac = raoInput.getCrac(); + Collection initialNetworkVariants = new HashSet<>(raoInput.getNetwork().getVariantManager().getVariantIds()); + + ToolProvider toolProvider = ToolProvider.buildFromRaoInputAndParameters(raoInput, parameters); + + PrePerimeterSensitivityAnalysis prePerimeterSensitivityAnalysis = new PrePerimeterSensitivityAnalysis( + crac.getFlowCnecs(), + crac.getRangeActions(), + parameters, + toolProvider); + + // 3. Run initial sensi (for initial values, and to know which cnecs to put in the first rao) + PrePerimeterResult initialResult = prePerimeterSensitivityAnalysis.runInitialSensitivityAnalysis(raoInput.getNetwork(), raoInput.getCrac()); + + if (initialResult.getSensitivityStatus() == ComputationStatus.FAILURE) { + BUSINESS_LOGS.error("Initial sensitivity analysis failed"); + return new FailedRaoResultImpl("Initial sensitivity analysis failed"); + } + + RaoLogger.logSensitivityAnalysisResults("Initial sensitivity analysis: ", + prePerimeterSensitivityAnalysis.getObjectiveFunction(), + RemedialActionActivationResultImpl.empty(initialResult), + initialResult, + parameters, + NUMBER_LOGGED_ELEMENTS_DURING_RAO); + + PrePerimeterResult stepResult = initialResult; + //computeAvailableRangeActions(initialResult, crac, network, parameters); + + FlowCnec worstCnec; + FastRaoResultImpl raoResult; + + com.powsybl.openrao.data.crac.api.Instant lastInstant = raoInput.getCrac().getLastInstant(); + AbstractNetworkPool networkPool = AbstractNetworkPool.create(raoInput.getNetwork(), raoInput.getNetworkVariantId(), 3, true); + int counter = 1; + do { + addWorstCnecs(consideredCnecs, NUMBER_OF_CNECS_TO_ADD, stepResult); + System.out.println(consideredCnecs.size()); + if (ADD_UNSECURE_CNECS) { + consideredCnecs.addAll(getUnsecureFunctionalCnecs(stepResult, parameters.getObjectiveFunctionParameters().getUnit())); + } + System.out.println(consideredCnecs.size()); + consideredCnecs.addAll(getCostlyVirtualCnecs(stepResult)); + System.out.println(consideredCnecs.size()); + consideredCnecs.add(getWorstPreventiveCnec(stepResult, crac)); + System.out.println(consideredCnecs.size()); + cleanVariants(raoInput.getNetwork(), initialNetworkVariants); + + raoResult = runFilteredRao(raoInput, parameters, targetEndInstant, consideredCnecs, toolProvider, initialResult, networkPool, counter); + stepResult = raoResult.getAppropriateResult(lastInstant); + + RaoLogger.logObjectiveFunctionResult(String.format("Iteration %d: sensitivity analysis: ", counter), + stepResult, //TODO: Find the right remedialActionActivationResult if we want to use costly objective function not needed otherwise + stepResult, + parameters, + NUMBER_LOGGED_ELEMENTS_DURING_RAO); + + worstCnec = stepResult.getMostLimitingElements(1).get(0); + counter++; + } while (!(consideredCnecs.contains(worstCnec.getId()) && consideredCnecs.containsAll(getCostlyVirtualCnecs(stepResult)))); + networkPool.shutdownAndAwaitTermination(24, TimeUnit.HOURS); + + RaoLogger.logObjectiveFunctionResult("Final Result: ", + stepResult, //TODO: Find the right remedialActionActivationResult if we want to use costly objective function not needed otherwise + stepResult, + parameters, + NUMBER_LOGGED_ELEMENTS_DURING_RAO); + + return raoResult; + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private static void addWorstCnecs(Set consideredCnecs, int numberOfCnecsToAdd, PrePerimeterResult ofResult) { + List orderedCnecs = ofResult.getMostLimitingElements(Integer.MAX_VALUE); + int counter = 0; + for (FlowCnec cnec : orderedCnecs) { + if (counter >= numberOfCnecsToAdd) { + return; + } + if (consideredCnecs.add(cnec.getId())) { + counter++; + } + } + } + + private static void cleanVariants(Network network, Collection initialNetworkVariants) { + VariantManager variantManager = network.getVariantManager(); + Set variantsToRemove = new HashSet<>(); + variantManager.getVariantIds().stream() + .filter(id -> !initialNetworkVariants.contains(id)) + .forEach(variantsToRemove::add); + variantsToRemove.forEach(variantManager::removeVariant); + } + + private static String getWorstPreventiveCnec(ObjectiveFunctionResult ofResult, Crac crac) { + List orderedCnecs = ofResult.getMostLimitingElements(Integer.MAX_VALUE); + return orderedCnecs.stream().filter(cnec -> cnec.getState().isPreventive()).findFirst().orElse( + // If only MNECs are present previous list will be empty + crac.getFlowCnecs(crac.getPreventiveState()).stream().findFirst().orElseThrow() + ).getId(); + } + + private static Set getUnsecureFunctionalCnecs(PrePerimeterResult prePerimeterResult, Unit unit) { + List orderedCnecs = prePerimeterResult.getMostLimitingElements(Integer.MAX_VALUE); + Set flowCnecs = new HashSet<>(); + for (FlowCnec cnec : orderedCnecs) { + if (prePerimeterResult.getMargin(cnec, unit) < MARGIN_LIMIT) { + flowCnecs.add(cnec.getId()); + } + } + return flowCnecs; + } + + private static Set getCostlyVirtualCnecs(ObjectiveFunctionResult ofResult) { + Set flowCnecs = new HashSet<>(); + ofResult.getVirtualCostNames().forEach(name -> ofResult.getCostlyElements(name, Integer.MAX_VALUE).forEach( + flowCnec -> flowCnecs.add(flowCnec.getId()) + )); + return flowCnecs; + } + + private static FastRaoResultImpl runFilteredRao(RaoInput raoInput, RaoParameters parameters, Instant targetEndInstant, Set flowCnecsToKeep, ToolProvider toolProvider, PrePerimeterResult initialResult, AbstractNetworkPool networkPool, int counter) throws IOException, InterruptedException { + Crac crac = raoInput.getCrac(); + // 4. Filter CRAC to only keep the worst CNECs + Crac filteredCrac = copyCrac(crac, raoInput.getNetwork()); + System.out.println(filteredCrac.getFlowCnecs().size()); + removeFlowCnecsFromCrac(filteredCrac, flowCnecsToKeep); + + BUSINESS_LOGS.info("***** Iteration {}: Run filtered RAO [start]", counter); + System.out.println(filteredCrac.getFlowCnecs().size()); + System.out.println(flowCnecsToKeep.size()); + + RaoInput filteredRaoInput = createFilteredRaoInput(raoInput, filteredCrac); + RaoResult raoResult; + try { + String networkVariantId = raoInput.getNetwork().getVariantManager().getWorkingVariantId(); + raoResult = new CastorFullOptimization(filteredRaoInput, parameters, targetEndInstant).run().get(); + raoInput.getNetwork().getVariantManager().setWorkingVariant(networkVariantId); + List preventiveNetworkActions = raoResult.getActivatedNetworkActionsDuringState(crac.getPreventiveState()).stream() + .map(Identifiable::getId) + .toList(); + if (preventiveNetworkActions.size() >= 2) { + List> predefinedCombinations = parameters.getExtension(OpenRaoSearchTreeParameters.class).getTopoOptimizationParameters().getPredefinedCombinations(); + if (!predefinedCombinations.contains(preventiveNetworkActions)) { + predefinedCombinations.add(preventiveNetworkActions); + } + parameters.getExtension(OpenRaoSearchTreeParameters.class).getTopoOptimizationParameters().setPredefinedCombinations(predefinedCombinations); + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + BUSINESS_LOGS.info("***** Iteration {}: Run filtered RAO [end]", counter); + + String finalVariantId = raoInput.getNetwork().getVariantManager().getWorkingVariantId(); + raoInput.getNetwork().getVariantManager().setWorkingVariant(raoInput.getNetworkVariantId()); + // 6. Apply / Force optimal RAs found on filter RAO + // 7. Run RAO with applied/forced RAs + BUSINESS_LOGS.info("***** Iteration {}: Run full sensitivity analysis [start]", counter); + //TODO: Semaphores won't quite work with the current implementation to parallelize the loadflows: + // eg if we applied PRAs, we need to give the post PRA result to the autoSensi etc, so the sensitivities are not parallelized + // Some options: + // - have a runBasedOnInitialAndPrePerimResults that takes a semaphore to be able to run a sensi, and then wait for the semaphore to build the result + // - ideally use the security analysis for faster runs anyways => rewrite the method completely + // (for now option 1) + // Solution selected : Do the three Sensitivity computation in parallel, wait for all of them to finish then compute objective function sequentially + + AtomicReference postPraSensi = new AtomicReference<>(); + AtomicReference postAraSensi = new AtomicReference<>(); + AtomicReference postCraSensi = new AtomicReference<>(); + + ForkJoinTask task1 = networkPool.submit(() -> { + try { + Network networkCopy = networkPool.getAvailableNetwork(); + applyOptimalPreventiveRemedialActions(networkCopy, filteredCrac.getPreventiveState(), raoResult); + postPraSensi.set(runBasedOnInitialResults(toolProvider, raoInput, networkCopy, parameters, crac.getFlowCnecs(), new AppliedRemedialActions(), initialResult)); + networkPool.releaseUsedNetwork(networkCopy); + + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + // TODO: Use list of tasks like everywhere else in the codebase + ForkJoinTask task2 = networkPool.submit(() -> { + try { + Network networkCopy = networkPool.getAvailableNetwork(); + applyOptimalPreventiveRemedialActions(networkCopy, filteredCrac.getPreventiveState(), raoResult); + AppliedRemedialActions appliedAutoRemedialActions = createAutoAppliedRemedialActionsFromRaoResult(filteredCrac, raoResult); + postAraSensi.set(runBasedOnInitialResults(toolProvider, raoInput, networkCopy, parameters, crac.getFlowCnecs(), appliedAutoRemedialActions, initialResult)); + networkPool.releaseUsedNetwork(networkCopy); + + } catch (OpenRaoException | InterruptedException e) { + throw new RuntimeException(e); + } + }); + + ForkJoinTask task3 = networkPool.submit(() -> { + try { + Network networkCopy = networkPool.getAvailableNetwork(); + applyOptimalPreventiveRemedialActions(networkCopy, filteredCrac.getPreventiveState(), raoResult); + AppliedRemedialActions appliedRemedialActions = createAppliedRemedialActionsFromRaoResult(filteredCrac, raoResult); + postCraSensi.set(runBasedOnInitialResults(toolProvider, raoInput, networkCopy, parameters, raoInput.getCrac().getFlowCnecs(), appliedRemedialActions, initialResult)); + networkPool.releaseUsedNetwork(networkCopy); + } catch (InterruptedException | OpenRaoException e) { + throw new RuntimeException(e); + } + }); + + task1.join(); + task2.join(); + task3.join(); + + raoInput.getNetwork().getVariantManager().setWorkingVariant(finalVariantId); + + postPraSensi.set(getCompletePrePerimeterSensitivityResultImpl(toolProvider, raoInput, parameters, raoInput.getCrac().getFlowCnecs(), initialResult, initialResult, postPraSensi.get())); + postAraSensi.set(getCompletePrePerimeterSensitivityResultImpl(toolProvider, raoInput, parameters, raoInput.getCrac().getFlowCnecs(), initialResult, postPraSensi.get(), postAraSensi.get())); + postCraSensi.set(getCompletePrePerimeterSensitivityResultImpl(toolProvider, raoInput, parameters, raoInput.getCrac().getFlowCnecs(), initialResult, postAraSensi.get(), postCraSensi.get())); + + BUSINESS_LOGS.info("***** Iteration {}: Run full sensitivity analysis [end]", counter); + + return new FastRaoResultImpl(initialResult, postPraSensi.get(), postAraSensi.get(), postCraSensi.get(), raoResult, raoInput.getCrac()); + } + + private static RaoInput createFilteredRaoInput(RaoInput raoInput, Crac filteredCrac) { + return RaoInput.build(raoInput.getNetwork(), filteredCrac) + .withPerimeter(raoInput.getPerimeter()) + .withGlskProvider(raoInput.getGlskProvider()) + .withRefProg(raoInput.getReferenceProgram()) + .withNetworkVariantId(raoInput.getNetworkVariantId()) + .build(); + } + + public static Crac copyCrac(Crac crac, Network network) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + new JsonExport().exportData(crac, outputStream); + ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); + return new JsonImport().importData(inputStream, new CracCreationParameters(), network).getCrac(); + } + + // Caution: We might remove CNECs associated to RAs with onConstraint usageRule + public static void removeFlowCnecsFromCrac(Crac crac, Collection flowCnecsToKeep) { + Set flowCnecsToRemove = crac.getFlowCnecs().stream() + .map(Identifiable::getId) + .filter(fcId -> !flowCnecsToKeep.contains(fcId)) + .collect(Collectors.toSet()); + // Remove FlowCNECs + crac.removeFlowCnecs(flowCnecsToRemove); + } + + private static void applyOptimalPreventiveRemedialActions(Network networkCopy, State state, RaoResult raoResult) { + raoResult.getActivatedRangeActionsDuringState(state).forEach(rangeAction -> rangeAction.apply(networkCopy, raoResult.getOptimizedSetPointOnState(state, rangeAction))); + raoResult.getActivatedNetworkActionsDuringState(state).forEach(networkAction -> networkAction.apply(networkCopy)); + } + + private static AppliedRemedialActions createAppliedRemedialActionsFromRaoResult(Crac crac, RaoResult raoResult) { + if (raoResult instanceof OneStateOnlyRaoResultImpl) { + return new AppliedRemedialActions(); + } + AppliedRemedialActions appliedRemedialActions = new AppliedRemedialActions(); + crac.getStates().stream().filter(state -> !state.isPreventive() && !state.getInstant().getKind().equals(InstantKind.OUTAGE)).forEach(state -> { + appliedRemedialActions.addAppliedNetworkActions(state, raoResult.getActivatedNetworkActionsDuringState(state)); + if (!raoResult.getActivatedRangeActionsDuringState(state).isEmpty()) { + appliedRemedialActions.addAppliedRangeActions(state, raoResult.getOptimizedSetPointsOnState(state)); + } + } + ); + return appliedRemedialActions; + } + + private static AppliedRemedialActions createAutoAppliedRemedialActionsFromRaoResult(Crac crac, RaoResult raoResult) { + if (raoResult instanceof OneStateOnlyRaoResultImpl) { + return new AppliedRemedialActions(); + } + AppliedRemedialActions appliedRemedialActions = new AppliedRemedialActions(); + + crac.getStates().stream().filter(state -> state.getInstant().getKind().equals(InstantKind.AUTO)).forEach(state -> { + appliedRemedialActions.addAppliedNetworkActions(state, raoResult.getActivatedNetworkActionsDuringState(state)); + appliedRemedialActions.addAppliedRangeActions(state, raoResult.getOptimizedSetPointsOnState(state)); + }); + return appliedRemedialActions; + } + + private static PrePerimeterSensitivityResultImpl runBasedOnInitialResults(ToolProvider toolProvider, + RaoInput raoInput, + Network network, + RaoParameters raoParameters, + Set flowCnecs, + AppliedRemedialActions appliedRemedialActions, + PrePerimeterResult initialFlowResult) throws InterruptedException { + Crac crac = raoInput.getCrac(); + SensitivityComputer.SensitivityComputerBuilder sensitivityComputerBuilder = SensitivityComputer.create() + .withToolProvider(toolProvider) + .withCnecs(flowCnecs) + .withRangeActions(crac.getRangeActions()) + .withOutageInstant(raoInput.getCrac().getOutageInstant()); + + if (raoParameters.getExtension(OpenRaoSearchTreeParameters.class).getLoopFlowParameters().isPresent()) { + if (raoParameters.getExtension(OpenRaoSearchTreeParameters.class).getLoopFlowParameters().get().getPtdfApproximation().shouldUpdatePtdfWithTopologicalChange()) { + sensitivityComputerBuilder.withCommercialFlowsResults(toolProvider.getLoopFlowComputation(), toolProvider.getLoopFlowCnecs(flowCnecs)); + } else { + sensitivityComputerBuilder.withCommercialFlowsResults(initialFlowResult); + } + } + if (raoParameters.getObjectiveFunctionParameters().getType().relativePositiveMargins()) { + if (raoParameters.getExtension(OpenRaoSearchTreeParameters.class).getRelativeMarginsParameters().get().getPtdfApproximation().shouldUpdatePtdfWithTopologicalChange()) { + sensitivityComputerBuilder.withPtdfsResults(toolProvider.getAbsolutePtdfSumsComputation(), flowCnecs); + } else { + sensitivityComputerBuilder.withPtdfsResults(initialFlowResult); + } + } + if (appliedRemedialActions != null) { + // for 2nd preventive initial sensi + sensitivityComputerBuilder.withAppliedRemedialActions(appliedRemedialActions); + } + SensitivityComputer sensitivityComputer = sensitivityComputerBuilder.build(); + sensitivityComputer.compute(network); + + FlowResult flowResult = sensitivityComputer.getBranchResult(network); + SensitivityResult sensitivityResult = sensitivityComputer.getSensitivityResult(); + RangeActionSetpointResult rangeActionSetpointResult = RangeActionSetpointResultImpl.buildWithSetpointsFromNetwork(network, crac.getRangeActions()); + + return new PrePerimeterSensitivityResultImpl( + flowResult, + sensitivityResult, + rangeActionSetpointResult, + null //complete later + ); + } + + private static PrePerimeterResult getCompletePrePerimeterSensitivityResultImpl(ToolProvider toolProvider, + RaoInput raoInput, + RaoParameters raoParameters, + Set flowCnecs, + PrePerimeterResult initialFlowResult, + PrePerimeterResult prePerimeterResult, + PrePerimeterResult currentPrePerimeterResult) { + + Crac crac = raoInput.getCrac(); + ObjectiveFunction objectiveFunction = ObjectiveFunction.build(flowCnecs, + toolProvider.getLoopFlowCnecs(flowCnecs), + initialFlowResult, + prePerimeterResult.getFlowResult(), + new StateTree(crac).getOperatorsNotSharingCras(), + raoParameters, + Set.of()); //TODO: To complete later if we want to use costly objective function not needed otherwise + + ObjectiveFunctionResult objectiveFunctionResult = objectiveFunction.evaluate( + currentPrePerimeterResult.getFlowResult(), + RemedialActionActivationResultImpl.empty(initialFlowResult.getRangeActionSetpointResult()) //TODO: Find the right remedialActionActivationResult if we want to use costly objective function not needed otherwise + ); + + return new PrePerimeterSensitivityResultImpl( + currentPrePerimeterResult.getFlowResult(), + currentPrePerimeterResult.getSensitivityResult(), + currentPrePerimeterResult.getRangeActionSetpointResult(), + objectiveFunctionResult + ); + } + + private static boolean anyActionActivatedDuringInstantKind(RaoResult raoResult, InstantKind instantKind, Crac crac) { + if (instantKind.equals(InstantKind.PREVENTIVE)) { + State preventiveState = crac.getPreventiveState(); + return !(raoResult.getActivatedNetworkActionsDuringState(preventiveState).isEmpty() && raoResult.getActivatedRangeActionsDuringState(preventiveState).isEmpty()); + } + if (raoResult instanceof OneStateOnlyRaoResultImpl) { + return false; + } + + return crac.getStates().stream() + .filter(state -> state.getInstant().getKind().equals(instantKind)) + .anyMatch(state -> + !(raoResult.getActivatedNetworkActionsDuringState(state).isEmpty() && raoResult.getActivatedRangeActionsDuringState(state).isEmpty()) + ); + } +} 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 6b47118d6d..85b8d107b3 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 @@ -18,7 +18,6 @@ import com.powsybl.openrao.data.raoresult.api.RaoResult; import com.powsybl.openrao.raoapi.InterTemporalRaoInput; import com.powsybl.openrao.raoapi.InterTemporalRaoProvider; -import com.powsybl.openrao.raoapi.Rao; import com.powsybl.openrao.raoapi.RaoInput; import com.powsybl.openrao.raoapi.parameters.RaoParameters; import com.powsybl.openrao.raoapi.parameters.extensions.OpenRaoSearchTreeParameters; @@ -29,6 +28,7 @@ import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.OptimizationPerimeter; import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.PreventiveOptimizationPerimeter; import com.powsybl.openrao.searchtreerao.commons.parameters.RangeActionLimitationParameters; +import com.powsybl.openrao.searchtreerao.fastrao.FastRao; import com.powsybl.openrao.searchtreerao.linearoptimisation.inputs.IteratingLinearOptimizerInput; import com.powsybl.openrao.searchtreerao.linearoptimisation.parameters.IteratingLinearOptimizerParameters; import com.powsybl.openrao.searchtreerao.marmot.results.GlobalFlowResult; @@ -91,9 +91,10 @@ public CompletableFuture> run(InterTemporalRaoInput raoI } private static TemporalData runTopologicalOptimization(TemporalData raoInputs, RaoParameters raoParameters) { + Set consideredCnecs = new HashSet<>(); return raoInputs.map(individualRaoInput -> { OpenRaoLoggerProvider.TECHNICAL_LOGS.info("[MARMOT] Running RAO for timestamp {}", individualRaoInput.getCrac().getTimestamp().orElseThrow()); - return Rao.run(individualRaoInput, raoParameters); + return FastRao.launchFilteredRao(individualRaoInput, raoParameters, null, consideredCnecs); }); } diff --git a/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/FastRaoResultImpl.java b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/FastRaoResultImpl.java new file mode 100644 index 0000000000..dd1074254e --- /dev/null +++ b/ra-optimisation/search-tree-rao/src/main/java/com/powsybl/openrao/searchtreerao/result/impl/FastRaoResultImpl.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2021, 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.result.impl; + +import com.powsybl.openrao.commons.OpenRaoException; +import com.powsybl.openrao.commons.PhysicalParameter; +import com.powsybl.openrao.commons.Unit; +import com.powsybl.openrao.data.crac.api.*; +import com.powsybl.openrao.data.crac.api.cnec.FlowCnec; +import com.powsybl.openrao.data.crac.api.networkaction.NetworkAction; +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.data.raoresult.api.RaoResult; +import com.powsybl.openrao.searchtreerao.result.api.*; + +import com.powsybl.iidm.network.TwoSides; + +import java.util.*; + +import static com.powsybl.openrao.data.raoresult.api.ComputationStatus.*; + +/** + * @author Philippe Edwards {@literal } + */ +public class FastRaoResultImpl implements RaoResult { + private final PrePerimeterResult initialResult; + private final PrePerimeterResult afterPraResult; + private final PrePerimeterResult afterAraResult; + private final PrePerimeterResult finalResult; + private final RaoResult filteredRaoResult; + private final Crac crac; + private String executionDetails; + + public FastRaoResultImpl(PrePerimeterResult initialResult, + PrePerimeterResult afterPraResult, + PrePerimeterResult afterAraResult, + PrePerimeterResult finalResult, + RaoResult filteredRaoResult, + Crac crac) { + this.initialResult = initialResult; + this.afterPraResult = afterPraResult; + this.afterAraResult = afterAraResult; + this.finalResult = finalResult; + this.filteredRaoResult = filteredRaoResult; + this.crac = crac; + executionDetails = filteredRaoResult.getExecutionDetails(); + removeFailingContingencies(initialResult, afterPraResult, afterAraResult, finalResult, crac); + } + + private static void removeFailingContingencies(PrePerimeterResult initialResult, PrePerimeterResult afterPraResult, PrePerimeterResult afterAraResult, PrePerimeterResult finalResult, Crac crac) { + Set failingContingencies = new HashSet<>(); + crac.getStates().stream().filter(state -> initialResult.getComputationStatus(state) == FAILURE && !state.isPreventive()) + .forEach(state -> failingContingencies.add(state.getContingency().get().getId())); + crac.getStates().stream().filter(state -> afterPraResult.getComputationStatus(state) == FAILURE && !state.isPreventive()) + .forEach(state -> failingContingencies.add(state.getContingency().get().getId())); + crac.getStates().stream().filter(state -> afterAraResult.getComputationStatus(state) == FAILURE && !state.isPreventive()) + .forEach(state -> failingContingencies.add(state.getContingency().get().getId())); + crac.getStates().stream().filter(state -> finalResult.getComputationStatus(state) == FAILURE && !state.isPreventive()) + .forEach(state -> failingContingencies.add(state.getContingency().get().getId())); + initialResult.excludeContingencies(failingContingencies); + afterPraResult.excludeContingencies(failingContingencies); + afterAraResult.excludeContingencies(failingContingencies); + finalResult.excludeContingencies(failingContingencies); + } + + @Override + public ComputationStatus getComputationStatus() { + //TODO: PreventivAndCurativesRaoResult has a postContingencyResults object that we go through to evaluate the PARTIAL_FAILURE status. Understand why this object is not defined here. + if (initialResult.getSensitivityStatus() == FAILURE) { + return FAILURE; + } + if (initialResult.getSensitivityStatus() == PARTIAL_FAILURE || + finalResult == null || finalResult.getSensitivityStatus() != DEFAULT || + afterPraResult == null || afterPraResult.getSensitivityStatus() != DEFAULT || + afterAraResult == null || afterAraResult.getSensitivityStatus() != DEFAULT + ) { + return PARTIAL_FAILURE; + } + return DEFAULT; + + } + + @Override + public ComputationStatus getComputationStatus(State state) { + return getAppropriateResult(state.getInstant()).getComputationStatus(state); + } + + public PrePerimeterResult getAppropriateResult(Instant optimizedInstant) { + if (optimizedInstant == null) { + return initialResult; + } + if (optimizedInstant.isPreventive() || optimizedInstant.isOutage()) { + return afterPraResult; + } + if (optimizedInstant.isAuto()) { + return afterAraResult; + } + if (optimizedInstant.isCurative()) { + return finalResult; + } + throw new OpenRaoException(String.format("Optimized instant %s was not recognized", optimizedInstant)); + } + + public PrePerimeterResult getAppropriateResult(Instant optimizedInstant, FlowCnec flowCnec) { + if (Objects.isNull(optimizedInstant)) { + return initialResult; + } + Instant minInstant = optimizedInstant.comesBefore(flowCnec.getState().getInstant()) ? + optimizedInstant : flowCnec.getState().getInstant(); + return getAppropriateResult(minInstant); + } + + @Override + public double getMargin(Instant optimizedInstant, FlowCnec flowCnec, Unit unit) { + return getAppropriateResult(optimizedInstant, flowCnec).getMargin(flowCnec, unit); + } + + @Override + public double getRelativeMargin(Instant optimizedInstant, FlowCnec flowCnec, Unit unit) { + return getAppropriateResult(optimizedInstant, flowCnec).getRelativeMargin(flowCnec, unit); + } + + @Override + public double getFlow(Instant optimizedInstant, FlowCnec flowCnec, TwoSides side, Unit unit) { + return getAppropriateResult(optimizedInstant, flowCnec).getFlow(flowCnec, side, unit); + } + + @Override + public double getCommercialFlow(Instant optimizedInstant, FlowCnec flowCnec, TwoSides side, Unit unit) { + return getAppropriateResult(optimizedInstant, flowCnec).getCommercialFlow(flowCnec, side, unit); + } + + @Override + public double getLoopFlow(Instant optimizedInstant, FlowCnec flowCnec, TwoSides side, Unit unit) { + return getAppropriateResult(optimizedInstant, flowCnec).getLoopFlow(flowCnec, side, unit); + } + + @Override + public double getPtdfZonalSum(Instant optimizedInstant, FlowCnec flowCnec, TwoSides side) { + return getAppropriateResult(optimizedInstant, flowCnec).getPtdfZonalSum(flowCnec, side); + } + + @Override + public double getFunctionalCost(Instant optimizedInstant) { + return getAppropriateResult(optimizedInstant).getFunctionalCost(); + } + + public List getMostLimitingElements(Instant optimizedInstant, int number) { + return getAppropriateResult(optimizedInstant).getMostLimitingElements(number); + } + + @Override + public double getVirtualCost(Instant optimizedInstant) { + return getAppropriateResult(optimizedInstant).getVirtualCost(); + } + + @Override + public Set getVirtualCostNames() { + Set virtualCostNames = new HashSet<>(); + if (initialResult.getVirtualCostNames() != null) { + virtualCostNames.addAll(initialResult.getVirtualCostNames()); + } + if (finalResult.getVirtualCostNames() != null) { + virtualCostNames.addAll(finalResult.getVirtualCostNames()); + } + return virtualCostNames; + } + + @Override + public double getVirtualCost(Instant optimizedInstant, String virtualCostName) { + return getAppropriateResult(optimizedInstant).getVirtualCost(virtualCostName); + } + + public List getCostlyElements(Instant optimizedInstant, String virtualCostName, int number) { + return getAppropriateResult(optimizedInstant).getCostlyElements(virtualCostName, number); + } + + @Override + public boolean isActivatedDuringState(State state, RemedialAction remedialAction) { + if (remedialAction instanceof NetworkAction networkAction) { + return isActivatedDuringState(state, networkAction); + } else if (remedialAction instanceof RangeAction rangeAction) { + return isActivatedDuringState(state, rangeAction); + } else { + throw new OpenRaoException("Unrecognized remedial action type"); + } + } + + @Override + public boolean wasActivatedBeforeState(State state, NetworkAction networkAction) { + return filteredRaoResult.wasActivatedBeforeState(state, networkAction); + } + + @Override + public boolean isActivatedDuringState(State state, NetworkAction networkAction) { + return filteredRaoResult.isActivatedDuringState(state, networkAction); + } + + @Override + public Set getActivatedNetworkActionsDuringState(State state) { + return filteredRaoResult.getActivatedNetworkActionsDuringState(state); + } + + @Override + public boolean isActivatedDuringState(State state, RangeAction rangeAction) { + return filteredRaoResult.isActivatedDuringState(state, rangeAction); + } + + @Override + public int getPreOptimizationTapOnState(State state, PstRangeAction pstRangeAction) { + return filteredRaoResult.getPreOptimizationTapOnState(state, pstRangeAction); + } + + @Override + public int getOptimizedTapOnState(State state, PstRangeAction pstRangeAction) { + return filteredRaoResult.getOptimizedTapOnState(state, pstRangeAction); + } + + @Override + public double getPreOptimizationSetPointOnState(State state, RangeAction rangeAction) { + return filteredRaoResult.getPreOptimizationSetPointOnState(state, rangeAction); + } + + @Override + public double getOptimizedSetPointOnState(State state, RangeAction rangeAction) { + return filteredRaoResult.getOptimizedSetPointOnState(state, rangeAction); + } + + @Override + public Set> getActivatedRangeActionsDuringState(State state) { + return filteredRaoResult.getActivatedRangeActionsDuringState(state); + } + + @Override + public Map getOptimizedTapsOnState(State state) { + return filteredRaoResult.getOptimizedTapsOnState(state); + + } + + @Override + public Map, Double> getOptimizedSetPointsOnState(State state) { + return filteredRaoResult.getOptimizedSetPointsOnState(state); + } + + @Override + public String getExecutionDetails() { + return executionDetails; + } + + @Override + public void setExecutionDetails(String executionDetails) { + this.executionDetails = executionDetails; + } + + @Override + public boolean isSecure(Instant optimizedInstant, PhysicalParameter... u) { + if (ComputationStatus.FAILURE.equals(getComputationStatus())) { + return false; + } + return getFunctionalCost(optimizedInstant) < 0; + } + + @Override + public boolean isSecure(PhysicalParameter... u) { + return isSecure(crac.getLastInstant(), u); + } + +} diff --git a/ra-optimisation/search-tree-rao/src/test/java/com/powsybl/openrao/searchtreerao/commons/marginevaluator/MarginEvaluatorWithMarginDecreaseUnoptimizedCnecsTest.java b/ra-optimisation/search-tree-rao/src/test/java/com/powsybl/openrao/searchtreerao/commons/marginevaluator/MarginEvaluatorWithMarginDecreaseUnoptimizedCnecsTest.java index 897c19df4a..d630ac328c 100644 --- a/ra-optimisation/search-tree-rao/src/test/java/com/powsybl/openrao/searchtreerao/commons/marginevaluator/MarginEvaluatorWithMarginDecreaseUnoptimizedCnecsTest.java +++ b/ra-optimisation/search-tree-rao/src/test/java/com/powsybl/openrao/searchtreerao/commons/marginevaluator/MarginEvaluatorWithMarginDecreaseUnoptimizedCnecsTest.java @@ -8,6 +8,8 @@ package com.powsybl.openrao.searchtreerao.commons.marginevaluator; 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.iidm.network.TwoSides; import com.powsybl.openrao.searchtreerao.result.api.FlowResult; @@ -72,6 +74,11 @@ void getMarginInMegawattOnConstrainedUnoptimizedCnec() { @Test void getMarginInMegawattOnUnconstrainedUnoptimizedCnec() { when(flowCnec.getOperator()).thenReturn("FR"); + Instant instant = Mockito.mock(Instant.class); + when(instant.isCurative()).thenReturn(true); + State state = Mockito.mock(State.class); + when(state.getInstant()).thenReturn(instant); + when(flowCnec.getState()).thenReturn(state); when(currentFlowResult.getMargin(flowCnec, TwoSides.ONE, Unit.MEGAWATT)).thenReturn(200.); when(prePerimeterFlowResult.getMargin(flowCnec, TwoSides.ONE, Unit.MEGAWATT)).thenReturn(100.); diff --git a/ra-optimisation/search-tree-rao/src/test/java/com/powsybl/openrao/searchtreerao/commons/objectivefunctionevaluator/MinMarginViolationEvaluatorTest.java b/ra-optimisation/search-tree-rao/src/test/java/com/powsybl/openrao/searchtreerao/commons/objectivefunctionevaluator/MinMarginViolationEvaluatorTest.java index bcb02d1dba..b67f645530 100644 --- a/ra-optimisation/search-tree-rao/src/test/java/com/powsybl/openrao/searchtreerao/commons/objectivefunctionevaluator/MinMarginViolationEvaluatorTest.java +++ b/ra-optimisation/search-tree-rao/src/test/java/com/powsybl/openrao/searchtreerao/commons/objectivefunctionevaluator/MinMarginViolationEvaluatorTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -56,6 +57,6 @@ void testWithoutOverload() { MinMarginViolationEvaluator evaluator = new MinMarginViolationEvaluator(Set.of(flowCnec), Unit.MEGAWATT, new BasicMarginEvaluator()); CostEvaluatorResult result = evaluator.evaluate(flowResult, null); assertEquals(0.0, result.getCost(Set.of())); - assertEquals(List.of(flowCnec), result.getCostlyElements(Set.of())); + assertEquals(Collections.emptyList(), result.getCostlyElements(Set.of())); } }