All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.powsybl.iidm.modification.topology.TopologyModificationUtils Maven / Gradle / Ivy

There is a newer version: 6.5.0-RC1
Show newest version
/**
 * Copyright (c) 2022, 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.modification.topology;

import com.google.common.collect.ImmutableList;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.commons.report.TypedValue;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.BusbarSectionPosition;
import com.powsybl.iidm.network.extensions.ConnectablePosition;
import com.powsybl.math.graph.TraverseResult;
import org.apache.commons.lang3.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.powsybl.iidm.modification.util.ModificationReports.*;

/**
 * @author Miora Vedelago {@literal }
 */
public final class TopologyModificationUtils {

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

    private TopologyModificationUtils() {
    }

    public static final class LoadingLimitsBags {

        private final LoadingLimitsBag activePowerLimits;
        private final LoadingLimitsBag apparentPowerLimits;
        private final LoadingLimitsBag currentLimits;

        public LoadingLimitsBags(Supplier> activePowerLimitsGetter, Supplier> apparentPowerLimitsGetter,
                          Supplier> currentLimitsGetter) {
            activePowerLimits = activePowerLimitsGetter.get().map(LoadingLimitsBag::new).orElse(null);
            apparentPowerLimits = apparentPowerLimitsGetter.get().map(LoadingLimitsBag::new).orElse(null);
            currentLimits = currentLimitsGetter.get().map(LoadingLimitsBag::new).orElse(null);
        }

        LoadingLimitsBags(LoadingLimitsBag activePowerLimits, LoadingLimitsBag apparentPowerLimits, LoadingLimitsBag currentLimits) {
            this.activePowerLimits = activePowerLimits;
            this.apparentPowerLimits = apparentPowerLimits;
            this.currentLimits = currentLimits;
        }

        Optional getActivePowerLimits() {
            return Optional.ofNullable(activePowerLimits);
        }

        Optional getApparentPowerLimits() {
            return Optional.ofNullable(apparentPowerLimits);
        }

        Optional getCurrentLimits() {
            return Optional.ofNullable(currentLimits);
        }
    }

    private static final class LoadingLimitsBag {

        private final double permanentLimit;
        private List temporaryLimits = new ArrayList<>();

        private LoadingLimitsBag(LoadingLimits limits) {
            this.permanentLimit = limits.getPermanentLimit();
            for (LoadingLimits.TemporaryLimit tl : limits.getTemporaryLimits()) {
                temporaryLimits.add(new TemporaryLimitsBag(tl));
            }
        }

        private LoadingLimitsBag(double permanentLimit, List temporaryLimitsBags) {
            this.permanentLimit = permanentLimit;
            this.temporaryLimits = temporaryLimitsBags;
        }

        private double getPermanentLimit() {
            return permanentLimit;
        }

        private List getTemporaryLimits() {
            return ImmutableList.copyOf(temporaryLimits);
        }
    }

    private static final class TemporaryLimitsBag {

        private final String name;
        private final int acceptableDuration;
        private final boolean fictitious;
        private final double value;

        TemporaryLimitsBag(LoadingLimits.TemporaryLimit temporaryLimit) {
            this.name = temporaryLimit.getName();
            this.acceptableDuration = temporaryLimit.getAcceptableDuration();
            this.fictitious = temporaryLimit.isFictitious();
            this.value = temporaryLimit.getValue();
        }

        private String getName() {
            return name;
        }

        private int getAcceptableDuration() {
            return acceptableDuration;
        }

        private boolean isFictitious() {
            return fictitious;
        }

        private double getValue() {
            return value;
        }
    }

    static LineAdder createLineAdder(double percent, String id, String name, String voltageLevelId1, String voltageLevelId2, Network network, Line line) {
        return network.newLine()
                .setId(id)
                .setName(name)
                .setVoltageLevel1(voltageLevelId1)
                .setVoltageLevel2(voltageLevelId2)
                .setR(line.getR() * percent / 100)
                .setX(line.getX() * percent / 100)
                .setG1(line.getG1() * percent / 100)
                .setB1(line.getB1() * percent / 100)
                .setG2(line.getG2() * percent / 100)
                .setB2(line.getB2() * percent / 100);
    }

