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

com.powsybl.cgmes.conversion.RegulatingTerminalMapper Maven / Gradle / Ivy

There is a newer version: 6.6.0
Show newest version
/**
 * Copyright (c) 2021, 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/.
 */

package com.powsybl.cgmes.conversion;

import com.powsybl.cgmes.model.CgmesNames;
import com.powsybl.cgmes.model.CgmesTerminal;
import com.powsybl.iidm.network.*;
import com.powsybl.math.graph.TraverseResult;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * IIDM does not have terminals at ends of switches.
 * When we found a CGMES regulating terminal that corresponds to the end of a switch,
 * we have to map it to an equivalent IIDM terminal.
 * The mapping has to take into account if the controlled magnitude corresponds to a node (voltage)
 * or if it is a flow (active/reactive power)
 *
 * @author Luma Zamarreño 
 * @author José Antonio Marqués 
 */
final class RegulatingTerminalMapper {

    private RegulatingTerminalMapper() {
        // Empty constructor for utility class
    }

    public static Optional mapForVoltageControl(String cgmesTerminalId, Context context) {
        return cgmesTerminalId == null ? Optional.empty() :
                // The CGMES terminal has been explicitly mapped to an IIDM terminal
                mapped(cgmesTerminalId, context)
                        // The CGMES terminal is the end of a switch but we can find a terminal in the same electrical node
                        .or(() -> new EquivalentTerminalFinderVoltageControl(cgmesTerminalId, context).find())
                        // As a last resource, rely on the "find" method of terminal mapping:
                        // Bus/branch models may define remote voltage controls that point to busbar sections
                        // Busbar sections are not mapped to IIDM
                        .or(() -> Optional.ofNullable(context.terminalMapping()
                                .findFromTopologicalNode(context.terminalMapping().getTopologicalNode(cgmesTerminalId))));
    }

    public static Optional mapForFlowControl(String cgmesTerminalId, Context context) {
        return cgmesTerminalId == null ? Optional.empty() :
                mapped(cgmesTerminalId, context)
                        .map(t -> new TerminalAndSign(t, 1))
                        .or(() -> new EquivalentTerminalFinderFlowControl(cgmesTerminalId, context).findWithSign());
    }

    public static Optional mapForTieFlow(String cgmesTerminalId, Context context) {
        return cgmesTerminalId == null ? Optional.empty() :
                mapped(cgmesTerminalId, context)
                        .or(() -> new EquivalentTerminalFinderTieFlow(cgmesTerminalId, context).find());
    }

    private static Optional mapped(String cgmesTerminalId, Context context) {
        return Optional.ofNullable(context.terminalMapping().get(cgmesTerminalId));
    }

    private static boolean isBusbarSection(Terminal t) {
        return t.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION;
    }

    private static boolean isEquipmentCapableOfVoltageControl(Terminal t) {
        IdentifiableType type = t.getConnectable().getType();
        return type == IdentifiableType.GENERATOR
                || type == IdentifiableType.SHUNT_COMPENSATOR
                || type == IdentifiableType.STATIC_VAR_COMPENSATOR
                || type == IdentifiableType.HVDC_CONVERTER_STATION;
    }

    private static  Stream allTerminals(VoltageLevel vl, Set vertices, Function terminalToVertex) {
        // The current implementation is:
        // For all connectables inside the voltage level:
        // For all terminals of connectable:
        // filter terminals in the voltage level
        // filter terminals with vertex in the list of vertices

        // A potential optimization could be:
        // instead of streaming from all terminals in the voltage level
        // iterate only over terminals obtained from the given vertices

        // But:
        // For node/breaker is ok, we can get the (optional) terminal from a node
        // But for bus/branch (bus breaker view):
        // There is no way to get the terminals of the equipment that could be connected to the bus
        // (the list of all "connectables" and their terminals)
        // Only the currently connected terminals can be obtained

        // So:
        // We leave the current implementation because it allows uniform processing at both levels
        // And the room for optimization seems really narrow

        return vl.getConnectableStream()
                .map(c -> (Connectable) c)
                .flatMap(c -> c.getTerminals().stream())
                .filter(terminal -> terminal.getVoltageLevel() == vl)
                .filter(terminal -> vertices.contains(terminalToVertex.apply(terminal)));
    }

    static class TerminalAndSign {
        private final Terminal terminal;
        private final int sign;

        TerminalAndSign(Terminal terminal, int sign) {
            this.terminal = terminal;
            this.sign = sign;
        }

        Terminal getTerminal() {
            return terminal;
        }

        int getSign() {
            return sign;
        }
    }

    abstract static class AbstractEquivalentTerminalFinder {
        protected final VoltageLevel vl;
        protected final Switch sw;
        protected final boolean isNodeBreaker;

        AbstractEquivalentTerminalFinder(String cgmesTerminalId, Context context) {
            this.sw = atSwitchEnd(cgmesTerminalId, context);
            this.vl = this.sw == null ? null : sw.getVoltageLevel();
            this.isNodeBreaker = this.vl != null && vl.getTopologyKind() == TopologyKind.NODE_BREAKER;
        }

        private static Switch atSwitchEnd(String cgmesTerminalId, Context context) {
            Objects.requireNonNull(cgmesTerminalId);
            CgmesTerminal cgmesTerminal = context.cgmes().terminal(cgmesTerminalId);
            if (cgmesTerminal != null && isSwitch(cgmesTerminal.conductingEquipmentType())) {
                return context.network().getSwitch(cgmesTerminal.conductingEquipment());
            }
            return null;
        }

        private static boolean isSwitch(String conductingEquipmentType) {
            return CgmesNames.SWITCH_TYPES.contains(conductingEquipmentType);
        }

        // All nodes reachable following opened and closed switches and internal connections, avoiding the given switch
        private static Set allNodesReachableBySwitchesExceptSwitch(VoltageLevel voltageLevel, int node, Switch exceptSwitch) {
            Set nodes = new HashSet<>();
            nodes.add(node);
            voltageLevel.getNodeBreakerView().traverse(node, (node1, sw, node2) -> {
                if (sw == exceptSwitch) {
                    return TraverseResult.TERMINATE_PATH;
                }
                nodes.add(node2);
                return TraverseResult.CONTINUE;
            });
            return nodes;
        }

        // All buses reachable following opened and closed switches and internal connections, avoiding the given switch
        private static Set allBusesReachableBySwitchesExceptSwitch(VoltageLevel voltageLevel, Bus bus, Switch exceptSwitch) {
            Set buses = new HashSet<>();
            buses.add(bus);
            voltageLevel.getBusBreakerView().traverse(bus, (bus1, sw, bus2) -> {
                if (sw == exceptSwitch) {
                    return TraverseResult.TERMINATE_PATH;
                }
                buses.add(bus2);
                return TraverseResult.CONTINUE;
            });
            return buses;
        }

        private static  Terminal selectValidTerminalForFlow(VoltageLevel vl, Set vertices, Function terminalToVertex) {
            List terminals = allTerminals(vl, vertices, terminalToVertex)
                    .filter(terminal -> terminal.getConnectable().getType() != IdentifiableType.BUSBAR_SECTION)
                    .collect(Collectors.toList());
            return terminals.size() == 1 ? terminals.get(0) : null;
        }

        Optional find() {
            return Optional.empty();
        }

        Optional findWithSign() {
            return Optional.empty();
        }

        protected Terminal findForFlow(int end, int otherEnd) {
            Set nodes = allNodesReachableBySwitchesExceptSwitch(vl, end, sw);
            // We are looking an equivalent terminal for a flow control magnitude.
            // If we find the node of the other end of the switch it means there is a bypass,
            // we do not have a chain
            if (nodes.contains(otherEnd)) {
                return null;
            }
            // We are able to map a flow control if we find only one terminal after expanding
            // If we find more than one terminal (equipment) we won't be able to decide which one to
            // assign for flow control magnitude
            return selectValidTerminalForFlow(vl, nodes, t -> t.getNodeBreakerView().getNode());
        }

        protected Terminal findForFlow(Bus end, Bus otherEnd) {
            Set buses = allBusesReachableBySwitchesExceptSwitch(vl, end, sw);
            // We are looking an equivalent terminal for a flow control magnitude.
            // If we find the node of the other end of the switch it means there is a bypass,
            // we do not have a chain
            if (buses.contains(otherEnd)) {
                return null;
            }
            // We are able to map a flow control if we find only one terminal after expanding
            // If we find more than one terminal (equipment) we won't be able to decide which one to
            // assign for flow control magnitude
            return selectValidTerminalForFlow(vl, buses, RegulatingTerminalMapper::getTerminalBus);
        }
    }

