# [Lith, Boonkkamp, and IJzerman](https://doi.org/10.1016/j.amc.2017.09.003)
# A family of different methods that includes the secant method and Newton's method

# return f^(i-1)(x); not the same as default eval call
function evalf(F::Callable_Function{S,T,𝑭,P}, x, i) where {N,S<:Val{N},T<:Val{true},𝑭,P}
    F.f[i](x)
end
function evalf(F::Callable_Function{S,T,𝑭,P}, x, i) where {S<:Val{1},T<:Val{false},𝑭,P}
    F.f(x)
end
evalf(::Callable_Function, x, i) = throw(
    ArgumentError(
        "LithBoonkkampIJzerman expects functions to be defined singly or as tuples",
    ),
)

"""
    LithBoonkkampIJzerman{S,D} <: AbstractNewtonLikeMethod
    LithBoonkkampIJzerman(S,D)

Specify a linear multistep solver with `S` steps and `D` derivatives following [Lith, Boonkkamp, and
IJzerman](https://doi.org/10.1016/j.amc.2017.09.003).

## Examples

```jldoctest lith
julia> using Roots

julia> find_zero(sin, 3, Roots.LithBoonkkampIJzerman(2,0)) ≈ π # the secant method
true

julia> find_zero((sin,cos), 3, Roots.LithBoonkkampIJzerman(1,1)) ≈ π # Newton's method
true

julia> find_zero((sin,cos), 3, Roots.LithBoonkkampIJzerman(3,1)) ≈ π # Faster convergence rate
true

julia> find_zero((sin,cos, x->-sin(x)), 3, Roots.LithBoonkkampIJzerman(1,2)) ≈ π # Halley-like method
true
```

The method can be more robust to the intial condition. This example is from the paper (p13). Newton's method (the `S=1`, `D=1` case) fails if `|x₀| ≥ 1.089` but methods with more memory succeed.

```jldoctest lith
julia> fx =  ZeroProblem((tanh,x->sech(x)^2), 1.239); # zero at 0.0

julia> solve(fx, Roots.LithBoonkkampIJzerman(1,1)) |> isnan# Newton, NaN
true

julia> solve(fx, Roots.LithBoonkkampIJzerman(2,1)) |> abs |> <(eps())
true

julia> solve(fx, Roots.LithBoonkkampIJzerman(3,1)) |> abs |> <(eps())
true
```

Multiple derivatives can be constructed automatically using automatic differentiation. For example,

```jldoctest lith
julia> using ForwardDiff

julia> function δ(f, n::Int=1)
           n <= 0 && return f
           n == 1 && return x -> ForwardDiff.derivative(f,float(x))
           δ(δ(f,1),n-1)
       end;

julia> fs(f,n) = ntuple(i -> δ(f,i-1), Val(n+1));

julia> f(x) = cbrt(x) * exp(-x^2); # cf. Table 6 in paper, α = 0

julia> fx = ZeroProblem(fs(f,1), 0.1147);

julia> opts = (xatol=2eps(), xrtol=0.0, atol=0.0, rtol=0.0); # converge if |xₙ - xₙ₋₁| <= 2ϵ

julia> solve(fx, Roots.LithBoonkkampIJzerman(1, 1); opts...) |> isnan # NaN -- no convergence
true

julia> solve(fx, Roots.LithBoonkkampIJzerman(2, 1); opts...) |> abs |> <(eps()) # converges
true

julia> fx = ZeroProblem(fs(f,2), 0.06);                       # need better starting point

julia> solve(fx, Roots.LithBoonkkampIJzerman(2, 2); opts...) |> abs |> <(eps()) # converges
true
```

For the case `D=1`, a bracketing method based on this approach is implemented in [`LithBoonkkampIJzermanBracket`](@ref)

## Reference

In [Lith, Boonkkamp, and
IJzerman](https://doi.org/10.1016/j.amc.2017.09.003) an analysis is
given of the convergence rates when using linear multistep methods to
solve `0=f(x)` using `f⁻¹(0) = x` when `f` is a sufficiently smooth
linear function. The reformulation, attributed to Grau-Sanchez, finds
a differential equation for `f⁻¹`: `dx/dy = [f⁻¹]′(y) = 1/f′(x) = F` as
`x(0) = x₀ + ∫⁰_y₀ F(x(y)) dy`.

A linear multi-step method is used to solve this equation
numerically.  Let S be the number of memory steps (S= 1,2,...) and D be
the number of derivatives employed, then, with `F(x) = dx/dy`
`x_{n+S} = ∑_{k=0}^{S-1} aₖ x_{n+k} +∑d=1^D ∑_{k=1}^{S-1} aᵈ_{n+k}F⁽ᵈ⁾(x_{n+k})`.
 The `aₖ`s and `aᵈₖ`s are computed each step.

This table is from Tables 1 and 3 of the paper and gives the
convergence rate for simple roots identified therein:

```
s: number of steps remembered
d: number of derivatives uses
s/d  0    1    2    3    4
1    .    2    3    4    5
2    1.62 2.73 3.79 4.82 5.85
3    1.84 2.91 3.95 4.97 5.98
4    1.92 2.97 3.99 4.99 5.996
5    1.97 .    .    .    .
```

That is, more memory leads to a higher convergence rate; more
derivatives leads to a higher convergence rate. However, the
interval about `α`, the zero, where the convergence rate is guaranteed
may get smaller.

!!! note
    For the larger values of `S`, the expressions to compute the next value get quite involved.
    The higher convergence rate is likely only to be of help for finding solutions to high precision.

"""
struct LithBoonkkampIJzerman{S,D} <: AbstractNewtonLikeMethod end
LithBoonkkampIJzerman(s::Int, d::Int) = LithBoonkkampIJzerman{s,d}()
fn_argout(::LithBoonkkampIJzerman{S,D}) where {S,D} = 1 + D

struct LithBoonkkampIJzermanState{S′,D⁺,T,S} <: AbstractUnivariateZeroState{T,S}
    xn1::T
    xn0::T
    m::NTuple{S′,T}
    fxn1::S
    fxn0::S
    fm::NTuple{D⁺,NTuple{S′,S}}
end

function init_state(L::LithBoonkkampIJzerman{S,0}, F, x) where {S}
    x₀, x₁ = x₀x₁(x)
    fx₀, fx₁ = evalf(F, x₀, 1), evalf(F, x₁, 1)
    state = init_state(L, F, x₀, x₁, fx₀, fx₁)
end

function init_state(L::LithBoonkkampIJzerman{S,D}, F, x) where {S,D}
    x₀ = float(first(x))
    fx₀ = evalf(F, x₀, 1)
    state = init_state(L, F, nan(x₀), x₀, nan(fx₀), fx₀)
end

function init_state(
    L::LithBoonkkampIJzerman{S,D},
    F,
    x₀,
    x₁::R,
    fx₀,
    fx₁::T,
) where {S,D,R,T}
    xs, ys = init_lith(L, F, x₁, fx₁, x₀, fx₀) # [x₀,x₁,…,xₛ₋₁], ...
    # skip unit consideration here, as won't fit within storage of ys
    state = LithBoonkkampIJzermanState{S,D + 1,R,T}(
        xs[end],    # xₙ
        S > 1 ? xs[end - 1] : nan(xs[end]), # xₙ₋₁
        xs,         # all xs
        ys[1][end], # fₙ
        S > 1 ? ys[1][end - 1] : nan(ys[1]), # fₙ₋₁
        ys,        #
    )

    state
end
initial_fncalls(::LithBoonkkampIJzerman{S,D}) where {S,D} = S * (D + 1)

function update_state(
    L::LithBoonkkampIJzerman{S,D},
    F::Callable_Function,
    o::LithBoonkkampIJzermanState{S⁺,D′,R,T},
    options,
    l=NullTracks(),
) where {S,D,S⁺,D′,R,T}
    xs, ys = o.m, o.fm

    xᵢ::R = lmm(L, xs, ys...)
    isissue(o.xn1 - xᵢ) && return (o, true)

    for i in 1:(S - 1)
        @set! xs[i] = xs[i + 1]
    end
    @set! xs[end] = xᵢ

    for i in 0:D
        i′ = i + 1
        for j in 1:(S - 1)
            @set! ys[i′][j] = ys[i′][j + 1]
        end
        yij::T = evalf(F, xᵢ, i′)
        @set! ys[i′][end] = yij
    end
    incfn(l, 1 + D)

    @set! o.xn0 = o.xn1
    @set! o.xn1 = xᵢ
    @set! o.fxn0 = o.fxn1
    @set! o.fxn1 = ys[1][end]
    @set! o.m = xs
    @set! o.fm = ys

    return (o, false)
end

# manufacture initial xs, ys
# use lower memory terms to boot strap up. Secant uses initial default step
#D=0, geneate [x0].x1,...,xs
function init_lith(
    L::LithBoonkkampIJzerman{S,0},
    F::Callable_Function{Si,Tup,𝑭,P},
    x₁::R,
    fx₁::T,
    x₀::R,
    fx₀::T,
) where {S,Si,Tup,𝑭,P,R,T}
    xs = NTuple{S,R}(ntuple(_ -> one(R), Val(S)))
    yᵢ = NTuple{S,T}(ntuple(_ -> one(T), Val(S)))
    ys = NTuple{1,NTuple{S,T}}((yᵢ,))

    # build up to get S of them
    x0::R = zero(R)
    if isnan(x₀)
        x0 = _default_secant_step(x₁)
        fx0::T = evalf(F, x0, 1)
    else
        x0, fx0 = x₀, fx₀
    end

    @set! xs[1] = x0
    @set! xs[2] = x₁
    @set! ys[1][1] = fx0
    @set! ys[1][2] = fx₁

    for i in 3:S
        xᵢ::R = lmm(Val(i - 1), Val(0), xs, ys) # XXX allocates due to runtime i-1
        y1i::T = evalf(F, xᵢ, 1)
        @set! xs[i] = xᵢ
        @set! ys[1][i] = y1i
    end

    xs, ys
end

#D≥1. ignore x₀
function init_lith(
    L::LithBoonkkampIJzerman{S,D},
    F::Callable_Function{Si,Tup,𝑭,P},
    x₁::R,
    fx₁::T,
    x₀::R,
    fx₀::T,
) where {S,D,Si,Tup,𝑭,P,R,T}
    xs = NTuple{S,R}(ntuple(_ -> one(R), Val(S)))
    yᵢ = NTuple{S,T}(ntuple(_ -> one(T), Val(S)))
    ys = NTuple{D + 1,NTuple{S,T}}(ntuple(_ -> yᵢ, Val(D + 1)))

    @set! xs[1] = x₁
    @set! ys[1][1] = fx₁
    for i in 1:D
        yi1::T = evalf(F, x₁, i + 1)
        @set! ys[i + 1][1] = yi1
    end

    # build up to get S of them
    for i in 2:S
        xᵢ::R = lmm(Val(i - 1), Val(D), xs, ys) # XXX allocates! clean up
        @set! xs[i] = xᵢ
        for j in 0:D
            yji::T = evalf(F, xᵢ, j + 1)
            @set! ys[j + 1][i] = yji
        end
    end

    xs, ys
end

"""
    LithBoonkkampIJzermanBracket()

A bracketing method which is a modification of Brent's method due to
[Lith, Boonkkamp, and
IJzerman](https://doi.org/10.1016/j.amc.2017.09.003). The best
possible convergence rate is 2.91.

A function, its derivative, and a bracketing interval need to be specified.

The state includes the 3 points -- a bracket `[a,b]` (`b=xₙ` has
`f(b)` closest to `0`) and `c=xₙ₋₁` -- and the corresponding values
for the function and its derivative at these three points.

The next proposed step is either a `S=2` or `S=3` selection for the
[`LithBoonkkampIJzerman`](@ref) methods with derivative information
included only if it would be of help. The proposed is modified if it
is dithering. The proposed is compared against a bisection step; the
one in the bracket and with the smaller function value is chosen as
the next step.

"""
struct LithBoonkkampIJzermanBracket <: AbstractBracketing end
struct LithBoonkkampIJzermanBracketState{T,S,R} <: AbstractUnivariateZeroState{T,S}
    xn1::T
    xn0::T
    c::T
    fxn1::S
    fxn0::S
    fc::S
    fp1::R
    fp0::R
    fpc::R
end

function init_state(L::LithBoonkkampIJzermanBracket, F, x₀, x₁, fx₀, fx₁)
    a, b, fa, fb = x₀, x₁, fx₀, fx₁
    if abs(fa) < abs(fb)
        a, b, fa, fb = b, a, fb, fa
    end

    assert_bracket(fa, fb)

    f′a, f′b = evalf(F, a, 2), evalf(F, b, 2)
    c, fc, f′c = a, fa, f′a

    # skip unit consideration here, as won't fit within storage of ys
    state = LithBoonkkampIJzermanBracketState(
        b,    # xₙ
        a, # xₙ₋₁
        c,
        fb, # fₙ
        fa, # fₙ₋₁
        fc,
        f′b,
        f′a,
        f′c,
    )

    state
end

function update_state(
    M::LithBoonkkampIJzermanBracket,
    F,
    state::LithBoonkkampIJzermanBracketState{T,S,R},
    options::UnivariateZeroOptions,
    l=NullTracks(),
) where {T,S,R}
    b::T, c::T, a::T = state.xn1, state.c, state.xn0
    fb::S, fc::S, fa::S = state.fxn1, state.fc, state.fxn0
    f′a::R, f′c::R, f′b::R = state.fp0, state.fpc, state.fp1

    # Get next interpolating step
    # decide on S and D;
    # S is 3 if a,b,c are distinct; D=1 unless all derivative info will be of the wrong sign.
    s::Int = ((a == c) || (b == c)) ? 2 : 3

    # which derivatives do we include
    sₘ = sign((fb - fa) / (b - a))
    mc, mb = sign(f′c) == sₘ, sign(f′b) == sₘ

    d₀::S = zero(S)
    if s == 2
        if mc || mb
            # D = 1
            a2s, b2s = lmm_coefficients(LithBoonkkampIJzerman{2,1}(), (c, b), (fc, fb))
            h = -fb

            d₀ = -sum(a2s .* (c, b))
            mb && (d₀ += h * b2s[2] / f′b)
            mc && (d₀ += h * b2s[1] / f′c)
        else
            d₀ = lmm(LithBoonkkampIJzerman{2,0}(), (c, b), (fc, fb))
        end
    else
        ma = sign(f′a) == sₘ
        if mc || mb || ma
            # D = 1
            as, bs = lmm_coefficients(LithBoonkkampIJzerman{3,1}(), (a, c, b), (fa, fc, fb))
            h = -fb

            d₀ = -sum(as .* (a, c, b))
            mb && (d₀ += h * bs[end] / f′b) # only when helpful
            mc && (d₀ += h * bs[end - 1] / f′c)
            ma && (d₀ += h * bs[end - 2] / f′a)
        else
            d₀ = lmm(LithBoonkkampIJzerman{3,0}(), (a, c, b), (fa, fc, fb))
        end
    end

    # If the step is smaller than the tolerance, use the tolerance as step size.
    xatol, xrtol = options.xabstol, options.xreltol
    δ = xatol + abs(b) * xrtol
    Δ₀ = b - d₀
    if abs(Δ₀) <= δ
        d₀ = b - sign(Δ₀) * δ
    end

    # compare to bisection step; extra function evalution
    d₁ = a + (b - a) * (0.5) #_middle(a, b)
    f₀, f₁ = evalf(F, d₀, 1), evalf(F, d₁, 1)

    # interpolation outside a,b or bisection better use that
    d::T, fd::S, f′d::S = zero(T), zero(S), zero(S)
    if (abs(f₀) < abs(f₁)) && (min(a, b) < d₀ < max(a, b))
        d, fd, f′d = d₀, f₀, evalf(F, d₀, 2)# interp
    else
        d, fd, f′d = d₁, f₁, evalf(F, d₁, 2)#  bisection
    end

    # either [a,d] a bracket or [d,b]
    # [a < d] < b ...c -- b -> d, c-> b (update?)
    # a < [d < b] ...c -- a -> d (update?)
    if sign(fa) * sign(fd) <= 0
        c, fc, f′c = b, fb, f′b
        b, fb, f′b = d, fd, f′d
    else
        a, fa, f′a = d, fd, f′d
    end

    # a,b bracket; keep |fb| ≤ |fa|
    if abs(fa) < abs(fb)
        c, fc, f′c = b, fb, f′b
        a, b, fa, fb, f′a, f′b = b, a, fb, fa, f′b, f′a
    end

    incfn(l, 3)
    @set! state.xn1 = b
    @set! state.xn0 = a
    @set! state.c = c
    @set! state.fxn1 = fb
    @set! state.fxn0 = fa
    @set! state.fc = fc
    @set! state.fp0 = f′a
    @set! state.fpc = f′c
    @set! state.fp1 = f′b

    return (state, false)
end

function default_tolerances(
    ::M,
    ::Type{T},
    ::Type{S},
) where {M<:LithBoonkkampIJzermanBracket,T,S}
    xatol = 2eps(T)
    xrtol = zero(one(T))
    atol = zero(float(one(S))) * oneunit(S)
    rtol = 2eps(float(one(S))) * one(S)
    maxevals = typemax(Int)
    maxfnevals = typemax(Int)
    strict = true
    (xatol, xrtol, atol, rtol, maxevals, maxfnevals, strict)
end

### ------
# Script used to generate expressions
#
# Finds expressions by assuming an interpolating polynomial
# goes through the points. From (20) in the paper
# some cases take a **long** time to run.
# At some point, early on, this gets to be more of an academic exercise than
# a performant solution

#= -------
using SymPy

