#Imports

import Pkg; Pkg.add("WAV"); Pkg.add("Plots"); Pkg.add("TickTock"); Pkg.add("MTH229"); Pkg.add("SymPy")
using Pkg

using WAV

whiteNoise = (rand(48000)*2).-1
WAV.wavwrite(whiteNoise, "whiteNoise.wav", Fs=4800) #sample freq is 4800Hz

#white noise generator script:

function createwhiten(n)
    whiten = Vector{Float64}();
    for i in 1:n*48000
        append!(whiten, (rand()*2. - 1));
    end
    return whiten
end

noise = createwhiten(1);
WAV.wavwrite(noise, "white_noise_sound2.wav", Fs=4800);
print("Number of samples: ", length(noise))

using Plots
h = Plots.histogram(noise)
Plots.display(h)
readline() #this will stop the program at this point till you press enter

using TickTock

# comparing the performance of the two approaches to generate white noise.

# Measuring excecution time of rand() generating 10 seconds of white noise:
tick()
whiteNoise = (rand(48000)*2).-1
#WAV.wavwrite(whiteNoise, "whiteNoise_test1_rand().wav", Fs=4800) #sample freq is 4800Hz
#tok()
#x = tok()
print("Time taken for rand() function to execute generating 10 seconds of white noise: ", tok(), "\n")

# Measuring excecution time of rand() generating 20 seconds of white noise:
tick()
whiteNoise = (rand(96000)*2).-1
#WAV.wavwrite(whiteNoise, "whiteNoise_test1_rand().wav", Fs=4800) #sample freq is 4800Hz
#tok()
#x = tok()
print("Time taken for rand() function to execute generating 20 seconds of white noise: ", tok(), "\n")

# Measuring excecution time of rand() generating 30 seconds of white noise:
tick()
whiteNoise = (rand(180000)*2).-1
#WAV.wavwrite(whiteNoise, "whiteNoise_test1_rand().wav", Fs=4800) #sample freq is 4800Hz
#tok()
#x = tok()
print("Time taken for rand() function to execute generating 30 seconds of white noise: ", tok(), "\n")

# Measuring the execution time of createwhiten(n) for n = 10;
tick()
noise = createwhiten(10);
#WAV.wavwrite(noise, "white_noise_sound2_test2_createwhiten.wav", Fs=4800);
#tok()
#y = tok()
print("Time taken for createwhiten(10) function to execute: ", tok(), "\n")

# Measuring the execution time of createwhiten(n) for n = 20;
tick()
noise = createwhiten(20);
#WAV.wavwrite(noise, "white_noise_sound2_test3_createwhiten.wav", Fs=4800);
print("Time taken for createwhiten(20) function to execute: ", tok(), "\n")

# Measuring the execution time of createwhiten(n) for n = 30;
tick()
noise = createwhiten(30);
#WAV.wavwrite(noise, "white_noise_sound2_test4_createwhiten.wav", Fs=4800);
print("Time taken for createwhiten(30) function to execute: ", tok(), "\n")

# Implement the Pearson’s correlation formula a new function call it corr().

function corr(x = Vector{Float64}(), y = Vector{Float}())

    #Setting up summation of xy:
    xyVal = 0
    for i in 1:length(x)
        #xyVal = 0;
        for z in 1:length(y)
            xyVal = xyVal + x[z]*y[z]
        end
        #return xyVal
    end

    #Setting up summation of x values:
    for n in 1:length(x)
        xVal = 0;
        for j in 1:length(x)
            xVal = xVal + x[j]
        end
        #return xVal
    end

    #Setting up the summation of y values:
    for c in 1:length(y)
        yVal = 0;
        for p in 1:length(y)
            yVal = yVal + y[p]
        end
        #return yVal
    end

    #Setting up summation of x^2
    x2Val = 0
    for q in 1:length(x)
        #x2Val = 0;
        for g in 1:length(x)
            x2Val = x2Val + x[g]*x[g]
        end
        #return x2Val
    end

    #Setting up summation of y^2
    y2Val = 0
    for h in 1:length(y)
        #y2Val = 0;
        for w in 1:length(y)
            y2Val = y2Val + y[w]*y[w]
        end
        #return y2Val
    end

    r = xyVal/(sqrt(x2Val)*sqrt(y2Val));
    return r

end

corr([1,2,3], [4,5,6])

# Using your output from the createWhiten(numberOfSeconds) function compare its
# correlation against itself using your correlation function and the Statistics.cor function.

using Statistics

#Correlation of createwhiten() against itself using the corr() function created.
corr(createwhiten(1), createwhiten(1))

#Correlation of creatwhiten() against itself using the Statistics.cor function
cor(createwhiten(1), createwhiten(1))

noise1 = createwhiten(1);
print("Number of samples: ", length((rand(48000)*2).-1), "\n")
print("Number of samples: ", length(noise1), "\n")
cor((rand(48000)*2).-1, createwhiten(1))

using Base
using MTH229
using Plots
using SymPy

#defining  variables used
#time
t=0:0.01:1 #100 samples
u=0:0.005:1 #200 samples used to plot and
#angular frequency
freq1=20*pi
freq2=50*2pi
freq3=2*pi
#empty array for correlation coefficients
k=zeros(0)

#Correlation coefficient vs shift, f = 20 Hz
for i in 0:0.005:1
    f(t)=sin.(freq1*(t))
    #shifted function
    g(t)=sin.(freq1*(t.-i))
    push!(k,cor(g(t),f(t)))
end

plot(u,k,
title="Corelation Coefficient vs shift, f= 20 Hz",
label=false,
lw=2,
xlabel= "Shift",
ylabel= "Correlation Coefficient")

#plot of graph and shifted graphs from above
f(t)= sin.(freq1*(t))
g(t)=sin.(freq1*(t).-pi/2)
i(t)=sin.(freq1*(t).-pi)
plot(t,f(t),
title="Graph displaying normal and shifted plots",
label=true,
lw=2,
xlabel= "t",
ylabel= "Ampltude",
)
plot!(t,g(t))
plot!(t,i(t))

#Correlation coefficient vs shift, f = 2 Hz
h=zeros(0)
for i in 0:0.005:1
    f(t)=sin.(freq3*(t))
    #shifted function
    g(t)=sin.(freq3*(t.-i))
    push!(h,cor(g(t),f(t)))
end
plot(u,h ,
title="Corelation Coefficient vs shift, f= 2 Hz",
label=false,
lw=2,
xlabel= "Shift in time",
ylabel= "Correlation Coefficient")

#Correlation coefficient vs shift, f = 50 Hz
l=zeros(0)
t=0:0.001:1 #1000 samples
u=0:0.001:1 #200 samples used to plot and
for i in 0:0.001:1
    f(t)=sin.(freq2*(t))
    #shifted function
    g(t)=sin.(freq2*(t.-i))
    push!(l,cor(g(t),f(t)))
end
plot(u,l ,
title="Corelation Coefficient vs shift, f= 50 Hz",
label=false,
lw=2,
xlabel= "Shift in time",
ylabel= "Correlation Coefficient")


__precompile__()

module ContMechTensors

import Base.@pure
using StaticArrays

export AbstractTensor, SymmetricTensor, Tensor, Vec, FourthOrderTensor, SecondOrderTensor

export otimes, ⊗, ⊡, dcontract, dev, vol, symmetric, skew, minorsymmetric, majorsymmetric
export minortranspose, majortranspose, isminorsymmetric, ismajorsymmetric
export tdot, dott, dotdot
export hessian#, gradient
export basevec, eᵢ
export rotate

@deprecate extract_components(tensor) Array(tensor)

#########
# Types #
#########
abstract AbstractTensor{order, dim, T <: Real} <: AbstractArray{T, order}

immutable SymmetricTensor{order, dim, T <: Real, M} <: AbstractTensor{order, dim, T}
   data::SVector{M, T}
end

immutable Tensor{order, dim, T <: Real, M} <: AbstractTensor{order, dim, T}
   data::SVector{M, T}
end

###############
# Typealiases #
###############
typealias Vec{dim, T, M} Tensor{1, dim, T, dim}

typealias AllTensors{dim, T} Union{SymmetricTensor{2, dim, T}, Tensor{2, dim, T},
                                   SymmetricTensor{4, dim, T}, Tensor{4, dim, T},
                                   Vec{dim, T}}

typealias SecondOrderTensor{dim, T} Union{SymmetricTensor{2, dim, T}, Tensor{2, dim, T}}
typealias FourthOrderTensor{dim, T} Union{SymmetricTensor{4, dim, T}, Tensor{4, dim, T}}
typealias SymmetricTensors{dim, T} Union{SymmetricTensor{2, dim, T}, SymmetricTensor{4, dim, T}}
typealias Tensors{dim, T} Union{Tensor{2, dim, T}, Tensor{4, dim, T}, Vec{dim, T}}

include("utilities.jl")
include("indexing.jl")
include("promotion_conversion.jl")
include("tensor_products.jl")
include("transpose.jl")
include("symmetric.jl")
include("math_ops.jl")
include("special_ops.jl")
include("tensor_ops_errors.jl")
include("automatic_differentiation.jl")

##############################
# Utility/Accessor Functions #
##############################
get_data(t::AbstractTensor) = t.data.data
tovector(t::AbstractTensor) = t.data

function tomatrix{dim}(t::Tensor{4, dim})
    N = n_components(Tensor{2,dim})
    return SMatrix{N, N}(tovector(t))
end

function tomatrix{dim}(t::Tensor{2, dim})
    N = n_components(Tensor{1,dim})
    return SMatrix{N, N}(tovector(t))
end

@pure n_components{dim}(::Type{SymmetricTensor{2, dim}}) = dim*dim - div((dim-1)*dim, 2)
@pure function n_components{dim}(::Type{SymmetricTensor{4, dim}})
    n = n_components(SymmetricTensor{2, dim})
    return n*n
end
@pure n_components{order, dim}(::Type{Tensor{order, dim}}) = dim^order

@pure get_base{order, dim, T, M}(::Type{SymmetricTensor{order, dim, T, M}}) = SymmetricTensor{order, dim}
@pure get_base{order, dim, T}(::Type{SymmetricTensor{order, dim, T}}) = SymmetricTensor{order, dim}
@pure get_base{order, dim}(::Type{SymmetricTensor{order, dim}}) = SymmetricTensor{order, dim}
@pure get_base{order, dim, T, M}(::Type{Tensor{order, dim, T, M}}) = Tensor{order, dim}
@pure get_base{order, dim, T}(::Type{Tensor{order, dim, T}}) = Tensor{order, dim}
@pure get_base{order, dim}(::Type{Tensor{order, dim}}) = Tensor{order, dim}

############################
# Abstract Array interface #
############################
Base.linearindexing{T <: SymmetricTensor}(::Type{T}) = Base.LinearSlow()
Base.linearindexing{T <: Tensor}(::Type{T}) = Base.LinearFast()

get_type{X}(::Type{Type{X}}) = X

########
# Size #
########
Base.size{dim}(::Vec{dim}) = (dim,)
Base.size{dim}(::SecondOrderTensor{dim}) = (dim, dim)
Base.size{dim}(::FourthOrderTensor{dim}) = (dim, dim, dim, dim)

#########################
# Internal constructors #
#########################
function _constructor_check{order, dim}(Tt::Union{Type{Tensor{order, dim}}, Type{SymmetricTensor{order, dim}}})
    !(order in (1,2,4)) && throw(ArgumentError("tensors only supported for order 1, 2 and 4"))
    !(dim in (1,2,3)) && throw(ArgumentError("tensors only supported for dim 1, 2 and 3"))
    order == 1 && Tt <: SymmetricTensor && throw(ArgumentError("symmetric tensors only supported for order 2 and 4"))
    return nothing
end

for TensorType in (SymmetricTensor, Tensor)
    @eval begin
        @inline (::Type{$TensorType{order, dim}}){order, dim, T <: Real, M}(t::NTuple{M, T}) = $TensorType{order, dim}(SVector{M,T}(t))
        @inline (::Type{$TensorType{order, dim, T1}}){order, dim, T1 <: Real, T2 <: Real, M}(t::NTuple{M, T2}) = $TensorType{order, dim}(SVector{M,T1}(t))
        @inline (::Type{$TensorType{order, dim, T1, M}}){order, dim, T1 <: Real, T2 <: Real, M}(t::NTuple{M, T2}) = $TensorType{order, dim}(SVector{M,T1}(t))

        @inline (::Type{$TensorType{order, dim}}){order, dim, T <: Real, M}(t::SVector{M, T}) = _constructor_check($TensorType{order, dim})
    end
    for order in (2,4), dim in (1,2,3)
        M = n_components(TensorType{order,dim})
        @eval @inline (::Type{$TensorType{$order, $dim}}){T <: Real}(t::SVector{$M, T}) = $TensorType{$order, $dim, T, $M}(t)
        @eval @inline (::Type{$TensorType{$order, $dim}}){T <: Real, Q}(t::SMatrix{Q, Q, T, $M}) = $TensorType{$order, $dim, T, $M}(t.data)
    end
    if TensorType == Tensor
        for dim in (1,2,3)
            @eval @inline (::Type{Tensor{1, $dim}}){T <: Real}(t::SVector{$dim, T}) = Tensor{1, $dim, T, $dim}(t)
        end
    end
end
# Special for Vec
@inline (Tt::Type{Vec{dim}}){dim}(data) = Tensor{1, dim}(data)

# General fallbacks
@inline          (Tt::Type{Tensor{order, dim, T}}){order, dim, T}(data::Union{AbstractArray, Tuple, Function}) = convert(Tensor{order, dim, T}, Tensor{order, dim}(data))
@inline (Tt::Type{SymmetricTensor{order, dim, T}}){order, dim, T}(data::Union{AbstractArray, Tuple, Function}) = convert(SymmetricTensor{order, dim, T}, SymmetricTensor{order, dim}(data))
#@inline          (Tt::Type{Tensor{order, dim, T, M}}){order, dim, T, M}(data::Union{AbstractArray, Tuple, Function}) = Tensor{order, dim, T}(data)
#@inline (Tt::Type{SymmetricTensor{order, dim, T, M}}){order, dim, T, M}(data::Union{AbstractArray, Tuple, Function}) = SymmetricTensor{order, dim, T}(data)

