"""
    B_SplineFFT{T, order} <: AbstractInterpolation{T, CircEdge, order}
    B_SplineFFT( order::Int, n::Int, T::DataType=Float64)

Type containing spline coefficients for b-spline interpolation based on fft, using the fact that b-spline matrix is a circulant matrix

# Type parameters
- `T` : the type of data that is interpolate
- `order::Int`: order of lagrange interpolation

# Implementation :
- `c_fft::Vector{Complex{T}}` : fft transform of coefficients
- `parfft::PrepareFftBig` : fft precomputed data
- `tabfct::Vector{Polynomial{T}}` : function table for interpolation

# Arguments :
- `n` : size of the matrix
- `order` : the order of interpolation
- `[T::DataType=Float64]` : The type values to interpolate

"""
struct B_SplineFFT{T,order} <: B_Spline{T,CircEdge,order}
    c_fft::Vector{Complex{T}}
    parfft::PrepareFftBig
    tabfct::Vector{Polynomial{T}}
    function B_SplineFFT(order::Int, n::Int, T::DataType = Float64)
        bspline = getbspline(order, 0)
        tabfct_rat = map(x -> bspline[order-x](Polynomial([order - x, 1])), 0:order)
        kl, ku = get_kl_ku(order)
        c = zeros(T, n)
        parfft = PrepareFftBig(n, T)
        tab_coef = convert.(T, bspline.(1:order))
        dec = n - kl - 1
        for i = 1:order
            c[(dec+i)%n+1] = tab_coef[i]
        end
        c_fft = fftgen(parfft, c)
        return new{T,order}(c_fft, parfft, convert.(Polynomial{T}, tabfct_rat))
    end
    B_SplineFFT(o::Int, n::Int, _::T) where {T} = B_SplineFFT(o, n, T)
end
function sol(bsp::B_SplineFFT{T}, b::AbstractVector{T}) where {T}
    return real(ifftgen(bsp.parfft, fftgen(bsp.parfft, b) ./ bsp.c_fft))
end
function sol!(Y::AbstractVector{T}, bsp::B_SplineFFT{T}, b::AbstractVector{T}) where {T}
    Y .= real(ifftgen(bsp.parfft, fftgen(bsp.parfft, b) ./ bsp.c_fft))
end


