Skip to content

Commit

Permalink
Parse Timeline dynawo file (#229)
Browse files Browse the repository at this point in the history
* CSV TimeLine parser
* XML TimeLine parser
* Add unit tests
* Add TimelineEntry pojo class for the output of the parsers

Signed-off-by: Marcos de Miguel <demiguelm@aia.es>
Signed-off-by: lisrte <laurent.issertial@rte-france.com>
Signed-off-by: Florian Dupuy <florian.dupuy@rte-france.com>
  • Loading branch information
Lisrte authored Jun 6, 2023
1 parent 851ea4d commit 2ad5009
Show file tree
Hide file tree
Showing 12 changed files with 407 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* 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.dynawo.commons.timeline;

import com.powsybl.commons.PowsyblException;
import com.univocity.parsers.common.ParsingContext;
import com.univocity.parsers.common.ResultIterator;
import com.univocity.parsers.csv.CsvParser;
import com.univocity.parsers.csv.CsvParserSettings;

import java.io.BufferedReader;
import java.io.IOException;
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;

/**
* @author Laurent Issertial <laurent.issertial at rte-france.com>
*/
public final class CsvTimeLineParser implements TimeLineParser {

private static final int NB_COLUMNS = 3;
private final char separator;

public CsvTimeLineParser() {
this('|');
}

public CsvTimeLineParser(char separator) {
this.separator = separator;
}

public List<TimelineEntry> parse(Path file) {
return parse(file, separator);
}

public static List<TimelineEntry> parse(Path file, char separator) {
try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
return parse(reader, separator);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

static List<TimelineEntry> parse(BufferedReader reader, char separator) {
Objects.requireNonNull(reader);
CsvParserSettings settings = new CsvParserSettings();
settings.getFormat().setDelimiter(separator);
settings.getFormat().setQuoteEscape('"');
settings.getFormat().setLineSeparator(System.lineSeparator());
settings.setMaxColumns(NB_COLUMNS);
CsvParser csvParser = new CsvParser(settings);
ResultIterator<String[], ParsingContext> iterator = csvParser.iterate(reader).iterator();
return read(iterator);
}

static List<TimelineEntry> read(ResultIterator<String[], ParsingContext> iterator) {
List<TimelineEntry> timeline = new ArrayList<>();
int iLine = 0;
while (iterator.hasNext()) {
iLine++;
String[] tokens = iterator.next();
if (tokens.length != NB_COLUMNS) {
throw new PowsyblException("Columns of line " + iLine + " are inconsistent");
}
TimeLineUtil.createEvent(tokens[0], tokens[1], tokens[2])
.ifPresent(timeline::add);
}
return timeline;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* 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.dynawo.commons.timeline;

import java.nio.file.Path;
import java.util.List;

/**
* @author Florian Dupuy <florian.dupuy at rte-france.com>
*/
public interface TimeLineParser {
List<TimelineEntry> parse(Path timeLineFile);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* 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.dynawo.commons.timeline;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;

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

private TimeLineUtil() {
}

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

static Optional<TimelineEntry> createEvent(String time, String modelName, String message) {
if (time == null || modelName == null || message == null) {
LOGGER.warn("Inconsistent event entry (time: '{}', modelName: '{}', message: '{}')", time, modelName, message);
} else {
try {
double timeD = Double.parseDouble(time);
return Optional.of(new TimelineEntry(timeD, modelName, message));
} catch (NumberFormatException e) {
LOGGER.warn("Inconsistent time entry '{}'", time);
}
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* 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.dynawo.commons.timeline;

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

private final double time;
private final String modelName;
private final String message;

public TimelineEntry(double time, String modelName, String message) {
this.time = time;
this.modelName = modelName;
this.message = message;
}

public double getTime() {
return time;
}

public String getModelName() {
return modelName;
}

public String getMessage() {
return message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* 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.dynawo.commons.timeline;

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

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.List;
import java.util.Objects;

/**
* @author Laurent Issertial <laurent.issertial at rte-france.com>
* @author Marcos de Miguel <demiguelm at aia.es>
*/
public final class XmlTimeLineParser 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);

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<>();
int state = xmlReader.next();
while (state == XMLStreamConstants.COMMENT) {
state = xmlReader.next();
}
XmlUtil.readUntilEndElement("timeline", xmlReader, () -> {
if (xmlReader.getLocalName().equals("event")) {
String time = xmlReader.getAttributeValue(null, TIME);
String modelName = xmlReader.getAttributeValue(null, MODEL_NAME);
String message = xmlReader.getAttributeValue(null, MESSAGE);
TimeLineUtil.createEvent(time, modelName, message)
.ifPresent(timeline::add);
}
});
return timeline;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* 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.dynawo.commons.timeline;

import com.powsybl.commons.PowsyblException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
* @author Laurent Issertial <laurent.issertial at rte-france.com>
* @author Marcos de Miguel <demiguelm at aia.es>
*/
class CsvTimeLineParserTest {

@ParameterizedTest
@ValueSource(strings = {"/timeline.log", "/timelineWithQuotes.log"})
void testTimeline(String fileName) throws URISyntaxException {

Path path = Path.of(Objects.requireNonNull(getClass().getResource(fileName)).toURI());
List<TimelineEntry> timeline = new CsvTimeLineParser().parse(path);

assertEquals(5, timeline.size());
assertEquals("PMIN : activation", timeline.get(0).getMessage());
assertEquals("GEN____8_SM", timeline.get(0).getModelName());
assertEquals(0., timeline.get(0).getTime(), 1e-9);
assertEquals("PMIN : activation", timeline.get(1).getMessage());
assertEquals("GEN____3_SM", timeline.get(1).getModelName());
assertEquals(0.0306911, timeline.get(1).getTime(), 1e-9);
assertEquals("PMIN : deactivation", timeline.get(2).getMessage());
assertEquals("GEN____8_SM", timeline.get(2).getModelName());
assertEquals("PMIN : deactivation", timeline.get(3).getMessage());
assertEquals("GEN____3_SM", timeline.get(3).getModelName());
assertEquals("PMIN : activation", timeline.get(4).getMessage());
assertEquals("GEN____8_SM", timeline.get(4).getModelName());
}

@Test
void testInconsistentFile() throws URISyntaxException {
Path path = Path.of(Objects.requireNonNull(getClass().getResource("/wrongTimeline.log")).toURI());
Exception e = assertThrows(PowsyblException.class, () -> CsvTimeLineParser.parse(path, '|'));
assertEquals("Columns of line 2 are inconsistent", e.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* 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.dynawo.commons.timeline;

import org.junit.jupiter.api.Test;

import javax.xml.stream.XMLStreamException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* @author Marcos de Miguel <demiguelm at aia.es>
*/
class XmlTimeLineParserTest {

@Test
void test() throws XMLStreamException {

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

assertEquals(5, timeline.size());
assertEquals("PMIN : activation", timeline.get(0).getMessage());
assertEquals("GEN____8_SM", timeline.get(0).getModelName());
assertEquals(0., timeline.get(0).getTime(), 1e-9);
assertEquals("PMIN : activation", timeline.get(1).getMessage());
assertEquals("GEN____3_SM", timeline.get(1).getModelName());
assertEquals(0.030691068513160655, timeline.get(1).getTime(), 1e-9);
assertEquals("PMIN : deactivation", timeline.get(2).getMessage());
assertEquals("GEN____8_SM", timeline.get(2).getModelName());
assertEquals("PMIN : deactivation", timeline.get(3).getMessage());
assertEquals("GEN____3_SM", timeline.get(3).getModelName());
assertEquals("PMIN : activation", timeline.get(4).getMessage());
assertEquals("GEN____8_SM", timeline.get(4).getModelName());
}

@Test
void parseFromPath() throws URISyntaxException {
Path path = Path.of(Objects.requireNonNull(getClass().getResource("/timeline.xml")).toURI());
List<TimelineEntry> timeline = new XmlTimeLineParser().parse(path);
assertEquals(5, timeline.size());
}

@Test
void testInconsistentFile() throws XMLStreamException {
InputStreamReader xml = new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("/wrongTimeline.xml")));
List<TimelineEntry> timeline = XmlTimeLineParser.parse(xml);
assertEquals(4, timeline.size());
}
}
5 changes: 5 additions & 0 deletions commons/src/test/resources/timeline.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0 | GEN____8_SM | PMIN : activation
0.0306911 | GEN____3_SM | PMIN : activation
0.348405 | GEN____8_SM | PMIN : deactivation
0.828675 | GEN____3_SM | PMIN : deactivation
0.834701 | GEN____8_SM | PMIN : activation
9 changes: 9 additions & 0 deletions commons/src/test/resources/timeline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!-- Dynawo timeline -->
<timeline xmlns="http://www.rte-france.com/dynawo">
<event time="0" modelName="GEN____8_SM" message="PMIN : activation"/>
<event time="0.030691068513160655" modelName="GEN____3_SM" message="PMIN : activation"/>
<event time="0.348405399018546" modelName="GEN____8_SM" message="PMIN : deactivation"/>
<event time="0.82867509138207629" modelName="GEN____3_SM" message="PMIN : deactivation"/>
<event time="0.83470144243143729" modelName="GEN____8_SM" message="PMIN : activation"/>
</timeline>
Loading

0 comments on commit 2ad5009

Please sign in to comment.