module TensorBalancing
export nBalancing, qnBalancing

"""
    calcResidual(P::Array{T,2})

Calculate the residual for balancing.
"""
calcResidual(P, scale=1.0) = norm(vcat((scale * sum(P, 1)' .- 1), (scale * sum(P, 2) .- 1)))

"""
    genβ{T<:AbstractArray}(P::Array{T,2})

Collect indices for β.
"""
function genβ{T<:AbstractFloat}(P::Array{T,2})
    ylen, xlen = size(P)
    return vcat([(1,1)], [(1,i) for i = 2:xlen], [(i,1) for i = 2:ylen])
end

"""
    calcη!{T<:AbstractArray}(η::Array{T,2}, P::Array{T,2})

Update η coordinate of a matrix.
"""
function calcη!{T<:AbstractFloat}(η::Array{T,2}, P::Array{T,2})
    shape = size(P)
    idx = [shape...]
    η[idx...] = P[idx...]
    for i in shape[1]-1:-1:1
        η[i,shape[2]] = η[i+1, shape[2]] + P[i, shape[2]]
    end
    for j in shape[2]-1:-1:1
        η[shape[1], j] = η[shape[1], j+1] + P[shape[1], j]
    end
    for i in shape[1]-1:-1:1
        for j in shape[2]-1:-1:1
            η[i,j] = η[i+1, j] + η[i, j+1] - η[i+1, j+1] + P[i,j] # de morgan
        end
    end
    η ./= η[1,1]
end

"""
    calcMarginals!{T<:AbstractArray}(η::Array{T,1}, A::Array{T,2})

Update η, a list containing the limb part of cumulative sums of a matrix.
"""
function calcMarginals!{T<:AbstractFloat}(η::Array{T,1}, A::Array{T,2})
    ylen, xlen = size(A)
    cumcol = zeros(ylen)
    for i = xlen-1:-1:1
        cumcol += A[:,i+1]
        η[i+1] = sum(cumcol)
    end
    cumcol += A[:,1]
    for i = length(cumcol)-1:-1:1
      cumcol[i] += cumcol[i+1]
    end
    η[xlen+1:end] = cumcol[2:end]
    η[1] = cumcol[1]
end

"""
    genTargetη(P::Array{T}, β)

Generate target value of elements in η.
"""
function genTargetη{T<:AbstractFloat}(P::AbstractArray{T}, β)
    shape = size(P)
    dim = length(β[1])
    return [prod(map(x->x[1]/x[2], zip([shape...]-[idx...]+ones(Int32, dim), shape))) for idx in β]
end

"""
    cumθ{I<:Integer, T<:AbstractFloat}(θ::Array{T,1}, β::Array{Tuple{I,I},1}, shape)

Calculate cumulative sum of θ for each row and column of a matrix.
"""
function cumθ{I<:Integer, T<:AbstractFloat}(θ::Array{T,1}, β::Array{Tuple{I,I},1}, shape)
    ylen, xlen = shape
    cumθ_x = zeros(xlen)
    cumθ_y = zeros(ylen)
    cumθ!(cumθ_x, cumθ_y, θ, β, shape)
    return cumθ_x, cumθ_y
end

"""
    cumθ!{I<:Integer, T<:AbstractFloat}(cumθ_x, cumθ_y, θ::Array{T,1}, β::Array{Tuple{I,I},1}, shape)

Calculate cumulative sum of θ for each row and column of a matrix.
"""
function cumθ!{I<:Integer, T<:AbstractFloat}(cumθ_x::Array{T,1}, cumθ_y::Array{T,1}, θ::Array{T,1}, β::Array{Tuple{I,I},1}, shape)
    ylen, xlen = shape
    for i in 1:length(β)
        if β[i][1] == 1
            cumθ_x[β[i][2]] = θ[i]
        else
            cumθ_y[β[i][1]] = θ[i]
        end
    end
    for i = 2:xlen
        cumθ_x[i] += cumθ_x[i-1]
    end
    for i = 2:ylen
        cumθ_y[i] += cumθ_y[i-1]
    end
end

function genSubβ{T<:AbstractFloat}(β, η::Array{T,2}; ϵ=1e-6)
    subβ_ = sort(β, 1; by=pos->η[pos...], rev=true)
    #subβ = [subβ_[1]]
    subβ = [1]
    for i = 2:length(subβ_)
        if abs(η[subβ[end]...] - η[subβ_[i]...]) > ϵ
            #append!(subβ, [subβ_[i]])
            push!(subβ, i)
        else
            common = tuple(map(x->max(x...), zip(subβ[end], subβ_[i]))...)
            if abs(η[subβ[end]...] - η[common...]) > ϵ
                #append!(subβ, [subβ_[i]])
                push!(subβ, i)
            end
        end
    end
    return subβ
end

"""
    nBalancing{T<:AbstractFloat}(P::Array{T,2}; ϵ=1e-9, max_iter=100, show_trace=false, show_every=100)

Matrix balancing by E-projection with Newton's method.

# Parameters
* P - Input matrix
* ϵ - Threshold for the error of η-coordinate
* max_iter - Max number of iterations for optimization
* show_trace - Whether to print trace
* show_every - Trace information is printed every `show_every`'th iteration
"""
function nBalancing{T<:AbstractFloat}(P::Array{T,2}; ϵ=1e-9, max_iter=100, show_trace=false, show_every=100)
    A = copy(P)
    ylen, xlen = size(A)
    β = genβ(A)
    η_target = genTargetη(A, β)
    lengthβ = length(β)

    # allocate memory
    η = zeros(size(A))
    θ, λ = zeros(lengthβ), 1.0 # θ and Lagrange multiplier
    δθλ = zeros(lengthβ+1)
    grad = zeros(lengthβ+1)
    jacobian = zeros(lengthβ+1, lengthβ+1)
    cumθ_y, cumθ_x = zeros(ylen), zeros(xlen)

    if show_trace
        @printf "Iter     Function value    Gradient norm\n"
    end

    # e-projection loop
    for count = 1:max_iter
        sm = sum(A)
        calcη!(η, A)
        for i = 1:lengthβ
            grad[i] .= (1 + λ * sm) * η[β[i]...] - η_target[i]
        end
        grad[end] = sm - 1

        res = calcResidual(A,xlen)
        if res < ϵ
            gnorm = sqrt(sum(grad[1:end-1].^2))
            show_trace && @printf "%6d   %14e    %14e\n" count-1 log(sm)-η_target'θ gnorm
            break
        end
        if show_trace && (count - 1) % show_every == 0
            gnorm = sqrt(sum(grad[1:end-1].^2))
            @printf "%6d   %14e    %14e\n" count-1 log(sm)-η_target'θ gnorm
        end

        # update jacobian
        for i in 1:lengthβ
            for j in i:lengthβ
                common = tuple(map(x->max(x...), zip(β[i], β[j]))...)
                jacobian[i,j] = (1 + λ * sm) * η[common...] - η[β[i]...] * η[β[j]...]
                jacobian[j,i] = jacobian[i,j]
            end
            jacobian[i,end] = jacobian[end,i] = sm * η[β[i]...]
        end

        # update parameters
        try
            δθλ .= jacobian \ grad # solve a linear equation
        catch e
            warn("Jacobian matrix got singular. Projection loop is terminated.")
            break
        end
        θ .-= δθλ[1:end-1]
        λ -= δθλ[end]
        # make cumulative sum of θ for fast update
        cumθ!(cumθ_x, cumθ_y, θ, β, size(A))
        # update matrix
        for j = 1:xlen, i = 1:ylen
            if P[i,j] > 1e-15
                A[i,j] = P[i,j] * exp(cumθ_y[i] + cumθ_x[j])
            end
        end
    end

    return A
end

include("QN.jl")

end # module


using StochasticDiffEq, DiffEqDevTools, DiffEqBase, Test
using DiffEqProblemLibrary.SDEProblemLibrary: importsdeproblems; importsdeproblems()
import DiffEqProblemLibrary.SDEProblemLibrary: prob_sde_linear
Random.seed!(100)
prob = prob_sde_linear

integrator = init(prob,EM(),dt=1//2^(4),tstops = [0.33])

for (u,t) in tuples(integrator)
  @show u,t
end

sol = solve(prob,EM(),dt=1//2^(4),tstops = [0.33])

@test 0.33 ∈ sol.t

sol = solve(prob,EM(),tstops = [0.33,0.80,1.0])

@test sol.t == [0.0,0.33,0.80,1.0]

sol = solve(prob,SRIW1(),tstops = [0.33])

@test 0.33 ∈ sol.t


"""
See also: [`ContinuousImpulse`](@ref), [`frequency_to_time`](@ref), [`DiscreteGaussianImpulse`](@ref)

    DiscreteImpulse{T<:AbstractFloat}

A struct used to represent a numerical impulse. Only the fields: `in_freq` which is the frequency response vector, and the frequency vector `ω` are required to use this struct to use in the function `frequency_to_time`.
"""
struct DiscreteImpulse{T<:AbstractFloat}
    t::Vector{T}
    in_time::Vector{Complex{T}}
    ω::Vector{T}
    in_freq::Vector{Complex{T}}
    function DiscreteImpulse{T}(t_vec::AbstractArray{T}, in_time::AbstractVector{Complex{T}}, ω_vec::AbstractArray{T}, in_freq::AbstractVector{Complex{T}}, do_self_test=true) where {T}
        impulse = new{T}(t_vec,collect(in_time),ω_vec,collect(in_freq))
        if do_self_test self_test(impulse) end
        return impulse
    end
end

"""
Check that the discrete impulse vectors are the right sizes
"""
function self_test(impulse::DiscreteImpulse{T}) where {T}

    right_size = length(impulse.t) == length(impulse.in_time)
    right_size = right_size && length(impulse.ω) == length(impulse.in_freq)

    return right_size
end

function DiscreteImpulse(t_vec::AbstractArray{T}, in_time::Union{AbstractArray{Complex{T}},AbstractArray{T}},
        ω_vec::AbstractArray{T} = t_to_ω(t_vec),
        in_freq::Union{AbstractArray{Complex{T}},AbstractArray{T}} = Complex{T}[];
    kws... ) where {T}

    if isempty(in_freq)
        in_freq = collect(time_to_frequency(Vector{T}(in_time), Vector{T}(t_vec), ω_vec; kws...))[:]
    end

    return DiscreteImpulse{T}(
        Vector{T}(t_vec), Vector{Complex{T}}(in_time), Vector{T}(ω_vec), Vector{Complex{T}}(in_freq)
    )
end

"""
See also: [`DiscreteImpulse`](@ref), [`frequency_to_time`](@ref)

    ContinuousImpulse{T<:AbstractFloat}

A struct used to represent an analytic impulse function. Has two fields: `in_time` a function of time `t`, and `in_freq` a function of the angular frequency `ω`. `in_freq` should be the Fourier transform of `in_time`, though this is not enforced.

We use the Fourier transform convention:
F(ω) =  ∫ f(t)*exp(im*ω*t) dt,
f(t) = (2π)^(-1) * ∫ F(ω)*exp(-im*ω*t) dω.

An impluse f(t) is convoluted in time with the field u(t), however we avoid the convolution by working with the fourier transform F(ω) of the impulse f(t), which results in

frequency to time: (2π)^(-1) * ∫ F(ω)*U(ω)*exp(-im*ω*t) dω
"""
struct ContinuousImpulse{T<:AbstractFloat}
    in_time::Function
    in_freq::Function
    # Enforce that the Types are the same
    function ContinuousImpulse{T}(in_time::Function, in_freq::Function, do_self_test=true) where {T}
        impulse = new{T}(in_time,in_freq)
        if do_self_test self_test(impulse) end
        return impulse
    end
end

"""
    continuous_to_discrete_impulse(impulse::ContinuousImpulse, t_vec, ω_vec = t_to_ω(t_vec); t_shift = 0.0, ω_shift = 0.0)

Returns a [`DiscreteImpulse`](@ref) by sampling `impulse`. The signal can be shifted in time and frequency by choosing `t_shit` and `ω_shift`.
"""
function continuous_to_discrete_impulse(impulse::ContinuousImpulse{T}, t_vec::AbstractArray{T}, ω_vec::AbstractArray{T} = t_to_ω(t_vec); t_shift::T = zero(T), ω_shift::T = zero(T)) where {T}

    # shift in frequency then in time
    in_time = impulse.in_time.(t_vec .- t_shift) .* cos.(ω_shift .* (t_vec .- t_shift))

    # shift in frequency then in time
    in_freq = (impulse.in_freq.(ω_vec .- ω_shift) .+ impulse.in_freq.(ω_vec .+ ω_shift)) .* exp.(im .* ω_vec .* t_shift) ./ T(2)

    return DiscreteImpulse(t_vec, in_time, ω_vec, in_freq)
end

"""
Check that the continuous impulse functions return the correct types
"""
function self_test(impulse::ContinuousImpulse{T}) where {T}

    t = one(T)
    impulse.in_time(t)::Union{T,Complex{T}}

    ω = one(T)
    impulse.in_freq(ω)::Union{T,Complex{T}}

    return true
end

import Base.(+)
function +(i1::ContinuousImpulse{T},i2::ContinuousImpulse{T})::ContinuousImpulse{T} where {T}
    in_time(t) = i1.in_time(t) + i2.in_time(t)
    in_freq(ω) = i1.in_freq(ω) + i2.in_freq(ω)

    ContinuousImpulse{T}(in_time,in_freq)
end

import Base.(*)
function *(a,impulse::ContinuousImpulse{T})::ContinuousImpulse{T} where {T}

    if typeof(one(Complex{T})*a) != Complex{T}
        throw(DomainError(a, "Multiplying impulse by $a would cause impulse type to change, please explicitly cast $a to same type as RegularSource ($T)"))
    end

    in_time(t) = a*impulse.in_time(t)
    in_freq(ω) = a*impulse.in_freq(ω)

    ContinuousImpulse{T}(in_time,in_freq)
end

*(i::ContinuousImpulse,a) = *(a,i)

"""
    TimeDiracImpulse(t0::T)

Dirac Delta function of unit area in the time domain centred at t=t0.

Warning: in the time domain this is a singuarity and so may lead to unexpected behaviour.
"""
function TimeDiracImpulse(t0::T) where {T<:AbstractFloat}
    in_time(t::T) = (t==t0) ? T(Inf) : zero(T)
    in_freq(ω::T) = exp(im*ω*t0)
    ContinuousImpulse{T}(in_time, in_freq, false)
end

function DiscreteTimeDiracImpulse(t0::T, t_vec::AbstractArray{T}, ω_vec::AbstractArray{T} = t_to_ω(t_vec)) where {T}

    return continuous_to_discrete_impulse(TimeDiracImpulse(t0), t_vec, ω_vec)
end

"""
Dirac Delta function of unit area in the frequency domain centred at ω=ω0.

Warning: in frequency space this is a singuarity and so may lead to unexpected behaviour.
"""
function FreqDiracImpulse(ω0::T, dω::T = one(T)) where {T<:AbstractFloat}
    in_time(t::T) = exp(-im*ω0*t) / (T(2)*π)
    in_freq(ω::T) = (ω==ω0) ? T(Inf) : zero(ω)
    ContinuousImpulse{T}(in_time, in_freq, false)
end

"""
See also: [`ContinuousImpulse`](@ref), [`TimeDiracImpulse`](@ref)

    GaussianImpulse(maxω[; σ = 3.0/maxω^2])

Returns a gaussian impulse function, which in the frequency domain is `exp(-σ*ω^2)*(2sqrt(σ*pi))`.
"""
function GaussianImpulse(maxω::T; σ::T = T(3.0)/maxω^2, t_shift = zero(T), ω_shift = zero(T)) where T<:AbstractFloat
    # shift in frequency then in time
    in_time(t::T) = exp(-(t - t_shift)^2 / (4σ)) * cos(ω_shift * (t - t_shift))

    # shift in frequency then in time
    gauss_freq(ω::T) = exp(-σ*ω^2) * (2sqrt(σ*pi))
    in_freq(ω::T) = (gauss_freq(ω - ω_shift) + gauss_freq(ω + ω_shift)) * exp.(im * ω * t_shift) / 2

    ContinuousImpulse{T}(in_time, in_freq)
end

"""
See also: [`ContinuousImpulse`](@ref), [`TimeDiracImpulse`](@ref)

    DiscreteGaussianImpulse(t_vec[, ω_vec])

Returns a discretised gaussian impulse.
"""
function DiscreteGaussianImpulse(t_vec::AbstractArray{T}, ω_vec::AbstractArray{T} = t_to_ω(t_vec);
        kws...) where {T}

    return continuous_to_discrete_impulse(GaussianImpulse(one(T); kws...), t_vec, ω_vec)
end


using RangeArrays
using Test

# Regular RangeMatrix tests

R = RangeMatrix(UnitRange{Int}[1:10, 11:20, 21:30, 31:40])

@test vec(collect(R)) == collect(1:40)
@test R[1,1] == 1

for i=1:40
    @test R[i] == i
end
for (i,j) in zip(eachindex(R), 1:length(R))
    @test R[i] == j
end

@test_throws BoundsError R[0]
@test_throws BoundsError R[41]

@test_throws BoundsError R[:, 0]
@test R[:, 1] == 1:10
@test R[:, 2] == 11:20
@test R[:, 3] == 21:30
@test R[:, 4] == 31:40
@test_throws BoundsError R[:, 5]

@test R[:, 1:2] == RangeMatrix(1:10, 11:20)
@test R[:, 1:4] == R[:,:] == RangeMatrix(1:10, 11:20, 21:30, 31:40)
@test R[:, 3:4] == RangeMatrix(21:30, 31:40)

# RepeatedRangeMatrix tests
R = RepeatedRangeMatrix(1:10, 0:10:30)

@test vec(collect(R)) == collect(1:40)
@test R[1,1] == 1

for i=1:40
    @test R[i] == i
end
for (i,j) in zip(eachindex(R), 1:length(R))
    @test R[i] == j
end

@test_throws BoundsError R[0]
@test_throws BoundsError R[41]

@test_throws BoundsError R[:, 0]
@test R[:, 1] == 1:10
@test R[:, 2] == 11:20
@test R[:, 3] == 21:30
@test R[:, 4] == 31:40
@test_throws BoundsError R[:, 5]

@test R[:, 1:2] == RangeMatrix(1:10, 11:20)
@test R[:, 1:4] == R[:,:] == RangeMatrix(1:10, 11:20, 21:30, 31:40)
@test R[:, 3:4] == RangeMatrix(21:30, 31:40)

A = 1:100
@test_throws BoundsError A[RangeMatrix(0:9, 20:29)]
@test_throws BoundsError A[RangeMatrix(20:29, 0:9)]
@test_throws BoundsError A[RangeMatrix(20:29, 92:101)]
@test_throws BoundsError A[RangeMatrix(92:101, 20:29)]
@test_throws BoundsError A[RangeMatrix(-100:100)]
@test A[RangeMatrix(1:10, 91:100)] == [1:10 91:100]

@test_throws BoundsError A[RepeatedRangeMatrix(1:10, [-1,33])]
@test_throws BoundsError A[RepeatedRangeMatrix(1:10, [33,-1])]
@test_throws BoundsError A[RepeatedRangeMatrix(1:10, [91,33])]
@test_throws BoundsError A[RepeatedRangeMatrix(1:10, [33,91])]
@test_throws BoundsError A[RepeatedRangeMatrix(-100:100, [0])]
@test A[RepeatedRangeMatrix(1:10, [0,90])] == [1:10 91:100]


using OpenQuantumBase, Test

x = range(0,stop=10,length=100)
y1 = Array(x) + 1.0im*Array(x)
y2 = (10.0+10.0im) .- Array(x)
inter_complex = construct_interpolations(x, y1)
inter_real = construct_interpolations(x, 10 .- collect(x))
gridded_2_range_inter = construct_interpolations(collect(x), y1)

test_x = range(0,stop=10,length=50)
exp_complex = collect(test_x) + 1.0im*collect(test_x)
res_complex = inter_complex.(test_x)
exp_real = (10.0) .- collect(test_x)
res_real = inter_real.(test_x)

@test res_complex ≈ exp_complex
@test res_real ≈ exp_real
@test isa(inter_complex(-1), Complex)
@test inter_complex(-1) ≈ 0
@test inter_real(11) ≈ 0
@test gridded_2_range_inter.(test_x) ≈ exp_complex
@test gradient(inter_real, 2.3) ≈ -1
@test gradient(inter_real, [0.13, 0.21]) ≈ [-1, -1]

# Test for multi-demension array
y_array = transpose(hcat(y1, y2))
y_array_itp = construct_interpolations(x, y_array, order=1)
@test exp_complex ≈ y_array_itp(1, test_x)
@test y_array_itp([1, 2], -1) ≈ [0, 0]

# Test for extrapolation
x = range(1.0,stop=10.0)
y = 10 .- collect(x)
y_c = x + 1.0im*y

eitp = construct_interpolations(x, Array(x); extrapolation="line")
@test isapprox(eitp(0.0),0.0, atol=1e-8)
eitp = construct_interpolations(x, y_c; extrapolation="line")
@test imag(eitp(0)) ≈ 10
eitp = construct_interpolations(x, y_c; extrapolation="flat")
@test eitp(0) == 1.0 + 9.0im

x = [1.0, 3, 4, 5, 6, 7, 8, 9, 10]
@test_logs (:warn,"The grid is not uniform. Using grided linear interpolation.") construct_interpolations(x, 10 .- x, method="bspline")


@testset "LineString" begin
    ps = [SVector(0,0), SVector(0,1), SVector(1,1), SVector(1,0)]
    ls = LineString(ps)

    @test length(ls) == 3
    @test ls[2] == Line(SVector(0,1), SVector(1,1))

    @test boundingbox(ls) == BoundingBox(0,0,typemin(Int),1,1,typemax(Int))
    @test isclosed(ls) == false
    @test issimple(ls) == false

    @test convert2d(ls) == ls
    @test convert3d(ls) == LineString([SVector(0,0,0), SVector(0,1,0), SVector(1,1,0), SVector(1,0,0)])
    @test convert3d(ls, 1) == LineString([SVector(0,0,1), SVector(0,1,1), SVector(1,1,1), SVector(1,0,1)])
    @test convert3d(LineString([SVector(0,0,1), SVector(0,1,1), SVector(1,1,1), SVector(1,0,1)]), 2) == LineString([SVector(0,0,2), SVector(0,1,2), SVector(1,1,2), SVector(1,0,2)])

    push!(ps, SVector(0,0))
    @test length(ls) == 4
    @test isclosed(ls) == true
    @test issimple(ls) == true

    @test issimple(LineString([SVector(0,0), SVector(0,1), SVector(1,0), SVector(1,1), SVector(0,0)])) == false

    @testset "Winding number" begin
        ls_clockwise = LineString([SVector(0.0,0.0), SVector(0.0,1.0), SVector(1.0,1.0), SVector(1.0,0.0), SVector(0.0,0.0)])
        ls_anticlockwise = LineString([SVector(0.0,0.0), SVector(1.0,0.0), SVector(1.0,1.0), SVector(0.0,1.0), SVector(0.0,0.0)])

        p_centre = SVector(0.5, 0.5)
        ps_outer_ring = SVector{2,Float64}[[-1.0,-1.0],[0.0,-1.0],[0.5,-1.0],[1.0,-1.0],
                                           [2.0,-1.0],[2.0,0.0],[2.0,0.5],[2.0,1.0],
                                           [2.0,2.0],[1.0,2.0],[0.5,2.0],[0.0,2.0],
                                           [-1.0,2.0],[-1.0,1.0],[-1.0,0.5],[-1.0,0.0]]

        @test winding_number(p_centre, ls_clockwise) == 1
        @test winding_number(p_centre, ls_anticlockwise) == -1

        for p in ps_outer_ring
            @test winding_number(p, ls_clockwise) == 0
            @test winding_number(p, ls_anticlockwise) == 0
        end
    end
end


function has_converged(cvchecker::ConvergenceChecker, optimizerstate::OptimizerState)
    return false
end

@with_kw struct FirstOrderOptimality <: ConvergenceChecker
    tol_tangent::Float64 = 1e-11
    tol_normal::Float64 = 1e-11
end

function has_converged(cvchecker::FirstOrderOptimality, pb::CompositeProblem, o::Optimizer, os::OptimizationState)
    return (os.norm_minsubgradient_tangent < cvchecker.tol_tangent) && (os.norm_minsubgradient_normal < cvchecker.tol_normal)
end


# test tools for matrix smoothing

T = Float32
@testset "SNR" begin
A = rand(100,100) .+ 10. .* sin.(0.:2π/100:2π) .- 0.5
A .= T.(A)
# test high SNR
@test all(snr(A) .> 10)
# test type stability
@test eltype(snr(A)) == eltype(A)

C = CorrData()
C.corr = A
@test all(snr(C) .> 10)
end

@testset "smoothing" begin
A = rand(Float32,1000,10)
presmooth = mean(abs.(diff(A,dims=1)),dims=1)
el = eltype(A)
# test return type
@test smooth!(A) == nothing
# test type stability
@test eltype(A) == el
# test smoothness
@test all(presmooth .> mean(abs.(diff(A,dims=1)),dims=1))

@test eltype(smooth(A)) == eltype(A)
@test size(smooth(A)) == size(A)
# test smooth with window lendth of 3
B = smooth(A)
@test isapprox(mean(A[3:9,1]),B[6,1])
end

@testset "abs_max" begin
A = (rand(T,1000,10) .- T(0.5)) .* 1000
@test abs_max!(A) == nothing
@test maximum(A) <= 1.
@test minimum(A) >= -1.
A[:,1] .= 0
@test_throws DomainError abs_max!(A)

# test allocating
A = (rand(T,1000,10) .- T(0.5)) .* 1000
@test size(abs_max(A)) == size(A)
@test eltype(abs_max(A)) == eltype(A)
B = abs_max(A)
@test maximum(B) <= 1.
@test minimum(B) >= -1.

# test CorrData
C = CorrData()
A = (rand(T,1000,10) .- T(0.5)) .* 1000
C.corr = A
@test abs_max!(C) == nothing
@test maximum(C.corr) <= 1.
@test minimum(C.corr) >= -1

# test CorrData
C = CorrData()
A = (rand(T,1000,10) .- T(0.5)) .* 1000
C.corr = A
@test isa(abs_max(C),CorrData)
c = abs_max(C)
@test maximum(c.corr) <= 1.
@test minimum(c.corr) >= -1
end

@testset "standardize" begin
A = (rand(T,1000,10) .- T(0.5)) .* 1000
@test standardize!(A) == nothing
@test all(std(A,dims=1) .≈ T(1.))

# test allocating
A = (rand(T,1000,10) .- T(0.5)) .* 1000
@test size(standardize(A)) == size(A)
@test eltype(standardize(A)) == eltype(A)
B = standardize(A)
@test all(std(B,dims=1) .≈ T(1.))

# test CorrData
C = CorrData()
A = (rand(T,1000,10) .- T(0.5)) .* 1000
C.corr = A
@test standardize!(C) == nothing
@test all(std(C.corr,dims=1) .≈ T(1.))

# test CorrData
C = CorrData()
A = (rand(T,1000,10) .- T(0.5)) .* 1000
C.corr = A
@test isa(standardize(C),CorrData)
c = standardize(C)
@test all(std(c.corr,dims=1) .≈ T(1.))
end

@testset "mad" begin
A = rand(100,10)
A .= T.(A)
# test high mad
@test all(mad(A) .< 1)
# test type stability
@test eltype(mad(A)) == eltype(A)
@test size(mad(A),2) .== size(A,2)

C = CorrData()
C.corr = A
@test all(mad(C) .< 1)
end

@testset "std_threshold" begin
A = rand(T,1000,10)
A[100,1] = T(1000)
@test std_threshold(A,5.) == collect(2:10)
@test std_threshold(A,5) == collect(2:10)
end


@inline function indmin3(a,b,c,i,j)
    if a <= b
        if a <= c
            return 1,i-1,j-1
        else
            return 3,i,j-1
        end
    else
        if b <= c
            return 2,i-1,j
        else
            return 3,i,j-1
        end
    end
end

"""
    imin, imax = radiuslimits(r, n::Int, m::Int)
    imin, imax = radiuslimits(r, seq1, seq2)
"""
function radiuslimits(r, n::Int, m::Int)
    d = abs(m-n)
    if m >= n
        imin, imax = max.((1:n) .- r,1), min.((1:n) .+ (r+d), m)
    else
        imin, imax = max.((1:n) .- (r+d),1), min.((1:n) .+ r, m)
    end

    imin, imax
end

radiuslimits(r,seq1, seq2) = radiuslimits(r, lastlength(seq1), lastlength(seq2))

"""
    inds = align_signals(s::AbstractVector{<:AbstractArray}, master = argmax(length.(s)); method = :dtw, output=:indices)

Compute a set of indices such that `s[i][inds[i]]` is optimally aligned to `s[master]`. All elements of `inds` will have the same length.

# Arguments:
- `s`: A vector of signals to align
- `master`: Index of the signal used as reference. All the other signals will be aligned to this one.
- `method`: `:dtw` uses the warping paths from dtw between `s[master]` and `s[i]`. `:xcorr` uses `DSP.finddelay` which internally computes the cross correlation between signals, which often results in a slight misalignment.
- `output`: The default is to output the aligning indices, alternatively, the aligned `:signals` themselves can be outputted.
"""
function align_signals(s::AbstractVector{<:AbstractArray}, master::Integer=argmax(length.(s)); method=:dtw, output=:indices, postprocess=nothing)
    inds = [1:lastlength(s) for s in s]
    # find delays to align with master
    d = map(s) do si
        si === s[master] && (return 0)
        if method ∈ (:xcorr, :crosscorr, :dsp)
            SlidingDistancesBase.DSP.finddelay(s[master], si) # suboptimal because xcorr does not do exactly what we want
        elseif method ∈ (:dtw, :DTW)
            d,i1,i2 = dtw(si, s[master], postprocess=postprocess)
            round(Int, median(i2-i1))
        else
            throw(ArgumentError("Unknown method"))
        end
    end

    # find left and right (virtual) zero padding
    lp = maximum(d)
    rp = maximum(length(s[master]) .- (length.(s) .+ d))

    # New window length
    wl = length(inds[master]) - lp - rp

    # trim individual index sets to fit into new master window
    for i in eachindex(inds)
        start = max(1, 1+lp-d[i])
        stop = min(length(s[i]),start+wl-1)
        inds[i] = start : stop
    end
    @assert all(length.(inds) .== length(inds[1]))
    if output === :indices
        return inds
    else
        return [s[!,i] for (s,i) in zip(s,inds)]
    end
end


using StochasticDiffEq, DiffEqDevTools, LinearAlgebra, Random, Test
Random.seed!(100)
dts = (1/2) .^ (10:-1:2) #14->7 good plot

# From RUNGE–KUTTA METHODS FOR THE STRONG APPROXIMATION OF SOLUTIONS OF STOCHASTIC DIFFERENTIAL EQUATIONS
# (7.4)

d = 4; m = 10
u0 = rand(4)
A = zeros(d,d)
for i in 1:d, j in 1:d
  global A
  i == j && (A[i,j] = -3/2)
  i != j && (A[i,j] = 1/20)
end

B = [zeros(d,d) for i in 1:m]
for k in 1:m, i in 1:d, j in 1:d
  global B
  i == j && (B[k][i,j] = 1/5)
  i != j && (B[k][i,j] =1/100)
end

function f_noncommute(du,u,p,t)
  mul!(du,A,u)
end

function g_noncommute(du,u,p,t)
  for i in 1:m
    mul!(@view(du[:,i]),B[i],u)
  end
end

function f_noncommute_analytic(u0,p,t,W)
  tmp = (A - 0.5*sum(B[i]^2 for i in 1:m))*t + sum(B[i]*W[i] for i in 1:m)
  exp(tmp)*u0
end

function f_noncommute_analytic_stratonovich(u0,p,t,W)
  tmp = A*t + sum(B[i]*W[i] for i in 1:m)
  exp(tmp)*u0
end

ff_noncommute = SDEFunction(f_noncommute,g_noncommute,analytic=f_noncommute_analytic)

prob = SDEProblem(ff_noncommute,g_noncommute,u0,(0.0,1.0),noise_rate_prototype=rand(4,m))

sol = solve(prob,EM(),dt=1/2^(8))
sol = solve(prob,RKMil_General(),dt=1/2^(8))

dts = (1/2) .^ (10:-1:3) #14->7 good plot
sim1 = test_convergence(dts,prob,EM(),trajectories=Int(1e2))
@test abs(sim1.𝒪est[:final] - 0.5) < 0.2
sim2 = test_convergence(dts,prob,RKMilCommute(),trajectories=Int(1e2))
@test abs(sim2.𝒪est[:final] - 1) < 0.2
sim3 = test_convergence(dts,prob,RKMil_General(),trajectories=Int(1e2))
@test abs(sim3.𝒪est[:final] - 1) < 0.2

ff_noncommute_stratonovich = SDEFunction(f_noncommute,g_noncommute,analytic=f_noncommute_analytic_stratonovich)
prob_stratonovich = SDEProblem(ff_noncommute_stratonovich,g_noncommute,u0,(0.0,1.0),noise_rate_prototype=rand(4,m))

sim4 = test_convergence(dts,prob_stratonovich,EulerHeun(),trajectories=Int(1e2))
@test abs(sim4.𝒪est[:final] - 1.0) < 0.2

d = 2; m = 4
u0 = [2.0,2.0]
α = 1/2

function f_noncommute_2(du,u,p,t)
  du .= 0
end

function g_noncommute_2(du,u,p,t)
  du[1,1] = cos(α)*sin(u[1])
  du[2,1] = sin(α)*sin(u[1])
  du[1,2] = cos(α)*cos(u[1])
  du[2,2] = sin(α)*cos(u[1])
  du[1,3] = -sin(α)*sin(u[2])
  du[2,3] = cos(α)*sin(u[2])
  du[1,4] = -sin(α)*cos(u[2])
  du[2,4] = cos(α)*cos(u[2])
end

prob2 = SDEProblem(f_noncommute_2,g_noncommute_2,u0,(0.0,1.0),noise_rate_prototype=rand(2,m))

sol1 = solve(prob2,EM(),dt=1/2^(8))
sol2 = solve(prob2,RKMil_General(),dt=1/2^(8),adaptive=false)
sol3 = solve(prob2,RKMil_General(),dt=1/2^(8))

dts = (1/2) .^ (7:-1:2) #14->7 good plot
test_dt = 1/2 ^ (14)
sim5 = analyticless_test_convergence(dts,prob2,EM(),test_dt,trajectories=300, use_noise_grid=false)
@test abs(sim5.𝒪est[:final] - 0.5) < 0.2
sim6 = analyticless_test_convergence(dts,prob2,RKMil_General(),test_dt,trajectories=300, use_noise_grid=false)
@test abs(sim6.𝒪est[:final] - 0.5) < 0.2

sim7 = analyticless_test_convergence(dts,prob2,EulerHeun(),test_dt,trajectories=300, use_noise_grid=false)
@test abs(sim7.𝒪est[:final] - 0.5) < 0.2


using Test, OMEinsum
using OMEinsum: IndexGroup, NestedEinsum, parse_nested, DynamicEinCode, isleaf, collect_ixs
@testset "einsequence" begin
    @test push!(IndexGroup([],1), 'c').inds == IndexGroup(['c'], 1).inds
    @test isempty(IndexGroup([],1))

    @test_throws ArgumentError parse_nested("((ij,jk),km")
    @test_throws ArgumentError parse_nested("((ij,jk),k1)")

    a, b, c = rand(2,2), rand(2,2), rand(2,2)
    abc1 = ein"(ij,jk),km -> im"(a,b,c)
    abc2 = ein"((ij,jk),km) -> im"(a,b,c)
    abc3 = ein"ij,jk,km -> im"(a,b,c)

    @test abc1 ≈ abc2 ≈ abc3
    size_info = Dict('k'=>2)
    a, b, c, d = randn(2), randn(2,2), randn(2), randn(2)
    @test ein"((i,ij),i),j->ik"(a, b, c, d; size_info=size_info) ≈ ein"i,ij,i,j->ik"(a, b, c, d; size_info=size_info)
    @test collect_ixs(ein"((i,ij),i),j->ik") == collect_ixs(ein"i,ij,i,j->ik") == collect_ixs(DynamicEinCode(ein"i,ij,i,j->ik")) == [['i'], ['i','j'], ['i'], ['j']]
end

@testset "macro" begin
    b, c, d = rand(2,2), rand(2,2,2), rand(2,2,2,2)
    @ein a[i,j] := b[i,k] * c[k,k,l] * d[l,m,m,j]
    @ein a2[i,j] := b[i,k] * (c[k,k,l] * d[l,m,m,j])
    @test a ≈ a2
end

@testset "flatten" begin
    ne = ein"(ij,j),k->"
    @test OMEinsum.flatten(ne) === ein"ij,j,k->"
    todynamic(ne::NestedEinsum) = isleaf(ne) ? NestedEinsum{DynamicEinCode{Char}}(ne.tensorindex) : NestedEinsum(todynamic.(ne.args), DynamicEinCode(ne.eins))
    ne2 = todynamic(ne)
    @test OMEinsum.flatten(ne2) isa DynamicEinCode && OMEinsum.flatten(ne2) == DynamicEinCode(ein"ij,j,k->")
end


using HypothesisTests, Test

@testset "Show" begin
# based on power_divergence.jl tests
d = [[762,484] [327,239] [468,477]]
m = PowerDivergenceTest(d)

@test sprint(show, m, context=:compact => true) ==
    """
    Pearson's Chi-square Test
    -------------------------
    Population details:
        parameter of interest:   Multinomial Probabilities
        value under h_0:         [0.255231, 0.19671, 0.11594, 0.0893561, 0.193574, 0.14919]
        point estimate:          [0.276387, 0.175553, 0.118607, 0.0866884, 0.16975, 0.173014]
        95% confidence interval: Tuple{Float64,Float64}[(0.2579, 0.2955), (0.1571, 0.1947), (0.1001, 0.1378), (0.0682, 0.1058), (0.1513, 0.1889), (0.1545, 0.1922)]

    Test summary:
        outcome with 95% confidence: reject h_0
        one-sided p-value:           <1e-6

    Details:
        Sample size:        2757
        statistic:          30.070149095754687
        degrees of freedom: 2
        residuals:          [2.19886, -2.50467, 0.41137, -0.468583, -2.84324, 3.23867]
        std. residuals:     [4.50205, -4.50205, 0.699452, -0.699452, -5.31595, 5.31595]
    """

d = [ 20, 15, 25 ]
m = PowerDivergenceTest(d)

@test sprint(show, m, context=:compact => true) ==
    """
    Pearson's Chi-square Test
    -------------------------
    Population details:
        parameter of interest:   Multinomial Probabilities
        value under h_0:         [0.333333, 0.333333, 0.333333]
        point estimate:          [0.333333, 0.25, 0.416667]
        95% confidence interval: Tuple{Float64,Float64}[(0.2167, 0.481), (0.1333, 0.3976), (0.3, 0.5643)]

    Test summary:
        outcome with 95% confidence: fail to reject h_0
        one-sided p-value:           0.2865

    Details:
        Sample size:        60
        statistic:          2.5
        degrees of freedom: 2
        residuals:          [0.0, -1.11803, 1.11803]
        std. residuals:     [0.0, -1.36931, 1.36931]
    """

# based on t.jl tests
tst = OneSampleTTest(-5:10)

@test sprint(show, tst) ==
    """
    One sample t-test
    -----------------
    Population details:
        parameter of interest:   Mean
        value under h_0:         0
        point estimate:          2.5
        95% confidence interval: (-0.0369, 5.0369)

    Test summary:
        outcome with 95% confidence: fail to reject h_0
        two-sided p-value:           0.0530

    Details:
        number of observations:   16
        t-statistic:              2.100420126042015
        degrees of freedom:       15
        empirical standard error: 1.1902380714238083
    """

end


import Xfoil
import LiftProps
using Plots
pyplot()

airfoil_file = "naca2412.dat"

open(airfoil_file,"r") do f
  global x = Float64[]
  global y = Float64[]
  for line in eachline(f)
    x = append!(x,Meta.parse(split(chomp(line))[1]))
    y = append!(y,Meta.parse(split(chomp(line))[2]))
  end
end

aoa = range(-15,stop=20,length=45)

cl,cd,cdp,cm,converged = Xfoil.xfoilsweep(x,y,aoa,400000.0,printdata=true,clminstop=true,clmaxstop=true)

aoa = aoa[findall(converged)]
cl = cl[findall(converged)]
cd = cd[findall(converged)]
cdp = cdp[findall(converged)]
cm = cm[findall(converged)]

liftslope,zeroliftangle,aoafit,clfit = LiftProps.fitliftslope(aoa,cl)
_,clmax1 = LiftProps.findclmax(aoa,cl)
_,clmax2 = LiftProps.findclmax(aoa,cl,2)
_,clmax3 = LiftProps.findclmax(aoa,cl,1.0)
_,clmaxlinear1 = LiftProps.findclmaxlinear(aoa,cl,liftslope,zeroliftangle,interpolate=false)
_,clmaxlinear2 = LiftProps.findclmaxlinear(aoa,cl,liftslope,zeroliftangle,interpolate=true)
_,clmin1 = LiftProps.findclmin(aoa,cl)
_,clmin2 = LiftProps.findclmin(aoa,cl,2)
_,clmin3 = LiftProps.findclmin(aoa,cl,1.0)

# LIFT PLOT
plot(overwrite_figure = false)
myfont = font(12)
scatter!(aoa*180/pi,cl,label = "",markersize=5)
plot!(ylims = [min(minimum(cl),-0.1), maximum(cl)*1.1])
plot!(xlabel = "\$\\alpha\$ (degrees)",ylabel = "\$c_{l}\$")
plot!(legendfont = myfont,guidefont = myfont,tickfont = myfont)
# Plot XFOIL Data
scatter!(aoa*180/pi,cl,label = "XFOIL Data",markersize=5,markercolor=:red)
# Plot Linear Lift Slope Line
indclmin = argmin(abs.(cl.-minimum(cl)))
liftslopelinex = range(min(zeroliftangle-2.5*pi/180, aoa[indclmin]),stop=maximum(aoa),length=1000)
liftslopeliney = liftslope*(liftslopelinex.-zeroliftangle)
minAoAidx = argmin(abs.(minimum(cl) .- liftslopeliney))
maxAoAidx = argmin(abs.(maximum(cl) .- liftslopeliney))
minAoA = liftslopelinex[minAoAidx]
maxAoA = liftslopelinex[maxAoAidx]
liftslopelinex = range(min(zeroliftangle.-2.5*pi/180,minAoA), stop=maxAoA, length=1000)
liftslopeliney = liftslope*(liftslopelinex.-zeroliftangle)
plot!(liftslopelinex*180/pi,liftslopeliney, linewidth=2,
    label="Linear Lift Slope Line", linecolor=:blue)
# Plot Linear Lift Slope Line Data
scatter!(aoafit*180/pi,clfit,label = "Least Squares Data",
    markersize = 8,markercolor = :red,markershape = :x)
# Plot Zero Lift Angle
plot!([zeroliftangle*180/pi],seriestype = [:vline],label = "\$\\alpha_{0}\$",
    linestyle = :dashdot,linewidth = 1.5,linecolor = :brown)
# Plot clmax
plot!([clmax1],seriestype = [:hline],label = "\$c_{l,max,1}\$",
    linestyle = :dash,linewidth = 1,linecolor = :green)
plot!([clmax2],seriestype = [:hline],label = "\$c_{l,max,2}\$",
    linestyle = :dashdot,linewidth = 2,linecolor = :green)
plot!([clmax3],seriestype = [:hline],label = "\$c_{l,max,3}\$",
    linestyle = :dot,linewidth = 3,linecolor = :green)
plot!(show = true)
# Plot clmaxlinear
plot!([clmaxlinear1],seriestype = [:hline],label = "\$c_{l,max,linear,1}\$",
    linestyle = :dash,linewidth = 1,linecolor = :orange)
plot!([clmaxlinear2],seriestype = [:hline],label = "\$c_{l,max,linear,2}\$",
    linestyle = :dashdot,linewidth = 2,linecolor = :orange)
# Plot clmin
plot!([clmin1],seriestype = [:hline],label = "\$c_{l,min,1}\$",
    linestyle = :dash,linewidth = 1,linecolor = :purple)
plot!([clmin2],seriestype = [:hline],label = "\$c_{l,min,2}\$",
    linestyle = :dashdot,linewidth = 2,linecolor = :purple)
plot!([clmin3],seriestype = [:hline],label = "\$c_{l,min,3}\$",
    linestyle = :dot,linewidth = 3,linecolor = :purple)
plot!(show = true)


using LinearAlgebraicRepresentation,ViewerGL
Lar = LinearAlgebraicRepresentation
GL = ViewerGL

function randomarrangement2d(V,EV)
	VV = [[k] for k=1:size(V,2)]
	#GL.VIEW( GL.numbering(0.1)((V,[VV, EV])) );

	W = convert(Lar.Points, V')
	cop_EV = Lar.coboundary_0(EV::Lar.Cells)
	cop_EW = convert(Lar.ChainOp, cop_EV)
	V, copEV, copFE = Lar.Arrangement.planar_arrangement(W::Lar.Points, cop_EW::Lar.ChainOp)

	triangulated_faces = Lar.triangulate2D(V, [copEV, copFE])
	FVs = convert(Array{Lar.Cells}, triangulated_faces)
	V = convert(Lar.Points, V')
	#GL.VIEW(GL.GLExplode(V,FVs,1.2,1.2,1.2,99));

	W, copEV, copFE = Lar.Arrangement.planar_arrangement(W::Lar.Points, cop_EW::Lar.ChainOp)
	EVs = Lar.FV2EVs(copEV, copFE) # polygonal face fragments
	V = convert(Lar.Points, W')
	#GL.VIEW(GL.GLExplode(V,EVs,1.2,1.2,1.2,1));

	return V,EVs,FVs
end

V,EV = Lar.randomcuboids(100, .4)
V = GL.normalize2(V,flag=true)

W,EVs,FVs = randomarrangement2d(V,EV)
GL.VIEW(GL.GLExplode(W,EVs,1.2,1.2,1.2,1,1));
GL.VIEW(GL.GLExplode(W,FVs,1.2,1.2,1.2,99,1));
GL.VIEW(GL.GLExplode(W,FVs,1,1,1,99,1));


using Test

using PyPlot
using LaTeXStrings

using Oceananigans.Advection

# Define a few utilities for running tests and unpacking and plotting results
include("ConvergenceTests/ConvergenceTests.jl")

using .ConvergenceTests
using .ConvergenceTests.OneDimensionalGaussianAdvectionDiffusion: run_test
using .ConvergenceTests.OneDimensionalUtils: unpack_errors, defaultcolors, removespines

""" Run advection test for all Nx in resolutions. """
function run_convergence_test(κ, U, resolutions, advection_scheme)

    # Determine safe time-step
           Lx = 2.5
    stop_time = 1e-3
            h = Lx / maximum(resolutions)
           Δt = min(1e-2 * h / U, 1e-3 * h^2 / κ)

    # Run the tests
    results = [run_test(Nx=Nx, Δt=Δt, advection=advection_scheme, stop_iteration=1,
                        U=U, κ=κ) for Nx in resolutions]

    return results
end

#####
##### Run test
#####

advection_schemes = (CenteredSecondOrder(), CenteredFourthOrder(), UpwindBiasedThirdOrder(), WENO5())

U = -3
κ = 1e-8
Nx = 2 .^ (7:10)

results = Dict()
for scheme in advection_schemes
    t_scheme = typeof(scheme)
    results[t_scheme] = run_convergence_test(κ, U, Nx, scheme)
end

rate_of_convergence(::CenteredSecondOrder) = 2
rate_of_convergence(::CenteredFourthOrder) = 4
rate_of_convergence(::UpwindBiasedThirdOrder) = 3
rate_of_convergence(::WENO5) = 5
rate_of_convergence(::WENO{K}) where K = 2K-1

fig, ax = subplots()

@testset "tmp" begin
for (j, scheme) in enumerate(advection_schemes)
    t_scheme = typeof(scheme)
    name = string(t_scheme)
    roc = rate_of_convergence(scheme)

    u_L₁, v_L₁, cx_L₁, cy_L₁, u_L∞, v_L∞, cx_L∞, cy_L∞ = unpack_errors(results[typeof(scheme)])

    atol = t_scheme == CenteredSecondOrder ? 0.05 :
           t_scheme == CenteredFourthOrder ? 0.12 :
           t_scheme == WENO5               ? 0.40 : Inf

    test_rate_of_convergence(u_L₁,  Nx, expected=-roc, atol=atol, name=name*" u_L₁")
    test_rate_of_convergence(v_L₁,  Nx, expected=-roc, atol=atol, name=name*" v_L₁")
    test_rate_of_convergence(cx_L₁, Nx, expected=-roc, atol=atol, name=name*" cx_L₁")
    test_rate_of_convergence(cy_L₁, Nx, expected=-roc, atol=atol, name=name*" cy_L₁")
    test_rate_of_convergence(u_L∞,  Nx, expected=-roc, atol=atol, name=name*" u_L∞")
    test_rate_of_convergence(v_L∞,  Nx, expected=-roc, atol=atol, name=name*" v_L∞")
    test_rate_of_convergence(cx_L∞, Nx, expected=-roc, atol=atol, name=name*" cx_L∞")
    test_rate_of_convergence(cy_L∞, Nx, expected=-roc, atol=atol, name=name*" cy_L∞")

    @test  u_L₁ ≈  v_L₁
    @test cx_L₁ ≈ cy_L₁
    @test  u_L∞ ≈  v_L∞
    @test cx_L∞ ≈ cy_L∞

    common_kwargs = (linestyle="None", color=defaultcolors[j], mfc="None", alpha=0.8)
    loglog(Nx,  u_L₁; basex=2, marker="o", label="\$L_1\$-norm, \$u\$ $name", common_kwargs...)
    loglog(Nx,  v_L₁; basex=2, marker="2", label="\$L_1\$-norm, \$v\$ $name", common_kwargs...)
    loglog(Nx, cx_L₁; basex=2, marker="*", label="\$L_1\$-norm, \$x\$ tracer $name", common_kwargs...)
    loglog(Nx, cy_L₁; basex=2, marker="+", label="\$L_1\$-norm, \$y\$ tracer $name", common_kwargs...)

    loglog(Nx,  u_L∞; basex=2, marker="1", label="\$L_\\infty\$-norm, \$u\$ $name", common_kwargs...)
    loglog(Nx,  v_L∞; basex=2, marker="_", label="\$L_\\infty\$-norm, \$v\$ $name", common_kwargs...)
    loglog(Nx, cx_L∞; basex=2, marker="^", label="\$L_\\infty\$-norm, \$x\$ tracer $name", common_kwargs...)
    loglog(Nx, cy_L∞; basex=2, marker="s", label="\$L_\\infty\$-norm, \$y\$ tracer $name", common_kwargs...)

    label = raw"\sim N_x^{-" * "$roc" * raw"}" |> latexstring
    loglog(Nx, cx_L₁[1] .* (Nx[1] ./ Nx) .^ roc, color=defaultcolors[j], basex=2, alpha=0.8, label=label)

    xlabel(L"N_x")
    ylabel("\$L\$-norms of \$ | c_\\mathrm{sim} - c_\\mathrm{analytical} |\$")
    removespines("top", "right")
    lgd = legend(loc="upper right", bbox_to_anchor=(1.4, 1.0), prop=Dict(:size=>6))

    if j == length(advection_schemes)
        filepath = joinpath(@__DIR__, "figs", "one_dimensional_convergence.png")
        savefig(filepath, dpi=480, bbox_extra_artists=(lgd,), bbox_inches="tight")
        close(fig)
    end
end
end


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# ╔═╡ 5fe9a49a-fdc2-11ea-3ff9-01e000224b3a
using Pkg, DrWatson

# ╔═╡ 5fe9db1c-fdc2-11ea-0c8c-7db1e606cbcb
begin
	#@quickactivate "StatisticalRethinkingStan"
	using StanQuap
	using StatisticalRethinking
end

# ╔═╡ 5b3cfe6e-fdc2-11ea-2462-cb4109cb77ee
md"## Clip-05-49.1s.jl"

# ╔═╡ 5fea720a-fdc2-11ea-2dde-493a3df79ff2
begin
	df = CSV.read(sr_datadir("milk.csv"), DataFrame; delim=';')
	df[!, :clade_id] = Int.(indexin(df[:, :clade], unique(df[:, :clade])))
	scale!(df, [:kcal_per_g])
	PRECIS(df[:, [:clade_id, :kcal_per_g]])
end

# ╔═╡ 5ffa4cfc-fdc2-11ea-0dd3-659a65ad4949
stan5_9 = "
data{
  int <lower=1> N;              // Sample size
  int <lower=1> k;
  vector[N] K;
  int clade_id[N];
}
parameters{
  vector[k] a;
  real<lower=0> sigma;
}
model{
  vector[N] mu;
  sigma ~ exponential( 1 );
  a ~ normal( 0 , 0.5 );
  for ( i in 1:N ) {
      mu[i] = a[clade_id[i]];
  }
  K ~ normal( mu , sigma );
}
";

# ╔═╡ 0233ffa6-fdc1-11ea-202f-d18f84a47bb1
md"##### Define the SampleModel."

# ╔═╡ 90cf5430-fdc2-11ea-279a-19fbadfdf16b
begin
	data = (N = size(df, 1), clade_id = df.clade_id,
    	K = df.kcal_per_g_s, k = length(unique(df[:, :clade_id])))
	init = (a = [-1.0, 0,0, 1.0, 1.0], sigma = 1.0,)
	q5_9s, m5_9s, _ = stan_quap("m5.9", stan5_9; data, init)

	if !isnothing(m5_9s)
		post5_9s_df = read_samples(m5_9s, :dataframe)
		PRECIS(post5_9s_df)
	end
end

# ╔═╡ 7e7479dc-fdc2-11ea-2644-a75204ec3851
if !isnothing(q5_9s)
	quap5_9s_df = sample(q5_9s)
	PRECIS(quap5_9s_df)
end

# ╔═╡ 7e7528a0-fdc2-11ea-1031-973876c110eb
rethinking = "
       mean   sd  5.5% 94.5%
a[1]  -0.48 0.22 -0.83 -0.14
a[2]   0.37 0.22  0.02  0.71
a[3]   0.68 0.26  0.26  1.09
a[4]  -0.59 0.27 -1.02 -0.15
sigma  0.72 0.10  0.57  0.87
";

# ╔═╡ 7e89657a-fdc2-11ea-33a9-779fdfd53519
md"## End of clip-05-49.1s.jl"

# ╔═╡ Cell order:
# ╟─5b3cfe6e-fdc2-11ea-2462-cb4109cb77ee
# ╠═5fe9a49a-fdc2-11ea-3ff9-01e000224b3a
# ╠═5fe9db1c-fdc2-11ea-0c8c-7db1e606cbcb
# ╠═5fea720a-fdc2-11ea-2dde-493a3df79ff2
# ╠═5ffa4cfc-fdc2-11ea-0dd3-659a65ad4949
# ╟─0233ffa6-fdc1-11ea-202f-d18f84a47bb1
# ╠═90cf5430-fdc2-11ea-279a-19fbadfdf16b
# ╠═7e7479dc-fdc2-11ea-2644-a75204ec3851
# ╠═7e7528a0-fdc2-11ea-1031-973876c110eb
# ╟─7e89657a-fdc2-11ea-33a9-779fdfd53519


using WAVEFORM, SparseArrays

n = 101 .* [1;1]
d = 10.0 .*[1;1]
npml = [1 1; 1 1]
freq = 5.0
v = ones((n...)) .* 2000
f0 = 10.0
unit = "m/s"

H, dH, ddH = helm2d_7pt(n,d,npml,freq,v,f0,unit)    # 10201
H, dH, ddH = WAVEFORM.helm2d_chen2013(n,d,npml,freq,v,f0,unit)


using FourierFlows: CPU, TwoDGrid
using FFTW
using Random: seed!, rand

dev = CPU()           # Device.
N = 256               # Number of grid points.
L = 2.0               # Length of the square domain.
stepper = "RK4"       # Type of time stepper (e.g., RK4, AB3).
dt = 2e-3             # Size of timestep.
effort = FFTW.PATIENT # FFTW effort flag (e.g., FFTW.MEASURE, FFTW.PATIENT).
nlog = 500            # Display log and save Hovmöller slices every `nlog` timesteps.
β = 0.0               # Planetary background potential vorticity gradient.
U₀ = 1.0              # Amplitude of basic jet.
Lj = L/10             # Half-width of basic jet.
qmag = 0.004          # Amplitude of the random initial condition.
Rthresh = 0.01        # Threshold energy ratio for renormalization.
cycles = 3            # Total number of renormalization cycles.

cyclesm = cycles - 1
gr = TwoDGrid(N, L, N, L, x0=0.0, aliased_fraction=0.0)
U, Uyy = NondimLinearOneHalfLayerQG.get_bickley(gr, U₀, Lj)
seed!(1234) # Fix the seed of the random number generator for reproducibility.
q₀ = qmag * gr.nx * gr.ny * rand(gr.nx, gr.ny)


"""
---
Apply SCFDMA demodulator to input signal and returns the T/F QAM matrix
# --- Syntax
qamRx	= scfdmaSigDecode(sigId,nFFT,nCP,allocSubcarriers,sizeDFT);
# --- Input parameters
# ---  Input parameters
- sigId	  : SCFDMA signal in time domain [Array{Complex{Float64},nbEch}]
- nFFT	  : FFT size [Int]
- nCp	  : Cyclic prefix size (in samples) [Int]
- allocSubcarriers : Vector of index of allocated subcarriers [Array{Int,nbSubcarriers}]
- sizeDFT	: Post processing size (DFT size)
- doPostProcessing : Do the post processing stage (IDFT): Default 1. In some case, we need the raw data (i.e data before postprocessing stage) such as in channel equalisation. In this case, the post processing should be done in a separate step (with the call of scfdmaPostProcessing function)
# ---  Output parameters
- qamRx  : Time frequency matrix : [Array{Complex{Float64},nbSubcarriers,nbSymb}]
# ---
# v 1.0 - Robin Gerzaguet.
"""
function scfdmaSigDecode(sigChan,nFFT,nCP,allocSubcarriers,sizeDFT,doPostProcessing=1)
	# Set 4 core for FFT computation
	#FFTW.set_num_threads(4)
	# --- Getting symbol size
	sizeSymb	   = nFFT + nCP;
	# --- Getting number of symbol to decode
	nbSymbDecode   = Int(floor(length(sigChan)/sizeSymb));
	# --- Initiate Rx T/F matrix
	qamRx		   = zeros(Complex{Float64},length(allocSubcarriers),nbSymbDecode);
	# --- SCFDMA demodulator
	for n = 1  : 1 : nbSymbDecode
		# --- Getting current symbol without CP
		currSymb  = sigChan[sizeSymb*(n-1).+collect(1+nCP:sizeSymb)];
		# --- TF
		fftEl	  = fft(currSymb,1);
		# --- Mapping in output matrix
		qamRx[:,n]		= fftEl[allocSubcarriers];
	end
	if Bool(doPostProcessing)
		qamRx	= scfdmaPostProcessing(qamRx,sizeDFT);
	end
	return qamRx;
end

"""
---
Apply Post processing for SC-FDMA (i.e DFT post-processing stage). This function can be called if scfdmaSigDecode is called with post processing flag to 0 (if frequency egalisation is done for example)
# --- Syntax
qamPost	  = scfdmaPostProcessing(qamMat,sizeDFT)
# --- Input parameters
- qamMat  : T/F matrix after Rx FFT (and before Rx IDFT) [Array{Complex{Float64}},nbSubcarriers,nbSymb]
- sizeDFT	: Precoder bloc size (often 12)
# --- Output parameters
- qamPost	: T/F matrix after IDFT processing [Array{Complex{Float64},nbSubcarriers,nbSymb}]
# ---
# v 1.0 - Robin Gerzaguet.
"""
function scfdmaPostProcessing(qamMat,sizeDFT)
	nbSymb		= size(qamMat,2);
	nbSubcarriers = size(qamMat,1);
	nbCarriers	= nbSubcarriers ÷ sizeDFT;
	qamIDFT		= zeros(Complex{Float64},nbSubcarriers,nbSymb);
	for iN = 1 : 1 : nbSymb
		for iC = 1 : 1 : nbCarriers
			# --- Apply DFT to current carrier, at current symbol
			qamIDFT[(iC-1)*sizeDFT .+ (1:sizeDFT),iN] = ifft(qamMat[(iC-1)*sizeDFT  .+ (1:sizeDFT),iN]);
		end
	end
	return qamIDFT
end

"""
---
Apply SCFDMA demodulator to input signal and returns the T/F QAM matrix
# --- Syntax
qamRx	= scfdmaSigDecode(sigId,nFFT,nCP,allocSubcarriers,sizeDFT);
# --- Input parameters
# ---  Input parameters
- sigId	  : SCFDMA signal in time domain [Array{Complex{Float64},nbEch}]
- scfdma  : SCFDMA structure [StrucSCFDMA]
- doPostProcessing : Do the post processing stage (IDFT): Default 1. In some case, we need the raw data (i.e data before postprocessing stage) such as in channel equalisation. In this case, the post processing should be done in a separate step (with the call of scfdmaPostProcessing function)
# ---  Output parameters
- qamRx  : Time frequency matrix : [Array{Complex{Float64},nbSubcarriers,nbSymb}]
# ---
# v 1.0 - Robin Gerzaguet.
"""
function scfdmaSigDecode(sigChan,scfdma::StrucSCFDMA,doPostProcessing=1)
	return scfdmaSigDecode(sigChan,scfdma.nFFT,scfdma.nCP,scfdma.allocatedSubcarriers,scfdma.sizeDFT,doPostProcessing);
end


include("euler/euler.jl")
using .Calculus: is_square
using .Primes: prime_sieve

function compute()::Int
    sieve = prime_sieve(6_000)
    prime_numbers = findall(sieve)
    i = 9
    while true
        if !sieve[i]
            j = 1
            is_not_expressible = true
            while prime_numbers[j] < i
                if is_square((i - prime_numbers[j]) ÷ 2)
                    is_not_expressible = false
                    break
                end
                j += 1
            end
            if is_not_expressible return i end
        end
        i += 2
    end
end


using DZLinearAlgebra: find_saturation_threshold
using MultiFloats
using Printf

const INVERSE_FACTORIALS_F64 = setprecision(() ->
    [Float64x{20}(inv(BigFloat(factorial(BigInt(i))))) for i = 1 : 170],
    BigFloat, 1200)

const LOG2_F64 = setprecision(() ->
    Float64x{20}(log(BigFloat(2))),
    BigFloat, 1200)

function test_multifloat_exp(x::Float64x{N}, num_terms::Int=170,
        reduction_power::Int=0, ::Val{B}=Val(false)) where {N,B}
    exponent_f = Base.rint_llvm(x._limbs[1] / 0.6931471805599453)
    exponent_i = Base.fptosi(Int, exponent_f)
    y = ldexp(Float64x{N}(Float64x{N+!B}(x) -
            exponent_f * Float64x{N+!B}(LOG2_F64)), -reduction_power)
    terms = Vector{Float64x{N}}(undef, num_terms)
    @inbounds terms[1] = y
    for i = 2 : num_terms
        @inbounds terms[i] = terms[div(i, 2)] * terms[div(i+1, 2)]
    end
    exp_y = terms[num_terms] * Float64x{N}(INVERSE_FACTORIALS_F64[num_terms])
    for i = num_terms-1 : -1 : 1
        exp_y += terms[i] * Float64x{N}(INVERSE_FACTORIALS_F64[i])
    end
    exp_y += 1.0
    for _ = 1 : reduction_power
        exp_y *= exp_y
    end
    return ldexp(exp_y, exponent_i)
end

function test_exp_precision(x::Float64x{N}, args...) where {N}
    setprecision(3000) do
        exp_x = test_multifloat_exp(x, args...)
        exp_exact = exp(BigFloat(x))
        -Float64(log2(abs((BigFloat(exp_x) - exp_exact) / exp_exact)))
    end
end

function run_test(num_trials::Int, max_reduction_power::Int, ::Val{B}) where {B}
    for N = 1 : 8
        for reduction_power = 0 : max_reduction_power
            data = [(Float64(num_terms), minimum(test_exp_precision(
                    100 * rand(Float64x{N}) - 50,
                    num_terms, reduction_power, Val{B}())
                for _ = 1 : num_trials)) for num_terms = 1 : 170]
            threshold = round(Int, find_saturation_threshold(data)[1]) + 1
            lost_bits = precision(Float64x{N}) - floor(Int,
                minimum(p[2] for p in data[threshold:end]))
            @printf("Float64x%d (power %2d): %3d terms; %3d bits\n",
                N, reduction_power, threshold, lost_bits)
        end
        println()
    end
end

num_trials = 1000
max_reduction_power = 20

use_clean_multifloat_arithmetic(9)
println("CLEAN (EXTRA BIT)")
run_test(num_trials, max_reduction_power, Val{false}())
println("CLEAN (NO EXTRA)")
run_test(num_trials, max_reduction_power, Val{true}())

use_standard_multifloat_arithmetic(9)
println("STANDARD (EXTRA BIT)")
run_test(num_trials, max_reduction_power, Val{false}())
println("STANDARD (NO EXTRA)")
run_test(num_trials, max_reduction_power, Val{true}())

use_sloppy_multifloat_arithmetic(9)
println("SLOPPY (EXTRA BIT)")
run_test(num_trials, max_reduction_power, Val{false}())
println("SLOPPY (NO EXTRA)")
run_test(num_trials, max_reduction_power, Val{true}())


using JuMP
include("../benchmark.jl")

function max_cut_model(n::Int64,r::Int64,edge_list::Array{Tuple{Int64,Int64},1})
    #edges = [(i,i+1) for i = 1:(n-1)]
    m = Model()

    #start_mat = randn(n,r)
    #
    #for i = 1:n
    #  start_mat[i,:] = start_mat[i,:] / norm(start_mat[i,:],2)
    #end

    @variable(m, x[i=1:n,k=1:r],start=randn() / sqrt(r))
    nedges = length(edge_list)
    @NLobjective(m, Min, sum(x[i,k] * x[j,k] for k = 1:r for (i,j) in edge_list) / 2.0 - nedges / 2.0)
    for i = 1:n
      @NLconstraint(m, sum(x[i,k]^2 for k=1:r) == 1)
    end

    return m
    #
end
edge_list = Array{Tuple{Int64,Int64},1}()
n = 200
for sample = 1:(20 * n)
  edge = (rand(1:n),rand(1:n))
  if edge[1] < edge[2] && !(edge in edge_list)
    push!(edge_list,edge)
  end
end

m = max_cut_model(n,6,edge_list)

pars = Class_parameters()
#pars.aggressive_dual_threshold = 1e-4
#pars.primal_bounds_dual_feas = true
iter, status, hist, t, err = one_phase_solve(m,pars);

using Ipopt
nlp_raw = MathProgNLPModel(m);
IpoptSolve(nlp_raw)


module AsymmetricRisk

using Statistics, LinearAlgebra
using StatsBase
using KernelDensity
using HCubature
using Distributions
using EmpiricalCDFs
import QuadGK: quadgk
# using GLM
# using NamedTupleTools

export semivariance, lowerpartialmom, upperpartialmom, valueatrisk, expectedshortfall

export quantiledep, exceedancecor, downsidebeta, upsidebeta, coskewness, cokurtosis

include("univariate.jl")
include("bivariate.jl")

end # module


#= -------------------------------------------------------------------------
# @file practica1_problema3.jl
#
# @date 09/22/17 13:41:52
# @author Martin Noblia
# @email martin.noblia@openmailbox.org
#
# @brief
#
# @detail
#
#  Licence:
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
---------------------------------------------------------------------------=#
using DifferentialEquations
using Plots; pyplot()

#---------------------------------------------------------------------------
# constants
#---------------------------------------------------------------------------
const ϵ = 0.8
const Rₑ = 0.4
const D = 1e-3
const T_alr = 300
const T_inf = 300
const h = 100
const σ = 5.67e-8
const ρ = 8933
const c = 385
I₁(t) = (t ≥ 0) ? 5.2 : 0.0 # step function
I₂(t) = t
I₃(t) = sin(t)
#differential equation
f(t, T) = (I₁(t)^2 * Rₑ - π * D * h * (T - T_inf) - π * D * ϵ * σ *(T^4 - T_alr^4)) / (ρ * c *(π * (D^2)/4))
T₀ = 25.0 + 273.0
tsimu = (0.0, 240.0)
prob  = ODEProblem(f, T₀, tsimu)
solution = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)

plot(solution.t, solution - 273)


using Test
using PolynomialBasis
using ImplicitDomainQuadrature
# using Revise
using CutCellDG
include("../useful_routines.jl")
include("test_problem_and_solver.jl")

function compute_stress_error(
    distancefunc,
    nelmts,
    polyorder,
    numqp,
    penaltyfactor;
    eta = +1,
)
    L = 1.0
    W = 1.0
    lambda, mu = 1.0, 2.0
    dx = 1.0 / nelmts
    penalty = penaltyfactor / dx * (lambda + mu)
    alpha = 0.1
    stiffness = CutCellDG.HookeStiffness(lambda, mu, lambda, mu)

    elasticitybasis = LagrangeTensorProductBasis(2, polyorder)

    mergedmesh, cellquads, facequads, interfacequads, levelset =
        construct_mesh_and_quadratures(
            [L, W],
            nelmts,
            elasticitybasis,
            distancefunc,
            numqp,
        )

    sysmatrix = CutCellDG.SystemMatrix()
    sysrhs = CutCellDG.SystemRHS()

    CutCellDG.assemble_displacement_bilinear_forms!(
        sysmatrix,
        elasticitybasis,
        cellquads,
        stiffness,
        mergedmesh,
    )
    CutCellDG.assemble_interelement_condition!(
        sysmatrix,
        elasticitybasis,
        facequads,
        stiffness,
        mergedmesh,
        penalty,
        eta,
    )
    CutCellDG.assemble_coherent_interface_condition!(
        sysmatrix,
        elasticitybasis,
        interfacequads,
        stiffness,
        mergedmesh,
        penalty,
        eta,
    )
    CutCellDG.assemble_penalty_displacement_bc!(
        sysmatrix,
        sysrhs,
        x -> displacement(alpha, x),
        elasticitybasis,
        facequads,
        stiffness,
        mergedmesh,
        x -> onboundary(x, L, W),
        penalty,
    )
    CutCellDG.assemble_body_force!(
        sysrhs,
        x -> body_force(lambda, mu, alpha, x),
        elasticitybasis,
        cellquads,
        mergedmesh,
    )

    matrix = CutCellDG.sparse_displacement_operator(sysmatrix, mergedmesh)
    rhs = CutCellDG.displacement_rhs_vector(sysrhs, mergedmesh)

    nodaldisplacement = matrix \ rhs

    stresserr = stress_L2_error(
        nodaldisplacement,
        elasticitybasis,
        cellquads,
        stiffness,
        mergedmesh,
        x -> stress_field(lambda, mu, alpha, x),
    )
    return stresserr
end

function update_cell_stress_error!(
    err,
    elasticitybasis,
    stiffness,
    celldisp,
    quad,
    jac,
    detjac,
    cellmap,
    vectosymmconverter,
    exactsolution,
)

    dim = length(vectosymmconverter)
    for (p, w) in quad
        grad = CutCellDG.transform_gradient(gradient(elasticitybasis, p), jac)
        NK = sum([
            CutCellDG.make_row_matrix(vectosymmconverter[k], grad[:, k]) for
            k = 1:dim
        ])
        symmdispgrad = NK * celldisp

        numericalstress = stiffness * symmdispgrad
        exactstress = exactsolution(cellmap(p))

        err .+= (numericalstress - exactstress) .^ 2 * detjac * w
    end
end

function stress_L2_error(
    nodaldisplacement,
    elasticitybasis,
    cellquads,
    stiffness,
    mesh,
    exactsolution,
)

    err = zeros(3)
    numcells = CutCellDG.number_of_cells(mesh)

    dim = CutCellDG.dimension(mesh)
    jac = CutCellDG.jacobian(mesh)
    detjac = CutCellDG.determinant_jacobian(mesh)
    vectosymmconverter = CutCellDG.vector_to_symmetric_matrix_converter()

    for cellid = 1:numcells
        cellsign = CutCellDG.cell_sign(mesh, cellid)

        CutCellDG.check_cellsign(cellsign)
        if cellsign == -1 || cellsign == 0
            nodeids = CutCellDG.nodal_connectivity(mesh, -1, cellid)
            celldofs = CutCellDG.element_dofs(nodeids, dim)
            celldisp = nodaldisplacement[celldofs]
            quad = cellquads[-1, cellid]
            cellmap = CutCellDG.cell_map(mesh, -1, cellid)

            update_cell_stress_error!(
                err,
                elasticitybasis,
                stiffness[-1],
                celldisp,
                quad,
                jac,
                detjac,
                cellmap,
                vectosymmconverter,
                exactsolution,
            )
        end

        if cellsign == +1 || cellsign == 0
            nodeids = CutCellDG.nodal_connectivity(mesh, +1, cellid)
            celldofs = CutCellDG.element_dofs(nodeids, dim)
            celldisp = nodaldisplacement[celldofs]
            quad = cellquads[+1, cellid]
            cellmap = CutCellDG.cell_map(mesh, +1, cellid)

            update_cell_stress_error!(
                err,
                elasticitybasis,
                stiffness[+1],
                celldisp,
                quad,
                jac,
                detjac,
                cellmap,
                vectosymmconverter,
                exactsolution,
            )
        end
    end
    return sqrt.(err)
end

function test_stress_convergence_edge_intersecting_curved_interface()
    powers = [3, 4, 5]
    nelmts = [2^p + 1 for p in powers]
    dx = 1.0 ./ nelmts
    interface_center = [1.0, 0.5]
    interface_radius = 0.45
    polyorder = 2
    numqp = required_quadrature_order(polyorder) + 2
    penaltyfactor = 1e3
    err = [
        compute_stress_error(
            x -> circle_distance_function(
                x,
                interface_center,
                interface_radius,
            )[1],
            ne,
            polyorder,
            numqp,
            penaltyfactor,
        ) for ne in nelmts
    ]
    serr = [[er[i] for er in err] for i = 1:3]
    rates = [convergence_rate(dx, v) for v in serr]
    @test all([all(rates[i] .> 1.95) for i = 1:3])
end

function test_stress_convergence_circular_interface()
    powers = [3, 4, 5]
    nelmts = [2^p + 1 for p in powers]
    dx = 1.0 ./ nelmts
    interface_center = [0.3, 0.8]
    interface_radius = 0.15
    polyorder = 2
    numqp = required_quadrature_order(polyorder) + 2
    penaltyfactor = 1e2
    err = [
        compute_stress_error(
            x -> circle_distance_function(
                x,
                interface_center,
                interface_radius,
            )[1],
            ne,
            polyorder,
            numqp,
            penaltyfactor,
        ) for ne in nelmts
    ]
    serr = [[er[i] for er in err] for i = 1:3]
    rates = [convergence_rate(dx, v) for v in serr]
    @test all([all(rates[i] .> 1.95) for i = 1:3])
end

test_stress_convergence_edge_intersecting_curved_interface()
test_stress_convergence_circular_interface()


###################################################################
#Description
###################################################################

###################################################################
##Packages
###################################################################
using LinearAlgebra, SpecialFunctions, NLsolve

###################################################################
##Parameters
###################################################################
σ = 3 #Elasticity of substitution, Source : Footnote 45, EK2002
θ = 8.28 #Technology dispersion parameter(Comparative Advantage), Source : Table VI, EK2002
β = 0.21 #Labor share in costs, Source : Table VIII, EK2002

###################################################################
#Generate data
#Wages, Trade costs, Technology shift parameter
###################################################################
N=43 #Number of countries

#Generate wages costs
ϵ_temp = randn(N)
w = 100*fill(1.0,N,1) + 100*ϵ_temp.^2

#Generate trade costs
ϵ_temp = randn(N,N)
D = fill(1.0,N,N) + ϵ_temp.^2
D=D-Diagonal(D)+I # Make diagonal entries equal 1 (No internal resistance)
#D = fill(1.0,N,N)

#Generate productivity shift parameter
ϵ_temp = randn(N)
T = 10*fill(1.0,N,1) + ϵ_temp.^2

###################################################################
#Model
###################################################################

############### Step 1 : Fixed point for prices ###################
function P_fixedpoint(P_telde,D,T,w,θ,β,σ,N)
    γ = (gamma((θ+1-σ)/θ))^(1/(1-σ)) #Gamma function
    Part1 = γ^(-θ)*T.*(w.^(-θ*β)) #create vector with product of gamma, wages^(θβ) and shift parameter
    Part2 = repeat(transpose(Part1),N) #create matrix which contain copies of above vector in each row
    G = (D.^(-θ)).*Part2 # point wise multiplication of distance^(-θ) and above matrix
    P_tilde = G*P_telde.^(1-β)
    return P_tilde
end
f(P_tilde) =  P_fixedpoint(P_tilde,D,T,w,θ,β,σ,N) #redefine function with single argument

init=ones(N,1) #initial value
sol = fixedpoint(f, init)
P = sol.zero.^(-1/θ)

############### Step 2 : Trade Shares ###################

γ = (gamma((θ+1-σ)/θ))^(1/(1-σ)) #Gamma function
Part1 = γ^(-θ)*T.*(w.^(-θ*β)).*(P.^(-θ*(1-β))) #create vector with product of gamma, wages^(θβ), P^(-θ(1-β)) and shift parameter
Part2 = repeat(transpose(Part1),N) #create matrix which contain copies of above vector in each row
Part3=transpose(repeat(transpose(P.^(-θ)),N)) #create matrix which contain copies of price vector in each row
Π = (D.^(-θ)).*Part2./Part3 # point wise multiplication of distance^(-θ) and above matrixs

#Correlation check
corr = cor(T,diag(Π))
println(corr)

############### Step 3 : Counterfactual ###################
# RTA between two countries : reduce distance between single pair and look at change in trade share for all countries

############### Step 4 : Graphics ###################
#Trade flows in circle