Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cgmes active reactive regulating control #2995

Merged
merged 23 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ private void addIidmMappingsGenerators(Network network) {
}

private static boolean hasVoltageControlCapability(Generator generator) {
if (Double.isNaN(generator.getTargetV()) || generator.getReactiveLimits() == null) {
if (generator.getReactiveLimits() == null) {
return false;
}

Expand Down Expand Up @@ -432,7 +432,8 @@ private void addIidmMappingsShuntCompensators(Network network) {
continue;
}
String regulatingControlId = shuntCompensator.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + REGULATING_CONTROL);
if (regulatingControlId == null && (shuntCompensator.isVoltageRegulatorOn() || !Objects.equals(shuntCompensator, shuntCompensator.getRegulatingTerminal().getConnectable()))) {
if (regulatingControlId == null && (CgmesExportUtil.isValidVoltageSetpoint(shuntCompensator.getTargetV())
|| !Objects.equals(shuntCompensator, shuntCompensator.getRegulatingTerminal().getConnectable()))) {
regulatingControlId = namingStrategy.getCgmesId(ref(shuntCompensator), Part.REGULATING_CONTROL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe create also the control when the regulating control is remote. If cgmes EQ file is imported (no SSH file), then only the regulating terminal is defined, given that the setpoint is in the SSH file. Better do not lose the regulating control in this case if the iidm model is exported. In iidm regulating terminal null is the same as local control, then only if the regulating control is remote we have to export it.

if (regulatingControlId == null
                   && (SteadyStateHypothesisExport.isValidVoltageSetpoint(shuntCompensator.getTargetV())
                   || !Objects.equals(shuntCompensator, shuntCompensator.getRegulatingTerminal().getConnectable()))) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is RC creation from scratch (regulatingControlId == null) so it comes down to deciding if Shunt has voltage regulating capability from IIDM.
If targetV is not set, in case of full CGMES export from IIDM, SSH will have incorrect values for the control.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the targetV is not set, the validation level is EQUIPMENT and does not make sense to export the SSH file.
I prefer to export properly the EQ file, including the control, in concordance with the validation level.

shuntCompensator.setProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + REGULATING_CONTROL, regulatingControlId);
}
Expand All @@ -442,7 +443,11 @@ private void addIidmMappingsShuntCompensators(Network network) {
private void addIidmMappingsStaticVarCompensators(Network network) {
for (StaticVarCompensator svc : network.getStaticVarCompensators()) {
String regulatingControlId = svc.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + REGULATING_CONTROL);
if (regulatingControlId == null && (StaticVarCompensator.RegulationMode.VOLTAGE.equals(svc.getRegulationMode()) || !Objects.equals(svc, svc.getRegulatingTerminal().getConnectable()))) {
boolean validVoltageSetpoint = CgmesExportUtil.isValidVoltageSetpoint(svc.getVoltageSetpoint());
boolean validReactiveSetpoint = CgmesExportUtil.isValidReactivePowerSetpoint(svc.getReactivePowerSetpoint());
if (regulatingControlId == null && (validReactiveSetpoint
|| validVoltageSetpoint
|| !Objects.equals(svc, svc.getRegulatingTerminal().getConnectable()))) {
regulatingControlId = namingStrategy.getCgmesId(ref(svc), Part.REGULATING_CONTROL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In concordance with the previous comment, we can export it if:

if (regulatingControlId == null && (validVoltageSetpoint
                            || validReactiveSetpoint
                            || !Objects.equals(svc, svc.getRegulatingTerminal().getConnectable()))) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this is related to RC creation from scratch (no CGMES import).
In CGMES, SVC must have a RC in order to function (SVC parameters regarding control should be ignored). This is valid for local and remote controls.
In this implementation, we avoid exporting RC only when regulationMode is not set and we have both voltage and reactive targets (it's not clear which to use).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in favor of exporting the maximum information if the information is valid. In this case, I prefer to export the voltage control as disabled than do not export anything, but of course both options are valid.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a use case where we export only an EQ file, without SSH file.

svc.setProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + REGULATING_CONTROL, regulatingControlId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.powsybl.cgmes.conversion.export;

import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.cgmes.conversion.export.elements.RegulatingControlEq;
import com.powsybl.cgmes.conversion.naming.CgmesObjectReference;
import com.powsybl.cgmes.conversion.naming.CgmesObjectReference.Part;
import com.powsybl.cgmes.extensions.CgmesTapChanger;
Expand All @@ -21,6 +22,7 @@
import com.powsybl.commons.report.TypedValue;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.LoadDetail;
import com.powsybl.iidm.network.extensions.RemoteReactivePowerControl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -404,7 +406,7 @@ private static <C extends Connectable<C>> String getTapChangerId(C twt, String c
}

static boolean regulatingControlIsDefined(RatioTapChanger rtc) {
return !Double.isNaN(rtc.getTargetV())
return !Double.isNaN(rtc.getRegulationValue())
&& !Double.isNaN(rtc.getTargetDeadband())
&& rtc.getRegulationTerminal() != null;
}
Expand Down Expand Up @@ -513,6 +515,68 @@ static String obtainCalculatedSynchronousMachineKind(double minP, double maxP, R
return kind;
}

public static boolean isValidVoltageSetpoint(double v) {
return Double.isFinite(v) && v > 0;
}

public static boolean isValidReactivePowerSetpoint(double q) {
return Double.isFinite(q);
}

public static String getGeneratorRegulatingControlMode(Generator generator, RemoteReactivePowerControl rrpc) {
if (rrpc == null) {
return RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
}
boolean enabledVoltageControl = generator.isVoltageRegulatorOn();
boolean enabledReactivePowerControl = rrpc.isEnabled();

if (enabledVoltageControl) {
return RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
} else if (enabledReactivePowerControl) {
return RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER;
} else {
boolean validVoltageSetpoint = isValidVoltageSetpoint(generator.getTargetV());
boolean validReactiveSetpoint = isValidReactivePowerSetpoint(rrpc.getTargetQ());
if (validReactiveSetpoint && !validVoltageSetpoint) {
return RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER;
}
return RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
}
}

public static String getSvcMode(StaticVarCompensator svc) {
if (svc.getRegulationMode().equals(StaticVarCompensator.RegulationMode.VOLTAGE)) {
return RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
} else if (svc.getRegulationMode().equals(StaticVarCompensator.RegulationMode.REACTIVE_POWER)) {
return RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER;
} else {
boolean validVoltageSetpoint = isValidVoltageSetpoint(svc.getVoltageSetpoint());
boolean validReactiveSetpoint = isValidReactivePowerSetpoint(svc.getReactivePowerSetpoint());
if (validReactiveSetpoint && !validVoltageSetpoint) {
return RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER;
}
return RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
}
}

public static String getTcMode(RatioTapChanger rtc) {
if (rtc.getRegulationMode() == null) {
throw new PowsyblException("Regulation mode not defined for RTC.");
}
return switch (rtc.getRegulationMode()) {
case VOLTAGE -> RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
case REACTIVE_POWER -> RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER;
};
}

public static String getPhaseTapChangerRegulationMode(PhaseTapChanger ptc) {
return switch (ptc.getRegulationMode()) {
case CURRENT_LIMITER -> RegulatingControlEq.REGULATING_CONTROL_CURRENT_FLOW;
case ACTIVE_POWER_CONTROL -> RegulatingControlEq.REGULATING_CONTROL_ACTIVE_POWER;
default -> throw new PowsyblException("Unexpected regulation mode: " + ptc.getRegulationMode());
};
}

public static boolean isMinusOrMaxValue(double value) {
return value == -Double.MAX_VALUE || value == Double.MAX_VALUE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.powsybl.commons.exceptions.UncheckedXmlStreamException;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.Identifiable;
import com.powsybl.iidm.network.extensions.RemoteReactivePowerControl;
import com.powsybl.iidm.network.extensions.VoltagePerReactivePowerControl;

import org.apache.commons.lang3.tuple.Pair;
Expand All @@ -45,9 +46,6 @@
public final class EquipmentExport {

private static final String AC_DC_CONVERTER_DC_TERMINAL = "ACDCConverterDCTerminal";
private static final String PHASE_TAP_CHANGER_REGULATION_MODE_ACTIVE_POWER = "activePower";
private static final String PHASE_TAP_CHANGER_REGULATION_MODE_CURRENT_FLOW = "currentFlow";
private static final String RATIO_TAP_CHANGER_REGULATION_MODE_VOLTAGE = "voltage";
private static final String TERMINAL_BOUNDARY = "Terminal_Boundary";
private static final Logger LOG = LoggerFactory.getLogger(EquipmentExport.class);

Expand Down Expand Up @@ -382,7 +380,9 @@ private static void writeGenerators(Network network, Map<Terminal, String> mapTe
Set<String> generatingUnitsWritten = new HashSet<>();
for (Generator generator : network.getGenerators()) {
String cgmesOriginalClass = generator.getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS, CgmesNames.SYNCHRONOUS_MACHINE);

RemoteReactivePowerControl rrpc = generator.getExtension(RemoteReactivePowerControl.class);
String mode = CgmesExportUtil.getGeneratorRegulatingControlMode(generator, rrpc);
Terminal regulatingTerminal = mode.equals(RegulatingControlEq.REGULATING_CONTROL_VOLTAGE) ? generator.getRegulatingTerminal() : rrpc.getRegulatingTerminal();
switch (cgmesOriginalClass) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My proposal:

RemoteReactivePowerControl rrpc = generator.getExtension(RemoteReactivePowerControl.class);
String mode = getGeneratorRegulatingControlMode(generator, rrpc);
Terminal regulatingTerminal = mode.equals(RegulatingControlEq.REGULATING_CONTROL_VOLTAGE) ? generator.getRegulatingTerminal() : rrpc.getRegulatingTerminal();
private static String getGeneratorRegulatingControlMode(Generator generator, RemoteReactivePowerControl rrpc) {
        if (rrpc == null) {
            return RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
        }
        boolean enabledVoltageControl = generator.isVoltageRegulatorOn();
        boolean enabledReactivePowerControl = rrpc.isEnabled();
        
        if (enabledVoltageControl) {
            return RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
        } else if (enabledReactivePowerControl) {
            return RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER;
        } else {
            boolean validVoltageSetpoint = SteadyStateHypothesisExport.isValidVoltageSetpoint(generator.getTargetV());
            if (validVoltageSetpoint) {
                return RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
            } else {
                return RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER;
            }
        }
    }

We can define the getGeneratorRegulatingControlMode method in the CgmesExportUtil class and use it during the SSH export to be coherent with the EQ export.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difficulty here is in priorities: Is the RRPC extension priority or voltage control?

Example: rrpc exists, voltage control is enabled -> We export voltage control, regardless of rrpc status.

Since rrpc has to be created explicitly as an extension, assumption is that it has the priority.
Therefore, if rrpc is there, we ignore voltage control completely and only use rrpc parameters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the reactive power control extension is present and enabled, it does not override the voltage control if enabled. Maybe it is a mistake but it is what is developed inside open loadflow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to select the control priority using the electrical information rather than the software information. The final user, in most of the cases, will not know how the model is internally implemented. Then, if both controls have the same valid attributes I prefer to export the voltage control. But as before, both options are valid.

case CgmesNames.EQUIVALENT_INJECTION:
String reactiveCapabilityCurveId = writeReactiveCapabilityCurve(generator, cimNamespace, writer, context);
Expand All @@ -393,14 +393,14 @@ private static void writeGenerators(Network network, Map<Terminal, String> mapTe
cimNamespace, writer, context);
break;
case CgmesNames.EXTERNAL_NETWORK_INJECTION:
String regulatingControlId = RegulatingControlEq.writeKindVoltage(generator, exportedTerminalId(mapTerminal2Id, generator.getRegulatingTerminal()), regulatingControlsWritten, cimNamespace, writer, context);
String regulatingControlId = RegulatingControlEq.writeRegulatingControlEq(generator, exportedTerminalId(mapTerminal2Id, regulatingTerminal), regulatingControlsWritten, mode, cimNamespace, writer, context);
ExternalNetworkInjectionEq.write(context.getNamingStrategy().getCgmesId(generator), generator.getNameOrId(),
context.getNamingStrategy().getCgmesId(generator.getTerminal().getVoltageLevel()),
obtainGeneratorGovernorScd(generator), generator.getMaxP(), obtainMaxQ(generator), generator.getMinP(), obtainMinQ(generator),
regulatingControlId, cimNamespace, writer, context);
break;
case CgmesNames.SYNCHRONOUS_MACHINE:
regulatingControlId = RegulatingControlEq.writeKindVoltage(generator, exportedTerminalId(mapTerminal2Id, generator.getRegulatingTerminal()), regulatingControlsWritten, cimNamespace, writer, context);
regulatingControlId = RegulatingControlEq.writeRegulatingControlEq(generator, exportedTerminalId(mapTerminal2Id, regulatingTerminal), regulatingControlsWritten, mode, cimNamespace, writer, context);
writeSynchronousMachine(generator, cimNamespace, writeInitialP,
generator.getMinP(), generator.getMaxP(), generator.getTargetP(), generator.getRatedS(), generator.getEnergySource(),
regulatingControlId, writer, context, generatingUnitsWritten);
Expand Down Expand Up @@ -574,13 +574,15 @@ private static void writeShuntCompensators(Network network, Map<Terminal, String
context.getNamingStrategy().getCgmesId(s.getTerminal().getVoltageLevel()),
cimNamespace, writer, context);
} else {
// Shunt can only regulate voltage
String mode = RegulatingControlEq.REGULATING_CONTROL_VOLTAGE;
double bPerSection = 0.0;
double gPerSection = Double.NaN;
if (s.getModelType().equals(ShuntCompensatorModelType.LINEAR)) {
bPerSection = ((ShuntCompensatorLinearModel) s.getModel()).getBPerSection();
gPerSection = ((ShuntCompensatorLinearModel) s.getModel()).getGPerSection();
}
String regulatingControlId = RegulatingControlEq.writeKindVoltage(s, exportedTerminalId(mapTerminal2Id, s.getRegulatingTerminal()), regulatingControlsWritten, cimNamespace, writer, context);
String regulatingControlId = RegulatingControlEq.writeRegulatingControlEq(s, exportedTerminalId(mapTerminal2Id, s.getRegulatingTerminal()), regulatingControlsWritten, mode, cimNamespace, writer, context);
ShuntCompensatorEq.write(context.getNamingStrategy().getCgmesId(s), s.getNameOrId(), s.getSectionCount(), s.getMaximumSectionCount(), s.getTerminal().getVoltageLevel().getNominalV(), s.getModelType(), bPerSection, gPerSection, regulatingControlId,
context.getNamingStrategy().getCgmesId(s.getTerminal().getVoltageLevel()), cimNamespace, writer, context);
if (s.getModelType().equals(ShuntCompensatorModelType.NON_LINEAR)) {
Expand All @@ -600,7 +602,8 @@ private static void writeShuntCompensators(Network network, Map<Terminal, String
private static void writeStaticVarCompensators(Network network, Map<Terminal, String> mapTerminal2Id, Set<String> regulatingControlsWritten, String cimNamespace,
XMLStreamWriter writer, CgmesExportContext context) throws XMLStreamException {
for (StaticVarCompensator svc : network.getStaticVarCompensators()) {
String regulatingControlId = RegulatingControlEq.writeKindVoltage(svc, exportedTerminalId(mapTerminal2Id, svc.getRegulatingTerminal()), regulatingControlsWritten, cimNamespace, writer, context);
String mode = CgmesExportUtil.getSvcMode(svc);
String regulatingControlId = RegulatingControlEq.writeRegulatingControlEq(svc, exportedTerminalId(mapTerminal2Id, svc.getRegulatingTerminal()), regulatingControlsWritten, mode, cimNamespace, writer, context);
double inductiveRating = svc.getBmin() != 0 ? 1 / svc.getBmin() : 0;
double capacitiveRating = svc.getBmax() != 0 ? 1 / svc.getBmax() : 0;
StaticVarCompensatorEq.write(context.getNamingStrategy().getCgmesId(svc), svc.getNameOrId(), context.getNamingStrategy().getCgmesId(svc.getTerminal().getVoltageLevel()), regulatingControlId, inductiveRating, capacitiveRating, svc.getExtension(VoltagePerReactivePowerControl.class), svc.getRegulationMode(), svc.getVoltageSetpoint(), cimNamespace, writer, context);
Expand Down Expand Up @@ -838,7 +841,7 @@ private static <C extends Connectable<C>> void writePhaseTapChanger(C eq, PhaseT
Optional<String> regulatingControlId = getTapChangerControlId(eq, tapChangerId);
String cgmesRegulatingControlId = null;
if (regulatingControlId.isPresent() && CgmesExportUtil.regulatingControlIsDefined(ptc)) {
String mode = getPhaseTapChangerRegulationMode(ptc);
String mode = CgmesExportUtil.getPhaseTapChangerRegulationMode(ptc);
String controlName = twtName + "_PTC_RC";
String terminalId = CgmesExportUtil.getTerminalId(ptc.getRegulationTerminal(), context);
cgmesRegulatingControlId = context.getNamingStrategy().getCgmesId(regulatingControlId.get());
Expand Down Expand Up @@ -879,14 +882,6 @@ private static <C extends Connectable<C>> Optional<String> getTapChangerControlI
return Optional.empty();
}

private static String getPhaseTapChangerRegulationMode(PhaseTapChanger ptc) {
return switch (ptc.getRegulationMode()) {
case CURRENT_LIMITER -> PHASE_TAP_CHANGER_REGULATION_MODE_CURRENT_FLOW;
case ACTIVE_POWER_CONTROL -> PHASE_TAP_CHANGER_REGULATION_MODE_ACTIVE_POWER;
default -> throw new PowsyblException("Unexpected regulation mode: " + ptc.getRegulationMode());
};
}

private static int getPhaseTapChangerNeutralStep(PhaseTapChanger ptc) {
int neutralStep = ptc.getLowTapPosition();
double minAlpha = Math.abs(ptc.getStep(neutralStep).getAlpha());
Expand Down Expand Up @@ -922,8 +917,11 @@ private static <C extends Connectable<C>> void writeRatioTapChanger(C eq, RatioT
String terminalId = CgmesExportUtil.getTerminalId(rtc.getRegulationTerminal(), context);
cgmesRegulatingControlId = context.getNamingStrategy().getCgmesId(regulatingControlId.get());
if (!regulatingControlsWritten.contains(cgmesRegulatingControlId)) {
// Regulating control mode is always "voltage"
TapChangerEq.writeControl(cgmesRegulatingControlId, controlName, RATIO_TAP_CHANGER_REGULATION_MODE_VOLTAGE, terminalId, cimNamespace, writer, context);
String tccMode = CgmesExportUtil.getTcMode(rtc);
if (tccMode.equals(RegulatingControlEq.REGULATING_CONTROL_REACTIVE_POWER)) {
controlMode = "reactive";
}
TapChangerEq.writeControl(cgmesRegulatingControlId, controlName, tccMode, terminalId, cimNamespace, writer, context);
regulatingControlsWritten.add(cgmesRegulatingControlId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use a method to obtain the tccMode and define it in the CgmesExportUtil class and use it during the SSH export to be always coherent with the EQ export.

}
}
Expand Down
Loading