# see Ansatz (20) on p10
function inverse_polynomial_interpretation(s=2,d=2)

    @vars y
    hs = [Sym("h$i") for i ∈ 0:(1+d)*s-1]
    xs = [Sym("x$i") for i ∈ 0:s-1]
    fs = [Sym("f$i") for i ∈ 0:s-1]
    f′s = [Sym("f′$i") for i ∈ 0:s-1]
    f′′s = [Sym("f′′$i") for i ∈ 0:s-1]
    f′′′s = [Sym("f′′′$i") for i ∈ 0:s-1]
    f′′′′s = [Sym("f′′′′$i") for i ∈ 0:s-1]
    f′′′′′s = [Sym("f′′′′′$i") for i ∈ 0:s-1]
    f′′′′′′s = [Sym("f′′′′′′$i") for i ∈ 0:s-1]
    h0 = first(hs)

    H(y) = sum(hs[i+1]*(y-fs[s])^i for i ∈ 0:(1+d)*s-1)
    Hⁱ = H⁰ = H(y)
    Hⁱs = eltype(H⁰)[]
    for i ∈ 1:d
        Hⁱ = diff(Hⁱ,y)
        push!(Hⁱs, Hⁱ)
    end

    eqs = Sym[subs(H(fs[i]), Dict(h0=>xs[s])) - xs[i] for i ∈ 1:s-1]
    for i ∈ 1:s
        # cf. Liptag
        f1,f2,f3,f4,f5,f6 = f′s[i],f′′s[i],f′′′s[i],f′′′′s[i],f′′′′′s[i],f′′′′′′s[i]
        g′ = 1/f1
        g′′ = -f2/f1^3
        g′′′ = (3*f2^2 - f1*f3)/(f1^5)
        g′′′′ = -(15*f2^2 + 10*f1*f2*f3+f1^2*f4)/f1^7
        g′′′′′ = (105*f2^4 -105*f1*f2^2*f3 + 10*f1^2*f3^2 + 15*f1^2*f2*f4 -f1^3*f5)/f1^9
        g′′′′′′ = (-f1^4*f6 + 21*f1^3*f2*f5 + 35*f1^3*f3*f4 - 210*f1^2*f2^2*f4 - 280*f1^2*f2*f3^2 + 1260*f1*f2^3*f3 - 945*f2^5)/f1^11
        gⁱs = [g′,g′′,g′′′, g′′′′,g′′′′′,g′′′′′′]

        for j ∈ 1:d
            push!(eqs, subs(Hⁱs[j], Dict(y=>fs[i], h0=>xs[s])) - gⁱs[j])
        end

    end

    ϕ = sympy.linsolve(eqs, hs[2:end]...)
    ϕ = first(elements(ϕ))
    ϕ = Sym.(convert(Tuple, ϕ.__pyobject__))
    D = Dict{Any,Any}(h0=>xs[s])
    for i in 1:(d+1)*s-1
        D[hs[i+1]] = ϕ[i]
    end
    subs(H(0), D) |> simplify
end

# For g = f⁻¹ return [g', g'', g''',..., g⁽ⁿ⁾]
# (cf [Liptaj](https://vixra.org/pdf/1703.0295v1.pdf)
function liptag(N)

    @vars x₀ Δₓ
    fs = [Sym("f$i") for i ∈ 1:N]
    gs = [Sym("g$i") for i ∈ 1:N]
    a(i) = fs[i]/factorial(i)
    b(i) = gs[i]/factorial(i)

    gᵏs = [1/fs[1]]
    for n ∈ 2:N
        Δy = sum(a(j) * Δₓ^j for j ∈ 1:n)
        l = x₀ + Δₓ
        r = x₀ + sum(b(i)*Δy^i for i ∈ 1:n)
        ϕ = solve(l-r, gs[n])[1]

        for j ∈ 1:n-1
            ϕ = subs(ϕ, gs[j] => gᵏs[j])
        end
        L = limit(ϕ, Δₓ => 0)
        push!(gᵏs, L)
    end
    gᵏs
end
=#

# have computed these
# S/D 0 1 2 3 4  5 6
# 1   x ✓ ✓ ✓ ✓ ✓ ✓
# 2   ✓ ✓ ✓ ✓ ✓ ✓ ✓
# 3   ✓ ✓ - - x x x
# 4   ✓ - x x x x x
# 5   ✓ x x x x x x
# 6   - x x x x x x

# - can be found with script, but answers are too long for
#   inclusion here

## We have two means to do this:
## Using coefficients as,bs, ... returned by lmm_coefficients
## x = ∑ aᵢxᵢ + ∑ⱼ₊₁ⁿ ∑ᵢ bʲᵢFʲᵢ, where Fʲ is the jth derivative of g⁻¹ (F¹ = 1/f'...)
## Using a polynomial interpolant, H(y), going through (xᵢ,fʲ(xᵢ)), j ∈ 0:N)

function lmm(::Val{S}, ::Val{D}, xs, ys) where {S,D}
    xi = ntuple(ii -> xs[ii], Val(S))
    yi = ntuple(ii -> ntuple(j -> ys[ii][j], Val(S)), Val(D + 1))
    lmm(LithBoonkkampIJzerman{S,D}(), xi, yi...)
end

# secant
function lmm(::LithBoonkkampIJzerman{2,0}, xs, fs)
    x0, x1 = xs
    f0, f1 = fs

    (f0 * x1 - f1 * x0) / (f0 - f1)
end

function lmm(::LithBoonkkampIJzerman{3,0}, xs, fs)
    x0, x1, x2 = xs
    f0, f1, f2 = fs

    (
        f0^2 * f1 * x2 - f0^2 * f2 * x1 - f0 * f1^2 * x2 + f0 * f2^2 * x1 + f1^2 * f2 * x0 -
        f1 * f2^2 * x0
    ) / (f0^2 * f1 - f0^2 * f2 - f0 * f1^2 + f0 * f2^2 + f1^2 * f2 - f1 * f2^2)
end

function lmm(::LithBoonkkampIJzerman{4,0}, xs, fs)
    x0, x1, x2, x3 = xs
    f0, f1, f2, f3 = fs

    (
        f0^3 * f1^2 * f2 * x3 - f0^3 * f1^2 * f3 * x2 - f0^3 * f1 * f2^2 * x3 +
        f0^3 * f1 * f3^2 * x2 +
        f0^3 * f2^2 * f3 * x1 - f0^3 * f2 * f3^2 * x1 - f0^2 * f1^3 * f2 * x3 +
        f0^2 * f1^3 * f3 * x2 +
        f0^2 * f1 * f2^3 * x3 - f0^2 * f1 * f3^3 * x2 - f0^2 * f2^3 * f3 * x1 +
        f0^2 * f2 * f3^3 * x1 +
        f0 * f1^3 * f2^2 * x3 - f0 * f1^3 * f3^2 * x2 - f0 * f1^2 * f2^3 * x3 +
        f0 * f1^2 * f3^3 * x2 +
        f0 * f2^3 * f3^2 * x1 - f0 * f2^2 * f3^3 * x1 - f1^3 * f2^2 * f3 * x0 +
        f1^3 * f2 * f3^2 * x0 +
        f1^2 * f2^3 * f3 * x0 - f1^2 * f2 * f3^3 * x0 - f1 * f2^3 * f3^2 * x0 +
        f1 * f2^2 * f3^3 * x0
    ) / (
        f0^3 * f1^2 * f2 - f0^3 * f1^2 * f3 - f0^3 * f1 * f2^2 +
        f0^3 * f1 * f3^2 +
        f0^3 * f2^2 * f3 - f0^3 * f2 * f3^2 - f0^2 * f1^3 * f2 +
        f0^2 * f1^3 * f3 +
        f0^2 * f1 * f2^3 - f0^2 * f1 * f3^3 - f0^2 * f2^3 * f3 +
        f0^2 * f2 * f3^3 +
        f0 * f1^3 * f2^2 - f0 * f1^3 * f3^2 - f0 * f1^2 * f2^3 +
        f0 * f1^2 * f3^3 +
        f0 * f2^3 * f3^2 - f0 * f2^2 * f3^3 - f1^3 * f2^2 * f3 +
        f1^3 * f2 * f3^2 +
        f1^2 * f2^3 * f3 - f1^2 * f2 * f3^3 - f1 * f2^3 * f3^2 + f1 * f2^2 * f3^3
    )
end

function lmm(::LithBoonkkampIJzerman{5,0}, xs, fs)
    x0, x1, x2, x3, x4 = xs
    f0, f1, f2, f3, f4 = fs

    (
        f0^4 * f1^3 * f2^2 * f3 * x4 - f0^4 * f1^3 * f2^2 * f4 * x3 -
        f0^4 * f1^3 * f2 * f3^2 * x4 +
        f0^4 * f1^3 * f2 * f4^2 * x3 +
        f0^4 * f1^3 * f3^2 * f4 * x2 - f0^4 * f1^3 * f3 * f4^2 * x2 -
        f0^4 * f1^2 * f2^3 * f3 * x4 +
        f0^4 * f1^2 * f2^3 * f4 * x3 +
        f0^4 * f1^2 * f2 * f3^3 * x4 - f0^4 * f1^2 * f2 * f4^3 * x3 -
        f0^4 * f1^2 * f3^3 * f4 * x2 +
        f0^4 * f1^2 * f3 * f4^3 * x2 +
        f0^4 * f1 * f2^3 * f3^2 * x4 - f0^4 * f1 * f2^3 * f4^2 * x3 -
        f0^4 * f1 * f2^2 * f3^3 * x4 +
        f0^4 * f1 * f2^2 * f4^3 * x3 +
        f0^4 * f1 * f3^3 * f4^2 * x2 - f0^4 * f1 * f3^2 * f4^3 * x2 -
        f0^4 * f2^3 * f3^2 * f4 * x1 +
        f0^4 * f2^3 * f3 * f4^2 * x1 +
        f0^4 * f2^2 * f3^3 * f4 * x1 - f0^4 * f2^2 * f3 * f4^3 * x1 -
        f0^4 * f2 * f3^3 * f4^2 * x1 + f0^4 * f2 * f3^2 * f4^3 * x1 -
        f0^3 * f1^4 * f2^2 * f3 * x4 +
        f0^3 * f1^4 * f2^2 * f4 * x3 +
        f0^3 * f1^4 * f2 * f3^2 * x4 - f0^3 * f1^4 * f2 * f4^2 * x3 -
        f0^3 * f1^4 * f3^2 * f4 * x2 +
        f0^3 * f1^4 * f3 * f4^2 * x2 +
        f0^3 * f1^2 * f2^4 * f3 * x4 - f0^3 * f1^2 * f2^4 * f4 * x3 -
        f0^3 * f1^2 * f2 * f3^4 * x4 +
        f0^3 * f1^2 * f2 * f4^4 * x3 +
        f0^3 * f1^2 * f3^4 * f4 * x2 - f0^3 * f1^2 * f3 * f4^4 * x2 -
        f0^3 * f1 * f2^4 * f3^2 * x4 +
        f0^3 * f1 * f2^4 * f4^2 * x3 +
        f0^3 * f1 * f2^2 * f3^4 * x4 - f0^3 * f1 * f2^2 * f4^4 * x3 -
        f0^3 * f1 * f3^4 * f4^2 * x2 +
        f0^3 * f1 * f3^2 * f4^4 * x2 +
        f0^3 * f2^4 * f3^2 * f4 * x1 - f0^3 * f2^4 * f3 * f4^2 * x1 -
        f0^3 * f2^2 * f3^4 * f4 * x1 +
        f0^3 * f2^2 * f3 * f4^4 * x1 +
        f0^3 * f2 * f3^4 * f4^2 * x1 - f0^3 * f2 * f3^2 * f4^4 * x1 +
        f0^2 * f1^4 * f2^3 * f3 * x4 - f0^2 * f1^4 * f2^3 * f4 * x3 -
        f0^2 * f1^4 * f2 * f3^3 * x4 +
        f0^2 * f1^4 * f2 * f4^3 * x3 +
        f0^2 * f1^4 * f3^3 * f4 * x2 - f0^2 * f1^4 * f3 * f4^3 * x2 -
        f0^2 * f1^3 * f2^4 * f3 * x4 +
        f0^2 * f1^3 * f2^4 * f4 * x3 +
        f0^2 * f1^3 * f2 * f3^4 * x4 - f0^2 * f1^3 * f2 * f4^4 * x3 -
        f0^2 * f1^3 * f3^4 * f4 * x2 +
        f0^2 * f1^3 * f3 * f4^4 * x2 +
        f0^2 * f1 * f2^4 * f3^3 * x4 - f0^2 * f1 * f2^4 * f4^3 * x3 -
        f0^2 * f1 * f2^3 * f3^4 * x4 +
        f0^2 * f1 * f2^3 * f4^4 * x3 +
        f0^2 * f1 * f3^4 * f4^3 * x2 - f0^2 * f1 * f3^3 * f4^4 * x2 -
        f0^2 * f2^4 * f3^3 * f4 * x1 +
        f0^2 * f2^4 * f3 * f4^3 * x1 +
        f0^2 * f2^3 * f3^4 * f4 * x1 - f0^2 * f2^3 * f3 * f4^4 * x1 -
        f0^2 * f2 * f3^4 * f4^3 * x1 + f0^2 * f2 * f3^3 * f4^4 * x1 -
        f0 * f1^4 * f2^3 * f3^2 * x4 +
        f0 * f1^4 * f2^3 * f4^2 * x3 +
        f0 * f1^4 * f2^2 * f3^3 * x4 - f0 * f1^4 * f2^2 * f4^3 * x3 -
        f0 * f1^4 * f3^3 * f4^2 * x2 +
        f0 * f1^4 * f3^2 * f4^3 * x2 +
        f0 * f1^3 * f2^4 * f3^2 * x4 - f0 * f1^3 * f2^4 * f4^2 * x3 -
        f0 * f1^3 * f2^2 * f3^4 * x4 +
        f0 * f1^3 * f2^2 * f4^4 * x3 +
        f0 * f1^3 * f3^4 * f4^2 * x2 - f0 * f1^3 * f3^2 * f4^4 * x2 -
        f0 * f1^2 * f2^4 * f3^3 * x4 +
        f0 * f1^2 * f2^4 * f4^3 * x3 +
        f0 * f1^2 * f2^3 * f3^4 * x4 - f0 * f1^2 * f2^3 * f4^4 * x3 -
        f0 * f1^2 * f3^4 * f4^3 * x2 +
        f0 * f1^2 * f3^3 * f4^4 * x2 +
        f0 * f2^4 * f3^3 * f4^2 * x1 - f0 * f2^4 * f3^2 * f4^3 * x1 -
        f0 * f2^3 * f3^4 * f4^2 * x1 +
        f0 * f2^3 * f3^2 * f4^4 * x1 +
        f0 * f2^2 * f3^4 * f4^3 * x1 - f0 * f2^2 * f3^3 * f4^4 * x1 +
        f1^4 * f2^3 * f3^2 * f4 * x0 - f1^4 * f2^3 * f3 * f4^2 * x0 -
        f1^4 * f2^2 * f3^3 * f4 * x0 +
        f1^4 * f2^2 * f3 * f4^3 * x0 +
        f1^4 * f2 * f3^3 * f4^2 * x0 - f1^4 * f2 * f3^2 * f4^3 * x0 -
        f1^3 * f2^4 * f3^2 * f4 * x0 +
        f1^3 * f2^4 * f3 * f4^2 * x0 +
        f1^3 * f2^2 * f3^4 * f4 * x0 - f1^3 * f2^2 * f3 * f4^4 * x0 -
        f1^3 * f2 * f3^4 * f4^2 * x0 +
        f1^3 * f2 * f3^2 * f4^4 * x0 +
        f1^2 * f2^4 * f3^3 * f4 * x0 - f1^2 * f2^4 * f3 * f4^3 * x0 -
        f1^2 * f2^3 * f3^4 * f4 * x0 +
        f1^2 * f2^3 * f3 * f4^4 * x0 +
        f1^2 * f2 * f3^4 * f4^3 * x0 - f1^2 * f2 * f3^3 * f4^4 * x0 -
        f1 * f2^4 * f3^3 * f4^2 * x0 +
        f1 * f2^4 * f3^2 * f4^3 * x0 +
        f1 * f2^3 * f3^4 * f4^2 * x0 - f1 * f2^3 * f3^2 * f4^4 * x0 -
        f1 * f2^2 * f3^4 * f4^3 * x0 + f1 * f2^2 * f3^3 * f4^4 * x0
    ) / (
        f0^4 * f1^3 * f2^2 * f3 - f0^4 * f1^3 * f2^2 * f4 - f0^4 * f1^3 * f2 * f3^2 +
        f0^4 * f1^3 * f2 * f4^2 +
        f0^4 * f1^3 * f3^2 * f4 - f0^4 * f1^3 * f3 * f4^2 - f0^4 * f1^2 * f2^3 * f3 +
        f0^4 * f1^2 * f2^3 * f4 +
        f0^4 * f1^2 * f2 * f3^3 - f0^4 * f1^2 * f2 * f4^3 - f0^4 * f1^2 * f3^3 * f4 +
        f0^4 * f1^2 * f3 * f4^3 +
        f0^4 * f1 * f2^3 * f3^2 - f0^4 * f1 * f2^3 * f4^2 - f0^4 * f1 * f2^2 * f3^3 +
        f0^4 * f1 * f2^2 * f4^3 +
        f0^4 * f1 * f3^3 * f4^2 - f0^4 * f1 * f3^2 * f4^3 - f0^4 * f2^3 * f3^2 * f4 +
        f0^4 * f2^3 * f3 * f4^2 +
        f0^4 * f2^2 * f3^3 * f4 - f0^4 * f2^2 * f3 * f4^3 - f0^4 * f2 * f3^3 * f4^2 +
        f0^4 * f2 * f3^2 * f4^3 - f0^3 * f1^4 * f2^2 * f3 +
        f0^3 * f1^4 * f2^2 * f4 +
        f0^3 * f1^4 * f2 * f3^2 - f0^3 * f1^4 * f2 * f4^2 - f0^3 * f1^4 * f3^2 * f4 +
        f0^3 * f1^4 * f3 * f4^2 +
        f0^3 * f1^2 * f2^4 * f3 - f0^3 * f1^2 * f2^4 * f4 - f0^3 * f1^2 * f2 * f3^4 +
        f0^3 * f1^2 * f2 * f4^4 +
        f0^3 * f1^2 * f3^4 * f4 - f0^3 * f1^2 * f3 * f4^4 - f0^3 * f1 * f2^4 * f3^2 +
        f0^3 * f1 * f2^4 * f4^2 +
        f0^3 * f1 * f2^2 * f3^4 - f0^3 * f1 * f2^2 * f4^4 - f0^3 * f1 * f3^4 * f4^2 +
        f0^3 * f1 * f3^2 * f4^4 +
        f0^3 * f2^4 * f3^2 * f4 - f0^3 * f2^4 * f3 * f4^2 - f0^3 * f2^2 * f3^4 * f4 +
        f0^3 * f2^2 * f3 * f4^4 +
        f0^3 * f2 * f3^4 * f4^2 - f0^3 * f2 * f3^2 * f4^4 + f0^2 * f1^4 * f2^3 * f3 -
        f0^2 * f1^4 * f2^3 * f4 - f0^2 * f1^4 * f2 * f3^3 +
        f0^2 * f1^4 * f2 * f4^3 +
        f0^2 * f1^4 * f3^3 * f4 - f0^2 * f1^4 * f3 * f4^3 - f0^2 * f1^3 * f2^4 * f3 +
        f0^2 * f1^3 * f2^4 * f4 +
        f0^2 * f1^3 * f2 * f3^4 - f0^2 * f1^3 * f2 * f4^4 - f0^2 * f1^3 * f3^4 * f4 +
        f0^2 * f1^3 * f3 * f4^4 +
        f0^2 * f1 * f2^4 * f3^3 - f0^2 * f1 * f2^4 * f4^3 - f0^2 * f1 * f2^3 * f3^4 +
        f0^2 * f1 * f2^3 * f4^4 +
        f0^2 * f1 * f3^4 * f4^3 - f0^2 * f1 * f3^3 * f4^4 - f0^2 * f2^4 * f3^3 * f4 +
        f0^2 * f2^4 * f3 * f4^3 +
        f0^2 * f2^3 * f3^4 * f4 - f0^2 * f2^3 * f3 * f4^4 - f0^2 * f2 * f3^4 * f4^3 +
        f0^2 * f2 * f3^3 * f4^4 - f0 * f1^4 * f2^3 * f3^2 +
        f0 * f1^4 * f2^3 * f4^2 +
        f0 * f1^4 * f2^2 * f3^3 - f0 * f1^4 * f2^2 * f4^3 - f0 * f1^4 * f3^3 * f4^2 +
        f0 * f1^4 * f3^2 * f4^3 +
        f0 * f1^3 * f2^4 * f3^2 - f0 * f1^3 * f2^4 * f4^2 - f0 * f1^3 * f2^2 * f3^4 +
        f0 * f1^3 * f2^2 * f4^4 +
        f0 * f1^3 * f3^4 * f4^2 - f0 * f1^3 * f3^2 * f4^4 - f0 * f1^2 * f2^4 * f3^3 +
        f0 * f1^2 * f2^4 * f4^3 +
        f0 * f1^2 * f2^3 * f3^4 - f0 * f1^2 * f2^3 * f4^4 - f0 * f1^2 * f3^4 * f4^3 +
        f0 * f1^2 * f3^3 * f4^4 +
        f0 * f2^4 * f3^3 * f4^2 - f0 * f2^4 * f3^2 * f4^3 - f0 * f2^3 * f3^4 * f4^2 +
        f0 * f2^3 * f3^2 * f4^4 +
        f0 * f2^2 * f3^4 * f4^3 - f0 * f2^2 * f3^3 * f4^4 + f1^4 * f2^3 * f3^2 * f4 -
        f1^4 * f2^3 * f3 * f4^2 - f1^4 * f2^2 * f3^3 * f4 +
        f1^4 * f2^2 * f3 * f4^3 +
        f1^4 * f2 * f3^3 * f4^2 - f1^4 * f2 * f3^2 * f4^3 - f1^3 * f2^4 * f3^2 * f4 +
        f1^3 * f2^4 * f3 * f4^2 +
        f1^3 * f2^2 * f3^4 * f4 - f1^3 * f2^2 * f3 * f4^4 - f1^3 * f2 * f3^4 * f4^2 +
        f1^3 * f2 * f3^2 * f4^4 +
        f1^2 * f2^4 * f3^3 * f4 - f1^2 * f2^4 * f3 * f4^3 - f1^2 * f2^3 * f3^4 * f4 +
        f1^2 * f2^3 * f3 * f4^4 +
        f1^2 * f2 * f3^4 * f4^3 - f1^2 * f2 * f3^3 * f4^4 - f1 * f2^4 * f3^3 * f4^2 +
        f1 * f2^4 * f3^2 * f4^3 +
        f1 * f2^3 * f3^4 * f4^2 - f1 * f2^3 * f3^2 * f4^4 - f1 * f2^2 * f3^4 * f4^3 +
        f1 * f2^2 * f3^3 * f4^4
    )
