VscDcTransmissionLineConverter.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.psse.converter;

import com.powsybl.iidm.network.HvdcLine.ConvertersMode;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.util.ContainersMapping;
import com.powsybl.iidm.network.util.HvdcUtils;
import com.powsybl.psse.model.PsseVersion;
import com.powsybl.psse.model.pf.*;

import java.util.*;
import java.util.stream.Stream;

import static com.powsybl.psse.converter.AbstractConverter.PsseEquipmentType.PSSE_VSC_DC_LINE;
import static com.powsybl.psse.model.PsseVersion.Major.V35;

/**
 * @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
 * @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
 */
class VscDcTransmissionLineConverter extends AbstractConverter {

    private static final double DEFAULT_MAXP_FACTOR = 1.2;

    VscDcTransmissionLineConverter(PsseVoltageSourceConverterDcTransmissionLine psseVscDcTransmissionLine, ContainersMapping containerMapping, Network network, PsseVersion version, NodeBreakerImport nodeBreakerImport) {
        super(containerMapping, network);
        this.psseVscDcTransmissionLine = Objects.requireNonNull(psseVscDcTransmissionLine);
        this.version = Objects.requireNonNull(version);
        this.nodeBreakerImport = nodeBreakerImport;
    }

    void create() {
        if (!getContainersMapping().isBusDefined(psseVscDcTransmissionLine.getConverter1().getIbus()) || !getContainersMapping().isBusDefined(psseVscDcTransmissionLine.getConverter2().getIbus())) {
            return;
        }

        PsseVoltageSourceConverter converter1 = psseVscDcTransmissionLine.getConverter1();
        PsseVoltageSourceConverter converter2 = psseVscDcTransmissionLine.getConverter2();
        double activePowerSetpoint = getVscDcTransmissionLineActivePowerSetpoint(converter1, converter2);
        ConvertersMode convertersMode = getConvertersMode(converter1, converter2);

        String busId1 = getBusId(converter1.getIbus());
        VoltageLevel voltageLevel1 = getNetwork().getVoltageLevel(getContainersMapping().getVoltageLevelId(converter1.getIbus()));
        VscConverterStationAdder adder1 = voltageLevel1.newVscConverterStation()
                .setId(getVscConverterId(getNetwork(), psseVscDcTransmissionLine, converter1))
                .setLossFactor((float) getLossFactor1(converter1, activePowerSetpoint, convertersMode))
                .setReactivePowerSetpoint(getReactiveSetpoint(converter1, activePowerSetpoint))
                .setVoltageRegulatorOn(false);

        String equipmentId1 = getNodeBreakerEquipmentId(PSSE_VSC_DC_LINE, converter1.getIbus(), psseVscDcTransmissionLine.getName());
        OptionalInt node1 = nodeBreakerImport.getNode(getNodeBreakerEquipmentIdBus(equipmentId1, converter1.getIbus(), 0, 0, converter1.getIbus(), "I"));
        if (node1.isPresent()) {
            adder1.setNode(node1.getAsInt());
        } else {
            adder1.setConnectableBus(busId1);
            adder1.setBus(psseVscDcTransmissionLine.getMdc() == 0 ? null : busId1);
        }
        VscConverterStation c1 = adder1.add();
        addReactiveLimits(c1, converter1);

        String busId2 = getBusId(converter2.getIbus());
        VoltageLevel voltageLevel2 = getNetwork().getVoltageLevel(getContainersMapping().getVoltageLevelId(converter2.getIbus()));
        VscConverterStationAdder adder2 = voltageLevel2.newVscConverterStation()
                .setId(getVscConverterId(getNetwork(), psseVscDcTransmissionLine, converter2))
                .setLossFactor((float) getLossFactor2(converter2, activePowerSetpoint, convertersMode))
                .setReactivePowerSetpoint(getReactiveSetpoint(converter2, activePowerSetpoint))
                .setVoltageRegulatorOn(false);

        String equipmentId2 = getNodeBreakerEquipmentId(PSSE_VSC_DC_LINE, converter2.getIbus(), psseVscDcTransmissionLine.getName());
        OptionalInt node2 = nodeBreakerImport.getNode(getNodeBreakerEquipmentIdBus(equipmentId2, converter2.getIbus(), 0, 0, converter2.getIbus(), "I"));
        if (node2.isPresent()) {
            adder2.setNode(node2.getAsInt());
        } else {
            adder2.setConnectableBus(busId2);
            adder2.setBus(psseVscDcTransmissionLine.getMdc() == 0 ? null : busId2);
        }
        VscConverterStation c2 = adder2.add();
        addReactiveLimits(c2, converter2);

        HvdcLineAdder adder = getNetwork().newHvdcLine()
            .setId(getVscDcTransmissionLineId(psseVscDcTransmissionLine.getName()))
            .setName(psseVscDcTransmissionLine.getName())
            .setR(psseVscDcTransmissionLine.getRdc())
            .setNominalV(getHvdcLineNominalV(voltageLevel1, voltageLevel2))
            .setActivePowerSetpoint(activePowerSetpoint)
            .setMaxP(getVscDcTransmissionLineMaxP(converter1, voltageLevel1.getNominalV(), converter2, voltageLevel2.getNominalV(), activePowerSetpoint))
            .setConvertersMode(convertersMode)
            .setConverterStationId1(c1.getId())
            .setConverterStationId2(c2.getId());
        adder.add();
    }

