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

Cse valid/glsk merged #92

Merged
merged 9 commits into from
Mar 29, 2023
35 changes: 35 additions & 0 deletions glsk/glsk-document-cse/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@
<name>GLSK document - CSE format</name>
<description>Model of GLSK according to CSE format with its importer.</description>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<source>src/main/resources/xsd/etso-code-lists.xsd</source>
<source>src/main/resources/xsd/etso-core-cmpts.xsd</source>
<source>src/main/resources/xsd/gsk-document.xsd</source>
</sources>
<xjbSources>
<source>src/main/resources/xjb/binding.xjb</source>
</xjbSources>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
Expand Down Expand Up @@ -48,6 +74,15 @@
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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/.
*/
package com.powsybl.glsk.cse;

import xsd.etso_core_cmpts.QuantityType;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* @author Vincent BOCHET {@literal <vincent.bochet at rte-france.com>}
*/
public final class BlockWrapper {
private final Object block;

public BlockWrapper(Object block) {
this.block = block;
}

public Object getBlock() {
return block;
}

public Optional<BigInteger> getOrder() {
try {
BigInteger order = (BigInteger) block.getClass().getDeclaredField("order").get(block);
return Optional.ofNullable(order);
} catch (NoSuchFieldException | IllegalAccessException e) {
return Optional.empty();
}
}

public Optional<BigDecimal> getMaximumShift() {
try {
QuantityType maximumShift = (QuantityType) block.getClass().getDeclaredField("maximumShift").get(block);
return Optional.ofNullable(maximumShift).map(QuantityType::getV);
} catch (NoSuchFieldException | IllegalAccessException e) {
return Optional.empty();
}
}

public Optional<BigDecimal> getFactor() {
try {
QuantityType factor = (QuantityType) block.getClass().getDeclaredField("factor").get(block);
return Optional.ofNullable(factor).map(QuantityType::getV);
} catch (NoSuchFieldException | IllegalAccessException e) {
return Optional.empty();
}
}

public Optional<List<NodeWrapper>> getNodeList() {
try {
List<Object> objectList = (List<Object>) block.getClass().getDeclaredField("node").get(block);
return Optional.ofNullable(objectList)
.map(list -> list.stream().map(NodeWrapper::new).collect(Collectors.toList()));
} catch (NoSuchFieldException | IllegalAccessException e) {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, RTE (http://www.rte-france.com)
* 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/.
Expand All @@ -9,72 +9,156 @@
import com.powsybl.glsk.api.GlskDocument;
import com.powsybl.glsk.api.GlskPoint;
import com.powsybl.glsk.api.util.converters.GlskPointScalableConverter;
import com.powsybl.glsk.commons.CountryEICode;
import com.powsybl.glsk.commons.GlskException;
import com.powsybl.glsk.commons.ZonalData;
import com.powsybl.glsk.commons.ZonalDataChronology;
import com.powsybl.glsk.commons.ZonalDataImpl;
import com.powsybl.iidm.modification.scalable.Scalable;
import com.powsybl.iidm.network.Country;
import com.powsybl.iidm.network.Network;
import com.powsybl.sensitivity.SensitivityVariableSet;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.JAXBIntrospector;
import jakarta.xml.bind.Unmarshaller;
import org.apache.commons.lang3.NotImplementedException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.InputStream;
import java.net.URL;
import java.time.Instant;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
* @author Sebastien Murgey {@literal <sebastien.murgey at rte-france.com>}
* @author Vincent BOCHET {@literal <vincent.bochet at rte-france.com>}
*/
public final class CseGlskDocument implements GlskDocument {
private static final Logger LOGGER = LoggerFactory.getLogger(CseGlskDocument.class);
private static final String LINEAR_GLSK_NOT_HANDLED = "CSE GLSK document does not handle Linear GLSK conversion";
private static final String COUNTRIES_IN_AREA_KEY = "countriesInArea";
private static final String COUNTRIES_OUT_AREA_KEY = "countriesOutArea";

/**
* list of GlskPoint in the given Glsk document
*/
private final Map<String, List<GlskPoint>> cseGlskPoints = new TreeMap<>();

public static CseGlskDocument importGlsk(Document document) {
return new CseGlskDocument(document);
}

public static CseGlskDocument importGlsk(InputStream inputStream) {
public static CseGlskDocument importGlsk(InputStream inputStream, boolean useCalculationDirections) {
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
documentBuilderFactory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
documentBuilderFactory.setNamespaceAware(true);

Document document = documentBuilderFactory.newDocumentBuilder().parse(inputStream);
document.getDocumentElement().normalize();
return new CseGlskDocument(document);
} catch (IOException | SAXException | ParserConfigurationException e) {
JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

// Setup schema validator
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
URL glskSchemaResource = CseGlskDocument.class.getResource("/xsd/gsk-document.xsd");
if (glskSchemaResource != null) {
Schema glskSchema = sf.newSchema(glskSchemaResource);
unmarshaller.setSchema(glskSchema);
} else {
LOGGER.warn("Unable to find GLSK Schema definition file. GLSK file will be imported without schema validation.");
}

// Unmarshal xml file
GSKDocument nativeGskDocument = (GSKDocument) JAXBIntrospector.getValue(unmarshaller.unmarshal(inputStream));
return new CseGlskDocument(nativeGskDocument, useCalculationDirections);
} catch (SAXException e) {
throw new GlskException("Unable to import CSE GLSK file: Schema validation failed.", e);
} catch (JAXBException e) {
throw new GlskException("Unable to import CSE GLSK file.", e);
}
}

private CseGlskDocument(Document document) {
NodeList timeSeriesNodeList = document.getElementsByTagName("TimeSeries");
for (int i = 0; i < timeSeriesNodeList.getLength(); i++) {
if (timeSeriesNodeList.item(i).getNodeType() == Node.ELEMENT_NODE) {
Element timeSeriesElement = (Element) timeSeriesNodeList.item(i);
GlskPoint glskPoint = new CseGlskPoint(timeSeriesElement);
cseGlskPoints.computeIfAbsent(glskPoint.getSubjectDomainmRID(), area -> new ArrayList<>());
cseGlskPoints.get(glskPoint.getSubjectDomainmRID()).add(glskPoint);
}
private CseGlskDocument(GSKDocument nativeGskDocument, boolean useCalculationDirections) {
// Computation of "standard" and "export" timeseries
Map<String, List<GlskPoint>> cseGlskPointsStandard = getGlskPointsFromTimeSeries(nativeGskDocument.getTimeSeries());
Map<String, List<GlskPoint>> cseGlskPointsExport = getGlskPointsFromTimeSeries(nativeGskDocument.getTimeSeriesExport());

if (!useCalculationDirections || calculationDirectionsAbsent(nativeGskDocument)) {
// "default" mode : all countries are considered in full import (use TimeSeries for all)
this.cseGlskPoints.putAll(cseGlskPointsStandard);
} else {
// Extract CalculationDirections
List<CalculationDirectionType> calculationDirections = nativeGskDocument.getCalculationDirections().get(0).getCalculationDirection();
Map<String, List<String>> countriesInAndOutArea = getCountriesInAndOutArea(calculationDirections);

// Use data from cseGlskPointsStandard or cseGlskPointsExport depending on CalculationDirections
fillGlskPointsForExportCorner(cseGlskPointsStandard, cseGlskPointsExport, countriesInAndOutArea);
}
}

private static Map<String, List<GlskPoint>> getGlskPointsFromTimeSeries(List<TimeSeriesType> timeSeries) {
Map<String, List<GlskPoint>> cseGlskPointsPerArea = new TreeMap<>();
if (timeSeries == null) {
return cseGlskPointsPerArea;
}

timeSeries.stream()
.map(CseGlskPoint::new)
.forEach(glskPoint -> {
cseGlskPointsPerArea.computeIfAbsent(glskPoint.getSubjectDomainmRID(), area -> new ArrayList<>());
cseGlskPointsPerArea.get(glskPoint.getSubjectDomainmRID()).add(glskPoint);
});
return cseGlskPointsPerArea;
}

private static boolean calculationDirectionsAbsent(GSKDocument nativeGskDocument) {
return nativeGskDocument.getCalculationDirections() == null
|| nativeGskDocument.getCalculationDirections().isEmpty()
|| nativeGskDocument.getCalculationDirections().get(0).getCalculationDirection() == null
|| nativeGskDocument.getCalculationDirections().get(0).getCalculationDirection().isEmpty();
}

private static Map<String, List<String>> getCountriesInAndOutArea(List<CalculationDirectionType> calculationDirections) {
String italyEIC = new CountryEICode(Country.IT).getCode();
List<String> countriesInArea = new ArrayList<>();
List<String> countriesOutArea = new ArrayList<>();

calculationDirections.stream()
.map(cd -> cd.getInArea().getV())
.filter(countryEIC -> !countryEIC.equals(italyEIC))
.forEach(countriesInArea::add);

countriesInArea.add(italyEIC);

calculationDirections.stream()
.map(cd -> cd.getOutArea().getV())
.filter(countryEIC -> !countryEIC.equals(italyEIC))
.forEach(countriesOutArea::add);

return Map.of(COUNTRIES_IN_AREA_KEY, countriesInArea,
COUNTRIES_OUT_AREA_KEY, countriesOutArea);
Comment on lines +126 to +143
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is Italy a special case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Export Corner developments, the CalculationDirections (representing which countries have activated Export Corner excahnges or not) should contain only directions from a country to Italy or vice-versa. E.g.: {InArea: France, OutArea: Italy} if France has activated Export Corner exchanges, or {InArea: Italy, OutArea: France} if France has not activated Export Corner exchanges.
There is no sense to put {InArea: Italy, OutArea: Italy} because no exchange is made from Italy to itself.
But we need to handle Italy because we need to retrieve its TimeSeries. As the rules for Italy (concerning TimeSeries retrieval) are the same as the rules for countries having activated Export Corner exchanges, I decided to put it in the countriesInArea List.

}

private void fillGlskPointsForExportCorner(Map<String, List<GlskPoint>> cseGlskPointsStandard,
Map<String, List<GlskPoint>> cseGlskPointsExport,
Map<String, List<String>> countriesInAndOutArea) {
countriesInAndOutArea.get(COUNTRIES_IN_AREA_KEY).forEach(eic -> {
List<GlskPoint> cseGlskPoint = cseGlskPointsExport.getOrDefault(eic, cseGlskPointsStandard.get(eic));
this.cseGlskPoints.computeIfAbsent(eic, area -> new ArrayList<>());
this.cseGlskPoints.get(eic).addAll(cseGlskPoint);
});

countriesInAndOutArea.get(COUNTRIES_OUT_AREA_KEY).forEach(eic -> {
List<GlskPoint> cseGlskPoint = cseGlskPointsStandard.get(eic);
this.cseGlskPoints.computeIfAbsent(eic, area -> new ArrayList<>());
this.cseGlskPoints.get(eic).addAll(cseGlskPoint);
});
}

@Override
public List<String> getZones() {
return new ArrayList<>(cseGlskPoints.keySet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ public class CseGlskDocumentImporter extends AbstractGlskDocumentImporter implem

@Override
public GlskDocument importGlsk(InputStream inputStream) {
if (document != null) {
return CseGlskDocument.importGlsk(document);
}
return CseGlskDocument.importGlsk(inputStream);
return importGlsk(inputStream, false);
}

@Override
public GlskDocument importGlsk(InputStream inputStream, boolean useCalculationDirections) {
return CseGlskDocument.importGlsk(inputStream, useCalculationDirections);
}

@Override
Expand Down
Loading