Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,174 @@
*/
package org.gridsuite.network.map.dto.definition.extension;

import com.powsybl.iidm.network.IdentifiableType;
import com.powsybl.iidm.network.Switch;
import com.powsybl.iidm.network.Terminal;
import com.powsybl.math.graph.TraverseResult;
import com.powsybl.iidm.network.*;

import java.util.*;

/**
* @author Slimane Amar <slimane.amar at rte-france.com>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ghazwa

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*/
public class BusbarSectionFinderTraverser implements Terminal.TopologyTraverser {
// TODO : to remove when this class is available in network-store
public final class BusbarSectionFinderTraverser {

/**
* Private constructor to prevent instantiation of this utility class.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment to remove

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

*/
private BusbarSectionFinderTraverser() {
throw new UnsupportedOperationException();
}

/**
* Finds the best busbar section connected to the given terminal.
* Uses a breadth-first search algorithm to explore all possible paths.
*
* @param terminal the starting terminal
* @return the best busbar result according to selection criteria, or null if none found
*/
public static BusbarSectionResult findBestBusbar(Terminal terminal) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should return the busbar section id

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, you can remove findBusbarSectionId() and name this method findBusbarSectionId()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed to getBusbarSectionResult

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer not remove findBusbarSectionId, renamed to getBusbarSectionResult

VoltageLevel.NodeBreakerView view = terminal.getVoltageLevel().getNodeBreakerView();
int startNode = terminal.getNodeBreakerView().getNode();
List<BusbarSectionResult> allResults = searchAllBusbars(view, startNode);
if (allResults.isEmpty()) {
return null;
}
return selectBestBusbar(allResults);
}

/**
* Selects the best busbar from a list of candidates using a priority-based approach:
* Priority 1: Busbar with closed last switch (minimum depth, then minimum switches before last)
* Priority 2: Busbar with open last switch (minimum depth, then minimum switches before last)
* Priority 3: Busbar without switch (direct connection, minimum depth)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this one is the least prioritized strategy ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be the first one ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*
* @param results list of all found busbar results
* @return the best busbar according to selection criteria
*/
private static BusbarSectionResult selectBestBusbar(List<BusbarSectionResult> results) {
// Priority 1: Search for busbar with closed last switch
List<BusbarSectionResult> withClosedSwitch = results.stream().filter(r -> r.lastSwitch() != null && !r.lastSwitch().isOpen()).toList();
if (!withClosedSwitch.isEmpty()) {
return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth).thenComparingInt(BusbarSectionResult::switchesBeforeLast)).orElse(null);
}

// Priority 2: Search for busbar with open last switch
List<BusbarSectionResult> withOpenSwitch = results.stream().filter(r -> r.lastSwitch() != null && r.lastSwitch().isOpen()).toList();
if (!withOpenSwitch.isEmpty()) {
return withOpenSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth).thenComparingInt(BusbarSectionResult::switchesBeforeLast)).orElse(null);
}

// Priority 3: Busbars without switch direct connection
List<BusbarSectionResult> withoutSwitch = results.stream().filter(r -> r.lastSwitch() == null).toList();
if (!withoutSwitch.isEmpty()) {
return withoutSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth)).orElse(null);
}

private final boolean onlyConnectedBbs;
// Fallback: select first busbar
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to remove

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return results.getFirst();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be empty

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case is handled by returning result.busbarSectionId() when result is not null, and otherwise the Id of the first busbar section in the terminal’s voltage level

}

private String firstTraversedBbsId;
/**
* Searches all accessible busbars from a starting node using breadth-first search.
* Explores the node-breaker topology through switches.
*
* @param view the node-breaker view of the voltage level
* @param startNode the starting node index
* @return list of all busbar results found
*/
private static List<BusbarSectionResult> searchAllBusbars(VoltageLevel.NodeBreakerView view, int startNode) {
List<BusbarSectionResult> results = new ArrayList<>();
Set<Integer> visited = new HashSet<>();
Queue<NodePath> queue = new LinkedList<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Queue<NodePath> queue = new LinkedList<>();
Queue<NodePath> pathsToVisit = new LinkedList<>();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nodePathsToVisit maybe ?

queue.offer(new NodePath(startNode, new ArrayList<>(), null));
while (!queue.isEmpty()) {
NodePath currentNodePath = queue.poll();
if (!hasNotBeenVisited(currentNodePath.node(), visited)) {
continue;
}
visited.add(currentNodePath.node());
Optional<BusbarSectionResult> busbarSectionResult = tryCreateBusbarResult(view, currentNodePath);
if (busbarSectionResult.isPresent()) {
results.add(busbarSectionResult.get());
} else {
exploreAdjacentNodes(view, currentNodePath, visited, queue);
}
}
return results;
}

