LayerAssignmentStep.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import LayerAssignmentStepError from "../../errors/optimizer/LayerAssignmentStepError.js";
  2. import Edge from "../../graph/edge/Edge.js";
  3. import Graph from "../../graph/Graph.js";
  4. import Layering from "../../graph/layering/Layering.js";
  5. import Node from "../../graph/node/Node.js";
  6. import AlgorithmStep from "../AlgorithmStep.js";
  7. import {EdgeSubdivisions, FeedbackSet, SiguiyamaContext} from "../siguiyama/SiguiyamaContext.js";
  8. import DummyNode from "../../graph/node/DummyNode.js";
  9. import DummyEdge from "../../graph/edge/DummyEdge.js";
  10. /**
  11. * Шаг алгоритма Sugiyama для присвоения слоёв вершинам графа.
  12. * Использует longest path layering для минимизации числа слоёв и обеспечения направления рёбер вниз.
  13. */
  14. export default class LayerAssignmentStep extends AlgorithmStep<SiguiyamaContext> {
  15. /**
  16. * Создаёт экземпляр шага LayerAssignmentStep.
  17. */
  18. public constructor() {
  19. super(LayerAssignmentStep.name);
  20. }
  21. /**
  22. * Выполняет шаг присвоения слоёв.
  23. * @param context Контекст алгоритма Sugiyama, содержащий граф, feedback set и edge subdivisions.
  24. * @throws {LayerAssignmentStepError} Если граф не найден, feedback set не определён или граф ацикличен.
  25. */
  26. public run(context: SiguiyamaContext): void {
  27. const {graph, feedbackSet} = context;
  28. if(!graph)
  29. throw new LayerAssignmentStepError("Graph was not found!");
  30. if(!feedbackSet)
  31. throw new LayerAssignmentStepError("Feedback set is undefined!");
  32. if(!graph.isAcyclic())
  33. throw new LayerAssignmentStepError("Graph is acyclic, can not assign layers to an acyclic graph!");
  34. const layering = this.longestPathAlgorithm(graph);
  35. const edgeSubdivisions: EdgeSubdivisions<Node, Edge<Node>> = new Map();
  36. this.divideLongEdges(graph, layering, feedbackSet, edgeSubdivisions);
  37. context.layering = layering;
  38. context.edgeSubdivisions = edgeSubdivisions;
  39. }
  40. /**
  41. * Реализация алгоритма longest path layering для присвоения слоёв вершинам DAG.
  42. * Каждая вершина получает слой, равный длине самого длинного пути от неё.
  43. * @param dag Направленный ациклический граф.
  44. * @returns Объект Layering с присвоенными слоями.
  45. * @private
  46. */
  47. private longestPathAlgorithm(dag: Graph<Node, Edge<Node>>): NonNullable<SiguiyamaContext["layering"]> {
  48. const layering = new Layering<Node, Edge<Node>>();
  49. const cache = new Map<string, number>();
  50. const getLongestPath = (node: Node): number => {
  51. if(cache.has(node.getId()))
  52. return cache.get(node.getId())!;
  53. const successors = dag.getNodeOutputs(node);
  54. if(successors.length === 0) {
  55. cache.set(node.getId(), 0);
  56. return 0;
  57. }
  58. const maxSucc = Math.max(...successors.map(s => getLongestPath(s)));
  59. const dist = 1 + maxSucc;
  60. cache.set(node.getId(), dist);
  61. return dist;
  62. };
  63. for(const node of dag.getNodes()) {
  64. const layer = getLongestPath(node);
  65. layering.addToLayer(layer, node);
  66. }
  67. return layering;
  68. }
  69. /**
  70. * Разбивает длинные рёбра (span >= 2) добавлением dummy-вершин между слоями.
  71. *
  72. * Для каждого ребра, у которого {@link Layering.getEdgeSpan} возвращает значение больше 1:
  73. * - создаётся цепочка из `span - 1` вершин {@link DummyNode};
  74. * - каждая dummy-вершина добавляется в промежуточный слой;
  75. * - исходная вершина/последняя dummy соединяется новым сегментом {@link Edge}.
  76. *
  77. * @param graph Граф, в который добавляются dummy-вершины и сегменты рёбер.
  78. * @param layering Текущее разбиение графа на слои, используемое для вычисления span и вставки dummy-вершин.
  79. * @param feedbackSet Набор рёбер обратной связи
  80. * @param edgeSubdivisions Информация о разделении рёбер в графе
  81. */
  82. private divideLongEdges(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, feedbackSet: FeedbackSet, edgeSubdivisions: EdgeSubdivisions<Node, Edge<Node>>) : void {
  83. const edges = graph.getEdges();
  84. for(const edge of edges) {
  85. const span = layering.getEdgeSpan(edge);
  86. if(span < 2)
  87. continue;
  88. const from = edge.getFrom(), to = edge.getTo();
  89. const fromLayer = layering.getNodeLayerIndex(from);
  90. let currentEdge: Edge<Node> = edge;
  91. graph.removeEdge(edge.getId());
  92. for(let i = 1; i < span; i++) {
  93. const dummyNode = new DummyNode(0, 0);
  94. layering.addToLayer(fromLayer - i, dummyNode);
  95. graph.addNode(dummyNode);
  96. const left = new DummyEdge(currentEdge.getFrom(), dummyNode);
  97. const right = new DummyEdge(dummyNode, currentEdge.getTo());
  98. edgeSubdivisions.set(currentEdge, { left, right });
  99. graph.addEdge(left).addEdge(right);
  100. const feedbackIndex = feedbackSet.indexOf(currentEdge);
  101. if(feedbackIndex >= 0) {
  102. feedbackSet.splice(feedbackIndex, 1);
  103. feedbackSet.push(left, right);
  104. }
  105. if(currentEdge !== edge)
  106. graph.removeEdge(currentEdge.getId());
  107. currentEdge = right;
  108. }
  109. }
  110. }
  111. }