# line with inset heatmap

#Makie
using CairoMakie
let
    x = y = -10:0.11:10
    y1d = sin.(x) ./ x
    # 3D heatmap
    sinc2d(x, y) = sin.(sqrt.(x .^ 2 + y .^ 2)) ./ sqrt.(x .^ 2 + y .^ 2)
    fxy = [sinc2d(x, y) for x in x, y in y]

    fig = Figure(resolution = (600, 400))
    ax1 = Axis(fig[1, 1], xlabel = "x", ylabel = "f(x)", xgridvisible = true,
        ygridvisible = true)
    CairoMakie.lines!(ax1, x, y1d, color = :red, label = "sinc(x)")
    axislegend()
    # inset
    ax2 = Axis(fig, bbox = BBox(140, 260, 260, 350), xticklabelsize = 12,
        yticklabelsize = 12, showgrid = false,
        title = "inset  at (140, 260, 260, 350)")
    hmap = CairoMakie.heatmap!(ax2, x, y, fxy, colormap = :Spectral_11)
    Colorbar(fig[1, 1], hmap, label = "sinc(x,y)", labelpadding = 5,
        tellheight = false, tellwidth = false, ticklabelsize = 12,
        width = 10, height = Relative(1.5 / 4),
        halign = :right, valign = :center)
    limits!(ax2, -10, 10, -10, 10)
    hidespines!(ax2)
    ax2.yticks = [-10, 0, 10]
    ax2.xticks = [-10, 0, 10]
    display(fig)
end;


########################## Auxiliary functions #############################

function eye(tpe::Type,n::S) where {S <: Integer}

  if tpe <: Number
      return Matrix{tpe}(I,n,n)
  end

end

eye(n::Integer) = eye(Float64,n::Integer)

function tracem(x::Array{T,2}) where {T <: Real}

# Computes the matrix-trace as defined by Gomme and Klein (2011)

# We require the number of rows to be greater than the number of columns, so
# that m is greater than one.

trans = false

