Replace3TwoWindingsTransformersByThreeWindingsTransformers.java
/**
* Copyright (c) 2024, 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/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.iidm.modification;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.computation.ComputationManager;
import com.powsybl.iidm.modification.topology.NamingStrategy;
import com.powsybl.iidm.modification.util.RegulatedTerminalControllers;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
import static com.powsybl.iidm.modification.util.ModificationLogs.logOrThrow;
import static com.powsybl.iidm.modification.util.ModificationReports.*;
import static com.powsybl.iidm.modification.util.TransformerUtils.*;
/**
* <p>This network modification is used to replace 3 twoWindingsTransformers by threeWindingsTransformers.</p>
* <ul>
* <li>BusbarSections and the three TwoWindingsTransformers are the only connectable equipment allowed in the voltageLevel associated with the star bus.</li>
* <li>The three TwoWindingsTransformers must be connected to the star bus.</li>
* <li>The star terminals of the twoWindingsTransformers must not be regulated terminals for any controller.</li>
* <li>Each twoWindingsTransformer is well oriented if the star bus is located at the end 2.</li>
* <li>A new ThreeWindingsTransformer is created for replacing the three TwoWindingsTransformers.</li>
* <li>The following attributes are copied from each twoWindingsTransformer to the new associated leg:
* <ul>
* <li>Electrical characteristics, ratioTapChangers, and phaseTapChangers. Adjustments are required if the twoWindingsTransformer is not well oriented.</li>
* <li>Only the Operational Loading Limits defined at the non-star end are copied to the leg.</li>
* <li>Active and reactive power at the non-star terminal are copied to the leg terminal.</li>
* </ul>
* </li>
* <li>Aliases:
* <ul>
* <li>Aliases for known CGMES identifiers (terminal, transformer end, ratio, and phase tap changer) are copied to the threeWindingsTransformer after adjusting the aliasType.</li>
* <li>Aliases that are not mapped are recorded in the functional log.</li>
* </ul>
* </li>
* <li>Properties:
* <ul>
* <li>Voltage and angle of the star bus are added as properties of the threeWindingsTransformer.</li>
* <li>Only the names of the transferred operational limits are copied as properties of the threeWindingsTransformer.</li>
* <li>All the properties of the first twoWindingsTransformer are transferred to the threeWindingsTransformer,
* then those of the second that are not in the first, and finally, the properties of the third that are not in the first two.</li>
* <li>Properties that are not mapped are recorded in the functional log.</li>
* </ul>
* </li>
* <li>Extensions:
* <ul>
* <li>Only IIDM extensions are copied: TransformerFortescueData, PhaseAngleClock, and TransformerToBeEstimated.</li>
* <li>CGMES extensions can not be copied, as they cause circular dependencies.</li>
* <li>Extensions that are not copied are recorded in the functional log.</li>
* </ul>
* </li>
* <li>All the controllers using any of the twoWindingsTransformer terminals as regulated terminal are updated.</li>
* <li>New and removed equipment is also recorded in the functional log.</li>
* <li>Internal connections are created to manage the replacement.</li>
*</ul>
*
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
* @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
*/
public class Replace3TwoWindingsTransformersByThreeWindingsTransformers extends AbstractNetworkModification {
private static final Logger LOG = LoggerFactory.getLogger(Replace3TwoWindingsTransformersByThreeWindingsTransformers.class);
private static final String TWO_WINDINGS_TRANSFORMER = "TwoWindingsTransformer";
private static final String WITH_FICTITIOUS_TERMINAL_USED_AS_REGULATED_TERMINAL = "with star terminal used as regulated terminal";
private static final String CGMES_OPERATIONAL_LIMIT_SET = "CGMES.OperationalLimitSet_";
private final List<String> transformersToBeReplaced;
/**
* <p>Used to replace all 3 twoWindingsTransformers by threeWindingsTransformers.</p>
*/
public Replace3TwoWindingsTransformersByThreeWindingsTransformers() {
this.transformersToBeReplaced = null;
}
/**
* <p>Used to replace the 3 twoWindingsTransformers defined in the list by threeWindingsTransformers.
* To be selected, at least one of the three transformers must be included in the list.</p>
*/
public Replace3TwoWindingsTransformersByThreeWindingsTransformers(List<String> transformersToBeReplaced) {
this.transformersToBeReplaced = Objects.requireNonNull(transformersToBeReplaced);
}
@Override
public String getName() {
return "Replace3TwoWindingsTransformersByThreeWindingsTransformers";
}
@Override
public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) {
RegulatedTerminalControllers regulatedTerminalControllers = new RegulatedTerminalControllers(network);
List<TwoR> twoWindingsTransformers = find3TwoWindingsTransformers(network, transformersToBeReplaced);
twoWindingsTransformers.forEach(twoR -> replace3TwoWindingsTransformerByThreeWindingsTransformer(twoR, regulatedTerminalControllers, throwException, reportNode));
}
private static List<TwoR> find3TwoWindingsTransformers(Network network, List<String> transformersToBeReplaced) {
Map<Bus, List<TransformerAndStarBusSide>> twoWindingTransformersByBus = new HashMap<>();
network.getTwoWindingsTransformers().forEach(t2w -> {
Bus bus1 = t2w.getTerminal1().getBusView().getBus();
Bus bus2 = t2w.getTerminal2().getBusView().getBus();
if (bus1 != null) {
twoWindingTransformersByBus.computeIfAbsent(bus1, k -> new ArrayList<>()).add(new TransformerAndStarBusSide(t2w, TwoSides.ONE));
}
if (bus2 != null) {
twoWindingTransformersByBus.computeIfAbsent(bus2, k -> new ArrayList<>()).add(new TransformerAndStarBusSide(t2w, TwoSides.TWO));
}
});
return twoWindingTransformersByBus.keySet().stream()
.filter(bus -> isStarBus(bus, twoWindingTransformersByBus.get(bus)))
.sorted(Comparator.comparing(Identifiable::getId))
.map(bus -> buildTwoR(bus, twoWindingTransformersByBus.get(bus)))
.filter(twoR -> isGoingToBeReplaced(twoR, transformersToBeReplaced)).toList();
}
record TransformerAndStarBusSide(TwoWindingsTransformer transformer, TwoSides side) {
}
private static boolean isGoingToBeReplaced(TwoR twoR, List<String> transformersToBeReplaced) {
return transformersToBeReplaced == null
|| transformersToBeReplaced.contains(twoR.t2w1.getId())
|| transformersToBeReplaced.contains(twoR.t2w2.getId())
|| transformersToBeReplaced.contains(twoR.t2w3.getId());
}
private static boolean isStarBus(Bus bus, List<TransformerAndStarBusSide> t2ws) {
return t2ws.size() == 3 && bus.getConnectedTerminalStream().filter(connectedTerminal -> connectedTerminal.getConnectable().getType() != IdentifiableType.BUSBAR_SECTION).count() == 3;
}
private static TwoR buildTwoR(Bus starBus, List<TransformerAndStarBusSide> starBusT2ws) {
List<TransformerAndStarBusSide> sortedStarBusT2ws = starBusT2ws.stream()
.sorted(Comparator.comparingDouble((TransformerAndStarBusSide t2w) -> getNominalV(starBus, t2w.transformer()))
.reversed()
.thenComparing((TransformerAndStarBusSide t2w) -> t2w.transformer().getId()))
.toList();
return new TwoR(starBus, sortedStarBusT2ws.get(0), sortedStarBusT2ws.get(1), sortedStarBusT2ws.get(2));
}
private static double getNominalV(Bus bus, TwoWindingsTransformer t2w) {
Bus terminalBus = t2w.getTerminal1().getBusView().getBus();
return terminalBus != null && bus != null && terminalBus.getId().equals(bus.getId())
? t2w.getTerminal2().getVoltageLevel().getNominalV()
: t2w.getTerminal1().getVoltageLevel().getNominalV();
}
private record TwoR(TwoWindingsTransformer t2w1, boolean isWellOrientedT2w1,
TwoWindingsTransformer t2w2, boolean isWellOrientedT2w2,
TwoWindingsTransformer t2w3, boolean isWellOrientedT2w3,
String starBusId, VoltageLevel starBusVoltageLevel,
double starBusV, double starBusAngle, List<Connectable> starBusConnectables) {
TwoR(Bus starBus, TransformerAndStarBusSide t1, TransformerAndStarBusSide t2, TransformerAndStarBusSide t3) {
this(t1.transformer(), isWellOriented(t1),
t2.transformer(), isWellOriented(t2),
t3.transformer(), isWellOriented(t3),
starBus.getId(), starBus.getVoltageLevel(),
starBus.getV(), starBus.getAngle(),
starBus.getConnectedTerminalStream().map(Terminal::getConnectable).toList());
}
private static boolean isWellOriented(TransformerAndStarBusSide transfoAndStarBusSide) {
return transfoAndStarBusSide.side() == TwoSides.TWO;
}
public String starBusVoltageLevelId() {
return starBusVoltageLevel.getId();
}
public double starBusNominalV() {
return starBusVoltageLevel.getNominalV();
}
}
// if the twoWindingsTransformer is not well oriented, and it has non-zero shunt admittance (G != 0 or B != 0)
// the obtained model is not equivalent to the initial one as the shunt admittance must be moved to the other side
private void replace3TwoWindingsTransformerByThreeWindingsTransformer(TwoR twoR, RegulatedTerminalControllers regulatedTerminalControllers, boolean throwException, ReportNode reportNode) {
Substation substation = findSubstation(twoR, throwException);
if (substation == null) {
return;
}
if (anyTwoWindingsTransformerStarTerminalDefinedAsRegulatedTerminal(twoR, regulatedTerminalControllers, throwException)) {
return;
}
double ratedU0 = twoR.starBusNominalV();
ThreeWindingsTransformerAdder t3wAdder = substation.newThreeWindingsTransformer()
.setEnsureIdUnicity(true)
.setId(getId(twoR))
.setName(getName(twoR))
.setRatedU0(ratedU0);
addLeg(t3wAdder.newLeg1(), twoR.t2w1, twoR.isWellOrientedT2w1, ratedU0);
addLeg(t3wAdder.newLeg2(), twoR.t2w2, twoR.isWellOrientedT2w2, ratedU0);
addLeg(t3wAdder.newLeg3(), twoR.t2w3, twoR.isWellOrientedT2w3, ratedU0);
ThreeWindingsTransformer t3w = t3wAdder.add();
// t3w is not considered in regulatedTerminalControllers (created later in the model)
setLegData(t3w.getLeg1(), twoR.t2w1, twoR.isWellOrientedT2w1, regulatedTerminalControllers, twoR);
setLegData(t3w.getLeg2(), twoR.t2w2, twoR.isWellOrientedT2w2, regulatedTerminalControllers, twoR);
setLegData(t3w.getLeg3(), twoR.t2w3, twoR.isWellOrientedT2w3, regulatedTerminalControllers, twoR);
copyStarBusVoltageAndAngle(twoR.starBusV(), twoR.starBusAngle(), t3w);
List<PropertyR> lostProperties = new ArrayList<>();
lostProperties.addAll(copyProperties(twoR.t2w1, t3w));
lostProperties.addAll(copyProperties(twoR.t2w2, t3w));
lostProperties.addAll(copyProperties(twoR.t2w3, t3w));
List<ExtensionR> lostExtensions = copyExtensions(twoR, t3w);
// copy necessary data before removing
List<AliasR> t2wAliases = new ArrayList<>();
t2wAliases.addAll(getAliases(twoR.t2w1, "1", getEnd1(twoR.isWellOrientedT2w1)));
t2wAliases.addAll(getAliases(twoR.t2w2, "2", getEnd1(twoR.isWellOrientedT2w2)));
t2wAliases.addAll(getAliases(twoR.t2w3, "3", getEnd1(twoR.isWellOrientedT2w3)));
String t2w1Id = twoR.t2w1.getId();
String t2w2Id = twoR.t2w2.getId();
String t2w3Id = twoR.t2w3.getId();
String starVoltageId = twoR.starBusVoltageLevelId();
List<LimitsR> lostLimits = findLostLimits(twoR);
remove(twoR);
// after removing
List<AliasR> lostAliases = copyAliases(t2wAliases, t3w);
// warnings
if (!lostProperties.isEmpty()) {
lostProperties.forEach(propertyR -> LOG.warn("Property '{}' of twoWindingsTransformer '{}' was not transferred", propertyR.propertyName, propertyR.t2wId));
}
if (!lostExtensions.isEmpty()) {
lostExtensions.forEach(extensionR -> LOG.warn("Extension '{}' of twoWindingsTransformer '{}' was not transferred", extensionR.extensionName, extensionR.t2wId));
}
if (!lostAliases.isEmpty()) {
lostAliases.forEach(aliasR -> LOG.warn("Alias '{}' '{}' of twoWindingsTransformer '{}' was not transferred", aliasR.alias, aliasR.aliasType, aliasR.t2wId));
}
if (!lostLimits.isEmpty()) {
lostLimits.forEach(limitsR -> LOG.warn("OperationalLimitsGroup '{}' of twoWindingsTransformer '{}' is lost", limitsR.operationalLimitsGroupName, limitsR.t2wId));
}
// report
createReportNode(reportNode, t2w1Id, t2w2Id, t2w3Id, starVoltageId, lostProperties, lostExtensions, lostAliases, lostLimits, t3w.getId());
}
private static void addLeg(ThreeWindingsTransformerAdder.LegAdder legAdder, TwoWindingsTransformer t2w, boolean isWellOriented, double ratedU0) {
legAdder.setVoltageLevel(findVoltageLevel(t2w, isWellOriented).getId())
.setR(findImpedance(t2w.getR(), getStructuralRatio(t2w), isWellOriented))
.setX(findImpedance(t2w.getX(), getStructuralRatio(t2w), isWellOriented))
.setG(findAdmittance(t2w.getG(), getStructuralRatio(t2w), isWellOriented))
.setB(findAdmittance(t2w.getB(), getStructuralRatio(t2w), isWellOriented))
.setRatedU(getRatedU1(t2w, ratedU0, isWellOriented));
connectAfterCreatingInternalConnection(legAdder, t2w, isWellOriented);
legAdder.add();
}
private static void setLegData(ThreeWindingsTransformer.Leg leg, TwoWindingsTransformer t2w, boolean isWellOriented, RegulatedTerminalControllers regulatedTerminalControllers, TwoR twoR) {
t2w.getOptionalRatioTapChanger().ifPresent(rtc -> copyOrMoveRatioTapChanger(leg.newRatioTapChanger(), rtc, isWellOriented));
t2w.getOptionalPhaseTapChanger().ifPresent(ptc -> copyOrMovePhaseTapChanger(leg.newPhaseTapChanger(), ptc, isWellOriented));
getOperationalLimitsGroups1(t2w, isWellOriented)
.forEach(operationalLimitGroup -> copyOperationalLimitsGroup(leg.newOperationalLimitsGroup(operationalLimitGroup.getId()), operationalLimitGroup));
regulatedTerminalControllers.replaceRegulatedTerminal(getTerminal1(t2w, isWellOriented), leg.getTerminal());
replaceRegulatedTerminal(leg, twoR);
copyTerminalActiveAndReactivePower(leg.getTerminal(), getTerminal1(t2w, isWellOriented));
}
private Substation findSubstation(TwoR twoR, boolean throwException) {
Optional<Substation> substation = twoR.t2w1.getSubstation();
if (substation.isEmpty()) {
logOrThrow(throwException, TWO_WINDINGS_TRANSFORMER + "'" + twoR.t2w1.getId() + "' without substation");
return null;
} else {
return substation.get();
}
}
private boolean anyTwoWindingsTransformerStarTerminalDefinedAsRegulatedTerminal(TwoR twoR, RegulatedTerminalControllers regulatedTerminalControllers, boolean throwException) {
if (regulatedTerminalControllers.usedAsRegulatedTerminal(getTerminal2(twoR.t2w1, twoR.isWellOrientedT2w1))) {
logOrThrow(throwException, TWO_WINDINGS_TRANSFORMER + "'" + twoR.t2w1.getId() + "' " + WITH_FICTITIOUS_TERMINAL_USED_AS_REGULATED_TERMINAL);
return true;
}
if (regulatedTerminalControllers.usedAsRegulatedTerminal(getTerminal2(twoR.t2w2, twoR.isWellOrientedT2w2))) {
logOrThrow(throwException, TWO_WINDINGS_TRANSFORMER + "'" + twoR.t2w2.getId() + "' " + WITH_FICTITIOUS_TERMINAL_USED_AS_REGULATED_TERMINAL);
return true;
}
if (regulatedTerminalControllers.usedAsRegulatedTerminal(getTerminal2(twoR.t2w3, twoR.isWellOrientedT2w3))) {
logOrThrow(throwException, TWO_WINDINGS_TRANSFORMER + "'" + twoR.t2w3.getId() + "' " + WITH_FICTITIOUS_TERMINAL_USED_AS_REGULATED_TERMINAL);
return true;
}
return false;
}
private static String getId(TwoR twoR) {
return twoR.t2w1.getId() + "-" + twoR.t2w2.getId() + "-" + twoR.t2w3.getId();
}
private static String getName(TwoR twoR) {
return twoR.t2w1.getNameOrId() + "-" + twoR.t2w2.getNameOrId() + "-" + twoR.t2w3.getNameOrId();
}
private static VoltageLevel findVoltageLevel(TwoWindingsTransformer t2w, boolean isWellOriented) {
return isWellOriented ? t2w.getTerminal1().getVoltageLevel() : t2w.getTerminal2().getVoltageLevel();
}
private static double getStructuralRatio(TwoWindingsTransformer twt) {
return twt.getRatedU1() / twt.getRatedU2();
}
private static double findImpedance(double impedance, double a, boolean isWellOriented) {
return isWellOriented ? impedance : impedanceConversion(impedance, a);
}
private static double findAdmittance(double admittance, double a, boolean isWellOriented) {
return isWellOriented ? admittance : admittanceConversion(admittance, a);
}
private static double getRatedU1(TwoWindingsTransformer t2w, double ratedU0, boolean isWellOriented) {
return isWellOriented ? getStructuralRatio(t2w) * ratedU0 : ratedU0 / getStructuralRatio(t2w);
}
private static void connectAfterCreatingInternalConnection(ThreeWindingsTransformerAdder.LegAdder legAdder, TwoWindingsTransformer t2w, boolean isWellOriented) {
Terminal terminal = getTerminal1(t2w, isWellOriented);
if (terminal.getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) {
int newNode = terminal.getVoltageLevel().getNodeBreakerView().getMaximumNodeIndex() + 1;
terminal.getVoltageLevel().getNodeBreakerView()
.newInternalConnection()
.setNode1(terminal.getNodeBreakerView().getNode())
.setNode2(newNode).add();
legAdder.setNode(newNode);
} else {
legAdder.setConnectableBus(terminal.getBusBreakerView().getConnectableBus().getId());
Bus bus = terminal.getBusBreakerView().getBus();
if (bus != null) {
legAdder.setBus(bus.getId());
}
}
}
private static void copyOrMoveRatioTapChanger(RatioTapChangerAdder rtcAdder, RatioTapChanger rtc, boolean isWellOriented) {
if (isWellOriented) {
copyAndAddRatioTapChanger(rtcAdder, rtc);
} else {
copyAndMoveAndAddRatioTapChanger(rtcAdder, rtc);
}
}
private static void copyOrMovePhaseTapChanger(PhaseTapChangerAdder ptcAdder, PhaseTapChanger ptc, boolean isWellOriented) {
if (isWellOriented) {
copyAndAddPhaseTapChanger(ptcAdder, ptc);
} else {
copyAndMoveAndAddPhaseTapChanger(ptcAdder, ptc);
}
}
private static Collection<OperationalLimitsGroup> getOperationalLimitsGroups1(TwoWindingsTransformer t2w, boolean isWellOriented) {
return isWellOriented ? t2w.getOperationalLimitsGroups1() : t2w.getOperationalLimitsGroups2();
}
private static Terminal getTerminal1(TwoWindingsTransformer t2w, boolean isWellOriented) {
return isWellOriented ? t2w.getTerminal1() : t2w.getTerminal2();
}
private static Terminal getTerminal2(TwoWindingsTransformer t2w, boolean isWellOriented) {
return isWellOriented ? t2w.getTerminal2() : t2w.getTerminal1();
}
private static String getEnd1(boolean isWellOriented) {
return isWellOriented ? "1" : "2";
}
private static void replaceRegulatedTerminal(ThreeWindingsTransformer.Leg t3wLeg, TwoR twoR) {
t3wLeg.getOptionalRatioTapChanger().ifPresent(rtc -> findNewRegulatedTerminal(rtc.getRegulationTerminal(), t3wLeg.getTransformer(), twoR).ifPresent(rtc::setRegulationTerminal));
t3wLeg.getOptionalPhaseTapChanger().ifPresent(ptc -> findNewRegulatedTerminal(ptc.getRegulationTerminal(), t3wLeg.getTransformer(), twoR).ifPresent(ptc::setRegulationTerminal));
}
private static Optional<Terminal> findNewRegulatedTerminal(Terminal regulatedTerminal, ThreeWindingsTransformer t3w, TwoR twoR) {
if (isRegulatedTerminalInTwoWindingsTransformer(regulatedTerminal, twoR.t2w1)) {
return Optional.of(t3w.getTerminal(ThreeSides.ONE));
} else if (isRegulatedTerminalInTwoWindingsTransformer(regulatedTerminal, twoR.t2w2)) {
return Optional.of(t3w.getTerminal(ThreeSides.TWO));
} else if (isRegulatedTerminalInTwoWindingsTransformer(regulatedTerminal, twoR.t2w3)) {
return Optional.of(t3w.getTerminal(ThreeSides.THREE));
} else {
return Optional.empty();
}
}
// we do not check the side, threeWindingsTransformers can only be controlled on the non-star side
private static boolean isRegulatedTerminalInTwoWindingsTransformer(Terminal regulatedTerminal, TwoWindingsTransformer t2w) {
return regulatedTerminal != null && regulatedTerminal.getConnectable().getId().equals(t2w.getId());
}
private static void copyStarBusVoltageAndAngle(double starBusV, double starBusAngle, ThreeWindingsTransformer t3w) {
if (Double.isFinite(starBusV) && starBusV > 0.0 && Double.isFinite(starBusAngle)) {
t3w.setProperty("v", String.valueOf(starBusV));
t3w.setProperty("angle", String.valueOf(starBusAngle));
}
}
private static List<PropertyR> copyProperties(TwoWindingsTransformer t2w, ThreeWindingsTransformer t3w) {
List<PropertyR> lostProperties = new ArrayList<>();
t2w.getPropertyNames().forEach(propertyName -> {
boolean copied = copyProperty(propertyName, t2w.getProperty(propertyName), t3w);
if (!copied) {
lostProperties.add(new PropertyR(t2w.getId(), propertyName));
}
});
return lostProperties;
}
private static boolean copyProperty(String propertyName, String property, ThreeWindingsTransformer t3w) {
boolean copied = true;
if (propertyName.startsWith(CGMES_OPERATIONAL_LIMIT_SET)) {
if (t3w.getLeg1().getOperationalLimitsGroups().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) {
t3w.setProperty(propertyName, property);
} else if (t3w.getLeg2().getOperationalLimitsGroups().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) {
t3w.setProperty(propertyName, property);
} else if (t3w.getLeg3().getOperationalLimitsGroups().stream().anyMatch(operationalLimitsGroup -> propertyName.equals(CGMES_OPERATIONAL_LIMIT_SET + operationalLimitsGroup.getId()))) {
t3w.setProperty(propertyName, property);
} else {
copied = false;
}
} else {
if (t3w.getPropertyNames().contains(propertyName)) {
copied = false;
} else {
t3w.setProperty(propertyName, property);
}
}
return copied;
}
private record PropertyR(String t2wId, String propertyName) {
}
// TODO For now, only a few extensions are supported. But a wider mechanism should be developed to support custom extensions.
private static List<ExtensionR> copyExtensions(TwoR twoR, ThreeWindingsTransformer t3w) {
List<ExtensionR> extensions = new ArrayList<>();
extensions.addAll(twoR.t2w1.getExtensions().stream().map(extension -> new ExtensionR(twoR.t2w1.getId(), extension.getName())).toList());
extensions.addAll(twoR.t2w2.getExtensions().stream().map(extension -> new ExtensionR(twoR.t2w2.getId(), extension.getName())).toList());
extensions.addAll(twoR.t2w3.getExtensions().stream().map(extension -> new ExtensionR(twoR.t2w3.getId(), extension.getName())).toList());
List<ExtensionR> lostExtensions = new ArrayList<>();
extensions.stream().map(extensionR -> extensionR.extensionName).collect(Collectors.toSet()).forEach(extensionName -> {
boolean copied = copyExtension(extensionName, twoR, t3w);
if (!copied) {
lostExtensions.addAll(extensions.stream().filter(extensionR -> extensionR.extensionName.equals(extensionName)).toList());
}
});
return lostExtensions;
}
private record ExtensionR(String t2wId, String extensionName) {
}
private static boolean copyExtension(String extensionName, TwoR twoR, ThreeWindingsTransformer t3w) {
boolean copied = true;
switch (extensionName) {
case "twoWindingsTransformerFortescue" ->
copyAndAddFortescue(t3w.newExtension(ThreeWindingsTransformerFortescueAdder.class),
twoR.t2w1.getExtension(TwoWindingsTransformerFortescue.class), twoR.isWellOrientedT2w1,
twoR.t2w2.getExtension(TwoWindingsTransformerFortescue.class), twoR.isWellOrientedT2w2,
twoR.t2w3.getExtension(TwoWindingsTransformerFortescue.class), twoR.isWellOrientedT2w3);
case "twoWindingsTransformerPhaseAngleClock" ->
copyAndAddPhaseAngleClock(t3w.newExtension(ThreeWindingsTransformerPhaseAngleClockAdder.class),
twoR.t2w2.getExtension(TwoWindingsTransformerPhaseAngleClock.class),
twoR.t2w3.getExtension(TwoWindingsTransformerPhaseAngleClock.class));
case "twoWindingsTransformerToBeEstimated" ->
copyAndAddToBeEstimated(t3w.newExtension(ThreeWindingsTransformerToBeEstimatedAdder.class),
twoR.t2w1.getExtension(TwoWindingsTransformerToBeEstimated.class),
twoR.t2w2.getExtension(TwoWindingsTransformerToBeEstimated.class),
twoR.t2w3.getExtension(TwoWindingsTransformerToBeEstimated.class));
default -> copied = false;
}
return copied;
}
private static List<AliasR> getAliases(TwoWindingsTransformer t2w, String leg, String end) {
return t2w.getAliases().stream().map(alias -> new AliasR(t2w.getId(), alias, t2w.getAliasType(alias).orElse(""), leg, end)).toList();
}
private static List<AliasR> copyAliases(List<AliasR> t2wAliases, ThreeWindingsTransformer t3w) {
List<AliasR> lostAliases = new ArrayList<>();
t2wAliases.forEach(aliasR -> {
boolean copied = copyAlias(aliasR.alias, aliasR.aliasType, aliasR.leg, aliasR.end, t3w);
if (!copied) {
lostAliases.add(aliasR);
}
});
return lostAliases;
}
private static boolean copyAlias(String alias, String aliasType, String leg, String end, ThreeWindingsTransformer t3w) {
boolean copied = true;
if (aliasType.equals("CGMES.TransformerEnd" + end)) {
t3w.addAlias(alias, "CGMES.TransformerEnd" + leg, true);
} else if (aliasType.equals("CGMES.Terminal" + end)) {
t3w.addAlias(alias, "CGMES.Terminal" + leg, true);
} else if (aliasType.equals("CGMES.RatioTapChanger1")) {
t3w.addAlias(alias, "CGMES.RatioTapChanger" + leg, true);
} else if (aliasType.equals("CGMES.PhaseTapChanger1")) {
t3w.addAlias(alias, "CGMES.PhaseTapChanger" + leg, true);
} else {
copied = false;
}
return copied;
}
private record AliasR(String t2wId, String alias, String aliasType, String leg, String end) {
}
private static void remove(TwoR twoR) {
VoltageLevel voltageLevel = twoR.starBusVoltageLevel();
twoR.starBusConnectables().forEach(Connectable::remove);
voltageLevel.remove();
}
private static List<LimitsR> findLostLimits(TwoR twoR) {
List<LimitsR> lostLimits = new ArrayList<>();
getOperationalLimitsGroups2(twoR.t2w1, twoR.isWellOrientedT2w1).forEach(operationalLimitsGroup -> lostLimits.add(new LimitsR(twoR.t2w1.getId(), operationalLimitsGroup.getId())));
getOperationalLimitsGroups2(twoR.t2w2, twoR.isWellOrientedT2w2).forEach(operationalLimitsGroup -> lostLimits.add(new LimitsR(twoR.t2w2.getId(), operationalLimitsGroup.getId())));
getOperationalLimitsGroups2(twoR.t2w3, twoR.isWellOrientedT2w3).forEach(operationalLimitsGroup -> lostLimits.add(new LimitsR(twoR.t2w3.getId(), operationalLimitsGroup.getId())));
return lostLimits;
}
private static Collection<OperationalLimitsGroup> getOperationalLimitsGroups2(TwoWindingsTransformer t2w, boolean isWellOriented) {
return isWellOriented ? t2w.getOperationalLimitsGroups2() : t2w.getOperationalLimitsGroups1();
}
private record LimitsR(String t2wId, String operationalLimitsGroupName) {
}
private static void createReportNode(ReportNode reportNode, String t2w1Id, String t2w2Id, String t2w3Id, String starVoltageId,
List<PropertyR> lostProperties, List<ExtensionR> lostExtensions, List<AliasR> lostAliases,
List<LimitsR> lostLimits, String t3wId) {
ReportNode reportNodeReplacement = replace3TwoWindingsTransformersByThreeWindingsTransformersReport(reportNode);
removedTwoWindingsTransformerReport(reportNodeReplacement, t2w1Id);
removedTwoWindingsTransformerReport(reportNodeReplacement, t2w2Id);
removedTwoWindingsTransformerReport(reportNodeReplacement, t2w3Id);
removedVoltageLevelReport(reportNodeReplacement, starVoltageId);
if (!lostProperties.isEmpty()) {
Set<String> t2wIds = lostProperties.stream().map(propertyR -> propertyR.t2wId).collect(Collectors.toSet());
t2wIds.stream().sorted().forEach(t2wId -> {
String properties = String.join(",", lostProperties.stream().filter(propertyR -> propertyR.t2wId.equals(t2wId)).map(propertyR -> propertyR.propertyName).toList());
lostTwoWindingsTransformerProperties(reportNodeReplacement, properties, t2wId);
});
}
if (!lostExtensions.isEmpty()) {
Set<String> t2wIds = lostExtensions.stream().map(extensionR -> extensionR.t2wId).collect(Collectors.toSet());
t2wIds.stream().sorted().forEach(t2wId -> {
String extensions = String.join(",", lostExtensions.stream().filter(extensionR -> extensionR.t2wId.equals(t2wId)).map(extensionR -> extensionR.extensionName).toList());
lostTwoWindingsTransformerExtensions(reportNodeReplacement, extensions, t2wId);
});
}
if (!lostAliases.isEmpty()) {
Set<String> t2wIds = lostAliases.stream().map(aliasR -> aliasR.t2wId).collect(Collectors.toSet());
t2wIds.stream().sorted().forEach(t2wId -> {
String aliases = lostAliases.stream().filter(aliasR -> aliasR.t2wId.equals(t2wId)).map(AliasR::alias).collect(Collectors.joining(","));
lostTwoWindingsTransformerAliases(reportNodeReplacement, aliases, t2wId);
});
}
if (!lostLimits.isEmpty()) {
Set<String> t2wIds = lostLimits.stream().map(limitsR -> limitsR.t2wId).collect(Collectors.toSet());
t2wIds.stream().sorted().forEach(t2wId -> {
String limits = lostLimits.stream().filter(limitsR -> limitsR.t2wId.equals(t2wId)).map(LimitsR::operationalLimitsGroupName).collect(Collectors.joining(","));
lostTwoWindingsTransformerOperationalLimitsGroups(reportNodeReplacement, limits, t2wId);
});
}
createdThreeWindingsTransformerReport(reportNodeReplacement, t3wId);
}
}