binary-decision-diagram 3.2.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +211 -31
- package/dist/cjs/src/abstract-node.js +7 -4
- package/dist/cjs/src/abstract-node.js.map +1 -1
- package/dist/cjs/src/branches.d.ts +4 -1
- package/dist/cjs/src/branches.js +10 -9
- package/dist/cjs/src/branches.js.map +1 -1
- package/dist/cjs/src/create-bdd-from-truth-table.js +1 -2
- package/dist/cjs/src/create-bdd-from-truth-table.js.map +1 -1
- package/dist/cjs/src/ensure-correct-bdd.js +2 -3
- package/dist/cjs/src/ensure-correct-bdd.js.map +1 -1
- package/dist/cjs/src/fill-truth-table.js +1 -2
- package/dist/cjs/src/fill-truth-table.js.map +1 -1
- package/dist/cjs/src/find-similar-node.js +1 -2
- package/dist/cjs/src/find-similar-node.js.map +1 -1
- package/dist/cjs/src/index.d.ts +2 -1
- package/dist/cjs/src/index.es5.js +17 -7
- package/dist/cjs/src/index.es5.js.map +1 -1
- package/dist/cjs/src/index.js +2 -1
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/internal-node.js +6 -3
- package/dist/cjs/src/internal-node.js.map +1 -1
- package/dist/cjs/src/optimize-brute-force.js +10 -12
- package/dist/cjs/src/optimize-brute-force.js.map +1 -1
- package/dist/cjs/src/ordering/build-bdd.d.ts +13 -0
- package/dist/cjs/src/ordering/build-bdd.js +64 -0
- package/dist/cjs/src/ordering/build-bdd.js.map +1 -0
- package/dist/cjs/src/ordering/dense-truth-table.d.ts +42 -0
- package/dist/cjs/src/ordering/dense-truth-table.js +125 -0
- package/dist/cjs/src/ordering/dense-truth-table.js.map +1 -0
- package/dist/cjs/src/ordering/exact-cost-ordering.d.ts +91 -0
- package/dist/cjs/src/ordering/exact-cost-ordering.js +351 -0
- package/dist/cjs/src/ordering/exact-cost-ordering.js.map +1 -0
- package/dist/cjs/src/ordering/exact-ordering.d.ts +89 -0
- package/dist/cjs/src/ordering/exact-ordering.js +260 -0
- package/dist/cjs/src/ordering/exact-ordering.js.map +1 -0
- package/dist/cjs/src/ordering/index.d.ts +22 -0
- package/dist/cjs/src/ordering/index.js +38 -0
- package/dist/cjs/src/ordering/index.js.map +1 -0
- package/dist/cjs/src/ordering/int-pair-map.d.ts +20 -0
- package/dist/cjs/src/ordering/int-pair-map.js +85 -0
- package/dist/cjs/src/ordering/int-pair-map.js.map +1 -0
- package/dist/cjs/src/ordering/local-search.d.ts +114 -0
- package/dist/cjs/src/ordering/local-search.js +317 -0
- package/dist/cjs/src/ordering/local-search.js.map +1 -0
- package/dist/cjs/src/ordering/optimize-ordering.d.ts +153 -0
- package/dist/cjs/src/ordering/optimize-ordering.js +406 -0
- package/dist/cjs/src/ordering/optimize-ordering.js.map +1 -0
- package/dist/cjs/src/ordering/ordering-evaluator.d.ts +198 -0
- package/dist/cjs/src/ordering/ordering-evaluator.js +384 -0
- package/dist/cjs/src/ordering/ordering-evaluator.js.map +1 -0
- package/dist/cjs/src/ordering/shared-diagram.d.ts +85 -0
- package/dist/cjs/src/ordering/shared-diagram.js +295 -0
- package/dist/cjs/src/ordering/shared-diagram.js.map +1 -0
- package/dist/cjs/src/packed/index.d.ts +2 -0
- package/dist/cjs/src/{minimal-string → packed}/index.js +2 -5
- package/dist/cjs/src/packed/index.js.map +1 -0
- package/dist/cjs/src/packed/packed-bdd-base64-string.d.ts +3 -0
- package/dist/cjs/src/packed/packed-bdd-base64-string.js +46 -0
- package/dist/cjs/src/packed/packed-bdd-base64-string.js.map +1 -0
- package/dist/cjs/src/packed/packed-bdd.d.ts +13 -0
- package/dist/cjs/src/packed/packed-bdd.js +70 -0
- package/dist/cjs/src/packed/packed-bdd.js.map +1 -0
- package/dist/cjs/src/root-node.d.ts +1 -2
- package/dist/cjs/src/root-node.js +4 -9
- package/dist/cjs/src/root-node.js.map +1 -1
- package/dist/cjs/src/types.d.ts +0 -12
- package/dist/cjs/src/util.js +29 -21
- package/dist/cjs/src/util.js.map +1 -1
- package/dist/esm/src/abstract-node.js +7 -4
- package/dist/esm/src/abstract-node.js.map +1 -1
- package/dist/esm/src/branches.d.ts +4 -1
- package/dist/esm/src/branches.js +8 -7
- package/dist/esm/src/branches.js.map +1 -1
- package/dist/esm/src/create-bdd-from-truth-table.js.map +1 -1
- package/dist/esm/src/index.d.ts +2 -1
- package/dist/esm/src/index.js +2 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/internal-node.js +7 -4
- package/dist/esm/src/internal-node.js.map +1 -1
- package/dist/esm/src/optimize-brute-force.js +5 -7
- package/dist/esm/src/optimize-brute-force.js.map +1 -1
- package/dist/esm/src/ordering/build-bdd.d.ts +13 -0
- package/dist/esm/src/ordering/build-bdd.js +61 -0
- package/dist/esm/src/ordering/build-bdd.js.map +1 -0
- package/dist/esm/src/ordering/dense-truth-table.d.ts +42 -0
- package/dist/esm/src/ordering/dense-truth-table.js +117 -0
- package/dist/esm/src/ordering/dense-truth-table.js.map +1 -0
- package/dist/esm/src/ordering/exact-cost-ordering.d.ts +91 -0
- package/dist/esm/src/ordering/exact-cost-ordering.js +347 -0
- package/dist/esm/src/ordering/exact-cost-ordering.js.map +1 -0
- package/dist/esm/src/ordering/exact-ordering.d.ts +89 -0
- package/dist/esm/src/ordering/exact-ordering.js +256 -0
- package/dist/esm/src/ordering/exact-ordering.js.map +1 -0
- package/dist/esm/src/ordering/index.d.ts +22 -0
- package/dist/esm/src/ordering/index.js +17 -0
- package/dist/esm/src/ordering/index.js.map +1 -0
- package/dist/esm/src/ordering/int-pair-map.d.ts +20 -0
- package/dist/esm/src/ordering/int-pair-map.js +81 -0
- package/dist/esm/src/ordering/int-pair-map.js.map +1 -0
- package/dist/esm/src/ordering/local-search.d.ts +114 -0
- package/dist/esm/src/ordering/local-search.js +308 -0
- package/dist/esm/src/ordering/local-search.js.map +1 -0
- package/dist/esm/src/ordering/optimize-ordering.d.ts +153 -0
- package/dist/esm/src/ordering/optimize-ordering.js +399 -0
- package/dist/esm/src/ordering/optimize-ordering.js.map +1 -0
- package/dist/esm/src/ordering/ordering-evaluator.d.ts +198 -0
- package/dist/esm/src/ordering/ordering-evaluator.js +377 -0
- package/dist/esm/src/ordering/ordering-evaluator.js.map +1 -0
- package/dist/esm/src/ordering/shared-diagram.d.ts +85 -0
- package/dist/esm/src/ordering/shared-diagram.js +289 -0
- package/dist/esm/src/ordering/shared-diagram.js.map +1 -0
- package/dist/esm/src/packed/index.d.ts +2 -0
- package/dist/esm/src/packed/index.js +3 -0
- package/dist/esm/src/packed/index.js.map +1 -0
- package/dist/esm/src/packed/packed-bdd-base64-string.d.ts +3 -0
- package/dist/esm/src/packed/packed-bdd-base64-string.js +42 -0
- package/dist/esm/src/packed/packed-bdd-base64-string.js.map +1 -0
- package/dist/esm/src/packed/packed-bdd.d.ts +13 -0
- package/dist/esm/src/packed/packed-bdd.js +66 -0
- package/dist/esm/src/packed/packed-bdd.js.map +1 -0
- package/dist/esm/src/root-node.d.ts +1 -2
- package/dist/esm/src/root-node.js +5 -10
- package/dist/esm/src/root-node.js.map +1 -1
- package/dist/esm/src/types.d.ts +0 -12
- package/dist/esm/src/util.js +14 -5
- package/dist/esm/src/util.js.map +1 -1
- package/docs/variable-ordering.md +306 -0
- package/package.json +15 -12
- package/src/abstract-node.ts +7 -5
- package/src/branches.ts +9 -7
- package/src/create-bdd-from-truth-table.ts +1 -1
- package/src/index.ts +2 -1
- package/src/internal-node.ts +7 -4
- package/src/optimize-brute-force.ts +5 -7
- package/src/ordering/build-bdd.ts +70 -0
- package/src/ordering/dense-truth-table.ts +161 -0
- package/src/ordering/exact-cost-ordering.ts +425 -0
- package/src/ordering/exact-ordering.ts +317 -0
- package/src/ordering/index.ts +57 -0
- package/src/ordering/int-pair-map.ts +85 -0
- package/src/ordering/local-search.ts +369 -0
- package/src/ordering/optimize-ordering.ts +570 -0
- package/src/ordering/ordering-evaluator.ts +479 -0
- package/src/ordering/shared-diagram.ts +310 -0
- package/src/packed/index.ts +2 -0
- package/src/packed/packed-bdd-base64-string.ts +47 -0
- package/src/packed/packed-bdd.ts +98 -0
- package/src/root-node.ts +8 -17
- package/src/types.ts +0 -18
- package/src/util.ts +15 -6
- package/dist/cjs/src/minimal-string/bdd-to-minimal-string.d.ts +0 -8
- package/dist/cjs/src/minimal-string/bdd-to-minimal-string.js +0 -60
- package/dist/cjs/src/minimal-string/bdd-to-minimal-string.js.map +0 -1
- package/dist/cjs/src/minimal-string/bdd-to-simple-bdd.d.ts +0 -11
- package/dist/cjs/src/minimal-string/bdd-to-simple-bdd.js +0 -24
- package/dist/cjs/src/minimal-string/bdd-to-simple-bdd.js.map +0 -1
- package/dist/cjs/src/minimal-string/index.d.ts +0 -5
- package/dist/cjs/src/minimal-string/index.js.map +0 -1
- package/dist/cjs/src/minimal-string/minimal-string-to-simple-bdd.d.ts +0 -2
- package/dist/cjs/src/minimal-string/minimal-string-to-simple-bdd.js +0 -58
- package/dist/cjs/src/minimal-string/minimal-string-to-simple-bdd.js.map +0 -1
- package/dist/cjs/src/minimal-string/resolve-with-simple-bdd.d.ts +0 -2
- package/dist/cjs/src/minimal-string/resolve-with-simple-bdd.js +0 -21
- package/dist/cjs/src/minimal-string/resolve-with-simple-bdd.js.map +0 -1
- package/dist/cjs/src/minimal-string/string-format.d.ts +0 -9
- package/dist/cjs/src/minimal-string/string-format.js +0 -61
- package/dist/cjs/src/minimal-string/string-format.js.map +0 -1
- package/dist/esm/src/minimal-string/bdd-to-minimal-string.d.ts +0 -8
- package/dist/esm/src/minimal-string/bdd-to-minimal-string.js +0 -55
- package/dist/esm/src/minimal-string/bdd-to-minimal-string.js.map +0 -1
- package/dist/esm/src/minimal-string/bdd-to-simple-bdd.d.ts +0 -11
- package/dist/esm/src/minimal-string/bdd-to-simple-bdd.js +0 -19
- package/dist/esm/src/minimal-string/bdd-to-simple-bdd.js.map +0 -1
- package/dist/esm/src/minimal-string/index.d.ts +0 -5
- package/dist/esm/src/minimal-string/index.js +0 -6
- package/dist/esm/src/minimal-string/index.js.map +0 -1
- package/dist/esm/src/minimal-string/minimal-string-to-simple-bdd.d.ts +0 -2
- package/dist/esm/src/minimal-string/minimal-string-to-simple-bdd.js +0 -54
- package/dist/esm/src/minimal-string/minimal-string-to-simple-bdd.js.map +0 -1
- package/dist/esm/src/minimal-string/resolve-with-simple-bdd.d.ts +0 -2
- package/dist/esm/src/minimal-string/resolve-with-simple-bdd.js +0 -17
- package/dist/esm/src/minimal-string/resolve-with-simple-bdd.js.map +0 -1
- package/dist/esm/src/minimal-string/string-format.d.ts +0 -9
- package/dist/esm/src/minimal-string/string-format.js +0 -54
- package/dist/esm/src/minimal-string/string-format.js.map +0 -1
- package/src/minimal-string/bdd-to-minimal-string.ts +0 -72
- package/src/minimal-string/bdd-to-simple-bdd.ts +0 -31
- package/src/minimal-string/index.ts +0 -5
- package/src/minimal-string/minimal-string-to-simple-bdd.ts +0 -67
- package/src/minimal-string/resolve-with-simple-bdd.ts +0 -25
- package/src/minimal-string/string-format.ts +0 -67
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import type { TruthTable } from '../types.js';
|
|
2
|
+
import { RootNode } from '../root-node.js';
|
|
3
|
+
import {
|
|
4
|
+
parseTruthTable,
|
|
5
|
+
denseTruthTableToMap,
|
|
6
|
+
ensureValidOrdering,
|
|
7
|
+
DenseTruthTable
|
|
8
|
+
} from './dense-truth-table.js';
|
|
9
|
+
import { OrderingEvaluator, OrderingEvaluation, CostModel } from './ordering-evaluator.js';
|
|
10
|
+
import {
|
|
11
|
+
LocalOrderingSearch,
|
|
12
|
+
seedOrderings,
|
|
13
|
+
OrderingProgressCallback,
|
|
14
|
+
DEFAULT_WINDOW_SIZE,
|
|
15
|
+
ensureWindowSize,
|
|
16
|
+
ensureNonNegativeInteger
|
|
17
|
+
} from './local-search.js';
|
|
18
|
+
import { ExactOrderingSearch } from './exact-ordering.js';
|
|
19
|
+
import { ExactCostOrderingSearch } from './exact-cost-ordering.js';
|
|
20
|
+
import { buildBddFromDiagram } from './build-bdd.js';
|
|
21
|
+
import type { AbstractNode } from '../abstract-node.js';
|
|
22
|
+
import type { InternalNode } from '../internal-node.js';
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_MAX_EXACT_VARIABLES = 20;
|
|
25
|
+
export const DEFAULT_MAX_EXACT_NODE_COUNT = 5000;
|
|
26
|
+
|
|
27
|
+
export interface FindOrderingInput {
|
|
28
|
+
truthTable: TruthTable;
|
|
29
|
+
/**
|
|
30
|
+
* Value of the rows that can never occur at runtime.
|
|
31
|
+
* These rows are resolved exactly like removeIrrelevantLeafNodes() does it:
|
|
32
|
+
* a branch that only leads to don't-care rows is replaced by its sibling.
|
|
33
|
+
* Which ordering profits most from that is part of the search, but the
|
|
34
|
+
* rule itself is a heuristic, not the smallest possible completion.
|
|
35
|
+
*/
|
|
36
|
+
dontCareValue?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Cost of calling each boolean function: functionCosts[i] belongs to the
|
|
39
|
+
* boolean function with index i (the i-th character of the truth table keys).
|
|
40
|
+
* When given, the optimizer minimizes the expected cost of one resolve() call,
|
|
41
|
+
* that is the sum of the costs of the boolean functions that are called on the
|
|
42
|
+
* way from the root to the leaf, averaged over the rows, instead of the amount
|
|
43
|
+
* of nodes. Among orderings with the same expected cost, fewer nodes are
|
|
44
|
+
* preferred (best effort, not part of the optimality proof).
|
|
45
|
+
*/
|
|
46
|
+
functionCosts?: number[];
|
|
47
|
+
/**
|
|
48
|
+
* How often each row occurs at runtime relative to the other rows,
|
|
49
|
+
* used together with functionCosts. Default: every row that is not a
|
|
50
|
+
* don't-care row occurs equally often. Don't-care rows never occur.
|
|
51
|
+
*/
|
|
52
|
+
rowWeight?: (key: string, value: number) => number;
|
|
53
|
+
/**
|
|
54
|
+
* Additional start orderings for the local search, for example the
|
|
55
|
+
* best ordering of a previous run. ordering[level] is the index
|
|
56
|
+
* of the boolean function tested at that level.
|
|
57
|
+
*/
|
|
58
|
+
initialOrderings?: number[][];
|
|
59
|
+
/**
|
|
60
|
+
* - 'auto' (default): run the exact search when the truth table has at
|
|
61
|
+
* most maxExactVariables boolean functions and the BDD found by the local
|
|
62
|
+
* search has at most maxExactNodeCount internal nodes (unstructured
|
|
63
|
+
* functions have big BDDs, and on them the exact search is slow and
|
|
64
|
+
* cannot gain much)
|
|
65
|
+
* - true: always run the exact search (within the budgets below)
|
|
66
|
+
* - false: never
|
|
67
|
+
*/
|
|
68
|
+
exact?: boolean | 'auto';
|
|
69
|
+
/** Default: 20 */
|
|
70
|
+
maxExactVariables?: number;
|
|
71
|
+
/** Default: 5000 */
|
|
72
|
+
maxExactNodeCount?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Budget of the exact search per candidate completion of the truth table.
|
|
75
|
+
* A candidate whose search exceeds it cannot be proven optimal.
|
|
76
|
+
* Default: 200000
|
|
77
|
+
*/
|
|
78
|
+
maxExactExpansions?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Memory budget of the exact search: the maximum amount of nodes of its
|
|
81
|
+
* internal diagram (roughly 100 bytes each).
|
|
82
|
+
* Default: 4000000
|
|
83
|
+
*/
|
|
84
|
+
maxExactDiagramNodes?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Size of the windows the local search permutes exhaustively,
|
|
87
|
+
* at most 8 (the work grows with windowSize!).
|
|
88
|
+
* Default: 3
|
|
89
|
+
*/
|
|
90
|
+
windowSize?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Maximum amount of sifting + window permutation rounds per start ordering.
|
|
93
|
+
* Default: 50
|
|
94
|
+
*/
|
|
95
|
+
maxRounds?: number;
|
|
96
|
+
log?: boolean;
|
|
97
|
+
/** called whenever a better ordering was found */
|
|
98
|
+
onImprovement?: OrderingProgressCallback;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface FindOrderingResult {
|
|
102
|
+
/** ordering[level] = index of the boolean function tested at that level */
|
|
103
|
+
ordering: number[];
|
|
104
|
+
/** amount of nodes of the BDD, as RootNode.countNodes() returns it */
|
|
105
|
+
nodeCount: number;
|
|
106
|
+
/**
|
|
107
|
+
* Expected cost of one resolve() call with the returned ordering,
|
|
108
|
+
* only set when functionCosts were given.
|
|
109
|
+
*/
|
|
110
|
+
expectedCost?: number;
|
|
111
|
+
/**
|
|
112
|
+
* true when the exact search has proven that no other ordering gives a
|
|
113
|
+
* BDD with fewer nodes (or, with functionCosts, a lower expected cost)
|
|
114
|
+
* for the truth table in `truthTable`. With don't-care rows that is one
|
|
115
|
+
* completion of the input: another ordering can occasionally reach a smaller
|
|
116
|
+
* BDD by resolving the don't-care rows differently.
|
|
117
|
+
*/
|
|
118
|
+
optimal: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* The truth table the BDD represents: the input table with the don't-care
|
|
121
|
+
* rows resolved, with the same keys as the input (iterated in row order).
|
|
122
|
+
* createBddWithOrdering(result.truthTable, result.ordering) rebuilds the BDD.
|
|
123
|
+
* When don't-care rows exist, this completion can differ from the one that
|
|
124
|
+
* createBddWithOrdering(input, result.ordering, dontCareValue) would pick.
|
|
125
|
+
*/
|
|
126
|
+
truthTable: TruthTable;
|
|
127
|
+
stats: OptimizeOrderingStats;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface OptimizeOrderingStats {
|
|
131
|
+
/** amount of orderings that were evaluated */
|
|
132
|
+
evaluations: number;
|
|
133
|
+
/** amount of states the exact search expanded */
|
|
134
|
+
exactExpansions: number;
|
|
135
|
+
/** the amount of nodes found by the local search alone */
|
|
136
|
+
localSearchNodeCount: number;
|
|
137
|
+
durationMs: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface BestOrdering {
|
|
141
|
+
ordering: number[];
|
|
142
|
+
evaluation: OrderingEvaluation;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface OptimizeOrderingInput extends FindOrderingInput {
|
|
146
|
+
/**
|
|
147
|
+
* Hook that runs on the created BDD before it is minimized,
|
|
148
|
+
* for compatibility with optimizeBruteForce().
|
|
149
|
+
* The optimizer cannot account for what the hook does, so prefer dontCareValue
|
|
150
|
+
* over removeIrrelevantLeafNodes() in the hook. When the hook changes the BDD,
|
|
151
|
+
* `optimal` is false, and `truthTable` and `expectedCost` describe the BDD
|
|
152
|
+
* before the hook.
|
|
153
|
+
*/
|
|
154
|
+
afterBddCreation?: (bdd: RootNode) => void;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface OptimizeOrderingResult extends FindOrderingResult {
|
|
158
|
+
bdd: RootNode;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Validates functionCosts and rowWeight and turns them into a cost model.
|
|
163
|
+
*/
|
|
164
|
+
export function buildCostModel(
|
|
165
|
+
table: DenseTruthTable,
|
|
166
|
+
functionCosts?: number[],
|
|
167
|
+
rowWeight?: (key: string, value: number) => number
|
|
168
|
+
): CostModel | null {
|
|
169
|
+
if (!functionCosts) {
|
|
170
|
+
if (rowWeight) {
|
|
171
|
+
throw new Error('rowWeight can only be used together with functionCosts');
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const n = table.variableCount;
|
|
176
|
+
if (functionCosts.length !== n) {
|
|
177
|
+
throw new Error('functionCosts must have one entry per boolean function (' + n + ')');
|
|
178
|
+
}
|
|
179
|
+
const costs = new Float64Array(n);
|
|
180
|
+
for (let v = 0; v < n; v++) {
|
|
181
|
+
const cost = functionCosts[v];
|
|
182
|
+
if (typeof cost !== 'number' || !isFinite(cost) || cost < 0) {
|
|
183
|
+
throw new Error('functionCosts[' + v + '] must be a non-negative number');
|
|
184
|
+
}
|
|
185
|
+
costs[v] = cost;
|
|
186
|
+
}
|
|
187
|
+
const weights = new Float64Array(table.rowCount);
|
|
188
|
+
let totalWeight = 0;
|
|
189
|
+
for (let row = 0; row < table.rowCount; row++) {
|
|
190
|
+
const id = table.terminalIds[row];
|
|
191
|
+
if (id === table.dontCareTerminal) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
let weight = 1;
|
|
195
|
+
if (rowWeight) {
|
|
196
|
+
const key = row.toString(2).padStart(n, '0');
|
|
197
|
+
weight = rowWeight(key, table.terminalValues[id]);
|
|
198
|
+
if (typeof weight !== 'number' || !isFinite(weight) || weight < 0) {
|
|
199
|
+
throw new Error('rowWeight must return a non-negative number, got ' + weight + ' for ' + key);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
weights[row] = weight;
|
|
203
|
+
totalWeight += weight;
|
|
204
|
+
}
|
|
205
|
+
if (totalWeight <= 0) {
|
|
206
|
+
throw new Error('all row weights are zero');
|
|
207
|
+
}
|
|
208
|
+
let maxCost = 0;
|
|
209
|
+
costs.forEach(cost => maxCost = Math.max(maxCost, cost));
|
|
210
|
+
if (!isFinite(totalWeight) || !isFinite(maxCost * totalWeight)) {
|
|
211
|
+
throw new Error('the function costs or row weights are too large, scale them down');
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
functionCosts: costs,
|
|
215
|
+
rowWeights: weights,
|
|
216
|
+
totalWeight
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Deterministically finds a variable ordering with a small BDD
|
|
222
|
+
* (or, with functionCosts, with a cheap resolve() call).
|
|
223
|
+
* See docs/variable-ordering.md for how it works.
|
|
224
|
+
* Does not create BDD nodes, use optimizeOrdering() for that.
|
|
225
|
+
*/
|
|
226
|
+
export function findOrdering(input: FindOrderingInput): FindOrderingResult {
|
|
227
|
+
return findOrderingWithTable(input).result;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Checks the options before any expensive work starts.
|
|
232
|
+
*/
|
|
233
|
+
function ensureValidOptions(input: FindOrderingInput) {
|
|
234
|
+
if (input.windowSize !== undefined) {
|
|
235
|
+
ensureWindowSize(input.windowSize);
|
|
236
|
+
}
|
|
237
|
+
if (input.maxRounds !== undefined) {
|
|
238
|
+
ensureNonNegativeInteger(input.maxRounds, 'maxRounds');
|
|
239
|
+
}
|
|
240
|
+
const budgets: (keyof FindOrderingInput)[] = ['maxExactVariables', 'maxExactNodeCount', 'maxExactExpansions', 'maxExactDiagramNodes'];
|
|
241
|
+
budgets.forEach(name => {
|
|
242
|
+
const value = input[name];
|
|
243
|
+
if (value !== undefined) {
|
|
244
|
+
ensureNonNegativeInteger(value as number, name);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
if (input.exact !== undefined && input.exact !== true && input.exact !== false && input.exact !== 'auto') {
|
|
248
|
+
throw new Error('exact must be true, false or \'auto\'');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function findOrderingWithTable(input: FindOrderingInput): { result: FindOrderingResult, table: DenseTruthTable } {
|
|
253
|
+
const startTime = Date.now();
|
|
254
|
+
ensureValidOptions(input);
|
|
255
|
+
const log = input.log ? (message: string) => console.log('findOrdering(): ' + message) : () => null;
|
|
256
|
+
const onImprovement = input.onImprovement ? input.onImprovement : () => null;
|
|
257
|
+
const base = parseTruthTable(input.truthTable, input.dontCareValue);
|
|
258
|
+
const n = base.variableCount;
|
|
259
|
+
const costModel = buildCostModel(base, input.functionCosts, input.rowWeight);
|
|
260
|
+
const baseEvaluator = new OrderingEvaluator(base, costModel);
|
|
261
|
+
const windowSize = input.windowSize === undefined ? DEFAULT_WINDOW_SIZE : input.windowSize;
|
|
262
|
+
let evaluations = 0;
|
|
263
|
+
const describe = (evaluation: OrderingEvaluation) => evaluation.nodeCount + ' nodes' +
|
|
264
|
+
(costModel ? ', expected cost ' + evaluation.expectedCost : '');
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* phase 1: deterministic seeds, each improved by sifting and window permutation
|
|
268
|
+
*/
|
|
269
|
+
const userSeeds = (input.initialOrderings ? input.initialOrderings : []).map((ordering, index) => {
|
|
270
|
+
ensureValidOrdering(ordering, n);
|
|
271
|
+
return { name: 'initial-' + index, ordering: Array.from(ordering) };
|
|
272
|
+
});
|
|
273
|
+
const seenSeeds: Set<string> = new Set();
|
|
274
|
+
const seeds = userSeeds.concat(seedOrderings(baseEvaluator)).filter(seed => {
|
|
275
|
+
const key = seed.ordering.join(',');
|
|
276
|
+
if (seenSeeds.has(key)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
seenSeeds.add(key);
|
|
280
|
+
return true;
|
|
281
|
+
});
|
|
282
|
+
let localBest: BestOrdering | null = null;
|
|
283
|
+
const seedResults: BestOrdering[] = [];
|
|
284
|
+
// the last evaluation passed to onImprovement
|
|
285
|
+
const reported: { evaluation: OrderingEvaluation | null } = { evaluation: null };
|
|
286
|
+
for (const seed of seeds) {
|
|
287
|
+
const search = new LocalOrderingSearch(baseEvaluator, seed.ordering, {
|
|
288
|
+
windowSize,
|
|
289
|
+
maxRounds: input.maxRounds,
|
|
290
|
+
log: message => log('[' + seed.name + '] ' + message),
|
|
291
|
+
onImprovement: progress => {
|
|
292
|
+
if (!localBest || baseEvaluator.isBetter(search.best, localBest.evaluation)) {
|
|
293
|
+
reported.evaluation = search.best;
|
|
294
|
+
onImprovement(progress);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
const result = search.run();
|
|
299
|
+
log('seed ' + seed.name + ': ' + describe(result.evaluation));
|
|
300
|
+
seedResults.push({ ordering: result.ordering, evaluation: result.evaluation });
|
|
301
|
+
if (!localBest || baseEvaluator.isBetter(result.evaluation, localBest.evaluation)) {
|
|
302
|
+
localBest = { ordering: result.ordering, evaluation: result.evaluation };
|
|
303
|
+
if (!reported.evaluation || baseEvaluator.isBetter(result.evaluation, reported.evaluation)) {
|
|
304
|
+
// the seed itself was better than everything before, the search did not report it
|
|
305
|
+
reported.evaluation = result.evaluation;
|
|
306
|
+
onImprovement({
|
|
307
|
+
ordering: result.ordering.slice(),
|
|
308
|
+
nodeCount: result.evaluation.nodeCount,
|
|
309
|
+
expectedCost: result.evaluation.expectedCost,
|
|
310
|
+
phase: 'seed'
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (!localBest) {
|
|
316
|
+
throw new Error('no ordering found, this should not happen');
|
|
317
|
+
}
|
|
318
|
+
let best: BestOrdering = localBest;
|
|
319
|
+
const localSearchNodeCount = best.evaluation.nodeCount;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* phase 2: exact search
|
|
323
|
+
*
|
|
324
|
+
* The exact search needs a fully specified function, so it runs on the
|
|
325
|
+
* truth table with the don't-care rows resolved. Different orderings resolve
|
|
326
|
+
* them differently, so every completion the local search came across is a
|
|
327
|
+
* candidate. Whenever a new best ordering is found, its own completion
|
|
328
|
+
* becomes a candidate too.
|
|
329
|
+
*/
|
|
330
|
+
let completed = baseEvaluator.complete(best.ordering);
|
|
331
|
+
let optimal = false;
|
|
332
|
+
let exactExpansions = 0;
|
|
333
|
+
const exactMode = input.exact === undefined ? 'auto' : input.exact;
|
|
334
|
+
const maxExactVariables = input.maxExactVariables === undefined ? DEFAULT_MAX_EXACT_VARIABLES : input.maxExactVariables;
|
|
335
|
+
const maxExactNodeCount = input.maxExactNodeCount === undefined ? DEFAULT_MAX_EXACT_NODE_COUNT : input.maxExactNodeCount;
|
|
336
|
+
const runExact = exactMode === true || (
|
|
337
|
+
exactMode === 'auto' &&
|
|
338
|
+
n <= maxExactVariables &&
|
|
339
|
+
best.evaluation.internalNodeCount <= maxExactNodeCount
|
|
340
|
+
);
|
|
341
|
+
if (exactMode === 'auto' && !runExact) {
|
|
342
|
+
log('exact search skipped: ' + n + ' boolean functions, ' + best.evaluation.internalNodeCount + ' internal nodes');
|
|
343
|
+
}
|
|
344
|
+
if (runExact) {
|
|
345
|
+
const candidates: { ordering: number[], table: DenseTruthTable }[] = [];
|
|
346
|
+
const seenCompletions: Set<string> = new Set();
|
|
347
|
+
const addCandidate = (ordering: number[], front: boolean) => {
|
|
348
|
+
const table = baseEvaluator.complete(ordering);
|
|
349
|
+
const key = completionKey(table);
|
|
350
|
+
if (seenCompletions.has(key)) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
seenCompletions.add(key);
|
|
354
|
+
const candidate = { ordering, table };
|
|
355
|
+
if (front) {
|
|
356
|
+
candidates.unshift(candidate);
|
|
357
|
+
} else {
|
|
358
|
+
candidates.push(candidate);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
addCandidate(best.ordering, false);
|
|
362
|
+
if (base.dontCareTerminal >= 0) {
|
|
363
|
+
seedResults.forEach(result => addCandidate(result.ordering, false));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* completions for which the exact search finished: on them, no ordering
|
|
368
|
+
* beats the best evaluation known at that time (which is never worse than
|
|
369
|
+
* the final one)
|
|
370
|
+
*/
|
|
371
|
+
const solvedCompletions: Set<string> = new Set();
|
|
372
|
+
while (candidates.length > 0) {
|
|
373
|
+
const candidate = candidates.shift() as { ordering: number[], table: DenseTruthTable };
|
|
374
|
+
const candidateKey = completionKey(candidate.table);
|
|
375
|
+
let exactOrdering: number[] | null;
|
|
376
|
+
let solved: boolean;
|
|
377
|
+
let expansions: number;
|
|
378
|
+
const searchOptions = { maxDiagramNodes: input.maxExactDiagramNodes };
|
|
379
|
+
if (costModel) {
|
|
380
|
+
const exactResult = new ExactCostOrderingSearch(candidate.table, costModel, searchOptions).run({
|
|
381
|
+
maxExpansions: input.maxExactExpansions,
|
|
382
|
+
upperBoundOrdering: candidate.ordering,
|
|
383
|
+
// only orderings that beat the best known one are interesting
|
|
384
|
+
upperBound: best.evaluation.expectedCost,
|
|
385
|
+
log
|
|
386
|
+
});
|
|
387
|
+
exactOrdering = exactResult.ordering;
|
|
388
|
+
solved = exactResult.solved;
|
|
389
|
+
expansions = exactResult.expansions;
|
|
390
|
+
} else {
|
|
391
|
+
const exactResult = new ExactOrderingSearch(candidate.table, searchOptions).run({
|
|
392
|
+
maxExpansions: input.maxExactExpansions,
|
|
393
|
+
upperBoundOrdering: candidate.ordering,
|
|
394
|
+
upperBound: best.evaluation.nodeCount - candidate.table.leafCount,
|
|
395
|
+
log
|
|
396
|
+
});
|
|
397
|
+
exactOrdering = exactResult.ordering;
|
|
398
|
+
solved = exactResult.solved;
|
|
399
|
+
expansions = exactResult.expansions;
|
|
400
|
+
}
|
|
401
|
+
exactExpansions += expansions;
|
|
402
|
+
if (!solved) {
|
|
403
|
+
log('exact search gave up on one completion of the truth table');
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
solvedCompletions.add(candidateKey);
|
|
407
|
+
if (!exactOrdering) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const candidateEvaluator = new OrderingEvaluator(candidate.table, costModel);
|
|
411
|
+
let exactEvaluation = candidateEvaluator.evaluate(exactOrdering);
|
|
412
|
+
if (costModel) {
|
|
413
|
+
/**
|
|
414
|
+
* The exact cost search only minimizes the expected cost. Among the
|
|
415
|
+
* orderings with the same cost, fewer nodes are better, which the
|
|
416
|
+
* local search on the same completion takes care of.
|
|
417
|
+
*/
|
|
418
|
+
const polished = new LocalOrderingSearch(candidateEvaluator, exactOrdering, {
|
|
419
|
+
windowSize,
|
|
420
|
+
maxRounds: input.maxRounds
|
|
421
|
+
}).run();
|
|
422
|
+
exactOrdering = polished.ordering;
|
|
423
|
+
exactEvaluation = polished.evaluation;
|
|
424
|
+
}
|
|
425
|
+
evaluations += candidateEvaluator.evaluations;
|
|
426
|
+
log('exact search: ' + describe(exactEvaluation) + ' after ' + expansions + ' expansions');
|
|
427
|
+
if (baseEvaluator.isBetter(exactEvaluation, best.evaluation)) {
|
|
428
|
+
best = { ordering: exactOrdering, evaluation: exactEvaluation };
|
|
429
|
+
completed = candidate.table;
|
|
430
|
+
onImprovement({
|
|
431
|
+
ordering: best.ordering.slice(),
|
|
432
|
+
nodeCount: exactEvaluation.nodeCount,
|
|
433
|
+
expectedCost: exactEvaluation.expectedCost,
|
|
434
|
+
phase: 'exact'
|
|
435
|
+
});
|
|
436
|
+
if (base.dontCareTerminal >= 0) {
|
|
437
|
+
// the don't-care rows might resolve even better with the new ordering
|
|
438
|
+
addCandidate(best.ordering, true);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* When the completion the ordering picks itself is just as good,
|
|
445
|
+
* prefer it: then createBddWithOrdering(input, ordering, dontCareValue)
|
|
446
|
+
* rebuilds the same BDD.
|
|
447
|
+
*/
|
|
448
|
+
if (base.dontCareTerminal >= 0) {
|
|
449
|
+
const own = baseEvaluator.complete(best.ordering);
|
|
450
|
+
if (completionKey(own) !== completionKey(completed)) {
|
|
451
|
+
const ownEvaluation = baseEvaluator.evaluate(best.ordering);
|
|
452
|
+
if (baseEvaluator.compare(ownEvaluation, best.evaluation) === 0) {
|
|
453
|
+
best = { ordering: best.ordering, evaluation: ownEvaluation };
|
|
454
|
+
completed = own;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
optimal = solvedCompletions.has(completionKey(completed));
|
|
459
|
+
}
|
|
460
|
+
evaluations += baseEvaluator.evaluations;
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
result: {
|
|
464
|
+
ordering: best.ordering,
|
|
465
|
+
nodeCount: best.evaluation.nodeCount,
|
|
466
|
+
expectedCost: costModel ? best.evaluation.expectedCost : undefined,
|
|
467
|
+
optimal,
|
|
468
|
+
truthTable: denseTruthTableToMap(completed),
|
|
469
|
+
stats: {
|
|
470
|
+
evaluations,
|
|
471
|
+
exactExpansions,
|
|
472
|
+
localSearchNodeCount,
|
|
473
|
+
durationMs: Date.now() - startTime
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
table: completed
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* identifies the content of a (completed) table
|
|
482
|
+
*/
|
|
483
|
+
function completionKey(table: DenseTruthTable): string {
|
|
484
|
+
let h1 = 0x811C9DC5;
|
|
485
|
+
let h2 = 0x01000193;
|
|
486
|
+
const ids = table.terminalIds;
|
|
487
|
+
for (let i = 0; i < ids.length; i++) {
|
|
488
|
+
h1 = Math.imul(h1 ^ ids[i], 0x01000193);
|
|
489
|
+
h2 = Math.imul(h2 + ids[i], 0x9E3779B1) ^ (h2 >>> 13);
|
|
490
|
+
}
|
|
491
|
+
return (h1 >>> 0).toString(16) + ':' + (h2 >>> 0).toString(16) + ':' + ids.length;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Deterministic replacement for optimizeBruteForce():
|
|
496
|
+
* finds a good variable ordering and returns the minimized BDD for it.
|
|
497
|
+
* All work is synchronous, the promise exists for compatibility with optimizeBruteForce().
|
|
498
|
+
*/
|
|
499
|
+
export async function optimizeOrdering(input: OptimizeOrderingInput): Promise<OptimizeOrderingResult> {
|
|
500
|
+
const { result: found, table } = findOrderingWithTable(input);
|
|
501
|
+
const bdd = buildBddForOrdering(table, found.ordering);
|
|
502
|
+
let optimal = found.optimal;
|
|
503
|
+
if (input.afterBddCreation) {
|
|
504
|
+
const before = fingerprint(bdd);
|
|
505
|
+
input.afterBddCreation(bdd);
|
|
506
|
+
bdd.minimize();
|
|
507
|
+
if (fingerprint(bdd) !== before) {
|
|
508
|
+
// the hook changed the BDD, the proof does not hold for it anymore
|
|
509
|
+
optimal = false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
bdd,
|
|
514
|
+
ordering: found.ordering,
|
|
515
|
+
nodeCount: bdd.countNodes(),
|
|
516
|
+
expectedCost: found.expectedCost,
|
|
517
|
+
optimal,
|
|
518
|
+
truthTable: found.truthTable,
|
|
519
|
+
stats: found.stats
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Describes the structure and the leaf values of a BDD,
|
|
525
|
+
* equal BDDs give equal strings.
|
|
526
|
+
*/
|
|
527
|
+
function fingerprint(bdd: RootNode): string {
|
|
528
|
+
const ids: Map<AbstractNode, number> = new Map();
|
|
529
|
+
const parts: string[] = [];
|
|
530
|
+
const visit = (node: AbstractNode): number => {
|
|
531
|
+
const known = ids.get(node);
|
|
532
|
+
if (known !== undefined) {
|
|
533
|
+
return known;
|
|
534
|
+
}
|
|
535
|
+
let part: string;
|
|
536
|
+
if (node.isLeafNode()) {
|
|
537
|
+
part = 'v' + String(node.asLeafNode().value);
|
|
538
|
+
} else {
|
|
539
|
+
const branches = (node as InternalNode).branches;
|
|
540
|
+
part = node.level + ':' + visit(branches.getBranch('0')) + ',' + visit(branches.getBranch('1'));
|
|
541
|
+
}
|
|
542
|
+
const id = parts.length;
|
|
543
|
+
parts.push(part);
|
|
544
|
+
ids.set(node, id);
|
|
545
|
+
return id;
|
|
546
|
+
};
|
|
547
|
+
visit(bdd);
|
|
548
|
+
return parts.join(';');
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Creates the minimized BDD of a truth table for a given ordering
|
|
553
|
+
* without going through the unreduced decision tree.
|
|
554
|
+
*/
|
|
555
|
+
export function createBddWithOrdering(
|
|
556
|
+
truthTable: TruthTable,
|
|
557
|
+
ordering: ArrayLike<number>,
|
|
558
|
+
dontCareValue?: number
|
|
559
|
+
): RootNode {
|
|
560
|
+
const base = parseTruthTable(truthTable, dontCareValue);
|
|
561
|
+
const evaluator = new OrderingEvaluator(base);
|
|
562
|
+
const completed = evaluator.complete(ordering);
|
|
563
|
+
return buildBddForOrdering(completed, ordering);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function buildBddForOrdering(table: DenseTruthTable, ordering: ArrayLike<number>): RootNode {
|
|
567
|
+
const evaluator = new OrderingEvaluator(table);
|
|
568
|
+
const diagram = evaluator.buildDiagram(ordering);
|
|
569
|
+
return buildBddFromDiagram(diagram, table);
|
|
570
|
+
}
|