    static LineAdder createLineAdder(String id, String name, String voltageLevelId1, String voltageLevelId2, Network network, Line line1, Line line2) {
        return network.newLine()
                .setId(id)
                .setName(name)
                .setVoltageLevel1(voltageLevelId1)
                .setVoltageLevel2(voltageLevelId2)
                .setR(line1.getR() + line2.getR())
                .setX(line1.getX() + line2.getX())
                .setG1(line1.getG1() + line2.getG1())
                .setB1(line1.getB1() + line2.getB1())
                .setG2(line1.getG2() + line2.getG2())
                .setB2(line1.getB2() + line2.getB2());
    }

    static void attachLine(Terminal terminal, LineAdder adder, BiConsumer connectableBusSetter,
                           BiConsumer busSetter, BiConsumer nodeSetter) {
        if (terminal.getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) {
            connectableBusSetter.accept(terminal.getBusBreakerView().getConnectableBus(), adder);
            Bus bus = terminal.getBusBreakerView().getBus();
            if (bus != null) {
                busSetter.accept(bus, adder);
            }
        } else if (terminal.getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) {
            int node = terminal.getNodeBreakerView().getNode();
            nodeSetter.accept(node, adder);
        } else {
            throw new IllegalStateException();
        }
    }

    public static void addLoadingLimits(Line created, LoadingLimitsBags limits, TwoSides side) {
        if (side == TwoSides.ONE) {
            limits.getActivePowerLimits().ifPresent(lim -> addLoadingLimits(created.newActivePowerLimits1(), lim));
            limits.getApparentPowerLimits().ifPresent(lim -> addLoadingLimits(created.newApparentPowerLimits1(), lim));
            limits.getCurrentLimits().ifPresent(lim -> addLoadingLimits(created.newCurrentLimits1(), lim));
        } else {
            limits.getActivePowerLimits().ifPresent(lim -> addLoadingLimits(created.newActivePowerLimits2(), lim));
            limits.getApparentPowerLimits().ifPresent(lim -> addLoadingLimits(created.newApparentPowerLimits2(), lim));
            limits.getCurrentLimits().ifPresent(lim -> addLoadingLimits(created.newCurrentLimits2(), lim));
        }
    }

    private static > void addLoadingLimits(A adder, LoadingLimitsBag limits) {
        adder.setPermanentLimit(limits.getPermanentLimit());
        for (TemporaryLimitsBag tl : limits.getTemporaryLimits()) {
            adder.beginTemporaryLimit()
                    .setName(tl.getName())
                    .setAcceptableDuration(tl.getAcceptableDuration())
                    .setFictitious(tl.isFictitious())
                    .setValue(tl.getValue())
                    .endTemporaryLimit();
        }
        adder.add();
    }

    static void removeVoltageLevelAndSubstation(VoltageLevel voltageLevel, ReportNode reportNode) {
        Optional substation = voltageLevel.getSubstation();
        String vlId = voltageLevel.getId();
        boolean noMoreEquipments = voltageLevel.getConnectableStream().noneMatch(c -> c.getType() != IdentifiableType.BUSBAR_SECTION);
        if (!noMoreEquipments) {
            voltageLevelRemovingEquipmentsLeftReport(reportNode, vlId);
            LOGGER.warn("Voltage level {} still contains equipments", vlId);
        }
        voltageLevel.remove();
        voltageLevelRemovedReport(reportNode, vlId);
        LOGGER.info("Voltage level {} removed", vlId);

        substation.ifPresent(s -> {
            if (s.getVoltageLevelStream().count() == 0) {
                String substationId = s.getId();
                s.remove();
                substationRemovedReport(reportNode, substationId);
                LOGGER.info("Substation {} removed", substationId);
            }
        });
    }

