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

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

/**
 * 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.powsybl.commons.PowsyblException;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.computation.ComputationManager;
import com.powsybl.iidm.modification.AbstractNetworkModification;
import com.powsybl.iidm.modification.NetworkModificationImpact;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.BusbarSectionPosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

import static com.powsybl.iidm.modification.util.ModificationLogs.busOrBbsDoesNotExist;
import static com.powsybl.iidm.modification.util.ModificationReports.*;
import static com.powsybl.iidm.modification.topology.TopologyModificationUtils.*;

/**
 * Adds a coupling device between two busbar sections. If topology extensions are present, then it creates open
 * disconnectors to connect the breaker to every parallel busbar section, else does not create them.
 * If there are exactly two busbar sections and
 * that they must have the same sectionIndex, then no open disconnector is created.
 * @author Coline Piloquet {@literal }
 */
public class CreateCouplingDevice extends AbstractNetworkModification {

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

    private final String busOrBbsId1;

    private final String busOrBbsId2;

    private String switchPrefixId;

    CreateCouplingDevice(String busOrBbsId1, String busOrBbsId2, String switchPrefixId) {
        this.busOrBbsId1 = Objects.requireNonNull(busOrBbsId1, "Busbar section 1 not defined");
        this.busOrBbsId2 = Objects.requireNonNull(busOrBbsId2, "Busbar section 2 not defined");
        this.switchPrefixId = switchPrefixId;
    }

    @Override
    public String getName() {
        return "CreateCouplingDevice";
    }

    public String getBusOrBbsId1() {
        return busOrBbsId1;
    }

    /**
     * @deprecated Use {@link #getBusOrBbsId1()} instead.
     */
    @Deprecated(since = "5.2.0")
    public String getBbsId1() {
        return getBusOrBbsId1();
    }

    public String getBusOrBbsId2() {
        return busOrBbsId2;
    }

    /**
     * @deprecated Use {@link #getBusOrBbsId2()} instead.
     */
    @Deprecated(since = "5.2.0")
    public String getBbsId2() {
        return getBusOrBbsId2();
    }

    public Optional getSwitchPrefixId() {
        return Optional.ofNullable(switchPrefixId);
    }

