(defn brent-min
"Find the minimum of the function f: R -> R in the interval [a,b] using Brent's
Method, described by Richard Brent in [Algorithms for Minimization without
Derivatives](https://books.google.com/books?id=AITCAgAAQBAJ&q=Brent%E2%80%99s#v=onepage&q=Parabolic&f=false).
Brent's method is a combination of a golden section search with a parabolic
interpolation step. Parabolic interpolation can go wild if the candidate point
is close to colinear with the search bounds, or of the points are too close
together.
Brent's method prevents this by applying an internal test that forces a golden
section step every so often. (If you want the details, see `parabola-valid?`
above.)
Supports the following optional keyword arguments:
`:callback` if supplied, the supplied fn will be invoked at each intermediate
point with the iteration count and the values of x and f(x) at each search
step.
`:relative-threshold` defaults to around 1.49e8, the sqrt of the machine
tolerance. You won't gain any benefit attempting to set the value less than
the default.
`:absolute-threshold` a smaller absolute threshold that applies when the
candidate minimum point is close to 0.
`:maxiter` Maximum number of iterations allowed for the minimizer. Defaults to
1000.
`:maxfun` Maximum number of times the function can be evaluated before
exiting. Defaults to `(inc maxiter)`.
"
([f a b] (brent-min f a b {}))
([f a b {:keys [relative-threshold
absolute-threshold
maxiter
maxfun
callback]
:or {relative-threshold (g/sqrt u/machine-epsilon)
absolute-threshold 1.0e-11
maxiter 1000
callback (constantly nil)}}]
(let [maxfun (or maxfun (inc maxiter))
[a b] [(min a b) (max a b)]
[f-counter f] (u/counted f)
xmid (* 0.5 (+ a b))
mid [xmid (f xmid)]]
(loop [
[a [xx fx :as x] b] [a mid b]
[x2 x1] [mid mid]
target 0
delta 0
iteration 0]
(let [
tol (+ absolute-threshold (* relative-threshold (g/abs xx)))
tol2 (* 2 tol)
converged? (terminate? a xx b tol2)]
(callback iteration xx fx)
(if (or (> iteration maxiter)
(> @f-counter maxfun)
converged?)
{:result xx
:value fx
:iterations iteration
:converged? converged?
:fncalls @f-counter}
(let [[new-target new-delta]
(if (<= (g/abs target) tol)
(golden-section-step a xx b)
(let [[p q] (ub/parabolic-pieces x1 x x2)]
(if (parabola-valid? a xx b target p q)
[delta (/ p q)]
(golden-section-step a xx b))))
xnew (apply-delta a xx b new-delta tol tol2)
new-pt [xnew (f xnew)]
[[xl fl :as l] [xr fr :as r]] (if (< xnew xx)
[new-pt x]
[x new-pt])]
(recur (if (<= fl fr)
[a l xr]
[xl r b])
(update-history x2 x1 x new-pt)
new-target
new-delta
(inc iteration)))))))))