export sincosh

# Precomputed values
const inv_fact = [DoubleFloat64(1.0 / BigFloat(factorial(Int64(k)))) for k = 3:17]
const ninv_fact = length(inv_fact)

Base.ldexp(a::DoubleFloat64, exp::Int) = DoubleFloat64(ldexp(a.hi, exp), ldexp(a.lo, exp))
"""
	mul_pwr2(a::DoubleFloat64, b::Float64)

`a` * `b`,  where `b` is a power of 2.
"""
mul_pwr2(a::DoubleFloat64, b::Float64) = DoubleFloat64(a.hi * b, a.lo * b)

function Base.exp(a::DoubleFloat64)
   #= Strategy:  We first reduce the size of x by noting that

          exp(kr + m * log(2)) = 2^m * exp(r)^k

     where m and k are integers.  By choosing m appropriately
     we can make |kr| <= log(2) / 2 = 0.347.  Then exp(r) is
     evaluated using the familiar Taylor series.  Reducing the
     argument substantially speeds up the convergence.       =#

	k = 512.0
	inv_k = 1.0 / k

	if a.hi ≤ -709.0
		return zero(a)
	end

	if a.hi ≥ 709.0
		convert(DoubleFloat64, Inf)
	end

    if iszero(a)
        return one(a)
    end

    if isone(a)
      	return double_e
    end

    m = floor(a.hi / double_log2.hi + 0.5)
    r = mul_pwr2(a - double_log2 * m, inv_k)

	#
    p = square(r);
    s = r + mul_pwr2(p, 0.5)
    p *= r
    t = p * inv_fact[1]

 	i = 1
    while true
	 	s += t
	 	p *= r
	 	i += 1
	 	t = p * inv_fact[i]

	 	if abs(convert(Float64, t)) < inv_k * double_eps || i > 6
	 		break
	 	end
  	end

    s += t

    s = mul_pwr2(s, 2.0) + square(s)
    s = mul_pwr2(s, 2.0) + square(s)
    s = mul_pwr2(s, 2.0) + square(s)
    s = mul_pwr2(s, 2.0) + square(s)
    s = mul_pwr2(s, 2.0) + square(s)
    s = mul_pwr2(s, 2.0) + square(s)
    s = mul_pwr2(s, 2.0) + square(s)
    s = mul_pwr2(s, 2.0) + square(s)
    s = mul_pwr2(s, 2.0) + square(s)
    s += 1.0

    return ldexp(s, convert(Int, m))
end

function Base.log(a::DoubleFloat64{T}) where {T}
	#= Strategy.  The Taylor series for log converges much more
	 slowly than that of exp, due to the lack of the factorial
	 term in the denominator.  Hence this routine instead tries
	 to determine the root of the function

	     f(x) = exp(x) - a

	 using Newton iteration.  The iteration is given by

	     x' = x - f(x)/f'(x)
	        = x - (1 - a * exp(-x))
	        = x + a * exp(-x) - 1.

	 Only one iteration is needed, since Newton's iteration
	 approximately doubles the number of digits per iteration. =#

	if isone(a)
		return zero(DoubleFloat64{T})
	end

	if a.hi ≤ 0.0
		throw(DomainError("Passed non-positive argument to log."))
	end

	x = DoubleFloat64(log(a.hi)) # Initial approximation

	x + a * exp(-x) - 1.0
end

const sin_table = [DoubleFloat64(sin(k * big(π) * 0.0625)) for k = 1:4]
const cos_table = [DoubleFloat64(cos(k * big(π) * 0.0625)) for k = 1:4]

"""
	sin_taylor(a::DoubleFloat64)

Computes sin(a) using Taylor series. Assumes |a| <= π/32.
"""
function sin_taylor(a::DoubleFloat64)
	thresh = 0.5 * abs(convert(Float64, a)) * double_eps

	if iszero(a)
		return zero(a)
	end

	i = 1
	x = -square(a)
	s = a
	r = a
	while true
		r *= x
		t = r * inv_fact[i]
		s += t
		i += 2
		if i > ninv_fact || abs(convert(Float64, t)) < thresh
			break
		end
	end

	s
end

"""
	cos_taylor(a::DoubleFloat64)

Computes cos(a) using Taylor series. Assumes |a| <= π/32.
"""
function cos_taylor(a::DoubleFloat64)
	thresh = 0.5 * double_eps

	if iszero(a)
		return one(a)
	end

	x = -square(a)
	r = x
	s = 1.0 + mul_pwr2(r, 0.5)
	i = 2
	while true
		r *= x
		t = r * inv_fact[i]
		s += t
		i += 2
		if i > ninv_fact || abs(convert(Float64, t)) < thresh
			break
		end
	end

	s
end

function sincos_taylor(a::DoubleFloat64)
	if iszero(a)
		zero(a), one(a)
	end

  	sin_a = sin_taylor(a)
  	cos_a = sqrt(1.0 - square(sin_a))

  	sin_a, cos_a
end

function Base.sin(a::DoubleFloat64)
  #= Strategy.  To compute sin(x), we choose integers a, b so that

       x = s + a * (pi/2) + b * (pi/16)

     and |s| <= pi/32.  Using the fact that

       sin(pi/16) = 0.5 * sqrt(2 - sqrt(2 + sqrt(2)))

     we can compute sin(x) from sin(s), cos(s).  This greatly
     increases the convergence of the sine Taylor series. =#

	if iszero(a)
		return zero(a)
	end

	# approximately reduce modulo 2*pi
	z = round(a / double_2pi)
	r = a - double_2pi * z

		# approximately reduce modulo pi/2 and then modulo pi/16.

	q = floor(r.hi / double_pi2.hi + 0.5)
	t = r - double_pi2 * q
	j = convert(Int, q)
	q = floor(t.hi / double_pi16.hi + 0.5)
	t -= double_pi16 * q
	k = convert(Int, q)
	abs_k = abs(k)

	if j < -2 || j > 2
		# Cannot reduce modulo pi/2.
		return double_nan
	end

	if (abs_k > 4)
		# Cannot reduce modulo pi/16.
		return double_nan
	end

	if k == 0
		if j == 0
			return sin_taylor(t)
		elseif j == 1
			return cos_taylor(t)
		elseif j == -1
			return -cos_taylor(t)
		else
			return -sin_taylor(t)
		end
	end

	u = cos_table[abs_k]
	v = sin_table[abs_k]
  	sin_t, cos_t = sincos_taylor(t)

	if j == 0
		r = k > 0 ? u * sin_t + v * cos_t : u * sin_t - v * cos_t
  	elseif j == 1
		r = k > 0 ? u * cos_t - v * sin_t : u * cos_t + v * sin_t
	elseif j == -1
		r = k > 0 ? v * sin_t - u * cos_t : -u * cos_t - v * sin_t
	else
		r = k > 0 ? -u * sin_t - v * cos_t : v * cos_t - u * sin_t
	end

	r
end

function Base.cos(a::DoubleFloat64)
	if iszero(a)
		return one(a)
	end

	# approximately reduce modulo 2*pi
	z = round(a / double_2pi)
	r = a - double_2pi * z

		# approximately reduce modulo pi/2 and then modulo pi/16.

	q = floor(r.hi / double_pi2.hi + 0.5)
	t = r - double_pi2 * q
	j = convert(Int, q)
	q = floor(t.hi / double_pi16.hi + 0.5)
	t -= double_pi16 * q
	k = convert(Int, q)
	abs_k = abs(k)

	if j < -2 || j > 2
		# Cannot reduce modulo pi/2.
		return double_nan
	end

	if (abs_k > 4)
		# Cannot reduce modulo pi/16.
		return double_nan
	end

	if k == 0
		if j == 0
			return cos_taylor(t)
		elseif j == 1
			return -sin_taylor(t)
		elseif j == -1
			return sin_taylor(t)
		else
			return -cos_taylor(t)
		end
	end

	u = cos_table[abs_k]
	v = sin_table[abs_k]
  	sin_t, cos_t = sincos_taylor(t)

	if j == 0
		r = k > 0 ? u * cos_t - v * sin_t : u * cos_t + v * sin_t
  	elseif j == 1
		r = k > 0 ? -u * sin_t - v * cos_t : v * cos_t - u * sin_t
	elseif j == -1
		r = k > 0 ? u * sin_t + v * cos_t : u * sin_t - v * cos_t
	else
		r = k > 0 ? v * sin_t - u * cos_t : -u * cos_t - v * sin_t
	end

	r
end

function _sincos(a::DoubleFloat64)
	if iszero(a)
		return zero(a), one(a)
	end

	# approximately reduce modulo 2*pi
	z = round(a / double_2pi)
	r = a - double_2pi * z

	# approximately reduce modulo pi/2 and then modulo pi/16.
	q = floor(r.hi / double_pi2.hi + 0.5)
	t = r - double_pi2 * q
	j = convert(Int, q)
	abs_j = abs(j)
	q = floor(t.hi / double_pi16.hi + 0.5)
	t -= double_pi16 * q
	k = convert(Int, q)
	abs_k = abs(k)

	if abs_j > 2
		# Cannot reduce modulo pi/2.
		return double_nan, double_nan
	end

	if abs_k > 4
		# Cannot reduce modulo pi/16.
		return double_nan, double_nan
	end

  	sin_t, cos_t = sincos_taylor(t)

	if abs_k == 0
		s = sin_t
		c = cos_t
	else
		u = cos_table[abs_k]
		v = sin_table[abs_k]

		s = k > 0 ? u * sin_t + v * cos_t : u * sin_t - v * cos_t
		c = k > 0 ? u * cos_t - v * sin_t : u * cos_t + v * sin_t
	end

	if j == 0
		s, c
  	elseif j == 1
		c, -s
	elseif j == -1
		-c, s
	else
		-s, -c
	end
end

# if VERSION > v"0.7.0-DEV.1319"
# 	@inline Base.sincos(a::DoubleFloat64) = _sincos(a)
# else
export sincos
"""
	sincos(x)

Compute `(sin(x), cos(x))`. This is faster than computing the values
separetly.
"""
@inline sincos(a::DoubleFloat64) = _sincos(a)
# end

Base.atan(a::DoubleFloat64) = atan2(a, one(a))

function Base.atan2(y::DoubleFloat64, x::DoubleFloat64)
	#= Strategy: Instead of using Taylor series to compute
     arctan, we instead use Newton's iteration to solve
     the equation

        sin(z) = y/r    or    cos(z) = x/r

     where r = sqrt(x^2 + y^2).
     The iteration is given by

        z' = z + (y - sin(z)) / cos(z)          (for equation 1)
        z' = z - (x - cos(z)) / sin(z)          (for equation 2)

     Here, x and y are normalized so that x^2 + y^2 = 1.
     If |x| > |y|, then first iteration is used since the
     denominator is larger.  Otherwise, the second is used.
  	=#
	if iszero(x)
		if iszero(y)
			double_nan
		end

		return y.hi > 0.0 ? double_pi2 : -double_pi2
	elseif iszero(y)
		return x.hi > 0.0 ? zero(x) : double_pi
	end

	if x == y
		return y.hi > 0.0 ? double_pi4 : -double_3pi4
	end

	if x == -y
		return y.hi > 0.0 ? double_3pi4 : -double_pi4
	end

	r = sqrt(square(x) + square(y))
	xx = x / r
	yy = y / r

	# Compute double precision approximation to atan.
	z = DoubleFloat64(atan2(convert(Float64, y), convert(Float64, x)))

	if abs(xx.hi) > abs(yy.hi)
		# Use Newton iteration 1.  z' = z + (y - sin(z)) / cos(z)
		sin_z, cos_z = _sincos(z)
		z += (yy - sin_z) / cos_z
	else
		# Use Newton iteration 2.  z' = z - (x - cos(z)) / sin(z)
		sin_z, cos_z = _sincos(z)
		z -= (xx - cos_z) / sin_z
	end

	z
end

function Base.tan(a::DoubleFloat64)
	s, c = _sincos(a)
	s / c
end

function Base.asin(a::DoubleFloat64)
	abs_a = abs(a)

	if abs_a > 1.0
		throw(DomainError())
	end

	if isone(abs_a)
		return a.hi > 0.0 ? double_pi2 : -double_pi2
	end

	atan2(a, sqrt(1.0 - square(a)))
end

function Base.acos(a::DoubleFloat64)
	abs_a = abs(a)

	if abs_a > 1.0
		throw(DomainError())
	end

	if isone(abs_a)
		return a.hi > 0.0 ? zero(a) : -double_pi
	end

	atan2(sqrt(1.0 - square(a)), a)
end

function Base.sinh(a::DoubleFloat64)
	if iszero(a)
		return zero(a)
	end
	if a > 0.05 || a < -0.05
		ea = exp(a)
		return mul_pwr2(ea - inv(ea), 0.5)
	end

	# The computation below didn't yield a satisfying accuracy
	# Thus we just fallback to the Float64 formula which yields at least
	# a
	return DoubleFloat64(sinh(convert(Float64, a)))

	#
	#
	# #=  since a is small, using the above formula gives
    # a lot of cancellation.  So use Taylor series. =#
	# s = a
	# t = a
	# r = square(t)
	# m = 1.0
	# thresh = abs(convert(Float64, a)) * double_eps
	#
	# while true
	# 	m += 2.0
	# 	t *= r
	# 	t /= (m - 1) * m
	#
	# 	s += t
	#
	# 	if abs(t) < thresh
	# 		break
	# 	end
	# end
	#
	# s
end

function Base.cosh(a::DoubleFloat64)
	if iszero(a)
		return one(a)
	end
	ea = exp(a)
	mul_pwr2(ea + inv(ea), 0.5)
end

"""
	sincosh(x)

Compute `(sinh(x), cosh(x))`. This is faster than computing the values
separetly.
"""
function sincosh(a::DoubleFloat64)
	if abs(convert(Float64, a)) ≤ 0.05
		s = sinh(a)
		c = sqrt(1.0 + square(s))
	else
		ea = exp(a)
		inv_ea = inv(ea)
		s = mul_pwr2(ea - inv_ea, 0.5)
		c = mul_pwr2(ea + inv_ea, 0.5)
	end
	s, c
end

function Base.tanh(a::DoubleFloat64)
	if iszero(a)
		return zero(a)
	end
	if abs(convert(Float64, a)) > 0.05
		ea = exp(a)
		inv_ea = inv(ea)
		return (ea - inv_ea) / (ea + inv_ea)
	else
		s = sinh(a)
		c = sqrt(1.0 + square(s))
		return s / c
	end
end


module FieldOperationsTests

using Gridap.Arrays
using Gridap.Fields
using Gridap.TensorValues
using FillArrays

# using BenchmarkTools
using Test

# Operations

d = 2
p = Point(1.0,2.0)

np = 3
x = fill(p,np)

v = VectorValue{d}(1.0,1.0)
f = MockField(v)
fp = evaluate(f,p)

df = f+f
∇fp = 2*evaluate(gradient(f),p)
test_field(df,p,2*fp,grad=2.0*∇fp)

c = return_cache(df,p)
# @btime evaluate!($c,$df,$p)

∇df = ∇(df)
c = return_cache(∇df,p)
# @btime evaluate!($c,$∇df,$p)

df = f-f
test_field(df,p,fp-fp,grad=0.0*∇fp)

df = ConstantField(2.0)*f
test_field(df,p,fp*2.0,grad=2.0*∇fp)

c = return_cache(df,p)
# @btime evaluate!($c,$df,$p)

q(x) = 2*x
∇q = gradient(q)

f = GenericField(q)
fp = evaluate(f,p)
∇f = ∇(f)
∇fp = evaluate(∇f,p)
@test ∇fp == TensorValue(2.0, 0.0, 0.0, 2.0)
test_field(f,p,fp,grad=∇fp)

c = return_cache(f,p)
# @btime evaluate!($c,$f,$p)

df = f+f
test_field(df,p,2*fp,grad=2.0*∇fp)

c = return_cache(df,p)
# @btime evaluate!($c,$df,$p)

df = f-f
test_field(df,p,fp-fp,grad=0.0*∇fp)

df = ConstantField(2.0)*f
evaluate(df,p)
∇df = ∇(df)
∇dfp = 2*∇fp
evaluate(∇df,p)
test_field(df,p,fp*2.0,grad=∇dfp)

c = return_cache(df,p)
# @btime evaluate!($c,$df,$p)
c = return_cache(∇df,p)
# @btime evaluate!($c,$∇df,$p)

df = f⋅f
∇df = ∇(df)
dfp = (∇fp⋅fp)*2
test_field(df,p,fp⋅fp,grad=dfp)

c = return_cache(df,p)
# @btime evaluate!($c,$df,$p)
c = return_cache(∇df,p)
# @btime evaluate!($c,$∇df,$p)

bdf = df
evaluate(bdf,x)
∇bdf = gradient(bdf)
evaluate(∇bdf,x)
bdfp = fill(fp⋅fp,np)
∇bdfp = fill(dfp,np)
test_field(bdf,x,bdfp,grad=∇bdfp)

c = return_cache(bdf,x)
# @btime evaluate!($c,$bdf,$x)
c = return_cache(∇bdf,x)
# @btime evaluate!($c,$∇bdf,$x)

# Composition

q(x) = VectorValue(x[1]^2,x[2]^2)

f = GenericField(q)
bf = f
∇bf = ∇(bf)

evaluate(bf,x)
bdfx = evaluate(bf,evaluate(bf,x))
bdf = evaluate(Operation(bf),bf)
@test evaluate(bdf,x) == bdfx
test_field(bdf,x,bdfx)#,grad=∇bdfp)

