gdp-wired 0.2.0 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
  import { add, k, scale, solve, sub } from "../core/solver.js";
2
- import { canon, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, obligations, stepLawObligations, truth, unreachable, withProducts } from "./kernel.js";
2
+ import { assumed, canon, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
3
3
  // The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
4
4
  function openE(e, path, out) {
5
5
  switch (e.op) {
@@ -32,7 +32,7 @@ function openE(e, path, out) {
32
32
  function openC(c, path, out) {
33
33
  if (truth(c, path) !== undefined)
34
34
  return;
35
- if (c.op === 'flag')
35
+ if (c.op === 'flag' || c.op === 'all')
36
36
  return;
37
37
  if (c.op === 'not')
38
38
  return openC(c.x, path, out);
@@ -68,7 +68,7 @@ function openH(c, path, out, positive = true) {
68
68
  openC(c, path, out);
69
69
  return;
70
70
  }
71
- if (c.op === 'flag')
71
+ if (c.op === 'flag' || c.op === 'all')
72
72
  return;
73
73
  const before = out.length;
74
74
  openE(c.a, path, out), openE(c.b, path, out);
@@ -113,10 +113,11 @@ function parse(text) {
113
113
  }
114
114
  return out;
115
115
  }
116
- // The budget: 200,000 nodes and, by default, 20 seconds per goal; past either the goal is left `unknown`.
117
- export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + 20_000 }) {
116
+ // The budget: 200,000 nodes and, by default, a second per hypothesis (at least 20 seconds) per goal; past either the goal
117
+ // is left `unknown`. A goal with many instances (a law about two keys at its pairs) has that many more rows per leaf.
118
+ export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
118
119
  if (++budget.nodes > 200_000 || (budget.until !== undefined && performance.now() > budget.until))
119
- return { stuck: 'unknown', path };
120
+ return { stuck: 'unknown', path, why: outOfBudget(goal, budget.nodes > 200_000 ? 'nodes' : 'time', path.size) };
120
121
  const rows = leafConstraints(goal, path);
121
122
  const verdict = solve(rows);
122
123
  if (verdict.kind === 'refuted')
@@ -181,6 +182,13 @@ export function grow(goal, path = new Map(), budget = { nodes: 0, until: perform
181
182
  return no;
182
183
  return { tree: { split, yes: yes.tree, no: no.tree }, nodes: budget.nodes };
183
184
  }
185
+ // What ran out, for a goal left unknown: its size, how deep the case splits went, and the two shapes that usually cause it.
186
+ function outOfBudget(goal, limit, depth) {
187
+ const steps = Number(/@(\d+) steps/.exec(goal.name)?.[1] ?? 1);
188
+ return `the prover's ${limit === 'nodes' ? '200,000-node' : 'time'} budget ran out at ${goal.hyps.length} hypotheses, ${depth} case splits deep. ` +
189
+ `Each case a step decides multiplies the cases to refute, so ${steps > 1 ? `a law over ${steps} steps` : 'many interacting cases'} can outgrow it: ` +
190
+ 'state the law over fewer steps (a one-step law and an invariant often give the same), or split it into laws about fewer conditions';
191
+ }
184
192
  // Integer completeness for bounded systems: when elimination is inexact and finds no witness, halve a variable's range
185
193
  // (x <= mid or x >= mid + 1). Each half is a split like any other, so the kernel checks it the same way; a variable
186
194
  // pinned to one value becomes an equality, which the solver substitutes exactly.
@@ -223,11 +231,17 @@ function fractional(point, path) {
223
231
  return undefined;
224
232
  }
225
233
  // The keys a proof assumes the every-key laws at: every key the step and the laws read, and the fresh key of the goal.
234
+ // A key bound by a quantified flag or a write at every key isn't one: those reads are at every key already.
226
235
  function readKeys(m, laws) {
227
236
  const out = new Map();
237
+ const bound = new Set([...Object.values(m.step.flags ?? {}).flatMap(c => (c.op === 'all' ? [c.key] : [])), ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.each] : []))]);
238
+ const free = (e) => !JSON.stringify(e).match(/"name":"([^"]+)"/g)?.some(n => bound.has(n.slice(8, -1)));
228
239
  const inE = (e) => {
229
- if (e.op === 'get')
230
- (out.set(JSON.stringify(e.key), e.key), inE(e.key));
240
+ if (e.op === 'get') {
241
+ if (free(e.key))
242
+ out.set(JSON.stringify(e.key), e.key);
243
+ inE(e.key);
244
+ }
231
245
  else if (e.op === 'if')
232
246
  (inC(e.c), inE(e.then), inE(e.else));
233
247
  else if (e.op === 'mul')
@@ -235,26 +249,214 @@ function readKeys(m, laws) {
235
249
  else if ('a' in e)
236
250
  (inE(e.a), inE(e.b));
237
251
  };
238
- const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
252
+ const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? inC(c.c) : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
239
253
  Object.values(m.step.state ?? {}).forEach(inE);
240
254
  Object.values(m.step.flags ?? {}).forEach(inC);
241
- for (const w of Object.values(m.step.maps ?? {}))
242
- (out.set(JSON.stringify(w.key), w.key), inE(w.key), inE(w.value));
255
+ for (const w of Object.values(m.step.maps ?? {})) {
256
+ if (!('each' in w)) {
257
+ out.set(JSON.stringify(w.key), w.key);
258
+ inE(w.key);
259
+ }
260
+ inE(w.value);
261
+ }
243
262
  for (const l of laws.filter(l => !l.forall))
244
263
  inC(l.holds);
245
264
  return [...out.values()];
246
265
  }
266
+ const shifts = (m) => Object.values(m.step.maps ?? {}).some(w => 'each' in w && !plainKey(w.value, w.each));
267
+ // The keys some goals read a map at.
268
+ function mapKeys(goals) {
269
+ const out = new Map();
270
+ const inE = (e) => {
271
+ if (e.op === 'get') {
272
+ out.set(JSON.stringify(e.key), e.key);
273
+ inE(e.key);
274
+ }
275
+ else if (e.op === 'if')
276
+ (inC(e.c), inE(e.then), inE(e.else));
277
+ else if (e.op === 'mul')
278
+ inE(e.a);
279
+ else if ('a' in e)
280
+ (inE(e.a), inE(e.b));
281
+ };
282
+ const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? inC(c.c) : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
283
+ for (const g of goals)
284
+ g.hyps.forEach(inC);
285
+ return [...out.values()];
286
+ }
287
+ // Instances with each every-key law also at these keys. A law about two keys or more goes at tuples that relate the
288
+ // goal's own keys (`focus`: the skolems of the law it proves, or a step law's keys) to each other and to the keys that
289
+ // read none of them: at the i-th place, a key that reads the i-th focus alone (the first, past the focus there is), and
290
+ // at most one place a key that reads no focus.
291
+ const withReads = (laws, inst, keys, focus) => ({ ...inst, ...Object.fromEntries(laws.filter(l => l.forall).map(l => {
292
+ const had = inst[l.name] ?? [];
293
+ if (l.keys?.length) {
294
+ const n = l.keys.length + 1;
295
+ const reads = (e, x) => x !== undefined && JSON.stringify(e).includes(`"name":${JSON.stringify(x)}`);
296
+ const own = (i) => { const f = focus[i] ?? focus[0]; return keys.filter(e => reads(e, f) && focus.every(g => g === f || !reads(e, g))); };
297
+ const R = keys.filter(e => !focus.some(f => reads(e, f)));
298
+ const out = new Map();
299
+ for (let i = 0; i + n <= had.length; i += n)
300
+ out.set(JSON.stringify(had.slice(i, i + n)), had.slice(i, i + n));
301
+ const go = (i, tuple, rest) => {
302
+ if (i === n) {
303
+ out.set(JSON.stringify(tuple), tuple);
304
+ return;
305
+ }
306
+ for (const e of own(i))
307
+ go(i + 1, [...tuple, e], rest);
308
+ if (!rest)
309
+ for (const e of R)
310
+ go(i + 1, [...tuple, e], true);
311
+ };
312
+ go(0, [], false);
313
+ return [l.name, [...out.values()].flat()];
314
+ }
315
+ const seen = new Set(had.map(e => JSON.stringify(e)));
316
+ return [l.name, [...had, ...keys.filter(e => { const id = JSON.stringify(e); return seen.has(id) ? false : (seen.add(id), true); })]];
317
+ })) });
318
+ // A law about every n keys at every n-tuple of the keys (flattened, as Instances keeps tuples).
319
+ const tuples = (keys, n) => (n === 0 ? [] : n === 1 ? keys : keys.flatMap(a => { const rest = tuples(keys, n - 1); return Array.from({ length: rest.length / (n - 1) }, (_, t) => [a, ...rest.slice(t * (n - 1), (t + 1) * (n - 1))]).flat(); }));
320
+ // The keys some goals read a function at.
321
+ function fnKeys(m, goals) {
322
+ const fns = Object.keys(m.fns ?? {}), out = new Map();
323
+ const inE = (e) => {
324
+ if (e.op === 'get') {
325
+ if (fns.includes(e.map))
326
+ out.set(JSON.stringify(e.key), e.key);
327
+ inE(e.key);
328
+ }
329
+ else if (e.op === 'if')
330
+ (inC(e.c), inE(e.then), inE(e.else));
331
+ else if (e.op === 'mul')
332
+ inE(e.a);
333
+ else if ('a' in e)
334
+ (inE(e.a), inE(e.b));
335
+ };
336
+ const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? inC(c.c) : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
337
+ for (const g of goals)
338
+ g.hyps.forEach(inC);
339
+ return [...out.values()];
340
+ }
341
+ // Instances with each fact about every key assumed where it reads a function at one of these keys: its key at each
342
+ // value that makes one of its reads one of these (by matching: a function of several arguments reads a paired key), and
343
+ // at the keys themselves.
344
+ const withFacts = (m, inst, keys) => ({ ...inst, ...Object.fromEntries((m.facts ?? []).filter(f => f.forall).map(f => {
345
+ if (f.keys?.length)
346
+ return [f.name, tuples(keys, f.keys.length + 1)];
347
+ const had = inst[f.name] ?? [], seen = new Set(had.map(e => JSON.stringify(e)));
348
+ const reads = fnKeys(m, [{ name: '', hyps: [f.holds] }]);
349
+ const at = [...keys, ...reads.flatMap(pattern => keys.flatMap(key => { const b = match(pattern, key, f.forall); return b ? [b] : []; }))];
350
+ return [f.name, [...had, ...at.filter(e => { const id = JSON.stringify(e); return seen.has(id) ? false : (seen.add(id), true); })]];
351
+ })) });
352
+ // The value of x that makes pattern the term, if one does (structurally).
353
+ function match(pattern, term, x) {
354
+ let bound;
355
+ const go = (p, t) => {
356
+ if (p.op === 'var' && p.name === x) {
357
+ if (bound)
358
+ return JSON.stringify(bound) === JSON.stringify(t);
359
+ bound = t;
360
+ return true;
361
+ }
362
+ if (p.op !== t.op)
363
+ return false;
364
+ const pe = Object.entries(p), te = t;
365
+ return pe.every(([k, v]) => k === 'op' || (typeof v === 'object' && v !== null ? (Array.isArray(v) ? Array.isArray(te[k]) && te[k].length === v.length && v.every((y, i) => go(y, te[k][i])) : go(v, te[k])) : v === te[k]));
366
+ };
367
+ return go(pattern, term) ? bound : undefined;
368
+ }
369
+ // Each quantified flag's witness w?f: a key where its condition fails, when it does; every-key facts are wanted there too.
370
+ const witnesses = (m) => Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [{ op: 'var', name: `w?${f}` }] : []));
371
+ // The keys each quantified flag is assumed at (instances under ?f).
372
+ const flagInstances = (m, keys) => Object.fromEntries(Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [[`?${f}`, keys]] : [])));
373
+ const someKeys = (c, positive = true) => c.op === 'all' ? [...(positive ? [] : [c]), ...someKeys(c.c, positive)] : c.op === 'not' ? someKeys(c.x, !positive) : c.op === 'and' || c.op === 'or' ? c.xs.flatMap(y => someKeys(y, positive)) : [];
374
+ function atWitness(c, picks, positive = true) {
375
+ switch (c.op) {
376
+ case 'all': return !positive && picks[c.key] ? atWitness(substVar(c.c, c.key, picks[c.key]), picks, positive) : { ...c, c: atWitness(c.c, picks, positive) };
377
+ case 'not': return { op: 'not', x: atWitness(c.x, picks, !positive) };
378
+ case 'and':
379
+ case 'or': return { op: c.op, xs: c.xs.map(y => atWitness(y, picks, positive)) };
380
+ case 'flag':
381
+ case '<':
382
+ case '<=':
383
+ case '=': return c;
384
+ }
385
+ }
386
+ function substVar(c, name, by) {
387
+ const e = (t) => {
388
+ switch (t.op) {
389
+ case 'var': return t.name === name ? by : t;
390
+ case 'const': return t;
391
+ case 'mul': return { ...t, a: e(t.a) };
392
+ case 'if': return { op: 'if', c: substVar(t.c, name, by), then: e(t.then), else: e(t.else) };
393
+ case 'get': return { ...t, key: e(t.key) };
394
+ case 'add':
395
+ case 'sub':
396
+ case 'min':
397
+ case 'max':
398
+ case 'prod': return { ...t, a: e(t.a), b: e(t.b) };
399
+ }
400
+ };
401
+ return c.op === 'flag' ? c : c.op === 'all' ? { ...c, c: substVar(c.c, name, by) } : c.op === 'not' ? { op: 'not', x: substVar(c.x, name, by) } : c.op === 'and' || c.op === 'or' ? { op: c.op, xs: c.xs.map(y => substVar(y, name, by)) } : { ...c, a: e(c.a), b: e(c.b) };
402
+ }
403
+ const candidates = (m, names) => {
404
+ const vars = [...new Set([...names, ...m.params, ...Object.keys(m.state)])];
405
+ return [{ op: 'const', value: 0 }, ...vars.map((name) => ({ op: 'var', name })), ...Object.keys(m.maps ?? {}).flatMap(map => vars.map((name) => ({ op: 'get', map, key: { op: 'var', name } })))];
406
+ };
407
+ // Witnesses for every some-key in `items`, chosen in order: each the first candidate under which `proves` accepts the
408
+ // items whose some-keys are all named so far. Undefined, with the some-key's law, if none does.
409
+ function chooseWitnesses(items, body, names, m, proves, rebuild) {
410
+ const picks = {};
411
+ for (const t of items)
412
+ for (const q of someKeys(body(t)))
413
+ if (q.witness)
414
+ picks[q.key] = q.witness;
415
+ const resolved = (all) => items.filter(t => all || someKeys(body(t)).every(q => q.key in picks)).map(t => (someKeys(body(t)).length ? rebuild(t, atWitness(body(t), picks)) : t));
416
+ for (const t of items)
417
+ for (const q of someKeys(body(t))) {
418
+ if (q.key in picks)
419
+ continue;
420
+ const found = candidates(m, names(t)).find(w => { picks[q.key] = w; return proves(resolved(false)); });
421
+ if (!found) {
422
+ delete picks[q.key];
423
+ return { stuck: `no witness found for some key in ${t.name}: name one (someKey(f, witness))` };
424
+ }
425
+ }
426
+ return { items: resolved(true) };
427
+ }
247
428
  export function proveLaws(m, laws) {
248
- const keys = readKeys(m, laws);
429
+ if (!laws.some(l => someKeys(l.holds).length))
430
+ return proveLawsAsGiven(m, laws);
431
+ const out = chooseWitnesses(laws, l => l.holds, l => (l.forall ? [l.forall] : []), m, ls => { const r = proveLawsAsGiven(m, ls); return r.proven && r.checked; }, (l, holds) => ({ ...l, holds, stated: l.stated ?? l.holds }));
432
+ if ('stuck' in out)
433
+ return { proven: false, nodes: 0, leaves: 0, failed: { goal: 'witnesses', stuck: out.stuck }, checked: false };
434
+ return { ...proveLawsAsGiven(m, out.items), laws: out.items };
435
+ }
436
+ function proveLawsAsGiven(m, laws) {
437
+ const keys = [...readKeys(m, laws), ...witnesses(m)];
249
438
  const instances = {};
250
- for (const law of laws)
251
- instances[law.name] = Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, [...keys, ...(law.forall ? [{ op: 'var', name: `${law.forall}#` }] : [])]]));
439
+ for (const law of laws) {
440
+ const at = [...keys, ...(law.forall ? [{ op: 'var', name: `${law.forall}#` }] : []), ...(law.keys ?? []).map((name) => ({ op: 'var', name }))];
441
+ instances[law.name] = { ...Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, l.keys?.length ? [] : at])), ...flagInstances(m, at) };
442
+ }
443
+ // The facts about functions, at every key each law's goals read a function at (found from the goals, twice round).
444
+ for (let round = 0; round < 2 && m.facts?.length; round++) {
445
+ const goals = obligations(m, laws, instances);
446
+ laws.forEach((law, i) => { instances[law.name] = withFacts(m, instances[law.name], fnKeys(m, goals.slice(2 * i, 2 * i + 2))); });
447
+ }
448
+ // Where positions shift (a write at every key reading its key at k ∸ 1 or k + 1) or a law is about two keys, the
449
+ // every-key laws also at each key the goals read a map at, once round.
450
+ if (shifts(m) || laws.some(l => l.keys?.length)) {
451
+ const goals = obligations(m, laws, instances);
452
+ laws.forEach((law, i) => { instances[law.name] = withReads(laws, instances[law.name], mapKeys(goals.slice(2 * i, 2 * i + 2)), [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])]); });
453
+ }
252
454
  const trees = {};
