function accumulative_backpropagation!(net, train_datum::TestDatum,
    dCx_dw_buffer, dCx_db_buffer, dCx_da_buffer, Bj_buffer)

    #feedforward
    feedforward!(net, train_datum);

    #BackPropagate
    update_Bj_buffer!(net, Bj_buffer, dCx_da_buffer, train_datum);
    accumulate_dCx_dw_buffer!(net, dCx_dw_buffer, Bj_buffer);
    accumulate_dCx_db_buffer!(net, dCx_db_buffer, Bj_buffer);

end

function update_Bj_buffer!(net, Bj_buffer, dCx_da_buffer, train_datum)

    #Reset Buffers
    for l in 2:net.depth
        Bj_buffer[l] .= 0.0;
        dCx_da_buffer[l] .= 0.0;
    end

    for l in net.depth:-1:2
        if l == net.depth
            for j in 1:outlength(net)
                dCx_da_buffer[net.depth][j] = single_cost_prime(output(net)[j], output(train_datum)[j]);
                Bj_buffer[net.depth][j] = act_function_prime(net.z[net.depth][j]) * dCx_da_buffer[net.depth][j];
            end
        else
            nextl = l + 1;
            for k in 1:net.lsizes[l]
                for j in 1:net.lsizes[nextl]
                    dCx_da_buffer[l][k] += net.w[nextl][j,k] * Bj_buffer[nextl][j];
                end
                Bj_buffer[l][k] = act_function_prime(net.z[l][k]) * dCx_da_buffer[l][k];
            end
        end
    end
end

function accumulate_dCx_db_buffer!(net, dCx_db_buffer, Bj_buffer)
    for l in 2:net.depth
        for j in 1:net.lsizes[l]
            dCx_db_buffer[l][j] += Bj_buffer[l][j];
        end
    end
end

function accumulate_dCx_dw_buffer!(net, dCx_dw_buffer, Bj_buffer)
    for l in 2:net.depth
        prevl = l-1;
        for j in 1:net.lsizes[l]
            for k in 1:net.lsizes[prevl]
                dCx_dw_buffer[l][j,k] += net.a[prevl][k] * Bj_buffer[l][j];
            end
        end
    end
end


function jump_sdplib(solver, path; verbose = false, test = false)

    println("running: $(path)")

    n, m, F, c = sdplib_data(path)

    # Build model
    model = Model(with_optimizer(solver))
    @variable(model, X[1:n, 1:n], PSD)

    # Objective function
    @objective(model, Min, sum(F[0][idx...] * X[idx...]
        for idx in zip(findnz(F[0])[1:end-1]...)))

    # Linear equality constraints
    for k = 1:m
        @constraint(model, sum(F[k][idx...] * X[idx...]
            for idx in zip(findnz(F[k])[1:end-1]...)) == c[k])
    end

    teste = @time optimize!(model)

    XX = value.(X)

    verbose && sdplib_eval(F,c,n,m,XX)

    objval = objective_value(model)
    stime = MOI.get(model, MOI.SolveTime())

    # @show tp = typeof(model.moi_backend.optimizer.model.optimizer)
    # @show fieldnames(tp)
    rank = -1
    try
        @show rank = model.moi_backend.optimizer.model.optimizer.sol.final_rank
    catch
    end
    status = 0
    if JuMP.termination_status(model) == MOI.OPTIMAL
        status = 1
    end
    return (objval, stime, rank, status)
    # return (objval, stime)
end


function cartesian_1d_crs(N, m)

    # make stencil
    ic = Int(ceil(m/2.0))
    M = zeros((m, m))
    for i=1:m
        for j=1:m
            M[i, j] = (i - ic)^(j-1) / factorial(j-1)
        end
    end
    Minv = inv(M)
    sten = transpose(Minv) * Float64[(0.5)^(j-1)/factorial(j-1) for j=1:m]

    Ih = zeros(Float64, (N,N))
    Is = I + Ih
    for i=1:m
        k = i - ic
        vec = sten[i] * ones(N - abs(k))
        Ih += diagm(k => vec)
    end

    Iout = zeros(Float64, (2*N,N))
    Iout[1:2:end,:] = Is
    Iout[2:2:end,:] = Ih
    Rout = transpose(Iout) / 2

    return sparse(Iout), sparse(Rout)
end

function cartesian_2d_crs(Nx, Ny, mx, my)

    Ix, Rx = cartesian_1d_crs(Nx, mx)
    Iy, Ry = cartesian_1d_crs(Ny, my)

    I2d = kron(Iy, Ix)
    R2d = kron(Ry, Rx)
    return I2d, R2d
end


module AccurateArithmetic

export
       four, two, negone, onehalf, onequarter,
       fourx, twox, halfx, quarterx,
#=
       These functions appear in the literature, named similarly.
=#
       two_sum, two_diff, two_prod,
       fast_two_sum, fast_two_diff,
       ufp, ulp, splitting, extractscalar,
#=
     These functions evaluate as multipart floats.
     The trailing '_' signifies this extra accuracy.
=#
       add_, sub_, add_hilo_, sub_hilo_,
       mul_, inv_, dvi_,
       powr2_, root2_, # powr3_, root3_,
       sum_,
       add_2, add_3, add_4, add_5,
       add_hilo_2, add_hilo_3, add_hilo_4, add_hilo_5,
       sub_2, sub_3,
       sub_hilo_2, sub_hilo_3,
       powr2_2, #powr3_2, powr3_3,
       root2_2, #root3_2,
       mul_2, mul_3,
       inv_2, dvi_2,
       fma_, fma_3, fma_2, fms_, fms_3, fms_2

if VERSION >= v"0.7.0-"
    import Base.IEEEFloat
else
    const IEEEFloat = Union{Float64, Float32, Float16}
end

import Base: signbit, flipsign, copysign, abs, inv, (+), (-), (*), (/)

include("constants.jl")
include("twoarith.jl")
include("renormalize.jl")
include("errorfree.jl")
include("faithful.jl")
include("nearfaithful.jl")
include("compensated.jl")
include("pairarith.jl")

end # module AccurateAritmetic


#function DemoLSTMsimple(PlotResults=true)
# Simple memory problem:
# If either of the first two inputs in x[t] is 1, then output to y[t] the previous memory m[t-1] and update the memory m[t] to store x[t]
# This can be achieved using a simple LSTM style approach.

useproc("CPU")

PlotResults=true

T=400
m=Array(ADnode,T)
alpha=Array(ADnode,T)
beta=Array(ADnode,T)
x=Array(ADnode,T)
y=Array(ADnode,T)
yout=Array(ADnode,T)
loss=Array(ADnode,T)

StartCode()
Win=ADvariable()
Wout=ADvariable()
Bin=ADvariable()
Bout=ADvariable()
x[1]=ADnode()
alpha[1]=sigmoid(Win*x[1])
m[1]=1*x[1]
y[1]=ADnode()
beta[1]=sigmoid(Wout*x[1])
for t=2:T
    x[t]=ADnode()
    y[t]=ADnode()
    alpha[t]=sigmoid(Win*x[t]+Bin)
    m[t]=alpha[t]*x[t]+(1-alpha[t])*m[t-1]
    beta[t]=sigmoid(Wout*x[t]+Bout)
    yout[t]=beta[t]*m[t-1]
    #loss[t]=meanSquareLoss(yout[t],y[t])
    loss[t]=BinaryKullbackLeiblerLoss(y[t],yout[t])
end
totalloss=mean(loss[2:T])
net=EndCode() # defines the graph

# instantiate parameter nodes and inputs:
X=4
# parameters:
net.value[Win]=0.01*randn(1,X)
net.value[Wout]=0.01*randn(1,X)
net.value[Bin]=0.01*randn(1,1)
net.value[Bout]=0.01*randn(1,1)

# training data:
# make close to zero or 1 (strictly 0 or 1 can give NAN in KL loss)
net.value[x[1]]=zeros(X,1)+0.001; net.value[x[1]][1]=0.999;
net.value[y[1]]=zeros(X,1)+0.001

xstore=copy(net.value[x[1]])
for t=2:T
    net.value[x[t]]=0.001+0.998*(rand(X,1).>0.5)
    net.value[x[t]][1]=0.001+0.998*(rand()>0.95)

    if sum(extract(net.value[x[t]][1]))>0.5
        net.value[y[t]]=xstore
        xstore=net.value[x[t]]
    else
        net.value[y[t]]=zeros(X,1)+0.001
    end
end

net=compile(net) # compile and preallocate memory
@gpu CUDArt.init([0])
@gpu net=convert(net,"GPU")

#gradcheck(net;showgrad=true) # use a small number of datapoints and small network to check the gradient, otherwise

# Training:
println("Training: using $(net.gpu==true? "GPU" : "CPU") ")
ParsToUpdate=Parameters(net)
nupdates=1000
error=Array(Float64,0)
    velo=NesterovInit(net) # Nesterov velocity
    for i=1:nupdates
    LearningRate=0.5
    ADforward!(net)
    ADbackward!(net)
    push!(error,extract(net.value[net.FunctionNode])[1])
    printover("iteration $i: training loss = $(error[i])")
    for par in ParsToUpdate
        #GradientDescentUpdate!(net.value[par],net.gradient[par],LearningRate)
        NesterovGradientDescentUpdate!(net.value[par],net.gradient[par],velo[par],LearningRate,i)
    end
end
println()

if PlotResults==true
    yy=zeros(X,T); yyout=zeros(X,T); xx=zeros(X,T)
    for t=2:T
        yy[:,t]=net.value[y[t]]; yyout[:,t]=net.value[yout[t]]
        xx[:,t]=net.value[x[t]];
    end

    fig,ax=PyPlot.subplots(3,1,sharex=true)
    ax[1,1][:imshow](xx,interpolation="none");ax[1,1][:set_aspect](15)
    ax[2,1][:imshow](yy,interpolation="none"); ax[2,1][:set_aspect](15);
    ax[3,1][:imshow](yyout,interpolation="none"); ax[3,1][:set_aspect](15)
    ax[1,1][:set_title]("x: train input")
    ax[2,1][:set_title]("y: train output")
    ax[3,1][:set_title]("yout: net predicted output given x")
    fig[:canvas][:draw]()

    figure(); plot(error); title("training loss")
end
#end


module BoxCoxTrans

using Optim: optimize, minimizer
using Statistics: mean, var
using StatsBase: geomean

"""
    transform(𝐱)

Transform an array using Box-Cox method.  The power parameter λ is derived
from maximizing a log-likelihood estimator.

If the array contains any non-positive values then a DomainError is thrown.
This can be avoided by providing the shift parameter α to make all values
positive.

Keyword arguments:
- α: added to all values in 𝐱 before transformation. Default = 0.
- scaled: scale transformation results.  Default = false.
"""
function transform(𝐱; α = 0, kwargs...)
    if α != 0
        𝐱 .+= α
    end
    λ, details = lambda(𝐱; kwargs...)
    #@info "estimated lambda = $λ"
    transform(𝐱, λ; kwargs...)
end

"""
    transform(𝐱, λ; α = 0)

Transform an array using Box-Cox method with the provided power parameter λ.
If the array contains any non-positive values then a DomainError is thrown.

Keyword arguments:
- α: added to all values in 𝐱 before transformation. Default = 0.
- scaled: scale transformation results.  Default = false.
"""
function transform(𝐱, λ; α = 0, scaled = false, kwargs...)
    if α != 0
        𝐱 .+= α
    end
    any(𝐱 .<= 0) && throw(DomainError("Data must be positive and ideally greater than 1.  You may specify α argument(shift). "))
    if scaled
        gm = geomean(𝐱)
        @. λ ≈ 0 ? gm * log(𝐱) : (𝐱 ^ λ - 1) / (λ * gm ^ (λ - 1))
    else
        @. λ ≈ 0 ? log(𝐱) : (𝐱 ^ λ - 1) / λ
    end
end

"""
    lambda(𝐱; interval = (-2.0, 2.0), method = :geomean)

Calculate lambda from an array using a log-likelihood estimator.

Keyword arguments:
- method: either :geomean or :normal
- any other keyword arguments accepted by Optim.optimize function e.g. abs_tol

See also: [`log_likelihood`](@ref)
"""
function lambda(𝐱 ; α = 0 , interval = (-2.0, 2.0), kwargs...)
    if α != 0
        𝐱 .+= α
    end

    i1, i2 = interval
    res = optimize(λ -> -log_likelihood(𝐱, λ; kwargs...), i1, i2)
    (value=minimizer(res), details=res)
end

"""
    log_likelihood(𝐱, λ; method = :geomean)

Return log-likelihood for the given array and lambda.

Method :geomean =>
    -N / 2.0 * log(2 * π * σ² / gm ^ (2 * (λ - 1)) + 1)

Method :normal =>
    -N / 2.0 * log(σ²) + (λ - 1) * sum(log.(𝐱))
"""
function log_likelihood(𝐱, λ; method = :geomean, kwargs...)
    N = length(𝐱)
    𝐲 = transform(float.(𝐱), λ)
    σ² = var(𝐲, corrected = false)
    gm = geomean(𝐱)
    if method == :geomean
        -N / 2.0 * log(2 * π * σ² / gm ^ (2 * (λ - 1)) + 1)
    elseif method == :normal
        -N / 2.0 * log(σ²) + (λ - 1) * sum(log.(𝐱))
    else
        throw(ArgumentError("Incorrect method. Please specify :geomean or :normal."))
    end
end

"""
    retransform(𝐱, λ; α = 0)

Retransform an array which is transformed using Box-Cox method with the provided power parameter λ and shift
argument α to the oreginal array.

Keyword arguments:
- α: added to all values in 𝐱 before transformation. Default = 0.
- scaled: scale transformation results.  Default = false.
"""

function backtransform(𝐱, λ; α = 0, scaled = false, kwargs...)
    if scaled
        gm = geomean(𝐱)
        @. λ ≈ 0 ? exp.(𝐱 / gm) - α  : (𝐱 * λ * gm ^ (λ -1) +1) ^ (1 / λ) - α
    else
        @. λ ≈ 0 ? exp.(𝐱) - α : (λ * 𝐱 + 1) ^ (1 / λ) - α
    end
end

end # module


using EngEconomics, Roots, Plots

# Given
aString = "Buy New Forklift"
aInit = -100000
aAnnual = 0
aLife = 5
aScrap = 20000

bString = "Rent Forklift"
bInit = 0
bAnnual = -20000
bLife = 5

cString = "Buy Used Forklift"
cInit = -50000
cSalvage = 0
cLife = 5
cAnnual = -5000
cAnnualG = -1000

allRevenue = 30000

MARR = 0.08

# Find
# - BC
IRR_A_eq(x) = aInit + allRevenue * seriesPresentAmountFactor(x, aLife) + aScrap * presentWorthFactor(x, aLife)
find_zero(IRR_A_eq, MARR)

IRR_B_eq(x) = (bAnnual + allRevenue) * seriesPresentAmountFactor(x, bLife)
find_zero(IRR_B_eq, MARR)

IRR_C_eq(x) = cInit + (allRevenue + cAnnual + cAnnualG * sinkingFundPaymentArithmetic(x, cLife)) * seriesPresentAmountFactor(x, cLife)
find_zero(IRR_C_eq, MARR)

