gdp-wired 0.10.1 → 0.12.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 +22 -8
- package/dist/core/compose.js +1 -1
- package/dist/core/dsl.d.ts +107 -9
- package/dist/core/dsl.js +328 -40
- package/dist/core/gdp.js +2 -2
- package/dist/core/machine.d.ts +4 -0
- package/dist/core/machine.js +93 -15
- package/dist/core/solver.d.ts +12 -0
- package/dist/core/solver.js +68 -17
- package/dist/kernel/kernel.d.ts +19 -3
- package/dist/kernel/kernel.js +106 -27
- package/dist/kernel/prove.js +73 -38
- package/dist/lean-kernel/emit.js +44 -19
- package/dist/lean-kernel/library.js +6 -6
- package/package.json +2 -2
package/dist/core/gdp.js
CHANGED
|
@@ -21,7 +21,7 @@ export function guard(prover, fn, field, opts) {
|
|
|
21
21
|
if (m.pre && !evalC(m.pre, ps, { scalars: {}, maps: {} }, m))
|
|
22
22
|
throw new Error(`${fn.name}: the params ${JSON.stringify(params)} don't meet its precondition`);
|
|
23
23
|
return (args, ...about) => {
|
|
24
|
-
const input = r.encodeInput(args);
|
|
24
|
+
const input = r.encodeInput(args, params);
|
|
25
25
|
if (m.assume && !evalC(m.assume, { ...ps, ...input }, { scalars: {}, maps: {} }, m))
|
|
26
26
|
return null;
|
|
27
27
|
return atPath(fn.run(args, params), field) === true ? prover.prove(...about) : null;
|
|
@@ -46,7 +46,7 @@ export function gate(prover, r, flag, opts) {
|
|
|
46
46
|
let st = opts.state ?? compiled.init(ps);
|
|
47
47
|
return {
|
|
48
48
|
request: (args, ...about) => {
|
|
49
|
-
const input = r.encodeInput(args);
|
|
49
|
+
const input = r.encodeInput(args, params);
|
|
50
50
|
if (!allowed(m, run, ps, st, input))
|
|
51
51
|
return null;
|
|
52
52
|
const granted = requestEnv(m, run, ps, st, input)[`?${flag}`] === 1;
|
package/dist/core/machine.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ export type C = {
|
|
|
46
46
|
} | {
|
|
47
47
|
op: 'all';
|
|
48
48
|
key: string;
|
|
49
|
+
keys?: string[];
|
|
49
50
|
c: C;
|
|
50
51
|
witness?: E;
|
|
51
52
|
};
|
|
@@ -71,6 +72,8 @@ export type Fn = {
|
|
|
71
72
|
js: string;
|
|
72
73
|
lean?: string;
|
|
73
74
|
recur?: Recurrence;
|
|
75
|
+
arity?: number;
|
|
76
|
+
call?: string;
|
|
74
77
|
};
|
|
75
78
|
export type Recurrence = {
|
|
76
79
|
args: string[];
|
|
@@ -162,6 +165,7 @@ export type Request = Record<string, number | ReadonlyMap<number, number>>;
|
|
|
162
165
|
export declare const naturalIn: (v: unknown, what: string) => number;
|
|
163
166
|
export declare function evalE(e: E, env: Env, st: State, m: Machine): number;
|
|
164
167
|
export declare const fnValue: (fn: Fn, key: number) => number;
|
|
168
|
+
export declare function pairedArgs(key: E, n: number): E[] | undefined;
|
|
165
169
|
export declare function evalC(c: C, env: Env, st: State, m: Machine): boolean;
|
|
166
170
|
export declare function carriedView(m: Machine, st: State, input: Request, rename?: (map: string) => string): State;
|
|
167
171
|
export declare const numbersOf: (input: Request) => Record<string, number>;
|
package/dist/core/machine.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Arithmetic is linear: +, −, × by a constant, min, max, if. Maps are naturals indexed by naturals, every cell starting
|
|
7
7
|
// at the same value; a law about a map holds for every key x, and is proven at one arbitrary x (with the law assumed at
|
|
8
8
|
// every other key the step reads).
|
|
9
|
-
import { historyMachine, keyTerm, plainKey, scope } from "../kernel/kernel.js";
|
|
9
|
+
import { boundOf, crossFree, historyMachine, keyTerm, plainKey, scope } from "../kernel/kernel.js";
|
|
10
10
|
import { add, eq, k, le, lt, nat, scale, show, solve, sub, v } from "./solver.js";
|
|
11
11
|
// A write at one key, for the parts that take no write at every key (the symbolic prover, the GPU synthesizers, the
|
|
12
12
|
// hand-written shader, the first Lean export): they refuse one by name.
|
|
@@ -49,7 +49,7 @@ function keysAt(st, maps) {
|
|
|
49
49
|
}
|
|
50
50
|
const breaksCache = new WeakMap();
|
|
51
51
|
function breaksOf(t, key) {
|
|
52
|
-
const hit = breaksCache.get(t);
|
|
52
|
+
const hit = breaksCache.get(t)?.get(key);
|
|
53
53
|
if (hit)
|
|
54
54
|
return hit;
|
|
55
55
|
const out = { reads: [], cmps: [] };
|
|
@@ -70,7 +70,9 @@ function breaksOf(t, key) {
|
|
|
70
70
|
(Array.isArray(v) ? v : [v]).forEach(y => walk(y));
|
|
71
71
|
};
|
|
72
72
|
walk(t);
|
|
73
|
-
breaksCache.
|
|
73
|
+
if (!breaksCache.has(t))
|
|
74
|
+
breaksCache.set(t, new Map());
|
|
75
|
+
breaksCache.get(t).set(key, out);
|
|
74
76
|
return out;
|
|
75
77
|
}
|
|
76
78
|
function breakKeys(b, env, st, m) {
|
|
@@ -103,6 +105,25 @@ function breakKeys(b, env, st, m) {
|
|
|
103
105
|
const sorted = [...keys].sort((a, b) => a - b);
|
|
104
106
|
return [...sorted, sorted.at(-1) + 1];
|
|
105
107
|
}
|
|
108
|
+
// A condition at every tuple of n keys: the breakpoints of each key (each read on its own, the comparisons between two
|
|
109
|
+
// of them set aside) together, each case with the n - 1 keys after it. Every stretch where no read moves and no
|
|
110
|
+
// comparison turns starts at one of those cases (a breakpoint, the key after one, or 0), so the keys after its start
|
|
111
|
+
// place n keys there in every order they can take; keys read only as a map's key need only the cells and one past them.
|
|
112
|
+
// Keys read only as a map's key and compared with each other: a read changes only at a cell, so each cell, the n keys
|
|
113
|
+
// after it (the stretch up to the next cell), 0 and the n after it, and past the last cell are every case.
|
|
114
|
+
function tupleKeys(c, env, st, m) {
|
|
115
|
+
const names = boundOf(c);
|
|
116
|
+
if (names.every(n => plainKey(c.c, n)))
|
|
117
|
+
return keysAt(st, mapsIn(c.c));
|
|
118
|
+
if (names.length === 1)
|
|
119
|
+
return breakKeys(breaksOf(c.c, c.key), env, st, m);
|
|
120
|
+
const body = crossFree(c.c, names);
|
|
121
|
+
if (names.every(n => plainKey(body, n)))
|
|
122
|
+
return spread([0, ...keysAt(st, mapsIn(c.c))], names.length + 1);
|
|
123
|
+
const parts = names.map(n => breaksOf(body, n));
|
|
124
|
+
return spread(breakKeys({ reads: parts.flatMap(b => b.reads), cmps: parts.flatMap(b => b.cmps) }, env, st, m), names.length);
|
|
125
|
+
}
|
|
126
|
+
const spread = (keys, n) => [...new Set(keys.flatMap(k => Array.from({ length: n }, (_, i) => k + i)))].sort((a, b) => a - b);
|
|
106
127
|
export const naturalIn = (v, what) => {
|
|
107
128
|
if (typeof v !== 'number' || !Number.isSafeInteger(v) || v < 0)
|
|
108
129
|
throw new RangeError(`${what} must be a natural below 2^53, not ${String(v)}`);
|
|
@@ -130,7 +151,12 @@ export function evalE(e, env, st, m) {
|
|
|
130
151
|
case 'max': return Math.max(evalE(e.a, env, st, m), evalE(e.b, env, st, m));
|
|
131
152
|
case 'if': return evalC(e.c, env, st, m) ? evalE(e.then, env, st, m) : evalE(e.else, env, st, m);
|
|
132
153
|
case 'get': {
|
|
133
|
-
const
|
|
154
|
+
const fn = m.fns?.[e.map], args = fn?.call ? pairedArgs(e.key, fn.arity) : undefined;
|
|
155
|
+
if (args) {
|
|
156
|
+
const xs = args.map(a => evalE(a, env, st, m));
|
|
157
|
+
return naturalIn(callOf(fn)(...xs), `${e.map}(${xs.join(', ')})`);
|
|
158
|
+
}
|
|
159
|
+
const key = evalE(e.key, env, st, m);
|
|
134
160
|
return fn ? naturalIn(fnOf(fn)(key), `${e.map}(${key})`) : st.maps[e.map].get(key) ?? st.defaults?.[e.map] ?? m.maps[e.map];
|
|
135
161
|
}
|
|
136
162
|
}
|
|
@@ -147,6 +173,34 @@ const fnOf = (fn) => {
|
|
|
147
173
|
}
|
|
148
174
|
return f;
|
|
149
175
|
};
|
|
176
|
+
const compiledCalls = new WeakMap();
|
|
177
|
+
const callOf = (fn) => {
|
|
178
|
+
let f = compiledCalls.get(fn);
|
|
179
|
+
if (!f) {
|
|
180
|
+
f = compileFunction([], `return (${fn.call})`)();
|
|
181
|
+
compiledCalls.set(fn, f);
|
|
182
|
+
}
|
|
183
|
+
return f;
|
|
184
|
+
};
|
|
185
|
+
// The n arguments a key pairs (left-nested, ((a, b), c)..., each pair (s)² + b with s = a + b), if it is their pairing.
|
|
186
|
+
const paired = new WeakMap();
|
|
187
|
+
export function pairedArgs(key, n) {
|
|
188
|
+
let byN = paired.get(key);
|
|
189
|
+
if (!byN)
|
|
190
|
+
paired.set(key, (byN = new Map()));
|
|
191
|
+
if (byN.has(n))
|
|
192
|
+
return byN.get(n);
|
|
193
|
+
let out;
|
|
194
|
+
if (n === 1)
|
|
195
|
+
out = [key];
|
|
196
|
+
else if (key.op === 'add' && key.a.op === 'prod' && key.a.a.op === 'add' && alike(key.a.a, key.a.b) && alike(key.a.a.b, key.b)) {
|
|
197
|
+
const first = pairedArgs(key.a.a.a, n - 1);
|
|
198
|
+
out = first && [...first, key.b];
|
|
199
|
+
}
|
|
200
|
+
byN.set(n, out);
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
const alike = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
150
204
|
export function evalC(c, env, st, m) {
|
|
151
205
|
switch (c.op) {
|
|
152
206
|
case '<': return evalE(c.a, env, st, m) < evalE(c.b, env, st, m);
|
|
@@ -156,7 +210,11 @@ export function evalC(c, env, st, m) {
|
|
|
156
210
|
case 'or': return c.xs.some(y => evalC(y, env, st, m));
|
|
157
211
|
case 'not': return !evalC(c.x, env, st, m);
|
|
158
212
|
case 'flag': return env[`?${c.name}`] === 1;
|
|
159
|
-
case 'all':
|
|
213
|
+
case 'all': {
|
|
214
|
+
const names = boundOf(c), keys = tupleKeys(c, env, st, m);
|
|
215
|
+
const go = (i, at) => (i === names.length ? evalC(c.c, at, st, m) : keys.every(key => go(i + 1, { ...at, [names[i]]: key })));
|
|
216
|
+
return go(0, env);
|
|
217
|
+
}
|
|
160
218
|
}
|
|
161
219
|
}
|
|
162
220
|
// A write at every key: its value at each key the maps it reads hold a cell at, and its new default past them.
|
|
@@ -310,11 +368,11 @@ export function compileJs(m) {
|
|
|
310
368
|
const { h, plain, queries, carried } = runnable(m);
|
|
311
369
|
const id = (name) => JSON.stringify(name);
|
|
312
370
|
const flags = Object.keys(plain.step.flags ?? {}), carriedMaps = m.inputMaps ?? [], fns = Object.keys(m.fns ?? {});
|
|
313
|
-
// A key bound by a quantified flag or a write at every key is the JS parameter k.
|
|
314
|
-
let bound;
|
|
371
|
+
// A key bound by a quantified flag or a write at every key is the JS parameter k (k1, k2 for a flag's further keys).
|
|
372
|
+
let bound = {};
|
|
315
373
|
const ex = (e) => {
|
|
316
374
|
switch (e.op) {
|
|
317
|
-
case 'var': return e.name
|
|
375
|
+
case 'var': return bound[e.name] ?? (e.name.startsWith('r.') ? `r[${id(e.name.slice(2))}]` : plain.params.includes(e.name) || plain.inputs.includes(e.name) ? `env[${id(e.name)}]` : `s[${id(e.name)}]`);
|
|
318
376
|
case 'const': return String(e.value);
|
|
319
377
|
case 'add': return `$x(${ex(e.a)} + ${ex(e.b)})`;
|
|
320
378
|
case 'sub': return `Math.max(0, ${ex(e.a)} - ${ex(e.b)})`;
|
|
@@ -323,7 +381,12 @@ export function compileJs(m) {
|
|
|
323
381
|
case 'min': return `Math.min(${ex(e.a)}, ${ex(e.b)})`;
|
|
324
382
|
case 'max': return `Math.max(${ex(e.a)}, ${ex(e.b)})`;
|
|
325
383
|
case 'if': return `(${cx(e.c)} ? ${ex(e.then)} : ${ex(e.else)})`;
|
|
326
|
-
case 'get':
|
|
384
|
+
case 'get': {
|
|
385
|
+
const fn = m.fns?.[e.map], args = fn?.call ? pairedArgs(e.key, fn.arity) : undefined;
|
|
386
|
+
if (args)
|
|
387
|
+
return `$n(fnc${fns.indexOf(e.map)}(${args.map(ex).join(', ')}), ${id(`${e.map}()`)})`;
|
|
388
|
+
return fns.includes(e.map) ? `$n(fn${fns.indexOf(e.map)}(${ex(e.key)}), ${id(`${e.map}()`)})` : `(maps[${id(e.map)}].get(${ex(e.key)}) ?? ${carriedMaps.includes(e.map) ? '0' : `dft[${id(e.map)}] ?? ${plain.maps[e.map]}`})`;
|
|
389
|
+
}
|
|
327
390
|
}
|
|
328
391
|
};
|
|
329
392
|
const cx = (c) => {
|
|
@@ -336,11 +399,11 @@ export function compileJs(m) {
|
|
|
336
399
|
case 'not': return `(!${cx(c.x)})`;
|
|
337
400
|
case 'flag': return `flag${flags.indexOf(c.name)}`;
|
|
338
401
|
case 'all': {
|
|
339
|
-
const keys = keysJs(c.c, c.key), outer = bound;
|
|
340
|
-
bound =
|
|
402
|
+
const names = boundOf(c), ks = names.map((_, i) => (i ? `k${i}` : 'k')), keys = names.length === 1 ? keysJs(c.c, c.key) : tupleJs(c), outer = bound;
|
|
403
|
+
bound = { ...bound, ...Object.fromEntries(names.map((n, i) => [n, ks[i]])) };
|
|
341
404
|
const body = cx(c.c);
|
|
342
405
|
bound = outer;
|
|
343
|
-
return `${keys}.every(k => ${body})`;
|
|
406
|
+
return names.length === 1 ? `${keys}.every(k => ${body})` : `(ks => ${ks.map(k => `ks.every(${k} => `).join('')}${body}${')'.repeat(ks.length)})(${keys})`;
|
|
344
407
|
}
|
|
345
408
|
}
|
|
346
409
|
};
|
|
@@ -348,10 +411,24 @@ export function compileJs(m) {
|
|
|
348
411
|
const keysJs = (t, key) => {
|
|
349
412
|
if (plainKey(t, key))
|
|
350
413
|
return `$keys(maps, ${JSON.stringify(mapsIn(t).filter(mp => !fns.includes(mp)))})`;
|
|
351
|
-
|
|
414
|
+
return breaksJs(breaksOf(t, key));
|
|
415
|
+
};
|
|
416
|
+
const breaksJs = (b) => {
|
|
417
|
+
const off = (o) => (o ? ex(o) : '0');
|
|
352
418
|
return `$breaks(maps, [${b.reads.map(r => `[${id(r.map)}, ${off(r.off)}, ${r.minus}]`).join(', ')}], [${b.cmps.map(c => `[${ex(c.value)}, ${off(c.off)}, ${c.minus}]`).join(', ')}])`;
|
|
353
419
|
};
|
|
354
|
-
|
|
420
|
+
// The interpreter's tupleKeys.
|
|
421
|
+
const tupleJs = (c) => {
|
|
422
|
+
const names = boundOf(c);
|
|
423
|
+
if (names.every(n => plainKey(c.c, n)))
|
|
424
|
+
return `$keys(maps, ${JSON.stringify(mapsIn(c.c).filter(mp => !fns.includes(mp)))})`;
|
|
425
|
+
const body = crossFree(c.c, names);
|
|
426
|
+
if (names.every(n => plainKey(body, n)))
|
|
427
|
+
return `$spread([0, ...$keys(maps, ${JSON.stringify(mapsIn(c.c).filter(mp => !fns.includes(mp)))})], ${names.length + 1})`;
|
|
428
|
+
const parts = names.map(n => breaksOf(body, n));
|
|
429
|
+
return `$spread(${breaksJs({ reads: parts.flatMap(b => b.reads), cmps: parts.flatMap(b => b.cmps) })}, ${names.length})`;
|
|
430
|
+
};
|
|
431
|
+
const every = (w) => { const keys = keysJs(w.value, w.each), dense = !plainKey(w.value, w.each), outer = bound; bound = { [w.each]: 'k' }; const body = ex(w.value); bound = outer; return `${dense ? '$dense' : '$every'}(${keys}, k => ${body})`; };
|
|
355
432
|
const writes = Object.entries(plain.step.maps ?? {});
|
|
356
433
|
const env = (input) => `{${[...m.params.map(p => `${id(p)}: $n(params[${id(p)}], ${id(`param ${p}`)})`), ...(input ? carried.map(i => `${id(i)}: $n(input[${id(i)}], ${id(`input ${i}`)})`) : [])].join(', ')}}`;
|
|
357
434
|
// Reads go through `maps` (with the maps the request carries); writes go to the state's own.
|
|
@@ -379,7 +456,8 @@ function $n(v, what) { if (typeof v !== 'number' || !Number.isSafeInteger(v) ||
|
|
|
379
456
|
function $x(v) { if (v > 9007199254740991) throw new RangeError('a value passed 2^53 - 1, past which the proofs over the naturals no longer describe the run'); return v; }
|
|
380
457
|
function $m(v, what) { if (!(v instanceof Map)) throw new RangeError(what + ' must be a Map of naturals'); for (const [key, x] of v) { $n(key, 'a key of ' + what); $n(x, 'a cell of ' + what); } return v; }
|
|
381
458
|
function $keys(maps, names) { const keys = new Set(); let past = 0; for (const c of Object.values(maps)) for (const key of c.keys()) past = Math.max(past, key + 1); for (const n of names) for (const key of maps[n].keys()) keys.add(key); return [...keys, past]; }
|
|
382
|
-
${fns.map((name, i) => `const fn${i} = (${m.fns[name].js});\n`).join('')}function $breaks(maps, reads, cmps) { const p = new Set([0]); for (const [n, d, minus] of reads) { for (const s of maps[n].keys()) { if (minus) p.add(s + d); else if (s >= d) p.add(s - d); } if (minus) p.add(d); } for (const [v, d, minus] of cmps) { if (minus) { p.add(v + d); p.add(d); } else if (v >= d) p.add(v - d); } const keys = new Set(); for (const q of p) for (let i = -1; i <= 2; i++) if (q + i >= 0) keys.add(q + i); const sorted = [...keys].sort((a, b) => a - b); sorted.push(sorted[sorted.length - 1] + 1); return sorted; }
|
|
459
|
+
${fns.map((name, i) => `const fn${i} = (${m.fns[name].js});\n${m.fns[name].call ? `const fnc${i} = (${m.fns[name].call});\n` : ''}`).join('')}function $breaks(maps, reads, cmps) { const p = new Set([0]); for (const [n, d, minus] of reads) { for (const s of maps[n].keys()) { if (minus) p.add(s + d); else if (s >= d) p.add(s - d); } if (minus) p.add(d); } for (const [v, d, minus] of cmps) { if (minus) { p.add(v + d); p.add(d); } else if (v >= d) p.add(v - d); } const keys = new Set(); for (const q of p) for (let i = -1; i <= 2; i++) if (q + i >= 0) keys.add(q + i); const sorted = [...keys].sort((a, b) => a - b); sorted.push(sorted[sorted.length - 1] + 1); return sorted; }
|
|
460
|
+
function $spread(keys, n) { const out = new Set(); for (const k of keys) for (let i = 0; i < n; i++) out.add(k + i); return [...out].sort((a, b) => a - b); }
|
|
383
461
|
function $dense(keys, f) { const top = keys[keys.length - 1]; if (top > 1000000) throw new RangeError('a write at every key reads its key in arithmetic past a million keys (' + top + ')'); const d = f(top), cells = new Map(); for (let k = 0; k < top; k++) { const v = f(k); if (v !== d) cells.set(k, v); } return {cells, d}; }
|
|
384
462
|
function $every(keys, f) { const past = keys[keys.length - 1], d = f(past), cells = new Map(); for (let i = 0; i < keys.length - 1; i++) { const v = f(keys[i]); if (v !== d) cells.set(keys[i], v); } return {cells, d}; }
|
|
385
463
|
export function init(params) {
|
package/dist/core/solver.d.ts
CHANGED
|
@@ -2,6 +2,18 @@ export type Lin = Record<string, number>;
|
|
|
2
2
|
export type Constraint = {
|
|
3
3
|
lin: Lin;
|
|
4
4
|
label: string;
|
|
5
|
+
src?: RowSrc;
|
|
6
|
+
};
|
|
7
|
+
export type RowSrc = {
|
|
8
|
+
atom: number;
|
|
9
|
+
} | {
|
|
10
|
+
fact: number;
|
|
11
|
+
} | {
|
|
12
|
+
nat: string;
|
|
13
|
+
} | {
|
|
14
|
+
pair: [string, string];
|
|
15
|
+
} | {
|
|
16
|
+
term: string;
|
|
5
17
|
};
|
|
6
18
|
export declare const v: (name: string) => Lin;
|
|
7
19
|
export declare const k: (value: number) => Lin;
|
package/dist/core/solver.js
CHANGED
|
@@ -54,7 +54,8 @@ export const nat = (...names) => names.flatMap(name => le(k(0), v(name), `${name
|
|
|
54
54
|
export const negate = (c) => ({ lin: add(k(1), scale(c.lin, -1)), label: `not (${c.label})` });
|
|
55
55
|
const gcd = (a, b) => (b === 0 ? Math.abs(a) : gcd(b, a % b));
|
|
56
56
|
const vars = (lin) => Object.keys(lin).filter(key => key !== '1');
|
|
57
|
-
// maxRows: elimination can square the rows each round; past this many live rows
|
|
57
|
+
// maxRows: elimination can square the rows each round; past this many live rows, or a round that would make more than
|
|
58
|
+
// five times as many before merging them, it gives up with `unknown`.
|
|
58
59
|
export function solve(constraints, maxRows = 20_000) {
|
|
59
60
|
const steps = [];
|
|
60
61
|
const lins = [];
|
|
@@ -62,26 +63,41 @@ export function solve(constraints, maxRows = 20_000) {
|
|
|
62
63
|
// A row after its cut, if its variables share a divisor.
|
|
63
64
|
const tighten = (index) => {
|
|
64
65
|
const lin = lins[index];
|
|
65
|
-
|
|
66
|
+
let g = 0;
|
|
67
|
+
for (const key in lin)
|
|
68
|
+
if (key !== '1') {
|
|
69
|
+
g = gcd(g, lin[key]);
|
|
70
|
+
if (g === 1)
|
|
71
|
+
return index;
|
|
72
|
+
}
|
|
66
73
|
if (g <= 1)
|
|
67
74
|
return index;
|
|
68
|
-
const cut =
|
|
75
|
+
const cut = {};
|
|
76
|
+
for (const key in lin)
|
|
77
|
+
if (key !== '1')
|
|
78
|
+
cut[key] = lin[key] / g;
|
|
69
79
|
const constant = Math.ceil((lin['1'] ?? 0) / g);
|
|
70
80
|
return push({ kind: 'cut', of: index, g }, add(cut, k(constant)));
|
|
71
81
|
};
|
|
72
82
|
let rows = constraints.map((c, index) => tighten(push({ kind: 'input', index }, c.lin)));
|
|
73
|
-
|
|
83
|
+
// A row with no variable and a positive constant (kept per row: a row never changes once pushed).
|
|
84
|
+
const contra = [];
|
|
85
|
+
const isContra = (r) => (contra[r] ??= (lins[r]['1'] ?? 0) > 0 && Object.keys(lins[r]).every(key => key === '1'));
|
|
86
|
+
const contradiction = () => rows.find(isContra);
|
|
74
87
|
const eliminated = [];
|
|
75
88
|
let exact = true;
|
|
76
89
|
// Equalities first: a pair of rows that are each other's negation, with a variable of coefficient ±1, defines that
|
|
77
90
|
// variable. Substituting it into every other row (a combination with one row of the pair) eliminates it exactly,
|
|
78
91
|
// with |rows| new rows instead of the |pos| × |neg| of a plain elimination.
|
|
92
|
+
// A row's key and its negation's, kept per row (a row never changes once pushed).
|
|
93
|
+
const keyOf = (lin) => JSON.stringify(Object.entries(lin).sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)));
|
|
94
|
+
const keys = [], negKeys = [];
|
|
95
|
+
const key = (r) => (keys[r] ??= keyOf(lins[r])), negKey = (r) => (negKeys[r] ??= keyOf(scale(lins[r], -1)));
|
|
79
96
|
for (let changed = true; changed && contradiction() === undefined;) {
|
|
80
97
|
changed = false;
|
|
81
|
-
const
|
|
82
|
-
const byKey = new Map(rows.map(r => [key(lins[r]), r]));
|
|
98
|
+
const byKey = new Map(rows.map(r => [key(r), r]));
|
|
83
99
|
for (const r of rows) {
|
|
84
|
-
const neg = byKey.get(
|
|
100
|
+
const neg = byKey.get(negKey(r));
|
|
85
101
|
if (neg === undefined || neg === r)
|
|
86
102
|
continue;
|
|
87
103
|
const x = vars(lins[r]).find(name => Math.abs(lins[r][name]) === 1);
|
|
@@ -105,17 +121,52 @@ export function solve(constraints, maxRows = 20_000) {
|
|
|
105
121
|
break;
|
|
106
122
|
}
|
|
107
123
|
}
|
|
124
|
+
const sigs = [];
|
|
125
|
+
const sigOf = (r) => {
|
|
126
|
+
let sig = sigs[r];
|
|
127
|
+
if (sig === undefined) {
|
|
128
|
+
const lin = lins[r], keys = [];
|
|
129
|
+
for (const key in lin)
|
|
130
|
+
if (key !== '1')
|
|
131
|
+
keys.push(key);
|
|
132
|
+
keys.sort();
|
|
133
|
+
sig = JSON.stringify(keys.map(key => [key, lin[key]]));
|
|
134
|
+
sigs[r] = sig;
|
|
135
|
+
}
|
|
136
|
+
return sig;
|
|
137
|
+
};
|
|
108
138
|
for (let found = contradiction(); found === undefined; found = contradiction()) {
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
// Each variable's rows by sign, in one pass (variables in the order they first appear).
|
|
140
|
+
const index = new Map();
|
|
141
|
+
for (const r of rows)
|
|
142
|
+
for (const [x, c] of Object.entries(lins[r])) {
|
|
143
|
+
if (x === '1')
|
|
144
|
+
continue;
|
|
145
|
+
let e = index.get(x);
|
|
146
|
+
if (!e)
|
|
147
|
+
index.set(x, (e = { pos: [], neg: [], posUnit: true, negUnit: true }));
|
|
148
|
+
if (c > 0) {
|
|
149
|
+
e.pos.push(r);
|
|
150
|
+
e.posUnit &&= c === 1;
|
|
151
|
+
}
|
|
152
|
+
else if (c < 0) {
|
|
153
|
+
e.neg.push(r);
|
|
154
|
+
e.negUnit &&= c === -1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (index.size === 0)
|
|
111
158
|
break;
|
|
112
|
-
// Eliminate the variable that makes the fewest new rows, preferring exact eliminations.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
159
|
+
// Eliminate the variable that makes the fewest new rows, preferring exact eliminations (the first such).
|
|
160
|
+
let choice;
|
|
161
|
+
for (const [x, e] of index) {
|
|
162
|
+
const c = { x, pos: e.pos, neg: e.neg, isExact: e.posUnit || e.negUnit, size: e.pos.length * e.neg.length - e.pos.length - e.neg.length };
|
|
163
|
+
if (!choice || (c.isExact && !choice.isExact) || (c.isExact === choice.isExact && c.size < choice.size))
|
|
164
|
+
choice = c;
|
|
165
|
+
}
|
|
166
|
+
if (!choice)
|
|
167
|
+
break;
|
|
168
|
+
if (choice.pos.length * choice.neg.length > 5 * maxRows)
|
|
169
|
+
return { kind: 'unknown' };
|
|
119
170
|
exact &&= choice.isExact;
|
|
120
171
|
eliminated.push({ x: choice.x, rows: [...choice.pos, ...choice.neg] });
|
|
121
172
|
const next = rows.filter(r => (lins[r][choice.x] ?? 0) === 0);
|
|
@@ -129,7 +180,7 @@ export function solve(constraints, maxRows = 20_000) {
|
|
|
129
180
|
// say nothing.
|
|
130
181
|
const best = new Map();
|
|
131
182
|
for (const r of next) {
|
|
132
|
-
const lin = lins[r], sig =
|
|
183
|
+
const lin = lins[r], sig = sigOf(r);
|
|
133
184
|
if (sig === '[]' && (lin['1'] ?? 0) <= 0)
|
|
134
185
|
continue;
|
|
135
186
|
const seen = best.get(sig);
|
package/dist/kernel/kernel.d.ts
CHANGED
|
@@ -29,10 +29,19 @@ export declare function truth(c: C, path: Path): boolean | undefined;
|
|
|
29
29
|
export declare function facts(c: C, path: Path, positive?: boolean): Lin[];
|
|
30
30
|
export declare function leafConstraints(goal: Goal, path: Path): Constraint[];
|
|
31
31
|
export declare function leafConstraintsReusing(goal: Goal, path: Path): Constraint[];
|
|
32
|
-
type
|
|
33
|
-
|
|
32
|
+
type Decisions = [string, boolean | undefined][];
|
|
33
|
+
type Node<T> = {
|
|
34
|
+
at: number;
|
|
35
|
+
key: string;
|
|
36
|
+
next: Map<boolean | undefined, Node<T>>;
|
|
37
|
+
} | {
|
|
38
|
+
reads: Decisions;
|
|
34
39
|
out: T;
|
|
35
|
-
}
|
|
40
|
+
};
|
|
41
|
+
export type Seen<T> = {
|
|
42
|
+
root?: Node<T>;
|
|
43
|
+
size?: number;
|
|
44
|
+
};
|
|
36
45
|
export declare function reusing<T>(seen: Seen<T>, path: Path, f: (path: Path) => T): T;
|
|
37
46
|
export declare function withProducts(rows: Constraint[], pairs: [number, number][]): Constraint[];
|
|
38
47
|
export declare function check(goal: Goal, tree: Tree, path?: Path): boolean;
|
|
@@ -45,6 +54,13 @@ export declare function substE(e: E, s: Subst): E;
|
|
|
45
54
|
export declare function substC(c: C, s: Subst): C;
|
|
46
55
|
export type Instances = Record<string, E[]>;
|
|
47
56
|
export declare const stateName: (n: string, j: number) => string;
|
|
57
|
+
export declare const boundOf: (c: Extract<C, {
|
|
58
|
+
op: "all";
|
|
59
|
+
}>) => string[];
|
|
60
|
+
export declare const witnessesOf: (f: string, q: Extract<C, {
|
|
61
|
+
op: "all";
|
|
62
|
+
}>) => string[];
|
|
63
|
+
export declare const crossFree: (c: C, keys: string[]) => C;
|
|
48
64
|
export declare const assumed: (laws: Law[], instances: Instances) => C[];
|
|
49
65
|
export declare function scope(m: Machine, laws?: Law[]): void;
|
|
50
66
|
export declare const keyTerm: (e: E, key: string) => boolean;
|