bdfx = evaluate(bf+bf,x)
bdf = evaluate(Operation(+),bf,bf)
@test evaluate(bdf,x) == bdfx
test_field(bdf,x,bdfx)#,grad=∇bdfp)

bdfp = evaluate(bf⋅bf,p)
bdfx = evaluate(bf⋅bf,x)
bdf = bf⋅bf
@test evaluate(bdf,x) == bdfx
test_field(bdf,x,bdfx)#,grad=∇bdfp)

evaluate(bf,x)
evaluate(∇bf,x)
evaluate(bdf,x)
evaluate(∇bdf,x)
∇bdfx = evaluate(∇bf,x).⋅evaluate(bf,x)*2
evaluate(∇bdf,x) == ∇bdfx
test_field(bdf,x,bdfx,grad=∇bdfx)

# Composition

bdf = bf∘bf
∇bdf = ∇(bdf)
bdfx = evaluate(bdf,x)
∇bdfx = evaluate(∇bf,evaluate(bf,x)).⋅evaluate(∇bf,x)
test_field(bdf,x,bdfx,grad=∇bdfx)

c = return_cache(∇bdf,x)
# @btime evaluate!(c,∇bdf,x)

end # module


"""
    calculate_conjugacy_classes(group::Bijection{String, T}; inverse=inv, prnt=true) -> conjugacy_classes::Vector{Set{String}}

Calculate all conjugacy classes {Cl(g) = {h * g * h^{-1} | h ∈ 𝒢}} for a given finite, closed group, given the `inverse` function for elements of type `T` in the group `group`.
The `inverse` function defaults to the Julia `inv` function.
`prnt` enables/disables output of the class-sizes and remaining elements while computing the classes.
"""
function calculate_conjugacy_classes(group::Bijection{String, T}; inverse=inv, prnt=true)::Vector{Set{String}} where T
    # store the labels of the conjugacy classes in an Array/Vector of Sets
    conj = Vector{Set{String}}([Set([])])

    # for performance reasons, we don't directly iterate over the keys
    all_keys = sort(collect(keys(group)))
    max_key_idx = length(all_keys)

    #=
    Direct iteration over the keys makes the function slow down at the last few conjugacy classes,
    because for large groups >95% of the elements where already calculated and are unnecessary to check again.
    Instead, we remove all keys that are already found from the all_keys array and check only the remaining ones.
    =#
    while max_key_idx > 0
        k = all_keys[1]

        g = group[k]
        g_conj = Set([k])

        # calculate all conjugate elements to g
        for λ in keys(group)
            h = group[λ]
            el = h * g * inverse(h)
            push!(g_conj, group(el))
        end
        if prnt print("Cl($k): \t|", length(g_conj), "|") end
        # store g_conj
        push!(conj, g_conj)

        # delete all keys in g_conj from all_keys and reset the maximum index
        setdiff!(all_keys, g_conj)
        max_key_idx = length(all_keys)
        if prnt println("\t remaining elements: $max_key_idx") end
    end

    # remove empty set from first position
    popfirst!(conj)
    # return all conjugacy classes
    return conj
end


#@ Model: Sudoku JuMP Model
#@ Description: This is a sample sudoku model

using JuMP, Cbc, Test

#@ Input File: initial_grid_file
#@ Description: Initial grid file
initial_grid_file = open("./examples/jump/sudoku/data.csv", "r")

#@ Helper Object: initial_grid
#@ Description: Initial grid array
initial_grid = zeros(Int, 9, 9)
for row in 1:9
    line = readline(initial_grid_file)
    initial_grid[row, :] .= parse.(Int, split(line, ","))
end

#@ Solver: solver
solver = Cbc.Optimizer

#@ Problem: model
model = Model(with_optimizer(solver, print_level=1))

# Labels
xx = Dict()
cell_labels = Dict()
row_labels = Dict()
col_labels = Dict()
ilabels = Dict()
for i in 1:9
    for j in 1:9
        cell_labels[(i,j)] = "row_$(i)_col_$(j)_single_num"
        row_labels[(i,j)] = "row_$(i)_num_$(j)_once"
        col_labels[(i,j)] = "col_$(i)_num_$(j)_once"
        for k in 1:9
            xx[(i,j,k)] = "row_$(i)_col_$(j)_num_$(k)"
        end
        if initial_grid[i, j] != 0
            k = initial_grid[i, j]
            ilabels[(i,j)] = "row_$(i)_col_$(j)_init_num_$(k)"
        end
    end
end

#@ Variable: x
#@ Description: Some variable x
#@ Labels: xx
@variable(model, x[1:9, 1:9, 1:9], Bin)

#@ Constraint: cell
#@ Description: Only one value appears in each cell
#@ Labels: cell_labels
@constraint(model, cell[i in 1:9, j in 1:9], sum(x[i, j, :]) == 1)

#@ Constraint: row
#@ Description: Each value appears in each row once only
#@ Labels: row_labels
@constraint(model, row[i in 1:9, k in 1:9], sum(x[i, :, k]) == 1)

#@ Constraint: col
#@ Description: Each value appears in each column once only
#@ Labels: col_labels
@constraint(model, col[j in 1:9, k in 1:9], sum(x[:, j, k]) == 1)

#@ Constraint: subgrid
#@ Description: Each value appears in each 3x3 subgrid once only
#@ Labels: sg_labels
@constraint(model, subgrid[i=1:3:7, j=1:3:7, val=1:9], sum(x[i:i+2, j:j+2, val]) == 1)

sg_labels = Dict()
for i in 1:3
    for j in 1:3
        for k in 1:9
            sg_labels[(i,j,k)] = "subgrid_$(i)_$(j)_num_$(k)"
        end
    end
end

#@ Constraint: initial
#@ Description: initial solution constraint
#@ Labels: ilabels
@constraint(model,
            initial[row in 1:9, col in 1:9; initial_grid[row, col] != 0],
            x[row, col, initial_grid[row, col]] == 1)

# Execution:
JuMP.optimize!(model)

#@ Helper Object: solved_grid
#@ Description: Solved grid
values = JuMP.value.(x)
solved_grid = zeros(Int, 9, 9)
for row in 1:9, col in 1:9, val in 1:9
    if values[row, col, val] >= 0.9
        solved_grid[row, col] = val
    end
end

#@ Output File: output.txt
#@ Description: This is a sample output file
open("output.txt", "w") do f
    write(f, "Solution:\n")
    write(f,"[-----------------------]\n")
    for row in 1:9
        write(f, "[ \n")
        for col in 1:9
            write(f, string(solved_grid[row, col]), " ")
            if col % 3 == 0 && col < 9
                write(f, "| \n")
            end
        end
        write(f, "\n]\n")
        if row % 3 == 0
            write(f, "[-----------------------]\n")
        end
    end
end


# [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

