Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.powsybl.computation.local.LocalComputationManager;
import com.powsybl.security.interceptors.SecurityAnalysisInterceptor;
import com.powsybl.security.monitor.StateMonitor;
import com.powsybl.security.results.SecurityAnalysisResultHandler;
import com.powsybl.security.strategy.OperatorStrategy;

import java.util.ArrayList;
Expand All @@ -34,6 +35,7 @@ public abstract class AbstractSecurityAnalysisRunParameters<T extends AbstractSe
private List<OperatorStrategy> operatorStrategies = new ArrayList<>();
private List<Action> actions = new ArrayList<>();
private List<StateMonitor> monitors = new ArrayList<>();
private List<SecurityAnalysisResultHandler> handlers = new ArrayList<>();
private ReportNode reportNode = ReportNode.NO_OP;

/**
Expand Down Expand Up @@ -131,6 +133,15 @@ public T setActions(List<Action> actions) {
return self();
}

/**
* Sets the list of handlers referenced in {@link SecurityAnalysisResultHandler}
*/
public T setResultHandlers(List<SecurityAnalysisResultHandler> handlers) {
Copy link
Contributor

@vidaldid-rte vidaldid-rte Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a list and not only one ?
If there was only one, we coud mimic the sensi API: no result handler provided means you return an in memory result.
Handler provided means you return void and send the data to the writer.
This view implies that the writer supports also the violation results, not only the monitored objects.

Then if it is useful to have serveral handlers, we would provided the implementation of a SecurityAnalysisResultMultiplexer that would dispatch the calls to a list of handlers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree, if some kind of multiplexing is needed the user has everything available to do it himself anyway. So ok for a single handler API.

Copy link
Member Author

@obrix obrix Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An handler provided would mean we do not fill the result objects then ? NetworkResult in PreContingencyResult / PostContingencyResult would remain empty ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the Sensitivity API. We could use the same idea for security + writer

No writer -> returns a Result. Internally it creates a writer that creates the result object. The provider only knows the writer.
public SensitivityAnalysisResult run(Network network,
String workingVariantId,
List factors,
SensitivityAnalysisRunParameters runParameters)

A writer -> returns void. The user code handles that data as needed, without memory overhead, for example if the result is directly streamed into a database or a python dataframe.
public void run(Network network,
String workingVariantId,
SensitivityFactorReader factorReader,
SensitivityResultWriter resultWriter,
SensitivityAnalysisRunParameters runParameters) {
runAsync(network, workingVariantId, factorReader, resultWriter, runParameters).join();
}

Objects.requireNonNull(handlers, "Handler list should not be null");
this.handlers = handlers;
return self();
}

/**
* Sets the reportNode used for functional logs, see {@link ReportNode}
*/
Expand Down Expand Up @@ -164,5 +175,11 @@ public T addAction(Action action) {
return self();
}

public T addResultHandler(SecurityAnalysisResultHandler handler) {
Objects.requireNonNull(handlers, "Handler should not be null");
handlers.add(handler);
return self();
}

protected abstract T self();
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.powsybl.security.LimitViolationType;
import com.powsybl.security.limitreduction.LimitReduction;
import com.powsybl.security.monitor.StateMonitor;
import com.powsybl.security.results.SecurityAnalysisResultHandler;
import com.powsybl.security.strategy.OperatorStrategy;

import java.util.*;
Expand All @@ -31,6 +32,7 @@ public abstract class AbstractSecurityAnalysisExecutionInput<T extends AbstractS
private final List<Action> actions = new ArrayList<>();
private final List<StateMonitor> monitors = new ArrayList<>();
private final List<LimitReduction> limitReductions = new ArrayList<>();
private final List<SecurityAnalysisResultHandler> resultHandlers = new ArrayList<>();