    static void createNBBreaker(int node1, int node2, String id, VoltageLevel.NodeBreakerView view, boolean open) {
        view.newSwitch()
                .setId(id)
                .setEnsureIdUnicity(true)
                .setKind(SwitchKind.BREAKER)
                .setOpen(open)
                .setRetained(true)
                .setNode1(node1)
                .setNode2(node2)
                .add();
    }

    static void createNBDisconnector(int node1, int node2, String id, VoltageLevel.NodeBreakerView view, boolean open) {
        view.newSwitch()
                .setId(id)
                .setEnsureIdUnicity(true)
                .setKind(SwitchKind.DISCONNECTOR)
                .setOpen(open)
                .setNode1(node1)
                .setNode2(node2)
                .add();
    }

    static void createBusBreakerSwitch(String busId1, String busId2, String id, VoltageLevel.BusBreakerView view) {
        view.newSwitch()
                .setId(id)
                .setEnsureIdUnicity(true)
                .setOpen(false)
                .setBus1(busId1)
                .setBus2(busId2)
                .add();
    }

    /**
     * Utility method that associates a busbar section position index to the orders taken by all the connectables
     * of the busbar sections of this index.
     **/
    static NavigableMap> getSliceOrdersMap(VoltageLevel voltageLevel) {
        // Compute the map of connectables by busbar sections
        Map>> connectablesByBbs = new LinkedHashMap<>();
        voltageLevel.getConnectableStream(BusbarSection.class)
                .forEach(bbs -> fillConnectablesMap(bbs, connectablesByBbs));

        // Merging the map by section index
        Map>> connectablesBySectionIndex = new LinkedHashMap<>();
        connectablesByBbs.forEach((bbs, connectables) -> {
            BusbarSectionPosition bbPosition = bbs.getExtension(BusbarSectionPosition.class);
            if (bbPosition != null) {
                connectablesBySectionIndex.merge(bbPosition.getSectionIndex(), connectables, (l1, l2) -> {
                    l1.addAll(l2);
                    return l1;
                });
            }
        });

        // Get the orders corresponding map
        TreeMap> ordersBySectionIndex = new TreeMap<>();
        connectablesBySectionIndex.forEach((sectionIndex, connectables) -> {
            List orders = new ArrayList<>();
            connectables.forEach(connectable -> addOrderPositions(connectable, voltageLevel, orders));
            ordersBySectionIndex.put(sectionIndex, orders);
        });

        return ordersBySectionIndex;
    }

    /**
     * Method that fills the map connectablesByBbs with all the connectables of a busbar section.
     */
    static void fillConnectablesMap(BusbarSection bbs, Map>> connectablesByBbs) {
        BusbarSectionPosition bbPosition = bbs.getExtension(BusbarSectionPosition.class);
        int bbSection = bbPosition.getSectionIndex();

        if (connectablesByBbs.containsKey(bbs)) {
            return;
        }
        Set> connectables = connectablesByBbs.compute(bbs, (k, v) -> new LinkedHashSet<>());

        bbs.getTerminal().traverse(new Terminal.TopologyTraverser() {
            @Override
            public TraverseResult traverse(Terminal terminal, boolean connected) {
                if (terminal.getVoltageLevel() != bbs.getTerminal().getVoltageLevel()) {
                    return TraverseResult.TERMINATE_PATH;
                }
                Connectable connectable = terminal.getConnectable();
                if (connectable instanceof BusbarSection otherBbs) {
                    BusbarSectionPosition otherBbPosition = otherBbs.getExtension(BusbarSectionPosition.class);
                    if (otherBbPosition.getSectionIndex() == bbSection) {
                        connectablesByBbs.put(otherBbs, connectables);
                    } else {
                        return TraverseResult.TERMINATE_PATH;
                    }
                }
                connectables.add(connectable);
                return TraverseResult.CONTINUE;
            }

            @Override
            public TraverseResult traverse(Switch aSwitch) {
                return TraverseResult.CONTINUE;
            }
        });
    }

