DynaFlowSecurityAnalysisTest.java
/**
* 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.dynaflow;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.computation.local.LocalCommandExecutor;
import com.powsybl.computation.local.LocalComputationConfig;
import com.powsybl.computation.local.LocalComputationManager;
import com.powsybl.contingency.Contingency;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.loadflow.LoadFlowResult;
import com.powsybl.security.SecurityAnalysis;
import com.powsybl.security.SecurityAnalysisReport;
import com.powsybl.security.SecurityAnalysisResult;
import com.powsybl.security.SecurityAnalysisRunParameters;
import com.powsybl.security.json.SecurityAnalysisResultSerializer;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ForkJoinPool;
import static com.powsybl.commons.test.ComparisonUtils.assertTxtEquals;
import static com.powsybl.commons.test.ComparisonUtils.assertXmlEquals;
import static com.powsybl.dynaflow.DynaFlowConstants.CONFIG_FILENAME;
import static com.powsybl.dynaflow.DynaFlowConstants.DYNAFLOW_NAME;
import static com.powsybl.dynaflow.SecurityAnalysisConstants.CONTINGENCIES_FILENAME;
import static com.powsybl.dynawo.commons.DynawoConstants.NETWORK_FILENAME;
import static com.powsybl.dynawo.contingency.ContingencyConstants.AGGREGATED_RESULTS;
import static com.powsybl.dynawo.contingency.ContingencyConstants.CONSTRAINTS_FOLDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* @author Marcos de Miguel {@literal <demiguelm at aia.es>}
*/
class DynaFlowSecurityAnalysisTest extends AbstractSerDeTest {
private static class LocalCommandExecutorMock extends AbstractLocalCommandExecutor {
private final String stdOutFileRef;
private final String inputFile;
private final String contingencyFile;
private final List<String> contingencyIds;
private final List<String> constraints;
private final String aggregatedResult;
public LocalCommandExecutorMock(String stdoutFileRef, String inputFile) {
this(stdoutFileRef, inputFile, null, List.of(), List.of(), null);
}
public LocalCommandExecutorMock(String stdoutFileRef, String inputFile, String contingencyFile, List<String> contingencyIds, List<String> outputConstraints, String aggregatedResult) {
this.stdOutFileRef = Objects.requireNonNull(stdoutFileRef);
this.inputFile = inputFile;
this.contingencyFile = contingencyFile;
this.contingencyIds = contingencyIds;
this.constraints = outputConstraints;
this.aggregatedResult = aggregatedResult;
}
@Override
public int execute(String program, List<String> args, Path outFile, Path errFile, Path workingDir, Map<String, String> env) {
try {
if (args.get(0).equals("--version")) {
copyFile(stdOutFileRef, errFile);
} else {
assertEquals("--network %s --config %s --contingencies %s".formatted(NETWORK_FILENAME, CONFIG_FILENAME, CONTINGENCIES_FILENAME),
String.join(" ", args));
validateInputs(workingDir);
copyOutputs(workingDir);
}
return 0;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void validateInputs(Path workingDir) throws IOException {
if (inputFile != null) {
assertXmlEquals(getClass().getResourceAsStream(inputFile), Files.newInputStream(workingDir.resolve(NETWORK_FILENAME)));
}
if (contingencyFile != null) {
InputStream contingencyIs = Objects.requireNonNull(getClass().getResourceAsStream(contingencyFile));
assertTxtEquals(contingencyIs, Files.newInputStream(workingDir.resolve(CONTINGENCIES_FILENAME)));
}
}
private void copyOutputs(Path workingDir) throws IOException {
copyFile(aggregatedResult, Files.createFile(workingDir.resolve(AGGREGATED_RESULTS)));
Path constraintsFolder = Files.createDirectories(workingDir.resolve(CONSTRAINTS_FOLDER));
for (int i = 0; i < contingencyIds.size(); i++) {
copyFile(constraints.get(i), constraintsFolder.resolve("constraints_" + contingencyIds.get(i) + ".xml"));
}
}
}
@Test
void testDefaultProvider() {
SecurityAnalysis.Runner dynawoSecurityAnalysisRunner = SecurityAnalysis.find();
assertEquals(DYNAFLOW_NAME, dynawoSecurityAnalysisRunner.getName());
}
@Test
void test() throws IOException {
Network network = buildNetwork();
Contingency contingency1 = Contingency.builder("NHV1_NHV2_2_contingency").addBranch("NHV1_NHV2_2").build();
Contingency contingency2 = Contingency.builder("NB_NGEN_contingency").addBranch("NB_NGEN").build();
List<Contingency> contingencies = List.of(contingency1, contingency2);
List<String> contingencyIds = contingencies.stream().map(Contingency::getId).toList();
LocalCommandExecutor commandExecutor = new LocalCommandExecutorMock("/dynawo_version.out",
"/SecurityAnalysis/input.xiidm", "/SecurityAnalysis/contingencies.json",
contingencyIds, List.of("/SecurityAnalysis/constraints1.xml", "/SecurityAnalysis/constraints2.xml"),
"/SecurityAnalysis/aggregatedResults.xml");
SecurityAnalysisRunParameters runParameters = new SecurityAnalysisRunParameters()
.setComputationManager(new LocalComputationManager(new LocalComputationConfig(fileSystem.getPath("/working-dir"), 1), commandExecutor, ForkJoinPool.commonPool()));
SecurityAnalysisReport report = SecurityAnalysis.run(network, contingencies, runParameters);
SecurityAnalysisResult result = report.getResult();
assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getPreContingencyResult().getStatus());
StringWriter writer = new StringWriter();
SecurityAnalysisResultSerializer.write(result, writer);
assertTxtEquals(Objects.requireNonNull(getClass().getResourceAsStream("/SecurityAnalysis/result.json")), writer.toString());
}
@Test
void testCallingBadVersionDynawo() throws IOException {
Network network = Network.create("test", "test");
LocalCommandExecutor commandExecutor = new LocalCommandExecutorMock("/dynawo_bad_version.out", null);
SecurityAnalysisRunParameters runParameters = new SecurityAnalysisRunParameters()
.setComputationManager(new LocalComputationManager(new LocalComputationConfig(fileSystem.getPath("/working-dir"), 1), commandExecutor, ForkJoinPool.commonPool()));
List<Contingency> contingencies = List.of();
assertThrows(PowsyblException.class, () -> SecurityAnalysis.run(network, contingencies, runParameters));
}
@Test
void loadDynaflowParameters() {
DynaFlowSecurityAnalysisProvider provider = new DynaFlowSecurityAnalysisProvider();
Map<String, String> properties = Map.of("contingenciesStartTime", Double.toString(23d));
assertThat(provider.loadSpecificParameters(properties))
.isNotEmpty()
.get()
.isInstanceOf(DynaFlowSecurityAnalysisParameters.class)
.hasFieldOrPropertyWithValue("contingenciesStartTime", 23.);
}
private static Network buildNetwork() {
Network network = EurostagTutorialExample1Factory.create();
network.setCaseDate(ZonedDateTime.parse("2023-03-23T16:40:48.060+01:00"));
// Changing the network for having some pre-contingencies violations
network.getBusBreakerView().getBus("NHV1").setV(380.0);
network.getBusBreakerView().getBus("NHV2").setV(380.0);
Line line = network.getLine("NHV1_NHV2_1");
line.getTerminal1().setP(560.0).setQ(550.0);
line.getTerminal2().setP(560.0).setQ(550.0);
// Adding strong current limits to have some post-contingencies current limit violations
line.newCurrentLimits1().setPermanentLimit(40.0).add();
line.newCurrentLimits2()
.beginTemporaryLimit().setName("10'").setAcceptableDuration(10 * 60).setValue(450.0).endTemporaryLimit()
.setPermanentLimit(1000)
.add();
// Adding a node breaker voltage level to the network
Substation sNb = network.newSubstation().setId("NB_S").add();
VoltageLevel vlNb = sNb.newVoltageLevel().setId("NB_VL").setTopologyKind(TopologyKind.NODE_BREAKER)
.setNominalV(400).setHighVoltageLimit(405).setLowVoltageLimit(395).add();
vlNb.getNodeBreakerView().newBusbarSection().setId("NB_BBS").setNode(0).add();
vlNb.getNodeBreakerView().newBreaker().setId("NB_BG").setNode1(0).setNode2(1).setRetained(true).add();
vlNb.getNodeBreakerView().newDisconnector().setId("NB_DL").setNode1(0).setNode2(2).add();
vlNb.newGenerator().setId("NB_GEN").setNode(1).setTargetP(8).setTargetV(390).setMinP(0).setMaxP(11).setVoltageRegulatorOn(true).add();
Line lineNbBb = network.newLine().setId("NB_NGEN").setVoltageLevel1(vlNb.getId()).setNode1(2).setVoltageLevel2("VLGEN").setBus2("NGEN")
.setR(3.0).setX(33.0).setB1(193E-6).setB2(193E-6).add();
lineNbBb.newCurrentLimits1().setPermanentLimit(41).add();
return network;
}
}