    static class EquivalentTerminalFinderFlowControl extends AbstractEquivalentTerminalFinder {
        private final int cgmesTerminalEnd;

        EquivalentTerminalFinderFlowControl(String cgmesTerminalId, Context context) {
            super(cgmesTerminalId, context);
            this.cgmesTerminalEnd = context.cgmes().terminal(cgmesTerminalId).getSequenceNumber();
        }

        // CgmesTerminal is defined at end1 of the switch and the terminal is obtained at the switchChain from end2 then same sign
        // CgmesTerminal is defined at end2 of the switch and the terminal is obtained at the switchChain from end1 then same sign
        //
        // N1---sw---N2 N2---sw2---N3 N3---sw3---N4 ... Nn---swn---Nn+1 Nn+1---
        // ct = cgmesTerminal defined at N1, t = terminal defined at Nn+1, ct and t same sign
        //
        private static Optional best(int cgmesTerminalEnd, Terminal iidmTerminalEnd1, Terminal iidmTerminalEnd2) {
            if (cgmesTerminalEnd == 1 && iidmTerminalEnd2 != null) {
                return Optional.of(new TerminalAndSign(iidmTerminalEnd2, 1));
            } else if (cgmesTerminalEnd == 2 && iidmTerminalEnd1 != null) {
                return Optional.of(new TerminalAndSign(iidmTerminalEnd1, 1));
            } else if (cgmesTerminalEnd == 1 && iidmTerminalEnd1 != null) {
                return Optional.of(new TerminalAndSign(iidmTerminalEnd1, -1));
            } else if (cgmesTerminalEnd == 2 && iidmTerminalEnd2 != null) {
                return Optional.of(new TerminalAndSign(iidmTerminalEnd2, -1));
            } else {
                return Optional.empty();
            }
        }

        @Override
        Optional findWithSign() {
            return sw == null ? Optional.empty() : findWithSign(cgmesTerminalEnd);
        }

        // Find a terminal in a chain of switches.
        Optional findWithSign(int cgmesTerminalEnd) {
            if (isNodeBreaker) {
                return findWithSignNodeBreaker(cgmesTerminalEnd);
            } else {
                return findWithSignBusBranch(cgmesTerminalEnd);
            }
        }

        private Optional findWithSignNodeBreaker(int cgmesTerminalEnd) {
            int end1 = vl.getNodeBreakerView().getNode1(sw.getId());
            int end2 = vl.getNodeBreakerView().getNode2(sw.getId());

            Terminal terminalEnd1 = findForFlow(end1, end2);
            Terminal terminalEnd2 = findForFlow(end2, end1);
            return best(cgmesTerminalEnd, terminalEnd1, terminalEnd2);
        }

        private Optional findWithSignBusBranch(int cgmesTerminalEnd) {
            Bus end1 = vl.getBusBreakerView().getBus1(sw.getId());
            Bus end2 = vl.getBusBreakerView().getBus2(sw.getId());

            Terminal terminalEnd1 = findForFlow(end1, end2);
            Terminal terminalEnd2 = findForFlow(end2, end1);
            return best(cgmesTerminalEnd, terminalEnd1, terminalEnd2);
        }
    }

    static class EquivalentTerminalFinderVoltageControl extends AbstractEquivalentTerminalFinder {
        EquivalentTerminalFinderVoltageControl(String cgmesTerminalId, Context context) {
            super(cgmesTerminalId, context);
        }

        // All nodes reachable from this one following opened and closed switches and internal connections
        private static Set allNodesReachableBySwitches(VoltageLevel voltageLevel, int node) {
            Set nodes = new HashSet<>();
            nodes.add(node);
            voltageLevel.getNodeBreakerView().traverse(node, (node1, sw, node2) -> {
                nodes.add(node2);
                return TraverseResult.CONTINUE;
            });
            return nodes;
        }