    /**
     * Get the list of parallel busbar sections on a given busbar section position
     *
     * @param voltageLevel Voltage level in which to find the busbar sections
     * @param position busbar section position according to which busbar sections are found
     * @return the list of busbar sections in the voltage level that have the same section position as the given position
     */
    static List getParallelBusbarSections(VoltageLevel voltageLevel, BusbarSectionPosition position) {
        // List of the bars for the second section
        return voltageLevel.getNodeBreakerView().getBusbarSectionStream()
            .filter(b -> b.getExtension(BusbarSectionPosition.class) != null)
            .filter(b -> b.getExtension(BusbarSectionPosition.class).getSectionIndex() == position.getSectionIndex()).toList();
    }

    /**
     * Creates a breaker and a disconnector between the connectable and the specified busbar
     */
    static void createNodeBreakerSwitchesTopology(VoltageLevel voltageLevel, int connectableNode, int forkNode, NamingStrategy namingStrategy, String baseId, BusbarSection bbs) {
        createNodeBreakerSwitchesTopology(voltageLevel, connectableNode, forkNode, namingStrategy, baseId, List.of(bbs), bbs);
    }

    /**
     * Creates open disconnectors between the fork node and every busbar section of the list in a voltage level
     */
    static void createNodeBreakerSwitchesTopology(VoltageLevel voltageLevel, int connectableNode, int forkNode, NamingStrategy namingStrategy, String baseId, List bbsList, BusbarSection bbs) {
        // Closed breaker
        createNBBreaker(connectableNode, forkNode, namingStrategy.getBreakerId(baseId), voltageLevel.getNodeBreakerView(), false);

        // Disconnectors - only the one on the chosen busbarsection is closed
        createDisconnectorTopology(voltageLevel, forkNode, namingStrategy, baseId, bbsList, bbs);
    }

    /**
     * Creates disconnectors between the fork node and every busbar section of the list in a voltage level. Each disconnector will be closed if it is connected to the given bar, else opened
     */
    static void createDisconnectorTopology(VoltageLevel voltageLevel, int forkNode, NamingStrategy namingStrategy, String baseId, List bbsList, BusbarSection bbs) {
        createDisconnectorTopology(voltageLevel, forkNode, namingStrategy, baseId, bbsList, bbs, 0);
    }

    /**
     * Creates disconnectors between the fork node and every busbar section of the list in a voltage level. Each disconnector will be closed if it is connected to the given bar, else opened
     */
    static void createDisconnectorTopology(VoltageLevel voltageLevel, int forkNode, NamingStrategy namingStrategy, String baseId, List bbsList, BusbarSection bbs, int side) {
        // Disconnectors - only the one on the chosen busbarsection is closed
        bbsList.forEach(b -> {
            int bbsNode = b.getTerminal().getNodeBreakerView().getNode();
            createNBDisconnector(forkNode, bbsNode, namingStrategy.getDisconnectorId(b, baseId, forkNode, bbsNode, side), voltageLevel.getNodeBreakerView(), b != bbs);
        });
    }

