com.powsybl.cim1.converter.CIM1Converter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of powsybl-cim1-converter Show documentation
Show all versions of powsybl-cim1-converter Show documentation
A converter implementation for CIM ENTSO-E V1 networks
/**
* Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium)
* 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.cim1.converter;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.powsybl.entsoe.util.EntsoeFileName;
import com.powsybl.iidm.network.*;
import org.jgrapht.UndirectedGraph;
import org.jgrapht.alg.ConnectivityInspector;
import org.jgrapht.graph.Pseudograph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.Supplier;
/**
* Building of an IIDM network model based on a CIM network model
*
* @author Olivier Bretteville
*/
class CIM1Converter implements CIM1Constants {
private static final Logger LOGGER = LoggerFactory.getLogger(CIM1Converter.class);
private static final String CIM_ENTSOE_PROFILE_1ST_EDITION_VERSION = "IEC61970CIM14v02";
private static final String XNODE_V_PROPERTY = "xnode_v";
private static final String XNODE_ANGLE_PROPERTY = "xnode_angle";
private final cim1.model.CIMModel cimModel;
private final String fileName;
private final CIM1ConverterConfig config;
private final CIM1NamingStrategy namingStrategy;
private final Map svVoltages = new IdentityHashMap<>();
private final List lowImpedanceLines = new ArrayList<>();
CIM1Converter(cim1.model.CIMModel cimModel, String fileName, CIM1ConverterConfig config) {
this.cimModel = Objects.requireNonNull(cimModel);
this.fileName = Objects.requireNonNull(fileName);
if (fileName.isEmpty()) {
throw new IllegalArgumentException("File name is null");
}
this.config = Objects.requireNonNull(config);
namingStrategy = config.getNamingStrategyFactory().create(cimModel);
}
private final Multimap terminalMapping = HashMultimap.create();
private final Map boundaryXNodes = new IdentityHashMap<>();
private final Map> mergedXNodes = new IdentityHashMap<>();
private void addTerminalMapping(cim1.model.TopologicalNode tn, Terminal t) {
terminalMapping.put(tn.getId(), t);
}
private Terminal getTerminalMapping(cim1.model.TopologicalNode tn) {
Collection terminals = terminalMapping.get(tn.getId());
if (terminals.isEmpty()) {
throw new CIM1Exception("Cannot find an IIDM terminal for CIM topological node "
+ tn.getId());
}
return terminals.iterator().next();
}
private static boolean isXNode(cim1.model.TopologicalNode tn) {
return tn.isFromBoundary() || tn.getConnectivityNodeContainer() == null;
}
private static String findUcteXnodeCode(cim1.model.TopologicalNode tn) {
// the xnode name is contained in the description field, starting
// from the letter X until ; character
int pos1 = tn.getDescription().indexOf('X');
int pos2 = tn.getDescription().indexOf(';');
if (pos1 == -1 || pos2 == -1) {
throw new CIM1Exception("Cannot find Xnode name from topological node description field '"
+ tn.getDescription() + "'");
}
return tn.getDescription().substring(pos1, pos2);
}
private static float[] getVoltageLimits(cim1.model.VoltageLevel vl, Set noOperationalLimitInOperationalLimitSet) {
float lowVoltageLimit = Float.NaN;
float highVoltageLimit = Float.NaN;
if (vl.getTopologicalNode() != null) {
for (cim1.model.TopologicalNode tn : vl.getTopologicalNode()) {
if (tn.getTerminal() != null) {
for (cim1.model.Terminal t : tn.getTerminal()) {
if (t.getOperationalLimitSet() != null) {
for (cim1.model.OperationalLimitSet ols : t.getOperationalLimitSet()) {
if (ols.getOperationalLimitValue() == null) {
noOperationalLimitInOperationalLimitSet.add(ols.getId());
continue;
}
for (cim1.model.OperationalLimit ol : ols.getOperationalLimitValue()) {
cim1.model.OperationalLimitType olt = ol.getOperationalLimitType();
float value;
switch (olt.getName()) {
case "LowVoltage":
value = ((cim1.model.VoltageLimit) ol).getValue();
if (Float.isNaN(lowVoltageLimit)) {
lowVoltageLimit = value;
} else {
lowVoltageLimit = Math.min(lowVoltageLimit, value);
}
break;
case "HighVoltage":
value = ((cim1.model.VoltageLimit) ol).getValue();
if (Float.isNaN(highVoltageLimit)) {
highVoltageLimit = value;
} else {
highVoltageLimit = Math.max(highVoltageLimit, value);
}
break;
}
}
}
}
}
}
}
}
return new float[] {lowVoltageLimit, highVoltageLimit};
}
private static void createCurrentLimits(cim1.model.Terminal t, Supplier owner, Set noOperationalLimitInOperationalLimitSet) {
if (t.getOperationalLimitSet() != null) {
CurrentLimitsAdder cla = owner.get();
boolean foundCurrentLimits = false;
for (cim1.model.OperationalLimitSet ols : t.getOperationalLimitSet()) {
if (ols.getOperationalLimitValue() == null) {
noOperationalLimitInOperationalLimitSet.add(ols.getId());
continue;
}
for (cim1.model.OperationalLimit ol : ols.getOperationalLimitValue()) {
cim1.model.OperationalLimitType olt = ol.getOperationalLimitType();
if (ol instanceof cim1.model.CurrentLimit) {
float value = ((cim1.model.CurrentLimit) ol).getValue();
if (value <= 0) {
LOGGER.warn("Invalid current limit {} for {}", value, ols.getId());
} else {
foundCurrentLimits = true;
switch (olt.getName()) {
case "PATL":
cla.setPermanentLimit(value);
break;
case "TATL":
if (olt.getDirection() != cim1.model.OperationalLimitDirectionKind.absoluteValue) {
throw new CIM1Exception("Direction not supported " + olt.getDirection());
}
int acceptableDuration = (int) olt.getAcceptableDuration();
cla.beginTemporaryLimit()
.setName(Integer.toString(acceptableDuration))
.setValue(value)
.setAcceptableDuration(acceptableDuration)
.endTemporaryLimit();
}
}
}
}
}
if (foundCurrentLimits) {
cla.add();
}
}
}
private void createLine(Network network, cim1.model.ACLineSegment l, Set noOperationalLimitInOperationalLimitSet) {
cim1.model.Terminal t1 = l.getTerminals().get(0);
cim1.model.Terminal t2 = l.getTerminals().get(1);
// t1 and t2 respect sequenceNumber
if (t1.getSequenceNumber() == 2) {
cim1.model.Terminal t = t1;
t1 = t2;
t2 = t;
}
cim1.model.TopologicalNode tn1 = t1.getTopologicalNode();
cim1.model.TopologicalNode tn2 = t2.getTopologicalNode();
String voltageLevelId1 = namingStrategy.getId(tn1.getConnectivityNodeContainer());
String voltageLevelId2 = namingStrategy.getId(tn2.getConnectivityNodeContainer());
cim1.model.SvPowerFlow svpf1 = t1.getSvPowerFlow();
cim1.model.SvPowerFlow svpf2 = t2.getSvPowerFlow();
float r = l.getR();
float x = l.getX();
float b = l.getBch();
float g = l.getGch();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Create line {} between bus {} and bus {}",
namingStrategy.getId(l), namingStrategy.getId(tn1), namingStrategy.getId(tn2));
}
final Line line = network.newLine()
.setId(namingStrategy.getId(l))
.setName(namingStrategy.getName(l))
.setEnsureIdUnicity(false)
.setBus1(t1.isConnected() ? namingStrategy.getId(tn1) : null)
.setBus2(t2.isConnected() ? namingStrategy.getId(tn2) : null)
.setConnectableBus1(namingStrategy.getId(tn1))
.setConnectableBus2(namingStrategy.getId(tn2))
.setVoltageLevel1(voltageLevelId1)
.setVoltageLevel2(voltageLevelId2)
.setR(r)
.setX(x)
.setG1(g / 2)
.setG2(g / 2)
.setB1(b / 2)
.setB2(b / 2)
.add();
addTerminalMapping(tn1, line.getTerminal1());
addTerminalMapping(tn2, line.getTerminal2());
createCurrentLimits(t1, line::newCurrentLimits1, noOperationalLimitInOperationalLimitSet);
createCurrentLimits(t2, line::newCurrentLimits2, noOperationalLimitInOperationalLimitSet);
if (svpf1 != null) {
line.getTerminal1().setP(svpf1.getP()).setQ(svpf1.getQ());
}
if (svpf2 != null) {
line.getTerminal2().setP(svpf2.getP()).setQ(svpf2.getQ());
}
}
/* xnode is on side 2 */
private void createDanglingLine(Network network, cim1.model.ACLineSegment l,
cim1.model.Terminal t1, cim1.model.Terminal t2,
cim1.model.TopologicalNode tn1, cim1.model.TopologicalNode tn2,
cim1.model.EnergyConsumer ec2,
Set noOperationalLimitInOperationalLimitSet) {
assert isXNode(tn2) && !isXNode(tn1);
String voltageLevelId1 = namingStrategy.getId(tn1.getConnectivityNodeContainer());
VoltageLevel voltageLevel1 = network.getVoltageLevel(voltageLevelId1);
boolean ect2isConnected = true;
float p0 = 0;
float q0 = 0;
if (ec2 != null) {
cim1.model.Terminal ect2 = ec2.getTerminals().get(0);
cim1.model.SvPowerFlow ect2svpf = ect2.getSvPowerFlow();
ect2isConnected = ect2.isConnected();
p0 = ect2svpf.getP();
q0 = ect2svpf.getQ();
}
cim1.model.SvPowerFlow svpf1 = t1.getSvPowerFlow();
cim1.model.SvVoltage svv2 = tn2.getSvVoltage();
float r = l.getR();
float x = l.getX();
float b = l.getBch();
float g = l.getGch();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Create dangling line {} connected to bus {}", namingStrategy.getId(l), namingStrategy.getId(tn1));
}
boolean connected = t1.isConnected() && t2.isConnected() && ect2isConnected;
DanglingLine dl = voltageLevel1.newDanglingLine()
.setId(namingStrategy.getId(l))
.setName(namingStrategy.getName(l))
.setEnsureIdUnicity(false)
.setBus(connected ? namingStrategy.getId(tn1) : null)
.setConnectableBus(namingStrategy.getId(tn1))
.setR(r)
.setX(x)
.setG(g)
.setB(b)
.setUcteXnodeCode(findUcteXnodeCode(tn2))
.setP0(p0)
.setQ0(q0)
.add();
addTerminalMapping(tn1, dl.getTerminal());
createCurrentLimits(t1, dl::newCurrentLimits, noOperationalLimitInOperationalLimitSet);
if (svpf1 != null) {
dl.getTerminal().setP(svpf1.getP()).setQ(svpf1.getQ());
}
// for debug only
if (svv2 != null) {
dl.getProperties().setProperty(XNODE_V_PROPERTY, Float.toString(svv2.getV()));
dl.getProperties().setProperty(XNODE_ANGLE_PROPERTY, Float.toString(svv2.getAngle()));
}
}
/**
* xnode is on side 2 of l1
*
* l1 l2
* *---------*---------*
* t1 xnode t2
* tn1 tn2
*/
private void createMergedLine(Network network,
cim1.model.ACLineSegment l1, cim1.model.ACLineSegment l2,
cim1.model.Terminal t1, cim1.model.TopologicalNode tn1,
cim1.model.TopologicalNode xn,
Set noOperationalLimitInOperationalLimitSet) {
cim1.model.Terminal t2;
cim1.model.TopologicalNode tn2;
if (l2.getTerminals().get(0).getTopologicalNode() == xn) {
t2 = l2.getTerminals().get(1);
tn2 = t2.getTopologicalNode();
} else if (l2.getTerminals().get(1).getTopologicalNode() == xn) {
t2 = l2.getTerminals().get(0);
tn2 = t2.getTopologicalNode();
} else {
throw new AssertionError();
}
String voltageLevelId1 = namingStrategy.getId(tn1.getConnectivityNodeContainer());
String voltageLevelId2 = namingStrategy.getId(tn2.getConnectivityNodeContainer());
cim1.model.SvPowerFlow svpf1 = t1.getSvPowerFlow();
cim1.model.SvPowerFlow svpf2 = t2.getSvPowerFlow();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Create merged line {} between bus {} and bus {}",
namingStrategy.getId(l1), namingStrategy.getId(tn1), namingStrategy.getId(xn));
}
Line line = network.newTieLine()
.setId(namingStrategy.getId(l1) + " + " + namingStrategy.getId(l2))
.setName(namingStrategy.getName(l1) + " + " + namingStrategy.getName(l2))
.setEnsureIdUnicity(false)
.setBus1(t1.isConnected() ? tn1.getId() : null)
.setBus2(t2.isConnected() ? tn2.getId() : null)
.setConnectableBus1(namingStrategy.getId(tn1))
.setConnectableBus2(namingStrategy.getId(tn2))
.setVoltageLevel1(voltageLevelId1)
.setVoltageLevel2(voltageLevelId2)
.line1().setId(namingStrategy.getId(l1))
.setName(namingStrategy.getName(l1))
.setR(l1.getR())
.setX(l1.getX())
.setG1(l1.getGch() / 2)
.setG2(l1.getGch() / 2)
.setB1(l1.getBch() / 2)
.setB2(l1.getBch() / 2)
.setXnodeP(0)
.setXnodeQ(0)
.line2().setId(namingStrategy.getId(l2))
.setName(namingStrategy.getName(l2))
.setR(l2.getR())
.setX(l2.getX())
.setG1(l2.getGch() / 2)
.setG2(l2.getGch() / 2)
.setB1(l2.getBch() / 2)
.setB2(l2.getBch() / 2)
.setXnodeP(0)
.setXnodeQ(0)
.setUcteXnodeCode(findUcteXnodeCode(xn))
.add();
addTerminalMapping(tn1, line.getTerminal1());
addTerminalMapping(tn2, line.getTerminal2());
createCurrentLimits(t1, line::newCurrentLimits1, noOperationalLimitInOperationalLimitSet);
createCurrentLimits(t2, line::newCurrentLimits2, noOperationalLimitInOperationalLimitSet);
if (svpf1 != null) {
line.getTerminal1().setP(svpf1.getP()).setQ(svpf1.getQ());
}
if (svpf2 != null) {
line.getTerminal2().setP(svpf2.getP()).setQ(svpf2.getQ());
}
}
private void createLines(Network network, Set noOperationalLimitInOperationalLimitSet) {
for (cim1.model.ACLineSegment l : cimModel.getId_ACLineSegment().values()) {
cim1.model.Terminal t1 = l.getTerminals().get(0);
cim1.model.Terminal t2 = l.getTerminals().get(1);
cim1.model.TopologicalNode tn1 = t1.getTopologicalNode();
cim1.model.TopologicalNode tn2 = t2.getTopologicalNode();
if (isXNode(tn1)) {
if (boundaryXNodes.containsKey(tn1)) {
// side 1 of the line is connected to a XNODE. Only one load
// is connected to the XNODE => replace the line, the XNODE
// and the load by an IIDM dangling line
cim1.model.EnergyConsumer ec1 = boundaryXNodes.get(tn1);
createDanglingLine(network, l, t2, t1, tn2, tn1, ec1, noOperationalLimitInOperationalLimitSet);
}
// nothing to do because merged line has already been created
// by test on tn2
} else if (isXNode(tn2)) {
if (boundaryXNodes.containsKey(tn2)) {
// side 2 of the line is connected to a XNODE. Only one load
// is connected to the XNODE => replace the line, the XNODE
// and the load by an IIDM dangling line
cim1.model.EnergyConsumer ec2 = boundaryXNodes.get(tn2);
createDanglingLine(network, l, t1, t2, tn1, tn2, ec2, noOperationalLimitInOperationalLimitSet);
} else {
// side 2 of the line is connected to a XNODE. Another line
// is connected to the XNODE and no non null injection is
// connected to the XNODE => replace both lines and the XNODE
// by an IIDM line
List linesToMerge = mergedXNodes.get(tn2);
cim1.model.ACLineSegment l2 = (cim1.model.ACLineSegment) (linesToMerge.get(0) == l ? linesToMerge.get(1) : linesToMerge.get(0));
createMergedLine(network, l, l2, t1, tn1, tn2, noOperationalLimitInOperationalLimitSet);
}
} else {
createLine(network, l, noOperationalLimitInOperationalLimitSet);
}
}
}
private static float getStepXforAsymmetrical(float xStepMin, float xStepMax,
float alpha, float alphaMax, float theta) {
double numer = Math.sin(theta) - Math.tan(alphaMax) * Math.cos(theta);
double denom = Math.sin(theta) - Math.tan(alpha) * Math.cos(theta);
return xStepMin + (xStepMax - xStepMin)
* (float) Math.pow(Math.tan(alpha) / Math.tan(alphaMax) * numer / denom, 2);
}
private static float getStepXforSymmetrical(float xStepMin, float xStepMax,
float alpha, float alphaMax) {
return xStepMin + (xStepMax - xStepMin)
* (float) Math.pow(Math.sin(alpha / 2) / Math.sin(alphaMax / 2), 2);
}
private void createPhaseTapChanger(cim1.model.PhaseTapChanger ptc, cim1.model.Terminal t1, cim1.model.Terminal t2,
TwoWindingsTransformer transfo) {
int lowStep = ptc.getLowStep();
int highStep = ptc.getHighStep();
int neutralStep = ptc.getNeutralStep();
if (neutralStep < lowStep || neutralStep > highStep) {
throw new CIM1Exception("Malformed ratio tap changer: neutral step ("
+ neutralStep + ") isn't between low (" + lowStep + ") and high ("
+ highStep + ")");
}
int position = (int) ptc.svTapStep.getContinuousPosition();
PhaseTapChangerAdder ptca = transfo.newPhaseTapChanger()
.setLowTapPosition(lowStep)
.setTapPosition(position);
double du0 = ptc.neutralU / ptc.transformerWinding.ratedU;
if (Math.abs(du0) > 0.5) {
du0 = 0;
}
float du;
if (ptc.voltageStepIncrementOutOfPhaseIsSet() && ptc.getVoltageStepIncrementOutOfPhase() != 0) {
du = (config.isInvertVoltageStepIncrementOutOfPhase() ? -1 : 1) * ptc.getVoltageStepIncrementOutOfPhase() / ptc.getTransformerWinding().getRatedU();
} else if (ptc.stepVoltageIncrementIsSet() && ptc.getStepVoltageIncrement() != 0) {
du = ptc.getStepVoltageIncrement() / 100f;
} else {
LOGGER.warn("Phase tap changer '{}' of power transformer '{}'" +
" do not have a valid value for voltageStepIncrementOutOfPhase or " +
"stepVoltageIncrement attribute, default to 1",
ptc.getId(), transfo.getId());
du = 1f / 100;
}
float theta;
if (ptc.windingConnectionAngleIsSet()) {
theta = (float) Math.toRadians(ptc.getWindingConnectionAngle());
} else {
theta = (float) Math.PI / 2;
LOGGER.warn("Phase tap changer '{}' of power transformer '{}'" +
" do not have windingConnectionAngle attribute, default to 90",
ptc.getId(), transfo.getId());
}
float xStepMin = 0;
float xStepMax = 0;
if (ptc.xStepMinIsSet() && ptc.xStepMaxIsSet()) {
xStepMin = ptc.xStepMin;
xStepMax = ptc.xStepMax;
}
boolean xStepRangeIsInconsistent = false;
if (xStepMin < 0 || xStepMax <= 0 || xStepMin > xStepMax) {
xStepRangeIsInconsistent = true;
LOGGER.info("xStepMin and xStepMax are inconsistents for transformer {}", transfo.getId());
}
List alphaList = new ArrayList<>();
List rhoList = new ArrayList<>();
switch (ptc.getPhaseTapChangerType()) {
case asymmetrical: {
for (int step = lowStep; step <= highStep; step++) {
int n = step - neutralStep;
double dx = (n * du - du0) * Math.cos(theta);
double dy = (n * du - du0) * Math.sin(theta);
float alpha = (float) Math.atan2(dy, 1 + dx);
float rho = (float) (1 / Math.hypot(dy, 1 + dx));
alphaList.add(alpha);
rhoList.add(rho);
}
}
break;
case symmetrical:
if (ptc.stepPhaseShiftIncrementIsSet() && ptc.stepPhaseShiftIncrement != 0) {
for (int step = lowStep; step <= highStep; step++) {
int n = step - neutralStep;
float alpha = n * (float) Math.toRadians((config.isInvertVoltageStepIncrementOutOfPhase() ? -1 : 1) * ptc.stepPhaseShiftIncrement);
float rho = 1f;
alphaList.add(alpha);
rhoList.add(rho);
}
} else {
for (int step = lowStep; step <= highStep; step++) {
int n = step - neutralStep;
double dy = (n * du / 2 - du0) * Math.sin(theta);
float alpha = (float) (2 * Math.asin(dy));
float rho = 1f;
alphaList.add(alpha);
rhoList.add(rho);
}
}
break;
default:
throw new AssertionError("Unexpected PhaseTapChangerKind value: " + ptc.getPhaseTapChangerType());
}
float alphaMax = (float) alphaList.stream().mapToDouble(Float::doubleValue).max().getAsDouble();
for (int i = 0; i < alphaList.size(); i++) {
float alpha = alphaList.get(i);
float rho = rhoList.get(i);
float x;
if (xStepRangeIsInconsistent || alphaMax == 0) {
x = transfo.getX();
} else {
switch (ptc.getPhaseTapChangerType()) {
case asymmetrical:
x = getStepXforAsymmetrical(xStepMin, xStepMax, alpha, alphaMax, theta);
break;
case symmetrical:
x = getStepXforSymmetrical(xStepMin, xStepMax, alpha, alphaMax);
break;
default:
throw new AssertionError("Unexpected PhaseTapChangerKind value: " + ptc.getPhaseTapChangerType());
}
}
ptca.beginStep()
.setAlpha((float) Math.toDegrees(alpha))
.setRho(rho)
.setR(0f)
.setX((x - transfo.getX()) / transfo.getX() * 100)
.setG(0f)
.setB(0f)
.endStep();
}
if (ptc.regulatingControlIsSet()) {
cim1.model.RegulatingControl rc = ptc.getRegulatingControl();
switch (rc.getMode()) {
case currentFlow:
Terminal regulationTerminal;
if (rc.getTerminal() == t1) {
regulationTerminal = transfo.getTerminal1();
} else if (rc.getTerminal() == t2) {
regulationTerminal = transfo.getTerminal2();
} else {
regulationTerminal = getTerminalMapping(rc.getTerminal().getTopologicalNode());
}
ptca.setRegulationMode(PhaseTapChanger.RegulationMode.CURRENT_LIMITER)
.setRegulationValue(rc.getTargetValue())
.setRegulating(true)
.setRegulationTerminal(regulationTerminal);
break;
case fixed:
break;
default:
LOGGER.warn("Phase tap changer '{}' of power transformer '{}'" +
" has an unsupported regulating mode: {}",
ptc.getId(), transfo.getId(), rc.getMode());
}
}
ptca.add();
}
private void createRatioTapChanger(cim1.model.RatioTapChanger rtc, Supplier transfo,
boolean rtcSide1, Map terminals) {
int lowStep = rtc.getLowStep();
int highStep = rtc.getHighStep();
int neutralStep = rtc.getNeutralStep();
if (neutralStep < lowStep || neutralStep > highStep) {
throw new CIM1Exception("Malformed ratio tap changer: neutral step ("
+ neutralStep + ") isn't between low (" + lowStep + ") and high ("
+ highStep + ")");
}
int position = (int) rtc.svTapStep.getContinuousPosition();
RatioTapChangerAdder rtca = transfo.get()
.setLowTapPosition(lowStep)
.setTapPosition(position);
for (int step = lowStep; step <= highStep; step++) {
int n = step - neutralStep;
float du = rtc.getStepVoltageIncrement() / 100;
float rho = rtcSide1 ? 1 / (1 + n * du) : (1 + n * du);
rtca.beginStep()
.setRho(rho)
.setR(0f)
.setX(0f)
.setG(0f)
.setB(0f)
.endStep();
}
if (rtc.regulatingControlIsSet()) {
cim1.model.RegulatingControl rc = rtc.getRegulatingControl();
switch (rc.getMode()) {
case voltage:
boolean regulating = true;
float targetV = rc.getTargetValue();
if (targetV <= 0) {
LOGGER.warn("Ratio tap changer '{}' of power transformer '{}'" +
" has a bad target voltage {}, switch off regulation",
rtc.getId(), transfo.toString(), targetV);
regulating = false;
targetV = Float.NaN;
}
Terminal regulationTerminal = null;
for (Map.Entry e : terminals.entrySet()) {
if (rc.getTerminal() == e.getKey()) {
regulationTerminal = e.getValue();
}
}
if (regulationTerminal == null) {
regulationTerminal = getTerminalMapping(rc.getTerminal().getTopologicalNode());
}
rtca.setLoadTapChangingCapabilities(true)
.setRegulating(regulating)
.setTargetV(targetV)
.setRegulationTerminal(regulationTerminal);
break;
case fixed:
rtca.setLoadTapChangingCapabilities(false);
break;
default:
rtca.setLoadTapChangingCapabilities(false);
LOGGER.warn("Ratio tap changer '{}' of power transformer '{}'" +
" has an unsupported regulating mode: {}",
rtc.getId(), transfo.toString(), rc.getMode());
}
} else {
rtca.setLoadTapChangingCapabilities(false);
}
rtca.add();
}
class RatioTapChangerToCreate {
cim1.model.RatioTapChanger rtc;
RatioTapChangerHolder transfo;
boolean rtcSide1;
Map terminals;
RatioTapChangerToCreate(cim1.model.RatioTapChanger rtc, RatioTapChangerHolder transfo, boolean rtcSide1, Map terminals) {
this.rtc = rtc;
this.rtcSide1 = rtcSide1;
this.transfo = transfo;
this.terminals = terminals;
}
}
private boolean isPrimary(cim1.model.TransformerWinding tw) {
return tw.getWindingType().equals(cim1.model.WindingType.primary);
}
private void create2WTransfos(cim1.model.PowerTransformer pt,
cim1.model.TransformerWinding tw1p,
cim1.model.TransformerWinding tw2p,
Network network,
Set noOperationalLimitInOperationalLimitSet,
List ratioTapChangerToCreateList) {
cim1.model.TransformerWinding tw1 = tw1p;
cim1.model.TransformerWinding tw2 = tw2p;
if (!isPrimary(tw1)) {
cim1.model.TransformerWinding tw = tw1;
tw1 = tw2;
tw2 = tw;
}
cim1.model.Terminal t1 = tw1.getTerminals().get(0);
cim1.model.Terminal t2 = tw2.getTerminals().get(0);
cim1.model.TopologicalNode tn1 = t1.getTopologicalNode();
cim1.model.TopologicalNode tn2 = t2.getTopologicalNode();
cim1.model.VoltageLevel vl1 = (cim1.model.VoltageLevel) tn1.getConnectivityNodeContainer();
cim1.model.VoltageLevel vl2 = (cim1.model.VoltageLevel) tn2.getConnectivityNodeContainer();
// check that there is only one ratio tap changer
TwoWindingsTransformer.Side rtcSide = null;
cim1.model.RatioTapChanger rtc = null;
cim1.model.PhaseTapChanger ptc = null;
if (tw1.ratioTapChangerIsSet()) {
if (tw2.ratioTapChangerIsSet()) {
throw new CIM1Exception("Unsupported modelling: transformer with two ratio tap changer"
+ pt.getId());
}
rtc = tw1.getRatioTapChanger();
rtcSide = Branch.Side.ONE;
} else {
if (tw2.ratioTapChangerIsSet()) {
rtc = tw2.getRatioTapChanger();
rtcSide = Branch.Side.TWO;
}
}
// check that there is only one phase tap changer
TwoWindingsTransformer.Side ptcSide = null;
if (tw1.phaseTapChangerIsSet()) {
if (tw2.phaseTapChangerIsSet()) {
throw new CIM1Exception("Unsupported modelling: transformer with two phase tap changer"
+ pt.getId());
}
ptc = tw1.getPhaseTapChanger();
ptcSide = Branch.Side.ONE;
} else {
if (tw2.phaseTapChangerIsSet()) {
ptc = tw2.getPhaseTapChanger();
ptcSide = Branch.Side.TWO;
}
}
if (rtcSide != null && ptcSide != null && rtcSide != ptcSide) {
throw new CIM1Exception("Unsupported modelling: transformer with ratio and tap changer not on the same winding "
+ pt.getId());
}
if (isXNode(tn1)) {
throw new CIM1Exception("Not supported: transformer '" + pt.getId() + "' connected to XNODE "
+ tn1.getId() + "(" + findUcteXnodeCode(tn1) + ")");
} else if (isXNode(tn2)) {
throw new CIM1Exception("Not supported: transformer '" + pt.getId() + "' connected to XNODE "
+ tn2.getId() + "(" + findUcteXnodeCode(tn2) + ")");
} else {
// IIDM 2 windings transformer modelling: impedances are specfied at
// side 2, in this case at the secondary voltage side.
float rho0 = tw2.getRatedU() / tw1.getRatedU();
float rho0Square = rho0 * rho0;
float r0 = tw1.getR() * rho0Square + tw2.getR();
float x0 = tw1.getX() * rho0Square + tw2.getX();
float g0 = tw1.getG() / rho0Square + tw2.getG();
float b0 = tw1.getB() / rho0Square + tw2.getB();
VoltageLevel voltageLevel1 = network.getVoltageLevel(namingStrategy.getId(vl1));
Substation substation = voltageLevel1.getSubstation();
TwoWindingsTransformer transfo = substation.newTwoWindingsTransformer()
.setId(namingStrategy.getId(pt))
.setName(namingStrategy.getName(pt))
.setEnsureIdUnicity(false)
.setR(r0)
.setX(x0)
.setG(g0)
.setB(b0)
.setRatedU1(tw1.getRatedU())
.setBus1(t1.isConnected() ? namingStrategy.getId(tn1) : null)
.setConnectableBus1(namingStrategy.getId(tn1))
.setVoltageLevel1(namingStrategy.getId(vl1))
.setRatedU2(tw2.getRatedU())
.setBus2(t2.isConnected() ? namingStrategy.getId(tn2) : null)
.setConnectableBus2(namingStrategy.getId(tn2))
.setVoltageLevel2(namingStrategy.getId(vl2))
.add();
addTerminalMapping(tn1, transfo.getTerminal1());
addTerminalMapping(tn2, transfo.getTerminal2());
if (rtc != null) {
ratioTapChangerToCreateList.add(new RatioTapChangerToCreate(rtc, transfo, rtcSide == Branch.Side.ONE,
ImmutableMap.of(t1, transfo.getTerminal1(),
t2, transfo.getTerminal2())));
}
if (ptc != null) {
createPhaseTapChanger(ptc, t1, t2, transfo);
}
createCurrentLimits(t1, transfo::newCurrentLimits1, noOperationalLimitInOperationalLimitSet);
createCurrentLimits(t2, transfo::newCurrentLimits2, noOperationalLimitInOperationalLimitSet);
cim1.model.SvPowerFlow svpf1 = t1.getSvPowerFlow();
cim1.model.SvPowerFlow svpf2 = t2.getSvPowerFlow();
if (svpf1 != null) {
transfo.getTerminal1().setP(svpf1.getP()).setQ(svpf1.getQ());
}
if (svpf2 != null) {
transfo.getTerminal2().setP(svpf2.getP()).setQ(svpf2.getQ());
}
}
}
private void create3WTransfos(cim1.model.PowerTransformer pt,
cim1.model.TransformerWinding tw1,
cim1.model.TransformerWinding tw2,
cim1.model.TransformerWinding tw3,
Network network,
Set noOperationalLimitInOperationalLimitSet,
List ratioTapChangerToCreateList) {
// side 1 is the high voltage, side 2 is the medium voltage, side 3
// is the low voltage
if (tw1.getRatedU() < tw2.getRatedU()) {
throw new IllegalStateException("ratedU1 < ratedU2");
}
if (tw2.getRatedU() < tw3.getRatedU()) {
throw new IllegalStateException("ratedU2 < ratedU3");
}
cim1.model.Terminal t1 = tw1.getTerminals().get(0);
cim1.model.Terminal t2 = tw2.getTerminals().get(0);
cim1.model.Terminal t3 = tw3.getTerminals().get(0);
cim1.model.TopologicalNode tn1 = t1.getTopologicalNode();
cim1.model.TopologicalNode tn2 = t2.getTopologicalNode();
cim1.model.TopologicalNode tn3 = t3.getTopologicalNode();
cim1.model.VoltageLevel vl1 = (cim1.model.VoltageLevel) tn1.getConnectivityNodeContainer();
cim1.model.VoltageLevel vl2 = (cim1.model.VoltageLevel) tn2.getConnectivityNodeContainer();
cim1.model.VoltageLevel vl3 = (cim1.model.VoltageLevel) tn3.getConnectivityNodeContainer();
if (isXNode(tn1) || isXNode(tn2) || isXNode(tn3)) {
throw new CIM1Exception("XNODE connected to a 3 windings transformer not supported");
}
if (tw1.ratioTapChangerIsSet()) {
LOGGER.warn("Power transformer " + pt.getId() + ": ratio tap changer on primary winding is not supported");
}
if (tw1.phaseTapChangerIsSet() || tw2.phaseTapChangerIsSet() || tw3.phaseTapChangerIsSet()) {
throw new CIM1Exception("Power transformer " + pt.getId() + ": phase tap changers on three windings transformer are not allowed");
}
cim1.model.RatioTapChanger rtc2 = tw2.getRatioTapChanger();
cim1.model.RatioTapChanger rtc3 = tw3.getRatioTapChanger();
VoltageLevel voltageLevel1 = network.getVoltageLevel(namingStrategy.getId(vl1));
Substation substation = voltageLevel1.getSubstation();
float ratedU1 = tw1.getRatedU();
float ratedU2 = tw2.getRatedU();
float ratedU3 = tw3.getRatedU();
float rho2Square = (float) Math.pow(tw2.getRatedU() / tw1.getRatedU(), 2);
float rho3Square = (float) Math.pow(tw3.getRatedU() / tw1.getRatedU(), 2);
float r1 = tw1.getR();
float x1 = tw1.getX();
float g1 = tw1.getG() + tw2.getG() * rho2Square + tw3.getG() * rho3Square;
float b1 = tw1.getB() + tw2.getB() * rho2Square + tw3.getB() * rho3Square;
float r2 = tw2.getR() / rho2Square;
float x2 = tw2.getX() / rho2Square;
float r3 = tw3.getR() / rho3Square;
float x3 = tw3.getX() / rho3Square;
ThreeWindingsTransformer transfo = substation.newThreeWindingsTransformer()
.setId(namingStrategy.getId(pt))
.setName(namingStrategy.getName(pt))
.setEnsureIdUnicity(false)
.newLeg1()
.setR(r1)
.setX(x1)
.setG(g1)
.setB(b1)
.setRatedU(ratedU1)
.setVoltageLevel(namingStrategy.getId(vl1))
.setBus(t1.isConnected() ? namingStrategy.getId(tn1) : null)
.setConnectableBus(namingStrategy.getId(tn1))
.add()
.newLeg2()
.setR(r2)
.setX(x2)
.setRatedU(ratedU2)
.setVoltageLevel(namingStrategy.getId(vl2))
.setBus(t2.isConnected() ? namingStrategy.getId(tn2) : null)
.setConnectableBus(namingStrategy.getId(tn2))
.add()
.newLeg3()
.setR(r3)
.setX(x3)
.setRatedU(ratedU3)
.setVoltageLevel(namingStrategy.getId(vl3))
.setBus(t3.isConnected() ? namingStrategy.getId(tn3) : null)
.setConnectableBus(namingStrategy.getId(tn3))
.add()
.add();
addTerminalMapping(tn1, transfo.getLeg1().getTerminal());
addTerminalMapping(tn2, transfo.getLeg2().getTerminal());
addTerminalMapping(tn3, transfo.getLeg3().getTerminal());
if (rtc2 != null) {
ratioTapChangerToCreateList.add(new RatioTapChangerToCreate(rtc2, transfo.getLeg2(), false,
ImmutableMap.of(t2, transfo.getLeg2().getTerminal()))); // TODO true/false?
}
if (rtc3 != null) {
ratioTapChangerToCreateList.add(new RatioTapChangerToCreate(rtc3, transfo.getLeg3(), false,
ImmutableMap.of(t3, transfo.getLeg3().getTerminal()))); // TODO true/false?
}
createCurrentLimits(t1, transfo.getLeg1()::newCurrentLimits, noOperationalLimitInOperationalLimitSet);
createCurrentLimits(t2, transfo.getLeg2()::newCurrentLimits, noOperationalLimitInOperationalLimitSet);
createCurrentLimits(t3, transfo.getLeg3()::newCurrentLimits, noOperationalLimitInOperationalLimitSet);
cim1.model.SvPowerFlow svpf1 = t1.getSvPowerFlow();
if (svpf1 != null) {
transfo.getLeg1().getTerminal().setP(svpf1.getP()).setQ(svpf1.getQ());
}
cim1.model.SvPowerFlow svpf2 = t2.getSvPowerFlow();
if (svpf2 != null) {
transfo.getLeg2().getTerminal().setP(svpf2.getP()).setQ(svpf2.getQ());
}
cim1.model.SvPowerFlow svpf3 = t3.getSvPowerFlow();
if (svpf3 != null) {
transfo.getLeg3().getTerminal().setP(svpf3.getP()).setQ(svpf3.getQ());
}
}
private void createTransfos(Network network, Set noOperationalLimitInOperationalLimitSet, List ratioTapChangerToCreateList) {
for (cim1.model.PowerTransformer pt : cimModel.getId_PowerTransformer().values()) {
LOGGER.trace("Create power transformer {}", namingStrategy.getId(pt));
List windings = pt.getContains_TransformerWindings();
if (windings.size() == 2) {
cim1.model.TransformerWinding tw1 = windings.get(0);
cim1.model.TransformerWinding tw2 = windings.get(1);
create2WTransfos(pt, tw1, tw2, network, noOperationalLimitInOperationalLimitSet, ratioTapChangerToCreateList);
} else if (windings.size() == 3) {
List sortedWindings = new ArrayList<>(3);
sortedWindings.add(windings.get(0));
sortedWindings.add(windings.get(1));
sortedWindings.add(windings.get(2));
Collections.sort(sortedWindings, new Comparator() {
@Override
public int compare(cim1.model.TransformerWinding tw1, cim1.model.TransformerWinding tw2) {
return (int) (tw2.getRatedU() - tw1.getRatedU());
}
});
cim1.model.TransformerWinding tw1 = sortedWindings.get(0);
cim1.model.TransformerWinding tw2 = sortedWindings.get(1);
cim1.model.TransformerWinding tw3 = sortedWindings.get(2);
create3WTransfos(pt, tw1, tw2, tw3, network, noOperationalLimitInOperationalLimitSet, ratioTapChangerToCreateList);
} else {
throw new CIM1Exception("Inconsistent power transformer found ("
+ pt.getId() + "): only 2 or 3 windings are supported");
}
}
}
private void createShunt(VoltageLevel voltageLevel, cim1.model.ShuntCompensator sc) {
LOGGER.trace("Create shunt compensator {}", namingStrategy.getId(sc));
cim1.model.Terminal t = sc.getTerminals().get(0);
cim1.model.TopologicalNode tn = t.getTopologicalNode();
int sectionCount = (int) sc.getSvShuntCompensatorSections().getContinuousSections();
sectionCount = Math.abs(sectionCount); // RTE Convergence CIM export bug (SVC)
float bPerSection = sc.getBPerSection();
if (bPerSection == 0) {
bPerSection = Float.MIN_VALUE;
LOGGER.warn("Fix {} susceptance per section: 0 -> {}", sc.getId(), bPerSection);
}
ShuntCompensator shunt = voltageLevel.newShunt()
.setId(namingStrategy.getId(sc))
.setName(namingStrategy.getName(sc))
.setEnsureIdUnicity(false)
.setBus(t.isConnected() ? namingStrategy.getId(tn) : null)
.setConnectableBus(namingStrategy.getId(tn))
.setCurrentSectionCount(sectionCount)
.setbPerSection(bPerSection)
.setMaximumSectionCount(Math.max(sc.getMaximumSections(), sectionCount))
.add();
addTerminalMapping(tn, shunt.getTerminal());
cim1.model.SvPowerFlow svfp = t.getSvPowerFlow();
if (svfp != null) {
shunt.getTerminal().setQ(svfp.getQ());
}
}
private void createLoad(VoltageLevel voltageLevel, cim1.model.EnergyConsumer ec) {
cim1.model.Terminal t = ec.getTerminals().get(0);
cim1.model.TopologicalNode tn = t.getTopologicalNode();
// We take P and Q from SvPowerFlow as nominal P and nominal Q,
// we don't deal with LoadResponseCharacteristic
float p = 0;
float q = 0;
cim1.model.SvPowerFlow svpf = t.getSvPowerFlow();
if (svpf != null) {
p = svpf.getP();
q = svpf.getQ();
} else {
LOGGER.warn("No active and reactive power value for load {}", ec.getId());
}
LoadType loadType = ec.getId().contains("fict") ? LoadType.FICTITIOUS : LoadType.UNDEFINED;
LOGGER.trace("Create load {}", namingStrategy.getId(ec));
Load load = voltageLevel.newLoad()
.setId(namingStrategy.getId(ec))
.setName(namingStrategy.getName(ec))
.setEnsureIdUnicity(false)
.setBus(t.isConnected() ? namingStrategy.getId(tn) : null)
.setConnectableBus(namingStrategy.getId(tn))
.setP0(p)
.setQ0(q)
.setLoadType(loadType)
.add();
addTerminalMapping(tn, load.getTerminal());
if (svpf != null) {
load.getTerminal().setP(p).setQ(q);
}
}
private static EnergySource getEnergySource(cim1.model.GeneratingUnit gu) {
EnergySource es = EnergySource.OTHER;
if (gu instanceof cim1.model.HydroGeneratingUnit) {
es = EnergySource.HYDRO;
} else if (gu instanceof cim1.model.NuclearGeneratingUnit) {
es = EnergySource.NUCLEAR;
} else if (gu instanceof cim1.model.ThermalGeneratingUnit) {
es = EnergySource.THERMAL;
} else if (gu instanceof cim1.model.WindGeneratingUnit) {
es = EnergySource.WIND;
}
return es;
}
private void createReactiveCapabilityCurve(Generator generator, cim1.model.SynchronousMachine sm, List synchronousMachinesWithReactiveRangeForMinus9999MW) {
if (sm.initialReactiveCapabilityCurveIsSet()) {
Map cdMap = new HashMap<>();
for (cim1.model.CurveData cd : sm.initialReactiveCapabilityCurve.getCurveScheduleDatas()) {
if (cdMap.containsKey(cd.getXvalue())) {
LOGGER.warn("Duplicated data for x value {} of {} reactive capability curve",
cd.getXvalue(), generator.getId());
continue;
}
if (cd.getXvalue() == -9999f) { // CVG bug
synchronousMachinesWithReactiveRangeForMinus9999MW.add(sm.getId());
} else {
cdMap.put(cd.getXvalue(), cd);
}
}
if (!cdMap.isEmpty()) {
if (cdMap.size() == 1) {
// there is just one value of minQ and maxQ
cim1.model.CurveData cd = cdMap.values().iterator().next();
generator.newMinMaxReactiveLimits()
.setMinQ(cd.getY1value())
.setMaxQ(cd.getY2value())
.add();
} else {
ReactiveCapabilityCurveAdder rcca = generator.newReactiveCapabilityCurve();
for (cim1.model.CurveData cd : cdMap.values()) {
rcca.beginPoint()
.setP(cd.getXvalue())
.setMinQ(cd.getY1value())
.setMaxQ(cd.getY2value())
.endPoint();
}
rcca.add();
}
}
} else {
// 4 points diagram with minQ and maxQ
generator.newMinMaxReactiveLimits()
.setMinQ(sm.getMinQ())
.setMaxQ(sm.getMaxQ())
.add();
}
}
private void createGenerator(VoltageLevel voltageLevel, cim1.model.SynchronousMachine sm,
List synchronousMachinesWithoutRegulatingControl,
List synchronousMachinesRegulatingVoltageWithZeroTargetVoltage,
List synchronousMachinesWithReactiveRangeForMinus9999MW) {
cim1.model.GeneratingUnit gu = sm.getMemberOf_GeneratingUnit();
cim1.model.Terminal t = sm.getTerminals().get(0);
cim1.model.TopologicalNode tn = t.getTopologicalNode();
LOGGER.trace("Create generator {}", namingStrategy.getId(sm));
float p = 0;
float q = 0;
cim1.model.SvPowerFlow svpf = t.getSvPowerFlow();
if (svpf != null) {
p = svpf.getP();
q = svpf.getQ();
} else {
LOGGER.warn("No SvPowerFlow for synchronous machine {}", sm.getId());
}
EnergySource es = getEnergySource(gu);
float minP = gu.getMinOperatingP();
float maxP = gu.getMaxOperatingP();
// if (minP == maxP) {
// maxP = minP + EPSILON;
// if (LOGGER.isWarnEnabled()) {
// LOGGER.warn("minP = {} maxP = {} for GeneratingUnit, setting maxP to {}",
// minP, gu.getId(), maxP);
// }
// }
boolean voltageRegulatorOn = false;
float targetP = -p;
float targetQ = -q;
float targetV = Float.NaN;
Terminal regulatingTerminal = null;
cim1.model.RegulatingControl rc = sm.getRegulatingControl();
if (rc != null) {
if (rc.getMode() == cim1.model.RegulatingControlModeKind.voltage) {
voltageRegulatorOn = true;
targetV = rc.getTargetValue();
if (targetV == 0) {
targetV = voltageLevel.getNominalV();
synchronousMachinesRegulatingVoltageWithZeroTargetVoltage.add(namingStrategy.getId(sm));
}
} else if (rc.getMode() == cim1.model.RegulatingControlModeKind.reactivePower) {
targetQ = rc.getTargetValue();
} else {
LOGGER.warn("Incorrect regulating control mode {} for synchronous machine {}",
rc.getMode(), sm.getId());
}
if (rc.getTerminal() != t) {
regulatingTerminal = getTerminalMapping(rc.getTerminal().getTopologicalNode());
}
} else {
synchronousMachinesWithoutRegulatingControl.add(sm.getId());
}
Generator generator = voltageLevel.newGenerator()
.setId(namingStrategy.getId(sm))
.setName(namingStrategy.getName(gu))
.setEnsureIdUnicity(false)
.setBus(t.isConnected() ? namingStrategy.getId(tn) : null)
.setConnectableBus(namingStrategy.getId(tn))
.setEnergySource(es)
.setMinP(minP)
.setMaxP(maxP)
.setVoltageRegulatorOn(voltageRegulatorOn)
.setRegulatingTerminal(regulatingTerminal)
.setTargetP(targetP)
.setTargetQ(targetQ)
.setTargetV(targetV)
.setRatedS(sm.getRatedS() > 0 ? sm.getRatedS() : Float.NaN)
.add();
addTerminalMapping(tn, generator.getTerminal());
if (svpf != null) {
generator.getTerminal().setP(p).setQ(q);
}
createReactiveCapabilityCurve(generator, sm, synchronousMachinesWithReactiveRangeForMinus9999MW);
}
private void createSwitch(VoltageLevel vl, cim1.model.Switch sw) {
cim1.model.Terminal t1 = sw.getTerminals().get(0);
cim1.model.Terminal t2 = sw.getTerminals().get(1);
cim1.model.TopologicalNode tn1 = t1.getTopologicalNode();
cim1.model.TopologicalNode tn2 = t2.getTopologicalNode();
cim1.model.VoltageLevel vl1 = (cim1.model.VoltageLevel) tn1.getConnectivityNodeContainer();
cim1.model.VoltageLevel vl2 = (cim1.model.VoltageLevel) tn2.getConnectivityNodeContainer();
if (!vl1.getId().equals(vl.getId()) || !vl2.getId().equals(vl.getId())) {
lowImpedanceLines.add(sw);
} else {
vl.getBusBreakerView().newSwitch()
.setId(namingStrategy.getId(sw))
.setName(namingStrategy.getName(sw))
.setEnsureIdUnicity(false)
.setBus1(namingStrategy.getId(tn1))
.setBus2(namingStrategy.getId(tn2))
.setOpen(!t1.isConnected() || !t2.isConnected())
.add();
}
}
private void createLowImpedanceLines(Network n) {
for (cim1.model.Switch sw : lowImpedanceLines) {
cim1.model.VoltageLevel vl = (cim1.model.VoltageLevel) sw.getMemberOf_EquipmentContainer();
cim1.model.Terminal t1 = sw.getTerminals().get(0);
cim1.model.Terminal t2 = sw.getTerminals().get(1);
cim1.model.TopologicalNode tn1 = t1.getTopologicalNode();
cim1.model.TopologicalNode tn2 = t2.getTopologicalNode();
cim1.model.VoltageLevel vl1 = (cim1.model.VoltageLevel) tn1.getConnectivityNodeContainer();
cim1.model.VoltageLevel vl2 = (cim1.model.VoltageLevel) tn2.getConnectivityNodeContainer();
LOGGER.warn("Switch '{}' is connected to a terminal not in the same voltage level '{}' (side 1: '{}', side 2: '{}') => create a low impedance line",
sw.getId(), vl.getId(), vl1.getId(), vl2.getId());
Line line = n.newLine()
.setId(namingStrategy.getId(sw))
.setName(namingStrategy.getName(sw))
.setEnsureIdUnicity(false)
.setVoltageLevel1(namingStrategy.getId(vl1))
.setVoltageLevel2(namingStrategy.getId(vl2))
.setBus1(t1.isConnected() ? namingStrategy.getId(tn1) : null)
.setConnectableBus1(namingStrategy.getId(tn1))
.setBus2(t2.isConnected() ? namingStrategy.getId(tn2) : null)
.setConnectableBus2(namingStrategy.getId(tn2))
.setR(0.05f)
.setX(0.05f)
.setG1(0f)
.setB1(0f)
.setG2(0f)
.setB2(0f)
.add();
addTerminalMapping(tn1, line.getTerminal1());
addTerminalMapping(tn2, line.getTerminal2());
}
}
public static Country getCountryFromSubregionName(String name) {
Objects.requireNonNull(name);
Country country = null;
switch (name) {
case "NO1":
case "NO2":
case "NO3":
case "NO4":
case "NO5":
country = Country.NO;
break;
case "SE1":
case "SE2":
case "SE3":
case "SE4":
country = Country.SE;
break;
case "FI1":
country = Country.FI;
break;
case "DK1":
case "DK2":
country = Country.DK;
break;
case "EE1":
country = Country.EE;
break;
case "LV1":
country = Country.LV;
break;
case "LT1":
country = Country.LT;
break;
}
return country;
}
Network convert() {
LOGGER.trace("Converting CIM model to IIDM model");
for (cim1.model.IEC61970CIMVersion v : cimModel.getId_IEC61970CIMVersion().values()) {
if (!CIM_ENTSOE_PROFILE_1ST_EDITION_VERSION.equals(v.version)) {
throw new CIM1Exception("CIM version " + v.version
+ " is incorrect, the official version of CIM ENTOSO-E profile 1st edition is "
+ CIM_ENTSOE_PROFILE_1ST_EDITION_VERSION);
}
}
EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName);
Network network = NetworkFactory.create(fileName, FORMAT);
network.setCaseDate(entsoeFileName.getDate());
network.setForecastDistance(entsoeFileName.getForecastDistance());
// Ends of transformers need to be in the same substation in the IIDM model, so check that a mapping is
// not needed
UndirectedGraph graph = new Pseudograph<>(Object.class);
for (cim1.model.Substation s : cimModel.getId_Substation().values()) {
graph.addVertex(namingStrategy.getId(s));
}
for (cim1.model.PowerTransformer pt : cimModel.getId_PowerTransformer().values()) {
List substationsIds = new ArrayList<>();
for (cim1.model.TransformerWinding tw : pt.getContains_TransformerWindings()) {
cim1.model.Terminal t = tw.terminals.get(0);
cim1.model.TopologicalNode tn = t.getTopologicalNode();
if (!isXNode(tn)) {
cim1.model.VoltageLevel vl = (cim1.model.VoltageLevel) tn.getConnectivityNodeContainer();
cim1.model.Substation s = vl.getMemberOf_Substation();
substationsIds.add(namingStrategy.getId(s));
}
}
if (substationsIds.size() > 1) {
for (int i = 1; i < substationsIds.size(); i++) {
graph.addEdge(substationsIds.get(0), substationsIds.get(i));
}
}
}
Map substationIdMapping = new HashMap<>();
new ConnectivityInspector<>(graph).connectedSets().stream().filter(substationIds -> substationIds.size() > 1).forEach(substationIds -> {
String selectedSubstationId = substationIds.stream()
.filter(substationId -> !config.getSubstationIdExcludedFromMapping().stream()
.anyMatch(pattern -> substationId.matches(pattern)))
.sorted()
.findFirst()
.orElse(substationIds.iterator().next());
for (String substationId : substationIds) {
if (!substationId.equals(selectedSubstationId)) {
substationIdMapping.put(substationId, selectedSubstationId);
}
}
});
if (!substationIdMapping.isEmpty()) {
LOGGER.warn("Substation id mapping needed for {} substations: {}", substationIdMapping.size(), substationIdMapping);
}
for (cim1.model.TopologicalNode tn : cimModel.getId_TopologicalNode().values()) {
if (isXNode(tn)) {
List branches = new ArrayList<>(2);
List loads = new ArrayList<>(1);
List injections = new ArrayList<>(1);
List xNodeTopo = new ArrayList<>();
for (cim1.model.Terminal t : tn.getTerminal()) {
xNodeTopo.add(namingStrategy.getId(t.getConductingEquipment()));
if (t.getConductingEquipment() instanceof cim1.model.ACLineSegment) {
branches.add(t.getConductingEquipment());
} else if (t.getConductingEquipment() instanceof cim1.model.TransformerWinding) {
cim1.model.TransformerWinding tw = (cim1.model.TransformerWinding) t.getConductingEquipment();
if (tw.getMemberOf_PowerTransformer().getContains_TransformerWindings().size() != 2) {
throw new CIM1Exception("XNODE connected to a 3 windings transformer not supported");
}
branches.add(tw);
} else if (t.getConductingEquipment() instanceof cim1.model.EnergyConsumer
|| t.getConductingEquipment() instanceof cim1.model.ShuntCompensator
|| t.getConductingEquipment() instanceof cim1.model.SynchronousMachine) {
injections.add(t.getConductingEquipment());
if (t.getConductingEquipment() instanceof cim1.model.EnergyConsumer) {
loads.add((cim1.model.EnergyConsumer) t.getConductingEquipment());
}
} else {
throw new CIM1Exception(t.getConductingEquipment().getClass()
+ " equipments cannot be connected to a merged XNODE ("
+ tn.getId() + ")");
}
}
if (!branches.isEmpty() || !injections.isEmpty()) {
if (branches.size() == 2) {
if (branches.get(0) instanceof cim1.model.TransformerWinding
|| branches.get(1) instanceof cim1.model.TransformerWinding) {
throw new CIM1Exception("Merged XNODE with transformers not supported");
}
LOGGER.trace("Found merged XNODE {} ({})", tn.getId(), findUcteXnodeCode(tn));
mergedXNodes.put(tn, branches);
if (!injections.isEmpty()) {
for (cim1.model.ConductingEquipment inj : injections) {
cim1.model.SvPowerFlow f = inj.getTerminals().get(0).getSvPowerFlow();
if (f != null) {
if (f.getP() != 0 || f.getQ() != 0) {
LOGGER.warn("A non null injection (p={}, q={}) should not be connected to a merged XNODE {} ({})",
f.getP(), f.getQ(), tn.getId(), findUcteXnodeCode(tn));
}
}
}
}
} else if (branches.size() == 1) {
if (loads.isEmpty()) {
LOGGER.trace("Found boundary XNODE {} ({})", tn.getId(), findUcteXnodeCode(tn));
boundaryXNodes.put(tn, null);
if (!injections.isEmpty()) {
LOGGER.warn("Strange boundary XNODE topology {}", xNodeTopo);
}
} else if (loads.size() == 1) {
LOGGER.trace("Found boundary XNODE {} ({})", tn.getId(), findUcteXnodeCode(tn));
boundaryXNodes.put(tn, loads.get(0));
if (injections.size() > 1) {
LOGGER.warn("Strange boundary XNODE topology {}", xNodeTopo);
}
} else {
throw new CIM1Exception("Unsupported XNODE " + tn.getId() + " ("
+ findUcteXnodeCode(tn) + ") topology: " + xNodeTopo);
}
} else {
throw new CIM1Exception("Unsupported XNODE " + tn.getId() + " ("
+ findUcteXnodeCode(tn) + ") topology: " + xNodeTopo);
}
}
}
}
List synchronousMachinesWithoutRegulatingControl = new ArrayList<>();
List synchronousMachinesRegulatingVoltageWithZeroTargetVoltage = new ArrayList<>();
List synchronousMachinesWithReactiveRangeForMinus9999MW = new ArrayList<>();
Set noOperationalLimitInOperationalLimitSet = new LinkedHashSet<>();
List substationsNotAssociatedToValidCountry = new ArrayList<>();
Multimap synchronousMachinesToAdd = HashMultimap.create();
// Substations
for (cim1.model.Substation s : cimModel.getId_Substation().values()) {
cim1.model.SubGeographicalRegion sgr = s.getRegion();
cim1.model.GeographicalRegion gr = sgr.getRegion();
// Country
Country country = null;
if (gr.getName() != null) {
if (gr.getName().equals("D1")
|| gr.getName().equals("D2")
|| gr.getName().equals("D4")
|| gr.getName().equals("D7")
|| gr.getName().equals("D8")) {
country = Country.DE;
} else {
try {
country = Country.valueOf(gr.getName());
} catch (IllegalArgumentException ignored) {
}
}
}
if ((country == null) && (sgr.getName() != null)) {
country = getCountryFromSubregionName(sgr.getName());
}
if (country == null) {
substationsNotAssociatedToValidCountry.add(s.getId());
country = config.getDefaultCountry();
}
// Region
String regionId = namingStrategy.getId(sgr);
// Substation
String newSubstationId = substationIdMapping.get(namingStrategy.getId(s));
String substationId;
String substationName;
if (newSubstationId == null) {
substationId = namingStrategy.getId(s);
substationName = s.getName();
} else {
cim1.model.Substation newS = cimModel.getId_Substation().get(namingStrategy.getCimId(newSubstationId));
substationId = namingStrategy.getId(newS);
substationName = newS.getName();
}
Substation substation = network.getSubstation(substationId);
if (substation == null) {
LOGGER.trace("Create substation {}", substationId);
substation = network.newSubstation()
.setId(substationId)
.setName(substationName)
.setEnsureIdUnicity(false)
.setCountry(country)
.setGeographicalTags(regionId)
.add();
}
// Voltage levels
if (s.getContains_VoltageLevels() != null) {
for (cim1.model.VoltageLevel vl : s.getContains_VoltageLevels()) {
String voltageLevelId = namingStrategy.getId(vl);
VoltageLevel voltageLevel = network.getVoltageLevel(voltageLevelId);
if (voltageLevel == null) {
float[] limits = getVoltageLimits(vl, noOperationalLimitInOperationalLimitSet);
LOGGER.trace("Create voltage level {}", voltageLevelId);
voltageLevel = substation.newVoltageLevel()
.setId(voltageLevelId)
.setName(vl.getName())
.setEnsureIdUnicity(false)
.setNominalV(vl.getBaseVoltage().getNominalVoltage())
.setTopologyKind(TopologyKind.BUS_BREAKER)
.setLowVoltageLimit(limits[0])
.setHighVoltageLimit(limits[1])
.add();
}
// Nodes
if (vl.topologicalNode == null) {
LOGGER.warn("No topological node for voltage level {} in substation {}",
vl.getId(), vl.idMemberOf_Substation);
} else {
for (cim1.model.TopologicalNode tn : vl.getTopologicalNode()) {
LOGGER.trace("Create bus {}", namingStrategy.getId(tn));
// Create the bus corresponding to the topological node
Bus bus = voltageLevel.getBusBreakerView() .newBus()
.setId(namingStrategy.getId(tn))
.add();
cim1.model.SvVoltage svv = tn.getSvVoltage();
if (svv != null) {
svVoltages.put(bus, svv);
} else {
LOGGER.warn("No SvVoltage for bus {}", tn.getId());
}
}
if (vl.getContains_Equipments() != null) {
for (cim1.model.Equipment eq : vl.getContains_Equipments()) {
if (eq instanceof cim1.model.EnergyConsumer) {
createLoad(voltageLevel, (cim1.model.EnergyConsumer) eq);
} else if (eq instanceof cim1.model.ShuntCompensator) {
createShunt(voltageLevel, (cim1.model.ShuntCompensator) eq);
} else if (eq instanceof cim1.model.SynchronousMachine) {
synchronousMachinesToAdd.put(voltageLevel, (cim1.model.SynchronousMachine) eq);
} else if (eq instanceof cim1.model.Switch) {
createSwitch(voltageLevel, (cim1.model.Switch) eq);
}
}
}
}
}
} else {
LOGGER.warn("Substation {} doesn't contain any voltage level", s.getName());
}
}
if (!substationsNotAssociatedToValidCountry.isEmpty()) {
LOGGER.warn("Substations not associated to a valid country and so on associated to default country {}: {}",
config.getDefaultCountry(), substationsNotAssociatedToValidCountry);
}
createLines(network, noOperationalLimitInOperationalLimitSet);
createLowImpedanceLines(network);
List ratioTapChangerToCreateList = new ArrayList<>();
createTransfos(network, noOperationalLimitInOperationalLimitSet, ratioTapChangerToCreateList);
for (Map.Entry> entry : synchronousMachinesToAdd.asMap().entrySet()) {
VoltageLevel vl = entry.getKey();
for (cim1.model.SynchronousMachine sm : entry.getValue()) {
createGenerator(vl, sm,
synchronousMachinesWithoutRegulatingControl,
synchronousMachinesRegulatingVoltageWithZeroTargetVoltage,
synchronousMachinesWithReactiveRangeForMinus9999MW);
}
}
if (!synchronousMachinesWithoutRegulatingControl.isEmpty()) {
LOGGER.warn("Synchronous machines without regulating control: {}", synchronousMachinesWithoutRegulatingControl);
}
if (!synchronousMachinesRegulatingVoltageWithZeroTargetVoltage.isEmpty()) {
LOGGER.warn("Synchronous machines with voltage regulator on and a voltage setpoint to zero, fixed to nominal voltage: {}",
synchronousMachinesRegulatingVoltageWithZeroTargetVoltage);
}
if (!synchronousMachinesWithReactiveRangeForMinus9999MW.isEmpty()) {
LOGGER.warn("CVG bug: synchronous machines with a reactive limit associated to -9999 MW: {}", synchronousMachinesWithReactiveRangeForMinus9999MW);
}
if (!noOperationalLimitInOperationalLimitSet.isEmpty()) {
LOGGER.warn("No OperationalLimit in OperationalLimitSet {}", noOperationalLimitInOperationalLimitSet);
}
for (RatioTapChangerToCreate ratioTapChangerToCreate : ratioTapChangerToCreateList) {
createRatioTapChanger(ratioTapChangerToCreate.rtc, ratioTapChangerToCreate.transfo::newRatioTapChanger,
ratioTapChangerToCreate.rtcSide1, ratioTapChangerToCreate.terminals);
}
for (Map.Entry entry : svVoltages.entrySet()) {
Bus bus = entry.getKey();
cim1.model.SvVoltage svv = entry.getValue();
bus.setAngle(svv.getAngle());
if (svv.getV() > 0) {
bus.setV(svv.getV());
}
}
LOGGER.trace("Network built");
return network;
}
}