DefaultNetworkReducer.java

/**
 * Copyright (c) 2019, 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.iidm.reducer;

import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.ActivePowerControlAdder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * @author Mathieu Bague {@literal <mathieu.bague at rte-france.com>}
 */
public class DefaultNetworkReducer extends AbstractNetworkReducer {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultNetworkReducer.class);

    private final ReductionOptions options;

    private final List<NetworkReducerObserver> observers = new ArrayList<>();

    public DefaultNetworkReducer(NetworkPredicate predicate, ReductionOptions options) {
        this(predicate, options, Collections.emptyList());
    }

    public DefaultNetworkReducer(NetworkPredicate predicate, ReductionOptions options, NetworkReducerObserver... observers) {
        this(predicate, options, Arrays.asList(observers));
    }

    public DefaultNetworkReducer(NetworkPredicate predicate, ReductionOptions options, List<NetworkReducerObserver> observers) {
        super(predicate);
        this.options = Objects.requireNonNull(options);
        this.observers.addAll(Objects.requireNonNull(observers));
    }

    @Override
    protected void reduce(Substation substation) {
        substation.remove();
        observers.forEach(o -> o.substationRemoved(substation));
    }

    @Override
    protected void reduce(VoltageLevel voltageLevel) {
        voltageLevel.remove();
        observers.forEach(o -> o.voltageLevelRemoved(voltageLevel));
    }

    @Override
    protected void reduce(Line line) {
        Terminal terminal1 = line.getTerminal1();
        Terminal terminal2 = line.getTerminal2();
        VoltageLevel vl1 = terminal1.getVoltageLevel();
        VoltageLevel vl2 = terminal2.getVoltageLevel();

        if (test(vl1)) {
            reduce(line, vl1, terminal1);
        } else if (test(vl2)) {
            reduce(line, vl2, terminal2);
        } else {
            line.remove();
        }

        observers.forEach(o -> o.lineRemoved(line));
    }

    @Override
    protected void reduce(TieLine tieLine) {
        Terminal terminal1 = tieLine.getTerminal1();
        Terminal terminal2 = tieLine.getTerminal2();
        VoltageLevel vl1 = terminal1.getVoltageLevel();
        VoltageLevel vl2 = terminal2.getVoltageLevel();

        // just remove the tie line if one of its voltage levels has to be removed
        if (!test(vl1) || !test(vl2)) {
            tieLine.remove();
        }

        observers.forEach(o -> o.tieLineRemoved(tieLine));
    }

    @Override
    protected void reduce(TwoWindingsTransformer transformer) {
        Terminal terminal1 = transformer.getTerminal1();
        Terminal terminal2 = transformer.getTerminal2();
        VoltageLevel vl1 = terminal1.getVoltageLevel();
        VoltageLevel vl2 = terminal2.getVoltageLevel();

        if (test(vl1)) {
            replaceTransformerByLoad(transformer, vl1, terminal1);
        } else if (test(vl2)) {
            replaceTransformerByLoad(transformer, vl2, terminal2);
        } else {
            transformer.remove();

        }

        observers.forEach(o -> o.transformerRemoved(transformer));
    }

    @Override
    protected void reduce(ThreeWindingsTransformer transformer) {
        Terminal terminal1 = transformer.getLeg1().getTerminal();
        Terminal terminal2 = transformer.getLeg2().getTerminal();
        Terminal terminal3 = transformer.getLeg3().getTerminal();
        VoltageLevel vl1 = terminal1.getVoltageLevel();
        VoltageLevel vl2 = terminal2.getVoltageLevel();
        VoltageLevel vl3 = terminal3.getVoltageLevel();

        if (test(vl1) ^ test(vl2) ^ test(vl3)) {
            VoltageLevel vlToKeep;
            Terminal terminal;
            if (test(vl1)) {
                vlToKeep = vl1;
                terminal = terminal1;
            } else {
                vlToKeep = test(vl2) ? vl2 : vl3;
                terminal = test(vl2) ? terminal2 : terminal3;
            }
            replaceTransformerByLoad(transformer, vlToKeep, terminal);
        } else if (!(test(vl1) || test(vl2) || test(vl3))) {
            transformer.remove();
        } else {
            throw new UnsupportedOperationException("Keeping only 2 legs of the 3 windings transformer " + transformer.getId() +
                    " is not possible : the third one should have also been kept by reduction.");
        }
        observers.forEach(o -> o.transformerRemoved(transformer));
    }

    @Override
    protected void reduce(HvdcLine hvdcLine) {
        Terminal terminal1 = hvdcLine.getConverterStation1().getTerminal();
        Terminal terminal2 = hvdcLine.getConverterStation2().getTerminal();
        VoltageLevel vl1 = terminal1.getVoltageLevel();
        VoltageLevel vl2 = terminal2.getVoltageLevel();
        HvdcConverterStation<?> station1 = hvdcLine.getConverterStation1();
        HvdcConverterStation<?> station2 = hvdcLine.getConverterStation2();
        if (test(vl1)) {
            replaceHvdcLine(hvdcLine, vl1, terminal1, station1);
        } else if (test(vl2)) {
            replaceHvdcLine(hvdcLine, vl2, terminal2, station2);
        } else {
            hvdcLine.remove();
            station1.remove();
            station2.remove();
        }
        observers.forEach(o -> o.hvdcLineRemoved(hvdcLine));
    }

    private void reduce(Line line, VoltageLevel vl, Terminal terminal) {
        if (options.isWithDanglingLines()) {
            replaceLineByDanglingLine(line, vl, terminal);
        } else {
            replaceLineByLoad(line, vl, terminal);
        }
    }

    private void replaceLineByLoad(Line line, VoltageLevel vl, Terminal terminal) {
        Load load = replaceConnectableByLoad(line, vl, terminal);
        observers.forEach(o -> o.lineReplaced(line, load));
    }

    private void replaceLineByDanglingLine(Line line, VoltageLevel vl, Terminal terminal) {
        TwoSides side = line.getSide(terminal);

        DanglingLineAdder dlAdder = vl.newDanglingLine()
                .setId(line.getId())
                .setName(line.getOptionalName().orElse(null))
                .setR(line.getR() / 2)
                .setX(line.getX() / 2)
                .setB(side == TwoSides.ONE ? line.getB1() : line.getB2())
                .setG(side == TwoSides.ONE ? line.getG1() : line.getG2())
                .setP0(checkP(terminal))
                .setQ0(checkQ(terminal));
        fillNodeOrBus(dlAdder, terminal);

        double p = terminal.getP();
        double q = terminal.getQ();
        line.remove();

        DanglingLine dl = dlAdder.add();
        dl.getTerminal()
                .setP(p)
                .setQ(q);

        observers.forEach(o -> o.lineReplaced(line, dl));
    }

    private void replaceTransformerByLoad(TwoWindingsTransformer transformer, VoltageLevel vl, Terminal terminal) {
        Load load = replaceConnectableByLoad(transformer, vl, terminal);
        observers.forEach(o -> o.transformerReplaced(transformer, load));
    }

    private void replaceTransformerByLoad(ThreeWindingsTransformer transformer, VoltageLevel vl, Terminal terminal) {
        Load load = replaceConnectableByLoad(transformer, vl, terminal);
        observers.forEach(o -> o.transformerReplaced(transformer, load));
    }

    private Load replaceConnectableByLoad(Connectable<?> connectable, VoltageLevel vl, Terminal terminal) {
        LoadAdder loadAdder = vl.newLoad()
                .setId(connectable.getId())
                .setName(connectable.getOptionalName().orElse(null))
                .setLoadType(LoadType.FICTITIOUS)
                .setP0(checkP(terminal))
                .setQ0(checkQ(terminal));
        fillNodeOrBus(loadAdder, terminal);

        double p = terminal.getP();
        double q = terminal.getQ();
        connectable.remove();

        Load load = loadAdder.add();
        load.getTerminal()
                .setP(p)
                .setQ(q);

        return load;
    }

    private void replaceHvdcLine(HvdcLine hvdcLine, VoltageLevel vl, Terminal terminal, HvdcConverterStation<?> station) {
        if (station.getHvdcType() == HvdcConverterStation.HvdcType.VSC) {
            VscConverterStation vscStation = (VscConverterStation) station;
            if (vscStation.isVoltageRegulatorOn()) {
                replaceHvdcLineByGenerator(hvdcLine, vl, terminal, vscStation);
            } else {
                replaceHvdcLineByLoad(hvdcLine, vl, terminal);
            }
        } else {
            replaceHvdcLineByLoad(hvdcLine, vl, terminal);
        }
    }

    private void replaceHvdcLineByLoad(HvdcLine hvdcLine, VoltageLevel vl, Terminal terminal) {
        LoadAdder loadAdder = vl.newLoad()
                .setId(hvdcLine.getId())
                .setName(hvdcLine.getOptionalName().orElse(null))
                .setLoadType(LoadType.FICTITIOUS)
                .setP0(checkP(terminal))
                .setQ0(checkQ(terminal));
        fillNodeOrBus(loadAdder, terminal);

        double p = terminal.getP();
        double q = terminal.getQ();
        HvdcConverterStation<?> converter1 = hvdcLine.getConverterStation1();
        HvdcConverterStation<?> converter2 = hvdcLine.getConverterStation2();
        hvdcLine.remove();
        converter1.remove();
        converter2.remove();

        Load load = loadAdder.add();
        load.getTerminal()
                .setP(p)
                .setQ(q);
        observers.forEach(o -> o.hvdcLineReplaced(hvdcLine, load));
    }

    private void replaceHvdcLineByGenerator(HvdcLine hvdcLine, VoltageLevel vl, Terminal terminal, VscConverterStation station) {
        double maxP = hvdcLine.getMaxP();
        GeneratorAdder genAdder = vl.newGenerator()
                .setId(hvdcLine.getId())
                .setName(hvdcLine.getOptionalName().orElse(null))
                .setEnergySource(EnergySource.OTHER)
                .setVoltageRegulatorOn(true)
                .setMaxP(maxP)
                .setMinP(-maxP)
                .setTargetP(-checkP(terminal))
                .setTargetV(station.getVoltageSetpoint());
        fillNodeOrBus(genAdder, terminal);

        double p = terminal.getP();
        double q = terminal.getQ();
        ReactiveLimits stationLimits = station.getReactiveLimits();

        HvdcConverterStation<?> converter1 = hvdcLine.getConverterStation1();
        HvdcConverterStation<?> converter2 = hvdcLine.getConverterStation2();
        hvdcLine.remove();
        converter1.remove();
        converter2.remove();

        Generator generator = genAdder.add();
        generator.getTerminal()
                .setP(p)
                .setQ(q);

        if (stationLimits != null) {
            if (stationLimits.getKind() == ReactiveLimitsKind.MIN_MAX) {
                MinMaxReactiveLimits minMaxLimits = (MinMaxReactiveLimits) stationLimits;
                generator.newMinMaxReactiveLimits()
                        .setMinQ(minMaxLimits.getMinQ())
                        .setMaxQ(minMaxLimits.getMaxQ())
                        .add();
            } else if (stationLimits.getKind() == ReactiveLimitsKind.CURVE) {
                ReactiveCapabilityCurve reactiveCurve = (ReactiveCapabilityCurve) stationLimits;
                ReactiveCapabilityCurveAdder curveAdder = generator.newReactiveCapabilityCurve();
                reactiveCurve.getPoints().forEach(point ->
                    curveAdder.beginPoint()
                            .setP(point.getP())
                            .setMinQ(point.getMinQ())
                            .setMaxQ(point.getMaxQ())
                            .endPoint()
                );
                curveAdder.add();
            }
        }

        generator.newExtension(ActivePowerControlAdder.class).withParticipate(false).add();

        observers.forEach(o -> o.hvdcLineReplaced(hvdcLine, generator));
    }

    private static void fillNodeOrBus(InjectionAdder<?, ?> adder, Terminal terminal) {
        if (terminal.getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) {
            adder.setNode(terminal.getNodeBreakerView().getNode());
        } else {
            if (terminal.isConnected()) {
                adder.setBus(terminal.getBusBreakerView().getBus().getId());
            }
            adder.setConnectableBus(terminal.getBusBreakerView().getConnectableBus().getId());
        }
    }

    private static double checkP(Terminal terminal) {
        if (!terminal.isConnected()) {
            return 0.0;
        }
        if (Double.isNaN(terminal.getP())) {
            String connectableId = terminal.getConnectable().getId();
            String voltageLevelId = terminal.getVoltageLevel().getId();
            LOGGER.warn("The active power of '{}' ({}) is undefined -> set to 0.0", connectableId, voltageLevelId);
            return 0.0;
        }
        return terminal.getP();
    }

    private static double checkQ(Terminal terminal) {
        if (!terminal.isConnected()) {
            return 0.0;
        }
        if (Double.isNaN(terminal.getQ())) {
            String connectableId = terminal.getConnectable().getId();
            String voltageLevelId = terminal.getVoltageLevel().getId();
            LOGGER.warn("The reactive power of '{}' ({}) is undefined -> set to 0.0", connectableId, voltageLevelId);
            return 0.0;
        }
        return terminal.getQ();
    }
}