###############
# Simple Math #
###############

# op(Number, Tensor): *, /
for TensorType in (SymmetricTensor, Tensor)
    @eval begin
        @inline Base.:*{order, dim, T, N}(n::Number, t::$TensorType{order, dim, T, N}) = $TensorType{order, dim}(n * tovector(t))
        @inline Base.:*{order, dim, T, N}(t::$TensorType{order, dim, T, N}, n::Number) = $TensorType{order, dim}(tovector(t) * n)
        @inline Base.:/{order, dim, T, N}(t::$TensorType{order, dim, T, N}, n::Number) = $TensorType{order, dim}(tovector(t) / n)

        # Unary -, +
        @inline Base.:-{order, dim, T, N}(t::$TensorType{order, dim, T, N}) = $TensorType{order, dim}(-tovector(t))
        @inline Base.:+{order, dim, T, N}(t::$TensorType{order, dim, T, N}) = $TensorType{order, dim}(+tovector(t))
    end
end

# Binary op(Tensor, Tensor): +, -, .+, .-, .*, ./
for op in (:+, :-, :.+, :.-, :.*, :./)
    for TensorType in (SymmetricTensor, Tensor)
        @eval begin
            @inline function Base.$op{order, dim, T1, T2, N}(t1::$TensorType{order, dim, T1, N}, t2::$TensorType{order, dim, T2, N})
                $TensorType{order, dim}($op(tovector(t1), tovector(t2)))
            end
        end
    end
    @eval begin
        Base.$op{order, dim}(t1::AbstractTensor{order, dim}, t2::AbstractTensor{order, dim}) = $op(promote(t1, t2)...)
    end
end

######################
# Basic constructors #
######################

# zero, rand, ones
for op in (:zero, :rand, :ones)
    for TensorType in (SymmetricTensor, Tensor)
        @eval begin
            @inline Base.$op{order, dim}(Tt::Type{$TensorType{order, dim}}) = $op($TensorType{order, dim, Float64})
            @inline Base.$op{order, dim, T, M}(Tt::Type{$TensorType{order, dim, T, M}}) = $op($TensorType{order, dim, T})
            @inline function Base.$op{order, dim, T}(Tt::Type{$TensorType{order, dim, T}})
                N = n_components($TensorType{order, dim})
                return $TensorType{order, dim}($op(SVector{N, T}))
            end
        end
    end
    # Special case for Vec
    @eval @inline Base.$op{dim}(Tt::Type{Vec{dim}}) = $op(Vec{dim, Float64})

    # zero, rand or ones of a tensor
    @eval @inline Base.$op(t::AllTensors) = $op(typeof(t))
end

# zeros, ones
for (op, el) in ((:zeros, :zero), (:ones, :one))
    for TensorType in (SymmetricTensor, Tensor)
        @eval begin
            @inline Base.$op{order, dim}(Tt::Type{$TensorType{order, dim}}, dims...) = $op($TensorType{order, dim, Float64}, dims...)
            @inline function Base.$op{order, dim, T}(Tt::Type{$TensorType{order, dim, T}}, dims...)
                N = n_components($TensorType{order, dim})
                return $op($TensorType{order, dim, T, N}, dims...)
            end
            @inline Base.$op{order, dim, T, M}(Tt::Type{$TensorType{order, dim, T, M}}, dims...) =
                fill!(Array{$TensorType{order, dim, T, M}}(dims...), $el($TensorType{order, dim, T}))
        end
    end
    @eval @inline Base.$op{dim}(::Type{Vec{dim}}, dims...) = $op(Vec{dim, Float64}, dims...)
end

# diagm
for TensorType in (SymmetricTensor, Tensor)
    @eval begin
        @generated function Base.diagm{dim}(Tt::Type{$(TensorType){2, dim}}, v::Union{AbstractVector, Tuple})
            f = (i,j) -> i == j ? :(v[$i]) : :($(zero(eltype(v))))
            exp = tensor_create(get_type(Tt), f)
            return quote
                $(Expr(:meta, :inline))
                @inbounds t = $exp
                $($TensorType){2, dim}(t)
            end
        end

        @inline Base.diagm{dim, T}(Tt::Type{$(TensorType){2, dim}}, v::T) = v * one($(TensorType){2, dim, T})
    end
end

# one (identity tensor)
for TensorType in (SymmetricTensor, Tensor)
    @eval begin
        @inline Base.one{order, dim}(Tt::Type{$(TensorType){order, dim}}) = one($TensorType{order, dim, Float64})
        @inline Base.one{order, dim, T, M}(Tt::Type{$(TensorType){order, dim, T, M}}) = one($TensorType{order, dim, T})
        @inline Base.one{order, dim, T}(Tt::$TensorType{order, dim, T}) = one($TensorType{order, dim, T})

        @generated function Base.one{order, dim, T}(Tt::Type{$(TensorType){order, dim, T}})
            !(order in (2,4)) && throw(ArgumentError("`one` only defined for order 2 and 4"))
            δ = (i,j) -> i == j ? :($(one(T))) : :($(zero(T)))
            if order == 2
                f = (i,j) -> δ(i,j)
            elseif order == 4 && $TensorType == Tensor
                f = (i,j,k,l) -> δ(i,k) * δ(j,l)
            else # order == 4 && TensorType == SymmetricTensor
                f = (i,j,k,l) -> (δ(i,k) * δ(j,l) + δ(i,l) * δ(j,k)) / 2
            end
            exp = tensor_create(get_base(get_type(Tt)), f)
            return quote
                $(Expr(:meta, :inline))
                $($TensorType){order, dim}($exp)
            end
        end
    end
end

# Tensor from function
@generated function (Tt::Union{Type{Tensor{order, dim}}, Type{SymmetricTensor{order, dim}}}){order, dim}(f::Function)
    _constructor_check(get_base(get_type(Tt)))
    if order == 1
        exp = tensor_create(get_base(get_type(Tt)), (i) -> :(f($i)))
    elseif order == 2
        exp = tensor_create(get_base(get_type(Tt)), (i,j) -> :(f($i, $j)))
    elseif order == 4
        exp = tensor_create(get_base(get_type(Tt)), (i,j,k,l) -> :(f($i, $j, $k, $l)))
    end

    return :(get_base(Tt)($exp))
end
Base.convert{dim, T}(::Type{SVector{dim, T}}, f::Function) = SVector{dim, T}(ntuple(f, Val{dim}))

# Tensor from AbstractArray
@generated function (Tt::Type{Tensor{order, dim}}){order, dim}(data::AbstractArray)
    _constructor_check(get_base(get_type(Tt)))
    n = n_components(Tensor{order,dim})
    return quote
        if length(data) != $n
            throw(ArgumentError("wrong number of vector elements, expected $($n), got $(length(data))"))
        end
        Tensor{order, dim}(SVector{$n}(data))
    end
end

# SymmetricTensor from AbstractArray
@generated function (Tt::Type{SymmetricTensor{order, dim}}){order, dim}(data::AbstractArray)
    _constructor_check(get_base(get_type(Tt)))
    n = n_components(Tensor{order,dim})
    m = n_components(SymmetricTensor{order,dim})
    return quote
        if length(data) != $n && length(data) != $m
            throw(ArgumentError("wrong number of vector elements, expected $($n) or $($m), got $(length(data))"))
        end
        if length(data) == $m
            return SymmetricTensor{order, dim}(SVector{$m}(data))
        end
        S = Tensor{order, dim}(SVector{$n}(data))
        return convert(SymmetricTensor{order, dim}, S)
    end
end

"""
```julia
basevec(::Type{Vec{dim, T}})
basevec(::Type{Vec{dim, T}}, i)
basevec(::Vec{dim, T})
basevec(::Vec{dim, T}, i)
```
Return a tuple with the base vectors corresponding to the dimension `dim` and type
`T`. An optional integer `i` can be used to extract the i:th base vector.
The alias `eᵢ` can also be used, written `e\\_i<TAB>`.

**Example:**

```jldoctest
julia> eᵢ(Vec{2, Float64})
([1.0,0.0],[0.0,1.0])

julia> eᵢ(Vec{2, Float64}, 2)
2-element ContMechTensors.Tensor{1,2,Float64,2}:
 0.0
 1.0
```
"""
@inline function basevec{T}(::Type{Vec{1, T}})
    o = one(T)
    return (Vec{1, T}((o,)), )
end
@inline function basevec{T}(::Type{Vec{2, T}})
    o = one(T)
    z = zero(T)
    return (Vec{2, T}((o, z)),
            Vec{2, T}((z, o)))
end
@inline function basevec{T}(::Type{Vec{3, T}})
    o = one(T)
    z = zero(T)
    return (Vec{3, T}((o, z, z)),
            Vec{3, T}((z, o, z)),
            Vec{3, T}((z, z, o)))
end

@inline basevec{dim}(::Type{Vec{dim}}) = basevec(Vec{dim, Float64})
@inline basevec{dim, T}(::Type{Vec{dim, T}}, i::Int) = basevec(Vec{dim, T})[i]
@inline basevec{dim}(::Type{Vec{dim}}, i::Int) = basevec(Vec{dim, Float64})[i]
@inline basevec{dim, T}(v::Vec{dim, T}) = basevec(typeof(v))
@inline basevec{dim, T}(v::Vec{dim, T}, i::Int) = basevec(typeof(v), i)

const eᵢ = basevec

end # module


using Test, Distributions
@testset "Normal" begin
    @test isa(convert(Normal{Float64}, Float16(0), Float16(1)),
              Normal{Float64})
    @test Inf === logpdf(Normal(0, 0), 0)
    @test -Inf === logpdf(Normal(), Inf)
    @test iszero(logcdf(Normal(0, 0), 0))
    @test iszero(logcdf(Normal(), Inf))
    @test -Inf === logccdf(Normal(0, 0), 0)
    @test iszero(logccdf(Normal(eps(), 0), 0))
    @test -Inf === quantile(Normal(), 0)
    @test iszero(quantile(Normal(), 0.5))
    @test Inf === quantile(Normal(), 1)
    @test -Inf === quantile(Normal(0, 0), 0)
    @test iszero(quantile(Normal(0, 0), 0.75))
    @test Inf === quantile(Normal(0, 0), 1)
    @test -Inf === quantile(Normal(0.25, 0), 0)
    @test 0.25 == quantile(Normal(0.25, 0), 0.95)
    @test Inf === quantile(Normal(0.25, 0), 1)
    @test Inf === cquantile(Normal(), 0)
    @test iszero(cquantile(Normal(), 0.5))
    @test -Inf === cquantile(Normal(), 1)
    @test Inf === cquantile(Normal(0, 0), 0)
    @test iszero(cquantile(Normal(0, 0), 0.75))
    @test -Inf === cquantile(Normal(0, 0), 1)
    @test Inf === cquantile(Normal(0.25, 0), 0)
    @test 0.25 == cquantile(Normal(0.25, 0), 0.95)
    @test -Inf === cquantile(Normal(0.25, 0), 1)
    @test -Inf === invlogcdf(Normal(), -Inf)
    @test isnan(invlogcdf(Normal(), NaN))
    @test Inf === invlogccdf(Normal(), -Inf)
    @test isnan(invlogccdf(Normal(), NaN))
    # test for #996 being fixed
    let d = Normal(0, 1), x = 1.0, ∂x = 2.0
        @inferred cdf(d, ForwardDiff.Dual(x, ∂x)) ≈ ForwardDiff.Dual(cdf(d, x), ∂x * pdf(d, x))
    end
end


# This file is part of IntegerSequences.
# Copyright Peter Luschny. License is MIT.

(@__DIR__) ∉ LOAD_PATH && push!(LOAD_PATH, (@__DIR__))

module RecordSearch

using Nemo

export ModuleRecordSearch
export Records

"""

The type object to construct an iterated search for records in sequences.

* Records
"""
const ModuleRecordSearch = ""

"""

The type object to construct an iterated search for records in sequences.
"""
struct Records
    "function representing the sequence"
    fun::Function
    "search limit OR search length"
    lim::Int
    "true ↦ search all below lim, false ↦ search length items"
    below::Bool
    "true ↦ return index of record, false ↦ return value of record"
    index::Bool
end

Base.iterate(::Records) = (ZZ(1), (ZZ(1), ZZ(0), ZZ(1)))

"""

Return the value or the index of the next record.
"""
function Base.iterate(R::Records, state)
    h, n, s = state
    (R.below ? s : n) >= R.lim && return nothing
    while true
        v = R.fun(n)
        v > h && return (R.index ? n : v, (v, n + 1, s + 1))
        n += 1
    end
end

Base.length(R::Records) = R.lim
Base.eltype(R::Records) = fmpz

end # module

# The module HighlyAbundant is the test case of this module.


# ------------------------------------------------------------
# Limiter functions
# ------------------------------------------------------------

"""
    linear(sL, sR)

linear average of slopes
"""
linear(sL, sR) = 0.5 * (sL + sR)

"""
    vanleer(sL, sR)
    vanleer(sL, s, sR, connect = 2)

van Leer limiter
"""
vanleer(sL, sR) =
    (fortsign(1.0, sL) + fortsign(1.0, sR)) * abs(sL) * abs(sR) /
    (abs(sL) + abs(sR) + 1.e-7)

#--- triangle ---#
function vanleer(sL, s, sR, connect = 2)
    δ = begin
        if connect == 2
            (
                (fortsign(1.0, sL) + fortsign(1.0, s)) * abs(sL) * abs(s) /
                (abs(sL) + abs(s) + 1.e-7),
                (fortsign(1.0, s) + fortsign(1.0, sR)) * abs(s) * abs(sR) /
                (abs(s) + abs(sR) + 1.e-7),
            )
        elseif connect == 3
            (
                (fortsign(1.0, sL) + fortsign(1.0, s)) * abs(sL) * abs(s) /
                (abs(sL) + abs(s) + 1.e-7),
                (fortsign(1.0, s) + fortsign(1.0, sR)) * abs(s) * abs(sR) /
                (abs(s) + abs(sR) + 1.e-7),
                (fortsign(1.0, sL) + fortsign(1.0, sR)) * abs(sL) * abs(sR) /
                (abs(sL) + abs(sR) + 1.e-7),
            )
        end
    end

    id = findmin(abs.(δ))[2]

    return δ[id]
