Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat: implement graphDiff algorithm #117

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ It represents the closest reasonable ESLint configuration to this
project's original TSLint configuration.

We recommend eventually switching this configuration to extend from
the recommended rulesets in typescript-eslint.
the recommended rulesets in typescript-eslint.
https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md

Happy linting! 💖
Expand All @@ -29,26 +29,8 @@ Happy linting! 💖
},
"cSpell.ignoreWords": ["editor", "format", "on", "save"],
"cSpell.words": [
"Algos",
"Conda",
"Customizer",
"Databricks",
"Dataset",
"Datasets",
"Datastore",
"Datastores",
"Dismissable",
"Dropdown",
"Edat",
"Ensembling",
"Interop",
"Prefetcher",
"Resizable",
"SKUs",
"Serializers",
"Subsampling",
"Timespan",
"abortcontroller",
"Algos",
"appinsights",
"automl",
"azureml",
Expand All @@ -58,12 +40,23 @@ Happy linting! 💖
"buildscripts",
"callout",
"cobertura",
"Conda",
"continuationtoken",
"cudatoolkit",
"Customizer",
"cyclomatic",
"Databricks",
"dataprep",
"Dataset",
"Datasets",
"Datastore",
"Datastores",
"dcid",
"Dismissable",
"Dropdown",
"eastus",
"Edat",
"Ensembling",
"esnext",
"etag",
"experimentrun",
Expand All @@ -75,6 +68,7 @@ Happy linting! 💖
"generageresult",
"generatebuildresult",
"hyperdrive",
"Interop",
"jsnext",
"jszip",
"junit",
Expand All @@ -84,6 +78,7 @@ Happy linting! 💖
"locstrings",
"machinelearningservices",
"managedenv",
"mcmf",
"mlworkspace",
"mockdate",
"msal",
Expand All @@ -96,28 +91,34 @@ Happy linting! 💖
"papaparse",
"plotly",
"polyfill",
"Prefetcher",
"pytorch",
"quickprofile",
"Resizable",
"resjson",
"rollup",
"runhistory",
"scipy",
"scriptrun",
"scrollable",
"serializer",
"Serializers",
"setuptools",
"sklearn",
"SKUs",
"sourcemap",
"spacy",
"storyshots",
"storysource",
"stylelint",
"stylelintrc",
"submodule",
"Subsampling",
"svgr",
"taskkill",
"theming",
"timeseries",
"Timespan",
"treeshake",
"tslib",
"uifabric",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"private": true,
"devDependencies": {
"@algorithm.ts/mcmf": "^2.0.14",
"@babel/core": "7.12.13",
"@babel/preset-typescript": "7.12.13",
"@fluentui/merge-styles": "^8.2.0",
Expand Down
1 change: 1 addition & 0 deletions packages/react-dag-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
},
"version": "0.3.5",
"dependencies": {
"@algorithm.ts/mcmf": "^2.0.14",
"@fluentui/merge-styles": "^8.2.0",
"eventemitter3": "^4.0.7",
"react-jss": "~10.2.0",
Expand Down
22 changes: 22 additions & 0 deletions packages/react-dag-editor/src/lib/utils/graphDiff/core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
This algorithm is used to find the minimum-cost-maximum-matching in a bipartite graph.

## Usage

1. implement the [IGraphDiffContext](./context/GraphDiffContext.ts)
2. implement the [IGraphDiffResolver](./resolver/BaseGraphDiffResolver.ts)


```typescript

const context: IGraphDiffContext;
const resolver: IGraphDiffResolver;

// Find the best bipartite graph matching.
const candidateMappings = resolver.findCandidateMapping(context);
const mappings = findBipartiteGraphMaxMapping(candidateMappings);
// You can perform additional matches and modify mappings.

// Build diff graph.
context.buildDiffGraph(mappings, abOnlyNodes,)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
IGraph,
IGraphDiffContext,
IGraphEdge,
IGraphNode,
IGraphNodeOutgoingEdge,
IGraphNodeWithStructure,
} from "../types";

export interface IGraphDiffContextProps<
Node extends IGraphNode,
Edge extends IGraphEdge
> {
readonly lGraph: IGraph<Node, Edge>;
readonly rGraph: IGraph<Node, Edge>;
}

export class GraphDiffContext<Node extends IGraphNode, Edge extends IGraphEdge>
implements IGraphDiffContext<Node, Edge>
{
public readonly ABOnlyCostThreshold: number = 100000;
public readonly PropertyDiffCostRate: number = 0.2;
public readonly StructureDiffCostRate: number = 1;

public readonly lGraph: IGraph<Node, Edge>;
public readonly rGraph: IGraph<Node, Edge>;

public readonly lNodesMap: ReadonlyMap<
string,
IGraphNodeWithStructure<Node, Edge>
>;
public readonly rNodesMap: ReadonlyMap<
string,
IGraphNodeWithStructure<Node, Edge>
>;

public constructor(props: IGraphDiffContextProps<Node, Edge>) {
this.lGraph = props.lGraph;
this.rGraph = props.rGraph;
this.lNodesMap = this.buildNodeMap(props.lGraph);
this.rNodesMap = this.buildNodeMap(props.rGraph);
}

protected buildNodeMap(
graph: IGraph<Node, Edge>
): ReadonlyMap<string, IGraphNodeWithStructure<Node, Edge>> {
const nodeMap: Map<string, IGraphNodeWithStructure<Node, Edge>> = new Map();
const idToNodeMap = new Map<string, Node>();
for (const node of graph.nodes) {
idToNodeMap.set(node.id, node);
}

for (const edge of graph.edges) {
const sourceNode = idToNodeMap.get(edge.source);
const targetNode = idToNodeMap.get(edge.target);
if (sourceNode && targetNode) {
{
const outEdge: IGraphNodeOutgoingEdge<Node, Edge> = {
portId: edge.sourcePort,
targetNode,
targetPortId: edge.targetPort,
originalEdge: edge,
};

const sourceNodeWithStructure = nodeMap.get(edge.source);
if (sourceNodeWithStructure) {
sourceNodeWithStructure.outEdges.push(outEdge);
} else {
nodeMap.set(edge.source, {
node: sourceNode,
inEdges: [],
outEdges: [outEdge],
});
}
}

{
const inEdge: IGraphNodeOutgoingEdge<Node, Edge> = {
portId: edge.targetPort,
targetNode: sourceNode,
targetPortId: edge.sourcePort,
originalEdge: edge,
};

const targetNodeWithStructure = nodeMap.get(edge.target);
if (targetNodeWithStructure) {
targetNodeWithStructure.inEdges.push(inEdge);
} else {
nodeMap.set(edge.target, {
node: targetNode,
inEdges: [inEdge],
outEdges: [],
});
}
}
}
}

return nodeMap;
}
}
10 changes: 10 additions & 0 deletions packages/react-dag-editor/src/lib/utils/graphDiff/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export * from "./context/GraphDiffContext";
export * from "./resolver/BaseGraphDiffResolver";
export * from "./resolver/defaultBuildCandidateMapping";
export * from "./resolver/defaultBuildDiffEdge";
export * from "./resolver/defaultBuildDiffGraph";
export * from "./resolver/defaultBuildDiffNode";
export * from "./resolver/defaultCalcStructureDiffCost";
export * from "./util/findBipartiteGraphMaxMapping";
export * from "./util/genAutoIncrementId";
export * from "./types";
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
IABOnlyNode,
IDiffGraph,
IDiffGraphEdge,
IDiffGraphNode,
IDiffGraphNodeSearcher,
IGraphDiffContext,
IGraphDiffResolver,
IGraphEdge,
IGraphNode,
IGraphNodeDiffResult,
IMapping,
} from "../types";
import { defaultBuildCandidateMapping } from "./defaultBuildCandidateMapping";
import { defaultBuildDiffEdges } from "./defaultBuildDiffEdge";
import { defaultBuildDiffNodes } from "./defaultBuildDiffNode";
import { defaultBuildDiffGraph } from "./defaultBuildDiffGraph";
import { defaultCalcStructureDiffCost } from "./defaultCalcStructureDiffCost";

export abstract class BaseGraphDiffResolver<
Node extends IGraphNode,
Edge extends IGraphEdge
> implements IGraphDiffResolver<Node, Edge>
{
public abstract areSameNodes(lNode: Node, rNode: Node): IGraphNodeDiffResult;

public buildCandidateMapping(
context: IGraphDiffContext<Node, Edge>
): IMapping<Node>[] {
return defaultBuildCandidateMapping<Node, Edge>(context, this);
}

public buildDiffEdges(
diffNodeSearcher: IDiffGraphNodeSearcher<Node>,
context: IGraphDiffContext<Node, Edge>
): { diffEdges: IDiffGraphEdge<Node, Edge>[] } {
return defaultBuildDiffEdges(diffNodeSearcher, context);
}

buildDiffGraph(
mappings: IMapping<Node>[],
abOnlyNodes: IABOnlyNode<Node>[],
context: IGraphDiffContext<Node, Edge>
): IDiffGraph<Node, Edge> {
return defaultBuildDiffGraph(mappings, abOnlyNodes, context, this);
}

public buildDiffNodes(
mappings: IMapping<Node>[],
abOnlyNodes: IABOnlyNode<Node>[],
_context: IGraphDiffContext<Node, Edge>
): {
diffNodes: IDiffGraphNode<Node>[];
diffNodeSearcher: IDiffGraphNodeSearcher<Node>;
} {
return defaultBuildDiffNodes(mappings, abOnlyNodes, this);
}

public calcStructureDiffCost(
lNode: Node,
rNode: Node,
context: IGraphDiffContext<Node, Edge>
): number {
return defaultCalcStructureDiffCost<Node, Edge>(
lNode,
rNode,
context,
this
);
}

public abstract calcPropertyDiffCost(
lNode: Node,
rNode: Node,
context: IGraphDiffContext<Node, Edge>
): number;

public hasSamePorts(lNode: Node, rNode: Node): boolean {
if (lNode.ports.length !== rNode.ports.length) {
return false;
}

const lPortSet: Set<string> = new Set<string>(lNode.ports);
return rNode.ports.every((port) => lPortSet.has(port));
}
}
Loading