Skip to content

Commit

Permalink
PostContingencyResult status fix (#405)
Browse files Browse the repository at this point in the history
Parse aggregatedResults.xml and use it for post contingency results

Signed-off-by: lisrte <[email protected]>
  • Loading branch information
Lisrte authored Dec 12, 2024
1 parent de9ea15 commit 64cffab
Show file tree
Hide file tree
Showing 20 changed files with 402 additions and 75 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright (c) 2024, 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.dynawo.commons;

import com.powsybl.commons.exceptions.UncheckedXmlStreamException;

import javax.xml.XMLConstants;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

/**
* @author Laurent Issertial {@literal <laurent.issertial at rte-france.com>}
*/
public abstract class AbstractXmlParser<T> {

public List<T> parse(Path path) {
List<T> series = new ArrayList<>();
parse(path, series::add);
return series;
}

public void parse(Path path, Consumer<T> consumer) {
Objects.requireNonNull(path);
if (!Files.exists(path)) {
return;
}
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
parse(reader, consumer);
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}

public List<T> parse(Reader reader) throws XMLStreamException {
List<T> series = new ArrayList<>();
parse(reader, series::add);
return series;
}

public void parse(Reader reader, Consumer<T> consumer) throws XMLStreamException {
XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
XMLStreamReader xmlReader = null;
try {
xmlReader = factory.createXMLStreamReader(reader);
read(xmlReader, consumer);
} finally {
if (xmlReader != null) {
xmlReader.close();
}
}
}

protected abstract void read(XMLStreamReader reader, Consumer<T> consumer) throws XMLStreamException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ private DynawoConstants() {

public static final String OUTPUT_IIDM_FILENAME = "outputIIDM.xml";

public static final String AGGREGATED_RESULTS = "aggregatedResults.xml";

public static final String FINAL_STATE_FOLDER = "finalState";

public static final String TIMELINE_FOLDER = "timeLine";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,68 +9,25 @@

import com.powsybl.commons.exceptions.UncheckedXmlStreamException;
import com.powsybl.commons.xml.XmlUtil;
import com.powsybl.dynawo.commons.AbstractXmlParser;

import javax.xml.XMLConstants;
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.Reader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

/**
* @author Laurent Issertial {@literal <laurent.issertial at rte-france.com>}
* @author Marcos de Miguel {@literal <demiguelm at aia.es>}
*/
public final class XmlTimeLineParser implements TimeLineParser {
public final class XmlTimeLineParser extends AbstractXmlParser<TimelineEntry> implements TimeLineParser {

private static final String TIME = "time";
private static final String MODEL_NAME = "modelName";
private static final String MESSAGE = "message";

public List<TimelineEntry> parse(Path timeLineFile) {
Objects.requireNonNull(timeLineFile);
if (!Files.exists(timeLineFile)) {
return Collections.emptyList();
}

try (Reader reader = Files.newBufferedReader(timeLineFile, StandardCharsets.UTF_8)) {
return parse(reader);
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}

public static List<TimelineEntry> parse(Reader reader) throws XMLStreamException {

List<TimelineEntry> timeLineSeries;
XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
XMLStreamReader xmlReader = null;
try {
xmlReader = factory.createXMLStreamReader(reader);
timeLineSeries = read(xmlReader);
} finally {
if (xmlReader != null) {
xmlReader.close();
}
}
return timeLineSeries;
}

private static List<TimelineEntry> read(XMLStreamReader xmlReader) throws XMLStreamException {
List<TimelineEntry> timeline = new ArrayList<>();
@Override
protected void read(XMLStreamReader xmlReader, Consumer<TimelineEntry> consumer) throws XMLStreamException {
int state = xmlReader.next();
while (state == XMLStreamConstants.COMMENT) {
state = xmlReader.next();
Expand All @@ -83,12 +40,11 @@ private static List<TimelineEntry> read(XMLStreamReader xmlReader) throws XMLStr
String message = xmlReader.getAttributeValue(null, MESSAGE);
XmlUtil.readEndElementOrThrow(xmlReader);
TimeLineUtil.createEvent(time, modelName, message)
.ifPresent(timeline::add);
.ifPresent(consumer);
}
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
});
return timeline;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class XmlTimeLineParserTest {
void test() throws XMLStreamException {

InputStreamReader xml = new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("/timeline.xml")));
List<TimelineEntry> timeline = XmlTimeLineParser.parse(xml);
List<TimelineEntry> timeline = new XmlTimeLineParser().parse(xml);

assertEquals(5, timeline.size());
assertEquals("PMIN : activation", timeline.get(0).message());
Expand All @@ -54,7 +54,7 @@ void parseFromPath() throws URISyntaxException {
@Test
void testInconsistentFile() throws XMLStreamException {
InputStreamReader xml = new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("/wrongTimeline.xml")));
List<TimelineEntry> timeline = XmlTimeLineParser.parse(xml);
List<TimelineEntry> timeline = new XmlTimeLineParser().parse(xml);
assertEquals(4, timeline.size());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.powsybl.contingency.contingency.list.ContingencyList;
import com.powsybl.contingency.json.ContingencyJsonModule;
import com.powsybl.dynaflow.json.DynaFlowConfigSerializer;
import com.powsybl.dynaflow.results.ContingencyResultsUtils;
import com.powsybl.dynawo.commons.DynawoUtil;
import com.powsybl.iidm.network.Network;
import com.powsybl.loadflow.LoadFlowParameters;
Expand All @@ -37,7 +38,6 @@

import static com.powsybl.dynaflow.DynaFlowConstants.CONFIG_FILENAME;
import static com.powsybl.dynaflow.SecurityAnalysisConstants.CONTINGENCIES_FILENAME;
import static com.powsybl.dynaflow.SecurityAnalysisConstants.CONSTRAINTS_FOLDER;
import static com.powsybl.dynawo.commons.DynawoConstants.NETWORK_FILENAME;
import static com.powsybl.dynawo.commons.DynawoConstants.TIMELINE_FOLDER;
import static com.powsybl.dynawo.commons.DynawoUtil.getCommandExecutions;
Expand Down Expand Up @@ -88,7 +88,7 @@ public SecurityAnalysisReport after(Path workingDir, ExecutionReport report) thr
return new SecurityAnalysisReport(
new SecurityAnalysisResult(
ContingencyResultsUtils.getPreContingencyResult(network, violationFilter),
ContingencyResultsUtils.getPostContingencyResults(network, violationFilter, workingDir.resolve(CONSTRAINTS_FOLDER), contingencies),
ContingencyResultsUtils.getPostContingencyResults(network, violationFilter, workingDir, contingencies),
Collections.emptyList())
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,34 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.dynaflow;
package com.powsybl.dynaflow.results;

import com.powsybl.commons.report.ReportNode;
import com.powsybl.contingency.Contingency;
import com.powsybl.dynaflow.DynaflowReports;
import com.powsybl.dynaflow.xml.ConstraintsReader;
import com.powsybl.dynawo.commons.CommonReports;
import com.powsybl.dynawo.commons.timeline.TimelineEntry;
import com.powsybl.dynawo.commons.timeline.XmlTimeLineParser;
import com.powsybl.iidm.network.Network;
import com.powsybl.loadflow.LoadFlowResult;
import com.powsybl.security.*;
import com.powsybl.security.LimitViolation;
import com.powsybl.security.LimitViolationFilter;
import com.powsybl.security.LimitViolationsResult;
import com.powsybl.security.Security;
import com.powsybl.security.results.NetworkResult;
import com.powsybl.security.results.PostContingencyResult;
import com.powsybl.security.results.PreContingencyResult;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Map;

import static com.powsybl.dynaflow.SecurityAnalysisConstants.CONSTRAINTS_FOLDER;
import static com.powsybl.dynawo.commons.DynawoConstants.AGGREGATED_RESULTS;

/**
* @author Laurent Issertial <laurent.issertial at rte-france.com>
Expand All @@ -48,22 +56,29 @@ public static PreContingencyResult getPreContingencyResult(Network network, Limi
* Build the post-contingency results from the constraints files written by dynawo
*/
public static List<PostContingencyResult> getPostContingencyResults(Network network, LimitViolationFilter violationFilter,
Path constraintsDir, List<Contingency> contingencies) {
Path workingDir, List<Contingency> contingencies) {
Path constraintsDir = workingDir.resolve(CONSTRAINTS_FOLDER);
Path results = workingDir.resolve(AGGREGATED_RESULTS);
Map<String, Status> scenarioResults = new HashMap<>();
if (Files.exists(results)) {
new XmlScenarioResultParser().parse(results, s -> scenarioResults.put(s.id(), s.status()));
}
return contingencies.stream()
.map(c -> getPostContingencyResult(network, violationFilter, constraintsDir, c))
.collect(Collectors.toList());
.map(c -> new PostContingencyResult(c,
ResultsUtil.convertStatus(scenarioResults.getOrDefault(c.getId(), Status.EXECUTION_PROBLEM)),
getLimitViolationsResult(network, violationFilter, constraintsDir, c)))
.toList();
}

private static PostContingencyResult getPostContingencyResult(Network network, LimitViolationFilter violationFilter,
Path constraintsDir, Contingency contingency) {
private static LimitViolationsResult getLimitViolationsResult(Network network, LimitViolationFilter violationFilter,
Path constraintsDir, Contingency contingency) {
Path constraintsFile = constraintsDir.resolve("constraints_" + contingency.getId() + ".xml");
if (Files.exists(constraintsFile)) {
List<LimitViolation> limitViolationsRead = ConstraintsReader.read(network, constraintsFile);
List<LimitViolation> limitViolationsFiltered = violationFilter.apply(limitViolationsRead, network);
return new PostContingencyResult(contingency, PostContingencyComputationStatus.CONVERGED, new LimitViolationsResult(limitViolationsFiltered));
} else {
return new PostContingencyResult(contingency, PostContingencyComputationStatus.FAILED, Collections.emptyList());
return new LimitViolationsResult(limitViolationsFiltered);
}
return LimitViolationsResult.empty();
}

// Report the timeline events from the timeline files written by dynawo
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2024, 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.results;

/**
* scenarioResults or loadIncreaseResults failed criterion
* @param description Failed criterion description
* @param time Failure time (in seconds)
* @author Laurent Issertial {@literal <laurent.issertial at rte-france.com>}
*/
public record FailedCriterion(String description, double time) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Copyright (c) 2024, 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.results;

import com.powsybl.security.PostContingencyComputationStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;

import static com.powsybl.dynaflow.results.Status.CRITERIA_NON_RESPECTED;
import static com.powsybl.security.PostContingencyComputationStatus.*;

/**
* @author Laurent Issertial {@literal <laurent.issertial at rte-france.com>}
*/
public final class ResultsUtil {

private ResultsUtil() {
}

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

public static PostContingencyComputationStatus convertStatus(Status status) {
return switch (status) {
case CONVERGENCE -> CONVERGED;
case DIVERGENCE -> SOLVER_FAILED;
case EXECUTION_PROBLEM, CRITERIA_NON_RESPECTED -> FAILED;
};
}

static Optional<ScenarioResult> createScenarioResult(String id, String status, List<FailedCriterion> failedCriteria) {
if (id == null || status == null || failedCriteria == null) {
LOGGER.warn("Inconsistent scenario result entry (id: '{}', status: '{}', failedCriteria: '{}')", id, status, failedCriteria);
} else {
try {
Status statusE = Status.valueOf(status);
boolean isCriterionError = CRITERIA_NON_RESPECTED == statusE;
if (isCriterionError && failedCriteria.isEmpty()) {
LOGGER.warn("ScenarioResult with {} status should have failed criteria", status);
return Optional.empty();
} else if (!isCriterionError && !failedCriteria.isEmpty()) {
LOGGER.warn("ScenarioResult with {} status should not have failed criteria", status);
return Optional.empty();
}
return Optional.of(new ScenarioResult(id, statusE, failedCriteria));
} catch (IllegalArgumentException e) {
logInconsistentEntry("status", status);
}
}
return Optional.empty();
}

static Optional<FailedCriterion> createFailedCriterion(String description, String time) {
if (description == null || time == null) {
LOGGER.warn("Inconsistent failed criterion entry (description: '{}', time: '{}')", description, time);
} else {
try {
double timeD = Double.parseDouble(time);
return Optional.of(new FailedCriterion(description, timeD));
} catch (NumberFormatException e) {
logInconsistentEntry("time", time);
}
}
return Optional.empty();
}

private static void logInconsistentEntry(String fieldName, String message) {
LOGGER.warn("Inconsistent {} entry '{}'", fieldName, message);
}
}
Loading

0 comments on commit 64cffab

Please sign in to comment.