mutable struct NDLCDBasis{T,M,P} <: Space{T}
    geo::M
    fns::Vector{Vector{Shape{T}}}
    pos::Vector{P}
end

NDLCDBasis(geo, fns) = NDLCDBasis(geo, fns, Vector{vertextype(geo)}(undef,length(fns)))

refspace(space::NDLCDBasis{T}) where {T} = NDLCDRefSpace{T}()
Base.similar(space::NDLCDBasis{T,M,P} where {T,M,P}, geo, fns, pos) = NDLCDBasis(geo, fns, pos)

function nedelecd3d(mesh, faces)
    T = coordtype(mesh)
    P = vertextype(mesh)
    num_faces = numcells(faces)

    C = connectivity(faces, mesh, identity)
    rows = rowvals(C)
    vals = nonzeros(C)

    fns = Vector{Vector{Shape{T}}}(undef,num_faces)
    pos = Vector{P}(undef,num_faces)
    for (i,face) in enumerate(cells(faces))

        fns[i] = Vector{Shape{T}}()
        pos[i] = cartesian(center(chart(faces,face)))

        for k in nzrange(C,i)

            j = rows[k]
            s = vals[k]
            push!(fns[i], Shape{T}(j, abs(s), sign(s)))
        end
    end

    NDLCDBasis(mesh, fns, pos)
end

function nedelecd3d(mesh)
    faces = skeleton(mesh,2)
    nedelecd3d(mesh, faces)
end

ntrace(X::NDLCDBasis, geo, fns) = LagrangeBasis{0,-1,1}(geo, fns, deepcopy(X.pos))
ttrace(X::NDLCDBasis, geo, fns) = NDBasis{}(geo, fns, deepcopy(X.pos))


# a intercept-free ridge regressor for testing meta-alogorithms

export FooBarRegressor

import MLJBase
using LinearAlgebra
using MLJScientificTypes

mutable struct FooBarRegressor <: MLJBase.Deterministic
    lambda::Float64
end

function FooBarRegressor(; lambda=0.0)
    simpleridgemodel = FooBarRegressor(lambda)
    message = MLJBase.clean!(simpleridgemodel)
    isempty(message) || @warn message
    return simpleridgemodel
end

function MLJBase.clean!(model::FooBarRegressor)
    warning = ""
    if model.lambda < 0
        warning *= "Need lambda ≥ 0. Resetting lambda=0. "
        model.lambda = 0
    end
    return warning
end

function MLJBase.fitted_params(::FooBarRegressor, fitresult)
    return (coefficients=fitresult)
end

function MLJBase.fit(model::FooBarRegressor, verbosity::Int, X, y)
    x = MLJBase.matrix(X)
    fitresult = (x'x - model.lambda*I)\(x'y)
    cache = nothing
    report = NamedTuple()
    return fitresult, cache, report
end

function MLJBase.predict(model::FooBarRegressor, fitresult, Xnew)
    x = MLJBase.matrix(Xnew)
    return x*fitresult
end

# metadata:
MLJBase.load_path(::Type{<:FooBarRegressor}) = "MLJBase.FooBarRegressor"
MLJBase.package_name(::Type{<:FooBarRegressor}) = "MLJBase"
MLJBase.package_uuid(::Type{<:FooBarRegressor}) = ""
MLJBase.is_pure_julia(::Type{<:FooBarRegressor}) = true
MLJBase.input_scitype(::Type{<:FooBarRegressor}) = Table(Continuous)
MLJBase.target_scitype(::Type{<:FooBarRegressor}) = AbstractVector{Continuous}


function pixel_mse(truth::Union{KnetArray{Float32,4},CuArray{Float32,4}},pred::HGType)
    loss = sum((pred .- truth).^2)
    loss / (size(pred,3) * size(pred,4))
end

function myfree(x::AutoGrad.Result)
    #myfree(x.value)
end

function myfree(x::KnetArray)
    Knet.KnetArrays.freeKnetPtr(x.ptr)
end

function myfree(x::CuArray)
end

function myfree(x::Array)
end

function gaussian_2d(x,y,x0,y0,sig_x=1,sig_y=1)
    out=[exp.(-1/2 .* ((xi .- x0).^2 ./sig_x^2 + (yi .- y0).^2 ./sig_y^2)) for xi in x, yi in y]
    out./sum(out)
end

function gaussian_1d(x,x0,sig)
    [1/(sig*sqrt(2*pi))*exp.(-1/2 .* ((xi .- x0).^2 ./sig^2)) for xi in x]
end

function calculate_sigma((in_x,in_y),(out_x,out_y))
    (0.75 * in_x/out_x , 0.75 * in_y/out_y)
end

function get_xy_gauss_cu((in_w,in_h),(out_w,out_h))

    (sig_x,sig_y) = StackedHourglass.calculate_sigma((in_w,in_h),(out_w,out_h))
    gauss_x = StackedHourglass.gaussian_1d(collect(-4:4),0,sig_x)
    gauss_x_cu = convert(CuArray,gauss_x);
    gauss_y = StackedHourglass.gaussian_1d(collect(-4:4),0,sig_y)
    gauss_y_cu = convert(CuArray,gauss_y);

    (gauss_x_cu, gauss_y_cu)
end

function create_padded_kernel(size_x,size_y,kl)
    kernel = gaussian_2d(collect(-kl:1:kl),collect(-kl:1:kl),0,0)
    kernel = kernel ./ maximum(kernel)
    kernel_pad = zeros(Float32,size_x,size_y)
    kernel_pad[(div(size_x,2)-kl):(div(size_x,2)+kl),(div(size_y,2)-kl):(div(size_y,2)+kl)] = kernel
    kernel_pad
end

#https://github.com/JuliaImages/ImageTransformations.jl/blob/master/src/resizing.jl#L239
function lowpass_filter_resize(img::AbstractArray{T,2},sz::Tuple) where T

    kern = make_gaussian_kernel(size(img),sz)
    imgr = imresize(imfilter(img, kern, NA()), sz) #Can include method here in newest ImageTransformations
end

function lowpass_filter_resize!(img::AbstractArray{T,2},output::AbstractArray{T,2},kern) where T

    imfilter!(img, img,kern, NA())
    ImageTransformations.imresize!(output, img) #Can include method here in newest ImageTransformations
    nothing
end

function make_gaussian_kernel(in_sz::Tuple,out_sz::Tuple)
    σ = map((o,n)->0.75*o/n, in_sz, out_sz)
    KernelFactors.gaussian(σ)
end

function low_pass_pyramid(im::AbstractArray{T,2},sz::Tuple) where T
    new_sz = (div(size(im,1),2),div(size(im,2),2))
    im2 = deepcopy(im)
    while ((new_sz[1] > sz[1])&&(new_sz[2] > sz[2]))
        im2=lowpass_filter_resize(im2,(new_sz))
        #im2 = im2 ./ maximum(im2)
        new_sz = (div(new_sz[1],2),div(new_sz[2],2))
    end
    im2=lowpass_filter_resize(im2,(sz))
end

function upsample_pyramid(im::AbstractArray{T,2},sz::Tuple) where T
    new_sz = (size(im,1)*2,size(im,2)*2)
    im2 = deepcopy(im)
    while ((new_sz[1] <= sz[1])&&(new_sz[2] <= sz[2]))
        im2=lowpass_filter_resize(im2,(new_sz))
        new_sz = (new_sz[1]*2,new_sz[2]*2)
    end
    im2=lowpass_filter_resize(im2,(sz))
end

function predict_single_frame(hg,img::AbstractArray{T,2},atype=KnetArray) where T

    temp_frame = convert(Array{Float32,2},img)
    temp_frame = convert(Array{Float32,2},lowpass_filter_resize(temp_frame,(256,256)))

    temp_frame = convert(atype,reshape(temp_frame,(256,256,1,1)))

    my_features = features(hg)

    set_testing(hg,false) #Turn off batch normalization for prediction
    myout=hg(temp_frame)[4]
    set_testing(hg,true) #Turn back on

    myout=convert(Array{Float32,4},myout)
end

#=
Convert Discrete points to heatmap for deep learning
=#
function make_heatmap_labels(han,real_w=640,real_h=480,label_img_size=64)

    d_points=make_discrete_all_whiskers(han)

    labels=zeros(Float32,label_img_size,label_img_size,size(d_points,1),size(han.woi,1))

    for i=1:size(labels,4)
        for j=1:size(labels,3)
            this_x = d_points[j,1,i] / real_w * label_img_size
            this_y = d_points[j,2,i] / real_h * label_img_size

            if (this_x !=0.0)&(this_y != 0.0)
                labels[:,:,j,i] = WhiskerTracking.gaussian_2d(1.0:1.0:label_img_size,1.0:1.0:label_img_size,this_y,this_x)
                labels[:,:,j,i] = labels[:,:,j,i] ./ maximum(labels[:,:,j,i])
            end
        end
    end

    labels
end

function make_heatmap_labels_keypoints(points::Array{Tuple,1},input_hw::Tuple,labels=zeros(Float32,64,64,length(points),1))

    for j=1:size(points,1)
        this_x = points[j][1] / input_hw[1] * size(labels,1)
        this_y = points[j][2] / input_hw[2] * size(labels,2)

        if (this_x !=0.0)&(this_y != 0.0)
            labels[:,:,j,1] = gaussian_2d(1.0:1.0:size(labels,1),1.0:1.0:size(labels,2),this_x,this_y)
        end
    end
end

