Benchmark: Compare quadratic entry precision and arithmetic cost

Generate plain long-double and experimental FMA variants from the production entry kernel. Compare exact-rational references, geometric validation, fallback counts and repeated timings with self-contained fixtures.

Validate cached build dependencies and reject stale timing output. Count all unconfirmed entry outcomes independently of reference classification.
This commit is contained in:
wyj committed 2026-10-09 00:15:02 -04:00
1 parent 789549ed2f
commit 7c980c35aa
6 files changed
+2805

No files matched your search

+232
View File
@@ -0,0 +1,232 @@
# quadratic_precision — stable entry quadratic: precision vs. cost
Self-contained, reproducible benchmark comparing four arithmetic realisations of
the **same** asymptotic-entry algebra. Nothing here modifies production code,
the `Makefile`, or git state.
## Question
The production camera pre-route solves the relative-distance quadratic
```
F(s) = |d + q s|^2 - (R0 - rr s)^2 = a s^2 + b s + c
d = x_cur - c_frame, q = w_frame + v_frame
```
in `long double`, with a stable root formula, an ordinary discriminant
`b*b - 4*a*c` and entry slope, a common power-of-two scaling, an
uncertainty band, geometric validation and a numerical fallback. Which matters
for accuracy and which for speed?
Four variants share the *entire* rest of the module (scaling, uncertainty band,
validation, fallback, dispatch) and are generated from the current
`src/asymptotic.c` by rewriting exactly one lexical region:
| variant | coefficients | type | discriminant / slope |
|---------------------|--------------|-------------|----------------------|
| `ld_plain` | as production| `long double` | plain `b*b - 4*a*c`, plain slope |
| `ld_fma` | as production| `long double` | compensated `fmal` (experimental arm) |
| `double_fma` | ordinary `+=`| `double` | compensated `fma` |
| `double_fma_coeff` | `fma` dots, `fma` products | `double` | compensated `fma` |
`double_fma` is the control that isolates *type* precision from *FMA*; the two
double variants isolate *coefficient accumulation*.
## Generation and build
`build.py` locates the kernel region between two lexical anchors in the current
working-tree `src/asymptotic.c`:
* start: the comment `/* Long-double coefficients of the relative-distance
quadratic`
* end: the forward declaration `static void minkowski_route_entry(...)`
It asserts each anchor and the presence of `entry_quadratic_coeffs`,
`entry_solve`, `entry_discriminant` exactly once, then emits four full-module
copies under `<output>/generated/`. Every other line (dispatch, fallback,
`asymptotic_route_camera`, Schwarzschild path, …) is copied verbatim. If a
future edit moves an anchor or changes the region, generation **fails** rather
than silently measuring the wrong code.
`ld_plain` retains the production kernel verbatim; `ld_fma` rewrites only the
discriminant body and the entry slope. Double
variants replace `long double`→`double`, `fmal`/`fabsl`/`fmaxl`/`frexpl`/
`scalbnl`/`sqrtl`/`copysignl`→their double forms, `LDBL_*`→`DBL_*`, and strip
`L` suffixes from literals. A guard rejects any remaining `long double`
promotion, `L` literal or `*l`/`fmal` call in the double kernel section, so no
double expression can be re-promoted to `long double`. `double_fma_coeff`
additionally changes the coefficient accumulation to `fma`.
The probe `#include`s the generated module, so the private static
`entry_quadratic_coeffs`/`entry_solve` and the public `asymptotic_route_camera`
that are exercised are the **actual generated code**, not an independent copy.
Compile flags, identical for all four variants:
```
-std=c11 -march=native -O2 -DNDEBUG -ffp-contract=off -fopenmp
```
`-ffp-contract=off` prevents implicit FMA contraction from silently changing
the `ld_plain` arm; explicit `fma`/`fmal` still lower as written.
The probe links a minimal production subset (`geodesic.c`,
`asymptotic_entry.c`, `asymptotic_schwarzschild.c`, `spacetime_common.c`,
`spacetime_minkowski.c`) plus libm. No FFTW, PNG, catalog or observer-track
data are needed.
## Inputs
All fixtures are frozen as IEEE-754 hex literals (`cases.json` and the
generated C header are bit-identical), including the two production grazing
rows reproduced from the Alcubierre pre-route (`x=(0,-24,0)`, `centre=2t`,
`v=2`, `R=5`, canonical `w` hex values). Families:
* **curated / adversarial**: head-on hit/miss, clear miss, grazing
`y=nextafter(R,0)`, exact tangent, near-boundary, `1e10 + 0.5, R=1`
cancellation, large-`t` `centre=0.1t`, exact linear `a==0` (inward and
initially outward), translated-origin cancellation.
* **fixed**: axes and three oblique frames, radii
`1e-100…1e100`, `D/R ∈ {1+ulp,2,10,100,512,1024,1e4,1e8,1e10}`, impact
ratios `{0,.5,.99,1-1e-6,nextafter(1,0),1,nextafter(1,∞),1+1e-6,1.1}`.
The `512`/`1024` ratios sample the crossover where long-double coefficient
rounding meets the `128 ε_D R²` geometry tolerance (`≈512`) and the double
crossover (`≈11`), separating fallback rates from the all-UNCERTAIN `1e10`
regime.
* **moving**: `v ∈ {0,.1,2,10}` along a transverse direction.
* **growing**: `rr = -1` and `nextafter(-1,∓∞)`, **inward and
initially-outward** photons (`w = ∓tow`), axis and oblique.
* **shrinking**: `rr>0`, labelled/domain-checked algebra (radius would go
negative on the open past; roots outside `R0 - rr·s > 0` are non-physical).
* **route**: `rr = 0` plus **`rr<0` growing inward/outward** families
(positive radius on the whole past, `valid_t_min=-1e300`) across all three
direction frames, driven through the public `asymptotic_route_camera`. The
oblique-2 growing-outward family reproduces the double kernel false-MISS
cases, where a kernel MISS bypasses the fallback and the route escapes
directly; the route reference uses the actual normalised canonical `w`, so
normalisation effects are explicit.
Exact counts are emitted by the run (and in `summary.json`); they are not
hard-coded here.
## Reference oracle
Inputs are exact, so they are represented as `fractions.Fraction` (Python
stdlib). `d`, `q`, `a`, `b`, `c`, the discriminant and polynomial residuals
are **exact rationals**; only `sqrt` uses `decimal` (`--precision`, default
160). A custom hex parser handles both `%a` (double) and `%La` (long double)
without `float.fromhex`, which would silently drop a 64-bit long-double mantissa
to 53 bits. Reference classification mirrors production semantics: `c<0` is
INSIDE (not an entry failure), `c==0` uses the boundary slope, `a==0` is the
exact linear branch, a real double root/tangent is MISS.
Route references are built from the **actual canonical state the probe printed**
(`canonx*`, `canonw*`) joined with the callback samples at `t0`, so the 1-ulp
normalisation of `w` and both callback models (`centre = c0 + v(t-t0)` and the
production `centre = v t`) are handled explicitly.
## Metrics
Kernel:
* status counts (ENTRY/MISS/UNCERTAIN) vs the reference, **false MISS**
(reference ENTER, kernel MISS) and **false candidate** (reference MISS,
kernel ENTRY);
* root error in ULP of the returned double root and relative error, for the
common reference-ENTER set and the intersection where *all* variants returned
ENTRY (so more UNCERTAIN cannot look better by selection);
* polynomial residual at the candidate root using the actual printed
coefficients, the exact ideal polynomial, and the nearest-double-rounded
reference root as a baseline;
* `a` exact-zero vs kernel-zero, `a` sign mismatches, `a` collapse/spurious
non-zero, and conditioning labels (`near_linear`, `late`, `ill_conditioned`).
Outward/growing and near-linear cases are ill-conditioned; a rounded-zero `a`
can turn a very late true entry into a false MISS, so these are reported
separately and are not treated as a universal precision claim.
Route:
* kind/status vs reference, false MISS, false candidate, camera-INSIDE
mismatches, total `INVALID/ENTRY_UNCONFIRMED` outcomes independent of reference
classification (a conservative refusal to confirm is not a false escape);
* the **public kernel classification at the actual canonical inputs**
(`kern_status` in the route CSV), so a reference ENTER / kernel MISS / route
ESCAPED (`kernel_false_miss_escaped`) is visible and is not confused with
canonical normalisation;
* fast-path vs fallback (`entry_fallback_evaluations`), `F/tol` for accepted
entries, and Pi/`L_camera` preservation.
The reference enforces `R0 - rr·s > 0` for quadratic roots; a positive root
outside the physical radius domain is reported as `domain_clipped` and is not
counted as a physical ENTER/false-MISS. A 160-vs-240-digit consistency check is
run for every curated and near-linear/late/domain-clipped id and must report the
same status, discriminant sign and nearest-double root.
Timing:
* kernel: coefficient assembly + `entry_solve`, serial, `noinline` +
`volatile` sink, and an inline-asm opaque loop index so GCC cannot hoist the
pure kernel out of the repetition loop (identical for all variants). A
linearity check runs the kernel at 1× and 2× calls and reports the ratio
(expect ≈2) to prove per-call execution.
* route: public pre-route, OpenMP `static` schedule + reductions, with
route-kind and fallback counts captured so a timing gain cannot come from
silently escaping more rays. `--threads` defaults to 4.
Codegen evidence is collected with `objdump`/`nm`: for the double variants
`entry_solve` contains hardware `vfmadd` and zero x87; for `ld_fma` it contains
x87 plus `fmal` PLT relocations (extended-precision FMA is a libm software
routine on x86-64).
## Run
```sh
python3 benchmarks/quadratic_precision/run.py \
--output-dir /tmp/opencode/quadratic-comparison
```
Useful overrides: `--rounds N`, `--threads T`, `--precision P`,
`--kernel-target-calls N`, `--route-target-calls N`, `--skip-build` (reuse the
last build), `--repo PATH`, `--cc CC`.
Artifacts (all under `--output-dir`):
```
generated/{ld_plain,ld_fma,double_fma,double_fma_coeff}.c
cases/quadratic_cases.h, cases/cases.json
environment.json, build_manifest.json
raw/kernel_<v>.csv, raw/route_<v>.csv
raw/kernel_metrics_<v>.csv, raw/route_metrics_<v>.csv
raw/curated_kernel.csv, raw/curated_route.csv
raw/microbench_<v>_r*.json, raw/routebench_<v>_r*.json
raw/fma_verify.json, raw/{linearity,curated}*
summary.json
logs/{build_commands,accuracy_*,microbench_*,routebench_*}.log
```
`summary.json` is the machine-readable aggregate; the script also prints the
coverage counts, per-variant ULP distributions, the curated case table, codegen
evidence and median ns/call.
## Interpretation caveats
* FMA improves the rounding of one product only. It **cannot** restore the
information already lost in the double inputs and in the coefficient sums
(`d = x - c`, `q = w + v`, and the `qq`/`dq`/`dd` accumulations). The data
should be read stage by stage.
* A kernel UNCERTAIN is not a failure: the shared route fallback may confirm the
entry. A kernel **MISS bypasses the fallback entirely**, so growing/outward
false-MISS families are exercised through dedicated `rr<0` route fixtures and
reported both as the raw kernel decision (`kern_status`) and the route
outcome. This benchmark does **not** claim that the fallback rescues a raw
kernel MISS on the same case; it only observes that more fallback is used to
confirm inaccurate candidates.
* The double uncertainty band differs from the long-double one by the precision
term: production uses `64·(DBL_EPSILON + LDBL_EPSILON)·scale` while the
double variants use `64·(DBL_EPSILON + DBL_EPSILON)·scale`; since
`LDBL_EPSILON ≪ DBL_EPSILON` this is roughly a factor of two in the band
width, which only matters exactly at the UNCERTAIN/MISS border.
* Number/scale results are machine- and compiler-specific; `environment.json`
records the source hash, compiler, flags and platform.
* This benchmark does not establish a universal guarantee, only the sampled
behaviour on the frozen fixture set.
+489
View File
@@ -0,0 +1,489 @@
#!/usr/bin/env python3
"""Generate the four precision variants of the asymptotic entry kernel and build
one self-contained probe executable per variant.
This module never edits the production tree. It reads the *current working
tree* ``src/asymptotic.c``, extracts the "entry kernel" region delimited by two
lexical anchors, emits four generated full-module copies under the scratch
directory, and compiles a probe that textually includes exactly one copy so the
private static ``entry_solve``/``entry_quadratic_coeffs`` are exercised as the
real generated code (not an independent toy copy).
Variants (all share the same stable-entry algebra, scaling, uncertainty policy,
validation and fallback; only the arithmetic under test changes):
ld_plain long double coefficients/solver, ordinary b*b-4ac and slope
ld_fma reconstructed experimental compensated fma disc/slope
double_fma double type/solver with compensated fma disc/slope,
ordinary coefficient accumulation (type-precision control)
double_fma_coeff double like double_fma plus fma coefficient accumulation
Only the kernel region is rewritten. Every other production dispatch/fallback
path is copied verbatim.
"""
from __future__ import annotations
import hashlib
import json
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
# ---------------------------------------------------------------------------
# Fixed compiler configuration. Identical for all four variants so the
# comparison is not confounded by flags. ``-ffp-contract=off`` disables the
# implicit FMA contraction GCC enables at -O2 for this target, so the ld_plain
# arm cannot silently gain extra precision; explicit fma()/fmal() calls still
# lower to hardware FMA.
# ---------------------------------------------------------------------------
BASE_FLAGS = [
"-std=c11",
"-march=native",
"-O2",
"-DNDEBUG",
"-ffp-contract=off",
"-fopenmp",
]
VARIANTS = ("ld_plain", "ld_fma", "double_fma", "double_fma_coeff")
# Common production translation units needed by the probe. Chosen as the
# minimal dependency closure of the entry kernel plus the public pre-route:
# geodesic initialisation, the numerical entry localizer, the Schwarzschild
# exterior referenced by the full module, the dispatch wrapper and the
# Minkowski provider. main/asymptotic/frame/catalog/PSF/FFTW are excluded.
COMMON_SOURCES = (
"src/geodesic.c",
"src/asymptotic_entry.c",
"src/asymptotic_schwarzschild.c",
"src/spacetime_common.c",
"src/spacetime_minkowski.c",
)
START_ANCHOR = "/* Long-double coefficients of the relative-distance quadratic"
FWD_PREFIX = "static void minkowski_route_entry("
END_ANCHOR = (
"static void minkowski_route_entry(const SpacetimeAsymptoticEnd *end, "
"double t0,"
)
class BuildError(RuntimeError):
pass
def _replace_once(text: str, old: str, new: str, what: str) -> str:
count = text.count(old)
if count != 1:
raise BuildError(f"expected exactly one occurrence of {what}, found {count}")
return text.replace(old, new, 1)
def locate_kernel(lines: list[str]) -> tuple[int, int]:
"""Return [start, end) line indices of the rewritten kernel region.
start is the 'Long-double coefficients' comment; end is the forward
declaration of ``minkowski_route_entry`` (preserved verbatim).
"""
starts = [i for i, l in enumerate(lines) if l.startswith(START_ANCHOR)]
if len(starts) != 1:
raise BuildError(
f"kernel start anchor must appear exactly once, found {len(starts)}"
)
start = starts[0]
ends = [
i
for i, l in enumerate(lines)
if i > start and l.startswith(FWD_PREFIX)
]
if not ends:
raise BuildError("kernel end anchor (minkowski_route_entry) not found")
end = ends[0]
if not lines[end].startswith(END_ANCHOR):
raise BuildError(
"kernel end anchor does not match the expected signature; "
"production source changed"
)
region = "".join(lines[start:end])
for needle in (
"typedef struct {",
"static EntryQuadratic entry_quadratic_coeffs(",
"static EntrySolveResult entry_solve(",
"static long double entry_discriminant(",
"EntryQuadratic;",
):
if region.count(needle) != 1:
raise BuildError(
f"kernel region must contain exactly one '{needle}', "
f"found {region.count(needle)}"
)
# The region must not contain the forward declaration or any later route
# code: those must be byte-for-byte preserved.
if "minkowski_preroute" in region or "AsymptoticStatus asymptotic_route" in region:
raise BuildError("kernel region overran into preserved route code")
return start, end
def transform_ld_plain(kernel: str) -> str:
"""Convert a compensated experimental kernel back to plain long double."""
old_disc = (
" const long double four_a = 4.0L * k->a;\n"
" const long double ac = four_a * k->c;\n"
" const long double ac_error = fmal(four_a, k->c, -ac);\n"
" *scale = k->b * k->b + fabsl(ac);\n"
" return fmal(k->b, k->b, -ac) - ac_error;\n"
)
new_disc = (
" const long double four_a = 4.0L * k->a;\n"
" const long double ac = four_a * k->c;\n"
" *scale = k->b * k->b + fabsl(ac);\n"
" return k->b * k->b - ac;\n"
)
out = _replace_once(kernel, old_disc, new_disc, "ld_plain discriminant body")
old_slope = "const long double slope = fmal(2.0L * k->a, s, k->b);"
new_slope = "const long double slope = 2.0L * k->a * s + k->b;"
out = _replace_once(out, old_slope, new_slope, "ld_plain slope")
return out
def _assert_double_clean(t: str) -> None:
"""The rewritten double kernel must contain no long-double promotion."""
forbidden = ("long double", "fmal(", "fabsl(", "fmaxl(", "frexpl(",
"scalbnl(", "sqrtl(", "copysignl(", "LDBL_")
for tok in forbidden:
if tok in t:
raise BuildError(f"double kernel still contains '{tok}'")
if re.search(r"(?<=[0-9])L(?![A-Za-z0-9_])", t):
raise BuildError("double kernel still contains an L-suffixed literal")
def transform_to_double(kernel: str) -> str:
"""Convert the kernel from long double to double arithmetic.
Explicit fmal->fma, *l->*, LDBL_*->DBL_*, long double->double and strips the
L suffix from the (few) decimal literals. Explicit fma calls are retained
(double_fma keeps compensated discriminant/slope).
"""
t = kernel
for old, new in (
("fmal(", "fma("),
("fabsl(", "fabs("),
("fmaxl(", "fmax("),
("frexpl(", "frexp("),
("scalbnl(", "scalbn("),
("sqrtl(", "sqrt("),
("copysignl(", "copysign("),
):
t = t.replace(old, new)
t = t.replace("LDBL_MIN", "DBL_MIN")
t = t.replace("LDBL_EPSILON", "DBL_EPSILON")
t = t.replace("long double", "double")
# Strip L suffixes from decimal literals; otherwise 4.0L/2.0L would
# re-promote the double expression and confound type accuracy/timing.
t = re.sub(r"(?<=[0-9])L(?![A-Za-z0-9_])", "", t)
_assert_double_clean(t)
return t
def transform_double_fma_coeff(kernel: str) -> str:
"""double variants plus fma coefficient accumulation and products."""
dbl = transform_to_double(kernel)
old_block = (
" double qq = 0.0, dot_dq = 0.0, dot_dd = 0.0;\n"
" for (int i = 0; i < 3; ++i) {\n"
" qq += q[i] * q[i];\n"
" dot_dq += d[i] * q[i];\n"
" dot_dd += d[i] * d[i];\n"
" }\n"
" const double R0_ld = (double)R0;\n"
" const double rr_ld = (double)rr;\n"
" EntryQuadratic k;\n"
" k.a = qq - rr_ld * rr_ld;\n"
" k.b = 2.0 * (dot_dq + R0_ld * rr_ld);\n"
" k.c = dot_dd - R0_ld * R0_ld;\n"
)
new_block = (
" double qq = 0.0, dot_dq = 0.0, dot_dd = 0.0;\n"
" for (int i = 0; i < 3; ++i) {\n"
" qq = fma(q[i], q[i], qq);\n"
" dot_dq = fma(d[i], q[i], dot_dq);\n"
" dot_dd = fma(d[i], d[i], dot_dd);\n"
" }\n"
" const double R0_ld = (double)R0;\n"
" const double rr_ld = (double)rr;\n"
" EntryQuadratic k;\n"
" k.a = fma(-rr_ld, rr_ld, qq);\n"
" k.b = 2.0 * fma(R0_ld, rr_ld, dot_dq);\n"
" k.c = fma(-R0_ld, R0_ld, dot_dd);\n"
)
return _replace_once(dbl, old_block, new_block, "double_fma_coeff block")
def generate_variants(repo: Path, out_dir: Path) -> dict:
"""Read current src/asymptotic.c and emit the four variant modules."""
src_path = repo / "src" / "asymptotic.c"
text = src_path.read_text()
lines = text.splitlines(keepends=True)
start, end = locate_kernel(lines)
kernel = "".join(lines[start:end])
prefix = "".join(lines[:start])
suffix = "".join(lines[end:])
sha = hashlib.sha256(text.encode()).hexdigest()
# Production now uses plain long-double arithmetic. Reconstruct the former
# compensated arm so the original four-way comparison remains reproducible.
plain_disc = (
" const long double four_a = 4.0L * k->a;\n"
" const long double ac = four_a * k->c;\n"
" *scale = k->b * k->b + fabsl(ac);\n"
" return k->b * k->b - ac;\n"
)
fused_disc = plain_disc.replace(
" *scale =", " const long double ac_error = fmal(four_a, k->c, -ac);\n *scale ="
).replace("return k->b * k->b - ac;", "return fmal(k->b, k->b, -ac) - ac_error;")
fused = _replace_once(kernel, plain_disc, fused_disc, "ld_fma discriminant body")
fused = _replace_once(
fused, "const long double slope = 2.0L * k->a * s + k->b;",
"const long double slope = fmal(2.0L * k->a, s, k->b);", "ld_fma slope"
)
kernels = {
"ld_plain": kernel,
"ld_fma": fused,
"double_fma": transform_to_double(fused),
"double_fma_coeff": transform_double_fma_coeff(fused),
}
gen_dir = out_dir / "generated"
gen_dir.mkdir(parents=True, exist_ok=True)
paths = {}
metas = {}
for name in VARIANTS:
body = kernels[name]
banner = (
f"/* GENERATED FILE - do not edit.\n"
f" * variant: {name}\n"
f" * source: src/asymptotic.c sha256={sha}\n"
f" * region lines [{start + 1}, {end}] rewritten; all other code verbatim.\n"
f" */\n"
)
out = banner + prefix + body + suffix
path = gen_dir / f"{name}.c"
path.write_text(out)
paths[name] = path
metas[name] = {
"path": str(path),
"sha256": hashlib.sha256(out.encode()).hexdigest(),
"kernel_sha256": hashlib.sha256(body.encode()).hexdigest(),
}
return {
"source": str(src_path),
"source_sha256": sha,
"kernel_line_range": [start + 1, end],
"variants": metas,
}
def cc_version(cc: str) -> str:
try:
out = subprocess.run(
[cc, "--version"], capture_output=True, text=True, check=False
)
return (out.stdout or out.stderr).splitlines()[0] if out.stdout or out.stderr else ""
except OSError:
return ""
def environment(repo: Path, cc: str, threads: int) -> dict:
src_path = repo / "src" / "asymptotic.c"
env = {
"repo": str(repo),
"cc": cc,
"cc_version": cc_version(cc),
"base_flags": list(BASE_FLAGS),
"threads": threads,
"source_sha256": hashlib.sha256(src_path.read_bytes()).hexdigest(),
"git_head": _git(repo, "rev-parse", "HEAD"),
"git_status_short": _git(repo, "status", "--short"),
"uname": os.uname().sysname + " " + os.uname().release + " " + os.uname().machine,
"cpu_model": _cpu_model(),
}
return env
def _git(repo: Path, *args: str) -> str:
try:
out = subprocess.run(
["git", "-C", str(repo), *args], capture_output=True, text=True, check=False
)
return out.stdout.strip()
except OSError:
return ""
def _cpu_model() -> str:
try:
for line in Path("/proc/cpuinfo").read_text().splitlines():
if line.startswith("model name"):
return line.split(":", 1)[1].strip()
except OSError:
pass
return "unknown"
def compile_common(repo: Path, out_dir: Path, cc: str, flags: list[str]) -> list[Path]:
obj_dir = out_dir / "build" / "common"
obj_dir.mkdir(parents=True, exist_ok=True)
objs = []
for rel in COMMON_SOURCES:
src = repo / rel
obj = obj_dir / (Path(rel).stem + ".o")
cmd = [cc, *flags, f"-I{repo / 'src'}", "-c", str(src), "-o", str(obj)]
_run(cmd, out_dir, f"compile common {rel}")
objs.append(obj)
return objs
def compile_probe(
repo: Path,
out_dir: Path,
cc: str,
flags: list[str],
variant: str,
probe_src: Path,
cases_dir: Path,
common_objs: list[Path],
) -> Path:
"""Compile the probe (which #includes the generated variant) and link."""
exe_dir = out_dir / "build" / "bin"
exe_dir.mkdir(parents=True, exist_ok=True)
obj = out_dir / "build" / f"probe_{variant}.o"
variant_src = out_dir / "generated" / f"{variant}.c"
cmd = [
cc,
*flags,
f"-I{repo / 'src'}",
f"-I{cases_dir}",
f'-DPROBE_VARIANT_SOURCE="{variant_src}"',
f'-DPROBE_VARIANT_NAME="{variant}"',
"-c",
str(probe_src),
"-o",
str(obj),
]
_run(cmd, out_dir, f"compile probe {variant}")
exe = exe_dir / f"probe_{variant}"
link = [cc, *flags, str(obj), *[str(o) for o in common_objs], "-lm", "-o", str(exe)]
_run(link, out_dir, f"link probe {variant}")
return exe
def _run(cmd: list[str], out_dir: Path, what: str) -> None:
log_dir = out_dir / "logs"
log_dir.mkdir(parents=True, exist_ok=True)
with open(log_dir / "build_commands.log", "a") as fh:
fh.write(what + "\n$ " + " ".join(cmd) + "\n")
try:
proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
except OSError as exc:
raise BuildError(f"{what}: {exc}") from exc
with open(log_dir / "build_commands.log", "a") as fh:
fh.write(f"exit={proc.returncode}\n")
if proc.stdout:
fh.write(proc.stdout)
if proc.stderr:
fh.write(proc.stderr)
if proc.returncode != 0:
raise BuildError(
f"{what} failed (exit {proc.returncode}); see logs/build_commands.log\n"
+ (proc.stderr or "")[-4000:]
)
def build_all(repo: Path, out_dir: Path, cc: str, flags: list[str], threads: int) -> dict:
out_dir.mkdir(parents=True, exist_ok=True)
(out_dir / "logs").mkdir(parents=True, exist_ok=True)
variant_info = generate_variants(repo, out_dir)
common = compile_common(repo, out_dir, cc, flags)
probe_src = repo / "benchmarks" / "quadratic_precision" / "fixtures" / "probe.c"
cases_dir = out_dir / "cases"
exes = {}
for name in VARIANTS:
exes[name] = compile_probe(
repo, out_dir, cc, flags, name, probe_src, cases_dir, common
)
result = {"variants": variant_info["variants"],
"source_sha256": variant_info["source_sha256"],
"base_flags": list(flags),
"dependencies": dependency_hashes(repo),
"compiler": {"command": cc, "version": cc_version(cc)},
"probe_sha256": _file_sha(probe_src),
"cases_header_sha256": _file_sha(cases_dir / "quadratic_cases.h")}
result["executables"] = {k: str(v) for k, v in exes.items()}
result["common_objects"] = [str(o) for o in common]
(out_dir / "build_manifest.json").write_text(json.dumps(result, indent=2) + "\n")
return result
def _file_sha(path: Path):
if not Path(path).exists():
return None
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def dependency_hashes(repo: Path):
# Include all project headers conservatively, including transitive includes.
paths = {repo / rel for rel in COMMON_SOURCES}
paths.update((repo / "src").rglob("*.h"))
return {str(p.relative_to(repo)): _file_sha(p) for p in sorted(paths)}
def verify_manifest(repo: Path, out_dir: Path, flags: list[str], cc: str):
"""Check that an existing build matches the current source, flags and
fixtures. Returns (manifest, list_of_mismatch_reasons). Regenerates the
variant sources as a side effect so their hashes can be compared."""
manifest = json.loads((out_dir / "build_manifest.json").read_text())
reasons = []
if manifest.get("dependencies") != dependency_hashes(repo):
reasons.append("dependencies")
if manifest.get("compiler") != {"command": cc, "version": cc_version(cc)}:
reasons.append("compiler")
current = generate_variants(repo, out_dir)
if manifest.get("source_sha256") != current["source_sha256"]:
reasons.append("source_sha256")
if list(manifest.get("base_flags", [])) != list(flags):
reasons.append("base_flags")
for name, meta in current["variants"].items():
if manifest.get("variants", {}).get(name, {}).get("sha256") != meta["sha256"]:
reasons.append(f"variant:{name}")
probe_src = repo / "benchmarks" / "quadratic_precision" / "fixtures" / "probe.c"
if manifest.get("probe_sha256") != _file_sha(probe_src):
reasons.append("probe_sha256")
cases_header = out_dir / "cases" / "quadratic_cases.h"
if cases_header.exists() and manifest.get("cases_header_sha256") != _file_sha(cases_header):
reasons.append("cases_header_sha256")
for name, exe in manifest.get("executables", {}).items():
if not Path(exe).exists():
reasons.append(f"missing_exe:{name}")
return manifest, reasons
if __name__ == "__main__":
import argparse
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--repo", default=str(Path(__file__).resolve().parents[2]))
ap.add_argument("--output-dir", default="/tmp/opencode/quadratic-comparison")
ap.add_argument("--cc", default=os.environ.get("CC", "cc"))
ap.add_argument("--threads", type=int, default=4)
args = ap.parse_args()
repo = Path(args.repo).resolve()
out = Path(args.output_dir).resolve()
try:
info = build_all(repo, out, args.cc, BASE_FLAGS, args.threads)
except BuildError as exc:
print(f"build failed: {exc}", file=sys.stderr)
sys.exit(1)
print(json.dumps(info, indent=2))
+441
View File
@@ -0,0 +1,441 @@
#!/usr/bin/env python3
"""Deterministic fixture generation for the quadratic precision benchmark.
All values are frozen as IEEE-754 hex literals in the generated C header and as
hex strings in cases.json, so the C probe and the Python oracle see bit-for-bit
identical inputs. No external catalog/observer/slab data is required.
Case families (kernel):
curated explicit adversarial / production fixtures
fixed fixed sphere, axis and oblique directions
moving constant sphere velocity (radial/transverse), inward photons
growing rr < 0 (radius grows along the past parameter), both inward
(toward-center) and initially-outward photons, axis + oblique
shrinking rr > 0, kernel only (radius would go negative on the far past)
cancellation translated/far-origin style input cancellation
Route cases use only rr == 0 (positive radius on the whole open past segment)
plus the two production grazing rows.
"""
from __future__ import annotations
import json
import math
from pathlib import Path
# Radii spanning subnormal-adjacent to huge scales.
RADII = [1e-100, 1e-10, 1.0, 1e10, 1e100]
DIST = [
math.nextafter(1.0, math.inf),
2.0,
10.0,
100.0,
512.0,
1024.0,
1e4,
1e8,
1e10,
]
IMPACTS = [
0.0,
0.5,
0.99,
1.0 - 1e-6,
math.nextafter(1.0, 0.0),
1.0,
math.nextafter(1.0, math.inf),
1.0 + 1e-6,
1.1,
]
VELS = [0.0, 0.1, 2.0, 10.0]
RR_GROW = [
-1.0,
math.nextafter(-1.0, -math.inf),
math.nextafter(-1.0, math.inf),
-0.999999,
]
def norm3(v):
n = math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
return [v[0] / n, v[1] / n, v[2] / n]
def cross(a, b):
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]]
# Fixed direction frames: (tow, perp). `tow` points from the sphere centre to
# the camera; `w = -tow` is the inward (toward-centre) past direction.
DIRS_AXIS = ([1.0, 0.0, 0.0], [0.0, 1.0, 0.0])
DIRS_OBLIQUE1 = (
norm3([1.0, 2.0, 3.0]),
norm3(cross(norm3([1.0, 2.0, 3.0]), [0.0, 0.0, 1.0])),
)
DIRS_OBLIQUE2 = (
norm3([-2.0, 1.0, 0.7]),
norm3(cross(norm3([-2.0, 1.0, 0.7]), [0.0, 1.0, 0.0])),
)
DIRS = [DIRS_AXIS, DIRS_OBLIQUE1, DIRS_OBLIQUE2]
def _case(cid, category, x, c, w, v, R0, rr):
return {
"id": cid,
"category": category,
"x": list(x),
"c": list(c),
"w": list(w),
"v": list(v),
"R0": R0,
"rr": rr,
}
def curated_kernel_cases():
"""Explicit adversarial and production-reproduction fixtures."""
cases = []
t63 = float.fromhex("0x1.fa8f5c28f5c29p+3")
t64 = float.fromhex("0x1.fb17e4b17e4b1p+3")
w63 = [-float.fromhex("0x1.d4afba4704cap-2"),
float.fromhex("0x1.c7378f8e872d1p-1"),
float.fromhex("0x1.15bad4e30e8ddp-8")]
w64 = [-float.fromhex("0x1.f98ae1a782104p-2"),
float.fromhex("0x1.bd3bb364ac492p-1"),
float.fromhex("0x1.102d2a1c6ac74p-7")]
# Production Alcubierre grazing rows: x=(0,-24,0), centre=2t, v=2, R=5.
cases.append(_case(0, "real63", [0.0, -24.0, 0.0],
[2.0 * t63, 0.0, 0.0], w63, [2.0, 0.0, 0.0], 5.0, 0.0))
cases.append(_case(1, "real64", [0.0, -24.0, 0.0],
[2.0 * t64, 0.0, 0.0], w64, [2.0, 0.0, 0.0], 5.0, 0.0))
D = 10.0
R = 5.0
cases += [
_case(2, "headon_hit", [D, 0.0, 0.0], [0.0] * 3, [-1.0, 0.0, 0.0],
[0.0] * 3, R, 0.0),
_case(3, "headon_miss", [D, 0.0, 0.0], [0.0] * 3, [1.0, 0.0, 0.0],
[0.0] * 3, R, 0.0),
_case(4, "clear_miss", [D, 6.0, 0.0], [0.0] * 3, [-1.0, 0.0, 0.0],
[0.0] * 3, R, 0.0),
_case(5, "grazing_in", [D, math.nextafter(R, 0.0), 0.0], [0.0] * 3,
[-1.0, 0.0, 0.0], [0.0] * 3, R, 0.0),
_case(6, "exact_tangent", [D, R, 0.0], [0.0] * 3, [-1.0, 0.0, 0.0],
[0.0] * 3, R, 0.0),
_case(7, "grazing_out", [D, math.nextafter(R, math.inf), 0.0],
[0.0] * 3, [-1.0, 0.0, 0.0], [0.0] * 3, R, 0.0),
_case(8, "near_boundary_hit",
[math.nextafter(R, math.inf), 0.0, 0.0], [0.0] * 3,
[-1.0, 0.0, 0.0], [0.0] * 3, R, 0.0),
# 1e10 + 0.5, R=1: c loses the transverse term, b*b-4ac rounds to 0.
_case(9, "cancel_1e10_05", [1e10, 0.5, 0.0], [0.0] * 3,
[-1.0, 0.0, 0.0], [0.0] * 3, 1.0, 0.0),
_case(10, "cancel_1e10_1", [1e10, 1.0, 0.0], [0.0] * 3,
[-1.0, 0.0, 0.0], [0.0] * 3, 1.0, 0.0),
]
# Large-t positive reconstructed minimum (tests/test_asymptotic.c).
camera_x = float.fromhex("0x1.6bcc41e901908p+46")
t0 = 1e15
vx = 0.1
cases.append(_case(11, "large_t_01t", [camera_x, 0.0, 0.0],
[vx * t0, 0.0, 0.0], [-1.0, 0.0, 0.0], [vx, 0.0, 0.0],
10.0, 0.0))
# Exact linear growing sphere (a == 0), inward.
cases.append(_case(12, "linear_grow_in", [100.0, 0.0, 0.0], [0.0] * 3,
[-1.0, 0.0, 0.0], [0.0] * 3, 10.0, -1.0))
# Exact linear, initially outward: constant gap, honest miss.
cases.append(_case(13, "linear_grow_out", [100.0, 0.0, 0.0], [0.0] * 3,
[1.0, 0.0, 0.0], [0.0] * 3, 10.0, -1.0))
# Near-linear oblique, inward vs initially outward, rr just around -1.
tow = DIRS_OBLIQUE1[0]
perp = DIRS_OBLIQUE1[1]
for rr in RR_GROW:
for sign, tag in ((-1.0, "in"), (1.0, "out")):
w = [sign * tow[i] for i in range(3)]
x = [50.0 * tow[i] + 0.25 * perp[i] for i in range(3)]
cases.append(_case(len(cases), f"nearlin_{tag}", x, [0.0] * 3, w,
[0.0] * 3, 4.0, rr))
# Translated-origin style cancellation: d = x - c with x = c + small.
base = 1e10
cbase = [base, -base, base * 0.5]
xb = [cbase[0] + 10.0, cbase[1] + 0.5, cbase[2] + 0.0]
cases.append(_case(len(cases), "translated_origin", xb, cbase,
[-1.0, 0.0, 0.0], [0.0] * 3, 1.0, 0.0))
return cases
def _broad_fixed(cid):
for di, (tow, perp) in enumerate(DIRS):
for R in RADII:
for dr in DIST:
for ir in IMPACTS:
D = dr * R
b = ir * R
x = [D * tow[i] + b * perp[i] for i in range(3)]
w = [-tow[i] for i in range(3)]
yield _case(cid, f"fixed_d{di}", x, [0.0] * 3, w, [0.0] * 3,
R, 0.0)
cid += 1
return cid
def _broad_moving(cid):
for (tow, perp) in DIRS:
for R in (1e-10, 1.0, 1e10):
for dr in (2.0, 100.0, 1e4):
for ir in (0.0, 0.99, 1.0, 1.1):
for vel in VELS:
D = dr * R
b = ir * R
x = [D * tow[i] + b * perp[i] for i in range(3)]
w = [-tow[i] for i in range(3)]
v = [vel * perp[i] for i in range(3)]
yield _case(cid, "moving", x, [0.0] * 3, w, v, R, 0.0)
cid += 1
return cid
def _broad_growing(cid):
for di, (tow, perp) in enumerate(DIRS):
for sign, tag in ((-1.0, "in"), (1.0, "out")):
for rr in RR_GROW:
for dr in (2.0, 10.0, 100.0, 1e4, 1e8):
for ir in (0.0, 0.5, 0.99, 1.0, 1.1):
D = dr * 4.0
b = ir * 4.0
x = [D * tow[i] + b * perp[i] for i in range(3)]
w = [sign * tow[i] for i in range(3)]
yield _case(cid, f"growing_{tag}", x, [0.0] * 3, w,
[0.0] * 3, 4.0, rr)
cid += 1
return cid
def _broad_shrinking(cid):
for (tow, perp) in (DIRS_AXIS, DIRS_OBLIQUE1):
for dr in (2.0, 10.0, 100.0):
for ir in (0.0, 0.99, 1.0):
D = dr * 10.0
b = ir * 10.0
x = [D * tow[i] + b * perp[i] for i in range(3)]
w = [-tow[i] for i in range(3)]
yield _case(cid, "shrinking", x, [0.0] * 3, w, [0.0] * 3, 10.0,
0.1)
cid += 1
return cid
def _materialize(gen, cases):
for c in gen:
cases.append(c)
def _build_all():
cases = curated_kernel_cases()
cid = 1000
for gen in (_broad_fixed, _broad_moving, _broad_growing, _broad_shrinking):
gen_cases = []
_materialize(gen(cid), gen_cases)
if gen_cases:
cid = gen_cases[-1]["id"] + 1
cases.extend(gen_cases)
return cases
# ---------------------------------------------------------------------------
def _route(cid, category, t0, obs, direction, c0, v, R0, rr, model=0,
valid_t_min=-1e300):
return {
"id": cid,
"category": category,
"t0": t0,
"obs": list(obs),
"dir": list(direction),
"c0": list(c0),
"v": list(v),
"R0": R0,
"rr": rr,
"valid_t_min": valid_t_min,
"model": model,
}
def route_cases():
cases = []
t63 = float.fromhex("0x1.fa8f5c28f5c29p+3")
t64 = float.fromhex("0x1.fb17e4b17e4b1p+3")
w63 = [-float.fromhex("0x1.d4afba4704cap-2"),
float.fromhex("0x1.c7378f8e872d1p-1"),
float.fromhex("0x1.15bad4e30e8ddp-8")]
w64 = [-float.fromhex("0x1.f98ae1a782104p-2"),
float.fromhex("0x1.bd3bb364ac492p-1"),
float.fromhex("0x1.102d2a1c6ac74p-7")]
# Production rows reproduced through the Alcubierre-style callback (model 1).
cases.append(_route(0, "real63", t63, [0.0, -24.0, 0.0], w63,
[2.0 * t63, 0.0, 0.0], [2.0, 0.0, 0.0], 5.0, 0.0,
model=1))
cases.append(_route(1, "real64", t64, [0.0, -24.0, 0.0], w64,
[2.0 * t64, 0.0, 0.0], [2.0, 0.0, 0.0], 5.0, 0.0,
model=1))
# Same rows through the input-stable callback (model 0).
cases.append(_route(2, "real63_stable", t63, [0.0, -24.0, 0.0], w63,
[2.0 * t63, 0.0, 0.0], [2.0, 0.0, 0.0], 5.0, 0.0,
model=0))
cases.append(_route(3, "real64_stable", t64, [0.0, -24.0, 0.0], w64,
[2.0 * t64, 0.0, 0.0], [2.0, 0.0, 0.0], 5.0, 0.0,
model=0))
R = 5.0
cases += [
_route(10, "headon_hit", 0.0, [10.0, 0.0, 0.0], [-1.0, 0.0, 0.0],
[0.0] * 3, [0.0] * 3, R, 0.0),
_route(11, "headon_miss", 0.0, [10.0, 0.0, 0.0], [1.0, 0.0, 0.0],
[0.0] * 3, [0.0] * 3, R, 0.0),
_route(12, "clear_miss", 0.0, [10.0, 6.0, 0.0], [-1.0, 0.0, 0.0],
[0.0] * 3, [0.0] * 3, R, 0.0),
_route(13, "grazing_in", 0.0, [10.0, math.nextafter(R, 0.0), 0.0],
[-1.0, 0.0, 0.0], [0.0] * 3, [0.0] * 3, R, 0.0),
_route(14, "exact_tangent", 0.0, [10.0, R, 0.0], [-1.0, 0.0, 0.0],
[0.0] * 3, [0.0] * 3, R, 0.0),
_route(15, "grazing_out", 0.0,
[10.0, math.nextafter(R, math.inf), 0.0], [-1.0, 0.0, 0.0],
[0.0] * 3, [0.0] * 3, R, 0.0),
_route(16, "camera_inside", 0.0, [2.0, 0.0, 0.0], [1.0, 0.0, 0.0],
[0.0] * 3, [0.0] * 3, R, 0.0),
_route(17, "near_boundary_hit", 0.0,
[math.nextafter(R, math.inf), 0.0, 0.0], [-1.0, 0.0, 0.0],
[0.0] * 3, [0.0] * 3, R, 0.0),
]
# Broad fixed-sphere subset through the public route.
cid = 100
for (tow, perp) in (DIRS_AXIS, DIRS_OBLIQUE1):
for R0 in (1e-10, 1.0, 1e10):
for dr in (2.0, 10.0, 100.0, 512.0, 1024.0, 1e4):
for ir in (0.0, 0.5, 0.99, 1.0, 1.1):
D = dr * R0
b = ir * R0
obs = [D * tow[i] + b * perp[i] for i in range(3)]
direction = [-tow[i] for i in range(3)]
cases.append(_route(cid, f"route_fixed_d{dr:g}", 0.0, obs,
direction, [0.0] * 3, [0.0] * 3, R0,
0.0))
cid += 1
# Moving spheres (v = 2 tow), model 0.
for ir in (0.0, 0.99, 1.1):
cases.append(_route(cid, "route_moving", 0.0, [100.0, 0.0, 0.0],
[-1.0, 0.0, 0.0], [0.0] * 3, [2.0, 0.0, 0.0], 5.0,
0.0))
cid += 1
# Growing worldtubes (rr <= 0, positive radius on the whole past), inward
# and initially-outward photons, axis + oblique. These mirror the
# growing_out kernel false-MISS family and exercise the public route where
# a kernel MISS bypasses the fallback entirely. valid_t_min is far below
# any sampled time so no artificial history clip is introduced.
R0 = 4.0
for (tow, perp) in DIRS:
for sign, tag in ((-1.0, "in"), (1.0, "out")):
for rr in RR_GROW:
for dr in (2.0, 10.0, 100.0, 512.0, 1024.0, 1e4):
for ir in (0.0, 1.0, 1.1):
D = dr * R0
b = ir * R0
obs = [D * tow[i] + b * perp[i] for i in range(3)]
direction = [sign * tow[i] for i in range(3)]
cases.append(_route(cid, f"route_grow_{tag}_d{dr:g}",
0.0, obs, direction, [0.0] * 3,
[0.0] * 3, R0, rr))
cid += 1
return cases
# ---------------------------------------------------------------------------
def _hex(x):
return float(x).hex()
def _fmt(v):
return _hex(v)
def write_header(kernel, route, out_path: Path):
lines = []
lines.append("/* Generated by benchmarks/quadratic_precision/cases.py. */\n")
lines.append("#ifndef QUADRATIC_CASES_H\n#define QUADRATIC_CASES_H\n")
lines.append("typedef struct {\n int id;\n const char *category;\n"
" double x[3], c[3], w[3], v[3];\n double R0, rr;\n"
"} QuadKernelCase;\n\n")
lines.append("typedef struct {\n int id;\n const char *category;\n"
" double t0;\n double obs[3];\n double dir[3];\n"
" double c0[3];\n double v[3];\n double R0, rr;\n"
" double valid_t_min;\n int model;\n} QuadRouteCase;\n\n")
lines.append("static const QuadKernelCase quad_kernel_cases[] = {\n")
for c in kernel:
lines.append(
" {.id=%d,.category=\"%s\","
".x={%s,%s,%s},.c={%s,%s,%s},.w={%s,%s,%s},.v={%s,%s,%s},"
".R0=%s,.rr=%s},\n"
% (c["id"], c["category"], *[_fmt(z) for z in c["x"]],
*[_fmt(z) for z in c["c"]], *[_fmt(z) for z in c["w"]],
*[_fmt(z) for z in c["v"]], _fmt(c["R0"]), _fmt(c["rr"])))
lines.append("};\n")
lines.append("static const int quad_kernel_case_count = %d;\n\n"
% len(kernel))
lines.append("static const QuadRouteCase quad_route_cases[] = {\n")
for c in route:
lines.append(
" {.id=%d,.category=\"%s\",.t0=%s,"
".obs={%s,%s,%s},.dir={%s,%s,%s},.c0={%s,%s,%s},.v={%s,%s,%s},"
".R0=%s,.rr=%s,.valid_t_min=%s,.model=%d},\n"
% (c["id"], c["category"], _fmt(c["t0"]),
*[_fmt(z) for z in c["obs"]], *[_fmt(z) for z in c["dir"]],
*[_fmt(z) for z in c["c0"]], *[_fmt(z) for z in c["v"]],
_fmt(c["R0"]), _fmt(c["rr"]), _fmt(c["valid_t_min"]),
c["model"]))
lines.append("};\n")
lines.append("static const int quad_route_case_count = %d;\n"
% len(route))
lines.append("#endif\n")
out_path.write_text("".join(lines))
def write_json(kernel, route, out_path: Path):
def enc(c):
d = {}
for k, v in c.items():
if isinstance(v, float):
d[k] = v.hex()
elif isinstance(v, list):
d[k] = [z.hex() for z in v]
else:
d[k] = v
return d
out_path.write_text(
json.dumps(
{"deterministic": True, "kernel": [enc(c) for c in kernel],
"route": [enc(c) for c in route]},
indent=1,
)
+ "\n")
def build():
kernel = _build_all()
route = route_cases()
return kernel, route
if __name__ == "__main__":
k, r = build()
print(f"kernel cases: {len(k)}, route cases: {len(r)}")
cats = {}
for c in k:
cats[c["category"]] = cats.get(c["category"], 0) + 1
for key in sorted(cats):
print(f" {key}: {cats[key]}")
rcats = {}
for c in r:
rcats[c["category"]] = rcats.get(c["category"], 0) + 1
print("route categories:")
for key in sorted(rcats):
print(f" {key}: {rcats[key]}")
@@ -0,0 +1,614 @@
/* Quadratic precision benchmark probe.
*
* Compiled once per generated variant, with
* -DPROBE_VARIANT_SOURCE=".../ld_fma.c" -DPROBE_VARIANT_NAME="ld_fma"
* The probe #includes the generated full module, so the private static
* entry_quadratic_coeffs / entry_solve and the public asymptotic_route_camera
* are the *actual generated* code. No production file is modified.
*
* Modes:
* accuracy -- run every kernel case through the generated kernel and every
* route case through the generated public pre-route; emit CSVs.
* microbench -- timed repeated kernel assembly + entry_solve (serial).
* routebench -- timed repeated public pre-route (OpenMP, static schedule).
*/
#include PROBE_VARIANT_SOURCE
#include <float.h>
#include <math.h>
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "quadratic_cases.h"
#ifndef PROBE_VARIANT_NAME
#define PROBE_VARIANT_NAME "unknown"
#endif
/* ------------------------------------------------------------------ */
/* Exact-value coefficient printing: long double uses %La (full mantissa),
* double uses %a. _Generic selects the right printer without knowing the
* generated EntryQuadratic field type. */
static void coeff_ld(FILE *f, long double v) { fprintf(f, "%La", v); }
static void coeff_d(FILE *f, double v) { fprintf(f, "%a", v); }
#define PRINT_COEFF(f, v) \
_Generic((v), long double : coeff_ld, double : coeff_d)((f), (v))
/* ------------------------------------------------------------------ */
/* Fixture source: flat identity metric, one Minkowski end whose worldtube is
* a constant-velocity (optionally linearly growing/shrinking) sphere.
*
* model 0 (input-stable): center = c0 + v*(t - t0), R = R0 + rr*(t - t0)
* model 1 (production-style): center = v*t, R = R0 + rr*t
*
* model 0 evaluates exactly at the camera: center(t0) == c0, R(t0) == R0, so
* the kernel inputs are the frozen case values. model 1 mirrors the
* Alcubierre-style callback (used only with rr == 0). */
typedef struct {
double c0[3];
double v[3];
double R0;
double rr;
double t0;
double valid_t_min;
int model;
} FixtureContext;
static double fixture_center(const FixtureContext *ctx, int i, double t) {
if (ctx->model == 0)
return ctx->c0[i] + ctx->v[i] * (t - ctx->t0);
return ctx->v[i] * t;
}
static double fixture_radius(const FixtureContext *ctx, double t) {
if (ctx->model == 0)
return ctx->R0 + ctx->rr * (t - ctx->t0);
return ctx->R0 + ctx->rr * t;
}
static SpacetimePointStatus fixture_eval(const SpacetimeSource *source,
double t, const double x[3],
MetricData *metric) {
(void)source;
(void)t;
(void)x;
*metric = (MetricData){.alpha = 1.0,
.gamma = {{1.0, 0.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 0.0, 1.0}}};
return SPACETIME_POINT_OK;
}
static SpacetimeRayStatus fixture_classify(const SpacetimeSource *source,
double t, const double x[3]) {
(void)source;
(void)t;
(void)x;
return SPACETIME_RAY_ACTIVE;
}
static size_t fixture_end_count(const SpacetimeSource *source) {
(void)source;
return 1;
}
static int fixture_end(const SpacetimeSource *source, size_t index,
SpacetimeAsymptoticEnd *out) {
const FixtureContext *ctx = source->context;
if (index != 0)
return -1;
*out = (SpacetimeAsymptoticEnd){
.end_id = 0,
.exterior_kind = ASYMPTOTIC_EXTERIOR_MINKOWSKI,
.mass = 0.0,
.frame_origin = {0.0, 0.0, 0.0},
.frame_axes = {{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}};
return 0;
}
static int fixture_worldtube(const SpacetimeSource *source,
SpacetimeEndId end_id, double t,
SpacetimeEscapeWorldtubeSample *out) {
const FixtureContext *ctx = source->context;
if (end_id != 0)
return -1;
if (!isfinite(t) || t < ctx->valid_t_min) {
*out = (SpacetimeEscapeWorldtubeSample){.valid = 0};
return 0;
}
*out = (SpacetimeEscapeWorldtubeSample){
.center = {fixture_center(ctx, 0, t), fixture_center(ctx, 1, t),
fixture_center(ctx, 2, t)},
.velocity = {ctx->v[0], ctx->v[1], ctx->v[2]},
.radius = fixture_radius(ctx, t),
.radius_rate = ctx->rr,
.velocity_constant = 1,
.valid = 1};
return 0;
}
static double fixture_next_segment(const SpacetimeSource *source,
SpacetimeEndId end_id, double t) {
(void)source;
(void)end_id;
(void)t;
return NAN;
}
static void fixture_destroy(SpacetimeSource *source) {
source->context = NULL;
source->ops = NULL;
}
static const SpacetimeOps fixture_ops = {
.eval = fixture_eval,
.classify = fixture_classify,
.asymptotic_end_count = fixture_end_count,
.asymptotic_end = fixture_end,
.escape_worldtube_sample = fixture_worldtube,
.escape_worldtube_next_segment = fixture_next_segment,
.destroy = fixture_destroy,
};
/* ------------------------------------------------------------------ */
static void print_environment(void) {
fprintf(stdout,
"{\"variant\":\"%s\",\"sizeof_long_double\":%zu,\"LDBL_MANT_DIG\":%d,"
"\"DBL_MANT_DIG\":%d,\"LDBL_MAX_EXP\":%d,\"hardware_threads\":%d,"
"\"omp_max_threads\":%d}\n",
PROBE_VARIANT_NAME, sizeof(long double), LDBL_MANT_DIG, DBL_MANT_DIG,
LDBL_MAX_EXP, omp_get_num_procs(), omp_get_max_threads());
}
/* ------------------------------------------------------------------ */
/* accuracy mode */
static void mode_accuracy(const char *kernel_out, const char *route_out) {
FILE *kf = fopen(kernel_out, "w");
if (kf == NULL) {
fprintf(stderr, "cannot open %s\n", kernel_out);
exit(2);
}
fprintf(kf,
"id,category,status,sigma,x0,x1,x2,c0,c1,c2,w0,w1,w2,v0,v1,v2,R0,rr,"
"a,b,c\n");
for (int i = 0; i < quad_kernel_case_count; ++i) {
const QuadKernelCase *c = &quad_kernel_cases[i];
EntryQuadratic k = entry_quadratic_coeffs(c->x, c->c, c->w, c->v, c->R0,
c->rr);
double s = -1.0;
EntrySolveResult r = entry_solve(&k, &s);
fprintf(kf, "%d,%s,%d,%a", c->id, c->category, (int)r, s);
for (int j = 0; j < 3; ++j)
fprintf(kf, ",%a", c->x[j]);
for (int j = 0; j < 3; ++j)
fprintf(kf, ",%a", c->c[j]);
for (int j = 0; j < 3; ++j)
fprintf(kf, ",%a", c->w[j]);
for (int j = 0; j < 3; ++j)
fprintf(kf, ",%a", c->v[j]);
fprintf(kf, ",%a,%a,", c->R0, c->rr);
PRINT_COEFF(kf, k.a);
fputc(',', kf);
PRINT_COEFF(kf, k.b);
fputc(',', kf);
PRINT_COEFF(kf, k.c);
fputc('\n', kf);
}
fclose(kf);
FILE *rf = fopen(route_out, "w");
if (rf == NULL) {
fprintf(stderr, "cannot open %s\n", route_out);
exit(2);
}
fprintf(rf,
"id,category,status,kind,failure_reason,fallback_evals,pi_match,"
"lcam_match,F,tol,why,x0,x1,x2,Pi0,Pi1,Pi2,ninf0,ninf1,ninf2,"
"canon_t,canonx0,canonx1,canonx2,canonw0,canonw1,canonw2,"
"cbx0,cbx1,cbx2,cbr,cbok,ct00,ct01,ct02,rt0,cb0ok,"
"kern_ok,kern_status,kern_sigma,kern_a,kern_b,kern_c,"
"logcamera,logentry,"
"t0,oc0,oc1,oc2,d0,d1,d2,fc0,fc1,fc2,v0,v1,v2,"
"R0,rr,model,reason_name\n");
for (int i = 0; i < quad_route_case_count; ++i) {
const QuadRouteCase *c = &quad_route_cases[i];
FixtureContext ctx = {.c0 = {c->c0[0], c->c0[1], c->c0[2]},
.v = {c->v[0], c->v[1], c->v[2]},
.R0 = c->R0,
.rr = c->rr,
.t0 = c->t0,
.valid_t_min = c->valid_t_min,
.model = c->model};
SpacetimeSource source = {.ops = &fixture_ops, .context = &ctx};
ObserverState obs = {0};
obs.coordinate_time = c->t0;
for (int j = 0; j < 3; ++j)
obs.coordinate_position[j] = c->obs[j];
obs.tetrad[0][0] = 1.0;
obs.tetrad[1][1] = 1.0;
obs.tetrad[2][2] = 1.0;
obs.tetrad[3][3] = 1.0;
int canon_ok = 0;
AsymptoticPhotonState canon = {0};
GeodesicRayState st = {0};
{
MetricData metric;
if (spacetime_eval(&source, c->t0, obs.coordinate_position, &metric) ==
SPACETIME_POINT_OK &&
geodesic_initialize_past_ray_metric(&metric, &obs, c->dir, &st) == 0 &&
asymptotic_canonical_from_backend(&source, 0, &metric, c->t0, st.x,
st.Pi, st.log_alpha_p0,
&canon) == 0)
canon_ok = 1;
}
AsymptoticRoute route;
AsymptoticStatus status =
asymptotic_route_camera(&source, &obs, c->dir, &route);
int pi_match = 1, lcam_match = 1;
if (canon_ok && status == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY) {
for (int j = 0; j < 3; ++j)
if (route.Pi[j] != st.Pi[j])
pi_match = 0;
if (route.log_alpha_p0_camera != st.log_alpha_p0)
lcam_match = 0;
}
double F = NAN, tol = NAN;
RayReason why = RAY_REASON_NONE;
if (status == ASYMPTOTIC_OK && route.kind == ASYMPTOTIC_ROUTE_ENTRY)
asymptotic_entry_geometry(&source, route.end_id, route.activate_t,
route.x, &F, &tol, &why);
SpacetimeEscapeWorldtubeSample cb = {0};
int cbok = 0;
{
const double tt = (status == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY)
? route.activate_t
: c->t0;
if (spacetime_escape_worldtube_sample(&source, route.end_id, tt, &cb) ==
0 &&
cb.valid)
cbok = 1;
}
/* Worldtube sample at the segment start used by the quadratic kernel. */
SpacetimeEscapeWorldtubeSample cb0 = {0};
int cb0ok = 0;
if (spacetime_escape_worldtube_sample(&source, route.end_id, c->t0,
&cb0) == 0 &&
cb0.valid)
cb0ok = 1;
/* Reproduce the first-segment public kernel classification at the actual
* canonical inputs, so a reference ENTER / kernel MISS / route ESCAPED is
* directly visible and not confused with canonical normalisation. */
int kern_ok = 0;
int kern_status = -1;
double kern_sigma = -1.0;
EntryQuadratic kk = {0};
SpacetimeAsymptoticEnd end_desc;
if (canon_ok && cb0ok &&
spacetime_asymptotic_end(&source, 0, &end_desc) == 0) {
double c_frame[3], v_frame[3];
backend_position_to_frame(&end_desc, cb0.center, c_frame);
backend_vector_to_frame(&end_desc, cb0.velocity, v_frame);
kk = entry_quadratic_coeffs(canon.x, c_frame, canon.w, v_frame,
cb0.radius, cb0.radius_rate);
kern_status = (int)entry_solve(&kk, &kern_sigma);
kern_ok = 1;
}
fprintf(rf, "%d,%s,%d,%d,%d,%u,%d,%d,%a,%a,%d", c->id, c->category,
(int)status, (int)route.kind, (int)route.failure_reason,
route.entry_fallback_evaluations, pi_match, lcam_match, F, tol,
(int)why);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", route.x[j]);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", route.Pi[j]);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", route.n_infinity[j]);
fprintf(rf, ",%a", canon.t);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", canon.x[j]);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", canon.w[j]);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", cb.center[j]);
fprintf(rf, ",%a,%d", cb.radius, cbok);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", cb0.center[j]);
fprintf(rf, ",%a,%d", cb0.radius, cb0ok);
fprintf(rf, ",%d,%d,%a,", kern_ok, kern_status, kern_sigma);
PRINT_COEFF(rf, kk.a);
fputc(',', rf);
PRINT_COEFF(rf, kk.b);
fputc(',', rf);
PRINT_COEFF(rf, kk.c);
fprintf(rf, ",%a,%a", route.log_alpha_p0_camera, route.log_alpha_p0);
fprintf(rf, ",%a", c->t0);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", c->obs[j]);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", c->dir[j]);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", c->c0[j]);
for (int j = 0; j < 3; ++j)
fprintf(rf, ",%a", c->v[j]);
fprintf(rf, ",%a,%a,%d,%s\n", c->R0, c->rr, c->model,
ray_reason_name(route.failure_reason));
}
fclose(rf);
}
/* ------------------------------------------------------------------ */
/* microbench mode: coefficient assembly + entry_solve, serial. */
static volatile double g_kernel_sink;
static __attribute__((noinline)) double kernel_batch(const QuadKernelCase *cs,
int n, long reps) {
double acc = 0.0;
for (long r = 0; r < reps; ++r) {
for (int i = 0; i < n; ++i) {
/* Force the index through an opaque register so the compiler cannot
* hoist the pure coefficient solve out of the repetition loop or prove
* the loaded fixture invariant. Identical for every variant. */
int idx = i;
__asm__ __volatile__("" : "+r"(idx) : : "memory");
const QuadKernelCase *c = cs + idx;
EntryQuadratic k = entry_quadratic_coeffs(c->x, c->c, c->w, c->v,
c->R0, c->rr);
double s = 0.0;
acc += (double)entry_solve(&k, &s) + s * 1e-300;
}
}
return acc;
}
static void mode_microbench(long target_calls, const char *out) {
const int n = quad_kernel_case_count;
long reps = target_calls / n;
if (reps < 1)
reps = 1;
/* Warm-up outside the timed region. */
g_kernel_sink += kernel_batch(quad_kernel_cases, n, 1);
const double t0 = omp_get_wtime();
const double acc = kernel_batch(quad_kernel_cases, n, reps);
const double t1 = omp_get_wtime();
g_kernel_sink += acc;
const long calls = (long)n * reps;
const double seconds = t1 - t0;
FILE *f = fopen(out, "w");
if (f == NULL) {
fprintf(stderr, "cannot open %s\n", out);
exit(2);
}
fprintf(f,
"{\"variant\":\"%s\",\"mode\":\"microbench\",\"cases\":%d,"
"\"reps\":%ld,\"calls\":%ld,\"seconds\":%.9f,\"ns_per_call\":%.6f,"
"\"sink\":%.17g}\n",
PROBE_VARIANT_NAME, n, reps, calls, seconds,
seconds * 1e9 / (double)calls, g_kernel_sink);
fclose(f);
fprintf(stdout, "microbench %s: %ld calls in %.6f s (%.2f ns/call)\n",
PROBE_VARIANT_NAME, calls, seconds, seconds * 1e9 / (double)calls);
}
/* ------------------------------------------------------------------ */
/* routebench mode: public asymptotic_route_camera, OpenMP static. */
typedef struct {
FixtureContext *ctxs;
SpacetimeSource *srcs;
ObserverState *obss;
const QuadRouteCase *rcs;
int n;
} Preloaded;
typedef struct {
long entry, escaped, inside, time_exhausted, invalid, unsupported, other;
long fallback_sum;
} RouteCounts;
static volatile double g_route_sink;
static __attribute__((noinline)) void route_batch(const Preloaded *p, long reps,
int threads, double *seconds,
RouteCounts *counts) {
const int n = p->n;
long entry = 0, escaped = 0, inside = 0, texh = 0, inv = 0, unsup = 0,
other = 0, fallback = 0;
const long total = (long)n * reps;
const double t0 = omp_get_wtime();
#pragma omp parallel num_threads(threads) reduction(+ : entry, escaped, inside, texh, inv, unsup, other, fallback)
{
#pragma omp for schedule(static)
for (long k = 0; k < total; ++k) {
long kk = k;
__asm__ __volatile__("" : "+r"(kk) : : "memory");
const int i = (int)(kk % n);
AsymptoticRoute route;
const AsymptoticStatus st = asymptotic_route_camera(
&p->srcs[i], &p->obss[i], p->rcs[i].dir, &route);
fallback += (long)route.entry_fallback_evaluations;
if (st == ASYMPTOTIC_OK) {
switch (route.kind) {
case ASYMPTOTIC_ROUTE_ENTRY:
++entry;
break;
case ASYMPTOTIC_ROUTE_ESCAPED:
++escaped;
break;
case ASYMPTOTIC_ROUTE_INSIDE:
++inside;
break;
case ASYMPTOTIC_ROUTE_TIME_RANGE_EXHAUSTED:
++texh;
break;
default:
++other;
break;
}
} else if (st == ASYMPTOTIC_UNSUPPORTED) {
++unsup;
} else {
++inv;
}
}
}
*seconds = omp_get_wtime() - t0;
counts->entry = entry;
counts->escaped = escaped;
counts->inside = inside;
counts->time_exhausted = texh;
counts->invalid = inv;
counts->unsupported = unsup;
counts->other = other;
counts->fallback_sum = fallback;
}
static void mode_routebench(long target_calls, int threads, double max_seconds,
const char *out) {
const int n = quad_route_case_count;
Preloaded p;
p.n = n;
p.rcs = quad_route_cases;
p.ctxs = malloc(sizeof(FixtureContext) * (size_t)n);
p.srcs = malloc(sizeof(SpacetimeSource) * (size_t)n);
p.obss = malloc(sizeof(ObserverState) * (size_t)n);
if (!p.ctxs || !p.srcs || !p.obss) {
fprintf(stderr, "allocation failure\n");
exit(2);
}
for (int i = 0; i < n; ++i) {
const QuadRouteCase *c = &quad_route_cases[i];
p.ctxs[i] = (FixtureContext){.c0 = {c->c0[0], c->c0[1], c->c0[2]},
.v = {c->v[0], c->v[1], c->v[2]},
.R0 = c->R0,
.rr = c->rr,
.t0 = c->t0,
.valid_t_min = c->valid_t_min,
.model = c->model};
p.srcs[i] = (SpacetimeSource){.ops = &fixture_ops, .context = &p.ctxs[i]};
p.obss[i] = (ObserverState){0};
p.obss[i].coordinate_time = c->t0;
for (int j = 0; j < 3; ++j)
p.obss[i].coordinate_position[j] = c->obs[j];
p.obss[i].tetrad[0][0] = 1.0;
p.obss[i].tetrad[1][1] = 1.0;
p.obss[i].tetrad[2][2] = 1.0;
p.obss[i].tetrad[3][3] = 1.0;
}
/* Calibrate with one pass, then size reps to the call/time budgets. */
double cal_seconds = 0.0;
RouteCounts cal_counts;
route_batch(&p, 1, threads, &cal_seconds, &cal_counts);
const double per_call = cal_seconds / (double)n;
long reps = target_calls / n;
if (reps < 1)
reps = 1;
if (per_call > 0.0) {
const long by_time = (long)(max_seconds / (per_call * (double)n));
if (by_time < 1)
reps = 1;
else if (reps > by_time)
reps = by_time;
}
double seconds = 0.0;
RouteCounts counts;
route_batch(&p, reps, threads, &seconds, &counts);
g_route_sink += (double)counts.entry + (double)counts.escaped;
FILE *f = fopen(out, "w");
if (f == NULL) {
fprintf(stderr, "cannot open %s\n", out);
exit(2);
}
fprintf(f,
"{\"variant\":\"%s\",\"mode\":\"routebench\",\"cases\":%d,"
"\"reps\":%ld,\"calls\":%ld,\"threads\":%d,\"cal_seconds\":%.9f,"
"\"seconds\":%.9f,\"ns_per_call\":%.6f,\"entry\":%ld,\"escaped\":%ld,"
"\"inside\":%ld,\"time_exhausted\":%ld,\"invalid\":%ld,"
"\"unsupported\":%ld,\"other\":%ld,\"fallback_sum\":%ld}\n",
PROBE_VARIANT_NAME, n, reps, (long)n * reps, threads, cal_seconds,
seconds, seconds * 1e9 / (double)((long)n * reps), counts.entry,
counts.escaped, counts.inside, counts.time_exhausted, counts.invalid,
counts.unsupported, counts.other, counts.fallback_sum);
fclose(f);
fprintf(stdout,
"routebench %s: %ld calls in %.6f s on %d threads (%.2f ns/call)\n",
PROBE_VARIANT_NAME, (long)n * reps, seconds, threads,
seconds * 1e9 / (double)((long)n * reps));
free(p.ctxs);
free(p.srcs);
free(p.obss);
}
/* ------------------------------------------------------------------ */
static void usage(const char *argv0) {
fprintf(stderr,
"usage:\n"
" %s accuracy --kernel-out K.csv --route-out R.csv\n"
" %s microbench --out F.json [--target-calls N]\n"
" %s routebench --out F.json [--target-calls N] [--threads T]"
" [--max-seconds S]\n",
argv0, argv0, argv0);
}
static const char *arg_value(int argc, char **argv, const char *flag) {
for (int i = 1; i + 1 < argc; ++i)
if (strcmp(argv[i], flag) == 0)
return argv[i + 1];
return NULL;
}
int main(int argc, char **argv) {
print_environment();
if (argc < 2) {
usage(argv[0]);
return 1;
}
if (strcmp(argv[1], "accuracy") == 0) {
const char *k = arg_value(argc, argv, "--kernel-out");
const char *r = arg_value(argc, argv, "--route-out");
if (!k || !r) {
usage(argv[0]);
return 1;
}
mode_accuracy(k, r);
return 0;
}
if (strcmp(argv[1], "microbench") == 0) {
const char *out = arg_value(argc, argv, "--out");
const char *tc = arg_value(argc, argv, "--target-calls");
if (!out) {
usage(argv[0]);
return 1;
}
mode_microbench(tc ? atol(tc) : 10000000L, out);
return 0;
}
if (strcmp(argv[1], "routebench") == 0) {
const char *out = arg_value(argc, argv, "--out");
const char *tc = arg_value(argc, argv, "--target-calls");
const char *th = arg_value(argc, argv, "--threads");
const char *ms = arg_value(argc, argv, "--max-seconds");
if (!out) {
usage(argv[0]);
return 1;
}
mode_routebench(tc ? atol(tc) : 1000000L, th ? atoi(th) : 4,
ms ? atof(ms) : 20.0, out);
return 0;
}
usage(argv[0]);
return 1;
}
+571
View File
@@ -0,0 +1,571 @@
#!/usr/bin/env python3
"""High-precision reference for the quadratic entry benchmark.
Inputs are exact IEEE-754 values, so they are represented exactly as
``fractions.Fraction``. The relative-distance quadratic coefficients, the
discriminant and the polynomial residuals are therefore *exact* rationals; only
the square root needs ``decimal`` (160 significant digits by default). This
avoids ``float.fromhex`` (which would silently drop a long-double coefficient to
53 bits) and keeps the sign of a near-zero discriminant exact.
Reference classification mirrors the production contract:
* c < 0 -> camera already INSIDE (never an entry failure)
* c == 0 -> boundary slope b decides; b < 0 enters at once, b == 0 with a < 0
enters later, otherwise no crossing
* c > 0 -> smallest positive root with inward slope; a missing/tangent root
is MISS. ``a == 0`` is the exact linear branch.
"""
from __future__ import annotations
import csv
import json
import math
from decimal import Decimal, localcontext
from fractions import Fraction
DEFAULT_PREC = 160
def parse_hex(s: str) -> Fraction:
"""Exact rational value of a C ``%a`` / ``%La`` hex float literal."""
s = s.strip()
low = s.lower()
if "nan" in low:
raise ValueError("NaN input")
if "inf" in low:
return Fraction(0) # only used for flags; never present in fixtures
neg = False
if s and s[0] in "+-":
neg = s[0] == "-"
s = s[1:]
if s[:2].lower() == "0x":
s = s[2:]
mant, _, exp_s = s.partition("p")
if not exp_s:
mant, _, exp_s = s.partition("P")
exp = int(exp_s) if exp_s else 0
ip, _, fp = mant.partition(".")
digits = (ip + fp) or "0"
val = Fraction(int(digits, 16), 1)
shift = exp - 4 * len(fp)
if shift >= 0:
val *= Fraction(2) ** shift
else:
val /= Fraction(2) ** (-shift)
return -val if neg else val
def frac_to_dec(fr: Fraction, prec: int = DEFAULT_PREC) -> Decimal:
with localcontext() as ctx:
ctx.prec = prec
return Decimal(fr.numerator) / Decimal(fr.denominator)
def hex_to_dec(s: str, prec: int = DEFAULT_PREC) -> Decimal:
return frac_to_dec(parse_hex(s), prec)
def classify(a: Fraction, b: Fraction, c: Fraction, prec: int,
R0: Fraction | None = None, rr: Fraction | None = None):
"""Return (status, root_decimal_or_None, info).
``R0``/``rr`` (radius at the segment start and dR/dt) enforce the physical
positive-radius domain R(s) = R0 - rr*s > 0 along the past parameter. A
positive root outside that domain is not a physical entry: it is reported
as MISS with ``info['domain_clipped']`` set (shrinking worldtubes).
"""
info = {"a_zero": a == 0, "disc_sign": 0, "domain_clipped": False}
def domain_ok(root: Fraction) -> bool:
if R0 is None:
return True
return (R0 - (rr if rr is not None else Fraction(0)) * root) > 0
if c < 0:
return "INSIDE", None, info
if c == 0:
if b < 0:
return "ENTER", Decimal(0), info
if b == 0:
if a < 0:
return "ENTER", Decimal(0), info
return "MISS", None, info
if a < 0:
root = -b / a
if root > 0 and domain_ok(root):
return "ENTER", frac_to_dec(root, prec), info
if root > 0:
info["domain_clipped"] = True
return "MISS", None, info
return "MISS", None, info
if a == 0:
if b < 0:
root = -c / b
if root > 0:
if domain_ok(root):
return "ENTER", frac_to_dec(root, prec), info
info["domain_clipped"] = True
return "MISS", None, info
disc = b * b - 4 * a * c
info["disc_sign"] = (disc > 0) - (disc < 0)
if disc < 0:
return "MISS", None, info
if disc == 0:
return "MISS", None, info # tangency is not a crossing
with localcontext() as ctx:
ctx.prec = prec
sd = frac_to_dec(disc, prec).sqrt()
ad = frac_to_dec(a, prec)
bd = frac_to_dec(b, prec)
r1 = (-bd - sd) / (2 * ad)
r2 = (-bd + sd) / (2 * ad)
pos = sorted(r for r in (r1, r2) if r > 0)
for r in pos:
if 2 * ad * r + bd < 0: # inward (outside -> inside) slope
rfr = _dec_to_frac_snapshot(r)
if domain_ok(rfr):
return "ENTER", r, info
info["domain_clipped"] = True
return "MISS", None, info
def _dec_to_frac_snapshot(d: Decimal) -> Fraction:
return Fraction(d)
def coeffs(x, c, w, v, R0, rr):
d = [x[i] - c[i] for i in range(3)]
q = [w[i] + v[i] for i in range(3)]
qq = sum(q[i] * q[i] for i in range(3))
dq = sum(d[i] * q[i] for i in range(3))
dd = sum(d[i] * d[i] for i in range(3))
a = qq - rr * rr
b = 2 * (dq + R0 * rr)
cq = dd - R0 * R0
return a, b, cq, qq
def load_cases(path):
data = json.loads(open(path).read())
kernel = {}
for c in data["kernel"]:
kernel[c["id"]] = {
"category": c["category"],
"x": [parse_hex(z) for z in c["x"]],
"c": [parse_hex(z) for z in c["c"]],
"w": [parse_hex(z) for z in c["w"]],
"v": [parse_hex(z) for z in c["v"]],
"R0": parse_hex(c["R0"]),
"rr": parse_hex(c["rr"]),
}
route = {c["id"]: c for c in data["route"]}
return kernel, route
def kernel_reference(case, prec):
a, b, cq, qq = coeffs(case["x"], case["c"], case["w"], case["v"],
case["R0"], case["rr"])
status, root, info = classify(a, b, cq, prec, case["R0"], case["rr"])
dd = sum(xi * xi for xi in
(case["x"][i] - case["c"][i] for i in range(3)))
return {
"status": status,
"root": root,
"a": a,
"b": b,
"c": cq,
"qq": qq,
"dd": dd,
"R0": case["R0"],
"rr": case["rr"],
"a_zero": info["a_zero"],
"disc_sign": info["disc_sign"],
"domain_clipped": info["domain_clipped"],
}
def ulp_dec(cand: float) -> Decimal:
return frac_to_dec(Fraction(math.ulp(cand)))
def dec_of_float(f: float) -> Decimal:
return frac_to_dec(Fraction(f), 300)
def poly_resid(a: Fraction, b: Fraction, c: Fraction, s: Fraction) -> Fraction:
return a * s * s + b * s + c
def poly_scale(a: Fraction, b: Fraction, c: Fraction, s: Fraction) -> Fraction:
return abs(a) * s * s + abs(b) * s + abs(c)
def analyze_kernel_variant(rows, refs, prec):
"""Return per-case records for one variant's kernel CSV."""
out = []
for r in rows:
cid = int(r["id"])
ref = refs[cid]
status = int(r["status"])
cand = float.fromhex(r["sigma"]) if r["sigma"] not in ("", "nan") else float("nan")
cand_fr = parse_hex(r["sigma"]) if r["sigma"] not in ("", "nan") else None
ak = parse_hex(r["a"])
bk = parse_hex(r["b"])
ck = parse_hex(r["c"])
rec = {
"id": cid,
"category": r["category"],
"variant_status": status,
"ref_status": ref["status"],
"a_zero_ref": ref["a_zero"],
"a_zero_kernel": ak == 0,
"a_sign_ref": _sign(ref["a"]),
"a_sign_kernel": _sign(ak),
"disc_sign_ref": ref["disc_sign"],
"late": False,
"near_linear": False,
"near_boundary": False,
"ill_conditioned": False,
"domain_clipped": ref["domain_clipped"],
"root_err_ulps": None,
"root_rel_err": None,
"resid_kernel_rel": None,
"resid_ideal_rel": None,
"baseline_resid_rel": None,
}
# conditioning flags
if ref["status"] == "ENTER" and ref["root"] is not None:
s = ref["root"]
scale = max(abs(ref["R0"]), Fraction(1))
rec["late"] = bool(s > frac_to_dec(scale, prec) * (10 ** 6))
rec["near_linear"] = abs(ref["a"]) <= Fraction(1, 10**10) * max(
ref["qq"], ref["rr"] * ref["rr"], Fraction(1)
)
r0sq = ref["R0"] * ref["R0"]
dd = ref["dd"]
rec["near_boundary"] = bool(
dd + r0sq != 0
and abs(ref["c"]) <= Fraction(1, 10**8) * (dd + r0sq)
)
rec["ill_conditioned"] = bool(
rec["near_linear"] or rec["late"] or rec["near_boundary"]
or r["category"].startswith("growing_out")
or r["category"] == "shrinking"
)
# root-level comparison
if status == 1 and ref["status"] == "ENTER" and ref["root"] is not None and cand_fr is not None:
err = abs(frac_to_dec(cand_fr, prec) - ref["root"])
u = ulp_dec(cand)
if u > 0:
rec["root_err_ulps"] = float(err / u)
if ref["root"] != 0:
rec["root_rel_err"] = float(err / abs(ref["root"]))
rec["resid_kernel_rel"] = _rel(
poly_resid(ak, bk, ck, cand_fr), ak, bk, ck, cand_fr
)
rec["resid_ideal_rel"] = _rel(
poly_resid(ref["a"], ref["b"], ref["c"], cand_fr),
ref["a"], ref["b"], ref["c"], cand_fr,
)
try:
nearest = float(ref["root"])
nfr = Fraction(nearest)
rec["baseline_resid_rel"] = _rel(
poly_resid(ref["a"], ref["b"], ref["c"], nfr),
ref["a"], ref["b"], ref["c"], nfr,
)
except (OverflowError, ValueError):
rec["baseline_resid_rel"] = None
out.append(rec)
return out
def _sign(fr: Fraction) -> int:
return (fr > 0) - (fr < 0)
def _rel(resid: Fraction, a, b, c, s) -> float:
scale = poly_scale(a, b, c, s)
if scale == 0:
return 0.0
return float(abs(resid) / scale)
def route_reference(row, prec):
x = [parse_hex(row[f"canonx{i}"]) for i in range(3)]
w = [parse_hex(row[f"canonw{i}"]) for i in range(3)]
v = [parse_hex(row[f"v{i}"]) for i in range(3)]
center = [parse_hex(row[f"ct0{i}"]) for i in range(3)]
rr = parse_hex(row["rr"])
R0 = parse_hex(row["rt0"]) if row["rt0"] not in ("", "nan") else parse_hex(row["R0"])
a, b, cq, qq = coeffs(x, center, w, v, R0, rr)
status, root, info = classify(a, b, cq, prec, R0, rr)
return status, root, a, b, cq, qq, info["domain_clipped"]
STATUS_NAME = {-1: "INVALID", 0: "MISS", 1: "ENTRY", 2: "UNCERTAIN"}
KIND_NAME = {
0: "INSIDE",
1: "ENTRY",
2: "ESCAPED",
3: "TIME_RANGE_EXHAUSTED",
4: "INVALID",
}
def analyze_route_variant(rows, prec, dbl_eps=2.220446049250313e-16):
out = []
for r in rows:
(ref_status, ref_root, a, b, cq, qq,
ref_domain_clipped) = route_reference(r, prec)
status = int(r["status"])
kind = int(r["kind"])
F = _maybe_dec(r["F"])
tol = _maybe_dec(r["tol"])
kern_ok = r.get("kern_ok", "0") == "1"
kern_status = int(r["kern_status"]) if kern_ok else None
rec = {
"id": int(r["id"]),
"category": r["category"],
"ref_status": ref_status,
"ref_domain_clipped": ref_domain_clipped,
"status": status,
"kind": kind,
"kind_name": KIND_NAME.get(kind, "?"),
"failure_reason": int(r["failure_reason"]),
"fallback": int(r["fallback_evals"]),
"pi_match": int(r["pi_match"]),
"lcam_match": int(r["lcam_match"]),
"kern_status": kern_status if kern_ok else "",
"kern_sigma": r.get("kern_sigma", ""),
"F_over_tol": None,
"inside_mismatch": False,
"false_miss": False,
"false_candidate": False,
"unconfirmed": kind == 4 and r["reason_name"] == "ENTRY_UNCONFIRMED",
"kernel_false_miss": False,
"kernel_false_miss_escaped": False,
}
if ref_status == "INSIDE":
if kind != 0:
rec["inside_mismatch"] = True
elif ref_status == "ENTER":
if kind == 2:
rec["false_miss"] = True
elif ref_status == "MISS":
if kind == 1:
rec["false_candidate"] = True
if ref_status == "ENTER" and kern_ok and kern_status == 0:
rec["kernel_false_miss"] = True
if kind == 2:
rec["kernel_false_miss_escaped"] = True
if F is not None and tol is not None and tol > 0:
rec["F_over_tol"] = float(F / tol)
out.append(rec)
return out
def _maybe_dec(s):
if s in ("", "nan"):
return None
low = s.lower()
if "inf" in low:
return None
return hex_to_dec(s, 300)
def summarize_kernel(records_by_variant, refs):
"""Aggregate counts, ULP distributions, and common-ENTRY intersections."""
variants = list(records_by_variant.keys())
by_id = {v: {} for v in variants}
for v in variants:
for rec in records_by_variant[v]:
by_id[v][rec["id"]] = rec
ids = sorted(refs.keys())
summary = {"variants": {}}
for v in variants:
recs = by_id[v]
counts = {
"enter": 0, "miss": 0, "uncertain": 0, "false_miss": 0,
"false_uncertain": 0, "false_candidate": 0,
"a_zero_ref": 0, "a_zero_kernel": 0, "a_sign_mismatch": 0,
"a_zero_collapse": 0, "a_spurious_nonzero": 0,
"domain_clipped_candidate": 0,
"ill_conditioned_enter": 0,
"near_boundary_enter": 0,
"domain_clipped_cases": 0,
"late_ref_enter": 0,
}
ulps = []
rels = []
for cid in ids:
rec = recs[cid]
st = rec["variant_status"]
if st == 1:
counts["enter"] += 1
elif st == 0:
counts["miss"] += 1
elif st == 2:
counts["uncertain"] += 1
if rec["ref_status"] == "ENTER" and st == 0:
counts["false_miss"] += 1
if rec["ref_status"] == "ENTER" and st == 2:
counts["false_uncertain"] += 1
if rec["ref_status"] == "MISS" and st == 1:
if rec["domain_clipped"]:
counts["domain_clipped_candidate"] += 1
else:
counts["false_candidate"] += 1
if rec["a_zero_ref"]:
counts["a_zero_ref"] += 1
if rec["a_zero_kernel"]:
counts["a_zero_kernel"] += 1
if not rec["a_zero_ref"] and rec["a_zero_kernel"]:
counts["a_zero_collapse"] += 1
if rec["a_zero_ref"] and not rec["a_zero_kernel"]:
counts["a_spurious_nonzero"] += 1
if rec["ill_conditioned"] and rec["ref_status"] == "ENTER":
counts["ill_conditioned_enter"] += 1
if rec["near_boundary"] and rec["ref_status"] == "ENTER":
counts["near_boundary_enter"] += 1
if rec["domain_clipped"]:
counts["domain_clipped_cases"] += 1
if (rec["a_sign_ref"] != 0 and rec["a_sign_kernel"] != 0
and rec["a_sign_ref"] != rec["a_sign_kernel"]):
counts["a_sign_mismatch"] += 1
if rec["late"]:
counts["late_ref_enter"] += 1
if rec["root_err_ulps"] is not None:
ulps.append(rec["root_err_ulps"])
if rec["root_rel_err"] is not None:
rels.append(rec["root_rel_err"])
summary["variants"][v] = {
"counts": counts,
"root_ulp_all_ref_enter": _dist(ulps),
"root_rel_all_ref_enter": _dist(rels),
}
# Common reference-ENTER subset that every variant solved as ENTRY.
common = []
for cid in ids:
if refs[cid]["status"] != "ENTER":
continue
if all(by_id[v][cid]["variant_status"] == 1 for v in variants):
common.append(cid)
summary["common_enter_ids"] = len(common)
for v in variants:
vals = []
vals_nonlate = []
rels_nonlate = []
for cid in common:
rec = by_id[v][cid]
e = rec["root_err_ulps"]
if e is not None:
vals.append(e)
if not rec["ill_conditioned"]:
vals_nonlate.append(e)
if rec["root_rel_err"] is not None:
rels_nonlate.append(rec["root_rel_err"])
summary["variants"][v]["root_ulp_common_enter"] = _dist(vals)
summary["variants"][v]["root_ulp_common_enter_wellcond"] = _dist(vals_nonlate)
summary["variants"][v]["root_rel_common_enter_wellcond"] = _dist(rels_nonlate)
return summary
def _dist(vals):
if not vals:
return {"n": 0}
vs = sorted(vals)
n = len(vs)
def pct(p):
idx = min(n - 1, max(0, int(math.ceil(p * n)) - 1))
return vs[idx]
return {
"n": n,
"min": vs[0],
"median": pct(0.5),
"p95": pct(0.95),
"max": vs[-1],
}
def summarize_route(records_by_variant):
summary = {"variants": {}}
for v, recs in records_by_variant.items():
counts = {
"inside_ok": 0, "inside_mismatch": 0, "false_miss": 0,
"false_candidate": 0, "unconfirmed": 0, "fallback_used": 0,
"accepted_entry": 0, "F_over_tol_gt1": 0, "pi_mismatch": 0,
"lcam_mismatch": 0, "kernel_false_miss": 0,
"kernel_false_miss_escaped": 0, "ref_domain_clipped": 0,
}
fmax = 0.0
by_cat = {}
for rec in recs:
cat = by_cat.setdefault(rec["category"], {"cases": 0, "fallback": 0,
"entry": 0, "escaped": 0})
cat["cases"] += 1
if rec["kind"] == 0 and rec["ref_status"] == "INSIDE":
counts["inside_ok"] += 1
if rec["inside_mismatch"]:
counts["inside_mismatch"] += 1
if rec["false_miss"]:
counts["false_miss"] += 1
if rec["false_candidate"]:
counts["false_candidate"] += 1
if rec["unconfirmed"]:
counts["unconfirmed"] += 1
if rec["kernel_false_miss"]:
counts["kernel_false_miss"] += 1
if rec["kernel_false_miss_escaped"]:
counts["kernel_false_miss_escaped"] += 1
if rec["ref_domain_clipped"]:
counts["ref_domain_clipped"] += 1
if rec["fallback"] > 0:
counts["fallback_used"] += 1
cat["fallback"] += 1
if rec["kind"] == 1:
counts["accepted_entry"] += 1
cat["entry"] += 1
if rec["F_over_tol"] is not None:
fmax = max(fmax, rec["F_over_tol"])
if rec["F_over_tol"] > 1.0:
counts["F_over_tol_gt1"] += 1
if rec["kind"] == 2:
cat["escaped"] += 1
if not rec["pi_match"]:
counts["pi_mismatch"] += 1
if not rec["lcam_match"]:
counts["lcam_mismatch"] += 1
summary["variants"][v] = {"counts": counts, "max_F_over_tol": fmax,
"by_category": by_cat}
return summary
def precision_consistency(cases, ids, prec_a, prec_b):
"""Compare reference classification and nearest-double root at two Decimal
precisions. Coefficients/discriminant are exact rationals, so only the
square-root precision can differ. Returns a list of mismatches."""
mismatches = []
for cid in sorted(ids):
ra = kernel_reference(cases[cid], prec_a)
rb = kernel_reference(cases[cid], prec_b)
reason = None
if ra["status"] != rb["status"]:
reason = "status"
elif ra["disc_sign"] != rb["disc_sign"]:
reason = "disc_sign"
elif ra["status"] == "ENTER":
try:
fa = float(ra["root"])
fb = float(rb["root"])
except (OverflowError, ValueError):
reason = "root_unrepresentable"
else:
if not (fa == fb or (math.isnan(fa) and math.isnan(fb))):
reason = "nearest_double_root"
if reason is not None:
mismatches.append({"id": cid, "reason": reason,
"status_a": ra["status"], "status_b": rb["status"]})
return mismatches
+458
View File
@@ -0,0 +1,458 @@
#!/usr/bin/env python3
"""End-to-end driver for the quadratic precision/performance benchmark.
Self-contained: reads the current working-tree ``src/asymptotic.c``, generates
and builds four variants, runs the accuracy probe, evaluates the exact-decimal
reference, and times the kernel and the public pre-route. All raw artifacts
are written under ``--output-dir`` (default /tmp/opencode/quadratic-comparison).
No production source, Makefile or git state is modified.
"""
from __future__ import annotations
import argparse
import csv
import json
import os
import re
import subprocess
import sys
# Do not leave __pycache__ inside the repository benchmark directory: the
# imported local modules (build/cases/oracle) must not be cached here.
sys.dont_write_bytecode = True
from pathlib import Path
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
import build as buildmod # noqa: E402
import cases as casesmod # noqa: E402
import oracle # noqa: E402
VARIANTS = list(buildmod.VARIANTS)
def run_cmd(cmd, log_path: Path, env=None):
with open(log_path, "a") as fh:
fh.write("$ " + " ".join(str(c) for c in cmd) + "\n")
proc = subprocess.run(cmd, capture_output=True, text=True, check=False, env=env)
with open(log_path, "a") as fh:
if proc.stdout:
fh.write(proc.stdout)
if proc.stderr:
fh.write(proc.stderr)
fh.write(f"exit={proc.returncode}\n")
return proc
def read_csv(path: Path):
with open(path, newline="") as fh:
return list(csv.DictReader(fh))
def run_timing(cmd, outp: Path, log_path: Path):
# Never ingest output left by a previous invocation, even after a failure.
outp.unlink(missing_ok=True)
proc = run_cmd(cmd, log_path)
if proc.returncode != 0 or not outp.is_file():
raise RuntimeError(f"timing failed or produced no fresh output: {log_path}")
return json.loads(outp.read_text())
def write_metrics_csv(path: Path, records, columns):
with open(path, "w", newline="") as fh:
w = csv.DictWriter(fh, fieldnames=columns, extrasaction="ignore")
w.writeheader()
for r in records:
w.writerow(r)
def write_curated(path: Path, rows, columns):
with open(path, "w", newline="") as fh:
w = csv.DictWriter(fh, fieldnames=columns, extrasaction="ignore")
w.writeheader()
for r in rows:
w.writerow(r)
def build_curated_kernel(kernel_records):
ref_ids = [r["id"] for r in kernel_records[VARIANTS[0]] if r["id"] < 1000]
by = {v: {r["id"]: r for r in kernel_records[v]} for v in VARIANTS}
rows = []
for cid in sorted(ref_ids):
row = {"id": cid, "category": by[VARIANTS[0]][cid]["category"],
"ref": by[VARIANTS[0]][cid]["ref_status"]}
for v in VARIANTS:
row[f"{v}_status"] = by[v][cid]["variant_status"]
row[f"{v}_ulp"] = by[v][cid]["root_err_ulps"]
row[f"{v}_rel"] = by[v][cid]["root_rel_err"]
rows.append(row)
return rows
def build_curated_route(route_records):
ref_ids = [r["id"] for r in route_records[VARIANTS[0]] if r["id"] < 100]
by = {v: {r["id"]: r for r in route_records[v]} for v in VARIANTS}
rows = []
for cid in sorted(ref_ids):
row = {"id": cid, "category": by[VARIANTS[0]][cid]["category"],
"ref": by[VARIANTS[0]][cid]["ref_status"]}
for v in VARIANTS:
row[f"{v}_kind"] = by[v][cid]["kind"]
row[f"{v}_fallback"] = by[v][cid]["fallback"]
row[f"{v}_F_over_tol"] = by[v][cid]["F_over_tol"]
rows.append(row)
return rows
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--repo", default=str(HERE.parents[1]))
ap.add_argument("--output-dir", default="/tmp/opencode/quadratic-comparison")
ap.add_argument("--cc", default=os.environ.get("CC", "cc"))
ap.add_argument("--threads", type=int, default=4)
ap.add_argument("--precision", type=int, default=160)
ap.add_argument("--rounds", type=int, default=3)
ap.add_argument("--kernel-target-calls", type=int, default=10_000_000)
ap.add_argument("--route-target-calls", type=int, default=1_000_000)
ap.add_argument("--skip-build", action="store_true")
args = ap.parse_args()
repo = Path(args.repo).resolve()
out = Path(args.output_dir).resolve()
(out / "raw").mkdir(parents=True, exist_ok=True)
(out / "cases").mkdir(parents=True, exist_ok=True)
(out / "logs").mkdir(parents=True, exist_ok=True)
import time as _time
t_start = _time.time()
phase_times = {}
# ---------------------------------------------------------------- cases
t0 = _time.time()
kernel_cases, route_cases = casesmod.build()
casesmod.write_header(kernel_cases, route_cases, out / "cases" / "quadratic_cases.h")
casesmod.write_json(kernel_cases, route_cases, out / "cases" / "cases.json")
phase_times["case_gen"] = _time.time() - t0
print(f"cases: kernel={len(kernel_cases)} route={len(route_cases)}")
# ------------------------------------------------------------ environment
env_info = buildmod.environment(repo, args.cc, args.threads)
env_info["python_version"] = sys.version.split()[0]
env_info["invocation"] = " ".join([sys.executable, *sys.argv])
(out / "environment.json").write_text(json.dumps(env_info, indent=2) + "\n")
(out / "raw" / "invocation.txt").write_text(
" ".join([sys.executable, *sys.argv]) + "\n"
)
print(f"source sha256: {env_info['source_sha256'][:16]} cc: {env_info['cc_version']}")
# ---------------------------------------------------------------- build
t0 = _time.time()
if not args.skip_build:
info = buildmod.build_all(repo, out, args.cc, buildmod.BASE_FLAGS, args.threads)
exes = {k: Path(v) for k, v in info["executables"].items()}
else:
manifest, reasons = buildmod.verify_manifest(repo, out, buildmod.BASE_FLAGS, args.cc)
if reasons:
print("refusing --skip-build: existing build does not match the "
f"current source/flags/fixtures ({', '.join(reasons)}); "
"rerun without --skip-build", file=sys.stderr)
sys.exit(1)
exes = {k: Path(v) for k, v in manifest["executables"].items()}
phase_times["build"] = _time.time() - t0
for name, exe in exes.items():
if not exe.exists():
print(f"missing executable for {name}: {exe}", file=sys.stderr)
sys.exit(1)
# -------------------------------------------------------------- accuracy
t0 = _time.time()
env_log = out / "logs" / "probe_environment.log"
env_log.write_text("")
for name in VARIANTS:
kout = out / "raw" / f"kernel_{name}.csv"
rout = out / "raw" / f"route_{name}.csv"
proc = run_cmd(
[str(exes[name]), "accuracy", "--kernel-out", str(kout),
"--route-out", str(rout)],
out / "logs" / f"accuracy_{name}.log",
)
with open(env_log, "a") as fh:
fh.write(proc.stdout)
if proc.returncode != 0:
print(f"accuracy probe failed for {name}", file=sys.stderr)
sys.exit(1)
phase_times["accuracy"] = _time.time() - t0
# ---------------------------------------------------------------- oracle
t0 = _time.time()
kernel_ref_cases, route_cases_json = oracle.load_cases(out / "cases" / "cases.json")
refs = {
cid: oracle.kernel_reference(case, args.precision)
for cid, case in kernel_ref_cases.items()
}
kernel_records = {}
route_records = {}
for name in VARIANTS:
krows = read_csv(out / "raw" / f"kernel_{name}.csv")
kernel_records[name] = oracle.analyze_kernel_variant(krows, refs, args.precision)
rrows = read_csv(out / "raw" / f"route_{name}.csv")
route_records[name] = oracle.analyze_route_variant(rrows, args.precision)
kcols = ["id", "category", "ref_status", "variant_status", "root_err_ulps",
"root_rel_err",
"resid_kernel_rel", "resid_ideal_rel", "baseline_resid_rel",
"a_zero_ref", "a_zero_kernel", "a_sign_ref", "a_sign_kernel",
"disc_sign_ref", "near_linear", "near_boundary",
"ill_conditioned", "domain_clipped", "late"]
for name in VARIANTS:
write_metrics_csv(out / "raw" / f"kernel_metrics_{name}.csv",
kernel_records[name], kcols)
rcols = ["id", "category", "ref_status", "ref_domain_clipped", "status",
"kind", "kind_name", "failure_reason", "fallback", "pi_match",
"lcam_match", "kern_status", "kern_sigma", "F_over_tol",
"inside_mismatch", "false_miss", "false_candidate", "unconfirmed",
"kernel_false_miss", "kernel_false_miss_escaped"]
for name in VARIANTS:
write_metrics_csv(out / "raw" / f"route_metrics_{name}.csv",
route_records[name], rcols)
ksummary = oracle.summarize_kernel(kernel_records, refs)
rsummary = oracle.summarize_route(route_records)
consistency_ids = {
rec["id"] for rec in kernel_records[VARIANTS[0]]
if rec["id"] < 1000 or rec["near_linear"] or rec["late"]
or rec["near_boundary"] or rec["domain_clipped"]
}
precision_mismatches = oracle.precision_consistency(
kernel_ref_cases, consistency_ids, args.precision, 240
)
curated_kernel = build_curated_kernel(kernel_records)
curated_route = build_curated_route(route_records)
write_curated(out / "raw" / "curated_kernel.csv", curated_kernel,
["id", "category", "ref"] + [f"{v}_{f}" for v in VARIANTS
for f in ("status", "ulp", "rel")])
write_curated(out / "raw" / "curated_route.csv", curated_route,
["id", "category", "ref"] + [f"{v}_{f}" for v in VARIANTS
for f in ("kind", "fallback",
"F_over_tol")])
# ------------------------------------------------------------- hardware FMA
phase_times["oracle"] = _time.time() - t0
fma_info = verify_fma(out, args.threads)
# ---------------------------------------------------------------- timing
t0 = _time.time()
timings = timed_runs(exes, out, args, kernel_target_calls=args.kernel_target_calls,
route_target_calls=args.route_target_calls)
lin_target = max(100_000, args.kernel_target_calls // 20)
timings["linearity"] = linearity_check(exes, out, lin_target)
phase_times["timing"] = _time.time() - t0
# ---------------------------------------------------------------- summary
summary = {
"source_sha256": env_info["source_sha256"],
"flags": buildmod.BASE_FLAGS,
"precision": args.precision,
"precision_consistency": {
"ids_checked": len(consistency_ids),
"mismatches": precision_mismatches,
},
"threads_timing": args.threads,
"cases": {"kernel": len(kernel_cases), "route": len(route_cases)},
"kernel_accuracy": ksummary,
"route_accuracy": rsummary,
"curated_kernel": curated_kernel,
"fma": fma_info,
"timing": timings,
"phase_seconds": phase_times,
"total_seconds": _time.time() - t_start,
}
(out / "summary.json").write_text(json.dumps(summary, indent=2) + "\n")
print_summary(summary)
print(f"\nartifacts under {out}")
def verify_fma(out: Path, threads: int):
info = {"objects": {}}
for name in VARIANTS:
obj = out / "build" / f"probe_{name}.o"
if not obj.exists():
continue
proc = subprocess.run(["objdump", "-d", str(obj)], capture_output=True,
text=True, check=False)
n = sum(1 for line in proc.stdout.splitlines()
if "vfmadd" in line or "vfmsub" in line or "fmadd" in line)
nm = subprocess.run(["nm", "-u", str(obj)], capture_output=True,
text=True, check=False)
undef = sorted({tok for line in nm.stdout.splitlines()
for tok in line.split()
if tok.startswith("fma")})
kd = subprocess.run(["objdump", "-dr", str(obj)], capture_output=True,
text=True, check=False)
kb_lines = []
inside = False
for line in kd.stdout.splitlines():
if re.match(r"^[0-9a-f]+ <entry_solve", line):
inside = True
kb_lines.append(line)
continue
if inside:
if re.match(r"^[0-9a-f]+ <", line):
break
kb_lines.append(line)
kb = "\n".join(kb_lines)
x87 = sum(1 for line in kb.splitlines()
if re.search(r"\b(fld|fstp|fmul|fadd|fsub|fdiv|fcom)\b", line))
vfma = sum(1 for line in kb.splitlines()
if "vfmadd" in line or "vfmsub" in line)
calls_fmal = sum(1 for line in kb.splitlines() if "fmal" in line)
info["objects"][name] = {
"fma_instructions": n,
"lowered_hardware": n > 0,
"undefined_fma_symbols": undef,
"entry_solve_x87": x87,
"entry_solve_vfmadd": vfma,
"entry_solve_fmal_calls": calls_fmal,
}
print(f"arith verify {name}: fused={n} undef={undef} "
f"entry_solve[x87={x87} vfmadd={vfma} fmal_calls={calls_fmal}]")
(out / "raw" / "fma_verify.json").write_text(json.dumps(info, indent=2) + "\n")
return info
def linearity_check(exes, out: Path, target_calls: int):
"""Confirm the microbench time scales with the call count (no hoisting)."""
res = {}
for name in ("ld_fma", "double_fma"):
secs = []
for mult in (1, 2):
outp = out / "raw" / f"linearity_{name}_{mult}.json"
d = run_timing([str(exes[name]), "microbench", "--out", str(outp),
"--target-calls", str(target_calls * mult)],
outp, out / "logs" / f"linearity_{name}_{mult}.log")
secs.append(d["seconds"])
res[name] = {"t1": secs[0], "t2": secs[1],
"ratio": secs[1] / secs[0] if secs[0] > 0 else None}
return res
def timed_runs(exes, out: Path, args, kernel_target_calls, route_target_calls):
schedules = []
fwd = list(VARIANTS)
schedules.append(fwd)
schedules.append(list(reversed(fwd)))
for i in range(2, args.rounds):
schedules.append(fwd if i % 2 == 0 else list(reversed(fwd)))
schedules = schedules[: args.rounds]
results = {"microbench": [], "routebench": []}
def order(seq):
return [exes[n] for n in seq]
for rnd, seq in enumerate(schedules):
for name in seq:
outp = out / "raw" / f"microbench_{name}_r{rnd}.json"
d = run_timing([str(exes[name]), "microbench", "--out", str(outp),
"--target-calls", str(kernel_target_calls)],
outp, out / "logs" / f"microbench_{name}_r{rnd}.log")
d["round"] = rnd
results["microbench"].append(d)
for rnd, seq in enumerate(schedules):
for name in seq:
outp = out / "raw" / f"routebench_{name}_r{rnd}.json"
d = run_timing([str(exes[name]), "routebench", "--out", str(outp),
"--target-calls", str(route_target_calls),
"--threads", str(args.threads), "--max-seconds", "20"],
outp, out / "logs" / f"routebench_{name}_r{rnd}.log")
d["round"] = rnd
results["routebench"].append(d)
return results
def print_summary(summary):
print("\n=== kernel accuracy (per variant) ===")
print(f"{'variant':<18}{'ENTER':>7}{'MISS':>7}{'UNC':>6}{'falseMiss':>10}"
f"{'falseCand':>10}{'domClipCand':>12}{'a0ref':>7}{'a0k':>6}{'asign':>6}"
f"{'illcond':>8}{'nearBnd':>8}{'medULP':>10}{'p95ULP':>11}{'maxULP':>11}")
for v, s in summary["kernel_accuracy"]["variants"].items():
c = s["counts"]
d = s["root_ulp_all_ref_enter"]
print(f"{v:<18}{c['enter']:>7}{c['miss']:>7}{c['uncertain']:>6}"
f"{c['false_miss']:>10}{c['false_candidate']:>10}"
f"{c['domain_clipped_candidate']:>12}"
f"{c['a_zero_ref']:>7}{c['a_zero_kernel']:>6}"
f"{c['a_sign_mismatch']:>6}{c['ill_conditioned_enter']:>8}"
f"{c['near_boundary_enter']:>8}"
f"{d.get('median', float('nan')):>10.4g}{d.get('p95', float('nan')):>11.4g}"
f"{d.get('max', float('nan')):>11.4g}")
print("well-conditioned common-ENTER ULP (excludes late / near-linear /"
" growing-out / shrinking):")
for v, s in summary["kernel_accuracy"]["variants"].items():
d = s["root_ulp_common_enter_wellcond"]
r = s["root_rel_common_enter_wellcond"]
print(f" {v:<18}n={d.get('n',0):>5} median={d.get('median', float('nan')):>9.3g}"
f" p95={d.get('p95', float('nan')):>9.3g}"
f" max={d.get('max', float('nan')):>9.3g}"
f" |rel err median={r.get('median', float('nan')):>9.3g}"
f" p95={r.get('p95', float('nan')):>9.3g}")
print(f"common reference-ENTER ids solved ENTRY by all variants: "
f"{summary['kernel_accuracy']['common_enter_ids']}")
print("\n=== curated kernel cases (status/ULP) ===")
print(f"{'id':>4} {'category':<20} {'ref':<6} "
+ " ".join(f"{v:>16}" for v in VARIANTS))
for row in summary.get("curated_kernel", []):
cells = []
for v in VARIANTS:
st = row.get(f"{v}_status")
u = row.get(f"{v}_ulp")
cells.append(f"{st}/{float(u):.3g}" if u not in (None, "") else f"{st}/-")
print(f"{row['id']:>4} {row['category']:<20} {row.get('ref',''):<6} "
+ " ".join(f"{c:>16}" for c in cells))
print("\n=== route accuracy (per variant) ===")
print(f"{'variant':<18}{'insideOK':>9}{'insideBad':>10}{'falseMiss':>10}"
f"{'falseCand':>10}{'unconf':>8}{'kernFM':>8}{'kernFMesc':>10}"
f"{'fallback':>9}{'F>tol':>7}{'maxF/tol':>10}")
for v, s in summary["route_accuracy"]["variants"].items():
c = s["counts"]
print(f"{v:<18}{c['inside_ok']:>9}{c['inside_mismatch']:>10}"
f"{c['false_miss']:>10}{c['false_candidate']:>10}"
f"{c['unconfirmed']:>8}{c['kernel_false_miss']:>8}"
f"{c['kernel_false_miss_escaped']:>10}"
f"{c['fallback_used']:>9}"
f"{c['F_over_tol_gt1']:>7}{s['max_F_over_tol']:>10.3g}")
pc = summary.get("precision_consistency", {})
print(f"reference precision {summary['precision']} vs 240: checked "
f"{pc.get('ids_checked')} curated/near-linear/late ids, "
f"mismatches={len(pc.get('mismatches', []))}")
print("\nroute fallback / entry / escaped by category "
"(ld_plain | ld_fma | double_fma):")
ra = summary["route_accuracy"]["variants"]
cats = sorted({c for s in ra.values() for c in s.get("by_category", {})})
for cat in cats:
cells = []
for v in ("ld_plain", "ld_fma", "double_fma"):
d = ra[v].get("by_category", {}).get(cat, {})
cells.append(f"fb={d.get('fallback',0)} e={d.get('entry',0)} "
f"x={d.get('escaped',0)}")
print(f" {cat:<20} " + " | ".join(cells))
print("\n=== timing (median ns/call over rounds) ===")
for mode in ("microbench", "routebench"):
per = {}
for rec in summary["timing"][mode]:
per.setdefault(rec["variant"], []).append(rec["ns_per_call"])
for v, vals in per.items():
vals.sort()
med = vals[len(vals) // 2]
print(f"{mode:<12}{v:<18}median={med:>10.2f} ns/call "
f"rounds={len(vals)}")
lin = summary["timing"].get("linearity", {})
for v, d in lin.items():
print(f"linearity {v:<18}t1={d['t1']:.4f}s t2={d['t2']:.4f}s "
f"ratio={d['ratio']:.3f} (expect ~2)")
if __name__ == "__main__":
main()