|
|
@@ -1,10 +1,11 @@
|
|
|
import LayerAssignmentStepError from "../../errors/optimizer/LayerAssignmentStepError.js";
|
|
|
import Edge from "../../graph/Edge.js";
|
|
|
import Graph from "../../graph/Graph.js";
|
|
|
+import Layering from "../../graph/layering/Layering.js";
|
|
|
import Node from "../../graph/Node.js";
|
|
|
import GraphService from "../../services/GraphService.js";
|
|
|
import AlgorithmStep from "../AlgorithmStep.js";
|
|
|
-import { GraphLayering, SiguiyamaContext } from "./SiguiyamaContext.js";
|
|
|
+import { SiguiyamaContext } from "./SiguiyamaContext.js";
|
|
|
|
|
|
// TODO
|
|
|
export default class LayerAssignmentStep implements AlgorithmStep<SiguiyamaContext> {
|
|
|
@@ -22,40 +23,12 @@ export default class LayerAssignmentStep implements AlgorithmStep<SiguiyamaConte
|
|
|
if(!feedbackSet)
|
|
|
throw new LayerAssignmentStepError("Feedback set is undefined!");
|
|
|
|
|
|
- const initialLayering = this.longestPathAlgorithm(graph);
|
|
|
- }
|
|
|
-
|
|
|
- private getSpanningTree(layering: NonNullable<SiguiyamaContext["layering"]>, dag: Graph<Node, Edge<Node>>) : Graph<Node, Edge<Node>> {
|
|
|
- throw new Error("Not implemented!")
|
|
|
- }
|
|
|
-
|
|
|
- private getNodeRank(layering: GraphLayering, nodeId: Node["id"]) : number | null {
|
|
|
- for(let i = 0; i < layering.length; i++) {
|
|
|
- const layer = layering[i]!;
|
|
|
- if(layer.includes(nodeId))
|
|
|
- return i;
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- private getEdgeSpan(layering: GraphLayering, edge: Edge<Node>) : number | null {
|
|
|
- const { from, to } = edge;
|
|
|
- const fromRank = this.getNodeRank(layering, from), toRank = this.getNodeRank(layering, to);
|
|
|
-
|
|
|
- if(fromRank == null || toRank == null)
|
|
|
- return null;
|
|
|
-
|
|
|
- return fromRank - toRank;
|
|
|
- }
|
|
|
+ const layering = this.longestPathAlgorithm(graph);
|
|
|
+ this.networkSimplexAlgorithm(graph, layering);
|
|
|
|
|
|
- private isTightEdge(layering: GraphLayering, edge: Edge<Node>) : boolean {
|
|
|
- return this.getEdgeSpan(layering, edge) === 1;
|
|
|
- }
|
|
|
+ console.log(JSON.stringify(layering, null, 2))
|
|
|
|
|
|
- private isLongEdge(layering: GraphLayering, edge: Edge<Node>) : boolean {
|
|
|
- const span = this.getEdgeSpan(layering, edge);
|
|
|
- return span ? span > 1 : false;
|
|
|
+ context.layering = layering;
|
|
|
}
|
|
|
|
|
|
private longestPathAlgorithm(dag: Graph<Node, Edge<Node>>) : NonNullable<SiguiyamaContext["layering"]> {
|
|
|
@@ -63,7 +36,7 @@ export default class LayerAssignmentStep implements AlgorithmStep<SiguiyamaConte
|
|
|
|
|
|
const alreadyLayeredNodeIds = new Set<Node["id"]>();
|
|
|
const underCurrentLayerNodeIds = new Set<Node["id"]>();
|
|
|
- const layering: NonNullable<SiguiyamaContext["layering"]> = [];
|
|
|
+ const layering = new Layering<Node, Edge<Node>>();
|
|
|
|
|
|
let currentLayerIndex = 0;
|
|
|
|
|
|
@@ -80,13 +53,7 @@ export default class LayerAssignmentStep implements AlgorithmStep<SiguiyamaConte
|
|
|
if(!isAllSuccessorsUnder)
|
|
|
continue;
|
|
|
|
|
|
- const currentLayer = layering[currentLayerIndex];
|
|
|
-
|
|
|
- if(!currentLayer)
|
|
|
- layering[currentLayerIndex] = [node.id];
|
|
|
- else
|
|
|
- currentLayer.push(node.id);
|
|
|
-
|
|
|
+ layering.assign(node, currentLayerIndex);
|
|
|
alreadyLayeredNodeIds.add(node.id);
|
|
|
isNodeSelected = true;
|
|
|
}
|
|
|
@@ -99,4 +66,236 @@ export default class LayerAssignmentStep implements AlgorithmStep<SiguiyamaConte
|
|
|
|
|
|
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);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|