com.powsybl.cgmes.conversion.Conversion Maven / Gradle / Ivy
Show all versions of powsybl-cgmes-conversion Show documentation
/**
* Copyright (c) 2017-2018, 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/.
*/
package com.powsybl.cgmes.conversion;
import com.powsybl.cgmes.conversion.elements.*;
import com.powsybl.cgmes.conversion.elements.hvdc.CgmesDcConversion;
import com.powsybl.cgmes.conversion.elements.transformers.ThreeWindingsTransformerConversion;
import com.powsybl.cgmes.conversion.elements.transformers.TwoWindingsTransformerConversion;
import com.powsybl.cgmes.extensions.*;
import com.powsybl.cgmes.model.*;
import com.powsybl.cgmes.model.triplestore.CgmesModelTripleStore;
import com.powsybl.commons.reporter.Reporter;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.util.Identifiables;
import com.powsybl.triplestore.api.PropertyBag;
import com.powsybl.triplestore.api.PropertyBags;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import static com.powsybl.cgmes.conversion.CgmesReports.importedCgmesNetworkReport;
import static com.powsybl.cgmes.conversion.Conversion.Config.StateProfile.SSH;
/**
* TwoWindingsTransformer Interpretation
*
* Ratio and Phase Interpretation (Xfmr2RatioPhaseInterpretationAlternative)
* END1. All tapChangers (ratioTapChanger and phaseTapChanger) are considered at end1 (before transmission impedance)
* END2. All tapChangers (ratioTapChanger and phaseTapChanger) are considered at end2 (after transmission impedance)
* END1_END2. TapChangers (ratioTapChanger and phaseTapChanger) are considered at the end where they are defined in Cgmes
* X. If x1 == 0 all tapChangers (ratioTapChanger and phaseTapChanger) are considered at the end1 otherwise they are considered at end2
*
* Shunt Admittance Interpretation (Xfmr2ShuntInterpretationAlternative)
* END1. All shunt admittances to ground (g, b) at end1 (before transmission impedance)
* END2. All shunt admittances to ground (g, b) at end2 (after transmission impedance)
* END1_END2. Shunt admittances to ground (g, b) at the end where they are defined in Cgmes model
* SPLIT. Split shunt admittances to ground (g, b) between end1 and end2.
*
* Structural Ratio (Xfmr2StructuralRatioInterpretationAlternative)
* END1. Structural ratio always at end1 (before transmission impedance)
* END2. Structural ratio always at end2 (after transmission impedance)
* X. If x1 == 0 structural ratio at end1, otherwise at end2
*
* ThreeWindingsTransformer Interpretation.
*
* Ratio and Phase Interpretation. (Xfmr3RatioPhaseInterpretationAlternative)
* NETWORK_SIDE. All tapChangers (ratioTapChanger and phaseTapChanger) at the network side.
* STAR_BUS_SIDE. All tapChangers (ratioTapChanger and phaseTapChanger) at the star bus side.
*
* Shunt Admittance Interpretation (Xfmr3ShuntInterpretationAlternative)
* NETWORK_SIDE. Shunt admittances to ground at the network side (end1 of the leg)
* STAR_BUS_SIDE. Shunt admittances to ground at the start bus side (end2 of the leg)
* SPLIT. Split shunt admittances to ground between two ends of the leg
*
* Structural Ratio Interpretation (Xfmr3StructuralRatioInterpretationAlternative)
* STAR_BUS_SIDE. Structural ratio at the star bus side of all legs and RatedU0 = RatedU1
* NETWORK_SIDE. Structural ratio at the network side of all legs. RatedU0 = 1 kv
* END1. Structural ratio at the network side of legs 2 and 3. RatedU0 = RatedU1
* END2. Structural ratio at the network side of legs 1 and 3. RatedU0 = RatedU2
* END3. Structural ratio at the network side of legs 1 and 2. RatedU0 = RatedU2
*
* @author Luma Zamarreño
* @author José Antonio Marqués
*
*/
public class Conversion {
public enum Xfmr2RatioPhaseInterpretationAlternative {
END1, END2, END1_END2, X
}
public enum Xfmr2ShuntInterpretationAlternative {
END1, END2, END1_END2, SPLIT
}
public enum Xfmr2StructuralRatioInterpretationAlternative {
END1, END2, X
}
public enum Xfmr3RatioPhaseInterpretationAlternative {
NETWORK_SIDE, STAR_BUS_SIDE
}
public enum Xfmr3ShuntInterpretationAlternative {
NETWORK_SIDE, STAR_BUS_SIDE, SPLIT
}
public enum Xfmr3StructuralRatioInterpretationAlternative {
NETWORK_SIDE, STAR_BUS_SIDE, END1, END2, END3
}
public Conversion(CgmesModel cgmes) {
this(cgmes, new Config());
}
public Conversion(CgmesModel cgmes, Conversion.Config config) {
this(cgmes, config, Collections.emptyList(), Collections.emptyList());
}
public Conversion(CgmesModel cgmes, Conversion.Config config, List postProcessors) {
this(cgmes, config, Collections.emptyList(), postProcessors, NetworkFactory.findDefault());
}
public Conversion(CgmesModel cgmes, Conversion.Config config, List preProcessors, List postProcessors) {
this(cgmes, config, preProcessors, postProcessors, NetworkFactory.findDefault());
}
public Conversion(CgmesModel cgmes, Config config, List activatedPreProcessors, List activatedPostProcessors, NetworkFactory networkFactory) {
this.cgmes = Objects.requireNonNull(cgmes);
this.config = Objects.requireNonNull(config);
this.preProcessors = Objects.requireNonNull(activatedPreProcessors);
this.postProcessors = Objects.requireNonNull(activatedPostProcessors);
this.networkFactory = Objects.requireNonNull(networkFactory);
}
public void report(Consumer out) {
new ReportTapChangers(cgmes, out).report();
}
public Network convert() {
return convert(Reporter.NO_OP);
}
public Network convert(Reporter reporter) {
Objects.requireNonNull(reporter);
// apply pre-processors before starting the conversion
for (CgmesImportPreProcessor preProcessor : preProcessors) {
preProcessor.process(cgmes);
}
if (LOG.isTraceEnabled() && cgmes.baseVoltages() != null) {
LOG.trace("{}{}{}", "BaseVoltages", System.lineSeparator(), cgmes.baseVoltages().tabulate());
}
// Check that at least we have an EquipmentCore profile
if (!cgmes.hasEquipmentCore()) {
throw new CgmesModelException("Data source does not contain EquipmentCore data");
}
Network network = createNetwork();
Context context = createContext(network, reporter);
assignNetworkProperties(context);
addCgmesSvMetadata(network, context);
addCgmesSshMetadata(network, context);
addCimCharacteristics(network);
BaseVoltageMappingAdder bvAdder = network.newExtension(BaseVoltageMappingAdder.class);
cgmes.baseVoltages().forEach(bv -> bvAdder.addBaseVoltage(bv.getId("BaseVoltage"), bv.asDouble("nominalVoltage"), isBoundaryBaseVoltage(bv.getLocal("graph"))));
bvAdder.add();
Function convf;
cgmes.computedTerminals().forEach(t -> context.terminalMapping().buildTopologicalNodeCgmesTerminalsMapping(t));
cgmes.regulatingControls().forEach(p -> context.regulatingControlMapping().cacheRegulatingControls(p));
convert(cgmes.substations(), s -> new SubstationConversion(s, context));
convert(cgmes.voltageLevels(), vl -> new VoltageLevelConversion(vl, context));
PropertyBags nodes = context.nodeBreaker()
? cgmes.connectivityNodes()
: cgmes.topologicalNodes();
String nodeTypeName = context.nodeBreaker()
? "ConnectivityNode"
: "TopologicalNode";
convert(nodes, n -> new NodeConversion(nodeTypeName, n, context));
if (!context.config().createBusbarSectionForEveryConnectivityNode()) {
convert(cgmes.busBarSections(), bbs -> new BusbarSectionConversion(bbs, context));
}
convert(cgmes.energyConsumers(), ec -> new EnergyConsumerConversion(ec, context));
convert(cgmes.energySources(), es -> new EnergySourceConversion(es, context));
convf = eqi -> new EquivalentInjectionConversion(eqi, context);
convert(cgmes.equivalentInjections(), convf);
convf = eni -> new ExternalNetworkInjectionConversion(eni, context);
convert(cgmes.externalNetworkInjections(), convf);
convert(cgmes.shuntCompensators(), sh -> new ShuntConversion(sh, context));
convert(cgmes.equivalentShunts(), es -> new EquivalentShuntConversion(es, context));
convf = svc -> new StaticVarCompensatorConversion(svc, context);
convert(cgmes.staticVarCompensators(), convf);
convf = asm -> new AsynchronousMachineConversion(asm, context);
convert(cgmes.asynchronousMachines(), convf);
convert(cgmes.synchronousMachines(), sm -> new SynchronousMachineConversion(sm, context));
// We will delay the conversion of some lines/switches that have an end at boundary
// They have to be processed after all lines/switches have been reviewed
// FIXME(Luma) store delayedBoundaryNodes in context
Set delayedBoundaryNodes = new HashSet<>();
convertSwitches(context, delayedBoundaryNodes);
convertACLineSegmentsToLines(context, delayedBoundaryNodes);
convertEquivalentBranchesToLines(context, delayedBoundaryNodes);
convert(cgmes.seriesCompensators(), sc -> new SeriesCompensatorConversion(sc, context));
convertTransformers(context, delayedBoundaryNodes);
delayedBoundaryNodes.forEach(node -> convertEquipmentAtBoundaryNode(context, node));
CgmesDcConversion cgmesDcConversion = new CgmesDcConversion(cgmes, context);
cgmesDcConversion.convert();
convert(cgmes.operationalLimits(), l -> new OperationalLimitConversion(l, context));
context.loadingLimitsMapping().addAll();
if (config.convertSvInjections()) {
convert(cgmes.svInjections(), si -> new SvInjectionConversion(si, context));
}
clearUnattachedHvdcConverterStations(network, context); // in case of faulty CGMES files, remove HVDC Converter Stations without HVDC lines
voltageAngles(nodes, context);
if (config.importControlAreas()) {
network.newExtension(CgmesControlAreasAdder.class).add();
CgmesControlAreas cgmesControlAreas = network.getExtension(CgmesControlAreas.class);
cgmes.controlAreas().forEach(ca -> createControlArea(cgmesControlAreas, ca));
cgmes.tieFlows().forEach(tf -> addTieFlow(context, cgmesControlAreas, tf));
cgmesControlAreas.cleanIfEmpty();
}
// set all regulating controls
context.regulatingControlMapping().setAllRegulatingControls(network);
if (context.config().debugTopology()) {
debugTopology(context);
}
if (config.storeCgmesModelAsNetworkExtension()) {
// Store a reference to the original CGMES model inside the IIDM network
network.newExtension(CgmesModelExtensionAdder.class).withModel(cgmes).add();
}
// apply post-processors
for (CgmesImportPostProcessor postProcessor : postProcessors) {
// FIXME generic cgmes models may not have an underlying triplestore
// TODO maybe pass the properties to the post processors
postProcessor.process(network, cgmes.tripleStore());
}
// Complete Voltages and angles in starBus as properties
// Complete Voltages and angles in boundary buses
completeVoltagesAndAngles(network);
if (config.storeCgmesConversionContextAsNetworkExtension()) {
// Store the terminal mapping in an extension for external validation
network.newExtension(CgmesConversionContextExtensionAdder.class).withContext(context).add();
}
importedCgmesNetworkReport(context.getReporter(), network.getId());
return network;
}
private Source isBoundaryBaseVoltage(String graph) {
//There are unit tests where the boundary file contains the sequence "EQBD" and others "EQ_BD"
return graph.contains("EQ") && graph.contains("BD") ? Source.BOUNDARY : Source.IGM;
}
private static void completeVoltagesAndAngles(Network network) {
// Voltage and angle in starBus as properties
network.getThreeWindingsTransformers()
.forEach(ThreeWindingsTransformerConversion::calculateVoltageAndAngleInStarBus);
// Voltage and angle in boundary buses
network.getDanglingLineStream(DanglingLineFilter.UNPAIRED)
.forEach(AbstractConductingEquipmentConversion::calculateVoltageAndAngleInBoundaryBus);
}
private static void createControlArea(CgmesControlAreas cgmesControlAreas, PropertyBag ca) {
String controlAreaId = ca.getId("ControlArea");
cgmesControlAreas.newCgmesControlArea()
.setId(controlAreaId)
.setName(ca.getLocal("name"))
.setEnergyIdentificationCodeEic(ca.getLocal("energyIdentCodeEic"))
.setNetInterchange(ca.asDouble("netInterchange", Double.NaN))
.setPTolerance(ca.asDouble("pTolerance", Double.NaN))
.add();
}
private static void addTieFlow(Context context, CgmesControlAreas cgmesControlAreas, PropertyBag tf) {
String controlAreaId = tf.getId("ControlArea");
CgmesControlArea cgmesControlArea = cgmesControlAreas.getCgmesControlArea(controlAreaId);
if (cgmesControlArea == null) {
context.ignored("Tie Flow", String.format("Tie Flow %s refers to a non-existing control area", tf.getId("TieFlow")));
return;
}
String terminalId = tf.getId("terminal");
Boundary boundary = context.terminalMapping().findBoundary(terminalId, context.cgmes());
if (boundary != null) {
cgmesControlArea.add(boundary);
return;
}
RegulatingTerminalMapper.mapForTieFlow(terminalId, context).ifPresent(cgmesControlArea::add);
}
private void convert(
PropertyBags elements,
Function f) {
String logTitle = null;
for (PropertyBag element : elements) {
AbstractObjectConversion c = f.apply(element);
if (LOG.isTraceEnabled()) {
if (logTitle == null) {
logTitle = c.getClass().getSimpleName();
logTitle = logTitle.replace("Conversion", "");
}
LOG.trace(element.tabulateLocals(logTitle));
}
if (c.insideBoundary()) {
c.convertInsideBoundary();
} else if (c.valid()) {
c.convert();
}
}
}
private Network createNetwork() {
String networkId = cgmes.modelId();
String sourceFormat = "CGMES";
return networkFactory.createNetwork(networkId, sourceFormat);
}
private Context createContext(Network network, Reporter reporter) {
Context context = new Context(cgmes, config, network, reporter);
context.substationIdMapping().build();
context.dc().initialize();
context.loadRatioTapChangers();
context.loadPhaseTapChangers();
context.loadRatioTapChangerTables();
context.loadPhaseTapChangerTables();
context.loadReactiveCapabilityCurveData();
return context;
}
private void assignNetworkProperties(Context context) {
context.network().setProperty(NETWORK_PS_CGMES_MODEL_DETAIL,
context.nodeBreaker()
? NETWORK_PS_CGMES_MODEL_DETAIL_NODE_BREAKER
: NETWORK_PS_CGMES_MODEL_DETAIL_BUS_BRANCH);
PropertyBags modelProfiles = context.cgmes().modelProfiles();
String fullModel = "FullModel";
modelProfiles.sort(Comparator.comparing(p -> p.getId(fullModel)));
for (PropertyBag modelProfile : modelProfiles) { // Import of profiles ID as properties TODO import them in a dedicated extension
if (modelProfile.getId(fullModel).equals(context.network().getId())) {
continue;
}
String profile = CgmesNamespace.getProfile(modelProfile.getId("profile"));
if (profile != null && !"EQ_OP".equals(profile) && !"SV".equals(profile)) { // don't import EQ_OP and SV profiles as they are not used for CGMES export
context.network()
.setProperty(Identifiables.getUniqueId(CGMES_PREFIX_ALIAS_PROPERTIES + profile + "_ID", property -> context.network().hasProperty(property)),
modelProfile.getId(fullModel));
}
}
DateTime modelScenarioTime = cgmes.scenarioTime();
DateTime modelCreated = cgmes.created();
long forecastDistance = new Duration(modelCreated, modelScenarioTime).getStandardMinutes();
context.network().setForecastDistance(forecastDistance >= 0 ? (int) forecastDistance : 0);
context.network().setCaseDate(modelScenarioTime);
LOG.info("cgmes scenarioTime : {}", modelScenarioTime);
LOG.info("cgmes modelCreated : {}", modelCreated);
LOG.info("network caseDate : {}", context.network().getCaseDate());
LOG.info("network forecastDistance : {}", context.network().getForecastDistance());
}
private void addCgmesSvMetadata(Network network, Context context) {
PropertyBags svDescription = cgmes.fullModel(CgmesSubset.STATE_VARIABLES.getProfile());
if (svDescription != null && !svDescription.isEmpty()) {
CgmesSvMetadataAdder adder = network.newExtension(CgmesSvMetadataAdder.class)
.setDescription(svDescription.get(0).get("description"))
.setSvVersion(readVersion(svDescription, context))
.setModelingAuthoritySet(svDescription.get(0).get("modelingAuthoritySet"));
svDescription.pluckLocals("DependentOn").forEach(adder::addDependency);
adder.add();
}
}
private void addCgmesSshMetadata(Network network, Context context) {
PropertyBags sshDescription = cgmes.fullModel(CgmesSubset.STEADY_STATE_HYPOTHESIS.getProfile());
if (sshDescription != null && !sshDescription.isEmpty()) {
CgmesSshMetadataAdder adder = network.newExtension(CgmesSshMetadataAdder.class)
.setId(sshDescription.get(0).get("FullModel"))
.setDescription(sshDescription.get(0).get("description"))
.setSshVersion(readVersion(sshDescription, context))
.setModelingAuthoritySet(sshDescription.get(0).get("modelingAuthoritySet"));
sshDescription.pluckLocals("DependentOn").forEach(adder::addDependency);
adder.add();
}
}
private int readVersion(PropertyBags propertyBags, Context context) {
try {
return propertyBags.get(0).asInt("version");
} catch (NumberFormatException e) {
context.fixed("Version", "The version is expected to be an integer: " + propertyBags.get(0).get("version") + ". Fixed to 1");
return 1;
}
}
private void addCimCharacteristics(Network network) {
if (cgmes instanceof CgmesModelTripleStore cgmesModelTripleStore) {
network.newExtension(CimCharacteristicsAdder.class)
.setTopologyKind(cgmes.isNodeBreaker() ? CgmesTopologyKind.NODE_BREAKER : CgmesTopologyKind.BUS_BRANCH)
.setCimVersion(cgmesModelTripleStore.getCimVersion())
.add();
}
}
private void putVoltageLevelRefByLineContainerIdIfPresent(String lineContainerId, Supplier terminalId1,
Supplier terminalId2,
Map nominalVoltageByLineContainerId,
Context context) {
String vlId = Optional.ofNullable(context.namingStrategy().getIidmId("VoltageLevel",
context.cgmes().voltageLevel(cgmes.terminal(terminalId1.get()), context.nodeBreaker())))
.orElseGet(() -> context.namingStrategy().getIidmId("VoltageLevel",
context.cgmes().voltageLevel(cgmes.terminal(terminalId2.get()), context.nodeBreaker())));
if (vlId != null) {
VoltageLevel vl = context.network().getVoltageLevel(vlId);
if (vl != null) {
nominalVoltageByLineContainerId.put(lineContainerId, vl);
}
}
}
private void convertACLineSegmentsToLines(Context context, Set delayedBoundaryNodes) {
Map voltageLevelRefByLineContainerId = new HashMap<>();
PropertyBags acLineSegments = cgmes.acLineSegments();
for (PropertyBag line : acLineSegments) { // Retrieve a voltage level reference for every line container of AC Line Segments outside boundaries
String lineContainerId = line.getId("Line");
if (lineContainerId != null && !voltageLevelRefByLineContainerId.containsKey(lineContainerId)) {
putVoltageLevelRefByLineContainerIdIfPresent(lineContainerId, () -> line.getId("Terminal1"), () -> line.getId("Terminal2"),
voltageLevelRefByLineContainerId, context);
}
}
for (PropertyBag line : acLineSegments) {
if (LOG.isTraceEnabled()) {
LOG.trace(line.tabulateLocals("ACLineSegment"));
}
String lineContainerId = line.getId("Line");
if (lineContainerId != null) { // Create fictitious voltage levels for AC line segments inside line containers outside boundaries
VoltageLevel vlRef = voltageLevelRefByLineContainerId.get(lineContainerId);
createLineContainerFictitiousVoltageLevels(context, lineContainerId, vlRef, line);
}
ACLineSegmentConversion c = new ACLineSegmentConversion(line, context);
if (c.valid()) {
String node = c.boundaryNode();
if (node != null && !context.config().convertBoundary()) {
context.boundary().addAcLineSegmentAtNode(line, node);
delayedBoundaryNodes.add(node);
} else {
c.convert();
}
}
}
}
static class LineContainerFictitiousVoltageLevelData {
String lineId;
String lineName;
String nodeId;
VoltageLevel vl;
String idForFictitiousVoltageLevel() {
return nodeId + "_VL";
}
}
private LineContainerFictitiousVoltageLevelData voltageLevelDataForACLSinLineContainer(Context context, String lineId, PropertyBag lineSegment, String terminalRef) {
LineContainerFictitiousVoltageLevelData vldata = new LineContainerFictitiousVoltageLevelData();
vldata.lineId = lineId;
vldata.lineName = lineSegment.get("lineName");
CgmesTerminal t = cgmes.terminal(lineSegment.getId(terminalRef));
vldata.nodeId = context.nodeBreaker() ? t.connectivityNode() : t.topologicalNode();
String vlId = context.namingStrategy().getIidmId("VoltageLevel", context.cgmes().voltageLevel(t, context.nodeBreaker()));
if (vlId != null) {
vldata.vl = context.network().getVoltageLevel(vlId);
} else {
vldata.vl = context.network().getVoltageLevel(vldata.idForFictitiousVoltageLevel());
}
return vldata;
}
private void createLineContainerFictitiousVoltageLevels(Context context, String lineId, VoltageLevel vlRef, PropertyBag lineSegment) {
// Try to obtain data for a potential fictitious voltage level from Terminal1 of AC Line Segment
LineContainerFictitiousVoltageLevelData vldata1 = voltageLevelDataForACLSinLineContainer(context, lineId, lineSegment, "Terminal1");
// The same, from Terminal2 of AC Line Segment
LineContainerFictitiousVoltageLevelData vldata2 = voltageLevelDataForACLSinLineContainer(context, lineId, lineSegment, "Terminal2");
// Only create a fictitious voltage levels replacing cim:Line Container if we are NOT at boundaries
if (vldata1.vl == null && !context.boundary().containsNode(vldata1.nodeId)) {
createLineContainerFictitiousVoltageLevel(context, vldata1, vlRef);
}
if (vldata2.vl == null && !context.boundary().containsNode(vldata2.nodeId)) {
createLineContainerFictitiousVoltageLevel(context, vldata2, vlRef);
}
}
private void createLineContainerFictitiousVoltageLevel(Context context, LineContainerFictitiousVoltageLevelData vldata, VoltageLevel vlref) {
String id = vldata.idForFictitiousVoltageLevel();
LOG.warn("Fictitious Voltage Level {} created for Line container {} node {}", id, vldata.lineId, vldata.lineName);
// Nominal voltage and low/high limits are copied from the reference voltage level, if it is given
VoltageLevel vl = context.network().newVoltageLevel()
.setNominalV(vlref.getNominalV())
.setTopologyKind(
context.nodeBreaker()
? TopologyKind.NODE_BREAKER
: TopologyKind.BUS_BREAKER)
.setLowVoltageLimit(vlref.getLowVoltageLimit())
.setHighVoltageLimit(vlref.getHighVoltageLimit())
.setId(id)
.setName(vldata.lineName)
.setEnsureIdUnicity(context.config().isEnsureIdAliasUnicity())
.add();
vl.setProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "LineContainerId", vldata.lineId);
if (!context.nodeBreaker()) {
vl.getBusBreakerView().newBus().setId(vldata.nodeId).add();
}
}
private void convertSwitches(Context context, Set delayedBoundaryNodes) {
for (PropertyBag sw : cgmes.switches()) {
if (LOG.isTraceEnabled()) {
LOG.trace(sw.tabulateLocals("Switch"));
}
SwitchConversion c = new SwitchConversion(sw, context);
if (c.valid()) {
String node = c.boundaryNode();
if (node != null && !context.config().convertBoundary()) {
context.boundary().addSwitchAtNode(sw, node);
delayedBoundaryNodes.add(node);
} else {
c.convert();
}
}
}
}
private void convertEquivalentBranchesToLines(Context context, Set delayedBoundaryNodes) {
for (PropertyBag equivalentBranch : cgmes.equivalentBranches()) {
if (LOG.isTraceEnabled()) {
LOG.trace(equivalentBranch.tabulateLocals("EquivalentBranch"));
}
EquivalentBranchConversion c = new EquivalentBranchConversion(equivalentBranch, context);
if (c.valid()) {
String node = c.boundaryNode();
if (node != null && !context.config().convertBoundary()) {
context.boundary().addEquivalentBranchAtNode(equivalentBranch, node);
delayedBoundaryNodes.add(node);
} else {
c.convert();
}
}
}
}
private void convertTransformers(Context context, Set delayedBoundaryNodes) {
cgmes.groupedTransformerEnds().forEach((t, ends) -> {
if (LOG.isTraceEnabled()) {
LOG.trace("Transformer {}, {}-winding", t, ends.size());
ends.forEach(e -> LOG.trace(e.tabulateLocals("TransformerEnd")));
}
if (ends.size() == 2) {
convertTwoWindingsTransformers(context, ends, delayedBoundaryNodes);
} else if (ends.size() == 3) {
convertThreeWindingsTransformers(context, ends);
} else {
String what = "PowerTransformer " + t;
Supplier reason = () -> String.format("Has %d ends. Only 2 or 3 ends are supported", ends.size());
context.invalid(what, reason);
}
});
}
private static void convertTwoWindingsTransformers(Context context, PropertyBags ends, Set delayedBoundaryNodes) {
AbstractConductingEquipmentConversion c = new TwoWindingsTransformerConversion(ends, context);
if (c.valid()) {
String node = c.boundaryNode();
if (node != null && !context.config().convertBoundary()) {
context.boundary().addTransformerAtNode(ends, node);
delayedBoundaryNodes.add(node);
} else {
c.convert();
}
}
}
private static void convertThreeWindingsTransformers(Context context, PropertyBags ends) {
AbstractConductingEquipmentConversion c = new ThreeWindingsTransformerConversion(ends, context);
if (c.valid()) {
c.convert();
}
}
// Supported conversions:
// Only one Line (--> create dangling line)
// Only one Switch (--> create dangling line with z0)
// Only one Transformer (--> create dangling line)
// Only one EquivalentBranch (--> create dangling line)
// Any combination of Line, Switch, Transformer and EquivalentBranch
private void convertEquipmentAtBoundaryNode(Context context, String node) {
// At least each delayed boundary node should have one equipment attached to it
// Currently supported equipment at boundary are lines and switches
List beqs = context.boundary().boundaryEquipmentAtNode(node);
if (LOG.isDebugEnabled()) {
LOG.debug("Delayed boundary node {} with {} equipment at it", node, beqs.size());
beqs.forEach(BoundaryEquipment::log);
}
int numEquipmentsAtNode = beqs.size();
if (numEquipmentsAtNode == 1) {
beqs.get(0).createConversion(context).convertAtBoundary();
} else if (numEquipmentsAtNode == 2) {
convertTwoEquipmentsAtBoundaryNode(context, node, beqs.get(0), beqs.get(1));
} else if (numEquipmentsAtNode > 2) {
// In some TYNDP there are three acLineSegments at the boundary node,
// one of them disconnected. The two connected acLineSegments are imported.
List connectedBeqs = beqs.stream()
.filter(beq -> !beq.isAcLineSegmentDisconnected(context)).toList();
if (connectedBeqs.size() == 2) {
convertTwoEquipmentsAtBoundaryNode(context, node, connectedBeqs.get(0), connectedBeqs.get(1));
// There can be multiple disconnected ACLineSegment to the same X-node (for example, for planning purposes)
beqs.stream().filter(beq -> !connectedBeqs.contains(beq)).toList()
.forEach(beq -> {
context.fixed("convertEquipmentAtBoundaryNode",
String.format("Multiple AcLineSegments at boundary %s. Disconnected AcLineSegment %s is imported as a dangling line.", node, beq.getAcLineSegmentId()));
beq.createConversion(context).convertAtBoundary();
});
} else {
// This case should not happen and will not result in an equivalent network at the end of the conversion
context.fixed(node, "More than two connected AcLineSegments at boundary: only dangling lines are created." +
" Please note that the converted IIDM network will probably not be equivalent to the CGMES network.");
beqs.forEach(beq -> beq.createConversion(context).convertAtBoundary());
}
}
}
private static void convertTwoEquipmentsAtBoundaryNode(Context context, String node, BoundaryEquipment beq1, BoundaryEquipment beq2) {
EquipmentAtBoundaryConversion conversion1 = beq1.createConversion(context);
EquipmentAtBoundaryConversion conversion2 = beq2.createConversion(context);
BoundaryLine boundaryLine1 = conversion1.asBoundaryLine(node);
BoundaryLine boundaryLine2 = conversion2.asBoundaryLine(node);
if (boundaryLine1 != null && boundaryLine2 != null) {
// there can be several dangling lines linked to same x-node in one IGM for planning purposes
// in this case, we don't merge them
// please note that only one of them should be connected
String regionName1 = context.network().getVoltageLevel(boundaryLine1.getModelIidmVoltageLevelId()).getSubstation()
.map(s -> s.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "regionName"))
.orElse(null);
String regionName2 = context.network().getVoltageLevel(boundaryLine2.getModelIidmVoltageLevelId()).getSubstation()
.map(s -> s.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "regionName"))
.orElse(null);
if (regionName1 != null && regionName1.equals(regionName2)) {
context.ignored(node, "Both dangling lines are in the same voltage level: we do not consider them as a merged line");
conversion1.convertAtBoundary();
conversion2.convertAtBoundary();
} else if (boundaryLine2.getId().compareTo(boundaryLine1.getId()) >= 0) {
ACLineSegmentConversion.convertBoundaryLines(context, node, boundaryLine1, boundaryLine2);
} else {
ACLineSegmentConversion.convertBoundaryLines(context, node, boundaryLine2, boundaryLine1);
}
} else {
context.invalid(node, "Unexpected boundaryLine");
}
}
private void voltageAngles(PropertyBags nodes, Context context) {
if (context.nodeBreaker()) {
// TODO(Luma): we create again one conversion object for every node
// In node-breaker conversion,
// set (voltage, angle) values after all nodes have been created and connected
for (PropertyBag n : nodes) {
NodeConversion nc = new NodeConversion("ConnectivityNode", n, context);
if (!nc.insideBoundary() || nc.insideBoundary() && context.config().convertBoundary()) {
nc.setVoltageAngleNodeBreaker();
}
}
}
}
private void clearUnattachedHvdcConverterStations(Network network, Context context) {
network.getHvdcConverterStationStream()
.filter(converter -> converter.getHvdcLine() == null)
.peek(converter -> context.ignored("HVDC Converter Station " + converter.getId(), "No correct linked HVDC line found."))
.toList()
.forEach(Connectable::remove);
}
private void debugTopology(Context context) {
context.network().getVoltageLevels().forEach(vl -> {
String name = vl.getSubstation().map(s -> s.getNameOrId() + "-").orElse("") + vl.getNameOrId();
name = name.replace('/', '-');
Path file = Paths.get(System.getProperty("java.io.tmpdir"), "temp-cgmes-" + name + ".dot");
try {
vl.exportTopology(file);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
public static class Config {
public enum StateProfile {
SSH,
SV
}
public List substationIdsExcludedFromMapping() {
return Collections.emptyList();
}
public boolean debugTopology() {
return false;
}
public boolean allowUnsupportedTapChangers() {
return allowUnsupportedTapChangers;
}
public Config setAllowUnsupportedTapChangers(boolean allowUnsupportedTapChangers) {
this.allowUnsupportedTapChangers = allowUnsupportedTapChangers;
return this;
}
public boolean useNodeBreaker() {
return true;
}
public double lowImpedanceLineR() {
return lowImpedanceLineR;
}
public double lowImpedanceLineX() {
return lowImpedanceLineX;
}
public boolean convertBoundary() {
return convertBoundary;
}
public Config setConvertBoundary(boolean convertBoundary) {
this.convertBoundary = convertBoundary;
return this;
}
public boolean changeSignForShuntReactivePowerFlowInitialState() {
return changeSignForShuntReactivePowerFlowInitialState;
}
public Config setChangeSignForShuntReactivePowerFlowInitialState(boolean b) {
changeSignForShuntReactivePowerFlowInitialState = b;
return this;
}
public boolean computeFlowsAtBoundaryDanglingLines() {
return true;
}
public boolean createBusbarSectionForEveryConnectivityNode() {
return createBusbarSectionForEveryConnectivityNode;
}
public Config setCreateBusbarSectionForEveryConnectivityNode(boolean b) {
createBusbarSectionForEveryConnectivityNode = b;
return this;
}
public boolean convertSvInjections() {
return convertSvInjections;
}
public Config setConvertSvInjections(boolean convertSvInjections) {
this.convertSvInjections = convertSvInjections;
return this;
}
public StateProfile getProfileForInitialValuesShuntSectionsTapPositions() {
return profileForInitialValuesShuntSectionsTapPositions;
}
public Config setProfileForInitialValuesShuntSectionsTapPositions(String profileForInitialValuesShuntSectionsTapPositions) {
switch (Objects.requireNonNull(profileForInitialValuesShuntSectionsTapPositions)) {
case "SSH":
case "SV":
this.profileForInitialValuesShuntSectionsTapPositions = StateProfile.valueOf(profileForInitialValuesShuntSectionsTapPositions);
break;
default:
throw new CgmesModelException("Unexpected profile used for shunt sections / tap positions state hypothesis: " + profileForInitialValuesShuntSectionsTapPositions);
}
return this;
}
public boolean storeCgmesModelAsNetworkExtension() {
return storeCgmesModelAsNetworkExtension;
}
public Config setStoreCgmesModelAsNetworkExtension(boolean storeCgmesModelAsNetworkExtension) {
this.storeCgmesModelAsNetworkExtension = storeCgmesModelAsNetworkExtension;
return this;
}
public boolean storeCgmesConversionContextAsNetworkExtension() {
return storeCgmesConversionContextAsNetworkExtension;
}
public Config setStoreCgmesConversionContextAsNetworkExtension(boolean storeCgmesTerminalMappingAsNetworkExtension) {
this.storeCgmesConversionContextAsNetworkExtension = storeCgmesTerminalMappingAsNetworkExtension;
return this;
}
public boolean createActivePowerControlExtension() {
return createActivePowerControlExtension;
}
public Config setCreateActivePowerControlExtension(boolean createActivePowerControlExtension) {
this.createActivePowerControlExtension = createActivePowerControlExtension;
return this;
}
public boolean isEnsureIdAliasUnicity() {
return ensureIdAliasUnicity;
}
public Config setEnsureIdAliasUnicity(boolean ensureIdAliasUnicity) {
this.ensureIdAliasUnicity = ensureIdAliasUnicity;
return this;
}
public boolean importControlAreas() {
return importControlAreas;
}
public Config setImportControlAreas(boolean importControlAreas) {
this.importControlAreas = importControlAreas;
return this;
}
public NamingStrategy getNamingStrategy() {
return namingStrategy;
}
public Config setNamingStrategy(NamingStrategy namingStrategy) {
this.namingStrategy = Objects.requireNonNull(namingStrategy);
return this;
}
public Xfmr2RatioPhaseInterpretationAlternative getXfmr2RatioPhase() {
return xfmr2RatioPhase;
}
public void setXfmr2RatioPhase(Xfmr2RatioPhaseInterpretationAlternative alternative) {
xfmr2RatioPhase = alternative;
}
public Xfmr2ShuntInterpretationAlternative getXfmr2Shunt() {
return xfmr2Shunt;
}
public void setXfmr2Shunt(Xfmr2ShuntInterpretationAlternative alternative) {
xfmr2Shunt = alternative;
}
public Xfmr2StructuralRatioInterpretationAlternative getXfmr2StructuralRatio() {
return xfmr2StructuralRatio;
}
public void setXfmr2StructuralRatio(Xfmr2StructuralRatioInterpretationAlternative alternative) {
xfmr2StructuralRatio = alternative;
}
public Xfmr3RatioPhaseInterpretationAlternative getXfmr3RatioPhase() {
return xfmr3RatioPhase;
}
public void setXfmr3RatioPhase(Xfmr3RatioPhaseInterpretationAlternative alternative) {
this.xfmr3RatioPhase = alternative;
}
public Xfmr3ShuntInterpretationAlternative getXfmr3Shunt() {
return xfmr3Shunt;
}
public void setXfmr3Shunt(Xfmr3ShuntInterpretationAlternative alternative) {
xfmr3Shunt = alternative;
}
public Xfmr3StructuralRatioInterpretationAlternative getXfmr3StructuralRatio() {
return xfmr3StructuralRatio;
}
public void setXfmr3StructuralRatio(Xfmr3StructuralRatioInterpretationAlternative alternative) {
xfmr3StructuralRatio = alternative;
}
public CgmesImport.FictitiousSwitchesCreationMode getCreateFictitiousSwitchesForDisconnectedTerminalsMode() {
return createFictitiousSwitchesForDisconnectedTerminalsMode;
}
public Config createFictitiousSwitchesForDisconnectedTerminalsMode(CgmesImport.FictitiousSwitchesCreationMode createFictitiousSwitchesForDisconnectedTerminalsMode) {
this.createFictitiousSwitchesForDisconnectedTerminalsMode = createFictitiousSwitchesForDisconnectedTerminalsMode;
return this;
}
private boolean allowUnsupportedTapChangers = true;
private boolean convertBoundary = false;
private boolean changeSignForShuntReactivePowerFlowInitialState = false;
private double lowImpedanceLineR = 7.0E-5;
private double lowImpedanceLineX = 7.0E-5;
private boolean createBusbarSectionForEveryConnectivityNode = false;
private boolean convertSvInjections = true;
private StateProfile profileForInitialValuesShuntSectionsTapPositions = SSH;
private boolean storeCgmesModelAsNetworkExtension = true;
private boolean storeCgmesConversionContextAsNetworkExtension = false;
private boolean createActivePowerControlExtension = false;
private CgmesImport.FictitiousSwitchesCreationMode createFictitiousSwitchesForDisconnectedTerminalsMode = CgmesImport.FictitiousSwitchesCreationMode.ALWAYS;
private boolean ensureIdAliasUnicity = false;
private boolean importControlAreas = true;
private NamingStrategy namingStrategy = new NamingStrategy.Identity();
// Default interpretation.
private Xfmr2RatioPhaseInterpretationAlternative xfmr2RatioPhase = Xfmr2RatioPhaseInterpretationAlternative.END1_END2;
private Xfmr2ShuntInterpretationAlternative xfmr2Shunt = Xfmr2ShuntInterpretationAlternative.END1_END2;
private Xfmr2StructuralRatioInterpretationAlternative xfmr2StructuralRatio = Xfmr2StructuralRatioInterpretationAlternative.X;
private Xfmr3RatioPhaseInterpretationAlternative xfmr3RatioPhase = Xfmr3RatioPhaseInterpretationAlternative.NETWORK_SIDE;
private Xfmr3ShuntInterpretationAlternative xfmr3Shunt = Xfmr3ShuntInterpretationAlternative.NETWORK_SIDE;
private Xfmr3StructuralRatioInterpretationAlternative xfmr3StructuralRatio = Xfmr3StructuralRatioInterpretationAlternative.STAR_BUS_SIDE;
}
private final CgmesModel cgmes;
private final Config config;
private final List postProcessors;
private final List preProcessors;
private final NetworkFactory networkFactory;
private static final Logger LOG = LoggerFactory.getLogger(Conversion.class);
public static final String NETWORK_PS_CGMES_MODEL_DETAIL = "CGMESModelDetail";
public static final String NETWORK_PS_CGMES_MODEL_DETAIL_BUS_BRANCH = "bus-branch";
public static final String NETWORK_PS_CGMES_MODEL_DETAIL_NODE_BREAKER = "node-breaker";
public static final String CGMES_PREFIX_ALIAS_PROPERTIES = "CGMES.";
public static final String PROPERTY_IS_CREATED_FOR_DISCONNECTED_TERMINAL = CGMES_PREFIX_ALIAS_PROPERTIES + "isCreatedForDisconnectedTerminal";
public static final String PROPERTY_IS_EQUIVALENT_SHUNT = CGMES_PREFIX_ALIAS_PROPERTIES + "isEquivalentShunt";
}