Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PostContingencyResult status fix #405

Merged
merged 9 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
@@ -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,10 +5,11 @@
* 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;
Expand All @@ -23,9 +24,14 @@
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.Map;
import java.util.stream.Collectors;

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 +54,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))
.map(c -> new PostContingencyResult(c,
ResultsUtil.convertStatus(scenarioResults.getOrDefault(c.getId(), Status.EXECUTION_PROBLEM)),
getLimitViolationsResult(network, violationFilter, constraintsDir, c)))
.collect(Collectors.toList());
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
}

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 message Failed criterion message
* @param time Failure time (in seconds)
* @author Laurent Issertial {@literal <laurent.issertial at rte-france.com>}
*/
public record FailedCriterion(String message, double time) {
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
}
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 message, String time) {
if (message == null || time == null) {
LOGGER.warn("Inconsistent failed criterion entry (message: '{}', time: '{}')", message, time);
} else {
try {
double timeD = Double.parseDouble(time);
return Optional.of(new FailedCriterion(message, 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
Loading