gdp-wired 0.18.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.
- package/dist/core/cyclic.d.ts +90 -6
- package/dist/core/cyclic.js +668 -27
- package/dist/core/dsl.js +25 -9
- package/dist/core/translate.d.ts +30 -0
- package/dist/core/translate.js +572 -0
- package/dist/kernel/prove.d.ts +13 -0
- package/dist/kernel/prove.js +112 -10
- package/dist/lean-kernel/emit.d.ts +34 -0
- package/dist/lean-kernel/emit.js +406 -50
- package/dist/lean-kernel/library.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { add, refuteFast, scale, solve } from "./solver.js";
|
|
2
|
+
import { checkRefutation } from "./check.js";
|
|
3
|
+
import { canon, canonFlip, columnTerm, factorsOf, isCell, isMonomial, leafConstraintsReusing, linear, substE, truth, withProducts } from "../kernel/kernel.js";
|
|
4
|
+
import { cellProducts, grow } from "../kernel/prove.js";
|
|
5
|
+
const omega = 'O$';
|
|
6
|
+
// The kernel's product of two linear forms (kernel.ts's `times`): each monomial its factors sorted, joined by '*'.
|
|
7
|
+
function times(a, b) {
|
|
8
|
+
const out = [];
|
|
9
|
+
for (const [p, cp] of Object.entries(a))
|
|
10
|
+
for (const [q, cq] of Object.entries(b)) {
|
|
11
|
+
if (p.includes('⟦') || q.includes('⟦'))
|
|
12
|
+
return undefined;
|
|
13
|
+
const fs = [...(p === '1' ? [] : factorsOf(p)), ...(q === '1' ? [] : factorsOf(q))].sort();
|
|
14
|
+
out.push({ [fs.length ? fs.join('*') : '1']: cp * cq });
|
|
15
|
+
}
|
|
16
|
+
return add(...out);
|
|
17
|
+
}
|
|
18
|
+
// A cell's key, from its name (`M[key]`, the key's canonical text, whose names may be cells themselves).
|
|
19
|
+
function keyOf(cell) {
|
|
20
|
+
const text = cell.slice(cell.indexOf('[') + 1, -1), out = {};
|
|
21
|
+
let depth = 0, start = 0;
|
|
22
|
+
for (let i = 0; i <= text.length; i++) {
|
|
23
|
+
const ch = text[i];
|
|
24
|
+
if (ch === '[')
|
|
25
|
+
depth++;
|
|
26
|
+
else if (ch === ']')
|
|
27
|
+
depth--;
|
|
28
|
+
else if ((ch === ',' && depth === 0) || i === text.length) {
|
|
29
|
+
if (i > start) {
|
|
30
|
+
const part = text.slice(start, i), at = part.lastIndexOf(':');
|
|
31
|
+
out[part.slice(0, at)] = Number(part.slice(at + 1));
|
|
32
|
+
}
|
|
33
|
+
start = i + 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
export function renamer(r) {
|
|
39
|
+
const terms = new Map(), memo = new WeakMap(), cells = r.cells ?? {};
|
|
40
|
+
const col = (name, wp) => {
|
|
41
|
+
let m = memo.get(wp);
|
|
42
|
+
if (!m)
|
|
43
|
+
memo.set(wp, (m = new Map()));
|
|
44
|
+
if (m.has(name))
|
|
45
|
+
return m.get(name);
|
|
46
|
+
const out = colOf(name, wp);
|
|
47
|
+
m.set(name, out);
|
|
48
|
+
return out;
|
|
49
|
+
};
|
|
50
|
+
const colOf = (name, wp) => {
|
|
51
|
+
if (name.startsWith('⟦')) {
|
|
52
|
+
let e = terms.get(name);
|
|
53
|
+
if (!e) {
|
|
54
|
+
const t = columnTerm(name);
|
|
55
|
+
if (!t)
|
|
56
|
+
return undefined;
|
|
57
|
+
terms.set(name, (e = substE(t, r.s)));
|
|
58
|
+
}
|
|
59
|
+
return linear(e, wp);
|
|
60
|
+
}
|
|
61
|
+
if (isMonomial(name)) {
|
|
62
|
+
let out = { 1: 1 };
|
|
63
|
+
for (const f of factorsOf(name)) {
|
|
64
|
+
const l = col(f, wp);
|
|
65
|
+
out = out && l && times(out, l);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
if (isCell(name)) {
|
|
70
|
+
const map = name.slice(0, name.indexOf('[')), key = lin(keyOf(name), wp);
|
|
71
|
+
if (!key)
|
|
72
|
+
return undefined;
|
|
73
|
+
const to = cells[map];
|
|
74
|
+
if (!to)
|
|
75
|
+
return { [`${map}[${canon(key)}]`]: 1 };
|
|
76
|
+
const u = linear(to.at, wp), ko = times(key, { [omega]: 1 });
|
|
77
|
+
return u && ko ? { [`${to.to}[${canon(add(u, ko))}]`]: 1 } : undefined;
|
|
78
|
+
}
|
|
79
|
+
const e = r.s.vars[name];
|
|
80
|
+
return e ? linear(e, wp) : { [name]: 1 };
|
|
81
|
+
};
|
|
82
|
+
const lin = (l, wp) => {
|
|
83
|
+
const parts = [];
|
|
84
|
+
for (const key in l) {
|
|
85
|
+
if (key === '1') {
|
|
86
|
+
parts.push({ 1: l[key] });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const c = col(key, wp);
|
|
90
|
+
if (!c)
|
|
91
|
+
return undefined;
|
|
92
|
+
parts.push(scale(c, l[key]));
|
|
93
|
+
}
|
|
94
|
+
return add(...parts);
|
|
95
|
+
};
|
|
96
|
+
return lin;
|
|
97
|
+
}
|
|
98
|
+
export const tstats = () => ({ leaves: 0, regrown: 0, splits: 0, carried: 0, uncarried: 0, cached: 0 });
|
|
99
|
+
const known = (lin, wp) => {
|
|
100
|
+
if (Object.keys(lin).every(key => key === '1'))
|
|
101
|
+
return (lin['1'] ?? 0) <= 0;
|
|
102
|
+
const a = wp.get(canon(lin));
|
|
103
|
+
if (a)
|
|
104
|
+
return a.holds;
|
|
105
|
+
const b = wp.get(canonFlip(lin));
|
|
106
|
+
return b ? !b.holds : undefined;
|
|
107
|
+
};
|
|
108
|
+
// The node's goal grown at a path (cell products on, as every cyclic goal is), within the deadline.
|
|
109
|
+
function regrow(whole, wp, until) {
|
|
110
|
+
const was = cellProducts.on;
|
|
111
|
+
cellProducts.on = true;
|
|
112
|
+
try {
|
|
113
|
+
const o = grow(whole, wp, { nodes: 0, until });
|
|
114
|
+
return 'tree' in o ? o.tree : undefined;
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
cellProducts.on = was;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// The usual tree for `part` read as a tree for `whole` (whose hypotheses hold part's, renamed), from the paths given.
|
|
121
|
+
// `swaps`: usual keys read as others (a cell at the first read as the cell at the second): where the path has a write's
|
|
122
|
+
// key W equal to a key K the node's conclusion reads its map at, that conclusion reads at K what the usual one reads at
|
|
123
|
+
// W (a write's reads at its key read at the key read, as the step's facts have them), so the usual cells at W are read
|
|
124
|
+
// as the cells at K (`readsFirst` decides the two equal or apart first).
|
|
125
|
+
export function translate(part, whole, tree, ren0, st, until, pp = new Map(), wp = new Map(), swaps = []) {
|
|
126
|
+
const ren = swaps.length ? (lin, p) => {
|
|
127
|
+
const out = {};
|
|
128
|
+
for (const key in lin) {
|
|
129
|
+
let to = key;
|
|
130
|
+
if (isCell(key)) {
|
|
131
|
+
const kt = canon(keyOf(key)), sw = swaps.find(x => x[0] === kt);
|
|
132
|
+
if (sw)
|
|
133
|
+
to = `${key.slice(0, key.indexOf('['))}[${sw[1]}]`;
|
|
134
|
+
}
|
|
135
|
+
out[to] = (out[to] ?? 0) + lin[key];
|
|
136
|
+
}
|
|
137
|
+
return ren0(out, p);
|
|
138
|
+
} : ren0;
|
|
139
|
+
if (!('leaf' in tree)) {
|
|
140
|
+
const id = canon(tree.split), wl = ren(tree.split, wp);
|
|
141
|
+
if (!wl)
|
|
142
|
+
return close(whole, wp, st, until);
|
|
143
|
+
const k0 = known(wl, wp);
|
|
144
|
+
if (k0 !== undefined)
|
|
145
|
+
return translate(part, whole, k0 ? tree.yes : tree.no, ren0, st, until, new Map(pp).set(id, { lin: tree.split, holds: k0 }), wp, swaps);
|
|
146
|
+
const wid = canon(wl);
|
|
147
|
+
const yes = translate(part, whole, tree.yes, ren0, st, until, new Map(pp).set(id, { lin: tree.split, holds: true }), new Map(wp).set(wid, { lin: wl, holds: true }), swaps);
|
|
148
|
+
const no = yes && translate(part, whole, tree.no, ren0, st, until, new Map(pp).set(id, { lin: tree.split, holds: false }), new Map(wp).set(wid, { lin: wl, holds: false }), swaps);
|
|
149
|
+
return yes && no ? { split: wl, yes, no } : undefined;
|
|
150
|
+
}
|
|
151
|
+
const rp = leafConstraintsReusing(part, pp), rw = leafConstraintsReusing(whole, wp), at = new Map();
|
|
152
|
+
rw.forEach((r, i) => { const key = canon(r.lin); if (!at.has(key))
|
|
153
|
+
at.set(key, i); });
|
|
154
|
+
// Two cells the usual path proves equal (a row of the leaf's from their pair) are cells over time here, at keys
|
|
155
|
+
// (t − 1) + a·Ω and (t − 1) + b·Ω: the path has a = b, and the keys' difference (a − b)·Ω is split (each other side
|
|
156
|
+
// refuted by (a − b)·Ω = 0, the product of a − b <= 0 or b − a <= 0 with Ω >= 0), the leaf then read again.
|
|
157
|
+
const usedRows = [...tree.leaf.flatMap(x => (x.kind === 'input' ? [x.index] : [])), ...(tree.products ?? []).flat()].filter(i => i < rp.length);
|
|
158
|
+
for (const i of usedRows) {
|
|
159
|
+
const src = rp[i].src;
|
|
160
|
+
if (!src || !('pair' in src))
|
|
161
|
+
continue;
|
|
162
|
+
const a = ren({ [src.pair[0]]: 1 }, wp), b = ren({ [src.pair[1]]: 1 }, wp), [ca] = Object.keys(a ?? {}), [cb] = Object.keys(b ?? {});
|
|
163
|
+
if (!ca || !cb || !isCell(ca) || !isCell(cb))
|
|
164
|
+
continue;
|
|
165
|
+
const d = add(keyOf(ca), scale(keyOf(cb), -1)), q0 = {};
|
|
166
|
+
if (!Object.keys(d).length || Object.keys(d).some(key => key === '1' || !factorsOf(key).includes(omega)))
|
|
167
|
+
continue;
|
|
168
|
+
for (const key of Object.keys(d)) {
|
|
169
|
+
const fs = [...factorsOf(key)];
|
|
170
|
+
fs.splice(fs.indexOf(omega), 1);
|
|
171
|
+
q0[fs.length ? fs.join('*') : '1'] = (q0[fs.length ? fs.join('*') : '1'] ?? 0) + d[key];
|
|
172
|
+
}
|
|
173
|
+
for (const [l, q] of [[d, q0], [scale(d, -1), scale(q0, -1)]]) {
|
|
174
|
+
const kd = known(l, wp);
|
|
175
|
+
if (kd === true)
|
|
176
|
+
continue;
|
|
177
|
+
if (kd === false)
|
|
178
|
+
return apart(whole, wp, l, q, st, until);
|
|
179
|
+
st.splits++;
|
|
180
|
+
const id = canon(l), yes = translate(part, whole, tree, ren0, st, until, pp, new Map(wp).set(id, { lin: l, holds: true }), swaps);
|
|
181
|
+
const no = yes && apart(whole, new Map(wp).set(id, { lin: l, holds: false }), l, q, st, until);
|
|
182
|
+
return yes && no ? { split: l, yes, no } : undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
st.leaves++;
|
|
186
|
+
const map = (i) => { if (i >= rp.length)
|
|
187
|
+
return i - rp.length + rw.length; const l = ren(rp[i].lin, wp); return l && at.get(canon(l)); };
|
|
188
|
+
const leaf = [], products = [], squares = (tree.squares ?? []).map(s => ren(s, wp));
|
|
189
|
+
let ok = squares.every(Boolean);
|
|
190
|
+
for (const s of ok ? tree.leaf : []) {
|
|
191
|
+
if (s.kind !== 'input') {
|
|
192
|
+
leaf.push(s);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const j = map(s.index);
|
|
196
|
+
if (j === undefined) {
|
|
197
|
+
ok = false;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
leaf.push({ ...s, index: j });
|
|
201
|
+
}
|
|
202
|
+
for (const [i, j] of ok ? tree.products ?? [] : []) {
|
|
203
|
+
const a = map(i), b = map(j);
|
|
204
|
+
if (a === undefined || b === undefined) {
|
|
205
|
+
ok = false;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
products.push([a, b]);
|
|
209
|
+
}
|
|
210
|
+
if (ok && checkRefutation(withProducts(rw, products, squares), leaf))
|
|
211
|
+
return { leaf, ...(tree.products ? { products } : {}), ...(tree.squares ? { squares: squares } : {}) };
|
|
212
|
+
return close(whole, wp, st, until);
|
|
213
|
+
}
|
|
214
|
+
export function readsFirst(whole, reads, ren, inner, st, until, wp = new Map(), swaps = []) {
|
|
215
|
+
if (!reads.length)
|
|
216
|
+
return inner(wp, swaps);
|
|
217
|
+
const [r, ...rest] = reads, ku = linear(r.k, new Map()), wu = linear(r.w, new Map()), kl = ku && ren(ku, wp), wl = wu && ren(wu, wp);
|
|
218
|
+
if (!ku || !wu || !kl || !wl)
|
|
219
|
+
return readsFirst(whole, rest, ren, inner, st, until, wp, swaps);
|
|
220
|
+
const q = add(kl, scale(wl, -1));
|
|
221
|
+
if (!Object.keys(q).some(key => key !== '1'))
|
|
222
|
+
return readsFirst(whole, rest, ren, inner, st, until, wp, swaps);
|
|
223
|
+
const dq = times(q, { [omega]: 1 });
|
|
224
|
+
// The cases in turn: q <= 0, then -q <= 0 (equal), then the keys over Ω.
|
|
225
|
+
const steps = [{ l: q }, { l: scale(q, -1) }, ...(dq ? [{ l: dq, q }, { l: scale(dq, -1), q: scale(q, -1) }] : [])];
|
|
226
|
+
const go = (i, p) => {
|
|
227
|
+
if (i === steps.length)
|
|
228
|
+
return readsFirst(whole, rest, ren, inner, st, until, p, [...swaps, [canon(wu), canon(ku)]]);
|
|
229
|
+
const { l, q: qq } = steps[i], kd = known(l, p);
|
|
230
|
+
if (kd === true)
|
|
231
|
+
return go(i + 1, p);
|
|
232
|
+
if (kd === false)
|
|
233
|
+
return i < 2 ? readsFirst(whole, rest, ren, inner, st, until, p, swaps) : apart(whole, p, l, qq, st, until);
|
|
234
|
+
st.splits++;
|
|
235
|
+
const id = canon(l), yes = go(i + 1, new Map(p).set(id, { lin: l, holds: true })), no = new Map(p).set(id, { lin: l, holds: false });
|
|
236
|
+
const n = yes && (i < 2 ? readsFirst(whole, rest, ren, inner, st, until, no, swaps) : apart(whole, no, l, qq, st, until));
|
|
237
|
+
return yes && n ? { split: l, yes, no: n } : undefined;
|
|
238
|
+
};
|
|
239
|
+
return go(0, wp);
|
|
240
|
+
}
|
|
241
|
+
// A path where l >= 1 (two cells' keys apart by l = q·Ω) while q <= 0 is a row: refuted by q·Ω <= 0, the product of
|
|
242
|
+
// that row and Ω >= 0.
|
|
243
|
+
function apart(whole, wp, l, q, st, until) {
|
|
244
|
+
const rows = leafConstraintsReusing(whole, wp), find = (x) => { const c = canon(x); return rows.findIndex(r => canon(r.lin) === c); };
|
|
245
|
+
const iq = find(q), io = find({ [omega]: -1 }), ia = find(add({ 1: 1 }, scale(l, -1)));
|
|
246
|
+
if (iq >= 0 && io >= 0 && ia >= 0) {
|
|
247
|
+
const leaf = [{ kind: 'input', index: ia }, { kind: 'input', index: rows.length }, { kind: 'combine', a: 0, b: 1, ma: 1, mb: 1 }], products = [[iq, io]];
|
|
248
|
+
if (checkRefutation(withProducts(rows, products), leaf)) {
|
|
249
|
+
st.leaves++;
|
|
250
|
+
return { leaf, products };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
st.regrown++;
|
|
254
|
+
return regrow(whole, wp, until);
|
|
255
|
+
}
|
|
256
|
+
// The atoms that make a condition true at the path (a comparison's, each lin <= 0), or undefined where it is no plain
|
|
257
|
+
// comparison or conjunction.
|
|
258
|
+
function atomsOf(c, wp) {
|
|
259
|
+
if (c.op === 'and') {
|
|
260
|
+
const out = [];
|
|
261
|
+
for (const x of c.xs) {
|
|
262
|
+
const a = atomsOf(x, wp);
|
|
263
|
+
if (!a)
|
|
264
|
+
return undefined;
|
|
265
|
+
out.push(...a);
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
if (c.op !== '<' && c.op !== '<=' && c.op !== '=')
|
|
270
|
+
return undefined;
|
|
271
|
+
const a = linear(c.a, wp), b = linear(c.b, wp);
|
|
272
|
+
if (!a || !b)
|
|
273
|
+
return undefined;
|
|
274
|
+
const d = add(a, scale(b, -1));
|
|
275
|
+
return c.op === '<=' ? [d] : c.op === '<' ? [add(d, { 1: 1 })] : [d, scale(d, -1)];
|
|
276
|
+
}
|
|
277
|
+
// A tree for `whole` that first decides each condition true (a guard, the antecedent of a use's implication), each
|
|
278
|
+
// other side grown on the goal (the node's hypotheses refute it), then `inner` at the path where all hold.
|
|
279
|
+
export function decide(whole, conds, inner, st, until, wp = new Map()) {
|
|
280
|
+
for (const [i, c] of conds.entries()) {
|
|
281
|
+
if (truth(c, wp) === true)
|
|
282
|
+
continue;
|
|
283
|
+
const atoms = atomsOf(c, wp)?.filter(l => known(l, wp) === undefined);
|
|
284
|
+
if (!atoms?.length) {
|
|
285
|
+
if (truth(c, wp) === false)
|
|
286
|
+
return undefined;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const lin = atoms[0], id = canon(lin);
|
|
290
|
+
st.splits++;
|
|
291
|
+
const yes = decide(whole, conds.slice(i), inner, st, until, new Map(wp).set(id, { lin, holds: true }));
|
|
292
|
+
const no = yes && regrow(whole, new Map(wp).set(id, { lin, holds: false }), until);
|
|
293
|
+
return yes && no ? { split: lin, yes, no } : undefined;
|
|
294
|
+
}
|
|
295
|
+
return inner(wp);
|
|
296
|
+
}
|
|
297
|
+
// A leaf of `g` at the path: a hypothesis false there (its row 1 <= 0), or with `solveIt` the rows refuted by the solver.
|
|
298
|
+
function leafAt(g, wp, solveIt, st) {
|
|
299
|
+
const rows = leafConstraintsReusing(g, wp), absurd = rows.findIndex(r => Object.keys(r.lin).every(key => key === '1') && (r.lin['1'] ?? 0) > 0);
|
|
300
|
+
if (absurd >= 0) {
|
|
301
|
+
st.leaves++;
|
|
302
|
+
return { leaf: [{ kind: 'input', index: absurd }] };
|
|
303
|
+
}
|
|
304
|
+
if (!solveIt)
|
|
305
|
+
return undefined;
|
|
306
|
+
const fast = rows.length > 60 ? refuteFast(rows) : undefined, v = fast?.kind === 'refuted' ? fast : solve(rows);
|
|
307
|
+
if (v.kind === 'refuted') {
|
|
308
|
+
st.leaves++;
|
|
309
|
+
return { leaf: v.steps };
|
|
310
|
+
}
|
|
311
|
+
// With monomials about (a law about cap·n over a node's children): each equation about a factor (a cell given by
|
|
312
|
+
// the facts) times each factor's sign, as the prover's search does (kernel/prove.ts).
|
|
313
|
+
if (!rows.some(r => Object.keys(r.lin).some(isMonomial)))
|
|
314
|
+
return undefined;
|
|
315
|
+
const factors = new Set(rows.flatMap(r => Object.keys(r.lin).filter(isMonomial).flatMap(factorsOf)));
|
|
316
|
+
const byCanon = new Map(rows.map((r, i) => [canon(r.lin), i]));
|
|
317
|
+
const eqs = rows.flatMap((r, i) => (Object.keys(r.lin).some(key => key !== '1' && factors.has(key)) && byCanon.has(canon(scale(r.lin, -1))) ? [i] : []));
|
|
318
|
+
const signs = rows.flatMap((r, i) => { const keys = Object.keys(r.lin); return keys.length === 1 && factors.has(keys[0]) && r.lin[keys[0]] < 0 ? [i] : []; });
|
|
319
|
+
const products = eqs.flatMap(i => signs.map(j => [i, j])).slice(0, 200);
|
|
320
|
+
if (!products.length)
|
|
321
|
+
return undefined;
|
|
322
|
+
const w = solve(withProducts(rows, products));
|
|
323
|
+
if (w.kind !== 'refuted')
|
|
324
|
+
return undefined;
|
|
325
|
+
st.leaves++;
|
|
326
|
+
return { leaf: w.steps, products };
|
|
327
|
+
}
|
|
328
|
+
const open = (c, wp) => atomsOf(c, wp)?.find(l => known(l, wp) === undefined);
|
|
329
|
+
// Two reads of one function at keys the path doesn't yet prove equal or apart, keys linear (keys through a choice's
|
|
330
|
+
// column after the others: such a column is often one the path decides later).
|
|
331
|
+
function cellAtom(g, wp) {
|
|
332
|
+
const rows = leafConstraintsReusing(g, wp), names = [...new Set(rows.flatMap(r => Object.keys(r.lin)))].filter(isCell);
|
|
333
|
+
let later;
|
|
334
|
+
for (const [i, p] of names.entries())
|
|
335
|
+
for (const q of names.slice(i + 1)) {
|
|
336
|
+
if (p.slice(0, p.indexOf('[')) !== q.slice(0, q.indexOf('[')))
|
|
337
|
+
continue;
|
|
338
|
+
const d = add(keyOf(p), scale(keyOf(q), -1));
|
|
339
|
+
if (Object.keys(d).some(key => key !== '1' && isMonomial(key)))
|
|
340
|
+
continue;
|
|
341
|
+
const column = Object.keys(d).some(key => key.includes('⟦'));
|
|
342
|
+
for (const l of [d, scale(d, -1)])
|
|
343
|
+
if (Object.keys(l).some(key => key !== '1') && known(l, wp) === undefined) {
|
|
344
|
+
if (!column)
|
|
345
|
+
return l;
|
|
346
|
+
later ??= l;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return later;
|
|
350
|
+
}
|
|
351
|
+
// A choice the rows read open (an if's condition: a map's write, read at a key that may be the written one).
|
|
352
|
+
// Then one the conclusion refuted reads where it gives no rows yet (a condition read through such a choice).
|
|
353
|
+
const choiceOf = (e, wp) => {
|
|
354
|
+
if (e.op === 'if')
|
|
355
|
+
return truth(e.c, wp) === undefined ? open(e.c, wp) : undefined;
|
|
356
|
+
if (e.op !== 'sub' && e.op !== 'min' && e.op !== 'max')
|
|
357
|
+
return undefined;
|
|
358
|
+
const a = linear(e.a, wp), b = linear(e.b, wp);
|
|
359
|
+
if (!a || !b)
|
|
360
|
+
return undefined;
|
|
361
|
+
const d = e.op === 'sub' ? add(b, scale(a, -1)) : add(a, scale(b, -1));
|
|
362
|
+
return Object.keys(d).some(key => key !== '1') && known(d, wp) === undefined ? d : undefined;
|
|
363
|
+
};
|
|
364
|
+
function choiceAtom(g, wp) {
|
|
365
|
+
const rows = leafConstraintsReusing(g, wp);
|
|
366
|
+
for (const name of new Set(rows.flatMap(r => Object.keys(r.lin)))) {
|
|
367
|
+
const e = name.startsWith('⟦') ? columnTerm(name) : undefined;
|
|
368
|
+
const l = e && e.op === 'if' ? choiceOf(e, wp) : undefined;
|
|
369
|
+
if (l)
|
|
370
|
+
return l;
|
|
371
|
+
}
|
|
372
|
+
let found;
|
|
373
|
+
const walk = (x) => {
|
|
374
|
+
if (found || !x || typeof x !== 'object')
|
|
375
|
+
return;
|
|
376
|
+
const o = x;
|
|
377
|
+
if ((o.op === 'if' || o.op === 'sub' || o.op === 'min' || o.op === 'max') && (found = choiceOf(o, wp)))
|
|
378
|
+
return;
|
|
379
|
+
for (const val of Object.values(o))
|
|
380
|
+
walk(val);
|
|
381
|
+
};
|
|
382
|
+
walk(g.hyps.at(-1));
|
|
383
|
+
return found;
|
|
384
|
+
}
|
|
385
|
+
// The cells of the conclusion refuted at the path (reads it gives no rows of yet among them).
|
|
386
|
+
function conclCells(g, wp) {
|
|
387
|
+
const out = [];
|
|
388
|
+
const walk = (x) => {
|
|
389
|
+
if (!x || typeof x !== 'object')
|
|
390
|
+
return;
|
|
391
|
+
const o = x;
|
|
392
|
+
if (o.op === 'get' && o.map.endsWith('@')) {
|
|
393
|
+
const l = linear(o, wp);
|
|
394
|
+
if (l)
|
|
395
|
+
out.push(...Object.keys(l));
|
|
396
|
+
}
|
|
397
|
+
for (const val of Object.values(o))
|
|
398
|
+
walk(val);
|
|
399
|
+
};
|
|
400
|
+
walk(g.hyps.at(-1));
|
|
401
|
+
return out;
|
|
402
|
+
}
|
|
403
|
+
// Two cells over time at keys u + a·Ω and u + b·Ω: a = b decided first, then the keys' difference, its other side
|
|
404
|
+
// refuted by the product (`apart`).
|
|
405
|
+
function omegaPair(g, wp) {
|
|
406
|
+
const rows = leafConstraintsReusing(g, wp), names = [...new Set([...rows.flatMap(r => Object.keys(r.lin)), ...conclCells(g, wp)])].filter(isCell);
|
|
407
|
+
for (const [i, p] of names.entries())
|
|
408
|
+
for (const q of names.slice(i + 1)) {
|
|
409
|
+
if (p.slice(0, p.indexOf('[')) !== q.slice(0, q.indexOf('[')))
|
|
410
|
+
continue;
|
|
411
|
+
const d = add(keyOf(p), scale(keyOf(q), -1)), q0 = {};
|
|
412
|
+
if (!Object.keys(d).length || Object.keys(d).some(key => key === '1' || !factorsOf(key).includes(omega)))
|
|
413
|
+
continue;
|
|
414
|
+
for (const key of Object.keys(d)) {
|
|
415
|
+
const fs = [...factorsOf(key)];
|
|
416
|
+
fs.splice(fs.indexOf(omega), 1);
|
|
417
|
+
const f = fs.length ? fs.join('*') : '1';
|
|
418
|
+
q0[f] = (q0[f] ?? 0) + d[key];
|
|
419
|
+
}
|
|
420
|
+
if (Object.keys(q0).some(key => key.includes('⟦')))
|
|
421
|
+
continue;
|
|
422
|
+
const a = known(q0, wp), b = known(scale(q0, -1), wp);
|
|
423
|
+
if (a === undefined)
|
|
424
|
+
return { l: q0 };
|
|
425
|
+
if (a && b === undefined)
|
|
426
|
+
return { l: scale(q0, -1) };
|
|
427
|
+
if (!(a && b))
|
|
428
|
+
continue;
|
|
429
|
+
for (const [l, qq] of [[d, q0], [scale(d, -1), scale(q0, -1)]])
|
|
430
|
+
if (known(l, wp) === undefined)
|
|
431
|
+
return { l, q: qq };
|
|
432
|
+
}
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
// A case of `g` closed: refuted by its rows, else an open choice, two reads of a function or two cells over time split
|
|
436
|
+
// (each then equal), else the goal grown at the path.
|
|
437
|
+
export function close(g, wp, st, until) {
|
|
438
|
+
const l = leafAt(g, wp, true, st);
|
|
439
|
+
if (l)
|
|
440
|
+
return l;
|
|
441
|
+
const pick = choiceAtom(g, wp) ?? cellAtom(g, wp), om = pick ? undefined : omegaPair(g, wp), next = pick ?? om?.l;
|
|
442
|
+
if (next && performance.now() < until) {
|
|
443
|
+
st.splits++;
|
|
444
|
+
const id = canon(next), y = close(g, new Map(wp).set(id, { lin: next, holds: true }), st, until);
|
|
445
|
+
const no = new Map(wp).set(id, { lin: next, holds: false }), n = y && (om?.q ? apart(g, no, next, om.q, st, until) : close(g, no, st, until));
|
|
446
|
+
return y && n ? { split: next, yes: y, no: n } : undefined;
|
|
447
|
+
}
|
|
448
|
+
st.regrown++;
|
|
449
|
+
if (globalThis.process?.env?.GDP_CYCLIC_DEBUG)
|
|
450
|
+
console.error(`[close] ${g.name}: grown again at ${[...wp.values()].map(a => `${JSON.stringify(a.lin)} ${a.holds}`).join('; ')}`);
|
|
451
|
+
return regrow(g, wp, until);
|
|
452
|
+
}
|
|
453
|
+
// The node at t's tree, made rather than searched (`carry`): its goal is the law at t refuted against the law at the
|
|
454
|
+
// start or after the step (a use each, under its guard) and the facts that give the cells at t (each under its guard).
|
|
455
|
+
// Splits first decide the uses' guards and the facts' guards (each other side refuted by the node's hypotheses: the
|
|
456
|
+
// time's bounds), then each comparison of the law at t and the same comparison of the law it is carried from, in
|
|
457
|
+
// turn; a case where the two differ is refuted by the facts' equations (the cells at t are the terms the step gives),
|
|
458
|
+
// one where both are decided by whichever of the two hypotheses is then false. A case none of that refutes is grown on
|
|
459
|
+
// the goal at its path.
|
|
460
|
+
// `nUses`: the uses' hypotheses, just before the conclusion refuted (mainGoal's order).
|
|
461
|
+
export function carry(g, nFacts, nUses, st, until) {
|
|
462
|
+
const last = g.hyps.length - 1, uses = g.hyps.slice(last - nUses, last), facts = g.hyps.slice(last - nUses - nFacts, last - nUses);
|
|
463
|
+
const law = g.hyps[last].x;
|
|
464
|
+
const parts = uses.map(u => (u.op === 'or' && u.xs.length === 2 && u.xs[0].op === 'not' ? { guard: u.xs[0].x, concl: u.xs[1] } : { guard: undefined, concl: u }));
|
|
465
|
+
// The comparisons of two instances of one law, in order, paired; where the law at t is an implication the other isn't
|
|
466
|
+
// (a law between two points, x <= y → ..., carried from y = x), its antecedent first, decided alone.
|
|
467
|
+
const pairs = (a, b, out = []) => {
|
|
468
|
+
if ((a.op === 'and' || a.op === 'or') && b.op === a.op && a.xs.length === b.xs.length)
|
|
469
|
+
a.xs.forEach((x, i) => pairs(x, b.xs[i], out));
|
|
470
|
+
else if (a.op === 'not' && b.op === 'not')
|
|
471
|
+
pairs(a.x, b.x, out);
|
|
472
|
+
else if ((a.op === '<' || a.op === '<=' || a.op === '=') && b.op === a.op)
|
|
473
|
+
out.push([a, b]);
|
|
474
|
+
else if (a.op === 'or' && a.xs.length === 2 && a.xs[0].op === 'not') {
|
|
475
|
+
out.push([a.xs[0].x, undefined]);
|
|
476
|
+
pairs(a.xs[1], b, out);
|
|
477
|
+
}
|
|
478
|
+
return out;
|
|
479
|
+
};
|
|
480
|
+
const paired = parts.map(p => pairs(law, p.concl));
|
|
481
|
+
// The times before (t ∸ 1, x ∸ 1, y ∸ 1, each a choice until c <= v is decided), decided after the guards: then the
|
|
482
|
+
// cells at t − 1 and at (t − 1) + 1 are the same reads.
|
|
483
|
+
const prev = [], seenPrev = new Set();
|
|
484
|
+
const walk = (x) => {
|
|
485
|
+
if (!x || typeof x !== 'object')
|
|
486
|
+
return;
|
|
487
|
+
const o = x;
|
|
488
|
+
if (o.op === 'sub' && o.a.op === 'var' && o.b.op === 'const' && !seenPrev.has(`${o.a.name}-${o.b.value}`)) {
|
|
489
|
+
seenPrev.add(`${o.a.name}-${o.b.value}`);
|
|
490
|
+
prev.push({ op: '<=', a: o.b, b: o.a });
|
|
491
|
+
}
|
|
492
|
+
for (const val of Object.values(o))
|
|
493
|
+
walk(val);
|
|
494
|
+
};
|
|
495
|
+
walk(g.hyps);
|
|
496
|
+
const guardsIn = (c, wp, out) => {
|
|
497
|
+
if (c.op === 'and') {
|
|
498
|
+
for (const x of c.xs)
|
|
499
|
+
guardsIn(x, wp, out);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (c.op === 'or' && c.xs.length === 2 && c.xs[0].op === 'not') {
|
|
503
|
+
const t = truth(c.xs[0].x, wp);
|
|
504
|
+
if (t === undefined)
|
|
505
|
+
out.push(c.xs[0].x);
|
|
506
|
+
else if (t)
|
|
507
|
+
guardsIn(c.xs[1], wp, out);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
const leafOf = (wp, solveIt) => leafAt(g, wp, solveIt, st), final = (wp) => close(g, wp, st, until);
|
|
511
|
+
const node = (wp, guardsDone) => {
|
|
512
|
+
if (performance.now() > until)
|
|
513
|
+
return undefined;
|
|
514
|
+
// A hypothesis false at the path: the law at t holds there, or a law it is carried from (its guard true) fails.
|
|
515
|
+
if (truth(law, wp) === true || parts.some(p => (p.guard === undefined || truth(p.guard, wp) === true) && truth(p.concl, wp) === false))
|
|
516
|
+
return final(wp);
|
|
517
|
+
let next;
|
|
518
|
+
if (!guardsDone) {
|
|
519
|
+
const gs = [];
|
|
520
|
+
for (const p of parts)
|
|
521
|
+
if (p.guard && truth(p.guard, wp) === undefined)
|
|
522
|
+
gs.push(p.guard);
|
|
523
|
+
gs.push(...prev);
|
|
524
|
+
for (const f of facts)
|
|
525
|
+
guardsIn(f, wp, gs);
|
|
526
|
+
for (const c of gs)
|
|
527
|
+
if ((next = open(c, wp)))
|
|
528
|
+
break;
|
|
529
|
+
if (!next)
|
|
530
|
+
return node(wp, true);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
// (the rows first: a law whose instances give rows as they are, a single comparison or the conjunctions their
|
|
534
|
+
// signs open, needs no split)
|
|
535
|
+
const l = leafOf(wp, true);
|
|
536
|
+
if (l)
|
|
537
|
+
return l;
|
|
538
|
+
for (const [i, p] of parts.entries()) {
|
|
539
|
+
if (p.guard && truth(p.guard, wp) !== true)
|
|
540
|
+
continue;
|
|
541
|
+
for (const [a, b] of paired[i]) {
|
|
542
|
+
const ta = truth(a, wp), tb = b && truth(b, wp);
|
|
543
|
+
if (!b) {
|
|
544
|
+
if (ta === undefined && (next = open(a, wp)))
|
|
545
|
+
break;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (ta !== undefined && tb !== undefined) {
|
|
549
|
+
if (ta !== tb)
|
|
550
|
+
return final(wp);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if ((next = ta === undefined ? open(a, wp) : open(b, wp)))
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
if (next)
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
if (!next)
|
|
560
|
+
return final(wp);
|
|
561
|
+
}
|
|
562
|
+
st.splits++;
|
|
563
|
+
const id = canon(next), yes = new Map(wp).set(id, { lin: next, holds: true }), no = new Map(wp).set(id, { lin: next, holds: false });
|
|
564
|
+
// (a guard's other side is refuted by the bounds at once: tried before splitting further)
|
|
565
|
+
const side = (p) => (guardsDone ? node(p, true) : leafOf(p, false) ?? quick(p) ?? node(p, false));
|
|
566
|
+
const y = side(yes), n = y && side(no);
|
|
567
|
+
return y && n ? { split: next, yes: y, no: n } : undefined;
|
|
568
|
+
};
|
|
569
|
+
const quick = (wp) => { const rows = leafConstraintsReusing(g, wp), v = refuteFast(rows); if (v?.kind !== 'refuted')
|
|
570
|
+
return undefined; st.leaves++; return { leaf: v.steps }; };
|
|
571
|
+
return node(new Map(), false);
|
|
572
|
+
}
|
package/dist/kernel/prove.d.ts
CHANGED
|
@@ -76,6 +76,19 @@ export declare function proveLaw(m: Machine, laws: Law[], i: number): {
|
|
|
76
76
|
stuck: string;
|
|
77
77
|
witness?: Record<string, number>;
|
|
78
78
|
};
|
|
79
|
+
export declare const usualCheck: {
|
|
80
|
+
on: boolean;
|
|
81
|
+
};
|
|
82
|
+
export declare const usualProof: {
|
|
83
|
+
last?: {
|
|
84
|
+
m: Machine;
|
|
85
|
+
laws: Law[];
|
|
86
|
+
proof: {
|
|
87
|
+
instances: Record<string, Instances>;
|
|
88
|
+
trees: Record<string, Tree>;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
};
|
|
79
92
|
type StepProof = {
|
|
80
93
|
laws: {
|
|
81
94
|
instances: Record<string, Instances>;
|