"""
    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

```
find_zero(sin, 3, Roots.LithBoonkkampIJzerman(2,0)) # the secant method
find_zero((sin,cos), 3, Roots.LithBoonkkampIJzerman(1,1)) # Newton's method
find_zero((sin,cos), 3, Roots.LithBoonkkampIJzerman(3,1)) # Faster convergence rate
find_zero((sin,cos, x->-sin(x)), 3, Roots.LithBoonkkampIJzerman(1,2)) # Halley-like method
```

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.

```
fx =  ZeroProblem((tanh,x->sech(x)^2), 1.239) # zero at 0.0
solve(fx, Roots.LithBoonkkampIJzerman(1,1)) # Newton, NaN
p = init(fx, Roots.LithBoonkkampIJzerman(2,1))
solve!(p); p.state.steps # 6
p = init(fx, Roots.LithBoonkkampIJzerman(3,1))
solve!(p); p.state.steps # 7
p = init(fx, Roots.LithBoonkkampIJzerman(4,1), verbose=true)
solve!(p); Roots.tracks(p) # 4 iterations, 16 function evaluations
```

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

```
using ForwardDiff
function δ(f, n::Int=1)
    n <= 0 && return f
    n == 1 && return x -> ForwardDiff.derivative(f,float(x))
    δ(δ(f,1),n-1)
end
fs(f,n) = ntuple(i->δ(f,i-1), Val(n+1))

f(x) = cbrt(x)*exp(-x^2) # cf. Table 6 in paper
fx = ZeroProblem(fs(f,1), 0.1147)
opts = (xatol=2eps(), xrtol=0.0, atol=0.0, rtol=0.0) # converge if |xₙ - xₙ₋₁| <= 2ϵ
solve(fx, Roots.LithBoonkkampIJzerman(1, 1); opts...) # NaN -- no convergence
solve(fx, Roots.LithBoonkkampIJzerman(2, 1); opts...) # converges
fx = ZeroProblem(fs(f,2), 0.06)                       # need better starting point
solve(fx, Roots.LithBoonkkampIJzerman(2, 2); opts...) # converges
```

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,d) = LithBoonkkampIJzerman{s,d}()

function init_state(L::LithBoonkkampIJzerman{S,D}, fs, x) where {S,D}

    xs, ys = init_lith(L, fs, x) # [x₀,x₁,…,xₛ₋₁], ...
    R = eltype(xs)
    T = eltype(ys[1])

    ys′ = vcat(ys...)
    # skip unit consideration here, as won't fit within storage of ys
    state = UnivariateZeroState(xs[end],    # xₙ
                                S >= 2 ? xs[end-1] : one(R)*NaN, # xₙ₋₁
                                one(R)*NaN, # α, or xtar
                                xs,         # all xs (not all but first 2)
                                ys[1][end], # fₙ
                                one(T)*NaN, # fₙ₋₁
                                one(T)*NaN, # f(α)
                                ys′,        # flattened ys, as a vector
                                0,          # no steps
                                iszero(D) + S*(1+D), # no function calls in initialization
                                false,false,false,false,
                                "")

    state
end

function update_state(L::LithBoonkkampIJzerman{S,D}, fs, o::UnivariateZeroState, options) where {S,D}

    xs, ys′ = o.m, o.fm
    ys = ntuple(i -> view(ys′, 1 + (i-1)*S:(i*S)), Val(1+D)) # unflatten ys′

    xᵢ = lmm(L, xs, ys...)

    for i in 1:S-1
        xs[i] = xs[i+1]
    end
    xs[end] = xᵢ

    for (i,fⁱ) ∈ enumerate(fs)
        for j ∈ 1:S-1
            ys[i][j] = ys[i][j+1]
        end
        ys[i][end] = fⁱ(xᵢ)
    end

    o.xn0, o.xn1 = o.xn1, xᵢ
    o.fxn0, o.fxn1 = o.fxn1, ys[1][end]
    incfn(o, 1+D)

    nothing

end

# manufacture initial xs, ys
# use lower memory terms to boot strap up. Secant uses initial default step
function init_lith(L::LithBoonkkampIJzerman{S,D}, fs, x₀) where {S,D}
    # initialize xs, ys
    x̃₀ = float(x₀)
    R = eltype(x̃₀)
    f = first(fs)
    T = promote_type(R, eltype(f(x̃₀)))

    xs = Vector{T}(undef,S)
    ys = ntuple(i -> Vector{T}(undef,S), D+1)
    xs[1] = x̃₀
    for (i,fⁱ) ∈ enumerate(fs)
        ys[i][1] = fⁱ(x̃₀)
    end

    # build up to get S of them
    N = 1
    if D == 0
        x₀ = _default_secant_step(x̃₀)
        xs[2] = xs[1] # shift to first
        xs[1] = x₀
        ys[1][2] = f(x₀)
        N += 1
    end
    for (i,S′) ∈ enumerate(N:S-1)
        xᵢ = lmm(LithBoonkkampIJzerman(S′,D), xs, ys...)
        xs[N+i] = xᵢ
        for (j,fʲ) ∈ enumerate(fs)
            ys[j][N+i] = fʲ(xᵢ)
        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

function init_state(::LithBoonkkampIJzermanBracket, F::FirstDerivative, xs)
    u, v = promote(float.(xs)...)
    fu,fv = F.f(u), F.f(v)
    isbracket(fu, fv) || throw(ArgumentError(bracketing_error))

    if abs(fu) < abs(fv)
        a,b,fa,fb = v,u,fv,fu
    else
        a,b,fa,fb = u,v,fu,fv
    end
    f′a,f′b = F.fp(a), F.fp(b)
    c,fc,f′c = a,fa,f′a

    # keep bracket, int[a,b] as xn1, xn0, m=[c]
    # store fb,fa, [fc, f′a, f′c,f′b]
    state = UnivariateZeroState(b,a, one(a)*NaN, [c], # b,c=xₙ,xₙ-1; int[a,b] a bracket
                                fb,fa, one(fb)*NaN, [fc, f′a, f′c, f′b],
                                0, 4,
                                false, false, false, false,
                                "")
end

function update_state(M::LithBoonkkampIJzermanBracket, F, state::UnivariateZeroState{T,S}, options::UnivariateZeroOptions) where {T,S}

    b::T,c::T,a::T = state.xn1, state.m[1], state.xn0
    fb::S,fc::S,fa::S = state.fxn1, state.fm[1], state.fxn0
    f′a::S, f′c::S, f′b::S = state.fm[2],state.fm[3],state.fm[4]

    # 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
            as, bs = lmm_coefficients(LithBoonkkampIJzerman{s,1}(), (c,b), (fc, fb))
            h = -fb

            d₀ = -sum(as .* (c,b))
            mb && (d₀ += h * bs[2]/f′b)
            mc && (d₀ += h * bs[1]/f′c)
        else
            d₀ = lmm(LithBoonkkampIJzerman{s, 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₁ = F.f(d₀), F.f(d₁)

    # 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₀,F.fp(d₀) # interp
    else
        d,fd,f′d = d₁,f₁,F.fp(d₁)  # 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

    state.xn1,state.xn0, state.m[1] = b,a,c
    state.fxn1,state.fxn0 = fb, fa
    state.fm .= (fc, f′a, f′c, f′b)

    incfn(state, 3)
    nothing

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)

# 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 ∈ 1:S) + h * sum(bs[i] * Fs[i] for i ∈ 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 MultipleViewGeometry, Test, Random
using MultipleViewGeometry.ModuleCostFunction
using MultipleViewGeometry.ModuleTypes
using MultipleViewGeometry.ModuleConstraints
using MultipleViewGeometry.ModuleConstruct
using LinearAlgebra
using StaticArrays

# Fix random seed.
Random.seed!(1234)

𝒳 = [Point3D(x,y,rand(50:100)) for x = -100:5:100 for y = -100:5:100]
𝒳 = 𝒳[1:50:end]

# Specify the coordinate systems of the world, the camera frame and the picture
# plane.
world_basis = (Vec(1.0, 0.0, 0.0), Vec(0.0, 1.0, 0.0), Vec(0.0, 0.0, 1.0))
camera_basis = (Point(0.0, 0.0, 0.0), Vec(-1.0, 0.0, 0.0), Vec(0.0, -1.0, 0.0), Vec(0.0, 0.0, 1.0))
picture_basis = (Point(0.0, 0.0), Vec(-1.0, 0.0), Vec(0.0, -1.0))

# The focal length for both cameras is one.
f = 1
image_width = 640 / 10
image_height = 480 / 10

camera₁ = Pinhole(image_width, image_height, f, camera_basis..., picture_basis...)
camera₂ = Pinhole(image_width, image_height, f, camera_basis..., picture_basis...)

# Rotate and translate camera one.
𝐑₁ = Matrix{Float64}(I,3,3)
𝐭₁ = [-50.0, -2.0, 0.0]
relocate!(camera₁, 𝐑₁, 𝐭₁)

# Rotate and translate camera two.
𝐑₂ = Matrix{Float64}(I,3,3)
𝐭₂ = [50.0, 2.0, 0.0]
relocate!(camera₂, 𝐑₂, 𝐭₂)

𝐑₁′, 𝐭₁′ = ascertain_pose(camera₁, world_basis... )
𝐊₁′ = obtain_intrinsics(camera₁, CartesianSystem())
𝐑₂′, 𝐭₂′ = ascertain_pose(camera₂, world_basis... )
𝐊₂′ = obtain_intrinsics(camera₂, CartesianSystem())

# Camera projection matrices.
𝐏₁ = construct(ProjectionMatrix(),𝐊₁′,𝐑₁′,𝐭₁′)
𝐏₂ = construct(ProjectionMatrix(),𝐊₂′,𝐑₂′,𝐭₂′)

# Set of corresponding points.
ℳ = project(camera₁,𝐏₁,𝒳)
ℳʹ = project(camera₂,𝐏₂,𝒳)

𝒴 = triangulate(DirectLinearTransform(),𝐏₁,𝐏₂,(ℳ,ℳʹ))

# Triangulating with the same projection matrices that were used to construct
# (ℳ,ℳʹ) should yield the same 3D points as the original 𝒳.
N = length(𝒴)
for n = 1:N
    @test  isapprox(sum(abs.(𝒳[n]-𝒴[n])/3), 0.0; atol = 1e-11)
end

𝐅 = construct(FundamentalMatrix(),𝐊₁′,𝐑₁′,-𝐭₁′,𝐊₂′,𝐑₂′,-𝐭₂′)

# To triangulate the corresponding points using the Fundamental matrix, we first
# have to factorise the Fundamental matrix into a pair of Camera matrices. Due
# to projective ambiguity, the camera matrices are not unique, and so the
# triangulated 3D points will most probably not match the original 3D points.
# However, when working with noiseless data, the projections of the triangulated
# points should satisfy the epipolar constraint. We can use this fact to
# validate that the triangulation is correctly implemented.
𝒴 = triangulate(DirectLinearTransform(),𝐅,(ℳ,ℳʹ))

𝐐₁, 𝐐₂ = construct(ProjectionMatrix(),𝐅)
# Because we constructed the projection matrices from the fundamental matrix we
# don't know the intrinsics or extrinsics of the camera.
# The current API requires us to construct a CameraModel type for the `project`
# function. TODO: Need to revisit this.
camera₁ = Pinhole(image_width, image_height, f, camera_basis..., picture_basis...)
camera₂ = Pinhole(image_width, image_height, f, camera_basis..., picture_basis...)
𝒪 = project(camera₁,𝐐₁,𝒴)
𝒪ʹ= project(camera₂,𝐐₂,𝒴)
N = length(𝒪)
for n = 1:N
    𝐦 = hom(𝒪[n])
    𝐦ʹ = hom(𝒪ʹ[n])
    @test  isapprox(𝐦'*𝐅*𝐦ʹ, 0.0; atol = 1e-12)
end


module NonSymFEMDriver

using Test
using Gridap
using GridapPardiso

model = CartesianDiscreteModel(
  domain=(0.0,1.0,0.0,1.0), partition=(2,2))

const T = VectorValue{2,Float64}

order = 2
fespace1 = FESpace(
  reffe = :QLagrangian,
  conformity = :H1,
  valuetype = T,
  model = model,
  order = order,
  diritags = "boundary")

fespace2 = FESpace(
  reffe = :PLagrangian,
  conformity = :L2,
  valuetype = Float64,
  model = model,
  order = order-1)

fespace2 = ConstrainedFESpace(fespace2,[1,])

V = TestFESpace(fespace1)
Q = TestFESpace(fespace2)
Y = [V, Q]

U = TrialFESpace(fespace1, zero(T))
P = TrialFESpace(fespace2)
X = [U, P]

trian = Triangulation(model)
quad = CellQuadrature(trian,degree=4)

function a_Ω(y,x)
  v, q = y
  u, p = x
  inner(∇(v),∇(u)) - inner(div(v),p) + inner(q,div(u))
end

function b_Ω(y)
  v, q = y
  inner(v,(x)->2*x)
end

t_Ω = AffineFETerm(a_Ω,b_Ω,trian,quad)

op = LinearFEOperator(Y,X,t_Ω)

ls = PardisoSolver()
# This one works: ls = LUSolver()
solver = LinearFESolver(ls)

xh = solve(solver,op)

@show num_free_dofs(U)
@show num_free_dofs(P)

x = free_dofs(xh)
A = op.mat
b = op.vec

@show all(abs.(A - A') .< 1e-13)

r = A*x - b

tol = 1e-10
@test maximum(abs.(r))/maximum(abs.(b)) < tol

end # module


# A complicated way to do argmin x^1/2 - x^1/3
module TestExampleDiagonal

using DiffEqBase
using Setfield
using SteadyStateFit
using Test

struct NullSetter <: Lens end
Setfield.set(obj, ::NullSetter, ::Any) = obj

ode = ODEProblem(
    ODEFunction(
        (u, p, t) -> [u[1]^2, u[2]^3] .- exp.(p);
        jac = (u, p, t) -> [
            2u[1]   0
            0       3u[2]^2
        ],
    ),
    [1.0, 1.0],  # u0
    nothing,
    [1.0],  # p
)

sso = SteadyStateObjective(
    (((x1, x2),), (c,), _) -> x1 - c * x2,  # loss
    ode,
    [1],                           # conditions
    NullSetter(),                  # conditionsetter
    (@lens _),                     # parameterlens
)

result = solve(sso)

@test exp(Optim.minimizer(result)[1]) ≈ 64/729  rtol=0.01
@test occursin("SteadyStateFitResult", sprint(show, "text/plain", result))
@test occursin("SteadyStateFitResult: success",
               sprint(show, result; context=:limit => true))
@test "SteadyStateObjective: 1 conditions 1 parameters" ==
    sprint(show, sso; context=:limit => true)

result2 = solve(sso, Optim.BFGS(), Optim.Options(store_trace=true))
@test exp(Optim.minimizer(result2)[1]) ≈ 64/729  rtol=0.01

end  # module


struct Bias{T}
    array::Vector{Vector{T}}
    function Bias{T}(bias_vec_of_vec::Vector{Vector{T}}) where {T}
        return new(copy(bias_vec_of_vec))
    end
end

function Bias(bias_vec_of_vec::Vector{Vector{T}}) where {T}
    return Bias{T}(bias_vec_of_vec)
end

function Bias(n_neuronns_per_layers::Vector{Int}, T = Float64)
    # for layers = 2..N_layers
    N_layers = length(n_neuronns_per_layers)
    bias_vec_of_vec = Vector{Vector{T}}(undef, N_layers-1)
    for ll = 1:N_layers-1
        tmp = randn(T, n_neuronns_per_layers[ll+1])
        bias_vec_of_vec[ll] = tmp
    end
    return Bias(bias_vec_of_vec)
end

function subarray(bias::Bias; layer = 1)
    return bias.array[layer]
end

struct WeightMatrix{T}
    # n_row = 1..n_neurons in the next layer
    # n_col = 1..n_neurons in the prev layer
    array::Vector{Matrix{T}}

    function WeightMatrix{T}(weightmat_vec::Vector{Matrix{T}}) where {T}
        return new(weightmat_vec)
    end
end

function WeightMatrix(weightmat_vec::Vector{Matrix{T}}) where {T}
    return WeightMatrix{T}(weightmat_vec)
end

function WeightMatrix(n_neuronns_per_layers::Vector{Int}, T = Float64)
    # for between layers = 1..N_layers
    N_layers   = length(n_neuronns_per_layers)
    weight_mat = Vector{Matrix{T}}(undef, N_layers-1)
    for ll = 1:N_layers-1
        n_prev         = n_neuronns_per_layers[ll]   # n_col
        n_next         = n_neuronns_per_layers[ll+1] # n_row
        tmp            = randn(T, n_next, n_prev)
        weight_mat[ll] = tmp
    end

    return WeightMatrix(weight_mat)
end

function submatrix(weight_mat::WeightMatrix; layer = 1)
    return weight_mat.array[layer]
end

function zeros!(weight_mat::WeightMatrix)
    L = length(weight_mat.array)
    for ii = 1:L
        v = weight_mat.array[ii]
        T = eltype(v)
        for jj in eachindex(v)
            v[jj] = zero(T)
        end
    end
end

struct IntermediateVectorResult{T}
    array::Vector{Vector{T}}
    function IntermediateVectorResult{T}(vec_of_vec::Vector{Vector{T}}) where {T}
        return new(copy(vec_of_vec))
    end
end

function IntermediateVectorResult(vec_of_vec::Vector{Vector{T}}) where {T}
    return IntermediateVectorResult{T}(vec_of_vec)
end

function IntermediateVectorResult(n_neuronns_per_layers::Vector{Int}, T = Float64)
    # for layers = 2..N_layers
    N_layers   = length(n_neuronns_per_layers)
    vec_of_vec = Vector{Vector{T}}(undef, N_layers-1)
    for ll = 1:N_layers-1
        # tmp            = Vector{T}(undef, n_neuronns_per_layers[ll+1])
        tmp            = zeros(T, n_neuronns_per_layers[ll+1])
        vec_of_vec[ll] = tmp
    end
    return IntermediateVectorResult(vec_of_vec)
end

function subarray(res::IntermediateVectorResult; layer = 1)
    return res.array[layer]
end

function zeros!(res::IntermediateVectorResult)
    L = length(res.array)
    for ii = 1:L
        v = res.array[ii]
        T = eltype(v)
        for jj in eachindex(v)
            v[jj] = zero(T)
        end
    end
end

struct PerformanceStat{T}
    perf::Vector{T}
    min::T
    max::T
    mean::T
    sigma::T
    function PerformanceStat{T}(perf::Vector{T}) where {T}
        min   = minimum(perf)
        max   = maximum(perf)
        s     = sum(perf)
        N     = length(perf)
        mean  = s / N
        v     = 1/(N-1) * sum( (perf .- mean).^2 )
        sigma = sqrt(v)
        return new(perf, min, max, mean, sigma)
    end
end

function PerformanceStat(perf::Vector{T}) where {T}
    return PerformanceStat{T}(perf)
end

function Base.show(io::IO, perfstat::PerformanceStat)
    println("Performance stat:")
    println("    - min:   $(perfstat.min)")
    println("    - mean:  $(perfstat.mean)")
    println("    - max:   $(perfstat.max)")
    println("    - sigma: $(perfstat.sigma)")

end


import MechanicalSketch
import MechanicalSketch: mm, kg, m, set_scale_sketch, HE, WI, EM, PT, circle, text, N, kN, PALETTE
import MechanicalSketch: color_with_lumin, empty_figure, background, sethue, O, °, finish
import MechanicalSketch: line, setline, setopacity, scale_pt_to_unit, empty_figure
let
# Rope data from https://www.hendrikvedergroup.com/_asset/_public/Hendrik-Veder-Group/Downloads/8896-03-Dyneema-folder-offset_LR-v4.pdf
# Diameters below 12 mm are excluded here, because they vary too much for reasonable generalization. Properties are generally better for
# smaller rope
ROPE_D = [12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44,
    46, 48, 50, 52, 56, 60, 64, 68, 72, 80, 88, 96]mm
ROPE_WEIGHT = [9.50, 12.80, 16, 20.80, 25.50, 30.50, 35.80, 41, 46.50, 52, 57, 62.50, 68, 74, 84, 93,
    102, 111, 121, 131, 141, 163, 175, 200, 226, 254, 313, 379, 451]kg/100m
ROPE_MINIMUM_BREAK = [161.90, 215.80, 269.80, 343.30, 407.10, 490.50, 569, 647.40, 725.90, 799.50, 868.20, 941.70, 1020.20,
    1098.70, 1245.80, 1373.40, 1491.10, 1618.60, 1755.90, 1893.30, 2020.80, 2315.10, 2472, 2766.30, 3099.90, 3413.80, 4139.70,
    4934.30, 5768.10]kN
ρ_SK75 = 0.975 * 0.001kg/(10mm)^3
σ_TS_SK75 = 3600N/mm^2

BACKCOLOR = color_with_lumin(PALETTE[8], 90);
function restart()
    empty_figure(filename = joinpath(@__DIR__, "test_17.png"))
    background(BACKCOLOR)
    sethue(PALETTE[5])
    set_scale_sketch(sum(ROPE_D) * 1.3, HE)
end
restart()

# Horizontal positions per rope
pts = let
    posx = [0.0]mm
    pop!(posx)
    for (i,d) in enumerate(ROPE_D)
        p = if i == 1
                -0.45 * WI * scale_pt_to_unit(m)
        else
            posx[i-1] + max(60.0mm, d*1.1)
        end
        push!(posx, p)
    end
    pts = map(x-> O +(x, 700.0mm), posx)
end
let
    circle.(pts, ROPE_D ./ 2,:fill)
    labs = string.(ROPE_D)
    text.(labs, O + (0, EM) .+pts, -60.0°)
end

circlearea(d) = π / 4 * d^2
circleradius(A) = sqrt(A/π)
# Plot packed area
let
    A_rope = ROPE_WEIGHT ./ ρ_SK75 .|> mm^2
    sethue(color_with_lumin(PALETTE[5], 80))
    circle.(pts , circleradius.(A_rope),:fill)
end

"Relative filled areas, actual to full circle"
area_rel =  (ROPE_WEIGHT ./ ρ_SK75 .|> mm^2) ./ circlearea.(ROPE_D)
# Plot relative filled areas
setline(3PT)
bottoms = map(pt -> pt + (0.0mm, -600.0mm), pts)
tops = map(pt -> pt + (0.0mm, 300.0mm), bottoms)
line.(bottoms, tops, :stroke)
stri = "Fill factors and approximation (green line)"
text(stri, tops[1] + (0.0, -0.5EM))

sethue(PALETTE[4])
map(tops, bottoms, area_rel) do t, b, rel
    line(b, b + (t - b) * rel, :stroke)
end
# lsq method applied
fill_factor(d) = (-0.003*d/mm + 0.853)
area_filled(d) = fill_factor(d)* circlearea(d)

# Plot appproximated area
sethue(PALETTE[3])
setopacity(0.5)
map(tops, bottoms, ROPE_D) do t, b, d
    rel = fill_factor(d)
    top = b + (t - b) * rel
    line(top + (-0.5EM, 0.0), top + (0.5EM, 0.0), :stroke)
end
setopacity(1.0)

ideal_spin_strength(d) = area_filled(d) * σ_TS_SK75 |> kN
spinfactors = ROPE_MINIMUM_BREAK./ ideal_spin_strength.(ROPE_D)
#lsq method applied
spin_factor(d) = 5*10^-5 * (d/mm)^2 + -0.0059*(d/mm) + 0.5504
breaking_strength(d) = spinfactor(d) * ideal_spin_strength(d)

# Plot spin factors
setline(3PT)
sethue(color_with_lumin(PALETTE[5], 80))
bottoms = map(pt -> pt + (0.0mm, -1200.0mm), pts)
tops = map(pt -> pt + (0.0mm, 300.0mm), bottoms)
line.(bottoms, tops, :stroke)
stri = "Spin factors and approximation (green line)"
text(stri, tops[1] + (0.0, -0.5EM))

sethue(PALETTE[4])
map(tops, bottoms, spinfactors) do t, b, rel
    line(b, b + (t - b) * rel, :stroke)
end

# Plot appproximated spin factor
sethue(PALETTE[3])
setopacity(0.5)
map(tops, bottoms, ROPE_D) do t, b, d
    rel = spin_factor(d)
    top = b + (t - b) * rel
    line(top + (-0.5EM, 0.0), top + (0.5EM, 0.0), :stroke)
end
setopacity(1.0)

finish()
# Reset default
set_scale_sketch()

nothing
end


#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Functions to compute general values related to the orbit.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

export @check_orbit
export angvel, angvel_to_a, dargp, draan, period

################################################################################
#                                    Macros
################################################################################

"""
    @check_orbit(a, e)

Verify if the orbit with semi-major axis `a` [m] and eccentricity `e` is valid.
This macro throws an exception if the orbit is not valid.

Return `true` is the orbit is valid, and `false` otherwise.

"""
macro check_orbit(a, e)
    quote
        # Check if the arguments are valid.
        if $(esc(a)) < 0
            false
        elseif !( 0 <= $(esc(e)) < 1 )
            false
        else
            # Compute the orbit perigee and check if it is inside Earth.
            perigee = $(esc(a))*(1-$(esc(e)))

            if perigee < R0
                false
            else
                true
            end
        end
    end
end

################################################################################
#                                  Functions
################################################################################

"""
    angvel(a::Number, e::Number, i::Number, pert::Symbol = :J2)
    angvel(orb::Orbit, pert::Symbol = :J2)

Compute the angular velocity [rad/s] of an object in an orbit with semi-major
axis `a` [m], eccentricity `e`, and inclination `i` [rad], using the
perturbation terms specified by the symbol `pert`. The orbit can also be
specified by `orb` (see [`Orbit`](@ref)).

`pert` can be:

* `:J0`: Consider a Keplerian orbit.
* `:J2`: Consider the perturbation terms up to J2.
* `:J4`: Consider the perturbation terms J2, J4, and J2².

If `pert` is omitted, then it defaults to `:J2`.