end

"""
    minmod(sL, sR)
    minmod(sL, s, sR, connect = 2)

MinMod limiter
"""
minmod(sL, sR) = 0.5 * (fortsign(1.0, sL) + fortsign(1.0, sR)) * min(abs(sR), abs(sL))

#--- triangle ---#
function minmod(sL, s, sR, connect = 2)
    δ = begin
        if connect == 2
            (
                0.5 * (fortsign(1.0, sL) + fortsign(1.0, s)) * min(abs(s), abs(sL)),
                0.5 * (fortsign(1.0, s) + fortsign(1.0, sR)) * min(abs(sR), abs(s)),
            )
        elseif connect == 3
            (
                0.5 * (fortsign(1.0, sL) + fortsign(1.0, s)) * min(abs(s), abs(sL)),
                0.5 * (fortsign(1.0, s) + fortsign(1.0, sR)) * min(abs(sR), abs(s)),
                0.5 * (fortsign(1.0, sL) + fortsign(1.0, sR)) * min(abs(sR), abs(sL)),
            )
        end
    end

    id = findmin(abs.(δ))[2]

    return δ[id]
end

"""
    superbee(sL, sR)

SuperBee limiter
"""
function superbee(sL, sR)
    if sR >= 0.5 * sL && sR <= 2.0 * sL
        return 0.5 * (fortsign(1.0, sL) + fortsign(1.0, sR)) * max(abs(sL), abs(sR))
    elseif sR < 0.5 * sL && sR > 2.0 * sL
        return (fortsign(1.0, sL) + fortsign(1.0, sR)) * min(abs(sL), abs(sR))
    else
        return 0.0
    end
end

"""
    vanalbaba(sL, sR)

van Albaba limiter
"""
vanalbaba(sL, sR) = (sL^2 * sR + sL * sR^2) / (sL^2 + sR^2 + 1.e-7)

"""
    weno5(wL2, wL1, wN, wR1, wR2)

5th-order WENO-JS interpolation
"""
function weno5(wL2, wL1, wN, wR1, wR2)
    ϵ = 1e-6

    β0 = 13.0 / 12.0 * (wN - 2.0 * wR1 + wR2)^2 + 1.0 / 4.0 * (3.0 * wN - 4.0 * wR1 + wR2)^2
    β1 = 13.0 / 12.0 * (wL1 - 2.0 * wN + wR1)^2 + 1.0 / 4.0 * (wL1 - wR1)^2
    β2 = 13.0 / 12.0 * (wL2 - 2.0 * wL1 + wN)^2 + 1.0 / 4.0 * (wL2 - 4.0 * wL1 + 3.0 * wN)^2

    # right interface
    dr0 = 0.3
    dr1 = 0.6
    dr2 = 0.1

    αr0 = dr0 / (ϵ + β0)^2
    αr1 = dr1 / (ϵ + β1)^2
    αr2 = dr2 / (ϵ + β2)^2

    ωr0 = αr0 / (αr0 + αr1 + αr2)
    ωr1 = αr1 / (αr0 + αr1 + αr2)
    ωr2 = αr2 / (αr0 + αr1 + αr2)

    qr0 = 1.0 / 3.0 * wN + 5.0 / 6.0 * wR1 - 1.0 / 6.0 * wR2
    qr1 = -1.0 / 6.0 * wL1 + 5.0 / 6.0 * wN + 1.0 / 3.0 * wR1
    qr2 = 1.0 / 3.0 * wL2 - 7.0 / 6.0 * wL1 + 11.0 / 6.0 * wN

    wR = ωr0 * qr0 + ωr1 * qr1 + ωr2 * qr2

    # left interface
    dl0 = 0.1
    dl1 = 0.6
    dl2 = 0.3

    αl0 = dl0 / (ϵ + β0)^2
    αl1 = dl1 / (ϵ + β1)^2
    αl2 = dl2 / (ϵ + β2)^2

    ωl0 = αl0 / (αl0 + αl1 + αl2)
    ωl1 = αl1 / (αl0 + αl1 + αl2)
    ωl2 = αl2 / (αl0 + αl1 + αl2)

    ql0 = 11.0 / 6.0 * wN - 7.0 / 6.0 * wR1 + 1.0 / 3.0 * wR2
    ql1 = 1.0 / 3.0 * wL1 + 5.0 / 6.0 * wN - 1.0 / 6.0 * wR1
    ql2 = -1.0 / 6.0 * wL2 + 5.0 / 6.0 * wL1 + 1.0 / 3.0 * wN

    wL = αl0 * ql0 + αl1 * ql1 + αl2 * ql2

    return wL, wR
end


using CauseMap
using PyCall

function getvectors(vec1::AbstractVector, vec2::AbstractVector, libsizemin::Int64, libsizemax::Int64, E::Int64,
    tau_s::Int64, tau_p::Int64, npred::Int64, pred_start::Int64, lib_start::Int64=1)

    shadowmat_dict_vec1, distmat_dict_vec1 = precalc_manif_dists(3:3, 1:1, vec1)
    shadowmat_dict_vec2, distmat_dict_vec2 = precalc_manif_dists(3:3, 1:1, vec2)

    librange12, yval_12 = calcCCM(vec1, vec2,
                                                        shadowmat_dict_vec1, distmat_dict_vec1,
                                                        E, tau_s, tau_p;
                                                        lib_start=lib_start, libsizemin=libsizemin, libsizemax=libsizemax,
                                                        npred=npred, pred_start=pred_start, pred_rand=false)
    librange21, yval_21 = calcCCM(vec2, vec1,
                                                        shadowmat_dict_vec2, distmat_dict_vec2,
                                                        E, tau_s, tau_p;
                                                        lib_start=lib_start, libsizemin=libsizemin, libsizemax=libsizemax,
                                                        npred=npred, pred_start=pred_start, pred_rand=false)
    return yval_12, yval_21
end

function validatevecs(yval_12::AbstractVector, yval_21::AbstractVector; margin=.01,
                                            fname="Para_xmap_Didi_values.txt", col12=2, col21=3)
    col12 = 2
    col21 = 3

    ds_validation = readdlm(fname, '\t', Float64, header=true)[1]
    sum1 = (yval_12 - ds_validation[:, col12])
    sum1 = dot(sum1, sum1)
    sum2 = (yval_21 - ds_validation[:, col21])
    sum2 = dot(sum2, sum2)
    fail1 =  (sum1 > (length(yval_12)*margin))
    fail2 = (sum2 > (length(yval_21)*margin))
    if fail1 | fail2
        if fail1
            println("Para didi xmap off by $sum1")
        end
        if fail2
            println("Didi para xmap off by $sum2")
        end
        error("Error greater than a margin of $margin")
    end
end

E = 3
tau_s = 1
tau_p = 0

libsizemin = 8
libsizemax = 58
pred_start = 13
npred = 60

ds   = readdlm("vr.raw_fixed.txt", '\t', Float64, header=false)
para = ds[:, 2]
didi = ds[:, 3]

para_didi, didi_para = getvectors(para, didi, libsizemin, libsizemax, E,
                                                    tau_s, tau_p, npred, pred_start)
println("Starting Para Didi test")
validatevecs(para_didi, didi_para)
println("Test passed!")


"""
    Disjunction{N, VT<:AbstractVector{<:Tuple{<:Predicate, <:AbstractVector{Int}}}} <: Predicate{N}

A disjunction of predicates of arity `N`.

### Fields

- `disjuncts` -- vector of disjuncts
"""
struct Disjunction{N, VT<:AbstractVector{<:Tuple{<:Predicate, <:AbstractVector{Int}}}} <: Predicate{N}
    disjuncts::VT

    function Disjunction(disjuncts::VT; N::Int=maximum([length(v) for (p, v) in disjuncts])
                        ) where {VT<:AbstractVector{<:Tuple{<:Predicate, <:AbstractVector{Int}}}}
        return new{Val{N}, VT}(disjuncts)
    end
end

# constructor for unary disjunction with only unary disjuncts
function Disjunction(disjuncts::AbstractVector{<:Predicate{Val{1}}})
    return Disjunction([(d, [1]) for d in disjuncts])
end

function Base.:(==)(d1::Disjunction, d2::Disjunction)
    return d1.disjuncts == d2.disjuncts
end

# function-like evaluation
@inline function (d::Disjunction)(args...)
    evaluate(d, args...)
end

function evaluate(d::Disjunction, args...)
    assert_same_length(d, args...)
    @inbounds for (conjunct, n_args) in d.disjuncts
        if evaluate(conjunct, args[n_args]...)
            return true
        end
    end
    return false
end


#import Base:isapprox, abs, angle

# isapprox, norm, norm2, normalize, abs, ortho, rotate, angle

S2Point(x, y, z) = [x, y, z]
S2Point(lat::Number, lon::Number) = let
    x = cos(deg2rad(lat)) * cos(deg2rad(lon))
    y = cos(deg2rad(lat)) * sin(deg2rad(lon))
    z = sin(deg2rad(lat))
    S2Point(x, y, z)
end
S2Point(latlon::String) = let args = parse.(Float64, split(latlon, ':'))
    S2Point(args[1], args[2])
end

# Origin of the coordinate system, [0,0,0]
ORIGIN = S2Point(0, 0, 0)

# Direction of the x-axis.
X_POS = S2Point(1, 0, 0)

# Opposite direction of the x-axis.
X_NEG = S2Point(-1, 0, 0)

# Direction of the y-axis.
Y_POS = S2Point(0, 1, 0)

# Opposite direction of the y-axis.
Y_NEG = S2Point(0, -1, 0)

# Direction of the z-axis.
Z_POS = S2Point(0, 0, 1)

# Opposite direction of the z-axis.
Z_NEG = S2Point(0, 0, -1)

md"""
Verify that a vector is a valid S2 point
"""
verifyPoint(a::Vector) = (length(a) == 3 && eltype(a) <: Number) || throw(ArgumentError("Value is not a valid S2Point: $a"))

coordinates(a::Vector) = verifyPoint(a) && (a[1], a[2], a[3])

md"""
Bounding shapes of all kinds are found using the same function `bound`. The first
argument determines what kind of bound you want.
"""
bound(::Type{S2Cap}, a::Vector) = verifyPoint(a) && S2Cap(a, S1ChordAngle(0))
bound(::Type{S2LatLngRect}, a::Vector) = verifyPoint(a) && fromPoint(S2LatLngRect, S2LatLng(a))

norm2(a::Vector) = sum(a.^2)

function distance2(a::Vector, b::Vector)
    verifyPoint(a)
    verifyPoint(b)
    dx = a - b
    return dx ⋅ dx
end

distance(a::Vector, b::Vector) = sqrt(distance2(a, b))

md"""
The scalar triple product is `a ⋅ (b × c)` but more efficiently computed.
"""
function scalarTripleProduct(a::Vector, b::Vector)
    x = b[2] * c[3] - b[3] * c[2]
    y = b[3] * c[1] - b[1] * c[3]
    z = b[1] * c[2] - b[2] * c[1]
    return a[1] * x + a[2] * y + a[3] * z
end

function ortho(a::Vector)
    k = largestAbsComponent(a)
    if k == 1
        normalize(a × Z_POS)
    elseif k == 2
        normalize(a × X_POS)
    elseif k == 3
        normalize(a × Y_POS)
    else
        throw(ArgumentError("Maximum component must be 1, 2, or 3"))
    end
end

largestAbsComponent(a::Vector) = findmax(coordinates(a))[2]

md"""
Rotates this point around an arbitrary axis. The result is normalized.

@param axis point around which rotation should be performed.
@param radians radians to rotate the point counterclockwise around the given axis.
"""
function rotate(p::Vector, axis::Vector, radians::Real)::Vector{Float64}
    verifyPoint(p)
    verifyPoint(axis)

    p = normalize(p)
    axis = normalize(axis)
    pointOnAxis = (p ⋅ axis) * axis
    axisToPoint = p - pointOnAxis
    axisToPointNormal = axis × axisToPoint
    axisToPoint = cos(radians) * axisToPoint
    axisToPointNormal = sin(radians) * axisToPointNormal
    return normalize(axisToPoint + axisToPointNormal + pointOnAxis)
end

md"""
The angle in radians between two points
"""
Base.angle(a::Vector, b::Vector) = atan(norm(a × b), a ⋅ b)

md"""
Return a vector `c` that is orthogonal to the given unit-length vectors `a` and `b`. This
function is similar to `a × b` except that it does a better job of ensuring
orthogonality when `a` is nearly parallel to `b`, and it returns a non-zero result even
when `a == b` or `a == -b`.

It satisfies the following properties (RCP == robustCrossProd):

1. `RCP(a,b) != 0` for all `a, b`
2. `RCP(b,a) == -RCP(a,b)` unless `a == b` or `a == -b`
3. `RCP(-a,b) == -RCP(a,b)` unless `a == b` or `a == -b`
4. `RCP(a,-b) == -RCP(a,b)` unless `a == b` or `a == -b`
"""
function robustCrossProd(a::Vector, b::Vector)
    (verifyPoint(a) && verifyPoint(b)) || throw(ArgumentError("Need valid points as args"))
    # The direction of `a × b` becomes unstable as `a + b` or `a - b`
    # approaches zero.  This leads to situations where `a × b` is not
    # very orthogonal to `a` and/or `b`.  We could fix this using Gram-Schmidt,
    # but we also want `robustCrossProd(b, a) == -robustCrossProd(a, b)`.
    #
    # The easiest fix is to just compute the cross product of `(b+a)` and `(b-a)`.
    # Mathematically, this cross product is exactly twice the cross product of
    # `a` and `b`, but it has the numerical advantage that `b+a` and `b-a`
    # are always perpendicular (since `a`and `b` are unit length).  This
    # yields a result that is nearly orthogonal to both `a` and `b` even if
    # these two values differ only in the lowest bit of one component.
    # assert (isUnitLength(a) && isUnitLength(b));
    x = (a + b) × (b - a)
    if x != ORIGIN
        return x
    else
        return ortho(a)
    end
