AbstractAcDcConverter.java

/**
 * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/)
 * 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.network.impl;

import com.powsybl.commons.ref.Ref;
import com.powsybl.iidm.network.*;
import gnu.trove.list.array.TDoubleArrayList;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * @author Damien Jeandemange {@literal <damien.jeandemange at artelys.com>}
 */
abstract class AbstractAcDcConverter<I extends AcDcConverter<I>> extends AbstractConnectable<I> implements AcDcConverter<I>, MultiVariantObject {

    public static final String IDLE_LOSS = "idleLoss";
    public static final String SWITCHING_LOSS = "switchingLoss";
    public static final String RESISTIVE_LOSS = "resistiveLoss";
    public static final String PCC_TERMINAL = "pccTerminal";
    public static final String CONTROL_MODE = "controlMode";
    public static final String TARGET_P = "targetP";
    public static final String TARGET_VDC = "targetVdc";
    protected final List<DcTerminalImpl> dcTerminals = new ArrayList<>();

    private double idleLoss;
    private double switchingLoss;
    private double resistiveLoss;

    private final RegulatingPoint pccRegulatingPoint;

    // attributes depending on the variant

    private final TDoubleArrayList targetP;

    private final TDoubleArrayList targetVdc;

    AbstractAcDcConverter(Ref<NetworkImpl> ref, String id, String name, boolean fictitious,
                          double idleLoss, double switchingLoss, double resistiveLoss,
                          TerminalExt pccTerminal, ControlMode controlMode, double targetP, double targetVdc) {
        super(ref, id, name, fictitious);
        this.idleLoss = idleLoss;
        this.switchingLoss = switchingLoss;
        this.resistiveLoss = resistiveLoss;
        int variantArraySize = ref.get().getVariantManager().getVariantArraySize();
        this.targetP = new TDoubleArrayList(variantArraySize);
        this.targetVdc = new TDoubleArrayList(variantArraySize);
        pccRegulatingPoint = new RegulatingPoint(id, () -> null, variantArraySize, controlMode.ordinal(), ControlMode.V_DC.ordinal(), false);
        pccRegulatingPoint.setRegulatingTerminal(pccTerminal);
        for (int i = 0; i < variantArraySize; i++) {
            this.targetP.add(targetP);
            this.targetVdc.add(targetVdc);
        }
    }