# CB
ΔCB_Init = cInit - bInit
ΔCB_Scrap = cScrap - bScrap
ΔCB_Annual_eq(x) = cAnnual + cAnnualG * sinkingFundPaymentArithmetic(x, cLife) - bAnnual
ΔCB_IRR_eq(x) = ΔCB_Init + ΔCB_Scrap * presentWorthFactor(x, aLife) + ΔCB_Annual_eq(x) * seriesPresentAmountFactor(x, aLife)
xVec = collect(0:0.01:1)
yVec = zeros(size(xVec))
plot(xVec, ΔCB_IRR_eq.(xVec))
plot!(xVec, yVec)

find_zero(ΔCB_IRR_eq, MARR)

# AC
ΔAC_Init = aInit - cInit
ΔAC_Scrap = aScrap - cScrap
ΔAC_Annual_eq(x) = aAnnual - (cAnnual + cAnnualG * sinkingFundPaymentArithmetic(x, cLife))
ΔAC_IRR_eq(x) = ΔAC_Init + ΔAC_Scrap * presentWorthFactor(x, aLife) + ΔAC_Annual_eq(x) * seriesPresentAmountFactor(x, aLife)

plot(xVec, ΔAC_IRR_eq.(xVec))
plot!(xVec, yVec)

find_zero(ΔCB_IRR_eq, MARR)


using Flux.Tracker, Statistics, DelimitedFiles
using Flux.Tracker: Params, gradient, update!
using Flux: gpu

# This replicates the housing data example from the Knet.jl readme. Although we
# could have reused more of Flux (see the mnist example), the library's
# abstractions are very lightweight and don't force you into any particular
# strategy.

cd(@__DIR__)

isfile("housing.data") ||
  download("https://raw.githubusercontent.com/MikeInnes/notebooks/master/housing.data",
           "housing.data")

rawdata = readdlm("housing.data")'

# The last feature is our target -- the price of the house.

x = rawdata[1:13,:] |> gpu
y = rawdata[14:14,:] |> gpu

# Normalise the data
x = (x .- mean(x, dims = 2)) ./ std(x, dims = 2)

# The model

W = param(randn(1,13)/10) |> gpu
b = param([0.]) |> gpu

predict(x) = W*x .+ b
meansquarederror(ŷ, y) = sum((ŷ .- y).^2)/size(y, 2)
loss(x, y) = meansquarederror(predict(x), y)

η = 0.1
θ = Params([W, b])

for i = 1:10
  g = gradient(() -> loss(x, y), θ)
  for x in θ
    update!(x, -g[x]*η)
  end
  @show loss(x, y)
end

predict(x[:,1]) / y[1]


## Julia program for Adaptive Rejection Sampling
## author: weiya <szcfweiya@gmail.com>
## date: 2018-08-20
##
## refer to Inferentialist's R code:
##   https://blog.inferentialist.com/2016/09/26/adaptive-sampling.html

module corears
# example 3.22 in Davison(2008)
r = 2; m = 10; mu = 0; sig2 = 1;
# range of y
yl = -2; yu = 2;
# function of h
function h(y)
    return(r * y - m * log(1 + exp(y)) - (y - mu)^2 / (2 * sig2))
end

function h(y::Array)
    return(r * y - m * log.(1 .+ exp.(y)) .- (y .- mu) .^2 ./ (2 * sig2))
end

# derivative of h
function dh(y)
    return(r - m * exp(y) / (1 + exp(y)) - (y - mu) / sig2)
end

function dh(y::Array)
    return(r .- m * exp.(y) ./ (1 .+ exp.(y)) .- (y .- mu)./sig2)
end

# intersection point
function zfix(yfixed::Array)
    yf0 = yfixed[1:end-1]
    yf1 = yfixed[2:end]
    zfixed = yf0 .+ (h(yf0) .- h(yf1) .+ (yf1 .- yf0) .* dh(yf1)) ./ (dh(yf1) .- dh(yf0))
    return(zfixed)
end

# evaluate log-density
function hplus(y, yfixed::Array)
    zfixed = zfix(yfixed)
    n = size(zfixed, 1)
    for i = 1:n
        if i == 1 && y < zfixed[i]
            return(h(yfixed[i]) + (y - yfixed[i]) * dh(yfixed[i]))
        elseif i < n && y >= zfixed[i] && y < zfixed[i+1]
            return(h(yfixed[i+1]) + (y - yfixed[i+1]) * dh(yfixed[i+1]))
        elseif i == n && y >= zfixed[n]
            return(h(yfixed[i+1]) + (y - yfixed[i+1]) * dh(yfixed[i+1]))
        end
    end
end

# calculate G_+(z_i)
function gplus_cdf(yfixed::Array, zfixed::Array)
    n = size(zfixed, 1)
    s = zeros(n+1)
    pr = zeros(n+1)
    for i = 1:(n+1)
        ## integral from -infty to zi
        if i == 1
        #    s[i] = exp(h(yfixed[i])) / dh(yfixed[i]) * (exp((zfixed[i]-yfixed[i]) * dh(yfixed[i])) - )
            s[i] = exp(h(yfixed[i])) / dh(yfixed[i]) * (exp((zfixed[i]-yfixed[i]) * dh(yfixed[i])) - exp((yl-yfixed[i]) * dh(yfixed[i])))
        elseif i == n+1
            s[i] = exp(h(yfixed[i])) / dh(yfixed[i]) * (exp((yu-yfixed[i]) * dh(yfixed[i])) - exp((zfixed[n]-yfixed[i]) * dh(yfixed[i])))
        else
            s[i] = exp(h(yfixed[i])) / dh(yfixed[i]) * (exp((zfixed[i]-yfixed[i]) * dh(yfixed[i])) - exp((zfixed[i]-yfixed[i]) * dh(yfixed[i])))
        end
    end
    pr = s / sum(s)
    return cumsum(pr), sum(s)
end

# sample from gplus density
function gplus_sample(yfixed::Array)
    zfixed = zfix(yfixed)
    gp = gplus_cdf(yfixed, zfixed)
    zpr = gp[1]
    norm_const = gp[2]
    n = size(zfixed, 1)
    u = rand()
    # Invert the gplus pdf
    for i = 1:n
        if i == 1 && u < zpr[i]
            ey = u * dh(yfixed[i]) * norm_const / exp(h(yfixed[i])) + exp((yl-yfixed[i])*dh(yfixed[i]))
            return(yfixed[i] + log(ey)/dh(yfixed[i]))
        elseif i == n && u >= zpr[i]
            ey = (u - zpr[i]) * dh(yfixed[i+1]) * norm_const / exp(h(yfixed[i+1])) + exp((zfixed[i]-yfixed[i+1])*dh(yfixed[i+1]))
            return(yfixed[i+1] + log(ey)/dh(yfixed[i+1]))
        elseif i < n && u >= zpr[i] && u < zpr[i+1]
            ey = (u - zpr[i]) * dh(yfixed[i+1]) * norm_const / exp(h(yfixed[i+1])) + exp((zfixed[i]-yfixed[i+1])*dh(yfixed[i+1]))
            return(yfixed[i+1] + log(ey)/dh(yfixed[i+1]))
        end
    end
end

## adaptive rejection sampling
function ars(yfixed::Array)
    x = gplus_sample(yfixed)
    u = rand()
    if u <= exp(h(x)-hplus(x, yfixed))
        return(x)
    else
        return(ars(append!(yfixed, x)))
    end
end

## example
N = 100
res = ones(N);
for i = 1:N
    res[i] = ars([-1.8,-1.1,-0.5,-0.2])
end

export zfix, hplus, gplus_cdf, gplus_sample
end


using LightGraphs
using GraphPlot

function visible_graph(x)
    N = length(x)
    g = SimpleGraph(N)
    for i = 1:N
        for j = i+1:N
            cnt = 0
            for k = i+1:j-1
                if x[k]>=x[j]+(x[i]-x[j])*(j-k)/(j-i)
                    cnt = 1
                    break
                end
            end
            if cnt == 0
                add_edge!(g,i,j)
            end
        end
    end
    return g
end


# frexp,ldexp,significand,exponent
@test frexp(12.8) == (0.8,4)
@test ldexp(0.8,4) == 12.8
@test significand(12.8) == 1.6
@test exponent(12.8) == 3

# degree-based trig functions
for T = (Float32,Float64)
    for x = -400:40:400
        @test_approx_eq_eps sind(convert(T,x))::T convert(T,sin(pi/180*x)) eps(deg2rad(convert(T,x)))
        @test_approx_eq_eps cosd(convert(T,x))::T convert(T,cos(pi/180*x)) eps(deg2rad(convert(T,x)))
    end
    for x = 0.0:180:720
        @test sind(convert(T,x)) === zero(T)
        @test sind(-convert(T,x)) === -zero(T)
    end

    for x = -3:0.3:3
        @test_approx_eq_eps sinpi(convert(T,x))::T convert(T,sin(pi*x)) eps(pi*convert(T,x))
        @test_approx_eq_eps cospi(convert(T,x))::T convert(T,cos(pi*x)) eps(pi*convert(T,x))
    end
    for x = 0.0:1.0:4.0
        @test sinpi(convert(T,x)) === zero(T)
        @test sinpi(-convert(T,x)) === -zero(T)
    end
end

# check type stability
for T = (Float32,Float64,BigFloat)
    for f = (sind,cosd,sinpi,cospi)
        @test Base.return_types(f,(T,)) == [T]
    end
end

# error functions
@test_approx_eq erf(1) 0.84270079294971486934
@test_approx_eq erfc(1) 0.15729920705028513066
@test_approx_eq erfcx(1) 0.42758357615580700442
@test_approx_eq erfi(1) 1.6504257587975428760
@test_approx_eq erfinv(0.84270079294971486934) 1
@test_approx_eq erfcinv(0.15729920705028513066) 1
@test_approx_eq dawson(1) 0.53807950691276841914
# TODO: complex versions only supported on 64-bit for now
@unix_only if WORD_SIZE==64
    @test_approx_eq erf(1+2im) -0.53664356577856503399-5.0491437034470346695im
    @test_approx_eq erfc(1+2im) 1.5366435657785650340+5.0491437034470346695im
    @test_approx_eq erfcx(1+2im) 0.14023958136627794370-0.22221344017989910261im
    @test_approx_eq erfi(1+2im) -0.011259006028815025076+1.0036063427256517509im
    @test_approx_eq dawson(1+2im) -13.388927316482919244-11.828715103889593303im
end
for x in logspace(-200, -0.01)
    @test_approx_eq_eps erf(erfinv(x)) x 1e-12*x
    @test_approx_eq_eps erf(erfinv(-x)) -x 1e-12*x
    @test_approx_eq_eps erfc(erfcinv(2*x)) 2*x 1e-12*x
    if x > 1e-20
        xf = float32(x)
        @test_approx_eq_eps erf(erfinv(xf)) xf 1e-5*xf
        @test_approx_eq_eps erf(erfinv(-xf)) -xf 1e-5*xf
        @test_approx_eq_eps erfc(erfcinv(2xf)) 2xf 1e-5*xf
    end
end

# airy
@test_approx_eq airy(1.8) 0.0470362168668458052247
@test_approx_eq airyprime(1.8) -0.0685247801186109345638
@test_approx_eq airybi(1.8) 2.595869356743906290060
@test_approx_eq airybiprime(1.8) 2.98554005084659907283
@test_throws Base.Math.AmosException airy(200im)
@test_throws Base.Math.AmosException airybi(200)
z = 1.8 + 1.0im
@test_approx_eq airyx(0, z) airy(0, z) * exp(2/3 * z * sqrt(z))
@test_approx_eq airyx(1, z) airy(1, z) * exp(2/3 * z * sqrt(z))
@test_approx_eq airyx(2, z) airy(2, z) * exp(-abs(real(2/3 * z * sqrt(z))))
@test_approx_eq airyx(3, z) airy(3, z) * exp(-abs(real(2/3 * z * sqrt(z))))

# besselh
true_h133 = 0.30906272225525164362 - 0.53854161610503161800im
@test_approx_eq besselh(3,1,3) true_h133
@test_approx_eq besselh(-3,1,3) -true_h133
@test_approx_eq besselh(3,2,3) conj(true_h133)
@test_approx_eq besselh(-3,2,3) -conj(true_h133)
@test_throws Base.Math.AmosException besselh(1,0)

# besseli
true_i33 = 0.95975362949600785698
@test_approx_eq besseli(3,3) true_i33
@test_approx_eq besseli(-3,3) true_i33
@test_approx_eq besseli(3,-3) -true_i33
@test_approx_eq besseli(-3,-3) -true_i33
@test_throws Base.Math.AmosException besseli(1,1000)

# besselj
@test besselj(0,0) == 1
for i = 1:5
    @test besselj(i,0) == 0
    @test besselj(-i,0) == 0
end

j33 = besselj(3,3.)
@test besselj(3,3) == j33
@test besselj(-3,-3) == j33
@test besselj(-3,3) == -j33
@test besselj(3,-3) == -j33

j43 = besselj(4,3.)
@test besselj(4,3) == j43
@test besselj(-4,-3) == j43
@test besselj(-4,3) == j43
@test besselj(4,-3) == j43

@test_approx_eq j33 0.30906272225525164362
@test_approx_eq j43 0.13203418392461221033
@test_throws DomainError    besselj(0.1, -0.4)
@test_approx_eq besselj(0.1, complex(-0.4)) 0.820421842809028916 + 0.266571215948350899im
@test_approx_eq besselj(3.2, 1.3+0.6im) 0.01135309305831220201 + 0.03927719044393515275im
@test_approx_eq besselj(1, 3im) 3.953370217402609396im
@test_throws Base.Math.AmosException besselj(20,1000im)

# besselk
true_k33 = 0.12217037575718356792
@test_approx_eq besselk(3,3) true_k33
@test_approx_eq besselk(-3,3) true_k33
true_k3m3 = -0.1221703757571835679 - 3.0151549516807985776im
@test_throws DomainError besselk(3,-3)
@test_approx_eq besselk(3,complex(-3)) true_k3m3
@test_approx_eq besselk(-3,complex(-3)) true_k3m3
@test_throws Base.Math.AmosException besselk(200,0.01)
# issue #6564
@test besselk(1.0,0.0) == Inf

# bessely
y33 = bessely(3,3.)
@test bessely(3,3) == y33
@test_approx_eq bessely(-3,3) -y33
@test_approx_eq y33 -0.53854161610503161800
@test_throws DomainError bessely(3,-3)
@test_approx_eq bessely(3,complex(-3)) 0.53854161610503161800 - 0.61812544451050328724im
@test_throws Base.Math.AmosException bessely(200.5,0.1)

# issue #6653
for f in (besselj,bessely,besseli,besselk,hankelh1,hankelh2)
    @test_approx_eq f(0,1) f(0,complex128(1))
    @test_approx_eq f(0,1) f(0,complex64(1))
end