    @Override
    public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) {
        Identifiable busOrBbs1 = network.getIdentifiable(busOrBbsId1);
        Identifiable busOrBbs2 = network.getIdentifiable(busOrBbsId2);
        if (failBbs(busOrBbs1, busOrBbs2, reportNode, throwException)) {
            return;
        }

        VoltageLevel voltageLevel1 = getVoltageLevel(busOrBbs1, reportNode, throwException);
        VoltageLevel voltageLevel2 = getVoltageLevel(busOrBbs2, reportNode, throwException);
        if (voltageLevel1 == null || voltageLevel2 == null) {
            LOGGER.error("Voltage level associated to {} or {} not found.", busOrBbs1, busOrBbs2);
            notFoundBusOrBusbarSectionVoltageLevelReport(reportNode, busOrBbsId1, busOrBbsId2);
            if (throwException) {
                throw new PowsyblException(String.format("Voltage level associated to %s or %s not found.", busOrBbs1, busOrBbs2));
            }
            return;
        }
        if (voltageLevel1 != voltageLevel2) {
            LOGGER.error("{} and {} are in two different voltage levels.", busOrBbsId1, busOrBbsId2);
            unexpectedDifferentVoltageLevels(reportNode, busOrBbsId1, busOrBbsId2);
            if (throwException) {
                throw new PowsyblException(String.format("%s and %s are in two different voltage levels.", busOrBbsId1, busOrBbsId2));
            }
            return;
        }
        if (busOrBbs1 instanceof Bus && busOrBbs2 instanceof Bus) {
            if (switchPrefixId == null) {
                switchPrefixId = voltageLevel1.getId();
            }
            // buses are identifiable: voltage level is BUS_BREAKER
            createBusBreakerSwitch(busOrBbsId1, busOrBbsId2, namingStrategy.getSwitchId(switchPrefixId), voltageLevel1.getBusBreakerView());
        } else if (busOrBbs1 instanceof BusbarSection bbs1 && busOrBbs2 instanceof BusbarSection bbs2) {
            // busbar sections exist: voltage level is NODE_BREAKER
            applyOnBusbarSections(voltageLevel1, voltageLevel2, bbs1, bbs2, namingStrategy, reportNode);
        }
        LOGGER.info("New coupling device was added to voltage level {} between {} and {}", voltageLevel1.getId(), busOrBbs1, busOrBbs2);
        newCouplingDeviceAddedReport(reportNode, voltageLevel1.getId(), busOrBbsId1, busOrBbsId2);
    }

    @Override
    public NetworkModificationImpact hasImpactOnNetwork(Network network) {
        impact = DEFAULT_IMPACT;
        Identifiable busOrBbs1 = network.getIdentifiable(busOrBbsId1);
        Identifiable busOrBbs2 = network.getIdentifiable(busOrBbsId2);
        if (!checkVoltageLevel(busOrBbs1) || !checkVoltageLevel(busOrBbs2)
            || busOrBbsId1.equals(busOrBbsId2)) {
            impact = NetworkModificationImpact.CANNOT_BE_APPLIED;
        }
        return impact;
    }

    /**
     * Apply the modification on the two specified busbar sections
     */
    private void applyOnBusbarSections(VoltageLevel voltageLevel1, VoltageLevel voltageLevel2, BusbarSection bbs1, BusbarSection bbs2, NamingStrategy namingStrategy, ReportNode reportNode) {
        if (switchPrefixId == null) {
            switchPrefixId = namingStrategy.getSwitchBaseId(voltageLevel1, bbs1, bbs2);
        }
        // busbar sections exist: voltage level is NODE_BREAKER
        int breakerNode1 = voltageLevel1.getNodeBreakerView().getMaximumNodeIndex() + 1;
        int breakerNode2 = breakerNode1 + 1;
        int nbOpenDisconnectors = 0;

        // Breaker
        createNBBreaker(breakerNode1, breakerNode2, namingStrategy.getBreakerId(switchPrefixId), voltageLevel1.getNodeBreakerView(), false);

        // Positions
        BusbarSectionPosition position1 = bbs1.getExtension(BusbarSectionPosition.class);
        BusbarSectionPosition position2 = bbs2.getExtension(BusbarSectionPosition.class);
        boolean bbsOnSameSection = position1 != null && position2 != null && position1.getSectionIndex() == position2.getSectionIndex();

        // If the positions are defined and on the same section, check if the first side is on the first bar or the second side on the last bar.
        // If true, the last bar will not be connected on the first side nor the first bar on the second side.
        // If false, it will be the opposite.
        boolean avoidLastBarOnFirstSide = bbsOnSameSection && checkSides(voltageLevel1, position1, position2);

        // Disconnectors
        if (position1 != null) {

            // List of the bars for the first section and creation of the topology
            List bbsList1 = computeBbsListAndCreateTopology(voltageLevel1, bbs1, namingStrategy, bbsOnSameSection, breakerNode1, avoidLastBarOnFirstSide, 1);

            nbOpenDisconnectors += bbsList1.size() - 1;
        } else {
            createDisconnectorTopology(voltageLevel1, breakerNode1, namingStrategy, switchPrefixId, List.of(bbs1), bbs1);
            LOGGER.warn("No busbar section position extension found on {}, only one disconnector is created.", bbs1.getId());
            noBusbarSectionPositionExtensionReport(reportNode, bbs1);
        }
        if (position2 != null) {
            // List of the bars for the second section and creation of the topology
            List bbsList2 = computeBbsListAndCreateTopology(voltageLevel2, bbs2, namingStrategy, bbsOnSameSection, breakerNode2, !avoidLastBarOnFirstSide, 2);

            nbOpenDisconnectors += bbsList2.size() - 1;
        } else {
            createDisconnectorTopology(voltageLevel2, breakerNode2, namingStrategy, switchPrefixId, List.of(bbs2), bbs2);
            LOGGER.warn("No busbar section position extension found on {}, only one disconnector is created.", bbs2.getId());
            noBusbarSectionPositionExtensionReport(reportNode, bbs2);
        }

        if (nbOpenDisconnectors > 0) {
            LOGGER.info("{} open disconnectors created on parallel busbar section in voltage level {}", nbOpenDisconnectors, voltageLevel1.getId());
            openDisconnectorsAddedReport(reportNode, voltageLevel1.getId(), nbOpenDisconnectors);
        }
    }

    /**
     * Check if the first side is on the first bar or the second side on the last bar
     * @param voltageLevel Voltage level in which the busbar sections are located
     * @param position1 Position on the first side
     * @param position2 Position on the second side
     * @return True if the first side is on the first bar or the second side is on the last bar, else False
     */
    private boolean checkSides(VoltageLevel voltageLevel, BusbarSectionPosition position1, BusbarSectionPosition position2) {
        // Check if the first side is on the first bar or the second side on the last bar
        OptionalInt minBbsIndex = getParallelBusbarSections(voltageLevel, position1).stream()
            .mapToInt(b -> b.getExtension(BusbarSectionPosition.class).getBusbarIndex()).min();
        OptionalInt maxBbsIndex = getParallelBusbarSections(voltageLevel, position2).stream()
            .mapToInt(b -> b.getExtension(BusbarSectionPosition.class).getBusbarIndex()).max();
        return position1.getBusbarIndex() == minBbsIndex.orElseThrow()
            || position2.getBusbarIndex() == maxBbsIndex.orElseThrow();
    }

    /**
     * Computes the list of the bars on which to connect the coupling device for the current side and creates the
     * disconnectors for the different parallel busbar sections.
     * @param voltageLevel Voltage level in which the busbar sections are located
     * @param bbs Current bar
     * @param namingStrategy Naming strategy used to name the disconnectors created
     * @param bbsOnSameSection True if the two busbar sections are located on the same section
     * @param breakerNode Node on the current site of the breaker
     * @param avoidLastBar If true, the last bar will not be connected, if false the first one will not be connected.
     * @param side Side of the coupling device on which to connect the bars
     * @return List of the busbar sections on which a connection was made
     */
    private List computeBbsListAndCreateTopology(VoltageLevel voltageLevel, BusbarSection bbs, NamingStrategy namingStrategy, boolean bbsOnSameSection, int breakerNode, boolean avoidLastBar, int side) {

        // Position of the bar
        BusbarSectionPosition position = bbs.getExtension(BusbarSectionPosition.class);
        // List of the bars parallel to the given position
        List bbsList = getParallelBusbarSections(voltageLevel, position);

        // If the two busbarsections are in the same section, avoid the last bar if avoidLastBar is true, else the first one
        if (bbsOnSameSection) {
            if (avoidLastBar) {
                int maxBbsIndex = bbsList.stream().mapToInt(b -> b.getExtension(BusbarSectionPosition.class).getBusbarIndex()).max().orElseThrow();
                bbsList = bbsList.stream().filter(b -> b.getExtension(BusbarSectionPosition.class).getBusbarIndex() != maxBbsIndex).toList();
            } else {
                int minBbsIndex = bbsList.stream().mapToInt(b -> b.getExtension(BusbarSectionPosition.class).getBusbarIndex()).min().orElseThrow();
                bbsList = bbsList.stream().filter(b -> b.getExtension(BusbarSectionPosition.class).getBusbarIndex() != minBbsIndex).toList();
            }
        }

        // Disconnectors
        createDisconnectorTopology(voltageLevel, breakerNode, namingStrategy, switchPrefixId, bbsList, bbs, bbsOnSameSection ? side : 0);
        return bbsList;
    }

    private boolean failBbs(Identifiable bbs1, Identifiable bbs2, ReportNode reportNode, boolean throwException) {
        if (bbs1 == null) {
            busOrBbsDoesNotExist(busOrBbsId1, reportNode, throwException);
            return true;
        }
        if (bbs2 == null) {
            busOrBbsDoesNotExist(busOrBbsId2, reportNode, throwException);
            return true;
        }
        if (bbs1 == bbs2) {
            LOGGER.error("No coupling device can be created on a same busbar section or bus ({})", busOrBbsId1);
            noCouplingDeviceOnSameBusOrBusbarSection(reportNode, busOrBbsId1);
            if (throwException) {
                throw new PowsyblException(String.format("No coupling device can be created on a same bus or busbar section (%s)", busOrBbsId1));
            }
            return true;
        }
        return false;
    }

    private static VoltageLevel getVoltageLevel(Identifiable identifiable, ReportNode reportNode, boolean throwException) {
        if (identifiable instanceof Bus bus) {
            return bus.getVoltageLevel();
        }
        if (identifiable instanceof BusbarSection bbs) {
            return bbs.getTerminal().getVoltageLevel();
        }
        LOGGER.error("Unexpected type of identifiable {}: {}", identifiable.getId(), identifiable.getType());
        unexpectedIdentifiableType(reportNode, identifiable);
        if (throwException) {
            throw new PowsyblException("Unexpected type of identifiable " + identifiable.getId() + ": " + identifiable.getType());
        }
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy