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/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;
@@ -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>;
@@ -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.set(t, out);
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 key = evalE(e.key, env, st, m), fn = m.fns?.[e.map];
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': return (plainKey(c.c, c.key) ? keysAt(st, mapsIn(c.c)) : breakKeys(breaksOf(c.c, c.key), env, st, m)).every(key => evalC(c.c, { ...env, [c.key]: key }, st, m));
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 === bound ? 'k' : 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)}]`;
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': 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]}`})`;
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 = c.key;
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
- const b = breaksOf(t, key), off = (o) => (o ? ex(o) : '0');
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
- const every = (w) => { const keys = keysJs(w.value, w.each), dense = !plainKey(w.value, w.each), outer = bound; bound = w.each; const body = ex(w.value); bound = outer; return `${dense ? '$dense' : '$every'}(${keys}, k => ${body})`; };
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) {
@@ -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;
@@ -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 it gives up with `unknown`.
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
- const g = vars(lin).reduce((acc, key) => gcd(acc, lin[key]), 0);
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 = Object.fromEntries(vars(lin).map(key => [key, lin[key] / g]));
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
- const contradiction = () => rows.find(r => vars(lins[r]).length === 0 && (lins[r]['1'] ?? 0) > 0);
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 key = (lin) => JSON.stringify(Object.entries(lin).sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)));
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(key(scale(lins[r], -1)));
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
- const remaining = [...new Set(rows.flatMap(r => vars(lins[r])))];
110
- if (remaining.length === 0)
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
- const cost = (x) => {
114
- const pos = rows.filter(r => (lins[r][x] ?? 0) > 0), neg = rows.filter(r => (lins[r][x] ?? 0) < 0);
115
- const isExact = pos.every(p => lins[p][x] === 1) || neg.every(n => lins[n][x] === -1);
116
- return { x, pos, neg, isExact, size: pos.length * neg.length - pos.length - neg.length };
117
- };
118
- const choice = remaining.map(cost).sort((a, b) => Number(b.isExact) - Number(a.isExact) || a.size - b.size)[0];
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 = JSON.stringify(vars(lin).sort().map(key => [key, lin[key]]));
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);
@@ -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 Seen<T> = {
33
- reads: [string, boolean | undefined][];
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;