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,161 @@
|
|
|
1
|
+
import type { TruthTable } from '../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A truth table stored as a flat array of terminal ids.
|
|
5
|
+
*
|
|
6
|
+
* Row index: parseInt(key, 2). The first character of a key
|
|
7
|
+
* (= boolean function 0) is the most significant bit of the row index,
|
|
8
|
+
* so variable v corresponds to bit (variableCount - 1 - v).
|
|
9
|
+
*
|
|
10
|
+
* Values are mapped to small consecutive "terminal ids" so that the
|
|
11
|
+
* hot loops only ever compare 32-bit integers.
|
|
12
|
+
*/
|
|
13
|
+
export interface DenseTruthTable {
|
|
14
|
+
/** amount of boolean functions, equals the key length of the truth table */
|
|
15
|
+
variableCount: number;
|
|
16
|
+
/** 2^variableCount */
|
|
17
|
+
rowCount: number;
|
|
18
|
+
/** terminal id of each row */
|
|
19
|
+
terminalIds: Int32Array;
|
|
20
|
+
/** terminal id -> value of the truth table */
|
|
21
|
+
terminalValues: number[];
|
|
22
|
+
/** terminal id of the don't-care value, -1 when there is none */
|
|
23
|
+
dontCareTerminal: number;
|
|
24
|
+
/** amount of leaf nodes the final BDD will have */
|
|
25
|
+
leafCount: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The hard limit of variables. Row indexes are handled with 32 bit integer
|
|
30
|
+
* operations and a table with 2^30 rows would need gigabytes of memory anyway.
|
|
31
|
+
*/
|
|
32
|
+
export const MAX_VARIABLE_COUNT = 30;
|
|
33
|
+
|
|
34
|
+
export function parseTruthTable(
|
|
35
|
+
truthTable: TruthTable,
|
|
36
|
+
dontCareValue?: number
|
|
37
|
+
): DenseTruthTable {
|
|
38
|
+
if (truthTable.size === 0) {
|
|
39
|
+
throw new Error('truth table is empty');
|
|
40
|
+
}
|
|
41
|
+
const firstKey: string = truthTable.keys().next().value as string;
|
|
42
|
+
const variableCount = firstKey.length;
|
|
43
|
+
if (variableCount < 1 || variableCount > MAX_VARIABLE_COUNT) {
|
|
44
|
+
throw new Error('truth table keys must have between 1 and ' + MAX_VARIABLE_COUNT + ' characters');
|
|
45
|
+
}
|
|
46
|
+
const rowCount = Math.pow(2, variableCount);
|
|
47
|
+
if (truthTable.size !== rowCount) {
|
|
48
|
+
throw new Error('truth table has missing entries');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const terminalIds = new Int32Array(rowCount).fill(-1);
|
|
52
|
+
const terminalValues: number[] = [];
|
|
53
|
+
const idByValue: Map<number, number> = new Map();
|
|
54
|
+
for (const [key, value] of truthTable) {
|
|
55
|
+
if (key.length !== variableCount || !/^[01]+$/.test(key)) {
|
|
56
|
+
throw new Error('invalid truth table key ' + key);
|
|
57
|
+
}
|
|
58
|
+
const row = parseInt(key, 2);
|
|
59
|
+
if (terminalIds[row] !== -1) {
|
|
60
|
+
throw new Error('duplicate truth table key ' + key);
|
|
61
|
+
}
|
|
62
|
+
let id = idByValue.get(value);
|
|
63
|
+
if (id === undefined) {
|
|
64
|
+
id = terminalValues.length;
|
|
65
|
+
terminalValues.push(value);
|
|
66
|
+
idByValue.set(value, id);
|
|
67
|
+
}
|
|
68
|
+
terminalIds[row] = id;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let dontCareTerminal = -1;
|
|
72
|
+
if (dontCareValue !== undefined) {
|
|
73
|
+
const dcId = idByValue.get(dontCareValue);
|
|
74
|
+
if (dcId !== undefined) {
|
|
75
|
+
dontCareTerminal = dcId;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const leafCount = terminalValues.length - (dontCareTerminal >= 0 ? 1 : 0);
|
|
79
|
+
if (leafCount === 0) {
|
|
80
|
+
throw new Error('truth table only consists of don\'t-care rows');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
variableCount,
|
|
85
|
+
rowCount,
|
|
86
|
+
terminalIds,
|
|
87
|
+
terminalValues,
|
|
88
|
+
dontCareTerminal,
|
|
89
|
+
leafCount
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Creates a dense table with the same terminal values
|
|
95
|
+
* but other row contents, for example after don't-care rows have been resolved.
|
|
96
|
+
*/
|
|
97
|
+
export function withTerminalIds(
|
|
98
|
+
table: DenseTruthTable,
|
|
99
|
+
terminalIds: Int32Array,
|
|
100
|
+
dontCareTerminal: number
|
|
101
|
+
): DenseTruthTable {
|
|
102
|
+
let leafCount = 0;
|
|
103
|
+
const seen = new Uint8Array(table.terminalValues.length);
|
|
104
|
+
for (let i = 0; i < terminalIds.length; i++) {
|
|
105
|
+
const id = terminalIds[i];
|
|
106
|
+
if (seen[id] === 0) {
|
|
107
|
+
seen[id] = 1;
|
|
108
|
+
if (id !== dontCareTerminal) {
|
|
109
|
+
leafCount++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (leafCount === 0) {
|
|
114
|
+
leafCount = 1;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
variableCount: table.variableCount,
|
|
118
|
+
rowCount: table.rowCount,
|
|
119
|
+
terminalIds,
|
|
120
|
+
terminalValues: table.terminalValues,
|
|
121
|
+
dontCareTerminal,
|
|
122
|
+
leafCount
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Converts the dense table back into a Map with keys in the original order.
|
|
128
|
+
*/
|
|
129
|
+
export function denseTruthTableToMap(table: DenseTruthTable): TruthTable {
|
|
130
|
+
const ret: TruthTable = new Map();
|
|
131
|
+
const n = table.variableCount;
|
|
132
|
+
for (let row = 0; row < table.rowCount; row++) {
|
|
133
|
+
ret.set(
|
|
134
|
+
row.toString(2).padStart(n, '0'),
|
|
135
|
+
table.terminalValues[table.terminalIds[row]]
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return ret;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function naturalOrdering(variableCount: number): number[] {
|
|
142
|
+
const ret: number[] = [];
|
|
143
|
+
for (let i = 0; i < variableCount; i++) {
|
|
144
|
+
ret.push(i);
|
|
145
|
+
}
|
|
146
|
+
return ret;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function ensureValidOrdering(ordering: ArrayLike<number>, variableCount: number) {
|
|
150
|
+
if (ordering.length !== variableCount) {
|
|
151
|
+
throw new Error('ordering must contain each of the ' + variableCount + ' boolean functions exactly once');
|
|
152
|
+
}
|
|
153
|
+
const seen = new Uint8Array(variableCount);
|
|
154
|
+
for (let i = 0; i < ordering.length; i++) {
|
|
155
|
+
const v = ordering[i];
|
|
156
|
+
if (!Number.isInteger(v) || v < 0 || v >= variableCount || seen[v] === 1) {
|
|
157
|
+
throw new Error('ordering must contain each of the ' + variableCount + ' boolean functions exactly once');
|
|
158
|
+
}
|
|
159
|
+
seen[v] = 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import type { DenseTruthTable } from './dense-truth-table.js';
|
|
2
|
+
import { naturalOrdering } from './dense-truth-table.js';
|
|
3
|
+
import { OrderingEvaluator, CostModel, costTolerance, costUnit } from './ordering-evaluator.js';
|
|
4
|
+
import { SharedDiagram, StateHeap, DiagramBudgetExceededError, DEFAULT_MAX_DIAGRAM_NODES } from './shared-diagram.js';
|
|
5
|
+
|
|
6
|
+
export interface ExactCostOrderingOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The search stops without a proof when more states were expanded.
|
|
9
|
+
* Default: 200000
|
|
10
|
+
*/
|
|
11
|
+
maxExpansions?: number;
|
|
12
|
+
/**
|
|
13
|
+
* The search stops without a proof when the stored states reference
|
|
14
|
+
* more rows in total (memory protection).
|
|
15
|
+
* Default: 2^25
|
|
16
|
+
*/
|
|
17
|
+
maxStoredRows?: number;
|
|
18
|
+
/**
|
|
19
|
+
* A known good ordering. Its expected cost is used as upper bound to prune the search.
|
|
20
|
+
*/
|
|
21
|
+
upperBoundOrdering?: ArrayLike<number>;
|
|
22
|
+
/**
|
|
23
|
+
* Only orderings with a lower expected cost than this are searched for.
|
|
24
|
+
*/
|
|
25
|
+
upperBound?: number;
|
|
26
|
+
log?: (message: string) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ExactCostOrderingResult {
|
|
30
|
+
/**
|
|
31
|
+
* true when it is proven that no ordering has a lower expected cost
|
|
32
|
+
* than the returned one (or than the upper bound, when ordering is null)
|
|
33
|
+
*/
|
|
34
|
+
solved: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The best ordering that was found. Irrelevant boolean functions
|
|
37
|
+
* are always at the bottom. null when no ordering below the numeric
|
|
38
|
+
* upper bound exists and the upperBoundOrdering (if given) is not
|
|
39
|
+
* below it either.
|
|
40
|
+
*/
|
|
41
|
+
ordering: number[] | null;
|
|
42
|
+
/** expected cost of one resolve() call with `ordering`, -1 when ordering is null */
|
|
43
|
+
expectedCost: number;
|
|
44
|
+
expansions: number;
|
|
45
|
+
generated: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* the rows of a search state, grouped by their cofactor
|
|
50
|
+
*/
|
|
51
|
+
interface GroupedRows {
|
|
52
|
+
/** indexes into activeRows, grouped */
|
|
53
|
+
rows: Int32Array;
|
|
54
|
+
/** group g spans rows[groupStart[g]] .. rows[groupStart[g + 1] - 1] */
|
|
55
|
+
groupStart: Int32Array;
|
|
56
|
+
/** node id of the cofactor of each group */
|
|
57
|
+
groupFunction: Int32Array;
|
|
58
|
+
/** total weight of the rows of each group */
|
|
59
|
+
groupWeight: Float64Array;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Exact minimization of the expected cost of a resolve() call.
|
|
64
|
+
*
|
|
65
|
+
* A row pays the cost of a boolean function when its path through the BDD
|
|
66
|
+
* visits a node of that function, which happens exactly when the cofactor of
|
|
67
|
+
* the row with respect to the functions above still depends on that function.
|
|
68
|
+
* So like the amount of nodes, the expected cost decomposes over the lattice of
|
|
69
|
+
* variable subsets: the edge S -> S + {x} costs cost(x) times the weight of the
|
|
70
|
+
* rows whose cofactor with respect to S depends on x (the root level always
|
|
71
|
+
* pays, because the root node always calls its function).
|
|
72
|
+
*
|
|
73
|
+
* A state keeps the rows with positive weight grouped by their cofactor.
|
|
74
|
+
* Rows whose cofactor became constant never pay anything again and are dropped.
|
|
75
|
+
* The admissible estimate for the remaining cost sums, for every function x
|
|
76
|
+
* not yet placed, cost(x) times the weight of the rows that are sensitive to x
|
|
77
|
+
* (the value changes when only x is flipped), because such rows pay for x in
|
|
78
|
+
* every ordering.
|
|
79
|
+
*/
|
|
80
|
+
export class ExactCostOrderingSearch {
|
|
81
|
+
private readonly diagram: SharedDiagram;
|
|
82
|
+
private readonly n: number;
|
|
83
|
+
/** natural indexes of the rows with positive weight, ascending */
|
|
84
|
+
private readonly activeRows: Int32Array;
|
|
85
|
+
private readonly activeWeights: Float64Array;
|
|
86
|
+
private readonly totalWeight: number;
|
|
87
|
+
/** sensitiveWeight[v] = weight of the active rows whose value changes when only v is flipped */
|
|
88
|
+
private readonly sensitiveWeight: Float64Array;
|
|
89
|
+
private readonly costs: Float64Array;
|
|
90
|
+
|
|
91
|
+
constructor(
|
|
92
|
+
private readonly table: DenseTruthTable,
|
|
93
|
+
private readonly costModel: CostModel,
|
|
94
|
+
options: { maxDiagramNodes?: number } = {}
|
|
95
|
+
) {
|
|
96
|
+
if (table.dontCareTerminal >= 0) {
|
|
97
|
+
throw new Error('the exact search needs a truth table without don\'t-care rows, resolve them with OrderingEvaluator.complete() first');
|
|
98
|
+
}
|
|
99
|
+
this.diagram = new SharedDiagram(
|
|
100
|
+
table,
|
|
101
|
+
options.maxDiagramNodes === undefined ? DEFAULT_MAX_DIAGRAM_NODES : options.maxDiagramNodes
|
|
102
|
+
);
|
|
103
|
+
this.n = table.variableCount;
|
|
104
|
+
this.costs = costModel.functionCosts;
|
|
105
|
+
const rowWeights = costModel.rowWeights;
|
|
106
|
+
let activeCount = 0;
|
|
107
|
+
for (let row = 0; row < table.rowCount; row++) {
|
|
108
|
+
if (rowWeights[row] > 0) {
|
|
109
|
+
activeCount++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
this.activeRows = new Int32Array(activeCount);
|
|
113
|
+
this.activeWeights = new Float64Array(activeCount);
|
|
114
|
+
let total = 0;
|
|
115
|
+
let index = 0;
|
|
116
|
+
for (let row = 0; row < table.rowCount; row++) {
|
|
117
|
+
if (rowWeights[row] > 0) {
|
|
118
|
+
this.activeRows[index] = row;
|
|
119
|
+
this.activeWeights[index] = rowWeights[row];
|
|
120
|
+
total += rowWeights[row];
|
|
121
|
+
index++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
this.totalWeight = total;
|
|
125
|
+
this.sensitiveWeight = new Float64Array(this.n);
|
|
126
|
+
const ids = table.terminalIds;
|
|
127
|
+
for (let v = 0; v < this.n; v++) {
|
|
128
|
+
const bit = 1 << (this.n - 1 - v);
|
|
129
|
+
let weight = 0;
|
|
130
|
+
for (let i = 0; i < activeCount; i++) {
|
|
131
|
+
const row = this.activeRows[i];
|
|
132
|
+
if (ids[row] !== ids[row ^ bit]) {
|
|
133
|
+
weight += this.activeWeights[i];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.sensitiveWeight[v] = weight;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public normalizeOrdering(ordering: ArrayLike<number>): number[] {
|
|
141
|
+
return this.diagram.normalizeOrdering(ordering);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public run(options: ExactCostOrderingOptions = {}): ExactCostOrderingResult {
|
|
145
|
+
const log = options.log ? options.log : () => null;
|
|
146
|
+
const maxExpansions = options.maxExpansions === undefined ? 200000 : options.maxExpansions;
|
|
147
|
+
const maxStoredRows = options.maxStoredRows === undefined ? Math.pow(2, 25) : options.maxStoredRows;
|
|
148
|
+
const diagram = this.diagram;
|
|
149
|
+
const relevant = diagram.relevant;
|
|
150
|
+
const m = relevant.length;
|
|
151
|
+
const total = this.totalWeight;
|
|
152
|
+
const costs = this.costs;
|
|
153
|
+
const irrelevant = diagram.irrelevantVariables();
|
|
154
|
+
// path costs are unnormalized (multiplied by the total weight), so is the tolerance unit
|
|
155
|
+
const unit = costUnit(this.costModel) * total;
|
|
156
|
+
const tolerance = (value: number) => costTolerance(value, unit);
|
|
157
|
+
/**
|
|
158
|
+
* Only costs below this threshold can beat the upper bound.
|
|
159
|
+
* Without an upper bound every finite cost can.
|
|
160
|
+
*/
|
|
161
|
+
const pruneThreshold = (bound: number) => isFinite(bound) ? bound - tolerance(bound) : Infinity;
|
|
162
|
+
|
|
163
|
+
// upper bound, kept unnormalized (multiplied by the total weight) like the path costs
|
|
164
|
+
let upperBound = options.upperBound === undefined ? Infinity : options.upperBound * total;
|
|
165
|
+
let incumbent: number[] | null = null;
|
|
166
|
+
if (options.upperBoundOrdering) {
|
|
167
|
+
incumbent = this.normalizeOrdering(options.upperBoundOrdering);
|
|
168
|
+
const incumbentCost = new OrderingEvaluator(this.table, this.costModel).evaluate(incumbent).expectedCost * total;
|
|
169
|
+
if (incumbentCost >= pruneThreshold(upperBound)) {
|
|
170
|
+
incumbent = null;
|
|
171
|
+
} else {
|
|
172
|
+
upperBound = incumbentCost;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const threshold = pruneThreshold(upperBound);
|
|
176
|
+
const result = (
|
|
177
|
+
solved: boolean,
|
|
178
|
+
ordering: number[] | null,
|
|
179
|
+
cost: number,
|
|
180
|
+
expansionCount: number,
|
|
181
|
+
generatedCount: number
|
|
182
|
+
): ExactCostOrderingResult => ({
|
|
183
|
+
solved,
|
|
184
|
+
ordering,
|
|
185
|
+
expectedCost: ordering ? cost / total : -1,
|
|
186
|
+
expansions: expansionCount,
|
|
187
|
+
generated: generatedCount
|
|
188
|
+
});
|
|
189
|
+
if (diagram.overBudget) {
|
|
190
|
+
log('exact cost search: diagram budget exceeded');
|
|
191
|
+
return result(false, incumbent, upperBound, 0, 0);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (m === 0) {
|
|
195
|
+
// constant function: only the root pays, so the cheapest function goes on top
|
|
196
|
+
const ordering = naturalOrdering(this.n).sort((a, b) => (costs[a] - costs[b]) || (a - b));
|
|
197
|
+
const cost = costs[ordering[0]] * total;
|
|
198
|
+
if (cost >= threshold) {
|
|
199
|
+
return result(true, incumbent, upperBound, 0, 0);
|
|
200
|
+
}
|
|
201
|
+
return result(true, ordering, cost, 0, 0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const full = (1 << m) - 1;
|
|
205
|
+
const states: Map<number, GroupedRows> = new Map();
|
|
206
|
+
const bestCost: Map<number, number> = new Map();
|
|
207
|
+
const parentVariable: Map<number, number> = new Map();
|
|
208
|
+
const closed: Set<number> = new Set();
|
|
209
|
+
const heap = new StateHeap();
|
|
210
|
+
|
|
211
|
+
const startRows = new Int32Array(this.activeRows.length);
|
|
212
|
+
for (let i = 0; i < startRows.length; i++) {
|
|
213
|
+
startRows[i] = i;
|
|
214
|
+
}
|
|
215
|
+
const startState: GroupedRows = {
|
|
216
|
+
rows: startRows,
|
|
217
|
+
groupStart: Int32Array.of(0, startRows.length),
|
|
218
|
+
groupFunction: Int32Array.of(diagram.rootId),
|
|
219
|
+
groupWeight: Float64Array.of(total)
|
|
220
|
+
};
|
|
221
|
+
states.set(0, startState);
|
|
222
|
+
bestCost.set(0, 0);
|
|
223
|
+
heap.push(this.estimate(0), 0, 0);
|
|
224
|
+
|
|
225
|
+
let expansions = 0;
|
|
226
|
+
let generated = 1;
|
|
227
|
+
let storedRows = startRows.length;
|
|
228
|
+
let solvedSet = -1;
|
|
229
|
+
let solvedCost = -1;
|
|
230
|
+
try {
|
|
231
|
+
while (heap.size > 0) {
|
|
232
|
+
const state = heap.pop();
|
|
233
|
+
const currentCost = state.cost;
|
|
234
|
+
const subset = state.subset;
|
|
235
|
+
if (currentCost !== bestCost.get(subset) || closed.has(subset)) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (state.estimate >= threshold) {
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
if (subset === full) {
|
|
242
|
+
solvedSet = subset;
|
|
243
|
+
solvedCost = currentCost;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
closed.add(subset);
|
|
247
|
+
expansions++;
|
|
248
|
+
if (expansions > maxExpansions) {
|
|
249
|
+
log('exact cost search: expansion budget exceeded');
|
|
250
|
+
return result(false, incumbent, upperBound, expansions, generated);
|
|
251
|
+
}
|
|
252
|
+
if (storedRows > maxStoredRows) {
|
|
253
|
+
log('exact cost search: memory budget exceeded');
|
|
254
|
+
return result(false, incumbent, upperBound, expansions, generated);
|
|
255
|
+
}
|
|
256
|
+
if (expansions % 10000 === 0) {
|
|
257
|
+
log('exact cost search: ' + expansions + ' states expanded, ' + generated + ' generated, ' + heap.size + ' open');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const grouped = states.get(subset) as GroupedRows;
|
|
261
|
+
for (let c = 0; c < m; c++) {
|
|
262
|
+
const bit = 1 << c;
|
|
263
|
+
if ((subset & bit) !== 0) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const variable = relevant[c];
|
|
267
|
+
const nextSubset = subset | bit;
|
|
268
|
+
const payingWeight = subset === 0 ? total : this.payingWeight(grouped, variable);
|
|
269
|
+
const nextCost = currentCost + costs[variable] * payingWeight;
|
|
270
|
+
const knownCost = bestCost.get(nextSubset);
|
|
271
|
+
if (knownCost !== undefined && knownCost <= nextCost + tolerance(nextCost)) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const estimate = nextCost + this.estimate(nextSubset);
|
|
275
|
+
if (estimate >= threshold) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
// the grouping of the rows is only needed when the state can be expanded
|
|
279
|
+
if (!states.has(nextSubset)) {
|
|
280
|
+
const next = this.transition(grouped, variable);
|
|
281
|
+
states.set(nextSubset, next);
|
|
282
|
+
generated++;
|
|
283
|
+
storedRows += next.rows.length;
|
|
284
|
+
}
|
|
285
|
+
bestCost.set(nextSubset, nextCost);
|
|
286
|
+
parentVariable.set(nextSubset, variable);
|
|
287
|
+
closed.delete(nextSubset);
|
|
288
|
+
heap.push(estimate, nextCost, nextSubset);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
if (err instanceof DiagramBudgetExceededError) {
|
|
293
|
+
log('exact cost search: diagram budget exceeded');
|
|
294
|
+
return result(false, incumbent, upperBound, expansions, generated);
|
|
295
|
+
}
|
|
296
|
+
throw err;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (solvedSet === -1) {
|
|
300
|
+
return result(true, incumbent, upperBound, expansions, generated);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const reversed: number[] = [];
|
|
304
|
+
let remainingSubset = solvedSet;
|
|
305
|
+
while (remainingSubset !== 0) {
|
|
306
|
+
const variable = parentVariable.get(remainingSubset) as number;
|
|
307
|
+
reversed.push(variable);
|
|
308
|
+
remainingSubset = remainingSubset & ~(1 << diagram.compactOf[variable]);
|
|
309
|
+
}
|
|
310
|
+
return result(true, reversed.reverse().concat(irrelevant), solvedCost, expansions, generated);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* lower bound for the remaining (unnormalized) cost below the subset
|
|
315
|
+
*/
|
|
316
|
+
private estimate(subset: number): number {
|
|
317
|
+
const relevant = this.diagram.relevant;
|
|
318
|
+
let estimate = 0;
|
|
319
|
+
for (let c = 0; c < relevant.length; c++) {
|
|
320
|
+
if ((subset & (1 << c)) === 0) {
|
|
321
|
+
const variable = relevant[c];
|
|
322
|
+
estimate += this.costs[variable] * this.sensitiveWeight[variable];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return estimate;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* the weight of the rows that pay for `variable` when it is fixed next
|
|
330
|
+
*/
|
|
331
|
+
private payingWeight(grouped: GroupedRows, variable: number): number {
|
|
332
|
+
const diagram = this.diagram;
|
|
333
|
+
const bit = 1 << diagram.compactOf[variable];
|
|
334
|
+
let payingWeight = 0;
|
|
335
|
+
for (let g = 0; g < grouped.groupFunction.length; g++) {
|
|
336
|
+
if ((diagram.supportOf(grouped.groupFunction[g]) & bit) !== 0) {
|
|
337
|
+
payingWeight += grouped.groupWeight[g];
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return payingWeight;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* fixes `variable` additionally: returns the new grouping of the rows
|
|
345
|
+
* that still have a non-constant cofactor
|
|
346
|
+
*/
|
|
347
|
+
private transition(grouped: GroupedRows, variable: number): GroupedRows {
|
|
348
|
+
const diagram = this.diagram;
|
|
349
|
+
const rowBit = 1 << (this.n - 1 - variable);
|
|
350
|
+
const groupCount = grouped.groupFunction.length;
|
|
351
|
+
const functions: number[] = [];
|
|
352
|
+
const rowLists: Int32Array[] = [];
|
|
353
|
+
for (let g = 0; g < groupCount; g++) {
|
|
354
|
+
const fn = grouped.groupFunction[g];
|
|
355
|
+
const start = grouped.groupStart[g];
|
|
356
|
+
const end = grouped.groupStart[g + 1];
|
|
357
|
+
const rows = grouped.rows.subarray(start, end);
|
|
358
|
+
if (!diagram.dependsOn(fn, variable)) {
|
|
359
|
+
functions.push(fn);
|
|
360
|
+
rowLists.push(rows);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const lo = diagram.restrict(fn, variable, 0);
|
|
364
|
+
const hi = diagram.restrict(fn, variable, 1);
|
|
365
|
+
let loCount = 0;
|
|
366
|
+
for (let i = 0; i < rows.length; i++) {
|
|
367
|
+
if ((this.activeRows[rows[i]] & rowBit) === 0) {
|
|
368
|
+
loCount++;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const loRows = new Int32Array(loCount);
|
|
372
|
+
const hiRows = new Int32Array(rows.length - loCount);
|
|
373
|
+
let loIndex = 0;
|
|
374
|
+
let hiIndex = 0;
|
|
375
|
+
for (let i = 0; i < rows.length; i++) {
|
|
376
|
+
if ((this.activeRows[rows[i]] & rowBit) === 0) {
|
|
377
|
+
loRows[loIndex++] = rows[i];
|
|
378
|
+
} else {
|
|
379
|
+
hiRows[hiIndex++] = rows[i];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (!diagram.isTerminal(lo) && loRows.length > 0) {
|
|
383
|
+
functions.push(lo);
|
|
384
|
+
rowLists.push(loRows);
|
|
385
|
+
}
|
|
386
|
+
if (!diagram.isTerminal(hi) && hiRows.length > 0) {
|
|
387
|
+
functions.push(hi);
|
|
388
|
+
rowLists.push(hiRows);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// merge the groups with equal cofactors
|
|
393
|
+
const order = functions.map((_fn, index) => index).sort((a, b) => (functions[a] - functions[b]) || (a - b));
|
|
394
|
+
let totalRows = 0;
|
|
395
|
+
rowLists.forEach(list => totalRows += list.length);
|
|
396
|
+
const mergedRows = new Int32Array(totalRows);
|
|
397
|
+
const groupStart: number[] = [0];
|
|
398
|
+
const groupFunction: number[] = [];
|
|
399
|
+
const groupWeight: number[] = [];
|
|
400
|
+
let position = 0;
|
|
401
|
+
let weight = 0;
|
|
402
|
+
for (let i = 0; i < order.length; i++) {
|
|
403
|
+
const fn = functions[order[i]];
|
|
404
|
+
const list = rowLists[order[i]];
|
|
405
|
+
mergedRows.set(list, position);
|
|
406
|
+
position += list.length;
|
|
407
|
+
for (let r = 0; r < list.length; r++) {
|
|
408
|
+
weight += this.activeWeights[list[r]];
|
|
409
|
+
}
|
|
410
|
+
if (i + 1 < order.length && functions[order[i + 1]] === fn) {
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
groupFunction.push(fn);
|
|
414
|
+
groupStart.push(position);
|
|
415
|
+
groupWeight.push(weight);
|
|
416
|
+
weight = 0;
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
rows: mergedRows,
|
|
420
|
+
groupStart: Int32Array.from(groupStart),
|
|
421
|
+
groupFunction: Int32Array.from(groupFunction),
|
|
422
|
+
groupWeight: Float64Array.from(groupWeight)
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|