using Plots, Measures, HTTP, Serialization, Random

#These four convenience functions extract the state variable from the state vector
#It is assumed the layout of the vector u is u = [v_x, v_y, x, y]
state_v_x(u::Vector{Float64}) = u[1]
state_v_y(u::Vector{Float64}) = u[2]
state_x(u::Vector{Float64}) = u[3]
state_y(u::Vector{Float64}) = u[4]

"""
Modified version of the given function. Takes a Vector of tuples of (Float, Vector), first element being h and the second being the values corresponding to that h
"""
function plot_solutions( t::Int64,
    u::Vector{Tuple{Float64,Vector{Vector{Float64}}}};
    title::String = "",
    label::Union{String, Bool} = false,
    show_energy = true,
    combine_positions = false) where T

    position_plots = [] # Storing plots
    energy_values = [] # Storing values to plot later

    # Iterates through given sets of values, does mostly the same thing as the given function
    for (h, u_vec) in u
        x, y, v_x, v_y = state_x.(u_vec), state_y.(u_vec), state_v_x.(u_vec), state_v_y.(u_vec)

        r = @. sqrt(x^2 + y^2)
        E = @. 0.5*(v_x^2 + v_y^2) - 1.0/r

        # Pushes to plot list instead of plotting outright
        push!(position_plots, plot(  reduce_data_points([x], [y])..., label = label, xlabel= "X", ylabel = "Y",
        title = title*" (position): h = $h", aspectratio=1,legend=:topleft,ylim=(-7,7)))
        scatter!([0], [0], ms=15, msw=0, c=:orange, shape =:star, label="Sun")
        scatter!([x[1]], [y[1]], ms=4, msw=0, c=:blue, shape =:circle, label="Earth initial position")

        # Push values without plotting
        push!(energy_values, (h, E))
    end

    # Plot all energy values all at once
    x_values_full, y_values_full = map(x->1:x[1]:t, energy_values), map(x->x[2], energy_values)
    p4 = plot(  reduce_data_points(x_values_full, y_values_full)..., xlabel = "Time", ylabel = "Energy",
    label = transpose(map(x->x[1], energy_values)), title = title*" (energy)")

    # Exclude the energy plot if not needed
    plots_to_plot = position_plots
    if show_energy
        push!(plots_to_plot, p4)
    end

    # Collate all plots into the apex plot
    return plot(plots_to_plot..., margin = 10mm,size=(1000,1000))
end;

"""
"Simple" function for reducing the amount of data points in a graph. Both args must be vectors of vectors.
"""
function reduce_data_points(x_values_full, y_values_full)
    x_values_truncated, y_values_truncated = [], []
    for i in eachindex(x_values_full)
        len = length(x_values_full[i])
        randsub = randsubseq(1:len, min(1000, len)/len)
        push!(x_values_truncated, x_values_full[i][randsub])
        push!(y_values_truncated, y_values_full[i][randsub])
    end
    return x_values_truncated, y_values_truncated
end

# Using the same starting values as given
h_list = map(x->10^(-x), 2.0:4.0)
t_max = 200
u_0 = [0., 1, 1.5, 0]

"""
Computes the RHS for the one body problem (the t arg has been removed)
"""
function df_dt_one_body(u::Vector{Float64})::Vector{Float64}
    M, G = 1, 1
    r = sqrt(state_x(u)^2 + state_y(u)^2)
    return [-M*G*state_x(u)/r^3, -M*G*state_y(u)/r^3, state_v_x(u), state_v_y(u)]
end;

"""
Performs euler method. f is a function which takes a single arg of same type as u_0
"""
function euler_method(f, u_0, step_size, t_vec)
    values = [u_0]
    previous = u_0
    for i in 2:length(t_vec)
        next = previous .+ step_size * f(previous)
        previous = next
        push!(values, next)
    end
    return values
end

"""
Performs RK4 method. f takes a single argument of same type as u_0
"""
function rk4_method(f, u_0, step_size, t_vec)
    values = [u_0]
    prev_value = u_0
    # Could go at it recursively, but I also don't want to make my code even more unreadable
    for i in 2:length(t_vec)
        next_value = prev_value .+ rk4_calculate_section(f, prev_value, step_size)
        push!(values, next_value)
        prev_value = next_value
    end
    return values
end

"""
Helper function for the cumbersome calculation. Args must be vectors.
"""
function rk4_calculate_section(f, current_elem, step_size)
    k_1 = f(current_elem)
    k_2 = f(current_elem .+ ((step_size/2) .* k_1))
    k_3 = f(current_elem .+ ((step_size/2) .* k_2))
    k_4 = f(current_elem .+ (step_size .* k_3))
    return (step_size/6) .* (k_1 .+ (2 .* k_2) .+ (2 .* k_3) .+ k_4)
end

# The part where the graphs get drawn

display(plot_solutions(t_max, map(h -> (h, euler_method(df_dt_one_body, u_0, h, 1:h:t_max)), h_list), title="Euler's Method"))

h_best, h_worst = 0.01, 0.01 # for RK4 and Euler respectively

anim = @animate for i = 30:max(t_max÷50, 1):t_max
    plot_solutions(i, [(h_best, rk4_method(df_dt_one_body, u_0, h_best, 1:h_best:i)), (h_worst, euler_method(df_dt_one_body, u_0, h_worst, 1:h_worst:i))], title="Combined animation", show_energy = false)
end

gif(anim, "tutorial_anim_fps30.gif", fps = 30)

h_list_rk4 = [0.01, 0.1, 0.5]
display(plot_solutions(t_max, map(h -> (h, rk4_method(df_dt_one_body, u_0, h, 1:h:t_max)), h_list_rk4), title="RK4 Method"))


"""
    LogNormal(μ,σ)

The *log normal distribution* is the distribution of the exponential of a [`Normal`](@ref) variate: if ``X \\sim \\operatorname{Normal}(\\mu, \\sigma)`` then
``\\exp(X) \\sim \\operatorname{LogNormal}(\\mu,\\sigma)``. The probability density function is

```math
f(x; \\mu, \\sigma) = \\frac{1}{x \\sqrt{2 \\pi \\sigma^2}}
\\exp \\left( - \\frac{(\\log(x) - \\mu)^2}{2 \\sigma^2} \\right),
\\quad x > 0
```

```julia
LogNormal()          # Log-normal distribution with zero log-mean and unit scale
LogNormal(mu)        # Log-normal distribution with log-mean mu and unit scale
LogNormal(mu, sig)   # Log-normal distribution with log-mean mu and scale sig

params(d)            # Get the parameters, i.e. (mu, sig)
meanlogx(d)          # Get the mean of log(X), i.e. mu
varlogx(d)           # Get the variance of log(X), i.e. sig^2
stdlogx(d)           # Get the standard deviation of log(X), i.e. sig
```

External links

* [Log normal distribution on Wikipedia](http://en.wikipedia.org/wiki/Log-normal_distribution)

"""
struct LogNormal{T<:Real} <: ContinuousUnivariateDistribution
    μ::T
    σ::T
    LogNormal{T}(μ::T, σ::T) where {T} = new{T}(μ, σ)
end

function LogNormal(μ::T, σ::T; check_args=true) where {T <: Real}
    check_args && @check_args(LogNormal, σ ≥ zero(σ))
    return LogNormal{T}(μ, σ)
end

LogNormal(μ::Real, σ::Real) = LogNormal(promote(μ, σ)...)
LogNormal(μ::Integer, σ::Integer) = LogNormal(float(μ), float(σ))
LogNormal(μ::T) where {T <: Real} = LogNormal(μ, one(T))
LogNormal() = LogNormal(0.0, 1.0, check_args=false)

@distr_support LogNormal 0.0 Inf

#### Conversions
convert(::Type{LogNormal{T}}, μ::S, σ::S) where {T <: Real, S <: Real} = LogNormal(T(μ), T(σ))
convert(::Type{LogNormal{T}}, d::LogNormal{S}) where {T <: Real, S <: Real} = LogNormal(T(d.μ), T(d.σ), check_args=false)

#### Parameters

params(d::LogNormal) = (d.μ, d.σ)
partype(::LogNormal{T}) where {T} = T

#### Statistics

meanlogx(d::LogNormal) = d.μ
varlogx(d::LogNormal) = abs2(d.σ)
stdlogx(d::LogNormal) = d.σ

mean(d::LogNormal) = ((μ, σ) = params(d); exp(μ + σ^2/2))
median(d::LogNormal) = exp(d.μ)
mode(d::LogNormal) = ((μ, σ) = params(d); exp(μ - σ^2))

function var(d::LogNormal)
    (μ, σ) = params(d)
    σ2 = σ^2
    (exp(σ2) - 1) * exp(2μ + σ2)
end

function skewness(d::LogNormal)
    σ2 = varlogx(d)
    e = exp(σ2)
    (e + 2) * sqrt(e - 1)
end

function kurtosis(d::LogNormal)
    σ2 = varlogx(d)
    e = exp(σ2)
    e2 = e * e
    e3 = e2 * e
    e4 = e3 * e
    e4 + 2*e3 + 3*e2 - 6
end

function entropy(d::LogNormal)
    (μ, σ) = params(d)
    (1 + log(twoπ * σ^2))/2 + μ
end

#### Evalution

function pdf(d::LogNormal, x::Real)
    if x ≤ zero(x)
        logx = log(zero(x))
        x = one(x)
    else
        logx = log(x)
    end
    return pdf(Normal(d.μ, d.σ), logx) / x
end

function logpdf(d::LogNormal, x::Real)
    if x ≤ zero(x)
        logx = log(zero(x))
        b = zero(logx)
    else
        logx = log(x)
        b = logx
    end
    return logpdf(Normal(d.μ, d.σ), logx) - b
end

function cdf(d::LogNormal, x::Real)
    logx = x ≤ zero(x) ? log(zero(x)) : log(x)
    return cdf(Normal(d.μ, d.σ), logx)
end

function ccdf(d::LogNormal, x::Real)
    logx = x ≤ zero(x) ? log(zero(x)) : log(x)
    return ccdf(Normal(d.μ, d.σ), logx)
    end

function logcdf(d::LogNormal, x::Real)
    logx = x ≤ zero(x) ? log(zero(x)) : log(x)
    return logcdf(Normal(d.μ, d.σ), logx)
end

function logccdf(d::LogNormal, x::Real)
    logx = x ≤ zero(x) ? log(zero(x)) : log(x)
    return logccdf(Normal(d.μ, d.σ), logx)
end

quantile(d::LogNormal, q::Real) = exp(quantile(Normal(params(d)...), q))
cquantile(d::LogNormal, q::Real) = exp(cquantile(Normal(params(d)...), q))
invlogcdf(d::LogNormal, lq::Real) = exp(invlogcdf(Normal(params(d)...), lq))
invlogccdf(d::LogNormal, lq::Real) = exp(invlogccdf(Normal(params(d)...), lq))

function gradlogpdf(d::LogNormal, x::Real)
    outofsupport = x ≤ zero(x)
    y = outofsupport ? one(x) : x
    z = (gradlogpdf(Normal(d.μ, d.σ), log(y)) - 1) / y
    return outofsupport ? zero(z) : z
end

# mgf(d::LogNormal)
# cf(d::LogNormal)

#### Sampling

rand(rng::AbstractRNG, d::LogNormal) = exp(randn(rng) * d.σ + d.μ)

## Fitting

function fit_mle(::Type{<:LogNormal}, x::AbstractArray{T}) where T<:Real
    lx = log.(x)
    μ, σ = mean_and_std(lx)
    LogNormal(μ, σ)
end

function StatsBase.fit(::Type{LogNormal}, m::AbstractMoments)
    # https://en.wikipedia.org/wiki/Log-normal_distribution
    n_moments(m) >= 2 || error("Need mean and variance to estimate lognormal")
    γ = 1+var(m)/mean(m)^2
    μ = log(mean(m)/sqrt(γ))
    σ = sqrt(log(γ))
    return LogNormal(μ,σ)
end

function StatsBase.fit(::Type{LogNormal}, lower::QuantilePoint, upper::QuantilePoint)
    #length(qset) == 2 || error("only implemented yet for exactly two quantiles.")
    #qset_log = [QuantilePoint(qp, q = log(qp.q)) for qp in qset]
    lower_log = QuantilePoint(lower, q = log(lower.q))
    upper_log = QuantilePoint(upper, q = log(upper.q))
    DN = fit(Normal, lower_log, upper_log)
    LogNormal(params(DN)...)
end

function fit_mean_quantile(::Type{LogNormal}, mean::Real, qp::QuantilePoint)
    # solution of
    # (1) mean = exp(mu + sigma^2/2)
    # (2) upper = mu + sigmaFac sigma
    # see R packaage lognorm inst/doc/coefLognorm.Rmd for derivation
    sigmaFac = quantile(Normal(),qp.p)
    m = log(mean)
    discr = sigmaFac^2 - 2*(log(qp.q) - m)
    (discr > 0) || error("Cannot fit LogNormal with quantile $(qp) and mean $(mean).")
    sigma = sigmaFac > sqrt(discr) ? (sigmaFac - sqrt(discr)) : sigmaFac + sqrt(discr)
    mu = m - sigma^2/2
    LogNormal(mu, sigma)
end

function fit_mode_quantile(::Type{LogNormal}, mode::Real, qp::QuantilePoint)
    # solution of
    # (1) mle = exp(mu - sigma^2)
    # (2) upper = mu + sigmaFac sigma
    # see R packaage lognorm inst/doc/coefLognorm.Rmd for derivation
    sigmaFac = quantile(Normal(),qp.p)
    m = log(mode)
    discr = sigmaFac^2/4 + (log(qp.q) - m)
    (discr > 0) || error("Cannot fit LogNormal with quantile $(qp) and mode $(mode).")
    root_discr = sqrt(discr)
    mfh = -sigmaFac/2
    sigma = mfh > root_discr ? (mfh - root_discr) : (mfh + root_discr)
    #sigma = mfh + root_discr
    mu = m + sigma^2
    LogNormal(mu, sigma)
