(defn bisect
"Given some function `f` and (inclusive) lower and upper bounds `a` and `b` on
the domain, attempts to find a root of `f` in that range, i.e., a value `x` for
which `(f x)` is equal to 0.
Supports the following optional keyword arguments:
`:method`: can be `:bisection`, `:secant` or `:mixed`. See the Methods section
below for a description of each. Defaults to `:mixed`
`:eps`: defaults to [[emmy.value/machine-epsilon]].
`:callback`: if supplied, the supplied `f` will be invoked at each
intermediate point with the iteration count and the values of x and f(x) at
each search step.
`: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)`.
`:n-break` defaults to the dynamically bindable `*bisect-break*` (which
defaults to 60). Bind `*bisect-break*` to modify the behavior of the `:mixed`
method (see below) when it's used inside a nested routine. Ignored if method
is not `:mixed`.
## Methods
- `:bisection` causes [[bisect]] to use the [Bisection
method](https://en.wikipedia.org/wiki/Bisection_method); at each iteration,
the midpoint between the bounds is chosen as the next point.
- `:secant` uses the [Secant
method](https://en.wikipedia.org/wiki/Secant_method); each candidate point is
chosen by taking the root of a line drawn between the two endpoints `[a (f
a)]` and `[b (f b)]`. This method is most useful when the bounds are close to
the root.
- `:mixed` uses `:bisection` up until `:n-break` iterations and `:secant`
beyond. This can be useful for narrowing down a very wide range close to the
root, and then switching in to a faster search method."
([f a b] (bisect f a b {}))
([f a b {:keys [eps
maxiter
maxfun
callback]
:or {eps u/machine-epsilon
maxiter 1000
callback (constantly nil)}
:as opts}]
(let [close? (us/close-enuf? eps)
get-next-pt (next-point-fn opts)
maxfun (or maxfun (inc maxiter))
[a b] [(min a b) (max a b)]
[f-counter f] (u/counted f)]
(loop [a a, fa (f a)
b b, fb (f b)
iteration 0]
(cond (zero? fa) (succeed a fa iteration @f-counter)
(zero? fb) (succeed b fb iteration @f-counter)
(pos? (* fa fb))
(fail "Root not bounded" a fa b fb iteration @f-counter)
(or (> iteration maxiter)
(> @f-counter maxfun))
(fail "Iteration bounds exceeded" a fa b fb iteration @f-counter)
:else
(let [mid (get-next-pt a fa b fb iteration)
fmid (f mid)]
(callback mid fmid iteration)
(if (close? a b)
(succeed a fa iteration @f-counter)
(if (pos? (* fb fmid))
(recur a fa mid fmid (inc iteration))
(recur mid fmid b fb (inc iteration))))))))))