binary-decision-diagram
Version:
A library to create, minimize and optimize binary decision diagrams
216 lines (189 loc) • 6.33 kB
text/typescript
import type { TruthTable } from './types.js';
import { RootNode } from './root-node.js';
import { createBddFromTruthTable } from './create-bdd-from-truth-table.js';
import { firstKeyOfMap, shuffleArray, lastOfArray } from './util.js';
import { AbstractNode } from './abstract-node.js';
/**
* a function that is called each time
* a 'better' bdd was found
*/
export type OptmisiationCallback = (bdd: OptimisationResult) => void;
export interface OptimisationResult {
bdd: RootNode;
truthTable: TruthTable;
}
export type CompareResultsFunction = (a: RootNode, b: RootNode) => RootNode | Promise<RootNode>;
/**
* returns the bdd with less nodes
*/
export const defaultCompareResults: CompareResultsFunction = function (
a: RootNode,
b: RootNode
): RootNode {
if (a.countNodes() <= b.countNodes()) {
return a;
} else {
return b;
}
};
export interface OptimizeBruteForceInput {
truthTable: TruthTable;
iterations?: number;
onBetterBdd?: OptmisiationCallback;
// a function that returns the 'better' bdd
compareResults?: CompareResultsFunction;
afterBddCreation?: (bdd: RootNode) => void;
initialBdd?: RootNode;
log?: boolean;
}
/**
* optimises the ordering of the boolean functions
* by randomly sorting the array
* and checking the resulting bdd
*/
export async function optimizeBruteForce({
truthTable,
iterations = Infinity,
onBetterBdd = () => null,
compareResults = defaultCompareResults,
afterBddCreation = () => null,
initialBdd,
log = false
}: OptimizeBruteForceInput): Promise<OptimisationResult> {
initialBdd = initialBdd ? initialBdd : await createBddFromTruthTable(truthTable);
afterBddCreation(initialBdd);
initialBdd.minimize();
let currentBestResult: OptimisationResult = {
truthTable,
bdd: initialBdd
};
onBetterBdd(currentBestResult);
if (log) {
initialBdd.log();
console.log('initial nodes amount: ' + initialBdd.countNodes());
}
let t = 0;
while (t < iterations) {
t++;
if (log) {
console.log('-'.repeat(50));
console.log('optimizeBruteForce() itterate once');
}
const shuffledOrdering = shuffleBooleanOrdering(truthTable);
const nextBdd = createBddFromTruthTable(shuffledOrdering.newTable);
// change the levels of each node
const newNodesByLevel: Map<number, Set<AbstractNode>> = new Map();
const lastLevel = lastOfArray(nextBdd.getLevels());
const newSortedLevels: number[] = [];
nextBdd.getLevels()
.filter(level => level !== lastLevel)
.forEach(level => {
const newLevel = shuffledOrdering.mappingBeforeToAfter[level];
newSortedLevels.push(newLevel);
const levelSet: Set<AbstractNode> = new Set();
newNodesByLevel.set(newLevel, levelSet);
nextBdd.getNodesOfLevel(level).forEach(node => {
node.level = newLevel;
levelSet.add(node);
});
});
const lastLevelSet: Set<AbstractNode> = new Set();
nextBdd.getNodesOfLevel(lastLevel).forEach(node => lastLevelSet.add(node));
newNodesByLevel.set(lastLevel, lastLevelSet);
newSortedLevels.push(lastLevel);
nextBdd.nodesByLevel = newNodesByLevel;
nextBdd.levels = newSortedLevels;
afterBddCreation(nextBdd);
nextBdd.minimize();
if (log) {
console.log('got new bdd with nodes amount of ' + nextBdd.countNodes());
// nextBdd.log();
console.dir(shuffledOrdering.mappingBeforeToAfter);
}
const betterBdd = await compareResults(
currentBestResult.bdd,
nextBdd
);
if (betterBdd.type !== 'RootNode') {
throw new Error('compareResults did not return a bdd');
}
if (betterBdd === nextBdd) {
if (log) {
console.log('#'.repeat(50));
console.log('found better bdd ' + nextBdd.countNodes());
}
currentBestResult = {
bdd: nextBdd,
truthTable: shuffledOrdering.newTable
};
onBetterBdd(currentBestResult);
}
}
return currentBestResult;
}
export type BooleanFunctionReorderMapping = {
[indexAfter: number]: number; // to indexBefore
};
export type BooleanFunctionReorderMappingReverse = {
[indexBefore: number]: number; // to indexAfter
};
export function shuffleBooleanOrdering(
truthTable: TruthTable
): {
newTable: TruthTable,
mapping: BooleanFunctionReorderMapping,
mappingBeforeToAfter: BooleanFunctionReorderMappingReverse
} {
const firstKey = firstKeyOfMap(truthTable);
const arrayWithIndexes = getArrayWithIndexes(firstKey.length);
const shuffled = shuffleArray(arrayWithIndexes);
const mapping: BooleanFunctionReorderMapping = {};
const mappingBeforeToAfter: BooleanFunctionReorderMappingReverse = {};
shuffled.forEach((indexBefore, indexAfter) => {
mapping[indexAfter] = indexBefore;
mappingBeforeToAfter[indexBefore] = indexAfter;
});
const newTable: TruthTable = new Map();
for (const [key, value] of truthTable.entries()) {
const newKey = changeKeyOrder(
key,
mapping
);
newTable.set(
newKey,
value
);
}
return {
newTable,
mapping,
mappingBeforeToAfter
};
}
export function changeKeyOrder(
oldKey: string,
mappingBeforeToAfter: BooleanFunctionReorderMapping
): string {
const chars = oldKey
.split('')
.map((char, indexBefore) => {
return {
char,
indexBefore,
indexAfter: mappingBeforeToAfter[indexBefore]
};
})
.sort((a, b) => a.indexAfter - b.indexAfter)
.map(charObj => charObj.char)
.join('');
return chars;
}
export function getArrayWithIndexes(size: number): number[] {
const ret: number[] = [];
let last = 0;
while (last < size) {
ret.push(last);
last++;
}
return ret;
}