Skip to content

Commit

Permalink
Add a new parameter and logic for CGM export
Browse files Browse the repository at this point in the history
Signed-off-by: Romain Courtier <romain.courtier@rte-france.com>
  • Loading branch information
rcourtier committed Mar 28, 2024
1 parent 487490e commit 5c7cdc4
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
import com.powsybl.cgmes.conversion.export.*;
import com.powsybl.cgmes.conversion.naming.NamingStrategy;
import com.powsybl.cgmes.conversion.naming.NamingStrategyFactory;
import com.powsybl.cgmes.extensions.CgmesMetadataModels;
import com.powsybl.cgmes.model.CgmesMetadataModel;
import com.powsybl.cgmes.model.CgmesNamespace;
import com.powsybl.cgmes.model.CgmesSubset;
import com.powsybl.commons.config.PlatformConfig;
import com.powsybl.commons.datasource.DataSource;
import com.powsybl.commons.exceptions.UncheckedXmlStreamException;
Expand Down Expand Up @@ -81,11 +83,7 @@ public void export(Network network, Properties params, DataSource ds, ReportNode
// If not given explicitly,
// the reference data provider can try to obtain it from the country of the network
// If we have multiple countries we do not pass this info to the reference data provider
Set<String> countries = network.getSubstationStream()
.map(Substation::getCountry)
.flatMap(Optional::stream)
.map(Enum::name)
.collect(Collectors.toUnmodifiableSet());
Set<String> countries = getCountries(network);
if (countries.size() == 1) {
countryName = countries.iterator().next();
}
Expand Down Expand Up @@ -142,48 +140,148 @@ public void export(Network network, Properties params, DataSource ds, ReportNode
context.getExportedSVModel().setVersion(Integer.parseInt(modelVersion));
}

try {
if (Parameter.readBoolean(getFormat(), params, EXPORT_AS_CGM_PARAMETER, defaultValueConfig)) {
/* CGM export
This export consists in providing an updated SSH for the IGMs and an updated SV for the whole CGM
The new updated IGMs SSH shall supersede the original ones
The new updated CGM SV is dependend on the new updated IGMs SHH and on the original IGMs TP
*/

// checkCgmConsistency();

Set<String> updatedIgmSshIds = new HashSet<>();
Set<String> originalIgmTpIds = new HashSet<>();
for (Network subnetwork : network.getSubnetworks()) {
// Retrieve the IGM original SSH and TP model
CgmesMetadataModels originalIgmModels = subnetwork.getExtension(CgmesMetadataModels.class);
Optional<CgmesMetadataModel> originalIgmSshModel = originalIgmModels != null ?
originalIgmModels.getModelForSubset(CgmesSubset.STEADY_STATE_HYPOTHESIS) :
Optional.empty();
Optional<CgmesMetadataModel> originalIgmTpModel = originalIgmModels != null ?
originalIgmModels.getModelForSubset(CgmesSubset.TOPOLOGY) :
Optional.empty();
originalIgmTpModel.ifPresent(m -> originalIgmTpIds.add(m.getId()));

// Create a new IGM SSH model based on the original one
String igmMAS = originalIgmSshModel
.map(CgmesMetadataModel::getModelingAuthoritySet)
.orElseGet(() -> CgmesExportContext.DEFAULT_MODELING_AUTHORITY_SET_VALUE);
CgmesMetadataModel updatedIgmSshModel = new CgmesMetadataModel(CgmesSubset.STEADY_STATE_HYPOTHESIS, igmMAS);
context.getExportedSSHModel().getProfiles().forEach(updatedIgmSshModel::setProfile);
originalIgmSshModel.ifPresent(m -> updatedIgmSshModel.setDescription(m.getDescription()));
originalIgmSshModel.ifPresent(m -> updatedIgmSshModel.setVersion(m.getVersion() + 1));
originalIgmSshModel.ifPresent(m -> updatedIgmSshModel.addSupersedes(m.getId()));

// Export the IGM SSH using the updated model
Set<String> countries = getCountries(subnetwork);
String igmName = countries.size() == 1 ? countries.iterator().next() : subnetwork.getId();
String igmFileNameSsh = baseName + "_" + igmName + "_SSH.xml";
context.addIidmMappings(subnetwork);
subsetExport(subnetwork, CgmesSubset.STEADY_STATE_HYPOTHESIS, igmFileNameSsh, ds, context, Optional.of(updatedIgmSshModel));
updatedIgmSshIds.add(updatedIgmSshModel.getId());
}

// Check for an existing CGM SV model
CgmesMetadataModels originalCgmModels = network.getExtension(CgmesMetadataModels.class);
Optional<CgmesMetadataModel> originalCgmSvModel = originalCgmModels != null ?
originalCgmModels.getModelForSubset(CgmesSubset.STATE_VARIABLES) :
Optional.empty();

// Create a new CGM SV model based on the original one
CgmesMetadataModel updatedCgmSvModel = new CgmesMetadataModel(CgmesSubset.STATE_VARIABLES, masUri);
context.getExportedSVModel().getProfiles().forEach(updatedCgmSvModel::setProfile);
updatedCgmSvModel.addDependentOn(updatedIgmSshIds);
updatedCgmSvModel.addDependentOn(originalIgmTpIds);
originalCgmSvModel.ifPresent(m -> updatedCgmSvModel.setDescription(m.getDescription()));
originalCgmSvModel.ifPresent(m -> updatedCgmSvModel.setVersion(m.getVersion() + 1));

// Export the CGM SV using the new model
subsetExport(network, CgmesSubset.STATE_VARIABLES, filenameSv, ds, context, Optional.of(updatedCgmSvModel));
} else {
// IGM export
context.updateDependenciesIGM();

List<String> profiles = Parameter.readStringList(getFormat(), params, PROFILES_PARAMETER, defaultValueConfig);
checkConsistency(profiles, network, context);
checkIgmConsistency(profiles, network, context);
if (profiles.contains("EQ")) {
try (OutputStream out = new BufferedOutputStream(ds.newOutputStream(filenameEq, false))) {
XMLStreamWriter writer = XmlUtil.initializeWriter(true, INDENT, out);
EquipmentExport.write(network, writer, context);
}
subsetExport(network, CgmesSubset.EQUIPMENT, filenameEq, ds, context, Optional.empty());
} else {
addSubsetIdentifiers(network, "EQ", context.getExportedEQModel());
context.getExportedEQModel().setId(context.getNamingStrategy().getCgmesId(network));
}
if (profiles.contains("TP")) {
try (OutputStream out = new BufferedOutputStream(ds.newOutputStream(filenameTp, false))) {
XMLStreamWriter writer = XmlUtil.initializeWriter(true, INDENT, out);
TopologyExport.write(network, writer, context);
}
subsetExport(network, CgmesSubset.TOPOLOGY, filenameTp, ds, context, Optional.empty());
} else {
addSubsetIdentifiers(network, "TP", context.getExportedTPModel());
}
if (profiles.contains("SSH")) {
try (OutputStream out = new BufferedOutputStream(ds.newOutputStream(filenameSsh, false))) {
XMLStreamWriter writer = XmlUtil.initializeWriter(true, INDENT, out);
SteadyStateHypothesisExport.write(network, writer, context);
}
subsetExport(network, CgmesSubset.STEADY_STATE_HYPOTHESIS, filenameSsh, ds, context, Optional.empty());
} else {
addSubsetIdentifiers(network, "SSH", context.getExportedSSHModel());
}
if (profiles.contains("SV")) {
try (OutputStream out = new BufferedOutputStream(ds.newOutputStream(filenameSv, false))) {
XMLStreamWriter writer = XmlUtil.initializeWriter(true, INDENT, out);
StateVariablesExport.write(network, writer, context);
}
subsetExport(network, CgmesSubset.STATE_VARIABLES, filenameSv, ds, context, Optional.empty());
}
context.getNamingStrategy().debug(baseName, ds);
}
}

/**
* Retrieve all the countries present in a network.
* @param network the network for which the countries are being looked for
* @return a Set of countries present in the network
*/
private static Set<String> getCountries(Network network) {
return network.getSubstationStream()
.map(Substation::getCountry)
.flatMap(Optional::stream)
.map(Enum::name)
.collect(Collectors.toUnmodifiableSet());
}

/**
* Export a CGMES subset of a network.
* @param network the network whose subset is to be exported
* @param subset the CGMES subset to export (accepted values are: EQ/TP/SSH/SV)
* @param fileName the name of the exported file
* @param dataSource the data source used for the export
* @param context the context used for the export
* @param model if provided, the model information to use
*/
private void subsetExport(Network network, CgmesSubset subset, String fileName, DataSource dataSource, CgmesExportContext context, Optional<CgmesMetadataModel> model) {
try (OutputStream out = new BufferedOutputStream(dataSource.newOutputStream(fileName, false))) {
XMLStreamWriter writer = XmlUtil.initializeWriter(true, INDENT, out);
switch (subset) {
case EQUIPMENT:
EquipmentExport.write(network, writer, context);
break;
case TOPOLOGY:
TopologyExport.write(network, writer, context);
break;
case STEADY_STATE_HYPOTHESIS:
SteadyStateHypothesisExport.write(network, writer, context, model);
break;
case STATE_VARIABLES:
StateVariablesExport.write(network, writer, context, model);
break;
default:
throw new IllegalArgumentException("Invalid subset, one of the following value is expected: EQ/TP/SSH/SV.");
}
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}

private static void checkCgmConsistency() {
/* TODO
Verify that each of the subnetwork has a CgmesMetadataModel of part SSH and TP
This is necessary in order to correctly build the references (dependentOn and supersedes)
to the IGM SSH and TP export files
*/
}

private String getBoundaryId(String profile, Network network, Properties params, Parameter parameter, ReferenceDataProvider referenceDataProvider) {
if (network.hasProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + profile + "_BD_ID")) {
return network.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + profile + "_BD_ID");
Expand All @@ -207,7 +305,7 @@ private static void addSubsetIdentifiers(Network network, String profile, CgmesM
.toList());
}

private static void checkConsistency(List<String> profiles, Network network, CgmesExportContext context) {
private static void checkIgmConsistency(List<String> profiles, Network network, CgmesExportContext context) {
boolean networkIsNodeBreaker = network.getVoltageLevelStream()
.map(VoltageLevel::getTopologyKind)
.anyMatch(tk -> tk == TopologyKind.NODE_BREAKER);
Expand Down Expand Up @@ -248,6 +346,7 @@ public String getFormat() {
public static final String EXPORT_POWER_FLOWS_FOR_SWITCHES = "iidm.export.cgmes.export-power-flows-for-switches";
public static final String NAMING_STRATEGY = "iidm.export.cgmes.naming-strategy";
public static final String PROFILES = "iidm.export.cgmes.profiles";
public static final String EXPORT_AS_CGM = "iidm.export.cgmes.export_as_cgm";
public static final String MODELING_AUTHORITY_SET = "iidm.export.cgmes.modeling-authority-set";
public static final String MODEL_DESCRIPTION = "iidm.export.cgmes.model-description";
public static final String EXPORT_TRANSFORMERS_WITH_HIGHEST_VOLTAGE_AT_END1 = "iidm.export.cgmes.export-transformers-with-highest-voltage-at-end1";
Expand Down Expand Up @@ -298,6 +397,11 @@ public String getFormat() {
"Profiles to export",
List.of("EQ", "TP", "SSH", "SV"),
List.of("EQ", "TP", "SSH", "SV"));
private static final Parameter EXPORT_AS_CGM_PARAMETER = new Parameter(
EXPORT_AS_CGM,
ParameterType.BOOLEAN,
"True for a CGM export, False for an IGM export",
CgmesExportContext.EXPORT_AS_CGM_VALUE);
private static final Parameter BOUNDARY_EQ_ID_PARAMETER = new Parameter(
BOUNDARY_EQ_ID,
ParameterType.STRING,
Expand Down Expand Up @@ -376,6 +480,7 @@ public String getFormat() {
EXPORT_POWER_FLOWS_FOR_SWITCHES_PARAMETER,
NAMING_STRATEGY_PARAMETER,
PROFILES_PARAMETER,
EXPORT_AS_CGM_PARAMETER,
BOUNDARY_EQ_ID_PARAMETER,
BOUNDARY_TP_ID_PARAMETER,
MODELING_AUTHORITY_SET_PARAMETER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public class CgmesExportContext {

private NamingStrategy namingStrategy = new NamingStrategy.Identity();

public static final boolean EXPORT_AS_CGM_VALUE = false;
public static final boolean EXPORT_BOUNDARY_POWER_FLOWS_DEFAULT_VALUE = true;
public static final boolean EXPORT_POWER_FLOWS_FOR_SWITCHES_DEFAULT_VALUE = true;
public static final boolean EXPORT_TRANSFORMERS_WITH_HIGHEST_VOLTAGE_AT_END1_DEFAULT_VALUE = false;
Expand Down Expand Up @@ -101,34 +102,7 @@ public class CgmesExportContext {
private final Map<String, Bus> topologicalNodes = new HashMap<>();
private final ReferenceDataProvider referenceDataProvider;

public void updateDependencies(Network network) {
boolean isCGM = network.getSubnetworks().size() > 1;
if (isCGM) {
updateDependenciesCGMSolution(network);
} else {
updateDependenciesIGM();
}
}

private void updateDependenciesCGMSolution(Network network) {
// FIXME(Luma) work in progress
// First step: we only know how to update dependencies of CGM SV solution from IGM (Subnetwork) original TP models
Set<String> igmOriginalTpModels = network.getSubnetworks().stream()
.map(sn -> sn.getExtension(CgmesMetadataModels.class))
.filter(Objects::nonNull)
.map(models -> ((CgmesMetadataModels) models).getModelForSubset(CgmesSubset.TOPOLOGY))
.filter(Optional::isPresent)
.map(Optional::get)
.map(CgmesMetadataModel::getId)
.collect(Collectors.toSet());
Set<String> igmExportedSshModels = Collections.emptySet();
getExportedSVModel()
.clearDependencies()
.addDependentOn(igmOriginalTpModels)
.addDependentOn(igmExportedSshModels);
}

private void updateDependenciesIGM() {
public void updateDependenciesIGM() {
// Update dependencies in a way that:
// [EQ.dependentOn EQ_BD]
// SV.dependentOn TP, SSH[, TP_BD]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,23 +115,23 @@ public static void writeRdfRoot(String cimNamespace, String euPrefix, String euN
}

public static void writeModelDescription(Network network, CgmesSubset subset, XMLStreamWriter writer, CgmesMetadataModel modelDescription, CgmesExportContext context) throws XMLStreamException {
// The ref to build a unique model id must contain:
// the network, the subset (EQ, SSH, SV, ...), the time of the scenario, the version, the business process and the FULL_MODEL part
// If we use name-based UUIDs this ensures that the UUID for the model will be specific enough
CgmesObjectReference[] modelRef = {
refTyped(network),
ref(subset),
ref(DATE_TIME_FORMATTER.format(context.getScenarioTime())),
ref(format(modelDescription.getVersion())),
ref(context.getBusinessProcess()),
Part.FULL_MODEL};
String modelId = "urn:uuid:" + context.getNamingStrategy().getCgmesId(modelRef);
modelDescription.setId(modelId);
context.updateDependencies(network);

if (modelDescription.getId() == null || modelDescription.getId().isEmpty()) {
// The ref to build a unique model id must contain:
// the network, the subset (EQ, SSH, SV, ...), the time of the scenario, the version, the business process and the FULL_MODEL part
// If we use name-based UUIDs this ensures that the UUID for the model will be specific enough
CgmesObjectReference[] modelRef = {
refTyped(network),
ref(subset),
ref(DATE_TIME_FORMATTER.format(context.getScenarioTime())),
ref(String.valueOf(modelDescription.getVersion())),
ref(context.getBusinessProcess()),
Part.FULL_MODEL};
String modelId = "urn:uuid:" + context.getNamingStrategy().getCgmesId(modelRef);
modelDescription.setId(modelId);
}
writer.writeStartElement(MD_NAMESPACE, "FullModel");
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.ABOUT, modelId);
context.getReportNode().newReportNode().withMessageTemplate("CgmesId", modelId).add();
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.ABOUT, modelDescription.getId());
context.getReportNode().newReportNode().withMessageTemplate("CgmesId", modelDescription.getId()).add();
writer.writeStartElement(MD_NAMESPACE, CgmesNames.SCENARIO_TIME);
writer.writeCharacters(DATE_TIME_FORMATTER.format(context.getScenarioTime()));
writer.writeEndElement();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.cgmes.extensions.CgmesTapChanger;
import com.powsybl.cgmes.extensions.CgmesTapChangers;
import com.powsybl.cgmes.model.CgmesMetadataModel;
import com.powsybl.cgmes.model.CgmesNames;
import com.powsybl.cgmes.model.CgmesSubset;
import com.powsybl.commons.PowsyblException;
Expand Down Expand Up @@ -47,12 +48,16 @@ public static void write(Network network, XMLStreamWriter writer) {
}

public static void write(Network network, XMLStreamWriter writer, CgmesExportContext context) {
write(network, writer, context, Optional.empty());
}

public static void write(Network network, XMLStreamWriter writer, CgmesExportContext context, Optional<CgmesMetadataModel> model) {
try {
String cimNamespace = context.getCim().getNamespace();
CgmesExportUtil.writeRdfRoot(cimNamespace, context.getCim().getEuPrefix(), context.getCim().getEuNamespace(), writer);

if (context.getCimVersion() >= 16) {
CgmesExportUtil.writeModelDescription(network, CgmesSubset.STATE_VARIABLES, writer, context.getExportedSVModel(), context);
CgmesExportUtil.writeModelDescription(network, CgmesSubset.STATE_VARIABLES, writer, model.orElseGet(context::getExportedSVModel), context);
writeTopologicalIslands(network, context, writer);
// Note: unmapped topological nodes (node breaker) & boundary topological nodes are not written in topological islands
}
Expand Down
Loading

0 comments on commit 5c7cdc4

Please sign in to comment.