end

function lmm(::LithBoonkkampIJzerman{6,0}, xs, fs)
    x0, x1, x2, x3, x4, x5 = xs
    f0, f1, f2, f3, f4, f5 = fs

    error("not implemented")
end

## d = 1; Newton-like

# return (as, bs⁰,[bs¹,...,bsⁿ⁻¹])
# where -∑ aᵢ xᵢ + h ⋅ ∑ₙ (∑ bsʲᵢ Fʲᵢ)l
function lmm_coefficients(::LithBoonkkampIJzerman{1,1}, xs, fs)
    a0 = -one(xs[1])
    b0 = one(fs[1])

    return (a0,), (b0,)
end

function lmm_coefficients(::LithBoonkkampIJzerman{2,1}, xs, fs)
    q = fs[1] / fs[2]

    # from the paper
    # x2 + a1 x1 + a0x0 =  h3 * (b1 * 1/fp1 + b0 * 1/fp0)
    a0 = (1 - 3q) / (q - 1)^3
    a1 = -1 - a0
    b0 = q / (q - 1)^2
    b1 = q * b0

    return (a0, a1), (b0, b1)
end

function lmm_coefficients(::LithBoonkkampIJzerman{3,1}, xs, fs)

    # from the paper
    q0 = fs[3 - 2] / fs[3]
    q1 = fs[3 - 1] / fs[3]

    a0 = (q1^2 * (q0 * (3 + 3q1 - 5q0) - q1)) / ((q0 - 1)^3 * (q0 - q1)^3)
    a1 = (q0^2 * (q1 * (5q1 - 3q0 - 3) + q0)) / ((q1 - 1)^3 * (q0 - q1)^3)
    a2 = (q0^2 * q1^2 * (3q1 - q0 * (q1 - 3) - 5)) / ((q0 - 1)^3 * (q1 - 1)^3) # minor typo in (27c)

    b0 = (q0 * q1^2) / ((q0 - 1)^2 * (q0 - q1)^2)
    b1 = (q0^2 * q1) / ((q0 - q1)^2 * (q1 - 1)^2)
    b2 = (q0^2 * q1^2) / ((q0 - 1)^2 * (q1 - 1)^2)

    return (a0, a1, a2), (b0, b1, b2)
end

function lmm_coefficients(::LithBoonkkampIJzerman{S,1}, xs, fs) where {S}
    error("not computed")
end

function lmm(L::LithBoonkkampIJzerman{S,1}, xs, fs, f′s) where {S}
    as, bs = lmm_coefficients(L, xs, fs)
    Fs = 1 ./ f′s # F = (g⁻¹)'
    h = -fs[S]

    -sum(as[i] * xs[i] for i in 1:S) + h * sum(bs[i] * Fs[i] for i in 1:S)
end

function lmm(::LithBoonkkampIJzerman{4,1}, xs, fs, f′s)
    x0, x1, x2, x3 = xs
    f0, f1, f2, f3 = fs
    f′0, f′1, f′2, f′3 = f′s

    # can get with script, but too long as found
    error("not implemented")
end

## d = 2; Halley-like
function lmm(::LithBoonkkampIJzerman{1,2}, xs, fs, f′s, f′′s)
    x0 = xs[1]
    f0 = fs[1]
    f′0 = f′s[1]
    f′′0 = f′′s[1]

    -f0^2 * f′′0 / (2 * f′0^3) - f0 / f′0 + x0
end

function lmm(::LithBoonkkampIJzerman{2,2}, xs, fs, f′s, f′′s)
    x0, x1 = xs
    f0, f1 = fs
    f′0, f′1 = f′s
    f′′0, f′′1 = f′′s

    (
        -f0^5 * f1^2 * f′0^3 * f′′1 / 2 - f0^5 * f1 * f′0^3 * f′1^2 +
        f0^5 * f′0^3 * f′1^3 * x1 +
        f0^4 * f1^3 * f′0^3 * f′′1 +
        f0^4 * f1^3 * f′1^3 * f′′0 / 2 +
        5 * f0^4 * f1^2 * f′0^3 * f′1^2 - 5 * f0^4 * f1 * f′0^3 * f′1^3 * x1 -
        f0^3 * f1^4 * f′0^3 * f′′1 / 2 - f0^3 * f1^4 * f′1^3 * f′′0 -
        4 * f0^3 * f1^3 * f′0^3 * f′1^2 +
        4 * f0^3 * f1^3 * f′0^2 * f′1^3 +
        10 * f0^3 * f1^2 * f′0^3 * f′1^3 * x1 +
        f0^2 * f1^5 * f′1^3 * f′′0 / 2 - 5 * f0^2 * f1^4 * f′0^2 * f′1^3 -
        10 * f0^2 * f1^3 * f′0^3 * f′1^3 * x0 +
        f0 * f1^5 * f′0^2 * f′1^3 +
        5 * f0 * f1^4 * f′0^3 * f′1^3 * x0 - f1^5 * f′0^3 * f′1^3 * x0
    ) / (
        f′0^3 *
        f′1^3 *
        (f0^5 - 5 * f0^4 * f1 + 10 * f0^3 * f1^2 - 10 * f0^2 * f1^3 + 5 * f0 * f1^4 - f1^5)
    )
end

function lmm(::LithBoonkkampIJzerman{3,2}, xs, fs, f′s, f′′s)
    x0, x1, x2 = xs
    f0, f1, f2 = fs
    f′0, f′1, f′2 = f′s
    f′′0, f′′1, f′′2 = f′′s

    ## can get from script, but too long for inclusion here
    error("not implemented")
end

function lmm(::LithBoonkkampIJzerman{4,2}, xs, fs, f′s, f′′s)
    error("Not computed")
end

## d = 3
function lmm(::LithBoonkkampIJzerman{1,3}, xs, fs, f′s, f′′s, f′′′s)
    x0 = xs[1]
    f0 = fs[1]
    f′0 = f′s[1]
    f′′0 = f′′s[1]
    f′′′0 = f′′′s[1]

    (
        f0^3 * (f′0 * f′′′0 - 3 * f′′0^2) / 6 - f0^2 * f′0^2 * f′′0 / 2 - f0 * f′0^4 +
        f′0^5 * x0
    ) / f′0^5
end

function lmm(::LithBoonkkampIJzerman{2,3}, xs, fs, f′s, f′′s, f′′′s)
    x0, x1 = xs
    f0, f1 = fs
    f′0, f′1 = f′s
    f′′0, f′′1 = f′′s
    f′′′0, f′′′1 = f′′′s

    (
        f0^7 * f1^3 * f′0^5 * f′1 * f′′′1 - 3 * f0^7 * f1^3 * f′0^5 * f′′1^2 -
        3 * f0^7 * f1^2 * f′0^5 * f′1^2 * f′′1 - 6 * f0^7 * f1 * f′0^5 * f′1^4 +
        6 * f0^7 * f′0^5 * f′1^5 * x1 - 3 * f0^6 * f1^4 * f′0^5 * f′1 * f′′′1 +
        9 * f0^6 * f1^4 * f′0^5 * f′′1^2 +
        f0^6 * f1^4 * f′0 * f′1^5 * f′′′0 - 3 * f0^6 * f1^4 * f′1^5 * f′′0^2 +
        21 * f0^6 * f1^3 * f′0^5 * f′1^2 * f′′1 +
        42 * f0^6 * f1^2 * f′0^5 * f′1^4 - 42 * f0^6 * f1 * f′0^5 * f′1^5 * x1 +
        3 * f0^5 * f1^5 * f′0^5 * f′1 * f′′′1 - 9 * f0^5 * f1^5 * f′0^5 * f′′1^2 -
        3 * f0^5 * f1^5 * f′0 * f′1^5 * f′′′0 + 9 * f0^5 * f1^5 * f′1^5 * f′′0^2 -
        33 * f0^5 * f1^4 * f′0^5 * f′1^2 * f′′1 - 15 * f0^5 * f1^4 * f′0^2 * f′1^5 * f′′0 -
        126 * f0^5 * f1^3 * f′0^5 * f′1^4 + 126 * f0^5 * f1^2 * f′0^5 * f′1^5 * x1 -
        f0^4 * f1^6 * f′0^5 * f′1 * f′′′1 +
        3 * f0^4 * f1^6 * f′0^5 * f′′1^2 +
        3 * f0^4 * f1^6 * f′0 * f′1^5 * f′′′0 - 9 * f0^4 * f1^6 * f′1^5 * f′′0^2 +
        15 * f0^4 * f1^5 * f′0^5 * f′1^2 * f′′1 +
        33 * f0^4 * f1^5 * f′0^2 * f′1^5 * f′′0 +
        90 * f0^4 * f1^4 * f′0^5 * f′1^4 - 90 * f0^4 * f1^4 * f′0^4 * f′1^5 -
        210 * f0^4 * f1^3 * f′0^5 * f′1^5 * x1 - f0^3 * f1^7 * f′0 * f′1^5 * f′′′0 +
        3 * f0^3 * f1^7 * f′1^5 * f′′0^2 - 21 * f0^3 * f1^6 * f′0^2 * f′1^5 * f′′0 +
        126 * f0^3 * f1^5 * f′0^4 * f′1^5 +
        210 * f0^3 * f1^4 * f′0^5 * f′1^5 * x0 +
        3 * f0^2 * f1^7 * f′0^2 * f′1^5 * f′′0 - 42 * f0^2 * f1^6 * f′0^4 * f′1^5 -
        126 * f0^2 * f1^5 * f′0^5 * f′1^5 * x0 +
        6 * f0 * f1^7 * f′0^4 * f′1^5 +
        42 * f0 * f1^6 * f′0^5 * f′1^5 * x0 - 6 * f1^7 * f′0^5 * f′1^5 * x0
    ) / (
        6 *
        f′0^5 *
        f′1^5 *
        (
            f0^7 - 7 * f0^6 * f1 + 21 * f0^5 * f1^2 - 35 * f0^4 * f1^3 + 35 * f0^3 * f1^4 -
            21 * f0^2 * f1^5 + 7 * f0 * f1^6 - f1^7
        )
    )
end

function lmm(::LithBoonkkampIJzerman{3,3}, xs, fs, f′s, f′′s, f′′′s)
    x0, x1, x2 = xs
    f0, f1, f2 = fs
    f′0, f′1, f′2 = f′s
    f′′0, f′′1, f′′2 = f′′s
    f′′′0, f′′′1, f′′′2 = f′′′s

    # can get from script, but too long for inclusion here
    error("not implemented")
end

function lmm(::LithBoonkkampIJzerman{4,3}, xs, fs, f′s, f′′s, f′′′s)
    x0, x1, x2, x3 = xs
    f0, f1, f2, f3 = fs
    f′0, f′1, f′2, f′3 = f′s
    f′′0, f′′1, f′′2, f′′3 = f′′s
    f′′′0, f′′′1, f′′′2, f′′′3 = f′′′s

    error("not computed")
end

## d = 4
function lmm(::LithBoonkkampIJzerman{1,4}, xs, fs, f′s, f′′s, f′′′s, f′′′′s)
    x0 = xs[1]
    f0 = fs[1]
    f′0 = f′s[1]
    f′′0 = f′′s[1]
    f′′′0 = f′′′s[1]
    f′′′′0 = f′′′′s[1]

    (
        -f0^4 * (f′0^2 * f′′′′0 + 10 * f′0 * f′′0 * f′′′0 + 15 * f′′0^2) / 24 +
        f0^3 * f′0^2 * (f′0 * f′′′0 - 3 * f′′0^2) / 6 - f0^2 * f′0^4 * f′′0 / 2 -
        f0 * f′0^6 + f′0^7 * x0
    ) / f′0^7
end

function lmm(::LithBoonkkampIJzerman{2,4}, xs, fs, f′s, f′′s, f′′′s, f′′′′s)
    x0, x1 = xs
    f0, f1 = fs
    f′0, f′1 = f′s
    f′′0, f′′1 = f′′s
    f′′′0, f′′′1 = f′′′s
    f′′′′0, f′′′′1 = f′′′′s

    (
        -f0^9 * f1^4 * f′0^7 * f′1^2 * f′′′′1 -
        10 * f0^9 * f1^4 * f′0^7 * f′1 * f′′1 * f′′′1 - 15 * f0^9 * f1^4 * f′0^7 * f′′1^2 +
        4 * f0^9 * f1^3 * f′0^7 * f′1^3 * f′′′1 -
        12 * f0^9 * f1^3 * f′0^7 * f′1^2 * f′′1^2 -
        12 * f0^9 * f1^2 * f′0^7 * f′1^4 * f′′1 - 24 * f0^9 * f1 * f′0^7 * f′1^6 +
        24 * f0^9 * f′0^7 * f′1^7 * x1 +
        4 * f0^8 * f1^5 * f′0^7 * f′1^2 * f′′′′1 +
        40 * f0^8 * f1^5 * f′0^7 * f′1 * f′′1 * f′′′1 +
        60 * f0^8 * f1^5 * f′0^7 * f′′1^2 +
        f0^8 * f1^5 * f′0^2 * f′1^7 * f′′′′0 +
        10 * f0^8 * f1^5 * f′0 * f′1^7 * f′′0 * f′′′0 +
        15 * f0^8 * f1^5 * f′1^7 * f′′0^2 - 36 * f0^8 * f1^4 * f′0^7 * f′1^3 * f′′′1 +
        108 * f0^8 * f1^4 * f′0^7 * f′1^2 * f′′1^2 +
        108 * f0^8 * f1^3 * f′0^7 * f′1^4 * f′′1 +
        216 * f0^8 * f1^2 * f′0^7 * f′1^6 - 216 * f0^8 * f1 * f′0^7 * f′1^7 * x1 -
        6 * f0^7 * f1^6 * f′0^7 * f′1^2 * f′′′′1 -
        60 * f0^7 * f1^6 * f′0^7 * f′1 * f′′1 * f′′′1 - 90 * f0^7 * f1^6 * f′0^7 * f′′1^2 -
        4 * f0^7 * f1^6 * f′0^2 * f′1^7 * f′′′′0 -
        40 * f0^7 * f1^6 * f′0 * f′1^7 * f′′0 * f′′′0 - 60 * f0^7 * f1^6 * f′1^7 * f′′0^2 +
        84 * f0^7 * f1^5 * f′0^7 * f′1^3 * f′′′1 -
        252 * f0^7 * f1^5 * f′0^7 * f′1^2 * f′′1^2 -
        24 * f0^7 * f1^5 * f′0^3 * f′1^7 * f′′′0 +
        72 * f0^7 * f1^5 * f′0^2 * f′1^7 * f′′0^2 -
        432 * f0^7 * f1^4 * f′0^7 * f′1^4 * f′′1 - 864 * f0^7 * f1^3 * f′0^7 * f′1^6 +
        864 * f0^7 * f1^2 * f′0^7 * f′1^7 * x1 +
        4 * f0^6 * f1^7 * f′0^7 * f′1^2 * f′′′′1 +
        40 * f0^6 * f1^7 * f′0^7 * f′1 * f′′1 * f′′′1 +
        60 * f0^6 * f1^7 * f′0^7 * f′′1^2 +
        6 * f0^6 * f1^7 * f′0^2 * f′1^7 * f′′′′0 +
        60 * f0^6 * f1^7 * f′0 * f′1^7 * f′′0 * f′′′0 +
        90 * f0^6 * f1^7 * f′1^7 * f′′0^2 - 76 * f0^6 * f1^6 * f′0^7 * f′1^3 * f′′′1 +
        228 * f0^6 * f1^6 * f′0^7 * f′1^2 * f′′1^2 +
        76 * f0^6 * f1^6 * f′0^3 * f′1^7 * f′′′0 -
        228 * f0^6 * f1^6 * f′0^2 * f′1^7 * f′′0^2 +
        588 * f0^6 * f1^5 * f′0^7 * f′1^4 * f′′1 +
        252 * f0^6 * f1^5 * f′0^4 * f′1^7 * f′′0 +
        2016 * f0^6 * f1^4 * f′0^7 * f′1^6 - 2016 * f0^6 * f1^3 * f′0^7 * f′1^7 * x1 -
        f0^5 * f1^8 * f′0^7 * f′1^2 * f′′′′1 -
        10 * f0^5 * f1^8 * f′0^7 * f′1 * f′′1 * f′′′1 - 15 * f0^5 * f1^8 * f′0^7 * f′′1^2 -
        4 * f0^5 * f1^8 * f′0^2 * f′1^7 * f′′′′0 -
        40 * f0^5 * f1^8 * f′0 * f′1^7 * f′′0 * f′′′0 - 60 * f0^5 * f1^8 * f′1^7 * f′′0^2 +
        24 * f0^5 * f1^7 * f′0^7 * f′1^3 * f′′′1 -
        72 * f0^5 * f1^7 * f′0^7 * f′1^2 * f′′1^2 -
        84 * f0^5 * f1^7 * f′0^3 * f′1^7 * f′′′0 +
        252 * f0^5 * f1^7 * f′0^2 * f′1^7 * f′′0^2 -
        252 * f0^5 * f1^6 * f′0^7 * f′1^4 * f′′1 -
        588 * f0^5 * f1^6 * f′0^4 * f′1^7 * f′′0 - 1344 * f0^5 * f1^5 * f′0^7 * f′1^6 +
        1344 * f0^5 * f1^5 * f′0^6 * f′1^7 +
        3024 * f0^5 * f1^4 * f′0^7 * f′1^7 * x1 +
        f0^4 * f1^9 * f′0^2 * f′1^7 * f′′′′0 +
        10 * f0^4 * f1^9 * f′0 * f′1^7 * f′′0 * f′′′0 +
        15 * f0^4 * f1^9 * f′1^7 * f′′0^2 +
        36 * f0^4 * f1^8 * f′0^3 * f′1^7 * f′′′0 -
        108 * f0^4 * f1^8 * f′0^2 * f′1^7 * f′′0^2 +
        432 * f0^4 * f1^7 * f′0^4 * f′1^7 * f′′0 - 2016 * f0^4 * f1^6 * f′0^6 * f′1^7 -
        3024 * f0^4 * f1^5 * f′0^7 * f′1^7 * x0 - 4 * f0^3 * f1^9 * f′0^3 * f′1^7 * f′′′0 +
        12 * f0^3 * f1^9 * f′0^2 * f′1^7 * f′′0^2 -
        108 * f0^3 * f1^8 * f′0^4 * f′1^7 * f′′0 +
        864 * f0^3 * f1^7 * f′0^6 * f′1^7 +
        2016 * f0^3 * f1^6 * f′0^7 * f′1^7 * x0 +
        12 * f0^2 * f1^9 * f′0^4 * f′1^7 * f′′0 - 216 * f0^2 * f1^8 * f′0^6 * f′1^7 -
        864 * f0^2 * f1^7 * f′0^7 * f′1^7 * x0 +
        24 * f0 * f1^9 * f′0^6 * f′1^7 +
        216 * f0 * f1^8 * f′0^7 * f′1^7 * x0 - 24 * f1^9 * f′0^7 * f′1^7 * x0
    ) / (
        24 *
        f′0^7 *
        f′1^7 *
        (
            f0^9 - 9 * f0^8 * f1 + 36 * f0^7 * f1^2 - 84 * f0^6 * f1^3 + 126 * f0^5 * f1^4 -
            126 * f0^4 * f1^5 + 84 * f0^3 * f1^6 - 36 * f0^2 * f1^7 + 9 * f0 * f1^8 - f1^9
        )
    )
end

function lmm(::LithBoonkkampIJzerman{3,4}, xs, fs, f′s, f′′s, f′′′s, f′′′′s)
    x0, x1, x2 = xs
    f0, f1, f2 = fs
    f′0, f′1, f′2 = f′s
    f′′0, f′′1, f′′2 = f′′s
    f′′′0, f′′′1, f′′′2 = f′′′s
    f′′′′0, f′′′′1, f′′′′2 = f′′′′s

    error("not computed")
end

# n = 5

function lmm(::LithBoonkkampIJzerman{1,5}, xs, fs, f′s, f′′s, f′′′s, f′′′′s, f′′′′′s)
    x0 = xs[1]
    f0 = fs[1]
    f′0 = f′s[1]
    f′′0 = f′′s[1]
    f′′′0 = f′′′s[1]
    f′′′′0 = f′′′′s[1]
    f′′′′′0 = f′′′′′s[1]

    (
        f0^5 * (
            f′0^3 * f′′′′′0 - 15 * f′0^2 * f′′0 * f′′′′0 - 10 * f′0^2 * f′′′0^2 +
            105 * f′0 * f′′0^2 * f′′′0 - 105 * f′′0^4
        ) - 5 * f0^4 * f′0^2 * (f′0^2 * f′′′′0 + 10 * f′0 * f′′0 * f′′′0 + 15 * f′′0^2) +
        20 * f0^3 * f′0^4 * (f′0 * f′′′0 - 3 * f′′0^2) - 60 * f0^2 * f′0^6 * f′′0 -
        120 * f0 * f′0^8 + 120 * f′0^9 * x0
    ) / (120 * f′0^9)
end

function lmm(::LithBoonkkampIJzerman{2,5}, xs, fs, f′s, f′′s, f′′′s, f′′′′s, f′′′′′s)
    x0, x1 = xs
    f0, f1 = fs
    f′0, f′1 = f′s
    f′′0, f′′1 = f′′s
    f′′′0, f′′′1 = f′′′s
    f′′′′0, f′′′′1 = f′′′′s
    f′′′′′0, f′′′′′1 = f′′′′′s

    (
        f0^11 * f1^5 * f′0^9 * f′1^3 * f′′′′′1 -
        15 * f0^11 * f1^5 * f′0^9 * f′1^2 * f′′1 * f′′′′1 -
        10 * f0^11 * f1^5 * f′0^9 * f′1^2 * f′′′1^2 +
        105 * f0^11 * f1^5 * f′0^9 * f′1 * f′′1^2 * f′′′1 -
        105 * f0^11 * f1^5 * f′0^9 * f′′1^4 - 5 * f0^11 * f1^4 * f′0^9 * f′1^4 * f′′′′1 -
        50 * f0^11 * f1^4 * f′0^9 * f′1^3 * f′′1 * f′′′1 -
        75 * f0^11 * f1^4 * f′0^9 * f′1^2 * f′′1^2 +
        20 * f0^11 * f1^3 * f′0^9 * f′1^5 * f′′′1 -
        60 * f0^11 * f1^3 * f′0^9 * f′1^4 * f′′1^2 -
        60 * f0^11 * f1^2 * f′0^9 * f′1^6 * f′′1 - 120 * f0^11 * f1 * f′0^9 * f′1^8 +
        120 * f0^11 * f′0^9 * f′1^9 * x1 - 5 * f0^10 * f1^6 * f′0^9 * f′1^3 * f′′′′′1 +
        75 * f0^10 * f1^6 * f′0^9 * f′1^2 * f′′1 * f′′′′1 +
        50 * f0^10 * f1^6 * f′0^9 * f′1^2 * f′′′1^2 -
        525 * f0^10 * f1^6 * f′0^9 * f′1 * f′′1^2 * f′′′1 +
        525 * f0^10 * f1^6 * f′0^9 * f′′1^4 +
        f0^10 * f1^6 * f′0^3 * f′1^9 * f′′′′′0 -
        15 * f0^10 * f1^6 * f′0^2 * f′1^9 * f′′0 * f′′′′0 -
        10 * f0^10 * f1^6 * f′0^2 * f′1^9 * f′′′0^2 +
        105 * f0^10 * f1^6 * f′0 * f′1^9 * f′′0^2 * f′′′0 -
        105 * f0^10 * f1^6 * f′1^9 * f′′0^4 +
        55 * f0^10 * f1^5 * f′0^9 * f′1^4 * f′′′′1 +
        550 * f0^10 * f1^5 * f′0^9 * f′1^3 * f′′1 * f′′′1 +
        825 * f0^10 * f1^5 * f′0^9 * f′1^2 * f′′1^2 -
        220 * f0^10 * f1^4 * f′0^9 * f′1^5 * f′′′1 +
        660 * f0^10 * f1^4 * f′0^9 * f′1^4 * f′′1^2 +
        660 * f0^10 * f1^3 * f′0^9 * f′1^6 * f′′1 +
        1320 * f0^10 * f1^2 * f′0^9 * f′1^8 - 1320 * f0^10 * f1 * f′0^9 * f′1^9 * x1 +
        10 * f0^9 * f1^7 * f′0^9 * f′1^3 * f′′′′′1 -
        150 * f0^9 * f1^7 * f′0^9 * f′1^2 * f′′1 * f′′′′1 -
        100 * f0^9 * f1^7 * f′0^9 * f′1^2 * f′′′1^2 +
        1050 * f0^9 * f1^7 * f′0^9 * f′1 * f′′1^2 * f′′′1 -
        1050 * f0^9 * f1^7 * f′0^9 * f′′1^4 - 5 * f0^9 * f1^7 * f′0^3 * f′1^9 * f′′′′′0 +
        75 * f0^9 * f1^7 * f′0^2 * f′1^9 * f′′0 * f′′′′0 +
        50 * f0^9 * f1^7 * f′0^2 * f′1^9 * f′′′0^2 -
        525 * f0^9 * f1^7 * f′0 * f′1^9 * f′′0^2 * f′′′0 +
        525 * f0^9 * f1^7 * f′1^9 * f′′0^4 - 170 * f0^9 * f1^6 * f′0^9 * f′1^4 * f′′′′1 -
        1700 * f0^9 * f1^6 * f′0^9 * f′1^3 * f′′1 * f′′′1 -
        2550 * f0^9 * f1^6 * f′0^9 * f′1^2 * f′′1^2 -
        35 * f0^9 * f1^6 * f′0^4 * f′1^9 * f′′′′0 -
        350 * f0^9 * f1^6 * f′0^3 * f′1^9 * f′′0 * f′′′0 -
        525 * f0^9 * f1^6 * f′0^2 * f′1^9 * f′′0^2 +
        1100 * f0^9 * f1^5 * f′0^9 * f′1^5 * f′′′1 -
        3300 * f0^9 * f1^5 * f′0^9 * f′1^4 * f′′1^2 -
        3300 * f0^9 * f1^4 * f′0^9 * f′1^6 * f′′1 - 6600 * f0^9 * f1^3 * f′0^9 * f′1^8 +
        6600 * f0^9 * f1^2 * f′0^9 * f′1^9 * x1 -
        10 * f0^8 * f1^8 * f′0^9 * f′1^3 * f′′′′′1 +
        150 * f0^8 * f1^8 * f′0^9 * f′1^2 * f′′1 * f′′′′1 +
        100 * f0^8 * f1^8 * f′0^9 * f′1^2 * f′′′1^2 -
        1050 * f0^8 * f1^8 * f′0^9 * f′1 * f′′1^2 * f′′′1 +
        1050 * f0^8 * f1^8 * f′0^9 * f′′1^4 +
        10 * f0^8 * f1^8 * f′0^3 * f′1^9 * f′′′′′0 -
        150 * f0^8 * f1^8 * f′0^2 * f′1^9 * f′′0 * f′′′′0 -
        100 * f0^8 * f1^8 * f′0^2 * f′1^9 * f′′′0^2 +
        1050 * f0^8 * f1^8 * f′0 * f′1^9 * f′′0^2 * f′′′0 -
        1050 * f0^8 * f1^8 * f′1^9 * f′′0^4 +
        230 * f0^8 * f1^7 * f′0^9 * f′1^4 * f′′′′1 +
        2300 * f0^8 * f1^7 * f′0^9 * f′1^3 * f′′1 * f′′′1 +
        3450 * f0^8 * f1^7 * f′0^9 * f′1^2 * f′′1^2 +
        145 * f0^8 * f1^7 * f′0^4 * f′1^9 * f′′′′0 +
        1450 * f0^8 * f1^7 * f′0^3 * f′1^9 * f′′0 * f′′′0 +
        2175 * f0^8 * f1^7 * f′0^2 * f′1^9 * f′′0^2 -
        2180 * f0^8 * f1^6 * f′0^9 * f′1^5 * f′′′1 +
        6540 * f0^8 * f1^6 * f′0^9 * f′1^4 * f′′1^2 +
        560 * f0^8 * f1^6 * f′0^5 * f′1^9 * f′′′0 -
        1680 * f0^8 * f1^6 * f′0^4 * f′1^9 * f′′0^2 +
        9900 * f0^8 * f1^5 * f′0^9 * f′1^6 * f′′1 +
        19800 * f0^8 * f1^4 * f′0^9 * f′1^8 - 19800 * f0^8 * f1^3 * f′0^9 * f′1^9 * x1 +
        5 * f0^7 * f1^9 * f′0^9 * f′1^3 * f′′′′′1 -
        75 * f0^7 * f1^9 * f′0^9 * f′1^2 * f′′1 * f′′′′1 -
        50 * f0^7 * f1^9 * f′0^9 * f′1^2 * f′′′1^2 +
        525 * f0^7 * f1^9 * f′0^9 * f′1 * f′′1^2 * f′′′1 -
        525 * f0^7 * f1^9 * f′0^9 * f′′1^4 - 10 * f0^7 * f1^9 * f′0^3 * f′1^9 * f′′′′′0 +
        150 * f0^7 * f1^9 * f′0^2 * f′1^9 * f′′0 * f′′′′0 +
        100 * f0^7 * f1^9 * f′0^2 * f′1^9 * f′′′0^2 -
        1050 * f0^7 * f1^9 * f′0 * f′1^9 * f′′0^2 * f′′′0 +
        1050 * f0^7 * f1^9 * f′1^9 * f′′0^4 - 145 * f0^7 * f1^8 * f′0^9 * f′1^4 * f′′′′1 -
        1450 * f0^7 * f1^8 * f′0^9 * f′1^3 * f′′1 * f′′′1 -
        2175 * f0^7 * f1^8 * f′0^9 * f′1^2 * f′′1^2 -
        230 * f0^7 * f1^8 * f′0^4 * f′1^9 * f′′′′0 -
        2300 * f0^7 * f1^8 * f′0^3 * f′1^9 * f′′0 * f′′′0 -
        3450 * f0^7 * f1^8 * f′0^2 * f′1^9 * f′′0^2 +
        1840 * f0^7 * f1^7 * f′0^9 * f′1^5 * f′′′1 -
        5520 * f0^7 * f1^7 * f′0^9 * f′1^4 * f′′1^2 -
        1840 * f0^7 * f1^7 * f′0^5 * f′1^9 * f′′′0 +
        5520 * f0^7 * f1^7 * f′0^4 * f′1^9 * f′′0^2 -
        12240 * f0^7 * f1^6 * f′0^9 * f′1^6 * f′′1 -
        5040 * f0^7 * f1^6 * f′0^6 * f′1^9 * f′′0 - 39600 * f0^7 * f1^5 * f′0^9 * f′1^8 +
        39600 * f0^7 * f1^4 * f′0^9 * f′1^9 * x1 - f0^6 * f1^10 * f′0^9 * f′1^3 * f′′′′′1 +
        15 * f0^6 * f1^10 * f′0^9 * f′1^2 * f′′1 * f′′′′1 +
        10 * f0^6 * f1^10 * f′0^9 * f′1^2 * f′′′1^2 -
        105 * f0^6 * f1^10 * f′0^9 * f′1 * f′′1^2 * f′′′1 +
        105 * f0^6 * f1^10 * f′0^9 * f′′1^4 +
        5 * f0^6 * f1^10 * f′0^3 * f′1^9 * f′′′′′0 -
        75 * f0^6 * f1^10 * f′0^2 * f′1^9 * f′′0 * f′′′′0 -
        50 * f0^6 * f1^10 * f′0^2 * f′1^9 * f′′′0^2 +
        525 * f0^6 * f1^10 * f′0 * f′1^9 * f′′0^2 * f′′′0 -
        525 * f0^6 * f1^10 * f′1^9 * f′′0^4 +
        35 * f0^6 * f1^9 * f′0^9 * f′1^4 * f′′′′1 +
        350 * f0^6 * f1^9 * f′0^9 * f′1^3 * f′′1 * f′′′1 +
        525 * f0^6 * f1^9 * f′0^9 * f′1^2 * f′′1^2 +
        170 * f0^6 * f1^9 * f′0^4 * f′1^9 * f′′′′0 +
        1700 * f0^6 * f1^9 * f′0^3 * f′1^9 * f′′0 * f′′′0 +
        2550 * f0^6 * f1^9 * f′0^2 * f′1^9 * f′′0^2 -
        560 * f0^6 * f1^8 * f′0^9 * f′1^5 * f′′′1 +
        1680 * f0^6 * f1^8 * f′0^9 * f′1^4 * f′′1^2 +
        2180 * f0^6 * f1^8 * f′0^5 * f′1^9 * f′′′0 -
        6540 * f0^6 * f1^8 * f′0^4 * f′1^9 * f′′0^2 +
        5040 * f0^6 * f1^7 * f′0^9 * f′1^6 * f′′1 +
        12240 * f0^6 * f1^7 * f′0^6 * f′1^9 * f′′0 +
        25200 * f0^6 * f1^6 * f′0^9 * f′1^8 - 25200 * f0^6 * f1^6 * f′0^8 * f′1^9 -
        55440 * f0^6 * f1^5 * f′0^9 * f′1^9 * x1 - f0^5 * f1^11 * f′0^3 * f′1^9 * f′′′′′0 +
        15 * f0^5 * f1^11 * f′0^2 * f′1^9 * f′′0 * f′′′′0 +
        10 * f0^5 * f1^11 * f′0^2 * f′1^9 * f′′′0^2 -
        105 * f0^5 * f1^11 * f′0 * f′1^9 * f′′0^2 * f′′′0 +
        105 * f0^5 * f1^11 * f′1^9 * f′′0^4 - 55 * f0^5 * f1^10 * f′0^4 * f′1^9 * f′′′′0 -
        550 * f0^5 * f1^10 * f′0^3 * f′1^9 * f′′0 * f′′′0 -
        825 * f0^5 * f1^10 * f′0^2 * f′1^9 * f′′0^2 -
        1100 * f0^5 * f1^9 * f′0^5 * f′1^9 * f′′′0 +
        3300 * f0^5 * f1^9 * f′0^4 * f′1^9 * f′′0^2 -
        9900 * f0^5 * f1^8 * f′0^6 * f′1^9 * f′′0 +
        39600 * f0^5 * f1^7 * f′0^8 * f′1^9 +
        55440 * f0^5 * f1^6 * f′0^9 * f′1^9 * x0 +
        5 * f0^4 * f1^11 * f′0^4 * f′1^9 * f′′′′0 +
        50 * f0^4 * f1^11 * f′0^3 * f′1^9 * f′′0 * f′′′0 +
        75 * f0^4 * f1^11 * f′0^2 * f′1^9 * f′′0^2 +
        220 * f0^4 * f1^10 * f′0^5 * f′1^9 * f′′′0 -
        660 * f0^4 * f1^10 * f′0^4 * f′1^9 * f′′0^2 +
        3300 * f0^4 * f1^9 * f′0^6 * f′1^9 * f′′0 - 19800 * f0^4 * f1^8 * f′0^8 * f′1^9 -
        39600 * f0^4 * f1^7 * f′0^9 * f′1^9 * x0 -
        20 * f0^3 * f1^11 * f′0^5 * f′1^9 * f′′′0 +
        60 * f0^3 * f1^11 * f′0^4 * f′1^9 * f′′0^2 -
        660 * f0^3 * f1^10 * f′0^6 * f′1^9 * f′′0 +
        6600 * f0^3 * f1^9 * f′0^8 * f′1^9 +
        19800 * f0^3 * f1^8 * f′0^9 * f′1^9 * x0 +
        60 * f0^2 * f1^11 * f′0^6 * f′1^9 * f′′0 - 1320 * f0^2 * f1^10 * f′0^8 * f′1^9 -
        6600 * f0^2 * f1^9 * f′0^9 * f′1^9 * x0 +
        120 * f0 * f1^11 * f′0^8 * f′1^9 +
        1320 * f0 * f1^10 * f′0^9 * f′1^9 * x0 - 120 * f1^11 * f′0^9 * f′1^9 * x0
    ) / (
        120 *
        f′0^9 *
        f′1^9 *
        (
            f0^11 - 11 * f0^10 * f1 + 55 * f0^9 * f1^2 - 165 * f0^8 * f1^3 +
            330 * f0^7 * f1^4 - 462 * f0^6 * f1^5 + 462 * f0^5 * f1^6 - 330 * f0^4 * f1^7 +
            165 * f0^3 * f1^8 - 55 * f0^2 * f1^9 + 11 * f0 * f1^10 - f1^11
        )
    )
