# Meshes a quadraterial area given by four corners with nx * xy * 2 triangles.

# TODO: Add quadraterial option
# TODO: Add test
# TODO: Check if the corners does not represent a convex set.

   # Node number for column i and row j

function meshquad(nx::Int, ny::Int, corners::Matrix{Float64},
                 ele_type::Union(Type{GeoQuad}, Type{GeoTrig}, Type{GeoQTrig}))

    if size(corners) != (2,4)
        error("corner argument needs to be a 2x4 matrix")
    end

    if (nx <= 0 || ny <= 0)
        error("Need at least 1x1 elements")
    end

    mesh = GeoMesh()

    #... you what Lint?
    @lintpragma("Ignore use of undeclared variable :")
    # The four corners
    LL = corners[:, 1]
    UL = corners[:, 2]
    UR = corners[:, 3]
    LR = corners[:, 4]

    if ele_type == GeoQTrig
        np_y = 2*ny
        np_x = 2*nx
    else
        np_y = ny
        np_x = ny
    end

     @inline node_nr(i::Int, j::Int) = (np_x + 1) * (i-1) + j

    # Add the nodes
    for i in 0:np_y
        ratio_bounds = i / np_y

        x0 = LL[1] * (1 - ratio_bounds) + ratio_bounds * UL[1]
        x1 = LR[1] * (1 - ratio_bounds) + ratio_bounds * UR[1]

        y0 = LL[2] * (1 - ratio_bounds) + ratio_bounds * UL[2]
        y1 = LR[2] * (1 - ratio_bounds) + ratio_bounds * UR[2]

        for j in 0:np_x
            ratio = j / np_x
            x = x0 * (1 - ratio) + ratio * x1
            y = y0 * (1 - ratio) + ratio * y1
            push!(mesh, GeoNode2(node_nr(i+1, j+1), [x,y]))
        end
    end

    # Add the elements
    n_elem = 0
       if ele_type == GeoQTrig
        jump = 2
    else
        jump = 1
    end

    for i in 1:jump:jump*ny
        for j in 1:jump:jump*nx
            if ele_type == GeoTrig
                n_elem += 1
                elem1 = GeoTrig(n_elem, Vertex3(node_nr(i, j), node_nr(i+1, j), node_nr(i+1, j+1)))
                push!(mesh, elem1)
                n_elem += 1
                elem2 = GeoTrig(n_elem, Vertex3(node_nr(i, j), node_nr(i+1, j+1), node_nr(i, j+1)))
                push!(mesh, elem2)
            elseif ele_type == GeoQTrig
                n_elem += 1
                elem1 = GeoQTrig(n_elem, Vertex6(node_nr(i, j), node_nr(i+2, j+2), node_nr(i+2, j),
                                                 node_nr(i+1, j+1), node_nr(i+2, j+1), node_nr(i+1, j)))
                push!(mesh, elem1)
                n_elem += 1
                elem1 = GeoQTrig(n_elem, Vertex6(node_nr(i, j), node_nr(i, j+2), node_nr(i+2, j+2),
                                                 node_nr(i, j+1), node_nr(i+1, j+2), node_nr(i+1, j+1)))
                push!(mesh, elem1)
            else
                n_elem += 1
                elem = GeoQuad(n_elem, Vertex4(node_nr(i,j), node_nr(i,j+1), node_nr(i+1,j+1), node_nr(i+1,j)))
                push!(mesh, elem)
            end
        end
    end
    return mesh
end

# Generates what is known as the "Cook membrane"
function gencook(nx, ny, ele_type::Union(Type{GeoQuad}, Type{GeoTrig}, Type{GeoQTrig}), scale = 1.0)
    m = scale * [[0.0; 0.0] [0.0; 44.0] [48.0; 60.0] [48.0; 44.0]]
    meshquad(nx, ny, m, ele_type)
end


using FEASTSolver
using LinearAlgebra
using DelimitedFiles
using SparseArrays

A = spdiagm(-1 => fill(-1.0, 99), 0 => fill(2.0, 100), 1 => fill(-1.0, 99))
# A = diagm(-1 => fill(-1.0, 99), 0 => fill(2.0, 100), 1 => fill(-1.0, 99))
B = sprand(100, 100, 0.2)
B = B + B'
C, R = complex(0.05, 0.0), 0.05 # contour
# C, R = complex(0.1, 0.0), 0.1 # contour
# X = rand(ComplexF64, 100, 20) # initial subspace
X = sprand(ComplexF64, 100, 20, 0.9)

contour = circular_contour_trapezoidal(C, R, 8)
# contour = rectangular_contour_gauss(0.0 - R*im, 2*R + R*im, 8)
# contour = rectangular_contour_trapezoidal(0.0 - R*im, 2*R + R*im, 32)
# contour = rectangular_contour_gauss(0.9 - R*im, 1.1 + R*im, 16)

# z = 0.025+0.0im
# println(rational_func(z, contour))

e, v, res = gen_feast!(X, A, B, contour, debug=true, ϵ=10e-15)
# @timev e, v, res = feast!(X, A, contour, debug=false, ϵ=10e-15, iter=1)

convergence_info(e,v,res,contour)
display(e)


# we write a function that solves Ux = y for x where U is upper triangular and y is a vector
function Usolve(U, y)

    # we get the sizes of U and y
    URows, UCols = size(U)
    yRows = size(y)[1]

    # we check that U and y ahve appropriate sizes
    if URows != UCols || yRows != URows
        error("The size of U or y is not appropriate")
    end

    # we initialize x to y
    x = copy(y);

    # we loop backwards over the rows of U
    for k = URows:-1:1

        # we check that we don't divide by zero
        if U[k,k] == 0
            error("There are zeros on the diagonal of U")
        end

        # we modify x accordingly
        for j = k+1:URows
            x[k] -= U[k,j] * x[j];
        end
        x[k] /= U[k,k];
    end

    # we return x
    return x
end


import SparseArrays
import NonlinearEquations
@NonlinearEquations.equations function gw1d(h, Q)
	NonlinearEquations.setnumequations(3)
	NonlinearEquations.addterm(1, -2 * h[1] + h[2] + Q[1])
	NonlinearEquations.addterm(2, h[1] - 2 * h[2] + h[3] + Q[1])
	NonlinearEquations.addterm(3, -2 * h[3] + h[2] + Q[3])
end
function solveforh(Q)
	res(h) = gw1d_residuals(h, Q)
	jac(h) = gw1d_h(h, Q)
	h0 = zeros(N)
	h = jac(h0) \ -res(h0)
	return h
end

N = 3
Q = 8 * ones(N) / N ^ 2
h = solveforh(Q)


using ReachabilityBenchmarks, MathematicalSystems, Reachability,
      TaylorIntegration, Plots

include(@relpath "bouncingBallNonlinear_model.jl")
include(@relpath "bouncingBallNonlinear_specifications.jl")

S = bouncingBallNonlinear_model()
X0, options = bouncingBallNonlinear_specification()
options = Options(options)

# initial value problem
problem = InitialValueProblem(S, X0)

# reachability algorithm
algorithm_continuous = TMJets(:orderT => 5, :orderQ => 2, :abs_tol => 1e-10)
algorithm_hybrid = LazyDiscretePost(:check_invariant_intersection => true)

# compute flowpipe
options[:mode] = "reach"
solution = solve(problem, options, algorithm_continuous, algorithm_hybrid)

# project flowpipe to x₁ and x₂
solution.options[:plot_vars] = [1, 2]
solution_proj = project(solution)

# plot projection
plot(solution_proj, xlab="x", ylab="v")
savefig("bouncingBallNonlinear")


function reldiff(x, y)
    p = (x - y) ./ abs.(x)
    return norm(p, Inf)
end


using SimpleSolvers
using Test

n = 1
T = Float64
x = [T(π),]
j = reshape(2x, 1, 1)

function F!(f::Vector, x::Vector)
    f .= x.^2
end

function J!(g::Matrix, x::Vector)
    g .= 0
    for i in eachindex(x)
        g[i,i] = 2x[i]
    end
end

JPAD = JacobianParameters{T}(F!, n; mode = :autodiff, diff_type = :forward)
JPFD = JacobianParameters{T}(F!, n; mode = :autodiff, diff_type = :finite)
JPUS = JacobianParameters{T}(J!, n; mode = :user)

@test typeof(JPAD) <: JacobianParametersAD
@test typeof(JPFD) <: JacobianParametersFD
@test typeof(JPUS) <: JacobianParametersUser

function test_jac(j1, j2, atol)
    for i in eachindex(j1,j2)
        @test j1[i] ≈ j2[i] atol=atol
    end
end

jad = zero(j)
jfd = zero(j)
jus = zero(j)

compute_jacobian!(jad, x, JPAD)
compute_jacobian!(jfd, x, JPFD)
compute_jacobian!(jus, x, JPUS)

test_jac(jad, j, eps())
test_jac(jfd, j, 1E-7)
test_jac(jus, j, 0)

jad1 = zero(j)
jfd1 = zero(j)
jus1 = zero(j)

compute_jacobian!(jad1, x, F!; mode = :autodiff, diff_type = :forward)
compute_jacobian!(jfd1, x, F!; mode = :autodiff, diff_type = :finite)
compute_jacobian!(jus1, x, J!; mode = :user)

test_jac(jad, jad1, 0)
test_jac(jfd, jfd1, 0)
test_jac(jus, jus1, 0)

jad2 = zero(j)
jfd2 = zero(j)

compute_jacobian_ad!(jad2, x, F!)
compute_jacobian_fd!(jfd2, x, F!)

test_jac(jad, jad2, 0)
test_jac(jfd, jfd2, 0)


haltonnumber = BlackBoxOptim.Utils.haltonnumber
haltonsequence = BlackBoxOptim.Utils.haltonsequence

# The Halton sequence with base 2 according to http://en.wikipedia.org/wiki/Halton_sequence
HaltonSequence2 = [
    1/2, 1/4, 3/4, 1/8, 5/8, 3/8, 7/8, 1/16, 9/16
]

# The Halton sequence with base 2 according to http://en.wikipedia.org/wiki/Halton_sequence
HaltonSequence3 = [
    1/3, 2/3, 1/9, 4/9, 7/9, 2/9, 5/9, 8/9, 1/27
]

@testset "Halton numbers and sequence" begin

    @testset "Halton numbers" begin

        map(1:length(HaltonSequence2)) do i
            @test isapprox(haltonnumber(2, i), HaltonSequence2[i])
        end

        map(1:length(HaltonSequence3)) do i
            @test isapprox(haltonnumber(3, i), HaltonSequence3[i])
        end

    end

    @testset "Halton sequences" begin

        hseq2 = haltonsequence(2, length(HaltonSequence2))
        @test length(hseq2) == length(HaltonSequence2)
        map(1:length(HaltonSequence2)) do i
            @test isapprox(hseq2[i], HaltonSequence2[i])
        end

        hseq3 = haltonsequence(3, length(HaltonSequence3))
        @test length(hseq3) == length(HaltonSequence3)
        map(1:length(HaltonSequence3)) do i
            @test isapprox(hseq3[i], HaltonSequence3[i])
        end

    end

end


export add, ∑

"""
    add(x::Int64, y::Int64)

Returns x + y

# Examples

```jldoctest
julia> x, y = 1, 3; add(x, y)
4

julia> @assert "add(x::Int64, y::Int64)" == which_sig(add, x, y)
```
"""
add(x::Int64, y::Int64) = x + y

"""
    add(x::T, y::T) where {T<:Number}

Returns x + y

# Examples

```jldoctest
julia> x, y = 1+3im, 3+5im; add(x, y)
4 + 8im

julia> @assert "add(x::T, y::T) where T<:Number" == which_sig(add, x, y)
```
"""
add(x::T, y::T) where {T<:Number} = x + y

"""
    add(x::T, y::T) where {T<:AbstractString}

Returns x*y

# Examples

```jldoctest
julia> x, y = "my", "string"; add(x, y)
"mystring"

julia> @assert "add(x::T, y::T) where T<:AbstractString" == which_sig(add, x, y)
```
"""
add(x::T, y::T) where {T<:AbstractString} = x*y

"""
    add(x, y)

Returns x + y

# Examples

```jldoctest
julia> x, y = Vector([1,2,3]), Vector([4,5,6]); add(x, y)
3-element Array{Int64,1}:
 5
 7
 9

julia> @assert "add(x, y)" == which_sig(add, x, y)
```
"""
add(x,y) = x + y

@doc raw"""
    ∑(x::Int64...)
Adds all parameters:

```math
\sum_i^n x_1, x_2, \dots x_n \forall x_k \in \mathbb{N}
```
# Examples

```jldoctest
julia> x1, x2, x3, x4, x5 = 1:5; ∑(x1,x2,x3,x4,x5)
15

julia> @assert "∑(x::Int64...)" == which_sig(∑, x1, x2, x3, x4, x5)
```
"""
∑(x::Int64...) = sum(x)

@doc raw"""
    ∑(x::Int64[])
Adds all parameters:

# Examples

```jldoctest
julia> x = Vector(1:5); ∑(x)
15

julia> @assert "∑(x::AbstractArray{Int64,1})" == which_sig(∑, x)

```
"""
∑(x::AbstractArray{Int64,1}) = ∑(x...)


struct SimulatedAnnealing{Tn, Ttemp} <: RandomSearch
    neighbor::Tn
    temperature::Ttemp
end
Base.summary(::SimulatedAnnealing) = "Simulated Annealing"

"""
# SimulatedAnnealing
## Constructor
```julia
SimulatedAnnealing(; neighbor = default_neighbor, temperature = log_temperature)
```

The constructor takes two keywords:
* `neighbor = a(x_now, [x_candidate])`, a function that generates a new iterate based on the current
* `temperature = b(iteration)`, a function of the current iteration that returns a temperature

## Description
Simulated Annealing is a derivative free method for optimization. It is based on the
Metropolis-Hastings algorithm that was originally used to generate samples from a
thermodynamics system, and is often used to generate draws from a posterior when doing
Bayesian inference. As such, it is a probabilistic method for finding the minimum of a
function, often over a quite large domains. For the historical reasons given above, the
algorithm uses terms such as cooling, temperature, and acceptance probabilities.
"""
SimulatedAnnealing(;neighbor = default_neighbor,
                    temperature = log_temperature) =
  SimulatedAnnealing(neighbor, temperature)

log_temperature(t) = 1 / log(t)^2

function default_neighbor(x_best)
  T = eltype(x_best)
  n = length(x_best)
  return x_best .+ T.(RandomNumbers.randn(n))
end

function minimize(objective, x0, method::SimulatedAnnealing; maxiter=1000)
    T = eltype(x0)

    x_best = copy(x0)
    f_best = objective(nothing, x_best)
    x_now = copy(x0)
    f_now = f_best
    for iter = 1:maxiter
        # Determine the temperature for current iteration
        t = method.temperature(iter)

        # Randomly generate a neighbor of our current state
        x_candidate = method.neighbor(x_best)

        # Evaluate the cost function at the proposed state
        f_candidate = objective(nothing, x_candidate)

        if f_candidate <= f_now
            # If proposal is superior, we always move to it
            x_now = copy(x_candidate)
            f_now = f_candidate

            # If the new state is the best state yet, keep a record of it
            if f_candidate < f_best
                x_best = copy(x_now)
                f_best = f_now
            end
        else
            # If proposal is inferior, we move to it with probability p
            p = exp(-(f_candidate - f_now) / t)
            if T(RandomNumbers.rand()) <= p
                x_now = copy(x_candidate)
                f_now = f_candidate
            end
        end
    end
    (f_best=f_best, x_best=x_best, f_now=f_now, x_now=x_now, temperature=method.temperature(maxiter))
end


using ModelingToolkit, StaticArrays, LinearAlgebra, LabelledArrays
using DiffEqBase, ForwardDiff
using Test

# Define some variables
@parameters t σ ρ β
@variables x(t) y(t) z(t)
D = Differential(t)

# Define a differential equation
eqs = [D(x) ~ σ * (y - x),
    D(y) ~ t * x * (ρ - z) - y,
    D(z) ~ x * y - β * z]

@named de = ODESystem(eqs)
ff = ODEFunction(de, [x, y, z], [σ, ρ, β], jac = true)

a = @SVector [1.0, 2.0, 3.0]
b = SLVector(x = 1.0, y = 2.0, z = 3.0)
c = [1.0, 2.0, 3.0]
p = SLVector(σ = 10.0, ρ = 26.0, β = 8 / 3)
@test ff(a, p, 0.0) isa SVector
@test typeof(ff(b, p, 0.0)) <: SLArray
@test ff(c, p, 0.0) isa Vector
@test ff(a, p, 0.0) == ff(b, p, 0.0)
@test ff(a, p, 0.0) == ff(c, p, 0.0)

@test ff.jac(a, p, 0.0) isa SMatrix
@test typeof(ff.jac(b, p, 0.0)) <: SMatrix
@test ff.jac(c, p, 0.0) isa Matrix
@test ff.jac(a, p, 0.0) == ff.jac(b, p, 0.0)
@test ff.jac(a, p, 0.0) == ff.jac(c, p, 0.0)

# Test similar_type
@test ff(b, p, ForwardDiff.Dual(0.0, 1.0)) isa SLArray
d = LVector(x = 1.0, y = 2.0, z = 3.0)
@test ff(d, p, ForwardDiff.Dual(0.0, 1.0)) isa LArray
@test ff.jac(b, p, ForwardDiff.Dual(0.0, 1.0)) isa SArray
@test eltype(ff.jac(b, p, ForwardDiff.Dual(0.0, 1.0))) <: ForwardDiff.Dual
@test ff.jac(d, p, ForwardDiff.Dual(0.0, 1.0)) isa Array
@inferred ff.jac(d, p, ForwardDiff.Dual(0.0, 1.0))
@test eltype(ff.jac(d, p, ForwardDiff.Dual(0.0, 1.0))) <: ForwardDiff.Dual


# Solves the Poisson equation using the Mixed finite element method
using Revise
using AdFem
using DelimitedFiles
using SparseArrays
using PyPlot

TestCase = [
    (
        (x,y)->begin;x*y*(x - 1)*(y - 1);end,
        (x,y)->begin;x*y*(x - 1)*(y - 1);end,
        (x,y)->begin;x^2 + 2*x*y - 2*x + 2*y^2 - 3*y + 1/2;end,
        (x,y)->begin;2*x^2 + 2*x*y - 3*x + y^2 - 2*y + 1/2;end,
    ),
    (
        (x,y)->begin;x^2*y^2*(x - 1)*(y^2 - 1);end,
        (x,y)->begin;x^2*y^2*(x - 1)*(y^2 - 1);end,
        (x,y)->begin;6*x^3*y^2 - x^3 + 6*x^2*y^3 - 6*x^2*y^2 - 3*x^2*y + x^2 + 6*x*y^4 - 4*x*y^3 - 6*x*y^2 + 2*x*y - 2*y^4 + 2*y^2;end,
        (x,y)->begin;12*x^3*y^2 - 2*x^3 + 6*x^2*y^3 - 12*x^2*y^2 - 3*x^2*y + 2*x^2 + 3*x*y^4 - 4*x*y^3 - 3*x*y^2 + 2*x*y - y^4 + y^2;end,
    ),
    (
        (x,y)->begin;x^2*y^2*(x - 1)*(y^2 - 1);end,
        (x,y)->begin;x*y*(x - 1)*(y - 1);end,
        (x,y)->begin;6*x^3*y^2 - x^3 - 6*x^2*y^2 + x^2 + 6*x*y^4 - 6*x*y^2 + 2*x*y - x - 2*y^4 + 2*y^2 - y + 1/2;end,
        (x,y)->begin;6*x^2*y^3 - 3*x^2*y + 2*x^2 - 4*x*y^3 + 2*x*y - 2*x + y^2 - y;end,
    )
]

for k = 1:length(TestCase)
    @info "Running TestCase $k..."
    ufunc, vfunc, gfunc, hfunc = TestCase[k]

    n = 50
    mmesh = Mesh(n, n, 1/n, degree = BDM1)

    A = compute_fem_bdm_mass_matrix(mmesh)
    B = compute_fem_bdm_div_matrix(mmesh)
    C = compute_fem_bdm_skew_matrix(mmesh)

    D = [A B' C'
        B spzeros(2mmesh.nelem, 3mmesh.nelem)
        C spzeros(mmesh.nelem, 3mmesh.nelem)]

    gD = bcedge(mmesh)
    t1 = eval_f_on_gauss_pts(gfunc, mmesh)
    t2 = eval_f_on_gauss_pts(hfunc, mmesh)
    f1 = compute_fvm_source_term(t1, mmesh)
    f2 = compute_fvm_source_term(t2, mmesh)

    rhs = [zeros(2mmesh.ndof); f1; f2; zeros(mmesh.nelem)]

    sol = D\rhs
    u = sol[2mmesh.ndof+1:2mmesh.ndof+2mmesh.nelem]
    close("all")
    figure(figsize=(15, 10))
    subplot(231)
    title("Reference")
    xy = fvm_nodes(mmesh)
    x, y = xy[:,1], xy[:,2]
    uf = ufunc.(x, y)
    visualize_scalar_on_fvm_points(uf, mmesh)
    subplot(232)
    title("Numerical")
    visualize_scalar_on_fvm_points(u[1:mmesh.nelem], mmesh)
    subplot(233)
    title("Absolute Error")
    visualize_scalar_on_fvm_points( abs.(u[1:mmesh.nelem] - uf) , mmesh)

    subplot(234)
    xy = fvm_nodes(mmesh)
    x, y = xy[:,1], xy[:,2]
    uf = vfunc.(x, y)
    visualize_scalar_on_fvm_points(uf, mmesh)
    subplot(235)
    visualize_scalar_on_fvm_points(u[mmesh.nelem+1:end], mmesh)
    subplot(236)
    visualize_scalar_on_fvm_points( abs.(u[mmesh.nelem+1:end] - uf) , mmesh)
    savefig("elasticity$k.png")
    end


mutable struct TrueSkillEnv
    mu::Float64
    sigma::Float64
    beta::Float64
    tau::Float64
    draw_probability::Float64
end

GLOBAL_ENV = TrueSkillEnv(25.0, 25.0/3, 25.0/6, 25.0/300, 0.10)
DELTA = 0.0001

function calc_draw_probability(draw_margin, size; env=GLOBAL_ENV)
    return 2 * cdf(draw_margin / (sqrt(size) * env.beta)) - 1.
end

function calc_draw_margin(draw_probability, size; env=GLOBAL_ENV)
    return ppf((draw_probability + 1) / 2.) * sqrt(size) * env.beta
end

function _team_sizes(rating_groups)
    szs = map(x -> length(x), rating_groups)
    cumsum(szs)
end

function _floating_point_error()
    error("Floating point error. Result out of range.")
end

struct Rating <: GaussianItem
    pi::Float64
    tau::Float64
end

function Rating(; mu=GLOBAL_ENV.mu, sigma=GLOBAL_ENV.sigma)
    pi = sigma^(-2)
    tau = pi * mu
    Rating(pi, tau)
end

function Rating(g::Gaussian)
    Rating(g.pi, g.tau)
end

@inline function mu(r::Rating)
    if r.pi != 0
        return r.tau / r.pi
    else
        return 0
    end
end

@inline function sigma(r::Rating)
    if r.pi != 0
        return(sqrt(1/r.pi))
    else
        return Inf
    end
end

@inline function isless(a::Rating, b::Rating)
    isless(mu(a), mu(b))
end

function show(io::IO, r::Rating)
    print(io, "Rating(mu=$(mu(r)), sigma=$(sigma(r)))")
end

function create_rating(;mu=GLOBAL_ENV.mu, sigma=GLOBAL_ENV.sigma)
    Rating(;mu=mu, sigma=sigma)
end

function v_win(diff::Real, draw_margin::Real)
    x = diff - draw_margin
    denom = cdf(x)
    if denom != 0.0
        return pdf(x) / denom
    else
        return -x
    end
end

function v_draw(diff::Real, draw_margin::Real)
    abs_diff = abs(diff)
    a, b = draw_margin - abs_diff, -draw_margin - abs_diff
    denom = cdf(a) - cdf(b)
    numer = pdf(b) - pdf(a)
    c = 0.0
    if denom != 0.0
        c = numer / denom
    else
        c = a
    end
    d = 0
    if diff < 0
        d = -1
    else
        d = 1
    end
    return c * d
end

function w_win(diff::Real, draw_margin::Real)
    x = diff - draw_margin
    v = v_win(diff, draw_margin)
    w = v * (v + x)
    if 0 < w < 1
        return w
    else
        _floating_point_error()
    end
end

function w_draw(diff::Real, draw_margin::Real)
    abs_diff = abs(diff)
    a, b = draw_margin - abs_diff, -draw_margin - abs_diff
    denom = cdf(a) - cdf(b)
    if denom == 0
        _floating_point_error()
    end
    v = v_draw(abs_diff, draw_margin)
    return (v ^ 2) + (a * pdf(a) - b * pdf(b)) / denom
end

function validate_rating_groups(rating_groups::Vector{Vector{Rating}})
    if length(rating_groups) < 2
        error("need multiple groups")
    end
    if any(map(length, rating_groups) .== 0)
        error("no empty groups allowed")
    end
    return rating_groups
end

function factor_graph_builders(rating_groups::Vector{Vector{Rating}}, ranks, weights; env=GLOBAL_ENV)
    # This is the main builder!!
    flatten_ratings = reduce(vcat, rating_groups)
    flatten_weights = reduce(vcat, weights)
    size = length(flatten_ratings)
    group_size = length(rating_groups)

    rating_vars = [Variable() for x in 1:size]
    perf_vars = [Variable() for x in 1:size]
    team_perf_vars = [Variable() for x in 1:group_size]
    team_diff_vars = [Variable() for x in 1:(group_size - 1)]
    team_sizes = _team_sizes(rating_groups)

    # layer builders
    function build_rating_layer()
        r = Vector{PriorFactor}()
        for (rating_var, rating) in zip(rating_vars, flatten_ratings)
            push!(r, PriorFactor(rating_var, rating, env.tau))
        end
        return r
    end

    function build_perf_layer()
        r = Vector{LikelihoodFactor}()
        for (rating_var, perf_var) in zip(rating_vars, perf_vars)
            push!(r, LikelihoodFactor(rating_var, perf_var, env.beta ^ 2))
        end
        return r
    end

    function build_team_perf_layer()
        r = Vector{SumFactor}()
        for (team, team_perf_var) in enumerate(team_perf_vars)
            if team > 1
                first = team_sizes[team - 1] + 1
            else
                first = 1
            end
            last = team_sizes[team]
            child_perf_vars = perf_vars[first:last]
            coeffs = flatten_weights[first:last]
            push!(r, SumFactor(team_perf_var, child_perf_vars, coeffs))
        end
        return r
    end

    function build_team_diff_layer()
        r = Vector{SumFactor}()
        for (team, team_diff_var) in enumerate(team_diff_vars)
            push!(r, SumFactor(team_diff_var,
                team_perf_vars[team:team+1], [+1, -1]))
        end
        return r
    end

    function build_trunc_layer()
        r = Vector{TruncateFactor}()
        for (x, team_diff_var) in enumerate(team_diff_vars)
            draw_probability = env.draw_probability
            size = sum(map(length, rating_groups[x:x+1]))
            draw_margin = calc_draw_margin(draw_probability, size; env=env)
            if ranks[x] == ranks[x + 1] # in case of tie
                v_func, w_func = v_draw, w_draw
            else
                v_func, w_func = v_win, w_win
            end
            push!(r, TruncateFactor(team_diff_var, v_func, w_func, draw_margin))
        end
        return r
    end
    return (build_rating_layer, build_perf_layer, build_team_perf_layer,
        build_team_diff_layer, build_trunc_layer)
end

