com.powsybl.cgmes.conversion.export.SteadyStateHypothesisExport Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of powsybl-cgmes-conversion Show documentation
Show all versions of powsybl-cgmes-conversion Show documentation
Conversion between CGMES and IIDM Network definitions
/**
* Copyright (c) 2020, 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.export;
import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.cgmes.extensions.CgmesControlArea;
import com.powsybl.cgmes.extensions.CgmesControlAreas;
import com.powsybl.cgmes.extensions.CgmesTapChanger;
import com.powsybl.cgmes.extensions.CgmesTapChangers;
import com.powsybl.cgmes.model.CgmesNames;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.exceptions.UncheckedXmlStreamException;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.ActivePowerControl;
import com.powsybl.iidm.network.extensions.LoadDetail;
import com.powsybl.iidm.network.extensions.SlackTerminal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.util.*;
import static com.powsybl.cgmes.model.CgmesNamespace.RDF_NAMESPACE;
/**
* @author Miora Ralambotiana
* @author Luma Zamarreño
*/
public final class SteadyStateHypothesisExport {
private static final Logger LOG = LoggerFactory.getLogger(SteadyStateHypothesisExport.class);
private static final String REGULATING_CONTROL_PROPERTY = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "RegulatingControl";
private static final String GENERATING_UNIT_PROPERTY = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "GeneratingUnit";
private SteadyStateHypothesisExport() {
}
public static void write(Network network, XMLStreamWriter writer, CgmesExportContext context) {
final Map> regulatingControlViews = new HashMap<>();
String cimNamespace = context.getCim().getNamespace();
try {
CgmesExportUtil.writeRdfRoot(cimNamespace, context.getCim().getEuPrefix(), context.getCim().getEuNamespace(), writer);
if (context.getCimVersion() >= 16) {
CgmesExportUtil.writeModelDescription(writer, context.getSshModelDescription(), context);
}
writeEnergyConsumers(network, cimNamespace, writer, context);
writeEquivalentInjections(network, cimNamespace, writer, context);
writeTapChangers(network, cimNamespace, regulatingControlViews, writer, context);
writeSynchronousMachines(network, cimNamespace, regulatingControlViews, writer, context);
writeShuntCompensators(network, cimNamespace, regulatingControlViews, writer, context);
writeStaticVarCompensators(network, cimNamespace, regulatingControlViews, writer, context);
writeRegulatingControls(regulatingControlViews, cimNamespace, writer, context);
writeGeneratingUnitsParticitationFactors(network, cimNamespace, writer, context);
writeConverters(network, cimNamespace, writer, context);
// FIXME open status of retained switches in bus-branch models
writeSwitches(network, cimNamespace, writer, context);
writeTerminals(network, cimNamespace, writer, context);
writeControlAreas(network, cimNamespace, writer, context);
writer.writeEndDocument();
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}
private static void writeSwitches(Network network, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) {
for (Switch sw : network.getSwitches()) {
if (context.isExportedEquipment(sw)) {
writeSwitch(sw, cimNamespace, writer, context);
}
}
}
private static final String ALIAS_TYPE_TERMINAL_1 = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.TERMINAL + "1";
private static final String ALIAS_TYPE_TERMINAL_2 = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.TERMINAL + "2";
private static void writeTerminals(Network network, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) {
for (Connectable> c : network.getConnectables()) { // TODO write boundary terminals for tie lines from CGMES
if (context.isExportedEquipment(c)) {
if (CgmesExportUtil.isEquivalentShuntWithZeroSectionCount(c)) {
// Equivalent shunts do not have a section count in SSH, SV profiles,
// the only way to make output consistent with IIDM section count == 0 is to disconnect its terminal
writeTerminal(CgmesExportUtil.getTerminalId(c.getTerminals().get(0), context), false, cimNamespace, writer, context);
} else {
for (Terminal t : c.getTerminals()) {
writeTerminal(t, cimNamespace, writer, context);
}
}
}
}
for (Switch sw : network.getSwitches()) {
if (context.isExportedEquipment(sw)) {
// Terminals for switches are exported as always connected
// The status of the switch is "open" if any of the original terminals were not connected
// An original "closed" switch with any terminal disconnected
// will be exported as "open" with terminals connected
if (sw.getAliasFromType(ALIAS_TYPE_TERMINAL_1).isPresent()) {
writeTerminal(context.getNamingStrategy().getCgmesIdFromAlias(sw, ALIAS_TYPE_TERMINAL_1), true, cimNamespace, writer, context);
}
if (sw.getAliasFromType(ALIAS_TYPE_TERMINAL_2).isPresent()) {
writeTerminal(context.getNamingStrategy().getCgmesIdFromAlias(sw, ALIAS_TYPE_TERMINAL_2), true, cimNamespace, writer, context);
}
}
}
for (DanglingLine dl : CgmesExportUtil.getBoundaryDanglingLines(network)) {
// Terminal for equivalent injection at boundary is always connected
if (dl.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "EquivalentInjectionTerminal") != null) {
writeTerminal(context.getNamingStrategy().getCgmesIdFromProperty(dl, Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "EquivalentInjectionTerminal"), true, cimNamespace, writer, context);
}
// Terminal for boundary side of original line/switch is always connected
if (dl.getAliasFromType(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "Terminal_Boundary").isPresent()) {
writeTerminal(context.getNamingStrategy().getCgmesIdFromAlias(dl, Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "Terminal_Boundary"), true, cimNamespace, writer, context);
}
}
}
private static void writeEquivalentInjections(Network network, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
// One equivalent injection for every dangling line
List exported = new ArrayList<>();
for (DanglingLine dl : CgmesExportUtil.getBoundaryDanglingLines(network)) {
String ei = dl.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "EquivalentInjection");
if (!exported.contains(ei) && ei != null) {
// Ensure equivalent injection identifier is valid
String cgmesId = context.getNamingStrategy().getCgmesIdFromProperty(dl, Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "EquivalentInjection");
// regulationStatus and regulationTarget are optional,
// but test cases contain the attributes with disabled and 0
boolean regulationStatus = false;
double regulationTarget = 0;
if (dl.getGeneration() != null) {
regulationStatus = dl.getGeneration().isVoltageRegulationOn();
regulationTarget = dl.getGeneration().getTargetV();
}
writeEquivalentInjection(cgmesId, dl.getP0(), dl.getQ0(), regulationStatus, regulationTarget, cimNamespace, writer, context);
exported.add(ei);
}
}
}
private static String cgmesTapChangerId(TwoWindingsTransformer twt, String tapChangerKind, CgmesExportContext context) {
String aliasType = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + tapChangerKind + 1;
if (twt.getAliasFromType(aliasType).isEmpty()) {
aliasType = Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + tapChangerKind + 2;
}
return context.getNamingStrategy().getCgmesIdFromAlias(twt, aliasType);
}
private static void writeTapChangers(Network network, String cimNamespace, Map> regulatingControlViews, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
for (TwoWindingsTransformer twt : network.getTwoWindingsTransformers()) {
if (twt.hasPhaseTapChanger()) {
String ptcId = cgmesTapChangerId(twt, CgmesNames.PHASE_TAP_CHANGER, context);
writeTapChanger(twt, ptcId, twt.getPhaseTapChanger(), CgmesNames.PHASE_TAP_CHANGER_TABULAR, regulatingControlViews, cimNamespace, writer, context);
}
if (twt.hasRatioTapChanger()) {
String rtcId = cgmesTapChangerId(twt, CgmesNames.RATIO_TAP_CHANGER, context);
writeTapChanger(twt, rtcId, twt.getRatioTapChanger(), CgmesNames.RATIO_TAP_CHANGER, regulatingControlViews, cimNamespace, writer, context);
}
}
for (ThreeWindingsTransformer twt : network.getThreeWindingsTransformers()) {
int i = 1;
for (ThreeWindingsTransformer.Leg leg : Arrays.asList(twt.getLeg1(), twt.getLeg2(), twt.getLeg3())) {
if (leg.hasPhaseTapChanger()) {
String ptcId = context.getNamingStrategy().getCgmesIdFromAlias(twt, Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.PHASE_TAP_CHANGER + i);
writeTapChanger(twt, ptcId, leg.getPhaseTapChanger(), CgmesNames.PHASE_TAP_CHANGER_TABULAR, regulatingControlViews, cimNamespace, writer, context);
}
if (leg.hasRatioTapChanger()) {
String rtcId = context.getNamingStrategy().getCgmesIdFromAlias(twt, Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.RATIO_TAP_CHANGER + i);
writeTapChanger(twt, rtcId, leg.getRatioTapChanger(), CgmesNames.RATIO_TAP_CHANGER, regulatingControlViews, cimNamespace, writer, context);
}
i++;
}
}
}
private static > void writeTapChanger(C eq, String tcId, TapChanger, ?> tc, String defaultType, Map> regulatingControlViews, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
String type = CgmesExportUtil.cgmesTapChangerType(eq, tcId).orElse(defaultType);
writeTapChanger(type, tcId, tc, cimNamespace, writer, context);
CgmesTapChangers cgmesTcs = eq.getExtension(CgmesTapChangers.class);
CgmesTapChanger cgmesTc = null;
if (cgmesTcs != null) {
cgmesTc = cgmesTcs.getTapChanger(tcId);
}
addRegulatingControlView(tc, cgmesTc, regulatingControlViews);
// If we are exporting equipment definitions the hidden tap changer will not be exported
// because it has been included in the model for the only tap changer left in IIDM
// If we are exporting only SSH, SV, ... we have to write the step we have saved for it
if (cgmesTcs != null && !context.isExportEquipment()) {
for (CgmesTapChanger tapChanger : cgmesTcs.getTapChangers()) {
if (tapChanger.isHidden() && tapChanger.getCombinedTapChangerId().equals(tcId)) {
writeHiddenTapChanger(tapChanger, defaultType, cimNamespace, writer, context);
}
}
}
}
private static void writeShuntCompensators(Network network, String cimNamespace, Map> regulatingControlViews,
XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
for (ShuntCompensator s : network.getShuntCompensators()) {
if ("true".equals(s.getProperty(Conversion.PROPERTY_IS_EQUIVALENT_SHUNT))) {
continue;
}
String shuntType;
switch (s.getModelType()) {
case LINEAR:
shuntType = "Linear";
break;
case NON_LINEAR:
shuntType = "Nonlinear";
break;
default:
throw new IllegalStateException("Unexpected shunt model type: " + s.getModelType());
}
boolean controlEnabled = s.isVoltageRegulatorOn();
CgmesExportUtil.writeStartAbout(shuntType + "ShuntCompensator", context.getNamingStrategy().getCgmesId(s), cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "ShuntCompensator.sections");
writer.writeCharacters(CgmesExportUtil.format(s.getSectionCount()));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "RegulatingCondEq.controlEnabled");
writer.writeCharacters(Boolean.toString(controlEnabled));
writer.writeEndElement();
writer.writeEndElement();
addRegulatingControlView(s, regulatingControlViews, context);
}
}
private static void addRegulatingControlView(ShuntCompensator s, Map> regulatingControlViews, CgmesExportContext context) {
if (s.hasProperty(REGULATING_CONTROL_PROPERTY)) {
// PowSyBl has considered the control as discrete, with a certain targetDeadband
// The target value is stored in kV by PowSyBl, so unit multiplier is "k"
String rcid = context.getNamingStrategy().getCgmesIdFromProperty(s, REGULATING_CONTROL_PROPERTY);
RegulatingControlView rcv = new RegulatingControlView(rcid, RegulatingControlType.REGULATING_CONTROL, true,
s.isVoltageRegulatorOn(), s.getTargetDeadband(), s.getTargetV(), "k");
regulatingControlViews.computeIfAbsent(rcid, k -> new ArrayList<>()).add(rcv);
}
}
private static void writeSynchronousMachines(Network network, String cimNamespace, Map> regulatingControlViews,
XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
for (Generator g : network.getGenerators()) {
boolean controlEnabled = g.isVoltageRegulatorOn();
CgmesExportUtil.writeStartAbout("SynchronousMachine", context.getNamingStrategy().getCgmesId(g), cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "RegulatingCondEq.controlEnabled");
writer.writeCharacters(Boolean.toString(controlEnabled));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "RotatingMachine.p");
writer.writeCharacters(CgmesExportUtil.format(-g.getTargetP()));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "RotatingMachine.q");
writer.writeCharacters(CgmesExportUtil.format(-g.getTargetQ()));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "SynchronousMachine.referencePriority");
// reference priority is used for angle reference selection (slack)
writer.writeCharacters(isInSlackBus(g) ? "1" : "0");
writer.writeEndElement();
writer.writeEmptyElement(cimNamespace, "SynchronousMachine.operatingMode");
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.RESOURCE, cimNamespace + "SynchronousMachineOperatingMode." + mode(g.getTargetP(), g.getMinP(), g.getReactiveLimits()));
writer.writeEndElement();
addRegulatingControlView(g, regulatingControlViews, context);
}
for (Battery b : network.getBatteries()) {
CgmesExportUtil.writeStartAbout("SynchronousMachine", context.getNamingStrategy().getCgmesId(b), cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "RegulatingCondEq.controlEnabled");
writer.writeCharacters("false"); // TODO handle battery regulation
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "RotatingMachine.p");
writer.writeCharacters(CgmesExportUtil.format(-b.getTargetP()));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "RotatingMachine.q");
writer.writeCharacters(CgmesExportUtil.format(-b.getTargetQ()));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "SynchronousMachine.referencePriority");
// reference priority is used for angle reference selection (slack)
writer.writeCharacters(isInSlackBus(b) ? "1" : "0");
writer.writeEndElement();
writer.writeEmptyElement(cimNamespace, "SynchronousMachine.operatingMode");
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.RESOURCE, cimNamespace + "SynchronousMachineOperatingMode." + mode(b.getTargetP(), b.getMinP(), b.getReactiveLimits()));
writer.writeEndElement();
}
}
private static String mode(double targetP, double minP, ReactiveLimits reactiveLimits) {
if (targetP < 0) {
return "motor";
} else if (targetP > 0) {
return "generator";
} else { // targetP == 0, mode can be motor or generator: we look at constructor reactive capability curve to decide the mode
return isMotor(minP, reactiveLimits) ? "motor" : "generator";
}
}
private static boolean isMotor(double minP, ReactiveLimits l) {
if (l instanceof ReactiveCapabilityCurve curve) {
return curve.getMinP() < 0;
} else {
return minP < 0;
}
}
private static void addRegulatingControlView(Generator g, Map> regulatingControlViews, CgmesExportContext context) {
if (g.hasProperty(REGULATING_CONTROL_PROPERTY)) {
// PowSyBl has considered the control as continuous and with targetDeadband of size 0
// The target value is stored in kV by PowSyBl, so unit multiplier is "k"
String rcid = context.getNamingStrategy().getCgmesIdFromProperty(g, REGULATING_CONTROL_PROPERTY);
double targetDeadband = 0;
RegulatingControlView rcv = new RegulatingControlView(rcid, RegulatingControlType.REGULATING_CONTROL, false,
g.isVoltageRegulatorOn(), targetDeadband, g.getTargetV(), "k");
regulatingControlViews.computeIfAbsent(rcid, k -> new ArrayList<>()).add(rcv);
}
}
private static void writeStaticVarCompensators(Network network, String cimNamespace, Map> regulatingControlViews,
XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
for (StaticVarCompensator svc : network.getStaticVarCompensators()) {
StaticVarCompensator.RegulationMode regulationMode = svc.getRegulationMode();
boolean controlEnabled = regulationMode != StaticVarCompensator.RegulationMode.OFF;
CgmesExportUtil.writeStartAbout("StaticVarCompensator", context.getNamingStrategy().getCgmesId(svc), cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "RegulatingCondEq.controlEnabled");
writer.writeCharacters(Boolean.toString(controlEnabled));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "StaticVarCompensator.q");
writer.writeCharacters(CgmesExportUtil.format(svc.getTerminal().getQ()));
writer.writeEndElement();
writer.writeEndElement();
if (svc.hasProperty(REGULATING_CONTROL_PROPERTY)) {
String rcid = context.getNamingStrategy().getCgmesIdFromProperty(svc, REGULATING_CONTROL_PROPERTY);
double targetDeadband = 0;
// Regulating control could be reactive power or voltage
double targetValue;
String multiplier;
if (regulationMode == StaticVarCompensator.RegulationMode.VOLTAGE) {
targetValue = svc.getVoltageSetpoint();
multiplier = "k";
} else if (regulationMode == StaticVarCompensator.RegulationMode.REACTIVE_POWER) {
targetValue = svc.getReactivePowerSetpoint();
multiplier = "M";
} else {
targetValue = 0;
multiplier = "k";
}
RegulatingControlView rcv = new RegulatingControlView(rcid, RegulatingControlType.REGULATING_CONTROL, false,
controlEnabled, targetDeadband, targetValue, multiplier);
regulatingControlViews.computeIfAbsent(rcid, k -> new ArrayList<>()).add(rcv);
}
}
}
private static boolean isInSlackBus(Injection> g) {
VoltageLevel vl = g.getTerminal().getVoltageLevel();
SlackTerminal slackTerminal = vl.getExtension(SlackTerminal.class);
if (slackTerminal != null) {
Bus slackBus = slackTerminal.getTerminal().getBusBreakerView().getBus();
if (slackBus == g.getTerminal().getBusBreakerView().getBus()) {
return true;
}
}
return false;
}
private static void writeTapChanger(String type, String id, TapChanger, ?> tc, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
writeTapChanger(type, id, tc.isRegulating(), tc.getTapPosition(), cimNamespace, writer, context);
}
private static void writeTapChanger(String type, String id, boolean controlEnabled, int step, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
CgmesExportUtil.writeStartAbout(type, id, cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "TapChanger.controlEnabled");
writer.writeCharacters(Boolean.toString(controlEnabled));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "TapChanger.step");
writer.writeCharacters(CgmesExportUtil.format(step));
writer.writeEndElement();
writer.writeEndElement();
}
private static void addRegulatingControlView(TapChanger, ?> tc, CgmesTapChanger cgmesTc, Map> regulatingControlViews) {
// Multiple tap changers can be stored at the same equipment
// We use the tap changer id as part of the key for storing the tap changer control id
if (cgmesTc != null && cgmesTc.getControlId() != null) {
String controlId = cgmesTc.getControlId();
RegulatingControlView rcv = null;
if (tc instanceof RatioTapChanger ratioTapChanger) {
rcv = new RegulatingControlView(controlId,
RegulatingControlType.TAP_CHANGER_CONTROL,
true,
tc.isRegulating(),
tc.getTargetDeadband(),
ratioTapChanger.getTargetV(),
// Unit multiplier is k for ratio tap changers (regulation value is a voltage in kV)
"k");
} else if (tc instanceof PhaseTapChanger phaseTapChanger) {
boolean valid;
String unitMultiplier;
switch (phaseTapChanger.getRegulationMode()) {
case CURRENT_LIMITER:
// Unit multiplier is none (multiply by 1), regulation value is a current in Amperes
valid = true;
unitMultiplier = "none";
break;
case ACTIVE_POWER_CONTROL:
// Unit multiplier is M, regulation value is an active power flow in MW
valid = true;
unitMultiplier = "M";
break;
case FIXED_TAP:
default:
valid = false;
unitMultiplier = "none";
break;
}
if (valid) {
rcv = new RegulatingControlView(controlId,
RegulatingControlType.TAP_CHANGER_CONTROL,
true,
tc.isRegulating(),
tc.getTargetDeadband(),
((PhaseTapChanger) tc).getRegulationValue(),
unitMultiplier);
}
}
if (rcv != null) {
regulatingControlViews.computeIfAbsent(controlId, k -> new ArrayList<>()).add(rcv);
}
}
}
private static void writeHiddenTapChanger(CgmesTapChanger cgmesTc, String defaultType, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
writeTapChanger(Optional.ofNullable(cgmesTc.getType()).orElse(defaultType), cgmesTc.getId(), false,
cgmesTc.getStep().orElseThrow(() -> new PowsyblException("Non null step expected for tap changer " + cgmesTc.getId())),
cimNamespace, writer, context);
}
private static void writeRegulatingControls(Map> regulatingControlViews, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
for (List views : regulatingControlViews.values()) {
writeRegulatingControl(combineRegulatingControlViews(views), cimNamespace, writer, context);
}
}
private static RegulatingControlView combineRegulatingControlViews(List rcs) {
RegulatingControlView combined = rcs.get(0);
if (rcs.size() > 1) {
LOG.warn("Multiple views ({}) for regulating control {} are combined", rcs.size(), rcs.get(0).id);
}
for (int k = 1; k < rcs.size(); k++) {
RegulatingControlView current = rcs.get(k);
if (combined.targetDeadband == 0 && current.targetDeadband > 0) {
combined.targetDeadband = current.targetDeadband;
}
if (!combined.discrete && current.discrete) {
combined.discrete = true;
}
if (!combined.controlEnabled && current.controlEnabled) {
combined.controlEnabled = true;
}
}
return combined;
}
private static void writeRegulatingControl(RegulatingControlView rc, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
CgmesExportUtil.writeStartAbout(regulatingControlClassname(rc.type), rc.id, cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "RegulatingControl.discrete");
writer.writeCharacters(Boolean.toString(rc.discrete));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "RegulatingControl.enabled");
writer.writeCharacters(Boolean.toString(rc.controlEnabled));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "RegulatingControl.targetDeadband");
writer.writeCharacters(CgmesExportUtil.format(rc.targetDeadband));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "RegulatingControl.targetValue");
writer.writeCharacters(CgmesExportUtil.format(rc.targetValue));
writer.writeEndElement();
writer.writeEmptyElement(cimNamespace, "RegulatingControl.targetValueUnitMultiplier");
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.RESOURCE, cimNamespace + "UnitMultiplier." + rc.targetValueUnitMultiplier);
writer.writeEndElement();
}
private static String regulatingControlClassname(RegulatingControlType type) {
if (type == RegulatingControlType.TAP_CHANGER_CONTROL) {
return "TapChangerControl";
} else {
return "RegulatingControl";
}
}
private static void writeSwitch(Switch sw, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) {
try {
String switchType = sw.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "switchType");
String className = switchType != null ? switchType : CgmesExportUtil.switchClassname(sw.getKind());
CgmesExportUtil.writeStartAbout(className, context.getNamingStrategy().getCgmesId(sw), cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "Switch.open");
writer.writeCharacters(Boolean.toString(sw.isOpen()));
writer.writeEndElement();
writer.writeEndElement();
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}
private static void writeTerminal(Terminal t, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) {
writeTerminal(CgmesExportUtil.getTerminalId(t, context), t.isConnected(), cimNamespace, writer, context);
}
private static void writeTerminal(String terminalId, boolean connected, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) {
try {
CgmesExportUtil.writeStartAbout(CgmesNames.TERMINAL, terminalId, cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "ACDCTerminal.connected");
writer.writeCharacters(Boolean.toString(connected));
writer.writeEndElement();
writer.writeEndElement();
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}
private static void writeEquivalentInjection(String cgmesId, double p, double q, boolean regulationStatus, double regulationTarget, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
CgmesExportUtil.writeStartAbout("EquivalentInjection", cgmesId, cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "EquivalentInjection.p");
writer.writeCharacters(CgmesExportUtil.format(p));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "EquivalentInjection.q");
writer.writeCharacters(CgmesExportUtil.format(q));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "EquivalentInjection.regulationStatus");
writer.writeCharacters(Boolean.toString(regulationStatus));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "EquivalentInjection.regulationTarget");
writer.writeCharacters(CgmesExportUtil.format(regulationTarget));
writer.writeEndElement();
writer.writeEndElement();
}
private static void writeEnergyConsumers(Network network, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
for (Load load : network.getLoads()) {
if (context.isExportedEquipment(load)) {
if (load.getP0() < 0) {
// As negative loads are not allowed, they are modeled as energy source.
// Note that negative loads can be the result of network reduction and could be modeled
// as equivalent injections.
String cgmesId = context.getNamingStrategy().getCgmesId(load);
writeEnergySource(cgmesId, load.getP0(), load.getQ0(), cimNamespace, writer, context);
} else {
writeSshEnergyConsumer(context.getNamingStrategy().getCgmesId(load), load.getP0(), load.getQ0(), load.getExtension(LoadDetail.class), cimNamespace, writer, context);
}
}
}
}
private static void writeEnergySource(String id, double p, double q, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
CgmesExportUtil.writeStartAbout("EnergySource", id, cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "EnergySource.activePower");
writer.writeCharacters(CgmesExportUtil.format(p));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "EnergySource.reactivePower");
writer.writeCharacters(CgmesExportUtil.format(q));
writer.writeEndElement();
writer.writeEndElement();
}
private static void writeSshEnergyConsumer(String id, double p, double q, LoadDetail loadDetail, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
CgmesExportUtil.writeStartAbout(CgmesExportUtil.loadClassName(loadDetail), id, cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "EnergyConsumer.p");
writer.writeCharacters(CgmesExportUtil.format(p));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "EnergyConsumer.q");
writer.writeCharacters(CgmesExportUtil.format(q));
writer.writeEndElement();
writer.writeEndElement();
}
private static void writeConverters(Network network, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
for (HvdcConverterStation> converterStation : network.getHvdcConverterStations()) {
CgmesExportUtil.writeStartAbout(CgmesExportUtil.converterClassName(converterStation), context.getNamingStrategy().getCgmesId(converterStation), cimNamespace, writer, context);
double ppcc;
if (CgmesExportUtil.isConverterStationRectifier(converterStation)) {
ppcc = converterStation.getHvdcLine().getActivePowerSetpoint();
} else {
double otherConverterStationLossFactor = converterStation.getOtherConverterStation().map(HvdcConverterStation::getLossFactor).orElse(0.0f);
double pDCInverter = converterStation.getHvdcLine().getActivePowerSetpoint() * (1 - otherConverterStationLossFactor / 100);
double poleLoss = converterStation.getLossFactor() / 100 * pDCInverter;
ppcc = -(pDCInverter - poleLoss);
}
writer.writeStartElement(cimNamespace, "ACDCConverter.targetPpcc");
writer.writeCharacters(CgmesExportUtil.format(ppcc));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "ACDCConverter.targetUdc");
writer.writeCharacters(CgmesExportUtil.format(0.0));
writer.writeEndElement();
if (converterStation instanceof LccConverterStation lccConverterStation) {
writePandQ(cimNamespace, ppcc, getQfromPowerFactor(ppcc, lccConverterStation.getPowerFactor()), writer);
writer.writeStartElement(cimNamespace, "CsConverter.targetAlpha");
writer.writeCharacters(CgmesExportUtil.format(0));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "CsConverter.targetGamma");
writer.writeCharacters(CgmesExportUtil.format(0));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "CsConverter.targetIdc");
writer.writeCharacters(CgmesExportUtil.format(0));
writer.writeEndElement();
writer.writeEmptyElement(cimNamespace, "CsConverter.operatingMode");
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.RESOURCE, cimNamespace + converterOperatingMode(converterStation));
writer.writeEmptyElement(cimNamespace, "CsConverter.pPccControl");
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.RESOURCE, cimNamespace + "CsPpccControlKind.activePower");
} else if (converterStation instanceof VscConverterStation vscConverterStation) {
writePandQ(cimNamespace, ppcc, vscConverterStation.getReactivePowerSetpoint(), writer);
writer.writeStartElement(cimNamespace, "VsConverter.droop");
writer.writeCharacters(CgmesExportUtil.format(0));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "VsConverter.droopCompensation");
writer.writeCharacters(CgmesExportUtil.format(0));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "VsConverter.qShare");
writer.writeCharacters(CgmesExportUtil.format(0));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "VsConverter.targetQpcc");
writer.writeCharacters(CgmesExportUtil.format(vscConverterStation.getReactivePowerSetpoint()));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "VsConverter.targetUpcc");
writer.writeCharacters(CgmesExportUtil.format(vscConverterStation.getVoltageSetpoint()));
writer.writeEndElement();
writer.writeEmptyElement(cimNamespace, "VsConverter.pPccControl");
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.RESOURCE, cimNamespace + converterOperatingMode(converterStation));
writer.writeEmptyElement(cimNamespace, "VsConverter.qPccControl");
writer.writeAttribute(RDF_NAMESPACE, CgmesNames.RESOURCE, cimNamespace + "VsQpccControlKind." + (vscConverterStation.isVoltageRegulatorOn() ? "voltagePcc" : "reactivePcc"));
}
writer.writeEndElement();
}
}
private static void writePandQ(String cimNamespace, double p, double q, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement(cimNamespace, "ACDCConverter.p");
writer.writeCharacters(CgmesExportUtil.format(p));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "ACDCConverter.q");
writer.writeCharacters(CgmesExportUtil.format(q));
writer.writeEndElement();
}
public static String converterOperatingMode(HvdcConverterStation> converterStation) {
if (CgmesExportUtil.isConverterStationRectifier(converterStation)) {
return converterStationRectifier(converterStation);
} else {
return converterStationInverter(converterStation);
}
}
public static String converterStationRectifier(HvdcConverterStation> converterStation) {
if (converterStation instanceof LccConverterStation) {
return "CsOperatingModeKind.rectifier";
} else if (converterStation instanceof VscConverterStation) {
return "VsPpccControlKind.pPcc";
}
throw new PowsyblException("Invalid converter type");
}
public static String converterStationInverter(HvdcConverterStation> converterStation) {
if (converterStation instanceof LccConverterStation) {
return "CsOperatingModeKind.inverter";
} else if (converterStation instanceof VscConverterStation) {
return "VsPpccControlKind.udc";
}
throw new PowsyblException("Invalid converter type");
}
private static double getQfromPowerFactor(double p, double powerFactor) {
if (powerFactor == 0.0) {
return 0.0;
}
return p * Math.sqrt((1 - powerFactor * powerFactor) / (powerFactor * powerFactor));
}
private static void writeGeneratingUnitsParticitationFactors(Network network, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
// Multiple generators may share the same generation unit,
// we will choose the participation factor from the last generator that references the generating unit
// We only consider generators and batteries that have participation factors
Map generatingUnits = new HashMap<>();
for (Generator g : network.getGenerators()) {
GeneratingUnit gu = generatingUnitForGeneratorAndBatteries(g, context);
if (gu != null) {
generatingUnits.put(gu.id, gu);
}
}
for (Battery b : network.getBatteries()) {
GeneratingUnit gu = generatingUnitForGeneratorAndBatteries(b, context);
if (gu != null) {
generatingUnits.put(gu.id, gu);
}
}
for (GeneratingUnit gu : generatingUnits.values()) {
writeGeneratingUnitParticipationFactor(gu, cimNamespace, writer, context);
}
}
private static GeneratingUnit generatingUnitForGeneratorAndBatteries(Injection> i, CgmesExportContext context) {
if (i.hasProperty(GENERATING_UNIT_PROPERTY) && (i.getExtension(ActivePowerControl.class) != null || i.hasProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "normalPF"))) {
GeneratingUnit gu = new GeneratingUnit();
gu.id = context.getNamingStrategy().getCgmesIdFromProperty(i, GENERATING_UNIT_PROPERTY);
if (i.getExtension(ActivePowerControl.class) != null) {
gu.participationFactor = i.getExtension(ActivePowerControl.class).getParticipationFactor();
} else {
gu.participationFactor = Double.valueOf(i.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + "normalPF"));
}
gu.className = generatingUnitClassname(i);
return gu;
}
return null;
}
private static void writeGeneratingUnitParticipationFactor(GeneratingUnit gu, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
CgmesExportUtil.writeStartAbout(gu.className, gu.id, cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "GeneratingUnit.normalPF");
writer.writeCharacters(CgmesExportUtil.format(gu.participationFactor));
writer.writeEndElement();
writer.writeEndElement();
}
private static String generatingUnitClassname(Injection> i) {
if (i instanceof Generator generator) {
EnergySource energySource = generator.getEnergySource();
if (energySource == EnergySource.HYDRO) {
return "HydroGeneratingUnit";
} else if (energySource == EnergySource.NUCLEAR) {
return "NuclearGeneratingUnit";
} else if (energySource == EnergySource.SOLAR) {
return "SolarGeneratingUnit";
} else if (energySource == EnergySource.THERMAL) {
return "ThermalGeneratingUnit";
} else if (energySource == EnergySource.WIND) {
return "WindGeneratingUnit";
} else {
return "GeneratingUnit";
}
}
if (i instanceof Battery) {
return "HydroGeneratingUnit"; // TODO export battery differently in CGMES 3.0
}
throw new PowsyblException("Unexpected class for " + i.getId() + " using generating units: " + i.getClass());
}
private static void writeControlAreas(Network network, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
CgmesControlAreas areas = network.getExtension(CgmesControlAreas.class);
for (CgmesControlArea area : areas.getCgmesControlAreas()) {
writeControlArea(area, cimNamespace, writer, context);
}
}
private static void writeControlArea(CgmesControlArea area, String cimNamespace, XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
String areaId = context.getNamingStrategy().getCgmesId(area.getId());
CgmesExportUtil.writeStartAbout("ControlArea", areaId, cimNamespace, writer, context);
writer.writeStartElement(cimNamespace, "ControlArea.netInterchange");
writer.writeCharacters(CgmesExportUtil.format(area.getNetInterchange()));
writer.writeEndElement();
writer.writeStartElement(cimNamespace, "ControlArea.pTolerance");
writer.writeCharacters(CgmesExportUtil.format(area.getPTolerance()));
writer.writeEndElement();
writer.writeEndElement();
}
private enum RegulatingControlType {
REGULATING_CONTROL, TAP_CHANGER_CONTROL
}
private static class GeneratingUnit {
String id;
String className;
double participationFactor;
}
static class RegulatingControlView {
String id;
RegulatingControlType type;
boolean discrete;
boolean controlEnabled;
double targetDeadband;
double targetValue;
String targetValueUnitMultiplier;
RegulatingControlView(String id, RegulatingControlType type, boolean discrete, boolean controlEnabled,
double targetDeadband, double targetValue, String targetValueUnitMultiplier) {
this.id = id;
this.type = type;
this.discrete = discrete;
this.controlEnabled = controlEnabled;
this.targetDeadband = targetDeadband;
this.targetValue = targetValue;
this.targetValueUnitMultiplier = targetValueUnitMultiplier;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy