Module Jera.Root
Zeros of functions of one variable.
Both methods are elementwise: every element of the input is its own problem, with its own status, and f must compute each element of its result from the same element of its argument alone. The answer is stated as a zero of f (Rune.root), so its derivative is the implicit one, −∂f/∂θ / ∂f/∂x at a converged element, through every tracked value f reads, and zero at an element that did not converge. At a converged zero where ∂f/∂x is 0 the derivative does not exist and is not finite.
The derivative solves elementwise and checks its solution with one more product: where it misses, f read another element than its own, and the derivative raises Invalid_argument naming the entry point.
(* Kepler's equation M = E − e sin E, for each epoch *) let eccentric_anomaly m e = Root.newton ~tol:(Tol.v ~rel:1e-12 ~abs:1e-15) ~budget:8 ~slope:(fun x -> Nx.rsub_s 1. (Nx.mul e (Nx.cos x))) (fun x -> Nx.sub (Nx.sub x (Nx.mul e (Nx.sin x))) m) (Nx.add m (Nx.mul e (Nx.sin m))) |> Solution.get
val bracket :
tol:Tol.t ->
((float, 'b, 'd) Nx.t -> (float, 'b, 'd) Nx.t) ->
lo:(float, 'b, 'd) Nx.t ->
hi:(float, 'b, 'd) Nx.t ->
((float, 'b, 'd) Nx.t, 'd) Solution.tbracket ~tol f ~lo ~hi is a zero of f between lo and hi in each element, given in either order, which broadcast together.
Method. ITP steps (Oliveira and Takahashi, 2020) interleaved with bisections of the floats' ordered-integer images, so an element ends within 2b + 2 evaluations for a b-bit dtype (130 in float64), whatever f. Error. e is half the final bracket and y its midpoint; an element also ends when no float lies strictly inside its bracket. It converges only if |f| at its estimate, the bracket's end of smaller |f|, is at most the smaller |f| at the given ends, so a pole ends Stalled; a bounded jump looks like a steep zero at float resolution and converges at the jump, where the derivative does not exist. Ends of one sign end Not_bracketed; a non-finite value of f ends Not_finite.
val newton :
tol:Tol.t ->
budget:int ->
slope:((float, 'b, 'd) Nx.t -> (float, 'b, 'd) Nx.t) ->
((float, 'b, 'd) Nx.t -> (float, 'b, 'd) Nx.t) ->
(float, 'b, 'd) Nx.t ->
((float, 'b, 'd) Nx.t, 'd) Solution.tnewton ~tol ~budget ~slope f x0 is a zero of f near x0 in each element, by Newton's method: slope x is f's derivative, elementwise. It steers the steps only: one too steep by a modest factor slows a solve, one too shallow by 2 or more need not converge, and one wrong by orders of magnitude at the answer can end it early, as System's error says.
Method. Undamped Newton steps δ = −f x / slope x, quadratic near a simple zero. Error. As System's, each element a system of one unknown: e = |δ| / (1 − q) and the answer x + δ. A zero or non-finite slope ends the element Stalled; a non-finite f ends it Not_finite; budget iterations end it Budget_spent, so a budget of 1 converges only on a zero step. Cost. One call of f and one of slope per iteration.
Raises Invalid_argument if budget < 1.