function run_schedule(build_rating_layer, build_perf_layer,
    build_team_perf_layer, build_team_diff_layer, build_trunc_layer; min_delta=DELTA)
    # This is the main runner!!
    if min_delta <= 0
        error("min_delta must be greater than 0")
    end
    layers = Vector{Vector{Node}}()

    rating_layer = build_rating_layer()
    perf_layer = build_perf_layer()
    team_perf_layer = build_team_perf_layer()

    push!(layers, rating_layer)
    push!(layers, perf_layer)
    push!(layers, team_perf_layer)

    for f in reduce(vcat, layers)
        down(f)
    end

    team_diff_layer = build_team_diff_layer()
    trunc_layer = build_trunc_layer()

    push!(layers, team_diff_layer)
    push!(layers, trunc_layer)

    team_diff_len = length(team_diff_layer)

    for i in 1:10
        if team_diff_len == 1
            # two teams
            down(team_diff_layer[1])
            delta = up(trunc_layer[1])
        else
            # three or more teams
            delta = 0.0
            for j in 1:(team_diff_len - 1)
                down(team_diff_layer[j])
                delta = max(delta, up(trunc_layer[j]))
                up(team_diff_layer[j]; index=2) # up to right Variable
            end
            for j in team_diff_len:-1:2
                down(team_diff_layer[j])
                delta = max(delta, up(trunc_layer[j]))
                up(team_diff_layer[j]; index=1) # up to left Variable
            end
        end
        if delta <= min_delta
            break
        end
    end

    # up both ends
    up(team_diff_layer[1]; index=1)
    up(team_diff_layer[team_diff_len]; index=2)

    # up the remainder of the black arrows
    for f in team_perf_layer
        for i in 1:(length(f.vars) - 1) # number of members.
            up(f; index=i)
        end
    end
    for f in perf_layer
        up(f)
    end
    return layers
end

function rate(rating_groups::Vector{Vector{Rating}}; ranks=1:length(rating_groups), weights=[ones(length(x)) for x in rating_groups], min_delta=DELTA, env=GLOBAL_ENV)
    validate_rating_groups(rating_groups)
    group_size = length(rating_groups)
    if length(ranks) != group_size
        error("wrong ranks")
    end

    # sort rating groups by ranks
    by_rank = x -> x[2][2]
    sorting = sort(collect(enumerate(zip(rating_groups, ranks, weights))), by=by_rank)
    sorted_rating_groups = Vector{Vector{Rating}}()
    sorted_ranks = Vector{Float64}()
    sorted_weights = Vector{Vector{Float64}}()
    for (x, (g, r, w)) in sorting
        push!(sorted_rating_groups, g)
        push!(sorted_ranks, r)
        push!(sorted_weights, [max(min_delta, w_) for w_ in w])
    end
    b1, b2, b3, b4, b5 = factor_graph_builders(sorted_rating_groups, sorted_ranks, sorted_weights; env=env)
    layers = run_schedule(b1, b2, b3, b4, b5; min_delta=min_delta)

    rating_layer, team_sizes = layers[1], _team_sizes(sorted_rating_groups)
    transformed_groups = Vector{Vector{Rating}}()
    for (firstm1, last) in zip(vcat([0], team_sizes[1:end-1]), team_sizes)
        group = Vector{Rating}()
        for f in rating_layer[(firstm1+1):last]
            push!(group, Rating(; mu=mu(var(f)), sigma=sigma(var(f))))
        end
        push!(transformed_groups, group)
    end

    by_hint = x -> x[1]
    unsorting = sort(collect(zip((x for (x, _) in sorting), transformed_groups)), by=by_hint)
    return [g for (x, g) in unsorting]
end

function expose(rating::Rating; env::TrueSkillEnv=GLOBAL_ENV)
    k = env.mu / env.sigma
    return mu(rating) - k * sigma(rating)
end

function make_as_global(env::TrueSkillEnv)
    GLOBAL_ENV = env
end

function rate(rating1::Rating, rating2::Rating; drawn=false, min_delta=DELTA)
    if drawn
        ranks = [0, 0]
    else
        ranks = [0 ,1]
    end
    teams = rate([[rating1], [rating2]]; ranks=ranks,  min_delta = min_delta)
    return teams[1][1], teams[2][1]
end

function rate(ratings::Vector{Rating}; ranks=1:length(ratings), min_delta=DELTA)
    teams = rate(map(x -> [x], ratings); ranks=ranks, min_delta=min_delta)
    return map(x -> x[1], teams)
end


squaresneeded(grains) = grains > 0 ? trunc( log2(grains)+1 ) : 0


# This file includes an example file to simulate chaotic maps

using ChaoticCommunications
using Plots

# Generate data
cmaps = [Logistic(), Tent(), Logistic(), Henon(), Bernoulli()]
plt = plot(layout=(length(cmaps), 1))
for (i, cmap) in enumerate(cmaps)
    tx = trajectory!(cmap, 100.)
    plot!(tx, marker=(:circle, 1), subplot=i)
end
display(plt)


#Reference:http://docs.juliaplots.org/latest/

using Plots

# initialize the attractor
n = 1500
dt = 0.02
σ, ρ, β = 10., 28., 8/3
x, y, z = 1., 1., 1.

# initialize a 3D plot with 1 empty series
plt = path3d(1, xlim=(-25,25), ylim=(-25,25), zlim=(0,50),
                xlab = "x", ylab = "y", zlab = "z",
                title = "Lorenz Attractor", marker = 1)

# build an animated gif, saving every 10th frame
@gif for i=1:n
    dx = σ*(y - x)     ; x += dt * dx
    dy = x*(ρ - z) - y ; y += dt * dy
    dz = x*y - β*z     ; z += dt * dz
    push!(plt, x, y, z)
end every 10


using NearestNeighbors

"""
    initialdensity = estimatedensities(smd::SMD,
        clusterdata::Vector{Matrix{Float32}}, params::ParamStruct)

Estimate local emitter densities for clusters in `smd` and `clusterdata`.

# Description
The initial local densities `initialdensity` around each pre-cluster present in
`smd`/`clusterdata` are estimated based on the local density of pre-clusters
throughout the entire set of data as well as some of the rate parameters
provided in `params`.
"""
function estimatedensities(smd::SMD, clusterdata::Vector{Matrix{Float32}},
    params::ParamStruct)
    # Define some new parameters.
    dutycycle = params.k_on / (params.k_on+params.k_off+params.k_bleach)
    nclusters = length(clusterdata)
    if isempty(smd.nframes)
        maxframe = maximum(smd.framenum)
    else
        maxframe = smd.nframes
    end

    # If only one cluster is present, we should return an answer right away and stop.
    if nclusters == 1
        if size(clusterdata[1], 1) == 1
            initialdensity = 1
        else
            clusterarea = (maximum(clusterdata[1][:, 1])-minimum(clusterdata[1][:, 1])) *
                (maximum(clusterdata[1][:, 1])-minimum(clusterdata[1][:, 1]))
            initialdensity = (1/clusterarea) *
                ((params.k_bleach/params.k_off) / (1-params.p_miss)) /
                (1-exp(-params.k_bleach*dutycycle*(maxframe-1)))
        end
        return initialdensity
    end

    # Determine the center of all clusters assuming each arose from the same
    # emitter.
    clustercenters = zeros(2, nclusters)
    for nn = 1:nclusters
        # Isolate some arrays to improve readability.
        x = clusterdata[nn][:, 1]
        y = clusterdata[nn][:, 2]
        x_se = clusterdata[nn][:, 3]
        y_se = clusterdata[nn][:, 4]

        # Compute the cluster centers based on MLE of position.
        clustercenters[1:2, nn] = [sum(x./x_se.^2) / sum(1 ./ x_se.^2);
            sum(y./y_se.^2) / sum(1 ./ y_se.^2)]
    end

    # Estimate the local cluster density based on the distance to
    # nearest-neighbors.
    kneighbors = minimum([params.nnearestclusters nclusters-1])
    kdtree = KDTree(clustercenters)
    _, nndist = knn(kdtree, clustercenters, kneighbors+1)
    nndist = getindex.(nndist, 1:(kneighbors-1))
    clusterdensity = (kneighbors+1) ./ (pi*nndist.^2)

    # Estimate the density of underlying emitters based on cluster density.
    lambda1 = params.k_bleach * dutycycle
    lambda2 = (params.k_on+params.k_off+params.k_bleach) - lambda1
    initialdensity = clusterdensity *
        (1.0/dutycycle) * (1.0/params.k_off) * (1.0/(1.0-params.p_miss)) ./
        ((1.0/lambda1)*(1.0-exp(-lambda1*(maxframe-1.0))) -
        (1.0/lambda2)*(1.0-exp(-lambda2*(maxframe-1.0))))

    return initialdensity
end


module RelPerm

module Corey
	function S_wn(S_w, S_wir, S_nir)
		return (S_w - S_wir) / (1 - S_wir - S_nir)
	end
	function kr_w(S_w, S_wir, S_nir, N_w, k0_w)
		if S_w < S_wir
			return 0.0
		elseif S_w > 1 - S_nir
			return k0_w
		else
			return k0_w * (S_wn(S_w, S_wir, S_nir)) ^ N_w
		end
	end
	function kr_n(S_n, S_wir, S_nir, N_n, k0_n)
		if S_n < S_nir
			return 0.0
		elseif S_n > 1 - S_wir
			return k0_n
		else
			return k0_n * (1 - S_wn(1 - S_n, S_wir, S_nir)) ^ N_n
		end
	end
end

end


# generate higher order functions of the propensity to be expanded
function gen_fs(network::DiffEqBase.AbstractReactionNetwork, momentstruct::MomentStruct, k_orders::Vector{Int}, e_orders::Vector{Int}; algebra::Algebra=sympy())
	propensities = mass_rate_MA.(network.reactions, algebra=algebra)
	propensities .* α_coeff(momentstruct.species, k_orders, e_orders)
end

# expand higher order functions of the propensity
function expand_fs(momentstruct::MomentStruct, fs::Vector{T}; algebra::Algebra=sympy()) where T <: AlgebraSet
	e_of_fs = [taylor_expand(f, momentstruct, algebra=algebra) for f in fs]
end

# differentiate powers of first order raw moments w.r.t. time
function dα_dt(momentstruct::MomentStruct, dμ::Vector{T}, n_orders::Vector{Int}, k_orders::Vector{Int}; algebra::Algebra=sympy()) where T <: AlgebraSet
	dα = zero(algebra)
	for i in eachindex(n_orders)
		dα += (n_orders[i] - k_orders[i]) * (1/momentstruct.species[i]) * α_coeff(momentstruct.species, n_orders, k_orders) * dμ[i]
	end
	dα
end

# differentiate mixed raw moments w.r.t time and express in terms of central moments
function dβ_dt(network::DiffEqBase.AbstractReactionNetwork, momentstruct::MomentStruct, k_order::Vector{Int}; algebra::Algebra=sympy())
	eq = repeat([zero(algebra)], length(network.reactions))
	for e_order in Iterators.product([0:k_max for k_max in k_order]...)
		if sum(e_order) == 0
			continue
		end
		fs = gen_fs(network, momentstruct, k_order, collect(e_order), algebra=algebra)
		e_of_fs = expand_fs(momentstruct, fs, algebra=algebra)
		coeffs = [binomial_coeff(k_order, collect(e_order)) * stoich_coeff(momentstruct.species, reaction, collect(e_order)) for reaction in network.reactions]
		eq += coeffs .* e_of_fs
	end
	sum(eq)
end

# central moments. For details of calculations, see https://arxiv.org/abs/1303.5848
function dcen_dt(network::DiffEqBase.AbstractReactionNetwork, momentstruct::MomentStruct, dμ::Vector{T}; algebra::Algebra=sympy()) where T <: AlgebraSet
	dcen = repeat([zero(algebra)], length(momentstruct.cen_moments) - momentstruct.num_species - 1)
	counter = 0
	for n_order in keys(momentstruct.cen_moments)
		if sum(n_order) <= 1
			continue
		else
			counter += 1
			for k_order in Iterators.product([0:n_max for n_max in n_order]...)
				__k = collect(k_order)
				β = momentstruct.raw_moments[__k]
				α = α_coeff(momentstruct.species, n_order, __k)
				dβ = dβ_dt(network, momentstruct, __k, algebra=algebra)
				dα = dα_dt(momentstruct, dμ, n_order, __k, algebra=algebra)
				nck = binomial_coeff(n_order, __k)
				__sign = sign_coeff(n_order, __k)
				__β = raw_to_cen(momentstruct, __k)
				dcen[counter] += nck*__sign*(α*dβ+__β*dα)
			end
		end
	end
	algebra.expand.(dcen)
end


# This simulation is referenced from
# [《量子力学本征值问题的数值方法——打靶法与Numerov方法》](https://zhuanlan.zhihu.com/p/59099100).
using LinearAlgebra: dot

using Roots
using SpecialMatrices: Strang

using NumericalMethodsInQuantumMechanics.EigenvalueProblems
using NumericalMethodsInQuantumMechanics.EigenvalueProblems.NumerovMethod
using NumericalMethodsInQuantumMechanics.EigenvalueProblems.ShootingMethod

x = range(0; stop = 1, step = 0.002)
n = length(x)
g = zeros(n)
s = zeros(n)
problem = setup_problem(BoundaryCondition(0.0, 1.0), x, g, s)
yd0 = shoot(problem, Bisection(), [0, 1000])
ψ = integrate(InitialCondition(0.0, yd0), x, g, s)
e = dot(ψ, Strang(n) * ψ)


