Skip to content

Commit

Permalink
Move getBlocksInTopologicalSorting function to PipelineWrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
georg-schwarz committed Dec 20, 2023
1 parent 78bba05 commit 2421058
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 70 deletions.
3 changes: 1 addition & 2 deletions libs/execution/src/lib/blocks/block-execution-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
CompositeBlocktypeDefinition,
PipelineDefinition,
PipelineWrapper,
getBlocksInTopologicalSorting,
} from '@jvalue/jayvee-language-server';

import { ExecutionContext } from '../execution-context';
Expand Down Expand Up @@ -41,7 +40,7 @@ export async function executeBlocks(
const executionOrder: {
block: BlockDefinition;
value: IOTypeImplementation | null;
}[] = getBlocksInTopologicalSorting(pipesContainer).map((block) => {
}[] = pipelineWrapper.getBlocksInTopologicalSorting().map((block) => {
return { block: block, value: NONE };
});

Expand Down
69 changes: 1 addition & 68 deletions libs/language-server/src/lib/ast/model-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,85 +2,18 @@
//
// SPDX-License-Identifier: AGPL-3.0-only

import { strict as assert } from 'assert';

import { AstNode, LangiumDocuments } from 'langium';

import {
BinaryExpression,
BlockDefinition,
BuiltinBlocktypeDefinition,
BuiltinConstrainttypeDefinition,
CompositeBlocktypeDefinition,
PipelineDefinition,
UnaryExpression,
isBuiltinBlocktypeDefinition,
isJayveeModel,
} from './generated/ast';
// eslint-disable-next-line import/no-cycle
import {
BlockTypeWrapper,
ConstraintTypeWrapper,
PipelineWrapper,
} from './wrappers';

/**
* Returns blocks in a pipeline in topological order, based on
* Kahn's algorithm.
*
* Considers a pipeline as a directed, acyclical graph where
* blocks are nodes and pipes are edges. A list in topological
* order has the property that parent nodes are always listed
* before their children.
*
* "[...] a list in topological order is such that no element
* appears in it until after all elements appearing on all paths
* leading to the particular element have been listed."
*
* Kahn, A. B. (1962). Topological sorting of large networks. Communications of the ACM, 5(11), 558–562.
*/
export function getBlocksInTopologicalSorting(
pipeline: PipelineDefinition | CompositeBlocktypeDefinition,
): BlockDefinition[] {
const pipelineWrapper = new PipelineWrapper(pipeline);
const sortedNodes = [];
const currentNodes = [...pipelineWrapper.getStartingBlocks()];
let unvisitedEdges = [...pipelineWrapper.allPipes];

while (currentNodes.length > 0) {
const node = currentNodes.pop();
assert(node !== undefined);

sortedNodes.push(node);

for (const childNode of pipelineWrapper.getChildBlocks(node)) {
// Mark edges between parent and child as visited
pipelineWrapper
.getIngoingPipes(childNode)
.filter((e) => e.from === node)
.forEach((e) => {
unvisitedEdges = unvisitedEdges.filter((edge) => !edge.equals(e));
});

// If all edges to the child have been visited
const notRemovedEdges = pipelineWrapper
.getIngoingPipes(childNode)
.filter((e) => unvisitedEdges.some((edge) => edge.equals(e)));
if (notRemovedEdges.length === 0) {
// Insert it into currentBlocks
currentNodes.push(childNode);
}
}
}

// If the graph still contains unvisited edges it is not a DAG
assert(
unvisitedEdges.length === 0,
`The pipeline ${pipeline.name} is expected to have no cycles`,
);

return sortedNodes;
}
import { BlockTypeWrapper, ConstraintTypeWrapper } from './wrappers';

export type UnaryExpressionOperator = UnaryExpression['operator'];
export type BinaryExpressionOperator = BinaryExpression['operator'];
Expand Down
56 changes: 56 additions & 0 deletions libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0-only

import { strict as assert } from 'assert';

import {
BlockDefinition,
CompositeBlocktypeDefinition,
Expand Down Expand Up @@ -82,4 +84,58 @@ export class PipelineWrapper
getParentBlocks(blockDefinition: BlockDefinition): BlockDefinition[] {
return this.getIngoingPipes(blockDefinition).map((p) => p.from);
}

/**
* Returns blocks in a pipeline in topological order, based on
* Kahn's algorithm.
*
* Considers a pipeline as a directed, acyclical graph where
* blocks are nodes and pipes are edges. A list in topological
* order has the property that parent nodes are always listed
* before their children.
*
* "[...] a list in topological order is such that no element
* appears in it until after all elements appearing on all paths
* leading to the particular element have been listed."
*
* Kahn, A. B. (1962). Topological sorting of large networks. Communications of the ACM, 5(11), 558–562.
*/
getBlocksInTopologicalSorting(): BlockDefinition[] {
const sortedNodes = [];
const currentNodes = [...this.getStartingBlocks()];
let unvisitedEdges = [...this.allPipes];

while (currentNodes.length > 0) {
const node = currentNodes.pop();
assert(node !== undefined);

sortedNodes.push(node);

for (const childNode of this.getChildBlocks(node)) {
// Mark edges between parent and child as visited
this.getIngoingPipes(childNode)
.filter((e) => e.from === node)
.forEach((e) => {
unvisitedEdges = unvisitedEdges.filter((edge) => !edge.equals(e));
});

// If all edges to the child have been visited
const notRemovedEdges = this.getIngoingPipes(childNode).filter((e) =>
unvisitedEdges.some((edge) => edge.equals(e)),
);
if (notRemovedEdges.length === 0) {
// Insert it into currentBlocks
currentNodes.push(childNode);
}
}
}

// If the graph still contains unvisited edges it is not a DAG
assert(
unvisitedEdges.length === 0,
`The pipeline ${this.astNode.name} is expected to have no cycles`,
);

return sortedNodes;
}
}

0 comments on commit 2421058

Please sign in to comment.