253
455
  let nodes = 0, leaves = 0;
254
456
  for (const goal of obligations(m, laws, instances)) {
255
457
  const out = grow(goal);
256
458
  if (!('tree' in out))
257
- return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
459
+ return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
258
460
  trees[goal.name] = out.tree;
259
461
  nodes += out.nodes;
260
462
  leaves += countLeaves(out.tree);
@@ -262,29 +464,93 @@ export function proveLaws(m, laws) {
262
464
  const proof = { instances, trees };
263
465
  return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
264
466
  }
265
- // Step laws (kernel.ts's stepLawObligations): the laws proven first (they are assumed), then one goal per step law, the
266
- // every-key laws assumed at every key the step, the laws and the step laws read.
267
- export function proveStepLaws(m, laws, stepLaws) {
268
- const base = proveLaws(m, laws);
467
+ export function proveStepLaws(m, given, stepLaws) {
468
+ const base = proveLaws(m, given);
269
469
  if (!base.proven || !base.proof)
270
470
  return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
471
+ const laws = base.laws ?? given;
472
+ if (stepLaws.some(sl => someKeys(sl.then).length)) {
473
+ // Each step law's witnesses on its own (a step law assumes no other), the request and the state after among them.
474
+ const plain = m.history ? historyMachine(m).machine : m, after = Object.keys(plain.state).map(n => `${n}'`);
475
+ const out = chooseWitnesses(stepLaws, sl => sl.then, sl => [...(sl.forall ? [sl.forall] : []), ...plain.inputs, ...after], plain, sls => sls.every(sl => { const r = proveStepLawsAsGiven(m, laws, [sl], base); return r.proven && r.checked; }), (sl, then) => ({ ...sl, then }));
476
+ if ('stuck' in out)
477
+ return { proven: false, nodes: base.nodes, leaves: base.leaves, failed: { goal: 'witnesses', stuck: out.stuck }, checked: false };
478
+ return { ...proveStepLawsAsGiven(m, laws, out.items, base), ...(base.laws ? { laws } : {}), stepLaws: out.items };
479
+ }
480
+ return { ...proveStepLawsAsGiven(m, laws, stepLaws, base), ...(base.laws ? { laws } : {}) };
481
+ }
482
+ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
483
+ if (!base.proof)
484
+ return { proven: false, nodes: base.nodes, leaves: base.leaves, checked: false };
271
485
  const plain = m.history ? historyMachine(m).machine : m;
272
- const keys = [...readKeys(plain, laws), ...stepLaws.flatMap(sl => [sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC))];
273
- const instances = Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, keys]));
486
+ const keys = [...readKeys(plain, laws), ...witnesses(plain), ...stepLaws.flatMap(sl => [sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC)),
487
+ ...stepLaws.flatMap(sl => [sl.forall, ...(sl.keys ?? [])].flatMap(key => (key ? [{ op: 'var', name: key }] : [])))];
488
+ let instances = { ...Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, l.keys?.length ? [] : keys])), ...flagInstances(plain, keys) };
489
+ for (let round = 0; round < 2 && plain.facts?.length; round++)
490
+ instances = withFacts(plain, instances, fnKeys(plain, stepLaws.flatMap(sl => stepLawObligations(m, laws, instances, sl))));
491
+ // Where positions shift or a law is about two keys, each step law gets its own instances, from its own goal.
492
+ const perStep = {};
493
+ if (shifts(plain) || laws.some(l => l.keys?.length))
494
+ for (const sl of stepLaws)
495
+ perStep[sl.name] = withReads(laws, instances, mapKeys(stepLawObligations(m, laws, instances, sl)), [sl.forall, ...(sl.keys ?? [])].flatMap(k => (k ? [k] : [])));
274
496
  const trees = {};
