export TensorOp, DiagonalOp,
    IdentityOp, TensorOpProduct,
    TensorOpProductSum,
    TensorOpProductKernel,
    create, destroy, sigma, jz, jplus, jminus,
    share, dim, tensor,
    iidx, oidx, all_close, apply_serial!

import Base: (*), transpose, conj, ctranspose, kron, A_mul_B!, sparse, full,
              convert, promote_rule

abstract TensorOp

immutable DiagonalOp{T<:Number} <: TensorOp
    # T: data type
    # J: number of zeros before first non-zero
    # K: number of zeros after last non-zero
    # M: Shift offset
    # vals: diagonal elements between the non-zeros
    vals::Vector{T}
    jkm::NTuple{3,Int64}
    # expr::Expr
    # DiagonalOp{T}(vals::Vector{T}, jkm, expr = :()) = new{T}(vals, jkm, expr)
end

# function DiagonalOp(T, op::DiagonalOp)
#   TT = typeof(op)
#   J,K,M = TT.parameters[2:end]
#   DiagonalOp{T,J,K,M}(convert(Vector{T}, op.vals))
# end
#

type IdentityOp{N} <: TensorOp
  # Identity operator with given dimension N
end

promote_rule{T,S}(::Type{DiagonalOp{T}}, ::Type{DiagonalOp{S}}) = DiagonalOp{promote_type(T,S)}
convert{T}(::Type{DiagonalOp{T}}, op::DiagonalOp) = DiagonalOp(convert(Vector{T}, op.vals), op.jkm)
convert{T,N}(::Type{DiagonalOp{T}}, op::IdentityOp{N}) = DiagonalOp(ones(T, N), (0,0,0))

function all_close(op1::DiagonalOp, op2::DiagonalOp; tol=1e-8)
  dim(op1) == dim(op2) || error("Incompatible shapes")
  op1.jkm == op2.jkm && norm(op1.vals-op2.vals) < tol
end

all_close{T}(op1::IdentityOp, op2::DiagonalOp{T}; kw...) = all_close(convert(DiagonalOp{T},op1),op2; kw...)
all_close{N}(op1::IdentityOp{N}, op2::IdentityOp{N}; kw...) = true
all_close{T}(op1::DiagonalOp{T}, op2::IdentityOp; kw...) = all_close(convert(DiagonalOp{T},op2),op1; kw...)

type TensorOpProduct{T,K}
    # Product of single degree tensor ops
    ops::NTuple{K,Union{DiagonalOp{T},IdentityOp}}
    kernel
end
TensorOpProduct(ops::TensorOp...) = TensorOpProduct(promote_diags(ops...), nothing)

function promote_diags(ops::TensorOp...)
  opsv = collect(ops)
  nontrivial=[isa(op, DiagonalOp) for op=opsv]
  promoted_opsv = Array(Any,length(ops))
  promoted_opsv[nontrivial] = collect(promote(opsv[nontrivial]...))
  promoted_opsv[~nontrivial] = opsv[~nontrivial]
  tuple(promoted_opsv...)
end

function convert_diags{T}(S::Type{DiagonalOp{T}}, ops::TensorOp...)
  nops = Any[]
  for op in ops
    if isa(op, DiagonalOp)
      push!(nops, convert(S, op))
    else
      push!(nops, op)
    end
  end
  tuple(nops...)
end

promote_rule{T,S,K}(::Type{TensorOpProduct{T,K}}, ::Type{TensorOpProduct{S,K}}) = TensorOpProduct{promote_type(T,S),K}
promote_rule{T,S}(::Type{TensorOpProduct{T}}, ::Type{TensorOpProduct{S}}) = TensorOpProduct{promote_type(T,S)}
convert{T,S,K}(::Type{TensorOpProduct{T,K}}, top::TensorOpProduct{S,K}) = TensorOpProduct(convert_diags(DiagonalOp{T}, top.ops...)...)
convert{T}(::Type{TensorOpProduct{T}}, top::TensorOpProduct) = TensorOpProduct(convert_diags(DiagonalOp{T}, top.ops...)...)

all_close(top1::TensorOpProduct, top2::TensorOpProduct; tol=1e-8) = all(Bool[all_close(op1, op2; tol=tol) for (op1,op2) in zip(top1.ops, top2.ops)])

immutable TensorOpProductKernel{K}
    # Product of single degree tensor ops
    ops::NTuple{K,Union{DenseVector,IdentityOp}}
    iidx::NTuple{K,Range{Int64}}
    oidx::NTuple{K,Range{Int64}}
end

function TensorOpProductKernel(tp::TensorOpProduct)
    iidxs = iidx(tp)
    oidxs = oidx(tp)
    ops_red = [(isa(op, DiagonalOp)? op.vals : op) for op in tp.ops]
    M = length(tp.ops)
    k = TensorOpProductKernel{M}(tuple(ops_red...), tuple(iidxs...), tuple(oidxs...))
    tp.kernel = k
    k
end

type TensorOpProductSum{T,K,N}
    # Sum of product of single degree tensor ops
    tops::NTuple{N,NTuple{K,Union{IdentityOp,DiagonalOp{T}}}}
    kernel
end

function TensorOpProductSum(tops::TensorOpProduct...)
  tops = promote(tops...)
  topops = [top.ops for top in tops]
  TensorOpProductSum(tuple(topops...), nothing)
end

all_close{T,S,K,N}(tops1::TensorOpProductSum{T,K,N},
          tops2::TensorOpProductSum{S,K,N}; tol=1e-8) = all(
            Bool[all_close(tops1.tops[n][k], tops2.tops[n][k]; tol=tol) for n=1:N, k=1:K])

#
# function share(a::SharedArray)
#     a
# end
#
# function share(a::Array)
#     as = SharedArray(eltype(a),size(a)...)
#     as[:] = a[:]
#     as
# end
#
# function share(op::IdentityOp)
#     op
# end
#
#
# function share{T,J,K,M}(op::DiagonalOp{T,J,K,M})
#     DiagonalOp{T,J,K,M}(share(op.vals))
# end
#
#
# function share(tp::TensorOpProduct)
#     TensorOpProduct(map(share, tp.ops))
# end
#
# function share(tpk::TensorOpProductKernel)
#     TensorOpProductKernel{length(tpk.ops)}(tuple(map(share, tpk.ops)), tpk.iidx, tpk.oidx)
# end
#

function dim(d::DiagonalOp)
  J,K,M = d.jkm
  J+length(d.vals)+K
end

function dim{N}(::IdentityOp{N})
    N
end

# range of input indices that are processed in own dimension.
iidx(d::DiagonalOp) = (1:length(d.vals))+(d.jkm[1]-d.jkm[3])
iidx{N}(::IdentityOp{N}) = 1:N

# range of output indices that are processed in own dimension.
oidx(d::DiagonalOp) =  (1:length(d.vals))+d.jkm[1]
oidx{N}(::IdentityOp{N})=1:N

for fn in [:iidx, :oidx]
    @eval $fn(tp::TensorOpProduct) = map($fn, tp.ops)
end

# conversion to sparse matrices
sparse{N}(::IdentityOp{N})= speye(N)
function sparse{T}(d::DiagonalOp{T})
    N = dim(d)
    J,K,M = d.jkm
    spdiagm([zeros(T,J);d.vals; zeros(T,K)], 0,N,N) * spdiagm(ones(N-abs(M)),-M, N, N)
end
sparse(tp::TensorOpProduct) = reduce(kron, (SparseMatrixCSC)[sparse(op) for op in reverse(tp.ops)])

# conversion of full matrices
full(d::Union{TensorOp, TensorOpProduct}) = d |> sparse |> full

create(N::Int) = DiagonalOp{Float64}(sqrt(1:(N-1)),(1,0,1))
destroy(N::Int) = DiagonalOp{Float64}(sqrt(1:(N-1)),(0,1,-1))
sigma(N::Int,j::Int,k::Int) = DiagonalOp{Float64}([1.],(j-1,N-j,j-k))

function jz(J::Int,trunc=-1)
  if trunc > 0
    N = trunc
  else
    N = 2J
  end
  DiagonalOp{Float64}(1.*collect(-J:(-J+N)),(0,0,0))
end

function jpm(J::Int, trunc=-1, pm=:(+))
  if trunc > 0
    N = trunc
  else
    N = 2J
  end
  rng = -J:(-J+N-1)
  diag = sqrt(J*(J+1) - rng .* (rng+1))
  if pm === :(+)
    DiagonalOp{Float64}(diag, (1,0,1))
  elseif pm === :(-)
    DiagonalOp{Float64}(diag, (0,1,-1))
  else
    error("Can only create J_+ and J_-")
  end
end

jplus(J, trunc=-1) = jpm(J,trunc, :(+))
jminus(J, trunc=-1) = jpm(J,trunc, :(-))

transpose(ii::IdentityOp) = ii
transpose(d::DiagonalOp) =  DiagonalOp(d.vals,(d.jkm[1]-d.jkm[3],d.jkm[2]+d.jkm[3],-d.jkm[3]))
conj(ii::IdentityOp) = ii
conj(d::DiagonalOp) = DiagonalOp(conj(d.vals), d.jkm)
ctranspose(op::TensorOp)= transpose(conj(op))

for fn in [:transpose, :conj, :ctranspose]
    @eval function $fn(tp::TensorOpProduct)
        TensorOpProduct([$fn(op) for op in tp.ops]...)
    end
end

# Multiplication of diagonal tensors on same degree of freedom
function (*){N,M}(a::IdentityOp{N}, b::IdentityOp{M})
    N == M || error("Wrong shape for multiplication: ($N x $N) * ($M x $M)")
    b
end

function (*){N}(a::IdentityOp{N}, b::TensorOp)
    M = dim(b)
    N == M || error("Wrong shape for multiplication: ($N x $N) * ($M x $M)")
    b
end

function (*){M}(a::TensorOp, b::IdentityOp{M})
    N = dim(a)
    N == M || error("Wrong shape for multiplication: ($N x $N) * ($M x $M)")
    a
end

function (*){S,T}(a::DiagonalOp{S}, b::DiagonalOp{T})
    N, M = dim(a), dim(b)
    J1,K1,M1 = a.jkm
    J2,K2,M2 = b.jkm
    N == M || error("Wrong shape for multiplication: ($N x $N) * ($M x $M)")
    da = [zeros(S, J1); a.vals; zeros(S,K1)]
    db = [zeros(T, J2); b.vals; zeros(T,K2)]
    if M1 >= 0
        dbshifted = [zeros(T, M1); db[1:(M-M1)]]
    else
        dbshifted = [db[(abs(M1)+1):M];zeros(T, abs(M1))]
    end
    dab = da .* dbshifted
    q, r = find(abs(dab) .> 0)[[1,end]]
    J = q - 1
    K = N - r
    DiagonalOp{eltype(dab)}(dab[q:r],(J,K,M1+M2))
end

# construction of tensor products
function tensor(a::TensorOp...)
    TensorOpProduct(a...)
end

function tensor(a::TensorOp, b::TensorOpProduct)
    TensorOpProduct(a, b.ops...)
end

function tensor(a::TensorOpProduct, b::TensorOp)
    TensorOpProduct(a.ops..., b)
end

# construction of tensor products for states
function tensor{S,T,M,N}(A::Array{T,M}, B::Array{S,N})
    broadcast(*, reshape(A, size(A)...,ones(Int64,N)...),reshape(B,ones(Int64,M)...,size(B)...))
end

# In-place mat-vec product without coefficients
A_mul_B!(C::DenseArray, A::Union{TensorOpProduct,TensorOpProductKernel}, B::DenseArray) = A_mul_B!(1, A, B, 0, C)

# In-place mat-vec product with coefficients
function A_mul_B!(a, A::TensorOpProduct, B::DenseArray, c, C::DenseArray)
    k = A.kernel === nothing ? TensorOpProductKernel(A) : A.kernel
    A_mul_B!(a, k, B, c, C)
end

function A_mul_B!{K}(a, A::TensorOpProductKernel{K}, B::DenseArray, c, C::DenseArray)
    ndims(B) == K || error("Mismatch between tensor factors")
    size(B) === size(C) || error("Dimension mismatch between input and output")
    if c != 1
      if c == 0
        fill!(C, 0)
      else
        scale!(C, c)
      end
    end
    top_kernel!(sub(C, A.oidx...), sub(B, A.iidx...), a, A.ops...)
    C
end

# code for generating mat-vec loops
@generated function top_kernel!(sC, sB, a, Avs::Union{DenseVector,IdentityOp}...)
   code = top_kernel_code(sC, sB, a, Avs)
  #  println(code)
   code
end
function top_kernel_code(sC, sB, a, Avs)
    K = length(Avs)
    @assert K >= 1
    loop_vars = [symbol("k_$(j)") for j=1:K]
    sCref = Expr(:ref, :sC, loop_vars...)
    sBref = Expr(:ref, :sB, loop_vars...)
    Avrefs = []
    for kk=1:K
        if Avs[kk] <: DenseArray
            push!(Avrefs, Expr(:ref, Expr(:ref, :Avs, kk), loop_vars[kk]))
        else
            @assert Avs[kk] <: IdentityOp
        end
    end

    innerloop_prod = Expr(:call, :*, :a, sBref, Avrefs...)
    innerloop = quote
        @simd for $(loop_vars[1]) = 1:size(sC,1)
            @inbounds $sCref += $innerloop_prod
        end
    end
    loop = innerloop
    for kk=2:K
        loop = quote
            for $(loop_vars[kk]) in 1:size(sC,$kk)
                $loop
            end
        end
    end
    loop
end

function apply_serial!(coeffs, toks, B, C, c=0)
    A_mul_B!(coeffs[1], toks[1], B, c, C)
    for kk=2:length(toks)
        A_mul_B!(coeffs[kk], toks[kk], B, 1, C)
    end
    C
end

function apply_serial!(coeffs, toks::TensorOpProductSum, B::DenseArray, C::DenseArray, c=0)
    apply_topsum!(coeffs, toks.tops, B, c, C)
end

@generated function apply_topsum!{T,N,K}(ts, tops::NTuple{N,NTuple{K,Union{IdentityOp,DiagonalOp{T}}}}, B::DenseArray{T,K}, c, C::DenseArray{T,K})
  code = apply_topsum_code(ts, tops, B, c, C)
    # println(code)
    code
end

