using Oceananigans.Grids: topology, min_Δx, min_Δy, min_Δz

"Returns the time-scale for advection on a regular grid across a single grid cell."
function cell_advection_timescale(u, v, w, grid)

    umax = maximum(abs, u)
    vmax = maximum(abs, v)
    wmax = maximum(abs, w)

    Δx = min_Δx(grid)
    Δy = min_Δy(grid)
    Δz = min_Δz(grid)

    return min(Δx/umax, Δy/vmax, Δz/wmax)
end

cell_advection_timescale(model) =
    cell_advection_timescale(model.velocities.u.data.parent,
                             model.velocities.v.data.parent,
                             model.velocities.w.data.parent,
                             model.grid)


function initialize_closest_weight_vectors!(parameters)
    distances = zeros(parameters.N, parameters.N)
    λ = parameters.λ
    for i in 1:parameters.N
        for j in (i+1):parameters.N
            distances[i, j] = norm(λ[i] - λ[j])
            distances[j, i] = distances[i, j]
        end
        I = sortperm(distances[i, :])
        push!(parameters.B, I[2:parameters.T+1])
    end
end

function update_reference_point!(z::Vector{Float64}, F::Vector{Float64})
    for i in 1:length(z)
        if z[i] > F[i]
            z[i] = F[i]
        end
    end
end

function update_reference_point!(z::Vector{Float64}, sol::xFgh_indiv)
    update_reference_point!(z, sol.f)
end

function update_reference_point!(z::Vector{Float64}, population)
    for sol in population
        update_reference_point!(z, sol)
    end
end

function g(fx, λ, z)
    m = -Inf
    for i in eachindex(z)
        @inbounds v = λ[i] * abs(fx[i] - z[i])
        if m < v
            m = v
        end
    end

    return m
end


import Base: intersect

"""
    intersect(line_a::AbstractLine, line_b::AbstractLine) -> AbstractVector

Compute the intersection of two lines.

The intersection of two lines is a point.

# Examples

```jldoctest
julia> intersect(Line([0, 0], [1, 0]), Line([5, 5], [0, 1]))
2-element StaticArrays.SVector{2, Float64} with indices SOneTo(2):
 5.0
 0.0
```

"""
function intersect(line_a::AbstractLine, line_b::AbstractLine)

    if !are_coplanar(line_a, line_b)
        throw(ArgumentError("The lines are not coplanar."))
    end

    if are_parallel(line_a.direction, line_b.direction)
        throw(ArgumentError("The lines are parallel."))
    end

    # Vector from line A to line B.
    vector_ab = Vector(line_a.point, line_b.point)

    # Vector perpendicular to both lines.
    vector_perpendicular = line_a.direction × line_b.direction

    num = (vector_ab × line_b.direction) ⋅ vector_perpendicular
    denom = norm(vector_perpendicular)^2

    # Vector along line A to the intersection point.
    vector_a_scaled = num / denom * line_a.direction

    return line_a.point + vector_a_scaled
end

"""
    intersect(plane::AbstractPlane, line::AbstractLine; kwargs...) -> AbstractVector

Compute the intersection point of a plane and a line.

An error is thrown if the plane and line are parallel
(i.e., the plane normal is perpendicular to the line direction).
The tolerances for this check can be passed as keyword arguments.

# Examples

```jldoctest
julia> intersect(Plane([0,0,0], [0, 0, 1]), Line([0, 0, 1], [1, 1, 1]))
3-element StaticArrays.SVector{3, Float64} with indices SOneTo(3):
 -1.0
 -1.0
  0.0

julia> intersect(Plane([0,0,0], [0, 0, 1]), Line([0, 0, 1], [1, 1, 0]))
ERROR: ArgumentError: The line and plane are parallel.
```

Tolerances for the parallel check can be passed as keyword arguments.

```
julia> plane = Plane([0, 0, 0], [0, 0, 1]);

julia> line = Line([0, 0, 1], [1, 1, 1e-3]);

julia> intersect(plane, line)
3-element StaticArrays.SVector{3, Float64} with indices SOneTo(3):
 -1000.0
 -1000.0
     0.0

julia> intersect(plane, line, atol=1e-3)
ERROR: ArgumentError: The line and plane are parallel.

julia> intersect(plane, line, rtol=1e-3)
ERROR: ArgumentError: The line and plane are parallel.
```
"""
function intersect(plane::AbstractPlane, line::AbstractLine; kwargs...)

    if are_perpendicular(line.direction, plane.normal; kwargs...)
        throw(ArgumentError("The line and plane are parallel."))
    end

    vector_plane_line = Vector(plane.point, line.point)

    num = -plane.normal ⋅ vector_plane_line
    denom = plane.normal ⋅ line.direction

    # Vector along the line to the intersection point.
    vector_line_scaled = num / denom * line.direction

    return line.point + vector_line_scaled
end

"""
    intersect(plane_a::AbstractPlane, plane_b::AbstractPlane; kwargs...) -> AbstractLine

Compute the intersection of two planes. The intersection of the planes is a line.

An error is thrown if the planes are parallel.
The tolerances for this check can be passed as keyword arguments.

# Examples

```jldoctest
julia> plane_a = Plane([0, 0, 0], [0, 0, 1]);

julia> plane_b = Plane([5, 3, 2], [1, 0, 0]);

julia> intersect(plane_a, plane_b)
Line{3, Float64}([5.0, 0.0, 0.0], [0.0, 1.0, 0.0])

julia> plane_a = Plane([0, 0, 0], [0, 0, 1]);

julia> plane_b = Plane([1, 1, 1], [0, 0, -3]);

julia> intersect(plane_a, plane_b)
ERROR: ArgumentError: The planes are parallel.

julia> plane_a = Plane([0, 0, 0], [0, 0, 1]);

julia> plane_b = Plane([1, 1, 1], [0, 1e-2, 1]);

julia> intersect(plane_a, plane_b, atol=1e-3)
ERROR: ArgumentError: The planes are parallel.
```

"""
function intersect(plane_a::AbstractPlane, plane_b::AbstractPlane; kwargs...)

    if are_parallel(plane_a.normal, plane_b.normal; kwargs...)
        throw(ArgumentError("The planes are parallel."))
    end

    array_normals_stacked = vcat(plane_a.normal', plane_b.normal')

    array_11 = 2 * Matrix(1I, 3, 3)
	array_12 = array_normals_stacked'
	array_21 = array_normals_stacked
	array_22 = zeros(2, 2)

	matrix = vcat(hcat(array_11, array_12), hcat(array_21, array_22))

    dot_a = plane_a.point ⋅ plane_a.normal
    dot_b = plane_b.point ⋅ plane_b.normal

    y = [0, 0, 0, dot_a, dot_b]

    solution = matrix \ y

    point = solution[1:3]
	direction = plane_a.normal × plane_b.normal

    return Line(point, direction)
end

"""
    intersect(circle::Circle, line::AbstractLine) -> Tuple{StaticArrays.SVector, StaticArrays.SVector}

Compute the intersection of a circle and a 2D line.

The intersection is returned as two points (which can be the same).
An error is thrown if the circle and line do not intersect.

# Examples

```jldoctest
julia> circle = Circle([0, 0], 1);

julia> intersect(circle, Line([0, 0], [1, 0]))
([-1.0, 0.0], [1.0, 0.0])

julia> intersect(circle, Line([0, 1], [1, 0]))
([0.0, 1.0], [0.0, 1.0])

julia> intersect(circle, Line([0, 2], [1, 0]))
ERROR: ArgumentError: The circle and line do not intersect.
```

"""
function intersect(circle::Circle, line::AbstractLine)

    # Two points on the line.
    point_1 = line.point
    point_2 = point_1 + unit(line.direction)

    # Translate the points on the line to mimic the circle being centered on the origin.
    point_translated_1 = point_1 - circle.point
    point_translated_2 = point_2 - circle.point

    x_1, y_1 = point_translated_1
    x_2, y_2 = point_translated_2

    d_x = x_2 - x_1
    d_y = y_2 - y_1

    # Pre-compute variables common to x and y equations.
    d_r_squared = d_x^2 + d_y^2
    determinant = x_1 * y_2 - x_2 * y_1
    discriminant = circle.radius^2 * d_r_squared - determinant^2

    if discriminant < 0
        throw(ArgumentError("The circle and line do not intersect."))
    end

    root = √discriminant

    mp = [-1, 1]  # Array to compute minus/plus.
    sign = d_y < 0 ? -1 : 1

    coords_x = (determinant * d_y .+ mp * sign * d_x * root) / d_r_squared
    coords_y = (-determinant * d_x .+ mp * abs(d_y) * root) / d_r_squared

    point_translated_a = [coords_x[1], coords_y[1]]
    point_translated_b = [coords_x[2], coords_y[2]]

    # Translate the intersection points back from origin circle to real circle.
    point_a = point_translated_a + circle.point
    point_b = point_translated_b + circle.point

    return point_a, point_b
end

"""
    intersect(sphere::Sphere, line::AbstractLine) -> Tuple{StaticArrays.SVector, StaticArrays.SVector}

Compute the intersection of a sphere and a 3D line.

The intersection is returned as two points (which can be the same).
An error is thrown if the sphere and line do not intersect.

# Examples

```jldoctest
julia> sphere = Sphere([0, 0, 0], 1);

julia> intersect(sphere, Line([0, 0, 0], [1, 0, 0]))
([-1.0, 0.0, 0.0], [1.0, 0.0, 0.0])

julia> intersect(sphere, Line([0, 0, -1], [1, 0, 0]))
([0.0, 0.0, -1.0], [0.0, 0.0, -1.0])

julia> intersect(sphere, Line([0, 1, 1], [1, 0, 0]))
ERROR: ArgumentError: The sphere and line do not intersect.
```

"""
function intersect(sphere::Sphere, line::AbstractLine)

    vector_to_line = Vector(sphere.point, line.point)
    direction_unit = unit(line.direction)

    dot = direction_unit ⋅ vector_to_line

    discriminant = dot^2 - (norm(vector_to_line)^2 - sphere.radius^2)

    if discriminant < 0
        throw(ArgumentError("The sphere and line do not intersect."))
    end

    pm = [-1, 1]  # Array to compute minus/plus.
    distances = -dot .+ √discriminant * pm

    points = line.point .+ distances' .* direction_unit

    point_a = points[:, 1]
    point_b = points[:, 2]

    return point_a, point_b
end