"""
`poro_elasticity_model(;
    n = 980,
    rho = 1e-3,
    alpha = 0.79,
    M = 1/7.80e3,
    kappanu = 633.33,
    eta = 1e-4,
    force_download = false
  )`

This function returns a port-Hamiltonian model of linear poroelasticity in a
bounded Lipschitz domain as described in
    Altmann, Mehrmann, Unger: Port-Hamiltonian Formulations of Poroelastic
    Network Models
# Arguments
- `n`: System dimension (can only be either: 320, 980, or 1805). Default = 980.
- `rho`: density. Default = `1e-3`.
- `alpha`: Biot-Willis fluid-solid coupling coefficient. Default = 0.79.
- `bm`: Biot-Modulus. Default = `1/7.8e3`.
- `kappanu`: Quotient kappa/Nu, where kappa denotes the permeability and nu denotes the fluid viscosity. Default = 633.33.
- `eta`: artificial damping coefficient. Default = `1e-4`.
"""
function poro_elasticity_model(;
    n = 980,
    rho = 1e-3,
    alpha = 0.79,
    bm = 1/7.80e3,
    kappanu = 633.33,
    eta = 1e-4,
    force_download = false
  )
  Y, D, M, K, Bp, Bf, A = load_poro_raw_data(
    n=n,
    force_download = force_download
  )
  Y = rho*sparse(Y)
  D = alpha*sparse(D)
  M = 1/bm .* sparse(M)
  K = kappanu*sparse(K)
  A = sparse(A)
  Bp = Bp'
  Bf = Bf'
  n = size(A, 1);
  m = size(M, 1);
  E = [Y spzeros(n,n+m); spzeros(n,n) A spzeros(n,m); spzeros(m,n+n) M];
  J = [spzeros(n,n) -A D';A spzeros(n,n+m); -D spzeros(m,n+m)];
  R = [spzeros(n,2*n+m); spzeros(n,2*n+m); spzeros(m,2*n) K] + eta*I
  B = [zeros(n,1); Bf; Bp];
  return E, J, R, B
end

function load_poro_raw_data(;
    n = 980,
    force_download = false
  )
  filename = "poro-n$n.mat"
  url = "https://zenodo.org/record/5702554/files/poro-n$n.mat?download=1"
  if n == 980
    md5_hash = hex(0x2961a189be7049ffe2d476b18cb1f678)
  elseif n == 320
    md5_hash = hex(0x97afe8c34f0e9a56bbe86d0a51b7b626)
  elseif n == 1805
    md5_hash = hex(0xc61f6687da9cd26cbf2d880d7d3a9ac9)
  else
    throw(ArgumentError("Model size is either 320, 980, or 1805"))
  end
  download_system_data_if_required(
    filename,
    url,
    md5_hash,
    force_download = force_download
  )
  dd = loadMAT(get_filepath(filename))
  return dd["Y"], dd["D"], dd["M"], dd["K"], dd["Bp"], dd["Bf"], dd["A"]
end

export poro_elasticity_model


"""
N: universe dimension
D: manifold dimension
T: coordinate type
"""
struct RectangleChart{N,D,T}
  origin::SVector{N,T}
  tangents::SVector{D,SVector{N,T}}
  normal::SVector{N,T}
end

function RectangleChart(o, tgs)
  n = tgs[1] × tgs[2]
  n = normalize(n)
  RectangleChart(o, tgs, n)
end

struct RectangleNBD{N,D,T}
  chart::RectangleChart{N,D,T}
  parm::SVector{D,T}
  cart::SVector{N,T}
end

cartesian(nbd::RectangleNBD) = nbd.cart

function neighborhood(rect::RectangleChart{3,2}, uv)
  o = rect.origin
  t1, t2 = rect.tangents
  u1, u2 = uv
  cart = o + u1*t1 + u2*t2
  RectangleNBD(rect, SVector(uv), cart)
end

getindex(nbd::RectangleNBD, i) = nbd.cart[i]
normal(nbd::RectangleNBD) = nbd.chart.normal

function quadpoints(chart::RectangleChart{3,2}, rule)

  T = eltype(chart.origin)

  o = chart.origin
  t1 = chart.tangents[1]
  t2 = chart.tangents[2]
  j = norm(t1 × t2)
  Q, P = rule
  U1, W1 = legendre(Q, zero(T), one(T))
  U2, W2 = legendre(P, zero(T), one(T))

  #pts = [o + u1 * t1 + u2 * t2 for u1 in U1 for u2 in U2]
  pts = [neighborhood(chart, (u1,u2)) for u1 in U1 for u2 in U2]
  wts = [j*w1*w2 for w1 in W1 for w2 in W2]

  collect(zip(pts,wts))
end


"""
    minimumAssignmentProblem(C)

Solve minimum assignment problem using vector of costs `C`.
"""
function minimumAssignmentProblem(C)
    @assert ndims(C) == 2 && size(C, 1) == size(C, 2)

    m = size(C, 1)
    model = Model(solver = CplexSolver(CPXPARAM_ScreenOutput = getProperty("minimumAssignmentProblem.cplexLogging")))
    @variable(model, x[1:m, 1:m], Bin)
    @objective(model, Min, vecdot(C, x))

    for constraint in getAssignmentConstraints(m)
        JuMP.addconstraint(model, constraint(x))
	end

    status = solve(model)
    return map((x) -> Int(x), getvalue(x))
end

"""
    getAssignmentConstraints(m)

Return a list of constraints defining a set of feasible solutions of a minimum assignment problem.
Each constraint is function with one parameter, which is variable of a mathematical programming model.
"""
function getAssignmentConstraints(m)
    constraints = []
    for j in 1:m
        push!(constraints, x-> @LinearConstraint(sum(x[i,j] for i=1:m)==1))
    end
    for i in 1:m
        push!(constraints, x -> @LinearConstraint(sum(x[i,j] for j=1:m)==1))
    end
    constraints
end


# ------------------------------------------------------------------------------------------
# # Julia 中的线性代数
# > Based on work by Andreas Noack Jensen (MIT) (http://www.econ.ku.dk/phdstudent/noack/)
# > with edits from Jane Herriman
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## 基础线性代数操作
# 定义一个随机矩阵
# ------------------------------------------------------------------------------------------

A = rand(1:4,3,3)

# ------------------------------------------------------------------------------------------
# 定义一个元素全为 1 的向量
# ------------------------------------------------------------------------------------------

x = fill(1.0, (3,)) # = fill(1.0, 3)

# ------------------------------------------------------------------------------------------
# 注意 $A$ 的类型为 `Array{Int64,2}`，而 $x$ 的类型为 `Array{Float64,1}`。
#
# Julia 定义向量 `Vector{Type}` 的别名为 `Array{Type,1}`，矩阵`Matrix{Type}` 的别名为 `Array{Type,2}`。
#
# 许多线性代数的基础操作和其他语言一样
#
# ### 乘法
# ------------------------------------------------------------------------------------------

b = A*x

# ------------------------------------------------------------------------------------------
# ### 转置
# 就像在其他语言中 `A'` 表示对 `A` 进行共轭转置
# ------------------------------------------------------------------------------------------

A'

# ------------------------------------------------------------------------------------------
# 我们还可以通过 `transpose` 函数获得转置矩阵
# ------------------------------------------------------------------------------------------

transpose(A)

# ------------------------------------------------------------------------------------------
# ### 转置的乘法
# Julia 中某些情况下可以省略 `*` 号
# ------------------------------------------------------------------------------------------

A'A

# ------------------------------------------------------------------------------------------
# ### 解线性方程组
# 用方阵 $A$ 表示的线性方程组 $Ax=b$ 可以用左除运算符（函数）`\` 求解
# ------------------------------------------------------------------------------------------

A\b

# ------------------------------------------------------------------------------------------
# 超定线性方程组（overdetermined linear system）的情况下 `A\b` 给出*最小二乘解（least squares solution）*
# ------------------------------------------------------------------------------------------

Atall = rand(3, 2)

Atall\b

# ------------------------------------------------------------------------------------------
# 缺秩最小二乘问题（a rank-deficient least squares problem）情况下，会给出*极小范数最小二乘解（minimum norm least
# squares solution）*
# ------------------------------------------------------------------------------------------

v = rand(3)
rankdef = hcat(v, v)

rankdef\b

# ------------------------------------------------------------------------------------------
# 欠定问题（an underdetermined solution）下，会给出 最小范数解（minimum norm solution）
# ------------------------------------------------------------------------------------------

bshort = rand(2)
Ashort = rand(2, 3)

Ashort\bshort

# ------------------------------------------------------------------------------------------
# ## `LinearAlgebra` 包
#
# 如上所示，Julia默认自带了很多线性代数操作，而标准库 `LinearAlgebra` 提供了更多的相关名词和函数。
# 特别是它提供了因子分解和一些结构化矩阵类型。
# 和其他包一样，可以通过 `using LinearAlgebra` 引入这些额外的特征。
# ------------------------------------------------------------------------------------------

using LinearAlgebra

# ------------------------------------------------------------------------------------------
# ## 练习
#
# ### 10.1
# 计算向量 `v` 和它自己的内积，并赋值给 `dot_v`。
# ------------------------------------------------------------------------------------------

v = [1,2,3]

# 在此作答
dot_v = v

@assert dot_v == 14

# ------------------------------------------------------------------------------------------
# ### 10.2
# 计算向量 v 和它自己的外积(Outer product)，并赋值给 `outer_v`。
#
# 提示：外积是一种克罗内克积(Kronecker product)
#
# 计算叉积(cross product)，赋值给 `cross_v`
# ------------------------------------------------------------------------------------------

# 在此作答
outer_v = v
cross_v = v

@assert outer_v == [1 2 3
                    2 4 6
                    3 6 9]

@assert cross_v == [0, 0, 0]

# ------------------------------------------------------------------------------------------
# 请在完成练习运行 `@assert` 块以检测结果是否正确，若无报错即为正确。
# ------------------------------------------------------------------------------------------


using LinearAlgebra

function lineq(;m, v, invert=false)
    s = m \ v
    if ! invert
        return (; bigarrs=(; s, ))
    else
        invm=inv(m)
        detm = det(m)
        return (; detm, bigarrs=(; s, invm))
    end
end

function eigval(;m)
    s = ComplexF64.(eigen(m).values) # result is otherwise type unstable (float or complex), and LabVIEW can't handle it
    return (; bigarrs=(; s, ))
end

fns = (; lineq, eigval,)


module GMMbox

using Distances
using Distributions
using Clustering
import Base.rand
import Base.rand!

export gaussian, logprob!, logprob, gmm, rand, rand!

mutable struct gaussian{T<:AbstractFloat}
  ndim::Int64
  mean::AbstractArray{T,2}
  cov::AbstractArray{T,2}
end

gaussian(ndim::Integer) = gaussian(ndim,zeros(ndim,1),eye(ndim))
gaussian(data::Matrix{T} where T<:AbstractFloat)  = gaussian(size(data,1),mean(data,2),cov(data,2))

mutable struct gmm{T<:AbstractFloat}
  nmix::Int64
  wts::AbstractArray{T,2}
  mix::AbstractArray{gaussian{T}}
end

function gmm(nmix::Integer, data::Matrix{T} where T <: AbstractFloat; method = "rand")
  if(method=="rand")
    ndim = size(data,1);
    dcov = cov(data,2);
    dmvn = MvNormal(vec(mean(data,2)),dcov);
    mix = Array{gaussian{eltype(data)}}(nmix);
    for i=1:nmix
      mixmean = convert(Array{eltype(data)},rand(dmvn));
      mix[i] = gaussian(ndim,reshape(mixmean,ndim,1),dcov);
    end
  elseif(method=="kmeans")
    km = kmeans(data,nmix);
    mix = Array{gaussian{eltype(data)}}(nmix);
    dcov = cov(data,2);
    for i=1:nmix
      mix[i] = gaussian(data[:,find(km.assignments.==i)]);
      mix[i].cov = dcov;
    end
  end

  return gmm(nmix,convert(Array{eltype(data)},(1/nmix)*ones(nmix,1)),mix);
end

rand!(g::gaussian{T},data::Matrix{T}) where T <: AbstractFloat = rand!(MvNormal(vec(g.mean),g.cov),data);
rand(g::gaussian{T},nvec::Integer) where T <: AbstractFloat = rand(MvNormal(vec(g.mean),g.cov),nvec);

function rand!(gm::gmm{T},data::Matrix{T}) where T <: AbstractFloat
  ftype = eltype(data);
  nvec = size(data,2);
  pmix = rand(ftype,nvec);
  nvecmix = zeros(Int64,gm.nmix);
  thr = zeros(ftype,1);
  for i=1:gm.nmix
    nvecmix[i] = sum((pmix.>=thr).&(pmix.<thr+gm.wts[i]))
    thr += gm.wts[i];
  end
  if(sum(nvecmix)<nvec)
    nvecmix[end] += nvec-sum(nvecmix);
    println("sum of number of samples per mixture not equal to requested number of samples");
  end
  sindx = 1;
  eindx = 0;
  for i=1:gm.nmix
    eindx += nvecmix[i];
    data[:,sindx:eindx] = rand(gm.mix[i],nvecmix[i]);
    sindx += nvecmix[i];
  end
  return nothing
end

function rand(gm::gmm{T},nvec::Integer) where T <: AbstractFloat
  ftype = eltype(gm.wts);
  pmix = rand(ftype,nvec);
  nvecmix = zeros(Int64,gm.nmix);
  thr = zeros(ftype,1);
  for i=1:gm.nmix
    nvecmix[i] = sum((pmix.>=thr).&(pmix.<thr+gm.wts[i]))
    thr += gm.wts[i];
  end
  if(sum(nvecmix)<nvec)
    nvecmix[end] += nvec-sum(nvecmix);
    println("sum of number of samples per mixture not equal to requested number of samples");
  end
  data = zeros(ftype,gm.mix[1].ndim,sum(nvec));
  sindx = 1;
  eindx = 0;
  for i=1:gm.nmix
    eindx += nvecmix[i];
    data[:,sindx:eindx] = rand(gm.mix[i],nvecmix[i]);
    sindx += nvecmix[i];
  end
  return data
end

logprob!(p::Array{T},g::gaussian{T},data::Matrix{T}) where T <: AbstractFloat = logpdf!(p,MvNormal(vec(g.mean),g.cov),data)
logprob(g::gaussian{T},data::Matrix{T}) where T <: AbstractFloat = logpdf(MvNormal(vec(g.mean),g.cov),data)

function logprob!(p::Array{T},gm::gmm{T},data::Matrix{T}) where T <: AbstractFloat
  mixprob = zeros(eltype(data),size(data,2),gm.nmix);
  for i=1:gm.nmix
    mixprob[:,i] = logprob(gm.mix[i],data);
  end
  maxprob = maximum(mixprob,2);
  broadcast!(-,mixprob,mixprob,maxprob);
  p .= vec(log.((exp.(mixprob))*gm.wts) .+ maxprob);
  return nothing
end

function logprob(gm::gmm{T},data::Matrix{T}) where T <: AbstractFloat
  mixprob = zeros(eltype(data),size(data,2),gm.nmix);
  for i=1:gm.nmix
    mixprob[:,i] = logprob(gm.mix[i],data);
  end
  maxprob = maximum(mixprob,2);
  broadcast!(-,mixprob,mixprob,maxprob);
  return vec(log.((exp.(mixprob))*gm.wts) .+ maxprob);
end

#=
function logprob!(p::Matrix{T},g::gaussian{T},data::Matrix{T}) where T <: AbstractFloat
  logpdf!(reshape(p,length(p)),MvNormal(vec(g.mean),g.cov),data);
  reshape(p,length(p),1);
end
=#

#=
#logpdf and logpdf! using Distributions.jl is faster than this version which uses Distances.jl for Float32 - not using this version for CPU code
function logprob!(p::Matrix{T},g::gaussian{T},data::Matrix{T}) where T <: AbstractFloat
  ndim = size(data,1);
  icov = inv(g.cov);
  lconst = ndim*log(2*pi) + log(det(g.cov));
  pairwise!(p,SqMahalanobis(icov),data,g.mean);
  p .+= lconst;
  p .*= -0.5;
  return nothing
end

function logprob(g::gaussian{T},data::Matrix{T}) where T <: AbstractFloat
  p = zeros(eltype(data),size(data,2),1);
  ndim = size(data,1);
  icov = inv(g.cov);
  lconst = ndim*log(2*pi) + log(det(g.cov));
  pairwise!(p,SqMahalanobis(icov),data,g.mean);
  p .+= lconst;
  p .*= -0.5;
  return p
end
=#

end


# Some global variables that will stay constant
α = 0.013
α_q = (1-(1-α)^3)
b_param = 0.0124
d_param = 0.00822
β = 0.98
γ = 1.0
σ = 2.0

# The default wage distribution: a discretized log normal
log_wage_mean, wage_grid_size, max_wage = 20, 200, 170
w_vec = linspace(1e-3, max_wage, wage_grid_size + 1)
logw_dist = Normal(log(log_wage_mean), 1)
cdf_logw = cdf.(logw_dist, log.(w_vec))
pdf_logw = cdf_logw[2:end] - cdf_logw[1:end-1]
p_vec = pdf_logw ./ sum(pdf_logw)
w_vec = (w_vec[1:end-1] + w_vec[2:end]) / 2

"""
Compute the reservation wage, job finding rate and value functions of the
workers given c and τ.

"""
function compute_optimal_quantities(c::AbstractFloat, τ::AbstractFloat)
    mcm = McCallModel(α_q,
                      β,
                      γ,
                      c-τ,                # post-tax compensation
                      σ,
                      collect(w_vec-τ),  # post-tax wages
                      p_vec)

    w_bar, V, U = compute_reservation_wage(mcm, return_values=true)
    λ = γ * sum(p_vec[w_vec - τ .> w_bar])

    return w_bar, λ, V, U
end

"""
Compute the steady state unemployment rate given c and tau using optimal
quantities from the McCall model and computing corresponding steady state
quantities

"""
function compute_steady_state_quantities(c::AbstractFloat, τ::AbstractFloat)
    w_bar, λ_param, V, U = compute_optimal_quantities(c, τ)

    # Compute steady state employment and unemployment rates
    lm = LakeModel(λ=λ_param, α=α_q, b=b_param, d=d_param)
    x = rate_steady_state(lm)
    u_rate, e_rate = x

    # Compute steady state welfare
    w = sum(V .* p_vec .* (w_vec - τ .> w_bar)) / sum(p_vec .* (w_vec - τ .> w_bar))
    welfare = e_rate .* w + u_rate .* U

    return u_rate, e_rate, welfare
end

"""
Find tax level that will induce a balanced budget.

"""
function find_balanced_budget_tax(c::Real)
    function steady_state_budget(t::Real)
      u_rate, e_rate, w = compute_steady_state_quantities(c, t)
      return t - u_rate * c
    end

    τ = brent(steady_state_budget, 0.0, 0.9 * c)

    return τ
end

# Levels of unemployment insurance we wish to study
Nc = 60
c_vec = linspace(5.0, 140.0, Nc)

tax_vec = Vector{Float64}(Nc)
unempl_vec = Vector{Float64}(Nc)
empl_vec = Vector{Float64}(Nc)
welfare_vec = Vector{Float64}(Nc)

for i = 1:Nc
    t = find_balanced_budget_tax(c_vec[i])
    u_rate, e_rate, welfare = compute_steady_state_quantities(c_vec[i], t)
    tax_vec[i] = t
    unempl_vec[i] = u_rate
    empl_vec[i] = e_rate
    welfare_vec[i] = welfare
end

fig, axes = subplots(2, 2, figsize=(15, 10))

plots = [unempl_vec, empl_vec, tax_vec, welfare_vec]
titles = ["Unemployment", "Employment", "Tax", "Welfare"]

for (ax, plot, title) in zip(axes, plots, titles)
    ax[:plot](c_vec, plot, "b-", lw=2, alpha=0.7)
    ax[:set](title=title)
end

fig[:tight_layout]()


using FastGaussQuadrature
include("chebyshev.jl")
include("pgrid.jl")

idx(ki, xi, order) = (ki - 1) * order + xi

struct CompositeGrid
    grid::Vector{Float64} # panel x finegrid
    wgrid::Vector{Float64}
    panel::Vector{Float64}
    x::Vector{Float64}
    w::Vector{Float64}
    # xp::Matrix{Float64} # the fine grid
    # wp::Matrix{Float64} # the weight of the fine grid
    Np::Int  # number of panel - 1
    order::Int  # number of fine grid
    type::Symbol
    function CompositeGrid(panel, order, type::Symbol)
        if type == :cheb
            x, w = barychebinit(order)
        elseif type == :gaussian
            x, w = gausslegendre(order)
        else
            throw("not implemented!")
        end

        # println("Quadrature type : ", type)
        # println("Quadrature points : ", x)
        # println("Quadrature weights: ", w)

        Np = length(panel) - 1

        grid = zeros(Float64, Np * order)
        wgrid = zeros(Float64, Np * order)
        # xp = zeros(Float64, (Np, order))
        # wp = zeros(Float64, (Np, order))

        for p in 1:Np
            a, b = panel[p], panel[p + 1]
            for xi in 1:order
                index = idx(p, xi, order)
                grid[index] = (a + b) / 2 + (b - a) / 2 * x[xi]
                wgrid[index] = (b - a) / 2 * w[xi]
            end
        end

        return new(grid, wgrid, panel, x, w, Np, order, type)
    end
end

function KPanel(Nk, kF, maxK, minK)
    panel = Grid.boseKUL(0.5 * kF, maxK, minK, Nk, 1).grid
    panel[1] = 0.0  # the kgrid start with 0.0
    return panel
end

function QPanel(Nk, kF, maxK, minK, k)
    panel = pGrid(k, kF, maxK, minK, [Nk,Nk,Nk,Nk]).grid

    # panel = Grid.boseKUL(0.5 * kF, maxK, minK, Nk, 1).grid
    # panel[1] = 0.0  # the kgrid start with 0.0

    return panel
end

"""
interpolate!(f, k::CompositeGrid, grid)

map f array in the grid k to a new grid

# Arguments
- f::Vector{Float64}: vector of data.
- k::CompositeGrid: the grid object that f is defined on
- grid::Vector{Float64}: new grid points
"""
function interpolate(f, k::CompositeGrid, grid)
    @assert k.type == :cheb
    order = k.order
    ff = zeros(eltype(f), length(grid))
    kpidx = 1 # panel index of the kgrid
    head, tail = idx(kpidx, 1, order), idx(kpidx, order, order)
    # extract all x in the kpidx-th k panel
    fx = @view f[head:tail] # all F in the same kpidx-th K panel
    x = @view k.grid[head:tail]
    w = @view k.wgrid[head:tail]

    for (qi, q) in enumerate(grid)
        # for a given q, one needs to find the k panel to do interpolation
        if q > k.panel[kpidx + 1]
            # if q is too large, move k panel to the next
            # println("before $q, $kpidx, $(k.panel[kpidx]) -> $(k.panel[kpidx + 1])")
            while q > kgrid.panel[kpidx + 1]
                kpidx += 1
            end
            # println("after $q, $kpidx, $(k.panel[kpidx]) -> $(k.panel[kpidx + 1])")
            head, tail = idx(kpidx, 1, order), idx(kpidx, order, order)
            fx = @view f[head:tail] # all F in the same kpidx-th K panel
            x = @view k.grid[head:tail]
            w = @view k.wgrid[head:tail]
            @assert kpidx <= k.Np
        end
        ff[qi] = barycheb(order, q, fx, w, x) # the interpolation is independent with the panel length
        # @assert k.panel[kpidx - 1][order] <= q <= k.panel[pidx + 1][1] "$q for kpidx=$kpidx with $x"
    end
    return ff
end

if abspath(PROGRAM_FILE) == @__FILE__
    grid=CompositeGrid([0.0, 1.0, 2.0], 4, :cheb)
    println(grid.grid)
end


# ----------
# Vector Calculus Module, Only work on peroideric boundary!
# ----------

function Curl(B1::Array,B2::Array,B3::Array;
              Lx = 2π, Ly = Lx, Lz = Lx,T = Float32)
    nx,ny,nz = size(B1);
    grid = ThreeDGrid(nx, Lx, ny, Ly, nz, Lz, T = T);
    cB1,cB2,cB3 = Curl(B1,B2,B3,grid;Lx = Lx, Ly = Ly, Lz = Lz,T = T)
    return cB1,cB2,cB3;
end

function Curl(B1::Array,B2::Array,B3::Array,grid;
              Lx = 2π, Ly = Lx, Lz = Lx,T = Float32)
    #funtion of computing ∇×Vector using the fourier method
    # fft(∇×Vector) -> im * k × V
    #| i j k  |
    #| x y z  |
    #|B1 B2 B3|
    #
    nx,ny,nz = size(B1);
    B1h = zeros(Complex{T},(div(nx,2)+1,ny,nz));
    B2h = zeros(Complex{T},(div(nx,2)+1,ny,nz));
    B3h = zeros(Complex{T},(div(nx,2)+1,ny,nz));
    cBxh = copy(B1h);
    cByh = copy(B2h);
    cBzh = copy(B3h);
    mul!(B1h, grid.rfftplan, B1);
    mul!(B2h, grid.rfftplan, B2);
    mul!(B3h, grid.rfftplan, B3);

    for k in 1:nz, j in 1:ny,i in 1:div(nx,2)+1
       x,y,z = grid.kr[i],grid.l[j],grid.m[k];
       cBxh[i,j,k] = im*(y*B3h[i,j,k] - z*B2h[i,j,k]);
       cByh[i,j,k] = im*(z*B1h[i,j,k] - x*B3h[i,j,k]);
       cBzh[i,j,k] = im*(x*B2h[i,j,k] - y*B1h[i,j,k]);
    end

    cB1,cB2,cB3 = zeros(T,size(B1)),zeros(T,size(B1)),zeros(T,size(B1));
    ldiv!(cB1, grid.rfftplan, deepcopy(cBxh));
    ldiv!(cB2, grid.rfftplan, deepcopy(cByh));
    ldiv!(cB3, grid.rfftplan, deepcopy(cBzh));
    return cB1,cB2,cB3
end

function Div(B1::Array,B2::Array,B3::Array;
             Lx = 2π, Ly = Lx, Lz = Lx,T = Float32)
    nx,ny,nz = size(B1);
    grid = ThreeDGrid(nx, Lx, ny, Ly, nz, Lz, T = T);
    cB1 = Div(B1,B2,B3,grid;Lx = Lx, Ly = Ly, Lz = Lz,T = T);

    return cB1
end

function Div(B1::Array,B2::Array,B3::Array,grid;
             Lx = 2π, Ly = Lx, Lz = Lx,T = Float32)
    #funtion of computing ∇̇ ⋅ Vector using the fourier method
    # fft(∇·Vector) -> im * k ⋅ V
    # = im* x*B1 + y*B2 + z*B3
    nx,ny,nz = size(B1);
    B1h = zeros(Complex{T},(div(nx,2)+1,ny,nz));
    B2h = zeros(Complex{T},(div(nx,2)+1,ny,nz));
    B3h = zeros(Complex{T},(div(nx,2)+1,ny,nz));
    Dot = copy(B1h);
    mul!(B1h, grid.rfftplan, B1);
    mul!(B2h, grid.rfftplan, B2);
    mul!(B3h, grid.rfftplan, B3);

    for k in 1:nz, j in 1:ny,i in 1:div(nx,2)+1
       x,y,z = grid.kr[i],grid.l[j],grid.m[k];
       Dot[i,j,k] = x*B1h[i,j,k] + y*B2h[i,j,k] + z*B3h[i,j,k];
    end

    cB1 = zeros(T,size(B1))
    ldiv!(cB1, grid.rfftplan, deepcopy(Dot));

    return cB1
end

function LaplaceSolver(B::Array; Lx=2π, Ly = Lx, Lz = Lz, T = Float32)
    nx,ny,nz = size(B);
    grid = ThreeDGrid(nx, Lx, ny, Ly, nz, Lz, T = T);
    Φ   = LaplaceSolver(B,grid; Lx=2π, Ly = Lx, Lz = Lz, T = Float32);
    return Φ
end

function LaplaceSolver(B::Array,grid; Lx=2π, Ly = Lx, Lz = Lz, T = Float32)
    #=
    funtion of computing ΔΦ = B using the fourier method, must be peroidic condition
    Considering in k-space, k² Φ' = B', we would get Φ = F(B'/k²)
    =#
    nx,ny,nz = size(B);
    Φ    = zeros(T,nx,ny,nz);
    Bh   = zeros(ComplexF32,(div(nx,2)+1,ny,nz));
    mul!(Bh, grid.rfftplan, B);
    for k in 1:nz, j in 1:ny, i in 1:div(nx,2)+1
       x,y,z = grid.kr[i],grid.l[j],grid.m[k];
       k² = x^2 + y^2 + z^2;
       Bh[i,j,k] = Bh[i,j,k]/k²;
       if k² == 0; Bh[i,j,k] = 0; end
    end
    ldiv!(Φ, grid.rfftplan, deepcopy(Bh));
    return Φ;
end

function Crossproduct(A1,A2,A3,B1,B2,B3)
    C1 = @.  (A2*B3 - A3*B2);
    C2 = @. -(A1*B3 - A3*B1);
    C3 = @.  (A1*B2 - A2*B1);
    return C1,C2,C3
end

function Dotproduct(A1,A2,A3,B1,B2,B3)
    return A1.*B1 + A2.*B2 + A3.*B3
end


export ShepardInterpolator, RBFInterpolator
export multiquadratic, invmultiquadratic, gaussian

using LinearAlgebra: Symmetric

#-------------------------------------------------------------------------------

function distance(a, b, n::Int)
    r = 0.0
    for i = 1:n
        @inbounds d = a[i] - b[i]
        r += d*d
    end
    sqrt(r)
end

function checkcoords(X, y)
    @assert size(X)[2] > 1 "Generally it doesn't make sense to apply scattered interpolation methods in one dimension. Consider using a 1D interpolator."
    p = size(X)[1]
    n = length(y)
    @assert p == n "number of points in X ($p) does not match number of values in y ($n)"
end

function checkdims(x, N::Int)
    n = length(x)
    @assert n == N "Number of interpolation coordinates ($n) does not match number of interpolator dimensions ($N)"
end

struct ScatteredPoints{T}
    #number of dimensions
    N::Int64
    #number of points
    p::Int64
    #table of points, p x N
    X::Array{T,2}
    #vector of values
    y::Vector{T}
end

function ScatteredPoints(X, y)
    checkcoords(X, y)
    N = size(X)[2]
    p = size(X)[1]
    ScatteredPoints(N, p, X, y)
end

#-------------------------------------------------------------------------------

"""
    multiquadratic(r, ϵ)

``\\sqrt{1 + (r/ϵ)^2}``
"""
multiquadratic(r, ϵ) = sqrt(1 + (r/ϵ)^2)

"""
    invmultiquadratic(r, ϵ)

``\\frac{1}{\\sqrt{1 + (r/ϵ)^2}}``
"""
invmultiquadratic(r, ϵ) = 1/multiquadratic(r, ϵ)

"""
    gaussian(r, ϵ)

``e^{-r^2/ϵ^2}``
"""
gaussian(r, ϵ) = exp(-r^2/ϵ^2)

struct RBFInterpolator{T,F}
    #interpolation points
    S::ScatteredPoints{T}
    #radial basis function
    rbf::F
    #distance scale factor
    ϵ::T
    #function weights
    w::Vector{T}
end

"""
    RBFInterpolator(X, y, ϵ, rbf=invmultiquadratic)

Construct a radial basis function (RBF) interpolator for an n-dimensional set of points with coordinates `X` and values `y`. `X` must be an p × N array, where p is the number of points and N is the number of dimensions. `y` must be a length p vector. The value of `ϵ` scales the radial basis function of choice, `f`, which is [`invmultiquadratic`](@ref) by default. Any function in the form ``ϕ(r,ϵ)`` can be passed to the `rbf` argument, where \$r\$ is the distance between points and ``ϵ`` is a scaling factor.
"""
function RBFInterpolator(X::AbstractMatrix,
                         y::AbstractVector,
                         ϵ,
                         rbf::F=multiquadratic) where {F}
    S = ScatteredPoints(X, y)
    #matrix of basis function evaluations
    A = ones(eltype(y), S.p, S.p)
    for i = 1:S.p
        a = view(S.X,i,:)
        for j = i+1:S.p
            b = view(S.X,j,:)
            r = distance(a, b, S.N)
            A[i,j] = rbf(r, ϵ)
        end
    end
    #solve for weights
    w = Symmetric(A)\S.y
    #construct
    RBFInterpolator(S, rbf, convert(eltype(y), ϵ), w)
end

function RBF(Φ::RBFInterpolator, x)
    checkdims(x, Φ.S.N)
    y = 0.0
    for i = 1:Φ.S.p
        #distance
        r = distance(x, view(Φ.S.X,i,:), Φ.S.N)
        #weighted evaluation
        @inbounds y += Φ.w[i]*Φ.rbf(r, Φ.ϵ)
    end
    return y
end

function (Φ::RBFInterpolator)(x::Union{AbstractVector,Tuple})
    RBF(Φ, x)
end

function (Φ::RBFInterpolator)(x::Number...)
    RBF(Φ, collect(Float64, x))
end

#-------------------------------------------------------------------------------

struct ShepardInterpolator{T}
    #interpolation points
    S::ScatteredPoints{T}
    #power-law exponent
    a::Float64
end

"""
    ShepardInterpolator(X, y, a=3)

Construct a `ShepardInterpolator` for an n-dimensional set of points with coordinates `X` and values `y`. `X` must be an p × N array, where p is the number of points and N is the number of dimensions. `y` must be a length p vector. The value of `a` defines the distance weighting function ``r^{-a}``.
"""
function ShepardInterpolator(X::AbstractMatrix,
                             y::AbstractVector,
                             a::Real=3.0)
    ShepardInterpolator(ScatteredPoints(X, y), a)
end

function shepard(Φ::ShepardInterpolator, x)
    checkdims(x, Φ.S.N)
    n = 0.0
    d = 0.0
    for i = 1:Φ.S.p
        #distance
        r = distance(x, view(Φ.S.X,i,:), Φ.S.N)
        #evaluate weighting function
        f = 1.0/r^Φ.a
        #weigted and unweighted contribution to numertor and denominator
        @inbounds n += Φ.S.y[i]*f
        d += f
    end
    return n/d
end

(Φ::ShepardInterpolator)(x::Union{AbstractVector,Tuple}) = shepard(Φ, x)

(Φ::ShepardInterpolator)(x::Number...) = shepard(Φ, x)


using LinearAlgebra
using FinEtools
using PGFPlotsX

objects = []

E = 200*phun("GPa");
nu = 0.3;
G = E/2/(1+nu)
a = 1.0*phun("m");
t = a/5
D = E*t^3/12/(1-nu^2)
P = 1.0
a = 1.0
r = collect(t/100000:t/10000:t)
w_b = @. P/16/pi/D * (2*r^2 * log(r/a) + (3+nu)/(1+nu)*(a^2 - r^2))
w_s = @. -P/4/pi/G/t * log(r/a)
max_w_b = maximum(w_b)

@pgf p = PGFPlotsX.Plot(
{
color = "black",
line_width  = 0.7,
style = "solid"
},
Coordinates([v for v in  zip(r./t, w_b./max_w_b)])
)
push!(objects, p)
push!(objects, LegendEntry("Bending only"))

@pgf p = PGFPlotsX.Plot(
{
color = "red",
line_width  = 0.7,
style = "solid"
},
Coordinates([v for v in  zip(r./t, (w_b+w_s)./max_w_b)])
)
push!(objects, p)
push!(objects, LegendEntry("Bending plus shear"))

@pgf ax = Axis(
    {
        xlabel = "Normalized Distance [ND]",
        ylabel = "Normalized Displacement [ND]",
        # xmin = range[1],
        # xmax = range[2],
        xmode = "linear",
        ymode = "linear",
        yminorgrids = "true",
        grid = "both",
        legend_style = {
            at = Coordinate(0.5, 1.05),
            anchor = "south",
            legend_columns = -1
        },
    },
    objects...
)

display(ax)
pgfsave("deflections.pdf", ax)


using Discrete
using Base.Test

# write your own tests here
@test 1 == 1

println("Differencing tests...");
de = (n) -> 2.0^n;
for i=1:10000
  n = rand(0:100);

  @test de(n) == fdiff(de, n);
  @test de(n) == fdiff(de, n, order=2);
  @test de(n) == fdiff(de, n, order=3);
  @test de(n) == fdiff(de, n, order=4);
  @test de(n) == fdiff(de, n, order=rand(1:10));
  @test de(n) == bdiff(de, n);
  @test de(n) == bdiff(de, n, order=2);
  @test de(n) == bdiff(de, n, order=3);
  @test de(n) == bdiff(de, n, order=4);
  @test de(n) == bdiff(de, n, order=rand(1:10));
end

f(n)   = n^2;
df(n)  = 2*n + 1;
dbf(n) = 2*n - 1;
d2f(n) = 2;
for i=1:10000
  n = rand(0:100);

  @test df(n)  == fdiff(f, n);
  @test d2f(n) == fdiff(f, n, order=2);
  @test dbf(n) == bdiff(f, n);
  @test d2f(n) == bdiff(f, n, order=2);
end
println("Tests passed.");

println("Integration tests...");
for n = 4:2:8
  @test abs(simpsons(x -> 1/x, 1, e, n) - 1.0) < 10.0^(-n/2);
  @test abs(trapezoid(x -> 1/x, 1, e, n) - 1.0) < 2 * 10.0^(-n/4);

  @test abs(simpsons(x -> sin(x), -π, π, n) - 0.0) < 10.0^(-n/2);
  @test abs(trapezoid(x -> sin(x), -π, π, n) - 0.0) < 2 * 10.0^(-n/4);
  @test abs(simpsons(x -> cos(x), -π, π, n) - 0.0) < 10.0^(-n/2);
  @test abs(trapezoid(x -> cos(x), -π, π, n) - 0.0) < 2 * 10.0^(-n/4);

  @test abs(simpsons(x -> x, -1, 1, n) - 0.0) < 10.0^(-n/2);
  @test abs(trapezoid(x -> x, -1, 1, n) - 0.0) < 3 * 10.0^(-n/4);
  @test abs(simpsons(x -> x^2, -1, 1, n) - 2/3) < 10.0^(-n/2);
  @test abs(trapezoid(x -> x^2, -1, 1, n) - 2/3) < 3 * 10.0^(-n/4);
end
for n = 10:2:20
  @test abs(simpsons(x -> 1/x, 1, e, n) - 1.0) < 5e-5;
  @test abs(trapezoid(x -> 1/x, 1, e, n) - 1.0) < 5e-2;

  @test abs(simpsons(x -> sin(x), -π, π, n) - 0.0) < 5e-5;
  @test abs(trapezoid(x -> sin(x), -π, π, n) - 0.0) < 5e-2;
  @test abs(simpsons(x -> cos(x), -π, π, n) - 0.0) < 5e-5;
  @test abs(trapezoid(x -> cos(x), -π, π, n) - 0.0) < 5e-2;

  @test abs(simpsons(x -> x, -1, 1, n) - 0.0) < 5e-5;
  @test abs(trapezoid(x -> x, -1, 1, n) - 0.0) < 5e-2;
  @test abs(simpsons(x -> x^2, -1, 1, n) - 2/3) < 5e-5;
  @test abs(trapezoid(x -> x^2, -1, 1, n) - 2/3) < 5e-2;
end
println("Tests passed.");


"
  Name     : c4_17_QuantEco_julia.jl
  Book     : Hands-on Data Science with Anaconda )
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan and James Yan
  Date     : 1/25/2018
  email    : yany@canisius.edu
             paulyxy@hotmail.com

            https://lectures.quantecon.org/jl/julia_plots.html
"

using QuantEcon: meshgrid
using PyPlot:surf
using Plots
n = 50
x = linspace(-3, 3, n)
y = x
z = Array{Float64}(n, n)
f(x, y) = cos(x^2 + y^2) / (1 + x^2 + y^2)
for i in 1:n
    for j in 1:n
        z[j, i] = f(x[i], y[j])
    end
end
xgrid, ygrid = meshgrid(x, y)
surf(xgrid, ygrid, z',alpha=0.7)


export getBoundModel,getBoundModelInv
function getBoundModel(m,a,b)
	d = (b-a)./2.0;
	mid = (b+a)./2.0;
	v = (1.0./d).*(m - mid);
	s = d.*(tanh.(v)+1) + a;
	dsdm = spdiagm(sech.(v).^2)
	return s, dsdm
end

function getBoundModelInv(s,a,b)
	d = (b-a)./2.0;
	mid = (b+a)./2.0;
    m = atanh.((s-a).*(1.0./d)-1) .* d + mid;
	return m
end


library( Matrix )

str_path <- "C:\\CBIG\\ZD\\"

myd <- read.csv( paste0( str_path, "logistic_regression_ex2.csv" ) )

x <- as.matrix( myd[, -c( 1 )] )
y <- myd[, c(1)]

## make sure its rank = # columns
ncol( x )
rankMatrix( x )

myglm <- glm( y ~ -1 + x, family = binomial( link = logit ) )

myglm_coeff <- summary( myglm )$coefficients

rownames( myglm_coeff ) <- c( paste0( "beta", seq( 1, 3 ) ), paste0( "creative_break_", seq( 1, 4 ) ) )

print( myglm_coeff )

## using optim to reproduce the estimates

Loglikelihood <- function( beta )
{
	xbeta <- x %*% beta
	Li <- -1 * y * xbeta + log( 1 + exp( xbeta ))
	sum( Li )
}

beta_start_value <- as.matrix( rep( 0.0, ncol( x ) ), ncol(x), 1 )

myoptim <- optim( beta_start_value, fn = Loglikelihood, gr = NULL, method = "BFGS", hessian = TRUE )

var_cov_m <- solve( myoptim$hessian )

beta_se <- sqrt( diag( var_cov_m ) )

zvalue <- myoptim$par / beta_se

pvalue <- 2 * ( 1 - pnorm(abs( zvalue ) ) )

coeff_outputs <- cbind( myoptim$par, beta_se, zvalue, round( pvalue, 6 ) )

colnames( coeff_outputs ) <- c( "Estimates", "Std. Error", "z value", "Pr(>|z|)" )
rownames( coeff_outputs ) <- c( paste0( "beta", seq( 1, 3 ) ), paste0( "creative_break_", seq( 1, 4 ) ) )
print( coeff_outputs )

print( summary( myglm )$coefficients )

#######################################################################

## this is the residual deviance
2 * myoptim$value

########## since colmn 4 to column 7 are indicators, assuming they are the compaign breaks :)
##### so we want to add penalty function to make the estimates >= 0, using the penalty that Zhiyuan recommended

## assuming the penalty function is :

a <- c( 1000, 9, 60 )

Penalized_Loglikelihood <- function( beta )
{
	xbeta <- x %*% beta
	Li <- -1 * y * xbeta + log( 1 + exp( xbeta ))
	## constraining beta[3] to beta[7]
	myPenValue <- 0
	for ( i in 4 : 7 )
	{
		myPenValue <- myPenValue + a[1] * exp( -1.0 * a[2] * exp( a[3] * beta[ i ] ) )
	}
	sum( Li ) + myPenValue
}

beta_start_value <- as.matrix( rep( 0.0, ncol( x ) ), ncol(x), 1 )

myoptim_Penalized <- optim( beta_start_value, fn = Penalized_Loglikelihood, gr = NULL, method = "BFGS", hessian = TRUE)

myoptim_Penalized

var_cov_m <- solve( myoptim_Penalized$hessian )

beta_se <- sqrt( diag( var_cov_m ) )

zvalue <- myoptim_Penalized$par / beta_se

pvalue <- 2 * ( 1 - pnorm(abs( zvalue ) ) )

coeff_outputs <- cbind( myoptim_Penalized$par, beta_se, zvalue, round( pvalue, 6 ) )

colnames( coeff_outputs ) <- c( "Estimates", "Std. Error", "z value", "Pr(>|z|)" )
rownames( coeff_outputs ) <- c( paste0( "beta", seq( 1, 3 ) ), paste0( "creative_break_", seq( 1, 4 ) ) )

print( coeff_outputs )


function max_divisor(number::Integer) ::Integer
    return Integer(floor(sqrt(number)))
end

function is_prime(number::Integer) ::Bool
    max::Integer = max_divisor(number)
    # False if any number evenly divides it.
    for x=2:max
        if number % x == 0
            return false
        end
    end
    # true otherwise.
    return true
end

function next_prime(number::Integer) ::Integer
    to_check = number
    while true
        to_check += 1
        if is_prime(to_check)
            return to_check
        end
    end
end

function first_n_primes(number::Integer) ::Vector{<:Integer}
    last::Integer = 0
    primes = Vector{Integer}()
    for n=1:number
        last = next_prime(last)
        push!(primes, last)
    end
    return primes
end

function first_n_twin_primes(number::Integer) ::Vector{<:Integer}
    last::Integer = 0
    primes = Vector{Integer}()
    for n=3:number
        while true
            last = next_prime(last)
            if is_prime(last + 2)
                push!(primes, last)
                push!(primes, last+2)
                last += 2
                break
            end
        end
    end
    return primes
end

sum(x^2 for x in first_n_twin_primes(10000))


#
# NBody calculations
#

"""
Returns total energy for `NBodySystem`.
"""
function system_energy(sys::NBodySystem)

    E = 0.0u"J"
    for i = 1:length(sys.body)

        E += sys.body[i].m * dot(sys.body[i].v̅, sys.body[i].v̅)
        for j = 1:length(sys.body)

            if i ≠ j
                E -= 6.6743e-11u"m^3/(kg*s^2)" * (sys.body[i].m * sys.body[j].m) /
                                  norm(sys.body[j].r̅ .- sys.body[i].r̅)

            end
        end
    end

    return E

end

"""
Returns total angular momentum for `NBodySystem`.
"""
function system_angular_momentum(sys::NBodySystem)

    H = reduce(+, [sys.body[i].m * cross(sys.body[i].r̅, sys.body[i].v̅) for i ∈ 1:length(sys.body)])

end


let
    f(X) = (10 - X[1, 1])^2 + (0 - X[1, 2])^2 + (0 - X[2, 1])^2 + (5 - X[2, 2])^2

    function g!(X, S)
        S[1, 1] = -20 + 2 * X[1, 1]
        S[1, 2] = 2 * X[1, 2]
        S[2, 1] = 2 * X[2, 1]
        S[2, 2] = -10 + 2 * X[2, 2]
        return
    end

    res = optimize(f, g!, eye(2), method = GradientDescent())

    @test norm(vec(Optim.minimizer(res) - [10.0 0.0; 0.0 5.0])) < 10e-8

    # TODO: Get finite differencing to work for generic arrays as well
    # optimize(f, eye(2), method = :gradient_descent)
end


#=
We shall say that an n-digit number is pandigital if it makes use of all the digits 1 to n exactly once. For example, 2143 is a 4-digit pandigital and is also prime.

What is the largest n-digit pandigital prime that exists?
=#
include("projecteulerutils.jl")
using Combinatorics
using Primes

function calc()
  largestpandigitalprime = 1
  for n in 2:9
    digit_arr = [i for i in 1:n]
    for permutation in permutations(digit_arr)
      x = nd(permutation)
      if isprime(x) && x > largestpandigitalprime
        largestpandigitalprime = x
      end
    end
  end
  largestpandigitalprime
end
@time println(calc())


# # Ice Flow over a Bumpy Bed

# This is the first two dimensional example: flow down a plane with bumps on different length scales.
# This example is similar in spirit to Experiment A in the Ice Sheet Model Intercomparison Exercise - Higher Order Model (ISMIP-HOM): doi: 10.5194/tc-2-95-2008
# This example demonstrates: How to use WAVI.jl in plan view (two horizontal spatial dimensions) with arbitrary bed shapes, and how to interact with Grid objects.

# ## Install dependencies
#
# First let's make sure we have all required packages installed.

# using Pkg
# Pkg.add("https://github.com/RJArthern/WAVI.jl"), Pkg.add(Plots)
using WAVI, Plots

# ## Basal Topography
#
# Following ISMIP-HOM, let's take a bed with a series of sinusoidal oscillations with an amplitude of 500m:
# $$
# z_b(x,y) = -x \tan \alpha + 500 \sin (\omega x) \sin(\omega y)
# $$
# Here $L$ is the lengthscale of the domain, $\alpha$ is the net slope of the plane, and $\omega = 2\pi / L$ is the frequency of the bumps.
# Let's define this bed as a function and take a look at it:

z_b(x,y; α, ω) = -x * tand(α) + 500sin(ω*x)*sin(ω*y);

# It's useful to define our model grid here. We can do this for an arbitary domain length L.
grid(; L, nx = 80,ny = 80) = Grid(nx = nx, ny = ny, dx = L/nx, dy = L/ny, y0 = 0.0, x0 = 0.0);

# The final two arguments simply set the origin of the co-ordinate system. This grid has 80 grid points in each direction by default

# Let's choose a domain of 80km with 80 grid points
L = 80000.;
grid80 = grid(L = L);

# This grid object contains information about the location of grid points. We use this to construct and array defining the bed:
z_b80 = z_b.(grid80.xxh,grid80.yyh; α = 0.5, ω = 2π/L );
plt = Plots.heatmap(grid80.xxh[:,1]/1e3, grid80.yyh[1,:]/1e3, z_b80,
                        xlabel = "x (km)",
                        ylabel = "y (km)",
                        colorbar_title = "bed depth (m)")
plot!(size = (800,400))
#display(plt)

# ## Model Instantiation and Initial Conditions
# In the ISMIP-HOM comparison, the main test is velocity along the line $y = L/4$, for various different values of $L$.
# Before we do that, lets look at the velocity for the example we started above with $L = 80$km.

# To begin, we create an `InitialConditions` object to prescribe the ice thickness of 1000m everywhere
initial_conditions = InitialConditions(initial_thickness = 1000. .* ones(grid80.nx, grid80.ny));

# Now we can build our model
model80 = Model(grid = grid80,
            bed_elevation = z_b80,
            initial_conditions = initial_conditions);

# ## Determining the velocity
# To bring the velocity in line with the ice thickness, we have to use the `update_state!` function
update_state!(model80);

# Let's have a look at the velocity component in the x-direction
Plots.heatmap(model80.grid.xxh[:,1]/1e3, model80.grid.yyh[1,:]/1e3, model80.fields.gh.u',
                        xlabel = "x (km)",
                        ylabel = "y (km)",
                        colorbar_title = "ice velocity in x-direction (m/yr)")
plot!(size = (1000,550))

# ## Different Lengthscales
# Now let's look at how the velocity along a flowline changes with L.
L_values =  [160, 80, 40, 20, 10, 5]*1.0e3;

# We loop over these values and store the info:
U_flowline = zeros(80, length(L_values));
grid_flowline = zeros(80, length(L_values));
for (count,L) in enumerate(L_values) ;
    gridL = grid(L = L);
    z_bL = z_b.(gridL.xxh,gridL.yyh; α = 0.5, ω = 2π/L );
    initial_conditions = InitialConditions(initial_thickness = 1000. .* ones(80, 80));
    model = Model(grid = gridL,
                bed_elevation = z_bL,
                initial_conditions = initial_conditions);
    update_state!(model);
    grid_flowline[:, count] .= model.grid.xxh[:, round(Int, gridL.nx/4)];
    U_flowline[:,count] .= model.fields.gh.u[:, round(Int, gridL.nx/4)];
end

#And make the plot in a single command
plot(grid_flowline/1e3,
    U_flowline,
    layout = (2,3),
    framestyle = :box,
    xlabel = "x (km)",
    ylabel = "horizontal velocity (m/yr)",
    label = :none,
    title =  ["160km" "80km" "40km" "20km" "10km" "5km"])
plot!(size = (1000,550))


export
    istriangular,
    nthtriangular, ntriangular,
    alltriangular, sometriangular, exacttriangular,
    primitive_pythagorean_triples

istriangular(n::Integer) = isperfectsquare(8*n + 1)

nthtriangular(n::Integer) = (n * (n+1)) ÷ 2
ntriangular(n::Int, T::Type = Int) = collect(exacttriangular(n, T))

alltriangular(T::Type = Int) = TriangularIterator{T}()
sometriangular(xmax::T) where {T<:Integer} = @pipe alltriangular(T) |> takewhile(x -> x <= xmax, _)
exacttriangular(n::Int, T::Type = Int) = @pipe alltriangular(T) |> take(_, n)

struct TriangularIterator{T<:Integer}
end

Base.start(::TriangularIterator{T}) where {T<:Integer} = zero(T), one(T)
Base.next(::TriangularIterator{T}, state) where {T<:Integer} = begin
    s, n = state
    s += n
    s, (s, n + 1)
end
Base.done(::TriangularIterator, _) = false

Base.eltype(it::TriangularIterator) = Base.eltype(typeof(it))
Base.eltype(::Type{TriangularIterator{T}}) where {T<:Integer} = T

Base.iteratorsize(::TriangularIterator) = Base.IsInfinite()

primitive_pythagorean_triples(T::Type = Int) = PrimitivePythagoreanTripleIterator{T}()
primitive_pythagorean_triples(maxperimeter::T) where {T} = PrimitivePythagoreanTriplePerimeterIterator{T}(maxperimeter)

abstract type PrimitivePythagoreanTripleIteratorBase{T<:Integer} end

struct PrimitivePythagoreanTripleIterator{T<:Integer} <: PrimitivePythagoreanTripleIteratorBase{T}
end

struct PrimitivePythagoreanTriplePerimeterIterator{T<:Integer} <: PrimitivePythagoreanTripleIteratorBase{T}
    maximum::T
end

Base.start(::PrimitivePythagoreanTripleIterator{T}) where {T} = begin
    q = List{Array{T,1}}()
    n = push!(q, [3,4,5])
    q, n
end
Base.start(it::PrimitivePythagoreanTriplePerimeterIterator{T}) where {T} = begin
    q = List{Array{T,1}}()
    n = it.maximum >= 12 ? push!(q, [3,4,5]) : q.node
    q, n
end

Base.next(::PrimitivePythagoreanTripleIterator, state) = begin
    q, n = state
    n.data, (q, next!(q, n))
end
Base.next(it::PrimitivePythagoreanTriplePerimeterIterator, state) = begin
    q, n = state
    n.data, (q, next!(q, n, it.maximum))
end

Base.done(::PrimitivePythagoreanTripleIteratorBase, state) = begin
    q, n = state
    iseol(q, n)
end

Base.eltype(it::PrimitivePythagoreanTripleIteratorBase) = Base.eltype(typeof(it))
Base.eltype(::Type{PrimitivePythagoreanTripleIterator{T}}) where {T} = Array{T,1}
Base.eltype(::Type{PrimitivePythagoreanTriplePerimeterIterator{T}}) where {T} = Array{T,1}

Base.iteratorsize(::PrimitivePythagoreanTripleIterator) = Base.IsInfinite()
Base.iteratorsize(::PrimitivePythagoreanTriplePerimeterIterator) = Base.SizeUnknown()

iseol(q::List{T}, n::ListNode{T}) where {T} = n == q.node

next!(q::List{T}, n::ListNode{T}) where {T} = begin
    n = n.next
    iseol(q, n) || return n

    x, y, z = calculate(shift!(q)...)
    n = push!(q, x)
    push!(q, y)
    push!(q, z)
    n
end

next!(q::List{T}, n::ListNode{T}, maxsum) where {T} = begin
    n = n.next
    iseol(q, n) || return n

    while iseol(q, n) && !isempty(q)
        x, y, z = calculate(shift!(q)...)
        sum(x) <= maxsum && (nx = push!(q, x); n = nx)
        sum(y) <= maxsum && (ny = push!(q, y); n = iseol(q, n) ? ny : n)
        sum(z) <= maxsum && (nz = push!(q, z); n = iseol(q, n) ? nz : n)
    end
    n
end

calculate(a, b, c) = begin
    x = [ a - 2b + 2c,  2a - b + 2c,  2a - 2b + 3c] # const U = [ 1  2  2, -2 -1 -2, 2 2 3], [a b c] * U
    y = [ a + 2b + 2c,  2a + b + 2c,  2a + 2b + 3c] # const A = [ 1  2  2,  2  1  2, 2 2 3], [a b c] * A
    z = [-a + 2b + 2c, -2a + b + 2c, -2a + 2b + 3c] # const D = [-1 -2 -2,  2  1  2, 2 2 3], [a b c] * D
    x, y, z
end


using Distributions
using IndependentRandomSequences
using Base.Test

#IID tests
W = Uniform(-1,1)
N = 2
Y = IIDRandomSequence(W,N)

@test insupport(Y,[0,0]) == true
@test insupport(Y,[-1,1]) == true
@test insupport(Y,[-2,0]) == false
@test insupport(Y,[0,0,0]) == false

@test pdf(Y,[0., 0.]) == 1/4
@test pdf(Y,[2., 0.]) == 0.
@test pdf(Y,[0. 0. ; 2. 0.]') == [1/4, 0.]

@test mean(Y) == [0., 0.]
@test var(Y) == [1/3, 1/3]

#INID tests, homogenous types
W,X = Uniform(-1,1),Uniform(0,1)
Y = INIDRandomSequence([W,X])

@test insupport(Y,[0,0]) == true
@test insupport(Y,[-1,1/2]) == true
@test insupport(Y,[-2,0]) == false
@test insupport(Y,[0,0,0]) == false

@test pdf(Y,[0., 1/2]) == 1/2
@test pdf(Y,[2., 1/2]) == 0.
@test pdf(Y,[0. 1/2;  2. 0.]') == [1/2, 0.]

@test mean(Y) == [0., 1/2]
@test var(Y) == [1/3, 1/12]

#INID test, hetergeneous types
W,X = Binomial(3,.5),Bernoulli(.5)
Y = INIDRandomSequence([W,X])

@test insupport(Y,[3,0]) == true
@test insupport(Y,[-2,0]) == false
@test insupport(Y,[0,0,0]) == false

@test cov(Y) == [3/4 0;0 1/4]


#The "1/e^2 beamwidth" seems to have inconsistent definitions,
#either meaning the half-width or the full width of the beam
#Here "beamwidth" will refer to the full width, and "sigma" will
#refer to the halfwidth
#Sigma is the width of a gaussian defined as y = e^-(2x^2/sigma^2)
#(this parameterization of the Gaussian is common for optics but not universal. The other
#common convention has a 2 in the denominator of the exponent rather thatn the numerator.
#This is the convention in statistics and thus is how variance and std are defined.  So if we want
#an r.m.s. value we should use this latter convention)
#By the former convention "sigma" is the distance from the origin at which the gaussian
#decays to 1/e^2 of its maximum value, i.e. the halfwidth.
#sigma_statistics = std = sigma_optics/2
#sigma_optics = 2*std

#gaussian approximation to airy disk
#taken from paraxial approximations derived in:
#Gaussian approximations of fluorescence microscope point-spread function models
#Bo Zhang, Josiane Zerubia, and Jean-Christophe Olivo-Marin
#1 April 2007 Vol. 46, No. 10 APPLIED OPTICS
#Note that the authors derive the below expression for "sigma"
#but their definition of sigma differs from ours; they use the statistical convention
#(see comments at top of this file) instead of 1/e^2 convention
#their r.h.s. is    e^-(x^2/2*sigma^2)
#...while ours is   e^-(2x^2/sigma^2)
#thus our axial sigma is twice theirs
approx_sigma_axial(lambda, NA, n) = 4*sqrt(6)*n/(2*pi/lambda*NA^2)
approx_sigma_axial(lambda, f, unfocused_sigma, n) = approx_sigma_axial(lambda, lens_na(unfocused_sigma, f), n)

approx_sigma_lateral(lambda, NA) = sqrt(2)*lambda / (pi*NA)
approx_sigma_lateral(lambda, f, unfocused_sigma) = approx_sigma_lateral(lambda, lens_na(unfocused_sigma, f))
focused_beamwidth(lambda, f, unfocused_beamwidth) = 2*approx_sigma_lateral(lambda, f, unfocused_beamwidth/2)

#assuming a lens has a big enough aperture to accept
#a collimated input of specified halfwidth and also
#that the medium is the same before and after focusing
#what is the NA of the system?
lens_na(unfocused_sigma, f) = unfocused_sigma/f

#Halfwidth of the Gaussian approximation of the axial PSF of a light sheet imaging system,
#considering both the PSF of the imaging lens and the sheet thickness
lightsheet_axial_combined_psf(det_sigma, sheet_sigma) =
    sqrt((det_sigma^2*sheet_sigma^2)/(det_sigma^2 + sheet_sigma^2))

rayleigh_length(lambda, focused_sigma) = pi*focused_sigma^2 / lambda
rayleigh_length(lambda, f, unfocused_sigma, n) =
    rayleigh_length(lambda, approx_sigma_lateral(lambda, f, unfocused_sigma, n))

#sigma of a focused beam as a function of displacement from the beam waist
displaced_sigma(lambda, focused_sigma, displacement) = sqrt(focused_sigma^2*(1+(lambda*displacement/(pi*focused_sigma^2))^2))

#This doesn't match the calculation on the OpenSPIM website.  Seems theirs is incorrect.
fwhm_stats(std) = 2*sqrt(2*log(2)) * std
fwhm_optics(sigma) = fwhm_stats(sigma/2)

#Currently aren't using these
abbe_lat(lambda, NA) = lambda/(2*NA)
abbe_ax(lambda, NA) = 2*lambda/(NA^2)


#<----Bayes Theorem---->
#P = prob, A = prior, B = Evidence,
"""
    ## Bayes Theorem
    ### Description
      Bayes theorem takes probability, prior observations, and evidence
      as percentages and will return a posterior.\n
      --------------------\n
    ### Input
      bay_ther(p, a, b)\n
      --------------------\n
      #### Positional Arguments
      Float64 - p:: A percentage representing probability.\n
      Float64 - a:: A percentage representing prior. <- Float64\n
      Float64 - b:: A percentage representing evidence. <- Float64\n
      --------------------\n
     ### Output
     P:: Posterior value.
       """
function bay_ther(p,a,b)
    psterior = (p*(b|a) * p*(a)) / (p*b)
    return(psterior)
end
"""
    ## Conditional Probability
    ### Description
      Performs Bayesian Conditional Probability with probability, prior, and
      evidence.\n
      --------------------\n
      ### Input
        cond_prob(p, a, b)\n
        --------------------\n
        #### Positional Arguments
        Float64 - p:: A percentage representing probability.\n
        Float64 - a:: A percentage representing prior. <- Float64\n
        Float64 - b:: A percentage representing evidence. <- Float64\n
        --------------------\n
       ### Output
       P:: Posterior value.
       """
function cond_prob(p,a,b)
    psterior = bay_ther(p,a,b)
    cond = p*(a|b)
    return(cond)
end


using MPFR_wrap

const atol = BigFloat("1e-100000")
const prec = 333_000
const π_MPFR = BigFloat(π, prec)

function brent_salamin()
    old_prec = precision(BigFloat)
    setprecision(BigFloat, prec)
    a_prev, b_prev = big(1.0), 1 / sqrt(big(2.0))
    s_prev, π_prev = big(1.0) / 2, big(4.0)
    π_next = BigFloat()
    k, p2 = 1, big(2)
    while true
        a_next = (a_prev + b_prev) / 2
        b_next = sqrt(a_prev * b_prev)
        c_next = (a_next - b_next) * (a_next + b_next)
        s_next = s_prev - p2 * c_next
        π_next = 2a_next^2 / s_next
        #println("$k -> $pn")
        δπ = π_next - π_prev
        abs(δπ) ≤ atol && break
        a_prev, b_prev, s_prev, π_prev = a_next, b_next, s_next, π_next
        k += 1
        p2 *= 2
    end
    setprecision(BigFloat, old_prec)
    @assert isapprox(π_next, π_MPFR, atol=atol)
    (k, π_next)
end

function inplace_brent_salamin()
    a_prev = BigFloat(1, precision=prec)
    b_prev = BigFloat(2, precision=prec)
    rec_sqrt!(b_prev, b_prev)
    s_prev = BigFloat(0.5, precision=prec)
    π_prev = BigFloat(4, precision=prec)
    a_next = BigFloat(precision=prec)
    b_next = BigFloat(precision=prec)
    c_next = BigFloat(precision=prec)
    s_next = BigFloat(precision=prec)
    π_next = BigFloat(precision=prec)
    δπ = BigFloat(precision=prec)
    k = Int32(1)
    while true
        add!(a_next, a_prev, b_prev)
        div_2!(a_next, a_next, Int32(1))

        mul!(b_next, a_prev, b_prev)
        sqrt!(b_next, b_next)

        sub!(c_next, a_prev, a_next)
        sqr!(c_next, c_next)

        mul_2!(c_next, c_next, k)
        sub!(s_next, s_prev, c_next)

        sqr!(π_next, a_next)
        mul_2!(π_next, π_next, Int32(1))
        div!(π_next, π_next, s_next)

        sub!(δπ, π_next, π_prev)
        abs(δπ) ≤ atol && break

        a_prev, a_next = a_next, a_prev
        b_prev, b_next = b_next, b_prev
        s_prev, s_next = s_next, s_prev
        π_prev, π_next = π_next, π_prev

        k += Int32(1)
    end
    @assert isapprox(π_next, π_MPFR, atol=atol)
    (k, π_next)
end

function inplace_borwein_cubic()
    a_prev = BigFloat(1, precision=prec)
    div!(a_prev, a_prev, Int32(3))
    s_prev = BigFloat(3, precision=prec)
    sqrt!(s_prev, s_prev)
    sub!(s_prev, s_prev, Int32(1))
    div_2!(s_prev, s_prev, Int32(1))
    r_next = BigFloat(precision=prec)
    s_next = BigFloat(precision=prec)
    rsq_next = BigFloat(precision=prec)
    a_next = BigFloat(precision=prec)
    tmp = BigFloat(precision=prec)
    π_prev = BigFloat(0, precision=prec)
    π_next = BigFloat(precision=prec)
    δπ = BigFloat(precision=prec)
    k = Int32(0)
    while true
        pow!(r_next, s_prev, Int32(3))
        sub!(r_next, Int32(1), r_next)
        cbrt!(r_next, r_next)
        mul_2!(r_next, r_next, Int32(1))
        add!(r_next, r_next, Int32(1))
        div!(r_next, Int32(3), r_next)

        sub!(s_next, r_next, Int32(1))
        div_2!(s_next, s_next, Int32(1))

        sqr!(rsq_next, r_next)
        sub!(tmp, rsq_next, Int32(1))
        mul!(tmp, tmp, Int32(3)^k)
        mul!(a_next, rsq_next, a_prev)
        sub!(a_next, a_next, tmp)

        div!(π_next, Int32(1), a_next)
        sub!(δπ, π_next, π_prev)
        abs(δπ) ≤ atol && break

        a_prev, a_next = a_next, a_prev
        s_prev, s_next = s_next, s_prev
        π_prev, π_next = π_next, π_prev

        k += Int32(1)
    end
    @assert isapprox(π_next, π_MPFR, atol=atol)
    (k, π_next)
end


using GeometricIntegrators.BasisFunctions
using GeometricIntegrators.CommonFunctions
using Test

include("vandermonde_tests.jl")
include("lagrange_basis_tests.jl")
include("lagrange_polynomials_tests.jl")


include("./Legendre_polynomials.jl")

function Spherical_harmonics_a!(x::Float64, lmax::Int64;
                                Yₗ = nothing, Pₗ = nothing)
    #### Returns an array of size lmax_half. Each cell contains one (even axisymmetric) spherical harmonic at point cos(θ) = x ####
    #### The "a" in the name is here to outline that this function only gives the "a"xisymmetric harmonics (i.e. m = 0).         ####
    # getting the constants we will need
    lmax_half = div(lmax, 2)

    # let's preallocate the arrays that haven't been given
    if isnothing(Yₗ)
        Yₗ = zeros(lmax_half) # WIll hold the spherical harmonics
    end
    if isnothing(Pₗ)
        Pₗ = zeros(lmax_half) # WIll hold the Legendre polynomials
    end

    # let's comptute the (even) Legendre polynomials at point x
    Legendre_polynomials!(x, lmax, Pₗ = Pₗ)

    # let's compute the spherical harmonics
    for l_half = 1:lmax_half
        l = 2 * l_half
        Yₗ[l_half] = Pₗ[l_half] * sqrt((2 * l + 1) / (4 * π)) # Yₗ₀ = Pₗ₀ * sqrt((2 l + 1) / 4 π)
    end

    return(Yₗ)
end

function Spherical_harmonics!(θ::Float64, ϕ::Float64, lmax::Int64;
                                Yₗₘ = nothing, Pₗₘ = nothing)
    #### Returns an array of size lmax_half * (2 lmax + 1). Each cell contains one (even) spherical harmonic at point (θ, ϕ) ####
    #### This function has no "a" in its title because it returns ALL the spherical harmonics (i.e. m = 0 and m != 0)        ####
    # getting the constants we will need
    lmax_half = div(lmax, 2)

    # let's preallocate the arrays that haven't been given
    if isnothing(Yₗₘ)
        Yₗₘ = zeros(2 * lmax + 1, lmax_half) # Will hold the spherical harmonics
    end
    if isnothing(Pₗₘ)
        Pₗₘ = zeros(lmax + 1, lmax_half) # Will hold the normalized associated Legendre polynomials
    end

    # Let's compute the normalzed associated Legendre polynomials
    Normalized_associated_Legendre_polynomials!(cos(θ), lmax, P = Pₗₘ)

    # Let's compute the spherical harmonics
    for l_half = 1:lmax_half
        l = 2 * l_half

        for m = 0:l
            # The passage from Pₗₘ to Yₗₘ is not the same if m is positive, null or negative.
            # Cf Wikipedia : https://en.wikipedia.org/wiki/Spherical_harmonics#Real_form
            if (m != 0)
                Yₗₘ[lmax + 1 - m, l_half] = (-1)^(m) * sqrt(2) * Pₗₘ[m + 1, l_half] * sin(m * ϕ)
                Yₗₘ[lmax + 1 + m, l_half] = (-1)^(m) * sqrt(2) * Pₗₘ[m + 1, l_half] * cos(m * ϕ)
            else
                Yₗₘ[lmax + 1, l_half] = Pₗₘ[1, l_half]
            end
        end
    end

    return(Yₗₘ)
end


# Common functions compatible for use with physical vectors

function dot_product(A::AbstractArray{T1,N},B::AbstractArray{T2,N}) where {T1<:Number,T2<:Number,N}

    size(A) === size(B) || throw(ArgumentError("Vectors must have the same shape"))
    u = zeros(promote_type(T1,T2),size(A)[1:end-1]...)
    dot_product!(u,A,B)
    return u
end

function dot_product!(u::AbstractArray{T1,N}, A::AbstractArray{T2,N2},B::AbstractArray{T3,N2}) where {T1<:Number,T2<:Number,T3<:Number,N,N2}

    (size(A) === size(B)) || throw(ArgumentError("Vectors must have the same shape"))
    (N === N2-1) || throw(ArgumentError("Output should be scalar matrix, one dimension less than input"))

    T = promote_type(T1,T2,T3)
    @turbo for I in CartesianIndices(u)
        x_temp = zero(T)
        for i in 1:N
            x_temp += A[I,i]*B[I,i]
        end
        u[I] = x_temp
    end
    return u
end

function cross_product(A::AbstractArray{T1,4},B::AbstractArray{T2,4}) where {T1<:Number,T2<:Number}

    size(A) === size(B) || throw(ArgumentError("Vectors must have the same shape"))
    s = size(A)
    u = zeros(promote_type(T1,T2),s...)
    cross_product!(u,A,B)
    return u
end

function cross_product!(u::AbstractArray{T1,4},A::AbstractArray{T2,4},B::AbstractArray{T3,4}) where {T1<:Number,T2<:Number,T3<:Number}

    (size(A) === size(B) && size(A) === size(u))|| throw(ArgumentError("Vectors must have the same shape"))
    s = size(u)

    @turbo for r in 1:s[3], q in 1:s[2], p in 1:s[1]
        u[p,q,r,1] = A[p,q,r,2]*B[p,q,r,3] - A[p,q,r,3]*B[p,q,r,2]
        u[p,q,r,2] = A[p,q,r,3]*B[p,q,r,1] - A[p,q,r,1]*B[p,q,r,3]
        u[p,q,r,3] = A[p,q,r,1]*B[p,q,r,2] - A[p,q,r,2]*B[p,q,r,1]
    end
end

function square_norm(A::AbstractArray{T,N}) where {T<:Number,N}
    u = zeros(T,size(A)[1:end-1]...)
    square_norm!(u,A)
    return u
end

function square_norm!(u::AbstractArray{T1,N1},A::AbstractArray{T2,N2}) where {T1<:Number,T2<:Number,N1,N2}
    dot_product!(u,A,A)
    @. u = u^0.5
end


# ## GPU Computing

# https://github.com/JuliaGPU

using Plots, BenchmarkTools, FFTW, LinearAlgebra

# ### Advection equation for a rotation in two dimensional domain
#md #
#md # ```math
#md #  \\frac{d f}{dt} +  (y \\frac{d f}{dx} - x \\frac{d f}{dy}) = 0
#md # ```
#md #
#md # ``x \in [-π, π], y \in [-π, π] `` and  `` t \in [0, 200π] ``

#md # ---

struct Mesh

    nx   :: Int64
    ny   :: Int64
    x    :: Vector{Float64}
    y    :: Vector{Float64}
    kx   :: Vector{Float64}
    ky   :: Vector{Float64}

    function Mesh( xmin, xmax, nx, ymin, ymax, ny)
        ## periodic boundary condition, we remove the end point.
        x = range(xmin, stop=xmax, length=nx+1)[1:end-1]
        y = range(ymin, stop=ymax, length=ny+1)[1:end-1]
        kx  = 2π ./ (xmax-xmin) .* [0:nx÷2-1;nx÷2-nx:-1]
        ky  = 2π ./ (ymax-ymin) .* [0:ny÷2-1;ny÷2-ny:-1]
        new( nx, ny, x, y, kx, ky)
    end
end

#md # ---

function exact(time, mesh :: Mesh; shift=1.0)

    f = zeros(Float64,(mesh.nx, mesh.ny))
    for (i, x) in enumerate(mesh.x), (j, y) in enumerate(mesh.y)
        xn = cos(time)*x - sin(time)*y
        yn = sin(time)*x + cos(time)*y
        f[i,j] = exp(-(xn-shift)*(xn-shift)/0.1)*exp(-(yn-shift)*(yn-shift)/0.1)
    end

    f
end

#md # ---

function animation( tf, nt)

    mesh = Mesh( -π, π, 64, -π, π, 64)
    dt = tf / nt
    t = 0
    anim = @animate for n=1:nt

       f = exact(t, mesh)
       t += dt
       p = contour(mesh.x, mesh.y, f, axis=[], framestyle=:none)
       plot!(p[1]; clims=(0.,1.), aspect_ratio=:equal, colorbar=false, show=false)
       plot!(sqrt(2) .* cos.(-pi:0.1:pi+0.1),
             sqrt(2) .* sin.(-pi:0.1:pi+0.1), label="", show=false)
       xlims!(-π,π)
       ylims!(-π,π)

    end

    anim

end

#md # ---

anim = animation( 2π, 100)
#md gif(anim, "rotation2d.gif", fps = 20)
#md nothing # hide

#md # ![](rotation2d.gif)

#md # ---

function rotation_on_cpu( mesh :: Mesh, nt :: Int64, tf :: Float64)

    dt = tf / nt

    f   = zeros(ComplexF64,(mesh.nx,mesh.ny))
    f  .= exact( 0.0, mesh )

    exky = exp.( 1im*tan(dt/2) .* mesh.x  .* mesh.ky')
    ekxy = exp.(-1im*sin(dt)   .* mesh.y' .* mesh.kx )

    for n = 1:nt

        fft!(f, 2)
        f .= exky .* f
        ifft!(f,2)

        fft!(f, 1)
        f .= ekxy .* f
        ifft!(f, 1)

        fft!(f, 2)
        f .= exky .* f
        ifft!(f, 2)

    end

    real(f)

end

#md # ---

mesh = Mesh( -π, π, 1024, -π, π, 1024)
nt, tf = 100, 20.
rotation_on_cpu(mesh, 1, 0.1) # trigger building
etime = @time norm( rotation_on_cpu(mesh, nt, tf) .- exact( tf, mesh))
println(etime)

@test true #src

#md # ---

using Pkg

GPU_ENABLED = haskey(Pkg.installed(), "CUDAdrv")

if GPU_ENABLED

    using CUDAdrv, CuArrays, CuArrays.CUFFT

    println(CUDAdrv.name(CuDevice(0)))

end

#md # ---

if GPU_ENABLED

    function rotation_on_gpu( mesh :: Mesh, nt :: Int64, tf :: Float64)

        dt  = tf / nt
        f   = zeros(ComplexF64,(mesh.nx, mesh.ny))
        f  .= exact( 0.0, mesh)

        d_f    = CuArray(f) # allocate f on GPU

        p_x    = plan_fft!(d_f,  [1]) # Create fft plans on GPU
        pinv_x = plan_ifft!(d_f, [1])
        p_y    = plan_fft!(d_f,  [2])
        pinv_y = plan_ifft!(d_f, [2])

        d_exky = CuArray(exp.( 1im*tan(dt/2) .* mesh.x  .* mesh.ky'))
        d_ekxy = CuArray(exp.(-1im*sin(dt)   .* mesh.y' .* mesh.kx ))

        for n = 1:nt

            p_y * d_f
            d_f .*= d_exky
            pinv_y * d_f

            p_x * d_f
            d_f .*= d_ekxy
            pinv_x * d_f

            p_y * d_f
            d_f .*= d_exky
            pinv_y * d_f

        end

        f .= collect(d_f) # Transfer f from GPU to CPU
        real(f)

    end

end

#md # ---

if GPU_ENABLED

    nt, tf = 100, 20.
    rotation_on_gpu(mesh, 1, 0.1)
    etime = @time norm( rotation_on_gpu(mesh, nt, tf) .- exact( tf, mesh))
    println(etime)
    @test true #src

end

#md # Next: [Metaprogrammimg and Particle Group](/Numkin2019/04/build/index.html)


@testset "DirectPowers" begin

   ×(a,b) = Groups.DirectPower(a,b)

   @testset "Constructors" begin
      G = PermutationGroup(3)

      @test Groups.DirectPowerGroup(G,2) isa AbstractAlgebra.Group
      @test G×G isa AbstractAlgebra.Group
      @test Groups.DirectPowerGroup(G,2) isa Groups.DirectPowerGroup{2, Generic.PermGroup{Int64}}

      @test (G×G)×G == DirectPowerGroup(G, 3)
      @test (G×G)×G == (G×G)×G

      GG = DirectPowerGroup(G,2)
      @test (G×G)() isa GroupElem
      @test (G×G)((G(), G())) isa GroupElem
      @test (G×G)([G(), G()]) isa GroupElem

      @test Groups.DirectPowerGroupElem((G(), G())) == (G×G)()
      @test GG(G(), G()) == (G×G)()

      g = perm"(1,2,3)"

      @test GG(g, g^2) isa GroupElem
      @test GG(g, g^2) isa Groups.DirectPowerGroupElem{2, Generic.perm{Int64}}

      h = GG(g,g^2)

      @test h == GG(h)

      @test GG(g, g^2) isa GroupElem
      @test GG(g, g^2) isa Groups.DirectPowerGroupElem

      @test_throws MethodError GG(g,g,g)
      @test GG(g,g^2) == h

      @test h[1] == g
      @test h[2] == g^2
      h = GG(g, G())
      @test h == GG(g, G())
   end

   @testset "Basic arithmetic" begin
      G = PermutationGroup(3)
      GG = G×G
      i = perm"(1,3)"
      g = perm"(1,2,3)"

      h = GG(g,g^2)
      k = GG(g^3, g^2)

      @test h^2 == GG(g^2,g)
      @test h^6 == GG()

      @test h*h == h^2
      @test h*k == GG(g,g)

      @test h*inv(h) == (G×G)()

      w = GG(g,i)*GG(i,g)
      @test w == GG(perm"(1,2)(3)", perm"(2,3)")
      @test w == inv(w)
      @test w^2 == w*w == GG()
   end

   @testset "elem/parent_types" begin
      G = PermutationGroup(3)
      g = perm"(1,2,3)"

      @test elem_type(G×G) == DirectPowerGroupElem{2, elem_type(G)}
      @test elem_type(G×G×G) == DirectPowerGroupElem{3, elem_type(G)}
      @test parent_type(typeof((G×G)(g,g^2))) == Groups.DirectPowerGroup{2, typeof(G)}
      @test parent(DirectPowerGroupElem((g,g^2,g^3))) == DirectPowerGroup(G,3)

      F = AdditiveGroup(GF(13))

      @test elem_type(F×F) ==
         DirectPowerGroupElem{2, Groups.AddGrpElem{AbstractAlgebra.gfelem{Int}}}
      @test parent_type(typeof((F×F)(1,5))) ==
         Groups.DirectPowerGroup{2, Groups.AddGrp{AbstractAlgebra.GFField{Int}}}
      parent((F×F)(1,5)) == DirectPowerGroup(F,2)

      F = MultiplicativeGroup(GF(13))

      @test elem_type(F×F) ==
         DirectPowerGroupElem{2, Groups.MltGrpElem{AbstractAlgebra.gfelem{Int}}}
      @test parent_type(typeof((F×F)(1,5))) ==
         Groups.DirectPowerGroup{2, Groups.MltGrp{AbstractAlgebra.GFField{Int}}}
      parent((F×F)(1,5)) == DirectPowerGroup(F,2)
   end

   @testset "Additive/Multiplicative groups" begin

      R, x = PolynomialRing(QQ, "x")
      F, a = NumberField(x^3 + x + 1, "a")
      G = PermutationGroup(3)

      @testset "MltGrp basic functionality" begin
         Gr = MltGrp(F)
         @test Gr(a) isa MltGrpElem
         g = Gr(a)
         @test deepcopy(g) isa MltGrpElem
         @test inv(g) == Gr(a^-1)
         @test Gr() == Gr(1)
         @test inv(g)*g == Gr()
      end

      @testset "AddGrp basic functionality" begin
         Gr = AddGrp(F)
         @test Gr(a) isa AddGrpElem
         g = Gr(a)
         @test deepcopy(g) isa AddGrpElem
         @test inv(g) == Gr(-a)
         @test Gr() == Gr(0)
         @test inv(g)*g == Gr()
      end
   end

   @testset "Direct Product of Multiplicative Groups" begin

      R, x = PolynomialRing(QQ, "x")
      F, a = NumberField(x^3 + x + 1, "a")
      FF = Groups.DirectPowerGroup(MltGrp(F),2)

      @test FF([a,1]) isa GroupElem
      @test FF([a,1]) isa DirectPowerGroupElem
      @test FF([a,1]) isa DirectPowerGroupElem{2, MltGrpElem{elem_type(F)}}
      @test_throws DomainError FF(1,0)
      @test_throws DomainError FF([0,1])
      @test_throws DomainError FF([1,0])

      @test MltGrp(F) isa AbstractAlgebra.Group
      @test MltGrp(F) isa MultiplicativeGroup
      @test DirectPowerGroup(MltGrp(F), 2) isa AbstractAlgebra.Group
      @test DirectPowerGroup(MltGrp(F), 2) isa DirectPowerGroup{2, MltGrp{typeof(F)}}

      F, a = NumberField(x^3 + x + 1, "a")
      FF = DirectPowerGroup(MltGrp(F), 2)

      @test FF(a,a+1) == FF([a,a+1])
      @test FF([1,a+1])*FF([a,a]) == FF(a,a^2+a)
      x, y = FF([1,a]), FF([a^2,1])
      @test x*y == FF([a^2, a])
      @test inv(x) == FF([1,-a^2-1])

      @test parent(x) == FF
   end

   @testset "Direct Product of Additive Groups" begin

      R, x = PolynomialRing(QQ, "x")
      F, a = NumberField(x^3 + x + 1, "a")

      # Additive Group
      @test AddGrp(F) isa AbstractAlgebra.Group
      @test AddGrp(F) isa AdditiveGroup
      @test DirectPowerGroup(AddGrp(F), 2) isa AbstractAlgebra.Group
      @test DirectPowerGroup(AddGrp(F), 2) isa DirectPowerGroup{2, AddGrp{typeof(F)}}

      FF = DirectPowerGroup(AdditiveGroup(F), 2)

      @test FF([0,a]) isa AbstractAlgebra.GroupElem
      @test FF(F(0),a) isa DirectPowerGroupElem
      @test FF(0,0) isa DirectPowerGroupElem{2, AddGrpElem{elem_type(F)}}

      @test FF(F(1),a+1) == FF([1,a+1])

      @test FF([F(1),a+1])*FF([a,a]) == FF(1+a,2a+1)

      x, y = FF([1,a]), FF([a^2,1])
      @test x*y == FF(a^2+1, a+1)
      @test inv(x) == FF([F(-1),-a])

      @test parent(x) == FF
   end

   @testset "Misc" begin
      F = GF(5)

      FF = DirectPowerGroup(AdditiveGroup(F),2)
      @test order(FF) == 25

      elts = vec(collect(FF))
      @test length(elts) == 25
      @test all([g*inv(g) == FF() for g in elts])
      @test all(inv(g*h) == inv(h)*inv(g) for g in elts for h in elts)

      FF = DirectPowerGroup(MultiplicativeGroup(F), 3)
      @test order(FF) == 64

      elts = vec(collect(FF))
      @test length(elts) == 64
      @test all([g*inv(g) == FF() for g in elts])
      @test all(inv(g*h) == inv(h)*inv(g) for g in elts for h in elts)

      G = PermutationGroup(3)
      GG = Groups.DirectPowerGroup(G,3)
      @test order(GG) == 216

      @test isa(collect(GG), Vector{Groups.DirectPowerGroupElem{3, elem_type(G)}})
      elts = vec(collect(GG))

      @test length(elts) == 216
      @test all([g*inv(g) == GG() for g in elts])
      @test all(inv(g*h) == inv(h)*inv(g) for g in elts for h in elts)
   end
end


"""

The chemostat steady state impose a contraint over the exchanges:

``intake <= c/ξ``

where ``c`` is the concentration of the metabolite in the medium and ``ξ`` is the cell-specific dilution rate.
Together with the thermodinamic bound we have

stst_bound(c, ξ, b) = sign(b) * min(c/ ξ, abs(b))

"""
stst_bound(c, ξ, b) = sign(b) * min(c/ ξ, abs(b))


"""
    function latticeCompactHexagonal(box,r)

Make a lattice with compact hexagonal structure in box with spheres of radius r.

# Returns
[posX,posY,posZ] Where pos are arrays with the positions of the cells for each component.
"""
function compactHexagonal(box::Array{Array{Float64,1},1},r::Number)

    d = length(box)
    if !(d in [1,2,3])
        error("Incompatible number of dimensions ", d)
    end

    #Make first dimension
    lineX = Array(box[1][1]:2*r:box[1][2])

    #Make second dimension
    if d > 1
        lineY = fill(box[2][1]+r,length(lineX))
        nY = ceil(Int,(box[2][2]-box[2][1])/(4*sin(pi/3)*r))
        areaX = Float64[]
        areaY = Float64[]
        dx = 2*r*cos(pi/3)
        dy = 2*r*sin(pi/3)
        for i in 0:nY-1
            append!(areaX,lineX)
            append!(areaX,lineX.+dx)

            append!(areaY,lineY.+dy*2*i)
            append!(areaY,lineY.+dy*2*i.+dy)
        end
    end

    #Make third dimension
    if d == 3
        areaZ = fill(box[2][1]+r,length(areaX))
        nZ = ceil(Int,(box[3][2]-box[3][1])/(4*sin(pi/3)*r))
        volumeX = Float64[]
        volumeY = Float64[]
        volumeZ = Float64[]
        dy = 2*r/2/cos(pi/6)
        dz = 2*sqrt(2/3)*r
        for i in 0:nZ-1
            append!(volumeX,areaX)
            append!(volumeX,areaX)

            append!(volumeY,areaY)
            append!(volumeY,areaY.+dy)

            append!(volumeZ,areaZ.+dz*2*i)
            append!(volumeZ,areaZ.+dz*2*i.+dz)
        end
    end

    #Return
    if d == 1
        return lineX
    elseif d == 2
        return areaX, areaY
    else
        return volumeX, volumeY, volumeZ
    end

end


###
  Name     : c7_22_optim.jl
  Book     : Hands-on Data Science with Anaconda )
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan and James Yan
  Date     : 3/15/2018
  email    : yany@canisius.edu
             paulyxy@hotmail.com
###

using Optim
function g!(s, x)
 s[1] = -2.0*(1.0-x[1])-400.0*(x[2]-x[1]^2)*x[1]
 s[2] = 200.0*(x[2]-x[1]^2)
end
lower = [1.25, -2.1]
upper = [Inf, Inf]
initial_x = [2.0, 2.0]
od = OnceDifferentiable(f, g!, initial_x)
results = optimize(od, initial_x,lower,upper,Fminbox{GradientDescent}())


using HypothesisTests, Base.Test
using HypothesisTests: default_tail

## ONE SAMPLE T-TEST

# One sample
@test pvalue(OneSampleTTest(-5:5)) == 1

tst = OneSampleTTest(-5:10)
@test abs(pvalue(tst) - 0.0530) <= 1e-4
@test abs(pvalue(tst; tail=:left) - 0.9735) <= 1e-4
@test abs(pvalue(tst; tail=:right) - 0.0265) <= 1e-4
@test default_tail(tst) == :both
show(IOBuffer(), tst)

tst = OneSampleTTest(mean(-5:10), std(-5:10), 16)
@test abs(pvalue(tst) - 0.0530) <= 1e-4

@test all(abs.([confint(tst)...;] - [-0.0369, 5.0369]) .<= 1e-4)
@test all(abs.([confint(tst, 0.1)...;] - [0.4135, 4.5865]) .<= 1e-4)
c = confint(tst; tail=:left)
@test c[1] == -Inf
@test abs(c[2] - 4.5865) .<= 1e-4
c = confint(tst; tail=:right)
@test abs(c[1] - 0.4135) .<= 1e-4
@test c[2] == Inf
show(IOBuffer(), tst)

tst = OneSampleTTest(-10:5)
@test abs(pvalue(tst) - 0.0530) <= 1e-4
@test abs(pvalue(tst; tail=:left) - 0.0265) <= 1e-4
@test abs(pvalue(tst; tail=:right) - 0.9735) <= 1e-4
@test all(abs.([confint(tst)...] - [-5.0369, 0.0369]) .<= 1e-4)
@test abs.(confint(tst; tail=:left)[2] - (-0.4135)) .<= 1e-4
@test abs.(confint(tst; tail=:right)[1] - (-4.5865)) .<= 1e-4
show(IOBuffer(), tst)

# Paired samples
@test abs(pvalue(OneSampleTTest([1, 1, 2, 1, 0], [0, 1, 1, 1, 0])) - 0.1778) <= 1e-4

## TWO SAMPLE T-TESTS

# From http://en.wikipedia.org/w/index.php?title=Student%27s_t-test&oldid=526762741

a1 = [30.02, 29.99, 30.11, 29.97, 30.01, 29.99]
a2 = [29.89, 29.93, 29.72, 29.98, 30.02, 29.98]

tst = EqualVarianceTTest(a1, a2)
@test tst.df == 10
@test abs(tst.t - 1.959) <= 1e-3
@test abs(pvalue(tst) - 0.078) <= 1e-3
@test all(abs.([confint(tst)...] - [-0.0131, 0.2031]) .<= 1e-4)
@test default_tail(tst) == :both
show(IOBuffer(), tst)

tst = UnequalVarianceTTest(a1, a2)
@test abs(tst.df - 7.03) <= 0.01
@test abs(tst.t - 1.959) <= 1e-3
@test abs(pvalue(tst) - 0.091) <= 1e-3
@test all(abs.([confint(tst)...] - [-0.0196, 0.2096]) .<= 1e-4)
@test default_tail(tst) == :both
show(IOBuffer(), tst)


using OrdinaryDiffEq, PoissonRandom

function rate_to_proportion(r::Float64,t::Float64)
    1-exp(-r*t)
end;

function sir_abm!(du,u,p,t)
    (β,c,γ,δt) = p
    N = length(u)
    # Initialize du to u
    for i in 1:N
        du[i] = u[i]
    end
    for i in 1:N # loop through agents
        # If recovered
        if u[i]==Recovered
            continue
        # If susceptible
        elseif u[i]==Susceptible
            ncontacts = pois_rand(c*δt)
            while ncontacts > 0
                j = rand(1:N)
                if j==i
                    continue
                end
                a = u[j]
                if a==Infected && rand() < β
                    du[i] = Infected
                    break
                end
                ncontacts -= 1
            end
        # If infected
        else u[i]==Infected
            if rand() < γ
                du[i] = Recovered
            end
        end
    end
    nothing
end

δt = 0.1
nsteps = 400
tf = nsteps*δt
tspan = (0.0,nsteps)
t = 0:δt:tf;
β = 0.05
c = 10.0
γ = rate_to_proportion(0.25,δt)
p = [β,c,γ,δt]

@enum InfectionStatus Susceptible Infected Recovered
Base.zero(::Type{<:InfectionStatus}) = Susceptible

N = 1000
I0 = 10
u0 = Array{InfectionStatus}(undef,N);
for i in 1:N
    if i <= I0
        s = Infected
    else
        s = Susceptible
    end
    u0[i] = s
end

prob_map = DiscreteProblem(sir_abm!,u0,tspan,p)
sol_map = solve(prob_map,FunctionMap())


# Code for Video 1.5:

function sum(x, y, z)
  return x + y + z
end

show(sum(2, 3, 4)) # 9
show(sum(2.1, 3.2, 4.3)) # 9.600000000000001
show(sum(true, false, true)) # 2

typeof(sum) # Function

function sum_prod(x, y)
    x + y, x * y
end

# sum("Iris", 7, true)
# ERROR: MethodError: `+` has no method matching +(::ASCIIString, ::Int64)

function sum(x:: Int64, y::Int64, z::Int64)
    return x + y + z
end

function sump(x:: Int64, y::Int64, z::Int64)
    p::Int64 = x * y * z
    return x + y + z + p
end

 sum(x, y, z) = x + y + z

function sumvar(args...)
    sum = 0
    for n in args
        sum += n
    end
    return sum
end

sumvar(2,3,4,100,-9) # 100
sumvar(2,3,4) # 9
sumvar() # 0

function sum(n1, n2, n3=5; n4=-9)
    return n1 + n2 + n3 + n4
end

sum(2, 3) # 1 (= 2 + 3 + 5 -9)
sum(2, 3, 4) # 0  (= 2 + 3 + 4 -9)
sum(2, 3, 4, n4=100) # 109 (= 2 + 3 + 4 + 100)

(x, y, z) -> x + y + z
# (anonymous function)

arr = rand(100)
# 100-element Array{Float64,1}:
#  0.0552819
#  0.319113
#  0.488649
#  ...

arr2 = map(x -> x^2, arr)
# 100-element Array{Float64,1}:
#  0.00305609
#  0.101833
#  0.238778

arr2 = [x^2 for x in arr]
# 100-element Array{Any,1}:
#  0.00305609
#  0.101833
#  0.238778

isodd(n) = (n % 2 != 0)

filter(isodd, round(Int64, rand(100) * 100))
# 50-element Array{Int64,1}:
#  57
#  99
#  23

filter(n -> n %2 != 0, round(Int64, rand(100) * 100))

map(collect(1:100)) do x
        7x^2 - 3.14x + sin(x) + 4
end
# 100-element Array{Float64,1}:
#      8.70147
#     26.6293
#     57.7211
#    102.683
# ...


"""
Generate a Ricker Wavelet. Reference:
Frequencies of the Ricker wavelet, Yanghua Wang, GEOPHYSICS, VOL. 80, NO. 2
Bandwidth is roughly 1.2 * fpeak for ricker

# Keyword Arguments

* `fqdom::Float64`: dominant frequency
* `tgrid`: time-domain grid
* `tpeak::Float64=tgrid[1]+1.5/fqdom`: the peak of the ricker in time (has a default)
* `tpeak_rand_flag` : randomly place peak of ricker
"""
function ricker(fqdom::Float64,
		tgrid::StepRangeLen;
		tpeak::Float64=tgrid[1]+1.5/fqdom, # using approximate half width of ricker
		attrib::AbstractString="",
		trim_tol::Float64=0.0,
		maxamp::Float64=1.0,
		tpeak_rand_flag=false,
		maxamp_rand_flag=false,
		phase_rand_flag=false,
		)
	(tpeak < tgrid[1]+1.5/fqdom) && error("cannot output Ricker for given tgrid and tpeak")
	(tpeak > tgrid[end]-1.5/fqdom) && error("cannot output Ricker for given tgrid and tpeak")

	if(tpeak_rand_flag)
		tpeak=rand(Uniform(tgrid[1]+1.5/fqdom,tgrid[end]-1.5/fqdom))
	end
	if(maxamp_rand_flag)
		maxamp=rand(Uniform(-1,1))
	end
	isapprox(fqdom,0.0) && error("dominant frequency cannot be zero")

	#! some constants
	pf = (π*π)*(fqdom^2.0)
	nt = length(tgrid)
	δt = step(tgrid)

	# a vector is odd number of samples (nt + 1 corresponds to time zero)
	wav = zero(tgrid);
	# k = (1 - 2* pf * t^2) * Exp[-pf *t^2]
	# Simplify[D[k,t]]
	# FortranForm[Simplify[D[k,t]]]
	if(occursin("[DIFF]",attrib))
			# ricker after a time derivative
			for it = 1:nt
				tsquare = (tgrid[it]-tpeak) * (tgrid[it]-tpeak)
				t       = -1.0 * (tgrid[it]-tpeak)
				wav[it] = (2.0 * pf * t * (-3.0 + 2.0 * pf * tsquare)) * exp(-1.0 * pf * tsquare)
			end
	else
	#! ricker wavelet
		for it = 1:nt
			tsquare = (tgrid[it]-tpeak) * (tgrid[it]-tpeak)
			wav[it] = (1.0 - 2.0 * pf * tsquare) * exp(-1.0e0 * pf * tsquare) * maxamp
		end
	end

	isapprox(maximum(abs.(wav)),0.0) && warn("wavelet is zeros")

	# apply random phase to the ricker wavelet
	if(phase_rand_flag)
		wav=apply_rand_phase(wav)
	end

	if(trim_tol != 0.0)
		return wav[abs(wav).>=trim_tol]
	else
		return wav
	end
end

"""
ormbsy wavelet

* `tperc::Float64=0.0` : the wavelet is tapered in time using this percentage value
"""
function  ormsby(
		fqdom,
		tgrid::StepRangeLen;
		fracbandwidth::Float64=1.2, # using same value as Ricker
		f1::Float64=fqdom-fracbandwidth*0.5*fqdom,
		f4::Float64=fqdom+fracbandwidth*0.5*fqdom,
		f2::Float64=0.25*f4+0.75*f1,
		f3::Float64=0.25*f1+0.75*f4,
		tpeak::Float64=tgrid[1]+1.5/(0.25*(f1+f4)), # using approximate half width
		trim_tol::Float64=0.0,
		tperc::Float64=0.0,
		tpeak_rand_flag=false,
		maxamp_rand_flag=false,
		phase_rand_flag=false,
		)

	# some constants
	A43 = (pi*f4)^2 / (pi*f4 - pi*f3);
	A34 = (pi*f3)^2 / (pi*f4 - pi*f3);
	A21 = (pi*f2)^2 / (pi*f2 - pi*f1);
	A12 = (pi*f1)^2 / (pi*f2 - pi*f1);

	wav = zeros(length(tgrid));

	if(tpeak_rand_flag)
		tpeak=rand(Uniform(tgrid[1]+1.5/(0.25*(f1+f4)),tgrid[end]-1.5/(0.25*(f1+f4))))
	end

	# ormsby wavelet
	for it = 1: length(tgrid)
		t = tgrid[it]-tpeak
		S4 = (sinc(pi*f4*t))^2
		S3 = (sinc(pi*f3*t))^2
		S2 = (sinc(pi*f2*t))^2
		S1 = (sinc(pi*f1*t))^2

		wav[it] =  ((A43 * S4 - A34 * S3) - (A21 * S2 - A12 * S1))
	end

	isapprox(maximum(abs.(wav)),0.0) && warn("wavelet is zeros")

	# normalize
	wav /= maximum(abs.(wav));

	if(maxamp_rand_flag)
		wav *= rand(Uniform(-1,1))
	end

	# apply random phase to the ricker wavelet
	if(phase_rand_flag)
		wav=apply_rand_phase(wav)
	end

	if(trim_tol != 0.0)
		wav = wav[abs(wav).>=trim_tol]
	end

	wav = wav .* Utils.taper(ones(length(wav)),tperc)
	return wav

end

function apply_rand_phase(wav)
	nt=length(wav)
	W=FFTW.rfft(wav);
	θ=rand(Uniform(-Float64(pi),Float64(pi)))
	for i in eachindex(W)
		W[i]=W[i]*complex(cos(θ),sin(θ))
	end
	wav=FFTW.irfft(W,nt)
end

"""
Return a ricker wavelet for a given input `Medium`.

# Arguments
* `mod::Medium` : Medium
* `nλ::Int64=10` : number of wavelengths (P-wave) in the medium
* `tmaxfrac::Float64=1.0` : by default the maximum modelling time is computed using the average velocity and the diagonal distance of the medium,
use this fraction to increase of reduce the maximum time

# Keyword Arguments
* all the keywords arguments of the `ricker` method can be used.
"""
function ricker(mod::Medium, nλ::Int=10, tmaxfrac::Real=1.0, epsilon=inv(sqrt(ndims(mod))); args... )
	@assert(!iszero(mod))
	fqdom, tgrid = get_fqdom_tgrid(mod, nλ, tmaxfrac, epsilon)
	wav=ricker(fqdom, tgrid; args...)
	return wav, tgrid
end

"""
Same as ricker, but return ormsby...
"""
function ormsby(mod::Medium, nλ::Int=10, tmaxfrac::Real=1.0, epsilon=inv(sqrt(ndims(mod))); args... )
	@assert(!iszero(mod))
	fqdom, tgrid = get_fqdom_tgrid(mod, nλ, tmaxfrac, epsilon)
	wav=ormsby(fqdom, tgrid; args...)
	return wav, tgrid
end

"""
Return dominant source frequency, and its temporal grid for a finite-difference simulation, for given number of wavelengths `nλ` in the medium.
The model has `nλ` wavelengths, and the maximum modeling time is determined by `tmaxfrac`.
`epsilon` is Courant number.
"""
function get_fqdom_tgrid( mod::Medium, nλ::Int, tmaxfrac::Real, epsilon)

	# maximum distance (diagnol) the wave travels
	d = sqrt(sum([(m[1]-m[end])^2 for m in mod.mgrid]))

	# dominant wavelength using mod dimensions
	λdom=d*inv(real(nλ))

	# average P velocity
	vavg=mod.ref[:vp]

	fqdom = vavg/λdom

	# use two-way maximum distance to get tmax
	tmax=2.0*d*inv(vavg)*tmaxfrac

	# choose sampling interval to obey max freq of source wavelet
	δmin = minimum(step.(mod.mgrid))
	vmax=try
		sqrt(mod.bounds[:vp][2]^2 + mod.bounds[:vs][2]^2) # see Virieux (1986)
	catch
		mod.bounds[:vp][2]
	end
	δt=epsilon*δmin/vmax

	# check if δt is reasonable
	#(δt > 0.1/fqdom) : error("decrease spatial sampling or nλ")

	tgrid=range(0.0, stop=tmax, step=δt)

	return fqdom, tgrid
end


# test initilization functions

X = rand(8, 12)

# randinit

W, H = NMF.randinit(X, 5)
@test size(W) == (8, 5)
@test size(H) == (5, 12)
@test all(W .>= 0.0)
@test all(H .>= 0.0)

W, H = NMF.randinit(X, 5; zeroh=true)
@test size(W) == (8, 5)
@test size(H) == (5, 12)
@test all(W .>= 0.0)
@test all(H .== 0.0)

W, H = NMF.randinit(X, 5; normalize=true)
@test size(W) == (8, 5)
@test size(H) == (5, 12)
@test all(W .>= 0.0)
@test all(H .>= 0.0)
@test vec(sum(W, 1)) ≈ ones(5)

## nndsvd

W, H = NMF.nndsvd(X, 5)
@test size(W) == (8, 5)
@test size(H) == (5, 12)
@test all(W .>= 0.0)
@test all(H .>= 0.0)
@test vec(sum(abs2, W, 1)) ≈ ones(5) atol=1.0e-8

W2, H2 = NMF.nndsvd(X, 5; zeroh=true)
@test size(W) == (8, 5)
@test size(H) == (5, 12)
@test all(W2 .== W)
@test all(H2 .== 0.0)

W, H = NMF.nndsvd(X, 5; variant=:ar)
@test all(W .> 0.0)
# NMF.printf_mat(W)