Link.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/.
 */
package com.powsybl.sld.layout.position.clustering;

import com.powsybl.sld.model.cells.ExternCell;
import com.powsybl.sld.model.cells.InternCell;
import com.powsybl.sld.model.cells.ShuntCell;
import com.powsybl.sld.model.coordinate.Side;
import com.powsybl.sld.model.nodes.BusNode;

import javax.annotation.Nonnull;
import java.util.*;
import java.util.stream.Collectors;

/**
 * A link is define between two VBSClusterSides (having the same implementation).
 * It implements Comparable that compares the strength of the link between two VBSClusterSides.
 * From the strongest to the weakest kind of link :
 * <ul>
 * <li>
 * having buses in common (the more there are the stronger is the link),
 * </li>
 * <li>
 * having flatcells in common (ie a cell with only two buses, one in each VBSClusterSide), the more there are,
 * the stronger is the link (for flatcell, the notion of distance to the edge is added
 * (in case of clustering by VBSClusterSide) to foster flatcell that are on the edge of the cluster)
 * </li>
 * <li>
 * having crossover cells in common defined as interncell that cannot be flatcell, and that potentially can span over
 * subsections
 * </li>
 * <li>
 * (TO BE IMPLEMENTED): having externCells bound by a SHUNT
 * </li>
 * </ul>
 * <p>
 * <p>
 * Sorting this way enables to foster merging of clusters according to the strength of the link.
 *
 * @author Benoit Jeanson {@literal <benoit.jeanson at rte-france.com>}
 */

class Link implements Comparable<Link> {

    enum LinkCategory {
        COMMON_BUSES, FLAT_CELLS, CROSSOVER, SHUNT
    }

    private final BSClusterSide bsClusterSide1;
    private final BSClusterSide bsClusterSide2;
    private final Map<LinkCategory, Integer> categoryToWeight = new EnumMap<>(LinkCategory.class);
    private final int nb;

    Link(BSClusterSide bsClusterSide1, BSClusterSide bsClusterSide2, int nb) {
        this.bsClusterSide1 = bsClusterSide1;
        this.bsClusterSide2 = bsClusterSide2;
        this.nb = nb;
        assessLink();
    }

    private void assessLink() {
        categoryToWeight.put(LinkCategory.COMMON_BUSES, assessCommonBusNodes());
        categoryToWeight.put(LinkCategory.FLAT_CELLS, assessFlatCell());
        categoryToWeight.put(LinkCategory.CROSSOVER, assessCrossOver());
        categoryToWeight.put(LinkCategory.SHUNT, assessShunt());
    }

    private int assessCommonBusNodes() {
        Set<BusNode> nodeBusesIntersect = new LinkedHashSet<>(bsClusterSide1.getBusNodeSet());
        nodeBusesIntersect.retainAll(bsClusterSide2.getBusNodeSet());
        return nodeBusesIntersect.size();
    }

    private int assessFlatCell() {
        Set<InternCell> flatCellIntersect = new LinkedHashSet<>(bsClusterSide1.getCandidateFlatCellList());
        flatCellIntersect.retainAll(bsClusterSide2.getCandidateFlatCellList());
        return flatCellIntersect.size() * 100
                - flatCellIntersect.stream()
                .mapToInt(internCell -> flatCellDistanceToEdges(internCell, bsClusterSide1, bsClusterSide2)).sum();
    }

    static int flatCellDistanceToEdges(InternCell cell, BSClusterSide bsCS1, BSClusterSide bsCS2) {
        return bsCS1.getCandidateFlatCellDistanceToEdge(cell) + bsCS2.getCandidateFlatCellDistanceToEdge(cell);
    }

    private int assessCrossOver() {
        Set<InternCell> commonInternCells = new LinkedHashSet<>(bsClusterSide1.getInternCellsFromShape(InternCell.Shape.UNDEFINED));
        commonInternCells.retainAll(bsClusterSide2.getInternCellsFromShape(InternCell.Shape.UNDEFINED));
        return (int) (commonInternCells.stream()
                .flatMap(internCell -> internCell.getBusNodes().stream()).distinct()
                .count());
    }

