diff --git a/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/AcReferenceFlowComputer.java b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/AcReferenceFlowComputer.java new file mode 100644 index 00000000..cb342b82 --- /dev/null +++ b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/AcReferenceFlowComputer.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, 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.flow_decomposition; + +import com.powsybl.iidm.network.Branch; +import com.powsybl.iidm.network.Identifiable; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Guillaume Verger {@literal } + */ +class AcReferenceFlowComputer { + private static ReferenceFlowComputer flowComputer = new ReferenceFlowComputer(); + + Map run(Collection xnecList, LoadFlowRunningService.Result loadFlowServiceAcResult) { + if (loadFlowServiceAcResult.fallbackHasBeenActivated()) { + return xnecList.stream().collect(Collectors.toMap(Identifiable::getId, branch -> Double.NaN)); + } + + return flowComputer.run(xnecList); + } +} diff --git a/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionComputer.java b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionComputer.java index 33f1555a..99d35078 100644 --- a/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionComputer.java +++ b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionComputer.java @@ -9,7 +9,6 @@ import com.powsybl.flow_decomposition.glsk_provider.AutoGlskProvider; import com.powsybl.iidm.network.Branch; import com.powsybl.iidm.network.Country; -import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; import com.powsybl.loadflow.LoadFlow; import com.powsybl.loadflow.LoadFlowParameters; @@ -19,13 +18,13 @@ import java.util.ArrayList; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; /** * @author Sebastien Murgey {@literal } * @author Hugo Schindler {@literal } */ public class FlowDecompositionComputer { + static final String DEFAULT_LOAD_FLOW_PROVIDER = null; static final String DEFAULT_SENSITIVITY_ANALYSIS_PROVIDER = null; private final LoadFlowParameters loadFlowParameters; @@ -34,6 +33,8 @@ public class FlowDecompositionComputer { private final SensitivityAnalysis.Runner sensitivityAnalysisRunner; private final LossesCompensator lossesCompensator; + private final FlowDecompositionObserverList observers; + public FlowDecompositionComputer() { this(new FlowDecompositionParameters()); } @@ -46,6 +47,7 @@ public FlowDecompositionComputer(FlowDecompositionParameters flowDecompositionPa this.loadFlowRunningService = new LoadFlowRunningService(LoadFlow.find(loadFlowProvider)); this.sensitivityAnalysisRunner = SensitivityAnalysis.find(sensitivityAnalysisProvider); this.lossesCompensator = parameters.isLossesCompensationEnabled() ? new LossesCompensator(parameters) : null; + this.observers = new FlowDecompositionObserverList(); } public FlowDecompositionComputer(FlowDecompositionParameters flowDecompositionParameters, @@ -63,30 +65,38 @@ public FlowDecompositionResults run(XnecProvider xnecProvider, Network network) } public FlowDecompositionResults run(XnecProvider xnecProvider, GlskProvider glskProvider, Network network) { - NetworkStateManager networkStateManager = new NetworkStateManager(network, xnecProvider); - - LoadFlowRunningService.Result loadFlowServiceAcResult = runAcLoadFlow(network); - - Map> glsks = glskProvider.getGlsk(network); - Map netPositions = getZonesNetPosition(network); - - FlowDecompositionResults flowDecompositionResults = new FlowDecompositionResults(network); - decomposeFlowForNState(network, - flowDecompositionResults, - xnecProvider.getNetworkElements(network), - netPositions, - glsks, - loadFlowServiceAcResult); - xnecProvider.getNetworkElementsPerContingency(network) - .forEach((contingencyId, xnecList) -> decomposeFlowForContingencyState(network, - flowDecompositionResults, - networkStateManager, - contingencyId, - xnecList, - netPositions, - glsks)); - networkStateManager.deleteAllContingencyVariants(); - return flowDecompositionResults; + observers.runStart(); + try { + NetworkStateManager networkStateManager = new NetworkStateManager(network, xnecProvider); + + LoadFlowRunningService.Result loadFlowServiceAcResult = runAcLoadFlow(network); + + Map> glsks = glskProvider.getGlsk(network); + observers.computedGlsk(glsks); + + Map netPositions = getZonesNetPosition(network); + observers.computedNetPositions(netPositions); + + FlowDecompositionResults flowDecompositionResults = new FlowDecompositionResults(network); + decomposeFlowForNState(network, + flowDecompositionResults, + xnecProvider.getNetworkElements(network), + netPositions, + glsks, + loadFlowServiceAcResult); + xnecProvider.getNetworkElementsPerContingency(network) + .forEach((contingencyId, xnecList) -> decomposeFlowForContingencyState(network, + flowDecompositionResults, + networkStateManager, + contingencyId, + xnecList, + netPositions, + glsks)); + networkStateManager.deleteAllContingencyVariants(); + return flowDecompositionResults; + } finally { + observers.runDone(); + } } private void decomposeFlowForNState(Network network, @@ -96,6 +106,7 @@ private void decomposeFlowForNState(Network network, Map> glsks, LoadFlowRunningService.Result loadFlowServiceAcResult) { if (!xnecList.isEmpty()) { + observers.computingBaseCase(); FlowDecompositionResults.PerStateBuilder flowDecompositionResultsBuilder = flowDecompositionResults.getBuilder(xnecList); decomposeFlowForState(network, xnecList, flowDecompositionResultsBuilder, netPositions, glsks, loadFlowServiceAcResult); } @@ -109,6 +120,7 @@ private void decomposeFlowForContingencyState(Network network, Map netPositions, Map> glsks) { if (!xnecList.isEmpty()) { + observers.computingContingency(contingencyId); networkStateManager.setNetworkVariant(contingencyId); LoadFlowRunningService.Result loadFlowServiceAcResult = runAcLoadFlow(network); FlowDecompositionResults.PerStateBuilder flowDecompositionResultsBuilder = flowDecompositionResults.getBuilder(contingencyId, xnecList); @@ -124,20 +136,27 @@ private void decomposeFlowForState(Network network, LoadFlowRunningService.Result loadFlowServiceAcResult) { saveAcReferenceFlow(flowDecompositionResultsBuilder, xnecList, loadFlowServiceAcResult); compensateLosses(network); + observers.computedAcFlows(network, loadFlowServiceAcResult); // None NetworkMatrixIndexes networkMatrixIndexes = new NetworkMatrixIndexes(network, new ArrayList<>(xnecList)); + observers.computedAcNodalInjections(networkMatrixIndexes, loadFlowServiceAcResult.fallbackHasBeenActivated()); runDcLoadFlow(network); + observers.computedDcFlows(network); + observers.computedDcNodalInjections(networkMatrixIndexes); SparseMatrixWithIndexesTriplet nodalInjectionsMatrix = getNodalInjectionsMatrix(network, netPositions, networkMatrixIndexes, glsks); saveDcReferenceFlow(flowDecompositionResultsBuilder, xnecList); + observers.computedNodalInjectionsMatrix(nodalInjectionsMatrix); // DC Sensi SensitivityAnalyser sensitivityAnalyser = getSensitivityAnalyser(network, networkMatrixIndexes); SparseMatrixWithIndexesTriplet ptdfMatrix = getPtdfMatrix(networkMatrixIndexes, sensitivityAnalyser); + observers.computedPtdfMatrix(ptdfMatrix); SparseMatrixWithIndexesTriplet psdfMatrix = getPsdfMatrix(networkMatrixIndexes, sensitivityAnalyser); + observers.computedPsdfMatrix(psdfMatrix); // None computeAllocatedAndLoopFlows(flowDecompositionResultsBuilder, nodalInjectionsMatrix, ptdfMatrix); @@ -146,17 +165,20 @@ private void decomposeFlowForState(Network network, flowDecompositionResultsBuilder.build(parameters.isRescaleEnabled()); } + public void addObserver(FlowDecompositionObserver observer) { + this.observers.addObserver(observer); + } + + public void removeObserver(FlowDecompositionObserver observer) { + this.observers.removeObserver(observer); + } + private LoadFlowRunningService.Result runAcLoadFlow(Network network) { return loadFlowRunningService.runAcLoadflow(network, loadFlowParameters, parameters.isDcFallbackEnabledAfterAcDivergence()); } private void saveAcReferenceFlow(FlowDecompositionResults.PerStateBuilder flowDecompositionResultBuilder, Set xnecList, LoadFlowRunningService.Result loadFlowServiceAcResult) { - Map acReferenceFlows; - if (loadFlowServiceAcResult.fallbackHasBeenActivated()) { - acReferenceFlows = xnecList.stream().collect(Collectors.toMap(Identifiable::getId, branch -> Double.NaN)); - } else { - acReferenceFlows = getXnecReferenceFlows(xnecList); - } + Map acReferenceFlows = new AcReferenceFlowComputer().run(xnecList, loadFlowServiceAcResult); flowDecompositionResultBuilder.saveAcReferenceFlow(acReferenceFlows); } @@ -165,9 +187,8 @@ private Map getZonesNetPosition(Network network) { return netPositionComputer.run(network); } - private Map getXnecReferenceFlows(Set xnecList) { - ReferenceFlowComputer referenceFlowComputer = new ReferenceFlowComputer(); - return referenceFlowComputer.run(xnecList); + private Map getBranchReferenceFlows(Set branches) { + return new ReferenceFlowComputer().run(branches); } private void compensateLosses(Network network) { @@ -189,7 +210,7 @@ private SparseMatrixWithIndexesTriplet getNodalInjectionsMatrix(Network network, } private void saveDcReferenceFlow(FlowDecompositionResults.PerStateBuilder flowDecompositionResultBuilder, Set xnecList) { - flowDecompositionResultBuilder.saveDcReferenceFlow(getXnecReferenceFlows(xnecList)); + flowDecompositionResultBuilder.saveDcReferenceFlow(getBranchReferenceFlows(xnecList)); } private SensitivityAnalyser getSensitivityAnalyser(Network network, NetworkMatrixIndexes networkMatrixIndexes) { diff --git a/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionObserver.java b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionObserver.java new file mode 100644 index 00000000..5d79f031 --- /dev/null +++ b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionObserver.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024, 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.flow_decomposition; + +import com.powsybl.iidm.network.Country; + +import java.util.Map; + +/** + * @author Guillaume Verger {@literal } + */ +public interface FlowDecompositionObserver { + + /** + * Called when the computation starts + */ + void runStart(); + + /** + * Called when the computation is done + */ + void runDone(); + + /** + * Called when the base case starts to be computed + */ + void computingBaseCase(); + + /** + * Called when a contingency computation starts + * + * @param contingencyId The current contingency id + */ + void computingContingency(String contingencyId); + + /** + * Called when Glsk are computed thanks to the GlskProvider (during base case computation) + * + * @param glsks the Glsks per country per generator + */ + void computedGlsk(Map> glsks); + + /** + * Called when net positions are computed (for base case computation) + * + * @param netPositions the net positions per country + */ + void computedNetPositions(Map netPositions); + + /** + * Called when the nodal injection matrix is computed (for base case or contingency) + * + * @param nodalInjections the matrix of nodal injections indexed by(node, flow) + */ + void computedNodalInjectionsMatrix(Map> nodalInjections); + + /** + * Called when the PTDF matrix is computed (for base case or contingency) + * + * @param pdtfMatrix the matrix of ptdf indexed by (line, node) + */ + void computedPtdfMatrix(Map> pdtfMatrix); + + /** + * Called when the PSDF matrix is computed (for base case or contingency) + * + * @param psdfMatrix the matrix of psdf indexed by (line, node) + */ + void computedPsdfMatrix(Map> psdfMatrix); + + /** + * Called when the AC nodal injections matrix is computed (for base case or contingency) + * + * @param positions the positions after AC loadflow + * @param fallbackHasBeenActivated true if AC loadflow didn't converge + */ + void computedAcNodalInjections(Map positions, boolean fallbackHasBeenActivated); + + /** + * Called when the DC nodal injections matrix is computed (for base case or contingency) + * + * @param positions the positions after DC loadflow + */ + void computedDcNodalInjections(Map positions); + + /** + * Called when the AC loadflow is computed (for base case or contingency) + * + * @param flows the flows for all branches + */ + void computedAcFlows(Map flows); + + /** + * Called when the DC loadflow is computed (for base case or contingency) + * + * @param flows the flows for all branches + */ + void computedDcFlows(Map flows); +} diff --git a/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionObserverList.java b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionObserverList.java new file mode 100644 index 00000000..4f55bd14 --- /dev/null +++ b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/FlowDecompositionObserverList.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024, 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.flow_decomposition; + +import com.powsybl.flow_decomposition.LoadFlowRunningService.Result; +import com.powsybl.iidm.network.Country; +import com.powsybl.iidm.network.Network; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author Guillaume Verger {@literal } + */ +public class FlowDecompositionObserverList { + private final List observers; + + public FlowDecompositionObserverList() { + this.observers = new ArrayList<>(); + } + + public void addObserver(FlowDecompositionObserver o) { + this.observers.add(o); + } + + public void removeObserver(FlowDecompositionObserver o) { + this.observers.remove(o); + } + + public void runStart() { + for (FlowDecompositionObserver o : observers) { + o.runStart(); + } + } + + public void runDone() { + for (FlowDecompositionObserver o : observers) { + o.runDone(); + } + } + + public void computingBaseCase() { + for (FlowDecompositionObserver o : observers) { + o.computingBaseCase(); + } + } + + public void computingContingency(String contingencyId) { + for (FlowDecompositionObserver o : observers) { + o.computingContingency(contingencyId); + } + } + + public void computedGlsk(Map> glsks) { + for (FlowDecompositionObserver o : observers) { + o.computedGlsk(glsks); + } + } + + public void computedNetPositions(Map netPositions) { + for (FlowDecompositionObserver o : observers) { + o.computedNetPositions(netPositions); + } + } + + public void computedNodalInjectionsMatrix(SparseMatrixWithIndexesTriplet matrix) { + sendMatrix(FlowDecompositionObserver::computedNodalInjectionsMatrix, matrix); + } + + public void computedPtdfMatrix(SparseMatrixWithIndexesTriplet matrix) { + sendMatrix(FlowDecompositionObserver::computedPtdfMatrix, matrix); + } + + public void computedPsdfMatrix(SparseMatrixWithIndexesTriplet matrix) { + sendMatrix(FlowDecompositionObserver::computedPsdfMatrix, matrix); + } + + public void computedAcFlows(Network network, Result loadFlowServiceAcResult) { + if (observers.isEmpty()) { + return; + } + + Map acFlows = + new AcReferenceFlowComputer().run(network.getBranchStream().toList(), loadFlowServiceAcResult); + + for (FlowDecompositionObserver o : observers) { + o.computedAcFlows(acFlows); + } + } + + public void computedDcFlows(Network network) { + if (observers.isEmpty()) { + return; + } + + Map dcFlows = new ReferenceFlowComputer().run(network.getBranchStream().toList()); + + for (FlowDecompositionObserver o : observers) { + o.computedDcFlows(dcFlows); + } + } + + public void computedAcNodalInjections(NetworkMatrixIndexes networkMatrixIndexes, boolean fallbackHasBeenActivated) { + if (observers.isEmpty()) { + return; + } + + Map results = new ReferenceNodalInjectionComputer().run(networkMatrixIndexes.getNodeList()); + + for (FlowDecompositionObserver o : observers) { + o.computedAcNodalInjections(results, fallbackHasBeenActivated); + } + } + + public void computedDcNodalInjections(NetworkMatrixIndexes networkMatrixIndexes) { + if (observers.isEmpty()) { + return; + } + + Map results = new ReferenceNodalInjectionComputer().run(networkMatrixIndexes.getNodeList()); + + for (FlowDecompositionObserver o : observers) { + o.computedDcNodalInjections(results); + } + } + + private void sendMatrix(MatrixNotification notification, SparseMatrixWithIndexesTriplet matrix) { + if (observers.isEmpty()) { + return; + } + + Map> mapMatrix = matrix.toMap(); + for (FlowDecompositionObserver o : observers) { + notification.sendMatrix(o, mapMatrix); + } + } + + @FunctionalInterface + private interface MatrixNotification { + public void sendMatrix(FlowDecompositionObserver o, Map> matrix); + } +} diff --git a/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/ReferenceFlowComputer.java b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/ReferenceFlowComputer.java index d1470fbe..68fb4061 100644 --- a/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/ReferenceFlowComputer.java +++ b/flow-decomposition/src/main/java/com/powsybl/flow_decomposition/ReferenceFlowComputer.java @@ -9,8 +9,8 @@ import com.powsybl.iidm.network.Branch; import com.powsybl.iidm.network.Identifiable; +import java.util.Collection; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; /** @@ -18,7 +18,7 @@ * @author Hugo Schindler {@literal } */ class ReferenceFlowComputer { - Map run(Set xnecList) { + Map run(Collection xnecList) { return xnecList.stream() .collect(Collectors.toMap( Identifiable::getId, diff --git a/flow-decomposition/src/test/java/com/powsybl/flow_decomposition/FlowDecompositionObserverTest.java b/flow-decomposition/src/test/java/com/powsybl/flow_decomposition/FlowDecompositionObserverTest.java new file mode 100644 index 00000000..327c9660 --- /dev/null +++ b/flow-decomposition/src/test/java/com/powsybl/flow_decomposition/FlowDecompositionObserverTest.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2024, 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.flow_decomposition; + +import com.powsybl.flow_decomposition.xnec_provider.XnecProviderByIds; +import com.powsybl.iidm.network.Country; +import com.powsybl.iidm.network.Network; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; + +/** + * @author Guillaume Verger {@literal } + */ +class FlowDecompositionObserverTest { + private enum Event { + RUN_START, + RUN_DONE, + COMPUTING_BASE_CASE, + COMPUTING_CONTINGENCY, + COMPUTED_GLSK, + COMPUTED_NET_POSITIONS, + COMPUTED_NODAL_INJECTIONS_MATRIX, + COMPUTED_PTDF_MATRIX, + COMPUTED_PSDF_MATRIX, + COMPUTED_AC_NODAL_INJECTIONS, + COMPUTED_DC_NODAL_INJECTIONS, + COMPUTED_AC_FLOWS, + COMPUTED_DC_FLOWS + } + + private static final String BASE_CASE = "base-case"; + + /** + * ObserverReport gathers all observed events from the flow decomposition. It keeps the events occuring, and the + * matrices + */ + private final class ObserverReport implements FlowDecompositionObserver { + + private List events = new LinkedList<>(); + private String currentContingency = null; + private ContingencyValue> eventsPerContingency = new ContingencyValue<>(); + private Map> glsks; + private Map netPositions; + private ContingencyValue>> nodalInjections = new ContingencyValue<>(); + private ContingencyValue>> ptdfs = new ContingencyValue<>(); + private ContingencyValue>> psdfs = new ContingencyValue<>(); + private ContingencyValue> acNodalInjections = new ContingencyValue<>(); + private ContingencyValue> dcNodalInjections = new ContingencyValue<>(); + private ContingencyValue> acFlows = new ContingencyValue<>(); + private ContingencyValue> dcFlows = new ContingencyValue<>(); + + public List allEvents() { + return events; + } + + public List eventsForBaseCase() { + return eventsPerContingency.forBaseCase(); + } + + public List eventsForContingency(String contingencyId) { + return eventsPerContingency.forContingency(contingencyId); + } + + @Override + public void runStart() { + addEvent(Event.RUN_START); + } + + @Override + public void runDone() { + addEvent(Event.RUN_DONE); + } + + @Override + public void computingBaseCase() { + currentContingency = BASE_CASE; + addEvent(Event.COMPUTING_BASE_CASE); + } + + @Override + public void computingContingency(String contingencyId) { + currentContingency = contingencyId; + addEvent(Event.COMPUTING_CONTINGENCY); + } + + @Override + public void computedGlsk(Map> glsks) { + addEvent(Event.COMPUTED_GLSK); + this.glsks = glsks; + } + + @Override + public void computedNetPositions(Map netPositions) { + addEvent(Event.COMPUTED_NET_POSITIONS); + this.netPositions = netPositions; + } + + @Override + public void computedNodalInjectionsMatrix(Map> nodalInjections) { + addEvent(Event.COMPUTED_NODAL_INJECTIONS_MATRIX); + this.nodalInjections.put(currentContingency, nodalInjections); + } + + @Override + public void computedPtdfMatrix(Map> pdtfMatrix) { + addEvent(Event.COMPUTED_PTDF_MATRIX); + this.ptdfs.put(currentContingency, pdtfMatrix); + } + + @Override + public void computedPsdfMatrix(Map> psdfMatrix) { + addEvent(Event.COMPUTED_PSDF_MATRIX); + this.psdfs.put(currentContingency, psdfMatrix); + } + + @Override + public void computedAcNodalInjections(Map positions, boolean fallbackHasBeenActivated) { + addEvent(Event.COMPUTED_AC_NODAL_INJECTIONS); + this.acNodalInjections.put(currentContingency, positions); + } + + @Override + public void computedDcNodalInjections(Map positions) { + addEvent(Event.COMPUTED_DC_NODAL_INJECTIONS); + this.dcNodalInjections.put(currentContingency, positions); + } + + @Override + public void computedAcFlows(Map flows) { + addEvent(Event.COMPUTED_AC_FLOWS); + this.acFlows.put(currentContingency, flows); + } + + @Override + public void computedDcFlows(Map flows) { + addEvent(Event.COMPUTED_DC_FLOWS); + this.dcFlows.put(currentContingency, flows); + } + + private void addEvent(Event e) { + if (currentContingency != null) { + eventsPerContingency.putIfAbsent(currentContingency, new LinkedList<>()); + eventsPerContingency.forContingency(currentContingency).add(e); + } + events.add(e); + } + } + + @Test + void testNStateN1AndN2PostContingencyState() { + String networkFileName = "19700101_0000_FO4_UX1.uct"; + String branchId = "DB000011 DF000011 1"; + String contingencyElementId1 = "FB000011 FD000011 1"; + String contingencyElementId2 = "FB000021 FD000021 1"; + String contingencyId1 = "DD000011 DF000011 1"; + String contingencyId2 = "FB000011 FD000011 1_FB000021 FD000021 1"; + + Network network = TestUtils.importNetwork(networkFileName); + Map> contingencies = Map.ofEntries( + Map.entry(contingencyId1, Set.of(contingencyId1)), + Map.entry(contingencyId2, Set.of(contingencyElementId1, contingencyElementId2))); + XnecProvider xnecProvider = XnecProviderByIds.builder() + .addContingencies(contingencies) + .addNetworkElementsAfterContingencies( + Set.of(branchId), + Set.of(contingencyId1, contingencyId2)) + .addNetworkElementsOnBasecase(Set.of(branchId)) + .build(); + var flowDecompositionParameters = FlowDecompositionParameters.load(); + FlowDecompositionComputer flowComputer = new FlowDecompositionComputer(flowDecompositionParameters); + var report = new ObserverReport(); + + flowComputer.addObserver(report); + + flowComputer.run(xnecProvider, network); + + assertEventsFired(report.allEvents(), Event.COMPUTED_GLSK, Event.COMPUTED_NET_POSITIONS); + + assertEventsFired( + report.eventsForBaseCase(), + Event.COMPUTED_AC_FLOWS, + Event.COMPUTED_AC_NODAL_INJECTIONS, + Event.COMPUTED_DC_FLOWS, + Event.COMPUTED_DC_NODAL_INJECTIONS, + Event.COMPUTED_NODAL_INJECTIONS_MATRIX, + Event.COMPUTED_PTDF_MATRIX, + Event.COMPUTED_PSDF_MATRIX); + + for (var contingencyId : List.of(contingencyId1, contingencyId2)) { + assertEventsFired( + report.eventsForContingency(contingencyId), + Event.COMPUTED_AC_FLOWS, + Event.COMPUTED_AC_NODAL_INJECTIONS, + Event.COMPUTED_DC_FLOWS, + Event.COMPUTED_DC_NODAL_INJECTIONS, + Event.COMPUTED_NODAL_INJECTIONS_MATRIX, + Event.COMPUTED_PTDF_MATRIX, + Event.COMPUTED_PSDF_MATRIX); + } + + // Checking GLSK + assertEquals(Set.of(Country.BE, Country.DE, Country.FR), report.glsks.keySet()); + assertEquals(Set.of("DB000011_generator", "DF000011_generator"), report.glsks.get(Country.DE).keySet()); + + // Checking net positions + assertEquals(Set.of(Country.BE, Country.DE, Country.FR), report.netPositions.keySet()); + + var xnecNodes = Set.of( + "BB000021_load", + "BF000011_generator", + "BF000021_load", + "DB000011_generator", + "DD000011_load", + "DF000011_generator", + "FB000021_generator", + "FB000022_load", + "FD000011_load", + "FF000011_generator", + "XES00011 FD000011 1", + "XNL00011 BB000011 1"); + + // Checking nodal injections + for (var contingencyId : List.of(BASE_CASE, contingencyId1, contingencyId2)) { + assertEquals(xnecNodes, report.nodalInjections.forContingency(contingencyId).keySet()); + assertEquals( + Set.of("Allocated Flow", "Loop Flow from BE"), + report.nodalInjections.forContingency(contingencyId).get("BB000021_load").keySet()); + } + + // Checking PTDFs + for (var contingencyId : List.of(BASE_CASE, contingencyId1, contingencyId2)) { + var branches = Set.of(branchId); + assertEquals(branches, report.ptdfs.forContingency(contingencyId).keySet()); + assertEquals(xnecNodes, report.ptdfs.forContingency(contingencyId).get(branchId).keySet()); + } + + // Checking PSDFs + for (var contingencyId : List.of(BASE_CASE, contingencyId1, contingencyId2)) { + var branches = Set.of(branchId); + var pstNodes = Set.of("BF000011 BF000012 1"); + assertEquals(branches, report.psdfs.forContingency(contingencyId).keySet()); + assertEquals( + pstNodes, + report.psdfs.forContingency(contingencyId).get(branchId).keySet(), + "contingency = " + contingencyId); + } + + // Checking AC nodal injections + for (var contingencyId : List.of(BASE_CASE, contingencyId1, contingencyId2)) { + assertEquals(xnecNodes, report.acNodalInjections.forContingency(contingencyId).keySet()); + } + + // Checking DC nodal injections + for (var contingencyId : List.of(BASE_CASE, contingencyId1, contingencyId2)) { + assertEquals(xnecNodes, report.dcNodalInjections.forContingency(contingencyId).keySet()); + } + + var allBranches = Set.of( + "BB000011 BB000021 1", + "BB000011 BD000011 1", + "BB000011 BF000012 1", + "BB000021 BD000021 1", + "BB000021 BF000021 1", + "BD000011 BD000021 1", + "BD000011 BF000011 1", + "BD000021 BF000021 1", + "BF000011 BF000012 1", + "BF000011 BF000021 1", + "DB000011 DD000011 1", + "DB000011 DF000011 1", + "DD000011 DF000011 1", + "FB000011 FB000022 1", + "FB000011 FD000011 1", + "FB000011 FF000011 1", + "FB000021 FD000021 1", + "FD000011 FD000021 1", + "FD000011 FF000011 1", + "FD000011 FF000011 2", + "XBD00011 BD000011 1 + XBD00011 DB000011 1", + "XBD00012 BD000011 1 + XBD00012 DB000011 1", + "XBF00011 BF000011 1 + XBF00011 FB000011 1", + "XBF00021 BF000021 1 + XBF00021 FB000021 1", + "XBF00022 BF000021 1 + XBF00022 FB000022 1", + "XDF00011 DF000011 1 + XDF00011 FD000011 1"); + + // Checking AC flows + for (var contingencyId : List.of(BASE_CASE, contingencyId1, contingencyId2)) { + assertEquals(allBranches, report.acFlows.forContingency(contingencyId).keySet()); + } + + // Checking DC flows + for (var contingencyId : List.of(BASE_CASE, contingencyId1, contingencyId2)) { + assertEquals(allBranches, report.dcFlows.forContingency(contingencyId).keySet()); + } + } + + @Test + void testRemoveObserver() { + String networkFileName = "19700101_0000_FO4_UX1.uct"; + String branchId = "DB000011 DF000011 1"; + + Network network = TestUtils.importNetwork(networkFileName); + XnecProvider xnecProvider = XnecProviderByIds.builder() + .addNetworkElementsOnBasecase(Set.of(branchId)) + .build(); + var flowDecompositionParameters = FlowDecompositionParameters.load(); + FlowDecompositionComputer flowComputer = new FlowDecompositionComputer(flowDecompositionParameters); + var reportInserted = new ObserverReport(); + flowComputer.addObserver(reportInserted); + + var reportRemoved = new ObserverReport(); + flowComputer.addObserver(reportRemoved); + + flowComputer.removeObserver(reportRemoved); + + flowComputer.run(xnecProvider, network); + + assertFalse(reportInserted.allEvents().isEmpty()); + assertTrue(reportRemoved.allEvents().isEmpty()); + } + + private void assertEventsFired(Collection firedEvents, Event... expectedEvents) { + var missing = new HashSet(); + Collections.addAll(missing, expectedEvents); + missing.removeAll(firedEvents); + assertTrue(missing.isEmpty(), () -> "Missing events: " + missing.toString()); + } + + private static final class ContingencyValue { + private Map values = new HashMap<>(); + + public void put(String contingencyId, T value) { + values.put(contingencyId, value); + } + + public void putIfAbsent(String contingencyId, T value) { + values.putIfAbsent(contingencyId, value); + } + + public T forContingency(String contingencyId) { + return values.get(contingencyId); + } + + public T forBaseCase() { + return values.get(BASE_CASE); + } + } +}