|
@@ -6,8 +6,15 @@ import Node from "../../graph/Node.js";
|
|
|
import AlgorithmStep from "../AlgorithmStep.js";
|
|
import AlgorithmStep from "../AlgorithmStep.js";
|
|
|
import { SiguiyamaContext } from "./SiguiyamaContext.js";
|
|
import { SiguiyamaContext } from "./SiguiyamaContext.js";
|
|
|
|
|
|
|
|
-export default class LayerAssignmentStep implements AlgorithmStep<SiguiyamaContext> {
|
|
|
|
|
- run(context: SiguiyamaContext): void {
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Network simplex algorithm
|
|
|
|
|
+ */
|
|
|
|
|
+export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext> {
|
|
|
|
|
+ public constructor() {
|
|
|
|
|
+ super(LayerAssignmentStep.name);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public run(context: SiguiyamaContext): void {
|
|
|
const { graph, feedbackSet } = context;
|
|
const { graph, feedbackSet } = context;
|
|
|
|
|
|
|
|
if(!graph)
|
|
if(!graph)
|
|
@@ -16,11 +23,9 @@ export default class LayerAssignmentStep implements AlgorithmStep<SiguiyamaConte
|
|
|
throw new LayerAssignmentStepError("Feedback set is undefined!");
|
|
throw new LayerAssignmentStepError("Feedback set is undefined!");
|
|
|
|
|
|
|
|
const layering = this.longestPathAlgorithm(graph);
|
|
const layering = this.longestPathAlgorithm(graph);
|
|
|
- this.networkSimplexAlgorithm(graph, layering);
|
|
|
|
|
-
|
|
|
|
|
- console.log(JSON.stringify(layering, null, 2))
|
|
|
|
|
|
|
|
|
|
context.layering = layering;
|
|
context.layering = layering;
|
|
|
|
|
+ console.log(layering.getLayers().map((l) => l.nodes));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private longestPathAlgorithm(dag: Graph<Node, Edge<Node>>) : NonNullable<SiguiyamaContext["layering"]> {
|
|
private longestPathAlgorithm(dag: Graph<Node, Edge<Node>>) : NonNullable<SiguiyamaContext["layering"]> {
|
|
@@ -58,236 +63,4 @@ export default class LayerAssignmentStep implements AlgorithmStep<SiguiyamaConte
|
|
|
|
|
|
|
|
return layering;
|
|
return layering;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- private networkSimplexAlgorithm(dag: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>) : void {
|
|
|
|
|
- const spanningTree = this.getFeasibleTree(dag, layering);
|
|
|
|
|
-
|
|
|
|
|
- let leaveEdge = this.leaveEdge(dag, layering, spanningTree);
|
|
|
|
|
- while(leaveEdge !== null) {
|
|
|
|
|
- const enterEdge = this.enterEdge(dag, layering, spanningTree, leaveEdge);
|
|
|
|
|
- this.exchange(dag, layering, spanningTree, leaveEdge, enterEdge);
|
|
|
|
|
- leaveEdge = this.leaveEdge(dag, layering, spanningTree);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- layering.normalize();
|
|
|
|
|
- this.balance(dag, layering);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private getFeasibleTree(dag: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>) : Set<Edge<Node>["id"]> {
|
|
|
|
|
- const spanningTree = new Set<Edge<Node>["id"]>();
|
|
|
|
|
- const visitedNodes = new Set<Node["id"]>();
|
|
|
|
|
- const nodes = dag.getNodes(), edges = dag.getEdges();
|
|
|
|
|
-
|
|
|
|
|
- if(nodes.length === 0)
|
|
|
|
|
- return spanningTree;
|
|
|
|
|
-
|
|
|
|
|
- const queue: Node["id"][] = [nodes[0]!.id];
|
|
|
|
|
- visitedNodes.add(nodes[0]!.id);
|
|
|
|
|
-
|
|
|
|
|
- while(queue.length > 0) {
|
|
|
|
|
- const nodeId = queue.shift()!;
|
|
|
|
|
-
|
|
|
|
|
- for(const edge of edges) {
|
|
|
|
|
- if(!layering.isEdgeTight(edge))
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- const { id: edgeId, from, to } = edge;
|
|
|
|
|
-
|
|
|
|
|
- const isFromCurrent = from === nodeId && !visitedNodes.has(to);
|
|
|
|
|
- const isToCurrent = to === nodeId && !visitedNodes.has(from);
|
|
|
|
|
-
|
|
|
|
|
- if(!isFromCurrent && !isToCurrent)
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- const neighbourId = isFromCurrent ? edge.to : edge.from;
|
|
|
|
|
- spanningTree.add(edgeId);
|
|
|
|
|
- visitedNodes.add(neighbourId);
|
|
|
|
|
- queue.push(neighbourId);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return spanningTree;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private leaveEdge(dag: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, spanningTree: Set<Edge<Node>["id"]>) : Edge<Node> | null {
|
|
|
|
|
- const edges = dag.getEdges();
|
|
|
|
|
-
|
|
|
|
|
- for(const edge of edges) {
|
|
|
|
|
- if(!spanningTree.has(edge.id))
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- const cutValue = this.getCutValue(dag, layering, spanningTree, edge);
|
|
|
|
|
- if(cutValue < 0)
|
|
|
|
|
- return edge;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private getCutValue(dag: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, spanningTree: Set<Edge<Node>["id"]>, edge: Edge<Node>) : number {
|
|
|
|
|
- const { head, tail } = this.splitTree(dag, spanningTree, edge);
|
|
|
|
|
-
|
|
|
|
|
- let cutValue = 0;
|
|
|
|
|
-
|
|
|
|
|
- for(const edge of dag.getEdges()) {
|
|
|
|
|
- const { from, to } = edge;
|
|
|
|
|
- const fromInTail = tail.has(from);
|
|
|
|
|
- const toInTail = tail.has(to);
|
|
|
|
|
- const fromInHead = head.has(from);
|
|
|
|
|
- const toInHead = head.has(to);
|
|
|
|
|
-
|
|
|
|
|
- if(fromInTail && toInHead) cutValue++; // tail -> head
|
|
|
|
|
- if(fromInHead && toInTail) cutValue--; // head -> tail
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return cutValue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private splitTree(dag: Graph<Node, Edge<Node>>, spanningTree: Set<Edge<Node>["id"]>, removedEdge: Edge<Node>) : { head: Set<Node["id"]>, tail: Set<Node["id"]> } {
|
|
|
|
|
- const { from } = removedEdge;
|
|
|
|
|
-
|
|
|
|
|
- const treeEdges = dag.getEdges().filter((edge) => {
|
|
|
|
|
- return spanningTree.has(edge.id) && edge.id !== removedEdge.id;
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- const tail = new Set<Node["id"]>();
|
|
|
|
|
- const queue = [removedEdge.from];
|
|
|
|
|
- tail.add(from);
|
|
|
|
|
-
|
|
|
|
|
- while(queue.length > 0) {
|
|
|
|
|
- const nodeId = queue.shift()!;
|
|
|
|
|
- for(const edge of treeEdges) {
|
|
|
|
|
- const { from, to } = edge;
|
|
|
|
|
- if(from == nodeId && !tail.has(to)) {
|
|
|
|
|
- tail.add(to);
|
|
|
|
|
- queue.push(to);
|
|
|
|
|
- }
|
|
|
|
|
- if(to == nodeId && !tail.has(from)) {
|
|
|
|
|
- tail.add(from);
|
|
|
|
|
- queue.push(from);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const head = new Set<Node["id"]>(
|
|
|
|
|
- dag.getNodes().map((node) => node.id).filter((id) => !tail.has(id))
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- return { head, tail };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private enterEdge(dag: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, spanningTree: Set<Edge<Node>["id"]>, leaveEdge: Edge<Node>) : Edge<Node> {
|
|
|
|
|
- const { head, tail } = this.splitTree(dag, spanningTree, leaveEdge);
|
|
|
|
|
-
|
|
|
|
|
- let minSpan = Infinity;
|
|
|
|
|
- let bestEdge: Edge<Node> | null = null;
|
|
|
|
|
-
|
|
|
|
|
- for(const edge of dag.getEdges()) {
|
|
|
|
|
- if(spanningTree.has(edge.id))
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- const fromInHead = head.has(edge.from);
|
|
|
|
|
- const toInTail = tail.has(edge.to);
|
|
|
|
|
- const fromInTail = tail.has(edge.from);
|
|
|
|
|
- const toInHead = head.has(edge.to);
|
|
|
|
|
-
|
|
|
|
|
- // edges between components (any direction)
|
|
|
|
|
- if((!fromInHead || !toInTail) && (!fromInTail || !toInHead))
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- const span = layering.getEdgeSpan(edge);
|
|
|
|
|
- if(span >= minSpan)
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- minSpan = span;
|
|
|
|
|
- bestEdge = edge;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if(!bestEdge)
|
|
|
|
|
- throw new LayerAssignmentStepError("No enter edge found");
|
|
|
|
|
-
|
|
|
|
|
- return bestEdge;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private exchange(dag: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, spanningTree: Set<Edge<Node>["id"]>, leaveEdge: Edge<Node>, enterEdge: Edge<Node>) : void {
|
|
|
|
|
- spanningTree.delete(leaveEdge.id);
|
|
|
|
|
- spanningTree.add(enterEdge.id);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private recomputeLayers(dag: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, spanningTree: Set<Edge<Node>["id"]>) : void {
|
|
|
|
|
- const nodes = dag.getNodes();
|
|
|
|
|
- const treeEdges = dag.getEdges().filter((edge) => spanningTree.has(edge.id));
|
|
|
|
|
-
|
|
|
|
|
- const inDegree = new Map<Node["id"], number>();
|
|
|
|
|
- nodes.forEach((node) => inDegree.set(node.id, 0));
|
|
|
|
|
- treeEdges.forEach((edge) => inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1));
|
|
|
|
|
-
|
|
|
|
|
- const source = nodes.find((node) => inDegree.get(node.id) === 0);
|
|
|
|
|
- if(!source)
|
|
|
|
|
- return;
|
|
|
|
|
-
|
|
|
|
|
- layering.setLayerOf(source.id, 0);
|
|
|
|
|
-
|
|
|
|
|
- const queue = [source.id];
|
|
|
|
|
- const visited = new Set<Node["id"]>([source.id]);
|
|
|
|
|
-
|
|
|
|
|
- while(queue.length > 0) {
|
|
|
|
|
- const nodeId = queue.shift()!;
|
|
|
|
|
- const currentLayer = layering.getLayerOf(nodeId) ?? 0;
|
|
|
|
|
-
|
|
|
|
|
- for(const edge of treeEdges) {
|
|
|
|
|
- const { from, to } = edge;
|
|
|
|
|
- if(from === nodeId && !visited.has(to)) {
|
|
|
|
|
- layering.setLayerOf(to, currentLayer + 1);
|
|
|
|
|
- visited.add(to);
|
|
|
|
|
- queue.push(to);
|
|
|
|
|
- }
|
|
|
|
|
- if(to === nodeId && !visited.has(from)) {
|
|
|
|
|
- layering.setLayerOf(from, currentLayer - 1);
|
|
|
|
|
- visited.add(from);
|
|
|
|
|
- queue.push(from);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private balance(dag: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>) : void {
|
|
|
|
|
- for(const { id } of dag.getNodes()) {
|
|
|
|
|
- const inDegree = dag.getNodeInputs(id).length;
|
|
|
|
|
- const outDegree = dag.getNodeOutputs(id).length;
|
|
|
|
|
-
|
|
|
|
|
- if(inDegree !== outDegree)
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- const currentLayer = layering.getLayerOf(id);
|
|
|
|
|
- if(currentLayer == null)
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- const predecessorLayers = dag.getNodeInputs(id).map((node) => layering.getLayerOf(node.id)).filter((layer) => layer !== null);
|
|
|
|
|
- if(predecessorLayers.length === 0)
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- const successorLayers = dag.getNodeOutputs(id).map((node) => layering.getLayerOf(node.id)).filter((layer) => layer !== null);
|
|
|
|
|
- if(successorLayers.length === 0)
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- const minFeasible = Math.max(...predecessorLayers) + 1;
|
|
|
|
|
- const maxFeasible = Math.min(...successorLayers) - 1;
|
|
|
|
|
-
|
|
|
|
|
- let bestLayer = currentLayer;
|
|
|
|
|
- let minWidth = layering.getLayers().find((layer) => layer.index === currentLayer)?.width ?? Infinity;
|
|
|
|
|
-
|
|
|
|
|
- for(let i = minFeasible; i <= maxFeasible; i++) {
|
|
|
|
|
- const width = layering.getLayers().find((layer) => layer.index === currentLayer)?.width ?? Infinity;
|
|
|
|
|
- if(width >= minWidth)
|
|
|
|
|
- continue;
|
|
|
|
|
-
|
|
|
|
|
- minWidth = width;
|
|
|
|
|
- bestLayer = i;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if(bestLayer !== currentLayer)
|
|
|
|
|
- layering.setLayerOf(id, bestLayer);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|