diff --git a/research/cyclic-zero-elimination/CLAIM.md b/research/cyclic-zero-elimination/CLAIM.md new file mode 100644 index 0000000000..3e68361d70 --- /dev/null +++ b/research/cyclic-zero-elimination/CLAIM.md @@ -0,0 +1,42 @@ +# Sprint claim + +- Track: verification engineering. OS-random assignment seed: 94. +- Base: `66849e1475c930fd7732b1a0067ffee7e1e95f60`. +- Object: PR621's standard-library prime-power cyclic-inverse verifier, + specifically zero-coefficient row updates in exact cyclotomic elimination. +- Method: compare the original and a zero-skipping implementation on the same + eight shipped cases, then exercise singular and pivot-swap controls. Seed 94 + selects the additional right-hand sides `(zeta_q^94, 94)` at q=3,4,5. +- Deliverable: one scoped tooling improvement, exact receipt comparisons, + same-input timings, and a reproducible command. No new mathematical bound. +- Expected finish: 2026-10-13 05:26 UTC, 10:56 IST. +- Coordination: no pinned issue existed when checked. Open PRs through #720 + were inspected. #668's modular CRT resultants extend coverage to larger + orders; this task accelerates the existing exact elimination and retains + its Bezout solve. #662/#667/#672/#707/#709 concern other kernels. +- Candidates considered: selected-source hashing, independent owner-check + arithmetic, and cyclotomic elimination. Hashing was already short, and owner + arithmetic overlapped pending work. Seed 94 directs this run to the older + exact verifier rather than another selected-frontier kernel. + +Prepared by thakrarsagar with substantial OpenAI Codex assistance. + +## Result update + +Submitted [PR736](https://github.com/CrocSwap/integer-mult-bounds/pull/736). +The source-pinned adapter preserves all eight cases and their negative controls. +All 28 measured full replays match the predecessor receipt. Seven alternating +pairs per interpreter measured 24.6% and 26.2% lower child CPU medians on local +Python 3.13 and 3.14. Wall medians improved with more variability, including one +slower pair. The [README](README.md) retains exact scope and all timing evidence. + +The 36 focused matrix/mode checks and corruption controls pass, including +optimized Python. Isolated optimized replays from `/tmp` match on Python 3.11 +and 3.14. The selected-record check passes. The original source and `upstream/` +remain unchanged. Full repository, cold frontier, and SymPy checks were not +rerun. A separate Codex agent reviewed the guard and controls. + +Before submission, the PR queue was refreshed through #732 and relevant heads +were checked separately. PR668 remains a distinct CRT coverage extension; +PR727 optimizes a different owner checker. No overlapping zero-row adapter was +found, and no pinned coordination issue was available. diff --git a/research/cyclic-zero-elimination/README.md b/research/cyclic-zero-elimination/README.md new file mode 100644 index 0000000000..8930b3f063 --- /dev/null +++ b/research/cyclic-zero-elimination/README.md @@ -0,0 +1,128 @@ +# Skip zero row updates in the exact cyclic-inverse replay + +This optional adapter speeds up Ali Romman's incorporated PR621 verifier by +skipping elimination updates whose coefficient is exactly zero. The full +eight-case output remains byte-identical to the predecessor's `EXPECTED.json`. +This is a tooling improvement with no new kappa or mathematical construction. + +The only arithmetic change is in `eliminate`, immediately after reading the +coefficient for a non-pivot row: + +```python +value = a[row][col] +if value == k.zero: + continue +``` + +The original implementation and all its callers remain unchanged on disk. +`replay.py` checks the predecessor's SHA256 and exactly one insertion site, +then applies these two statements in memory before running the original CLI. +The historical review binds the original source bytes, so editing that package +would invalidate its provenance. Default repository verification is unchanged. + +## Reproduce + +Use Python 3.11 or later and the standard library. The benchmark uses POSIX +child CPU accounting and supports macOS and Linux. + +```sh +python3 -I -B research/cyclic-zero-elimination/verify.py --output /tmp/cyclic-zero-result.json +``` + +Use a fresh output path. Expected terminal result: +`PASS_CYCLIC_ZERO_ELIMINATION`. The JSON records all fourteen full replay +timings, source hashes, interpreter, platform, and control results. Every +replay must match the pinned predecessor receipt before its timing is accepted. +There is no machine-dependent speed threshold in the correctness checks. + +To run just the faster replay, including all inherited checks: + +```sh +python3 -I -B research/cyclic-zero-elimination/replay.py --output /tmp/cyclic-fast.json +``` + +The output must match `research/prime-power-cyclic-inverse/EXPECTED.json` +byte for byte. Both entrypoints retain their checks under `-O`. + +## Why the guard preserves the check + +`Field` represents each element as a fixed-length tuple of exact rational +coefficients reduced modulo the cyclotomic polynomial. Equality with `k.zero` +is exact. For a zero elimination coefficient, every omitted operation has +the form `a[row][j] = a[row][j] - 0 * a[col][j]`. If a boundary is present, +its omitted update is `b[row] = b[row] - 0 * b[col]`. Each leaves its value +unchanged. The skipped block contains no validation or inversion. + +The guard leaves pivot search, row swaps and their determinant sign, pivot +inversion, nonzero elimination, singular detection, and solution checks in +place. Both original callers run: the Sylvester system with its Bezout +boundary and the singular unit-root control without a boundary. + +No input cases or series terms are removed. All eight shipped cases still +check tail products, integral coefficients, residue identities, determinant, +Bezout identity, norm, and conjugate-reversal squared moduli. Each retains its +unit-root and missing-conjugation negative controls. These are finite checks, +not a formal proof of the all-prime-power theorem or integer multiplication. + +## Evidence + +Seven pairs run sequentially in alternating order. Each member is a fresh +isolated Python process on the same eight inputs. The guarded timing includes +loading, hashing, and compiling the adapter. Within each recorded series, +no priming run or timing sample is discarded. Times include the entire process, +not just elimination. + +| Local interpreter | Reference wall median | Guarded wall median | Reference CPU median | Guarded CPU median | +| --- | ---: | ---: | ---: | ---: | +| CPython 3.13.16 | 3.709 s | 3.156 s | 3.149 s | 2.374 s | +| CPython 3.14.8 | 4.205 s | 2.649 s | 3.212 s | 2.371 s | + +Both runs were on macOS 26.5.2, arm64. Child CPU medians fell by 24.6% and +26.2%, respectively. Wall time is noisier: one Python 3.14 pair was slower +with the guard. No universal speedup or complete frontier replay speedup is +claimed. Raw samples are retained in [RESULT-python313.json](RESULT-python313.json) +and [RESULT-python314.json](RESULT-python314.json). + +Before these series, a baseline-only smoke run took 2.7844 seconds. A separate +feasibility pair using `python -c` measured 3.2008 seconds before the guard and +2.8955 seconds after it, with identical receipts. Those preliminary runs use +different launch conditions and are not included in the table's medians. + +The focused checks cover 36 matrix/mode combinations at orders 3, 4, and 5. +They include sparse, dense, row-swapped, and singular matrices, non-rational +entries, both boundary modes, direct determinant formulas, direct substitution +of solutions, and input preservation. Seed 94 selects the extra boundary +`(zeta_q^94, 94)`. The identity solve uses 12 field multiplications before the +guard and 7 after it, so disabling the guard fails the work-reduction check. +Changing the source bytes is rejected. Inverting the guard or skipping all +rows makes the inherited identity check fail. + +All 28 measured full replays matched the expected receipt. Additional isolated, +optimized runs from `/tmp` matched on Python 3.11 and 3.14. A separate Codex +agent reviewed the change and ran the 36 focused checks under optimized Python. +This is AI-assisted review, not independent human review. + +The original source, expected receipt, selected frontier, and `upstream/` are +unchanged. The full `make verify`, cold frontier replay, and optional SymPy +engine were not rerun for this adapter. The affected standard-library replay +was run in full; no production integration is claimed. + +## Pins, attribution, and scope + +Base commit: `66849e1475c930fd7732b1a0067ffee7e1e95f60`. +[SOURCE.json](SOURCE.json) pins the PR621 source and exact receipt. The original +package's [notice](../prime-power-cyclic-inverse/NOTICE.md) and +[source record](../prime-power-cyclic-inverse/SOURCE.json) retain its kernel, +proof, and classical-source attribution. The algebra and verifier are Ali +Romman's predecessor work. The only proposed arithmetic change is the guard. + +Scott Woolsey Biggart's pending PR668 uses CRT and a different implementation +to extend resultant coverage through order 27. It is related work, not a +dependency or duplicated search. This contribution keeps the predecessor's +eight-case domain and rational-field method. It does not settle efficient +preparation, rounding, restoration, fixed-tape implementation, or the inherited +analytic interfaces. + +Adapter and experiment contributed by [thakrarsagar](https://github.com/thakrarsagar) +with substantial OpenAI Codex assistance, including a separate AI review. +Licensed under the repository's Apache-2.0 license. diff --git a/research/cyclic-zero-elimination/RESULT-python313.json b/research/cyclic-zero-elimination/RESULT-python313.json new file mode 100644 index 0000000000..7ff10655fd --- /dev/null +++ b/research/cyclic-zero-elimination/RESULT-python313.json @@ -0,0 +1,143 @@ +{ + "status": "PASS_CYCLIC_ZERO_ELIMINATION", + "assignment_seed": 94, + "source_pins": { + "verify.py": "40e5e18cbd98d39ba6d0bb036778ccacc077dff009099a54b9c8f441af5d6a56", + "EXPECTED.json": "2c4ab855129670053f4f39ee14269cc9b5bda015eab71d5a226dd0d395740ebd" + }, + "controls": { + "matrix_checks": 36, + "source_mutations_rejected": 1, + "guard_mutations_rejected": 2, + "identity_matrix_multiplications": { + "reference": 12, + "guarded": 7 + } + }, + "python": "3.13.16 (main, Sep 30 2026, 18:57:20) [Clang 21.0.0 (clang-2100.1.1.101)]", + "platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O", + "optimized": false, + "expected_sha256": "2c4ab855129670053f4f39ee14269cc9b5bda015eab71d5a226dd0d395740ebd", + "cases_per_replay": 8, + "samples": [ + { + "pair": 1, + "order": [ + "reference", + "guarded" + ], + "reference": { + "wall_seconds": 2.5722171250381507, + "cpu_seconds": 2.497716 + }, + "guarded": { + "wall_seconds": 2.154557666974142, + "cpu_seconds": 2.0441480000000003 + } + }, + { + "pair": 2, + "order": [ + "guarded", + "reference" + ], + "guarded": { + "wall_seconds": 3.156417792022694, + "cpu_seconds": 2.3740729999999997 + }, + "reference": { + "wall_seconds": 4.490490208030678, + "cpu_seconds": 3.2731140000000014 + } + }, + { + "pair": 3, + "order": [ + "reference", + "guarded" + ], + "reference": { + "wall_seconds": 3.566854833974503, + "cpu_seconds": 2.883663999999998 + }, + "guarded": { + "wall_seconds": 2.452533375006169, + "cpu_seconds": 2.161766000000001 + } + }, + { + "pair": 4, + "order": [ + "guarded", + "reference" + ], + "guarded": { + "wall_seconds": 3.2455744999460876, + "cpu_seconds": 2.589204999999998 + }, + "reference": { + "wall_seconds": 3.6067326249904, + "cpu_seconds": 3.031494000000002 + } + }, + { + "pair": 5, + "order": [ + "reference", + "guarded" + ], + "reference": { + "wall_seconds": 3.7094254999537952, + "cpu_seconds": 3.148820999999999 + }, + "guarded": { + "wall_seconds": 2.802376125007868, + "cpu_seconds": 2.3624490000000002 + } + }, + { + "pair": 6, + "order": [ + "guarded", + "reference" + ], + "guarded": { + "wall_seconds": 3.719457249972038, + "cpu_seconds": 2.5570999999999984 + }, + "reference": { + "wall_seconds": 4.3459909170051105, + "cpu_seconds": 3.267953999999999 + } + }, + { + "pair": 7, + "order": [ + "reference", + "guarded" + ], + "reference": { + "wall_seconds": 4.210881583043374, + "cpu_seconds": 3.2588989999999978 + }, + "guarded": { + "wall_seconds": 3.225713125022594, + "cpu_seconds": 2.4038690000000016 + } + } + ], + "medians": { + "reference": { + "wall_seconds": 3.7094254999537952, + "cpu_seconds": 3.148820999999999 + }, + "guarded": { + "wall_seconds": 3.156417792022694, + "cpu_seconds": 2.3740729999999997 + } + }, + "speedup": { + "wall_seconds": 1.1752010489006663, + "cpu_seconds": 1.3263370587172338 + } +} diff --git a/research/cyclic-zero-elimination/RESULT-python314.json b/research/cyclic-zero-elimination/RESULT-python314.json new file mode 100644 index 0000000000..837549f35c --- /dev/null +++ b/research/cyclic-zero-elimination/RESULT-python314.json @@ -0,0 +1,143 @@ +{ + "status": "PASS_CYCLIC_ZERO_ELIMINATION", + "assignment_seed": 94, + "source_pins": { + "verify.py": "40e5e18cbd98d39ba6d0bb036778ccacc077dff009099a54b9c8f441af5d6a56", + "EXPECTED.json": "2c4ab855129670053f4f39ee14269cc9b5bda015eab71d5a226dd0d395740ebd" + }, + "controls": { + "matrix_checks": 36, + "source_mutations_rejected": 1, + "guard_mutations_rejected": 2, + "identity_matrix_multiplications": { + "reference": 12, + "guarded": 7 + } + }, + "python": "3.14.8 (main, Sep 30 2026, 17:55:09) [Clang 21.0.0 (clang-2100.1.1.101)]", + "platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O", + "optimized": false, + "expected_sha256": "2c4ab855129670053f4f39ee14269cc9b5bda015eab71d5a226dd0d395740ebd", + "cases_per_replay": 8, + "samples": [ + { + "pair": 1, + "order": [ + "reference", + "guarded" + ], + "reference": { + "wall_seconds": 4.204747915966436, + "cpu_seconds": 3.2121450000000005 + }, + "guarded": { + "wall_seconds": 3.5135904169874266, + "cpu_seconds": 2.674842 + } + }, + { + "pair": 2, + "order": [ + "guarded", + "reference" + ], + "guarded": { + "wall_seconds": 2.8346277920063585, + "cpu_seconds": 2.424727000000001 + }, + "reference": { + "wall_seconds": 3.9777265830198303, + "cpu_seconds": 3.181296999999999 + } + }, + { + "pair": 3, + "order": [ + "reference", + "guarded" + ], + "reference": { + "wall_seconds": 4.20667666598456, + "cpu_seconds": 3.2498050000000003 + }, + "guarded": { + "wall_seconds": 2.0614804169745184, + "cpu_seconds": 2.0468710000000017 + } + }, + { + "pair": 4, + "order": [ + "guarded", + "reference" + ], + "guarded": { + "wall_seconds": 5.252856166975107, + "cpu_seconds": 2.5806690000000008 + }, + "reference": { + "wall_seconds": 4.627032250049524, + "cpu_seconds": 3.5539669999999974 + } + }, + { + "pair": 5, + "order": [ + "reference", + "guarded" + ], + "reference": { + "wall_seconds": 5.454746083007194, + "cpu_seconds": 3.4818090000000006 + }, + "guarded": { + "wall_seconds": 2.6490648330072872, + "cpu_seconds": 2.371294000000004 + } + }, + { + "pair": 6, + "order": [ + "guarded", + "reference" + ], + "guarded": { + "wall_seconds": 2.5394550409982912, + "cpu_seconds": 2.299143999999999 + }, + "reference": { + "wall_seconds": 2.9724037920241244, + "cpu_seconds": 2.8052769999999976 + } + }, + { + "pair": 7, + "order": [ + "reference", + "guarded" + ], + "reference": { + "wall_seconds": 3.0204936250229366, + "cpu_seconds": 2.8682600000000074 + }, + "guarded": { + "wall_seconds": 2.3432646250003017, + "cpu_seconds": 2.242236999999998 + } + } + ], + "medians": { + "reference": { + "wall_seconds": 4.204747915966436, + "cpu_seconds": 3.2121450000000005 + }, + "guarded": { + "wall_seconds": 2.6490648330072872, + "cpu_seconds": 2.371294000000004 + } + }, + "speedup": { + "wall_seconds": 1.5872574591513855, + "cpu_seconds": 1.354595845137716 + } +} diff --git a/research/cyclic-zero-elimination/SOURCE.json b/research/cyclic-zero-elimination/SOURCE.json new file mode 100644 index 0000000000..e89f96309b --- /dev/null +++ b/research/cyclic-zero-elimination/SOURCE.json @@ -0,0 +1,16 @@ +{ + "base_commit": "66849e1475c930fd7732b1a0067ffee7e1e95f60", + "predecessor_pr": 621, + "predecessor_head": "520774462b19b29af41554b74def31f9d01e997e", + "author": "Ali Romman", + "files": { + "verify.py": "40e5e18cbd98d39ba6d0bb036778ccacc077dff009099a54b9c8f441af5d6a56", + "EXPECTED.json": "2c4ab855129670053f4f39ee14269cc9b5bda015eab71d5a226dd0d395740ebd" + }, + "related_work": { + "pr": 668, + "author": "Scott Woolsey Biggart", + "scope": "Independent CRT resultants extend the finite domain. This adapter has no dependency on that pending work and retains the original rational-field solve." + }, + "license": "Apache-2.0" +} diff --git a/research/cyclic-zero-elimination/replay.py b/research/cyclic-zero-elimination/replay.py new file mode 100644 index 0000000000..9c1e67fd43 --- /dev/null +++ b/research/cyclic-zero-elimination/replay.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +"""Run PR621's exact checks with zero-coefficient row updates omitted. + +Ali Romman's original verifier remains byte-pinned and unchanged. Adapter by +thakrarsagar with substantial OpenAI Codex assistance. Apache-2.0. +""" +import hashlib +import json +from pathlib import Path + +PACKAGE = Path(__file__).resolve().parent +SOURCE = PACKAGE.parent / 'prime-power-cyclic-inverse' +PINS = json.loads((PACKAGE / 'SOURCE.json').read_text())['files'] +ANCHOR = ' value = a[row][col]\n for j' +GUARD = ' value = a[row][col]\n if value == k.zero:\n continue\n for j' + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def prepare(data, fast): + require(hashlib.sha256(data).hexdigest() == PINS['verify.py'], + 'Predecessor verifier source changed') + text = data.decode('utf-8') + require(text.count(ANCHOR) == 1, 'Expected exactly one elimination anchor') + return text.replace(ANCHOR, GUARD) if fast else text + + +def load(fast): + namespace = {'__name__': 'cyclic_verifier', '__file__': str(SOURCE / 'verify.py')} + exec(compile(prepare((SOURCE / 'verify.py').read_bytes(), fast), + str(SOURCE / 'verify.py'), 'exec'), namespace) + return namespace + + +if __name__ == '__main__': + code = prepare((SOURCE / 'verify.py').read_bytes(), True) + exec(compile(code, str(SOURCE / 'verify.py'), 'exec'), + {'__name__': '__main__', '__file__': str(SOURCE / 'verify.py')}) diff --git a/research/cyclic-zero-elimination/verify.py b/research/cyclic-zero-elimination/verify.py new file mode 100644 index 0000000000..b56bafc487 --- /dev/null +++ b/research/cyclic-zero-elimination/verify.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Check the zero-row guard and benchmark seven alternating full replay pairs.""" +import argparse +import copy +import hashlib +import importlib.util +import json +from pathlib import Path +import platform +import resource +import statistics +import subprocess +import sys +import time + +PACKAGE = Path(__file__).resolve().parent +spec = importlib.util.spec_from_file_location('cyclic_adapter', PACKAGE / 'replay.py') +adapter = importlib.util.module_from_spec(spec) +spec.loader.exec_module(adapter) +require = adapter.require + + +def rejected(call, message): + try: + call() + except ValueError: + return + raise ValueError(message) + + +def controls(): + original, fast = adapter.load(False), adapter.load(True) + checks = 0 + for radix in (3, 4, 5): + k = original['Field'](radix) + z, o, u = k.zero, k.one, k.root(1) + # Exact determinants cover sparse, swapped, dense, and singular inputs. + fixtures = [([[o, z], [z, o]], o), + ([[z, o], [o, z]], k.scale(o, -1)), + ([[u, o], [o, u]], k.sub(k.mul(u, u), o)), + ([[o, u], [z, o]], o), + ([[o, u], [o, u]], z), + ([[z, z], [z, z]], z)] + for matrix, determinant in fixtures: + for boundary in (None, [k.root(94), k.scale(o, 94)]): + before = copy.deepcopy((matrix, boundary)) + expected = original['eliminate'](k, matrix, boundary) + result = fast['eliminate'](k, matrix, boundary) + require(result == expected, 'Elimination differs from reference') + require((matrix, boundary) == before, 'Elimination mutated its input') + require(result[0] == determinant, 'Wrong independently computed determinant') + if determinant == z or boundary is None: + require(result[1] is None, 'Unexpected solution') + else: + for row, target in zip(matrix, boundary): + actual = z + for value, solution in zip(row, result[1]): + actual = k.add(actual, k.mul(value, solution)) + require(actual == target, 'Solution does not satisfy the input system') + checks += 1 + + def work(namespace): + k = namespace['Field'](3) + multiply = k.mul + calls = 0 + + def counted(a, b): + nonlocal calls + calls += 1 + return multiply(a, b) + + k.mul = counted + namespace['eliminate'](k, [[k.one, k.zero], [k.zero, k.one]], [k.one, k.one]) + return calls + + before, after = work(original), work(fast) + require(after < before, 'Zero-row guard removed no arithmetic') + source = (adapter.SOURCE / 'verify.py').read_bytes() + rejected(lambda: adapter.prepare(source + b'\n', True), 'Changed source was admitted') + # These faults skip necessary nonzero work, so an inherited identity must fail. + guarded = adapter.prepare(source, True) + for guard in ('if value != k.zero:', 'if True:'): + namespace = {'__name__': 'mutated_cyclic_verifier'} + exec(compile(guarded.replace('if value == k.zero:', guard), '', 'exec'), namespace) + rejected(lambda: namespace['case'](3, 1), 'Incorrect row-skipping guard accepted') + return {'matrix_checks': checks, 'source_mutations_rejected': 1, + 'guard_mutations_rejected': 2, + 'identity_matrix_multiplications': {'reference': before, 'guarded': after}} + + +def replay(path, expected): + command = [sys.executable, '-I', '-B'] + if sys.flags.optimize: + command.append('-O') + usage = resource.getrusage(resource.RUSAGE_CHILDREN) + start = time.perf_counter() + result = subprocess.run(command + [str(path)], capture_output=True, check=True) + wall = time.perf_counter() - start + end_usage = resource.getrusage(resource.RUSAGE_CHILDREN) + require(result.stdout == expected, 'Full replay differs from pinned EXPECTED.json') + require(not result.stderr, 'Unexpected replay diagnostics') + return {'wall_seconds': wall, + 'cpu_seconds': end_usage.ru_utime + end_usage.ru_stime - usage.ru_utime - usage.ru_stime} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', type=Path, help='fresh JSON evidence path') + args = parser.parse_args() + if args.output is not None: + require(not args.output.exists(), 'Use a fresh evidence path') + expected = (adapter.SOURCE / 'EXPECTED.json').read_bytes() + digest = hashlib.sha256(expected).hexdigest() + require(digest == adapter.PINS['EXPECTED.json'], 'Predecessor receipt changed') + evidence = {'status': 'PASS_CYCLIC_ZERO_ELIMINATION', 'assignment_seed': 94, + 'source_pins': adapter.PINS, 'controls': controls(), + 'python': sys.version, 'platform': platform.platform(), + 'optimized': bool(sys.flags.optimize), 'expected_sha256': digest, + 'cases_per_replay': len(json.loads(expected)['cases']), + 'samples': []} + paths = {'reference': adapter.SOURCE / 'verify.py', 'guarded': PACKAGE / 'replay.py'} + for pair in range(7): + order = ('reference', 'guarded') if pair % 2 == 0 else ('guarded', 'reference') + sample = {'pair': pair + 1, 'order': list(order)} + for label in order: + sample[label] = replay(paths[label], expected) + evidence['samples'].append(sample) + evidence['medians'] = { + label: {metric: statistics.median(s[label][metric] for s in evidence['samples']) + for metric in ('wall_seconds', 'cpu_seconds')} + for label in paths} + evidence['speedup'] = { + metric: evidence['medians']['reference'][metric] / evidence['medians']['guarded'][metric] + for metric in ('wall_seconds', 'cpu_seconds')} + payload = json.dumps(evidence, indent=2) + '\n' + if args.output is None: + print(payload, end='') + else: + with args.output.open('x') as stream: + stream.write(payload) + print(evidence['status']) + + +if __name__ == '__main__': + main()