# scaled bessel[ijky] and hankelh[12]
for x in (1.0, 0.0, -1.0), y in (1.0, 0.0, -1.0), nu in (1.0, 0.0, -1.0)
    z = complex128(x + y * im)
    z == zero(z) || @test_approx_eq hankelh1x(nu, z) hankelh1(nu, z) * exp(-z * im)
    z == zero(z) || @test_approx_eq hankelh2x(nu, z) hankelh2(nu, z) * exp(z * im)
    (nu < 0 && z == zero(z)) || @test_approx_eq besselix(nu, z) besseli(nu, z) * exp(-abs(real(z)))
    (nu < 0 && z == zero(z)) || @test_approx_eq besseljx(nu, z) besselj(nu, z) * exp(-abs(imag(z)))
    z == zero(z) || @test_approx_eq besselkx(nu, z) besselk(nu, z) * exp(z)
    z == zero(z) || @test_approx_eq besselyx(nu, z) bessely(nu, z) * exp(-abs(imag(z)))
end
@test_throws Base.Math.AmosException hankelh1x(1, 0)
@test_throws Base.Math.AmosException hankelh2x(1, 0)
@test_throws Base.Math.AmosException besselix(-1, 0)
@test_throws Base.Math.AmosException besseljx(-1, 0)
@test besselkx(1, 0) == Inf
@test_throws Base.Math.AmosException besselyx(1, 0)

# beta, lbeta
@test_approx_eq beta(3/2,7/2) 5π/128
@test_approx_eq beta(3,5) 1/105
@test_approx_eq lbeta(5,4) log(beta(5,4))
@test_approx_eq beta(5,4) beta(4,5)
@test_approx_eq beta(-1/2, 3) -16/3
@test_approx_eq lbeta(-1/2, 3) log(16/3)

# gamma, lgamma (complex argument)
@test gamma(1:25) == gamma(Float64[1:25])
@test_approx_eq gamma(1/2) sqrt(π)
@test_approx_eq gamma(-1/2) -2sqrt(π)
@test_approx_eq lgamma(-1/2) log(abs(gamma(-1/2)))
@test_approx_eq lgamma(1.4+3.7im) -3.7094025330996841898 + 2.4568090502768651184im
@test_approx_eq lgamma(1.4+3.7im) log(gamma(1.4+3.7im))

# digamma
for elty in (Float32, Float64)

    @test_approx_eq digamma(convert(elty, 9)) convert(elty, 2.140641477955609996536345)
    @test_approx_eq digamma(convert(elty, 2.5)) convert(elty, 0.7031566406452431872257)
    @test_approx_eq digamma(convert(elty, 0.1)) convert(elty, -10.42375494041107679516822)
    @test_approx_eq digamma(convert(elty, 7e-4)) convert(elty, -1429.147493371120205005198)
    @test_approx_eq digamma(convert(elty, 7e-5)) convert(elty, -14286.29138623969227538398)
    @test_approx_eq digamma(convert(elty, 7e-6)) convert(elty, -142857.7200612932791081972)
    @test_approx_eq digamma(convert(elty, 2e-6)) convert(elty, -500000.5772123750382073831)
    @test_approx_eq digamma(convert(elty, 1e-6)) convert(elty, -1000000.577214019968668068)
    @test_approx_eq digamma(convert(elty, 7e-7)) convert(elty, -1428572.005785942019703646)
    @test_approx_eq digamma(convert(elty, -0.5)) convert(elty, .03648997397857652055902367)
    @test_approx_eq digamma(convert(elty, -1.1)) convert(elty,  10.15416395914385769902271)

    @test_approx_eq digamma(convert(elty, 0.1)) convert(elty, -10.42375494041108)
    @test_approx_eq digamma(convert(elty, 1/2)) convert(elty, -γ - log(4))
    @test_approx_eq digamma(convert(elty, 1)) convert(elty, -γ)
    @test_approx_eq digamma(convert(elty, 2)) convert(elty, 1 - γ)
    @test_approx_eq digamma(convert(elty, 3)) convert(elty, 3/2 - γ)
    @test_approx_eq digamma(convert(elty, 4)) convert(elty, 11/6 - γ)
    @test_approx_eq digamma(convert(elty, 5)) convert(elty, 25/12 - γ)
    @test_approx_eq digamma(convert(elty, 10)) convert(elty, 7129/2520 - γ)
end

# trigamma
for elty in (Float32, Float64)
    @test_approx_eq trigamma(convert(elty, 0.1)) convert(elty, 101.433299150792758817)
    @test_approx_eq trigamma(convert(elty, 1/2)) convert(elty, π^2/2)
    @test_approx_eq trigamma(convert(elty, 1)) convert(elty, π^2/6)
    @test_approx_eq trigamma(convert(elty, 2)) convert(elty, π^2/6 - 1)
    @test_approx_eq trigamma(convert(elty, 3)) convert(elty, π^2/6 - 5/4)
    @test_approx_eq trigamma(convert(elty, 4)) convert(elty, π^2/6 - 49/36)
    @test_approx_eq trigamma(convert(elty, 5)) convert(elty, π^2/6 - 205/144)
    @test_approx_eq trigamma(convert(elty, 10)) convert(elty, π^2/6 - 9778141/6350400)
end

# invdigamma
for elty in (Float32, Float64)
    for val in [0.001, 0.01, 0.1, 1.0, 10.0]
        @test abs(invdigamma(digamma(convert(elty, val))) - convert(elty, val)) < 1e-8
    end
end

@test_approx_eq polygamma(20, 7.) -4.644616027240543262561198814998587152547

# eta, zeta
@test_approx_eq eta(1) log(2)
@test_approx_eq eta(2) pi^2/12
@test_approx_eq zeta(0) -0.5
@test_approx_eq zeta(2) pi^2/6
@test_approx_eq zeta(4) pi^4/90

# quadgk
@test_approx_eq quadgk(cos, 0,0.7,1)[1] sin(1)
@test_approx_eq quadgk(x -> exp(im*x), 0,0.7,1)[1] (exp(1im)-1)/im
@test_approx_eq quadgk(x -> exp(im*x), 0,1im)[1] -1im*expm1(-1)
@test_approx_eq_eps quadgk(cos, 0,BigFloat(1),order=40)[1] sin(BigFloat(1)) 1000*eps(BigFloat)
@test_approx_eq quadgk(x -> exp(-x), 0,0.7,Inf)[1] 1.0
@test_approx_eq quadgk(x -> exp(x), -Inf,0)[1] 1.0
@test_approx_eq quadgk(x -> exp(-x^2), -Inf,Inf)[1] sqrt(pi)
@test_approx_eq quadgk(x -> [exp(-x), exp(-2x)], 0, Inf)[1] [1,0.5]
@test_approx_eq quadgk(cos, 0,0.7,1, norm=abs)[1] sin(1)

# Ensure subnormal flags functions don't segfault
@test any(ccall("jl_zero_subnormals", Uint8, (Uint8,), 1) .== [0x00 0x01])
@test any(ccall("jl_zero_subnormals", Uint8, (Uint8,), 0) .== [0x00 0x01])

# isqrt (issue #4884)
@test isqrt(9223372030926249000) == 3037000498
@test isqrt(typemax(Int128)) == int128("13043817825332782212")
@test isqrt(int128(typemax(Int64))^2-1) == 9223372036854775806
@test isqrt(0) == 0
for i = 1:1000
    n = rand(Uint128)
    s = isqrt(n)
    @test s*s <= n
    @test (s+1)*(s+1) > n
    n = rand(Uint64)
    s = isqrt(n)
    @test s*s <= n
    @test (s+1)*(s+1) > n
end

# useful test functions for relative error
err(z, x) = abs(z - x) / abs(x)
errc(z, x) = max(err(real(z),real(x)), err(imag(z),imag(x)))

for x in -10.2:0.3456:50
    @test 1e-12 > err(digamma(x+0im), digamma(x))
end

# digamma, trigamma, polygamma & zeta test cases (compared to Wolfram Alpha)
@test 1e-13 > err(digamma(7+0im), 1.872784335098467139393487909917597568957840664060076401194232)
@test 1e-13 > errc(digamma(7im), 1.94761433458434866917623737015561385331974500663251349960124 + 1.642224898223468048051567761191050945700191089100087841536im)
@test 1e-13 > errc(digamma(-3.2+0.1im), 4.65022505497781398615943030397508454861261537905047116427511+2.32676364843128349629415011622322040021960602904363963042380im)
@test 1e-13 > err(trigamma(8+0im), 0.133137014694031425134546685920401606452509991909746283540546)
@test 1e-13 > errc(trigamma(8im), -0.0078125000000000000029194973110119898029284994355721719150 - 0.12467345030312762782439017882063360876391046513966063947im)
@test 1e-13 > errc(trigamma(-3.2+0.1im), 15.2073506449733631753218003030676132587307964766963426965699+15.7081038855113567966903832015076316497656334265029416039199im)
@test 1e-13 > err(polygamma(2, 8.1+0im), -0.01723882695611191078960494454602091934457319791968308929600)
@test 1e-13 > errc(polygamma(30, 8.1+2im), -2722.8895150799704384107961215752996280795801958784600407589+6935.8508929338093162407666304759101854270641674671634631058im)
@test 1e-13 > errc(polygamma(3, 2.1+1im), 0.00083328137020421819513475400319288216246978855356531898998-0.27776110819632285785222411186352713789967528250214937861im)
@test 1e-11 > err(polygamma(3, -4.2 + 2im),-0.0037752884324358856340054736472407163991189965406070325067-0.018937868838708874282432870292420046797798431078848805822im)
@test 1e-13 > err(polygamma(13, 5.2 - 2im), 0.08087519202975913804697004241042171828113370070289754772448-0.2300264043021038366901951197725318713469156789541415899307im)
@test 1e-11 > err(polygamma(123, -47.2 + 0im), 5.7111648667225422758966364116222590509254011308116701029e291)
@test 1e-13 > errc(zeta(4.1+0.3im, -3.2+0.1im), -461.95403678374488506025596495576748255121001107881278765917+926.02552636148651929560277856510991293536052745360005500774im)
@test 1e-13 > errc(zeta(4.1+0.3im, 3.2+0.1im), 0.0121197525131633219465301571139288562254218365173899270675-0.00687228692565614267981577154948499247518236888933925740902im)
@test 1e-13 > errc(zeta(4.1, 3.2+0.1im),0.0137637451187986846516125754047084829556100290057521276517-0.00152194599531628234517456529686769063828217532350810111482im)
@test 1e-12 > errc(zeta(1.0001, -4.5e2+3.2im), 9993.89099199843392251301993718413132850540848778561412270571-3.13257480938495907945892330398176989805350557816701044268548im)
@test_throws DomainError zeta(3.1,-4.2)
@test 1e-13 > errc(zeta(3.1,-4.2+0im), -138.06320182025311080661516120845508778572835942189570145952+45.586579397698817209431034568162819207622092308850063038062im)
@test 1e-15 > errc(zeta(3.1+0im,-4.2), zeta(3.1,-4.2+0im))
@test 1e-13 > errc(zeta(3.1,4.2), 0.029938344862645948405021260567725078588893266227472565010234)
@test 1e-13 > err(zeta(27, 3.1), 5.413318813037879056337862215066960774064332961282599376e-14)
@test 1e-13 > err(zeta(27, 2), 7.4507117898354294919810041706041194547190318825658299932e-9)
@test 1e-12 > err(zeta(27, -105.3), -1.311372652244914148556295810515903234635727465138859603e14)
@test polygamma(4, -3.1+Inf*im) == polygamma(4, 3.1+Inf*im) == 0
@test polygamma(4, -0.0) == Inf == -polygamma(4, +0.0)
@test zeta(4, +0.0) == Inf == zeta(4, -0.0)
@test zeta(5, +0.0) == Inf == -zeta(5, -0.0)
@test isa([digamma(x) for x in [1.0]], Vector{Float64})
@test isa([trigamma(x) for x in [1.0]], Vector{Float64})
@test isa([polygamma(3,x) for x in [1.0]], Vector{Float64})
@test 1e-13 > errc(zeta(2 + 1im, -1.1), zeta(2 + 1im, -1.1+0im))
@test 1e-13 > errc(zeta(2 + 1im, -1.1), -1525.8095173321060982383023516086563741006869909580583246557 + 1719.4753293650912305811325486980742946107143330321249869576im)

@test @evalpoly(2,3,4,5,6) == 3+2*(4+2*(5+2*6)) == @evalpoly(2+0im,3,4,5,6)

@test 1e-14 > err(eta(1+1e-9), 0.693147180719814213126976796937244130533478392539154928250926)
@test 1e-14 > err(eta(1+5e-3), 0.693945708117842473436705502427198307157819636785324430166786)
@test 1e-13 > err(eta(1+7.1e-3), 0.694280602623782381522315484518617968911346216413679911124758)
@test 1e-13 > err(eta(1+8.1e-3), 0.694439974969407464789106040237272613286958025383030083792151)
@test 1e-13 > err(eta(1 - 2.1e-3 + 2e-3 * im), 0.69281144248566007063525513903467244218447562492555491581+0.00032001240133205689782368277733081683574922990400416791019im)
@test 1e-13 > err(eta(1 + 5e-3 + 5e-3 * im), 0.69394652468453741050544512825906295778565788963009705146+0.00079771059614865948716292388790427833787298296229354721960im)
@test 1e-12 > errc(zeta(1e-3+1e-3im), -0.5009189365276307665899456585255302329444338284981610162-0.0009209468912269622649423786878087494828441941303691216750im)
@test 1e-13 > errc(zeta(1e-4 + 2e-4im), -0.5000918637469642920007659467492165281457662206388959645-0.0001838278317660822408234942825686513084009527096442173056im)

# Issue #7169: (TODO: better accuracy should be possible?)
@test 1e-9 > errc(zeta(0 + 99.69im), 4.67192766128949471267133846066040655597942700322077493021802+3.89448062985266025394674304029984849370377607524207984092848im)
@test 1e-12 > errc(zeta(3 + 99.69im), 1.09996958148566565003471336713642736202442134876588828500-0.00948220959478852115901654819402390826992494044787958181148im)
@test 1e-9 > errc(zeta(-3 + 99.69im), 10332.6267578711852982128675093428012860119184786399673520976+13212.8641740351391796168658602382583730208014957452167440726im)
@test 1e-13 > errc(zeta(2 + 99.69im, 1.3), 0.41617652544777996034143623540420694985469543821307918291931-0.74199610821536326325073784018327392143031681111201859489991im)


