MetrixNetwork.java

/*
 * Copyright (c) 2020, 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.metrix.integration;

import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.util.StringToIntMapper;
import com.powsybl.contingency.*;
import com.powsybl.iidm.modification.tripping.Tripping;
import com.powsybl.iidm.network.*;
import com.powsybl.metrix.integration.metrix.MetrixInputAnalysis;
import com.powsybl.metrix.integration.remedials.Remedial;
import com.powsybl.metrix.integration.remedials.RemedialReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Paul Bui-Quang {@literal <paul.buiquang at rte-france.com>}
 */
public class MetrixNetwork {

    private static final Logger LOGGER = LoggerFactory.getLogger(MetrixNetwork.class);
    private static final String PAYS_CVG_PROPERTY = "paysCvg";
    private static final String PAYS_CVG_UNDEFINED = "Undefined";

    private final Network network;

    private final StringToIntMapper<MetrixSubset> mapper = new StringToIntMapper<>(MetrixSubset.class);

    private final Set<String> countryList = new HashSet<>();

    private final Set<Load> loadList = new LinkedHashSet<>();

    private final Set<Generator> generatorList = new LinkedHashSet<>();
    private final Set<String> generatorTypeList = new HashSet<>();

    private final Set<Line> lineList = new LinkedHashSet<>();
    private final Set<TwoWindingsTransformer> twoWindingsTransformerList = new LinkedHashSet<>();
    private final Set<ThreeWindingsTransformer> threeWindingsTransformerList = new LinkedHashSet<>();
    private final Set<DanglingLine> danglingLineList = new LinkedHashSet<>();
    private final Set<Switch> switchList = new LinkedHashSet<>();

    private final Set<PhaseTapChanger> phaseTapChangerList = new LinkedHashSet<>();

    private final Set<HvdcLine> hvdcLineList = new LinkedHashSet<>();

    private final Set<Bus> busList = new LinkedHashSet<>();

    private final List<Contingency> contingencyList = new ArrayList<>();

    private final Set<Identifiable<?>> disconnectedElements = new HashSet<>();

    private final Map<String, String> mappedSwitchMap = new HashMap<>();

    protected MetrixNetwork(Network network) {
        // TODO: switch T3T for 3xTWT in the network using a network modification
        this.network = Objects.requireNonNull(network);
    }

    public Network getNetwork() {
        return network;
    }

    public List<String> getCountryList() {
        return List.copyOf(countryList);
    }

    public List<Load> getLoadList() {
        return List.copyOf(loadList);
    }

    public List<Generator> getGeneratorList() {
        return List.copyOf(generatorList);
    }

    public List<String> getGeneratorTypeList() {
        return List.copyOf(generatorTypeList);
    }

    public List<Line> getLineList() {
        return List.copyOf(lineList);
    }

    public List<TwoWindingsTransformer> getTwoWindingsTransformerList() {
        return List.copyOf(twoWindingsTransformerList);
    }

    public List<ThreeWindingsTransformer> getThreeWindingsTransformerList() {
        return List.copyOf(threeWindingsTransformerList);
    }

    public List<DanglingLine> getDanglingLineList() {
        return List.copyOf(danglingLineList);
    }

    public List<Switch> getSwitchList() {
        return List.copyOf(switchList);
    }

    public List<PhaseTapChanger> getPhaseTapChangerList() {
        return List.copyOf(phaseTapChangerList);
    }

    public List<HvdcLine> getHvdcLineList() {
        return List.copyOf(hvdcLineList);
    }

    public List<Bus> getBusList() {
        return List.copyOf(busList);
    }

    public List<Contingency> getContingencyList() {
        return List.copyOf(contingencyList);
    }

    public Set<Identifiable<?>> getDisconnectedElements() {
        return Set.copyOf(disconnectedElements);
    }

    public int getIndex(Identifiable<?> identifiable) {
        MetrixSubset subset = getMetrixSubset(identifiable);
        return mapper.getInt(subset, identifiable.getId());
    }

