gdp-wired 0.15.0 → 0.16.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 CHANGED
@@ -69,6 +69,10 @@ All of these are proven automatically, and each proof is also a Lean theorem:
69
69
  machine runs as, with each guard and action it names given again in gdp-wired's terms, and `conforms(chart, machine,
70
70
  createActor)` drives XState's own actor and the compiled rule with the same random events, comparing every state and
71
71
  context; what a chart can't run the same way is refused when built, with the reason (`examples/xstate.ts`).
72
+ - **What nobody stated, looked for when the prover alone stops**: where `proveRule` or `proveSteps` doesn't prove the laws
73
+ and no run breaks them, it looks for lemmas about the rule's recurrences and lists (`discover`) and then invariants
74
+ (`inferLaws`), within 10 s (`{search: {ms}}`; `{search: false}`, the prover alone); the verdict's `found` holds what
75
+ proved them, as laws and lemmas with the source to paste.
72
76
  - **Invariants nobody wrote** (`gdp-wired/infer`): `inferLaws(rule, laws)` proves a law that holds but isn't inductive
73
77
  alone, with the laws it finds (bounds, sums and differences over the state, the same where a flag or a choice holds
74
78
  one value, and laws about every key of a map), each proven by the kernel with yours and handed back as the
@@ -246,6 +250,13 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
246
250
  `Acct.set(k, value)`, `Acct.each(k => value)`) and `.items({Queue: Job})` (`push`, `pop`, `set`, `insertAt`,
247
251
  `removeAt`, `apply`, `has`, `within`, `increasing(by)`), one map per variable of the kind; `rule.decode` reads them back
248
252
  as objects and arrays.
253
+ - **Sequences a request carries** (`.carriesSeq('Path')`, and tables of them, `.carriesSeqs('Paths')`): a sequence of
254
+ naturals as positions and a length, `Path.at(i)`, `Path.len`, a table's row `Paths.row(r)`; `A.prefixOf(B)`,
255
+ `startsWith`, `equals`, `extends(B, k)`, `has(x)`, each a condition about every position below a length, so laws about
256
+ prefixes of paths need no induction or lemma. The request gives an array of naturals (interned names), a table an
257
+ array of them. `everyBelow(n, k => ...)` checks bookkeeping a request carries row by row (where each grant went). A
258
+ condition about every row of a table can't compare the rows' sequences (that is two keys at once); compare a row at a
259
+ named row, a witness, instead (`examples/scopes.ts`: pi-fabric's path scopes, covering, attenuation and normalization).
249
260
  - **Lists a request carries** (`.carriesList('Lines', Line, folds, lemmas)`): the items one carried map per variable of
250
261
  the kind, the length `Lines.len`, each fold (`{total: {from: 0, step: (acc, line) => acc.plus(line.amount)}}`) a
251
262
  carried map of its running values whose equations every request is assumed to meet (the encoding computes them from the
@@ -256,9 +256,9 @@ export class Chart {
256
256
  missed.push({ name, law: r.law(`${name} is never reached`, (env) => env[reg.field].is(l).not()) });
257
257
  }
258
258
  const proven = (v) => v.proven && v.checked;
259
- const allMissed = missed.length > 0 && proven(proveRule(r, [...this.chartLaws(), ...missed.map(x => x.law)]));
259
+ const allMissed = missed.length > 0 && proven(proveRule(r, [...this.chartLaws(), ...missed.map(x => x.law)], { search: false }));
260
260
  for (const x of missed)
261
- reached[x.name] = allMissed || proven(proveRule(r, [...this.chartLaws(), x.law])) ? 'unreachable' : 'unknown';
261
+ reached[x.name] = allMissed || proven(proveRule(r, [...this.chartLaws(), x.law], { search: false })) ? 'unreachable' : 'unknown';
262
262
  // A way out is a transition to a state: an internal one stays where it is.
263
263
  const stuck = regions.flatMap(reg => reg.leaves.filter(l => !reg.finals.has(l) && !placed.some(t => t.to !== undefined && t.region === reg.name && t.sources.includes(l))).map(l => label(reg, l)));
264
264
  // Pairs as specific as each other, on one event, from a state they share: proven never both enabled, all at once,
@@ -270,9 +270,9 @@ export class Chart {
270
270
  pairs.push({ t, u, law: r.stepLaw(`transitions ${t.order} and ${u.order} on ${t.event} are never both enabled`, (env) => all(env[`on${i}`], env[`on${j}`]).not()) });
271
271
  }));
272
272
  const ambiguous = [];
273
- if (pairs.length && !proven(proveSteps(r, this.chartLaws(), pairs.map(x => x.law))))
273
+ if (pairs.length && !proven(proveSteps(r, this.chartLaws(), pairs.map(x => x.law), { search: false })))
274
274
  for (const { t, u, law } of pairs) {
275
- const v = proveSteps(r, this.chartLaws(), [law]);
275
+ const v = proveSteps(r, this.chartLaws(), [law], { search: false });
276
276
  if (!proven(v))
277
277
  ambiguous.push({ event: t.event, transitions: [t.order, u.order], verdict: v });
278
278
  }
@@ -12,7 +12,7 @@ export type Discovery = {
12
12
  rule: AnyRule;
13
13
  found: Record<string, Found>;
14
14
  };
15
- export declare function discover(r: AnyRule, laws: Law[], stepLaws?: StepLaw[]): Discovery;
15
+ export declare function discover(r: AnyRule, laws: Law[], stepLaws?: StepLaw[], first?: Verdict): Discovery;
16
16
  type TreeFold = {
17
17
  fold: Fold;
18
18
  lemmas?: Law[];
@@ -259,8 +259,8 @@ function houdiniList(r, part, cands, params) {
259
259
  // --- the search ----------------------------------------------------------------------------------------------------
260
260
  // The laws of r, and where they aren't proven as r states them, the lemmas found for its functions and lists: all that
261
261
  // is proven first, then each dropped in turn while the laws stay proven without it.
262
- export function discover(r, laws, stepLaws = []) {
263
- const first = proveSteps(r, laws, stepLaws);
262
+ // `first`: the laws' verdict on r alone, when the caller has it.
263
+ export function discover(r, laws, stepLaws = [], first = proveSteps(r, laws, stepLaws, { search: false })) {
264
264
  if (first.proven && first.checked)
265
265
  return { verdict: first, rule: r, found: {} };
266
266
  const { params, fns, lists } = r.parts, ps = natParams(params);
@@ -284,7 +284,7 @@ export function discover(r, laws, stepLaws = []) {
284
284
  listKept[part[0]] = out.kept;
285
285
  }
286
286
  }
287
- const verdict = proveSteps(rule, laws, stepLaws);
287
+ const verdict = proveSteps(rule, laws, stepLaws, { search: false });
288
288
  const found = () => ({
289
289
  ...Object.fromEntries(Object.entries(fnMade).map(([n, m]) => { const args = [...(recurOf(m.d).def.args ?? [])]; return [n, { ...(m.lemmas.length ? { lemmas: m.lemmas.map(c => recurSrc(c, args, false)) } : {}), ...(m.between.length ? { between: m.between.map(c => recurSrc(c, args, true)) } : {}) }]; })),
290
290
  ...Object.fromEntries(Object.entries(listKept).map(([n, cs]) => [n, { lemmas: cs.map(c => listSrc(c, Object.keys(lists.find(l => l[0] === n)[2]), ps, c.target)) }])),
@@ -297,7 +297,7 @@ export function discover(r, laws, stepLaws = []) {
297
297
  // A rule made again and the laws proven of it; or the lemma whose proof failed without the one dropped.
298
298
  const tryRule = (next) => {
299
299
  try {
300
- const r2 = next(), v = proveSteps(r2, laws, stepLaws);
300
+ const r2 = next(), v = proveSteps(r2, laws, stepLaws, { search: false });
301
301
  return v.proven && v.checked ? { rule: r2, verdict: v } : undefined;
302
302
  }
303
303
  catch (e) {
@@ -299,6 +299,26 @@ export declare class ListRef {
299
299
  within(): Forall;
300
300
  increasing(): Forall;
301
301
  }
302
+ export declare class SeqRef {
303
+ private readonly map;
304
+ readonly len: Term;
305
+ private readonly base?;
306
+ constructor(map: MapRef, len: Term, base?: Term | undefined);
307
+ at(i: TermLike): Term;
308
+ private agree;
309
+ prefixOf(o: SeqRef): Cond;
310
+ startsWith(o: SeqRef): Cond;
311
+ equals(o: SeqRef): Cond;
312
+ extends(o: SeqRef, by: TermLike): Cond;
313
+ has(x: TermLike): Cond;
314
+ }
315
+ export declare class SeqTable {
316
+ readonly name: string;
317
+ readonly rows: Term;
318
+ constructor(name: string, rows: Term);
319
+ row(r: TermLike): SeqRef;
320
+ }
321
+ export declare const everyBelow: (n: TermLike, f: (key: Term) => Cond) => Cond;
302
322
  export type PartsWrite = {
303
323
  readonly parts: Record<string, MapWrite>;
304
324
  };
@@ -473,6 +493,8 @@ type Spec = {
473
493
  nodes?: [string, Kind<unknown>, string[], Record<string, NodeFold<unknown, string>>][];
474
494
  kinded?: [string, Kind<unknown>][];
475
495
  carried?: string[];
496
+ seqs?: string[];
497
+ seqTables?: string[];
476
498
  lists2?: [string, Kind<unknown>, Record<string, ListFold<unknown>>, Record<string, ListLemma>][];
477
499
  fns?: [string, FnDef][];
478
500
  facts?: ((env: never) => Record<string, Cond | Forall>)[];
@@ -539,6 +561,12 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
539
561
  carries<const N extends string>(...names: N[]): Rule<P, I & {
540
562
  [k in N]: MapRef;
541
563
  }, S, M, F, R, Su, Q>;
564
+ carriesSeq<const N extends string>(...names: N[]): Rule<P, I & {
565
+ [k in N]: SeqRef;
566
+ }, S, M, F, R, Su, Q>;
567
+ carriesSeqs<const N extends string>(...names: N[]): Rule<P, I & {
568
+ [k in N]: SeqTable;
569
+ }, S, M, F, R, Su, Q>;
542
570
  carriesList<const N extends string, V, Fd extends string = never>(name: N, of: Kind<V>, folds?: Record<Fd, ListFold<V, P>>, lemmas?: Record<string, (acc: Record<Fd, Term>, i: Term, p: P) => Cond>): Rule<P, I & {
543
571
  [k in N]: CarriedList<V, Fd>;
544
572
  }, S, M, F, R, Su, Q>;
@@ -604,7 +632,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
604
632
  [k in keyof P]: Raw;
605
633
  }): Record<string, number>;
606
634
  encodeInput(values: {
607
- [k in keyof I]: I[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : I[k] extends CarriedList<any, any> ? readonly Raw[] : Raw;
635
+ [k in keyof I]: I[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : I[k] extends SeqRef ? readonly number[] : I[k] extends SeqTable ? readonly (readonly number[])[] : I[k] extends CarriedList<any, any> ? readonly Raw[] : Raw;
608
636
  }, params?: {
609
637
  [k in keyof P]: Raw;
610
638
  }): Request;
@@ -678,7 +706,7 @@ type Value<T> = {
678
706
  [k in keyof T]: ValueOf<T[k]>;
679
707
  };
680
708
  type RawInputs<T> = {
681
- [k in keyof T]: T[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : T[k] extends CarriedList<infer V, infer _F> ? readonly ValueOf<V>[] : ValueOf<T[k]>;
709
+ [k in keyof T]: T[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : T[k] extends SeqRef ? readonly number[] : T[k] extends SeqTable ? readonly (readonly number[])[] : T[k] extends CarriedList<infer V, infer _F> ? readonly ValueOf<V>[] : ValueOf<T[k]>;
682
710
  };
683
711
  type Field = Term | Cond | Int | Decimal | Choice<string> | Str | Variant<any, any> | Maybe<any> | {
684
712
  [f: string]: Field | number;
@@ -770,13 +798,30 @@ export type Verdict = {
770
798
  params: Record<string, number>;
771
799
  trace: Record<string, unknown>[];
772
800
  };
801
+ found?: Found;
802
+ };
803
+ export type Found = {
804
+ laws?: {
805
+ law: Law;
806
+ text: string;
807
+ source?: string;
808
+ }[];
809
+ lemmas?: Record<string, {
810
+ lemmas?: string[];
811
+ between?: string[];
812
+ }>;
813
+ };
814
+ export type ProveOptions = {
815
+ search?: boolean | {
816
+ ms?: number;
817
+ };
773
818
  };
774
819
  export declare class Unproven extends Error {
775
820
  readonly verdict: Verdict;
776
821
  constructor(what: string, verdict: Verdict);
777
822
  }
778
- export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[]): Verdict;
779
- export declare function proveSteps(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], stepLaws: StepLaw[]): Verdict;
823
+ export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], opts?: ProveOptions): Verdict;
824
+ export declare function proveSteps(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], stepLaws: StepLaw[], opts?: ProveOptions): Verdict;
780
825
  export type { StepLaw };
781
826
  export type { Liveness };
782
827
  export declare function proveLeadsTo(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], l: Liveness): Verdict;
package/dist/core/dsl.js CHANGED
@@ -19,6 +19,8 @@ import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersO
19
19
  import { allowed, keysOf, requestEnv, watch } from "./watch.js";
20
20
  import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
21
21
  import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws } from "../kernel/prove.js";
22
+ import { discover } from "./discover.js";
23
+ import { inferLaws } from "./infer.js";
22
24
  // --- terms and conditions -------------------------------------------------------------------------------------------
23
25
  const naturalConst = (v) => {
24
26
  if (!Number.isSafeInteger(v) || v < 0)
@@ -860,6 +862,43 @@ export class ListRef {
860
862
  within() { return forall(k => this.len.atMost(k).implies(this.at(k).eq(0))); }
861
863
  increasing() { return forall((i, j) => i.lt(j).and(j.lt(this.len)).implies(this.at(i).lt(this.at(j)))); }
862
864
  }
865
+ // A sequence of naturals as positions and a length (a path's segments, as interned names): `at(i)` its value at i,
866
+ // `len` its length. Comparing two is a condition about every position below a length (the array property fragment,
867
+ // decidable), so a law about prefixes needs no induction. One a request carries (`carriesSeq`), or a row of a table of
868
+ // them (`carriesSeqs`), its positions read from its row's offset in the table's one map.
869
+ export class SeqRef {
870
+ map;
871
+ len;
872
+ base;
873
+ constructor(map, len, base) {
874
+ this.map = map;
875
+ this.len = len;
876
+ this.base = base;
877
+ }
878
+ at(i) { return this.map.at(!this.base ? i : typeof i === 'number' ? this.base.plus(i) : i.plus(this.base)); }
879
+ // The same values at every position below `upTo` (both have them).
880
+ agree(o, upTo) { return everyKey(k => k.lt(upTo).implies(this.at(k).eq(o.at(k)))); }
881
+ // This one is where o starts; o is where this one starts; the same; o followed by `by` more; x at some position.
882
+ prefixOf(o) { return this.len.atMost(o.len).and(this.agree(o, this.len)); }
883
+ startsWith(o) { return o.prefixOf(this); }
884
+ equals(o) { return this.len.eq(o.len).and(this.agree(o, this.len)); }
885
+ extends(o, by) { return o.prefixOf(this).and(this.len.eq(o.len.plus(by))); }
886
+ has(x) { return everyKey(k => k.lt(this.len).implies(this.at(k).ne(x))).not(); }
887
+ }
888
+ // A table of sequences a request carries (each grant's path, say): `row(r)` is row r's sequence, `rows` how many. Rows
889
+ // are read by their number as a map's key, so a condition about every row reads a row only there (`everyBelow(T.rows,
890
+ // r => ...)`).
891
+ export class SeqTable {
892
+ name;
893
+ rows;
894
+ constructor(name, rows) {
895
+ this.name = name;
896
+ this.rows = rows;
897
+ }
898
+ row(r) { return new SeqRef(new MapRef(this.name, 0), new MapRef(`${this.name}_len`, 0).at(r), new MapRef(`${this.name}_base`, 0).at(r)); }
899
+ }
900
+ // f at every key below n: what bookkeeping a request carries meets, row by row (where each grant went, say).
901
+ export const everyBelow = (n, f) => everyKey(k => k.lt(n).implies(f(k)));
863
902
  const when_ = (c, then, otherwise) => new Term({ op: 'if', c: c.c, then: termOf(then), else: termOf(otherwise) }, c.ok === undefined ? undefined : valOf(c.ok ? then : otherwise));
864
903
  const isPartsWrite = (u) => typeof u === 'object' && u !== null && 'parts' in u;
865
904
  // The parts of a value written at a cell, by the names of the maps they go to.
@@ -995,7 +1034,7 @@ export class CarriedList {
995
1034
  .state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, typeof d.from === 'number' ? d.from : (p) => d.from(p)])) })
996
1035
  .step((env) => ({ i: env.i.plus(1), ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.step(env[`acc$${f}`], item(env.i), env)])) }));
997
1036
  const accs = (env) => Object.fromEntries(folds.map(([f]) => [f, env[`acc$${f}`]]));
998
- return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i, env))));
1037
+ return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i, env))), { search: false });
999
1038
  }
1000
1039
  // Every carried map it is kept in: the items' parts, then each fold's.
1001
1040
  get maps() { return [...this.kind.vars(this.name), ...Object.keys(this.folds).map(f => `${this.name}$${f}`)]; }
@@ -1374,7 +1413,7 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1374
1413
  const r = r0.state({ x: 0, fx: ((p) => (zeroReads ? p.fx0 : zeroAt(args.map(a => p[a])))) })
1375
1414
  .step((env) => ({ x: env.x.plus(1), fx: def.next(env.x, env.fx, envAt(args.map(a => env[a]))) }));
1376
1415
  const laws = Object.entries(lemmas).map(([n, f]) => r.law(n, (env) => f(env.x, env.fx, envAt(args.map(a => env[a])))));
1377
- verdict = proveRule(r, laws);
1416
+ verdict = proveRule(r, laws, { search: false });
1378
1417
  failed('lemmas', verdict);
1379
1418
  proofs.lemmas = { machine: r.machine, laws };
