TransformerConverter.java
/**
* Copyright (c) 2022, 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.powerfactory.converter;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.ThreeWindingsTransformer.Leg;
import com.powsybl.iidm.network.extensions.ThreeWindingsTransformerPhaseAngleClockAdder;
import com.powsybl.iidm.network.extensions.TwoWindingsTransformerPhaseAngleClockAdder;
import com.powsybl.powerfactory.converter.PowerFactoryImporter.ImportContext;
import com.powsybl.powerfactory.model.DataObject;
import com.powsybl.powerfactory.model.PowerFactoryException;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.linear.RealMatrix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
* @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
*/
class TransformerConverter extends AbstractConverter {
private enum WindingType {
HIGH, MEDIUM, LOW
}
TransformerConverter(ImportContext importContext, Network network) {
super(importContext, network);
}
void createTwoWindings(DataObject elmTr2) {
DataObject typTr2 = elmTr2.getObjectAttributeValue(DataAttributeNames.TYP_ID).resolve().orElseThrow();
List<NodeRef> nodeRefs = checkNodes(elmTr2, 2);
NodeRef nodeRef1 = nodeRefs.get(0);
NodeRef nodeRef2 = nodeRefs.get(1);
VoltageLevel vl1 = getNetwork().getVoltageLevel(nodeRef1.voltageLevelId);
VoltageLevel vl2 = getNetwork().getVoltageLevel(nodeRef2.voltageLevelId);
boolean highAtEnd1 = highVoltageAtEnd1(vl1, vl2);
boolean tapChangerAtEnd1 = tapChangerAtEnd1(typTr2, highAtEnd1);
RatedModel ratedModel = RatedModel.create(typTr2, highAtEnd1);
double nominalVoltageEnd2 = vl2.getNominalV();
TransformerModel transformerModel = TransformerModel.create(typTr2, ratedModel.ratedS, nominalVoltageEnd2);
if (!tapChangerAtEnd1) {
// Structural ratio at end2 = ratedU2 / vn2
transformerModel.moveStructuralRatioFromEnd2ToEnd1(ratedModel.ratedU2 / vl2.getNominalV());
}
PowerFactoryTapChanger powerFactoryTapChanger = PowerFactoryTapChanger.create(elmTr2, typTr2);
Optional<TapChangerModel> tc = TapChangerModel.create(powerFactoryTapChanger, tapChangerAtEnd1);
Substation substation = vl1.getSubstation().orElseThrow();
TwoWindingsTransformer t2wt = substation.newTwoWindingsTransformer()
.setId(elmTr2.getLocName())
.setEnsureIdUnicity(true)
.setVoltageLevel1(nodeRef1.voltageLevelId)
.setVoltageLevel2(nodeRef2.voltageLevelId)
.setNode1(nodeRef1.node)
.setNode2(nodeRef2.node)
.setRatedU1(ratedModel.ratedU1)
.setRatedU2(ratedModel.ratedU2)
.setRatedS(ratedModel.ratedS)
.setR(transformerModel.r)
.setX(transformerModel.x)
.setG(transformerModel.g)
.setB(transformerModel.b)
.add();
tc.ifPresent(t -> tapChangerToIidm(t, t2wt));
Optional<PhaseAngleClockModel> pacModel = PhaseAngleClockModel.create(typTr2);
pacModel.ifPresent(phaseAngleClockModel -> t2wt.newExtension(TwoWindingsTransformerPhaseAngleClockAdder.class)
.withPhaseAngleClock(phaseAngleClockModel.pac).add());
}
void createThreeWindings(DataObject elmTr3) {
DataObject typTr3 = elmTr3.getObjectAttributeValue(DataAttributeNames.TYP_ID).resolve().orElseThrow();
List<NodeRef> nodeRefs = checkNodes(elmTr3, 3);
NodeRef nodeRef1 = nodeRefs.get(0);
NodeRef nodeRef2 = nodeRefs.get(1);
NodeRef nodeRef3 = nodeRefs.get(2);
// The three connection buses of the transformer are defined in power factory
// using the attribute busIndexIn: 0, 1, 2
// But all the characteristics are given by winding type: high, medium or low
// The order of busIndexIn may not always follow high, medium, low
// So we need to map the busIndexIn to its winding type
// Additionally, IIDM model will respect the order defined by busIndexIn
// So IIDM Leg 1, 2, 3 will correspond to busIndexIn 0, 1, 2
VoltageLevel vl1 = getNetwork().getVoltageLevel(nodeRef1.voltageLevelId);
VoltageLevel vl2 = getNetwork().getVoltageLevel(nodeRef2.voltageLevelId);
VoltageLevel vl3 = getNetwork().getVoltageLevel(nodeRef3.voltageLevelId);
List<WindingType> windingTypeEnds = createWindingTypeEnds(vl1, vl2, vl3);
double vn0 = 1.0;
Rated3WModel rated3WModel = Rated3WModel.create(typTr3, vn0);
RatedModel ratedModel1 = rated3WModel.getEnd(windingTypeEnds.get(0));
RatedModel ratedModel2 = rated3WModel.getEnd(windingTypeEnds.get(1));
RatedModel ratedModel3 = rated3WModel.getEnd(windingTypeEnds.get(2));
double ratedU1 = ratedModel1.ratedU1;
double ratedU2 = ratedModel2.ratedU1;
double ratedU3 = ratedModel3.ratedU1;
Transformer3WModel transformer3WModel = Transformer3WModel.create(typTr3, rated3WModel, vn0);
TransformerModel transformerModel1 = transformer3WModel.getEnd(windingTypeEnds.get(0));
TransformerModel transformerModel2 = transformer3WModel.getEnd(windingTypeEnds.get(1));
TransformerModel transformerModel3 = transformer3WModel.getEnd(windingTypeEnds.get(2));
Substation substation = vl1.getSubstation().orElseThrow();
ThreeWindingsTransformerAdder adder = substation.newThreeWindingsTransformer()
.setRatedU0(vn0)
.setEnsureIdUnicity(true)
.setId(elmTr3.getLocName())
.newLeg1()
.setR(transformerModel1.r)
.setX(transformerModel1.x)
.setG(transformerModel1.g)
.setB(transformerModel1.b)
.setRatedU(ratedU1)
.setRatedS(ratedModel1.ratedS)
.setVoltageLevel(nodeRef1.voltageLevelId)
.setNode(nodeRef1.node)
.add()
.newLeg2()
.setR(transformerModel2.r)
.setX(transformerModel2.x)
.setG(transformerModel2.g)
.setB(transformerModel2.b)
.setRatedU(ratedU2)
.setRatedS(ratedModel2.ratedS)
.setVoltageLevel(nodeRef2.voltageLevelId)
.setNode(nodeRef2.node)
.add()
.newLeg3()
.setR(transformerModel3.r)
.setX(transformerModel3.x)
.setG(transformerModel3.g)
.setB(transformerModel3.b)
.setRatedU(ratedU3)
.setRatedS(ratedModel3.ratedS)
.setVoltageLevel(nodeRef3.voltageLevelId)
.setNode(nodeRef3.node)
.add();
ThreeWindingsTransformer t3wt = adder.add();
PowerFactoryTapChangers3W powerFactoryTapChangers3W = PowerFactoryTapChangers3W.create(elmTr3, typTr3);
TapChanger3W tapChanger3w = TapChanger3W.create(powerFactoryTapChangers3W);
Optional<TapChangerModel> tc1 = tapChanger3w.getEnd(windingTypeEnds.get(0));
Optional<TapChangerModel> tc2 = tapChanger3w.getEnd(windingTypeEnds.get(1));
Optional<TapChangerModel> tc3 = tapChanger3w.getEnd(windingTypeEnds.get(2));
tc1.ifPresent(tc -> tapChangerToIidm(tc, t3wt.getLeg1()));
tc2.ifPresent(tc -> tapChangerToIidm(tc, t3wt.getLeg2()));
tc3.ifPresent(tc -> tapChangerToIidm(tc, t3wt.getLeg3()));
PhaseAngleClock3WModel pac3WModel = PhaseAngleClock3WModel.create(typTr3);
Optional<PhaseAngleClockModel> pac2 = pac3WModel.getEnd(windingTypeEnds.get(1));
Optional<PhaseAngleClockModel> pac3 = pac3WModel.getEnd(windingTypeEnds.get(2));
if (pac2.isPresent() || pac3.isPresent()) {
t3wt.newExtension(ThreeWindingsTransformerPhaseAngleClockAdder.class)
.withPhaseAngleClockLeg2(pac2.map(model -> model.pac).orElse(0))
.withPhaseAngleClockLeg3(pac3.map(model -> model.pac).orElse(0)).add();
}
}
private static boolean highVoltageAtEnd1(VoltageLevel vl1, VoltageLevel vl2) {
return vl1.getNominalV() >= vl2.getNominalV();
}
private static boolean tapChangerAtEnd1(DataObject typTr2, boolean highAtEnd1) {
int tapSide = typTr2.getIntAttributeValue("tap_side");
// tap_side = 0 then tap_side = High voltage winding, tap_side = 1 then tap_side = Low voltage
// tap_side is not an bus index
return tapSide == 0 && highAtEnd1 || tapSide == 1 && !highAtEnd1;
}
private static void tapChangerToIidm(TapChangerModel tapChangerModel, TwoWindingsTransformer twt) {
if (isPhaseTapChanger(tapChangerModel)) {
PhaseTapChangerAdder ptc = twt.newPhaseTapChanger();
tapChangerToPhaseTapChanger(tapChangerModel, ptc);
} else if (isRatioTapChanger(tapChangerModel)) {
RatioTapChangerAdder rtc = twt.newRatioTapChanger();
tapChangerToRatioTapChanger(tapChangerModel, rtc);
}
}
private static void tapChangerToIidm(TapChangerModel tapChangerModel, Leg leg) {
if (isPhaseTapChanger(tapChangerModel)) {
PhaseTapChangerAdder ptc = leg.newPhaseTapChanger();
tapChangerToPhaseTapChanger(tapChangerModel, ptc);
} else if (isRatioTapChanger(tapChangerModel)) {
RatioTapChangerAdder rtc = leg.newRatioTapChanger();
tapChangerToRatioTapChanger(tapChangerModel, rtc);
}
}
private static boolean isPhaseTapChanger(TapChangerModel tapChangerModel) {
return tapChangerModel.steps.stream().anyMatch(step -> step.angle != 0.0);
}
private static boolean isRatioTapChanger(TapChangerModel tapChangerModel) {
return tapChangerModel.steps.stream().anyMatch(step -> step.ratio != 1.0);
}
private static void tapChangerToRatioTapChanger(TapChangerModel tapChangerModel, RatioTapChangerAdder rtc) {
rtc.setLoadTapChangingCapabilities(false)
.setLowTapPosition(tapChangerModel.lowTapPosition)
.setTapPosition(tapChangerModel.tapPosition);
tapChangerModel.steps.forEach(step ->
rtc.beginStep()
.setRho(1 / step.ratio)
.setR(step.r)
.setX(step.x)
.setG(step.g1)
.setB(step.b1)
.endStep());
rtc.add();
}
private static void tapChangerToPhaseTapChanger(TapChangerModel tapChangerModel, PhaseTapChangerAdder ptc) {
ptc.setLowTapPosition(tapChangerModel.lowTapPosition)
.setTapPosition(tapChangerModel.tapPosition);
tapChangerModel.steps.forEach(step ->
ptc.beginStep()
.setRho(1 / step.ratio)
.setAlpha(-step.angle)
.setR(step.r)
.setX(step.x)
.setG(step.g1)
.setB(step.b1)
.endStep());
ptc.setRegulating(false).setRegulationMode(PhaseTapChanger.RegulationMode.FIXED_TAP).add();
}
static final class TransformerModel {
private double r;
private double x;
private double g;
private double b;
private TransformerModel(Complex impedance, Complex shuntAdmittance) {
this.r = impedance.getReal();
this.x = impedance.getImaginary();
this.g = shuntAdmittance.getReal();
this.b = shuntAdmittance.getImaginary();
}
private static TransformerModel create(DataObject typTr2, double ratedApparentPower, double nominalVoltageEnd2) {
Complex impedance = createImpedance("uktr", "pcutr", typTr2, ratedApparentPower, nominalVoltageEnd2);
Complex shuntAdmittance = createShuntAdmittance("curmg", "pfe", typTr2, ratedApparentPower, nominalVoltageEnd2);
Complex proportion = createProportion("itrdr", "itrdl", typTr2);
if (isProportionDefined(proportion) && shuntAdmittance.abs() != 0.0) {
return transformerTModelToPiModel(impedance, shuntAdmittance, proportion);
} else {
return aproximatePiModel(impedance, shuntAdmittance);
}
}
private static Complex createImpedance(String uktrT, String pcutrT, DataObject typTr2, double ratedApparentPower, double nominalVoltage) {
float uktr = typTr2.getFloatAttributeValue(uktrT);
float pcutr = typTr2.getFloatAttributeValue(pcutrT);
return createImpedanceFromMeasures(uktr, pcutr, ratedApparentPower, nominalVoltage);
}
private static Complex createShuntAdmittance(String curmgT, String pfeT, DataObject typTr2, double ratedApparentPower, double nominalVoltage) {
float curmg = typTr2.getFloatAttributeValue(curmgT);
float pfe = typTr2.getFloatAttributeValue(pfeT);
return createShuntAdmittanceFromMeasures(curmg, pfe, ratedApparentPower, nominalVoltage);
}
private static Complex createProportion(String itrdrT, String itrdlT, DataObject typTr2) {
Optional<Float> itrdr = typTr2.findFloatAttributeValue(itrdrT);
Optional<Float> itrdl = typTr2.findFloatAttributeValue(itrdlT);
return new Complex(itrdr.isPresent() ? itrdr.get() : Double.NaN, itrdl.isPresent() ? itrdl.get() : Double.NaN);
}
private static boolean isProportionDefined(Complex proportion) {
return !Double.isNaN(proportion.getReal()) && !Double.isNaN(proportion.getImaginary());
}
private static TransformerModel transformerTModelToPiModel(Complex z, Complex ym, Complex proportion) {
Complex zh = new Complex(z.getReal() * proportion.getReal(), z.getImaginary() * proportion.getImaginary());
Complex zl = new Complex(z.getReal() * (1 - proportion.getReal()), z.getImaginary() * (1 - proportion.getImaginary()));
Complex y11h = zh.reciprocal();
Complex y12h = zh.reciprocal().negate();
Complex y21h = zh.reciprocal().negate();
Complex y22h = zh.reciprocal().add(ym);
Complex y11l = zl.reciprocal();
Complex y12l = zl.reciprocal().negate();
Complex y21l = zl.reciprocal().negate();
Complex y22l = zl.reciprocal();
Complex y11pi = y11h.subtract(y12h.multiply(y21h).divide(y22h.add(y11l)));
Complex y12pi = y12h.multiply(y12l).divide(y22h.add(y11l)).negate();
Complex y21pi = y21l.multiply(y21h).divide(y22h.add(y11l)).negate();
Complex y22pi = y22l.subtract(y21l.multiply(y12l).divide(y22h.add(y11l)));
return new TransformerModel(
y12pi.reciprocal().negate().add(y21pi.reciprocal().negate()).multiply(0.5),
y11pi.add(y12pi).add(y22pi.add(y21pi)));
}
private static TransformerModel aproximatePiModel(Complex z, Complex ym) {
return new TransformerModel(z, ym);
}
/**
* Create a transformer model from measures.
* <p>
* shortCircuitVoltage short-circuit voltage in %
* copperLosses copper loss in KWh
* openCircuitCurrent open circuit in %
* coreLosses core (or iron) losses in KWh
* ratedApparentPower rated apparent power in MVA
* nominalVoltage nominal voltage in Kv
*/
static Complex createImpedanceFromMeasures(double shortCircuitVoltage, double copperLosses,
double ratedApparentPower, double nominalVoltage) {
// calculate leakage impedance from short-circuit measurements
double zpu = shortCircuitVoltage / 100;
double rpu = copperLosses / (1000 * ratedApparentPower);
double xpu = Math.sqrt(zpu * zpu - rpu * rpu) * Math.signum(shortCircuitVoltage);
double r = impedanceFromPerUnitToEngineeringUnits(rpu, nominalVoltage, ratedApparentPower);
double x = impedanceFromPerUnitToEngineeringUnits(xpu, nominalVoltage, ratedApparentPower);
return new Complex(r, x);
}
static Complex createShuntAdmittanceFromMeasures(double openCircuitCurrent, double coreLosses,
double ratedApparentPower, double nominalVoltage) {
// calculate exciting branch admittance from open circuit measures
// Ym = gfe - jbm
double ypu = openCircuitCurrent / 100.0;
double gpu = coreLosses / (1000.0 * ratedApparentPower);
double bpu = -Math.sqrt(ypu * ypu - gpu * gpu);
double g = admittanceFromPerUnitToEngineeringUnits(gpu, nominalVoltage, ratedApparentPower);
double b = admittanceFromPerUnitToEngineeringUnits(bpu, nominalVoltage, ratedApparentPower);
return new Complex(g, b);
}
private void moveStructuralRatioFromEnd2ToEnd1(double a02) {
Complex a0 = new Complex(a02, 0.0);
r = TapChangerModel.impedanceConversion(r, a0);
x = TapChangerModel.impedanceConversion(x, a0);
g = TapChangerModel.admittanceConversion(g, a0);
b = TapChangerModel.admittanceConversion(b, a0);
}
}
private static final class Transformer3WModel {
private final Map<WindingType, TransformerModel> transformerModels = new EnumMap<>(WindingType.class);
private TransformerModel getEnd(WindingType windingType) {
return transformerModels.get(windingType);
}
private static Transformer3WModel create(DataObject typTr3, Rated3WModel rated3WModel, double nominalVoltage) {
double ratedSH = rated3WModel.getEnd(WindingType.HIGH).ratedS;
double ratedSM = rated3WModel.getEnd(WindingType.MEDIUM).ratedS;
double ratedSL = rated3WModel.getEnd(WindingType.LOW).ratedS;
double apparentPowerH = Math.min(ratedSH, ratedSM);
double apparentPowerM = Math.min(ratedSM, ratedSL);
double apparentPowerL = Math.min(ratedSL, ratedSH);
Complex zHM = TransformerModel.createImpedance("uktr3_h", "pcut3_h", typTr3, apparentPowerH, nominalVoltage);
Complex zML = TransformerModel.createImpedance("uktr3_m", "pcut3_m", typTr3, apparentPowerM, nominalVoltage);
Complex zLH = TransformerModel.createImpedance("uktr3_l", "pcut3_l", typTr3, apparentPowerL, nominalVoltage);
Complex zH = zHM.add(zLH).subtract(zML).multiply(0.5);
Complex zM = zHM.add(zML).subtract(zLH).multiply(0.5);
Complex zL = zML.add(zLH).subtract(zHM).multiply(0.5);
Complex ysh = TransformerModel.createShuntAdmittance("curm3", "pfe", typTr3, ratedSH, nominalVoltage);
int i3loc = typTr3.findIntAttributeValue("i3loc").orElse(0);
Complex yshH = assignShuntAdmittanceToWinding(ysh, i3loc, WindingType.HIGH);
Complex yshM = assignShuntAdmittanceToWinding(ysh, i3loc, WindingType.MEDIUM);
Complex yshL = assignShuntAdmittanceToWinding(ysh, i3loc, WindingType.LOW);
Transformer3WModel transformer3WModel = new Transformer3WModel();
transformer3WModel.transformerModels.put(WindingType.HIGH, new TransformerModel(zH, yshH));
transformer3WModel.transformerModels.put(WindingType.MEDIUM, new TransformerModel(zM, yshM));
transformer3WModel.transformerModels.put(WindingType.LOW, new TransformerModel(zL, yshL));
return transformer3WModel;
}
private static Complex assignShuntAdmittanceToWinding(Complex ysh, int i3loc, WindingType windingType) {
// location here is not a busIndexIn, it is not a bus index
// loc = 0 refers to high voltage winding,
// loc = 1 means medium,
// loc = 2 means low
return windingType.equals(positionToWindingType(i3loc)) ? ysh : Complex.ZERO;
}
}
private static final class RatedModel {
private final double ratedU1;
private final double ratedU2;
private final double ratedS;
private RatedModel(double ratedU1, double ratedU2, double ratedS) {
this.ratedU1 = ratedU1;
this.ratedU2 = ratedU2;
this.ratedS = ratedS;
}
private static RatedModel create(DataObject typTr2, boolean highAtEnd1) {
float strn = typTr2.getFloatAttributeValue("strn");
float utrnL = typTr2.getFloatAttributeValue("utrn_l");
float utrnH = typTr2.getFloatAttributeValue("utrn_h");
double ratedU1;
double ratedU2;
if (highAtEnd1) {
ratedU1 = utrnH;
ratedU2 = utrnL;
} else {
ratedU1 = utrnL;
ratedU2 = utrnH;
}
return new RatedModel(ratedU1, ratedU2, strn);
}
}
private static final class Rated3WModel {
private final Map<WindingType, RatedModel> ratedModels = new EnumMap<>(WindingType.class);
private RatedModel getEnd(WindingType windingType) {
return ratedModels.get(windingType);
}
private static Rated3WModel create(DataObject typTr3, double ratedU0) {
float strnL = typTr3.getFloatAttributeValue("strn3_l");
float strnM = typTr3.getFloatAttributeValue("strn3_m");
float strnH = typTr3.getFloatAttributeValue("strn3_h");
float utrnL = typTr3.getFloatAttributeValue("utrn3_l");
float utrnM = typTr3.getFloatAttributeValue("utrn3_m");
float utrnH = typTr3.getFloatAttributeValue("utrn3_h");
Rated3WModel rated3WModel = new Rated3WModel();
rated3WModel.ratedModels.put(WindingType.HIGH, new RatedModel(utrnH, ratedU0, strnH));
rated3WModel.ratedModels.put(WindingType.MEDIUM, new RatedModel(utrnM, ratedU0, strnM));
rated3WModel.ratedModels.put(WindingType.LOW, new RatedModel(utrnL, ratedU0, strnL));
return rated3WModel;
}
}
private static final class PhaseAngleClockModel {
private final int pac;
private PhaseAngleClockModel(int pac) {
this.pac = pac;
}
private static Optional<PhaseAngleClockModel> create(DataObject typTr2) {
float nt2ag = typTr2.findFloatAttributeValue("nt2ag").orElse(0f);
if (nt2ag > 0) {
return Optional.of(new PhaseAngleClockModel((int) nt2ag));
} else {
return Optional.empty();
}
}
}
private static final class PhaseAngleClock3WModel {
private final Map<WindingType, Optional<PhaseAngleClockModel>> phaseAngleClockModels = new EnumMap<>(WindingType.class);
private Optional<PhaseAngleClockModel> getEnd(WindingType windingType) {
return phaseAngleClockModels.get(windingType);
}
private static PhaseAngleClock3WModel create(DataObject typTr3) {
float nt3agL = typTr3.findFloatAttributeValue("nt3ag_l").orElse(0f);
float nt3agM = typTr3.findFloatAttributeValue("nt3ag_m").orElse(0f);
float nt3agH = typTr3.findFloatAttributeValue("nt3ag_h").orElse(0f);
PhaseAngleClock3WModel phaseAngleClockModel = new PhaseAngleClock3WModel();
phaseAngleClockModel.phaseAngleClockModels.put(WindingType.LOW, nt3agL > 0 ? Optional.of(new PhaseAngleClockModel((int) nt3agL)) : Optional.empty());
phaseAngleClockModel.phaseAngleClockModels.put(WindingType.MEDIUM, nt3agM > 0 ? Optional.of(new PhaseAngleClockModel((int) nt3agM)) : Optional.empty());
phaseAngleClockModel.phaseAngleClockModels.put(WindingType.HIGH, nt3agH > 0 ? Optional.of(new PhaseAngleClockModel((int) nt3agH)) : Optional.empty());
return phaseAngleClockModel;
}
}
private static final class PowerFactoryTapChanger {
private final int nntap;
private final int nntap0;
private final int ntpmn;
private final int ntpmx;
private final double dutap;
private final double phitr;
private RealMatrix mTaps = null;
private PowerFactoryTapChanger(int nntap, int nntap0, int ntpmn, int ntpmx, double dutap, double phitr) {
this.nntap = nntap;
this.nntap0 = nntap0;
this.ntpmn = ntpmn;
this.ntpmx = ntpmx;
this.dutap = dutap;
this.phitr = phitr;
}
private static PowerFactoryTapChanger create(DataObject elmTr2, DataObject typTr2) {
PowerFactoryTapChanger powerFactoryTapChanger = create("nntap", "nntap0", "ntpmn", "ntpmx", "dutap", "phitr", elmTr2, typTr2);
powerFactoryTapChanger.mTaps = elmTr2.findDoubleMatrixAttributeValue("mTaps").orElse(null);
return powerFactoryTapChanger;
}
private static PowerFactoryTapChanger create(String nntapT, String nntap0T, String ntpmnT, String ntpmxT, String duTapT,
String phitrT, DataObject elmTr2, DataObject typTr2) {
int nntap = elmTr2.getIntAttributeValue(nntapT);
int nntap0 = typTr2.getIntAttributeValue(nntap0T);
int ntpmn = typTr2.getIntAttributeValue(ntpmnT);
int ntpmx = typTr2.getIntAttributeValue(ntpmxT);
nntap = fixTapInsideLimits(nntap, ntpmn, ntpmx, elmTr2);
Optional<Float> opdutap = typTr2.findFloatAttributeValue(duTapT);
Optional<Float> opphitr = typTr2.findFloatAttributeValue(phitrT);
double dutap = opdutap.isPresent() ? opdutap.get() : 0.0;
double phitr = opphitr.isPresent() ? opphitr.get() : 0.0;
return new PowerFactoryTapChanger(nntap, nntap0, ntpmn, ntpmx, dutap, phitr);
}
private static int fixTapInsideLimits(int nntap, int ntpmn, int ntpmx, DataObject elementObj) {
if (nntap < ntpmn) {
LOGGER.warn("{}: Tap {} has been fixed to the minimum tap {} '{}'", elementObj.getDataClassName(), nntap, ntpmn, elementObj);
return ntpmn;
} else if (nntap > ntpmx) {
LOGGER.warn("{}: Tap {} has been fixed to the maximum tap {} '{}'", elementObj.getDataClassName(), nntap, ntpmx, elementObj);
return ntpmx;
} else {
return nntap;
}
}
}
private static final class PowerFactoryTapChangers3W {
private PowerFactoryTapChanger high;
private PowerFactoryTapChanger medium;
private PowerFactoryTapChanger low;
private static PowerFactoryTapChangers3W create(DataObject elmTr3, DataObject typTr3) {
PowerFactoryTapChangers3W pft = new PowerFactoryTapChangers3W();
pft.high = PowerFactoryTapChanger.create("n3tap_h", "n3tp0_h", "n3tmn_h", "n3tmx_h", "du3tp_h", "ph3tr_h", elmTr3, typTr3);
pft.medium = PowerFactoryTapChanger.create("n3tap_m", "n3tp0_m", "n3tmn_m", "n3tmx_m", "du3tp_m", "ph3tr_m", elmTr3, typTr3);
pft.low = PowerFactoryTapChanger.create("n3tap_l", "n3tp0_l", "n3tmn_l", "n3tmx_l", "du3tp_l", "ph3tr_l", elmTr3, typTr3);
int iMeasTap = elmTr3.findIntAttributeValue("iMeasTap").orElse(0);
elmTr3.findDoubleMatrixAttributeValue("mTaps").ifPresent(mTaps -> {
switch (positionToWindingType(iMeasTap)) {
case HIGH:
pft.high.mTaps = mTaps;
break;
case MEDIUM:
pft.medium.mTaps = mTaps;
break;
case LOW:
pft.low.mTaps = mTaps;
break;
}
});
return pft;
}
}
private static final class TapChangerModel {
private final int lowTapPosition;
private final int tapPosition;
private final List<TapChangerStep> steps;
private TapChangerModel(int lowTapPosition, int tapPosition) {
this.lowTapPosition = lowTapPosition;
this.tapPosition = tapPosition;
steps = new ArrayList<>();
}
// angle in degrees
private static final class TapChangerStep {
private double ratio;
private double angle;
private double r;
private double x;
private double g1;
private double b1;
private TapChangerStep(double ratio, double angle) {
this(ratio, angle, 0.0, 0.0, 0.0, 0.0);
}
private TapChangerStep(double ratio, double angle, double r, double x, double g1, double b1) {
this.ratio = ratio;
this.angle = angle;
this.r = r;
this.x = x;
this.g1 = g1;
this.b1 = b1;
}
}
private static Optional<TapChangerModel> create(PowerFactoryTapChanger powerFactoryTapChanger, boolean tapChangerAtEnd1) {
Optional<TapChangerModel> tapChanger = TapChangerModel.create(powerFactoryTapChanger);
if (tapChanger.isPresent()) {
if (tapChangerAtEnd1) {
return tapChanger;
} else {
return Optional.of(moveTapChanger(tapChanger.get()));
}
}
return tapChanger;
}
private static Optional<TapChangerModel> create(PowerFactoryTapChanger powerFactoryTapChanger) {
if (powerFactoryTapChanger.dutap == 0.0 && powerFactoryTapChanger.phitr == 0.0 && powerFactoryTapChanger.mTaps == null) {
return Optional.empty();
}
PowerFactoryTapChanger fixedTapchangerPar = fixAndCheckTapChangerPar(powerFactoryTapChanger);
if (fixedTapchangerPar.mTaps != null) {
return Optional.of(createTapChangerFromResourceTable(fixedTapchangerPar));
}
return Optional.of(createTapChangerFromAtributes(fixedTapchangerPar));
}
private static PowerFactoryTapChanger fixAndCheckTapChangerPar(PowerFactoryTapChanger powerFactoryTapChanger) {
// In IIDM always minTap = 0
int nntap = powerFactoryTapChanger.nntap - powerFactoryTapChanger.ntpmn;
int nntap0 = powerFactoryTapChanger.nntap0 - powerFactoryTapChanger.ntpmn;
int ntpmn = 0;
int ntpmx = powerFactoryTapChanger.ntpmx - powerFactoryTapChanger.ntpmn;
PowerFactoryTapChanger fixedPowerFactoryTapChanger = new PowerFactoryTapChanger(nntap, nntap0, ntpmn, ntpmx, powerFactoryTapChanger.dutap, powerFactoryTapChanger.phitr);
fixedPowerFactoryTapChanger.mTaps = powerFactoryTapChanger.mTaps;
return fixedPowerFactoryTapChanger;
}
private static TapChangerModel createTapChangerFromResourceTable(PowerFactoryTapChanger powerFactoryTapChanger) {
if (powerFactoryTapChanger.mTaps.getColumnDimension() == 5) {
return createTapChangerFromResourceTableForTwoWindingsTansformer(powerFactoryTapChanger);
}
if (powerFactoryTapChanger.mTaps.getColumnDimension() == 8) {
return createTapChangerFromResourceTableForThreeWindingsTansformer(powerFactoryTapChanger);
}
throw new PowerFactoryException("Unexpected number of columns in mTaps");
}
private static TapChangerModel createTapChangerFromResourceTableForTwoWindingsTansformer(PowerFactoryTapChanger powerFactoryTapChanger) {
int rows = powerFactoryTapChanger.mTaps.getRowDimension();
if (rows != powerFactoryTapChanger.ntpmx - powerFactoryTapChanger.ntpmn + 1) {
throw new PowerFactoryException("Unexpected number of rows in mTaps");
}
TapChangerModel tapChangerModel = new TapChangerModel(powerFactoryTapChanger.ntpmn, powerFactoryTapChanger.nntap);
for (int row = 0; row < rows; row++) {
double ratio = powerFactoryTapChanger.mTaps.getEntry(row, 4);
double angle = powerFactoryTapChanger.mTaps.getEntry(row, 1);
tapChangerModel.steps.add(new TapChangerStep(ratio, angle));
}
return tapChangerModel;
}
private static TapChangerModel createTapChangerFromResourceTableForThreeWindingsTansformer(PowerFactoryTapChanger powerFactoryTapChanger) {
int rows = powerFactoryTapChanger.mTaps.getRowDimension();
if (rows != powerFactoryTapChanger.ntpmx - powerFactoryTapChanger.ntpmn + 1) {
throw new PowerFactoryException("Unexpected mTaps dimension");
}
double ratio = 1.0;
TapChangerModel tapChangerModel = new TapChangerModel(powerFactoryTapChanger.ntpmn, powerFactoryTapChanger.nntap);
for (int row = 0; row < rows; row++) {
double angle = powerFactoryTapChanger.mTaps.getEntry(row, 1);
tapChangerModel.steps.add(new TapChangerStep(ratio, angle));
}
return tapChangerModel;
}
private static TapChangerModel createTapChangerFromAtributes(PowerFactoryTapChanger powerFactoryTapChanger) {
TapChangerModel tapChangerModel = new TapChangerModel(powerFactoryTapChanger.ntpmn, powerFactoryTapChanger.nntap);
for (int tap = powerFactoryTapChanger.ntpmn; tap <= powerFactoryTapChanger.ntpmx; tap++) {
TapChangerStep tapChangerStep = createTapChangerStep(tap, powerFactoryTapChanger.nntap0, powerFactoryTapChanger.dutap, powerFactoryTapChanger.phitr);
tapChangerModel.steps.add(tapChangerStep);
}
return tapChangerModel;
}
private static TapChangerStep createTapChangerStep(int tap, int nntap0, double dutap, double phitr) {
double ratio = 1 + (tap - nntap0) * dutap / 100.0;
double angle = (tap - nntap0) * phitr;
return new TapChangerStep(ratio, angle);
}
/**
* Step corrections are updated to obtain an equivalent tapChanger in the other side.
* Step r, x, g, b are already percentage deviations of nominal values
* R = R * (1 + r / 100)
* X = X * (1 + x / 100)
* G = G * (1 + g / 100)
* B = B * (1 + b / 100)
*/
private static TapChangerModel moveTapChanger(TapChangerModel tc) {
tc.steps.forEach(step -> {
double ratio = step.ratio;
double angle = step.angle;
double r = step.r;
double x = step.x;
double g1 = step.g1;
double b1 = step.b1;
calculateConversionStep(step, ratio, angle, r, x, g1, b1);
});
return tc;
}
private static void calculateConversionStep(TapChangerStep step, double ratio, double angle, double r, double x, double g1, double b1) {
Complex a = new Complex(ratio * Math.cos(Math.toRadians(angle)), ratio * Math.sin(Math.toRadians(angle)));
Complex na = a.reciprocal();
step.ratio = na.abs();
step.angle = Math.toDegrees(na.getArgument());
step.r = 100 * (impedanceConversion(1 + r / 100, a) - 1);
step.x = 100 * (impedanceConversion(1 + x / 100, a) - 1);
step.g1 = 100 * (admittanceConversion(1 + g1 / 100, a) - 1);
step.b1 = 100 * (admittanceConversion(1 + b1 / 100, a) - 1);
}
private static double admittanceConversion(double correction, Complex a) {
double a2 = a.abs() * a.abs();
return correction / a2;
}
private static double impedanceConversion(double correction, Complex a) {
double a2 = a.abs() * a.abs();
return correction * a2;
}
}
private static final class TapChanger3W {
private final Map<WindingType, Optional<TapChangerModel>> tapChangers = new EnumMap<>(WindingType.class);
private Optional<TapChangerModel> getEnd(WindingType windingType) {
return tapChangers.get(windingType);
}
private static TapChanger3W create(PowerFactoryTapChangers3W tapChangerTap3w) {
TapChanger3W tapChanger3W = new TapChanger3W();
tapChanger3W.tapChangers.put(WindingType.HIGH, TapChangerModel.create(tapChangerTap3w.high));
tapChanger3W.tapChangers.put(WindingType.MEDIUM, TapChangerModel.create(tapChangerTap3w.medium));
tapChanger3W.tapChangers.put(WindingType.LOW, TapChangerModel.create(tapChangerTap3w.low));
return tapChanger3W;
}
}
private static List<WindingType> createWindingTypeEnds(VoltageLevel vl1, VoltageLevel vl2, VoltageLevel vl3) {
double vn1 = vl1.getNominalV();
double vn2 = vl2.getNominalV();
double vn3 = vl3.getNominalV();
if (vn1 >= vn2 && vn2 >= vn3) {
return List.of(WindingType.HIGH, WindingType.MEDIUM, WindingType.LOW);
} else if (vn1 >= vn3 && vn3 >= vn2) {
return List.of(WindingType.HIGH, WindingType.LOW, WindingType.MEDIUM);
} else if (vn2 >= vn1 && vn1 >= vn3) {
return List.of(WindingType.MEDIUM, WindingType.HIGH, WindingType.LOW);
} else if (vn1 >= vn2) {
return List.of(WindingType.MEDIUM, WindingType.LOW, WindingType.HIGH);
} else if (vn2 >= vn3) {
return List.of(WindingType.LOW, WindingType.HIGH, WindingType.MEDIUM);
} else {
return List.of(WindingType.LOW, WindingType.MEDIUM, WindingType.HIGH);
}
}
private static WindingType positionToWindingType(int position) {
return switch (position) {
case 0 -> WindingType.HIGH;
case 1 -> WindingType.MEDIUM;
case 2 -> WindingType.LOW;
default -> throw new PowerFactoryException("Unexpected position: " + position);
};
}
private static final Logger LOGGER = LoggerFactory.getLogger(TransformerConverter.class);
}