    private MetrixSubset getMetrixSubset(Identifiable<?> identifiable) {
        MetrixSubset subset = MetrixSubset.QUAD;
        if (identifiable instanceof Generator) {
            subset = MetrixSubset.GROUPE;
        } else if (identifiable instanceof Bus) {
            subset = MetrixSubset.NOEUD;
        } else if (identifiable instanceof HvdcLine) {
            subset = MetrixSubset.HVDC;
        } else if (identifiable instanceof Load) {
            subset = MetrixSubset.LOAD;
        }
        return subset;
    }

    public Identifiable<?> getIdentifiable(String id) {
        return network.getIdentifiable(id);
    }

    public int getIndex(String id) {
        return getIndex(getIdentifiable(id));
    }

    public int getIndex(MetrixSubset subset, String id) {
        return mapper.getInt(subset, id);
    }

    public boolean isMapped(Identifiable<?> identifiable) {
        MetrixSubset subset = getMetrixSubset(identifiable);
        return mapper.isMapped(subset, identifiable.getId());
    }

    public int getCountryIndex(String country) {
        return mapper.getInt(MetrixSubset.REGION, country);
    }

    public String getGeneratorType(Generator generator) {
        return generator.getProperty("genreCvg", generator.getEnergySource().toString());
    }

    public String getCountryCode(VoltageLevel voltageLevel) {
        return voltageLevel.getSubstation().map(s -> {
            if (s.hasProperty(PAYS_CVG_PROPERTY)) {
                return s.getProperty(PAYS_CVG_PROPERTY);
            } else {
                return s.getCountry().map(Country::toString).orElse(PAYS_CVG_UNDEFINED);
            }
        }).orElse(PAYS_CVG_UNDEFINED);
    }

    private void addCountry(String country) {
        if (countryList.add(country)) {
            mapper.newInt(MetrixSubset.REGION, country);
        }
    }

    private void addGenerator(Generator generator) {
        if (generatorList.add(generator)) {
            mapper.newInt(MetrixSubset.GROUPE, generator.getId());
            generatorTypeList.add(getGeneratorType(generator));
        }
    }

    private void addLine(Line line) {
        if (lineList.add(line)) {
            mapper.newInt(MetrixSubset.QUAD, line.getId());
        }
    }

    private void addTwoWindingsTransformer(TwoWindingsTransformer twt) {
        if (twoWindingsTransformerList.add(twt)) {
            mapper.newInt(MetrixSubset.QUAD, twt.getId());
        }
    }

    private void addThreeWindingsTransformer(ThreeWindingsTransformer twt) {
        if (threeWindingsTransformerList.add(twt)) {
            mapper.newInt(MetrixSubset.QUAD, twt.getId());
        }
    }

    private void addDanglingLine(DanglingLine dl) {
        if (danglingLineList.add(dl)) {
            mapper.newInt(MetrixSubset.QUAD, dl.getId());
        }
    }

    private void addHvdcLine(HvdcLine line) {
        if (hvdcLineList.add(line)) {
            mapper.newInt(MetrixSubset.HVDC, line.getId());
        }
    }

    private void addSwitch(Switch sw) {
        if (!isMapped(sw) && switchList.add(sw)) {
            mapper.newInt(MetrixSubset.QUAD, sw.getId());
        }
    }

    private void addLoad(Load load) {
        if (loadList.add(load)) {
            mapper.newInt(MetrixSubset.LOAD, load.getId());
        }
    }

    private void addPhaseTapChanger(TwoWindingsTransformer twt) {
        if (phaseTapChangerList.add(twt.getPhaseTapChanger())) {
            mapper.newInt(MetrixSubset.DEPHA, twt.getId());
        }
    }

    private void addBus(Bus bus) {
        if (busList.add(bus)) {
            mapper.newInt(MetrixSubset.NOEUD, bus.getId());
        }
    }

