binary-decision-diagram
Version:
A library to create, minimize and optimize binary decision diagrams
141 lines (117 loc) • 4.52 kB
text/typescript
import { RootNode } from './root-node.js';
import { AbstractNode } from './abstract-node.js';
import type { NonRootNode, NonLeafNode } from './types.js';
import { InternalNode } from './internal-node.js';
/**
* run some tests on the bdd
* to ensure everything is correct
*/
export function ensureCorrectBdd(bdd: RootNode) {
const jsonString = JSON.stringify(bdd.toJSON(true));
let allNodes: AbstractNode[] = [];
const nodesById: Map<string, AbstractNode> = new Map();
bdd.getLevels().forEach(level => {
const levelNodes = bdd.getNodesOfLevel(level);
levelNodes.forEach(node => {
nodesById.set(node.id, node);
});
allNodes = allNodes.concat(levelNodes);
});
const recursiveNodes = getNodesRecursive(bdd);
if (allNodes.length !== recursiveNodes.size) {
const allNodesIds = allNodes.map(n => n.id).sort();
const recursiveNodesIds = Array.from(recursiveNodes).map(n => n.id).sort();
const nodesOnlyInRecursive: string[] = recursiveNodesIds.filter(id => !allNodesIds.includes(id));
// console.log(JSON.stringify(allNodes.map(n => n.id).sort(), null, 2));
// console.log(JSON.stringify(Array.from(recursiveNodes).map(n => n.id).sort(), null, 2));
if (recursiveNodes.size > allNodes.length) {
const firstId = nodesOnlyInRecursive[0];
const referenceToFirst = allNodes.find(n => {
if (n.isInternalNode()) {
return (n as InternalNode).branches.hasNodeIdAsBranch(firstId);
}
return false;
});
console.log('referenceToFirst:');
referenceToFirst?.log();
}
throw new Error(
'ensureCorrectBdd() ' +
'nodes in list not equal size to recursive nodes ' +
'allNodes: ' + allNodes.length + ' ' +
'recursiveNodes: ' + recursiveNodes.size + ' ' +
'nodesOnlyInRecursive: ' + nodesOnlyInRecursive.join(', ') + ' '
);
}
allNodes.forEach(node => {
if (node.isRootNode()) {
return;
}
const useNode = node as NonRootNode;
if (node.deleted) {
throw new Error(
'ensureCorrectBdd() ' +
'bdd includes a deleted node'
);
}
// each node should have a parent
if (useNode.parents.size === 0) {
throw new Error(
'ensureCorrectBdd() ' +
'node has no parent ' + useNode.id
);
}
if (useNode.isInternalNode()) {
const internalNode: InternalNode = useNode as any;
const bothBranches = internalNode.branches.getBothBranches();
// a node should not have 2 equal branches
if (internalNode.branches.areBranchesStrictEqual()) {
throw new Error(
'ensureCorrectBdd() ' +
'node has two equal branches: ' +
bothBranches.map(n => n.id).join(', ')
);
}
// each branch should have the node as parent
bothBranches.forEach(branch => {
if (!branch.parents.has(internalNode)) {
throw new Error(
'ensureCorrectBdd() ' +
'branch must have the node as parent'
);
}
});
}
// each parent should have the child as branch
useNode.parents.getAll().forEach(parent => {
if (!parent.branches.hasBranchAsNode(useNode)) {
throw new Error(
'ensureCorrectBdd() ' +
'parent node does not have child as branch'
);
}
});
});
if (jsonString.includes('"deleted":true')) {
throw new Error(
'ensureCorrectBdd() ' +
'bdd includes a deleted node'
);
}
}
export function getNodesRecursive(
node: AbstractNode,
set: Set<AbstractNode> = new Set()
): Set<AbstractNode> {
set.add(node);
if (!node.isLeafNode()) {
const useNode = node as NonLeafNode;
const branch1 = useNode.branches.getBranch('0');
set.add(branch1);
getNodesRecursive(branch1, set);
const branch2 = useNode.branches.getBranch('1');
set.add(branch2);
getNodesRecursive(branch2, set);
}
return set;
}