end


using AstroBase
import EarthOrientation
using ERFA
using Test

EarthOrientation.update()

@testset "AstroBase" begin
    include("util.jl")
    include("earth_attitude.jl")
    include("bodies.jl")
    include("ephemerides.jl")
    include("two_body.jl")
    # include("n_body.jl")
    include("frames.jl")
    include("coords.jl")
end


to_int(s) = parse(Int, s)

str2tuple(s) = (x->x+1).(to_int.(split(s, ',')))

parse_line(line) = str2tuple.(split(line, " -> "))

function line_to_points((x1, y1), (x2, y2))
    display([x1,y1,x2,y2])
    if x1 == x2
        ys = min(y1, y2):max(y1, y2)
        ys, x1
    elseif y1 == y2
        xs = min(x1, x2):max(x1, x2)
        y1, xs
    end
end

line_to_points(parse_line("0,9 -> 5,9")...)

function solve1(filename)
    board = zeros(1000, 1000)
    for line in readlines(filename)
        points = line_to_points(parse_line(line)...)
        display(points)
        if !isnothing(points)
            board[points...] .+= 1
        end
    end
    length(filter(>=(2), board))
end

solve1("5.txt")


# extending linear operations for Gaussian types
function Base.:*(a::Real, G::Union{Univariate, Multivariate})
    return Gaussian(a*mean(G), a^2*cov(G))