if size(x,1) < size(x,2)
  x = Matrix(x')
  trans = true
end

n = size(x,2)
m = Int(size(x,1)/n)

y = zeros(m,1)  # We want this to be a 2-d array for subsequent matrix multiplication

@inbounds for i = 1:m
  @views y[i,1] = tr(x[(n*(i-1)+1):i*n,1:n])
end

if trans == true
  y = y'
end

return Matrix(y)

end

function kron_prod_times_vector(a::AbstractArray{T,2},b::AbstractArray{T,2},v::AbstractArray{T,1}) where {T <: Real}

  # This function efficiently computes kron(a,b)*v where a and b are matrices
  # and v is a vector.

  (n1,n2) = size(a)
  (n3,n4) = size(b)
  n5      = length(v)

  if n5 != n2*n4
      error("a, b, and v do not have the correct sizes")
  end

  p = vec(b*reshape(v,n4,n2)*a')

  return p

end

function vector_times_kron_prod(v::AbstractArray{T,1},a::AbstractArray{T,2},b::AbstractArray{T,2}) where {T <: Real}

  product = Matrix(kron_prod_times_vector(a',b',v')')

  return product

end

function kron_prod_times_matrix(a::AbstractArray{T,2},b::AbstractArray{T,2},v::AbstractArray{T,2}) where {T <: Real}

  # This function efficiently computes kron(a,b)*v where a, b, and v are
  # conformable matrices.

  (n1,n2) = size(a)
  (n3,n4) = size(b)
  (n5,n6) = size(v)

  if n5 != n2*n4
      error("a, b, and v do not have the correct sizes")
  end

  p = zeros(n1*n3,n6)
  for i = 1:n6
      @views p[:,i] = kron_prod_times_vector(a,b,v[:,i])
  end

  return p

end

function matrix_times_kron_prod(v::AbstractArray{T,2},a::AbstractArray{T,2},b::AbstractArray{T,2}) where {T <: Real}

  product = Matrix(kron_prod_times_matrix(a',b',v')')

  return product

end

function kron_prod_times_vector(a::AbstractArray{T,2},b::AbstractArray{T,2},c::AbstractArray{T,2},v::AbstractArray{T,1}) where {T <: Real}

  # This function efficiently computes kron(a,kron(b,c))*v where a, b, and c
  # are matrices and v is a vector.  Included for completeness, but not
  # actually used.

  (n1,n2) = size(a)
  (n3,n4) = size(b)
  (n5,n6) = size(c)
  n7      = length(v)

  if n7 != n2*n4*n6
      error("a, b, c, and v do not have the correct sizes")
  end

  v_tilda = reshape(v,n4*n6,n2)*a'
  p = vec(kron_prod_times_matrix(b,c,v_tilda))

  return p

end

function vector_times_kron_prod(v::AbstractArray{T,1},a::AbstractArray{T,2},b::AbstractArray{T,2},c::AbstractArray{T,2}) where {T <: Real}

  product = Matrix(kron_prod_times_vector(a',b',c',v')')

  return product

end

function kron_prod_times_matrix(a::AbstractArray{T,2},b::AbstractArray{T,2},c::AbstractArray{T,2},v::AbstractArray{T,2}) where {T <: Real}

  # This function efficiently computes kron(a,kron(b,c))*v where a, b, c and
  # v are comformable matrices.

  (n1,n2) = size(a)
  (n3,n4) = size(b)
  (n5,n6) = size(c)
  (n7,n8) = size(v)

  if n7 != n2*n4*n6
      error("a, b, c, and v do not have the correct sizes")
  end

  p = zeros(n1*n3*n5,n8)
  for i = 1:n8
      @views v_tilda = reshape(v[:,i],n4*n6,n2)*a'
      p[:,i] .= vec(kron_prod_times_matrix(b,c,v_tilda))
  end

  return p

end

function matrix_times_kron_prod(v::AbstractArray{T,2},a::AbstractArray{T,2},b::AbstractArray{T,2},c::AbstractArray{T,2}) where {T <: Real}

  product = Matrix(kron_prod_times_matrix(a',b',c',v')')

  return product

end

function dsylvester(a::AbstractArray{T,2}, b::AbstractArray{T,2}, c::Union{AbstractArray{T,1},AbstractArray{T,2}}) where {T <: Real}

  #= Uses the Hessenberg-Schur method to find the bounded solution of the
     discrete Sylvester equation:

           X + A*X*B = C

     Based on Golub, Nash, and Van Loan (1979).

  =#

  n = size(a, 1)
  m = size(b, 1)
  x = zeros(size(c))

  (s,u) = schur(Matrix(b'))
  (v,t) = hessenberg(a)

  c = v'*c*u

  j = m
  while j > 0
      j1 = j
      if j == 1
          block_size = 1
      elseif isequal(s[j,j-1],0.0) == false
          block_size = 2
          j -= 1
      else;
          block_size = 1
      end
      @views ajj = kron(s[j:j1,j:j1],t) + I
      @views rhs = vec(c[:,j:j1])
      if j1 < m
          @views rhs2 = t*(x[:,(j+1):m]*s[j:j1,(j+1):m]')
          rhs -= vec(rhs2)
      end
      w = ajj\rhs
      @views x[:,j] = w[1:n]
      if block_size == 2
          @views x[:,j1] = w[(n+1):2*n]
      end
      j -= 1
  end

  x = v*x*u'

  return x

end

function trm(x::AbstractArray{T,2}) where {T <: Real}

  # Computes the matrix trace as defined by Binning (2013).  Used for
  # computing the second-order terms, hss, gss.

  (n1,n2) = size(x)
  k = Int(round(sqrt(n2)))

  y = zeros(n1)
  for i = 1:k
      @views y += x[:,i+(i-1)*k]
  end

  return y

end

function trm2(x::AbstractArray{T,2}) where {T <: Real}

  # Computes the matrix trace as defined by Binning (2013).  Used for
  # computing the third-order terms, hssx, gssx.

  (n1,n2) = size(x)
  k = Int(round(n2^(1//3)))

  y = zeros(n1,k)
  for j = 1:k
      for i = 1:k
          @views y[:,j] += x[:,(j-1)+i+(i-1)*k^2]
      end
  end

  return y

end

function create_omega3(n::S) where {S <: Integer}

  # Creates the combination matrix for a third-order perturbation as defined in Levintal (2017).

  # This function is a simplified version of the create_OMEGA function originally written in
  # Matlab by Oren Levintal for his paper "Fifth Order Perturbation Solution to DSGE Models"
  # published in the Journal of Economic Dynamics and Control, 2017.  Permission to translate
  # this function into Julia and release it within the SolveDSGE module was granted by Oren
  # Levintal on February 5, 2020.

  ind = [1:n^3;]
  M = reshape(ind,1,n,n,n)
  Ix = eye(S,n^3)
  omega3 = (reshape(Ix[:,PermutedDimsArray(M,[1,4,2,3])],n^3,n^3)
         + reshape(Ix[:,PermutedDimsArray(M,[1,2,4,3])],n^3,n^3)
         + reshape(Ix[:,PermutedDimsArray(M,[1,2,3,4])],n^3,n^3))

  return omega3

end

function kron_prod_times_vector(A::Union{Array{Array{T,2},1},Array{Array{Complex{T},2},1}},x::Union{Array{T,1},Array{Complex{T},1}},n::Array{S,1},p::S) where {T <: Real, S <: Integer}

  # Computes y = (A[p] * A[p-1] * ... * A[1] )*x

  N = prod(n[1:p])
  z = copy(x)
  for i = 1:p
      z = (A[i]*reshape(z,n[i],Int(N/n[i])))'
  end
  y = reshape(Matrix(z),N)

  return y

end

function kron_prod_times_matrix(A::Union{Array{Array{T,2},1},Array{Array{Complex{T},2},1}},x::Union{Array{T,2},Array{Complex{T},2}},n::Array{S,1},p::S) where {T <: Real, S <: Integer}

  # Computes y = (A[p] * A[p-1] * ... * A[1] )*x

  N = prod(n[1:p])
  y = Array{Complex{T}}(undef,N,size(x,2))
  for j = 1:size(x,2)
      @views z = x[:,j]
      for i = 1:p
          z = (A[i]*reshape(z,n[i],Int(N/n[i])))'
      end
      y[:,j] .= reshape(Matrix(z),N)
  end

  return y

end

function KPShiftSolve(TT::Union{Array{Array{T,2},1},Array{Array{Complex{T},2},1}},n::Array{S,1},c::Union{Array{T,1},Array{Complex{T},1}},lambda::T,alpha::Union{T,Complex{T}}) where {T <: Real, S <: Integer}

  p = length(n)
  N = prod(n)

  c = copy(c)

  TT[p] = alpha*TT[p]
  if p == 1
      y = (TT[1] + lambda*Matrix{T}(I,n[1],n[1]))\c
  else
      y = Array{Complex{T}}(undef,N)
      mp = Int(N/n[p])
      for i = n[p]:-1:1
          idx = ((i-1)*mp+1):(i*mp)
          y[idx] = KPShiftSolve(TT[1:(p-1)],n[1:(p-1)],c[idx],lambda,TT[p][i,i])
          z = kron_prod_times_vector(TT[1:p],y[idx],n,p-1)
          for j = 1:(i-1)
              jdx = ((j-1)*mp+1):(j*mp)
              c[jdx] = c[jdx] - TT[p][j,i]*z
          end
      end
  end

  return y

end

function martin_van_loan(a::Array{T,2},b::Array{T,2},c::Array{T,2},d::Array{T,2},k::S) where {T <: Real, S <: Integer}

  #= Uses a recursive Schur method to find the bounded solution of the Sylvester
     equation:

     AX + B*X*(Kron^(k)C) = D

     Based on Martin and Van Loan (2006).  This is a simplified implementation
     of their algorithm, but it captures most of the gains over Golub, Nash,
     and van Loan (1979).

  =#

  a = copy(a)
  b = a\copy(b)
  c = copy(c)
  d = a\copy(d)

  (v,s) = hessenberg(b)       # v*s*v' = b
  (t,q) = schur(complex(c'))  # q*t*q' = c'

  v = Matrix(v)

  p = k + 2
  TT = Array{typeof(t)}(undef,p)
  TT[1] = s
  for i = 2:p
      TT[i] = conj(t)
  end

  Q     = fill(q,k+1)
  inv_Q = fill(Matrix(q'),k+1)

  n = fill(size(c,1),p)
  n[1] = size(b,1)
  N = prod(n)

  lambda = 1.0

  e = vec(v'*kron_prod_times_matrix(inv_Q,Matrix(d'),n[2:end],k+1)')

  y = reshape(KPShiftSolve(TT,n,e,lambda,1.0),size(d))
  x = real(Matrix(v*kron_prod_times_matrix(Q,Matrix(y'),n[2:end],k+1)'))

  return x

end

function dlyap(a::Array{T,2}, b::Array{T,2}) where {T <: Real}

  n = size(a,1)
  x = zeros(n,n)
  j = n

  (s,u) = schur(a)
  b = u'b*u

  while j > 0
      j1 = j
      if j == 1
          block = 1
      elseif !isequal(s[j,j-1],0.0)
          block = 2
          j -= 1
      else
          block = 1
      end
      @views lhs = kron(s[j:j1,j:j1],s) - I # I = eye(block*n)
      @views rhs = vec(b[:,j:j1])
      if j1 < n
          @views rhs2 = s*(x[:,(j1+1):n]*s[j:j1,(j1+1):n]')
          rhs += vec(rhs2)
      end
      w = -lhs\rhs
      @views x[:,j] = w[1:n]
      if block == 2
          @views x[:,j1] = w[(n+1):block*n]
      end
      j -= 1
  end

  x = u*x*u'

  return x

end

function ind2sub(i::S,dims::Tuple{S,Vararg{S}}) where {S <: Integer}

  if i < 1 || i > prod(dims)
      error("index is out of bounds.")
  end

  subs = CartesianIndices(dims)[i]

  return subs

end

function compute_variances(soln::FirstOrderSolutionStoch)

  hx = soln.hx
  k = soln.k
  gx = soln.gx
  sigma = soln.sigma

  var_states = dlyap(hx,k*sigma*k')
  var_jumps  = gx*var_states*gx'

return var_states,var_jumps

end

function _compute_chebyshev_integrals(eps_nodes::Array{T,1},eps_weights::Array{T,1},nodes::Array{T,1},order::S,rho::T,sigma::T) where {T <: AbstractFloat, S <: Integer}

  # Case where shocks are AR(1) and innovations are independent

  if rho == 0.0
    rho = eps()
  end

  selected_nodes = copy(nodes)
  if length(nodes) > 2
    selected_number = ceil(Int,length(nodes)^(1/2))
    if isodd(length(nodes)) && iseven(selected_number)
      selected_number -= 1
    elseif iseven(length(nodes)) && isodd(selected_number)
      selected_number -= 1
    end
    start_index = Int((length(nodes)-selected_number)/2)
    selected_nodes = nodes[start_index+1:start_index+selected_number]
  end

  terms_num  = Array{T}(undef,length(eps_nodes))
  integrals  = Array{T,2}(undef,order+1,length(selected_nodes))
  integrals2 = Array{T}(undef,order+1)
  for i = 1:(order+1)
    integrals2[i] = sum(exp.(sqrt(2)*sigma*(i-1)*eps_nodes).*eps_weights)*pi^(-1/2)
    for j = 1:length(selected_nodes)
      terms_num     .= rho*nodes[j] .+ sqrt(2)*sigma*eps_nodes
      terms_den      = rho*nodes[j]
      terms_num     .= chebyshev_polynomial(i,terms_num)[:,i]
      terms_den      = chebyshev_polynomial(i,terms_den)[i]
      integrals[i,j] = sum((terms_num/terms_den).*eps_weights)*pi^(-1/2)
     end
  end

  nodetoosmall = abs.(selected_nodes) .< sqrt(eps())
  if sum(nodetoosmall) > 0
    if length(selected_nodes) == 1
      integrals[:,1] .= integrals2
    else
      for i = 1:length(selected_nodes)
        if nodetoosmall[i] == 1
          if i == 1
            integrals[:,i] .= integrals[:,i+1]
        elseif i == length(selected_nodes)
            integrals[:,i] .= integrals[:,i-1]
          else
            integrals[:,i] .= (integrals[:,i-1]+integrals[:,i+1])/2
          end
        end
      end
    end
  end

  return reshape(sum(integrals,dims=2)/length(selected_nodes),order+1)

end

function _compute_chebyshev_integrals(eps_nodes::Array{T,1},eps_weights::Array{T,1},nodes::Array{Array{T,1},1},order::Union{S,Array{S,1}},Ρ::Array{T,2},k::Array{T,2}) where {T <: AbstractFloat, S <: Integer}

N = size(k,2)
for i = 1:N
  if Ρ[i,i] == 0.0
    Ρ[i,i] = eps()
  end
end

selected_nodes = similar(nodes)
for i = 1:length(nodes)
  if length(nodes[i]) > 2
  selected_number = ceil(Int,sqrt(length(nodes[i])))
    if isodd(length(nodes[i])) && iseven(selected_number)
      selected_number -= 1
    elseif iseven(length(nodes[i])) && isodd(selected_number)
      selected_number -= 1
    end
    start_index = Int((length(nodes[i])-selected_number)/2)
    selected_nodes[i] = nodes[i][start_index+1:start_index+selected_number]
  end
end

if typeof(order) == S
  ord = fill(order,N)
else
  ord = copy(order)
end

terms_num  = Array{T}(undef,length(eps_nodes))
integrals  = Array{T}(undef,((ord.+1)...,length.(selected_nodes)...))
integrals2 = Array{T}(undef,(ord.+1)...)

order_prod = prod(ord.+1)
nodes_prod = prod(length.(selected_nodes))
eps_prod   = length(eps_nodes)^N

for i = 1:order_prod
  ii = ind2sub(i,Tuple(ord.+1))
  int2 = 0.0
  for j = 1:eps_prod
    jj = ind2sub(j,Tuple(fill(length(eps_nodes),N)))
    eps_w    = eps_weights[jj[1]]
    eps_node = eps_nodes[jj[1]]
    for k = 2:N
      eps_w    *= eps_weights[jj[k]]
      eps_node = [eps_node;eps_nodes[jj[k]]]
    end
    int2 += exp(2^(N/2)*sqrt(det(k*k'))*collect(ii.-1)'*(k*k')*eps_node)*eps_w*pi^(-N/2)
  end
  integrals2[ii...] = int2 # exp(collect(ii.-1)'*(k*k')*collect(ii.-1)/2) # This is the analytic expression
end

for i = 1:order_prod
  ii = ind2sub(i,Tuple(ord.+1))
  for m = 1:nodes_prod
    mm = ind2sub(m,Tuple(length.(selected_nodes)))
    int2 = 0.0
    node = selected_nodes[1][mm[1]]
    for k = 2:N
      node = [node;selected_nodes[k][mm[k]]]
    end
    for j = 1:eps_prod
      jj = ind2sub(j,Tuple(fill(length(eps_nodes),N)))
      eps_w    = eps_weights[jj[1]]
      eps_node = eps_nodes[jj[1]]
      for k = 2:N
        eps_w    *= eps_weights[jj[k]]
        eps_node = [eps_node;eps_nodes[jj[k]]]
      end
      terms_num = Ρ*node + sqrt(2)*k*eps_node
      terms_den = Ρ*node
      num       = chebyshev_polynomial(ii[1],terms_num[1])[ii[1]]
      den       = chebyshev_polynomial(ii[1],terms_den[1])[ii[1]]
      for k = 2:N
        num = [num;chebyshev_polynomial(ii[k],terms_num[k])[ii[k]]]
        den = [den;chebyshev_polynomial(ii[k],terms_den[k])[ii[k]]]
      end
      int2 += (prod(num)/prod(den))*eps_w*pi^(-N/2)
    end
    integrals[ii...,mm...] = int2
  end
end

for i in eachindex(integrals)
  ii = ind2sub(i,Tuple(size(integrals)))
  if abs(integrals[i]) > 2.0 || isnan(integrals[i])
    if length(integrals) == 1
      integrals[i] = integrals2[i]
    else
      integrals[i] = integrals2[CartesianIndex(Tuple(ii)[1:N])]
    end
  end
end

for i = N:-1:1
  integrals = sum(integrals,dims = (N+i))/length(selected_nodes[i])
end

return reshape(integrals,Tuple(ord.+1))

end

function compute_chebyshev_integrals(eps_nodes::Array{T,1},eps_weights::Array{T,1},nodes::Array{Array{T,1},1},order::Union{S,Array{S,1}},Ρ::Array{T,2},k::Array{T,2}) where {T <: AbstractFloat, S <: Integer}

if !isdiag(Ρ.>sqrt(eps()))
  error("The autoregression matrix for the shocks must be diagonal")
end

ns = size(k,2)

if typeof(order) == S
  ord = fill(order,ns)
else
  ord = order[1:ns]
end

if !isdiag(abs.(k).>sqrt(eps())) # Correlated innovations
  integrals = _compute_chebyshev_integrals(eps_nodes,eps_weights,nodes[1:ns],ord,Ρ,k)
  return integrals
else
  integrals = Array{Array{T,1},1}(undef,ns)
  for i = 1:ns
    integrals[i] = _compute_chebyshev_integrals(eps_nodes,eps_weights,nodes[i],ord[i],Ρ[i,i],k[i,i])
  end
  return integrals
end

end

function scale_chebyshev_weights!(weights::Array{Array{T,N},1},scaled_weights::Array{Array{T,N},1},integrals::Array{Array{T,1},1},j_approx::Union{S,Array{S,1}},ns::S) where {T <: AbstractFloat, N, S <: Integer}

  for i = 1:length(j_approx)
      for j = 1:ns
          index = [1:ndims(weights[i]);]
          index[1],index[j] = index[j],index[1]
          scaled_weights[i] .= permutedims(integrals[j].*permutedims(weights[i],index),index)
      end
  end

end

function scale_chebyshev_weights!(weights::Array{Array{T,N},1},scaled_weights::Array{Array{T,N},1},integrals::Array{T,N2},j_approx::Union{S,Array{S,1}},ns::S) where {T <: AbstractFloat, N, N2, S <: Integer}

  for i = 1:length(j_approx)
      for j = 1:ns
          scaled_weights[i] .= integrals.*weights[i]
      end
  end

end

function _compute_smolyak_integrals(eps_nodes::Array{T,1},eps_weights::Array{T,1},nodes::Array{T,1},order::S,rho::T,sigma::T) where {T <: AbstractFloat, S <: Integer}

  # Case where shocks are AR(1) and innovations are independent

  if rho == 0.0
    rho = eps()
  end

  terms_num  = Array{T}(undef,length(eps_nodes))
  integrals  = Array{T,2}(undef,order+1,length(nodes))
  integrals2 = Array{T}(undef,order+1)
  for i = 1:(order+1)
    integrals2[i] = sum(exp.(sqrt(2)*sigma*(i-1)*eps_nodes).*eps_weights)*pi^(-1/2)
    for j = 1:length(nodes)
      terms_num     .= rho*nodes[j] .+ sqrt(2)*sigma*eps_nodes
      terms_den      = rho*nodes[j]
      terms_num     .= chebyshev_polynomial(i,terms_num)[:,i]
      terms_den      = chebyshev_polynomial(i,terms_den)[i]
      integrals[i,j] = sum((terms_num/terms_den).*eps_weights)*pi^(-1/2)
     end
  end

  nodetoosmall = abs.(nodes) .< sqrt(eps())
  if sum(nodetoosmall) > 0
    if length(nodes) == 1
      integrals[:,1] .= integrals2
    else
      for i = 1:length(nodes)
        if nodetoosmall[i] == 1
          if i == 1
            integrals[:,i] .= integrals[:,i+1]
        elseif i == length(nodes)
            integrals[:,i] .= integrals[:,i-1]
          else
            integrals[:,i] .= (integrals[:,i-1]+integrals[:,i+1])/2
          end
        end
      end
    end
  end

  return reshape(sum(integrals,dims=2)/length(nodes),order+1)

end

function compute_smolyak_integrals(eps_nodes,eps_weights,nx,order,grid,RHO,k)

integrals = ones(nx,order+1)
for j = 1:size(k,2)
    nodes = unique(grid[:,j])
    integrals[j,:] .= _compute_smolyak_integrals(eps_nodes,eps_weights,nodes,order,RHO[j,j],k[j,j])
end

#  for j = 1:size(sigma,2)
#      for i = 1:(order+1)
#          integrals[j,i] = sum(exp.(sqrt(2)*k[j,j]*(i-1)*eps_nodes).*eps_weights)*pi^(-1/2)
#      end
#  end

return integrals

end

function weight_scale_factors(eps_nodes,eps_weights,multi_index,nx,grid,RHO,sigma)

unique_multi_index = sort(unique(multi_index))
unique_orders = SmolyakApprox.m_i(unique_multi_index).-1

# Here we construct the base integrals

base_integrals = Array{Array{Float64,2}}(undef,length(unique_orders))
for i = 1:length(unique_orders)
  base_integrals[i] = compute_smolyak_integrals(eps_nodes,eps_weights,nx,unique_orders[i],grid,RHO,sigma)
end

# Compute the unique polynomial terms from the base polynomials

unique_base_integrals = Array{Array{Float64,2}}(undef,length(unique_orders))
for i = length(unique_orders):-1:2
  unique_base_integrals[i] = base_integrals[i][:,size(base_integrals[i-1],2)+1:end]
end
unique_base_integrals[1] = base_integrals[1]

# Construct the first row of the interplation matrix

new_integrals = unique_base_integrals[multi_index[1,1]][1,:]
for i = 2:size(multi_index,2)
  new_integrals = kron(new_integrals,unique_base_integrals[multi_index[1,i]][i,:])
end

weight_scale_factor = copy(new_integrals)

# Iterate over nodes, doing the above three steps at each iteration

for j = 2:size(multi_index,1)

  new_integrals = unique_base_integrals[multi_index[j,1]][1,:]
  for i = 2:size(multi_index,2)
    new_integrals = kron(new_integrals,unique_base_integrals[multi_index[j,i]][i,:])
  end
  weight_scale_factor = [weight_scale_factor; new_integrals]

end

return weight_scale_factor

end

function scale_smolyak_weights(weights,weight_scale_factor)

  scaled_weights = weights.*weight_scale_factor

end

function compute_piecewise_linear_integrals(eps_nodes,eps_weights,sigma)

integral = 1.0
#integral = sum(exp.(sqrt(2)*sigma*eps_nodes).*eps_weights)*pi^(-1/2)

return integral

end

function mylen(len::T,a::Array{T,N},b::Array{T,N}) where {T <: AbstractFloat, N}

  for i in eachindex(a)
      len = max(len,abs(a[i]-b[i]))
  end

  return len

end


"""
This module provides some linear fitting methods.
"""
module LinearFitting

using LinearAlgebra: dot
using Polynomials: polyder, polyfit, degree, coeffs, Poly
using MLStyle: @match

using ..FiniteStrains

export energy_strain_expansion,
       energy_strain_derivative,
       strain_volume_derivative,
       energy_volume_expansion,
       energy_volume_derivatives,
       energy_volume_derivative_at_order

energy_strain_expansion(f::Vector{<:Real}, e::Vector{<:Real}, n::Int)::Poly =
    polyfit(f, e, n)

energy_strain_derivative(p::Poly, m::Int)::Poly = polyder(p, m)

function strain_volume_derivative(s::EulerianStrain, v0::Real, v::Real, m::Int)
    m == 1 && return -1 / (3v) * cbrt(v0 / v)^2
    -(3m + 2) / (3v) * strain_volume_derivative(s, v0, v, m - 1)
end
function strain_volume_derivative(s::LagrangianStrain, v0::Real, v::Real, m::Int)
    m == 1 && return -1 / (3v) * cbrt(v / v0)^2
    -(3m - 2) / (3v) * strain_volume_derivative(s, v0, v, m - 1)
end
function strain_volume_derivative(s::NaturalStrain, v0::Real, v::Real, m::Int)
    m == 1 && return 1 / (3v)
    -m / v * strain_volume_derivative(s, v0, v, m - 1)
end
function strain_volume_derivative(s::InfinitesimalStrain, v0::Real, v::Real, m::Int)
    m == 1 && return (1 - getstrain(s, v0, v))^4 / 3 / v0
    -(3m + 1) / (3v) * strain_volume_derivative(s, v0, v, m - 1)
end

function energy_volume_expansion(
    s::FiniteStrain,
    v0::Real,
    v::Real,
    p::Poly,
    highest_order::Int = degree(p),
)
    # The zeroth order value plus values from the first to the ``highest_order`.
    p(v) + dot(
        energy_volume_derivatives(s, v0, v, p, highest_order),
        getstrain(s, v0, v) .^ collect(1:highest_order),
    )
end

function energy_volume_derivatives(
    s::FiniteStrain,
    v0::Real,
    v::Real,
    p::Poly,
    highest_order::Int,
)
    0 ≤ highest_order ≤ degree(p) ? (x = 1:highest_order) :
    throw(DomainError("The `highest_order` must be within 0 to $(degree(p))!"))
    strain_derivatives = map(m -> strain_volume_derivative(s, v0, v, m), x)
    energy_derivatives = map(f -> f(v), map(m -> energy_strain_derivative(p, m), x))
    map(
        m -> energy_volume_derivative_at_order(m)(strain_derivatives, energy_derivatives),
        x,
    )
end

function energy_volume_derivative_at_order(m::Int)::Function
    function (f::Vector{<:Real}, e::Vector{<:Real})
        @match m begin
            1 => e[1] * f[1]
            2 => e[2] * f[1]^2 + e[1] * f[1]
            3 => e[3] * f[1]^3 + 3 * f[1] * f[2] * e[2] + e[1] * f[3]
            4 => e[4] * f[1]^4 + 6 * f[1]^2 * f[2] * e[3] +
                 (4 * f[1] * f[3] + 3 * f[3]^2) * e[2] + e[1] * f[3]
            _ => error("Expansion is not defined at order = $(m)!")
        end
    end
end

end


using JLD
using SharedArrays
using Distributed
include("../examples/baker.jl")
function obj_fun(x,y)
		return cos(4*y)
end
function obj_fun_erg_avg(s)
	nSteps = 10000
	u = 2*pi*rand(2)
	J = 0.
	u_trj = step(u,s,nSteps-1)
	x, y = view(u_trj,1,:), view(u_trj,2,:)
	J = sum(obj_fun.(x,y)/nSteps)
	return J
end
function get_Javg_vs_s(ind)
	s = zeros(4)
	n_pts = 10
	n_rep = 16000
	s_ind = LinRange(0.,1.0,n_pts)
	J = zeros(n_pts)
	J_proc = SharedArray{Float64}(n_rep)
	for i = 1:n_pts
		@show s_ind[i]
		s[ind] = s_ind[i]
		#s[3] = s_ind[i]
		J_proc .= 0.
		t = @distributed for n=1:n_rep
			J_proc[n] = obj_fun_erg_avg(s)/n_rep
		end
		wait(t)
		J[i] = sum(J_proc)
	end
	save("../data/obj_erg_avg/cos4y_s$(ind)_sens.jld",
		 "s$ind", s_ind,
		"J", J)
end


module ComputationalHomology

using LinearAlgebra
using SparseArrays
import SmithNormalForm: smith
import Distances
import Random
import BoundingSphere
import Statistics: mean

import Base: ==, -, +, *
import Base.Iterators: pairs

export AbstractCell,
       dim, faces, volume,
       Simplex, Cube,

       AbstractChain,
       setdim!, simplify,
       Chain,

       AbstractComplex,
       boundary, coboundary, celltype, cells, boundary_matrix,
       SimplicialComplex,
       vietorisrips, witness, cech, čech,

       AbstractHomology, grouptype, group,
       Homology, homology, withgenerators, generators,

       Filtration, filtration, order, simplices,

       AbstractPersistenceReduction,
       StandardReduction, TwistReduction,
       pairs, intervals, reduce!, reduce,
       PersistentHomology, persistenthomology,

       Interval, Landscape, landscape, mean

include("cells.jl")
include("chain.jl")
include("complex.jl")
include("simplicial.jl")
include("constructions.jl")
include("filtration.jl")
include("homology.jl")
include("persistence.jl")
include("landscape.jl")

end # module


module MatrixCoalescent

using LinearAlgebra

export MatCoal, eigenvals!, project!, interval_lengths!, dim

"""
An object of type MatCoal holds the data needed to calculate two
quantities under the matrix coalescent: (1) the probability that there
are k lineages at the ancient end of an epoch, given that there are
nLineages at the recent end, and (2) the expected duration of the
subinterval during which there are k lineages.
"""
mutable struct MatCoal{T<:Real}
    nLineages :: Int # number of lineages at recent end of epoch

    # Vector of dimension nLineages-1. beta[i] is (i+1) choose 2.  For
    # example, if nLineages=3, then beta has two entries: 2 choose 2
    # and 3 choose 2.
    beta :: Vector{T}

    # Vector for calculating E[len] of coalescent intervals
    # Reciprocal of beta.
    rbeta :: Vector{T}

    # Matrix of scaled column eigenvectors (upper triangular)
    gmat :: Matrix{T}

    # Matrix for calculating expected lengths of coalescent intervals.
    # Also upper triangular.
    hmat :: Matrix{T}
end

"""
    MatCoal(el_type::DataType, nLineages)

Construct an object of type `MatCoal`, which describes an epoch of
population history during which the population's size was
constant. The first argument is the type to be used in representing
probabilities and expectations. The constructor will accept any Real
type, but the other functions require a subtype of AbstractFloat. The
second argument is the number of lineages at the recent end of this
epoch of population history.
"""
function MatCoal(el_type::DataType, nLineages)

    nLin = Int(nLineages)
    n = nLin - 1

    # Column eigenvectors and row eigenvectors
    cvec = zeros(Rational{Int}, n, n)
    rvec = zeros(Rational{Int}, n, n)
    for j = 2:nLin
        jj = j-1
        lambda = -j*(j-1) # eigenvalue
        cvec[jj,jj] = rvec[jj,jj] = 1//1
        for i in j-1 : -1 : 2
            ii = i-1
            m = (i*(i+1)) // (i*(i-1) + lambda)
            cvec[ii, jj] = m * cvec[ii+1, jj]
        end
        for i in j+1 : nLin
            ii = i-1
            m = (i*(i-1)) // (i*(i-1) + lambda)
            rvec[jj, ii] = m * rvec[jj, ii-1]
        end
    end

    # Calculate coefficients of exponentials in x(t).  This will be
    # converted to float to produce the the matrix gmat.
    for ii in 1:n
        for jj in ii:n
            cvec[ii,jj] *= rvec[jj, n]
        end
    end

    # rbeta[i] is reciprocal of beta[i] = (i+1) choose 2.
    beta = Vector{Rational{Int}}(undef, n)
    rbeta = Vector{Rational{Int}}(undef, n)
    for i in 1:n
        j = i+1
        beta[i] = (j*(j-1)) // 2
        rbeta[i] = 1/beta[i]
    end

    # Initially hmat == cvec
    hmat = Matrix{Rational{Int}}(cvec)

    # Cumulative sum, so i'th row is sum of i:n initial rows
    for i in n-1 : -1 : 1
        for j in 1:n
            hmat[i,j] += hmat[i+1, j]
        end
    end

    # Weight row i by -1/beta[i]
    for i in 1:n
        for j in 1:n
            hmat[i,j] *= -rbeta[i]
        end
    end

    MatCoal(nLin,
            Vector{el_type}(beta),
            Vector{el_type}(rbeta),
            Matrix{el_type}(cvec),
            Matrix{el_type}(hmat))
end

"""
Dimension of a MatCoal object is 1 less than the number (nLin) of
lineages at the recent end of the epoch.
"""
function dim(mc::MatCoal)
    return length(mc.beta)
end

"""
Write a representation of an MatCoal to io.
"""
function Base.show(io::IO, mc::MatCoal)
    println(io, "nLineages:", mc.nLineages)
    print(io, "beta:")
    for b in mc.beta
        print(io, " ", b)
    end
    println(io)
    print(io, "rbeta:")
    for nrb in mc.rbeta
        print(io, " ", nrb)
    end
    println(io)
    println(io, "gmat:")
    println(io, mc.gmat)
    println(io, "hmat:")
    println(io, mc.hmat)
end

"""
    eigenvals!(eig::Vector{T}, v::T,
                    mc::MatCoal{T}) where {T<:AbstractFloat}

Calculate eigenvalues for time ``v``, where ``v = t/2N`` is time
measured backwards from the recent end of the epoch in units of
``2N`` generations. It represents the length of the epoch and may be
0 or infinite. Time affects the coalescent process only via its effect
on eigenvalues. Consequently, this is the only function that takes
time as an argument.
"""
function eigenvals!(eig::Vector{T}, v::T,
                    mc::MatCoal{T}) where {T<:AbstractFloat}
    n = length(eig)
    @assert n == dim(mc)
    for i in 1:n
        eig[i] = exp(-v*mc.beta[i]);
    end
    return eig
end

"""
    project!(ans::Vector{T}, eig::Vector{T},
                 mc::MatCoal{T}) where {T <: AbstractFloat}

Calculate the probability that there are 2,3,...(dim+1) lines of
descent at the ancient end of the epoch. Call eigenvals! first to
calculate eig. The length of the interval affects the calculation only
through its effect on the eigenvalues in eig, so it does not appear as
an argument to project!.
"""
function project!(ans::Vector{T}, eig::Vector{T},
                 mc::MatCoal{T}) where {T <: AbstractFloat}
    @assert length(ans) == length(eig)
    @assert length(ans) == dim(mc)

    mul!(ans, mc.gmat, eig)
end

"""
    interval_lengths!(ans::Vector{T}, eig::Vector{T},
                 mc::MatCoal{T}) where {T <: AbstractFloat}

Vector of expected lengths of coalescent intervals during which there
were 2,3,...(dim+1) lines of descent. To get the expected length of
the interval with 1 line of descent, subtract the sum of ans from v,
where v is the time argument given to eigenvals!.  Call eigenvals!
first to calculate eig.  The length of the interval affects the
calculation only via the eigenvalues in eig and therefore does not
appear as an argument in interval_lengths;
"""
function interval_lengths!(ans::Vector{T}, eig::Vector{T},
                 mc::MatCoal{T}) where {T <: AbstractFloat}
    @assert length(ans) == length(eig)
    @assert length(ans) == dim(mc)

    # ans = rbeta + hmat*eig
    copy!(ans, mc.rbeta)
    mul!(ans, mc.hmat, eig, 1, 1)
end

end # module


function renormalize{T}(tensorT1::Array{T,6}, tensorT2::Array{T,6}, dimM::Int; whichIsTensorT::Int = 1)
	if whichIsTensorT ==1
		tensorT = tensorT1
		tensorTtilde = tensorT2
	else
		tensorT = tensorT2
		tensorTtilde = tensorT1
	end
	tensorU = getTensorU(tensorT, dimM)
	tensorV = getTensorV(tensorT, dimM)
	newTensorT = getNewTensorT_2dQ(tensorT, tensorT, tensorU, tensorV)
	normalizationFactor = maximum(newTensorT)
	newTensorTtilde = getNewTensorT_2dQ(tensorT1, tensorT2, tensorU, tensorV)
	newTensorTtilde = newTensorTtilde./ normalizationFactor
	newTensorT = newTensorT./ normalizationFactor
	return newTensorT, newTensorTtilde, normalizationFactor
end

function getNewTensorT_2dQ{T}(tensorT1::Array{T,6}, tensorT2::Array{T,6}, tensorU::Array{T,3}, tensorV::Array{T,3})
	@tensor begin
		d1[x2,x,x1p,y1,y1p,z,i] := tensorT1[x1,x1p,y1,y1p,z,i] * tensorU[x1,x2,x]
		d2[x2,x,x2p,xp,y1,y1p,z,i] := d1[x2,x,x1p,y1,y1p,z,i] * tensorU[x1p,x2p,xp]
		d3[x,xp,y1,y1p,y2,y2p,z,zp] := d2[x2,x,x2p,xp,y1,y1p,z,i] * tensorT2[x2,x2p,y2,y2p,i,zp]
		d4[x,xp,y,y1p,y2p,z,zp] := d3[x,xp,y1,y1p,y2,y2p,z,zp] * tensorV[y1,y2,y]
		newTensorT[x,xp,y,yp,z,zp] := d4[x,xp,y,y1p,y2p,z,zp] * tensorV[y1p,y2p,yp]
	end
	return newTensorT
end

function getTensorU{T}(tensorT::Array{T,6}, dimM::Int)
	tenmatMMd = getTenmatMMd(tensorT)
	Ul, lambdaVector, Uld = svd(tenmatMMd.matrix)
	trunUl = truncMatrixU(Ul,dimM)
	tensorU = matU2tenU(trunUl)
	return tensorU
end

function getTensorV{T}(tensorT::Array{T,6}, dimM::Int)
	tensorV = getTensorU(permutedims(tensorT,[3,4,1,2,5,6]), dimM)
	return tensorV
end

function getTenmatMMd{T}(tensorT::Array{T,6})
	@tensor begin
		dummy1[x1,a1,i,j] := tensorT[x1,x1p,y1,y1p,z,i] *
			tensorT[a1,x1p,y1,y1p,z,j]
		dummy2[x2,a2,i,j] := tensorT[x2,x2p,y2,y2p,i,zp] *
			tensorT[a2,x2p,y2,y2p,j,zp]
		tensorMMd[x1,x2,a1,a2] := dummy1[x1,a1,i,j] *
			dummy2[x2,a2,i,j]
	end
	tenmatMMd = tensor2tenmat(tensorMMd,[1,2],[3,4])
	return tenmatMMd
end


######## FARIMA Process #########
"""
Fractional Auto-regression Integrated Moving Average (FARIMA) process.
"""
struct FARIMA <: DiscreteTimeStochasticProcess
    d::Real
    ar::Vector{AbstractFloat}
    ma::Vector{AbstractFloat}

    FARIMA(d::Real, ar::Vector{<:AbstractFloat}, ma::Vector{<:AbstractFloat}) = new(d, ar, ma)

    # function FARIMA(d::Real, ar::Vector{AbstractFloat}, ma::Vector{AbstractFloat})
    #     # (typeof(P)==Int64 && P==length(ar)) || error("Inconsistent parameters for auto-regression.")
    #     # (typeof(Q)==Int64 && Q==length(ma)) || error("Inconsistent parameters for moving average.")
    #     # abs(d) < 0.5 || error("Order of fractional differential must be in the range (-0.5, 0.5).")
    #     # new(d, all(ar.==0) ? Float64[] : ar, all(ma.==0) ? AbstractFloat[] : ma)
    #     new(d, ar, ma)
    # end
end

FARIMA(d::Real) = FARIMA(d, Float64[], Float64[])

# parameters(X::FARIMA{P,Q}) where {P,Q} = Dict('p'=>P, 'd'=>X.d, 'q'=>Q)
# parameters(X::FARIMA{P,Q}) where {P,Q} = (P, X.d, Q)

# FARIMA(d::Real, ar::Vector{AbstractFloat}, ma::Vector{AbstractFloat}) = FARIMA{length(ar), length(ma)}(d, ar, ma)

######## Fractional Integrated Process ########
"""
Fractional integrated process.

This is a stationary process defined by ∇^d X(t) = ε(t), where ∇^d, d in (-1/2, 1/2) is the fractional differential operator, and ε(t) are i.i.d. standard Gaussian variables.
"""
struct FractionalIntegrated <: DiscreteTimeStationaryProcess
    d::Real

    function FractionalIntegrated(d::Real)
        abs(d) < 0.5 || error("Order of fractional differential must be in the range (-0.5, 0.5).")
        # new(d, all(ar.==0) ? Float64[] : ar, all(ma.==0) ? Float64[] : ma)
        new(d)
    end
end

ss_exponent(X::FractionalIntegrated) = X.d + 1/2

partcorr(X::FractionalIntegrated, k::DiscreteTime) = X.d/(k-X.d)

function autocov(X::FractionalIntegrated, n::DiscreteTime)
    return n > 0 ? (n-1+X.d) / (n-X.d) * autocov(X, n-1) : gamma(1-2*X.d) / gamma(1-X.d)^2
end

"""
Note: The covariance of a fractional integrated process is computed recursively, and `autocov!()` is reloaded for reason of efficiency.
"""
function autocov!(C::Vector{<:AbstractFloat}, X::FractionalIntegrated, G::AbstractVector{<:DiscreteTime})
    # check dimension
    # @assert length(C) == length(G)
    # @assert isregulargrid(G) && eltype(G)<:Int

    V = zeros(G[end])
    V[1] = gamma(1-2*X.d) / gamma(1-X.d)^2  # cov(0)
    for n = 1:length(V)-1
        V[n+1] = V[n] * (n-1+X.d) / (n-X.d)
    end
    for n = 1:length(C)
        C[n] = V[G[n]]
    end
    return C
end


using SwitchTimeOpt
using Base.Test
using Ipopt

include("linsys.jl")
linsystest()

include("nonlinsys.jl")
nonlinsystest()


# file compied from https://github.com/davidanthoff/WRS.jl/tree/master/src/WRS.jl

module WRS

using Distributions
using Distributed

export hd, pb2gen

# This implements (3.16) from (2017) p. 71-72
function hd(X, q=0.5, issorted=false)
    n = length(X)

    a = (n+1)*q
    b = (n+1)*(1-q)

    β = Beta(a, b)

    sorted_X = issorted ? X : sort(X)

    θ = 0.
    for i=1:n
        w = cdf(β, i/n) - cdf(β, (i-1)/n)
        θ += w*sorted_X[i]
    end

    return θ
end

function pb2gen(x, y; quantiles=[0.05, 0.25, 0.5, 0.75, 0.95], alpha=0.05, nboot=2000, estimator=hd)
    bootstrapped_diff_est = pmap(1:nboot) do i
        sampled_values_x = sample(x, length(x), replace=true)
        sampled_values_y = sample(y, length(y), replace=true)

        sort!(sampled_values_x)
        sort!(sampled_values_y)

        return [estimator(sampled_values_x, q, true) - estimator(sampled_values_y, q, true) for q in quantiles]
    end
    bootstrapped_diff_estimates = [[bootstrapped_diff_est[r_i][q_i] for r_i in 1:nboot] for q_i in 1:length(quantiles)]

    return map(enumerate(bootstrapped_diff_estimates)) do (q_i, bootstrapped_diff_est)
        sort!(bootstrapped_diff_est)

        low = round(Int, (alpha/2)*nboot)+1
        up = nboot-low

        q = quantiles[q_i]

        est_x = estimator(x, q)
        est_y = estimator(y, q)
        est_diff = est_x - est_y
        ci_low = bootstrapped_diff_est[low]
        ci_up = bootstrapped_diff_est[up]

        A = count(i->i<0,bootstrapped_diff_est)
        C = count(i->i==0,bootstrapped_diff_est)
        p_hat_star = A/nboot+0.5*C/nboot
        pvalue = 2*(min(p_hat_star,1-p_hat_star))

        se = var(bootstrapped_diff_est)

        return (quantile=q, est_x=est_x, est_y=est_y, est_diff=est_diff, ci_low=ci_low, ci_up=ci_up, pvalue=pvalue, se=se, signif=pvalue < alpha)
    end
end

end # module


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

function displacement(alpha, x)
    u1 = alpha * x[2] * sin(pi * x[1])
    u2 = alpha * (x[1]^3 + cos(pi * x[2]))
    return [u1, u2]
end

function body_force(lambda, mu, alpha, x)
    b1 = alpha * (lambda + 2mu) * pi^2 * x[2] * sin(pi * x[1])
    b2 =
        -alpha * (6mu * x[1] + (lambda + mu) * pi * cos(pi * x[1])) +
        alpha * (lambda + 2mu) * pi^2 * cos(pi * x[2])
    return [b1, b2]
end

function onboundary(x, L, W)
    return x[2] ≈ 0.0 || x[1] ≈ L || x[2] ≈ W || x[1] ≈ 0.0
end

function error_for_plane_interface(
    x0,
    normal,
    nelmts,
    polyorder,
    numqp,
    penaltyfactor,
    eta,
)
    L = 1.0
    W = 1.0
    lambda, mu = 1.0, 2.0
    dx = 1.0 / nelmts
    penalty = penaltyfactor / dx * (lambda + mu)
    alpha = 0.1
    stiffness = CutCellDG.HookeStiffness(lambda, mu, lambda, mu)

    elasticitybasis = LagrangeTensorProductBasis(2, polyorder)
    levelsetbasis = HermiteTensorProductBasis(2)

    dim, nf = size(interpolation_points(levelsetbasis))
    refpoints = interpolation_points(elasticitybasis)

    cgmesh =
        CutCellDG.CGMesh([0.0, 0.0], [L, W], [nelmts, nelmts], nf)
    mesh = CutCellDG.DGMesh([0.0, 0.0], [L, W], [nelmts, nelmts], refpoints)

    levelset = CutCellDG.LevelSet(
        x -> plane_distance_function(x, normal, x0),
        cgmesh,
        levelsetbasis,
    )

    cutmesh = CutCellDG.CutMesh(mesh, levelset)
    cellquads = CutCellDG.CellQuadratures(cutmesh, levelset, numqp)
    interfacequads = CutCellDG.InterfaceQuadratures(cutmesh, levelset, numqp)
    facequads = CutCellDG.FaceQuadratures(cutmesh, levelset, numqp)

    mergedmesh =
        CutCellDG.MergedMesh!(cutmesh, cellquads, facequads, interfacequads)

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

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

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

    solution = matrix \ rhs
    nodaldisplacement = reshape(solution, 2, :)

    err = mesh_L2_error(
        nodaldisplacement,
        x -> displacement(alpha, x),
        elasticitybasis,
        cellquads,
        mergedmesh,
    )
end

x0 = [0.5, 0.0]
normal = [1.0, 0.0]
powers = [3, 4, 5]
nelmts = [2^p + 1 for p in powers]
polyorder = 1
numqp = required_quadrature_order(polyorder)
penaltyfactor = 1e2
eta = 1

err = [
    error_for_plane_interface(
        x0,
        normal,
        ne,
        polyorder,
        numqp,
        penaltyfactor,
        eta,
    ) for ne in nelmts
]

u1err = [er[1] for er in err]
u2err = [er[2] for er in err]

dx = 1.0 ./ nelmts
u1rate = convergence_rate(dx, u1err)
u2rate = convergence_rate(dx, u2err)

@test allapprox(u1rate, repeat([2.0], length(u1rate)), 0.1)
@test allapprox(u1rate, repeat([2.0], length(u2rate)), 0.1)

x0 = [0.5, 0.0]
normal = [1.0, 0.0]
powers = [3, 4, 5]
nelmts = [2^p + 1 for p in powers]
polyorder = 2
numqp = required_quadrature_order(polyorder)
penaltyfactor = 1e2
eta = 1

err = [
    error_for_plane_interface(
        x0,
        normal,
        ne,
        polyorder,
        numqp,
        penaltyfactor,
        eta,
    ) for ne in nelmts
]

u1err = [er[1] for er in err]
u2err = [er[2] for er in err]

dx = 1.0 ./ nelmts
u1rate = convergence_rate(dx, u1err)
u2rate = convergence_rate(dx, u2err)

@test allapprox(u1rate, repeat([3.0], length(u1rate)), 0.05)
@test allapprox(u1rate, repeat([3.0], length(u2rate)), 0.05)


using HypothesisTests, Test
using HypothesisTests: default_tail

@testset "Wilcoxon" begin
@testset "Basic exact test" begin
	@test abs(pvalue(ExactSignedRankTest([1:10;], [2:2:20;])) - 0.0020) <= 1e-4
	@test abs(pvalue(ExactSignedRankTest([2:2:20;], [1:10;])) - 0.0020) <= 1e-4
	@test abs(pvalue(ExactSignedRankTest([1:10;], [2:2:16; -1; 1])) - 0.4316) <= 1e-4
	@test abs(pvalue(ExactSignedRankTest([2:2:16; -1; 1], [1:10;])) - 0.4316) <= 1e-4
	@test default_tail(ExactSignedRankTest([1:10;], [2:2:20;])) == :both
	show(IOBuffer(), ExactSignedRankTest([1:10;], [2:2:20;]))
end

@testset "Exact with ties" begin
	@test abs(pvalue(ExactSignedRankTest([1:10;], [1:10;])) - 1) <= 1e-4
	@test abs(pvalue(ExactSignedRankTest([1:10;], [2:11;])) - 0.0020) <= 1e-4
	@test abs(pvalue(ExactSignedRankTest([2:11;], [1:10;])) - 0.0020) <= 1e-4
	@test abs(pvalue(ExactSignedRankTest(1:10, [1:5; ones(5)])) - 0.0625) <= 1e-4
	@test abs(pvalue(ExactSignedRankTest([1:5; ones(5)], [1:10;])) - 0.0625) <= 1e-4
	show(IOBuffer(), ExactSignedRankTest([1:10;], [1:10;]))
end

@testset "Approximate test" begin
	@test abs(pvalue(ApproximateSignedRankTest([1:10;], [2:2:20;])) - 0.005922) <= 1e-6
	@test abs(pvalue(ApproximateSignedRankTest([2:2:20;], [1:10;])) - 0.005922) <= 1e-6
	@test abs(pvalue(ApproximateSignedRankTest([1:10;], [2:2:16; -1; 1])) - 0.4148) <= 1e-4
	@test abs(pvalue(ApproximateSignedRankTest([2:2:16; -1; 1], [1:10;])) - 0.4148) <= 1e-4
	@test default_tail(ApproximateSignedRankTest([1:10;], [2:2:20;])) == :both
	show(IOBuffer(), ApproximateSignedRankTest([1:10;], [2:2:20;]))
end

@testset "Approximate with ties" begin
	@test abs(pvalue(ApproximateSignedRankTest([1:10;], [1:10;])) - 1) <= 1e-4
	@test abs(pvalue(ApproximateSignedRankTest([1:10;], [2:11;])) - 0.001904) <= 1e-6
	@test abs(pvalue(ApproximateSignedRankTest([2:11;], [1:10;])) - 0.001904) <= 1e-6
	@test abs(pvalue(ApproximateSignedRankTest([1:10;], [1:5; ones(5)])) - 0.05906) <= 1e-5
	@test abs(pvalue(ApproximateSignedRankTest([1:5; ones(5)], 1:10)) - 0.05906) <= 1e-5
	show(IOBuffer(), ApproximateSignedRankTest([1:10;], [1:10;]))
end

@testset "Tests for automatic selection" begin
	@test abs(pvalue(SignedRankTest([1:10;], [2:2:20;])) - 0.0020) <= 1e-4
	@test abs(pvalue(SignedRankTest([1:10;], [2:11;])) - 0.0020) <= 1e-4
	@test default_tail(SignedRankTest([1:10;], [2:2:20;])) == :both
	show(IOBuffer(), SignedRankTest([1:10;], [2:2:20;]))
end

@testset "One Sample tests" begin
	# P-value computed using R wilcox.test
	@test abs(pvalue(SignedRankTest([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] .- 10.1)) - 0.09460449) <= 1e-4
	# P-value computed using R wilcox.test
	@test abs(pvalue(SignedRankTest([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] .- 10.1)) - 0.1928101) <= 1e-4
end

@testset "One Sample tests with ties" begin
	# P-value computed using R package exactRankTests wilcox.exact
	@test abs(pvalue(SignedRankTest([1,2,3,4,5,6,7,10,10,10,10,10,13,14,15] .- 10.1)) - 0.04052734) <= 1e-4
	# P-value computed using R wilcox.test
	@test abs(pvalue(SignedRankTest([1,2,3,4,5,6,7,10,10,10,10,10,13,14,15,16] .- 10.1)) - 0.1021964) <= 1e-4
end

@testset "Issue 128" begin
	@test pvalue(SignedRankTest([54.5, 54.5, 95.0, 51.5]), tail=:left)  == 1
	@test pvalue(SignedRankTest([54.5, 54.5, 95.0, 51.5]), tail=:right) == 0.0625
end

@testset "Test confidence interval" begin
x = [-7.8, -6.9, -4.7, 3.7, 6.5, 8.7, 9.1, 10.1, 10.8, 13.6, 14.4, 16.6, 20.2, 22.4, 23.5]
	@test isapprox(confint(ExactSignedRankTest(x))[1], 3.3, atol=1e-4)
	@test isapprox(confint(ExactSignedRankTest(x))[2], 15.5, atol=1e-4)
	@test isapprox(confint(ApproximateSignedRankTest(x))[1], 3.3, atol=1e-4)
	@test isapprox(confint(ApproximateSignedRankTest(x))[2], 15.5, atol=1e-4)
	@test isapprox(confint(SignedRankTest(x); tail=:left)[1], 4.45, atol=1e-4)
	@test isapprox(confint(SignedRankTest(x); tail=:right)[2], 14.45, atol=1e-4)
end
end


"""
CLUSTERCOEFFS
-------------
    compute undirected clustering coefficients for a graph. clustercoeffs(A) computes a
    normalized, weighted clustering coefficients from a graph represented by a symmetric
    adjacency matrix A. clustercoeffs(A,weighted,normalized), with weighted and normalized
    boolean values indicating whether the computation has to be weighted and/or normalized.

Functions
---------
- cc = clustercoeffs(A::MatrixNetwork,weighted::Bool,normalized::Bool)
- cc = clustercoeffs{T}(A::SparseMatrixCSC{T,Int64},weighted::Bool,normalized::Bool)\n
If weighted and normalized are not specified, they are understood as true

Example
-------
~~~
A = load_matrix_network("clique-10")
cc = clustercoeffs(MatrixNetwork(A))
~~~
"""

function clustercoeffs(A::MatrixNetwork)
    return clustercoeffs(A, true, true);
end

function clustercoeffs(A::MatrixNetwork,weighted::Bool,normalized::Bool)
    donorm = true
    usew = true
    if !normalized
        donorm = false
    end
    if !weighted
        usew = false
    end

    if is_undirected(A) == false
        error("Only undirected (symmetric) inputs are allowed")
    end

    (rp,ci,ai) = (A.rp,A.ci,A.vals)
    if length(find(ai.<0)) != 0
        error("only positive edge weights allowed")
    end
    return clustercoeffs_phase2(donorm,rp,ci,ai,usew)
end

function clustercoeffs_phase2{T}(donorm::Bool,rp::Vector{Int64},ci::Vector{Int64},
                                          ai::Vector{T},usew::Bool)
    n = length(rp) - 1
    cc = zeros(Float64,n)
    # ind = falses(n,1)
    ind = zeros(Bool,n)
    cache=zeros(Float64,n)
    ew = 1
    ew2 = 1
    for v = 1:n
        for rpi = rp[v]:rp[v+1]-1
            w = ci[rpi]
            if usew
                ew = ai[rpi]
            end
            if v != w
                ind[w] = 1
                cache[w] = ew^(1/3)
            end
        end
        curcc = 0
        d = rp[v+1]-rp[v]
        # run two steps of bfs to try and find triangles.
        for rpi = rp[v]:rp[v+1]-1
            w = ci[rpi]
            if v == w
                d = d-1
                continue
            end #discount self-loop
            for rpi2 = rp[w]:rp[w+1]-1
                x = ci[rpi2]
                if x == w
                    continue
                end
                if ind[x]
                    if usew
                        ew = ai[rpi]
                        ew2 = ai[rpi2]
                    end
                    curcc = curcc+ew^(1/3)*ew2^(1/3)*cache[x]
                end
            end
        end
        if donorm && d>1
            cc[v] = curcc/(d*(d-1))
        elseif d>1
            cc[v] = curcc
        end

        for rpi = rp[v]:rp[v+1]-1
            w = ci[rpi]
            ind[w] = 0
        end # reset indicator
    end
    return cc
end

############################
### Additional functions ###
############################
# sparse matrices:
function clustercoeffs{T}(A::SparseMatrixCSC{T,Int64},weighted::Bool,normalized::Bool)
    donorm = true
    usew = true
    if !normalized
        donorm = false
    end
    if !weighted
        usew = false
    end

    At = A'
    if !(At == A)
        error("Only undirected (symmetric) inputs are allowed")
    end

    (rp,ci,ai) = (At.colptr,At.rowval,At.nzval);
    if length(find(ai.<0)) != 0
        error("only positive edge weights allowed")
    end
    return clustercoeffs_phase2(donorm,rp,ci,ai,usew)
end

function clustercoeffs{T}(A::SparseMatrixCSC{T,Int64})
    return clustercoeffs(A, true, true);
end

## Triplet Format:
function clustercoeffs(ei::Vector{Int64},ej::Vector{Int64})
    return clustercoeffs(MatrixNetwork(ei,ej))
end

function clustercoeffs(ei::Vector{Int64},ej::Vector{Int64},weighted::Bool,normalized::Bool)
    return clustercoeffs(MatrixNetwork(ei,ej),weighted,normalized)
end

## CSR sparse matrices - basically just like type MatrixNetwork
function clustercoeffs{T}(rp::Vector{Int64},ci::Vector{Int64},vals::Vector{T},n::Int64)
    return clustercoeffs(MatrixNetwork(n,rp,ci,vals))
end

function clustercoeffs{T}(rp::Vector{Int64},ci::Vector{Int64},vals::Vector{T},n::Int64,weighted::Bool,normalized::Bool)
    return clustercoeffs(MatrixNetwork(n,rp,ci,vals),weighted,normalized)
end


# This file is part of the TaylorIntegration.jl package; MIT licensed

using TaylorIntegration
using LinearAlgebra: norm, tr, dot, istriu, diag, I
using Test

@testset "Testing `lyapunov.jl`" begin

    local _order = 28
    local _abstol = 1.0E-20

    #Lorenz system parameters
    local σ = 16.0
    local β = 4.0
    local ρ = 45.92

    #Lorenz system ODE:
    function lorenz!(dx, x, p, t)
        dx[1] = σ*(x[2]-x[1])
        dx[2] = x[1]*(ρ-x[3])-x[2]
        dx[3] = x[1]*x[2]-β*x[3]
        nothing
    end

    #Lorenz system Jacobian (in-place):
    function lorenz_jac!(jac, x, p, t)
        jac[1,1] = -σ+zero(x[1]); jac[1,2] = σ+zero(x[1]); jac[1,3] = zero(x[1])
        jac[2,1] = ρ-x[3]; jac[2,2] = -1.0+zero(x[1]); jac[2,3] = -x[1]
        jac[3,1] = x[2]; jac[3,2] = x[1]; jac[3,3] = -β+zero(x[1])
        nothing
    end

    @testset "Test `stabilitymatrix!`" begin
        t0 = rand() #the initial time
        xi = set_variables("δ", order=1, numvars=3)
        t_ = Taylor1([t0,1],_order)
        δx = Array{TaylorN{Taylor1{Float64}}}(undef, 3)
        dδx = similar(δx)
        jac_auto = Array{Taylor1{Float64}}(undef, 3, 3)
        jac_user = Array{Taylor1{Float64}}(undef, 3, 3)
        _δv = Array{TaylorN{Taylor1{Float64}}}(undef, 3)
        for ind in 1:3
            _δv[ind] = one(t_)*TaylorN(Taylor1{Float64}, ind, order=1)
        end
        # test computation of jac via: autodiff and user-provided Jacobian function
        for i in 1:10
            x0 = 10rand(3) #the initial condition
            x0T = Taylor1.(x0,_order)
            TaylorIntegration.stabilitymatrix!(lorenz!, t_, x0T, δx, dδx, jac_auto, _δv, nothing)
            @test tr(constant_term.(jac_auto)) == -(1+σ+β)
            TaylorIntegration.stabilitymatrix!(lorenz!, t_, x0T, δx, dδx, jac_user, _δv, nothing, lorenz_jac!)
            @test tr(constant_term.(jac_user)) == -(1+σ+β)
            @test jac_user == jac_auto
        end
    end

    @testset "Test `classicalGS!`" begin
        dof = 3
        jt = rand(dof, dof)
        QH = Array{eltype(jt)}(undef, dof, dof)
        RH = Array{eltype(jt)}(undef, dof, dof)
        aⱼ = Array{eltype(jt)}(undef, dof)
        qᵢ = similar(aⱼ)
        vⱼ = similar(aⱼ)
        TaylorIntegration.classicalGS!( jt, QH, RH, aⱼ, qᵢ, vⱼ )
        @test norm(jt-QH*RH, Inf) < 1e-14
        for i in 1:dof
            for j in i:dof
                if j == i
                    @test isapprox( dot(QH[:,i],QH[:,j]), one(eltype(jt)) )
                else
                    @test abs( dot(QH[:,i],QH[:,j]) ) < 1E-10
                end
            end
        end
        @test istriu(RH) #is RH an upper triangular matrix?
        @test prod(diag(RH) .> 0.0) #are the diagonal elements of RH positive?
    end

    @testset "Test Lyapunov spectrum integrator (t0, tmax): Lorenz system" begin
        q0 = [19.0, 20.0, 50.0] #the initial condition
        t0 = 0.0 #the initial time
        tmax = t0+20.0 #final time of integration
        dof = length(q0) # degrees of freedom
        Q0 = Matrix{Float64}(I, dof, dof) # dof x dof identity matrix
        x0 = vcat(q0, reshape(Q0, dof*dof)) # initial conditions: eqs of motion and variationals

        #Taylor1 variables for evaluation of eqs of motion
        t = Taylor1(_order)
        x = Taylor1.(x0, _order)
        dx = similar(x)

        #Calculate trace of Lorenz system Jacobian via TaylorSeries.jacobian:
        xi = set_variables("δ", order=1, numvars=length(q0))
        q0TN = [ q0[1]+xi[1], q0[2]+xi[2], q0[3]+xi[3] ]
        dq0TN = similar(q0TN)
        lorenz!(dq0TN, q0TN, nothing, t0)
        jac_autodiff = TaylorSeries.jacobian(dq0TN) # Jacobian, computed via automatic differentiation
        lorenztr = tr(jac_autodiff) #trace of Lorenz system Jacobian matrix

        @test lorenztr == -(1+σ+β)

        # Compute Jacobian using previously defined `lorenz_jac!` function
        jac = Matrix{Taylor1{eltype(q0)}}(undef, dof, dof)
        lorenz_jac!(jac, x, nothing, t)
        @test tr(constant_term.(jac)) == -(1+σ+β)

        # Jacobian values should be equal
        @test constant_term.(jac) == jac_autodiff

        # Number of TaylorN variables should be equal to length of vector of initial conditions
        xi = set_variables("δ", order=1, numvars=length(q0)-1)
        @test_throws AssertionError lyap_taylorinteg(lorenz!, q0, t0, tmax, _order, _abstol; maxsteps=2)
        xi = set_variables("δ", order=1, numvars=length(q0))

        #Test lyap_taylorinteg with autodiff-computed Jacobian, maxsteps=5
        tv, xv, λv = lyap_taylorinteg(lorenz!, q0, t0, tmax, _order, _abstol, nothing; maxsteps=5)
        @test size(tv) == (6,)
        @test size(xv) == (6,3)
        @test size(λv) == (6,3)

        #Test lyap_taylorinteg with user-defined Jacobian, maxsteps=5
        tv2, xv2, λv2 = lyap_taylorinteg(lorenz!, q0, t0, tmax, _order, _abstol, nothing, lorenz_jac!; maxsteps=5)
        @test size(tv2) == (6,)
        @test size(xv2) == (6,3)
        @test size(λv2) == (6,3)
        @test tv == tv2
        @test xv == xv2
        @test λv == λv2

        #Test lyap_taylorinteg with autodiff-computed Jacobian, maxsteps=2000
        tv, xv, λv = lyap_taylorinteg(lorenz!, q0, t0, tmax, _order, _abstol; maxsteps=2000)
        @test xv[1,:] == q0
        @test tv[1] == t0
        @test size(xv) == size(λv)
        @test isapprox(sum(λv[1,:]), lorenztr) == false
        @test isapprox(sum(λv[end,:]), lorenztr)
        mytol = 1e-4
        @test isapprox(λv[end,1], 1.47167, rtol=mytol, atol=mytol)
        @test isapprox(λv[end,2], -0.00830, rtol=mytol, atol=mytol)
        @test isapprox(λv[end,3], -22.46336, rtol=mytol, atol=mytol)
        # Check integration consistency (orbit should not depend on variational eqs)
        t_, x_ = taylorinteg(lorenz!, q0, t0, tmax, _order, _abstol; maxsteps=2000)
        @test t_ == tv
        @test x_ == xv
        # test backward integration
        tvb, xvb, λvb = lyap_taylorinteg(lorenz!, q0, t0, -tmax, _order, _abstol; maxsteps=2000)
        @test isapprox(sum(λvb[1,:]), lorenztr) == false
        @test isapprox(sum(λvb[end,:]), lorenztr)

        #Test lyap_taylorinteg with user-defined Jacobian, maxsteps=2000
        tv2, xv2, λv2 = lyap_taylorinteg(lorenz!, q0, t0, tmax, _order, _abstol, nothing, lorenz_jac!; maxsteps=2000)
        @test xv2[1,:] == q0
        @test tv2[1] == t0
        @test size(xv2) == size(λv2)
        @test tv == tv2
        @test xv == xv2
        @test λv == λv2
        @test isapprox(sum(λv2[1,:]), lorenztr) == false
        @test isapprox(sum(λv2[end,:]), lorenztr)
        mytol = 1e-4
        @test isapprox(λv2[end,1], 1.47167, rtol=mytol, atol=mytol)
        @test isapprox(λv2[end,2], -0.00830, rtol=mytol, atol=mytol)
        @test isapprox(λv2[end,3], -22.46336, rtol=mytol, atol=mytol)
        # Check integration consistency (orbit should not depend on variational eqs)
        @test t_ == tv2
        @test x_ == xv2
        # test backward integration
        tv2b, xv2b, λv2b = lyap_taylorinteg(lorenz!, q0, t0, -tmax, _order, _abstol, nothing, lorenz_jac!; maxsteps=2000)
        @test isapprox(sum(λv2b[1,:]), lorenztr) == false
        @test isapprox(sum(λv2b[end,:]), lorenztr)
    end

    @testset "Test Lyapunov spectrum integrator (trange): Lorenz system" begin
        q0 = [19.0, 20.0, 50.0] #the initial condition
        t0 = 0.0 #the initial time
        tmax = t0+20.0 #final time of integration

        #Calculate trace of Lorenz system Jacobian via TaylorSeries.jacobian:
        xi = set_variables("δ", order=1, numvars=length(q0))
        q0TN = q0+xi
        dq0TN = similar(q0TN)
        lorenz!(dq0TN, q0TN, nothing, t0)
        lorenztr = tr(TaylorSeries.jacobian(dq0TN)) #trace of Lorenz system Jacobian matrix
        @test lorenztr == -(1+σ+β)

        trange = t0:1.0:tmax
        xw, λw = lyap_taylorinteg(lorenz!, q0, trange, _order, _abstol; maxsteps=5)
        @test xw[1,:] == q0
        @test size(xw) == (length(trange), 3)
        @test λw[1,:] == zeros(3)
        @test size(λw) == size(xw)
        @test all(isnan.(xw[2:end,:]))
        @test all(isnan.(λw[2:end,:]))
        xw2, λw2 = lyap_taylorinteg(lorenz!, q0, vec(trange), _order, _abstol; maxsteps=5)
        @test xw[1, :] == xw2[1, :]
        @test all(isnan.(xw2[2:end,:]))
        @test λw2[1, :] == zeros(3)
        @test all(isnan.(λw2[2:end,:]))
        @test size(xw2) == (length(trange), 3)
        @test size(λw2) == size(xw2)
        xw_, λw_ = lyap_taylorinteg(lorenz!, q0, trange, _order, _abstol, nothing, lorenz_jac!; maxsteps=5)
        @test xw_[1,:] == q0
        @test all(isnan.(xw_[2:end,:]))
        @test size(xw_) == (length(trange), 3)
        @test size(λw_) == size(xw_)
        @test λw_[1, :] == zeros(3)
        @test all(isnan.(λw_[2:end,:]))
        xw2_, λw2_ = lyap_taylorinteg(lorenz!, q0, vec(trange), _order, _abstol, nothing, lorenz_jac!; maxsteps=5)
        @test xw_[1, :] == xw2_[1, :]
        @test all(isnan.(xw2_[2:end,:]))
        @test λw2_[1, :] == zeros(3)
        @test all(isnan.(λw2_[2:end,:]))
        @test size(xw2_) == (length(trange), 3)
        @test size(λw2_) == size(xw2_)

        xw, λw = lyap_taylorinteg(lorenz!, q0, trange, _order, _abstol; maxsteps=2000)
        @test xw[1,:] == q0
        @test size(xw) == (length(trange), length(q0))
        @test size(λw) == (length(trange), length(q0))
        @test isapprox(sum(λw[1,:]), lorenztr) == false
        @test isapprox(sum(λw[end,:]), lorenztr)
        tz, xz, λz = lyap_taylorinteg(lorenz!, q0, trange[1], trange[end], _order, _abstol; maxsteps=2000)
        @test λw[end,:] == λz[end,:]
        @test xw[end,:] == xz[end,:]
        @test tz[end] == trange[end]
        mytol = 1e-4
        @test isapprox(λw[end,1], 1.47167, rtol=mytol, atol=mytol)
        @test isapprox(λw[end,2], -0.00831, rtol=mytol, atol=mytol)
        @test isapprox(λw[end,3], -22.46336, rtol=mytol, atol=mytol)
        xw_, λw_ = lyap_taylorinteg(lorenz!, q0, trange, _order, _abstol, nothing, lorenz_jac!; maxsteps=2000)
        @test xw == xw_
        @test λw == λw_
        tz_, xz_, λz_ = lyap_taylorinteg(lorenz!, q0, trange[1], trange[end], _order, _abstol, nothing, lorenz_jac!; maxsteps=2000)
        @test λw_[end,:] == λz_[end,:]
        @test xw_[end,:] == xz_[end,:]
        @test tz_[end] == trange[end]

        xw2, λw2 = lyap_taylorinteg(lorenz!, q0, vec(trange), _order, _abstol; maxsteps=2000)
        @test xw2 == xw
        @test λw2 == λw
        @test xw2[1,:] == q0
        @test size(xw) == (length(trange), length(q0))
        @test isapprox(sum(λw2[1,:]), lorenztr) == false
        @test isapprox(sum(λw2[end,:]), lorenztr)
        tz2, xz2, λz2 = lyap_taylorinteg(lorenz!, q0, trange[1], trange[end], _order, _abstol; maxsteps=2000)
        @test λw2[end,:] == λz2[end,:]
        @test xw2[end,:] == xz2[end,:]
        @test tz2[end] == trange[end]
        mytol = 1e-4
        @test isapprox(λw2[end,1], 1.47167, rtol=mytol, atol=mytol)
        @test isapprox(λw2[end,2], -0.00831, rtol=mytol, atol=mytol)
        @test isapprox(λw2[end,3], -22.46336, rtol=mytol, atol=mytol)
        xw2_, λw2_ = lyap_taylorinteg(lorenz!, q0, vec(trange), _order, _abstol, nothing, lorenz_jac!; maxsteps=2000)
        @test xw2 == xw2_
        @test λw2 == λw2_
        tz2_, xz2_, λz2_ = lyap_taylorinteg(lorenz!, q0, trange[1], trange[end], _order, _abstol, nothing, lorenz_jac!; maxsteps=2000)
        @test λw2_[end,:] == λz2_[end,:]
        @test xw2_[end,:] == xz2_[end,:]
        @test tz2_[end] == trange[end]

        # Check integration consistency (orbit should not depend on variational eqs)
        x_ = taylorinteg(lorenz!, q0, vec(trange), _order, _abstol; maxsteps=2000)
        @test x_ == xw2

    end

end


immutable dis_powerlaw <: DiscreteUnivariateDistribution
    α::Float64
    θ::Float64

    function dis_powerlaw(α::Real, θ::Real)
        @check_args(dis_powerlaw, α > zero(α) && θ > zero(θ))
        new(α, θ)
    end
    dis_powerlaw(α::Real) = dis_powerlaw(α, 1.0)
    dis_powerlaw() = new(1.0, 1.0)
end
@distr_support dis_powerlaw d.θ Inf
#### Parameters

shape(d::dis_powerlaw) = d.α
scale(d::dis_powerlaw) = d.θ
params(d::dis_powerlaw) = (d.α, d.θ)

#### Evaluation

function pdf(d::dis_powerlaw, x::Float64)
    (α, θ) = params(d)
    x >= θ ?  x ^ (-α) / zeta(α, θ) : 0.0
end

function pdf(d::dis_powerlaw, x::AbstractArray)
  (α, θ) = params(d)
  z = zeta(α, θ)
  pdfs = Array(Float64,0)
  for num in x
    push!(pdfs,(num >= θ ?  num ^ (-α) / z : 0.0))
  end
  return pdfs
end

function logpdf(d::dis_powerlaw, x::Float64)
    (α, θ) = params(d)
    x >= θ ? -log(zeta(α, θ)) - α * log(x) : -Inf
end
function logpdf(d::dis_powerlaw, x::AbstractArray)
  (α, θ) = params(d)
  log_zeta = -log(zeta(α, θ))
  lpdfs = Array(Float64,0)
  for num in x
    push!(lpdfs,(num >= θ ? log_zeta - α * log(num) : -Inf))
  end
  return lpdfs
end

function ccdf(d::dis_powerlaw, x::Float64)
    (α, θ) = params(d)
    x >= θ ? zeta(α, x) / zeta(α, θ) : 1.0
end

function ccdf(d::dis_powerlaw, x::AbstractArray)
    (α, θ) = params(d)
    z = zeta(α, θ)
    ccdfs = Array(Float64,0)
    for num in x
      push!(ccdfs,(num >= θ ? zeta(α, num) / z : 1.0))
    end
    return ccdfs
end

cdf(d::dis_powerlaw, x::Float64) = 1.0 - ccdf(d, x)
cdf(d::dis_powerlaw, x::AbstractArray) = 1.0 - ccdf(d, x)

logccdf(d::dis_powerlaw, x::Float64) = log(ccfd(d,x))
logccdf(d::dis_powerlaw, x::AbstractArray) = log(ccfd(d,x))

logcdf(d::dis_powerlaw, x::Float64) = log(cdf(d, x))
logcdf(d::dis_powerlaw, x::AbstractArray) = log(cdf(d, x))

cquantile(d::dis_powerlaw, p::Float64) = (d.θ -0.5) *((p) ^ (-1.0 / (d.α - 1.0))) + 0.5
quantile(d::dis_powerlaw, p::Float64) = cquantile(d, 1.0 - p)

#### Sampling

rand(d::dis_powerlaw) = floor(quantile(d,rand()))

## Fitting
#The discrete MLE of α, is not available, instead we use the approximation
function fit_mle{T <: Real}(::Type{dis_powerlaw}, x::AbstractArray{T})
    θ = minimum(x)
    n = length(x)
    lθ = log(θ-0.5)
    temp1 = zero(T)
    for i=1:n
        temp1 += log(x[i]) - lθ
    end
    α = 1.0 +n*(temp1)^(-1.0)

    return dis_powerlaw(α, θ)
end


abstract type GradientAlgorithm <: MLAlgorithm end

# function gradient!(alg::GradientAlgorithm, model, data)
#     A_mul_B!(alg.nvec, data.x, model.β)          # nvec ← x * β
#     deriv!(alg.nvec, model.loss, data.y, alg.nvec) # nvec ← deriv(L, y, x * β)
#     multiply_by_weights!(alg.nvec, data.w)   # nvec .*= w ./ n
#     At_mul_B!(alg.pvec, o.x, alg.nvec)      # pvec ← x'nvec
# end

# multiply_by_weights!(nvec, w::Void) = scale!(nvec, 1 / length(nvec))
# function multiply_by_weights!(nvec, w)
#     wt = inv(length(nvec))
#     for i in eachindex(nvec)
#         @inbounds nvec[i] *= w[i] * wt
#     end
# end

mutable struct AdaptiveProxGrad <: GradientAlgorithm
    nvec::Vector{Float64}
    pvec::Vector{Float64}
    step::Vector{Float64}
    divisor::Float64
    function AdaptiveProxGrad(initstep = 1.0, divisor = 1.5)
        divisor > 1  || error("divisor must be > 1")
        initstep > 0 || error("step size must be > 0")
        new(zeros(0), zeros(0), fill(initstep, 1), divisor)
    end
end

function setup!(alg::AdaptiveProxGrad, mod::AbstractMLModel, data::MLData)
    _modelsetup!(mod, data)
    alg.nvec = zeros(nobs(data))
    alg.pvec = zeros(npredictors(data))
    alg.step = fill(alg.step[1], npredictors(data))
end


### A Pluto.jl notebook ###
# v0.14.1

using Markdown
using InteractiveUtils

# ╔═╡ 2264b716-a37a-11eb-1393-7f19142f6d9c
begin
	using LinearAlgebra
	using BandedMatrices

	using Plots; Plots.gr()
	using LaTeXStrings
end

# ╔═╡ 44c0e18e-a42b-11eb-1810-3fe2e9e81d53
begin
	# Taken from https://github.com/JuliaMatrices/BandedMatrices.jl/issues/180
	function kron(a::BandedMatrix{T}, b::BandedMatrix{V}) where {T,V}
		R = BandedMatrix{Base.promote_op(*,T,V)}(undef, (size(a,1) * size(b,1), size(a,2) * size(b,2)), (bandwidth(a,1) * size(b,1) + bandwidth(b,1), bandwidth(a,2) * size(b,2) + bandwidth(b,2)))
		m = 0
		@inbounds for j = 1:size(a,2), l = 1:size(b,2), i = 1:size(a,1)
			aij = a[i,j]
			for k = 1:size(b,1)
				R[m += 1] = aij * b[k,l]
			end
		end
		R
	end

	kron(A::BandedMatrix, B::BandedMatrix, C::AbstractMatrix...) = kron_(kron_(A,B), C...)
	kron(A) = A
end

# ╔═╡ 3cec498e-a37b-11eb-0b97-e33d6bec2dea
begin
	# Simulation parameters
	const N_max = 5 	# Fock space truncation

	# Single site operators
	const bₙ = BandedMatrix(1 => (1:N_max) .^ (1/2))
	const Iₙ = BandedMatrix(0 => ones(Float64, N_max+1))

	@assert bₙ' * bₙ ≈ Iₙ .* (0:N_max)

	"""
		H(t, μ, b̄, L)

	Bose-Hubbard `L`-chain mean-field Hamiltonian
	"""
	function H(t, μ, b̄, L)
		b = [kron((Iₙ for _ in 1:(i-1))..., bₙ, (Iₙ for _ in (i+1):L)...) for i in 1:L]
		I = BandedMatrix(0 => ones(Float64, (N_max+1)^L))

		# on-site
		h = sum(-μ * b[i]' * b[i] + 0.5 * b[i]' * b[i] .* (b[i]' * b[i] .- I) for i in 1:L)

		# mean-field hopping
		h += -t * (b[1]' * b̄ + b̄' * b[1])

		if L > 1
			# mean-field hopping
			h += -t * (b[L]' * b̄ + b̄' * b[L])

			# chain hopping
			h += -t * sum(b[i+1]' * b[i] + b[i]' * b[i+1] for i in 1:(L-1))
		end

		return h
	end

	"""
		mean_field(t, μ, b̄, L)

	Bose-Hubbard mean-field at T=0 (ϕ being the ground-state)
		<ϕ|b|ϕ>
	"""
	function mean_field(t, μ, b̄, L)
		ϕ = begin
			_, vecs = eigen(Matrix(H(t, μ, b̄, L)))
			vecs[:,1] # eigen-vector of the lowest eigen-value
		end

		# If H is huge, prefer `using KrylovKit`
		# ϕ = eigsolve(H(t, μ, b̄, L), 1, :SR)[2][1]

		return ϕ' * kron(bₙ, (Iₙ for _ in 2:L)...) * ϕ
	end

	"""
		fixedpoint(f, x0; xtol)

	Finds the fixed point
		x* = f(x*)
	"""
	function fixedpoint(f, x; xtol=1e-4, β=0.1)
		x0, x = Inf, x
		while norm(x - x0, Inf) > xtol
			x0, x = x, β * x + (1 - β) * f(x)
		end
		return x
	end
end;

# ╔═╡ 80506ae8-a40e-11eb-117c-6b22718cc945
begin
	# Physical parameters (units of U)
	μs = 0:0.03:4
	ts = 0:0.006:0.2

	L = 1 	# chain size

	# Parameter scan
	data = [fixedpoint(b̄ -> mean_field(t, μ, b̄, L), 1.0) for μ in μs, t in ts]
end;

# ╔═╡ 3958ae48-a37d-11eb-26f7-b7511953016e
heatmap(ts, μs, data;
	xlabel=L"t/U",
	ylabel=L"\mu/U",
	title=L"\textrm{mean-field} <b>",
	xlims=extrema(ts))

# ╔═╡ Cell order:
# ╠═2264b716-a37a-11eb-1393-7f19142f6d9c
# ╟─44c0e18e-a42b-11eb-1810-3fe2e9e81d53
# ╠═3cec498e-a37b-11eb-0b97-e33d6bec2dea
# ╠═80506ae8-a40e-11eb-117c-6b22718cc945
# ╠═3958ae48-a37d-11eb-26f7-b7511953016e


using ApproxFun, SingularIntegralEquations, Test
import ApproxFunBase: choosedomainspace, promotedomainspace, ConstantSpace, interlace,
                        testraggedbelowoperator, testblockbandedoperator, blocklengths

@testset "Ideal Fluid Flow" begin
    k = 50
    Γ = Segment(0.,1+0.5im)
    z = Fun(Γ)
    α = exp(-π*k/50im)

    @test choosedomainspace(Hilbert(),z) == JacobiWeight(-0.5,-0.5,ChebyshevDirichlet{1,1}(Γ))

    Ai=[1 Hilbert()]

    @test choosedomainspace(Ai.ops[1],space(z)) isa ConstantSpace

    S = choosedomainspace(Ai,space(z))
    @test component(S,1) isa ConstantSpace
    @test component(S,2) isa JacobiWeight{ChebyshevDirichlet{1,1,Segment{Complex{Float64}},Float64},
                                                            Segment{Complex{Float64}},Float64,Float64}

    AiS = promotedomainspace(Ai,S)
    @test domainspace(AiS) == S

    S = JacobiWeight(0.5,0.5,Ultraspherical(1,Γ))
    @time c,ui = [1 Hilbert(S)]\[imag(α*z)]

    u =(x,y) -> α*(x+im*y)+2cauchy(ui,x+im*y)

    @test u(0.1,0.2) ≈ 0.039532462109794025-0.3188804984805561im # empirical

    k = 227;
    Γ = 0.5+exp(im*Segment(0.1,-42))
    z = Fun(Γ)
    α = exp(-k/50im)

    Ai = [1 PseudoHilbert()]

    @test isa(choosedomainspace(Ai.ops[1],space(z)),ConstantSpace)
    @test isa(component(choosedomainspace(Ai,space(z)),1),ConstantSpace)
    @test isa(component(choosedomainspace(Ai,space(z)),2),
                JacobiWeight{ChebyshevDirichlet{1,1,Arc{Float64,Float64,Complex{Float64}},Float64},
                                                                        Arc{Float64,Float64,Complex{Float64}},Float64})

    S = choosedomainspace(Ai,space(z))
    AiS = promotedomainspace(Ai,S)
    @test domainspace(AiS) == S

    k = 227;
    Γ = 0.5+exp(im*Segment(0.1,-42))
    z = Fun(Γ)
    α = exp(-k/50im)
    S = JacobiWeight(0.5,0.5,Γ)
    @time c,ui = [1 PseudoHilbert(S)]\[imag(α*z)]

    u = (x,y) -> α*(x+im*y)+2pseudocauchy(ui,x+im*y)

    @test u(0.1,0.2) ≈ 0.6063720775017964 - 0.6382733554119975im # empirical

    Γ = Circle()
    z = Fun(Fourier(Γ))

    Ai = [0 DefiniteLineIntegral();
          1 real(Hilbert())]

    S = choosedomainspace(Ai,space([Fun(0.);z]))

    @test isa(component(S,1),ConstantSpace)
    @test component(S,2) == Fourier(Γ)

    @test domainspace(promotedomainspace(Ai,S)) == S

    A=promotedomainspace(Ai,S)

    testraggedbelowoperator(A)

    k=239;
    α=exp(-k/45im)

    c,ui=[0 DefiniteLineIntegral();
          1 real(Hilbert())]\[Fun(0.);imag(α*z)]

    u =(x,y)->α*(x+im*y)+2cauchy(ui,x+im*y)
    @test u(2.,1.1) ≈ 2.426592437403252-0.8340542386599383im

    ## Curve
    Γ=Curve(Fun(x->exp(0.8im)*(x+x^2-1+im*(x-4x^3+x^4)/6)))
        z=Fun(Γ)
        α=im
        S=JacobiWeight(0.5,0.5,Γ)
        @time c,ui=[1 real(Hilbert(S))]\[imag(α*z)]

    u=(x,y)->α*(x+im*y)+2cauchy(ui,x+im*y)

    @test u(0.1,0.2) ≈ (-1.1657816742288978-0.21306668168680534im)

    ## 2 intervals
    Γ=Segment(-1.,-0.5) ∪ Segment(-0.3,1.)
    z=Fun(Γ)

    S=PiecewiseSpace(map(d->JacobiWeight(0.5,0.5,Ultraspherical(1,d)),components(Γ)))

    k=114;
        α=exp(k/50*im)

    Ai = [ones(component(Γ,1))+zeros(component(Γ,2)) zeros(component(Γ,1))+ones(component(Γ,2)) Hilbert(S)]
    testblockbandedoperator(Ai)

    QR = qrfact(Ai)
          resizedata!(QR, :, Block(10)) # check for bug in resizedata!

    @time a,b,ui = [ones(component(Γ,1))+zeros(component(Γ,2)) zeros(component(Γ,1))+ones(component(Γ,2)) Hilbert(S)] \ [imag(α*z)]

    u=(x,y)->α*(x+im*y)+2cauchy(ui,x+im*y)

    @test u(0.1,0.2) ≈ (-0.5762722129639637 + 0.04348367760228282im) # empirical
end


"""
Compute pair warping between two functions using Bayesian method

    pair_warping_bayes(f1, f2; iter=15000, times=5, powera=1)
    :param f1, f2: vectors describing functions
    :param iter: number of iterations
    :param times: MCMC parameter
    :param powera: MCMC parameter

    Returns Dict containing
    :return f1:
    :return f2_q: srsf registration
    :return gam_q: warping function
    :return f2a: registered f2
    :return gam: warping function
    :return dist_collect: distance
    :return best_match: best match
"""
function pair_warping_bayes(f1, f2; iter=15000, times=5, powera=1)

    tau = ceil(times*.4);

    # Default setting shall work for many situations.
    # If convergence issues arise then adjust proposal variance tau.
    if (times == 2)
        warn("Small times may lead to convergence issues")
    end

    burnin = NaN;
    kappa = 1000;
    thin = 1;
    cut = 5*times;
    alpha = 1;
    beta = 0.001;
    scale_data = true;

    # Convert to SRSF
    q1 = f_to_srsf(f1);
    q2 = f_to_srsf(f2);

    times = 5;
    L = round(Integer,length(q1)/times);
    row = times*collect(0:L-1)+1;
    p = length(q1);

    if (mod(p,times) != 0)
        error(@sprintf("Number of points on q function = %d is not a multiple of times = %d", p, times))
    end

    if (scale_data)
        rescale = sqrt(p/sum(q1.^2));
        q1 = rescale.*q1;
        rescale = sqrt(p/sum(q2.^2));
        q2 = rescale.*q2;
    end

    MatchIn2, NDist, q2LL = dp_bayes(q1[row], q1, q2, times, cut);
    match = [MatchIn2; p+1];
    match_collect = Array(Float64, round(Integer, iter/thin), round(Integer,L+1));
    best_match = copy(match);
    dist = NaN;
    dist_collect = zeros(iter+1);
    f_i = interpolate(([row;p+1],), float(match), Gridded(Linear()))
    idy = round(Integer, f_i[1:p]);
    idy[idy .> p] = p;
    scale1 = sqrt(diff(match)*(1/times));
    scalevec = kron(scale1, ones(times));
    dist = Enorm(q1-scalevec.*q2[idy])^2/p;
    dist_collect[1] = dist;
    dist_min = dist;

    res = simuiter(iter, p, q1, q2, L, tau, times, kappa, alpha, beta,
                   powera, dist, dist_min, best_match, match, thin, cut);

    best_match = res["best_match"];
    match_collect = res["match_collect"];
    dist_min = res["dist_min"];
    log_posterior = res["log_posterior"];
    dist_collect = res["dist_collect"];
    kappafamily = res["kappafamily"];
    f_i = interpolate(([row; p+1],), float(best_match), Gridded(Linear()))
    bestidy = f_i[1:p];
    bestidy[bestidy .> p] = p;
    push!(bestidy,p+1);
    burnin = round(Integer, 0.5*iter/thin);
    LowerP = Array(Float64, L+1);
    UpperP = Array(Float64, L+1);
    MeanP = Array(Float64, L+1);
    idx_sub = round(Integer, iter/thin);
    for i = 1:L+1
        LowerP[i] = quantile(match_collect[burnin:idx_sub,i], 0.025);
        UpperP[i] = quantile(match_collect[burnin:idx_sub,i], 0.975);
        MeanP[i] = mean(match_collect[burnin:idx_sub,i]);
    end

    f_i = interpolate(([row; p+1],), float(MeanP), Gridded(Linear()))
    Meanidy = f_i[1:p];
    Meanidy[Meanidy .> p] = p;
    push!(Meanidy,p+1);

    f_i = interpolate((collect(1:p),), f2, Gridded(Linear()))
    reg_q = f_i[linspace(1,p,times*(p+1)-1)];
    reg_q = reg_q[round(Integer, (bestidy-1)*times+1)];
    reg_a = f_i[linspace(1,p,times*(p+1)-1)];
    reg_a = reg_a[floor(Integer, (Meanidy-1)*times+1)];

    out = Dict("f1" => f1, "f2_q" => reg_q, "gam_q" => (bestidy-1)/p,
               "f2a" => reg_a, "gam_a" => (Meanidy-1)/p,
               "dist_collect" => dist_collect, "best_match" => best_match);
    return out
end


@testset "Simplex" begin
    s = Simplex(1, 2, 3)
    @test s[:index] == 0
    @test s[:values] == [1,2,3]
    s[:index] = 1
    @test s[:index] == 1

    s = Simplex([1,2,3])
    @test dim(s) == 2
    @test s[:values] == [1,2,3]
    @testset "Simplex faces" for (o,t) in zip(faces(s), [Simplex(2, 3), Simplex(1, 3), Simplex(1, 2)])
        @test o[:values] == t[:values]
    end

    ss = Set{Simplex}()
    push!(ss, s)
    push!(ss, s)
    @test length(ss) == 1

    function Base.isless(a::Vector{Int},b::Vector{Int})
        length(a) < length(b) && return true
        length(a) > length(b) && return false
        return any([aa < bb for (aa,bb) in zip(a,b)])
    end
    @test [1] < [1,3]
    @test [1,2,1] > [1,3]
    @test [1,2] < [1,3]
    s = Simplex([1,2], [1,3])
    @test dim(s) == 1

    X = hcat(zeros(3), Matrix(I,3,3))
    S = Simplex(1,2,3,4)
    @test volume(S,X) == 1/6
end

@testset "Cube" begin
    c = Cube{Vector{Int}}([0,0,0],Int[]) # 0-cube
    @test dim(c) == 0
    c = Cube{Vector{Int}}([0,0,0],Int[0,0,0]) # 0-cube
    @test dim(c) == 0
    c = Cube{Vector{Int}}([0,0,0],[1]) # 1-cube
    @test dim(c) == 1
    c = Cube{Vector{Int}}([0,0,0],[1,0,1]) # 2-cube
    @test dim(c) == 2
    c = Cube{Vector{Int}}([0,0,0],[1,1,1]) # 3-cube
    @test dim(c) == 3
    @test c[:values] == ([0,0,0],[1,1,1])

    c = Cube{Vector{Float64}}([0.,0.,0.],[.5,.0,.5]) # 2-cube
    @test volume(c) == 0.5^2
    c = Cube{Vector{Float64}}([0.,0.,0.],[.5,.5,.5]) # 2-cube
    @test volume(c) == 0.5^3
end


function simplextest{Lib<:PolyhedraLibrary}(lib::Lib)
  A = [1 1; -1 0; 0 -1]
  b = [1, 0, 0]
  linset = IntSet([1])
  V = [0 1; 1 0]

  ine = SimpleHRepresentation(A, b, linset)
  poly1 = polyhedron(ine, lib)
  @test !isempty(poly1)
  inequality_fulltest(poly1, A, b, linset)
  generator_fulltest(poly1, V)

  ext = SimpleVRepresentation(V)
  poly2 = polyhedron(ext, lib)
  @test !isempty(poly2)
  inequality_fulltest(poly2, A, b, linset)
  generator_fulltest(poly2, V)

  # x_1 cannot be 2
  @test isempty(polyhedron(SimpleHRepresentation([A; 1 0], [b; 2], union(linset, IntSet([4]))), lib))

  # We now add the vertex (0, 0)
  V0 = [0 0]
  ext0 = SimpleVRepresentation(V0)

  push!(poly1, ext0)
  inequality_fulltest(poly1, A, b, IntSet([]))
  generator_fulltest(poly1, [V0; V])
  push!(poly2, ext0)
  inequality_fulltest(poly2, A, b, IntSet([]))
  generator_fulltest(poly2, [V0; V])

  # nonnegative orthant cut by x_1 + x_2 = 1
  Vray = [1 0; 0 1]
  extray = SimpleVRepresentation(Matrix{Int}(0,2), Vray)
  poly3 = polyhedron(extray, lib)
  Acut = [1 1]
  bcut = [1]
  linsetcut = IntSet([1])
  inecut = SimpleHRepresentation(Acut, bcut, linsetcut)
  push!(poly3, inecut)
  inequality_fulltest(poly3, A, b, linset)
  generator_fulltest(poly3, V)

  poly4 = project(poly1, [1; 0])
  inequality_fulltest(poly4, [-1; 1], [0, 1], IntSet())
  generator_fulltest(poly4, [0; 1], [])

  #\
  # \
  # |\
  # |_\
  #    \
  Alin = [1 1]
  blin = [1]
  linsetlin = IntSet(1)
  Vlin = [1 0]
  Rlin = [1 -1]
  inelin = SimpleHRepresentation([1 1; -1 -1], [1, -1], IntSet())
  plin = polyhedron(inelin, lib)
  inequality_fulltest(plin, Alin, blin, linsetlin)
  generator_fulltest(plin, Vlin, Rlin, IntSet(), IntSet(1))
  ineout = getinequalities(plin)
  @test ineout.linset == IntSet(1)
  Vlin = [1 0]
  Rlin = [1 -1]
  extlin = SimpleVRepresentation(Vlin, [1 -1; -1 1])
  plin = polyhedron(extlin, lib)
  inequality_fulltest(plin, Alin, blin, linsetlin)
  generator_fulltest(plin, Vlin, Rlin, IntSet(), IntSet(1))
  extout = SimpleVRepresentation(getgenerators(plin))
  @test extout.Rlinset == IntSet(1)
end


module ARMSampling

import Base: length, copy, insert!
export TrapezoidalProposal, sample, sample!, overrelaxation!

#From StatsBase.jl/src/sampling.jl
function _sample(w)
    t = rand() * sum(w)
    n = length(w)
    i = 1
    cw = w[1]
    while cw < t && i < n
        i += 1
        @inbounds cw += w[i]
    end
    return i
end

slope(x0, x1, y0, y1) = (y1-y0)/(x1-x0)
intercept(x0, x1, y0, y1) = (x1*y0 - x0*y1)/(x1-x0)
slope_intercept(x0, x1, y0, y1) = (y1-y0)/(x1-x0), (x1*y0 - x0*y1)/(x1-x0)
straight_line_fun(x, x0, x1, y0, y1) = slope(x0, x1, y0, y1)*x + intercept(x0, x1, y0, y1)

function integrate_straight_line_exp(x0, x1, y0, y1)
    a, b = slope_intercept(x0, x1, y0, y1)
    if abs(a) < eps(typeof(a))
        return exp(b)*(x1-x0)
    end

    return (exp(a*x1+b)-exp(a*x0+b))/a
end

function trapezoidalweight(x::AbstractVector{T}, y::AbstractVector{T}) where T
    n = length(x) - 2
    w = T[integrate_straight_line_exp(x[i], x[i+1], y[i], y[i+1]) for i in 1:(n+1)]
    return w
end

struct TrapezoidalProposal{T,V<:AbstractVector{T}}
    x::V
    y::V
    w::V

    function TrapezoidalProposal{T,V}(x::V, y::V, w::V) where {T,V<:AbstractVector{T}}
        @assert issorted(x, lt = (x,y) -> x <= y)
        @assert length(x) >= 3
        @assert length(y) == length(x)
        @assert length(w) == length(x) - 1
        new{T,V}(x, y, w)
    end
end

function TrapezoidalProposal(x::AbstractVector{T}, y::AbstractVector{T}) where T
    w = trapezoidalweight(x, y)
    return TrapezoidalProposal{T,typeof(x)}(copy(x), copy(y), copy(w))
end

function TrapezoidalProposal(x::AbstractVector{T}, logf::Function) where T
    y = T[logf(xx) for xx in x]
    return TrapezoidalProposal(x, y)
end

length(proposal::TrapezoidalProposal) = length(proposal.x) - 2

function copy(proposal::TrapezoidalProposal{T, V}) where {T,V<:AbstractVector{T}}
    newx = copy(proposal.x)
    newy = copy(proposal.y)
    neww = copy(proposal.w)
    TrapezoidalProposal{T,V}(newx, newy, neww)
end

function insert!(proposal::TrapezoidalProposal{T}, x0::T, y0::T) where T
    n = length(proposal)
    x = proposal.x
    y = proposal.y
    w = proposal.w

    index = searchsorted(x, x0)
    left = index.stop
    right = index.start

    if left == right
        return proposal
    elseif left == 0
        w0 = integrate_straight_line_exp(x0, x[1], y0, y[1])
        insert!(w, 1, w0)
    elseif right == n + 3
        w0 = integrate_straight_line_exp(x[n+2], x0, y[n+2], y0)
        insert!(w, n+2, w0)
    else
        w_left  = integrate_straight_line_exp(x[left], x0, y[left], y0)
        w_right = integrate_straight_line_exp(x0, x[right], y0, y[right])
        splice!(w, left, [w_left, w_right])
    end
    insert!(x, right, x0)
    insert!(y, right, y0)
    return proposal
end

insert!(proposal::TrapezoidalProposal{T}, x0::T, logf::Function) where T = insert!(proposal, x0, logf(x0))

function (proposal::TrapezoidalProposal)(x0)
    if x0 < proposal.x[1] || x0 > proposal.x[end]
        error("x out of range of proposal: x = $x0")
    end
    x = proposal.x
    y = proposal.y
    index = searchsorted(x, x0)
    left = index.stop
    right = index.start
    if left == right
        return y[left]
    end

    return straight_line_fun(x0, x[left], x[right], y[left], y[right])
end

function sample(proposal::TrapezoidalProposal)
    x = proposal.x
    y = proposal.y
    w = proposal.w

    n = length(proposal)
    k = _sample(w)

    a, b = slope_intercept(x[k], x[k+1], y[k], y[k+1])
    if abs(a) < eps(typeof(a))
        return rand()*w[k]*exp(-b) + x[k]
    end

    #out = log(rand()*w[k]*a*exp(-b)+exp(a*x[k]))/a
    out = (log(rand()*w[k]*a + exp(y[k]))-b)/a
    if !isfinite(out)
        println()
        error("Got infinity! k = $k, a = $a, b = $b, w = $(w[k])")
    end
    return out
end

function sample!(proposal::TrapezoidalProposal{T},
                 logf::Function,
                 x0::T;
                 max_n = 2*length(proposal)) where T
    logfx0 = logf(x0)
    logfx = zero(T)
    x = zero(T)
    while true
        x = sample(proposal)
        logfx = logf(x)
        if rand() > exp(logfx - proposal(x))
            if length(proposal) < max_n
                insert!(proposal, x, logfx)
            end
        else
            break
        end
    end

    if rand() > min(1, exp(logfx - logfx0)*min(exp(logfx0), exp(proposal(x0)))/min(exp(logfx),exp(proposal(x))) )
    #if rand() > min(1, exp(logfx - logfx0 + proposal(x0) - proposal(x)))

        if length(proposal) < max_n
            insert!(proposal, x0, logfx0)
        end

        return x0
    else
        return x
    end
end

function sample!(out::Vector{T},
                 nsample::Integer,
                 proposal::TrapezoidalProposal{T},
                 logf::Function;
                 kwargs...) where T
    nout = length(out)
    append!(out, zeros(T, nsample))
    for i in 1:nsample
        out[i+nout] = sample!(proposal, logf, out[i+nout-1]; kwargs...)
    end
    return out
end

function rank(out)
    x0 = out[1]
    sorted_out = sort(out)
    index = searchsorted(sorted_out, x0)

    if index.start == index.stop
        r = index.start - 1
    else
        r = (index.start - 1) + _sample(ones(length(index))) - 1
    end
    return r, sorted_out
end

function overrelaxation!(proposal::TrapezoidalProposal{T},
                         logf::Function,
                         x0::T,
                         K::Integer;
                         max_n = 2*length(proposal),
                         max_try = 10*length(proposal)) where T
    x = x0
    max_try_counter = 0
    while max_try_counter < max_try && length(proposal) < max_n
        x = sample!(proposal, logf, x; max_n = max_n)
        max_try_counter += 1
    end

    out = T[x0]
    sample!(out, 2*K, proposal, logf; max_n = max_n)
    out = out[1:2:end]
    r, sorted_out = rank(out)
    return sorted_out[K-r+1], sorted_out
end

end # module


export Mesh

struct Mesh

    xmin :: Float64
    xmax :: Float64
    nx   :: Int
    dx   :: Float64
    ymin :: Float64
    ymax :: Float64
    ny   :: Int
    dy   :: Float64

    function Mesh( xmin, xmax, nx, ymin, ymax, ny )

        dx = (xmax - xmin) / nx
        dy = (ymax - ymin) / ny

        new( xmin, xmax, nx, dx, ymin, ymax, ny, dy )

    end

end

export MeshFields

struct MeshFields

    mesh :: Mesh

    e :: Array{Float64,3}
    ρ :: Array{Float64,2}

    function MeshFields( mesh :: Mesh )

	     nx, ny = mesh.nx, mesh.ny

         e = zeros(Float64, (2, nx+1, ny+1))
         ρ = zeros(Float64, (nx+1, ny+1))

         new( mesh, e, ρ)

    end

end


# Two-state Bioreactor
id = :PerezGalvan2017d

pL = [0.8; 1.1]
pU = [0.85; 1.2]
PerezGalvan2017d_x0(p::Vector{T}) where T = T[p[1]; 0.8*one(T)]
function PerezGalvan2017d_f!(du::Vector{T}, u::Vector{T}, p::Vector{T}, t) where T
    α = 0.5; k = 10.53; D = 0.36; Sf = 5.7; Ks = 7.0; k0 = 0.022
    μ = p[2]*u[2]/(Ks + u[2])
    du[1] = (μ - α*D)*u[1]
    du[2] = D*(Sf - u[2]) - k*μ*u[1]
    return
end
tspan = (0.0, 10.0)

prob = ODERelaxProb(PerezGalvan2017d_f!, tspan, PerezGalvan2017d_x0, pL, pU)
url = "https://www.sciencedirect.com/science/article/abs/pii/S0098135417300923"
source = "PerezGalvan2017"
desc = "Two-state Bioreactor"


# Class weight : w_j = n_samples / (n_classes * n_samples_j)

"""
    metrics(cm)
    metrics(cm, met...)
* `:TPR` true positive rate
* `:TNR` true negative rate
* `:NPV` negative predictive value
* `:FNR` false negative rate
* `:FPR` false positive rate
* `:FDR` false discovery rate
* `:FOR` false omission rate
* `:ACC` accuracy
* `(:Fscore, β)` Fβ score
* `:PT` prevalence threshold
"""
function metrics(cm::ConfMat{2, T}) where T
    TP = cm.counts[1, 1] |> float
    TN = cm.counts[2, 2] |> float
    FP = cm.counts[2, 1] |> float
    FN = cm.counts[1, 2] |> float
    Dict(:TP => TP, :TN => TN, :FP => FP, :FN => FN)
end

function metrics(cm::ConfMat)
    TP, TN, FP, FN = [], [], [], []
    for l ∈ cm.labels
        push!(TP, cm.counts[l, l] .|> float)
        push!(TN, sum(cm.counts[Not(l), Not(l)]) .|> float)
        push!(FP, sum(cm.counts[Not(l), l]) .|> float)
        push!(FN, sum(cm.counts[l, Not(l)]) .|> float)
    end
    Dict(:TP => TP, :TN => TN, :FP => FP, :FN => FN)
end

function metrics(cm::ConfMat, met...)
    metrics(cm) |> m -> metrics!(m, met...)
end

tosymbol(x) = x
function tosymbol(x::Tuple{Symbol, Real})
    s, v = x
    st = string(s) * "_" * string(v)
    st = replace(st, "." => "_")
    Symbol(st)
end

function metrics!(mm::Dict, met...)
    for m ∈ met
        if !haskey(mm, m)
            # F-score special case handling : tuple is transformed to a symbol
            k = tosymbol(m)
            mm[k] = metric(mm, m)
        end
    end
    mm
end

@inline nozero(x) = iszero(float(x)) ? eps(float(x)) : x

#avoidzero2(x::Int) = (avoidzero2(x) |> float)

# function safediv(n, d)
#     d = float(d)
#     float(n) / (iszero(d) ? eps(d) : d)
# end

# macro safediv(d)
#     :(iszero.($d) ? eps.(float.($d)) : float.($d))
# end

# vérif les formules

metric(b, m::Symbol) = metric(b, Val{m}())

metric(b, m::Val{:TPR}) = @. b[:TP] / nozero(b[:TP] + b[:FN])

metric(b, m::Val{:TNR}) = @. b[:TN] / nozero(b[:TN] + b[:FP])

metric(b, m::Val{:NPV}) = @. b[:TN] / nozero(b[:TN] + b[:FN])

metric(b, m::Val{:FNR}) = @. b[:FN] / nozero(b[:FN] + b[:TP])

metric(b, m::Val{:FPR}) = @. b[:FP] / nozero(b[:FP] + b[:TN])

metric(b, m::Val{:FDR}) = @. b[:FP] / nozero(b[:FP] + b[:TP])

metric(b, m::Val{:FOR}) = @. b[:FN] / nozero(b[:FN] + b[:TN])

metric(b, m::Val{:ACC}) =
    @. (b[:TP] + b[:TN]) / nozero(b[:TP] + b[:TN] + b[:FP] + b[:FN])

#_metric(b, t::Int) = println("pouet")

metric(b, m::Symbol, p) = metric(b, Val{m}(), p)

metric(b, t::Tuple{Symbol, Real}) = metric(b, t...)

metric(b, m::Val{:Fscore}, β) = begin
    a = (1 + β^2)
    @. a * b[:TP] / nozero(a * b[:TP] + β^2 * b[:FN] + b[:FP])
end

function metric(b, m::Val{:PT})
    fpr = haskey(b, :FPR) ? b[:FPR] : metric(b, :FPR)
    tpr = haskey(b, :TPR) ? b[:TPR] : metric(b, :TPR)
    a = @. sqrt(fpr)
    @. a / nozero(sqrt(tpr) + a)
end


using Distributions
using Base.Test
using SkewDist

if isa(Pkg.installed("Calculus"), VersionNumber)
    using Calculus
    function mean_mgf_consistent(dist::ContinuousUnivariateDistribution)
        d = derivative(x->mgf(dist, x))
        m0 = d(0)
        @test_approx_eq derivative(x->mgf(dist, x), 0.0) mean(dist)
    end
    function std_mgf_consistent(dist::ContinuousUnivariateDistribution)
        d1 = derivative(x->mgf(dist,x))
        d2 = derivative(d1)
        @test_approx_eq_eps sqrt(d2(0) - d1(0)^2)  std(dist) 1e-6
    end
else
    mean_mgf_consistent(dist::ContinuousUnivariateDistribution) = true
    std_mgf_consistent(dist::ContinuousUnivariateDistribution) = true
end

function pdf_integrates_to_one(dist::ContinuousUnivariateDistribution)
    @test_approx_eq quadgk(t->pdf(dist, t), minimum(dist), maximum(dist))[1] 1.0
end

function mean_pdf_consistent(dist::ContinuousUnivariateDistribution)
    @test_approx_eq quadgk(t->t*pdf(dist, t), minimum(dist), maximum(dist))[1] mean(dist)
end

function quantile_cdf_consistent(dist::ContinuousUnivariateDistribution)
    for β in 0.05:0.05:0.95
        @test_approx_eq β cdf(dist, quantile(dist, β))
    end
end

# function mean_rand_consistent(dist::ContinuousUnivariateDistribution, n::Int, β::Real)
#     samp = rand(dist, n)
#     m0 = mean(samp)

# end

function test_dist(dist::ContinuousUnivariateDistribution)
    println("Testing distribution $(dist)...")
    pdf_integrates_to_one(dist)
    mean_pdf_consistent(dist)
    quantile_cdf_consistent(dist)
    try
        mean_mgf_consistent(dist)
        std_mgf_consistent(dist)
    catch err
        if !isa(err, MethodError)
            rethrow(err)
        end
    end
end

skewnorm1 = SkewNormal(1.0, 1.0, -1.0)
test_dist(skewnorm1)

skewtdist1 = SkewTDist(1.0, 2.0, 2.0, 3.0)
test_dist(skewtdist1)


# compare output from KernelDensity.jl with SINC kernel
# to what Comte-Butucea yields. (should be the same)
# also look at how DeLaValleePoussin performs

using EmpiricalBayes
using Test
using Distributions
using Random

true_dist = MixtureModel([ Normal(-0.3,.5), Normal(1.05,.5)])
marginal_grid = collect(range(-6, stop=6, length=1001));

marginal_h = marginal_grid[2] - marginal_grid[1] # should really replace by range

prior_grid = collect(range(-3, stop=3, length=121));
d_true = NormalConvolutionProblem(true_dist, marginal_grid);

ds = MixingNormalConvolutionProblem(Normal, 0.2, prior_grid, marginal_grid);

m = 10_000

Random.seed!(2)
Xs = rand(d_true, m)

#----------------------------------------
#---- Test with actual Sinc Kernel-------
#----------------------------------------
f_sinc = sinc_kde(Xs, marginal_grid, SincKernel)

x_tst_1 = f_sinc.x[700]
f_tst_1 = f_sinc.density[700]

# Let us compare to the result from comte_butucea
f_cbt_1 = estimate(Xs, ComteButucea,
  MarginalDensityTarget(x_tst_1), marginal_grid)

@test f_cbt_1 ≈ f_tst_1 atol=0.001

#----- Check default dispatch ---------------------------------
dv_kde = sinc_kde(Xs, marginal_grid, DeLaValleePoussinKernel)
dv_kde_auto_dispatch = sinc_kde(Xs, marginal_grid)
@test dv_kde.density == dv_kde_auto_dispatch.density

#--------------------------------------------------------------
#---- Tests with both Sinc and DeLaValleePoussin Kernels-------
#--- Are our bands longer than true bands?
#--------------------------------------------------------------

f_true = pdf.(Ref(d_true), marginal_grid)

for T in [SincKernel, DeLaValleePoussinKernel]
  f_kde= sinc_kde(Xs, marginal_grid, T)
  true_C = maximum(abs.(f_true .- f_kde.density))

  f_nb = fit(BinnedMarginalDensityNeighborhood, Xs, marginal_grid, T)
  f_nb.C_std

  f_nb_ds = fit(BinnedMarginalDensityNeighborhood, Xs, ds, T)
  @show true_C, f_nb_ds.C_std, f_nb_ds.C_bias, f_nb_ds.C_std + f_nb_ds.C_bias
  @test true_C <= f_nb_ds.C_std + f_nb_ds.C_bias
end

# What are these doing here?
Xs_test = rand(d_true, m)
full_CI = CEB_ci(Xs, Xs_test, ds, PosteriorTarget(PosteriorMeanNumerator(2.0)))

full_CI[1].ci_left, full_CI[1].ci_right

# Use output from first run to speed up 2nd run by factor 4!
full_CI_fast_comp = CEB_ci(Xs, Xs_test, ds, PosteriorTarget(PosteriorMeanNumerator(2.0)),
                            full_CI[2], full_CI[3], full_CI[4].C_bias)

full_CI_fast_comp[1].ci_left, full_CI_fast_comp[1].ci_right

@test full_CI[1].ci_left ≈ full_CI_fast_comp[1].ci_left atol=0.005
@test full_CI[1].ci_right ≈ full_CI_fast_comp[1].ci_right atol=0.005

full_CI_inf = CEB_ci(Xs, Xs_test, ds, PosteriorTarget(PosteriorMeanNumerator(2.0)); C=Inf)

full_CI_inf[1].ci_left, full_CI_inf[1].ci_right

posterior_stats(d_true,  PosteriorTarget(PosteriorMeanNumerator(2.0)))


cd(@__DIR__)
using AdvancedPS, Random, Distributions

Random.seed!(50514)

priors = (
    μ=(Normal(0, 10),),
    σ=(Truncated(Cauchy(0, 1), 0.0, Inf),)
)

bounds = ((-Inf,Inf),(0.0,Inf))

data = rand(Normal(0.0, 1.0), 50)

function loglike(μ, σ, data)
    return sum(logpdf.(Normal(μ, σ), data))
end

loglike(θ) = loglike(θ..., data)

model = DEModel(priors=priors, model=loglike)

de = DE(bounds=bounds, burnin=1000, priors=priors, progress=true)
n_iter = 2000
chains = psample(model, de, n_iter)
println(chains)


using Calculus
using PGFPlots
using QuadGK
using SpecialFunctions

# WARNING: integrate(f,a,b) is deprecated, use (quadgk(f,a,b))[1] instead
# f(x) = integrate(z -> besselj(1, z), 0.0, x)

f(x) = QuadGK.quadgk(z -> besselj(1, z), 0.0, x)[1]

function my_draw(f)
    my_plot_pgf = Axis([
                        Plots.Linear(f, (0, 100), style="smooth", legendentry=L"\int_{z=0}^{z=\textbf{r}}J_1(z)dz"),
                    ], legendPos="north west")

    save("images/Int_J1_tex3.tex", my_plot_pgf)
end

@time my_draw(f)
@time my_draw(f)

y = z -> besselj(1, z)
g(x) = QuadGK.quadgk(y, 0.0, x)[1]

@time my_draw(g)
@time my_draw(g)


import DifferentialEquations
const DE = DifferentialEquations

# This is curiously slow
function sde_exercise()
    f = (u,p,t) -> 1.01*u
    g = (u,p,t) -> 0.87*u
    u0 = 0.5
    tspan = (0.0,1.0)
    prob = DE.SDEProblem(f,g,u0,tspan)
    sol = DE.solve(prob,reltol=1e-3,abstol=1e-3)
    return nothing
end

function ode_exercise()
    f = (u,p,t) -> -u
    u0 = 0.5
    tspan = (0., 1.)
    prob = DE.ODEProblem(f, u0, tspan)
    sol = DE.solve(prob)
    return nothing
end

function ode_exercise2()
    f = function(u,p,t)
        x, y, z = u
        sigma, rho, beta = p
        return [sigma * (y - x), x * (rho - z) - y, x * y - beta * z]
    end
    u0 = [1.0,0.0,0.0]
    tspan = (0., 100.)
    p = [10.0,28.0,8/3]
    prob = DE.ODEProblem(f, u0, tspan, p)
    sol = DE.solve(prob,saveat=0.01)
    return nothing
end

# From ODE docs
function ode_exercise3()
    f(u,p,t) = 1.01*u
    u0 = 1/2
    tspan = (0.0,1.0)
    prob = ODEProblem(f,u0,tspan)
    sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)
    return nothing
end

ode_exercise()
ode_exercise2()
ode_exercise3()
sde_exercise()


using Test
using LinearAlgebra
using QuantumPropagators

@testset "random state" begin

    precision = 1e-10

    # Input
    N = 1000

    X = rand(ComplexF64, (N, N))
    H = Hermitian(X)

    dt = 0.5

    Ψ₀ = rand(ComplexF64, N)
    Ψ₀ ./= norm(Ψ₀)

    @test norm(Ψ₀) ≈ 1

    # Expected
    U = exp(-im * H * dt)
    @test norm(U * U'  - one(U)) < precision  # U is unitary
    Ψ_out_expected = U * Ψ₀
    @test norm(Ψ_out_expected) ≈ 1

    # Cheby result
    evals = eigvals(H)

    E_min = evals[1]
    Δ = evals[end] - evals[1]

    a = cheby_coeffs(Δ, dt)
    @test length(a) == 267
    b = zeros(20);
    n = cheby_coeffs!(b, Δ, dt)
    @test b[1:n] ≈ a[1:n]

    Ψ = copy(Ψ₀)
    wrk = ChebyWrk(Ψ₀, Δ, E_min, dt)
    cheby!(Ψ, H, dt, wrk)
    Ψ_out = copy(Ψ)

    # Comparison
    @test norm(Ψ_out - Ψ_out_expected) < precision

end