gdp-wired 0.17.0 → 0.19.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,6 @@
1
1
  import { add, k, refuteFast, scale, solve, sub, v } from "../core/solver.js";
2
- import { crossCyclic, cyclicMachine, cyclicOn, machineNodes, proveCyclic } from "../core/cyclic.js";
2
+ import { checkRefutation } from "../core/check.js";
3
+ import { allowNode, certCyclic, certMs, certOn, certSearch, closingInsts, crossCyclic, cyclicOn, cycLog, hasQuantified, machineNodes, proveCyclic, qflagNodes, reqTS, searchedStepNodes, skMachine, timedKind, withWitness, translatedProof, uncoveredWhy } from "../core/cyclic.js";
3
4
  import { assumed, atomOf, boundOf, canon, canonFlip, columnTerm, decided, factorsOf, isCell, isMonomial, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, leafConstraintsReusing, reusing, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, witnessesOf, withProducts } from "./kernel.js";
4
5
  // The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
5
6
  function openE(e, path, out) {
@@ -77,7 +78,10 @@ function openH(c, path, out, positive = true) {
77
78
  if (out.length === before && !positive && c.op === '=' && truth(c, path) === undefined)
78
79
  openC(c, path, out);
79
80
  }
80
- // Pairs of reads of one map whose keys the path doesn't yet compare.
81
+ // Pairs of reads of one map whose keys the path doesn't yet compare. With `pairedKeys.skip` (core/data.ts), reads at
82
+ // keys a pairing builds ((a + b)² + b, keys whose difference has monomials) are left uncompared: that encoding states
83
+ // their congruence as facts, and comparing their keys only brings products into leaves its linear rows decide.
84
+ export const pairedKeys = { skip: false };
81
85
  function openCells(rows, path) {
82
86
  const names = [...new Set(rows.flatMap(r => Object.keys(r.lin)))].filter(isCell);
83
87
  const out = [];
@@ -87,7 +91,27 @@ function openCells(rows, path) {
87
91
  if (p.slice(0, p.indexOf('[')) !== q.slice(0, q.indexOf('[')))
88
92
  continue;
89
93
  const d = sub(parse(keyText(p)), parse(keyText(q)));
94
+ if (pairedKeys.skip && Object.keys(d).some(isMonomial))
95
+ continue;
90
96
  const dec = (lin) => path.get(canon(lin))?.holds ?? (path.has(canonFlip(lin)) ? !path.get(canonFlip(lin)).holds : undefined);
97
+ // Two cells of a run's map (core/cyclic.ts: time u and key k at u + k·Ω) at keys k·Ω and k'·Ω: k = k' first, a
98
+ // linear atom; the keys' own difference only where k = k' (its other side then refuted by (k − k')·Ω = 0, a product).
99
+ const q0 = cellProducts.on ? quotient(d) : undefined;
100
+ if (q0) {
101
+ const qa = dec(q0), qb = dec(scale(q0, -1));
102
+ if (qa === undefined) {
103
+ out.push(q0);
104
+ continue;
105
+ }
106
+ if (qa === true && qb === undefined) {
107
+ out.push(scale(q0, -1));
108
+ continue;
109
+ }
110
+ if (!(qa && qb))
111
+ continue;
112
+ cellDiffs.set(canon(d), q0);
113
+ cellDiffs.set(canon(scale(d, -1)), scale(q0, -1));
114
+ }
91
115
  const a = dec(d), b = dec(scale(d, -1));
92
116
  if (a === undefined)
93
117
  out.push(d);
@@ -96,6 +120,24 @@ function openCells(rows, path) {
96
120
  }
97
121
  return out;
98
122
  }
123
+ // A key difference q·F over monomials that share one factor F (Ω), with no other part: q, else undefined.
124
+ const quotient = (d) => {
125
+ const keys = Object.keys(d);
126
+ if (!keys.length || keys.some(key => key === '1' || !isMonomial(key)))
127
+ return undefined;
128
+ const shared = factorsOf(keys[0]).find(f => keys.every(key => factorsOf(key).includes(f)));
129
+ if (!shared)
130
+ return undefined;
131
+ const out = {};
132
+ for (const key of keys) {
133
+ const fs = [...factorsOf(key)];
134
+ fs.splice(fs.indexOf(shared), 1);
135
+ out[fs.join('*')] = (out[fs.join('*')] ?? 0) + d[key];
136
+ }
137
+ return out;
138
+ };
139
+ // The cells' key differences split at their quotient's equality, by canonical text: their quotient.
140
+ const cellDiffs = new Map();
99
141
  function parse(text) {
100
142
  const out = {};
101
143
  let depth = 0, start = 0;
@@ -225,7 +267,7 @@ function growPlain(goal, path, budget) {
225
267
  return { stuck: 'unknown', path, why: outOfBudget(goal, budget.nodes > 200_000 ? 'nodes' : 'time', path.size) };
226
268
  // Rows that read a term column only once a cut has put one on the path; before, the rows a choice-free search uses
227
269
  // (what it saw before term columns), renumbered to the whole leaf's when a leaf is found.
228
- const full = timed(() => leafConstraintsReusing(goal, path), ms => { profile.rowsMs += ms; });
270
+ const full = timed(() => rowsAt(goal, path), ms => { profile.rowsMs += ms; });
229
271
  const columns = [...path.values()].some(a => Object.keys(a.lin).some(key => key.startsWith('⟦')));
230
272
  const idx = full.flatMap((r, i) => (columns || !Object.keys(r.lin).some(key => key.startsWith('⟦')) ? [i] : []));
231
273
  const rows = idx.map(i => full[i]), at = (i) => (i < rows.length ? idx[i] : full.length + i - rows.length);
@@ -278,6 +320,21 @@ function growPlain(goal, path, budget) {
278
320
  return { tree: { leaf: fast.steps }, nodes: budget.nodes };
279
321
  }
280
322
  let split = next ?? (verdict.kind === 'unknown' ? bisect(rows, path) ?? fractional(verdict.point, path) : undefined);
323
+ // A cell difference's failing side (k = k' on the path, the keys apart): (k − k')·Ω = 0 refutes it, a product of each
324
+ // of the path's two atoms k − k' <= 0 and k' − k <= 0 with Ω >= 0.
325
+ if (cellProducts.on && path.size) {
326
+ const [id, last] = [...path].at(-1), q0 = !last.holds ? cellDiffs.get(id) : undefined;
327
+ if (q0) {
328
+ const at = (lin) => rows.findIndex(r => canon(r.lin) === canon(lin));
329
+ const d = last.lin, f = factorsOf(Object.keys(d)[0]).find(x => !Object.keys(q0).includes(x) && Object.keys(d).every(key => factorsOf(key).includes(x)));
330
+ const e1 = at(q0), e2 = at(scale(q0, -1)), sign = f === undefined ? -1 : at({ [f]: -1 });
331
+ if (e1 >= 0 && e2 >= 0 && sign >= 0) {
332
+ const pairs = [[e1, sign], [e2, sign]], out = solve(withProducts(rows, pairs));
333
+ if (out.kind === 'refuted')
334
+ return { tree: leafOf(out.steps, pairs), nodes: budget.nodes };
335
+ }
336
+ }
337
+ }
281
338
  // Products: with monomials about, try every pair of rows multiplied; failing that, split a factor at zero.
282
339
  const monomials = rows.some(r => Object.keys(r.lin).some(isMonomial));
283
340
  // A witness that multiplies out (each monomial's value is its factors' product) is a real model: nothing to refute.
@@ -500,6 +557,24 @@ function squareForms(rows) {
500
557
  // the decision's own row, and any row only it makes, are not there), every split kept. The derivations are the same
501
558
  // arithmetic over the same rows, and the kernel checks the whole tree in the end. Undefined at the first leaf that
502
559
  // needs the decision.
560
+ // A leaf's rows, by its path's decisions in order (what they are a function of): a lift reads every leaf of a subtree
561
+ // at its own path, found when it was grown, and at the path without the split, which the next lift up reads again.
562
+ const rowsSeen = new WeakMap();
563
+ function rowsAt(goal, path) {
564
+ let byPath = rowsSeen.get(goal);
565
+ if (!byPath)
566
+ rowsSeen.set(goal, (byPath = new Map()));
567
+ let id = '';
568
+ for (const [key, a] of path)
569
+ id += `${a.holds ? '+' : '-'}${key}|`;
570
+ let rows = byPath.get(id);
571
+ if (!rows) {
572
+ if (byPath.size >= 4096)
573
+ byPath.clear();
574
+ byPath.set(id, (rows = leafConstraintsReusing(goal, path)));
575
+ }
576
+ return rows;
577
+ }
503
578
  function lift(goal, tree, from, to) {
504
579
  const own = to.size; // the decision's row: the last of `from`'s path rows, below every later split's
505
580
  const reads = (t) => ('leaf' in t ? t.leaf.some(st => st.kind === 'input' && st.index === own) || (t.products ?? []).some(([i, j]) => i === own || j === own) : reads(t.yes) || reads(t.no));
@@ -514,7 +589,7 @@ function lift(goal, tree, from, to) {
514
589
  const no = yes && go(t.no, new Map(a).set(sid, { lin: t.split, holds: false }), new Map(b).set(sid, { lin: t.split, holds: false }));
515
590
  return yes && no ? { split: t.split, yes, no } : undefined;
516
591
  }
517
- const ra = leafConstraintsReusing(goal, a), rb = leafConstraintsReusing(goal, b), at = new Map();
592
+ const ra = rowsAt(goal, a), rb = rowsAt(goal, b), at = new Map();
518
593
  rb.forEach((r, i) => { const key = canon(r.lin); if (!at.has(key))
519
594
  at.set(key, i); });
520
595
  const map = (i) => (i < ra.length ? at.get(canon(ra[i].lin)) : undefined);
@@ -540,6 +615,86 @@ function lift(goal, tree, from, to) {
540
615
  };
541
616
  return go(tree, from, to);
542
617
  }
618
+ // A tree for a weaker goal (some hypotheses left out, some conjuncts of one left out) made a tree for the whole: at any
619
+ // path a leaf's rows are among the whole goal's (each hypothesis' facts are a hypothesis' own, the cells, terms and
620
+ // naturals they read a subset), so each input is renumbered to the same row there, a square or product past the rows
621
+ // moved by the difference. Where a dropped conjunct makes the whole hypothesis false under the path, its row 1 <= 0
622
+ // refutes the leaf alone; a leaf neither maps is grown again on the whole goal.
623
+ // `opaque`: variables the weaker goal has for terms of the whole (a step's value, read the same in two hypotheses), each
624
+ // read as the term's linear form at the whole goal's path; a split the whole's path already decides is followed, not
625
+ // made again.
626
+ export function widen(part, whole, tree, opaque = {}, pp = new Map(), wp = pp) {
627
+ const names = Object.keys(opaque);
628
+ const rename = (lin) => {
629
+ if (!names.length)
630
+ return lin;
631
+ let out = {};
632
+ for (const [key, c] of Object.entries(lin)) {
633
+ const e = opaque[key];
634
+ if (e) {
635
+ const l = linear(e, wp);
636
+ if (!l)
637
+ return undefined;
638
+ out = add(out, scale(l, c));
639
+ }
640
+ else if (names.some(n => key.includes(n)))
641
+ return undefined;
642
+ else
643
+ out = add(out, { [key]: c });
644
+ }
645
+ return out;
646
+ };
647
+ if (!('leaf' in tree)) {
648
+ const id = canon(tree.split), wl = rename(tree.split);
649
+ if (!wl) {
650
+ const o = grow(whole, wp);
651
+ return 'tree' in o ? o.tree : undefined;
652
+ }
653
+ const wid = canon(wl), known = Object.keys(wl).every(key => key === '1') ? (wl['1'] ?? 0) <= 0 : wp.has(wid) ? wp.get(wid).holds : wp.has(canonFlip(wl)) ? !wp.get(canonFlip(wl)).holds : undefined;
654
+ if (known !== undefined)
655
+ return widen(part, whole, known ? tree.yes : tree.no, opaque, new Map(pp).set(id, { lin: tree.split, holds: known }), wp);
656
+ const yes = widen(part, whole, tree.yes, opaque, new Map(pp).set(id, { lin: tree.split, holds: true }), new Map(wp).set(wid, { lin: wl, holds: true }));
657
+ const no = yes && widen(part, whole, tree.no, opaque, new Map(pp).set(id, { lin: tree.split, holds: false }), new Map(wp).set(wid, { lin: wl, holds: false }));
658
+ return yes && no ? { split: wl, yes, no } : undefined;
659
+ }
660
+ const rp = rowsAt(part, pp), rw = rowsAt(whole, wp), at = new Map();
661
+ rw.forEach((r, i) => { const key = canon(r.lin); if (!at.has(key))
662
+ at.set(key, i); });
663
+ const map = (i) => { if (i >= rp.length)
664
+ return i - rp.length + rw.length; const l = rename(rp[i].lin); return l && at.get(canon(l)); };
665
+ const leaf = [], products = [], squares = (tree.squares ?? []).map(rename);
666
+ let ok = squares.every(Boolean);
667
+ for (const st of ok ? tree.leaf : []) {
668
+ if (st.kind !== 'input') {
669
+ leaf.push(st);
670
+ continue;
671
+ }
672
+ const j = map(st.index);
673
+ if (j === undefined) {
674
+ ok = false;
675
+ break;
676
+ }
677
+ leaf.push({ ...st, index: j });
678
+ }
679
+ for (const [i, j] of ok ? tree.products ?? [] : []) {
680
+ const pi = map(i), pj = map(j);
681
+ if (pi === undefined || pj === undefined) {
682
+ ok = false;
683
+ break;
684
+ }
685
+ products.push([pi, pj]);
686
+ }
687
+ // (renamed, a derivation through products or cuts is replayed: a cut's divisor may not divide the terms' coefficients)
688
+ if (ok && names.length && !checkRefutation(withProducts(rw, products, squares), leaf))
689
+ ok = false;
690
+ if (ok)
691
+ return { leaf, ...(tree.products ? { products } : {}), ...(tree.squares ? { squares: squares } : {}) };
692
+ const absurd = rw.findIndex(r => Object.keys(r.lin).every(key => key === '1') && (r.lin['1'] ?? 0) > 0);
693
+ if (absurd >= 0)
694
+ return { leaf: [{ kind: 'input', index: absurd }] };
695
+ const out = grow(whole, wp);
696
+ return 'tree' in out ? out.tree : undefined;
697
+ }
543
698
  // Whether each hypothesis reads only names the goal's conclusion (its last hypothesis) reads.
544
699
  const nearSeen = new WeakMap();
545
700
  const namesOf = (t) => (t === undefined ? [] : [...JSON.stringify(t).matchAll(/"(?:name|map)":"([^"]+)"/g)].map(m => m[1]));
@@ -1051,9 +1206,9 @@ function triggered(m, laws, inst, build, focus = []) {
1051
1206
  // fewer hypotheses (a law about pairs of items with 900 instance hypotheses and 30 leaves using a few of them, say).
1052
1207
  export const minimal = { on: false };
1053
1208
  // The hypotheses a tree's leaves read: each leaf's input rows, and the rows its products multiply.
1054
- function usedHyps(goal, tree, path = new Map(), out = new Set()) {
1209
+ export function usedHyps(goal, tree, path = new Map(), out = new Set()) {
1055
1210
  if ('leaf' in tree) {
1056
- const rows = leafConstraints(goal, path), n = rows.length, sq = tree.squares?.length ?? 0;
1211
+ const rows = rowsAt(goal, path), n = rows.length, sq = tree.squares?.length ?? 0;
1057
1212
  const mark = (i) => {
1058
1213
  if (i < n) {
1059
1214
  const s = rows[i].src;
@@ -1207,15 +1362,54 @@ function proveLawsAsGiven(m, laws) {
1207
1362
  focus: [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])],
1208
1363
  });
1209
1364
  });
1210
- const t0 = performance.now(), out = proveThinned(m, laws, { ...instances }), usualMs = performance.now() - t0;
1365
+ // (GDP_CERT=cyclic: a covered machine's certificate is its cyclic proof, core/cyclic.ts's certCyclic; the usual trees
1366
+ // are its input, not checked again)
1367
+ // (what no instance covers, read only where a cyclic mode is on: the usual route's own proofs, inferLaws' trial ones
1368
+ // among them, do nothing more than before)
1369
+ // A machine with a history: its laws are its plain machine's (historyMachine; the record goals leaf goals beside them).
1370
+ const modes = cyclicOn() || certOn(), hist = modes && m.history ? historyMachine(m) : undefined, mc = hist ? hist.machine : m;
1371
+ const why = modes ? uncoveredWhy(mc, laws) : undefined;
1372
+ // (flags that hold at every key: searched, where GDP_CERT_SEARCH=1)
1373
+ const q = why === 'a flag at every key' && certOn() && certSearch(), cert = certOn() && (!why || q);
1374
+ usualCheck.on = !cert || q;
1375
+ const t0 = performance.now(), out = (() => { try {
1376
+ return proveThinned(m, laws, { ...instances });
1377
+ }
1378
+ finally {
1379
+ usualCheck.on = true;
1380
+ } })(), usualMs = performance.now() - t0;
1381
+ if (out.proof)
1382
+ usualProof.last = { m, laws, proof: out.proof };
1383
+ const inst = out.proof?.instances ?? instances;
1384
+ const searched = () => { const { facts, specs } = q ? qflagNodes(mc, laws, inst) : machineNodes(mc, laws, inst); return proveCyclic(facts, specs); };
1385
+ const translated = !q && out.proof ? ((st) => translatedProof(() => JSON.stringify(['machine', mc, laws, inst]), () => timedKind('instance', 0, () => machineNodes(mc, laws, inst, out.proof, st)), st)) : undefined;
1211
1386
  if (cyclicOn()) {
1212
- if (cyclicMachine(m, laws))
1213
- crossCyclic('machine', m.name, out.proven && out.checked, () => { const { facts, specs } = machineNodes(m, laws, instances); return proveCyclic(facts, specs); }, usualMs);
1387
+ if (!why)
1388
+ crossCyclic('machine', m.name, out.proven && out.checked, searched, usualMs, translated);
1214
1389
  else
1215
- console.error(`cyclic: ${JSON.stringify({ kind: 'machine', name: m.name, usual: out.proven && out.checked, uncovered: true })}`);
1390
+ cycLog({ kind: 'machine', name: m.name, usual: out.proven && out.checked, uncovered: true, why });
1391
+ }
1392
+ if (cert && out.proven && out.proof) {
1393
+ const until = performance.now() + certMs(), c = q ? withDeadline(until, () => certCyclic('machine', m.name, undefined, searched, usualMs)) : certCyclic('machine', m.name, translated, searched, usualMs);
1394
+ if (q && !c.checked)
1395
+ uncovered('machine', m.name, `${why}: no cyclic proof found${performance.now() > until ? ` within ${certMs()} ms` : ''}`);
1396
+ else {
1397
+ // (a history's record goals checked as they are: leaf goals)
1398
+ const records = !hist || hist.goals.every(g => out.proof.trees[g.name] !== undefined && check(g, out.proof.trees[g.name]));
1399
+ out.checked = c.checked && records;
1400
+ Object.assign(out.proof, { cyclic: c.proof });
1401
+ }
1216
1402
  }
1403
+ else if (certOn() && why && out.proven)
1404
+ cycLog({ kind: 'machine', name: m.name, cert: true, uncovered: true, why });
1217
1405
  return out;
1218
1406
  }
1407
+ // A proof of a kind no cyclic instance covers, counted (`cyclic: {..., uncovered: true}`).
1408
+ const uncovered = (kind, name, why) => cycLog({ kind, name, uncovered: true, why });
1409
+ // Whether the usual proof's trees are checked (off while GDP_CERT=cyclic makes the certificate a cyclic proof).
1410
+ export const usualCheck = { on: true };
1411
+ // The last machine proof made (core/dsl.ts's recurrences read their machine's, to translate it: core/cyclic.ts).
1412
+ export const usualProof = {};
1219
1413
  // Each law with its instances thinned (`triggered`) where that proves it, else with all of them.
1220
1414
  function proveThinned(m, laws, instances) {
1221
1415
  const trees = {}, fulls = { ...instances };
@@ -1261,7 +1455,7 @@ function proveThinned(m, laws, instances) {
1261
1455
  leaves += countLeaves(out.tree);
1262
1456
  }
1263
1457
  const proof = { instances, trees };
1264
- return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
1458
+ return { proven: true, nodes, leaves, checked: !usualCheck.on || checkLaws(m, laws, proof), proof };
1265
1459
  }
1266
1460
  // Every goal of the laws with these instances.
1267
1461
  function proveWith(m, laws, instances) {
@@ -1276,7 +1470,7 @@ function proveWith(m, laws, instances) {
1276
1470
  leaves += countLeaves(out.tree);
1277
1471
  }
1278
1472
  const proof = { instances, trees };
1279
- return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
1473
+ return { proven: true, nodes, leaves, checked: !usualCheck.on || checkLaws(m, laws, proof), proof };
1280
1474
  }
1281
1475
  export function proveStepLaws(m, given, stepLaws) {
1282
1476
  const base = proveLaws(m, given);
@@ -1296,6 +1490,7 @@ export function proveStepLaws(m, given, stepLaws) {
1296
1490
  function proveStepLawsAsGiven(m, laws, stepLaws, base) {
1297
1491
  if (!base.proof)
1298
1492
  return { proven: false, nodes: base.nodes, leaves: base.leaves, checked: false };
1493
+ const t0 = performance.now();
1299
1494
  const plain = m.history ? historyMachine(m).machine : m;
1300
1495
  // Each step law its own instances, from its own goal: the keys the step and the laws read, and its own (a k-step
1301
1496
  // law's later frames read keys no other step law has); where positions shift or a law is about several keys, the
@@ -1350,12 +1545,44 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
1350
1545
  leaves += countLeaves(out.tree);
1351
1546
  }
1352
1547
  const proof = { laws: base.proof, instances, ...(Object.keys(perStep).length ? { perStep } : {}), trees };
1353
- return { proven: true, nodes, leaves, checked: checkStepLaws(m, laws, stepLaws, proof), proof };
1548
+ const out = { proven: true, nodes, leaves, checked: checkStepLaws(m, laws, stepLaws, proof), proof };
1549
+ // Step laws of a machine the cyclic instances cover (one step, or several over no map) are translated with the laws
1550
+ // (core/cyclic.ts); the rest are counted as no instance's.
1551
+ if (!cyclicOn() && !certOn())
1552
+ return out;
1553
+ // (a machine with a history: its plain machine's; flags at every key and k-step laws over a map searched, where
1554
+ // GDP_CERT_SEARCH=1)
1555
+ const why = uncoveredWhy(plain, laws), one = stepLaws.filter(sl => (sl.steps ?? 1) === 1 || (!Object.keys(plain.maps ?? {}).length && !plain.inputMaps?.length));
1556
+ if (!why && one.length && out.checked) {
1557
+ const steps = one.map(sl => { const inst = perStep[sl.name] ?? instances, goal = stepLawObligations(m, laws, inst, sl)[0]; return { sl, inst, goal, tree: trees[goal.name] }; });
1558
+ const translated = (st) => translatedProof(() => JSON.stringify(['steps', plain, laws, base.proof.instances, steps.map(x => [x.sl, x.inst])]), reused => timedKind('instance', 0, () => machineNodes(plain, laws, base.proof.instances, { trees: base.proof.trees, steps, lawsMade: reused }, st)), st, () => JSON.stringify(['machine', plain, laws, base.proof.instances]));
1559
+ const usualMs = performance.now() - t0;
1560
+ if (cyclicOn())
1561
+ crossCyclic('step law', m.name, true, undefined, usualMs, translated);
1562
+ if (certOn())
1563
+ out.checked = certCyclic('step law', m.name, translated, undefined, usualMs).checked;
1564
+ }
1565
+ const rest = stepLaws.filter(sl => why || !one.includes(sl));
1566
+ if (certOn() && certSearch() && out.checked && rest.length && (!why || why === 'a flag at every key')) {
1567
+ // The rest searched: the laws' nodes (searched), each step law's node (core/cyclic.ts's stepLawNodes).
1568
+ const searchedSteps = () => {
1569
+ const b = why ? qflagNodes(plain, laws, base.proof.instances) : machineNodes(plain, laws, base.proof.instances);
1570
+ return proveCyclic(b.facts, [...b.specs, ...searchedStepNodes(plain, laws, rest.map(sl => ({ law: sl, instances: perStep[sl.name] ?? instances })), b.facts, b.specs)]);
1571
+ };
1572
+ const until = performance.now() + certMs(), c = withDeadline(until, () => certCyclic('step law', m.name, undefined, searchedSteps, performance.now() - t0));
1573
+ const none = `no cyclic proof found${performance.now() > until ? ` within ${certMs()} ms` : ''}`;
1574
+ for (const sl of c.checked ? [] : rest)
1575
+ uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, `${why ?? `${sl.steps} steps over a map`}: ${none}`);
1576
+ }
1577
+ else if (cyclicOn() || certOn())
1578
+ for (const sl of rest)
1579
+ uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, why ?? `${sl.steps} steps over a map`);
1580
+ return out;
1354
1581
  }
1355
1582
  // Progress (kernel.ts's progressObligations): the laws proven first, then each goal, the every-key laws assumed at every
1356
1583
  // key the goals read a map at.
1357
1584
  export function proveProgress(m, laws, p) {
1358
- const base = proveLaws(m, laws);
1585
+ const t0 = performance.now(), base = proveLaws(m, laws);
1359
1586
  if (!base.proven || !base.checked)
1360
1587
  return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
1361
1588
  const ls = base.laws ?? laws;
@@ -1370,12 +1597,42 @@ export function proveProgress(m, laws, p) {
1370
1597
  nodes += out.nodes;
1371
1598
  leaves += countLeaves(out.tree);
1372
1599
  }
1373
- return { proven: true, nodes, leaves, checked: progressObligations(m, ls, instances, p).every(g => check(g, trees[g.name])), instances, trees };
1600
+ const checked = progressObligations(m, ls, instances, p).every(g => check(g, trees[g.name]));
1601
+ // (GDP_CERT=cyclic with GDP_CERT_SEARCH=1: a request allowed whose claim is that alone, over a machine without a history
1602
+ // or carried maps, gets core/cyclic.ts's allowNode after the laws' nodes, BridgeRules.lean's cycAOk: from every
1603
+ // reachable state, every request meeting the region, its terms and the witnesses withWitness finds is allowed)
1604
+ const alone = JSON.stringify(p.then) === JSON.stringify(trueC) && !m.history && !m.inputMaps?.length && m.assume !== undefined;
1605
+ if (certOn() && certSearch() && alone && checked && base.proof) {
1606
+ const until = performance.now() + certMs(), lp = base.proof, q = hasQuantified(m);
1607
+ const allowed = () => {
1608
+ const b = q ? qflagNodes(m, ls, lp.instances) : machineNodes(m, ls, lp.instances, lp);
1609
+ if (b.why || (b.specs[0]?.rank.length ?? 2) < 2)
1610
+ return { proven: false, checked: false, node: m.name, goal: 'allowed', stuck: b.why ?? 'laws ranked by time alone' };
1611
+ let last = { proven: false, checked: false, node: m.name, goal: 'allowed', stuck: 'no witness' };
1612
+ const found = withWitness(m, p.request, wit => {
1613
+ if (performance.now() > until)
1614
+ return undefined;
1615
+ const w = { op: 'and', xs: [p.when, reqTS(Object.entries(p.request)), reqTS(wit)] };
1616
+ const spec = allowNode(skMachine(m), ls, instances, w, b.facts, b.specs, `${m.name}: allowed`, q ? closingInsts(m, b.facts, [instances, ...Object.values(lp.instances)], [{ op: 'var', name: 't$' }], ls) : []);
1617
+ last = proveCyclic(b.facts, [...b.specs, spec]);
1618
+ return last.proven && last.checked ? last : undefined;
1619
+ });
1620
+ return found?.out ?? last;
1621
+ };
1622
+ const c = withDeadline(until, () => certCyclic('progress', m.name, undefined, allowed, performance.now() - t0));
1623
+ if (!c.checked)
1624
+ uncovered('progress', m.name, `a request allowed: no cyclic proof found${performance.now() > until ? ` within ${certMs()} ms` : ''}`);
1625
+ }
1626
+ else if (cyclicOn() || certOn())
1627
+ uncovered('progress', m.name, alone ? 'a request allowed' : 'a request allowed and a claim after it');
1628
+ return { proven: true, nodes, leaves, checked, instances, trees };
1374
1629
  }
1375
1630
  // Liveness (kernel.ts's livenessParts): its step laws, proven together. The claim needs no more (a run takes the
1376
1631
  // request, so it is allowed); but a request never allowed would leave the claim about no run, so where a helpful request
1377
1632
  // names every input a client sends and carries no maps, it is proven allowed in its region too.
1378
1633
  export function proveLiveness(m, laws, l) {
1634
+ if (cyclicOn() || certOn())
1635
+ uncovered('liveness', m.name, 'a claim about runs that never end');
1379
1636
  const { stay, cover, helps } = livenessParts(m, l);
1380
1637
  const a = proveStepLaws(m, laws, [stay, cover, ...helps]);
1381
1638
  if (!a.proven || !a.checked)
@@ -37,6 +37,7 @@ export declare function readable(id: string, laws: Law[], vars: string[], maps:
37
37
  binders: string;
38
38
  hyp: string;
39
39
  call: string;
40
+ have?: (j: number) => string;
40
41
  }): string;
41
42
  export type ProgressCase = {
42
43
  id: string;
@@ -61,6 +62,10 @@ export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts
61
62
  stepLaws?: StepLaw[];
62
63
  }): string;
63
64
  export declare const hasSets: (m: Machine) => boolean;
65
+ export declare function machineLit(mm: Machine, t: {
66
+ e: (x: E) => string;
67
+ c: (x: C) => string;
68
+ }, ix: (name: string) => number, mx: (name: string) => number): string;
64
69
  export declare function smachineToLean(id: string, given: Machine, laws: Law[], opts?: {
65
70
  stepLaws?: StepLaw[];
66
71
  liveness?: Liveness[];
@@ -96,6 +101,39 @@ export declare function smachineToLean(id: string, given: Machine, laws: Law[],
96
101
  };
97
102
  noFacts?: boolean;
98
103
  }): string;
104
+ export declare const cyclicCerts: () => boolean;
105
+ export declare const certReport: {
106
+ cyclic: string[];
107
+ usual: {
108
+ id: string;
109
+ why: string;
110
+ }[];
111
+ translatedSteps: string[];
112
+ };
113
+ export declare function cycMachineToLean(id: string, given: Machine, laws: Law[], opts?: {
114
+ stepLaws?: StepLaw[];
115
+ liveness?: Liveness[];
116
+ simp?: string[];
117
+ free?: string[];
118
+ ghosts?: string[];
119
+ after?: (ix: (name: string) => number) => string;
120
+ loop?: {
121
+ loop: Loop;
122
+ };
123
+ noFacts?: boolean;
124
+ refined?: {
125
+ impl: Machine;
126
+ spec: Machine;
127
+ wit: Record<string, E>;
128
+ stutter: C;
129
+ specPre: number;
130
+ step: StepLaw;
131
+ };
132
+ }): {
133
+ text: string;
134
+ } | {
135
+ why: string;
136
+ };
99
137
  type Uses = Record<string, {
100
138
  lean?: string;
101
139
  recur?: Recurrence;
@@ -152,15 +190,71 @@ export type LeanItem = {
152
190
  proof: Cyclic;
153
191
  }[];
154
192
  after?: (ix: (name: string) => number, mx: (map: string) => number) => string;
193
+ bridge?: CycBridge;
194
+ recur?: CycRecur;
195
+ fold?: {
196
+ fold: Fold;
197
+ laws: Law[];
198
+ };
199
+ data?: true;
155
200
  };
156
201
  export declare function toLean(items: LeanItem[], opts?: {
157
202
  title?: string;
158
203
  native?: boolean;
204
+ cyclic?: boolean;
159
205
  }): string;
160
206
  export declare function leanProject(file: string, opts?: {
161
207
  name?: string;
162
208
  }): Record<string, string>;
209
+ export type CycBridge = {
210
+ machine: Machine;
211
+ laws: Law[];
212
+ tampered?: {
213
+ what: string;
214
+ machine: Machine;
215
+ }[];
216
+ };
217
+ export type CycRecur = {
218
+ name: string;
219
+ args: string[];
220
+ zero: E;
221
+ next: E;
222
+ lemmas: {
223
+ name: string;
224
+ holds: C;
225
+ }[];
226
+ between: {
227
+ name: string;
228
+ holds: C;
229
+ }[];
230
+ };
163
231
  export declare function cyclicToLean(id: string, p: Cyclic, tampered?: {
164
232
  what: string;
165
233
  proof: Cyclic;
166
- }[], after?: (ix: (name: string) => number, mx: (map: string) => number) => string): string;
234
+ }[], after?: (ix: (name: string) => number, mx: (map: string) => number) => string, bridge?: CycBridge, recur?: CycRecur, fold?: {
235
+ fold: Fold;
236
+ laws: Law[];
237
+ }): string;
238
+ export declare function cycEncodingLean(id: string, facts: {
239
+ name: string;
240
+ keys: string[];
241
+ holds: C;
242
+ }[], nodes: {
243
+ name: string;
244
+ hyps: C[];
245
+ concl: C;
246
+ }[], b: CycBridge): string;
247
+ export declare function cycRecurCheckLean(id: string, facts: {
248
+ name: string;
249
+ keys: string[];
250
+ holds: C;
251
+ }[], r: CycRecur): string;
252
+ export declare function cycFoldCheckLean(id: string, facts: {
253
+ name: string;
254
+ keys: string[];
255
+ holds: C;
256
+ }[], nodes: {
257
+ name: string;
258
+ hyps: C[];
259
+ concl: C;
260
+ }[], f: Fold, laws: Law[]): string;