NodeOrderingStep.ts 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import NodeOrderingStepError from "../../errors/optimizer/NodeOrderingStepError.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 {SiguiyamaContext} from "../siguiyama/SiguiyamaContext.js";
  8. enum Direction {
  9. Up,
  10. Down
  11. }
  12. /**
  13. * Шаг минимизации пересечений рёбер между слоями
  14. * Использует barycenter-эвристику с попеременными проходами сверху-вниз и снизу-вверх
  15. */
  16. export default class NodeOrderingStep extends AlgorithmStep<SiguiyamaContext> {
  17. /**
  18. * Количество итераций по умолчанию для barycenter-оптимизации
  19. */
  20. private static readonly DefaultIterationsCount = 24;
  21. /**
  22. * Максимальное количество итераций переупорядочивания слоёв
  23. */
  24. private readonly _iterationsCount: number;
  25. /**
  26. * @param iterationsCount Максимальное число итераций barycenter-эвристики
  27. */
  28. public constructor(iterationsCount: number = NodeOrderingStep.DefaultIterationsCount) {
  29. super(NodeOrderingStep.name);
  30. if(iterationsCount <= 0)
  31. throw new NodeOrderingStepError("Iterations count must be greater than 0");
  32. this._iterationsCount = iterationsCount;
  33. }
  34. /**
  35. * Выполняет шаг упорядочивания вершин внутри слоёв
  36. * @param context Контекст алгоритма Sugiyama
  37. * @throws {NodeOrderingStepError} Если граф или слоистая укладка отсутствуют
  38. */
  39. public run(context: SiguiyamaContext): void {
  40. const { graph, layering } = context;
  41. if(!graph)
  42. throw new NodeOrderingStepError("Source graph was not found!");
  43. if(!layering)
  44. throw new NodeOrderingStepError("Layering of graph was not found!");
  45. this.barycenter(graph, layering);
  46. }
  47. /**
  48. * Основной цикл barycenter-оптимизации.
  49. * На каждой итерации выполняются проходы вниз и вверх; если число пересечений ухудшилось,
  50. * алгоритм останавливается и возвращает лучшее найденное состояние слоёв.
  51. * @param graph Граф
  52. * @param layering Слои графа
  53. */
  54. private barycenter(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>) : void {
  55. let bestCrossingsCount = this.countTotalCrossings(graph, layering);
  56. let bestLayers = this.captureLayerOrder(layering);
  57. for(let i = 0; i < this._iterationsCount; i++) {
  58. this.sweepDown(graph, layering);
  59. this.sweepUp(graph, layering);
  60. const currentCrossingsCount = this.countTotalCrossings(graph, layering);
  61. if(currentCrossingsCount > bestCrossingsCount) {
  62. this.restoreLayerOrder(layering, bestLayers);
  63. break;
  64. }
  65. bestCrossingsCount = currentCrossingsCount;
  66. bestLayers = this.captureLayerOrder(layering);
  67. }
  68. }
  69. /**
  70. * Проход сверху-вниз: переупорядочивает каждый слой по barycenter-значениям
  71. * относительно предыдущего слоя
  72. * @param graph Граф
  73. * @param layering Слои графа
  74. */
  75. private sweepDown(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>) : void {
  76. const layers = layering.getLayers();
  77. for(let i = 1; i < layers.length; i++) {
  78. const currLayer = layers[i]!;
  79. currLayer.setNodes(this.applyBarycenter(graph, layering, currLayer.getNodes(), Direction.Down));
  80. }
  81. }
  82. /**
  83. * Проход снизу-вверх: переупорядочивает каждый слой по barycenter-значениям
  84. * относительно следующего слоя
  85. * @param graph Граф
  86. * @param layering Слои графа
  87. */
  88. private sweepUp(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>) : void {
  89. const layers = layering.getLayers();
  90. for(let i = layers.length - 2; i >= 0; i--) {
  91. const currLayer = layers[i]!;
  92. currLayer.setNodes(this.applyBarycenter(graph, layering, currLayer.getNodes(), Direction.Up));
  93. }
  94. }
  95. /**
  96. * Сортирует вершины слоя по barycenter-значению
  97. * Для вершины вычисляется средняя позиция соседей в фиксированном соседнем слое:
  98. * - при проходе вниз используются входящие соседи
  99. * - при проходе вверх используются исходящие соседи.
  100. * Если соседей нет, вершина сохраняет текущий порядок (по своей позиции в слое)
  101. * @param graph Граф
  102. * @param layering Слои графа
  103. * @param nodes Вершины текущего слоя
  104. * @param direction Направление прохода
  105. * @returns Новый порядок вершин в текущем слое
  106. */
  107. private applyBarycenter(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, nodes: Node[], direction: Direction) : Node[] {
  108. const layerPositionMap = this.buildLayerPositionMap(layering);
  109. const stableIndex = new Map<string, number>();
  110. for(let i = 0; i < nodes.length; i++)
  111. stableIndex.set(nodes[i]!.getId(), i);
  112. const barycenterValues = nodes.map((node) => {
  113. const neighbors = direction === Direction.Down ? graph.getNodeInputs(node) : graph.getNodeOutputs(node);
  114. if(neighbors.length === 0)
  115. return { node, value: stableIndex.get(node.getId())! };
  116. const positionSum = neighbors.reduce((sum, neighbor) => {
  117. const position = layerPositionMap.get(neighbor.getId());
  118. return sum + (position !== undefined ? position : 0);
  119. }, 0);
  120. return { node, value: positionSum / neighbors.length };
  121. });
  122. return barycenterValues
  123. .sort((a, b) => {
  124. if(a.value !== b.value)
  125. return a.value - b.value;
  126. return stableIndex.get(a.node.getId())! - stableIndex.get(b.node.getId())!;
  127. })
  128. .map((entry) => entry.node);
  129. }
  130. /**
  131. * Подсчитывает суммарное число пересечений между всеми соседними парами слоёв
  132. * @param graph Граф
  133. * @param layering Слои графа
  134. * @returns Общее число пересечений
  135. */
  136. private countTotalCrossings(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>) : number {
  137. const layers = layering.getLayers();
  138. let totalCrossings = 0;
  139. for(let i = 0; i < layers.length - 1; i++)
  140. totalCrossings += this.countCrossingsBetweenLayers(graph, layering, i, i + 1);
  141. return totalCrossings;
  142. }
  143. /**
  144. * Подсчитывает число пересечений рёбер между двумя соседними слоями.
  145. * @param graph Граф
  146. * @param layering Слои графа
  147. * @param upperLayerIndex Индекс верхнего слоя
  148. * @param lowerLayerIndex Индекс нижнего слоя
  149. * @returns Число пересечений между рёбрами, соединяющими эти слои
  150. */
  151. private countCrossingsBetweenLayers(graph: Graph<Node, Edge<Node>>, layering: Layering<Node, Edge<Node>>, upperLayerIndex: number, lowerLayerIndex: number) : number {
  152. const layers = layering.getLayers();
  153. const upperLayer = layers[upperLayerIndex];
  154. const lowerLayer = layers[lowerLayerIndex];
  155. if(!upperLayer || !lowerLayer)
  156. return 0;
  157. const upperPosition = new Map<string, number>(
  158. upperLayer.getNodes().map((node, index) => [node.getId(), index])
  159. );
  160. const lowerPosition = new Map<string, number>(
  161. lowerLayer.getNodes().map((node, index) => [node.getId(), index])
  162. );
  163. const edges = graph.getEdges().filter((edge) => {
  164. const fromId = edge.getFrom().getId();
  165. const toId = edge.getTo().getId();
  166. return upperPosition.has(fromId) && lowerPosition.has(toId);
  167. });
  168. let crossings = 0;
  169. for(let i = 0; i < edges.length; i++) {
  170. for(let j = i + 1; j < edges.length; j++) {
  171. const posU = upperPosition.get(edges[i]!.getFrom().getId())!;
  172. const posV = lowerPosition.get(edges[i]!.getTo().getId())!;
  173. const posS = upperPosition.get(edges[j]!.getFrom().getId())!;
  174. const posT = lowerPosition.get(edges[j]!.getTo().getId())!;
  175. if((posU < posS && posV > posT) || (posU > posS && posV < posT))
  176. crossings++;
  177. }
  178. }
  179. return crossings;
  180. }
  181. /**
  182. * Формирует отображение "id вершины -> позиция в своём слое"
  183. * @param layering Слои графа
  184. * @returns Карта позиций для всех вершин, присутствующих в слоях
  185. */
  186. private buildLayerPositionMap(layering: Layering<Node, Edge<Node>>) : Map<string, number> {
  187. const positions = new Map<string, number>();
  188. const layers = layering.getLayers();
  189. for(const layer of layers) {
  190. for(const [position, node] of layer.getNodes().entries())
  191. positions.set(node.getId(), position);
  192. }
  193. return positions;
  194. }
  195. /**
  196. * Сохраняет текущий порядок вершин по слоям.
  197. * @param layering Слои графа.
  198. * @returns Снимок порядка: массив массивов вершин по слоям.
  199. */
  200. private captureLayerOrder(layering: Layering<Node, Edge<Node>>) : Node[][] {
  201. const layers = layering.getLayers();
  202. return layers.map((layer) => layer.getNodes());
  203. }
  204. /**
  205. * Восстанавливает порядок вершин в слоях из ранее сохранённого снимка
  206. * @param layering Слои графа
  207. * @param snapshot Снимок, полученный методом {@link captureLayerOrder}
  208. */
  209. private restoreLayerOrder(layering: Layering<Node, Edge<Node>>, snapshot: Node[][]) : void {
  210. const layers = layering.getLayers();
  211. for(let i = 0; i < layers.length; i++) {
  212. const layer = layers[i];
  213. const nodes = snapshot[i];
  214. if(!layer || !nodes)
  215. continue;
  216. layer.setNodes(nodes);
  217. }
  218. }
  219. }