    private static double getLossFactor1(PsseVoltageSourceConverter converter1, double activePowerSetpoint, ConvertersMode convertersMode) {
        return convertersMode == ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER
                ? getLossFactor(converter1, activePowerSetpoint, true)
                : getLossFactor(converter1, activePowerSetpoint, false);
    }

    private static double getLossFactor2(PsseVoltageSourceConverter converter2, double activePowerSetpoint, ConvertersMode convertersMode) {
        return convertersMode == ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER
                ? getLossFactor(converter2, activePowerSetpoint, false)
                : getLossFactor(converter2, activePowerSetpoint, true);
    }

    private static double getLossFactor(PsseVoltageSourceConverter converter1, double activePowerSetpoint, boolean isRectifier) {
        if (isRectifier) {
            double pAC = activePowerSetpoint + Math.abs(converter1.getAloss() / 1000.0);
            return pAC > 0.0 ? (1.0 - activePowerSetpoint / pAC) * 100.0 : 0.0;
        } else {
            double pAC = activePowerSetpoint - Math.abs(converter1.getAloss() / 1000.0);
            return activePowerSetpoint > 0.0 ? (1.0 - pAC / activePowerSetpoint) * 100.0 : 0.0;
        }
    }

    private static double getReactiveSetpoint(PsseVoltageSourceConverter converter, double activePowerSetpoint) {
        if (converter.getMode() == 1) {
            return 0.0;
        }
        double powerFactor = converter.getAcset();
        return powerFactor != 0.0 && Math.abs(powerFactor) <= 1.0 ? activePowerSetpoint * Math.sqrt(1 - powerFactor * powerFactor) / powerFactor : 0.0;
    }

    private static void addReactiveLimits(VscConverterStation c, PsseVoltageSourceConverter converter) {
        c.newMinMaxReactiveLimits()
                .setMaxQ(getMaxQ(converter))
                .setMinQ(getMinQ(converter))
                .add();
    }

    private static double getMaxQ(PsseVoltageSourceConverter converter) {
        return converter.getMode() == 1 ? converter.getMaxq() : 0.0;
    }

    private static double getMinQ(PsseVoltageSourceConverter converter) {
        return converter.getMode() == 1 ? converter.getMinq() : 0.0;
    }

    private static double getHvdcLineNominalV(VoltageLevel voltageLevel1, VoltageLevel voltageLevel2) {
        return Math.max(voltageLevel1.getNominalV(), voltageLevel2.getNominalV());
    }

    private static double getVscDcTransmissionLineActivePowerSetpoint(PsseVoltageSourceConverter converter1, PsseVoltageSourceConverter converter2) {
        if (converter1.getType() == 2) {
            return Math.abs(converter1.getDcset());
        } else if (converter2.getType() == 2) {
            return Math.abs(converter2.getDcset());
        } else {
            return 0.0;
        }
    }

    private static double getVscDcTransmissionLineMaxP(PsseVoltageSourceConverter converter1, double nominalV1, PsseVoltageSourceConverter converter2, double nominalV2, double activePowerSetpoint) {
        double maxP = Stream.of(converter1.getSmax(),
                        currentInAmpsToMw(converter1.getImax(), nominalV1),
                        converter2.getSmax(),
                        currentInAmpsToMw(converter2.getImax(), nominalV2)).max(Comparator.naturalOrder()).orElse(0.0);
        return maxP > 0.0 ? maxP : activePowerSetpoint * DEFAULT_MAXP_FACTOR;
    }

    private static ConvertersMode getConvertersMode(PsseVoltageSourceConverter converter1, PsseVoltageSourceConverter converter2) {
        if (converter1.getType() == 2) {
            return converter1.getDcset() > 0 ? ConvertersMode.SIDE_1_INVERTER_SIDE_2_RECTIFIER : ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER;
        } else if (converter2.getType() == 2) {
            return converter1.getDcset() > 0 ? ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER : ConvertersMode.SIDE_1_INVERTER_SIDE_2_RECTIFIER;
        } else {
            return ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER;
        }
    }

