DefaultSecurityAnalysis.java
/**
* Copyright (c) 2016-2018, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.security.impl;
import com.powsybl.commons.config.PlatformConfig;
import com.powsybl.commons.exceptions.UncheckedInterruptedException;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.computation.ComputationManager;
import com.powsybl.contingency.ContingenciesProvider;
import com.powsybl.contingency.Contingency;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.limitmodification.LimitsComputer;
import com.powsybl.loadflow.LoadFlow;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.loadflow.LoadFlowResult;
import com.powsybl.security.*;
import com.powsybl.security.detectors.LimitViolationDetector;
import com.powsybl.security.detectors.LoadingLimitType;
import com.powsybl.security.interceptors.CurrentLimitViolationInterceptor;
import com.powsybl.security.interceptors.RunningContext;
import com.powsybl.security.interceptors.SecurityAnalysisInterceptor;
import com.powsybl.security.monitor.StateMonitor;
import com.powsybl.security.monitor.StateMonitorIndex;
import com.powsybl.security.results.BranchResult;
import com.powsybl.security.results.BusResult;
import com.powsybl.security.results.ConnectivityResult;
import com.powsybl.security.results.ThreeWindingsTransformerResult;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
* @author Teofil Calin BANC {@literal <teofil-calin.banc at rte-france.com>}
*/
public class DefaultSecurityAnalysis {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSecurityAnalysis.class);
/**
* This executor is used to create the variants of the network, submit the tasks
* for computing contingency loadflows and submit the tasks for checking for the
* violations. Submitting tasks itself is blocking because we can only run a
* limited number of loadflows in parallel because we need the memory for the
* variant, and we don't want to submit tasks that would immediately block to
* get an available variant (they hurt the performance of the executor who
* excutes them)
*/
private static final ExecutorService SCHEDULER_EXECUTOR = createThreadPool(getOptionalIntProperty("default-security-analysis", "scheduler-pool-size", 10));
private static final int MAX_VARIANTS_PER_ANALYSIS = getOptionalIntProperty("default-security-analysis", "max-variants-per-analysis", 10);
/**
* Return the value of the property or the default value if the module or the property doesn't exist in the configuration.
*
* @param moduleName The name of the module
* @param propertyName The name of the property
* @param defaultValue The default value
* @return The value of the property if it exists, the default value otherwise
*/
private static int getOptionalIntProperty(String moduleName, String propertyName, int defaultValue) {
return PlatformConfig.defaultConfig()
.getOptionalModuleConfig(moduleName)
.map(m -> m.getOptionalIntProperty(propertyName).orElse(defaultValue))
.orElse(defaultValue);
}
private static ExecutorService createThreadPool(int poolSize) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, poolSize, 1L, TimeUnit.NANOSECONDS, new LinkedBlockingQueue<>());
executor.allowCoreThreadTimeOut(true);
return executor;
}
private final ComputationManager computationManager;
private final Network network;
private final LimitViolationDetector violationDetector;
private final LimitViolationFilter violationFilter;
private final List<SecurityAnalysisInterceptor> interceptors;
private final StateMonitorIndex monitorIndex;
private final ReportNode reportNode;
public DefaultSecurityAnalysis(Network network, LimitViolationFilter filter, ComputationManager computationManager,
List<StateMonitor> monitors, ReportNode reportNode) {
this(network, null, filter, computationManager, monitors, reportNode);
}
public DefaultSecurityAnalysis(Network network, @Nullable LimitViolationDetector detector,
LimitViolationFilter filter, ComputationManager computationManager,
List<StateMonitor> monitors, ReportNode reportNode) {
this.network = Objects.requireNonNull(network);
this.violationDetector = detector;
this.violationFilter = Objects.requireNonNull(filter);
this.interceptors = new ArrayList<>();
this.computationManager = Objects.requireNonNull(computationManager);
this.monitorIndex = new StateMonitorIndex(monitors);
this.reportNode = Objects.requireNonNull(reportNode);
interceptors.add(new CurrentLimitViolationInterceptor());
}
public void addInterceptor(SecurityAnalysisInterceptor interceptor) {
interceptors.add(Objects.requireNonNull(interceptor));
}
public boolean removeInterceptor(SecurityAnalysisInterceptor interceptor) {
return interceptors.remove(interceptor);
}
private SecurityAnalysisResultBuilder createResultBuilder(String initialWorkingStateId) {
return new SecurityAnalysisResultBuilder(violationFilter, new RunningContext(network, initialWorkingStateId), interceptors);
}
public CompletableFuture<SecurityAnalysisReport> run(String workingVariantId,
SecurityAnalysisParameters securityAnalysisParameters, ContingenciesProvider contingenciesProvider) {
Objects.requireNonNull(workingVariantId);
Objects.requireNonNull(securityAnalysisParameters);
Objects.requireNonNull(contingenciesProvider);
LoadFlowParameters loadFlowParameters = securityAnalysisParameters.getLoadFlowParameters();
// start post contingency LF from pre-contingency state variables
LoadFlowParameters postContParameters = loadFlowParameters.copy()
.setVoltageInitMode(LoadFlowParameters.VoltageInitMode.PREVIOUS_VALUES);
SecurityAnalysisResultBuilder resultBuilder = createResultBuilder(workingVariantId);
return LoadFlow
.runAsync(network, workingVariantId, computationManager, loadFlowParameters, reportNode)
.thenCompose(loadFlowResult -> {
if (loadFlowResult.isOk()) {
return CompletableFuture
.runAsync(() -> {
network.getVariantManager().setWorkingVariant(workingVariantId);
setPreContingencyOkAndCheckViolations(resultBuilder);
}, computationManager.getExecutor())
.thenComposeAsync(aVoid ->
submitAllLoadFlows(workingVariantId, contingenciesProvider, postContParameters, resultBuilder),
SCHEDULER_EXECUTOR);
} else {
return setPreContingencyKo(resultBuilder);
}
})
.thenApply(aVoid -> new SecurityAnalysisReport(resultBuilder.build()));
}
private void setPreContingencyOkAndCheckViolations(SecurityAnalysisResultBuilder resultBuilder) {
SecurityAnalysisResultBuilder.PreContingencyResultBuilder builder =
resultBuilder.preContingency()
.setStatus(LoadFlowResult.ComponentResult.Status.CONVERGED);
checkPreContingencyViolations(network, builder::addViolation);
addMonitorInfos(network, monitorIndex.getAllStateMonitor(), builder::addBranchResult, builder::addBusResult, builder::addThreeWindingsTransformerResult);
addMonitorInfos(network, monitorIndex.getNoneStateMonitor(), builder::addBranchResult, builder::addBusResult, builder::addThreeWindingsTransformerResult);
builder.endPreContingency();
}
private CompletableFuture<Void> setPreContingencyKo(SecurityAnalysisResultBuilder resultBuilder) {
resultBuilder.preContingency().setStatus(LoadFlowResult.ComponentResult.Status.FAILED).endPreContingency();
return CompletableFuture.completedFuture(null);
}
private CompletableFuture<Void> submitAllLoadFlows(String workingVariantId,
ContingenciesProvider contingenciesProvider, LoadFlowParameters postContParameters,
SecurityAnalysisResultBuilder resultBuilder) {
List<Contingency> contingencies = contingenciesProvider.getContingencies(network);
int workerCount = Math.min(MAX_VARIANTS_PER_ANALYSIS, Math.min(computationManager.getResourcesStatus().getAvailableCores(), contingencies.isEmpty() ? 1 : contingencies.size()));
List<String> variantIds = makeWorkingVariantsNames(workerCount);
BlockingQueue<String> queue = new ArrayBlockingQueue<>(workerCount, false, variantIds);
network.getVariantManager().allowVariantMultiThreadAccess(true);
network.getVariantManager().cloneVariant(workingVariantId, variantIds);
return CompletableFuture
.allOf(contingencies.stream()
.map(contingency -> submitOneLoadFlow(workingVariantId, contingency, postContParameters, resultBuilder, queue))
.toArray(CompletableFuture[]::new))
.whenComplete((aVoid, throwable) -> variantIds.forEach(network.getVariantManager()::removeVariant));
}
private static List<String> makeWorkingVariantsNames(int workerCount) {
String hash = UUID.randomUUID().toString();
return IntStream.range(0, workerCount).mapToObj(i -> hash + "_" + i).collect(Collectors.toList());
}
// Block for an available variant, then submit a loadflow on this variant, then
// make the variant available again
private CompletableFuture<Void> submitOneLoadFlow(String workingVariantId, Contingency contingency, LoadFlowParameters postContParameters,
SecurityAnalysisResultBuilder resultBuilder, BlockingQueue<String> queue) {
return CompletableFuture.completedFuture(null).thenCompose(aaVoid -> {
String postContVariantId = getVariantId(queue);
return runOneLoadFlowAsync(workingVariantId, postContVariantId, postContParameters, resultBuilder, contingency)
.whenComplete((aVoid, throwable) -> queue.add(postContVariantId));
});
}
private static String getVariantId(BlockingQueue<String> queue) {
try {
return queue.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new UncheckedInterruptedException(e);
}
}
private CompletableFuture<Void> runOneLoadFlowAsync(String workingVariantId, String postContVariantId, LoadFlowParameters postContParameters,
SecurityAnalysisResultBuilder resultBuilder, Contingency contingency) {
return CompletableFuture
.runAsync(() -> {
LOGGER.debug("Worker {} run loadflow for contingency '{}'.", postContVariantId, contingency.getId());
applyContingency(workingVariantId, postContVariantId, contingency);
}, computationManager.getExecutor())
.thenCompose(aVoid ->
LoadFlow.runAsync(network, postContVariantId, computationManager, postContParameters, reportNode)
)
.thenApplyAsync(lfResult -> {
setContingencyOkAndCheckViolations(postContVariantId, resultBuilder, contingency, lfResult);
return null;
}, computationManager.getExecutor());
}
private void setContingencyOkAndCheckViolations(String postContVariantId, SecurityAnalysisResultBuilder resultBuilder,
Contingency contingency, LoadFlowResult lfResult) {
network.getVariantManager().setWorkingVariant(postContVariantId);
SecurityAnalysisResultBuilder.PostContingencyResultBuilder builder =
resultBuilder.contingency(contingency)
.setStatus(lfResult.isOk() ? PostContingencyComputationStatus.CONVERGED : PostContingencyComputationStatus.FAILED)
.setConnectivityResult(new ConnectivityResult(0, 0, 0.0, 0.0, Collections.emptySet()));
if (lfResult.isOk()) {
checkPostContingencyViolations(contingency, network, builder::addViolation);
addMonitorInfos(network, monitorIndex.getAllStateMonitor(), builder::addBranchResult, builder::addBusResult, builder::addThreeWindingsTransformerResult);
StateMonitor stateMonitor = monitorIndex.getSpecificStateMonitors().get(contingency.getId());
if (stateMonitor != null) {
addMonitorInfos(network, stateMonitor, builder::addBranchResult, builder::addBusResult, builder::addThreeWindingsTransformerResult);
}
}
builder.endContingency();
}
private void applyContingency(String workingVariantId, String postContVariantId, Contingency contingency) {
network.getVariantManager().cloneVariant(workingVariantId, postContVariantId, true);
network.getVariantManager().setWorkingVariant(postContVariantId);
contingency.toModification().apply(network, computationManager);
}
private void addMonitorInfos(Network network, StateMonitor monitor, Consumer<BranchResult> branchResultConsumer,
Consumer<BusResult> busResultsConsumer, Consumer<ThreeWindingsTransformerResult> threeWindingsTransformerResultConsumer) {
monitor.getBranchIds().forEach(branchId -> {
Branch branch = network.getBranch(branchId);
if (branch != null) {
branchResultConsumer.accept(createBranchResult(network.getBranch(branchId)));
}
});
monitor.getVoltageLevelIds().forEach(voltageLevelId -> {
VoltageLevel voltageLevel = network.getVoltageLevel(voltageLevelId);
if (voltageLevel != null) {
voltageLevel.getBusView().getBuses().forEach(bus ->
busResultsConsumer.accept(createBusResult(bus, voltageLevelId)));
}
});
monitor.getThreeWindingsTransformerIds().forEach(threeWindingsTransformerId -> {
ThreeWindingsTransformer twt = network.getThreeWindingsTransformer(threeWindingsTransformerId);
if (twt != null) {
threeWindingsTransformerResultConsumer
.accept(createThreeWindingsTransformerResult(twt));
}
});
}
private BranchResult createBranchResult(Branch branch) {
return new BranchResult(branch.getId(), branch.getTerminal1().getP(), branch.getTerminal1().getQ(), branch.getTerminal1().getI(),
branch.getTerminal2().getP(), branch.getTerminal2().getQ(), branch.getTerminal2().getI(), 0.0);
}
private BusResult createBusResult(Bus bus, String voltageLevelId) {
return new BusResult(voltageLevelId, bus.getId(), bus.getV(), bus.getAngle());
}
private ThreeWindingsTransformerResult createThreeWindingsTransformerResult(ThreeWindingsTransformer threeWindingsTransformer) {
return new ThreeWindingsTransformerResult(threeWindingsTransformer.getId(), threeWindingsTransformer.getLeg1().getTerminal().getP(),
threeWindingsTransformer.getLeg1().getTerminal().getQ(), threeWindingsTransformer.getLeg1().getTerminal().getI(),
threeWindingsTransformer.getLeg2().getTerminal().getP(), threeWindingsTransformer.getLeg2().getTerminal().getQ(), threeWindingsTransformer.getLeg2().getTerminal().getI(),
threeWindingsTransformer.getLeg3().getTerminal().getP(), threeWindingsTransformer.getLeg3().getTerminal().getQ(), threeWindingsTransformer.getLeg3().getTerminal().getI());
}
private void checkPreContingencyViolations(Network network, Consumer<LimitViolation> consumer) {
if (violationDetector != null) {
violationDetector.checkAll(network, consumer);
} else {
LimitViolationDetection.checkAll(network, EnumSet.allOf(LoadingLimitType.class), LimitsComputer.NO_MODIFICATIONS, consumer);
}
}
protected void checkPostContingencyViolations(Contingency contingency, Network network, Consumer<LimitViolation> consumer) {
if (violationDetector != null) {
violationDetector.checkAll(contingency, network, consumer);
} else {
// TODO: Take the contingency into account with full support of LimitReductions
LimitViolationDetection.checkAll(network, EnumSet.allOf(LoadingLimitType.class), LimitsComputer.NO_MODIFICATIONS, consumer);
}
}
}