```@meta
DocTestSetup = quote
    using IntervalArithmetic, IntervalArithmetic.Symbols, IntervalRootFinding
end
```
# `roots` interface

## Methods

Three root-finding contractors currently available through the `roots` interface are the following:
  - `Newton` (default);
  - `Krawczyk`;
  - `Bisection`

Both the Newton and Krawczyk methods can determine if a root is unique in an interval, at the cost of requiring that the function is differentiable. The bisection method has no such requirement, but can never guarantee the existence or uniqueness of a root.

The method used is given using the `contractor` keyword argument:

```jldoctest
julia> roots(log, -2 .. 2 ; contractor = Newton)
1-element Vector{Root{Interval{Float64}}}:
 Root([0.999999, 1.00001]_com, :unique)

julia> roots(log, -2 .. 2 ; contractor = Krawczyk)
1-element Vector{Root{Interval{Float64}}}:
 Root([0.999999, 1.00001]_com, :unique)

julia> roots(log, -2 .. 2 ; contractor = Bisection)
1-element Vector{Root{Interval{Float64}}}:
 Root([0.999999, 1.00001]_com, :unknown)
 └ Not converged: region size smaller than the tolerance
```

Note that as shown in the example, the `log` function does not complain about being given an interval going outside its domain. While this may be surprising, this is the expected behavior and no root will ever be found outside the domain of a function.

## Explicit derivatives

Newton and Krawczyk methods require the function to be differentiable, but the derivative is usually computed automatically using forward-mode automatic differentiation, provided by the `ForwardDiff.jl` package. It is however possible to provide the derivative explicitly using the `derivative` keyword argument:

```jldoctest
julia> roots(log, -2 .. 2 ; contractor = Newton, derivative = x -> 1/x)
1-element Vector{Root{Interval{Float64}}}:
 Root([0.999999, 1.00001]_com_NG, :unique)

julia> roots(log, -2 .. 2 ; contractor = Krawczyk, derivative = x -> 1/x)
1-element Vector{Root{Interval{Float64}}}:
 Root([0.999999, 1.00001]_com_NG, :unique)
```

When providing the derivative explicitly, the computation is expected to be slightly faster, but the precision of the result is unlikely to be affected.

```julia-repl
julia> using BenchmarkTools

julia> @btime roots(log, -2 .. 2 ; derivative = x -> 1/x)
  7.050 μs (129 allocations: 9.33 KiB)
1-element Vector{Root{Interval{Float64}}}:
 Root([1.0, 1.0]_com_NG, :unique)

julia> @btime roots(log, -2 .. 2)
  7.743 μs (129 allocations: 9.33 KiB)
1-element Vector{Root{Interval{Float64}}}:
 Root([1.0, 1.0]_com, :unique)
```

This may be useful in some special cases where `ForwardDiff.jl` is unable to compute the derivative of a function. Examples are complex functions and functions whose interval extension must be manually defined (e.g. special functions like `zeta`).

In dimension greater than one, the derivative can be given as a function returning the Jacobi matrix:

```jldoctest
julia> f( (x, y) ) = [sin(x), cos(y)]
f (generic function with 1 method)

julia> df( (x, y) ) = [cos(x) 0 ; 0 -sin(y)]
df (generic function with 1 method)

julia> roots(f, [-3 .. 3, -3 .. 3] ; derivative = df)
2-element Vector{Root{Vector{Interval{Float64}}}}:
 Root(Interval{Float64}[[-3.93242e-20, 3.93242e-20]_com_NG, [-1.5708, -1.57079]_com_NG], :unique)
 Root(Interval{Float64}[[-3.93242e-20, 3.93242e-20]_com_NG, [1.57079, 1.5708]_com_NG], :unique)
```

## Tolerance

An absolute tolerance for the search may be specified as the `abstol` keyword argument.

```jldoctest
julia> g(x) = sin(exp(x))
g (generic function with 1 method)

julia> roots(g, 0 .. 2)
2-element Vector{Root{Interval{Float64}}}:
 Root([1.14472, 1.14473]_com, :unique)
 Root([1.83787, 1.83788]_com, :unique)

julia> roots(g, 0 .. 2 ; abstol = 1e-1)
2-element Vector{Root{Interval{Float64}}}:
 Root([1.12049, 1.16994]_com, :unique)
 Root([1.82369, 1.85206]_com, :unique)
```