using HOODESolver: PolyExp, coeffs, derivative, integrate
using Test
function testPolyExp()

    @time @testset "test PolyExp" begin
        pol = PolyExp(convert( Vector{Complex{Rational{BigInt}}}, [2//3, 5//23, 57//4, 1//5]), big(im//7), big(1//10+0im) )

        c_res = coeffs(pol)

 #       println("pol=$(string(pol))")

        @test [2//3, 5//23, 57//4, 1//5, im//7, 1//10 ] == c_res

        polder = derivative(pol)

        c_resder = coeffs(polder)

        @test [2im//21+5//23, 5im//(23*7)+ 57//2, 57im//(4*7)+3//5, 1im//35,  im//7, 1//10 ] == c_resder

        polintder = integrate(polder)
        c_resintder = coeffs(polintder)

        @test c_res == c_resintder

        p1 = PolyExp([1.0, 2, 3], 3.4, 1.1)
        p2 = PolyExp([0.5, -1, 1], 1.3, 0.245)

        c_resmul = coeffs(p1*p2)
        @test isapprox([0.5, 0.0, 0.5, -1.0, 3.0, 4.7, 1.345], c_resmul, atol=1e-15)

        p3 = PolyExp([0.2, 0, 3.0, 2.0], 1.3, 0.245)

        c_resadd = coeffs(p2 + p3)
        @test isapprox([0.7, -1.0, 4.0, 2.0, 1.3, 0.245], c_resadd, atol=1e-15)
    end
end

testPolyExp()


using Plots, BenchmarkTools
Threads.nthreads()
function juliaset_pixel(z₀, c)
    z = z₀
    for i in 1:255
        abs2(z)> 4.0 && return (i - 1)%UInt8
        z = z*z + c
    end
    return UInt8(255)
end

function juliaset_column!(img, c, n, j)
    x = -2.0 + (j-1)*4.0/(n-1)
    for i in 1:n
        y = -2.0 + (i-1)*4.0/(n-1)
        @inbounds img[i,j] = juliaset_pixel(x+im*y, c)
    end
    nothing
end

function juliaset_single!(img, c, n)
    for j in 1:n
        juliaset_column!(img, c, n, j)
    end
    nothing
end

function juliaset(x, y, n=1000, method = juliaset_single!, extra...)
    c = x + y*im
    img = Array{UInt8,2}(undef,n,n)
    method(img, c, n, extra...)
    return img
end

# frac = juliaset(-0.79, 0.15)
# plot(heatmap(1:size(frac,1),1:size(frac,2), frac, color=:Spectral))

@btime juliaset(-0.79, 0.15);

function juliaset_static!(img, c, n)
    Threads.@threads for j in 1:n
        juliaset_column!(img, c, n, j)
    end
    nothing
end

@btime juliaset(-0.79, 0.15, 1000, juliaset_static!);

using Folds
function juliaset_folds!(img, c, n)
    Folds.foreach(j -> juliaset_column!(img, c, n, j), 1:n)
    nothing
end
julia> @btime juliaset(-0.79, 0.15, 1000, juliaset_folds!);
  16.267 ms (25 allocations: 978.20 KiB)

function juliaset_folds!(img, c, n, nt)
    parts = collect(Iterators.partition(1:n, cld(n, nt)))
    Folds.foreach(parts) do ii
        foreach(j ->juliaset_column!(img, c, n, j), ii)
    end
    nothing
end
julia> @btime juliaset(-0.79, 0.15, 1000, (args...) -> juliaset_folds!(args..., 16));
  16.716 ms (25 allocations: 978.61 KiB)

using FLoops, FoldsThreads
function juliaset_folds!(img, c, n)
    @floop ThreadedEx(basesize = 2) for j in 1:n
        juliaset_column!(img, c, n, j)
    end
    nothing
end
@btime juliaset(-0.79, 0.15, 1000, juliaset_folds!);


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.8.0-DEV
#     language: julia
#     name: julia-1.8
# ---

# %%
using Memoization
using SpecialFunctions
fact(n) = gamma(n + 1)

# %%
fact(100)

# %%
factorial(big(100)) |> Float64

# %%
coeff_sin(n) = Tuple((-1)^k/fact(2k+1) for k in 0:n-1)
taylor_odd(x, c) = x*evalpoly(x^2, c)

# %%
c = coeff_sin(8)
x = π/4
@show sin(x)
[(n, taylor_odd(x, coeff_sin(n))) for n in 1:9]

# %%


import Random
Random.seed!(1337)

println("\n")

sp = sptenrand([10, 10, 10], 0.01)
dense = Array(sp)

println("... Testing sparse and dense tensor product equivalence.")
@test ttt(sp,sp) == ttt(dense,dense)

println("... Testing sparse tensor decomposition")
@test cp_als(reduce(ttt,SparseTensor.([[0, 0, 1],[0, 1, 0],[1, 0, 0]])),1) == ktensor([[0 0 1],[0 1 0],[1 0 0]].|>permutedims .|> x->x .|> Float64)

println("... Testing sparse tensor broadcast")
@test SparseTensor([0 0 1]) .* SparseTensor([1 0 0]) == [0 0 0]


#=
# 125: Terminal flux calculation via test functions
([source code](SOURCE_URL))

For a rather comprehensive explanation
see [225: Terminal flux calculation via test functions, nD](@ref)
=#

module Example125_TestFunctions1D
using Printf
using VoronoiFVM
using ExtendableGrids
using GridVisualize

function main(;n=100,Plotter=nothing,verbose=false,unknown_storage=:sparse)
    h=1/n
    grid=VoronoiFVM.Grid(collect(0:h:1))

    eps::Vector{Float64}=[1,1.0e-1]

    physics=VoronoiFVM.Physics(
         num_species=2,
    reaction=function(f,u,node)
        f[1]=10*(u[1]-u[2])
        f[2]=10*(u[2]-u[1])
    end,

    flux=function(f,u0,edge)
        u=unknowns(edge,u0)
        f[1]=eps[1]*(u[1,1]-u[1,2])
        f[2]=eps[2]*(u[2,1]-u[2,2])
    end,

    storage=function(f,u,node)
        f[1]=u[1]
        f[2]=u[2]
    end
    )
    sys=VoronoiFVM.System(grid,physics,unknown_storage=unknown_storage)

    enable_species!(sys,1,[1])
    enable_species!(sys,2,[1])

    boundary_neumann!(sys,1,1,0.01)
    boundary_dirichlet!(sys,2,2,0.0)

    factory=VoronoiFVM.TestFunctionFactory(sys)
    tf1=testfunction(factory,[2],[1])
    tf2=testfunction(factory,[1],[2])

    U=unknowns(sys)
    inival=unknowns(sys)
    inival[2,:].=0.1
    inival[1,:].=0.1

    control=VoronoiFVM.NewtonControl()
    control.verbose=verbose
    control.damp_initial=0.1
    I1=0
    p=GridVisualizer(Plotter=Plotter,layout=(2,1))
    for xeps in [1.0,0.1,0.01]
        eps=[xeps,xeps]
        solve!(U,inival,sys,control=control)
        I1=integrate(sys,tf1,U)
        coord=coordinates(grid)
        inival.=U
        scalarplot!(p[1,1],grid,U[1,:])
        scalarplot!(p[2,1],grid,U[2,:])
        reveal(p)
        u5=U[5]
    end
    return I1[1]
end

function test()
    main(unknown_storage=:sparse) ≈ 0.01 &&
        main(unknown_storage=:dense) ≈ 0.01
end
end


#=
Testing of frequencyfilt.jl

Hard to test these functions other than visually.  This script simply
tries to run then all to make sure that they at least run

Set disp = true to display images for visual verification
=#

disp = false

using ImagePhaseCongruency, Test, TestImages, AbstractFFTs

if disp
    using PyPlot
end

println("Testing frequencyfilt...")

function mypause()
    println("Hit return to continue ")
    a = readline()
    return nothing
end

if disp; PyPlot.set_cmap(PyPlot.ColorMap("gray")); end

lena = Float64.(testimage("lena_gray"))

rows = 101
cols = 200
(f, fx, fy) = filtergrids((rows, cols))
if disp; imshow(f); title("filtergrids() f"); mypause(); end

if disp; imshow(fx); title("filtergrids() fx"); mypause(); end
if disp; imshow(fy); title("filtergrids() fy"); mypause(); end

f = filtergrid((rows, cols))
if disp; imshow(f); title("filtergrid() f"); mypause(); end

(H1, H2, f) = monogenicfilters((rows, cols))
if disp; imshow(real(-im*H1)); title("H1"); mypause(); end
if disp; imshow(real(-im*H2)); title("H2"); mypause(); end
if disp; imshow(f); title("monogenicfilters() f"); mypause(); end

(H, f) = packedmonogenicfilters((rows, cols))
if disp; imshow(real(H)); title("packedmonogenicfilters() H1"); mypause(); end
if disp; imshow(imag(H)); title("packedmonogenicfilters() H2"); mypause(); end

sze = (rows,cols)
cutin = 0.1
cutoff = 0.2
n = 2
boost = 2

f = lowpassfilter(sze, cutoff, n)
if disp; imshow(f); title("lowpassfilter() cutoff 0.2"); mypause(); end

f = bandpassfilter(sze, cutin, cutoff, n)
if disp; imshow(f); title("bandpassfilter() 0.1 0.2"); mypause(); end

f = highboostfilter(sze, cutoff, n, boost)
if disp; imshow(f); title("highboostfilter() 0.2 "); mypause(); end

f = highpassfilter(sze, cutoff, n)
if disp; imshow(f); title("highpassfilter() 0.2"); mypause(); end

f = 0.2
fo = 0.3
sigmaOnf = 0.55
@test abs(loggabor(0, fo, sigmaOnf) - 0) < eps()
@test abs(loggabor(fo, fo, sigmaOnf) - 1) < eps()

(f, fx, fy) = filtergrids((rows, cols))
(sintheta, costheta) = gridangles(f, fx, fy)
if disp; imshow(sintheta); title("filtergrids() sintheta"); mypause(); end
if disp; imshow(costheta); title("filtergrids() costheta"); mypause(); end

angl = pi/4
wavelen = pi/8
flter = cosineangularfilter(angl, wavelen, sintheta, costheta)
if disp; imshow(flter); title("cosineangularfilter"); mypause(); end

thetaSigma = .4
flter = gaussianangularfilter(angl, thetaSigma, sintheta, costheta)
if disp; imshow(flter); title("gaussianangularfilter"); mypause(); end

(P, S, p, s) = perfft2(lena)
if disp; imshow(lena); title("lena"); mypause(); end
if disp; imshow(p); title("periodic lena"); mypause(); end
if disp; imshow(s); title("lena - periodic lena"); mypause(); end

s1 = geoseries(0.5, 2, 4)
s2 = geoseries((0.5, 4), 4)
@test sum(abs.(s1 .- [0.5000,    1.0000,    2.0000,    4.0000])) < eps()
@test sum(abs.(s2 .- [0.5000,    1.0000,    2.0000,    4.0000])) < eps()

nothing


# Script to generate a regular grid of lines in ply format for testing

f = open("grid.ply", "w")

N = 7

write(f, """
ply
format ascii 1.0
element vertex $(N^3)
property float x
property float y
property float z
element color $(N^3)
property float r
property float g
property float b
element edge $(3*N^2*(N-1))
property list uchar int vertex_index
end_header
""")

# Vertex positions
for i=1:N
    for j=1:N
        for k=1:N
            write(f, "$i $j $k\n")
        end
    end
end

# Vertex colors
for i=1:N
    for j=1:N
        for k=1:N
            r = rand()
            gb = rand()
            if gb > r
                gb = r
            end
            write(f, "$r $gb $gb\n")
        end
    end
end

# Edges
strides = [1,N,N*N]
for axis=0:2
    for i=1:N, j=1:N, k=1:N-1
        i1 = sum(strides .* (circshift([i,j,k], axis) - 1))
        i2 = sum(strides .* (circshift([i,j,k+1], axis) - 1))
        write(f, "2 $i1 $i2\n")
    end
end

close(f)


#=Copyright (c) 2017 Gabriel Goh

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.=#

n = 50
κ = 1000
A = SymTridiagonal(2*ones(n), -ones(n-1))
A = (κ-1)*A/4 + eye(n)
cond(A)
b = zeros(n); b[1] = (κ - 1)/4

Λ, Q = eig(A)

κ = Λ[1]/Λ[n]
α = 2/(Λ[1] + Λ[end])
β = 0.98
#β = 0

# Function from momentum2.jl
Z = cat(2,[ runmomentumcheat(A,b,i)[2] for i = 1:400]...)
heatmap(Z)

plot(b2)
plot!(A\b)


#####################################################
# Set-based coverage.
#
# This is an example of another data structure to
# support generation of test cases. It's a set of sets
# of the remaining covers.
#####################################################

mutable struct SetCoverage{T <: Integer}
    cover::Dict{Tuple{Vararg{T}},Set{Tuple{Vararg{T}}}}
    arity::Array{T, 1}
end

function SetCoverage(arity::Array{T}, n_way::Integer) where {T <: Integer}
    param_cnt = length(arity)
    param_combo = [Tuple(x) for x in combinations(1:param_cnt, n_way)]
    # The dictionary with keys = parameter indices and dictionary
    # value is set of tuples of parameter values.
    cover = Dict(x => Set{NTuple{n_way,T}}() for x in param_combo)
    SetCoverage{T}(cover, arity)
end

eltype(sc::SetCoverage{T}) where T = T
remaining(sc::SetCoverage) = sum([length(aset) for aset in values(sc.cover)])

function build_all_combinations!(sc::SetCoverage, n_way)
    allc = all_combinations(sc.arity, n_way)
    param_cnt = size(allc, 1)
    for cover_idx in 1:size(allc, 2)
        acover = allc[:, cover_idx]
        nonzero = acover .!= 0
        indices = tuple((1:param_cnt)[nonzero]...)
        values = tuple(acover[nonzero]...)
        if indices ∉ keys(sc.cover)
            sc.cover[indices] = Set{NTuple{n_way, Int}}()
        end
        push!(sc.cover[indices], values)
    end
end

function add_coverage!(sc::SetCoverage, entry::Array{T, 1}) where T
    add_cnt = 0  # Record the number removed for visibility.
    for indices in keys(sc.cover)
        value = tuple([entry[x] for x in indices]...)
        if value ∈ sc.cover[indices]
            pop!(sc.cover[indices], value)
            add_cnt += 1
        end
    end
    add_cnt
end

function add_coverage!(sc::SetCoverage, entries::Array{T, 2}) where T
    add_cnt = 0  # Record the number removed for visibility.
    for entry_idx in 1:size(entries, 2)
        add_cnt += add_coverage!(sc, entries[:, entry_idx])
    end
    add_cnt
end

function test_coverage(test_cases, arity, n_way)
    sc = SetCoverage(arity, n_way)
    build_all_combinations!(sc, n_way)
    start = remaining(sc)
    add_coverage!(sc, test_cases)
    finish = remaining(sc)
    (start = start, finish = finish)
end

"""
    tuples_in_trials(trials, n_way)

Find the number of distinct tuples in a set of trials. This uses a different method
to count and store those tuples, as a check on other methods. It returns a dictionary
where each key is the index of non-zero parameters and the value is the set of all
ways those values appear.
"""
function tuples_in_trials(trials, n_way)
    param_cnt = length(trials[1])
    param_combo = [Tuple(x) for x in combinations(1:param_cnt, n_way)]
    # The dictionary with keys = parameter indices and dictionary
    # value is set of tuples of parameter values.
    seen = Dict(x => Set{NTuple{n_way,Int}}() for x in param_combo)
    for trial_idx in 1:length(trials)
        entry = trials[trial_idx]
        for params in param_combo
            values = entry[[x for x in params]]
            # values can be missing, in which case this isn't a tuple.
            if !any(values .== 0)
                push!(seen[params], Tuple(values))
            end
        end
    end
    seen
end

function coverage_by_tuple(trials, n_way)
    accumulated = tuples_in_trials(trials, n_way)
    sum([length(aset) for aset in values(accumulated)])
end


using fci
using JLD2

@load "_testdata_h4_triplet_ymatrix.jld2"

norbs = 4
nalpha = 3
nbeta = 1

@testset "make xy for grms" begin
    n_unocc_a = (norbs-nalpha)+1
    n_unocc_b = (norbs-nbeta)+1

    #make x matricies
    xalpha = zeros(n_unocc_a, nalpha+1)
    xbeta = zeros(n_unocc_b, nbeta+1)
    #fill first row and columns
    xalpha[:,1] .= 1
    xbeta[:,1] .= 1
    xalpha[1,:] .= 1
    xbeta[1,:] .= 1

    for i in 2:nalpha+1
        for j in 2:n_unocc_a
            xalpha[j, i] = xalpha[j-1, i] + xalpha[j, i-1]
        end
    end

    for i in 2:nbeta+1
        for j in 2:n_unocc_b
            xbeta[j, i] = xbeta[j-1, i] + xbeta[j, i-1]
        end
    end

    #make y matrices
    copya = deepcopy(xalpha)
    copyb = deepcopy(xbeta)
    arraya = zeros(size(xalpha)[2])
    arrayb = zeros(size(xbeta)[2])
    yalpha_test = vcat(transpose(arraya), xalpha[1:size(xalpha)[1]-1, :])
    ybeta_test = vcat(transpose(arrayb), xbeta[1:size(xbeta)[1]-1, :])#=}}}=#
    @test isapprox(ya, yalpha_test, atol=0.05)
    @test isapprox(yb, ybeta_test, atol=0.05)
end


# Load Julia packages (libraries) needed  for the snippets in chapter 0

using DynamicHMCModels

# CmdStan uses a tmp directory to store the output of cmdstan

ProjDir = @__DIR__
cd(ProjDir)

# Read the milk data

df = DataFrame(CSV.read(joinpath("..", "..", "data", "milk.csv"), delim=';'))
df = filter(row -> !(row[:neocortex_perc] == "NA"), df)
#df[:, :kcal_per_g] = convert(Vector{Float64}, df[:, :kcal_per_g])
df[!, :log_mass] = log.(convert(Vector{Float64}, df[:, :mass]))

# Define the model struct

Base.@kwdef mutable struct MilkModel{Ty <: AbstractVector, Tx <: AbstractMatrix}
    "Observations."
    y::Ty
    "Covariates"
    x::Tx
end

# Write a function to return properly dimensioned transformation.

function make_transformation(model::MilkModel)
  as((β = as(Array, size(model.x, 2)), σ = asℝ₊))
end

# Instantiate the model with data and inits.

x = hcat(ones(size(df, 1)), df[:, :log_mass]);
model = MilkModel(;y=df[:, :kcal_per_g], x=x)

# Make the type callable with the parameters *as a single argument*.

function (model::MilkModel)(θ)
    @unpack y, x, = model   # extract the data
    @unpack β, σ = θ            # works on the named tuple too
    ll = 0.0
    ll += logpdf(Normal(0, 100), x[1])
    ll += logpdf(Normal(0, 1), x[2])
    ll += logpdf(TDist(1.0), σ)
    ll += loglikelihood(Normal(0, σ), y .- x*β)
    ll
end

println()
model((β = [1.0, 2.0], σ = 1.0))
println()

# Wrap the problem with a transformation, then use Flux for the gradient.

P = TransformedLogDensity(make_transformation(model), model)
∇P = ADgradient(:ForwardDiff, P);

# Tune and sample.

results = mcmc_with_warmup(Random.GLOBAL_RNG, ∇P, 1000)
posterior = P.transformation.(results.chain)

println()
DynamicHMC.Diagnostics.EBFMI(results.tree_statistics) |> display
println()

DynamicHMC.Diagnostics.summarize_tree_statistics(results.tree_statistics) |> display
println()

a3d = Array{Float64, 3}(undef, 1000, 3, 1);
for j in 1:1
  for i in 1:1000
    a3d[i, 1:2, j] = values(posterior[i].β)
    a3d[i, 3, j] = values(posterior[i].σ)
  end
end

pnames = ["β[1]", "β[2]", "σ"]
sections =   Dict(
  :parameters => pnames,
)
chns = create_mcmcchains(a3d, pnames, sections, start=1);
chns = set_names(chns, Dict("β[1]" => "α", "β[2]" => "β"))

stan_result = "
Iterations = 1:1000
Thinning interval = 1
Chains = 1,2,3,4
Samples per chain = 1000

Empirical Posterior Estimates:
          Mean         SD        Naive SE       MCSE      ESS
    a  0.70472876 0.057040655 0.00090189195 0.0011398893 1000
   bm -0.03150330 0.023642759 0.00037382484 0.0004712342 1000
sigma  0.18378372 0.039212805 0.00062000888 0.0011395979 1000

Quantiles:
          2.5%       25.0%       50.0%        75.0%       97.5%
    a  0.59112968  0.66848775  0.70444950  0.741410500 0.81915225
   bm -0.07729257 -0.04708425 -0.03104865 -0.015942925 0.01424901
sigma  0.12638780  0.15605950  0.17800600  0.204319250 0.27993590
";

describe(chns)

# End of `05/5.6d.jl`


function morsefreq(c::CWT{W,T,Morse,N}) where {W,T,N}

    # measures of frequency for generalized Morse wavelet. [with F. Rekibi]
    # the output returns the modal or peak

    # For be=0, the "wavelet" becomes an analytic lowpass filter

    # Lilly and Olhede (2009).  Higher-order properties of analytic wavelets.
    # IEEE Trans. Sig. Proc., 57 (1), 146--160.

    ga = c.waveType.ga;
    be = c.waveType.be;

    fm = exp.((log.(be) - log.(ga)) ./ ga);

    if sum(be.==0) != 0 && size(fm) == ()
        fm = (log(2))^(1 / ga);
    elseif sum(be.==0) != 0 && size(fm) != ()
        fm[be.==0] = (log(2))^(1 / ga[be.==0]);
    end

    fm = fm / (2 * pi);

    return fm

end

"""
    nOctaves, totalWavelets, sRanges, sWidth = getNWavelets(n1,c)

utility for understanding the spacing of the wavelets. `sRanges` is a list of
the s values used in each octave. sWidth is a list of the corresponding
variance adjustments
"""
function getNWavelets(n1, c::CWT)
    nOctaves = getNOctaves(n1, c)
    n,nSpace = setn(n1,c)
    isAve = !(typeof(c.averagingType) <: NoAve)
    if nOctaves ≤ c.averagingLength + getMinScaling(c) # there isn't enough space for anything but averaging.
        totalWavelets = isAve
        sRanges = [1.0]
        sWidth = [1.0]
        return nOctaves, totalWavelets, sRanges, sWidth
    end
    sRange = 2 .^ (polySpacing(nOctaves, c))
    totalWavelets = round(Int, length(sRange) + isAve)
    sWidth = varianceAdjust(c,totalWavelets,nOctaves)
    return nOctaves, totalWavelets, sRange, sWidth
end

"""
different wavelet familes need to end at a different number of octaves because they have different tail behavior
"""
getNOctaves(n1,c::CWT{W,T, M, N}) where {W, T, N, M} = log2(n1>>1+1) + c.extraOctaves
# choose the number of octaves so the last mean, which is at s*σ[1]
# is 3 standard devations away from the end
getNOctaves(n1,c::CWT{W,T, Morlet, N}) where {W, T, N} = log2((n1>>1+1)/(c.σ[1]+3)) + c.extraOctaves
getNOctaves(n1,c::CWT{W,T, <:Paul, N}) where {W, T, N} = log2((n1>>1+1)/(2c.α+5)) + c.extraOctaves
# choose the number of octaves so the last mean is 5 standard deviations from the end
function getNOctaves(n1,c::CWT{W,T, <:Dog, N}) where {W, T, N}
    μ = getMean(c)
    σ = getStd(c)
    log2(n1>>1/(μ+5σ)) + c.extraOctaves
end
# choose the number of octaves so the smallest support is twice the qmf
getNOctaves(n1,c::CWT{W,T, <:ContOrtho, N}) where {W, T, N} = log2(n1) - 2 - log2(length(qmf(c.waveType))) + c.extraOctaves
getNOctaves(n1,c::CWT{W,T, Morse, N}) where {W, T, N} = log2((n1>>1+1)/(morsefreq(c) + 1)) + c.extraOctaves
# getNOctaves(n1,c::CWT{W,T, Morse, N}) where {W, T, N} = 4 + c.extraOctaves

"""
As with the last octave, different wavelet families have different space decay rates, and in the case of symmetric or zero padding we don't want wavelets that bleed across the boundary from the center.
"""
getMinScaling(c::CWT{W,T,M,N}) where {W,T,N,M} = 0 # by default all scales are allowed (all of the orthogonal transforms)
getMinScaling(c::CWT{W,T,<:Morlet,N}) where {W,T,N} = 1/(c.β)^.8 # morlet is slightly too large at the boundaries by default
getMinScaling(c::CWT{W,T,<:Paul,N}) where {W,T,N} = 2/(2c.α+1) # Paul presents some difficulties, as the decay changes quickly (like 1/t^(α+1))
getMinScaling(c::CWT{W,T,<:Dog,N}) where {W,T,N} = 2 # like morlet, the decay for Dog is exponential and consistent across derivatives
# getMinScaling(c::CWT{W,T,<:Morse,N}) where {W,T,N,M} = log2(2 * morsefreq(c))
getMinScaling(c::CWT{W,T,<:Morse,N}) where {W,T,N,M} = 5 * morsefreq(c)

function varianceAdjust(this::CWT{W,T, M, N}, totalWavelets, nOct) where {W,T,N, M}
    # increases the width of the wavelets by σ[i] = (1+a(total-i)ᴾ)σₛ
    # which is a polynomial of order p going from 1 at the highest frequency to
    # sqrt(p) at the lowest
    β = this.β
    x = exp(nOct)/(100+exp(nOct)) # the fewer octaves there are, the smaller the width adjustment we need
    a = (β^x-1)/(totalWavelets-1)^β
    sWidth = 1 .+a .*(totalWavelets .- (1:totalWavelets)).^β
    if any(isnan.(sWidth))
        return [1]
    end
    return sWidth
end

function polySpacing(nOct, c)
    a = max(getMinScaling(c) + c.averagingLength, 0)
    O = nOct
    β = c.β
    Q = c.Q
    if O ≤ a
        # averagingLength and the min scaling are too large for anything to be done
        return [1.0]
    elseif 0 < O - a ≤ 1
        # there's only one octave, just return the linear rate of Q
        return range(a, O, length=1+round(Int, Q))
    end
    # x is the index, y is the scale
    # y= b*x^(1/β), solve for a and b with
    # (x₀,y₀)=(1, aveLength + minScale)
    # dy/dx = 1/s, so the Quality factor gives the slope at the last frequency
    b = (β/Q)^(1/β) * (O)^((β-1)/β)
    # the point x so that the second condition holds
    lastWavelet = Q * (O)/β
    # the point so that the first wavelet is at a
    firstWavelet = (a/b)^β
    # step size so that there are actually Q wavelets in the last octave
    startOfLastOctave = ((nOct-1)/b)^β
    stepSize = (lastWavelet - startOfLastOctave)/Q
    samplePoints = range(firstWavelet, stop=lastWavelet,
                         step = stepSize)#, length = round(Int, O * Q^(1/p)))
    return b .* (samplePoints).^(1/β)
end

# a utility to just get the start, stop, and step size used in polySpacing. Only used for explanatory purposes
function genSamplePoints(nOct, c)
    a = getMinScaling(c) + c.averagingLength
    O = nOct
    β = c.β
    Q = c.Q
    if O ≤ a
        # averagingLength and the min scaling are too large for anything to be done
        return [1.0]
    elseif 0 < O - a ≤ 1
        # there's only one octave, just return the linear rate of Q
        return range(a, O, length=1+round(Int, Q))
    end
    # x is the index, y is the scale
    # y= b*x^(1/β), solve for a and b with
    # (x₀,y₀)=(1, aveLength + minScale)
    # dy/dx = 1/s, so the Quality factor gives the slope at the last frequency
    b = (β/Q)^(1/β) * (O)^((β-1)/β)
    # the point x so that the second condition holds
    lastWavelet = Q * (O)/β
    # the point so that the first wavelet is at a
    firstWavelet = (a/b)^β
    # step size so that there are actually Q wavelets in the last octave
    startOfLastOctave = ((nOct-1)/b)^β
    stepSize = (lastWavelet - startOfLastOctave)/Q
    firstWavelet, lastWavelet, stepSize
end

"""

adjust the length of the storage based on the boundary conditions
"""
function setn(n1, c)
    if boundaryType(c) <: ZPBoundary
        base2 = ceil(Int,log2(n1));   # power of 2 nearest to n1
        nSpace = 2^(base2)
        n = nSpace>>1 + 1
    elseif boundaryType(c) <: SymBoundary
        # n1+1 rather than just n1 because this is going to be used in an rfft
        # for real data
        n = n1 + 1
        nSpace = 2*n1
    elseif boundaryType(c) <: PerBoundary
        n= n1>>1 + 1
        nSpace = n1
    else
        error("No such boundary as $(boundaryType(c))")
    end
    return n, nSpace
end

# computing averaging function utils

# this makes sure the averaging function is real and positive
adjust(c::CWT) = 1
adjust(c::CWT{W, T, <:Dog}) where {W,T} = 1/(im)^(c.α)

function locationShift(c::CWT{W, T, <:Morlet, N}, s, ω, sWidth) where {W,T,N}
        s0 = 3*c.σ[1] *s*sWidth
        ω_shift = ω .+ c.σ[1] * s0
    return (s0, ω_shift)
end

function locationShift(c::CWT{W, T, <:Dog, N}, s, ω, sWidth) where {W,T,N}
    μNoS = getMean(c)
    μLast = μNoS * (2s)
    s0 = μLast / 2 / getStd(c)
    μ = μNoS * s0
    ω_shift = ω .+ μ
    return (s0, ω_shift)
end

function locationShift(c::CWT{W, T, <:Morse, N}, s, ω, sWidth) where {W,T,N}
        # s0 = c.waveType.cf * s * sWidth
        s0 = morsefreq(c) * s * sWidth
        # ω_shift = ω .+ c.waveType.cf * s0
        ω_shift = ω .+ s0
    return (s0, ω_shift)
end

"""
get the mean of the mother wavelet where s=1
"""
function getMean(c::CWT{W, T, <:Dog},s=1) where {W,T}
    Gm1 = gamma((c.α+1)/2)
    Gm2 = gamma((c.α+2)/2)
    Gm2 / Gm1 * sqrt(2) * s^2
end
getMean(c::CWT{W,T,<:Paul}, s=1) where {W,T} = (c.α+1)*s
function getMean(c::CWT{W, T, <:Morlet},s=1) where {W,T}
    return s*c.σ[1]
end
function getMean(c::CWT{W, T, <:Morse},s=1) where {W,T}
    #return s*c.waveType.cf
    return s*morsefreq(c)
end
"""
    getStd(c::CWT{W, T, <:Dog}, s=1) where {W,T}
get the standard deviation of the mother wavelet
"""
getStd(c::CWT{W, T, <:Dog}, s=1) where {W,T} = sqrt(c.α + 1 - getMean(c)^2)*s^(3/2)
getStd(c::CWT{W, T, <:Paul},s=1) where {W, T} = sqrt((c.α+2)*(c.α+1)) * s
getStd(c::CWT{W, T, <:Morlet},s=1) where {W,T} = (s * c.β^.8)/sqrt(2)
getStd(c::CWT{W, T, <:Morse},s=1) where {W,T} = (s * c.β^.8)/sqrt(2)

function locationShift(c::CWT{W, T, <:Paul, N}, s, ω, sWidth) where {W,T,N}
    s0 = s*sqrt(c.α+1)
    ω_shift = ω .+ (c.α .+ 1) * s0
    return (s0, ω_shift)
end

function getUpperBound(c::CWT{W, T, <:Morlet, N}, s) where {W,T,N}
    return c.σ[1] * s
end

function getUpperBound(c::CWT{W, T, <:Dog, N}, s) where {W,T,N}
    return sqrt(2)* s * gamma((c.α+2)/2) / gamma((c.α+1)/2)
end

function getUpperBound(c::CWT{W, T, <:Paul, N}, s) where {W,T,N}
    return (c.α + 1) * s
end

"""
    arrayOfFreqs = getMeanFreq(Ŵ, δt=1000)
Calculate each of the mean frequencies of a collection of analytic or real wavelets Ŵ. assumes a sampling rate of 2kHz, so the maximum frequency is 1kHz. Change δt to adjust to your problem.
"""
function getMeanFreq(Ŵ, δt=2000)
    eachNorm = [norm(w,1) for w in eachslice(Ŵ,dims=ndims(Ŵ))]'
    freqs = range(0,δt/2, length= size(Ŵ,1))
    dropdims(sum(freqs .* abs.(Ŵ), dims=1) ./ eachNorm,dims=1)
end

# orthogonal wavelet utils
function getContWaveFromOrtho(c,N)
    depth, ψLen = calcDepth(c,N)
    φ, ψ, t = makewavelet(c.waveType.o, depth)
    return φ, ψ, ψLen
end

"""
    nIters, sigLength = calcDepth(w,N)
given a `CWT` with an orthogonal wavelet type, calculate the depth `nIters`
necessary to get a resulting wavelet longer than `N`. `sigLength` is the
resulting length.
"""
function calcDepth(w, N)
    origLen = length(qmf(w.waveType))
    netLen = origLen
    nIters = 0
    if N < netLen
        return nIters
    end
    while N > netLen - 2^nIters + 1
        netLen = nextLength(netLen, origLen)
        nIters += 1
    end
    return (nIters, netLen - 2^nIters + 1)
end
# the actual length calculator, as this is recursive
nextLength(curr,orig) = curr*2 + orig - 1 # upsample then convolve
# pad a vector `v` to have length `N`. Or chop off from both sides if its too long
function padTo(v, N)
    nDiff = N - length(v)
    if nDiff > 0 # there should be more entries than there are
        cat(v, zeros(eltype(v), max(N-length(v),0)), dims = 1)
    elseif nDiff ==0
        return v
    else # there are too many, so chop off half the difference from each side
       inds = ceil(Int, 1-nDiff/2):length(v)+ceil(Int,nDiff/2)
       return v[inds]
    end
end

# create interpolater for the orthogonal cases
genInterp(ψ) = interpolate(ψ, BSpline(Quadratic(Reflect(OnGrid()))))

"""
    β = computeDualWeights(Ŵ)
compute the weight given to each wavelet so that in the Fourier domain, the sum across wavelets is as close to 1 at every frequency below the peak of the last wavelet (that is `β' .*abs.(Ŵ[1:lastFreq,:]) ≈ ones(lastFreq)` in an ℓ^2 sense)
"""
function computeDualWeights(Ŵ, wav)
    @views lastReasonableFreq = computeLastFreq(Ŵ[:,end], wav)
    Wdag = pinv(Ŵ[1:lastReasonableFreq,:])
    β = conj.(Wdag * ones(size(Wdag,2)))'
    return β
end
# function computeDualWeights(Ŵ, wav::CWT{W,T,<:ContOrtho,N}, n,naive=true) where {W,T,N}
#     β = computeNaiveDualWeights(Ŵ, wav, n)
#     return β
# end
computeLastFreq(ŵ, wav) = length(ŵ) - 3 # we have constructed the wavelets so the last frequency is <1%, so trying to get it to 1 will fail
computeLastFreq(ŵ, wav::CWT{W,T,<:Morlet}) where {W,T} = findlast(abs.(ŵ) .> 1/4 * maximum(abs.(ŵ))) # the gaussian decay makes this way too large by the end

"""
    computeNaiveDualWeights(Ŵ, wav, n1)
Compute the dual weights using the scaling amounts and the normalization power p. Primarily used when the least squares version is poorly constructed. When the least squares version doesn't perform well, this is also likely to have poor reconstruction, but it won't give extremely negative or oscillatory weights like the least squares version.
"""
function computeNaiveDualWeights(Ŵ, wav, n1)
    _, _, sRange, _ = ContinuousWavelets.getNWavelets(n1, wav)
    isAve = !(wav.averagingType isa NoAve)
    p = wav.p
    # every wavelet is multiplied by 1/s^(1/p), so we need to multiply by that to normalize in frequency. The extra +1 is to convert from uniform in scale to uniform in frequency
    sToTheP = sRange' .^ (1/p+1)
    if isAve && size(Ŵ,2)>1
        # averaging needs to have maximum value of 1
        aveMultiplier = 1/norm(Ŵ[:,1],Inf)
        dual = sum(Ŵ[:,2:end] .* sToTheP, dims=2)
        renormalize = norm(dual,Inf)
        dual ./=renormalize
        #dual += Ŵ[:,1] .* sToTheP[1]
        β = cat(aveMultiplier, 2 .* sToTheP./renormalize..., dims=2)
    elseif size(Ŵ,2)>1
        dual = sum(Ŵ .* sToTheP, dims=2)
        β = sToTheP ./ norm(dual, Inf)
        β[2:end] .* 2
    else
        # there's only the averaging
        β = [1/norm(Ŵ[:,1],Inf)]
    end
    return β
end

"""
    dualCover, dualNorm = getDualCoverage(n,cWav, invType)
get the sum of the weights and its deviation from 1
"""
function getDualCoverage(n,cWav, invType)
    Ŵ = computeWavelets(n,cWav)[1]
    if invType isa DualFrames
        dualCover = sum(conj.(Ŵ) .* explicitConstruction(Ŵ),dims=2)
        return dualCover, norm(dualCover .- 1)
    elseif invType isa PenroseDelta
        β = computeDualWeights(Ŵ, cWav)
    elseif invType isa NaiveDelta
        β = computeNaiveDualWeights(Ŵ, cWav, n)
    end
    dualCover = sum(conj.(β) .* Ŵ, dims=2)
    dualCover, norm(dualCover .- 1)
end

function dualDeviance(n, cWav, naive)
    getDualCoverage(n,cWav,naive)
end

"""
Explicitly construct the canonical dual frame elements for a translation invariant frame. This is likely to end poorly due to the low representation at high frequencies.
"""
function explicitConstruction(Ŵ)
    Ŵdual = conj.(Ŵ ./ [norm(ŵ)^2 for ŵ in eachslice(Ŵ,dims=1)])
end


function solve_max_seat_id(input)
	max_seat_id = 0
	for pass in input
		seat_id = parse(Int16, replace(replace(pass, ('B', 'R') => '1'), ('F', 'L')=>'0'), base = 2)
		if seat_id > max_seat_id
			max_seat_id = seat_id
		end
	end
	max_seat_id
end

function solve_my_seat_id(input)
	my_seat_id = max_seat_id = 0
	min_seat_id = 1023
	for pass in input
		seat_id = parse(Int16, replace(replace(pass, ('B', 'R') => '1'), ('F', 'L')=>'0'), base = 2)
		if seat_id > max_seat_id
			max_seat_id = seat_id
		elseif seat_id < min_seat_id
			min_seat_id = seat_id
		end
		my_seat_id ⊻= seat_id
	end
	for i in 1:min_seat_id - 1
		my_seat_id ⊻= i
	end
	for i in max_seat_id + 1:1023
		my_seat_id ⊻= i
	end
	my_seat_id
end

@assert solve_max_seat_id(eachline("input/test/test_day5.txt")) == 820 ["test case 1 failed"]

println("First star: " * string(solve_max_seat_id(eachline("input/day5.txt"))))
println("Second star: " * string(solve_my_seat_id(eachline("input/day5.txt"))))


"""
`utd(yr,r,g,t)`: Share of total trade by region.

```math
\\alpha_{yr,r,g,t}^{utd}
=
\\begin{cases}
\\dfrac{           \\bar{utd}_{yr,r ,g,t}}
       {\\sum_{r'} \\bar{utd}_{yr,r',g,t}}        & notrd\\ni g
\\\\
\\dfrac{\\sum_{yr'}    \\bar{utd}_{yr',r ,g,t}}
       {\\sum_{yr',r'} \\bar{utd}_{yr',r',g,t}}   & notrd\\in g
\\end{cases}
```
"""
function share_utd!(d::Dict, set::Dict)
    println("  Calculating utd(yr,r,g,t), share of total trade by region")
    d[:utd] = fill_zero(d[:utd])
    df = d[:utd] / transform_over(d[:utd], :r)

    df_yr = transform_over(d[:utd], :yr) / transform_over(d[:utd], [:yr,:r])
    df[isnan.(df[:,:value]), :value] .= df_yr[isnan.(df[:,:value]),:value]

    # Check import and export shares.
    verify_over(filter_with(df, (t = "imports",)), :r) !== true && @error("Import shares don't sum to 1.")
    verify_over(filter_with(df, (t = "exports",)), :r) !== true && @error("Export shares don't sum to 1.")

    d[:utd] = dropnan(dropzero(df))
    set[:notrd] = _set_notrd!(d, set)
    return d[:utd]
end

" `notrd`: Goods/sectors not included in imports/exports."
function _set_notrd!(d::Dict, set::Dict)
    !(:utd in keys(d)) && @error("shr[:utd] required to find set[:notrd]")
    set[:notrd] = setdiff(set[:g], d[:utd][:,:g])
    return set[:notrd]
end


# Parameters
β = 1 / 1.05
P = [0.8 0.2 0.0
     0.0 0.5 0.5
     0.0 0.0 1.0]

# Possible states of the world
# Each column is a state of the world. The rows are [g d b s 1]
x_vals = [0.5 0.5 0.25;
          0.0 0.0  0.0;
          2.2 2.2  2.2;
          0.0 0.0  0.0;
          1.0 1.0  1.0]
Sg = [1.0 0.0 0.0 0.0 0.0]
Sd = [0.0 1.0 0.0 0.0 0.0]
Sb = [0.0 0.0 1.0 0.0 0.0]
Ss = [0.0 0.0 0.0 1.0 0.0]
discrete = true
proc = DiscreteStochProcess(P, x_vals)

econ = Economy(β, Sg, Sd, Sb, Ss, discrete, proc)
T = 15
path = compute_paths(econ, T)

gen_fig_1(path)


using JuMP, JuMPeR, Gurobi, Random, Distributions, LinearAlgebra, Plots

n_moves = 2
n_players = 3
# Some payoff matrix
payoffM = Dict(1 => zeros(2,2,2), 2 => zeros(2,2,2), 3 => zeros(2,2,2))
# Jean's payoff
payoffM[1][:,:,1] = [3 2; 1 5]
payoffM[1][:,:,2] = [2 4; 0 7]
# Enrico's payoff
payoffM[2][:,:,1] = [5 4; 3 2]
payoffM[2][:,:,2] = [4 6; 2 7]
# Guy's payoff
payoffM[3][:,:,1] = [8 1; 2 4]
payoffM[3][:,:,2] = [5 3; 1 7]

function collaborative_game()
    m = Model(solver = GurobiSolver())
    @variable(m, 0 <= strategy[1:n_moves, 1:n_moves, 1:n_moves] <= 1)
    @variable(m, payoff[1:n_players])
    @constraint(m, sum(strategy) == 1)
    for i = 1:n_players
        @constraint(m, payoff[i] == sum(payoffM[i] .* strategy))
    end
    @objective(m, Max, sum(payoff))
    return m
end

function invididual_game(ind=1)
    m = Model(solver = GurobiSolver());
    @variable(m, 0 <= move <= 1);
    @variable(m, payoff >= 0);
    all_opposing_strats = reshape(collect(Base.product(zip([1,2], [1,2])...)), (1, n_moves^(n_players-1)))
    @objective(m, Max, sum(dot(payoffM[ind][:, all_opposing_strats[i][1], all_opposing_strats[i][2]], [move, 1-move])
                for i=1:length(all_opposing_strats))); # Average return
    solve(m)
    println("Average return fixed move: $(getvalue(move)) with payoff $(getobjectivevalue(m)).")

    for i = 1:length(all_opposing_strats)
        @constraint(m, payoff <= sum(dot(payoffM[ind][:, all_opposing_strats[i][1], all_opposing_strats[i][2]],
                    [move, 1-move]))) # Minimum return
    end
    @objective(m, Max, payoff)
    solve(m)
    println("Max-min return, fixed move: $(getvalue(move)) with payoff $(getvalue(payoff)).")

    # Adaptive strategy
    m = Model(solver = GurobiSolver());
    @variable(m, 0 <= move <= 1);
    @variable(m, strat[1:n_players-1])
    @variable(m, strat0)
    @variable(m, 0 <= opp_moves[1:n_players-1] <= 1)
    @constraint(m, move == dot(strat, opp_moves) + strat0)
    @variable(m, payoff >= 0);

    # TODO: clean-up!
    @variable(m, zero_payoffs[1:n_players-1] >= 0)
    @variable(m, one_payoffs[1:n_players-1] >= 0)
    @constraint(m, zero_payoffs .<= opp_moves' * payoffM[ind][:,:,1] * opp_moves)
    @constraint(m, one_payoffs .<= payoffM[ind][:,:,2] * opp_moves)
    @objective(m, Max, sum(zero_payoffs) + sum(one_payoffs))
    @constraint(m, payoff <= )
    @objective(m, Max, payoff)


@testset "Supercell angles" begin
@testset "fcc" begin
    @test all(MkCell.cell_angles(fcc1*cellfcc)   .≈ (90.0, 120.0, 60.0))
    @test all(MkCell.cell_angles(fcc4*cellfcc)   .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(fcc7*cellfcc)   .≈ (99.59406822686046, 80.40593177313954, 80.40593177313954))
    @test all(MkCell.cell_angles(fcc13*cellfcc)  .≈ (72.54239687627792, 107.4576031237221, 72.54239687627792))
    @test all(MkCell.cell_angles(fcc23*cellfcc)  .≈ (90.0, 85.90395624184767, 107.9752838098089))
    @test all(MkCell.cell_angles(fcc26*cellfcc)  .≈ (85.90395624184767, 94.09604375815232, 94.09604375815232))
    @test all(MkCell.cell_angles(fcc32*cellfcc)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(fcc38*cellfcc)  .≈ (93.1847385367204, 86.8152614632796, 86.8152614632796))
    @test all(MkCell.cell_angles(fcc50*cellfcc)  .≈ (97.83747976308429, 82.16252023691571, 82.16252023691571))
    @test all(MkCell.cell_angles(fcc70*cellfcc)  .≈ (90.0, 87.79577249602796, 79.68680947509941))
    @test all(MkCell.cell_angles(fcc92*cellfcc)  .≈ (88.30279727450392, 88.08978682829007, 95.09758601873176))
    @test all(MkCell.cell_angles(fcc96*cellfcc)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(fcc100*cellfcc) .≈ (93.2771683126184, 86.7228316873816, 79.83575137827653))
    @test all(MkCell.cell_angles(fcc108*cellfcc) .≈ (90.0, 90.0, 90.0))
end;
@testset "cubic" begin
    @test all(MkCell.cell_angles(cubic1*cellcubic)   .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(cubic3*cellcubic)   .≈ (90.0, 90.0, 60.00000000000001))
    @test all(MkCell.cell_angles(cubic7*cellcubic)   .≈ (104.96321743330712, 75.03678256669288, 66.42182152179818))
    @test all(MkCell.cell_angles(cubic8*cellcubic)   .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(cubic13*cellcubic)  .≈ (90.0, 79.48026510934139, 99.59406822686046))
    @test all(MkCell.cell_angles(cubic14*cellcubic)  .≈ (99.59406822686046, 99.59406822686046, 99.59406822686046))
    @test all(MkCell.cell_angles(cubic24*cellcubic)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(cubic27*cellcubic)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(cubic33*cellcubic)  .≈ (90.0, 95.73917047726677, 90.0))
    @test all(MkCell.cell_angles(cubic38*cellcubic)  .≈ (85.20308428929593, 95.21590857045413, 75.47084566659821))
    @test all(MkCell.cell_angles(cubic52*cellcubic)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(cubic56*cellcubic)  .≈ (97.44883282786358, 98.21321070173819, 97.44883282786358))
    @test all(MkCell.cell_angles(cubic64*cellcubic)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(cubic73*cellcubic)  .≈ (90.0, 80.12503860217872, 93.1847385367204))
    @test all(MkCell.cell_angles(cubic76*cellcubic)  .≈ (93.1847385367204, 93.1847385367204, 93.1847385367204))
    @test all(MkCell.cell_angles(cubic93*cellcubic)  .≈ (92.666603476751, 92.86957619589583, 95.61383424102878))
    @test all(MkCell.cell_angles(cubic100*cellcubic) .≈ (90.0, 90.0, 90.0))
end;
@testset "bcc" begin
    @test all(MkCell.cell_angles(bcc1*cellbcc)   .≈ (70.52877936550931, 109.47122063449069, 70.52877936550931))
    @test all(MkCell.cell_angles(bcc2*cellbcc)   .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(bcc6*cellbcc)   .≈ (90.0, 90.0, 115.2394018206789))
    @test all(MkCell.cell_angles(bcc9*cellbcc)   .≈ (84.78409142954588, 95.21590857045413, 84.78409142954588))
    @test all(MkCell.cell_angles(bcc16*cellbcc)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(bcc21*cellbcc)  .≈ (86.98303869018402, 95.88878512679155, 95.88878512679155))
    @test all(MkCell.cell_angles(bcc25*cellbcc)  .≈ (90.0, 85.06265611930155, 104.96321743330712))
    @test all(MkCell.cell_angles(bcc28*cellbcc)  .≈ (80.40593177313954, 99.59406822686046, 80.40593177313954))
    @test all(MkCell.cell_angles(bcc35*cellbcc)  .≈ (87.87744863785665, 92.12255136214335, 87.87744863785665))
    @test all(MkCell.cell_angles(bcc48*cellbcc)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(bcc53*cellbcc)  .≈ (96.05074601591176, 83.86309610848119, 73.63722782068534))
    @test all(MkCell.cell_angles(bcc54*cellbcc)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(bcc65*cellbcc)  .≈ (77.91853011529311, 102.08146988470688, 77.91853011529311))
    @test all(MkCell.cell_angles(bcc84*cellbcc)  .≈ (92.24963404707006, 87.75036595292995, 84.78409142954588))
    @test all(MkCell.cell_angles(bcc91*cellbcc)  .≈ (88.87648134229958, 91.12351865770042, 88.87648134229958))
    @test all(MkCell.cell_angles(bcc103*cellbcc) .≈ (92.06928050584378, 93.99037534653964, 94.25094462500175))
    @test all(MkCell.cell_angles(bcc107*cellbcc) .≈ (90.0, 94.09604375815232, 86.00962465346038))
    @test all(MkCell.cell_angles(bcc112*cellbcc) .≈ (94.86140539579802, 85.13859460420197, 85.13859460420197))
    @test all(MkCell.cell_angles(bcc128*cellbcc) .≈ (90.0, 90.0, 90.0))
end;
@testset "hcp" begin
    @test all(MkCell.cell_angles(hcp1*cellhcp)   .≈ (90.0, 90.0, 60.0))
    @test all(MkCell.cell_angles(hcp3*cellhcp)   .≈ (90.0, 90.0, 60.0))
    @test all(MkCell.cell_angles(hcp6*cellhcp)   .≈ (90.0, 90.0, 97.7493663782984))
    @test all(MkCell.cell_angles(hcp8*cellhcp)   .≈ (86.62771331656612, 90.0, 90.0))
    @test all(MkCell.cell_angles(hcp11*cellhcp)  .≈ (93.10882512596606, 81.58250966338895, 76.22172246000032))
    @test all(MkCell.cell_angles(hcp14*cellhcp)  .≈ (95.7391704772668, 80.43938360731833, 78.01745450750637))
    @test all(MkCell.cell_angles(hcp16*cellhcp)  .≈ (90.0, 90.0, 98.21321070173819))
    @test all(MkCell.cell_angles(hcp21*cellhcp)  .≈ (85.05457028211667, 94.94542971788334, 85.05457028211667))
    @test all(MkCell.cell_angles(hcp27*cellhcp)  .≈ (99.04109614857515, 80.95890385142484, 80.95890385142484))
    @test all(MkCell.cell_angles(hcp37*cellhcp)  .≈ (87.38866773903177, 96.46147790595835, 87.99219492277635))
    @test all(MkCell.cell_angles(hcp39*cellhcp)  .≈ (93.26480184633287, 80.51388629606356, 88.73996568504259))
    @test all(MkCell.cell_angles(hcp42*cellhcp)  .≈ (91.26003431495744, 91.26003431495741, 82.05063659026476))
    @test all(MkCell.cell_angles(hcp53*cellhcp)  .≈ (97.09038962820946, 88.0060223885153, 85.52821741779152))
    @test all(MkCell.cell_angles(hcp62*cellhcp)  .≈ (93.27084136698927, 97.84269934104985, 95.41194366545838))
    @test all(MkCell.cell_angles(hcp65*cellhcp)  .≈ (81.55960115382774, 88.0060223885153, 93.63706568080396))
    @test all(MkCell.cell_angles(hcp69*cellhcp)  .≈ (90.0, 90.0, 94.30661909550145))
    @test all(MkCell.cell_angles(hcp76*cellhcp)  .≈ (89.57829310179544, 91.72387995423274, 94.12652542280888))
    @test all(MkCell.cell_angles(hcp80*cellhcp)  .≈ (86.62771331656612, 90.0, 90.0))
    @test all(MkCell.cell_angles(hcp88*cellhcp)  .≈ (90.80700949307113, 90.74639151793876, 90.74639151793875))
    @test all(MkCell.cell_angles(hcp90*cellhcp)  .≈ (90.0, 90.0, 90.0))
    @test all(MkCell.cell_angles(hcp100*cellhcp) .≈ (87.97513170272266, 90.0, 90.0))
end;

end;


module ScalarProducts

using Plots
using LinearAlgebra
using BenchmarkTools

"""
    scalar_product_v1(x, y)

Compute the scalar product of two vectors using the for loop.
"""
function scalar_product_v1(x::Array{Float64,1}, y::Array{Float64,1})
    @assert length(x) == length(y)

    tmp::Float64 = 0.

    for i in 1:length(x)
        tmp += x[i] * y[i]
    end

    return sum(tmp)
end

"""
    scalar_product_v2(x, y)

Compute the scalar product of two vectors using the list comprehension.
"""
function scalar_product_v2(x::Array{Float64,1}, y::Array{Float64,1})
    return sum([i * j for (i,j) in [x, y]])
end

"""
    scalar_product_v3(x, y)

Compute the scalar product of two vectors using element-wise `.*` operation.
"""
function scalar_product_v3(x::Array{Float64,1}, y::Array{Float64,1})
    return sum(x .* y)
end

"""
    scalar_product_v4(x, y)

Compute the scalar product of two vectors using `LinearAlgebra.dot()` function.
"""
function scalar_product_v4(x::Array{Float64,1}, y::Array{Float64,1})
    return dot(x, y)
end

function scalar_product_v5(x::Array{Float64,1}, y::Array{Float64,1})
    s = 0.
    foreach((v,w) -> s += v*w, x, y)
    return s
end

"""
    test(F, N)

Generates a couple of random vectors of length `N:100:M` and plot the time necessary
for a functions from your module to calculate the result.
"""
function test(N::Int64, M::Int64)
    # Don't call @time not in global scope if you
    # like to get proper allocation info from time macro, see
    # https://stackoverflow.com/a/46263352 and https://stackoverflow.com/a/46660228.

    ns = N:(10^4):M

    fs = Dict(
        "v1" => scalar_product_v1,
        "v2" => scalar_product_v2,
        "v3" => scalar_product_v3,
        "v4" => scalar_product_v4,
        # scalar_product_v5,
    )

    gr()    # Empty plot with GR backend.
    plot()

    for (name, f) in fs
        # The function `f` is compiled here because
        # you like to exclude compile time from test.

        f(rand(Float64, N), rand(Float64, N))
        println( f([2.0, 1.0], [1.0, 2.0])) # simple test
        ts = Vector{Float64}()

        for n in ns
            x = rand(Float64, n)
            y = rand(Float64, n)
            t = @elapsed f(x, y)
            append!(ts, t)
        end
        plot!(ns, ts, title = "Benchmarks", label=name)

    end

    savefig("Benchmarks.png")
end

end # module


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md

using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Postprocess
using JuliaFEM.Testing

@testset "calculate cross-sectional properties" begin
    mesh_file = Pkg.dir("JuliaFEM") * "/test/testdata/primitives.med"
    mesh = aster_read_mesh(mesh_file, "CYLINDER_20_TET4")
    # calculate cross-sectional properties A and Iₓ
    fixed1 = Problem(Dirichlet, "left support", 3, "displacement")
    fixed1.elements = create_elements(mesh, "FACE1")
    A = calculate_area(fixed1)
    info("cross-section area: $A")
    # real area is π
    @test isapprox(A, pi; rtol=0.1)
    Xc = calculate_center_of_mass(fixed1)
    info("center of mass: $Xc")
    @test isapprox(Xc, [0.0, 0.0, 0.0]; atol=1.0e-12)
    I = calculate_second_moment_of_mass(fixed1)
    info("moments:")
    info(I)
    I_expected = zeros(3, 3)
    I_expected[2,2] = I_expected[3,3] = pi/4
    rtol = norm(I[2,2]-I_expected[2,2]) / max(I[2,2],I_expected[2,2])
    info("I rtol = $rtol")
    @test isapprox(I, I_expected; rtol = 0.2)
end

#=
test subjects:
- calculate cross-sectional properties
- modal analysis with known solution

Fixed-fixed solution is ωᵢ = λᵢ²√(EI/ρA) , where λᵢ = cosh(λᵢℓ)cos(λᵢℓ)

1:  4.730040744862704
2:  7.853204624095838
3: 10.995607838001671

Youngs modulus is tuned such that lowest eigenfrequency matches 1.0

5 lowest eigenfrequencies using Code Aster and Tet4 elements:
numéro    fréquence (HZ)     norme d'erreur
    1       1.19789E+00        2.20137E-12
    2       1.20179E+00        1.99034E-12
    3       3.07391E+00        3.29226E-13
    4       3.08812E+00        2.91550E-13
    5       4.87370E+00        2.95986E-13

5 lowest eigenfrequencies using Code Aster and Tet10 elements:
numéro    fréquence (HZ)     norme d'erreur
    1       9.65942E-01        1.54950E-11
    2       9.66160E-01        1.62712E-11
    3       2.52127E+00        2.06544E-12
    4       2.52187E+00        1.77970E-12
    5       3.48584E+00        9.96170E-13

[1] De Silva, Clarence W. Vibration: fundamentals and practice. CRC press, 2006, p.355
=#

@testset "long rod natural frequencies" begin
    mesh_file = Pkg.dir("JuliaFEM") * "/test/testdata/primitives.med"
    mesh = aster_read_mesh(mesh_file, "CYLINDER_20_TET10")
#   for (id, coords) in mesh.nodes
#       mesh.nodes[id][1] *= 5.0
#   end
    body = Problem(Elasticity, "rod", 3)
    body.elements = create_elements(mesh, "CYLINDER")
    #E = 50475.44814745859
    E = 50475.5
    rho = 1.0
    update!(body.elements, "youngs modulus", E)
    update!(body.elements, "poissons ratio", 0.3)
    update!(body.elements, "density", rho)
    # calculate cross-sectional properties A and Iₓ
    fixed1 = Problem(Dirichlet, "left support", 3, "displacement")
    fixed1.elements = create_elements(mesh, "FACE1")
    update!(fixed1.elements, "displacement 1", 0.0)
    update!(fixed1.elements, "displacement 2", 0.0)
    update!(fixed1.elements, "displacement 3", 0.0)
    fixed2 = Problem(Dirichlet, "right support", 3, "displacement")
    fixed2.elements = create_elements(mesh, "FACE2")
    update!(fixed2.elements, "displacement 1", 0.0)
    update!(fixed2.elements, "displacement 2", 0.0)
    update!(fixed2.elements, "displacement 3", 0.0)
    A = calculate_area(fixed1)
    info("cross-section area: $A")
    # using SALOME / SMESH, A = 2.82843
    # real area is π
    @test isapprox(A, pi; rtol=0.1)
    Xc = calculate_center_of_mass(fixed1)
    info("center of mass: $Xc")
    @test isapprox(Xc, [0.0, 0.0, 0.0]; atol=1.0e-5)
    I = calculate_second_moment_of_mass(fixed1)
    info("moments:")
    info(I)
    I_expected = zeros(3, 3)
    r = 1.0
    I_expected[2,2] = I_expected[3,3] = pi/4*r^2
    rtol = norm(I[2,2]-I_expected[2,2]) / max(I[2,2],I_expected[2,2])
    info("I rtol = $rtol")
    @test isapprox(I, I_expected; rtol = 0.2)
#=
    # apply transform Tx + b, in this case move cross-section to
    # xy-plane from yz-plane, i.e.
    # x₁ = y₂
    # y₁ = z₂
    T = [
        0.0 1.0 0.0
        0.0 0.0 1.0]
    b = [0.0, 0.0]
    X 1 = first(cross_section)("geometry", [1/3, 1/3], 0.0)
    apply_affine_transform!(cross_section, T, b)
    X2 = first(cross_section)("geometry", [1/3, 1/3], 0.0)
    info("X1 = $X1, X2 = $X2")
    @test isapprox(T*X1+b, X2)
=#
    c = sqrt(E*I[2,2]/(rho*A))
    info("c = $c")

    # analytical solution is
    l = 20.0
    r = 1.0
    la = 4.730040744862704/l

    # semi-analytical (c numerical)
    freq_sa = (c*la^2)/(2*pi)
    info("freq_sa = $freq_sa")

    A = pi*r^2
    I = pi/4*r^4
    c = sqrt(E*I/(rho*A))
    info("c analytical = $c")
    freq_a = (c*la^2)/(2*pi)

    info("freq_a = $freq_a")

    solver = Solver(Modal, body, fixed1, fixed2)
    solver.properties.nev = 5
    solver()
    freqs_jf = sqrt(solver.properties.eigvals)/(2.0*pi)
    # with Tet4 elements
    #freqs_ca = [1.19789E+00, 1.20179E+00, 3.07391E+00, 3.08813E+00, 4.87370E+00]
    # with Tet10 elements
    freqs_ca = [9.65942E-01, 9.66160E-01, 2.52127E+00, 2.52187E+00, 3.48584E+00]

    # looks that juliafem results are more close to 1.0, maybe different integration order
    rtol1 = norm(freq_sa - freqs_jf[1])/max(freq_sa, freqs_jf[1])
    rtol2 = norm(freq_a - freqs_jf[1])/max(freq_a, freqs_jf[1])
    info("rtol 1 = $rtol1, rtol 2 = $rtol2")
    passed = true
    for (f1, f2) in zip(freqs_jf, freqs_ca)
        rtol = norm(f1-f2) / max(f1,f2)
        @printf "JF: %8.5e | CA: %8.5e | rtol: %8.5e\n" f1 f2 rtol
        passed &= (rtol < 3.0e-2)
    end
    @test rtol2 < 3.0e-2
    @test passed

#=
    result = XDMF()
    for (i, freq) in enumerate(freqs)
        isapprox(freq, 0.0) && continue
        info("$i freq: $freq")
        xdmf_new_result!(result, body, freq)
        xdmf_save_field!(result, body, freq, "displacement"; field_type="Vector")
    end
    xdmf_save!(result, "/tmp/rod_nf.xmf")
=#

end

@testset "eigenvalues of cube (tet4)" begin
    meshfile = Pkg.dir("JuliaFEM") * "/test/testdata/primitives.med"
    mesh = aster_read_mesh(meshfile, "CUBE_TET4")
    cube = Problem(mesh, Elasticity, "CUBE", 3)
    update!(cube.elements, "youngs modulus", 10000.0)
    update!(cube.elements, "poissons ratio", 0.3)
    update!(cube.elements, "density", 10.0)
    sym23 = create_elements(mesh, "FACE231")
    update!(sym23, "displacement 1", 0.0)
    sym13 = create_elements(mesh, "FACE131")
    update!(sym13, "displacement 2", 0.0)
    sym12 = create_elements(mesh, "FACE121")
    update!(sym12, "displacement 3", 0.0)
    bcs = Problem(Dirichlet, "bcs", 3, "displacement")
    bcs.elements = [sym23; sym13; sym12]
    solver = Solver(Modal)
    solver.properties.nev = 5
    push!(solver, cube, bcs)
    solver()
    freqs_jf = sqrt(solver.properties.eigvals)/(2.0*pi)
    freqs_ca = [3.73724E+00, 3.73724E+00, 4.93519E+00, 6.59406E+00, 7.65105E+00]
    for (f1, f2) in zip(freqs_jf, freqs_ca)
        rtol = norm(f1-f2) / max(f1,f2)
        @printf "JF: %8.5e | CA: %8.5e | rtol: %8.5e\n" f1 f2 rtol
        @test rtol < 1.0e-5
    end
end


# ---
# title: 1401. Circle and Rectangle Overlapping
# id: problem1401
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Geometry
# link: <https://leetcode.com/problems/circle-and-rectangle-overlapping/description/>
# hidden: true
# ---
#
# Given a circle represented as (`radius`, `x_center`, `y_center`) and an axis-
# aligned rectangle represented as (`x1`, `y1`, `x2`, `y2`), where (`x1`, `y1`)
# are the coordinates of the bottom-left corner, and (`x2`, `y2`) are the
# coordinates of the top-right corner of the rectangle.
#
# Return True if the circle and rectangle are overlapped otherwise return False.
#
# In other words, check if there are **any** point (xi, yi) such that belongs to
# the circle and the rectangle at the same time.
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2020/02/20/sample_4_1728.png)
#
#
#
#     Input: radius = 1, x_center = 0, y_center = 0, x1 = 1, y1 = -1, x2 = 3, y2 = 1
#     Output: true
#     Explanation: Circle and rectangle share the point (1,0)
#
#
# **Example 2:**
#
# **![](https://assets.leetcode.com/uploads/2020/02/20/sample_2_1728.png)**
#
#
#
#     Input: radius = 1, x_center = 0, y_center = 0, x1 = -1, y1 = 0, x2 = 0, y2 = 1
#     Output: true
#
#
# **Example 3:**
#
# **![](https://assets.leetcode.com/uploads/2020/03/03/sample_6_1728.png)**
#
#
#
#     Input: radius = 1, x_center = 1, y_center = 1, x1 = -3, y1 = -3, x2 = 3, y2 = 3
#     Output: true
#
#
# **Example 4:**
#
#
#
#     Input: radius = 1, x_center = 1, y_center = 1, x1 = 1, y1 = -3, x2 = 2, y2 = -1
#     Output: false
#
#
#
#
# **Constraints:**
#
#   * `1 <= radius <= 2000`
#   * `-10^4 <= x_center, y_center, x1, y1, x2, y2 <= 10^4`
#   * `x1 < x2`
#   * `y1 < y2`
#
#
## @lc code=start
using LeetCode

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


"""
    pp_prob_plot(sepp::SEPP)
Plot the probability plot for the point process.

See 4.1 in Li2020.
"""
function pp_prob_plot(sepp::SEPP)
    s, p = pp_analysis(sepp)

    id = layer(x = p, y = p, color = [color("red")], Geom.line, order = 2)

    emp = layer(x = p, y = s, color = [color("black")], Geom.line, order = 1)

    plt = plot(id, emp, Guide.title("Point process probability plot"), Guide.xlabel("Empirical"), Guide.ylabel("Model"))

    return plt
end

"""
    marks_prob_plot(sempp::SEMPPExpKern)
Plot the probability plot for the marks.

See 4.2 in Li2020.
"""
function marks_prob_plot(sempp::SEMPPExpKern)

    s, p = transformed_marks_ecdf(sempp)

    id = layer(x = p, y = p, color = [color("red")], Geom.line, order = 2)

    emp = layer(x = p, y = s, color = [color("black")], Geom.point, order = 1)

    plt = plot(id, emp, Guide.title("Marks probability plot"), Guide.xlabel("Empirical"), Guide.ylabel("Model"))

    return plt
end

"""
    marks_qq_plot(sempp::SEMPPExpKern)
Plot the quantile plot based on the unit exponential distribution.

See 4.2 in Li2020.
"""
function marks_qq_plot(sempp::SEMPPExpKern)

    emp_q, mod_q = marks_unit_exponential_qq(sempp)

    id = layer(x = mod_q, y = mod_q, color = [color("red")], Geom.line, order = 2)

    emp = layer(x = mod_q, y = emp_q, color = [color("black")], Geom.point, order = 1)

    plt = plot(id, emp, Guide.title("Marks quantile plot"), Guide.xlabel("Empirical"), Guide.ylabel("Model"))

    return plt
end


eigvals(A::Operator,n::Integer;tolerance::Float64=100eps()) =
    eigs(A,n;tolerance=tolerance)[1]

"""
    λ, V = eigs(A::Operator,n::Integer;tolerance::Float64=100eps())

Compute the eigenvalues and eigenvectors of the operator `A`. This is done in the following way:

* Truncate `A` into an n×n matrix `A₁`.
* Compute eigenvalues and eigenvectors of `A₁`.
* Filter out those eigenvectors of `A₁`, which are approximately eigenvectors
of `A` as well. The `tolerance` argument controls, which eigenvectors of the approximation are kept.
"""
function eigs(A::Operator,n::Integer;tolerance::Float64=100eps())
    typ = eltype(A)

    ds=domainspace(A)
    C = Conversion(ds,rangespace(A))

    A1,C1 = zeros(typ,n,n),zeros(typ,n,n)
    A1[1:end,1:n] = A[1:n,1:n]
    C1[1:end,1:n] = C[1:n,1:n]

    λ,V=eigen(A1,C1)

    pruneeigs(λ,V,ds,tolerance)
end

eigvals(Bcs::Operator,A::Operator,n::Integer;tolerance::Float64=100eps()) =
    eigs(Bcs,A,n;tolerance=tolerance)[1]

function eigs(Bcs_in::Operator,A_in::Operator,n::Integer;tolerance::Float64=100eps())
    Bcs, A = promotedomainspace([Bcs_in, A_in])

    nf = size(Bcs,1)
    @assert isfinite(nf)

    typ = promote_type(eltype(Bcs),eltype(A))

    ds=domainspace(A)
    C = Conversion(ds,rangespace(A))

    A1,C1 = zeros(typ,n,n),zeros(typ,n,n)
    A1[1:nf,1:n] = Bcs[1:nf,1:n]
    A1[nf+1:end,1:n] = A[1:n-nf,1:n]
    C1[nf+1:end,1:n] = C[1:n-nf,1:n]

    λ,V = eigen(A1,C1)

    λ, V = pruneeigs(λ,V,ds,tolerance)
    p = sortperm(λ; lt=(x,y) -> isless(abs(x),abs(y)))
    λ[p], V[p]
end

function pruneeigs(λ,V,ds,tolerance)
    retλ=eltype(λ)[]
    retV=VFun{typeof(ds),eltype(V)}[]
    n=length(λ)
    for k=1:n
        if slnorm(V,n-3:n,k)≤tolerance
            push!(retλ,λ[k])
            push!(retV,Fun(ds,V[:,k]))
        end
    end
    retλ,retV
end


# ---
# title: 1262. Greatest Sum Divisible by Three
# id: problem1262
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/greatest-sum-divisible-by-three/description/>
# hidden: true
# ---
#
# Given an array `nums` of integers, we need to find the maximum possible sum of
# elements of the array such that it is divisible by three.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [3,6,5,1,8]
#     Output: 18
#     Explanation: Pick numbers 3, 6, 1 and 8 their sum is 18 (maximum sum divisible by 3).
#
# **Example 2:**
#
#
#
#     Input: nums = [4]
#     Output: 0
#     Explanation: Since 4 is not divisible by 3, do not pick any number.
#
#
# **Example 3:**
#
#
#
#     Input: nums = [1,2,3,4,4]
#     Output: 12
#     Explanation: Pick numbers 1, 3, 4 and 4 their sum is 12 (maximum sum divisible by 3).
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 4 * 10^4`
#   * `1 <= nums[i] <= 10^4`
#
#
## @lc code=start
using LeetCode

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


using Surrogates

lb = 0.1
ub = 1.0
n = 100
s = sample(n,lb,ub,GridSample(0.1))
sample(n,lb,ub,UniformSample())
sample(n,lb,ub,SobolSample())
sample(n,lb,ub,LatinHypercubeSample())
sample(20,lb,ub,LowDiscrepancySample(10))
f(x) = x^2
f.(s)

lb = [0.1,-0.5]
ub = [1.0,20.0]
n = 100
s = sample(n,lb,ub,GridSample([0.1,0.5]))
sample(n,lb,ub,UniformSample())
sample(n,lb,ub,SobolSample())
sample(n,lb,ub,LatinHypercubeSample())

f(x) = x[1]+x[2]^2
f.(s)

lb = [1.2,1.2]
ub = [2.0,2.0]
n = 5
sample(n,lb,ub,LatinHypercubeSample())
sample(n,lb,ub,LowDiscrepancySample([10,3]))