    /**
     * Get all the unused positions before the lowest used position on the busbar section bbs.
     * It is a range between the maximum used position on the busbar section with the highest section index lower than the section
     * index of the given busbar section and the minimum position on the given busbar section.
     * For two busbar sections with following indexes BBS1 with used orders 1,2,3 and BBS2 with used orders 7,8, this method
     * applied to BBS2 will return a range from 4 to 6.
     */
    public static Optional> getUnusedOrderPositionsBefore(BusbarSection bbs) {
        BusbarSectionPosition busbarSectionPosition = bbs.getExtension(BusbarSectionPosition.class);
        if (busbarSectionPosition == null) {
            throw new PowsyblException("busbarSection has no BusbarSectionPosition extension");
        }
        VoltageLevel voltageLevel = bbs.getTerminal().getVoltageLevel();
        NavigableMap> allOrders = getSliceOrdersMap(voltageLevel);

        int sectionIndex = busbarSectionPosition.getSectionIndex();
        Optional previousSliceMax = getMaxOrderUsedBefore(allOrders, sectionIndex);
        Optional sliceMin = allOrders.get(sectionIndex).stream().min(Comparator.naturalOrder());
        int min = previousSliceMax.map(o -> o + 1).orElse(0);
        int max = sliceMin.or(() -> getMinOrderUsedAfter(allOrders, sectionIndex)).map(o -> o - 1).orElse(Integer.MAX_VALUE);
        return Optional.ofNullable(min <= max ? Range.of(min, max) : null);
    }

    /**
     * Get all the unused positions after the highest used position on the busbar section bbs.
     * It is a range between the minimum used position on the busbar section with the lowest section index higher than the section
     * index of the given busbar section and the maximum position on the given busbar section.
     * For two busbar sections with following indexes BBS1 with used orders 1,2,3 and BBS2 with used orders 7,8, this method
     * applied to BBS1 will return a range from 4 to 6.
     */
    public static Optional> getUnusedOrderPositionsAfter(BusbarSection bbs) {
        BusbarSectionPosition busbarSectionPosition = bbs.getExtension(BusbarSectionPosition.class);
        if (busbarSectionPosition == null) {
            throw new PowsyblException("busbarSection has no BusbarSectionPosition extension");
        }
        VoltageLevel voltageLevel = bbs.getTerminal().getVoltageLevel();
        NavigableMap> allOrders = getSliceOrdersMap(voltageLevel);

        int sectionIndex = busbarSectionPosition.getSectionIndex();
        Optional nextSliceMin = getMinOrderUsedAfter(allOrders, sectionIndex);
        Optional sliceMax = allOrders.get(sectionIndex).stream().max(Comparator.naturalOrder());
        int min = sliceMax.or(() -> getMaxOrderUsedBefore(allOrders, sectionIndex)).map(o -> o + 1).orElse(0);
        int max = nextSliceMin.map(o -> o - 1).orElse(Integer.MAX_VALUE);
        return Optional.ofNullable(min <= max ? Range.of(min, max) : null);
    }

    /**
     * Get the range of connectable positions delimited by neighbouring busbar sections, for a given busbar section.
     * If the range is empty (for instance if positions max on left side is above position min on right side), the range returned is empty.
     * Note that the connectable positions needs to be in ascending order in the voltage level for ascending busbar section index positions.
     */
    public static Optional> getPositionRange(BusbarSection bbs) {
        BusbarSectionPosition positionExtension = bbs.getExtension(BusbarSectionPosition.class);
        if (positionExtension != null) {
            VoltageLevel voltageLevel = bbs.getTerminal().getVoltageLevel();
            NavigableMap> allOrders = getSliceOrdersMap(voltageLevel);

            int sectionIndex = positionExtension.getSectionIndex();
            int max = getMinOrderUsedAfter(allOrders, sectionIndex).map(o -> o - 1).orElse(Integer.MAX_VALUE);
            int min = getMaxOrderUsedBefore(allOrders, sectionIndex).map(o -> o + 1).orElse(0);

            return Optional.ofNullable(min <= max ? Range.of(min, max) : null);
        }
        return Optional.of(Range.of(0, Integer.MAX_VALUE));
    }

    /**
     * Method returning the maximum order in the slice with the highest section index lower to the given section.
     * For two busbar sections with following indexes BBS1 with used orders 1,2,3 and BBS2 with used orders 7,8, this method
     * applied to BBS2 will return 3.
     */
    public static Optional getMaxOrderUsedBefore(NavigableMap> allOrders, int section) {
        int s = section;
        Map.Entry> lowerEntry;
        do {
            lowerEntry = allOrders.lowerEntry(s);
            if (lowerEntry == null) {
                break;
            }
            s = lowerEntry.getKey();
        } while (lowerEntry.getValue().isEmpty());

        return Optional.ofNullable(lowerEntry)
                .flatMap(entry -> entry.getValue().stream().max(Comparator.naturalOrder()));
    }

