Skip to content

Commit

Permalink
Complete results with limit violations from DynaFlow (#98)
Browse files Browse the repository at this point in the history
* read constraints.xml and fill LimitViolation elements
* Read constraints from constraints folder
* getLimitViolation for all type of equipment/bus
* Clean security analysis unit test
* Remove reading powsybl-like sa output from dynawo
* Force limit violations in integration tests and check results
* Use ViolationLimitFilter
* LimitViolationDetector not used
* Add ConstraintsReader test and consecutive fixes

Signed-off-by: AIA_NT\demiguelm <demiguelm@aia.es>
Co-authored-by: Florian Dupuy <florian.dupuy@rte-france.com>
  • Loading branch information
marcosmc and flo-dup authored Apr 4, 2023
1 parent 6422fd5 commit 2f3264d
Show file tree
Hide file tree
Showing 31 changed files with 1,646 additions and 9,979 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@
import com.powsybl.contingency.contingency.list.ContingencyList;
import com.powsybl.contingency.json.ContingencyJsonModule;
import com.powsybl.dynaflow.json.DynaFlowConfigSerializer;
import com.powsybl.dynaflow.xml.ConstraintsReader;
import com.powsybl.dynawo.commons.DynawoUtil;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.xml.NetworkXml;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.loadflow.LoadFlowResult;
import com.powsybl.security.*;
import com.powsybl.security.interceptors.CurrentLimitViolationInterceptor;
import com.powsybl.security.interceptors.SecurityAnalysisInterceptor;
import com.powsybl.security.json.SecurityAnalysisResultDeserializer;
import com.powsybl.security.results.NetworkResult;
import com.powsybl.security.results.PostContingencyResult;
import com.powsybl.security.results.PreContingencyResult;
Expand All @@ -48,24 +47,18 @@ public class DynaFlowSecurityAnalysis {
private static final String WORKING_DIR_PREFIX = "dynaflow_sa_";
private static final String DYNAFLOW_LAUNCHER_PROGRAM_NAME = "dynaflow-launcher.sh";
private static final String CONTINGENCIES_FILENAME = "contingencies.json";
private static final String SECURITY_ANALYSIS_RESULTS_FILENAME = "securityAnalysisResults.json";
private static final String DYNAFLOW_OUTPUT_FOLDER = "outputs";
private static final String DYNAWO_FINAL_STATE_FOLDER = "finalState";
private static final String DYNAWO_OUTPUT_NETWORK_FILENAME = "outputIIDM.xml";
private static final String DYNAWO_CONSTRAINTS_FOLDER = "constraints";

private final Supplier<DynaFlowConfig> configSupplier;

private final ComputationManager computationManager;
private final Network network;
private final LimitViolationDetector violationDetector;
private final LimitViolationFilter violationFilter;
private final List<SecurityAnalysisInterceptor> interceptors;

public DynaFlowSecurityAnalysis(Network network, LimitViolationDetector detector,
LimitViolationFilter filter, ComputationManager computationManager,
public DynaFlowSecurityAnalysis(Network network, LimitViolationFilter filter, ComputationManager computationManager,
Supplier<DynaFlowConfig> configSupplier) {
this.network = Objects.requireNonNull(network);
this.violationDetector = Objects.requireNonNull(detector);
this.violationFilter = Objects.requireNonNull(filter);
this.interceptors = new ArrayList<>();
this.computationManager = Objects.requireNonNull(computationManager);
Expand Down Expand Up @@ -125,7 +118,6 @@ private static void writeParameters(SecurityAnalysisParameters securityAnalysisP
// TODO(Luma) Take into account also Security Analysis parameters
LoadFlowParameters loadFlowParameters = securityAnalysisParameters.getLoadFlowParameters();
DynaFlowParameters dynaFlowParameters = getParametersExt(loadFlowParameters);
dynaFlowParameters.setChosenOutputs(Collections.singletonList(DynaFlowConstants.OutputTypes.STEADYSTATE.name()));
DynaFlowConfigSerializer.serialize(loadFlowParameters, dynaFlowParameters, Path.of("."), workingDir.resolve(CONFIG_FILENAME));
}

Expand Down Expand Up @@ -165,56 +157,38 @@ public SecurityAnalysisReport after(Path workingDir, ExecutionReport report) thr
super.after(workingDir, report);
network.getVariantManager().setWorkingVariant(workingVariantId);

// If the results have already been prepared, just read them ...
Path saOutput = workingDir.resolve(DYNAFLOW_OUTPUT_FOLDER).resolve(SECURITY_ANALYSIS_RESULTS_FILENAME);
if (Files.exists(saOutput)) {
return new SecurityAnalysisReport(SecurityAnalysisResultDeserializer.read(saOutput));
} else {
// Build the results from the output networks written by DynaFlow
PreContingencyResult preContingencyResult = getPreContingencyResult(network);
List<PostContingencyResult> contingenciesResults = contingencies.stream()
.map(c -> getPostContingencyResult(workingDir, c))
.collect(Collectors.toList());
return new SecurityAnalysisReport(
new SecurityAnalysisResult(preContingencyResult, contingenciesResults, Collections.emptyList())
);
}
// Build the pre-contingency results from the input network
PreContingencyResult preContingencyResult = getPreContingencyResult(network, violationFilter);
Path constraintsDir = workingDir.resolve(DYNAWO_CONSTRAINTS_FOLDER);

// Build the post-contingency results from the constraints files written by dynawo
List<PostContingencyResult> contingenciesResults = contingencies.stream()
.map(c -> getPostContingencyResult(network, violationFilter, constraintsDir, c))
.collect(Collectors.toList());

return new SecurityAnalysisReport(
new SecurityAnalysisResult(preContingencyResult, contingenciesResults, Collections.emptyList())
);
}
});
}

