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

CGMES export SSH: allow update of control area, label as superseding #2699

Merged
merged 7 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -281,6 +281,7 @@ private static void createControlArea(CgmesControlAreas cgmesControlAreas, Prope
.setName(ca.getLocal("name"))
.setEnergyIdentificationCodeEic(ca.getLocal("energyIdentCodeEic"))
.setNetInterchange(ca.asDouble("netInterchange", Double.NaN))
.setPTolerance(ca.asDouble("pTolerance", Double.NaN))
.add();
}

Expand Down Expand Up @@ -385,6 +386,7 @@ private void addCgmesSshMetadata(Network network, Context context) {
PropertyBags sshDescription = cgmes.fullModel(CgmesSubset.STEADY_STATE_HYPOTHESIS.getProfile());
if (sshDescription != null && !sshDescription.isEmpty()) {
CgmesSshMetadataAdder adder = network.newExtension(CgmesSshMetadataAdder.class)
.setId(sshDescription.get(0).get("FullModel"))
.setDescription(sshDescription.get(0).get("description"))
.setSshVersion(readVersion(sshDescription, context))
.setModelingAuthoritySet(sshDescription.get(0).get("modelingAuthoritySet"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public static final class ModelDescription {

private String description;
private int version = 1;
private String supersedes;
private final List<String> dependencies = new ArrayList<>();
private String modelingAuthoritySet = "powsybl.org";
private Set<String> ids = new HashSet<>();
Expand All @@ -136,6 +137,10 @@ public String getDescription() {
return description;
}

public String getSupersedes() {
return supersedes;
}

public ModelDescription setDescription(String description) {
this.description = description;
return this;
Expand Down Expand Up @@ -208,6 +213,10 @@ public void setIds(String... ids) {
public Set<String> getIds() {
return Collections.unmodifiableSet(ids);
}

public void setSupersedes(String id) {
this.supersedes = id;
}
}

public CgmesExportContext() {
Expand Down Expand Up @@ -246,6 +255,7 @@ public CgmesExportContext(Network network, ReferenceDataProvider referenceDataPr
if (sshMetadata != null) {
sshModelDescription.setDescription(sshMetadata.getDescription());
sshModelDescription.setVersion(sshMetadata.getSshVersion() + 1);
sshModelDescription.setSupersedes(sshMetadata.getId());
sshModelDescription.addDependencies(sshMetadata.getDependencies());
sshModelDescription.setModelingAuthoritySet(sshMetadata.getModelingAuthoritySet());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ public static void writeModelDescription(XMLStreamWriter writer, ModelDescriptio
writer.writeEmptyElement(MD_NAMESPACE, CgmesNames.DEPENDENT_ON);
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.RESOURCE, dependency);
}
if (modelDescription.getSupersedes() != null) {
writer.writeEmptyElement(MD_NAMESPACE, CgmesNames.SUPERSEDES);
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.RESOURCE, modelDescription.getSupersedes());
}
writer.writeStartElement(MD_NAMESPACE, CgmesNames.PROFILE);
writer.writeCharacters(modelDescription.getProfile());
writer.writeEndElement();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public static void write(Network network, XMLStreamWriter writer, CgmesExportCon
writeConverters(network, cimNamespace, writer, context);
// FIXME open status of retained switches in bus-branch models
writeSwitches(network, cimNamespace, writer, context);
// TODO writeControlAreas
writeTerminals(network, cimNamespace, writer, context);
writeControlAreas(network, cimNamespace, writer, context);

Expand Down Expand Up @@ -778,6 +777,9 @@ private static void writeControlArea(CgmesControlArea area, String cimNamespace,
writer.writeStartElement(cimNamespace, "ControlArea.netInterchange");
writer.writeCharacters(CgmesExportUtil.format(area.getNetInterchange()));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "ControlArea.pTolerance");
writer.writeCharacters(CgmesExportUtil.format(area.getPTolerance()));
writer.writeEndElement();
writer.writeEndElement();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,19 @@ static ComparisonResult noKnownDiffs(Comparison comparison, ComparisonResult res
return result;
}

static ComparisonResult ignoringCgmesSshMetadataId(Comparison comparison, ComparisonResult result) {
if (result == ComparisonResult.DIFFERENT) {
Node control = comparison.getControlDetails().getTarget();
if (comparison.getType() == ComparisonType.ATTR_VALUE
&& control.getNodeName().equals("id")) {
if (comparison.getControlDetails().getXPath().contains("cgmesSshMetadata")) {
return ComparisonResult.EQUAL;
}
}
}
return result;
}

static ComparisonResult ensuringIncreasedModelVersion(Comparison comparison, ComparisonResult result) {
if (result == ComparisonResult.DIFFERENT) {
Node control = comparison.getControlDetails().getTarget();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@

import com.powsybl.cgmes.conformity.CgmesConformity1Catalog;
import com.powsybl.cgmes.conformity.CgmesConformity1ModifiedCatalog;
import com.powsybl.cgmes.conformity.CgmesConformity3Catalog;
import com.powsybl.cgmes.conversion.CgmesExport;
import com.powsybl.cgmes.conversion.CgmesImport;
import com.powsybl.cgmes.conversion.export.CgmesExportContext;
import com.powsybl.cgmes.conversion.export.SteadyStateHypothesisExport;
import com.powsybl.cgmes.extensions.CgmesControlArea;
import com.powsybl.cgmes.extensions.CgmesControlAreas;
import com.powsybl.cgmes.model.CgmesNamespace;
import com.powsybl.commons.test.AbstractConverterTest;
Expand All @@ -31,9 +34,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.*;

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

Expand All @@ -45,31 +46,40 @@ class SteadyStateHypothesisExportTest extends AbstractConverterTest {

@Test
void microGridBE() throws IOException, XMLStreamException {
DifferenceEvaluator knownDiffs = DifferenceEvaluators.chain(
DifferenceEvaluator knownDiffsSsh = DifferenceEvaluators.chain(
ExportXmlCompare::sameScenarioTime,
ExportXmlCompare::ensuringIncreasedModelVersion,
ExportXmlCompare::ignoringSynchronousMachinesSVCsWithTargetDeadband,
ExportXmlCompare::ignoringJunctionOrBusbarTerminals);
test(CgmesConformity1Catalog.microGridBaseCaseBE().dataSource(), 2, knownDiffs);
DifferenceEvaluator knownDiffsXiidm = DifferenceEvaluators.chain(
DifferenceEvaluators.Default,
ExportXmlCompare::ignoringCgmesSshMetadataId);
test(CgmesConformity1Catalog.microGridBaseCaseBE().dataSource(), 2, knownDiffsSsh, knownDiffsXiidm);
}

@Test
void microGridBEWithHiddenTapChangers() throws IOException, XMLStreamException {
DifferenceEvaluator knownDiffs = DifferenceEvaluators.chain(
DifferenceEvaluator knownDiffsSsh = DifferenceEvaluators.chain(
ExportXmlCompare::sameScenarioTime,
ExportXmlCompare::ensuringIncreasedModelVersion,
ExportXmlCompare::ignoringSynchronousMachinesSVCsWithTargetDeadband,
ExportXmlCompare::ignoringJunctionOrBusbarTerminals);
test(CgmesConformity1ModifiedCatalog.microGridBaseCaseBEHiddenTapChangers().dataSource(), 2, knownDiffs);
DifferenceEvaluator knownDiffsXiidm = DifferenceEvaluators.chain(
DifferenceEvaluators.Default,
ExportXmlCompare::ignoringCgmesSshMetadataId);
test(CgmesConformity1ModifiedCatalog.microGridBaseCaseBEHiddenTapChangers().dataSource(), 2, knownDiffsSsh, knownDiffsXiidm);
}

@Test
void microGridBEWithSharedRegulatingControl() throws IOException, XMLStreamException {
DifferenceEvaluator knownDiffs = DifferenceEvaluators.chain(
DifferenceEvaluator knownDiffsSsh = DifferenceEvaluators.chain(
ExportXmlCompare::sameScenarioTime,
ExportXmlCompare::ensuringIncreasedModelVersion,
ExportXmlCompare::ignoringJunctionOrBusbarTerminals);
test(CgmesConformity1ModifiedCatalog.microGridBaseCaseBESharedRegulatingControl().dataSource(), 2, knownDiffs);
DifferenceEvaluator knownDiffsXiidm = DifferenceEvaluators.chain(
DifferenceEvaluators.Default,
ExportXmlCompare::ignoringCgmesSshMetadataId);
test(CgmesConformity1ModifiedCatalog.microGridBaseCaseBESharedRegulatingControl().dataSource(), 2, knownDiffsSsh, knownDiffsXiidm);
}

@Test
Expand All @@ -81,6 +91,7 @@ void smallGrid() throws IOException, XMLStreamException {
test(CgmesConformity1Catalog.smallBusBranch().dataSource(), 4, knownDiffs, DifferenceEvaluators.chain(
DifferenceEvaluators.Default,
ExportXmlCompare::numericDifferenceEvaluator,
ExportXmlCompare::ignoringCgmesSshMetadataId,
ExportXmlCompare::ignoringControlAreaNetInterchange));
}

Expand All @@ -94,6 +105,7 @@ void smallGridHVDC() throws IOException, XMLStreamException {
DifferenceEvaluators.Default,
ExportXmlCompare::numericDifferenceEvaluator,
ExportXmlCompare::ignoringControlAreaNetInterchange,
ExportXmlCompare::ignoringCgmesSshMetadataId,
ExportXmlCompare::ignoringHvdcLinePmax));
}

Expand Down Expand Up @@ -222,4 +234,111 @@ void add(String shuntCompensatorId, int sections, boolean controlEnabled) {
map.put(shuntCompensatorId, Pair.of(sections, controlEnabled));
}
}

@Test
void testUpdateControlArea() throws IOException {
Path outputPath = tmpDir.resolve("update-control-areas");
Files.createDirectories(outputPath);

// Read network and check control area data
Network be = Network.read(CgmesConformity3Catalog.microGridBaseCaseBE().dataSource());
CgmesControlAreas controlAreas = be.getExtension(CgmesControlAreas.class);
assertNotNull(controlAreas);
System.out.println("cgmes control areas = " + controlAreas);
assertFalse(controlAreas.getCgmesControlAreas().isEmpty());
CgmesControlArea controlArea = controlAreas.getCgmesControlAreas().iterator().next();
assertEquals(236.9798, controlArea.getNetInterchange(), 1e-10);
assertEquals(10, controlArea.getPTolerance(), 1e-10);

// Update control area data
controlArea.setNetInterchange(controlArea.getNetInterchange() * 2);
controlArea.setPTolerance(controlArea.getPTolerance() / 2);

// Write and read the network to check serialization of the extension
Path updatedXiidm = outputPath.resolve("BE-updated.xiidm");
be.write("XIIDM", null, updatedXiidm);
Network beUpdated = Network.read(updatedXiidm);

// Export only SSH instance file
Properties exportParams = new Properties();
exportParams.put(CgmesExport.PROFILES, "SSH");
beUpdated.write("CGMES", exportParams, outputPath.resolve("BE"));

// Check that exported SSH contains updated values for net interchange and p tolerance
Collection<SshExportedControlArea> sshExportedControlAreas = readSshControlAreas(outputPath.resolve("BE_SSH.xml"));
assertFalse(sshExportedControlAreas.isEmpty());
SshExportedControlArea sshExportedControlArea = sshExportedControlAreas.iterator().next();
assertEquals(473.9596, sshExportedControlArea.netInterchange, 1e-10);
assertEquals(5, sshExportedControlArea.pTolerance, 1e-10);

// Check that SSH full model contains a reference to the original SSH that is superseding
String modelSupersedes = readSshModelSupersedes(outputPath.resolve("BE_SSH.xml"));
assertEquals("urn:uuid:1b092ff0-f8a0-49da-82d3-75eff5f1e820", modelSupersedes);
}

static class SshExportedControlArea {
String id;
double netInterchange;
double pTolerance;
}

private static final String ATTR_ABOUT = "about";
private static final String CONTROL_AREA = "ControlArea";
private static final String CONTROL_AREA_NET_INTERCHANGE = "ControlArea.netInterchange";
private static final String CONTROL_AREA_P_TOLERANCE = "ControlArea.pTolerance";

private static Collection<SshExportedControlArea> readSshControlAreas(Path ssh) {
List<SshExportedControlArea> sshExportedControlAreas = new ArrayList<>();
SshExportedControlArea sshExportedControlArea = null;
try (InputStream is = Files.newInputStream(ssh)) {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(is);
while (reader.hasNext()) {
int next = reader.next();
if (next == XMLStreamConstants.START_ELEMENT) {
if (reader.getLocalName().equals(CONTROL_AREA)) {
sshExportedControlArea = new SshExportedControlArea();
sshExportedControlArea.id = reader.getAttributeValue(CgmesNamespace.RDF_NAMESPACE, ATTR_ABOUT).substring(2);
} else if (reader.getLocalName().equals(CONTROL_AREA_NET_INTERCHANGE) && sshExportedControlArea != null) {
sshExportedControlArea.netInterchange = Double.parseDouble(reader.getElementText());
} else if (reader.getLocalName().equals(CONTROL_AREA_P_TOLERANCE) && sshExportedControlArea != null) {
sshExportedControlArea.pTolerance = Double.parseDouble(reader.getElementText());
}
} else if (next == XMLStreamConstants.END_ELEMENT) {
if (reader.getLocalName().equals(CONTROL_AREA) && sshExportedControlArea != null) {
sshExportedControlAreas.add(sshExportedControlArea);
sshExportedControlArea = null;
}
}
}
reader.close();
} catch (XMLStreamException | IOException e) {
throw new RuntimeException(e);
}
return sshExportedControlAreas;
}

private static final String ATTR_RESOURCE = "resource";
private static final String MODEL_SUPERSEDES = "Model.Supersedes";

private static String readSshModelSupersedes(Path ssh) {
String modelSupersedes;
try (InputStream is = Files.newInputStream(ssh)) {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(is);
while (reader.hasNext()) {
int next = reader.next();
if (next == XMLStreamConstants.START_ELEMENT) {
if (reader.getLocalName().equals(MODEL_SUPERSEDES)) {
modelSupersedes = reader.getAttributeValue(CgmesNamespace.RDF_NAMESPACE, ATTR_RESOURCE);
reader.close();
return modelSupersedes;
}
}
}
reader.close();
} catch (XMLStreamException | IOException e) {
throw new RuntimeException(e);
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
package com.powsybl.cgmes.extensions;

import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.Boundary;
import com.powsybl.iidm.network.Terminal;

Expand All @@ -31,4 +32,16 @@ public interface CgmesControlArea {
Set<Terminal> getTerminals();

Set<Boundary> getBoundaries();

default void setNetInterchange(double netInterchange) {
throw new PowsyblException("Unsupported method");
}

default double getPTolerance() {
throw new PowsyblException("Unsupported method");
}

default void setPTolerance(double pTolerance) {
throw new PowsyblException("Unsupported method");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/
package com.powsybl.cgmes.extensions;

import com.powsybl.commons.PowsyblException;

/**
* @author Miora Ralambotiana <miora.ralambotiana at rte-france.com>
*/
Expand All @@ -19,5 +21,9 @@ public interface CgmesControlAreaAdder {

CgmesControlAreaAdder setNetInterchange(double netInterchange);

default CgmesControlAreaAdder setPTolerance(double pTolerance) {
throw new PowsyblException("Unsupported method");
}

CgmesControlArea add();
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CgmesControlAreaAdderImpl implements CgmesControlAreaAdder {
private String name;
private String energyIdentificationCodeEic;
private double netInterchange = Double.NaN;
private double pTolerance = Double.NaN;

CgmesControlAreaAdderImpl(CgmesControlAreasImpl mapping) {
this.mapping = Objects.requireNonNull(mapping);
Expand Down Expand Up @@ -49,11 +50,17 @@ public CgmesControlAreaAdder setNetInterchange(double netInterchange) {
return this;
}

@Override
public CgmesControlAreaAdder setPTolerance(double pTolerance) {
this.pTolerance = pTolerance;
return this;
}

@Override
public CgmesControlAreaImpl add() {
if (id == null) {
throw new PowsyblException("Undefined ID for CGMES control area");
}
return new CgmesControlAreaImpl(id, name, energyIdentificationCodeEic, netInterchange, mapping);
return new CgmesControlAreaImpl(id, name, energyIdentificationCodeEic, netInterchange, pTolerance, mapping);
}
}
Loading