275
497
  let nodes = base.nodes, leaves = base.leaves;
276
498
  for (const sl of stepLaws)
277
- for (const goal of stepLawObligations(m, laws, instances, sl)) {
499
+ for (const goal of stepLawObligations(m, laws, perStep[sl.name] ?? instances, sl)) {
278
500
  const out = grow(goal);
279
501
  if (!('tree' in out))
280
- return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
502
+ return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
281
503
  trees[goal.name] = out.tree;
282
504
  nodes += out.nodes;
283
505
  leaves += countLeaves(out.tree);
284
506
  }
285
- const proof = { laws: base.proof, instances, trees };
507
+ const proof = { laws: base.proof, instances, ...(Object.keys(perStep).length ? { perStep } : {}), trees };
286
508
  return { proven: true, nodes, leaves, checked: checkStepLaws(m, laws, stepLaws, proof), proof };
287
509
  }
510
+ // Progress (kernel.ts's progressObligations): the laws proven first, then each goal, the every-key laws assumed at every
511
+ // key the goals read a map at.
512
+ export function proveProgress(m, laws, p) {
513
+ const base = proveLaws(m, laws);
514
+ if (!base.proven || !base.checked)
515
+ return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
516
+ const ls = base.laws ?? laws;
517
+ let instances = { ...withReads(ls, {}, mapKeys(progressObligations(m, ls, {}, p)), []), ...flagInstances(m, mapKeys(progressObligations(m, ls, {}, p))) };
518
+ instances = { ...instances, ...withReads(ls, instances, mapKeys(progressObligations(m, ls, instances, p)), []) };
519
+ if (m.facts?.length)
520
+ instances = withFacts(m, instances, fnKeys(m, progressObligations(m, ls, instances, p)));
521
+ const trees = {};
522
+ let nodes = base.nodes, leaves = base.leaves;
523
+ for (const goal of progressObligations(m, ls, instances, p)) {
524
+ const out = grow(goal);
525
+ if (!('tree' in out))
526
+ return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
527
+ trees[goal.name] = out.tree;
528
+ nodes += out.nodes;
529
+ leaves += countLeaves(out.tree);
530
+ }
531
+ return { proven: true, nodes, leaves, checked: progressObligations(m, ls, instances, p).every(g => check(g, trees[g.name])), instances, trees };
532
+ }
533
+ // Liveness (kernel.ts's livenessParts): its step laws, proven together. The claim needs no more (a run takes the
534
+ // request, so it is allowed); but a request never allowed would leave the claim about no run, so where a helpful request
535
+ // names every input a client sends and carries no maps, it is proven allowed in its region too.
536
+ export function proveLiveness(m, laws, l) {
537
+ const { stay, cover, helps } = livenessParts(m, l);
538
+ const a = proveStepLaws(m, laws, [stay, cover, ...helps]);
539
+ if (!a.proven || !a.checked)
540
+ return { proven: false, nodes: a.nodes, leaves: a.leaves, ...(a.failed ? { failed: a.failed } : {}), checked: false };
541
+ const plain = m.history ? historyMachine(m).machine : m;
542
+ let nodes = a.nodes, leaves = a.leaves;
543
+ for (const [i, h] of livenessHelpful(l).entries()) {
544
+ if (m.history || plain.inputMaps?.length || !plain.assume || !plain.inputs.every(inp => inp in h.request))
545
+ continue;
546
+ const b = proveProgress(plain, laws, { name: `${l.name}: helpful request ${i + 1} is allowed`, when: { op: 'and', xs: [l.from, { op: 'not', x: l.to }, h.when] }, then: trueC, request: h.request });
547
+ nodes += b.nodes;
548
+ leaves += b.leaves;
549
+ if (!b.proven || !b.checked)
550
+ return { proven: false, nodes, leaves, ...(b.failed ? { failed: b.failed } : {}), checked: false };
551
+ }
552
+ return { proven: true, checked: true, nodes, leaves, steps: a };
553
+ }
288
554
  // The key terms a condition reads maps at; a primed map's key is a key of the map before the step too.
