DCLink.java

/**
 * Copyright (c) 2025, 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.dc;

import com.powsybl.cgmes.model.CgmesNames;
import com.powsybl.iidm.network.HvdcLine.ConvertersMode;
import com.powsybl.triplestore.api.PropertyBag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.powsybl.cgmes.model.CgmesNames.DC_LINE_SEGMENT;
import static com.powsybl.iidm.network.HvdcLine.ConvertersMode.*;

/**
 * @author Romain Courtier {@literal <romain.courtier at rte-france.com>}
 */
public class DCLink {

    private ConvertersMode mode;
    private double r;
    private double ratedUdc;
    private double targetP;
    private double pDcInverter;
    private double lossFactor1;
    private double lossFactor2;

    private final PropertyBag converter1;
    private final PropertyBag converter2;
    private final PropertyBag dcLine1;
    private final PropertyBag dcLine2;

    private static final String OPERATING_MODE = "operatingMode";
    private static final String TARGET_PPCC = "targetPpcc";
    private static final String POLE_LOSS_P = "poleLossP";
    private static final Logger LOG = LoggerFactory.getLogger(DCLink.class);

    public DCLink(PropertyBag converter1, PropertyBag converter2, PropertyBag dcLine1, PropertyBag dcLine2) {
        this.converter1 = converter1;
        this.converter2 = converter2;
        this.dcLine1 = dcLine1;
        this.dcLine2 = dcLine2;

        computeMode();
        computeR();
        computeRatedUdc();
        computeActivePowers();
        computeLossFactors();
    }

    private void computeMode() {
        String mode1 = converter1.getLocal(OPERATING_MODE);
        String mode2 = converter2.getLocal(OPERATING_MODE);
        if (isRectifier(mode1) && isInverter(mode2)) {
            mode = SIDE_1_RECTIFIER_SIDE_2_INVERTER;
        } else if (isInverter(mode1) && isRectifier(mode2)) {
            mode = SIDE_1_INVERTER_SIDE_2_RECTIFIER;
        } else if (targetPpcc1() > 0.0 || targetPpcc2() < 0.0) {
            mode = SIDE_1_RECTIFIER_SIDE_2_INVERTER;
        } else if (targetPpcc1() < 0.0 || targetPpcc2() > 0.0) {
            mode = SIDE_1_INVERTER_SIDE_2_RECTIFIER;
        } else {
            mode = SIDE_1_RECTIFIER_SIDE_2_INVERTER;
            String dcLine1Id = dcLine1.getId(DC_LINE_SEGMENT);
            LOG.info("Default mode: {} for DCLink with DCLineSegment: {}.", mode, dcLine1Id);
        }
    }

    private boolean isRectifier(String operatingMode) {
        return operatingMode != null && operatingMode.toLowerCase().endsWith("rectifier");
    }

    private boolean isInverter(String operatingMode) {
        return operatingMode != null && operatingMode.toLowerCase().endsWith("inverter");
    }

    private double targetPpcc1() {
        double targetPpcc = converter1.asDouble(TARGET_PPCC);
        return Double.isNaN(targetPpcc) ? 0.0 : targetPpcc;
    }

    private double targetPpcc2() {
        double targetPpcc = converter2.asDouble(TARGET_PPCC);
        return Double.isNaN(targetPpcc) ? 0.0 : targetPpcc;
    }

    private double pole1Losses() {
        double poleLosses = converter1.asDouble(POLE_LOSS_P);
        return Double.isNaN(poleLosses) ? 0.0 : poleLosses;
    }

    private double pole2Losses() {
        double poleLosses = converter2.asDouble(POLE_LOSS_P);
        return Double.isNaN(poleLosses) ? 0.0 : poleLosses;
    }