end

function lmm(::LithBoonkkampIJzerman{3,5}, xs, fs, f′s, f′′s, f′′′s, f′′′′s, f′′′′′s)
    x0, x1, x2 = xs
    f0, f1, f2 = fs
    f′0, f′1, f′2 = f′s
    f′′0, f′′1, f′′2 = f′′s
    f′′′0, f′′′1, f′′′2 = f′′′s
    f′′′′0, f′′′′1, f′′′′2 = f′′′′s
    f′′′′′0, f′′′′′1, f′′′′′2 = f′′′′′s

    error("not computed")
end

## n = 6
function lmm(
    ::LithBoonkkampIJzerman{1,6},
    xs,
    fs,
    f′s,
    f′′s,
    f′′′s,
    f′′′′s,
    f′′′′′s,
    f′′′′′′s,
)
    x0 = xs[1]
    f0 = fs[1]
    f′0 = f′s[1]
    f′′0 = f′′s[1]
    f′′′0 = f′′′s[1]
    f′′′′0 = f′′′′s[1]
    f′′′′′0 = f′′′′′s[1]
    f′′′′′′0 = f′′′′′′s[1]

    (
        f0^6 * (
            -f′0^4 * f′′′′′′0 + 21 * f′0^3 * f′′0 * f′′′′′0 + 35 * f′0^3 * f′′′0 * f′′′′0 -
            210 * f′0^2 * f′′0^2 * f′′′′0 - 280 * f′0^2 * f′′0 * f′′′0^2 +
            1260 * f′0 * f′′0^3 * f′′′0 - 945 * f′′0^5
        ) +
        6 *
        f0^5 *
        f′0^2 *
        (
            f′0^3 * f′′′′′0 - 15 * f′0^2 * f′′0 * f′′′′0 - 10 * f′0^2 * f′′′0^2 +
            105 * f′0 * f′′0^2 * f′′′0 - 105 * f′′0^4
        ) - 30 * f0^4 * f′0^4 * (f′0^2 * f′′′′0 + 10 * f′0 * f′′0 * f′′′0 + 15 * f′′0^2) +
        120 * f0^3 * f′0^6 * (f′0 * f′′′0 - 3 * f′′0^2) - 360 * f0^2 * f′0^8 * f′′0 -
        720 * f0 * f′0^10 + 720 * f′0^11 * x0
    ) / (720 * f′0^11)
end

function lmm(
    ::LithBoonkkampIJzerman{2,6},
    xs,
    fs,
    f′s,
    f′′s,
    f′′′s,
    f′′′′s,
    f′′′′′s,
    f′′′′′′s,
)
    x0, x1 = xs
    f0, f1 = fs
    f′0, f′1 = f′s
    f′′0, f′′1 = f′′s
    f′′′0, f′′′1 = f′′′s
    f′′′′0, f′′′′1 = f′′′′s
    f′′′′′0, f′′′′′1 = f′′′′′s
    f′′′′′′0, f′′′′′′1 = f′′′′′′s

    (
        -f0^13 * f1^6 * f′0^11 * f′1^4 * f′′′′′′1 +
        21 * f0^13 * f1^6 * f′0^11 * f′1^3 * f′′1 * f′′′′′1 +
        35 * f0^13 * f1^6 * f′0^11 * f′1^3 * f′′′1 * f′′′′1 -
        210 * f0^13 * f1^6 * f′0^11 * f′1^2 * f′′1^2 * f′′′′1 -
        280 * f0^13 * f1^6 * f′0^11 * f′1^2 * f′′1 * f′′′1^2 +
        1260 * f0^13 * f1^6 * f′0^11 * f′1 * f′′1^3 * f′′′1 -
        945 * f0^13 * f1^6 * f′0^11 * f′′1^5 + 6 * f0^13 * f1^5 * f′0^11 * f′1^5 * f′′′′′1 -
        90 * f0^13 * f1^5 * f′0^11 * f′1^4 * f′′1 * f′′′′1 -
        60 * f0^13 * f1^5 * f′0^11 * f′1^4 * f′′′1^2 +
        630 * f0^13 * f1^5 * f′0^11 * f′1^3 * f′′1^2 * f′′′1 -
        630 * f0^13 * f1^5 * f′0^11 * f′1^2 * f′′1^4 -
        30 * f0^13 * f1^4 * f′0^11 * f′1^6 * f′′′′1 -
        300 * f0^13 * f1^4 * f′0^11 * f′1^5 * f′′1 * f′′′1 -
        450 * f0^13 * f1^4 * f′0^11 * f′1^4 * f′′1^2 +
        120 * f0^13 * f1^3 * f′0^11 * f′1^7 * f′′′1 -
        360 * f0^13 * f1^3 * f′0^11 * f′1^6 * f′′1^2 -
        360 * f0^13 * f1^2 * f′0^11 * f′1^8 * f′′1 - 720 * f0^13 * f1 * f′0^11 * f′1^10 +
        720 * f0^13 * f′0^11 * f′1^11 * x1 +
        6 * f0^12 * f1^7 * f′0^11 * f′1^4 * f′′′′′′1 -
        126 * f0^12 * f1^7 * f′0^11 * f′1^3 * f′′1 * f′′′′′1 -
        210 * f0^12 * f1^7 * f′0^11 * f′1^3 * f′′′1 * f′′′′1 +
        1260 * f0^12 * f1^7 * f′0^11 * f′1^2 * f′′1^2 * f′′′′1 +
        1680 * f0^12 * f1^7 * f′0^11 * f′1^2 * f′′1 * f′′′1^2 -
        7560 * f0^12 * f1^7 * f′0^11 * f′1 * f′′1^3 * f′′′1 +
        5670 * f0^12 * f1^7 * f′0^11 * f′′1^5 +
        f0^12 * f1^7 * f′0^4 * f′1^11 * f′′′′′′0 -
        21 * f0^12 * f1^7 * f′0^3 * f′1^11 * f′′0 * f′′′′′0 -
        35 * f0^12 * f1^7 * f′0^3 * f′1^11 * f′′′0 * f′′′′0 +
        210 * f0^12 * f1^7 * f′0^2 * f′1^11 * f′′0^2 * f′′′′0 +
        280 * f0^12 * f1^7 * f′0^2 * f′1^11 * f′′0 * f′′′0^2 -
        1260 * f0^12 * f1^7 * f′0 * f′1^11 * f′′0^3 * f′′′0 +
        945 * f0^12 * f1^7 * f′1^11 * f′′0^5 -
        78 * f0^12 * f1^6 * f′0^11 * f′1^5 * f′′′′′1 +
        1170 * f0^12 * f1^6 * f′0^11 * f′1^4 * f′′1 * f′′′′1 +
        780 * f0^12 * f1^6 * f′0^11 * f′1^4 * f′′′1^2 -
        8190 * f0^12 * f1^6 * f′0^11 * f′1^3 * f′′1^2 * f′′′1 +
        8190 * f0^12 * f1^6 * f′0^11 * f′1^2 * f′′1^4 +
        390 * f0^12 * f1^5 * f′0^11 * f′1^6 * f′′′′1 +
        3900 * f0^12 * f1^5 * f′0^11 * f′1^5 * f′′1 * f′′′1 +
        5850 * f0^12 * f1^5 * f′0^11 * f′1^4 * f′′1^2 -
        1560 * f0^12 * f1^4 * f′0^11 * f′1^7 * f′′′1 +
        4680 * f0^12 * f1^4 * f′0^11 * f′1^6 * f′′1^2 +
        4680 * f0^12 * f1^3 * f′0^11 * f′1^8 * f′′1 +
        9360 * f0^12 * f1^2 * f′0^11 * f′1^10 - 9360 * f0^12 * f1 * f′0^11 * f′1^11 * x1 -
        15 * f0^11 * f1^8 * f′0^11 * f′1^4 * f′′′′′′1 +
        315 * f0^11 * f1^8 * f′0^11 * f′1^3 * f′′1 * f′′′′′1 +
        525 * f0^11 * f1^8 * f′0^11 * f′1^3 * f′′′1 * f′′′′1 -
        3150 * f0^11 * f1^8 * f′0^11 * f′1^2 * f′′1^2 * f′′′′1 -
        4200 * f0^11 * f1^8 * f′0^11 * f′1^2 * f′′1 * f′′′1^2 +
        18900 * f0^11 * f1^8 * f′0^11 * f′1 * f′′1^3 * f′′′1 -
        14175 * f0^11 * f1^8 * f′0^11 * f′′1^5 -
        6 * f0^11 * f1^8 * f′0^4 * f′1^11 * f′′′′′′0 +
        126 * f0^11 * f1^8 * f′0^3 * f′1^11 * f′′0 * f′′′′′0 +
        210 * f0^11 * f1^8 * f′0^3 * f′1^11 * f′′′0 * f′′′′0 -
        1260 * f0^11 * f1^8 * f′0^2 * f′1^11 * f′′0^2 * f′′′′0 -
        1680 * f0^11 * f1^8 * f′0^2 * f′1^11 * f′′0 * f′′′0^2 +
        7560 * f0^11 * f1^8 * f′0 * f′1^11 * f′′0^3 * f′′′0 -
        5670 * f0^11 * f1^8 * f′1^11 * f′′0^5 +
        300 * f0^11 * f1^7 * f′0^11 * f′1^5 * f′′′′′1 -
        4500 * f0^11 * f1^7 * f′0^11 * f′1^4 * f′′1 * f′′′′1 -
        3000 * f0^11 * f1^7 * f′0^11 * f′1^4 * f′′′1^2 +
        31500 * f0^11 * f1^7 * f′0^11 * f′1^3 * f′′1^2 * f′′′1 -
        31500 * f0^11 * f1^7 * f′0^11 * f′1^2 * f′′1^4 -
        48 * f0^11 * f1^7 * f′0^5 * f′1^11 * f′′′′′0 +
        720 * f0^11 * f1^7 * f′0^4 * f′1^11 * f′′0 * f′′′′0 +
        480 * f0^11 * f1^7 * f′0^4 * f′1^11 * f′′′0^2 -
        5040 * f0^11 * f1^7 * f′0^3 * f′1^11 * f′′0^2 * f′′′0 +
        5040 * f0^11 * f1^7 * f′0^2 * f′1^11 * f′′0^4 -
        2340 * f0^11 * f1^6 * f′0^11 * f′1^6 * f′′′′1 -
        23400 * f0^11 * f1^6 * f′0^11 * f′1^5 * f′′1 * f′′′1 -
        35100 * f0^11 * f1^6 * f′0^11 * f′1^4 * f′′1^2 +
        9360 * f0^11 * f1^5 * f′0^11 * f′1^7 * f′′′1 -
        28080 * f0^11 * f1^5 * f′0^11 * f′1^6 * f′′1^2 -
        28080 * f0^11 * f1^4 * f′0^11 * f′1^8 * f′′1 -
        56160 * f0^11 * f1^3 * f′0^11 * f′1^10 +
        56160 * f0^11 * f1^2 * f′0^11 * f′1^11 * x1 +
        20 * f0^10 * f1^9 * f′0^11 * f′1^4 * f′′′′′′1 -
        420 * f0^10 * f1^9 * f′0^11 * f′1^3 * f′′1 * f′′′′′1 -
        700 * f0^10 * f1^9 * f′0^11 * f′1^3 * f′′′1 * f′′′′1 +
        4200 * f0^10 * f1^9 * f′0^11 * f′1^2 * f′′1^2 * f′′′′1 +
        5600 * f0^10 * f1^9 * f′0^11 * f′1^2 * f′′1 * f′′′1^2 -
        25200 * f0^10 * f1^9 * f′0^11 * f′1 * f′′1^3 * f′′′1 +
        18900 * f0^10 * f1^9 * f′0^11 * f′′1^5 +
        15 * f0^10 * f1^9 * f′0^4 * f′1^11 * f′′′′′′0 -
        315 * f0^10 * f1^9 * f′0^3 * f′1^11 * f′′0 * f′′′′′0 -
        525 * f0^10 * f1^9 * f′0^3 * f′1^11 * f′′′0 * f′′′′0 +
        3150 * f0^10 * f1^9 * f′0^2 * f′1^11 * f′′0^2 * f′′′′0 +
        4200 * f0^10 * f1^9 * f′0^2 * f′1^11 * f′′0 * f′′′0^2 -
        18900 * f0^10 * f1^9 * f′0 * f′1^11 * f′′0^3 * f′′′0 +
        14175 * f0^10 * f1^9 * f′1^11 * f′′0^5 -
        540 * f0^10 * f1^8 * f′0^11 * f′1^5 * f′′′′′1 +
        8100 * f0^10 * f1^8 * f′0^11 * f′1^4 * f′′1 * f′′′′1 +
        5400 * f0^10 * f1^8 * f′0^11 * f′1^4 * f′′′1^2 -
        56700 * f0^10 * f1^8 * f′0^11 * f′1^3 * f′′1^2 * f′′′1 +
        56700 * f0^10 * f1^8 * f′0^11 * f′1^2 * f′′1^4 +
        246 * f0^10 * f1^8 * f′0^5 * f′1^11 * f′′′′′0 -
        3690 * f0^10 * f1^8 * f′0^4 * f′1^11 * f′′0 * f′′′′0 -
        2460 * f0^10 * f1^8 * f′0^4 * f′1^11 * f′′′0^2 +
        25830 * f0^10 * f1^8 * f′0^3 * f′1^11 * f′′0^2 * f′′′0 -
        25830 * f0^10 * f1^8 * f′0^2 * f′1^11 * f′′0^4 +
        6060 * f0^10 * f1^7 * f′0^11 * f′1^6 * f′′′′1 +
        60600 * f0^10 * f1^7 * f′0^11 * f′1^5 * f′′1 * f′′′1 +
        90900 * f0^10 * f1^7 * f′0^11 * f′1^4 * f′′1^2 +
        1080 * f0^10 * f1^7 * f′0^6 * f′1^11 * f′′′′0 +
        10800 * f0^10 * f1^7 * f′0^5 * f′1^11 * f′′0 * f′′′0 +
        16200 * f0^10 * f1^7 * f′0^4 * f′1^11 * f′′0^2 -
        34320 * f0^10 * f1^6 * f′0^11 * f′1^7 * f′′′1 +
        102960 * f0^10 * f1^6 * f′0^11 * f′1^6 * f′′1^2 +
        102960 * f0^10 * f1^5 * f′0^11 * f′1^8 * f′′1 +
        205920 * f0^10 * f1^4 * f′0^11 * f′1^10 -
        205920 * f0^10 * f1^3 * f′0^11 * f′1^11 * x1 -
        15 * f0^9 * f1^10 * f′0^11 * f′1^4 * f′′′′′′1 +
        315 * f0^9 * f1^10 * f′0^11 * f′1^3 * f′′1 * f′′′′′1 +
        525 * f0^9 * f1^10 * f′0^11 * f′1^3 * f′′′1 * f′′′′1 -
        3150 * f0^9 * f1^10 * f′0^11 * f′1^2 * f′′1^2 * f′′′′1 -
        4200 * f0^9 * f1^10 * f′0^11 * f′1^2 * f′′1 * f′′′1^2 +
        18900 * f0^9 * f1^10 * f′0^11 * f′1 * f′′1^3 * f′′′1 -
        14175 * f0^9 * f1^10 * f′0^11 * f′′1^5 -
        20 * f0^9 * f1^10 * f′0^4 * f′1^11 * f′′′′′′0 +
        420 * f0^9 * f1^10 * f′0^3 * f′1^11 * f′′0 * f′′′′′0 +
        700 * f0^9 * f1^10 * f′0^3 * f′1^11 * f′′′0 * f′′′′0 -
        4200 * f0^9 * f1^10 * f′0^2 * f′1^11 * f′′0^2 * f′′′′0 -
        5600 * f0^9 * f1^10 * f′0^2 * f′1^11 * f′′0 * f′′′0^2 +
        25200 * f0^9 * f1^10 * f′0 * f′1^11 * f′′0^3 * f′′′0 -
        18900 * f0^9 * f1^10 * f′1^11 * f′′0^5 +
        510 * f0^9 * f1^9 * f′0^11 * f′1^5 * f′′′′′1 -
        7650 * f0^9 * f1^9 * f′0^11 * f′1^4 * f′′1 * f′′′′1 -
        5100 * f0^9 * f1^9 * f′0^11 * f′1^4 * f′′′1^2 +
        53550 * f0^9 * f1^9 * f′0^11 * f′1^3 * f′′1^2 * f′′′1 -
        53550 * f0^9 * f1^9 * f′0^11 * f′1^2 * f′′1^4 -
        510 * f0^9 * f1^9 * f′0^5 * f′1^11 * f′′′′′0 +
        7650 * f0^9 * f1^9 * f′0^4 * f′1^11 * f′′0 * f′′′′0 +
        5100 * f0^9 * f1^9 * f′0^4 * f′1^11 * f′′′0^2 -
        53550 * f0^9 * f1^9 * f′0^3 * f′1^11 * f′′0^2 * f′′′0 +
        53550 * f0^9 * f1^9 * f′0^2 * f′1^11 * f′′0^4 -
        7590 * f0^9 * f1^8 * f′0^11 * f′1^6 * f′′′′1 -
        75900 * f0^9 * f1^8 * f′0^11 * f′1^5 * f′′1 * f′′′1 -
        113850 * f0^9 * f1^8 * f′0^11 * f′1^4 * f′′1^2 -
        4590 * f0^9 * f1^8 * f′0^6 * f′1^11 * f′′′′0 -
        45900 * f0^9 * f1^8 * f′0^5 * f′1^11 * f′′0 * f′′′0 -
        68850 * f0^9 * f1^8 * f′0^4 * f′1^11 * f′′0^2 +
        60600 * f0^9 * f1^7 * f′0^11 * f′1^7 * f′′′1 -
        181800 * f0^9 * f1^7 * f′0^11 * f′1^6 * f′′1^2 -
        14400 * f0^9 * f1^7 * f′0^7 * f′1^11 * f′′′0 +
        43200 * f0^9 * f1^7 * f′0^6 * f′1^11 * f′′0^2 -
        257400 * f0^9 * f1^6 * f′0^11 * f′1^8 * f′′1 -
        514800 * f0^9 * f1^5 * f′0^11 * f′1^10 +
        514800 * f0^9 * f1^4 * f′0^11 * f′1^11 * x1 +
        6 * f0^8 * f1^11 * f′0^11 * f′1^4 * f′′′′′′1 -
        126 * f0^8 * f1^11 * f′0^11 * f′1^3 * f′′1 * f′′′′′1 -
        210 * f0^8 * f1^11 * f′0^11 * f′1^3 * f′′′1 * f′′′′1 +
        1260 * f0^8 * f1^11 * f′0^11 * f′1^2 * f′′1^2 * f′′′′1 +
        1680 * f0^8 * f1^11 * f′0^11 * f′1^2 * f′′1 * f′′′1^2 -
        7560 * f0^8 * f1^11 * f′0^11 * f′1 * f′′1^3 * f′′′1 +
        5670 * f0^8 * f1^11 * f′0^11 * f′′1^5 +
        15 * f0^8 * f1^11 * f′0^4 * f′1^11 * f′′′′′′0 -
        315 * f0^8 * f1^11 * f′0^3 * f′1^11 * f′′0 * f′′′′′0 -
        525 * f0^8 * f1^11 * f′0^3 * f′1^11 * f′′′0 * f′′′′0 +
        3150 * f0^8 * f1^11 * f′0^2 * f′1^11 * f′′0^2 * f′′′′0 +
        4200 * f0^8 * f1^11 * f′0^2 * f′1^11 * f′′0 * f′′′0^2 -
        18900 * f0^8 * f1^11 * f′0 * f′1^11 * f′′0^3 * f′′′0 +
        14175 * f0^8 * f1^11 * f′1^11 * f′′0^5 -
        246 * f0^8 * f1^10 * f′0^11 * f′1^5 * f′′′′′1 +
        3690 * f0^8 * f1^10 * f′0^11 * f′1^4 * f′′1 * f′′′′1 +
        2460 * f0^8 * f1^10 * f′0^11 * f′1^4 * f′′′1^2 -
        25830 * f0^8 * f1^10 * f′0^11 * f′1^3 * f′′1^2 * f′′′1 +
        25830 * f0^8 * f1^10 * f′0^11 * f′1^2 * f′′1^4 +
        540 * f0^8 * f1^10 * f′0^5 * f′1^11 * f′′′′′0 -
        8100 * f0^8 * f1^10 * f′0^4 * f′1^11 * f′′0 * f′′′′0 -
        5400 * f0^8 * f1^10 * f′0^4 * f′1^11 * f′′′0^2 +
        56700 * f0^8 * f1^10 * f′0^3 * f′1^11 * f′′0^2 * f′′′0 -
        56700 * f0^8 * f1^10 * f′0^2 * f′1^11 * f′′0^4 +
        4590 * f0^8 * f1^9 * f′0^11 * f′1^6 * f′′′′1 +
        45900 * f0^8 * f1^9 * f′0^11 * f′1^5 * f′′1 * f′′′1 +
        68850 * f0^8 * f1^9 * f′0^11 * f′1^4 * f′′1^2 +
        7590 * f0^8 * f1^9 * f′0^6 * f′1^11 * f′′′′0 +
        75900 * f0^8 * f1^9 * f′0^5 * f′1^11 * f′′0 * f′′′0 +
        113850 * f0^8 * f1^9 * f′0^4 * f′1^11 * f′′0^2 -
        48600 * f0^8 * f1^8 * f′0^11 * f′1^7 * f′′′1 +
        145800 * f0^8 * f1^8 * f′0^11 * f′1^6 * f′′1^2 +
        48600 * f0^8 * f1^8 * f′0^7 * f′1^11 * f′′′0 -
        145800 * f0^8 * f1^8 * f′0^6 * f′1^11 * f′′0^2 +
        297000 * f0^8 * f1^7 * f′0^11 * f′1^8 * f′′1 +
        118800 * f0^8 * f1^7 * f′0^8 * f′1^11 * f′′0 +
        926640 * f0^8 * f1^6 * f′0^11 * f′1^10 -
        926640 * f0^8 * f1^5 * f′0^11 * f′1^11 * x1 -
        f0^7 * f1^12 * f′0^11 * f′1^4 * f′′′′′′1 +
        21 * f0^7 * f1^12 * f′0^11 * f′1^3 * f′′1 * f′′′′′1 +
        35 * f0^7 * f1^12 * f′0^11 * f′1^3 * f′′′1 * f′′′′1 -
        210 * f0^7 * f1^12 * f′0^11 * f′1^2 * f′′1^2 * f′′′′1 -
        280 * f0^7 * f1^12 * f′0^11 * f′1^2 * f′′1 * f′′′1^2 +
        1260 * f0^7 * f1^12 * f′0^11 * f′1 * f′′1^3 * f′′′1 -
        945 * f0^7 * f1^12 * f′0^11 * f′′1^5 -
        6 * f0^7 * f1^12 * f′0^4 * f′1^11 * f′′′′′′0 +
        126 * f0^7 * f1^12 * f′0^3 * f′1^11 * f′′0 * f′′′′′0 +
        210 * f0^7 * f1^12 * f′0^3 * f′1^11 * f′′′0 * f′′′′0 -
        1260 * f0^7 * f1^12 * f′0^2 * f′1^11 * f′′0^2 * f′′′′0 -
        1680 * f0^7 * f1^12 * f′0^2 * f′1^11 * f′′0 * f′′′0^2 +
        7560 * f0^7 * f1^12 * f′0 * f′1^11 * f′′0^3 * f′′′0 -
        5670 * f0^7 * f1^12 * f′1^11 * f′′0^5 +
        48 * f0^7 * f1^11 * f′0^11 * f′1^5 * f′′′′′1 -
        720 * f0^7 * f1^11 * f′0^11 * f′1^4 * f′′1 * f′′′′1 -
        480 * f0^7 * f1^11 * f′0^11 * f′1^4 * f′′′1^2 +
        5040 * f0^7 * f1^11 * f′0^11 * f′1^3 * f′′1^2 * f′′′1 -
        5040 * f0^7 * f1^11 * f′0^11 * f′1^2 * f′′1^4 -
        300 * f0^7 * f1^11 * f′0^5 * f′1^11 * f′′′′′0 +
        4500 * f0^7 * f1^11 * f′0^4 * f′1^11 * f′′0 * f′′′′0 +
        3000 * f0^7 * f1^11 * f′0^4 * f′1^11 * f′′′0^2 -
        31500 * f0^7 * f1^11 * f′0^3 * f′1^11 * f′′0^2 * f′′′0 +
        31500 * f0^7 * f1^11 * f′0^2 * f′1^11 * f′′0^4 -
        1080 * f0^7 * f1^10 * f′0^11 * f′1^6 * f′′′′1 -
        10800 * f0^7 * f1^10 * f′0^11 * f′1^5 * f′′1 * f′′′1 -
        16200 * f0^7 * f1^10 * f′0^11 * f′1^4 * f′′1^2 -
        6060 * f0^7 * f1^10 * f′0^6 * f′1^11 * f′′′′0 -
        60600 * f0^7 * f1^10 * f′0^5 * f′1^11 * f′′0 * f′′′0 -
        90900 * f0^7 * f1^10 * f′0^4 * f′1^11 * f′′0^2 +
        14400 * f0^7 * f1^9 * f′0^11 * f′1^7 * f′′′1 -
        43200 * f0^7 * f1^9 * f′0^11 * f′1^6 * f′′1^2 -
        60600 * f0^7 * f1^9 * f′0^7 * f′1^11 * f′′′0 +
        181800 * f0^7 * f1^9 * f′0^6 * f′1^11 * f′′0^2 -
        118800 * f0^7 * f1^8 * f′0^11 * f′1^8 * f′′1 -
        297000 * f0^7 * f1^8 * f′0^8 * f′1^11 * f′′0 -
        570240 * f0^7 * f1^7 * f′0^11 * f′1^10 +
        570240 * f0^7 * f1^7 * f′0^10 * f′1^11 +
        1235520 * f0^7 * f1^6 * f′0^11 * f′1^11 * x1 +
        f0^6 * f1^13 * f′0^4 * f′1^11 * f′′′′′′0 -
        21 * f0^6 * f1^13 * f′0^3 * f′1^11 * f′′0 * f′′′′′0 -
        35 * f0^6 * f1^13 * f′0^3 * f′1^11 * f′′′0 * f′′′′0 +
        210 * f0^6 * f1^13 * f′0^2 * f′1^11 * f′′0^2 * f′′′′0 +
        280 * f0^6 * f1^13 * f′0^2 * f′1^11 * f′′0 * f′′′0^2 -
        1260 * f0^6 * f1^13 * f′0 * f′1^11 * f′′0^3 * f′′′0 +
        945 * f0^6 * f1^13 * f′1^11 * f′′0^5 +
        78 * f0^6 * f1^12 * f′0^5 * f′1^11 * f′′′′′0 -
        1170 * f0^6 * f1^12 * f′0^4 * f′1^11 * f′′0 * f′′′′0 -
        780 * f0^6 * f1^12 * f′0^4 * f′1^11 * f′′′0^2 +
        8190 * f0^6 * f1^12 * f′0^3 * f′1^11 * f′′0^2 * f′′′0 -
        8190 * f0^6 * f1^12 * f′0^2 * f′1^11 * f′′0^4 +
        2340 * f0^6 * f1^11 * f′0^6 * f′1^11 * f′′′′0 +
        23400 * f0^6 * f1^11 * f′0^5 * f′1^11 * f′′0 * f′′′0 +
        35100 * f0^6 * f1^11 * f′0^4 * f′1^11 * f′′0^2 +
        34320 * f0^6 * f1^10 * f′0^7 * f′1^11 * f′′′0 -
        102960 * f0^6 * f1^10 * f′0^6 * f′1^11 * f′′0^2 +
        257400 * f0^6 * f1^9 * f′0^8 * f′1^11 * f′′0 -
        926640 * f0^6 * f1^8 * f′0^10 * f′1^11 -
        1235520 * f0^6 * f1^7 * f′0^11 * f′1^11 * x0 -
        6 * f0^5 * f1^13 * f′0^5 * f′1^11 * f′′′′′0 +
        90 * f0^5 * f1^13 * f′0^4 * f′1^11 * f′′0 * f′′′′0 +
        60 * f0^5 * f1^13 * f′0^4 * f′1^11 * f′′′0^2 -
        630 * f0^5 * f1^13 * f′0^3 * f′1^11 * f′′0^2 * f′′′0 +
        630 * f0^5 * f1^13 * f′0^2 * f′1^11 * f′′0^4 -
        390 * f0^5 * f1^12 * f′0^6 * f′1^11 * f′′′′0 -
        3900 * f0^5 * f1^12 * f′0^5 * f′1^11 * f′′0 * f′′′0 -
        5850 * f0^5 * f1^12 * f′0^4 * f′1^11 * f′′0^2 -
        9360 * f0^5 * f1^11 * f′0^7 * f′1^11 * f′′′0 +
        28080 * f0^5 * f1^11 * f′0^6 * f′1^11 * f′′0^2 -
        102960 * f0^5 * f1^10 * f′0^8 * f′1^11 * f′′0 +
        514800 * f0^5 * f1^9 * f′0^10 * f′1^11 +
        926640 * f0^5 * f1^8 * f′0^11 * f′1^11 * x0 +
        30 * f0^4 * f1^13 * f′0^6 * f′1^11 * f′′′′0 +
        300 * f0^4 * f1^13 * f′0^5 * f′1^11 * f′′0 * f′′′0 +
        450 * f0^4 * f1^13 * f′0^4 * f′1^11 * f′′0^2 +
        1560 * f0^4 * f1^12 * f′0^7 * f′1^11 * f′′′0 -
        4680 * f0^4 * f1^12 * f′0^6 * f′1^11 * f′′0^2 +
        28080 * f0^4 * f1^11 * f′0^8 * f′1^11 * f′′0 -
        205920 * f0^4 * f1^10 * f′0^10 * f′1^11 -
        514800 * f0^4 * f1^9 * f′0^11 * f′1^11 * x0 -
        120 * f0^3 * f1^13 * f′0^7 * f′1^11 * f′′′0 +
        360 * f0^3 * f1^13 * f′0^6 * f′1^11 * f′′0^2 -
        4680 * f0^3 * f1^12 * f′0^8 * f′1^11 * f′′0 +
        56160 * f0^3 * f1^11 * f′0^10 * f′1^11 +
        205920 * f0^3 * f1^10 * f′0^11 * f′1^11 * x0 +
        360 * f0^2 * f1^13 * f′0^8 * f′1^11 * f′′0 - 9360 * f0^2 * f1^12 * f′0^10 * f′1^11 -
        56160 * f0^2 * f1^11 * f′0^11 * f′1^11 * x0 +
        720 * f0 * f1^13 * f′0^10 * f′1^11 +
        9360 * f0 * f1^12 * f′0^11 * f′1^11 * x0 - 720 * f1^13 * f′0^11 * f′1^11 * x0
    ) / (
        720 *
        f′0^11 *
        f′1^11 *
        (
            f0^13 - 13 * f0^12 * f1 + 78 * f0^11 * f1^2 - 286 * f0^10 * f1^3 +
            715 * f0^9 * f1^4 - 1287 * f0^8 * f1^5 + 1716 * f0^7 * f1^6 -
            1716 * f0^6 * f1^7 + 1287 * f0^5 * f1^8 - 715 * f0^4 * f1^9 +
            286 * f0^3 * f1^10 - 78 * f0^2 * f1^11 + 13 * f0 * f1^12 - f1^13
        )
    )