289
555
  function readKeysC(c) {
290
556
  const out = [];
@@ -296,14 +562,14 @@ function readKeysC(c) {
296
562
  inE(e.a);
297
563
  else if ('a' in e)
298
564
  (inE(e.a), inE(e.b)); };
299
- const inC = (x) => (x.op === 'flag' ? undefined : x.op === 'not' ? inC(x.x) : x.op === 'and' || x.op === 'or' ? x.xs.forEach(inC) : (inE(x.a), inE(x.b)));
565
+ const inC = (x) => (x.op === 'flag' || x.op === 'all' ? undefined : x.op === 'not' ? inC(x.x) : x.op === 'and' || x.op === 'or' ? x.xs.forEach(inC) : (inE(x.a), inE(x.b)));
300
566
  inC(c);
301
567
  return out;
302
568
  }
303
569
  export const countLeaves = (t) => ('leaf' in t ? 1 : countLeaves(t.yes) + countLeaves(t.no));
304
570
  // A real counterexample for a goal the prover couldn't close (with products, its witness may be abstract): the free
305
571
  // variables over 0..bound, every hypothesis evaluated with true arithmetic. Goals that read maps are left to the
306
- // machine searches (search/), which run whole histories.
572
+ // machine searches (research/search/), which run whole histories.
307
573
  export function concretize(goal, bound = 12) {
308
574
  const names = new Set();
309
575
  let reads = false;
@@ -315,7 +581,7 @@ export function concretize(goal, bound = 12) {
315
581
  (walkC(e.c), walkE(e.then), walkE(e.else));
316
582
  else if ('a' in e)
317
583
  (walkE(e.a), 'b' in e && walkE(e.b)); };
318
- const walkC = (c) => (c.op === 'flag' ? undefined : c.op === 'not' ? walkC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(walkC) : (walkE(c.a), walkE(c.b)));
584
+ const walkC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? void (reads = true) : c.op === 'not' ? walkC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(walkC) : (walkE(c.a), walkE(c.b)));
319
585
  goal.hyps.forEach(walkC);