end
function Base.:*(A::AbstractMatOrFacOrUni, G::Multivariate)
    if !(size(A, 2) == length(G) || size(A, 2) == 1)
        throw(DimensionMismatch("size of G = $(length(G)) inconsistent with A = $(size(A))"))
    end
    μ = A*mean(G)
    Σ = *(A, cov(G), A')
    return Gaussian(μ, Σ)
end
# vector * scalar distribution gives rise to rank-one covariance
Base.:*(a::AbstractVector, G::Uni) = Gaussian(a*mean(G), LowRank(a*std(G)))

Base.:/(G::Union{Univariate, Multivariate}, a::Real) = (1/a)*G
Base.:\(a::Real, G::Union{Univariate, Multivariate}) = (1/a)*G
function Base.:\(A::AbstractMatOrFacOrUni, G::Multivariate)
    F = factorize(A)
    μ = F \ mean(G)
    Σ = (A\(A\cov(G))')'
    Gaussian(μ, Σ)
end
Base.:+(G::Univariate, b::Real) = Gaussian(mean(G) + b, cov(G))
Base.:-(G::Univariate, b::Real) = Gaussian(mean(G)-b, cov(G))

Base.:+(G::Multivariate, b::AbstractVector) = Gaussian(mean(G) + b, cov(G))
Base.:-(G::Multivariate, b::AbstractVector) = Gaussian(mean(G)-b, cov(G))

Base.:-(G::Union{Univariate, Multivariate}) = Gaussian(-mean(G), cov(G))

function Base.sum(G::Multivariate)
    μ = sum(mean(G))
    Σ = sum(cov(G))
    Gaussian(μ, Σ)
end
function LinearAlgebra.dot(G::Multivariate, x::AbstractVector)
    μ = dot(mean(G), x)
    Σ = dot(x, cov(G), x)
    Gaussian(μ, Σ)
end
LinearAlgebra.dot(x::AbstractVector, G::Multivariate) = dot(G, x)
function Base.diff(G::Multivariate)
    μ = diff(mean(G))
    Σ = diff(diff(cov(G), dims = 1), dims = 2)
    Gaussian(μ, Σ)
end

################################# Process ######################################
# assumes f is a deterministic function
# IDEA: could specialize mean to the closures μ to speed up batch computation
function Base.:*(f, G::Gaussian)
    μ(x) = f(x) * mean(G)(x)
    Σ(x, y) = f(x) * cov(G)(x, y) * f(y)
    Gaussian(μ, Σ)
end
function Base.:+(G::Gaussian, f)
    μ(x) = mean(G)(x) + f(x)
    Σ(x, y) = cov(G)(x, y)
    Gaussian(μ, Σ)
end
Base.:*(a::Real, G::Gaussian) = (x->a) * G
# Base.:*(G::Gaussian, a::Real) = a * G

Base.:/(G::Gaussian, a::Real) = (x->inv(a)) * G

Base.:+(G::Gaussian, b::Real) = G + (x->b)
Base.:+(b::Real, G::Gaussian) = G + b

Base.:-(G::Gaussian) = (x -> -1) * G
Base.:-(G::Gaussian, b::Real) = G + (x -> -b)
# Base.:-(b::Real, G::Gaussian) = b + -G

# other argument positioning
Base.:*(G::Gaussian, a) = a * G
Base.:+(a, G::Gaussian) = G + a
Base.:\(a, G::Gaussian) = G/a
Base.:-(b, G::Gaussian) = b + -G

######################## input transformations #################################
# IDEA: output_transformation defaulting to laplace approximation
function input_transformation(G::Gaussian, f)
    μ(x) = G.μ(f(x))
    Σ(x, y) = G.Σ(f(x), f(y))
    Gaussian(μ, Σ)
end
const warp = input_transformation # from warped Gaussian processes
Base.:∘(G::Gaussian, f) = warp(G, f)

# a.k.a. translate
shift(G::Gaussian, b::Real) = G ∘ (x->x+b)
scale(G::Gaussian, a::Real) = G ∘ (x->a*x)

################## integral and differential functionals #######################
function integral end
function gradient end
function value_gradient end
# TODO:
function hessian end
function value_gradient_hessian end

# TODO: get rid of dimensionality d
# TODO: add noise variance for better conditioning
function conditional(G::Gaussian, x::VecOfVec, ::typeof(gradient),
                    y::AbstractVector, d::Int; tol = tol)
    y = _vec(y)
    H = gradient(G, d) # make gradient process for observations
    Kxx = factorize(gramian(H.Σ, x)) # add a noise variance?
    # Kxx = LazyMatrixSum(σ²*I(d*n), factorize(gramian(H.Σ, x))) # add a noise variance?
    kxu(x, u) = reshape(ForwardDiff.gradient(z->G.Σ(x, z), u), 1, :)
    kuy(u, y) = reshape(ForwardDiff.gradient(z->G.Σ(z, y), u), :, 1)
    μ = ConditionalMean(G.μ, kxu, H.μ, x, y, Kxx)
    Σ = ConditionalKernel(G.Σ, kxu, kuy, x, Kxx)
    return Gaussian(μ, Σ)
end

function conditional(G::Gaussian, x::VecOfVec, ::typeof(value_gradient),
                     y::AbstractVector, d::Int; tol = tol)
    y = _vec(y)
    H = value_gradient(G, d) # make gradient process for observations
    Kxx = factorize(gramian(H.Σ, x))
    kxu(x, u) = reshape(value_gradient(z->G.Σ(x, z), u), 1, :)
    kuy(u, y) = reshape(value_gradient(z->G.Σ(z, y), u), :, 1)
    μ = ConditionalMean(G.μ, kxu, H.μ, x, y, Kxx)
    Σ = ConditionalKernel(G.Σ, kxu, kuy, x, Kxx)
    return Gaussian(μ, Σ)
end

# general design question: should these functions return joint distribution
# or only marginal distribution of functional?
# could be solved with "multi" or "joint" argument
# integral of 1d GP
# should we instead extend sum?
function integral(G::Gaussian, a::Real, b::Real, n::Int = 128)
    x, w = range(a, b, length = n), Fill(1/n, n) # quadrature nodes and weights
    Gx = marginal(G, x)
    dot(w, Gx)
end
const ∫ = integral

function gradient(G::Gaussian, d::Int)
    μ(x::AbstractVector) = ForwardDiff.gradient(mean(G), x)
    Σ = CovarianceFunctions.GradientKernel(cov(G))
    VectorGaussian(μ, Σ, d)
end
const ∇ = gradient

# computes joint distribution of function and its gradient
# d is the dimensionality of the input space of the function
function value_gradient(G::Gaussian, d::Int)
    μ(x::AbstractVector) = value_gradient(mean(G), x)
    Σ = CovarianceFunctions.ValueGradientKernel(cov(G))
    VectorGaussian(μ, Σ, d+1)
end

function hessian(G::Gaussian, d::Int)
    μ(x::AbstractVector) = vec(ForwardDiff.hessian(mean(G), x))
    Σ = CovarianceFunctions.HessianKernel(cov(G))
    VectorGaussian(μ, Σ, d^2)
end

function value_gradient_hessian(G::Gaussian, d::Int)
    μ(x::AbstractVector) = value_gradient_hessian(mean(G), x)
    Σ = CovarianceFunctions.ValueGradientHessianKernel(cov(G))
    VectorGaussian(μ, Σ, d^2 + d + 1)
end

function value_derivative(G::Gaussian)
    μ(x::Real) = value_derivative(mean(G), x)
    Σ = CovarianceFunctions.ValueDerivativeKernel(cov(G))
    VectorGaussian(μ, Σ, 2)
end
function derivative(G::Gaussian)
    μ(x::Real) = ForwardDiff.derivative(mean(G), x)
    Σ = CovarianceFunctions.DerivativeKernel(cov(G))
    Gaussian(μ, Σ)
end


using NLopt

include(joinpath(Pkg.dir("MathProgBase"),"test","nlp.jl"))
nlptest(NLoptSolver(algorithm=:LD_SLSQP))
# test derivative-free
rosenbrocktest(NLoptSolver(algorithm=:LN_PRAXIS))


#=
Author: John Tinnerholm
Grammar:
<exp>    ::= <term><exp'>
<exp'>   ::= +<term><exp'> | - <term><exp'> | ϵ
<term>   ::= <factor><term'>
<term'>  ::= *<factor><term'> | / <factor><term'> | ϵ
<factor> ::= (<exp>) |<ident>|<num>
<ident>  ::= A|B|...|Z
<num>    ::  Real, let's assume all Float64 numbers
=#

#=
<exp> -> <exp> + <term>
|        <exp> - <term>
<term> -> <term> * <factor>
        <term> - <factor>

=#

#=
   Recursive descent parser.
   We do LL(1). That is left-to-right leftmost derivation
=#

#=
 Let's construct one method for each non terminal.
 We also have a method scanm that updates our current token.
 The current token represent our look-ahead of 1.

 The parser use K allows a recursive descent parser to decide which production
 to use by examining only the next k tokens of input

=#

global current_token = ""

function scan()
  res = read(stdin, Char)
  global current_token = string(res)
  return string(res)
end

function main()
  while true
    print("Enter expression  => ")
    scan()
    res = exp()
    println("Result of res is: $res")
  end
end

function exp()
  res = term()
  res = expPrime(res)
  return res
end

function term()
  res = factor()
  res = termPrime(res)
  return res
end

function expPrime(res)
  if current_token == "+"
    scan()
    res = res + term()
    return expPrime(res)
  elseif current_token == "-"
    scan()
    res = res - term()
    return expPrime(res)
  else #= ϵ =#
    return res
  end
end

function termPrime(res)
  if current_token == "*"
    scan()
    res = res * factor()
    return termPrime(res)
  elseif current_token == "/"
    scan()
    res = res / factor()
    return termPrime(res)
  else #= ϵ =#
    return res
  end
end

function factor()
  if current_token == "("
    scan()
    res = exp()
    if current_token != ")"
      throw("Right parenthesis expected")
    else
      scan()
      return res
    end
  elseif nothing != match(r"[A-Z]+", current_token)
    throw("TODO: Not implemented, do as an exercise at home:)")
  elseif nothing != match(r"[1-9]+", current_token)
    res = parse(Float64, current_token)
    scan()
    return res
  else #= Faulty input =#
    throw("Unexpected token: $(current_token)")
  end
end

main()


using DelimitedFiles

puzzleinput = readdlm("inputs/day15.txt", ',', Int)

function play(startingnums, numofturns)

    spoken = fill(-1, numofturns)

    for i in eachindex(startingnums[1:end - 1])
        spoken[startingnums[i] + 1] = i
    end
    # Preparation of the game: every elf says his number that is the puzzleinput

    lstspkn = last(startingnums)
    turn = length(startingnums)
    stop = numofturns - 1
    while true
        # Game loop

        newnumber = spoken[lstspkn + 1] > 0 ? turn - spoken[lstspkn + 1] : 0
        # Had the last number spoken also been spoken before?

        # YES: then the next number to speak is the difference between
        # the turn number when it was last spoken (actual turn - 1) and
        # the turn number the time it was most recently spoken before then

        # NO: is a new numbers then the elf says "0"

        spoken[lstspkn + 1] = turn
        lstspkn = newnumber
        # Now the elf says the number

        if turn < stop
            turn += 1
        else
            return lstspkn
        end
    end

end

# First Half

@time play(puzzleinput, 2020)

# Second Half

@time play(puzzleinput, 30000000)


using BenchmarkTools, SparseArrays, LinearAlgebra
import ExpmV, Expokit

#BenchmarkTools.DEFAULT_PARAMETERS.seconds = 120

const p = 1e-3
SUITE = BenchmarkGroup()

SUITE["Expm"] = BenchmarkGroup()
SUITE["ExpmV"] = BenchmarkGroup()
SUITE["Expokit"] = BenchmarkGroup()

dimensions = [d for d in 2 .^ (5:10)]

for d in dimensions
    #SUITE[d] = BenchmarkGroup()
    SUITE["Expm"][d] = @benchmarkable exp(full_r)*rv setup=((full_r,rv) = (Matrix(randn()*sprandn($d,$d,p/2)+1im*sprandn($d,$d,p/2)), normalize(randn($d)+1im*randn($d))))
    SUITE["ExpmV"][d] = @benchmarkable ExpmV.expmv(rt,r,rv) setup=((rt,r,rv)=(rand(), sprandn($d,$d,p/2)+1im*sprandn($d,$d,p/2), normalize(randn($d)+1im*randn($d))))
    SUITE["Expokit"][d] = @benchmarkable Expokit.expmv(rt,r,rv) setup=((rt,r,rv)=(rand(), sprandn($d,$d,p/2)+1im*sprandn($d,$d,p/2), normalize(randn($d)+1im*randn($d))))
end

results = run(SUITE, verbose = true)


################################################################################
## Linear Regression
##   y ~ MvNormal(X * (beta0 .* gamma), 1)
##   gamma ~ DiscreteUniform(0, 1)
################################################################################

using MCPhylo

## Data
n, p = 25, 10
X = randn(n, p)
beta0 = randn(p)
gamma0 = rand(0:1, p)
y = X * (beta0 .* gamma0) + randn(n)

## Log-transformed Posterior(gamma) + Constant
logf = function(gamma::DenseVector)
  logpdf(MvNormal(X * (beta0 .* gamma), 1.0), y)
end

## MCMC Simulation with Binary MCMC Model Composition
t = 10000
sim1 = Chains(t, p, names = map(i -> "gamma[$i]", 1:p))
sim2 = Chains(t, p, names = map(i -> "gamma[$i]", 1:p))
gamma1 = BMC3Variate(zeros(p), logf)
gamma2 = BMC3Variate(zeros(p), logf, k=Vector{Int}[[i] for i in 1:p])
for i in 1:t
  sample!(gamma1)
  sample!(gamma2)
  sim1[i, :, 1] = gamma1
  sim2[i, :, 1] = gamma2
end
describe(sim1)
describe(sim2)

#mark that we got to the end of the test file succesfully
@test true


@testset "Nested" begin
  # nested variogram with nugget effect
  γ = NuggetEffect(0.2) + GaussianVariogram(nugget=0.1, sill=0.8, range=50.0)
  @test nugget(γ) ≈ 0.3
  @test sill(γ) ≈ 1.0
  @test range(γ) ≈ 50.0
  @test (@elapsed sill(γ)) < 1e-6
  @test (@elapsed nugget(γ)) < 1e-6
  @test (@allocated sill(γ)) < 32
  @test (@allocated nugget(γ)) < 32
  γ = 2.0*NuggetEffect(0.2)
  @test nugget(γ) ≈ 0.4
  @test sill(γ) ≈ 0.4
  @test range(γ) ≈ 0.0
  @test (@elapsed sill(γ)) < 1e-5
  @test (@elapsed nugget(γ)) < 1e-5
  @test (@allocated sill(γ)) < 32
  @test (@allocated nugget(γ)) < 32

  # sill is defined for nested models
  γ = GaussianVariogram(sill=1.) + ExponentialVariogram(sill=2.)
  @test sill(γ) == 3.0
  @test (@elapsed sill(γ)) < 1e-5
  @test (@elapsed nugget(γ)) < 1e-5
  @test (@allocated sill(γ)) < 32
  @test (@allocated nugget(γ)) < 32

  # nugget is defined for nested models
  γ₁ = GaussianVariogram()
  γ₂ = GaussianVariogram() + ExponentialVariogram()
  @test nugget(γ₁) == nugget(γ₂)

  # stationarity of nested models
  γ = GaussianVariogram() + ExponentialVariogram() + SphericalVariogram()
  @test isstationary(γ)
  @test sill(γ) == 3.0
  @test !isstationary(γ + PowerVariogram())
  @test (@elapsed sill(γ)) < 1e-5
  @test (@elapsed nugget(γ)) < 1e-5
  @test (@allocated sill(γ)) < 32
  @test (@allocated nugget(γ)) < 32

  # result type is defined for nested models
  # see https://github.com/JuliaEarth/GeoStats.jl/issues/121
  γ = GaussianVariogram() + ExponentialVariogram()
  @test Variography.result_type(γ, rand(Point3), rand(Point3)) == Float64
  γ = GaussianVariogram(sill=1.0f0,range=1.0f0,nugget=0.1f0)
  @test Variography.result_type(γ, rand(Point3f), rand(Point3f)) == Float32

  # nested model with matrix coefficients
  C₁ = [1.0 0.5; 0.5 2.0]
  C₂ = [3.0 0.0; 0.0 3.0]
  γ = C₁ * GaussianVariogram(range=1.0) + C₂ * SphericalVariogram(range=2.0)
  @test range(γ) ≈ 2.0
  @test sill(γ)  ≈ C₁ .+ C₂
  @test γ(10.0) ≈ sill(γ)
  @test γ(Point(10.,0.), Point(0.,0.)) ≈ sill(γ)
  @test isstationary(γ)

  # nested model with matrix coefficients
  C = [1. 0.; 0. 1.]
  γ = C*GaussianVariogram() + C*ExponentialVariogram() + C*CubicVariogram()
  @test range(γ) ≈ 1.0
  @test sill(γ) ≈ [3. 0.; 0. 3.]
  @test γ(10.0) ≈ sill(γ)
  @test γ(Point(10.,0.), Point(0.,0.)) ≈ sill(γ)
  @test isstationary(γ)

  # test constructor explicitly
  γ = NestedVariogram((1., 2.), (ExponentialVariogram(), SphericalVariogram()))
  @test sill(γ) == 3.0
  @test range(γ) == 1.0
  @test nugget(γ) == 0.0
  @test (@elapsed sill(γ)) < 1e-5
  @test (@elapsed nugget(γ)) < 1e-5
  @test (@allocated sill(γ)) < 32
  @test (@allocated nugget(γ)) < 32

  # test individual structures
  γ = SphericalVariogram() + 2ExponentialVariogram() + NuggetEffect(10.0)
  @test structures(γ) == (10.0, (1.0, 2.0), (SphericalVariogram(), ExponentialVariogram()))
  @test (@elapsed sill(γ)) < 1e-5
  @test (@elapsed nugget(γ)) < 1e-5
  @test (@allocated sill(γ)) < 32
  @test (@allocated nugget(γ)) < 32
  γ = SphericalVariogram(sill=2.0) + ExponentialVariogram(nugget=0.1)
  @test structures(γ) == (0.1, (2.0, 0.9), (SphericalVariogram(), ExponentialVariogram()))
  @test structures(SphericalVariogram()) == (0.0, (1.0,), (SphericalVariogram(),))
  @test (@elapsed sill(γ)) < 1e-5
  @test (@elapsed nugget(γ)) < 1e-5
  @test (@allocated sill(γ)) < 32
  @test (@allocated nugget(γ)) < 32

  # nested model with change of support
  γ = GaussianVariogram() + SphericalVariogram()
  p = Point(1., 2., 3.)
  h = CartesianGrid(10,10,10)[1]
  @test γ(p, h) == γ(h, p)
  @test γ(h, h) < γ(p, h)
end


########################################################################
# Atoms of the trace-norm ball.
########################################################################
"""
    TraceBallAtom(n, v)

Atom in the TraceBall of dimension n×n.
"""
struct TraceBallAtom{T1<:Int64, T2<:Vector{Float64}} <: AbstractAtom
    n::T1
    v::T2
    function TraceBallAtom(v::Vector{Float64})
        n = length(v)
        new{Int64, Vector{Float64}}(n, v)
    end
end

"""
    *(M::AbstractLinearOp, a::TraceBallAtom) -> Y

Multiply a trace-norm atom by a linear map, and return the matrix `Y`.
"""
Base.:(*)(M::LinearOp, a::TraceBallAtom{Int64, Vector{Float64}}) = M*vec(a.v*a.v')
LinearAlgebra.mul!(Ma::Vector{Float64}, M::LinearOp, a::TraceBallAtom{Int64, Vector{Float64}}) = mul!(Ma, M, vec(a.v*a.v'))
Base.vec(a::TraceBallAtom{Int64, Vector{Float64}}) = vec(a.v*a.v')
Base.length(a::TraceBallAtom{Int64, Vector{Float64}}) = a.n * a.n
Base.size(a::TraceBallAtom{Int64, Vector{Float64}}) = a.n, a.n

"""
    TraceBall(n)

Atomic set defined by trace-norm ball in `n by n` matrices.
"""
struct TraceBall{T<:Int64} <: AbstractAtomicSet
    n::T
    maxrank::T
    function TraceBall(n::Int64, maxrank::Int64)
        n ≥ 1 || throw(DomainError(n,"n must be a positive integer"))
        n ≥ maxrank ≥ 0 || throw(DomainError(maxrank,"maxrank must be ≥ 0"))
        new{Int64}(n, maxrank)
    end
end
TraceBall(n::Int64; maxrank=n) = TraceBall(n, maxrank)

"""
    gauge(A::TraceBall, x::Matrix)

Gives the trace-norm of `x`.
"""
gauge(::TraceBall{Int64}, x::Matrix{Float64}) = tr(x)
gauge(A::TraceBall{Int64}, x::Vector{Float64}) = gauge(A, reshape(x, A.n, A.n))

"""
    support(A::TraceBall, z::AbstractMatrix)

Gives the largest singular value of `z`.
"""
support(::TraceBall{Int64}, z::AbstractMatrix{Float64}) = real(eigs(z, nev = 1, which=:LR)[1][1])
support(A::TraceBall{Int64}, z::AbstractVector{Float64}) = support(A, reshape(z, A.n, A.n))

"""
    expose!(A::TraceBall, z::Matrix, a::TraceBallAtom)

Gives the top eigen vector of `z`.
"""
function expose!(::TraceBall{Int64}, z::AbstractMatrix{Float64}, a::TraceBallAtom{Int64, Vector{Float64}})
    ~, v = eigs(z, nev = 1, which=:LR)
    a.v .= v[:]
    return nothing
end
expose!(A::TraceBall{Int64}, z::AbstractVector{Float64}, a::TraceBallAtom{Int64, Vector{Float64}}) = expose!(A, reshape(z, A.n, A.n), a)

function expose(A::TraceBall{Int64}, z::AbstractMatrix{Float64})
    n = A.n
    a = TraceBallAtom(zeros(n))
    expose!(A, z, a)
    return a
end
expose(A::TraceBall{Int64}, z::AbstractVector{Float64}) = expose(A, reshape(z, A.n, A.n))

Base.length(A::TraceBall{Int64}) = A.n*A.n
Base.size(A::TraceBall{Int64}) = A.n, A.n
rank(A::TraceBall{Int64}) = A.maxrank
atom_name(A::TraceBall{Int64}) = "trace-norm ball"
atom_description(A::TraceBall{Int64}) = "{ vv^T | v ∈ ℝⁿ, ||v||₂ ≤ 1 }"
atom_parameters(A::TraceBall{Int64}) = "n = $(A.n), maxrank = $(A.maxrank)"

########################################################################
# Face of the trace-norm ball.
########################################################################

mutable struct TraceBallFace{T1<:Int64, T2<:Matrix{Float64}} <: AbstractFace
    n::T1
    r::T1
    V::T2
    function TraceBallFace(V::Matrix{Float64})
        n, r = size(V)
        new{Int64, Matrix{Float64}}(n, r, V)
    end
end

"""
    face(A, z) -> F

Return a face `F` of the atomic set `A` exposed by the vector `z`. The dimension of
the exposed face is limited by `k=maxrank(A)`. If the dimension is being limited
by `k`, then the routine returns a set of rank `k` atoms that define the subset
of the exposed face.
"""
function face!(A::TraceBall{Int64}, z::AbstractMatrix{Float64}, F::TraceBallFace{Int64, Matrix{Float64}})
    r = A.maxrank
    if r == 0
        V = zeros(A.n, 1)
    elseif r < min(A.n) - 1
        ~, V = eigs(z, nev=r, which=:LR)
        F.V .= V
    else
        V = eigvecs(z)
        F.V .= V[:, 1:r]
    end
    return nothing
end
face!(A::TraceBall{Int64}, z::AbstractVector{Float64}, F::TraceBallFace{Int64, Matrix{Float64}}) = face!(A, reshape(z, A.n, A.n), F)

function face(A::TraceBall{Int64}, z::AbstractMatrix{Float64})
    n, r = A.n, max(A.maxrank, 1)
    V = zeros(n, r)
    F = TraceBallFace(V)
    face!(A, z, F)
    return F
end
face(A::TraceBall{Int64}, z::AbstractVector{Float64}) = face(A, reshape(z, A.n, A.n))

"""
Given a face and a set of weights, reveal the corresponding
point on the face.
"""
Base.:(*)(F::TraceBallFace{Int64, Matrix{Float64}}, S::Matrix{Float64}) = vec(F.U*S*F.U')
Base.:(*)(F::TraceBallFace{Int64, Matrix{Float64}}, c::Vector{Float64}) = F*reshape(c, F.r, F.r)

"""
    *(M::LinearOp, F::TraceBallFace)

Return a LinearMap `L` whose forward and adjoing operators
are defined as

    L*p = M*vec(V*matrix(p)*V')
    L'*q = vec(V'*matrix(M'*q)*V)

where the `n`-by-`k` matrice `V` is the left
eigen vectors that define the face `F` of the trace-norm ball.
"""
function Base.:(*)(M::LinearOp, F::TraceBallFace{Int64, Matrix{Float64}})
    n, r = F.n, F.r
    f = p->M*vec(F.V*reshape(p, r, r)*F.V')
    fc = q->vec(F.V'*reshape(M'*q, n, n)*F.V)
    return LinearMap(f, fc, size(M, 1), r*r)
end

Base.:(*)(λ::Real, F::TraceBallFace{Int64, Matrix{Float64}}) = λ*vec(F)
Base.length(F::TraceBallFace{Int64, Matrix{Float64}}) = F.n*F.n
Base.size(F::TraceBallFace{Int64, Matrix{Float64}}) = F.n, F.n
rank(F::TraceBallFace{Int64, Matrix{Float64}}) = F.r^2
vec(F::TraceBallFace{Int64, Matrix{Float64}}) = I(F.n*F.n)*F
face_name(F::TraceBallFace{Int64, Matrix{Float64}}) = "face of trace-norm ball"
face_parameters(F::TraceBallFace{Int64, Matrix{Float64}}) = "n = $(F.n), rank = $(rank(F))"


mountaincar = QuickMDP(
    function (s, a, rng)
        x, v = s
        vp = clamp(v + a*0.001 + cos(3*x)*-0.0025, -0.07, 0.07)
        xp = x + vp
        if xp > 0.5
            r = 100.0
        else
            r = -1.0
        end
        return (sp=(xp, vp), r=r)
    end,
    actions = [-1., 0., 1.],
    initialstate = Deterministic((-0.5, 0.0)),
    discount = 0.95,
    isterminal = s -> s[1] > 0.5,

    render = function (step)
        cx = step.s[1]
        cy = 0.45*sin(3*cx)+0.5
        car = (context(), circle(cx, cy+0.035, 0.035), fill("blue"))
        track = (context(), line([(x, 0.45*sin(3*x)+0.5) for x in -1.2:0.01:0.6]), stroke("black"))
        goal = (context(), star(0.5, 1.0, -0.035, 5), fill("gold"), stroke("black"))
        bg = (context(), rectangle(), fill("white"))
        ctx = context(0.7, 0.05, 0.6, 0.9, mirror=Mirror(0, 0, 0.5))
        return compose(context(), (ctx, car, track, goal), bg)
    end
)


"""
$(TYPEDEF)

Two sample equivalence test for mean

Constructors
------------
* `TwoSampleMeanEqual(mu1::Real, mu2::Real, k::Real, delta::Real, stdunknown::Bool)`

Arguments
---------
* `mu1`: Mean of group 1

* `mu2`: Mean of group 2

* `k`: Allocation ratio of the groups, k = n(group1) / n(group 2)

* `delta`: Non-inferiority/Superiority Margin

* `stdunknown`: If the population standard deviation is known, default = false,
when the population parameter is known, the power / sample size are calculated based
on a standard normal distribution, otherwise a t distribution will be used

Fields
------
$(FIELDS)
"""
type TwoSampleMeanEqual <: TrialTest
    mu1::Real
    mu2::Real
    k::Real
    delta::Real
    stdunknown::Bool

    # Validator
    function TwoSampleMeanEqual(mu1, mu2, k, delta, stdunknown = false)

        if !((-Inf < mu1 < Inf) & (-Inf < mu2 < Inf))

            error("Mean value must be in (-Inf, Inf)")

        end # end if

        if !(0 < k < Inf)

            error("Allocation ratio must be in (0, Inf)")

        end # end if

        if !(0 <= delta < Inf)

            error("The superiority margin δ must be in [0 , Inf)")

        end # end if
        new(mu1, mu2, k, delta, stdunknown)

    end # end function

end # end type

# Two-sample equivalence test for mean, equal standard deviaiton
function hypotheses{T <: TwoSampleMeanEqual}(test::T, n::Real, std::Real, alpha::Real, side::String)

    diff = abs(test.mu1 - test.mu2) - test.delta
    se = sqrt(1 / n + 1 / (test.k * n)) * std
    ts = diff / se
    if test.stdunknown == false

        p = 2 * (cdf(ZDIST, ts - quantile(ZDIST, 1 - alpha)) + cdf(ZDIST, -ts - quantile(ZDIST, 1 - alpha))) - 1

    else

        p = 2 * (cdf(TDist(n + test.k * n - 2), ts - quantile(TDist(n + test.k * n - 2), 1 - alpha)) +
            cdf(TDist(n + test.k * n - 2), -ts - quantile(TDist(n + test.k * n - 2), 1 - alpha))) - 1

    end # end if
    # power_result(test, p)
    return p

end # end function

# Two-sample equivalence test for mean, unequal standard deviation
function hypotheses{T <: TwoSampleMeanEqual}(test::T, n::Real, std::Tuple{Real, Real}, alpha::Real, side::String)

    diff = abs(test.mu1 - test.mu2) - test.delta
    se = sqrt(std[1] ^ 2 / (test.k * n) + std[2] ^ 2 / n)
    ts = diff / se
    if test.stdunknown == false

        p = 2 * (cdf(ZDIST, ts - quantile(ZDIST, 1 - alpha)) + cdf(ZDIST, -ts - quantile(ZDIST, 1 - alpha))) - 1

    else

        p = 2 * (cdf(TDist(n + test.k * n - 2), ts - quantile(TDist(n + test.k * n - 2), 1 - alpha)) +
            cdf(TDist(n + test.k * n - 2), -ts - quantile(TDist(n + test.k * n - 2), 1 - alpha))) - 1

    end # end if
    # power_result(test, p)
    return p

end # end function


# utility functions that provide Gaussian odometry accumulation

import IncrementalInference: getFactorMean

export getFactorMean
export accumulateDiscreteLocalFrame!, duplicateToStandardFactorVariable, extractDeltaOdo, resetFactor!
export odomKDE

"""
    $SIGNATURES

Return a number increment on symbol.  Example :x2 -> :x3.
"""
nextPose(sym::Symbol, identifier::Union{String, Char}=string(sym)[1]) = Symbol(string(identifier,parse(Int,string(sym)[2:end])+1))

getFactorMean(fct::PriorPose2) = getFactorMean(fct.Z)
getFactorMean(fct::Pose2Pose2) = getFactorMean(fct.z)
getFactorMean(fct::MutablePose2Pose2Gaussian) = getFactorMean(fct.Zij)

"""
    $SIGNATURES

Advance an odometry factor as though integrating an ODE -- i.e. ``X_2 = X_1 ⊕ ΔX``. Accepts continuous domain process noise density `Qc` which is internally integrated to discrete process noise Qd.  ``DX`` is assumed to already be incrementally integrated before this function.  See related `accumulateContinuousLocalFrame!` for fully continuous system propagation.

Notes
- This update stays in the same reference frame but updates the local vector as though accumulating measurement values over time.
- Kalman filter would have used for noise propagation: ``Pk1 = F*Pk*F' + Qdk``
- From Chirikjian, Vol.II, 2012, p.35: Jacobian SE(2), Jr = [cθ sθ 0; -sθ cθ 0; 0 0 1] -- i.e. dSE2/dX' = SE2([0;0;-θ])
- `DX = dX/dt*Dt`
- assumed process noise for `{}^b Qc = {}^b [x;y;yaw] = [fwd; sideways; rotation.rate]`

Dev Notes
- TODO many operations here can be done in-place.

Related

accumulateContinuousLocalFrame!, accumulateDiscreteReferenceFrame!
"""
function accumulateDiscreteLocalFrame!(mpp::MutablePose2Pose2Gaussian,
                                       DX::Vector{Float64},
                                       Qc::Matrix{Float64},
                                       dt::Float64=1.0;
                                       Fk = SE2([0;0;-DX[3]]),
                                       Gk = Matrix{Float64}(LinearAlgebra.I, 3,3),
                                       Phik = SE2(DX) )
  #
  kXk1 = SE2(mpp.Zij.μ)*Phik
  phi, gamma, Qd = cont2disc(Fk, Gk, Qc, dt, Phik)
  Covk1 = Phik*(mpp.Zij.Σ.mat)*(Phik') + Qd
  check = norm(Covk1 - Covk1')
  1e-4 < check ? @warn("Covk1 is getting dangerously non-Hermitian, still forcing symmetric covariance matrix.") : nothing
  @assert check < 1.0
  Covk1 .+= Covk1'
  Covk1 ./= 2
  mpp.Zij = MvNormal(se2vee(kXk1), Covk1)
  nothing
end

"""
    $SIGNATURES

Helper function to duplicate values from a special factor variable into standard factor and variable.  Returns the name of the new factor.

Notes:
- Developed for accumulating odometry in a `MutablePosePose` and then cloning out a standard PosePose and new variable.
- Does not change the original MutablePosePose source factor or variable in any way.
"""
function duplicateToStandardFactorVariable(::Type{Pose2Pose2},
                                           mpp::MutablePose2Pose2Gaussian,
                                           dfg::AbstractDFG,
                                           prevsym::Symbol,
                                           newsym::Symbol;
                                           solvable::Int=1,
                                           autoinit::Bool=true,
                                           cov::Union{Nothing, Matrix{Float64}}=nothing  )::Symbol
  #
  # extract factor values and create PosePose object
  posepose = Pose2Pose2(MvNormal(mpp.Zij.μ, cov==nothing ? mpp.Zij.Σ.mat : cov))

  # modify the factor graph
  addVariable!(dfg, newsym, Pose2, solvable=solvable)
  addFactor!(dfg, [prevsym; newsym], posepose, solvable=solvable, autoinit=autoinit)
  return ls(dfg, newsym)[1]
end

"""
    $SIGNATURES

Reset the transform value stored in a `::MutablePose2Pose2Gaussian` to zero.
"""
function resetFactor!(mpp::MutablePose2Pose2Gaussian)::Nothing
  mpp.Zij = MvNormal(zeros(3), 1e-6*Matrix{Float64}(LinearAlgebra.I, 3,3) )
  nothing
end

"""
    $SIGNATURES

Extract deltas from existing dead reckoning data so that odometry calculations can be repeated later.

Notes
- Useful for reverse engineering data or simulation tools.
"""
function extractDeltaOdo(XX, YY, TH)
  dt = 1.0
  DX = zeros(3,length(XX))
  nXYT__ = zeros(3,size(DX,2))
  nXYT__[:,1] = [XX[1];YY[1];TH[1]]
  for i in 2:length(XX)
    wTbk = SE2([XX[i-1];YY[i-1];TH[i-1]])
    wTbk1 = SE2([XX[i];YY[i];TH[i]])
    bkTbk1 = wTbk\wTbk1
    DX[:,i] = se2vee(bkTbk1)

    # test
    nXYT__[:,i] .= se2vee(SE2(nXYT__[:,i-1])*SE2(DX[:,i]))
    # nXYT__[:,i] .= se2vee(SE2(nXYT__[:,i-1])*bkTbk1)
  end

  return DX
end

## Previous methods

function odomKDE(p1,dx,cov)
  @warn "odomKDE is beig deprecated in its current form, consider using approxConv or predictVariableByFactor instead."
  X = getPoints(p1)
  sig = diag(cov)
  RES = zeros(size(X))
  # increases the number of particles based on the number of modes in the measurement Z
  for i in 1:size(X,2)
      ent = [randn()*sig[1]; randn()*sig[2]; randn()*sig[3]]
      RES[:,i] = addPose2Pose2(X[:,i], dx + ent)
  end
  return manikde!(RES, Pose2)
end


A = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

function zad43(A,key)
    m = Int(floor(length(A)/2))
    if A[m]<key
        return "Element se nalazi u prvoj polovini"
    elseif A[m] == key
        return "Element se nalazi na sredini niza"
    else
        return "Element se nalazi u drugoj polovini"
    end
end

function zad43A(A,key)
    m = Int(floor(length(A)/2))
    if A[m] == key
        return "Element se nalazi na sredini niza"
    end
    k = length(A)
    i = 1
    while(i<m && k > m)
        if A[i] == key
            return "Element nalazi se u prvoj polovini"
        elseif A[k] == key
            return "Element se nalazi u drugoj polovini"
        end
        i+=1
        k-=1
    end

    return "Element se ne nalazi u nizu"
end


"""
    is_leap_year(year)

Return `true` if `year` is a leap year in the gregorian calendar.

"""
function is_leap_year(year)
    divisible_by(n) = year % n == 0

    return divisible_by(4) && (!divisible_by(100) || divisible_by(400))
end


using DynamicPolynomials
using MultivariatePolynomials
using Test
using LinearAlgebra

# TODO move to MP
@testset "Issue #70" begin
    @ncpolyvar y0 y1 x0 x1
    p = x1 * x0 * x1
    @test subs(p, x0 => y0, x1 => y1) == y1 * y0 * y1
    @test subs(p, x0 => 1) == x1^2
    @test p(x0 => y0, x1 => y1) == y1 * y0 * y1
end

include("mono.jl")
include("poly.jl")
include("comp.jl")
include("mutable_arithmetics.jl")

module newmodule
    using Test
    import DynamicPolynomials
    @testset "Polyvar macro hygiene" begin
        # Verify that the @polyvar macro works when the package has been activated
        # with `import` instead of `using`.
        DynamicPolynomials.@polyvar x y
        @test isa(x, DynamicPolynomials.PolyVar)
        @test isa(y, DynamicPolynomials.PolyVar)
    end
end

include("mvp.jl")


y2 = @. sin(x^2) * exp(-x/10)
plot!(x, y2, col="blue", lwidth=0.05, label="plot 2")

xlabel("x-axis")
ylabel("y-axis")
xticks([-pi/2, 0, pi/2], ["π/2", "0", "π/2"])
ylim(-1.5, 1.5)
yticks(-1:0.25:1)

legend()


function rotate_char(num::Int64, char::Char)
    if char in 'a':'z'
        return 'a' + (char - 'a' + num) % 26
    elseif char in 'A':'Z'
        return 'A' + (char - 'A' + num) % 26
    else
        return char
    end
end

function rotate(num::Int64, text::String)
    map(char->rotate_char(num, char), text)
end

rotate(num::Int64, char::Char)  = rotate_char(num, char)

for i=0:26
    @eval macro $(Symbol('R', i, "_str"))(text::String)
        rotate($i, text)
    end
end


export Model, SyntheticData

"""
        Model

A structure to perform the twin-experiment

## Fields
- `Nx` : Dimension of the state variable
- `Ny` : Dimension of the observation variable
- `Δtdyn` : Time-step for the dynamical model
- `Δtobs` : Time step between two observations of the state
- `ϵx` : Process noise
- `ϵy` : Observation noise
- `π0` : Multivariate distribution for the initial condition
- `Tburn` : Number of steps to burn from the end of the spin up to compute the metrics
- `Tstep` : Number of steps for which the filter to be tested is applied
- `Tspinup` : Number of steps of spin-up phase,
   i.e. number of steps to generate the initial ensemble for the filtering algorithms
- `F` : State-Space Model
"""
struct Model

    "Dimension of the state variable"
    Nx::Int64

	"Dimension of the observation variable"
    Ny::Int64

	"Time-step for the dynamical model"
    Δtdyn::Float64

	"Time step between two observations of the state"
    Δtobs::Float64

	"Process noise"
    ϵx::InflationType

	"Observation noise"
    ϵy::AdditiveInflation

    "Multivariate distribution for the initial condition"
    π0::ContinuousMultivariateDistribution

    "Number of steps to burn from the end of the spin up to compute the metrics"
    Tburn::Int64
    Tstep::Int64
    Tspinup::Int64

    " State-Space Model"
    F::StateSpace
end

"""
    SyntheticData

A structure to store the synthetic data in a twin-experiment

## Fields
- `tt` : time history
- `Δt` : time step
- `x0` : the initial condition
- `xt` : history of the state
- `yt` : history of the observations
"""
struct SyntheticData
	tt::Array{Float64,1}
	Δt::Float64
	x0::Array{Float64,1}
	xt::Array{Float64,2}
	yt::Array{Float64,2}
end


# ------------------------------------------------------------------------------
#
#  Gmsh Julia tutorial 4
#
#  Holes in surfaces, annotations, entity colors
#
# ------------------------------------------------------------------------------
import gmsh

gmsh.initialize()

gmsh.model.add("t4")

cm = 1e-02
e1 = 4.5 * cm; e2 = 6 * cm / 2; e3 =  5 * cm / 2
h1 = 5 * cm; h2 = 10 * cm; h3 = 5 * cm; h4 = 2 * cm; h5 = 4.5 * cm
R1 = 1 * cm; R2 = 1.5 * cm; r = 1 * cm
Lc1 = 0.01
Lc2 = 0.003

function hypot(a, b)
    return sqrt(a * a + b * b)
end

ccos = (-h5*R1 + e2 * hypot(h5, hypot(e2, R1))) / (h5*h5 + e2*e2)
ssin = sqrt(1 - ccos*ccos)

# We start by defining some points and some lines. To make the code shorter we
# can redefine a namespace:
factory = gmsh.model.geo
factory.addPoint(-e1-e2, 0    , 0, Lc1, 1)
factory.addPoint(-e1-e2, h1   , 0, Lc1, 2)
factory.addPoint(-e3-r , h1   , 0, Lc2, 3)
factory.addPoint(-e3-r , h1+r , 0, Lc2, 4)
factory.addPoint(-e3   , h1+r , 0, Lc2, 5)
factory.addPoint(-e3   , h1+h2, 0, Lc1, 6)
factory.addPoint( e3   , h1+h2, 0, Lc1, 7)
factory.addPoint( e3   , h1+r , 0, Lc2, 8)
factory.addPoint( e3+r , h1+r , 0, Lc2, 9)
factory.addPoint( e3+r , h1   , 0, Lc2, 10)
factory.addPoint( e1+e2, h1   , 0, Lc1, 11)
factory.addPoint( e1+e2, 0    , 0, Lc1, 12)
factory.addPoint( e2   , 0    , 0, Lc1, 13)

factory.addPoint( R1 / ssin, h5+R1*ccos, 0, Lc2, 14)
factory.addPoint( 0        , h5        , 0, Lc2, 15)
factory.addPoint(-R1 / ssin, h5+R1*ccos, 0, Lc2, 16)
factory.addPoint(-e2       , 0.0       , 0, Lc1, 17)

factory.addPoint(-R2 , h1+h3   , 0, Lc2, 18)
factory.addPoint(-R2 , h1+h3+h4, 0, Lc2, 19)
factory.addPoint( 0  , h1+h3+h4, 0, Lc2, 20)
factory.addPoint( R2 , h1+h3+h4, 0, Lc2, 21)
factory.addPoint( R2 , h1+h3   , 0, Lc2, 22)
factory.addPoint( 0  , h1+h3   , 0, Lc2, 23)

factory.addPoint( 0, h1+h3+h4+R2, 0, Lc2, 24)
factory.addPoint( 0, h1+h3-R2,    0, Lc2, 25)

factory.addLine(1 , 17, 1)
factory.addLine(17, 16, 2)

# Gmsh provides other curve primitives than straight lines: splines, B-splines,
# circle arcs, ellipse arcs, etc. Here we define a new circle arc, starting at
# point 14 and ending at point 16, with the circle's center being the point 15:
factory.addCircleArc(14,15,16, 3)

# Note that, in Gmsh, circle arcs should always be smaller than Pi. The
# OpenCASCADE geometry kernel does not have this limitation.

# We can then define additional lines and circles, as well as a new surface:
factory.addLine(14,13, 4)
factory.addLine(13,12, 5)
factory.addLine(12,11, 6)
factory.addLine(11,10, 7)
factory.addCircleArc(8,9,10, 8)
factory.addLine(8,7, 9)
factory.addLine(7,6, 10)
factory.addLine(6,5, 11)
factory.addCircleArc(3,4,5, 12)
factory.addLine(3,2, 13)
factory.addLine(2,1, 14)
factory.addLine(18,19, 15)
factory.addCircleArc(21,20,24, 16)
factory.addCircleArc(24,20,19, 17)
factory.addCircleArc(18,23,25, 18)
factory.addCircleArc(25,23,22, 19)
factory.addLine(21,22, 20)

# But we still need to define the exterior surface. Since this surface has a
# hole, its definition now requires two curves loops:
factory.addCurveLoop([17,-15,18,19,-20,16], 21)
factory.addPlaneSurface([21], 22)
factory.addCurveLoop([11,-12,13,14,1,2,-3,4,5,6,7,-8,9,10], 23)

factory.addPlaneSurface([23,21], 24)

# As a general rule, if a surface has N holes, it is defined by N+1 curve loops:
# the first loop defines the exterior boundary; the other loops define the
# boundaries of the holes.
factory.synchronize()

# Finally, we can add some comments by creating a post-processing view
# containing some strings:
v = gmsh.view.add("comments")

# Add a text string in window coordinates, 10 pixels from the left and 10 pixels
# from the bottom:
gmsh.view.addListDataString(v, [10, -10], ["Created with Gmsh"])

# Add a text string in model coordinates centered at (X,Y,Z) = (0, 0.11, 0),
# with some style attributes:
gmsh.view.addListDataString(v, [0, 0.11, 0], ["Hole"],
                            ["Align", "Center", "Font", "Helvetica"])

# These annotations are handled by a list-based post-processing view. For
# large post-processing datasets, that contain actual field values defined on
# a mesh, you should use model-based post-processing views instead, which
# allow to efficiently store continuous or discontinuous scalar, vector and
# tensor fields, or arbitrary polynomial order.

# Views and geometrical entities can be made to respond to double-click
# events, here to print some messages to the console:
gmsh.option.setString("View[0].DoubleClickedCommand",
                      "Printf('View[0] has been double-clicked!');")
gmsh.option.setString(
    "Geometry.DoubleClickedLineCommand",
    "Printf('Curve %g has been double-clicked!', Geometry.DoubleClickedEntityTag);")

# We can also change the color of some entities:
gmsh.model.setColor([(2, 22)], 127, 127, 127)
gmsh.model.setColor([(2, 24)], 160, 32, 240)
gmsh.model.setColor([(1, i) for i in 1:14], 255, 0, 0)
gmsh.model.setColor([(1, i) for i in 15:20], 255, 255, 0)

gmsh.model.mesh.generate(2)

gmsh.write("t4.msh")

gmsh.finalize()


#=

  Global constraint inverse in Julia ConstraintSolver.jl

  From Global Constraint Catalog
  http://www.emn.fr/z-info/sdemasse/gccat/Cinverse.html
  """
  inverse(NODES)

  Enforce each vertex of a digraph to have exactly one predecessor and
  one successor. In addition the following two statements are equivalent:
    - The successor of the ith node is the jth node.
    - The predecessor of the jth node is the ith node.
  """

  Note: This is implemented as "self-assignment", i.e. that
     inverse(X) -> assignment(X,X)
  This means that for each element X[I] either
     - X[I] = I
     or
     - X[I] = J <=> X[J] = I

  Example N=4:

    [1,2,3,4]
    [1,2,4,3]    (X[3]=4, X[4]=3)
    [1,3,2,4]    (X[2]=3, X[3]=2)
    [1,4,3,2]
    [2,1,3,4]
    [2,1,4,3]    (X[1]=2,X[2]=1, X[3]=4,X[4]=3)
    [3,2,1,4]
    [3,4,1,2]
    [4,2,3,1]
    [4,3,2,1]

   Number of solutions for N=1..11:
     [1,2,4,10,26,76,232,764,2620,9496,35696]
   OEIS:
   http://oeis.org/search?q=1%2C2%2C4%2C10%2C26%2C76%2C232%2C764%2C2620%2C9496%2C35696&language=english&go=Search

   http://oeis.org/A000085
   """
   Number of self-inverse permutations on n letters, also known as
   involutions; number of Young tableaux with n cells.
   """

  This model was created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#
using ConstraintSolver, JuMP
using Cbc, GLPK, Ipopt
const CS = ConstraintSolver
include("constraints_utils.jl")

function inverse_test(n,print_solutions=true,all_solutions=true)

    cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,   "all_solutions"=> all_solutions,
                                                            # "all_optimal_solutions"=>true,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            # "traverse_strategy"=>:DFS,
                                                            # "traverse_strategy"=>:DBFS,

                                                            # "branch_split"=>:Smallest,
                                                            # "branch_split"=>:Biggest,
                                                            "branch_split"=>:InHalf,

                                                            # "simplify"=>false,
                                                            # "simplify"=>true, # default

                                                            "time_limit"=>6,

                                                            # "lp_optimizer" => cbc_optimizer,
                                                            "lp_optimizer" => glpk_optimizer,
                                                            # "lp_optimizer" => ipopt_optimizer,
                                        ))

    @variable(model, 1 <= x[1:n] <= n, Int)
    @constraint(model, x in CS.AllDifferent())

    inverse(model,x)

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    if status == MOI.OPTIMAL
        num_sols = MOI.get(model, MOI.ResultCount())
        println("num_sols:$num_sols\n")
        if print_solutions
            for sol in 1:num_sols
                println("solution #$sol")
                xx = convert.(Integer,JuMP.value.(x; result=sol))
                println("x:$xx")

            end
        end
        # com = JuMP.backend(model).optimizer.model.inner
        # println("info:$(com.info)")
        # println("start_time:$(com.start_time)")
        # println("solve_time:$(com.solve_time)")
        println()
    else
        println("status:$status")
    end

    return status
end

function test_inverse_range(from,to)
    print_solutions=true
    all_solutions=true
    for n in from:to
        println("n:$n")
        if n > 4
            print_solutions = false
        end
        if n > 9
            all_solutions=false
            print_solutions=true
        end
        @time status = inverse_test(n,print_solutions,all_solutions)
        if status != MOI.OPTIMAL
            break
        end
    end
end

test_inverse_range(1,21)


## Framework for setting up an iterative problem for finding a zero
## TODO
## * a graphic of trace when verbose=true?

#
# In McNamee & Pan (DOI:10.1016/j.camwa.2011.11.015 there are a number of
# results on efficiencies of a solution, (1/d) log_10(q)
# Those implemented here are:
# quadratic cut (Muller) .265 (in a42)
# Newton() newton = .1505   or 1/2log(2)
# Order1() secant method .20 (1/1 * log(1.6)
# FalsePostion(12) Anderson-Bjork [.226, .233]
# FalsePostion(3) (King?) .264
# A42() 0.191 but convergence guaranteed
# Order8() 8th order 4 steps: .225 (log10(8)/4
# Order16() 16th order 5 steps .240
# Order5(): 5th order, 4 steps. 0.1747

# A zero is found by specifying:
# the method to use <: AbstractUnivariateZeroMethod
# the function(s)
# the initial state through a value for x either x, [a,b], or (a,b) <: AbstractUnivariateZeroState
# the options (e.g., tolerances) <: UnivariateZeroOptions

# The minimal amount needed to add a method, is to define a Method and an update_state method.

### Methods
abstract type AbstractUnivariateZeroMethod end
abstract type AbstractBracketing <: AbstractUnivariateZeroMethod end
abstract type AbstractNonBracketing <: AbstractUnivariateZeroMethod end
abstract type AbstractSecant <: AbstractNonBracketing end

# indicate if we expect f() to return one or multiple values (e.g. Newton)
fn_argout(::AbstractUnivariateZeroMethod) = 1

### States
abstract type  AbstractUnivariateZeroState{T,S} end

struct UnivariateZeroState{T,S} <: AbstractUnivariateZeroState{T,S}
    xn1::T
    xn0::T
    fxn1::S
    fxn0::S
end

# init_state(M, F, state) -- convert
# basic idea to convert from N to M:
# Fₘ = copy(M, fₙ)
# stateₘ = init_state(M, stateₙ, Fₘ)
function init_state(M::AbstractUnivariateZeroMethod, state::AbstractUnivariateZeroState, F)
    init_state(M, F, state.xn0, state.xn1, state.fxn0, state.fxn1)
end

# init_state(M,F,x) --> call init_state(M,F,x₀,x₁,fx₀, fx₁)
function init_state(M::AbstractUnivariateZeroMethod, F, x)
    error("no default method")
end

# initialize from xs, fxs
function init_state(::AbstractUnivariateZeroMethod, F, x₀, x₁, fx₀, fx₁)
    error("no default method")
end

# init_state(M, F, x; kwargs...)
# init_state(M, F x₀,x₁,fx₀,fx₁; kwargs...)
# init_state(M, state, F)
#
# A state holds at a minimum:
#
# * the values xₙ₋₁, xₙ and f(xₙ₋₁), f(xₙ) along with
# * some method-specific values
#
# A state is initialized with `init_state(M, F, x)` which sets up xₙ₋₁, xₙ, f(xₙ₋₁), f(xₙ)
# which then calls `init_state(M, F, xₙ₋₁, xₙ, f(xₙ₋₁), f(xₙ))` to finish the initialization
# to change to a new state use `init_state(M, state, F)`
#

# how many function evaluations in init_state
# this is a worst case estimate leading to the function call count being an upper bound only
initial_fncalls(M::AbstractUnivariateZeroState) = @warn "initial_fncalls fix $M"

## -----
### Options
struct UnivariateZeroOptions{Q,R,S,T}
    xabstol::Q
    xreltol::R
    abstol::S
    reltol::T
    maxevals::Int
    maxfnevals::Int
    strict::Bool
end

"""
    default_tolerances(M::AbstractUnivariateZeroMethod, [T], [S])

The default tolerances for most methods are `xatol=eps(T)`,
`xrtol=eps(T)`, `atol=4eps(S)`, and `rtol=4eps(S)`, with the proper
units (absolute tolerances have the units of `x` and `f(x)`; relative
tolerances are unitless). For `Complex{T}` values, `T` is used.

The number of iterations is limited by `maxevals=40`.

"""
default_tolerances(M::AbstractUnivariateZeroMethod) = default_tolerances(M, Float64, Float64)
function default_tolerances(::AbstractUnivariateZeroMethod, ::Type{T}, ::Type{S}) where {T, S}
    xatol = eps(real(T)) * oneunit(real(T))
    xrtol = eps(real(T))  # unitless
    atol = 4 * eps(real(float(S))) * oneunit(real(S))
    rtol = 4 * eps(real(float(S))) * one(real(S))
    maxevals = 40
    maxfnevals = typemax(Int)
    strict = false
    (xatol, xrtol, atol, rtol, maxevals, maxfnevals, strict)
end

init_options(M::AbstractUnivariateZeroMethod,
             state::AbstractUnivariateZeroState{T,S};
             kwargs...
             ) where {T, S} = init_options(M, T, S; kwargs...)

function init_options(M, T=Float64, S=Float64; kwargs...)
    d = kwargs

    defs = default_tolerances(M, T, S)
    options = UnivariateZeroOptions(get(d, :xatol, get(d, :xabstol, defs[1])),
                                    get(d, :xrtol, get(d, :xreltol, defs[2])),
                                    get(d, :atol,  get(d, :abstol,  defs[3])),
                                    get(d, :rtol,  get(d, :reltol,  defs[4])),
                                    get(d, :maxevals,   get(d, :maxsteps, defs[5])),
                                    get(d, :maxfnevals, defs[6]),
                                    get(d, :strict,     defs[7]))
    if haskey(d, :maxfnevals)
        @warn("maxfnevals is ignored. See the test for an example to implement this featrue")
    end
    options
end

# # reset options to default values
@deprecate init_options!(options, M) init_options(M)

## Tracks (for logging actual steps)
## when no logging this should get optimized out
abstract type AbstractTracks end
struct NullTracks <: AbstractTracks end
# api
log_step(s::NullTracks, M, x, init=false) = nothing
log_steps(::NullTracks, n=1) = nothing
incfn(::NullTracks, i=1) = nothing
log_message(::NullTracks, msg) = nothing
log_convergence(::NullTracks, msg) = nothing
log_state(::NullTracks, state) = nothing
log_method(::NullTracks, method) = nothing
log_nmethod(::NullTracks, method) = nothing

# a tracks object to record tracks
"""
    Tracks(T, S)

Construct a `Tracks` object used to record the progress of the algorithm.

By default, a null tracks object used, but if `verbose=true` is specified of a `Tracks` object passed to the keyword `tracks` then the steps of the algorithm are recorded in the `Tracks` object. If `verbose=true`, the `Tracks` object will be shown.
"""
mutable struct Tracks{T,S} <: AbstractTracks
    xs::Vector{T}
    fs::Vector{S}
    steps::Int
    fncalls::Int
    convergence_flag::Symbol
    message::String
    state
    method
    nmethod
end
Tracks(T,S) = Tracks(T[],S[], 0, 0, :not_converged, "", nothing, nothing,nothing)
Tracks(s::AbstractUnivariateZeroState{T,S}) where {T, S} = Tracks(T,S)
Tracks(verbose, tracks, state) = (verbose && isa(tracks, NullTracks)) ? Tracks(state) : tracks

function log_step(l::Tracks, M::Any, state, init=nothing)
    if init != nothing
        x₀, fx₀ = state.xn0, state.fxn0
        push!(l.xs, x₀)
        push!(l.fs, fx₀)
    end
    x₁, fx₁ = state.xn1, state.fxn1
    push!(l.xs, x₁)
    push!(l.fs, fx₁)

    init == nothing && log_steps(l, 1)
    nothing
end

incfn(l::Tracks, i=1) = (l.fncalls += i; nothing)
log_steps(l::Tracks, n=1) = (l.steps += n; nothing)
log_message(l::Tracks, msg) = (l.message *= msg; nothing)
log_convergence(l::Tracks, msg) = (l.convergence_flag = msg; nothing)
log_state(l::Tracks, state) = (l.state = state; nothing)
log_method(l::Tracks, method) = (l.method = method; nothing)
log_nmethod(l::Tracks, method) = (l.nmethod = method; nothing)

Base.show(io::IO, l::Tracks) = show_trace(io, l.method, l.nmethod, l.state, l)

function show_tracks(io::IO, s::Tracks, M::AbstractUnivariateZeroMethod)
    for (i, (xi, fxi)) in enumerate(zip(s.xs, s.fs))
        println(io,@sprintf("%s = % 18.16f,\t %s = % 18.16f", "x_$(i-1)", float(xi), "fx_$(i-1)", float(fxi)))
    end
    println(io,"")
end

function show_trace(io::IO, method, N, state, tracks)

    if state == nothing
        print(io, "Algorithm has not been run")
        return nothing
    end

    converged = !isnan(state.xn1)
    println(io, "Results of univariate zero finding:\n")
    if converged
        println(io, "* Converged to: $(state.xn1)")
        if N == nothing || isa(method, AbstractBracketing)
            println(io, "* Algorithm: $(method)")
        else
            println(io, "* Algorithm: $(method), with possible bracketing with $N")
        end
        println(io, "* iterations: $(tracks.steps)")
        println("* function evaluations ≈ $(tracks.fncalls)")
        tracks.convergence_flag == :x_converged && println(io, "* stopped as x_n ≈ x_{n-1} using atol=xatol, rtol=xrtol")
        tracks.convergence_flag == :f_converged && tracks.message == "" && println(io, "* stopped as |f(x_n)| ≤ max(δ, |x|⋅ϵ) using δ = atol, ϵ = rtol")
        tracks.message != "" && println(io, "* Note: $(tracks.message)")
    else
        println(io, "* Convergence failed: $(tracks.message)")
        println(io, "* Algorithm $(method)")
    end
    println(io, "")
    println(io, "Trace:")
    show_tracks(io, tracks, method)
end

### Functions
# A hacky means to call a function so that parameters can be passed as desired
# and the correct number of outputs are computed
struct Callable_Function{Single, Tup, F, P}
    f::F
    p::P
    function Callable_Function(M, f, p=nothing)
        Single = Val{fn_argout(M)}
        Tup = Val{isa(f, Tuple)}
        F = typeof(f)
        P = typeof(p)
        new{Single, Tup, F, P}(f, p)
    end
end

function Callable_Function(M,F::Callable_Function, p=F.p)
    Callable_Function(M, F.f, p)
end

# return f(x); (f(x), f(x)/f'(x)); *or* f(x), (f(x)/f'(x), f'(x)/f''(x), ...) # so N=1, 2 are special cased
# Callable_Function(output_arity, input_arity, F, p)
# First handle: x -> (f,f/f', f'/f'', ...)
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{1}, T <: Val{false}, 𝑭, P<:Nothing} =
    first(F.f(x))
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{1}, T <: Val{false}, 𝑭, P} =
    first(F.f(x, F.p))

(F::Callable_Function{S,T,𝑭,P})(x) where {N, S <: Val{2}, T <: Val{false}, 𝑭, P<:Nothing} =
    F.f(x)[1:2]
(F::Callable_Function{S,T,𝑭,P})(x) where {N, S <: Val{2}, T <: Val{false}, 𝑭, P} =
    F.f(x, F.p)[1:2]

# N ≥ 3 returns (f, (...))
(F::Callable_Function{S,T,𝑭,P})(x) where {N, S <: Val{N}, T <: Val{false}, 𝑭, P<:Nothing} = begin
    fs = F.f(x)
    fs[1], ntuple(i->fs[i+1], Val(N-1))
end
(F::Callable_Function{S,T,𝑭,P})(x) where {N, S <: Val{N}, T <: Val{false}, 𝑭, P} = begin
    fs = F.f(x, F.p)
    fs[1], ntuple(i->fs[i+1], Val(N-1))
end

## f is specified as a tuple (f,f',f'', ...)
## N =1  return f(x)
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{1}, T <: Val{true}, 𝑭, P<:Nothing} =
    first(F.f)(x)
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{1}, T <: Val{true}, 𝑭, P} =
    first(F.f)(x, F.p)
## N=2 return (f(x), f(x)/f'(x))
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{2}, T <: Val{true}, 𝑭, P<:Nothing} = begin
    f, f′ = (F.f[1])(x), (F.f[2])(x)
    f, f/f′
end
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{2}, T <: Val{true}, 𝑭, P} = begin
    f, f′ = (F.f[1])(x, F.p), (F.f[2])(x, F.p)
    f, f/f′
end

## For N ≥ 3 we return (f, (f/f', f'/f'', ...);
## Pay no attention to this code; we hand write a bunch, as the
## general formula later runs more slowly.
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{3}, T <: Val{true}, 𝑭, P<:Nothing} = begin
    f, f′, f′′ = (F.f[1])(x), (F.f[2])(x), (F.f[3])(x)
    f, (f/f′, f′/f′′)
