binary-decision-diagram 4.0.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.
Files changed (185) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +211 -31
  3. package/dist/cjs/src/abstract-node.js +7 -4
  4. package/dist/cjs/src/abstract-node.js.map +1 -1
  5. package/dist/cjs/src/branches.d.ts +1 -0
  6. package/dist/cjs/src/branches.js +2 -1
  7. package/dist/cjs/src/branches.js.map +1 -1
  8. package/dist/cjs/src/create-bdd-from-truth-table.js.map +1 -1
  9. package/dist/cjs/src/index.d.ts +2 -1
  10. package/dist/cjs/src/index.es5.js +17 -7
  11. package/dist/cjs/src/index.es5.js.map +1 -1
  12. package/dist/cjs/src/index.js +2 -1
  13. package/dist/cjs/src/index.js.map +1 -1
  14. package/dist/cjs/src/internal-node.js +6 -3
  15. package/dist/cjs/src/internal-node.js.map +1 -1
  16. package/dist/cjs/src/optimize-brute-force.js +5 -7
  17. package/dist/cjs/src/optimize-brute-force.js.map +1 -1
  18. package/dist/cjs/src/ordering/build-bdd.d.ts +13 -0
  19. package/dist/cjs/src/ordering/build-bdd.js +64 -0
  20. package/dist/cjs/src/ordering/build-bdd.js.map +1 -0
  21. package/dist/cjs/src/ordering/dense-truth-table.d.ts +42 -0
  22. package/dist/cjs/src/ordering/dense-truth-table.js +125 -0
  23. package/dist/cjs/src/ordering/dense-truth-table.js.map +1 -0
  24. package/dist/cjs/src/ordering/exact-cost-ordering.d.ts +91 -0
  25. package/dist/cjs/src/ordering/exact-cost-ordering.js +351 -0
  26. package/dist/cjs/src/ordering/exact-cost-ordering.js.map +1 -0
  27. package/dist/cjs/src/ordering/exact-ordering.d.ts +89 -0
  28. package/dist/cjs/src/ordering/exact-ordering.js +260 -0
  29. package/dist/cjs/src/ordering/exact-ordering.js.map +1 -0
  30. package/dist/cjs/src/ordering/index.d.ts +22 -0
  31. package/dist/cjs/src/ordering/index.js +38 -0
  32. package/dist/cjs/src/ordering/index.js.map +1 -0
  33. package/dist/cjs/src/ordering/int-pair-map.d.ts +20 -0
  34. package/dist/cjs/src/ordering/int-pair-map.js +85 -0
  35. package/dist/cjs/src/ordering/int-pair-map.js.map +1 -0
  36. package/dist/cjs/src/ordering/local-search.d.ts +114 -0
  37. package/dist/cjs/src/ordering/local-search.js +317 -0
  38. package/dist/cjs/src/ordering/local-search.js.map +1 -0
  39. package/dist/cjs/src/ordering/optimize-ordering.d.ts +153 -0
  40. package/dist/cjs/src/ordering/optimize-ordering.js +406 -0
  41. package/dist/cjs/src/ordering/optimize-ordering.js.map +1 -0
  42. package/dist/cjs/src/ordering/ordering-evaluator.d.ts +198 -0
  43. package/dist/cjs/src/ordering/ordering-evaluator.js +384 -0
  44. package/dist/cjs/src/ordering/ordering-evaluator.js.map +1 -0
  45. package/dist/cjs/src/ordering/shared-diagram.d.ts +85 -0
  46. package/dist/cjs/src/ordering/shared-diagram.js +295 -0
  47. package/dist/cjs/src/ordering/shared-diagram.js.map +1 -0
  48. package/dist/cjs/src/packed/index.d.ts +2 -0
  49. package/dist/cjs/src/{minimal-string → packed}/index.js +2 -5
  50. package/dist/cjs/src/packed/index.js.map +1 -0
  51. package/dist/cjs/src/packed/packed-bdd-base64-string.d.ts +3 -0
  52. package/dist/cjs/src/packed/packed-bdd-base64-string.js +46 -0
  53. package/dist/cjs/src/packed/packed-bdd-base64-string.js.map +1 -0
  54. package/dist/cjs/src/packed/packed-bdd.d.ts +13 -0
  55. package/dist/cjs/src/packed/packed-bdd.js +70 -0
  56. package/dist/cjs/src/packed/packed-bdd.js.map +1 -0
  57. package/dist/cjs/src/root-node.d.ts +1 -2
  58. package/dist/cjs/src/root-node.js +1 -5
  59. package/dist/cjs/src/root-node.js.map +1 -1
  60. package/dist/cjs/src/types.d.ts +0 -12
  61. package/dist/cjs/src/util.js +14 -5
  62. package/dist/cjs/src/util.js.map +1 -1
  63. package/dist/esm/src/abstract-node.js +7 -4
  64. package/dist/esm/src/abstract-node.js.map +1 -1
  65. package/dist/esm/src/branches.d.ts +1 -0
  66. package/dist/esm/src/branches.js +1 -0
  67. package/dist/esm/src/branches.js.map +1 -1
  68. package/dist/esm/src/create-bdd-from-truth-table.js.map +1 -1
  69. package/dist/esm/src/index.d.ts +2 -1
  70. package/dist/esm/src/index.js +2 -1
  71. package/dist/esm/src/index.js.map +1 -1
  72. package/dist/esm/src/internal-node.js +7 -4
  73. package/dist/esm/src/internal-node.js.map +1 -1
  74. package/dist/esm/src/optimize-brute-force.js +5 -7
  75. package/dist/esm/src/optimize-brute-force.js.map +1 -1
  76. package/dist/esm/src/ordering/build-bdd.d.ts +13 -0
  77. package/dist/esm/src/ordering/build-bdd.js +61 -0
  78. package/dist/esm/src/ordering/build-bdd.js.map +1 -0
  79. package/dist/esm/src/ordering/dense-truth-table.d.ts +42 -0
  80. package/dist/esm/src/ordering/dense-truth-table.js +117 -0
  81. package/dist/esm/src/ordering/dense-truth-table.js.map +1 -0
  82. package/dist/esm/src/ordering/exact-cost-ordering.d.ts +91 -0
  83. package/dist/esm/src/ordering/exact-cost-ordering.js +347 -0
  84. package/dist/esm/src/ordering/exact-cost-ordering.js.map +1 -0
  85. package/dist/esm/src/ordering/exact-ordering.d.ts +89 -0
  86. package/dist/esm/src/ordering/exact-ordering.js +256 -0
  87. package/dist/esm/src/ordering/exact-ordering.js.map +1 -0
  88. package/dist/esm/src/ordering/index.d.ts +22 -0
  89. package/dist/esm/src/ordering/index.js +17 -0
  90. package/dist/esm/src/ordering/index.js.map +1 -0
  91. package/dist/esm/src/ordering/int-pair-map.d.ts +20 -0
  92. package/dist/esm/src/ordering/int-pair-map.js +81 -0
  93. package/dist/esm/src/ordering/int-pair-map.js.map +1 -0
  94. package/dist/esm/src/ordering/local-search.d.ts +114 -0
  95. package/dist/esm/src/ordering/local-search.js +308 -0
  96. package/dist/esm/src/ordering/local-search.js.map +1 -0
  97. package/dist/esm/src/ordering/optimize-ordering.d.ts +153 -0
  98. package/dist/esm/src/ordering/optimize-ordering.js +399 -0
  99. package/dist/esm/src/ordering/optimize-ordering.js.map +1 -0
  100. package/dist/esm/src/ordering/ordering-evaluator.d.ts +198 -0
  101. package/dist/esm/src/ordering/ordering-evaluator.js +377 -0
  102. package/dist/esm/src/ordering/ordering-evaluator.js.map +1 -0
  103. package/dist/esm/src/ordering/shared-diagram.d.ts +85 -0
  104. package/dist/esm/src/ordering/shared-diagram.js +289 -0
  105. package/dist/esm/src/ordering/shared-diagram.js.map +1 -0
  106. package/dist/esm/src/packed/index.d.ts +2 -0
  107. package/dist/esm/src/packed/index.js +3 -0
  108. package/dist/esm/src/packed/index.js.map +1 -0
  109. package/dist/esm/src/packed/packed-bdd-base64-string.d.ts +3 -0
  110. package/dist/esm/src/packed/packed-bdd-base64-string.js +42 -0
  111. package/dist/esm/src/packed/packed-bdd-base64-string.js.map +1 -0
  112. package/dist/esm/src/packed/packed-bdd.d.ts +13 -0
  113. package/dist/esm/src/packed/packed-bdd.js +66 -0
  114. package/dist/esm/src/packed/packed-bdd.js.map +1 -0
  115. package/dist/esm/src/root-node.d.ts +1 -2
  116. package/dist/esm/src/root-node.js +1 -5
  117. package/dist/esm/src/root-node.js.map +1 -1
  118. package/dist/esm/src/types.d.ts +0 -12
  119. package/dist/esm/src/util.js +14 -5
  120. package/dist/esm/src/util.js.map +1 -1
  121. package/docs/variable-ordering.md +306 -0
  122. package/package.json +11 -9
  123. package/src/abstract-node.ts +7 -5
  124. package/src/branches.ts +2 -0
  125. package/src/create-bdd-from-truth-table.ts +1 -1
  126. package/src/index.ts +2 -1
  127. package/src/internal-node.ts +7 -4
  128. package/src/optimize-brute-force.ts +5 -7
  129. package/src/ordering/build-bdd.ts +70 -0
  130. package/src/ordering/dense-truth-table.ts +161 -0
  131. package/src/ordering/exact-cost-ordering.ts +425 -0
  132. package/src/ordering/exact-ordering.ts +317 -0
  133. package/src/ordering/index.ts +57 -0
  134. package/src/ordering/int-pair-map.ts +85 -0
  135. package/src/ordering/local-search.ts +369 -0
  136. package/src/ordering/optimize-ordering.ts +570 -0
  137. package/src/ordering/ordering-evaluator.ts +479 -0
  138. package/src/ordering/shared-diagram.ts +310 -0
  139. package/src/packed/index.ts +2 -0
  140. package/src/packed/packed-bdd-base64-string.ts +47 -0
  141. package/src/packed/packed-bdd.ts +98 -0
  142. package/src/root-node.ts +2 -8
  143. package/src/types.ts +0 -18
  144. package/src/util.ts +15 -6
  145. package/dist/cjs/src/minimal-string/bdd-to-minimal-string.d.ts +0 -8
  146. package/dist/cjs/src/minimal-string/bdd-to-minimal-string.js +0 -59
  147. package/dist/cjs/src/minimal-string/bdd-to-minimal-string.js.map +0 -1
  148. package/dist/cjs/src/minimal-string/bdd-to-simple-bdd.d.ts +0 -11
  149. package/dist/cjs/src/minimal-string/bdd-to-simple-bdd.js +0 -23
  150. package/dist/cjs/src/minimal-string/bdd-to-simple-bdd.js.map +0 -1
  151. package/dist/cjs/src/minimal-string/index.d.ts +0 -5
  152. package/dist/cjs/src/minimal-string/index.js.map +0 -1
  153. package/dist/cjs/src/minimal-string/minimal-string-to-simple-bdd.d.ts +0 -2
  154. package/dist/cjs/src/minimal-string/minimal-string-to-simple-bdd.js +0 -57
  155. package/dist/cjs/src/minimal-string/minimal-string-to-simple-bdd.js.map +0 -1
  156. package/dist/cjs/src/minimal-string/resolve-with-simple-bdd.d.ts +0 -2
  157. package/dist/cjs/src/minimal-string/resolve-with-simple-bdd.js +0 -18
  158. package/dist/cjs/src/minimal-string/resolve-with-simple-bdd.js.map +0 -1
  159. package/dist/cjs/src/minimal-string/string-format.d.ts +0 -9
  160. package/dist/cjs/src/minimal-string/string-format.js +0 -61
  161. package/dist/cjs/src/minimal-string/string-format.js.map +0 -1
  162. package/dist/esm/src/minimal-string/bdd-to-minimal-string.d.ts +0 -8
  163. package/dist/esm/src/minimal-string/bdd-to-minimal-string.js +0 -55
  164. package/dist/esm/src/minimal-string/bdd-to-minimal-string.js.map +0 -1
  165. package/dist/esm/src/minimal-string/bdd-to-simple-bdd.d.ts +0 -11
  166. package/dist/esm/src/minimal-string/bdd-to-simple-bdd.js +0 -19
  167. package/dist/esm/src/minimal-string/bdd-to-simple-bdd.js.map +0 -1
  168. package/dist/esm/src/minimal-string/index.d.ts +0 -5
  169. package/dist/esm/src/minimal-string/index.js +0 -6
  170. package/dist/esm/src/minimal-string/index.js.map +0 -1
  171. package/dist/esm/src/minimal-string/minimal-string-to-simple-bdd.d.ts +0 -2
  172. package/dist/esm/src/minimal-string/minimal-string-to-simple-bdd.js +0 -54
  173. package/dist/esm/src/minimal-string/minimal-string-to-simple-bdd.js.map +0 -1
  174. package/dist/esm/src/minimal-string/resolve-with-simple-bdd.d.ts +0 -2
  175. package/dist/esm/src/minimal-string/resolve-with-simple-bdd.js +0 -15
  176. package/dist/esm/src/minimal-string/resolve-with-simple-bdd.js.map +0 -1
  177. package/dist/esm/src/minimal-string/string-format.d.ts +0 -9
  178. package/dist/esm/src/minimal-string/string-format.js +0 -54
  179. package/dist/esm/src/minimal-string/string-format.js.map +0 -1
  180. package/src/minimal-string/bdd-to-minimal-string.ts +0 -72
  181. package/src/minimal-string/bdd-to-simple-bdd.ts +0 -31
  182. package/src/minimal-string/index.ts +0 -5
  183. package/src/minimal-string/minimal-string-to-simple-bdd.ts +0 -67
  184. package/src/minimal-string/resolve-with-simple-bdd.ts +0 -23
  185. 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
+ }