public BusbarSectionFinderTraverser(boolean onlyConnectedBbs) {
this.onlyConnectedBbs = onlyConnectedBbs;
private static boolean hasNotBeenVisited(int node, Set<Integer> visited) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static boolean hasNotBeenVisited(int node, Set<Integer> visited) {
private static boolean hasBeenVisited(int node, Set<Integer> visited) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return !visited.contains(node);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return !visited.contains(node);
return visited.contains(node);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

@Override
public TraverseResult traverse(Terminal terminal, boolean connected) {
if (terminal.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) {
firstTraversedBbsId = terminal.getConnectable().getId();
return TraverseResult.TERMINATE_TRAVERSER;
private static Optional<BusbarSectionResult> tryCreateBusbarResult(VoltageLevel.NodeBreakerView view, NodePath currentNodePath) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static Optional<BusbarSectionResult> tryCreateBusbarResult(VoltageLevel.NodeBreakerView view, NodePath currentNodePath) {
private static Optional<BusbarSectionResult> getDirectlyConnectedBusbarSection(VoltageLevel.NodeBreakerView view, NodePath currentNodePath) {

Something like this ? Here, we check if a busbar section is directly connected to the node and return it if yes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something better than getDirectlyConnectedBusbarSection() to be found

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findBusbarSectionAtNode maybe ?

Optional<Terminal> nodeTerminal = view.getOptionalTerminal(currentNodePath.node());
if (nodeTerminal.isEmpty()) {
return Optional.empty();
}
return TraverseResult.CONTINUE;
Terminal term = nodeTerminal.get();
// Check if current node is a busbar section
if (term.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) {
String busbarSectionId = term.getConnectable().getId();
int depth = currentNodePath.pathSwitches().size();
SwitchInfo lastSwitch = currentNodePath.lastSwitch();
int switchesBeforeLast = lastSwitch != null ? (depth - 1) : 0;
return Optional.of(new BusbarSectionResult(busbarSectionId, depth, switchesBeforeLast, lastSwitch));
}
return Optional.empty();
}

private static void exploreAdjacentNodes(VoltageLevel.NodeBreakerView view, NodePath currentNodePath, Set<Integer> visited, Queue<NodePath> queue) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static void exploreAdjacentNodes(VoltageLevel.NodeBreakerView view, NodePath currentNodePath, Set<Integer> visited, Queue<NodePath> queue) {
private static void exploreAdjacentNodes(VoltageLevel.NodeBreakerView view, NodePath currentNodePath, Set<Integer> visited, Queue<NodePath> pathsToVisit) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

view.getSwitchStream().forEach(sw -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not efficient. Maybe we can do something better

int node1 = view.getNode1(sw.getId());
int node2 = view.getNode2(sw.getId());
Optional<Integer> nextNode = getNextNodeIfAdjacent(currentNodePath.node(), node1, node2);
if (nextNode.isPresent() && !visited.contains(nextNode.get())) {
NodePath newPath = createNodePath(currentNodePath, sw, node1, node2, nextNode.get());
queue.offer(newPath);
}
});
}

@Override
public TraverseResult traverse(Switch aSwitch) {
if (onlyConnectedBbs && aSwitch.isOpen()) {
return TraverseResult.TERMINATE_PATH;
private static Optional<Integer> getNextNodeIfAdjacent(int currentNode, int node1, int node2) {
if (node1 == currentNode) {
return Optional.of(node2);
}
return TraverseResult.CONTINUE;
if (node2 == currentNode) {
return Optional.of(node1);
}
return Optional.empty();
}

private static NodePath createNodePath(NodePath currentNodePath, Switch sw, int node1, int node2, int nextNode) {
List<SwitchInfo> newPathSwitches = new ArrayList<>(currentNodePath.pathSwitches());
SwitchInfo switchInfo = new SwitchInfo(sw.getId(), sw.getKind(), sw.isOpen(), node1, node2);
newPathSwitches.add(switchInfo);
return new NodePath(nextNode, newPathSwitches, switchInfo);
}

public String getFirstTraversedBbsId() {
return firstTraversedBbsId;
/**
* Internal record to track the path during graph traversal.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments on records to remove ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

*/
private record NodePath(int node, List<SwitchInfo> pathSwitches, SwitchInfo lastSwitch) { }

/**
* Record containing information about a switch in the topology.
*/
public record SwitchInfo(String id, SwitchKind kind, boolean isOpen, int node1, int node2) { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int node1, int node2 are not used

Copy link
Contributor Author

@ghazwarhili ghazwarhili Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had used them for debugging purposes, but now we don't need them anymore
removed


/**
* Record containing the result of a busbar search with selection metadata.
*/
public record BusbarSectionResult(String busbarSectionId, int depth, int switchesBeforeLast, SwitchInfo lastSwitch) { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between depth and switchesBeforeLast ? It is redondant nope ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depth is not the same as switchesBeforeLast (busbarSectionId)
but switchesBeforeLast to be removed


/**
* Convenience method to get only the busbar ID.
*
* @param terminal the starting terminal
* @return the busbar ID or null if none found
*/
public static String findBusbarSectionId(Terminal terminal) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the records and this method just under the constructor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

BusbarSectionResult result = findBestBusbar(terminal);
return result != null ? result.busbarSectionId() : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.ConnectablePosition;
import com.powsybl.math.graph.TraversalType;
import org.gridsuite.network.map.dto.common.ReactiveCapabilityCurveMapData;
import org.gridsuite.network.map.dto.common.TapChangerData;
import org.gridsuite.network.map.dto.common.TapChangerStepData;
Expand Down Expand Up @@ -92,9 +91,8 @@ public static String getBusOrBusbarSection(Terminal terminal) {
return terminal.getBusBreakerView().getConnectableBus().getId();
}
} else {
final BusbarSectionFinderTraverser connectedBusbarSectionFinder = new BusbarSectionFinderTraverser(terminal.isConnected());
terminal.traverse(connectedBusbarSectionFinder, TraversalType.BREADTH_FIRST);
return connectedBusbarSectionFinder.getFirstTraversedBbsId();
// NODE_BREAKER: explore all paths and choose the busbar with the closed disconnector
return BusbarSectionFinderTraverser.findBusbarSectionId(terminal);
}
}

Expand Down
Loading