function apply_topsum_code(ts, tops, B, c, C)
    # function diag_op_code(has_identity, offsets::Matrix{Int64}, nontrivial_factors::Matrix{Bool})
    # N = number of nontrivial terms in sum
    # D = number of tensor dimensions.
    N = length(tops.parameters)
    N >= 1 || error("Trivial Operator sum passed.")
    D = length(tops.parameters[1].parameters)

    nontrivial_factors = Array(Bool, N, D)
    for n=1:N
        for d=1:D
            nontrivial_factors[n,d] = tops.parameters[n].parameters[d] <: DiagonalOp
        end
    end

    IIDXs = [symbol("IIDX_$(n)") for n=1:N]
    kds = [symbol("k_$d") for d=1:D]
    flags = [symbol("apply_t$(n)_d$(d)") for n=1:N, d=1:D]

    diags = [symbol("diag_t$(n)_d$(d)") for n=1:N, d=1:D]
    jnds = [symbol("j_$(n)_$(d)") for n=1:N, d=1:D]
    knds = [symbol("k_$(n)_$(d)") for n=1:N, d=1:D]
    mnds = [symbol("m_$(n)_$(d)") for n=1:N, d=1:D]
    kdsmmnds = [nontrivial_factors[n,d] ? :($(kds[d])-$(mnds[n,d])) : kds[d] for n=1:N, d=1:D]

    diag_assignments = quote
        # Assign DiagonalOp.vals and .jkm properties to variables
    end
    for n=1:N
        for d=1:D
            if nontrivial_factors[n,d]
                push!(diag_assignments.args, quote
                    $(diags[n,d]) = tops[$n][$d].vals
                    ($(jnds[n,d]),$(knds[n,d]),$(mnds[n,d])) = tops[$n][$d].jkm
#                     println($n, ", ", $d, ": ", tops[$n][$d].jkm)
                    end)
            end
        end
    end

    iidx_init = quote
        IIDX=1
    end

    iidx_inc = quote
        IIDX += 1
    end

    for n=1:N
        terms = Any[]
        for d=1:D
            if nontrivial_factors[n,d]
                push!(terms, :($(mnds[n,d]) * stride(C,$d)))
            end
        end
        if length(terms) > 1
            offset = :(1-$(Expr(:call, :+, terms...)))
        elseif length(terms) == 1
            offset = :(1-$(terms[1]))
        else
            offset = :(1)
        end
        push!(iidx_init.args, :($(IIDXs[n])= $offset))
        push!(iidx_inc.args, :($(IIDXs[n]) += 1))
    end

    flag_updates = Any[quote end for d=1:D]

    for n=1:N
        if nontrivial_factors[n,D]
            push!(flag_updates[D].args,:(
            $(flags[n,D]) = ($(kdsmmnds[n,D]) in 1:size(C,$D)) && $(kds[D]) in (1+$(jnds[n,D])):(size(C,$D)-$(knds[n,D]));
            ))
        else
            push!(flag_updates[D].args,:($(flags[n,D]) = true))
        end

    end
    for d=1:D-1
        for n=1:N
            if nontrivial_factors[n,d]
                push!(flag_updates[d].args,:(
                $(flags[n,d]) = $(flags[n,d+1]) && ($(kdsmmnds[n,d]) in 1:(size(C,$d))) && $(kds[d]) in (1+$(jnds[n,d])):(size(C,$d)-$(knds[n,d]));
                ))

            else
                push!(flag_updates[d].args,:($(flags[n,d]) = $(flags[n,d+1])))
            end
        end
    end

    innerloop_stmts = quote
        C[IIDX] = c * C[IIDX]
    end

    for n=1:N

        tensor_elements = Expr(:call, :(*), :(ts[$n]), Expr(:ref, :B, IIDXs[n]))

        for d=1:D
            if nontrivial_factors[n,d]
                push!(tensor_elements.args,:($(diags[n,d])[$(kds[d])-$(jnds[n,d])]))
            end
        end

        push!(innerloop_stmts.args,
            :(
            if $(flags[n,1])
            C[IIDX] += $tensor_elements
#             else
#             println($n, ":    ", $(kds[1]),", ", $(kds[2]))
        end
            )
        )

    end

    innerloop = quote
        @simd for $(kds[1])=1:size(C,1)
            $(flag_updates[1])
            @inbounds begin
                $innerloop_stmts
            end
            $iidx_inc

        end
    end
    loop = innerloop
    for d=2:D
        loop = quote
            for $(kds[d])=1:size(C,$d)
                $(flag_updates[d])
                $loop
            end
        end
    end

    quote
        $diag_assignments
        $iidx_init
        $loop
        C
    end
end


"""
**Expected number of outgoing degrees**

    degree_out(N::AbstractEcologicalNetwork)
"""
function degree_out(N::AbstractEcologicalNetwork)
  d_o = vec(sum(N.A, dims=2))
  return Dict(zip(species(N; dims=1), d_o))
end

"""
**Expected number of ingoing degrees**

    degree_in(N::AbstractEcologicalNetwork)
"""
function degree_in(N::AbstractEcologicalNetwork)
  d_i = vec(sum(N.A, dims=1))
  return Dict(zip(species(N; dims=2), d_i))
end

function degree(N::AbstractEcologicalNetwork; dims::Union{Nothing,Integer}=nothing)
  dims == 1 && return degree_out(N)
  dims == 2 && return degree_in(N)
  if dims === nothing
    if typeof(N) <: AbstractBipartiteNetwork
      return merge(degree_out(N), degree_in(N))
    else
      din = degree_in(N)
      dout = degree_out(N)
      for k in keys(din)
        din[k] += dout[k]
      end
      return din
    end
  end
  throw(ArgumentError("dims can only be 1 (out-degre) or 2 (in-degree) or `nothing` (both), you used $(dims)"))
end

function degree(N::QuantitativeNetwork; dims::Union{Nothing,Integer}=nothing)
  degree(convert(BinaryNetwork, N), dims=dims)
end

"""
**Variance in the outgoing degree**

    degree_out_var(N::ProbabilisticNetwork)
"""
function degree_out_var(N::ProbabilisticNetwork)
  d_o_v = mapslices(a_var, N.A; dims=2)
  return Dict(zip(species(N; dims=1), d_o_v))
end

"""
**Variance in the ingoing degree**

    degree_in_var(N::ProbabilisticNetwork)
"""
function degree_in_var(N::ProbabilisticNetwork)
  d_i_v = mapslices(a_var, N.A; dims=1)'
  return Dict(zip(species(N; dims=2), d_i_v))
end

"""
    degree_var(N::ProbabilisticNetwork; dims::Union{Nothing,Integer}=nothing)

Variance in the degree of species in a probabilistic network.
"""
function degree_var(N::ProbabilisticNetwork; dims::Union{Nothing,Integer}=nothing)
  dims == 1 && return degree_out_var(N)
  dims == 2 && return degree_in_var(N)
  if dims === nothing
    if typeof(N) <: AbstractBipartiteNetwork
      return merge(degree_out_var(N), degree_in_var(N))
    else
      din = degree_in_var(N)
      dout = degree_out_var(N)
      for k in keys(din)
        din[k] += dout[k]
      end
      return din
    end
  end
  throw(ArgumentError("dims can only be 1 (out-degre) or 2 (in-degree) or `nothing` (both), you used $(dims)"))
end


# Unit Test for `SolveQuadraticProgram()`

using LinearAlgebra;
using SparseArrays;
using Plots;
using Random;
using StableRNGs;
using MAT;
using BenchmarkTools;

seedNumber = 1234;
Random.seed!(seedNumber);
# Random.seed!(StableRNG(seedNumber), seedNumber);

include("GenerateQuadraticProgram.jl");
include("SolveQuadraticProgram.jl");
include("LinearSystemSolvers.jl");

# @enum ProblemClass randomQp = 1 inequalityConstrainedQp equalityConstrainedQp optimalControl portfolioOptimization lassoOptimization huberFitting supportVectorMachine isotonicRegression
@enum DataSource dataSourceGenerated = 1 dataSourceLoaded

## Parameters

# Simulaion
numElements     = 800;
numConstraints  = 400;

dataSource      = dataSourceGenerated;
dataFileName    = "QpModel.mat";

# problemClass    = rand(instances(ProblemClass));
problemClass    = randomQp;

# Solver
numIterations   = 7500;
ρ               = 1e6
adptΡ           = true;
linSolverMode   = modeItertaive;
numItrPolish    = 0;

if (dataSource == dataSourceGenerated)
    mP, vQ, mA, vL, vU = GenerateRandomQP(problemClass, numElements, numConstraints = numConstraints);
else
    dMatFile = matread(dataFileName);
    mP = dMatFile["mP"];
    vQ = dropdims(dMatFile["vQ"]; dims = 2);
    mA = dMatFile["mA"];
    vL = dropdims(dMatFile["vL"]; dims = 2);
    vU = dropdims(dMatFile["vU"]; dims = 2);
end

# Some problems might change the actual data
numElements     = size(mP, 1);
numConstraints  = size(mA, 1);

vXX = zeros(numElements);

hLinSolInit = QDLdlInit;
hLinSol     = QDLdl!;

sBenchMark = @benchmarkable SolveQuadraticProgram!(vX, $mP, $vQ, $mA, $vL, $vU, hLinSolInit, hLinSol; numIterations = $numIterations, ρ = $ρ, adptΡ = $adptΡ, numItrPolish = $numItrPolish) setup = (vX = copy($vXX));
currTime    = time();
tuRunResult = BenchmarkTools.run_result(sBenchMark, samples = 10, evals = 1, seconds = 150); #<! A trick to get result as well
runTime     = time() - currTime;

display(tuRunResult[1]);
println("\nTotal Run Time: $runTime [Sec]");
println("The Alogrihtm Convergence Status: $(tuRunResult[2])\n\n");


using Random
using BenchmarkTools

function row_major(A::Array{Float64})

    m, n = size(A)

    for i in 1:m
        for j in 1:n
            A[i,j] = 1
        end
    end

end

function col_major(A::Array{Float64})

    m, n = size(A)

    for j in 1:n
        for i in 1:m
            A[i,j] = 1
        end
    end

end

function main()

    n = 100

    A = randn(n,n)

    # @benchmark row_major($A)
    @benchmark col_major($A)

end

main()


using StochasticArithmetic

# proposed by Siegfried Rump (rump's royal pain)
# http://www.davidhbailey.com/dhbtalks/dhb-num-repro.pdf
#
# true result: -0.82739605994682136814116
# the result is astronomicaly wrong !
# but consistant across precisions
function rumpTest(T)
    x::T = 77617.0
    y::T = 33096.0
    x2 = x*x
    y2 = y*y
    y4 = y2*y2
    y6 = y4*y2
    y8 = y4*y4
    return 333.75*y6 + x2*(11.0*x2*y2 - y6 - 121.0*y4 - 2.0) + 5.5*y8 + x/(2.0*y)
end

# non-instrumented run
println("result float: ", rumpTest(Float64))

# instrumented (thus likely different from Float64 runs) but we always get the same result as we are using the same seed
println("result Sfloat: ", rumpTest(SFloat64).value)
println("result Sfloat: ", rumpTest(SFloat64).value)
println("result Sfloat: ", rumpTest(SFloat64).value)
println("result Sfloat: ", rumpTest(SFloat64).value)
println("result Sfloat: ", rumpTest(SFloat64).value)

# now varying the seed between runs (getting the usual behaviour of stochastic arithmetic)
StochasticArithmetic.Determinism.resetSeed()
println("result Sfloat (reset): ", rumpTest(SFloat64).value)
StochasticArithmetic.Determinism.resetSeed()
println("result Sfloat (reset): ", rumpTest(SFloat64).value)
StochasticArithmetic.Determinism.resetSeed()
println("result Sfloat (reset): ", rumpTest(SFloat64).value)
StochasticArithmetic.Determinism.resetSeed()
println("result Sfloat (reset): ", rumpTest(SFloat64).value)
StochasticArithmetic.Determinism.resetSeed()
println("result Sfloat (reset): ", rumpTest(SFloat64).value)

# number of digits, computed over 10 runs
println( @reliable_digits rumpTest(SFloat64) )

# this function is exact despite having imprecise components
# it is, wrongly, labelled as inacurate by traditional stochastic arithmetic
function detTest(T)
    x = rumpTest(T) # numerical noise
    y = rumpTest(T) # identical numerical noise
    zero = x - y # exact zero
    return 1. + zero # exact one
end

# this should have an infinity of digits
println( @reliable_digits detTest(SFloat64) )


# This is for the PriorityQueue
using DataStructures

# creating abstract type for binary trees
abstract type BT end
type Empty <: BT end

type Node <: BT
    right::BT
    left::BT
    weight::Int64
    key::String
    bitstring::String
end

# This will search through the tree to create bitstrings for each character
# This outputs encoding Dict to be used for encoding
function create_codebook(n::Node)
    s = Stack(Node)
    push!(s, n)

    codebook = Dict{Char, String}()

    while (length(s) > 0)
        temp = pop!(s)
        if (typeof(temp.left) != Empty)
            temp.left.bitstring = temp.bitstring * "0"
            push!(s, temp.left)
        end
        if (typeof(temp.right) != Empty)
            temp.right.bitstring = temp.bitstring * "1"
            push!(s, temp.right)
        end
        if (typeof(temp.right) == Empty &&
            typeof(temp.left) == Empty)
            codebook[temp.key[1]] = temp.bitstring
        end
    end

    return codebook
end

# This outputs huffman tree to generate dictionary for encoding
function create_tree(phrase::String)

    # creating weights
    weights = PriorityQueue()
    for i in phrase
        temp_string = string(i)
        if (haskey(weights, temp_string))
            weights[temp_string] += 1
        else
            weights[temp_string] = 1
        end
    end

    # Creating all nodes to iterate through
    nodes = PriorityQueue{Node, Int64}()
    while(length(weights) > 0)
        weight = peek(weights)[2]
        key = dequeue!(weights)
        temp_node = Node(Empty(), Empty(), weight, key, "")
        enqueue!(nodes, temp_node, weight)
    end

    while(length(nodes) > 1)
        node1 = dequeue!(nodes)
        node2 = dequeue!(nodes)
        temp_node = Node(node1, node2, node1.weight + node2.weight, "", "")
        enqueue!(nodes, temp_node, temp_node.weight)
    end

    huffman_tree = dequeue!(nodes)
    return huffman_tree

end

function encode(codebook::Dict{Char, String}, phrase::String)
    final_bitstring = ""
    for i in phrase
        final_bitstring = final_bitstring * codebook[i]
    end

    return final_bitstring
end

function decode(huffman_tree::Node, bitstring::String)
    current = huffman_tree
    final_string = ""
    for i in bitstring
        if (i == '0')
            current = current.left
        else
            current = current.right
        end
        if(typeof(current.left) == Empty && typeof(current.right) == Empty)
            final_string = final_string * current.key
            current = huffman_tree
        end
    end

    return final_string
end

function two_pass_huffman(phrase::String)
    huffman_tree = create_tree(phrase)
    codebook = create_codebook(huffman_tree)
    println(codebook)
    bitstring = encode(codebook, phrase)
    final_string = decode(huffman_tree, bitstring)
    println(bitstring)
    println(final_string)
end

two_pass_huffman("aaaabbbccd")


# #
# Reproduces the figures in
# the work of V. Duchêne and B. Mélinand
# on the quadratic pseudo-spectral method (WW2)
# #
export IntegrateWW2,Figure
using WaterWaves1D,FFTW,Plots,LinearAlgebra,ProgressMeter;
#using JLD #(uncomment if using @save)

#--- Integration

"""
	IntegrateWW2(;init,args)

Integrate in time the WW2 system with an initial data depending on the provided `init`
- if `init=1`, then surface deformation `η(t=0,x)=exp(-x^p)` and velocity `v(t=0,x)=0` (with `p` provided as an optional argument, by default `p=2`)
- if `init=2`, then surface deformation `η(t=0,x)=exp(-x^2)` and velocity `v(t=0,x)=exp.(-x^2)*(sin(x)+sin(K*x)/K^2)` (with `K` provided as an optional argument, by default `K=100`)

Other arguments are optional:
- `μ` the shallowness parameter (default is `1`),
- `ϵ` the nonlinearity parameter (default is `0.1`),
- `L` the half-length of the mesh (default is `20`),
- `N` the number of collocation points (default is `2^10`),
- `T` the final time of integration (default is `10`),
- `dt` the timestep (default is `0.001`),
- `dealias`: dealiasing with Orlicz rule `1-dealias/(dealias+2)` (default is `1`, i.e. 2/3 rule, `0` means no dealiasing);
- `δ` the strength of the rectifier (default is `0.001`),
- `m` the order of the rectifier (as a regularizing operator, default is `-1`),
- `Ns` the number of stored computed times (default is all times).

Return `(problem,blowup_time,blowup,error_energy)` where
- `problem` contains all the information,
- `blowup_time` is the first time with NaN values (final time if there is none)
- `blowup` is a boolean indicating if NaN values occured
- `error_energy` is the relative energy preservation between first and final time
"""
function IntegrateWW2(;init=1,μ=1,ϵ=0.1,L=20,N=2^10,T=10,dt = 0.001,dealias=1,δ=0.001,m=-1,K=100,p=2,Ns=nothing)
	if isnothing(Ns)
		param = ( μ  = μ, ϵ  = ϵ,
				N  = N, L  = L,
	            T  = T, dt = dt )
	else
		param = ( μ  = μ, ϵ  = ϵ,
				N  = N, L  = L,
	            T  = T, dt = dt,
				Ns = Ns )
	end

	mesh=Mesh(param)
	if init == 1
		init = Init(x->exp.(-abs.(x).^p),x->zero(x))
	elseif init == 2
		init = Init(x->zero(x), x->exp.(-x.^2).*(sin.(x).+sin.(K*x)/K^2))
	else
		@error "argument init must be 1 or 2"
	end

	model = WWn(param;n = 2, δ = δ, m = m, dealias = dealias)
	problem = Problem(model, init, param)
	solve!( problem )

	(ηfin,vfin)   =  solution(problem)

	# check energy preservation
	x=mesh.x;k=mesh.k;
	Tmu = -1im*tanh.(sqrt(μ)*k)
	tmu = tanh.(sqrt(μ)*k)./k;tmu[1]=sqrt(μ);
	∂ₓ= 1im*k

	e(η,v) = 1/2* ( η.^2 .+ v.*ifft(tmu.*fft(v)) +ϵ*η.*(v.^2-ifft(Tmu.*fft(v)).^2) );
	e(η1,v1,η2,v2) = 1/2* ( (η1-η2).*(η1+η2) .+ (v1-v2).*ifft(tmu.*fft(v1+v2))
						+ϵ*(η1-η2).*(v1.^2-ifft(Tmu.*fft(v1)).^2)
						+ϵ*η2.*((v1-v2).*(v1+v2)-ifft(Tmu.*fft(v1-v2)).*ifft(Tmu.*fft(v1+v2))) );
	η0=init.η(x);v0=init.v(x)
	#error_energy_1 = abs(sum(e(ηfin,vfin)-e(η0,v0))/sum(e(η0,v0)))
	error_energy = abs(sum(e(ηfin,vfin,η0,v0))/sum(e(η0,v0)))
	@info "normalized preservation of the total energy: $error_energy\n"

	# compute blowup time (if any)
	blowup_index=0;blowup=false;
	for j in 1:problem.times.Ns
		blowup_index+=1
		if isnan(problem.data.U[j][1,1])
			blowup=true
			break
		end
	end
	blowup_time=	problem.times.ts[blowup_index]
	if blowup_time<T
		@info "The solution blowed at time t=$blowup_time (first NaN occurence)\n"
	end
	#display(plt)
	return problem,blowup_time,blowup,error_energy
end

#--- Figures

"""
	Figure(scenario;name,anim)

Several numerical experiments,
depending on the argument `scenario` (between 1 and 12)
corresponding to different figures in [DM]

- `scenario∈[1,7]`: spurious instability formation for smooth initial data.
    - `scenario=1`: no instability even without dealiasing and rectification, if small number of modes.
    - `scenario=2`: instabilities without dealiasing and rectification, if larger number of modes.
    - `scenario=3`: no instability with dealiasing and without rectification, if small number of modes.
    - `scenario=4`: instabilities with dealiasing and without rectification, if larger number of modes.
    - `scenario=5`: no instability in the presence of sufficiently regularizing rectifiers.
    - `scenario=6`: instabilities in the presence of insufficiently regularizing rectifiers.
    - `scenario=7`: mild instabilities in the presence of insufficiently strong rectifiers (δ small).
- `scenario∈[8,10]`: blowups for an initial data with a low-frequency and a high-frequency (with wavenumber K) component.
    - `scenario=8`: dependency of blowup times with respect to K (also `scenario=8.5` with a cut-off proportional to K)
    - `scenario=9`: dependency of blowup times with respect to ϵ.
    - `scenario=10`: an example of blowup with two different dealiasing.
- `scenario=11`: critical rectifier strength (δ) as a function of ϵ.
- `scenario≈12`: error of the rectified model (WW2) with respect to the water waves system, depending on δ and ϵ.
    - `scenario=12.1`: with initial data exp(-|x|)
    - `scenario=12.2`: with initial data exp(-|x|^2)
    - `scenario=12.3`: with initial data exp(-|x|^3)

Optional arguments are
- `compression`, an integer `m` used to reduce the size of figures by plotting only one every `m` points (when relevant),
- `name` (a string) used to save figures,
- `anim` generate animations (when relevant) if set to `true`.

Return relevant plots and problems depending on the situation.
"""
function Figure(scenario;compression=false,name=nothing,anim=false)

	if scenario == 1
		problem,=IntegrateWW2(init=1,μ=1,ϵ=0.1,L=20,N=2^9,T=10,dt = 0.001,dealias=0,δ=0,m=-1)
		plt=plot(problem;var=[:surface,:fourier],compression=compression,label="")
		if !isnothing(name)
			savefig(plt,"$name.pdf");savefig(plt,"$name.svg");
			if anim
				anim = @animate for t in LinRange(0,problem.times.tfin,101)
					plt = plot(problem;var=[:surface,:fourier],T=t,compression=compression)
					ylims!(plt[1],(-0.5,1.1))
				end
				gif(anim, "$name.gif", fps = 15)
			end
 		end
		return problem,plt

	elseif scenario == 2
		problem,=IntegrateWW2(init=1,μ=1,ϵ=0.1,L=20,N=2^11,T=1.5,dt = 0.001,dealias=0,δ=0,m=-1)
		plt=plot(problem,var=[:surface,:fourier],T=1.2;compression=compression,label="")
		if !isnothing(name)
			savefig(plt,"$name.pdf");savefig(plt,"$name.svg");
			if anim
				anim = @animate for t in LinRange(0,problem.times.tfin,101)
					plt = plot(problem;var=[:surface,:fourier],T=t,compression=compression)
					ylims!(plt[1],(-0.5,1.1))
				end
				gif(anim, "$name.gif", fps = 15)
			end
		end
		return problem,plt

	elseif scenario == 3
		problem,=IntegrateWW2(init=1,μ=1,ϵ=0.1,L=20,N=2^12,T=10,dt = 0.001,dealias=1,δ=0,m=-1)
		plt=plot(problem;var=[:surface,:fourier],compression=compression,label="")
		if !isnothing(name)
			savefig(plt,"$name.pdf");savefig(plt,"$name.svg");
			if anim
				anim = @animate for t in LinRange(0,problem.times.tfin,101)
					plt = plot(problem;var=[:surface,:fourier],T=t,compression=compression)
					ylims!(plt[1],(-0.5,1.1))
				end
				gif(anim, "$name.gif", fps = 15)
			end
		end
		return problem,plt

	elseif scenario == 4
		problem,=IntegrateWW2(init=1,μ=1,ϵ=0.1,L=20,N=2^14,T=1.5,dt = 0.001,dealias=1,δ=0,m=-1)
		plt=plot(problem,var=[:surface,:fourier],T=1.3;compression=compression,label="")
		if !isnothing(name)
			savefig(plt,"$name.pdf");savefig(plt,"$name.svg");
			if anim
				anim = @animate for t in LinRange(0,problem.times.tfin,101)
					plt = plot(problem;var=[:surface,:fourier],T=t,compression=compression)
					ylims!(plt[1],(-0.5,1.1))
				end
				gif(anim, "$name.gif", fps = 15)
			end
 		end
		return problem,plt

	elseif scenario == 5
		if anim Ns=nothing else Ns=1 end
		problem0,=IntegrateWW2(init=1,μ=1,ϵ=0.1,L=20,N=2^18,T=10,dt = 0.01,dealias=1,δ=0.01,m=-1/2,Ns=Ns)
		problem1,=IntegrateWW2(init=1,μ=1,ϵ=0.1,L=20,N=2^18,T=10,dt = 0.01,dealias=1,δ=0.01,m=-1,Ns=Ns)
		plt0=plot(problem0;var=[:surface,:fourier],compression=compression,label="")
		plt1=plot(problem1;var=[:surface,:fourier],compression=compression,label="")
		if !isnothing(name)
			savefig(plt0,"$name-r12.pdf");savefig(plt0,"$name-r12.svg");
			savefig(plt1,"$name-r1.pdf");savefig(plt1,"$name-r1.svg");
			if anim
				anim = @animate for t in LinRange(0,problem0.times.tfin,101)
					plt = plot([problem0,problem1];var=[:surface,:fourier],T=t,compression=compression,label=["m=-1/2" "m=-1"])
					ylims!(plt[1],(-0.5,1.1))
				end
				gif(anim, "$name.gif", fps = 15)
			end
		end
		return problem0,problem1,plt0,plt1

	elseif scenario == 6
		if anim Ns=nothing else Ns=10 end
		problem0,=IntegrateWW2(init=1,μ=1,ϵ=0.1,L=20,N=2^18,T=1,dt = 0.01,dealias=1,δ=0.01,m=-1/4,Ns=Ns)
		problem1,=IntegrateWW2(init=1,μ=1,ϵ=0.1,L=20,N=2^16,T=1,dt = 0.01,dealias=1,δ=0.01,m=-1/4,Ns=Ns)
		plt0=plot(problem0,var=[:surface,:fourier],T=0.6;compression=compression,label="")
		plt1=plot(problem1,var=[:surface,:fourier],T=0.6;compression=compression,label="")
		if !isnothing(name)
			savefig(plt0,"$name-N18.pdf");savefig(plt0,"$name-N18.svg");
			savefig(plt1,"$name-N16.pdf");savefig(plt1,"$name-N16.svg");
			if anim
				anim = @animate for t in LinRange(0,problem0.times.tfin,101)
					plt = plot([problem0,problem1];var=[:surface,:fourier],T=t,compression=compression,label=["N=2^18" "N=2^16"])
					ylims!(plt[1],(-0.5,1.1))
				end
				gif(anim, "$name.gif", fps = 15)
			end
		end
		return problem0,problem1,plt0,plt1

	elseif scenario in [7,7.1,7.2,7.3]
		if anim Ns=nothing else Ns=10 end
		p=round((scenario-7)*10);if p==0 p=2 end
		problem0,=IntegrateWW2(init=1,p=p,μ=1,ϵ=0.1,L=20,N=2^14,T=10,dt = 0.01,dealias=1,δ=0.01,m=-1,Ns=Ns)
		problem1,=IntegrateWW2(init=1,p=p,μ=1,ϵ=0.1,L=20,N=2^14,T=10,dt = 0.01,dealias=1,δ=0.002,m=-1,Ns=Ns)
		#problem2,=IntegrateWW2(init=1,p=p,μ=1,ϵ=0.1,L=20,N=2^14,T=2,dt = 0.01,dealias=1,δ=0.001,m=-1)
		plt0=plot(problem0;var=[:surface,:fourier],compression=compression,label="t=10")
		plot!(plt0,problem0,T=2;var=[:surface,:fourier],compression=compression,label="t=2")
		title!(plt0[1,1],"surface deformation")
		plt1=plot(problem1;var=[:surface,:fourier],compression=compression,label="t=10")
		plot!(plt1,problem1,T=2;var=[:surface,:fourier],compression=compression,label="t=2")
		title!(plt1[1,1],"surface deformation")
		if !isnothing(name)
			savefig(plt0,"$name-d01.pdf");savefig(plt0,"$name-d01.svg");
			savefig(plt1,"$name-d002.pdf");savefig(plt1,"$name-d002.svg");
			if anim
				anim = @animate for t in LinRange(0,problem0.times.tfin,101)
					plt = plot([problem0,problem1];var=[:surface,:fourier],T=t,compression=compression,label=["δ=0.01" "δ=0.002"])
					ylims!(plt[1],(-0.5,1.1))
				end
				gif(anim, "$name.gif", fps = 15)
			end
		end
		return problem0,problem1,plt0,plt1

	elseif scenario == 8
		blowups0=Real[];blowups1=Real[];
		K=100:20:800;iter=0;
		for k in K
			iter+=1;@info string("K=",k," (iteration ",iter,"/",length(K),")\n")
			~,blowup0=IntegrateWW2(init=2,K=k,μ=1,ϵ=0.15,L=20,N=2^14,T=10,dt = 0.001,dealias=1,δ=0,m=-1)
			~,blowup1=IntegrateWW2(init=2,K=k,μ=1,ϵ=0.2,L=20,N=2^14,T=10,dt = 0.001,dealias=1,δ=0,m=-1)
			push!(blowups0,blowup0);push!(blowups1,blowup1);
		end
		plt=scatter(K,[blowups0 blowups1],
				label=["ϵ=0.15" "ϵ=0.2"],
				marker=[:c :d],
				xlabel="K (in log scale)",
				ylabel="t (in log scale)",
				xscale=:log10,yscale=:log10)
		if !isnothing(name)
			savefig(plt,"$name.pdf");savefig(plt,"$name.svg");
			#@save(name,K,blowups0,blowups1)
		end
		return K,blowups0,blowups1,plt

	elseif scenario == 8.5
		K=100:20:800;
		blowups0=Real[];blowups1=Real[];iter=0;
		for k in K
			iter+=1;@info string("K=",k," (iteration ",iter,"/",length(K),")\n")
			N=2^14;d=N*π/k/20 # useful to define truncation at frequency K
			~,blowup0=IntegrateWW2(init=2,K=k,μ=1,ϵ=0.2,L=20,N=N,T=10,dt = 0.001,dealias=1,δ=0,m=-1)
			~,blowup1=IntegrateWW2(init=2,K=k,μ=1,ϵ=0.2,L=20,N=N,T=10,dt = 0.001,dealias=3/4*d-2,δ=0,m=-1)
			push!(blowups0,blowup0);push!(blowups1,blowup1);
		end
		plt=scatter(K,[blowups0 blowups1],
				label=["usual dealiasing" "adapted dealiasing"],
				marker=[:c :d],
				xlabel="K (in log scale)",
				ylabel="t (in log scale)",
				xscale=:log10,yscale=:log10)
		if !isnothing(name)
			savefig(plt,"$name.pdf");savefig(plt,"$name.svg");
			#@save(name,K,blowups0,blowups1)
		end
		return K,blowups0,blowups1,plt

	elseif scenario == 9
		Eps=10 .^(-1:0.02:0)
		blowups1=Real[];blowups2=Real[];blowups3=Real[];blowups4=Real[];iter=0;
		for eps in Eps
			iter+=1;@info string("ϵ=",eps," (iteration ",iter,"/",length(Eps),")\n")
			~,blowup1=IntegrateWW2(init=2,K=100,μ=1,ϵ=eps,L=20,N=2^14,T=10,dt = 0.001,dealias=1,δ=0,m=-1)
			~,blowup2=IntegrateWW2(init=2,K=200,μ=1,ϵ=eps,L=20,N=2^14,T=10,dt = 0.001,dealias=1,δ=0,m=-1)
			~,blowup3=IntegrateWW2(init=2,K=400,μ=1,ϵ=eps,L=20,N=2^14,T=10,dt = 0.001,dealias=1,δ=0,m=-1)
			~,blowup4=IntegrateWW2(init=2,K=800,μ=1,ϵ=eps,L=20,N=2^14,T=10,dt = 0.001,dealias=1,δ=0,m=-1)
			push!(blowups1,blowup1);push!(blowups2,blowup2);
			push!(blowups3,blowup3);push!(blowups4,blowup4);
		end
		plt=scatter(Eps,[blowups1 blowups2 blowups3 blowups4],
				label=["K=100" "K=200" "K=400" "K=800"],
				marker=[:c :d :ut :s],
				xlabel="ϵ (in log scale)",
				ylabel="t (in log scale)",
				xscale=:log10,yscale=:log10)
		if !isnothing(name)
			savefig(plt,"$name.pdf");savefig(plt,"$name.svg");
			#@save(name,Eps,blowups1,blowups2,blowups3,blowups4)
 		end
		return Eps,blowups1,blowups2,blowups3,blowups4,plt

	elseif scenario == 10
		K=400;N=2^14
		d=N*π/K/20 # useful to define truncation at frequency K
		problem0,=IntegrateWW2(init=2,K=K,μ=1,ϵ=0.2,L=20,N=N,T=2,dt = 0.001,dealias=1,δ=0,m=-1)
		problem1,=IntegrateWW2(init=2,K=K,μ=1,ϵ=0.2,L=20,N=N,T=2,dt = 0.001,dealias=3/4*d-2,δ=0,m=-1)

		plt0=plot(problem0, T=0.1;var=[:surface,:fourier],compression=compression,label="t=0.1")
		plot!(plt0,problem0,T=0.3;var=[:surface,:fourier],compression=compression,label="t=0.3")
		plot!(plt0,problem0,T=0.5;var=[:surface,:fourier],compression=compression,label="t=0.5")
		plot!(plt0[1,1],title="surface deformation")

		plt1=plot(problem1, T=0.1;var=[:surface,:fourier],compression=compression,label="t=0.1")
		plot!(plt1,problem1,T=0.3;var=[:surface,:fourier],compression=compression,label="t=0.3")
		plot!(plt1,problem1,T=0.5;var=[:surface,:fourier],compression=compression,label="t=0.5")
		plot!(plt1[1,1],title="surface deformation")
		if !isnothing(name)
			savefig(plt0,"$name-a.pdf");savefig(plt0,"$name-a.svg");
			savefig(plt1,"$name-b.pdf");savefig(plt1,"$name-b.svg");
			if anim
				anim = @animate for t in LinRange(0,problem0.times.tfin,101)
					plt = plot([problem0,problem1];var=[:surface,:fourier],T=t,compression=compression,label=["dealiasing" "low-pass filter"])
					ylims!(plt[1],(-0.5,1.1))
				end
				gif(anim, "$name.gif", fps = 15)

			end
		end
		return problem0,problem1,plt0,plt1

	elseif scenario == 11
		Eps=[0.01 0.0125 0.015 0.02 0.025 0.03 0.04 0.05 0.06 0.08 0.1 0.125 0.15 0.2 0.25 0.3 0.4 0.5 0.6 0.8]
		δc2=Real[];δc10=Real[];i=0;
		for eps in Eps
			i+=1
			#T=2
			δc_max=eps^2/2;δc_min=eps/500;
			for n=1:6
				@info string("iteration ",(i-1)*10+n,"/",10*length(Eps),"\n")
				@info string("critical ratio interval: ",(δc_min,δc_max))
				δc=sqrt(δc_max*δc_min)
				problem,blowuptime,blowup=IntegrateWW2(init=1,μ=1,ϵ=eps,L=20,N=2^20,T=2,dt = 0.005,dealias=1,δ=δc,m=-1,Ns=1)
				if blowup
					δc_min=δc
				else
					δc_max=δc
				end
			end
			push!(δc2,[δc_min,δc_max])
			#T=10
			δc_max=δc_min*1.5;
			for n=1:4
				@info string("iteration ",(i-1)*10+6+n,"/",10*length(Eps),"\n")
				@info string("critical ratio interval: ",(δc_min,δc_max))
				δc=sqrt(δc_max*δc_min)
				problem,blowuptime,blowup=IntegrateWW2(init=1,μ=1,ϵ=eps,L=20,N=2^20,T=10,dt = 0.005,dealias=1,δ=δc,m=-1,Ns=1)
				if blowup
					δc_min=δc
				else
					δc_max=δc
				end
			end
			push!(δc10,[δc_min,δc_max])
			#@save(name,Eps,δc2,δc10)
		end

		plt=plot()
		scatter!(plt,log10.(Eps[:]),[sqrt(δc10[i][2]*δc10[i][1]) for i in 1:length(Eps)],label="T=10",color=:1,marker=:c)
		scatter!(plt,log10.(Eps[:]),[sqrt(δc2[i][2]*δc2[i][1]) for i in 1:length(Eps)],label="T=2",color=:2,marker=:d)
		y2=OHLC[(δc2[i][1],δc2[i][1],δc2[i][2],δc2[i][2]) for i=1:length(Eps)]
		y10=OHLC[(δc10[i][1],δc10[i][1],δc10[i][2],δc10[i][2]) for i=1:length(Eps)]
		ohlc!(plt,log10.(Eps[:]),y10,label="",linecolor=:blue)
		ohlc!(plt,log10.(Eps[:]),y2,label="",linecolor=:orange)
		plot!(plt,log10.(Eps[:]),Eps[:].^2,label="δ=ϵ²",linecolor=:3)
		plot!(yscale=:log10,legend=:topleft,ylabel="δ (in log scale)",xlabel="ϵ (in log scale)")
		if !isnothing(name)
			savefig(plt,"$name.pdf");savefig(plt,"$name.svg");
			#@save(name,Eps,δc2,δc10)
		end
		return Eps,δc2,δc10,plt

	elseif scenario in [12,12.1,12.2,12.3]
		p=round((scenario-12)*10);if p==0 p=2 end

		Eps=[0.2,0.1,0.05]
		Delta=10 .^ (-2:0.1:0)
		Norms=Vector{Real}[];j=0;
		for eps in Eps
			norms=Real[];i=0;j+=1;
			for delta in Delta
				i+=1
				@info string("ϵ=",eps,", δ=",delta," (iteration ",(j-1)*length(Delta)+i,"/",length(Eps)*length(Delta),")")
				problem,=IntegrateWW2(init=1,p=p,μ=1,ϵ=eps,L=20,N=2^12,T=10,dt = 0.01,dealias=1,δ=delta,m=-1)
				param = (μ=1,ϵ=eps,L=20,N=2^12,T=10,dt = 0.01)
				model = WaterWaves(param; dealias = 1,method=1,maxiter=20)
				problem0 = Problem(model, problem.initial, param)
				solve!( problem0 )
				(η0,v0,x0)=solution(problem0)
				(η,v)=solution(problem,x=x0)
				push!(norms,norm(η-η0,Inf))
			end
			push!(Norms,norms)
		end

		plt=plot()
		markers=[:c :d :ut :s :r :lt :pent :hex :hep :oct :+ :x]
		for i in 1:length(Eps)
			scatter!(plt,Delta,Norms[i],label="ϵ=$Eps[i]",marker=markers[i])
		end
		plot!(plt,xlabel="δ",ylabel="error",legend=:topleft)
		if !isnothing(name)
			savefig(plt,"$name.pdf");savefig(plt,"$name.svg");
			plot!(plt,xscale=:log10,yscale=:log10)
			plot!(plt,xlabel="δ (in log scale)",ylabel="error (in log scale)")
			savefig(plt,"$name-log.pdf");savefig(plt,"$name-log.svg");
			#@save(name,Eps,Delta,Norms)
		end
		return Eps,Delta,Norms,plt

	else
		error("The first argument must be as specified in the documentation (basically between 1 and 12)")
	end

