"""
    Unfold( Z::Array )

Unfolds a tensor into a 2-tensor.
"""
function Unfold(Z::Array)
    dimensions = size( Z )
    return reshape( Z, ( dimensions[ 1 ], prod( dimensions[ 2 : end ] ) ) )
end

struct MultiCenter{B} <: Transform
    Mean::B
    Mode::Int
    invertible::Bool
end

"""
    MultiCenter(Z, mode = 1)

Acquires the mean of the specified mode in `Z` and returns a transform that will remove those means from any future data.
"""
function MultiCenter(Z, mode = 1)
    Modes = size(Z)
    Core = Base.permutedims(Z, vcat(mode, setdiff( 1:length(Modes), mode ) ) )
    Core = reshape( Core , size(Core)[ 1 ], prod( size(Core)[ 2 : end ] ) )
    return MultiCenter( StatsBase.mean(Core, dims = 2), mode, true )
end

"""
    (T::MultiCenter)(Z; inverse = false)

Centers data in Tensor `Z` mode-wise according to learned centers in MultiCenter object `T`.
"""
function (T::MultiCenter)(Z; inverse = false)
    Modes = size(Z)
    Core = Base.permutedims(Z, vcat(T.Mode, setdiff( 1:length(Modes), T.Mode ) ) )
    ModesPerm = size(Core)
    Core = reshape( Core , size(Core)[ 1 ], prod( size(Core)[ 2 : end ] ) )
    if inverse
        Core = Core .+ T.Mean
    else
        Core = Core .- T.Mean
    end
    Core = reshape( Core,  ModesPerm  )
    Revert = collect(2:length(Modes))
    splice!(Revert, T.Mode:(T.Mode - 1), [1])
    return Base.permutedims(Core, Revert )
end

struct MultiScale{B} <: Transform
    StdDev::B
    Mode::Int
    invertible::Bool
end

"""
    MultiScale(Z, mode = 1)

Acquires the standard deviations of the specified mode in `Z` and returns a transform that will scale by those standard deviations from any future data.
"""
function MultiScale(Z, mode = 1)
    Modes = size(Z)
    Core = Base.permutedims(Z, vcat(mode, setdiff( 1:length(Modes), mode ) ) )
    Core = reshape( Core , size(Core)[ 1 ], prod( size(Core)[ 2 : end ] ) )
    return MultiCenter( StatsBase.std(Core, dims = 2), mode, true )
end

"""
    (T::MultiScale)(Z; inverse = false)

Scales data in Tensor `Z` mode-wise according to learned standard deviations in MultiScale object `T`.
"""
function (T::MultiScale)(Z; inverse = false)
    Modes = size(Z)
    Core = Base.permutedims(Z, vcat(T.Mode, setdiff( 1:length(Modes), T.Mode ) ) )
    ModesPerm = size(Core)
    Core = reshape( Core , size(Core)[ 1 ], prod( size(Core)[ 2 : end ] ) )
    if inverse
        Core = Core .* T.StdDev
    else
        Core = Core ./ T.StdDev
    end
    Core = reshape( Core,  ModesPerm  )
    Revert = collect(2:length(Modes))
    splice!(Revert, T.Mode:(T.Mode - 1), [1])
    return Base.permutedims(Core, Revert )
end

"""
    MultiNorm(T)

Computes the equivalent of the Froebinius norm on a tensor `T`. Returns a scalar.
"""
MultiNorm(T) = sqrt(sum(T .^ 2))

struct MultilinearPCA
    Core::Array
    Loadings::Array
    ExplainedVariance::Float64
end

"""
    HOSVD(X; Factors = 2)

Performs multilinear PCA aka Higher Order SVD aka Tucker, etc. The number of factors decomposed
can be a scalar (repeated across all modes) or a vector/tuple for each mode.

Returns a MultilinearPCA object containing (Core Tensor, Basis Tensors, Explained Variance)

Tucker, L. R. (1964). "The extension of factor analysis to three-dimensional matrices". In N. Frederiksen and H. Gulliksen (Eds.), Contributions to Mathematical Psychology. New York: Holt, Rinehart and Winston: 109–127.
"""
function HOSVD(X; Factors = 2)
    Modes = size(X)
    Factors = (length(Factors) == 1) ? repeat([Factors], length(Modes)) : Factors
    @assert length( Factors ) == length( Modes )
    @assert all( Factors .<= Modes)
    Order = length( Modes )
    Loadings = [ zeros(Modes[N], Factors[N]) for N in 1 : length(Modes) ]
    for n in 1 : Order
        resize = (n, setdiff( 1 : Order, n) )
        unfold = reshape( Base.permutedims( X, vcat(resize[1],resize[2]) ), Modes[ resize[ 1 ] ], prod( Modes[ resize[ 2 ] ] ) )
        svdres = LinearAlgebra.svd( unfold )
        factor = Factors[n]
        Loadings[n] = svdres.U[:, 1:factor]
    end
    Core = X
    modes = 1:length(Loadings)
    #ToDo Update to use the TensorProduct method...
    for i = modes #This is a doohicky to do the tensor products...
        Core = Base.permutedims(Core, vcat(i, setdiff( modes, i ) ) )
        Un = reshape( Core , size(Core)[ 1 ], prod( size(Core)[ 2 : end ] ) )
        Core = Loadings[i]' * Un
        newshape = Tuple(vcat(collect(Factors[1:i]), collect(Modes[ ( i + 1 ) : end])))
        Core = reshape( Core,  newshape  )
    end
    return MultilinearPCA(Core, Loadings, MultiNorm( Core ) / MultiNorm( X ) )
end

"""
    HOOI( X; Factors = 1, maxiters = 100, init = :HOSVD, tolerance = 1e-9 )

Performs multiway PCA aka Higher Order SVD aka Tucker, etc via the `Higher Order Orthogonal Iteration` (HOOI) of tensors.

Returns a MultilinearPCA object containing (Core Tensor, Basis Tensors, Explained Variance)

Lieven De Lathauwer, Bart De Moor, and Joos Van-dewalle. A multilinear singular value decomposition. SIAM J. Matrix Anal. Appl., 2000.
"""
function HOOI( X; Factors = 1, maxiters = 100, init = :HOSVD, tolerance = 1e-9 )
    Size = size(X)
    Modes = length(Size)
    X_loads = []
    if init == :HOSVD
        X_loads = HOSVD( X; Factors = Factors ).Loadings
    else #Random initialization - not reccomended for reproducibility...
        X_loads = [ randn( s, Factors ) for s in Size]
    end
    LastNorm = Inf
    Norm = X_loads[1]' * Unfold(X) * foldr(kron, X_loads[reverse(2:end)])
    #Norm = MultiNorm( foldl( kron, X_loads ) )
    it = 0
    while ( it < maxiters ) && ( (MultiNorm( LastNorm .- Norm ) / MultiNorm( Norm ) ) > tolerance )
        for Mode in 1 : Modes
            NotMode = setdiff( 1:Modes, Mode )
            Core = copy(X)
            for (Collapsed, NM) in enumerate( NotMode )
                Core = TensorProduct(Core, X_loads[NM], NM - Collapsed + 1, 1;
                                        RemoveSingularModes = true )
            end
            Core = Unfold( Core )
            #Get Left Hand Singular Vectors
            X_loads[ Mode ] = LinearAlgebra.svd( Core ).U[ : , 1 : Factors ]
        end
        it += 1
        LastNorm = deepcopy( Norm )
        Norm = X_loads[1]' * Unfold(X) * foldr(kron, X_loads[reverse(2:end)])
    end
    Core = X
    for M in 1 : Modes
        Core = TensorProduct(Core, X_loads[M], 1, 1;
                                RemoveSingularModes = false )
    end
    return MultilinearPCA( Core, X_loads, MultiNorm( Core ) / MultiNorm( X ) )
end

"""
    TensorProduct(A, B, IndexA, IndexB; RemoveSingularModes = true,
                        SizeA = size(A), SizeB = size(B) )

Computes the tensor product of tensors `A` & `B` across their respective indices.

Note: This is primairily an method for internal use to the library, but feel free to
use it for your own needs.

"""
function TensorProduct(A, B, IndexA, IndexB; RemoveSingularModes = true,
                        SizeA = size(A), SizeB = size(B) )
    ModesA, ModesB   = length( SizeA ), length( SizeB )
    Ac, Bc           = copy(A), copy(B)
    UnfoldA, UnfoldB = SizeA, SizeB
    #Do we need to permute A?
    if IndexA != 1
        Swap = vcat(IndexA, setdiff( 1:ModesA, IndexA )... )
        Ac = permutedims( Ac, Swap )
        UnfoldA = Swap
        SizeA = SizeA[Swap]
    end
    #Do we need to permute B?
    if IndexB != 1
        Swap = vcat(IndexB, setdiff( 1:ModesB, IndexB )...)
        Bc = permutedims( Bc, Swap )
        UnfoldB = Swap
        SizeB = SizeB[ Swap ]
    end
    #Unfold A and B into a matrix form.
    Ac = reshape( Ac, ( SizeA[ 1 ], prod( SizeA[ 2 : end ] ) ) )
    Bc = reshape( Bc, ( SizeB[ 1 ], prod( SizeB[ 2 : end ] ) ) )
    #Multiply them
    C = Ac' * Bc
    #New shape is now prod(Ja,Ka,..Za) x prod(Jb,Kb,..Zb)
    #Need to unfold to (Ja,Ka,..Za, Jb,Kb,..Zb )
    NewShape = collect(vcat( SizeA[2:end]..., SizeB[2:end]... ))
    if RemoveSingularModes
        NewShape = NewShape[ NewShape .!= 1 ]
    end
    return reshape( C, Tuple(NewShape)... )
end

struct MultilinearPLS
    XLoadings::Array    #W
    XScores::Array      #T
    YLoadings::Array    #Q
    YScores::Array      #U
    Coefficients::Array #B
    Factors::Int        ##
end

"""
    MultilinearPLS(Y, X; Factors = minimum(size(X)) - 2,
                         tolerance = 1e-8, maxiters = 200 )

Performs a Multilinear PLS regression from `X` and `Y` tensors. The number of
`Factors`, convergence `tolerance`, and the `maxiters`(maximum iterations) may be set.

Method returns a `MultilinearPLS` object.

Notes:
    - Only Y orders < 2 are currently supported.
    - X order must be >= 2
    - X orders > 3 are currently unreviewed. Please contribute!

Bro, Rasmus. (1996), Multiway calibration. Multilinear PLS. J. Chemometrics, 10: 47-61. doi:10.1002/(SICI)1099-128X(199601)10:1<47::AID-CEM400>3.0.CO;2-C
"""
function MultilinearPLS(Y, X; Factors = minimum(size(X)) - 2,
                           tolerance = 1e-8, maxiters = 200 )
    X_size,  Y_size  = size( forceMatrix( X ) ), size( forceMatrix( Y ) )
    X_order, Y_order = length( X_size ), length( Y_size )
    @assert(Y_order < 3, "Only Y orders < 2 are currently supported.")
    @assert(X_order > 2, "Use PartialLeastSquaresRegression when X order is < 3.")
    @warn("This code algorithm has only been inspected for X orders of 3, and Y orders < 3.\n Use at your own risk, and please report any bugs!")
    X_e, Y_e        = Unfold(deepcopy(X)), Unfold(deepcopy(Y))
    T = zeros( X_size[1], Factors )                     #X Scores
    W = [ zeros( i, Factors ) for i in X_size[2:end] ]  #X Loadings
    U = zeros( Y_size[1], Factors )                     #Y Scores
    Q = [ zeros( i, Factors ) for i in Y_size[2:end] ]  #Y Loadings
    B = zeros( Factors, Factors )                       #Coefficient matrix
    iteration = 0
    for Factor in 1 : Factors
        u = [0.0]
        Weights_unfolded = [0.0]
        iteration = 0
        #Initialize Y Scores
        if (length( Q ) == 1) && (Y_size[2] == 1)
            u = Y_e
        else
            #Could extend Y decomposition to higher orders but for now consider Classic PLS2
            #mpcay = HOOI( Y_e; Factors = 1 ) #u = mpcay.Core[:] .* mpcay.Loadings[ 1 ]
            u = copy( Y_e[ :, argmax( [ (Y_e[:,col]' * Y_e[:,col])[1] for col in 1:Y_size[2]] ) ] )
        end
        change = Inf
        while (change > tolerance) && (iteration < maxiters)
            iteration += 1
            #Calculate X Contributions
            u_prime = copy( u ) #Hacky but it works
            X_project = reshape( (X_e' * u), X_size[2], prod( X_size[3:end] ) )
            mpca = HOOI( X_project; Factors = 1, tolerance = tolerance)
            for (i, w) in enumerate( mpca.Loadings )
                #99.9% sure HOOI/HOSVD scales by tensor core so no need for it here?
                W[i][:, Factor] = w
            end
            Weights_unfolded = foldr(kron, reverse(mpca.Loadings) )
            T[ :, Factor ] = X_e * Weights_unfolded
            #Calculate Y Contributions
            q = transpose( T[ :, Factor ] ) * Y_e
            q = q ./ sqrt(sum( q .^ 2))
            Q[1][:, Factor] = q[:]
            u = Y_e * q'
            U[:, Factor] = u
            #Monitor convergence by tensor norm of U
            change = sqrt( sum( (u_prime - U[:, Factor]) .^ 2 ) / sum( U[:, Factor] .^ 2 ) )
        end
        #Solve for regression coefficients and prediction intermediates
        TTT = Base.inv(T[:,1:Factor]' * T[:,1:Factor]) * T[:,1:Factor]'
        B[1:Factor,Factor] .= TTT * U[:,Factor]
        #Deflate/map residues
        Y_e = Y - (T[ :, 1:Factor] * B[1:Factor, 1:Factor] * Q[1][:, 1:Factor]')
        X_e = X_e - (T[ :, Factor ] * Weights_unfolded')
    end

    return MultilinearPLS( W, T, Q, U, B, Factors)
end

"""
    (M::MultilinearPLS)( X; Factors = M.Factors )

Applies a Multilinear PLS regression object to new `X` data with a prescribed number of
`Factors`. Method returns a matrix of the calibrated size `Y`.

Bro, Rasmus. (1996), Multiway calibration. Multilinear PLS. J. Chemometrics, 10: 47-61. doi:10.1002/(SICI)1099-128X(199601)10:1<47::AID-CEM400>3.0.CO;2-C
"""
function (M::MultilinearPLS)(X; Factors = M.Factors)
    X_size = size( forceMatrix( X ) )
    X_order = length( X_size )
    XProjections = zeros( X_size[1] , Factors )
    X_e = Unfold( copy(X) )
    for Factor in 1:Factors
        Weights_unfolded = foldr(kron, map( x -> x[:,Factor]', reverse(M.XLoadings) ) )
        XProjections[:,Factor] = X_e * Weights_unfolded'
        X_e = X_e - XProjections[:,Factor] * Weights_unfolded
    end
    F = 1:Factors
    return XProjections[ :, F ] * M.Coefficients[ F, F ] * M.YLoadings[1][ :, F ]'
end


immutable NoncentralT{T<:Real} <: ContinuousUnivariateDistribution
    ν::T
    λ::T

    function (::Type{NoncentralT{T}}){T}(ν::T, λ::T)
        @check_args(NoncentralT, ν > zero(ν))
        @check_args(NoncentralT, λ >= zero(λ))
        new{T}(ν, λ)
    end
end

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

@distr_support NoncentralT -Inf Inf

### Conversions
convert{T <: Real, S <: Real}(::Type{NoncentralT{T}}, ν::S, λ::S) = NoncentralT(T(ν), T(λ))
convert{T <: Real, S <: Real}(::Type{NoncentralT{T}}, d::NoncentralT{S}) = NoncentralT(T(d.ν), T(d.λ))

### Parameters

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

### Statistics

function mean{T<:Real}(d::NoncentralT{T})
    if d.ν > 1
        isinf(d.ν) ? d.λ :
        sqrt(d.ν/2) * d.λ * gamma((d.ν - 1)/2) / gamma(d.ν/2)
    else
        T(NaN)
    end
end

function var{T<:Real}(d::NoncentralT{T})
    d.ν > 2 ? d.ν*(1 + d.λ^2) / (d.ν - 2) - mean(d)^2 : T(NaN)
end

### Evaluation & Sampling

@_delegate_statsfuns NoncentralT ntdist ν λ

function rand(d::NoncentralT)
    z = randn()
    v = rand(Chisq(d.ν))
    (z+d.λ)/sqrt(v/d.ν)
end


@testset "Finds the eigenvalue 1 for an identity matrix" begin

    identity = Matrix{Int}(I, 2, 2)
    guess = [1; 0]
    stoppingcriteria = Eigen.ExactlyNIterations(1)

    actual = Eigen.powermethod(identity, guess, stoppingcriteria)
    expected = [1; 0]

    @test actual.eigenvector == expected
    @test actual.isverified == false
end

@testset "Finds the eigenvector for a large eigenvalue using the residual stopping criteria" begin
    matrix = [6 0;0 2]
    guess = [1.0; 2]
    stoppingcriteria = Eigen.Residual(0.1)

    actual = Eigen.powermethod(matrix, guess, stoppingcriteria)
    expected = [1; 0]

    @test LinearAlgebra.norm(actual.eigenvector - expected) <= 0.1
    @test actual.isverified == true
end

@testset "Stops before finding the eigenvector due to a constraint on the maximum number of iterations" begin
    matrix = [6 0; 0 2]
    guess = [1.0; 1]
    stoppingcriteria = Eigen.CompositeStoppingCriteria([Eigen.Residual(0.1), Eigen.ExactlyNIterations(0)])

    actual = Eigen.powermethod(matrix, guess, stoppingcriteria)
    @test LinearAlgebra.norm(LinearAlgebra.normalize(actual.eigenvector) - LinearAlgebra.normalize(guess)) <= 0.001
    @test actual.isverified == false
end


module Interpolate

using Geotherm.Geometry: Point2D, Point3D, Rectangle

export linear_interpolate, bilinear_interpolate

"""
    linear_interpolate(a, b)

Return a function of the linear interpolation between any 2 points `(x₁, f(x₁))` and `(x₂, g(x₂))`.
"""
function linear_interpolate(a::Point2D, b::Point2D)
    x₁, y₁, x₂, y₂ = a.x, a.y, b.x, b.y
    @assert x₁ != x₂ "The x-coordinates of the 2 arguments `a` and `b` cannot be equal!"
    return x -> ((x₂ - x) * y₁ + (x - x₁) * y₂) / (x₂ - x₁)
end

"""
    bilinear_interpolate(q₁₁, q₁₂, q₂₁, q₂₂)

If 4 points consisting of a rectangle and their z-coordinates are known, return a bilinear interpolation of the 4 points.
"""
function bilinear_interpolate(q₁₁::T, q₁₂::T, q₂₁::T, q₂₂::T) where {T<:Point3D}
    x₁, x₂, y₁, y₂ = q₁₁.x, q₂₁.x, q₁₁.y, q₂₂.y
    z₁₁, z₁₂, z₂₁, z₂₂ = map(q -> q.z, (q₁₁, q₁₂, q₂₁, q₂₂))
    rec = Rectangle(x₁, y₁, x₂, y₂)
    return function (x, y)
        if Point2D(x, y) ∉ rec
            error("point ($x, $y) is out of boundary $rec.")
        end
        v₁ = linear_interpolate(Point2D(x₁, z₁₁), Point2D(x₂, z₂₁))(x)
        v₂ = linear_interpolate(Point2D(x₁, z₁₂), Point2D(x₂, z₂₂))(x)
        return linear_interpolate(Point2D(y₁, v₁), Point2D(y₂, v₂))(y)
    end
end

end


using DataFrames

Base.convert(::Type{Float64}, ::NAtype) = NaN
using Gadfly

mad(X) = mad(X, median(X))
mad(X, med) = median([abs(x - med) for x in X])
spread(X) = maximum(x) - minimum(X)
radius(x, y) = √((x)^2 + (y)^2)

function v_a(df)
  vx = DataArray(Float64, size(df, 1))
  vy = DataArray(Float64, size(df, 1))
  ax = DataArray(Float64, size(df, 1))
  ay = DataArray(Float64, size(df, 1))

  vx[1] = NA
  vy[1] = NA
  ax[1] = NA
  ay[1] = NA

  for i in 2:size(df, 1)
    # check that frames directly follow each other
    if df[i-1, :Frame] == df[i, :Frame]-1
      Δy = df[i, :y] - df[i-1, :y]
      Δx = df[i, :x] - df[i-1, :x]
      Δt = df[i, :Time] - df[i-1, :Time]

      vx[i] = Δx/Δt
      vy[i] = Δy/Δt

      Δvx = vx[i] - vx[i-1]
      Δvy = vy[i] - vy[i-1]

      ax[i] = Δvx/Δt
      ay[i] = Δvy/Δt
    else #  Discontinuity
      vx[i] = NA
      vy[i] = NA
      ax[i] = NA
      ay[i] = NA
    end
  end

  r = DataFrame(t = df[:, :Time], vx = vx, vy = vy, ax = ax, ay = ay)
  r[:s] = √(r[:vx].^2 .+ r[:vy].^2)
  r[:a] = √(r[:ax].^2 .+ r[:ay].^2)
  r[:s_avg] = moving_avg(r[:s], 120) #at 30fps  => 4s
  r[:a_avg] = moving_avg(r[:a], 120)
  return r
end

function processData(folder, fileN)
  df = readtable("$folder/$fileN")
  dfOld = df

  median_r = median(head(df[:Radius]))

  # petri dish
  pR, px, py = readdlm("$folder/petriDish.txt")[1, 2:end]
  pDcm = 25 # cm
  cboatD = 0.3 #cm
  ratioCboat = median_r/(0.5*cboatD)

  rInfluence = 3.5 #cm
  rValid = pR/ratioCboat - rInfluence

  checkValid(x, y) = radius(x, y) < rValid

  df[:x] = (df[:x] - px)/ratioCboat
  df[:y] = (df[:y] - py)/ratioCboat

  #polar coordinates
  # df[:r] = map(radius, df[:x], df[:y])
  # df[:ϕ] = map((x, y) -> atan2(x,y), df[:x], df[:y])

  print("Recorded $(size(df, 1)) data points")
  # Remove all points that are probably bogus
  # cond = median_r - 2σ_r .<= df[:Radius] .<= median_r + 2σ_r

  duplicates = Int64[]
  for subdf in groupby(df, :Frame)
      if size(subdf, 1) > 1
          push!(duplicates, subdf[1,:Frame])
      end
  end

  cond = Bool[x ∉ duplicates for x in df[:Frame]]

  dfDiscarded = sub(df, !cond)
  df = sub(df, cond)
  print(" $(size(df, 1)) of them are valid and")
  # remove all points that are in the influence of the border
  dfValid = sub(df, map(checkValid, df[:x], df[:y]))
  println(" $(size(dfValid, 1)) are outside the influence of the boundary.")
  # plot(df, x = :x, y = :y)

  dfVA = v_a(df)
  dfValidVA = v_a(dfValid)
  writetable("$folder/va.csv", dfVA)
  writetable("$folder/vaFiltered.csv", dfValidVA)

  df, dfOld, dfVA, dfValidVA
end

function moving_avg(X :: DataArray, window :: Integer)
  avg = similar(X, Float64)
  if isodd(window)
    step = div(window - 1, 2)
  else
    step = div(window, 2)
  end

  for i in 1:size(X,1)
    if isna(X[i])
      avg[i] = NA
    else
      sum = 0.0
      n = 0
      for j in i-step:i+step
        if (1 <= j <= size(X,1)) && (!isna(X[j]))
          sum += X[j]
          n += 1
        end
      end
      avg[i] = sum / n
    end
  end
  return avg
end

function mean_windowed_frames(df, id, c, Δ)
  r = 1:Δ:size(df, 1)
  idx = Array(Float64, length(r))
  vals = Array(Float64, length(r))
  j = 1
  for i in r
    if i+Δ < size(df, 1)
      idx[j] = mean(df[i:i+Δ, id])
      vals[j] = mean(df[i:i+Δ, c])
    else
      idx[j] = mean(df[i:end, id])
      vals[j] = mean(df[i:end, c])
    end
    j += 1
  end
  idx, vals
end

function mean_windowed_time(df, id, c, Δt)
  r = 0:Δt:maximum(df[id])
  idx = Array(Float64, length(r)-1)
  vals = Array(Float64, length(r)-1)

  j = 1
  for t0 in r
    if j < length(r)
      t1 = t0 + Δt
      idx[j] = t0 + 0.5Δt

      cond = t0 .<= df[id] .< t1
      subdf = sub(df, cond, c)

      vals[j] = mean(subdf[c])
    end
    j += 1
  end
  idx, vals
end

# plot(vs, x = :t, y = :v, Geom.line)


using Plots
using Statistics
using StatsBase
import Base.length
# histograms

"""
    digitize(x, bins)

    Return the indices of the bins to which each value in input array belongs.
"""
function digitize(x::Vector{<:Real}, bins::LinRange{<:Real})
    return searchsortedlast.(Ref(bins), x)
end
# Notice that in this case the bins are expected to be sorted
function digitize(x::Vector{<:Real}, bins::Vector{<:Real})
    return searchsortedlast.(Ref(bins), x)
end

"""
    hist1d(x::Vector{T}, nbins::Integer, xmin::T=typemin(T), xmax::T=typemax(T))
    hist1d(x::Vector{T}, xs::String, nbins::Integer,
                    xmin::T=typemin(T), xmax::T=typemax(T); datap = true, fwl=true)
    hist1d(h::Histogram, xs::String; datap = true, markersize=3, fwl=false)

return a 1d histogram and its corresponding graphics (plots)
"""
function hist1d(x::Vector{<:Real}, bins::Vector{<:Real}, norm=false)
    h = fit(Histogram, x, bins)
    if norm
        h = StatsBase.normalize(h, mode=:density)
    end
    return h
end

function hist1d(x::Vector{T}, nbins::Integer,
                xmin::T=typemin(T), xmax::T=typemax(T), norm=false) where T
    xx   = in_range(x, xmin, xmax)
    bins = collect(LinRange(xmin, xmax, nbins + 1))
    h    = hist1d(xx, bins, norm)
    return h
end

"""
    edges(h::Histogram)

edges of the histogram
"""
function edges(h::Histogram, index::Int64=1)
    collect(h.edges[index])
end

"""
    centers(h::Histogram)

return the centres of the histogram bins.
"""
centers(h::Histogram) = centers(edges(h))

"""
    centers(edges::Vector{<:Real})

Calculate the bin centers from a vector of bin edges
"""
function centers(edges::Vector{<:Real})
    edges[1:end-1] + .-(@view(edges[2:end]), @view(edges[1:end-1])) / 2
end

"""
    hist_weights

return histogram weights of a set of data.
"""
function hist_weights(edges::Vector{<:Real})
  function get_weights(y::SubArray{<:Real})
    histo = fit(Histogram, y, edges)
    return histo.weights
  end
  return get_weights
end

"""
    hist1d(x::Vector{T}, xs::String, nbins::Integer,
           xmin::T=typemin(T), xmax::T=typemin(T);
           norm::Bool=false, datap::Bool=true,
           markersize::Int64=3, fwl::Bool=false,
           label::String="", legend::Bool=false) where T <: Real
return 1D histogram and plot axes for data x and number of bins nbins.
"""
function hist1d(x::Vector{T}, xs::String, nbins::Integer,
                xmin::T=typemin(T), xmax::T=typemax(T);
                norm::Bool=false, datap::Bool=true,
                markersize::Int64=3, fwl::Bool=false,
                label::String="", legend::Bool=false) where T <: Real

    h = hist1d(x, nbins, xmin, xmax, norm)
    return h, hist1d(h, xs, datap=datap, markersize=markersize, fwl=fwl,
                     label=label, legend=legend)
end

"""
    hist1d(h::Histogram, xs::String;
           datap::Bool=true, markersize::Int64=3, fwl::Bool=false,
           label::String="", legend::Bool=false)
Plot 1D histogram from Histogram object and options.
"""
function hist1d(h::Histogram, xs::String;
                datap::Bool=true, markersize::Int64=3, fwl::Bool=false,
                label::String="", legend::Bool=false)

    if datap
        xg = centers(h)
        yg = eltype(xg).(h.weights)
        p  = scatter(xg, yg, yerr=sqrt.(yg),
                     markersize=markersize, label=label, legend=legend)
        if fwl
            p = plot!(p, xg, yg, yerr=sqrt.(yg), fmt=:png,
                      linewidth=1, label=label, legend=legend)
        end
    else
        p = plot(h, xlabel=xs, fmt=:png, yl="frequency")
    end
    xlabel!(xs)
    ylabel!("frequency")

    return p
end

"""
    plot2hist1d(h1::Histogram, h2::Histogram, xs::String;
                markersize::Int64=2, norm::Bool=false)
Plot two 1D histograms on the same axes.
"""
function plot2hist1d(h1::Histogram, h2::Histogram, xs::String;
                     markersize::Int64=2, norm::Bool=false)

    if norm
        h1 = StatsBase.normalize(h1, mode=:density)
        h2 = StatsBase.normalize(h2, mode=:density)
    end

    xg1 = centers(h1)
    yg1 = eltype(xg1).(h1.weights)
    xg2 = centers(h2)
    yg2 = eltype(xg2).(h2.weights)

    p1 = scatter(     xg1, yg1, yerr=sqrt.(yg1), fmt=:png, markersize=markersize)
    p1 = scatter!(p1, xg2, yg2, yerr=sqrt.(yg2), fmt=:png, markersize=markersize)
    xlabel!(xs)
    ylabel!("frequency")

    return p1
end

"""
    hist2d(x::Vector{T}, y::Vector{T}, nbins::Integer,
           xl::String, yl::String,
           xmin::T=typemin(T), xmax::T=typemax(T),
           ymin::T=typemin(T), ymax::T=typemax(T)) where T <: Real

return a 2d histogram and its corresponding graphics (plots)
given data x, y, labels xl and yl and limits for each axis.
"""
function hist2d(x::Vector{T},y::Vector{T}, nbins::Integer,
                xl::String, yl::String,
                xmin::T=typemin(T), xmax::T=typemax(T),
                ymin::T=typemin(T), ymax::T=typemax(T); title="") where T <: Real
    function select_data(xval::T, yval::T)
        range_bound(xmin, xmax, OpenBound)(xval) .&
        range_bound(ymin, ymax, OpenBound)(yval)
    end
    mask = select_data.(x, y)
    data = (y[mask], x[mask])
    h    = fit(Histogram, data, nbins=nbins)
    ye   = centers(edges(h, 1))
    xe   = centers(edges(h, 2))
    hm   = heatmap(xe, ye, h.weights)
    xlabel!(xl)
    ylabel!(yl)
    if title != ""
        title!(title)
    end
    return h, hm
end

"""
    fmean_std

returns the weighted mean and std of an array given a minimum
bin weight for consideration.
"""
function fmean_std(x::Vector{<:Real}, min_prop::Float64=0.1)
    mask_func = weights -> weights .> min_prop * maximum(weights)
    function filt_mean(w::SubArray{<:Real})
        mask = mask_func(w)
        return [mean_and_std(x[mask], FrequencyWeights(w[mask]), corrected=true)]
    end
    return filt_mean
end

"""
    p1df(x, y, nbins; ybin_width, ymin, ymax, min_proportion)

return a profile DataFrame. This is a DF in which the variable y is
histogrammed as a function of the average of variable x in each bin.
The keyword arguments allow for a filtering in the y variable by
range and min_proportion of the maximum in the bin.
TODO: Protect against fine binning that results in zeros.
"""
function p1df(x::Vector{<:Real}, y::Vector{<:Real}, nbins::Integer;
              ybin_width::Real=0.1, ymin::Real=minimum(y), ymax::Real=maximum(y),
              min_proportion::Real=0.0)
    df           = DataFrame(:y => y)
    x_upper      = nextfloat(maximum(x))
    bin_edges    = LinRange(minimum(x), x_upper, nbins + 1)
    df[!, "bin"] = digitize(x, bin_edges)
    bin_centers  = centers(collect(bin_edges))
    bin_width    = .-(@view(bin_edges[2:end]), @view(bin_edges[1:end-1]))
    ## Bin in y so filtering can be done on bin content
    y_upper      = nextfloat(ymax)
    y_bins       = collect(ymin:ybin_width:y_upper)
    y_centers    = centers(y_bins)
    y_weights    = combine(groupby(df, :bin),
                           :y => hist_weights(y_bins) => :yweights,
                           ungroup = false)
    bin_stats    = fmean_std(y_centers, min_proportion)
    ndf = combine(y_weights,
                  :yweights => bin_stats => [:y_mean, :y_std])
    ndf = ndf[:, [:y_mean, :y_std]]
    ndf[!, :x_mean] = bin_centers
    ndf[!, :x_std]  = bin_width / 2

    p1 = plot(ndf.x_mean,ndf.y_mean, yerror=ndf.y_std, fmt = :png,
              shape = :circle, color = :black, legend=false)
    return ndf, p1
end


using Distributions
using Test
using DataFrames
using CSVFiles
using DataStructures

import GlobalSensitivityAnalysis: ishigami, split_output, sample

################################################################################
## JULIA
################################################################################

# define the (uncertain) parameters of the problem and their distributions
data = SobolData(
    params = OrderedDict(:x1 => Uniform(-3.14159265359, 3.14159265359),
        :x2 => Uniform(-3.14159265359, 3.14159265359),
        :x3 => Uniform(-3.14159265359, 3.14159265359)),
    N = 1000
)

N = data.N
D = length(data.params)

# sampling
julia_samples = sample(data)
julia_ishigami = ishigami(julia_samples)

# analysis
julia_A, julia_B, julia_AB, julia_BA = split_output(julia_ishigami, N, D, data.calc_second_order)
julia_results = analyze(data, julia_ishigami; num_resamples = 10_000)

################################################################################
## Python
################################################################################

# sampling
py_samples = load("data/sobol/py_ishigami/py_samples.csv", header_exists=false, colnames = ["x1", "x2", "x3"]) |> DataFrame
py_ishigami = load("data/sobol/py_ishigami/py_ishigami.csv", header_exists=false) |> DataFrame

# analysis
py_A = load("data/sobol/py_ishigami/py_A.csv", header_exists=false) |> DataFrame
py_B = load("data/sobol/py_ishigami/py_B.csv", header_exists=false) |> DataFrame
py_AB = load("data/sobol/py_ishigami/py_AB.csv", header_exists=false) |> DataFrame
py_BA = load("data/sobol/py_ishigami/py_BA.csv", header_exists=false) |> DataFrame

py_firstorder = load("data/sobol/py_ishigami/py_firstorder.csv", header_exists=false) |> DataFrame
py_secondorder = load("data/sobol/py_ishigami/py_secondorder.csv", header_exists=false) |> DataFrame
py_totalorder = load("data/sobol/py_ishigami/py_totalorder.csv", header_exists=false) |> DataFrame

py_firstorder_conf = load("data/sobol/py_ishigami/py_firstorder_conf.csv", header_exists=false) |> DataFrame
py_secondorder_conf = load("data/sobol/py_ishigami/py_secondorder_conf.csv", header_exists=false) |> DataFrame
py_totalorder_conf = load("data/sobol/py_ishigami/py_totalorder_conf.csv", header_exists=false) |> DataFrame

################################################################################
## Testing
################################################################################

@testset "Ishigami Function - Sampling" begin
    @test julia_samples ≈ Matrix(py_samples) atol = ATOL_sample
end

@testset "Ishigami Function - Analysis" begin
    @test julia_ishigami ≈ Matrix(py_ishigami) atol = ATOL_sobol
    @test julia_A ≈ Matrix(py_A) atol = ATOL_sobol
    @test julia_B ≈ Matrix(py_B) atol = ATOL_sobol
    @test julia_AB ≈ Matrix(py_AB) atol = ATOL_sobol
    @test julia_BA ≈ Matrix(py_BA) atol = ATOL_sobol

    @test julia_results[:firstorder] ≈ Matrix(py_firstorder) atol = ATOL_sobol
    @test julia_results[:totalorder] ≈ Matrix(py_totalorder) atol = ATOL_sobol

    for i = 1:D
        for j = i+1:D
            @test julia_results[:secondorder][i,j] ≈ Matrix(py_secondorder)[i,j] atol = ATOL_sobol
        end
    end

    # test confidence intervals
    @test julia_results[:firstorder_conf] ≈ Matrix(py_firstorder_conf) atol = ATOL_CI
    @test julia_results[:totalorder_conf] ≈ Matrix(py_totalorder_conf) atol = ATOL_CI

    for i = 1:D
        for j = i+1:D
            @test julia_results[:secondorder_conf][i,j] ≈ Matrix(py_secondorder_conf)[i,j] atol = ATOL_CI
        end
    end
end


# %% Plot average winnings over time
using Distributions
using DataFrames
using Gadfly
using Colors
import Cairo, Fontconfig
using Printf
# %%
g = Geometric(0.5)

toss() = rand(g)+1

n_games = 20

n_players = 5
winnings = zeros(n_games,n_players)
for p in 1:n_players
    for n in 1:n_games
        payout = 0
        for i in 1:2^n
            payout += 2^toss()
        end
        winnings[n,p] = payout/(2^n)
    end
end

# %%

theme = Theme(major_label_font="PragmataPro Mono",
              minor_label_font="PragmataPro Mono",
              key_title_font="PragmataPro Mono Bold")
Gadfly.push_theme(theme)

x = 1:n_games
p = plot(x=x,
         Coord.cartesian(ymax=40),
         color=[RGBA(0,0,0,0.2)],
         Scale.x_continuous(labels = x -> @sprintf("2%d",x)),
         Guide.xlabel("ROUNDS PLAYED"),
         Guide.ylabel("MEAN PAYOUT"),
         Guide.title("Average Payout Per Round vs. Total Rounds Played"),
         )

for n in 1:n_players
    l = layer(y=winnings[:,n],
              Geom.line)
    push!(p,l)
end

draw(PDF("../../figures/average-over-time.pdf"), p)

legend = Guide.manual_color_key("", ["log2n+2"], ["deepskyblue"])
push!(p,legend)

l = layer(y=(1:n_games).+2,
          Geom.line,
          color=[theme.default_color],
          # color=["sine"],
          style(line_width=0.5mm),
          )

push!(p,l)
draw(PDF("../../figures/average-over-time-fit.pdf"), p)
# %%


using Plots,LaTeXStrings
using LinearAlgebra,FundamentalsNumericalComputation

gr()
default(legend=nothing,linewidth=2)
plt = plot(showaxis=false,grid=false,xaxis=:none,yaxis=false,aspect_ratio=0.4)

f = x->1+exp(sin(2.6x))
y = f.(t)
plot!(f,0,2)

t = collect(0:1/3:2)
plot!(t,y,l=:black,m=(5,:black,:black))
plot!([t';t'],[y';0.9.+ 0*t'],l=(1,:dash,:black))

plot!(t[[3,4,4,3]],[0.9,0.9,y[4],y[3]],l=nothing,seriestype=:shape,color=RGBA(0,0,0,0.2))

annotate!(t[1],.75,text(L"t_0",:center))
annotate!(t[2],.75,text(L"t_1",:center))
annotate!(t[end],.75,text(L"t_n",:center))
annotate!(.43,3.6,text(L"f(\!x)",:right))

savefig("trapezoid.svg")


using JLD
using DataFrames
using Dates
using LinearAlgebra
using DifferentialEquations
using Distributions
using Random
using CSV
using Printf
using RCall
using CPUTime
using StatsPlots
include("utils.jl")
include("sir_ode.jl")
include("abc.jl")
include("abcMCMC.jl")
include("abcSMC.jl")
include("BayesianCalibration.jl")

df_pre = DataFrame(CSV.File("../data/covid19_bc_pre_vaccine.csv"))
df_post = DataFrame(CSV.File("../data/covid19_bc_post_vaccine.csv"))

""" Pre-vaccine analysis """
tot_days = size(df_pre)[1]
time_interval = 7

y_pre = hcat([df_pre[:S],df_pre[:I],df_pre[:R]]...)[1:7:tot_days,:]
y_pre = y_pre./1e4
y_dim = size(y_pre)[1]
# test ABC
function data_generator_day(p)
    initial_state = y_pre[1,:]; time_window=(1,Float64(tot_days))
    solve_ode(initial_state,time_window,p,Float64(7))
end

# test ABC
function data_generator_week(p)
    initial_state = y_pre[1,:]; time_window=(1,Float64(tot_days)/time_interval)
    solve_ode(initial_state,time_window,p)
end

algo_parameters_abc = (prior = (Truncated(Normal(0.1,0.5),0,Inf),Truncated(Normal(0.2,0.2),0,Inf)),epsilon = 0.1,
eta= identity_mapping, d= distanceFunction)
output_abc=ABC(y_pre', data_generator_week, algo_parameters_abc, 500)

algo_parameter_mcmc = (prior = (Truncated(Normal(0.1,0.5),0,Inf),Truncated(Normal(0.2,0.2),0,Inf)),epsilon = 1, eta = identity_mapping,
                        d= compute_norm, proposal = proposal_Normal,
                        proposalRatio = proposalRatio_Normal,sd = (0.25,0.25),
                        thinning = 10000, burn_in=100, verbose=true)

output_mcmc = ABC_MCMC(y_pre', data_generator_week, algo_parameter_mcmc, 500)

algo_parameters_smc = (prior = (Truncated(Normal(0.1,0.5),0,Inf),Truncated(Normal(0.2,0.2),0,Inf)), time_final=5, eps_list = [10, 5, 1, 0.5, 0.1],
eta= identity_mapping, d= distanceFunction, kernel=proposal_Normal,
kernel_density=proposal_Normal_density,
sd=(0.5,0.5), resample_method=systematic_resample, verbose=true)
output_smc = ABC_SMC(y_pre', data_generator_week, algo_parameters_smc, 500)

abc_solutions=zeros(3,y_dim,500)
abc_mcmc_solutions=zeros(3,y_dim,500)
abc_smc_solutions=zeros(3,y_dim,500)
for i=1:500
    abc_solutions[:,:,i] = data_generator_week(output_abc[1][i,:])[1:3,:]
    abc_mcmc_solutions[:,:,i] = data_generator_week(output_mcmc[1][i,:])[1:3,:]
    abc_smc_solutions[:,:,i] = data_generator_week(output_smc[1][i,:])[1:3,:]
end

# save("../results/abc_solution_500.jld","abc_solutions",abc_solutions)
# save("../results/abc_mcmc_solution_500.jld","abc_mcmc_solutions",abc_mcmc_solutions)
# save("../results/abc_smc_solution_500.jld","abc_smc_solutions",abc_smc_solutions)

# alpha_level=1
# beta_plot = density(output_abc[1][:,1],alpha=alpha_level, label="ABC",  title="Predicted β Posterior", xlabel="β", ylabel="Density")
# density!(output_mcmc[1][:,1],alpha=alpha_level, label="ABC-MCMC")
# density!(output_smc[1][:,1],alpha=alpha_level, label="ABC-SMC")
# savefig(beta_plot,"../figs/beta_plot.png")

# gamma_plot = density(output_abc[1][:,2],alpha=alpha_level, label="ABC",  title="Predicted γ Posterior", xlabel="γ", ylabel="Density")
# density!(output_mcmc[1][:,2],alpha=alpha_level, label="ABC-MCMC")
# density!(output_smc[1][:,2],alpha=alpha_level, label="ABC-SMC")
# savefig(gamma_plot,"../figs/gamma_plot.png")

 # ess
 ess_output = zeros(3)
 ess_output_time = copy(ess_output)
 ess_output[1]  = try rcopy(R"mean(mcmcse::ess($(output_abc[1])))") catch; 0 end
 ess_output_time[1]  = ess_output[1] / output_abc[2]
 ess_output[2]  = try rcopy(R"mean(mcmcse::ess($(output_mcmc[1])))") catch; 0 end
 ess_output_time[2]  = ess_output[2] / output_mcmc[2]
 ess_output[3]  = try rcopy(R"mean(mcmcse::ess($(output_smc[1])))") catch; 0 end
 ess_output_time[3]  = ess_output[3] / output_smc[2]

 save("../results/covid/ess_pre.jld","ess_output",ess_output)
 save("../results/covid/ess_cputime_pre.jld","ess_output_time",ess_output_time)

# solution_day = data_generator_day((0.042,0.07))
# solution_week = data_generator_week((0.12,0.2))

# plot(solution_day[2:end], yaxis=:log, label = ["s_model" "i_model" "r_model"], title="Daily:β=0.042, γ=0.07");
# plot!(1:7:250, y_pre, yaxis=:log, label=["s" "i" "r"], seriestype=:scatter)

# plot(1:7:250,solution_week', yaxis=:log, label = ["s_model" "i_model" "r_model"], title="Weekly:β=0.2, γ=0.3");
# plot!(1:7:250, y_pre, yaxis=:log, label=["s" "i" "r"], seriestype=:scatter)

# plot(hcat([df_pre[:S],df_pre[:I],df_pre[:R]]...), yaxis=:log, label=["s" "i" "r"], seriestype=:scatter)


tp = [Point(0.1234, 0.9876),
      Point(0.0000, 2.0000),
      Point(0.1234, 0.9876),
      Point(0.1234, 0.9876),
      Point(0.1234, 0.9876)]

tq = [Point(0.8765, 0.2345),
      Point(0.0000, 0.0000),
      Point(0.1234, 0.9876),
      Point(0.8765, 0.2345),
      Point(0.1234, 0.9876)]

tr = [2.0, 1.0, 2.0, 0.5, 0.0]

println("Testing circlepoints:")
for i in 1:length(tp)
    p = tp[i]
    q = tq[i]
    r = tr[i]
    (cp, rstatus) = circlepoints(p, q, r)
    println(@sprintf("(%.4f, %.4f), (%.4f, %.4f), %.4f => %s",
                     p.x, p.y, q.x, q.y, r, rstatus))
    for c in cp
        println(@sprintf("    (%.4f, %.4f), %.4f",
                         c.c.x, c.c.y, c.r))
    end
end


# Dantzig-Wolfe Reformulation and Column Generation
# Data for General Assignment Problem 1
# Edward J. Xu
# 2019.5.1

function getData()
    vec_c = [#=
        =# 9 9 1 4 6 4 2 3 5 1 9 9 7 6 3  #=
        =# 9 1 8 6 7 8 2 6 6 5 3 4 7 5 3  #=
        =# 4 8 1 8 1 4 1 4 2 6 2 1 2 5 1  #=
        =# 7 9 7 8 3 6 2 3 4 7 9 9 3 9 1
        ]
    mat_a = [
        Matrix{Float64}(I, 15, 15) Matrix{Float64}(I, 15, 15) Matrix{Float64}(I, 15, 15) Matrix{Float64}(I, 15, 15);
        8 6 1 7 7 7 5 5 7 7 3 3 8 5 4 zeros(1,45) ;
        zeros(1,15) 5 5 3 8 5 6 5 9 9 6 1 9 5 6 3 zeros(1,30);
        zeros(1,30) 3 2 4 4 9 1 7 3 3 3 5 3 7 7 1 zeros(1,15);
        zeros(1,45) 6 7 9 9 3 5 2 1 5 5 4 4 6 2 1
        ]
    vec_b = hcat([ones(15,1); 22.0; 18.0; 18.0; 19.0])
    indexSub = [collect(1: 15), collect(16: 30), collect(31: 45), collect(46: 60)]
    numSub = 4
    num_x_sub = 15
    num_border = 15
    #
    return (vec_c, mat_a, vec_b, indexSub, numSub, num_x_sub, num_border, vecStrNameVar)
end

(vec_c, mat_a, vec_b, indexSub, numSub, num_x_sub, num_border, vecStrNameVar) = getData()


"""

#### References

A Fast and Accurate Online Sequential Learning Algorithm for Feedforward
Networks

Guang-Bin Huang, Qin-Yu Zhu, and Chee-Kheong Siew.

Neurocomputing, 2006 vol. 70 (1-3) pp. 489-501.

http://linkinghub.elsevier.com/retrieve/pii/S0925231206000385
"""
mutable struct OSELM{TN,TV} <: AbstractSLFN where TN <: AbstractNodeInput where TV <: AbstractArray{Float64}
    p::Int  # Number of training points
    q::Int  # Dimensionality of function domain
    s::Int  # number of neurons
    neuron_type::TN
    μx::Vector{Float64}
    σx::Vector{Float64}
    Wt::Matrix{Float64}  # transpose of W matrix
    d::Vector{Float64}

    M::Matrix{Float64}  # internal
    p_tot::Int
    v::TV

    function OSELM(p::Int, q::Int, s::Int, neuron_type::TN, μx, σx) where TN
        # NOTE: Wt and d are constant throughout all learning chunks
        Wt = 2*rand(q, s) .- 1
        d = rand(s)
        M = zeros(Float64, s, s)
        new{TN,typeof(d)}(p, q, s, neuron_type, μx, σx, Wt, d, M, 0)
    end
end

function OSELM(x::AbstractArray, y::TV;
               neuron_type::TN=Linear(Tanh()),
               s::Int=size(x, 1)) where TN <: AbstractNodeInput where TV <: AbstractArray
    q = size(x, 2)  # dimensionality of function domain
    p = size(x, 1)  # number of training points
    s = min(p, s)   # Can't have more neurons than training points
    xn, μx, σx = standardize(x[:, :])
    out = OSELM(p, q, s, neuron_type, μx, σx)
    fit!(out, xn, y)
end

## API methods
function fit!(elm::OSELM, x::AbstractArray, y::AbstractArray)
    # check if we are boosting or not
    S = hidden_out(elm, x)
    if elm.p_tot == 0  # this is the initialization phase
        # elm.v = S \ y
        elm.M = pinv(S'S)
        elm.v = elm.M*S'y
    else
        elm.M -= elm.M*S' * inv(I + S*elm.M*S') * S * elm.M
        elm.v += elm.M * S' * (y - S*elm.v)
    end
    elm.p_tot += size(x, 1)
    elm
end

function Base.show(io::IO, elm::OSELM{TA}) where TA
    s =
    """
    OSELM with
      - $(TA) Activation function
      - $(elm.q) input dimension(s)
      - $(elm.s) neuron(s)
      - $(elm.p) initial training point(s)
      - $(elm.p_tot) total training points
    """
    print(io, s)
end

function test_me(::Type{OSELM})
    x = range(0, stop=1, length=20)
    y = sin.(6*x[:])
    oselm = OSELM(x, y)

    xt = range(0, stop=1, length=300)
    yt = sin.(6*xt)

    @show maximum(abs.(oselm(xt) .- yt))

    for i in 1:50
        x2 = rand(15)
        y2 = sin.(6*x2)
        fit!(oselm, x2, y2)
        @printf "%i\t%2.4e\n" i maximum(abs.(oselm(xt) - yt))
    end
end


# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.6.2
#     language: julia
#     name: julia-1.6
# ---

# %%
using QuadGK

f(x) = (x + 2) * x * (x - 2)
g(x) = -f(5 - x)

@show quadgk(f, 0, 5)
@show quadgk(f, 5, 0)
@show quadgk(g, 0, 5);

# %%


"""
    make_C_H(params)
Generate the values of C (correlation sum) and H (generalized entropy) for the given
parameters. This inclues `qH, qC, N, D` as well as the system name to generate data
from. Used in `produce_or_load` from `DrWatson` and utilizes the `Data` module created
in the source folder.

Important: function deduces suitable boxsizes `e` via the automated pipeline, however
they can also be given as name `"e"` in the `params` dictionary.
By default different boxsizes are calculated for entropy/corrsum, because of the
boxed version requiring smaller boxsizes.
"""
function make_C_H(params)
    @unpack qH, qC, data = params

    # This syntax calls the data generating function and creates the dataset
    # and propagates all keywords of `params` into it (if any can be used, e.g. `D` or `dt`)
    @info "Producing data..."
    data_producing_function = getfield(Data, data)
    X = data_producing_function(; dict2ntuple(params)...)

    method = get(params, "Cmethod", "standard")
    autoexpand = get(params, "autoexpand", false)
    @info "Calculating εs and H..."
    # boxsizes for entropy and corrsum versions
    if haskey(params, "e")
        eH = eC = params["e"]
    else
        # Entropy always gets max range
        eH = estimate_boxsizes(X;
            w = get(params, "w", 1), z = get(params, "z", -2), k = 20,
            autoexpand,
        )
        if method == "bueno"
            P = autoprismdim(X)
            r0 = ChaosTools.estimate_r0_buenoorovio(X, P)
            ε0 = ChaosTools.minimum_pairwise_distance(X)[1]
            if ε0 == 0
                ε0 = r0/(MathConstants.e^3)
            end
            eC = MathConstants.e .^ range(log(ε0)+1, log(r0); length = 12)
        elseif method == "standard"
            eC = eH
        end
    end
    @time H = genentropy.(Ref(X), eH; q = qH)

    # Theiler window
    if !haskey(params, "theiler")
        cols = columns(X)
        theiler = maximum(estimate_delay(x, "mi_min") for x in cols)
        theiler = clamp(theiler, 0, 100) # safety
    else
        theiler = params["theiler"]
    end

    @info "Calculating correlation sum..."
    @time C = boxed_correlationsum(X, eC; q = qC, w = theiler, show_progress=true)
    @pack! params = eH, eC, H, C, theiler
    @info "All done. `make_C_H` is returning now."
    return params
end

export make_C_H


using StaticArrays
using IntervalArithmetic
import IntervalArithmetic: interval

using LinearAlgebra

import Base: +, *, ^, -, sqrt, inv

"""
Affine form with center `c`, affine components `γ` and error `Δ`.

Variant where Δ is an interval
"""
struct AFF{N,T<:AbstractFloat}
    affine::AF{N,T}
    range::Interval{T}
end

interval(x::AFF) = x.range

function Base.show(io::IO, C::AFF{N,T}) where {N,T}
    print(io, "affine=", C.affine, "; range=", C.range)
end

# ==(C::Affine, D::Affine) = C.c == D.c && C.γ == D.γ

"""
Make an `AF` based on an interval, which is number `i` of `n` total variables.
"""
function AFF(X::Interval{T}, n, i) where {T}
    return AFF(AF(X, n, i), X)
end

for op in (:+, :*, :-)
    @eval function $op(x::AFF, y::AFF)
        affine = $op(x.affine, y.affine)

        range = $op(x.range, y.range)

        range = range ∩ interval(affine)

        return AFF(affine, range)
    end
end

for op in (:sqrt, :inv)
    @eval function $op(x::AFF)
        affine = $op(x.affine, x.range)

        range = $op(x.range)

        range = range ∩ interval(affine)

        return AFF(affine, range)
    end
end

*(x::AFF, α::Real) = AFF(α*x.affine, α*x.range)
*(α::Real, x::AFF) = x * α

+(x::AFF, α::Real) = AFF(α+x.affine, α + x.range)
+(α::Real, x::AFF) = x + α

-(x::AFF) = AFF(-x.affine, -x.range)
-(x::AFF, α::Real) = AFF(x.affine - α, x.range - α)
-(α::Real, x::AFF) = α + (-x)

/(x::AFF, α::Real) = AFF(x.affine/α, x.range/α)

function ^(x::AFF, n::Integer)

    invert = false

    if n < 0
        invert = true
        n = -n
        # @show n
    end

    result = Base.power_by_squaring(x, n)

    if invert
        result = inv(result)
    end

    return result
end
Base.literal_pow(::typeof(^), x::AFF, ::Val{p}) where {T,p} = x^p

# x = AF{2,Float64}(0.0, SVector(1.0, 0.0), 0..0)
# y = AF{2,Float64}(0.0, SVector(0.0, 1.0), 0..0)

#
# x = AF(3..5, 2, 1)
# y = AF(2..4, 2, 2)
#
# 3-x
# interval(3-x)
#
# x + y
#
#
# interval(x+y)
#
# x * y
# interval(x * y)
#
# interval(x * y)
# interval(x) * interval(y)
#
# z = AF(-1..1, 1, 1)
# z^2
# interval(z^2)
#
# using Polynomials
#
# p = Poly([-3, 1])
# p2 = p^8
#
# x = 4 ± 1e-4
# y = AF(x, 1, 1)
#
# interval(y)
# interval(p2(x))
# interval(p2(y))
#
# @time interval(p2(y))
#
#
# f( (x, y) ) = [x^3 + y, (x - y)^2]
#
# X = IntervalBox(-1..1, -1..1)
#
# f(X)
#
# xx = AF(X[1], 2, 1)
# yy = AF(X[2], 2, 2)
#
# interval.(f((xx, yy)))
#
# f(X)
#
#
#
#
# x = AF(4..6, 1, 1)    # example from Messine
# f(x) = x * (10 - x)
#
# f(x)
# interval(f(x))
#
# interval(10*x - x^2)


module ContinuousWavelets
using Wavelets
using Interpolations
using AbstractFFTs
using FFTW
using LinearAlgebra
using SpecialFunctions

import Wavelets.wavelet
import Wavelets.WT.name, Wavelets.WT.class,
   Wavelets.WT.vanishingmoments

export ContWave, CWT, cwt, icwt

# Boundaries
export WaveletBoundary, PerBoundary, ZPBoundary, NullBoundary, SymBoundary,
    Periodic, DEFAULT_BOUNDARY, padded, NaivePer, SymBound
# waveletTypes (note there are also export statements in the for loops of waveletTypes.jl)
export Morlet, Paul, Dog, ContOrtho, Morse, name, vanishingmoments, ContOrtho
export cHaar, cBeyl, cVaid, morl, morse
# averaging types
export Average, Dirac, Father, NoAve
# inverse types
export DualFrames, NaiveDelta, PenroseDelta, InverseType
# CWT constructors
export wavelet, waveletType
# general utilities
export qmf, computeWavelets, getNWavelets, mother, father, getMeanFreq, isAnalytic, getDualCoverage

@doc """
    ContWave{Boundary,T}
The abstract type encompassing the various types of wavelets implemented in
the package. The abstract type has parameters `Boundary<:WaveletBoundary` and
`T<:Number`, which gives the element output type. Each has both a constructor,
and a default predefined entry. These
are:
- `Morlet`: A complex approximately analytic wavelet that is just a frequency
    domain Gaussian with mean subtracted. `Morlet(σ::T) where T<: Real`. `σ`
    gives the frequency domain variance of the mother Wavelet. As `σ` goes to
    zero, all of the information becomes spatial. Default is `morl` which has
    σ=2π.

    ``\\psi\\hat(\\omega) \\propto \\textrm{e}^{-\\frac{\\mu^2}{2}}\\big(\\textrm{e}^{-(\\mu - \\omega)^2} -\\textrm{e}^{\\frac{\\omega^2-\\mu^2}{2}}\\big)``

- `Paul{N}`: A complex analytic wavelet, also known as Cauchy wavelets. `pauln` for n in `1:20` e.g. `paul5`

    ``\\psi\\hat(\\omega) \\propto \\chi_{\\omega \\geq 0} \\omega^N\\textrm{e}^{-\\omega}``
- `Dog{N}`: Derivative of a Gaussian, where N is the number of
    derivatives. `dogn` for `n` in `0:6`. The Sombrero/mexican hat/Marr wavelet
    is `n=2`.

    ``\\psi\\hat(\\omega) \\propto \\omega^N\\textrm{e}^{-\\frac{\\omega^2}{2}}``
- `ContOrtho{OWT}`. OWT is some orthogonal wavelet of type `OrthoWaveletClass`
    from [Wavelets.jl](https://github.com/JuliaDSP/Wavelets.jl). This uses an
    explicit construction of the mother wavelet for these orthogonal wavelets
    to do a continuous transform. Constructed via `ContOrtho(o::W)` where `o`
    is from Wavelets.jl. Alternatively, you can get them directly as
    `ContOrtho` objects via:
   + `cHaar` Haar Wavelets
   + `cBeyl` Beylkin Wavelets
   + `cVaid` Vaidyanthan Wavelets
   + `cDbn` Daubhechies Wavelets. n ranges from `1:Inf`
   + `cCoifn` Coiflets. n ranges from `2:2:8`
   + `cSymn` Symlets. n ranges from `4:10`
   + `cBattn` Battle-Lemarie wavelets. n ranges from `2:2:6`
"""
abstract type ContWave{Boundary,T} end # equivalent to ContinuousWavelet in Wavelets.jl
# BOUNDARY TYPES

"""
the abstract type for the various types of boundaries
"""
abstract type WaveletBoundary end
# periodic (default)
"""
    PerBoundary() <: WaveletBoundary
standard periodic boundary assumption made by the fft. Aliases of `NaivePer`
and `Periodic`.
"""
struct PerBoundary <: WaveletBoundary end
# zero padding
"""
    ZPBoundary() <: WaveletBoundary
zero pads the signal before doing an fft, rounding up to the nearest power of
two. Alias of `padded`.
"""
struct ZPBoundary <: WaveletBoundary end
# constant padding
# struct CPBoundary <: WaveletBoundary end
# symmetric boundary (as in the DCTII)
"""
    SymBoundary() <: WaveletBoundary
symmetric boundary, as in the DCT type II. Repeats the edge value, which
alleviates derivative discontinuities. Alias of `DEFAULT_BOUNDARY` and
`SymBound`.
"""
struct SymBoundary <: WaveletBoundary end
# and so on...

const Periodic = PerBoundary()
const DEFAULT_BOUNDARY = SymBoundary()
const padded = ZPBoundary()
const NaivePer = PerBoundary()
const SymBound = SymBoundary()

abstract type ContWaveClass end # equivalent to ContinuousWaveletClass in Wavelets.jl
include("waveletTypes.jl")
include("CWTConstruction.jl")
include("utils.jl")
include("sanityChecks.jl")
include("createWavelets.jl")
include("apply.jl")
end


# Copyright (c) 2020 Idiap Research Institute, http://www.idiap.ch/
#  Niccolò Antonello <nantonel@idiap.ch>

export forward!

"""
`forward!(alpha,c,Nt,a,A,y)`

In-place version of [`forward`](@ref).
"""
function forward!(alpha::AbstractArray{T},c::Vector{T},
                  Nt::Integer,
                  a::AbstractVector{T},
                  A::AbstractMatrix{T},
                  y::AbstractArray{T}) where {T <: AbstractFloat}
  Ns = size(A,1)
  fill!(alpha,zero(T))
  fill!(c,zero(T))
  for j = 1:Ns
    alpha[1,j] = a[j]*y[1,j]
    c[1] += alpha[1,j]
  end

  # normalize
  for j = 1:Ns
    alpha[1,j] /= c[1]
  end

  for t = 2:Nt
    for j = 1:Ns
      for i = 1:Ns
        aij = A[i,j]
        if aij != T(0)
          alpha[t,j] += alpha[t-1,i]*aij*y[t,j]
        end
      end
      c[t] += alpha[t,j]
    end
    for j = 1:Ns
      alpha[t,j] /= c[t]
    end
  end
  return alpha, c
end

# forward with constrained path
function forward!(alpha::AbstractArray{T},c::Vector{T},
                  Nt::Integer,
                  t2IJ::Vector{Pair{Vector{D},Vector{D}}},
                  A::AbstractMatrix{T},
                  y::AbstractArray{T}) where {D<:Integer, T <: AbstractFloat}
  Ns = size(A,1)

  fill!(alpha,zero(T))
  fill!(c,zero(T))
  idx0 = unique(t2IJ[1][1])
  for i in idx0
    alpha[1,i] = y[1,i]/length(idx0)
    c[1] += alpha[1,i]
  end

  # normalize
  for j = 1:Ns
    alpha[1,j] /= c[1]
  end

  for t = 2:Nt
    I,J = t2IJ[t-1]
    for k in eachindex(I)
      i,j = I[k],J[k]
      alpha[t,j] += alpha[t-1,i]*A[i,j]*y[t,j]
    end
    for j = 1:Ns
      c[t] += alpha[t,j]
    end
    for j = 1:Ns
      alpha[t,j] /= c[t]
    end
  end
  return alpha, c
end

export forward

"""
  `alpha, c = forward(Nt,a,A,y)`

Computes the scaled forward probabilities (``\\bar{\\alpha}``) of an Hidden Markov Model (HMM) of `Ns` states with transition matrix `A` (must be a `Ns` × `Ns` matrix), initial probabilities `a` (must be a `Ns`-long vector) and observation likelihoods `y` (must be of size `Nt2` × `Ns` with `Nt2` ≥ `Nt`).

Returns:
* `alpha` a matrix of size `(Nt,Ns)` containing the forward probabilities
* `c` a `Nt`-long vector containing the normalization coefficients which can be used to compute the backward probabilities (see `backward`) and the log maximum likelihood (`sum(log.(c))`).

`alpha, c = forward(Nt,t2tr,A,y)`

Here `t2tr` can be an `Nt-1` vector of `Pair{Vector{D},Vector{D}}`. For example given `Nt=3`:
```julia
t2tr = [[1,1]=>[1,2],[1,2]=>[3,3],[3]=>[4]]
```
indicates that at time `t=1` transitions from state `1` to `1` and from `1` to `2` are allowed.
At time `t=2` transitions from state `1` to `3` and from `2` to `3` are allowed.
At time `t=3` only the transition from state `3` to `4` is allowed.
In practice these indices can be used to construct the (possibly sparse) time-dependent transition matrices described in the [theory section](@ref theory).
"""
function forward(Nt::Integer,a,A::AbstractArray{T},y) where {T <: AbstractFloat}
  Ns = size(y,2)
  alpha = zeros(T,Nt,Ns)
  c = zeros(T,Nt)
  forward!(alpha,c,Nt,a,A,y)
end

# y is a tensor Nt2 × Ns × Nb
function forward(Nts::Array{D},
                 as::AbstractMatrix{T},
                 As::AbstractArray{T,3},
                 ys::AbstractArray{T,3}) where {D <: Integer,
                                       T <: AbstractFloat}
  Ns = size(As,1)
  Nb = size(ys,3) # batch number
  @assert Nb == length(Nts) == size(as,2) == size(As,3)
  alphas = [zeros(T,Nt,Ns) for Nt in Nts]
  cs = [zeros(T,Nt) for Nt in Nts]
  for i = 1:Nb
    forward!(alphas[i],cs[i],Nts[i],view(as,:,i),view(As,:,:,i),view(ys,:,:,i))
  end
  return alphas, cs
end

# y is a tensor Nt2 × Ns × Nb, constraint path
function forward(Nts::Array{D},
                 as::AbstractVector,
                 A::AbstractMatrix{T},
                 y::Array{T,3}) where {D <: Integer,
                                       T <: AbstractFloat}
  Ns = size(A,1)
  Nb = size(y,3) # batch number
  @assert Nb == length(Nts) == length(as)
  alphas = [zeros(T,Nt,Ns) for Nt in Nts]
  cs = [zeros(T,Nt) for Nt in Nts]
  for i = 1:Nb
    forward!(alphas[i],cs[i],Nts[i],as[i],A,view(y,:,:,i))
  end
  return alphas, cs
end


module SliceSampling

using Distributions, Roots, DataFrames

########## ALgorithm ##########
# 1. Choose a random starting value of x, let's call it x₀
# 2. Uniformly sample on interval [0, f(x₀)], let's call it a
# 3. Here's the tricky part - imagine a horizontal line at y = a. Figure out all the line segments under the curve.
# 4. From all the line segments, draw a value of x uniformly.
# 5. Repeat from step 2 until you have as many draws as you want.
######### end of Alg #########

# this implements slice sampling of a normal distribution with mean 10
    # and standard deviatio  2.
    # I used an x interval of 0 to 20,
    # with 0.1 root finding step size

# sliceSample function:
    # @ n =  number of points wanted
    # @ f = target distribution
    # @ w = root accuracy
    # @ interval = (lb, ub) of x values possible

function sliceSample(n, f , interval, w)
    pts = fill(0.0, n) # this vector will hold our points

    L = interval[1]
    R = interval[2]

    x   = rand(1) * (R - L)
    x   = x[1]  # random starting value x₀

    # we want to sample n points:
    for i in 1:n
        pts[i] = x
        y      = rand(1) * (f(x)[1] - 0.0)
        y      = y[1]
        # imagine a horizontal line across the dstribution
        # find intersections across that line
        function fshift(x)
            f(x) - y
        end

        roots = []
        seq   = collect(range(L, step = w, stop = R))

        for j in seq
            if (fshift(j) < 0) != (f(j + w)<y)
                # signs don't macth, so we have a root
                root = find_zero(z -> f(z) - y, j)
                if j< root < j + w
                    push!(roots, root)
                end
            end
        end

        # include the endpoints of the interval
        pushfirst!(roots, L)
        push!(roots, R)

        # devide the intersections into line segments
        segments = Array{Float64, 2}(undef, 1, 2)
        segments = DataFrame(segments)
        for j in 1 : (length(roots) - 1)
            midpoint = (roots[j + 1] + roots[j]) / 2.0
            if (f(midpoint) > y[1])
                # since this segment is under the curve, add it to the segments
                intv = [roots[j] roots[j+1]]
                append!(segments, intv)
            end
        end

        # uniformly sample next x from segements
        # assign each segment a probability, then unif based on those probabilities
        # note that the first row of segments is some undef number
        segments = segments[2:end, :] # drop the first row

        total = sum(segments[:, 2] .- segments[:, 1])

        prob_vec = (segments[:, 2] .- segments[:, 1]) ./ total

        # assign probabilities to each line segement based on how long it is
        # select a line segment by index(named seg)

        p = rand(1)

        @inline function selectSegment(x, i)
            if p[1] < x
                return i
            else
                return (selectSegment(x + prob_vec[i + 1], i + 1))
            end
        end

        seg = selectSegment(prob_vec[1], 1) # start from the first segment

        # uniformly sample new x value
        x = rand(Uniform(segments[seg, 1], segments[seg, 2]), 1)
        x = x[1]
    end

    return pts

end

end  # module SliceSamplinmg


module Sim3
include("./lib/probs.jl")
include("./lib/pccf.jl")
include("./detector.jl")
include("./lib/roc.jl")
include("./lib/utils.jl")
#using Probs, Detector, Pccf

function calcTmax(mu, n)
    round(Int,(mu*(n+1)))
end

function calcWidth(mu)
    1 + round(Int, 1.33 * mu)
end

# 1
function changesLocations(n, mu, sigma)
    changes1::Array{Int,1}
    tMax = calcTmax(mu,n)
    width = calcWidth(mu)
    changes1 = Probs.generateChanges(n,mu,sigma,width)
end
# 2
function errorsLocations(n,mu,errRate)
    tMax = calcTmax(mu,n)
    avgNumOfErrors = errRate * n
    #round(Int, rand(1:tMax, avgNumOfErrors))
    rand(1:tMax, avgNumOfErrors)
end

## ;;;;;;;;;;;;;;;;;Pccf/v1.0/src/recurrency_modelling2/sim3.clj;;;;;;;
## (defn gen-vals-in-interval-uniform [n from-v to-v]
##   "Function to generate values within interval inclusive"
##   (let [rng (inc (- to-v from-v))]
##     (map #(+ % from-v) (repeatedly n #(rand-int rng)))))
##
## (defn gen-vals-in-interval-gauss [n from-v to-v]
##   (let [mu (/ (+ to-v from-v) 2.0) sigma (inc (- to-v from-v))]
##     (utils/rnorm n mu sigma)))
## ;; (gen-vals-in-interval 5 7 8)
## ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

function genValsInIntervalGauss(n,st,en)
    # used to simulate changes
    mu = (en+st)/2 #old: (n+st)/2
    sigma = en-st+1
    randn(n) * sigma + mu
end

function updVec(vec, ids::Array{Int,1}, newVals)
    v=copy(vec)
    for i in 1:length(ids)
        v[ids[i]]=newVals[i]
    end
    return v
end

function genSig(n,mu,sigma,errRate,tvf,tvt,evf,evt)
    # Generate signal. For changes values are from tvf-tvt; errors in rvf-evt
    tMax        = calcTmax(mu,n)
    changesLoc  = changesLocations(n,mu,sigma)
    errorsLoc   = errorsLocations(n,mu,errRate)
    nChps       = length(changesLoc)
    nErrors     = length(errorsLoc)
    valsChanges = genValsInIntervalGauss(nChps, tvf, tvt)
    valsErrors  = genValsInIntervalGauss(nErrors, evf, evt)
    vals        = zeros(tMax)
    changes1    = updVec(vals, errorsLoc, valsErrors)
    changes2    = updVec(changes1, changesLoc, valsChanges)
    Dict("changes"=>changesLoc, "sig"=>changes2)
end

function avgPairs(listOfTuples)
    f=[x[1] for x in listOfTuples]
    s=[x[2] for x in listOfTuples]
    (mean(f),mean(s))
end
# julia> avgPairs([(1,2),(5,7)])-> (3.0,4.5)

function simForH(nSim,nChps, mu,sigma,errRate,tvFrom, tvTo, evFrom,evTo,h)
    # Perform n simulations for a given threshold h
    performance=[]
    for i in 1:nSim
        dat=genSig(nChps,mu,sigma,errRate,tvFrom,tvTo,evFrom,evTo)
        newsignal=dat["sig"]
        tChps=dat["changes"]
        maxDelay=1
        detections=Detector.naiveDetector(newsignal,h)
        tprate=Roc.tpRate(detections,tChps,maxDelay)
        farate=Roc.faRate(detections,tChps,maxDelay,length(newsignal))
        push!(performance,(tprate,farate))
    end
    avgPairs(performance)
end

function simDynamicH(probsFile,nSim,nChps,mu,sigma,errRate,tvFrom,tvTo,
                     evFrom,evTo,delayInMu,hmin,hmax)
    # Calculate TP/FA rates for a given pair of hmin hmax
    maxDelay=1
    tMax=calcTmax(mu,nChps)
    hForProbsFunction=0.1
    T=round(Int, mu*nChps*2)
    probsNoConfirm=Pccf.pccf(T,mu,sigma)
    performance=[]
    for i in 1:nSim
        dat = genSig(nChps,mu,sigma,errRate,tvFrom,tvTo,evFrom,evTo)
        newsignal = dat["sig"]
        tChps = dat["changes"]
        subProbsNoConfirm = probsNoConfirm[1:length(newsignal)]
        probs = Pccf.pccfWithConfirmation(subProbsNoConfirm,delayInMu,tChps)
        dynamicHvec = Probs.dynamicH(probs,hForProbsFunction,hmin,hmax, tMax)

        detections = Detector.naiveDetectorDynamic(newsignal,dynamicHvec)

        tprate = Roc.tpRate(detections, tChps, maxDelay)
        farate = Roc.faRate(detections, tChps, maxDelay,length(newsignal))
        push!(performance, (tprate,farate))
    end
    avgPairs(performance)
end

function simNotAdaptive(nSim,nChps,mu,sigma,
                        errRate,tvFrom,tvTo,evFrom,
                        evTo,h1,h2)
    # For every threshold h from h1 to h2 perform n-sim simulations
    performance=[]
    h=h1
    while h<=h2
        avgPerf=simForH(nSim,nChps,mu,sigma,errRate,tvFrom,tvTo,evFrom,evTo, float(h))
        push!(performance, (h, avgPerf[1],avgPerf[2]))
        h=h+1
    end
    performance
end

function runSimNotAdaptive(nSim, mainpath, settings)
    s=settings
    notAdaptivePerf=simNotAdaptive(nSim,s["nchps"],s["mu"],s["sigma"],
                                   s["errRate"],s["tvFrom"],s["tvTo"],s["evFrom"],
                                   s["evTo"],20,50)

    write_items(mainpath*"NotadAptivePerf.txt", notAdaptivePerf, "Delay,TP,FA")
end

function runSimAdaptive(nSim,delay1,delay2,hmin,hmax,mainpath,settings)
    s=settings
    performance=[]
    for confirmDelay in delay1:delay2
        adaptivPerf=simDynamicH(mainpath*"recprobs.txt",nSim,s["nchps"],s["mu"],
                                s["sigma"],s["errRate"],s["tvFrom"],s["tvTo"],
                                s["evFrom"],s["evTo"],confirmDelay,hmin,hmax)
        push!(performance, (confirmDelay,adaptivPerf[1],adaptivPerf[2]))
    end
    outpath=mainpath*"DelayVsPerformance.txt"
    write_items(outpath, performance, "Delay,TP,FA")
    println("... runSimAdaptive(): results are written into $outpath")
end

function run(settings)
    mainpath="D:/_tmp/out/"
    stCommon=Dict("nsim"=>200)
    stAdaptive=Dict("delay1"=>1, "delay2"=>3,"hmin"=>27.0,"hmax"=>30.0)
    runSimNotAdaptive(stCommon["nsim"], mainpath, settings)
    runSimAdaptive(stCommon["nsim"],stAdaptive["delay1"],stAdaptive["delay2"],
                   stAdaptive["hmin"],stAdaptive["hmax"], mainpath, settings)
end

end


using Test
using SDPNAL

# Example page 4 of SeDuMi_Guide_105R5.pdf
@testset "Linear Programming example" begin
    blk = ["l"  4]
    c = [ 1.0
         -1.0
          0.0
          0.0]
    A = [10.0 -7.0 -1.0 0.0
          1.0  0.5  0.0 1.0]
    b = [5.0
         3.0]
    obj, X, s, y, Z1, Z2, y2, v, info, runhist = sdpnalplus(blk, [Matrix(A')], [c], b, printlevel = 0)
    tol = 1e-5
    @test obj[1] ≈ -1/8 atol=tol rtol=tol
    @test obj[2] ≈ -1/8 atol=tol rtol=tol
    @test X[1] ≈ [47/24, 25//12, 0, 0] atol=tol rtol=tol
    @test isempty(s)
    @test y ≈ [1/8, -1/4] atol=tol rtol=tol
    @test Z1[1] ≈ [0, 0, 1/8, 1/4] atol=tol rtol=tol
    @test length(Z2) == 1
    @test size(Z2[1]) == (4, 1)
    @test all(iszero, Z2[1])
    @test isempty(y2)
    @test isempty(v)
    @test info["termcode"] == 0.0
end


@testset "Rate type" begin

    default = Yields.Rate{Float64,typeof(Yields.DEFAULT_COMPOUNDING)}

    y = Yields.Constant(0.05)
    @test Yields.__ratetype(y) == Yields.Rate{Float64, Periodic}
    @test Yields.__ratetype(typeof(Periodic(0.05,2))) == Yields.Rate{Float64, Periodic}
    @test Yields.CompoundingFrequency(y) == Periodic(1)

    y = Yields.Constant(Continuous(0.05))
    @test Yields.__ratetype(y) == Yields.Rate{Float64, Continuous}
    @test Yields.__ratetype(typeof(Continuous(0.05))) == Yields.Rate{Float64, Continuous}
    @test Yields.CompoundingFrequency(y) == Continuous()

    y = Yields.Step([0.02,0.05], [1,2])
    @test Yields.__ratetype(y) == Yields.Rate{Float64, Periodic}
    @test Yields.CompoundingFrequency(y) == Periodic(1)

    y = Yields.Forward( [0.01,0.02,0.03] )
    @test Yields.__ratetype(y) == default

    rates =[0.01, 0.01, 0.03, 0.05, 0.07, 0.16, 0.35, 0.92, 1.40, 1.74, 2.31, 2.41] ./ 100
    mats = [1/12, 2/12, 3/12, 6/12, 1, 2, 3, 5, 7, 10, 20, 30]

    y = Yields.CMT(rates,mats)
    @test Yields.__ratetype(y) == default
    @test Yields.CompoundingFrequency(y) == Continuous()

    combination = y + y
    @test Yields.__ratetype(combination) == default

end

@testset "type coercion" begin
    @test Yields.__coerce_rate(0.05, Periodic(1)) == Periodic(0.05,1)
    @test Yields.__coerce_rate(Periodic(0.05,12), Periodic(1)) == Periodic(0.05,12)
end


using DSP, Plots

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

for LENGTH=3:5
    for i = 1:10
        x = rand((-1,0,1), LENGTH)
        h = rand((-1,0,1), LENGTH)
        y = conv(x, h)

        p1 = plot(1:LENGTH, x,
                    seriestype=:bar,
                    title="x[n]",
                    linealpha=0,
                    color=:blue,
                    alpha=0.7,
                    legend=:none,
                    showaxis=true,
                    ylims=(-1,1))

        p2 = plot(1:LENGTH, h,
                    seriestype=:bar,
                    title="h[n]",
                    linealpha=0,
                    color=:grey,
                    alpha=0.7,
                    legend=:none,
                    showaxis=true,
                    ylims=(-1,1))

        p3 = plot(1:LENGTH*2-1,
                    y,
                    seriestype=:bar,
                    linealpha=0,
                    alpha=0.7,
                    color=:red,
                    title="y[n] = x[n] * h[n] = h[n] * x[n]",
                    ylims=(-5,5),
                    showaxis=true,
                    minorgrid=true,
                    legend=:none)
        plot!(1:LENGTH*2-1,
                    [x;zeros(LENGTH-1)],
                    seriestype=:bar,
                    linealpha=0,
                    alpha=0.2,
                    color=:blue)
        plot!(1:LENGTH*2-1,
                    [h;zeros(LENGTH-1)],
                    seriestype=:bar,
                    linealpha=0,
                    alpha=0.1,
                    color=:grey)

        question_layout = @layout[a;b]

        if (!isdir("images"))
            mkdir("images")
        end

        plot(p1, p2, layout = question_layout, title="Convolve these two series.")
        png("images/$(LENGTH)_$(i)_side-a_question")

        plot(p3, title="Convolution in red! Did you get it?")
        png("images/$(LENGTH)_$(i)_side-b_answer")
    end
end


using GlobalSensitivity, QuasiMonteCarlo, Test

function linear_batch(X)
    A= 7
    B= 0.1
    @. A*X[1,:]+B*X[2,:]
end
function linear(X)
    A= 7
    B= 0.1
    A*X[1]+B*X[2]
end

p_range = [[-1, 1], [-1, 1]]
reg = gsa(linear_batch, RegressionGSA(), p_range; batch = true)

reg = gsa(linear, RegressionGSA(), p_range; batch = false)
reg = gsa(linear, RegressionGSA(true), p_range; batch = false)

f1m(x) = [x[1], -x[1]]
fn1(x) = 7x[1]
fnm(x) = [x[1], -x[2]]

@testset "f: R -> R^m" begin
    reg = gsa(f1m, RegressionGSA(rank = true), p_range; batch = false)
    @test reg.pearson[1, 1] ≈ 1
    @test reg.standard_regression[1, 1] ≈ 1
    @test reg.partial_correlation[1, 1] ≈ -1
    @test reg.pearson_rank[1, 1] ≈ 1
    @test reg.standard_rank_regression[1, 1] ≈ 1
    @test reg.partial_rank_correlation[1, 1] ≈ -1

    # loose tolerances, exact in limit
    @test reg.pearson[1, 2] ≈ -1
    @test reg.standard_regression[1, 2] ≈ -1
    @test reg.partial_correlation[1, 2] ≈ 1
    @test reg.standard_rank_regression[2, 1] ≈ 0 atol=1e-2
end

@testset "f: R^n -> R" begin
    reg = gsa(fn1, RegressionGSA(rank = true), p_range; batch = false)
    @test reg.pearson[1, 1] ≈ 1
    @test reg.standard_regression[1, 1] ≈ 1
    @test reg.partial_correlation[1, 1] ≈ -1
    @test reg.pearson_rank[1, 1] ≈ 1
    @test reg.standard_rank_regression[1, 1] ≈ 1
    @test reg.partial_rank_correlation[1, 1] ≈ -1

    # loose tolerances, exact in limit
    @test reg.pearson[2, 1] ≈ 0 atol=1e-2
    @test reg.standard_regression[2, 1] ≈ 0 atol=1e-2
    @test reg.partial_correlation[2, 1] ≈ 0 atol=1e-2
    @test reg.standard_rank_regression[2, 1] ≈ 0 atol=1e-2
end

@testset "f: R^n -> R^m" begin
    reg = gsa(fnm, RegressionGSA(rank = true), p_range; batch = false)

    @test reg.pearson[2, 2] ≈ -1
    @test reg.standard_regression[2, 2] ≈ -1
    @test reg.partial_correlation[2, 2] ≈ 1

    # loose tolerances, exact in limit
    @test reg.pearson[2, 1] ≈ 0 atol=1e-2
    @test reg.standard_regression[2, 1] ≈ 0 atol=1e-2
    @test reg.partial_correlation[2, 1] ≈ 0 atol=1e-2
    @test reg.pearson[1, 2] ≈ 0 atol=1e-2
    @test reg.standard_regression[1, 2] ≈ 0 atol=1e-2
    @test reg.partial_correlation[1, 2] ≈ 0 atol=1e-2
end


using BCTRNN
using DiffEqSensitivity
using OrdinaryDiffEq
import DiffEqFlux: FastChain, FastDense
import Flux: ClipValue, ADAM

# Not in Project.toml
using Plots
gr()

include("sine_wave_dataloader.jl")

function train_sine_fc(epochs, solver=Tsit5();
  sensealg=InterpolatingAdjoint(autojacvec=ReverseDiffVJP(true)),
  T=Float32, in_size=10, model_size=5,
  tgrad=false, gen_jac=false, lr=0.02, kwargs...)

  train_dl = generate_2d_data(T)

  f_in = in_size
  f_out = 1

  # input_matrices = Dict(
  #     :w_sens => ones(T, f_in, model_size),
  #     :E_sens => ones(T, f_in, model_size)
  # )
  # syns_matrices = Dict(
  #     :w_syns => ones(T, model_size, model_size),
  #     :E_syns => ones(T, model_size, model_size)
  # )
  # im = BCTRNN.InputMatrices(input_matrices)
  # sm = BCTRNN.SynsMatrices(syns_matrices)

  im = BCTRNN.InputAllToAll()
  sm = BCTRNN.SynsFullyConnected()
  om = BCTRNN.OutputAll()
  wiring = BCTRNN.WiringConfig(f_in, model_size, im,sm,om)
  cell,defs,sys = BCTRNN.LTCOrigMTK(wiring, solver, sensealg; T, tgrad, gen_jac, kwargs...)
  model = FastChain(#(x,p)->ones(eltype(x), f_in, size(x)[2]),
                    FastDense(2, in_size),
                    cell,
                    FastDense(wiring.n_out, f_out))

  hs = []
  for (k,v) in wiring.matrices
    push!(hs, heatmap(v, title=k))
  end
  display(plot(hs..., layout=length(hs)))

  cb = BCTRNN.MyCallback(T; ecb=mycb, nepochs=epochs, nsamples=length(train_dl))
  #opt = GalacticOptim.Flux.Optimiser(ClipValue(0.5), ADAM(0.02))
  opt = BCTRNN.ClampBoundOptim(BCTRNN.get_bounds(model,T)..., ClipValue(T(1.0)), ADAM(T(lr)))
  BCTRNN.optimize(model, BCTRNN.loss_seq, cb, opt, train_dl, epochs, T), model, defs, sys
end

@time res, model, defs, sys = train_sine_fc(200, Tsit5(); lr=0.02, in_size=3, model_size=5,
tgrad=true, gen_jac=false,
save_everystep=false, save_start=false, save_end=true,
abstol=1e-4, reltol=1e-3#, dtmin=1e-3, dtmax=1.0
#dt=0.2f0, adaptive=false,
)


using MOCNeutronTransport
@testset "Quadrilateral_2D" begin
    for F in [Float32, Float64]
        @testset "Constructors" begin
            p₁ = Point_2D(F, 0)
            p₂ = Point_2D(F, 1)
            p₃ = Point_2D(F, 1, 1)
            p₄ = Point_2D(F, 0, 1)
            quad = Quadrilateral_2D(SVector(p₁, p₂, p₃, p₄))
            @test quad.points == SVector(p₁, p₂, p₃, p₄)

            # single constructor
            quad = Quadrilateral_2D(p₁, p₂, p₃, p₄)
            @test quad.points == SVector(p₁, p₂, p₃, p₄)
        end

        @testset "Methods" begin
            p₁ = Point_2D(F, 0)
            p₂ = Point_2D(F, 1)
            p₃ = Point_2D(F, 1, 1)
            p₄ = Point_2D(F, 0, 1)
            quad = Quadrilateral_2D(SVector(p₁, p₂, p₃, p₄))

            # interpolation
            @test quad(0, 0) ≈ p₁
            @test quad(1, 0) ≈ p₂
            @test quad(1, 1) ≈ p₃
            @test quad(0, 1) ≈ p₄
            @test quad(1//2, 1//2) ≈ Point_2D(F, 1//2, 1//2)

            # area
            a = area(quad)
            @test typeof(a) == F
            @test a == F(1)

            # in
            p = Point_2D(F, 1//2, 1//10)
            @test p ∈  quad
            p = Point_2D(F, 1//2, -1//10)
            @test p ∉ quad

            # 3 intersections
            l = LineSegment_2D(p₃, p₁)
            ipoints, points = intersect(l, quad)
            @test ipoints == 3
            @test points[1] ≈ p₁
            @test points[2] ≈ p₃
            @test points[3] ≈ p₃

            # 2 intersections
            l = LineSegment_2D(Point_2D(F, 0, 1//2), Point_2D(F, 1, 1//2))
            ipoints, points = intersect(l, quad)
            @test ipoints == 2
            @test points[1] ≈ Point_2D(F, 1, 1//2)
            @test points[2] ≈ Point_2D(F, 0, 1//2)

            # 0 intersections
            l = LineSegment_2D(Point_2D(F, -1, -1), Point_2D(F, 2, -1))
            ipoints, points = intersect(l, quad)
            @test ipoints == 0
        end
    end
end


using Catalyst
using Distributions, Random
using DelaySSAToolkit

rn = @reaction_network begin
    A, 0 --> X
    B, Y --> 0
end A B

u0 = [0,0]
de_chan0 = [[]]
tf = 100.
tspan = (0,tf)
ps = [10., 1.] # dummy variables, later on will be drawn from a Gamma distribution
τ = 1 # dummy variables, later on will be drawn from a Gamma distribution
delay_trigger = Dict(1=>[1=>τ])
delay_complete = Dict(1=>[1=>-1, 2=>1])
delay_interrupt = Dict()

jumpsys = convert(JumpSystem, rn, combinatoric_ratelaws=false)
dprob = DiscreteProblem(jumpsys,u0,tspan,ps)
# use ensemble problem
algo = DelayRejection()
delayjumpset = DelayJumpSet(delay_trigger, delay_complete, delay_interrupt)
djprob = DelayJumpProblem(jumpsys, dprob, algo, delayjumpset, de_chan0, save_positions=(false,false))

function prob_func1(prob, i, repeat)
    rng = Random.seed!(i)
    A = rand(rng, Gamma(8,1/0.23))
    B = rand(rng, Gamma(9,1/625))
    τ  = rand(rng, Gamma(7,1))
    remake(prob, p = [A,B], delay_trigger=Dict(1=>[1=>τ]))
end
function prob_func2(prob, i, repeat)
    rng = Random.seed!(i)
    α = rand(rng, Gamma(63,1/9)) # the Gamma distribution uses shape α, scale θ, Gamma(α,θ). In the paper, Gamma distribution uses shape α and rate  β. One needs to set the inverse.
    β = rand(rng, Gamma(10.,1/10.))
    A = rand(rng, Gamma(8,1/0.23))
    B = rand(rng, Gamma(9,1/625))
    τ  = rand(rng, Gamma(α,1/β))
    remake(prob, p = [A,B], delay_trigger=Dict(1=>[1=>τ]))
end

prob1 = EnsembleProblem(djprob, prob_func = prob_func1)
@time ens1 = solve(prob1, SSAStepper(), EnsembleThreads(), trajectories = 40, saveat= 1.)
Y_evolution  = [ens1[i][2,:] for i in 1:40]

using DifferentialEquations.EnsembleAnalysis
using Plots;theme(:vibrant)
plot(Y_evolution, linealpha=0.4, legend=false , linewidth = 2, fmt=:svg, xlabel = "Time", ylabel="# of Y individuals")
savefig("docs/src/assets/heterogeneous_delay1.svg")

prob2 = EnsembleProblem(djprob, prob_func = prob_func2)
@time ens2 = solve(prob2, SSAStepper(), EnsembleThreads(), trajectories = 10^4)

last_slice = componentwise_vectors_timepoint(ens2, tf)
histogram(last_slice[1], bins=0:20:1000, normalize=:pdf, xlabel = "# of X individuals", ylabel = "Probability",fillalpha = 0.6, linecolor = :orange, legend = false)
# savefig("docs/src/assets/heterogeneous_delay2.svg")


using RungeKutta
using Test

include("test_utils.jl")
include("test_tableau.jl")
include("test_tableau_partitioned.jl")
include("test_order_conditions.jl")
include("test_symmetry.jl")
include("test_symplecticity.jl")
include("test_gauss.jl")
include("test_lobatto.jl")
include("test_radau.jl")
include("test_tableaus.jl")
include("test_tableaus_prk.jl")
include("test_list.jl")


function topologicalorder(network::AbstractNetwork{T}) where T<:Integer
    nnodes = numnodes(network)
    nodeorder = zeros(T, nnodes)
    deg = indegree.(Ref(network), 1:nnodes)

    S = filter((i) -> deg[i] == 0, 1:nnodes)
    if length(S) == 0
        @warn "No nodes with no incoming links. Cannot define a topological order!"
    elseif length(S) > 1
        @warn "More than one nodes with zero indegree. Topological order not unique!"
    end
    o = 0
    while length(S) > 0
        i = pop!(S)
        o += 1
        nodeorder[i] = o
        for j in outneighbors(network, i)
            deg[j] -= 1
            if deg[j] == 0
                push!(S, j)
            end
        end
    end
    if (o != nnodes)
        @warn "Network is not acyclic. Incomplete topological order!"
    end

    nodeorder
end

orderednodes(network::AbstractNetwork) = invperm(topologicalorder(network))


"""
```
t = find_threshold(MinimumIntermodes(),  histogram, edges)
```

Under the assumption that the histogram is bimodal the histogram is smoothed
using a length-3 mean filter until two modes remain. The threshold is then set
to the minimum value between the two modes.

# Output

Returns `t`, a real number that specifies the threshold.

# Arguments

The function arguments are described in more detail below.

##  `histogram`

An `AbstractArray` storing the frequency distribution.

##  `edges`

An `AbstractRange` specifying how the intervals for the frequency distribution
are divided.

# Example

Compute the threshold for the "camerman" image in the `TestImages` package.

```julia
using TestImages, ImageContrastAdjustment, HistogramThresholding

img = testimage("cameraman")
edges, counts = build_histogram(img,256)
#=
  The `counts` array stores at index 0 the frequencies that were below the
  first bin edge. Since we are seeking a threshold over the interval
  partitioned by `edges` we need to discard the first bin in `counts`
  so that the dimensions of `edges` and `counts` match.
=#
t = find_threshold(MinimumIntermodes(),counts[1:end], edges)
```

# Reference

1. C. A. Glasbey, “An Analysis of Histogram-Based Thresholding Algorithms,” CVGIP: Graphical Models and Image Processing, vol. 55, no. 6, pp. 532–537, Nov. 1993. [doi:10.1006/cgip.1993.1040](https://doi.org/10.1006/cgip.1993.1040)
"""
function find_threshold(algorithm::MinimumIntermodes, histogram::AbstractArray, edges::AbstractRange)
    histogram_local = smooth_histogram(histogram, 1000)
    indices = find_maxima_indices(histogram_local)
    if length(indices) != 2
        return 0
    end
    t = 0
    min_value = typemax(Float64)
    for i = min(indices[1],indices[2]):max(indices[1],indices[2])
        if histogram_local[i] < min_value
            min_value = histogram_local[i]
            t = i
        end
    end
    index = round(Int, t)
    return edges[index]
end


#=
Copyright (c) 2015, Intel Corporation
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
=#

module SeqTest
using ParallelAccelerator

#ParallelAccelerator.DomainIR.set_debug_level(4)
#ParallelAccelerator.ParallelIR.set_debug_level(4)
#ParallelAccelerator.CGen.set_debug_level(4)
#ParallelAccelerator.set_debug_level(4)

# testing sequential code taken from quant example
@acc function mmult(u::Array{Float64, 2}, v::Array{Float64, 2})
    m, l0 = size(u)
    l, n = size(v)
    # assert(l0 == l)
    r = Array(Float64, m, n)
    for i = 1:m
        for j = 1:n
            s = convert(Float64, 0)
            for k = 1:l
                s += u[i, k] * v[k, j]
            end
            r[i, j] = s
        end
    end
    return r
end

function test1()
    A = [2.0 1.0; 2.0 2.0]
    B = [4.0 2.0; 1.0 8.0]
    return mmult(A, B)
end

end

using Base.Test

println("Testing sequential code...")
@test SeqTest.test1() ≈ [9.0 12.0; 10.0 20.0]
println("Done testing sequential code.")


@testset "DBSCAN" begin

    Random.seed!(42)

    df = CSV.read("data/blob_data.csv", DataFrame, drop=[1]);
    X = df[:,1:2];

    ϵ = 0.35;
    min_pts = 10;

    @testset "test errors" begin
        @test_throws ArgumentError dbscan(X[:, 2], ϵ, min_pts)
    end

    @testset "test Array{Float64, 2}" begin
        X_array = Matrix(X)
        res_ar = dbscan(X_array, ϵ, min_pts)
        n_clusters = unique(res_ar.labels) |> length
        @test length(res_ar.labels) == size(X_array, 1)
    end

    @testset "test dimensions results" begin
        res = dbscan(X, ϵ, min_pts)
        n_clusters = unique(res.labels) |> length
        @test length(res.labels) == size(X, 1)
        @test length(res.clusters) == n_clusters - 1
        @test length(res.clusters[1]) == 1010
        @test length(res.clusters[2]) == 999
        @test length(res.clusters[3]) == 994
        @test length(res.clusters[4]) == 1000
    end

    @testset "test Integer conversion" begin
        X_int = round.(Int, X.*100)
        res_int = dbscan(X_int, ϵ*100, min_pts)
        @test eltype(res_int.df) == Float64
        @test length(res_int.labels) == size(X_int, 1)
    end

end


function initializeWorld()
    Δϕ_lim = 0.25*pi # Turn rate limit per time unit
    r_lim = 1.0 # Distance limit per time unit

    ϕ_t_min = ϕ_0
    x_t_min = x_0
    y_t_min = y_0
    function execute(u_hat_t::Vector{Float64})
        Δϕ_t = u_hat_t[1] # Proposed turn rate
        Δϕ_t = clamp(Δϕ_t, -Δϕ_lim, Δϕ_lim) # Allowed turn rate

        r_t = u_hat_t[2] # Proposed distance
        r_t = clamp(r_t, -r_lim, r_lim) # Allowed distance

        # Compute next state
        ϕ_t = ϕ_t_min + Δϕ_t
        x_t = x_t_min + r_t*cos(ϕ_t)
        y_t = y_t_min + r_t*sin(ϕ_t)

        # Reset state for next step
        ϕ_t_min = ϕ_t
        x_t_min = x_t
        y_t_min = y_t
    end

    ϕ_t = ϕ_0
    x_t = x_0
    y_t = y_0
    observe() = [ϕ_t, x_t, y_t]

    return (execute, observe)
end


#
# Distance function, considering PBC
#
function rpbc(x,y,side)
  # dx is a vector of the difference in position of y and x
  d = 0.
  for i in 1:2
    dx = (y[i]-x[i])%side
    if dx > side/2
      dx = dx - side
    elseif  dx < -side/2
      dx = dx + side
    end
    d = d + dx^2
  end
  return sqrt(d)
end


module WRS

using Distributions
using Distributed

export hd, pb2gen

# This implements (3.16) from (2017) p. 71-72
function hd(X, q = 0.5, issorted = false)
    n = length(X)

    a = (n + 1) * q
    b = (n + 1) * (1 - q)

    β = Beta(a, b)

    sorted_X = issorted ? X : sort(X)

    θ = 0.
    for i = 1:n
        w = cdf(β, i / n) - cdf(β, (i - 1) / n)
        θ += w * sorted_X[i]
    end

    return θ
end

function pb2gen(x, y; quantiles = [0.05, 0.25, 0.5, 0.75, 0.95], alpha = 0.05, nboot = 2000, estimator = hd)
    bootstrapped_diff_est = pmap(1:nboot) do i
        sampled_values_x = sample(x, length(x), replace = true)
        sampled_values_y = sample(y, length(y), replace = true)

        sort!(sampled_values_x)
        sort!(sampled_values_y)

        return [estimator(sampled_values_x, q, true) - estimator(sampled_values_y, q, true) for q in quantiles]
    end
    bootstrapped_diff_estimates = [[bootstrapped_diff_est[r_i][q_i] for r_i in 1:nboot] for q_i in 1:length(quantiles)]

    return map(enumerate(bootstrapped_diff_estimates)) do (q_i, bootstrapped_diff_est)
        sort!(bootstrapped_diff_est)

        low = round(Int, (alpha / 2) * nboot) + 1
        up = nboot - low

        q = quantiles[q_i]

        est_x = estimator(x, q)
        est_y = estimator(y, q)
        est_diff = est_x - est_y
        ci_low = bootstrapped_diff_est[low]
        ci_up = bootstrapped_diff_est[up]

        A = count(i->i < 0, bootstrapped_diff_est)
        C = count(i->i == 0, bootstrapped_diff_est)
        p_hat_star = A / nboot + 0.5 * C / nboot
        pvalue = 2 * (min(p_hat_star, 1 - p_hat_star))

        se = var(bootstrapped_diff_est)

        return (quantile = q, est_x = est_x, est_y = est_y, est_diff = est_diff, ci_low = ci_low, ci_up = ci_up, pvalue = pvalue, se = se, signif = pvalue < alpha)
    end
end

end # module


using GreatCircle
using Base.Test

# One test decimal degree is 111000m
latitude = 40.0
longitude = -76.0

"""
    Great Circle Tests
"""
azimuth = 90.
new_gc = great_circle(111000., azimuth, latitude, longitude)
# We should have gone to the right
@test new_gc["longitude"] > longitude + 0.9

azimuth = 270.
new_gc = great_circle(111000., azimuth, latitude, longitude)
# We should have gone to the left
@test new_gc["longitude"] < longitude - 0.9

azimuth = 180.
new_gc = great_circle(111000., azimuth, latitude, longitude)
# We should have gone down
@test new_gc["latitude"] < latitude - 0.9

azimuth = 0.
new_gc = great_circle(111000., azimuth, latitude, longitude)
# We should have gone up
@test new_gc["latitude"] > latitude + 0.9

azimuth = 315.
new_gc = great_circle(111000., azimuth, latitude, longitude)
# We should have gone up and to the left
@test new_gc["latitude"] > latitude + 0.45
@test new_gc["longitude"] < longitude - 0.45

# One decimal degree is 111000m
latitude = [40.0:5.:60.0]
longitude = [-40.0:-5.:-60.0]

azimuth = ones(longitude) * 90.
new_gc = great_circle(111000., azimuth, latitude, longitude)
# We should have gone to the right
@test all(new_gc["longitude"] .> longitude + 0.9)

azimuth = 270.
distance = 111000. * ones(longitude)
new_gc = great_circle(distance, azimuth, latitude, longitude)
# We should have gone to the left
@test all(new_gc["longitude"] .< longitude - 0.9)

azimuth = 180.
new_gc = great_circle(111000., azimuth, latitude, longitude)
# We should have gone down
@test all(new_gc["latitude"] .< latitude - 0.9)

azimuth = 0.
new_gc = great_circle(111000., azimuth, latitude, longitude)
# We should have gone up
@test all(new_gc["latitude"] .> latitude + 0.9)

azimuth = 315.
new_gc = great_circle(111000., azimuth, latitude, longitude)
# We should have gone up and to the left
@test all(new_gc["latitude"] .> latitude + 0.45)
@test all(new_gc["longitude"] .< longitude - 0.45)

"""
    Great Distance Tests
"""
# One decimal degree at the equator is about 111.32km
latitude_start  = 0.
latitude_end    = 0.
longitude_start = 50.
longitude_end   = 52.
gd = great_distance(latitude_start, longitude_start, latitude_end, longitude_end)
@test round(gd["distance"] ./ 1000, 2) .== 111.32 .* 2

# One decimal degree is 111000m
latitude_start  = 0.
latitude_end    = 0.
longitude_start = [49., 75.]
longitude_end   = [50., 76.]
gd = great_distance(latitude_start, longitude_start, latitude_end, longitude_end)
@test all( round(gd["distance"] ./ 1000, 2) .== 111.32 )


function DiffusionDefinition.diffusion_message(::Val{:FitzHughNagumo})
    println("* * * * * * * * * * * * *")
    println("FitzHugh–Nagumo diffusion")
    println("* * * * * * * * * * * * *")
    println("Description")
    println("------------")
    println("A model developed to mimic the evolution of a neuron's membrane ")
    println("potential. Represented by two-dimensional process (Yₜ, Xₜ)")
    println("dYₜ = (Yₜ - (Yₜ)³ - Xₜ + s)/ϵ dt,")
    println("dXₜ = (γYₜ - Xₜ - β)dt + σdWₜ.")
    println("The model has five parameters:")
    println("\tϵ, s, γ, β, σ")
    println("\nConstructors")
    println("------------")
    println("You may initialize the process either by passing the parameters")
    println("in the order above, as in:")
    println("ϵ, s, γ, β, σ = ...")
    println("P = FitzHughNagumo(ϵ, s, γ, β, σ)")
    println("or by keyword arguments")
    println("p = (ϵ=..., s=..., γ=..., β=..., σ=...)")
    println("P = FitzHughNagumo(;p...)")
    println()
end

@diffusion_process FitzHughNagumo{T} begin
    :dimensions
    process --> 2
    wiener --> 1

    :parameters
    (ϵ, s, γ, β, σ) --> T

    :additional
    constdiff --> true
end

DiffusionDefinition.b(t, x, P::FitzHughNagumo) = @SVector [
    (x[1] - x[2] - x[1]^3 + P.s)/P.ϵ,
    P.γ*x[1] - x[2] + P.β
]

DiffusionDefinition.σ(t, x, P::FitzHughNagumo) = @SMatrix [0.0; P.σ]


using Pkg
Pkg.activate(@__DIR__)

using MuJoCo
mj_activate("/home/taylor/.mujoco/bin/mjkey.txt") # set location to MuJoCo key path

using LyceumMuJoCo, LyceumMuJoCoViz
using FiniteDiff

using IterativeLQR
using LinearAlgebra
using Random

# ## load MuJoCo model
path = joinpath(@__DIR__, "acrobot.xml")
path = joinpath(@__DIR__, "acrobot_limits.xml")

include("mujoco_model.jl")
acrobot_mujoco = MuJoCoModel(path)
sim = MJSim(acrobot_mujoco.m, acrobot_mujoco.d);

# ## horizon
T = 101

# ## acrobot_mujoco
nx = acrobot_mujoco.nx
nu = acrobot_mujoco.nu

# ## model
dyn = IterativeLQR.Dynamics(
    (y, x, u, w) -> f_mujoco!(y, acrobot_mujoco, x, u),
    (dx, x, u, w) -> fx_mujoco!(dx, acrobot_mujoco, x, u),
    (du, x, u, w) -> fu_mujoco!(du, acrobot_mujoco, x, u),
    nx, nx, nu)

model = [dyn for t = 1:T-1];

# ## initial conditions
x1 = [π; 0.0; 0.0; 0.0]
xT = [0.0; 0.0; 0.0; 0.0]

# ## objective
function objt(x, u, w)
	J = 0.0
    v = x[3:4]
	J += 0.5 * 0.1 * transpose(v) * v
	J += 0.5 * transpose(u) * u
	return J
end

function objT(x, u, w)
	J = 0.0
    v = x[3:4]
	J += 0.5 * 0.1 * transpose(v) * v
	return J
end

ct = IterativeLQR.Cost(objt, acrobot_mujoco.nx, acrobot_mujoco.nu)
cT = IterativeLQR.Cost(objT, acrobot_mujoco.nx, 0)
obj = [[ct for t = 1:T-1]..., cT];

# ## constraints
goal(x, u, w) = x - xT

cont = IterativeLQR.Constraint()
conT = IterativeLQR.Constraint(goal, nx, 0)
cons = [[cont for t = 1:T-1]..., conT];

# # rollout
Random.seed!(1)
ū = [1.0e-3 * randn(acrobot_mujoco.nu) for t = 1:T-1]
w = [zeros(0) for t = 1:T-1]
x̄ = IterativeLQR.rollout(model, x1, ū);

# ## solver
solver = IterativeLQR.solver(model, obj, cons,
    opts=iLQR.Options(
        linesearch = :armijo,
        α_min=1.0e-5,
        obj_tol=1.0e-5,
        grad_tol=1.0e-5,
        max_iter=50,
        max_al_iter=10,
        con_tol=0.001,
        ρ_init=1.0,
        ρ_scale=10.0,
        verbose=false))
IterativeLQR.initialize_controls!(solver, ū)
IterativeLQR.initialize_states!(solver, x̄);

# ## solve
IterativeLQR.reset!(solver.s_data)
@time IterativeLQR.solve!(solver);

@show solver.s_data.iter[1]
@show IterativeLQR.eval_obj(solver.m_data.obj.costs, solver.m_data.x, solver.m_data.u, solver.m_data.w)
@show norm(goal(solver.m_data.x[T], zeros(0), zeros(0)), Inf)
@show solver.s_data.obj[1] # augmented Lagrangian cost

# ## benchmark
solver.options.verbose = true
@benchmark IterativeLQR.solve!($solver, x̄, ū) setup=(x̄=deepcopy(x̄), ū=deepcopy(ū));

# ## solution
x_sol, u_sol = IterativeLQR.get_trajectory(solver);

# ## MuJoCo visualizer
states = Array(undef, statespace(sim), T-1)
for t = 1:T-1
    sim.d.qpos .= x_sol[t][acrobot_mujoco.idx_pos]
    sim.d.qvel .= x_sol[t][acrobot_mujoco.idx_vel]
    sim.d.ctrl .= u_sol[t]
    states[:, t] .= getstate(sim)
end

visualize(sim, trajectories=[states]); # ctrl + LEFT (access trajectory mode)


@testset "periodic_transform" begin
    @testset "compare to periodic exponentiated quadratic" begin
        rng = MersenneTwister(123456)
        f = rand(rng) + 2.0
        x = collect(range(0.0, 3.0 / f; length=1_000))

        # Construct in the usual way.
        k_eq_periodic = PeriodicKernel(; r=[sqrt(0.25)]) ∘ ScaleTransform(f)

        # Construct using the peridic transform.
        k_eq_transform = SqExponentialKernel() ∘ PeriodicTransform(f)

        @test kernelmatrix(k_eq_periodic, x) ≈ kernelmatrix(k_eq_transform, x)
        # TODO - add interface_tests once #159 is merged.
    end
end


# Load Julia packages (libraries)

using CSV, DataFrames, Statistics
using AxisKeys
using StanSample

df = CSV.read(joinpath(@__DIR__, "..", "..", "..", "data", "chimpanzees.csv"), DataFrame);

# Define the Stan language model

stan10_4 = "
data{
    int N;
    int N_actors;
    int pulled_left[N];
    int prosoc_left[N];
    int condition[N];
    int actor[N];
}
parameters{
    vector[N_actors] a;
    real bp;
    real bpC;
}
model{
    vector[N] p;
    bpC ~ normal( 0 , 10 );
    bp ~ normal( 0 , 10 );
    a ~ normal( 0 , 10 );
    for ( i in 1:504 ) {
        p[i] = a[actor[i]] + (bp + bpC * condition[i]) * prosoc_left[i];
        p[i] = inv_logit(p[i]);
    }
    pulled_left ~ binomial( 1 , p );
}
";

data = (N = size(df, 1), N_actors = length(unique(df.actor)),
    actor = df.actor, pulled_left = df.pulled_left,
    prosoc_left = df.prosoc_left, condition = df.condition);

# Sample using cmdstan

m10_4s = SampleModel("m10.4s", stan10_4)
rc10_4s = stan_sample(m10_4s; data);

if success(rc10_4s)
    chns = read_samples(m10_4s, :keyedarray)

    # Display the chns

    chns |> display
    println()

    # Display the keys

    axiskeys(chns) |> display
    println()

    axiskeys(chns, :param) |> display
    println()

    chns(chain=1) |> display
    println()

    chns[:, 1, 8] |> display
    println()

    chns(chain=1, param=:bp) |> display
    println()

    chns(chain=[1, 3], param=[:bp, :bpC]) |> display
    println()

    # Select all elements starting with 'a'

    chns_a = matrix(chns, :a)
    chns_a |> display
    println()

    mean(chns_a, dims=1) |> display
    println()

    typeof(chns_a.data) |> display
    println()

    ndraws_a, nchains_a, nparams_a = size(chns_a)
    chn_a = reshape(chns_a, ndraws_a*nchains_a, nparams_a)
    println()

    for row in eachrow(chn_a)
        # ...
    end

    # Or use read_samples to only use chains 2 and 4 using the chains kwarg.

    chns2 = read_samples(m10_4s; chains=[2, 4])
    chns2_a = matrix(chns2, :a)
    ndraws2_a, nchains2_a, nparams2_a = size(chns2_a)
    chn2_a = reshape(chns2_a, ndraws2_a*nchains2_a, nparams2_a)
    mean(chns2_a, dims=1) |> display

end