@testset "cme.jl" begin
    @testset "estimate and statistic" begin
        kernel = (ExponentialKernel() ∘ ScaleTransform(0.1)) ⊗ WhiteKernel()

        function deviation(testprediction, testtarget, prediction, target)
            testlocation = (testprediction, testtarget)
            return mapreduce(+, prediction, 1:length(prediction)) do p, t
                ((t == target) - p) * kernel(testlocation, (prediction, t))
            end
        end

        for nclasses in (2, 10, 100), nsamples in (10, 50, 100)
            # define estimator (sample test locations uniformly)
            dist = Dirichlet(nclasses, 1)
            testpredictions = [rand(dist) for _ in 1:(nsamples ÷ 10)]
            testtargets = rand(1:nclasses, nsamples ÷ 10)
            estimator = UCME(kernel, testpredictions, testtargets)

            # sample predictions and targets
            predictions = [rand(dist) for _ in 1:nsamples]
            targets_consistent = [
                rand(Categorical(prediction)) for prediction in predictions
            ]
            targets_onlyone = ones(Int, length(predictions))

            # compute calibration error estimate and test statistic
            for targets in (targets_consistent, targets_onlyone)
                test = AsymptoticCMETest(estimator, predictions, targets)

                @test test.kernel == kernel
                @test test.nsamples == nsamples
                @test test.ntestsamples == nsamples ÷ 10
                @test test.estimate ≈ estimator(predictions, targets)

                deviations =
                    deviation.(testpredictions', testtargets', predictions, targets)
                mean_deviations = vec(mean(deviations; dims=1))
                @test test.mean_deviations ≈ mean_deviations

                # use of `inv` can lead to slightly different results
                S = cov(deviations; dims=1)
                statistic = nsamples * mean_deviations' * inv(S) * mean_deviations
                @test test.statistic ≈ statistic rtol = 1e-4
            end
        end
    end

    @testset "consistency" begin
        kernel = (ExponentialKernel() ∘ ScaleTransform(0.1)) ⊗ WhiteKernel()
        αs = 0.05:0.1:0.95
        nsamples = 100

        pvalues_consistent = Vector{Float64}(undef, 100)
        pvalues_onlyone = similar(pvalues_consistent)

        for nclasses in (2, 10)
            # create estimator (sample test locations uniformly)
            dist = Dirichlet(nclasses, 1)
            testpredictions = [rand(dist) for _ in 1:5]
            testtargets = rand(1:nclasses, 5)
            estimator = UCME(kernel, testpredictions, testtargets)

            predictions = [Vector{Float64}(undef, nclasses) for _ in 1:nsamples]
            targets_consistent = Vector{Int}(undef, nsamples)
            targets_onlyone = ones(Int, nsamples)

            for i in eachindex(pvalues_consistent)
                # sample predictions and targets
                for j in 1:nsamples
                    rand!(dist, predictions[j])
                    targets_consistent[j] = rand(Categorical(predictions[j]))
                end

                # define test
                test_consistent = AsymptoticCMETest(
                    estimator, predictions, targets_consistent
                )
                test_onlyone = AsymptoticCMETest(estimator, predictions, targets_onlyone)

                # estimate pvalues
                pvalues_consistent[i] = pvalue(test_consistent)
                pvalues_onlyone[i] = pvalue(test_onlyone)
            end

            # compute empirical test errors
            ecdf_consistent = ecdf(pvalues_consistent)
            @test all(ecdf_consistent(α) < α + 0.15 for α in αs)
            @test all(p < 0.05 for p in pvalues_onlyone)
        end
    end
end


using SpectralKit: to_domain, from_domain

@testset "Chebyshev bounded" begin
    @test_throws DomainError BoundedLinear(-1.0, Inf)
    @test_throws DomainError BoundedLinear(-1.0, -2.0)

    f(x) = exp(-3*x)
    f′(x) = -3*f(x)
    A, B = 1, 5
    N = 11
    grid_dense = range(A, B, length = 100)
    trans = BoundedLinear(A, B)

    for grid_kind in (InteriorGrid(), EndpointGrid())
        basis0 = Chebyshev(grid_kind, N)
        basis = univariate_basis(Chebyshev, grid_kind, N, trans)
        @test dimension(basis) == N
        @test domain(basis) == (A, B)

        @testset "transformation" begin
            for i in 1:100
                x = rand_in_domain(i, A, B)
                @test from_domain(trans, basis0, to_domain(trans, basis0, x)) ≈ x
            end
        end

        g = @inferred grid(basis)
        @test length(g) == N
        C = collocation_matrix(basis, g)
        θ = C \ f.(g)

        if grid_kind ≡ InteriorGrid()
            @test all(A .< g .< B)
            @test maximum(x -> abs(linear_combination(basis, θ, x) - f(x)),
                          grid_dense) ≤ 1e-5
            @test maximum(x -> abs(derivative(linear_combination(basis, θ), x) - f′(x)),
                          grid_dense) ≤ 5e-4
        else
            @test all(A .≤ g .≤ B)
            @test maximum(x -> abs(linear_combination(basis, θ, x) - f(x)),
                          grid_dense) ≤ 1e-5
            @test maximum(x -> abs(derivative(linear_combination(basis, θ), x) - f′(x)),
                          grid_dense) ≤ 5e-4
        end
    end
end

@testset "Chebyshev semi-infinite" begin
    @test_throws DomainError SemiInfRational(-1.0, Inf)
    @test_throws DomainError SemiInfRational(-1.0, 0.0)
    @test_throws DomainError SemiInfRational(NaN, 2.0)

    f(x) = exp(-3*x)
    f′(x) = -3*f(x)
    grid_dense = range(3.1, 8, length = 100)

    A = 3.0
    L = 4.0
    N = 11
    trans = SemiInfRational(A, L)

    @testset "example mappings" begin
        basis0 = Chebyshev(InteriorGrid(), 10)
        @test from_domain(trans, basis0, 0) ≈ A + L
        @test from_domain(trans, basis0, -0.5) ≈ A + L / 3
        @test from_domain(trans, basis0, 0.5) ≈ A + 3 * L
    end

    for grid_kind in (InteriorGrid(), EndpointGrid())
        basis0 = Chebyshev(grid_kind, N)
        basis = univariate_basis(Chebyshev, grid_kind, N, trans)
        @test dimension(basis) == N
        @test domain(basis) == (A, Inf)

        @testset "transformation" begin
            for i in 1:100
                x = rand_in_domain(i, A, Inf)
                @test from_domain(trans, basis0, to_domain(trans, basis0, x)) ≈ x
            end
        end

        g = grid(basis)
        @test length(g) == N
        C = collocation_matrix(basis, g)
        θ = C \ f.(g)

        if grid_kind ≡ InteriorGrid()
            @test sum(g .< A + L) == 5
            @test all(A .< g .< Inf)
            @test maximum(x -> abs(linear_combination(basis, θ, x) - f(x)),
                          grid_dense) ≤ 1e-8
            @test maximum(x -> abs(derivative(linear_combination(basis, θ), x) - f′(x)),
                          grid_dense) ≤ 1e-7
        else

            @test sum(g .< A + L) == 5
            @test all(A .≤ g .≤ Inf)
            @test maximum(x -> abs(linear_combination(basis, θ, x) - f(x)),
                          grid_dense) ≤ 1e-8
            @test maximum(x -> abs(derivative(linear_combination(basis, θ), x) - f′(x)),
                          grid_dense) ≤ 1e-7
        end
    end
end

@testset "Chebyshev infinite" begin
    @test_throws DomainError InfRational(1.0, Inf)
    @test_throws DomainError InfRational(1.0, 0.0)
    @test_throws DomainError InfRational(1.0, -2.0)
    @test_throws DomainError InfRational(NaN, 1)

    f(x) = exp(-4*abs2(x))
    f′(x) = -8*x*f(x)
    grid_dense = range(-1, 2, length = 100)
    A = 0.0
    L = 1.0
    N = 20
    trans = InfRational(A, L)

    @testset "example mappings" begin
        basis0 = Chebyshev(InteriorGrid(), 10)
        @test from_domain(trans, basis0, 0) ≈ A
        @test from_domain(trans, basis0, -0.5) ≈ A - L / √3
        @test from_domain(trans, basis0, 0.5) ≈ A + L / √3
    end

    for grid_kind in (InteriorGrid(), EndpointGrid())
        basis0 = Chebyshev(grid_kind, N)
        basis = univariate_basis(Chebyshev, grid_kind, N, trans)
        @test dimension(basis) == N
        @test domain(basis) == (-Inf, Inf)

        @testset "transformation" begin
            for i in 1:100
                x = rand_in_domain(i, -Inf, Inf)
                @test from_domain(trans, basis0, to_domain(trans, basis0, x)) ≈ x
            end
        end

        g = grid(basis)
        @test length(g) == N
        C = collocation_matrix(basis, g)
        θ = C \ f.(g)

        if grid_kind ≡ InteriorGrid()
            @test sum(g .< A) == 10
            @test all(-Inf .< g .< Inf)
            @test maximum(x -> abs(linear_combination(basis, θ, x) - f(x)),
                          grid_dense) ≤ 1e-4
            @test maximum(x -> abs(derivative(linear_combination(basis, θ), x) - f′(x)),
                          grid_dense) ≤ 2e-3
        else
            @test sum(g .< A) == 10
            @test all(-Inf .≤ g .≤ Inf)
            @test maximum(x -> abs(linear_combination(basis, θ, x) - f(x)),
                          grid_dense) ≤ 1e-4
            @test maximum(x -> abs(derivative(linear_combination(basis, θ), x) - f′(x)),
                          grid_dense) ≤ 2e-3
        end
    end
end


using LinearMaps
using Magnus
using PyPlot

include("plot_convergence.jl")

b = -1.
ω = 2π
f = t -> sin(ω*t)
c = 10.
y0 = [1.0]

tmax = π
N = 200

t = linspace(0,tmax,N+1)
y = exp.(-(b*t - (cos.(ω*t)-1)/ω * c))*y0[1]

function plot_approx(t_approx,y_approx,subs,label)
    st = length(t_approx) > 100 ? "-" : ".-"
    subplot(subs[1])
    plot(t_approx, y_approx, st, label=label)
    subplot(subs[2])
    semilogy(t_approx, abs(y_approx-y), st, label=label)
end

Exp = ScalarExponentiator(y0)
midpoint = MidpointPropagator(t -> LinearMap(b + f(t)*c), -1, Exp)
cfet = CFET4BfCPropagator(LinearMap(b), f, LinearMap(c), -1, Exp)

figure("scalar",figsize=(10,14))
clf()
subplot(411)
for (prop,name) in [(midpoint,"Midpoint"),
                    (cfet, "CFET (25)")]
    result = integrate(y0, tmax, N, prop,
                       save_intermediate = true,
                       verbose = true)
    plot_approx(t, vec(result[:V]),
                [411,412], name)
end
legend(framealpha=0.75)
margins(0,1)
xlabel(L"t")
ylabel("Error")
subplot(411)
plot(t, y, "k--", label="Exact")
ylabel(L"y(t)")
margins(0,0.1)
gca()[:set_xticklabels]([])

Nl = 0,5
plot_convergence(y[end], y0, tmax, Nl..., midpoint, [413,414], "Midpoint")
plot_convergence(y[end], y0, tmax, Nl..., cfet, [413,414], "CFET (25)")
subplot(413)
Nl = logspace(Nl...,10)
plot(Nl,1./Nl, ":", label="Linear")
plot(Nl,1./Nl.^2, ":", label="Quadratic")
plot(Nl,1./Nl.^3, ":", label="Cubic")
plot(Nl,1./Nl.^4, ":", label="Quartic")
plot(Nl,1./Nl.^5, ":", label="Quintic")
plot(Nl,1./Nl.^6, ":", label="Sextic")

xscale("log")
yscale("log")
xlabel("Number of time steps")
ylabel("Error")
legend(framealpha=0.75,loc=4,ncol=2)

subplot(414)
xscale("log")
yscale("log")
xlabel("Error")
ylabel("Execution time [s]")
legend()
tight_layout()


#*******************************************************************************
#
#
#                Aplicação do split-operator em um sistema 2D
#               com condições de contorno tipo hardwall.
#
#*******************************************************************************
using PyPlot

# constantes físicas
Ry = 13.6056917253e3; # meV
a0 = 0.0529177208319; # nm
h2m = (Ry*a0^2); #  massa efetiva GaAs
hbar = 0.65821188926; # meV.ps

################################################################################
                    # Grid Bidimensional (Espaço real)
################################################################################
Lx = 1000; # Tamanho da dimensão "x" em nanometros (nm)
Ly = 400; # Tamanho da dimensão "y" em nanometros (nm)
Nx = div(Lx,2) # número de pontos na dimensão x
Ny = div(Ly,2) # número de pontos na dimensão y
# Nx = 2*Lx;
# Ny = 2*Ly;