"""
@inline function angvel(a::Number, e::Number, i::Number, pert::Symbol = :J2)
    # Unperturbed orbit period.
    n0 = sqrt(m0/Float64(a)^3)

    # Perturbation computed using a Keplerian orbit.
    if pert == :J0
        return n0
    # Perturbation computed using perturbations terms up to J2.
    elseif pert == :J2
        # Auxiliary variables.
        cos_i² = cos(i)^2
        aux   = 1-e^2

        # Semi-lactum rectum.
        p = a*aux

        # Orbit period considering the perturbations (up to J2).
        return n0 + 3R0^2*J2/(4p^2)*n0*(sqrt(aux)*(3cos_i²-1) + (5cos_i²-1))

    # Perturbation computed using perturbations terms J2, J4, and J2².
    elseif pert == :J4

        # Auxiliary variables
        e²     = e^2
        e⁴     = e^4
        sin_i  = sin(i)
        sin_i² = sin_i^2
        sin_i⁴ = sin_i^4
        aux    = (1-e²)
        saux   = sqrt(aux)
        p      = (a/R0)*aux
        p²     = p^2
        p⁴     = p^4

        # Notice that:
        #            .   .
        #   n = n₀ + ω + M₀
        #
        # in which the time-derivatives are computed as in [1, p. 692].

        δω   =  3/  4*n0*J2  /p²*(4 - 5sin_i²) +
                9/384*n0*J2^2/p⁴*(     56e² + (760 -  36e²)*sin_i² - (890 +  45e²)*sin_i⁴) -
               15/128*n0*J4  /p⁴*(64 + 72e² - (248 + 252e²)*sin_i² + (196 + 189e²)*sin_i⁴)

        δM_0 =  3/  4*n0*J2  /p²*saux*(2 - 3sin_i²) +
                3/512*n0*J2^2/p⁴/saux*(320e² - 280e⁴ + (1600 - 1568e² + 328e⁴)*sin_i² + (-2096 + 1072e² +  79e⁴)*sin_i⁴) -
               45/128*n0*J4  /p⁴*saux*e²*(-8 + 40sin_i - 35sin_i²)

        return n0 + δω + δM_0
    else
        throw(ArgumentError("The perturbation parameter $pert is not defined."))
    end
end

@inline function angvel(orb::Orbit, pert::Symbol = :J2)
    # Convert first to Keplerian elements.
    k = convert(KeplerianElements, orb)
    return angvel(get_a(k), get_e(k), get_i(k), pert)
end

"""
    angvel_to_a(n::Number, e::Number, i::Number, pert::Symbol = :J2; μ::Number = m0, max_iter::Int = 20, tol::Number = 1e-10)

