logarithmic space mode: pure_root gains space='x'|'log' (default reproduces the published grid bit-for-bit), u = sign(x-c)*log1p(|x-c|/xtol) with c = the end of smaller |f| and s = xtol fixed by derivation; gates G1/G2' pinned by tests/test_log_space.py (7 green); sweep writes the log grid to its OWN sweep_log.json so the verified sweep_full.json is untouched

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-20 22:52:58 +01:00
co-authored by Claude Opus 5
parent bf221531ac
commit a9060b3dcc
5 changed files with 231 additions and 9 deletions
+62 -2
View File
@@ -118,6 +118,50 @@ PURE_SIMS = {
GUARDS = ("none", "bracket", "step", "both", "project", "safe", "credit")
SPACES = ("x", "log")
def _log_map(t, c, s):
"""u = sign(t - c) * log1p(|t - c| / s). An ADDITIVE step in u is a
MULTIPLICATIVE step in x, which is what the wide-bracket families need: the
search there must cross up to twelve decades, and an absolute step sized by
the local slope knows nothing about how many decades remain."""
d = t - c
return math.copysign(math.log1p(abs(d) / s), d)
def _log_unmap(u, c, s):
return c + math.copysign(s * (math.expm1(abs(u))), u)
def _log_space_step(S, x, fx, far, slope, c, s):
"""The same similarity rule, the same derived scale, read in u rather than x.
dx/du = s + |x - c|, so the slope in u is slope_x * (s + |x - c|); the scale
is that slope times the distance to the far end IN u, exactly as in x-space.
Returns the step in x (None when u-space cannot be read).
`s` is NOT a free unit: PREREG amendment 1 fixes s = xtol by derivation - the
only length in the problem that says "below this nothing is distinguishable",
supplied by the caller like a, b and f. With it u spans log(w/xtol) units,
the very measure the method's own guarantee 2*ceil(log2(w/xtol)) + 3 is
stated in, so the march is linear in the measure of the guarantee.
"""
u_x = _log_map(x, c, s)
u_far = _log_map(far, c, s)
du = u_far - u_x
if du == 0.0 or not math.isfinite(du):
return None
slope_u = slope * (s + abs(x - c))
sc_u = abs(slope_u) * abs(du)
if sc_u == 0.0 or not math.isfinite(sc_u):
return None
u_new = u_x + (1.0 - S(abs(fx) / sc_u)) * du
x_new = _log_unmap(u_new, c, s)
if not math.isfinite(x_new):
return None
return x_new - x
class _Safeguard:
"""Parameter-free worst-case protection. See the module docstring."""
@@ -369,7 +413,7 @@ def _local_order(x2, f2, x1, f1, x0, f0):
def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
guard="step", stats=None, budget_factor=2.0,
budget_extra=None, endowment=1.0):
budget_extra=None, endowment=1.0, space="x"):
"""The pure similarity root finder.
similarity : key into PURE_SIMS
@@ -421,6 +465,12 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
if scale not in ("derived", "derived3", "derived_rho", "derived3_rho",
"derived3_m", "start", "bracket"):
raise ValueError(f"unknown scale {scale!r}")
if space not in SPACES:
raise ValueError(f"unknown space {space!r}")
if space == "log" and scale not in ("derived", "derived3"):
# u-space needs a slope; with a chosen or bracket scale there is none to
# transform, and computing something anyway would hide that (PREREG §3a).
raise ValueError("space='log' requires scale in {'derived', 'derived3'}")
S = PURE_SIMS[similarity]
g = Counter(f, maxfev)
x = 0.5 * (a + b)
@@ -541,7 +591,17 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
if sc == 0.0 or not math.isfinite(sc):
x_new = 0.5 * (A + B)
else:
step = (1.0 - S(abs(fx) / sc)) * (far - x)
step = None
if space == "log":
# c = the bracket end with the smaller |f| (the side the
# root is nearer); s = xtol, fixed by derivation in
# PREREG amendment 1, never chosen. Falls back to the
# x-space step when u cannot be read, so the mode can
# only differ where it is defined.
c_near = A if abs(FA) <= abs(FB) else B
step = _log_space_step(S, x, fx, far, slope, c_near, xtol)
if step is None:
step = (1.0 - S(abs(fx) / sc)) * (far - x)
if sg.reject_step(step):
x_new = 0.5 * (A + B)
step = x_new - x