two more places for similarity: points='shape' (WHICH triple the model uses, argmin |phi-xi| over declared candidates — select, never weight) and direction='dissimilar' (the far end read by |f| rather than by distance); defaults bit-identical, P1-P5 pinned (7 green), inertness counters make an inert mode a failed EXPERIMENT rather than a pass; each grid writes to its own file
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+87
-6
@@ -121,6 +121,47 @@ GUARDS = ("none", "bracket", "step", "both", "project", "safe", "credit")
|
||||
SPACES = ("x", "log")
|
||||
|
||||
|
||||
POINT_RULES = ("recent", "shape")
|
||||
DIRECTIONS = ("distance", "dissimilar")
|
||||
|
||||
|
||||
def _shape_defect(x, fx, u, fu, v, fv):
|
||||
"""|phi - xi| for the triple, or None when it is degenerate.
|
||||
|
||||
xi = (x - u)/(v - u) is a ratio of POSITIONS, phi = (fx - fu)/(fv - fu) a ratio of
|
||||
VALUES; both are invariant under affine rescaling of x and of f. For a linear f they
|
||||
are equal, so their gap measures how unlike a linear configuration the triple is —
|
||||
the same dimensionless currency Chandrupatla's acceptance band is written in."""
|
||||
if v == u or fv == fu:
|
||||
return None
|
||||
xi = (x - u) / (v - u)
|
||||
ph = (fx - fu) / (fv - fu)
|
||||
if not (math.isfinite(xi) and math.isfinite(ph)):
|
||||
return None
|
||||
return abs(ph - xi)
|
||||
|
||||
|
||||
def _pick_triple(x, fx, x_prev, f_prev, x_prev2, f_prev2, A, FA, B, FB, far):
|
||||
"""SELECT the triple whose configuration is least unlike a linear one (PREREG
|
||||
2026-09-21, mode A). Candidates are declared, not searched: the recency triple, the
|
||||
recent point with the far bracket end, and the two proven bracket ends. Returns
|
||||
((xu, fu), (xv, fv)) or None when nothing is admissible."""
|
||||
far_f = FB if far == B else FA
|
||||
cands = (((x_prev, f_prev), (x_prev2, f_prev2)),
|
||||
((x_prev, f_prev), (far, far_f)),
|
||||
((A, FA), (B, FB)))
|
||||
best, best_d = None, None
|
||||
for u, v in cands:
|
||||
if u[0] is None or v[0] is None:
|
||||
continue
|
||||
d = _shape_defect(x, fx, u[0], u[1], v[0], v[1])
|
||||
if d is None or x == u[0] or x == v[0]:
|
||||
continue
|
||||
if best_d is None or d < best_d:
|
||||
best, best_d = (u, v), d
|
||||
return best
|
||||
|
||||
|
||||
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
|
||||
@@ -413,7 +454,8 @@ 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, space="x", prestep="none"):
|
||||
budget_extra=None, endowment=1.0, space="x", prestep="none",
|
||||
points="recent", direction="distance"):
|
||||
"""The pure similarity root finder.
|
||||
|
||||
similarity : key into PURE_SIMS
|
||||
@@ -469,6 +511,14 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
raise ValueError(f"unknown space {space!r}")
|
||||
if prestep not in ("none", "progress", "slope"):
|
||||
raise ValueError(f"unknown prestep {prestep!r}")
|
||||
if points not in POINT_RULES:
|
||||
raise ValueError(f"unknown points rule {points!r}")
|
||||
if direction not in DIRECTIONS:
|
||||
raise ValueError(f"unknown direction {direction!r}")
|
||||
if points == "shape" and scale not in ("derived3", "derived3_rho", "derived3_m"):
|
||||
# the triple only exists for the three-point scale; selecting one under a
|
||||
# two-point scale would change nothing and hide that
|
||||
raise ValueError("points='shape' requires a three-point scale (derived3*)")
|
||||
if prestep == "slope" and scale not in ("derived", "derived3"):
|
||||
# there is no slope to judge under a chosen or bracket scale; judging something
|
||||
# else would hide that, so the call fails instead
|
||||
@@ -501,11 +551,15 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
sg.credit = endowment
|
||||
forced = 0
|
||||
pre_refused = 0
|
||||
triple_changed = 0
|
||||
dir_changed = 0
|
||||
slope = 0.0
|
||||
slope_prev = None
|
||||
if stats is not None:
|
||||
stats['forced'] = 0
|
||||
stats['pre_refused'] = 0
|
||||
stats['triple_changed'] = 0
|
||||
stats['dir_changed'] = 0
|
||||
|
||||
while abs(B - A) > xtol:
|
||||
if sg.bisect_before_step(abs(B - A)):
|
||||
@@ -516,11 +570,38 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
sg.note_step(x_new - x)
|
||||
else:
|
||||
far = B if abs(B - x) >= abs(A - x) else A
|
||||
if direction == "dissimilar":
|
||||
# PREREG 2026-09-21, mode B: the far end read by SIMILARITY instead of
|
||||
# geometry — the end whose |f| is larger is the one LESS similar to zero,
|
||||
# i.e. the one the root is further from. Two values already in hand are
|
||||
# compared; no constant.
|
||||
far_sim = B if abs(FB) >= abs(FA) else A
|
||||
if far_sim != far:
|
||||
dir_changed += 1
|
||||
far = far_sim
|
||||
if scale in ("derived", "derived_rho"):
|
||||
slope = ((fx - f_prev) / (x - x_prev)) if x != x_prev else 0.0
|
||||
sc = abs(slope) * abs(far - x)
|
||||
elif scale in ("derived3", "derived3_rho", "derived3_m"):
|
||||
slope = _slope3(x, fx, x_prev, f_prev, x_prev2, f_prev2)
|
||||
if points == "shape":
|
||||
# PREREG 2026-09-21, mode A: which THREE points the model is built
|
||||
# on, chosen by the SHAPE of the configuration instead of by recency.
|
||||
# For a linear f the position ratio and the value ratio coincide, so
|
||||
# |phi - xi| is a dimensionless measure of how unlike a linear
|
||||
# configuration a triple is; the triple minimising it is SELECTED
|
||||
# (the corpus rule is select, never weight). argmin over the declared
|
||||
# candidates needs no threshold.
|
||||
pick = _pick_triple(x, fx, x_prev, f_prev, x_prev2, f_prev2,
|
||||
A, FA, B, FB, far)
|
||||
if pick is None:
|
||||
slope = _slope3(x, fx, x_prev, f_prev, x_prev2, f_prev2)
|
||||
else:
|
||||
(xu, fu), (xv, fv) = pick
|
||||
if xu != x_prev or xv != x_prev2:
|
||||
triple_changed += 1
|
||||
slope = _slope3(x, fx, xu, fu, xv, fv)
|
||||
else:
|
||||
slope = _slope3(x, fx, x_prev, f_prev, x_prev2, f_prev2)
|
||||
sc = abs(slope) * abs(far - x)
|
||||
if scale == "derived3_m":
|
||||
# REFUTED as an improvement - kept for reproducibility.
|
||||
@@ -665,7 +746,7 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
if x_new == x or x_new == x_prev:
|
||||
# the bracket can no longer be split in floating point
|
||||
if stats is not None:
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused; stats['triple_changed'] = triple_changed; stats['dir_changed'] = dir_changed
|
||||
return x_new, g.n, abs(B - A) <= xtol
|
||||
|
||||
w_before = abs(B - A)
|
||||
@@ -677,7 +758,7 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
x, fx = x_new, fnew
|
||||
if fnew == 0.0:
|
||||
if stats is not None:
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused; stats['triple_changed'] = triple_changed; stats['dir_changed'] = dir_changed
|
||||
return x_new, g.n, True
|
||||
if FA * fnew < 0.0:
|
||||
B, FB = x_new, fnew
|
||||
@@ -686,9 +767,9 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
if guard == "credit":
|
||||
sg.note_shrink(w_before, abs(B - A))
|
||||
if stats is not None:
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused; stats['triple_changed'] = triple_changed; stats['dir_changed'] = dir_changed
|
||||
return 0.5 * (A + B), g.n, True
|
||||
except _Budget:
|
||||
if stats is not None:
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused; stats['triple_changed'] = triple_changed; stats['dir_changed'] = dir_changed
|
||||
return x, g.n, False
|
||||
|
||||
Reference in New Issue
Block a user