Files
wyj 7c980c35aa 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.
2026-10-09 00:15:02 -04:00

442 lines
17 KiB
Python

#!/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]}")