    private int assessShunt() {
        List<ExternCell> externCells1 = extractExternShuntedCells(bsClusterSide1);
        List<ExternCell> externCells2 = extractExternShuntedCells(bsClusterSide2);
        List<ShuntCell> shuntCells = extractShuntCells(externCells1);
        shuntCells.retainAll(extractShuntCells(externCells2));
        List<ExternCell> myShuntedExternCells = shuntCells.stream()
                .flatMap(sc -> sc.getSideCells().stream()).collect(Collectors.toList());
        externCells1.retainAll(myShuntedExternCells);
        externCells2.retainAll(myShuntedExternCells);
        return shuntAttractivity(externCells1, bsClusterSide1) + shuntAttractivity(externCells2, bsClusterSide2);
    }

    private List<ExternCell> extractExternShuntedCells(BSClusterSide bsClusterSide) {
        return bsClusterSide.getExternCells().stream().filter(ExternCell::isShunted).collect(Collectors.toList());
    }

    private List<ShuntCell> extractShuntCells(List<ExternCell> externCells) {
        return externCells.stream().map(ExternCell::getShuntCells).flatMap(List::stream).collect(Collectors.toList());
    }

    private int shuntAttractivity(List<ExternCell> cells, BSClusterSide bsClusterSide) {
        return cells.stream().mapToInt(bsClusterSide::getExternCellAttractionToEdge).sum();
    }

    private int getLinkCategoryWeight(LinkCategory cat) {
        return categoryToWeight.get(cat);
    }

    BSClusterSide getOtherBsClusterSide(BSClusterSide bsClusterSide) {
        if (bsClusterSide == bsClusterSide1) {
            return bsClusterSide2;
        }
        if (bsClusterSide == bsClusterSide2) {
            return bsClusterSide1;
        }
        return null;
    }

    BSClusterSide getBsClusterSide(int i) {
        if (i == 0) {
            return bsClusterSide1;
        } else if (i == 1) {
            return bsClusterSide2;
        }
        return null;
    }

    void mergeClusters() {
        if (bsClusterSide1.getCluster() == bsClusterSide2.getCluster()
                || bsClusterSide1.getMySideInCluster() == Side.UNDEFINED
                || bsClusterSide2.getMySideInCluster() == Side.UNDEFINED) {
            return;
        }
        bsClusterSide1.getCluster().merge(
                bsClusterSide1.getMySideInCluster(),
                bsClusterSide2.getCluster(),
                bsClusterSide2.getMySideInCluster(),
                PositionByClustering::mergeHorizontalBusLists);
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Link)) {
            return false;
        }
        return bsClusterSide1.equals(((Link) obj).bsClusterSide1)
                && bsClusterSide2.equals(((Link) obj).bsClusterSide2);
    }

    @Override
    public int hashCode() {
        return nb;
    }

    @Override
    public int compareTo(@Nonnull Link oLink) {
        for (LinkCategory category : LinkCategory.values()) {
            if (oLink.getLinkCategoryWeight(category) > getLinkCategoryWeight(category)) {
                return -1;
            }
            if (oLink.getLinkCategoryWeight(category) < getLinkCategoryWeight(category)) {
                return 1;
            }
        }
        return this.nb - oLink.nb;
    }

    @Override
    public String toString() {
        return "CommonBus: " + categoryToWeight.get(LinkCategory.COMMON_BUSES)
                + " FlatCell: " + categoryToWeight.get(LinkCategory.FLAT_CELLS)
                + " CrossOver: " + categoryToWeight.get(LinkCategory.CROSSOVER)
                + " Shunt: " + categoryToWeight.get(LinkCategory.SHUNT)
                + "\n\tvbsClusterSide1: " + bsClusterSide1.toString()
                + "\n\tvbsClusterSide2: " + bsClusterSide2.toString();
    }
}