    private void createLoadList() {
        int nbNok = 0;
        for (Load load : network.getLoads()) {
            Terminal t = load.getTerminal();
            Bus b = t.getBusBreakerView().getBus();
            if (b != null) {
                if (busList.contains(b)) {
                    addLoad(load);
                } else {
                    nbNok++;
                }
            } else {
                nbNok++;
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Loads      total = <%5d> ok = <%5d> not = <%5d>", loadList.size() + nbNok, loadList.size(), nbNok));
        }
    }

    private void createGeneratorList() {
        int nbNok = 0;
        for (Generator generator : network.getGenerators()) {
            Terminal t = generator.getTerminal();
            Bus b = t.getBusBreakerView().getBus();
            if (b != null) {
                if (busList.contains(b)) {
                    addGenerator(generator);
                } else {
                    nbNok++;
                }
            } else {
                nbNok++;
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Generators total = <%5d> ok = <%5d> not = <%5d>", generatorList.size() + nbNok, generatorList.size(), nbNok));
        }
    }

    private void createLineList() {
        int nbNok = 0;
        for (Line line : network.getLines()) {
            Terminal t1 = line.getTerminal1();
            Bus b1 = t1.getBusBreakerView().getBus();
            Terminal t2 = line.getTerminal2();
            Bus b2 = t2.getBusBreakerView().getBus();
            if (b1 != null && b2 != null) {
                if (busList.contains(b1) && busList.contains(b2)) {
                    addLine(line);
                } else {
                    nbNok++;
                }
            } else {
                nbNok++;
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Lines      total = <%5d> ok = <%5d> not = <%5d>", lineList.size() + nbNok, lineList.size(), nbNok));
        }
    }

    private void addTwoWindingsTransformer(TwoWindingsTransformer twt, AtomicInteger nbNok, AtomicInteger nbPtcNok) {
        Terminal t1 = twt.getTerminal1();
        Terminal t2 = twt.getTerminal2();
        Bus b1 = t1.getBusBreakerView().getBus();
        Bus b2 = t2.getBusBreakerView().getBus();
        if (b1 != null && b2 != null) {
            if (busList.contains(b1) && busList.contains(b2)) {
                addTwoWindingsTransformer(twt);
                if (twt.hasPhaseTapChanger()) {
                    addPhaseTapChanger(twt);
                }
            } else {
                nbNok.incrementAndGet();
                if (twt.hasPhaseTapChanger()) {
                    nbPtcNok.incrementAndGet();
                }
            }
        } else {
            nbNok.incrementAndGet();
        }
    }

    private void createTwoWindingsTransformersList() {
        AtomicInteger nbNok = new AtomicInteger(0);
        AtomicInteger nbPtcNok = new AtomicInteger(0);
        network.getTwoWindingsTransformers().forEach(twt -> addTwoWindingsTransformer(twt, nbNok, nbPtcNok));
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Twotrfo    total = <%5d> ok = <%5d> not = <%5d>", twoWindingsTransformerList.size() + nbNok.get(), twoWindingsTransformerList.size(), nbNok.get()));
            LOGGER.debug(String.format("PhaseTC    total = <%5d> ok = <%5d> not = <%5d>", phaseTapChangerList.size() + nbPtcNok.get(), phaseTapChangerList.size(), nbPtcNok.get()));
        }
    }

    private void createThreeWindingsTransformersList() {
        int nbNok = 0;
        for (ThreeWindingsTransformer twt : network.getThreeWindingsTransformers()) {
            ThreeWindingsTransformer.Leg leg1 = twt.getLeg1();
            ThreeWindingsTransformer.Leg leg2 = twt.getLeg2();
            ThreeWindingsTransformer.Leg leg3 = twt.getLeg3();
            Terminal t1 = leg1.getTerminal();
            Terminal t2 = leg2.getTerminal();
            Terminal t3 = leg3.getTerminal();
            Bus b1 = t1.getBusBreakerView().getBus();
            Bus b2 = t2.getBusBreakerView().getBus();
            Bus b3 = t3.getBusBreakerView().getBus();
            if (b1 != null && b2 != null && b3 != null) {
                if (busList.contains(b1) && busList.contains(b2) && busList.contains(b3)) {
                    addThreeWindingsTransformer(twt);
                } else {
                    nbNok++;
                }
            } else {
                nbNok++;
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Threetrfo  total = <%5d> ok = <%5d> not = <%5d>", threeWindingsTransformerList.size() + nbNok, threeWindingsTransformerList.size(), nbNok));
        }
    }

    private void createTransformerList() {

        // List the TwoWindingsTransformers
        createTwoWindingsTransformersList();

        // List the ThreeWindingsTransformers
        createThreeWindingsTransformersList();
    }

    private void createDanglingLineList() {
        int nbNok = 0;
        for (DanglingLine dl : network.getDanglingLines()) {
            Terminal t = dl.getTerminal();
            Bus b = t.getBusBreakerView().getBus();
            if (b != null) {
                if (busList.contains(b)) {
                    addDanglingLine(dl);
                } else {
                    nbNok++;
                }
            } else {
                nbNok++;
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Dangling   total = <%5d> ok = <%5d> not = <%5d>", danglingLineList.size() + nbNok, danglingLineList.size(), nbNok));
        }
    }

    private void createSwitchList() {
        int nbNok = 0;
        for (VoltageLevel vl : network.getVoltageLevels()) {
            for (Switch sw : vl.getBusBreakerView().getSwitches()) {
                if (sw.isRetained() && !sw.isOpen()) {
                    Bus b1 = sw.getVoltageLevel().getBusBreakerView().getBus1(sw.getId());
                    Bus b2 = sw.getVoltageLevel().getBusBreakerView().getBus2(sw.getId());
                    if (b1 != null && b2 != null && busList.contains(b1) && busList.contains(b2)) {
                        addSwitch(sw);
                    } else {
                        nbNok++;
                    }
                } else {
                    nbNok++;
                }
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Switches    total = <%5d> ok = <%5d> not = <%5d>", switchList.size() + nbNok, switchList.size(), nbNok));
        }
    }

    private void createHvdcLineList() {
        int nbNok = 0;
        for (HvdcLine line : network.getHvdcLines()) {
            Terminal t1 = line.getConverterStation1().getTerminal();
            Bus b1 = t1.getBusBreakerView().getBus();
            Terminal t2 = line.getConverterStation2().getTerminal();
            Bus b2 = t2.getBusBreakerView().getBus();
            if (b1 != null && b2 != null) {
                if (busList.contains(b1) && busList.contains(b2)) {
                    addHvdcLine(line);
                } else {
                    nbNok++;
                }
            } else {
                nbNok++;
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Hvdc       total = <%5d> ok = <%5d> not = <%5d>", hvdcLineList.size() + nbNok, hvdcLineList.size(), nbNok));
        }
    }

    private void createBusList() {
        int nbNok = 0;
        for (VoltageLevel vl : network.getVoltageLevels()) {
            for (Bus bus : vl.getBusBreakerView().getBuses()) {
                if (bus.isInMainConnectedComponent()) {
                    addBus(bus);
                    addCountry(getCountryCode(vl));
                } else {
                    nbNok++;
                }
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Bus        total = <%5d> ok = <%5d> not = <%5d>", busList.size() + nbNok, busList.size(), nbNok));
        }
    }

    private void createContingencyList(ContingenciesProvider provider, boolean propagate) {

        if (Objects.isNull(provider)) {
            return;
        }

        List<Contingency> ctyList = provider.getContingencies(network);
        ctyList.forEach(contingency -> addContingencyToList(contingency, propagate));

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Cty        total = <%5d> ok = <%5d> not = <%5d>", contingencyList.size(), ctyList.size(), contingencyList.size() - ctyList.size()));
        }
    }

    private void addContingencyToList(Contingency contingency, boolean propagate) {
        boolean ctyOk = true;
        for (ContingencyElement element : contingency.getElements()) {
            boolean elemOk = true;
            Identifiable<?> identifiable = network.getIdentifiable(element.getId());
            if (identifiable == null || !MetrixInputAnalysis.isValidContingencyElement(identifiable.getType(), element.getType())) {
                elemOk = false;
            }
            if (!elemOk) {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn(String.format("Contingency '%s' : element '%s' not found in the network", contingency.getId(), element.getId()));
                }
                ctyOk = false;
            }
        }
        if (ctyOk) {
            if (propagate) {
                // replace elements with new elements from propagation
                // this will keep the original extensions
                List<ContingencyElement> extendedElements = new ArrayList<>(getElementsToTrip(contingency, true));
                Collection<ContingencyElement> originalElements = new ArrayList<>(contingency.getElements());
                originalElements.forEach(contingency::removeElement);
                extendedElements.forEach(contingency::addElement);
            }
            contingencyList.add(contingency);
        }
    }

    private void setSwitchRetainToFalse() {
        for (VoltageLevel vl : network.getVoltageLevels()) {
            if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) {
                for (Switch sw : vl.getNodeBreakerView().getSwitches()) {
                    sw.setRetained(false);
                }
            }
        }
    }

    private void init() {
        createBusList();
        createGeneratorList();
        createLineList();
        createTransformerList();
        createDanglingLineList();
        createSwitchList();
        createLoadList();
        createHvdcLineList();
    }

    public static MetrixNetwork create(Network network) {
        return create(network, null, null, new MetrixParameters(), (Path) null);
    }

    public static MetrixNetwork create(Network network, ContingenciesProvider contingenciesProvider, Set<String> mappedSwitches,
                                       MetrixParameters parameters, Path remedialActionFile) {
        Reader reader = null;
        if (remedialActionFile != null) {
            try {
                reader = Files.newBufferedReader(remedialActionFile, StandardCharsets.UTF_8);
            } catch (IOException e) {
                LOGGER.error("Failed to read remedialActionFile {}", remedialActionFile, e);
                throw new UncheckedIOException(e);
            }
        }
        return create(
                network,
                contingenciesProvider,
                mappedSwitches,
                parameters,
                reader
        );
    }

    public static MetrixNetwork create(Network network, ContingenciesProvider contingenciesProvider, Set<String> mappedSwitches,
                                       MetrixParameters parameters, Reader remedialActionReader) {

        Objects.requireNonNull(network);
        Objects.requireNonNull(parameters);

        MetrixNetwork metrixNetwork = new MetrixNetwork(network);

        // Create contingencies list
        metrixNetwork.createContingencyList(contingenciesProvider, parameters.isPropagateBranchTripping());

        // Create opened and switch-retained lists (network will be modified)
        List<Remedial> remedials = RemedialReader.parseFile(remedialActionReader);
        Set<String> retainedElements = Stream.concat(
                remedials.stream().flatMap(remedial -> remedial.getBranchToOpen().stream()),
                remedials.stream().flatMap(remedial -> remedial.getBranchToClose().stream())
        ).collect(Collectors.toSet());
        Set<String> openedElements = remedials.stream().flatMap(remedial -> remedial.getBranchToClose().stream()).collect(Collectors.toSet());
        if (!Objects.isNull(mappedSwitches)) {
            // close mapped switches as their openness is set via mapping
            for (String switchId : mappedSwitches) {
                network.getSwitch(switchId).setOpen(false);
            }
            retainedElements.addAll(mappedSwitches);
        }
        metrixNetwork.setSwitchRetainToFalse();
        metrixNetwork.createOpenedBranchesList(openedElements);
        metrixNetwork.createRetainedBreakersList(retainedElements);
        metrixNetwork.init();

        return metrixNetwork;
    }

    private void createRetainedBreakersList(Set<String> breakerList) {
        for (String breakerId : breakerList) {

            if (breakerId == null || breakerId.isEmpty()) {
                throw new PowsyblException("Empty switch name in configuration");
            }

            Switch sw = network.getSwitch(breakerId);

            // Stop the step if needed
            boolean skipThisStep = false;
            if (sw == null) {
                LOGGER.debug("Switch '{}' not found or not a switch", breakerId);
                skipThisStep = true;
            } else if (sw.isOpen()) {
                LOGGER.warn("Switch '{}' is opened in basecase", breakerId);
                skipThisStep = true;
            }
            if (skipThisStep) {
                continue;
            }

            addElementToRetainedBreakersList(sw);
        }
    }

    private void addElementToRetainedBreakersList(Switch sw) {
        String switchId = sw.getId();

        VoltageLevel voltageLevel = sw.getVoltageLevel();

        if (voltageLevel.getTopologyKind() == TopologyKind.NODE_BREAKER) {
            // Get the terminals on both sides of the switch
            VoltageLevel.NodeBreakerView nodeBreakerView = voltageLevel.getNodeBreakerView();
            Terminal terminal1 = nodeBreakerView.getTerminal1(switchId);
            Terminal terminal2 = nodeBreakerView.getTerminal2(switchId);

            // Check on both sides of the switch to find a connectable that is not another switch nor a bus bar section
            if (isSwitchNotConnectedToOtherSwitchOrBbs(terminal1)) {
                addElementToRetainedBreakersList(sw, terminal1, true);
            } else if (isSwitchNotConnectedToOtherSwitchOrBbs(terminal2)) {
                addElementToRetainedBreakersList(sw, terminal2, true);
            } else {
                // Both sides have either a switch or a bus bar section : the switch is registered by itself
                addElementToRetainedBreakersList(sw, switchId, true);
            }
        } else if (voltageLevel.getTopologyKind() == TopologyKind.BUS_BREAKER) {
            // Get the terminals on both sides of the switch
            VoltageLevel.BusBreakerView busBreakerView = voltageLevel.getBusBreakerView();
            List<? extends Terminal> terminalsBus1 = busBreakerView.getBus1(switchId).getConnectedTerminalStream().toList();
            List<? extends Terminal> terminalsBus2 = busBreakerView.getBus2(switchId).getConnectedTerminalStream().toList();

            // Check on both sides of the switch to check if there is one and only one connectable
            if (terminalsBus1.size() == 1) {
                addElementToRetainedBreakersList(sw, terminalsBus1.get(0), false);
            } else if (terminalsBus2.size() == 1) {
                addElementToRetainedBreakersList(sw, terminalsBus2.get(0), false);
            } else {
                addElementToRetainedBreakersList(sw, switchId, false);
            }
        }
    }

    private boolean isSwitchNotConnectedToOtherSwitchOrBbs(Terminal terminal) {
        return terminal != null && terminal.getConnectable().getType() != IdentifiableType.BUSBAR_SECTION;
    }

    private void addElementToRetainedBreakersList(Switch sw, Terminal terminal, boolean setRetained) {
        String switchId = sw.getId();
        switch (terminal.getConnectable().getType()) {
            // Since switches connected to lines and TWT are "replaced" by those connectables, no need to set them retained
            case LINE, TWO_WINDINGS_TRANSFORMER -> addElementToRetainedBreakersList(sw, terminal.getConnectable().getId(), false);
            case LOAD, GENERATOR -> addElementToRetainedBreakersList(sw, switchId, setRetained);
            case DANGLING_LINE, HVDC_CONVERTER_STATION, SHUNT_COMPENSATOR, STATIC_VAR_COMPENSATOR,
                 THREE_WINDINGS_TRANSFORMER -> {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Unsupported connectable type ({}) for switch '{}'", terminal.getConnectable().getType(), switchId);
                }
            }
            default ->
                throw new PowsyblException("Unexpected connectable type : " + terminal.getConnectable().getType());
        }
    }

    private void addElementToRetainedBreakersList(Switch sw, String id, boolean setRetained) {
        if (setRetained) {
            sw.setRetained(true);
        }
        mappedSwitchMap.put(sw.getId(), id);
    }

    private void createOpenedBranchesList(Set<String> openedBranches) {
        for (String branchId : openedBranches) {
            Identifiable<?> identifiable = network.getIdentifiable(branchId);
            if (identifiable != null) {
                addElementToOpenedBranchesList(identifiable);
            } else {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Opened branch '{}' is missing in the network", branchId);
                }
            }
        }
    }

    private void addElementToOpenedBranchesList(Identifiable<?> identifiable) {
        if (identifiable instanceof Branch<?> branchToClose) {
            if (!branchToClose.getTerminal1().isConnected() || !branchToClose.getTerminal2().isConnected()) {
                closeBranch(branchToClose);
                disconnectedElements.add(branchToClose);
            }
        } else if (identifiable instanceof Switch switchToClose) {
            if (switchToClose.isOpen()) {
                closeSwitch(switchToClose);
                disconnectedElements.add(switchToClose);
            }
        } else {
            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn("Unsupported open branch type : {}", identifiable.getClass());
            }
        }
    }

    private void closeBranch(Branch<?> branchToClose) {
        branchToClose.getTerminal1().connect();
        branchToClose.getTerminal2().connect();
        if (branchToClose.getTerminal1().isConnected() && branchToClose.getTerminal2().isConnected()) {
            LOGGER.debug("Reconnecting open branch : {}", branchToClose.getId());
        } else {
            LOGGER.warn("Unable to reconnect open branch : {}", branchToClose.getId());
        }
    }

    private void closeSwitch(Switch switchToClose) {
        switchToClose.setOpen(false);
        switchToClose.setRetained(true);
        LOGGER.debug("Reconnecting open switch : {}", switchToClose.getId());
    }

    Optional<String> getMappedBranch(Switch sw) {
        if (!Objects.isNull(sw)) {
            return Optional.ofNullable(mappedSwitchMap.get(sw.getId()));
        }
        return Optional.empty();
    }

    public Set<ContingencyElement> getElementsToTrip(Contingency contingency, boolean propagate) {

        if (!propagate) {
            return new HashSet<>(contingency.getElements());
        } else {

            Set<ContingencyElement> elementsToTrip = new HashSet<>();

            Set<Switch> switchesToOpen = new HashSet<>();
            Set<Terminal> terminalsToDisconnect = new HashSet<>();

            for (ContingencyElement element : contingency.getElements()) {
                if (element.getType() == ContingencyElementType.GENERATOR ||
                        element.getType() == ContingencyElementType.HVDC_LINE) {
                    elementsToTrip.add(element);
                } else {
                    Tripping modification = element.toModification();
                    modification.traverse(network, switchesToOpen, terminalsToDisconnect);
                }
            }

            Set<IdentifiableType> types = EnumSet.of(IdentifiableType.LINE,
                    IdentifiableType.TWO_WINDINGS_TRANSFORMER,
                    IdentifiableType.THREE_WINDINGS_TRANSFORMER,
                    IdentifiableType.HVDC_CONVERTER_STATION);

            // disconnect equipments and open switches
            for (Switch s : switchesToOpen) {
                VoltageLevel.NodeBreakerView nodeBreakerView = s.getVoltageLevel().getNodeBreakerView();
                terminalsToDisconnect.add(nodeBreakerView.getTerminal1(s.getId()));
                terminalsToDisconnect.add(nodeBreakerView.getTerminal2(s.getId()));
            }
            terminalsToDisconnect.stream()
                    .filter(Objects::nonNull)
                    .forEach(t -> {
                        Connectable<?> connectable = t.getConnectable();
                        if (connectable != null && types.contains(connectable.getType())) {
                            elementsToTrip.add(new BranchContingency(connectable.getId()));
                        }
                    });
            return elementsToTrip;
        }
    }
}