x = linspace(-Lx/2, Lx/2, Nx+1)[1:end-1];
y = linspace(-Ly/2, Ly/2, Ny+1)[1:end-1];
dx = x[2] - x[1];
dy = y[2] - y[1];
X = kron(x', ones(Ny));
Y = kron(ones(Nx)', y);

################################################################################
                    # Espaço-k (na dimensãolongitudinal à tira)
################################################################################
kx = fftshift(linspace(-pi/dx, pi/dx, Nx+1)[1:end-1]); # Valores possíveis de k_x
k0x_max = pi/(2*dx);
k0y_max = pi/(2*dy);

################################################################################
                    # Condição Inicial: Pacote Gaussiano
################################################################################
x_0 = 0; #  centro do pacote
y_0 = 0; #  centro do pacote
sigma_x = 50.0; # largura longitudinal
sigma_y = 50.0; # largura transversal
k0_x = 0.1 * k0x_max; # momento inicial
k0_y = 0; # momento inicial
# Norm = ((pi^2)*(sigma_x*sigma_y)^2)^(-1/4); # Fator para normalização
Norm = 1 ; # Fator para pacote NÃO normalizado

# pacote de ondas na representação de posições:
Ψ_0 = Norm * exp(-(X-x_0).^2/(2*sigma_x^2)-(Y-y_0).^2/(2*sigma_y^2)
                    + 1im*(k0_x*X + k0_y*Y))
# fft!(Ψ_0,2)
#
# contourf(X,Y,fftshift(abs(Ψ_0).^2,2))
# ylim(-200,200)
################################################################################
                        # Hamiltoniano
################################################################################

# Potencial
α, β, γ = 5, 0.01, 0;
V_degrau = α./(e.^(-β*(X-γ))+1);

# pcolormesh(x,y,V_degrau)

# Cinética:
# matrizes Tridiagonais (Ny x Ny)
d2dy2 = 1/(dy^2) * SymTridiagonal(-2*ones(Ny), ones(Ny-1));
# ddy = 1/(2*dy) * Tridiagonal(-1 * ones(Ny-1), zeros(Ny), ones(Ny-1));

T_y  = - h2m * full(d2dy2)
T_kx =   h2m * kx

function psi_dt(ψ,Δt)
  # espaço de posições 'x'
  psi_spinor = ψ;
  psi_spinor = [exp(-1im*V_degrau[j,i]*Δt/(2*hbar))*psi_spinor[j,i] for j=1:Ny, i=1:Nx ];
  # representação mista (kx,y)
  psi_spinor = fft(psi_spinor,2);

  for i = 1:Nx
    psi_spinor[:,i] = expm(-1im*(T_kx[i] + T_y)*Δt/hbar)*psi_spinor[:,i] ;
  end

  # espaço de posições 'x'
  psi_spinor = ifft(psi_spinor,2);
  psi_spinor = [exp(-1im*V_degrau[j,i]*Δt/(2*hbar))*psi_spinor[j,i] for j=1:Ny, i=1:Nx ];

  return psi_spinor;
end

function psit(Ψ, t_final, N_steps = 20)
  tspan = linspace(0, t_final, N_steps);
  delta_t = tspan[2]-tspan[1];

  for i = 1:N_steps
    psi_t = psi_dt(Ψ,delta_t);
    Ψ = psi_t;
  end
  Ψ
end

function psit_livre(Ψ,Δt)
  fft!(Ψ,2)
  psi = copy(Ψ);
  for i = 1:Nx
    psi[:,i] = expm(-1im*(T_kx[i] + T_y)*Δt/hbar)*Ψ[:,i] ;
  end
  ifft(psi,2)
end

# ψ_1 = psit(Ψ_0, 0.01,5)
ψ_1 = psit_livre(Ψ_0, 0.01)
pcolormesh(x,y,abs(ψ_1).^2)
ylim(-200,200)
# plot(x,10.0^(-1)*V_x, linewidth=2.0);
# # ylim(-0.05,1.05);
# # xlim(-20,20)
# title("Gaussian distribution");
# xlabel(L"$x$");
# ylabel(L"$|\psi(x)|^2$");


n = rand(1:7)
A = matrixdepot("invol", n)
# A*A \approx eye(n)
@test_approx_eq_eps A*A eye(n,n) 1e-5
println("'invol' passed test...")


# Script that creates a animated figure of the spectrum of the periodogram
# estimator when trying to resolve two nearby frequencies.
# This highlights the resolution limit of the periodogram estimator.
#
# Parameters:
# N: number of measurements
# f1: fixed frequency of the first sinusoid
# f_ticks: varying frequency of the second sinusoid
# x: vector of the signal amplitudes of the two sinusoids

using Plots
include("periodogram.jl")

N = 10
f1 = 0.50
x =[1,1]
f_ticks=range(0.25,0.75,length=300)
f_range = range(0,1,length=200)

A(f,N) = exp.(2*pi*1im*kron(f,(0:N-1)))

anim = @animate for i=1:length(f_ticks)
    y = A([0.5 f_ticks[i]],N)*x
    spec=periodogram(y,f_range)
    plot(f_range,spec,xlim=(0,1), ylim=(0,1),
        xticks = 0:0.1:1, yticks = 0:0.1:1,
        xlabel="frequency",ylabel="|Y(f)|^2",
        title = "Periodogram",label="")
    plot!([f1 f_ticks[i]],seriestype="vline",
        label=["f1 = $(round(f1,digits=2))",
        "f2 = $(round(f_ticks[i],digits=2))"])
end every 1

gif(anim, string(pwd(),"/periodogram_fps30.gif"), fps = 30)


# Native implementation of ode45 to check for speed.
include("Joint.jl")

function rk4(tEnd::Float64, tStart::Float64, x0::Vector{Float64}, p::Tuple{Vararg{Joint}}, f::Function; h::Float64 = 0.01)
    # x0's type has not been declared because we do not know if it is a vector or a single float object.
    # eps: Tolerance, default value: 1e-3
    # To solve xdot = f(t,x)
    # p: Parameters
    # h: tSpan (interval time)

    tSim = Float64[]; push!(tSim,tStart)
    xSol = Float64[]; xSol = vcat(xSol,x0)
    len = length(x0) # Length of vector
    x = x0;
    t = tStart
    while t < tEnd
        k1 = h*f(t, x, p)
        k2 = h*f(t + h/2, x + k1/2, p)
        k3 = h*f(t + h/2, x + k2/2, p)
        k4 = h*f(t + h, x + k3, p)

        y1 = x + 1/6*(k1 + 2*k2 + 2*k3 + k4)

        t = t + h
        push!(tSim,t)
        x = y1
        xSol = vcat(xSol,x)
    end
    return tSim, xSol
end


## Incidence matrix
ei,ej = copy.(eachcol(Int.(readdlm("undir-graph-14.edges"))))
xy = readdlm("undir-graph-14.xy")'
n = max(maximum(ei),maximum(ej))
A = sparse(ei,ej,1,n,n)
A = A+A'
##
m = length(ei) # number of edges
B = sparse(1:m,ei,1,m,n) - sparse(1:m,ej,1,m,n)
## Output this for Latex
using Printf
@printf("\\left[ \\begin{array}{*{14}{c}} \n ")
for i=1:m
  for j=1:n
    if B[i,j] == 0
      @printf("\\textcolor{gray}{0} & ")
    else
      @printf("\\mathbf{%i} & ", B[i,j])
    end
  end
  println("\\\\")
end
@printf("\\end{array} \\right] \n ")


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:5] <= 1)

                     @variable(m, -12.266285154621722 <= q <= 12.793564807427844)

                     add_NL_constraint(m, :(1/(1 + exp(-(-0.46956444239618067 + 0.18451073354487368*$(x[1]) + -0.3056648113863898*$(x[2]) + -0.8733496775879632*$(x[3]) + -0.7482422964857776*$(x[4]) + 0.39599386992219143*$(x[5])))) + 1/(1 + exp(-(0.2831226968799312 + -0.000968797402465249*$(x[1]) + -0.6607077518367617*$(x[2]) + 0.11528579207894962*$(x[3]) + -0.7069435392252328*$(x[4]) + 0.7900850540395843*$(x[5])))) + 1/(1 + exp(-(0.41681338081805386 + 0.17922182588365843*$(x[1]) + 0.5927695896331957*$(x[2]) + -0.635120363897093*$(x[3]) + 0.31189019033409826*$(x[4]) + 0.11999673266347255*$(x[5])))) + 1/(1 + exp(-(-0.4140404108339504 + 0.583322476422016*$(x[1]) + 0.9921442381523282*$(x[2]) + 0.45546141054937284*$(x[3]) + -0.3096177212085385*$(x[4]) + 0.6860556002335301*$(x[5])))) + 1/(1 + exp(-(0.44730860193520705 + -0.9763312564707065*$(x[1]) + 0.297857633230612*$(x[2]) + -0.7735590056574844*$(x[3]) + -0.3545666189247747*$(x[4]) + -0.48025799425370996*$(x[5])))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


function q=qsat(Ta)
# QSAT: computes specific humidity at saturation.
# q=QSAT[Ta] computes the specific humidity [kg/kg] at satuation at
# air temperature Ta [deg C]. Dependence on air pressure, Pa, is small
# but is included as an optional input.
#
#    INPUT:   Ta - air temperature  [C]
#             Pa - (optional) pressure [mb]
#
#    OUTPUT:  q  - saturation specific humidity  [kg/kg]
#
# Version 1.0 used Tetens' formula for saturation vapor pressure
# from Buck [1981], J. App. Meteor., 1527-1532.  This version()
# follows the saturation specific humidity computation in the COARE
# Fortran code v2.5b.  This results in an increase of ~5# in
# latent heat flux compared to the calculation with version 1.0.
#
# References
# 1. https://github.com/kongdd/air-sea/blob/master/qsat.m
# 2. https://github.com/kongdd/air-sea/blob/master/as_consts.m
######################################################################
# 3/8/97: version 1.0
# 4/7/99: version 1.2 [revised as above by AA]
# 8/5/99: version 2.0
######################################################################

# if nargin==1
#   as_consts
#   Pa=P_default; # pressure in mb
# end
Pa = 1024.0

# original code
# a=(1.004.*6.112*0.6220)./Pa
# q=a.*exp((17.502.*Ta)./(240.97+Ta))

# as in Fortran code v2.5b for COARE
ew = 6.1121 * (1.0007 + 3.46e-6 * Pa) .* exp((17.502*Ta)./(240.97+Ta)); # in mb
q  = 0.62197*(ew./(Pa-0.378*ew));

end


function mk_original_test(x, α = 0.05)
    n = length(x)
    s = mk_score(x)
    var_s = variance_s(x,length(x))
    τ = s/(.5*n*(n-1))
    z = z_score(s, var_s)

    p, h, trend = p_value(z, α)

    return (trend=trend,  h=h, p=p, z=z, τ=τ, s=s, var_s=var_s)
end


using LinearAlgebra: diag, eigen, eigvals, eigvecs, Symmetric, Diagonal, tr, cholesky, LowerTriangular

@doc doc"""
    SymmetricPositiveDefinite{N} <: Manifold

The manifold of symmetric positive definite matrices, i.e.

```math
\mathcal P(n) =
\bigl\{
x \in \mathbb R^{n\times n} :
\xi^\mathrm{T}x\xi > 0 \text{ for all } \xi \in \mathbb R^{n}\backslash\{0\}
\bigr\}
```

# Constructor

    SymmetricPositiveDefinite(n)

generates the manifold $\mathcal P(n) \subset \mathbb R^{n\times n}$
"""
struct SymmetricPositiveDefinite{N} <: Manifold end
SymmetricPositiveDefinite(n::Int) = SymmetricPositiveDefinite{n}()

@doc doc"""
    LinearAffineMetric <: Metric

The linear affine metric is the metric for symmetric positive definite matrices, that employs
matrix logarithms and exponentials, which yields a linear and affine metric.
"""
struct LinearAffineMetric <: RiemannianMetric end
# Make this metric default, i.e. automatically convert
convert(::Type{SymmetricPositiveDefinite{N}}, M::MetricManifold{SymmetricPositiveDefinite{N},LinearAffineMetric}) where N = M.manifold
convert(::Type{SymmetricPositiveDefinite}, M::MetricManifold{SymmetricPositiveDefinite{N},LinearAffineMetric}) where N = M.manifold
convert(::Type{MetricManifold{SymmetricPositiveDefinite{N},LinearAffineMetric}}, M::SymmetricPositiveDefinite{N}) where N = MetricManifold(M, LinearAffineMetric())

@doc doc"""
    LogEuclideanMetric <: Metric

The LogEuclidean Metric consists of the Euclidean metric applied to all elements after mapping them
into the Lie Algebra, i.e. performing a matrix logarithm beforehand.
"""
struct LogEuclideanMetric <: RiemannianMetric end

@doc doc"""
    LogCholeskyMetric <: Metric

The Log-Cholesky metric imposes a metric based on the Cholesky decomposition as
introduced by
> Lin, Zenhua: "Riemannian Geometry of Symmetric Positive Definite Matrices via
> Cholesky Decomposition", arXiv: [1908.09326](https://arxiv.org/abs/1908.09326).
"""
struct LogCholeskyMetric <: RiemannianMetric end

cholesky_to_spd(l,w) = (l*l', w*l' + l*w')
tangent_cholesky_to_tangent_spd!(l,w) = (w .= w*l' + l*w')
spd_to_cholesky(x,v) = spd_to_cholesky(x,cholesky(x).L,v)
function spd_to_cholesky(x,l,v)
    w = inv(l)*v*inv(transpose(l))
    # strictly lower triangular plus half diagonal
    return (l, l*(LowerTriangular(w) - Diagonal(w)/2) )
end

@doc doc"""
    manifold_dimension(M)

returns the dimension of the manifold [`SymmetricPositiveDefinite`](@ref) $\mathcal P(n), N\in \mathbb N$, i.e.
```math
    \frac{n(n+1)}{2}
```
"""
@generated manifold_dimension(::SymmetricPositiveDefinite{N}) where {N} = div(N*(N+1), 2)
@generated manifold_dimension(::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric}) where {N} = div(N*(N+1), 2)
@generated manifold_dimension(::MetricManifold{SymmetricPositiveDefinite{N},LogEuclideanMetric}) where {N} = div(N*(N+1), 2)

@doc doc"""
    distance(M,x,y)

computes the distance on the [`SymmetricPositiveDefinite`](@ref) manifold between `x` and `y`,
as a [`MetricManifold`](@ref) with [`LinearAffineMetric`](@ref). The formula reads

```math
d_{\mathcal P(n)}(x,y) = \lVert \operatorname{Log}(x^{-\frac{1}{2}}yx^{-\frac{1}{2}})\rVert_{\mathrm{F}}.,
```
where $\operatorname{Log}$ denotes the matrix logarithm and $\lVert\cdot\rVert_{\mathrm{F}}$ denotes the
matrix Frobenius norm.
"""
function distance(M::SymmetricPositiveDefinite{N},x,y) where N
    s = real.( eigvals( x,y ) )
    return any(s .<= eps() ) ? 0 : sqrt(  sum( abs.(log.(s)).^2 )  )
end

@doc doc"""
    distance(M,x,y)

computes the distance on the manifold of [`SymmetricPositiveDefinite`](@ref)
nmatrices, i.e. between two symmetric positive definite matrices `x` and `y`
with respect to the [`LogCholeskyMetric`](@ref). The formula reads

````math
d_{\mathcal P(n)}(x,y) = \sqrt{
 \lVert \lfloor l \rfloor - \lfloor k \rfloor \rVert_{\mathrm{F}}^2
 + \lVert \log(\operatorname{diag}(l)) - \log(\operatorname{diag}(k))\rVert_{\mathrm{F}}^2 }\ \ ,
````
where $l$ and $k$ are the cholesky factors of $x$ and $y$, respectively,
$\lfloor\cdot\rfloor$ denbotes the strictly lower triangular matrix of its argument,
and $\lVert\cdot\rVert_{\mathrm{F}}$ denotes the Frobenius norm.
"""
distance(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric},x,y) where N = distance(CholeskySpace{N}(), cholesky(x).L, cholesky(y).L)

@doc doc"""
    distance(M,x,y)

computes the distance on the [`SymmetricPositiveDefinite`](@ref) manifold between
`x` and `y` as a [`MetricManifold`](@ref) with [`LogEuclideanMetric`](@ref).
The formula reads

```math
    d_{\mathcal P(n)}(x,y) = \lVert \Log x - \Log y \rVert_{\mathrm{F}}
```
where $\operatorname{Log}$ denotes the matrix logarithm and $\lVert\cdot\rVert_{\mathrm{F}}$ denotes the
matrix Frobenius norm.
"""
function distance(M::MetricManifold{SymmetricPositiveDefinite{N},LogEuclideanMetric},x,y) where N
    return norm(log(Symmetric(x)) - log(Symmetric(y)))
end

@doc doc"""
    inner(M,x,v,w)

compute the inner product of `v`, `w` in the tangent space of `x` on
the [`SymmetricPositiveDefinite`](@ref) manifold `M`, as
a [`MetricManifold`](@ref) with [`LinearAffineMetric`](@ref). The formula reads

```math
( v, w)_x = \operatorname{tr}(x^{-1}\xi x^{-1}\nu ),
```
"""
function inner(M::SymmetricPositiveDefinite{N}, x, w, v) where N
    F = cholesky(Symmetric(x)).L
    return tr((Symmetric(w) / F) * (Symmetric(v) / F))
end

@doc doc"""
    inner(M,x,v,w)

compute the inner product of two matrices `v`, `w` in the tangent space of `x`
on the [`SymmetricPositiveDefinite`](@ref) manifold `M`, as
a [`MetricManifold`](@ref) with [`LogCholeskyMetric`](@ref). The formula reads

````math
    (v,w)_x = (p_l(w),p_l(v))_l,
````
where the right hand side is the inner product on the [`CholeskySpace`](@ref),
$l$ is the cholesky factor of $x$,
$p_l(w) = l (l^{-1}wl^{-\mathrm{T}})_{\frac{1}{2}}$, and $(\cdot)_\frac{1}{2}$
denotes the lower triangular matrix with the diagonal multiplied by $\frac{1}{2}$
"""
function inner(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric},x,v,w) where N
    (l,vl) = spd_to_cholesky(x,v)
    (l,wl) = spd_to_cholesky(x,l,w)
    return inner(CholeskySpace{N}(), l, vl, wl)
end
inner(M::MetricManifold{SymmetricPositiveDefinite{N},LinearAffineMetric}, x,v,w) where {N} = inner(base_manifold(M),x,v,w)

norm(M::MetricManifold{SymmetricPositiveDefinite{N},LinearAffineMetric}, x,v) where {N} = norm(base_manifold(M), x,v)
norm(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric}, x,v) where {N} = sqrt(inner(M,x,v,v))

@doc doc"""
    exp!(M,y,x,v)

compute the exponential map from `x` with tangent vector `v` on the [`SymmetricPositiveDefinite`](@ref)
as a [`MetricManifold`](@ref) with [`LinearAffineMetric`](@ref) and modify `y`. The formula reads

```math
    \exp_x v = x^{\frac{1}{2}}\operatorname{Exp}(x^{-\frac{1}{2}} v x^{-\frac{1}{2}})x^{\frac{1}{2}},
```
where $\operatorname{Exp}$ denotes to the matrix exponential.
"""
function exp!(M::SymmetricPositiveDefinite{N}, y, x, v) where N
    e = eigen(Symmetric(x))
    U = e.vectors
    S = e.values
    Ssqrt = Diagonal( sqrt.(S) )
    SsqrtInv = Diagonal( 1 ./ sqrt.(S) )
    xSqrt = Symmetric(U*Ssqrt*transpose(U))
    xSqrtInv = Symmetric(U*SsqrtInv*transpose(U))
    T = Symmetric(xSqrtInv * v * xSqrtInv)
    eig1 = eigen( T ) # numerical stabilization
    Se = Diagonal( exp.(eig1.values) )
    Ue = eig1.vectors
    xue = xSqrt*Ue
    copyto!(y, xue*Se*transpose(xue) )
    return y
end

exp!(M::MetricManifold{SymmetricPositiveDefinite{N},LinearAffineMetric}, args...) where {N} = exp!(base_manifold(M), args...)

@doc doc"""
    exp!(M,y,x,v)

compute the exponential map on the [`SymmetricPositiveDefinite`](@ref) `M` with
[`LogCholeskyMetric`](@ref) from `x` into direction `v` and store the result in
`y`. The formula reads

````math
\exp_x v = (\exp_l w)(\exp_l w)^\mathrm{T}
````
where $\exp_lw$ is the exponential map on [`CholeskySpace`](@ref), $l$ is the
cholesky decomposition of $x$, $w = l(l^{-1}vl^{-\mathrm{T}})_\frac{1}{2}$,
and $(\cdot)_\frac{1}{2}$
denotes the lower triangular matrix with the diagonal multiplied by $\frac{1}{2}$.
"""
function exp!(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric}, y, x, v) where N
    (l,w) = spd_to_cholesky(x,v)
    z = exp!(CholeskySpace{N}(),y,l,w)
    mul!(y,z,z')
    return y
end
# take the same retractions as for the default
retract!(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric},y,args...) where N = retract!(M.manifold,y,args...)

@doc doc"""
    log!(M,v,x,y)

compute the exponential map from `x` to `y` on the [`SymmetricPositiveDefinite`](@ref)
as a [`MetricManifold`](@ref) with [`LinearAffineMetric`](@ref) and modify `v`. The formula reads

```math
\log_x y = x^{\frac{1}{2}}\operatorname{Log}(x^{-\frac{1}{2}} y x^{-\frac{1}{2}})x^{\frac{1}{2}},
```
where $\operatorname{Log}$ denotes to the matrix logarithm.
"""
function log!(M::SymmetricPositiveDefinite{N}, v, x, y) where N
    e = eigen(Symmetric(x))
    U = e.vectors
    S = e.values
    Ssqrt = Diagonal( sqrt.(S) )
    SsqrtInv = Diagonal( 1 ./ sqrt.(S) )
    xSqrt = Symmetric( U*Ssqrt*transpose(U) )
    xSqrtInv = Symmetric( U*SsqrtInv*transpose(U) )
    T = Symmetric( xSqrtInv * y * xSqrtInv )
    e2 = eigen( T )
    Se = Diagonal( log.(max.(e2.values,eps()) ) )
    xue = xSqrt*e2.vectors
    mul!(v,xue,Se*transpose(xue))
    return v
end

@doc doc"""
    log!(M,v,x,y)

computes the logarithmic map o [`SymmetricPositiveDefinite`](@ref) `M` with
respect to the [`LogCholeskyMetric`](@ref). The formula can be adapted from
the [`CholeskySpace`](@ref) as
````math
\log_xy = lw^{\mathrm{T}} + wl^{\mathrm{T}},
````
where $l$ is the colesky factor of $x$ and $w=\log_lk$ for $k$ the cholesky factor
of $y$ and the just mentioned logarithmic map is the one on [`CholeskySpace`](@ref).
"""
function log!(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric}, v, x, y) where N
    l = cholesky(x).L
    k = cholesky(y).L
    log!(CholeskySpace{N}(), v, l, k)
    tangent_cholesky_to_tangent_spd!(l, v)
    return v
end

@doc doc"""
    representation_size(M)

returns the size of an array representing an element on the
[`SymmetricPositiveDefinite`](@ref) manifold `M`,
i.e. $n\times n$, the size of such a symmetric positive definite matrix on
$\mathcal M = \mathcal P(n)$.
"""
representation_size(::SymmetricPositiveDefinite{N}) where N = (N,N)
representation_size(::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric}) where N = (N,N)

@doc doc"""
    vector_transport_to!(M,vto,x,v,y,::ParallelTransport)

compute the parallel transport on the [`SymmetricPositiveDefinite`](@ref) as a
[`MetricManifold`](@ref) with the [`LinearAffineMetric`](@ref).
The formula reads

```math
P_{x\to y}(v) = x^{\frac{1}{2}}
\operatorname{Exp}\bigl(
\frac{1}{2}x^{-\frac{1}{2}}\log_x(y)x^{-\frac{1}{2}}
\bigr)
x^{-\frac{1}{2}}v x^{-\frac{1}{2}}
\operatorname{Exp}\bigl(
\frac{1}{2}x^{-\frac{1}{2}}\log_x(y)x^{-\frac{1}{2}}
\bigr)
x^{\frac{1}{2}},
```

where $\operatorname{Exp}$ denotes the matrix exponential
and `log` the logarithmic map on [`SymmetricPositiveDefinite`](@ref)
(again with respect to the metric mentioned).
"""
function vector_transport_to!(M::SymmetricPositiveDefinite{N}, vto, x, v, y, ::ParallelTransport) where N
    if distance(M,x,y)<2*eps(eltype(x))
        copyto!(vto, v)
        return vto
    end
    e = eigen(Symmetric(x))
    U = e.vectors
    S = e.values
    Ssqrt = sqrt.(S)
    SsqrtInv = Diagonal( 1 ./ Ssqrt )
    Ssqrt = Diagonal( Ssqrt )
    xSqrt = Symmetric(U*Ssqrt*transpose(U))
    xSqrtInv = Symmetric(U*SsqrtInv*transpose(U))
    tv = Symmetric(xSqrtInv * v * xSqrtInv)
    ty = Symmetric(xSqrtInv * y * xSqrtInv)
    e2 = eigen( ty )
    Se = Diagonal( log.(e2.values) )
    Ue = e2.vectors
    ty2 = Symmetric(Ue*Se*transpose(Ue))
    e3 = eigen( ty2 )
    Sf = Diagonal( exp.(e3.values) )
    Uf = e3.vectors
    xue = xSqrt*Uf*Sf*transpose(Uf)
    vtp = Symmetric(xue*tv*transpose(xue))
    copyto!(vto, vtp)
    return vto
end

@doc doc"""
    vector_transport_to!(M,vto,x,v,y,::ParallelTransport)

parallely transport the tangent vector `v` at `x` along the geodesic to `y`
with respect to the [`SymmetricPositiveDefinite`](@ref) manifold `M` and
[`LogCholeskyMetric`](@ref). The parallel transport is based on the parallel
transport on [`CholeskySpace`](@ref): Let $l$ and $k$ denote the cholesky
factors of `x` and `y`, respectively and $w = l(l^{-1}vl^{-\mathrm{T}})_\frac{1}{2}$,
where $(\cdot)_\frac{1}{2}$ denotes the lower triangular matrix with the diagonal multiplied by $\frac{1}{2}$.
With $u$ the parallel transport on [`CholeskySpace`](@ref) from $l$ to $k$ the
formula hear reads

````math
    \mathcal P_{x\to y}(v) = ku^{\mathrm{T}} + uk^{\mathrm{T}}
````
"""
function vector_transport_to!(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric}, vto, x, v, y, ::ParallelTransport) where N
    k = cholesky(y).L
    (l,w) = spd_to_cholesky(x,v)
    vector_transport_to!(CholeskySpace{N}(),vto,l , w , k, ParallelTransport())
    tangent_cholesky_to_tangent_spd!(k,vto)
    return vto
end

@doc doc"""
    [Ξ,κ] = tangent_orthonormal_basis(M,x,v)

returns a orthonormal basis `Ξ` as a vector of tangent vectors (of length
[`manifold_dimension`](@ref) of `M`) in the tangent space of `x` on the
[`MetricManifold`](@ref of [`SymmetricPositiveDefinite`](@ref) manifold `M` with
[`LinearAffineMetric`](@ref) that diagonalizes the curvature tensor $R(u,v)w$
with eigenvalues `κ` and where the direction `v` has curvature `0`.
"""
function tangent_orthonormal_basis(M::SymmetricPositiveDefinite{N},x,v) where N
    xSqrt = sqrt(x)
    V = eigvecs(v)
    Ξ = [ (i==j ? 1/2 : 1/sqrt(2))*( V[:,i] * transpose(V[:,j])  +  V[:,j] * transpose(V[:,i]) )
        for i=1:N for j= i:N
    ]
    λ = eigvals(v)
    κ = [ -1/4 * (λ[i]-λ[j])^2 for i=1:N for j= i:N ]
  return Ξ,κ
end

@doc doc"""
    injectivity_radius(M)

return the injectivity radius of the [`SymmetricPositiveDefinite`](@ref). Since `M`  is a Hadamard manifold,
the injectivity radius is $\infty$.
"""
injectivity_radius(M::SymmetricPositiveDefinite{N}, args...) where N = Inf
injectivity_radius(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric}, args...) where N = Inf

@doc doc"""
    zero_tangent_vector(M,x)

returns the zero tangent vector in the tangent space of the symmetric positive
definite matrix `x` on the [`SymmetricPositiveDefinite`](@ref) manifold `M`.
"""
zero_tangent_vector(M::SymmetricPositiveDefinite{N}, x) where N = zero(x)
zero_tangent_vector(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric}, x) where N = zero(x)

@doc doc"""
    zero_tangent_vector(M,v,x)

returns the zero tangent vector in the variable `v` from the tangent space of
the symmetric positive definite matrix `x` on
the [`SymmetricPositiveDefinite`](@ref) manifold `M`.
THe result is returned also in place in the variable `v`.
"""
zero_tangent_vector!(M::SymmetricPositiveDefinite{N}, v, x) where N = fill!(v, 0)
zero_tangent_vector!(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric}, v, x) where N = fill!(v, 0)

"""
    check_manifold_point(M,x; kwargs...)

checks, whether `x` is a valid point on the [`SymmetricPositiveDefinite`](@ref) `M`, i.e. is a matrix
of size `(N,N)`, symmetric and positive definite.
The tolerance for the second to last test can be set using the ´kwargs...`.
"""
function check_manifold_point(M::SymmetricPositiveDefinite{N},x; kwargs...) where N
    if size(x) != representation_size(M)
        return DomainError(size(x),"The point $(x) does not lie on $(M), since its size is not $(representation_size(M)).")
    end
    if !isapprox(norm(x-transpose(x)), 0.; kwargs...)
        return DomainError(norm(x), "The point $(x) does not lie on $(M) since its not a symmetric matrix:")
    end
    if ! all( eigvals(x) .> 0 )
        return DomainError(norm(x), "The point $x does not lie on $(M) since its not a positive definite matrix.")
    end
    return nothing
end
check_manifold_point(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric},x; kwargs...) where N = check_manifold_point(M.manifold,x;kwargs...)

"""
    check_tangent_vector(M,x,v; kwargs... )

checks whether `v` is a tangent vector to `x` on the [`SymmetricPositiveDefinite`](@ref) `M`, i.e.
atfer [`check_manifold_point`](@ref)`(M,x)`, `v` has to be of same dimension as `x`
and a symmetric matrix, i.e. this stores tangent vetors as elements of the corresponding Lie group.
The tolerance for the last test can be set using the ´kwargs...`.
"""
function check_tangent_vector(M::SymmetricPositiveDefinite{N},x,v; kwargs...) where N
    mpe = check_manifold_point(M,x)
    mpe === nothing || return mpe
    if size(v) != representation_size(M)
        return DomainError(size(v),
            "The vector $(v) is not a tangent to a point on $(M) since its size does not match $(representation_size(M)).")
    end
    if !isapprox(norm(v-transpose(v)), 0.; kwargs...)
        return DomainError(size(v),
            "The vector $(v) is not a tangent to a point on $(M) (represented as an element of the Lie algebra) since its not symmetric.")
    end
    return nothing
end
check_tangent_vector(M::MetricManifold{SymmetricPositiveDefinite{N},LogCholeskyMetric},x,v; kwargs...) where N = check_tangent_vector(M.manifold,x,v;kwargs...)


# Copyright (c) 2021
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

using LinearAlgebra, Random;

"""
    SL0(n::Int64, p::Int64, k::Int64, type::String="Gaussian")

Generate a dataset for compressed sensing.

### Input

- `n       -- Int64: number of rows
- `p`      -- Int64: number of columns
- `k`      -- Int64: number true non-zeros
- `type`   -- String: type to generate ["Gaussian"]

### Output

(A, x, y) for Ax=y
"""
function cs_model(n::Int64, p::Int64, k::Int64, type::String="Gaussian")
    local A, x;
    if type == "Gaussian"
        A = randn(n, p);
        while rank(A) != n
            A = randn(n, p);
        end
        x = zeros(p);
        pp = sign.(randn(k)).*(ones(k)+abs.(randn(k)));
        rp = randperm(p);
        x[rp[1:k]] = pp;
        b = A*x;
    else
        error("Uknown type in cs_model(n,m,type).")
    end

    return A, x, b;
end


# Julia tutorial. Concepts:
# - Pkg: clone, add, rm, update, init, status, using
# - Arrays: ranges, tuples, multidimensional arrays, indexing, eltype, size, length, sizeof, ndims, transpose, mean, std, minimum, maximum, reshape, vec, randn, rand, zeros, ones, unary ops, element-wise ops, broadcasting ops, reductions, sub, map, copy, copy!, == vs ===, @time, CudaArray, concatenation
# - Functions: if, for, function, anonymous function, map, optional args, keyword args, three-dot notation, Dict, Symbol

# Packages
julia> Pkg.init()
julia> Pkg.add("Knet")
julia> Pkg.status()
julia> Pkg.add("ASCIIPlots")
julia> using ASCIIPlots
julia> a = eye(3)
julia> imagesc(a)
julia> Pkg.rm("ASCIIPlots")
julia> Pkg.update()

# Arrays: indexing
julia> a=reshape([1:15;], (3,5))
julia> a[1,1]
julia> a[3,5]
julia> a[4]

# Ranges
julia> 1:10
julia> for i=1:10; println(i); end
julia> for i=1:3:10; println(i); end
julia> 1:100000000000
julia> [1:10;]
julia> a=1:10
julia> typeof(a)
julia> a=1:0.5:10
julia> typeof(a)
julia> [a;]
julia> collect(a)

# Indexing continued
julia> a[3,1:2]
julia> a[2:3,1:2]
julia> a
julia> a[2:3,:]
julia> b=[1,3,5]
julia> a[:,b]
julia> b=[false, true, false, true, false]
julia> a[:,b]
julia> a
julia> a[10]
julia> a
julia> a[1]
julia> a[3:5]
julia> a = randn(10)
julia> a[a.>0]
julia> a.>0
julia> a
julia> b = (a.>0)
julia> a[b]

# Arrays: basic ops
julia> a = zeros(3,5)
julia> eltype(a)
julia> size(a)
julia> size(a,1)
julia> size(a,2)
julia> length(a)
julia> sizeof(a)
julia> ndims(a)
julia> transpose(a)
julia> a.'
julia> a'

# Arrays: unary ops
julia> a = rand(3,5)
julia> sin(a)
julia> -a
julia> 10a

# Arrays: binary ops
julia> a = rand(3,5)
julia> b = rand(3,5)
julia> a + b
julia> a * b  => matmul
julia> a .* b => element-wise
julia> a
julia> b
julia> a .+ b
julia> a + b
julia> b = rand(5,2)
julia> a * b
julia> a = rand(2000,1000)
julia> b = rand(1000, 3000)
julia> c = a*b
julia> a = rand(10000, 2000)
julia> b = rand(2000, 5000)
julia> c = a * b
julia> map(size, (a,b,c))

# Arrays: broadcasting
julia> a = rand(3,5)
julia> a > 0.5
julia> a .> 0.5
julia> b = rand(3,1)+10
julia> a + b
julia> a .+ b
julia> a
julia> b
julia> broadcast(+, a, b)
julia> a + b

# Arrays: reductions
julia> a = rand(3,5)
julia> mean(a)
julia> maximum(a)
julia> minimum(a)
julia> mean(a)
julia> mean(a,1)
julia> mean(a,2)
julia> minimum(a,1)

# Arrays: slices vs subarrays
julia> a = rand(3,5)
julia> b = a[1:2,1:2]
julia> b[1,1]=0
julia> b
julia> a
julia> b = sub(a, 1:2, 1:2)
julia> a
julia> b
julia> b[1,1]=0
julia> b
julia> a

