-
Notifications
You must be signed in to change notification settings - Fork 2
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
Changes from 8 commits
3a7e1b6
990e85f
96094e5
f0dde73
c70aea1
2689b5a
f63a550
2738b6c
5feca81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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/. | ||
|
@@ -9,70 +9,154 @@ | |
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); | ||
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>> cseGlskPoints = new TreeMap<>(); | ||
if (timeSeries == null) { | ||
return cseGlskPoints; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should a warning be logged / an exception be thrown here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An exception, I'm sure not. |
||
} | ||
|
||
timeSeries.stream() | ||
.map(CseGlskPoint::new) | ||
.forEach(glskPoint -> { | ||
cseGlskPoints.computeIfAbsent(glskPoint.getSubjectDomainmRID(), area -> new ArrayList<>()); | ||
cseGlskPoints.get(glskPoint.getSubjectDomainmRID()).add(glskPoint); | ||
} | ||
} | ||
}); | ||
return cseGlskPoints; | ||
} | ||
|
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is Italy a special case? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
|
||
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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe you could rename it to cseGlskPointsPerArea