Compute the semi-major axis that will provide an angular velocity `n` [rad/s] in
an orbit with eccentricity `e` and inclination `i` [rad], using the perturbation
terms specified by the symbol `pert`.

Notice that the angular velocity `n` is related to the nodal period, *i.e.* the
time between two consecutive passages by the ascending node.

`pert` can be:

* `:J0`: Consider a Keplerian orbit.
* `:J2`: Consider the perturbation terms up to J2.
* `:J4`: Consider the perturbation terms J2, J4, and J2².

If `pert` is omitted, then it defaults to `:J2`.

# Keyword

* `μ`: Standard gravitational parameter for Earth [m^3/s^2].
       (**Default** = `m0`)
* `max_iter`: Maximum number of iterations allowed in the Newton-Raphson
              algorithm. (**Default** = 20)
* `tol`: Tolerance to stop the Newton-Raphson algorithm. (**Default** = 1e-10)

"""
function angvel_to_a(n::Number, e::Number, i::Number, pert::Symbol = :J2;
                     μ::Number = m0, max_iter::Int = 20, tol::Number = 1e-10)

    if pert == :J0

        a = (μ/n^2)^(1/3)
        return a

    elseif pert == :J2
        # Get the semi-major axis [m] that will provide the mean motion `n`
        # using perturbation terms up to J2.
        #
        # This can only be done using a numerical algorithm to solve the
        # following equation for `a`:
        #
        #           -                                                      -
        #          |      3        sqrt(1-e²).(3cos²(i)-1) + (5cos²(i)-1))  |
        #   n = n₀ | 1 + ---. J2 .----------------------------------------- |
        #          |      4                    a^2.(1-e^2)^2                |
        #           -                                                      -
        #
        #         sqrt(μ)
        #   n₀ = ----------
        #         a^(3/2)
        #

        # To improve algorithm stability, we will compute the normalized
        # semi-major axis, i.e. `a/R0`.

        # Auxiliary variables to solve for the semi-major axis.
        sqrt_μ = sqrt(μ/R0^3)
        cos_i  = cos(i)
        K      = 3/4*J2*( sqrt(1-e^2)*(3cos_i^2-1) + (5cos_i^2-1) )/(1-e^2)^2

        # Initial guess using a non-perturbed orbit.
        a = (μ/n^2)^(1/3)/R0

        # Newton-Raphson algorithm.
        #
        # Notice that we will allow, at most, 20 iterations.
        for k = 1:20
            # Auxiliary variables.
            ap3o2  = a^(3/2)
            ap5o2  = ap3o2*a # -> a^(5/2)
            ap7o2  = ap5o2*a # -> a^(7/2)
            ap11o2 = ap7o2*a # -> a^(11/2)

            # Compute the residue.
            res = n - sqrt_μ/ap3o2 - K*sqrt_μ/ap7o2

            # Compute the Jacobian of the function.
            df = +3/2*sqrt_μ/ap5o2 + 7/2*K*sqrt_μ/ap11o2

            # Compute the new estimate.
            a = a - res/df

            (abs(res) < tol) && break
        end

        return a*R0

    elseif pert == :J4

        # Auxiliary variables
        sqrt_μ = sqrt(μ/R0^3)
        sin_i  = sin(i)
        sin_i² = sin_i^2
        sin_i⁴ = sin_i^3
        e²     = e^2
        e⁴     = e^4
        aux    = 1-e²
        saux   = sqrt(aux)

        # Get the semi-major axis using J4 perturbation theory [er].
        #
        # This can only be done using a numerical algorithm to solve the
        # following equation for `a`:
        #
        #            .   .
        #   n = n₀ + ω + M₀ ,
        #
        #         sqrt(μm)
        #   n₀ = ---------- ,
        #          a^(3/2)
        #
        # and the time-derivatives of the argument of perigee and the initial
        # mean anomaly is compute considering the terms J2, J4, and J2².

        # To improve algorithm stability, we will compute the normalized
        # semi-major axis, i.e. `a/R0`.

        K₁ = 3/4*J2/aux^2*( (4 - 5sin_i²) + sqrt(1-e²)*(2 - 3sin_i²) )

        K₂ = 3/512*J2^2/aux^4*(224e² + (3040 - 144e²)*sin_i² - (3560 + 180e²)*sin_i⁴ +
                               1/saux*((         320e² - 280e⁴) +
                                       ( 1600 - 1568e² + 328e⁴)*sin_i² +
                                       (-2096 + 1072e² +  79e⁴)*sin_i⁴))

        K₃ = 15/128*J4/aux^4*(64 + 72e² - (248 + 252e²)*sin_i² + (196 + 189e²)*sin_i⁴ +
                              e²/saux*(-8 + 40sin_i - 35sin_i²))

        # Initial guess using a non-perturbed orbit.
        a = (μ/n^2)^(1/3)/R0

        # Newton-Raphson algorithm.
        #
        # Notice that we will allow, at most, 20 iterations.
        for k = 1:20
            # Auxiliary variables.
            ap3o2  = a^(3/2)
            ap5o2  =  ap3o2*a  # -> a^(5/2)
            ap7o2  =  ap5o2*a  # -> a^(7/2)
            ap9o2  =  ap7o2*a  # -> a^(9/2)
            ap11o2 =  ap9o2*a  # -> a^(11/2)
            ap13o2 = ap11o2*a  # -> a^(13/2)

            # Compute the residue.
            res = n - sqrt_μ*(1/ap3o2 + K₁/ap7o2 + (K₂ + K₃)/ap11o2)

            # Compute the Jacobian of the function.
            df = sqrt_μ/2*( 3ap5o2 + 7K₁/ap9o2 + 11*(K₂ + K₃)/ap13o2)

            # Compute the new estimate.
            a = a - res/df

            (abs(res) < tol) && break
        end

        return a*R0
    else
        throw(ArgumentError("The perturbation parameter $pert is not defined."))
    end
end

"""
    dargp(a::Number, e::Number, i::Number, pert::Symbol = :J2)
    dargp(orb::Orbit, pert::Symbol = :J2)

Compute the time-derivative of the argument of perigee [rad/s] of an orbit with
semi-major axis `a` [m], eccentricity `e`, and inclination `i` [rad], using the
perturbation terms specified by the symbol `pert`. The orbit can also be
specified by `orb` (see [`Orbit`](@ref)).

`pert` can be:

* `:J0`: Consider a Keplerian orbit.
* `:J2`: Consider the perturbation terms up to J2.
* `:J4`: Consider the perturbation terms J2, J4, and J2².

If `pert` is omitted, then it defaults to `:J2`.

"""
@inline function dargp(a::Number, e::Number, i::Number, pert::Symbol = :J2)
    # Perturbation computed using a Keplerian orbit.
    if pert == :J0
        return 0.0
    # Perturbation computed using perturbations terms up to J2.
    elseif pert == :J2
        # Semi-lactum rectum.
        p = a*(1-e^2)

        # Unperturbed orbit period.
        n0 = angvel(a, e, i, :J0)

        # Perturbation of the argument of perigee.
        return 3R0^2*J2/(4p^2)*n0*(5cos(i)^2-1)

    elseif pert == :J4
        # Auxiliary variables
        e²     = e^2
        e⁴     = e^4
        sin_i  = sin(i)
        sin_i² = sin_i^2
        sin_i⁴ = sin_i^4
        aux    = (1-e²)
        saux   = sqrt(aux)
        p      = (a/R0)*aux
        p²     = p^2
        p⁴     = p^4

        # Unperturbed orbit period.
        n0 = angvel(a, e, i, :J0)

        # Perturbation of the argument of perigee.
        δω =  3/  4*n0*J2  /p²*(4 - 5sin_i²) +
              9/384*n0*J2^2/p⁴*(     56e² + (760 -  36e²)*sin_i² - (890 +  45*e²)*sin_i⁴) -
             15/128*n0*J4  /p⁴*(64 + 72e² - (248 + 252e²)*sin_i² + (196 + 189*e²)*sin_i⁴)

        return δω
    else
        throw(ArgumentError("The perturbation parameter $pert is not defined."))
    end
end

function dargp(orb::Orbit, pert::Symbol = :J2)
    # Convert first to Keplerian elements.
    k = convert(KeplerianElements, orb)
    return dargp(get_a(k), get_e(k), get_i(k), pert)
end

"""
    draan(a::Number, e::Number, i::Number, pert::Symbol = :J2)
    draan(orb::Orbit, pert::Symbol = :J2)