end

function lmm(
    ::LithBoonkkampIJzerman{3,6},
    xs,
    fs,
    f′s,
    f′′s,
    f′′′s,
    f′′′′s,
    f′′′′′s,
    f′′′′′′s,
)
    error("not computed")
end


using Base.Test
using PGM

@testset "DMRF" begin

@testset "DPMRF.Basics" begin

    vlst = VarList([dvar("x1", 3),
                    dvar("x2", 3),
                    dvar("x3", 2),
                    dvar("x4", 2),
                    dvar("x5", 3),
                    dvar("x6", 3)])
    elists = [[(1, 2), (5, 6)],
              [(3, 4)],
              [(1, 3), (2, 4), (5, 3), (6, 4)]]
    gr = ugraph_with_tedges(6, elists)
    mrf0 = DiscretePairwiseMRF{Float64}(vlst, gr)

    @test nvars(mrf0) == 6
    @test nvertices(mrf0) == 6
    @test nedgetypes(mrf0) == 3
    @test nedges(mrf0) == 7
    @test [edges(mrf0, t) for t in 1:3] == elists
    @test vars(mrf0) === vlst
    @test [var_cardinalities(mrf0, t) for t in 1:6] == [3, 3, 2, 2, 3, 3]
    @test [edge_pdims(mrf0, t) for t in 1:3] == [(3, 3), (2, 2), (3, 2)]
    @test !has_vertex_potentials(mrf0)
    @test !has_edge_potentials(mrf0)

    vpots = [floor.(rand(d) * 100) for d in [3, 3, 2, 2, 3, 3]]
    epots = [floor.(rand(d1, d2) * 100) for (d1, d2) in [(3, 3), (2, 2), (3, 2)]]

    mrf1 = pwmrf(vlst, gr, vpots, epots)
    @test nvars(mrf1) == 6
    @test nvertices(mrf1) == 6
    @test nedgetypes(mrf1) == 3
    @test nedges(mrf1) == 7
    @test [var_cardinalities(mrf1, t) for t in 1:6] == [3, 3, 2, 2, 3, 3]
    @test [edge_pdims(mrf1, t) for t in 1:3] == [(3, 3), (2, 2), (3, 2)]
    @test has_vertex_potentials(mrf1)
    @test has_edge_potentials(mrf1)
    @test vertex_potentials(mrf1) === vpots
    @test edge_potentials(mrf1) === epots
end

function collect_tpotentials(mrf::DiscretePairwiseMRF)
    r = zeros(mrf.cards...)
    for ci in Base.CartesianRange(size(r))
        r[ci] = tpotential(mrf, collect(ci.I))
    end
    r
end

@testset "DPMRF.Potentials" begin

    vlst = VarList([dvar("x1", 3),
                    dvar("x2", 3),
                    dvar("x3", 2),
                    dvar("x4", 2),
                    dvar("x5", 3),
                    dvar("x6", 3)])
    elists = [[(1, 2), (5, 6)],
              [(3, 4)],
              [(1, 3), (2, 4), (5, 3), (6, 4)]]
    gr = ugraph_with_tedges(6, elists)
    mrf = DiscretePairwiseMRF{Float64}(vlst, gr)

    r0_a = collect_tpotentials(mrf)
    r0_b = tpotentials(mrf)

    z = zeros(3, 3, 2, 2, 3, 3)
    @test r0_a == z
    @test r0_b == z

    vpots = [floor.(rand(d) * 100) for d in [3, 3, 2, 2, 3, 3]]
    epots = [floor.(rand(d1, d2) * 100) for (d1, d2) in [(3, 3), (2, 2), (3, 2)]]
    set_vertex_potentials!(mrf, vpots)
    set_edge_potentials!(mrf, epots)

    vp1, vp2, vp3, vp4, vp5, vp6 = vpots
    ep1, ep2, ep3 = epots

    s1 = [1, 1, 1, 1, 1, 1]
    p1 = vp1[1] + vp2[1] + vp3[1] + vp4[1] + vp5[1] + vp6[1] +
         ep1[1,1] * 2 + ep2[1,1] + ep3[1,1] * 4
    @test tpotential(mrf, s1) == p1

    s2 = [2, 1, 1, 2, 3, 2]
    p2 = vp1[2] + vp2[1] + vp3[1] + vp4[2] + vp5[3] + vp6[2] +
         ep1[2,1] + ep1[3,2] + ep2[1,2] + ep3[2,1] + ep3[1,2] + ep3[3,1] + ep3[2,2]
    @test tpotential(mrf, s2) == p2

    r1_a = collect_tpotentials(mrf)
    r1_b = tpotentials(mrf)
    @test r1_a == r1_b
end

end # DMRF