A lower tolerance may greatly reduce the computation time, at the cost of an increased number of returned roots having `:unknown` status:

```julia-repl
julia> h(x) = cos(x) * sin(1 / x)
h (generic function with 1 method)

julia> @btime roots(h, 0.05 .. 1)
  484.488 μs (12218 allocations: 590.76 KiB)
6-element Vector{Root{Interval{Float64}}}:
 Root([0.0530516, 0.0530517]_com_NG, :unique)
 Root([0.063662, 0.063662]_com_NG, :unique)
 Root([0.0795775, 0.0795775]_com_NG, :unique)
 Root([0.106103, 0.106103]_com_NG, :unique)
 Root([0.159155, 0.159155]_com_NG, :unique)
 Root([0.31831, 0.31831]_com_NG, :unique)

julia> @btime roots(h, 0.05 .. 1 ; abstol = 1e-2)
  249.720 μs (6141 allocations: 297.80 KiB)
6-element Vector{Root{Interval{Float64}}}:
 Root([0.0514446, 0.0531086]_com_NG, :unique)
 Root([0.0570254, 0.0641614]_com, :unknown)
    Not converged: region size smaller than the tolerance
 Root([0.0785458, 0.0797797]_com_NG, :unknown)
    Not converged: region size smaller than the tolerance
 Root([0.104755, 0.107541]_com_NG, :unknown)
    Not converged: region size smaller than the tolerance
 Root([0.157236, 0.165988]_com_NG, :unknown)
    Not converged: region size smaller than the tolerance
 Root([0.317161, 0.319318]_com_NG, :unique)

julia> @btime roots(h, 0.05 .. 1 ; abstol = 1e-1)
  66.330 μs (1402 allocations: 69.10 KiB)
3-element Vector{Root{Interval{Float64}}}:
 Root([0.05, 0.107541]_com, :unknown)
    Not converged: region size smaller than the tolerance
 Root([0.107541, 0.165988]_com, :unknown)
    Not converged: region size smaller than the tolerance
 Root([0.283804, 0.356099]_com_NG, :unknown)
    Not converged: region size smaller than the tolerance
```

The last example shows a case where the tolerance was too large to be able to isolate the roots in distinct regions.

!!! warning

    For a root `x` of some function, if the absolute tolerance is smaller than `eps(x)` i.e. if `tol + x == x`, `roots` may never be able to converge to the required tolerance and the function may get stuck in an infinite loop.
    To avoid that, either increase `abstol` or `reltol`,
    or set a maximum number of iterations with the `max_iteration` keyword.

## Error during computation

By default, the `roots` function will ignore `IntervalArithmetic.InconclusiveBooleanOperation`
errors and continue bisecting the given interval,
hoping to eventually find regions that are small enough to avoid the error.

This is useful when using code that is using comparisons,
which are ill-defined for intervals
(see [the IntervalArithmetic.jl documentation](https://juliaintervals.github.io/IntervalArithmetic.jl/stable/manual/usage/#Comparisons)
for more information).

For example, the following will find all roots,
and flag the erroring singularity:
```jldoctest bisect_on_error
julia> f(x) = x > 2 ? 7 - x : x
f (generic function with 1 method)

julia> roots(f, -10 .. 10)
3-element Vector{Root{Interval{Float64}}}:
 Root([0.0, 0.0]_com, :unique)
 Root([1.99999, 2.00001]_com, :unknown)
 ├ Not converged: region size smaller than the tolerance
 ├ Warning: error encountered during computation (use showerror(root.error) to see the whole stacktrace)
 └ InconclusiveBooleanOperation: The operation `[2.0, 2.0]_com_NG < [1.99999, 2.00001]_com` cannot be determined unambiguously. See the documentation for more information. See also `strictprecedes`.
 Root([7.0, 7.0]_com_NG, :unique)
```

This behavior can be controlled by changing the `ignored_errors` field.

To deactivate it, change it to an empty list,
so that the process is interrupted as soon as an error is encountered.
This is useful while debugging your code.

```julia-repl
julia> roots(f, -10 .. 10, ; ignored_errors = [])
ERROR: InconclusiveBooleanOperation: The operation `[2.0, 2.0]_com_NG < [-10.0, 10.0]_com` cannot be determined unambiguously. See the documentation for more information. See also `strictprecedes`.
Stacktrace: [..]
```