    public void addControl() {
        String id = getVscDcTransmissionLineId(psseVscDcTransmissionLine.getName());
        HvdcLine hvdcLine = getNetwork().getHvdcLine(id);

        if (hvdcLine == null) {
            return;
        }
        addControlConverter(getNetwork(), psseVscDcTransmissionLine.getConverter1(), (VscConverterStation) hvdcLine.getConverterStation1(), version, nodeBreakerImport);
        addControlConverter(getNetwork(), psseVscDcTransmissionLine.getConverter2(), (VscConverterStation) hvdcLine.getConverterStation2(), version, nodeBreakerImport);
    }

    private static void addControlConverter(Network network, PsseVoltageSourceConverter converter, VscConverterStation c, PsseVersion psseVersion, NodeBreakerImport nodeBreakerImport) {
        Terminal regulatingTerminal = findRegulatingTerminal(network, converter, c, nodeBreakerImport, psseVersion);
        c.setRegulatingTerminal(regulatingTerminal)
                .setVoltageSetpoint(findTargetVpu(converter) * regulatingTerminal.getVoltageLevel().getNominalV())
                .setVoltageRegulatorOn(findIsRegulatingOn(converter));
    }

    private static Terminal findRegulatingTerminal(Network network, PsseVoltageSourceConverter converter, VscConverterStation c, NodeBreakerImport nodeBreakerImport, PsseVersion psseVersion) {
        Terminal regulatingTerminal = null;
        Optional<NodeBreakerImport.ControlR> control = nodeBreakerImport.getControl(vscDcTransmissionLineRegulatingBus(converter, psseVersion));
        if (control.isPresent()) {
            regulatingTerminal = findTerminalNode(network, control.get().voltageLevelId(), control.get().node());
        } else {
            String regulatingBusId = getBusId(vscDcTransmissionLineRegulatingBus(converter, psseVersion));
            Bus bus = network.getBusBreakerView().getBus(regulatingBusId);
            if (bus != null) {
                regulatingTerminal = bus.getConnectedTerminalStream().findFirst().orElse(null);
            }
        }
        return regulatingTerminal != null ? regulatingTerminal : c.getTerminal();
    }

    private static int vscDcTransmissionLineRegulatingBus(PsseVoltageSourceConverter converter, PsseVersion psseVersion) {
        if (psseVersion.major() == V35) {
            return converter.getVsreg();
        } else {
            return converter.getRemot();
        }
    }

    private static double findTargetVpu(PsseVoltageSourceConverter converter) {
        return converter.getMode() == 1 && isVoltageSetpointValid(converter.getAcset()) ? converter.getAcset() : 1.0;
    }

    private static boolean isVoltageSetpointValid(double targetV) {
        return Double.isFinite(targetV) && targetV > 0.0;
    }

    private static boolean findIsRegulatingOn(PsseVoltageSourceConverter converter) {
        return converter.getMode() == 1;
    }

    static void create(Network network, PssePowerFlowModel psseModel, ContextExport contextExport) {
        PsseVersion version = PsseVersion.fromRevision(psseModel.getCaseIdentification().getRev());
        network.getHvdcLines().forEach(hvdcLine -> {
            if (isVscDcTransmissionLine(hvdcLine)) {
                psseModel.addVoltageSourceConverterDcTransmissionLines(Collections.singletonList(createVscDcTransmissionLine(hvdcLine, version, contextExport)));
            }
        });
        psseModel.replaceAllVoltageSourceConverterDcTransmissionLines(psseModel.getVoltageSourceConverterDcTransmissionLines().stream().sorted(Comparator.comparing(PsseVoltageSourceConverterDcTransmissionLine::getName)).toList());
    }

    private static PsseVoltageSourceConverterDcTransmissionLine createVscDcTransmissionLine(HvdcLine hvdcLine, PsseVersion version, ContextExport contextExport) {
        PsseVoltageSourceConverterDcTransmissionLine vscDcTransmissionLine = new PsseVoltageSourceConverterDcTransmissionLine();
        vscDcTransmissionLine.setName(extractVscDcTransmissionLineName(hvdcLine.getId()));
        vscDcTransmissionLine.setMdc(findControlMode(hvdcLine, contextExport));
        vscDcTransmissionLine.setRdc(hvdcLine.getR());
        vscDcTransmissionLine.setOwnership(createDefaultOwnership());

        vscDcTransmissionLine.setConverter1(createConverter((VscConverterStation) hvdcLine.getConverterStation1(), version, contextExport));
        vscDcTransmissionLine.setConverter2(createConverter((VscConverterStation) hvdcLine.getConverterStation2(), version, contextExport));
        return vscDcTransmissionLine;
    }