# Arrays: concatenation
julia> a = rand(3,3)
julia> b = rand(3)
julia> hcat(a,b)
julia> [a b]
julia> vcat(a,b) => Error
julia> vcat(a,b')
julia> [a; b']

# Arrays: reshaping
julia> a = reshape([1:15.0;], 3, 5)
julia> vec(a)
julia> a[1,2]
julia> a[2,2]
julia> a[2,3]
julia> a[2,4]
julia> a
julia> reshape(a, (5,3))
julia> a
julia> size(a,1)
julia> a

# Arrays: copying and identity
julia> a
julia> d = copy(a)
julia> d[1]=0
julia> d
julia> a
julia> a===d
julia> copy!(d, a)
julia> d
julia> a
julia> a==d
julia> a===d
julia> f = a
julia> a==f
julia> a===f
julia> a==d
julia> a===d

# KnetArrays: (for GPU machines)
julia> a = rand(10000, 2000)
julia> b = rand(2000, 5000)
julia> @time c = a * b;
julia> @time c = a * b;
julia> @time c = a * b;
julia> @time c = a * b;
julia> using Knet
julia> a2 = KnetArray(a)
julia> b2 = KnetArray(b)
julia> @time c2 = a2 * b2;
julia> c3 = Array(c2)
julia> isapprox(c,c3)

# Conditionals and Loops:
julia> if 1 > 0; println(:foo); else; println(:bar); end
julia> if 1 > 0; println(:foo); elseif 2 > 3; println(:bar); else 0
julia> for i=1:10; println(i); end

# Functions:
julia> function f(x,y); x+y; end
julia> f(2,3)

# Functions and files:
julia> pwd()
julia> cd("tutorial")
julia> include("foo.jl")
julia> foo(4,5)
julia> include("foo.jl")
julia> foo(4,5)
julia> include("foo.jl")
julia> foo(4,5)
julia> include("foo.jl")
julia> foo(4,5)

# Tuples
julia> a=(1,2,3)
julia> typeof(a)
julia> a[1]
julia> a[1] = 10 => Error

# Functions that return tuples:
julia> include("foo.jl")
julia> foo(4,5) => (23,9)
julia> var1,var2 = foo(4,5)
julia> var1
julia> var2

# Functions: anonymous
julia> function (x); x+1; end
julia> x->x+1
julia> a = rand(10)
julia> map(x->2x, a)
julia> bar(x,y)=2x+3y
julia> bar(3,5)
julia> foo(3,5)

# Functions: first class entities
julia> baz = foo
julia> baz(3,5)
julia> baz === foo
julia> baz == foo

# Functions: optional arguments
julia> include("foo.jl")
julia> foo(3,5) => (21,8)
julia> foo(3) => (15,6)
julia> foo() => Error
julia> foo(1,2,3) => Error

# Functions: keyword arguments
julia> include("foo.jl")
julia> foo(1,2)
julia> foo(1,2,scale=100)
julia> foo(1,2;scale=100)
julia> foo(1,2,100) => Error
julia> include("foo.jl")
julia> foo(1,2)
julia> foo(1,2,scale=100)
julia> foo(1,2,bias=30)
julia> foo(1,2,bias=30,scale=200)

# Functions variable number of arguments
julia> f(x,y...)=println("x=$x y=$y")
julia> f()
julia> f(3)
julia> f(3,4)
julia> z(x,y...)=println("x=$x y=$y")
julia> z(3,4)
julia> z(3,4,5,6,7,8)
julia> q(x,y; scale=10, kwargs...)=println("x=$x y=$y scale=$scale kwargs=$kwargs")
julia> q(1,2)
julia> q(1,2; scale=3)
julia> q(1,2; fish=6)
julia> q(1,2; fish=6, dog=1)

# Dictionaries
julia> d=Dict()
julia> d[:foo]=1
julia> d[:bar]=2
julia> d
julia> d[:bar]
julia> d[:baz]

# Dictionaries as keyword argument lists
julia> q(1,2; d...)
julia> q2(x,y; scale=10, kwargs...)=println("x=$x y=$y scale=$scale kwargs[foo]=$(Dict(kwargs)[:foo])")
julia> q2(1,2; d...)


function test_permutation_permutations()
    @test collect(permutations(Int[], Val{:unique})) == Array{Int,1}[[]]
    @test length(permutations(Int[], Val{:unique})) == 1
    @test eltype(permutations(Int[], Val{:unique})) == Array{Int,1}

    @test collect(permutations(["a", "b", "c"], Val{:unique})) == Array[
        ["a", "b", "c"],
        ["a", "c", "b"],
        ["b", "a", "c"],
        ["b", "c", "a"],
        ["c", "a", "b"],
        ["c", "b", "a"],
    ]
    @test length(permutations(["a", "b", "c"], Val{:unique})) == 6
    @test eltype(permutations(["a", "b", "c"], Val{:unique})) == Array{String,1}
end

function test_permutation_permutations_with_repetition()
    @test collect(permutations(Int[], Val{:repeated})) == Array{Int,1}[[]]
    @test length(permutations(Int[], Val{:repeated})) == 1
    @test eltype(permutations(Int[], Val{:repeated})) == Array{Int,1}

    @test collect(permutations(["a", "b", "c"], Val{:repeated})) == Array[
        ["a", "a", "a"],
        ["a", "a", "b"],
        ["a", "a", "c"],
        ["a", "b", "a"],
        ["a", "b", "b"],
        ["a", "b", "c"],
        ["a", "c", "a"],
        ["a", "c", "b"],
        ["a", "c", "c"],
        ["b", "a", "a"],
        ["b", "a", "b"],
        ["b", "a", "c"],
        ["b", "b", "a"],
        ["b", "b", "b"],
        ["b", "b", "c"],
        ["b", "c", "a"],
        ["b", "c", "b"],
        ["b", "c", "c"],
        ["c", "a", "a"],
        ["c", "a", "b"],
        ["c", "a", "c"],
        ["c", "b", "a"],
        ["c", "b", "b"],
        ["c", "b", "c"],
        ["c", "c", "a"],
        ["c", "c", "b"],
        ["c", "c", "c"],
    ]
    @test length(permutations(["a", "b", "c"], Val{:repeated})) == 27
    @test eltype(permutations(["a", "b", "c"], Val{:repeated})) == Array{String,1}
end

function test_permutation_permutations_unknown_mode()
    @test_throws MethodError permutations(["a", "b", "c"], Val{:unknown})
end

function test_permutation_kpermutations()
    @test collect(permutations(Int[], 0, Val{:unique})) == Array{Int,1}[[]]
    @test length(permutations(Int[], 0, Val{:unique})) == 1
    @test eltype(permutations(Int[], 0, Val{:unique})) == Array{Int,1}

    @test collect(permutations(Int[], 1, Val{:unique})) == Array{Int,1}[]
    @test length(permutations(Int[], 1, Val{:unique})) == 0
    @test eltype(permutations(Int[], 1, Val{:unique})) == Array{Int,1}

    @test collect(permutations(["a", "b", "c", "d"], 2, Val{:unique})) == Array[
        ["a", "b"],
        ["a", "c"],
        ["a", "d"],
        ["b", "a"],
        ["b", "c"],
        ["b", "d"],
        ["c", "a"],
        ["c", "b"],
        ["c", "d"],
        ["d", "a"],
        ["d", "b"],
        ["d", "c"],
    ]
    @test length(permutations(["a", "b", "c", "d"], 2, Val{:unique})) == 12
    @test eltype(permutations(["a", "b", "c", "d"], 2, Val{:unique})) == Array{String,1}

    @test collect(permutations(["a", "b", "c", "d"], 3, Val{:unique})) == Array[
        ["a", "b", "c"],
        ["a", "b", "d"],
        ["a", "c", "b"],
        ["a", "c", "d"],
        ["a", "d", "b"],
        ["a", "d", "c"],
        ["b", "a", "c"],
        ["b", "a", "d"],
        ["b", "c", "a"],
        ["b", "c", "d"],
        ["b", "d", "a"],
        ["b", "d", "c"],
        ["c", "a", "b"],
        ["c", "a", "d"],
        ["c", "b", "a"],
        ["c", "b", "d"],
        ["c", "d", "a"],
        ["c", "d", "b"],
        ["d", "a", "b"],
        ["d", "a", "c"],
        ["d", "b", "a"],
        ["d", "b", "c"],
        ["d", "c", "a"],
        ["d", "c", "b"],
    ]
    @test length(permutations(["a", "b", "c", "d"], 3, Val{:unique})) == 24
    @test eltype(permutations(["a", "b", "c", "d"], 3, Val{:unique})) == Array{String,1}

    @test collect(permutations(["a", "b"], 3, Val{:unique})) == Array{String,1}[]
    @test length(permutations(["a", "b"], 3, Val{:unique})) == 0
end

function test_permutation_kpermutations_with_repetition()
    @test collect(permutations(Int[], 0, Val{:repeated})) == Array{Int,1}[[]]
    @test length(permutations(Int[], 0, Val{:repeated})) == 1
    @test eltype(permutations(Int[], 0, Val{:repeated})) == Array{Int,1}

    @test collect(permutations(Int[], 1, Val{:repeated})) == Array{Int,1}[]
    @test length(permutations(Int[], 1, Val{:repeated})) == 0
    @test eltype(permutations(Int[], 1, Val{:repeated})) == Array{Int,1}

    @test collect(permutations(["a", "b", "c", "d"], 2, Val{:repeated})) == Array[
        ["a", "a"],
        ["a", "b"],
        ["a", "c"],
        ["a", "d"],
        ["b", "a"],
        ["b", "b"],
        ["b", "c"],
        ["b", "d"],
        ["c", "a"],
        ["c", "b"],
        ["c", "c"],
        ["c", "d"],
        ["d", "a"],
        ["d", "b"],
        ["d", "c"],
        ["d", "d"],
    ]
    @test length(permutations(["a", "b", "c", "d"], 2, Val{:repeated})) == 4^2
    @test eltype(permutations(["a", "b", "c", "d"], 2, Val{:repeated})) == Array{String,1}

    @test collect(permutations(["a", "b"], 3, Val{:repeated})) == Array[
        ["a", "a", "a"],
        ["a", "a", "b"],
        ["a", "b", "a"],
        ["a", "b", "b"],
        ["b", "a", "a"],
        ["b", "a", "b"],
        ["b", "b", "a"],
        ["b", "b", "b"],
    ]
    @test length(permutations(["a", "b"], 3, Val{:repeated})) == 2^3
end

function test_permutation_kpermutations_unknown_mode()
    @test_throws MethodError permutations(["a", "b", "c", "d"], 2, Val{:unknown})
end

function test_permutation_all()
    print(rpad("Math.Combinatorics.Permutation...", 50, ' '))

    test_permutation_permutations()
    test_permutation_permutations_with_repetition()
    test_permutation_permutations_unknown_mode()
    test_permutation_kpermutations()
    test_permutation_kpermutations_with_repetition()
    test_permutation_kpermutations_unknown_mode()

    println("PASS")
end


# #
# Reproduce the figures in the Appendix of
# [Duchêne](https://www.ams.org/open-math-notes/omn-view-listing?listingId=111309)
# comparing solutions to the water waves system with predictions of
# the Serre-Green-Naghdi and Whitham-Green-Naghdi systems
# #
export Integrate,Figures
using WaterWaves1D,Plots,LinearAlgebra;
#using JLD #(uncomment if using @save)

#----
"""
	Integrate(scenario;kwargs)

Integrate in time the water waves system as well as
the Serre-Green-Naghdi, Whitham-Green-Naghdi and Isobe-Kakinuma models
with an initial datum depending on the argument (`scenario`).
If `scenario==1`, the initial datum is a heap of water with zero velocity.
If `scenario==2`, the initial datum is generates at first order a right-going wave.

Other arguments are optional:
- `μ` is the shallowness parameter,
- `ϵ` in the nonlinearity parameter,
- `p` is the power in the initial datum `η = exp(-|x|^p)`
- `N` the number of collocation points,
- `L` the half-length of the mesh,
- `T` the final time of integration,
- `dt` the timestep,
- `dealias`: dealiasing with Orlicz rule `1-dealias/(dealias+2)` (default is `0`, i.e. no dealiasing),
- `iterate`: use GMRES if `true`, and LU decomposition otherwise (default is `true`),
- `precond` gives some choice in the preconditioner for GMRES (default is `true` for the default preconditioner),
- `method` and `maxiter` are the corresponding options for generating the initial data in the water waves model; see doc of `WaterWaves`
- `name`: a string used to save the figures.

Return `(Diff,problems,plt)` where `Diff` is a measure of the errors, `problems` contains all the information and `plt` is a plot of the final time solution.
"""
function Integrate(scenario;μ=.1,ϵ=.1,p=2,N=2^10,L=15,T=10,dt=0.001,dealias=0,iterate=true,precond=true,method=1,maxiter=100,name=nothing)
	#---- preparation

	if name != nothing ns=floor(Int,max(1,T/dt/100)) else ns=1 end

	if typeof(dealias)==Int64 dealias=dealias*ones(Int64,4) end

	# the set of parameters as indicated by the user
	param = ( μ  = μ, ϵ  = ϵ,
				N  = N, L  = L,
	            T  = T, dt = dt,
				ns=ns )

	# generate the initial data
	mesh=Mesh(param)
	k=mesh.k

	η = exp.(-abs.(mesh.x ).^p)
	if scenario == 1
		v = 0*η
	elseif scenario == 2
		v = 2/ϵ*(sqrt.(1 .+ ϵ*η) .- 1)
	else
		@error("the argument must be 1 or 2")
	end
	init     = Init(mesh,η,v)

	# construct the models to be solved
	modelWW	 = WaterWaves(param;dealias=dealias[1],method=method,maxiter=maxiter)
	modelSGN = SerreGreenNaghdi(param;ktol=0, gtol=1e-14, iterate=iterate, precond = precond, dealias = dealias[2])
	modelWGN = WhithamGreenNaghdi(param;SGN=false, ktol=0, gtol=1e-14, iterate=iterate, precond = precond, dealias = dealias[3])
	modelIK2 = IsobeKakinuma(param; ktol=0, gtol=1e-14, iterate=iterate, precond = precond, dealias = dealias[4])

	# construct the associated problems
	problems = []
	push!(problems, Problem(modelWW, init, param) )
	push!(problems, Problem(modelSGN, init, param) )
	push!(problems, Problem(modelWGN, init, param) )
	push!(problems, Problem(modelIK2, init, param) )

	#---- computation
	for problem in problems
		solve!( problem )
	end

	#---- analysis
	# Errors at final time
	(ηww,vww,xww,t)=solution(problems[1])
	(ηSGN,vSGN)=solution(problems[2],x=xww)
	(ηWGN,vWGN)=solution(problems[3],x=xww)
	(ηIK2,vIK2)=solution(problems[4],x=xww)

	Diff = [norm(ηww-ηSGN,Inf) norm(ηww-ηWGN,Inf) norm(ηww-ηIK2,Inf)]
	@info string("Error of SGN, WGN, IK2: ",round.(Diff, sigdigits=3))

	#---- plots
	# solutions at final time
	plt = plot_solution(problems;fourier=true)
	display(plt)

	if name != nothing
		#save(problem,name);
		#@save(name,xww,ηww,ηSGN,ηWGN,ηIK2,scenario,μ,ϵ,p,N,L,T,dt,dealias,iterate,precond,name)

		if scenario == 1
			xlims=(0,last(xww))
			ind=range(1,step=length(xww)÷32,length=32);
		else
			xlims=(first(xww),last(xww))
			ind=range(1,step=length(xww)÷16,length=16);
		end

		# Plot and save the solutions at final time
		plot_solution(problems;fourier=false)
		plot!(xlims=xlims,legend=:topleft)
		savefig(string(name,".pdf"));

		# Plot and save the differences at final time
		plot(xww, [ηww-ηSGN ηww-ηWGN ηww-ηIK2],
		 	color=[:2 :3 :4],primary=false)
		scatter!(xww[ind], [ηww[ind]-ηSGN[ind] ηww[ind]-ηWGN[ind] ηww[ind]-ηIK2[ind]],
		 	color=[:2 :3 :4],marker=([:d :h :ut], 8, Plots.stroke(1, :gray)),#linewidth=0,
		 	xlims=xlims,
		   title=string("difference (surface deformation) at t=",t),
		   label=["Serre-Green-Naghdi" "Whitham-Green-Naghdi" "Isobe-Kakinuma"])
		savefig(string(name,"-differences.pdf"));

		# Generate and save an animation of the evolution in time
		create_animation(problems;fourier=false,xlims=xlims,ylims=(-0.5,1),name=name)

	end

	return Diff,problems,plt
end

#----
"""
	Figures()

Reproduce the figures in Section I.5 of the monograph
[Many Models for Water Waves](https://www.ams.org/open-math-notes/omn-view-listing?listingId=111309)
"""
function Figures()
	MU=[1 0.1 0.01]
	EPS=[0.5 0.25 0.125]

	DiffSGN=zeros(3,3);DiffWGN=zeros(3,3);DiffIK2=zeros(3,3);

	for i in 1:3
		for j in 1:3
			mu=MU[i];eps=EPS[j]
			if mu == 1. mu = 1 end
			name=string("mu",mu,"eps",eps)
			name=replace(name, '.' => "")
			@info string("Save plots and animation with the name ",name)
			Diff,=Integrate(1;p=3,μ=mu,ϵ=eps,N=2^10,T=10,L=15,dt=0.001,dealias=0,maxiter=100,name=name);
			DiffSGN[i,j]=Diff[1];DiffWGN[i,j]=Diff[2];DiffIK2[i,j]=Diff[3];
			#@save("MM4WW",DiffSGN,DiffWGN,DiffIK2)
		end
	end
end
nothing


using Test
using CLIMA

let
  for N = 1:10
    for FloatType in (Float64, Float32)
      (ξ, ω) = CLIMA.Mesh.Elements.lglpoints(FloatType, N)
      I∫ = CLIMA.Mesh.Grids.indefinite_integral_interpolation_matrix(ξ, ω)
      for n = 1:N
        if N == 1
          @test sum(abs.(I∫ * ξ)) < 10*eps(FloatType)
        else
          @test I∫ * ξ.^n ≈ (ξ.^(n+1) .- (-1).^(n+1)) / (n+1)
        end
      end
    end
  end
end


function mkscale(scalevec::Vector{Float64})
    mult = eye(4)
    for i in 1:size(scalevec, 1)
        mult[i, i] = scalevec[i]
    end
    mult
end

function mktranslate(transvec::Vector{Float64})
    mult = eye(4)
    for i in 1:3
        mult[i, 4] = transvec[i]
    end
    mult
end

function mkrotate(angle::Float64, direction::Symbol)
    x = [cosd(angle), -sind(angle), sind(angle), cosd(angle)]
    indices = ROTATIONINDICES[direction]
    mult = eye(4)
    for i in 1:size(indices, 1)
        mult[indices[i, 1], indices[i, 2]] = x[i]
    end
    mult
end


module TontiTest
  using AlgebraicDynamics.Tonti
  using CombinatorialSpaces
  using GeometryBasics
  using Random
  using Test
  td = TontiDiagram(2, [:C =>:IP,  :dC =>:TP,  :ΔC=>:IL,
                         :∑ϕ=>:TS2, :ϕ=>:TL2]);

  s = EmbeddedDeltaSet2D{Bool, Point{3,Float64}}()
  points = [(0,0,0),(0,0,1),(0,1,0),(0,1,1)]

  add_vertices!(s, 4, point=points)
  glue_sorted_triangle!(s, 1,2,3)
  glue_sorted_triangle!(s, 2,3,4)

  sd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point{3,Float64}}(s)
  subdivide_duals!(sd, Barycenter())
  star0 = ⋆(0,sd)

  @test gen_form(s, x->1) == [1,1,1,1]

  addSpace!(td, s)
  addTime!(td)
  const k = 0.003

  diffusion(diff) = ΔC -> diff*ΔC
  mass_conv() = ∑ϕ->∑ϕ

  addTransform!(td, s, [:ΔC], diffusion(k), [:ϕ])
  addTransform!(td, s, [:∑ϕ], mass_conv(), [:dC]);

  sim, data_syms = vectorfield(td, s)

  @test data_syms[1][1] == :C
  @test data_syms[1][2] == 4

  Random.seed!(42)
  u  = gen_form(s,x->rand())
  du = zeros(Float64,nv(s))

  tempu = copy(u)
  dt = 0.001
  for i in 10000
    sim(du, tempu, [],0)
    tempu .+= du
    # Check that mass is conserved
  end
  @test all(tempu .!= u)
  @test all(sum(star0*tempu)-sum(star0*u) < 1e-6)
end


@testset "Norm" begin
    n = 1_000
    ν = 1.2
    p = 2.7

    a_geo = Sequence(Taylor(n), [inv(ν^abs(i)) for i ∈ 0:n])
    a_alg = Sequence(Taylor(n), [inv((1.0 + abs(i))^p) for i ∈ 0:n])
    @test rate(geometricweights(a_geo)) ≈ ν
    @test rate(algebraicweights(a_alg)) ≈ p
    @test norm(a_geo, Weightedℓ¹Norm(GeometricWeights(ν))) ≈
        opnorm(project(Multiplication(a_geo), space(a_geo), space(a_geo), Float64), Weightedℓ¹Norm(GeometricWeights(ν))) ≈
        n+1
    @test norm(a_alg, Weightedℓ¹Norm(AlgebraicWeights(p))) ≈
        opnorm(project(Multiplication(a_alg), space(a_alg), space(a_alg), Float64), Weightedℓ¹Norm(AlgebraicWeights(p))) ≈
        n+1

    a_geo = Sequence(Fourier(n, 1.), [inv(ν^abs(i)) for i ∈ -n:n])
    a_alg = Sequence(Fourier(n, 1.), [inv((1.0 + abs(i))^p) for i ∈ -n:n])
    @test rate(geometricweights(a_geo)) ≈ ν
    @test rate(algebraicweights(a_alg)) ≈ p
    @test norm(a_geo, Weightedℓ¹Norm(GeometricWeights(ν))) ≈
        opnorm(project(Multiplication(a_geo), space(a_geo), space(a_geo), Float64), Weightedℓ¹Norm(GeometricWeights(ν))) ≈
        2n+1
    @test norm(a_alg, Weightedℓ¹Norm(AlgebraicWeights(p))) ≈
        opnorm(project(Multiplication(a_alg), space(a_alg), space(a_alg), Float64), Weightedℓ¹Norm(AlgebraicWeights(p))) ≈
        2n+1

    a_geo = Sequence(Chebyshev(n), [inv(ν^abs(i)) for i ∈ 0:n])
    a_alg = Sequence(Chebyshev(n), [inv((1.0 + abs(i))^p) for i ∈ 0:n])
    @test rate(geometricweights(a_geo)) ≈ ν
    @test rate(algebraicweights(a_alg)) ≈ p
    @test norm(a_geo, Weightedℓ¹Norm(GeometricWeights(ν))) ≈ n+1 ≤
        opnorm(project(Multiplication(a_geo), space(a_geo), space(a_geo), Float64), Weightedℓ¹Norm(GeometricWeights(ν)))
    @test norm(a_alg, Weightedℓ¹Norm(AlgebraicWeights(p))) ≈ n+1 ≤
        opnorm(project(Multiplication(a_alg), space(a_alg), space(a_alg), Float64), Weightedℓ¹Norm(AlgebraicWeights(p)))

    a_geo = Sequence(Taylor(10) ⊗ Fourier(10, 1.) ⊗ Chebyshev(10),
        vec([inv(ν^abs(α[1]) * ν^abs(α[2]) * ν^abs(α[3])) for α ∈ indices(Taylor(10) ⊗ Fourier(10, 1.) ⊗ Chebyshev(10))]))
    a_alg = Sequence(Taylor(10) ⊗ Fourier(10, 1.) ⊗ Chebyshev(10),
        vec([inv((1.0 + abs(α[1]))^p * (1.0 + abs(α[2]))^p * (1.0 + abs(α[3]))^p) for α ∈ indices(Taylor(10) ⊗ Fourier(10, 1.) ⊗ Chebyshev(10))]))
    @test all( rate.(geometricweights(a_geo)) .≈ (ν, ν, ν) )
    @test all( rate.(algebraicweights(a_alg)) .≈ (p, p, p) )
    @test norm(a_geo, Weightedℓ¹Norm( (GeometricWeights(ν), GeometricWeights(ν), GeometricWeights(ν)) )) ≈ length(a_geo) ≤
        opnorm(project(Multiplication(a_geo), space(a_geo), space(a_geo), Float64), Weightedℓ¹Norm( (GeometricWeights(ν), GeometricWeights(ν), GeometricWeights(ν)) ))
    @test norm(a_alg, Weightedℓ¹Norm( (AlgebraicWeights(p), AlgebraicWeights(p), AlgebraicWeights(p)) )) ≈ length(a_alg) ≤
        opnorm(project(Multiplication(a_alg), space(a_alg), space(a_alg), Float64), Weightedℓ¹Norm( (AlgebraicWeights(p), AlgebraicWeights(p), AlgebraicWeights(p)) ))

    s1 = ParameterSpace() × (Taylor(1) ⊗ Fourier(1, 1.0) ⊗ Chebyshev(1))^1
    s2 = ParameterSpace() × (Taylor(2) ⊗ Fourier(0, 1.0) ⊗ Chebyshev(1))^1
    a_ = Sequence(s1, collect(1.0:13.0))
    A_ = LinearOperator(s1, s2, ones(dimension(s2), dimension(s1)))

    metric = CartesianProductNorm((ℓᵖNorm(Inf), CartesianPowerNorm(Weightedℓ¹Norm((GeometricWeights(1.0), GeometricWeights(1.0), GeometricWeights(1.0))), ℓᵖNorm(Inf))), ℓᵖNorm(Inf))

    @test norm(a_, metric) == sum(2.0:13.0)
    @test opnorm(A_, metric, metric) == 12.0
end


using GLM
glm(y ~ 1, DataFrame(y = float64(randbool(10))), Binomial())

n = 2_500_000; srand(1234321)
df2 = DataFrame(x1 = rand(Normal(), n),
                x2 = rand(Exponential(), n),
                ss = pool(rand(DiscreteUniform(50), n)));
beta = unshift!(rand(Normal(),52), 0.5); # "true" parameter values

## Create linear predictor and mean response
df2["eta"] = eta = ModelMatrix(ModelFrame(x1 ~ x1 + x2 + ss, df2)).m * beta;
df2["mu"] = mu = linkinv!(LogitLink(), similar(eta), eta);

df2["y"] = float64(rand(n) .< mu);        # simulate observed responses
head(df2)

gm6 = glm(y ~ x1 + x2 + ss, df2, Binomial())
@time glm(y ~ x1 + x2 + ss, df2, Binomial());


function check(p, x, m)
    n = BigInt(0)
    pp = BigInt(1)
    x = reverse(x)
    for d in x
        n += parse(Int, d) * pp
        pp *= p
    end
    # println(" ", p, " ", reverse(x), " ", n)
    n ≤ m
end

function solve()
    x = readline()
    m = parse(BigInt, readline())

    if length(x) == 1
        parse(Int, x) ≤ m ? 1 : 0
    else
        max_ = 10::Int64^18 + 1
        min_min = maximum([parse(Int, a) for a in x]) + 1
        min_ = min_min

        if !check(min_, x, m)
            0
        else
            while min_ + 1 < max_
                # println(min_, " ", max_)
                p = (min_ + max_) ÷ 2
                if check(p, x, m)
                    min_ = p
                else
                    max_ = p
                end
            end
            # println("sol ", min_min, " ", min_, " ", max_)
            min_ - min_min + 1
        end
    end
end

println(solve())


include("euler/euler.jl")
using .Primes: prime_numbers

compute(n::Int)::Int = prime_numbers(20n)[n]