gdp-wired 0.14.1 → 0.15.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.
@@ -212,11 +212,11 @@ export function termRows(name, path) {
212
212
  }
213
213
  }
214
214
  // An if is one of its two naturals, so at most their sum, whichever way its condition goes (a yes/no's if(c, 1, 0)
215
- // is at most 1 without deciding c). Only over branches that read no term column: one that did would bring that
216
- // column's rows into the leaf, and theirs, through every if a run of steps nests.
215
+ // is at most 1 without deciding c). Only over constant branches: over a variable one (a map's if(c, Given[k], 0)),
216
+ // each such row is one more in every leaf and slows the solve more than it saves.
217
217
  if (e.op === 'if') {
218
- const a = linear(e.then, path), b = linear(e.else, path), plain = (l) => Object.keys(l).every(key => !key.startsWith('⟦'));
219
- if (a && b && plain(a) && plain(b))
218
+ const a = linear(e.then, path), b = linear(e.else, path), constant = (l) => Object.entries(l).every(([key, c]) => key === '1' || c === 0);
219
+ if (a && b && constant(a) && constant(b))
220
220
  out.push(sub(col, add(a, b)));
221
221
  }
222
222
  if (decided(e, path)) {
@@ -226,20 +226,46 @@ export function termRows(name, path) {
226
226
  }
227
227
  return out;
228
228
  }
229
- // Products: multiply out, each monomial (variables joined by '*', sorted) one more natural variable. Forgetting how
230
- // monomials relate (x*x to x, say) only weakens what a leaf may use, so it stays sound; distributing is what proves
231
- // spend <= m·count over a node from the same law at its children. Map reads can't be factors.
229
+ // Products: multiply out, each monomial (its factors joined by '*', sorted) one more natural variable. A factor is a
230
+ // variable or a map's cell (used ≤ per·Seats[k] reads one); an open choice isn't (the path decides it first, and the
231
+ // product is of its branch). Forgetting how monomials relate (x*x to x, say, or two monomials over cells the path
232
+ // proves equal) only weakens what a leaf may use, so it stays sound; distributing is what proves spend <= m·count over
233
+ // a node from the same law at its children.
232
234
  function times(a, b) {
233
235
  const out = [];
234
236
  for (const [p, cp] of Object.entries(a))
235
237
  for (const [q, cq] of Object.entries(b)) {
236
- if (p.includes('[') || q.includes('[') || p.includes('⟦') || q.includes('⟦'))
237
- return undefined; // factors are variables (and monomials of them)
238
- const factors = [...(p === '1' ? [] : p.split('*')), ...(q === '1' ? [] : q.split('*'))].sort();
239
- out.push({ [factors.length ? factors.join('*') : '1']: cp * cq });
238
+ if (p.includes('⟦') || q.includes('⟦'))
239
+ return undefined;
240
+ const fs = [...(p === '1' ? [] : factorsOf(p)), ...(q === '1' ? [] : factorsOf(q))].sort();
241
+ out.push({ [fs.length ? fs.join('*') : '1']: cp * cq });
240
242
  }
241
243
  return add(...out);
242
244
  }
245
+ // A name's factors: split at each '*' outside a cell's brackets (a cell's key may itself read a monomial).
246
+ const factorMemo = new Map();
247
+ export function factorsOf(name) {
248
+ let out = factorMemo.get(name);
249
+ if (out)
250
+ return out;
251
+ out = [];
252
+ let depth = 0, start = 0;
253
+ for (let i = 0; i <= name.length; i++) {
254
+ const ch = name[i];
255
+ if (ch === '[')
256
+ depth++;
257
+ else if (ch === ']')
258
+ depth--;
259
+ else if ((ch === '*' && depth === 0) || i === name.length) {
260
+ out.push(name.slice(start, i));
261
+ start = i + 1;
262
+ }
263
+ }
264
+ factorMemo.set(name, out);
265
+ return out;
266
+ }
267
+ export const isMonomial = (name) => factorsOf(name).length > 1;
268
+ export const isCell = (name) => name.includes('[') && !isMonomial(name);
243
269
  const comparisonMemo = new WeakMap(), freeComparison = new WeakMap();
