Skip to content

Commit b2ab161

Browse files
fgittinsChrisRackauckas
authored andcommitted
Add benchmarks of suite
1 parent faceca8 commit b2ab161

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
---
2+
title: NonlinearSolve.jl suite of interval root-finding algorithms
3+
author: Fabian Gittins
4+
---
5+
6+
In this benchmark, we will examine how the interval root-finding algorithms
7+
provided in `NonlinearSolve.jl` fare against one another for a selection of
8+
examples.
9+
10+
## `Roots.jl` baseline
11+
12+
To give us sensible measure to compare with, we will use the `Roots.jl` package
13+
as a baseline,
14+
15+
```julia
16+
using BenchmarkTools
17+
using Roots
18+
```
19+
20+
and search for the roots of the function
21+
22+
```julia
23+
f(u, p) = u * sin(u) - p;
24+
```
25+
26+
To get a good idea of the performance of the algorithms, we will use a large
27+
number of random `p` values and determine the roots with all of them.
28+
Specifically, we will draw `N = 100_000` random values (which we seed for
29+
reproducibility),
30+
31+
```julia
32+
using Random
33+
34+
Random.seed!(42)
35+
36+
const N = 100_000
37+
ps = 1.5 .* rand(N)
38+
39+
function g!(out, ps, uspan)
40+
for i in 1:N
41+
out[i] = find_zero(f, uspan, ps[i])
42+
end
43+
out
44+
end;
45+
```
46+
47+
Now, we can run the benchmark for `Roots.jl`:
48+
49+
```julia
50+
out = zeros(N)
51+
uspan = (0.0, 2.0)
52+
53+
@btime g!(out, ps, uspan);
54+
```
55+
56+
However, speed is not the only thing we care about. We also want the algorithms
57+
to be accurate. We will use the mean of the absolute errors to measure the
58+
accuracy,
59+
60+
```julia
61+
println("Mean absolute error: $(mean(abs.(f.(out, ps))))")
62+
```
63+
64+
For simplicity, we will assume the default tolerances of the methods, while
65+
noting that these can be set.
66+
67+
## `NonlinearSolve.jl` algorithms
68+
69+
With the preliminaries out of the way, let's see how the `NonlinearSolve.jl`
70+
solvers perform! We define a (non-allocating) function to benchmark,
71+
72+
```julia
73+
using NonlinearSolve
74+
75+
function h!(out, ps, uspan, alg)
76+
for i in 1:N
77+
prob = IntervalNonlinearProblem{false}(IntervalNonlinearFunction{false}(f), uspan, ps[i])
78+
sol = solve(prob, alg)
79+
out[i] = sol.u
80+
end
81+
out
82+
end;
83+
```
84+
85+
and loop through the methods,
86+
87+
```julia
88+
for alg in (Alefeld, NonlinearSolve.Bisection, Brent, Falsi,
89+
ITP, Muller, Ridder)
90+
println("Benchmark of $alg:")
91+
@btime h!($out, $ps, $uspan, $(alg()))
92+
println("Mean absolute error: $(mean(abs.(f.(out, ps))))\n")
93+
end
94+
```
95+
96+
Although each method finds the roots with different accuracies, we can see that
97+
all the `NonlinearSolve.jl` algorithms are performant and non-allocating.
98+
99+
## A different function
100+
101+
At this point, we will consider a separate function to solve. We will now
102+
search for the root of
103+
104+
```julia
105+
g(u) = exp(u) - 1e-15;
106+
```
107+
108+
The root of this particular function is analytic and given by
109+
`u = - 15 * log(10)`. Due to the nature of the function, it can be difficult to
110+
numerically resolve the root.
111+
112+
Since we do not adjust the value of `p` here, we will just solve this same
113+
function `N` times. As before, we start with `Roots.jl`,
114+
115+
```julia
116+
function i!(out, uspan)
117+
for i in 1:N
118+
out[i] = find_zero(g, uspan)
119+
end
120+
out
121+
end
122+
123+
uspan = (-100.0, 0.0)
124+
125+
@btime i!(out, uspan)
126+
println("Mean absolute error: $(mean(abs.(g.(out))))")
127+
```
128+
129+
So, how do the `NonlinearSolve.jl` methods fare?
130+
131+
```julia
132+
g(u, p) = g(u)
133+
134+
function j!(out, uspan, alg)
135+
N = length(out)
136+
for i in 1:N
137+
prob = IntervalNonlinearProblem{false}(IntervalNonlinearFunction{false}(g), uspan)
138+
sol = solve(prob, alg)
139+
out[i] = sol.u
140+
end
141+
out
142+
end
143+
144+
for alg in (Alefeld, NonlinearSolve.Bisection, Brent, Falsi,
145+
ITP, Muller, Ridder)
146+
println("Benchmark of $alg:")
147+
@btime j!($out, $uspan, $(alg()))
148+
println("Mean absolute error: $(mean(abs.(g.(out))))\n")
149+
end
150+
```
151+
152+
Again, we see that the `NonlinearSolve.jl` root-finding algorithms are fast.
153+
However, it is notable that some are able to resolve the root more accurately
154+
than others. This is entirely to be expected as some of the algorithms, like
155+
`Bisection`, bracket the root and thus will reliably converge to high accuracy.
156+
Others, like `Muller`, are not bracketing methods, but can be extremely fast.
157+
158+
```julia, echo = false
159+
using SciMLBenchmarks
160+
SciMLBenchmarks.bench_footer(WEAVE_ARGS[:folder], WEAVE_ARGS[:file])
161+
```

0 commit comments

Comments
 (0)