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,369 @@
1
+ import { OrderingEvaluator, OrderingEvaluation, OrderingCursor } from './ordering-evaluator.js';
2
+ import { ensureValidOrdering, naturalOrdering } from './dense-truth-table.js';
3
+
4
+ export interface OrderingProgress {
5
+ ordering: number[];
6
+ nodeCount: number;
7
+ /** 0 without cost model */
8
+ expectedCost: number;
9
+ phase: string;
10
+ }
11
+
12
+ export type OrderingProgressCallback = (progress: OrderingProgress) => void;
13
+
14
+ export interface LocalSearchOptions {
15
+ /**
16
+ * Size of the windows whose variables are permuted exhaustively.
17
+ * 0 or 1 disables window permutation. The work grows with windowSize!,
18
+ * so at most MAX_WINDOW_SIZE (8) is allowed.
19
+ * Default: 3
20
+ */
21
+ windowSize?: number;
22
+ /**
23
+ * Maximum amount of sifting rounds.
24
+ * Default: 50
25
+ */
26
+ maxRounds?: number;
27
+ onImprovement?: OrderingProgressCallback;
28
+ log?: (message: string) => void;
29
+ }
30
+
31
+ export interface OrderingSearchResult {
32
+ ordering: number[];
33
+ nodeCount: number;
34
+ expectedCost: number;
35
+ nodesPerLevel: number[];
36
+ evaluation: OrderingEvaluation;
37
+ }
38
+
39
+ /**
40
+ * Deterministic greedy construction:
41
+ * fills the levels from the top, at each level the boolean function
42
+ * that leads to the best BDD (with the other functions in their index order)
43
+ * is picked.
44
+ */
45
+ export function greedyTopDownOrdering(evaluator: OrderingEvaluator): number[] {
46
+ const n = evaluator.variableCount;
47
+ const prefix: number[] = [];
48
+ let remaining = naturalOrdering(n);
49
+ while (remaining.length > 1) {
50
+ let bestVar = -1;
51
+ let best: OrderingEvaluation | null = null;
52
+ for (const candidate of remaining) {
53
+ const ordering = prefix.concat([candidate], remaining.filter(v => v !== candidate));
54
+ const evaluation = evaluator.evaluate(ordering);
55
+ if (!best || evaluator.isBetter(evaluation, best)) {
56
+ best = evaluation;
57
+ bestVar = candidate;
58
+ }
59
+ }
60
+ prefix.push(bestVar);
61
+ remaining = remaining.filter(v => v !== bestVar);
62
+ }
63
+ return prefix.concat(remaining);
64
+ }
65
+
66
+ /**
67
+ * Deterministic greedy construction from the bottom:
68
+ * at each step the boolean function that is best as the lowest
69
+ * remaining level is picked.
70
+ */
71
+ export function greedyBottomUpOrdering(evaluator: OrderingEvaluator): number[] {
72
+ const n = evaluator.variableCount;
73
+ let suffix: number[] = [];
74
+ let remaining = naturalOrdering(n);
75
+ while (remaining.length > 1) {
76
+ let bestVar = -1;
77
+ let best: OrderingEvaluation | null = null;
78
+ for (const candidate of remaining) {
79
+ const ordering = remaining.filter(v => v !== candidate).concat([candidate], suffix);
80
+ const evaluation = evaluator.evaluate(ordering);
81
+ if (!best || evaluator.isBetter(evaluation, best)) {
82
+ best = evaluation;
83
+ bestVar = candidate;
84
+ }
85
+ }
86
+ suffix = [bestVar].concat(suffix);
87
+ remaining = remaining.filter(v => v !== bestVar);
88
+ }
89
+ return remaining.concat(suffix);
90
+ }
91
+
92
+ /**
93
+ * Lexicographically ordered permutations of 0..size-1
94
+ */
95
+ function allPermutations(size: number): number[][] {
96
+ const ret: number[][] = [];
97
+ const recurse = (prefix: number[], rest: number[]) => {
98
+ if (rest.length === 0) {
99
+ ret.push(prefix);
100
+ return;
101
+ }
102
+ for (let i = 0; i < rest.length; i++) {
103
+ recurse(
104
+ prefix.concat([rest[i]]),
105
+ rest.slice(0, i).concat(rest.slice(i + 1))
106
+ );
107
+ }
108
+ };
109
+ recurse([], naturalOrdering(size));
110
+ return ret;
111
+ }
112
+
113
+ export const DEFAULT_WINDOW_SIZE = 3;
114
+ export const DEFAULT_MAX_ROUNDS = 50;
115
+ /**
116
+ * Window permutation tries all w! orderings of every window of w levels,
117
+ * larger windows take too long.
118
+ */
119
+ export const MAX_WINDOW_SIZE = 8;
120
+
121
+ export function ensureNonNegativeInteger(value: number, name: string) {
122
+ if (!Number.isInteger(value) || value < 0) {
123
+ throw new Error(name + ' must be a non-negative integer');
124
+ }
125
+ }
126
+
127
+ export function ensureWindowSize(windowSize: number) {
128
+ ensureNonNegativeInteger(windowSize, 'windowSize');
129
+ if (windowSize > MAX_WINDOW_SIZE) {
130
+ throw new Error('windowSize must be at most ' + MAX_WINDOW_SIZE + ', window permutation tries windowSize! orderings per window');
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Improves an ordering by sifting (moving each variable through all positions
136
+ * and keeping the best one) and exhaustive window permutation until no
137
+ * strict improvement is found anymore. "Better" is decided by
138
+ * OrderingEvaluator.isBetter(): fewer nodes, or with a cost model a lower
139
+ * expected cost. Everything is deterministic: the same input always gives
140
+ * the same output.
141
+ */
142
+ export class LocalOrderingSearch {
143
+ public best: OrderingEvaluation;
144
+
145
+ private readonly cursor: OrderingCursor;
146
+ private readonly n: number;
147
+ private readonly windowPermutations: number[][];
148
+ private readonly windowSize: number;
149
+ private readonly maxRounds: number;
150
+ private readonly onImprovement: OrderingProgressCallback;
151
+ private readonly log: (message: string) => void;
152
+
153
+ constructor(
154
+ private readonly evaluator: OrderingEvaluator,
155
+ initialOrdering: ArrayLike<number>,
156
+ options: LocalSearchOptions = {}
157
+ ) {
158
+ this.n = evaluator.variableCount;
159
+ ensureValidOrdering(initialOrdering, this.n);
160
+ const windowSize = options.windowSize === undefined ? DEFAULT_WINDOW_SIZE : options.windowSize;
161
+ ensureWindowSize(windowSize);
162
+ const maxRounds = options.maxRounds === undefined ? DEFAULT_MAX_ROUNDS : options.maxRounds;
163
+ ensureNonNegativeInteger(maxRounds, 'maxRounds');
164
+ this.cursor = evaluator.cursor(initialOrdering);
165
+ this.best = this.cursor.evaluation;
166
+ this.windowSize = Math.min(windowSize, this.n);
167
+ // the identity permutation is the current state, no need to evaluate it again
168
+ this.windowPermutations = this.windowSize >= 2 ?
169
+ allPermutations(this.windowSize).filter(permutation => permutation.some((v, i) => v !== i)) :
170
+ [];
171
+ this.maxRounds = maxRounds;
172
+ this.onImprovement = options.onImprovement ? options.onImprovement : () => null;
173
+ this.log = options.log ? options.log : () => null;
174
+ }
175
+
176
+ /**
177
+ * ordering[level] = boolean function tested on that level
178
+ */
179
+ get ordering(): number[] {
180
+ return this.cursor.ordering;
181
+ }
182
+
183
+ public result(): OrderingSearchResult {
184
+ return {
185
+ ordering: this.ordering.slice(),
186
+ nodeCount: this.best.nodeCount,
187
+ expectedCost: this.best.expectedCost,
188
+ nodesPerLevel: this.best.nodesPerLevel.slice(),
189
+ evaluation: this.best
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Runs sifting rounds and window permutation rounds
195
+ * until a full cycle gives no improvement.
196
+ */
197
+ public run(): OrderingSearchResult {
198
+ let round = 0;
199
+ while (round < this.maxRounds) {
200
+ round++;
201
+ const before = this.best;
202
+ this.siftRound();
203
+ if (this.windowSize >= 2) {
204
+ this.windowRound();
205
+ }
206
+ this.log('local search round ' + round + ': ' + this.describe(before) + ' -> ' + this.describe(this.best));
207
+ if (!this.evaluator.isBetter(this.best, before)) {
208
+ break;
209
+ }
210
+ }
211
+ return this.result();
212
+ }
213
+
214
+ /**
215
+ * One sifting round: each variable, largest level first,
216
+ * is moved to the position where the BDD is best.
217
+ * @returns true if the ordering improved
218
+ */
219
+ public siftRound(): boolean {
220
+ const startEvaluation = this.best;
221
+ const n = this.n;
222
+ const positionOf = (v: number) => this.ordering.indexOf(v);
223
+ const variables = this.ordering.slice().sort((a, b) => {
224
+ const diff = this.best.nodesPerLevel[positionOf(b)] - this.best.nodesPerLevel[positionOf(a)];
225
+ return diff !== 0 ? diff : a - b;
226
+ });
227
+
228
+ for (const variable of variables) {
229
+ const start = positionOf(variable);
230
+ let bestEvaluation = this.best;
231
+ let bestPosition = start;
232
+ let position = start;
233
+
234
+ const moveTo = (target: number) => {
235
+ while (position < target) {
236
+ this.swapAdjacent(position);
237
+ position++;
238
+ }
239
+ while (position > target) {
240
+ this.swapAdjacent(position - 1);
241
+ position--;
242
+ }
243
+ };
244
+ const sweep = (target: number) => {
245
+ while (position !== target) {
246
+ const step = position < target ? 1 : -1;
247
+ this.swapAdjacent(step === 1 ? position : position - 1);
248
+ position += step;
249
+ const evaluation = this.cursor.evaluation;
250
+ if (this.evaluator.isBetter(evaluation, bestEvaluation)) {
251
+ bestEvaluation = evaluation;
252
+ bestPosition = position;
253
+ }
254
+ }
255
+ };
256
+
257
+ // go to the closer end first, then to the other end
258
+ if (start < n - 1 - start) {
259
+ sweep(0);
260
+ sweep(n - 1);
261
+ } else {
262
+ sweep(n - 1);
263
+ sweep(0);
264
+ }
265
+ moveTo(bestPosition);
266
+ if (this.evaluator.isBetter(bestEvaluation, this.best)) {
267
+ this.best = bestEvaluation;
268
+ this.log('sifting: ' + this.describe(bestEvaluation));
269
+ this.onImprovement(this.progress('sifting'));
270
+ }
271
+ }
272
+ return this.evaluator.isBetter(this.best, startEvaluation);
273
+ }
274
+
275
+ /**
276
+ * One window permutation round: for each window of adjacent levels
277
+ * all permutations of its variables are tried.
278
+ * @returns true if the ordering improved
279
+ */
280
+ public windowRound(): boolean {
281
+ const startEvaluation = this.best;
282
+ const w = this.windowSize;
283
+ if (w < 2) {
284
+ return false;
285
+ }
286
+ for (let windowStart = 0; windowStart + w <= this.n; windowStart++) {
287
+ const original = this.ordering.slice(windowStart, windowStart + w);
288
+ let bestEvaluation = this.best;
289
+ let bestPermutation: number[] | null = null;
290
+ for (const permutation of this.windowPermutations) {
291
+ this.setWindow(windowStart, original, permutation);
292
+ const evaluation = this.cursor.evaluation;
293
+ if (this.evaluator.isBetter(evaluation, bestEvaluation)) {
294
+ bestEvaluation = evaluation;
295
+ bestPermutation = permutation;
296
+ }
297
+ }
298
+ if (bestPermutation) {
299
+ this.setWindow(windowStart, original, bestPermutation);
300
+ this.best = bestEvaluation;
301
+ this.log('window permutation: ' + this.describe(bestEvaluation));
302
+ this.onImprovement(this.progress('window'));
303
+ } else {
304
+ this.setWindow(windowStart, original, naturalOrdering(w));
305
+ }
306
+ }
307
+ return this.evaluator.isBetter(this.best, startEvaluation);
308
+ }
309
+
310
+ private progress(phase: string): OrderingProgress {
311
+ return {
312
+ ordering: this.ordering.slice(),
313
+ nodeCount: this.best.nodeCount,
314
+ expectedCost: this.best.expectedCost,
315
+ phase
316
+ };
317
+ }
318
+
319
+ private describe(evaluation: OrderingEvaluation): string {
320
+ return evaluation.nodeCount + ' nodes' +
321
+ (this.evaluator.costModel ? ', expected cost ' + evaluation.expectedCost : '');
322
+ }
323
+
324
+ /**
325
+ * swaps the variables of level and level+1
326
+ */
327
+ private swapAdjacent(level: number) {
328
+ this.swapLevels(level, level + 1);
329
+ }
330
+
331
+ private swapLevels(levelA: number, levelB: number) {
332
+ this.cursor.swap(levelA, levelB);
333
+ }
334
+
335
+ /**
336
+ * Arranges the window so that level (windowStart + i) holds original[permutation[i]].
337
+ */
338
+ private setWindow(windowStart: number, original: number[], permutation: number[]) {
339
+ for (let i = 0; i < permutation.length; i++) {
340
+ const wanted = original[permutation[i]];
341
+ const currentLevel = this.ordering.indexOf(wanted);
342
+ this.swapLevels(windowStart + i, currentLevel);
343
+ }
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Deterministic start orderings for the local search.
349
+ */
350
+ export function seedOrderings(evaluator: OrderingEvaluator): { name: string, ordering: number[] }[] {
351
+ const n = evaluator.variableCount;
352
+ const natural = naturalOrdering(n);
353
+ const seeds = [
354
+ { name: 'natural', ordering: natural },
355
+ { name: 'reversed', ordering: natural.slice().reverse() },
356
+ { name: 'greedy-top-down', ordering: greedyTopDownOrdering(evaluator) },
357
+ { name: 'greedy-bottom-up', ordering: greedyBottomUpOrdering(evaluator) }
358
+ ];
359
+ // remove duplicates, keep the first occurrence
360
+ const seen: Set<string> = new Set();
361
+ return seeds.filter(seed => {
362
+ const key = seed.ordering.join(',');
363
+ if (seen.has(key)) {
364
+ return false;
365
+ }
366
+ seen.add(key);
367
+ return true;
368
+ });
369
+ }