    @Override
    public Terminal getTerminal1() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, "terminal1");
        return getTerminals().get(0);
    }

    @Override
    public Optional<Terminal> getTerminal2() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, "terminal2");
        if (terminals.size() > 1) {
            return Optional.of(terminals.get(1));
        }
        return Optional.empty();
    }

    @Override
    public TwoSides getSide(Terminal terminal) {
        Objects.requireNonNull(terminal);
        if (getTerminal1() == terminal) {
            return TwoSides.ONE;
        } else if (getTerminal2().orElse(null) == terminal) {
            return TwoSides.TWO;
        } else {
            throw new IllegalStateException("The terminal is not connected to this AC/DC converter");
        }
    }

    @Override
    public Terminal getTerminal(TwoSides side) {
        Objects.requireNonNull(side);
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, "terminal");
        if (side == TwoSides.ONE) {
            return getTerminal1();
        } else if (side == TwoSides.TWO) {
            return getTerminal2().orElseThrow(() -> new IllegalStateException("This AC/DC converter does not have a second AC Terminal"));
        }
        throw new IllegalStateException("Unexpected side: " + side);
    }

    @Override
    public DcTerminal getDcTerminal1() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, "dcTerminal1");
        return this.dcTerminals.get(0);
    }

    @Override
    public DcTerminal getDcTerminal2() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, "dcTerminal2");
        return this.dcTerminals.get(1);
    }

    @Override
    public TwoSides getSide(DcTerminal dcTerminal) {
        Objects.requireNonNull(dcTerminal);
        if (getDcTerminal1() == dcTerminal) {
            return TwoSides.ONE;
        } else if (getDcTerminal2() == dcTerminal) {
            return TwoSides.TWO;
        } else {
            throw new IllegalStateException("The DC terminal is not connected to this AC/DC converter");
        }
    }

    @Override
    public DcTerminal getDcTerminal(TwoSides side) {
        Objects.requireNonNull(side);
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, "terminal");
        if (side == TwoSides.ONE) {
            return this.dcTerminals.get(0);
        } else if (side == TwoSides.TWO) {
            return this.dcTerminals.get(1);
        }
        throw new IllegalStateException("Unexpected side: " + side);
    }

    @Override
    public List<DcTerminal> getDcTerminals() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, "dcTerminals");
        return List.copyOf(dcTerminals);
    }

    void addDcTerminal(DcTerminalImpl dcTerminal) {
        this.dcTerminals.add(dcTerminal);
        dcTerminal.setDcConnectable(this);
    }

    @Override
    public double getIdleLoss() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, IDLE_LOSS);
        return this.idleLoss;
    }

    @Override
    public I setIdleLoss(double idleLoss) {
        ValidationUtil.checkModifyOfRemovedEquipment(this.id, this.removed, IDLE_LOSS);
        ValidationUtil.checkDoubleParamPositive(this, idleLoss, IDLE_LOSS);
        double oldValue = this.idleLoss;
        this.idleLoss = idleLoss;
        getNetwork().getListeners().notifyUpdate(this, IDLE_LOSS, oldValue, idleLoss);
        return self();
    }

    @Override
    public double getSwitchingLoss() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, SWITCHING_LOSS);
        return this.switchingLoss;
    }

    @Override
    public I setSwitchingLoss(double switchingLoss) {
        ValidationUtil.checkModifyOfRemovedEquipment(this.id, this.removed, SWITCHING_LOSS);
        ValidationUtil.checkDoubleParamPositive(this, switchingLoss, SWITCHING_LOSS);
        double oldValue = this.switchingLoss;
        this.switchingLoss = switchingLoss;
        getNetwork().getListeners().notifyUpdate(this, SWITCHING_LOSS, oldValue, switchingLoss);
        return self();
    }

    @Override
    public double getResistiveLoss() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, RESISTIVE_LOSS);
        return this.resistiveLoss;
    }

    @Override
    public I setResistiveLoss(double resistiveLoss) {
        ValidationUtil.checkModifyOfRemovedEquipment(this.id, this.removed, RESISTIVE_LOSS);
        ValidationUtil.checkDoubleParamPositive(this, resistiveLoss, RESISTIVE_LOSS);
        double oldValue = this.resistiveLoss;
        this.resistiveLoss = resistiveLoss;
        getNetwork().getListeners().notifyUpdate(this, RESISTIVE_LOSS, oldValue, resistiveLoss);
        return self();
    }

    @Override
    public I setPccTerminal(Terminal pccTerminal) {
        Objects.requireNonNull(pccTerminal);
        ValidationUtil.checkModifyOfRemovedEquipment(this.id, this.removed, PCC_TERMINAL);
        ValidationUtil.checkAcDcConverterPccTerminal(this, getTerminal2().isPresent(), pccTerminal, getTerminal1().getVoltageLevel());
        Terminal oldValue = pccRegulatingPoint.getRegulatingTerminal();
        pccRegulatingPoint.setRegulatingTerminal((TerminalExt) pccTerminal);
        notifyUpdate(PCC_TERMINAL, oldValue, pccRegulatingPoint.getRegulatingTerminal());
        return self();
    }

    @Override
    public Terminal getPccTerminal() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, PCC_TERMINAL);
        return pccRegulatingPoint.getRegulatingTerminal();
    }

    @Override
    public I setControlMode(ControlMode controlMode) {
        Objects.requireNonNull(controlMode);
        ValidationUtil.checkModifyOfRemovedEquipment(this.id, this.removed, CONTROL_MODE);
        NetworkImpl n = getNetwork();
        ValidationUtil.checkAcDcConverterControl(this, controlMode, getTargetP(), getTargetVdc(),
                n.getMinValidationLevel(), n.getReportNodeContext().getReportNode());
        int variantIndex = n.getVariantIndex();
        int oldValueOrdinal = pccRegulatingPoint.setRegulationMode(variantIndex, controlMode.ordinal());
        String variantId = n.getVariantManager().getVariantId(variantIndex);
        notifyUpdate(CONTROL_MODE, variantId, ControlMode.values()[oldValueOrdinal], controlMode);
        return self();
    }

    @Override
    public ControlMode getControlMode() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, CONTROL_MODE);
        int variantIndex = getNetwork().getVariantIndex();
        return ControlMode.values()[pccRegulatingPoint.getRegulationMode(variantIndex)];
    }

    @Override
    public I setTargetP(double targetP) {
        ValidationUtil.checkModifyOfRemovedEquipment(this.id, this.removed, TARGET_P);
        NetworkImpl n = getNetwork();
        ValidationUtil.checkAcDcConverterControl(this, getControlMode(), targetP, getTargetVdc(),
                n.getMinValidationLevel(), n.getReportNodeContext().getReportNode());
        int variantIndex = n.getVariantIndex();
        double oldValue = this.targetP.set(variantIndex, targetP);
        String variantId = n.getVariantManager().getVariantId(variantIndex);
        n.invalidateValidationLevel();
        notifyUpdate(TARGET_P, variantId, oldValue, targetP);
        return self();
    }

    @Override
    public double getTargetP() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, TARGET_P);
        return targetP.get(getNetwork().getVariantIndex());
    }

    @Override
    public I setTargetVdc(double targetVdc) {
        ValidationUtil.checkModifyOfRemovedEquipment(this.id, this.removed, TARGET_VDC);
        NetworkImpl n = getNetwork();
        ValidationUtil.checkAcDcConverterControl(this, getControlMode(), getTargetP(), targetVdc,
                n.getMinValidationLevel(), n.getReportNodeContext().getReportNode());
        int variantIndex = n.getVariantIndex();
        double oldValue = this.targetVdc.set(variantIndex, targetVdc);
        String variantId = n.getVariantManager().getVariantId(variantIndex);
        n.invalidateValidationLevel();
        notifyUpdate(TARGET_VDC, variantId, oldValue, targetVdc);
        return self();
    }

    @Override
    public double getTargetVdc() {
        ValidationUtil.checkAccessOfRemovedEquipment(this.id, this.removed, TARGET_VDC);
        return targetVdc.get(getNetwork().getVariantIndex());
    }

    @Override
    public void extendVariantArraySize(int initVariantArraySize, int number, int sourceIndex) {
        super.extendVariantArraySize(initVariantArraySize, number, sourceIndex);
        targetP.ensureCapacity(targetP.size() + number);
        targetVdc.ensureCapacity(targetVdc.size() + number);
        for (int i = 0; i < number; i++) {
            targetP.add(targetP.get(sourceIndex));
            targetVdc.add(targetVdc.get(sourceIndex));
        }

        for (DcTerminalImpl t : dcTerminals) {
            t.extendVariantArraySize(initVariantArraySize, number, sourceIndex);
        }
        pccRegulatingPoint.extendVariantArraySize(initVariantArraySize, number, sourceIndex);
    }

    @Override
    public void reduceVariantArraySize(int number) {
        super.reduceVariantArraySize(number);
        targetP.remove(targetP.size() - number, number);
        targetVdc.remove(targetVdc.size() - number, number);

        for (DcTerminalImpl t : dcTerminals) {
            t.reduceVariantArraySize(number);
        }
        pccRegulatingPoint.reduceVariantArraySize(number);
    }

    @Override
    public void deleteVariantArrayElement(int index) {
        super.deleteVariantArrayElement(index);

        for (DcTerminalImpl t : dcTerminals) {
            t.deleteVariantArrayElement(index);
        }
        pccRegulatingPoint.deleteVariantArrayElement(index);
    }

    @Override
    public void allocateVariantArrayElement(int[] indexes, int sourceIndex) {
        super.allocateVariantArrayElement(indexes, sourceIndex);
        for (int index : indexes) {
            targetP.set(index, targetP.get(sourceIndex));
            targetVdc.set(index, targetVdc.get(sourceIndex));
        }

        for (DcTerminalImpl t : dcTerminals) {
            t.allocateVariantArrayElement(indexes, sourceIndex);
        }
        pccRegulatingPoint.allocateVariantArrayElement(indexes, sourceIndex);
    }

    @Override
    public void remove() {
        dcTerminals.forEach(DcTerminalImpl::remove);
        pccRegulatingPoint.remove();
        super.remove();
    }

    protected abstract I self();
}