All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.powsybl.openrao.searchtreerao.searchtree.algorithms.SearchTree Maven / Gradle / Ivy

/*
 * Copyright (c) 2019, 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.searchtree.algorithms;

import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.commons.Unit;
import com.powsybl.openrao.commons.logs.OpenRaoLogger;
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.data.crac.api.networkaction.NetworkAction;
import com.powsybl.openrao.searchtreerao.commons.NetworkActionCombination;
import com.powsybl.openrao.searchtreerao.commons.RaoLogger;
import com.powsybl.openrao.searchtreerao.commons.SensitivityComputer;
import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.GlobalOptimizationPerimeter;
import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.OptimizationPerimeter;
import com.powsybl.openrao.searchtreerao.commons.parameters.TreeParameters;
import com.powsybl.openrao.searchtreerao.result.api.OptimizationResult;
import com.powsybl.openrao.searchtreerao.result.api.PrePerimeterResult;
import com.powsybl.openrao.searchtreerao.result.api.RangeActionActivationResult;
import com.powsybl.openrao.searchtreerao.result.impl.RangeActionActivationResultImpl;
import com.powsybl.openrao.searchtreerao.searchtree.inputs.SearchTreeInput;
import com.powsybl.openrao.searchtreerao.searchtree.parameters.SearchTreeParameters;
import com.powsybl.openrao.sensitivityanalysis.AppliedRemedialActions;
import com.powsybl.openrao.util.AbstractNetworkPool;
import com.google.common.hash.Hashing;
import com.powsybl.iidm.network.Network;
import org.apache.commons.lang3.NotImplementedException;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.powsybl.openrao.commons.logs.OpenRaoLoggerProvider.*;
import static com.powsybl.openrao.searchtreerao.castor.algorithm.AutomatonSimulator.getRangeActionsAndTheirTapsAppliedOnState;

/**
 * The "tree" is one of the core object of the search-tree algorithm.
 * It aims at finding a good combination of Network Actions.
 * 

* The tree is composed of leaves which evaluate the impact of Network Actions, * one by one. The tree is orchestrating the leaves : it looks for a smart * routing among the leaves in order to converge as quickly as possible to a local * minimum of the objective function. *

* The leaves of a same depth can be evaluated simultaneously. * * @author Joris Mancini {@literal } * @author Baptiste Seguinot {@literal } */ public class SearchTree { private static final int NUMBER_LOGGED_ELEMENTS_DURING_TREE = 2; private static final int NUMBER_LOGGED_ELEMENTS_END_TREE = 5; private static final int NUMBER_LOGGED_VIRTUAL_COSTLY_ELEMENTS = 10; /** * attribute defined in constructor of the search tree class */ private final SearchTreeInput input; private final SearchTreeParameters parameters; private final OpenRaoLogger topLevelLogger; /** * attribute defined and used within the class */ private final boolean purelyVirtual; private final SearchTreeBloomer bloomer; private Leaf rootLeaf; private Leaf optimalLeaf; private Leaf previousDepthOptimalLeaf; private Optional combinationFulfillingStopCriterion = Optional.empty(); public SearchTree(SearchTreeInput input, SearchTreeParameters parameters, boolean verbose) { // inputs this.input = input; this.parameters = parameters; this.topLevelLogger = verbose ? BUSINESS_LOGS : TECHNICAL_LOGS; // build from inputs this.purelyVirtual = input.getOptimizationPerimeter().getOptimizedFlowCnecs().isEmpty(); this.bloomer = new SearchTreeBloomer(input, parameters); } public CompletableFuture run() { initLeaves(input); TECHNICAL_LOGS.debug("Evaluating root leaf"); rootLeaf.evaluate(input.getObjectiveFunction(), getSensitivityComputerForEvaluation(true)); if (rootLeaf.getStatus().equals(Leaf.Status.ERROR)) { topLevelLogger.info("Could not evaluate leaf: {}", rootLeaf); logOptimizationSummary(rootLeaf); rootLeaf.finalizeOptimization(); return CompletableFuture.completedFuture(rootLeaf); } else if (stopCriterionReached(rootLeaf)) { topLevelLogger.info("Stop criterion reached on {}", rootLeaf); RaoLogger.logMostLimitingElementsResults(topLevelLogger, rootLeaf, parameters.getObjectiveFunction(), NUMBER_LOGGED_ELEMENTS_END_TREE); logOptimizationSummary(rootLeaf); rootLeaf.finalizeOptimization(); return CompletableFuture.completedFuture(rootLeaf); } TECHNICAL_LOGS.info("{}", rootLeaf); RaoLogger.logMostLimitingElementsResults(TECHNICAL_LOGS, rootLeaf, parameters.getObjectiveFunction(), NUMBER_LOGGED_ELEMENTS_DURING_TREE); TECHNICAL_LOGS.info("Linear optimization on root leaf"); optimizeLeaf(rootLeaf); topLevelLogger.info("{}", rootLeaf); RaoLogger.logRangeActions(TECHNICAL_LOGS, optimalLeaf, input.getOptimizationPerimeter(), null); RaoLogger.logMostLimitingElementsResults(topLevelLogger, optimalLeaf, parameters.getObjectiveFunction(), NUMBER_LOGGED_ELEMENTS_DURING_TREE); logVirtualCostInformation(rootLeaf, ""); if (stopCriterionReached(rootLeaf)) { logOptimizationSummary(rootLeaf); rootLeaf.finalizeOptimization(); return CompletableFuture.completedFuture(rootLeaf); } iterateOnTree(); TECHNICAL_LOGS.info("Search-tree RAO completed with status {}", optimalLeaf.getSensitivityStatus()); TECHNICAL_LOGS.info("Best leaf: {}", optimalLeaf); RaoLogger.logRangeActions(TECHNICAL_LOGS, optimalLeaf, input.getOptimizationPerimeter(), "Best leaf: "); RaoLogger.logMostLimitingElementsResults(TECHNICAL_LOGS, optimalLeaf, parameters.getObjectiveFunction(), NUMBER_LOGGED_ELEMENTS_END_TREE); logOptimizationSummary(optimalLeaf); optimalLeaf.finalizeOptimization(); return CompletableFuture.completedFuture(optimalLeaf); } void initLeaves(SearchTreeInput input) { rootLeaf = makeLeaf(input.getOptimizationPerimeter(), input.getNetwork(), input.getPrePerimeterResult(), input.getPreOptimizationAppliedRemedialActions()); optimalLeaf = rootLeaf; previousDepthOptimalLeaf = rootLeaf; } Leaf makeLeaf(OptimizationPerimeter optimizationPerimeter, Network network, PrePerimeterResult prePerimeterOutput, AppliedRemedialActions appliedRemedialActionsInSecondaryStates) { return new Leaf(optimizationPerimeter, network, prePerimeterOutput, appliedRemedialActionsInSecondaryStates); } private void logOptimizationSummary(Leaf optimalLeaf) { State state = input.getOptimizationPerimeter().getMainOptimizationState(); RaoLogger.logOptimizationSummary(BUSINESS_LOGS, state, optimalLeaf.getActivatedNetworkActions(), getRangeActionsAndTheirTapsAppliedOnState(optimalLeaf, state), rootLeaf.getPreOptimObjectiveFunctionResult(), optimalLeaf); logVirtualCostInformation(optimalLeaf, ""); } private void iterateOnTree() { int depth = 0; boolean hasImproved = true; if (input.getOptimizationPerimeter().getNetworkActions().isEmpty()) { topLevelLogger.info("No network action available"); return; } int leavesInParallel = Math.min(input.getOptimizationPerimeter().getNetworkActions().size(), parameters.getTreeParameters().leavesInParallel()); TECHNICAL_LOGS.debug("Evaluating {} leaves in parallel", leavesInParallel); try (AbstractNetworkPool networkPool = makeOpenRaoNetworkPool(input.getNetwork(), leavesInParallel)) { while (depth < parameters.getTreeParameters().maximumSearchDepth() && hasImproved && !stopCriterionReached(optimalLeaf)) { TECHNICAL_LOGS.info("Search depth {} [start]", depth + 1); previousDepthOptimalLeaf = optimalLeaf; updateOptimalLeafWithNextDepthBestLeaf(networkPool); hasImproved = previousDepthOptimalLeaf != optimalLeaf; // It means this depth evaluation has improved the global cost if (hasImproved) { TECHNICAL_LOGS.info("Search depth {} [end]", depth + 1); topLevelLogger.info("Search depth {} best leaf: {}", depth + 1, optimalLeaf); RaoLogger.logRangeActions(TECHNICAL_LOGS, optimalLeaf, input.getOptimizationPerimeter(), String.format("Search depth %s best leaf: ", depth + 1)); RaoLogger.logMostLimitingElementsResults(topLevelLogger, optimalLeaf, parameters.getObjectiveFunction(), NUMBER_LOGGED_ELEMENTS_DURING_TREE); } else { topLevelLogger.info("No better result found in search depth {}, exiting search tree", depth + 1); } depth += 1; if (depth >= parameters.getTreeParameters().maximumSearchDepth()) { topLevelLogger.info("maximum search depth has been reached, exiting search tree"); } } networkPool.shutdownAndAwaitTermination(24, TimeUnit.HOURS); } catch (InterruptedException e) { TECHNICAL_LOGS.warn("A computation thread was interrupted"); Thread.currentThread().interrupt(); } } /** * Evaluate all the leaves. We use OpenRaoNetworkPool to parallelize the computation */ private void updateOptimalLeafWithNextDepthBestLeaf(AbstractNetworkPool networkPool) throws InterruptedException { TreeSet naCombinationsSorted = new TreeSet<>(this::deterministicNetworkActionCombinationComparison); naCombinationsSorted.addAll(bloomer.bloom(optimalLeaf, input.getOptimizationPerimeter().getNetworkActions())); int numberOfCombinations = naCombinationsSorted.size(); networkPool.initClones(numberOfCombinations); if (naCombinationsSorted.isEmpty()) { TECHNICAL_LOGS.info("No more network action available"); return; } else { TECHNICAL_LOGS.info("Leaves to evaluate: {}", numberOfCombinations); } AtomicInteger remainingLeaves = new AtomicInteger(numberOfCombinations); List> tasks = naCombinationsSorted.stream().map(naCombination -> networkPool.submit(() -> optimizeOneLeaf(networkPool, naCombination, remainingLeaves)) ).toList(); for (ForkJoinTask task : tasks) { try { task.get(); } catch (ExecutionException e) { throw new OpenRaoException(e); } } } private Object optimizeOneLeaf(AbstractNetworkPool networkPool, NetworkActionCombination naCombination, AtomicInteger remainingLeaves) throws InterruptedException { Network networkClone = networkPool.getAvailableNetwork(); //This is where the threads actually wait for available networks try { if (combinationFulfillingStopCriterion.isEmpty() || deterministicNetworkActionCombinationComparison(naCombination, combinationFulfillingStopCriterion.get()) < 0) { boolean shouldRangeActionBeRemoved = bloomer.shouldRangeActionsBeRemovedToApplyNa(naCombination, optimalLeaf); if (shouldRangeActionBeRemoved) { // Remove parentLeaf range actions to respect every maxRa or maxOperator limitation input.getOptimizationPerimeter().getRangeActions().forEach(ra -> ra.apply(networkClone, input.getPrePerimeterResult().getRangeActionSetpointResult().getSetpoint(ra)) ); } else { // Apply range actions that have been changed by the previous leaf on the network to start next depth leaves // from previous optimal leaf starting point previousDepthOptimalLeaf.getRangeActions() .forEach(ra -> ra.apply(networkClone, previousDepthOptimalLeaf.getOptimizedSetpoint(ra, input.getOptimizationPerimeter().getMainOptimizationState())) ); } optimizeNextLeafAndUpdate(naCombination, shouldRangeActionBeRemoved, networkClone); } else { topLevelLogger.info("Skipping {} optimization because earlier combination fulfills stop criterion.", naCombination.getConcatenatedId()); } } catch (Exception e) { BUSINESS_WARNS.warn("Cannot optimize remedial action combination {}: {}", naCombination.getConcatenatedId(), e.getMessage()); } TECHNICAL_LOGS.info("Remaining leaves to evaluate: {}", remainingLeaves.decrementAndGet()); networkPool.releaseUsedNetwork(networkClone); return null; } int deterministicNetworkActionCombinationComparison(NetworkActionCombination ra1, NetworkActionCombination ra2) { // 1. First priority given to combinations detected during RAO int comp1 = compareIsDetectedDuringRao(ra1, ra2); if (comp1 != 0) { return comp1; } // 2. Second priority given to pre-defined combinations int comp2 = compareIsPreDefined(ra1, ra2); if (comp2 != 0) { return comp2; } // 3. Third priority given to large combinations int comp3 = compareSize(ra1, ra2); if (comp3 != 0) { return comp3; } // 4. Last priority is random but deterministic return Integer.compare(Hashing.crc32().hashString(ra1.getConcatenatedId(), StandardCharsets.UTF_8).asInt(), Hashing.crc32().hashString(ra2.getConcatenatedId(), StandardCharsets.UTF_8).asInt()); } /** * Prioritizes the better network action combination that was detected by the RAO */ private int compareIsDetectedDuringRao(NetworkActionCombination ra1, NetworkActionCombination ra2) { return -Boolean.compare(ra1.isDetectedDuringRao(), ra2.isDetectedDuringRao()); } /** * Prioritizes the network action combination that pre-defined by the user */ private int compareIsPreDefined(NetworkActionCombination ra1, NetworkActionCombination ra2) { return -Boolean.compare(this.bloomer.hasPreDefinedNetworkActionCombination(ra1), this.bloomer.hasPreDefinedNetworkActionCombination(ra2)); } /** * Prioritizes the bigger network action combination */ private int compareSize(NetworkActionCombination ra1, NetworkActionCombination ra2) { return -Integer.compare(ra1.getNetworkActionSet().size(), ra2.getNetworkActionSet().size()); } private String printNetworkActions(Set networkActions) { return networkActions.stream().map(NetworkAction::getId).collect(Collectors.joining(" + ")); } AbstractNetworkPool makeOpenRaoNetworkPool(Network network, int leavesInParallel) { return AbstractNetworkPool.create(network, network.getVariantManager().getWorkingVariantId(), leavesInParallel, false); } void optimizeNextLeafAndUpdate(NetworkActionCombination naCombination, boolean shouldRangeActionBeRemoved, Network network) { Leaf leaf; try { // We get initial range action results from the previous optimal leaf leaf = createChildLeaf(network, naCombination, shouldRangeActionBeRemoved); } catch (OpenRaoException e) { Set networkActions = new HashSet<>(previousDepthOptimalLeaf.getActivatedNetworkActions()); networkActions.addAll(naCombination.getNetworkActionSet()); topLevelLogger.info("Could not evaluate network action combination \"{}\": {}", printNetworkActions(networkActions), e.getMessage()); return; } catch (NotImplementedException e) { throw e; } // We evaluate the leaf with taking the results of the previous optimal leaf if we do not want to update some results leaf.evaluate(input.getObjectiveFunction(), getSensitivityComputerForEvaluation(shouldRangeActionBeRemoved)); topLevelLogger.info("Evaluated {}", leaf); if (!leaf.getStatus().equals(Leaf.Status.ERROR)) { if (!stopCriterionReached(leaf)) { if (combinationFulfillingStopCriterion.isPresent() && deterministicNetworkActionCombinationComparison(naCombination, combinationFulfillingStopCriterion.get()) > 0) { topLevelLogger.info("Skipping {} optimization because earlier combination fulfills stop criterion.", naCombination.getConcatenatedId()); } else { optimizeLeaf(leaf); topLevelLogger.info("Optimized {}", leaf); logVirtualCostInformation(leaf, "Optimized "); } } else { topLevelLogger.info("Optimized {}", leaf); } updateOptimalLeaf(leaf, naCombination); } else { topLevelLogger.info("Could not evaluate {}", leaf); } } Leaf createChildLeaf(Network network, NetworkActionCombination naCombination, boolean shouldRangeActionBeRemoved) { return new Leaf( input.getOptimizationPerimeter(), network, previousDepthOptimalLeaf.getActivatedNetworkActions(), naCombination, shouldRangeActionBeRemoved ? new RangeActionActivationResultImpl(input.getPrePerimeterResult()) : previousDepthOptimalLeaf.getRangeActionActivationResult(), input.getPrePerimeterResult(), shouldRangeActionBeRemoved ? input.getPreOptimizationAppliedRemedialActions() : getPreviousDepthAppliedRemedialActionsBeforeNewLeafEvaluation(previousDepthOptimalLeaf)); } private void optimizeLeaf(Leaf leaf) { if (!input.getOptimizationPerimeter().getRangeActions().isEmpty()) { leaf.optimize(input, parameters); if (!leaf.getStatus().equals(Leaf.Status.OPTIMIZED)) { topLevelLogger.info("Failed to optimize leaf: {}", leaf); } } else { TECHNICAL_LOGS.info("No range actions to optimize"); } } private SensitivityComputer getSensitivityComputerForEvaluation(boolean isRootLeaf) { SensitivityComputer.SensitivityComputerBuilder sensitivityComputerBuilder = SensitivityComputer.create() .withToolProvider(input.getToolProvider()) .withCnecs(input.getOptimizationPerimeter().getFlowCnecs()) .withRangeActions(input.getOptimizationPerimeter().getRangeActions()) .withOutageInstant(input.getOutageInstant()); if (isRootLeaf) { sensitivityComputerBuilder.withAppliedRemedialActions(input.getPreOptimizationAppliedRemedialActions()); } else { sensitivityComputerBuilder.withAppliedRemedialActions(getPreviousDepthAppliedRemedialActionsBeforeNewLeafEvaluation(previousDepthOptimalLeaf)); } if (parameters.getObjectiveFunction().relativePositiveMargins()) { if (parameters.getMaxMinRelativeMarginParameters().getPtdfApproximation().shouldUpdatePtdfWithTopologicalChange()) { sensitivityComputerBuilder.withPtdfsResults(input.getToolProvider().getAbsolutePtdfSumsComputation(), input.getOptimizationPerimeter().getFlowCnecs()); } else { sensitivityComputerBuilder.withPtdfsResults(input.getInitialFlowResult()); } } if (parameters.getLoopFlowParameters() != null && parameters.getLoopFlowParameters().getPtdfApproximation().shouldUpdatePtdfWithTopologicalChange()) { sensitivityComputerBuilder.withCommercialFlowsResults(input.getToolProvider().getLoopFlowComputation(), input.getOptimizationPerimeter().getLoopFlowCnecs()); } else if (parameters.getLoopFlowParameters() != null) { sensitivityComputerBuilder.withCommercialFlowsResults(input.getInitialFlowResult()); } return sensitivityComputerBuilder.build(); } private synchronized void updateOptimalLeaf(Leaf leaf, NetworkActionCombination networkActionCombination) { if (improvedEnough(leaf)) { // nominal case: stop criterion hasn't been reached yet if (combinationFulfillingStopCriterion.isEmpty() && leaf.getCost() < optimalLeaf.getCost()) { optimalLeaf = leaf; if (stopCriterionReached(leaf)) { TECHNICAL_LOGS.info("Stop criterion reached, other threads may skip optimization."); combinationFulfillingStopCriterion = Optional.of(networkActionCombination); } } // special case: stop criterion has been reached if (combinationFulfillingStopCriterion.isPresent() && stopCriterionReached(leaf) && deterministicNetworkActionCombinationComparison(networkActionCombination, combinationFulfillingStopCriterion.get()) < 0) { optimalLeaf = leaf; combinationFulfillingStopCriterion = Optional.of(networkActionCombination); } } } /** * This method evaluates stop criterion on the leaf. * * @param leaf: Leaf to evaluate. * @return True if the stop criterion has been reached on this leaf. */ private boolean stopCriterionReached(Leaf leaf) { if (leaf.getVirtualCost() > 1e-6) { return false; } if (purelyVirtual && leaf.getVirtualCost() < 1e-6) { TECHNICAL_LOGS.debug("Perimeter is purely virtual and virtual cost is zero. Exiting search tree."); return true; } return costSatisfiesStopCriterion(leaf.getCost()); } /** * Returns true if a given cost value satisfies the stop criterion */ boolean costSatisfiesStopCriterion(double cost) { if (parameters.getTreeParameters().stopCriterion().equals(TreeParameters.StopCriterion.MIN_OBJECTIVE)) { return false; } else if (parameters.getTreeParameters().stopCriterion().equals(TreeParameters.StopCriterion.AT_TARGET_OBJECTIVE_VALUE)) { return cost < parameters.getTreeParameters().targetObjectiveValue(); } else { throw new OpenRaoException("Unexpected stop criterion: " + parameters.getTreeParameters().stopCriterion()); } } /** * This method checks if the leaf's cost respects the minimum impact thresholds * (absolute and relative) compared to the previous depth's optimal leaf. * * @param leaf: Leaf that has to be compared with the optimal leaf. * @return True if the leaf cost diminution is enough compared to optimal leaf. */ private boolean improvedEnough(Leaf leaf) { double relativeImpact = Math.max(parameters.getNetworkActionParameters().getRelativeNetworkActionMinimumImpactThreshold(), 0); double absoluteImpact = Math.max(parameters.getNetworkActionParameters().getAbsoluteNetworkActionMinimumImpactThreshold(), 0); double previousDepthBestCost = previousDepthOptimalLeaf.getCost(); double newCost = leaf.getCost(); if (previousDepthBestCost > newCost && stopCriterionReached(leaf)) { return true; } return previousDepthBestCost - absoluteImpact > newCost // enough absolute impact && (1 - Math.signum(previousDepthBestCost) * relativeImpact) * previousDepthBestCost > newCost; // enough relative impact } private AppliedRemedialActions getPreviousDepthAppliedRemedialActionsBeforeNewLeafEvaluation(RangeActionActivationResult previousDepthRangeActionActivations) { AppliedRemedialActions alreadyAppliedRa = input.getPreOptimizationAppliedRemedialActions().copy(); if (input.getOptimizationPerimeter() instanceof GlobalOptimizationPerimeter) { input.getOptimizationPerimeter().getRangeActionsPerState().entrySet().stream() .filter(e -> !e.getKey().equals(input.getOptimizationPerimeter().getMainOptimizationState())) // remove preventive state .forEach(e -> e.getValue().forEach(ra -> alreadyAppliedRa.addAppliedRangeAction(e.getKey(), ra, previousDepthRangeActionActivations.getOptimizedSetpoint(ra, e.getKey())))); } return alreadyAppliedRa; } /** * This method logs information about positive virtual costs */ private void logVirtualCostInformation(Leaf leaf, String prefix) { leaf.getVirtualCostNames().stream() .filter(virtualCostName -> leaf.getVirtualCost(virtualCostName) > 1e-6) .forEach(virtualCostName -> logVirtualCostDetails(leaf, virtualCostName, prefix)); } /** * If stop criterion could have been reached without the given virtual cost, this method logs a message, in order * to inform the user that the given network action was rejected because of a virtual cost * (message is not logged if it has already been logged at previous depth) * In all cases, this method also logs most costly elements for given virtual cost */ void logVirtualCostDetails(Leaf leaf, String virtualCostName, String prefix) { OpenRaoLogger logger = topLevelLogger; if (!costSatisfiesStopCriterion(leaf.getCost()) && costSatisfiesStopCriterion(leaf.getCost() - leaf.getVirtualCost(virtualCostName)) && (leaf.isRoot() || !costSatisfiesStopCriterion(previousDepthOptimalLeaf.getFunctionalCost()))) { // Stop criterion would have been reached without virtual cost, for the first time at this depth // and for the given leaf BUSINESS_LOGS.info("{}{}, stop criterion could have been reached without \"{}\" virtual cost", prefix, leaf.getIdentifier(), virtualCostName); // Promote detailed logs about costly elements to BUSINESS_LOGS logger = BUSINESS_LOGS; } getVirtualCostlyElementsLogs(leaf, virtualCostName, prefix).forEach(logger::info); } List getVirtualCostlyElementsLogs(Leaf leaf, String virtualCostName, String prefix) { Unit unit = parameters.getObjectiveFunction().getUnit(); List logs = new ArrayList<>(); int i = 1; for (FlowCnec flowCnec : leaf.getCostlyElements(virtualCostName, NUMBER_LOGGED_VIRTUAL_COSTLY_ELEMENTS)) { TwoSides limitingSide = leaf.getMargin(flowCnec, TwoSides.ONE, unit) < leaf.getMargin(flowCnec, TwoSides.TWO, unit) ? TwoSides.ONE : TwoSides.TWO; double flow = leaf.getFlow(flowCnec, limitingSide, unit); Double limitingThreshold = flow >= 0 ? flowCnec.getUpperBound(limitingSide, unit).orElse(flowCnec.getLowerBound(limitingSide, unit).orElse(Double.NaN)) : flowCnec.getLowerBound(limitingSide, unit).orElse(flowCnec.getUpperBound(limitingSide, unit).orElse(Double.NaN)); logs.add(String.format(Locale.ENGLISH, "%s%s, limiting \"%s\" constraint #%02d: flow = %.2f %s, threshold = %.2f %s, margin = %.2f %s, element %s at state %s, CNEC ID = \"%s\", CNEC name = \"%s\"", prefix, leaf.getIdentifier(), virtualCostName, i, flow, unit, limitingThreshold, unit, leaf.getMargin(flowCnec, limitingSide, unit), unit, flowCnec.getNetworkElement().getId(), flowCnec.getState().getId(), flowCnec.getId(), flowCnec.getName())); i++; } return logs; } }