Subsection.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/.
*/
package com.powsybl.sld.layout.position;
import com.powsybl.commons.PowsyblException;
import com.powsybl.sld.model.blocks.BodyPrimaryBlock;
import com.powsybl.sld.model.cells.*;
import com.powsybl.sld.model.coordinate.Orientation;
import com.powsybl.sld.model.coordinate.Side;
import com.powsybl.sld.model.graphs.VoltageLevelGraph;
import com.powsybl.sld.model.nodes.BusNode;
import com.powsybl.sld.model.nodes.ConnectivityNode;
import com.powsybl.sld.model.nodes.FeederNode;
import com.powsybl.sld.model.nodes.Node;
import com.powsybl.sld.util.GraphTraversal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.powsybl.sld.model.coordinate.Side.LEFT;
import static com.powsybl.sld.model.coordinate.Side.RIGHT;
/**
* @author Benoit Jeanson {@literal <benoit.jeanson at rte-france.com>}
*/
public class Subsection {
private final int size;
private final BusNode[] busNodes;
private final Set<InternCellSide> internCellSides = new LinkedHashSet<>();
private final List<ExternCell> externCells = new ArrayList<>();
private final List<ArchCell> archCells = new ArrayList<>();
private static final Comparator<BusCell> COMPARE_ORDER = Comparator
.comparingInt(extCell -> extCell.getOrder().orElse(-1));
private static final Logger LOGGER = LoggerFactory.getLogger(Subsection.class);
Subsection(int size) {
this.size = size;
busNodes = new BusNode[size];
}
private boolean checkAbsorbability(Set<BusNode> extendedNodeSet) {
return extendedNodeSet.stream().noneMatch(busNode -> {
int vIndex = busNode.getBusbarIndex() - 1;
return busNodes[vIndex] != null && busNodes[vIndex] != busNode;
});
}
private void addVerticalBusSet(VerticalBusSet vbs, Set<BusNode> extendedNodeSet) {
extendedNodeSet.forEach(bus -> busNodes[bus.getBusbarIndex() - 1] = bus);
externCells.addAll(vbs.getExternCells());
externCells.sort(COMPARE_ORDER);
archCells.addAll(vbs.getArchCells());
archCells.sort(COMPARE_ORDER);
internCellSides.addAll(vbs.getInternCellSides());
}
public int getSize() {
return size;
}
public BusNode[] getBusNodes() {
return busNodes;
}
BusNode getBusNode(int index) {
return busNodes[index];
}
List<InternCell> getInternCells(InternCell.Shape shape, Side side) {
return internCellSides.stream()
.filter(ics -> ics.getCell().checkIsShape(shape) && ics.getSide() == side)
.map(InternCellSide::getCell).collect(Collectors.toList());
}
List<InternCell> getVerticalInternCells() {
return internCellSides.stream()
.filter(ics -> ics.getCell().checkIsShape(InternCell.Shape.VERTICAL)
|| ics.getCell().checkIsShape(InternCell.Shape.ONE_LEG))
.map(InternCellSide::getCell).collect(Collectors.toList());
}
public List<ExternCell> getExternCells() {
return externCells;
}
public List<ArchCell> getArchCells() {
return archCells;
}
private boolean containsAllBusNodes(List<BusNode> nodes) {
return Arrays.asList(busNodes).containsAll(nodes);
}
static List<Subsection> createSubsections(VoltageLevelGraph graph, BSCluster bsCluster, Map<BusNode, Integer> busToNb, boolean handleShunts) {
List<Subsection> subsections = new ArrayList<>();
int vSize = graph.getMaxVerticalBusPosition();
Subsection currentSubsection = new Subsection(vSize);
subsections.add(currentSubsection);
int i = 0;
for (VerticalBusSet vbs : bsCluster.getVerticalBusSets()) {
Set<BusNode> extendedNodeSet = new TreeSet<>(Comparator.comparingInt(busToNb::get));
List<BusNode> vbn = bsCluster.getVerticalBusNodes(i);
if (vbs.getBusNodeSet().containsAll(vbn)) {
// The given busNodes correspond to all vertical bus nodes for a specific index of the horizontalBusLanes:
// those nodes correspond to a slice of busbars.
// There can't be more than one busNode per busbar index, we check this by creating a HashSet with busbar indices
Set<Integer> indices = new HashSet<>();
for (BusNode busNode : vbn) {
if (!indices.add(busNode.getBusbarIndex())) {
throw new PowsyblException("Inconsistent legBusSet: extended node set contains two busNodes with same index");
}
}
extendedNodeSet.addAll(vbn);
} else {
LOGGER.error("ExtendedNodeSet inconsistent with NodeBusSet");
}
if (!currentSubsection.checkAbsorbability(extendedNodeSet)) {
currentSubsection = new Subsection(vSize);
subsections.add(currentSubsection);
}
currentSubsection.addVerticalBusSet(vbs, extendedNodeSet);
i++;
}
internCellCoherence(graph, bsCluster.getVerticalBusSets(), subsections);
graph.getShuntCellStream().forEach(ShuntCell::alignExternCells);
if (handleShunts) {
shuntCellCoherence(graph, subsections);
}
return subsections;
}
private static void internCellCoherence(VoltageLevelGraph vlGraph, List<VerticalBusSet> vbsList, List<Subsection> subsections) {
identifyOneLegInternCells(vlGraph, subsections);
identifyVerticalInternCells(vlGraph, subsections);
identifyFlatInternCells(vbsList);
identifyCrossOverAndCheckOrientation(subsections);
slipInternCellSideToEdge(subsections);
}
private static void identifyOneLegInternCells(VoltageLevelGraph graph, List<Subsection> subsections) {
graph.getInternCellStream()
.filter(c -> c.checkIsShape(InternCell.Shape.MAYBE_FLAT, InternCell.Shape.UNDEFINED))
.filter(c -> c.getBodyBlock() instanceof BodyPrimaryBlock bpb && bpb.getNodes().size() == 1 && bpb.getNodes().get(0) instanceof ConnectivityNode)
.forEach(c -> subsections.stream().filter(subsection -> subsection.containsAllBusNodes(c.getBusNodes())).findFirst().ifPresent(s -> {
c.replaceBackMultiLegByOneLeg();
s.internCellSides.removeIf(ics -> ics.getCell() == c);
s.internCellSides.add(new InternCellSide(c, Side.UNDEFINED));
}));
}
private static void identifyVerticalInternCells(VoltageLevelGraph graph, List<Subsection> subsections) {
Map<InternCell, Subsection> verticalCells = new LinkedHashMap<>();
graph.getInternCellStream()
.filter(c -> c.checkIsNotShape(InternCell.Shape.ONE_LEG, InternCell.Shape.UNHANDLED_PATTERN))
.forEach(c ->
subsections.stream()
.filter(subsection -> subsection.containsAllBusNodes(c.getBusNodes()))
.findFirst().ifPresent(subsection -> verticalCells.putIfAbsent(c, subsection)));
verticalCells.forEach((cell, sub) -> {
cell.setShape(InternCell.Shape.VERTICAL);
sub.internCellSides.removeIf(ics -> ics.getCell() == cell);
sub.internCellSides.add(new InternCellSide(cell, Side.UNDEFINED));
});
}
private static void identifyFlatInternCells(List<VerticalBusSet> vbsList) {
vbsList.stream()
.flatMap(vbs -> vbs.getInternCellsFromShape(InternCell.Shape.MAYBE_FLAT).stream())
.distinct()
.forEach(internCell -> {
List<BusNode> buses = internCell.getBusNodes();
if (Math.abs(buses.get(1).getSectionIndex() - buses.get(0).getSectionIndex()) == 1 && buses.get(1).getBusbarIndex() == buses.get(0).getBusbarIndex()) {
internCell.setFlat();
internCell.getRootBlock().setOrientation(Orientation.RIGHT);
} else {
internCell.setShape(InternCell.Shape.CROSSOVER);
}
});
}
private static void identifyCrossOverAndCheckOrientation(List<Subsection> subsections) {
final class SideSs {
private Side side;
private Subsection ss;
private SideSs(Side side, Subsection ss) {
this.side = side;
this.ss = ss;
}
}
Map<InternCell, List<SideSs>> cellToSideSs = new LinkedHashMap<>();
for (Subsection ss : subsections) {
ss.internCellSides.stream()
.filter(ics -> {
InternCell.Shape shape = ics.getCell().getShape();
return shape == InternCell.Shape.UNDEFINED
|| shape == InternCell.Shape.FLAT
|| shape == InternCell.Shape.CROSSOVER;
})
.forEach(ics -> {
cellToSideSs.putIfAbsent(ics.getCell(), new ArrayList<>());
cellToSideSs.get(ics.getCell()).add(new SideSs(ics.getSide(), ss));
});
}
cellToSideSs.forEach((cell, sideSses) -> {
if (sideSses.size() == 2) {
if (!cell.checkIsShape(InternCell.Shape.FLAT)) {
cell.setShape(InternCell.Shape.CROSSOVER);
}
if (sideSses.get(0).side == RIGHT) {
cell.reverseCell();
sideSses.stream().flatMap(sss -> sss.ss.internCellSides.stream())
.filter(ics -> ics.getCell() == cell)
.forEach(InternCellSide::flipSide);
}
}
});
}
private static void slipInternCellSideToEdge(List<Subsection> subsections) {
Map<InternCellSide, Subsection> cellSideToMove = new LinkedHashMap<>();
new ArrayList<>(subsections).forEach(ss -> {
List<InternCellSide> cellToRemove = new ArrayList<>();
ss.internCellSides.stream()
.filter(ics -> ics.getCell().checkIsShape(InternCell.Shape.FLAT, InternCell.Shape.CROSSOVER))
.forEach(ics -> {
List<BusNode> nodes = ics.getCell().getSideBusNodes(ics.getSide());
List<Subsection> candidateSss = subsections.stream().filter(ss2 -> ss2.containsAllBusNodes(nodes)).collect(Collectors.toList());
if (!candidateSss.isEmpty()) {
Subsection candidateSs = ics.getSide() == LEFT ? candidateSss.get(candidateSss.size() - 1) : candidateSss.get(0);
if (ss != candidateSs) {
cellToRemove.add(ics);
cellSideToMove.put(ics, candidateSs);
}
}
});
ss.internCellSides.removeAll(cellToRemove);
});
cellSideToMove.forEach((cellSide, ss) -> ss.internCellSides.add(cellSide));
}
private static void shuntCellCoherence(VoltageLevelGraph vlGraph, List<Subsection> subsections) {
Map<ShuntCell, List<BusNode>> shuntCells2Buses = vlGraph.getShuntCellStream()
.collect(Collectors.toMap(Function.identity(), ShuntCell::getParentBusNodes, (u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
}, LinkedHashMap::new));
if (shuntCells2Buses.isEmpty()) {
return;
}
List<ShuntCell> sameSubsectionShunts = identifySameSubsectionShuntCells(subsections, shuntCells2Buses);
slipInternShuntedCellsToEdge(subsections, shuntCells2Buses.keySet(), sameSubsectionShunts);
alignMultiFeederShunt(shuntCells2Buses.keySet());
arrangeExternCellsOrders(subsections);
}
private static List<ShuntCell> identifySameSubsectionShuntCells(List<Subsection> subsections, Map<ShuntCell, List<BusNode>> shuntCells2Buses) {
List<ShuntCell> modifiedShunts = new ArrayList<>();
subsections.forEach(ss -> shuntCells2Buses.keySet().stream()
.filter(sc -> ss.containsAllBusNodes(shuntCells2Buses.get(sc)))
.forEach(sc -> {
sc.getSideCells().forEach(c -> moveExternCellToSubsection(c, ss, subsections, Side.UNDEFINED));
int iLeft = ss.externCells.indexOf(sc.getSideCell(LEFT));
int iRight = ss.externCells.indexOf(sc.getSideCell(RIGHT));
if (iRight != iLeft + 1) {
ExternCell leftCell = sc.getSideCell(LEFT);
ss.externCells.remove(leftCell);
ss.externCells.add(ss.externCells.indexOf(sc.getSideCell(RIGHT)), leftCell);
}
modifiedShunts.add(sc);
})
);
return modifiedShunts;
}
private static void slipInternShuntedCellsToEdge(List<Subsection> subsections, Set<ShuntCell> shuntCells, List<ShuntCell> sameSubsectionShunts) {
shuntCells.stream().filter(sc -> !sameSubsectionShunts.contains(sc))
.forEach(sc -> {
for (Side side : Side.defined()) {
ExternCell cell = sc.getSideCell(side);
subsections.stream().filter(ss -> ss.containsAllBusNodes(cell.getBusNodes()))
.map(subsections::indexOf).mapToInt(j -> side == LEFT ? j : -j).max()
.ifPresent(j -> moveExternCellToSubsection(cell, subsections.get(Math.abs(j)), subsections,
side.getFlip()));
}
});
}
private static void moveExternCellToSubsection(ExternCell c, Subsection ss, List<Subsection> subsections, Side side) {
if (ss.externCells.contains(c) && side == Side.UNDEFINED) {
return;
}
for (Subsection sub : subsections) {
if (sub.externCells.contains(c)) {
sub.externCells.remove(c);
break;
}
}
if (side == LEFT) {
ss.externCells.add(0, c);
} else {
ss.externCells.add(c);
}
}
private static void alignMultiFeederShunt(Set<ShuntCell> shCells) {
shCells.forEach(sc -> {
for (Side side : Side.defined()) {
ExternCell cell = sc.getSideCell(side);
List<FeederNode> feeders = cell.getFeederNodes();
if (feeders.size() > 1) {
Node shNode = sc.getSideShuntNode(side);
Set<Node> outsideNodes = new HashSet<>();
outsideNodes.add(shNode);
List<FeederNode> shuntSideFeederNodes = buildShuntSideFeederNodes(shNode, outsideNodes);
feeders.removeAll(shuntSideFeederNodes);
List<FeederNode> newlyOrderdFeeders;
if (side == RIGHT) {
newlyOrderdFeeders = shuntSideFeederNodes;
newlyOrderdFeeders.addAll(feeders);
} else {
newlyOrderdFeeders = feeders;
newlyOrderdFeeders.addAll(shuntSideFeederNodes);
}
for (int i = 0; i < newlyOrderdFeeders.size(); i++) {
newlyOrderdFeeders.get(i).setOrder(i);
}
}
}
});
}
private static List<FeederNode> buildShuntSideFeederNodes(Node shNode, Set<Node> outsideNodes) {
Objects.requireNonNull(shNode);
return shNode.getAdjacentNodes().stream().flatMap(node -> {
Set<Node> gtResult = new LinkedHashSet<>();
if (GraphTraversal.run(node, node1 -> node1.getType() == Node.NodeType.FEEDER, node1 -> node1.getType() == Node.NodeType.BUS, gtResult, outsideNodes)) {
return gtResult.stream().filter(n -> n.getType() == Node.NodeType.FEEDER).map(FeederNode.class::cast);
} else {
return Stream.empty();
}
}).collect(Collectors.toList());
}
private static void arrangeExternCellsOrders(List<Subsection> subsections) {
subsections.forEach(ss -> {
List<ExternCell> eCells = ss.getExternCells();
for (int i = 1; i < eCells.size(); i++) {
int prevIndex = eCells.get(i - 1).getOrder().orElse(-1);
if (eCells.get(i).getOrder().orElse(-1) <= prevIndex) {
eCells.get(i).setOrder(prevIndex + 1);
}
}
});
}
}