include("HWLC.jl")
using .HWLC
using Plots, Distributions, MAT

#Set plot parameters
gr(xlabel="F(X) (pN)", label="",ylabel="F'(X) (pN/nm)",
    grid=false,guidefont=("Arial",10),
    legend=:topleft,
    legendfont=("Arial",10),scale=:log10)

#Import experiment data for comparison
data=matread("Experimental_data/U2OS_ForceStiffnessStd.mat")
mean_experiment=vec(data["k_avg"])
std_exp=vec(data["k_err"])
fs_exp=vec(data["f_ax"])

#Set parameters
kT=4.22
Marko_Siggia=true

f_min,f_max,N_f=extrema(fs_exp)...,500
forces=exp.(range(log(f_min),log(f_max),length=N_f))

N=30        #Number of component chains
L=3000.     #Total length of assembly

#Power-law distribution for persistence lengths
slope=0.86        #Slope of stiffness-force graph
α= slope -2.     #Power law P(L_p)~L_p^(α)
minp, maxp=kT/100000,kT/9.

"""
Show individual and total curves for one assembly
"""
persistence_lengths=power_law_samples(α,N,minp,maxp)
#ls=ones(N)*L/N  #All equal length
ls=rand(Normal(L/N,L/N*0.1),N) #Alternatively sample from normal distr

total_ext,extensions=force_extensions(forces,ls,persistence_lengths,kT,Marko_Siggia)
total_stiffness=stiffnesses(forces,total_ext)[1]
individual_stiffnesses=stiffnesses(forces,extensions)

#Plot individual and total response
plot(forces[2:end],total_stiffness, label="Assembly");
plot!([20,200],[10^-1.5,(200/20)^slope*10^-1.5], color="black",label="Slope $slope");
for ind in individual_stiffnesses
    plot!(forces[2:end],ind, color="lightgrey")
end
display(current())
png("Plots/power_law_individual_responses")

"""
Sample many assemblies, and compare mean
stiffening behaviour to data
"""
n_samples=15
sim_data=zeros(length(forces)-1,n_samples)
for i in 1:n_samples
    #sample assembly
    sample_ls=rand(Normal(L/N,L/N*0.1),N) #comment if using constant lengths
    sample_ps=power_law_samples(α,N,minp,maxp)
    tot,exts=force_extensions(forces,sample_ls,sample_ps,kT,Marko_Siggia)
    sim_data[:,i]=stiffnesses(forces,tot)[1]
end

mean_sim=mean(sim_data,dims=2)
std_sim=std(sim_data,dims=2)

plot(fs_exp,mean_experiment, label="Experimental mean",legend=:topleft, color="blue");
plot!(fs_exp,mean_experiment.+std_exp, color="lightblue");
plot!(fs_exp[findall(>(0.),mean_experiment.-std_exp)],(mean_experiment.-std_exp)[findall(>(0.),mean_experiment.-std_exp)], color="lightblue");
plot!(forces[2:end],mean_sim, label="Sim mean", color="orange", ribbon=std_sim)
png("Plots/power_law_compare_data_sim")


"""
A symmetric arrowhead matrix:

A = [a_1           b_1]
    [    a_2       b_2]
    [       a_3    b_3]
    [           ⋱   ⋮ ]
    [b_1 b_2 b_3 ⋯  c ]
"""
struct SymArrow{T} <: AbstractMatrix{T}
    a::Vector{T}
    b::Vector{T}
    c::T
end

size(A::SymArrow) = length(A.a) + 1, length(A.a) + 1

function getindex(A::SymArrow{T}, i::Integer, j::Integer) where T
    n = size(A, 1)
    if i == j
        if i == n
            return A.c
        else
            return A.a[i]
        end
    elseif i == n
        return A.b[j]
    elseif j == n
        return A.b[i]
    else
        return zero(T)
    end
end

issymmetric(A::SymArrow) = true

+(A::SymArrow, B::SymArrow) = SymArrow(A.a+B.a, A.b+B.b, A.c+B.c)
-(A::SymArrow, B::SymArrow) = SymArrow(A.a-B.a, A.b-B.b, A.c-B.c)

function isincreasing(a::Vector)
    ret = true
    @inbounds for i = 2:length(a)
        ret *= a[i] ≥ a[i-1]
    end
    ret
end

function eig(A::SymArrow{T}) where T
    n = size(A, 1)
    λ = eigvals(A)
    Q = zeros(T, n, n)
    A1 = synthesize_symarrow(A, λ)
    a, b = A1.a, A1.b
    nrmb = norm(b)
    for j = 1:n-1
        λj = λ[j]
        if norm(b[j]) < nrmb*eps(T)
            println("EZ out: ", norm(b[j]) < nrmb*eps(T))
            if norm(a[j] - λj) < norm(a[j] - λ[j+1])
                Q[j,j] = one(T)
            else
                Q[j-1,j] = one(T)
            end
        else
            nrm = zero(T)
            @inbounds @simd for i = 1:n-1
                Q[i,j] = b[i]/(λj-a[i])
                !isfinite(b[i]/(λj-a[i])) && println("j = ",j,", and i = ",i," and ",b[i],"  ",λj,"  ",a[i],"  ",b[i]/(λj-a[i]))
                nrm += abs2(Q[i,j])
            end
            Q[n,j] = one(T)
            nrm += abs2(Q[n,j])
            nrm = sqrt(nrm)
            invnrm = inv(nrm)
            @inbounds @simd for i = 1:n
                Q[i,j] *= invnrm
            end
        end
    end
    λn = λ[n]
    if norm(a[n-1] - λn) < eps(T)
        println("EZ out: ", norm(a[n-1] - λn))
        Q[n-1,n] = one(T)
        #else
        #    Q[n,n] = one(T)
        #end
    else
        nrm = zero(T)
        @inbounds @simd for i = 1:n-1
            Q[i,n] = b[i]/(λn-a[i])
            !isfinite(b[i]/(λn-a[i])) && println("j = ",n,", and i = ",i," and ",b[i],"  ",λn,"  ",a[i],"  ",b[i]/(λn-a[i]))
            nrm += abs2(Q[i,n])
        end
        Q[n,n] = one(T)
        nrm += abs2(Q[n,n])
        nrm = sqrt(nrm)
        invnrm = inv(nrm)
        @inbounds @simd for i = 1:n
            Q[i,n] *= invnrm
        end
    end
    λ, Q
end

"""
Synthesize a symmetric arrow matrix by its eigenvalues and its shaft.
"""
function synthesize_symarrow(A::SymArrow{T}, λ::Vector{T}) where T
    n = size(A, 1)
    a = copy(A.a)
    b = Vector{T}(n-1)
    c = zero(T)
    @inbounds for i = 1:n-1
        c += λ[i] - a[i]
    end
    c += λ[n]

    @inbounds for i = 1:n-1
        ai = a[i]
        t = (ai-λ[1])*(λ[n]-ai)
        for j = 1:i-1
            t *= (λ[j+1]-ai)/(a[j]-ai)
        end
        for j = i+1:n-1
            t *= (λ[j]-ai)/(a[j]-ai)
        end
        b[i] = copysign(sqrt(t), A.b[i])
    end

    SymArrow(a, b, c)
end

function eigvals(A::SymArrow{T}; verbose::Bool = false) where T
    a, b, c = A.a, A.b, A.c
    @assert isincreasing(a)
    n = size(A, 1)
    n2 = n^2
    λ = zeros(T, n)

    nrmb2 = abs(dot(b, b))
    absc = abs(c)

    λk = first_initial_guess(a[1], nrmb2, c)
    δλk = λk
    while abs(δλk) > n2*eps(λk)
        δλk = first_pick_zero_update(A, λk)
        λk += isfinite(δλk) ? δλk : zero(T)
        verbose && println(1," This is the update: ", δλk," and this is the condition: ",n2*eps(λk))
    end
    λ[1] = λk

    for j = 2:n-1
        λk = (a[j]+a[j-1])/2
        δλk = λk
        while abs(δλk) > n2*eps(λk)
            δλk = pick_zero_update(A, j, λk)
            λk += isfinite(δλk) ? δλk : zero(T)
            verbose && println(j," This is the update: ", δλk," and this is the condition: ",n2*eps(λk))
        end
        λ[j] = λk
    end

    λk = last_initial_guess(a[n-1], nrmb2, c)
    δλk = λk
    verbose && println(n," This is the update: ", δλk," and this is the condition: ",n2*eps(λk))
    while abs(δλk) > n2*eps(λk)
        δλk = last_pick_zero_update(A, λk)
        λk += isfinite(δλk) ? δλk : zero(T)
        verbose && println(n," This is the update: ", δλk," and this is the condition: ",n2*eps(λk))
    end
    λ[n] = λk

    return λ
end

function eigvals_FMM(A::SymArrow{T}; verbose::Bool = false) where T
    a, b, c = A.a, A.b, A.c
    @assert isincreasing(a)
    n = size(A, 1)
    n2 = n^2
    λ = zeros(T, n)

    nrmb2 = abs(dot(b, b))
    absc = abs(c)

    λk = first_initial_guess(a[1], nrmb2, c)
    δλk = λk
    while abs(δλk) > n2*eps(λk)
        δλk = first_pick_zero_update(A, λk)
        λk += isfinite(δλk) ? δλk : zero(T)
        verbose && println(1," This is the update: ", δλk," and this is the condition: ",n2*eps(λk))
    end
    λ[1] = λk

    λmidk = (a[2:n-1]+a[1:n-2])/2
    δλk = λmidk
    while norm(δλk) > n2*eps(norm(λmidk))
        δλk = pick_zero_update_FMM(A, λmidk)
        δλk = T[isfinite(δλk) ? δλk : zero(T) for δλk in δλk]
        λmidk .+= δλk
        verbose && println("j This is the update: ", norm(δλk)," and this is the condition: ",n2*eps(norm(λk)))
    end
    λ[2:n-1] .= λmidk

    λk = last_initial_guess(a[n-1], nrmb2, c)
    δλk = λk
    while abs(δλk) > n2*eps(λk)
        δλk = last_pick_zero_update(A, λk)
        λk += isfinite(δλk) ? δλk : zero(T)
        verbose && println(n," This is the update: ", δλk," and this is the condition: ",n2*eps(λk))
    end
    λ[n] = λk

    return λ
end


test_str = "L.LL.LL.LL
LLLLLLL.LL
L.L.L..L..
LLLL.LL.LL
L.LL.LL.LL
L.LLLLL.LL
..L.L.....
LLLLLLLLLL
L.LLLLLL.L
L.LLLLL.LL"