function get_labeled_frames(han,out_hw=256,h=480,w=640,frame_rate=25)

    imgs=zeros(Float32,out_hw,out_hw,1,length(han.frame_list))

    temp=zeros(UInt8,w,h)
    for i=1:length(han.frame_list)
        frame_time = han.frame_list[i] / frame_rate
        WhiskerTracking.load_single_frame(frame_time,temp,han.wt.vid_name)
        imgs[:,:,1,i]=Images.imresize(temp',(out_hw,out_hw))
    end
    imgs
end

function batch_predict(hg::StackedHourglass.NN,input_images::CuArray{T,4},sub_input_images,
    input_f,return_ind=4,batch_size=32,batch_per_load=4) where T

    batch_predict(hg,KnetArray(input_images),sub_input_images,input_f,return_ind,batch_size,batch_per_load)
end

function batch_predict(hg::StackedHourglass.NN,input_images::KnetArray{T,4},sub_input_images::KnetArray{T,4},
        input_f::KnetArray{T,4},return_ind=4,batch_size=32,batch_per_load=4) where T

    input_hw=size(input_images,1)
    output_hw=size(input_f,1)
    my_features=size(input_f,3)

    for k=0:(batch_per_load-1)
        copyto!(sub_input_images,1,input_images,k*input_hw*input_hw*batch_size+1,input_hw*input_hw*batch_size)
        myout=hg(sub_input_images)
        copyto!(input_f,k*output_hw*output_hw*my_features*batch_size+1,myout[return_ind],1,length(myout[return_ind]))
        for kk=1:length(myout)
            Knet.KnetArrays.freeKnetPtr(myout[kk].ptr)
        end
    end

    nothing
end


module ldtiSubTests

using LinearAlgebra
using Subsystems
using Test

@testset "ldti Subsystem" begin
    A = Matrix{Float64}(I, 2, 2);
    B = reshape([1., 0.], :, 1)
    C = I;

    @testset "defines correct system" begin
        s = LdtiSubsystem(1, A, B, C)
        @test s.A == A
        @test s.C == I
        @test s.B == B
    end

    @testset "implements correct conservation" begin
        s = LdtiSubsystem(1, A, B, C)
        s1 = LdtiSubsystem(2, A, B, C)
        k = [0.1 0; 0 0]

        addNeighbour(s, s1, k, true)
        @test s.A == A - k

        removeNeighbour(s, 2, true)
        @test s.A == A
        addNeighbour(s, s1, k, true)
        setNeighbour(s, s1, 2, 0.2*k, true)
        @test s.A == A - 0.2*k

        s = LdtiSubsystem(1, A, B, C)
        s2 = LdtiSubsystem(3, A, B, C)

        addNeighbour(s, s1, k, true)
        addNeighbour(s, s2, k, true)
        @test s.A == A - 2*k
    end

    @testset "updateE" begin
        s = LdtiSubsystem(1, A, B, C)
        s1 = LdtiSubsystem(2, A, B, C)
        k = [0.1 0; 0 0]

        addNeighbour(s, s1, k)
        @test s.E == k

        removeNeighbour(s, 2)
        @test s.E == zeros(s.nx, 0)
        addNeighbour(s, s1, k)
        setNeighbour(s, s1, 2, 0.2*k)
        @test s.E == 0.2*k

        s = LdtiSubsystem(1, A, B, C)
        s2 = LdtiSubsystem(3, A, B, C)

        addNeighbour(s, s1, k)
        addNeighbour(s, s2, k)
        @test s.E == repeat(k, 1, 2)
    end

    @testset "getNeighbourStates" begin
        @testset "gets the correct state" begin
            s1 = LdtiSubsystem(1, A, B, C)
            s2 = LdtiSubsystem(2, A, B, C)
            s3 = LdtiSubsystem(3, A, B, C)
            Aij = rand(2,2)
            addNeighbour(s1, s2, Aij)
            addNeighbour(s1, s3, Aij)
            x2 = rand(2)
            x3 = rand(2)
            setInitialState(s2, x2)
            setInitialState(s3, x3)
            x1bar = getNeighbourStates(s1)
            @test all(x1bar .≈ vcat(x2, x3))
        end
        @testset "gets correct zero on no neighbours" begin
            s1 = LdtiSubsystem(1, A, B, C)
            x1bar = getNeighbourStates(s1)
            @test x1bar == zero(s1.x)
        end
    end

    @testset "getOutboundWeights" begin
        s1 = LdtiSubsystem(1, A, B, C)
        s2 = LdtiSubsystem(2, A, B, C)
        s3 = LdtiSubsystem(3, A, B, C)
        A1x = rand(2,2)
        A31 = rand(2,2)
        A21 = rand(2,2)
        addNeighbour(s1, s2, A1x)
        addNeighbour(s1, s3, A1x)
        addNeighbour(s2, s1, A21)
        addNeighbour(s3, s1, A31)

        @test getOutboundWeights(s1) == [A21, A31]
    end

    @testset "updateState" begin
        @testset "implements correct state equation" begin
            s = LdtiSubsystem(1, A, B, C)
            updateState(s, 1.0)
            @test s.xnext == [1.,0.]
            @test s.x == [0., 0.]
        end
    end
end

end


"""
nsolpde_test(n)

Test elliptic pde with nsol. Newton and Shamanskii. Query
convergence history, accuracy, agreement.
"""
function nsolpde_test(n)
    h = 1 / (n + 1)
    x = collect(h:h:1.0-h)
    uexact = solexact(x)
    ue = reshape(uexact, (n * n,))
    houtn = NsolPDE(n)
    histn = houtn.history
    npass =
        (length(histn) == 7) &&
        (sum(houtn.stats.iarm) == 2) &&
        (histn[7] / histn[1] < 1.e-13)
    houts = NsolPDE(n; sham = Inf, resdec = 0.1)
    hists = houts.history
    spass =
        (length(hists) == 8) &&
        (sum(houts.stats.iarm) == 2) &&
        (hists[8] / hists[1] < 1.e-7)
    errn = norm(houtn.solution - ue, Inf)
    errs = norm(houts.solution - ue, Inf)
    delsol = norm(houts.solution - houtn.solution, Inf)
    accpass = (errn < 1.e-3) && (errs < 1.e-3) && (delsol < 1.e-8)
    return npass && accpass
end


"Utility functions"

import LinearAlgebra: inv
import ForneyLab: cholinv
import SpecialFunctions: digamma
export noisecov

function cholinv(M::Matrix{Float64})
    return LinearAlgebra.inv(M)
end

function mvdigamma(x, order)
    "Multivariate digamma function (see https://en.wikipedia.org/wiki/Multivariate_gamma_function)"
    result = 0.
    for i = 1:order
        result += digamma(x + (1 - i)/2.)
    end
    return result
end

function noisecov(Δt; dims=2)
    "Construct noise covariance matrix"

    if dims == 1
        Q = Δt
    elseif dims == 2
        Q = [Δt^3/3   Δt^2/2;
             Δt^2/2       Δt]
    elseif dims >= 3
        G = [Δt^n /factorial(n) for n in dims:-1:1]
        g = [Δt^(2n) /factorial(n-1) for n in dims+1:-1:2]
        Q = (G*G' + diagm(g))
    end
    return Q
end


using Distributions, Random

#Simulates with the call gapping policy
function simCallGapping(N,τ,γ,c,tMax; seed = 1974)
    Random.seed!(seed)
    state = [0 for _ in 1:N]
    currentArm = 1
    allowedToSwitch = false
    t = 0.0
    reward = 0.0
    λ, μ = 1.0, 1/γ - 1
    rλ() = rand(Exponential(1/λ))
    rμ() = rand(Exponential(1/μ))
    nextArm(i) = (i%N)+1

    #N+1 event types in this discrete event simulation
    timeJumps = [rλ() for _ in 1:N]
    push!(timeJumps,τ) #The last one is for the call gapping

    while t<tMax
       tLast = t
       t,i = findmin(timeJumps)
       reward += state[currentArm]*(t-tLast)

       if i == N+1 #call gapping timer up
            #println("$(t),$(currentArm): gap reached")
            if state[currentArm] == 0
                #println("$(t),$(currentArm): switching from $(currentArm)")
                currentArm = nextArm(currentArm)
                allowedToSwitch = false
                timeJumps[N+1] = t+τ
                reward -= c
            else
                allowedToSwitch = true
                timeJumps[N+1] = Inf
            end
        else #arm changing state
           if state[i] == 0
              #println("$(t),$(currentArm): Arm $(i) switch to 1")
              state[i] = 1
              timeJumps[i] = t+rμ()
           else #state was 1
               #println("$(t),$(currentArm): Arm $(i) switch to 0")
               state[i] = 0
               timeJumps[i] = t+rλ()
               if allowedToSwitch && state[currentArm] == 0
                    #println("$(t),$(currentArm): switching from $(currentArm)")
                    currentArm = nextArm(currentArm)
                    allowedToSwitch = false
                    timeJumps[N+1] = t+τ
                    reward -= c
               end
           end
       end
    end
    reward/tMax
end


include(joinpath(@__DIR__, "pedagogy_graph.jl"))
using Random

dims = [5, 5]
g = LG.grid(dims)

sfg = PRS.SinkFreeGraph(g)

rng = Random.MersenneTwister(123)
@time sample = PRS.generate_sample_prs(rng, sfg)

path = joinpath(@__DIR__, "output" "sink_free_graph", "sink_free_graph.pdf")
p = plot(sfg, sample, dims, path)


function _posterior(
    latent::Latent1D,
    parameters::Parameters2PL,
    response::Response,
    w::PolyaGammaSample,
)
    sigma2 = (parameters.a^2) * w.val
    sigma2 = 1 / (sigma2 + (1 / Distributions.var(latent.prior)))
    mu =
        sigma2 * (
            parameters.a * (parameters.a * parameters.b * w.val + (response.val - 0.5)) +
            (latent.prior.μ / Distributions.var(latent.prior))
        )
    return Distributions.Normal(mu, sqrt(sigma2))
end

function posterior(
    examinee::AbstractExaminee,
    item::AbstractItem,
    response::Response,
    w::PolyaGammaSample
)
    return _posterior(examinee.latent, item.parameters, response, w)
end

function __posterior(
    latent::Latent1D,
    parameters::Vector{Parameters2PL},
    responses_val::Vector{Float64},
    W_val::Vector{Float64},
)
    prior = latent.prior
    sigma2 = mapreduce((p, w_val) -> (p.a^2) * w_val, +, parameters, W_val)
    sigma2 = 1 / (sigma2 + (1 / Distributions.var(prior)))
    mu =
        sigma2 * (
            mapreduce(
                (p, w_val, r_val) -> p.a * (p.a * p.b * w_val +
                                    #(get_responses_by_item_id(i.id, responses_e)[1].val - 0.5)
                                    (r_val - 0.5)),
                +,
                parameters,
                W_val,
                responses_val,
            ) + (prior.μ / Distributions.var(prior))
        )
    return Distributions.Normal(mu, sqrt(sigma2))::Distributions.ContinuousDistribution
end

function _posterior(
    latent::Latent1D,
    parameters::Vector{Parameters2PL},
    responses::Vector{Response},
    W::Vector{PolyaGammaSample},
)
    prior = latent.prior
    sigma2 = mapreduce((i, w) -> (i.a^2) * w.val, +, parameters, W)
    sigma2 = 1 / (sigma2 + (1 / Distributions.var(prior)))
    mu =
        sigma2 * (
            mapreduce(
                (i, w, r) -> i.a * (i.a * i.b * w.val +
                                    #(get_responses_by_item_id(i.id, responses_e)[1].val - 0.5)
                                    (r.val - 0.5)),
                +,
                parameters,
                W,
                responses,
            ) + (prior.μ / Distributions.var(prior))
        )
    return Distributions.Normal(mu, sqrt(sigma2))::Distributions.ContinuousDistribution
end

function posterior(
    examinee::AbstractExaminee,
    items::Vector{<:AbstractItem}, #only items answered by examinee sorted by i.idx
    responses::Vector{<:AbstractResponse}, #only responses of examinee sorted by i.idx
    W::Vector{PolyaGammaSample};
    already_sorted = false,
    )
    if !already_sorted
        sort!(items, by = i -> i.idx)
        sort!(responses, by = r -> r.item_idx)
        already_sorted = true
    end
    return _posterior(examinee.latent, map(i -> i.parameters, items), responses, W)
end

function update_posterior!(
    examinee::AbstractExaminee,
    items::Vector{<:AbstractItem},
    responses::Vector{<:AbstractResponse},
    W::Vector{PolyaGammaSample};
    already_sorted = false,
    )
    if !already_sorted
        sort!(items, by = i -> i.idx)
        sort!(responses, by = r -> r.item_idx)
        already_sorted = true
    end
    examinee.latent.posterior = posterior(examinee, items, responses, W; already_sorted = already_sorted)
end


function main()
    score = Dict(')' => 3, ']' => 57, '}' => 1197, '>' => 25137)
    opposite = Dict(')' => '(', ']' => '[', '}' => '{', '>' => '<')
    points = Dict('(' => 1, '[' => 2, '{' => 3, '<' => 4)
    s = 0
    scores = Vector{Int}()
    for line in readlines()
        stack = Vector{Char}()
        corrupted = false
        for char in line
            if char in "([{<"
                push!(stack, char)
            else
                last = pop!(stack)
                if opposite[char] != last
                    s += score[char]
                    corrupted = true
                    break
                end
            end
        end
        if !corrupted
            curr = 0
            while !isempty(stack)
                curr = 5curr + points[pop!(stack)]
            end
            if curr != 0
                push!(scores, curr)
            end
        end
    end
    println(s)
    sort!(scores)
    println(scores[(length(scores)+1)÷2])
end

main()


function transit_poly_integrate(r, b, u, N)
    n = length(u)
    if r ≤ b - 1
        return 1.0
    end
    s_1 = max(0, b - r)
    s_2 = min(1, b + r)
    ds = (s_2 - s_1) / N
    s = range(s_1 + 0.5 * ds, s_2 - 0.5 * ds, length=N)

    # normalization
    norm = mapreduce(i -> 2 * u[i] / (2 + 3 * i + i^2), -, 1:n, init=1.0)

    flux = sum(s) do s_j
        if s_j < r - b
            dϕ = 2π
        else
            f = (s_j^2 + b^2 - r^2) / (2 * b * s_j)
            if abs(f) ≈ 1 && abs(f) > 1
                f = clamp(f, -1, 1)
            end
            dϕ = 2 * acos(f)
        end
        z = sqrt(1 - s_j^2)
        iμ = mapreduce(i -> u[i] * (1 - z)^i, -, 1:n, init=1.0)

        return s_j * ds * dϕ * iμ
    end

    flux /= π * norm
    flux = 1 - flux
    return flux
end

@testset "polynomial limb darkening" begin
    r0 = [
        fill(0.01, 5)
        fill(0.1, 5)
        fill(1, 3)
        fill(10, 3)
        fill(100, 3)
    ]
    b0 = [
        0, 0.1, 0.99, 1, 1.01,
        0, 0.1, 0.9, 1, 1.1,
        0, 1, 2,
        9, 10, 11,
        99, 100, 101
    ]
    n = 2 + ceil(Int, rand(rng) * 20)
    u = rand(rng, n)
    u *= rand(rng) / sum(u)

    N = 5000

    ld = PolynomialLimbDark(u)
    flux = @. ld(b0, r0)
    f_num = @. transit_poly_integrate(r0, b0, (u,), N)

    @info "Maximum difference of lightcurve for N=$N: $(maximum(abs,flux-f_num)))"
    @test flux ≈ f_num atol=1e-5

    # test vs. quad limb dark
    ld_ = PolynomialLimbDark(u[1:2])
    ld_q = QuadLimbDark(u[1:2])
    @test ld_.(b0, r0) == ld_q.(b0, r0)

    npts = 1000
    iu = 30
    u_n = ones(iu) / iu
    b0 = @. 3 * ((1:npts) / npts) - 0.5
    ld = PolynomialLimbDark(u_n)
    lc_ana = @. ld(abs(b0), 0.1)
    lc_num = @. transit_poly_integrate(0.1, abs(b0), (u_n,), N)

    @info "Maximum difference of lightcurve for N=$iu: $(maximum(abs,lc_ana-lc_num)))"
    @test lc_ana ≈ lc_num atol=1e-5

end

@testset "QuadLimbDark interface" begin
    # test truncation
    ld = (@test_logs (:warn, "Higher-order terms will be ignored") QuadLimbDark(ones(10)))
    @test ld.n_max == 2
    @test ld.g_n == QuadLimbDark(ones(2)).g_n
end

@testset "IntegratedLimbDark interface" for basis in [:legendre, :radau, :lobatto]
    u = [0.4, 0.26]
    ld = PolynomialLimbDark(u)
    ldt = IntegratedLimbDark(ld, basis=basis)

    @test IntegratedLimbDark(u).driver isa PolynomialLimbDark

    orbit = SimpleOrbit(period=3, duration=1)
    # sharp edges
    ts = [-0.5, -0.25, 0, 0.25, 0.5]
    above_or_below = [-1, 1, 1, 1, -1]

    # test no exposure time is exactly same
    @test ld.(orbit, ts, 0.01) == ldt.(orbit, ts, 0.01)

    # test exposure "smears" signal causing it to be
    # lower or higher then the exact signal
    f = @. ldt(orbit, ts, 0.01, [0.3 0.5])
    base = @. ld(orbit, ts, 0.01)
    @test sign.(f[:, 1] .- base) == above_or_below
    @test sign.(f[:, 2] .- base) == above_or_below
    # test higher exposure time is more smearing
    @test sign.(f[:, 2] .- f[:, 1]) == above_or_below

end

@testset "secondary" begin
    u = [0.4, 0.26]
    d1 = PolynomialLimbDark(u)
    d2 = PolynomialLimbDark(ones(2))
    ld = SecondaryLimbDark(d1, d2; brightness_ratio=0.1)
    ld2 = SecondaryLimbDark(u, ones(2))
    @test ld.primary_driver.g_n == ld2.primary_driver.g_n == d1.g_n
    @test ld.secondary_driver.g_n == ld2.secondary_driver.g_n == d2.g_n

    orbit = SimpleOrbit(period=4, duration=1)
    r = 0.01
    f1 = ld(orbit, 0, r)
    f2 = ld(orbit, 2, r)
    @test f1 ≈ f2 rtol=1e-3
end

@testset "reciprocity" begin
    u = [0.4, 0.26]
    d1 = PolynomialLimbDark(u)
    d2 = PolynomialLimbDark(ones(2))
    ld1 = IntegratedLimbDark(SecondaryLimbDark(d1, d2))
    ld2 = SecondaryLimbDark(IntegratedLimbDark(d1), IntegratedLimbDark(d2))

    orbit = SimpleOrbit(period=4, duration=1)
    r = 0.01
    t = range(-1.5, 1.5, length=1000)
    for texp in [nothing, 0.1, 0.3]
        @test ld1.(orbit, t, r; texp) ≈ ld2.(orbit, t, r; texp)
    end
end


__precompile__(true)
module LightGraphs

using GZip
using Distributions: Binomial
using Base.Collections
using LightXML
using ParserCombinator: Parsers.DOT, Parsers.GML
using StatsBase: fit, Histogram

import Base: write, ==, <, *, ≈, isless, issubset, union, intersect,
            reverse, reverse!, blkdiag, getindex, setindex!, show, print, copy, in,
            sum, size, sparse, eltype, length, ndims, issymmetric, transpose,
            ctranspose, join, start, next, done, eltype, get, A_mul_B!

# core
export SimpleGraph, Edge, Graph, DiGraph, vertices, edges, src, dst,
fadj, badj, in_edges, out_edges, has_vertex, has_edge, is_directed,
nv, ne, add_edge!, rem_edge!, add_vertex!, add_vertices!,
indegree, outdegree, degree, degree_histogram, density, Δ, δ,
Δout, Δin, δout, δin, neighbors, in_neighbors, out_neighbors,
common_neighbors, all_neighbors, has_self_loop, rem_vertex!,

# distance
eccentricity, diameter, periphery, radius, center,

# distance between graphs
spectral_distance, edit_distance,

# edit path cost functions
MinkowskiCost, BoundedMinkowskiCost,

# operators
complement, reverse, reverse!, blkdiag, union, intersect,
difference, symmetric_difference,
induced_subgraph, join, tensor_product, cartesian_product,
crosspath,

# graph visit
SimpleGraphVisitor, TrivialGraphVisitor, LogGraphVisitor,
discover_vertex!, open_vertex!, close_vertex!,
examine_neighbor!, visited_vertices, traverse_graph!, traverse_graph_withlog,

# bfs
BreadthFirst, gdistances, gdistances!, bfs_tree, is_bipartite, bipartite_map,

# dfs
DepthFirst, is_cyclic, topological_sort_by_dfs, dfs_tree,

# random
randomwalk, saw, non_backtracking_randomwalk,

# connectivity
connected_components, strongly_connected_components, weakly_connected_components,
is_connected, is_strongly_connected, is_weakly_connected, period,
condensation, attracting_components, neighborhood, egonet, isgraphical,

# maximum_adjacency_visit
MaximumAdjacency, AbstractMASVisitor, mincut, maximum_adjacency_visit,

# a-star, dijkstra, bellman-ford, floyd-warshall
a_star, dijkstra_shortest_paths,
bellman_ford_shortest_paths, has_negative_edge_cycle, enumerate_paths,
floyd_warshall_shortest_paths,

# centrality
betweenness_centrality, closeness_centrality, degree_centrality,
indegree_centrality, outdegree_centrality, katz_centrality, pagerank,

# spectral
adjacency_matrix,laplacian_matrix, adjacency_spectrum, laplacian_spectrum,
CombinatorialAdjacency, non_backtracking_matrix, incidence_matrix,
nonbacktrack_embedding, Nonbacktracking,
contract,

# astar
a_star,

# persistence
# readgraph, readgraphml, readgml, writegraphml, writegexf, readdot,
load, save,

# flow
maximum_flow, EdmondsKarpAlgorithm, DinicAlgorithm, BoykovKolmogorovAlgorithm, PushRelabelAlgorithm,
multiroute_flow, KishimotoAlgorithm, ExtendedMultirouteFlowAlgorithm,

# randgraphs
erdos_renyi, watts_strogatz, random_regular_graph, random_regular_digraph, random_configuration_model,
StochasticBlockModel, make_edgestream, nearbipartiteSBM, blockcounts, blockfractions,
stochastic_block_model, barabasi_albert, barabasi_albert!, static_fitness_model, static_scale_free,

#community
modularity, core_periphery_deg,
local_clustering,local_clustering_coefficient, global_clustering_coefficient, triangles,
label_propagation, maximal_cliques,

#generators
CompleteGraph, StarGraph, PathGraph, WheelGraph, CycleGraph,
CompleteBipartiteGraph, CompleteDiGraph, StarDiGraph, PathDiGraph, Grid,
WheelDiGraph, CycleDiGraph, BinaryTree, DoubleBinaryTree, RoachGraph, CliqueGraph,

#smallgraphs
smallgraph,

# Euclidean graphs
euclidean_graph

"""An optimized graphs package.

Simple graphs (not multi- or hypergraphs) are represented in a memory- and
time-efficient manner with adjacency lists and edge sets. Both directed and
undirected graphs are supported via separate types, and conversion is available
from directed to undirected.

The project goal is to mirror the functionality of robust network and graph
analysis libraries such as NetworkX while being simpler to use and more
efficient than existing Julian graph libraries such as Graphs.jl. It is an
explicit design decision that any data not required for graph manipulation
(attributes and other information, for example) is expected to be stored
outside of the graph structure itself. Such data lends itself to storage in
more traditional and better-optimized mechanisms.
"""
LightGraphs

include("core.jl")
    include("digraph.jl")
    include("graph.jl")
        include("edgeiter.jl")
        include("traversals/graphvisit.jl")
            include("traversals/bfs.jl")
            include("traversals/dfs.jl")
            include("traversals/maxadjvisit.jl")
            include("traversals/randomwalks.jl")
        include("connectivity.jl")
        include("distance.jl")
        include("edit_distance.jl")
        include("shortestpaths/astar.jl")
        include("shortestpaths/bellman-ford.jl")
        include("shortestpaths/dijkstra.jl")
        include("shortestpaths/floyd-warshall.jl")
        include("spectral.jl")
        include("operators.jl")
        include("persistence/common.jl")
            include("persistence/lg.jl")
            include("persistence/dot.jl")
            include("persistence/gexf.jl")
            include("persistence/gml.jl")
            include("persistence/graphml.jl")
            include("persistence/net.jl")
            include("persistence/jld.jl")
        include("generators/staticgraphs.jl")
            include("generators/randgraphs.jl")
            include("generators/euclideangraphs.jl")
            include("generators/smallgraphs.jl")
        include("centrality/betweenness.jl")
        include("centrality/closeness.jl")
        include("centrality/degree.jl")
        include("centrality/katz.jl")
        include("centrality/pagerank.jl")
        include("community/modularity.jl")
        include("community/label_propagation.jl")
        include("community/core-periphery.jl")
        include("community/clustering.jl")
        include("community/cliques.jl")
        include("flow/maximum_flow.jl")
            include("flow/edmonds_karp.jl")
            include("flow/dinic.jl")
            include("flow/boykov_kolmogorov.jl")
            include("flow/push_relabel.jl")
            include("flow/multiroute_flow.jl")
                include("flow/kishimoto.jl")
                include("flow/ext_multiroute_flow.jl")
        include("utils.jl")

end # module


using BosonSampling
using Test
using JLD

### scattering ###

m = 5
n = 3

interf = RandHaar(m)
i = Input{RandomGramMatrix}(first_modes(n,m))
o = FockDetection(first_modes(n,m))
ev = Event(i,o,interf)

compute_probability!(ev)

ev.proba_params

### bunching ###

m = 5
n = 3

interf = RandHaar(m)
ib = Input{Bosonic}(first_modes(n,m))
ipd = Input{RandomGramMatrix}(first_modes(n,m))
subset_modes = first_modes(n,m)

typeof(subset_modes)

pb = full_bunching_probability(interf, ib, subset_modes)
ppd = full_bunching_probability(interf, ipd, subset_modes)

@test pb/ppd > 1. # this doesn't HAVE TO pass but will pass in nearly all
# cases

### Classical sampling ###

m = 16
n = 3

input = Input{Distinguishable}(first_modes(n,m))
interf = RandHaar(m)
out = classical_sampler(input=input, interf=interf)

## Cliffords sampler ###

m = 16
n = 3
input = Input{Bosonic}(first_modes(n,m))
interf = RandHaar(m)
out = cliffords_sampler(input=input, interf=interf)

## Noisy sampling ###

m = 16
n = 3

x = 0.8 # distinguishability
η = 0.8 # reflectivity

input = Input{OneParameterInterpolation}(first_modes(n,m), x)
interf = RandHaar(m)

out = noisy_sampler(input=input, reflectivity=η, interf=interf)

### MIS sampling ###

n = 8
m = n^2
starting_state = zeros(Int, m)
input_state = first_modes_array(n,m)

U = copy(rand_haar(m))

# generate a collisionless state as a starting point
starting_state = iterate_until_collisionless(() -> random_occupancy(n,m))

known_pdf(state) = process_probability_distinguishable(U, input_state, state)
target_pdf(state) = process_probability(U, input_state, state)
known_sampler = () -> iterate_until_collisionless(() -> classical_sampler(U, m, n)) # gives a classical sampler

# samples = metropolis_sampler(;target_pdf = target_pdf, known_pdf = known_pdf, known_sampler = known_sampler, starting_state = starting_state, n_iter = 100)

### Noisy distribution ###

n = 3
m = 6
x = 0.8
η = 0.8

input = Input{OneParameterInterpolation}(first_modes(n,m), x)
interf = RandHaar(m)

output_statistics = noisy_distribution(input=input, reflectivity=η, interf=interf)
p_exact = output_statistics[1]
p_approx = output_statistics[2]
p_sampled = output_statistics[3]

### Theoretical distribution ###

n = 3
m = 6

input = Input{Bosonic}(first_modes(n,m))
interf = RandHaar(m)
output_distribution = theoretical_distribution(input=input, interf=interf)

### Usage Interferometer ###

B = BeamSplitter(1/sqrt(2))
n = 2 # photon number
m = 2 # mode number
proba_bunching = Vector{Float64}(undef, 0)

for x = 0.0:0.01:1.0
    local input = Input{OneParameterInterpolation}(first_modes(n,m), x)
    p_theo = theoretical_distribution(input=input, interf=B)

    push!(proba_bunching, p_theo[2]) # store the probabilty to observe one photon in each mode
end

### subsets ###

s1 = Subset([1,1,0,0,0])
s2 = Subset([0,0,1,1,0])
s3 = Subset([1,0,1,0,0])

"subsets are not allowed to overlap"

# check_subset_overlap([s1,s2,s3]) will fail

### HOM tests: one mode ###

input_state = Input{Bosonic}(first_modes(n,m))

set1 = [1,0]
physical_interferometer = Fourier(m)
part = Partition([Subset(set1)])

(physical_indexes, pdf) = compute_probabilities_partition(physical_interferometer, part, input_state)

### HOM tests: mode1, mode2 ###

n = 2
m = 2

input_state = Input{Bosonic}(first_modes(n,m))

set1 = [1,0]
set2 = [0,1]
physical_interferometer = Fourier(m)
part = Partition([Subset(set1), Subset(set2)])

(physical_indexes, pdf) = compute_probabilities_partition(physical_interferometer, part, input_state)

print_pdfs(physical_indexes, pdf,n; partition_spans_all_modes = true, physical_events_only = true)

# for a single count

part_occ = PartitionOccupancy(ModeOccupation([1,1]),2,part)

compute_probability_partition_occupancy(physical_interferometer, part_occ, input_state)

# the same using an event

PartitionCount(part_occ)

o = PartitionCount(part_occ)
ev = Event(input_state, o, physical_interferometer)
############ need to change the constructor of Event

get_parametric_type(input_state)
get_parametric_type(o)

length(collect(typeof(o).parameters))
typeof(o)
collect(typeof(input_state).parameters)

### multiset for a random interferometer ###

m = 4
n = 3

inp = Input{Bosonic}(first_modes(n,m))
set1 = zeros(Int,m)
set2 = zeros(Int,m)
set1[1:2] .= 1
set2[3:4] .= 1

physical_interferometer = RandHaar(m)
part = Partition([Subset(set1), Subset(set2)])

(physical_indexes,  pdf) = compute_probabilities_partition(physical_interferometer, part, inp)
fourier_indexes = copy(physical_indexes)

print_pdfs(physical_indexes, pdf, n; physical_events_only = true, partition_spans_all_modes = true)
#print_pdfs(physical_indexes,  probas_fourier, n)

### partitions, subsets ###

n = 2
m = 5

s1 = Subset([1,1,0,0,0])
s2 = Subset([0,0,1,1,0])

part = Partition([s1,s2])
part_occ = PartitionOccupancy(ModeOccupation([2,0]),n,part)

i = Input{Bosonic}(first_modes(n,m))
o = PartitionCount(part_occ)
interf = RandHaar(m)
ev = Event(i,o,interf)

compute_probability!(ev)

### multiple counts probabilities ###

m = 10
n = 3
set1 = zeros(Int,m)
set2 = zeros(Int,m)
set1[1:2] .= 1
set2[3:4] .= 1

interf = RandHaar(m)
part = Partition([Subset(set1), Subset(set2)])

i = Input{Bosonic}(first_modes(n,m))
o = PartitionCountsAll(part)
ev = Event(i,o,interf)

compute_probability!(ev)


# tid = parse(Int64, ARGS[1])
force_scale = 6.0 #50
tid = 203
fiber_size = 2
porder = 2
if length(ARGS) == 4
   global tid = parse(Int64, ARGS[1])
   global force_scale = parse(Float64, ARGS[2])
   global fiber_size = parse(Int64, ARGS[3])
   global porder = parse(Int64, ARGS[4])
end
printstyled("force_scale=$force_scale, tid=$tid, fiber_size=$fiber_size, porder=$porder\n", color=:green)
include("CommonFuncs.jl")
np = pyimport("numpy")
"""
Property:
The matrix is made of titanium, which are assumed to be elasto-plastic titanium material,
time ms
ρ = 4.5 g/cm^3;  E = 100GPa = 	100*10^9 Kg/m/s^2 = 10^6 g/cm/ms^2, K=10e+9Pa =  10^5 g/cm/ms^2
ν =0.2   σY=970 MPa = 9700

Fibers are made of SiC, which are assumed to be isotropic and elastic, with
https://www.azom.com/properties.aspx?ArticleID=42
ρ = 3.2 g/cm^3  E = 400GPa =  4*10^6 g/cm/ms^2  ν = 0.35
length scale cm
"""
prop1 = Dict("name"=> "PlaneStressPlasticity","rho"=> 4.5, "E"=> 1e+6, "nu"=> 0.2,
"sigmaY"=>0.97e+4, "K"=>1e+5)
prop2 = Dict("name"=> "PlaneStress", "rho"=> 3.2, "E"=>4e6, "nu"=>0.35)

# prop1 = prop2
ps1 = PlaneStress(prop1); H1 = ps1.H
ps2 = PlaneStress(prop2); H2 = ps2.H

T = 200.0
NT = 200

nxc, nyc = 80,40
nx, ny =  nxc*fiber_size, nyc*fiber_size
#Type 1=> SiC(fiber), type 0=>Ti(matrix), each fiber has size is k by k
fiber_fraction = 0.25
fiber_distribution = "Uniform"
ele_type = generateEleType(nxc, nyc, fiber_size, fiber_fraction, fiber_distribution)
@show "fiber fraction: ",  sum(ele_type)/(nx*ny)
# matshow(ele_type)
# savefig("test.png")
# error()

nodes, EBC, g, gt, FBC, fext, ft, npoints, node_to_point = BoundaryCondition(tid, nx, ny, porder; force_scale=force_scale)
elements = []
for j = 1:ny
    for i = 1:nx
        n = (nx*porder+1)*(j-1)*porder + (i-1)porder+1
        #element (i,j)
        if porder == 1
            #   4 ---- 3
            #
            #   1 ---- 2

            elnodes = [n, n + 1, n + 1 + (nx + 1), n + (nx + 1)]
        elseif porder == 2
            #   4 --7-- 3
            #   8   9   6
            #   1 --5-- 2
            elnodes = [n, n + 2, n + 2 + 2*(2*nx+1),  n + 2*(2*nx+1), n+1, n + 2 + (2*nx+1), n + 1 + 2*(2*nx+1), n + (2*nx+1), n+1+(2*nx+1)]
        else
            error("polynomial order error, porder= ", porder)
        end

        coords = nodes[elnodes,:]
        # 0=> matrix, 1=> fiber
        prop = ele_type[i,j] == 0 ? prop1 : prop2
        push!(elements,SmallStrainContinuum(coords,elnodes, prop, 3))
    end
end

ndofs = 2
domain = Domain(nodes, elements, ndofs, EBC, g, FBC, fext)
setGeometryPoints!(domain, npoints, node_to_point)

state = zeros(domain.neqs)
∂u = zeros(domain.neqs)
globdat = GlobalData(state,zeros(domain.neqs), zeros(domain.neqs),∂u, domain.neqs, gt, ft)

assembleMassMatrix!(globdat, domain)
updateStates!(domain, globdat)

Δt = T/NT

ρ_oo = 0.0
αm = (2*ρ_oo - 1)/(ρ_oo + 1)
αf = ρ_oo/(ρ_oo + 1)

for i = 1:NT
    @info i, "/" , NT

    solver = NewmarkSolver(Δt, globdat, domain, αm, αf, 1e-4, 1e-6, 10)
    #solver = NewmarkSolver(Δt, globdat, domain, 0.5, 0.5, 1e-4, 1e-6, 10)
    # close("all")
    # visσ(domain)
    # axis("equal")
    # savefig("Debug/$i.png")
    # error()
    # close("all")
    # visσ(domain)
    # # visσ(domain,-1.5e9, 4.5e9)
    # savefig("Debug/terminal$(tid)i=$i.png")
    # if i==75
    #     close("all")
    #     visσ(domain)
    #     # visσ(domain,-1.5e9, 4.5e9)
    #     savefig("Debug/terminal$(tid)i=75.png")
    # end
end

# error()
# todo write data
if !isdir("$(@__DIR__)/Data/order$porder")
    mkdir("$(@__DIR__)/Data/order$porder")
end
if !isdir("$(@__DIR__)/Debug/order$porder")
    mkdir("$(@__DIR__)/Debug/order$porder")
end

write_data("$(@__DIR__)/Data/order$porder/$(tid)_$(force_scale)_$(fiber_size).dat", domain)
@save "Data/order$porder/domain$(tid)_$(force_scale)_$(fiber_size).jld2" domain

close("all")
visσ(domain)
axis("equal")
savefig("Debug/order$porder/terminal$(tid)_$(force_scale)_$(fiber_size).png")

close("all")
ux = [reshape(domain.history["state"][i][1:(nx*porder+1)*(ny*porder+1)], ny*porder+1, nx*porder+1)[1,end] for i = 1:length(domain.history["state"])]
plot(ux)
savefig("Debug/order$porder/ux$(tid)_$(force_scale)_$(fiber_size).png")

close("all")
uy = [reshape(domain.history["state"][i][(nx*porder+1)*(ny*porder+1)+1:end], ny*porder+1, nx*porder+1)[1,end] for i = 1:length(domain.history["state"])]
plot(uy)
savefig("Debug/order$porder/uy$(tid)_$(force_scale)_$(fiber_size).png")


using LinearAlgebra, BesselK, BenchmarkTools, Printf, StaticArrays
include("shared.jl")

const GRIDN  = 24
const GRID1D = range(0.0, 1.0, length=GRIDN)
const PTS    = map(x->SVector{2,Float64}(x[1], x[2]),
                   vec(collect.(Iterators.product(GRID1D, GRID1D))))

function checkcovmat(fn, p, v)
  _p     = pv(1.0, p, v)
  M      = assemble_matrix(fn, PTS, _p)
  em     = eigmin(M)
  Mf     = cholesky!(M, check=false)
  if issuccess(Mf)
    ld = logdet(Mf)
  else
    ld = NaN
  end
  (issuccess(Mf) ? "S" : "F", em, ld)
end

const VRANGE = (0.4, 1.25, 3.5)
const PRANGE = (0.01, 1.0, 100.0)

# For now, I might even keep the timing in seconds.
for (j, (v, p)) in enumerate(Iterators.product(VRANGE, PRANGE))
  _p  = pv(1.0, p, v)
  # test 1: assembly time.
  t_u = @belapsed assemble_matrix(matern_us,   $PTS, $_p) samples=8
  t_a = @belapsed assemble_matrix(matern_amos, $PTS, $_p) samples=8
  # test 2: Cholesky success or failure:
  (s_u, em_u, ld_u) = checkcovmat(matern_us,   p, v)
  (s_a, em_a, ld_a) = checkcovmat(matern_amos, p, v)
  # PRINTING:
  # (v,p) pair
  # times (u,p)
  # eigmins (u,p)
  # eigmin difference
  # logdets (u.p)
  # logdet difference
  if j != length(VRANGE)*length(PRANGE)
    @printf "(%1.3f, %1.2f) & %1.1e & %1.1e & %1.2e & %1.2e & %1.2e & %1.2e & %1.2e & %1.2e \\\\\n" p v t_u t_a em_u em_a abs(em_u-em_a) ld_u ld_a abs(ld_u-ld_a)
  else
    @printf "(%1.3f, %1.2f) & %1.1e & %1.1e & %1.2e & %1.2e & %1.2e & %1.2e & %1.2e & %1.2e\n" p v t_u t_a em_u em_a abs(em_u-em_a) ld_u ld_a abs(ld_u-ld_a)
  end
end


#Tests steady state finders for minimal reaction network.
for rn in min_reaction_networks_standard
    addequi1!(rn)
    addequi2!(rn)
    p_vals = map(amp->amp*rand(length(rn.params)),[1.,10.,100.])
    for p in p_vals
        fps = steady_states(rn,p)
        stab = map(fp->stability(fp,rn,p), fps)
        for i=1:length(fps)
            stab[i] ? (tend = 10) : (tend = -10)
            end_point = OrdinaryDiffEq.solve(ODEProblem(rn,fps[i],(0.,tend),p),Rosenbrock23())[end]
            @test maximum(end_point-fps[i])<0.0001
        end
    end
end


type ChangepointSampler <: Sampleable
    rand_dist_gen::Function                  # Function which generates a random distribution
    λ::Int                                   # Frequency of changepoints
    changepoints::Array{Int64,1}               # Changepoints indices
    segment_params::Array{Any}               # Params of segments
    # Current state of sampler
    _counter::Int                            # number of changepoints
    _current_dist::Sampleable                # Current distribution
    _next_change::Int                        # Index of next changepoint
end

function ChangepointSampler(rand_dist_gen, λ::Int = 10)
    _current_dist = rand_dist_gen()
    self = ChangepointSampler(rand_dist_gen, λ, [], [], 0, rand_dist_gen(), rand(Poisson(λ)) + 1)
    push!(self.changepoints, 0)
    push!(self.segment_params, params(_current_dist))
    self
end

function rand(dist::ChangepointSampler, n::Int)
    s = 0
    x = Array(Float64, n)
    while s < n
        t = min(dist._next_change - dist._counter - 1, n)
        x[s+1:t] = rand(dist._current_dist, t - s)
        # print(x[s+1:t])
        if t == dist._next_change - dist._counter - 1
            dist._next_change += 1 + rand(Poisson(dist.λ))
            dist._current_dist = dist.rand_dist_gen()
            # Sample and store new change parameters
            push!(dist.changepoints, dist._counter + t)
            push!(dist.segment_params, params(dist._current_dist))
        end
        s = t
    end
    dist._counter += n
    return x
end

cp_rand(dist::ChangepointSampler, n::Int) = rand(dist, n), dist.changepoints

function ran_dist(dist_type::Type, args...)
    newargs = Any[]
    for a in args[2:end]
        if isa(a, Sampleable)
            push!(newargs, rand(a))
        else push!(newargs, a) end
    end
        return dist_type(newargs...)
end

macro changepoint_sampler(n, λ, dist)
    if !isexpr(dist, :call)
        error("Syntax error: expected distribution construction in second argument")
    end

    if length(dist.args) == 1
        return Expr(:call, :rand, dist, n)
    else
        nargs = length(dist.args)
        args = Array(Any, nargs)
        return esc(:(Changepoints.cp_rand(ChangepointSampler(()->Changepoints.ran_dist($(dist.args...)), $(λ)), $(n))))
    end
end


"""
Reference:
1. Structure Discovery in Nonparametric Regression through Compositional Kernel Search
		David Duvenaud, James Robert Lloyd, Roger Grosse, Joshua B. Tenenbaum, Zoubin Ghahramani (2013)

2. https://www.cs.toronto.edu/~duvenaud/cookbook/

3. GaussianProcesses.jl: A Nonparametric Bayes package for the Julia Language
"""

"""
RBF Kernel
"""
struct ArdGaussKernel{T, VT<:AbstractVector{T}}
	l::VT
	σ::VT
end
@functor ArdGaussKernel
function (GK::ArdGaussKernel{T, VT})(x; λ=T(1e-6)) where {T, VT}
	length(GK.l) == size(x, 1) || throw(DimensionMismatch("size of length scale parameter should be the same as number of features"))
	n = size(x, 2)
	scaled_x = x ./ GK.l
	d = square_metric(scaled_x)
	σ_square = GK.σ[1]*GK.σ[1]
	(@. σ_square*exp(-0.5*d)) + Diagonal(λ*ones(n))
end
function (GK::ArdGaussKernel{T, VT})(x, xo) where {T, VT}
	σ_square = GK.σ[1]*GK.σ[1]
	scaled_x = x ./ GK.l
	scaled_xo = xo ./ GK.l
	d = square_metric(scaled_x, scaled_xo)
	@. σ_square*exp(-0.5*d)
end

function ArdGaussKernel(n_features::Int)
	l = rand(n_features)
	σ = rand(1)
	GaussKernel(l, σ)
end

reset(GK::ArdGaussKernel) = ArdGaussKernel(length(GK.l))

## derived kernel
struct IsoGaussKernel{T, VT<:AbstractVector{T}}
	l::VT
	σ::VT
end
@functor IsoGaussKernel
function (IsoGK::IsoGaussKernel{T, VT})(x; λ=T(1e-6)) where {T, VT}
	n = size(x, 2)
	scaled_x = x ./ IsoGK.l[1]
	d = square_metric(scaled_x)
	σ_square = IsoGK.σ[1]*IsoGK.σ[1]
	(@. σ_square*exp(-0.5*d)) + Diagonal(λ*ones(n))
end
function (IsoGK::IsoGaussKernel{T, VT})(x, xo) where {T, VT}
	σ_square = IsoGK.σ[1]*IsoGK.σ[1]
	scaled_x = x ./ IsoGK.l[1]
	scaled_xo = xo ./ IsoGK.l[1]
	d = square_metric(scaled_x, scaled_xo)
	@. σ_square*exp(-0.5*d)
end

function IsoGaussKernel()
	l = rand(1)
	σ = rand(1)
	IsoGaussKernel(l, σ)
end

reset(IsoGK::IsoGaussKernel) = IsoGaussKernel()

"""
Rational quardratic kernel
"""
struct ArdRQKernel{T, VT<:AbstractVector{T}}
	l::VT
	α::VT
	σ::VT
end
@functor ArdRQKernel
function (RQK::ArdRQKernel{T, VT})(x; λ=T(1e-6)) where {T, VT}
	length(RQK.l) == size(x, 1) || throw(DimensionMismatch("size of length scale parameter should be the same as number of features"))
	n = size(x, 2)
	scaled_x = x ./ RQK.l
	d = square_metric(scaled_x)
	σ_square = RQK.σ[1]*RQK.σ[1]
	(@. σ_square*exp((-RQK.α[1])*log(1.0 + 0.5*d/RQK.α[1]))) + Diagonal(λ*ones(n))
end
function (RQK::ArdRQKernel{T, VT})(x, xo) where {T, VT}
	σ_square = RQK.σ[1]*RQK.σ[1]
	scaled_x = x ./ RQK.l
	scaled_xo = xo ./ RQK.l
	d = square_metric(scaled_x, scaled_xo)
	@. σ_square*exp((-RQK.α[1])*log(1.0 + 0.5*d/RQK.α[1]))
end

function ArdRQKernel(n_features::Int)
	l = rand(n_features)
	α = rand(1)
	σ = rand(1)
	RQKernel(l, α, σ)
end

reset(RQK::ArdRQKernel) = ArdRQKernel(length(RQK.l))

## derived kernel
struct IsoRQKernel{T, VT<:AbstractVector{T}}
	l::VT
	α::VT
	σ::VT
end
@functor IsoRQKernel
function (IsoRQK::IsoRQKernel{T, VT})(x; λ=T(1e-6)) where {T, VT}
	n = size(x, 2)
	scaled_x = x ./ IsoRQK.l[1]
	d = square_metric(scaled_x)
	σ_square = IsoRQK.σ[1]*IsoRQK.σ[1]
	(@. σ_square*exp((-IsoRQK.α[1])*log(1.0 + 0.5*d/IsoRQK.α[1]))) + Diagonal(λ*ones(n))
end
function (IsoRQK::IsoRQKernel{T, VT})(x, xo) where {T, VT}
	σ_square = IsoRQK.σ[1]*IsoRQK.σ[1]
	scaled_x = x ./ IsoRQK.l[1]
	scaled_xo = xo ./ IsoRQK.l[1]
	d = square_metric(scaled_x, scaled_xo)
	@. σ_square*exp((-IsoRQK.α[1])*log(1.0 + 0.5*d/IsoRQK.α[1]))
end

function IsoRQKernel()
	l = rand(1)
	α = rand(1)
	σ = rand(1)
	IsoRQKernel(l, α, σ)
end

reset(IsoRQK::IsoRQKernel) = IsoRQKernel()

"""
Iso Periodic kernel
"""
struct IsoPeriodKernel{T, VT<:AbstractVector{T}}
	p::VT
	l::VT
	σ::VT
end
@functor IsoPeriodKernel
function (IsoPK::IsoPeriodKernel{T, VT})(x; λ=T(1e-6)) where {T, VT}
	n = size(x, 2)
	scaled_x = x ./ IsoPK.p[1]
	d = norm2_metric(scaled_x)
	σ_square = IsoPK.σ[1]*IsoPK.σ[1]
	l_square = IsoPK.l[1]*IsoPK.l[1]
	(@. σ_square*exp(-2.0*sinpi(d)*sinpi(d)/l_square)) + Diagonal(λ*ones(n))
end
function (IsoPK::IsoPeriodKernel{T, VT})(x, xo) where {T, VT}
	scaled_x = x ./ IsoPK.p[1]
	scaled_xo = xo ./ IsoPK.p[1]
	d = norm2_metric(scaled_x, scaled_xo)
	σ_square = IsoPK.σ[1]*IsoPK.σ[1]
	l_square = IsoPK.l[1]*IsoPK.l[1]
	@. σ_square*exp(-2.0*sinpi(d)*sinpi(d)/l_square)
end

function IsoPeriodKernel()
	p = 1.0 ./ rand(1)
	l = rand(1)
	σ = rand(1)
	IsoPeriodKernel(p, l, σ)
end

reset(IsoPK::IsoPeriodKernel) = IsoPeriodKernel()

"""
Linear Kernel
"""
struct ArdLinearKernel{T, VT<:AbstractVector{T}}
	σb::VT
	σv::VT
	c::VT
end
@functor ArdLinearKernel
function (LK::ArdLinearKernel{T, VT})(x; λ=T(1e-6)) where {T, VT}
	length(LK.c) == size(x, 1) && length(Lk.σv) == size(x, 1) || throw(DimensionMismatch("size of offset parameter should be the same as number of features"))
	n = size(x, 2)
	shifted_x = x .- LK.c
	scaled_shifted_x = shifted_x ./ LK.σv
	d = inner_prod_metric(scaled_shifted_x)
	σb_square = LK.σb[1]*LK.σb[1]
	(@. d+σb_square) + Diagonal(λ*ones(n))
end
function (LK::ArdLinearKernel{T, VT})(x, xo) where {T, VT}
	shifted_x = x .- LK.c
	scaled_shifted_x = shifted_x ./ LK.σv
	shifted_xo = xo .- LK.c
	scaled_shifted_xo = shifted_xo ./ LK.σv
	d = inner_prod_metric(scaled_shifted_x, scaled_shifted_xo)
	σb_square = LK.σb[1]*LK.σb[1]
	d+σb_square
end

function ArdLinearKernel(n_features::Int)
	σb = rand(1)
	σv = rand(n_features)
	c  = rand(n_features)
	LinearKernel(σb, σv, c)
end

reset(LK::ArdLinearKernel) = ArdLinearKernel(length(LK.c))

"""
White noise kernel
"""
struct WhiteNoiseKernel{T, VT<:AbstractVector{T}}
	σ::VT
end
@functor WhiteNoiseKernel
function (WNK::WhiteNoiseKernel{T, VT})(x) where {T, VT}
	n = size(x, 2)
	σ_square = WNK.σ[1]*WNK.σ[1]
	Diagonal(σ_square*ones(n))
end

function WhiteNoiseKernel()
	σ = rand(1)
	WhiteNoiseKernel(σ)
end

reset(WNK::WhiteNoiseKernel) = WhiteNoiseKernel()

"""
Constant Kernel (suffers PSD problem !!!)
"""
struct ConstantKernel{T, VT<:AbstractVector{T}}
	σ::VT
end
@functor ConstantKernel
function (CK::ConstantKernel{T, VT})(x) where {T, VT}
	n = size(x, 2)
	CK.σ[1]*ones(n, n)
end

function ConstantKernel()
	σ = rand(1)
	ConstantKernel(σ)
end

reset(CK::ConstantKernel) = ConstantKernel()


abstract type RotationType{T} end

"""
    Rotation(Quaternion(1., 0., 0., 0.))

Rotation around the origin implemented from a rotation type `R`.
`R` can be any kind of rotation which implements a method for
`rotate(x, r::R) where {R<:RotationType}`. In 3D, this can be
`Quaternion` or `Euler`, for example.
"""
struct Rotation{T,R<:RotationType{T}} <: Transform
  rot::R
end
(r::Rotation)(p::AbstractVector) = r.rot * p

(≈)(x::Rotation, y::Rotation) = x.rot ≈ y.rot

inv(r::Rotation) = typeof(r)(-r.rot)

const Rotated{O,R<:Rotation} = Transformed{O,R}
Rotated(obj::O, transf::R) where {O,R} = Rotated{O,R}(obj, transf)

struct Quaternion{T} <: RotationType{T}
  coords::SVector{4,T}
end

Quaternion(coords::AbstractArray) = Quaternion(SVector{length(coords),eltype(coords)}(coords))
Quaternion(coords::Vararg{T}) where {T} = Quaternion(SVector{length(coords),T}(coords))


# Invertible CNN layer from Dinh et al. (2017)/Kingma & Dhariwal (2019)
# Author: Philipp Witte, pwitte3@gatech.edu
# Date: January 2020

using InvertibleNetworks, LinearAlgebra, Test

# Input
nx = 64
ny = 64
k = 20
n_in = 10
n_hidden = 20
batchsize = 2
type = "Flux"   # Flux CNN or residual block

# Input image
X = glorot_uniform(nx, ny, k, batchsize)
X0 = glorot_uniform(nx, ny, k, batchsize)

# 1x1 convolution and residual blocks
C = Conv1x1(k)

if type == "Flux"
    # Flux residual block (needs twice the number of output as input channels)
    model = Chain(
        Conv((3,3), n_in => n_hidden; pad=1),
        BatchNorm(n_hidden, relu),
        Conv((3,3), n_hidden => n_hidden; pad=1),
        BatchNorm(n_hidden, relu),
        Conv((3,3), n_hidden => n_hidden; pad=1),
        BatchNorm(n_hidden, relu)
    )
    RB = FluxBlock(model)
else
    RB = ResidualBlock(nx, ny, n_in, n_hidden, batchsize; fan=true)
end

# Invertible splitting layer
L = CouplingLayerGlow(C, RB; logdet=true)   # compute logdet

# Forward + backward
Y = L.forward(X)[1]
Y0, logdet = L.forward(X0)
ΔY = Y0 - Y
ΔX, X0_ = L.backward(ΔY, Y0)

@test isapprox(norm(X0_ - X0)/norm(X0), 0f0, atol=1f-2)


abstract type Copula{T} end
value_eltype(::Copula{T}) where T = T

"""
    simulate_copula(t::Int, copula::Copula; rng::AbstractRNG = Random.GLOBAL_RNG)

Returns t realizations from a given copula

```jldoctest
julia> using Random

julia> Random.seed!(43);

julia> simulate_copula(2, Gumbel_cop(3, 1.5))
2×3 Array{Real,2}:
 0.740038  0.918928  0.950674
 0.637826  0.483514  0.123949

julia> Random.seed!(43);

julia> simulate_copula(3, Clayton_cop(2, 1.))
3×2 Array{Float64,2}:
 0.562482  0.896247
 0.968953  0.731239
 0.749178  0.38015

 julia> Random.seed!(43);

 julia> simulate_copula(2, Clayton_cop(2, -.5))
 2×2 Array{Float64,2}:
  0.180975  0.818017
  0.888934  0.863358

julia> Random.seed!(43);

julia> simulate_copula(2, Gumbel_cop_rev(3, 1.5))
2×3 Array{Real,2}:
 0.259962  0.081072  0.0493259
 0.362174  0.516486  0.876051

  julia> Random.seed!(43);

 julia> simulate_copula(2, Clayton_cop_rev(2, -0.5))
 2×2 Array{Float64,2}:
   0.819025  0.181983
   0.111066  0.136642

julia> Random.seed!(43);

julia> simulate_copula(4, AMH_cop(2, 0.5))
4×2 Array{Float64,2}:
 0.483939  0.883911
 0.962064  0.665769
 0.707543  0.25042
 0.915491  0.494523

julia> Random.seed!(43);

julia> simulate_copula(4, AMH_cop(2, -0.5))
4×2 Array{Float64,2}:
 0.180975  0.820073
 0.888934  0.886169
 0.408278  0.919572
 0.828727  0.335864

julia> Random.seed!(43);

julia> simulate_copula(4, AMH_cop_rev(2, 0.5))
4×2 Array{Float64,2}:
 0.516061   0.116089
 0.0379356  0.334231
 0.292457   0.74958
 0.0845089  0.505477

julia> simulate_copula(4, AMH_cop_rev(2, -0.5))
4×2 Array{Float64,2}:
 0.819025  0.179927
 0.111066  0.113831
 0.591722  0.0804284
 0.171273  0.664136

julia> Random.seed!(43);

julia> simulate_copula(4, Frank_cop(2, 3.5))
4×2 Array{Float64,2}:
 0.650276  0.910212
 0.973726  0.789701
 0.690966  0.358523
 0.747862  0.29333

julia> Random.seed!(43);

julia> simulate_copula(4, Frank_cop(2, 0.2, SpearmanCorrelation))
4×2 Array{Float64,2}:
 0.504123  0.887296
 0.962936  0.678791
 0.718628  0.271543
 0.917759  0.51439

julia> Random.seed!(43);

julia> c = Chain_of_Archimedeans([4., 11.], "frank")
Chain_of_Archimedeans(3, [4.0, 11.0], ["frank", "frank"])

julia> simulate_copula(1, c)
1×3 Array{Float64,2}:
 0.180975  0.492923  0.679345

julia> c = Chain_of_Archimedeans([.5, .7], ["frank", "clayton"], KendallCorrelation)
Chain_of_Archimedeans(3, [5.736282707019972, 4.666666666666666], ["frank", "clayton"])

julia> Random.seed!(43);

julia> simulate_copula(1, c)
1×3 Array{Float64,2}:
 0.180975  0.408582  0.646887

julia> Random.seed!(43)

julia> simulate_copula(10, Chain_of_Frechet([0.6, 0.4], [0.3, 0.5]))
  10×3 Array{Float64,2}:
  0.996764  0.996764  0.996764
  0.204033  0.795967  0.204033
  0.979901  0.979901  0.0200985
  0.120669  0.879331  0.120669
  0.453027  0.453027  0.453027
  0.800909  0.199091  0.800909
  0.54892   0.54892   0.54892
  0.933832  0.933832  0.0661679
  0.396943  0.396943  0.396943
  0.804096  0.851275  0.955881

julia> Random.seed!(43);

julia> simulate_copula(10, Gaussian_cop([1. 0.5; 0.5 1.]))
10×2 Array{Real,2}:
 0.589188  0.815308
 0.708285  0.924962
 0.747341  0.156994
 0.227634  0.183116
 0.227575  0.957376
 0.271558  0.364803
 0.445691  0.52792
 0.585362  0.23135
 0.498593  0.48266
 0.190283  0.594451

julia> Random.seed!(43);

julia> simulate_copula(10, Student_cop([1. 0.5; 0.5 1.], 10))
10×2 Array{Real,2}:
 0.658199  0.937148
 0.718244  0.92602
 0.809521  0.0980325
 0.263068  0.222589
 0.187187  0.971109
 0.245373  0.346428
 0.452336  0.524498
 0.57113   0.272525
 0.498443  0.48082
 0.113788  0.633349

julia> Random.seed!(43);

julia> f = Frechet_cop(3, 0.5)
Frechet_cop(3, 0.5, 0.0)

julia> simulate_copula(1, f)
1×3 Array{Real,2}:
0.180975  0.775377  0.888934

julia> Random.seed!(43);

julia> f = Frechet_cop(2, 0.5, 0.2)
Frechet_cop(2, 0.5, 0.2)

julia> simulate_copula(1, f)
1×2 Array{Real,2}:
0.180975  0.775377

julia> Random.seed!(43);

julia> cop = Marshall_Olkin_cop([1.,2.,3.])
Marshall_Olkin_cop(2, [1.0, 2.0, 3.0])

julia> simulate_copula(1, cop)
1×2 Array{Float64,2}:
  0.854724  0.821831

julia> Random.seed!(43);

julia> c = Hierarchical_Gumbel_cop([5., 4., 3.])
Hierarchical_Gumbel_cop(4, [5.0, 4.0, 3.0])

julia> simulate_copula(3, c)
3×4 Array{Float64,2}:
 0.100353  0.207903  0.0988337  0.0431565
 0.347417  0.217052  0.223734   0.042903
 0.73617   0.347349  0.168348   0.410963

julia> Random.seed!(43);

julia> c1 = Clayton_cop(2, 2.)
Clayton_cop(2, 2.0)

julia> c2 = Clayton_cop(2, 3.)
Clayton_cop(2, 3.0)

julia> cp = Nested_Clayton_cop([c1, c2], 1, 1.1)
Nested_Clayton_cop(Clayton_cop[Clayton_cop(2, 2.0), Clayton_cop(2, 3.0)], 1, 1.1)

julia> simulate_copula(4, cp)
4×5 Array{Float64,2}:
0.514118  0.84089   0.870106  0.906233  0.739349
0.588245  0.85816   0.935308  0.944444  0.709009
0.59625   0.665947  0.483649  0.603074  0.153501
0.200051  0.304099  0.242572  0.177836  0.0851603

julia> c1 = AMH_cop(2, .7)
AMH_cop(2, 0.7)

julia> c2 = AMH_cop(2, .8)
AMH_cop(2, 0.8)

julia> cp = Nested_AMH_cop([c1, c2], 1, 0.2)
Nested_AMH_cop(AMH_cop[AMH_cop(2, 0.7), AMH_cop(2, 0.8)], 1, 0.2)

julia> Random.seed!(43);

julia> simulate_copula(4, cp)
4×5 Array{Float64,2}:
 0.557393  0.902767  0.909853  0.938522  0.586068
 0.184204  0.866664  0.699134  0.226744  0.102932
 0.268634  0.383355  0.179023  0.533749  0.995958
 0.578143  0.840169  0.743728  0.963226  0.576695

julia> c1 = Frank_cop(2, 4.)
Frank_cop(2, 4.0)

julia> c2 = Frank_cop(2, 5.)
Frank_cop(2, 5.0)

julia> c = Nested_Frank_cop([c1, c2],1, 2.0)
Nested_Frank_cop(Frank_cop[Frank_cop(2, 4.0), Frank_cop(2, 5.0)], 1, 2.0)

julia> Random.seed!(43);

julia> simulate_copula(1, c)
1×5 Array{Float64,2}:
 0.642765  0.901183  0.969422  0.9792  0.74155

julia> Random.seed!(43);

julia> c1 = Gumbel_cop(2, 2.)
Gumbel_cop(2, 2.0)

julia> c2 = Gumbel_cop(2, 3.)
Gumbel_cop(2, 3.0)

julia> cp = Nested_Gumbel_cop([c1, c2], 1, 1.1)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 2.0), Gumbel_cop(2, 3.0)], 1, 1.1)

julia> simulate_copula(4, cp)
4×5 Array{Float64,2}:
 0.387085   0.693399   0.94718   0.953776  0.583379
 0.0646972  0.0865914  0.990691  0.991127  0.718803
 0.966896   0.709233   0.788019  0.855622  0.755476
 0.272487   0.106996   0.756052  0.834068  0.661432

julia> a = Gumbel_cop(2, 5.)
Gumbel_cop(2, 5.0)

julia> b = Gumbel_cop(2, 6.)
Gumbel_cop(2, 6.0)

julia> c = Gumbel_cop(2, 5.5)
Gumbel_cop(2, 5.5)

julia> p1 = Nested_Gumbel_cop([a,b], 1, 2.)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.0), Gumbel_cop(2, 6.0)], 1, 2.0)

julia> p2 = Nested_Gumbel_cop([c], 2, 2.1)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.5)], 2, 2.1)

julia> copula = Double_Nested_Gumbel_cop([p1, p2], 1.5)
Double_Nested_Gumbel_cop(Nested_Gumbel_cop[Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.0), Gumbel_cop(2, 6.0)], 1, 2.0), Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.5)], 2, 2.1)], 1.5)

julia> Random.seed!(43);

julia> simulate_copula(5, copula)
5×9 Array{Float64,2}:
 0.598555   0.671584   0.8403     0.846844  0.634609  0.686927  0.693906  0.651968    0.670812
 0.0518892  0.191236   0.0803859  0.104325  0.410727  0.529354  0.557387  0.370518    0.592302
 0.367914   0.276196   0.382616   0.470171  0.264135  0.144503  0.13097   0.00687015  0.01417
 0.632727   0.596879   0.244176   0.338809  0.58771   0.147539  0.219099  0.287937    0.0569943
 0.310365   0.0483216  0.119312   0.107155  0.336619  0.279602  0.262756  0.438432    0.403061
```
"""
function simulate_copula(t, copula; rng = Random.GLOBAL_RNG)
    U = zeros(value_eltype(copula), t, copula.n)
    simulate_copula!(U, copula; rng = rng)
    return U
end


using SeisProcessing
using Test

d = SeisLinearEvents();
snr_i =1.2
dn = SeisAddNoise(d,snr_i);

snr = MeasureSNR(d,dn)

# test that snr = snr_i
quality_factor = abs(snr-snr_i)
@test quality_factor < 1


using ODE

T = 1.0
Q = 0.1
dtsampling = 0.0125
dt=0.0025
nsteps = 50000 #If lyapunov_exponent function is chosen, total time = nsteps*dt
nsimulations = 50 #If trajectory function is chosen, total time = nsteps*dt*nsimulations.
thermo = Thermostat("logistic", z-> exp(z/Q)/(Q*( 1 +exp(z/Q))^2.))
potential = Potential("quartic", x->x^4./4.)
integrator = Integrator("RK4", ode4)


using PETSc, MPI, Printf
MPI.Initialized() || MPI.Init()
PETSc.initialize()

# Set up the RHS for a cell centered scheme for PDE
#
#  Δu = -cos(π * x) * cos(π * y)
#
# with zeros Neumann boundary conditions
function rhs!(
    ksp::PETSc.AbstractKSP{PetscScalar},
    b_vec::PETSc.AbstractVec{PetscScalar},
) where {PetscScalar}
    dm = PETSc.KSPGetDM(ksp)
    comm = PETSc.PetscObjectGetComm(ksp)
    corners = PETSc.DMDAGetCorners(dm)
    global_size = PETSc.DMDAGetInfo(dm).global_size[1:2]

    # Grid spacing in each direction
    h = PetscScalar(1) ./ global_size

    # Build the RHS vector for the cell-centered grid. Since the b_vec is not a
    # julia vector but a PETSc vector we need to convert it before operating on it
    PETSc.map_unsafe_localarray!(b_vec; read = false) do b
        b = reshape(b, Int64(corners.size[1]), Int64(corners.size[2]))
        for (iy, y) in enumerate(corners.lower[2]:corners.upper[2])
            y = (2y - 1) * h[2] / 2
            for (ix, x) in enumerate(corners.lower[1]:corners.upper[1])
                x = (2x - 1) * h[1] / 2
                b[ix, iy] = -cos(π * x) * cos(π * y) * h[1] * h[2]
            end
        end
    end

    # Assemble the PETSc vector
    PETSc.assemblybegin(b_vec)
    PETSc.assemblyend(b_vec)

    # Hack to remove constant from the vector since we are using all Neumann
    # boundary conditions
    nullspace = PETSc.MatNullSpace{PetscScalar}(comm, PETSc.PETSC_TRUE)
    PETSc.MatNullSpaceRemove!(nullspace, b_vec)
    PETSc.destroy(nullspace)

    # zero is the PETSc sucess code
    return 0
end

function jacobian!(
    ksp::PETSc.AbstractKSP{PetscScalar},
    J::PETSc.AbstractMat{PetscScalar},
    jac::PETSc.AbstractMat{PetscScalar},
) where {PetscScalar}
    dm = PETSc.KSPGetDM(ksp)
    corners = PETSc.DMDAGetCorners(dm)
    PetscInt = eltype(corners.size)

    global_size = PETSc.DMDAGetInfo(dm).global_size[1:2]

    # Grid spacing in each direction
    h = PetscScalar(1) ./ global_size

    # XXX: Would Mvectors be better?
    Sten = PETSc.MatStencil{PetscInt}
    col = Vector{Sten}(undef, 5)
    row = Vector{Sten}(undef, 1)
    val = Vector{PetscScalar}(undef, 5)

    for j in corners.lower[2]:corners.upper[2]
        for i in corners.lower[1]:corners.upper[1]
            row[1] = Sten(i = i, j = j)
            num = 1
            fill!(val, 0)
            if i > 1
                val[num] = -h[2] / h[1]
                col[num] = Sten(i = i - 1, j = j)
                num += 1
            end
            if i < global_size[1]
                val[num] = -h[2] / h[1]
                col[num] = Sten(i = i + 1, j = j)
                num += 1
            end
            if j > 1
                val[num] = -h[1] / h[2]
                col[num] = Sten(i = i, j = j - 1)
                num += 1
            end
            if j < global_size[2]
                val[num] = -h[1] / h[2]
                col[num] = Sten(i = i, j = j + 1)
                num += 1
            end
            val[num] = -sum(val)
            col[num] = Sten(i = i, j = j)
            PETSc.MatSetValuesStencil!(
                jac,
                row,
                col,
                val,
                PETSc.INSERT_VALUES;
                num_cols = num,
            )
        end
    end

    PETSc.assemblybegin(jac)
    PETSc.assemblyend(jac)

    # Since we have all Neumann there is constant nullspace
    comm = PETSc.PetscObjectGetComm(ksp)
    nullspace = PETSc.MatNullSpace{PetscScalar}(comm, PETSc.PETSC_TRUE)
    PETSc.MatSetNullSpace!(J, nullspace)
    PETSc.destroy(nullspace)
    return 0
end

function main(PetscScalar; comm = MPI.COMM_WORLD, options...)
    boundary_type = (PETSc.DM_BOUNDARY_NONE, PETSc.DM_BOUNDARY_NONE)
    stencil_type = PETSc.DMDA_STENCIL_STAR
    global_size = (11, 11)
    procs = (PETSc.PETSC_DECIDE, PETSc.PETSC_DECIDE)
    dof_per_node = 1
    stencil_width = 1
    points_per_proc = (nothing, nothing)

    opts =
        isinteractive() ? (ksp_monitor = nothing, ksp_view = nothing) :
        PETSc.parse_options(ARGS)

    da = PETSc.DMDACreate2d(
        PetscScalar,
        comm,
        boundary_type...,
        stencil_type,
        global_size...,
        procs...,
        dof_per_node,
        stencil_width,
        points_per_proc...;
        opts...,
    )

    PETSc.DMSetUp!(da)

    ksp = PETSc.KSP(da; opts...)

    PETSc.KSPSetComputeRHS!(ksp, rhs!)
    PETSc.KSPSetComputeOperators!(ksp, jacobian!)
    PETSc.setfromoptions!(ksp)
    PETSc.solve!(ksp)
end

main(Float64)
# main(Float32)


function reorder_complex_schur{T<:FloatingPoint}(s::Array{T,2},q::Array{T,2})

  n = size(s,1)

  for ilast = n:-1:2

    mag = norm(s[ilast,ilast])
    icurrent = ilast

    for j = (ilast-1):-1:1

      if norm(s[j,j]) > mag
        mag = norm(s[j,j])
        icurrent = j
      end

    end

    for i = icurrent:(ilast-1)

      u = s[i:i+1,i+1]
      u[2] = u[2]-s[i,i]
      g = givens(u'[:])
      s[i:i+1,:] = g's[i:i+1,:]
      s[:,i:i+1] = s[:,i:i+1]*g
      q[i:i+1,:] = g'q[i:i+1,:]

    end

  end

  return s,q

end

function reorder_complex_schur{T<:FloatingPoint}(s::Array{Complex{T},2},q::Array{Complex{T},2})

  n = size(s,1)

  for ilast = n:-1:2

    mag = norm(s[ilast,ilast])
    icurrent = ilast

    for j = (ilast-1):-1:1

      if norm(s[j,j]) > mag
        mag = norm(s[j,j])
        icurrent = j
      end

    end

    for i = icurrent:(ilast-1)

      u = s[i:i+1,i+1]
      u[2] = u[2]-s[i,i]
      g = givens(u'[:])
      s[i:i+1,:] = g's[i:i+1,:]
      s[:,i:i+1] = s[:,i:i+1]*g
      q[i:i+1,:] = g'q[i:i+1,:]

    end

  end

  return s,q

end

function reorder_generalized_complex_schur{T<:FloatingPoint}(s::Array{T,2},t::Array{T,2},q::Array{T,2},z::Array{T,2})

  # This draws on draws on Kressner, D., (2006), "BLOCK ALGORITHMS FOR REORDERING STANDARD AND GENERALIZED SCHUR FORMS", LAPACK WORKING NOTE 171

  n = size(s,1)

  for ilast = n:-1:2

    if norm(t[ilast,ilast]) < 2*eps(T)*norm(t)
      mag = Inf
    else
      mag = norm(s[ilast,ilast]/t[ilast,ilast])
    end
    icurrent = ilast

    if mag != Inf
      for j = (ilast-1):-1:1

        if norm(t[j,j]) < 2*eps(T)*norm(t)
          mag = Inf
          icurrent = j
        elseif norm(s[j,j]/t[j,j]) > mag
          mag = norm(s[j,j]/t[j,j])
          icurrent = j
        end

      end
    end

    for i = icurrent:(ilast-1)

      s1 = s[i,i]
      s2 = s[i,i+1]
      s4 = s[i+1,i+1]
      t1 = t[i,i]
      t2 = t[i,i+1]
      t4 = t[i+1,i+1]

      if norm(t1) < 2*eps(T)*norm(t) && norm(s1) < 2*eps(T)*norm(s)
        error("Bz-A is not a regular matrix pencil: your model is not well posed")
      elseif norm(t4) < 2*eps(T)*norm(t) && norm(s4) < 2*eps(T)*norm(s)
        error("Bz-A is not a regular matrix pencil: your model is not well posed")
      elseif norm(t1) < 2*eps(T)*norm(t) && norm(t4) < 2*eps(T)*norm(s)
        # Both eigenvalues are infinite and do not need to be switched
        gq = eye(2)
        gz = eye(2)
      elseif norm(t1*s4-t4*s1) < 2*eps(T)*maximum([norm(s),norm(t)])
        # Both eigenvalues have equal modulus and do not need to be switched
        gq = eye(2)
        gz = eye(2)
      else
        h = [s1 -s4; t1 -t4]
        f = [s2; t2]
        p = inv(h)*f
        if norm(p[1]) < 2*eps(T)*norm(h) && norm(p[2]) < 2*eps(T)*norm(h)
          gq = [0.0 1.0; 1.0 0.0]
          gz = [0.0 1.0; 1.0 0.0]
        else
          u = [-p[2], complex(1.0)]
          (gq,r) = householder(u)
          u = [complex(1.0) p[1]]'
          (gz,r) = householder(u)
          gz = [gz[:,2] gz[:,1]]
        end
      end

      s[i:i+1,:] = gq's[i:i+1,:]
      t[i:i+1,:] = gq't[i:i+1,:]
      s[:,i:i+1] = s[:,i:i+1]*gz
      t[:,i:i+1] = t[:,i:i+1]*gz
      q[i:i+1,:] = gq'q[i:i+1,:]
      z[:,i:i+1] = z[:,i:i+1]*gz

    end

  end

  return s,t,q,z

end

function reorder_generalized_complex_schur{T<:FloatingPoint}(s::Array{Complex{T},2},t::Array{Complex{T},2},q::Array{Complex{T},2},z::Array{Complex{T},2})

  # This method draws on Kressner, D., (2006), "BLOCK ALGORITHMS FOR REORDERING STANDARD AND GENERALIZED SCHUR FORMS", LAPACK WORKING NOTE 171

  n = size(s,1)

  for ilast = n:-1:2

    if norm(t[ilast,ilast]) < 2*eps(T)*norm(t)
      mag = Inf
    else
      mag = norm(s[ilast,ilast]/t[ilast,ilast])
    end
    icurrent = ilast

    if mag != Inf
      for j = (ilast-1):-1:1

        if norm(t[j,j]) < 2*eps(T)*norm(t)
          mag = Inf
          icurrent = j
        elseif norm(s[j,j]/t[j,j]) > mag
          mag = norm(s[j,j]/t[j,j])
          icurrent = j
        end

      end
    end

    for i = icurrent:(ilast-1)

      s1 = s[i,i]
      s2 = s[i,i+1]
      s4 = s[i+1,i+1]
      t1 = t[i,i]
      t2 = t[i,i+1]
      t4 = t[i+1,i+1]

      if norm(t1) < 2*eps(T)*norm(t) && norm(s1) < 2*eps(T)*norm(s)
        error("Bz-A is not a regular matrix pencil: your model is not well posed")
      elseif norm(t4) < 2*eps(T)*norm(t) && norm(s4) < 2*eps(T)*norm(s)
        error("Bz-A is not a regular matrix pencil: your model is not well posed")
      elseif norm(t1) < 2*eps(T)*norm(t) && norm(t4) < 2*eps(T)*norm(s)
        # Both eigenvalues are infinite and do not need to be switched
        gq = eye(2)
        gz = eye(2)
      elseif norm(t1*s4-t4*s1) < 2*eps(T)*maximum([norm(s),norm(t)])
        # Both eigenvalues have equal modulus and do not need to be switched
        gq = eye(2)
        gz = eye(2)
      else
        h = [s1 -s4; t1 -t4]
        f = [s2; t2]
        p = inv(h)*f
        if norm(p[1]) < 2*eps(T)*norm(h) && norm(p[2]) < 2*eps(T)*norm(h)
          gq = [0.0 1.0; 1.0 0.0]
          gz = [0.0 1.0; 1.0 0.0]
        else
          u = [-p[2], complex(1.0)]
          (gq,r) = householder(u)
          u = [complex(1.0) p[1]]'
          (gz,r) = householder(u)
          gz = [gz[:,2] gz[:,1]]
        end
      end

      s[i:i+1,:] = gq's[i:i+1,:]
      t[i:i+1,:] = gq't[i:i+1,:]
      s[:,i:i+1] = s[:,i:i+1]*gz
      t[:,i:i+1] = t[:,i:i+1]*gz
      q[i:i+1,:] = gq'q[i:i+1,:]
      z[:,i:i+1] = z[:,i:i+1]*gz

    end

  end

  return s,t,q,z

end

#=

function reorder_complex_schur{T<:FloatingPoint}(s::Array{T,2},q::Array{T,2})

  # Alternative reordering using Householder transform.  This method draws on Kressner, D., (2006), "BLOCK ALGORITHMS FOR REORDERING
  # STANDARD AND GENERALIZED SCHUR FORMS", LAPACK WORKING NOTE 171

  n = size(s,1)

  for ilast = n:-1:2

    mag = norm(s[ilast,ilast])
    icurrent = ilast

    for j = (ilast-1):-1:1

      if norm(s[j,j]) > mag
        mag = norm(s[j,j])
        icurrent = j
      end

    end

    for i = icurrent:(ilast-1)

      s1 = s[i,i]
      s2 = s[i,i+1]
      s4 = s[i+1,i+1]

      if norm(s1-s4) < 2*eps(T)*norm(s)
        g = eye(2)
      else
        (g,r) = householder([-(s1-s4)^(-1)*s2,1.0])
      end

      s[i:i+1,:] = g's[i:i+1,:]
      s[:,i:i+1] = s[:,i:i+1]*g
      q[i:i+1,:] = g'q[i:i+1,:]

    end

  end

  return s,q

end

function reorder_complex_schur{T<:FloatingPoint}(s::Array{Complex{T},2},q::Array{Complex{T},2})

  # Alternative reordering using Householder transform.  This method draws on Kressner, D., (2006), "BLOCK ALGORITHMS FOR REORDERING
  # STANDARD AND GENERALIZED SCHUR FORMS", LAPACK WORKING NOTE 171

  n = size(s,1)

  for ilast = n:-1:2

    mag = norm(s[ilast,ilast])
    icurrent = ilast

    for j = (ilast-1):-1:1

      if norm(s[j,j]) > mag
        mag = norm(s[j,j])
        icurrent = j
      end

    end

    for i = icurrent:(ilast-1)

      s1 = s[i,i]
      s2 = s[i,i+1]
      s4 = s[i+1,i+1]

      if norm(s1-s4) < 2*eps(T)*norm(s)
        g = eye(2)
      else
        p = [-(s1-s4)^(-1)*s2,complex(1.0)]
        (g,r) = householder(p)
      end

      s[i:i+1,:] = g's[i:i+1,:]
      s[:,i:i+1] = s[:,i:i+1]*g
      q[i:i+1,:] = g'q[i:i+1,:]

    end

  end

  return s,q

end

=#


include(joinpath(@__DIR__, "pedagogy_graph.jl"))
using Random

dims = [5, 5]
g = LG.grid(dims)
roots = [1, 5]

rsf = PRS.RootedSpanningForest(g, roots)

rng = Random.MersenneTwister(123)
@time sample = PRS.generate_sample_prs(rng, rsf)

path = joinpath(@__DIR__, "output", "rooted_spanning_tree", "rooted_spanning_forest.pdf")
p = plot(rsf, sample, dims, path)


using Base.Cartesian: @nexprs

macro multistep(fname,rule)
	@assert rule.head == :(=)
	y = rule.args[1].args[1]  # output variable
    vecs = Set{Symbol}()
    consts = Set{Symbol}()
    inds = Set{Symbol}()
    offsets = Set{Int}()
    extractvars!(rule,vecs,consts,inds,offsets)
    delete!(vecs,y)
    vecs = sort([vecs...])
    consts = sort([consts...])
    @assert length(inds) == 1 "only 1 index variable allowed"
    ind = pop!(inds)
    maxoffset = maximum(offsets)
    minoffset = minimum(offsets)
    nconsts = maxoffset - minoffset
    fname! = symbol(string(fname) * "!")

    yarg = :($y::AbstractVector{T})
    yconsts = [symbol(string(y) * "_$i") for i in 1:nconsts]
    yconstargs = [:($c::T) for c in yconsts]
    refargs = [Expr(:(::), v, :(AbstractVector{T})) for v in vecs]
    constargs = [Expr(:(::), c, :T) for c in consts]
    args = [yconstargs;refargs;constargs]
    fsig = esc(Expr(:call, Expr(:curly, fname, :T), args...))
    fsig! = esc(Expr(:call, Expr(:curly, fname!, :T), yarg, args...))
    fcall = esc(Expr(:call, Expr(:curly, fname!, :T), y, yconsts..., vecs..., consts...))
    ind = esc(ind)

    quote
        $fsig! = begin
            @nexprs $nconsts j -> $y[j] = $yconsts[j]
            @inbounds for $ind in $(1-minoffset):length($y)-$maxoffset
                $rule
            end
        end

        $fsig = begin
            $(esc(y)) = similar($(esc(vecs[1])))
            $(Expr(:escape, Expr(:call, fname!, y, yconsts..., vecs..., consts...)))
            $(esc(y))
        end
    end
end

function extractvars!(e, vecs, consts, inds, offsets)
    if isa(e, Symbol) && isalpha(string(e))
        push!(consts, e)
        return
    end

    if isa(e, Expr)
        if e.head == :ref
            push!(vecs, e.args[1])
            ind = e.args[2]

            if isa(ind, Symbol)
                push!(inds, ind)
            elseif isa(ind, Expr)
                op = ind.args[1]
                @assert ind.head == :call && (op == :+ || op == :-)
                offset = op == :+ ? ind.args[3] : -ind.args[3]
                push!(inds, ind.args[2])
                push!(offsets, offset)
            end

            return
        end

        for i in 1:length(e.args)
            extractvars!(e.args[i], vecs, consts, inds, offsets)
        end
    end
end

macro adamsmoulton3_multistep(name,kernel)
    vecs = Set()
    consts = Set()
    inds = Set()
    offsets = Set()
    extractvars!(kernel,vecs,consts,inds,offsets)
    delete!(vecs,:y)
    delete!(vecs,:f)
    push!(consts,:h)
    vecs = sort([vecs...])
    consts = sort([consts...])
    @assert length(inds) == 1 "only 1 index variable allowed"
    ind = pop!(inds)
    yarg = :(y::AbstractVector)
    vecargs = [Expr(:(::), v, :(AbstractVector)) for v in vecs]
    fsig = esc(Expr(:call, :($name), yarg, vecargs..., consts...))

	y = esc(:y)
	i = esc(ind)
	f = esc(:f)

    quote
        $fsig = begin
            $f = similar($y)

            $y[1] = 0.
            $f[1] = $(replace(kernel,ind,1))
            $y[2] = $y[1] + h*$f[1]
            $f[2] = $(replace(kernel,ind,2))
            $y[3] = $y[2] + h*($f[2]*3./2. - $f[1]/2.)
            $f[3] = $(replace(kernel,ind,3))
            for $i in 4:length($y)
                $y[$i] = $y[$i-1] + h*($f[$i-1]*23./12. - $f[$i-2]*4./3. + $f[$i-3]*5./12.)
                $f[$i] = $(replace(kernel,ind,i))
            end
        end
    end
end

function replace!(e,var,val)
    !isa(e,Expr) && return
    for i in 1:length(e.args)
        if isa(e.args[i],Expr)
            replace!(e.args[i],var,val)
        elseif e.args[i] == var
            e.args[i] = val
        end
    end
end

function replace(e,var,val)
    ec = copy(e)
    replace!(ec,var,val)
    ec
end

function isvariable(s)
    if isa(s, Symbol) && isalpha(string(s))
        return true
    end

    if isa(s, Expr)
        return reduce(|, map(isvariable, s.args))
    end

    return false
end


#=
"""
```
hessizero{T<:AbstractFloat}(fcn::Function, x::Vector{T};
                            check_neg_diag::Bool=false,
                            verbose::Symbol=:none,
                            distr::Bool=true)
```

Compute Hessian of function `fcn` evaluated at `x`.

### Arguments
- `check_neg_diag`: Throw an error if any negative diagonal elements are detected.
- `verbose`: Print verbose output
- `distr`: Use available parallel workers to increase performance.
"""
function hessizero{T<:AbstractFloat}(fcn::Function,
                                    x::Vector{T};
                                    check_neg_diag::Bool=false,
                                    verbose::Symbol=:none,
                                    distr::Bool=true)
    n_para = length(x)
    hessian  = zeros(n_para, n_para)

    # Compute diagonal elements first
    if distr && nworkers() > 1
        diag_elements = @sync @distributed (hcat) for i = 1:n_para
            hess_diag_element(fcn, x, i; check_neg_diag=check_neg_diag, verbose=verbose)
        end
        for i = 1:n_para
            hessian[i, i] = diag_elements[i]
        end
    else
        for i=1:n_para
            hessian[i,i] = hess_diag_element(fcn, x, i; check_neg_diag=check_neg_diag,
                                             verbose=verbose)
        end
    end

    # Now compute off-diagonal elements
    # Make sure that correlations are between -1 and 1
    # invalid_corr indexes elements that are invalid
    invalid_corr = Dict{Tuple{Int,Int}, Float64}()

    # Build indices to iterate over
    n_off_diag_els = Int(n_para*(n_para-1)/2)

    #this allows for base case where there is only 1 free parameter
    if n_off_diag_els > 0

        off_diag_inds = Vector{Tuple{Int,Int}}(n_off_diag_els)
        k=1
        for i=1:(n_para-1), j=(i+1):n_para
            off_diag_inds[k] = (i,j)
            k = k+1
        end

        # Iterate over off diag elements
        if distr
            off_diag_out = @sync @distributed (hcat) for (i,j) in off_diag_inds
                σ_xσ_y = sqrt(abs(hessian[i, i]*hessian[j, j]))
                hess_offdiag_element(fcn, x, i, j, σ_xσ_y; verbose=verbose)
            end
            # Ensure off_diag_out is array
            off_diag_out = hcat(off_diag_out)
        else
            off_diag_out = Array{Tuple{T, T},1}(n_off_diag_els)
            for (k,(i,j)) in enumerate(off_diag_inds)
                σ_xσ_y = sqrt(abs(hessian[i, i]*hessian[j, j]))
                off_diag_out[k] = hess_offdiag_element(fcn, x, i, j, σ_xσ_y; verbose=verbose)
            end
        end

        # Fill in values
        for k=1:n_off_diag_els
            (i,j) = off_diag_inds[k]
            (value, ρ_xy) = off_diag_out[k]

            hessian[i,j] = value
            hessian[j,i] = value

            if ρ_xy < -1 || 1 < ρ_xy
                invalid_corr[(i, j)] = ρ_xy
            end
        end
    end

    has_errors = false
    if !isempty(invalid_corr)
        println("Errors: $invalid_corr")
        has_errors = true
    end

    return hessian, has_errors
end
=#


using Test, JLD, LinearAlgebra
mutable struct problem
    data::Array{Float64}
    Sigma::Array{Float64}
end
###############################################################################
### This test script aims to verify correctness of our code,
### by verifying that the sparse principal components found here match those
### found by (Berk+Bertsimas, 2017) on the UCI dataset
###
### We have hard-coded the test data here, in order to avoid Julia dependency issues
###############################################################################
include(normpath(joinpath(@__FILE__,"..",".."))*"Algorithm/branchAndBound.jl")
include(normpath(joinpath(@__FILE__,"..",".."))*"Algorithm/utilities.jl")
include(normpath(joinpath(@__FILE__,"..",".."))*"Algorithm/multiComponents.jl")
################################################################################
######## Test pitprops data set for each cardinality level
################################################################################
@testset "Berk and Bertsimas SPCA Table 8" begin

pitprops=[[1,0.954,0.364,0.342,-0.129,0.313,0.496,0.424,0.592,0.545,0.084,-0.019,0.134];
       [0.954,1,0.297,0.284,-0.118,0.291,0.503,0.419,0.648,0.569,0.076,-0.036,0.144];
       [0.364,0.297,1,0.882,-0.148,0.153,-0.029,-0.054,0.125,-0.081,0.162,0.22,0.126];
       [0.342,0.284,0.882,1,0.22,0.381,0.174,-0.059,0.137,-0.014,0.097,0.169,0.015];
       [-0.129,-0.118,-0.148,0.22,1,0.364,0.296,0.004,-0.039,0.037,-0.091,-0.145,-0.208];
       [0.313,0.291,0.153,0.381,0.364,1,0.813,0.09,0.211,0.274,-0.036,0.024,-0.329];
       [0.496,0.503,-0.029,0.174,0.296,0.813,1,0.372,0.465,0.679,-0.113,-0.232,-0.424];
       [0.424,0.419,-0.054,-0.059,0.004,0.09,0.372,1,0.482,0.557,0.061,-0.357,-0.202];
       [0.592,0.648,0.125,0.137,-0.039,0.211,0.465,0.482,1,0.526,0.085,-0.127,-0.076];
       [0.545,0.569,-0.081,-0.014,0.037,0.274,0.679,0.557,0.526,1,-0.319,-0.368,-0.291];
       [0.084,0.076,0.162,0.097,-0.091,-0.036,-0.113,0.061,0.085,-0.319,1,0.029,0.007];
       [-0.019,-0.036,0.22,0.169,-0.145,0.024,-0.232,-0.357,-0.127,-0.368,0.029,1,0.184];
       [0.134,0.144,0.126,0.015,-0.208,-0.329,-0.424,-0.202,-0.076,-0.291,0.007,0.184,1];]
spca_pitrops_value=[1.000, 1.954, 2.475, 2.937, 3.406, 3.771, 3.996, 4.069, 4.139, 4.173, 4.208, 4.218, 4.219]#source: Berk+B paper (unpublished manuscript)
pitprops=reshape(pitprops, (13,13));
B=sqrt(pitprops);
theProb=problem(B, pitprops)
    for k=1:13
           println("Running pitprops data set k= ", k)
           obj, xVal, timetoBound, timetoConverge, timeOut, explored, toPrint=branchAndBound(theProb, k)
           println("Testing pitprops data set k= ", k)
           @show @test(abs(obj-spca_pitrops_value[k])<1e-3)
    end

end;


# This file was formerly a part of Julia. License is MIT: https://julialang.org/license

include("../perfutil.jl")

function cat2d_perf(n, iter)
    a = rand(n,n)
    b = rand(n,n)
    for i=1:iter
        c = [a b; b a]
    end
end

function cat2d_perf2(n, iter)
    a = rand(n,n)
    b = rand(n,n)
    for i=1:iter
        c = Matrix{Float64}(uninitialized, 2n,2n)
        c[  1:n,  1:n] = a
        c[  1:n,  n+1:end] = b
        c[n+1:end,1:n] = b
        c[n+1:end,n+1:end] = a
    end
end

function hcat_perf(n, iter)
    a = rand(n,n)
    b = rand(n,n)
    for i=1:iter
        c = [a b b a]
    end
end

function hcat_perf2(n, iter)
    a = rand(n,n)
    b = rand(n,n)
    for i=1:iter
        c = Matrix{Float64}(uninitialized, n, 4n)
        c[:,    1:  n] = a
        c[:,  n+1: 2n] = b
        c[:, 2n+1: 3n] = b
        c[:, 3n+1:end] = a
    end
end

function vcat_perf(n, iter)
    a = rand(n,n)
    b = rand(n,n)
    for i = 1:iter
        c = [a; b; b; a]
    end
end

function vcat_perf2(n, iter)
    a = rand(n,n)
    b = rand(n,n)
    for i=1:iter
        c = Matrix{Float64}(uninitialized, 4n, n)
        c[   1: n, :] = a
        c[ n+1:2n, :] = b
        c[2n+1:3n, :] = b
        c[3n+1:4n, :] = a
    end
end

function catnd_perf(n, iter)
    a = rand(1,n,n,1)
    b = rand(1,n,n)
    for i = 1:iter
        c = cat(3, a, b, b, a)
    end
end

function catnd_perf2(n, iter)
    a = rand(1,n,n,1)
    b = rand(1,n,n)
    for i = 1:iter
        c = Array{Float64}(uninitialized, 1, n, 4n, 1)
        c[1,:,   1: n,1] = a
        c[1,:, n+1:2n,1] = b
        c[1,:,2n+1:3n,1] = b
        c[1,:,3n+1:4n,1] = a
    end
end

problemsizes = [(5, 20000, "small"), (500, 2, "large")]
testdata = [(cat2d_perf,  "hvcat",        "horizontal/vertical matrix concatenation", problemsizes),
            (cat2d_perf2, "hvcat_setind", "horizontal/vertical matrix concatenation using setindex", problemsizes),
            (hcat_perf,   "hcat",         "horizontal matrix concatenation", problemsizes),
            (hcat_perf2,  "hcat_setind",  "horizontal matrix concatenation using setindex", problemsizes),
            (vcat_perf,   "vcat",         "vertical matrix concatenation", problemsizes),
            (vcat_perf2,  "vcat_setind",  "vertical matrix concatenation using setindex", problemsizes),
            (catnd_perf,  "catnd",        "N-dimensional matrix concatenation", problemsizes),
            (catnd_perf2, "catnd_setind", "N-dimensional matrix concatenation using setindex", problemsizes)]
include("../perfgeneric.jl")


module ScalingWithExtrapTests

using Interpolations, Base.Test

xs = linspace(-5, 5, 10)
ys = sin(xs)

function run_tests{T,N,IT}(sut::Interpolations.AbstractInterpolation{T,N,IT,OnGrid}, itp)
    for x in xs
        @test_approx_eq_eps sut[x] sin(x) sqrt(eps(sin(x)))
    end
    @test sut[-5] == sut[-5.1] == sut[-15.8] == sut[-Inf] == itp[1]
    @test sut[5] == sut[5.1] == sut[15.8] == sut[Inf] == itp[end]
end

function run_tests{T,N,IT}(sut::Interpolations.AbstractInterpolation{T,N,IT,OnCell}, itp)
    halfcell = (xs[2] - xs[1]) / 2

    for x in (5 + halfcell, 5 + 1.1halfcell, 15.8, Inf)
        @test sut[-x] == itp[.5]
        @test sut[x] == itp[end+.5]
    end
end

for GT in (OnGrid, OnCell)
    itp = interpolate(ys, BSpline(Quadratic(Flat())), GT())

    # Test extrapolating, then scaling
    eitp = extrapolate(itp, Flat())
    seitp = scale(eitp, xs)
    run_tests(seitp, itp)

    # Test scaling, then extrapolating
    sitp = scale(itp, xs)
    esitp = extrapolate(sitp, Flat())
    run_tests(esitp, itp)
end

end


export go_board_graph, go_game_graph

# Can't use import or using here without getting a warning about
# missing dependencies. Instead we have to qualify everything used
# from LightGraphs. Let's make that more convenient with an alias.
const LG = LightGraphs

"""
    go_board_graph(n::Integer)

Construct an undirected graph according to the numbering scheme of the
GoGameGraphs package. The graph is returned as a `LightGraphs.SimpleGraph`.
"""
function go_board_graph(n::Integer)
    adjacency_list = Board(n).edges
    return LG.SimpleGraph(sum(length.(adjacency_list)) ÷ 2, adjacency_list)
end

"""
    go_game_graph(board_graph::LightGraphs.SimpleGraph)

Return the directed graph corresponding to playing the game of go on
the undirected graph `board_graph`. Although go is traditionally
played on a square grid, the rules can be applied to any undirected
graph. All the possible board positions act as vertices in the game
graph and the moves as edges. The empty board corresponds to vertex 1.
The result is a sparse directed and strongly connected graph with lots
of cycles. It is returned as a `LightGraphs.SimpleDiGraph`.

The size of the game graph grows exponentially with the number of
vertices in the corresponding board graph. This function supports
board graphs of up to 12 vertices. The size limit can be overridden
but you have been warned.

    go_game_graph(board_graph::LightGraphs.SimpleGraph; allow_self_capture = false)

The keyword argument `allow_self_capture` can be set to `false` to
disallow moves that causes the player to lose his own stones. This
results in a game graph with fewer edges than when self captures are
allowed.

(Side note: all variations of go rules agree on the mechanics of
playing moves but can have different opinions about which moves are
valid. Whether to allow multi-stone self captures is one such
difference. However, all go rules agree that single-stone self
captures are invalid since that would not change the position. In
graph terms that means no self-edges, regardless of the setting of
`allow_self_capture`.)
"""
function go_game_graph(board_graph::LG.SimpleGraph; allow_self_capture = true,
                       override_size_limit = false,
                       boards = nothing)
    if LG.nv(board_graph) > 12 && !override_size_limit
        error("Board has more than 12 vertices, which will produce a very large game graph. If you are confident that your computer can handle that, add the keyword argument `override_size_limit=true`.")
    end
    board_edges = [LG.outneighbors(board_graph, v) for v in LG.vertices(board_graph)]
    game_graph = GameGraph(Board(board_edges),
                           allow_suicide = allow_self_capture)
    forward_adjacency_list = game_graph.edges
    nv = length(forward_adjacency_list)
    ne = sum(length.(forward_adjacency_list))
    backward_adjacency_list = [Int[] for v in 1:nv]
    for v = 1:nv
        for w in forward_adjacency_list[v]
            push!(backward_adjacency_list[w], v)
        end
    end

    if boards isa Vector{Vector{Int}}
        append!(boards, game_graph.board)
    end

    return LG.SimpleDiGraph(ne, forward_adjacency_list, backward_adjacency_list)
end


## Linear Solve
for TYP in (:Fun,:StridedVector,:AbstractVector,:Any)
    @eval function \(A::Operator,b::$TYP;kwds...)
        if isambiguous(domainspace(A))
            A=choosespaces(A,b)
            if isambiguous(domainspace(A))
                error("Cannot infer spaces")
            end
            \(A,b;kwds...)
        else
            Fun(domainspace(A),
                ldiv_coefficients(A,coefficients(b,rangespace(A));kwds...))
        end
    end
end

"""
    \\(A::Operator,b;tolerance=tol,maxlength=n)

solves a linear equation, usually differential equation, where `A` is an operator
or array of operators and `b` is a `Fun` or array of funs.  The result `u`
will approximately satisfy `A*u = b`.
"""
\(::Operator,_)

# Solve each column separately
function \(A::Operator, B::AbstractMatrix; kwds...)
    ds=domainspace(A)
    if isambiguous(ds)
        return choosespaces(A,B[:,1])\B
    end

    ret=Matrix{VFun{typeof(ds),
               promote_type(eltype(A),mapreduce(cfstype,promote_type,B))}}(undef,1,size(B,2))

    QR = factorize(A) # reuse computation
    for j=1:size(B,2)
        ret[1,j] = \(QR,B[:,j];kwds...)
    end
    Fun(ret)
end

\(A::Operator,B::MatrixFun;kwds...) = \(A,Array(B);kwds...)

ldiv_coefficients(A::Operator,b;kwds...) = ldiv_coefficients(qr(A),b;kwds...)

\(A::Operator,B::Operator) = TimesOperator(inv(A),B)

#TODO: Remove these when interlace is automatic
for TYP in (:Vector,:Matrix)
    @eval begin
        \(A::$TYP{OO},b::StridedVecOrMat;kwds...) where {OO<:Operator} =
            \(interlace(A),b;kwds...)
        \(A::$TYP{OO},b::AbstractVecOrMat;kwds...) where {OO<:Operator} =
            \(interlace(A),b;kwds...)
        \(A::$TYP{OO},b::Fun;kwds...) where {OO<:Operator} =
            \(interlace(A),b;kwds...)
    end
end
ldiv_coefficients(A::AbstractArray{OO},b;kwds...) where {OO<:Operator} =
    ldiv_coefficients(interlace(A),b;kwds...)


# ---
# title: 1093. Statistics from a Large Sample
# id: problem1093
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Math, Two Pointers
# link: <https://leetcode.com/problems/statistics-from-a-large-sample/description/>
# hidden: true
# ---
#
# We sampled integers between `0` and `255`, and stored the results in an array
# `count`:  `count[k]` is the number of integers we sampled equal to `k`.
#
# Return the minimum, maximum, mean, median, and mode of the sample
# respectively, as an array of **floating point numbers**.  The mode is
# guaranteed to be unique.
#
# _(Recall that the median of a sample is:_
#
#   * _The middle element, if the elements of the sample were sorted and the number of elements is odd;_
#   * _The average of the middle two elements, if the elements of the sample were sorted and the number of elements is even.)_
#
#
#
# **Example 1:**
#
#
#
#     Input: count = [0,1,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
#     Output: [1.00000,3.00000,2.37500,2.50000,3.00000]
#
#
# **Example 2:**
#
#
#
#     Input: count = [0,4,3,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
#     Output: [1.00000,4.00000,2.18182,2.00000,1.00000]
#
#
#
#
# **Constraints:**
#
#   1. `count.length == 256`
#   2. `1 <= sum(count) <= 10^9`
#   3. The mode of the sample that count represents is unique.
#   4. Answers within `10^-5` of the true value will be accepted as correct.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


using LinearAlgebra
using TensorOperations
import Strided: UnsafeStridedView

@testset "Arrays" begin

    A = FermiMDrand(5,5)
    @test FermiMDArray(A) === A
    @test size(A) == (5,5)
    @test A[2,2] == A.data[2,2]
    A[2,2] = 0.75
    @test A[2,2] == 0.75
    @test ndims(A) == 2
    @test length(A) == 25
    At = similar(A)
    At .= transpose(A)
    @test permutedims(A, (2,1)) == At
    permutedims!(A, (2,1))
    @test A == At

    @test diag(A) == diag(A.data)

    # Make it Hermitian
    A = A + transpose(A)

    @test A^2 == A.data^2
    @test A^-1 ≈ A^-1.0

    Hvals, Hvec = diagonalize(Hermitian(A), hermitian=true)
    adjoint(Hvec) * A*Hvec ≈ diagm(Hvals)
    vals, vec = diagonalize(A, hermitian=false)
    vec^-1 * A*vec ≈ diagm(vals)

    b = rand(5,5)
    B = FermiMDArray(b)
    @test A + b == A + B == b + A
    @test A - b == A - B == -(b - A)
    @test 2*A == 2*A.data == A*2

    @test FermiMDArray(2.0) == 2.0

    C = FermiMDzeros(5,5)
    @test C == zeros(5,5)

    Bv = similar(B, (5,))
    Bv .= B[1,:]
    @test Bv == B.data[1,:]

    Ct = rand(Float32, (5,5))
    C .= Ct
    @test C.data == Base.convert(Array{Float64}, Ct)

    @test UnsafeStridedView(A) == UnsafeStridedView(A.data)

    idx = [ (1,1,1,1), (2,2,2,2), (3,3,3,3)]
    val = rand(3)
    A = FermiSparse(idx, val)
    @test ndims(A) == 4

    # x = @capture_out display(A)
    # println("")
    # @test occursin("Fermi Memory-held Dense Array", x)
end