eyeprolog 1.5.85 → 1.5.86

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
@@ -84,7 +84,7 @@ The checked [Symbiotic Knowledge Graphs example](examples/symbiotic-knowledge-gr
84
84
  The same RDF → Prolog → RDF boundary is exercised by five additional checked scenarios: [cross-organization data sharing](https://eyereasoner.github.io/eyeprolog/examples/deck/cross-organization-data-sharing), [explainable EV-depot configuration](https://eyereasoner.github.io/eyeprolog/examples/deck/explainable-ev-depot-configuration), [operational incident response](https://eyereasoner.github.io/eyeprolog/examples/deck/operational-incident-response), [software supply-chain vulnerability response](https://eyereasoner.github.io/eyeprolog/examples/deck/sbom-vulnerability-response), and a [scientific evidence graph](https://eyereasoner.github.io/eyeprolog/examples/deck/scientific-evidence-graph). Together they cover policy decisions, reversible configuration reasoning, dependency-graph diagnosis, transitive SBOM exposure, and evidence aggregation with explicit disagreement.
85
85
 
86
86
  ## Benchmarks
87
- EyeProlog has 21 checksum-protected wall-clock benchmarks spanning recursion/indexing, constraints, tabling/WFS, DCGs, Eyelet, search, term I/O, attributes, rewriting, the dynamic database, and bignum arithmetic. Short workloads are adaptively batched before timing so millisecond-scale noise is not mistaken for a regression. Run `npm run benchmark`; create a machine-local comparison point with `npm run benchmark -- --save .benchmarks/baseline.json`; use `node test/run-benchmark-tests.mjs` for harness checks. For a classic LIPS number, run `node test/lips-benchmark.mjs`: it executes the classic failure-driven `dobench/1` and `dodummy/1` loops in Prolog over the checked [`examples/bench.pl`](examples/bench.pl) naive-reverse workload (the classic Quintus 1984 `nrev/2` benchmark on a 30-element list), subtracts dummy-loop CPU time, and applies the historical 496 procedure calls per reversal. LIPS is a historical basic-engine-speed indicator, not a whole-system performance score. Details are in [*The Art of EyeProlog*](the-art-of-eyeprolog.md).
87
+ EyeProlog has 21 checksum-protected wall-clock benchmarks spanning recursion/indexing, constraints, tabling/WFS, DCGs, Eyelet, search, term I/O, attributes, rewriting, the dynamic database, and bignum arithmetic. Short workloads are adaptively batched before timing so millisecond-scale noise is not mistaken for a regression. Run `npm run benchmark`; create a machine-local comparison point with `npm run benchmark -- --save .benchmarks/baseline.json`; use `node test/run-benchmark-tests.mjs` for harness checks. Details are in [*The Art of EyeProlog*](the-art-of-eyeprolog.md).
88
88
  For the project policy on post-ISO-standard and WG17 compatibility features such as digit separators, see [ISO/WG17 compatibility extensions](test/conformance/ISO-WG17-EXTENSIONS.md).
89
89
  ## Development
90
90
  ```sh
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.85",
6
+ "version": "1.5.86",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/test/README.md CHANGED
@@ -27,7 +27,6 @@ These runners retain their existing options; there is no separate npm alias for
27
27
  each one. Focused checks do not replace the full release gate.
28
28
 
29
29
  For performance measurements, use `npm run benchmark`. Save a local baseline with
30
- `npm run benchmark -- --save .benchmarks/baseline.json`, or run
31
- `node test/lips-benchmark.mjs` for the classic LIPS measurement.
30
+ `npm run benchmark -- --save .benchmarks/baseline.json`.
32
31
 
33
32
  See the [conformance guide](conformance/README.md) for report maintenance.
@@ -17,21 +17,18 @@ quads for the ISO read and write option `variable_names/1`:
17
17
  Retrieved on 2026-08-25. It is vendored so all input, output, waiting, and
18
18
  error cases remain release-gated.
19
19
 
20
- `prologue_quad.pl` is an unmodified snapshot of the 72 machine-readable quads
21
- for the predicates proposed by the Prolog Prologue working draft:
22
-
23
- <https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue_quad.pl>
24
-
25
- The corresponding working draft is at
26
- <https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue>.
27
- The corpus snapshot was retrieved on 2026-09-12; it merges what used to be
28
- tracked as a separate, narrower `call_nth` corpus (retired -- see git
29
- history) and updates the `member/2`, `select/3`, `nth0/3`, and `max_integer`
30
- quads to the working draft's current wording. The regression gate requires
31
- the entire vendored corpus to pass outright, the same as the live Neumerkel
32
- gate checks the current upstream bytes on every `npm test` run (see
33
- [NEUMERKEL-LIVE.md](../conformance/NEUMERKEL-LIVE.md)).
34
-
35
- `prologue_quad_runner.pl` loads EyeProlog's `library(prologue)` and includes
36
- the unmodified corpus, mirroring the draft's requirement that a Prologue be
37
- included before its examples are run.
20
+ There used to be a `prologue_quad.pl`/`prologue_quad_runner.pl` pair here: an
21
+ unmodified snapshot of the Prolog Prologue working draft's machine-readable
22
+ quads (<https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue_quad.pl>),
23
+ plus a runner that loaded `library(prologue)` before including it. It was
24
+ retired (see git history) once its coverage became strictly redundant with
25
+ the *live* Prologue source the Neumerkel gate already fetches and checks on
26
+ every `npm test` run -- the same reason the WG17 syntax matrix has no
27
+ vendored snapshot of its own either (see
28
+ [NEUMERKEL-LIVE.md](../conformance/NEUMERKEL-LIVE.md) and
29
+ [conformance/README.md](../conformance/README.md)). Unlike `phrase_quad.pl`
30
+ and `variable_names_quad.pl` below, the vendored Prologue corpus also
31
+ included two open-ended STO/rational-tree generator quads
32
+ (`member(X,X)`, `select(E,Xs,Xs)`) that made checking it offline
33
+ disproportionately slow, with no coverage benefit over the live gate to
34
+ justify the cost.
@@ -2396,41 +2396,6 @@ c4 ?- call((!;1)).
2396
2396
  }
2397
2397
  },
2398
2398
  },
2399
- {
2400
- name: 'runQuads passes the complete vendored Prolog Prologue corpus',
2401
- run: () => {
2402
- const filename = path.join(testRoot, 'fixtures', 'prologue_quad_runner.pl');
2403
- const source = fs.readFileSync(filename, 'utf8');
2404
- const program = Program.parseSources([{
2405
- text: source,
2406
- filename,
2407
- baseDir: path.dirname(filename),
2408
- }]);
2409
- assertEqual(program.quads.length, 72, 'vendored quad total');
2410
- const maxIntegerQuads = program.quads.filter(({ query }) =>
2411
- termToString(query).includes('current_prolog_flag(max_integer, MI)'));
2412
- assertEqual(maxIntegerQuads.length, 4, 'max_integer quad count');
2413
-
2414
- // The full 72-quad corpus, including its rational-tree/occurs-check
2415
- // STO examples (member(X,X) and select(E,Xs,Xs), both open-ended
2416
- // native-generator searches -- see the maxInferences accounting added
2417
- // to generatedLengthAllocationCheckpoint in src/solver.js) is bounded
2418
- // and takes on the order of several seconds, not the indefinite hang
2419
- // it used to depend on ambient heap pressure to avoid (see
2420
- // Solver#reclaimMemory in src/solver.js).
2421
- const result = publicApi.runQuads(program);
2422
- // The current upstream max_integer quads already accept EyeProlog's
2423
- // own bounded=false behavior (no max_integer value, so
2424
- // current_prolog_flag(max_integer, MI) simply fails); a stale, since
2425
- // superseded vendored snapshot once needed a documented divergence
2426
- // for this (see git history), but the current upstream corpus needs
2427
- // none: the whole vendored corpus passes outright.
2428
- assertEqual(result.total, 72, 'quad total');
2429
- assertEqual(result.passed, 72, 'quad passed');
2430
- assertEqual(result.failed, 0, 'quad failed');
2431
- assertEqual(result.stdout, 'quads: 72 run, 72 passed, 0 failed.\n', 'quad report');
2432
- },
2433
- },
2434
2399
  {
2435
2400
  name: 'CLI passes the complete authoritative length quad corpus',
2436
2401
  run: () => {
@@ -70,16 +70,4 @@ ok(adaptive.results[0].batchSize > 1, 'adaptive benchmark smoke test should batc
70
70
  ok(adaptive.results[0].sha256 === manifest.find((item) => item.name === 'dcg-expression').expectedSha256,
71
71
  'adaptive batching should preserve the semantic checksum');
72
72
 
73
- const classicLips = await spawnJson([
74
- path.join(root, 'test', 'lips-benchmark.mjs'),
75
- '--count', '20',
76
- '--runs', '1',
77
- '--warmup', '1',
78
- '--json',
79
- ]);
80
- ok(classicLips.count === 20, 'classic LIPS harness should honor the requested reversal count');
81
- ok(classicLips.methodology.includes('496 calls/reversal'), 'classic LIPS harness should identify the historical accounting');
82
- ok(Number.isFinite(classicLips.lips) && classicLips.lips > 0, 'classic LIPS harness should report positive CPU LIPS');
83
- ok(Number.isFinite(classicLips.netCpuMs) && classicLips.netCpuMs > 0, 'classic LIPS harness should subtract positive CPU time');
84
-
85
73
  process.stdout.write(`Benchmark harness tests: ${passed}/${passed} passed.\n`);
@@ -10366,7 +10366,6 @@ and fast:
10366
10366
  ```sh
10367
10367
  npm run benchmark
10368
10368
  npm run benchmark -- --save .benchmarks/baseline.json
10369
- node test/lips-benchmark.mjs
10370
10369
  ```
10371
10370
 
10372
10371
  The benchmark suite contains 21 representative workloads and stores their
@@ -10378,15 +10377,6 @@ with independent `run()` calls until a batch is roughly 400 ms long. After one
10378
10377
  warm-up batch, five measured batches are reported as milliseconds per workload
10379
10378
  execution. Naturally long workloads keep a batch size of one.
10380
10379
 
10381
- The classic `examples/bench.pl` workload also has a dedicated LIPS harness.
10382
- `node test/lips-benchmark.mjs` follows the 1984 Quintus method more closely than the
10383
- generic runner: `dobench/1` and `dodummy/1` execute failure-driven loops inside
10384
- Prolog, the dummy CPU time is subtracted, and the remaining time is converted
10385
- using 496 procedure calls for one reversal of the 30-element list. Node's
10386
- process CPU clock is used for the primary figure, with wall-clock LIPS printed
10387
- as a cross-check. Because LIPS measures a deliberately small recursive kernel,
10388
- it is useful for engine tuning but is not a complete application benchmark.
10389
-
10390
10380
  The report shows the median, per-operation range, chosen batch size, saved
10391
10381
  baseline median, and the percentage change between the current median and baseline
10392
10382
  median. The range remains visible as context, but it does not suppress or reinterpret
@@ -1,287 +0,0 @@
1
- % p.p.1 member/2
2
-
3
- ?- member(X, [1,2]).
4
- X = 1
5
- ; X = 2.
6
-
7
- ?- member(1, L).
8
- L = [1|_A]
9
- ; L = [_A,1|_B]
10
- ; L = [_A,_B,1|_C]
11
- ; ..., ad_infinitum.
12
-
13
- ?- member(X, [Y,Z|nonlist]).
14
- X = Y
15
- ; X = Z.
16
-
17
- ?- member(X, nonlist).
18
- false.
19
-
20
- ?- member(X, X).
21
- sto, % occurs-check
22
- loops
23
- | sto, % rational trees
24
- X = [X|_A]
25
- ; X = [_A,X|_B]
26
- ; X = [_A,_B,X|_C]
27
- ; ..., ad_infinitum
28
- | sto, % literal substitutions
29
- X = [_A|_B]
30
- ; X = [_A,[_A,[_A|_B]|_C]|_C]
31
- ; X = [_A,_B,[_A,_B,[_A,_B|_C]|_D]|_D]
32
- ; ..., ad_infinitum.
33
-
34
- % p.p.2 append/3
35
-
36
- ?- append([a,b],[c,d], Xs).
37
- Xs = [a,b,c,d].
38
-
39
- ?- append([a], nonlist, Xs).
40
- Xs = [a|nonlist].
41
-
42
- ?- append([a], Ys, Zs).
43
- Zs = [a|Ys].
44
-
45
- ?- append(Xs, Ys, [a,b,c]).
46
- Xs = [], Ys = [a,b,c]
47
- ; Xs = [a], Ys = [b,c]
48
- ; Xs = [a,b], Ys = [c]
49
- ; Xs = [a,b,c], Ys = [].
50
-
51
- % p.p.3 length/2
52
-
53
- ?- length([a,b,c], Length).
54
- Length = 3.
55
-
56
- ?- length(List, 5).
57
- List = [_,_,_,_,_].
58
-
59
- ?- length(List, Length).
60
- List = [], Length = 0
61
- ; List = [_], Length = 1
62
- ; List = [_,_], Length = 2
63
- ; ... . % Ad infinitum.
64
-
65
- % p.p.4 between/3
66
-
67
- ?- between(1, 2, 0).
68
- false.
69
-
70
- ?- between(1, 2, I).
71
- I = 1
72
- ; I = 2.
73
-
74
- ?- between(2, 1, I).
75
- false.
76
-
77
- ?- between(I, I, 0).
78
- instantiation_error.
79
-
80
- ?- between(1, I, 0).
81
- instantiation_error.
82
-
83
- ?- between(I, -1, 0).
84
- instantiation_error.
85
-
86
- ?- between(1, c, 0).
87
- type_error(integer,c).
88
-
89
- ?- between(1+1,2,I).
90
- type_error(integer,1+1).
91
-
92
- % p.p.5 select/3
93
-
94
- ?- select(X, [1,2], Xs).
95
- X = 1, Xs = [2]
96
- ; X = 2, Xs = [1].
97
-
98
- ?- select(X, [Y|nonlist], Xs).
99
- X = Y, Xs = nonlist.
100
-
101
- ?- select(E, Xs, Xs).
102
- sto, % occurs-check
103
- loops
104
- | sto, % rational trees
105
- Xs = [E|Xs]
106
- ; Xs = [_A|_B], _B = [E|_B]
107
- ; ..., ad_infinitum
108
- | sto, % literal substitutions
109
- Xs = [E,E|_A]
110
- ; Xs = [_A,E,E|_B]
111
- ; ..., ad_infinitum.
112
-
113
- % p.p.6 succ/2
114
-
115
- ?- succ(X, S).
116
- instantiation_error.
117
-
118
- ?- succ(X, X).
119
- instantiation_error.
120
-
121
- ?- succ(0, S).
122
- S = 1.
123
-
124
- ?- succ(1, 1+1).
125
- type_error(integer, 1+1).
126
-
127
- ?- succ(X, 0).
128
- false.
129
-
130
- ?- succ(-1, S).
131
- domain_error(not_less_than_zero, -1).
132
-
133
- ?- current_prolog_flag(max_integer, MI), succ(MI, 0).
134
- false.
135
-
136
- ?- current_prolog_flag(max_integer, MI), succ(MI, 1).
137
- false.
138
-
139
- ?- current_prolog_flag(max_integer, MI), succ(MI, MI).
140
- false.
141
-
142
- ?- current_prolog_flag(max_integer, MI), succ(MI, S).
143
- false
144
- | evaluation_error(int_overflow)
145
- | representation_error(max_integer).
146
-
147
- % p.p.7
148
-
149
- ?- maplist(>(3), [1, 2]).
150
- true.
151
-
152
- ?- maplist(>(3), [1, 2, 3]).
153
- false.
154
-
155
- ?- maplist(=(X), Xs).
156
- Xs = []
157
- ; Xs = [X]
158
- ; Xs = [X, X]
159
- ; Xs = [X, X, X]
160
- ; ... . % Ad infinitum.
161
-
162
- % p.p.8
163
-
164
- ?- nth0(1, [a,b,c], E).
165
- E = b.
166
-
167
- ?- nth0(N, [a,b,c], E).
168
- N = 0, E = a
169
- ; N = 1, E = b
170
- ; N = 2, E = c.
171
-
172
- ?- nth0(0, [A,B|non_list], E).
173
- A = E.
174
-
175
- ?- nth0(2, Es, E).
176
- Es = [_A,_B,E|_C].
177
-
178
- ?- nth0(N, Es, E).
179
- N = 0, Es = [E|_A]
180
- ; N = 1, Es = [_A,E|_B]
181
- ; N = 2, Es = [_A,_B,E|_C]
182
- ; N = 3, Es = [_A,_B,_C,E|_D]
183
- ; ..., ad_infinitum.
184
-
185
- ?- nth0(non_integer, Es, E).
186
- type_error(integer, non_integer).
187
-
188
- ?- nth0(-1, Es, E).
189
- domain_error(not_less_than_zero, -1).
190
-
191
- ?- nth0(N, [[]|Es], Es).
192
- N = 0, Es = []
193
- ; sto, % occurs-check
194
- loops
195
- | N = 0, Es = []
196
- ; sto, % rational trees
197
- N = 1, Es = [Es|_A]
198
- ; N = 2, Es = [_A,Es|_B]
199
- ; ..., ad_infinitum.
200
-
201
- ?- nth1(0, Es, E).
202
- false.
203
-
204
- % p.p.9
205
-
206
- ?- call_nth(true, Nth).
207
- Nth = 1.
208
-
209
- ?- call_nth(false, Nth).
210
- false.
211
-
212
- ?- call_nth(repeat, Nth).
213
- Nth = 1
214
- ; Nth = 2
215
- ; Nth = 3
216
- ; Nth = 4
217
- ; Nth = 5
218
- ; ... .
219
-
220
- ?- call_nth(( N = 1 ; N = 2 ), Nth).
221
- N = 1, Nth = 1
222
- ; N = 2, Nth = 2.
223
-
224
- ?- call_nth(true, non_integer).
225
- type_error(integer,non_integer).
226
-
227
- ?- call_nth(true, 1.0).
228
- type_error(integer,1.0).
229
-
230
- ?- call_nth(true, 0).
231
- false.
232
-
233
- ?- call_nth(repeat, 0).
234
- false.
235
-
236
- ?- call_nth(repeat, -1).
237
- domain_error(not_less_than_zero,-1).
238
-
239
- ?- call_nth(length(L,N), 3).
240
- L = [_A,_B], N = 2.
241
-
242
- ?- call_nth(inex, 0).
243
- false. % thus not existence_error(procedure,inex/0)
244
-
245
- ?- call_nth(inex, 0).
246
- existence_error(procedure,...), unexpected.
247
-
248
- ?- call_nth(1, 0).
249
- false.
250
-
251
- ?- call_nth(V, 0).
252
- false.
253
-
254
- ?- call_nth(N = 1, N).
255
- N = 1.
256
-
257
- ?- call_nth(N = -1, N).
258
- false.
259
-
260
- ?- call_nth(repeat,1+1).
261
- type_error(integer,1+1).
262
-
263
- % p.p.10
264
-
265
- ?- foldl(append, [[1,2],[3],[4,5]], [],Xs).
266
- Xs = [4,5,3,1,2].
267
-
268
- % p.p.11
269
-
270
- ?- countall((X=1;X=2), N).
271
- N = 2.
272
- ?- countall((true;true), N).
273
- N = 2.
274
- ?- countall(G_0, N).
275
- instantiation_error.
276
- ?- countall((length(L,5),nth0(_,L,_),nth0(_,L,_)), N).
277
- N = 25.
278
- ?- countall(N = 1, N).
279
- N = 1.
280
- ?- countall(N = non_integer, N).
281
- N = 1.
282
- ?- countall(false, 1).
283
- false.
284
- ?- countall(false, -1).
285
- domain_error(not_less_than_zero,-1).
286
- ?- countall(false, non_integer).
287
- type_error(integer,non_integer).
@@ -1,2 +0,0 @@
1
- :- use_module(library(prologue)).
2
- :- include('prologue_quad.pl').
@@ -1,93 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import process from 'node:process';
4
- import { performance } from 'node:perf_hooks';
5
- import { fileURLToPath } from 'node:url';
6
- import { Program, parseGoalText, run } from '../index.js';
7
-
8
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
9
-
10
- function integer(value, name, minimum) {
11
- const n = Number(value);
12
- if (!Number.isInteger(n) || n < minimum) throw new Error(`${name} must be an integer >= ${minimum}`);
13
- return n;
14
- }
15
-
16
- const options = { count: 50, runs: 5, warmup: 2, json: false };
17
- for (let i = 2; i < process.argv.length; i++) {
18
- const arg = process.argv[i];
19
- if (arg === '--count') options.count = integer(process.argv[++i], '--count', 1);
20
- else if (arg === '--runs') options.runs = integer(process.argv[++i], '--runs', 1);
21
- else if (arg === '--warmup') options.warmup = integer(process.argv[++i], '--warmup', 0);
22
- else if (arg === '--json') options.json = true;
23
- else if (arg === '-h' || arg === '--help') {
24
- process.stdout.write('Usage: node test/lips-benchmark.mjs [--count N] [--runs N] [--warmup N] [--json]\n');
25
- process.exit(0);
26
- } else throw new Error(`unknown option: ${arg}`);
27
- }
28
-
29
- const source = await fs.readFile(path.join(root, 'examples', 'bench.pl'), 'utf8');
30
- const program = Program.parse(source);
31
- const dobench = parseGoalText(`dobench(${options.count})`);
32
- const dodummy = parseGoalText(`dodummy(${options.count})`);
33
-
34
- function execute(goal) {
35
- const cpuStart = process.cpuUsage();
36
- const wallStart = performance.now();
37
- const result = run(program, { goals: [goal] });
38
- const wallMs = performance.now() - wallStart;
39
- const cpu = process.cpuUsage(cpuStart);
40
- if (result.haltCode != null) throw new Error(`benchmark halted with code ${result.haltCode}`);
41
- if (!result.stdout) throw new Error('benchmark goal produced no success answer');
42
- return { cpuMs: (cpu.user + cpu.system) / 1000, wallMs };
43
- }
44
-
45
- for (let i = 0; i < options.warmup; i++) {
46
- execute(dodummy);
47
- execute(dobench);
48
- }
49
-
50
- const samples = [];
51
- for (let i = 0; i < options.runs; i++) {
52
- const dummy = execute(dodummy);
53
- const bench = execute(dobench);
54
- const netCpuMs = bench.cpuMs - dummy.cpuMs;
55
- const netWallMs = bench.wallMs - dummy.wallMs;
56
- if (netCpuMs <= 0) throw new Error(`non-positive control-subtracted CPU time (${netCpuMs.toFixed(3)} ms); increase --count`);
57
- samples.push({
58
- dummyCpuMs: dummy.cpuMs,
59
- benchCpuMs: bench.cpuMs,
60
- netCpuMs,
61
- netWallMs,
62
- lips: 496 * options.count * 1000 / netCpuMs,
63
- wallLips: netWallMs > 0 ? 496 * options.count * 1000 / netWallMs : null,
64
- });
65
- }
66
-
67
- function median(values) {
68
- const sorted = [...values].sort((a, b) => a - b);
69
- const middle = Math.floor(sorted.length / 2);
70
- return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
71
- }
72
-
73
- const result = {
74
- methodology: 'Quintus 1984 naive reverse; dobench minus dodummy; 496 calls/reversal',
75
- count: options.count,
76
- runs: options.runs,
77
- warmup: options.warmup,
78
- lips: median(samples.map((sample) => sample.lips)),
79
- wallLips: median(samples.map((sample) => sample.wallLips).filter(Number.isFinite)),
80
- netCpuMs: median(samples.map((sample) => sample.netCpuMs)),
81
- samples,
82
- };
83
-
84
- if (options.json) {
85
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
86
- } else {
87
- process.stdout.write(`EyeProlog classic nrev: ${Math.round(result.lips).toLocaleString('en-US')} LIPS\n`);
88
- process.stdout.write(` ${options.count} reversals/run, ${options.runs} measured runs, ${options.warmup} warmups\n`);
89
- process.stdout.write(` median control-subtracted CPU: ${result.netCpuMs.toFixed(1)} ms\n`);
90
- if (Number.isFinite(result.wallLips)) {
91
- process.stdout.write(` wall-clock cross-check: ${Math.round(result.wallLips).toLocaleString('en-US')} LIPS\n`);
92
- }
93
- }