Impact, every sweep regenerated: the paper's key configurations are unchanged to the evaluation (proposed 2806/3220, credit 3568/3020, Chandrupatla, TOMS 748, Brent, bisection); 46 stated numbers move (ITP CHA 2857 -> 2896, ITP oracle 2611 -> 2647, record CHA figures by 14-18, the grid means by ~1%) and one finding weakens: the measured-order exponent of section 7.1 was cheaper in 29 of 60 far cells and 7 of 7 safeguards; with the fix 12 of 61 and 6 of 7, and on the wide-bracket set it now loses in every safeguard and drops 5 problems. PREREG G1-G3 outcome on the fixed code: G2 (repeating step pattern, period from the data) PASSES Y1-Y4 and is ACCEPTED by the stop rule - proposed rule APS 2806 -> 2782 (below Chandrupatla's 2787), CHA 3220 -> 3169, both 6026 -> 5951, per problem vs Chandrupatla on APS 95/20/38 p = 8e-7. G1 fails Y1/Y2 (its Y3 miss was the sign bug), G3 fails Y2, A fails Y2. Regression 887 pass; the two red tests are the paper-number check (awaiting the paper update) and test_blend (fixed since, copy in stage16). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
161 lines
6.2 KiB
Python
161 lines
6.2 KiB
Python
"""Stage 0b - the PURE similarity idea, taken literally as a STEP-SIZE rule.
|
|
|
|
Valeri's formulation, word for word:
|
|
|
|
"in bisection the next point is chosen through half of the interval.
|
|
With similarity the next point is chosen proportionally to the value of
|
|
the similarity function (0 = not similar at all, 1 = identical) of the
|
|
result to the expectation (e.g. zero). So the step is LARGER when the
|
|
result is not similar to the expectation, and SMALLER when it is."
|
|
|
|
This is NOT the same object as Stage 0. Stage 0 asked "where between a and b
|
|
does the root sit" (an interpolation rule, which turned out to be regula
|
|
falsi identically). Here the rule is:
|
|
|
|
direction <- given by the sign (towards the side that brackets the root)
|
|
step <- (1 - S(f(x_n))) * (distance to the far end of the bracket)
|
|
|
|
with S a genuine [0,1] similarity function of a single value f(x) to the
|
|
target 0. Bisection is the special case S == 1/2 for every input, i.e.
|
|
"the similarity function carries no information".
|
|
|
|
The whole content of the method is therefore the similarity function itself.
|
|
The one thing S needs that a two-point rule did not is a SCALE: how large is
|
|
|f| = 3? Any constant here would be a hidden tuning parameter. Three
|
|
scale sources are compared, all taken from the run itself:
|
|
|
|
s0 the value at the starting point (fixed reference)
|
|
srun the largest |f| seen so far (growing reference)
|
|
slive the largest |f| at the current bracket ends (shrinking reference)
|
|
|
|
and the last one is the one that reproduces Stage 0 / regula falsi for the
|
|
linear S, which is exactly the collapse this stage is meant to expose.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from simroot.baselines import _opposite, _same_sign # 2026-09-21 fix
|
|
|
|
import math
|
|
|
|
from simroot.baselines import Counter, _Budget, bisection, brent
|
|
|
|
XTOL = 1e-12
|
|
MAXFEV = 200
|
|
|
|
# --------------------------------------------------------------------------
|
|
# genuine [0,1] similarity functions of a normalised residual z = f / scale
|
|
# S(0) = 1 (identical to the expectation), S(large) -> 0 (not similar at all)
|
|
# --------------------------------------------------------------------------
|
|
SIMS_01 = {
|
|
# linear ramp, saturating at the reference scale
|
|
"linear": lambda z: max(0.0, 1.0 - abs(z)),
|
|
# rational, never saturates
|
|
"rational": lambda z: 1.0 / (1.0 + abs(z)),
|
|
# rational in the square
|
|
"rat_sq": lambda z: 1.0 / (1.0 + z * z),
|
|
# exponential decay of the distance
|
|
"exp_abs": lambda z: math.exp(-abs(z)),
|
|
# the article's negative squared Euclidean distance, exponentiated to [0,1]
|
|
"exp_sq": lambda z: math.exp(-z * z),
|
|
# cosine similarity of (1, f) against (1, 0) - article eq. (4) on the
|
|
# value axis lifted to 2-D
|
|
"cosine": lambda z: 1.0 / math.sqrt(1.0 + z * z),
|
|
}
|
|
|
|
SCALES = ("s0", "srun", "slive")
|
|
|
|
|
|
def pure_similarity_step(f, a, b, xtol, maxfev, sim="rational", scale="s0"):
|
|
"""The literal step-size rule. Bracket kept only for safety and for
|
|
knowing which way to walk; the step LENGTH comes from similarity alone."""
|
|
S = SIMS_01[sim]
|
|
g = Counter(f, maxfev)
|
|
x = 0.5 * (a + b)
|
|
try:
|
|
fa, fb = g(a), g(b)
|
|
if fa == 0.0:
|
|
return a, g.n, True
|
|
if fb == 0.0:
|
|
return b, g.n, True
|
|
|
|
# start from the endpoint whose result is already more similar to 0
|
|
if abs(fa) <= abs(fb):
|
|
x, fx, far, ffar = a, fa, b, fb
|
|
else:
|
|
x, fx, far, ffar = b, fb, a, fa
|
|
|
|
s0 = abs(fx)
|
|
smax = max(abs(fa), abs(fb))
|
|
|
|
while abs(far - x) > xtol:
|
|
if scale == "s0":
|
|
sc = s0
|
|
elif scale == "srun":
|
|
sc = smax
|
|
else:
|
|
sc = max(abs(fx), abs(ffar))
|
|
if sc == 0.0:
|
|
return x, g.n, True
|
|
|
|
s = S(abs(fx) / sc) # 1 = the result already IS zero
|
|
step = (1.0 - s) * (far - x) # dissimilar -> long step
|
|
x_new = x + step
|
|
lo, hi = (x, far) if x < far else (far, x)
|
|
if not math.isfinite(x_new) or not (lo < x_new < hi):
|
|
x_new = 0.5 * (x + far)
|
|
|
|
fnew = g(x_new)
|
|
if fnew == 0.0:
|
|
return x_new, g.n, True
|
|
smax = max(smax, abs(fnew))
|
|
if _opposite(fx, fnew):
|
|
# the root is between the old point and the new one
|
|
far, ffar = x, fx
|
|
x, fx = x_new, fnew
|
|
return x, g.n, True
|
|
except _Budget:
|
|
return x, g.n, False
|
|
|
|
|
|
PROBLEMS = [
|
|
("x^2 - 2", lambda x: x * x - 2.0, 0.0, 2.0, math.sqrt(2.0)),
|
|
("cos(x) - x", lambda x: math.cos(x) - x, 0.0, 1.0, 0.7390851332151607),
|
|
("exp(x) - 2", lambda x: math.exp(x) - 2.0, 0.0, 2.0, math.log(2.0)),
|
|
("x^3 - x - 2", lambda x: x ** 3 - x - 2.0, 1.0, 2.0, 1.5213797068045676),
|
|
("sin(x) - 0.5", lambda x: math.sin(x) - 0.5, 0.0, 1.5, math.asin(0.5)),
|
|
("x^10 - 1 STALL", lambda x: x ** 10 - 1.0, 0.0, 1.3, 1.0),
|
|
("exp(-x)(x-1) FLAT", lambda x: math.exp(-x) * (x - 1.0), -1.0, 40.0, 1.0),
|
|
("atan(x)", lambda x: math.atan(x), -1.0, 3.0, 0.0),
|
|
]
|
|
|
|
|
|
def run():
|
|
methods = [("bisection (S=1/2)", lambda f, a, b: bisection(f, a, b, XTOL, MAXFEV)),
|
|
("brent [reference]", lambda f, a, b: brent(f, a, b, XTOL, MAXFEV))]
|
|
for sc in SCALES:
|
|
for s in SIMS_01:
|
|
methods.append((
|
|
f"pure.{s}.{sc}",
|
|
(lambda ss, scl: lambda f, a, b: pure_similarity_step(
|
|
f, a, b, XTOL, MAXFEV, sim=ss, scale=scl))(s, sc)))
|
|
|
|
width = max(len(n) for n, _ in methods) + 1
|
|
header = (f"{'method':{width}s}" +
|
|
"".join(f"{p[0][:13]:>15s}" for p in PROBLEMS) +
|
|
f"{'TOTAL':>8s}{'FAIL':>6s}")
|
|
print(header)
|
|
print("-" * len(header))
|
|
for name, fn in methods:
|
|
cells, total, fails = [], 0, 0
|
|
for _, f, a, b, root in PROBLEMS:
|
|
x, n, conv = fn(f, a, b)
|
|
ok = conv and abs(x - root) <= 1e-8
|
|
cells.append(f"{n:>15d}" if ok else f"{'FAIL(' + str(n) + ')':>15s}")
|
|
total += n
|
|
fails += 0 if ok else 1
|
|
print(f"{name:{width}s}" + "".join(cells) + f"{total:>8d}{fails:>6d}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|