TwoWindingsTransformerConversion.java
/**
* Copyright (c) 2019, 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.cgmes.conversion.elements.transformers;
import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.*;
import com.powsybl.cgmes.conversion.Context;
import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.cgmes.conversion.ConversionException;
import com.powsybl.cgmes.conversion.RegulatingControlMappingForTransformers.CgmesRegulatingControlPhase;
import com.powsybl.cgmes.conversion.RegulatingControlMappingForTransformers.CgmesRegulatingControlRatio;
import com.powsybl.cgmes.conversion.elements.EquipmentAtBoundaryConversion;
import com.powsybl.cgmes.model.CgmesNames;
import com.powsybl.triplestore.api.PropertyBags;
import java.util.Optional;
/**
* TwoWindingsTransformer Cgmes Conversion
* <p>
* Cgmes conversion for transformers (two and three windings) is divided into four stages: load, interpret, convert and set.
* <p>
* Load <br>
* Native CGMES data is loaded from the triple store query and is put in the CGMES model object (CgmesT2xModel).
* <p>
* Interpret <br>
* CgmesT2xModel data is mapped to a more general two windings transformer model (InterpretedT2xModel)
* according to a predefined configured alternative. It is an elemental process as the only objective is to put
* Cgmes data in the fields of the general two windings transformer model.
* All possible alternatives and the default one are defined in conversion class. See {@link Conversion} <br>
* InterpretedT2xModel supports ratioTapChanger and phaseTapChanger at each end. Shunt admittances can be defined at both ends and
* allows to specify the end of the structural ratio.
* <p>
* Convert <br>
* Converts the interpreted model (InterpretedT2xModel) to the converted model object (ConvertedT2xModel). <br>
* The ConvertedT2xModel only allows to define ratioTapChanger and phaseTapChanger at end1.
* Shunt admittances and structural ratio must be also at end1. <br>
* To do this process the following methods are used: <br>
* moveTapChangerFrom2To1: To move a tapChanger from end2 to end1 <br>
* combineTapChanger: To reduce two tapChangers to one <br>
* moveRatioFrom2To1: To move structural ratio from end2 to end1 <br>
* Finally shunt admittance of both ends are added to end1. This step is an approximation and only
* will be possible to reproduce the exact case result if Cgmes shunts are defined at end1 or
* are split and the LoadflowParameter splitShuntAdmittance option is selected. <br>
* See {@link TapChangerConversion}
* <p>
* Set <br>
* A direct map from ConvertedT2xModel to IIDM model
* <p>
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
* @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
*/
public class TwoWindingsTransformerConversion extends AbstractTransformerConversion implements EquipmentAtBoundaryConversion {
private DanglingLine danglingLine;
public TwoWindingsTransformerConversion(PropertyBags ends, Context context) {
super(CgmesNames.POWER_TRANSFORMER, ends, context);
}
@Override
public boolean valid() {
// An transformer end voltage level may be null
// (when it is in the boundary and the boundary nodes are not converted)
// So we do not use the generic validity check for conducting equipment
// or branch. We only ensure we have nodes at both ends
for (int k = 1; k <= 2; k++) {
if (nodeId(k) == null) {
missing(nodeIdPropertyName() + k);
return false;
}
}
return true;
}
@Override
public void convert() {
CgmesT2xModel cgmesT2xModel = new CgmesT2xModel(ps, context);
InterpretedT2xModel interpretedT2xModel = new InterpretedT2xModel(cgmesT2xModel, context.config(), context);
ConvertedT2xModel convertedT2xModel = new ConvertedT2xModel(interpretedT2xModel, context);
setToIidm(convertedT2xModel);
}
@Override
public void convertAtBoundary() {
// If we have created buses and substations for boundary nodes,
// convert as a regular line
if (context.config().convertBoundary()) {
convert();
return;
}
String eqInstance = ps.get(0).get("graph");
if (isBoundary(1)) {
convertTwoWindingsTransformerAtBoundary(eqInstance, 1);
} else if (isBoundary(2)) {
convertTwoWindingsTransformerAtBoundary(eqInstance, 2);
} else {
throw new ConversionException("Boundary must be at one end of the twoWindingsTransformer");
}
}
@Override
public Optional <DanglingLine> getDanglingLine() {
return Optional.ofNullable(danglingLine);
}
private void convertTwoWindingsTransformerAtBoundary(String eqInstance, int boundarySide) {
CgmesT2xModel cgmesT2xModel = new CgmesT2xModel(ps, context);
InterpretedT2xModel interpretedT2xModel = new InterpretedT2xModel(cgmesT2xModel, context.config(), context);
ConvertedT2xModel convertedT2xModel = new ConvertedT2xModel(interpretedT2xModel, context);
// The twoWindingsTransformer is converted to a danglingLine with different VoltageLevels at its ends.
// As the current danglingLine only supports shunt admittance at the end1 we can only map twoWindingsTransformers with
// ratio 1.0 and angle 0.0
// Since the ratio has been fixed to 1.0, if the current (ratio, angle) of the transformer
// (getRatio(convertedT2xModel), getAngle(convertedT2xModel)) is not (1.0, 0.0)
// we will have differences in the LF computation.
// TODO support in the danglingLine the complete twoWindingsTransformer model (transformer + tapChangers)
danglingLine = convertToDanglingLine(eqInstance, boundarySide, getR(convertedT2xModel), getX(convertedT2xModel), getG(convertedT2xModel), getB(convertedT2xModel));
}
private void setToIidm(ConvertedT2xModel convertedT2xModel) {
TwoWindingsTransformerAdder adder = substation()
.map(Substation::newTwoWindingsTransformer)
.orElseThrow(() -> new PowsyblException("Substation null! Transformer must be within a substation"))
.setR(convertedT2xModel.r)
.setX(convertedT2xModel.x)
.setG(Double.isNaN(convertedT2xModel.end1.g) ? 0.0 : convertedT2xModel.end1.g)
.setB(Double.isNaN(convertedT2xModel.end1.b) ? 0.0 : convertedT2xModel.end1.b)
.setRatedU1(convertedT2xModel.end1.ratedU)
.setRatedU2(convertedT2xModel.end2.ratedU);
if (convertedT2xModel.ratedS != null) {
adder.setRatedS(convertedT2xModel.ratedS);
}
identify(adder);
connect(adder);
TwoWindingsTransformer tx = adder.add();
addAliasesAndProperties(tx);
convertedTerminals(tx.getTerminal1(), tx.getTerminal2());
setToIidmRatioTapChanger(convertedT2xModel, tx);
setToIidmPhaseTapChanger(convertedT2xModel, tx, context);
setRegulatingControlContext(convertedT2xModel, tx);
addCgmesReferences(tx, convertedT2xModel.end1.ratioTapChanger);
addCgmesReferences(tx, convertedT2xModel.end1.phaseTapChanger);
}
private static void setToIidmRatioTapChanger(ConvertedT2xModel convertedT2xModel, TwoWindingsTransformer tx) {
TapChanger rtc = convertedT2xModel.end1.ratioTapChanger;
if (rtc == null) {
return;
}
RatioTapChangerAdder rtca = newRatioTapChanger(tx);
setToIidmRatioTapChanger(rtc, rtca);
}
private static void setToIidmPhaseTapChanger(ConvertedT2xModel convertedT2xModel, TwoWindingsTransformer tx, Context context) {
TapChanger ptc = convertedT2xModel.end1.phaseTapChanger;
if (ptc == null) {
return;
}
PhaseTapChangerAdder ptca = newPhaseTapChanger(tx);
setToIidmPhaseTapChanger(ptc, ptca, context);
}
private static RatioTapChangerAdder newRatioTapChanger(TwoWindingsTransformer tx) {
return tx.newRatioTapChanger();
}
private static PhaseTapChangerAdder newPhaseTapChanger(TwoWindingsTransformer tx) {
return tx.newPhaseTapChanger();
}
private void setRegulatingControlContext(ConvertedT2xModel convertedT2xModel, TwoWindingsTransformer tx) {
CgmesRegulatingControlRatio rcRtc = setContextRegulatingDataRatio(convertedT2xModel.end1.ratioTapChanger);
CgmesRegulatingControlPhase rcPtc = setContextRegulatingDataPhase(convertedT2xModel.end1.phaseTapChanger);
context.regulatingControlMapping().forTransformers().add(tx.getId(), rcRtc, rcPtc);
}
private static int getStepIndex(TapChanger tapChanger) {
return tapChanger.getTapPosition();
}
private static double getStepR(TapChanger tapChanger) {
if (tapChanger != null) {
return tapChanger.getSteps().get(getStepIndex(tapChanger)).getR();
}
return 0.0;
}
private static double getStepX(TapChanger tapChanger) {
if (tapChanger != null) {
return tapChanger.getSteps().get(getStepIndex(tapChanger)).getX();
}
return 0.0;
}
private static double getStepG1(TapChanger tapChanger) {
if (tapChanger != null) {
return tapChanger.getSteps().get(getStepIndex(tapChanger)).getG1();
}
return 0.0;
}
private static double getStepB1(TapChanger tapChanger) {
if (tapChanger != null) {
return tapChanger.getSteps().get(getStepIndex(tapChanger)).getB1();
}
return 0.0;
}
private static double getValue(double initialValue, double rtcStepValue, double ptcStepValue) {
return initialValue * (1 + rtcStepValue / 100) * (1 + ptcStepValue / 100);
}
private static double getR(ConvertedT2xModel convertedT2xModel) {
return getValue(convertedT2xModel.r, getStepR(convertedT2xModel.end1.ratioTapChanger), getStepR(convertedT2xModel.end1.phaseTapChanger));
}
private static double getX(ConvertedT2xModel convertedT2xModel) {
return getValue(convertedT2xModel.x, getStepX(convertedT2xModel.end1.ratioTapChanger), getStepX(convertedT2xModel.end1.phaseTapChanger));
}
private static double getG(ConvertedT2xModel convertedT2xModel) {
return getValue(convertedT2xModel.end1.g, getStepG1(convertedT2xModel.end1.ratioTapChanger), getStepG1(convertedT2xModel.end1.phaseTapChanger));
}
private static double getB(ConvertedT2xModel convertedT2xModel) {
return getValue(convertedT2xModel.end1.b, getStepB1(convertedT2xModel.end1.ratioTapChanger), getStepB1(convertedT2xModel.end1.phaseTapChanger));
}
}