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,310 @@
1
+ import type { DenseTruthTable } from './dense-truth-table.js';
2
+ import { naturalOrdering } from './dense-truth-table.js';
3
+ import { OrderingEvaluator } from './ordering-evaluator.js';
4
+ import { IntPairMap } from './int-pair-map.js';
5
+
6
+ /**
7
+ * thrown by SharedDiagram when more nodes than allowed would be created
8
+ */
9
+ export class DiagramBudgetExceededError extends Error {
10
+ constructor() {
11
+ super('the exact search would need more diagram nodes than allowed');
12
+ }
13
+ }
14
+
15
+ export const DEFAULT_MAX_DIAGRAM_NODES = 4000000;
16
+
17
+ /**
18
+ * A hash-consed decision diagram of a fully specified truth table in the
19
+ * natural variable order, with a restrict() operation.
20
+ *
21
+ * Because the variable order is fixed, two functions are equal if and only if
22
+ * they have the same node id. The exact searches rely on this to recognize
23
+ * equal cofactors.
24
+ */
25
+ export class SharedDiagram {
26
+ public readonly variableCount: number;
27
+ public readonly terminalCount: number;
28
+ public readonly rootId: number;
29
+ /** the boolean functions the truth table depends on, ascending */
30
+ public readonly relevant: number[];
31
+ /** bit index of each relevant boolean function inside support masks, -1 for irrelevant ones */
32
+ public readonly compactOf: Int32Array;
33
+
34
+ private nodeVar: Int32Array;
35
+ private nodeLo: Int32Array;
36
+ private nodeHi: Int32Array;
37
+ private nodeSupport: Int32Array;
38
+ private nodeCount: number;
39
+ private readonly uniqueTables: IntPairMap[];
40
+ private readonly restrictMemo: IntPairMap = new IntPairMap(1 << 16);
41
+ private readonly maxNodes: number;
42
+
43
+ constructor(
44
+ public readonly table: DenseTruthTable,
45
+ /**
46
+ * restrict() creates nodes for the restricted functions, which can be a lot
47
+ * on unstructured functions. More nodes than this throw a DiagramBudgetExceededError.
48
+ * Default: 4000000 (roughly 400 MB)
49
+ */
50
+ maxNodes: number = DEFAULT_MAX_DIAGRAM_NODES
51
+ ) {
52
+ this.maxNodes = maxNodes;
53
+ this.variableCount = table.variableCount;
54
+ this.terminalCount = table.terminalValues.length;
55
+ const evaluator = new OrderingEvaluator(table);
56
+ const diagram = evaluator.buildDiagram(naturalOrdering(this.variableCount));
57
+ const count = diagram.nodeLevel.length;
58
+ const capacity = Math.max(1024, count * 2);
59
+ this.nodeVar = new Int32Array(capacity);
60
+ this.nodeLo = new Int32Array(capacity);
61
+ this.nodeHi = new Int32Array(capacity);
62
+ this.nodeSupport = new Int32Array(capacity);
63
+ this.nodeVar.set(diagram.nodeLevel);
64
+ this.nodeLo.set(diagram.nodeLo);
65
+ this.nodeHi.set(diagram.nodeHi);
66
+ this.nodeCount = count;
67
+ this.rootId = diagram.root;
68
+
69
+ // relevant variables and their compact bit index
70
+ this.compactOf = new Int32Array(this.variableCount).fill(-1);
71
+ this.relevant = [];
72
+ const rootSupport = this.rawSupport(diagram.root);
73
+ for (let v = 0; v < this.variableCount; v++) {
74
+ if ((rootSupport & (1 << v)) !== 0) {
75
+ this.compactOf[v] = this.relevant.length;
76
+ this.relevant.push(v);
77
+ }
78
+ }
79
+ // support masks in compact bit indexes; children always have smaller ids
80
+ for (let i = 0; i < count; i++) {
81
+ this.nodeSupport[i] = this.supportOf(this.nodeLo[i]) |
82
+ this.supportOf(this.nodeHi[i]) |
83
+ (1 << this.compactOf[this.nodeVar[i]]);
84
+ }
85
+ this.uniqueTables = [];
86
+ for (let v = 0; v < this.variableCount; v++) {
87
+ this.uniqueTables.push(new IntPairMap(256));
88
+ }
89
+ for (let id = this.terminalCount; id < this.terminalCount + count; id++) {
90
+ const i = id - this.terminalCount;
91
+ this.uniqueTables[this.nodeVar[i]].set(this.nodeLo[i], this.nodeHi[i], id);
92
+ }
93
+ }
94
+
95
+ public isTerminal(id: number): boolean {
96
+ return id < this.terminalCount;
97
+ }
98
+
99
+ /** amount of internal nodes created so far */
100
+ public get size(): number {
101
+ return this.nodeCount;
102
+ }
103
+
104
+ /** true when the diagram of the natural ordering alone already exceeds the node budget */
105
+ public get overBudget(): boolean {
106
+ return this.nodeCount > this.maxNodes;
107
+ }
108
+
109
+ /**
110
+ * support mask (compact bit indexes) of a node, 0 for terminals
111
+ */
112
+ public supportOf(id: number): number {
113
+ return id < this.terminalCount ? 0 : this.nodeSupport[id - this.terminalCount];
114
+ }
115
+
116
+ public dependsOn(id: number, variable: number): boolean {
117
+ return (this.supportOf(id) & (1 << this.compactOf[variable])) !== 0;
118
+ }
119
+
120
+ /**
121
+ * the function with `variable` fixed to `value`
122
+ */
123
+ public restrict(g: number, variable: number, value: number): number {
124
+ if (g < this.terminalCount) {
125
+ return g;
126
+ }
127
+ const index = g - this.terminalCount;
128
+ if ((this.nodeSupport[index] & (1 << this.compactOf[variable])) === 0) {
129
+ return g;
130
+ }
131
+ const v = this.nodeVar[index];
132
+ if (v === variable) {
133
+ return value === 0 ? this.nodeLo[index] : this.nodeHi[index];
134
+ }
135
+ const memoKey = variable * 2 + value;
136
+ const memo = this.restrictMemo.get(g, memoKey);
137
+ if (memo !== -1) {
138
+ return memo;
139
+ }
140
+ const lo = this.restrict(this.nodeLo[index], variable, value);
141
+ const hi = this.restrict(this.nodeHi[index], variable, value);
142
+ const result = this.makeNode(v, lo, hi);
143
+ this.restrictMemo.set(g, memoKey, result);
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Moves the boolean functions the truth table does not depend on
149
+ * to the bottom, keeping the order of the others.
150
+ */
151
+ public normalizeOrdering(ordering: ArrayLike<number>): number[] {
152
+ const relevant: number[] = [];
153
+ const irrelevant: number[] = [];
154
+ for (let i = 0; i < ordering.length; i++) {
155
+ if (this.compactOf[ordering[i]] === -1) {
156
+ irrelevant.push(ordering[i]);
157
+ } else {
158
+ relevant.push(ordering[i]);
159
+ }
160
+ }
161
+ return relevant.concat(irrelevant);
162
+ }
163
+
164
+ public irrelevantVariables(): number[] {
165
+ return naturalOrdering(this.variableCount).filter(v => this.compactOf[v] === -1);
166
+ }
167
+
168
+ private makeNode(variable: number, lo: number, hi: number): number {
169
+ if (lo === hi) {
170
+ return lo;
171
+ }
172
+ const table = this.uniqueTables[variable];
173
+ const existing = table.get(lo, hi);
174
+ if (existing !== -1) {
175
+ return existing;
176
+ }
177
+ if (this.nodeCount >= this.maxNodes) {
178
+ throw new DiagramBudgetExceededError();
179
+ }
180
+ if (this.nodeCount === this.nodeVar.length) {
181
+ this.growNodes();
182
+ }
183
+ const index = this.nodeCount++;
184
+ const id = index + this.terminalCount;
185
+ this.nodeVar[index] = variable;
186
+ this.nodeLo[index] = lo;
187
+ this.nodeHi[index] = hi;
188
+ this.nodeSupport[index] = this.supportOf(lo) | this.supportOf(hi) | (1 << this.compactOf[variable]);
189
+ table.set(lo, hi, id);
190
+ return id;
191
+ }
192
+
193
+ /**
194
+ * support in natural variable bits, only used before the compact mapping exists
195
+ */
196
+ private rawSupport(id: number): number {
197
+ const memo: Map<number, number> = new Map();
198
+ const recurse = (node: number): number => {
199
+ if (node < this.terminalCount) {
200
+ return 0;
201
+ }
202
+ const known = memo.get(node);
203
+ if (known !== undefined) {
204
+ return known;
205
+ }
206
+ const index = node - this.terminalCount;
207
+ const ret = recurse(this.nodeLo[index]) | recurse(this.nodeHi[index]) | (1 << this.nodeVar[index]);
208
+ memo.set(node, ret);
209
+ return ret;
210
+ };
211
+ return recurse(id);
212
+ }
213
+
214
+ private growNodes() {
215
+ const capacity = this.nodeVar.length * 2;
216
+ const grow = (old: Int32Array) => {
217
+ const ret = new Int32Array(capacity);
218
+ ret.set(old);
219
+ return ret;
220
+ };
221
+ this.nodeVar = grow(this.nodeVar);
222
+ this.nodeLo = grow(this.nodeLo);
223
+ this.nodeHi = grow(this.nodeHi);
224
+ this.nodeSupport = grow(this.nodeSupport);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * binary min-heap of search states, deterministic tie breaking:
230
+ * smaller estimate first, then larger cost (deeper states), then smaller subset
231
+ */
232
+ export class StateHeap {
233
+ private readonly estimates: number[] = [];
234
+ private readonly costs: number[] = [];
235
+ private readonly subsets: number[] = [];
236
+
237
+ get size(): number {
238
+ return this.estimates.length;
239
+ }
240
+
241
+ public push(estimate: number, cost: number, subset: number) {
242
+ this.estimates.push(estimate);
243
+ this.costs.push(cost);
244
+ this.subsets.push(subset);
245
+ let i = this.estimates.length - 1;
246
+ while (i > 0) {
247
+ const parent = (i - 1) >> 1;
248
+ if (this.less(i, parent)) {
249
+ this.swap(i, parent);
250
+ i = parent;
251
+ } else {
252
+ break;
253
+ }
254
+ }
255
+ }
256
+
257
+ public pop(): { estimate: number, cost: number, subset: number } {
258
+ const ret = {
259
+ estimate: this.estimates[0],
260
+ cost: this.costs[0],
261
+ subset: this.subsets[0]
262
+ };
263
+ const last = this.estimates.length - 1;
264
+ this.swap(0, last);
265
+ this.estimates.pop();
266
+ this.costs.pop();
267
+ this.subsets.pop();
268
+ let i = 0;
269
+ const size = this.estimates.length;
270
+ while (true) {
271
+ const left = i * 2 + 1;
272
+ const right = left + 1;
273
+ let smallest = i;
274
+ if (left < size && this.less(left, smallest)) {
275
+ smallest = left;
276
+ }
277
+ if (right < size && this.less(right, smallest)) {
278
+ smallest = right;
279
+ }
280
+ if (smallest === i) {
281
+ break;
282
+ }
283
+ this.swap(i, smallest);
284
+ i = smallest;
285
+ }
286
+ return ret;
287
+ }
288
+
289
+ private less(a: number, b: number): boolean {
290
+ if (this.estimates[a] !== this.estimates[b]) {
291
+ return this.estimates[a] < this.estimates[b];
292
+ }
293
+ if (this.costs[a] !== this.costs[b]) {
294
+ return this.costs[a] > this.costs[b];
295
+ }
296
+ return this.subsets[a] < this.subsets[b];
297
+ }
298
+
299
+ private swap(a: number, b: number) {
300
+ let tmp = this.estimates[a];
301
+ this.estimates[a] = this.estimates[b];
302
+ this.estimates[b] = tmp;
303
+ tmp = this.costs[a];
304
+ this.costs[a] = this.costs[b];
305
+ this.costs[b] = tmp;
306
+ tmp = this.subsets[a];
307
+ this.subsets[a] = this.subsets[b];
308
+ this.subsets[b] = tmp;
309
+ }
310
+ }
@@ -0,0 +1,2 @@
1
+ export * from './packed-bdd.js';
2
+ export * from './packed-bdd-base64-string.js';
@@ -0,0 +1,47 @@
1
+ import type { PackedBdd } from './packed-bdd.js';
2
+
3
+ /*
4
+
5
+ String transport format of a PackedBdd, to embed it into JavaScript source code.
6
+ The string only contains ASCII characters that are safe inside of quotes,
7
+ so bundlers and minifiers never have to escape anything.
8
+
9
+ - The first char is the byte width of the entries: '1', '2' or '4'
10
+ - The rest is the standard base64 of the memory of the typed array
11
+
12
+ The base64 is decoded with the native Uint8Array.fromBase64() where it exists
13
+ and with atob() everywhere else, both are faster than decoding it in JavaScript.
14
+ The typed array then directly uses the decoded memory. This assumes a little-endian
15
+ platform, which are all browsers and nearly all Node.js servers.
16
+
17
+ */
18
+
19
+ export function packedBddToBase64String(packedBdd: PackedBdd): string {
20
+ const bytes = new Uint8Array(packedBdd.buffer, packedBdd.byteOffset, packedBdd.byteLength);
21
+
22
+ let binary = '';
23
+ const chunkSize = 0x8000;
24
+ for (let i = 0; i < bytes.length; i += chunkSize) {
25
+ binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunkSize) as any);
26
+ }
27
+ return packedBdd.BYTES_PER_ELEMENT + btoa(binary);
28
+ }
29
+
30
+ function base64ToBytes(base64: string): Uint8Array {
31
+ const fromBase64 = (Uint8Array as any).fromBase64;
32
+ if (fromBase64) {
33
+ return fromBase64(base64);
34
+ }
35
+ const binary = atob(base64);
36
+ const bytes = new Uint8Array(binary.length);
37
+ for (let i = 0; i < binary.length; i++) {
38
+ bytes[i] = binary.charCodeAt(i);
39
+ }
40
+ return bytes;
41
+ }
42
+
43
+ export function packedBddFromBase64String(str: string): PackedBdd {
44
+ const width = str.charCodeAt(0) - 48;
45
+ const bytes = base64ToBytes(str.slice(1));
46
+ return width === 1 ? bytes : width === 2 ? new Uint16Array(bytes.buffer) : new Uint32Array(bytes.buffer);
47
+ }
@@ -0,0 +1,98 @@
1
+ import type { RootNode } from '../root-node.js';
2
+ import type { AbstractNode } from '../abstract-node.js';
3
+ import type { NonLeafNode, ResolverFunctions } from '../types.js';
4
+
5
+ /*
6
+
7
+ A PackedBdd is the whole BDD in a single typed array.
8
+ It can be resolved directly without creating any JavaScript objects,
9
+ so there is nothing to parse on startup.
10
+
11
+ Layout:
12
+ - Every non-leaf node takes three consecutive entries: [level, ref0, ref1]
13
+ - level is the index of the boolean function
14
+ - ref0 is the reference that is followed when the boolean function returns false
15
+ - ref1 is the reference that is followed when it returns true
16
+ - The node with the id x starts at index 3 * x, the root node has the id 0.
17
+ - With n = array.length / 3 (the amount of non-leaf nodes), a reference r
18
+ - r < n points to the non-leaf node with the id r
19
+ - r >= n is a leaf node with the value r - n
20
+
21
+ The narrowest array type that fits all entries is used,
22
+ so a BDD with less than ~250 nodes and values only needs 3 bytes per node.
23
+
24
+ */
25
+
26
+ export type PackedBdd = Uint8Array | Uint16Array | Uint32Array;
27
+
28
+ /**
29
+ * Creates a PackedBdd from a (minimized) BDD.
30
+ * The leaf values must be non-negative integers.
31
+ */
32
+ export function bddToPackedBdd(bdd: RootNode): PackedBdd {
33
+ const branchesOf = (node: AbstractNode) => (node as NonLeafNode).branches;
34
+
35
+ // breadth first, branch 0 before branch 1, so the output is deterministic
36
+ const nonLeafNodes: AbstractNode[] = [bdd];
37
+ const idByNode = new Map<AbstractNode, number>([[bdd, 0]]);
38
+ for (let i = 0; i < nonLeafNodes.length; i++) {
39
+ const branches = branchesOf(nonLeafNodes[i]);
40
+ for (const child of [branches.getBranch('0'), branches.getBranch('1')]) {
41
+ if (!child.isLeafNode() && !idByNode.has(child)) {
42
+ idByNode.set(child, nonLeafNodes.length);
43
+ nonLeafNodes.push(child);
44
+ }
45
+ }
46
+ }
47
+
48
+ const n = nonLeafNodes.length;
49
+ const ref = (node: AbstractNode): number => {
50
+ if (!node.isLeafNode()) {
51
+ return idByNode.get(node) as number;
52
+ }
53
+ const value = node.asLeafNode().value;
54
+ if (!Number.isInteger(value) || value < 0) {
55
+ throw new Error('PackedBdd only supports non-negative integer leaf values, got ' + value);
56
+ }
57
+ return n + value;
58
+ };
59
+ const entries: number[] = [];
60
+ let max = 0;
61
+ nonLeafNodes.forEach(node => {
62
+ const branches = branchesOf(node);
63
+ const nodeEntries = [node.level, ref(branches.getBranch('0')), ref(branches.getBranch('1'))];
64
+ nodeEntries.forEach(entry => {
65
+ entries.push(entry);
66
+ max = Math.max(max, entry);
67
+ });
68
+ });
69
+
70
+ if (max <= 0xFF) {
71
+ return Uint8Array.from(entries);
72
+ } else if (max <= 0xFFFF) {
73
+ return Uint16Array.from(entries);
74
+ } else if (max <= 0xFFFFFFFF) {
75
+ return Uint32Array.from(entries);
76
+ }
77
+ throw new Error('BDD too big for a PackedBdd');
78
+ }
79
+
80
+ /**
81
+ * Resolves a value by walking directly over the PackedBdd.
82
+ * Works with every array type (also a plain number[]).
83
+ */
84
+ export function resolveWithPackedBdd<T = any>(
85
+ packedBdd: ArrayLike<number>,
86
+ fns: ResolverFunctions<T>,
87
+ input: T
88
+ ): number {
89
+ const n = packedBdd.length / 3;
90
+ let i = 0;
91
+ while (true) {
92
+ const ref = packedBdd[fns[packedBdd[i]](input) ? i + 2 : i + 1];
93
+ if (ref >= n) {
94
+ return ref - n;
95
+ }
96
+ i = ref * 3;
97
+ }
98
+ }
package/src/root-node.ts CHANGED
@@ -2,15 +2,13 @@ import { AbstractNode } from './abstract-node.js';
2
2
  import { Branches } from './branches.js';
3
3
  import type {
4
4
  NonRootNode,
5
- ResolverFunctions,
6
- SimpleBdd
5
+ ResolverFunctions
7
6
  } from './types.js';
8
7
  import {
9
8
  lastOfArray
10
9
  } from './util.js';
11
10
  import { InternalNode } from './internal-node.js';
12
11
  import { LeafNode } from './leaf-node.js';
13
- import { bddToSimpleBdd } from './minimal-string/index.js';
14
12
 
15
13
  export class RootNode extends AbstractNode {
16
14
  public branches: Branches = new Branches(this);
@@ -87,7 +85,7 @@ export class RootNode extends AbstractNode {
87
85
  }
88
86
  let successCount = 0;
89
87
  let lastLevel = lastOfArray(this.getLevels());
90
- while (lastLevel > 0) {
88
+ while (lastLevel >= 0) {
91
89
  const nodes: InternalNode[] = this.getNodesOfLevel(lastLevel) as InternalNode[];
92
90
  if (logState) {
93
91
  console.log(
@@ -185,8 +183,4 @@ export class RootNode extends AbstractNode {
185
183
  }
186
184
  }
187
185
  }
188
-
189
- public toSimpleBdd(): SimpleBdd {
190
- return bddToSimpleBdd(this);
191
- }
192
186
  }
package/src/types.ts CHANGED
@@ -17,21 +17,3 @@ export type ResolverFunction<T> = (i: T) => boolean;
17
17
  export type ResolverFunctions<T = any> = {
18
18
  [k: number]: ResolverFunction<T>;
19
19
  };
20
-
21
-
22
-
23
-
24
- // 1 char which is the value
25
- export type SimpleBddLeafNode = number;
26
-
27
- /**
28
- * a simple bdd is a json-representation
29
- * which could be parsed from the minimal string
30
- * use this to have great performance
31
- * when resolving values
32
- */
33
- export type SimpleBdd = {
34
- 0: SimpleBdd | SimpleBddLeafNode; // branch-0
35
- 1: SimpleBdd | SimpleBddLeafNode; // branch-1
36
- l: number; // level of the boolean function
37
- };
package/src/util.ts CHANGED
@@ -56,7 +56,8 @@ export function decimalToPaddedBinary(
56
56
  decimal: number,
57
57
  padding: number
58
58
  ) {
59
- const binary = (decimal >>> 0).toString(2);
59
+ // >>> 0 would cut off everything above 32 bits, only use it for the two's complement of negative numbers
60
+ const binary = (decimal < 0 ? decimal >>> 0 : decimal).toString(2);
60
61
  const padded = binary.padStart(padding, '0');
61
62
  return padded;
62
63
  }
@@ -86,15 +87,23 @@ export function maxBinaryWithLength(length: number): string {
86
87
  export function getNextStateSet(
87
88
  stateSet: string
88
89
  ): string {
89
- const decimal = binaryToDecimal(stateSet);
90
- const increase = decimal + 1;
91
- const binary = decimalToPaddedBinary(increase, stateSet.length);
92
- return binary;
90
+ if (stateSet.length <= 52) {
91
+ // fast path, the number stays exact
92
+ const decimal = binaryToDecimal(stateSet);
93
+ return decimalToPaddedBinary(decimal + 1, stateSet.length);
94
+ }
95
+ // longer keys would lose precision as a number, so increment the string directly
96
+ const lastZero = stateSet.lastIndexOf('0');
97
+ if (lastZero === -1) {
98
+ // overflow, the result gets one char longer
99
+ return '1' + '0'.repeat(stateSet.length);
100
+ }
101
+ return stateSet.slice(0, lastZero) + '1' + '0'.repeat(stateSet.length - lastZero - 1);
93
102
  }
94
103
 
95
104
  export function firstKeyOfMap(map: Map<string, any>): string {
96
105
  const iterator1 = map.keys();
97
- return iterator1.next().value;
106
+ return iterator1.next().value as string;
98
107
  }
99
108
 
100
109
  /**
@@ -1,8 +0,0 @@
1
- import { RootNode } from '../root-node.js';
2
- import { AbstractNode } from '../abstract-node.js';
3
- export declare function bddToMinimalString(bdd: RootNode): string;
4
- export declare function nodeToString(node: AbstractNode, idByNode: Map<AbstractNode, string>, lastCode: number): {
5
- id: string;
6
- str: string;
7
- nextCode: number;
8
- };
@@ -1,59 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.bddToMinimalString = bddToMinimalString;
4
- exports.nodeToString = nodeToString;
5
- const string_format_js_1 = require("./string-format.js");
6
- const string_format_js_2 = require("./string-format.js");
7
- function bddToMinimalString(bdd) {
8
- let ret = '';
9
- let currentCharCode = string_format_js_2.FIRST_CHAR_CODE_FOR_ID;
10
- // add leaf node count
11
- const leafNodeAmount = bdd.getLeafNodes().length;
12
- if (leafNodeAmount > 99) {
13
- throw new Error('cannot build string with too many leaf nodes');
14
- }
15
- ret += leafNodeAmount.toString().padStart(2, '0');
16
- const levelsHighestFirst = bdd.levels.slice().reverse();
17
- const idByNode = new Map();
18
- levelsHighestFirst.forEach(level => {
19
- const nodes = bdd.getNodesOfLevel(level);
20
- nodes.forEach(node => {
21
- const stringRep = nodeToString(node, idByNode, currentCharCode);
22
- currentCharCode = stringRep.nextCode;
23
- idByNode.set(node, stringRep.id);
24
- ret += stringRep.str;
25
- });
26
- });
27
- return ret;
28
- }
29
- function nodeToString(node, idByNode, lastCode) {
30
- const nextId = (0, string_format_js_1.getNextCharId)(lastCode);
31
- switch (node.type) {
32
- case 'LeafNode':
33
- const valueChar = (0, string_format_js_1.getCharOfValue)(node.asLeafNode().value);
34
- return {
35
- id: nextId.char,
36
- nextCode: nextId.nextCode,
37
- str: nextId.char + valueChar
38
- };
39
- case 'InternalNode':
40
- const branch0Id = idByNode.get(node.asInternalNode().branches.getBranch('0'));
41
- const branch1Id = idByNode.get(node.asInternalNode().branches.getBranch('1'));
42
- return {
43
- id: nextId.char,
44
- nextCode: nextId.nextCode,
45
- str: nextId.char + branch0Id + branch1Id + (0, string_format_js_1.getCharOfLevel)(node.level)
46
- };
47
- case 'RootNode':
48
- const branch0IdRoot = idByNode.get(node.asRootNode().branches.getBranch('0'));
49
- const branch1IdRoot = idByNode.get(node.asRootNode().branches.getBranch('1'));
50
- return {
51
- id: nextId.char,
52
- nextCode: nextId.nextCode,
53
- str: '' + branch0IdRoot + branch1IdRoot + (0, string_format_js_1.getCharOfLevel)(node.level)
54
- };
55
- default:
56
- throw new Error('unknown node type');
57
- }
58
- }
59
- //# sourceMappingURL=bdd-to-minimal-string.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bdd-to-minimal-string.js","sourceRoot":"","sources":["../../../../src/minimal-string/bdd-to-minimal-string.ts"],"names":[],"mappings":"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"}
@@ -1,11 +0,0 @@
1
- import { RootNode } from '../root-node.js';
2
- import { SimpleBdd } from '../types.js';
3
- import { AbstractNode } from '../abstract-node.js';
4
- /**
5
- * @recursive
6
- */
7
- export declare function bddToSimpleBdd(bdd: RootNode): SimpleBdd;
8
- /**
9
- * @recursive
10
- */
11
- export declare function nodeToSimpleBddNode(node: AbstractNode): SimpleBdd;
@@ -1,23 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.bddToSimpleBdd = bddToSimpleBdd;
4
- exports.nodeToSimpleBddNode = nodeToSimpleBddNode;
5
- /**
6
- * @recursive
7
- */
8
- function bddToSimpleBdd(bdd) {
9
- return nodeToSimpleBddNode(bdd);
10
- }
11
- /**
12
- * @recursive
13
- */
14
- function nodeToSimpleBddNode(node) {
15
- const branch0 = node.branches.getBranch('0');
16
- const branch1 = node.branches.getBranch('1');
17
- return {
18
- l: node.level,
19
- 0: branch0.isLeafNode() ? branch0.asLeafNode().value : nodeToSimpleBddNode(branch0),
20
- 1: branch1.isLeafNode() ? branch1.asLeafNode().value : nodeToSimpleBddNode(branch1),
21
- };
22
- }
23
- //# sourceMappingURL=bdd-to-simple-bdd.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bdd-to-simple-bdd.js","sourceRoot":"","sources":["../../../../src/minimal-string/bdd-to-simple-bdd.ts"],"names":[],"mappings":";;AAUA,wCAEC;AAMD,kDAYC;AAvBD;;GAEG;AACH,SAAgB,cAAc,CAAC,GAAa;IACxC,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;AACpC,CAAC;AAGD;;GAEG;AACH,SAAgB,mBAAmB,CAC/B,IAAkB;IAGlB,MAAM,OAAO,GAAkB,IAAqB,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAkB,IAAqB,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAE7E,OAAO;QACH,CAAC,EAAE,IAAI,CAAC,KAAK;QACb,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC;QACnF,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC;KACtF,CAAC;AACN,CAAC"}
@@ -1,5 +0,0 @@
1
- export * from './bdd-to-minimal-string.js';
2
- export * from './minimal-string-to-simple-bdd.js';
3
- export * from './resolve-with-simple-bdd.js';
4
- export * from './string-format.js';
5
- export * from './bdd-to-simple-bdd.js';
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/minimal-string/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6DAA2C;AAC3C,oEAAkD;AAClD,+DAA6C;AAC7C,qDAAmC;AACnC,yDAAuC"}