test_list = split(test_str, '\n')

list = readlines("day11_input.txt")

function seating(list)
    n, m = length(list), length(list[1])
    extended_list = vcat("."^(m+2), ".".*list.*".", "."^(m+2))
    previous = fill('.', n+2, m+2)
    next = fill('.', n+2,m+2)
    for i in 2:n+1
        for (j, c) in enumerate(list[i-1])
            next[i, j+1] = c
        end
    end
    while next != previous
        previous = deepcopy(next)
        for j in 2:m+1
            for i in 2:n+1
                if previous[i,j] == 'L' && count(==('#'), previous[i-1:i+1, j-1:j+1]) == 0
                    next[i,j] = '#'
                elseif previous[i,j] == '#' && count(==('#'), previous[i-1:i+1, j-1:j+1]) ≥ 5
                    next[i,j] = 'L'
                end
            end
        end
    end
    return count(==('#'), next)
end

function seating_longrange(list)
    n, m = length(list), length(list[1])
    extended_list = vcat("."^(m+2), ".".*list.*".", "."^(m+2))
    previous = fill('.', n+2, m+2)
    next = fill('.', n+2,m+2)
    for i in 2:n+1
        for (j, c) in enumerate(list[i-1])
            next[i, j+1] = c
        end
    end
    regex = r"^\.*#"
    while next != previous
        previous = deepcopy(next)
        for j in 2:m+1
            for i in 2:n+1
                if previous[i,j] != '.'
                    occupied =
                        occursin(regex, join([previous[i+k,j+k] for k in 1:min(n+1-i, m+2-j)])) +
                        occursin(regex, join([previous[i-k,j+k] for k in 1:min(i-1, m+2-j)])) +
                        occursin(regex, join([previous[i+k,j-k] for k in 1:min(n+2-i, j-1)])) +
                        occursin(regex, join([previous[i-k,j-k] for k in 1:min(i-1, j-1)])) +
                        occursin(regex, join(previous[i+1:n+1,j])) +
                        occursin(regex, join(previous[i-1:-1:1,j])) +
                        occursin(regex, join(previous[i,j+1:m+2])) +
                        occursin(regex, join(previous[i,j-1:-1:1]))
                    if previous[i,j] == 'L' && occupied == 0
                        next[i,j] = '#'
                    elseif previous[i,j] == '#' && occupied ≥ 5
                        next[i,j] = 'L'
                    end
                end
            end
        end
    end
    return count(==('#'), next)
end

@show seating(test_list)
@show seating(list)
@show longrange_test_list = seating_longrange(test_list)
@show longrange_list = seating_longrange(list)
@show seating(test_list) == 37
@show seating(list) == 2296
@show longrange_test_list == 26
@show longrange_list == 2089

nothing

# Timings

```julia
julia> @btime seating(list);
  120.436 ms (901425 allocations: 114.39 MiB)

julia> @btime seating_longrange(list);
  19.891 s (45729676 allocations: 3.24 GiB)
```


"""
ZeroKernel([tr=IdentityTransform()])

Create a kernel always returning zero
"""
struct ZeroKernel{Tr} <: Kernel{Tr}
    transform::Tr
end

ZeroKernel() = ZeroKernel(IdentityTransform())

@inline kappa(κ::ZeroKernel, d::T) where {T<:Real} = zero(T)

metric(::ZeroKernel) = Delta()

"""
`WhiteKernel([tr=IdentityTransform()])`

```
    κ(x,y) = δ(x,y)
```
Kernel function working as an equivalent to add white noise.
"""
struct WhiteKernel{Tr} <: Kernel{Tr}
    transform::Tr
end

WhiteKernel() = WhiteKernel(IdentityTransform())

@inline kappa(κ::WhiteKernel,δₓₓ::Real) = δₓₓ

metric(::WhiteKernel) = Delta()

"""
`ConstantKernel([tr=IdentityTransform(),[c=1.0]])`
```
    κ(x,y) = c
```
Kernel function always returning a constant value `c`
"""
struct ConstantKernel{Tr, Tc<:Real} <: Kernel{Tr}
    transform::Tr
    c::Tc
end

params(k::ConstantKernel) = (params(k.transform),k.c)
opt_params(k::ConstantKernel) = (opt_params(k.transform),k.c)

ConstantKernel(c::Real=1.0) = ConstantKernel(IdentityTransform(),c)

ConstantKernel(t::Tr,c::Tc=1.0) where {Tr<:Transform,Tc<:Real} = ConstantKernel{Tr,Tc}(t,c)

@inline kappa(κ::ConstantKernel,x::Real) = κ.c

metric(::ConstantKernel) = Delta()


function solve(i::ElementaryPathInstance{T}, ::DynamicProgramming) where {T}
    return solve(i, BellmanFordAlgorithm())
end

function solve(i::ElementaryPathInstance{T}, ::BellmanFordAlgorithm) where {T}
    # Assumption: no positive-cost cycle in the graph.
    V = Dict{T, Float64}()
    S = Dict{T, Vector{Edge{T}}}()

    # Initialise.
    for v in vertices(i.graph)
        V[v] = -Inf
        S[v] = Edge{T}[]
    end
    V[i.src] = 0.0

    # Dynamic part.
    for _ in 1:ne(i.graph)
        changes = false # Stop the algorithm as soon as it no more makes progress.

        for e in edges(i.graph)
            u, v = src(e), dst(e)
            w = i.rewards[Edge(u, v)]

            # If using the solution to the currently explored subproblem would
            # lead to a cycle, skip it.
            if any(src(e) == v for e in S[u])
                continue
            end

            # Compute the maximum: is passing through w advantageous?
            if V[u] + w > V[v]
                V[v] = V[u] + w
                S[v] = vcat(S[u], Edge(u, v))

                changes = true
            end
        end

        if !changes
            break
        end
    end

    # Checking existence of negative-cost cycle.
    for e in edges(i.graph)
        u, v = src(e), dst(e)
        w = i.rewards[Edge(u, v)]

        if V[u] + w > V[v]
            @warn(
                "The graph contains a positive-cost cycle around edge $(u) -> $(v)."
            )
        end
    end

    return ElementaryPathSolution(i, S[i.dst], S)
end


sum_bcast(x, y) = sum(x .+ y)

@testset "special bcast" begin
    for args in [
        (rand(3, 4), rand(3)),
        (rand(3, 4), rand(3, 1)),
        (rand(3, 4), rand(1, 4)),
        (rand(3), rand(3, 4)),
        (rand(3, 1), rand(3, 4)),
        (rand(1, 4), rand(3, 4)),
    ]
        val, g = grad(sum_bcast, args...; static=false)
        for i=1:length(args)
            @test size(g[i]) == size(args[i])
        end
    end
end

mutable struct Linear{T}
    W::AbstractArray{T,2}
    b::AbstractArray{T}
end

forward(m::Linear, X) = m.W * X

loss(m::Linear, X) = sum(forward(m, X))

@testset "grad: structs" begin

    args = Any[Linear(rand(3,4), rand(3)), rand(4,5)]
    val, g = grad(loss, args...)

    @test val == loss(args...)

    play!(g.tape)
    val, g = grad(loss, args...)
    @test val == loss(args...)

end

HESS = randn(3,3)
hessian_fun(x) = x'*(HESS*x)
hessian_fun2(x) = 0.5*x'*(HESS*x)

@testset "grad: adjoint" begin
    H = randn(3,3)
    x = rand(3)
    val, g = grad(hessian_fun2, x)
    @test val isa Real
end

@testset "grad: compiled" begin
    args = Any[Linear(rand(3,4), rand(3)), rand(4,5)]

    # static vs. dynamic
    val1, g1 = grad(loss, args...; static=false)
    grad(loss, args...; static=true)
    val2, g2 = grad(loss, args...; static=true)
    @test val1 == val2
    @test g1[1][(:W,)] == g2[1][(:W,)]

    # compiled vs. non-compiled
    tape = g1.tape
    args = Any[Linear(rand(3,4), rand(3)), rand(4,5)]

    play!(tape, args...; use_compiled=true)
    last_val1 = getvalue(tape[end])
    play!(tape, args...; use_compiled=false)
    last_val2 = getvalue(tape[end])
    @test last_val1 == last_val2

    # (* -> mul!) for mixed array-scalar vars
    x = rand(3)
    val1, g1 = grad(hessian_fun, x)  # interpreted
    val2, g2 = grad(hessian_fun, x)  # compiled
    @test val1 == val2
    @test g1[1] == g2[1]
end


using Plots, Dates

func_evals=10000
n_vars=10
lb=-10ones(n_vars)
ub=15ones(n_vars)
x=lb+(ub-lb).*rand(n_vars)
x_evals=zeros(func_evals,n_vars)
x_evals[1,:]=lb
x_evals[2,:]=x
x_evals[3,:]=ub
function func(x)
    # sum(x.^2)

    n = length(x)
    1 + (1/4000)*sum(abs2, (x.+7)) - prod(cos.((x.+7) ./ sqrt.(1:n)))
end
opti_evals=zeros(func_evals)
opti_evals[1]=eval(func)(lb)
opti_evals[2]=eval(func)(x)
opti_evals[3]=eval(func)(ub)
iopt=sortperm(opti_evals[1:3])
opti_f=opti_evals[iopt[1]]
opti_x=x_evals[iopt[1],:]
x=copy(opti_x)
iter=3

while iter<func_evals
    global opti_f,opti_x,x_evals,opti_evals,iter
    for j=rand(1:n_vars)
        iter+=1; if iter>func_evals @goto escape_label end
        if iter>func_evals/3 alpha1=Int64(floor((func_evals/3)*(1-iter/func_evals)))+2 else alpha1=iter end
        # println(Dates.format(now(), "HH:MM:SS")," iter= ",iter," alpha1= ",alpha1)
        indsBEST=sortperm(opti_evals[1:iter-1])[1:alpha1-1]
        inp_x=x_evals[indsBEST,j];
        rr=minimum(inp_x)+rand()*(maximum(inp_x)-minimum(inp_x));
        x[j]=rr; fu=eval(func)(x); opti_evals[iter]=fu; x_evals[iter,:].=copy(x)

        if fu<opti_f
            opti_x.=copy(x);
            opti_f=copy(fu);
            println(Dates.format(now(), "HH:MM:SS")," iter= ",iter," otpi_f= ",opti_f," alpha1= ",alpha1)
        else
            x.=copy(vec(opti_x))
        end
    end
    @label escape_label
