UNPKG

binary-decision-diagram

Version:

A library to create, minimize and optimize binary decision diagrams

207 lines (179 loc) 6.44 kB
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; } } }