diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java index d64e9d0650f..1fa2f960162 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/CgmesExport.java @@ -107,14 +107,16 @@ public void export(Network network, Properties parameters, DataSource dataSource private void exportCGM(Network network, DataSource dataSource, CgmesExportContext context) { checkCgmConsistency(network, context); - // Initialize models for export. The original IGM TP and SSH don't get exported, + // Initialize models for export. The original IGM EQ, SSH, TP and TP_BD don't get exported, // but we need to init their models to retrieve their IDs when building the dependencies. Map igmModels = new HashMap<>(); for (Network subnetwork : network.getSubnetworks()) { IgmModelsForCgm igmModelsForCgm = new IgmModelsForCgm( - initializeModelForExport(subnetwork, CgmesSubset.STEADY_STATE_HYPOTHESIS, context, false, false), initializeModelForExport(subnetwork, CgmesSubset.STEADY_STATE_HYPOTHESIS, context, false, true), - initializeModelForExport(subnetwork, CgmesSubset.TOPOLOGY, context, false, false) + initializeModelForExport(subnetwork, CgmesSubset.EQUIPMENT, context, false, false), + initializeModelForExport(subnetwork, CgmesSubset.STEADY_STATE_HYPOTHESIS, context, false, false), + initializeModelForExport(subnetwork, CgmesSubset.TOPOLOGY, context, false, false), + initializeModelForExport(subnetwork, CgmesSubset.TOPOLOGY_BOUNDARY, context, false, false) ); igmModels.put(subnetwork, igmModelsForCgm); } @@ -122,7 +124,7 @@ private void exportCGM(Network network, DataSource dataSource, CgmesExportContex // Update dependencies if (context.updateDependencies()) { - updateDependenciesCGM(igmModels.values(), updatedCgmSvModel); + updateDependenciesCGM(igmModels.values(), updatedCgmSvModel, context.getBoundaryTpId()); } // Export the SSH for the IGMs and the SV for the CGM @@ -227,22 +229,29 @@ public static CgmesMetadataModel initializeModelForExport( } /** - * Update cross dependencies between the subset models through the dependentOn relationship. - * The IGMs updated SSH supersede the original ones. - * The CGM updated SV depends on the IGMs updated SSH and on the IGMs original TP. - * @param igmModels For each IGM: the original SSH model, the updated SSH model and the original TP model. + * Update cross dependencies between the subset models (including boundaries) through the dependentOn relationship. + * The IGMs updated SSH supersede the original ones and depend on the original EQ. Other dependencies are kept. + * The CGM updated SV depends on the IGMs updated SSH and on the IGMs original TP and TP_BD. + * @param igmModels For each IGM: the updated SSH model and the original SSH, TP and TP_BD models. * @param updatedCgmSvModel The SV model for the CGM. + * @param boundaryTpId The model id for the TP_BD subset. */ - private void updateDependenciesCGM(Collection igmModels, CgmesMetadataModel updatedCgmSvModel) { + private void updateDependenciesCGM(Collection igmModels, CgmesMetadataModel updatedCgmSvModel, String boundaryTpId) { + // Each updated SSH model depends on the original EQ model + igmModels.forEach(m -> m.updatedSsh.addDependentOn(m.originalEq.getId())); + // Each updated SSH model supersedes the original one - // Clear previous dependencies - igmModels.forEach(m -> m.updatedSsh.clearDependencies()); igmModels.forEach(m -> m.updatedSsh.clearSupersedes()); igmModels.forEach(m -> m.updatedSsh.addSupersedes(m.originalSsh.getId())); - // Updated SV model depends on updated SSH models and original TP models + // Updated SV model depends on updated SSH models and original TP and TP_BD models updatedCgmSvModel.addDependentOn(igmModels.stream().map(m -> m.updatedSsh.getId()).collect(Collectors.toSet())); updatedCgmSvModel.addDependentOn(igmModels.stream().map(m -> m.originalTp.getId()).collect(Collectors.toSet())); + if (boundaryTpId != null) { + updatedCgmSvModel.addDependentOn(boundaryTpId); + } else { + updatedCgmSvModel.addDependentOn(igmModels.stream().map(m -> m.originalTpBd.getId()).collect(Collectors.toSet())); + } } /** @@ -492,14 +501,19 @@ private String getBaseName(CgmesExportContext context, DataSource dataSource, Ne * when setting the relationships (dependOn, supersedes) between them in a CGM export. */ private static class IgmModelsForCgm { - CgmesMetadataModel originalSsh; CgmesMetadataModel updatedSsh; + CgmesMetadataModel originalEq; + CgmesMetadataModel originalSsh; CgmesMetadataModel originalTp; + CgmesMetadataModel originalTpBd; - public IgmModelsForCgm(CgmesMetadataModel originalSsh, CgmesMetadataModel updatedSsh, CgmesMetadataModel originalTp) { - this.originalSsh = originalSsh; + public IgmModelsForCgm(CgmesMetadataModel updatedSsh, CgmesMetadataModel originalEq, CgmesMetadataModel originalSsh, + CgmesMetadataModel originalTp, CgmesMetadataModel originalTpBd) { this.updatedSsh = updatedSsh; + this.originalEq = originalEq; + this.originalSsh = originalSsh; this.originalTp = originalTp; + this.originalTpBd = originalTpBd; } } diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/CommonGridModelExportTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/CommonGridModelExportTest.java index 47ae912b2ae..26f2917527d 100644 --- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/CommonGridModelExportTest.java +++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/CommonGridModelExportTest.java @@ -140,14 +140,20 @@ void testCgmExportNoModelsNoProperties() throws IOException { String updatedNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_2_1D__FM"; String originalBeTpId = "urn:uuid:Network_BE_N_TOPOLOGY_2021-02-03T04:30:00Z_1_1D__FM"; String originalNlTpId = "urn:uuid:Network_NL_N_TOPOLOGY_2021-02-03T04:30:00Z_1_1D__FM"; - Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId); + String originalBeTpBdId = "urn:uuid:Network_BE_N_TOPOLOGY_BOUNDARY_2021-02-03T04:30:00Z_1_1D__FM"; + String originalNlTpBdId = "urn:uuid:Network_NL_N_TOPOLOGY_BOUNDARY_2021-02-03T04:30:00Z_1_1D__FM"; + Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId, originalBeTpBdId, originalNlTpBdId); assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON)); - // Each updated IGM SSH should supersede the original one + // Each updated IGM SSH should supersede the original one and depend on the original EQ String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM"; + String originalBeEqId = "urn:uuid:Network_BE_N_EQUIPMENT_2021-02-03T04:30:00Z_1_1D__FM"; String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM"; + String originalNlEqId = "urn:uuid:Network_NL_N_EQUIPMENT_2021-02-03T04:30:00Z_1_1D__FM"; assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES)); + assertEquals(originalBeEqId, getFirstOccurrence(updatedBeSshXml, REGEX_DEPENDENT_ON)); assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES)); + assertEquals(originalNlEqId, getFirstOccurrence(updatedNlSshXml, REGEX_DEPENDENT_ON)); // Profiles should be consistent with the instance files assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedBeSshXml, REGEX_PROFILE)); @@ -195,14 +201,17 @@ void testCgmExportWithModelsForSubnetworks() throws IOException { String updatedNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_2_1D__FM"; String originalBeTpId = "urn:uuid:Network_BE_N_TOPOLOGY_2021-02-03T04:30:00Z_1_1D__FM"; String originalNlTpId = "urn:uuid:Network_NL_N_TOPOLOGY_2021-02-03T04:30:00Z_1_1D__FM"; - Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId); + String originalTpBdId = "Common TP_BD model ID"; + Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId, originalTpBdId); assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON)); - // Each updated IGM SSH should supersede the original one + // Each updated IGM SSH should supersede the original one and depend on the original EQ String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM"; String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM"; assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES)); assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES)); + assertEquals(Set.of("BE EQ model ID"), getOccurrences(updatedBeSshXml, REGEX_DEPENDENT_ON)); + assertEquals(Set.of("NL EQ model ID"), getOccurrences(updatedNlSshXml, REGEX_DEPENDENT_ON)); // Profiles should be consistent with the instance files assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedBeSshXml, REGEX_PROFILE)); @@ -254,15 +263,19 @@ void testCgmExportWithModelsForAllNetworks() throws IOException { String updatedNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_4_1D__FM"; String originalBeTpId = "urn:uuid:Network_BE_N_TOPOLOGY_2022-03-04T05:30:00Z_1_1D__FM"; String originalNlTpId = "urn:uuid:Network_NL_N_TOPOLOGY_2022-03-04T05:30:00Z_1_1D__FM"; + String originalTpBdId = "Common TP_BD model ID"; String additionalDependency = "Additional dependency"; - Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId, additionalDependency); + Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, + originalNlTpId, originalTpBdId, additionalDependency); assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON)); - // Each updated IGM SSH should supersede the original one + // Each updated IGM SSH should supersede the original one and depend on the original EQ String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_1_1D__FM"; String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_1_1D__FM"; assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES)); assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES)); + assertEquals(Set.of("BE EQ model ID"), getOccurrences(updatedBeSshXml, REGEX_DEPENDENT_ON)); + assertEquals(Set.of("NL EQ model ID"), getOccurrences(updatedNlSshXml, REGEX_DEPENDENT_ON)); // Profiles should be consistent with the instance files // The model of the main network brings an additional profile @@ -288,6 +301,7 @@ void testCgmExportWithProperties() throws IOException { exportParams.put(CgmesExport.MODELING_AUTHORITY_SET, "Regional Coordination Center"); exportParams.put(CgmesExport.MODEL_DESCRIPTION, "Common Grid Model export"); exportParams.put(CgmesExport.MODEL_VERSION, "4"); + exportParams.put(CgmesExport.BOUNDARY_TP_ID, "ENTSOE TP_BD model ID"); String basename = "test_bare+properties"; network.write("CGMES", exportParams, tmpDir.resolve(basename)); String updatedBeSshXml = Files.readString(tmpDir.resolve(basename + "_BE_SSH.xml")); @@ -314,14 +328,19 @@ void testCgmExportWithProperties() throws IOException { String updatedNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_4_1D__FM"; String originalBeTpId = "urn:uuid:Network_BE_N_TOPOLOGY_2021-02-03T04:30:00Z_1_1D__FM"; String originalNlTpId = "urn:uuid:Network_NL_N_TOPOLOGY_2021-02-03T04:30:00Z_1_1D__FM"; - Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId); + String originalTpBdId = "ENTSOE TP_BD model ID"; + Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId, originalTpBdId); assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON)); - // Each updated IGM SSH should supersede the original one + // Each updated IGM SSH should supersede the original one and depend on the original EQ String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM"; + String originalBeEqId = "urn:uuid:Network_BE_N_EQUIPMENT_2021-02-03T04:30:00Z_1_1D__FM"; String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM"; + String originalNlEqId = "urn:uuid:Network_NL_N_EQUIPMENT_2021-02-03T04:30:00Z_1_1D__FM"; assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES)); + assertEquals(originalBeEqId, getFirstOccurrence(updatedBeSshXml, REGEX_DEPENDENT_ON)); assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES)); + assertEquals(originalNlEqId, getFirstOccurrence(updatedNlSshXml, REGEX_DEPENDENT_ON)); // Profiles should be consistent with the instance files assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedBeSshXml, REGEX_PROFILE)); @@ -347,6 +366,7 @@ void testCgmExportWithModelsAndProperties() throws IOException { exportParams.put(CgmesExport.MODELING_AUTHORITY_SET, "Regional Coordination Center"); exportParams.put(CgmesExport.MODEL_DESCRIPTION, "Common Grid Model export"); exportParams.put(CgmesExport.MODEL_VERSION, "4"); + exportParams.put(CgmesExport.BOUNDARY_TP_ID, "ENTSOE TP_BD model ID"); String basename = "test_bare+models+properties"; network.write("CGMES", exportParams, tmpDir.resolve(basename)); String updatedBeSshXml = Files.readString(tmpDir.resolve(basename + "_BE_SSH.xml")); @@ -375,15 +395,19 @@ void testCgmExportWithModelsAndProperties() throws IOException { String updatedNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_4_1D__FM"; String originalBeTpId = "urn:uuid:Network_BE_N_TOPOLOGY_2022-03-04T05:30:00Z_1_1D__FM"; String originalNlTpId = "urn:uuid:Network_NL_N_TOPOLOGY_2022-03-04T05:30:00Z_1_1D__FM"; + String originalTpBdId = "ENTSOE TP_BD model ID"; // the parameter prevails on the extension String additionalDependency = "Additional dependency"; - Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId, additionalDependency); + Set expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, + originalNlTpId, originalTpBdId, additionalDependency); assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON)); - // Each updated IGM SSH should supersede the original one + // Each updated IGM SSH should supersede the original one and depend on the original EQ String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_1_1D__FM"; String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_1_1D__FM"; assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES)); assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES)); + assertEquals(Set.of("BE EQ model ID"), getOccurrences(updatedBeSshXml, REGEX_DEPENDENT_ON)); + assertEquals(Set.of("NL EQ model ID"), getOccurrences(updatedNlSshXml, REGEX_DEPENDENT_ON)); // Profiles should be consistent with the instance files, CGM SV has an additional profile assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedBeSshXml, REGEX_PROFILE)); @@ -647,6 +671,20 @@ private void addModelsForSubnetworks(Network network, int version) { .addSupersedes("BE SSH previous ID") .addProfile("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1") .add() + .newModel() + .setId("BE EQ model ID") + .setSubset(CgmesSubset.EQUIPMENT) + .setVersion(1) + .setModelingAuthoritySet("http://elia.be/CGMES/2.4.15") + .addProfile("http://entsoe.eu/CIM/EquipmentCore/3/1") + .add() + .newModel() + .setId("Common TP_BD model ID") + .setSubset(CgmesSubset.TOPOLOGY_BOUNDARY) + .setVersion(1) + .setModelingAuthoritySet("http://www.entsoe.eu/OperationalPlanning") + .addProfile("http://entsoe.eu/CIM/TopologyBoundary/3/1") + .add() .add(); network.getSubnetwork("Network_NL") .newExtension(CgmesMetadataModelsAdder.class) @@ -659,6 +697,20 @@ private void addModelsForSubnetworks(Network network, int version) { .addSupersedes("NL SSH previous ID") .addProfile("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1") .add() + .newModel() + .setId("NL EQ model ID") + .setSubset(CgmesSubset.EQUIPMENT) + .setVersion(1) + .setModelingAuthoritySet("http://tennet.nl/CGMES/2.4.15") + .addProfile("http://entsoe.eu/CIM/EquipmentCore/3/1") + .add() + .newModel() + .setId("Common TP_BD model ID") + .setSubset(CgmesSubset.TOPOLOGY_BOUNDARY) + .setVersion(1) + .setModelingAuthoritySet("http://www.entsoe.eu/OperationalPlanning") + .addProfile("http://entsoe.eu/CIM/TopologyBoundary/3/1") + .add() .add(); } diff --git a/docs/grid_exchange_formats/cgmes/export.md b/docs/grid_exchange_formats/cgmes/export.md index 5c30e44e3a5..b8121191c1f 100644 --- a/docs/grid_exchange_formats/cgmes/export.md +++ b/docs/grid_exchange_formats/cgmes/export.md @@ -39,8 +39,8 @@ If a version number is given as a parameter, it is used for the exported files. The quick CGM export will always write updated SSH files for IGMs and a single SV for the CGM. The parameter for selecting which profiles to export is ignored in this kind of export. If the dependencies have to be updated automatically (see parameter **iidm.export.cgmes.update-dependencies** below), the exported instance files will contain metadata models where: -* Updated SSH for IGMs supersede the original ones. -* Updated SV for the CGM depends on the updated SSH from IGMs and on the original TP from IGMs. +* Updated SSH for IGMs supersede the original ones, and depend on the original EQ from IGMs. +* Updated SV for the CGM depends on the updated SSH from IGMs and on the original TP and TP_BD from IGMs. The filenames of the exported instance files will follow the pattern: * For the CGM SV: `_SV.xml`. @@ -71,7 +71,7 @@ exampleBase_NL_SSH.xml exampleBase_SV.xml ``` -where the updated SSH files will supersede the original ones, and the SV will contain the correct dependencies of new SSH and original TPs. +where the updated SSH files will supersede the original ones and depend on the original EQs, and the SV will contain the correct dependencies of new SSH and original TPs and TP_BD. (cgmes-cgm-manual-export)= ## CGM (Common Grid Model) manual export