public Optional<ByteSource> getContingenciesSource() {
return Optional.ofNullable(contingenciesSource);
Expand Down Expand Up @@ -64,6 +66,10 @@ public List<LimitReduction> getLimitReductions() {
return Collections.unmodifiableList(limitReductions);
}

public List<SecurityAnalysisResultHandler> getResultHandlers() {
return Collections.unmodifiableList(resultHandlers);
}

public boolean isWithLogs() {
return withLogs;
}
Expand Down Expand Up @@ -103,6 +109,11 @@ public T addActions(List<Action> actions) {
return self();
}

public T addHandlers(List<SecurityAnalysisResultHandler> handlers) {
this.resultHandlers.addAll(Objects.requireNonNull(handlers));
return self();
}

public T setNetworkVariant(Network network, String variantId) {
networkVariant = new NetworkVariant(network, variantId);
return self();
Expand Down Expand Up @@ -136,6 +147,13 @@ public T setLimitReductions(List<LimitReduction> limitReductions) {
return self();
}

public T setResultHandlers(List<SecurityAnalysisResultHandler> handlers) {
Objects.requireNonNull(handlers);
this.resultHandlers.clear();
this.resultHandlers.addAll(handlers);
return self();
}

public T setWithLogs(boolean withLogs) {
this.withLogs = withLogs;
return self();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/)
* 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.results;

import com.powsybl.contingency.Contingency;
import com.powsybl.security.strategy.OperatorStrategy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;

/**
* @author Bertrand Rix {@literal <bertrand.rix at artelys.com>}
*/
public class InMemoryResultHandler implements SecurityAnalysisResultHandler {

public static class StateResult {

private final List<BranchResult> branchResultList;
private final List<ThreeWindingsTransformerResult> threeWindingsTransformerResultList;
private final List<BusResult> busResultList;

StateResult() {
branchResultList = new ArrayList<>();
threeWindingsTransformerResultList = new ArrayList<>();
busResultList = new ArrayList<>();
}

List<BranchResult> getBranchResultList() {
return branchResultList;
}

List<ThreeWindingsTransformerResult> getThreeWindingsTransformerResultList() {
return threeWindingsTransformerResultList;
}

List<BusResult> getBusResultList() {
return busResultList;
}

void addBranchResult(BranchResult branchResult) {
branchResultList.add(branchResult);
}

void addThreeWindingsTransformerResult(ThreeWindingsTransformerResult threeWindingsTransformerResult) {
threeWindingsTransformerResultList.add(threeWindingsTransformerResult);
}

void addBusResult(BusResult busResult) {
busResultList.add(busResult);
}
}

private final StateResult baseCaseResult;
private final Map<String, StateResult> postContingencyResults;
private final Map<String, StateResult> operatorStrategyResults;

public InMemoryResultHandler() {
baseCaseResult = new StateResult();
postContingencyResults = new HashMap<>();
operatorStrategyResults = new HashMap<>();
}

@Override
public void writeBranchResult(Contingency contingency, OperatorStrategy operatorStrategy, BranchResult branchResult) {
registerResult(contingency, operatorStrategy, branchResult, StateResult::addBranchResult);
}

@Override
public void writeThreeWindingsTransformerResult(Contingency contingency, OperatorStrategy operatorStrategy, ThreeWindingsTransformerResult threeWindingsTransformerResult) {
registerResult(contingency, operatorStrategy, threeWindingsTransformerResult, StateResult::addThreeWindingsTransformerResult);
}

@Override
public void writeBusResult(Contingency contingency, OperatorStrategy operatorStrategy, BusResult busResult) {
registerResult(contingency, operatorStrategy, busResult, StateResult::addBusResult);
}

StateResult getBaseCaseResult() {
return baseCaseResult;
}

Map<String, StateResult> getPostContingencyResults() {
return postContingencyResults;
}

Map<String, StateResult> getOperatorStrategyResults() {
return operatorStrategyResults;
}

<T> void registerResult(Contingency contingency, OperatorStrategy operatorStrategy, T result, BiConsumer<StateResult, T> resultAdder) {
if (contingency == null) {
resultAdder.accept(baseCaseResult, result);
} else if (operatorStrategy == null) {
resultAdder.accept(postContingencyResults.computeIfAbsent(contingency.getId(), k -> new StateResult()), result);
} else {
resultAdder.accept(operatorStrategyResults.computeIfAbsent(operatorStrategy.getId(), k -> new StateResult()), result);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/)
* 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.results;

import com.powsybl.contingency.Contingency;
import com.powsybl.security.strategy.OperatorStrategy;

/**
* @author Bertrand Rix {@literal <bertrand.rix at artelys.com>}
*/
public interface SecurityAnalysisResultHandler {

void writeBranchResult(Contingency contingency, OperatorStrategy operatorStrategy, BranchResult branchResult);

void writeThreeWindingsTransformerResult(Contingency contingency, OperatorStrategy operatorStrategy, ThreeWindingsTransformerResult branchResult);

void writeBusResult(Contingency contingency, OperatorStrategy operatorStrategy, BusResult busResult);

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To align with the suggestion in #3626 (that extends the sensitivity writer for multi-component, better failure diagnosis and interrupt) you could add

// numCC and numCs are used for contingencies that have equipments in multiple components
void writeContingencyStatus contingency(int contingencyIndex, LoadFlowResult.ComponentResult.Status, String statusText, int numCC, int numCs);

void writeSynchronousComponentStatus(int numCC, int numCS, LoadFlowResult.ComponentResult.Status, String statusText,);

// Called at the end of the computation if the computation has not been interrupted
void computationComplete();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To turn this in to a way to get result in streaming, you could also add methods for listening to violations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@obrix yes, here you only streamed only a part of the SecurityAnalysisResult API. We should fully convert it even for violations.