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.
- 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 +1 -0
- package/dist/cjs/src/branches.js +2 -1
- package/dist/cjs/src/branches.js.map +1 -1
- package/dist/cjs/src/create-bdd-from-truth-table.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 +5 -7
- 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 +1 -5
- 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 +14 -5
- 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 +1 -0
- package/dist/esm/src/branches.js +1 -0
- 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 +1 -5
- 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 +11 -9
- package/src/abstract-node.ts +7 -5
- package/src/branches.ts +2 -0
- 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 +2 -8
- 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 -59
- 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 -23
- 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 -57
- 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 -18
- 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 -15
- 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 -23
- 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
|
+
}
|