private static PreContingencyResult getPreContingencyResult(Network network) {
private static PreContingencyResult getPreContingencyResult(Network network, LimitViolationFilter violationFilter) {
List<LimitViolation> limitViolations = Security.checkLimits(network);
List<LimitViolation> filteredViolations = violationFilter.apply(limitViolations, network);
NetworkResult networkResult = new NetworkResult(Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
return new PreContingencyResult(LoadFlowResult.ComponentResult.Status.CONVERGED, new LimitViolationsResult(limitViolations), networkResult);
}

private static PostContingencyResult getPostContingencyResult(Path workingDir, Contingency c) {
Path folder = workingDir.resolve(c.getId());
return new PostContingencyResult(c, statusFromOutputNetwork(folder), limitViolationsFromOutputNetwork(folder));
return new PreContingencyResult(LoadFlowResult.ComponentResult.Status.CONVERGED, new LimitViolationsResult(filteredViolations), networkResult);
}

private static PostContingencyComputationStatus statusFromOutputNetwork(Path folder) {
Path outputNetworkPath = outputNetworkPath(folder);
return Files.exists(outputNetworkPath) ? PostContingencyComputationStatus.CONVERGED : PostContingencyComputationStatus.FAILED;
}

private static LimitViolationsResult limitViolationsFromOutputNetwork(Path folder) {
List<LimitViolation> limitViolations;
Path outputNetworkPath = outputNetworkPath(folder);
if (Files.exists(outputNetworkPath)) {
Network outputNetwork = NetworkXml.read(outputNetworkPath);
limitViolations = Security.checkLimits(outputNetwork);
private static PostContingencyResult getPostContingencyResult(Network network, LimitViolationFilter violationFilter,
Path constraintsDir, Contingency c) {
Path constraintsFile = constraintsDir.resolve("constraints_" + c.getId() + ".xml");
if (Files.exists(constraintsFile)) {
List<LimitViolation> limitViolationsRead = ConstraintsReader.read(network, constraintsFile);
List<LimitViolation> limitViolationsFiltered = violationFilter.apply(limitViolationsRead, network);
return new PostContingencyResult(c, PostContingencyComputationStatus.CONVERGED, new LimitViolationsResult(limitViolationsFiltered));
} else {
limitViolations = Collections.emptyList();
return new PostContingencyResult(c, PostContingencyComputationStatus.FAILED, Collections.emptyList());
}
return new LimitViolationsResult(limitViolations);
}

private static Path outputNetworkPath(Path folder) {
return folder
.resolve(DYNAFLOW_OUTPUT_FOLDER)
.resolve(DYNAWO_FINAL_STATE_FOLDER)
.resolve(DYNAWO_OUTPUT_NETWORK_FILENAME);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public CompletableFuture<SecurityAnalysisReport> run(Network network,
List<Action> actions,
List<StateMonitor> monitors,
Reporter reporter) {
if (detector != null) {
LOG.error("LimitViolationDetector is not used in Dynaflow implementation.");
}
if (monitors != null && !monitors.isEmpty()) {
LOG.error("Monitoring is not possible with Dynaflow implementation. There will not be supplementary information about monitored equipment.");
}
Expand All @@ -70,7 +73,7 @@ public CompletableFuture<SecurityAnalysisReport> run(Network network,
if (actions != null && !actions.isEmpty()) {
LOG.error("Actions are not implemented in Dynaflow");
}
DynaFlowSecurityAnalysis securityAnalysis = new DynaFlowSecurityAnalysis(network, detector, filter, computationManager, configSupplier);
DynaFlowSecurityAnalysis securityAnalysis = new DynaFlowSecurityAnalysis(network, filter, computationManager, configSupplier);
interceptors.forEach(securityAnalysis::addInterceptor);
return securityAnalysis.run(workingVariantId, parameters, contingenciesProvider);
}
Expand Down
174 changes: 174 additions & 0 deletions dynaflow/src/main/java/com/powsybl/dynaflow/xml/ConstraintsReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
* Copyright (c) 2023, 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.dynaflow.xml;

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.exceptions.UncheckedXmlStreamException;
import com.powsybl.commons.xml.XmlUtil;
import com.powsybl.iidm.network.*;
import com.powsybl.security.LimitViolation;
import com.powsybl.security.LimitViolationType;
import com.powsybl.security.comparator.LimitViolationComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
* @author Marcos de Miguel <demiguelm at aia.es>
* @author Florian Dupuy <florian.dupuy at rte-france.com>
*/
public final class ConstraintsReader {

private static final Logger LOGGER = LoggerFactory.getLogger(ConstraintsReader.class);

private static final String CONSTRAINTS_ELEMENT_NAME = "constraints";
private static final String CONSTRAINT_ELEMENT_NAME = "constraint";
private static final String MODEL_NAME = "modelName";
private static final String DESCRIPTION = "description";
private static final String TYPE = "type";
private static final String KIND = "kind";
private static final String LIMIT = "limit";
private static final String VALUE = "value";
private static final String SIDE = "side";
private static final String ACCEPTABLE_DURATION = "acceptableDuration";

private static final Supplier<XMLInputFactory> XML_INPUT_FACTORY_SUPPLIER = Suppliers.memoize(XMLInputFactory::newInstance);
public static final String DYN_CALCULATED_BUS_PREFIX = "calculatedBus_";

public static List<LimitViolation> read(Network network, Path xmlFile) {
try (InputStream is = Files.newInputStream(xmlFile)) {
return read(network, is);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

public static List<LimitViolation> read(Network network, InputStream is) {
List<LimitViolation> limitViolations = new ArrayList<>();

try {
XMLStreamReader reader = XML_INPUT_FACTORY_SUPPLIER.get().createXMLStreamReader(is);
int state = reader.next();
while (state == XMLStreamConstants.COMMENT) {
state = reader.next();
}

XmlUtil.readUntilEndElement(CONSTRAINTS_ELEMENT_NAME, reader, () -> {
if (!reader.getLocalName().equals(CONSTRAINT_ELEMENT_NAME)) {
throw new AssertionError();
}
String name = reader.getAttributeValue(null, MODEL_NAME);
reader.getAttributeValue(null, DESCRIPTION); // description: unused
reader.getAttributeValue(null, TYPE); // type: unused
String kind = reader.getAttributeValue(null, KIND);
double limit = XmlUtil.readOptionalDoubleAttribute(reader, LIMIT);
double value = XmlUtil.readOptionalDoubleAttribute(reader, VALUE);
Integer side = XmlUtil.readOptionalIntegerAttribute(reader, SIDE);
Integer acceptableDuration = XmlUtil.readOptionalIntegerAttribute(reader, ACCEPTABLE_DURATION, Integer.MAX_VALUE);

getLimitViolation(network, name, kind, limit, 1f, value, side, acceptableDuration)
.ifPresent(lvRead -> addOrDismiss(lvRead, limitViolations));
});
return limitViolations;
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}

private static void addOrDismiss(LimitViolation lvRead, List<LimitViolation> limitViolations) {
LimitViolationComparator comparator = new LimitViolationComparator();
limitViolations.stream().filter(lv -> comparator.compare(lvRead, lv) == 0).findFirst()
.ifPresentOrElse(
lv -> replaceLimitViolationIfStronger(lvRead, lv, limitViolations),
() -> limitViolations.add(lvRead));
}

private static void replaceLimitViolationIfStronger(LimitViolation newLimitViolation, LimitViolation similarLimitViolation,
List<LimitViolation> limitViolations) {
if (newLimitViolation.getAcceptableDuration() < similarLimitViolation.getAcceptableDuration()) {
limitViolations.remove(similarLimitViolation);
limitViolations.add(newLimitViolation);
}
}

private static Optional<LimitViolation> getLimitViolation(Network network, String name, String kind, double limit,
float limitReduction, double value, Integer side, Integer acceptableDuration) {

return getLimitViolationIdentifiable(network, name)
.map(identifiable -> new LimitViolation(
identifiable.getId(), identifiable.getOptionalName().orElse(null),
toLimitViolationType(kind), kind, acceptableDuration,
limit, limitReduction, value, toBranchSide(side)));
}

private static Optional<Identifiable<?>> getLimitViolationIdentifiable(Network network, String name) {
if (name.matches(DYN_CALCULATED_BUS_PREFIX + ".*_\\d*")) {
// FIXME: the voltage level information should be directly referenced
// The naming corresponds to buses which are calculated in dynawo: https://github.com/dynawo/dynawo/blob/8f1e20e43db7ec4d2e4982deac8307dfa8d0dbec/dynawo/sources/Modeler/DataInterface/PowSyblIIDM/DYNVoltageLevelInterfaceIIDM.cpp#L290
String vlId = name.substring(DYN_CALCULATED_BUS_PREFIX.length(), name.lastIndexOf("_"));
VoltageLevel vl = network.getVoltageLevel(vlId); // Limit violation on buses are identified by their voltage level id
if (vl == null) {
LOGGER.warn("Constraint on dynawo-calculated bus {} with unknown voltage level {}", name, vlId);
}
return Optional.ofNullable(vl);
} else {
Identifiable<?> identifiable = network.getIdentifiable(name);
if (identifiable == null) {
LOGGER.warn("Unknown equipment/bus {} for limit violation in result constraints file", name);
}
if (identifiable instanceof Bus) {
identifiable = ((Bus) identifiable).getVoltageLevel(); // Limit violation on buses are identified by their voltage level id
}
return Optional.ofNullable(identifiable);
}
}

private static Branch.Side toBranchSide(Integer side) {
if (side == null) {
return null;
} else if (side == 1) {
return Branch.Side.ONE;
} else if (side == 2) {
return Branch.Side.TWO;
} else {
return null;
}
}

private static LimitViolationType toLimitViolationType(String kind) {
switch (kind) {
case "UInfUmin":
return LimitViolationType.LOW_VOLTAGE;
case "USupUmax":
return LimitViolationType.HIGH_VOLTAGE;
case "OverloadOpen":
case "OverloadUp":
case "PATL":
return LimitViolationType.CURRENT;
default:
throw new PowsyblException("Unexpect violation type " + kind);
}
}

private ConstraintsReader() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2023, 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.dynaflow;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.powsybl.commons.json.JsonUtil;
import com.powsybl.dynaflow.xml.ConstraintsReader;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory;
import com.powsybl.security.LimitViolation;
import com.powsybl.security.json.SecurityAnalysisJsonModule;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import java.util.Objects;

import static com.powsybl.commons.test.ComparisonUtils.compareTxt;
import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* @author Florian Dupuy <florian.dupuy at rte-france.com>
*/
class ConstraintsReaderTest {

@Test
void test() throws IOException {
Network network = FourSubstationsNodeBreakerFactory.create();
List<LimitViolation> violations = ConstraintsReader.read(network, getClass().getResourceAsStream("/constraints_sample.xml"));
assertEquals(3, violations.size());

Writer stringWriter = new StringWriter();
ObjectMapper mapper = JsonUtil.createObjectMapper().registerModule(new SecurityAnalysisJsonModule());
mapper.writerWithDefaultPrettyPrinter().writeValue(stringWriter, violations);

compareTxt(Objects.requireNonNull(getClass().getResourceAsStream("/limitViolations.json")), stringWriter.toString());
}
}
Loading

0 comments on commit 2f3264d

Please sign in to comment.