        // All buses reachable from this one following opened and closed switches and internal connections
        private static Set allBusesReachableBySwitches(VoltageLevel voltageLevel, Bus bus) {
            Set buses = new HashSet<>();
            buses.add(bus);
            voltageLevel.getBusBreakerView().traverse(bus, (bus1, sw, bus2) -> {
                buses.add(bus2);
                return TraverseResult.CONTINUE;
            });
            return buses;
        }

        private static  List allTerminals(VoltageLevel vl, Set vertices, Function terminalToVertex) {
            return RegulatingTerminalMapper.allTerminals(vl, vertices, terminalToVertex).collect(Collectors.toList());
        }

        private static Optional best(List terminals) {
            // Prefer first terminals corresponding to busbar sections,
            // then terminals of equipment capable of voltage control,
            // then any terminal available
            return terminals.stream()
                    .filter(RegulatingTerminalMapper::isBusbarSection).findFirst()
                    .or(() -> terminals.stream().filter(RegulatingTerminalMapper::isEquipmentCapableOfVoltageControl).findFirst())
                    .or(() -> terminals.stream().findFirst());
        }

        @Override
        Optional find() {
            if (sw == null) {
                return Optional.empty();
            }
            return isNodeBreaker ? findNodeBreaker() : findBusBranch();
        }

        private Optional findNodeBreaker() {
            int end1 = vl.getNodeBreakerView().getNode1(sw.getId());
            List terminals = findTerminalsNodeBreaker(end1);
            return best(terminals);
        }

        private Optional findBusBranch() {
            Bus end1 = vl.getBusBreakerView().getBus1(sw.getId());
            List terminals = findTerminalsBusBranch(end1);
            return best(terminals);
        }

        protected List findTerminalsNodeBreaker(int node) {
            Set nodes = allNodesReachableBySwitches(vl, node);
            return allTerminals(vl, nodes, t -> t.getNodeBreakerView().getNode());
        }

        private List findTerminalsBusBranch(Bus end) {
            Set buses = allBusesReachableBySwitches(vl, end);
            return allTerminals(vl, buses, RegulatingTerminalMapper::getTerminalBus);
        }
    }

    static class EquivalentTerminalFinderTieFlow extends AbstractEquivalentTerminalFinder {
        EquivalentTerminalFinderTieFlow(String cgmesTerminalId, Context context) {
            super(cgmesTerminalId, context);
        }

        /**
         * The best terminal is the terminal associated to the line, branch at the border
         * one end inside the controlArea and the other outside.
         * At this moment we only know this information in danglingLines so
         * the terminal will only be accepted if it is a danglingLine
         */
        private static Optional best(Terminal terminalEnd1, Terminal terminalEnd2) {
            if (terminalEnd1 != null && terminalEnd1.getConnectable().getType() == IdentifiableType.DANGLING_LINE) {
                return Optional.of(terminalEnd1);
            }
            if (terminalEnd2 != null && terminalEnd2.getConnectable().getType() == IdentifiableType.DANGLING_LINE) {
                return Optional.of(terminalEnd2);
            }
            return Optional.empty();
        }

        @Override
        Optional find() {
            // Find a Danging Line terminal in a chain of switches
            if (sw == null) {
                return Optional.empty();
            }
            return isNodeBreaker ? findNodeBreaker() : findBusBranch();
        }

        private Optional findNodeBreaker() {
            int end1 = vl.getNodeBreakerView().getNode1(sw.getId());
            int end2 = vl.getNodeBreakerView().getNode2(sw.getId());
            Terminal terminalEnd1 = findForFlow(end1, end2);
            Terminal terminalEnd2 = findForFlow(end2, end1);
            return best(terminalEnd1, terminalEnd2);
        }

        private Optional findBusBranch() {
            Bus end1 = vl.getBusBreakerView().getBus1(sw.getId());
            Bus end2 = vl.getBusBreakerView().getBus2(sw.getId());
            Terminal terminalEnd1 = findForFlow(end1, end2);
            Terminal terminalEnd2 = findForFlow(end2, end1);
            return best(terminalEnd1, terminalEnd2);
        }
    }

    private static Bus getTerminalBus(Terminal terminal) {
        return terminal.getBusBreakerView().getBus() != null ? terminal.getBusBreakerView().getBus()
            : terminal.getBusBreakerView().getConnectableBus();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy