binary-decision-diagram 3.2.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +211 -31
- package/dist/cjs/src/abstract-node.js +7 -4
- package/dist/cjs/src/abstract-node.js.map +1 -1
- package/dist/cjs/src/branches.d.ts +4 -1
- package/dist/cjs/src/branches.js +10 -9
- package/dist/cjs/src/branches.js.map +1 -1
- package/dist/cjs/src/create-bdd-from-truth-table.js +1 -2
- package/dist/cjs/src/create-bdd-from-truth-table.js.map +1 -1
- package/dist/cjs/src/ensure-correct-bdd.js +2 -3
- package/dist/cjs/src/ensure-correct-bdd.js.map +1 -1
- package/dist/cjs/src/fill-truth-table.js +1 -2
- package/dist/cjs/src/fill-truth-table.js.map +1 -1
- package/dist/cjs/src/find-similar-node.js +1 -2
- package/dist/cjs/src/find-similar-node.js.map +1 -1
- package/dist/cjs/src/index.d.ts +2 -1
- package/dist/cjs/src/index.es5.js +17 -7
- package/dist/cjs/src/index.es5.js.map +1 -1
- package/dist/cjs/src/index.js +2 -1
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/internal-node.js +6 -3
- package/dist/cjs/src/internal-node.js.map +1 -1
- package/dist/cjs/src/optimize-brute-force.js +10 -12
- package/dist/cjs/src/optimize-brute-force.js.map +1 -1
- package/dist/cjs/src/ordering/build-bdd.d.ts +13 -0
- package/dist/cjs/src/ordering/build-bdd.js +64 -0
- package/dist/cjs/src/ordering/build-bdd.js.map +1 -0
- package/dist/cjs/src/ordering/dense-truth-table.d.ts +42 -0
- package/dist/cjs/src/ordering/dense-truth-table.js +125 -0
- package/dist/cjs/src/ordering/dense-truth-table.js.map +1 -0
- package/dist/cjs/src/ordering/exact-cost-ordering.d.ts +91 -0
- package/dist/cjs/src/ordering/exact-cost-ordering.js +351 -0
- package/dist/cjs/src/ordering/exact-cost-ordering.js.map +1 -0
- package/dist/cjs/src/ordering/exact-ordering.d.ts +89 -0
- package/dist/cjs/src/ordering/exact-ordering.js +260 -0
- package/dist/cjs/src/ordering/exact-ordering.js.map +1 -0
- package/dist/cjs/src/ordering/index.d.ts +22 -0
- package/dist/cjs/src/ordering/index.js +38 -0
- package/dist/cjs/src/ordering/index.js.map +1 -0
- package/dist/cjs/src/ordering/int-pair-map.d.ts +20 -0
- package/dist/cjs/src/ordering/int-pair-map.js +85 -0
- package/dist/cjs/src/ordering/int-pair-map.js.map +1 -0
- package/dist/cjs/src/ordering/local-search.d.ts +114 -0
- package/dist/cjs/src/ordering/local-search.js +317 -0
- package/dist/cjs/src/ordering/local-search.js.map +1 -0
- package/dist/cjs/src/ordering/optimize-ordering.d.ts +153 -0
- package/dist/cjs/src/ordering/optimize-ordering.js +406 -0
- package/dist/cjs/src/ordering/optimize-ordering.js.map +1 -0
- package/dist/cjs/src/ordering/ordering-evaluator.d.ts +198 -0
- package/dist/cjs/src/ordering/ordering-evaluator.js +384 -0
- package/dist/cjs/src/ordering/ordering-evaluator.js.map +1 -0
- package/dist/cjs/src/ordering/shared-diagram.d.ts +85 -0
- package/dist/cjs/src/ordering/shared-diagram.js +295 -0
- package/dist/cjs/src/ordering/shared-diagram.js.map +1 -0
- package/dist/cjs/src/packed/index.d.ts +2 -0
- package/dist/cjs/src/{minimal-string → packed}/index.js +2 -5
- package/dist/cjs/src/packed/index.js.map +1 -0
- package/dist/cjs/src/packed/packed-bdd-base64-string.d.ts +3 -0
- package/dist/cjs/src/packed/packed-bdd-base64-string.js +46 -0
- package/dist/cjs/src/packed/packed-bdd-base64-string.js.map +1 -0
- package/dist/cjs/src/packed/packed-bdd.d.ts +13 -0
- package/dist/cjs/src/packed/packed-bdd.js +70 -0
- package/dist/cjs/src/packed/packed-bdd.js.map +1 -0
- package/dist/cjs/src/root-node.d.ts +1 -2
- package/dist/cjs/src/root-node.js +4 -9
- package/dist/cjs/src/root-node.js.map +1 -1
- package/dist/cjs/src/types.d.ts +0 -12
- package/dist/cjs/src/util.js +29 -21
- package/dist/cjs/src/util.js.map +1 -1
- package/dist/esm/src/abstract-node.js +7 -4
- package/dist/esm/src/abstract-node.js.map +1 -1
- package/dist/esm/src/branches.d.ts +4 -1
- package/dist/esm/src/branches.js +8 -7
- package/dist/esm/src/branches.js.map +1 -1
- package/dist/esm/src/create-bdd-from-truth-table.js.map +1 -1
- package/dist/esm/src/index.d.ts +2 -1
- package/dist/esm/src/index.js +2 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/internal-node.js +7 -4
- package/dist/esm/src/internal-node.js.map +1 -1
- package/dist/esm/src/optimize-brute-force.js +5 -7
- package/dist/esm/src/optimize-brute-force.js.map +1 -1
- package/dist/esm/src/ordering/build-bdd.d.ts +13 -0
- package/dist/esm/src/ordering/build-bdd.js +61 -0
- package/dist/esm/src/ordering/build-bdd.js.map +1 -0
- package/dist/esm/src/ordering/dense-truth-table.d.ts +42 -0
- package/dist/esm/src/ordering/dense-truth-table.js +117 -0
- package/dist/esm/src/ordering/dense-truth-table.js.map +1 -0
- package/dist/esm/src/ordering/exact-cost-ordering.d.ts +91 -0
- package/dist/esm/src/ordering/exact-cost-ordering.js +347 -0
- package/dist/esm/src/ordering/exact-cost-ordering.js.map +1 -0
- package/dist/esm/src/ordering/exact-ordering.d.ts +89 -0
- package/dist/esm/src/ordering/exact-ordering.js +256 -0
- package/dist/esm/src/ordering/exact-ordering.js.map +1 -0
- package/dist/esm/src/ordering/index.d.ts +22 -0
- package/dist/esm/src/ordering/index.js +17 -0
- package/dist/esm/src/ordering/index.js.map +1 -0
- package/dist/esm/src/ordering/int-pair-map.d.ts +20 -0
- package/dist/esm/src/ordering/int-pair-map.js +81 -0
- package/dist/esm/src/ordering/int-pair-map.js.map +1 -0
- package/dist/esm/src/ordering/local-search.d.ts +114 -0
- package/dist/esm/src/ordering/local-search.js +308 -0
- package/dist/esm/src/ordering/local-search.js.map +1 -0
- package/dist/esm/src/ordering/optimize-ordering.d.ts +153 -0
- package/dist/esm/src/ordering/optimize-ordering.js +399 -0
- package/dist/esm/src/ordering/optimize-ordering.js.map +1 -0
- package/dist/esm/src/ordering/ordering-evaluator.d.ts +198 -0
- package/dist/esm/src/ordering/ordering-evaluator.js +377 -0
- package/dist/esm/src/ordering/ordering-evaluator.js.map +1 -0
- package/dist/esm/src/ordering/shared-diagram.d.ts +85 -0
- package/dist/esm/src/ordering/shared-diagram.js +289 -0
- package/dist/esm/src/ordering/shared-diagram.js.map +1 -0
- package/dist/esm/src/packed/index.d.ts +2 -0
- package/dist/esm/src/packed/index.js +3 -0
- package/dist/esm/src/packed/index.js.map +1 -0
- package/dist/esm/src/packed/packed-bdd-base64-string.d.ts +3 -0
- package/dist/esm/src/packed/packed-bdd-base64-string.js +42 -0
- package/dist/esm/src/packed/packed-bdd-base64-string.js.map +1 -0
- package/dist/esm/src/packed/packed-bdd.d.ts +13 -0
- package/dist/esm/src/packed/packed-bdd.js +66 -0
- package/dist/esm/src/packed/packed-bdd.js.map +1 -0
- package/dist/esm/src/root-node.d.ts +1 -2
- package/dist/esm/src/root-node.js +5 -10
- package/dist/esm/src/root-node.js.map +1 -1
- package/dist/esm/src/types.d.ts +0 -12
- package/dist/esm/src/util.js +14 -5
- package/dist/esm/src/util.js.map +1 -1
- package/docs/variable-ordering.md +306 -0
- package/package.json +15 -12
- package/src/abstract-node.ts +7 -5
- package/src/branches.ts +9 -7
- package/src/create-bdd-from-truth-table.ts +1 -1
- package/src/index.ts +2 -1
- package/src/internal-node.ts +7 -4
- package/src/optimize-brute-force.ts +5 -7
- package/src/ordering/build-bdd.ts +70 -0
- package/src/ordering/dense-truth-table.ts +161 -0
- package/src/ordering/exact-cost-ordering.ts +425 -0
- package/src/ordering/exact-ordering.ts +317 -0
- package/src/ordering/index.ts +57 -0
- package/src/ordering/int-pair-map.ts +85 -0
- package/src/ordering/local-search.ts +369 -0
- package/src/ordering/optimize-ordering.ts +570 -0
- package/src/ordering/ordering-evaluator.ts +479 -0
- package/src/ordering/shared-diagram.ts +310 -0
- package/src/packed/index.ts +2 -0
- package/src/packed/packed-bdd-base64-string.ts +47 -0
- package/src/packed/packed-bdd.ts +98 -0
- package/src/root-node.ts +8 -17
- package/src/types.ts +0 -18
- package/src/util.ts +15 -6
- package/dist/cjs/src/minimal-string/bdd-to-minimal-string.d.ts +0 -8
- package/dist/cjs/src/minimal-string/bdd-to-minimal-string.js +0 -60
- package/dist/cjs/src/minimal-string/bdd-to-minimal-string.js.map +0 -1
- package/dist/cjs/src/minimal-string/bdd-to-simple-bdd.d.ts +0 -11
- package/dist/cjs/src/minimal-string/bdd-to-simple-bdd.js +0 -24
- package/dist/cjs/src/minimal-string/bdd-to-simple-bdd.js.map +0 -1
- package/dist/cjs/src/minimal-string/index.d.ts +0 -5
- package/dist/cjs/src/minimal-string/index.js.map +0 -1
- package/dist/cjs/src/minimal-string/minimal-string-to-simple-bdd.d.ts +0 -2
- package/dist/cjs/src/minimal-string/minimal-string-to-simple-bdd.js +0 -58
- package/dist/cjs/src/minimal-string/minimal-string-to-simple-bdd.js.map +0 -1
- package/dist/cjs/src/minimal-string/resolve-with-simple-bdd.d.ts +0 -2
- package/dist/cjs/src/minimal-string/resolve-with-simple-bdd.js +0 -21
- package/dist/cjs/src/minimal-string/resolve-with-simple-bdd.js.map +0 -1
- package/dist/cjs/src/minimal-string/string-format.d.ts +0 -9
- package/dist/cjs/src/minimal-string/string-format.js +0 -61
- package/dist/cjs/src/minimal-string/string-format.js.map +0 -1
- package/dist/esm/src/minimal-string/bdd-to-minimal-string.d.ts +0 -8
- package/dist/esm/src/minimal-string/bdd-to-minimal-string.js +0 -55
- package/dist/esm/src/minimal-string/bdd-to-minimal-string.js.map +0 -1
- package/dist/esm/src/minimal-string/bdd-to-simple-bdd.d.ts +0 -11
- package/dist/esm/src/minimal-string/bdd-to-simple-bdd.js +0 -19
- package/dist/esm/src/minimal-string/bdd-to-simple-bdd.js.map +0 -1
- package/dist/esm/src/minimal-string/index.d.ts +0 -5
- package/dist/esm/src/minimal-string/index.js +0 -6
- package/dist/esm/src/minimal-string/index.js.map +0 -1
- package/dist/esm/src/minimal-string/minimal-string-to-simple-bdd.d.ts +0 -2
- package/dist/esm/src/minimal-string/minimal-string-to-simple-bdd.js +0 -54
- package/dist/esm/src/minimal-string/minimal-string-to-simple-bdd.js.map +0 -1
- package/dist/esm/src/minimal-string/resolve-with-simple-bdd.d.ts +0 -2
- package/dist/esm/src/minimal-string/resolve-with-simple-bdd.js +0 -17
- package/dist/esm/src/minimal-string/resolve-with-simple-bdd.js.map +0 -1
- package/dist/esm/src/minimal-string/string-format.d.ts +0 -9
- package/dist/esm/src/minimal-string/string-format.js +0 -54
- package/dist/esm/src/minimal-string/string-format.js.map +0 -1
- package/src/minimal-string/bdd-to-minimal-string.ts +0 -72
- package/src/minimal-string/bdd-to-simple-bdd.ts +0 -31
- package/src/minimal-string/index.ts +0 -5
- package/src/minimal-string/minimal-string-to-simple-bdd.ts +0 -67
- package/src/minimal-string/resolve-with-simple-bdd.ts +0 -25
- package/src/minimal-string/string-format.ts +0 -67
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import type { DenseTruthTable } from './dense-truth-table.js';
|
|
2
|
+
import { ensureValidOrdering, withTerminalIds } from './dense-truth-table.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Describes what a resolve() call costs.
|
|
6
|
+
*/
|
|
7
|
+
export interface CostModel {
|
|
8
|
+
/** functionCosts[v] = cost of calling boolean function v */
|
|
9
|
+
functionCosts: Float64Array;
|
|
10
|
+
/**
|
|
11
|
+
* rowWeights[row] = how often the row occurs at runtime, relative to the other rows.
|
|
12
|
+
* Must be 0 for don't-care rows, they never occur.
|
|
13
|
+
*/
|
|
14
|
+
rowWeights: Float64Array;
|
|
15
|
+
/** sum of rowWeights */
|
|
16
|
+
totalWeight: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Result of evaluating a variable ordering.
|
|
21
|
+
*/
|
|
22
|
+
export interface OrderingEvaluation {
|
|
23
|
+
/**
|
|
24
|
+
* nodesPerLevel[k] is the amount of nodes of the reduced BDD
|
|
25
|
+
* that test the boolean function ordering[k].
|
|
26
|
+
*/
|
|
27
|
+
nodesPerLevel: number[];
|
|
28
|
+
/** sum of nodesPerLevel */
|
|
29
|
+
internalNodeCount: number;
|
|
30
|
+
/**
|
|
31
|
+
* Amount of nodes as RootNode.countNodes() would return it:
|
|
32
|
+
* the root node, plus all nodes below the top level, plus the leaf nodes.
|
|
33
|
+
*/
|
|
34
|
+
nodeCount: number;
|
|
35
|
+
/**
|
|
36
|
+
* Expected cost of one resolve() call: the costs of the boolean functions
|
|
37
|
+
* that are called on the way from the root to the leaf, averaged over the
|
|
38
|
+
* rows with their weights. 0 when the evaluator has no cost model.
|
|
39
|
+
*/
|
|
40
|
+
expectedCost: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A reduced ordered (multi terminal) decision diagram
|
|
45
|
+
* in a flat array representation. Ids below terminalCount are terminals
|
|
46
|
+
* (and equal the terminal id of the dense truth table), ids from terminalCount
|
|
47
|
+
* on are internal nodes: node i is stored at index (i - terminalCount).
|
|
48
|
+
*/
|
|
49
|
+
export interface ReducedDiagram {
|
|
50
|
+
variableCount: number;
|
|
51
|
+
terminalCount: number;
|
|
52
|
+
/** id of the root, can be a terminal when the function is constant */
|
|
53
|
+
root: number;
|
|
54
|
+
/** level (index into the ordering) of each internal node */
|
|
55
|
+
nodeLevel: Int32Array;
|
|
56
|
+
/** id of the 0-branch of each internal node */
|
|
57
|
+
nodeLo: Int32Array;
|
|
58
|
+
/** id of the 1-branch of each internal node */
|
|
59
|
+
nodeHi: Int32Array;
|
|
60
|
+
/** ordering[level] = boolean function tested on that level */
|
|
61
|
+
ordering: number[];
|
|
62
|
+
nodesPerLevel: number[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface DiagramRecord {
|
|
66
|
+
nodeLevel: Int32Array;
|
|
67
|
+
nodeLo: Int32Array;
|
|
68
|
+
nodeHi: Int32Array;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* relative tolerance for comparing expected costs
|
|
73
|
+
*/
|
|
74
|
+
export const COST_EPSILON = 1e-9;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Two costs closer than this are treated as equal. The tolerance is relative
|
|
78
|
+
* to the larger of the value and the cost of one call of the most expensive
|
|
79
|
+
* boolean function (`unit`), so it does not depend on the unit the
|
|
80
|
+
* functionCosts are given in (milliseconds, seconds, ...).
|
|
81
|
+
*/
|
|
82
|
+
export function costTolerance(value: number, unit: number): number {
|
|
83
|
+
return COST_EPSILON * Math.max(unit, Math.abs(value));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The cost of one call of the most expensive boolean function,
|
|
88
|
+
* the unit of costTolerance() for expected costs.
|
|
89
|
+
*/
|
|
90
|
+
export function costUnit(costModel: CostModel): number {
|
|
91
|
+
let unit = 0;
|
|
92
|
+
for (let v = 0; v < costModel.functionCosts.length; v++) {
|
|
93
|
+
unit = Math.max(unit, costModel.functionCosts[v]);
|
|
94
|
+
}
|
|
95
|
+
return unit;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* An ordering together with the intermediate results of its evaluation.
|
|
100
|
+
*
|
|
101
|
+
* levelIds[L] holds the ids of the 2^L blocks of level L (levelIds[n] are the
|
|
102
|
+
* rows of the truth table). Because blocks are indexed by the natural
|
|
103
|
+
* assignment to the functions above their level, swapping the functions of two
|
|
104
|
+
* levels keeps all levels below the lower one valid, only the levels from the
|
|
105
|
+
* lower one up to the root are recomputed.
|
|
106
|
+
*/
|
|
107
|
+
export class OrderingCursor {
|
|
108
|
+
public readonly ordering: number[];
|
|
109
|
+
public evaluation: OrderingEvaluation;
|
|
110
|
+
/** id of the root block, a terminal when the function is constant */
|
|
111
|
+
public root: number = 0;
|
|
112
|
+
|
|
113
|
+
private readonly n: number;
|
|
114
|
+
private readonly levelIds: Int32Array[] = [];
|
|
115
|
+
private readonly levelWeights: Float64Array[] | null;
|
|
116
|
+
private readonly created: number[];
|
|
117
|
+
private readonly payingWeights: number[];
|
|
118
|
+
|
|
119
|
+
constructor(
|
|
120
|
+
private readonly evaluator: OrderingEvaluator,
|
|
121
|
+
ordering: ArrayLike<number>,
|
|
122
|
+
private readonly record: DiagramRecord | null = null
|
|
123
|
+
) {
|
|
124
|
+
this.n = evaluator.variableCount;
|
|
125
|
+
ensureValidOrdering(ordering, this.n);
|
|
126
|
+
this.ordering = Array.from(ordering);
|
|
127
|
+
for (let level = 0; level < this.n; level++) {
|
|
128
|
+
this.levelIds.push(new Int32Array(1 << level));
|
|
129
|
+
}
|
|
130
|
+
this.levelIds.push(evaluator.table.terminalIds);
|
|
131
|
+
if (evaluator.costModel) {
|
|
132
|
+
this.levelWeights = [];
|
|
133
|
+
for (let level = 0; level < this.n; level++) {
|
|
134
|
+
this.levelWeights.push(new Float64Array(1 << level));
|
|
135
|
+
}
|
|
136
|
+
this.levelWeights.push(evaluator.costModel.rowWeights);
|
|
137
|
+
} else {
|
|
138
|
+
this.levelWeights = null;
|
|
139
|
+
}
|
|
140
|
+
this.created = new Array(this.n).fill(0);
|
|
141
|
+
this.payingWeights = new Array(this.n).fill(0);
|
|
142
|
+
this.evaluation = this.recomputeFrom(this.n - 1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Starts over with another ordering.
|
|
147
|
+
*/
|
|
148
|
+
public reset(ordering: ArrayLike<number>) {
|
|
149
|
+
ensureValidOrdering(ordering, this.n);
|
|
150
|
+
for (let i = 0; i < this.n; i++) {
|
|
151
|
+
this.ordering[i] = ordering[i];
|
|
152
|
+
}
|
|
153
|
+
this.evaluation = this.recomputeFrom(this.n - 1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Swaps the boolean functions of two levels and updates the evaluation.
|
|
158
|
+
*/
|
|
159
|
+
public swap(levelA: number, levelB: number) {
|
|
160
|
+
if (levelA === levelB) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const tmp = this.ordering[levelA];
|
|
164
|
+
this.ordering[levelA] = this.ordering[levelB];
|
|
165
|
+
this.ordering[levelB] = tmp;
|
|
166
|
+
this.evaluation = this.recomputeFrom(Math.max(levelA, levelB));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* recomputes the levels top .. 0 (the levels below stay as they are)
|
|
171
|
+
*/
|
|
172
|
+
private recomputeFrom(top: number): OrderingEvaluation {
|
|
173
|
+
this.evaluator.evaluations++;
|
|
174
|
+
const n = this.n;
|
|
175
|
+
const terminalCount = this.evaluator.terminalCount;
|
|
176
|
+
// ids used by the levels below `top`
|
|
177
|
+
let nextId = terminalCount;
|
|
178
|
+
for (let level = n - 1; level > top; level--) {
|
|
179
|
+
nextId += this.created[level];
|
|
180
|
+
}
|
|
181
|
+
for (let level = top; level >= 0; level--) {
|
|
182
|
+
const result = this.evaluator.computeLevel(
|
|
183
|
+
this.ordering,
|
|
184
|
+
level,
|
|
185
|
+
this.levelIds[level + 1],
|
|
186
|
+
this.levelWeights ? this.levelWeights[level + 1] : null,
|
|
187
|
+
this.levelIds[level],
|
|
188
|
+
this.levelWeights ? this.levelWeights[level] : null,
|
|
189
|
+
nextId,
|
|
190
|
+
this.record
|
|
191
|
+
);
|
|
192
|
+
this.created[level] = result.created;
|
|
193
|
+
this.payingWeights[level] = result.payingWeight;
|
|
194
|
+
nextId += result.created;
|
|
195
|
+
}
|
|
196
|
+
this.root = this.levelIds[0][0];
|
|
197
|
+
|
|
198
|
+
const nodesPerLevel = this.created.slice();
|
|
199
|
+
const internalNodeCount = nextId - terminalCount;
|
|
200
|
+
const nodeCount = 1 + (internalNodeCount - nodesPerLevel[0]) + this.evaluator.table.leafCount;
|
|
201
|
+
let expectedCost = 0;
|
|
202
|
+
const costModel = this.evaluator.costModel;
|
|
203
|
+
if (costModel) {
|
|
204
|
+
const costs = costModel.functionCosts;
|
|
205
|
+
// the root node always calls its boolean function
|
|
206
|
+
expectedCost = costs[this.ordering[0]];
|
|
207
|
+
for (let level = 1; level < n; level++) {
|
|
208
|
+
expectedCost += costs[this.ordering[level]] * this.payingWeights[level] / costModel.totalWeight;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
nodesPerLevel,
|
|
213
|
+
internalNodeCount,
|
|
214
|
+
nodeCount,
|
|
215
|
+
expectedCost
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Evaluates variable orderings of a truth table without ever creating
|
|
222
|
+
* BDD node objects.
|
|
223
|
+
*
|
|
224
|
+
* The reduced BDD is built bottom-up: the rows of the truth table are the
|
|
225
|
+
* leaves, every level above merges the two children of each block into a node
|
|
226
|
+
* through a hash table (hash-consing). Equal children collapse to a single edge
|
|
227
|
+
* (reduction rule) and equal (child0, child1) pairs share one node (elimination
|
|
228
|
+
* rule), so the amount of nodes created per level is exactly the amount of
|
|
229
|
+
* nodes the reduced BDD has on that level. Everything runs in O(2^n) integer
|
|
230
|
+
* operations.
|
|
231
|
+
*
|
|
232
|
+
* A block on level L is the set of rows that agree on the boolean functions
|
|
233
|
+
* above level L. Blocks are indexed by the assignment to those functions in
|
|
234
|
+
* their natural (index) order, not in the order of the levels. That way the
|
|
235
|
+
* blocks of a level only depend on the SET of functions above it: when two
|
|
236
|
+
* levels swap their functions, nothing below them changes and OrderingCursor
|
|
237
|
+
* only recomputes the levels above.
|
|
238
|
+
*
|
|
239
|
+
* Don't-care rows are handled like RootNode.removeIrrelevantLeafNodes() does it:
|
|
240
|
+
* a node whose one branch is entirely don't-care is replaced by its other branch.
|
|
241
|
+
*
|
|
242
|
+
* With a cost model, the same pass also sums up the weight of the rows that
|
|
243
|
+
* pass through a node on each level, which gives the expected cost of a
|
|
244
|
+
* resolve() call: a row pays for the boolean function of a level exactly when
|
|
245
|
+
* its block on that level has a node. The root node always calls its boolean
|
|
246
|
+
* function, even when the function is irrelevant.
|
|
247
|
+
*/
|
|
248
|
+
export class OrderingEvaluator {
|
|
249
|
+
public readonly variableCount: number;
|
|
250
|
+
public readonly rowCount: number;
|
|
251
|
+
public readonly terminalCount: number;
|
|
252
|
+
public readonly dontCare: number;
|
|
253
|
+
/** amount of evaluations run so far */
|
|
254
|
+
public evaluations: number = 0;
|
|
255
|
+
|
|
256
|
+
private readonly hashLo: Int32Array;
|
|
257
|
+
private readonly hashHi: Int32Array;
|
|
258
|
+
private readonly hashId: Int32Array;
|
|
259
|
+
private scratch: OrderingCursor | null = null;
|
|
260
|
+
private costUnit: number = 0;
|
|
261
|
+
|
|
262
|
+
constructor(
|
|
263
|
+
public readonly table: DenseTruthTable,
|
|
264
|
+
public readonly costModel: CostModel | null = null
|
|
265
|
+
) {
|
|
266
|
+
this.variableCount = table.variableCount;
|
|
267
|
+
this.rowCount = table.rowCount;
|
|
268
|
+
this.terminalCount = table.terminalValues.length;
|
|
269
|
+
this.dontCare = table.dontCareTerminal;
|
|
270
|
+
if (costModel) {
|
|
271
|
+
if (costModel.functionCosts.length !== this.variableCount || costModel.rowWeights.length !== this.rowCount) {
|
|
272
|
+
throw new Error('cost model does not fit the truth table');
|
|
273
|
+
}
|
|
274
|
+
for (let row = 0; row < this.rowCount; row++) {
|
|
275
|
+
if (costModel.rowWeights[row] !== 0 && table.terminalIds[row] === table.dontCareTerminal) {
|
|
276
|
+
// a don't-care row is merged into the subtree of its sibling, its weight would be lost
|
|
277
|
+
throw new Error('don\'t-care rows must have a row weight of 0');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
this.costUnit = costUnit(costModel);
|
|
281
|
+
}
|
|
282
|
+
// at most rowCount/2 distinct nodes per level -> load factor <= 0.5
|
|
283
|
+
let hashSize = 16;
|
|
284
|
+
while (hashSize < this.rowCount) {
|
|
285
|
+
hashSize *= 2;
|
|
286
|
+
}
|
|
287
|
+
this.hashLo = new Int32Array(hashSize);
|
|
288
|
+
this.hashHi = new Int32Array(hashSize);
|
|
289
|
+
this.hashId = new Int32Array(hashSize);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Evaluates an ordering.
|
|
294
|
+
*/
|
|
295
|
+
public evaluate(ordering: ArrayLike<number>): OrderingEvaluation {
|
|
296
|
+
if (!this.scratch) {
|
|
297
|
+
this.scratch = new OrderingCursor(this, ordering);
|
|
298
|
+
} else {
|
|
299
|
+
this.scratch.reset(ordering);
|
|
300
|
+
}
|
|
301
|
+
return this.scratch.evaluation;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* A cursor keeps the intermediate results of an ordering so that
|
|
306
|
+
* swapping two levels only recomputes the levels above them.
|
|
307
|
+
*/
|
|
308
|
+
public cursor(ordering: ArrayLike<number>): OrderingCursor {
|
|
309
|
+
return new OrderingCursor(this, ordering);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* @returns true when evaluation a is strictly better than b:
|
|
314
|
+
* fewer nodes, or with a cost model a lower expected cost
|
|
315
|
+
* (ties in the expected cost are broken by the amount of nodes)
|
|
316
|
+
*/
|
|
317
|
+
public isBetter(a: OrderingEvaluation, b: OrderingEvaluation): boolean {
|
|
318
|
+
return this.compare(a, b) < 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* negative when a is better than b, positive when b is better, 0 when equal
|
|
323
|
+
*/
|
|
324
|
+
public compare(a: OrderingEvaluation, b: OrderingEvaluation): number {
|
|
325
|
+
if (this.costModel) {
|
|
326
|
+
const epsilon = costTolerance(Math.max(Math.abs(a.expectedCost), Math.abs(b.expectedCost)), this.costUnit);
|
|
327
|
+
if (a.expectedCost < b.expectedCost - epsilon) {
|
|
328
|
+
return -1;
|
|
329
|
+
}
|
|
330
|
+
if (a.expectedCost > b.expectedCost + epsilon) {
|
|
331
|
+
return 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return a.nodeCount - b.nodeCount;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Builds the reduced diagram of an ordering.
|
|
339
|
+
*/
|
|
340
|
+
public buildDiagram(ordering: ArrayLike<number>): ReducedDiagram {
|
|
341
|
+
const capacity = Math.max(1, this.rowCount);
|
|
342
|
+
const record: DiagramRecord = {
|
|
343
|
+
nodeLevel: new Int32Array(capacity),
|
|
344
|
+
nodeLo: new Int32Array(capacity),
|
|
345
|
+
nodeHi: new Int32Array(capacity)
|
|
346
|
+
};
|
|
347
|
+
const cursor = new OrderingCursor(this, ordering, record);
|
|
348
|
+
const count = cursor.evaluation.internalNodeCount;
|
|
349
|
+
return {
|
|
350
|
+
variableCount: this.variableCount,
|
|
351
|
+
terminalCount: this.terminalCount,
|
|
352
|
+
root: cursor.root,
|
|
353
|
+
nodeLevel: record.nodeLevel.subarray(0, count),
|
|
354
|
+
nodeLo: record.nodeLo.subarray(0, count),
|
|
355
|
+
nodeHi: record.nodeHi.subarray(0, count),
|
|
356
|
+
ordering: Array.from(ordering),
|
|
357
|
+
nodesPerLevel: cursor.evaluation.nodesPerLevel
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Resolves the don't-care rows the same way the BDD of the given ordering
|
|
363
|
+
* resolves them (a don't-care branch takes the value of its sibling branch)
|
|
364
|
+
* and returns a table without don't-care rows.
|
|
365
|
+
* When the table has no don't-care rows, the same table is returned.
|
|
366
|
+
*/
|
|
367
|
+
public complete(ordering: ArrayLike<number>): DenseTruthTable {
|
|
368
|
+
if (this.dontCare < 0) {
|
|
369
|
+
return this.table;
|
|
370
|
+
}
|
|
371
|
+
const diagram = this.buildDiagram(ordering);
|
|
372
|
+
const n = this.variableCount;
|
|
373
|
+
const terminalCount = this.terminalCount;
|
|
374
|
+
const terminalIds = new Int32Array(this.rowCount);
|
|
375
|
+
let stillDontCare = false;
|
|
376
|
+
for (let row = 0; row < this.rowCount; row++) {
|
|
377
|
+
let id = diagram.root;
|
|
378
|
+
while (id >= terminalCount) {
|
|
379
|
+
const index = id - terminalCount;
|
|
380
|
+
const variable = diagram.ordering[diagram.nodeLevel[index]];
|
|
381
|
+
id = ((row >>> (n - 1 - variable)) & 1) === 0 ? diagram.nodeLo[index] : diagram.nodeHi[index];
|
|
382
|
+
}
|
|
383
|
+
terminalIds[row] = id;
|
|
384
|
+
if (id === this.dontCare) {
|
|
385
|
+
stillDontCare = true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return withTerminalIds(this.table, terminalIds, stillDontCare ? this.dontCare : -1);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Computes the blocks of one level out of the blocks of the level below.
|
|
393
|
+
* @internal
|
|
394
|
+
*/
|
|
395
|
+
public computeLevel(
|
|
396
|
+
ordering: number[],
|
|
397
|
+
level: number,
|
|
398
|
+
deeperIds: Int32Array,
|
|
399
|
+
deeperWeights: Float64Array | null,
|
|
400
|
+
ids: Int32Array,
|
|
401
|
+
weights: Float64Array | null,
|
|
402
|
+
firstId: number,
|
|
403
|
+
record: DiagramRecord | null
|
|
404
|
+
): { created: number, payingWeight: number } {
|
|
405
|
+
const dc = this.dontCare;
|
|
406
|
+
const terminalCount = this.terminalCount;
|
|
407
|
+
const hashLo = this.hashLo;
|
|
408
|
+
const hashHi = this.hashHi;
|
|
409
|
+
const hashId = this.hashId;
|
|
410
|
+
const count = 1 << level;
|
|
411
|
+
let tableSize = 16;
|
|
412
|
+
while (tableSize < count * 2) {
|
|
413
|
+
tableSize *= 2;
|
|
414
|
+
}
|
|
415
|
+
const mask = tableSize - 1;
|
|
416
|
+
hashLo.fill(-1, 0, tableSize);
|
|
417
|
+
|
|
418
|
+
// the position of this level's function inside the index of the level below:
|
|
419
|
+
// the functions above the level below are sorted ascending, the largest one is bit 0
|
|
420
|
+
const variable = ordering[level];
|
|
421
|
+
let position = 0;
|
|
422
|
+
for (let k = 0; k <= level; k++) {
|
|
423
|
+
if (ordering[k] > variable) {
|
|
424
|
+
position++;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const lowMask = (1 << position) - 1;
|
|
428
|
+
const hiBit = 1 << position;
|
|
429
|
+
|
|
430
|
+
let nextId = firstId;
|
|
431
|
+
let created = 0;
|
|
432
|
+
let payingWeight = 0;
|
|
433
|
+
for (let block = 0; block < count; block++) {
|
|
434
|
+
const loIndex = ((block >>> position) << (position + 1)) | (block & lowMask);
|
|
435
|
+
const hiIndex = loIndex | hiBit;
|
|
436
|
+
const lo = deeperIds[loIndex];
|
|
437
|
+
const hi = deeperIds[hiIndex];
|
|
438
|
+
let blockWeight = 0;
|
|
439
|
+
if (deeperWeights && weights) {
|
|
440
|
+
blockWeight = deeperWeights[loIndex] + deeperWeights[hiIndex];
|
|
441
|
+
weights[block] = blockWeight;
|
|
442
|
+
}
|
|
443
|
+
let id: number;
|
|
444
|
+
if (lo === dc) {
|
|
445
|
+
id = hi;
|
|
446
|
+
} else if (hi === dc || lo === hi) {
|
|
447
|
+
id = lo;
|
|
448
|
+
} else {
|
|
449
|
+
payingWeight += blockWeight;
|
|
450
|
+
let h = (Math.imul(lo, 0x9E3779B1) ^ Math.imul(hi, 0x85EBCA77)) >>> 0;
|
|
451
|
+
h = (h ^ (h >>> 15)) & mask;
|
|
452
|
+
while (true) {
|
|
453
|
+
const storedLo = hashLo[h];
|
|
454
|
+
if (storedLo === -1) {
|
|
455
|
+
hashLo[h] = lo;
|
|
456
|
+
hashHi[h] = hi;
|
|
457
|
+
hashId[h] = nextId;
|
|
458
|
+
id = nextId;
|
|
459
|
+
if (record) {
|
|
460
|
+
record.nodeLevel[nextId - terminalCount] = level;
|
|
461
|
+
record.nodeLo[nextId - terminalCount] = lo;
|
|
462
|
+
record.nodeHi[nextId - terminalCount] = hi;
|
|
463
|
+
}
|
|
464
|
+
nextId++;
|
|
465
|
+
created++;
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
if (storedLo === lo && hashHi[h] === hi) {
|
|
469
|
+
id = hashId[h];
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
h = (h + 1) & mask;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
ids[block] = id;
|
|
476
|
+
}
|
|
477
|
+
return { created, payingWeight };
|
|
478
|
+
}
|
|
479
|
+
}
|