# Arquivo contendo comparação cinemática, neste arquivo farei a geração de trajetória no domínio cartesiano e no domínio das juntas para comparação.
push!(LOAD_PATH, expanduser("/home/phelipe/Documentos"))
using Trajectory
using Plots
pyplot()

m=[23.902, 1.285]
l= [.45, .45]
r= [0.091, 0.048]
I= [1.266, 0.093]
g = 9.81
t0 = 0.
tend = 4.
Ts = 0.01

function cinematica_direta(θ::T) where T<:AbstractArray
    x = l[1] * sin(θ[1]) + l[2] * sin(θ[1] + θ[2])
    y = -(l[1] * cos(θ[1]) + l[2] * cos(θ[1] + θ[2]))
    [x,y]
end

function cinematica_inversa(cord::Vector{T}) where T<:AbstractFloat
    x = cord[1]
    y = cord[2]
    t = (x^2 + y^2 - l[1]^2 - l[2]^2) / (2 * l[1] * l[2])
    θ2 = atan2( (1 - t^2), t)
    k1 = l[1] + l[2] * cos(θ2)
    k2 = l[2] * sin(θ2)
    γ = atan2(k2,k1)
    θ1 = atan2(x,-y) - γ
    [θ1, θ2]
end

θ_start = [0.,0.]
#θ_end = [deg2rad(10),deg2rad(90)]
θ_end = [1.8, 2.0]
position_start = cinematica_direta(θ_start)
position_end = cinematica_direta(θ_end)

#teta1 = deg2rad(45)
#teta2 = deg2rad(0)
#println("Valores na cinemática direta $(cord = cinematica_direta([teta1,teta2])) metros")
#println("Valores na cinemática inversa $(rad2deg.(cinematica_inversa(cord))) deg")

#mínimo jerk cartesiano
x1,v1,a1,j1 = minimumjerkf(position_start[1],0.,0.,t0,position_end[1],0.,0.,tend)
x2,v2,a2,j2 = minimumjerkf(position_start[2],0.,0.,t0,position_end[2],0.,0.,tend)

time = collect(t0:Ts:tend)
x1data = map(a-> x1(a),time)
x2data = map(a-> x2(a),time)
function plotcartesiano()
    p1 = plot(time,x1data,xlabel="tempo",ylabel="posição x")
    p2 = plot(time,x2data,xlabel="tempo",ylabel="posição y")
    plot(p1,p2, tittle="Cartesiano")
end

θ = map((x,y)-> cinematica_inversa([x,y]),x1data,x2data)
θ1 = map(x->x[1],θ)
θ2 = map(x->x[2],θ)
vθ1 = diff(θ1)./Ts
vθ2 = diff(θ2)./Ts
aθ1 = diff(vθ1)./Ts
aθ2 = diff(vθ2)./Ts
jθ1 = diff(aθ1)./Ts
jθ2 = diff(aθ2)./Ts

function plotjuntas()
    p1 = plot(time,θ1,xlabel="tempo",ylabel="posição θ1")
    p2 = plot(time,θ2,xlabel="tempo",ylabel="posição θ2")
    plot(p1,p2, tittle="Jerk cartesiano nas juntas")
end

function plotvjuntas()
    p1 = plot(time[1:length(vθ1)],vθ1,xlabel="tempo",ylabel="velocidade θ1")
    p2 = plot(time[1:length(vθ1)],vθ2,xlabel="tempo",ylabel="velocidade θ2")
    plot(p1,p2, tittle="Jerk cartesiano nas juntas")
end

function plotajuntas()
    p1 = plot(time[1:length(aθ1)],aθ1,xlabel="tempo",ylabel="aceleração θ1")
    p2 = plot(time[1:length(aθ1)],aθ2,xlabel="tempo",ylabel="aceleração θ2")
    plot(p1,p2, tittle="Jerk cartesiano nas juntas")
end

function plotjjuntas()
    p1 = plot(time[1:length(jθ1)],jθ1,xlabel="tempo",ylabel="tranco θ1")
    p2 = plot(time[1:length(jθ2)],jθ2,xlabel="tempo",ylabel="tranco θ2")
    plot(p1,p2, tittle="Jerk cartesiano nas juntas")
end

# domínio das juntas
xj1,vj1,aj1,jj1 = minimumjerkf(θ_start[1],0.,0.,t0,θ_end[1],0.,0.,tend)
xj2,vj2,aj2,jj2 = minimumjerkf(θ_start[2],0.,0.,t0,θ_end[2],0.,0.,tend)

θ_junta1 = map(a-> xj1(a),time)
θ_junta2 = map(a-> xj2(a),time)
v_junta1 = map(a-> vj1(a),time)
v_junta2 = map(a-> vj2(a),time)
a_junta1 = map(a-> aj1(a),time)
a_junta2 = map(a-> aj2(a),time)
j_junta1 = map(a-> jj1(a),time)
j_junta2 = map(a-> jj2(a),time)

cart = map((x,y)-> cinematica_direta([x,y]),θ_junta1,θ_junta2)
cart_x = map(x->x[1],cart)
cart_y = map(x->x[2],cart)

function plotjerks()
    p1 = plot(time,[jθ1,j_junta1],xlabel="tempo",ylabel="tranco θ1")
    p2 = plot(time,[jθ2,j_junta2],xlabel="tempo",ylabel="tranco θ2")
    plot(p1,p2)
end

function plotpositions()
    p1 = plot(time,[θ1,θ_junta1],xlabel="tempo",ylabel="posição θ1")
    p2 = plot(time,[θ2,θ_junta2],xlabel="tempo",ylabel="posição θ2")
    plot(p1,p2)
end

function plotvelocitys()
    p1 = plot(time,[vθ1,v_junta1],xlabel="tempo",ylabel="velocidade θ1")
    p2 = plot(time,[vθ2,v_junta2],xlabel="tempo",ylabel="velocidade θ2")
    plot(p1,p2)
end

function plotacelerations()
    p1 = plot(time,[aθ1,a_junta1],xlabel="tempo",ylabel="aceleração θ1")
    p2 = plot(time,[aθ2,a_junta2],xlabel="tempo",ylabel="aceleração θ2")
    plot(p1,p2)
end

function plotxy()
    p1 = plot(x1data,x2data)
    p2 = plot(cart_x,cart_y)
    p = plot(p1,p2)
end

#Essas funções mostram o que acontece nas juntas quando o jerk é feito no domínio cartesiano
#plotjuntas()
#plotvjuntas()
#plotajuntas()
#plotjjuntas()

# Essas funções comparam as ações nas juntas
plotxy()
plotjerks()


using Indicators
using Temporal
using Test
using Random
using Statistics

const N = 1_000
const X0 = 50.0
const seed = 1

Random.seed!(seed)
x = cumsum(randn(N), dims=1) .+ X0
ohlc = cumsum(randn(N,4), dims=1) .+ X0
hlc = ohlc[:,2:4]
hl = ohlc[:,2:3]

count_nans(x) = sum(isnan.(x))

@testset "Utilities" begin
    y = x + randn(N)
    cxo = crossover(x, y)
    cxu = crossunder(x, y)
    @test any(cxo)
    @test any(cxu)
    @test !any(cxo .* cxu)  # ensure crossovers and crossunders never coincide
end

# trendy
@testset "Trendlines" begin
    tmp = resistance(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = support(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = minima(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = maxima(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
end

# moving regressions
@testset "Regressions" begin
    tmp = mlr_beta(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    @test count_nans(tmp) != N
    tmp = mlr_slope(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mlr_intercept(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mlr(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mlr_se(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mlr_ub(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mlr_lb(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mlr_bands(tmp)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    @test count_nans(tmp) != N
    tmp = mlr_rsq(x, adjusted=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mlr_rsq(x, adjusted=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
end

@testset "Running Calculations" begin
    tmp = diffn(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = diffn(ohlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == size(ohlc,2)
    @test count_nans(tmp) != N
    tmp = runmean(x, cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmean(x, cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runsum(x, cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = wilder_sum(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmad(x, cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmad(x, cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runvar(x, cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runvar(x, cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runsd(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runcov(x, x.*rand(N), cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runcov(x, x.*rand(N), cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runcor(x, x.*rand(N), cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runcor(x, x.*rand(N), cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmin(x, cumulative=true, inclusive=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmin(x, cumulative=true, inclusive=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmin(x, cumulative=false, inclusive=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmin(x, cumulative=false, inclusive=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmax(x, cumulative=true, inclusive=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmax(x, cumulative=true, inclusive=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmax(x, cumulative=false, inclusive=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = runmax(x, cumulative=false, inclusive=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mode(map(xi->round(xi), x))
    @test size(tmp, 1) == 1
    @test size(tmp, 2) == 1
    @test !isnan(tmp)
    tmp = runquantile(x, cumulative=true)
    @test !isnan(tmp[2]) && isnan(tmp[1])
    @test tmp[10] == quantile(x[1:10], 0.05)
    tmp = runquantile(x, cumulative=false)
    @test tmp[10] == quantile(x[1:10], 0.05)
    n = 20
    tmp = runacf(x, n=n, maxlag=15, cumulative=true)
    @test all(tmp[n:end,1] .== 1.0)
    tmp = runacf(x, n=n, maxlag=15, cumulative=false)
    @test all(tmp[n:end,1] .== 1.0)
end

# moving average functions
@testset "Moving Averages" begin
    tmp = sma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mama(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    @test count_nans(tmp) != N
    tmp = ema(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = wma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = hma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = trima(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = mma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = tema(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = dema(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = swma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = kama(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = alma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = zlema(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
end

# momentum function
@testset "Momentum" begin
    tmp = aroon(hl)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    @test count_nans(tmp) != N
    tmp = donch(hl)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    @test count_nans(tmp) != N
    tmp = momentum(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = roc(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = macd(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    @test count_nans(tmp) != N
    tmp = rsi(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = adx(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    @test count_nans(tmp) != N
    tmp = adx(hlc, wilder=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    @test count_nans(tmp) != N
    tmp = psar(hl)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = kst(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = wpr(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = cci(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = stoch(hlc, kind=:fast)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    @test count_nans(tmp) != N
    tmp = stoch(hlc, kind=:slow)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    @test count_nans(tmp) != N
    tmp = smi(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    @test count_nans(tmp) != N
end

# volatility functions
@testset "Volatility" begin
    tmp = bbands(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    @test count_nans(tmp) != N
    tmp = tr(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = atr(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = keltner(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    @test count_nans(tmp) != N
end

# chart patterns functions
@testset "Charting" begin
    tmp = renko(hlc, use_atr=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
    tmp = renko(hlc, use_atr=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    @test count_nans(tmp) != N
end

# ==== TEMPORAL INTERACTIONS ====
@testset "Temporal" begin
    x = TS(cumsum(randn(N), dims=1) .+ X0)
    x.fields = [:Close]
    ohlc = TS(cumsum(randn(N,4), dims=1))
    ohlc.fields = [:Open, :High, :Low, :Close]
    hlc = ohlc[:,2:4]
    # moving regressions
    tmp = mlr_beta(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    tmp = mlr_slope(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = mlr_intercept(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = mlr(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = mlr_se(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = mlr_ub(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = mlr_lb(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = mlr_bands(tmp)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    tmp = mlr_rsq(x, adjusted=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = mlr_rsq(x, adjusted=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    # running calculations
    tmp = runmean(x, cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmean(x, cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runsum(x, cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = wilder_sum(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmad(x, cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmad(x, cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runvar(x, cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runvar(x, cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runsd(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runcov(ohlc[:,1], ohlc[:,4], cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runcov(ohlc[:,1], ohlc[:,4], cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runcor(ohlc[:,1], ohlc[:,4], cumulative=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runcor(ohlc[:,1], ohlc[:,4], cumulative=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmin(x, cumulative=true, inclusive=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmin(x, cumulative=true, inclusive=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmin(x, cumulative=false, inclusive=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmin(x, cumulative=false, inclusive=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmax(x, cumulative=true, inclusive=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmax(x, cumulative=true, inclusive=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmax(x, cumulative=false, inclusive=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runmax(x, cumulative=false, inclusive=false)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = runquantile(x, cumulative=true)
    @test !isnan(tmp.values[2,1]) && isnan(tmp.values[1,1])
    @test tmp.values[10,1] == quantile(x.values[1:10,1], 0.05)
    tmp = runquantile(x, cumulative=false)
    @test tmp.values[10,1] == quantile(x.values[1:10,1], 0.05)
    n = 20
    tmp = runacf(x, n=n, maxlag=15, cumulative=true)
    @test all(tmp.values[n:end,1] .== 1.0)
    tmp = runacf(x, n=n, maxlag=15, cumulative=false)
    @test all(tmp.values[n:end,1] .== 1.0)
    # moving average functions
    tmp = sma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = mama(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    tmp = ema(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = wma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = hma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = trima(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = mma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = tema(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = dema(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = swma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = kama(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = alma(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = zlema(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    # momentum function
    tmp = aroon(hl)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    tmp = donch(hl)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    tmp = momentum(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = roc(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = macd(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    tmp = rsi(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = adx(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    tmp = adx(hlc, wilder=true)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    tmp = psar(hl)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = kst(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = wpr(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = cci(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = stoch(hlc, kind=:fast)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    tmp = stoch(hlc, kind=:slow)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    tmp = smi(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 2
    # volatility functions
    tmp = bbands(x)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
    tmp = tr(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = atr(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 1
    tmp = keltner(hlc)
    @test size(tmp, 1) == N
    @test size(tmp, 2) == 3
end


# Tests of the AbstractModel objects in FiniteVolumes.jl

using Test
using StaticArrays
using FiniteVolumes
using ForwardDiff, LinearAlgebra

@testset "Fluxes" begin

@testset "Anonynous models" begin
    @testset "(1 var, 1D)" begin
        flux(α, n) = 0.5*α.^2 * n
        @test flux(3.0, 1.0) == 4.5
        @test FiniteVolumes.jacobian(flux, 2.0, 1.0) == 2.0
    end

    @testset "(1 var, 2D)" begin
        flux(α, n) = n' * [α, 1.0] * α
        @test flux(1.0, SVector(1.0, 0.0)) == 1.0
        @test flux(1.0, SVector(0.0, 1.0)) == 1.0
        @test FiniteVolumes.jacobian(flux, 1.0, SVector(1.0, 0.0)) == 2.0
        @test FiniteVolumes.jacobian(flux, 1.0, SVector(0.0, 1.0)) == 1.0
    end

    @testset "(2 var, 1D)" begin
        flux(u, n) = [0.5*u[1].^2 + u[2], u[2]] .* n
        @test flux([1.0, 2.0], 1.0) == [2.5, 2.0]
        @test flux(SVector(1.0, 2.0), 1.0) == SVector(2.5, 2.0)
        @test FiniteVolumes.jacobian(flux, [1.0, 1.0], 1.0) == [1.0 1.0; 0.0 1.0]
    end

    @testset "(2 var, 2D)" begin
        flux(u, n) = n' * [[u[1], u[2]], [0.0, 0.0]]
        @test flux([1.0, 2.0], [1.0, 0.0]) == [1.0, 2.0]
        @test flux([1.0, 2.0], [0.0, 1.0]) == [0.0, 0.0]
        @test FiniteVolumes.jacobian(flux, [1.0, 2.0], [1.0, 0.0]) == [1.0 0.0; 0.0 1.0]
        @test FiniteVolumes.jacobian(flux, [1.0, 2.0], [0.0, 1.0]) == [0.0 0.0; 0.0 0.0]
    end
end

@testset "LinearAdvection" begin
    mesh = CartesianMesh(10)
    w0 = [sin(2π*x) for x in cell_centers(mesh)]
    i_face = (FiniteVolumes.CartesianMeshes.Half(11),)

    flux = LinearAdvectionFlux(1.0)
    @test (Upwind())(flux, mesh, w0, i_face) == w0[5]
    # @btime (Upwind())($f, $mesh, $w, $i_face)  # ~4ns

    flux = LinearAdvectionFlux(-1.0)
    @test (Upwind())(flux, mesh, w0, i_face) == -w0[6]
end

@testset "Shallow water" begin
    @testset "1D" begin
        f = ShallowWater()
        @test f(SVector(1.0, 0.0), 1.0) == SVector(0.0, 9.81/2)
        @test f(SVector(1.0, 1.0), 1.0) == SVector(1.0, 1 + 9.81/2)
        @test f(SVector(1.0, 1.0), -1.0) == -SVector(-1.0, 1 + 9.81/2)

        for h in [1.0, 2.0], u in [-1.0, 0.0, 1.0]
            @test all(FiniteVolumes.eigvals(f, SVector(h, h*u), 1.0) .≈ SVector(u-sqrt(h*f.g), u+sqrt(h*f.g)))
        end

        f2(v, n) = n * SVector(v[2], v[2]^2/v[1] + v[1]^2*9.81/2)
        mesh = CartesianMesh(2)
        w = [SVector(1.0, 0.0), SVector(2.0, 0.0)]
        i_face = (FiniteVolumes.CartesianMeshes.Half(3),)
        @test (Upwind())(f, mesh, w, i_face) ≈ (Upwind())(f2, mesh, w, i_face)
    end

    @testset "2D" begin
        flux = ShallowWater(9.8)

        for v in [SVector(1.0, 0.0, 0.0), SVector(1.0, 1.0, 1.0)], n in [SVector(1.0, 0.0), SVector(0.0, 1.0), sqrt(2)/2*SVector(1.0, 1.0)]
            J1 = ForwardDiff.jacobian(v -> flux(v, n), v)
            J2 = FiniteVolumes.jacobian(flux, v, n)
            @test J1 == J2
            λ1 = LinearAlgebra.eigvals(Array(J1))
            λ2 = FiniteVolumes.eigvals(flux, v, n)
            @test λ1 ≈ λ2
            eg1 = LinearAlgebra.eigen(Array(J1)).vectors
            eg2 = FiniteVolumes.eigen(flux, v, n)[2]
            for (c1, c2) in zip(eachcol(eg1), eachcol(eg2))
                @test maximum(abs.(c2)) .* abs.(c1) ≈ maximum(abs.(c1)) .* abs.(c2)
            end
        end
    end
end

end


using LowRank

using JLD, Test

# rank 1
file = load(joinpath(@__DIR__, "..","datasets", "uniform_2d_10_pts.jld"))
data_set, (err, indices) = file["data"], file["size4_rank1"]
k = 1
n = 4

# @test find_low_rank_subset_checkall(data_set,n,k)[1] <= err + 0.0001
@test find_low_rank_subset_sample_rep(data_set,n,k)[1] <= 2*err + .0001
@test FastLowRank(data_set, n, k, verbose = true)[1] <= err + 0.0001

FastLowRank(data_set, n, k, verbose = true, num_trajectories = 100, convergence_threshold = 0.00000001)

# PLotting
# using Plots
# p = scatter([Tuple(x) for x in data_set], label = "all points",  series_annotations = text.(1:10, :bottom))
# scatter!(p, [Tuple(data_set[i]) for i in indices], label = "optimal points")


function test(signalVector)

    memory = length(signalVector)

    # we use binary 0,1 under the covers - so lets get rid of the -1 and replace w/0 -
    binarySignal = replace(signalVector, -1 => 0)
    iv = range(memory, stop=1, step=-1) |> collect

    # convert the binarySignal to an int -
    strategy_index = 0
    for index in iv
        value = binarySignal[index]
        strategy_index += value * 2^(memory - index)
    end

    return (strategy_index)
end

signalVector = [-1,-1, 1]
r = test(signalVector)


# matmul.jl
module Matmul
export foo!
include("row_major.jl")

function gemm(x, y, z)
    @inbounds @fastmath for m ∈ axes(x, 1), n ∈ axes(y, 2)
        zmn = zero(eltype(z))
        for k ∈ axes(x, 2)
            zmn += x[m, k] * y[k, n]
        end
        z[m, n] = zmn
    end
    return z
end

# z is output, should use . to inplace
# julia array is column-major, numpy aray is row-major
# user should change julia or numpy's layout to keep same behavior
#= EXAMPLE
A[2,3]               B[3,4]               C[2,4]
NUMPY:
[[1, 2, 3]       [[1, 2, 3, 4]         [[38, 44, 50,  56]
 [4, 5, 6]]       [5, 6, 7, 8]          [83, 98, 113,128]]
                  [9,10,11,12]]
JULIA:
inputs read numpy data from memory:
[[1, 3, 5]       [[1, 4, 7,10]
 [2, 4, 6]]       [2, 5, 8,11]
                  [3, 6, 9,12]]
inputs after reshape(reverse(shape)):
[[1, 4]          [[1, 5, 9]
 [2, 5]           [2, 6,10]
 [3, 6]]          [3, 7,11]
                  [4, 8,12]]
inputs after transpose/permutedims:
[[1, 2, 3]       [[1, 2, 3, 4]         [[38, 44, 50,  56]
 [4, 5, 6]]       [5, 6, 7, 8]          [83, 98, 113,128]]
                  [9,10,11,12]]
output after transpose/permutedims:
                                       [[38, 83]
                                        [44, 98]
                                        [50,113]
                                        [56,128]
output after reshape:
                                       [[38, 50, 83, 113]
                                        [44, 56, 98, 128]]
output read numpy data from memory:
                                       [[38, 44, 50,  56]
                                        [83, 98,113, 128]]
=#
function foo!(x, y, z)
    x = change_input_to_row_major(x)
    y = change_input_to_row_major(y)
    z .= gemm(x, y, z)
    z .= change_output_to_row_major(z)
end

end


# Copyright (c) 2021, R. Dong, C. Goodbreak, H. Harrington, G. Pogudin
# Copyright (c) 2020, A. Ovchinnikov, A. Pillay, G. Pogudin, T. Scanlon

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

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

"""
    linear_compartment_model(graph, inputs, outputs, leaks)

Input: defines a linear compartment model with nodes numbered from 1 to `n` by
- `graph` - and array of integer arrays representing the adjacency lists of the graph
- `inputs` - array of input nodes
- `outputs` - array of output nodes
- `leaks` - array of sink nodes

Output:
- the corresponding ODE system in the notation of https://doi.org/10.1007/s11538-015-0098-0
"""
function linear_compartment_model(
    graph::Vector{Vector{Int}},
    inputs::Vector{Int},
    outputs::Vector{Int},
    leaks::Vector{Int}
)
    n = length(graph)
    x_vars_names = ["x$i" for i in 1:n]
    y_vars_names = ["y$i" for i in outputs]
    u_vars_names = ["u$i" for i in inputs]
    edges_vars_names = Array{String, 1}()
    for i in 1:n
        for j in graph[i]
            push!(edges_vars_names, "a_$(j)_$(i)")
        end
    end
    for s in leaks
        push!(edges_vars_names, "a_0_$(s)")
    end

    R, vars = StructuralIdentifiability.Nemo.PolynomialRing(
        StructuralIdentifiability.Nemo.QQ,
        vcat(x_vars_names, y_vars_names, u_vars_names, edges_vars_names)
    )
    x_vars = @view vars[1:n]
    x_equations = Dict{fmpq_mpoly, Union{fmpq_mpoly, Generic.Frac{fmpq_mpoly}}}(x => R(0) for x in x_vars)
    for i in 1:n
        for j in graph[i]
            rate = str_to_var("a_$(j)_$(i)", R)
            x_equations[x_vars[j]] += x_vars[i] * rate
            x_equations[x_vars[i]] -= x_vars[i] * rate
        end
        if i in leaks
            rate = str_to_var("a_0_$(i)", R)
            x_equations[x_vars[i]] += -x_vars[i] * rate
        end
        if i in inputs
            x_equations[x_vars[i]] += str_to_var("u$i", R)
        end
    end

    y_equations = Dict{fmpq_mpoly, Union{fmpq_mpoly, Generic.Frac{fmpq_mpoly}}}(str_to_var("y$i", R) => str_to_var("x$i", R) for i in outputs)

    return ODE{fmpq_mpoly}(x_equations, y_equations, Array{fmpq_mpoly}([str_to_var("u$i", R) for i in inputs]))
end

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


using Optim
using HTTP
using GLM
using LinearAlgebra
using Random
using Statistics
using DataFrames
using CSV
using FreqTables
using CategoricalArrays
using Tables
using StatsModels
cd("C:/Users/jakek/Documents/econ_phd/structural_modeling_course/forked_class/ProblemSets/PS3-gev/")

## 1.
df = CSV.read("nlsw88w.csv")
X = [df.age df.white df.collgrad]
Z = hcat(df.elnwage1, df.elnwage2, df.elnwage3, df.elnwage4,
df.elnwage5, df.elnwage6, df.elnwage7, df.elnwage8)
y = df.occupation

# make cat 8 the base category.
function mlogit(alpha, X, d,Z)
	Zbar = Z .- Z[:,8]
	u = reduce(hcat,[X*alpha[(3*i-2):(3*i)] .+ Zbar[:,i] .*alpha[22] for i in 1:7])
	help = reduce(hcat,[d.==i for i in 1:7])
	loglike = sum(help .*u) - sum(log.(1 .+sum(exp.(u), dims=2)))
	return -loglike
end

# starting values - set intercepts to fraction present in data.
starting = Vector(prop(freqtable(df, :occupation)))
starting = repeat(starting[Not(8)], inner = 3)
starting[Not((1:7) .*3 .-2)] .= 0
# one extra row for the wage coef
starting = append!(starting, 0)

beta_hat_mlogit = optimize(b -> mlogit(b, X, y,Z),starting, BFGS(), Optim.Options(g_tol=1e-6, iterations=100_000, show_trace=true))
res = reduce(hcat,[vec(beta_hat_mlogit.minimizer)[(3*i-2):(3*i)] for i in 1:7])
res = [res [vec(beta_hat_mlogit.minimizer)[22] 0 0]']
CSV.write("q1.csv", convert(DataFrame,res))
# gamma hat represents how much people value wages relative to non-wage amenities. it also represents how sensitive job choice is to relative wage

## 3.
# construct nests.
df.nest = (3 .* (df.occupation .>7) .+  Int.(df.occupation .<=3) .+ 2 .* (3 .< df.occupation .<=7) )

# make other (3) the base.
# denote the lambdas as the last coef in the set.

function nlogit(alpha, X, d,Z)
	Zbar = Z .- Z[:,8]
	u = reduce(hcat,[exp.((X*alpha[1:3].*(i<=3)+ X*alpha[5:7].*(3<i<=7).+ Zbar[:,i] .*alpha[9]) ./ (alpha[4]*(i<=3)+alpha[8]*(3<i<=7))) for i in 1:8])
	u[:, 8] .= 1
	wc_denom = sum(u[:,1:3], dims=2)
	bc_denom = sum(u[:,4:7], dims=2)
	u[:, 1:3] = (u[:, 1:3] .* wc_denom .^(alpha[4]-1)) ./ (1 .+ bc_denom.^alpha[8] .+wc_denom.^alpha[4])
	u[:, 4:7] = u[:, 4:7] .* bc_denom .^(alpha[8]-1) ./ (1 .+ bc_denom.^alpha[8] .+wc_denom.^alpha[4])
	u[:, 8] = u[:, 8] ./ (1 .+ bc_denom.^alpha[8] .+wc_denom.^alpha[4])
	u = log.(u)
	help = reduce(hcat,[d.==i for i in 1:8])
	loglike = sum( help .*u )
	return -loglike
end

# starting values - set intercepts to fraction present in data.
starting = Vector(prop(freqtable(df, :nest)))
starting = repeat(starting[Not(3)], inner = 4)
starting[Not((1:2) .*4 .-3)] .= 0
starting[[4 8]] .= 1
# one extra row for the wage coef
starting = append!(starting, 0)
beta_hat_nlogit = optimize(b -> nlogit(b, X, y,Z),starting, BFGS(), Optim.Options(g_tol=1e-6, iterations=100_000, show_trace=true))
res = reduce(hcat,[vec(beta_hat_nlogit.minimizer)[(4*i-3):(4*i)] for i in 1:2])
res = [res [vec(beta_hat_nlogit.minimizer)[9] 0 0 0]']
CSV.write("q3.csv", convert(DataFrame,res))


using Base.Test
using QuantumOptics.sparsematrix

# SparseMatrix = quantumoptics.sparsematrix.SparseMatrix
const SparseMatrix = SparseMatrixCSC{Complex128, Int}

@testset "sparsematrix" begin

# Set up test matrices
A = rand(Complex128, 5, 5)
A_sp = sparse(A)

B = eye(Complex128, 5)
B_sp = speye(Complex128, 5)

C = rand(Complex128, 3, 3)
C[2,:] = 0
C_sp = sparse(C)

R_sp = A_sp + B_sp
R = A + B

# Test arithmetic
@test 0 ≈ norm(full(R_sp) - R)
@test 0 ≈ norm(full(Complex128(0.5,0)*A_sp) - 0.5*A)
@test 0 ≈ norm(full(A_sp/2) - A/2)
@test 0 ≈ norm(full(A_sp*B_sp) - A*B)

# Test kronecker product
@test 0 ≈ norm(full(kron(A_sp, C_sp)) - kron(A, C))
@test 0 ≈ norm(full(kron(A_sp, B_sp)) - kron(A, B))

end # testset


my_funct(x, y) = 2*x + y
der(x,y) = ForwardDiff.derivative(x->my_funct(x,y), x)


"""
    alpha(p::Int)

Returns coefficients for the spline kernel of order `p`.
"""
function alpha(p::Int)
    if p < 1
        throw(DomainError("Spline order has to be strictly positive"))
    end
    a = zeros(p)
    for i = 0:p-1
        for j = 0:i
            for k = i:p-1
                a[i+1] = a[i+1] + (-1.0)^(k-j)/((k+j+1)*
                        factorial(j)*
                        factorial(p-1)*
                        factorial(p-1-k)*
                        factorial(i-j)*
                        factorial(k-i))
            end
        end
    end
    return a
end

"""
    spline_kernel(t, p)

Returns generators `U` and `V` for the spline kernel of order `p` given a list of knots `t`.
"""
function spline_kernel(t::AbstractArray, p::Int)

    fp = factorial.(p-1:-1:0)
    a = alpha(p).*fp
    Ut = (repeat(t,p,1).^Vector(p-1:-1:0))./fp
    Vt = (repeat(t,p,1).^Vector(p:2*p-1)).*a

    return Ut,Vt
end

"""
    spline_kernel_matrix(U, V)

Returns the dense spline kernel matrix with generators `U` and `V`.
"""
function spline_kernel_matrix(U, V)
    return tril(U*V') + triu(V*U',1)
end

"""
    spline_kernel_matrix(U, V, d)

Returns the dense spline kernel matrix with generators `U` and `V` plus `Diagonal(d)`.
"""
function spline_kernel_matrix(U, V, d )
    return spline_kernel_matrix(U, V) + Diagonal(d)
end

"""
    logp(Σ, T, y, σf, σn)

Generalized Log-likelihood evalation for the spline kernel.
"""
function logp(Σ, T, y, σf, σn)
    m = rank(T)
    n = length(y)
    Ki = cholesky(σf^2*Σ  + σn^2*I)
    if LinearAlgebra.issuccess(Ki) == false
        throw(DomainError("Cholesky Factorization failed"))
    end
    A = T'*(Ki\T)
    C = (Ki\T)*(A\(T'*inv(Ki)))
    lp = -0.5*y'*(Ki\y)  +
          0.5*y'*(C*y)   +
         -0.5*logdet(Ki) +
         -0.5*logdet(A)  +
         -(n-m)*0.5*log(2*π)
    return lp
end

"""
    dlogp(Σ, T, y, σf, σn)

Derivatives of the generalized Log-likelihood for the spline kernel.
"""
function dlogp(Σ, T, y, σf, σn)
    Ki = cholesky(σf^2*Σ  + σn^2*I)
    if LinearAlgebra.issuccess(Ki) == false
        throw(DomainError("Cholesky Factorization failed"))
    end
    A = T'*(Ki\T)
    # TO_DO: Solving for the identity?
    dKf = 2.0*σf*Σ
    dAf = -T'*(Ki\dKf)*(Ki\T)
    dKn = 2.0*σn*I
    dAn = -T'*(inv(Ki)*dKn)*(Ki\T)
    Pf = Ki\dKf*inv(Ki)
    Pn = Ki\(dKn*inv(Ki))
    G = T*(A\(T'*(Ki\y)))
    dlpf =  0.5*y'*(Pf*y)                +
            0.5*(-2*(y'*Pf)*G + G'*Pf*G) +
           -0.5*tr(Ki\dKf)               +
           -0.5*tr(A\dAf)
   dlpn =   0.5*y'*(Pn*y)                   +
            0.5*(-2*(y'*Pn)*G + G'*Pn*G)    +
           -0.5*tr(inv(Ki)*dKn)             +
           -0.5*tr(A\dAn)
    return [dlpf; dlpn]
end

"""
    create_T(t, m)

Creates `m`-order Taylor-polynomial basis from the knots in `t`.
"""
function create_T(t, m)
    T = ones(length(t), m)
    for ν = 2:m
        T[:, ν] = t.^(ν-1)/factorial(ν - 1)
    end
    return T
end


# open-loop-bank-model.jl
# SimLynx Open Loop Processing Example

using SimLynx
SimLynx.greet()

using Distributions: Exponential, Uniform
using Random

const N_TELLERS = 2
const MEAN_INTERARRIVAL_TIME = 4.0
const MIN_SERVICE_TIME = 2.0
const MAX_SERVICE_TIME = 10.0

"Process to generate n customers arriving into the system."
@process generator(n::Integer) begin
    dist = Exponential(MEAN_INTERARRIVAL_TIME)
    for i = 1:n
        @schedule now customer(i)
        # We don't want to wait after the last customer, which could extend the
        # simulation time past the last customer leaving.
        if i < n
            work(rand(dist))
        end
    end
end

"The ith customer into the system."
@process customer(i::Integer) begin
    dist = Uniform(MIN_SERVICE_TIME, MAX_SERVICE_TIME)
    @with_resource tellers begin
        work(rand(dist))
    end
end

"Run the simulation for n customers."
function run_simulation(n1::Integer, n2::Integer)
    @assert n1 > 0 && n2 > 0
    println("SimLynx Open Loop Processing Example")
    println("Number of runs = $n1, number of customers = $n2")
    println("Single Queue, $N_TELLERS Teller Bank Model")
    @simulation begin
        avg_wait = Variable{Float64}(data=:tally, history=true)
        for i = 1:n1
            @simulation begin
                global tellers = Resource(N_TELLERS, "tellers")
                @schedule at 0.0 generator(n2)
                start_simulation()
                avg_wait.value = tellers.wait.stats.mean
            end
        end
        print_stats(avg_wait, title="Average Wait Statistics")
        plot_history(avg_wait, title="Average Wait History")
    end
end

run_simulation(10_000, 100)


using QPDAS, Random, Test, LinearAlgebra
import QPDAS: deleterowcol!, addrowcol!, CholeskySpecialShifted, ldiv2!

@testset "Projections and solutions" begin
@testset "Projections onto nullspace, $ϵ, $lambdamin, $closesol" for (ϵ, lambdamin, closesol) in [(1e-8, false, false), (1e-5, false, false), (1e-6, false, false), (1e-7, false, false),
                                 (1e-8, true , false), (1e-5, true , false), (1e-6, true , false), (1e-7, true , false),
                                 (1e-8, false, true ), (1e-5, false, true ), (1e-6, false, true ), (1e-7, false, true ),
                                 (1e-8, true , true ), (1e-5, true , true ), (1e-6, true , true ), (1e-7, true , true )]

Random.seed!(3141592)

m, n = 60, 40

A = randn(m,n)
M = A*A'
b = randn(m)

# Almost sinugar
F2 = svd(M, full=true)
F2.S[n+1] += 1e-1
F2.S[n+2] += 1e-3
F2.S[n+3] += 1e-5
if lambdamin
    M = F2.U*Diagonal(F2.S)*F2.U'
end

F = CholeskySpecialShifted(Symmetric(M), ϵ)

b = randn(m)

#  # Almost solution
x0 = randn(m)
# d contains nothing in range(M)
d = 1e-5*(I-M*pinv(M))*randn(m)
if closesol
    b = M*x0
    b .+= d
end

@testset "no sol" begin
    x = copy(b)
    #data1 = Data()
    xsol, projection = ldiv2!(F, x)#, data=data1)
    @test projection == true
    @test norm((I-M*pinv(M))*b - xsol) < 2/ϵ*1e-11
    @test norm(M*xsol) < 1e-8
    #println("xsol $closesol $lambdamin: $(dot(b, xsol)/norm(b)/norm(xsol))")
    #data2 = Data()
    x2sol, projection2 = ldiv2!(F, 0*x, x0=b)#, data=data2)
    @test norm((I-M*pinv(M))*b-x2sol) < 5/ϵ*1e-11 #This seems to work better
    #println("x2sol $closesol $lambdamin: $(dot(b, x2sol)/norm(b)/norm(x2sol))")
    @test norm(M*x2sol) < 1e-8
end

@testset "test sol zero residual?" begin
    b = M*x0
    x = copy(b)
    #data2 = Data()
    xsol, projection = ldiv2!(F, x)
    @test projection == false
    @test norm(M*xsol - b) < 1e-10
end

@testset "test sol ortogonal" begin
    # We should get x0 back
    b = M*x0
    x02 = x0 + pinv(M)*M*randn(m) # Add something orthogonal to Mx=0
    xsol, projection = ldiv2!(F, copy(b), x0=x02)
    @test projection == false
    @test norm(M*xsol - b) < 1e-12
    @test norm(xsol - x0) < 1e-4
end

@testset "test sol projection from zero" begin
    # We should get x0 back
    x0 = pinv(M)*M*randn(m) # Something orthogonal to Mx=0 (should be solution from 0)
    b = M*x0
    xsol, projection = ldiv2!(F, copy(b), x0=0)
    @test projection == false
    @test norm(M*xsol - b) < 1e-12
    @test norm(xsol - x0) < 1e-4
end

end

end


# This file was formerly a part of Julia. License is MIT: https://julialang.org/license
using FFTW
using FFTW: fftw_provider
using AbstractFFTs: Plan, plan_inv
using Test
using LinearAlgebra

@info "FFTW provider: $(fftw_provider)"

@testset "colmajorstrides" begin
    @test @inferred(FFTW.colmajorstrides((3, 5, 7))) == (1, 3, 15)
end

m4 = [16.    2     3    13;
      5    11    10     8;
      9     7     6    12;
      4    14    15     1]

true_fft_m4 = [
    34.            34.            34.            34.;
    7. - 1.0im  -5. + 3.0im  -3. + 5.0im   1. - 7.0im;
    16.           -16.           -16.            16.;
    7. + 1.0im  -5. - 3.0im  -3. - 5.0im   1. + 7.0im ]

true_fftn_m4 = [
    136.        0          0         0 ;
    0.       20          8 + 8im   0 - 12im ;
    0.       32 + 32im   0        32 - 32im ;
    0.        0 + 12im   8 - 8im  20 ]

true_fftd2_m4 = [
    34.   13 + 11im    4   13 - 11im ;
    34.   -5 -  3im   -4   -5 +  3im ;
    34.    3 +  5im   -4    3 -  5im ;
    34.  -11 - 13im    4  -11 + 13im ]

b = rand(17,14)
b[3:6,9:12] = m4
sm4 = view(b,3:6,9:12)

m3d = map(Float32,copy(reshape(1:5*3*2, 5, 3, 2)))
true_fftd3_m3d = Array{Float32}(undef, 5, 3, 2)
true_fftd3_m3d[:,:,1] = 17:2:45
true_fftd3_m3d[:,:,2] .= -15

@testset "fft" begin
    a = rand(8) + im*rand(8)
    @test norm(ifft(fft(a)) - a) < 1e-8
    @test norm(ifft(fft(a,1),1) - a) < 1e-8
    @test norm(ifft(fft(a,[1]),[1]) - a) < 1e-8
    @test norm(ifft(fft(a,(1,)),(1,)) - a) < 1e-8
    a = rand(-10:10, 8) + im*rand(-10:10, 8)
    @test norm(ifft(fft(a)) - a) < 1e-8

    # use invoke to force usage of CTPlan versions even if FFTW is present
    for A in (Array,SubArray)
        for f in (:fft,:ifft,:plan_fft,:plan_ifft)
            f_ = Symbol(f, "_")
            @eval begin
                $f_(x::$A{T,N}) where {T,N} = invoke($f, Tuple{AbstractArray{T,N}}, x)
                $f_(x::$A{T,N},r::R) where {T,N,R} = invoke($f,Tuple{AbstractArray{T,N},R},x,r)
            end
        end
    end

    for (f,fi,pf,pfi) in ((fft,ifft,plan_fft,plan_ifft),
                          (fft_,ifft_,plan_fft_,plan_ifft_))
        pm4 = pf(m4,1)

        fft_m4 = f(m4,1)
        fftd2_m4 = f(m4,2)
        ifft_fft_m4 = fi(f(m4,1),1)
        fftn_m4 = f(m4)
        ifftn_fftn_m4 = fi(f(m4))

        fft!_m4 = complex(m4); fft!(fft!_m4,1)
        fft!d2_m4 = complex(m4); fft!(fft!d2_m4,2)
        ifft!_fft_m4 = f(m4,1); ifft!(ifft!_fft_m4,1)
        fft!n_m4 = complex(m4); fft!(fft!n_m4)
        ifft!n_fftn_m4 = f(m4); ifft!(ifft!n_fftn_m4)

        pfft_m4 = pf(m4,1)*m4
        pfftd2_m4 = pf(m4,2)*m4
        pifft_fft_m4 = pfi(fft_m4,1)*fft_m4
        pfftn_m4 = pf(m4)*m4
        pifftn_fftn_m4 = pfi(fftn_m4)*fftn_m4

        pfft!_m4 = complex(m4); plan_fft!(pfft!_m4,1)*pfft!_m4
        pfft!d2_m4 = complex(m4); plan_fft!(pfft!d2_m4,2)*pfft!d2_m4
        pifft!_fft_m4 = f(m4,1); plan_ifft!(pifft!_fft_m4,1)*pifft!_fft_m4
        pfft!n_m4 = complex(m4); plan_fft!(pfft!n_m4)*pfft!n_m4
        pifft!n_fftn_m4 = f(m4); plan_ifft!(pifft!n_fftn_m4)*pifft!n_fftn_m4

        sfftn_m4 = f(sm4)
        psfftn_m4 = pf(sm4)*sm4
        sfft!n_b = map(Complex{Float64},b)
        sfft!n_m4 = view(sfft!n_b,3:6,9:12); fft!(sfft!n_m4)
        psfft!n_b = map(Complex{Float64},b)
        psfft!n_m4 = view(psfft!n_b,3:6,9:12); plan_fft!(psfft!n_m4)*psfft!n_m4

        for i = 1:length(m4)
            @test fft_m4[i] ≈ true_fft_m4[i]
            @test fftd2_m4[i] ≈ true_fftd2_m4[i]
            @test ifft_fft_m4[i] ≈ m4[i]
            @test fftn_m4[i] ≈ true_fftn_m4[i]
            @test ifftn_fftn_m4[i] ≈ m4[i]

            @test fft!_m4[i] ≈ true_fft_m4[i]
            @test fft!d2_m4[i] ≈ true_fftd2_m4[i]
            @test ifft!_fft_m4[i] ≈ m4[i]
            @test fft!n_m4[i] ≈ true_fftn_m4[i]
            @test ifft!n_fftn_m4[i] ≈ m4[i]

            @test pfft_m4[i] ≈ true_fft_m4[i]
            @test pfftd2_m4[i] ≈ true_fftd2_m4[i]
            @test pifft_fft_m4[i] ≈ m4[i]
            @test pfftn_m4[i] ≈ true_fftn_m4[i]
            @test pifftn_fftn_m4[i] ≈ m4[i]

            @test pfft!_m4[i] ≈ true_fft_m4[i]
            @test pfft!d2_m4[i] ≈ true_fftd2_m4[i]
            @test pifft!_fft_m4[i] ≈ m4[i]
            @test pfft!n_m4[i] ≈ true_fftn_m4[i]
            @test pifft!n_fftn_m4[i] ≈ m4[i]

            @test sfftn_m4[i] ≈ true_fftn_m4[i]
            @test sfft!n_m4[i] ≈ true_fftn_m4[i]
            @test psfftn_m4[i] ≈ true_fftn_m4[i]
            @test psfft!n_m4[i] ≈ true_fftn_m4[i]
        end

        ifft!(sfft!n_m4)
        plan_ifft!(psfft!n_m4)*psfft!n_m4
        @test norm(sfft!n_m4 - m4) < 1e-8
        @test norm(psfft!n_m4 - m4) < 1e-8

        # The following capabilities are FFTW only.
        # They are not available in MKL, and hence do not test them.
        if fftw_provider == "fftw"
            @testset "FFTW-specific" begin
                ifft3_fft3_m3d = fi(f(m3d))

                fftd3_m3d = f(m3d,3)
                ifftd3_fftd3_m3d = fi(fftd3_m3d,3)

                fft!d3_m3d = complex(m3d); fft!(fft!d3_m3d,3)
                ifft!d3_fftd3_m3d = copy(fft!d3_m3d); ifft!(ifft!d3_fftd3_m3d,3)

                pfftd3_m3d = pf(m3d,3)*m3d
                pifftd3_fftd3_m3d = pfi(fftd3_m3d,3)*fftd3_m3d

                pfft!d3_m3d = complex(m3d); plan_fft!(pfft!d3_m3d,3)*pfft!d3_m3d
                pifft!d3_fftd3_m3d = copy(fft!d3_m3d); plan_ifft!(pifft!d3_fftd3_m3d,3)*pifft!d3_fftd3_m3d

                @test isa(fftd3_m3d, Array{Complex{Float32},3})
                @test isa(ifftd3_fftd3_m3d, Array{Complex{Float32},3})
                @test isa(fft!d3_m3d, Array{Complex{Float32},3})
                @test isa(ifft!d3_fftd3_m3d, Array{Complex{Float32},3})
                @test isa(pfftd3_m3d, Array{Complex{Float32},3})
                @test isa(pifftd3_fftd3_m3d, Array{Complex{Float32},3})
                @test isa(pfft!d3_m3d, Array{Complex{Float32},3})
                @test isa(pifft!d3_fftd3_m3d, Array{Complex{Float32},3})

                for i = 1:length(m3d)
                    @test fftd3_m3d[i] ≈ true_fftd3_m3d[i]
                    @test ifftd3_fftd3_m3d[i] ≈ m3d[i]
                    @test ifft3_fft3_m3d[i] ≈ m3d[i]

                    @test fft!d3_m3d[i] ≈ true_fftd3_m3d[i]
                    @test ifft!d3_fftd3_m3d[i] ≈ m3d[i]

                    @test pfftd3_m3d[i] ≈ true_fftd3_m3d[i]
                    @test pifftd3_fftd3_m3d[i] ≈ m3d[i]
                    @test pfft!d3_m3d[i] ≈ true_fftd3_m3d[i]
                    @test pifft!d3_fftd3_m3d[i] ≈ m3d[i]
                end
            end
        end  # if fftw_provider == "fftw"

    end
end

@testset "rfft/rfftn" begin
    rfft_m4 = rfft(m4,1)
    rfftd2_m4 = rfft(m4,2)
    rfftn_m4 = rfft(m4)

    prfft_m4 = plan_rfft(m4,1)*m4
    prfftd2_m4 = plan_rfft(m4,2)*m4
    prfftn_m4 = plan_rfft(m4)*m4

    srfftn_m4 = rfft(sm4)
    psrfftn_m4 = plan_rfft(sm4)*sm4

    for i = 1:3, j = 1:4
        @test rfft_m4[i,j] ≈ true_fft_m4[i,j]
        @test rfftd2_m4[j,i] ≈ true_fftd2_m4[j,i]
        @test rfftn_m4[i,j] ≈ true_fftn_m4[i,j]

        @test prfft_m4[i,j] ≈ true_fft_m4[i,j]
        @test prfftd2_m4[j,i] ≈ true_fftd2_m4[j,i]
        @test prfftn_m4[i,j] ≈ true_fftn_m4[i,j]

        @test srfftn_m4[i,j] ≈ true_fftn_m4[i,j]
        @test psrfftn_m4[i,j] ≈ true_fftn_m4[i,j]
    end

    irfft_rfft_m4 = irfft(rfft_m4,size(m4,1),1)
    irfft_rfftd2_m4 = irfft(rfftd2_m4,size(m4,2),2)
    irfftn_rfftn_m4 = irfft(rfftn_m4,size(m4,1))

    pirfft_rfft_m4 = plan_irfft(rfft_m4,size(m4,1),1)*rfft_m4
    pirfft_rfftd2_m4 = plan_irfft(rfftd2_m4,size(m4,2),2)*rfftd2_m4
    pirfftn_rfftn_m4 = plan_irfft(rfftn_m4,size(m4,1))*rfftn_m4

    for i = 1:length(m4)
        @test irfft_rfft_m4[i] ≈ m4[i]
        @test irfft_rfftd2_m4[i] ≈ m4[i]
        @test irfftn_rfftn_m4[i] ≈ m4[i]

        @test pirfft_rfft_m4[i] ≈ m4[i]
        @test pirfft_rfftd2_m4[i] ≈ m4[i]
        @test pirfftn_rfftn_m4[i] ≈ m4[i]
    end

    @testset "with preallocated array" begin
        prfft_m4_prealloc = zero(prfft_m4)
        prfftd2_m4_prealloc = zero(prfftd2_m4)
        prfftn_m4_prealloc = zero(prfftn_m4)

        pirfft_rfft_m4_prealloc = zero(pirfft_rfft_m4)
        pirfft_rfftd2_m4_prealloc = zero(pirfft_rfftd2_m4)
        pirfftn_rfftn_m4_prealloc = zero(pirfftn_rfftn_m4)

        mul!(prfft_m4_prealloc,plan_rfft(m4,1),m4)
        mul!(prfftd2_m4_prealloc,plan_rfft(m4,2),m4)
        mul!(prfftn_m4_prealloc,plan_rfft(m4),m4)

        for i = 1:3, j = 1:4
            @test prfft_m4_prealloc[i,j] ≈ true_fft_m4[i,j]
            @test prfftd2_m4_prealloc[j,i] ≈ true_fftd2_m4[j,i]
            @test prfftn_m4_prealloc[i,j] ≈ true_fftn_m4[i,j]
        end

        mul!(pirfft_rfft_m4_prealloc,plan_irfft(rfft_m4,size(m4,1),1),prfft_m4_prealloc)
        mul!(pirfft_rfftd2_m4_prealloc,plan_irfft(rfftd2_m4,size(m4,2),2),prfftd2_m4_prealloc)
        mul!(pirfftn_rfftn_m4_prealloc,plan_irfft(rfftn_m4,size(m4,1)),prfftn_m4_prealloc)

        for i = 1:length(m4)
            @test pirfft_rfft_m4_prealloc[i] ≈ m4[i]
            @test pirfft_rfftd2_m4_prealloc[i] ≈ m4[i]
            @test pirfftn_rfftn_m4_prealloc[i] ≈ m4[i]
        end
    end

    if fftw_provider == "fftw"
        @testset "FFTW-specific" begin
            rfftn_m3d = rfft(m3d)
            rfftd3_m3d = rfft(m3d,3)
            @test size(rfftd3_m3d) == size(true_fftd3_m3d)
            irfft_rfftd3_m3d = irfft(rfftd3_m3d,size(m3d,3),3)
            irfftn_rfftn_m3d = irfft(rfftn_m3d,size(m3d,1))
            for i = 1:length(m3d)
                @test rfftd3_m3d[i] ≈ true_fftd3_m3d[i]
                @test irfft_rfftd3_m3d[i] ≈ m3d[i]
                @test irfftn_rfftn_m3d[i] ≈ m3d[i]
            end

            fftn_m3d = fft(m3d)
            @test size(fftn_m3d) == (5,3,2)
            rfftn_m3d = rfft(m3d)
            @test size(rfftn_m3d) == (3,3,2)
            for i = 1:3, j = 1:3, k = 1:2
                @test rfftn_m3d[i,j,k] ≈ fftn_m3d[i,j,k]
            end
        end # !mkl
    end
end

# FFT self-test algorithm (for unscaled 1d forward FFTs):
#   Funda Ergün, "Testing multivariate linear functions: Overcoming
#   the generator bottleneck," Proc. 27th ACM Symposium on the Theory
#   of Computing, pp. 407-416 (1995).
# Check linearity, impulse-response, and time-shift properties.
function fft_test(p::Plan{T}, ntrials=4,
                  tol=1e5 * eps(real(T))) where T<:Complex
    ndims(p) == 1 || throw(ArgumentError("not a 1d FFT"))
    n = length(p)
    twopi_i = (-2 * convert(real(T), π)/n * (0:n-1)) * im
    for trial = 1:ntrials
        # linearity:
        x = rand(T, n)
        y = rand(T, n)
        α = rand(T)
        β = rand(T)
        X = p * (α*x + β*y)
        err = norm(α * (p*x) + β * (p*y) - X, Inf) / norm(X, Inf)
        err <= tol || error("linearity error $err in $p")

        # impulse-response:
        z = zeros(T, n)
        i = rand(0:n-1)
        z[i+1] = 1
        X = exp.(twopi_i*i)
        err = norm(p*z - X, Inf) / norm(X, Inf)
        err <= tol || error("impulse-response error $err in $p")

        # time-shift:
        if n > 1
            s = rand(1:n-1)
            X = (p*x).*exp.(twopi_i*s)
            err = norm(p*circshift(x,s) - X, Inf) / norm(X, Inf)
            err <= tol || error("time-shift error $err in $p")
        end
    end
end

@testset "Self-test: $(T)" for T in (Complex{Float32}, Complex{Float64})
    for n in [1:100; 121; 143; 1000; 1024; 1031; 2000; 2048]
        x = zeros(T, n)
        fft_test(plan_fft(x))
        fft_test(plan_fft_(x))
    end
end

@testset "Inversion, scaling, and pre-allocated variants: $(T)" for T in (Complex{Float32}, Complex{Float64})
    for x in (T[1:100;], copy(reshape(T[1:200;], 20,10)))
        y = similar(x)
        for planner in (plan_fft, plan_fft_, plan_ifft, plan_ifft_)
            p = planner(x)
            pi = inv(p)
            p3 = 3*p
            p3i = inv(p3)
            @test eltype(p) == eltype(pi) == eltype(p3) == eltype(p3i) == T
            @test norm(x - p3i * (p * 3x)) < eps(real(T)) * 10000
            @test norm(3x - pi * (p3 * x)) < eps(real(T)) * 10000
            mul!(y, p, x)
            @test y == p * x
            ldiv!(y, p, x)
            @test y == p \ x
        end
    end
end

let
    plan32 = plan_fft([1.0:2048.0;])
    plan64 = plan_fft([1f0:2048f0;])
    FFTW.flops(plan32)
    FFTW.flops(plan64)
end

@testset "Base Julia issue #9772, with size $(size(x))" for x in (randn(10),randn(10,12))
    z = complex(x)
    y = rfft(x)
    @inferred rfft(x)

    # See Julia issue #23063
    if ndims(x) == 2
        @test_broken @inferred brfft(x,18)
    end

    @inferred brfft(y,10)
    for f in (plan_bfft!, plan_fft!, plan_ifft!,
              plan_bfft, plan_fft, plan_ifft,
              fft, bfft, fft_, ifft)
        p = @inferred f(z)
        if isa(p, Plan)
            @inferred plan_inv(p)
        end
    end
    for f in (plan_bfft, plan_fft, plan_ifft,
              plan_rfft, fft, bfft, fft_, ifft)
        # More of #23063 (why does plan_rfft work and the others don't)?
        if ndims(x) == 2 && f != plan_rfft
            @test_broken @inferred f(x)
            @test_broken @inferred plan_inv(f(x))
            continue
        end
        p = @inferred f(x)
        if isa(p, Plan)
            @inferred plan_inv(p)
        end
    end
    # note: inference doesn't work for plan_fft_ since the
    #       algorithm steps are included in the CTPlan type
end

@testset "Base Julia issue #17896" begin
    a = rand(5)
    @test  fft(a) ==  fft(view(a,:)) ==  fft(view(a, 1:5)) ==  fft(view(a, [1:5;]))
    @test rfft(a) == rfft(view(a,:)) == rfft(view(a, 1:5)) == rfft(view(a, [1:5;]))
    a16 = convert(Vector{Float16}, a)
    @test  fft(a16) ==  fft(view(a16,:)) ==  fft(view(a16, 1:5)) ==  fft(view(a16, [1:5;]))
    @test rfft(a16) == rfft(view(a16,:)) == rfft(view(a16, 1:5)) == rfft(view(a16, [1:5;]))
end

if fftw_provider == "fftw"
    @testset "Discrete cosine transform (DCT) tests" begin
        a = rand(8,11) + im*rand(8,11)
        @test norm(idct(dct(a)) - a) < 1e-8

        X = reshape([1,2,7,2,1,5,9,-1,3,4,6,9],3,4)
        Y = rand(17,14)
        Y[3:5,9:12] = X
        sX = view(Y,3:5,9:12)

        true_Xdct = [  13.856406460551018  -3.863239728836245   2.886751345948129  -0.274551994240164; -2.828427124746190  -2.184015211898548  -4.949747468305834   3.966116180118245; 4.898979485566356  -0.194137576915510  -2.857738033247041   2.731723009609389 ]

        true_Xdct_1 = [    5.773502691896258   4.618802153517007   6.350852961085884  10.969655114602890; -4.242640687119286  -2.121320343559643   4.242640687119286  -3.535533905932738; 1.632993161855452   2.041241452319315   5.715476066494083   0.408248290463863 ]

        true_Xdct_2 = [    8.  -3.854030797826254  -3.0  3.761176226848022;
                           4.0  -2.071929829606556   4.0  -2.388955165168770; 12.  -0.765366864730179   4.0  -1.847759065022573 ]

        Xdct = dct(X)
        Xdct! = float(X); dct!(Xdct!)
        Xdct_1 = dct(X,1)
        Xdct!_1 = float(X); dct!(Xdct!_1,1)
        Xdct_2 = dct(X,2)
        Xdct!_2 = float(X); dct!(Xdct!_2,2)

        Xidct = idct(true_Xdct)
        Xidct! = copy(true_Xdct); idct!(Xidct!)
        Xidct_1 = idct(true_Xdct_1,1)
        Xidct!_1 = copy(true_Xdct_1); idct!(Xidct!_1,1)
        Xidct_2 = idct(true_Xdct_2,2)
        Xidct!_2 = copy(true_Xdct_2); idct!(Xidct!_2,2)

        pXdct = plan_dct(X)*(X)
        pXdct! = float(X); plan_dct!(pXdct!)*(pXdct!)
        pXdct_1 = plan_dct(X,1)*(X)
        pXdct!_1 = float(X); plan_dct!(pXdct!_1,1)*(pXdct!_1)
        pXdct_2 = plan_dct(X,2)*(X)
        pXdct!_2 = float(X); plan_dct!(pXdct!_2,2)*(pXdct!_2)

        pXidct = plan_idct(true_Xdct)*(true_Xdct)
        pXidct! = copy(true_Xdct); plan_idct!(pXidct!)*(pXidct!)
        pXidct_1 = plan_idct(true_Xdct_1,1)*(true_Xdct_1)
        pXidct!_1 = copy(true_Xdct_1); plan_idct!(pXidct!_1,1)*(pXidct!_1)
        pXidct_2 = plan_idct(true_Xdct_2,2)*(true_Xdct_2)
        pXidct!_2 = copy(true_Xdct_2); plan_idct!(pXidct!_2,2)*(pXidct!_2)

        sXdct = dct(sX)
        psXdct = plan_dct(sX)*(sX)
        sYdct! = copy(Y); sXdct! = view(sYdct!,3:5,9:12); dct!(sXdct!)
        psYdct! = copy(Y); psXdct! = view(psYdct!,3:5,9:12); plan_dct!(psXdct!)*(psXdct!)

        for i = 1:length(X)
            @test Xdct[i] ≈ true_Xdct[i]
            @test Xdct![i] ≈ true_Xdct[i]
            @test Xdct_1[i] ≈ true_Xdct_1[i]
            @test Xdct!_1[i] ≈ true_Xdct_1[i]
            @test Xdct_2[i] ≈ true_Xdct_2[i]
            @test Xdct!_2[i] ≈ true_Xdct_2[i]

            @test pXdct[i] ≈ true_Xdct[i]
            @test pXdct![i] ≈ true_Xdct[i]
            @test pXdct_1[i] ≈ true_Xdct_1[i]
            @test pXdct!_1[i] ≈ true_Xdct_1[i]
            @test pXdct_2[i] ≈ true_Xdct_2[i]
            @test pXdct!_2[i] ≈ true_Xdct_2[i]

            @test Xidct[i] ≈ X[i]
            @test Xidct![i] ≈ X[i]
            @test Xidct_1[i] ≈ X[i]
            @test Xidct!_1[i] ≈ X[i]
            @test Xidct_2[i] ≈ X[i]
            @test Xidct!_2[i] ≈ X[i]

            @test pXidct[i] ≈ X[i]
            @test pXidct![i] ≈ X[i]
            @test pXidct_1[i] ≈ X[i]
            @test pXidct!_1[i] ≈ X[i]
            @test pXidct_2[i] ≈ X[i]
            @test pXidct!_2[i] ≈ X[i]

            @test sXdct[i] ≈ true_Xdct[i]
            @test psXdct[i] ≈ true_Xdct[i]
            @test sXdct![i] ≈ true_Xdct[i]
            @test psXdct![i] ≈ true_Xdct[i]
        end
    end
end # fftw_provider == "fftw"

@testset "UNALIGNED flag" begin
    A = rand(Float32, 35)
    Ac = rand(Complex{Float32}, 35)
    Y = Array{Complex{Float32}}(undef, 20)
    Yc = Array{Complex{Float32}}(undef, 35)
    planr = plan_rfft(Array{Float32}(undef, 32), flags=FFTW.UNALIGNED)
    planc = plan_fft(Array{Complex{Float32}}(undef, 32), flags=FFTW.UNALIGNED)
    for ioff in 0:3
        ii = 1+ioff:32+ioff
        @test planr * view(A, ii) ≈ planr * A[ii] ≈ rfft(view(A, ii)) ≈ rfft(A[ii])
        @test planc * view(Ac, ii) ≈ planc * Ac[ii] ≈ fft(view(Ac, ii)) ≈ fft(Ac[ii])
        for ooff in 0:3
            io = 1+ooff:17+ooff
            FFTW.mul!(view(Y, io), planr, view(A, ii))
            @test Y[io] ≈ rfft(A[ii])
            FFTW.mul!(view(Y, io), planr, A[ii])
            @test Y[io] ≈ rfft(A[ii])
            io = 1+ooff:32+ooff
            FFTW.mul!(view(Yc, io), planc, view(Ac, ii))
            @test Yc[io] ≈ fft(Ac[ii])
            FFTW.mul!(view(Yc, io), planc, Ac[ii])
            @test Yc[io] ≈ fft(Ac[ii])
        end
    end

    # check whether FFTW on this architecture has nontrivial alignment requirements
    nontrivial_alignment = FFTW.fftw_provider == "fftw" && ccall((:fftwf_alignment_of, FFTW.libfftw3f[]), Int32, (Int,), 8) != 0
    if nontrivial_alignment
        @test_throws ArgumentError plan_rfft(Array{Float32}(undef, 32)) * view(A, 2:33)
        @test_throws ArgumentError plan_fft(Array{Complex{Float32}}(undef, 32)) * view(Ac, 2:33)
    end
end

@testset "Base Julia issue #19892" begin
    a = randn(10^5,1)
    FFTW.set_num_threads(1)
    p1 = plan_rfft(a, flags=FFTW.ESTIMATE)
    FFTW.set_num_threads(2)
    p2 = plan_rfft(a, flags=FFTW.ESTIMATE)
    @test p1*a ≈ p2*a
    # make sure threads are actually being used for p2
    # (tests #21163).
    if FFTW.has_sprint_plan
        @test !occursin("dft-thr", string(p1))
        @test occursin("dft-thr", string(p2))
    end
end


#==============================================================================#
# RJNN: Random network and data generation
#==============================================================================#

using Random

"Returns a neural network with random edges and weights."
function randnet(ins::Integer, outs::Integer, hidden::Vector{<:Integer};
        batch_size=1024, density=0.5)
    sizes = [ins, hidden..., outs]
    dims = zip(sizes[1:end-1], sizes[2:end])
    weights = [zeros(m, n) for (m, n) in dims]
    biases = [zeros(1, n) for n in sizes[2:end]]

    # Initialise a random subset of the weights and biases, and ensure that
    # every unit is connected to its neighbouring layers by at least one edge --
    # i.e., that the weight matrices have no empty rows or columns.
    for (W, b) in zip(weights, biases)
        randnfill!(W, density)
        randnfill!(b, density)
        randnconnect!(W)
    end

    Net(weights, biases, batch_size)
end

"Initialises a random subset of an array's elements using a standard normal
 distribution."
function randnfill!(A::AbstractArray{<:AbstractFloat}, p)
    js = randsubseq(eachindex(A), p) # each position is chosen with independent
    randn!(@view A[js])              # probability p (Bernoulli sampling).
end

"Ensures that every row and column of a matrix contains at least one non-zero
 value."
function randnconnect!(A::AbstractMatrix{<:AbstractFloat})
    for i in axes(A, 1)
        if iszero(@view A[i,:])
            A[i, rand(axes(A, 2))] = randn()
        end
    end
    for j in axes(A, 2)
        if iszero(@view A[:,j])
            A[rand(axes(A, 1)), j] = randn()
        end
    end
end


# Embed distance matrix
function embedmatrix1(x, delay, metric="max")
    dist = getmetric(metric)
    fun = Dict(Euclidean()=>+, Chebyshev()=>max)[dist]
    nr, nc = size(x)
    x = abs2(x)
    sqrt(fun(x[1:nr-delay, 1:nc-delay], x[1+delay:nr, 1+delay:nc]))
end

"""
    fnn(x, mbounds, delay, thresholds; metric="max")

Calculate the number of false nearest neighbours (FNN) by Kennel's algorithm.

FNN are calculated for the embedding dimensions between `mbounds[1]` and
`mbounds[2]`, and for the given delay, taking `thresholds[1]` as the parameter
Rtol, and `thresholds[2]` as the parameter Atol. The argument `metric` defines
what kind of distance is calculated between pairs of points, as in
`distancematrix`.
"""
function fnn(x, mbounds, delay, thresholds; metric="max")
    Rtol2 = thresholds[1]^2
    Atol = thresholds[2]
    Ra = std(x)
    m1, m2 = mbounds
    nfnn = zeros(m2-m1+1)
    # DM - with maximum value in the diagonal to rule it out in neighbour search
    # Start with matrix embedded at m=m1
    dm = distancematrix(embed(x, m1, delay), metric)
    n = size(dm)[1]
    dm[1:n+1:end] = maximum(dm)
    for m = 1:length(nfnn)
        # Look for nearest neighbours within the first n-delay
        n -= delay
        nnval, nnpos = findmin(dm[1:n,1:n], 2)
        dm = embedmatrix1(dm, delay, metric)
        nnv1 = nnval[1:n]
        nnv2 = dm[1:n,1:n][nnpos]
        fnn1 = ( ((nnv2.^2)./(nnv1.^2)-1) .> Rtol2 )
        fnn2 = ( (nnv2/Ra) .> Atol )
        nfnn[m] = sum(fnn1 | fnn2)
    end
    nfnn
end

"""
    afnn(x, mbounds, delay; metric="max")

Calculate the ratios E1 and E2 of Cao's method of "averaged false nearest neighbours".

The ratios are calculated for the embedding dimensions between `mbounds[1]` and
`mbounds[2]`, and for the given delay. The argument `metric` defines
what kind of distance is calculated between pairs of points, as in
`distancematrix`.
"""

function afnn(x, mbounds, delay; metric="max")
    m1, m2 = mbounds
    dm = distancematrix(embed(x, m1, delay), metric)
    n = size(dm)[1]
    dm[1:n+1:end] = maximum(dm)
    mean_ratio = zeros(m2-m1+2)
    mean_increment = zeros(m2-m1+2)
    for m = 1:length(mean_ratio)
        n -= delay
        nnval, nnpos = findmin(dm[1:n,1:n], 2)
        dm = embedmatrix1(dm, delay, metric)
        nnv1 = nnval[1:n]
        nnv2 = dm[1:n,1:n][nnpos]
        mean_ratio[m] = mean(nnv2./nnv1)
        # Project nnpos in original series
        nnx = div(nnpos, n) + 1
        d = (m1+m-1)*delay
        mean_increment[m] = mean(abs(x[(1:n)+d]-x[nnx+d]))
    end
    e1 = mean_ratio[2:end]./mean_ratio[1:end-1]
    e2 = mean_increment[2:end]./mean_increment[1:end-1]
    e1, e2
end

"""
    ffnn(x, mbounds, delay; metric="max")

Calculate the ratio of false first nearest neighbours (FFNN) by Krakovská's algorithm.

The ratios are calculated for the embedding dimensions between `mbounds[1]` and
`mbounds[2]`, and for the given delay. The argument `metric` defines
what kind of distance is calculated between pairs of points, as in
`distancematrix`.
"""
function ffnn(x, mbounds, delay; metric="max")
    m1, m2 = mbounds
    dm = distancematrix(embed(x, m1, delay), metric)
    n = size(x)[1]
    dm[1:n+1:end] = maximum(dm)
    fnn_ratio = zeros(m2-m1+1)
    for m = 1:length(fnn_ratio)
        n -= delay
        nnval, nnpos1 = findmin(dm[1:n,1:n], 2)
        dm = embedmatrix1(dm, delay, metric)
        nnval, nnpos2 = findmin(dm, 2)
        fnn_ratio[m] = sum(nnpos1 .!= nnpos2)/n
    end
    fnn_ratio
end


function burgularSimple()

    burgular,earthquake,alarm,radio = 1,2,3,4 #variables

    yes,no=1,2 #states

    #useful for printing reaults
    dictVariable=Dict{Integer,DiscreteVariable}()
    dictVariable[burglar]=DiscreteVariable("burglar",["yes","no"])
    dictVariable[earthquake]=DiscreteVariable("earthquake",["yes","no"])
    dictVariable[alarm]=DiscreteVariable("alarm",["yes","no"])
    dictVariable[radio]=DiscreteVariable("radio",["yes","no"])

    #dicrete probability tables:
    potBurglar=PotArray(burgular,[0.01 0.99])
    potEarthquake=PotArray(earthquake,[0.000001 , 1-0.000001])

    table = zeros(2,2)
    table[yes,yes]=1
    table[no,no]=0
    table[yes,no]=0
    table[no,no]=1
    potRadio=PotArray([radio earthquake],table)

    table=zeros(2,2,2)
    table[yes,yes,yes]=0.9999
    table[yes,yes,no]=0.99
    table[yes,no,yes]=0.99
    table[yes,no,no]=0.0001

    table[no,:,:] = 1-table[yes,:,:]
    potAlarm=PotArray([radio burgular earthquake], table)

    jointPot=prod([potBurglar potRadio potAlarm potEarthquake])

    println("p(burglar|alarm=yes):")
    show(condpot(setpot(jointpot,alarm,yes),burglar),dictVariable)
end


immutable NoncentralChisq{T<:Real} <: ContinuousUnivariateDistribution
    ν::T
    λ::T
    function NoncentralChisq(ν::T, λ::T)
        @check_args(NoncentralChisq, ν > zero(ν))
        @check_args(NoncentralChisq, λ >= zero(λ))
    	new(ν, λ)
    end
end

NoncentralChisq{T<:Real}(ν::T, λ::T) = NoncentralChisq{T}(ν, λ)
NoncentralChisq(ν::Real, λ::Real) = NoncentralChisq(promote(ν, λ)...)
NoncentralChisq(ν::Integer, λ::Integer) = NoncentralChisq(Float64(ν), Float64(λ))

@distr_support NoncentralChisq 0.0 Inf

#### Conversions

function convert{T <: Real, S <: Real}(::Type{NoncentralChisq{T}}, ν::S, λ::S)
    NoncentralChisq(T(ν), T(λ))
end
function convert{T <: Real, S <: Real}(::Type{NoncentralChisq{T}}, d::NoncentralChisq{S})
    NoncentralChisq(T(d.ν), T(d.λ))
end

### Parameters

params(d::NoncentralChisq) = (d.ν, d.λ)
@inline partype{T<:Real}(d::NoncentralChisq{T}) = T

### Statistics

mean(d::NoncentralChisq) = d.ν + d.λ
var(d::NoncentralChisq) = 2(d.ν + 2d.λ)
skewness(d::NoncentralChisq) = 2sqrt2*(d.ν + 3d.λ)/sqrt(d.ν + 2d.λ)^3
kurtosis(d::NoncentralChisq) = 12(d.ν + 4d.λ)/(d.ν + 2d.λ)^2

function mgf(d::NoncentralChisq, t::Real)
    exp(d.λ * t/(1 - 2t))*(1 - 2t)^(-d.ν/2)
end

function cf(d::NoncentralChisq, t::Real)
    cis(d.λ * t/(1 - 2im*t))*(1 - 2im*t)^(-d.ν/2)
end

### Evaluation & Sampling

@_delegate_statsfuns NoncentralChisq nchisq ν λ

rand(d::NoncentralChisq) = StatsFuns.RFunctions.nchisqrand(d.ν, d.λ)


module SpecialFuncs

using PyCall
using SymPy
using SpecialFunctions
import SpecialFunctions: besseli, besselj, besselk, bessely
import SpecialFunctions: airyai, airyaiprime, airybi, airybiprime, airyaix,airyaiprimex,
airybix, airybiprimex, besselh, besselhx, besselix, besselj0, besselj1, besseljx, besselkx,
bessely0, bessely1, besselyx, beta, dawson, erf, erfc, erfcinv, erfcx, erfi, erfinv,
eta, digamma, gamma, invdigamma, lgamma, polygamma, trigamma,
hankelh1, hankelh1x, hankelh2, hankelh2x, zeta

## https://github.com/JuliaMath/SpecialFunctions.jl/blob/master/src/SpecialFunctions.jl
## julia -> sympy mapping.
julia_sympy_map = (
                   :airyai       => :airyai,
                   :airyaiprime  => :airyaiprime,
                   :airybi       => :airybi,
                   :airybiprime  => :airybiprime,
                   :airyaix      => :nothing,
                   :airyaiprimex => :nothing,
                   :airybix      => :nothing,
                   :airybiprimex => :nothing,
                   :besselh      => :nothing,
                   :besselhx     => :nothing,
#                  :besseli      => :besseli,
                   :besselix     => :nothing,
#                  :besselj      => :besselj,
                   :besselj0     => :nothing,
:besselj1   => :nothing,
:besseljx   => :nothing,
## :besselk    => :besselk,
:besselkx   => :nothing,
## :bessely    => :bessely,
:bessely0   => :nothing,
:bessely1   => :nothing,
:besselyx   => :nothing,
#:beta       => :beta,
:dawson     => :nothing,
:erf        => :erf,
:erfc       => :erfc,
:erfcinv    => :erfcinv,
:erfcx      => :erfcx,
:erfi       => :erfi,
:erfinv     => :erfinv,
:eta        => :nothing,
:digamma    => :digamma,
:gamma      => :gamma,
:invdigamma => :nothing,
:lgamma     => :lgamma,
:polygamma  => :polygamma,
:trigamma   => :trigamma,
:hankelh1   => :nothing,
:hankelh1x  => :nothing,
:hankelh2   => :nothing,
:hankelh2x  => :nothing,
:zeta       => :zeta,
#
#  SymPy only
#
:nothing    => :uppergamma,
:nothing    => :lowergamma,
:nothing    => :erf2,
:nothing    => :erf2inv,
:nothing    => :fresnels,
:nothing    => :fresnelc,
:nothing    => :Ei,
:nothing    => :expint,
:nothing    => :li,
:nothing    => :Li,
:nothing    => :Si,
:nothing    => :Ci,
:nothing    => :Shi,
:nothing    => :Chi,
:nothing    => :hankel1,
:nothing    => :hankel2,
:nothing    => :jn,
:nothing    => :yn,
:nothing    => :hn1,
:nothing    => :hn2,
:nothing    => :jn_zeros,
:nothing    => :dirichlet_eta,
:nothing    => :polylog,
:nothing    => :lerchphi,
:nothing    => :stieltjes,
:nothing    => :elliptic_k,
:nothing    => :elliptic_f,
:nothing    => :elliptic_e,
:nothing    => :elliptic_pi,
:nothing    => :mathieus,
:nothing    => :mathieuc,
:nothing    => :mathieusprime,
:nothing    => :mathieucprime,
:nothing    => :jacobi,
:nothing    => :jacobi_normalized,
:nothing    => :gegenbauer,
:nothing    => :chebyshevt,
:nothing    => :chebyshevu,
:nothing    => :chebyshevt_root,
:nothing    => :chebyshevu_root,
:nothing    => :legendre,
:nothing    => :assoc_legendre,
:nothing    => :hermite,
:nothing    => :laguerre,
:nothing    => :assoc_laguerre,
:nothing    => :Ynm,
:nothing    => :Ynm_c

)

for (jmeth, smeth) in [(j,s) for (j,s) in julia_sympy_map if s !== :nothing && j!== :nothing]
    meth_name = string(smeth)
    @eval import SpecialFunctions: $jmeth
    @eval begin
        # @doc """
        # `$($meth_name)`: a SymPy function.
        #     The SymPy documentation can be found through: http://docs.sympy.org/latest/search.html?q=$($meth_name)
        #     """ ->
        ($jmeth)(x::Sym, xs...;kwargs...) = SymPy._sympy_meth($meth_name, x, xs...; kwargs...)
    end
    eval(Expr(:export,smeth))
end

## only in SymPy
for meth in [v for (k,v) in julia_sympy_map if k == :nothing]
    meth_name = string(meth)
    @eval begin
        # @doc """
        #     `$($meth_name)`: a SymPy function.
        #         The SymPy documentation can be found through: http://docs.sympy.org/latest/search.html?q=$($meth_name)
        #         """ ->
        ($meth)(xs...; kwargs...) = SymPy._sympy_meth($meth_name, xs...; kwargs...)
    end
    eval(Expr(:export, meth))
end

#gamma(x::Sym, args...; kwargs...) = sympy_meth(:gamma, x, args...; kwargs...)
#lgamma(x::Sym, args...; kwargs...) = sympy_meth(:loggamma, x, args...; kwargs...)

beta(x::Sym, y::Number) = sympy_meth(:beta, x, y)
export beta
lgamma(x::Sym) = log(gamma(x))

## these have (parameter, x) signature. Use symbolic x to call sympy version, othewise
## should dispatch to julia version.
for fn in (:besselj, :bessely, :besseli, :besselk)
    meth = string(fn)
    @eval import SpecialFunctions: $fn
    @eval ($fn)(nu::Number, x::Sym; kwargs...) = SymPy._sympy_meth($meth, nu, x; kwargs...)
    @eval ($fn)(nu::Number, a::AbstractArray{Sym}) = map(x ->$fn(nu, x), a)
end

## Hyper and friends don't really have symbolic use...
"""

Evaluates the generalized hypergeometric function:
[hyper](http://docs.sympy.org/dev/modules/mpmath/functions/hypergeometric.html#hyper)

"""
hyper(as::Vector{T}, bs::Vector{S}, z::Number) where {T<:Number, S<:Number} = sympy_meth(:hyper, as, bs, z)
export hyper

"""

[docs](http://docs.sympy.org/dev/modules/mpmath/functions/hypergeometric.html#meijer-g-function)

"""
function meijerg(a1s::Vector{T}, a2s::Vector{T}, b1s::Vector{S}, b2s::Vector{S}, z::Number, r=1;kwargs...) where {T<:Number, S<:Number}
    sympy_meth(:meijerg, a1s, a2s, b1s, b2s, z;  kwargs...)
end
export meijerg

end


if lowercase(get(ENV, "CI", "false")) == "true"
    include("install_dependencies.jl")
end

using SymPy
using Test

include("tests.jl")
include("test-math.jl")
include("test-matrix.jl")
include("test-ode.jl")
include("test-logical.jl")
#include("test-specialfuncs.jl")  ## XXX NEEDS WORK
#include("test-permutations.jl")
include("test-physics.jl")
include("test-external-module.jl")


# Contains S-norms
# ----------------------------------

"""
    Compute the maximum value of the tmp_strengths vector

    maximum_value(tmp_strengths<:AbstractFloat)
"""
function maximum_value(tmp_strengths::Vector{<:AbstractFloat})
    return maximum(tmp_strengths)
end

"""
    Compute the algebraic sum of the tmp_strengths vector

    algebraic_sum(tmp_strengths<:AbstractFloat)
"""
function algebraic_sum(tmp_strengths::Vector{<:AbstractFloat})
    if length(tmp_strengths) > 1
        val = tmp_strengths[1]
        for i = 2:length(tmp_strengths)
            val = val + tmp_strengths[i] - (val * tmp_strengths[i])
        end
        return val
    else
        return tmp_strengths[1]
    end
end

"""
    Compute the bounded sum of the tmp_strengths vector

    bounded_sum(tmp_strengths<:AbstractFloat)
"""
function bounded_sum(tmp_strengths::Vector{<:AbstractFloat})
    if length(tmp_strengths) > 1
        val = tmp_strengths[1]
        for i = 2:length(tmp_strengths)
            val = min(1.0, val + tmp_strengths[i])
        end
        return val
    else
        return tmp_strengths[1]
    end
end

"""
    Compute the drastic sum of the tmp_strengths vector

    drastic_sum(tmp_strengths<:AbstractFloat)
"""
function drastic_sum(tmp_strengths::Vector{<:AbstractFloat})
    if length(tmp_strengths) > 1
        val = tmp_strengths[1]
        for i = 2:length(tmp_strengths)
            if min(val, tmp_strengths[i]) == 0.0
                val = max(val, tmp_strengths[i])
            else
                val = 1.0
            end
        end
        return val
    else
        return tmp_strengths[1]
    end
end

"""
    Compute the Einstein sum of the tmp_strengths vector

    einstein_sum(tmp_strengths<:AbstractFloat)
"""
function einstein_sum(tmp_strengths::Vector{<:AbstractFloat})
    if length(tmp_strengths) > 1
        val = tmp_strengths[1]
        for i = 2:length(tmp_strengths)
            val = (val * tmp_strengths[i]) / (1.0 + (val * tmp_strengths[i]))
        end
        return val
    else
        return tmp_strengths[1]
    end
end

"""
    Compute the Hamacher sum of the tmp_strengths vector

    hamacher_sum(tmp_strengths<:AbstractFloat)
"""
function hamacher_sum(tmp_strengths::Vector{<:AbstractFloat})
    if length(tmp_strengths) > 1
        val = tmp_strengths[1]
        for i = 2:length(tmp_strengths)
            val = (val + tmp_strengths[i] - (2 * val * tmp_strengths[i])) / (1 - (val * tmp_strengths[i]))
        end
        return val
    else
        return tmp_strengths[1]
    end
end