320
586
  if (reads || names.size > 4)
321
587
  return undefined;
@@ -334,7 +600,7 @@ export function concretize(goal, bound = 12) {
334
600
  }
335
601
  };
336
602
  const holds = (c, env) => c.op === 'not' ? !holds(c.x, env) : c.op === 'and' ? c.xs.every(y => holds(y, env)) : c.op === 'or' ? c.xs.some(y => holds(y, env))
337
- : c.op === 'flag' ? false : c.op === '<' ? ev(c.a, env) < ev(c.b, env) : c.op === '<=' ? ev(c.a, env) <= ev(c.b, env) : ev(c.a, env) === ev(c.b, env);
603
+ : c.op === 'flag' || c.op === 'all' ? false : c.op === '<' ? ev(c.a, env) < ev(c.b, env) : c.op === '<=' ? ev(c.a, env) <= ev(c.b, env) : ev(c.a, env) === ev(c.b, env);
338
604
  const vars = [...names];
339
605
  const total = (bound + 1) ** vars.length;
340
606
  for (let i = 0; i < total; i++) {
@@ -345,3 +611,26 @@ export function concretize(goal, bound = 12) {
345
611
  }
346
612
  return undefined;
347
613
  }
614
+ // The facts a goal needs, as hypotheses: each fact about every key at the keys the goal reads a function at (and at
615
+ // the keys those facts read, a second round), each plain fact as it is.
616
+ export function factsFor(m, goal) {
617
+ let instances = {};
618
+ for (let round = 0; round < 2; round++)
619
+ instances = withFacts(m, instances, fnKeys(m, [{ ...goal, hyps: [...assumed(m.facts ?? [], instances), ...goal.hyps] }]));
620
+ return assumed(m.facts ?? [], instances);
621
+ }
622
+ // A goal built from its instances (an exporter's goal, shaped as its Lean twin builds it): the every-key laws, the
623
+ // quantified flags and the facts assumed at every key it reads a map or function at, then a tree, checked.
624
+ export function proveBuilt(m, laws, build) {
625
+ let instances = {};
626
+ for (let round = 0; round < 2; round++) {
627
+ const g = build(instances), keys = mapKeys([g]);
628
+ instances = { ...withReads(laws, instances, keys, []), ...flagInstances(m, keys) };
629
+ if (m.facts?.length)
630
+ instances = withFacts(m, instances, fnKeys(m, [build(instances)]));
631
+ }
632
+ const goal = build(instances), out = grow(goal);
633
+ if (!('tree' in out) || !check(goal, out.tree))
634
+ return { proven: false };
635
+ return { proven: true, instances, goal, tree: out.tree };
636
+ }
@@ -0,0 +1,124 @@
1
+ import type { C, E, Law, Machine } from '../core/machine.js';
2
+ import { type Step } from '../core/solver.js';
3
+ import { type Fold, type Goal, type Instances, type Liveness, type Loop, type Path, type StepLaw, type Tree } from '../kernel/kernel.js';
4
+ import { leanLibrary } from './library.js';
5
+ export { leanLibrary };
6
+ export type Exported = {
7
+ name: string;
8
+ goal: Goal;
9
+ tree: Tree;
10
+ };
11
+ export declare const int: (v: number) => string;
12
+ export declare const list: (xs: string[]) => string;
13
+ export declare function leaves(t: Tree, path: Path, visit: (t: Tree & {
14
+ leaf: Step[];
15
+ products?: [number, number][];
16
+ }, path: Path) => void): void;
17
+ export declare function encode(ex: Exported, tamper?: boolean, dropStep?: boolean, globalVars?: string[], globalMaps?: string[]): {
18
+ tab: string;
19
+ hyps: string;
20
+ tree: string;
21
+ cols: string[];
22
+ c: (t: C) => string;
23
+ e: (t: E) => string;
24
+ };
25
+ export declare function goalToLean(ex: Exported, i: number, tamper?: boolean, dropStep?: boolean): string;
26
+ export declare function machineToLean(id: string, m: Machine, laws: Law[], corollary?: string): string;
27
+ export declare const TRUE = ".le (.const 0) (.const 0)";
28
+ export declare const grown: (g: Goal) => Tree;
29
+ export declare function loopToLean(id: string, l: Loop, laws: Law[], rank: E | E[], tamper?: {
30
+ what: string;
31
+ loop: Loop;
32
+ }): string;
33
+ export declare function foldToLean(id: string, f: Fold, laws: Law[], extra?: string): string;
34
+ export declare function readable(id: string, laws: Law[], vars: string[], maps: string[], reach: {
35
+ binders: string;
36
+ hyp: string;
37
+ call: string;
38
+ }): string;
39
+ export type ProgressCase = {
40
+ id: string;
41
+ name: string;
42
+ when: C;
43
+ then: C;
44
+ request: Record<string, E>;
45
+ instances: Instances;
46
+ tamper?: {
47
+ what: string;
48
+ when: C;
49
+ };
50
+ };
51
+ export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts?: {
52
+ corollary?: string;
53
+ tamper?: {
54
+ what: string;
55
+ machine: Machine;
56
+ };
57
+ progress?: ProgressCase[];
58
+ free?: string[];
59
+ stepLaws?: StepLaw[];
60
+ }): string;
61
+ export declare const hasSets: (m: Machine) => boolean;
62
+ export declare function smachineToLean(id: string, m: Machine, laws: Law[], opts?: {
63
+ stepLaws?: StepLaw[];
64
+ liveness?: Liveness[];
65
+ tamper?: {
66
+ what: string;
67
+ machine: Machine;
68
+ };
69
+ simp?: string[];
70
+ free?: string[];
71
+ ghosts?: string[];
72
+ after?: (ix: (name: string) => number) => string;
73
+ refined?: {
74
+ impl: Machine;
75
+ spec: Machine;
76
+ wit: Record<string, E>;
77
+ stutter: C;
78
+ specPre: number;
79
+ };
80
+ }): string;
81
+ export declare function hmachineToLean(id: string, pruned: Machine, laws: Law[], tampers?: {
82
+ what: string;
83
+ relations: {
84
+ when: C;
85
+ holds: C;
86
+ }[];
87
+ withRecordTrees: boolean;
88
+ }[], corollary?: (ix: (name: string) => number) => string): string;
89
+ export type LeanItem = {
90
+ id: string;
91
+ machine: Machine;
92
+ laws: Law[];
93
+ stepLaws?: StepLaw[];
94
+ liveness?: Liveness[];
95
+ ghosts?: string[];
96
+ lean?: {
97
+ defs?: string;
98
+ simp?: string[];
99
+ after?: (ix: (name: string) => number) => string;
100
+ };
101
+ } | {
102
+ id: string;
103
+ refinement: {
104
+ impl: Machine;
105
+ spec: Machine;
106
+ relation: Law[];
107
+ witness: Record<string, E>;
108
+ stutter?: C;
109
+ };
110
+ } | {
111
+ id: string;
112
+ loop: Loop;
113
+ rank: E | E[];
114
+ laws?: Law[];
115
+ } | {
116
+ id: string;
117
+ goal: Goal;
118
+ };
119
+ export declare function toLean(items: LeanItem[], opts?: {
120
+ title?: string;
121
+ }): string;
122
+ export declare function leanProject(file: string, opts?: {
123
+ name?: string;
124
+ }): Record<string, string>;