Compute the time-derivative of the right ascension of the ascending node [rad/s]
of an orbit with semi-major axis `a` [m], eccentricity `e`, and inclination `i`
[rad], using the perturbation terms specified by the symbol `pert`. The orbit
can also be specified by `orb` (see [`Orbit`](@ref)).

`pert` can be:

* `:J0`: Consider a Keplerian orbit.
* `:J2`: Consider the perturbation terms up to J2.
* `:J4`: Consider the perturbation terms J2, J4, and J2².

If `pert` is omitted, then it defaults to `:J2`.

"""
@inline function draan(a::Number, e::Number, i::Number, pert::Symbol = :J2)
    # Perturbation computed using a Keplerian orbit.
    if pert == :J0
        return 0.0
    # Perturbation computed using perturbations terms up to J2.
    elseif pert == :J2
        # Semi-lactum rectum.
        p = a*(1-e^2)

        # Unperturbed orbit period.
        n0 = angvel(a, e, i, :J0)

        # Perturbation of the right ascension of the ascending node.
        return -3/2*R0^2/(p^2)*n0*J2*cos(i)

    # Perturbation computed using perturbation terms J2, J4, and J2².
    elseif pert == :J4
        # Auxiliary variables
        sin_i, cos_i = sincos(i)
        sin_i²       = sin_i^2
        e²           = e^2
        p            = (a/R0)*(1-e²)
        p²           = p^2
        p⁴           = p^4

        # Unperturbed orbit period.
        n0 = angvel(a, e, i, :J0)

        # Perturbation of the right ascension of the ascending node.
        δΩ = -3/2 *n0*J2  /p²*cos_i +
              3/32*n0*J2^2/p⁴*cos_i*(12 -  4e² - (80 +  5e²)*sin_i²) +
             15/32*n0*J4  /p⁴*cos_i*( 8 + 12e² - (14 + 21e²)*sin_i²)

        return δΩ
    else
        throw(ArgumentError("The perturbation parameter $pert is not defined."))
    end
end

function draan(orb::Orbit, pert::Symbol = :J2)
    # Convert first to Keplerian elements.
    k = convert(KeplerianElements, orb)
    return draan(get_a(k), get_e(k), get_i(k), pert)
end

"""
    period(a::Number, e::Number, i::Number, pert::Symbol = :J2)

Compute the period [s] of an object in an orbit with semi-major axis `a` [m],
eccentricity `e`, and inclination `i` [rad], using the perturbation terms
specified by the symbol `pert`. The orbit can also be specified by `orb` (see
[`Orbit`](@ref)).

pert` can be:

* `:J0`: Consider a Keplerian orbit.
* `:J2`: Consider the perturbation terms up to J2.
* `:J4`: Consider the perturbation terms J2, J4, and J2².

If `pert` is omitted, then it defaults to `:J2`.

"""
@inline function period(a::Number, e::Number, i::Number, pert::Symbol = :J2)
    n = angvel(a, e, i, pert)
    return 2π/n
end

function period(orb::Orbit, pert::Symbol = :J2)
    # Convert first to Keplerian elements.
    k = convert(KeplerianElements, orb)
    return period(get_a(k), get_e(k), get_i(k), pert)
end


@testset "blockBiCGSTB" begin
	@testset "real matrix" begin
		nrhs = 4;
		n = 100
		A  = sprandn(n,n,.1) + 10*I
		D  = Array(diag(A))
		Af = x ->  A*x
		M  = x ->  D.\x
		rhs = randn(n,nrhs)

		# test flag for early stopping
		xt = blockBiCGSTB(A,rhs,tol=1e-6,maxIter=3,out=2)
		@test xt[2]==-1

		# test handling of zero rhs
		xt = blockBiCGSTB(A,zeros(n,nrhs),tol=1e-6,maxIter=3,out=2)
		@test  all(xt[1].==0)
		@test xt[2]==-9

		x0 = blockBiCGSTB(Matrix(A),rhs,tol=1e-6)
		x1 = blockBiCGSTB(A,rhs,tol=1e-6,out=1)
		x2 = blockBiCGSTB(Af,rhs,tol=1e-6)
		x3 = blockBiCGSTB(Af,rhs,tol=1e-6,maxIter=100,x=randn(size(rhs)))
		x5 = blockBiCGSTB(Af,rhs,tol=1e-6,maxIter=100,M1=M)

		@test norm(A*x0[1]-rhs)/norm(rhs) < 1e-6
		@test norm(A*x1[1]-rhs)/norm(rhs) < 1e-6
		@test norm(A*x2[1]-rhs)/norm(rhs) < 1e-6
		@test norm(A*x3[1]-rhs)/norm(rhs) < 1e-6
		@test norm(A*x5[1]-rhs)/norm(rhs) < 1e-6
		@test norm(x2[1]-x1[1])/norm(x1[1]) < 1e-12
		@test norm(x3[1]-x1[1])/norm(x1[1]) < 1e-5
		@test norm(x0[1]-x1[1])/norm(x1[1]) < 1e-5
	end
	@testset "complex matrix" begin
		nrhs = 4;
		n = 100
		A  = sprandn(n,n,.1) + 10*I + im*(sprandn(n,n,.1) + 10*I )
		D  = Array(diag(A))
		Af = x -> A*x
		M  = x -> D.\x
		rhs = randn(n,nrhs) + 1im * randn(n,nrhs)

		x1 = blockBiCGSTB(A,rhs,tol=1e-6)
		x2 = blockBiCGSTB(Af,rhs,tol=1e-6)
		x3 = blockBiCGSTB(Af,rhs,tol=1e-6,maxIter=200,x=randn(size(rhs))+im*randn(size(rhs)))
		x4 = blockBiCGSTB(Af,rhs,tol=1e-6,maxIter=200,M1=M)

		@test norm(A*x1[1]-rhs)/norm(rhs) < 1e-6
		@test norm(A*x2[1]-rhs)/norm(rhs) < 1e-6
		@test norm(A*x3[1]-rhs)/norm(rhs) < 1e-6
		@test norm(A*x4[1]-rhs)/norm(rhs) < 1e-6
		@test norm(x2[1]-x1[1])/norm(x1[1]) < 1e-12
		@test norm(x3[1]-x1[1])/norm(x1[1]) < 1e-5
	end
end


# # Iterators

# `for` loops "lower" to `while` loops plus calls to the `iterate` function:

# ```
# for i in iter
#     # body
# end
# ```

# is internally converted to:

# ```
# next = iterate(iter)
# while next !== nothing
#     (i, state) = next
#     # body
#     next = iterate(iter, state)
# end
# ```

# The same applies to comprehensions and generators.

# Note `nothing` is a singleton value (the only value of its type `Nothing`)
# used by convention when there is no value to return.

typeof(print("hello"))

A = ['a','b','c'];

iterate(A)
iterate(A, 2)
iterate(A, 3)
iterate(A, 4)

# Iteration is also used by "destructuring" assignment:

x, y = A
x
y

# Yet another user of this "iteration protocol" is argument "splatting":

string(A)
string('a','b','c')
string(A...)

# ## Iteration utilities

# `collect` gives you elements of an iterator as an array.
# Comprehensions work by calling `collect` on a generator.

collect(pairs(A))
summary(collect(pairs(A)))
collect(zip(100:102,A))

# There are several other highly popular iteration utilities in the
# built-in module `Iterators`:
# - `enumerate`
# - `rest`
# - `take`
# - `drop`
# - `product`
# - `flatten`
# - `partition`

#-

# Some iterators are infinite!
# - `countfrom`
# - `repeated`
# - `cycle`

I = zip(Iterators.cycle(0:1), Iterators.flatten([[2,3],[4,5]]))

collect(I)

collect(Iterators.product(I,A))

string(I...)

# ## Generator syntax

# Generators provide convenient syntax for composing map, filter, product, and
# flatten.

collect( (2i,j,k) for i = 1:3, j = 1:3 for k = 1:j if i==k)

change(x) = ( (p,n÷5,d÷10,q÷25) for q = 0:25:x for d = 0:10:x-q for n = 0:5:x-q-d for p = x-q-d-n )

collect(change(200))

# ## Defining iterators
#
# Defining iterable types is not necessary that often, since you can often
# get what you need by composing existing iterators.

struct SimpleRange
    lo::Int
    hi::Int
end

Base.iterate(r::SimpleRange, state = r.lo) = state > r.hi ? nothing : (state, state+1)
Base.length(r::SimpleRange) = r.hi - r.lo + 1

collect(SimpleRange(2,8))

# ## Iterator traits
#
# For many algorithms, it's useful to know certain properties of an iterator
# up front.
# The most useful is whether it has a known length.

Base.IteratorSize([1])
Base.IteratorSize(Iterators.repeated(1))
Base.IteratorSize(eachline(open("/dev/null")))

# ## Exercise
#
# Define an iterator giving the first N fibonacci numbers.