StateVariablesExportTest.java
/**
* Copyright (c) 2020, 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.cgmes.conversion.test.export;
import com.powsybl.cgmes.conformity.*;
import com.powsybl.cgmes.conversion.CgmesExport;
import com.powsybl.cgmes.conversion.CgmesImport;
import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.cgmes.conversion.export.CgmesExportContext;
import com.powsybl.cgmes.conversion.export.StateVariablesExport;
import com.powsybl.cgmes.conversion.export.TopologyExport;
import com.powsybl.cgmes.conversion.test.ConversionUtil;
import com.powsybl.cgmes.model.CgmesNames;
import com.powsybl.cgmes.model.CgmesNamespace;
import com.powsybl.cgmes.model.PowerFlow;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.datasource.ReadOnlyDataSource;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.commons.xml.XmlUtil;
import com.powsybl.computation.DefaultComputationManagerConfig;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.ReferencePriorities;
import com.powsybl.iidm.network.extensions.ReferenceTerminals;
import com.powsybl.iidm.network.extensions.SlackTerminal;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.serde.ExportOptions;
import com.powsybl.iidm.serde.NetworkSerDe;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.loadflow.resultscompletion.LoadFlowResultsCompletion;
import com.powsybl.loadflow.resultscompletion.LoadFlowResultsCompletionParameters;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.xml.stream.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Miora Ralambotiana {@literal <miora.ralambotiana at rte-france.com>}
*/
class StateVariablesExportTest extends AbstractSerDeTest {
private Properties importParams;
@Override
@BeforeEach
public void setUp() throws IOException {
super.setUp();
importParams = new Properties();
importParams.put(CgmesImport.IMPORT_CGM_WITH_SUBNETWORKS, "false");
}
@Test
void microGridBE() throws IOException, XMLStreamException {
assertTrue(test(CgmesConformity1Catalog.microGridBaseCaseBE().dataSource(),
false,
false,
StateVariablesExportTest::addRepackagerFiles));
}
@Test
void microGridBEFlowsForSwitches() throws IOException, XMLStreamException {
// Activate export of flows for switches on a small network with few switches,
// Writing flows for all switches has impact on performance
assertTrue(test(CgmesConformity1Catalog.microGridBaseCaseNL().dataSource(),
false,
true,
StateVariablesExportTest::addRepackagerFiles));
}
@Test
void minimalNodeBreakerFlowsForSwitches() throws XMLStreamException {
Network n = Network.create("minimal", "iidm");
Substation s1 = n.newSubstation().setId("S1").add();
Substation s2 = n.newSubstation().setId("S2").add();
VoltageLevel vl1 = s1.newVoltageLevel().setId("VL1").setNominalV(100).setTopologyKind(TopologyKind.NODE_BREAKER).add();
vl1.newLoad().setId("LOAD").setNode(0)
.setP0(10).setQ0(1).add();
vl1.getNodeBreakerView().newBreaker().setId("BK11").setNode1(0).setNode2(1).add();
vl1.getNodeBreakerView().newBusbarSection().setId("BBS1").setNode(1).add();
vl1.getNodeBreakerView().newBreaker().setId("BK12").setNode1(1).setNode2(2).add();
VoltageLevel vl2 = s2.newVoltageLevel().setId("VL2").setNominalV(100).setTopologyKind(TopologyKind.NODE_BREAKER).add();
vl2.newGenerator().setId("GEN").setNode(0)
.setTargetP(10.01).setTargetQ(1.10)
.setMinP(0).setMaxP(20)
.setVoltageRegulatorOn(false).add();
vl2.getNodeBreakerView().newBreaker().setId("BK21").setNode1(0).setNode2(1).add();
vl2.getNodeBreakerView().newBusbarSection().setId("BBS2").setNode(1).add();
vl2.getNodeBreakerView().newBreaker().setId("BK22").setNode1(1).setNode2(2).add();
n.newLine().setId("LINE").setVoltageLevel1("VL1").setVoltageLevel2("VL2").setNode1(2).setNode2(2)
.setR(1).setX(10).add();
Bus b1 = vl1.getBusView().getBuses().iterator().next();
Bus b2 = vl2.getBusView().getBuses().iterator().next();
b2.setV(100.0).setAngle(0);
b1.setV(99.7946677).setAngle(-0.568404640);
new LoadFlowResultsCompletion(new LoadFlowResultsCompletionParameters(), new LoadFlowParameters()).run(n, null);
String sv = exportSvAsString(n, true);
assertEqualsPowerFlow(new PowerFlow(10, 1), extractSvPowerFlow(sv, cgmesTerminal(n, "LOAD", 1)));
assertEqualsPowerFlow(new PowerFlow(-10, -1), extractSvPowerFlow(sv, cgmesTerminal(n, "BK11", 1)));
assertEqualsPowerFlow(new PowerFlow(10, 1), extractSvPowerFlow(sv, cgmesTerminal(n, "BK11", 2)));
assertEqualsPowerFlow(new PowerFlow(-10, -1), extractSvPowerFlow(sv, cgmesTerminal(n, "BK12", 1)));
assertEqualsPowerFlow(new PowerFlow(10, 1), extractSvPowerFlow(sv, cgmesTerminal(n, "BK12", 2)));
assertEqualsPowerFlow(new PowerFlow(-10, -1), extractSvPowerFlow(sv, cgmesTerminal(n, "LINE", 1)));
assertEqualsPowerFlow(new PowerFlow(10.01, 1.1), extractSvPowerFlow(sv, cgmesTerminal(n, "LINE", 2)));
assertEqualsPowerFlow(new PowerFlow(-10.01, -1.1), extractSvPowerFlow(sv, cgmesTerminal(n, "GEN", 1)));
assertEqualsPowerFlow(new PowerFlow(10.01, 1.1), extractSvPowerFlow(sv, cgmesTerminal(n, "BK21", 1)));
assertEqualsPowerFlow(new PowerFlow(-10.01, -1.1), extractSvPowerFlow(sv, cgmesTerminal(n, "BK21", 2)));
assertEqualsPowerFlow(new PowerFlow(10.01, 1.1), extractSvPowerFlow(sv, cgmesTerminal(n, "BK22", 1)));
assertEqualsPowerFlow(new PowerFlow(-10.01, -1.1), extractSvPowerFlow(sv, cgmesTerminal(n, "BK22", 2)));
}
private static void assertEqualsPowerFlow(PowerFlow expected, PowerFlow actual) {
final double epsilon = 1e-2;
assertEquals(expected.p(), actual.p(), epsilon);
assertEquals(expected.q(), actual.q(), epsilon);
}
private static String cgmesTerminal(Network n, String id, int terminal) {
return n.getIdentifiable(id)
.getAliasFromType(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.TERMINAL + terminal)
.orElseThrow();
}
private static PowerFlow extractSvPowerFlow(String sv, String terminalId) {
Pattern p = Pattern.compile(
"<cim:SvPowerFlow.p>([\\d.\\-]*)</cim:SvPowerFlow.p>\\s*.\\s*" +
"<cim:SvPowerFlow.q>([\\d.\\-]*)</cim:SvPowerFlow.q>\\s*.\\s*" +
"<cim:SvPowerFlow.Terminal.rdf.resource..#_" + terminalId,
Pattern.DOTALL);
Matcher m = p.matcher(sv);
assertTrue(m.find());
return new PowerFlow(Double.parseDouble(m.group(1)), Double.parseDouble(m.group(2)));
}
@Test
void microGridAssembled() throws IOException, XMLStreamException {
assertTrue(test(CgmesConformity1Catalog.microGridBaseCaseAssembled().dataSource(),
false,
false,
r -> {
addRepackagerFiles("NL", r);
addRepackagerFiles("BE", r);
}));
}
@Test
void smallGridBusBranch() throws IOException, XMLStreamException {
assertTrue(test(CgmesConformity1Catalog.smallBusBranch().dataSource(),
false,
false,
StateVariablesExportTest::addRepackagerFiles));
}
@Test
void smallGridNodeBreakerHVDC() throws IOException, XMLStreamException {
assertTrue(test(CgmesConformity1Catalog.smallNodeBreakerHvdc().dataSource(),
true,
false,
StateVariablesExportTest::addRepackagerFilesExcludeTp));
}
@Test
void smallGridNodeBreaker() throws IOException, XMLStreamException {
assertTrue(test(CgmesConformity1Catalog.smallNodeBreaker().dataSource(),
true,
false,
StateVariablesExportTest::addRepackagerFilesExcludeTp));
}
@Test
void miniBusBranchWithSvInjection() throws IOException, XMLStreamException {
assertTrue(test(CgmesConformity1ModifiedCatalog.smallBusBranchWithSvInjection().dataSource(),
false,
false,
StateVariablesExportTest::addRepackagerFiles));
}
@Test
void miniBusBranchWithSvInjectionExportPQ() throws XMLStreamException {
Network network = importNetwork(CgmesConformity1ModifiedCatalog.smallBusBranchWithSvInjection().dataSource());
String loadId = "0448d86a-c766-11e1-8775-005056c00008";
Load load = network.getLoad(loadId);
String cgmesTerminal = getCgmesTerminal(load.getTerminal());
// Only when P and Q are NaN is not exported
load.getTerminal().setP(-0.12);
load.getTerminal().setQ(-13.03);
String sv = exportSvAsString(network);
assertTrue(sv.contains(cgmesTerminal));
load.getTerminal().setP(Double.NaN);
load.getTerminal().setQ(-13.03);
String sv1 = exportSvAsString(network);
assertTrue(sv1.contains(cgmesTerminal));
load.getTerminal().setP(-0.12);
load.getTerminal().setQ(Double.NaN);
String sv2 = exportSvAsString(network);
assertTrue(sv2.contains(cgmesTerminal));
}
@Test
void miniBusBranchWithSvInjectionExportQ() throws XMLStreamException {
Network network = importNetwork(CgmesConformity1ModifiedCatalog.smallBusBranchWithSvInjection().dataSource());
String shuntCompensatorId = "04553478-c766-11e1-8775-005056c00008";
ShuntCompensator shuntCompensator = network.getShuntCompensator(shuntCompensatorId);
String cgmesTerminal = getCgmesTerminal(shuntCompensator.getTerminal());
// If P and Q both are NaN is not exported
shuntCompensator.getTerminal().setQ(-13.03);
String sv = exportSvAsString(network);
assertTrue(sv.contains(cgmesTerminal));
}
@Test
void microGridBEWithHiddenTapChangers() throws XMLStreamException {
Network network = importNetwork(CgmesConformity1ModifiedCatalog.microGridBaseCaseBEHiddenTapChangers().dataSource());
String sv = exportSvAsString(network);
String hiddenTapChangerId = "_6ebbef67-3061-4236-a6fd-6ccc4595f6c3-x";
assertTrue(sv.contains(hiddenTapChangerId));
}
@Test
void equivalentShuntTest() throws XMLStreamException {
ReadOnlyDataSource ds = CgmesConformity1ModifiedCatalog.microGridBaseCaseBEEquivalentShunt().dataSource();
Network network = new CgmesImport().importData(ds, NetworkFactory.findDefault(), importParams);
String sv = exportSvAsString(network);
String equivalentShuntId = "d771118f-36e9-4115-a128-cc3d9ce3e3da";
assertNotNull(network.getShuntCompensator(equivalentShuntId));
SvShuntCompensatorSections svShuntCompensatorSections = readSvShuntCompensatorSections(sv);
assertFalse(svShuntCompensatorSections.map.isEmpty());
assertFalse(svShuntCompensatorSections.map.containsKey(equivalentShuntId));
}
private static SvShuntCompensatorSections readSvShuntCompensatorSections(String sv) {
final String svShuntCompensatorSections = "SvShuntCompensatorSections";
final String svShuntCompensatorSectionsSections = "SvShuntCompensatorSections.sections";
final String svShuntCompensatorSectionsShuntCompensator = "SvShuntCompensatorSections.ShuntCompensator";
final String attrResource = "resource";
SvShuntCompensatorSections svdata = new SvShuntCompensatorSections();
try (InputStream is = new ByteArrayInputStream(sv.getBytes(StandardCharsets.UTF_8))) {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(is);
Integer sections = null;
String shuntCompensatorId = null;
while (reader.hasNext()) {
int next = reader.next();
if (next == XMLStreamConstants.START_ELEMENT) {
if (reader.getLocalName().equals(svShuntCompensatorSections)) {
sections = null;
shuntCompensatorId = null;
} else if (reader.getLocalName().equals(svShuntCompensatorSectionsSections)) {
String text = reader.getElementText();
sections = Integer.parseInt(text);
} else if (reader.getLocalName().equals(svShuntCompensatorSectionsShuntCompensator)) {
shuntCompensatorId = reader.getAttributeValue(CgmesNamespace.RDF_NAMESPACE, attrResource).substring(2);
}
} else if (next == XMLStreamConstants.END_ELEMENT) {
if (reader.getLocalName().equals(svShuntCompensatorSections) && sections != null) {
svdata.add(shuntCompensatorId, sections);
}
}
}
reader.close();
} catch (XMLStreamException | IOException e) {
throw new RuntimeException(e);
}
return svdata;
}
private static final class SvShuntCompensatorSections {
private final Map<String, Integer> map = new HashMap<>();
void add(String shuntCompensatorId, int sections) {
map.put(shuntCompensatorId, sections);
}
}
@Test
void cgmes3MiniGridwithTransformersWithRtcAndPtc() throws XMLStreamException {
Network network = ConversionUtil.networkModel(Cgmes3Catalog.miniGrid(), new Conversion.Config());
// Add a PTC
TwoWindingsTransformer t2wt = network.getTwoWindingsTransformer("813365c3-5be7-4ef0-a0a7-abd1ae6dc174");
t2wt.newPhaseTapChanger()
.setLowTapPosition(0)
.setTapPosition(0)
.beginStep()
.setR(0.0)
.setX(0.0)
.setB(0)
.setG(0)
.setRho(1.0)
.setAlpha(20)
.endStep()
.add();
// We have added a PTC, we add a known alias for it
// This is not mandatory, but it simplifies comparing the data in the network and the exported SV file
// If we do not add it in advance, a proper CGMES ID for the new PTC would have been generated during export
String t2ptcId = "ptc2w";
t2wt.addAlias(t2ptcId, Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.PHASE_TAP_CHANGER + 1);
// Also, change the RTC tap position and add a PTC to a three-winding transformer
ThreeWindingsTransformer t3wt = network.getThreeWindingsTransformer("411b5401-0a43-404a-acb4-05c3d7d0c95c");
t3wt.getLeg1().getRatioTapChanger()
.setTapPosition(16);
t3wt.getLeg1().newPhaseTapChanger()
.setLowTapPosition(0)
.setTapPosition(1)
.beginStep()
.setR(0.0)
.setX(0.0)
.setB(0)
.setG(0)
.setRho(1.0)
.setAlpha(10)
.endStep()
.beginStep()
.setR(0.0)
.setX(0.0)
.setB(0)
.setG(0)
.setRho(1.0)
.setAlpha(20)
.endStep()
.add();
String t3ptcId = "ptc3w";
t3wt.addAlias(t3ptcId, Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.PHASE_TAP_CHANGER + 1);
String expected = buildNetworkSvTapStepsString(network);
String sv = exportSvAsString(network);
String actual = readSvTapSteps(sv).toSortedString();
assertEquals(expected, actual);
}
@Test
void testTopologicalIslandSolvedNodeBreaker() {
Network network = Network.read(CgmesConformity3Catalog.microGridBaseCaseNL().dataSource());
Path outputPath = fileSystem.getPath("tmp-grid");
Path outputSv = fileSystem.getPath("tmp-grid_SV.xml");
Properties parameters = new Properties();
parameters.setProperty(CgmesExport.EXPORT_LOAD_FLOW_STATUS, "true");
setReferenceTerminalsFromReferencePriority(network);
network.write("CGMES", parameters, outputPath);
assertEquals("converged", readFirstTopologicalIslandDescription(outputSv));
}
@Test
void testTopologicalIslandSolvedBusBranch() {
Network network = Network.read(CgmesConformity1Catalog.miniBusBranch().dataSource());
new LoadFlowResultsCompletion().run(network, null);
Path outputPath = fileSystem.getPath("tmp-grid");
Path outputSv = fileSystem.getPath("tmp-grid_SV.xml");
Properties parameters = new Properties();
parameters.setProperty(CgmesExport.EXPORT_LOAD_FLOW_STATUS, "true");
setReferenceTerminalsFromReferencePriority(network);
network.write("CGMES", parameters, outputPath);
assertEquals("converged", readFirstTopologicalIslandDescription(outputSv));
parameters.setProperty(CgmesExport.MAX_P_MISMATCH_CONVERGED, "0.000001");
network.write("CGMES", parameters, outputPath);
assertEquals("diverged", readFirstTopologicalIslandDescription(outputSv));
}
@Test
void testDisconnectedGeneratorWithReferenceTerminal() {
// Create a small network
Network network = Network.create("network", "iidm");
Substation s = network.newSubstation().setId("S").add();
VoltageLevel vl = s.newVoltageLevel().setId("VL").setNominalV(400.0).setTopologyKind(TopologyKind.BUS_BREAKER).add();
vl.getBusBreakerView().newBus().setId("B").add().setV(400).setAngle(0);
vl.newLoad().setId("L").setConnectableBus("B").setBus("B").setP0(100.0).setQ0(0.0).add();
Generator g = vl.newGenerator().setId("G").setBus("B").setMaxP(100.0).setMinP(50.0).setTargetP(100.0).setTargetV(400.0).setVoltageRegulatorOn(true).add();
// Set reference terminal
ReferenceTerminals.addTerminal(g.getTerminal());
// Disconnect the generator
Terminal t = g.getTerminal();
assertTrue(t.disconnect());
assertFalse(t.isConnected());
assertNull(t.getBusView().getBus());
// Verify in the output file that no TopologicalIsland was created
Path outputPath = fileSystem.getPath("tmp-referenceTerminal");
Path outputSv = fileSystem.getPath("tmp-referenceTerminal_SV.xml");
network.write("CGMES", new Properties(), outputPath);
assertEquals("", readFirstTopologicalIslandDescription(outputSv));
}
private static String readFirstTopologicalIslandDescription(Path sv) {
String description = "";
boolean insideTopologicalIsland = false;
try (InputStream is = Files.newInputStream(sv)) {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(is);
while (reader.hasNext()) {
int token = reader.next();
if (token == XMLStreamConstants.START_ELEMENT) {
// Retrieve the TopologicalIsland node
if (reader.getLocalName().equals(CgmesNames.TOPOLOGICAL_ISLAND)) {
insideTopologicalIsland = true;
}
if (insideTopologicalIsland && reader.getLocalName().equals(CgmesNames.IDENTIFIED_OBJECT_DESCRIPTION)) {
description = reader.getElementText();
}
} else if (token == XMLStreamConstants.END_ELEMENT && reader.getLocalName().equals(CgmesNames.TOPOLOGICAL_ISLAND)) {
break;
}
}
reader.close();
} catch (IOException | XMLStreamException e) {
throw new RuntimeException(e);
}
return description;
}
private static void setReferenceTerminalsFromReferencePriority(Network network) {
// Assume all terminals with reference priority > 0 are set as angle reference terminals by the load flow
ReferencePriorities.get(network)
.stream()
.filter(p -> p.getPriority() > 0)
.forEach(p -> ReferenceTerminals.addTerminal(p.getTerminal()));
}
private static String buildNetworkSvTapStepsString(Network network) {
SvTapSteps svTapSteps = new SvTapSteps();
network.getTwoWindingsTransformers().forEach(twt -> {
twt.getOptionalRatioTapChanger().ifPresent(rtc -> svTapSteps.add(getTapChangerId(twt, CgmesNames.RATIO_TAP_CHANGER), rtc.getTapPosition()));
twt.getOptionalPhaseTapChanger().ifPresent(ptc -> svTapSteps.add(getTapChangerId(twt, CgmesNames.PHASE_TAP_CHANGER), ptc.getTapPosition()));
});
network.getThreeWindingsTransformers().forEach(twt -> {
twt.getLeg1().getOptionalRatioTapChanger().ifPresent(rtc -> svTapSteps.add(getTapChangerId(twt, CgmesNames.RATIO_TAP_CHANGER, 1), rtc.getTapPosition()));
twt.getLeg1().getOptionalPhaseTapChanger().ifPresent(ptc -> svTapSteps.add(getTapChangerId(twt, CgmesNames.PHASE_TAP_CHANGER, 1), ptc.getTapPosition()));
twt.getLeg2().getOptionalRatioTapChanger().ifPresent(rtc -> svTapSteps.add(getTapChangerId(twt, CgmesNames.RATIO_TAP_CHANGER, 2), rtc.getTapPosition()));
twt.getLeg2().getOptionalPhaseTapChanger().ifPresent(ptc -> svTapSteps.add(getTapChangerId(twt, CgmesNames.PHASE_TAP_CHANGER, 2), ptc.getTapPosition()));
twt.getLeg3().getOptionalRatioTapChanger().ifPresent(rtc -> svTapSteps.add(getTapChangerId(twt, CgmesNames.RATIO_TAP_CHANGER, 3), rtc.getTapPosition()));
twt.getLeg3().getOptionalPhaseTapChanger().ifPresent(ptc -> svTapSteps.add(getTapChangerId(twt, CgmesNames.PHASE_TAP_CHANGER, 3), ptc.getTapPosition()));
});
return svTapSteps.toSortedString();
}
private static String getTapChangerId(TwoWindingsTransformer twt, String baseAliasType) {
// For two winding transformers the CGMES tap changer id may be stored with suffix 1 or 2,
// depending on its original location in CGMES model
String aliasType1 = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + baseAliasType + 1;
String aliasType2 = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + baseAliasType + 2;
return twt.getAliasFromType(aliasType1)
.or(() -> twt.getAliasFromType(aliasType2))
.orElseThrow(() -> new PowsyblException("Missing alias " + aliasType1 + " or " + aliasType2));
}
private static String getTapChangerId(ThreeWindingsTransformer twt, String baseAliasType, int leg) {
// For three winding transformers, the tap changer id has to be stored in the alias number corresponding to the leg
String aliasType = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + baseAliasType + leg;
return twt.getAliasFromType(aliasType)
.orElseThrow(() -> new PowsyblException("Missing alias " + aliasType));
}
private static SvTapSteps readSvTapSteps(String sv) {
final String svTapStep = "SvTapStep";
final String svTapStepPosition = "SvTapStep.position";
final String svTapStepTapChanger = "SvTapStep.TapChanger";
final String attrResource = "resource";
SvTapSteps svTapSteps = new SvTapSteps();
try (InputStream is = new ByteArrayInputStream(sv.getBytes(StandardCharsets.UTF_8))) {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(is);
Integer position = null;
String tapChangerId = null;
while (reader.hasNext()) {
int next = reader.next();
if (next == XMLStreamConstants.START_ELEMENT) {
if (reader.getLocalName().equals(svTapStep)) {
position = null;
tapChangerId = null;
} else if (reader.getLocalName().equals(svTapStepPosition)) {
String text = reader.getElementText();
position = Integer.parseInt(text);
} else if (reader.getLocalName().equals(svTapStepTapChanger)) {
tapChangerId = reader.getAttributeValue(CgmesNamespace.RDF_NAMESPACE, attrResource).substring(2);
}
} else if (next == XMLStreamConstants.END_ELEMENT) {
if (reader.getLocalName().equals(svTapStep) && position != null) {
svTapSteps.add(tapChangerId, position);
}
}
}
reader.close();
} catch (XMLStreamException | IOException e) {
throw new RuntimeException(e);
}
return svTapSteps;
}
private static final class SvTapSteps {
private final Map<String, Integer> svTapSteps = new HashMap<>();
void add(String tapChangerId, int position) {
svTapSteps.put(tapChangerId, position);
}
String toSortedString() {
return svTapSteps.entrySet().stream()
.map(e -> String.format("%50s %05d", e.getKey(), e.getValue()))
.sorted()
.collect(Collectors.joining("\n"));
}
}
private static Network importNetwork(ReadOnlyDataSource ds) {
Properties importParams = new Properties();
importParams.put("iidm.import.cgmes.create-cgmes-export-mapping", "true");
importParams.put(CgmesImport.IMPORT_CGM_WITH_SUBNETWORKS, "false");
return new CgmesImport().importData(ds, NetworkFactory.findDefault(), importParams);
}
private String exportSvAsString(Network network) throws XMLStreamException {
return exportSvAsString(network, false);
}
private String exportSvAsString(Network network, boolean exportFlowsForSwitches) throws XMLStreamException {
CgmesExportContext context = new CgmesExportContext(network);
StringWriter stringWriter = new StringWriter();
XMLStreamWriter writer = XmlUtil.initializeWriter(true, " ", stringWriter);
context.setExportBoundaryPowerFlows(true);
context.setExportFlowsForSwitches(exportFlowsForSwitches);
StateVariablesExport.write(network, writer, context);
return stringWriter.toString();
}
private static String getCgmesTerminal(Terminal terminal) {
return ((Connectable<?>) terminal.getConnectable()).getAliasFromType(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.TERMINAL1).orElse(null);
}
private static void addRepackagerFiles(String tso, Repackager repackager) {
repackager.with("test_" + tso + "_EQ.xml", name -> name.contains(tso) && name.contains("EQ"))
.with("test_" + tso + "_TP.xml", name -> name.contains(tso) && name.contains("TP"))
.with("test_" + tso + "_SSH.xml", name -> name.contains(tso) && name.contains("SSH"));
}
private static void addRepackagerFiles(Repackager repackager) {
repackager.with("test_EQ.xml", Repackager::eq)
.with("test_TP.xml", Repackager::tp)
.with("test_SSH.xml", Repackager::ssh);
}
private static void addRepackagerFilesExcludeTp(Repackager repackager) {
repackager.with("test_EQ.xml", Repackager::eq)
.with("test_SSH.xml", Repackager::ssh);
}
private boolean test(ReadOnlyDataSource dataSource, boolean exportTp, boolean exportFlowsForSwitches, Consumer<Repackager> repackagerConsumer) throws XMLStreamException, IOException {
// Import original
importParams.put("iidm.import.cgmes.create-cgmes-export-mapping", "true");
Network expected0 = new CgmesImport().importData(dataSource, NetworkFactory.findDefault(), importParams);
// Ensure all information in IIDM mapping extensions is created
// Some mappings are not built until export is requested
new CgmesExportContext().addIidmMappings(expected0);
// Export to XIIDM and re-import to test serialization of CGMES-IIDM extension
NetworkSerDe.write(expected0, tmpDir.resolve("temp.xiidm"));
Network expected = NetworkSerDe.read(tmpDir.resolve("temp.xiidm"));
// Export SV
CgmesExportContext context = new CgmesExportContext(expected);
context.setExportBoundaryPowerFlows(true);
context.setExportFlowsForSwitches(exportFlowsForSwitches);
context.setExportSvInjectionsForSlacks(false);
Path exportedSv = tmpDir.resolve("exportedSv.xml");
try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(exportedSv))) {
XMLStreamWriter writer = XmlUtil.initializeWriter(true, " ", os);
StateVariablesExport.write(expected, writer, context);
}
// Export TP if required (node/breaker models require an export of TP in addition to SV file)
Path exportedTp = tmpDir.resolve("exportedTp.xml");
if (exportTp) {
try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(exportedTp))) {
XMLStreamWriter writer = XmlUtil.initializeWriter(true, " ", os);
TopologyExport.write(expected, writer, context);
}
}
// Zip with new SV (and eventually a new TP)
Path repackaged = tmpDir.resolve("repackaged.zip");
Repackager r = new Repackager(dataSource)
.with("test_SV.xml", exportedSv)
.with("test_EQ_BD.xml", Repackager::eqBd)
.with("test_TP_BD.xml", Repackager::tpBd);
if (exportTp) {
r.with("test_TP.xml", exportedTp);
}
repackagerConsumer.accept(r);
r.zip(repackaged);
// Import with new SV
Network actual = Network.read(repackaged,
DefaultComputationManagerConfig.load().createShortTimeExecutionComputationManager(), ImportConfig.load(), importParams);
// Before comparison, set undefined p/q in expected network at 0.0
expected.getConnectableStream()
.filter(c -> !(c instanceof BusbarSection))
.filter(c -> !(c instanceof HvdcConverterStation))
.flatMap(c -> (Stream<Terminal>) c.getTerminals().stream())
.filter(t -> Double.isNaN(t.getP()) && Double.isNaN(t.getQ()))
.forEach(t -> t.setP(0.0).setQ(0.0));
// Export original and with new SV
// comparison without extensions, only Networks
ExportOptions exportOptions = new ExportOptions().setSorted(true);
exportOptions.setExtensions(Collections.emptySet());
Path expectedPath = tmpDir.resolve("expected.xml");
Path actualPath = tmpDir.resolve("actual.xml");
NetworkSerDe.write(expected, exportOptions, expectedPath);
NetworkSerDe.write(actual, exportOptions, actualPath);
NetworkSerDe.validate(actualPath);
// Compare
return ExportXmlCompare.compareNetworks(expectedPath, actualPath);
}
@Test
void testDisconnectedTerminalForSlack() {
Network network = EurostagTutorialExample1Factory.createWithLFResults();
Pattern svInjectionPattern = Pattern.compile("cim:SvInjection.TopologicalNode rdf:resource=\"#(.*)\"/>");
// Set slack terminal
Terminal terminal = network.getGenerator("GEN").getTerminal();
SlackTerminal.reset(network.getVoltageLevel("VLGEN"), terminal);
// Export only the CGMES SV instance file and check that no SvInjection is present (slack bus is balanced)
String svBalanced = export(network, "tmp-slackTerminal-balanced").sv;
assertFalse(svInjectionPattern.matcher(svBalanced).find());
// Introduce a mismatch in the slack bus
terminal.setP(0);
// Export again, now an SvInjection must be present in the SV output (slack has a mismatch)
// And it should refer to a TopologicalNode present in the TP output
ExportedContent exportedMismatch = export(network, "tmp-slackTerminal-mismatch");
Matcher m = svInjectionPattern.matcher(exportedMismatch.sv);
assertTrue(exportedMismatch.sv.contains("cim:SvInjection.TopologicalNode"));
assertTrue(m.find());
String svInjectionTopologicalNode = m.group(1);
String tnDefinition = "cim:TopologicalNode rdf:ID=\"" + svInjectionTopologicalNode + "\"";
assertTrue(exportedMismatch.tp.contains(tnDefinition));
// Disconnect the generator
terminal.disconnect();
assertFalse(terminal.isConnected());
assertNull(terminal.getBusView().getBus());
// We still have a mismatch, but we do not have a slack bus to assign it, no SvInjection in the output
String svDisconnected = export(network, "tmp-slackTerminal-disconnected").sv;
assertFalse(svInjectionPattern.matcher(svDisconnected).find());
}
@Test
void testWriteBoundaryTnInTopologicalIsland() throws XMLStreamException {
Network network = Network.read(CgmesConformity1Catalog.microGridBaseCaseNL().dataSource());
Optional<? extends Terminal> terminal = network.getBusBreakerView().getBus("97d7d14a-7294-458f-a8d7-024700a08717").getConnectedTerminalStream().findFirst();
assertTrue(terminal.isPresent());
ReferenceTerminals.addTerminal(terminal.get());
String sv = exportSvAsString(network, false);
Pattern p = Pattern.compile("<cim:TopologicalIsland.TopologicalNodes rdf:resource=");
assertEquals(10, p.matcher(sv).results().count());
// 10 is the number of topological nodes in the island associated to buses and to dangling lines
assertEquals(5, network.getBusBreakerView().getBusStream().count());
assertEquals(5, network.getDanglingLineStream().count());
}
record ExportedContent(String sv, String tp) {
}
private ExportedContent export(Network network, String basename) {
Path outputPath = tmpDir.resolve(basename);
Properties exportParams = new Properties();
exportParams.put(CgmesExport.PROFILES, "SV,TP");
exportParams.put(CgmesExport.NAMING_STRATEGY, "cgmes");
network.write("CGMES", exportParams, outputPath);
try {
return new ExportedContent(
Files.readString(tmpDir.resolve(String.format("%s_SV.xml", basename))),
Files.readString(tmpDir.resolve(String.format("%s_TP.xml", basename)))
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}