end
nothing


using Dates
using DataFrames, CSV
using Gadfly, Cairo, Fontconfig, Compose
using StatsBase, Statistics

theme = Theme(plot_padding = [1mm])

## upload date

seconds_per_week = 7 * 24 * 3600

df = CSV.read("dates.1.csv", DataFrame)
v3c1 = DataFrame(collection = "V3C1", time = map(x -> round(Int, datetime2unix(DateTime(x)) / seconds_per_week / 2) * seconds_per_week * 2, df[:, :date]), count = df[:, :count] ./ sum(df[:, :count]))
df = CSV.read("dates.2.csv", DataFrame)
v3c2 = DataFrame(collection = "V3C2", time = map(x -> round(Int, datetime2unix(DateTime(x)) / seconds_per_week / 2) * seconds_per_week * 2, df[:, :date]), count = df[:, :count] ./ sum(df[:, :count]))

h = innerjoin(v3c1, v3c2, on = :time, makeunique=true)

df = combine(groupby(vcat(v3c1, v3c2), [:time, :collection]), :count => sum)

p = plot(df, x = :time, y = :count_sum, color = :collection, Geom.line,
Stat.xticks(ticks=[datetime2unix(DateTime("$(y)-01-01")) for y in 2006:2018]), Stat.yticks(ticks=collect(0:0.002:0.012)),
Scale.x_continuous(labels=x -> Dates.format(unix2datetime(x), "yyyy")), Scale.y_continuous(labels = x -> "$(x * 1000) ‰"),
Guide.XLabel(""), Guide.YLabel("Relative upload rate", orientation=:vertical), Guide.colorkey(title = "Collection"), theme)

draw(PDF("upload_hist.pdf", 10cm, 4.5cm), p)

##resolution

res_ticks = [(320,240), (640, 360), (640, 480), (960,540), (960,720), (1280, 720), (1280,960), (1920, 1080)]
tic_dict = Dict{Number, Tuple}()
for t in res_ticks tic_dict[reduce(*, t)] = t end

df = CSV.read("resolution.1.csv", DataFrame)
v3c1 = DataFrame(collection = "V3C1", count = df[:, :count] ./ sum(df[:, :count]), pixels = df[:, :width] .* df[:, :height])
df = CSV.read("resolution.2.csv", DataFrame)
v3c2 = DataFrame(collection = "V3C2", count = df[:, :count] ./ sum(df[:, :count]), pixels = df[:, :width] .* df[:, :height])

h = innerjoin(v3c1, v3c2, on = :pixels, makeunique=true)
cor(h[:, :count], h[:, :count_1])

df = combine(groupby(vcat(v3c1, v3c2), [:pixels, :collection]), :count => sum)

p = plot(df, y = :count_sum, x = :pixels, color = :collection, Geom.line,
Coord.cartesian(xmax=2.2e6),
Stat.yticks(ticks = map(sqrt, [1e-3, 1e-2, 5e-2, 1e-1, 2e-1, 5e-1])), Scale.y_sqrt(labels = x ->(v = ((x^2) * 10000); "$(round(Int, v) / 100) %")),
Stat.xticks(ticks = collect(keys(tic_dict))), Guide.xticks(orientation=:vertical),
Scale.x_continuous(labels = x -> (t = tic_dict[x]; return "$(t[1])×$(t[2])")),
Guide.XLabel(""),#Guide.XLabel("Video resolution in pixels"),
Guide.YLabel("Fraction of videos", orientation=:vertical), Guide.colorkey(title = "Collection"), Theme(plot_padding = [2mm, -1mm, 2mm, -3mm])
)

draw(PDF("resolution_hist.pdf", 10cm, 5cm), p)

## segment duration

df = CSV.read("segment_duration.1.csv", DataFrame)
df[:, :duration]= round.(df[:, :duration]; digits = 1)
df = combine(groupby(df, :duration), :count => sum)
v3c1 = DataFrame(collection = "V3C1", count = df[:, :count_sum] ./ sum(df[:, :count_sum]), duration = df[:, :duration])

df = CSV.read("segment_duration.2.csv", DataFrame)
df[:, :duration]= round.(df[:, :duration]; digits = 1)
df = combine(groupby(df, :duration), :count => sum)
v3c2 = DataFrame(collection = "V3C2", count = df[:, :count_sum] ./ sum(df[:, :count_sum]), duration = df[:, :duration])

h = innerjoin(v3c1, v3c2, on = :duration, makeunique=true)
cor(h[:, :count], h[:, :count_1])

df = vcat(v3c1, v3c2)

p = plot(df, x = :duration, y = :count, color = :collection, Geom.line, Coord.cartesian(xmin = 0, xmax = 50, ymin = -6, ymax = -1),  Scale.y_log10,
Guide.XLabel(""), Guide.YLabel("Fraction of segments", orientation = :vertical), Guide.colorkey(title = "Collection"), theme)

draw(PDF("segment_duration_hist.pdf", 10cm, 4.5cm), p)

## video duration

df = CSV.read("duration.1.csv", DataFrame)
df[:, :duration_min]= round.(df[:, :duration] / 60.0; digits = 2)
df = combine(groupby(df, :duration_min), :count => sum)
v3c1 = DataFrame(collection = "V3C1", count = df[:, :count_sum] ./ sum(df[:, :count_sum]), duration = df[:, :duration_min])
v3c1 = sort(v3c1, :duration)
v3c1[:, :cumsum] = cumsum(v3c1[:, :count])

