CgmesNamingStrategyTest.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.cgmes.conversion.test.export;
import com.powsybl.cgmes.conformity.CgmesConformity1Catalog;
import com.powsybl.cgmes.conformity.CgmesConformity1ModifiedCatalog;
import com.powsybl.cgmes.conversion.*;
import com.powsybl.cgmes.conversion.export.CgmesExportUtil;
import com.powsybl.cgmes.conversion.naming.NamingStrategyFactory;
import com.powsybl.cgmes.model.CgmesModel;
import com.powsybl.cgmes.model.CgmesNames;
import com.powsybl.commons.datasource.*;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.iidm.network.*;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Marcos de Miguel {@literal <demiguelm at aia.es>}
*/
class CgmesNamingStrategyTest extends AbstractSerDeTest {
@Test
void testExportUsingCgmesNamingStrategyCgmesMicroGrid() throws IOException {
ReadOnlyDataSource ds = CgmesConformity1Catalog.microGridBaseCaseAssembled().dataSource();
Network network = Network.read(ds);
network.setName("MicroGrid-NS-CGMES");
testExport(network, ds, NamingStrategyFactory.CGMES);
}
@Test
void testExportUsingCgmesNamingStrategyMicroGrid() throws IOException {
// We select a case that contains invalid IDs
ReadOnlyDataSource ds = CgmesConformity1ModifiedCatalog.microGridBaseCaseAssembledBadIds().dataSource();
Network network = Network.read(ds);
network.setName("MicroGrid-NS-CGMES_FIX_ALL_INVALID_IDS");
testExport(network, ds, NamingStrategyFactory.CGMES_FIX_ALL_INVALID_IDS);
}
void testExport(Network network, ReadOnlyDataSource originalDataSource, String namingStrategy) throws IOException {
String baseName = network.getNameOrId();
Properties exportParams = new Properties();
exportParams.put(CgmesExport.NAMING_STRATEGY, namingStrategy);
exportParams.put(CgmesExport.EXPORT_SV_INJECTIONS_FOR_SLACKS, "false");
String outputFolder = "exportedCgmes" + baseName;
DataSource exportedCgmes = tmpDataSource(outputFolder, baseName);
network.write("CGMES", exportParams, exportedCgmes);
if (originalDataSource != null) {
copyBoundary(outputFolder, baseName, originalDataSource);
}
// Load the exported CGMES model and check that all objects have valid CGMES identifiers
Network network1 = Network.read(exportedCgmes);
checkAllIdentifiersAreValidCimCgmesIdentifiers(network1);
// Also, all Identifiables that do not have a valid CIM mRID must have a valid UUID alias
for (Identifiable<?> i : network1.getIdentifiables()) {
if (!i.isFictitious() && !i.getType().equals(IdentifiableType.TIE_LINE) && !CgmesExportUtil.isValidCimMasterRID(i.getId())) {
Optional<String> uuid = i.getAliasFromType(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "UUID");
assertTrue(uuid.isPresent());
assertTrue(CgmesExportUtil.isValidCimMasterRID(uuid.get()));
}
}
// Now that we have valid identifiers stored as aliases, we should be able to re-export to CGMES
// with the same naming strategy to use aliases to fix bad mrids
String reOutputFolder = "reExportedCgmes" + baseName;
DataSource reExportedCgmes = tmpDataSource(reOutputFolder, baseName);
network1.write("CGMES", exportParams, reExportedCgmes);
if (originalDataSource != null) {
copyBoundary(reOutputFolder, baseName, originalDataSource);
}
Network network1Reimported = Network.read(reExportedCgmes);
// Convert to strings with newlines for easier visual comparison in case of differences
assertEquals(
Arrays.toString(network1.getIdentifiables().stream()
.filter(i -> !(i instanceof Network) && !i.isFictitious())
.map(i -> i.getType() + "::" + i.getId())
.sorted().toArray()).replace(",", System.lineSeparator()),
Arrays.toString(network1Reimported.getIdentifiables().stream()
.filter(i -> !(i instanceof Network) && !i.isFictitious())
.map(i -> i.getType() + "::" + i.getId())
.sorted().toArray()).replace(",", System.lineSeparator()));
}
private void checkAllIdentifiersAreValidCimCgmesIdentifiers(Network network) {
CgmesModel cgmes = network.getExtension(CgmesModelExtension.class).getCgmesModel();
Supplier<Stream<String>> badIds = () -> Stream.of(
network.getIdentifiables().stream().filter(i -> !i.isFictitious() && !i.getType().equals(IdentifiableType.TIE_LINE)).map(Identifiable::getId),
// Some CGMES identifiers do not end as Network identifiables
cgmes.terminals().stream().map(o -> o.getId(CgmesNames.TERMINAL)),
cgmes.connectivityNodes().stream().map(o -> o.getId(CgmesNames.CONNECTIVITY_NODE)),
cgmes.topologicalNodes().stream().map(o -> o.getId(CgmesNames.TOPOLOGICAL_NODE)),
cgmes.topologicalIslands().stream().flatMap(o -> Stream.of(
o.getId(CgmesNames.TOPOLOGICAL_ISLAND),
o.getId(CgmesNames.ANGLEREF_TOPOLOGICALNODE),
o.getId(CgmesNames.TOPOLOGICAL_NODES))),
cgmes.topologicalIslands().stream().map(o -> o.getId(CgmesNames.ANGLEREF_TOPOLOGICALNODE)),
cgmes.transformerEnds().stream().map(o -> o.getId(CgmesNames.TRANSFORMER_END)),
cgmes.phaseTapChangers().stream().map(o -> o.getId(CgmesNames.PHASE_TAP_CHANGER)),
cgmes.ratioTapChangers().stream().map(o -> o.getId(CgmesNames.RATIO_TAP_CHANGER)),
cgmes.regulatingControls().stream().map(o -> o.getId("RegulatingControl")),
cgmes.controlAreas().stream().map(o -> o.getId("ControlArea")),
cgmes.synchronousMachinesGenerators().stream().map(o -> o.getId("GeneratingUnit")),
cgmes.operationalLimits().stream().map(o -> o.getId("OperationalLimit")),
cgmes.substations().stream().map(o -> o.getId("Region")),
cgmes.substations().stream().map(o -> o.getId("SubRegion"))
)
.flatMap(id -> id)
.filter(id -> !CgmesExportUtil.isValidCimMasterRID(id));
assertEquals(0,
badIds.get().count(),
String.format("Identifiers not valid as CIM mRIDs : %s", badIds.get().collect(Collectors.joining(","))));
}
private DataSource tmpDataSource(String folder, String baseName) throws IOException {
Path exportFolder = tmpDir.resolve(folder);
if (Files.exists(exportFolder)) {
FileUtils.cleanDirectory(exportFolder.toFile());
}
Files.createDirectories(exportFolder);
return new DirectoryDataSource(exportFolder, baseName);
}
private void copyBoundary(String outputFolderName, String baseName, ReadOnlyDataSource originalDataSource) throws IOException {
Path outputFolder = tmpDir.resolve(outputFolderName);
String eqbd = originalDataSource.listNames(".*EQ_BD.*").stream().findFirst().orElse(null);
if (eqbd != null) {
try (InputStream is = originalDataSource.newInputStream(eqbd)) {
Files.copy(is, outputFolder.resolve(baseName + "_EQ_BD.xml"));
}
}
String tpbd = originalDataSource.listNames(".*TP_BD.*").stream().findFirst().orElse(null);
if (tpbd != null) {
try (InputStream is = originalDataSource.newInputStream(tpbd)) {
Files.copy(is, outputFolder.resolve(baseName + "_TP_BD.xml"));
}
}
}
}