    /**
     * Method returning the minimum order in the slice with the lowest section index higher to the given section.
     * For two busbar sections with following indexes BBS1 with used orders 1,2,3 and BBS2 with used orders 7,8, this method
     * applied to BBS1 will return 7.
     */
    public static Optional getMinOrderUsedAfter(NavigableMap> allOrders, int section) {
        int s = section;
        Map.Entry> higherEntry;
        do {
            higherEntry = allOrders.higherEntry(s);
            if (higherEntry == null) {
                break;
            }
            s = higherEntry.getKey();
        } while (higherEntry.getValue().isEmpty());

        return Optional.ofNullable(higherEntry)
                .flatMap(entry -> entry.getValue().stream().min(Comparator.naturalOrder()));
    }

    /**
     * Utility method to get all the taken feeder positions on a voltage level.
     */
    public static Set getFeederPositions(VoltageLevel voltageLevel) {
        Set feederPositionsOrders = new HashSet<>();
        voltageLevel.getConnectables().forEach(connectable -> addOrderPositions(connectable, voltageLevel, feederPositionsOrders));
        return feederPositionsOrders;
    }

    /**
     * Utility method to get all the taken feeder positions on a voltage level by connectable.
     */
    public static Map> getFeederPositionsByConnectable(VoltageLevel voltageLevel) {
        Map> feederPositionsOrders = new HashMap<>();
        getFeedersByConnectable(voltageLevel).forEach((k, v) -> {
            List orders = new ArrayList<>();
            v.forEach(feeder -> feeder.getOrder().ifPresent(orders::add));
            if (orders.size() > 1) {
                Collections.sort(orders);
            }
            feederPositionsOrders.put(k, orders);
        });
        return feederPositionsOrders;
    }

    private static void addOrderPositions(Connectable connectable, VoltageLevel voltageLevel, Collection feederPositionsOrders) {
        addOrderPositions(connectable, voltageLevel, feederPositionsOrders, false, ReportNode.NO_OP);
    }

    /**
     * Method adding order position(s) of a connectable on a given voltage level to the given collection.
     */
    private static void addOrderPositions(Connectable connectable, VoltageLevel voltageLevel, Collection feederPositionsOrders, boolean throwException, ReportNode reportNode) {
        ConnectablePosition position = (ConnectablePosition) connectable.getExtension(ConnectablePosition.class);
        if (position != null) {
            List orders = getOrderPositions(position, voltageLevel, connectable, throwException, reportNode);
            feederPositionsOrders.addAll(orders);
        }
    }

    /**
     * Utility method to get all the feeders on a voltage level by connectable.
     */
    public static Map> getFeedersByConnectable(VoltageLevel voltageLevel) {
        Map> feedersByConnectable = new HashMap<>();
        voltageLevel.getConnectables().forEach(connectable -> {
            ConnectablePosition position = (ConnectablePosition) connectable.getExtension(ConnectablePosition.class);
            if (position != null) {
                List feeder = getFeeders(position, voltageLevel, connectable, false, ReportNode.NO_OP);
                feedersByConnectable.put(connectable.getId(), feeder);
            }
        });
        return feedersByConnectable;
    }

