`GoldenSearch` returns wrong results for any bracket not straddling zero
Nobody has claimed this yet.
Assessment
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Newbie friendliness
- 82/100
Research direction
Start in optimistix/_solver/golden.py at GoldenSearch.init(), then run the minimal reproduction from the issue with a bracket that does not straddle zero. Confirm the initial midpoint stays within the supplied bracket and that the existing solver converges successfully for both shifted and zero-centered brackets.
Written by the indexing model from the issue text.
Description
Summary
optimistix.GoldenSearch computes its initial midpoint incorrectly in
GoldenSearch.init(). The bug is masked whenever the search bracket
[lower, upper] straddles zero (lower < 0 < upper), which is why it's easy
to miss in casual testing, but it produces wrong results — often
outside the bracket — for any bracket that doesn't straddle zero
(e.g. [99, 101], or any bracket from a physically-motivated problem where
the variable of interest isn't centered near 0).
Minimal reproduction
import jax.numpy as jnp
import optimistix as optx
def f(x, args):
return (x - 100.0) ** 2 # true minimum at x=100
solver = optx.GoldenSearch(rtol=1e-4, atol=1e-4)
# Bracket [99, 101] -- does NOT straddle zero
sol = optx.minimise(
f, solver, y0=jnp.array(100.0), args=None,
options={"lower": jnp.array(99.0), "upper": jnp.array(101.0)},
max_steps=64, throw=False,
)
print(sol.value, sol.result)
# -6286.344418537492 RESULTS<max_steps reached ...>
# Expected: something close to 100.0, well inside [99, 101].
# Same function, bracket [-1, 1] shifted so the minimum sits at 0 instead
def g(x, args):
return x ** 2
sol2 = optx.minimise(
g, solver, y0=jnp.array(0.0), args=None,
options={"lower": jnp.array(-1.0), "upper": jnp.array(1.0)},
max_steps=64, throw=False,
)
print(sol2.value, sol2.result)
# 0.0001322139224121388 RESULTS<successful>
# Converges correctly -- only difference is the bracket straddles zero.
Root cause
In optimistix/_solver/golden.py, GoldenSearch.init():
golden_ratio = (1 + math.sqrt(5)) / 2
middle = (upper - lower) / (golden_ratio + 1)
middle is used throughout the rest of the solver (step(), terminate())
as an absolute coordinate within [lower, upper] — it's passed directly
to fn(middle, args) on the very next line, and later compared against y_
(also an absolute coordinate) via is_lower = y_ < state.middle.
But (upper - lower) / (golden_ratio + 1) is only the offset from lower
— the width of the shorter of the two golden-ratio segments — not a point
within [lower, upper]. The lower + term is missing:
middle = lower + (upper - lower) / (golden_ratio + 1)
For a bracket that happens to straddle zero symmetrically (e.g. [-1, 1]),
the buggy value and the correct value are both coincidentally inside the
bracket (0.764 vs. the correct -0.236 for [-1, 1]), so the algorithm
still converges — just to a different, "wrong" first midpoint than intended,
which happens not to matter because both are still valid interior points to
start bisecting from. For any bracket not centered near zero, the buggy
middle lands outside the bracket entirely (e.g. for [99, 101]:
(101-99)/2.618 ≈ 0.764, nowhere near [99, 101]), and every subsequent
step() compounds the error, since step()'s y_ = state.lower + (state.upper - state.middle) uses this already-wrong middle.
Suggested fix
golden_ratio = (1 + math.sqrt(5)) / 2
- middle = (upper - lower) / (golden_ratio + 1)
+ middle = lower + (upper - lower) / (golden_ratio + 1)
One line. Verified locally (monkeypatching just this line) that it fixes
both the minimal repro above and a real, non-toy physics optimization we
were using GoldenSearch for for (bracket ~[-0.211, -0.201], several
independent test points) — all previously-divergent cases converged
correctly in 8-10 steps each after the fix.
Environment
optimistix: 0.1.0jax: 0.10.2equinox: 0.13.8python: 3.13.14
- Dominant language
- Python
- Stars
- 623
- Forks
- 54
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from patrick-kidger/optimistix
-
question
Difficulty 4/5 3-5 days Newbie friendliness 48/100
patrick-kidger/optimistix#231 · 1 comment ·
-
Difficulty 4/5 3-5 days Newbie friendliness 35/100
patrick-kidger/optimistix#229 · 3 comments ·
-
Difficulty 5/5 Over a week Newbie friendliness 20/100
patrick-kidger/optimistix#223 · 1 comment ·
-
question
Difficulty 5/5 Over a week Newbie friendliness 25/100
patrick-kidger/optimistix#222 · 5 comments ·
-
question
Difficulty 5/5 Over a week Newbie friendliness 25/100
patrick-kidger/optimistix#208 · 6 comments ·
All issues in patrick-kidger/optimistix
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
bancolombia/sentinel#23 ·
-
test md OpenCI
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
-
integration:quickjs org:external priority:backlog topic:code-interpreter topic:middleware type:feature
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
langchain-ai/deepagents#6450 ·
-
bug client
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100