end
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{3}, T <: Val{true}, 𝑭, P} = begin
    f, f′, f′′ = (F.f[1])(x, F.p), (F.f[2])(x, F.p), (F.f[3])(x, F.p)
    f, (f/f′, f′/f′′)
end

(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{4}, T <: Val{true}, 𝑭, P<:Nothing} = begin
    f, f′, f′′, f′′′ = (F.f[1])(x), (F.f[2])(x), (F.f[3])(x), (F.f[4])(x)
    f, (f/f′, f′/f′′, f′′/f′′′)
end
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{4}, T <: Val{true}, 𝑭, P} = begin
    f, f′,f′′,f′′′ = (F.f[1])(x, F.p), (F.f[2])(x, F.p), (F.f[3])(x, F.p), (F.f[4])(x, F.p)
    𝐓 = eltype(f/f′)
    f, NTuple{3,𝐓}((f/f′, f′/f′′, f′′/f′′′))
end

(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{5}, T <: Val{true}, 𝑭, P<:Nothing} = begin
    f, f′, f′′, f′′′, f′′′′ = (F.f[1])(x), (F.f[2])(x), (F.f[3])(x), (F.f[4])(x), (F.f[5])(x)
    f, (f/f′, f′/f′′, f′′/f′′′, f′′′/f′′′′)
