gdp-wired 0.14.2 → 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.
- package/README.md +26 -13
- package/dist/core/discover.d.ts +27 -0
- package/dist/core/discover.js +491 -0
- package/dist/core/dsl.d.ts +18 -0
- package/dist/core/dsl.js +33 -3
- package/dist/core/infer.d.ts +29 -0
- package/dist/core/infer.js +573 -0
- package/dist/kernel/kernel.d.ts +5 -1
- package/dist/kernel/kernel.js +44 -12
- package/dist/kernel/prove.d.ts +8 -0
- package/dist/kernel/prove.js +152 -15
- package/dist/lean-kernel/emit.d.ts +2 -1
- package/dist/lean-kernel/emit.js +37 -23
- package/dist/lean-kernel/library.js +1 -1
- package/package.json +10 -2
package/dist/kernel/kernel.js
CHANGED
|
@@ -226,20 +226,46 @@ export function termRows(name, path) {
|
|
|
226
226
|
}
|
|
227
227
|
return out;
|
|
228
228
|
}
|
|
229
|
-
// Products: multiply out, each monomial (
|
|
230
|
-
//
|
|
231
|
-
//
|
|
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('
|
|
237
|
-
return undefined;
|
|
238
|
-
const
|
|
239
|
-
out.push({ [
|
|
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(
|
|
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
|
|
484
|
-
//
|
|
485
|
-
|
|
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;
|
package/dist/kernel/prove.d.ts
CHANGED
|
@@ -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>;
|
package/dist/kernel/prove.js
CHANGED
|
@@ -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(
|
|
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 = (
|
|
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(
|
|
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
|
|
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(
|
|
240
|
-
const relevant = rows.flatMap((r, i) => (Object.keys(r.lin).every(key => key === '1' || (
|
|
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
|
|
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 && !
|
|
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(
|
|
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
|
|
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;
|
package/dist/lean-kernel/emit.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
115
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
145
|
-
:
|
|
146
|
-
|
|
147
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
}
|