AbstractTapChanger.java

/**
 * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium)
 * 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.iidm.network.Terminal;
import com.powsybl.iidm.network.ValidationException;
import com.powsybl.iidm.network.ValidationUtil;
import com.powsybl.commons.ref.Ref;
import gnu.trove.list.array.TDoubleArrayList;

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

/**
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
abstract class AbstractTapChanger<H extends TapChangerParent, C extends AbstractTapChanger<H, C, S>, S extends TapChangerStepImpl<S>> implements MultiVariantObject {

    protected final Ref<? extends VariantManagerHolder> network;

    protected final H parent;

    protected int lowTapPosition;

    protected Integer relativeNeutralPosition;

    protected List<S> steps;

    private final String type;

    protected final RegulatingPoint regulatingPoint;

    // attributes depending on the variant

    protected final ArrayList<Integer> tapPosition;

    protected final TDoubleArrayList targetDeadband;

    protected AbstractTapChanger(H parent,
                                 int lowTapPosition, List<S> steps, TerminalExt regulationTerminal,
                                 Integer tapPosition, boolean regulating, double targetDeadband, String type) {
        // The Ref object should be the one corresponding to the subnetwork of the tap changer holder
        // (to avoid errors when the subnetwork is detached)
        this.network = parent.getParentNetwork().getRootNetworkRef();
        this.parent = parent;
        this.lowTapPosition = lowTapPosition;
        this.steps = steps;
        steps.forEach(s -> s.setParent(this));
        int variantArraySize = network.get().getVariantManager().getVariantArraySize();
        regulatingPoint = createRegulatingPoint(variantArraySize, regulating);
        regulatingPoint.setRegulatingTerminal(regulationTerminal);
        this.tapPosition = new ArrayList<>(variantArraySize);
        this.targetDeadband = new TDoubleArrayList(variantArraySize);
        for (int i = 0; i < variantArraySize; i++) {
            this.tapPosition.add(tapPosition);
            this.targetDeadband.add(targetDeadband);
        }
        this.type = Objects.requireNonNull(type);
        relativeNeutralPosition = getRelativeNeutralPosition();
    }

    protected abstract RegulatingPoint createRegulatingPoint(int variantArraySize, boolean regulating);

    protected NetworkImpl getNetwork() {
        return parent.getNetwork();
    }

    protected abstract Integer getRelativeNeutralPosition();

    public int getStepCount() {
        return steps.size();
    }

    public int getLowTapPosition() {
        return lowTapPosition;
    }

    public C setLowTapPosition(int lowTapPosition) {
        int oldValue = this.lowTapPosition;
        this.lowTapPosition = lowTapPosition;
        parent.getNetwork().getListeners().notifyUpdate(parent.getTransformer(), () -> getTapChangerAttribute() + ".lowTapPosition", oldValue, lowTapPosition);
        int variantIndex = network.get().getVariantIndex();
        Integer position = tapPosition.get(network.get().getVariantIndex());
        this.tapPosition.set(variantIndex, position != null ? position + (this.lowTapPosition - oldValue) : null);
        return (C) this;
    }

    public int getHighTapPosition() {
        return lowTapPosition + steps.size() - 1;
    }

    public int getTapPosition() {
        Integer position = tapPosition.get(network.get().getVariantIndex());
        if (position == null) {
            throw ValidationUtil.createUndefinedValueGetterException();
        }
        return position;
    }

    public OptionalInt findTapPosition() {
        Integer position = tapPosition.get(network.get().getVariantIndex());
        return position == null ? OptionalInt.empty() : OptionalInt.of(position);
    }

    public OptionalInt getNeutralPosition() {
        return relativeNeutralPosition != null ? OptionalInt.of(lowTapPosition + relativeNeutralPosition) : OptionalInt.empty();
    }

    protected abstract String getTapChangerAttribute();

    public C setTapPosition(int tapPosition) {
        NetworkImpl n = getNetwork();
        if (tapPosition < lowTapPosition
            || tapPosition > getHighTapPosition()) {
            throwIncorrectTapPosition(tapPosition, getHighTapPosition());
        }
        int variantIndex = n.getVariantIndex();
        Integer oldValue = this.tapPosition.set(variantIndex, tapPosition);
        String variantId = n.getVariantManager().getVariantId(variantIndex);
        n.invalidateValidationLevel();
        parent.getNetwork().getListeners().notifyUpdate(parent.getTransformer(), () -> getTapChangerAttribute() + ".tapPosition", variantId, oldValue, tapPosition);
        return (C) this;
    }

    public C unsetTapPosition() {
        NetworkImpl n = getNetwork();
        ValidationUtil.throwExceptionOrIgnore(parent, "tap position has been unset", n.getMinValidationLevel());
        int variantIndex = network.get().getVariantIndex();
        Integer oldValue = this.tapPosition.set(variantIndex, null);
        String variantId = network.get().getVariantManager().getVariantId(variantIndex);
        n.invalidateValidationLevel();
        n.getListeners().notifyUpdate(parent.getTransformer(), () -> getTapChangerAttribute() + ".tapPosition", variantId, oldValue, null);
        return (C) this;
    }

    public S getStep(int tapPosition) {
        if (tapPosition < lowTapPosition || tapPosition > getHighTapPosition()) {
            throwIncorrectTapPosition(tapPosition, getHighTapPosition());
        }
        return steps.get(tapPosition - lowTapPosition);
    }

    protected C setSteps(List<S> steps) {
        if (steps == null || steps.isEmpty()) {
            throw new ValidationException(parent, "a tap changer shall have at least one step");
        }
        steps.forEach(step -> step.validate(parent));

        // We check if the tap position is still correct
        int newHighTapPosition = lowTapPosition + steps.size() - 1;
        if (getTapPosition() > newHighTapPosition) {
            throwIncorrectTapPosition(getTapPosition(), newHighTapPosition);
        }

        this.steps = steps;
        this.relativeNeutralPosition = getRelativeNeutralPosition();
        return (C) this;
    }

    public S getCurrentStep() {
        Integer position = tapPosition.get(network.get().getVariantIndex());
        if (position == null) {
            return null;
        }
        return getStep(position);
    }

    public boolean isRegulating() {
        return regulatingPoint.isRegulating(network.get().getVariantIndex());
    }

    public C setRegulating(boolean regulating) {
        NetworkImpl n = getNetwork();
        int variantIndex = network.get().getVariantIndex();
        ValidationUtil.checkTargetDeadband(parent, type, regulating, targetDeadband.get(variantIndex), n.getMinValidationLevel(), n.getReportNodeContext().getReportNode());
        boolean oldValue = regulatingPoint.setRegulating(variantIndex, regulating);
        String variantId = network.get().getVariantManager().getVariantId(variantIndex);
        n.invalidateValidationLevel();
        n.getListeners().notifyUpdate(parent.getTransformer(), () -> getTapChangerAttribute() + ".regulating", variantId, oldValue, regulating);
        return (C) this;
    }

    public TerminalExt getRegulationTerminal() {
        return regulatingPoint.getRegulatingTerminal();
    }

    public C setRegulationTerminal(Terminal regulationTerminal) {
        if (regulationTerminal != null && ((TerminalExt) regulationTerminal).getVoltageLevel().getNetwork() != getNetwork()) {
            throw new ValidationException(parent, "regulation terminal is not part of the network");
        }
        Terminal oldValue = regulatingPoint.getRegulatingTerminal();
        regulatingPoint.setRegulatingTerminal((TerminalExt) regulationTerminal);
        getNetwork().getListeners().notifyUpdate(parent.getTransformer(), () -> getTapChangerAttribute() + ".regulationTerminal", oldValue, regulationTerminal);
        return (C) this;
    }

    public double getTargetDeadband() {
        return targetDeadband.get(network.get().getVariantIndex());
    }

    public C setTargetDeadband(double targetDeadband) {
        int variantIndex = network.get().getVariantIndex();
        NetworkImpl n = getNetwork();
        ValidationUtil.checkTargetDeadband(parent, type, regulatingPoint.isRegulating(variantIndex),
                targetDeadband, n.getMinValidationLevel(), n.getReportNodeContext().getReportNode());
        double oldValue = this.targetDeadband.set(variantIndex, targetDeadband);
        String variantId = network.get().getVariantManager().getVariantId(variantIndex);
        n.invalidateValidationLevel();
        n.getListeners().notifyUpdate(parent.getTransformer(), () -> getTapChangerAttribute() + ".targetDeadband", variantId, oldValue, targetDeadband);
        return (C) this;
    }

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

    @Override
    public void reduceVariantArraySize(int number) {
        List<Integer> tmpInt = new ArrayList<>(tapPosition.subList(0, tapPosition.size() - number));
        tapPosition.clear();
        tapPosition.addAll(tmpInt);
        targetDeadband.remove(targetDeadband.size() - number, number);
        regulatingPoint.reduceVariantArraySize(number);
    }

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

    @Override
    public void allocateVariantArrayElement(int[] indexes, final int sourceIndex) {
        for (int index : indexes) {
            tapPosition.set(index, tapPosition.get(sourceIndex));
            targetDeadband.set(index, targetDeadband.get(sourceIndex));
        }
        regulatingPoint.allocateVariantArrayElement(indexes, sourceIndex);
    }

    private void throwIncorrectTapPosition(int tapPosition, int highTapPosition) {
        throw new ValidationException(parent, "incorrect tap position "
            + tapPosition + " [" + lowTapPosition + ", " + highTapPosition
            + "]");
    }

    public void remove() {
        regulatingPoint.remove();
    }
}