df = CSV.read("duration.2.csv", DataFrame)
df[:, :duration_min]= round.(df[:, :duration] / 60.0; digits = 2)
df = combine(groupby(df, :duration_min), :count => sum)
v3c2 = DataFrame(collection = "V3C2", count = df[:, :count_sum] ./ sum(df[:, :count_sum]), duration = df[:, :duration_min])
v3c2 = sort(v3c2, :duration)
v3c2[:, :cumsum] = cumsum(v3c2[:, :count])

h = innerjoin(v3c1, v3c2, on = :duration, makeunique=true)
cor(h[:, :count], h[:, :count_1])
cor(h[:, :cumsum], h[:, :cumsum_1])

df = vcat(v3c1, v3c2)

p = plot(df, x = :duration, y = :cumsum, color = :collection, Geom.line, Coord.cartesian(xmin = 0, xmax = 60, ymin = 0, ymax = 1),
	Guide.YTicks(ticks = collect(0:0.1:1)), Scale.y_continuous(labels = x -> "$(round(Int, 100 * x))%"),
    Guide.XLabel(""), Guide.YLabel("Cumulative fraction", orientation = :vertical), Guide.colorkey(title = "Collection"), theme)

draw(PDF("video_duration.pdf", 10cm, 4.5cm), p)

## vimeo category

df = CSV.read("category.1.csv", DataFrame)
v3c1 = DataFrame(collection = "V3C1", count = df[:, :count] ./ sum(df[:, :count]), category = df[:, :category])
df = CSV.read("category.2.csv", DataFrame)
v3c2 = DataFrame(collection = "V3C2", count = df[:, :count] ./ sum(df[:, :count]), category = df[:, :category])

h = innerjoin(
	DataFrame(category = sort(v3c1, :count, rev = true)[:, :category], rank = collect(1:size(v3c1,1))),
	DataFrame(category = sort(v3c2, :count, rev = true)[:, :category], rank = collect(1:size(v3c2,1))),
	on = :category, makeunique=true)

corspearman(h[:, :rank], h[:, :rank_1])

df = vcat(v3c1, v3c2)
df[:, :category] = map(x -> x[13:end], df[:, :category])
df = sort(df, :count, rev = true)

p = plot(df[1:20, :], x = :category, y = :count, color = :collection, Geom.bar(position = :dodge),
       Guide.XLabel(""), Guide.YLabel("Fraction of all videos", orientation=:vertical), Guide.colorkey(title = "Collection"),
       Scale.y_continuous(labels = x -> "$(round(Int, 100 * x))%"), Theme(bar_spacing = 1mm, plot_padding = [2mm, 0mm, 2mm, -3mm]))

draw(PDF("category_hist.pdf", 10cm, 6.5cm), p)

## semantic distances

total = CSV.read("distances/total.csv", DataFrame)
v3c1 = CSV.read("distances/v3c1.csv", DataFrame)
v3c2 = CSV.read("distances/v3c2.csv", DataFrame)

df = vcat(
DataFrame(collection = "V3C1", dist = v3c1[:, :dist] ./ 1000, count = v3c1[:, :count_sum]),
DataFrame(collection = "V3C2", dist = v3c2[:, :dist] ./ 1000, count = v3c2[:, :count_sum]),
DataFrame(collection = "V3C1 + V3C2", dist = total[:, :dist] ./ 1000, count = total[:, :count_sum])
)

h = df[df[:, :collection] .== "V3C1", :]
mean(h[:, :dist], weights(h[:, :count])) #45.917683444097236
std(h[:, :dist], weights(h[:, :count])) #6.492919349371129

h = df[df[:, :collection] .== "V3C2", :]
mean(h[:, :dist], weights(h[:, :count])) #46.22709575581058
std(h[:, :dist], weights(h[:, :count])) #6.489823152880049

h = df[df[:, :collection] .== "V3C1 + V3C2", :]
mean(h[:, :dist], weights(h[:, :count])) #46.09621236345112
std(h[:, :dist], weights(h[:, :count])) #6.491085271947892

p = plot(df, x = :dist, y = :count, color = :collection, Geom.line, Coord.cartesian(xmin = 0, xmax = 110),
Guide.XTicks(ticks = collect(0:10:110)), Guide.XLabel("Pairwise L1-Distance"), Guide.YLabel("Count"), Guide.colorkey(title = "Collection"), theme)
draw(PDF("l1_dist_hist.pdf", 10cm, 6cm), p)

##ASR

df = CSV.read("asr_statistics.csv", DataFrame)
wordrate = ceil.(df[:, :wordcount] ./df[:, :speechtime] .* 60)
cmap = countmap(wordrate)
k = collect(keys(cmap))
hist = DataFrame(wpm = k, count = map(x -> cmap[x], k))
p = plot(hist, x = :wpm, y = :count, Geom.bar, Guide.XLabel(""), Guide.YLabel("Count"), Coord.cartesian(xmin = 1, ymin = 0, xmax = 220, ymax = 1200), theme)
draw(PDF("wpm.pdf", 10cm, 5cm), p)

##OCR

#df = CSV.read("textcount.csv", DataFrame)
#p = plot(df, x = :segments, y = :count, Geom.bar(), Coord.cartesian(xmin = 0, xmax = 500, ymin = 0, ymax = 350), Guide.XLabel("Key-frames with Text per Video"), Guide.YLabel("Count"))

df = CSV.read("segment_ocr_count.csv", DataFrame)
p = plot(df, x = :text_fraction, Geom.histogram(bincount = 100), Guide.XTicks(ticks = collect(0:0.1:1)), Scale.x_continuous(labels = x -> "$(round(Int, x * 100)) %"), Guide.XLabel(""), Guide.YLabel("Videos"), theme)

draw(PDF("ocr_hist.pdf", 10cm, 6cm), p)

df = CSV.read("text_per_frame.csv", DataFrame)
p = plot(df, x = :segments, y = :count, Geom.bar(), Guide.XLabel(""), Guide.YLabel("Count"), Scale.y_log10, Coord.cartesian(xmin = 0, xmax = 600), theme)
draw(PDF("text_per_frame_hist.pdf", 10cm, 5cm), p)

##Faces

df = CSV.read("V3C2-faces.csv/V3C2-faces.csv", DataFrame)
h = countmap(df[:, 2])
k = collect(keys(h))
df = DataFrame(faces = k, count = map(x -> h[x], k))
df = sort(df, :count, rev = true)
df[:, :cumsum] = cumsum(df[:, :count])

plot(df, x = :faces, y = :cumsum, Geom.line)


"Integration type for systems with user defined discrete dynamics."
abstract type PassThrough <: Explicit end
integrate(::Type{PassThrough}, model::AbstractModel, x::StaticVector, u::StaticVector, t, dt) = x

"Exponential integration for linear systems with ZOH on controls."
abstract type Exponential <: Explicit end

abstract type AbstractLinearModel <: AbstractModel end

"""
    LinearModel{n,m,T} <: AbstractModel

A concrete type for creating efficient linear model representations. This model type will
automatically define the continuous or discrete version of the dynamics and jacobian functions.
Supports continuous/discrete, time invariant/varying, and affine models.

# Constructors
    LinearModel(A::AbstractMatrix, B::AbstractMatrix, [dt=0, use_static]) # time invariant
    LinearModel(A::AbstractMatrix, B::AbstractMatrix, d::AbstractVector, [dt=0, use_static]) # time invariant affine
    LinearModel(A::Vector{TA}, B::Vector{TB}, [times::AbstractVector, dt::Real=0, use_static]) # time varying
    LinearModel(A::Vector{TA}, B::Vector{TB}, d::Vector{Td}, [times::AbstractVector, dt=0, use_static]) # time varying affine

    LinearModel(n::Integer, m::Integer, [is_affine=false, times=1:0, dt=0, use_static]) # constructor with zero dynamics matrices

By default, the model is assumed to be continuous unless a non-zero dt is specified. For time varying models, `searchsortedlast` is
called on the `times` vector to get the discrete time index from the continuous time. The `use_static` keyword is automatically
specified based on array size, but can be turned off in case of excessive compilation times.
"""
struct LinearModel{n,m,T,e} <: AbstractLinearModel
    A::Vector{SizedMatrix{n,n,T,2,Matrix{T}}}
    B::Vector{SizedMatrix{n,m,T,2,Matrix{T}}}
    d::Vector{SizedVector{n,T,Vector{T}}}
    times::Vector{T}
    dt::T
    xdot::MVector{n,T}
    E::SizedMatrix{e,e,T,2,Matrix{T}}   # Matrix for exponential integration, if needed
    use_static::Bool
    function LinearModel(
            A::Vector{TA},
            B::Vector{TB},
            d::Vector{Td} = SVector{size(A[1],1),eltype(A[1])}[];
            times::AbstractVector = 1:0,
            dt::Real = 0,
            use_static::Bool = (length(A[1]) < 14*14)
        ) where {TA <: AbstractMatrix,TB<:AbstractMatrix,Td<:AbstractVector}
        n,m = size(B[1])
        @assert size(A[1]) == (n,n)
        isempty(d) || @assert size(d[1]) == (n,)
        @assert length(A) == length(B)
        length(A) > 1 && @assert length(A) == length(times) "Must pass in time vector of appropriate length. Expected length of $(length(A)), got $(length(times))."
        @assert length(A) > 0
        @assert issorted(times)
        T = promote_type(eltype(TA), eltype(TB), eltype(Td))
        A = SizedMatrix{n,n,T}.(a for a in A)
        B = SizedMatrix{n,m,T}.(b for b in B)
        d = SizedVector{n,T}.(d_ for d_ in d)
        times = Vector{T}(times)
        xdot = @MVector zeros(n)

        # size of matrix exponential
        is_affine = !isempty(d)
        e = is_affine ? 2n+m : n+m
        E = SizedMatrix{e,e}(zeros(e,e))

        new{n,m,T,e}(A, B, d, times, dt, xdot, E, use_static)
    end
end
state_dim(::LinearModel{n}) where n = n
control_dim(::LinearModel{<:Any,m}) where m = m

is_discrete(model::LinearModel) = model.dt !== zero(model.dt)
is_affine(model::LinearModel) = !isempty(model.d)
is_timevarying(model::LinearModel) = !isempty(model.times)
get_k(model::LinearModel, t) = is_timevarying(model) ? searchsortedlast(model.times, t) : 1
get_A(model::LinearModel, k=1) = model.A[k]
get_B(model::LinearModel, k=1) = model.B[k]
get_d(model::LinearModel, k=1) = model.d[k]

LinearModel(A::AbstractMatrix, B::AbstractMatrix; dt=0, kwargs...) = LinearModel([A],[B], dt=dt; kwargs...)
LinearModel(A::AbstractMatrix, B::AbstractMatrix, d::AbstractVector; dt=0, kwargs...) = LinearModel([A],[B],[d], dt=dt; kwargs...)

function LinearModel(n::Integer, m::Integer; is_affine=false, times=1:0, kwargs...)
    N_ = (length(times) > 1) ? length(times) : 1

    # only linearize about N-1 points in trajectory
    A = [zero(SizedMatrix{n,n}) for i=1:N_]
    B = [zero(SizedMatrix{n,m}) for i=1:N_]
    d = is_affine ? [zero(SizedVector{n,Float64}) for i=1:N_] : SizedVector{n,Float64}[]

    LinearModel(A, B, d; times=times, kwargs...)
end

function linear_dynamics(model::LinearModel, x, u, k::Int=1)
    if model.use_static
        A = SMatrix(model.A[k])
        B = SMatrix(model.B[k])
        xdot = linear_dynamics(A, B, x, u)
    else
        A = model.A[k]
        B = model.B[k]
        linear_dynamics!(model.xdot, A, B, x, u)
        xdot = SVector(model.xdot)
    end

    if is_affine(model)
        d = SVector(model.d[k])
        xdot += d
    end

    return xdot
end

linear_dynamics(A, B, x, u) = A*x + B*u
function linear_dynamics!(xdot, A, B, x, u)
    mul!(xdot, A, x)
    mul!(xdot, B, u, 1.0, 1.0)
end

function dynamics(model::LinearModel, x, u, t=0.0)
    @assert !is_discrete(model) "Can't call continuous dynamics on a discrete LinearModel"
    k = get_k(model, t)
    linear_dynamics(model, x, u, k)
end

function discrete_dynamics(::Type{PassThrough}, model::LinearModel, x, u, t, dt)
    @assert is_discrete(model) "Can't call discrete dynamics without integration on a continuous LinearModel"
    k = get_k(model, t)
    dt_model = is_timevarying(model) ? model.times[k+1] - model.times[k] : model.dt
    @assert dt ≈ dt_model "Incorrect dt. Expected $dt_model, got $dt."
    linear_dynamics(model, x, u, k)
end

function jacobian!(∇f::AbstractMatrix, model::LinearModel, z::AbstractKnotPoint)
    @assert !is_discrete(model) "Can't call continuous jacobian on a discrete LinearModel"

	t = z.t
    k = get_k(model, t)

    n = state_dim(model)
    m = control_dim(model)

    ∇f[1:n, 1:n] .= model.A[k]
    ∇f[1:n, (n+1):(n+m)] .= model.B[k]
    true
end

function discrete_jacobian!(::Type{PassThrough}, ∇f, model::LinearModel, z::AbstractKnotPoint{<:Any,n,m}) where {n,m}
    @assert is_discrete(model) "Can't call discrete jacobian without integration on a continuous LinearModel"

    t = z.t
    k = get_k(model, t)
    ix = 1:n
    iu = n .+ (1:m)
    ∇f[ix,ix] .= model.A[k]
    ∇f[ix,iu] .= model.B[k]

    nothing
end

# Exponential Integration
function discrete_dynamics(::Type{Exponential}, model::LinearModel,
        z::AbstractKnotPoint{<:Any,n,m}) where {n,m}
    k = get_k(model, z.t)
    A = model.A[k]
    B = model.B[k]
    d = model.d[k]
    E = model.E

    # Calculate the matrix exponential
    matrix_exponential!(E, A, B, z)

    if is_affine
        z̄ = [z.z; d]
        return E * z̄
    else
        return E * z.z
    end
end

function discrete_jacobian!(::Type{Exponential}, F, model::LinearModel{n,m},
        z::AbstractKnotPoint) where {n,m}
    # Calculate the matrix exponential
    matrix_exponential!(model, z)

    F .= model.E[1:n, 1:n+m]
end

function matrix_exponential!(E::SizedMatrix, A, B, dt,
        ::Val{S}=Val(length(E) < 14*14)) where S

    # Copy Jacobian to matrix exponential
    n,m = size(B)
    ix,iu = 1:n, n .+ (1:m)
    E[ix, ix] .= A
    E[ix, iu] .= B

    if size(E,1) == 2n+m
        E[1:n, (1:n) .+ (n+m)] .= I(n)
    end

    # Exponentiate
    E.data .*= dt
    if S
        Ec = SMatrix(E)
    else
        Ec = E.data
    end
    Ed = exp(Ec)

    # Copy back to E
    E .= Ed
end

function discretize!(::Type{Exponential}, F::DynamicsJacobian, d,
        model::AbstractModel, z::AbstractKnotPoint, is_affine::Bool=true) where Q <: Explicit

end


# Tests for Cost DEA Models
@testset "CostDEAModel" begin

    ## Test Cost DEA Model with Cooper et al. (2007)
    # Test agains book results
    X = [3 2; 1 3; 4 6]
    Y = [3; 5; 6]
    W = [4 2; 4 2; 4 2]

    deacostcooper = deacost(X, Y, W, rts = :CRS)

    @test typeof(deacostcooper) == CostDEAModel

    @test nobs(deacostcooper) == 3
    @test ninputs(deacostcooper) == 2
    @test noutputs(deacostcooper) == 1
    @test ismonetary(deacostcooper) == false

    @test efficiency(deacostcooper, :Economic)   ≈ [0.375; 1; 0.429] atol = 1e-3
    @test efficiency(deacostcooper, :Technical)  ≈ [0.9  ; 1; 0.6  ] atol = 1e-3
    @test efficiency(deacostcooper, :Allocative) ≈ [0.417; 1; 0.714] atol = 1e-3

    ## Test Cost DEA Model with Zofío and Prieto (2006) data.
    # Test agains results in R
    X = [5 3; 2 4; 4 2; 4 8; 7 9]
    Y = [7 4; 10 8; 8 10; 5 4; 3 6]
    W = [2 1; 2 1; 2 1; 2 1; 2 1.0]

    # Cost CRS
    deacostcrs = deacost(X, Y, W, rts = :CRS)
    @test efficiency(deacostcrs) ≈ [0.4307692308;
                                    1.000;
                                    1.000;
                                    0.250;
                                    0.2608695652]
    @test efficiency(deacostcrs, :Technical) ≈ [0.6364;
                                1.000;
                                1.000;
                                0.250;
                                0.2609]  atol = 1e-3
    @test efficiency(deacostcrs, :Allocative) ≈ [0.6769230769;
                                1.000;
                                1.000;
                                1.000;
                                1.000]

    @test efficiency(deacost(targets(deacostcrs, :X), targets(deacostcrs, :Y), W, rts = :CRS)) ≈ ones(5)

    # Cost VRS
    deacostvrs = deacost(X, Y, W, rts = :VRS)
    @test efficiency(deacostvrs) ≈ [0.6153846154;
                                    1.000;
                                    1.000;
                                    0.500;
                                    0.3478260870]
    @test efficiency(deacostvrs, :Technical) ≈ [0.750;
                                1.000;
                                1.000;
                                0.500 ;
                                0.375]  atol = 1e-3
    @test efficiency(deacostvrs, :Allocative) ≈ [0.8205128205;
                                1.000;
                                1.000;
                                1.000;
                                0.9275362319]

    @test efficiency(deacost(targets(deacostvrs, :X), targets(deacostvrs, :Y), W, rts = :VRS)) ≈ ones(5)

    # Check defaults
    @test efficiency(deacost(X, Y, W)) == efficiency(deacostvrs)
    @test efficiency(deacostvrs, :Economic) == efficiency(deacostvrs)

    # Print
    show(IOBuffer(), deacostcooper)

    # Test errors
    @test_throws DimensionMismatch deacost([1; 2 ; 3], [4 ; 5], [1; 1; 1]) #  Different number of observations
    @test_throws DimensionMismatch deacost([1; 2; 3], [4; 5; 6], [1; 2; 3; 4]) # Different number of observation in prices
    @test_throws DimensionMismatch deacost([1 1; 2 2; 3 3 ], [4; 5; 6], [1 1 1; 2 2 2; 3 3 3]) # Different number of input prices and inputs
    @test_throws ArgumentError deacost([1; 2; 3], [4; 5; 6], [1; 2; 3], rts = :Error) # Invalid returns to scale
    @test_throws ArgumentError deacost([1; 2; 3], [4; 5; 6], [1; 2; 3], dispos = :Error) # Invalid disposability
    @test_throws ArgumentError normfactor(deacost(X, Y, W)) # CostDEAModel has no normalization factor

    # ------------------
    # Weak Disposability Tests
    # ------------------

    X = [1; 2; 3; 2; 4]
    Y = [2; 3; 4; 1; 3]
    W = [1; 1; 1; 1; 1]

    deacostStrong = deacost(X, Y, W, dispos = :Strong)
    @test efficiency(deacostStrong, :Economic) ≈ [1.0; 1.0; 1.0; 0.5; 0.5]
    @test efficiency(deacostStrong, :Technical) ≈ [1.0; 1.0; 1.0; 0.5; 0.5]
    @test efficiency(deacostStrong, :Allocative) ≈ [1.0; 1.0; 1.0; 1.0; 1.0]

    deacostWeak = deacost(X, Y, W, dispos = :Weak)
    @test efficiency(deacostWeak, :Economic) ≈ [1.0; 1.0; 1.0; 1.0; 0.5]
    @test efficiency(deacostWeak, :Technical) ≈ [1.0; 1.0; 1.0; 1.0; 0.5]
    @test efficiency(deacostWeak, :Allocative) ≈ [1.0; 1.0; 1.0; 1.0; 1.0]

    # ------------------
    # Test Vector and Matrix inputs and outputs
    # ------------------
    # Tests against results in R

    # Inputs is Matrix, Outputs is Vector
    X = [2 2; 1 4; 4 1; 4 3; 5 5; 6 1; 2 5; 1.6	8]
    Y = [1; 1; 1; 1; 1; 1; 1; 1]
    W = [1 1; 1 1; 1 1; 1 1; 1 1; 1 1; 1 1; 1 1]

    @test efficiency(deacost(X, Y, W)) ≈ [1; 0.8; 0.8; 0.5714285714; 0.4; 0.5714285714; 0.5714285714; 0.4166666667]

    # Inputs is Vector, Output is Matrix
    X = [1; 1; 1; 1; 1; 1; 1; 1]
    Y = [7 7; 4 8; 8 4; 3 5; 3 3; 8 2; 6 4; 1.5 5]
    W = [1; 1; 1; 1; 1; 1; 1; 1]

    @test efficiency(deacost(X, Y, W)) ≈ [1; 1; 1; 1; 1; 1; 1; 1]

    # Inputs is Vector, Output is Vector
    X = [2; 4; 8; 12; 6; 14; 14; 9.412]
    Y = [1; 5; 8; 9; 3; 7; 9; 2.353]
    W = [1; 1; 1; 1; 1; 1; 1; 1]

    @test efficiency(deacost(X, Y, W)) ≈ [1; 1; 1; 1; 0.5; 0.4761904762; 0.8571428571; 0.2843710157]

end


immutable Truncated{D<:UnivariateDistribution,S<:ValueSupport} <: Distribution{Univariate,S}
    untruncated::D
    lower::Float64
    upper::Float64
    nc::Float64
    function Truncated{T<:UnivariateDistribution}(d::T, l::Real, u::Real, nc::Real)
        if l >= u
            error("upper must be > lower")
        end
        new(d, float64(l), float64(u), float64(nc))
    end
end

function Truncated{S<:ValueSupport}(d::UnivariateDistribution{S}, l::Real, u::Real, nc::Real)
    Truncated{typeof(d),S}(d,l,u,nc)
end

function Truncated{S<:ValueSupport}(d::UnivariateDistribution{S}, l::Real, u::Real)
    Truncated{typeof(d),S}(d,l,u, cdf(d, u) - cdf(d, l))
end

insupport(d::Truncated, x::Real) =
    x >= d.lower && x <= d.upper && insupport(d.untruncated, x)

function pdf(d::Truncated, x::Real)
    if !insupport(d, x)
        return 0.0
    else
        return pdf(d.untruncated, x) / d.nc
    end
end

function logpdf(d::Truncated, x::Real)
    if !insupport(d, x)
        return -Inf
    else
        return logpdf(d.untruncated, x) - log(d.nc)
    end
end

function cdf(d::Truncated, x::Real)
    if x < d.lower
        return 0.0
    elseif x > d.upper
        return 1.0
    else
        return (cdf(d.untruncated, x) - cdf(d.untruncated, d.lower)) / d.nc
    end
end

function quantile(d::Truncated, p::Real)
    top = cdf(d.untruncated, d.upper)
    bottom = cdf(d.untruncated, d.lower)
    return quantile(d.untruncated, bottom + p * (top - bottom))
end

median(d::Truncated) = quantile(d, 0.5)

function rand(d::Truncated)
    while true
        r = rand(d.untruncated)
        if d.lower <= r <= d.upper
            return r
        end
    end
end

# from fallbacks
function rand{D<:ContinuousUnivariateDistribution}(d::Truncated{D}, dims::Dims)
    return rand!(d, Array(Float64, dims))
end

function rand{D<:DiscreteUnivariateDistribution}(d::Truncated{D}, dims::Dims)
    return rand!(d, Array(Int, dims))
end


@testset "$TEST $G" begin

g5 = DG(4)
add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4)
@test isapprox(pagerank(g5)[3], 0.318,  atol=0.001)
@test_throws ErrorException pagerank(g5, 2)
@test_throws ErrorException pagerank(g5, 0.85, 2)

end # testset


push!(LOAD_PATH, "Modules")
using LinearAlgebra
using Colors
using JuliRay

Base.@irrational ° 0.0174532925199432957692369076848861271344 (big(pi)/big(180))

function Plane2Sphere(p::Array{T,1}) where T<:Real
    if length(p)≠2
        error("No Point on ℝ²")
    end
    return [2p[1]/(1+p[1]^2+p[2]^2),2p[2]/(1+p[1]^2+p[2]^2),(-1+p[1]^2+p[2]^2)/(1+p[1]^2+p[2]^2)]
end

function Plane2Sphere(θ::Real,p::Array{T,1}) where T<:Real
    if length(p)≠2
        error("No Point on ℝ²")
    end
    c=[0,0,tan(θ)]
    r=1/cos(θ)
    P=p*cot(θ/2+π/4)
    return Plane2Sphere(P)*r+c
end

function POLYGON(vertices;r1=1.0,r2=0.05,r3=0.025)
    n=length(vertices)
    V=rgbColor(csgUnion((p->Sphere(p,r2)).(vertices)),RGB(0.1,0.1,0.1))
    E=rgbColor(csgUnion([Cylinder(vertices[i],vertices[mod(i,n)+1],r3) for i in 1:n]),RGB(0.1,0.1,0.1))
    F=rgbftColor(Polygon(vertices),RGB(0.5,0.5,1),FT(0.1,0.1))
    return csgUnion(V,E,F)
end

function Mirror(q,p1,p2,p3)
    e₃=JuliRay.NormalVector(p1,p2,p3)
    e₁=JuliRay.OrthogonalVector(e₃)
    e₂=cross(e₃,e₁)
    A=hcat(e₁,e₂,e₃)*transpose(hcat(e₁,e₂,-e₃))
    fixedpoint=(p1+p2+p3)/3
    b=fixedpoint-A*fixedpoint
    return A*q+b
end

function Put(F,i,O)
    p1=O
    n=length(F)
    p2,p3=F[mod(i-1,n)+1],F[mod(i,n)+1]
    return (p->Mirror(p,p1,p2,p3)).(F)
end

function f(x)
    ifelse(x>0,exp(-1/x),0)
end
function g(x)
    f(2x/√3)/(f(2x/√3)+f(2/√3-2x/√3))
end
function Smooth(a,b,x)
    g((x-a)/(b-a))
end

## Cube

h=1/√3 # 単位球面に内接させた際の内接球半径
n=4 # n角形
m=6 # m面体
M=80 # アニメーションの刻み数

r1=√(1-h^2) # n角形の外接円半径

for i ∈ 1:M
    t=3(i-1)/M
    θ=(1-Smooth(0.5,1.4,t)+Smooth(1.6,2.5,t))*π/2 # パラメータθは0 → π/2 → 0の順で滑らかに変化
    R=1/cos(θ)
    O=[0,0,tan(θ)]
    N=O+[0,0,R]
    H=O-[0,0,√(R^2-r1^2)]
    F=[[[r1*cos(2π*i/n),r1*sin(2π*i/n),0.0]+H for i in 1:n] for i ∈ 1:m] # 最初のn角形の頂点
    NET(F)=Scaling(Rotate(csgUnion([POLYGON(F[i],r2=0.02,r3=0.02) for i ∈ 1:m]),[0,0,1],45°),1.5/√3)

    F[2]=Put(F[1],1,O)
    F[3]=Put(F[1],2,O)
    F[4]=Put(F[1],3,O)
    F[5]=Put(F[1],4,O)
    F[6]=Put(F[4],1,O)
    object01=NET(F)

    F[2]=Put(F[1],1,O)
    F[3]=Put(F[1],3,O)
    F[4]=Put(F[2],2,O)
    F[5]=Put(F[2],4,O)
    F[6]=Put(F[3],1,O)
    object02=NET(F)

    F[2]=Put(F[1],1,O)
    F[3]=Put(F[1],3,O)
    F[4]=Put(F[1],2,O)
    F[5]=Put(F[2],4,O)
    F[6]=Put(F[3],1,O)
    object03=NET(F)

    F[2]=Put(F[1],4,O)
    F[3]=Put(F[1],3,O)
    F[4]=Put(F[3],2,O)
    F[5]=Put(F[2],1,O)
    F[6]=Put(F[4],1,O)
    object04=NET(F)

    F[2]=Put(F[1],1,O)
    F[3]=Put(F[1],3,O)
    F[4]=Put(F[3],2,O)
    F[5]=Put(F[1],4,O)
    F[6]=Put(F[3],1,O)
    object05=NET(F)

    F[2]=Put(F[1],1,O)
    F[3]=Put(F[1],3,O)
    F[4]=Put(F[3],1,O)
    F[5]=Put(F[2],2,O)
    F[6]=Put(F[4],4,O)
    object06=NET(F)

    F[2]=Put(F[1],4,O)
    F[3]=Put(F[1],2,O)
    F[4]=Put(F[1],3,O)
    F[5]=Put(F[2],1,O)
    F[6]=Put(F[4],1,O)
    object07=NET(F)

    F[2]=Put(F[1],1,O)
    F[3]=Put(F[1],3,O)
    F[4]=Put(F[3],1,O)
    F[5]=Put(F[2],4,O)
    F[6]=Put(F[3],2,O)
    object08=NET(F)

    F[2]=Put(F[1],4,O)
    F[3]=Put(F[1],3,O)
    F[4]=Put(F[3],1,O)
    F[5]=Put(F[2],1,O)
    F[6]=Put(F[3],2,O)
    object09=NET(F)

    F[2]=Put(F[1],4,O)
    F[3]=Put(F[1],3,O)
    F[4]=Put(F[3],1,O)
    F[5]=Put(F[2],1,O)
    F[6]=Put(F[4],2,O)
    object10=NET(F)

    F[2]=Put(F[1],1,O)
    F[3]=Put(F[2],3,O)
    F[4]=Put(F[1],2,O)
    F[5]=Put(F[4],3,O)
    F[6]=Put(F[5],1,O)
    object11=NET(F)

    object=csgUnion(
        ParallelTranslation(object01,[-8,4,0]),
        ParallelTranslation(object02,[-3,4,0]),
        ParallelTranslation(object03,[2,4,0]),
        ParallelTranslation(object04,[7,4,0]),
        ParallelTranslation(object05,[-8,0,0]),
        ParallelTranslation(object06,[-3,0,0]),
        ParallelTranslation(object07,[2,0,0]),
        ParallelTranslation(object08,[7,0,0]),
        ParallelTranslation(object09,[-8,-4,0]),
        ParallelTranslation(object10,[-3,-4,0]),
        ParallelTranslation(object11,[5,-3.5,0])
    )
    lng = -90°-15°*(Smooth(0.1,0.5,t)-Smooth(2.5,2.9,t))
    lat = 90°-50°*(Smooth(0.1,0.5,t)-Smooth(2.5,2.9,t))
    camera = LngLatCamera(lng=lng,lat=lat,pers=0.2,zoom=0.065,width=640*2,height=360*2)
    render(object, camera=camera, name="NetOfCubes", index=i)