    private static List getOrderPositions(ConnectablePosition position, VoltageLevel voltageLevel, Connectable connectable, boolean throwException, ReportNode reportNode) {
        List feeders;
        if (connectable instanceof Injection) {
            feeders = getInjectionFeeder(position);
        } else if (connectable instanceof Branch) {
            feeders = getBranchFeeders(position, voltageLevel, (Branch) connectable);
        } else if (connectable instanceof ThreeWindingsTransformer twt) {
            feeders = get3wtFeeders(position, voltageLevel, twt);
        } else {
            LOGGER.error("Given connectable not supported: {}", connectable.getClass().getName());
            connectableNotSupported(reportNode, connectable);
            if (throwException) {
                throw new IllegalStateException("Given connectable not supported: " + connectable.getClass().getName());
            }
            return Collections.emptyList();
        }
        List orders = new ArrayList<>();
        feeders.forEach(feeder -> feeder.getOrder().ifPresent(orders::add));
        if (orders.size() > 1) {
            Collections.sort(orders);
        }
        return orders;
    }

    private static List getFeeders(ConnectablePosition position, VoltageLevel voltageLevel, Connectable connectable, boolean throwException, ReportNode reportNode) {
        if (connectable instanceof Injection) {
            return getInjectionFeeder(position);
        } else if (connectable instanceof Branch) {
            return getBranchFeeders(position, voltageLevel, (Branch) connectable);
        } else if (connectable instanceof ThreeWindingsTransformer twt) {
            return get3wtFeeders(position, voltageLevel, twt);
        } else {
            LOGGER.error("Given connectable not supported: {}", connectable.getClass().getName());
            connectableNotSupported(reportNode, connectable);
            if (throwException) {
                throw new IllegalStateException("Given connectable not supported: " + connectable.getClass().getName());
            }
        }
        return Collections.emptyList();
    }

    private static List getInjectionFeeder(ConnectablePosition position) {
        return Optional.ofNullable(position.getFeeder()).map(List::of).orElse(Collections.emptyList());
    }

    private static List getBranchFeeders(ConnectablePosition position, VoltageLevel voltageLevel, Branch branch) {
        List feeders = new ArrayList<>();
        if (branch.getTerminal1().getVoltageLevel() == voltageLevel) {
            Optional.ofNullable(position.getFeeder1()).ifPresent(feeders::add);
        }
        if (branch.getTerminal2().getVoltageLevel() == voltageLevel) {
            Optional.ofNullable(position.getFeeder2()).ifPresent(feeders::add);
        }
        return feeders;
    }

    private static List get3wtFeeders(ConnectablePosition position, VoltageLevel voltageLevel, ThreeWindingsTransformer twt) {
        List feeders = new ArrayList<>();
        if (twt.getLeg1().getTerminal().getVoltageLevel() == voltageLevel) {
            Optional.ofNullable(position.getFeeder1()).ifPresent(feeders::add);
        }
        if (twt.getLeg2().getTerminal().getVoltageLevel() == voltageLevel) {
            Optional.ofNullable(position.getFeeder2()).ifPresent(feeders::add);
        }
        if (twt.getLeg3().getTerminal().getVoltageLevel() == voltageLevel) {
            Optional.ofNullable(position.getFeeder3()).ifPresent(feeders::add);
        }
        return feeders;
    }

    /**
     * Method returning the first busbar section with the lowest BusbarSectionIndex if there are the BusbarSectionPosition extensions and the first busbar section found otherwise.
     */
    public static BusbarSection getFirstBusbarSection(VoltageLevel voltageLevel) {
        BusbarSection bbs;
        if (voltageLevel.getNodeBreakerView().getBusbarSectionStream().anyMatch(b -> b.getExtension(BusbarSectionPosition.class) != null)) {
            bbs = voltageLevel.getNodeBreakerView().getBusbarSectionStream()
                    .min(Comparator.comparingInt((BusbarSection b) -> {
                        BusbarSectionPosition position = b.getExtension(BusbarSectionPosition.class);
                        return position == null ? Integer.MAX_VALUE : position.getSectionIndex();
                    }).thenComparingInt((BusbarSection b) -> {
                        BusbarSectionPosition position = b.getExtension(BusbarSectionPosition.class);
                        return position == null ? Integer.MAX_VALUE : position.getBusbarIndex();
                    })).orElse(null);
        } else {
            bbs = voltageLevel.getNodeBreakerView().getBusbarSectionStream().findFirst().orElse(null);
        }
        if (bbs == null) {
            throw new PowsyblException(String.format("Voltage level %s has no busbar section.", voltageLevel.getId()));
        }
        return bbs;
    }

