MatpowerExporterTest.java
/**
* Copyright (c) 2022, 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.matpower.converter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.powsybl.cgmes.conformity.CgmesConformity1ModifiedCatalog;
import com.powsybl.commons.config.InMemoryPlatformConfig;
import com.powsybl.commons.config.PlatformConfig;
import com.powsybl.commons.datasource.DirectoryDataSource;
import com.powsybl.commons.datasource.MemDataSource;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.commons.test.ComparisonUtils;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.SlackTerminal;
import com.powsybl.iidm.network.test.DanglingLineNetworkFactory;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory;
import com.powsybl.matpower.model.MatpowerModel;
import com.powsybl.matpower.model.MatpowerModelFactory;
import com.powsybl.matpower.model.MatpowerReader;
import com.powsybl.matpower.model.MatpowerWriter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.util.Locale;
import java.util.Properties;
/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
class MatpowerExporterTest extends AbstractSerDeTest {
private PlatformConfig platformConfig;
@Override
@BeforeEach
public void setUp() throws IOException {
super.setUp();
platformConfig = new InMemoryPlatformConfig(fileSystem);
}
private void exportToMatAndCompareTo(Network network, String refJsonFile) throws IOException {
Properties parameters = new Properties();
parameters.setProperty(MatpowerExporter.WITH_BUS_NAMES_PARAMETER_NAME, "true");
exportToMatAndCompareTo(network, refJsonFile, parameters, null);
}
private void exportToMatAndCompareTo(Network network, String refJsonFile, Properties parameters) throws IOException {
exportToMatAndCompareTo(network, refJsonFile, parameters, null);
}
private void exportToMatAndCompareTo(Network network, String refJsonFile, Properties parameters, DecimalFormat decimalFormat) throws IOException {
MemDataSource dataSource = new MemDataSource();
new MatpowerExporter(platformConfig).export(network, parameters, dataSource);
byte[] mat = dataSource.getData(null, "mat");
MatpowerModel model = MatpowerReader.read(new ByteArrayInputStream(mat), network.getId());
// Map to JSON with a specific serializer for doubles if decimal format is received
ObjectMapper jsonMapper = new ObjectMapper();
if (decimalFormat != null) {
jsonMapper.registerModule(new SimpleModule().addSerializer(double.class, new JsonSerializer<>() {
@Override
public void serialize(Double value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (value == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeNumber(decimalFormat.format(value));
}
}
}));
}
String json = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(model);
ComparisonUtils.assertTxtEquals(MatpowerExporterTest.class.getResourceAsStream(refJsonFile), json);
}
@Test
void testEsgTuto1() throws IOException {
var network = EurostagTutorialExample1Factory.create();
exportToMatAndCompareTo(network, "/sim1.json");
}
@Test
void testEsgTuto1WithoutActivePowerLimit() throws IOException {
var network = EurostagTutorialExample1Factory.create();
Generator gen = network.getGenerator("GEN");
gen.newMinMaxReactiveLimits()
.setMinQ(-Double.MAX_VALUE)
.setMaxQ(Double.MAX_VALUE)
.add();
exportToMatAndCompareTo(network, "/sim1-without-active-power-limit.json");
}
@Test
void testEsgTuto1WithoutBusNames() throws IOException {
var network = EurostagTutorialExample1Factory.create();
Properties parameters = new Properties();
parameters.setProperty(MatpowerExporter.WITH_BUS_NAMES_PARAMETER_NAME, "false");
exportToMatAndCompareTo(network, "/sim1-without-bus-names.json", parameters);
}
@Test
void testMicroGridBe() throws IOException {
Network network = Network.read(CgmesConformity1ModifiedCatalog.microGridBaseCaseBERatioPhaseTapChangerTabular().dataSource());
exportToMatAndCompareTo(network, "/be.json");
}
@Test
void testWithTieLines() throws IOException {
var network = EurostagTutorialExample1Factory.createWithTieLine();
exportToMatAndCompareTo(network, "/sim1-with-tie-lines.json");
}
@Test
void testWithHvdcLines() throws IOException {
var network = FourSubstationsNodeBreakerFactory.create();
exportToMatAndCompareTo(network, "/fourSubstationFactory.json");
}
@Test
void testCase30ConsideringBaseVoltage() throws IOException {
MatpowerModel model = MatpowerModelFactory.create30();
model.setCaseName("ieee30-considering-base-voltage");
String caseId = model.getCaseName();
Path matFile = tmpDir.resolve(caseId + ".mat");
MatpowerWriter.write(model, matFile, true);
Properties properties = new Properties();
properties.put("matpower.import.ignore-base-voltage", false);
Network network = new MatpowerImporter().importData(new DirectoryDataSource(tmpDir, caseId), NetworkFactory.findDefault(), properties);
exportToMatAndCompareTo(network, "/ieee30-considering-base-voltage.json");
}
@Test
void testNonRegulatingGenOnPVBus() throws IOException {
var network = EurostagTutorialExample1Factory.create();
Bus slackBus = network.getBusView().getBus("VLHV1_0");
SlackTerminal.attach(slackBus);
network.getVoltageLevel("VLGEN").newGenerator()
.setId("GEN2")
.setBus("NGEN")
.setTargetP(10)
.setTargetQ(5)
.setMinP(0)
.setMaxP(1000)
.setVoltageRegulatorOn(false)
.add();
exportToMatAndCompareTo(network, "/sim1-with-non-regulating-gen.json");
}
@Test
void testWithCurrentLimits() throws IOException {
var network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits();
exportToMatAndCompareTo(network, "/sim1-with-current-limits.json");
}
@Test
void testWithCurrentLimits2() throws IOException {
var network = EurostagTutorialExample1Factory.create();
Line line = network.getLine("NHV1_NHV2_1");
line.newCurrentLimits1()
.setPermanentLimit(1000)
.beginTemporaryLimit()
.setName("20'")
.setAcceptableDuration(20 * 60)
.setValue(1100)
.endTemporaryLimit()
.beginTemporaryLimit()
.setName("10'")
.setAcceptableDuration(10 * 60)
.setValue(1200)
.endTemporaryLimit()
.beginTemporaryLimit()
.setName("1'")
.setAcceptableDuration(60)
.setValue(1300)
.endTemporaryLimit()
.beginTemporaryLimit()
.setName("N/A")
.setAcceptableDuration(0)
.setValue(Double.MAX_VALUE)
.endTemporaryLimit()
.add();
exportToMatAndCompareTo(network, "/sim1-with-current-limits2.json");
}
@Test
void testWithApparentPowerLimits() throws IOException {
var network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits();
var l = network.getLine("NHV1_NHV2_1");
l.newApparentPowerLimits1()
.setPermanentLimit(1000)
.beginTemporaryLimit()
.setName("1'")
.setAcceptableDuration(60)
.setValue(1500)
.endTemporaryLimit()
.add();
l.newCurrentLimits2()
.setPermanentLimit(1000)
.add();
exportToMatAndCompareTo(network, "/sim1-with-apparent-power-limits.json");
}
@Test
void testNanTargetQIssue() throws IOException {
var network = EurostagTutorialExample1Factory.create();
network.getGenerator("GEN").setTargetQ(Double.NaN);
exportToMatAndCompareTo(network, "/sim1-with-nan-target-q.json");
}
@Test
void testVscNpeIssue() throws IOException {
var network = EurostagTutorialExample1Factory.create();
VoltageLevel vlgen = network.getVoltageLevel("VLGEN");
vlgen.newVscConverterStation()
.setId("VSC")
.setConnectableBus("NGEN")
.setVoltageRegulatorOn(true)
.setVoltageSetpoint(100)
.setLossFactor(0)
.add();
exportToMatAndCompareTo(network, "/vsc-npe-issue.json");
}
@Test
void testDanglingLineWithGeneration() throws IOException {
var network = DanglingLineNetworkFactory.createWithGeneration();
exportToMatAndCompareTo(network, "/dangling-line-generation.json");
}
@Test
void testLineConnectedToSameBus() throws IOException {
var network = EurostagTutorialExample1Factory.create();
network.newLine()
.setId("NL")
.setBus1("NGEN")
.setVoltageLevel1("VLGEN")
.setBus2("NGEN")
.setVoltageLevel2("VLGEN")
.setR(0.1)
.setX(0.1)
.add();
exportToMatAndCompareTo(network, "/line-connected-same-bus.json");
}
@Test
void testSmallImpedanceLine() throws IOException {
var network = EurostagTutorialExample1Factory.create();
network.getLine("NHV1_NHV2_1")
.setR(0.00000001)
.setX(0.00000003);
exportToMatAndCompareTo(network, "/small-impedance-line.json");
}
@Test
void testExportCase9DcLine() throws IOException {
MatpowerModel matpowerModel = MatpowerModelFactory.create9Dcline();
String caseId = matpowerModel.getCaseName();
Path matFile = tmpDir.resolve(caseId + ".mat");
MatpowerWriter.write(matpowerModel, matFile, true);
var network = new MatpowerImporter().importData(new DirectoryDataSource(tmpDir, caseId), NetworkFactory.findDefault(), null);
exportToMatAndCompareTo(network, "/t_case9_dcline_exported.json");
}
static class DecimalFormat14 extends DecimalFormat {
private static final DecimalFormatSymbols SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US);
private static final DecimalFormat SCI = new DecimalFormat("0.0###############E0", SYMBOLS);
DecimalFormat14() {
super("0.0", SYMBOLS);
super.setMaximumFractionDigits(14);
}
@Override
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
if (number != 0.0 && Math.abs(number) < 1e-5 || Math.abs(number) > 1e10) {
return SCI.format(number, result, fieldPosition);
}
return super.format(number, result, fieldPosition);
}
}
@Test
void testBusesToBeExported() throws IOException {
Network network = createThreeComponentsConnectedByHvdcLinesNetwork();
Properties parameters = new Properties();
parameters.setProperty(MatpowerExporter.WITH_BUS_NAMES_PARAMETER_NAME, "true");
// Write all doubles with a maximum precision of 15 fraction digits to avoid macOS 14 small diffs in output
exportToMatAndCompareTo(network, "/threeComponentsConnectedByHvdcLines.json", parameters, new DecimalFormat14());
}
private static Network createThreeComponentsConnectedByHvdcLinesNetwork() {
Network network = Network.create("threeComponentsConnectedByHvdcLines", "iidm");
// Component 1
VoltageLevel vl11 = network.newSubstation().setId("S11").add()
.newVoltageLevel().setId("VL11").setNominalV(400.0).setTopologyKind(TopologyKind.BUS_BREAKER).add();
vl11.getBusBreakerView().newBus().setId("BUS-11").add();
VoltageLevel vl12 = network.newSubstation().setId("S12").add()
.newVoltageLevel().setId("VL12").setNominalV(400.0).setTopologyKind(TopologyKind.BUS_BREAKER).add();
vl12.getBusBreakerView().newBus().setId("BUS-12").add();
// Component 2
VoltageLevel vl21 = network.newSubstation().setId("S21").add()
.newVoltageLevel().setId("VL21").setNominalV(400.0).setTopologyKind(TopologyKind.BUS_BREAKER).add();
vl21.getBusBreakerView().newBus().setId("BUS-21").add();
VoltageLevel vl22 = network.newSubstation().setId("S22").add()
.newVoltageLevel().setId("VL22").setNominalV(400.0).setTopologyKind(TopologyKind.BUS_BREAKER).add();
vl22.getBusBreakerView().newBus().setId("BUS-22").add();
// Component 3
VoltageLevel vl31 = network.newSubstation().setId("S31").add()
.newVoltageLevel().setId("VL31").setNominalV(400.0).setTopologyKind(TopologyKind.BUS_BREAKER).add();
vl31.getBusBreakerView().newBus().setId("BUS-31").add();
VoltageLevel vl32 = network.newSubstation().setId("S32").add()
.newVoltageLevel().setId("VL32").setNominalV(400.0).setTopologyKind(TopologyKind.BUS_BREAKER).add();
vl32.getBusBreakerView().newBus().setId("BUS-32").add();
vl11.newGenerator().setId("GENERATOR-11").setBus("BUS-11").setTargetP(10.0).setTargetQ(8.0).setTargetV(410.0).setMinP(0.0).setMaxP(15.0).setVoltageRegulatorOn(true).add();
SlackTerminal.attach(network.getBusBreakerView().getBus("BUS-11"));
vl22.newLoad().setId("LOAD-22").setBus("BUS-22").setP0(5.0).setQ0(4.0).add();
vl32.newLoad().setId("LOAD-32").setBus("BUS-32").setP0(5.0).setQ0(4.0).add();
network.newLine().setId("LINE-11-12").setBus1("BUS-11").setBus2("BUS-12").setR(0.0).setX(1.0).add();
network.newLine().setId("LINE-21-22").setBus1("BUS-21").setBus2("BUS-22").setR(0.0).setX(1.0).add();
network.newLine().setId("LINE-31-32").setBus1("BUS-31").setBus2("BUS-32").setR(0.0).setX(1.0).add();
vl12.newLccConverterStation().setId("LCC-12").setBus("BUS-12").setPowerFactor(0.90f).setLossFactor(0.0f).add();
vl21.newLccConverterStation().setId("LCC-21").setBus("BUS-21").setPowerFactor(0.90f).setLossFactor(0.0f).add();
network.newHvdcLine().setId("HVDCLINE-12-21").setConverterStationId1("LCC-12").setConverterStationId2("LCC-21").setNominalV(400.0).setActivePowerSetpoint(5.0).setMaxP(5.0).setR(0.0).setConvertersMode(HvdcLine.ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER).add();
vl12.newVscConverterStation().setId("VSC-12").setBus("BUS-12").setLossFactor(0.0f).setReactivePowerSetpoint(4.0).setVoltageSetpoint(410.0).setVoltageRegulatorOn(true).add();
vl31.newVscConverterStation().setId("VSC-31").setBus("BUS-31").setLossFactor(0.0f).setReactivePowerSetpoint(4.0).setVoltageSetpoint(410.0).setVoltageRegulatorOn(true).add();
network.newHvdcLine().setId("HVDCLINE-12-31").setConverterStationId1("VSC-12").setConverterStationId2("VSC-31").setNominalV(400.0).setActivePowerSetpoint(5.0).setMaxP(5.0).setR(0.0).setConvertersMode(HvdcLine.ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER).add();
return network;
}
}