end

#md"Returns the R3 vector cross product of `p1` and `p2`"
#function LinearAlgebra.cross(p1::Vector,  p2::Vector)
#    (verifyPoint(a) && verifyPoint(b)) || throw(ArgumentError("Need valid points as args"))
#    return S2Point(
#        p1.y * p2.z - p1.z * p2.y,
#        p1.z * p2.x - p1.x * p2.z,
#        p1.x * p2.y - p1.y * p2.x)
#end


function img2colConvolve(cLayer::CL, Ai::AbstractArray{T,N}) where {T,N, CL <: ConvLayer}

    Aip = padding(cLayer, Ai)
    axBT = axes(cLayer.B)
    axB = axBT[1:end-2]
    axBend = axBT[end]
    Z = col2img(cLayer.K * img2col(Aip), cLayer.outputS) .+
            view(cLayer.B, axB..., 1, axBend)

    actFun = cLayer.actFun
    Ao = eval(:($actFun($Z)))

    return Dict(:Z => Z, :A => Ao)
end #function img2colConvolve(cLayer::CL

export img2colConvolve

### dimg2colConvolve!

export dimg2colConvolve!

function dimg2colConvolve!(
    cLayer::Conv1D,
    Ai::AbstractArray{T1,3},
    dAi::AbstractArray{T2,3},
    dZ::AbstractArray{T3,3},
) where {T1,T2,T3}

    padS = paddingSize(cLayer, Ai)
    Aip = padding(cLayer, Ai)
    Aipv = img2col(Aip)
    dZv = img2col(dZ)

    n_Hi, c, m = size(Aip)
    s_H = cLayer.s
    f_H = cLayer.f
    n_H = (n_Hi - f_H) ÷ s_H + 1

    dAi .= col2img(cLayer.K' * dZv, (n_Hi,c,m))[1+padS[1]:end-padS[2], :, :]
    cLayer.dK = dZv * Aipv'
    cLayer.dB = sum(permutedims(dZ, [1, 3, 2]), dims = 1:2)

    return nothing
end # function dimg2colConvolve!(
    #     cLayer::Conv1D,
    #     Ai::AbstractArray{T,3},
    #     dA::AbstractArray{T,3},
    #     dZ::AbstractArray{T,3},
    # ) where {T}

function dimg2colConvolve!(
    cLayer::Conv2D,
    Ai::AbstractArray{T1,4},
    dAi::AbstractArray{T2,4},
    dZ::AbstractArray{T3,4},
) where {T1,T2,T3}

    padS = paddingSize(cLayer, Ai)
    Aip = padding(cLayer, Ai)
    Aipv = img2col(Aip)
    dZv = img2col(dZ)
    n_Hi, n_Wi, c, m = size(Aip)
    s_H, s_W = cLayer.s
    f_H, f_W = cLayer.f
    n_H = (n_Hi - f_H) ÷ s_H + 1
    n_W = (n_Wi - f_W) ÷ s_W + 1

    dAi .= col2img(cLayer.K' * dZv, (n_Hi,n_Wi,c,m))[1+padS[1]:end-padS[2], 1+padS[3]:end-padS[4], :, :]
    cLayer.dK = dZv * Aipv'
    cLayer.dB = sum(permutedims(dZ, [1, 2, 4, 3]), dims = 1:3)

    return nothing
end # function dimg2colConvolve!(
    #     cLayer::Conv2D,
    #     Ai::AbstractArray{T,4},
    #     dA::AbstractArray{T,4},
    #     dZ::AbstractArray{T,4},
    # ) where {T}

function dimg2colConvolve!(
    cLayer::Conv3D,
    Ai::AbstractArray{T1,5},
    dAi::AbstractArray{T2,5},
    dZ::AbstractArray{T3,5},
) where {T1,T2,T3}

    padS = paddingSize(cLayer, Ai)
    Aip = padding(cLayer, Ai)
    Aipv = img2col(Aip)
    dZv = img2col(dZ)
    n_Hi, n_Wi, n_Di, c, m = size(Aip)
    s_H, s_W, s_D = cLayer.s
    f_H, f_W, f_D = cLayer.f
    n_H = (n_Hi - f_H) ÷ s_H + 1
    n_W = (n_Wi - f_W) ÷ s_W + 1
    n_D = (n_Di - f_D) ÷ s_D + 1

    dAi .= col2img(cLayer.K' * dZv, (n_Hi, n_Wi, n_Di, c, m))[1+padS[1]:end-padS[2],
        1+padS[3]:end-padS[4],
        1+padS[5]:end-padS[6],
        :,
        :,
    ]
    cLayer.dK = dZv * Aipv'
    cLayer.dB = sum(permutedims(dZ, [1, 2, 3, 5, 4]), dims = 1:4)

    return nothing
end # function dimg2colConvolve!(
    #     cLayer::Conv3D,
    #     Ai::AbstractArray{T,5},
    #     dA::AbstractArray{T,5},
    #     dZ::AbstractArray{T,5},
    # ) where {T}


using Test
using LinearAlgebra, SparseArrays, Random
using LuxurySparse

Random.seed!(2)

p1 = IMatrix{4}()
sp = sprand(ComplexF64, 4, 4, 0.5)
ds = rand(ComplexF64, 4, 4)
pm = PermMatrix([2, 3, 4, 1], randn(4))
v = [0.5, 0.3im, 0.2, 1.0]
dv = Diagonal(v)

@testset "invdet" begin
    ####### linear algebra  ######
    @test inv(p1) == inv(Matrix(p1))
    @test det(p1) == det(Matrix(p1))
    @test diag(p1) == diag(Matrix(p1))
    @test logdet(p1) == 0
    @test inv(pm) == inv(Matrix(pm))

    for m in [pm, sp, p1, dv]
        @test !(m |> isdense)
        @test !(m' |> isdense)
        @test !(transpose(m) |> isdense)
    end
    for m in [ds, v]
        @test m |> isdense
        @test m' |> isdense
        @test transpose(m) |> isdense
    end
end

@testset "multiply" begin
    for source_ in [p1, sp, ds, dv, pm]
        for target in [p1, sp, ds, dv, pm]
            for source in [source_, source_', transpose(source_)]
                lres = source * target
                rres = target * source
                flres = Matrix(source) * Matrix(target)
                frres = Matrix(target) * Matrix(source)
                @test lres ≈ flres
                @test rres ≈ frres
                if !(target === p1 || parent(source) === p1)
                    @test eltype(lres) == eltype(flres)
                    @test eltype(rres) == eltype(frres)
                end
                if !(target |> isdense) && !(parent(source) |> isdense)
                    @test lres |> isdense == false
                    @test lres |> isdense == false
                end
            end
        end
    end
end

@testset "mul-vector" begin
    # permutation multiply
    lres = transpose(conj(v)) * pm  #! v' not realized!
    rres = pm * v
    flres = v' * Matrix(pm)
    frres = Matrix(pm) * v
    @test lres == flres
    @test rres == frres
    @test eltype(lres) == eltype(flres)
    @test eltype(rres) == eltype(frres)

    # IMatrix
    @test v' * p1 == v'
    @test p1 * v == v
end

@testset "sparse-diag" begin
    N = 100
    dg = Diagonal(randn(ComplexF64, 1000))
    sp = SparseMatrixCSC(dg)
    @test sp * dg == sp * sp
    @test dg * sp == sp * sp
end

@testset "randn" begin
    Random.seed!(2)
    T = ComplexF64
    for m in [sprand(T, 5, 5, 0.5), pmrand(T, 5), Diagonal(randn(T, 5))]
        zm = zero(m)
        @test zm ≈ zeros(T, 5, 5)
        rand!(zm)
        @test !(zm ≈ zeros(T, 5, 5))
        zm = zero(m)
        randn!(zm)
        @test !(zm ≈ zeros(T, 5, 5))
    end
end

@testset "multiply rectangular matrix" begin
    pm = pmrand(4)
    sp = sparse(reshape([1.0 2 3 4], 4, 1))
    res = pm * sp
    @test res ≈ Matrix(pm) * Matrix(sp)
end

@testset "findnz" begin
    for m in [p1, sp, ds, dv, pm]
        for _m in [m, staticize(m)]
            out = zeros(eltype(m), size(m)...)
            for (i, j, v) in zip(LuxurySparse.findnz(_m)...)
                out[i, j] = v
            end
            @test out ≈ m
        end
    end
end


module RayCaster

function cast_ray_continous_world(obstacle_tile_map::AbstractArray{Bool, 2}, x_start::T1, y_start::T1, cos_theta::T2, sin_theta::T2) where {T1, T2}
    i_start = floor(Int, x_start) + 1
    j_start = floor(Int, y_start) + 1

    delta_euclidean_per_unit_x = abs(1 / cos_theta)
    delta_euclidean_per_unit_y = abs(1 / sin_theta)

    hit_dimension = 1

    if cos_theta < zero(T2)
        delta_i = -1
        delta_x_world_units_to_exit_start_tile = x_start - i_start + 1
    else
        delta_i = 1
        delta_x_world_units_to_exit_start_tile = i_start - x_start
    end

    delta_euclidean_x = delta_x_world_units_to_exit_start_tile * delta_euclidean_per_unit_x

    if sin_theta < zero(T2)
        delta_j = -1
        delta_y_world_units_to_exit_start_tile = y_start - j_start + 1
    else
        delta_j = 1
        delta_y_world_units_to_exit_start_tile = j_start - y_start
    end

    delta_euclidean_y = delta_y_world_units_to_exit_start_tile * delta_euclidean_per_unit_y

    i_stop = i_start
    j_stop = j_start

    while !obstacle_tile_map[i_stop, j_stop]

        if (delta_euclidean_x <= delta_euclidean_y)
            delta_euclidean_x += delta_euclidean_per_unit_x
            i_stop += delta_i
            hit_dimension = 1
        else
            delta_euclidean_y += delta_euclidean_per_unit_y
            j_stop += delta_j
            hit_dimension = 2
        end

    end

    return i_stop, j_stop, hit_dimension, delta_x_world_units_to_exit_start_tile, delta_y_world_units_to_exit_start_tile
end

function cast_ray_discrete_world(obstacle_tile_map::AbstractArray{Bool, 2}, i_start_world_units, j_start_world_units, delta_i_world_units, delta_j_world_units, world_units_per_tile_unit)
    I = typeof(i_start_world_units)

    i_start_tile_units = i_start_world_units ÷ world_units_per_tile_unit + one(I)
    j_start_tile_units = j_start_world_units ÷ world_units_per_tile_unit + one(I)

    delta_euclidean_per_world_unit_i = abs(delta_j_world_units)
    delta_euclidean_per_world_unit_j = abs(delta_i_world_units)

    delta_euclidean_per_tile_unit_i = world_units_per_tile_unit * delta_euclidean_per_world_unit_i
    delta_euclidean_per_tile_unit_j = world_units_per_tile_unit * delta_euclidean_per_world_unit_j

    if delta_i_world_units < zero(I)
        delta_i_tile_units = -one(I)
        delta_i_world_units_to_exit_start_tile = (i_start_world_units - (i_start_tile_units - one(I)) * world_units_per_tile_unit)
    else
        delta_i_tile_units = one(I)
        delta_i_world_units_to_exit_start_tile = (i_start_tile_units * world_units_per_tile_unit - i_start_world_units + one(I))
    end

    delta_euclidean_i = delta_i_world_units_to_exit_start_tile * delta_euclidean_per_world_unit_i

    if delta_j_world_units < zero(I)
        delta_j_tile_units = -one(I)
        delta_j_world_units_to_exit_start_tile = (j_start_world_units - (j_start_tile_units - one(I)) * world_units_per_tile_unit)
    else
        delta_j_tile_units = one(I)
        delta_j_world_units_to_exit_start_tile = (j_start_tile_units * world_units_per_tile_unit - j_start_world_units + one(I))
    end

    delta_euclidean_j = delta_j_world_units_to_exit_start_tile * delta_euclidean_per_world_unit_j

    i_stop_tile_units = i_start_tile_units
    j_stop_tile_units = j_start_tile_units
    hit_dimension = 1

    while !obstacle_tile_map[i_stop_tile_units, j_stop_tile_units]

        if (delta_euclidean_i <= delta_euclidean_j)
            delta_euclidean_i += delta_euclidean_per_tile_unit_i
            i_stop_tile_units += delta_i_tile_units
            hit_dimension = 1
        else
            delta_euclidean_j += delta_euclidean_per_tile_unit_j
            j_stop_tile_units += delta_j_tile_units
            hit_dimension = 2
        end

    end

    return i_stop_tile_units, j_stop_tile_units, hit_dimension, delta_i_world_units_to_exit_start_tile, delta_j_world_units_to_exit_start_tile
end

end


import GeometryTypes: type_immutable, with_immutable, make_immutable
import GeometryTypes: gjk0, ⊖, support_vector_max

@testset "gjk" begin

    @testset "gjk examples" begin
        @testset "two simplices" begin
            c1 = Simplex(Vec(-1.))
            c2 = Simplex(Vec(4.))
            @test gjk(c1,c2) ≈5
            @test min_euclidean(c1,c2) ≈5

            c1 = Simplex(Vec(-1.,0.,0.))
            c2 = Simplex(Vec(4.,0.,0.))
            @test gjk(c1,c2) ≈5
            @test min_euclidean(c1,c2) ≈5

            c1 = FlexibleConvexHull([Vec(0.,0.), Vec(0.,1.), Vec(1.,0.),Vec(1.,1.)])
            c2 = Simplex(Vec(4.,0.5))
            @test gjk(c1, c2) ≈ 3
            @test min_euclidean(c1,c2) ≈ 3

            pt1 = Vec(1.,2.,3.)
            pt2 = Vec(3.,4.,5.)
            @test gjk(pt1, pt2) ≈norm(pt1-pt2)
            @test min_euclidean(pt1, pt2) ≈norm(pt1-pt2)
        end

        @testset "gjk intersecting lines" begin
            c1 = Simplex(Vec(1.,1.), Vec(1., 2.))
            @test gjk(c1, c1) == 0.
            @test min_euclidean(c1,c1) == 0.

            c2 = Simplex(Vec(1.,1.), Vec(10., 2.))
            @test gjk(c1, c2) == 0.
            @test min_euclidean(c1,c2) == 0.

            c3 = Simplex(Vec(0., 1.), Vec(2.,2.))
            md = vertices(c1 ⊖ c3)
            @test md == [Vec(1.0,0.0),Vec(-1.0,-1.0),Vec(1.0,1.0),Vec(-1.0,0.0)]
            @test gjk0(FlexibleConvexHull(md)) == (Vec(0., 0.), 0.)
            @test gjk(c1, c3) == 0.
            @test min_euclidean(c1,c3) == 0.

        end

        @testset "Cube" begin
            c = HyperCube(Vec(0.5,0.5,0.5), 1.)
            @test min_euclidean(Vec(2.,2.,2.), c) ≈ gjk(Vec(2.,2.,2.), c) ≈ √(3/4)

            s = Simplex(Vec(1., 0.5, 0.5), Vec(1.,2.,3.))
            @test 0 <= min_euclidean(s, c) <= 1e-14
            @test 0 <= gjk(s, c) <= 1e-14

            s = Simplex(Vec(2.,2.,2.), Vec(2.,3.,2.), Vec(2.,2.,7.), Vec(3.,4.,5.))
            @test min_euclidean(s,c) ≈ gjk(s, c) ≈ √(3/4)
        end

    end

    @testset "support_vector_max" begin
        r = HyperRectangle(Vec(-0.5, -1.), Vec(1., 2.))
        @test support_vector_max(r, Vec(1.,0.)) == (Vec(0.5,-1.), 0.5)
        @test support_vector_max(r, Vec(2.,0.)) == (Vec(0.5,-1.), 1.)
        @test support_vector_max(r, Vec(-1.,0.)) == (Vec(-0.5,-1.), 0.5)
        @test support_vector_max(r, Vec(0., 1.)) == (Vec(-0.5,1.), 1.)
        @test support_vector_max(r, Vec(1., 1.)) == (Vec(0.5,1.), 1.5)
        @test support_vector_max(FlexibleConvexHull(r), Vec(1., 1.)) == (Vec(0.5,1.), 1.5)

        c1 = Simplex(Vec(1.,1.), Vec(1., 2.))
        c3 = Simplex(Vec(0., 1.), Vec(2.,2.))
        md = c1 ⊖ c3
        fh = FlexibleConvexHull(md)
        for v in [Vec(1.,0.), Vec(12.,-10.), Vec(0.,-1.), Vec(1.,1.)]
            v_md, s_md = support_vector_max(md, v)
            v_fh, s_fh = support_vector_max(fh, v)
            @test s_md ≈ s_fh
            @test v_md ≈ v_fh
        end
    end

    @testset "make immutable" begin
        T = Vec{2, Float64}
        n = 3
        S = Simplex{n, T}
        FS = FlexibleSimplex{T}
        fs = FS([Vec(0.0,0.0), Vec(0.0,1.0), Vec(1.0,0.0)])
        s = S((Vec(0.0,0.0), Vec(0.0,1.0), Vec(1.0,0.0)))
        @test (@inferred type_immutable(FS, Val{3})) == S
        @test type_immutable(fs) == S
        @test (@inferred type_immutable(fs, Val{3})) == S
        @test make_immutable(fs) == s
        @test (@inferred make_immutable(fs, Val{3})) == s
        @test with_immutable(identity, fs) == s
    end

end


#
#      Gr(k,V) - The space of all k-dimensional linear subspaces
#           of the n-dimensional vector space C^n
#  A point on this manifold is a vector subspace of dimension k embedded in C^n
#  represented by a n×k matrix with full rank.
#  A point in an tangential space is a matrix.
#
import LinearAlgebra: norm, nullspace, det, tr, qr, triu, rank, svd, Diagonal, diag
import Base: exp, log, show, cat, rand, Matrix, real, atan
export Grassmannian, GrPoint, GrTVector, getValue
export distance, dot, exp, log, manifoldDimension, norm, retraction, inverseRetraction
export parallelTransport, randomTVector, randomMPoint, validateMPoint, validateTVector
export projection, zeroTVector
#
# Type definitions
#

@doc doc"""
    Grassmannian <: Manifold

The manifold $\mathcal M = \mathrm{Gr}(k,n)$ of the set of k-dimensional
subspaces in $\mathbb{K}^{n}$ represented by $n\times k$
orthonormal matrices, that are either real- or complex-valued.

# Abbreviation
`Gr`

# Constructor
    Grassmannian(k, n[, d=Float64])

generates the manifold $\mathrm{Gr}(k,n)$ where the integer `n` is the
number of rows and `k` is the number of columns of the matrices and the optional
parameter `d` sets the `DataType` of the matrix entries.
"""
struct Grassmannian{T<:Union{U, Complex{U}} where U<:AbstractFloat} <: Manifold
  name::String
  dimensionsubspace::Int
  dimensionvecspace::Int
  abbreviation::String
  function Grassmannian{T}(dimensionsubspace::Int, dimensionvecspace::Int) where T<:Union{U, Complex{U}} where U<:AbstractFloat
    if dimensionsubspace > dimensionvecspace
      throw(ErrorException("dimensionsubspace can't be bigger than dimensionvecspace: $dimensionsubspace > $dimensionvecspace"))
    else
     new("of the set of $dimensionsubspace-dimensional subspaces in K$dimensionvecspace in $T", dimensionsubspace, dimensionvecspace,"Gr($dimensionsubspace,$dimensionvecspace)")
    end
  end
end
Grassmannian(dimensionsubspace::Int, dimensionvecspace::Int, D::DataType=Float64) = Grassmannian{D}(dimensionsubspace, dimensionvecspace)

@doc doc"""
    GrPoint <: MPoint

A point $x$ on the manifold $\mathcal M = \mathrm{Gr}(k,n)$ is an orthonormal
matrix of size $ n×k$. The matrix is a numerical representation of the vector
subspace its columns span.

# Constructor

    GrPoint(p)

where `p::Matrix` is an orthonormal matrix of dimension $n×k$.
"""
struct GrPoint{T<:Union{U, Complex{U}} where U<:AbstractFloat} <: MPoint
  value::Matrix{T}
  GrPoint{T}(value::Matrix{T}) where T<:Union{U, Complex{U}} where U<:AbstractFloat = new(value)
end
GrPoint(value::Matrix{T}) where T<:Union{U, Complex{U}} where U<:AbstractFloat = GrPoint{T}(value)
getValue(x::GrPoint) = x.value;

@doc doc"""
    GrTVector <: TVector

A tangent vector $\xi \in T_x\mathcal M$ on the manifold
$\mathcal M = \mathrm{Gr}(k,n)$. The tangent space is given by as

```math
T_x\mathrm{Gr}(k,n) = \bigl\{
\xi \in \mathbb{K}^{n\times k} \ \big|
\ {\bar ξ}^\mathrm{T}x = {\bar x}^\mathrm{T}ξ = 0_{k×k} \bigr\}.
```

# Constructor

    GrTVector(ξ)

where `ξ` is an $n\times k$ `Matrix` that satisfies the above.
"""
struct GrTVector{T<:Union{U, Complex{U}} where U<:AbstractFloat} <: TVector
  value::Matrix{T}
  GrTVector{T}(value::Matrix{T}) where T<:Union{U, Complex{U}} where U<:AbstractFloat = new(value)
end
GrTVector(value::Matrix{T}) where T<:Union{U, Complex{U}} where U<:AbstractFloat = GrTVector{T}(value)
getValue(ξ::GrTVector) = ξ.value;

@traitimpl IsMatrixM{Grassmannian}
@traitimpl IsMatrixP{GrPoint}
@traitimpl IsMatrixTV{GrTVector}

@doc doc"""
    dot(M,x,ξ,ν)

compute the Riemannian inner product for two [`GrTVector`](@ref)s `ξ` and `ν`
from $T_x\mathcal M$ of the [`Grassmannian`](@ref) manifold `M` given by

$\langle \xi, \nu \rangle_x = \operatorname{Re}(\operatorname{tr}(\xi^{\mathrm{T}} ν)),$
"""
function dot(M::Grassmannian{T}, x::GrPoint{T}, ξ::GrTVector{T}, ν::GrTVector{T})::Float64 where T<:Union{U, Complex{U}} where U<:AbstractFloat
  return real(tr(getValue(ξ)' * getValue(ν)))
end

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

compute the Riemannian distance on [`Grassmannian`](@ref) manifold `M`$= \mathrm{Gr}(k,n)$ embedded in
$\mathbb R$. Let $USV = {\bar x}^\mathrm{T}y$ denote the SVD decomposition of
$x'y$. Then we compute

$d_{\mathrm{GR}(k,n)}(x,y) = \operatorname{norm}(\operatorname{Re}(b))$

where

$b_{i}=\begin{cases} 0 & \text{if} \; S_i≧1 \\ \operatorname{acos}(S_i) & \, \text{if} \; S_i<1 \end{cases}.$
"""
function distance(M::Grassmannian{T},x::GrPoint{T},y::GrPoint{T})::Float64 where T<:Union{U, Complex{U}} where U<:AbstractFloat
  if x==y
	return 0
  else
    z = getValue(x)'*getValue(y)
    a = svd(z).S
    b = zero(a)
    b[a.<1] = acos.(a[a.<1])
	return norm(real(b), 2)
  end
end

@doc doc"""
    exp(M,x,ξ,[t=1.0])

compute the exponential map on the [`Grassmannian`](@ref) manifold `M`$= \mathrm{Gr}(k,n)$ with
respect to the [`GrPoint`](@ref) `x` and the [`GrTVector`](@ref) `ξ`, which can
be shortened with `t` to `tξ`. Let $USV = t\xi$ denote the SVD decomposition of
$t\xi$. Then we compute

$A = x\cdot V\cdot \cos(S)\cdot {\bar V}^\mathrm{T} + U \cdot \sin(S) \cdot {\bar V}^\mathrm{T},$

where cosine and sine are applied element wise to the diagonal entries of $S$.
The resulting point $y$ of the exponential map is then the matrix $Q$ of the
QR decomposition $A=QR$ of $A$.
"""
function exp(M::Grassmannian{T},x::GrPoint{T},ξ::GrTVector{T},t::Float64=1.0) where T<:Union{U, Complex{U}} where U<:AbstractFloat
  ξ = t*ξ
  d = svd(getValue(ξ))
  Y = getValue(x) * d.V * cos(Diagonal(d.S)) * (d.V)' + (d.U) * sin(Diagonal(d.S)) * (d.V)'
  GrPoint( Matrix( qr(Y).Q ) )
end

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

return a [`GrTVector`](@ref) `ξ` of the tagent space $T_x\mathrm{Gr}(k,n)$
with which the [`GrPoint`](@ref) `y` can be reached on the
[`Grassmannian`](@ref) manifold `M`by the
[`retraction`](@ref) from the [`GrPoint`](@ref) `x` after time 1.
The formula reads

$\xi = y \cdot (x^{\mathrm{T}}y)^{-1} - x$

This function is implemented only for the case $\mathbb{K}=\mathbb{R}$.
This is also the standard retraction.
"""
function inverseRetraction(M::Grassmannian{T},x::GrPoint{T},y::GrPoint{T}) where T<:AbstractFloat
  U = getValue(y)/(transpose(getValue(x))*getValue(y))
  A = U-getValue(x)
  GrPoint{T}(A)
end

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

compute the logarithmic map on the [`Grassmannian`](@ref) manifold
$\mathcal M=\mathrm{Gr}(k,n)$, i.e. the [`GrTVector`](@ref) whose corresponding
[`geodesic`](@ref) starting from [`GrPoint`](@ref) `x` reaches the
[`GrPoint`](@ref) `y` after time 1 on the [`Grassmannian`](@ref) manifold `M`.
The formula reads

$\log_x y = V\cdot \operatorname{atan}(S) \cdot {\bar U}^\mathrm{T}$

where $U$ and $V$ are the unitary matrices and $S$ is a diagonal matrix containing the
singular values of the SVD-decomposition of

$USV = ({\bar y}^\mathrm{T}x)^{-1} ( {\bar y}^\mathrm{T} - {\bar y}^\mathrm{T}x{\bar x}^\mathrm{T} )$

and the $\operatorname{atan}$ is meant elementwise.
"""
function log(M::Grassmannian{T},x::GrPoint{T},y::GrPoint{T}) where T<:Union{U, Complex{U}} where U<:AbstractFloat
  Z = getValue(y)'*getValue(x)
  if det(Z)≠0
   W = Z\(getValue(y)'-Z*getValue(x)')
   d = svd(W, full = false)
   A = (d.V)*atan(Diagonal(svd(W, full = false).S))*(d.U)'
   GrTVector{T}(A)
  else
   throw( ErrorException("The points $x and $y are antipodal, thus these input parameters are invalid.") )
  end
end

@doc doc"""
    manifoldDimension(x)

return the dimension of the [`Grassmannian`](@ref) manifold `M`$= \mathrm{Gr}(k,n)$, the
[`GrPoint`](@ref) `x`, itself embedded in $\mathbb{K}^{n\times k}$, belongs to.
The dimension for $\mathbb{K}=\mathbb{R}$ is defined by

$k(n-k)$

and for $\mathbb{K}=\mathbb{C}$

$2k \cdot (n-k).$
"""
function manifoldDimension(x::GrPoint{T}) where T<:AbstractFloat
	return Int(size(getValue(x),2) * (size(getValue(x),1) - size(getValue(x),2)))
end

function manifoldDimension(x::GrPoint{T}) where T<:Complex{U} where U<:AbstractFloat
	return Int(2 * size(getValue(x),2) * (size(getValue(x),1) - size(getValue(x),2)))
end

@doc doc"""
    manifoldDimension(M)

return the dimension of the [`Grassmannian`](@ref) manifold `M`.
The dimension for $\mathbb{K}=\mathbb{R}$ is defined by

$k(n-k)$

and for $\mathbb{K}=\mathbb{C}$

$2k \cdot (n-k).$
"""
function manifoldDimension(M::Grassmannian{T}) where T<:AbstractFloat
	return Int(M.dimensionsubspace * (M.dimensionvecspace - M.dimensionsubspace))
end

function manifoldDimension(M::Grassmannian{T}) where T<:Complex{U} where U<:AbstractFloat
	return Int(2 * M.dimensionsubspace * (M.dimensionvecspace - M.dimensionsubspace))
end

@doc doc"""
    norm(M,x,ξ)

compute the norm of the [`GrTVector`](@ref) `ξ` in the tangent space
$T_x\mathcal M$ at [`GrPoint`](@ref) `x` of the [`Grassmannian`](@ref) manifold `M`.

$\lVert \xi \rVert_x = \sqrt{\sum_{i,j=0}^n \xi_{ij}^2}$

where $\xi_{ij}$ are the entries of the matrix `ξ`, i.e. the norm is given by
the Frobenius norm of `ξ`.
"""
function norm(M::Grassmannian{T}, x::GrPoint{T}, ξ::GrTVector{T})::Float64 where T<:Union{U, Complex{U}} where U<:AbstractFloat
  norm(getValue(ξ))
end

@doc doc"""
    parallelTransport(M,x,y,ξ)

compute the paralllel transport of the [`GrTVector`](@ref) `ξ` from
the tangent space $T_x\mathcal M$ at [`GrPoint`](@ref) `x` to
$T_y\mathcal M$ at [`GrPoint`](@ref) `y` on the [`Grassmannian`](@ref) manifold `M` provided
that the corresponding [`geodesic`](@ref) $g(\cdot;x,y)$ is unique.
The formula reads

$P_{x\to y}(\xi) = \operatorname{proj}_{\mathcal M}(y,\xi).$

where $\operatorname{proj}_{\mathcal M}$ is the [`projection`](@ref) onto the
tangent space $T_y\mathcal M$.
"""
function parallelTransport(M::Grassmannian{T}, x::GrPoint{T}, y::GrPoint{T}, ξ::GrTVector{T}) where T<:Union{U, Complex{U}} where U<:AbstractFloat
 GrTVector(projection(M,y,getValue(ξ)))
end

@doc doc"""
    projection(M,x,q)

project a matrix q orthogonally on the [`GrPoint`](@ref) `x` of the manifold
[`Grassmannian`](@ref) manifold `M`. The formula reads

$\operatorname{proj}_{\mathcal M}(x,q) = q-x({\bar x}^\mathrm{T}q),$

i.e. the difference matrix of the image and the output matrix lies in
the orthogonal complement of all [`GrTVector`](@ref)s from the tangent space
$T_x\mathcal M$ at [`GrPoint`](@ref) `x`.

# see also
[`parallelTransport`](@ref), [`randomTVector`](@ref)
"""
function projection(M::Grassmannian{T},x::GrPoint{T},q::Matrix{T}) where T<:Union{U, Complex{U}} where U<:AbstractFloat
  A = getValue(x)'*q
  B = q - getValue(x)*A
  return B
end

@doc doc"""
    randomMPoint(M [,type=:Gaussian, σ=1.0])

return a random [`GrPoint`](@ref) `x` on [`Grassmannian`](@ref) manifold `M` by
generating a random (Gaussian) matrix with standard deviation `σ` in matching
size, which is orthonormal.
"""
function randomMPoint(M::Grassmannian{T}, ::Val{:Gaussian}, σ::Float64=1.0) where T<:Union{U, Complex{U}} where U<:AbstractFloat
  V = σ * randn(T, (M.dimensionvecspace, M.dimensionsubspace))
  A = qr(V).Q[:,1:M.dimensionsubspace]
  GrPoint{T}( A )
end

@doc doc"""
    randomTVector(M,x [,type=:Gaussian, σ=1.0])

return a (Gaussian) random vector [`GrTVector`](@ref) in the tangential space
$T_x\mathrm{Gr}(k,n)$ with mean zero and standard deviation `σ` by projecting
a random Matrix onto the  [`GrPoint`](@ref) `x` with [`projection`](@ref).
"""
function randomTVector(M::Grassmannian{T},x::GrPoint{T}, ::Val{:Gaussian}, σ::Float64=1.0) where T<:Union{U, Complex{U}} where U<:AbstractFloat
  y = σ * randn(T, (M.dimensionvecspace,M.dimensionsubspace))
  Y = projection(M, x, y)
  Y = Y/norm(Y)
  GrTVector(Y)
end

@doc doc"""
    retraction(M,x,ξ,[t=1.0])

move the [`GrPoint`](@ref) `x` in the direction of the [`GrTVector`](@ref) `ξ`
on the  [`Grassmannian`](@ref) manifold `M`. This SVD-based retraction is an
approximation of the exponential map [`exp`](@ref). Let

$USV = x + tξ$

be the singular value decomposition, then the formula reads

$\operatorname{retr}_x\xi = U{\bar V}^\mathrm{T}.$
"""
function retraction(M::Grassmannian{T},x::GrPoint{T},ξ::GrTVector{T},t::Float64=1.0) where T<:Union{U, Complex{U}} where U<:AbstractFloat
	y = getValue(x) + t * getValue(ξ)
    S = svd(y)
    A = S.U * S.V'
    GrPoint{T}(A)
end

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

return a zero tangent vector in the tangent space of the [`GrPoint`](@ref) on
the [`Grassmannian`](@ref) manifold `M`.
"""
function zeroTVector(M::Grassmannian{T}, x::GrPoint{T}) where T<:Union{U, Complex{U}} where U<:AbstractFloat
  return GrTVector{T}(zeros(M.dimensionvecspace,M.dimensionsubspace))
end

function validateMPoint(M::Grassmannian{T}, x::GrPoint{T}) where T<:Union{U, Complex{U}} where U<:AbstractFloat
  if size(getValue(x), 1) ≠ M.dimensionvecspace
    throw( ErrorException("The dimension of $x must be $(M.dimensionvecspace) × $(M.dimensionsubspace) but it is $(size(getValue(x), 1)) × $(size(getValue(x), 2)).") )
  end
  if size(getValue(x), 2) ≠ M.dimensionsubspace
    throw( ErrorException("The dimension of $x must be $(M.dimensionvecspace) × $(M.dimensionsubspace) but it is $(size(getValue(x), 1)) × $(size(getValue(x), 2)).") )
  end
  if rank(getValue(x)) ≠ M.dimensionsubspace
    throw( ErrorException("$x must have  full rank.") )
  end

  z=getValue(x)'*getValue(x)
  if z ≉ one(z)
    throw( ErrorException("$x has to be orthonormal but it's not.") )
  end
end

function validateTVector(M::Grassmannian{T}, x::GrPoint{T}, ξ::GrTVector{T},) where T<:Union{U, Complex{U}} where U<:AbstractFloat
  if size(getValue(ξ), 1) ≠ M.dimensionvecspace
    throw( ErrorException("The dimension of $ξ must be $(M.dimensionvecspace) × $(M.dimensionsubspace) but it is $(size(getValue(ξ), 1)) × $(size(getValue(ξ), 2))") )
  end
  if size(getValue(ξ), 2) ≠ M.dimensionsubspace
    throw( ErrorException("The dimension of $ξ must be $(M.dimensionvecspace) × $(M.dimensionsubspace) but it is $(size(getValue(ξ), 1)) × $(size(getValue(ξ), 2))") )
  end
  if norm(getValue(x)'*getValue(ξ) + getValue(ξ)'*getValue(x)) > 10^(-15)
    throw( ErrorException("The matrix $x'$ξ must be skew-symmetric!") )
  end
end


using DataFrames
using CSV
using GLM
using Lathe

df_steam_t = DataFrame(CSV.File("../resources/data3.csv"))
coln_steam = Symbol[]
for i in string.(names(df_steam_t))
    i = replace(strip(i), " " => "_")
    i = replace(i, "-" => "")
    i = replace(i, "/" => "")
    i = replace(i, "(" => "")
    i = replace(i, ")" => "")
    i = replace(i, "%" => "")
    i = replace(i, "," => "")
    i = replace(i, "*" => "")
    i = replace(i, "+" => "")
    println(i)
    push!(coln_steam, Symbol(i))
end

rename!(df_steam_t, coln_steam)
#LP_Turbine_Power_kW
#Condenser_Duty_MMBTUhr
#DAC_Steam_Duty_MMBTUhr
#Total_ST_and_BOP_Auxiliary_Loads_kW
formulasSteamT = FormulaTerm[]
#
fm0st = @formula(LP_Turbine_Power_kW ~ Percent_of_Gas_Turbine_Electrical_Load)
push!(formulasSteamT, fm0st)
fm1st = @formula(Duty_of_CC_Reboiler_MMBTUhr ~ Percent_of_Gas_Turbine_Electrical_Load)
push!(formulasSteamT, fm1st)
fm2st = @formula(DAC_Steam_Duty_MMBTUhr ~ Percent_of_Gas_Turbine_Electrical_Load)
push!(formulasSteamT, fm2st)
fm3st = @formula(Total_ST_and_BOP_Auxiliary_Loads_kW ~ Percent_of_Gas_Turbine_Electrical_Load)
push!(formulasSteamT, fm3st)
coef_steam_df = DataFrame(y = String[], b = Float64[], a = Float64[])
for i in formulasSteamT
    linReg = lm(i, df_steam_t)
    y = string(terms(i)[1])
    b = coef(linReg)[1]
    a = coef(linReg)[2]
    push!(coef_steam_df, (y, b, a))
    println(linReg)
    println(deviance(linReg))
    println(coef(linReg))
end

CSV.write("steam_coeffs_v3.csv", coef_steam_df)


import MPI

include("types.jl")
include("init.jl")
include("get_sim_params.jl")
include("update.jl")
include("io.jl")
include("get_sim_params.jl")

function main()
    #MPI Initialization
    MPI.Init()
    comm = MPI.COMM_WORLD
    my_id = MPI.Comm_rank(comm)
    nproc = MPI.Comm_size(comm)
    println("Number of MPI processes: ", nproc)

    #temp1_init: temperature init on borders
    temp1_init = 10.0
    #temp2_init: temperature init inside domain
    temp2_init = -10.0
    #diffusion coefficient
    k0 = Float64(1)

    #define rank of root process
    root = 0

    #simulation parameters
    params_int = Array{Int64}(undef, 5)
    params_double = Array{Float64}(undef, 2)
    if (my_id == root)
        params_int, params_double, output_path = get_sim_params(ARGS)
    end

    MPI.Barrier(comm)
    MPI.Bcast!(params_int, 5, 0, comm)
    MPI.Bcast!(params_double, 2, 0, comm)

    size_x    = params_int[1]
    size_y    = params_int[2]
    nx_domains = params_int[3]
    ny_domains = params_int[4]
    maxStep   = params_int[5]
    dt1       = params_double[1]
    epsilon   = params_double[2]

    #Warning message if dimensions and number of processes don't match
    if ((my_id == root) && (nproc != (nx_domains * ny_domains)))
        println("ERROR - Number of processes not equal to number of subdomains")
        println("nprocs: ", nproc, " domains: ",  nx_domains * ny_domains )
    end

    #Various other variables
    size_global_x = size_x + 2
    size_global_y = size_y + 2
    hx = Float64(1.0 / size_global_x)
    hy = Float64(1.0 / size_global_y)
    dt2 = 0.25 * min(hx, hy)^2 / k0 #fraction of CFL condition
    size_total_x = size_x + 2 * nx_domains + 2 #including ghost cells
    size_total_y = size_y + 2 * ny_domains + 2 #including ghost cells

    #Take a right time step for convergence
    if (dt1 >= dt2)
        if (my_id == 0)
            println()
            println("Time step too large ==> Taking convergence criterion")
        end
        dt = dt2
    else
        dt = dt1
    end

    #2D solution including ghost cells
    u0 = twoDArray(Float64, size_total_x, size_total_y)
    u = twoDArray(Float64, size_total_x, size_total_y)

    #Allocate coordinates of processes (start cell, end cell)
    xs = oneDArray(Int, nproc)
    xe = oneDArray(Int, nproc)
    ys = oneDArray(Int, nproc)
    ye = oneDArray(Int, nproc)

    #Size of each physical domain
    xcell = Int(size_x / nx_domains)
    ycell = Int(size_y / ny_domains)

    #allocate flattened (1D) local physical solution (i.e., relative to sub-domain)
    u_local = oneDArray(Float64, xcell * ycell)
    #allocate flattened (1D) global physical solution
    u_global = oneDArray(Float64, size_x * size_y)

    #find processes surrounding mine (if they exist)
    my_neighbors = neighbors(my_id, nproc, nx_domains, ny_domains)

    #compute coordinates of processes for each sub-domain
    process_coordinates!(xs, ys, xe, ye, xcell, ycell, nx_domains, ny_domains, nproc)

    #initialize domain
    init_values(u0, size_total_x, size_total_y, temp1_init, temp2_init)

    #update ghost cells
    updateBound!(u0, size_total_x, size_total_y, my_neighbors, comm,
                my_id, xs, ys, xe, ye, xcell, ycell, nproc)

    #Initialize step, time and convergence boolean
    step = 0
    t = 0.0
    converged = false

    #Starting time
    if (my_id==0)
        time_init = time()
    end

    #Main loop : until convergence
    while (!converged)
        #increment step and time
        step += 1
        t += dt
        #perform one step of the explicit scheme
        local_diff = computeNext!(u0, u, size_total_x, size_total_y, dt, hx, hy,
            my_id, xs, ys, xe, ye, nproc, k0)

        #update ghost cells
        updateBound!(u0, size_total_x, size_total_y, my_neighbors, comm,
            my_id, xs, ys, xe, ye, xcell, ycell, nproc)

        MPI.Barrier(comm)

        #sum local_diff to get global difference
        global_diff = MPI.Allreduce(local_diff, MPI.SUM, comm)
        global_diff = sqrt(global_diff)
        #break if convergence reached or step greater than maxStep
        if ((global_diff <= epsilon) || (step >= maxStep))
            converged = true
        end
    end

    #get ending time
    if (my_id == 0)
        time_final = time()
        #elapsed time
        elapsed_time = time_final - time_init
        println("Elapsed time = ", elapsed_time)
        println("Steps = ", step)
    end

    #find solution on my sub-domain (as a 1-dimensional array)
    i = 1
    for j = ys[my_id+1]:ye[my_id+1]
        u_local[(i-1)*xcell+1:i*xcell] = u0[xs[my_id+1]:xe[my_id+1],j]
        i = i+1
    end

    #gather local solution to global solution (as a 1-dimensional array)
    u_global = MPI.Gather(u_local, root, comm)

    #write to disk
    if (my_id == 0)
        write_to_disk(u_global, nx_domains, ny_domains, xcell, ycell, temp1_init, output_path)
    end

    MPI.Finalize()

end

main()


module TestDifferentiableGivens
# test
using Test
using LinearAlgebra
using LinearAlgebra: givensAlgorithm
using ForwardDiff: gradient, derivative

i = 3
g = x->givensAlgorithm(x, 2x)[i]
nexp = 16
@testset "differentiating givensAlgorithm" begin
    for _ in 1:nexp
        x = randn()
        gx = g(x)
        h = 1e-4
        y = x + h
        gy = g(y)
        gd_fd = (gy - gx) / h

        gd = derivative(g, x)

        tol = 1e-4
        @test abs(gd_fd - gd) < tol
    end
end

end


module HSMath

    export powi, optimize, offset_func, offset_calc, cplx_floor, cplx_ceil, cplx_mod, ∏, ∑, ≆, ≊

    include("Primes.jl")

    powi(base, pow) = base < 0 ? -((-base) ^ pow) : base ^ pow

    """f -> Function in modifying g(x)
       g -> Internal Core Function
       o -> Operator function

       Returns required such that O(f(g(x))) = f(g(x) + C). Solves for C
    """

    ≆(x, p) = !isapprox(x, p)
    ≊(x, p) = isapprox(x, p)

    offset_func(f::Function, f_inv::Function, g::Function, operator::Function) = x -> offset_calc(x, f, f_inv, g, operator)

    offset_calc(x, f::Function, f_inv::Function, g::Function, operator::Function) = f_inv(operator(f(g(x)))) - g(x)

    cplx_floor(x) = Base.floor(real(x)) + Base.floor(imag(x)) * im

    cplx_ceil(x) = Base.ceil(real(x)) + Base.ceil(imag(x)) * im

    cplx_mod(base, num) = base - num * cplx_floor(base / num)

    function ∏(array::NTuple{N, T}) where {T, N}
        prod::T = 1
        for arg in array
            prod *= arg
        end
        prod
    end

    function ∏(array::AbstractArray{T}) where T
        prod::T = 1
        for arg in array
            prod *= arg
        end
        prod
    end

    function ∑(array::AbstractArray{T}) where T
        sum::T = 0
        for arg in array
            sum += arg
        end
        sum
    end

    function ∑(array::NTuple{N, T}) where {T, N}
        sum::T = 0
        for arg in array
            sum += arg
        end
        sum
    end
end


"""
Bridges `CP.AbsoluteValue` to linear constraints and integer constraints.

The implemented model is the most generic one, so that the absolute value
always has a well-defined value. This requires the use of a binary variable.
In many cases, this is not necessary, and simpler models could be used, but
checking this automatically would require access to the whole model.

Based on Mosek's
[modelling cookbook](https://docs.mosek.com/modeling-cookbook/mio.html#exact-absolute-value).

YALMIP implements [a similar formulation, with big-M and small-m values
computed based on the bounds for each variable](https://github.com/yalmip/YALMIP/blob/develop/operators/alldifferent.m).
"""
struct AbsoluteValue2MILPBridge{T} <: MOIBC.AbstractBridge
    var_bin::MOI.VariableIndex
    var_bin_con::MOI.ConstraintIndex{MOI.VariableIndex, MOI.ZeroOne}
    var_pos::MOI.VariableIndex
    var_pos_con::MOI.ConstraintIndex{MOI.VariableIndex, MOI.GreaterThan{T}}
    var_neg::MOI.VariableIndex
    var_neg_con::MOI.ConstraintIndex{MOI.VariableIndex, MOI.GreaterThan{T}}

    con_original_var::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}
    con_abs_var::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}
    con_pos_var_big_m::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.LessThan{T}}
    con_neg_var_big_m::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.LessThan{T}}
end

function MOIBC.bridge_constraint(
    ::Type{AbsoluteValue2MILPBridge{T}},
    model,
    f::MOI.VectorOfVariables,
    s::CP.AbsoluteValue,
) where {T}
    return MOIBC.bridge_constraint(
        AbsoluteValue2MILPBridge{T},
        model,
        MOI.VectorAffineFunction{T}(f),
        s,
    )
end

function MOIBC.bridge_constraint(
    ::Type{AbsoluteValue2MILPBridge{T}},
    model,
    f::MOI.VectorAffineFunction{T},
    s::CP.AbsoluteValue,
) where {T}
    f_scalars = MOIU.scalarize(f)

    # For this formulation work, both lower and upper bounds are required on
    # the argument of the absolute value.
    @assert CP.has_lower_bound(model, f_scalars[2])
    @assert CP.has_upper_bound(model, f_scalars[2])

    # Create the variables.
    var_bin, var_bin_con = MOI.add_constrained_variable(model, MOI.ZeroOne())
    var_pos, var_pos_con = MOI.add_constrained_variable(model, MOI.GreaterThan(zero(T)))
    var_neg, var_neg_con = MOI.add_constrained_variable(model, MOI.GreaterThan(zero(T)))

    # Relate the positive and negative parts to the set variables.
    con_original_var = MOI.add_constraint(
        model,
        f_scalars[2] - var_pos + var_neg,
        MOI.EqualTo(zero(T))
    )
    con_abs_var = MOI.add_constraint(
        model,
        f_scalars[1] - var_pos - var_neg,
        MOI.EqualTo(zero(T))
    )

    # Compute a bound on M.
    M = max(
        abs(CP.get_lower_bound(model, f_scalars[2])),
        abs(CP.get_upper_bound(model, f_scalars[2])),
    )

    # Big-M constraints.
    con_pos_var_big_m = MOI.add_constraint(
        model,
        var_pos - M * var_bin,
        MOI.LessThan(zero(T))
    )
    con_neg_var_big_m = MOI.add_constraint(
        model,
        var_neg - M * (1 - var_bin),
        MOI.LessThan(zero(T))
    )

    return AbsoluteValue2MILPBridge(
        var_bin, var_bin_con, var_pos, var_pos_con, var_neg, var_neg_con,
        con_original_var, con_abs_var, con_pos_var_big_m, con_neg_var_big_m,
    )
end

function MOI.supports_constraint(
    ::Type{AbsoluteValue2MILPBridge{T}},
    ::Union{Type{MOI.VectorOfVariables}, Type{MOI.VectorAffineFunction{T}}},
    ::Type{CP.AbsoluteValue},
) where {T}
    return true
end

function MOIB.added_constrained_variable_types(::Type{AbsoluteValue2MILPBridge{T}}) where {T}
    return [(MOI.ZeroOne), (MOI.GreaterThan{T})]
end

function MOIB.added_constraint_types(::Type{AbsoluteValue2MILPBridge{T}}) where {T}
    return [
        (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}),
        (MOI.ScalarAffineFunction{T}, MOI.LessThan{T}),
    ]
end

function MOI.get(::AbsoluteValue2MILPBridge, ::MOI.NumberOfVariables)
    return 3
end

function MOI.get(
    ::AbsoluteValue2MILPBridge{T},
    ::MOI.NumberOfConstraints{
        MOI.VariableIndex, MOI.ZeroOne,
    },
) where {T}
    return 1
end

function MOI.get(
    ::AbsoluteValue2MILPBridge{T},
    ::MOI.NumberOfConstraints{
        MOI.VariableIndex, MOI.GreaterThan{T},
    },
) where {T}
    return 2
end

function MOI.get(
    ::AbsoluteValue2MILPBridge{T},
    ::MOI.NumberOfConstraints{
        MOI.ScalarAffineFunction{T}, MOI.EqualTo{T},
    },
) where {T}
    return 2
end

function MOI.get(
    ::AbsoluteValue2MILPBridge{T},
    ::MOI.NumberOfConstraints{
        MOI.ScalarAffineFunction{T}, MOI.LessThan{T},
    },
) where {T}
    return 2
end

function MOI.get(
    b::AbsoluteValue2MILPBridge{T},
    ::MOI.ListOfVariableIndices,
) where {T}
    return [b.var_bin, b.var_pos, b.var_neg]
end

function MOI.get(
    b::AbsoluteValue2MILPBridge{T},
    ::MOI.ListOfConstraintIndices{
        MOI.VariableIndex, MOI.ZeroOne,
    },
) where {T}
    return [b.var_bin_con]
end

function MOI.get(
    b::AbsoluteValue2MILPBridge{T},
    ::MOI.ListOfConstraintIndices{
        MOI.VariableIndex, MOI.GreaterThan{T},
    },
) where {T}
    return [b.var_pos_con, b.var_neg_con]
end

function MOI.get(
    b::AbsoluteValue2MILPBridge{T},
    ::MOI.ListOfConstraintIndices{
        MOI.ScalarAffineFunction{T}, MOI.EqualTo{T},
    },
) where {T}
    return [b.con_original_var, b.con_abs_var]
end

function MOI.get(
    b::AbsoluteValue2MILPBridge{T},
    ::MOI.ListOfConstraintIndices{
        MOI.ScalarAffineFunction{T}, MOI.LessThan{T},
    },
) where {T}
    return [b.con_pos_var_big_m, b.con_neg_var_big_m]
end


using SymbolicUtils, Test
using SymbolicUtils: Term, symtype
using BenchmarkTools

function goldstein_price()
    @syms x1 x2
    f1 = x1+x2+1
    f2 = 19-14*x1+3*x1^2-14*x2+6*x1*x2+3*x2^2
    f3 = 2*x1-3*x2
    f4 = 18-32*x1+12*x1^2+48*x2-36*x1*x2+27*x2^2

    # f(x) is the Goldstein-Price function
    f = (1+f1^2*f2)*(30+f3^2*f4)
    @btime expand($f)
    f2 = f^2
    @btime expand($f2)
    f3 = f^3
    @btime expand($f3)
end


# rotate2d.jl

using MIRT: rotate2d
using Test: @test

@test [rotate2d(1, 1, π/2)...] ≈ [(1.,-1.)...]


@testset "Cost Matrix/String" begin
    @test distance_matrix("ab", "aab", deletion = 2) == 1                # Should go with INSERTION instead of DELETION.
    @test distance_matrix("ab", "aab", deletion = 2, insertion = 2) == 2 # Could go with either.
    @test distance_matrix("abc", "bbc", substitution = 2) == 2           # DELETION + INSERTION = SUBSTITUTION
end

@testset "Cost Matrix/Array" begin
    @test distance_matrix([ 1 2 ], [ 1 1 2 ], deletion = 2) == 1                # Should go with INSERTION instead of DELETION.
    @test distance_matrix([ 1 2 ], [ 1 1 2 ], deletion = 2, insertion = 2) == 2 # Could go with either.
    @test distance_matrix([ 1 2 3 ], [ 2 2 3 ], substitution = 2) == 2          # DELETION + INSERTION = SUBSTITUTION
end


# MCP example by Josephy.

@testset "mcp_josephy" begin

function f!(F, x)
    F[1]=3*x[1]^2+2*x[1]*x[2]+2*x[2]^2+x[3]+3*x[4]-6
    F[2]=2*x[1]^2+x[1]+x[2]^2+3*x[3]+2*x[4]-2
    F[3]=3*x[1]^2+x[1]*x[2]+2*x[2]^2+2*x[3]+3*x[4]-1
    F[4]=x[1]^2+3*x[2]^2+2*x[3]+3*x[4]-3
end

function j!(J, x)
    J[1,1] = 6*x[1]+2*x[2]
    J[1,2] = 2*x[1]+4*x[2]
    J[1,3] = 1
    J[1,4] = 3
    J[2,1] = 4*x[1]+1
    J[2,2] = 2*x[2]
    J[2,3] = 3
    J[2,4] = 2
    J[3,1] = 6*x[1]+x[2]
    J[3,2] = x[1]+4*x[2]
    J[3,3] = 2
    J[3,4] = 3
    J[4,1] = 2*x[1]
    J[4,2] = 6*x[2]
    J[4,3] = 2
    J[4,4] = 3
end

solution = [ 1.22474487, 0., 0., 0.5 ]

df = OnceDifferentiable(f!, j!, rand(4), rand(4))

# Test smooth reformulation with trust region

r = mcpsolve(df, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :smooth)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, j!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :smooth)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :smooth)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :smooth, autodiff = true)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