244
270
  function comparison(c, path) {
245
271
  const free = choiceFree(c.a) && choiceFree(c.b);
@@ -426,7 +452,7 @@ function leafRows(goal, path, factsOf) {
426
452
  rows.push({ lin, label: '', src: { fact: i } });
427
453
  const names = new Set(rows.flatMap(r => Object.keys(r.lin).filter(key => key !== '1')));
428
454
  // Two reads of one map whose keys the path proves equal are equal.
429
- const cells = [...names].filter(name => name.includes('[')).map(name => ({ name, map: name.slice(0, name.indexOf('[')), key: keyOf(name) }));
455
+ const cells = [...names].filter(isCell).map(name => ({ name, map: name.slice(0, name.indexOf('[')), key: keyOf(name) }));
430
456
  for (const [i, p] of cells.entries())
431
457
  for (const q of cells.slice(i + 1)) {
432
458
  if (p.map !== q.map)
@@ -480,10 +506,16 @@ const keyOf = (cell) => {
480
506
  }
481
507
  return out;
482
508
  };
483
- // A leaf may also use the product of two of its rows: p <= 0 and q <= 0 give p·q >= 0, as nlinarith does. A pair may
484
- // name an earlier product row too (index past the leaf's rows), which is a row of the same kind, so degrees go higher.
485
- export function withProducts(rows, pairs) {
509
+ // A leaf may also use the product of two of its rows: p <= 0 and q <= 0 give p·q >= 0, as nlinarith does; and the
510
+ // square of any linear form t, which holds whatever t's sign: t·t >= 0 (a sum of squares, as a Positivstellensatz
511
+ // certificate has). The squares follow the leaf's rows, then the products; a pair may name a square or an earlier
512
+ // product (an index past the leaf's rows), each a row of the same kind, so degrees go higher.
513
+ export function withProducts(rows, pairs, squares = []) {
486
514
  const out = [...rows];
515
+ for (const t of squares) {
516
+ const tt = times(t, t);
517
+ out.push(tt ? { lin: scale(tt, -1), label: '' } : { lin: {}, label: '' });
518
+ }
487
519
  for (const [i, j] of pairs) {
488
520
  const p = out[i], q = out[j], pq = p && q ? times(p.lin, q.lin) : undefined;
489
521
  out.push(pq ? { lin: scale(pq, -1), label: '' } : { lin: {}, label: '' });
@@ -492,7 +524,7 @@ export function withProducts(rows, pairs) {
492
524
  }
493
525
  export function check(goal, tree, path = new Map()) {
494
526
  if ('leaf' in tree)
495
- return checkRefutation(withProducts(leafConstraints(goal, path), tree.products ?? []), tree.leaf);
527
+ return checkRefutation(withProducts(leafConstraints(goal, path), tree.products ?? [], tree.squares), tree.leaf);
496
528
  const id = canon(tree.split);
497
529
  if (path.has(id) || Object.keys(tree.split).every(key => key === '1'))
498
530
  return false;
@@ -53,6 +53,14 @@ export declare function proveLaws(m: Machine, laws: Law[]): Report & {
53
53
  trees: Record<string, Tree>;
54
54
  };
55
55
  };
56
+ export declare function proveLaw(m: Machine, laws: Law[], i: number): {
57
+ proven: true;
58
+ } | {
59
+ proven: false;
60
+ goal: string;
61
+ stuck: string;
62
+ witness?: Record<string, number>;
63
+ };
56
64
  type StepProof = {
57
65
  laws: {
58
66
  instances: Record<string, Instances>;
@@ -1,5 +1,5 @@
1
1
  import { add, k, refuteFast, scale, solve, sub, v } from "../core/solver.js";
2
- import { assumed, boundOf, canon, canonFlip, columnTerm, decided, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, leafConstraintsReusing, reusing, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, witnessesOf, withProducts } from "./kernel.js";
2
+ import { assumed, 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";
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) {
@@ -78,7 +78,7 @@ function openH(c, path, out, positive = true) {
78
78
  }
79
79
  // Pairs of reads of one map whose keys the path doesn't yet compare.
80
80
  function openCells(rows, path) {
81
- const names = [...new Set(rows.flatMap(r => Object.keys(r.lin)))].filter(n => n.includes('['));
81
+ const names = [...new Set(rows.flatMap(r => Object.keys(r.lin)))].filter(isCell);
82
82
  const out = [];
83
83
  const keyText = (n) => n.slice(n.indexOf('[') + 1, -1);
84
84
  for (const [i, p] of names.entries())
@@ -181,7 +181,10 @@ function growPlain(goal, path, budget) {
181
181
  const columns = [...path.values()].some(a => Object.keys(a.lin).some(key => key.startsWith('⟦')));
182
182
  const idx = full.flatMap((r, i) => (columns || !Object.keys(r.lin).some(key => key.startsWith('⟦')) ? [i] : []));
183
183
  const rows = idx.map(i => full[i]), at = (i) => (i < rows.length ? idx[i] : full.length + i - rows.length);
184
- const leafOf = (steps, products) => ({ leaf: steps.map(st => (st.kind === 'input' ? { ...st, index: at(st.index) } : st)), ...(products ? { products: products.map(([i, j]) => [at(i), at(j)]) } : {}) });
184
+ const leafOf = (steps0, products0, squares0) => {
185
+ const { steps, products, squares } = products0 || squares0 ? trimmed(rows.length, steps0, products0 ?? [], squares0 ?? []) : { steps: steps0, products: undefined, squares: undefined };
186
+ return { leaf: steps.map(st => (st.kind === 'input' ? { ...st, index: at(st.index) } : st)), ...(products?.length ? { products: products.map(([i, j]) => [at(i), at(j)]) } : {}), ...(squares?.length ? { squares } : {}) };
187
+ };
185
188
  // The conclusion's own atoms first (the goal's last hypothesis is the law refuted): a case it doesn't read is often
186
189
  // one no refutation needs, and splitting it first multiplies every case after it.
187
190
  const open = [];
@@ -226,18 +229,18 @@ function growPlain(goal, path, budget) {
226
229
  }
227
230
  let split = next ?? (verdict.kind === 'unknown' ? bisect(rows, path) ?? fractional(verdict.point, path) : undefined);
228
231
  // Products: with monomials about, try every pair of rows multiplied; failing that, split a factor at zero.
229
- const monomials = rows.some(r => Object.keys(r.lin).some(key => key.includes('*')));
232
+ const monomials = rows.some(r => Object.keys(r.lin).some(isMonomial));
230
233
  // A witness that multiplies out (each monomial's value is its factors' product) is a real model: nothing to refute.
231
234
  if (!split && monomials && verdict.kind === 'sat') {
232
235
  const w = verdict.witness, value = (key) => w[key] ?? 0;
233
- const real = rows.every(r => Object.keys(r.lin).every(key => !key.includes('*') || value(key) === key.split('*').reduce((acc, f) => acc * value(f), 1)));
236
+ const real = rows.every(r => Object.keys(r.lin).every(key => !isMonomial(key) || value(key) === factorsOf(key).reduce((acc, f) => acc * value(f), 1)));
234
237
  if (real)
235
238
  return { stuck: 'sat', witness: w, path };
236
239
  }
237
240
  if (!split && monomials && rows.length <= 40) {
238
- // Only rows about the factors (no map cells), each pair once: elimination over hundreds of products blows up.
239
- const factorSet = new Set(rows.flatMap(r => Object.keys(r.lin).filter(key => key.includes('*')).flatMap(key => key.split('*'))));
240
- const relevant = rows.flatMap((r, i) => (Object.keys(r.lin).every(key => key === '1' || (!key.includes('[') && key.split('*').every(f => factorSet.has(f)))) ? [i] : []));
241
+ // Only rows about the factors (no other map cells), each pair once: elimination over hundreds of products blows up.
242
+ const factorSet = new Set(rows.flatMap(r => Object.keys(r.lin).filter(isMonomial).flatMap(factorsOf)));
243
+ const relevant = rows.flatMap((r, i) => (Object.keys(r.lin).every(key => key === '1' || factorsOf(key).every(f => factorSet.has(f))) ? [i] : []));
241
244
  const pairs = [];
242
245
  for (const [a, i] of relevant.entries())
243
246
  for (const j of relevant.slice(a))
@@ -247,18 +250,40 @@ function growPlain(goal, path, budget) {
247
250
  if (product.kind === 'refuted')
248
251
  return { tree: leafOf(product.steps, pairs), nodes: budget.nodes };
249
252
  // Degree three: each row times each product of two variables (x·y >= 0 is the pair of -x <= 0 and -y <= 0).
250
- const degree = Math.max(...rows.flatMap(r => Object.keys(r.lin).map(key => (key === '1' ? 0 : key.split('*').length))));
253
+ const degree = Math.max(...rows.flatMap(r => Object.keys(r.lin).map(key => (key === '1' ? 0 : factorsOf(key).length))));
251
254
  if (degree >= 3) {
252
- const signs = rows.flatMap((r, i) => (Object.keys(r.lin).length === 1 && Object.values(r.lin)[0] < 0 && !Object.keys(r.lin)[0].includes('*') && !Object.keys(r.lin)[0].includes('[') ? [i] : []));
255
+ const signs = rows.flatMap((r, i) => { const key = Object.keys(r.lin)[0]; return Object.keys(r.lin).length === 1 && Object.values(r.lin)[0] < 0 && !isMonomial(key) && (!isCell(key) || factorSet.has(key)) ? [i] : []; });
253
256
  const sq = signs.flatMap((i, a) => signs.slice(a).map(j => [i, j])), cubes = [];
254
257
  for (let s = 0; s < sq.length; s++)
255
258
  for (let i = 0; i < rows.length; i++)
256
259
  cubes.push([rows.length + s, i]);
257
- const third = solve(withProducts(rows, [...sq, ...cubes]));
260
+ const third = solve(withProducts(rows, [...sq, ...cubes, ...pairs]));
258
261
  if (third.kind === 'refuted')
259
- return { tree: leafOf(third.steps, [...sq, ...cubes]), nodes: budget.nodes };
262
+ return { tree: leafOf(third.steps, [...sq, ...cubes, ...pairs]), nodes: budget.nodes };
263
+ }
264
+ // Sums of squares: each product the hypotheses multiply, x·y, gives (x − y)² >= 0, its factors as the path reads them
265
+ // (4·a'·b' <= (a' + b')² after a step that moves one from b to a is (a − b + 2)²); and each monomial written as u·w,
266
+ // u and w of half its degree (one factor set aside when the degree is odd), gives (u − w)² >= 0. With the pairs above,
267
+ // one solve finds a certificate of degree two in those forms (Cauchy–Schwarz's (ad − bc)²); past degree two, each
268
+ // square times each factor's sign too (a³ + b³ + c³ − 3abc is a sum of a·(b − c)² and the like).
269
+ const squares = [...new Map([...productForms(goal, path), ...squareForms(rows)].map(t => [canon(t), t])).values()].slice(0, 64);
270
+ if (squares.length) {
271
+ // The squares alone first (a small system), then with the pairs.
272
+ const alone = solve(withProducts(rows, [], squares));
273
+ if (alone.kind === 'refuted')
274
+ return { tree: leafOf(alone.steps, [], squares), nodes: budget.nodes };
275
+ const sos = solve(withProducts(rows, pairs, squares));
276
+ if (sos.kind === 'refuted')
277
+ return { tree: leafOf(sos.steps, pairs, squares), nodes: budget.nodes };
278
+ if (degree >= 3) {
279
+ const signs = rows.flatMap((r, i) => { const key = Object.keys(r.lin)[0]; return Object.keys(r.lin).length === 1 && Object.values(r.lin)[0] < 0 && factorSet.has(key) ? [i] : []; });
280
+ const weighted = [...pairs, ...signs.flatMap(i => squares.map((_, s) => [i, rows.length + s]))].slice(0, 600);
281
+ const third = solve(withProducts(rows, weighted, squares));
282
+ if (third.kind === 'refuted')
283
+ return { tree: leafOf(third.steps, weighted, squares), nodes: budget.nodes };
284
+ }
260
285
  }
261
- const factors = [...new Set(rows.flatMap(r => Object.keys(r.lin).filter(key => key.includes('*')).flatMap(key => key.split('*'))))];
286
+ const factors = [...new Set(rows.flatMap(r => Object.keys(r.lin).filter(isMonomial).flatMap(factorsOf)))];
262
287
  const fresh = (lin) => !path.has(canon(lin)) && !path.has(canonFlip(lin));
263
288
  // Split a factor at zero, else two factors against each other (so (x - y)² >= 0 becomes a product of rows).
264
289
  const zero = factors.find(f => fresh({ [f]: 1 }));
@@ -283,6 +308,98 @@ function growPlain(goal, path, budget) {
283
308
  return no;
284
309
  return { tree: { split, yes: yes.tree, no: no.tree }, nodes: budget.nodes };
285
310
  }
311
+ // A leaf's squares and products without those its derivation never reads (directly, or through a product that does),
312
+ // the rest renumbered: the same derivation over the same rows, so a smaller certificate (and a smaller Lean term).
313
+ function trimmed(n0, steps, pairs, squares) {
314
+ const used = new Set(steps.flatMap(st => (st.kind === 'input' && st.index >= n0 ? [st.index] : [])));
315
+ for (let j = pairs.length - 1; j >= 0; j--)
316
+ if (used.has(n0 + squares.length + j))
317
+ for (const i of pairs[j])
318
+ if (i >= n0)
319
+ used.add(i);
320
+ const keepS = squares.flatMap((_, s) => (used.has(n0 + s) ? [s] : [])), keepP = pairs.flatMap((_, j) => (used.has(n0 + squares.length + j) ? [j] : []));
321
+ const to = new Map();
322
+ keepS.forEach((s, r) => to.set(n0 + s, n0 + r));
323
+ keepP.forEach((j, r) => to.set(n0 + squares.length + j, n0 + keepS.length + r));
324
+ const re = (i) => (i < n0 ? i : to.get(i));
325
+ return {
326
+ steps: steps.map(st => (st.kind === 'input' ? { ...st, index: re(st.index) } : st)),
327
+ products: keepP.map(j => [re(pairs[j][0]), re(pairs[j][1])]),
328
+ squares: keepS.map(s => squares[s]),
329
+ };
330
+ }
331
+ // The difference of each product's two factors in the goal's hypotheses, as the path reads them (a factor with an open
332
+ // choice in it is left out: a product of one isn't a monomial).
333
+ const productMemo = new WeakMap();
334
+ function productForms(goal, path) {
335
+ let prods = productMemo.get(goal);
336
+ if (!prods) {
337
+ const found = [], seen = new WeakSet();
338
+ const walk = (t) => {
339
+ if (seen.has(t))
340
+ return;
341
+ seen.add(t);
342
+ if (t.op === 'prod')
343
+ found.push(t);
344
+ for (const val of Object.values(t))
345
+ if (Array.isArray(val))
346
+ val.forEach(x => walk(x));
347
+ else if (typeof val === 'object' && val !== null)
348
+ walk(val);
349
+ };
350
+ goal.hyps.forEach(walk);
351
+ productMemo.set(goal, (prods = found));
352
+ }
353
+ const out = [];
354
+ for (const e of prods) {
355
+ if (e.op !== 'prod')
356
+ continue;
357
+ const a = linear(e.a, path), b = linear(e.b, path);
358
+ if (!a || !b)
359
+ continue;
360
+ const t = sub(a, b);
361
+ if (Object.keys(t).some(key => key.includes('⟦')) || Object.entries(t).every(([key, c]) => key === '1' || c === 0))
362
+ continue;
363
+ out.push(Object.fromEntries(Object.entries(t).filter(([, c]) => c !== 0)));
364
+ }
365
+ return out;
366
+ }
367
+ // The forms whose squares a leaf may try: for each monomial, (u − w) for every way to split its factors (all of them,
368
+ // or all but one when there is an odd number) into two halves u and w that differ (u = w is a square already, a
369
+ // natural monomial). At most 64, from the smallest monomials up.
370
+ function squareForms(rows) {
371
+ const monos = [...new Set(rows.flatMap(r => Object.keys(r.lin).filter(isMonomial)))].sort((a, b) => factorsOf(a).length - factorsOf(b).length);
372
+ const out = new Map();
373
+ const name = (fs) => [...fs].sort().join('*');
374
+ const halves = (fs) => {
375
+ const pick = (from, chosen) => {
376
+ if (chosen.length === fs.length / 2) {
377
+ const u = name(chosen.map(i => fs[i])), w = name(fs.filter((_, i) => !chosen.includes(i)));
378
+ if (u < w) {
379
+ const t = { [u]: 1, [w]: -1 };
380
+ out.set(canon(t), t);
381
+ }
382
+ return;
383
+ }
384
+ for (let i = from; i < fs.length; i++)
385
+ pick(i + 1, [...chosen, i]);
386
+ };
387
+ pick(0, []);
388
+ };
389
+ for (const m of monos) {
390
+ const fs = factorsOf(m);
391
+ if (fs.length > 6)
392
+ continue;
393
+ if (fs.length % 2 === 0)
394
+ halves(fs);
395
+ else
396
+ for (let i = 0; i < fs.length; i++)
397
+ halves(fs.filter((_, j) => j !== i));
398
+ if (out.size >= 64)
399
+ break;
400
+ }
401
+ return [...out.values()].slice(0, 64);
402
+ }
286
403
  // A tree found under one more decision (`from`, the path with it) as a tree under the path without it (`to`), if no
287
404
  // leaf reads the decision: each leaf's rows found again among the rows the shorter path gives (the same rows, renumbered:
288
405
  // the decision's own row, and any row only it makes, are not there), every split kept. The derivations are the same
@@ -324,7 +441,7 @@ function lift(goal, tree, from, to) {
324
441
  return undefined;
325
442
  products.push([pi, pj]);
326
443
  }
327
- return { leaf, ...(t.products ? { products } : {}) };
444
+ return { leaf, ...(t.products ? { products } : {}), ...(t.squares ? { squares: t.squares } : {}) };
328
445
  };
329
446
  return go(tree, from, to);
330
447
  }
@@ -475,7 +592,7 @@ function bisect(rows, path) {
475
592
  // Branch and bound for unbounded systems: split on a variable the rational solution puts between two integers.
476
593
  function fractional(point, path) {
477
594
  for (const [x, value] of Object.entries(point ?? {})) {
478
- if (Math.abs(value - Math.round(value)) < 1e-9 || x.includes('*'))
595
+ if (Math.abs(value - Math.round(value)) < 1e-9 || isMonomial(x))
479
596
  continue;
480
597
  const c = Math.floor(value), lin = c === 0 ? { [x]: 1 } : { [x]: 1, 1: -c };
481
598
  if (!path.has(canon(lin)) && !path.has(canonFlip(lin)))
@@ -790,6 +907,26 @@ export function proveLaws(m, laws) {
790
907
  return { proven: false, nodes: 0, leaves: 0, failed: { goal: 'witnesses', stuck: out.stuck }, checked: false };
791
908
  return { ...proveLawsAsGiven(m, out.items), laws: out.items };
792
909
  }
910
+ // One law's two goals, every law assumed (core/infer.ts adds laws one at a time and reproves only what isn't proven
911
+ // yet); not a proof of the set, which proveLaws gives. Laws with some key go through proveLaws.
912
+ export function proveLaw(m, laws, i) {
913
+ const law = laws[i];
914
+ if (laws.some(l => someKeys(l.holds).length)) {
915
+ const r = proveLaws(m, laws);
916
+ return r.proven ? { proven: true } : { proven: false, goal: r.failed.goal, stuck: r.failed.stuck, ...(r.failed.witness ? { witness: r.failed.witness } : {}) };
917
+ }
918
+ const keys = [...readKeys(m, laws), ...witnesses(m)];
919
+ const inst = instancesFor(m, laws, inst => obligations(m, laws, { [law.name]: inst }, i), {
920
+ seed: [...keys, ...(law.forall ? [{ op: 'var', name: `${law.forall}#` }] : []), ...(law.keys ?? []).map((name) => ({ op: 'var', name }))],
921
+ focus: [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])],
922
+ });
923
+ for (const goal of obligations(m, laws, { [law.name]: inst }, i)) {
924
+ const out = grow(goal);
925
+ if (!('tree' in out))
926
+ return { proven: false, goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) };
927
+ }
928
+ return { proven: true };
929
+ }
793
930
  function proveLawsAsGiven(m, laws) {
794
931
  // Each law's two goals (start and step) get their own instances, seeded with its own keys.
795
932
  const keys = [...readKeys(m, laws), ...witnesses(m)], instances = {};
@@ -1,5 +1,5 @@
1
1
  import type { C, E, Law, Machine, Recurrence } from '../core/machine.js';
2
- import { type Step } from '../core/solver.js';
2
+ import { type Lin, type Step } from '../core/solver.js';
3
3
  import { type Fold, type Goal, type Instances, type Liveness, type Loop, type Path, type StepLaw, type Tree } from '../kernel/kernel.js';
4
4
  import { leanLibrary } from './library.js';
5
5
  export { leanLibrary };
@@ -13,6 +13,7 @@ export declare const list: (xs: string[]) => string;
13
13
  export declare function leaves(t: Tree, path: Path, visit: (t: Tree & {
14
14
  leaf: Step[];
15
15
  products?: [number, number][];
16
+ squares?: Lin[];
16
17
  }, path: Path) => void): void;
17
18
  export declare function encode(ex: Exported, tamper?: boolean, dropStep?: boolean, globalVars?: string[], globalMaps?: string[]): {
18
19
  tab: string;
@@ -1,6 +1,6 @@
1
1
  import { atMost, n, not, pointWrite } from "../core/machine.js";
2
2
  import { add, k, scale, sub } from "../core/solver.js";
3
- import { boundOf, canon, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, loopLiveness, loopMachine, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, witnessesOf, withProducts } from "../kernel/kernel.js";
3
+ import { boundOf, canon, factorsOf, isCell, isMonomial, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, loopLiveness, loopMachine, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, witnessesOf, withProducts } from "../kernel/kernel.js";
4
4
  import { grow, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
5
5
  import { refinement, refinementSides } from "../core/compose.js";
6
6
  import { leanSurface, leanTerms } from "./lean-terms.js";
@@ -53,14 +53,20 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
53
53
  // shallower cells first so a key reads only earlier columns.
54
54
  const vars = globalVars ?? [...new Set(goal.hyps.flatMap(varsOf))];
55
55
  const cells = new Set(), terms = new Set();
56
+ // Monomials: every one a leaf's rows or products use, or that a product term has under the leaf's path, with the
57
+ // cells among their factors.
58
+ const monos = new Set();
59
+ const addMono = (name) => { monos.add(name); for (const f of factorsOf(name))
60
+ if (isCell(f))
61
+ addCell(f); };
56
62
  const addCell = (name) => { if (cells.has(name))
57
63
  return; cells.add(name); for (const key of Object.keys(cellKey(name)))
58
- if (key.includes('['))
64
+ if (isMonomial(key))
65
+ addMono(key);
66
+ else if (key.includes('['))
59
67
  addCell(key);
60
68
  else if (key.startsWith('⟦'))
61
69
  terms.add(key); };
62
- // Monomials: every one a leaf's rows or products use, or that a product term has under the leaf's path.
63
- const monos = new Set();
64
70
  const subterms = (c) => {
65
71
  const out2 = [];
66
72
  const we = (e) => { out2.push(e); if (e.op === 'if') {
@@ -96,14 +102,14 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
96
102
  };
97
103
  goal.hyps.forEach(choices);
98
104
  leaves(tree, new Map(), (leaf, path) => {
99
- for (const r of withProducts(leafConstraints(goal, path), leaf.products ?? []))
105
+ for (const r of [...withProducts(leafConstraints(goal, path), leaf.products ?? [], leaf.squares), ...(leaf.squares ?? []).map(lin => ({ lin }))])
100
106
  for (const key of Object.keys(r.lin)) {
101
- if (key.includes('['))
107
+ if (isMonomial(key))
108
+ addMono(key);
109
+ else if (key.includes('['))
102
110
  addCell(key);
103
111
  else if (key.startsWith('⟦'))
104
112
  terms.add(key);
105
- else if (key.includes('*'))
106
- monos.add(key);
107
113
  }
108
114
  // Each cell a hypothesis reads under the path, kept or not: two reads of one cell cancel in a row (T − T), yet
109
115
  // Lean's linear form of each still needs the cell's column to see the comparison decided.
@@ -111,18 +117,25 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
111
117
  for (const e of subterms(h)) {
112
118
  if (e.op === 'prod')
113
119
  for (const key of Object.keys(linear(e, path) ?? {}))
114
- if (key.includes('*'))
115
- monos.add(key);
120
+ if (isMonomial(key))
121
+ addMono(key);
116
122
  if (e.op === 'get')
117
123
  for (const key of Object.keys(linear(e, path) ?? {}))
118
- if (key.includes('['))
124
+ if (isCell(key))
119
125
  addCell(key);
120
126
  }
121
127
  });
122
128
  const depth = (name) => name.split('[').length;
123
- // Term columns (open choices, each standing for its term's value) before the cells, whose keys may read them.
124
- const cols = [...vars, ...[...monos].sort((a, b) => a.split('*').length - b.split('*').length), ...[...terms].sort((a, b) => Number(a.slice(1, -1)) - Number(b.slice(1, -1))), ...[...cells].sort((a, b) => depth(a) - depth(b))];
129
+ // Term columns (open choices, each standing for its term's value) before the cells, whose keys may read them; a
130
+ // monomial with a cell among its factors after the cells.
131
+ const degree = (a, b) => factorsOf(a).length - factorsOf(b).length;
132
+ const plainMonos = [...monos].filter(m => factorsOf(m).every(f => !isCell(f))).sort(degree), cellMonos = [...monos].filter(m => factorsOf(m).some(isCell)).sort(degree);
133
+ const cols = [...vars, ...plainMonos, ...[...terms].sort((a, b) => Number(a.slice(1, -1)) - Number(b.slice(1, -1))), ...[...cells].sort((a, b) => depth(a) - depth(b)), ...cellMonos];
125
134
  const col = new Map(cols.map((name, j) => [name, j]));
135
+ for (const cell of cells)
136
+ for (const key of Object.keys(cellKey(cell)))
137
+ if (cellMonos.includes(key))
138
+ throw new Error(`${ex.name}: the key of ${cell} reads a product of cells`);
126
139
  const maps = globalMaps ?? [...new Set([...[...cells].map(cellMap), ...goal.hyps.flatMap(h => [...JSON.stringify(h).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]))])];
127
140
  const dense = (lin) => {
128
141
  const coefs = new Array(cols.length).fill(0);
@@ -141,10 +154,10 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
141
154
  const sparse = (coefs) => list(coefs.flatMap((x, j) => (x === 0 ? [] : [`(${j}, ${int(x)})`])));
142
155
  const row = (lin) => `(sp ${sparse(dense(lin))} ${int(lin['1'] ?? 0)})`;
143
156
  const { e, c } = leanTerms(vars, maps, ex.name);
144
- const tab = cols.map(name => (name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
145
- : name.startsWith('⟦') ? `.term (${e(columnTerm(name))})`
146
- : name.includes('*') ? `.mono ${list(name.split('*').map(f => { const j = vars.indexOf(f); if (j < 0)
147
- throw new Error(`${ex.name}: factor ${f}`); return j; }).sort((a, b) => a - b).map(String))}`
157
+ const tab = cols.map(name => (isMonomial(name) ? `.mono ${list(factorsOf(name).map(f => { const j = col.get(f); if (j === undefined)
158
+ throw new Error(`${ex.name}: factor ${f}`); return j; }).sort((a, b) => a - b).map(String))}`
159
+ : name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
160
+ : name.startsWith('⟦') ? `.term (${e(columnTerm(name))})`
148
161
  : `.var ${vars.indexOf(name)}`));
149
162
  let leafNo = 0;
150
163
  const lean = (t, path) => {
@@ -152,10 +165,10 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
152
165
  const id = canon(t.split);
153
166
  return `.split ${row(t.split)}\n (${lean(t.yes, new Map(path).set(id, { lin: t.split, holds: true }))})\n (${lean(t.no, new Map(path).set(id, { lin: t.split, holds: false }))})`;
154
167
  }
155
- const base = leafConstraints(goal, path), prods = t.products ?? [];
156
- const rows = withProducts(base, prods).map(r => r.lin), n0 = base.length;
157
- // Only the rows the derivation and the products read, renumbered (products follow them, in order); cuts carry
158
- // their quotient row (Cert's format).
168
+ const base = leafConstraints(goal, path), prods = t.products ?? [], squares = t.squares ?? [];
169
+ const rows = withProducts(base, prods, squares).map(r => r.lin), n0 = base.length;
170
+ // Only the rows the derivation and the products read, renumbered (the squares follow them, then the products, in
171
+ // order); cuts carry their quotient row (Cert's format).
159
172
  const used = [...new Set([...t.leaf.flatMap(s => (s.kind === 'input' && s.index < n0 ? [s.index] : [])), ...prods.flat().filter(i => i < n0)])].sort((a, b) => a - b);
160
173
  const at = new Map(used.map((old, j) => [old, j]));
161
174
  const idx = (i) => (i < n0 ? at.get(i) : used.length + i - n0);
@@ -179,7 +192,7 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
179
192
  steps = steps.slice(0, -1); // the first leaf loses its last step
180
193
  // Cell pairs the path proves equal (Lean re-checks each).
181
194
  const eqs = [], pairs = [];
182
- const cellCols = cols.map((name, j) => ({ name, j })).filter(c2 => c2.name.includes('['));
195
+ const cellCols = cols.map((name, j) => ({ name, j })).filter(c2 => isCell(c2.name));
183
196
  for (const [a, p] of cellCols.entries())
184
197
  for (const q of cellCols.slice(a + 1)) {
185
198
  if (cellMap(p.name) !== cellMap(q.name))
@@ -201,7 +214,8 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
201
214
  const j = 'pair' in src ? pairs.indexOf([...src.pair].sort().join('\n')) : col.get('nat' in src ? src.nat : src.term) ?? -1;
202
215
  return j < 0 ? '.any' : 'pair' in src ? `.eq ${j}` : 'nat' in src ? `.nat ${j}` : `.term ${j}`;
203
216
  };
204
- return `.leafAt ${list(used.map(u => `(${row(rows[u])}, ${hint(base[u].src)})`))} ${list(steps)} ${list(eqs)} ${list(prods.map(([i, j]) => `(${idx(i)}, ${idx(j)})`))}`;
217
+ const at2 = list(used.map(u => `(${row(rows[u])}, ${hint(base[u].src)})`)), rest = `${list(steps)} ${list(eqs)} ${list(prods.map(([i, j]) => `(${idx(i)}, ${idx(j)})`))}`;
218
+ return squares.length ? `.leafSq ${at2} ${list(squares.map(row))} ${rest}` : `.leafAt ${at2} ${rest}`;
205
219
  };
206
220
  return { tab: list(tab), hyps: list(goal.hyps.map(c)), tree: lean(tree, new Map()), cols, c, e };
207
221
  }