浏览代码

documentation + rework 2

icestormikk 1 月之前
父节点
当前提交
430a659ca6

+ 3 - 0
src/v1/optimizer/AlgorithmContext.ts

@@ -2,6 +2,9 @@ import Edge from "../graph/edge/Edge.js"
 import Graph from "../graph/Graph.js"
 import Node from "../graph/node/Node.js"
 
+/**
+ * Контекст алгоритма, основные свойства, доступные для каждого алгоритма
+ */
 export type AlgorithmContext = {
 	graph: Graph<Node, Edge<Node>>,
 	config: object

+ 18 - 1
src/v1/optimizer/AlgorithmStep.ts

@@ -1,18 +1,35 @@
 import { AlgorithmContext } from "./AlgorithmContext.js";
 
+/**
+ * Шаг алгоритма. Позволяет проводить определённые операции, читая и изменяя текущий контекст
+ */
 export default abstract class AlgorithmStep<C extends AlgorithmContext> {
+	/**
+	 * Уникальный идентификатор шага алгоритма
+	 * @private
+	 */
 	private readonly _id: string;
 
 	protected constructor(id: string) {
 		this._id = id;
 	}
 
+	/**
+	 * Функция, выполняющася до запуска шага
+	 */
 	public onBeforeRun() : void {
 		console.log(this._id + ' before');
 	}
 
+	/**
+	 * Запуск шага алгоритма
+	 * @param context Контекст выполнения шага
+	 */
 	public abstract run(context: C) : void;
-	
+
+	/**
+	 * Функция, которая выполняется после завершения шага
+	 */
 	public onAfterRun() : void {
 		console.log(this._id + ' after');
 	}

+ 17 - 1
src/v1/optimizer/siguiyama/SiguiyamaAlgorithm.ts

@@ -4,14 +4,30 @@ import Node from "../../graph/node/Node.js";
 import Step from "../AlgorithmStep.js";
 import { SiguiyamaContext } from "./SiguiyamaContext.js";
 
+/**
+ * Реализоция алгоритма послойного рисования графа, основанного на алгоритме Сигуямы
+ * @see [Послойное рисование графа](https://ru.wikipedia.org/wiki/Послойное_рисование_графа)
+ */
 export default class SiguiyamaAlgorithm {
-	private readonly _steps: Step<SiguiyamaContext>[] = [];
+	/**
+	 * Шаги алгоритма рисования графа
+	 * @protected
+	 */
+	protected readonly _steps: Step<SiguiyamaContext>[] = [];
 
+	/**
+	 * Добавление шага в алгоритм Сигуямы
+	 * @param step Шаг, который нужно добавить
+	 */
 	public addStep(step: Step<SiguiyamaContext>): this {
 		this._steps.push(step)
 		return this;
 	}
 
+	/**
+	 * Запуск алгоритма Сигуямы
+	 * @param graph Граф, который нужно послойно отрисовать
+	 */
 	public run(graph: Graph<Node, Edge<Node>>): SiguiyamaContext {
 		const context: SiguiyamaContext = { graph, edgeSubdivisions: new Map(), config: {} };
 

+ 1 - 1
src/v1/optimizer/siguiyama/SiguiyamaContext.ts

@@ -20,7 +20,7 @@ export type EdgeSubdivision<TNode extends Node> = {
 };
 
 /**
- * Контекст, передаваемый между шагами алгоритма Siguiyama. Содержит всю необходимую информацию о графе, его разбиении на слои, сетке и рёбрах обратной связи, которая может быть использована и модифицирована на каждом этапе оптимизации.
+ * Контекст, передаваемый между шагами алгоритма Сигуямы. Содержит всю необходимую информацию о графе, его разбиении на слои, сетке и рёбрах обратной связи, которая может быть использована и модифицирована на каждом этапе оптимизации.
  */
 export type SiguiyamaContext = AlgorithmContext & Partial<{
 	feedbackSet: FeedbackSet,

+ 56 - 40
src/v1/optimizer/steps/CycleRemoveStep.ts

@@ -1,12 +1,14 @@
 import CycleRemoveStepError from "../../errors/optimizer/CycleRemoveStepError.js";
-import Edge from "../../graph/edge/Edge.js";
-import Graph from "../../graph/Graph.js";
-import Node from "../../graph/node/Node.js";
 import AlgorithmStep from "../AlgorithmStep.js";
 import {SiguiyamaContext} from "../siguiyama/SiguiyamaContext.js";
+import Node from "../../graph/node/Node.js";
+import Graph from "../../graph/Graph.js";
+import Edge from "../../graph/edge/Edge.js";
 
 /**
- * Greedy Cycle Removal Algorithm
+ * Шаг удаления циклов из графа, преобразование исходного ориентированного графа в ациклический
+ * В данной реализации шага преобразование графа в ациклический происходит при помощи топологической сортировки
+ * @see [Послойное рисование графа. Алгоритм компоновки](https://ru.wikipedia.org/wiki/Послойное_рисование_графа)
  */
 export default class CycleRemoveStep extends AlgorithmStep<SiguiyamaContext> {
 	public constructor() {
@@ -18,59 +20,73 @@ export default class CycleRemoveStep extends AlgorithmStep<SiguiyamaContext> {
 		if(!graph)
 			throw new CycleRemoveStepError("Graph not found!");
 
-		context.feedbackSet = this.removeCycles(graph);
-	}
-
-	protected removeCycles(graph: Graph<Node, Edge<Node>>) : NonNullable<SiguiyamaContext["feedbackSet"]> {
-		const nodes = graph.getNodes(), edges = graph.getEdges();
+		const sortedNodes = this.greedyCycleRemovalOrder(graph);
 
-		const middleNodes: Node[] = [], l: Node[] = [], r: Node[] = [];
-		const feedbackSet: NonNullable<SiguiyamaContext["feedbackSet"]> = [];
+		context.feedbackSet = this.createFeedbackSet(sortedNodes, graph);
+	}
 
-		for(const node of nodes) {
-			const isSource = graph.isSourceNode(node);
-			if(isSource) {
+	/**
+	 * Строит линейный порядок вершин для эвристического удаления циклов (greedy cycle removal).
+	 *
+	 * Порядок собирается из трёх частей:
+	 * - `l` (левая часть): все источники (`source`) в порядке обхода;
+	 * - `r` (правая часть): все стоки (`sink`) через `unshift`;
+	 * - `middle`: остальные вершины, отсортированные по убыванию `d⁺(v) - d⁻(v)`.
+	 *
+	 * @param graph Ориентированный граф.
+	 * @returns Линейный порядок вершин.
+	 */
+	private greedyCycleRemovalOrder(graph: Graph<Node, Edge<Node>>) : Node[] {
+		const l: Node[] = [], r: Node[] = [], middle: Node[] = [];
+
+		for(const node of graph.getNodes()) {
+			if(graph.isSinkNode(node)) {
+				r.unshift(node);
+			} else if(graph.isSourceNode(node)) {
 				l.push(node);
-				continue;
+			} else {
+				middle.push(node);
 			}
+		}
 
-			const isSink = graph.isSinkNode(node);
-			if(isSink) {
-				r.unshift(node);
-				continue;
-			}
+		const getDegreeDiff = (node: Node) : number => {
+			const outDegree = graph.getNodeOutputs(node).length;
+			const inDegree = graph.getNodeInputs(node).length;
 
-			middleNodes.push(node);
+			return outDegree - inDegree;
 		}
 
-		middleNodes.sort((a, b) => this.calculateScore(graph, b) - this.calculateScore(graph, a));
+		middle.sort((a, b) => getDegreeDiff(b) - getDegreeDiff(a));
 
-		while(middleNodes.length > 0) {
-			const node = middleNodes[0]!;
+		for(const node of middle)
 			l.push(node);
-			middleNodes.splice(0, 1);
-		}
 
-		const sortedNodes = [...l, ...r.reverse()];
+		return [...l, ...r];
+	}
+
+	/**
+     * Формирует feedback set относительно заданного линейного порядка и инвертирует обратные рёбра.
+     * Ребро `(u -> v)` считается обратным, если индекс `u` в `sortedNodes` больше индекса `v`.
+     * @param orderedNodes
+     * @param graph Граф, в котором могут быть инвертированы рёбра.
+     * @returns Массив рёбер, которые были инвертированы.
+     */
+	protected createFeedbackSet(orderedNodes: Node[], graph: Graph<Node, Edge<Node>>) : NonNullable<SiguiyamaContext["feedbackSet"]> {
+		const feedbackSet: NonNullable<SiguiyamaContext["feedbackSet"]> = [];
+
+		for(const edge of graph.getEdges()) {
+			const from = edge.getFrom();
+			const fromIndex = orderedNodes.indexOf(from);
 
-		for(const edge of edges) {
-			const from = edge.getFrom(), to = edge.getTo();
-			const fromIndex = sortedNodes.findIndex((node) => node.getId() === from.getId());
-			const toIndex = sortedNodes.findIndex((node) => node.getId() === to.getId());
+			const to = edge.getTo();
+			const toIndex = orderedNodes.indexOf(to);
 
 			if(fromIndex > toIndex) {
-				edge.setFrom(to).setTo(from);
 				feedbackSet.push(edge);
+				edge.setFrom(to).setTo(from);
 			}
 		}
 
 		return feedbackSet;
 	}
-
-	private calculateScore(graph: Graph<Node, Edge<Node>>, node: Node) : number {
-		const outDegree = graph.getNodeOutputs(node).length;
-		const inDegree = graph.getNodeInputs(node).length;
-
-		return outDegree - inDegree;
-	}
-}
+}