# Matrix: create matrix representation of LinearMap
function Base.Matrix(A::LinearMap)
    M, N = size(A)
    T = eltype(A)
    mat = Matrix{T}(undef, (M, N))
    v = fill(zero(T), N)
    @inbounds for i = 1:N
        v[i] = one(T)
        mul!(view(mat, :, i), A, v)
        v[i] = zero(T)
    end
    return mat
end
Base.Array(A::LinearMap) = Matrix(A)
Base.convert(::Type{Matrix}, A::LinearMap) = Matrix(A)
Base.convert(::Type{Array}, A::LinearMap) = convert(Matrix, A)
Base.convert(::Type{AbstractMatrix}, A::LinearMap) = convert(Matrix, A)
Base.convert(::Type{AbstractArray}, A::LinearMap) = convert(AbstractMatrix, A)

# special cases
Base.convert(::Type{AbstractMatrix}, A::WrappedMap) = convert(AbstractMatrix, A.lmap)
Base.convert(::Type{Matrix}, A::WrappedMap) = convert(Matrix, A.lmap)
function Base.convert(::Type{Matrix}, ΣA::LinearCombination{<:Any,<:Tuple{Vararg{MatrixMap}}})
    if length(ΣA.maps) <= 10
        return (+).(map(A->getfield(A, :lmap), ΣA.maps)...)
    else
        S = zero(first(ΣA.maps).lmap)
        for A in ΣA.maps
            S .+= A.lmap
        end
        return S
    end
end
function Base.convert(::Type{Matrix}, AB::CompositeMap{<:Any,<:Tuple{MatrixMap,MatrixMap}})
    B, A = AB.maps
    return A.lmap*B.lmap
end
function Base.convert(::Type{Matrix}, λA::CompositeMap{<:Any,<:Tuple{MatrixMap,UniformScalingMap}})
    A, J = λA.maps
    return J.λ*A.lmap
end
function Base.convert(::Type{Matrix}, Aλ::CompositeMap{<:Any,<:Tuple{UniformScalingMap,MatrixMap}})
    J, A = Aλ.maps
    return A.lmap*J.λ
end

Base.Matrix(A::BlockMap) = materialize!(Matrix{eltype(A)}(undef, size(A)), A)

# sparse: create sparse matrix representation of LinearMap
function SparseArrays.sparse(A::LinearMap{T}) where {T}
    M, N = size(A)
    rowind = Int[]
    nzval = T[]
    colptr = Vector{Int}(undef, N+1)
    v = fill(zero(T), N)
    Av = Vector{T}(undef, M)

    for i = 1:N
        v[i] = one(T)
        mul!(Av, A, v)
        js = findall(!iszero, Av)
        colptr[i] = length(nzval) + 1
        if length(js) > 0
            append!(rowind, js)
            append!(nzval, Av[js])
        end
        v[i] = zero(T)
    end
    colptr[N+1] = length(nzval) + 1

    return SparseMatrixCSC(M, N, colptr, rowind, nzval)
end

Base.convert(::Type{SparseMatrixCSC}, A::LinearMap) = sparse(A)
Base.convert(::Type{SparseMatrixCSC}, A::WrappedMap) = convert(SparseMatrixCSC, A.lmap)
Base.convert(::Type{SparseMatrixCSC}, A::BlockMap) = materialize!(spzeros(eltype(A), size(A)...), A)


# ------------------------------------------------------------------
# Licensed under the ISC License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    CylinderNeighborhood{N}(radius, height)

A `N`-dimensional cylinder neighborhood with `radius` and `height`.

### Notes

The `height` parameter is only half of the actual cylinder height.
"""
struct CylinderNeighborhood{T,N} <: AbstractNeighborhood{T,N}
  radius::T
  height::T
end

function CylinderNeighborhood{N}(radius::T, height::T) where {N,T}
  @assert radius > 0 "cylinder radius must be positive"
  @assert height > 0 "cylinder height must be positive"
  CylinderNeighborhood{T,N}(radius, height)
end

"""
    radius(cylinder)

Return the radius of the `cylinder`.
"""
radius(cylinder::CylinderNeighborhood) = cylinder.radius

"""
    height(cylinder)

Return (half of) the height of the `cylinder`.
"""
height(cylinder::CylinderNeighborhood) = cylinder.height

function isneighbor(cylinder::CylinderNeighborhood, xₒ::AbstractVector, x::AbstractVector)
  l = length(xₒ)
  @inbounds abs(x[l] - xₒ[l]) ≤ cylinder.height && norm(view(x,1:l-1) - view(xₒ,1:l-1)) ≤ cylinder.radius
end

# ------------
# IO methods
# ------------
function Base.show(io::IO, cylinder::CylinderNeighborhood{T,N}) where {N,T}
  r = cylinder.radius
  h = cylinder.height
  print(io, "$(N)D CylinderNeighborhood($r, $h)")
end

function Base.show(io::IO, ::MIME"text/plain",
                   cylinder::CylinderNeighborhood{T,N}) where {N,T}
  println(io, "$(N)D CylinderNeighborhood")
  println(io, "  radius: ", cylinder.radius)
  print(  io, "  height: ", cylinder.height)
end


"""Importance sampling examples.

TODOs:
- Try other f's, distributions (see Distributions.jl).
- Try a simple adaptive importance sampling algorithm.
"""

using Distributions
using PyPlot
using Random

# Plots f, p, q, and fp in the domain [lb, ub].
#
# Args:
# - f_x: a Function.
# - p_x: a Distribution.
# - q_x: a Function, a Distribution, or a Vector of the types.
# - lb/ub: Floats.
function plot_fs(f_x, p_x, q_x, lb, ub; lw=0.75)
    xl = [x for x in lb:0.05:ub]

    pdf_p_xl = pdf.(p_x, xl)
    plt.plot(xl, pdf_p_xl, label=raw"$p$", lw=lw)

    q_x_l = (isa(q_x, Vector) ? q_x : [q_x])
    for (idx, q_x_i) in enumerate(q_x_l)
        pdf_q_xl = (isa(q_x_i, Function) ? q_x_i.(xl) : pdf.(q_x_i, xl))
        label = string(raw"$q$", (idx == 1 ? "" : string(idx)))
        plt.plot(xl, pdf_q_xl, label=label, lw=lw)
    end

    f_xl = f_x.(xl)
    plt.plot(xl, f_xl, label=raw"$f$", lw=lw)
    plt.plot(xl, f_xl .* pdf_p_xl, label=raw"$f \times p$", lw=lw)
    plt.legend();
end

# Plots incremental sampling estimates with a confidence region.
#
# Args:
# - n_estimates: a Vector of estimates of length n.
# - delta_n: compute incremental estimates every this many steps.
# - label: a String label to use for the plot.
function plot_estimates(n_estimates::Vector, delta_n::Integer, label::String)

    function _x_range(l)
        total_n = length(l)
        ret = delta_n:delta_n:total_n
        if (last(ret) != total_n); push!(ret, total_n); end
        return ret
    end

    function _n_mean(n_estimates, xl)
        ret = []
        # TODO(kykim): Do a cumsum type of optimization.
        for x in xl
            x_n_estimates = [mean(estimates[1:x]) for estimates in n_estimates]
            push!(ret, x_n_estimates)
        end
        return ret
    end

    xl = _x_range(first(n_estimates))
    n_y = _n_mean(n_estimates, xl)
    min_y, max_y = minimum.(n_y), maximum.(n_y)
    mid_y = (max_y + min_y) ./ 2.0

    # For Plots.jl:
    #  plot(x_mc, mid_y_mc, ribbon=(max_y_mc - mid_y_mc), fillalpha=0.15,
    #       label="MC", lw=2, xlabel="no. of samples", ylabel="estimates")
    #  plot(x_is, mid_y_is, ribbon=(max_y_is - mid_y_is), fillalpha=0.15, label="IS", lw=2)
    plt.plot(xl, mid_y, label=label, lw=0.5)
    plt.fill_between(xl, min_y, max_y, alpha=0.1)
end

# Computes standard weights.
function is_standard_weights(p_x, q_x, samples)
    weights = pdf.(p_x, samples) ./ pdf.(q_x, samples)
    return weights
end

# Computes mixture weights based on all proposals.
function is_mixture_weights(p_x, q_x_l, samples)
    q_x_l = (isa(q_x_l, Vector) ? q_x_l : [q_x_l])
    denom = .+([pdf.(q_x_i, samples) for q_x_i in q_x_l]...) / length(q_x_l)
    weights = pdf.(p_x, samples) ./ denom
    return weights
end

# Simple experiment comparing Gaussian nominal and propsal distributions.
#
# Importance sampling with the proposal used leads to slightly smaller variance
# as it is better configured w.r.t to f * p.
function simple_exp(mc_n, is_n, delta_n, n_trials)
    Random.seed!(0)

    f_x = (x) -> (1. / (1. + exp(-(x - 4.5))))
    p_x = Normal(2.0, 1.0)
    # Note. Using a smaller σ (e.g., 0.5) leads to higher variance as q now has
    # lighter tails than p. This case of using a Gaussian proposal when the
    # nominal is also Gaussian is discussed in Owen 9.1.
    q_x = Normal(3.0, 0.50)  # Compare σ=1.0 vs. σ=0.5

    # Plot f, p, fp, q.
    figure(figsize=(9.0, 6.0))
    plot_fs(f_x, p_x, q_x, -3.0, 9.0)
    plt.tight_layout()
    # plt.savefig("distribs.png", dpi=500)

    # Compute n_trials many estimates.
    n_mc_estimates, n_is_estimates = [], []
    for _ in 1:n_trials
        mc_estimates = f_x.(rand(p_x, mc_n))
        push!(n_mc_estimates, mc_estimates)

        is_samples = rand(q_x, is_n)
        pdf_p, pdf_q = pdf.(p_x, is_samples), pdf.(q_x, is_samples)
        is_estimates = f_x.(is_samples) .* (pdf_p ./ pdf_q)
        push!(n_is_estimates, is_estimates)
    end

    # Plot MC and IS estimates w/ confidence regions.
    figure(figsize=(9.0, 6.0))
    plot_estimates(n_mc_estimates, delta_n, "MC")
    plot_estimates(n_is_estimates, delta_n, "IS")
    plt.xlabel("samples"); plt.ylabel("estimates"); plt.legend();
    plt.tight_layout()
    # plt.savefig("estimates.png", dpi=500)
end

# This is an example given in the chapter 6 importance sampling note.
#
# The exponential proposal distribution is so well-suited for the given f * p
# that importance sampling done with the distribution leads to extremely small
# variance.
#
# An alternate importance sampling with a "better" Gaussian also works well.
function normal_exponential_exp(mc_n, is_n, delta_n, n_trials)
    Random.seed!(0)

    f_x = (x) -> (x >= 4 ? 1.0 : 0.0)
    p_x = Normal(2.5, 1.0)

    # Using this to sample from an exponential shifted to the right by 4.
    q_x_tmp = Exponential(1.0)
    shift = 4.0
    pdf_q_fn = (x) -> pdf(q_x_tmp, x - shift)
    is_samples1 = rand(q_x_tmp, is_n) .+ shift

    q_x2 = Normal(3.5, 1.0)
    is_samples2 = rand(q_x2, is_n)

    # Plot f, p, fp, q.
    plt.figure(figsize=(9.0, 6.0))
    plot_fs(f_x, p_x, [pdf_q_fn, q_x2], -3.0, 9.0)

    # Compute n_trials many estimates.
    n_mc_estimates, n_is_estimates1, n_is_estimates2 = [], [], []
    for _ in 1:n_trials
        mc_estimates = f_x.(rand(p_x, mc_n))
        push!(n_mc_estimates, mc_estimates)

        pdf_p1, pdf_q1 = pdf.(p_x, is_samples1), pdf_q_fn.(is_samples1)
        is_estimates1 = f_x.(is_samples1) .* (pdf_p1 ./ pdf_q1)
        push!(n_is_estimates1, is_estimates1)

        pdf_p2, pdf_q2 = pdf.(p_x, is_samples2), pdf.(q_x2, is_samples2)
        is_estimates2 = f_x.(is_samples2) .* (pdf_p2 ./ pdf_q2)
        push!(n_is_estimates2, is_estimates2)
    end

    # Plot MC and IS estimates w/ confidence regions.
    plt.figure(figsize=(9.0, 6.0))
    plot_estimates(n_mc_estimates, delta_n, "MC")
    for (idx, n_is_estimates) in enumerate([n_is_estimates1, n_is_estimates2])
        label = string("IS", (idx == 1 ? "" : string(idx)))
        plot_estimates(n_is_estimates, delta_n, label)
    end
    plt.xlabel("samples"); plt.ylabel("estimates"); plt.legend();
end

# Experiment to demonstrate a simple multiple importance sampling use case.
#
# The f and p in the experiment are such that when a Gaussian proposal with a
# smaller variance is used for importance sampling, it can lead to a higher
# variance in estimate than the Monte Carlo estimate. This experiment
# demonstrates that using multiple importance sampling can be an option in such
# cases.
function exp_gaussian_mis(mc_n, is_n, delta_n, n_trials, weight_type="mixture")
    Random.seed!(0)

    f_x = (x) -> (1. / (1. + exp(-(x - 4.5))))
    p_x = Normal(2.0, 1.0)
    q_x1 = Normal(3.0, 0.75)  # Compare σ=0.75 vs. σ=0.5
    q_x2 = Normal(2.25, 0.75)
    q_x3 = Normal(3.75, 0.75)
    q_x_l = [q_x1, q_x2, q_x3]
    is_n_i = is_n ÷ length(q_x_l)  # Draw an equal no. of samples for each.

    # Plot f, p, fp, q.
    plt.figure(figsize=(9.0, 6.0))
    plot_fs(f_x, p_x, [q_x1, q_x2, q_x3], -3.0, 9.0)

    # Compute n_trials many estimates.
    n_mc_estimates, n_is_estimates = [], []
    for _ in 1:n_trials
        mc_estimates = f_x.(rand(p_x, mc_n))
        push!(n_mc_estimates, mc_estimates)

        is_estimates = []
        for q_x_i in q_x_l
            is_samples = rand(q_x_i, is_n_i)
            if weight_type == "standard"
                weights = is_standard_weights(p_x, q_x_i, is_samples)
            elseif weight_type == "mixture"
                weights = is_mixture_weights(p_x, q_x_l, is_samples)
            end
            append!(is_estimates, f_x.(is_samples) .* weights)
        end
        shuffle!(is_estimates)
        push!(n_is_estimates, is_estimates)
    end

    # Plot MC and IS estimates w/ confidence regions.
    plt.figure(figsize=(9.0, 6.0))
    plot_estimates(n_mc_estimates, delta_n, "MC")
    plot_estimates(n_is_estimates, delta_n, "IS")
    plt.xlabel("samples"); plt.ylabel("estimates"); plt.legend();
    plt.tight_layout()
    # plt.savefig("myplot.png", dpi=500)
end

# Experiment to compare a single Gaussian proposal and multiple proposals.
#
# The nominal distribution is a multimodal distribution designed in such a way
# that also leads to a multimodal f * p. Importance sampling with a single
# Gaussian can work reasonably well when configured properly to cover the
# regions of f * p, but the multimodal nature makes this somewhat tricky.
# Instead, importance sampling done with multiple Gaussians makes it easier to
# achieve the desired "coverage" and leads to lower variance.
function exp_mixture_gaussian_mis(mc_n, is_n, delta_n, n_trials,
                                  weight_type="mixture")
    Random.seed!(0)

    f_x = (x) -> (1. / (1. + exp(-(x - 1.5))))
    p_x = MixtureModel(
        Normal[Normal(-2.0, 1.0), Normal(1.0, 0.50), Normal(3.0, 0.5)],
        [0.6, 0.25, 0.15])

    # Compare a single Guassian proposal vs. multiple proposals.
    q_x = Normal(0.0, 1.5)
    q_x_mis1 = Normal(3.0, 0.75)
    q_x_mis2 = Normal(1.0, 1.0)
    q_x_mis3 = Normal(-1.0, 1.5)
    q_x_misl = [q_x_mis1, q_x_mis2, q_x_mis3]
    is_n_i = is_n ÷ length(q_x_misl)  # Draw an equal no. of samples for each.

    # Plot f, p, fp, q.
    plt.figure(figsize=(9.0, 6.0))
    plot_fs(f_x, p_x, [q_x, q_x_misl...], -6.0, 7.0)

    # Compute n_trials many estimates.
    n_mc_estimates, n_is_estimates, n_mis_estimates = [], [], []
    for _ in 1:n_trials
        mc_estimates = f_x.(rand(p_x, mc_n))
        push!(n_mc_estimates, mc_estimates)

        is_samples = rand(q_x, is_n)
        pdf_p, pdf_q = pdf.(p_x, is_samples), pdf.(q_x, is_samples)
        is_estimates = f_x.(is_samples) .* (pdf_p ./ pdf_q)
        push!(n_is_estimates, is_estimates)

        mis_estimates = []
        for q_x_i in q_x_misl
            mis_samples = rand(q_x_i, is_n_i)
            if weight_type == "standard"
                weights = is_standard_weights(p_x, q_x_i, mis_samples)
            elseif weight_type == "mixture"
                weights = is_mixture_weights(p_x, q_x_misl, mis_samples)
            end
            append!(mis_estimates, f_x.(mis_samples) .* weights)
        end
        shuffle!(mis_estimates)
        push!(n_mis_estimates, mis_estimates)
    end

    # Plot MC and IS estimates w/ confidence regions.
    plt.figure(figsize=(9.0, 6.0))
    plot_estimates(n_mc_estimates, delta_n, "MC")
    plot_estimates(n_is_estimates, delta_n, "IS")
    plot_estimates(n_mis_estimates, delta_n, "MIS")
    plt.xlabel("no. samples"); plt.ylabel("estimates"); plt.legend();
    plt.tight_layout()
    # plt.savefig("myplot.png", dpi=500)
end

simple_exp(10_000, 10_000, 100, 5)

# normal_exponential_exp(10_000, 10_000, 100, 5)

# Compare the "standard" and "mixture" weightings. Mixture weights does
# noticeably better presumably because it is more robust against small q which
# can significantly increase variance.
# exp_gaussian_mis(9000, 9000, 100, 5, "mixture")

# exp_mixture_gaussian_mis(9000, 9000, 100, 5, "mixture")


# ---
# title: 1514. Path with Maximum Probability
# id: problem1514
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Graph
# link: <https://leetcode.com/problems/path-with-maximum-probability/description/>
# hidden: true
# ---
#
# You are given an undirected weighted graph of `n` nodes (0-indexed),
# represented by an edge list where `edges[i] = [a, b]` is an undirected edge
# connecting the nodes `a` and `b` with a probability of success of traversing
# that edge `succProb[i]`.
#
# Given two nodes `start` and `end`, find the path with the maximum probability
# of success to go from `start` to `end` and return its success probability.
#
# If there is no path from `start` to `end`, **return  0**. Your answer will be
# accepted if it differs from the correct answer by at most **1e-5**.
#
#
#
# **Example 1:**
#
# **![](https://assets.leetcode.com/uploads/2019/09/20/1558_ex1.png)**
#
#
#
#     Input: n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.2], start = 0, end = 2
#     Output: 0.25000
#     Explanation:  There are two paths from start to end, one having a probability of success = 0.2 and the other has 0.5 * 0.5 = 0.25.
#
#
# **Example 2:**
#
# **![](https://assets.leetcode.com/uploads/2019/09/20/1558_ex2.png)**
#
#
#
#     Input: n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.3], start = 0, end = 2
#     Output: 0.30000
#
#
# **Example 3:**
#
# **![](https://assets.leetcode.com/uploads/2019/09/20/1558_ex3.png)**
#
#
#
#     Input: n = 3, edges = [[0,1]], succProb = [0.5], start = 0, end = 2
#     Output: 0.00000
#     Explanation:  There is no path between 0 and 2.
#
#
#
#
# **Constraints:**
#
#   * `2 <= n <= 10^4`
#   * `0 <= start, end < n`
#   * `start != end`
#   * `0 <= a, b < n`
#   * `a != b`
#   * `0 <= succProb.length == edges.length <= 2*10^4`
#   * `0 <= succProb[i] <= 1`
#   * There is at most one edge between every two nodes.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


# atom
a0 = Atom(() -> true; N=0)
a1 = Atom(x -> x > 1)
a2 = Atom((x, y) -> x > y; N=2)

@test a0 == a0 && a0 != a1
@test evaluate(a0) && a0()
@test evaluate(a1, 4) && a1(4)
@test !evaluate(a2, 4, 5) && !a2(4, 5)
@test evaluate(a2, 6, 5) && a2(6, 5)

# negation
n0 = Negation(a0)
n1 = Negation(a1)
n2 = Negation(a2)

@test n0 == n0 && n0 != n1
@test !evaluate(n0) && !n0()
@test !evaluate(n1, 4) && !n1(4)
@test evaluate(n2, 4, 5) && n2(4, 5)
@test !evaluate(n2, 6, 5) && !n2(6, 5)

# conjunction
c0 = Conjunction([(n0, Int[]), (a0, Int[])]; N=0)
c0_3 = Conjunction([(a0, Int[]), (a0, Int[]), (a0, Int[])]; N=0)
c1 = Conjunction([(a0, Int[]), (a1, Int[1])]; N=1)
c1_3 = Conjunction([(a0, Int[]), (a1, Int[1]), (a1, Int[1])]; N=1)
c2 = Conjunction([(a2, Int[2, 1]), (a2, Int[1, 2])]; N=2)
c2_3 = Conjunction([(a0, Int[]), (a1, Int[2]), (a2, Int[2, 1])]; N=2)

@test c0 == c0 && c0 != c1
@test !evaluate(c0) && !c0()
@test evaluate(c0_3) && c0_3()
@test evaluate(c1, 4) && c1(4)
@test evaluate(c1_3, 4) && c1_3(4)
@test !evaluate(c2, 4, 5) && !c2(4, 5)
@test evaluate(c2_3, 4, 5) && c2_3(4, 5)

# default constructor
@test Conjunction([(a0, Int[]), (a0, Int[]), (a0, Int[])]) isa Conjunction{Val{0}}
@test Conjunction([(a0, Int[]), (a1, Int[2]), (a2, Int[2, 1])]) isa Conjunction{Val{2}}

# disjunction
d0 = Disjunction([(n0, Int[]), (a0, Int[])]; N=0)
d0_3 = Disjunction([(n0, Int[]), (n0, Int[]), (n0, Int[])]; N=0)
d1 = Disjunction([(c0, Int[]), (a1, Int[1])]; N=1)
d1_3 = Disjunction([(a0, Int[]), (a1, Int[1]), (a1, Int[1])]; N=1)
d2 = Disjunction([(a2, Int[2, 1]), (a2, Int[1, 2])]; N=2)
d2_3 = Disjunction([(a0, Int[]), (a1, Int[2]), (a2, Int[2, 1])]; N=2)

@test d0 == d0 && d0 != d1
@test evaluate(d0) && d0()
@test !evaluate(d0_3) && !d0_3()
@test evaluate(d1, 4) && d1(4)
@test evaluate(d1_3, 4) && d1_3(4)
@test evaluate(d2, 4, 5) && d2(4, 5)
@test evaluate(d2_3, 4, 5) && d2_3(4, 5)

# default constructor
@test Disjunction([(a0, Int[]), (a0, Int[]), (a0, Int[])]) isa Disjunction{Val{0}}
@test Disjunction([(a0, Int[]), (a1, Int[2]), (a2, Int[2, 1])]) isa Disjunction{Val{2}}

# unary conjunction
cu = Conjunction([n1, a1])
cu_3 = Conjunction([c1, a1, d1])

@test !evaluate(cu, 5) && !cu(5)
@test evaluate(cu_3, 5) && cu_3(5)

# unary disjunction
du = Disjunction([n1, a1])
du_1 = Disjunction([n1])

@test evaluate(du, 5) && du(5)
@test !evaluate(du_1, 5) && !du_1(5)


# simple iid bootstrap
function bootstrap(data)
    n = size(data,1)
    data[rand(1:n,n),:]
end


module BiochemicalLibrary

using ModelingToolkit
using OrderedCollections

export biochemical_library

const _R = 8.314
const _T = 310.0

@parameters t
D = Differential(t)

@variables E[1:2](t) F[1:2](t)

# Chemical species (:Ce)
@parameters k R T
@variables q(t)
Ce_dict = Dict(
    :description => "Chemical species",
    :numports => 1,
    :parameters => OrderedDict(
        k => "Biochemical Constant; exp(mu_0/RT)/V",
        R => "Universal Gas Constant",
        T => "Temperature"
    ),
    :states => OrderedDict(
        q => "Molar Quantity"
    ),
    :equations => [
        0 ~ R * T * log(k * q) - E[1],
        D(q) ~ F[1]
    ],
    :defaults => Dict(k => 1.0, R => _R, T => _T)
)

# Normalised chemical species (:ce)
@parameters k
@variables q(t)
ce_dict = Dict(
    :description => "Chemical species (normalised)",
    :numports => 1,
    :parameters => OrderedDict(
        k => "Biochemical Constant; exp(mu_0/RT)/V"
    ),
    :states => OrderedDict(
        q => "Molar Quantity"
    ),
    :equations => [
        0 ~ log(k * q) - E[1],
        D(q) ~ F[1]
    ],
    :defaults => Dict(k => 1.0, q => 0.0)
)

# Chemical reaction (:Re)
@parameters r R T
Re_dict = Dict(
    :description => "Biochemical reaction",
    :numports => 2,
    :parameters => OrderedDict(
        r => "Reaction Rate",
        R => "Universal Gas Constant",
        T => "Temperature"
    ),
    :equations => [
        0 ~ F[1] + F[2],
        0 ~ F[1] - r * (exp(E[1] / R / T) - exp(E[2] / R / T))
    ],
    :defaults => Dict(r => 1.0, R => _R, T => _T)
)

# Normalised chemical reaction (:re)
@parameters r
re_dict = Dict(
    :description => "Biochemical reaction (normalised)",
    :numports => 2,
    :parameters => OrderedDict(
        r => "Reaction Rate"
    ),
    :equations => [
        0 ~ F[1] + F[2],
        0 ~ F[1] - r * (exp(E[1]) - exp(E[2]))
    ],
    :defaults => Dict(r => 1.0)
)

const biochemical_library = Dict(
    :Ce => Ce_dict,
    :ce => ce_dict,
    :Re => Re_dict,
    :re => re_dict
)

end


import MittagLefflerFunctions.MLF: mlf1

α = 1/2
β = 1.0
z = Complex(-0.5, 0.4)
@assert ( α < 1 ) && ( abs(z) ≤ 1 )

val = mlf1(α, β, z, 10)
ref = erfcx(-z)

@test abs(val-ref) ≤ 1e-10

α = 1.5
β = 1.0
z = Complex(5.5, 0.0)
@assert ( 1 ≤ α < 2 ) && ( abs(z) ≤ floor(20/(2.1-α)^(5.5-2α)) )

val = mlf1(α, β, z, 10)
Eq = MLQuad(α, β, 10, :hyperbola)
ref = Eq(real(z))

@test abs(val-ref) ≤ 1e-10 * abs(ref)

α = 4.0
β = 1.0
z = Complex(15.0, 17.3)
@assert ( α ≥ 2 ) && ( abs(z) ≤ 50 )

val = mlf1(α, β, z, 10)
ref = ( cos(z^(1/4)) + cosh(z^(1/4)) ) / 2

@test abs(val-ref) ≤ 1e-10 * abs(ref)


push!(LOAD_PATH, pwd()*"/../../src/")
using Distributions
using PyPlot
using BVB

lower = -100.
upper = 100.

max_iter = 30
init_approx_d =  Normal(0, 40)

# example 2
true_d = MixtureModel(Normal, [(-3.5, 1), (-1.0, 0.4), (1.0, 0.5), (6.0, 1)], [0.2, 0.3, 0.3, 0.2])
ll(x) = logpdf(true_d, x)
x = linspace(-20,20,500)

include("run_laplacian_gradient_boosting_exclusive.jl")
save_trace("./mog-1/", x, init_approx_d, approx_d, true_d,
                    iter_vec, α_vec,
                    eval_vec, -elbo_vec)
show()

println("Done.")
readline(STDIN)


function f=TBaroWave(x,y,eta,Param)
y=abs(y);
PhiPrim=Param.u0/2*((Param.f0-Param.beta0*Param.y0)...
  *(y-Param.Ly/2-Param.Ly/(2*pi)*sin(2*pi*y/Param.Ly))...
  +Param.beta0/2*(y*y-Param.Ly*y/pi*sin(2*pi*y/Param.Ly)...
  -Param.Ly*Param.Ly/(pi*pi*2)*cos(2*pi/Param.Ly)-...
  -Param.Ly*Param.Ly/3-Param.Ly*Param.Ly/(pi*pi*2)));
TAvg=Param.T0*eta^(Param.Rd*Param.Lapse/Param.Grav);
f=TAvg+PhiPrim/Param.Rd*(2/Param.b^2*(log(eta))^2-1)...
  *exp(-(log(eta)/Param.b)^2);
end


# Examples of abstract types:
abstract type Number end
abstract type Real     <: Number end
abstract type AbstractFloat <: Real end
abstract type Integer  <: Real end
abstract type Signed   <: Integer end
abstract type Unsigned <: Integer end

# Examples of primitive types:
primitive type Char 32 end
primitive type Bool <: Integer 8 end

primitive type Int8    <: Signed   8 end
primitive type Int16   <: Signed   16 end
primitive type Int32   <: Signed   32 end
primitive type Int64   <: Signed   64 end

primitive type UInt8   <: Unsigned 8 end
primitive type UInt16  <: Unsigned 16 end
primitive type UInt32  <: Unsigned 32 end
primitive type UInt64  <: Unsigned 64 end

primitive type Float16 <: AbstractFloat 16 end
primitive type Float32 <: AbstractFloat 32 end
primitive type Float64 <: AbstractFloat 64 end


module CorrNoise

using Random

export Flat128RNG, initflatrng128, GaussRNG, Oof2RNG, OofRNG, randoof2, randoof

const scalefactor = 1 / (1 + 0xFFFFFFFF)

function twiddle(var::UInt32)
    for i = 1:9
        var = xor(var, var << 13)
        var = xor(var, var >> 17)
        var = xor(var, var << 5)
    end

    var
end

"""
    Flat128RNG