end


function ueberpruefeMatrixVektora(A,m)

f = [2; 2; 2; 5; 1; 1; 6; 5; 0];

if norm(A*f-transpose(m)) <= 0.01
    # homogene Lösungen berechnen
    homogen = nullspace(A);

    # inhomogene Lösung berechnen
    inhomogen = pinv(A)*m';

    if size(homogen,2) > 0
        println("Euer Gleichungssystem stimmt zwar, allerdings betrachtet ihr noch nicht genug Strahlen.")
    else
        println("Ihr habt genug Strahlen verwendet. Die Lösung eures Gleichungssystems lautet:");
        print("f1 = ", round(inhomogen[1];digits = 2),"\n")
        print("f2 = ", round(inhomogen[2];digits = 2),"\n")
        print("f3 = ", round(inhomogen[3];digits = 2), "\n")
        print("f4 = ", round(inhomogen[4];digits = 2), "\n")
        print("f5 = ", round(inhomogen[5];digits = 2), "\n")
        print("f6 = ", round(inhomogen[6];digits = 2), "\n")
        print("f7 = ", round(inhomogen[7];digits = 2),"\n")
        print("f8 = ", round(inhomogen[8];digits = 2),"\n")
        print("f9 = ", round(inhomogen[9];digits = 2));
    end
else
    println("Schaut euch noch einmal den Verlauf der Strahlen an und überdenkt eure Matrix.")
end

end


include("cg_sem.jl")
using LinearAlgebra: eigen
import Random

