117 lines
4.5 KiB
Python
117 lines
4.5 KiB
Python
"""PREREG_estimated_transform_2026-09-22 (control K3): Chandrupatla's method on
|
|
g = sign(f)|f|^(1/m_hat), with m_hat estimated during the run.
|
|
|
|
The solver is simroot.baselines.chandrupatla line for line (it matches scipy's
|
|
elementwise.find_root on all 199 problems); it keeps the TRUE values of f at its three points, so
|
|
signs, the best point and the stopping rule are those of f, and only the acceptance band's
|
|
(xi, phi) and the inverse quadratic interpolation read G(v) = sign(v)|v|^(1/m_hat), recomputed
|
|
from the stored f whenever m_hat changes - no extra evaluation. m_hat starts at 1 and is
|
|
replaced by an order read from the last three evaluations only when it lies on a branch of
|
|
section 6.1 (m >= 2 or 0 < m <= 2/3); there is no reset. With estimator="none" the method is
|
|
the original, bit for bit (tested).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
from .baselines import Counter, _Budget
|
|
from .pure import _analogy_order, _config3, _lagouanelle_raw
|
|
|
|
# "lag2": POSTHOC_lag_branch_2026-09-22 - Lagouanelle on the branch m >= 2 only (post hoc)
|
|
ESTIMATORS = ("none", "lag", "analogy", "lag2")
|
|
|
|
|
|
def _G(v, inv):
|
|
if inv == 1.0:
|
|
return v
|
|
if v == 0.0:
|
|
return 0.0
|
|
try:
|
|
return math.copysign(abs(v) ** inv, v)
|
|
except OverflowError:
|
|
# 1/m_hat > 1 on the fractional branch: a huge |f| overflows; the interpolation that
|
|
# reads it is then not finite and the step halves, as the method does for any such t
|
|
return math.copysign(math.inf, v)
|
|
|
|
|
|
def chandrupatla_transformed(f, a, b, xtol, maxfev, estimator="none", stats=None):
|
|
if estimator not in ESTIMATORS:
|
|
raise ValueError(f"unknown estimator {estimator!r}")
|
|
g = Counter(f, maxfev)
|
|
x1, x2 = b, a
|
|
f1 = f2 = 0.0
|
|
m_hat = 1.0
|
|
hist = [] # evaluations in order: (x, f)
|
|
configs = [] # analogy memory (estimator "analogy")
|
|
|
|
def observe(x, fx):
|
|
nonlocal m_hat
|
|
hist.append((x, fx))
|
|
if estimator == "none" or len(hist) < 3:
|
|
return
|
|
(xp2, fp2), (xp, fp), (xc, fc) = hist[-3], hist[-2], hist[-1]
|
|
if estimator in ("lag", "lag2"):
|
|
m = _lagouanelle_raw(xc, fc, xp, fp, xp2, fp2)
|
|
if estimator == "lag2" and m is not None and m < 2.0:
|
|
m = None # the fractional branch is not used (post hoc variant)
|
|
else:
|
|
cur = _config3(xc, fc, xp, fp, xp2, fp2)
|
|
m = _analogy_order(cur, configs, translation=True)
|
|
if cur is not None:
|
|
configs.append(cur)
|
|
if m is not None and (m >= 2.0 or 0.0 < m <= 2.0 / 3.0):
|
|
if stats is not None and m != m_hat:
|
|
stats["updates"] = stats.get("updates", 0) + 1
|
|
m_hat = m
|
|
if stats is not None:
|
|
stats["m_hat"] = m_hat # diagnostic only: the order last held
|
|
|
|
try:
|
|
f1 = g(x1)
|
|
observe(x1, f1)
|
|
f2 = g(x2)
|
|
observe(x2, f2)
|
|
if f1 == 0.0:
|
|
return x1, g.n, True
|
|
if f2 == 0.0:
|
|
return x2, g.n, True
|
|
x3, f3 = x2, f2
|
|
t = 0.5
|
|
while True:
|
|
x = x1 + t * (x2 - x1)
|
|
fx = g(x)
|
|
observe(x, fx)
|
|
if (fx > 0.0) == (f1 > 0.0):
|
|
x3, f3 = x1, f1
|
|
x1, f1 = x, fx
|
|
else:
|
|
x3, f3 = x2, f2
|
|
x2, f2 = x1, f1
|
|
x1, f1 = x, fx
|
|
xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2)
|
|
if fm == 0.0:
|
|
return xm, g.n, True
|
|
tol = 2.0 * 2.22e-16 * abs(xm) + 0.5 * xtol
|
|
span = abs(x2 - x1)
|
|
if span == 0.0:
|
|
return xm, g.n, True
|
|
tl = tol / span
|
|
if tl > 0.5:
|
|
return xm, g.n, True
|
|
inv = 0.0 if math.isinf(m_hat) else 1.0 / m_hat
|
|
G1, G2, G3 = _G(f1, inv), _G(f2, inv), _G(f3, inv)
|
|
t = 0.5
|
|
if x3 != x2 and G3 != G2:
|
|
xi = (x1 - x2) / (x3 - x2)
|
|
ph = (G1 - G2) / (G3 - G2)
|
|
if 0.0 < xi < 1.0 and 1.0 - math.sqrt(1.0 - xi) < ph < math.sqrt(xi):
|
|
d1, d2, d3 = G1 - G2, G1 - G3, G2 - G3
|
|
if d1 != 0.0 and d2 != 0.0 and d3 != 0.0:
|
|
t = (G1 / d1) * (G3 / (-d3)) + \
|
|
((x3 - x1) / (x2 - x1)) * (G1 / d2) * (G2 / d3)
|
|
if not math.isfinite(t):
|
|
t = 0.5
|
|
t = min(max(t, tl), 1.0 - tl)
|
|
except _Budget:
|
|
return (x1 if abs(f1) < abs(f2) else x2), g.n, False
|