State of the base-128 uniform random generator. Initialize this using the
function `initflatrng128`.
"""
mutable struct Flat128RNG <: AbstractRNG
    state::Array{UInt32}
end

function nextstate(state::Array{UInt32})
    tmp::UInt32 = xor(state[1], state[1] << 11)
    state[1] = state[2]
    state[2] = state[3]
    state[3] = state[4]
    state[4] = xor(xor(state[4], state[4] >> 19), xor(tmp, tmp >> 8))

    state
end

"""
    initflatrng128(xstart = 123456789, ystart = 362436069, zstart = 521288629, wstart = 88675123)

Initialize a flat random number generator with period 2^128. To draw random numbers,
use the `Base.rand` function as usual.

Example:
```
rng = initflatrng128()
print([rand(rng) for i in 1:4])
```
"""
function initflatrng128(xstart = 123456789, ystart = 362436069, zstart = 521288629, wstart = 88675123)
    rng = Flat128RNG(Array{UInt32}([xstart, ystart, zstart, wstart]))

    # Suffle the bits in the seeds
    for i = 1:4
        rng.state[i] = twiddle(rng.state[i])
    end

    # Burnin the RNG
    for i = 1:17
        rng.state = nextstate(rng.state)
    end

    rng
end

function Base.rand(rng::Flat128RNG)
    result = rng.state[4] * scalefactor
    nextstate(rng.state)
    result
end

# Gaussian distribution

mutable struct GaussRNG
    flatrng
    empty::Bool
    gset
end

"""
    GaussRNG(flatrng::AbstractRNG)
    GaussRNG(seed=0)

Initialize a Gaussian RNG. The parameter `flatrng` must be a uniform RNG.
If a `seed` is used, then a `MersenneTwister` RNG is used.
"""
GaussRNG(flatrng::AbstractRNG) = GaussRNG(flatrng, true, 0)
GaussRNG(seed=0) = GaussRNG(MersenneTwister(seed))

function Base.randn(state::GaussRNG)
    if state.empty
        local v1, v2, rsq

        while true
            v1 = 2 * rand(state.flatrng) - 1.0
            v2 = 2 * rand(state.flatrng) - 1.0
            rsq = v1^2 + v2^2
            if 0 < rsq < 1
                break
            end
        end

        fac = sqrt(-2log(rsq) / rsq)
        state.gset = v1 * fac
        state.empty = false

        v2 * fac
    else
        state.empty = true
        state.gset
    end
end

# 1/f^2 distribution

mutable struct Oof2RNG
    normrng
    c0::Float64
    c1::Float64
    d0::Float64
    x1::Float64
    y1::Float64
end

"""
    Oof2RNG(normrng, fmin, fknee, fsample)

Create a `Oof2RNG` RNG object. It requires a gaussian RNG generator in `normrng` (use `GaussRNG`),
the minimum frequency (longest period) in `fmin`, the knee frequency and the sampling frequency.
The measure unit of the three frequencies must be the same (e.g., Hz).

Use `randoof2` to draw samples from a `Oof2RNG` object, as in the following example:
```
rng = Oof2RNG(GaussRNG(), 1e-3, 1.0, 1e2)
print([randoof2(rng) for i in 1:4])
```
"""
function Oof2RNG(normrng, fmin::Number, fknee::Number, fsample::Number)
    w0 = π * fmin / fsample
    w1 = π * fknee / fsample

    Oof2RNG(normrng,
            (1.0 + w1) / (1.0 + w0),
            (w1 - 1.0) / (1.0 + w0),
            (1.0 - w0) / (1.0 + w0),
            0,
            0)
end

function oof2filter(rng::Oof2RNG, x2::Float64)
    y2 = rng.c0 * x2 + rng.c1 * rng.x1 + rng.d0 * rng.y1
    rng.x1 = x2
    rng.y1 = y2

    y2
end

"""
    randoof2(rng::Oof2RNG)

Draw a random sample from a 1/f^2 distribution.
"""
randoof2(rng::Oof2RNG) = oof2filter(rng, randn(rng.normrng))

# 1/f^alpha distribution

mutable struct OofRNG
    normrng
    slope
    fmin
    fknee
    fsample
    oof2states::Array{Oof2RNG}
end

wminmax(fmin::Number, fknee::Number) = (log10(2 * π * fmin), log10(2 * π * fknee))

function numofpoles(fmin::Number, fknee::Number, fsample::Number)
    (wmin, wmax) = wminmax(fmin, fknee)

    convert(Int32, floor(2(wmax - wmin) + log10(fsample)))
end

const OOF2STATESIZE = 5
oofstatesize(fmin::Number, fknee::Number, fsample::Number) = OOF2STATESIZE * numofpoles(fmin, fknee, fsample)

"""
    OofRNG(normrng, slope, fmin, fknee, fsample)

Create a `OofRNG` RNG object. It requires a gaussian RNG generator in `normrng`
(use `GaussRNG`), the slope α of the noise in `slope`, the minimum frequency
(longest period) in `fmin`, the knee frequency and the sampling frequency. The
measure unit of the three frequencies must be the same (e.g., Hz).

Use `randoof` to draw samples from a `OofRNG` object, as in the following example:
```
rng = OofRNG(GaussRNG(), -1.5, 1e-3, 1.0, 1e2)
print([randoof(rng) for i in 1:4])
```
"""
function OofRNG(normrng, slope::Number, fmin::Number, fknee::Number, fsample::Number)
    (wmin, wmax) = wminmax(fmin, fknee)
    a = -slope
    nproc = numofpoles(fmin, fknee, fsample)
    dp = (wmax - wmin) / nproc

    p = wmin + (1 - a / 2) * dp / 2
    z = p + a * dp / 2

    oof2states = Array{Oof2RNG}(undef, nproc)
    for i = 1:nproc
        oof2states[i] = Oof2RNG(normrng, 10^p / (2π), 10^z / (2π), fsample)

        p += dp
        z = p + a * dp / 2
    end

    OofRNG(normrng, slope, fmin, fknee, fsample, oof2states)
end

"""
    randoof(rng::OofRNG)

Draw a random sample from a 1/f^α distribution.
"""
function randoof(rng::OofRNG)
    x2 = randn(rng.normrng)
    for curstate in rng.oof2states
        x2 = oof2filter(curstate, x2)
    end

    x2
end

end # module


# This file is a part of AstroLib.jl. License is MIT "Expat".
# Copyright (C) 2016 Mosè Giordano.

# XXX: trailsign keyword not implemented, not sure it's worth the effort.
# Possible strategy: hold the sign of number in a variable (you can use
# copysign(1, number)), set result[1] equal to dd and multiply the first
# non-zero element (use findfirst for that) by the sign of number.
function sixty(number::T) where {T<:AbstractFloat}
    dd = trunc(abs(number))
    mm = trunc(abs(60 * number) - 60 * dd)
    ss = abs(3600 * number)
    return SVector{3,T}(trunc(number), mm, ss - 3600 * dd - 60 * mm)
end

"""
    sixty(number) -> [deg, min, sec]

### Purpose ###

Converts a decimal number to sexagesimal.

### Explanation ###

The reverse of `ten` function.

### Argument ###

* `number`: decimal number to be converted to sexagesimal.

### Output ###

An array of three `AbstractFloat`, that are the sexagesimal counterpart
(degrees, minutes, seconds) of `number`.

### Example ###

```jldoctest
julia> using AstroLib

julia> sixty(-0.615)
3-element StaticArrays.SArray{Tuple{3},Float64,1,3}:
 -0.0
 36.0
 54.0
```

### Notes ###

Code of this function is based on IDL Astronomy User's Library.
"""
sixty(n::Real) = sixty(float(n))