# Test minmax reformulation with trust region

r = mcpsolve(df, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :minmax)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, j!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :minmax)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :minmax)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :minmax, autodiff = true)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

# Test with Newton method

r = mcpsolve(df, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], method = :newton)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

end


"""
A CPD for which the distribution never changes.
    target: name of the CPD's variable
    parents: list of parent variables.
    d: a Distributions.jl distribution

While a StaticCPD can have parents, their assignments will not affect the distribution.
"""
mutable struct StaticCPD{D} <: CPD{D}
    target::NodeName
    parents::NodeNames
    d::D
end
StaticCPD(target::NodeName, d::Distribution) = StaticCPD(target, NodeName[], d)

name(cpd::StaticCPD) = cpd.target
parents(cpd::StaticCPD) = cpd.parents
(cpd::StaticCPD)(a::Assignment) = cpd.d # no update
(cpd::StaticCPD)() = (cpd)(Assignment()) # cpd()
(cpd::StaticCPD)(pair::Pair{NodeName}...) = (cpd)(Assignment(pair)) # cpd(:A=>1)

function Distributions.fit(::Type{StaticCPD{D}},
                           data::DataFrame,
                           target::NodeName,
                           parents::NodeNames=NodeName[],
                           ) where {D<:Distribution}

    d = fit(D, data[target])
    StaticCPD(target, parents, d)