end
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{5}, T <: Val{true}, 𝑭, P} = begin
    f, f′,f′′,f′′′,f′′′′ = (F.f[1])(x, F.p), (F.f[2])(x, F.p), (F.f[3])(x, F.p), (F.f[4])(x, F.p), (F.f[5])(x, F.p)
    f, (f/f′, f′/f′′, f′′/f′′′, f′′′/f′′′′)
end

(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{6}, T <: Val{true}, 𝑭, P<:Nothing} = begin
    f, f′, f′′, f′′′, f′′′′, f′′′′′ = (F.f[1])(x), (F.f[2])(x), (F.f[3])(x), (F.f[4])(x), (F.f[5])(x),(F.f[6])(x)
    f, (f/f′, f′/f′′, f′′/f′′′, f′′′/f′′′′, f′′′′/f′′′′′)
end
(F::Callable_Function{S,T,𝑭,P})(x) where {S <: Val{6}, T <: Val{true}, 𝑭, P} = begin
    f, f′,f′′,f′′′,f′′′′, f′′′′′ = (F.f[1])(x, F.p), (F.f[2])(x, F.p), (F.f[3])(x, F.p), (F.f[4])(x, F.p), (F.f[5])(x, F.p), (F.f[6])(x, F.p)
    f, (f/f′, f′/f′′, f′′/f′′′, f′′′/f′′′′, f′′′′/f′′′′′)
