@cavelang/solver 0.35.0 → 0.36.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/README.md +2155 -3
- package/dist/src/adapter.d.ts +6 -0
- package/dist/src/adapter.d.ts.map +1 -1
- package/dist/src/adapter.js +3 -0
- package/dist/src/adapter.js.map +1 -1
- package/dist/src/balanced-reduce.d.ts +3 -0
- package/dist/src/balanced-reduce.d.ts.map +1 -0
- package/dist/src/balanced-reduce.js +10 -0
- package/dist/src/balanced-reduce.js.map +1 -0
- package/dist/src/canonical-owned.d.ts +5 -0
- package/dist/src/canonical-owned.d.ts.map +1 -0
- package/dist/src/canonical-owned.js +206 -0
- package/dist/src/canonical-owned.js.map +1 -0
- package/dist/src/canonical.d.ts +3 -2
- package/dist/src/canonical.d.ts.map +1 -1
- package/dist/src/canonical.js +12 -85
- package/dist/src/canonical.js.map +1 -1
- package/dist/src/capability.d.ts.map +1 -1
- package/dist/src/capability.js +43 -33
- package/dist/src/capability.js.map +1 -1
- package/dist/src/clone.d.ts +3 -0
- package/dist/src/clone.d.ts.map +1 -0
- package/dist/src/clone.js +54 -0
- package/dist/src/clone.js.map +1 -0
- package/dist/src/constant-sign.d.ts +5 -0
- package/dist/src/constant-sign.d.ts.map +1 -0
- package/dist/src/constant-sign.js +54 -0
- package/dist/src/constant-sign.js.map +1 -0
- package/dist/src/context.d.ts +6 -0
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +151 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/diagnostic-text.d.ts +3 -0
- package/dist/src/diagnostic-text.d.ts.map +1 -0
- package/dist/src/diagnostic-text.js +5 -0
- package/dist/src/diagnostic-text.js.map +1 -0
- package/dist/src/evaluate-expression.d.ts +4 -0
- package/dist/src/evaluate-expression.d.ts.map +1 -0
- package/dist/src/evaluate-expression.js +24 -0
- package/dist/src/evaluate-expression.js.map +1 -0
- package/dist/src/exact.d.ts +2 -0
- package/dist/src/exact.d.ts.map +1 -1
- package/dist/src/exact.js +125 -28
- package/dist/src/exact.js.map +1 -1
- package/dist/src/explain.d.ts +4 -0
- package/dist/src/explain.d.ts.map +1 -1
- package/dist/src/explain.js +330 -91
- package/dist/src/explain.js.map +1 -1
- package/dist/src/explanation-budget.d.ts +14 -0
- package/dist/src/explanation-budget.d.ts.map +1 -0
- package/dist/src/explanation-budget.js +46 -0
- package/dist/src/explanation-budget.js.map +1 -0
- package/dist/src/expression-key.d.ts +8 -0
- package/dist/src/expression-key.d.ts.map +1 -0
- package/dist/src/expression-key.js +54 -0
- package/dist/src/expression-key.js.map +1 -0
- package/dist/src/fraction-product.d.ts +3 -0
- package/dist/src/fraction-product.d.ts.map +1 -0
- package/dist/src/fraction-product.js +13 -0
- package/dist/src/fraction-product.js.map +1 -0
- package/dist/src/fraction-sum.d.ts +3 -0
- package/dist/src/fraction-sum.d.ts.map +1 -0
- package/dist/src/fraction-sum.js +23 -0
- package/dist/src/fraction-sum.js.map +1 -0
- package/dist/src/integer-gcd.d.ts +3 -0
- package/dist/src/integer-gcd.d.ts.map +1 -0
- package/dist/src/integer-gcd.js +50 -0
- package/dist/src/integer-gcd.js.map +1 -0
- package/dist/src/linear.d.ts +2 -1
- package/dist/src/linear.d.ts.map +1 -1
- package/dist/src/linear.js +85 -17
- package/dist/src/linear.js.map +1 -1
- package/dist/src/numeric-size.d.ts +2 -0
- package/dist/src/numeric-size.d.ts.map +1 -0
- package/dist/src/numeric-size.js +33 -0
- package/dist/src/numeric-size.js.map +1 -0
- package/dist/src/options.d.ts +4 -0
- package/dist/src/options.d.ts.map +1 -0
- package/dist/src/options.js +18 -0
- package/dist/src/options.js.map +1 -0
- package/dist/src/result.d.ts +4 -0
- package/dist/src/result.d.ts.map +1 -0
- package/dist/src/result.js +29 -0
- package/dist/src/result.js.map +1 -0
- package/dist/src/solve.d.ts.map +1 -1
- package/dist/src/solve.js +34 -3
- package/dist/src/solve.js.map +1 -1
- package/dist/src/validate.d.ts +5 -0
- package/dist/src/validate.d.ts.map +1 -1
- package/dist/src/validate.js +323 -65
- package/dist/src/validate.js.map +1 -1
- package/dist/src/workflow.d.ts.map +1 -1
- package/dist/src/workflow.js +98 -26
- package/dist/src/workflow.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +9 -0
- package/src/balanced-reduce.ts +9 -0
- package/src/canonical-owned.ts +186 -0
- package/src/canonical.ts +14 -90
- package/src/capability.ts +41 -30
- package/src/clone.ts +47 -0
- package/src/constant-sign.ts +59 -0
- package/src/context.ts +114 -0
- package/src/diagnostic-text.ts +4 -0
- package/src/evaluate-expression.ts +21 -0
- package/src/exact.ts +122 -27
- package/src/explain.ts +342 -94
- package/src/explanation-budget.ts +47 -0
- package/src/expression-key.ts +45 -0
- package/src/fraction-product.ts +10 -0
- package/src/fraction-sum.ts +20 -0
- package/src/integer-gcd.ts +47 -0
- package/src/linear.ts +82 -16
- package/src/numeric-size.ts +26 -0
- package/src/options.ts +18 -0
- package/src/result.ts +28 -0
- package/src/solve.ts +34 -3
- package/src/validate.ts +312 -66
- package/src/workflow.ts +92 -26
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/explain.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/explain.ts"],"names":[],"mappings":"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"}
|
package/dist/src/explain.js
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ExplanationBudget } from "./explanation-budget.js";
|
|
2
|
+
import { expressionKey } from "./expression-key.js";
|
|
3
|
+
import { constantSign } from "./constant-sign.js";
|
|
4
|
+
import { captureResult } from "./result.js";
|
|
5
|
+
import { balancedReduce } from "./balanced-reduce.js";
|
|
6
|
+
import { sum } from "./fraction-sum.js";
|
|
7
|
+
import { product } from "./fraction-product.js";
|
|
8
|
+
import { gcd } from "./integer-gcd.js";
|
|
9
|
+
import { clone } from "./clone.js";
|
|
10
|
+
import { stringifyInput, validateContext } from "./context.js";
|
|
11
|
+
import { mergeLimits } from "./validate.js";
|
|
12
|
+
import { digestOwned } from "./canonical-owned.js";
|
|
2
13
|
import * as Exact from "./exact.js";
|
|
3
14
|
export const schema = 'cave.solver/explanation@1';
|
|
4
15
|
const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
5
16
|
const normalize = (numerator, denominator) => {
|
|
6
17
|
if (denominator === 0n)
|
|
7
18
|
throw new TypeError('cannot explain division by zero');
|
|
8
|
-
|
|
9
|
-
|
|
19
|
+
if (denominator < 0n) {
|
|
20
|
+
numerator = -numerator;
|
|
21
|
+
denominator = -denominator;
|
|
22
|
+
}
|
|
23
|
+
const divisor = gcd(numerator, denominator);
|
|
24
|
+
return { kind: 'number', numerator: numerator / divisor, denominator: denominator / divisor };
|
|
10
25
|
};
|
|
11
|
-
const number = (value) => {
|
|
26
|
+
const number = (value, budget) => {
|
|
27
|
+
budget.input(value);
|
|
12
28
|
const exact = Exact.rational(value);
|
|
13
29
|
return { kind: 'number', numerator: BigInt(exact.numerator), denominator: BigInt(exact.denominator) };
|
|
14
30
|
};
|
|
@@ -22,15 +38,59 @@ const bool = (value) => {
|
|
|
22
38
|
throw new TypeError(`expected a Boolean explanation value, received ${value.kind}`);
|
|
23
39
|
return value.value;
|
|
24
40
|
};
|
|
25
|
-
const add = (left, right) =>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
const add = (left, right, budget) => {
|
|
42
|
+
if (left.numerator === 0n)
|
|
43
|
+
return right;
|
|
44
|
+
if (right.numerator === 0n)
|
|
45
|
+
return left;
|
|
46
|
+
if (left.denominator === right.denominator) {
|
|
47
|
+
budget.check(budget.sum(left.numerator, right.numerator), budget.bits(left.denominator));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
budget.check(Math.max(budget.product(left.numerator, right.denominator), budget.product(right.numerator, left.denominator)) + 1, budget.product(left.denominator, right.denominator));
|
|
51
|
+
}
|
|
52
|
+
// Adding an integer preserves gcd(numerator, denominator) = 1.
|
|
53
|
+
if (left.denominator === 1n)
|
|
54
|
+
return { kind: 'number',
|
|
55
|
+
numerator: left.numerator * right.denominator + right.numerator, denominator: right.denominator };
|
|
56
|
+
if (right.denominator === 1n)
|
|
57
|
+
return { kind: 'number',
|
|
58
|
+
numerator: left.numerator + right.numerator * left.denominator, denominator: left.denominator };
|
|
59
|
+
return normalize(...sum(left.numerator, left.denominator, right.numerator, right.denominator));
|
|
60
|
+
};
|
|
61
|
+
const multiply = (left, right, budget) => {
|
|
62
|
+
budget.check(budget.product(left.numerator, right.numerator), budget.product(left.denominator, right.denominator));
|
|
63
|
+
// Evaluation values are reduced with positive denominators. Squaring preserves
|
|
64
|
+
// coprimality; otherwise cross-cancellation leaves a reduced product directly.
|
|
65
|
+
const [numerator, denominator] = left.numerator === right.numerator && left.denominator === right.denominator
|
|
66
|
+
? [left.numerator * right.numerator, left.denominator * right.denominator]
|
|
67
|
+
: product(left.numerator, left.denominator, right.numerator, right.denominator);
|
|
68
|
+
return { kind: 'number', numerator, denominator };
|
|
69
|
+
};
|
|
70
|
+
const negate = (value) => ({ kind: 'number', numerator: -value.numerator, denominator: value.denominator });
|
|
71
|
+
const compare = (left, right, budget) => {
|
|
72
|
+
// Evaluation values are normalized, so both denominators are positive.
|
|
73
|
+
if (left.denominator === right.denominator || left.numerator === 0n || right.numerator === 0n ||
|
|
74
|
+
(left.numerator < 0n) !== (right.numerator < 0n)) {
|
|
75
|
+
return left.numerator < right.numerator ? -1 : left.numerator > right.numerator ? 1 : 0;
|
|
76
|
+
}
|
|
77
|
+
if (left.numerator === right.numerator) {
|
|
78
|
+
return (left.denominator < right.denominator) === (left.numerator > 0n) ? 1 : -1;
|
|
79
|
+
}
|
|
80
|
+
if ((left.numerator < right.numerator) ===
|
|
81
|
+
(left.numerator > 0n ? left.denominator > right.denominator : left.denominator < right.denominator)) {
|
|
82
|
+
return left.numerator < right.numerator ? -1 : 1;
|
|
83
|
+
}
|
|
84
|
+
budget.check(budget.product(left.numerator, right.denominator), budget.product(right.numerator, left.denominator));
|
|
85
|
+
const a = left.numerator * right.denominator;
|
|
86
|
+
const b = right.numerator * left.denominator;
|
|
87
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
30
88
|
};
|
|
31
89
|
const equal = (left, right) => {
|
|
32
|
-
if (left.kind === 'number' && right.kind === 'number')
|
|
33
|
-
|
|
90
|
+
if (left.kind === 'number' && right.kind === 'number') {
|
|
91
|
+
// Every evaluated number is reduced with a positive denominator.
|
|
92
|
+
return left.numerator === right.numerator && left.denominator === right.denominator;
|
|
93
|
+
}
|
|
34
94
|
if (left.kind !== right.kind)
|
|
35
95
|
return false;
|
|
36
96
|
if (left.kind === 'bool' && right.kind === 'bool')
|
|
@@ -39,57 +99,163 @@ const equal = (left, right) => {
|
|
|
39
99
|
return left.domain === right.domain && left.value === right.value;
|
|
40
100
|
return false;
|
|
41
101
|
};
|
|
42
|
-
const assigned = (value) => {
|
|
102
|
+
const assigned = (value, budget) => {
|
|
43
103
|
switch (value.sort) {
|
|
44
|
-
case 'bool':
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
104
|
+
case 'bool':
|
|
105
|
+
if (typeof value.value !== 'boolean')
|
|
106
|
+
throw new TypeError('boolean assignment value must be a boolean');
|
|
107
|
+
return { kind: 'bool', value: value.value };
|
|
108
|
+
case 'int': return number({ numerator: value.value, denominator: '1' }, budget);
|
|
109
|
+
case 'real': return number({ numerator: value.numerator, denominator: value.denominator }, budget);
|
|
110
|
+
case 'enum':
|
|
111
|
+
if (typeof value.domain !== 'string' || typeof value.value !== 'string')
|
|
112
|
+
throw new TypeError('enum assignment domain and value must be strings');
|
|
113
|
+
return { kind: 'enum', domain: value.domain, value: value.value };
|
|
48
114
|
}
|
|
49
115
|
};
|
|
50
|
-
|
|
51
|
-
|
|
116
|
+
/** A captured assignment has one validation result for the whole report. */
|
|
117
|
+
const evaluateAssignment = (id, context) => {
|
|
118
|
+
const cached = context.assigned.get(id);
|
|
119
|
+
if (cached !== undefined) {
|
|
120
|
+
if ('error' in cached)
|
|
121
|
+
throw cached.error;
|
|
122
|
+
return cached.value;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const value = context.assignment[id];
|
|
126
|
+
if (value === undefined)
|
|
127
|
+
throw new TypeError(`assignment omits ${JSON.stringify(id)}`);
|
|
128
|
+
const evaluated = assigned(value, context.budget);
|
|
129
|
+
const variable = context.variables.get(id);
|
|
130
|
+
const kind = variable.sort === 'int' || variable.sort === 'real' ? 'number' : variable.sort;
|
|
131
|
+
if (evaluated.kind !== kind)
|
|
132
|
+
throw new TypeError('assignment kind must match the declared variable');
|
|
133
|
+
if (variable.sort === 'int' && evaluated.kind === 'number' && evaluated.denominator !== 1n) {
|
|
134
|
+
throw new TypeError('integer variable assignment must be integral');
|
|
135
|
+
}
|
|
136
|
+
if (evaluated.kind === 'number' && (variable.sort === 'int' || variable.sort === 'real')) {
|
|
137
|
+
const min = variable.min === undefined ? undefined : variable.sort === 'int'
|
|
138
|
+
? number({ numerator: variable.min, denominator: '1' }, context.budget) : number(variable.min, context.budget);
|
|
139
|
+
const max = variable.max === undefined ? undefined : variable.sort === 'int'
|
|
140
|
+
? number({ numerator: variable.max, denominator: '1' }, context.budget) : number(variable.max, context.budget);
|
|
141
|
+
if ((min !== undefined && compare(evaluated, min, context.budget) < 0) || (max !== undefined && compare(evaluated, max, context.budget) > 0)) {
|
|
142
|
+
throw new TypeError('numeric assignment must lie within the declared bounds');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (variable.sort === 'enum' && (evaluated.kind !== 'enum' || evaluated.domain !== variable.domain || !context.enumDomains.get(variable.domain).has(evaluated.value))) {
|
|
146
|
+
throw new TypeError('enum assignment must belong to the declared domain');
|
|
147
|
+
}
|
|
148
|
+
context.assigned.set(id, { value: evaluated });
|
|
149
|
+
return evaluated;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
context.assigned.set(id, { error });
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
function* evaluateNode(expression, context) {
|
|
157
|
+
const signOf = (value) => constantSign(value, context.signs, literal => {
|
|
158
|
+
if (literal.kind !== 'literal' || (literal.sort !== 'int' && literal.sort !== 'real'))
|
|
159
|
+
throw new TypeError('expected a numeric literal');
|
|
160
|
+
const input = literal.sort === 'int' ? { numerator: literal.value, denominator: '1' } : literal.value;
|
|
161
|
+
context.budget.input(input);
|
|
162
|
+
return Exact.rational(input);
|
|
163
|
+
});
|
|
164
|
+
if (expression.kind === 'eq' || expression.kind === 'neq' || expression.kind === 'lt' ||
|
|
165
|
+
expression.kind === 'lte' || expression.kind === 'gt' || expression.kind === 'gte') {
|
|
166
|
+
const zero = (value) => value.kind === 'literal' &&
|
|
167
|
+
(value.sort === 'int' || value.sort === 'real') && signOf(value) === 0;
|
|
168
|
+
// A proof reads only captured constants. Unknown signs (including undefined
|
|
169
|
+
// arithmetic or any variable) retain ordinary assignment/error evaluation.
|
|
170
|
+
const sign = zero(expression.right) ? signOf(expression.left)
|
|
171
|
+
: zero(expression.left) ? signOf(expression.right) : undefined;
|
|
172
|
+
if (sign !== undefined) {
|
|
173
|
+
const order = zero(expression.right) ? sign : -sign;
|
|
174
|
+
return { kind: 'bool', value: expression.kind === 'eq' ? order === 0
|
|
175
|
+
: expression.kind === 'neq' ? order !== 0 : expression.kind === 'lt' ? order < 0
|
|
176
|
+
: expression.kind === 'lte' ? order <= 0 : expression.kind === 'gt' ? order > 0 : order >= 0 };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
52
179
|
switch (expression.kind) {
|
|
53
180
|
case 'literal':
|
|
54
181
|
switch (expression.sort) {
|
|
55
182
|
case 'bool': return { kind: 'bool', value: expression.value };
|
|
56
|
-
case 'int': return number({ numerator:
|
|
57
|
-
case 'real': return number(expression.value);
|
|
183
|
+
case 'int': return number({ numerator: expression.value, denominator: '1' }, context.budget);
|
|
184
|
+
case 'real': return number(expression.value, context.budget);
|
|
58
185
|
case 'enum': return { kind: 'enum', domain: expression.domain, value: expression.value };
|
|
59
186
|
}
|
|
60
|
-
case 'variable':
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
187
|
+
case 'variable': return evaluateAssignment(expression.id, context);
|
|
188
|
+
case 'not': return { kind: 'bool', value: !bool((yield expression.value)) };
|
|
189
|
+
case 'and':
|
|
190
|
+
for (const value of expression.operands)
|
|
191
|
+
if (!bool(yield value))
|
|
192
|
+
return { kind: 'bool', value: false };
|
|
193
|
+
return { kind: 'bool', value: true };
|
|
194
|
+
case 'or':
|
|
195
|
+
for (const value of expression.operands)
|
|
196
|
+
if (bool(yield value))
|
|
197
|
+
return { kind: 'bool', value: true };
|
|
198
|
+
return { kind: 'bool', value: false };
|
|
199
|
+
case 'implies': return { kind: 'bool', value: !bool((yield expression.left)) || bool((yield expression.right)) };
|
|
200
|
+
case 'eq': return { kind: 'bool', value: equal((yield expression.left), (yield expression.right)) };
|
|
201
|
+
case 'neq': return { kind: 'bool', value: !equal((yield expression.left), (yield expression.right)) };
|
|
202
|
+
case 'lt': return { kind: 'bool', value: compare(numeric((yield expression.left)), numeric((yield expression.right)), context.budget) < 0 };
|
|
203
|
+
case 'lte': return { kind: 'bool', value: compare(numeric((yield expression.left)), numeric((yield expression.right)), context.budget) <= 0 };
|
|
204
|
+
case 'gt': return { kind: 'bool', value: compare(numeric((yield expression.left)), numeric((yield expression.right)), context.budget) > 0 };
|
|
205
|
+
case 'gte': return { kind: 'bool', value: compare(numeric((yield expression.left)), numeric((yield expression.right)), context.budget) >= 0 };
|
|
206
|
+
case 'add':
|
|
207
|
+
case 'multiply': {
|
|
208
|
+
const values = [];
|
|
209
|
+
for (const value of expression.operands)
|
|
210
|
+
values.push(yield value);
|
|
211
|
+
const numbers = values.map(numeric);
|
|
212
|
+
// Evaluate and type-check every operand before applying the zero identity:
|
|
213
|
+
// zero must not hide undefined arithmetic in another operand.
|
|
214
|
+
if (expression.kind === 'multiply' && numbers.some(value => value.numerator === 0n)) {
|
|
215
|
+
return normalize(0n, 1n);
|
|
216
|
+
}
|
|
217
|
+
return balancedReduce(numbers, (left, right) => expression.kind === 'add'
|
|
218
|
+
? add(left, right, context.budget) : multiply(left, right, context.budget));
|
|
65
219
|
}
|
|
66
|
-
case 'not': return { kind: 'bool', value: !bool(run(expression.value)) };
|
|
67
|
-
case 'and': return { kind: 'bool', value: expression.operands.every(value => bool(run(value))) };
|
|
68
|
-
case 'or': return { kind: 'bool', value: expression.operands.some(value => bool(run(value))) };
|
|
69
|
-
case 'implies': return { kind: 'bool', value: !bool(run(expression.left)) || bool(run(expression.right)) };
|
|
70
|
-
case 'eq': return { kind: 'bool', value: equal(run(expression.left), run(expression.right)) };
|
|
71
|
-
case 'neq': return { kind: 'bool', value: !equal(run(expression.left), run(expression.right)) };
|
|
72
|
-
case 'lt': return { kind: 'bool', value: compare(numeric(run(expression.left)), numeric(run(expression.right))) < 0 };
|
|
73
|
-
case 'lte': return { kind: 'bool', value: compare(numeric(run(expression.left)), numeric(run(expression.right))) <= 0 };
|
|
74
|
-
case 'gt': return { kind: 'bool', value: compare(numeric(run(expression.left)), numeric(run(expression.right))) > 0 };
|
|
75
|
-
case 'gte': return { kind: 'bool', value: compare(numeric(run(expression.left)), numeric(run(expression.right))) >= 0 };
|
|
76
|
-
case 'add': return expression.operands.map(run).map(numeric).reduce(add);
|
|
77
|
-
case 'multiply': return expression.operands.map(run).map(numeric).reduce(multiply);
|
|
78
220
|
case 'subtract': {
|
|
79
|
-
const left = numeric(
|
|
80
|
-
const right = numeric(
|
|
81
|
-
return add(left,
|
|
221
|
+
const left = numeric((yield expression.left));
|
|
222
|
+
const right = numeric((yield expression.right));
|
|
223
|
+
return add(left, negate(right), context.budget);
|
|
82
224
|
}
|
|
83
225
|
case 'divide': {
|
|
84
|
-
const left = numeric(
|
|
85
|
-
const right = numeric(
|
|
86
|
-
|
|
226
|
+
const left = numeric((yield expression.left));
|
|
227
|
+
const right = numeric((yield expression.right));
|
|
228
|
+
if (right.numerator === 0n)
|
|
229
|
+
throw new TypeError('cannot explain division by zero');
|
|
230
|
+
context.budget.check(context.budget.product(left.numerator, right.denominator), context.budget.product(left.denominator, right.numerator));
|
|
231
|
+
// Cross-cancellation preserves reduction; only denominator sign can change.
|
|
232
|
+
const [numerator, denominator] = product(left.numerator, left.denominator, right.denominator, right.numerator);
|
|
233
|
+
return denominator < 0n
|
|
234
|
+
? { kind: 'number', numerator: -numerator, denominator: -denominator }
|
|
235
|
+
: { kind: 'number', numerator, denominator };
|
|
87
236
|
}
|
|
88
237
|
case 'negate': {
|
|
89
|
-
const value = numeric(
|
|
90
|
-
return
|
|
238
|
+
const value = numeric((yield expression.value));
|
|
239
|
+
return negate(value);
|
|
240
|
+
}
|
|
241
|
+
case 'if': return bool((yield expression.condition)) ? (yield expression.then) : (yield expression.else);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const evaluate = (expression, context) => {
|
|
245
|
+
const stack = [evaluateNode(expression, context)];
|
|
246
|
+
let step = stack[0].next();
|
|
247
|
+
while (true) {
|
|
248
|
+
if (step.done) {
|
|
249
|
+
stack.pop();
|
|
250
|
+
if (stack.length === 0)
|
|
251
|
+
return step.value;
|
|
252
|
+
step = stack[stack.length - 1].next(step.value);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
const child = evaluateNode(step.value, context);
|
|
256
|
+
stack.push(child);
|
|
257
|
+
step = child.next();
|
|
91
258
|
}
|
|
92
|
-
case 'if': return bool(run(expression.condition)) ? run(expression.then) : run(expression.else);
|
|
93
259
|
}
|
|
94
260
|
};
|
|
95
261
|
const sorted = (values) => [...new Set(values ?? [])].sort();
|
|
@@ -100,26 +266,45 @@ const element = (value) => ({
|
|
|
100
266
|
evidenceRowIds: sorted(value.evidenceRowIds),
|
|
101
267
|
scenarioInputIds: sorted(value.scenarioInputIds)
|
|
102
268
|
});
|
|
103
|
-
const
|
|
104
|
-
|
|
269
|
+
const evaluation = (expression, context) => {
|
|
270
|
+
const repeated = context.expressionResults.get(expression);
|
|
271
|
+
if (repeated !== undefined)
|
|
272
|
+
return repeated;
|
|
273
|
+
const key = expressionKey(expression, context.expressionStrings);
|
|
274
|
+
const cached = context.evaluations.get(key);
|
|
275
|
+
if (cached !== undefined) {
|
|
276
|
+
context.expressionResults.set(expression, cached);
|
|
277
|
+
return cached;
|
|
278
|
+
}
|
|
279
|
+
let result;
|
|
105
280
|
try {
|
|
106
|
-
|
|
281
|
+
result = { value: bool(evaluate(expression, context)) };
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
// Local arithmetic/assignment failure does not reclassify the backend result.
|
|
285
|
+
result = { evaluationReason: error instanceof Error ? error.message : 'constraint evaluation failed' };
|
|
107
286
|
}
|
|
108
|
-
|
|
109
|
-
|
|
287
|
+
context.evaluations.set(key, result);
|
|
288
|
+
context.expressionResults.set(expression, result);
|
|
289
|
+
return result;
|
|
290
|
+
};
|
|
291
|
+
const constraint = (value, context) => {
|
|
292
|
+
const result = evaluation(value.expression, context);
|
|
293
|
+
return {
|
|
294
|
+
...element(value),
|
|
295
|
+
evaluation: result.value === undefined ? 'indeterminate' : result.value ? 'satisfied' : 'violated',
|
|
296
|
+
...(result.evaluationReason === undefined ? {} : { evaluationReason: result.evaluationReason })
|
|
297
|
+
};
|
|
298
|
+
};
|
|
299
|
+
const softConstraint = (value, context) => {
|
|
300
|
+
const result = evaluation(value.expression, context);
|
|
301
|
+
return {
|
|
302
|
+
...element(value),
|
|
303
|
+
evaluation: result.value === undefined ? 'indeterminate' : result.value ? 'accepted' : 'violated',
|
|
304
|
+
...(result.evaluationReason === undefined ? {} : { evaluationReason: result.evaluationReason }),
|
|
305
|
+
weight: Exact.rational(value.weight)
|
|
306
|
+
};
|
|
110
307
|
};
|
|
111
|
-
const softConstraint = (value, assignment) => ({
|
|
112
|
-
...element(value),
|
|
113
|
-
evaluation: (() => {
|
|
114
|
-
try {
|
|
115
|
-
return bool(evaluate(value.expression, assignment)) ? 'accepted' : 'violated';
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return 'indeterminate';
|
|
119
|
-
}
|
|
120
|
-
})(),
|
|
121
|
-
weight: Exact.rational(value.weight)
|
|
122
|
-
});
|
|
123
308
|
const assignments = (model, assignment) => {
|
|
124
309
|
const variables = new Map(model.variables.map(variable => [variable.id, variable]));
|
|
125
310
|
return Object.entries(assignment)
|
|
@@ -129,11 +314,19 @@ const assignments = (model, assignment) => {
|
|
|
129
314
|
return { ...element(variable ?? { id }), declared: variable !== undefined, value };
|
|
130
315
|
});
|
|
131
316
|
};
|
|
132
|
-
const feasible = (model, assignment) =>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
317
|
+
const feasible = (model, assignment, limits) => {
|
|
318
|
+
// The report owns a captured assignment; normalized values live only for this report.
|
|
319
|
+
const context = {
|
|
320
|
+
assignment, budget: new ExplanationBudget(limits.maxExplanationBits, limits.maxExplanationWork), assigned: new Map(), signs: new WeakMap(), expressionResults: new WeakMap(), evaluations: new Map(), expressionStrings: new Map(),
|
|
321
|
+
variables: new Map(model.variables.map(variable => [variable.id, variable])),
|
|
322
|
+
enumDomains: new Map((model.enums ?? []).map(domain => [domain.id, new Set(domain.values)]))
|
|
323
|
+
};
|
|
324
|
+
return {
|
|
325
|
+
assignments: assignments(model, assignment),
|
|
326
|
+
hardConstraints: model.constraints.map(value => constraint(value, context)),
|
|
327
|
+
softConstraints: (model.softConstraints ?? []).map(value => softConstraint(value, context))
|
|
328
|
+
};
|
|
329
|
+
};
|
|
137
330
|
const objective = (declaration, result) => ({
|
|
138
331
|
...element(declaration ?? { id: result.objectiveId }),
|
|
139
332
|
declared: declaration !== undefined,
|
|
@@ -141,14 +334,14 @@ const objective = (declaration, result) => ({
|
|
|
141
334
|
value: result.value,
|
|
142
335
|
...(result.bound === undefined ? {} : { bound: result.bound })
|
|
143
336
|
});
|
|
144
|
-
const outcome = (model, result) => {
|
|
337
|
+
const outcome = (model, result, limits) => {
|
|
145
338
|
switch (result.status) {
|
|
146
|
-
case 'satisfied': return { status: result.status, ...feasible(model, result.assignment) };
|
|
339
|
+
case 'satisfied': return { status: result.status, ...feasible(model, result.assignment, limits) };
|
|
147
340
|
case 'optimal': {
|
|
148
341
|
const declarations = new Map((model.objectives ?? []).map(value => [value.id, value]));
|
|
149
342
|
return {
|
|
150
343
|
status: result.status,
|
|
151
|
-
...feasible(model, result.assignment),
|
|
344
|
+
...feasible(model, result.assignment, limits),
|
|
152
345
|
objectives: result.objectives.map(value => objective(declarations.get(value.objectiveId), value)),
|
|
153
346
|
optimalityProved: result.optimalityProved
|
|
154
347
|
};
|
|
@@ -172,10 +365,22 @@ const outcome = (model, result) => {
|
|
|
172
365
|
};
|
|
173
366
|
/** Build stable JSON data without mutating the model, scenario, or CAVE store. */
|
|
174
367
|
export const report = (model, result, limits, context = {}) => {
|
|
175
|
-
|
|
368
|
+
limits = mergeLimits(limits);
|
|
369
|
+
model = clone(model);
|
|
370
|
+
result = captureResult(result);
|
|
371
|
+
validateContext(context);
|
|
372
|
+
context = clone({
|
|
373
|
+
modelDigest: context.modelDigest,
|
|
374
|
+
scenario: context.scenario,
|
|
375
|
+
snapshot: context.snapshot,
|
|
376
|
+
inputs: context.inputs,
|
|
377
|
+
});
|
|
378
|
+
validateContext(context);
|
|
379
|
+
const modelDigest = digestOwned(model, limits);
|
|
176
380
|
if (context.modelDigest !== undefined && context.modelDigest !== modelDigest) {
|
|
177
381
|
throw new TypeError(`scenario model digest ${context.modelDigest} does not match ${modelDigest}`);
|
|
178
382
|
}
|
|
383
|
+
// All referenced data is owned by the captures above or constructed in this call.
|
|
179
384
|
return {
|
|
180
385
|
schema,
|
|
181
386
|
run: {
|
|
@@ -188,67 +393,101 @@ export const report = (model, result, limits, context = {}) => {
|
|
|
188
393
|
...(context.snapshot === undefined ? {} : { snapshot: context.snapshot }),
|
|
189
394
|
inputs: [...(context.inputs ?? [])].sort((left, right) => compareText(left.id, right.id))
|
|
190
395
|
},
|
|
191
|
-
outcome: outcome(model, result)
|
|
396
|
+
outcome: outcome(model, result, limits)
|
|
192
397
|
};
|
|
193
398
|
};
|
|
399
|
+
const sourceLocation = (declaration) => {
|
|
400
|
+
if (declaration.line !== undefined) {
|
|
401
|
+
return `${inlineText(declaration.uri)}:${declaration.line}${declaration.column === undefined ? '' : `:${declaration.column}`}`;
|
|
402
|
+
}
|
|
403
|
+
return `${inlineText(declaration.uri)}${declaration.column === undefined ? '' : ` (column ${declaration.column})`}`;
|
|
404
|
+
};
|
|
194
405
|
const evidence = (value) => {
|
|
195
406
|
const parts = [
|
|
196
|
-
value.declaration === undefined ? undefined :
|
|
197
|
-
value.evidenceRowIds.length === 0 ? undefined : `rows ${value.evidenceRowIds.join(', ')}`,
|
|
198
|
-
value.scenarioInputIds.length === 0 ? undefined : `inputs ${value.scenarioInputIds.join(', ')}`
|
|
407
|
+
value.declaration === undefined ? undefined : sourceLocation(value.declaration),
|
|
408
|
+
value.evidenceRowIds.length === 0 ? undefined : `rows ${value.evidenceRowIds.map(inlineText).join(', ')}`,
|
|
409
|
+
value.scenarioInputIds.length === 0 ? undefined : `inputs ${value.scenarioInputIds.map(inlineText).join(', ')}`
|
|
199
410
|
].filter((part) => part !== undefined);
|
|
200
411
|
return parts.length === 0 ? '' : ` — ${parts.join('; ')}`;
|
|
201
412
|
};
|
|
202
413
|
const valueText = (value) => {
|
|
414
|
+
const invalid = '(invalid backend value)';
|
|
415
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
416
|
+
return invalid;
|
|
203
417
|
switch (value.sort) {
|
|
204
|
-
case 'bool': return String(value.value);
|
|
205
|
-
case 'int': return value.value;
|
|
206
|
-
case 'real': return
|
|
207
|
-
|
|
418
|
+
case 'bool': return typeof value.value === 'boolean' ? String(value.value) : invalid;
|
|
419
|
+
case 'int': return typeof value.value === 'string' ? inlineText(value.value) : invalid;
|
|
420
|
+
case 'real': return typeof value.numerator === 'string' && typeof value.denominator === 'string'
|
|
421
|
+
? inlineText(`${value.numerator}/${value.denominator}`) : invalid;
|
|
422
|
+
case 'enum': return typeof value.domain === 'string' && typeof value.value === 'string' ? inlineText(value.value) : invalid;
|
|
423
|
+
default: return invalid;
|
|
208
424
|
}
|
|
209
425
|
};
|
|
426
|
+
const jsonControlEscapes = new Map([...Array.from({ length: 33 }, (_, index) => index + 0x7f), 0x2028, 0x2029]
|
|
427
|
+
.map(code => [String.fromCharCode(code), `\\u${code.toString(16).padStart(4, '0')}`]));
|
|
428
|
+
const escapeJsonControls = (text) => text.replace(/[\u007f-\u009f\u2028\u2029]/g, character => jsonControlEscapes.get(character));
|
|
429
|
+
const quotedText = (message) => escapeJsonControls(JSON.stringify(message));
|
|
430
|
+
const inlineText = (message) => /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/.test(message) ? quotedText(message) : message;
|
|
210
431
|
/** Render the same report as concise, deterministic plain text. */
|
|
211
432
|
export const render = (value) => {
|
|
212
433
|
const lines = [
|
|
213
434
|
`Solver result: ${value.outcome.status}`,
|
|
214
|
-
`Model: ${value.run.modelDigest}`,
|
|
215
|
-
`Backend: ${value.run.backend.name} ${value.run.backend.version}`,
|
|
435
|
+
`Model: ${inlineText(value.run.modelDigest)}`,
|
|
436
|
+
`Backend: ${inlineText(value.run.backend.name)} ${inlineText(value.run.backend.version)}`,
|
|
216
437
|
`Elapsed: ${value.run.elapsedMs} ms`
|
|
217
438
|
];
|
|
218
439
|
if (value.run.snapshot !== undefined) {
|
|
219
|
-
|
|
440
|
+
const snapshot = value.run.snapshot;
|
|
441
|
+
const policies = [];
|
|
442
|
+
if (snapshot.aliases !== undefined)
|
|
443
|
+
policies.push(`aliases ${snapshot.aliases}`);
|
|
444
|
+
if (snapshot.resolution !== undefined)
|
|
445
|
+
policies.push(`resolution ${snapshot.resolution}`);
|
|
446
|
+
if (snapshot.minimumConfidence !== undefined)
|
|
447
|
+
policies.push(`minimum confidence ${snapshot.minimumConfidence}`);
|
|
448
|
+
lines.push(`Snapshot: transaction ${inlineText(snapshot.transactionTime ?? 'empty')}${snapshot.validTime === undefined ? '' : `, valid ${inlineText(snapshot.validTime)}`}${policies.length === 0 ? '' : `; ${policies.join('; ')}`}`);
|
|
220
449
|
}
|
|
221
450
|
if (value.run.scenario !== undefined)
|
|
222
|
-
lines.push(`Scenario: ${value.run.scenario.id} (${value.run.scenario.inputDigest})`);
|
|
451
|
+
lines.push(`Scenario: ${inlineText(value.run.scenario.id)} (input ${inlineText(value.run.scenario.inputDigest)}; overlay ${inlineText(value.run.scenario.overlayDigest)})`);
|
|
223
452
|
for (const input of value.run.inputs) {
|
|
224
|
-
|
|
453
|
+
const values = [];
|
|
454
|
+
if (input.value !== undefined)
|
|
455
|
+
values.push(escapeJsonControls(stringifyInput(input.value)));
|
|
456
|
+
if (input.authoredValue !== undefined)
|
|
457
|
+
values.push(`authored ${escapeJsonControls(stringifyInput(input.authoredValue))}`);
|
|
458
|
+
const sources = [];
|
|
459
|
+
if (input.evidenceRowIds.length > 0)
|
|
460
|
+
sources.push(`rows ${sorted(input.evidenceRowIds).map(inlineText).join(', ')}`);
|
|
461
|
+
if (input.scenarioClaimIds.length > 0)
|
|
462
|
+
sources.push(`scenario claims ${sorted(input.scenarioClaimIds).map(inlineText).join(', ')}`);
|
|
463
|
+
lines.push(`Input ${inlineText(input.id)}: ${values.length === 0 ? '(no value)' : values.join('; ')}${input.query === undefined ? '' : ` via ${inlineText(input.query)}`}${sources.length === 0 ? '' : ` — ${sources.join('; ')}`}`);
|
|
225
464
|
}
|
|
226
465
|
switch (value.outcome.status) {
|
|
227
466
|
case 'satisfied':
|
|
228
467
|
case 'optimal':
|
|
229
468
|
for (const assignment of value.outcome.assignments) {
|
|
230
|
-
lines.push(`Assignment ${assignment.id} = ${valueText(assignment.value)}${assignment.declared ? '' : ' (undeclared backend ID)'}${evidence(assignment)}`);
|
|
469
|
+
lines.push(`Assignment ${inlineText(assignment.id)} = ${valueText(assignment.value)}${assignment.declared ? '' : ' (undeclared backend ID)'}${evidence(assignment)}`);
|
|
231
470
|
}
|
|
232
471
|
for (const constraint of value.outcome.hardConstraints) {
|
|
233
|
-
lines.push(`Hard constraint ${constraint.id}: ${constraint.evaluation}${evidence(constraint)}`);
|
|
472
|
+
lines.push(`Hard constraint ${inlineText(constraint.id)}: ${constraint.evaluation}${constraint.evaluationReason === undefined ? '' : ` (${quotedText(constraint.evaluationReason)})`}${evidence(constraint)}`);
|
|
234
473
|
}
|
|
235
474
|
for (const constraint of value.outcome.softConstraints) {
|
|
236
|
-
lines.push(`Soft constraint ${constraint.id}: ${constraint.evaluation}, weight ${constraint.weight.numerator}/${constraint.weight.denominator}${evidence(constraint)}`);
|
|
475
|
+
lines.push(`Soft constraint ${inlineText(constraint.id)}: ${constraint.evaluation}${constraint.evaluationReason === undefined ? '' : ` (${quotedText(constraint.evaluationReason)})`}, weight ${constraint.weight.numerator}/${constraint.weight.denominator}${evidence(constraint)}`);
|
|
237
476
|
}
|
|
238
477
|
if (value.outcome.status === 'optimal') {
|
|
239
478
|
for (const objective of value.outcome.objectives) {
|
|
240
|
-
lines.push(`Objective ${objective.id} (${objective.direction}) = ${valueText(objective.value)}${objective.declared ? '' : ' (undeclared backend ID)'}${evidence(objective)}`);
|
|
479
|
+
lines.push(`Objective ${inlineText(objective.id)} (${objective.direction}) = ${valueText(objective.value)}${objective.declared ? '' : ' (undeclared backend ID)'}${evidence(objective)}`);
|
|
241
480
|
}
|
|
242
481
|
}
|
|
243
482
|
break;
|
|
244
483
|
case 'unsatisfied':
|
|
245
484
|
lines.push('Infeasibility proved. Unsatisfiable cores are not necessarily minimal.');
|
|
246
485
|
for (const constraint of value.outcome.core ?? []) {
|
|
247
|
-
lines.push(`Core constraint ${constraint.id}${constraint.declared ? '' : ' (undeclared backend ID)'}${evidence(constraint)}`);
|
|
486
|
+
lines.push(`Core constraint ${inlineText(constraint.id)}${constraint.declared ? '' : ' (undeclared backend ID)'}${evidence(constraint)}`);
|
|
248
487
|
}
|
|
249
488
|
break;
|
|
250
489
|
case 'unknown':
|
|
251
|
-
lines.push(`Unknown: ${value.outcome.reason.kind} — ${value.outcome.reason.message}`);
|
|
490
|
+
lines.push(`Unknown: ${value.outcome.reason.kind} — ${inlineText(value.outcome.reason.message)}`);
|
|
252
491
|
break;
|
|
253
492
|
}
|
|
254
493
|
return `${lines.join('\n')}\n`;
|