    private void computeR() {
        double r1 = dcLine1.asDouble("r");
        r1 = Double.isNaN(r1) ? 0.1 : r1;
        if (dcLine2 == null) {
            r = r1;
        } else {
            double r2 = dcLine2.asDouble("r", 0.1);
            r2 = Double.isNaN(r2) ? 0.1 : r2;
            r = r1 + r2;
        }

        if (r < 0.0) {
            String dcLine1Id = dcLine1.getId(DC_LINE_SEGMENT);
            LOG.warn("Invalid r for DCLink with DCLineSegment: {}. Was: {}, fixed to 0.1.", dcLine1Id, r);
            r = 0.1;
        }
    }

    private void computeRatedUdc() {
        ratedUdc = converter1.asDouble(CgmesNames.RATED_UDC);
        if (ratedUdc == 0.0) {
            ratedUdc = converter2.asDouble(CgmesNames.RATED_UDC);
        }
    }

    private void computeActivePowers() {
        // targetP is AC active power on rectifier side.
        double pDcRectifier;
        if (getTargetPpccRectifier() != 0.0) {
            targetP = getTargetPpccRectifier();
            pDcRectifier = targetP - getPoleLossesRectifier();
            pDcInverter = -1 * (pDcRectifier - resistiveLossesFromPdcRectifier(pDcRectifier));
        } else if (getTargetPpccInverter() != 0.0) {
            pDcInverter = -1 * (Math.abs(getTargetPpccInverter()) + getPoleLossesInverter());
            pDcRectifier = Math.abs(pDcInverter) + resistiveLossesFromPdcInverter(pDcInverter);
            targetP = pDcRectifier + getPoleLossesRectifier();
        } else {
            targetP = 0.0;
            pDcInverter = 0.0;
        }
    }

    private double getTargetPpccRectifier() {
        if (mode == SIDE_1_RECTIFIER_SIDE_2_INVERTER) {
            return targetPpcc1();
        }
        return targetPpcc2();
    }

    private double getTargetPpccInverter() {
        if (mode == SIDE_1_RECTIFIER_SIDE_2_INVERTER) {
            return targetPpcc2();
        }
        return targetPpcc1();
    }

    private double getPoleLossesRectifier() {
        if (mode == SIDE_1_RECTIFIER_SIDE_2_INVERTER) {
            return pole1Losses();
        }
        return pole2Losses();
    }

    private double getPoleLossesInverter() {
        if (mode == SIDE_1_RECTIFIER_SIDE_2_INVERTER) {
            return pole2Losses();
        }
        return pole1Losses();
    }

    private double resistiveLossesFromPdcRectifier(double pDcRectifier) {
        double idc = ratedUdc != 0.0 ? pDcRectifier / ratedUdc : 0.0;
        return r * idc * idc;
    }

    private double resistiveLossesFromPdcInverter(double pDcInverter) {
        double idc = r != 0.0 ? (ratedUdc - Math.sqrt(ratedUdc * ratedUdc - 4 * r * Math.abs(pDcInverter))) / (2 * r) : 0.0;
        return r * idc * idc;
    }

    private void computeLossFactors() {
        // Loss factor is pole losses divided by incoming power.
        if (targetP == 0.0) {
            lossFactor1 = 0.0;
            lossFactor2 = 0.0;
        } else if (mode == SIDE_1_RECTIFIER_SIDE_2_INVERTER) {
            lossFactor1 = getPoleLossesRectifier() / targetP * 100;
            lossFactor2 = getPoleLossesInverter() / Math.abs(pDcInverter) * 100;
        } else {
            lossFactor1 = getPoleLossesInverter() / Math.abs(pDcInverter) * 100;
            lossFactor2 = getPoleLossesRectifier() / targetP * 100;
        }
    }

    public ConvertersMode getMode() {
        return mode;
    }

    public double getR() {
        return r;
    }

    public double getRatedUdc() {
        return ratedUdc;
    }

    public double getTargetP() {
        return targetP;
    }

    public double getLossFactor1() {
        return lossFactor1;
    }

    public double getLossFactor2() {
        return lossFactor2;
    }

    public PropertyBag getConverter1() {
        return converter1;
    }

    public PropertyBag getConverter2() {
        return converter2;
    }

    public PropertyBag getDcLine1() {
        return dcLine1;
    }

    public PropertyBag getDcLine2() {
        return dcLine2;
    }
}