end

# faster with the above written out, should generate them...
(F::Callable_Function{S,T,𝑭,P})(x) where {𝐍, S <: Val{𝐍}, T <: Val{true}, 𝑭, P<:Nothing} = begin
    fs = ntuple(i -> F.f[i](x), Val(𝐍))
    first(fs), ntuple(i -> fs[i]/fs[i+1], Val(𝐍-1))
end

(F::Callable_Function{S,T,𝑭,P})(x) where {𝐍, S <: Val{𝐍}, T <: Val{true}, 𝑭, P} = begin
    fs = ntuple(i -> F.f[i](x, p), Val(𝐍))
    first(fs), ntuple(i -> fs[i]/fs[i+1], Val(𝐍-1))
end

## Assess convergence
@inline function _is_f_approx_0(fa, a, atol, rtol, relaxed::Any)
    aa, afa = abs(a), abs(fa)
    tol = max(_unitless(atol), _unitless(aa) * rtol)
    tol = cbrt(abs(_unitless(tol)))  # relax test
    afa <= tol * oneunit(afa)
end
@inline function _is_f_approx_0(fa, a, atol, rtol)
    aa, afa = abs(a), abs(fa)
    tol = max(_unitless(atol), _unitless(aa) * rtol)
    afa <= tol * oneunit(afa)