end

nparams(cpd::StaticCPD) = paramcount(params(cpd.d))


using Test, LinearMaps, LinearAlgebra, SparseArrays

@testset "Kronecker products and sums" begin
    @testset "Kronecker product" begin
        A = rand(ComplexF64, 3, 3)
        B = rand(ComplexF64, 2, 2)
        K = kron(A, B)
        LA = LinearMap(A)
        LB = LinearMap(B)
        LK = @inferred kron(LA, LB)
        @test kron(LA, 2LB) isa LinearMaps.ScaledMap
        @test kron(3LA, LB) isa LinearMaps.ScaledMap
        @test kron(3LA, 2LB) isa LinearMaps.ScaledMap
        @test kron(3LA, 2LB).λ == 6
        @test kron(kron(LA, LA), 2LB) isa LinearMaps.ScaledMap
        @test kron(3LA, kron(LB, LB)) isa LinearMaps.ScaledMap
        @test_throws ErrorException LinearMaps.KroneckerMap{Float64}((LA, LB))
        @test occursin("6×6 LinearMaps.KroneckerMap{$(eltype(LK))}", sprint((t, s) -> show(t, "text/plain", s), LK))
        @test @inferred size(LK) == size(K)
        @test LinearMaps.MulStyle(LK) === LinearMaps.ThreeArg()
        for i in (1, 2)
            @test @inferred size(LK, i) == size(K, i)
        end
        @test LK isa LinearMaps.KroneckerMap{ComplexF64}
        L = ones(3) ⊗ ones(ComplexF64, 4)'
        v = rand(4)
        @test Matrix(L) == ones(3,4)
        @test L*v ≈ fill(sum(v), 3)

        for transform in (identity, transpose, adjoint)
            @test Matrix(transform(LK)) ≈ transform(Matrix(LK)) ≈ transform(K)
            @test Matrix(kron(transform(LA), transform(LB))) ≈ transform(K)
            @test Matrix(transform(LinearMap(LK))) ≈ transform(Matrix(LK)) ≈ transform(K)
        end
        @test kron(LA, kron(LA, B)) == kron(LA, LA, LB)
        @test kron(kron(LA, LB), kron(LA, LB)) == kron(LA, LB, LA, LB) == ⊗(LA, LB, LA, LB)
        @test kron(A, A, A) ≈ Matrix(@inferred kron(LA, LA, LA)) ≈ Matrix(@inferred LA^⊗(3)) ≈ Matrix(@inferred A^⊗(3))
        LAs = LinearMap(sparse(A))
        K = @inferred kron(A, A, A, LAs)
        @test K isa LinearMaps.KroneckerMap
        @test Matrix(K) ≈ kron(A, A, A, A)
        @test convert(AbstractMatrix, K) isa SparseMatrixCSC
        @test convert(AbstractMatrix, K) ≈ kron(A, A, A, A)
        @test sparse(K) ≈ kron(A, A, A, A)
        @test Matrix(@inferred K*K) ≈ kron(A, A, A, A)*kron(A, A, A, A)
        K4 = @inferred kron(A, B, B, LB)
        # check that matrices don't get Kronecker-multiplied, but that everything is lazy
        @test K4.maps[1].lmap === A
        @test @inferred kron(LA, LB)' == @inferred kron(LA', LB')
        @test (@inferred kron(LA, B)) == (@inferred kron(LA, LB)) == (@inferred kron(A, LB))
        @test @inferred ishermitian(kron(LA'LA, LB'LB))
        A = rand(2, 5); B = rand(4, 2)
        K = @inferred kron(A, LinearMap(B))
        v = rand(size(K, 2))
        @test K*v ≈ kron(A, B)*v
        @test Matrix(K) ≈ kron(A, B)
        K = @inferred kron(LinearMap(B), A)
        v = rand(size(K, 2))
        @test K*v ≈ kron(B, A)*v
        @test Matrix(K) ≈ kron(B, A)
        A = rand(3, 3); B = rand(2, 2); LA = LinearMap(A); LB = LinearMap(B)
        @test @inferred issymmetric(kron(LA'LA, LB'LB))
        @test @inferred ishermitian(kron(LA'LA, LB'LB))
        # use mixed-product rule
        K = kron(LA, LB) * kron(LA, LB) * kron(LA, LB)
        @test Matrix(K) ≈ kron(A, B)^3
        # example that doesn't use mixed-product rule
        A = rand(3, 2); B = rand(2, 3)
        K = @inferred kron(A, LinearMap(B))
        @test Matrix(@inferred K*K) ≈ kron(A, B)*kron(A, B)
        A = rand(3, 2); B = rand(4, 3)
        @test Matrix(kron(LinearMap(A), B, [A A])*kron(LinearMap(A), B, A')) ≈ kron(A, B, [A A])*kron(A, B, A')

        m = 3
        A = rand(m, m)
        F = LinearMap(x -> A*x, m, m)
        S = sparse(I, m, m)
        J = LinearMap(I, m)
        @test kron(J, J) == LinearMap(I, m*m)
        v = rand(m^3)
        for (K, M) in ((⊗(A, J, J), kron(A, S, S)),
                       (⊗(J, A, J), kron(S, A, S)),
                       (⊗(J, J, A), kron(S, S, A)),
                       (⊗(F, J, J), kron(A, S, S)),
                       (⊗(J, F, J), kron(S, A, S)),
                       (⊗(J, J, F), kron(S, S, A)))
            @test K * v ≈ M * v
            @test Matrix(K) ≈ M
        end
    end

    @testset "Kronecker sum" begin
        for elty in (Float64, ComplexF64)
            A = rand(elty, 3, 3)
            B = rand(elty, 2, 2)
            LA = LinearMap(A)
            LB = LinearMap(B)
            KS = @inferred kronsum(LA, B)
            @test occursin("6×6 LinearMaps.KroneckerSumMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), KS))
            @test_throws ArgumentError kronsum(LA, [B B]) # non-square map
            KSmat = kron(A, Matrix(I, 2, 2)) + kron(Matrix(I, 3, 3), B)
            @test Matrix(KS) ≈ Matrix(kron(A, LinearMap(I, 2)) + kron(LinearMap(I, 3), B))
            @test KS * ones(size(KS, 2)) ≈ KSmat * ones(size(KS, 2))
            @test size(KS) == size(kron(A, Matrix(I, 2, 2)))
            for transform in (identity, transpose, adjoint)
                @test Matrix(transform(KS)) ≈ transform(Matrix(KS)) ≈ transform(KSmat)
                @test Matrix(kronsum(transform(LA), transform(LB))) ≈ transform(KSmat)
                @test Matrix(transform(LinearMap(kronsum(LA, LB)))) ≈ Matrix(transform(KS)) ≈ transform(KSmat)
            end
            @test @inferred(kronsum(A, A, LB)) == @inferred(⊕(A, A, B))
            @test Matrix(@inferred LA^⊕(3)) == Matrix(@inferred A^⊕(3)) ≈ Matrix(kronsum(LA, A, A))
            @test @inferred(kronsum(LA, LA, LB)) == @inferred(kronsum(LA, kronsum(LA, LB))) == @inferred(kronsum(A, A, B))
            @test Matrix(@inferred kronsum(A, B, A, B, A, B)) ≈ Matrix(@inferred kronsum(LA, LB, LA, LB, LA, LB))
            T = typeof(kron(Diagonal(rand(elty, 3)), sprand(3, 3, 0.3)))
            @test convert(AbstractMatrix, kronsum(sparse(A), sparse(B), sparse(A))) isa T
            @test convert(AbstractMatrix, kronsum(A, B, A)) == Matrix(kronsum(A, B, A))
            @test sparse(kronsum(sparse(A), B, A)) == Matrix(kronsum(sparse(A), B, A))
        end
    end
end


using GLMakie
using Makie.Colors

# Change these
Δθ = 1//16 # Must be rational. Multiplied by π later to avoid floating point errors
max_prime = 500 # List will choose all numbers less than or equal to this
framerate = 60
speed_multiplier = 50//1 # Must be Rational. Multiplier to change dot speed
total_time = 180 # seconds
set_theme!(theme_black())
resolution = (1920, 1080)
tail_length = 1000
show_dots = false

# Don't change these
primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
            101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199,
            211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293,
            307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397,
            401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499,
            503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599,
            601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691,
            701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797,
            809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887,
            907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]
primes_list = collect(Iterators.takewhile(<=(max_prime), primes))
y_height = ceil(1.05 * primes_list[end])
x_width = ceil(y_height * resolution[1] // resolution[2])
speed_multiplier *= (framerate//60)
Δθ /= denominator(speed_multiplier)

# Function to get next frame
function step_data!(points, current_step, cycle, top_half, Δθ, scale)
    current_step += 1
    progress = current_step * Δθ
    if progress >= 1
        push!(points, (2*scale*(cycle+1), 0))
        current_step -= 1//Δθ
        top_half = !top_half
        cycle += 1
    end

    if current_step != 0
        if top_half
            θ = π*(1 - progress)
        else
            θ = π*(1 + progress)
        end
        push!(points, (2*scale*cycle + scale*(cos(θ)+1), scale*sin(θ)))
    end

    return current_step, cycle, top_half
end

function filter_points(list, tail_length)

    num_to_remove = 0
    if length(list) > tail_length
        num_to_remove = length(list) - tail_length
    end

    counter = 1
    x_lim = x_center[] - (3*x_width/4)
    while list[counter][1] < x_lim
        num_to_remove += 1
        counter +=1
    end

    if num_to_remove > 0
        list = list[1+num_to_remove:end]
    end

    return num_to_remove
end

#######################################################################
#   ANIMATION
#######################################################################
begin
    x_center = Node(0//1)
    tick_step = ceil(x_width / 4.4)

    # Make scene
    fig = Figure(resolution = resolution)
    ax = fig[1,1] = Axis(fig,
                        xticks = MultiplesTicks(4, tick_step, string(" * ", tick_step)),
                        yticks = LinearTicks(4))

    ax.autolimitaspect = 1

    # Add scroll
    on(x_center) do x_center
        # Screen limits
        screen_begin = x_center[] - 2*x_width/3
        limits!(ax, screen_begin, screen_begin + x_width, -y_height/2, y_height/2)
    end
    notify(x_center) # Manually do first update

    # Add time
    timestamps = range(0, total_time, step=1/framerate)

    # Initialize lists
    points, current_step, cycle, top_half = Node[], [], [], []
    for p in primes_list
        push!(points, Point2[(0.0,0.0)])
        append!(current_step, [0//1])
        append!(cycle, [0])
        append!(top_half, [true])
    end

    # Add lines
    for i in 1:length(points)
        lines!(ax, points[i])
    end
    scat_points = Node([a[end] for a in [b[] for b in points]])
    if show_dots
        scatter!(scat_points)
    end

    # Record it
    record(fig, "append_animation.mp4", timestamps;
            framerate = framerate) do frame
        for i in 1:length(points)
            for j in 1:numerator(speed_multiplier)
                current_step[i], cycle[i], top_half[i] = step_data!(points[i][], current_step[i], cycle[i], top_half[i], Δθ//primes_list[i], primes_list[i]/2)
            end

            num_to_remove = filter_points(points[i][], tail_length)
            if num_to_remove > 0
                deleteat!(points[i][], 1:num_to_remove)
            end
            notify(points[i]) # Force notify update
        end

        # Draw dots
        if show_dots
            scat_points[] = [a[end] for a in [b[] for b in points]]
        end
        x_center[] += Δθ * numerator(speed_multiplier)
    end
end


using DelaySSAToolkit
using Catalyst
# using Plots
################################################################################
# the following example illustrates how to deal with a oscillatory system
# d: X-> 0; k1/(1+Y^2): 0-> X; [trigger X-> Y after τ time;] k2*Y/(1+Y):  Y-> 0;

rn = @reaction_network begin
    1/(1+Y^2), 0 --> X
    1/(1+Y),   Y --> 0
end

jumpsys = convert(JumpSystem, rn, combinatoric_ratelaws = false)

u0 = [0,0]
de_chan0 = [[]]
tf = 400.
tspan = (0,tf)
τ = 20.
dprob = DiscreteProblem(jumpsys, u0, tspan)
# jumpsys.dep_graph

delay_trigger_affect! = function (integrator, rng)
  append!(integrator.de_chan[1], τ)
end
delay_trigger = Dict(1=>delay_trigger_affect!)
delay_complete = Dict(1=>[2=>1, 1=>-1])
delay_interrupt = Dict()
delayjumpset = DelayJumpSet(delay_trigger, delay_complete, delay_interrupt)
# alg = DelayRejection()
# alg = DelayDirect()
# alg = DelayMNRM()
alg = DelayDirectCR()
djprob = DelayJumpProblem(jumpsys, dprob, alg, delayjumpset, de_chan0)
sol = solve(djprob, SSAStepper(), seed = 12345)
using Plots
plot(sol.t,[sol.u[i][1] for i in eachindex(sol.u)],alpha=0.3, label="X",color="green",linewidth = 2, legend = :top, ylabel = "# of individuals", xlabel = "Time", fmt=:svg)

sol = solve(djprob, SSAStepper(), seed = 1234)
plot!(sol.t,[sol.u[i][1] for i in eachindex(sol.u)], label="X",linewidth = 2, line=:dash,color="green")

Sample_size = Int(1e4)
ens_prob = EnsembleProblem(djprob)
ens = solve(ens_prob, SSAStepper(), EnsembleThreads(), trajectories = Sample_size, saveat = .1)
using StatsBase
tsm(t) = timepoint_mean(ens,t)
mean_X(t) = tsm(t)[1]
mean_Y(t) = tsm(t)[2]

timestamps=0:1.0:tf
plot(timestamps,mean_X.(timestamps),linewidth=3,line=:dash,label="X",xlabel="time",ylabel="Mean Value")
plot!(timestamps,mean_Y.(timestamps),linewidth=3,line=:dash, legend = :topright,label="Y")

# plot(mean_X.(timestamps),mean_Y.(timestamps))
# might be very slow, can try with small end point
# @gif for i in 1:200
#     plot(mean_X,mean_Y,0,i, linewidth=range(0, 5, length = 200),seriesalpha=range(0, 1, length = 200),xlim=(0,21),ylim=(0,7),label=false,xlabel="X",ylabel="Y")
# end