-
Notifications
You must be signed in to change notification settings - Fork 43
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
CGMES: remove extension for Control Areas, use IIDM Area #3149
base: main
Are you sure you want to change the base?
Changes from 13 commits
9bc3e5c
f164ae5
8831363
38c02c9
df99a96
9387a35
5019069
c67ca0c
69026ac
e946c3f
20cd5e5
ee15e39
0af40d1
f51309f
53cb10d
81b63e3
f2a31ac
c081ba2
cc60a53
4f9d471
d264397
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 |
---|---|---|
|
@@ -232,11 +232,8 @@ public Network convert(ReportNode reportNode) { | |
|
||
if (config.importControlAreas()) { | ||
context.pushReportNode(CgmesReports.convertingElementTypeReport(reportNode, CgmesNames.CONTROL_AREA)); | ||
network.newExtension(CgmesControlAreasAdder.class).add(); | ||
CgmesControlAreas cgmesControlAreas = network.getExtension(CgmesControlAreas.class); | ||
cgmes.controlAreas().forEach(ca -> createControlArea(cgmesControlAreas, ca)); | ||
cgmes.tieFlows().forEach(tf -> addTieFlow(context, cgmesControlAreas, tf)); | ||
cgmesControlAreas.cleanIfEmpty(); | ||
cgmes.controlAreas().forEach(ca -> createControlArea(context, ca)); | ||
cgmes.tieFlows().forEach(tf -> addTieFlow(context, tf)); | ||
context.popReportNode(); | ||
} | ||
|
||
|
@@ -401,31 +398,46 @@ private static void completeVoltagesAndAngles(Network network) { | |
network.getTieLines().forEach(tieLine -> AbstractConductingEquipmentConversion.calculateVoltageAndAngleInBoundaryBus(tieLine.getDanglingLine1(), tieLine.getDanglingLine2())); | ||
} | ||
|
||
private static void createControlArea(CgmesControlAreas cgmesControlAreas, PropertyBag ca) { | ||
String controlAreaId = ca.getId("ControlArea"); | ||
cgmesControlAreas.newCgmesControlArea() | ||
private static void createControlArea(Context context, PropertyBag ca) { | ||
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. I suggest you create an AreaConversion.java class in com.powsybl.cgmes.conversion.elements package and move that piece of code there. 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. Code moved. Had to make public a class in conversion, related to the mapping of terminals of tie flows |
||
String controlAreaId = ca.getId(CgmesNames.CONTROL_AREA); | ||
String type = ca.getLocal("controlAreaType"); | ||
Area area = context.network().newArea() | ||
.setAreaType(type) // Copy the type defined by CGMES | ||
.setId(controlAreaId) | ||
.setName(ca.getLocal("name")) | ||
.setEnergyIdentificationCodeEic(ca.getLocal("energyIdentCodeEic")) | ||
.setNetInterchange(ca.asDouble("netInterchange", Double.NaN)) | ||
.setPTolerance(ca.asDouble("pTolerance", Double.NaN)) | ||
.setInterchangeTarget(ca.asDouble("netInterchange", Double.NaN)) | ||
.add(); | ||
String pTolerance = "0"; | ||
if (ca.containsKey(CgmesNames.P_TOLERANCE)) { | ||
pTolerance = ca.get(CgmesNames.P_TOLERANCE); | ||
} | ||
area.setProperty(CgmesNames.P_TOLERANCE, pTolerance); | ||
if (ca.containsKey(CgmesNames.ENERGY_IDENT_CODE_EIC)) { | ||
area.addAlias(ca.get(CgmesNames.ENERGY_IDENT_CODE_EIC), CgmesNames.ENERGY_IDENT_CODE_EIC); | ||
} | ||
} | ||
|
||
private static void addTieFlow(Context context, CgmesControlAreas cgmesControlAreas, PropertyBag tf) { | ||
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. I suggest you create a TieFlowConversion.java class in com.powsybl.cgmes.conversion.elements package and move that piece of code there. 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. Code moved. Had to make public a class in conversion, related to the mapping of terminals of tie flows |
||
String controlAreaId = tf.getId("ControlArea"); | ||
CgmesControlArea cgmesControlArea = cgmesControlAreas.getCgmesControlArea(controlAreaId); | ||
if (cgmesControlArea == null) { | ||
private static void addTieFlow(Context context, PropertyBag tf) { | ||
String controlAreaId = tf.getId(CgmesNames.CONTROL_AREA); | ||
Area area = context.network().getArea(controlAreaId); | ||
if (area == null) { | ||
context.ignored("Tie Flow", String.format("Tie Flow %s refers to a non-existing control area", tf.getId("TieFlow"))); | ||
return; | ||
} | ||
String terminalId = tf.getId("terminal"); | ||
Boundary boundary = context.terminalMapping().findBoundary(terminalId, context.cgmes()); | ||
if (boundary != null) { | ||
cgmesControlArea.add(boundary); | ||
area.newAreaBoundary() | ||
.setAc(true) | ||
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. Isn't it possible to set the correct value for this boolean here in CGMES import, the same way it is done in the CGMES export? 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. Done. Added a specific unit test. |
||
.setBoundary(boundary) | ||
.add(); | ||
return; | ||
} | ||
RegulatingTerminalMapper.mapForTieFlow(terminalId, context).ifPresent(cgmesControlArea::add); | ||
RegulatingTerminalMapper.mapForTieFlow(terminalId, context) | ||
.ifPresent(t -> area.newAreaBoundary() | ||
.setAc(true) | ||
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. Isn't it possible to set the correct value for this boolean here in CGMES import, the same way it is done in the CGMES export? |
||
.setTerminal(t) | ||
.add()); | ||
} | ||
|
||
private void convert(PropertyBags elements, String elementType, Context context) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -530,21 +530,51 @@ private void addIidmMappingsEquivalentInjection(Network network) { | |
} | ||
|
||
private void addIidmMappingsControlArea(Network network) { | ||
CgmesControlAreas cgmesControlAreas = network.getExtension(CgmesControlAreas.class); | ||
if (cgmesControlAreas == null) { | ||
network.newExtension(CgmesControlAreasAdder.class).add(); | ||
cgmesControlAreas = network.getExtension(CgmesControlAreas.class); | ||
String cgmesControlAreaId = namingStrategy.getCgmesId(refTyped(network), CONTROL_AREA); | ||
cgmesControlAreas.newCgmesControlArea() | ||
.setId(cgmesControlAreaId) | ||
.setName("Network") | ||
.setEnergyIdentificationCodeEic("Network--1") | ||
// If no control area exists, create one for the whole network, containing the dangling lines as boundaries, | ||
// but only if the network does not contain subnetworks | ||
long numControlAreas = network.getAreaStream().filter(a -> a.getAreaType().equals("ControlAreaTypeKind.Interchange")).count(); | ||
long numSubnetworks = network.getSubnetworks().size(); | ||
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 int could suffice since size() returns an int anyway. 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. changed |
||
if (numControlAreas == 0 && numSubnetworks == 0) { | ||
createDefaultControlArea(network); | ||
} | ||
} | ||
|
||
private void createDefaultControlArea(Network network) { | ||
String controlAreaId = namingStrategy.getCgmesId(refTyped(network), CONTROL_AREA); | ||
Area area = network.newArea() | ||
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. The export process shouldn't alter the original IIDM network. All information necessary for the control area and tie flow export should be stored somewhere else. 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. The information would be kept in objects identical to the Area objects defined in IIDM, that will result in duplicated classes. Also, the network may already have control areas that need to be exported. This means that we will either end up with duplicated classes and code (export control areas defined in the IIDM, then export the default area defined in other objects with the same structure) or transform the control areas defined in IIDM to the "export objects" before exporting. What about defining a configuration parameter in the export, where the user explicitly tells the CGMES module export to "create-default-area-if-none-exists" ? 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. After discussion in a technical meeting we opted for creating a helper public method that allows the user to explicitly requests to add a default control area of type interchange to the network. This must be invoked explicitly by the user before exporting. The export process itself will not add, remove or change the attributes of the objects in the IIDM model. As an implementation note: we have made available this helper method in the This is the way the user should call the helper method before exporting, if everything is configured: new CgmesExport().createDefaultControlAreaInterchange(network);
network.write("CGMES", outputPath); Also the user can provide some of the parameters explicitly, if needed. As an example: Properties exportParams = new Properties();
exportParams.put(CgmesImport.BOUNDARY_LOCATION, "....");
exportParams.put(CgmesExport.NAMING_STRATEGY, NamingStrategyFactory.CGMES);
new CgmesExport().createDefaultControlAreaInterchange(network, exportParams);
network.write("CGMES", exportParams, outputPath); |
||
.setAreaType("ControlAreaTypeKind.Interchange") | ||
.setId(controlAreaId) | ||
.setName("Network") | ||
.add(); | ||
if (referenceDataProvider != null && referenceDataProvider.getSourcingActor().containsKey(CgmesNames.ENERGY_IDENT_CODE_EIC)) { | ||
area.addAlias(referenceDataProvider.getSourcingActor().get(CgmesNames.ENERGY_IDENT_CODE_EIC), CgmesNames.ENERGY_IDENT_CODE_EIC); | ||
} | ||
double currentInterchange = 0; | ||
Set<String> boundaryDcNodes = getBoundaryDcNodes(); | ||
for (DanglingLine danglingLine : CgmesExportUtil.getBoundaryDanglingLines(network)) { | ||
// Our exchange should be referred the boundary | ||
area.newAreaBoundary() | ||
.setAc(isAcBoundary(danglingLine, boundaryDcNodes)) | ||
.setBoundary(danglingLine.getBoundary()) | ||
.add(); | ||
CgmesControlArea cgmesControlArea = cgmesControlAreas.getCgmesControlArea(cgmesControlAreaId); | ||
for (DanglingLine danglingLine : CgmesExportUtil.getBoundaryDanglingLines(network)) { | ||
cgmesControlArea.add(danglingLine.getTerminal()); | ||
} | ||
currentInterchange += danglingLine.getBoundary().getP(); | ||
} | ||
area.setInterchangeTarget(currentInterchange); | ||
} | ||
|
||
private Set<String> getBoundaryDcNodes() { | ||
return referenceDataProvider.getBoundaryNodes().stream() | ||
.filter(node -> node.containsKey("topologicalNodeDescription") && node.get("topologicalNodeDescription").startsWith("HVDC")) | ||
.map(node -> node.getId(CgmesNames.TOPOLOGICAL_NODE)) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
private boolean isAcBoundary(DanglingLine danglingLine, Set<String> boundaryDcNodes) { | ||
String dlBoundaryNode = danglingLine.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.TOPOLOGICAL_NODE_BOUNDARY); | ||
if (dlBoundaryNode != null) { | ||
return !boundaryDcNodes.contains(dlBoundaryNode); | ||
} | ||
return true; | ||
} | ||
|
||
public int getCimVersion() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1365,37 +1365,47 @@ private static void writeAcdcConverterDCTerminal(String id, String conductingEqu | |
} | ||
|
||
private static void writeControlAreas(String energyAreaId, Network network, String cimNamespace, String euNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException { | ||
CgmesControlAreas cgmesControlAreas = network.getExtension(CgmesControlAreas.class); | ||
for (CgmesControlArea cgmesControlArea : cgmesControlAreas.getCgmesControlAreas()) { | ||
writeControlArea(cgmesControlArea, energyAreaId, cimNamespace, euNamespace, writer, context, network); | ||
for (Area area : network.getAreas()) { | ||
if (area.getAreaType().equals("ControlAreaTypeKind.Interchange")) { | ||
writeControlArea(area, energyAreaId, cimNamespace, euNamespace, writer, context, network); | ||
} | ||
} | ||
} | ||
|
||
private static void writeControlArea(CgmesControlArea cgmesControlArea, String energyAreaId, String cimNamespace, String euNamespace, | ||
private static void writeControlArea(Area controlArea, String energyAreaId, String cimNamespace, String euNamespace, | ||
XMLStreamWriter writer, CgmesExportContext context, Network network) throws XMLStreamException { | ||
// Original control area identifiers may not respect mRID rules, so we pass it through naming strategy | ||
// to obtain always valid mRID identifiers | ||
String controlAreaCgmesId = context.getNamingStrategy().getCgmesId(cgmesControlArea.getId()); | ||
ControlAreaEq.write(controlAreaCgmesId, cgmesControlArea.getName(), cgmesControlArea.getEnergyIdentificationCodeEIC(), energyAreaId, cimNamespace, euNamespace, writer, context); | ||
for (Terminal terminal : cgmesControlArea.getTerminals()) { | ||
Connectable<?> c = terminal.getConnectable(); | ||
if (c instanceof DanglingLine dl) { | ||
if (network.isBoundaryElement(dl)) { | ||
String tieFlowId = context.getNamingStrategy().getCgmesId(refTyped(c), TIE_FLOW); | ||
String terminalId = context.getNamingStrategy().getCgmesIdFromAlias(dl, Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + TERMINAL_BOUNDARY); | ||
TieFlowEq.write(tieFlowId, controlAreaCgmesId, terminalId, cimNamespace, writer, context); | ||
} else { | ||
LOG.error("Unsupported tie flow at TieLine boundary {}", dl.getId()); | ||
} | ||
} else { | ||
LOG.warn("Ignored tie flow at {}: should be a dangling line to retrieve boundary terminal", terminal.getConnectable().getId()); | ||
} | ||
String controlAreaCgmesId = context.getNamingStrategy().getCgmesId(controlArea.getId()); | ||
String energyIdentCodeEic = controlArea.getAliasFromType(CgmesNames.ENERGY_IDENT_CODE_EIC).orElse(""); | ||
ControlAreaEq.write(controlAreaCgmesId, controlArea.getNameOrId(), energyIdentCodeEic, energyAreaId, cimNamespace, euNamespace, writer, context); | ||
for (AreaBoundary areaBoundary : controlArea.getAreaBoundaries()) { | ||
TieFlow.from(areaBoundary, context, network).ifPresent(tieFlow -> | ||
TieFlowEq.write(tieFlow.id(), controlAreaCgmesId, tieFlow.terminalId(), cimNamespace, writer, context) | ||
); | ||
} | ||
} | ||
|
||
private record TieFlow(String id, String terminalId) { | ||
static Optional<TieFlow> from(AreaBoundary areaBoundary, CgmesExportContext context, Network network) { | ||
return areaBoundary.getTerminal().map(terminal -> from(terminal, context)) | ||
.orElse(areaBoundary.getBoundary().flatMap(boundary -> from(boundary, context, network))); | ||
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. Maybe use |
||
} | ||
|
||
static Optional<TieFlow> from(Terminal terminal, CgmesExportContext context) { | ||
return Optional.of(new TieFlow( | ||
context.getNamingStrategy().getCgmesId(refTyped(terminal.getConnectable()), TIE_FLOW), | ||
CgmesExportUtil.getTerminalId(terminal, context))); | ||
} | ||
for (Boundary boundary : cgmesControlArea.getBoundaries()) { | ||
|
||
static Optional<TieFlow> from(Boundary boundary, CgmesExportContext context, Network network) { | ||
String terminalId = getTieFlowBoundaryTerminal(boundary, context, network); | ||
if (terminalId != null) { | ||
String tieFlowId = context.getNamingStrategy().getCgmesId(ref(terminalId), TIE_FLOW); | ||
TieFlowEq.write(tieFlowId, controlAreaCgmesId, terminalId, cimNamespace, writer, context); | ||
return Optional.of(new TieFlow( | ||
context.getNamingStrategy().getCgmesId(ref(terminalId), TIE_FLOW), | ||
terminalId)); | ||
} else { | ||
return Optional.empty(); | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,8 +10,6 @@ | |
import com.powsybl.cgmes.conversion.CgmesExport; | ||
import com.powsybl.cgmes.conversion.Conversion; | ||
import com.powsybl.cgmes.conversion.export.elements.RegulatingControlEq; | ||
import com.powsybl.cgmes.extensions.CgmesControlArea; | ||
import com.powsybl.cgmes.extensions.CgmesControlAreas; | ||
import com.powsybl.cgmes.extensions.CgmesTapChanger; | ||
import com.powsybl.cgmes.extensions.CgmesTapChangers; | ||
import com.powsybl.cgmes.model.CgmesMetadataModel; | ||
|
@@ -843,20 +841,28 @@ private static String generatingUnitClassname(Injection<?> i) { | |
} | ||
|
||
private static void writeControlAreas(Network network, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException { | ||
CgmesControlAreas areas = network.getExtension(CgmesControlAreas.class); | ||
for (CgmesControlArea area : areas.getCgmesControlAreas()) { | ||
writeControlArea(area, cimNamespace, writer, context); | ||
for (Area area : network.getAreas()) { | ||
if (area.getAreaType().equals("ControlAreaTypeKind.Interchange")) { | ||
writeControlArea(area, cimNamespace, writer, context); | ||
} | ||
} | ||
} | ||
|
||
private static void writeControlArea(CgmesControlArea area, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException { | ||
String areaId = context.getNamingStrategy().getCgmesId(area.getId()); | ||
private static void writeControlArea(Area controlArea, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException { | ||
String areaId = context.getNamingStrategy().getCgmesId(controlArea.getId()); | ||
CgmesExportUtil.writeStartAbout("ControlArea", areaId, cimNamespace, writer, context); | ||
writer.writeStartElement(cimNamespace, "ControlArea.netInterchange"); | ||
writer.writeCharacters(CgmesExportUtil.format(area.getNetInterchange())); | ||
double netInterchange = controlArea.getInterchangeTarget().orElse(Double.NaN); | ||
writer.writeCharacters(CgmesExportUtil.format(netInterchange)); | ||
writer.writeEndElement(); | ||
double pTolerance; | ||
if (controlArea.hasProperty("pTolerance")) { | ||
pTolerance = Double.parseDouble(controlArea.getProperty("pTolerance")); | ||
} else { | ||
pTolerance = Math.abs(0.01 * netInterchange); | ||
} | ||
writer.writeStartElement(cimNamespace, "ControlArea.pTolerance"); | ||
writer.writeCharacters(CgmesExportUtil.format(area.getPTolerance())); | ||
writer.writeCharacters(CgmesExportUtil.format(pTolerance)); | ||
writer.writeEndElement(); | ||
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. Since this attribute is not required in the SSH, I'm not sure it is necessary to have an else clause here. I would simply keep the if property is present then write it logic, but with no else. Another argument for droping the else clause: QoCDC already defines default tolerance values for raising a warning (50 MW) and an error (200 MW) in case the tolerance is not provided, so since the missing value case is covered I don't think it's absolutely necessary to export a value if not present. 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. Done. Tolerance is written only if explicitly defined. No default value is assigned during import. |
||
writer.writeEndElement(); | ||
} | ||
|
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.
You could create an AreaConversion and a TieFlowConversion class (see comments below) and rely on the convert method here (just like other network elements are converted).
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.
specific classes created