Z0BusGroup.java

/**
 * Copyright (c) 2019, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.loadflow.resultscompletion.z0flows;

import java.util.*;

import org.jgrapht.alg.interfaces.SpanningTreeAlgorithm.SpanningTree;
import org.jgrapht.alg.spanning.KruskalMinimumSpanningTree;
import org.jgrapht.graph.SimpleWeightedGraph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.powsybl.iidm.network.Bus;
import com.powsybl.iidm.network.Line;
import com.powsybl.iidm.network.Terminal;

/**
 * @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
 */
public class Z0BusGroup {

    public Z0BusGroup(Bus bus, Z0LineChecker z0checker) {
        this.seed = bus;
        this.z0checker = z0checker;
    }

    public boolean contains(Bus bus) {
        return buses.contains(bus);
    }

    public boolean valid() {
        return buses.size() > 1 || loops != null && !loops.isEmpty();
    }

    public void exploreZ0(Set<Bus> processed) {
        Objects.requireNonNull(processed);

        buses.add(seed);
        processed.add(seed);
        int k = 0;
        while (k < buses.size()) {
            Bus b = buses.get(k);
            b.getLineStream().forEach(line -> {
                Bus other = other(line, b);
                if (other != null && z0checker.isZ0(line)) {
                    if (b == other) {
                        addLoop(line);
                    } else {
                        addToGraph(line, b, other);
                        if (!buses.contains(other)) {
                            buses.add(other);
                            processed.add(other);
                        }
                    }
                }
            });
            k++;
        }
    }

    public void complete() {
        if (!valid()) {
            LOG.warn("Z0 flow group not valid for seed bus {}", seed);
            return;
        }
        if (loops != null) {
            assignZeroFlowToLoops();
        }
        if (graph != null) {
            computeTreeFromGraph();
            assignZeroFlowToEdgesOutsideTree();
            completeFlowsForEdgesInsideTree();
        }
    }

    private void computeTreeFromGraph() {
        Objects.requireNonNull(graph);
        tree = new KruskalMinimumSpanningTree<>(graph).getSpanningTree();
        levels = new ArrayList<>();
        parent = new HashMap<>();
        Set<Line> processed = new HashSet<>();
        Map<Bus, List<Z0Edge>> linesInBus = new HashMap<>();

        // Map to iterate once over the edges of the bus
        tree.getEdges().forEach(e -> {
            Bus b1 = e.getLine().getTerminal1().getBusView().getBus();
            Bus b2 = e.getLine().getTerminal2().getBusView().getBus();

            linesInBus.computeIfAbsent(b1, b -> new ArrayList<>()).add(e);
            linesInBus.computeIfAbsent(b2, b -> new ArrayList<>()).add(e);
        });

        // Add root level
        Bus root = buses.get(0);
        levels.add(new ArrayList<>(Collections.singleton(root)));

        // Build levels of the tree
        int level = 0;
        while (level < levels.size()) {
            List<Bus> nextLevel = new ArrayList<>();
            levels.get(level).forEach(bus -> linesInBus.get(bus).forEach(e -> {
                Bus other = other(e.getLine(), bus);
                if (other == null) {
                    return;
                }
                if (processed.contains(e.getLine())) {
                    return;
                }
                nextLevel.add(other);
                parent.put(other, e.getLine());
                processed.add(e.getLine());
            }));
            if (!nextLevel.isEmpty()) {
                levels.add(nextLevel);
            }
            level++;
        }
    }

    private void assignZeroFlowToEdgesOutsideTree() {
        Objects.requireNonNull(graph);
        graph.edgeSet().forEach(e -> {
            if (!tree.getEdges().contains(e)) {
                assignZeroFlowTo(e.getLine());
            }
        });
    }

    private void assignZeroFlowToLoops() {
        Objects.requireNonNull(loops);
        loops.forEach(this::assignZeroFlowTo);
    }

    private void assignZeroFlowTo(Line line) {
        Objects.requireNonNull(line);
        line.getTerminal1().setP(0.0);
        line.getTerminal1().setQ(0.0);
        line.getTerminal2().setP(0.0);
        line.getTerminal2().setQ(0.0);
        if (line.getB1() != 0.0 || line.getB2() != 0.0
                || line.getG1() != 0.0 || line.getG2() != 0.0) {
            LOG.error("Z0 line {} has B1, G1, B2, G2 != 0", line);
        }
    }

    private void completeFlowsForEdgesInsideTree() {
        // Traverse the tree from leaves to root
        // (The root itself does not need to be processed)
        int level = levels.size() - 1;
        while (level >= 1) {
            levels.get(level).forEach(bus -> {
                Line line = parent.get(bus);
                new Z0FlowFromBusBalance(bus, line).complete();
            });
            level--;
        }
    }

    private static Bus other(Line line, Bus bus) {
        Terminal t = BranchTerminal.ofOtherBus(line, bus);
        if (t == null) {
            return null;
        }
        return t.getBusView().getBus();
    }

    private void addToGraph(Line line, Bus b1, Bus b2) {
        if (graph == null) {
            // Lazy creation of graph
            graph = new SimpleWeightedGraph<>(Z0Edge.class);
        }
        graph.addVertex(b1);
        graph.addVertex(b2);
        graph.addEdge(b1, b2, new Z0Edge(line));
    }

    private void addLoop(Line line) {
        if (loops == null) {
            loops = new ArrayList<>();
        }
        loops.add(line);
    }

    private final Bus seed;
    private final Z0LineChecker z0checker;
    private final List<Bus> buses = new ArrayList<>();

    private SimpleWeightedGraph<Bus, Z0Edge> graph;
    private SpanningTree<Z0Edge> tree;
    private List<List<Bus>> levels;
    private Map<Bus, Line> parent;

    // Lines with same bus at both ends
    private List<Line> loops;

    private static final Logger LOG = LoggerFactory.getLogger(Z0BusGroup.class);
}