gdp-wired 0.19.0 → 0.21.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 +37 -6
- package/dist/core/cycles.d.ts +35 -2
- package/dist/core/cycles.js +458 -51
- package/dist/core/cyclic.d.ts +18 -4
- package/dist/core/cyclic.js +319 -263
- package/dist/core/data.d.ts +196 -15
- package/dist/core/data.js +1284 -213
- package/dist/core/discover.js +54 -33
- package/dist/core/dsl.d.ts +31 -3
- package/dist/core/dsl.js +168 -38
- package/dist/core/exists.d.ts +26 -0
- package/dist/core/exists.js +625 -0
- package/dist/core/infer.d.ts +13 -0
- package/dist/core/infer.js +258 -41
- package/dist/core/machine.d.ts +27 -0
- package/dist/core/machine.js +45 -0
- package/dist/core/solver.d.ts +3 -0
- package/dist/core/solver.js +5 -0
- package/dist/core/transfer.d.ts +17 -0
- package/dist/core/transfer.js +468 -0
- package/dist/core/translate.js +10 -4
- package/dist/core/watch.js +5 -1
- package/dist/kernel/prove.d.ts +8 -1
- package/dist/kernel/prove.js +106 -55
- package/dist/lean-kernel/emit.d.ts +4 -1
- package/dist/lean-kernel/emit.js +336 -69
- package/dist/lean-kernel/library.js +2 -2
- package/package.json +2 -2
package/dist/core/infer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Invariants nobody wrote: a law that holds but isn't inductive, proven with laws the search finds. Nothing here is
|
|
2
2
|
// trusted; what it finds is proven by the kernel with the rule's own laws, as laws of the same rule.
|
|
3
|
-
// - runs: random runs from the start (params and requests by their kinds, the assumption met) give reachable states
|
|
3
|
+
// - runs: random runs from the start (params and requests by their kinds, the assumption met) give reachable states,
|
|
4
|
+
// 3,000 kept as samples
|
|
4
5
|
// - candidates: linear forms Σ c·v ≤ d over the state and the params (an integer as its value, its two naturals
|
|
5
6
|
// together), up to three terms with small coefficients and the rule's own constants, each d the largest value the
|
|
6
7
|
// runs reached (and the rule's constants just above it); the same forms where a yes/no, a choice or a small natural
|
|
@@ -14,11 +15,23 @@
|
|
|
14
15
|
// added; a chosen candidate that nothing supports, or that makes a proof slow, is dropped; the set proven as a whole
|
|
15
16
|
// is the answer, after each chosen candidate the proof doesn't need is taken out again
|
|
16
17
|
import { quietly } from "./cyclic.js";
|
|
17
|
-
import { any, atMost, evalC, get, interpret, n, not, plus, same, times, x } from "./machine.js";
|
|
18
|
+
import { any, atMost, divisorsIn, evalC, get, interpret, machineTerms, modName, n, not, plus, same, times, withArithmetic, x } from "./machine.js";
|
|
18
19
|
import { proveRule, proveSteps } from "./dsl.js";
|
|
19
20
|
import { allowed, watch } from "./watch.js";
|
|
20
21
|
import { proveLaw, proveLaws, proveStepLaws, withDeadline } from "../kernel/prove.js";
|
|
21
|
-
|
|
22
|
+
import { work } from "./solver.js";
|
|
23
|
+
const KEY = 'key$found', AT = '@', K = '#', SOME = 'k$found';
|
|
24
|
+
// The counted clock's rates, units a millisecond: about the slowest tenth of what Modal's 1-vCPU containers do in each
|
|
25
|
+
// phase (`runs/modal/infer/after2`, GDP_INFER_TRACE=1: requests tried 85 a ms, rows 13,093, forms evaluated 11,240, the
|
|
26
|
+
// kernel's work 37; laws watched 198 by a fit), so a counted budget runs out no later than a timed one would there.
|
|
27
|
+
// The runs' requests: 16,000, 400 trials of a rule whose every request is allowed. Every search of the suite's and the
|
|
28
|
+
// frontier corpus's then keeps 3,000 samples, at least as many as 500 ms of wall time gave on 2 vCPU (`runs/modal/infer/
|
|
29
|
+
// before`'s cal2 jobs: 29 to 11,625 trials, 745 to 3,000 samples). Of 100 to 3,000 trials (`runs/modal/infer/grid`),
|
|
30
|
+
// 100 lost a corpus item (metered blocks) and 200 to 3,000 none: 400 is a step above the least that lost none.
|
|
31
|
+
export const inferRates = { tries: 100, rows: 10_000, evals: 10_000, watched: 100, work: 40 };
|
|
32
|
+
export const inferRuns = 16_000;
|
|
33
|
+
// GDP_INFER_TRACE=1: each phase's wall time beside what it counted, to stderr (the calibration's numbers).
|
|
34
|
+
const inferTrace = !!globalThis.process?.env?.GDP_INFER_TRACE;
|
|
22
35
|
const seeded = (seed) => { let s = seed; return k => { s = (s * 1103515245 + 12345) % 2 ** 31; return Math.floor(s / 2 ** 31 * k); }; };
|
|
23
36
|
const isRule = (t) => typeof t.kindsOfState === 'function';
|
|
24
37
|
const gcd = (a, b) => (b === 0 ? Math.abs(a) : gcd(b, a % b));
|
|
@@ -110,17 +123,54 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
110
123
|
return quietly(() => withDeadline(performance.now() + (opts.ms ?? 20000), () => inferLawsSearch(target, laws, stepLaws, opts)));
|
|
111
124
|
}
|
|
112
125
|
function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
113
|
-
const t0 = performance.now(),
|
|
126
|
+
const t0 = performance.now(), ms = opts.ms ?? 20000, wallUntil = t0 + ms, rule = isRule(target) ? target : undefined;
|
|
127
|
+
// The clock the budgets read (counted milliseconds since the start, or wall time where `timed`), and the stop.
|
|
128
|
+
const timed = !!opts.timed, w0 = work.n, used = { tries: 0, rows: 0, evals: 0, watched: 0 };
|
|
129
|
+
const now = () => (timed ? performance.now() - t0
|
|
130
|
+
: used.tries / inferRates.tries + used.rows / inferRates.rows + used.evals / inferRates.evals + used.watched / inferRates.watched + (work.n - w0) / inferRates.work);
|
|
131
|
+
let stopped = false;
|
|
132
|
+
const stop = () => stopped || (stopped = performance.now() > wallUntil);
|
|
133
|
+
const until = ms;
|
|
134
|
+
let tp = performance.now(), cp = 0, wp = w0;
|
|
135
|
+
const trace = (what, o = {}) => {
|
|
136
|
+
if (!inferTrace)
|
|
137
|
+
return;
|
|
138
|
+
const t = performance.now(), c = now();
|
|
139
|
+
globalThis.process.stderr.write(`[infer] ${what} ${JSON.stringify({ ms: Math.round(t - tp), counted: Math.round(c - cp), work: work.n - wp, ...used, ...o })}\n`);
|
|
140
|
+
tp = t;
|
|
141
|
+
cp = c;
|
|
142
|
+
wp = work.n;
|
|
143
|
+
};
|
|
144
|
+
if (rule) {
|
|
145
|
+
const ks = divisorsIn([...laws, ...stepLaws]);
|
|
146
|
+
if (ks.size)
|
|
147
|
+
rule.dividesBy(...ks);
|
|
148
|
+
}
|
|
114
149
|
const m = rule ? rule.machine : target;
|
|
115
150
|
let proofs = 0;
|
|
151
|
+
// The rule (or machine) the kernel proves the candidates with: once a remainder is among them, one that declares
|
|
152
|
+
// div$k and mod$k for its divisors (rule.withDivisors leaves the caller's rule as it was until a found law needs them).
|
|
153
|
+
// Only the laws proven read: a search none of whose chosen candidates is a remainder proves with the rule as it is.
|
|
154
|
+
const byDivisors = new Map();
|
|
155
|
+
const forLaws = (ls) => {
|
|
156
|
+
const ks = [...divisorsIn(ls.map(l => l.holds))].filter(k2 => !m.fns?.[modName(k2)]).sort((a, b) => a - b), key = ks.join(',');
|
|
157
|
+
let out = byDivisors.get(key);
|
|
158
|
+
if (!out) {
|
|
159
|
+
const r2 = ks.length ? rule?.withDivisors(...ks) : rule;
|
|
160
|
+
out = { r: r2, m: r2 ? r2.machine : withArithmetic(m, [], ks) };
|
|
161
|
+
byDivisors.set(key, out);
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
};
|
|
116
165
|
const prove = (ls) => {
|
|
117
166
|
proofs++;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
167
|
+
const { r: searchRule, m: mm } = forLaws(ls);
|
|
168
|
+
if (searchRule)
|
|
169
|
+
return stepLaws.length ? proveSteps(searchRule, ls, stepLaws, { search: false }) : proveRule(searchRule, ls, { search: false });
|
|
170
|
+
const out = stepLaws.length ? proveStepLaws(mm, ls, stepLaws) : proveLaws(mm, ls);
|
|
121
171
|
return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
|
|
122
172
|
};
|
|
123
|
-
const stats = (samples, candidates, alive) => ({ samples, candidates, alive, proofs, ms: Math.round(performance.now() - t0) });
|
|
173
|
+
const stats = (samples, candidates, alive) => ({ samples, candidates, alive, proofs, ms: Math.round(performance.now() - t0), stopped: stop() });
|
|
124
174
|
const first = opts.first ?? prove(laws);
|
|
125
175
|
if ((first.proven && first.checked) || first.run)
|
|
126
176
|
return { ...first, found: [], stats: stats(0, 0, 0) };
|
|
@@ -138,9 +188,19 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
138
188
|
for (const st of opts.seen ?? [])
|
|
139
189
|
if (!m.params.length)
|
|
140
190
|
keep({ params: {}, st });
|
|
191
|
+
trace('start');
|
|
141
192
|
const mapsText = (st) => JSON.stringify(Object.values(st.maps).map(c => [...c]));
|
|
142
|
-
|
|
143
|
-
|
|
193
|
+
// Counted: `runs` requests tried at least, and on until the samples are full (a trial keeps its start at least, so as
|
|
194
|
+
// many trials as they hold fill them), the trials at most `runs` or that many. Timed: `runsMs` of wall time.
|
|
195
|
+
const runsTimed = timed || opts.runsMs !== undefined, runsUntil = Math.min(wallUntil, performance.now() + (opts.runsMs ?? 500)), least = opts.runs ?? inferRuns;
|
|
196
|
+
const moreRuns = (trial) => (runsTimed ? performance.now() < runsUntil : trial < Math.max(cap, least) && (used.tries < least || samples.length < cap) && !stop());
|
|
197
|
+
let trials = 0;
|
|
198
|
+
// (an assumption with "there exists", a value the request brings, may admit no request at a small bound, a multiple
|
|
199
|
+
// of 5 but 0, say: a trial whose steps moved nothing doubles the bound at once)
|
|
200
|
+
const skolems = m.inputs.some(i => i.startsWith('some$a'));
|
|
201
|
+
for (let trial = 0, bound = 2, stuck = false; moreRuns(trial); trial++, bound = trial % 20 === 0 || stuck ? (bound >= 1024 ? 2 : bound * 2) : bound) {
|
|
202
|
+
stuck = false;
|
|
203
|
+
trials++;
|
|
144
204
|
const params = randomParams(bound);
|
|
145
205
|
if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
|
|
146
206
|
continue;
|
|
@@ -152,7 +212,9 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
152
212
|
continue;
|
|
153
213
|
}
|
|
154
214
|
keep({ params, st });
|
|
215
|
+
let took = 0;
|
|
155
216
|
for (let d = 0, tries = 0; d < 40 && tries < 200; tries++) {
|
|
217
|
+
used.tries++;
|
|
156
218
|
const input = randomInput(params, bound);
|
|
157
219
|
let next;
|
|
158
220
|
try {
|
|
@@ -164,12 +226,16 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
164
226
|
break;
|
|
165
227
|
}
|
|
166
228
|
d++;
|
|
167
|
-
if (JSON.stringify(next.scalars) !== JSON.stringify(st.scalars) || mapsText(next) !== mapsText(st))
|
|
229
|
+
if (JSON.stringify(next.scalars) !== JSON.stringify(st.scalars) || mapsText(next) !== mapsText(st)) {
|
|
168
230
|
keep({ params, st: next });
|
|
231
|
+
took++;
|
|
232
|
+
}
|
|
169
233
|
st = next;
|
|
170
234
|
}
|
|
235
|
+
stuck = skolems && took === 0;
|
|
171
236
|
}
|
|
172
|
-
opts.progress?.('runs', { samples: samples.length });
|
|
237
|
+
opts.progress?.('runs', { samples: samples.length, trials });
|
|
238
|
+
trace('runs', { samples: samples.length, trials });
|
|
173
239
|
// --- atoms and rows --------------------------------------------------------------------------------------------------
|
|
174
240
|
const scalars = Object.keys(m.state), stateSet = new Set(scalars), mapNames = Object.keys(m.maps ?? {}), mapSet = new Set(mapNames);
|
|
175
241
|
const atoms = [];
|
|
@@ -276,11 +342,12 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
276
342
|
// guard in one pass over (at most 1,500 of) the rows. Building candidates takes at most a quarter of the time.
|
|
277
343
|
const guards = [...modes].sort((p, q) => Number(!!q.choice) - Number(!!p.choice) || Number(!!q.bool) - Number(!!p.bool)).slice(0, 8);
|
|
278
344
|
const strideS = Math.max(1, Math.ceil(NS / 1500)), strideK = Math.max(1, Math.ceil(NK / 1500));
|
|
279
|
-
const buildUntil = Math.min(until,
|
|
345
|
+
const buildUntil = Math.min(until, now() + (until - now()) / 4);
|
|
280
346
|
for (const [n_, { coef, cells }] of shapes.entries()) {
|
|
281
|
-
if (forms.length > maxCandidates || (n_ % 256 === 0 &&
|
|
347
|
+
if (forms.length > maxCandidates || (n_ % 256 === 0 && (now() > buildUntil || stop())))
|
|
282
348
|
break;
|
|
283
349
|
const vals = cells ? valsK : valsS, N = cells ? NK : NS, rows = cells ? rowsK : rowsS;
|
|
350
|
+
used.rows += N + (coef.length > 2 ? 0 : Math.ceil(N / (cells ? strideK : strideS)));
|
|
284
351
|
let d = -Infinity;
|
|
285
352
|
for (let j = 0; j < N; j++)
|
|
286
353
|
d = Math.max(d, at(coef, vals, j));
|
|
@@ -313,7 +380,76 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
313
380
|
for (const value of mode.values)
|
|
314
381
|
if (NS && !valsS[mode.atom].includes(value))
|
|
315
382
|
forms.push({ coef: [], d: 0, cells: false, unreachable: { atom: mode.atom, value }, size: 1 });
|
|
383
|
+
// Remainders: a value, or the sum or difference of two, at one remainder by k in every run (or in every run where a
|
|
384
|
+
// guard holds), k each divisor the rule and its laws read (modBy, divBy) and 2: x mod 2 = 0 for a counter that steps
|
|
385
|
+
// by 2, the parity of a sum that two counters keep. No form above states one (no linear bound does).
|
|
386
|
+
// The moduli: the divisors the rule and its laws read, then 2, 3, 4 and the rule's own constants up to 64 (six in all).
|
|
387
|
+
const divisors = [...new Set([...[...divisorsIn([...machineTerms(m), ...laws.map(l => l.holds)])].filter(k => k >= 2 && k <= 1_000_000).sort((p, q) => p - q), 2, 3, 4, ...consts.filter(k => k > 4 && k <= 64)])].slice(0, 6);
|
|
388
|
+
const modAtoms = terms.filter(i => !atoms[i].param && !modeOf(i)?.bool), modShapes = modAtoms.map(i => [[i, 1]]);
|
|
389
|
+
if (modAtoms.length * (modAtoms.length - 1) <= 2000)
|
|
390
|
+
for (const [a, i] of modAtoms.entries())
|
|
391
|
+
for (const j of modAtoms.slice(a + 1))
|
|
392
|
+
modShapes.push([[i, 1], [j, 1]], [[i, 1], [j, -1]]);
|
|
393
|
+
const residue = (v, k) => ((v % k) + k) % k;
|
|
394
|
+
// A shape whose runs reached two values or fewer is a bound's (above), not a remainder's.
|
|
395
|
+
const varied = (coef) => { const vs = new Set(); for (let j = 0; j < NS && vs.size < 3; j += strideS) {
|
|
396
|
+
used.rows++;
|
|
397
|
+
vs.add(at(coef, valsS, j));
|
|
398
|
+
} return vs.size >= 3; };
|
|
399
|
+
for (const coef of NS && opts.remainders !== false ? modShapes.filter(varied) : [])
|
|
400
|
+
for (const k of divisors) {
|
|
401
|
+
if (forms.length > maxCandidates || now() > buildUntil || stop())
|
|
402
|
+
break;
|
|
403
|
+
const all = new Set(), byGuard = guards.map(() => new Map());
|
|
404
|
+
used.rows += Math.ceil(NS / strideS);
|
|
405
|
+
for (let j = 0; j < NS; j += strideS) {
|
|
406
|
+
const r = residue(at(coef, valsS, j), k);
|
|
407
|
+
all.add(r);
|
|
408
|
+
guards.forEach((mode, g) => { const v = valsS[mode.atom][j], b = byGuard[g]; if (!b.has(v))
|
|
409
|
+
b.set(v, new Set()); b.get(v).add(r); });
|
|
410
|
+
}
|
|
411
|
+
const size = sizeOf(coef) + 30 + k;
|
|
412
|
+
if (all.size === 1) {
|
|
413
|
+
forms.push({ coef, d: 0, cells: false, mod: { k, r: [...all][0] }, size });
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
guards.forEach((mode, g) => { if (coef.some(([i]) => i === mode.atom))
|
|
417
|
+
return; for (const [value, rs] of byGuard[g])
|
|
418
|
+
if (rs.size === 1 && mode.values.includes(value))
|
|
419
|
+
forms.push({ coef, d: 0, cells: false, guard: { atom: mode.atom, value }, mod: { k, r: [...rs][0] }, size: size + 50 }); });
|
|
420
|
+
}
|
|
421
|
+
// Congruences, where remainders are off: a state value (or the sum or difference of two) whose every sample leaves
|
|
422
|
+
// one remainder by a constant of the rule's (2 first), with at least three values reached: ∃k. v = c·k + r. The
|
|
423
|
+
// remainder above says the same without a quantifier, so the kernel proves it as it stands (no witness to choose),
|
|
424
|
+
// under a guard where only some runs keep it, and Lean states it with %: that is the form tried by default.
|
|
425
|
+
if (opts.remainders === false && opts.existentials !== false && NS) {
|
|
426
|
+
const mods = [...new Set([2, 3, 4, ...consts.filter(k => k > 4 && k <= 64)])].slice(0, 6);
|
|
427
|
+
const congOf = (coef) => {
|
|
428
|
+
const vs = new Set();
|
|
429
|
+
used.rows += NS;
|
|
430
|
+
for (let j = 0; j < NS; j++)
|
|
431
|
+
vs.add(at(coef, valsS, j));
|
|
432
|
+
if (vs.size < 3)
|
|
433
|
+
return;
|
|
434
|
+
const nat = coef.every(([i, c]) => !atoms[i].int && c > 0), list = [...vs];
|
|
435
|
+
for (const c of mods) {
|
|
436
|
+
const r = ((list[0] % c) + c) % c;
|
|
437
|
+
if (list.every(x => ((x % c) + c) % c === r) && (!nat || list.every(x => x >= r)))
|
|
438
|
+
forms.push({ coef, d: 0, cells: false, cong: { mod: c, rest: r }, size: sizeOf(coef) + 40 + c });
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
const st = plain.filter(i => !atoms[i].param && !modeOf(i));
|
|
442
|
+
for (const i of st)
|
|
443
|
+
congOf([[i, 1]]);
|
|
444
|
+
if (st.length <= 24)
|
|
445
|
+
for (const [a, i] of st.entries())
|
|
446
|
+
for (const j of st.slice(a + 1)) {
|
|
447
|
+
congOf([[i, 1], [j, 1]]);
|
|
448
|
+
congOf([[i, 1], [j, -1]]);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
316
451
|
opts.progress?.('candidates', { atoms: A, shapes: shapes.length, forms: forms.length });
|
|
452
|
+
trace('candidates', { forms: forms.length });
|
|
317
453
|
// --- how a form reads, and as a law ------------------------------------------------------------------------------------
|
|
318
454
|
const nameOf = (i) => atoms[i].name;
|
|
319
455
|
const shownValue = (i, value) => { const mo = modeOf(i); return mo?.choice ? (mo.choice[value] ?? String(value)) : mo?.bool ? (value === 1 ? 'yes' : 'no') : String(value); };
|
|
@@ -333,13 +469,18 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
333
469
|
const textOf = (f) => {
|
|
334
470
|
if (f.unreachable)
|
|
335
471
|
return `${nameOf(f.unreachable.atom)} is never ${shownValue(f.unreachable.atom, f.unreachable.value)}`;
|
|
472
|
+
if (f.cong)
|
|
473
|
+
return `∃k. ${f.coef.map(([i, c], j) => `${j ? (c > 0 ? ' + ' : ' − ') : c < 0 ? '−' : ''}${nameOf(i)}`).join('')} = ${f.cong.mod}·k${f.cong.rest ? ` + ${f.cong.rest}` : ''}`;
|
|
336
474
|
const { l, r } = sides(f), within = allowedValues(f);
|
|
337
|
-
const body = within ? `${nameOf(f.coef[0][0])} is ${within.map(v => shownValue(f.coef[0][0], v)).join(' or ')}` : `${l} <= ${r}`;
|
|
475
|
+
const body = f.mod ? `${f.coef.map(([i, c], j) => `${c < 0 ? (j ? ' − ' : '−') : j ? ' + ' : ''}${nameOf(i)}`).join('')} mod ${f.mod.k} = ${f.mod.r}` : within ? `${nameOf(f.coef[0][0])} is ${within.map(v => shownValue(f.coef[0][0], v)).join(' or ')}` : `${l} <= ${r}`;
|
|
338
476
|
const under = f.when ? (({ l: wl, r: wr }) => `${wl} <= ${wr} → `)(sides({ coef: f.when.coef, d: f.when.d, cells: false, size: 0 })) : '';
|
|
339
477
|
const guarded = !f.guard ? under + body : 'keyNot' in f.guard ? `k = ${nameOf(f.guard.keyNot)} or ${body}` : `${nameOf(f.guard.atom)} is ${shownValue(f.guard.atom, f.guard.value)} → ${under}${body}`;
|
|
340
478
|
return f.cells ? `every k: ${guarded}` : guarded;
|
|
341
479
|
};
|
|
342
480
|
const sum = (es) => es.reduce((p, q) => plus(p, q), n(0));
|
|
481
|
+
// Each congruence its own witness's name (each may be a ghost of the machine).
|
|
482
|
+
const someNames = new Map(), someName = (f) => { let k = someNames.get(f); if (!k)
|
|
483
|
+
someNames.set(f, (k = `${SOME}${someNames.size}`)); return k; };
|
|
343
484
|
const part = (a, p) => (a.cell ? get(p, x(KEY)) : x(p));
|
|
344
485
|
const partsOf = (f, sign) => f.coef.flatMap(([i, c]) => atoms[i].parts.filter(([, s]) => s * c * sign > 0).map(([p]) => { const e = part(atoms[i], p); return Math.abs(c) === 1 ? e : times(Math.abs(c), e); }));
|
|
345
486
|
// Σ c·v ≤ d over naturals: the positive parts on the left, the negative ones and d on the right (an integer's
|
|
@@ -353,6 +494,19 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
353
494
|
};
|
|
354
495
|
if (f.unreachable)
|
|
355
496
|
return notAt(f.unreachable.atom, f.unreachable.value);
|
|
497
|
+
if (f.mod) {
|
|
498
|
+
// Σ c·v = P − N over naturals (P the positive parts, N the negative ones), whose remainder by k is that of the
|
|
499
|
+
// natural P + (k − 1)·N.
|
|
500
|
+
const { k: dk, r } = f.mod, neg = partsOf(f, -1), e = get(modName(dk), sum([...partsOf(f, 1), ...neg.map(t => times(dk - 1, t))]));
|
|
501
|
+
const body = same(e, n(r));
|
|
502
|
+
return !f.guard ? body : 'keyNot' in f.guard ? any(same(x(KEY), x(atoms[f.guard.keyNot].parts[0][0])), body) : any(notAt(f.guard.atom, f.guard.value), body);
|
|
503
|
+
}
|
|
504
|
+
// Σ = c·k + r, k = SOME (an integer's two parts where Σ reads an integer or a difference): the negative parts and
|
|
505
|
+
// c·k's negative part on the other side.
|
|
506
|
+
if (f.cong) {
|
|
507
|
+
const k = someName(f), intK = f.coef.some(([i, c]) => atoms[i].int || c < 0), ck = times(f.cong.mod, x(k)), ckNeg = times(f.cong.mod, x(`${k}$neg`));
|
|
508
|
+
return same(sum([...partsOf(f, 1), ...(intK ? [ckNeg] : [])]), sum([...partsOf(f, -1), ck, ...(f.cong.rest ? [n(f.cong.rest)] : [])]));
|
|
509
|
+
}
|
|
356
510
|
const lhs = sum([...partsOf(f, 1), ...(f.d < 0 ? [n(-f.d)] : [])]), rhs = sum([...partsOf(f, -1), ...(f.d > 0 ? [n(f.d)] : [])]);
|
|
357
511
|
// Under a condition Σ ≤ d: its negation, −Σ ≤ −d − 1, or the body.
|
|
358
512
|
const body = f.when ? any(condOf({ coef: f.when.coef.map(([i, c]) => [i, -c]), d: -f.when.d - 1, cells: false, size: 0 }), atMost(lhs, rhs)) : atMost(lhs, rhs);
|
|
@@ -360,7 +514,7 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
360
514
|
};
|
|
361
515
|
// The builder call, for a rule whose atoms are its own variables (naturals, or integers alone).
|
|
362
516
|
const sourceOf = (f, text) => {
|
|
363
|
-
if (!rule || f.when)
|
|
517
|
+
if (!rule || f.when || f.mod)
|
|
364
518
|
return undefined;
|
|
365
519
|
const used = [...new Set([...f.coef.map(([i]) => i), ...(f.guard ? ['keyNot' in f.guard ? f.guard.keyNot : f.guard.atom] : []), ...(f.unreachable ? [f.unreachable.atom] : [])])];
|
|
366
520
|
const ints = f.coef.filter(([i]) => atoms[i].int).length;
|
|
@@ -373,6 +527,12 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
373
527
|
const wrap = (body) => (f.cells ? `forall(k => ${body})` : body);
|
|
374
528
|
if (f.unreachable)
|
|
375
529
|
return `r.law('${text}', ${args} => ${cond(f.unreachable.atom, f.unreachable.value)}.not())`;
|
|
530
|
+
if (f.cong) {
|
|
531
|
+
if (f.coef.length !== 1 || f.coef[0][1] !== 1)
|
|
532
|
+
return undefined;
|
|
533
|
+
const intK = atoms[f.coef[0][0]].int;
|
|
534
|
+
return `r.law('${text}', ${args} => exists(${intK ? 'int, ' : ''}k => ${val(f.coef[0][0])}.eq(k.times(${f.cong.mod})${f.cong.rest ? `.plus(${f.cong.rest})` : ''})))`;
|
|
535
|
+
}
|
|
376
536
|
const term = (ts, k) => {
|
|
377
537
|
const parts = ts.map(([i, c]) => (Math.abs(c) === 1 ? val(i) : `${val(i)}.times(${Math.abs(c)})`));
|
|
378
538
|
if (!parts.length)
|
|
@@ -385,28 +545,49 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
385
545
|
return `r.law('${text}', ${args} => ${wrap(guarded)})`;
|
|
386
546
|
};
|
|
387
547
|
// --- evaluating a form ---------------------------------------------------------------------------------------------
|
|
548
|
+
// (each row's atom values read once, however many forms are evaluated there: rows are never changed once made)
|
|
549
|
+
const valuesAt = new WeakMap();
|
|
550
|
+
const valuesOf = (env) => {
|
|
551
|
+
let vs = valuesAt.get(env);
|
|
552
|
+
if (!vs) {
|
|
553
|
+
vs = new Float64Array(A);
|
|
554
|
+
for (let i = 0; i < A; i++)
|
|
555
|
+
vs[i] = valueOf(atoms[i], env);
|
|
556
|
+
valuesAt.set(env, vs);
|
|
557
|
+
}
|
|
558
|
+
return vs;
|
|
559
|
+
};
|
|
388
560
|
const holdsRow = (f, env) => {
|
|
561
|
+
used.evals++;
|
|
562
|
+
const vs = valuesOf(env);
|
|
389
563
|
if (f.unreachable)
|
|
390
|
-
return
|
|
564
|
+
return vs[f.unreachable.atom] !== f.unreachable.value;
|
|
565
|
+
if (f.cong) {
|
|
566
|
+
let v = 0;
|
|
567
|
+
for (const [i, c] of f.coef)
|
|
568
|
+
v += c * vs[i];
|
|
569
|
+
const nat = f.coef.every(([i, c]) => !atoms[i].int && c > 0);
|
|
570
|
+
return ((v - f.cong.rest) % f.cong.mod + f.cong.mod) % f.cong.mod === 0 && (!nat || v >= f.cong.rest);
|
|
571
|
+
}
|
|
391
572
|
if (f.guard) {
|
|
392
573
|
if ('keyNot' in f.guard) {
|
|
393
|
-
if (env[K] ===
|
|
574
|
+
if (env[K] === vs[f.guard.keyNot])
|
|
394
575
|
return true;
|
|
395
576
|
}
|
|
396
|
-
else if (
|
|
577
|
+
else if (vs[f.guard.atom] !== f.guard.value)
|
|
397
578
|
return true;
|
|
398
579
|
}
|
|
399
580
|
if (f.when) {
|
|
400
581
|
let w = 0;
|
|
401
582
|
for (const [i, c] of f.when.coef)
|
|
402
|
-
w += c *
|
|
583
|
+
w += c * vs[i];
|
|
403
584
|
if (w > f.when.d)
|
|
404
585
|
return true;
|
|
405
586
|
}
|
|
406
587
|
let s = 0;
|
|
407
588
|
for (const [i, c] of f.coef)
|
|
408
|
-
s += c *
|
|
409
|
-
return s <= f.d;
|
|
589
|
+
s += c * vs[i];
|
|
590
|
+
return f.mod ? residue(s, f.mod.k) === f.mod.r : s <= f.d;
|
|
410
591
|
};
|
|
411
592
|
const pointOf = (params, st) => ({ scalar: { ...params, ...st.scalars }, cells: cellRows(params, st) });
|
|
412
593
|
const holdsAt = (f, p) => (f.cells ? p.cells.every(r => holdsRow(f, r)) : holdsRow(f, p.scalar));
|
|
@@ -430,16 +611,16 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
430
611
|
// --- Houdini on the runs' neighbors ----------------------------------------------------------------------------------
|
|
431
612
|
let alive = forms.slice().sort((p, q) => p.size - q.size).slice(0, maxCandidates);
|
|
432
613
|
const ctis = [];
|
|
433
|
-
const lawful = (params, st) => { try {
|
|
614
|
+
const lawful = (params, st) => { used.watched += given.length; try {
|
|
434
615
|
return given.every(l => watch(m, run, l, params, 4).start(st));
|
|
435
616
|
}
|
|
436
617
|
catch {
|
|
437
618
|
return false;
|
|
438
619
|
} };
|
|
439
|
-
const houdiniUntil = Math.min(until,
|
|
440
|
-
for (let quiet = 0; samples.length &&
|
|
620
|
+
const houdiniUntil = Math.min(until, now() + Math.max(500, (until - now()) / 4));
|
|
621
|
+
for (let quiet = 0; samples.length && now() < houdiniUntil && !stop() && quiet < 3;) {
|
|
441
622
|
const killed = new Set();
|
|
442
|
-
for (let tries = 0; tries < 400 &&
|
|
623
|
+
for (let tries = 0; tries < 400 && now() < houdiniUntil && !stop(); tries++) {
|
|
443
624
|
const base = samples[rand(samples.length)], scal = { ...base.st.scalars }, maps = { ...base.st.maps };
|
|
444
625
|
for (let k = 0, moves = 1 + rand(2); k < moves; k++) {
|
|
445
626
|
if (mapNames.length && rand(2) === 0) {
|
|
@@ -480,23 +661,27 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
480
661
|
alive = alive.filter(f => !killed.has(f));
|
|
481
662
|
}
|
|
482
663
|
opts.progress?.('houdini', { alive: alive.length, ctis: ctis.length });
|
|
664
|
+
trace('houdini', { alive: alive.length });
|
|
483
665
|
// --- the kernel ----------------------------------------------------------------------------------------------------
|
|
484
666
|
const lawOf = new Map();
|
|
667
|
+
const someOf = (f) => (!f.cong ? [] : f.coef.some(([i, c]) => atoms[i].int || c < 0) ? [someName(f), `${someName(f)}$neg`] : [someName(f)]);
|
|
485
668
|
const lawFor = (f) => { let l = lawOf.get(f); if (!l)
|
|
486
|
-
lawOf.set(f, (l = { name: textOf(f), holds: condOf(f), ...(f.cells ? { forall: KEY } : {}) })); return l; };
|
|
669
|
+
lawOf.set(f, (l = { name: textOf(f), holds: condOf(f), ...(f.cells ? { forall: KEY } : {}), ...(f.cong ? { some: someOf(f) } : {}) })); return l; };
|
|
487
670
|
const names = new Set(given.map(l => l.name));
|
|
488
671
|
alive = alive.filter(f => !names.has(textOf(f)));
|
|
489
672
|
let pool = alive.slice(), chosen = [];
|
|
490
673
|
const goalName = (g) => g.replace(/ @[^@]*$/, '');
|
|
491
674
|
let last = first;
|
|
492
|
-
const slow = Math.max(3000,
|
|
675
|
+
const slow = Math.max(3000, ms / 10);
|
|
493
676
|
// A point a form is false at; a cell form is judged only at rows that read every map it reads.
|
|
494
677
|
const reads = (f, r) => f.coef.every(([i]) => !atoms[i].cell || atoms[i].parts.every(([q]) => AT + q in r));
|
|
495
678
|
const falseAt = (f, p) => (f.cells ? p.cells.some(r => reads(f, r) && !holdsRow(f, r)) : !holdsRow(f, p.scalar));
|
|
496
|
-
|
|
679
|
+
// (a remainder or a congruence only where no bound of the candidates rules the point out, before the counterexample's own forms:
|
|
680
|
+
// false at half the states a run doesn't reach, it would otherwise be chosen first everywhere)
|
|
681
|
+
const best = (p, congs = false) => {
|
|
497
682
|
let pickF, score = -Infinity;
|
|
498
683
|
for (const f of pool) {
|
|
499
|
-
if (chosen.includes(f) || !falseAt(f, p))
|
|
684
|
+
if (chosen.includes(f) || !!(f.cong || f.mod) !== congs || !falseAt(f, p))
|
|
500
685
|
continue;
|
|
501
686
|
const s = ctis.reduce((k, c) => k + (falseAt(f, c) ? 1 : 0), 0) * 1000 - f.size;
|
|
502
687
|
if (s > score)
|
|
@@ -511,7 +696,7 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
511
696
|
const choiceMode = modes.find(mo => mo.choice && !atoms[mo.atom].cell);
|
|
512
697
|
const condAtoms = plain.filter(i => !atoms[i].param && i !== choiceMode?.atom);
|
|
513
698
|
const fromCti = (p) => {
|
|
514
|
-
const
|
|
699
|
+
const end = Math.min(until, now() + Math.max(300, (until - now()) / 8));
|
|
515
700
|
const sc = p.scalar, pv = (coef) => { let v = 0; for (const [i, c] of coef)
|
|
516
701
|
v += c * valueOf(atoms[i], sc); return v; };
|
|
517
702
|
const gv = choiceMode ? valueOf(atoms[choiceMode.atom], sc) : undefined;
|
|
@@ -561,9 +746,10 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
561
746
|
for (const coef of conds) {
|
|
562
747
|
const here = pv(coef);
|
|
563
748
|
for (const d of [...new Set([here, 0, 1, -1])].filter(d => here <= d)) {
|
|
564
|
-
if (
|
|
749
|
+
if (now() > end || stop())
|
|
565
750
|
break;
|
|
566
751
|
const rows = [];
|
|
752
|
+
used.rows += inGuard.length;
|
|
567
753
|
for (const j of inGuard)
|
|
568
754
|
if (at(coef, valsS, j) <= d)
|
|
569
755
|
rows.push(j);
|
|
@@ -574,9 +760,10 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
574
760
|
}
|
|
575
761
|
const stride = Math.max(1, Math.ceil(rows.length / 600));
|
|
576
762
|
for (const [q, sh] of bodies.entries()) {
|
|
577
|
-
if (q % 256 === 0 &&
|
|
763
|
+
if (q % 256 === 0 && (now() > end || stop()))
|
|
578
764
|
break;
|
|
579
765
|
const v = pv(sh.coef);
|
|
766
|
+
used.rows += Math.ceil(rows.length / stride);
|
|
580
767
|
let most = -Infinity, ok = true;
|
|
581
768
|
for (let t = 0; t < rows.length; t += stride) {
|
|
582
769
|
const w = at(sh.coef, valsS, rows[t]);
|
|
@@ -604,11 +791,18 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
604
791
|
done.add(b); };
|
|
605
792
|
const drop = (f) => { chosen = chosen.filter(g => g !== f); pool = pool.filter(g => g !== f); forget(); };
|
|
606
793
|
const search = (ls) => {
|
|
794
|
+
// (laws with "there exists" are proven as a set, their witnesses chosen together: core/exists.ts)
|
|
795
|
+
if (ls.some(l => l.some?.length)) {
|
|
796
|
+
const whole = prove([...laws, ...chosen.map(lawFor)]);
|
|
797
|
+
if (!(whole.proven && whole.checked))
|
|
798
|
+
forget();
|
|
799
|
+
return whole;
|
|
800
|
+
}
|
|
607
801
|
for (let i = 0; i < ls.length; i++) {
|
|
608
802
|
if (done.has(ls[i].name))
|
|
609
803
|
continue;
|
|
610
804
|
proofs++;
|
|
611
|
-
const r = proveLaw(m, ls, i);
|
|
805
|
+
const r = proveLaw(forLaws(ls).m, ls, i);
|
|
612
806
|
if (!r.proven)
|
|
613
807
|
return { proven: false, checked: false, goal: r.goal, stuck: r.stuck, ...(r.witness ? { witness: r.witness } : {}) };
|
|
614
808
|
done.add(ls[i].name);
|
|
@@ -617,7 +811,7 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
617
811
|
}
|
|
618
812
|
if (stepLaws.length) {
|
|
619
813
|
proofs++;
|
|
620
|
-
const r = proveStepLaws(m, ls, stepLaws);
|
|
814
|
+
const r = proveStepLaws(forLaws(ls).m, ls, stepLaws);
|
|
621
815
|
if (!r.proven)
|
|
622
816
|
return { proven: false, checked: false, ...(r.failed ? { goal: r.failed.goal, stuck: r.failed.stuck, ...(r.failed.witness ? { witness: r.failed.witness } : {}) } : {}) };
|
|
623
817
|
}
|
|
@@ -626,11 +820,11 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
626
820
|
forget();
|
|
627
821
|
return whole;
|
|
628
822
|
};
|
|
629
|
-
for (let iter = 0; iter < 300 &&
|
|
630
|
-
const began = performance.now();
|
|
823
|
+
for (let iter = 0; iter < 300 && now() < until && !stop(); iter++) {
|
|
824
|
+
const began = now(), wall = performance.now();
|
|
631
825
|
last = search([...given, ...chosen.map(lawFor)]);
|
|
632
|
-
const took =
|
|
633
|
-
opts.progress?.('proof', { chosen: chosen.length, goal: last.goal ?? '', stuck: last.stuck ?? '', ms: Math.round(took) });
|
|
826
|
+
const took = now() - began;
|
|
827
|
+
opts.progress?.('proof', { chosen: chosen.length, goal: last.goal ?? '', stuck: last.stuck ?? '', ms: Math.round(performance.now() - wall), counted: Math.round(took) });
|
|
634
828
|
if (last.proven && last.checked)
|
|
635
829
|
break;
|
|
636
830
|
if (!last.goal)
|
|
@@ -652,6 +846,8 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
652
846
|
if (p)
|
|
653
847
|
ctis.push(p);
|
|
654
848
|
let next = p ? best(p) : undefined;
|
|
849
|
+
if (!next && p)
|
|
850
|
+
next = best(p, true);
|
|
655
851
|
if (!next && p) {
|
|
656
852
|
const t = performance.now(), made = fromCti(p);
|
|
657
853
|
pool.push(...made);
|
|
@@ -671,15 +867,36 @@ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
|
671
867
|
break;
|
|
672
868
|
drop(stuckOn);
|
|
673
869
|
}
|
|
674
|
-
|
|
870
|
+
// Unproven: each congruence the runs left alive, with the laws alone (the counterexamples' choice may have gone to the
|
|
871
|
+
// bounds instead), at most four.
|
|
872
|
+
if (!(last.proven && last.checked)) {
|
|
873
|
+
for (const f of alive.filter(g => g.cong).slice(0, 4)) {
|
|
874
|
+
if (now() > until || stop())
|
|
875
|
+
break;
|
|
876
|
+
const v = prove([...laws, lawFor(f)]);
|
|
877
|
+
if (v.proven && v.checked) {
|
|
878
|
+
chosen = [f];
|
|
879
|
+
last = v;
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
trace('kernel', { chosen: chosen.length, proven: !!(last.proven && last.checked) });
|
|
885
|
+
if (!(last.proven && last.checked)) {
|
|
886
|
+
trace('end', { proven: false, stopped: stop() });
|
|
675
887
|
return { ...last, found: [], stats: stats(samples.length, forms.length, alive.length) };
|
|
888
|
+
}
|
|
676
889
|
// Take out what the proof doesn't need, the last added first.
|
|
677
890
|
for (const f of [...chosen].reverse()) {
|
|
678
|
-
if (
|
|
891
|
+
if (now() > until || stop())
|
|
679
892
|
break;
|
|
680
893
|
const without = chosen.filter(g => g !== f), v = prove([...laws, ...without.map(lawFor)]);
|
|
681
894
|
if (v.proven && v.checked)
|
|
682
895
|
(chosen = without, last = v);
|
|
683
896
|
}
|
|
897
|
+
const kept = chosen.flatMap(f => (f.mod ? [f.mod.k] : []));
|
|
898
|
+
if (kept.length)
|
|
899
|
+
rule?.dividesBy(...kept);
|
|
900
|
+
trace('end', { proven: true, stopped: stop(), found: chosen.map(f => textOf(f)).join('; ') });
|
|
684
901
|
return { ...last, found: chosen.map(f => { const law = lawFor(f), text = law.name, source = sourceOf(f, text); return { law, text, ...(source ? { source } : {}) }; }), stats: stats(samples.length, forms.length, alive.length) };
|
|
685
902
|
}
|
package/dist/core/machine.d.ts
CHANGED
|
@@ -134,6 +134,22 @@ export type Law = {
|
|
|
134
134
|
forall?: string;
|
|
135
135
|
keys?: string[];
|
|
136
136
|
stated?: C;
|
|
137
|
+
some?: string[];
|
|
138
|
+
witness?: SomeWitness;
|
|
139
|
+
ex?: SomeProof;
|
|
140
|
+
};
|
|
141
|
+
export type SomeWitness = {
|
|
142
|
+
terms?: Record<string, E>;
|
|
143
|
+
ghost?: Record<string, {
|
|
144
|
+
start: E;
|
|
145
|
+
step: E;
|
|
146
|
+
}>;
|
|
147
|
+
};
|
|
148
|
+
export type SomeProof = {
|
|
149
|
+
vars: string[];
|
|
150
|
+
body: C;
|
|
151
|
+
sub?: Record<string, E>;
|
|
152
|
+
ghost?: true;
|
|
137
153
|
};
|
|
138
154
|
export { unreachable } from '../kernel/kernel.js';
|
|
139
155
|
export declare const x: (name: string) => E;
|
|
@@ -153,6 +169,17 @@ export declare const all: (...xs: C[]) => C;
|
|
|
153
169
|
export declare const any: (...xs: C[]) => C;
|
|
154
170
|
export declare const not: (c: C) => C;
|
|
155
171
|
export declare const bit: (name: string) => C;
|
|
172
|
+
export declare const divName: (k: number) => string;
|
|
173
|
+
export declare const modName: (k: number) => string;
|
|
174
|
+
export declare const isArithmetic: (name: string) => boolean;
|
|
175
|
+
export declare const divisor: (k: number, what: string) => number;
|
|
176
|
+
export declare function divisorsIn(ts: readonly (E | C)[], out?: Set<number>): Set<number>;
|
|
177
|
+
export declare function arithmetic(ks: Iterable<number>): {
|
|
178
|
+
fns: Record<string, Fn>;
|
|
179
|
+
facts: Law[];
|
|
180
|
+
};
|
|
181
|
+
export declare function withArithmetic(m: Machine, more?: readonly (E | C)[], ks?: Iterable<number>): Machine;
|
|
182
|
+
export declare const machineTerms: (m: Machine) => (E | C)[];
|
|
156
183
|
export type State = {
|
|
157
184
|
scalars: Record<string, number>;
|
|
158
185
|
maps: Record<string, Map<number, number>>;
|
package/dist/core/machine.js
CHANGED
|
@@ -33,6 +33,51 @@ export const all = (...xs) => ({ op: 'and', xs });
|
|
|
33
33
|
export const any = (...xs) => ({ op: 'or', xs });
|
|
34
34
|
export const not = (c) => ({ op: 'not', x: c });
|
|
35
35
|
export const bit = (name) => any(same(x(name), n(0)), same(x(name), n(1)));
|
|
36
|
+
// --- division and remainder by a constant ------------------------------------------------------------------------
|
|
37
|
+
// x div k and x mod k, for a constant k >= 1, are reads of two functions of the rule, div$k and mod$k, at x. The kernel
|
|
38
|
+
// knows them by two facts: x = k·div$k(x) + mod$k(x), and mod$k(x) < k (both naturals, as every value is), which fix
|
|
39
|
+
// both values (Euclid's division), so a goal about them is Presburger arithmetic the kernel already decides: nothing
|
|
40
|
+
// new is trusted. The runtimes compute them as Math.floor(x / k) and x % k; the Lean export defines them as Lean's
|
|
41
|
+
// x / k and x % k and proves the two facts of those by omega.
|
|
42
|
+
export const divName = (k) => `div$${k}`;
|
|
43
|
+
export const modName = (k) => `mod$${k}`;
|
|
44
|
+
const arithRe = /^(div|mod)\$(\d+)$/;
|
|
45
|
+
export const isArithmetic = (name) => arithRe.test(name);
|
|
46
|
+
export const divisor = (k, what) => {
|
|
47
|
+
if (!Number.isSafeInteger(k) || k < 1)
|
|
48
|
+
throw new RangeError(`${what} divides by a whole number at least 1, not ${k}`);
|
|
49
|
+
return k;
|
|
50
|
+
};
|
|
51
|
+
// The constants some terms divide by.
|
|
52
|
+
export function divisorsIn(ts, out = new Set()) {
|
|
53
|
+
for (const t of ts)
|
|
54
|
+
for (const m of JSON.stringify(t).matchAll(/"op":"get","map":"(?:div|mod)\$(\d+)"/g))
|
|
55
|
+
out.add(Number(m[1]));
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
// The two functions and their facts for each divisor: what a machine that divides by k declares.
|
|
59
|
+
export function arithmetic(ks) {
|
|
60
|
+
const fns = {}, facts = [];
|
|
61
|
+
for (const k of [...new Set(ks)].sort((a, b) => a - b)) {
|
|
62
|
+
const d = divName(k), r = modName(k), key = `x$${r}`, at = (map) => get(map, x(key));
|
|
63
|
+
fns[d] = { js: `x => Math.floor(x / ${k})`, lean: `fun x => x / ${k}` };
|
|
64
|
+
fns[r] = { js: `x => x % ${k}`, lean: `fun x => x % ${k}` };
|
|
65
|
+
facts.push({ name: `${r}: x = ${k}·(x div ${k}) + x mod ${k}`, forall: key, holds: same(x(key), plus(times(k, at(d)), at(r))) }, { name: `${r}: x mod ${k} < ${k}`, forall: key, holds: less(at(r), n(k)) });
|
|
66
|
+
}
|
|
67
|
+
return { fns, facts };
|
|
68
|
+
}
|
|
69
|
+
// A machine with the functions and facts of every divisor its terms (or the laws given) read, those it lacks added.
|
|
70
|
+
export function withArithmetic(m, more = [], ks = []) {
|
|
71
|
+
const all = divisorsIn(more, divisorsIn(machineTerms(m), new Set(ks)));
|
|
72
|
+
const { fns, facts } = arithmetic([...all].filter(k => !m.fns?.[modName(k)]));
|
|
73
|
+
if (!Object.keys(fns).length)
|
|
74
|
+
return m;
|
|
75
|
+
return { ...m, fns: { ...m.fns, ...fns }, facts: [...(m.facts ?? []), ...facts] };
|
|
76
|
+
}
|
|
77
|
+
// Every term and condition a machine holds.
|
|
78
|
+
export const machineTerms = (m) => [...Object.values(m.state), ...Object.values(m.step.flags ?? {}), ...Object.values(m.step.state ?? {}),
|
|
79
|
+
...Object.values(m.step.maps ?? {}).flatMap(w => ('key' in w ? [w.key, w.value] : [w.value])), ...(m.pre ? [m.pre] : []), ...(m.assume ? [m.assume] : []),
|
|
80
|
+
...(m.facts ?? []).map(f => f.holds), ...(m.history ? [m.history.keep, ...Object.values(m.history.sums), ...Object.values(m.history.queries), ...(m.history.relations ?? []).flatMap(r => [r.when, r.holds])] : [])];
|
|
36
81
|
// A condition at every key, or a value at every key. Where the bound key is read only as a map's key, each key a map
|
|
37
82
|
// read holds a cell at, and one key past them all (where every map reads its default), are every case there is.
|
|
38
83
|
const mapsIn = (t) => [...new Set([...JSON.stringify(t).matchAll(/"op":"get","map":"([^"]+)"/g)].map(x => x[1]))];
|
package/dist/core/solver.d.ts
CHANGED
|
@@ -50,5 +50,8 @@ export type Verdict = {
|
|
|
50
50
|
kind: 'unknown';
|
|
51
51
|
point?: Record<string, number>;
|
|
52
52
|
};
|
|
53
|
+
export declare const work: {
|
|
54
|
+
n: number;
|
|
55
|
+
};
|
|
53
56
|
export declare function solve(constraints: Constraint[], maxRows?: number): Verdict;
|
|
54
57
|
export declare function refuteFast(constraints: Constraint[], maxRows?: number): Verdict | undefined;
|