function form_matrix(N, K, z0, z1)
    elem = element_operators(N)
    mesh = create_mesh(K, elem, z0, z1)

    problem = Isothermal{Float64}()

    # Matrices for upwinding the boundary conditions
    A0 = A_(problem, z0)
    (λ, V) = eigen(A0)
    A0⁺ = V * Diagonal(max.(0, λ)) / V
    A0⁻ = V * Diagonal(min.(0, λ)) / V

    A1 = -A_(problem, z1)
    (λ, V) = eigen(A1)
    A1⁺ = V * Diagonal(max.(0, λ)) / V
    A1⁻ = V * Diagonal(min.(0, λ)) / V

    bc = (
        (A = A0, A⁺ = A0⁺, A⁻ = A0⁻, δw = t -> 0),
        (A = A1, A⁺ = A1⁺, A⁻ = A1⁻, δw = t -> 0),
    )

    # Initial conditions
    q = (
        δρ = fill!(similar(mesh.zdg), 0),
        δw = fill!(similar(mesh.zdg), 0),
        δE = fill!(similar(mesh.zdg), 0),
    )

    # advance in time
    ∂q = (
        δρ = fill!(similar(mesh.zdg), 0),
        δw = fill!(similar(mesh.zdg), 0),
        δE = fill!(similar(mesh.zdg), 0),
    )
    rhs!(∂q, q, t) = tendency!(∂q, q, t, problem, mesh, elem, bc)

    Npcg = N * K + 1
    v = zeros(Npcg)
    A = zeros(3 * Npcg, 3 * Npcg)
    col = 0
    Nq = N + 1
    cgpts =  [((1:N) .+ Nq * (0:K-1)')[:]..., Nq * K]
    for f = 1:3
      q[f] .= 0
    end
    for f = 1:3
      for i = 1:Npcg
        col = col + 1
        v[i] = 1
        q[f] .= reshape(mesh.scatter * v, N+1, K)
        rhs!(∂q, q, 0)
        for k = 1:3
          A[(k-1) * Npcg .+ (1:Npcg), col] .= ∂q[k][cgpts]
          ∂q[k][cgpts] .= 0
        end
        q[f] .= 0
        v[i] = 0
      end
    end

    # Check the cgpts
    qrnd = rand(Npcg, 3)
    for k = 1:3
      q[k][:] .= mesh.scatter * qrnd[:, k]
      @assert all(q[k][cgpts] .== qrnd[:, k])
    end
    rhs!(∂q, q, 0)
    ∂q2 = reshape(A * qrnd[:], Npcg, 3)
    for k = 1:3
      @assert all(∂q[k] .≈ reshape(mesh.scatter * ∂q2[:, k], Nq, K))
    end

    return A
end

let
  N = 5
  K = 100
  A = A = form_matrix(N, K, 0.0, 30e3)
  ev = eigen(A);
  vs = findall(abs.(ev.values) .< 1e-14)
  @assert length(vs) == N * K + 1
  for v in vs
    @assert all(abs.(reshape(ev.vectors[:, v], N*K+1, 3))[:, 2] .< 1e-15)
  end
  @show extrema(real.(ev.values))
end


get_ν(β) = β/(1 - β)
get_β(ν) = ν/(1 + ν)
get_k(p, β) = (β - p*β + p)/(2β - p*β + p - 1)
get_d(setup::Setup) = convert(Integer, (size(setup.m, 1) - 1)/size(setup.m, 2))


using StaticArrays

# Karlgaard's formulation of the relative equations of motion
function sphericalkarlgaard(t, x::SVector, u::SVector, v::SVector, μ::Float64)

# Pull out parameters
ρ = x(1)
θ = x(3)
ϕ = x(5)
r = x(7)
f = x(9) # what is f?
ω = x(10)

dρ = x(2)
dθ = x(4)
dϕ = x(6)
dr = x(8)

# Define mu as gravparameter type?
Ga = [1 0 0; 0 1/ρ 0; 0 0 1/ρ]

# Common denominator
Ψ = (r^2 + ρ^2 + 2*r*ρ*cos(θ)*cos(ϕ))^(3/2)

# Spherical equations of motion
fρ = (ω^2 + 2*ω*dθ + dθ^2)*ρ*cos(ϕ)^2 + ρ*dϕ^2 - μ*(r*cos(θ)*cos(ϕ) + ρ)/Ψ + μ*cos(θ)*cos(ϕ)/r^2
fθ = 2*(ω + dθ)*dϕ*tan(ϕ) - dω - 2*(ω + dθ)*dρ/ρ + μ*(r*sin(θ)*sec(ϕ))/ρ*Ψ - μ*sin(θ)*sec(ϕ)/ρ*r^2
fϕ = -0.5*sin(2*ϕ)*(ω + dθ)^2 - 2*dρ*dϕ/ρ - μ*cos(θ)*sin(ϕ)/ρ*r^2 + μ*r*cos(θ)*sin(ϕ)/ρ*Ψ
fa = [fρ;fθ;fϕ]

# Need control noise term here

# Process noise term - Why did Jimmy multiply by gaussian random variables?
v = diagm(randn(3))*sqrt.(v)

# Full relative motion acceleration with controls, η = {ρ;θ;ϕ}
ddη = fa + Ga*(u*v)
ddρ = ddη(1)
ddθ = ddη(2)
ddϕ = ddη(3)
ddr = r*ω^2 - μ/r^2
dω = -2*dr*ω/r

# State deriviative (velocity and acceleration)
dx = [dρ;ddρ;dθ;ddθ;dϕ;ddϕ;dr;ddr;ω;dω]
return dx

end


using VortexCollisions

include("test_functions.jl")

function u_test_sinx4(x, y)
    sin(x)^4
end

function mfunc_one!(u, m)
    m .= 1
end

function hfunc_ϕ!(u, ϕ, h)
    h .= ϕ
end

function testNullspace(utest, mfunc, hfunc)
    M  = 64
    N  = 64

    grid = Grid2d(M,N)
    u    = get_field(grid)
    divJ = get_field(grid)
    ft   = FourierTransform(grid)
    op   = FokkerPlanckOperator(grid, ft; mfunc=mfunc, hfunc=hfunc)

    evaluate_function_on_grid(grid, utest, u)

    collision_operator!(op, u, divJ)

    @test maximum(abs.(divJ)) == 0.0

end

testNullspace(u_test_sinx4, mfunc_one!, hfunc_ϕ!)


module CoarseGraining3DPrisms
using LinearAlgebra

greet() = print("Hello World!")

export numchop, delta, qn, qnfact, visqrt, trian, RacWig6j, Fsymb, Gsymb, Gsymb2

numchop(num::Real)=(abs(num) >= 1000*eps() ? num : zero(Real))

function numchop(num::Complex)
    numchop(imag(num)) == 0 ? numchop(real(num)) : Complex(numchop(real(num)), numchop(imag(num)))
end

const K = 5
const x = K+1
const y = K/2

#Define delta_{ijkl} -> coupling rules
function delta(i::Float64,j::Float64,k::Float64)
    sol = 0
    if i <=(j+k) && j<=(i+k) && k<=(i+j) && i+j+k <= K && 2*(i+j+k)%2 ==0
        sol = 1
    end
    return sol
end

#Define quantum numbers qn (this is real)
function qn(n::Float64)
    sol = (exp(pi*n*im/(K+2)) - exp(-pi*n*im/(K+2))) / (exp(pi*im/(K+2)) - exp(-pi*im/(K+2)))
    return real(sol)
end

#Define qn factorial
function qnfact(n::Float64)
    sol = 1
    for i in 1:n
        sol *= qn(i)
    end
    return sol
end

#Define square root of quantum dimension
function visqrt(i::Float64)
    sol = ((-1+0im)^i )*sqrt(qn(2*i+1))
    return sol
end

#Define triangle equality
function trian(i::Float64,j::Float64,k::Float64)
    sol = 0
    if delta(i,j,k) == 1
        sol = delta(i,j,k)*sqrt(qnfact(i+j-k)*qnfact(i-j+k)*qnfact(-i+j+k)/qnfact(i+j+k+1))
    end
    return sol
end

# Define Racah-Wigner six-j symbol
function RacWig6j(i::Float64,j::Float64,m::Float64,k::Float64,l::Float64,n::Float64)
    a = i+j+m; b = i+l+n; c = k+j+n; d = k+l+m;  e = i+j+k+l; f = i+k+m+n; g = j+l+m+n
    sol = 0
    if delta(i,j,m) != 0 && delta(i,l,n) != 0 && delta(k,j,n) != 0 && delta(k,l,m) != 0
        sumz = 0
        for z in max(a,b,c,d):min(e,f,g)
            sumz += (-1)^z *qnfact(z+1)/
                ((qnfact(e-z)*qnfact(f-z)*qnfact(g-z))* (qnfact(z-a)*qnfact(z-b)*qnfact(z-c)*qnfact(z-d)))
        end
        sol = trian(i,j,m)*trian(i,l,n)*trian(k,j,n)*trian(k,l,m)*sumz
    end
    return sol
end

#Define F-symbol
function Fsymb(i::Float64,j::Float64,m::Float64,k::Float64,l::Float64,n::Float64)
    sol = 0
    if delta(i,j,m) != 0 && delta(i,l,n) != 0 && delta(k,j,n) != 0 && delta(k,l,m) != 0
        sol = (-1+0im)^(i+j+k+l)*sqrt(qn(2*m+1)*qn(2*n+1)) * RacWig6j(i,j,m,k,l,n)
    end
    return sol
end

#Define G-symbol
function Gsymb(i::Float64,j::Float64,m::Float64,k::Float64,l::Float64,n::Float64)
    sol = 0
    if delta(i,j,m) != 0 && delta(i,l,n) != 0 && delta(k,j,n) != 0 && delta(k,l,m) != 0
        sol = Fsymb(i,j,m,k,l,n) /(visqrt(m)*visqrt(n))
    end
    return sol
end

#Define G-symbol 2
function Gsymb2(i::Float64,j::Float64,m::Float64,k::Float64,l::Float64,n::Float64)
    sol = 0
    if delta(i,j,m) != 0 && delta(i,l,n) != 0 && delta(k,j,n) != 0 && delta(k,l,m) != 0
        sol = (-1+0im)^(i+j+k+l+m+n)*RacWig6j(i,j,m,k,l,n)
    end
    return sol
end

export prismA, prismB, prismB2, blocksPrA, blocksPrB, blockReduceA, blockReduceA1, blockReduceA2, svdA, svdB

#d1,d2,e1 is triangle we shall cut along
function prismA(ja1::Float64,jb1::Float64,jd1::Float64,je1::Float64,jf1::Float64,ja2::Float64,jb2::Float64,
		                 jd2::Float64,je2::Float64,jf2::Float64,jc::Float64,jg::Float64)
    sol = 0
    if delta(jd1,jd2,je1) != 0 && delta(jd1,jb1,jg) != 0 && delta(ja1,jd2,jg) != 0 && delta(ja1,jb1,je1) != 0 && delta(jd1,jc,jb2) != 0 && delta(jf1,jd2,jb2) != 0 && delta(jf1,jc,je1) != 0 && delta(jd1,ja2,jf2) != 0 && delta(jc,je2,jf2) != 0 && delta(jb2,je2,ja2) != 0
        dims = visqrt(ja1)*visqrt(jb1)*visqrt(jg)*visqrt(jf1)*visqrt(jc)*visqrt(jb2)*visqrt(je2)*visqrt(ja2)*visqrt(jf2)*visqrt(jd1)*visqrt(jd2)*visqrt(je1)
        sol =  dims*Gsymb(jd1,jd2,je1,ja1,jb1,jg) * Gsymb(jd1,jd2,je1,jf1,jc,jb2) * Gsymb(jd1,jc,jb2,je2,ja2,jf2)
    end
    return sol
end

function prismB(ja1::Float64,jb1::Float64,jd1p::Float64,je1::Float64,jf1::Float64,ja2::Float64,jb2::Float64,
		                 jd2p::Float64,je2::Float64,jf2::Float64,jc::Float64,jg::Float64)
    sol = 0
    for jd1 in 0.:0.5:y, jd2 in 0.:0.5:y
        sol += numchop(Fsymb(ja1,jg,jd2,jb2,jf1,jd2p)*Fsymb(jb1,jg,jd1,ja2,jf2,jd1p)*prismA(ja1,jb1,jd1,je1,jf1,ja2,jb2,jd2,je2,jf2,jc,jg))
    end
    return sol
end

function prismB2(ja1::Float64,jb1::Float64,jd1::Float64,je1::Float64,jf1::Float64,ja2::Float64,jb2::Float64,
		                 jd2::Float64,je2::Float64,jf2::Float64,jc::Float64,jg::Float64)
    return prismA(ja2,jb2,jd2,je2,jf2,ja1,jb1,jd1,je1,jf1,jc,jg)
end

#APrism(j5,j4,j7,j2,j1,jb,j3,j8,ja,j9,j6,jc)

function blocksPrA(Prsm::Array{Float64,12},jd1::Float64,jd2::Float64,je1::Float64)
    mat = zeros(x^3,x^6)
    row = 1
    col = 1
    for ja1 in 0.:0.5:y, jb1 in 0.:0.5:y, jg in 0.:0.5:y
        for jf1 in 0.:0.5:y, ja2 in 0.:0.5:y, jb2 in 0.:0.5:y, je2 in 0.:0.5:y, jf2 in 0.:0.5:y, jc in 0.:0.5:y
            mat[row,col] =  Prsm[Int(2*ja1+1),Int(2*jb1+1),Int(2*jd1+1),Int(2*je1+1),Int(2*jf1+1),Int(2*ja2+1),Int(2*jb2+1),Int(2*jd2+1),Int(2*je2+1),Int(2*jf2+1),Int(2*jc+1),Int(2*jg+1)]
            col += 1
        end
        col = 1
        row += 1
    end
    return mat
end

function blocksPrB(Prsm::Array{Float64,12},jd2::Float64,jd1::Float64,je2::Float64)
    mat = zeros(x^3,x^6)
    row = 1
    col = 1
    for ja2 in 0.:0.5:y, jb2 in 0.:0.5:y, jg in 0.:0.5:y
        for jf2 in 0.:0.5:y, ja1 in 0.:0.5:y, jb1 in 0.:0.5:y, je1 in 0.:0.5:y, jf1 in 0.:0.5:y, jc in 0.:0.5:y
            mat[row,col] =  Prsm[Int(2*ja1+1),Int(2*jb1+1),Int(2*jd1+1),Int(2*je1+1),Int(2*jf1+1),Int(2*ja2+1),Int(2*jb2+1),Int(2*jd2+1),Int(2*je2+1),Int(2*jf2+1),Int(2*jc+1),Int(2*jg+1)]
            col += 1
        end
        col = 1
        row += 1
    end
    return mat
end

function blockReduceA(ampJ::Array{Any,1},jd1::Float64,jd2::Float64,je1::Float64)
    #a = Float64[]
    #b = Array{Int64,1}[]
    #ampInfo = [] # will contain indices and amplitudes
    # position of d1 = 3, d2= 8 and e1 = 4
    ampInfo = ampJ[findall(x-> x[1][3] == jd1 && x[1][8] == jd2 && x[1][4] == je1 , ampJ)]
    nzcol = Array{Float64,1}[]
    #for ja1 in 0.:0.5:y, jb1 in 0.:0.5:y, jg in 0.:0.5:y
    nzrow = Array{Float64,1}[]
    #hh = unique!([ [ampInfo[i][1][1],ampInfo[i][1][2],ampInfo[i][1][12]] for 1:length(ampInfo) ])
    for i in 1:length(ampInfo) # position of a1 = 1, b1 = 2 g = 12
        push!(nzcol,[ampInfo[i][1][1],ampInfo[i][1][2],ampInfo[i][1][12]])
        push!(nzrow,[ampInfo[i][1][5],ampInfo[i][1][6],ampInfo[i][1][7],ampInfo[i][1][9],ampInfo[i][1][10],ampInfo[i][1][11]])
    end
    unique!(nzcol)
    unique!(nzrow)
    dd = zeros(length(nzcol),length(nzrow))
    if length(dd) != 0
        for i in 1:length(ampInfo)
            qa = findall(x-> x == [ampInfo[i][1][1], ampInfo[i][1][2], ampInfo[i][1][12]],nzcol)
            qb = findall(x-> x == [ampInfo[i][1][5], ampInfo[i][1][6], ampInfo[i][1][7], ampInfo[i][1][9], ampInfo[i][1][10], ampInfo[i][1][11] ], nzrow)
            dd[qa,qb] = [ampInfo[i][2]]
        end
        return nzcol,nzrow, dd
    else
        return 0,0,0
    end
end

function svdA(prA::Array{Float64,12},ja::Float64,jb::Float64,jc::Float64)# parameters d1,d2,e1
    mat = blocksPrA(prA,ja,jb,jc)
    U, s, V = svd(mat)
    #V = ctranspose(V)
    return (U,s,V)
end

function svdB(prB::Array{Float64,12},ja::Float64,jb::Float64,jc::Float64)# parameters d1,d2,e2
    mat = blocksPrB(prB,ja,jb,jc)
    U, s, V = svd(mat)
    return (U,s,V)
end

export UtensorA, UtensorB, VtensorA, VtensorB

function UtensorA(prA::Array{Float64,12},ja::Float64,jb::Float64,jc::Float64)# parameters d1,d2,e1
    UT = zeros(x,x,x)
    U = svdA(prA,ja,jb,jc)[1]
    s = svdA(prA,ja,jb,jc)[2]
    index = 1
    for ja1 in 0.:0.5:y, jb1 in 0.:0.5:y, jg in 0.:0.5:y
        UT[Int(2*ja1+1),Int(2*jb1+1),Int(2*jg+1)] = numchop(U[index,1]*sqrt(s[1]))
        index += 1
    end
    return UT
end

function UtensorB(prB::Array{Float64,12},ja::Float64,jb::Float64,jc::Float64)# parameters d1,d2,e1
    UT = zeros(x,x,x)
    U = svdB(prB,ja,jb,jc)[1]
    s = svdB(prB,ja,jb,jc)[2]
    index = 1
    for ja2 in 0.:0.5:y, jb2 in 0.:0.5:y, jg in 0.:0.5:y
        UT[Int(2*ja2+1),Int(2*jb2+1),Int(2*jg+1)] = numchop(U[index,1]*sqrt(s[1]))
        index += 1
    end
    return UT
end

#VVtensors
function VtensorA(prA::Array{Float64,12},ja::Float64,jb::Float64,jc::Float64)# parameters d1,d2,e1
    VT = zeros(x,x,x,x,x,x)
    V = svdA(prA,ja,jb,jc)[3]
    s = svdA(prA,ja,jb,jc)[2]
    index = 1
    for jf1 in 0.:0.5:y, ja2 in 0.:0.5:y, jb2 in 0.:0.5:y, je2 in 0.:0.5:y, jf2 in 0.:0.5:y, jc in 0.:0.5:y
        VT[Int(2*jf1+1),Int(2*ja2+1),Int(2*jb2+1),Int(2*je2+1),Int(2*jf2+1),Int(2*jc+1)] = numchop(V[index,1]*sqrt(s[1]))
        index += 1
    end
    return VT
end

function VtensorB(prB::Array{Float64,12},ja::Float64,jb::Float64,jc::Float64)# parameters d1,d2,e1
    VT = zeros(x,x,x,x,x,x)
    V = svdB(prB,ja,jb,jc)[3]
    s = svdB(prB,ja,jb,jc)[2]
    index = 1
    for jf2 in 0.:0.5:y, ja1 in 0.:0.5:y, jb1 in 0.:0.5:y, je1 in 0.:0.5:y, jf1 in 0.:0.5:y, jc in 0.:0.5:y
        VT[Int(2*jf2+1),Int(2*ja1+1),Int(2*jb1+1),Int(2*je1+1),Int(2*jf1+1),Int(2*jc+1)] = numchop(V[index,1]*sqrt(s[1]))
        index += 1
    end
    return VT
end

export  fullUtensorA,  fullUtensorB, fullVtensorA, fullVtensorA

function fullUtensorA(prA::Array{Float64,12})
    fullUT = zeros(x,x,x,x,x,x)
    for jd1 in 0.:0.5:y, jd2 in 0.:0.5:y, je1 in 0.:0.5:y
        fullUT[Int(2*jd1+1),Int(2*jd2+1),Int(2*je1+1),:,:,:] = UtensorA(prA,jd1,jd2,je1)
    end
    return fullUT
end

function fullVtensorA(prA::Array{Float64,12})
    fullVT = zeros(x,x,x,x,x,x,x,x,x)
    for jd1 in 0.:0.5:y, jd2 in 0.:0.5:y, je1 in 0.:0.5:y
        fullVT[Int(2*jd1+1),Int(2*jd2+1),Int(2*je1+1),:,:,:,:,:,:] = VtensorA(prA,jd1,jd2,je1)
    end
    return fullVT
end

function fullUtensorB(prB::Array{Float64,12})
    fullUT = zeros(x,x,x,x,x,x)
    for jd1 in 0.:0.5:y, jd2 in 0.:0.5:y, je2 in 0.:0.5:y
        fullUT[Int(2*jd1+1),Int(2*jd2+1),Int(2*je2+1),:,:,:] = UtensorB(prB,jd1,jd2,je2)
    end
    return fullUT
end

function fullVtensorB(prB::Array{Float64,12})
    fullVT = zeros(x,x,x,x,x,x,x,x,x)
    for jd1 in 0.:0.5:y, jd2 in 0.:0.5:y, je2 in 0.:0.5:y
        fullVT[Int(2*jd1+1),Int(2*jd2+1),Int(2*je2+1),:,:,:,:,:,:] = VtensorB(prB,jd1,jd2,je2)
    end
    return fullVT
end

export prsmUAVB, prsmUBVA, prsmEff

function prsmUAVB(prA::Array{Float64,12},prB::Array{Float64,12})
    UA = fullUtensorA(prA)
    VB = fullVtensorB(prB)
    prsm = zeros(x,x,x,x,x,x,x,x,x,x,x,x)
    for jb1 in 0.:0.5:y, jc1 in 0.:0.5:y, jd1 in 0.:0.5:y, jh1 in 0.:0.5:y, je11 in 0.:0.5:y, jf12 in 0.:0.5:y, ja2 in 0.:0.5:y, jf2 in 0.:0.5:y, jh2 in 0.:0.5:y, je21 in 0.:0.5:y, jg in 0.:0.5:y, jn in 0.:0.5:y
        ans = UA[Int(2*jb1+1),Int(2*jd1+1),Int(2*jg+1),Int(2*jn+1),Int(2*jh1+1),Int(2*je11+1)] *
                VB[Int(2*jd1+1),Int(2*ja2+1),Int(2*jf2+1),Int(2*je21+1),Int(2*jn+1),Int(2*jh2+1),Int(2*jf12+1),Int(2*je11+1),Int(2*jc1+1)]
        prsm[Int(2*jb1+1),Int(2*jc1+1),Int(2*jd1+1),Int(2*jh1+1),Int(2*je11+1),Int(2*jf12+1),Int(2*ja2+1),Int(2*jf2+1),Int(2*jh2+1),Int(2*je21+1),Int(2*jg+1),Int(2*jn+1)] = ans
    end
    return prsm
end

function prsmUBVA(prA::Array{Float64,12},prB::Array{Float64,12})
    UB = fullUtensorB(prB)
    VA = fullVtensorA(prA)
    prsm = Array{Float64}(x,x,x,x,x,x,x,x,x,x,x,x)
    for jb1 in 0.:0.5:y, jc1 in 0.:0.5:y, jd1 in 0.:0.5:y, jh1 in 0.:0.5:y, je11 in 0.:0.5:y, jf12 in 0.:0.5:y, ja2 in 0.:0.5:y, jf2 in 0.:0.5:y, jh2 in 0.:0.5:y, je21 in 0.:0.5:y, jg in 0.:0.5:y, jn in 0.:0.5:y
        ans = UB[Int(2*jb1+1),Int(2*jd1+1),Int(2*jg+1),Int(2*jn+1),Int(2*jh1+1),Int(2*je11+1)] *
                VA[Int(2*jd1+1),Int(2*ja2+1),Int(2*jf2+1),Int(2*je21+1),Int(2*jn+1),Int(2*jh2+1),Int(2*jf12+1),Int(2*je11+1),Int(2*jc1+1)]
        prsm[Int(2*jb1+1),Int(2*jc1+1),Int(2*jd1+1),Int(2*jh1+1),Int(2*je11+1),Int(2*jf12+1),Int(2*ja2+1),Int(2*jf2+1),Int(2*jh2+1),Int(2*je21+1),Int(2*jg+1),Int(2*jn+1)] = ans
    end
    return prsm
end

function prsmEff(prA::Array{Float64,12},prB::Array{Float64,12})
    pr1 = prsmUAVB(prA,prB)
    pr2 = prsmUBVA(prA,prB)
    prsm = zeros(x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x)
    for ja1 in 0.:0.5:y, jb1 in 0.:0.5:y, jc1 in 0.:0.5:y, jd1 in 0.:0.5:y, je11 in 0.:0.5:y, je12 in 0.:0.5:y,jf1 in 0.:0.5:y, jh1 in 0.:0.5:y, ja2 in 0.:0.5:y, jb2 in 0.:0.5:y, jc2 in 0.:0.5:y, jd2 in 0.:0.5:y, je21 in 0.:0.5:y,je22 in 0.:0.5:y, jf2 in 0.:0.5:y, jh2 in 0.:0.5:y, jf12 in 0.:0.5:y, jg in 0.:0.5:y
        sum = 0
        for jn in 0.:0.5:y
            ans1 = pr1[Int(2*jb1+1),Int(2*jc1+1),Int(2*jd1+1),Int(2*jh1+1),Int(2*je11+1),Int(2*jf12+1),Int(2*ja2+1),Int(2*jf2+1),Int(2*jh2+1),Int(2*je21+1),Int(2*jg+1),Int(2*jn+1)]
            ans2 = pr2[Int(2*jb2+1),Int(2*jc2+1),Int(2*jd2+1),Int(2*jh2+1),Int(2*je22+1),Int(2*jf12+1),Int(2*ja1+1),Int(2*jf1+1),Int(2*jh1+1),Int(2*je12+1),Int(2*jg+1),Int(2*jn+1)]
            sum+= (ans1*ans2)
        end
        prsm[Int(2*jb1+1),Int(2*jc1+1),Int(2*jd1+1),Int(2*je11+1),Int(2*ja2+1),Int(2*jf2+1),Int(2*je21+1),Int(2*jb2+1),Int(2*jc2+1),Int(2*jd2+1),Int(2*je12+1),Int(2*ja1+1),Int(2*jf1+1),Int(2*je22+1),Int(2*jh1+1),Int(2*jf12+1),Int(2*jh2+1),Int(2*jg+1)] = sum
    end
    return prsm
end

end # module