    private static PsseVoltageSourceConverter createConverter(VscConverterStation vscConverter, PsseVersion version, ContextExport contextExport) {
        PsseVoltageSourceConverter psseConverter = createDefaultConverter();
        int busI = getTerminalBusI(vscConverter.getTerminal(), contextExport);
        int regulatingBus = getRegulatingTerminalBusI(vscConverter.getRegulatingTerminal(), busI, vscDcTransmissionLineRegulatingBus(psseConverter, version), contextExport);
        double converterTargetP = HvdcUtils.getConverterStationTargetP(vscConverter);

        psseConverter.setIbus(busI);
        psseConverter.setType(2);
        psseConverter.setMode(getMode(vscConverter));
        psseConverter.setDcset(converterTargetP);
        psseConverter.setAcset(findAcset(vscConverter, getMode(vscConverter), converterTargetP));
        psseConverter.setAloss(findALosses(vscConverter.getLossFactor(), converterTargetP));
        psseConverter.setMaxq(checkAndFixMaxQ(vscConverter.getReactiveLimits().getMaxQ(converterTargetP)));
        psseConverter.setMinq(checkAndFixMinQ(vscConverter.getReactiveLimits().getMinQ(converterTargetP)));
        psseConverter.setVsreg(regulatingBus);
        psseConverter.setNreg(getRegulatingTerminalNode(vscConverter.getRegulatingTerminal(), contextExport));

        return psseConverter;
    }

    private static int getMode(VscConverterStation vscConverter) {
        return vscConverter.isVoltageRegulatorOn() ? 1 : 2;
    }

    private static double findAcset(VscConverterStation vscConverter, int mode, double targetP) {
        if (mode == 1) {
            double targetV = vscConverter.getVoltageSetpoint();
            return Double.isFinite(targetV) && targetV > 0.0 ? targetV / vscConverter.getRegulatingTerminal().getVoltageLevel().getNominalV() : 1.0;
        } else {
            double targetQ = vscConverter.getReactivePowerSetpoint();
            return Double.isFinite(targetP) && targetP != 0.0 && Double.isFinite(targetQ) && targetQ != 0.0 ? targetP / Math.sqrt(targetP * targetP + targetQ * targetQ) : 1.0;
        }
    }

    private static double findALosses(double lossFactor, double converterTargetP) {
        return converterTargetP != 0.0 ? 1000.0 * lossFactor * converterTargetP / 100.0 : 0.0;
    }

    private static double checkAndFixMaxQ(double maxQ) {
        return Double.isNaN(maxQ) ? 9999.0 : maxQ;
    }

    private static double checkAndFixMinQ(double minQ) {
        return Double.isNaN(minQ) ? -9999.0 : minQ;
    }

    private static PsseVoltageSourceConverter createDefaultConverter() {
        PsseVoltageSourceConverter converter = new PsseVoltageSourceConverter();
        converter.setIbus(0);
        converter.setType(1);
        converter.setMode(1);
        converter.setDcset(0.0);
        converter.setAcset(1.0);
        converter.setAloss(0.0);
        converter.setBloss(0.0);
        converter.setMinloss(0.0);
        converter.setSmax(0.0);
        converter.setImax(0.0);
        converter.setPwf(1.0);
        converter.setMaxq(9999.0);
        converter.setMinq(-9999.0);
        converter.setVsreg(0);
        converter.setNreg(0);
        converter.setRmpct(100.0);
        return converter;
    }

    static void update(Network network, PssePowerFlowModel psseModel) {
        psseModel.getVoltageSourceConverterDcTransmissionLines().forEach(psseVscDcTransmissionLine -> {
            String hvdcId = getVscDcTransmissionLineId(psseVscDcTransmissionLine.getName());
            HvdcLine hvdcLine = network.getHvdcLine(hvdcId);

            if (hvdcLine == null) {
                psseVscDcTransmissionLine.setMdc(0);
            } else {
                psseVscDcTransmissionLine.setMdc(findUpdatedControlMode(hvdcLine));
            }
        });
    }

    private static int findControlMode(HvdcLine hvdcLine, ContextExport contextExport) {
        return getStatus(hvdcLine.getConverterStation1().getTerminal(), contextExport) == 1
                && getStatus(hvdcLine.getConverterStation2().getTerminal(), contextExport) == 1 ? 1 : 0;
    }

    private static int findUpdatedControlMode(HvdcLine hvdcLine) {
        return getUpdatedStatus(hvdcLine.getConverterStation1().getTerminal()) == 1
                && getUpdatedStatus(hvdcLine.getConverterStation2().getTerminal()) == 1 ? 1 : 0;
    }

    private final PsseVoltageSourceConverterDcTransmissionLine psseVscDcTransmissionLine;
    private final PsseVersion version;
    private final NodeBreakerImport nodeBreakerImport;
}