end

"""
    Roots.assess_convergence(method, state, options)

Assess if algorithm has converged.

Return a convergence flag and a Boolean indicating if algorithm has terminated (converged or not converged)

If algrithm hasn't converged returns `(:not_converged, false)`.

If algorithm has stopped or converged, return flag and `true`. Flags are:

* `:x_converged` if `abs(xn1 - xn0) < max(xatol, max(abs(xn1), abs(xn0)) * xrtol)`

* `:f_converged` if  `|f(xn1)| < max(atol, |xn1|*rtol)`

* `:nan`, `:inf` if xn1 or fxn1 is `NaN` or an infinity

* `:not_converged` if algorithm should continue

Does not check number of steps taken nor number of function evaluations.

In `decide_convergence`, stopped values (and `:x_converged` when `strict=false`) are checked for convergence with a relaxed tolerance.

"""
function assess_convergence(method::Any, state::AbstractUnivariateZeroState{T,S}, options) where {T,S}

    # return convergence_flag, boolean

    xn0, xn1 = state.xn0, state.xn1
    fxn1 = state.fxn1

    if isnan(xn1) || isnan(fxn1)
        return (:nan, true)
    end

    if isinf(xn1) || isinf(fxn1)
        return (:inf, true)
    end

    # f(xstar) ≈ xstar * f'(xstar)*eps(), so we pass in lambda
    if _is_f_approx_0(fxn1, xn1, options.abstol, options.reltol)
        return (:f_converged, true)
    end

    # stop when xn1 ~ xn.
    # in find_zeros there is a check that f could be a zero with a relaxed tolerance
    if isapprox(xn1, xn0, atol=options.xabstol, rtol=options.xreltol)
        return (:x_converged, true)
    end

    return (:not_converged, false)
end

# state has stopped, this identifies if it has converged
"""
    decice_convergence(M,F,state,options, convergence_flag)

When the algorithm terminates, this function decides the stopped value or returns NaN
"""
function decide_convergence(M::AbstractUnivariateZeroMethod,  F, state::AbstractUnivariateZeroState{T,S}, options, val) where {T,S}

    xn0, xn1 = state.xn0, state.xn1
    fxn1 = state.fxn1

    val ∈ (:f_converged, :exact_zero, :converged) && return xn1

    ## stopping is a heuristic, x_converged can mask issues
    ## if strict=true or the tolerance for f is 0 this will return xn1 if x_converged
    ## if strict == false, this will also check f(xn) ~ - with a relaxed
    ## tolerance
    if options.strict || (iszero(options.abstol) && iszero(options.reltol))
        val == :x_converged && return xn1
        _is_f_approx_0(fxn1, xn1, options.abstol, options.reltol) && return xn1
    else
        if val ∈ (:x_converged, :not_converged)
            # |fxn| <= relaxed

            _is_f_approx_0(fxn1, xn1, options.abstol, options.reltol, true) && return xn1
        end
    end

    # if (state.stopped || state.x_converged) && !(state.f_converged)
    #     ## stopped is a heuristic, x_converged can mask issues
    #     ## if strict == false, this will also check f(xn) ~ - with a relaxed
    #     ## tolerance

    #     ## are we at a crossing values?
    #     ## seems worth a check for 2 fn evals.
    #     if T <: Real && S <: Real
    #         for u in (prevfloat(xn1), nextfloat(xn1))
    #             fu = first(F(u))
    #             incfn(state)
    #             if iszero(fu) || _unitless(fu * fxn1) < 0
    #                 state.message *= "Change of sign at xn identified. "
    #                 state.f_converged = true
    #             end
    #         end
    #     end

    #     δ = maximum(_unitless, (options.abstol, options.reltol))
    #     if options.strict || iszero(δ)
    #         if state.x_converged
    #             state.f_converged = true
    #         else
    #             state.convergence_failed = true
    #         end

    #     else
    #         xstar, fxstar = state.xn1, state.fxn1
    #         if _is_f_approx_0(fxstar, xstar, options.abstol, options.reltol, :relaxed)
    #             state.xstar, state.fxstar = xstar, fxstar
    #             msg = "Algorithm stopped early, but |f(xn)| < ϵ^(1/3), where ϵ depends on xn, rtol, and atol. "
    #             state.message = state.message == "" ? msg : state.message * "\n\t" * msg
    #             state.f_converged = true
    #         else
    #             state.convergence_failed = true
    #         end
    #     end
    # end

    # if !state.f_converged
    #     state.xstar, state.fxstar = NaN*xn1, NaN*fxn1
    # end

    NaN * xn1

end

## ----

"""

    find_zero(f, x0, M, [N::AbstractBracketing]; kwargs...)

Interface to one of several methods for finding zeros of a univariate function, e.g. solving ``f(x)=0``.

# Initial starting value

For most methods, `x0` is a scalar value indicating the initial value
in the iterative procedure. (Secant methods can have a tuple specify
their initial values.) Values must be a subtype of `Number` and have
methods for `float`, `real`, and `oneunit` defined.

For bracketing intervals, `x0` is specified using a tuple, a vector, or any iterable with `extrema` defined. A bracketing interval, ``[a,b]``, is one where f(a) and f(b) have different signs.

# Return value

If the algorithm suceeds, the approximate root identified is returned. A `ConvergenceFailed` error is thrown if the algorithm fails. The alternate form `solve(ZeroProblem(f,x0), M)` returns `NaN` in case of failure.

# Specifying a method

A method is specified to indicate which algorithm to employ:

* There are methods for bisection where a bracket is specified: [`Bisection`](@ref), [`Roots.A42`](@ref), [`Roots.AlefeldPotraShi`](@ref), [`Roots.Brent`](@ref), and [`FalsePosition`](@ref)

* There are several derivative-free methods: cf. [`Order0`](@ref), [`Order1`](@ref) (also [`Roots.Secant`](@ref)), [`Order2`](@ref) (also [`Roots.Steffensen`](@ref)), [`Order5`](@ref), [`Order8`](@ref), and [`Order16`](@ref), where the number indicates the order of the convergence. Methods [`Roots.Order1B`](@ref) and [`Roots.Order2B`](@ref) are useful when the desired zero has a multiplicity.

* There are some classical methods where derivatives are required: [`Roots.Newton`](@ref), [`Roots.Halley`](@ref), [`Roots.Schroder`](@ref).

* The family [`Roots.LithBoonkkampIJzerman{S,D}`](@ref) for different `S` and `D` uses a linear multistep method root finder. The `(2,0)` method is the secant method, `(1,1)` is Newton's method.

For more detail, see the help page for each method (e.g., `?Order1`). Non-exported methods must be qualified with module name, as in `?Roots.Schroder`.

If no method is specified, the default method depends on `x0`:

* If `x0` is a scalar, the default is the slower, but more robust `Order0` method.

* If `x0` is a tuple, vector, or iterable with `extrema` defined indicating a *bracketing* interval, the `Bisection` method is used. (The exact algorithm depends on the number type and the tolerances.)

# Specifying the function

The function(s) are passed as the first argument.

For the few methods that use one or more derivatives (`Newton`, `Halley`,
`Schroder`, `LithBoonkkampIJzerman(S,D)`, and  `Order5Derivative`) a
tuple of functions is used. For the classical algorithms, a function returning `(f(x), f(x)/f'(x), [f'(x)/f''(x)])` may be used.

# Optional arguments (tolerances, limit evaluations, tracing)

* `xatol` - absolute tolerance for `x` values. Passed to `isapprox(x_n, x_{n-1})`.
* `xrtol` - relative tolerance for `x` values. Passed to `isapprox(x_n, x_{n-1})`.
* `atol`  - absolute tolerance for `f(x)` values.
* `rtol`  - relative tolerance for `f(x)` values.
* `maxevals`   - limit on maximum number of iterations.
* `strict` - if `false` (the default), when the algorithm stops, possible zeros are checked with a relaxed tolerance.
* `verbose` - if `true` a trace of the algorithm will be shown on successful completion. See the internal `Tracks` object to save this trace.

See the help string for `Roots.assess_convergence` for details on
convergence. See the help page for `Roots.default_tolerances(method)`
for details on the default tolerances.

In general, with floating point numbers, convergence must be
understood as not an absolute statement. Even if mathematically `α` is
an answer and `xstar` the floating point realization, it may be that
`f(xstar) - f(α)  ≈ xstar ⋅  f'(α) ⋅ eps(α)`, so tolerances must be
appreciated, and at times specified.

For the `Bisection` methods, convergence is guaranteed, so the tolerances are set to be ``0`` by default.

If a bracketing method is passed in after the method specification,
then whenever a bracket is identified during the algorithm, the method
will switch to the bracketing method to identify the zero. (Bracketing
methods are mathematicall guaranteed to converge, non-bracketing methods may or may not converge.)
This is what `Order0` does by default, with an initial secant method switching
to the `AlefeldPotraShi` method should a bracket be encountered.

Note: The order of the method is hinted at in the naming scheme. A
scheme is order `r` if, with `eᵢ = xᵢ - α`, `eᵢ₊₁ = C⋅eᵢʳ`. If the
error `eᵢ` is small enough, then essentially the error will gain `r`
times as many leading zeros each step. However, if the error is not
small, this will not be the case. Without good initial guesses, a high
order method may still converge slowly, if at all. The `OrderN`
methods have some heuristics employed to ensure a wider range for
convergence at the cost of not faithfully implementing the method,
though those are available through unexported methods.

# Examples:

Default methods.

```jldoctest find_zero
julia> using Roots

julia> find_zero(sin, 3)  # use Order0()
3.141592653589793

julia> find_zero(sin, (3,4)) # use Bisection()
3.141592653589793
```

Specifying a method,

```jldoctest find_zero
julia> find_zero(sin, (3,4), Order1())            # can specify two starting points for secant method
3.141592653589793

julia> find_zero(sin, 3.0, Order2())              # Use Steffensen method
3.1415926535897936

julia> find_zero(sin, big(3.0), Order16())        # rapid convergence
3.141592653589793238462643383279502884197169399375105820974944592307816406286198

julia> find_zero(sin, (3, 4), Roots.A42())      # fewer function calls than Bisection(), in this case
3.141592653589793

julia> find_zero(sin, (3, 4), FalsePosition(8))   # 1 of 12 possible algorithms for false position
3.141592653589793

julia> find_zero((sin,cos), 3.0, Roots.Newton())  # use Newton's method
3.141592653589793

julia> find_zero((sin, cos, x->-sin(x)), 3.0, Roots.Halley())  # use Halley's method
3.141592653589793
```

Changing tolerances.

```jldoctest find_zero
julia> fn = x -> (2x*cos(x) + x^2 - 3)^10/(x^2 + 1);

julia> x0, xstar = 3.0,  2.9947567209477;

julia> fn(find_zero(fn, x0, Order2())) <= 1e-14  # f(xₙ) ≈ 0, but Δxₙ can be largish
true

julia> find_zero(fn, x0, Order2(), atol=0.0, rtol=0.0) # error: x_n ≉ x_{n-1}; just f(x_n) ≈ 0
ERROR: Roots.ConvergenceFailed("Algorithm failed to converge")
[...]

julia> fn = x -> (sin(x)*cos(x) - x^3 + 1)^9;

julia> x0, xstar = 1.0,  1.112243913023029;

julia> find_zero(fn, x0, Order2()) ≈ xstar
true

julia> find_zero(fn, x0, Order2(), maxevals=3)    # need more steps to converge
ERROR: Roots.ConvergenceFailed("Algorithm failed to converge")
[...]
```

# Tracing

Passing `verbose=true` will show details on the steps of the algorithm.
The `tracks` argument allows
the passing of storage to record the values of `x` and `f(x)` used in
the algorithm.

!!! note
    See [`solve!`](@ref) and [`ZeroProblem`](@ref) for an alternate interface.
"""
function find_zero(f, x0, M::AbstractUnivariateZeroMethod;
                   p = nothing,
                   verbose=false,
                   tracks::AbstractTracks=NullTracks(),
                   kwargs...)

    xstar = solve(ZeroProblem(f, x0), M;
                  p=p, verbose=verbose, tracks=tracks,
                  kwargs...)

    isnan(xstar) && throw(ConvergenceFailed("Algorithm failed to converge"))

    xstar

end

# defaults when method is not specified
# if a number, use Order0
# O/w use a bracketing method of an assumed iterable
find_zero(f, x0::T; kwargs...)  where {T <: Number} = find_zero(f, x0, Order0(); kwargs...)
find_zero(f, x0; kwargs...) = find_zero(f, x0, Bisection(); kwargs...)

"""
    find_zero(M, F, state, [options], [l])

Find zero using method `M`, function(s) `F`, and initial state
`state`. Returns an approximate zero or `NaN`. Useful when some part
of the processing pipeline is to be adjusted.

* `M::AbstractUnivariateZeroMethod` a method, such as `Secant()`
* `F`: A callable object (or tuple of callable objects for certain methods)
* `state`: An initial state, as created by `init_state` (or `_init_state`).
* `options::UnivariateZeroOptions`: specification of tolerances
* `l::AbstractTracks`: used to record steps in algorithm, when requested.
```

!!! note
    To be deprecated in favor of `solve!(init(...))`.
"""
function find_zero(M::AbstractUnivariateZeroMethod,
                   F,
                   state::AbstractUnivariateZeroState,
                   options::UnivariateZeroOptions=init_options(M, state),
                   l::AbstractTracks=NullTracks()
                   )
    solve!(init(M, F, state, options, l))
end

## ---------------

## Create an Iterator interface
# returns NaN, not an error, if there are issues

"""
    ZeroProblem{F,X}

A container for a function and initial guess to be used with `solve`.
"""
struct ZeroProblem{F,X}
    F::F
    x₀::X
end

## The actual iterating object
struct ZeroProblemIterator{M,F,S,O,L}
    M::M
    F::F
    state::S
    options::O
    logger::L
end

Base.show(io::IO, Z::ZeroProblemIterator) = print(io, "A problem object to pass to `solve!`")

## Initialize a Zero Problem Iterator
## init(Z,p)
## init(Z,M,p)
## init(M,F,state, [options], [logger])
## want p to be positional, not named⁺
function init(𝑭𝑿::ZeroProblem, M::AbstractUnivariateZeroMethod, p′ = nothing;
              p = nothing,
              verbose::Bool=false,
              tracks = NullTracks(),
              kwargs...)

    F = Callable_Function(M, 𝑭𝑿.F, p === nothing ? p′ : p)  #⁺
    state = init_state(M, F, 𝑭𝑿.x₀)
    options = init_options(M, state; kwargs...)
    l = Tracks(verbose, tracks, state)
    incfn(l, initial_fncalls(M))
    ZeroProblemIterator(M,F,state,options,l)

end

function init(𝑭𝑿::ZeroProblem, p′=nothing; kwargs...)
    M = length(𝑭𝑿.x₀) == 1 ? Secant() : Bisection()
    init(𝑭𝑿, M, p′; kwargs...)
end

function init(M::AbstractUnivariateZeroMethod, F,
              state::AbstractUnivariateZeroState,
              options::UnivariateZeroOptions=init_options(M, state),
              l::AbstractTracks=NullTracks())
    ZeroProblemIterator(M, Callable_Function(M,F), state, options, l)
end

# Optional iteration interface to handle looping
# errors on non-convergence, unlike solve!(init(...)) which returns NaN
# allocates...
# This should be deprecated
function Base.iterate(P::ZeroProblemIterator, st=nothing)
    ## st = (val, (state, ctr, val))
    if st == nothing
        state = P.state
        ctr = 1
    else
        (state, ctr, val) = st
        ctr += 1
    end
    M, options, l = P.M, P.options, P.logger
    val,stopped = :no_convergence, false
    while true
        val, stopped = assess_convergence(M, state, options)
        stopped && break
        if ctr > options.maxevals
            stopped = true
            break
        end
        state, stopped = update_state(M, P.F, state, options, l)
        log_step(l, M, state)
        break
    end

    if stopped
        xstar = decide_convergence(P.M, P.F, state, P.options, val)
        isnan(xstar) && throw(ConvergenceFailed("Stopped at $(state.xn1)"))
        nothing
    else
        return (state.xn1, (state, ctr, val))
    end
end

"""
    solve(fx::ZeroProblem, [p=nothing]; kwargs...)
    solve(fx::ZeroProblem, M, [p=nothing]; kwargs...)
    init(fx::ZeroProblem, [M], [p=nothing];
         verbose=false, tracks=NullTracks(), kwargs...)
    solve!(P::ZeroProblemIterator)

Solve for the zero of a function specified through a  `ZeroProblem` or `ZeroProblemIterator`

The methods involved with this interface are:

* `ZeroProblem`: used to specify a problem with a function (or functions) and an initial guess
* `solve`: to solve for a zero in a `ZeroProblem`

The latter calls the following, which can be useful independently:

* `init`: to initialize an iterator with a method for solution, any adjustments to the default tolerances, and a specification to log the steps or not.
* `solve!` to iterate to convergence.

Returns `NaN`, not an error, when the problem can not be solved. Tested for zero allocations.

## Examples:

```jldoctest find_zero
julia> using Roots

julia> fx = ZeroProblem(sin, 3)
ZeroProblem{typeof(sin), Int64}(sin, 3)

julia> solve(fx)
3.141592653589793
```

Or, if the iterable is required

```jldoctest find_zero
julia> problem = init(fx);

julia> solve!(problem)
3.141592653589793
```

The default method is `Order1()`, when  `x0` is a number, or `Bisection()` when `x0` is an iterable with 2 or more values.

A second position argument for `solve` or `init` is used to specify a different method; keyword arguments can be used to adjust the default tolerances.

```jldoctest find_zero
julia> solve(fx, Order5(), atol=1/100)
3.1425464815525403
```

The above is equivalent to:

```jldoctest find_zero
julia> problem = init(fx, Order5(), atol=1/100);

julia> solve!(problem)
3.1425464815525403
```

The  argument `p` may be used if the function(s) to be solved depend on a parameter in their second positional argument (e.g., `f(x,p)`). For example

```jldoctest find_zero
julia> f(x,p) = exp(-x) - p # to solve p = exp(-x)
f (generic function with 1 method)

julia> fx = ZeroProblem(f, 1)
ZeroProblem{typeof(f), Int64}(f, 1)

julia> solve(fx, 1/2)  # log(2)
0.6931471805599453
```

This would be recommended, as there is no recompilation due to the function changing.

The argument `verbose=true` for `init` instructs that steps to be logged;

The iterator interface allows for the creation of hybrid solutions, for example, this is essentially how `Order0` is constructed (`Order0` follows secant steps until a bracket is identified, after which is switches to a bracketing algorithm.)

```jldoctest find_zero
julia> function order0(f, x)
           fx = ZeroProblem(f, x)
           p = init(fx, Roots.Secant())
           xᵢ,st = ϕ = iterate(p)
           while ϕ != nothing
               xᵢ, st = ϕ
               state, ctr = st
               fᵢ₋₁, fᵢ = state.fxn0, state.fxn1
               if sign(fᵢ₋₁)*sign(fᵢ) < 0 # check for bracket
                   x0 = (state.xn0, state.xn1)
                   fx′ = ZeroProblem(f, x0)
                   p = init(fx′, Bisection())
                   xᵢ = solve!(p)
                   break
               end
               ϕ = iterate(p, st)
           end
           xᵢ
       end
order0 (generic function with 1 method)

julia> order0(sin, 3)
3.141592653589793
```

"""
function solve!(P::ZeroProblemIterator; verbose=false)

    M, F, state, options, l = P.M, P.F, P.state, P.options, P.logger

    val, stopped = :not_converged, false
    ctr = 1
    log_step(l, M, state, :init)

    while !stopped
        val, stopped = assess_convergence(M, state, options)
        stopped && break
        ctr > options.maxevals && break

        state, stopped = update_state(M, F, state, options, l)

        log_step(l, M, state)
        ctr += 1

    end

    if !(l isa NullTracks)
        log_convergence(l, val); log_state(l, state); log_method(l, M)
        verbose && display(l)
    end

    val, stopped = assess_convergence(M, state, options) # udpate val flag
    decide_convergence(M, F, state, options, val)

end

# thread verbose through
CommonSolve.solve(F::ZeroProblem, args...; verbose=false, kwargs...) = solve!(init(F, args...; verbose=verbose, kwargs...); verbose=verbose)


"""
    vlsrelief(data::Array{<:Real,2}, target::Array{<:Integer,1}, subset_size::Integer=-1,
                   num_subsets::Integer=100; rba::Any=relieff)::Array{Float64,1}

Compute feature weights using VLSRelief algorithm. The num_partitions_to_select argument specifies how many partitions
to select in each iteration. The num_subsets argument specifies the number of subset iterations to perform. The subset_size
argument specifies the size of a partition. The rba argument specifies a (partially applied) wrapped
RBA algorithm that should accept just the data and target values.

---
# Reference:
- Margaret Eppstein and Paul Haake. Very large scale ReliefF for genome-
wide association analysis. In 2008 IEEE Symposium on Computational
Intelligence in Bioinformatics and Computational Biology, CIBCB ’08,
2008.
"""
function vlsrelief(data::Array{<:Real,2}, target::Array{<:Integer,1}, subset_size::Integer=-1,
                   num_subsets::Integer=100; rba::Any=relieff)::Array{Float64,1}

    # If subset_size argument has signal value of -1, compute default value from data size.
    if subset_size == -1
        subset_size = Int64(ceil(size(data, 2) / 10))
    elseif subset_size > size(data, 2)  # If specified subset size greater than number of features, throw error.
        throw(DomainError(subset_size, "Feature subset size cannot exceed the number of features in the dataset."))
    end

    # Initialize feature weights vector.
    weights = zeros(Float64, size(data, 2))

    # Go over feature subsets.
    @inbounds for i = 1:num_subsets

        # Sample features.
        sample_idxs = StatsBase.sample(1:size(data, 2), subset_size, replace=false)

        # Use RBA on subset to obtain local weights.
        rba_weights = rba(data[:, sample_idxs], target)

        # Update weights using computed local weights.
        weights[sample_idxs] = vec(maximum(hcat(weights[sample_idxs], rba_weights), dims=2))
    end

    # Return computed feature weights.
    return weights
end


export CountingMeasure

struct CountingMeasure{X} <: PrimitiveMeasure end

@primitive CountingMeasure

function Base.show(io::IO, ::MIME"text/plain", μ::CountingMeasure{X}) where {X}
    io = IOContext(io, :compact => true)
    print(io, "CountingMeasure(", X, ")")
end

CountingMeasure(X) = CountingMeasure{X}()

# sampletype(::CountingMeasure{ℝ}) = Float64
# sampletype(::CountingMeasure{ℝ₊}) = Float64
# sampletype(::CountingMeasure{𝕀}) = Float64

sampletype(::CountingMeasure) = Int

testvalue(μ::CountingMeasure{X}) where {X} = testvalue(X)

logdensity(::CountingMeasure, x) = zero(float(x))

# (::CountingMeaure)(s) = length(Set(s))


using AccurateArithmetic
using Test

using AccurateArithmetic.Summation: accumulate
using AccurateArithmetic.Summation: sumAcc, compSumAcc, dotAcc, compDotAcc
using AccurateArithmetic.Test: generate_sum, generate_dot
using LinearAlgebra

@testset "AccurateArithmetic" begin
    @testset "Tests" begin
        @testset "generate_sum" begin
            @testset "vector length" begin
                for n in 100:110
                    let (x, s, c_) = generate_sum(n, 1e10)
                        @test length(x) == n
                    end
                end
            end

            @testset "condition number" begin
                for c in (1e10, 1e20)
                    let (x, s, c_) = generate_sum(101, c)
                        @test c/100 < c_ < 1000c
                    end
                end
            end
        end

        @testset "generate_dot" begin
            @testset "vector length" begin
                for n in 100:110
                    let (x, y, s, c_) = generate_dot(n, 1e10)
                        @test length(x) == n
                        @test length(y) == n
                    end
                end
            end

            @testset "condition number" begin
                for c in (1e10, 1e20)
                    let (x, y, s, c_) = generate_dot(101, c)
                        @test c/100 < c_ < 1000c
                    end
                end
            end
        end
    end

    @testset "summation" begin
        @testset "naive" begin
            for N in 100:110
                x = rand(N)
                ref = sum(x)
                @test ref ≈ sum_naive(x)

                acc = sumAcc
                @test ref ≈ accumulate((x,), acc, Val(:scalar), Val(2))
                @test ref ≈ accumulate((x,), acc, Val(:mask),   Val(2))
            end
        end

        @testset "compensated" begin
            for N in 100:110
                x, ref, _ = generate_sum(N, 1e10)
                @test ref == sum_oro(x)
                @test ref == sum_kbn(x)

                acc = compSumAcc(two_sum)
                @test ref == accumulate((x,), acc, Val(:scalar), Val(2))
                @test ref == accumulate((x,), acc, Val(:mask),   Val(2))
            end
        end
    end

    @testset "dot product" begin
        @testset "naive" begin
            for N in 100:110
                x = rand(N)
                y = rand(N)
                ref = dot(x, y)
                @test ref ≈ dot_naive(x, y)

                acc = dotAcc
                @test ref ≈ accumulate((x,y), acc, Val(:scalar), Val(2))
                @test ref ≈ accumulate((x,y), acc, Val(:mask),   Val(2))
            end
        end

        @testset "compensated" begin
            for N in 100:110
                x, y, ref, _ = generate_dot(N, 1e10)
                @test ref == dot_oro(x, y)

                acc = compDotAcc
                @test ref == accumulate((x,y), acc, Val(:scalar), Val(2))
                @test ref == accumulate((x,y), acc, Val(:mask),   Val(2))
            end
        end
    end
end

using BenchmarkTools

acc = compSumAcc(two_sum)

BenchmarkTools.DEFAULT_PARAMETERS.evals = 1000
println("\nsize 10_000")
print("  sum_oro"); @btime sum_oro($(rand(10_000)))
print("  sum_kbn"); @btime sum_kbn($(rand(10_000)))

BenchmarkTools.DEFAULT_PARAMETERS.evals = 10
println("\nsize 1_000_000")
print("  sum_oro"); @btime sum_oro($(rand(1_000_000)))
print("  sum_kbn"); @btime sum_kbn($(rand(1_000_000)))

println("\nsize 100_000_000")
x = rand(100_000_000)
print("  sum_oro"); @btime sum_oro($x)
print("  sum_kbn"); @btime sum_naive($x)
print("  sum    "); @btime sum($x)

println()
y = rand(100_000_000)
print("  dot_oro"); @btime dot_oro($x, $y)
print("  dot_kbn"); @btime dot_naive($x, $y)
print("  dot    "); @btime dot($x, $y)


"""
    const R = 8314.46261815324 #J/(kmol*K)

Gas constant
"""
const R = 8314.46261815324 #J/(kmol*K)
export R

"""
    const one_atm = 101325.0 #pascal

One atm
"""
const one_atm = 101325.0 #pascal
export one_atm


@testset "AffineScalingLayer" begin
    include("affineScalingLayerTest.jl")
end

@testset "DoubleSymLayer" begin
    include("doubleSymLayerTest.jl")
end

@testset "NormLayer" begin
    include("normLayerTest.jl")
end

@testset "SingleLayer" begin
    include("singleLayerTest.jl")
end