import MathOptInterface
const MOI = MathOptInterface

"""
    strengthen(a, u0, v0, ::MOI.Nonnegatives)

Compute strenghtened coefficient for non-negative variable.

The strengthened coefficient is given by
```
α = min(-u0 * ⌊π⌋, a + v0 * ⌊π⌋)
```
where ``π = -a / (u0 + v0)``
"""
function strengthen(a::Float64, u0::Float64, v0::Float64, k::MOI.Nonnegatives)

    # Sanity checks
    MOI.dimension(k) == 1 || error("Trying to strengthen a $(typeof(k)) of dimension $(MOI.dimension(k)) (should be 1)")
    @assert u0 + v0 > 1e-4  "u0 + v0 = $(u0 + v0) (should be > 1e-4)"  # Otherwise cut is essentially a K* cut

    π = -a / (u0 + v0)
    return min(
        -u0 * floor(π),
        a + v0 * ceil(π)
    )
end

"""
    strengthen(a, u0, v0, ::MOI.Nonpositive)

Compute strenghtened coefficient for non-positive variable.

The strengthened coefficient is given by
```
α = min(-u0 * ⌊π⌋, a + v0 * ⌊π⌋)
```
where ``π = -a / (u0 + v0)``
"""
function strengthen(a::Float64, u0::Float64, v0::Float64, k::MOI.Nonpositives)

    # Sanity checks
    MOI.dimension(k) == 1 || error("Trying to strengthen a $(typeof(k)) of dimension $(MOI.dimension(k)) (should be 1)")
    @assert u0 + v0 > 1e-4  # Otherwise cut is essentially a K* cut

    π = -a / (u0 + v0)
    return max(
        -u0 * ceil(π),
        a + v0 * floor(π)
    )
end

# TODO: strengthening for conic components


function gausschebyshev( n::Int32, kind::Int32=1 )
# GAUSS-CHEBYSHEV NODES AND WEIGHTS.

x = (Array(Float64,n), Array(Float64,n))
# Use known explicit formulas. Complexity O(n).
if kind == 1
    # Gauss-ChebyshevT quadrature, i.e., w(x) = 1/sqrt(1-x^2)
    x = (cos((2*[n:-1:1]-1)*pi/2n), pi./n*ones(n))
elseif kind == 2
    # Gauss-ChebyshevU quadrature, i.e., w(x) = sqrt(1-x^2)
    x = (cos([n:-1:1]*pi./(n+1)), pi/(n+1)*sin([n:-1:1]./(n+1)*pi).^2 )
elseif kind == 3
    # Gauss-ChebyshevV quadrature, i.e., w(x) = sqrt((1+x)/(1-x))
    x = (cos(([n:-1:1]-.5)*pi/(n+.5)), 2*pi/(n+.5)*cos(([n:-1:1]-.5)*pi/(2(n+.5))).^2)
elseif kind == 4
    # Gauss-ChebyshevW quadrature, i.e., w(x) = sqrt((1-x)/(1+x))
    x = (cos([n:-1:1]*pi/(n+.5)), 2*pi/(n+.5)*sin([n:-1:1]*pi/(2(n+.5))).^2)
else
   throw(ArgumentError("Chebyshev kind should be 1, 2, 3, or 4"))
end
return x
end


using DimensionalData

@inline gaussian(t, t₀, w) = exp(-((t - t₀) / w)^2)

const KDM = 4.148808e3 # MHz^2 pc^-1 cm^3 s

"""
Δt(DM, ν₁, ν₂)
Calculates the time delay corresponding to a dispersed pulse at dispersion measure `DM` in pc/cc between
frequencies `ν₁` and `ν₂`
"""
Δt(DM, ν₁, ν₂) = KDM * DM * (ν₁^-2 - ν₂^-2)

