binary-decision-diagram
Version:
A library to create, minimize and optimize binary decision diagrams
207 lines (179 loc) • 6.44 kB
text/typescript
import { nextNodeId } from './util.js';
import type { NonRootNode, NonLeafNode, NodeType } from './types.js';
import { Branches } from './branches.js';
import { RootNode } from './root-node.js';
import { InternalNode } from './internal-node.js';
import { LeafNode } from './leaf-node.js';
import { findSimilarNode } from './find-similar-node.js';
export class AbstractNode {
readonly id: string = nextNodeId();
public deleted: boolean = false;
public type: NodeType;
public rootNode: RootNode;
constructor(
public level: number,
rootNode: RootNode | null,
type: NodeType
) {
this.type = type;
this.rootNode = rootNode as RootNode;
if (rootNode) {
this.rootNode.addNode(this as any);
}
}
isEqualToOtherNode(
otherNode: NonRootNode | RootNode,
// optimisation shortcut, is faster if own string already known
ownString: string = this.toString()
): boolean {
const ret = ownString === otherNode.toString();
return ret;
}
// deletes the whole node
public remove() {
this.ensureNotDeleted('remove');
// console.log('AbstractNode().remove() node: ' + this.id);
// console.log(this.toJSON(true));
if (this.isInternalNode()) {
const useNode: InternalNode = this as any;
if (useNode.parents.size > 0) {
throw new Error('cannot remove node with parents ' + this.id);
}
}
if ((this as any).branches) {
const useNode: NonLeafNode = this as any;
if (useNode.branches.areBranchesStrictEqual()) {
useNode.branches.getBranch('0').parents.remove(useNode);
} else {
useNode.branches.getBranch('0').parents.remove(useNode);
useNode.branches.getBranch('1').parents.remove(useNode);
}
}
this.deleted = true;
this.rootNode.removeNode(this as any);
}
toJSON(withId: boolean = false): any {
const ret: any = {
id: withId ? this.id : undefined,
deleted: withId ? this.deleted : undefined,
type: this.type,
level: this.level
};
if (withId && (this as any).parents) {
ret.parents = (this as any).parents.toString();
}
if (this.isLeafNode()) {
ret.value = this.asLeafNode().value;
}
if ((this as any).branches && !(this as any).branches.deleted) {
const branches: Branches = (this as any).branches;
ret.branches = {
'0': branches.getBranch('0').toJSON(withId),
'1': branches.getBranch('1').toJSON(withId)
};
}
return ret;
}
// a strange string-representation
// to make an equal check between nodes
toString(): string {
let ret = '' +
'<' +
this.type + ':' + this.level;
if ((this as any).branches) {
const branches: Branches = (this as any).branches;
ret += '|0:' + branches.getBranch('0');
ret += '|1:' + branches.getBranch('1');
}
if (this.isLeafNode()) {
ret += '|v:' + this.asLeafNode().value;
}
ret += '>';
return ret;
}
isRootNode(): boolean {
return this.type === 'RootNode';
}
isInternalNode(): boolean {
return this.type === 'InternalNode';
}
isLeafNode(): boolean {
return this.type === 'LeafNode';
}
asRootNode(): RootNode {
if (!this.isRootNode()) {
throw new Error('ouch');
}
return this as any;
}
asInternalNode(): InternalNode {
if (!this.isInternalNode()) {
throw new Error('ouch');
}
return this as any;
}
asLeafNode(): LeafNode {
if (!this.isLeafNode()) {
throw new Error('ouch');
}
return this as any;
}
ensureNotDeleted(op: string = 'unknown') {
if (this.deleted) {
throw new Error('forbidden operation ' + op + ' on deleted node ' + this.id);
}
}
public log() {
console.log(JSON.stringify(this.toJSON(true), null, 2));
}
/**
* by the elimination-rule of bdd,
* if two branches of the same level are equal,
* one can be removed
*
* See page 21 at:
* @link https://people.eecs.berkeley.edu/~sseshia/219c/lectures/BinaryDecisionDiagrams.pdf
*/
applyEliminationRule<T extends AbstractNode>(
// can be provided for better performance
nodesOfSameLevel?: T[]
): boolean {
this.ensureNotDeleted('applyEliminationRule');
if (!nodesOfSameLevel) {
nodesOfSameLevel = this.rootNode.getNodesOfLevel(this.level) as any[];
}
const other = findSimilarNode(
this,
nodesOfSameLevel as any[]
);
if (other) {
// console.log('applyEliminationRule() remove:' + this.id + '; other: ' + other.id);
// keep 'other', remove 'this'
// move own parents to other
const ownParents = (this as any).parents.getAll();
const parentsWithStrictEqualBranches: NonLeafNode[] = [];
ownParents.forEach((parent: InternalNode) => {
// console.log('ownParent: ' + parent.id);
const branchKey = parent.branches.getKeyOfNode(this as any);
// console.log('branchKey: ' + branchKey);
parent.branches.setBranch(branchKey, other);
if (parent.branches.areBranchesStrictEqual()) {
parentsWithStrictEqualBranches.push(parent);
}
// remove parents from own list
// this will auto-remove the connection to the other '1'-branch
(this as any).parents.remove(parent);
});
// parents that now have equal branches, must be removed again
parentsWithStrictEqualBranches.forEach(node => {
if (node.isInternalNode()) {
// console.log('trigger applyReductionRule from applyEliminationRule');
(node as InternalNode).applyReductionRule();
}
});
return true;
} else {
return false;
}
}
}