1380
1419
  }
@@ -1411,7 +1450,7 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1411
1450
  .step((env) => ({ y: env.y.plus(1), fy: def.next(env.y, env.fy, envAt(at(env))) }));
1412
1451
  const laws = [...Object.entries(lemmas).map(([n, l]) => r.law(`${n} at y`, (env) => l(env.y, env.fy, envAt(at(env))))),
1413
1452
  ...Object.entries(between).map(([n, l]) => r.law(n, (env) => l(env.x, env.fx, env.y, env.fy, envAt(at(env)))))];
1414
- const v = proveRule(r, laws);
1453
+ const v = proveRule(r, laws, { search: false });
1415
1454
  failed('laws between two points', v);
1416
1455
  proofs.between = { machine: r.machine, laws };
1417
1456
  verdict = { proven: true, checked: verdict.checked && v.checked };
@@ -1631,6 +1670,16 @@ export class Rule {
1631
1670
  facts(f) { return this.with({ facts: [...(this.spec.facts ?? []), f] }); }
1632
1671
  // Maps each request carries whole (a set of names, say: cells 0 or 1), every cell past them 0; read like any map.
1633
1672
  carries(...names) { return this.with({ carried: [...(this.spec.carried ?? []), ...names] }); }
1673
+ // Sequences each request carries (SeqRef): `.carriesSeq('Path')`, then `Path.at(i)`, `Path.len`, `A.prefixOf(B)`; the
1674
+ // request gives each as an array of naturals.
1675
+ carriesSeq(...names) {
1676
+ return this.with({ carried: [...(this.spec.carried ?? []), ...names], inputs: [...this.spec.inputs, ...names.map(n => [`${n}_len`, nat])], seqs: [...(this.spec.seqs ?? []), ...names] });
1677
+ }
1678
+ // Tables of sequences each request carries (SeqTable): `.carriesSeqs('Paths')`, then `Paths.row(r)`, `Paths.rows`; the
1679
+ // request gives each as an array of arrays of naturals.
1680
+ carriesSeqs(...names) {
1681
+ return this.with({ carried: [...(this.spec.carried ?? []), ...names.flatMap(n => [n, `${n}_base`, `${n}_len`])], inputs: [...this.spec.inputs, ...names.map(n => [`${n}_rows`, nat])], seqTables: [...(this.spec.seqTables ?? []), ...names] });
1682
+ }
1634
1683
  // A list of values of a kind each request carries, with folds over it and lemmas about the folds (CarriedList):
1635
1684
  // .carriesList('Lines', record({amount: nat}), {total: {from: 0, step: (acc, line) => acc.plus(line.amount)}})
1636
1685
  // Each fold's start and step, and each lemma, may read the params declared before it.
@@ -1846,6 +1895,15 @@ export class Rule {
1846
1895
  out[n] = k.value(n);
1847
1896
  for (const n of this.spec.carried ?? [])
1848
1897
  out[n] = new MapRef(n, 0);
1898
+ for (const n of this.spec.seqs ?? []) {
1899
+ delete out[`${n}_len`];
1900
+ out[n] = new SeqRef(new MapRef(n, 0), v(`${n}_len`));
1901
+ }
1902
+ for (const n of this.spec.seqTables ?? []) {
1903
+ for (const part of [`${n}_base`, `${n}_len`, `${n}_rows`])
1904
+ delete out[part];
1905
+ out[n] = new SeqTable(n, v(`${n}_rows`));
1906
+ }
1849
1907
  const ps = Object.fromEntries(this.spec.params.map(([n]) => [n, out[n]]));
1850
1908
  for (const [n, k, fs, ls] of this.spec.lists2 ?? []) {
1851
1909
  const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, ps, this.spec.params);
@@ -2187,16 +2245,35 @@ export class Rule {
2187
2245
  // --- running it -----------------------------------------------------------------------------------------------------
2188
2246
  // Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
2189
2247
  encodeParams(values) { return encode(this.spec.params, values); }
2190
- // A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural.
2248
+ // A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural; a sequence an array of
2249
+ // naturals, a table of them an array of such arrays.
2191
2250
  // A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
2192
2251
  // folds that read the params are computed with.
2193
2252
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2194
2253
  encodeInput(values, params) {
2195
2254
  const at = params && this.paramValues(this.encodeParams(params));
2196
2255
  const lists = Object.fromEntries((this.spec.lists2 ?? []).map(([n, k, fs]) => [n, new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, at).encode(values[n])]));
2197
- const out = encode(this.spec.inputs, { ...values, ...Object.fromEntries(Object.entries(lists).map(([n, l]) => [`${n}_len`, l.len])) });
2256
+ // A sequence: its values at their positions (no 0 cell), its length; a table: the rows one after another in one map,
2257
+ // each row's offset and length.
2258
+ const seqMaps = {}, seqLens = {};
2259
+ const cells = (xs, from = 0) => xs.flatMap((x, i) => (x ? [[from + i, x]] : []));
2260
+ for (const n of this.spec.seqs ?? []) {
2261
+ const xs = values[n] ?? [];
2262
+ seqMaps[n] = new Map(cells(xs));
2263
+ seqLens[`${n}_len`] = xs.length;
2264
+ }
2265
+ for (const n of this.spec.seqTables ?? []) {
2266
+ const rows = values[n] ?? [], base = [];
2267
+ rows.reduce((at, r) => (base.push(at), at + r.length), 0);
2268
+ seqMaps[n] = new Map(rows.flatMap((r, i) => cells(r, base[i])));
2269
+ seqMaps[`${n}_base`] = new Map(base.flatMap((b, i) => (b ? [[i, b]] : [])));
2270
+ seqMaps[`${n}_len`] = new Map(rows.flatMap((r, i) => (r.length ? [[i, r.length]] : [])));
2271
+ seqLens[`${n}_rows`] = rows.length;
2272
+ }
2273
+ const out = encode(this.spec.inputs, { ...values, ...seqLens, ...Object.fromEntries(Object.entries(lists).map(([n, l]) => [`${n}_len`, l.len])) });
2198
2274
  for (const l of Object.values(lists))
2199
2275
  Object.assign(out, l.maps);
2276
+ Object.assign(out, seqMaps);
2200
2277
  for (const n of this.spec.carried ?? []) {
2201
2278
  if (n in out)
2202
2279
  continue;
@@ -2432,14 +2509,16 @@ const withKinds = (r, laws) => [...r.kindLaws().filter(k => !laws.some(l => l.na
2432
2509
  // interpreter took. A rule that reads functions is skipped: the kernel knows them only by their facts (a recurrence's
2433
2510
  // induction machine reads the function it proves things about), so a run computing them proves nothing about the goal.
2434
2511
  const refuteBudget = { trials: 60, depth: 6, bound: 3, ms: 150 };
2435
- function refuteByRuns(r, laws, stepLaws = []) {
2512
+ // The search's own runs, before it looks for what the laws need: deeper and wider, from another seed.
2513
+ const deeperBudget = { trials: 600, depth: 24, bound: 8, ms: 1500, seed: 29 };
2514
+ function refuteByRuns(r, laws, stepLaws = [], budget = refuteBudget) {
2436
2515
  const m = r.machine;
2437
2516
  if (Object.keys(m.fns ?? {}).length || m.facts?.length)
2438
2517
  return undefined;
2439
- const run = interpret(m), rand = seeded(13), until = performance.now() + refuteBudget.ms, bound = refuteBudget.bound;
2518
+ const run = interpret(m), rand = seeded(budget.seed ?? 13), until = performance.now() + budget.ms, bound = budget.bound;
2440
2519
  const shown = (input) => Object.fromEntries(Object.entries(input).map(([n, x]) => [n, typeof x === 'number' ? x : Object.fromEntries(x)]));
2441
2520
  try {
2442
- for (let trial = 0; trial < refuteBudget.trials && performance.now() < until; trial++) {
2521
+ for (let trial = 0; trial < budget.trials && performance.now() < until; trial++) {
2443
2522
  const params = r.randomParams(rand, bound);
2444
2523
  if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
2445
2524
  continue;
@@ -2451,7 +2530,7 @@ function refuteByRuns(r, laws, stepLaws = []) {
2451
2530
  for (const { w, on } of watching)
2452
2531
  if (!('then' in w) && !on.start(st))
2453
2532
  return broken(w, '@start', st);
2454
- for (let d = 0, tries = 0; d < refuteBudget.depth && tries < 40; tries++) {
2533
+ for (let d = 0, tries = 0; d < budget.depth && tries < 40; tries++) {
2455
2534
  const input = r.randomInput(rand, bound, params);
2456
2535
  if (!allowed(m, run, params, st, input))
2457
2536
  continue;
@@ -2471,7 +2550,7 @@ function refuteByRuns(r, laws, stepLaws = []) {
2471
2550
  // variable of a kind is drawn as its kind draws (33 yes/nos drawn from 0..7 almost never make a lawful state).
2472
2551
  const steps1 = stepLaws.filter(sl => (sl.steps ?? 1) === 1), given = withKinds(r, laws);
2473
2552
  const wide = Object.keys(m.state).length + m.inputs.length >= 32, lawsHere = wide ? given : [], kinded = wide ? r.kindsOfState() : [];
2474
- for (let trial = 0; !m.history && (steps1.length || lawsHere.length) && trial < refuteBudget.trials * 4 && performance.now() < until; trial++) {
2553
+ for (let trial = 0; !m.history && (steps1.length || lawsHere.length) && trial < budget.trials * 4 && performance.now() < until; trial++) {
2475
2554
  const params = r.randomParams(rand, bound);
2476
2555
  if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
2477
2556
  continue;
@@ -2499,25 +2578,56 @@ function refuteByRuns(r, laws, stepLaws = []) {
2499
2578
  return undefined;
2500
2579
  }
2501
2580
  const refutedVerdict = (out) => ({ proven: false, checked: false, goal: out.goal, stuck: 'sat', witness: out.witness, ...(out.run ? { run: out.run } : {}) });
2502
- export function proveRule(r, laws) {
2581
+ export function proveRule(r, laws, opts = {}) {
2503
2582
  const refuted = refuteByRuns(r, laws);
2504
2583
  if (refuted)
2505
2584
  return refutedVerdict(refuted);
2506
2585
  const out = proveLaws(r.machine, withKinds(r, laws));
2507
- return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
2586
+ const v = { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
2587
+ return searched(r, laws, [], v, opts);
2508
2588
  }
2509
- export function proveSteps(r, laws, stepLaws) {
2589
+ export function proveSteps(r, laws, stepLaws, opts = {}) {
2510
2590
  const refuted = refuteByRuns(r, laws, stepLaws);
2511
2591
  if (refuted)
2512
2592
  return refutedVerdict(refuted);
2513
2593
  const out = proveStepLaws(r.machine, withKinds(r, laws), stepLaws);
2514
- return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
2594
+ const v = { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
2595
+ return searched(r, laws, stepLaws, v, opts);
2596
+ }
2597
+ // The search, where the prover alone didn't prove the laws and nothing refuted them: the lemmas first where the rule has
2598
+ // recurrences or carried lists (the rule made again with them), then invariants over what it then is. The verdict is the
2599
+ // prover's on the laws with what was found; when nothing is found, the prover's own (with why).
2600
+ function searched(r, laws, stepLaws, v, opts) {
2601
+ if ((v.proven && v.checked) || opts.search === false || v.run || v.stuck === 'sat' && !/@step|@\d+ steps/.test(v.goal ?? ''))
2602
+ return v;
2603
+ const deeper = refuteByRuns(r, laws, stepLaws, deeperBudget);
2604
+ if (deeper?.run)
2605
+ return refutedVerdict(deeper);
2606
+ const until = performance.now() + (typeof opts.search === 'object' && opts.search.ms !== undefined ? opts.search.ms : 10_000);
2607
+ let rule = r, lemmas;
2608
+ const { fns, lists } = r.parts;
2609
+ if (lists.length || fns.some(([, d]) => recurOf(d))) {
2610
+ const d = discover(r, laws, stepLaws, v);
2611
+ if (d.verdict.proven && d.verdict.checked)
2612
+ return { ...d.verdict, found: { lemmas: d.found } };
2613
+ if (Object.keys(d.found).length)
2614
+ (rule = d.rule, lemmas = d.found);
2615
+ }
2616
+ const ms = until - performance.now();
2617
+ if (ms < 500)
2618
+ return v;
2619
+ const inf = inferLaws(rule, laws, stepLaws, { ms, ...(rule === r ? { first: v } : {}) });
2620
+ if (!(inf.proven && inf.checked))
2621
+ return { ...v, why: [v.why, `the search found nothing that proves it in ${Math.round(inf.stats.ms)} ms`].filter(Boolean).join('; ') };
2622
+ const { found, stats: _stats, ...verdict } = inf;
2623
+ void _stats;
2624
+ return { ...verdict, found: { laws: found, ...(lemmas ? { lemmas } : {}) } };
2515
2625
  }
2516
2626
  export function proveLeadsTo(r, laws, l) {
2517
2627
  const out = proveLiveness(r.machine, withKinds(r, laws), l);
2518
2628
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
2519
2629
  }
2520
- export const proveDecision = (d) => (d.steps ? proveSteps(d.steps.rule, [], [d.steps.law]) : goalsVerdict([d.goal]));
2630
+ export const proveDecision = (d) => (d.steps ? proveSteps(d.steps.rule, [], [d.steps.law], { search: false }) : goalsVerdict([d.goal]));
2521
2631
  const seeded = (seed) => { let x = seed; return k => { x = (x * 1103515245 + 12345) % 2 ** 31; return Math.floor(x / 2 ** 31 * k); }; };
2522
2632
  // A decision's callbacks against its condition, on random params and requests: `given` and `then` each run with every
2523
2633
  // value known, against what the interpreter evaluates of the decision built from them.
@@ -112,7 +112,7 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
112
112
  const prove = (ls) => {
113
113
  proofs++;
114
114
  if (rule)
115
- return stepLaws.length ? proveSteps(rule, ls, stepLaws) : proveRule(rule, ls);
115
+ return stepLaws.length ? proveSteps(rule, ls, stepLaws, { search: false }) : proveRule(rule, ls, { search: false });
116
116
  const out = stepLaws.length ? proveStepLaws(m, ls, stepLaws) : proveLaws(m, ls);
117
117
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
118
118
  };
@@ -245,16 +245,18 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
245
245
  };
246
246
  const choose = (k, of) => { let r = 1; for (let i = 0; i < k; i++)
247
247
  r = r * (of - i) / (i + 1); return r; };
248
- const plain = atoms.flatMap((a, i) => (a.cell ? [] : [i])), P = plain.length;
248
+ // A choice's index orders its values arbitrarily: it is a guard, or alone (a bound on it reads as the values it
249
+ // allows), never a term among others.
250
+ const plain = atoms.flatMap((a, i) => (a.cell ? [] : [i])), terms = plain.filter(i => !modeOf(i)?.choice), P = terms.length;
249
251
  const scalarOk = (acc) => acc.some(([i]) => !atoms[i].param);
250
252
  pick(1, [1], plain, scalarOk, false);
251
- pick(2, choose(2, P) * 4 * mags.length ** 2 <= maxCandidates / 4 ? mags : [1], plain, scalarOk, false);
253
+ pick(2, choose(2, P) * 4 * mags.length ** 2 <= maxCandidates / 4 ? mags : [1], terms, scalarOk, false);
252
254
  if (choose(3, P) * 8 * mags.length ** 3 <= maxCandidates / 2)
253
- pick(3, mags, plain, scalarOk, false);
255
+ pick(3, mags, terms, scalarOk, false);
254
256
  else if (choose(3, P) * 8 <= maxCandidates / 2)
255
- pick(3, [1], plain, scalarOk, false);
257
+ pick(3, [1], terms, scalarOk, false);
256
258
  if (cellAtoms.length) {
257
- const all = atoms.map((_, i) => i), cellOk = (acc) => acc.some(([i]) => atoms[i].cell) && acc.filter(([i]) => !atoms[i].cell).length <= 1;
259
+ const all = atoms.flatMap((_, i) => (modeOf(i)?.choice ? [] : [i])), cellOk = (acc) => acc.some(([i]) => atoms[i].cell) && acc.filter(([i]) => !atoms[i].cell).length <= 1;
258
260
  pick(1, [1], cellAtoms, () => true, true);
259
261
  pick(2, [1], all, cellOk, true);
260
262
  if (choose(3, A) * 8 <= maxCandidates / 4)
@@ -329,7 +331,8 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
329
331
  return `${nameOf(f.unreachable.atom)} is never ${shownValue(f.unreachable.atom, f.unreachable.value)}`;
330
332
  const { l, r } = sides(f), within = allowedValues(f);
331
333
  const body = within ? `${nameOf(f.coef[0][0])} is ${within.map(v => shownValue(f.coef[0][0], v)).join(' or ')}` : `${l} <= ${r}`;
332
- const guarded = !f.guard ? body : 'keyNot' in f.guard ? `k = ${nameOf(f.guard.keyNot)} or ${body}` : `${nameOf(f.guard.atom)} is ${shownValue(f.guard.atom, f.guard.value)} → ${body}`;
334
+ const under = f.when ? (({ l: wl, r: wr }) => `${wl} <= ${wr} → `)(sides({ coef: f.when.coef, d: f.when.d, cells: false, size: 0 })) : '';
335
+ const guarded = !f.guard ? under + body : 'keyNot' in f.guard ? `k = ${nameOf(f.guard.keyNot)} or ${body}` : `${nameOf(f.guard.atom)} is ${shownValue(f.guard.atom, f.guard.value)} → ${under}${body}`;
333
336
  return f.cells ? `every k: ${guarded}` : guarded;
334
337
  };
335
338
  const sum = (es) => es.reduce((p, q) => plus(p, q), n(0));
@@ -347,12 +350,13 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
347
350
  if (f.unreachable)
348
351
  return notAt(f.unreachable.atom, f.unreachable.value);
349
352
  const lhs = sum([...partsOf(f, 1), ...(f.d < 0 ? [n(-f.d)] : [])]), rhs = sum([...partsOf(f, -1), ...(f.d > 0 ? [n(f.d)] : [])]);
350
- const body = atMost(lhs, rhs);
353
+ // Under a condition Σ ≤ d: its negation, −Σ ≤ −d − 1, or the body.
354
+ const body = f.when ? any(condOf({ coef: f.when.coef.map(([i, c]) => [i, -c]), d: -f.when.d - 1, cells: false, size: 0 }), atMost(lhs, rhs)) : atMost(lhs, rhs);
351
355
  return !f.guard ? body : 'keyNot' in f.guard ? any(same(x(KEY), x(atoms[f.guard.keyNot].parts[0][0])), body) : any(notAt(f.guard.atom, f.guard.value), body);
352
356
  };
353
357
  // The builder call, for a rule whose atoms are its own variables (naturals, or integers alone).
354
358
  const sourceOf = (f, text) => {
355
- if (!rule)
359
+ if (!rule || f.when)
356
360
  return undefined;
357
361
  const used = [...new Set([...f.coef.map(([i]) => i), ...(f.guard ? ['keyNot' in f.guard ? f.guard.keyNot : f.guard.atom] : []), ...(f.unreachable ? [f.unreachable.atom] : [])])];
358
362
  const ints = f.coef.filter(([i]) => atoms[i].int).length;
@@ -388,6 +392,13 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
388
392
  else if (valueOf(atoms[f.guard.atom], env) !== f.guard.value)
389
393
  return true;
390
394
  }
395
+ if (f.when) {
396
+ let w = 0;
397
+ for (const [i, c] of f.when.coef)
398
+ w += c * valueOf(atoms[i], env);
399
+ if (w > f.when.d)
400
+ return true;
401
+ }
391
402
  let s = 0;
392
403
  for (const [i, c] of f.coef)
393
404
  s += c * valueOf(atoms[i], env);
@@ -489,6 +500,97 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
489
500
  }
490
501
  return pickF;
491
502
  };
503
+ // A counterexample to induction no candidate rules out: forms false at it that every sample meets under a condition
504
+ // it meets too (one value, or a sum or difference of two, at most a threshold: 0, ±1, or its own value there), the
505
+ // choice (loc, say) at its value there as the guard. A condition no sample meets gives its negation instead. Only the
506
+ // eight values most unusual at it are read (furthest past the samples in its guard, then the rarest there).
507
+ const choiceMode = modes.find(mo => mo.choice && !atoms[mo.atom].cell);
508
+ const condAtoms = plain.filter(i => !atoms[i].param && i !== choiceMode?.atom);
509
+ const fromCti = (p) => {
510
+ const stop = Math.min(until, performance.now() + Math.max(300, (until - performance.now()) / 8));
511
+ const sc = p.scalar, pv = (coef) => { let v = 0; for (const [i, c] of coef)
512
+ v += c * valueOf(atoms[i], sc); return v; };
513
+ const gv = choiceMode ? valueOf(atoms[choiceMode.atom], sc) : undefined;
514
+ const inGuard = [];
515
+ for (let j = 0; j < NS; j++)
516
+ if (gv === undefined || valsS[choiceMode.atom][j] === gv)
517
+ inGuard.push(j);
518
+ const unusual = (i) => {
519
+ const v = valueOf(atoms[i], sc);
520
+ let lo = Infinity, hi = -Infinity, same = 0;
521
+ for (const j of inGuard) {
522
+ const w = valsS[i][j];
523
+ if (w < lo)
524
+ lo = w;
525
+ if (w > hi)
526
+ hi = w;
527
+ if (w === v)
528
+ same++;
529
+ }
530
+ return [Math.max(0, lo - v, v - hi), -same];
531
+ };
532
+ const focus = condAtoms.map(i => ({ i, u: unusual(i) })).sort((a, b) => b.u[0] - a.u[0] || b.u[1] - a.u[1]).slice(0, 8).map(x => x.i);
533
+ // Over the focus: one or two values with the magnitudes, three with ±1.
534
+ const bodies = [];
535
+ const signed = (ks) => ks.flatMap(k => [k, -k]);
536
+ for (const [a, i] of focus.entries()) {
537
+ bodies.push({ coef: [[i, 1]] }, { coef: [[i, -1]] });
538
+ for (const [b2, k] of focus.slice(a + 1).entries()) {
539
+ for (const c1 of signed(mags))
540
+ for (const c2 of signed(mags))
541
+ if (gcd(c1, c2) === 1)
542
+ bodies.push({ coef: [[i, c1], [k, c2]] });
543
+ for (const q of focus.slice(a + b2 + 2))
544
+ for (const c1 of [1, -1])
545
+ for (const c2 of [1, -1])
546
+ for (const c3 of [1, -1])
547
+ bodies.push({ coef: [[i, c1], [k, c2], [q, c3]] });
548
+ }
549
+ }
550
+ const conds = [];
551
+ for (const [a, i] of focus.entries()) {
552
+ conds.push([[i, 1]], [[i, -1]]);
553
+ for (const k of focus.slice(a + 1))
554
+ conds.push([[i, 1], [k, -1]], [[i, -1], [k, 1]], [[i, 1], [k, 1]]);
555
+ }
556
+ const out = [], guard = choiceMode ? { atom: choiceMode.atom, value: gv } : undefined;
557
+ for (const coef of conds) {
558
+ const here = pv(coef);
559
+ for (const d of [...new Set([here, 0, 1, -1])].filter(d => here <= d)) {
560
+ if (performance.now() > stop)
561
+ break;
562
+ const rows = [];
563
+ for (const j of inGuard)
564
+ if (at(coef, valsS, j) <= d)
565
+ rows.push(j);
566
+ const when = { coef, d }, base = sizeOf(coef) + 80;
567
+ if (!rows.length) {
568
+ out.push({ coef: coef.map(([i, c]) => [i, -c]), d: -d - 1, cells: false, ...(guard ? { guard } : {}), size: base });
569
+ continue;
570
+ }
571
+ const stride = Math.max(1, Math.ceil(rows.length / 600));
572
+ for (const [q, sh] of bodies.entries()) {
573
+ if (q % 256 === 0 && performance.now() > stop)
574
+ break;
575
+ const v = pv(sh.coef);
576
+ let most = -Infinity, ok = true;
577
+ for (let t = 0; t < rows.length; t += stride) {
578
+ const w = at(sh.coef, valsS, rows[t]);
579
+ if (w >= v) {
580
+ ok = false;
581
+ break;
582
+ }
583
+ if (w > most)
584
+ most = w;
585
+ }
586
+ if (ok && Number.isFinite(most))
587
+ out.push({ coef: sh.coef, d: most, cells: false, ...(guard ? { guard } : {}), when, size: base + sizeOf(sh.coef) });
588
+ }
589
+ }
590
+ }
591
+ // A stride may skip a sample that breaks a form: each is checked again at every sample in its guard.
592
+ return out.sort((x, y) => x.size - y.size).slice(0, 2000).filter(f => inGuard.every(j => holdsRow(f, rowsS[j]))).slice(0, 400);
593
+ };
492
594
  // The search proves one law at a time (proveLaw), each with every law assumed, and keeps what it proved while laws
493
595
  // are only added (more laws assumed keep a proof a proof); dropping one forgets it all. Once every law is proven so,
494
596
  // the set is proven as a whole, by the rule's own prover: that verdict is the answer.
@@ -545,7 +647,13 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
545
647
  const p = /@step|@\d+ steps/.test(last.goal) && last.witness ? witnessPoint(last.witness) : undefined;
546
648
  if (p)
547
649
  ctis.push(p);
548
- const next = p ? best(p) : undefined;
650
+ let next = p ? best(p) : undefined;
651
+ if (!next && p) {
652
+ const t = performance.now(), made = fromCti(p);
653
+ pool.push(...made);
654
+ next = best(p);
655
+ opts.progress?.('cti', { forms: made.length, ms: Math.round(performance.now() - t) });
656
+ }
549
657
  if (next) {
550
658
  chosen.push(next);
551
659
  continue;
@@ -283,6 +283,27 @@ function growPlain(goal, path, budget) {
283
283
  return { tree: leafOf(third.steps, weighted, squares), nodes: budget.nodes };
284
284
  }
285
285
  }
286
+ // A multiplier: every row times a factor the path has at least 1 (the row 1 − x <= 0), with the squares (x·a − b)
287
+ // and (x·b − a) of each product a·b the rows read: a certificate that multiplies the goal by x first ((Σv)² <= n·Σv²
288
+ // over running samples is n times the goal, closed by (n·v − s)²).
289
+ const ones = rows.flatMap((r, i) => { const ks = Object.keys(r.lin); return ks.length === 2 && r.lin['1'] === 1 && ks.some(key => key !== '1' && r.lin[key] === -1 && factorSet.has(key)) ? [i] : []; });
290
+ for (const xi of ones.slice(0, 3)) {
291
+ const x = Object.keys(rows[xi].lin).find(key => key !== '1'), name = (fs) => [...fs].sort().join('*');
292
+ const forms = new Map();
293
+ for (const m of new Set(rows.flatMap(r => Object.keys(r.lin).filter(isMonomial)))) {
294
+ const [a, b, ...more] = factorsOf(m);
295
+ if (!a || !b || more.length)
296
+ continue;
297
+ for (const [u, w] of [[a, b], [b, a]]) {
298
+ const t = { [name([x, u])]: 1, [w]: -1 };
299
+ forms.set(canon(t), t);
300
+ }
301
+ }
302
+ const times = relevant.map(i => [i, xi]), sq = [...forms.values()].slice(0, 32);
303
+ const multiplied = solve(withProducts(rows, times, sq));
304
+ if (multiplied.kind === 'refuted')
305
+ return { tree: leafOf(multiplied.steps, times, sq), nodes: budget.nodes };
306
+ }
286
307
  const factors = [...new Set(rows.flatMap(r => Object.keys(r.lin).filter(isMonomial).flatMap(factorsOf)))];
287
308
  const fresh = (lin) => !path.has(canon(lin)) && !path.has(canonFlip(lin));
288
309
  // Split a factor at zero, else two factors against each other (so (x - y)² >= 0 becomes a product of rows).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdp-wired",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "An automatic prover for stateful rules (budgets, caps, windows, workflows, policies): laws proven with no proof to write, checked again in Lean, counterexamples found on the GPU, the rule shipped as code",
5
5
  "keywords": [
6
6
  "verification",
@@ -101,7 +101,7 @@
101
101
  "build": "bun scripts/build.ts",
102
102
  "prepublishOnly": "bun scripts/build.ts",
103
103
  "smoke": "sh scripts/smoke.sh",
104
- "test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts gdp.test.ts datatypes.test.ts lemmas.test.ts discover.test.ts infer.test.ts invoices.test.ts folds.test.ts trees.test.ts pairs.test.ts chart.test.ts xstate.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/guide.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/datatypes.ts && bun examples/lemmas.ts && bun examples/discover.ts && bun examples/invoices.ts && bun examples/folds.ts && bun examples/trees.ts && bun examples/pairs.ts && bun examples/groups.ts && bun examples/charts.ts && bun examples/xstate.ts && bun examples/infer.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/gdp.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
104
+ "test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts gdp.test.ts datatypes.test.ts lemmas.test.ts discover.test.ts infer.test.ts seqs.test.ts search.test.ts invoices.test.ts folds.test.ts trees.test.ts pairs.test.ts chart.test.ts xstate.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/guide.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/datatypes.ts && bun examples/lemmas.ts && bun examples/discover.ts && bun examples/invoices.ts && bun examples/folds.ts && bun examples/trees.ts && bun examples/pairs.ts && bun examples/groups.ts && bun examples/charts.ts && bun examples/xstate.ts && bun examples/infer.ts && bun examples/scopes.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/gdp.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
105
105
  },
106
106
  "devDependencies": {
107
107
  "@gdp-ts/core": "0.1.0",