"""
fake_pulse(DM,f_low,f_high)

Generates a `Filterbank` file corresponding to a fake pulse of dispersion measure `DM`
from frequencies `f_low` to `f_high` (MHz).

# Optional Arguments
- `channels`: Number of frequency channels
- `samples`: Number of time samples
- 'δt`: The time step (s) represented by one sample
- `w`: Width of the pulse in time samples
- `A`: Amplitude of pulse in SNR
- `α`: Spectral index, i.e. attenuation of pulse over frequency
- `start`: Index of start of pulse
- `noise_floor`: Value of the top of the noise floor. This is 1 for `dtype` of Floats and one tenth `typemax` for integers by default.
- `dtype`: Data type of data
"""
function fake_pulse(DM, f_low, f_high;
                    channels=1024,
                    samples=1024,
                    δt=1e-3,
                    w=8,
                    A=2,
                    α=4,
                    start=1,
                    noise_floor::Union{T,Nothing}=nothing,
                    dtype::Type{T}=Float32) where {T}
    @assert start < samples "Starting sample must be less than the number of samples"

    if isnothing(noise_floor)
        if dtype <: Integer
            # Default to one tenth the range of the integer type
            noise_floor = typemax(T) ÷ 0xA
        else
            noise_floor = one(T)
        end
    end

    freqs = range(; start=f_high, stop=f_low, length=channels)
    time = range(; start=0, step=δt, length=samples)

    t_start = start * δt

    # Caclulate shifts
    shifts = @. Δt(DM, freqs', f_high)

    # Generate the raw pulse, these are floats at this point
    raw_pulse = @. gaussian(time, t_start + shifts, w * δt) * A * noise_floor * (freqs' / f_high)^α

    # Prep the dynamic spectrum with the background noise
    dyn_spec = rand(zero(T):noise_floor, samples, channels)

    # Add in the pulse
    if dtype <: Integer
        dyn_spec += round.(dtype,raw_pulse)
    else
        dyn_spec += raw_pulse
    end

    # Build Filterbank
    return Filterbank(DimArray(dyn_spec,
                               (Ti(time),
                                Freq(freqs))),
                      Dict("tsamp" => δt,
                           "nbits" => sizeof(dtype) * 8,
                           "nsamples" => samples,
                           "nchans" => channels,
                           "fch1" => f_high,
                           "foff" => step(freqs)))
end

export fake_pulse


"""
    boys_quadgk(x, n::Int; rtol=1e-12)

Calculates Boys function with Gaussian Quadrature using QuadGK.jl.
Accuracy can be tuned with `rtol` argument.
This is accurate, but slow.
"""
function boys_quadgk(x, n::Int; rtol=1e-12)
    tmp = quadgk( 0, 1; rtol=rtol) do t
        t^(2n) * exp( -x*t^2 )
    end
    return tmp[1]
end

"""
    boys_gamma(x, n::Int)

Evaluate Boys function with gamma function.
This is accurate.
"""
function boys_gamma(x, n::Int)
    upper = gamma( n + 1//2 ) - gamma( n + 1//2, x )
    lower = 2x^( n + 1//2 )
    return upper / lower
end

"""
    boys_rec_largex(x, n::Int)

For large `x` > 30. Calculate using recursion.
Uses approximations.
This is fast, but loses accuracy on high `n` values.

Based on:
A. K. H. Weiss, C. Ochsenfeld J. Comput.
Chem. 2015, 36, 1390–1398. DOI: 10.1002/jcc.23935
"""
function boys_rec_largex(x, n::Int)
    if n == 0
        return √π / (2√x)
    else
        return ( n - 1//2 ) * boys_rec_largex(x, n-1) / x
    end
end


@testset "Searches" begin

    @testset "Searches: Binary" begin
        sample = [0, 23, 52, 552, 555, 602, 1004]
        reversed_sample = reverse(sample)
        unsorted_sample = [124, 53, 21, 163]
        binary_search(sample, 52)
        binary_search(reversed_sample, 52, rev=true)
        @test_throws ErrorException binary_search(unsorted_sample, 21)  # throws an error
    end

    @testset "Searches: Linear" begin
        array = [1,3,4,7,8,11]
        linear_search(array, 3)
        linear_search(array, 8)
        linear_search(array, 12)
    end

    @testset "Searches: Exponential" begin
        arr = [1, 2, 3, 4, 13, 15, 20];
        x = 4;
        n = size(arr)[1]
        l = 1;
        r = n;

        exponential_search(arr, x)
    end

    @testset "Searches: Interpolation" begin
        arr = [1, 2, 3, 4, 13, 15, 20];
        x = 15;
        n = size(arr)[1]
        l = 1;
        r = n;

        interpolation_search(arr, l, r, x)
    end

    @testset "Searches: Jump" begin
        arr = [1, 2, 3, 31, 4, 13];
        x = 31;
        jump = 2; # optimum is sqroot(n)
        n = size(arr)[1];

        jump_search(arr, x, jump)
    end

end


using MLDatasets, LightGraphs, Plots, LaTeXStrings, NGWP, MTSG
# Load local module
push!(LOAD_PATH, @__DIR__)
using pSGWT

example, label = MNIST.traindata(Float64, 28)
digit_img = example[4:26, 26:-1:5]
# heatmap(digit_img', ratio = 1, c=:viridis, frame = :none, xlim = [1, 22])

Nx, Ny = size(digit_img)
G = LightGraphs.grid([Nx, Ny]); N = nv(G);
W = Matrix(adjacency_matrix(G))
L = Matrix(laplacian_matrix(G))
Q = incidence_matrix(G; oriented = true)
𝛌, 𝚽 = eigen(L); 𝚽 = 𝚽 .* sign.(𝚽[1, :])';

D = natural_eigdist(𝚽, 𝛌, Q; distance = :DAG)

function grid_eigenvector_plot(l)
    heatmap(reshape(𝚽[:, l], (Nx, Ny))', c = :viridis, ratio = 1, frame = :none,
        xlim = [1, Nx], size = (500, 400), cbar = true)
end

function grid_NGWFvector_plot(l, x, D, 𝚽; σ = 0.2 * maximum(D))
    heatmap(reshape(ngwf_vector(D, l, x, 𝚽; σ = σ)', (Nx, Ny))',
        c = :viridis, ratio = 1, frame = :none, xlim = [1, Nx], size = (500, 400))
end

function plot_edge!(A, B; style = :solid, subplot = 1)
    plot!([A[1], B[1], NaN], [A[2], B[2], NaN], c = :red, legend = false,
        width = 10, style = style, subplot = subplot)
end

function plot_square!(Nx, Ny; subplot = 1)
    plot_edge!([-0.33, 0], [Nx + 1.24, 0]; subplot = subplot)
    plot_edge!([0, 0], [0, Ny + 1]; subplot = subplot)
    plot_edge!([-0.33, Ny + 1], [Nx + 1.24, Ny + 1]; subplot = subplot)
    plot_edge!([Nx + 0.92, 0], [Nx + 0.92, Ny + 1]; subplot = subplot)
end

function grid_vector_plot!(l, i, VECs)
    v = deepcopy(VECs[:, l])
    v ./= norm(v, 1)
    heatmap!(reshape(v, (Nx, Ny))', c = :viridis, ratio = 1, frame = :none,
        xlim = [-0.5, Nx+1.5], cbar = false, subplot = i)
end

function grid_vec_heatmap(VEC, Nx, Ny; l = 1)
    v = VEC[:, l]
    heatmap(reshape(v, (Nx, Ny))', c = :viridis, ratio = 1, frame = :none,
        xlim = [1, Nx], size = (500, 400), cbar = true)
end


"""
    expand_poly(x, [degree = 5], [obsdim]) -> Matrix

Perform a simple polynomial basis expansion of the given `degree`
for the vector `x`.

The optional parameter `obsdim` can be used to specify which
dimension of the resulting `Matrix` should correspond to the
observations. By default each column will denote an observation,
which means that the resulting `Matrix` would have the size
`(degree, length(x))`.

```@jldoctest
julia> expand_poly(1:5, degree = 3)
3×5 Array{Int64,2}:
 1  2   3   4    5
 1  4   9  16   25
 1  8  27  64  125

julia> expand_poly(1f0:5f0, 3) # positional arguments are type-stable
3×5 Array{Float32,2}:
 1.0  2.0   3.0   4.0    5.0
 1.0  4.0   9.0  16.0   25.0
 1.0  8.0  27.0  64.0  125.0
```

Alternatively it is also possible to specify `obsdim = 1`, which
will cause each row to denote an observation.

```@jldoctest
julia> expand_poly(1:5, degree = 3, obsdim = 1)
5×3 Array{Int64,2}:
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125

julia> expand_poly(1:5, 3, ObsDim.First()); # same but type-stable
```
"""
function expand_poly(x::AbstractVector; degree::Integer = 5, obsdim = ObsDim.Last())
    expand_poly(x, degree, convert(LearnBase.ObsDimension, obsdim))
end

function expand_poly(x::AbstractVector, degree::Integer, ::ObsDim.Last)
    expand_poly(x, degree, ObsDim.Constant{2}())
end

function expand_poly(
    x::AbstractVector{T},
    degree::Integer,
    ::ObsDim.Constant{2} = ObsDim.Constant{2}()
) where T

    n = length(x)
    X = zeros(floattype(T), (degree, n))
    for i in 1:n
        for d in 1:degree
            @inbounds X[d, i] += float(x[i])^d
        end
    end
    return X
end

function expand_poly(
    x::AbstractVector{T},
    degree::Integer,
    ::ObsDim.Constant{1}
) where T

    n = length(x)
    X = zeros(floattype(T), (n, degree))
    for d in 1:degree
        for i in 1:n
            @inbounds X[i, d] += float(x[i])^d
        end
    end
    return X
end

floattype(::Type{T}) where T = float(T)
floattype(::Type{Missing}) = Missing
floattype(U::Union) = Union{floattype(U.a), floattype(U.b)}


using SparseArrays
using LinearAlgebra

function euler_fluxes(r, ru, rv, rE)
    gamma = 7.0/5
    u = @. ru/r
    v = @. rv/r
    p = @. (gamma - 1.0)*(rE-(ru^2/r+rv^2/r)/2.0)

    Frx = ru
    Fry = rv
    Frux= @. r*u^2+p
    Fruy= @. ru*v
    Frvx= @. ru*v
    Frvy= @. r*v^2+p
    FrEx= @. u*(rE+p)
    FrEy= @. v*(rE+p)
    return Frx, Fry, Frux, Fruy, Frvx, Frvy, FrEx, FrEy
end

function compact_div(Fx, Fy, h)
    global m
    m=ceil(Int,1/h)
    #x = h * (1:m)
    #m = 4  # testing purpose
    #println("m=",m)
    LHS = SymTridiagonal(ones(m), ones(m-1)/4);
    #println(LHS)
    RHS = (3 / 4h) * Tridiagonal(-ones(m-1), zeros(m), ones(m-1));
    #println(RHS)
    RHS = sparse(RHS)
    #println(RHS)
    dFx = LHS \ (RHS * Fx)
    dFy = LHS \ (RHS * Fy)
    divF = dFx+dFy
    return divF
end

function compact_filter(u, alpha)
    a = 5/8+3*alpha/4
    b = alpha+1/2
    c = alpha/4-1/8
    global m
    LHS = SymTridiagonal(ones(m), alpha*ones(m-1));
    RHS=sparse([1:m;2:m;1:m-1;3:m;1:m-2], [1:m;1:m-1;2:m;1:m-2;3:m],
           [a*ones(m); b/2.0*ones(2*(m-1)); c/2.0*ones(2*(m-2))])
    u_filt = LHS \ (RHS * u)
    return u_filt
end

function euler_rhs(r, ru, rv, rE, h)
    Frx, Fry, Frux, Fruy, Frvx, Frvy, FrEx, FrEy = euler_fluxes(r, ru, rv, rE)
    #println("Frx=",Frx)
    #println("Fry=",Fry)
    fr  =-compact_div(Frx,  Fry,  h)
    fru =-compact_div(Frux, Fruy, h)
    frv =-compact_div(Frvx, Frvy, h)
    frE =-compact_div(FrEx, FrEy, h)
    return fr, fru, frv, frE
end

function ff(u,h)
    #println("size of u in ff=",size(u))
    #println("u in ff=",u)
    fr, fru, frv, frE = euler_rhs(u[:,1], u[:,2], u[:,3], u[:,4], h)
    return [fr fru frv frE]
end

function euler_rk4step(r, ru, rv, rE, h, k, alpha)
    u = [r ru rv rE]
    #println("u=",u)
    #println("size of u=",size(u))
    k1 = k * ff(u, h)
    k2 = k * ff(u + 0.5k1, h)
    k3 = k * ff(u + 0.5k2, h)
    k4 = k * ff(u + k3, h)
    u = u + (k1 + 2k2 + 2k3 + k4) / 6
    #println(u)
    u = compact_filter(u,alpha)
    return u[:,1], u[:,2], u[:,3], u[:,4]
end

if true
  ru = [1;2;3;4]
  rv = [5;6;7;8]
  r  = [2;3;4;2]
  rE = [1;2;2;2]
  println(euler_rk4step(r, ru, rv, rE, 0.25, 0.2, 0.3))
end

#f(x, y) = x * sqrt(y)
function rk4(f::Function, x0::Float64, y0::Float64, x1::Float64, n)
    vx = Array{Float64}(undef, n+1)
    vy = Array{Float64}(undef, n+1)
    vx[1] = x = x0
    vy[1] = y = y0
    h = (x1 - x0) / n
    for i in 1:n
        k1 = h * f(x, y)
        k2 = h * f(x + 0.5h, y + 0.5k1)
        k3 = h * f(x + 0.5h, y + 0.5k2)
        k4 = h * f(x + h, y + k3)
        vx[i + 1] = x = x0 + i * h
        vy[i + 1] = y = y + (k1 + 2k2 + 2k3 + k4) / 6
    end
    return vx, vy
end


# Pulse Parameters
vmax = 5  # volts
fc = 5e9  # GHz
bw = 50  # percent
v0 = 0.00001  # Maximum voltage allowed at t = 0
ptype = "normal"

# Time vector
t = linspace(0, 5e-9, 2001);

# Compute sigma (control's pulse width)
sigma = 1/(2*pi*(fc*bw/100)/2.354820045030949);

# Compute mu (time offset)
# Note that this is only approximate for the Differentiated Pulse.
mu = sigma*sqrt(-2*log(v0/vmax));

if ptype == "normal"
    pulse = vmax*exp.(-(t - mu).^2/(2*sigma^2));
elseif ptype == "diff"
    pulse = -vmax/sigma*(t - mu).*exp.(0.5-(t - mu).^2/(2*sigma^2));
elseif ptype == "sinmod"
    pulse = vmax*exp.(-(t - mu).^2/(2*sigma^2)).*cos.(2*pi*fc*(t - mu));
else
    error("Unsupported pulse type")
end

using PyPlot
plot(t*1e9, pulse, linewidth=2.0)
xlabel("Time (ns)")
ylabel("Amplitude (Volts)")
grid(true)


using LinearAlgebra
# Testing the following distribution: WishartGaussian, DirichletMultinomial, GammaNormal, NormalNormal, BetaBernoulli

@testset "testing posterior paramters" begin

  @testset "Normal-Inverse-Wishart" begin

    # prior
    μ0 = zeros(2)
    κ0 = 5.0
    ν0 = 9.0
    Σ0 = Matrix{Float64}(I, 2, 2)

    # data
    x = [0.2 0.1; 0.1 0.0; 0.2 0.05]
    (N, D) = size(x)

    # distribution
    d = WishartGaussian(μ0, κ0, ν0, Σ0)

    # test prior
    (μ, κ, ν, Σ) = BayesianNonparametrics.posteriorParameters(d)

    @test κ == κ0
    @test ν == ν0
    @test all( μ .== μ0 )
    @test all( Σ .== Σ0 )

    # add data
    BayesianNonparametrics.add!(d, x)

    # test posterior parameters
    (μ, κ, ν, Σ) = BayesianNonparametrics.posteriorParameters(d)

    mu = vec(mean(x, dims=1))
    SS = x' * x

    @test κ == κ0 + N
    @test ν == ν0 + N
    @test all( μ .≈ (κ0 * μ0 .+ mu * N) ./ κ )
    @test all( Σ .== Σ0 + SS - κ * (μ * μ') + κ0 * (μ0 * μ0') )

    # remove data
    BayesianNonparametrics.remove!(d, x)

    # test prior
    (μ, κ, ν, Σ) = BayesianNonparametrics.posteriorParameters(d)

    @test κ == κ0
    @test ν == ν0
    @test all( μ .== μ0 )
    @test all( Σ .== Σ0 )
  end

  @testset "Normal-Gamma" begin

    # prior
    μ0 = 0.0
    λ0 = 1.0
    α0 = 1.0
    β0 = 1.0

    # data
    x = [0.1, 0.0, 0.05]
    N = length(x)

    # distribution
    d = GammaNormal(μ0 = μ0, λ0 = λ0, α0 = α0, β0 = β0)

    # test prior
    (μ, λ, α, β) = BayesianNonparametrics.posteriorParameters(d)

    @test μ == μ0
    @test λ == λ0
    @test α == α0
    @test β == β0

    # add data
    BayesianNonparametrics.add!(d, x)

    # test posterior paramters
    # see: https://www.cs.ubc.ca/~murphyk/Papers/bayesGauss.pdf page 8.
    (μ, λ, α, β) = BayesianNonparametrics.posteriorParameters(d)

    @test λ == λ0 + N
    @test α == α0 + (N / 2)
    @test μ == (λ0 * μ0 + N * mean(x)) / (λ0 + N)
    @test β == β0 + 1/2 * sum( (x .- mean(x)).^2 ) + ( λ0 * N * (mean(x) - μ0)^2 ) / (2 * λ)

    # remove data
    BayesianNonparametrics.remove!(d, x)

    # test prior
    (μ, λ, α, β) = BayesianNonparametrics.posteriorParameters(d)

    @test μ == μ0
    @test λ == λ0
    @test α == α0
    @test β == β0
  end

  @testset "Beta-Bernoulli" begin

    # prior
    α0 = 1.0
    β0 = 1.0

    # data
    x = Bool[0, 1, 0, 0, 1]
    N = length(x)

    # distribution
    d = BetaBernoulli(α0 = α0, β0 = β0)

    # test prior
    (α, β) = BayesianNonparametrics.posteriorParameters(d)

    @test α == α0
    @test β == β0

    # add data
    BayesianNonparametrics.add!(d, x)

    # test posterior paramters
    (α, β) = BayesianNonparametrics.posteriorParameters(d)

    @test α == α0 + sum(x)
    @test β == β0 + (N - sum(x))

    # remove data
    BayesianNonparametrics.remove!(d, x)

    # test prior
    (α, β) = BayesianNonparametrics.posteriorParameters(d)

    @test α == α0
    @test β == β0
  end

end


function score(x, y)
    dist = sqrt(x^2 + y^2)
    if dist > 10
        return 0
    elseif 5 < dist <= 10
        return 1
    elseif 1 < dist <= 5
        return 5
    else
        return 10
    end
end


## anisotropic_exponential.jl : implementation of anisotropic exponential covariance function

## AnisotropicExponential ##
struct AnisotropicExponential{T,M} <: AnisotropicCovarianceStructure{T}
    A::M
    σ::T
end

"""
    AnisotropicExponential(A, σ=1)

Create an anisotropic exponential covariance structure with anisotropy matrix A and (optional) marginal standard deviation `σ`.

# Examples
```jldoctest
julia> A = [1 0.5; 0.5 1]
2×2 Array{Float64,2}:
 1.0  0.5
 1.0  0.5

julia> a1 = AnisotropicExponential(A)
anisotropic exponential (A=[1.0 0.5; 0.5 1.0], σ=1.0)

```
"""
function AnisotropicExponential(A::Matrix{T} where {T<:Real}; σ=1.0::T where {T<:Real})
	isposdef(A) || throw(ArgumentError("anisotropy matrix A must be positive definite"))
    σ > 0 || throw(ArgumentError("marginal standard deviation σ of exponential covariance cannot be negative or zero"))
	T = promote_type(eltype(A),typeof(σ))
	AnisotropicExponential{T,typeof(A)}(A,convert(T,σ))
end

# evaluate exponential covariance
function apply(a::AnisotropicExponential,x::Vector{T}) where {T<:Real}
    exp(-x'*a.A*x)
end

show(io::IO, a::AnisotropicExponential) = print(io, "anisotropic exponential (A=$(a.A), σ=$(a.σ))")


using Test

# This function takes
#     - v: value in register
#     - a: a  scaling value for the logarithm based on Morris's paper
# It returns n(v,a), the approximate count
function n(v, a)
    a*((1+1/a)^v-1)
end

# This function takes
#    - v: value in register
#    - a: a scaling value for the logarithm based on Morris's paper
# It returns a new value for v
function increment(v, a)
    # delta is the probability of incrementing our counter
    delta = 1/(n(v+1, a)-n(v, a))

    if rand() <= delta
        return v += 1
    else
        return v
    end
end

# This simulates counting and takes
#     - n_items: number of items to count and loop over
#     - a: a scaling value for the logarithm based on Morris's paper
# It returns n(v,a), the approximate count
function approximate_count(n_items, a)
    v = 0
    for i = 1:n_items
        v = increment(v, a)
    end

    return n(v, a)
end

# This function takes
#     - n_trials: the number of counting trials
#     - n_items: the number of items to count to
#     - a: a scaling value for the logarithm based on Morris's paper
#     - threshold: the maximum percent error allowed
# It returns a true / false test value
function test_approximate_count(n_trials, n_items, a, threshold)
    samples = [approximate_count(n_items, a) for i = 1:n_trials]

    avg = sum(samples)/n_trials

    @test ((avg - n_items) / n_items < threshold)
end

@testset "Counting Tests, 100 trials" begin
    println("testing 1,000, a = 30, 1% error")
        test_approximate_count(100, 1000, 30, 0.1)
    println("testing 12,345, a = 10, 1% error")
        test_approximate_count(100, 12345, 10, 0.1)
    # Note: with a lower a, we need more trials, so a higher % error here.
    println("testing 222,222, a = 0.5, 10% error")
        test_approximate_count(100, 222222, 0.5, 0.2)
end


## MNIST data using Non-negative matrix Factorization
using MLDatasets
train_x, train_y = MNIST.traindata()
test_x,  test_y  = MNIST.testdata()

## Create the input for NMF
m = size(train_x,1)*size(train_x,2)
n = size(train_x,3)
X = Float64.(reshape(train_x, m, n))
A = copy(X') # input

## Show the average image
using Images
heatmap(Gray.(reshape(sum(A;dims=1)/size(A,1),28,28)'),
  framestyle=:none,yflip=true,colorbar=true)

## Show a random set of images
imgs = Gray.(reshape(A[1:25,:]',28,28,25))
p = plot([heatmap(imgs[:,:,i]') for i=1:25]...,framestyle=:none,yflip=true)
## Turn this into a function
myheatmap(X) = heatmap(Gray.(X'./maximum(X)),framestyle=:none,yflip=true)
plotimgs(X) = plot([myheatmap(X[:,:,i]) for i=1:size(X,3)]...)
plotimgs(reshape(A[1:25,:]',28,28,25))
## Use NMF to find structure
#using ImageMagick
using NMF
## Make a rank 5 factorization
k = 5
W, H = NMF.nndsvd(A, k; variant= :ar)
# alginst = NMF.MultUpdate{Float64}(obj=:mse, maxiter=1000, verbose=false)
alginst = NMF.ALSPGrad{Float64}(maxiter=100)
r = NMF.solve!(alginst, A, W, H)
## show the images
plotimgs(reshape(r.H',28,28,k))
## Turn this into a method
function nmf(A,k)
  W, H = NMF.nndsvd(A, k; variant= :ar)
  alginst = NMF.ALSPGrad{Float64}(maxiter=100)
  r = NMF.solve!(alginst, A, W, H)
  p = plotimgs(reshape(r.H',28,28,k))
  savefig("nmf-mnist-$k.pdf")
  return p, r
end
p5, r5 = nmf(A,5)
p5

## Run a set of these
p8, r8 = nmf(A,8)
p10, r10 = nmf(A,10)
p15, r15 = nmf(A,15)
p25, r25 = nmf(A,25)
p35, r35 = nmf(A,35)
p50, r50 = nmf(A,50)
p100, r100 = nmf(A,100)

## Use these to re-assemble digits
ndig = 60
Xs = reshape((r10.W*r10.H)[1:ndig,:]',28,28,ndig)
As = reshape(A[1:ndig,:]',28,28,ndig)
anim = @animate for i=1:60
  plot(myheatmap(Xs[:,:,i]),myheatmap(As[:,:,i]))
end
gif(anim, "nmf-mnist-10-approx.gif",fps=3)

##
ndig = 60
Xs = reshape((r50.W*r50.H)[1:ndig,:]',28,28,ndig)
As = reshape(A[1:ndig,:]',28,28,ndig)
anim = @animate for i=1:60
  plot(myheatmap(Xs[:,:,i]),myheatmap(As[:,:,i]))
end
gif(anim, "nmf-mnist-50-approx.gif",fps=3)


"""
$(TYPEDSIGNATURES)

Resurrect linspace despite https://github.com/JuliaLang/julia/pull/25896#issuecomment-363769368
"""
linspace(a,b,n)=collect(range(a,b,length=n))

"""
$(TYPEDSIGNATURES)

(Try to) create a subdivision of interval (a,b) stored in the
returned array X such that
  - `X[1]==a, X[end]==b`
  - `(X[2]-X[1])<=ha+tol*(b-a)`
  - `(X[end]-X[end-1])<=hb+tol*(b-a)`
  - There is a number q such that  `X[i+1]-X[i] == q*(X[i]-X[i-1])`
  - X is the array with the minimal possible number of points with the above property

Caveat: the algorithm behind this is  tested for many cases but unproven.

Returns an Array containing the points of the subdivision.
"""
function geomspace(a, b, ha, hb ; tol=1.0e-10, maxiterations=100)
    a=Float64(a)
    b=Float64(b)
    ha=Float64(ha)
    hb=Float64(hb)

    function _geomspace0(l,h0, hl, tol=1.0e-10)
        @assert (l>0.0)
        @assert (h0>0.0)
        @assert (hl>=h0)
        @assert((hl+h0)<l)

        #  We need to  adjust two things:

        # The sum of the geometric progression must
        # match the length of the interval, so lmismatch
        # should be zero:
        function lmismatch(q,k)
            return l - h0*(1-q^k)/(1-q)
        end

        # The claim from experimenral evidence (Wolfram)
        # is that, if written as a polynomial,
        # it has two real zeros: one and the value searched for which
        # is slightly larger than one. All other zeros are on one circle.

        # The size of the last interval should be close to
        # to hl, so hmismatch should be close to one and not larger than one
        function  hmismatch(q,k)
            return  h0*q^(k-1)/hl
        end

        # define initial number of intervals from
        # average of minmal and maximal h
        n=Int(ceil((2.0*l/(h0+hl))))

        if n==1
            n=2
        end

        # define initial q such that hmismatch is one.
        q=(hl/h0)^(1.0/(n-1.0))

        # Iteration until both mismatches are satisfactory
        # Outer loop runs until hmismatch is less than 1
        hmiss=10.0 # some initial value >1 just to run the loop at least once
        if abs(q-1.0)<tol
            hmiss=1.0
        end

        while  hmiss>1.0
            # increase number of intervals until
            # lmismatch becomes less than zero
            while  lmismatch(q,n)>tol
                n+=1
            end

            # find initial interval for q containing
            # value with zero lmismatch
            ns=0

            while lmismatch(q,n)<tol &&  ns<maxiterations
                q*=0.99
                ns+=1
            end

            ql=q*0.9
            qr=q*1.1
            @assert ns<maxiterations "Unable to determine geomspace data after $(maxiterations) iterations"

            # bisection to define q with zero lmismatch
            ns=0
            qm=0.5*(ql+qr)
            while abs(qr-ql)>tol && ns<maxiterations
                ns+=1
                mmm=lmismatch(qm,n)
                if abs(mmm)<0
                    break
                elseif lmismatch(ql,n)*mmm<0
                    qr=qm
                else
                    ql=qm
                end
                qm=0.5*(ql+qr)
            end
            # increase q slightly to increase probability
            # for last interval to be <=hl
            q=qm*(1.0+tol)
            @assert ns<maxiterations "Unable to determine geomspace data after $(maxiterations) iterations"
            hmiss=hmismatch(q,n)
            if hmiss>1.0+tol
                n=n+1
            end
        end
        #  printf("%d %g %g %g\n",n,q,lmismatch(q,n),hmismatch(q,n))

        X = Array{Float64,1}(undef,n+1)
        X[1]=0
        h=h0
        for i=1:n
            X[i+1]=X[i]+h
            h*=q
        end
        X[n+1]=l

        # Fix last interval and remove "overshoots"
        if X[end]>l
            X[end]=l
        end

        while X[end-1]+tol>X[end]
            pop!(X)
            X[end]=l
        end

        return X
    end

    @assert (ha>0.0) "Start step size $(ha) should be positive"
    @assert (hb>0.0) "End step size $(hb) should be positive"
    @assert (a<b)    "Interval ends $(a), $(b) should be increasing"
    @assert ((ha+hb)<b-a) "Sum of step sizes $(ha)+$(hb) should not exceed interval size $(b)-$(a)"

    # Map things to [0,b-a]
    tol=tol*(b-a)
    if ha<hb-tol
        X=_geomspace0(b-a,ha,hb,tol)
        X.+=a
    elseif ha>hb+tol
        X=-reverse(_geomspace0(b-a,hb,ha,tol))
        X.+=b
    else
        n=Int(ceil((b-a)/ha))
        X=collect(range(a,b,length=n+1))
    end
#    @show X[2]-X[1],ha, X[end]-X[end-1],hb

    @assert (X[2]-X[1])<=ha+tol  "First interval turned out to be larger than $(ha)"
    @assert (X[2]-X[1])>ha/2  "First interval turned out to be less  than $(ha)/10"
    @assert (X[end]-X[end-1])<=hb+tol  "Last interval turned out to be larger than $(hb)"
    @assert (X[end]-X[end-1])>=hb/2  "Last interval turned out to be less than $(hb)/10"
    @assert abs(X[1]-a)<tol  "Range start $(X[1]) differs from $(a)"
    @assert abs(X[end]-b)<tol "Range end $(X[end])  differs from $(b)"

    return X
end

collect_or_assign(X)=X
collect_or_assign(X::AbstractRange)=collect(X)
is_monotone(X)=all(X[2:end]-X[1:end-1].>0)

"""
    c=glue(a,b)

Glue together two vectors `a` and b resulting in a vector c. They last element
of `a` shall be equal (up to tol) to the first element of b.
The result fulfills `length(c)=length(a)+length(b)-1`
"""
function glue(_a::AbstractVector, _b::AbstractVector; tol=1.0e-10)
    a=collect_or_assign(_a)
    b=collect_or_assign(_b)

    is_monotone(a) || error("non-monotonous first argument of glue")
    is_monotone(b) || error("non-monotonous second argument of glue")
    Tv=promote_type(eltype(a),eltype(b))
    na=length(a)
    nb=length(b)

    d=b[1]-a[na-1]
    @assert(d>0)
    d=b[1]-a[na]
    @assert(d>-tol)
    @assert(d<tol)

    c=Vector{Tv}(undef,na+nb-1)
    ic=0
    for ia=1:na
        ic+=1
        c[ic]=a[ia]
    end
    for ib=2:nb
        ic+=1
        c[ic]=b[ib]
    end
    return c
end


using ModelingToolkit
using Test

# Define some variables
@Param t σ ρ β
@Unknown x(t) y(t) z(t)
@Deriv D'~t # Default of first derivative, Derivative(t,1)
@Const c=0

# Define a differential equation
eqs = [D(x) ~ σ*(y-x),
       D(y) ~ x*(ρ-z)-y,
       D(z) ~ x*y - β*z]
de = DiffEqSystem(eqs,[t],[x,y,z],[σ,ρ,β])
ModelingToolkit.generate_ode_function(de)
ModelingToolkit.generate_ode_function(de;version=ModelingToolkit.SArrayFunction)
jac_expr = ModelingToolkit.generate_ode_jacobian(de)
jac = ModelingToolkit.calculate_jacobian(de)
f = ODEFunction(de)
ModelingToolkit.generate_ode_iW(de)

# Differential equation with automatic extraction of variables
de2 = DiffEqSystem(eqs, [t])

function test_vars_extraction(de, de2)
    for el in (:ivs, :dvs, :ps)
        names2 = sort(collect(var.name for var in getfield(de2,el)))
        names = sort(collect(var.name for var in getfield(de,el)))
        @test names2 == names
    end
end
test_vars_extraction(de, de2)

# Conversion to first-order ODEs #17
@Deriv D3'''~t
@Deriv D2''~t
@Unknown u(t) u_tt(t) u_t(t) x_t(t)
eqs = [D3(u) ~ 2(D2(u)) + D(u) + D(x) + 1
       D2(x) ~ D(x) + 2]
de = DiffEqSystem(eqs, [t])
de1 = ode_order_lowering(de)
lowered_eqs = [D(u_tt) ~ 2u_tt + u_t + x_t + 1
               D(x_t)  ~ x_t + 2
               D(u_t)  ~ u_tt
               D(u)    ~ u_t
               D(x)    ~ x_t]
function test_eqs(eqs1, eqs2)
    length(eqs1) == length(eqs2) || return false
    eq = true
    for (eq1, eq2) in zip(eqs1, eqs2)
        lhs1, lhs2 = eq1.lhs, eq2.lhs
        typeof(lhs1) === typeof(lhs2) || return false
        for f in fieldnames(typeof(lhs1))
            eq = eq & isequal(getfield(lhs1, f), getfield(lhs2, f))
        end
        eq = eq & isequal(eq1.rhs, eq2.rhs)
    end
    eq
end
@test_broken test_eqs(de1.eqs, lowered_eqs)

# Internal calculations
a = y - x
eqs = [D(x) ~ σ*a,
       D(y) ~ x*(ρ-z)-y,
       D(z) ~ x*y - β*z]
de = DiffEqSystem(eqs,[t],[x,y,z],[σ,ρ,β])
ModelingToolkit.generate_ode_function(de)
jac = ModelingToolkit.calculate_jacobian(de)
f = ODEFunction(de)

# Define a nonlinear system
eqs = [0 ~ σ*(y-x),
       0 ~ x*(ρ-z)-y,
       0 ~ x*y - β*z]
ns = NonlinearSystem(eqs,[x,y,z],[t,σ,ρ,β])
ns2 = NonlinearSystem(eqs)
for el in (:vs, :ps)
    names2 = sort(collect(var.name for var in getfield(ns2,el)))
    names = sort(collect(var.name for var in getfield(ns,el)))
    @test names2 == names
end

ModelingToolkit.generate_nlsys_function(ns)

@Deriv D'~t
@Param A B C
_x = y / C
eqs = [D(x) ~ -A*x,
       D(y) ~ A*x - B*_x]
de = DiffEqSystem(eqs,[t],[x,y],[A,B,C])
test_vars_extraction(de, DiffEqSystem(eqs,[t]))
test_vars_extraction(de, DiffEqSystem(eqs))
@test eval(ModelingToolkit.generate_ode_function(de))([0.0,0.0],[1.0,2.0],[1,2,3],0.0) ≈ -1/3

# Now nonlinear system with only variables
@Unknown x y z
@Param σ ρ β

# Define a nonlinear system
eqs = [0 ~ σ*(y-x),
       0 ~ x*(ρ-z)-y,
       0 ~ x*y - β*z]
ns = NonlinearSystem(eqs)
nlsys_func = ModelingToolkit.generate_nlsys_function(ns)
jac = ModelingToolkit.generate_nlsys_jacobian(ns)
f = @eval eval(nlsys_func)

# Intermediate calculations
# Define a nonlinear system
eqs = [a ~ y-x,
       0 ~ σ*a,
       0 ~ x*(ρ-z)-y,
       0 ~ x*y - β*z]
ns = NonlinearSystem(eqs,[x,y,z],[σ,ρ,β])
nlsys_func = ModelingToolkit.generate_nlsys_function(ns)
jac = ModelingToolkit.calculate_jacobian(ns)
jac = ModelingToolkit.generate_nlsys_jacobian(ns)


function ctr(f::Function, box=[0;1], N = 10)
    h = (box[2]-box[1])/N
    val = 0
    val += f(box[1])/2
    for j = 1:N-1
        x = box[1] + j*h
        val += f(x)
    end
    val += f(box[2])/2
    val *= h
    return val
end


using OrdinaryDiffEq, Test, DiffEqDevTools, DiffEqOperators
u0 = rand(2)
A = DiffEqArrayOperator([2.0 -1.0; -1.0 2.0])
function (p::typeof(A))(::Type{Val{:analytic}},u0,p,t)
    exp(p.A*t)*u0
end

prob = ODEProblem(A,u0,(0.0,1.0))

x = rand(2)
@test A(0.0,x) == A*x

sol = solve(prob,LinearImplicitEuler())

dts = 1./2.^(8:-1:4) #14->7 good plot
sim  = test_convergence(dts,prob,LinearImplicitEuler())
@test abs(sim.𝒪est[:l2]-1) < 0.2

# using Plots; pyplot(); plot(sim)

B = ones(2)
L = AffineDiffEqOperator{Float64}((A,),(B,),rand(2))
prob = ODEProblem(L,u0,(0.0,1.0))
sol = solve(prob,LinearImplicitEuler())

B = DiffEqArrayOperator(ones(2,2))
L = AffineDiffEqOperator{Float64}((A,B),(),rand(2))
function (p::typeof(L))(::Type{Val{:analytic}},u0,p,t)
    exp((p.As[1].A+p.As[2].A)*t)*u0
end

# Midpoint splitting

prob = ODEProblem(L,u0,(0.0,1.0))
sol = solve(prob,MidpointSplitting(),dt=1/10)
# using Plots; pyplot; plot(sol)

## Midpoint splitting convergence
##
## We use the inhomogeneous Lorentz equation for an electron in a
## time-dependent field. To write this on matrix form and simplify
## comparison with the analytic solution, we introduce two dummy
## variables:
## 1) As the third component, a one is stored to allow the
##    inhomogeneous part to be expressed on matrix form.
## 2) As the fourth component, the initial time t_i is stored,
##    for use by the analytical formula.
## This wastes a lot of space, but simplifies the error analysis.
##
## We can then write the Lorentz equation as q̇ = [A + f(t)B]q.

f = t -> -sin(2pi*t)
F = t -> cos(2pi*t)/2pi # Primitive function of f(t)

A = DiffEqArrayOperator([0 1 0 0
                         0 0 0 0
                         0 0 0 0
                         0 0 0 0])

B = DiffEqArrayOperator([0 0 0 0
                         0 0 1 0
                         0 0 0 0
                         0 0 0 0], f)

H = AffineDiffEqOperator{Float64}((A,B),(),rand(4))
function (p::typeof(H))(::Type{Val{:analytic}},u0,p,t)
    x0,v0 = u0[1:2]
    ti = u0[end]
    x = x0 + (t-ti)*v0 - (f.(t)-f(ti))/(2pi)^2 - (t-ti)*F(ti)
    v = v0 + (F.(t)-F(ti))
    [x, v, 1, ti]
end

x0,v0,ti = rand(3)
prob = ODEProblem(H, [x0, v0, 1, ti], (ti, 5.))
dts = 1./2.^(10:-1:1)
sim  = test_convergence(dts,prob,MidpointSplitting())
@test abs(sim.𝒪est[:l2]-2) < 0.2


@testset "Bitpattern entropy" begin
    for N in [100,1000,10000,100000]
        # every bitpattern is only hit once, hence entropy = log2(N)
        @test isapprox(log2(N),bitpattern_entropy(rand(Float32,N)),atol=1e-1)
        @test isapprox(log2(N),bitpattern_entropy(rand(Float64,N)),atol=1e-1)
    end

    N = 1000_000   # more bitpattern than there are in 8 or 16-bit
    @test isapprox(16.0,bitpattern_entropy(rand(UInt16,N)),atol=1e-1)
    @test isapprox(16.0,bitpattern_entropy(rand(Int16,N)),atol=1e-1)

    @test isapprox(8.0,bitpattern_entropy(rand(UInt8,N)),atol=1e-1)
    @test isapprox(8.0,bitpattern_entropy(rand(Int8,N)),atol=1e-1)
end

@testset "Bitcount" begin
    @test bitcount(UInt8[1,2,4,8,16,32,64,128]) == ones(8)
    @test bitcount(collect(0x0000:0xffff)) == 2^15*ones(16)

    N = 100_000
    c = bitcount(rand(N))
    @test c[1] == 0         # sign always 0
    @test c[2] == 0         # first expbit always 0, i.e. U(0,1) < 1
    @test c[3] == N         # second expont always 1

    @test all(isapprox.(c[15:50],N/2,rtol=1e-1))
end

@testset "Bitcountentropy" begin

    # test the PRNG on uniformity
    N = 100_000
    H = bitcount_entropy(rand(UInt8,N))
    @test all(isapprox.(H,ones(8),rtol=5e-4))

    H = bitcount_entropy(rand(UInt16,N))
    @test all(isapprox.(H,ones(16),rtol=5e-4))

    H = bitcount_entropy(rand(UInt32,N))
    @test all(isapprox.(H,ones(32),rtol=5e-4))

    H = bitcount_entropy(rand(UInt64,N))
    @test all(isapprox.(H,ones(64),rtol=5e-4))

    # also for random floats
    H = bitcount_entropy(rand(N))
    @test H[1:5] == zeros(5)    # first bits never change
    @test all(isapprox.(H[16:55],ones(40),rtol=1e-4))
end

@testset "Bitinformation random" begin
    N = 100_000
    A = rand(UInt64,N)
    @test all(bitinformation(A) .< 1e-3)

    A = rand(Float32,N)
    bi = bitinformation(A)
    @test all(bi[1:4] .== 0.0) # the first bits are always 0011
    sort!(A)
    @test sum(bitinformation(A)) > 12
end

@testset "Bitinformation in chunks" begin
    N = 1000
    Nhalf = N ÷ 2
    A = rand(UInt32,N)
    n11,npair1,N1 = bitcount(A[1:Nhalf-1]),bitpaircount(A[1:Nhalf]),Nhalf
    n12,npair2,N2 = bitcount(A[Nhalf:end-1]),bitpaircount(A[Nhalf:end]),Nhalf-1
    @test bitinformation(n11+n12,npair1+npair2,N1+N2) == bitinformation(A)
end

@testset "Bitinformation dimensions" begin
    A = rand(Float32,30,40,50)
    @test bitinformation(A) == bitinformation(A,dims=1)

    # check that the :all_dimensions flag is
    bi1 = bitinformation(A,dims=1)
    bi2 = bitinformation(A,dims=2)
    bi3 = bitinformation(A,dims=3)
    @test bitinformation(A,:all_dimensions) == ((bi1 .+ bi2 .+ bi3)/3)

    # check that there is indeed more information in the sorted dimensions
    sort!(A,dims=2)
    @test sum(bitinformation(A,dims=1)) < sum(bitinformation(A,dims=2))
end

@testset "Mutual information" begin
    N = 10_000

    # equal probabilities for 00|01|10|11
    @test 0.0 == mutual_information([0.25 0.25;0.25 0.25])

    # every 0 or 1 in A is also a 0 or 1 in B
    @test 1.0 == mutual_information([0.5 0.0;0.0 0.5])

    # as before but more 1s means a lower entropy
    @test entropy([0.25,0.75],2) == mutual_information([0.25 0.0;0.0 0.75])

    # every bit is inverted
    @test 1.0 == mutual_information([0.0 0.5;0.5 0.0])

    # two independent arrays
    for T in [UInt8,UInt16,UInt32,UInt64,Float16,Float32,Float64]
        mutinf = bitinformation(rand(T,N),rand(T,N))
        for m in mutinf
            @test isapprox(0,m,atol=1e-3)
        end
    end

    # mutual information of identical arrays
    # for 0,1 occuring exactly 50/50 this is 1bit
    # but so in practice slightly lower for rand(UInt),
    # or clearly lower for low entropy bits in Float16/32/64
    # but identical to the bitcount_entropy (up to rounding errors)
    for T in [UInt8,UInt16,UInt32,UInt64,Float16,Float32,Float64]
        R = rand(T,N)
        mutinf = bitinformation(R,R)
        @test mutinf ≈ bitcount_entropy(R)
    end
end

@testset "Redundancy" begin
    N = 100_000

    # No redundancy in independent arrays
    for T in [UInt8,UInt16,UInt32,UInt64,Float16,Float32,Float64]
        redun = redundancy(rand(T,N),rand(T,N))
        for r in redun
            @test isapprox(0,r,atol=2e-3)
        end
    end

    # Full redundancy in identical arrays
    for T in [UInt8,UInt16,UInt32,UInt64,Float16,Float32,Float64]
        A = rand(T,N)
        H = bitcount_entropy(A)
        R = redundancy(A,A)
        for (r,h) in zip(R,H)
            if iszero(h)
                @test iszero(r)
            else
                @test isapprox(1,r,atol=2e-3)
            end
        end
    end
end

@testset "Mutual information with round to nearest" begin
    N = 100_000

    # compare shaving to round to nearest
    # for round to nearest take m more bits into account for the
    # joint probability
    m = 8

    for T in [Float32,Float64]
        R = rand(T,N)
        for keepbit in [5,10,15]
            mutinf_shave = bitinformation(R,shave(R,keepbit))
            mutinf_round = bitinformation(R,round(R,keepbit),m)
            for (s,r) in zip(mutinf_shave,mutinf_round)
                @test isapprox(s,r,atol=1e-2)
            end
        end
    end

    # shaving shouldn't change
    for T in [Float32,Float64]
        R = rand(T,N)
        for keepbit in [5,10,15]
            mutinf_shave = bitinformation(R,shave(R,keepbit))
            mutinf_round = bitinformation(R,shave(R,keepbit),m)
            for (s,r) in zip(mutinf_shave,mutinf_round)
                @test isapprox(s,r,atol=1e-2)
            end
        end
    end
end

@testset "Information of random set to zero" begin
    for T in (UInt32,UInt64,Float32,Float64)
        for N in [10_000,100_000]
            A = rand(T,N)
            # increase confidence here from the default 0.99 to avoid test failures
            # from false positives
            b = bitinformation(A,confidence=0.9999)
            for ib in b
                @test 0 == ib
            end

            m = bitinformation(A[1:end-1],A[2:end],confidence=0.9999)
            for im in m
                @test 0 == im
            end
        end
    end
end


# basis functions
linear(x::Number) = vcat(1, x)
linear(x::AbstractArray{T, 1}) where {T} = hcat(linear.(x)...)'

gaussian(x::Number, μ, σ=0.1) = vcat(1, exp.(-0.5*(x .- μ).^2/σ^2))
gaussian(x::AbstractArray{T, 1}, μ, σ=0.1) where {T} = hcat(gaussian.(x, Ref(μ), Ref(σ))...)'

polynomial(x::Number, r) = x.^r
polynomial(x::AbstractArray{T, 1}, r) where {T} = hcat(polynomial.(x, Ref(r))...)'

"""
    posterior(Y, ϕX, α, β)

Compute the posterior mean and variance given the observations Y,
regressor matrix ϕX, prior precision α, and the additive noise precision β.
"""
function posterior(Y, ϕX, α, β)
    Σ = inv(α*I + β*ϕX'*ϕX)
    μ = β*Σ*ϕX'*Y
    return μ, Σ
end

"""
    predict(ϕx, μ, Σ, β)

Compute the predictive distribution and standard deviation given the
regressed test point ϕx, posterior mean μ, posterior variance Σ, and
the additive noise precision β.
"""
function predict(ϕx, μ, Σ, β)
    μy = μ'*ϕx
    Σy = 1/β + ϕx'*Σ*ϕx
    return μy, √Σy
end

"""
    regress(Xo, Yo, Xt, Yt, ϕ, α, β)

Plot the point predictions given the observed data (Xo, Yo), test data (Xt, Yt),
basis functions ϕ, prior precision α, and the additive noise precision β.
"""
function regress(Xo, Yo, Xt, Yt, ϕ, α, β, ϕargs...)
    μ, Σ = posterior(Yo, ϕ(Xo, ϕargs...), α, β)

    # predictions
    Yμ = similar(Xt); Yσ = similar(Xt)
    for (i, xt) in enumerate(Xt)
        yμ, yσ = predict(ϕ(xt, ϕargs...), μ, Σ, β)
        Yμ[i] = yμ; Yσ[i] = yσ
    end

    Prediction1D(Xo, Yo, Xt, Yt, Yμ, Yσ)
end

struct Prediction1D{T}
    Xo::T
    Yo::T
    Xt::T
    Yt::T
    μ::T
    σ::T
end

@recipe function f(p::Prediction1D)
    legend --> false
    @series begin
        fillcolor --> :grey
        fillalpha --> 0.2
        linewidth --> 1.0
        linecolor --> :grey
        ribbon := p.σ
        (p.Xt, p.μ)
    end
    @series begin
        linewidth --> 2.0
        linecolor --> :black
        linestyle --> :dash
        (p.Xt, p.Yt)
    end
    @series begin
        seriestype --> :scatter
        markercolor --> :red
        markershape --> :circle
        markersize --> 3.0
        markerstrokecolor --> :red
        (p.Xo, p.Yo)
    end
end

function linefit(N, α=2.0, β=25.0; seeded=false)
    f(x, β=Inf) = -0.3 + 0.5*x + 1/β*randn()
    if seeded
        seed!(1729)
    end
    Xo = 2.0*rand(N) .- 1.0
    Yo = f.(Xo, β)
    Xt = collect(-1.0:0.01:1.0)
    Yt = f.(Xt)
    regress(Xo, Yo, Xt, Yt, linear, α, β)
end

function gaussfit(N, α=1.0, β=25.0, μ=0:0.1:3; seeded=false)
    f(x, β=Inf) = 0.5 + x*sin(2*π*x) + 1/β*randn()
    if seeded
        seed!(1729)
    end
    Xo = rand(N)*3.0
    Yo = f.(Xo, β)
    Xt = collect(0.0:0.01:3.0)
    Yt = f.(Xt)
    regress(Xo, Yo, Xt, Yt, gaussian, α, β, μ)
end

function polyfit(N, α=0.005, β=1/(0.3^2), r=0:5; seeded=false)
    f(x, β=Inf) = 0.5 + sin(2*π*x) + 1/β*randn()
    if seeded
        seed!(1729)
    end
    Xo = rand(N)
    Yo = f.(Xo, β)
    Xt = collect(0.0:0.005:1.0)
    Yt = f.(Xt)
    regress(Xo, Yo, Xt, Yt, polynomial, α, β, r)
end


"""
    σ(x) = 1 / (1 + exp(-x))

Classic [sigmoid](https://en.wikipedia.org/wiki/Sigmoid_function) activation
function.
"""
σ(x) = one(x) / (one(x) + exp(-x))

const sigmoid = σ

# ForwardDiff numerical stability hack
σ_stable(x) = ifelse(x < -80, zero(x), one(x) / (one(x) + exp(-x)))

σ(x::Float32) = σ_stable(x)

@require ForwardDiff begin
  import ForwardDiff: Dual
  σ(x::Dual{T,Float32}) where T = σ_stable(x)
end

"""
    logσ(x)

Return `log(σ(x))` which is computed in a numerically stable way.

    julia> logσ(0.)
    -0.6931471805599453
    julia> logσ.([-100, -10, 100.])
    3-element Array{Float64,1}:
     -100.0
      -10.0
       -0.0
"""
function logσ(x)
  max_v = max(zero(x), -x)
  z = exp(-max_v) + exp(-x-max_v)
  -(max_v + log(z))
end

const logsigmoid = logσ

"""
    relu(x) = max(0, x)

[Rectified Linear Unit](https://en.wikipedia.org/wiki/Rectifier_(neural_networks))
activation function.
"""
relu(x) = max(zero(x), x)

"""
    leakyrelu(x) = max(0.01x, x)

Leaky [Rectified Linear Unit](https://en.wikipedia.org/wiki/Rectifier_(neural_networks))
activation function.
You can also specify the coefficient explicitly, e.g. `leakyrelu(x, 0.01)`.
"""
leakyrelu(x, a = oftype(x/1, 0.01)) = max(a*x, x/1)

"""
    elu(x, α = 1) =
      x > 0 ? x : α * (exp(x) - 1)

Exponential Linear Unit activation function.
See [Fast and Accurate Deep Network Learning by Exponential Linear Units](https://arxiv.org/abs/1511.07289).
You can also specify the coefficient explicitly, e.g. `elu(x, 1)`.
"""
elu(x, α = one(x)) = ifelse(x ≥ 0, x/1, α * (exp(x) - one(x)))

"""
    swish(x) = x * σ(x)

Self-gated actvation function.
See [Swish: a Self-Gated Activation Function](https://arxiv.org/pdf/1710.05941.pdf).
"""
swish(x) = x * σ(x)

"""
    selu(x) = λ * (x ≥ 0 ? x : α * (exp(x) - 1))

    λ ≈ 1.0507
    α ≈ 1.6733

Scaled exponential linear units.
See [Self-Normalizing Neural Networks](https://arxiv.org/pdf/1706.02515.pdf).
"""
function selu(x)
  λ = oftype(x/1, 1.0507009873554804934193349852946)
  α = oftype(x/1, 1.6732632423543772848170429916717)
  λ * ifelse(x > 0, x/1, α * (exp(x) - 1))
end

"""
    softsign(x) = x / (1 + |x|)

See [Quadratic Polynomials Learn Better Image Features](http://www.iro.umontreal.ca/~lisa/publications2/index.php/attachments/single/205).
"""
softsign(x) = x / (one(x) + abs(x))

"""
    softplus(x) = log(exp(x) + 1)

See [Deep Sparse Rectifier Neural Networks](http://proceedings.mlr.press/v15/glorot11a/glorot11a.pdf).
"""
softplus(x) = log1p(exp(x))


using MathGL

# Create the graph, using a sufficient resolution for 4x2 pictures here
gr = MathGL.Graph(2400, 1200)

# Standard sine and cosine plots with grid ect.
@mglplot gr [
    # Give position of the next plot: In a grid of 4x2 plots take the first (top left)
    subplot 4 2 1
    # The title for this subplot
    title "Sine and cosine plot"
    # The xticks, given as array of values and array of corresponding strings
    xtick [-2pi, -pi, 0, pi, 2pi] ["-2\\pi", "-\\pi", "0", "\\pi", "2\\pi"]
    # Set the ranges for x and y respectively. Each range is -1 to 1 by default
    xrange -2pi 2pi; yrange -1 1
    # Plot the axis (meaning: plot tics and tic labels) and the grid with style "h="
    # 'h' is a lighter gray, '=' is a type of dashed line
    axis; grid stl="h="
    # Labels for the x and y axis
    xlabel "x"; ylabel "y"
    # The x-values (as array, not as linspace!) that shall be used for plotting
    x = Array(linspace(-2pi,2pi,100))
    # The values for the two curves
    y1 = 0.5sin(x); y2 = 0.5cos(x)
    # Plot with style options "B" (dark Blue) and "G" (dark green)
    plot x y1 stl="B"
    plot x y2 stl="G"
    # Reset the tics
    xtick
]

# Some log-log plot
@mglplot gr [
    # Same as before
    subplot 4 2 2
    title "Log-log plot"
    # This time: set x and y range in one command, ranges, instead of
    # xrange and yrange
    ranges 1e-2 1e2 7e-1 1e1
    # Set logscale axis by defining the coordinate transformations for the
    # x respectively y values. So we have  φ: xrange x yrange --> ℝ²,
    # φ(x,y) = (lg(x), lg(y)) as transformation applied before plotting
    coords X="lg(x)" Y="lg(y)"
    # Plot the ticks and tick labels, plot the grid for minor ticks
    # (subgrid) and the ordinary grid
    axis; subgrid; grid
    # Once again the labels. MathGL supports some light form of support for
    # latex-like commands
    xlabel "x"; ylabel "y = \\sqrt{1+x^2}"
    # Generate the data and plot
    x = 1logspace(-2, 2, 200)
    plot x sqrt(1+x)
]

# Using cylindrical coordinates
@mglplot gr [
    subplot 4 2 3
    title "Cylindrical plot"
    # This range will be fed to the transformation of cylindrical coordinates
    # The xrange has the role of the angle / times 2pi, so it is between 0 and 1
    # The yrange has the role of the radius, it is also between 0 and 1
    # The zrange stays the same, so it stays from -1 to 1
    ranges 0 1 0 1 -1 1
    coords X="y*sin(2*pi*x)" Y="y*cos(2*pi*x)"
    # Rotate the picture. 50 degrees away from top view, -60 degrees around
    # z axis
    rotate 50 -60
    # Plot 6 circles
    x = Array(linspace(0,1,50))
    o = ones(50)
    # The numbers in the style string determine the width of the line to be
    # drawn
    plot x 0.80o 0.7o  stl="b9"
    plot x 0.65o 0.3o  stl="B7"
    plot x 0.50o 0.0o  stl="r5"
    plot x 0.30o -0.3o stl="G3"
    plot x 0.10o -0.7o stl="g1"
    # Set the origin to a margin value so that it does not disturb drawing the grid
    origin -1 1 -1
    # Plot a grid with light grey color ('h') and undashed lines ('-')
    grid stl="h-"
    # reset coordinates
    coords
]

# A plot marking the area between the y values and the x-axis
@mglplot gr [
    subplot 4 2 4
    title "Area plot"
    # Set ticks and ranges like before
    xtick [-2pi, -pi, 0, pi, 2pi] ["-2\\pi", "-\\pi", "0", "\\pi", "2\\pi"]
    xrange -2pi 2pi; yrange -1 1; zrange 0 0
    # As an alternative: set the origin to the top of the drawing. This
    # affects the axis command and the xlabel / ylabel commands so that the
    # ticks ect. for the x-axis are now drawn above the graph
    origin -2pi 1
    box; axis
    xlabel "x"; ylabel "y"
    # Set the origin back to 0. This is important for the area-plot, since
    # the area marked is the area between the x-axis defined by the origin
    origin 0 0
    # Prepare data
    x = Array(linspace(-2pi,2pi,100))
    y1 = 0.5sin(x)+0.4sin(3x); y2 = 0.8cos(x); y3 = -0.3cos(x)
    # Do the plots. g,G: light,dark green; k: black; y: yellow, c: cyan, b:
    # blue, #: special style for the area drawing: draw lines instead of
    # filling the full area
    area x y1 stl="gG#"
    plot x y1 stl="k"
    area x y2 stl="yr#"
    plot x y2 stl="k"
    area x y3 stl="cb#"
    plot x y3 stl="k"
    # reset the xticks
    xtick
]

# A default chart plot (as implemented in MathGL)
@mglplot gr [
    subplot 4 2 5
    title "Chart plot"
    # Use light-effects. One can also set up different light sources of different color and type
    light true
    rotate 50 60
    # Prepare data by constructing a random 7x2 matrix. So we compare
    # 2 datasets with 7 values each
    data = rand(7,2)+0.1
    # Here '#' means that the edges shall be plotted as lines (black by default)
    chart data stl="#"
]

# A ring like chart plot
@mglplot gr [
    subplot 4 2 6
    title "Ring chart plot"
    light true
    # Will again use something like cylinder coordinates, but won't let the radius go to 0,
    # so y ∈ [-1, 1] with r = y+2
    xrange 0 1
    coords X="(y+2)/3*cos(2*pi*x)" Y="(y+2)/3*sin(2*pi*x)"
    rotate 50 100
    # Prepare data by constructing a random 7x2 matrix. So we compare
    # 2 datasets with 7 values each
    data = rand(7,2)+0.1
    # The last values will be made invisible (by the space ' ' in the style string),
    # define some fixed values for it so that one can see the rings internals
    data[7,:] = [0.25,0.5]
    # "bgrcmy ": use colors blue,green,red,cyan,mangenta,yellow,invisible
    # in this order for the 7 values supplied. '#': plot the edges as black lines
    chart data stl="bgrcmy #"
    # Reset the coordinate system
    coords
]

# Prepare some data that will be used in the next two plots
v = Array(linspace(-1,1,9))
x = Array(0:49)/49
y = Array(0:39)/39
a = 0.6sin(2pi*x).*sin(3pi*y') + 0.4cos(3pi*x.*y')
b = 0.6cos(2pi*x).*cos(3pi*y') + 0.4cos(3pi*x.*y')

# A surface plot with density plot at the bottom
@mglplot gr [
    subplot 4 2 7
    title "Surface plot"
    ranges -1 1 -1 1 -1 1
    light true
    rotate 50 60
    # Plots a bounding box around the 3d figure to be plotted
    box
    # Surface plot with "wire"-option '#' and optional parameter meshnum=12
    surf a stl="#" opt="meshnum 12"
    # density plot with "wire"-option '#' and optional parameter meshnum=12
    # and alpha=0.75 (meaning transparency). (The density plot can of course
    # also be given at other positions than at the bottom).
    dens a stl="#" opt="meshnum 12; alpha 0.75"
]

# Create a very nice contour plot using 3 different ways of drawing contours
@mglplot gr [
    subplot 4 2 8
    title "Contour plot"
    light true
    rotate 50 60
    box
    # This is responsible for the vertical areas
    contv a
    # This is responsible for the horizontal areas
    contf a
    # This is responsible for drawing black lines at the meeting edges
    # between vertical + horizontal areas
    cont  a stl="k"
]

MathGL.write(gr, "../graphs/main_example.png")


# The functions below check if a matrix is approximately Hermitian
using Compat.LinearAlgebra: RealHermSymComplexHerm

const ERR = 1E-13

isapproxhermitian(A::RealHermSymComplexHerm) = true

function isapproxhermitian(A::AbstractMatrix)
    m, n = size(A)
    m == n || return false
    @inbounds for j = 1:m
        for i = 1:j-1
            Aij, Aji = A[i,j], A[j,i]
            if abs(real(Aij)-real(Aji)) > ERR || abs(imag(Aij)+imag(Aji)) > ERR
                return false
            end
        end
        abs(imag(A[j,j])) > ERR && return false
    end
    return true
end

function isapproxhermitian(A::SparseMatrixCSC)
    # Modified from base
    m, n = size(A)
    m == n || return false
    colptr = A.colptr
    rowval = A.rowval
    nzval = A.nzval
    @inbounds for col = 1:m
        for p = colptr[col]:colptr[col+1]-1
            val = nzval[p]
            row = rowval[p]
            if val == 0 # ignore stored zeros
                continue
            end
            if row == col # diagonal element
                abs(imag(val)) > ERR && return false
            else # off-diagonal element
                val2 = A[col,row]
                if abs(real(val)-real(val2)) > ERR || abs(imag(val)+imag(val2)) > ERR
                    return false
                end
            end
        end
    end
    return true
end

function hermitianize!(A::AbstractMatrix)
    m = checksquare(A)
    @inbounds for j = 1:m
         for i = 1:j-1
            A[i,j] = conj(A[j,i])
        end
        A[j,j] = real(A[j,j])
    end
    return A
end

function isunitary(A::AbstractMatrix{T}) where {T}
    # check efficiently if A'*A = I
    m, n = size(A)
    m == n || return false
    @inbounds for i = 1:m, j = 1:m
        a = zero(T)
        for k = 1:m
            a += conj(A[k,i]) * A[k,j]
        end
        if i == j
            a == 1 || return false
        else
            a == 0 || return false
        end
    end
    return true
end

function isunitary(A::SparseMatrixCSC{T,S}) where {T,S}
    # check efficiently if A'*A = I
    m, n = size(A)
    m == n || return false
    colptr = A.colptr
    rowval = A.rowval
    nzval = A.nzval
    @inbounds for i = 1:m, j = 1:m
        a = zero(T)
        for p1 = colptr[i]:colptr[i+1]-1, p2 = colptr[j]:colptr[j+1]-1
            if rowval[p1] < rowval[p2]
                continue
            elseif rowval[p1]==rowval[p2]
                a += conj(nzval[p1]) * nzval[p2]
                continue
            end
        end
        if i == j
            a == 1 || return false
        else
            a == 0 || return false
        end
    end
    return true
end

function isapproxunitary(A::AbstractMatrix{T}) where {T}
    # check efficiently if A'*A ≈ I
    m, n = size(A)
    m == n || return false
    @inbounds for i = 1:m, j = 1:m
        a = zero(T)
        for k = 1:m
            a += conj(A[k,i]) * A[k,j]
        end
        if i == j
            if abs(1-real(a)) > ERR || abs(imag(a)) > ERR
                return false
            end
        else
            if abs(real(a)) > ERR || abs(imag(a)) > ERR
                return false
            end
        end
    end
    return true
end

function isapproxunitary(A::SparseMatrixCSC{T,S}) where {T,S}
    # check efficiently if A'*A ≈ I
    m, n = size(A)
    m == n || return false
    colptr = A.colptr
    rowval = A.rowval
    nzval = A.nzval
    @inbounds for i = 1:m, j = 1:m
        a = zero(T)
        for p1 = colptr[i]:colptr[i+1]-1, p2 = colptr[j]:colptr[j+1]-1
            if rowval[p1] < rowval[p2]
                continue
            elseif rowval[p1]==rowval[p2]
                a += conj(nzval[p1]) * nzval[p2]
                continue
            end
        end
        if i == j
            if abs(1-real(a)) > ERR || abs(imag(a)) > ERR
                return false
            end
        else
            if abs(real(a)) > ERR || abs(imag(a)) > ERR
                return false
            end
        end
    end
    return true
end


#### Number theory and combinatorics.

import Primes
import Combinatorics
import Polynomials

### BinarySearch

@mkapprule BinarySearch

@sjdoc BinarySearch """
    BinarySearch(list,item)

returns a list of the indices at which `item` appears in the sorted list `list`.
"""

@doap BinarySearch(a::ListT,x) = tolist(searchsorted(margs(a),x))

## TODO: We have a julia package that is much more efficient
## at functions related to primes and factoring. Find a way to make an optional Symata package.

### FactorInteger

@sjdoc FactorInteger """
    FactorInteger(n)

give a list of prime factors of `n` and their multiplicities.
"""
@mkapprule FactorInteger nargs => 1:2
@doap FactorInteger(x) = tolistoflistsfixed(Primes.factor(x))

### PrimeList

@sjdoc PrimeList """
    PrimeList(n)

return the list of all prime numbers `<= n`.
"""

## Convert `n` to Int to work around julia bug
@mkapprule PrimeList nargs => 1
@doap PrimeList(n::Integer) = tolistfixed(Primes.primes(convert(Int,n)))

### Prime

@sjdoc Prime """
    Prime(n)

returns the `n`th prime number.
"""
@mkapprule Prime :nargs => 1
@doap Prime(n::Integer) = sympy.prime(n)

### PrimePi

@mkapprule PrimePi nargs => 1  """
    PrimePi(n)

returns the number of primes less than or equal to `n`.
"""

@doap PrimePi(n::Integer) = sympy.primepi(n)

### Permutations

@mkapprule Permutations nargs => 1:2  """
    Permutations(expr)

give a list of all permutations of elements in `expr`.
"""

@doap Permutations(x::Mxpr) = tolistoflistsfixed(Combinatorics.permutations(margs(x)))

### IntegerPartitions

## The Mathematica "way" is not very convenient. It is clumsy to controll what you
## want (regardless of the implmentation here, it is clumsy for the user.) But, it
## is not clear how to avoid this kind of interface to julia `partitions`

## Setting "fixed" below increases speed by 3 or 5 times in some tests.

@mkapprule IntegerPartitions
@doap IntegerPartitions(n::Integer) = tolistoflistsfixed(Combinatorics.partitions(n))
@doap IntegerPartitions(n::Integer, m::Integer) = _integer_partitions_range(n,1,m)

function _integer_partitions_range(n,m1,m2)
    a = collect(Combinatorics.partitions(n,m1))
    for i in (m1+1):m2
        c = collect(Combinatorics.partitions(n,i))
        isempty(c) && continue
        append!(a,c)
    end
    tolistoflists(a)
end

@doap IntegerPartitions(n::Integer, ls::ListT) = listofintegersq(ls) ?  _integer_partitions_list(mx,n,margs(ls)...) : mx
_integer_partitions_list(mx,n,m) = tolistoflistsfixed(collect(Combinatorics.partitions(n,m)))
_integer_partitions_list(mx,n,m1,m2) = _integer_partitions_range(n,m1,m2)
_integer_partitions_list(mx,n,m,args...) = mx

@mkapprule NumberOfPartitions nargs => 1  """
    NumberOfPartitions(n)

gives the number of integer partitions of `n`.
"""

@doap NumberOfPartitions(n::Integer) = n <= 405 ? length(Combinatorics.partitions(n)) : bnpartitions(n)

## copied from partitions.jl. We just add the BigInt dict
## If n > 405, we need BigInt.
## We intentionally eschew type stability.
bnpartitions(n::Integer) = _bnpartitions_func(convert(Int,n))

let _bnpartitions = Dict{Int,BigInt}()
    global _bnpartitions_func
    function _bnpartitions_func(n::Int)
        if n < 0
            0
        elseif n < 2
            1
        elseif (np = get(_bnpartitions, n, 0)) > 0
            np
        else
            np = 0
            sgn = 1
            for k = 1:n
                np += sgn * (bnpartitions(n - (k * (3k - 1)) >> 1) + bnpartitions(n - (k * (3k + 1)) >> 1))
                sgn = -sgn
            end
            _bnpartitions[convert(Int,n)] = np
        end
    end
end

### Named polynomial sequences.

## @runtime_include is defined in util.jl

@runtime_include "PolynomialSequences.jl" _init_polys

### Fibonaccci

@mkapprule Fibbonaci nargs => 1:2

@doap function Fibbonaci(n::Integer)
    n < 0 && return mx
    Combinatorics.fibonaccinum(n)
end

# The choice of n <= 92 is arbitrary. This is where evaluation with x = 1 causes Int overflow
@doap function Fibbonaci(n::Integer,x)
    n < 0 && return mx
    _init_polys() || return mx
    p =  n <= 92 ? fibpoly(n) :
          fibpoly(big(n))
    fromjuliaPolynomial(p,x)
end

### LucasL

@mkapprule LucasL nargs => 1:2

@doap function LucasL(n::Integer)
    n < 0 && return mx
    Combinatorics.lucasnum(n)
end

@doap function LucasL(n::Integer,x)
    n < 0 && return mx
    _init_polys() || return mx
    p =  n <= 90 ? lucaspoly(n) :
          lucaspoly(big(n))
    fromjuliaPolynomial(p,x)
end

### Interface to Julia Polynomials.jl

## TODO: why do we have to do mergesyms here, but not when "var" is a Mxpr, as below
## ... a method is missing in evaluation.jl ?
function fromjuliaPolynomial(p::Polynomials.Poly, var::Symbol)
    res = _fromjuliaPolynomial(p,var)
    mergesyms(res,var)  # force evaluation when var changes.
    if isa(res,Mxpr)
        a = margs(res)
        foreach(setfixed, a)
    end
    setfixed(res)
end

## symbols are merged correctly automatically.
## TODO: It is probably faster to merge symbols here
function fromjuliaPolynomial(p::Polynomials.Poly, var)
    _fromjuliaPolynomial(p,var)
end

function _fromjuliaPolynomial(p::Polynomials.Poly, var)
    cs = Polynomials.coeffs(p)
    nc = length(cs)
    nargs = newargs()
    if nc > 0
        if cs[1] != 0
            push!(nargs,cs[1])
        end
    end
    if nc > 1
        if cs[2] > 1
            push!(nargs, mmul(cs[2], var))
        elseif cs[2] == 1
            push!(nargs,var)
        end
    end
    for i in 3:length(cs)
        c = cs[i]
        c == 0 && continue
        n = i - 1
        push!(nargs, _monomial(var, c, n))
    end
    mxpra(:Plus,nargs)
end

## assumes n != 0,  n != 1
function _monomial(var,c,n)
    if c == 1
        mpow(var,n)
    else
        mmul(c, mpow(var, n))
    end
end


struct LinearLS{T<:Real}
    x :: Vector{T}  # x data
    y :: Vector{T}  # y data
    f :: Function   # y = m*f(x) + q
    w :: Vector{T}  # weights (inverse a priori error on the `y`'s)
    m :: Float64    # slope
    q :: Float64    # intercept
    function LinearLS(x::AbstractVector{<:Real}, y::AbstractVector{<:Real},
        f::Function, w::AbstractVector{<:Real}, m::Real, q::Real)
        T = promote_type(eltype(x), eltype(y), eltype(w))
        new{T}(Vector{T}(x), Vector{T}(y), f, Vector{T}(w), m, q)
    end
end

function Base.show(io::IO, ls::LinearLS)
    println(io, "Linear fit Y ~ m*f(X) + q")
    println(io, "\tf = ", ls.f)
    println(io, "\tm = ", ls.m)
    println(io, "\tq = ", ls.q)
end

"""
    linearls(x::AbstractVector, y::AbstractVector, f::Function=identity;
        weights=ones(length(x)))

Linear fit for `y = m*f(x) + q`.
Return a `LinearLS` object storing the estimated `m, q`
Optionally provide `weights` for Weighted LS

"""
function linearls(x::AbstractVector, y::AbstractVector, f::Function=identity;
        weights=ones(length(x)))
    X = diagm(weights) * [f.(x) ones(length(x))]
    Y = weights .* y
    m, q = X \ Y
    LinearLS(x, y, f, weights, m, q)
end

"""
    predict(ls::LinearLS, x::AbstractVector)

Predict new values based on the linear fit object `ls`. See `linearls`

"""
function predict(ls::LinearLS, x::AbstractVector)
    ls.f.(x) .* ls.m .+ ls.q
end


"""
    relax(t, M0, T1, T2)

Simulate relaxation.

# Arguments
- `t::Real`: Duration of relaxation
- `M0::Real`: Equilibrium magnetization
- `T1::Real`: Spin-lattice recovery time constant
- `T2::Real`: Spin-spin recovery time constant

## Note
Inputs `t`, `T1`, and `T2` should all have the same units, typically ms.

# Return
- `A::Matrix`: 3×3 matrix that describes relaxation
- `B::Vector`: 3-vector that describes recovery

# Examples
```jldoctest
julia> (A, B) = relax(100, 1, 1000, 100); A * [1, 0, 0] + B
3-element Array{Float64,1}:
 0.36787944117144233
 0.0
 0.09516258196404048
```
"""
function relax(t::Real, M0::Real, T1::Real, T2::Real)

    E1 = exp(-t/T1)
    E2 = exp(-t/T2)
    A = [E2 0 0; 0 E2 0; 0 0 E1]
    B = [0, 0, M0 * (1 - E1)]
    return (A, B)

end

"""
    rotatex(α)

Simulate left-handed rotation about the x-axis.

# Arguments
- `α::Real`: Rotation angle (rad)

# Return
- `R::Matrix`: 3×3 matrix that describes rotation about the x-axis by angle `α`

# Examples
```jldoctest
julia> R = rotatex(π/2); R * [0, 0, 1]
3-element Array{Float64,1}:
 0.0
 1.0
 6.123233995736766e-17
```
"""
function rotatex(α::Real)

    return [1 0 0; 0 cos(α) sin(α); 0 -sin(α) cos(α)]

end

"""
    rotatey(α)

Simulate left-handed rotation about the y-axis.

# Arguments
- `α::Real`: Rotation angle (rad)

# Return
- `R::Matrix`: 3×3 matrix that describes rotation about the y-axis by angle `α`

# Examples
```jldoctest
julia> R = rotatey(π/2); R * [0, 0, 1]
3-element Array{Float64,1}:
 -1.0
  0.0
  6.123233995736766e-17
```
"""
function rotatey(α::Real)

    return [cos(α) 0 -sin(α); 0 1 0; sin(α) 0 cos(α)]

end

"""
    rotatez(ϕ)

Simulate left-handed rotation about the z-axis.

# Arguments
- `ϕ::Real`: Rotation angle (rad)

# Return
- `R::Matrix`: 3×3 matrix that describes rotation about the z-axis by angle `ϕ`

# Examples
```jldoctest
julia> R = rotatez(π/2); R * [0, 1, 0]
3-element Array{Float64,1}:
 1.0
 6.123233995736766e-17
 0.0
```
"""
function rotatez(ϕ::Real)

    return [cos(ϕ) sin(ϕ) 0; -sin(ϕ) cos(ϕ) 0; 0 0 1]

end

"""
    rotatetheta(θ, α)

Simulate left-handed rotation about an axis in the x-y plane that makes angle
`θ` with the negative y-axis.

# Arguments
- `θ::Real`: Orientation of the axis about which to rotate (rad)
- `α::Real`: Rotation angle (rad)

# Return
- `R::Matrix`: 3×3 matrix that describes rotation by angle `α` about an axis in
    the x-y plane that makes angle `θ` with the negative y-axis

## Note
`rotatetheta(θ, α) == rotatez(θ) * rotatey(-α) * rotatez(-θ)`

# Examples
```jldoctest
julia> R = rotatetheta(π/4, π/2); R * [0, 0, 1]
3-element Array{Float64,1}:
  0.7071067811865476
 -0.7071067811865475
  6.123233995736766e-17
```
"""
function rotatetheta(θ::Real, α::Real)

    return [ sin(θ)^2+cos(α)*cos(θ)^2           sin(θ)*cos(θ)-cos(α)*sin(θ)*cos(θ)  sin(α)*cos(θ);
             sin(θ)*cos(θ)-cos(α)*sin(θ)*cos(θ) cos(θ)^2+cos(α)*sin(θ)^2           -sin(α)*sin(θ);
            -sin(α)*cos(θ)                      sin(α)*sin(θ)                       cos(α)]

end

"""
    freeprecess(t, M0, T1, T2, Δf)

Simulate free-precession, i.e., relaxation and off-resonance precession.

# Arguments
- `t::Real`: Duration of free-precession (ms)
- `M0::Real`: Equilibrium magnetization
- `T1::Real`: Spin-lattice recovery time constant (ms)
- `T2::Real`: Spin-spin recovery time constant (ms)
- `Δf::Real`: Off-resonance frequency (Hz)

# Return
- `A::Matrix`: 3×3 matrix that describes relaxation and precession
- `B::Vector`: 3-vector that describes recovery

# Examples
```jldoctest
julia> (A, B) = freeprecess(100, 1, 1000, 100, 3.75); A * [1, 0, 0] + B
3-element Array{Float64,1}:
 -0.2601300475114444
 -0.2601300475114445
  0.09516258196404048
```
"""
function freeprecess(t::Real, M0::Real, T1::Real, T2::Real, Δf::Real)

    (A, B) = relax(t, M0, T1, T2)
    C = rotatez(2π * Δf * t/1000)
    return (C * A, B)

end


### General constraint Jacobians match known solutions
pI = 6
n = 3
m = 3

function cI(cdot,x,u)
    cdot[1] = x[1]^3 + u[1]^2
    cdot[2] = x[2]*u[2]
    cdot[3] = x[3]
    cdot[4] = u[1]^2
    cdot[5] = u[2]^3
    cdot[6] = u[3]
end

c_jac = TrajectoryOptimization.generate_general_constraint_jacobian(cI,pI,n,m)

x = [1;2;3]
u = [4;5;6]

A = zeros(6,3)
B = zeros(6,3)

# cx, cu = c_jac(x,u)
c_jac(A,B,x,u)
cx_known = [3 0 0; 0 5 0; 0 0 1; 0 0 0; 0 0 0; 0 0 0]
cu_known = [8 0 0; 0 2 0; 0 0 0; 8 0 0; 0 75 0; 0 0 1]

@test all(A .== cx_known)
@test all(B .== cu_known)
###

# ### Custom equality constraint on quadrotor quaternion state: sqrt(q1^2 + q2^2 + q3^2 + q4^2) == 1
# opts = TrajectoryOptimization.SolverOptions()
# opts.verbose = false
# opts.constraint_tolerance = 1e-3
# opts.cost_tolerance_intermediate = 1e-3
# opts.cost_tolerance = 1e-3
# ######################
#
# ### Set up model, objective, solver ###
# # Model
# n = 13 # states (quadrotor w/ quaternions)
# m = 4 # controls
# dt = 0.05
# model, obj_uncon = TrajectoryOptimization.Dynamics.quadrotor
#
# # -control limits
# u_min = -50.0
# u_max = 50.0
#
# # -constraint that quaternion should be unit
# function cE(cdot,x,u)
#     cdot[1] = sqrt(x[4]^2 + x[5]^2 + x[6]^2 + x[7]^2) - 1.0
# end
#
# obj_con = TrajectoryOptimization.ConstrainedObjective(obj_uncon, u_min=u_min, u_max=u_max, cE=cE)#,cI=cI)
# # Solver
# # - Initial control and state trajectories
# solver = TrajectoryOptimization.Solver(model,obj_con,integration=:rk4,dt=dt,opts=opts)
# U = 10.0*ones(solver.model.m, solver.N)
#
# ##################
#
# ### Solve ###
# results, stats = TrajectoryOptimization.solve(solver,U)
# #############
# @test stats["c_max"][end] < opts.constraint_tolerance


using LinearAlgebra: I, diagm, norm, tr
using Unitful: @u_str

# Example from https://www.continuummechanics.org/hydrodeviatoricstrain.html
@testset "Test `hydrostatic` and `deviatoric`" begin
    E = TensorStrain([
        0.5 0.3 0.2
        0.3 -0.2 -0.1
        0.2 -0.1 0.1
    ])
    @test hydrostatic(E) == TensorStrain(diagm(0 => [2, 2, 2] / 15))
    @test deviatoric(E) == TensorStrain(E - diagm(0 => [2, 2, 2] / 15))
    @test TensorStrain(hydrostatic(E) + deviatoric(E)) ≈ E
end

@testset "Test unitary transformation" begin
    E = TensorStrain([
        0.5 0.3 0.2
        0.3 -0.2 -0.1
        0.2 -0.1 0.1
    ])
    evecs = principal_axes(E)
    @test transpose(evecs) * evecs ≈ evecs * transpose(evecs) ≈ I
    normal_strains = transpose(evecs) * E * evecs
    @test norm(normal_strains - [
        -0.370577 0 0
        0 0.115308 0
        0 0 0.655269
    ]) < 1e-6
    @test isapprox(principal_values(E), [-0.370577, 0.115308, 0.655269]; atol = 1e-6)
    @test tr(normal_strains) ≈ tr(E) ≈ 2 / 5
end

# Example from https://www.continuummechanics.org/principalstrain.html
@testset "Test principal invariants" begin
    E = TensorStrain([
        0.5 0.3 0.2
        0.3 -0.2 -0.1
        0.2 -0.1 0.1
    ])
    @test length(principal_invariants(E)) == 3
    @test principal_invariants(E)[1] == 0.4
    @test principal_invariants(E)[2] ≈ -0.21
    @test principal_invariants(E)[3] == -0.028
end

@testset "Tests from homework 1" begin
    σ = TensorStress([
        300 100 0
        100 100 0
        0 0 0
    ] * u"MPa")
    @test length(principal_invariants(σ)) == 3
    @test principal_invariants(σ) == (400u"MPa", 2e4u"MPa^2", 0u"MPa^3")
    @test maximum(principal_values(σ)) ≈ (200 + 100 * sqrt(2))u"MPa"
    @test sort(principal_values(σ))[2] ≈ (200 - 100 * sqrt(2))u"MPa"
    @test hydrostatic(σ) == TensorStress(diagm(0 => [400, 400, 400] * u"MPa" / 3))
    @test hydrostatic(σ) + deviatoric(σ) == σ
end

@testset "Tests from homework 2" begin
    σ₂₂ = Int64(200)u"MPa"
    σ₁₂ = σ₂₃ = Int64(141)u"MPa"
    σ = TensorStress(
        [
            Int64(0)u"MPa" σ₁₂ Int64(0)u"MPa"
            σ₁₂ σ₂₂ σ₂₃
            Int64(0)u"MPa" σ₂₃ Int64(0)u"MPa"
        ],
    )
    @test minimum(principal_values(σ)) ≈ -123.07397876041034u"MPa"
    @test isapprox(sort(principal_values(σ))[2], 0u"MPa"; atol = 1e-12u"MPa")
    @test maximum(principal_values(σ)) == 323.07397876041034u"MPa"
    @test norm(
        principal_axes(σ) - Float64[
            0.601723 0.707107 0.371389
            -0.525223 0 0.850965
            0.601723 -0.707107 0.371389
        ],
    ) < 1e-6
end


# Exercise 1a
using LinearAlgebra

function trap_weights(x)
    return step(x) * [0.5; ones(length(x) - 2); 0.5]
end
x = range(0.0, 1.0, length = 10)
ω = trap_weights(x)
f(x) = x^2
dot(f.(x), ω)

struct UniformTrapezoidal
    count::Int
    Δ::Float64
end

Base.iterate(S::UniformTrapezoidal, state=1) = state > S.count ? nothing : (if state == 1 || state == count; Δ*0.5 else Δ*1.0 end, state+1)

function integration(f, x::AbstractArray; count = 10)
    Δ = step(x)
    int = similar(x)
    for (i,value) in enumerate(UniformTrapezoidal(count,Δ))
        int[i] = f(x[i]) * value
    end
    return sum(int)
end

count = 100
x = range(0.0, 1.0, length = count)
@show Δ = step(x)
f(x) = x^2

integral = integration(f,x,count = count)

struct Squares
    count::Int
end

Base.iterate(S::Squares, state=1) = state > S.count ? nothing : (state*state, state+1)

for i in Squares(7)
    println(i)
end

struct SquaresVector <: AbstractArray{Int, 1}
    count::Int
end
Base.size(S::SquaresVector) = (S.count,)
Base.IndexStyle(::Type{<:SquaresVector}) = IndexLinear()
Base.getindex(S::SquaresVector, i::Int) = i*i

s = SquaresVector(4)
s[2]

# Exercise 1b

struct TrapezoidalVector <: AbstractArray{Int, 1}
    count::Int
end
Base.size(S::TrapezoidalVector) = (S.count,)
Base.IndexStyle(::Type{<:TrapezoidalVector}) = IndexLinear()
Base.getindex(S::TrapezoidalVector, i::Int) = 1
Base.setindex!(S::TrapezoidalVector, v, 2) = 2

V = TrapezoidalVector(4)


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md

# some preliminary code for constructing hierarchical elements

abstract Hierarchical <: Element

bin(n, k) = prod([(n + 1 - i)/i for i=1:k])

"""
Return Legendgre polynomial of order n to inverval ξ ∈  [-1, 1]

Parameters
----------
n :: Int
    order of polynomial

Returns
-------
function
    Legendgre polynomial of order n in interval ξ ∈ [-1, 1]
"""
function get_legendre_polynomial(n::Int)
    P(xi) = 2^n*sum([xi.^k*bin(n, k)*bin(1/2*(n+k-1), n) for k=0:n])
    P
end

"""
Return derivative of Legendgre polynomial of order n to inverval ξ ∈  [-1, 1]
"""
function get_legendre_polynomial_derivative(n::Int)
    dP(xi) = 2^n*sum([k*xi.^(k-1)*bin(n, k)*bin(1/2*(n+k-1), n) for k=1:n])
    dP
end

"""
Return Legendgre polynomial of order n to inverval ξ ∈ [1, 1].

Parameters
----------
n :: Int
    order of polynomial

Returns
-------
function
    Legendgre polynomial of order n in interval ξ ∈ [-1, 1]

Notes
-----
Uses Bonnet's recursion formula. See
https://en.wikipedia.org/wiki/Legendre_polynomials
"""
function get_legendre_polynomial_recursive(n)
    if n == 0
        P(xi) = 1
    elseif n == 1
        P(xi) = xi
    else
        Pm1 = get_legendre_polynomial(n-1)
        Pm2 = get_legendre_polynomial(n-2)
        P(xi) = 1/n*((2*n-1)*xi*Pm1(xi) - (n-1)*Pm2(xi))
    end
    return P
end

"""
Return derivative of Legendgre polynomial of order n to inverval ξ ∈  [-1, 1]
"""
function get_legendre_polynomial_derivative_recursive(n)
    if n == 0
        P(xi) = 0
    elseif n == 1
        P(xi) = 1
    else
        Pm1 = get_legendre_polynomial_derivative(n-1)
        Pm2 = get_legendre_polynomial_derivative(n-2)
        P(xi) = 1/(n-1)*( (2*(n-1)+1)*xi.*Pm1(xi) - (n+1-1)*Pm2(xi))
    end
    return P
end

"""
Return hierarchical shape function of order N
"""
function get_hierarchial_basis(n)
    if n == 1
        N(xi) = 1/2*(1 - xi)
    elseif n == 2
        N(xi) = 1/2*(1 + xi)
    else
        j = n-1
        Pj = get_legendre_polynomial(j)
        Pjm2 = get_legendre_polynomial(j-2)
        N(xi) = 1/sqrt(2*(2*j-1))*(Pj(xi) - Pjm2(xi))
    end
    return N
end

"""
Return derivative of hierarchical shape function of order N
"""
function get_hierarchial_basis_derivative(n)
    if n == 1
        dN(xi) = -1/2
    elseif n == 2
        dN(xi) = 1/2
    else
        j = n-1
        Pj = get_legendre_polynomial_derivative(j)
        Pjm2 = get_legendre_polynomial_derivative(j-2)
        dN(xi) = 1/sqrt(2*(2*j-1))*(Pj(xi) - Pjm2(xi))
    end
    return dN
end

"""
Set degree of hierarchical element
"""
function set_degree(el::Hierarchical, degree)
    el.degree = degree
end

"""
Get degree of hierarchical element
"""
function get_degree(el::Hierarchical)
    el.degree
end

"""
Hierarchical 1d segment element.
"""
type PSeg <: Hierarchical
    connectivity :: Array{Int, 1}
    fields :: Dict{Any, Any}
    degree :: Int
end
PSeg(connectivity) = PSeg(connectivity, Dict{Any,Any}(), 1)
get_number_of_basis_functions(el::Type{PSeg}) = 2
get_number_of_basis_functions(el::PSeg) = 2 + el.degree - 1
get_element_dimension(el::Type{PSeg}) = 1
function get_basis(el::PSeg, xi)
    m = get_number_of_basis_functions(el)
    out = zeros(m)
    for n=1:m
        N = get_hierarchial_basis(n)
        out[n] = N(xi[1])
    end
    return out
end
function get_dbasisdxi(el::PSeg, xi)
    m = get_number_of_basis_functions(el)
    out = zeros(m)
    for n=1:m
        dN = get_hierarchial_basis_derivative(n)
        out[n] = dN(xi[1])
    end
    return out
end


using Base.Test
using Quaternions

qx = qrotation([1,0,0], pi/4)
@test_approx_eq qx*qx qrotation([1,0,0], pi/2)
@test_approx_eq qx^2 qrotation([1,0,0], pi/2)
theta = pi/8
qx = qrotation([1,0,0], theta)
c = cos(theta); s = sin(theta)
Rx = [1 0 0; 0 c -s; 0 s c]
@test_approx_eq rotationmatrix(qx) Rx
theta = pi/6
qy = qrotation([0,1,0], theta)
c = cos(theta); s = sin(theta)
Ry = [c 0 s; 0 1 0; -s 0 c]
@test_approx_eq rotationmatrix(qy) Ry
theta = 4pi/3
qz = qrotation([0,0,1], theta)
c = cos(theta); s = sin(theta)
Rz = [c -s 0; s c 0; 0 0 1]
@test_approx_eq rotationmatrix(qz) Rz

@test_approx_eq rotationmatrix(qx*qy*qz) Rx*Ry*Rz
@test_approx_eq rotationmatrix(qy*qx*qz) Ry*Rx*Rz
@test_approx_eq rotationmatrix(qz*qx*qy) Rz*Rx*Ry

a, b = qrotation([0,0,1], deg2rad(0)), qrotation([0,0,1], deg2rad(180))
@test_approx_eq slerp(a,b,0.0) a
@test_approx_eq slerp(a,b,1.0) b
@test_approx_eq slerp(a,b,0.5) qrotation([0,0,1], deg2rad(90))

@test_approx_eq angle(qrotation([1,0,0], 0)) 0
@test_approx_eq angle(qrotation([0,1,0], pi/4)) pi/4
@test_approx_eq angle(qrotation([0,0,1], pi/2)) pi/2


XYTuple{T} = Tuple{Real, Real}

+(p1::XYTuple, p2::XYTuple) = (p1[1] + p2[1], p1[2] + p2[2])
-(p1::XYTuple, p2::XYTuple) = (p1[1] - p2[1], p1[2] - p2[2])
*(p::XYTuple, c::Real) = (c * p[1], c * p[2])
*(c::Real, p::XYTuple) = (c * p[1], c * p[2])
/(p::XYTuple, c::Real) = (p[1] / c, p[2] / c)

"""
    partition(start, stop, N)

Generates N equally spaced, centered points between start and stop.
"""
function partition(start::XYTuple, stop::XYTuple, N::Integer)
    dist = stop -start
    step = dist / N
    [start + (i - 0.5) * step for i in 1:N]
end


module LinearAlgebraStudy
using Symbolics
using SymbolicUtils
using Plots
using LinearAlgebra

export drawVector, testVectorsIndependance, transformVectorBasis2D, transformVectorBasis4D

"""
    drawVector(f, V::Vector, origin::Vector = [0,0], xlim=(0,5), ylim=(0,5), title="Vector Plot", label = "Vector 1"))

    Draws a Vector V in the current plot at the origin.

    # Parameters
    f: Plot function
        plot or plot!
    V : Vector
        The vector to be drawn.
    origin : Origin Coordinate
        The origin of the coordinate system.

    # Returns
    plot :
        The plot of the vector.

    # Example
   ```julia
    using LinearAlgebraStudy
    V = Vector([1,2])
    origin = [0,0]
    drawVector(V, origin, (0,5), (0,5), "Vector Plot", "Vector 1")
    ```
"""
function drawVector(f, V::Vector, origin::Vector = [0,0], xlim=(0,5), ylim=(0,5), title="Vector Plot", label = "Vector 1")

    return f([origin[1], V[1] + origin[1]], [origin[2], V[2] + origin[2]],  xlims = xlim, ylims = ylim, line=:arrow, title = title, label = label)

end

"""
testVectorsIndependance(kwargs...)
    This function will take a variable number of vectors and test if they are independant.
    A Matrix "M" will be created with the vectors as columns.
    The rank of the Matrix will then be calculated.
    If the rank is equal to the number of vectors, then the vectors are independant.

    # Parameters
    kwargs : Vectors
        The vectors to be tested.

    # Returns
    result : Bool
        True if the vectors are independant, false otherwise.

"""
function testVectorsIndependance(kwargs...)
    M = hcat(kwargs...)
    rk = rank(M)
    if rk == size(M)[2]
        return true
    else
        return false
    end
end

"""
    transformVectorBasis2D(P::Vector, BX::Vector, BY::Vector)
        Function to transform a given Vector P point to a new Basis. Original Basis is assumed to be
        the standard cartesian basis.
        The transformed Vector is the equivalent of the original Vector P in the new Basis.

        Vectors can be described in terms of scalar multiplication of the basis vectors:
        Example:
            P=[2,1] is the original Vector with standard cartersian basis (X=[1,0],Y=[0,1]).
            This can be written as linear combination of the basis vectors:
                P=2*X+1*Y

            If we want to transform this Vector to a new basis (X'=[1,1],Y'=[0,2]), we can write the
            new vector on the new basis as a linear combination and then solve for the new scalar coefficients:

             ```

            P'=c₁*X'+ c₂*Y'  ⟹ c₁ * [1,1] + c₂ * [0,2] = [2,1]

            let A = [1 0   # Matrix A with new basis vectors
                     1 2]

            let b = [2 1] # Vector b with original coordinates of point P.

            let c = Vector of new coordinates on new Basis corresponding to original coordinates point P.

                c = A\b

            ```

    # Parameters
    b::Vector
        Vector to be transformed.
    BX::Vector
        X-component of the new basis.
    BY::Vector
        Y-component of the new basis.

    # Returns
    p::Vector
        Transformed vector.

    # Examples

"""
function transformVectorBasis2D(b::Vector, BX::Vector, BY::Vector)
    A = [BX BY] # Matrix A with new basis vectors as columns

    c = A\b # Solve for new coordinates on new Basis corresponding to original coordinates point P.

    return c
end

"""
    transformVectorBasis2D(P::Vector, BX::Vector, BY::Vector)
        Function to transform a given Vector P point to a new Basis. Original Basis is assumed to be
        the standard cartesian basis.
        The transformed Vector is the equivalent of the original Vector P in the new Basis.

        Vectors can be described in terms of scalar multiplication of the basis vectors:
        Example:
            P=[2,1] is the original Vector with standard cartersian basis (X=[1,0],Y=[0,1]).
            This can be written as linear combination of the basis vectors:
                P=2*X+1*Y

            If we want to transform this Vector to a new basis (X'=[1,1],Y'=[0,2]), we can write the
            new vector on the new basis as a linear combination and then solve for the new scalar coefficients:

             ```

            P'=c₁*X'+ c₂*Y'  ⟹ c₁ * [1,1] + c₂ * [0,2] = [2,1]

            let A = [1 0   # Matrix A with new basis vectors
                     1 2]

            let b = [2 1] # Vector b with original coordinates of point P.

            let c = Vector of new coordinates on new Basis corresponding to original coordinates point P.

                c = A\b

            ```

    # Parameters
    b::Vector
        Vector to be transformed.
    BX::Vector
        X-component of the new basis.
    BY::Vector
        Y-component of the new basis.

    # Returns
    p::Vector
        Transformed vector.

    # Examples

"""
function transformVectorBasis4D(b::Vector, BX::Vector, BY::Vector, BZ::Vector, BW::Vector)
    A = [BX BY BZ BW] # Matrix A with new basis vectors as columns

    c = A\b # Solve for new coordinates on new Basis corresponding to original coordinates point P.

    return c
end

end


include("featurescaling.jl")

X = [randn(100); 5+10*randn(100); 50+2*randn(100)]

X = reshape(X,(100,3))'

X_stand = standardization(X)
mean(X_stand,2)
std(X_stand,2)

standardization!(X)
mean(X,2)
std(X,2)

X = [randn(100); 5+10*randn(100); 50+2*randn(100)]

X = reshape(X,(100,3))

X_stand = standardization(X)
mean(X_stand,2)
std(X_stand,2)

standardization!(X)
mean(X,1)
std(X,1)

X = 5+10*randn(100)

X_stand = standardization(X)
mean(X_stand)
std(X_stand)

standardization!(X)
mean(X)
std(X)


function dp(M, i, p, memo)
    # Base case
    if i == 1
        return M[i][p]
    end

    #Memorized
    if memo[i, p] > 0
        return memo[i, p]
    end

    # Recursion
    if p == 1
        return memo[i, p] = M[i][p] + max(dp(M, i-1, 2, memo), dp(M, i-1, 3, memo))
    elseif p == 2
        return memo[i, p] = M[i][p] + max(dp(M, i-1, 1, memo), dp(M, i-1, 3, memo))
    elseif p == 3
        return memo[i, p] = M[i][p] + max(dp(M, i-1, 1, memo), dp(M, i-1, 2, memo))
    end
end

function main()
    N = parse(Int, readline())
    M = [parse.(Int, split(readline())) for _ in 1:N]
    memo = zeros(Int, N, 3)

    println(max(dp(M, N, 1, memo), dp(M, N, 2, memo), dp(M, N, 3, memo)))
end

main()


using MultipleViewGeometry, Test, LinearAlgebra
using MultipleViewGeometry.ModuleTypes
using StaticArrays, Calculus, GeometryTypes
using MultipleViewGeometry.ModuleMove

# Tests for fundamental matrix estimation

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

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

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

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

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

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

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

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

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

# Estimate of the fundamental matrix and the true fundamental matrix.
𝐅 = estimate(FundamentalMatrix(), DirectLinearTransform(), (ℳ, ℳʹ))

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

# Ensure the estimated and true matrix have the same scale and sign.
𝐅 = 𝐅 / norm(𝐅)
𝐅 = 𝐅 / sign(𝐅[3,1])
𝐅ₜ = 𝐅ₜ / norm(𝐅ₜ)
𝐅ₜ = 𝐅ₜ / sign(𝐅ₜ[3,1])

@test 𝐅 ≈ 𝐅ₜ

# Check that the fundamental matrix satisfies the corresponding point equation.
npts = length(ℳ)
residual = zeros(Float64,npts,1)
for correspondence in zip(1:length(ℳ),ℳ, ℳʹ)
    i, m , mʹ = correspondence
    𝐦  = hom(m)
    𝐦ʹ = hom(mʹ)
    residual[i] = (𝐦ʹ'*𝐅*𝐦)
end

@test isapprox(sum(residual), 0.0; atol = 1e-7)

# Test the Fundamental Numerical Scheme on the Fundamental matrix problem.
Λ₁ =  [SMatrix{3,3}(Matrix(Diagonal([1.0,1.0,0.0]))) for i = 1:length(ℳ)]
Λ₂ =  [SMatrix{3,3}(Matrix(Diagonal([1.0,1.0,0.0]))) for i = 1:length(ℳ)]
𝐅₀ = estimate(FundamentalMatrix(),DirectLinearTransform(),  (ℳ, ℳʹ))
𝐅 = estimate(FundamentalMatrix(),
              FundamentalNumericalScheme(vec(𝐅₀), 5, 1e-10),
               (Λ₁,Λ₂), (ℳ, ℳʹ))

# Ensure the estimated and true matrix have the same scale and sign.
𝐅 = 𝐅 / norm(𝐅)
𝐅 = 𝐅 / sign(𝐅[3,1])

@test 𝐅 ≈ 𝐅ₜ

# Test the Bundle Adjustment estimator on the Fundamental matrix problem.
𝐅, lsqFit = estimate(FundamentalMatrix(),
                      BundleAdjustment(vec(𝐅₀), 5, 1e-10),
                        (ℳ, ℳʹ))
𝐅 = 𝐅 / norm(𝐅)
𝐅 = 𝐅 / sign(𝐅[3,1])
@test 𝐅 ≈ 𝐅ₜ


d1 = float([ 0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0])
d2 = sparse(float([ 0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]))

y = dijkstra_shortest_paths(g4, 2, d1)
z = dijkstra_shortest_paths(g4, 2, d2)

@test y.parents == z.parents == [0, 0, 2, 3, 4]
@test y.dists == z.dists == [Inf, 0, 6, 17, 33]

y = dijkstra_shortest_paths(g4, 2, d1; allpaths=true)
z = dijkstra_shortest_paths(g4, 2, d2; allpaths=true)
@test z.predecessors[3] == y.predecessors[3] == [2]

@test enumerate_paths(z) == enumerate_paths(y)
@test enumerate_paths(z)[4] ==
    enumerate_paths(z,4) ==
    enumerate_paths(y,4) == [2,3,4]

g = PathGraph(5)
add_edge!(g,2,4)
d = ones(Int, 5,5)
d[2,3] = 100
z = dijkstra_shortest_paths(g,1,d)
@test z.dists == [0, 1, 3, 2, 3]
@test z.parents == [0, 1, 4, 2, 4]


using Test
import Vol
import LinearAlgebra, DelimitedFiles

return_series = 0.01 * collect(1:50)
lambda = 0.94

@test isapprox(Vol.ewma(return_series, lambda), sqrt(Vol.ewma_canonical(return_series.^2, lambda)))
@test isapprox(Vol.ewma(0.01 * ones(100), lambda), 0.01)

r = [ 1. 2. 3.;
      3. 4. 5.;
      5. 6. 7.;
      10. 20. 30.]

@test LinearAlgebra.issymmetric(Vol.ewma(r, lambda))
@test LinearAlgebra.issymmetric(Vol.ewma_cov(r, lambda))

# the diagonal of the correlation matrix should be ones.
@test isapprox(LinearAlgebra.diag(Vol.ewma(r, lambda)), ones(3))

single_serie = [1.0, 3.0, 10.0]
single_discrete_returns = zeros(2)
single_discrete_returns[1] = (3.0-1.0)/1.0
single_discrete_returns[2] = (10.0-3.0)/3.0
@test isapprox(Vol.discrete_returns(single_serie), single_discrete_returns)

single_log_returns = zeros(2)
single_log_returns[1] = log(3.0) - log(1.0)
single_log_returns[2] = log(10.0) - log(3.0)
@test isapprox(Vol.log_returns(single_serie), single_log_returns)

two_series = [ 1.0 10.0;
               3.0 5.0;
               2.0 15.0]

two_discrete_returns=zeros(2,2)
two_discrete_returns[1,1] = (3.0-1.0)/1.0
two_discrete_returns[2,1] = (2.0-3.0)/3.0
two_discrete_returns[1,2] = (5.0-10.0)/10.0
two_discrete_returns[2,2] = (15.0-5.0)/5.0
@test isapprox(Vol.discrete_returns(two_series), two_discrete_returns)

two_log_returns=zeros(2,2)
two_log_returns[1,1] = log(3.0) - log(1.0)
two_log_returns[2,1] = log(2.0) - log(3.0)
two_log_returns[1,2] = log(5.0) - log(10.0)
two_log_returns[2,2] = log(15.0) - log(5.0)
@test isapprox(Vol.log_returns(two_series), two_log_returns)

@test isapprox(Vol.discrete_returns(r)[1:end, 1], Vol.discrete_returns(r[1:end, 1]))
@test isapprox(Vol.log_returns(r)[1:end, 1], Vol.log_returns(r[1:end, 1]))

single_return = [0.05, 0.01, 0.10, -0.02, -0.10, 0.0]
lambda = 0.94
ewma_canonical_single_return = (0.0 * 1 + (-0.10)*lambda + (-0.02)*(lambda^2) + 0.10*(lambda^3) + 0.01*(lambda^4) + 0.05*(lambda^5)) / (1 + lambda + lambda^2 + lambda^3 + lambda^4 + lambda^5)
@test isapprox(Vol.ewma_canonical(single_return, lambda), ewma_canonical_single_return)

ewma_single_return = sqrt( ((0.0^2) * 1 + ((-0.10)^2)*lambda + ((-0.02)^2)*(lambda^2) + (0.10^2)*(lambda^3) + (0.01^2)*(lambda^4) + (0.05^2)*(lambda^5)) / (1 + lambda + lambda^2 + lambda^3 + lambda^4 + lambda^5))
@test isapprox(Vol.ewma(single_return, lambda), ewma_single_return)

# tests cov -> correl
three_returns = [ 0.1 0.3 0.2 ;
                  0.02 0.03 0.0;
                  0.3  0.3  0.3;
                  0.2  0.0  0.1]

lambda = 0.94

covmatrix = Vol.ewma_cov(three_returns, lambda)
vol_array = Vector{Float64}()
for i in 1:3
    push!(vol_array, Vol.ewma(three_returns[:, i], lambda))
end

corrmatrix = Vol.ewma(three_returns, lambda)
for i in 1:3
    for j in 1:3
        @test isapprox(corrmatrix[i,j], covmatrix[i,j]/(vol_array[i]*vol_array[j]))
    end
end

# Diagonal of the covmatrix should be squared volatilities
@test isapprox(sqrt.(LinearAlgebra.diag(covmatrix)), [Vol.ewma(three_returns[:,i], lambda) for i in 1:3 ])

# market prices
lambda = 0.94
(raw_data, table_header) = DelimitedFiles.readdlm("prices.csv", ',', header=true)
prices = raw_data[:, 4:end]
(rows, cols) = size(prices)
returns = Vol.log_returns(prices)
covmatrix = Vol.ewma_cov(returns, lambda)
corrmatrix = Vol.ewma(returns, lambda)

@test LinearAlgebra.issymmetric(covmatrix)
@test LinearAlgebra.issymmetric(corrmatrix)
@test isapprox(sqrt.(LinearAlgebra.diag(covmatrix)), [Vol.ewma(returns[:,i], lambda) for i in 1:cols ])
@test isapprox(LinearAlgebra.diag(corrmatrix), ones(cols))


#--------------------------------------------------------------------
# Spacetime Discretization methods in Julia
# Soham 01-2018
#--------------------------------------------------------------------

function testchebx(N::Int)
	x  = chebx(5, N)
	Tx = cos(N*acos(x))
	return abs(Tx)
end

function testchebd(N::Int)
	DR = Float64[chebd(fld(N, 3), j, N) for j in 1:N+1]
	return sum(DR)
end

function testchebdpoly(N::Int)::Float64
    x     = chebgrid(N)
    fx    = x.^5
    edfx  = 5.*x.^4
    chebD = Float64[chebd(i,j,N) for i in 1:N+1, j in 1:N+1]
    ndfx  = chebD*fx
    return maximum(abs.(ndfx - edfx))
end

function testchebw(N::Int)
	w = Float64[chebw(i, N) for i in 1:N+1]
	return sum(w)
end

@test chebx(3, 10) ≈ 0.8090169943749475
@test chebw(9, 12) ≈ 0.2258075258075258
@test vandermonde(10)[2,5] == cheb(4, chebx(2,10))
@test pseudovandermonde(10, chebgrid(14))[3,7] == cheb(6, chebx(3,14))
@test chebgrid(23)[7] == chebx(7,23)
@test chebweights(12)[8] == chebw(8,12)

@test testchebx(rand(10:20)) ≈ 1.0
@test testchebd(rand(10:20)) < 1e-14
@test testchebw(rand(2:20)) ≈ 2.0
@test chebgrid(4, 2, 1) == (chebgrid(4) + 1)/2
@test chebgrid(4, 2, 2) == (chebgrid(4) - 1)/2
@test chebgrid(2, 2, 1)[end] == chebgrid(2, 2, 2)[1]
@test testchebdpoly(10) < 1e-13