HvdcConversionTest.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;
import static com.powsybl.cgmes.conversion.test.ConversionUtil.*;
import static com.powsybl.iidm.network.HvdcLine.ConvertersMode.*;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.iidm.network.*;
import org.junit.jupiter.api.Test;
import com.powsybl.cgmes.conversion.Conversion;
/**
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
* @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
* @author Romain Courtier {@literal <romain.courtier at rte-france.com>}
*/
class HvdcConversionTest extends AbstractSerDeTest {
private static final String DIR = "/issues/hvdc/";
private static final double TOLERANCE = 0.001;
@Test
void monopoleEqOnlyTest() {
// CGMES network:
// 2 HVDC monopole with ground return:
// - DCLineSegment DCL_12 with CsConverter CSC_1 and CSC_2 (LCC line).
// - DCLineSegment DCL_34 with VsConverter VSC_3 and VSC_4 (VSC line).
// IIDM network:
// - HvdcLine DCL_12 with LccConverterStation CSC_1 and CSC_2.
// - HvdcLine DCL_34 with VscConverterStation VSC_3 and VSC_4.
Network network = readCgmesResources(DIR, "monopole_EQ.xml");
// EQ contains the name of equipments and static values (DCLine resistances).
// Converter's loss factor, power factor, modes and line's active power setpoint and max value get default value.
assertContainsLccConverter(network, "CSC_1", "Current source converter 1", "DCL_12", 0.0, 0.8);
assertContainsLccConverter(network, "CSC_2", "Current source converter 2", "DCL_12", 0.0, 0.8);
assertContainsHvdcLine(network, "DCL_12", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 12", "CSC_1", "CSC_2", 4.64, 0.0, 0.0);
assertContainsVscConverter(network, "VSC_3", "Voltage source converter 3", "DCL_34", 0.0, Double.NaN, 0.0);
assertContainsVscConverter(network, "VSC_4", "Voltage source converter 4", "DCL_34", 0.0, Double.NaN, 0.0);
assertContainsHvdcLine(network, "DCL_34", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 34", "VSC_3", "VSC_4", 9.92, 0.0, 0.0);
}
@Test
void monopoleEqSshTest() {
// Same test as with EQ only, but this time the SSH is also read.
Network network = readCgmesResources(DIR, "monopole_EQ.xml", "monopole_SSH.xml");
// SSH provides converter state data (operating kinds, powers). From those we calculate resistive losses.
assertContainsLccConverter(network, "CSC_1", "Current source converter 1", "DCL_12", 0.0, -0.8251);
assertContainsLccConverter(network, "CSC_2", "Current source converter 2", "DCL_12", 0.0, 0.8);
assertContainsHvdcLine(network, "DCL_12", SIDE_1_INVERTER_SIDE_2_RECTIFIER, "DC line 12", "CSC_1", "CSC_2", 4.64, 99.198, 119.038);
assertContainsVscConverter(network, "VSC_3", "Voltage source converter 3", "DCL_34", 0.0, 95.0, 0.0);
assertContainsVscConverter(network, "VSC_4", "Voltage source converter 4", "DCL_34", 0.0, 90.0, 0.0);
assertContainsHvdcLine(network, "DCL_34", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 34", "VSC_3", "VSC_4", 9.92, 100.0, 120.0);
}
@Test
void monopoleFullTest() {
// Same test as with EQ and SSH, but this time the SV is also read (the TP too but it isn't used).
Network network = readCgmesResources(DIR, "monopole_EQ.xml", "monopole_SSH.xml", "monopole_SV.xml", "monopole_TP.xml");
// SV gives losses in converters.
assertContainsLccConverter(network, "CSC_1", "Current source converter 1", "DCL_12", 0.4024, -0.8251);
assertContainsLccConverter(network, "CSC_2", "Current source converter 2", "DCL_12", 0.4, 0.8);
assertContainsHvdcLine(network, "DCL_12", SIDE_1_INVERTER_SIDE_2_RECTIFIER, "DC line 12", "CSC_1", "CSC_2", 4.64, 100.0, 120.0);
assertContainsVscConverter(network, "VSC_3", "Voltage source converter 3", "DCL_34", 0.6, 95.0, 0.0);
assertContainsVscConverter(network, "VSC_4", "Voltage source converter 4", "DCL_34", 0.6085, 90.0, 0.0);
assertContainsHvdcLine(network, "DCL_34", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 34", "VSC_3", "VSC_4", 9.92, 100.0, 120.0);
}
@Test
void monopoleWithMetallicReturnTest() {
// CGMES network:
// A HVDC monopole with metallic return:
// The positive polarity DCLineSegment DCL_34P has r = 4.94.
// The negative polarity DCLineSegment DCL_34N has r = 4.98.
// IIDM network:
// HvdcLine DCL_34P with VscConverterStation VSC_3 and VSC_4.
Network network = readCgmesResources(DIR, "monopole_with_metallic_return.xml");
// A single HvdcLine has been created with an equivalent resistance.
assertContainsVscConverter(network, "VSC_3", "Voltage source converter 3", "DCL_34N", 0.0, Double.NaN, 0.0);
assertContainsVscConverter(network, "VSC_4", "Voltage source converter 4", "DCL_34N", 0.0, Double.NaN, 0.0);
assertContainsHvdcLine(network, "DCL_34N", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 34N", "VSC_3", "VSC_4", 9.92, 0.0, 0.0);
// The other DCLineSegment identifier is kept as an alias.
assertEquals("DCL_34P", network.getHvdcLine("DCL_34N").getAliasFromType(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "DCLineSegment2").orElse(""));
}
@Test
void monopoleWith2AcDcConvertersPerUnitTest() {
// CGMES network:
// A HVDC monopole with ground return.
// Each converter unit is made of 2 ACDCConverter in series: CSC_1A and CSC_1B, and CSC_2A and CSC_2B.
// The DCLineSegment DCL_12 has r = 4.64 and is the closest to CSC_1A and CSC_2A.
// IIDM network:
// - HvdcLine DCL_12 with LccConverterStation CSC_1A and CSC_2A.
// - HvdcLine DCL_12-1 with LccConverterStation CSC_1B and CSC_2B.
Network network = readCgmesResources(DIR, "monopole_with_two_ACDCConverters_per_unit.xml");
// HvdcLine DCL_12 links LccConverterStation CSC_1A and CSC_2A with an equivalent resistance.
assertContainsLccConverter(network, "CSC_1A", "Current source converter 1A", "DCL_12", 0.0, 0.8);
assertContainsLccConverter(network, "CSC_2A", "Current source converter 2A", "DCL_12", 0.0, 0.8);
assertContainsHvdcLine(network, "DCL_12", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 12", "CSC_1A", "CSC_2A", 2.32, 0.0, 0.0);
// HvdcLine DCL_12-1 links LccConverterStation CSC_1B and CSC_2B with an equivalent resistance.
assertContainsLccConverter(network, "CSC_1B", "Current source converter 1B", "DCL_12-1", 0.0, 0.8);
assertContainsLccConverter(network, "CSC_2B", "Current source converter 2B", "DCL_12-1", 0.0, 0.8);
assertContainsHvdcLine(network, "DCL_12-1", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 12-1", "CSC_1B", "CSC_2B", 2.32, 0.0, 0.0);
}
@Test
void pPccControlKindTest() {
// Control kind is active power at Point of Common Coupling on both sides.
Network network = readCgmesResources(DIR, "monopole_EQ.xml", "monopole_Ppcc_SSH.xml", "monopole_SV.xml", "monopole_TP.xml");
// This gives more precise loss and power factor compared to dc voltage control kind at rectifier side.
assertContainsLccConverter(network, "CSC_1", "Current source converter 1", "DCL_12", 0.4024, -0.8251);
assertContainsLccConverter(network, "CSC_2", "Current source converter 2", "DCL_12", 0.4, 0.9119);
assertContainsHvdcLine(network, "DCL_12", SIDE_1_INVERTER_SIDE_2_RECTIFIER, "DC line 12", "CSC_1", "CSC_2", 4.64, 100.0, 120.0);
// This doesn't change calculations when control kind at rectifier side was already active power at point of common coupling.
assertContainsVscConverter(network, "VSC_3", "Voltage source converter 3", "DCL_34", 0.6, 95.0, 0.0);
assertContainsVscConverter(network, "VSC_4", "Voltage source converter 4", "DCL_34", 0.6085, 90.0, 0.0);
assertContainsHvdcLine(network, "DCL_34", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 34", "VSC_3", "VSC_4", 9.92, 100.0, 120.0);
}
@Test
void qPccControlKindTest() {
// Control kind is reactive power at Point of Common Coupling on both sides.
// This regulation mode exists only for VSC lines.
Network network = readCgmesResources(DIR, "monopole_EQ.xml", "monopole_Qpcc_SSH.xml", "monopole_SV.xml", "monopole_TP.xml");
// Control variable is reactive power at PCC.
assertContainsVscConverter(network, "VSC_3", "Voltage source converter 3", "DCL_34", 0.6, 0.0, -22.5);
assertContainsVscConverter(network, "VSC_4", "Voltage source converter 4", "DCL_34", 0.6085, 0.0, -30.0);
assertContainsHvdcLine(network, "DCL_34", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 34", "VSC_3", "VSC_4", 9.92, 100.0, 120.0);
}
@Test
void inconsistentConverterTypesTest() {
// CGMES network:
// A HVDC line (monopole, ground return) linking a CsConverter and a VsConverter.
// IIDM network:
// Neither HvdcConverterStation nor HvdcLine are created when inputs are inconsistent.
Network network = readCgmesResources(DIR, "inconsistent_converter_types.xml");
assertEquals(0, network.getHvdcConverterStationCount());
assertEquals(0, network.getHvdcLineCount());
}
@Test
void missingDCLineSegmentTest() {
// CGMES network:
// An EQ file with 4 ACDCConverter, but no DCLineSegment joining them.
// IIDM network:
// Neither HvdcConverterStation nor HvdcLine are created when inputs are missing.
Network network = readCgmesResources(DIR, "missing_DCLineSegment.xml");
assertEquals(4, network.getSubstationCount());
assertEquals(0, network.getHvdcConverterStationCount());
assertEquals(0, network.getHvdcLineCount());
}
@Test
void missingAcDcConverterTest() {
// CGMES network:
// An EQ file with 2 ACDCConverter and 2 DCLineSegment on one side, but no ACDCConverter on the other side.
// IIDM network:
// Neither HvdcConverterStation nor HvdcLine are created when inputs are missing.
Network network = readCgmesResources(DIR, "missing_ACDCConverter.xml");
assertEquals(4, network.getSubstationCount());
assertEquals(0, network.getHvdcConverterStationCount());
assertEquals(0, network.getHvdcLineCount());
}
@Test
void missingPpccTest() {
// Control kind is active power at Point of Common Coupling on both sides, but Ppcc values are missing.
Network network = readCgmesResources(DIR, "monopole_EQ.xml", "monopole_missing_Ppcc_SSH.xml", "monopole_SV.xml", "monopole_TP.xml");
// Loss factor can't be calculated when Ppcc is missing. They are set to 0.0 and conversion goes on with these values.
assertContainsLccConverter(network, "CSC_1", "Current source converter 1", "DCL_12", 0.0, -0.8251);
assertContainsLccConverter(network, "CSC_2", "Current source converter 2", "DCL_12", 0.0, 0.9119);
assertContainsHvdcLine(network, "DCL_12", SIDE_1_INVERTER_SIDE_2_RECTIFIER, "DC line 12", "CSC_1", "CSC_2", 4.64, 0.0, 0.0);
// For VSC line, in addition to also set loss factors to 0.0,
// it's not possible anymore to compute which side is inverter and which is rectifier without P.
assertContainsVscConverter(network, "VSC_3", "Voltage source converter 3", "DCL_34", 0.0, 95.0, 0.0);
assertContainsVscConverter(network, "VSC_4", "Voltage source converter 4", "DCL_34", 0.0, 90.0, 0.0);
assertContainsHvdcLine(network, "DCL_34", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 34", "VSC_3", "VSC_4", 9.92, 0.0, 0.0);
}
@Test
void vscWithCapabilityCurveTest() throws IOException {
// CGMES network:
// An EQ file with a VSC HVDC line (monopole, ground return).
// VsConverter VSC_3 has a VsCapabilityCurve.
// IIDM network:
// HvdcLine and VscConverterStation have been created.
// VscConverterStation VSC_3 has ReactiveLimits.
Network network = readCgmesResources(DIR, "vsCapabilityCurve.xml");
assertEquals(2, network.getHvdcConverterStationCount());
assertEquals(1, network.getHvdcLineCount());
VscConverterStation vscConverter = network.getVscConverterStation("VSC_3");
assertContainsVsCapabilityCurve(vscConverter, Map.of(
-100.0, new double[]{-25.0, 25.0},
0.0, new double[]{-100.0, 100.0},
100.0, new double[]{-25.0, 25.0}
));
// Curve and CurveData are correctly exported to CGMES.
String eqFile = writeCgmesProfile(network, "EQ", tmpDir, new Properties());
assertEquals(1, getElementCount(eqFile, "VsCapabilityCurve"));
assertTrue(getElement(eqFile, "VsConverter", "VSC_3").contains("VsConverter.CapabilityCurve"));
assertEquals(3, getElementCount(eqFile, "CurveData"));
assertContainsCurveData(eqFile, "VSC_3_DCCS_0_RCC_CP", "-100", "-25", "25");
assertContainsCurveData(eqFile, "VSC_3_DCCS_1_RCC_CP", "0", "-100", "100");
assertContainsCurveData(eqFile, "VSC_3_DCCS_2_RCC_CP", "100", "-25", "25");
}
@Test
void vscWithRemotePccTerminalTest() {
// CGMES network:
// A VSC HVDC line DCL_34 (monopole, ground return).
// VsConverter VSC_3 has a remote pccTerminal, VsConverter VSC_4 hasn't.
// IIDM network:
// HvdcLine and VscConverterStation have been created.
// VscConverterStation VSC_3 regulating terminal is a remote one, VSC_4 regulating terminal is local.
Network network = readCgmesResources(DIR, "remote_pccTerminal_EQ.xml", "remote_pccTerminal_SSH.xml");
// HVDC line and converters are imported with the same characteristics.
assertContainsVscConverter(network, "VSC_3", "Voltage source converter 3", "DCL_34", 0.0, 0.0, -22.5);
assertContainsVscConverter(network, "VSC_4", "Voltage source converter 4", "DCL_34", 0.0, 0.0, -30.0);
assertContainsHvdcLine(network, "DCL_34", SIDE_1_RECTIFIER_SIDE_2_INVERTER, "DC line 34", "VSC_3", "VSC_4", 9.92, 100.0, 120.0);
// VSC_3 regulating terminal is remote, VSC_4 regulating terminal is local.
assertEquals("PT_3", network.getVscConverterStation("VSC_3").getRegulatingTerminal().getConnectable().getId());
assertEquals("VSC_4", network.getVscConverterStation("VSC_4").getRegulatingTerminal().getConnectable().getId());
}
private void assertContainsLccConverter(Network network, String id, String name,
String hvdcLineId, double lossFactor, double powerFactor) {
LccConverterStation lccConverter = network.getLccConverterStation(id);
assertNotNull(lccConverter);
assertEquals(name, lccConverter.getNameOrId());
assertEquals(hvdcLineId, lccConverter.getHvdcLine().getId());
assertEquals(lossFactor, lccConverter.getLossFactor(), TOLERANCE);
assertEquals(powerFactor, lccConverter.getPowerFactor(), TOLERANCE);
}
private void assertContainsVscConverter(Network network, String id, String name,
String hvdcLineId, double lossFactor, double voltageSetpoint, double reactivePowerSetpoint) {
VscConverterStation vscConverter = network.getVscConverterStation(id);
assertNotNull(vscConverter);
assertEquals(name, vscConverter.getNameOrId());
assertEquals(hvdcLineId, vscConverter.getHvdcLine().getId());
assertEquals(lossFactor, vscConverter.getLossFactor(), TOLERANCE);
assertEquals(voltageSetpoint, vscConverter.getVoltageSetpoint(), TOLERANCE);
assertEquals(reactivePowerSetpoint, vscConverter.getReactivePowerSetpoint(), TOLERANCE);
}
private void assertContainsHvdcLine(Network network, String id, HvdcLine.ConvertersMode convertersMode, String name,
String converterStation1, String converterStation2, double r, double activePowerSetpoint, double maxP) {
HvdcLine hvdcLine = network.getHvdcLine(id);
assertNotNull(hvdcLine);
assertEquals(convertersMode, hvdcLine.getConvertersMode());
assertEquals(name, hvdcLine.getNameOrId());
assertEquals(converterStation1, hvdcLine.getConverterStation1().getId());
assertEquals(converterStation2, hvdcLine.getConverterStation2().getId());
assertEquals(r, hvdcLine.getR(), TOLERANCE);
assertEquals(activePowerSetpoint, hvdcLine.getActivePowerSetpoint(), TOLERANCE);
assertEquals(maxP, hvdcLine.getMaxP(), TOLERANCE);
}
private void assertContainsVsCapabilityCurve(VscConverterStation vscConverter, Map<Double, double[]> values) {
assertNotNull(vscConverter);
assertNotNull(vscConverter.getReactiveLimits());
assertEquals(ReactiveLimitsKind.CURVE, vscConverter.getReactiveLimits().getKind());
ReactiveCapabilityCurve curve = vscConverter.getReactiveLimits(ReactiveCapabilityCurve.class);
assertEquals(values.size(), curve.getPointCount());
for (ReactiveCapabilityCurve.Point point : curve.getPoints()) {
double pValue = point.getP();
assertTrue(values.containsKey(pValue));
assertEquals(point.getMinQ(), values.get(pValue)[0]);
assertEquals(point.getMaxQ(), values.get(pValue)[1]);
}
}
private void assertContainsCurveData(String eqFile, String curveDataId, String xValue, String y1Value, String y2Value) {
String curveData = getElement(eqFile, "CurveData", curveDataId);
assertTrue(curveData.contains("<cim:CurveData.xvalue>" + xValue + "</cim:CurveData.xvalue>"));
assertTrue(curveData.contains("<cim:CurveData.y1value>" + y1Value + "</cim:CurveData.y1value>"));
assertTrue(curveData.contains("<cim:CurveData.y2value>" + y2Value + "</cim:CurveData.y2value>"));
}
}