end
opti_evals.-=minimum(opti_evals); opti_evals./=maximum(opti_evals)
plot(sort(opti_evals,rev=true).^(1/10))

# using Optim
# res = Optim.optimize(func, fill(lb[1], n_vars), fill(ub[1], n_vars), x_evals[2,:], SAMIN(verbosity=0), Optim.Options(f_calls_limit=func_evals,store_trace =true));
# opti_x=res.minimizer; println(func(opti_x))
# res = Optim.optimize(func, fill(lb[1], n_vars), fill(ub[1], n_vars), x_evals[2,:], NelderMead(), Optim.Options(f_calls_limit=func_evals,store_trace =true))
# opti_x=res.minimizer; println(func(opti_x))
# res = Optim.optimize(func, fill(lb[1], n_vars), fill(ub[1], n_vars), x_evals[2,:], ParticleSwarm(), Optim.Options(f_calls_limit=func_evals,store_trace =true))
# opti_x=res.minimizer; println(func(opti_x))


using OrdinaryDiffEq, DiffEqSensitivity, Zygote, ForwardDiff, Test

function lotka_volterra!(du, u, p, t)
  x, y = u
  α, β, δ, γ = p
  du[1] = dx = α*x - β*x*y
  du[2] = dy = -δ*y + γ*x*y
end

# Initial condition
u0 = [1.0, 1.0]

# Simulation interval and intermediary points
tspan = (0.0, 10.0)
tsteps = 0.0:0.1:10.0

# LV equation parameter. p = [α, β, δ, γ]
p = [1.5, 1.0, 3.0, 1.0]

# Setup the ODE problem, then solve
prob = ODEProblem(lotka_volterra!, u0, tspan, p)

function loss(p)
  sol = solve(prob, Tsit5(), p=p, save_idxs=[2], saveat = tsteps, abstol=1e-14, reltol=1e-14)
  loss = sum(abs2, sol.-1)
  return loss
end

grad1 = Zygote.gradient(loss,p)[1]
grad2 = ForwardDiff.gradient(loss,p)
@test grad1 ≈ grad2


export
    radrec,
    rav2xf,
    raxisa,
    reccyl,
    recgeo,
    reclat,
    recpgr,
    recsph,
    recrad,
    removc!,
    removc,
    removd!,
    removd,
    removi!,
    removi,
    reordc,
    reordd,
    reordi,
    reordl,
    rotate,
    rotmat,
    rotvec,
    rpd,
    rquad

"""
    radrec(range, ra, dec)

Convert from range, right ascension, and declination to rectangular coordinates.

### Arguments ###

- `range`: Distance of a point from the origin
- `ra`: Right ascension of point in radians
- `dec`: Declination of point in radians

### Output ###

Returns the rectangular coordinates of the point.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/radrec_c.html)
"""
function radrec(range, ra, dec)
    rectan = Array{SpiceDouble}(undef, 3)
    ccall((:radrec_c, libcspice), Cvoid,
          (SpiceDouble, SpiceDouble, SpiceDouble, Ref{SpiceDouble}),
          range, ra, dec, rectan)
    rectan
end

"""
    rav2xf(rot, av)

Determine a state transformation matrix from a rotation matrix and the angular
velocity of the rotation.

### Arguments ###

- `rot`: Rotation matrix
- `av`: Angular velocity vector

### Output ###

Returns state transformation matrix associated with `rot` and `av`.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/rav2xf_c.html)
"""
function rav2xf(rot, av)
    @checkdims 3 3 rot
    @checkdims 3 av
    xform = Array{SpiceDouble}(undef, 6, 6)
    ccall((:rav2xf_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          permutedims(rot), av, xform)
    permutedims(xform)
end

"""
    raxisa(matrix)

Compute the axis of the rotation given by an input matrix and the angle of the rotation
about that axis.

### Arguments ###

- `matrix`: A 3x3 rotation matrix

### Output ###

- `axis`: Axis of the rotation
- `angle`: Angle through which the rotation is performed

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/raxisa_c.html)
"""
function raxisa(matrix)
    @checkdims 3 3 matrix
    axis = Array{SpiceDouble}(undef, 3)
    angle = Ref{SpiceDouble}()
    ccall((:raxisa_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          permutedims(matrix), axis, angle)
    handleerror()
    axis, angle[]
end

"""
    reccyl(rectan)

Convert from rectangular to cylindrical coordinates.

### Arguments ###

- `rectan`: Rectangular coordinates of a point

### Output ###

- `r`: Distance of the point from the Z axis
- `lon`: Angle (radians) of the point from the XZ plane
- `z`: Height of the point above the XY plane

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/reccyl_c.html)
"""
function reccyl(rectan)
    @checkdims 3 rectan
    r = Ref{SpiceDouble}()
    lon = Ref{SpiceDouble}()
    z = Ref{SpiceDouble}()
    ccall((:reccyl_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          rectan, r, lon, z)
    r[], lon[], z[]
end

"""
    recgeo(rectan, re, f)

Convert from rectangular coordinates to geodetic coordinates.

### Arguments ###

- `rectan`: Rectangular coordinates of a point
- `re`: Equatorial radius of the reference spheroid
- `f`: Flattening coefficient

### Output ###

- `lon`: Geodetic longitude of the point (radians)
- `lat`: Geodetic latitude  of the point (radians)
- `alt`: Altitude of the point above reference spheroid

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/recgeo_c.html)
"""
function recgeo(rectan, re, f)
    @checkdims 3 rectan
    lon = Ref{SpiceDouble}()
    lat = Ref{SpiceDouble}()
    alt = Ref{SpiceDouble}()
    ccall((:recgeo_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, SpiceDouble, SpiceDouble,
           Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          rectan, re, f, lon, lat, alt)
    handleerror()
    lon[], lat[], alt[]
end

"""
    reclat(rectan)

Convert from rectangular coordinates to latitudinal coordinates.

### Arguments ###

- `rectan`: Rectangular coordinates of a point

### Output ###

Returns a tuple consisting of:

- `rad`: Distance of the point from the origin
- `lon`: Planetographic longitude of the point (radians)
- `lat`: Planetographic latitude of the point (radians)

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/reclat_c.html)
"""
function reclat(rectan)
    @checkdims 3 rectan
    lon = Ref{SpiceDouble}()
    lat = Ref{SpiceDouble}()
    rad = Ref{SpiceDouble}()
    ccall((:reclat_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          collect(rectan), rad, lon, lat)
    rad[], lon[], lat[]
end

"""
    recpgr(body, rectan, re, f)

Convert rectangular coordinates to planetographic coordinates.

### Arguments ###

- `body`: Body with which coordinate system is associated
- `rectan`: Rectangular coordinates of a point
- `re`: Equatorial radius of the reference spheroid
- `f`: flattening coefficient

### Output ###

- `lon`: Planetographic longitude of the point (radians).
- `lat`: Planetographic latitude of the point (radians).
- `alt`: Altitude of the point above reference spheroid.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/recpgr_c.html)
"""
function recpgr(body, rectan, re, f)
    @checkdims 3 rectan
    lon = Ref{SpiceDouble}()
    lat = Ref{SpiceDouble}()
    alt = Ref{SpiceDouble}()
    ccall((:recpgr_c, libcspice), Cvoid,
          (Cstring, Ref{SpiceDouble}, SpiceDouble, SpiceDouble,
           Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          body, rectan, re, f, lon, lat, alt)
    handleerror()
    lon[], lat[], alt[]
end

"""
    recrad(rectan)

Convert rectangular coordinates to range, right ascension, and
declination.

### Arguments ###

- `rectan`: Rectangular coordinates of a point

### Output ###

Return the tuple `(range, ra, dec)`.

- `range`: Distance of the point from the origin
- `ra`: Right ascension in radians
- `dec`: Declination in radians

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/recrad_c.html)
"""
function recrad(rectan)
    @checkdims 3 rectan
    range = Ref{SpiceDouble}()
    ra = Ref{SpiceDouble}()
    dec = Ref{SpiceDouble}()
    ccall((:recrad_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          rectan, range, ra, dec)
    range[], ra[], dec[]
end

"""
    recsph(rectan)

Convert from rectangular coordinates to spherical coordinates.

### Arguments ###

- `rectan`: Rectangular coordinates of a point

### Output ###

- `r     `: Distance of the point from the origin
- `colat `: Angle of the point from the Z-axis in radian
- `lon   `: Longitude of the point in radians

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/recsph_c.html)
"""
function recsph(rectan)
    @checkdims 3 rectan
    r = Ref{SpiceDouble}()
    colat = Ref{SpiceDouble}()
    lon = Ref{SpiceDouble}()
    ccall((:recsph_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          rectan, r, colat, lon)
    r[], colat[], lon[]
end

"""
    removc!(set, item)

Remove an item from a character set.

### Arguments ###

- `set`: A set
- `item`: Item to be removed

### Output ###

Returns the updated set.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/removc_c.html)
"""
function removc!(set, item)
    data = set.data
    GC.@preserve data begin
        ccall((:removc_c, libcspice), Cvoid, (Cstring, Ref{Cell{SpiceChar}}), item, set.cell)
    end
    set
end

@deprecate removc removc!

"""
    removd!(set, item)

Remove an item from a double set.

### Arguments ###

- `set`: A set
- `item`: Item to be removed

### Output ###

Returns the updated set.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/removd_c.html)
"""
function removd!(set, item)
    data = set.data
    GC.@preserve data begin
        ccall((:removd_c, libcspice), Cvoid, (SpiceDouble, Ref{Cell{SpiceDouble}}), item, set.cell)
    end
    set
end

@deprecate removd removd!

"""
    removi!(set, item)

Remove an item from a character set.

### Arguments ###

- `set`: A set
- `item`: Item to be removed

### Output ###

Returns the updated set.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/removi_c.html)
"""
function removi!(set, item)
    data = set.data
    GC.@preserve data begin
        ccall((:removi_c, libcspice), Cvoid, (SpiceInt, Ref{Cell{SpiceInt}}), item, set.cell)
    end
    set
end

@deprecate removi removi!

function _reordc(iorder, array)
    iorder_ = SpiceInt.(iorder .- 1)
    array_, m, n = chararray(array)
    ccall((:reordc_c, libcspice), Cvoid,
          (Ref{SpiceInt}, SpiceInt, SpiceInt, Ref{SpiceChar}),
          iorder_, m, n, array_)
    chararray_to_string(array_, m)
end

@deprecate reordc(iorder, array) array[iorder]

"""
    reordc(iorder, array)

!!! warning "Deprecated"
    Use `array[iorder]` instead.
"""
reordc

function _reordd(iorder, array)
    iorder_ = SpiceInt.(iorder .- 1)
    n = length(iorder)
    ccall((:reordd_c, libcspice), Cvoid,
          (Ref{SpiceInt}, SpiceInt, Ref{SpiceDouble}),
          iorder_, n, array)
    array
end

@deprecate reordd(iorder, array) array[iorder]

"""
    reordd(iorder, array)

!!! warning "Deprecated"
    Use `array[iorder]` instead.
"""
reordd

function _reordi(iorder, array)
    iorder_ = SpiceInt.(iorder .- 1)
    array_ = SpiceInt.(array)
    n = length(iorder)
    ccall((:reordi_c, libcspice), Cvoid,
          (Ref{SpiceInt}, SpiceInt, Ref{SpiceInt}),
          iorder_, n, array_)
    Int.(array_)
end

@deprecate reordi(iorder, array) array[iorder]

"""
    reordi(iorder, array)

!!! warning "Deprecated"
    Use `array[iorder]` instead.
"""
reordi

function _reordl(iorder, array)
    iorder_ = SpiceInt.(iorder .- 1)
    array_ = SpiceBoolean.(array)
    n = length(iorder)
    ccall((:reordl_c, libcspice), Cvoid,
          (Ref{SpiceInt}, SpiceInt, Ref{SpiceBoolean}),
          iorder_, n, array_)
    Bool.(array_)
end

@deprecate reordl(iorder, array) array[iorder]

"""
    reordl(iorder, array)

!!! warning "Deprecated"
    Use `array[iorder]` instead.
"""
reordl

function _repmc(input, marker, value)
    lenout = length(input) - length(marker) + length(value) + 1
    out = Array{SpiceChar}(undef, lenout)
    ccall((:repmc_c, libcspice), Cvoid,
          (Cstring, Cstring, Cstring, SpiceInt, Ref{SpiceChar}),
          input, marker, value, lenout, out)
    handleerror()
    chararray_to_string(out)
end

@deprecate repmc(input, marker, value) replace(input, marker=>value)

"""
    repmc(input, marker, value)

!!! warning "Deprecated"
    Use `replace(input, marker=>value)` instead.
"""
repmc

@deprecate repmct replace

"""
    repmct

!!! warning "Deprecated"
    Use `replace` instead.
"""
repmct

@deprecate repmd replace

"""
    repmd

!!! warning "Deprecated"
    Use `replace` instead.
"""
repmd

@deprecate repmf replace

"""
    repmf

!!! warning "Deprecated"
    Use `replace` instead.
"""
repmf

@deprecate repmi replace

"""
    repmi

!!! warning "Deprecated"
    Use `replace` instead.
"""
repmi

@deprecate repmot replace

"""
    repmot

!!! warning "Deprecated"
    Use `replace` instead.
"""
repmot

"""
    rotate(angle, iaxis)

Calculate the 3x3 rotation matrix generated by a rotation
of a specified angle about a specified axis. This rotation
is thought of as rotating the coordinate system.

### Arguments ###

- `angle`: Angle of rotation (radians)
- `iaxis`: Axis of rotation (X=1, Y=2, Z=3)

### Output ###

Returns rotation matrix associated with `angle` and `iaxis`.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/rotate_c.html)
"""
function rotate(angle, iaxis)
    r = Matrix{SpiceDouble}(undef, 3, 3)
    ccall((:rotate_c, libcspice), Cvoid, (SpiceDouble, SpiceInt, Ref{SpiceDouble}), angle, iaxis, r)
    permutedims(r)
end

"""
    rotmat(m1, angle, iaxis)

Applies a rotation of `angle` radians about axis `iaxis` to a matrix `m1`. This rotation is thought
of as rotating the coordinate system.

### Arguments ###

- `m1`: Matrix to be rotated
- `angle`: Angle of rotation (radians)
- `iaxis`: Axis of rotation (X=1, Y=2, Z=3)

### Output ###

Returns the resulting rotated matrix.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/rotmat_c.html)
"""
function rotmat(m1, angle, iaxis)
    @checkdims 3 3 m1
    mout = Array{SpiceDouble}(undef, 3, 3)
    ccall((:rotmat_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, SpiceDouble, SpiceInt, Ref{SpiceDouble}),
          permutedims(m1), angle, iaxis, mout)
    permutedims(mout)
end

"""
    rotvec(v1, angle, iaxis)

Transform a vector to a new coordinate system rotated by `angle` radians about axis `iaxis`.
This transformation rotates `v1` by `-angle` radians about the specified axis.

### Arguments ###

- `v1`: Vector whose coordinate system is to be rotated
- `angle`: Angle of rotation in radians
- `iaxis`: Axis of rotation (X=1, Y=2, Z=3)

### Output ###

Returns the resulting vector expressed in the new coordinate system.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/rotvec_c.html)
"""
function rotvec(v1, angle, iaxis)
    @checkdims 3 v1
    vout = Array{SpiceDouble}(undef, 3)
    ccall((:rotvec_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, SpiceDouble, SpiceInt, Ref{SpiceDouble}),
          v1, angle, iaxis, vout)
    vout
end

function _rpd()
    ccall((:rpd_c, libcspice), SpiceDouble, ())
end

@deprecate rpd() deg2rad(1.0)

"""
    rpd()

!!! warning "Deprecated"
    Use `deg2rad` instead.
"""
rpd

"""
    rquad(a, b, c)

Find the roots of a quadratic equation.

### Arguments ###

- `a`: Coefficient of quadratic term
- `b`: Coefficient of linear term
- `c`: Constant

### Output ###

- `root1`: Root built from positive discriminant term
- `root2`: Root built from negative discriminant term

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/rquad_c.html)
"""
function rquad(a, b, c)
    root1 = Array{SpiceDouble}(undef, 2)
    root2 = Array{SpiceDouble}(undef, 2)
    ccall((:rquad_c, libcspice), Cvoid,
          (SpiceDouble, SpiceDouble, SpiceDouble, Ref{SpiceDouble}, Ref{SpiceDouble}),
          a, b, c, root1, root2)
    handleerror()
    root1, root2
end


function e010(N)
    return sum_of_primes_below(N)
end

function solution010()
    N = 2000000
    answer = e010(N)
    println("The answer to problem 010 is $answer")
end

function sum_of_primes_below(n::Integer)
    s = 0
    for x in Channel(c -> get_primes_below(c,n))
        s += x
    end
    return s
end


using DSGE
using BenchmarkTools, JLD

path = dirname(@__FILE__)
filepath = "$path/../../reference/BondLabor/jacobian.jld"

# Benchmark current function
m = BondLabor()
steadystate!(m)
trial = @benchmark DSGE.jacobian($m) gcsample = true

# # Optionally over-write the existing reference trial
# write_ref_trial(trial, "jacobian", filepath = filepath)

# Compute time differential
print_all_benchmarks(trial, filepath, "jacobian")


## Test ARX model
function arx_mdl(y, u)
    return (0.5*y[1] - 0.5*u[1])
end

@testset "Test on ARX model" begin
    yterms0 = [1]
    uterms0 = [1]
    u = NarmaxLasso.generate_random_input(100, 5)
    y = NarmaxLasso.simulate(u, arx_mdl, yterms0, uterms0;
                             σv=0)
    mdl = NarmaxLasso.generate_all(NarmaxLasso.NarmaxRegressors,
                                   NarmaxLasso.Monomial,
                                   2, 2, 0, 1)
    result = NarmaxLasso.narmax_lasso(y, u, mdl; nλ=5, λ_min_ratio=1e-6)

    @test result.β ≈ [0.0         -0.0398   0.0        0.1998   0.24836;
                      -1.552e-16  -0.4788  -0.49886  -0.4998  -0.499996;
                      0.0          0.0       0.0       -0.1996  -0.2484;
                      0.0          0.43622   0.49973   0.89964   0.99671] rtol=1e-4

    if NarmaxLasso.glmnet_loaded
        result_glmnet = NarmaxLasso.narmax_lasso(y, u, mdl;
            intercept=false, standardize=false, use_glmnet=true, nlambda=5)
        @test result_glmnet.β ≈ [0.0 -0.13385 -0.010741 0.0 0.0349759;
                                 0.0 -0.432755 -0.493341 -0.498908 -0.499592;
                                 0.0 0.0 0.0 0.0 -0.0340471;
                                 0.0 0.289839 0.481891 0.499722 0.569338] rtol=1e-4
    else
        @test_throws(ArgumentError,
         result_glmnet = NarmaxLasso.narmax_lasso(y, u, mdl; use_glmnet=true))
    end
end

## Test ARMAX model
function armax_mdl(y, u, v)
    return (0.5*y[1] - 0.5*u[1] + 0.5*v[1])
end

@testset "Test on ARMAX model" begin
    yterms0 = [1]
    uterms0 = [1]
    vterms0 = [1]
    u = NarmaxLasso.generate_random_input(1000, 5)
    y = NarmaxLasso.simulate(u, armax_mdl, yterms0, uterms0, vterms0;
                             σv=1.0, seed=1)
    mdl = NarmaxLasso.generate_all(NarmaxLasso.NarmaxRegressors,
                                   NarmaxLasso.Monomial,
                                   1, 1, 1, 1)
    result = NarmaxLasso.narmax_lasso(y, u, mdl; nλ=5)
    @test result.β ≈ [0.0  -0.204043  -0.457963  -0.4847    -0.487381;
                      0.0   0.555936   0.478094   0.469134   0.468238;
                      0.0   0.240684   0.514044   0.546466   0.549734] rtol=1e-3
end


# This file was generated, do not modify it. # hide
figure(figsize=(8,6))
hist(res, bins=30, density=true, color="green")

xx = range(-1100, 1100, length=100)
ndfit = D.fit_mle(D.Normal, res)
lfit  = D.fit_mle(D.Laplace, res)

plot(xx, pdf.(ndfit, xx), lw=3, color="orange", label="Normal fit")
plot(xx, pdf.(lfit, xx), lw=3, color="magenta", label="Laplace fit")

legend(fontsize=12)

xticks(fontsize=12); yticks(fontsize=12)
xlabel("Residual (ŷ - y)", fontsize=14); ylabel("Density", fontsize=14)
xlim([-1100, 1100])

savefig(joinpath(@OUTPUT, "ISL-lab-6-g4.svg")) # hide


# This file is part of Maxima.jl. It is licensed under the MIT license
#   Copyright (c) 2016 Nathan Smith

export sum,
       integrate,
       risch,
       diff,
       limit,
       taylor,
       product,
       changevar,
       lbfgs,
       minimum,
       laplace,
       ilt

import Base: sum,
             minimum

function integrate(m::MExpr, s)
    MExpr("integrate($m, $s)") |> mcall
end

"""
    integrate(f::T, x) where T

Evaluate the indefinite integral
```math
\\int f(x) dx
```
## Examples

```julia
julia> integrate(:(sin(x)), :x)
:(-cos(x))
```

## See also

* [`risch`](@refs Maxima.risch)
"""
function integrate(expr::T, s) where T
    m = MExpr(expr)
    out = integrate(m, s)
    convert(T, out)
end

function integrate(m::MExpr, s, lower, upper)
    MExpr("integrate($m, $s, $lower, $upper)") |> mcall
end

"""
    integrate(f::T, x, a, b) where T

Evaluate the definite integral of ``f`` with respect to ``x`` from ``a`` to ``b``.

```math
    \\int_a^b f(x) dx
```
"""
function integrate(expr::T, s, lower, upper) where T
    m = MExpr(expr)
    out = integrate(m, s, lower, upper)
    convert(T, out)
end

function risch(m::MExpr, var)
    MExpr("risch($m, $var)") |> mcall
end

"""
    risch(f::T, x) where T

Compute the indefinite integral of ``f`` with respect to ``x`` using the Risch algorithm

## See also
* [`integrate`](@ref Maxima.integrate)
"""
function risch(exp::T, var) where T
    m = MExpr(exp)
    convert(T, risch(m, var))
end

function diff(m::MExpr, s)
    MExpr("diff($m, $s)") |> mcall
end

"""
    diff(f, x) where T

Take the derivative of ``f`` with respect to ``x``

```math
\\frac{\\partial f(x, ...)}{\\partial x}
```
"""
function diff(exp::T, s) where T
    m = MExpr(exp)
    out = diff(m, s)
    convert(T, out)
end

function diff(m::MExpr, s, order::Integer)
    if order < 0
        error("Order of derivative must be positive integer")
    end
    MExpr("diff($m, $s, $order)") |> mcall
end

"""
    diff(f, x, n) where T

Take the nth order derivative of ``f`` with respect to ``x``.

```math
\\frac{\\partial^n f(x,...)}{\\partial x^n}
```
"""
function diff(exp::T, s, order::Integer) where T
    m = MExpr(exp)
    out = diff(m, s, order)
    convert(T, out)
end

function limit(m::MExpr, x, a)
    MExpr("limit($m, $x, $a)") |> mcall
end

"""
    limit(f, x, a) where T

Take the limit as ``x`` approaches ``a`` of ``f(x)``

```math
lim_{x \\rightarrow a} f(x)
```
"""
function limit(exp::T, x, a) where T
    m = MExpr(exp)
    out = limit(m, x, a)
    convert(T, out)
end

"""
    limit(f, x, a, side) where T

Take the left or right sided limit as ``x`` approaches ``a`` of ``f(x)``.

`side` may be either `\"plus\"` or `\"minus\"` denoting the right and left sided limit respectively
"""
function limit(m::MExpr, x, a, side)
    if "$side" != "plus" && "$side" != "minus"
        error("Side of limit must be \"plus\" or \"minus\"")
    end
    MExpr("limit($m, $x, $a, $side)") |> mcall
end

function limit(exp::T, x, a, side) where T
    m = MExpr(exp)
    out = limit(m, x, a, side)
    convert(T, out)
end

function sum(m::MExpr, k, start, finish)
    MExpr("sum($m, $k, $start, $finish), simpsum") |> mcall
end

"""
    sum(f::T, k, start, finish) where T

Compute the sum,

```math
    \\sum_{k=start}^{finish} f(k),
```
simplifying the sum if possible.
## Examples
```julia
julia> sum(m"1/n^2", :n, 1, "inf")

                                        2
                                     %pi
                                     ----
                                      6

```
"""
function sum(exp::T, k, start, finish) where T
    sumexp = Meta.parse("sum($exp, $k, $start, $finish)")
    m = MExpr(sumexp)
    out = mcall(MExpr("$m, simpsum"))
    convert(T, out)
end

function taylor(m::MExpr, x, x0, order::Integer)
    if order < 0
        error("Order of taylor expansion must be ≥ 0")
    end
    MExpr("taylor($m, $x, $x0, $order)") |> mcall
end

"""
    taylor(f::T, x, x0, n) where T

Taylor expand ``f(x)`` around ``x_0`` to nth order
"""
function taylor(exp::T, x, x0, order::Integer) where T
    m = MExpr(exp)
    out = taylor(m, x, x0, order)
    convert(T, out)
end

function product(m::MExpr, k, start, finish)
    MExpr("product($m, $k, $start, $finish), simpsum") |> mcall
end

"""
    product(f::T, k, start, finish) where T

Compute the product,

```math
    \\prod_{k=start}^{finish} f(k),
```
simplifying the product if possible.
"""
function product(exp::T, k, start, finish) where T
    sumexp = parse("product($exp, $k, $start, $finish)")
    convert(T, sumexp |> MExpr |> mcall)
end

function changevar(integral::MExpr, transform::MExpr, new, old)
    "changevar($integral, $transform, $new, $old)" |> MExpr |> mcall
end

function lbfgs(func::MExpr, var, guess; xtol = 1e-8)
    temp = "lbfgs($func, [$var], [$guess], $xtol)" |> MExpr |> mcall
    "rhs($temp[1])" |> MExpr |> mcall
end

function minimum(func::MExpr, var, guess; xtol = 1e-8)
    lbfgs(func, var, guess; xtol = xtol)
end

function laplace(func::MExpr, oldvar, newvar)
    "laplace($func, $oldvar, $newvar)" |> MExpr |> mcall
end

"""
    laplace(f::T, t, s) where T

Compute the Laplace transform of ``f(t)`` where ``s`` is the new variable.

```math
\\mathcal{L}\\lbrace f \\rbrace (s) = \\int_0^\\infty f(t) e^{-st} dt
```
"""
function laplace(func::T, oldvar, newvar) where T
    m = MExpr(func)
    convert(T, "laplace($m, $oldvar, $newvar)" |> MExpr |> mcall)
end

function ilt(func::MExpr, oldvar, newvar)
    "ilt($func, $oldvar, $newvar" |> MExpr |> mcall
end

"""
    ilt(f::T, s, t) where T

Compute the inverse Laplace transform of ``f(s)``.

``t`` is the new variable and ``s`` is the old variable.
"""
function ilt(func::T, oldvar, newvar) where T
    m = MExpr(func)
    convert(T, "ilt($m, $oldvar, $newvar)" |> MExpr |> mcall)
end


using Test
using IncrementalInference
using Random

##

@testset "test basic multihypo" begin

## A simple multihypo example

Random.seed!(42) # The answer to reproducable noise

fg = LocalDFG(solverParams=SolverParams(graphinit=false, gibbsIters=5, spreadNH=5.0))

pRight = 0.99
pWrong = 0.01
pr_noise = 0.01
od_noise = 0.1
lm_noise = 0.01

# true positions
# x0 at 0
# x1 at 1
# l1 at 1
# l2 at 2

#x0 prior
addVariable!(fg, :x0, ContinuousScalar)
prpo = Normal(0.0, pr_noise)
addFactor!(fg, [:x0], Prior(Normal(rand(prpo), pr_noise)))

#l1 and l2
addVariable!(fg, :l1, ContinuousScalar, tags=[:LANDMARK])
addVariable!(fg, :l2, ContinuousScalar, tags=[:LANDMARK])

#x0 to l1 or l2
p2ln = Normal(1.0, lm_noise)
p2p = LinearRelative(Normal(rand(p2ln), lm_noise))
addFactor!(fg, [:x0; :l1; :l2], p2p, multihypo = [1, pRight, pWrong])
# addFactor!(fg, [:x0; :l1], p2p) #this one used for sanity check

#x0 to x1
addVariable!(fg, :x1, ContinuousScalar)
pp = Normal(1.0, od_noise)
addFactor!(fg, [:x0,:x1], LinearRelative(Normal(rand(pp), od_noise)))

#x1 to l1 or l2
p2ln = Normal(0.0, lm_noise)
p2p = LinearRelative(Normal(rand(p2ln), lm_noise))
addFactor!(fg, [:x1; :l1; :l2], p2p, multihypo = [1, pRight, pWrong])
# addFactor!(fg, [:x1; :l1], p2p) #this one used for sanity check

#x1 to l2 or l1
p2ln = Normal(1.0, lm_noise)
p2p = LinearRelative(Normal(rand(p2ln), lm_noise))
addFactor!(fg, [:x1; :l2; :l1], p2p, multihypo = [1, pRight, pWrong])
# addFactor!(fg, [:x1; :l2], p2p) #this one used for sanity check

##

# prescribe an elimination order to get a single clique
eo = [:l2,:x1,:x0,:l1]
# fg.solverParams.graphinit=true
smtasks = Task[]
tree = solveTree!(fg, eliminationOrder=eo) #, smtasks=smtasks, recordcliqs=ls(fg));

# hists = fetchCliqHistoryAll!(smtasks)

# plotKDE(fg, ls(fg))

##

@test isapprox(DFG.getPPESuggested(fg, :x0)[], 0, atol = 0.2)
@test isapprox(DFG.getPPESuggested(fg, :x1)[], 1, atol = 0.2)
@test isapprox(DFG.getPPESuggested(fg, :l1)[], 1, atol = 0.2)

L2 = getBelief(fg, :l2)
npts = length(getPoints(L2))
pts = [2.0.+0.1*randn(1) for _ in 1:npts]
L2_ = manikde!(ContinuousScalar, pts)

# test that there is at least a mode present
@test mmd(L2_, L2, ContinuousScalar) < 1e-3
# @test isapprox(DFG.getPPESuggested(fg, :l2)[], 2, atol = 0.2)

##

end

@testset "test multihypo chain example (see #462)..." begin

##

l1 = -10.0
l2 = +10.0
lnoise = 1.0
x1 = 0
x2 = 0
Onoise = 0.1

fg = initfg()

addVariable!(fg, :x1, ContinuousScalar)
addVariable!(fg, :x2, ContinuousScalar)
addVariable!(fg, :l1, ContinuousScalar)
addVariable!(fg, :l1_0, ContinuousScalar)
addVariable!(fg, :l2, ContinuousScalar)
addVariable!(fg, :l2_0, ContinuousScalar)

# priors on two landmarks only
addFactor!(fg, [:l1], Prior(Normal(l1, lnoise)))
addFactor!(fg, [:l2], Prior(Normal(l2, lnoise)))

# relative constraints
addFactor!(fg, [:x1;:l1;:l1_0], LinearRelative(Normal(l1-x1, lnoise)), multihypo=[1;1/2;1/2])
addFactor!(fg, [:x2;:l2;:l2_0], LinearRelative(Normal(l2-x2, lnoise)), multihypo=[1;1/2;1/2])
addFactor!(fg, [:x1;:x2], LinearRelative(Normal(0, Onoise)))

tree = solveTree!(fg)

# drawTree(tree, show=true)

# expect x1 x2 to have at least one mode at 0

@test getPPE(fg, :x1).suggested[1] - x1 |> abs < 1.2
@test getPPE(fg, :x2).suggested[1] - x2 |> abs < 1.2

@test getPPE(fg, :l1).suggested[1] - l1 |> abs < 1.2
@test getPPE(fg, :l2).suggested[1] - l2 |> abs < 1.2

# l1_0, l2_0 should be nearby around l1 and l2, but cannot confirm 100%
@test getPPE(fg, :l1_0).suggested[1] - l1 |> abs < 10
@test getPPE(fg, :l2_0).suggested[1] - l2 |> abs < 10

##

end

# using RoMEPlotting
# Gadfly.set_default_plot_size(35cm,25cm)
#
# plotKDE(fg, [:l1;:l2])
# plotKDE(fg, [:l1_0;:l2_0])
# plotKDE(fg, [:x1;:x2])

#


module Collatz

using Base.Checked

export collatz

function collatz(n::Integer)
    k = 0
    while n > 1
        n = iseven(n) ? n >> 1 : checked_add(checked_mul(3,n), 1)
        k += 1
    end
    return k
end

end


module MyPkg

# Write your package code here.
function add_nums(a,b)
    a+b
end
function Simpson(f, a, b)
    # Simpson Formula
    # (b-a)/6 * (f(a) + 4*f((a+b)/2) + f(b))
    h = (b - a)/2
    x = a:h:b
    sum((x[3] - x[1])/6*(f(x[3]) + 4*f(x[2]/2) + f(x[1])))
end

end


weight = 1.0e-6

cost_function = build_loss_objective(prob1,Tsit5(),L2Loss(t,data,colloc_grad=colloc_grad(t, data)),
                                     maxiters=10000,verbose=false)
result = Optim.optimize(cost_function, 1., 2.)
@test result.minimizer ≈ 1.5 atol=3e-1

cost_function = build_loss_objective(prob2,Tsit5(),L2Loss(t,data,
  differ_weight=weight,data_weight=weight,colloc_grad=colloc_grad(t, data)),
                                     maxiters=10000,verbose=false)
result = Optim.optimize(cost_function, [1.3,2.8], Optim.BFGS())
@test result.minimizer ≈ [1.5;3.0] atol=3e-1

cost_function = build_loss_objective(prob3,Tsit5(),L2Loss(t,data,
  differ_weight=weight,colloc_grad=colloc_grad(t, data)), maxiters=10000,verbose=false)
result = Optim.optimize(cost_function, [1.3,0.8,2.8,1.2], Optim.BFGS())
@test result.minimizer ≈ [1.5,1.0,3.0,1.0] atol=3e-1

cost_function = build_loss_objective(prob1,Tsit5(),L2Loss(t,data,
  data_weight=weight,colloc_grad=colloc_grad(t, data)),maxiters=10000,verbose=false)
result = Optim.optimize(cost_function, 1., 2)
@test result.minimizer ≈ 1.5 atol=3e-1


"""
Original publication:
A. Ghasemi, M. T. Manzuri, H. R. Rabiee, M. H.
Rohban, and S. Haghiri. Active one-class learning by
kernel density estimation. In 2011 IEEE International
Workshop on Machine Learning for Signal Processing,
pages 1–6, Sept 2011.
"""
struct MinimumLossQs <: DataBasedQs
    p_x::Array{Float64, 1}
    bw_method::Union{String, U} where U <: Real
    p_inlier::Float64
    function MinimumLossQs(x::Array{T, 2}; bw_method="scott", p_inlier=nothing) where T <: Real
        ((p_inlier === nothing) || !(0 <= p_inlier <= 1)) && throw(ArgumentError("Invalid inlier probability $(p_inlier)."))
        return new(multi_kde(x, bw_method)(x), bw_method, p_inlier)
    end
end

function qs_score(qs::MinimumLossQs, x::Array{T, 2}, labels::Dict{Symbol, Array{Int, 1}})::Array{Float64, 1} where T <: Real
    haskey(labels, :Lin) || throw(MissingLabelTypeException(:Lin))
    s_t_a(u) = leave_out_one_cv_kde(hcat(x[:, labels[:Lin]], u), qs.bw_method)
    s_t_b(u) = haskey(labels, :Lout) ? mean(multi_kde(hcat(x[:, labels[:Lin]], u), qs.bw_method)(x[:, labels[:Lout]])) : 0
    s_o_a = leave_out_one_cv_kde(x[:, labels[:Lin]], qs.bw_method)
    s_o_b(u) = haskey(labels, :Lout) ? mean(multi_kde(x[:, labels[:Lin]], qs.bw_method)(hcat(x[:, labels[:Lout]], u))) : 0
    s_t(u) = s_t_a(u) - s_t_b(u)
    s_o(u) = s_o_a - s_o_b(u)
    p_inlier = length(labels[:Lin]) / size(x, 2)
    s(u) = qs.p_inlier * s_t(u) + (1 - qs.p_inlier) * s_o(u)
    return [s(x[:, i]) for i in 1:size(x, 2)]
end


# A collection of extensions to the DomainSets package.

using DomainSets: inverse_map, forward_map

###########################
# Applying broadcast to in
###########################

# Intercept a broadcasted call to indomain. We assume that the user wants evaluation
# in a set of points (which we call a grid), rather than in a single point.
# TODO: the user may want to evaluate a single point in a sequence of domains...
broadcast(::typeof(in), grid, d::Domain) = indomain_broadcast(grid, d)

# # Default methods for evaluation on a grid: the default is to call eval on the domain with
# # points as arguments. Domains that have faster grid evaluation routines may define their own version.
indomain_broadcast(grid, d::Domain) = indomain_broadcast!(BitArray(undef, size(grid)), grid, d)
# TODO: use BitArray here

function indomain_broadcast!(result, grid::AbstractGrid, domain::Domain)
    for (i,x) in enumerate(grid)
        result[i] = DomainSets.in(x, domain)
    end
    result
end

function indomain_broadcast(grid::AbstractGrid, d::UnionDomain)
    z = indomain_broadcast(grid, element(d,1))
    for i in 2:numelements(d)
        z = z .| indomain_broadcast(grid, element(d,i))
    end
    z
end

function indomain_broadcast(grid::AbstractGrid, d::IntersectionDomain)
    z = indomain_broadcast(grid, element(d,1))
    for i in 2:numelements(d)
        z = z .& indomain_broadcast(grid, element(d,i))
    end
    z
end

function indomain_broadcast(grid::AbstractGrid, d::DifferenceDomain)
    z1 = indomain_broadcast(grid, d.d1)
    z2 = indomain_broadcast(grid, d.d2)
    z1 .& (.~z2)
end


using Test
using Bifurcation

### Tests

function test_decimal_round()
  @test Bifurcation.decimal_round(0.001422, convert(Unsigned, 3)) == 0.001
  @test Bifurcation.decimal_round(0.000037, convert(Unsigned, 5)) == 0.00004
  @test Bifurcation.decimal_round(0.895, convert(Unsigned, 0)) == 1.0
  println("all decimal_round tests passed!")
end

function test_logistic_map()
  @test Bifurcation.logistic_map(.4, 2.6) == 0.624
  @test Bifurcation.logistic_map(.3, 1.0) == 0.21
  @test Bifurcation.decimal_round(
    Bifurcation.logistic_map(.2, 1.1),
    convert(Unsigned, 3)
    ) == 0.176
  @test Bifurcation.decimal_round(
    Bifurcation.logistic_map(.374, .7),
    convert(Unsigned, 4)
    ) == 0.1639
  println("all logistic_map tests passed!")
end

function test_logistic_map_series()
  s = Bifurcation.logistic_map_series(1, 2, 100, 100)
  @test size(s, 1) == 100

  # fancy broadcast syntax
  @test Bifurcation.decimal_round.(
    Bifurcation.logistic_map_series(0.77, 1.33, 100, 10),
    convert(Unsigned, 3)
   ) == fill(0.248, (10))
  println("all logistic_map_series tests passed!")
end

function run_tests()
  test_decimal_round();
  test_logistic_map();
  test_logistic_map_series();
end

run_tests()


# following
#  https://www.mrao.cam.ac.uk/~dag/CUBEHELIX/
#
# published in
#  Green, D. A., 2011, `A colour scheme for the display of astronomical intensity images', Bulletin of the Astronomical Society of India, 39, 289.
#    (2011BASI...39..289G at ADS.)

import Colors: RGB

# parameters: N number of entries in result colormap (btw: Array(RGB{Float64},N))
#  rotations: rotations -> added term
#  start: startpoint in H -> 0.0 -> Red
#  hue: saturation of hue -> 0.0 -> grayscale only, >1.0 -> clipping in some areas
#  gamma: gamma factor

function cubehelix(N::Integer; rotations::Real=-1.5,start::Real=0.5,hue::Real=1.0,gamma::Real=1.0)
    colormap = Array(RGB{Float64},N)
    fract_all = linspace(0,1,N)
    coeff_cos = [+0.14861, -0.29227, +1.97294]
    coeff_sin = [+1.78277, -0.90649, 0.0]
    c = zeros(3)
    for (i,fract) in enumerate(fract_all)
        c[1]=c[2]=c[3]=0

        angle = 2*pi*(((start/3.0)+rotations)*fract)
        fract = fract ^ gamma
        amp = hue*fract*(1-fract)/2.0
        for j in 1:3
            c[j] = clamp(
                fract + amp * ((coeff_cos[j]*cos(angle)) + (coeff_sin[j] * sin(angle))),
                0.0,1.0)
        end
        colormap[i] = RGB{Float64}(c[1],c[2],c[3])
    end
    colormap
end

cubehelix() = cubehelix(64)
access_dict["cubehelix"] = cubehelix


print("threshold: execute functions ...\n")

#threshold
let
    x = randn(200)*2
    for th in (BiggestTH(), HardTH(), SoftTH(), SemiSoftTH(), SteinTH())
        threshold(x, th, 2)
    end
    for th in (PosTH(), NegTH())
        threshold(x, th)
    end
end

# denoising
let
    vs = VisuShrink(10)
    n = 2^8
    x0 = testfunction(n, "Doppler")
    x = x0 + 0.05*randn(n)
    y = denoise(x, TI=true)
    y = denoise(x, TI=true, nspin = 8)
    y = denoise(x, TI=false)
    y = denoise(x, nothing)
    y = denoise(randn(32,32), TI=true)
end

# best basis
let
    wt = wavelet(WT.db4)
    x = sin.(4*range(0, stop=2*pi-eps(), length=1024))
    tree = bestbasistree(x, wt)
    xtb = wpt(x, wt, tree)
    @test iwpt(xtb, wt, tree) ≈ x

    x = sin.(4*range(0, stop=2*pi-eps(), length=5*64)) # non-dyadic
    tree = bestbasistree(x, wt)
    xtb = wpt(x, wt, tree)
    @test iwpt(xtb, wt, tree) ≈ x
end

#matching pursuit
let
    N = 128
    M = 64
    y=randn(N)
    Arr = randn(M,N)
    func(a::AbstractVector) = Arr*a
    funct(a::AbstractVector) = Arr'*a
    x = func(y)
    matchingpursuit(x, func, funct, 0.1)
end


@testset "Updating bounds" begin
    m = MCPModel()
    @variable(m, x >= 0)
    @mapping(m, F, x+2)
    @complementarity(m, F, x)

    status = solveMCP(m)
    @test result_value(x) ≈ 0.

    JuMP.fix(x, 1., force=true)
    status = solveMCP(m)
    @test result_value(x) ≈ 1.

    JuMP.unfix(x)
    JuMP.set_lower_bound(x, 0)
    status = solveMCP(m)
    @test result_value(x) ≈ 0.
end


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

"""
    RegularGridDiscretizer(dims)

Discretize spatial region into regular grid of given
dimensions `dims`.
"""
struct RegularGridDiscretizer{N} <: AbstractDiscretizer
  dims::Dims{N}
end

discretize(r::RectangleRegion{T,N}, d::RegularGridDiscretizer{N}) where {N,T} =
  RegularGrid(lowerleft(r), upperright(r), dims=d.dims)


using Distributions
using Base.Test

# Test the special base where PoissonBinomial distribution reduces
# to Binomial distribution
for (p, n) in [(0.8, 6), (0.5, 10), (0.04, 20)]

    d = PoissonBinomial(fill(p, n))
    dref = Binomial(n, p)
    println("   testing PoissonBinomial p=$p, n=$n")

    @test isa(d, PoissonBinomial)
    @test minimum(d) == 0
    @test maximum(d) == n
    @test ntrials(d) == n
    @test_approx_eq entropy(d) entropy(dref)
    @test_approx_eq median(d) median(dref)
    @test_approx_eq mean(d) mean(dref)
    @test_approx_eq var(d) var(dref)
    @test_approx_eq kurtosis(d) kurtosis(dref)
    @test_approx_eq skewness(d) skewness(dref)

    for t=0:5
        @test_approx_eq mgf(d, t) mgf(dref, t)
        @test_approx_eq cf(d, t) cf(dref, t)
    end
    for i=0.1:0.1:.9
        @test_approx_eq quantile(d, i) quantile(dref, i)
    end
    for i=0:n
        @test_approx_eq_eps cdf(d, i) cdf(dref, i) 1e-15
        @test_approx_eq_eps pdf(d, i) pdf(dref, i) 1e-15
    end

end

# Test against a sum of three Binomial distributions
for (n₁, n₂, n₃, p₁, p₂, p₃) in [(10, 10, 10, 0.1, 0.5, 0.9),
                                 (1, 10, 100, 0.99, 0.1, 0.05),
                                 (5, 1, 3, 0.01, 0.99, 0.999),
                                 (10, 7, 10, 0., 0.9, 0.5)]

    n = n₁ + n₂ + n₃
    p = zeros(n)
    p[1:n₁] = p₁
    p[n₁+1: n₁ + n₂] = p₂
    p[n₁ + n₂ + 1:end] = p₃
    d = PoissonBinomial(p)
    println("   testing PoissonBinomial [$(n₁) × $(p₁), $(n₂) × $(p₂), $(n₃) × $(p₃)]")
    b1 = Binomial(n₁, p₁)
    b2 = Binomial(n₂, p₂)
    b3 = Binomial(n₃, p₃)

    pmf1 = pdf(b1)
    pmf2 = pdf(b2)
    pmf3 = pdf(b3)

    @test_approx_eq mean(d) (mean(b1) + mean(b2) + mean(b3))
    @test_approx_eq var(d) (var(b1) + var(b2) + var(b3))
    for t=0:5
        @test_approx_eq mgf(d, t) (mgf(b1, t) * mgf(b2, t) * mgf(b3, t))
        @test_approx_eq cf(d, t) (cf(b1, t) * cf(b2, t) * cf(b3, t))
    end

    for k=0:n
        m = 0.
        for i=0:min(n₁, k)
            mc = 0.
            for j=i:min(i+n₂, k)
                mc += (k - j <= n₃) && pmf2[j-i+1] * pmf3[k-j+1]
            end
            m += pmf1[i+1] * mc
        end
        @test_approx_eq_eps pdf(d, k) m 1e-15
    end
end


# create an adjacency matrix based on the airdistance
function adjacency_matrix(airdist::Array{Float64,2})
    adjacent = Array{Bool,2}(undef,size(airdist,1),size(airdist,1)) .= 0
    one_dist = minimum(airdist[airdist .> 0])
    for i = 1:size(airdist,1)
        for j = 1:size(airdist,1)
            if i != j
                if airdist[i,j] <= one_dist * 1.5
                    adjacent[i,j] = 1
                end
            end
        end
    end
    return adjacent::Array{Bool,2}
end


export Operator, Plus, Minus, Times, Divide
export calculate_typed, calculate_branch

abstract type Operator end
struct Plus  <: Operator end
struct Minus <: Operator end
struct Times <: Operator end
struct Divide <: Operator end

(::Plus)(a, b) = a + b
(::Minus)(a, b) = a - b
(::Times)(a, b) = a * b
(::Divide)(a, b) = a / b

"""
    calculate_typed(numbers, operations)

`calculate_typed` performs the computations decribed by `operations` on the values in `numbers`.
`numbers[1]` and `numbers[2]` get combined via `operations[1]`, and the result is then kept and updated
with additional elements of each.

# Examples

```julia
julia> numbers = [1, 2, 5];

julia> operations = Operator[Plus(), Divide()]

julia> calculate_typed(numbers, operations)
0.6
```

This corresponds to the calculation: `1 Plus() 2 -> 3`, followed by `3 Divide() 5 -> 0.6`.
The `3` in the second calculation is the result of the first calculation.
"""
function calculate_typed(numbers::AbstractVector{<:Real}, operations::AbstractVector{Operator})
    a = Float64(numbers[1])
    for i = 1:length(operations)
        b = numbers[i+1]
        op = operations[i]
        a = op(a, b)
    end
    return a
end

"""
calculate_typed(numbers, operations)

`calculate_branch` does the same job as `calculate_typed`, except the operations are encoded as a string.

# Examples

```julia
julia> numbers = [1, 2, 5];

julia> operations = "+/";

julia> calculate_branch(numbers, operations)
0.6
```
"""
function calculate_branch(numbers::AbstractVector{<:Real}, operations::AbstractString)
    # Duplicate the function above, except instead of iterating through Operators,
    # iterate through the characters in `operations` (which will all be one of `+`, `-`, `*`, `/`)
    # Do not create an Operator from the character, instead just use a set of `if...elseif...end`
    # conditionals.
    error("you have to implement this function")
end


"""
    systematic_sample([rng::AbstractRNG,] N, d=Normal(0,1); permute=true)

returns a `Vector` of length `N` sampled systematically from the distribution `d`. If `permute=false`, this vector will be sorted.
"""
function systematic_sample(rng::AbstractRNG, N, d=Normal(0,1); permute=true)
    T = eltype(d)
    e = T(0.5/N) # rand()/N
    y = e:1/N:1
    o = quantile.(d,y)
    permute && permute!(o, randperm(rng, N))
    return eltype(o) == T ? o : T.(o)
end

function systematic_sample(N, d=Normal(0,1); kwargs...)
    return systematic_sample(Random.GLOBAL_RNG, N, d; kwargs...)
end

"""
    ess(p::AbstractParticles{T,N})

Calculates the effective sample size. This is useful if particles come from MCMC sampling and are correlated in time. The ESS is a number between [0,N].

Initial source: https://github.com/tpapp/MCMCDiagnostics.jl
"""
function ess(p::AbstractParticles)
    ac    = autocor(p.particles,1:min(250, length(p)÷2))
    N     = length(ac)
    τ_inv = 1 + 2ac[1]
    K     = 2
    while K < N - 2
        Δ = ac[K] + ac[K+1]
        Δ < 0 && break
        τ_inv += 2Δ
        K += 2
    end
    min(1 / τ_inv, one(τ_inv))*length(p)
end


module Dec9Part1

function check_validity(x::Array{Int64,1}, ref::Int64)::Bool
    x = collect(filter(y -> y <= ref, x))
    n_x = length(x)
    if n_x < 2
        return false
    else
        for i in 1:n_x - 1
            for j in i + 1:n_x
                if ref == x[i] + x[j]
                    return true
                end
            end
        end
    end
    return false
end
function main(file_path::String)::Union{Int64,Nothing}
    numbers = parse.(Int64, readlines(file_path))
    stride = 25
    n = length(numbers)
    for i in 1:n - (stride + 1)
        validity = check_validity(numbers[i:i + stride], numbers[i + stride])
        if !validity
            return numbers[i + stride]
        end
    end
    return nothing
end
end