    private static Optional mergeLimits(String lineId,
                                                          Optional limits1,
                                                          Optional limitsTeePointSide,
                                                          ReportNode reportNode) {
        Optional limits;

        double permanentLimit = limits1.map(LoadingLimitsBag::getPermanentLimit).orElse(Double.NaN);
        List temporaryLimits1 = limits1.map(LoadingLimitsBag::getTemporaryLimits).orElse(new ArrayList<>());
        List temporaryLimitsTeePointSide = limitsTeePointSide.map(LoadingLimitsBag::getTemporaryLimits).orElse(new ArrayList<>());
        List temporaryLimits = new ArrayList<>();

        if (!limitsTeePointSide.isPresent()) {  // no limits on tee point side : we keep limits on other side
            limits = limits1;
        } else {
            // permanent limit : we keep the minimum permanent limit from both sides
            if (Double.isNaN(permanentLimit)) {
                permanentLimit = limitsTeePointSide.get().getPermanentLimit();
            } else if (!Double.isNaN(limitsTeePointSide.get().getPermanentLimit())) {
                permanentLimit = Math.min(permanentLimit, limitsTeePointSide.get().getPermanentLimit());
            }

            // temporary limits on both sides : they are ignored, otherwise, we keep temporary limits on side where they are defined
            if (!temporaryLimits1.isEmpty() && !temporaryLimitsTeePointSide.isEmpty()) {
                LOGGER.warn("Temporary limits on both sides for line {} : They are ignored", lineId);
                reportNode.newReportNode()
                        .withMessageTemplate("limitsLost", "Temporary limits on both sides for line ${lineId} : They are ignored")
                        .withUntypedValue("lineId", lineId)
                        .withSeverity(TypedValue.WARN_SEVERITY)
                        .add();
            } else {
                temporaryLimits = !temporaryLimits1.isEmpty() ? temporaryLimits1 : temporaryLimitsTeePointSide;
            }

            limits = Optional.of(new LoadingLimitsBag(permanentLimit, temporaryLimits));
        }

        return limits;
    }

    public static LoadingLimitsBags mergeLimits(String lineId, LoadingLimitsBags limits, LoadingLimitsBags limitsTeePointSide, ReportNode reportNode) {
        Optional activePowerLimits = mergeLimits(lineId, limits.getActivePowerLimits(), limitsTeePointSide.getActivePowerLimits(), reportNode);
        Optional apparentPowerLimits = mergeLimits(lineId, limits.getApparentPowerLimits(), limitsTeePointSide.getApparentPowerLimits(), reportNode);
        Optional currentLimits = mergeLimits(lineId, limits.getCurrentLimits(), limitsTeePointSide.getCurrentLimits(), reportNode);

        return new LoadingLimitsBags(activePowerLimits.orElse(null), apparentPowerLimits.orElse(null), currentLimits.orElse(null));
    }

    /**
     * Find tee point connecting the 3 given lines, if any
     * @return the tee point connecting the 3 given lines or null if none
     */
    public static VoltageLevel findTeePoint(Line line1, Line line2, Line line3) {
        Map countVoltageLevels = Stream.of(line1, line2, line3)
                .map(Line::getTerminals)
                .flatMap(List::stream)
                .collect(Collectors.groupingBy(Terminal::getVoltageLevel, Collectors.counting()));
        var commonVlMapEntry = Collections.max(countVoltageLevels.entrySet(), Map.Entry.comparingByValue());
        // If the lines are connected by a tee point, there should be 4 distinct voltage levels and one of them should be found 3 times
        if (countVoltageLevels.size() == 4 && commonVlMapEntry.getValue() == 3) {
            return commonVlMapEntry.getKey();
        } else {
            return null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy