module TestCWrapper

using Ipopt
using Test

function test_hs071()
  # hs071
  # min x1 * x4 * (x1 + x2 + x3) + x3
  # st  x1 * x2 * x3 * x4 >= 25
  #     x1^2 + x2^2 + x3^2 + x4^2 = 40
  #     1 <= x1, x2, x3, x4 <= 5
  # Start at (1,5,5,1)
  # End at (1.000..., 4.743..., 3.821..., 1.379...)

  function eval_f(x::Vector{Float64})
    return x[1] * x[4] * (x[1] + x[2] + x[3]) + x[3]
  end

  function eval_g(x::Vector{Float64}, g::Vector{Float64})
    # Bad: g    = zeros(2)  # Allocates new array
    # OK:  g[:] = zeros(2)  # Modifies 'in place'
    g[1] = x[1]   * x[2]   * x[3]   * x[4]
    g[2] = x[1]^2 + x[2]^2 + x[3]^2 + x[4]^2
  end

  function eval_grad_f(x::Vector{Float64}, grad_f::Vector{Float64})
    # Bad: grad_f    = zeros(4)  # Allocates new array
    # OK:  grad_f[:] = zeros(4)  # Modifies 'in place'
    grad_f[1] = x[1] * x[4] + x[4] * (x[1] + x[2] + x[3])
    grad_f[2] = x[1] * x[4]
    grad_f[3] = x[1] * x[4] + 1
    grad_f[4] = x[1] * (x[1] + x[2] + x[3])
  end

  function eval_jac_g(x::Vector{Float64}, mode, rows::Vector{Int32}, cols::Vector{Int32}, values::Vector{Float64})
    if mode == :Structure
      # Constraint (row) 1
      rows[1] = 1; cols[1] = 1
      rows[2] = 1; cols[2] = 2
      rows[3] = 1; cols[3] = 3
      rows[4] = 1; cols[4] = 4
      # Constraint (row) 2
      rows[5] = 2; cols[5] = 1
      rows[6] = 2; cols[6] = 2
      rows[7] = 2; cols[7] = 3
      rows[8] = 2; cols[8] = 4
    else
      # Constraint (row) 1
      values[1] = x[2]*x[3]*x[4]  # 1,1
      values[2] = x[1]*x[3]*x[4]  # 1,2
      values[3] = x[1]*x[2]*x[4]  # 1,3
      values[4] = x[1]*x[2]*x[3]  # 1,4
      # Constraint (row) 2
      values[5] = 2*x[1]  # 2,1
      values[6] = 2*x[2]  # 2,2
      values[7] = 2*x[3]  # 2,3
      values[8] = 2*x[4]  # 2,4
    end
  end

  function eval_h(x::Vector{Float64}, mode, rows::Vector{Int32}, cols::Vector{Int32}, obj_factor::Float64, lambda::Vector{Float64}, values::Vector{Float64})
    if mode == :Structure
      # Symmetric matrix, fill the lower left triangle only
      idx = 1
      for row = 1:4
        for col = 1:row
          rows[idx] = row
          cols[idx] = col
          idx += 1
        end
      end
    else
      # Again, only lower left triangle
      # Objective
      values[1] = obj_factor * (2*x[4])  # 1,1
      values[2] = obj_factor * (  x[4])  # 2,1
      values[3] = 0                      # 2,2
      values[4] = obj_factor * (  x[4])  # 3,1
      values[5] = 0                      # 3,2
      values[6] = 0                      # 3,3
      values[7] = obj_factor * (2*x[1] + x[2] + x[3])  # 4,1
      values[8] = obj_factor * (  x[1])  # 4,2
      values[9] = obj_factor * (  x[1])  # 4,3
      values[10] = 0                     # 4,4

      # First constraint
      values[2] += lambda[1] * (x[3] * x[4])  # 2,1
      values[4] += lambda[1] * (x[2] * x[4])  # 3,1
      values[5] += lambda[1] * (x[1] * x[4])  # 3,2
      values[7] += lambda[1] * (x[2] * x[3])  # 4,1
      values[8] += lambda[1] * (x[1] * x[3])  # 4,2
      values[9] += lambda[1] * (x[1] * x[2])  # 4,3

      # Second constraint
      values[1]  += lambda[2] * 2  # 1,1
      values[3]  += lambda[2] * 2  # 2,2
      values[6]  += lambda[2] * 2  # 3,3
      values[10] += lambda[2] * 2  # 4,4
    end
  end

  n = 4
  x_L = [1.0, 1.0, 1.0, 1.0]
  x_U = [5.0, 5.0, 5.0, 5.0]

  m = 2
  g_L = [25.0, 40.0]
  g_U = [2.0e19, 40.0]

  prob = createProblem(n, x_L, x_U, m, g_L, g_U, 8, 10,
  eval_f, eval_g, eval_grad_f, eval_jac_g, eval_h)

  prob.x = [1.0, 5.0, 5.0, 1.0]
  solvestat = solveProblem(prob)

  @test Ipopt.ApplicationReturnStatus[solvestat] == :Solve_Succeeded
  @test prob.x[1] ≈ 1.0000000000000000 atol=1e-5
  @test prob.x[2] ≈ 4.7429996418092970 atol=1e-5
  @test prob.x[3] ≈ 3.8211499817883077 atol=1e-5
  @test prob.x[4] ≈ 1.3794082897556983 atol=1e-5
  @test prob.obj_val ≈ 17.014017145179164 atol=1e-5

  # This tests callbacks.
  function intermediate(alg_mod::Int, iter_count::Int,
    obj_value::Float64, inf_pr::Float64, inf_du::Float64, mu::Float64,
    d_norm::Float64, regularization_size::Float64, alpha_du::Float64, alpha_pr::Float64,
    ls_trials::Int)
    return iter_count < 1  # Interrupts after one iteration.
  end

  setIntermediateCallback(prob, intermediate)

  solvestat = solveProblem(prob)

  @test Ipopt.ApplicationReturnStatus[solvestat] == :User_Requested_Stop

  # Test setting some options
  # String option
  println("\nString option")
  addOption(prob, "hessian_approximation", "exact")
  @test_throws ErrorException addOption(prob, "hessian_approximation", "badoption")
  println("\nInt option")
  # Int option
  addOption(prob, "file_print_level", 3) == nothing
  @test_throws ErrorException addOption(prob, "file_print_level", -1)
  # Double option
  println("\nFloat option")
  addOption(prob, "derivative_test_tol", 0.5)
  @test_throws ErrorException addOption(prob, "derivative_test_tol", -1.0)

  # Test opening an output file
  openOutputFile(prob, "blah.txt", 5)
  Ipopt.freeProblem(prob) # Needed before the `rm` on Windows.
  # unlink the output file
  rm("blah.txt")
end

function test_binary()
  # Test that the ipopt binary works
  @test Ipopt.amplexefun("-v") == 0
end

end  # TestCWrapper

runtests(TestCWrapper)


# ---
# title: 121. Best Time to Buy and Sell Stock
# id: problem121
# author: zhwang
# date: 2022-01-17
# difficulty: Easy
# categories: Array, Dynamic Programming
# link: <https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/>
# hidden: true
# ---
#
# Say you have an array for which the _i_ th element is the price of a given
# stock on day _i_.
#
# If you were only permitted to complete at most one transaction (i.e., buy one
# and sell one share of the stock), design an algorithm to find the maximum
# profit.
#
# Note that you cannot sell a stock before you buy one.
#
# **Example 1:**
#
#
#
#     Input: [7,1,5,3,6,4]
#     Output: 5
#     Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
#                  Not 7-1 = 6, as selling price needs to be larger than buying price.
#
#
# **Example 2:**
#
#
#
#     Input: [7,6,4,3,1]
#     Output: 0
#     Explanation: In this case, no transaction is done, i.e. max profit = 0.
#
#
#
## @lc code=start
using LeetCode

function max_profit_121(prices::Vector{Int})::Int
    min_cost, profit = prices[1], 0
    for price in @view prices[2:end]
        profit = max(profit, price - min_cost)
        min_cost = min(min_cost, price)
    end
    return profit
end

## @lc code=end


# # Robust approximate fitting
# Section 6.4.2
# Boyd & Vandenberghe "Convex Optimization"
# Original by Lieven Vandenberghe
# Adapted for Convex by Joelle Skaf - 10/03/05
#
# Adapted for Convex.jl by Karanveer Mohan and David Zeng - 26/05/14
# Original cvx code and plots here:
# <http://web.cvxr.com/cvx/examples/cvxbook/Ch06_approx_fitting/html/fig6_15.html>
#
# Consider the least-squares problem:
#       minimize $\|(A + tB)x - b\|_2$
# where $t$ is an uncertain parameter in [-1,1]
# Three approximate solutions are found:
#
#   1. nominal optimal (i.e. letting t=0)
#   2. stochastic robust approximation:
#           minimize $\mathbb{E}\|(A+tB)x - b\|_2$
#      assuming $u$ is uniformly distributed on [-1,1].
#      (reduces to minimizing $\mathbb{E} \|(A+tB)x-b\|^2 = \|A*x-b\|^2  + x^TPx$
#        where $P = \mathbb{E}(t^2) B^TB = (1/3) B^TB$ )
#   3. worst-case robust approximation:
#           minimize $\mathrm{sup}_{-1\leq u\leq 1} \|(A+tB)x - b\|_2$
#      (reduces to minimizing $\max\{\|(A-B)x - b\|_2, \|(A+B)x - b\|_2\}$ ).
#
using Convex, LinearAlgebra, SCS

# Input Data
m = 20;
n = 10;
A = randn(m, n);
(U, S, V) = svd(A);
S = diagm(exp10.(range(-1, stop = 1, length = n)));
A = U[:, 1:n] * S * V';

B = randn(m, n);
B = B / norm(B);

b = randn(m, 1);
x = Variable(n)

# Case 1: Nominal optimal solution
p = minimize(norm(A * x - b, 2))
solve!(p, SCS.Optimizer; silent_solver = true)
x_nom = evaluate(x)

# Case 2: Stochastic robust approximation
P = 1 / 3 * B' * B;
p = minimize(square(pos(norm(A * x - b))) + quadform(x, Symmetric(P)))
solve!(p, SCS.Optimizer; silent_solver = true)
x_stoch = evaluate(x)

# Case 3: Worst-case robust approximation
p = minimize(max(norm((A - B) * x - b), norm((A + B) * x - b)))
solve!(p, SCS.Optimizer; silent_solver = true)
x_wc = evaluate(x)

# Plot residuals:
parvals = range(-2, stop = 2, length = 100);

errvals(x) = [norm((A + parvals[k] * B) * x - b) for k in eachindex(parvals)]
errvals_ls = errvals(x_nom)
errvals_stoch = errvals(x_stoch)
errvals_wc = errvals(x_wc)

using Plots
plot(parvals, errvals_ls, label = "Nominal problem")
plot!(parvals, errvals_stoch, label = "Stochastic Robust Approximation")
plot!(parvals, errvals_wc, label = "Worst-Case Robust Approximation")
plot!(
    title = "Residual r(u) vs a parameter u for three approximate solutions",
    xlabel = "u",
    ylabel = "r(u) = ||A(u)x-b||_2",
)


import Pkg; Pkg.activate(joinpath(@__DIR__, "../../../../../"))
using Distributions
using PyPlot

"""
Paper: Bayesian inference for finite mixtures of univariate and multivariate
       skew-normal and skew-t distributions, Biostatistics 2010.
skew (delta): a real number in (-1, 1)
"""
function rand_skewnormal(loc, scale, skew)
    z = rand(TruncatedNormal(0, 1, 0, Inf))
    return loc + scale * skew * z + scale * sqrt(1 - skew ^ 2) * randn()
end

function rand_skewnormal(loc, scale, skew, dims...)
    z = rand(TruncatedNormal(0, 1, 0, Inf), dims...)
    return loc .+ scale * skew * z + scale * sqrt(1 - skew ^ 2) * randn(dims...)
end

x = rand_skewnormal(1, .5, -.97, 100000)
plt.hist(x, bins=100);

mean(x .< 0)

mean(x), std(x)


module MDL

setprecision(BigFloat, 64)

"""
scalable binomial coefficient
"""
function choose(n::Int64, k::Int64)
    if 0 < k <= n
        p = BigInt(1)
        for t=0:(min(k,n-k)-1)
            p = floor((p * (n - t)) / (t + 1))
        end
        return p
    else
        return 0.
    end
end

"""
binary logarithm
"""
function log2(n::Real)
    return log(2, n)
end

"""
binary logarithm returning 0. at zero
"""
function log2_zero(n::Real)
    if n == 0
        return 0.
    else
        return log2(n)
    end
end

"""
binary logarithm of binomial coefficient
"""
function log2_choose(n::Int64, k::Int64)
    if n == 0 || k == 0
        return 0.
    else
        return log2_zero(choose(n,k))
    end
end

"""
iterated logarithm (log-star)
"""
function log2_star(n::Union{Int64,Float64,BigInt,BigFloat})
    if n <= 1
        return 0.
    else
        return 1. + log2_star(log2(n))
    end
end

"""
:param z: an int >= 1
:return: the encoded size of the int according to Rissanen 1983's universal code for integers
"""
function universal_integer(z::Int64)
    NORMALIZATION_CONSTANT = 2.865064
    if z <= 0
        return 0.
    end
    c = log2(NORMALIZATION_CONSTANT)
    logstar_z = log2_star(z)
    return logstar_z + c
end

end


"""
    treynor_ratio(asset_returns, benchmark_returns; multiplier=1.0, risk_free=0.0)

Calculates the Treynor ratio as the ratio of excess return divided by the CAPM beta. This ratio is similar to the Sharpe Ratio, but instead of dividing by the volatility, we devide by the CAPM beta as risk proxy.

# Formula

    TR = E[asset_returns - risk_free] / beta * multiplier

# Arguments
- `asset_returns`:      Vector of asset returns.
- `benchmark_returns`:  Vector of benchmark returns (e.g. market portfolio returns).
- `multiplier`:         Optional scalar multiplier, i.e. use `12` to annualize monthly returns, and use `252` to annualize daily returns.
- `risk_free`:          Optional vector or scalar value denoting the risk-free return (must have same frequency as the provided returns, e.g. daily).
"""
function treynor_ratio(asset_returns, benchmark_returns; multiplier=1.0, risk_free=0.0)
    α, β = capm(asset_returns, benchmark_returns; risk_free=risk_free)
    mean(asset_returns .- risk_free) / β * multiplier
end


using DifferentialEquations
using SimpleDiffEq
using Random
using Distributions
using GpABC
using Distances
using ApproxBayes
using Plots

function sir_ode!(du,u,p,t)
    (S,I,R,C) = u
    (β,c,γ) = p
    N = S+I+R
    infection = β*c*I/N*S
    recovery = γ*I
    @inbounds begin
        du[1] = -infection
        du[2] = infection - recovery
        du[3] = recovery
        du[4] = infection
    end
    nothing
end;

tmax = 40.0
δt = 1.0
tspan = (0.0,tmax)
obstimes = 1.0:δt:tmax;
u0 = [990.0,10.0,0.0,0.0]; # S,I.R,C
p = [0.05,10.0,0.25]; # β,c,γ

prob_ode = ODEProblem(sir_ode!,u0,tspan,p)
sol_ode = solve(prob_ode,saveat=δt)
out_ode = Array(sol_ode)
C = out_ode[4,:]
X = C[2:end] .- C[1:(end-1)];

Random.seed!(1234)
Y = rand.(Poisson.(X));

bar(obstimes,Y)
plot!(obstimes,X)

function simdata(x)
    (i0,β) = x
    I = i0*1000.0
    prob = remake(prob_ode,u0=[1000-I,I,0.0,0.0],p=[β,10.0,0.25])
    sol = solve(prob,Tsit5(),saveat=δt)
    out = Array(sol)
    C = out[4,:]
    X = C[2:end] .- C[1:(end-1)]
    transpose(X)
end;

priors = [Uniform(0.0,0.1),Uniform(0.0,0.1)];

Yt = transpose(float.(Y));

n_particles = 2000
threshold = 80.0
sim_rej_result = SimulatedABCRejection(
    Yt, # data
    simdata, # simulator
    priors, # priors
    threshold, # threshold distance
    n_particles; # particles required
    max_iter=convert(Int, 1e7),
    distance_function = Distances.euclidean,
    write_progress=false);

plot(sim_rej_result)

n_design_points = 500
emu_rej_result = EmulatedABCRejection(Yt,
    simdata,
    priors,
    threshold,
    n_particles,
    n_design_points;
    max_iter=convert(Int, 1e7),
    distance_function = Distances.euclidean,
    write_progress=false);

plot(emu_rej_result)

threshold_schedule = [110.0,100.0,90.0,80.0];

sim_smc_result = SimulatedABCSMC(Yt,
    simdata,
    priors,
    threshold_schedule,
    n_particles;
    max_iter=convert(Int, 1e7),
    distance_function = Distances.euclidean,
    write_progress=false);

population_colors=["#FF2F4E", "#D0001F", "#A20018", "#990017"]
plot(sim_smc_result, population_colors=population_colors)

emu_smc_result = EmulatedABCSMC(Yt,
    simdata,
    priors,
    threshold_schedule,
    n_particles,
    n_design_points;
    distance_metric = Distances.euclidean,
    batch_size=1000,
    write_progress=false,
    emulator_retraining = PreviousPopulationThresholdRetraining(n_design_points, 100, 10),
    emulated_particle_selection = MeanVarEmulatedParticleSelection());

plot(emu_smc_result, population_colors=population_colors)

function simdist(x, constants, y)
  s = transpose(simdata(x))
  Distances.euclidean(s, y), 1
end;

ab_rej_setup = ABCRejection(simdist, #simulation function
  2, # number of parameters
  threshold, #target ϵ
  Prior(priors); # Prior for each of the parameters
  maxiterations = 10^7, #Maximum number of iterations before the algorithm terminates
  nparticles = n_particles
  );

ab_rej = runabc(ab_rej_setup,
            Y,
            verbose = true,
            progress = true,
            parallel = true);

plot(ab_rej)

ab_smc_setup = ABCSMC(simdist, #simulation function
  2, # number of parameters
  threshold, #target ϵ
  Prior(priors), #Prior for each of the parameters
  maxiterations=convert(Int,1e7),
  nparticles=n_particles,
  α = 0.3,
  convergence = 0.05,
  kernel = uniformkernel
  );

ab_smc = runabc(ab_smc_setup,
            Y,
            verbose = true,
            progress = true,
            parallel = true);

plot(ab_smc)


"""
    angularspectrum(Uin, d, λ, lx, ly)

calculate the propagation light field based on the angular spectrum.

## Arguments

- `Uin::AbstractArray{<:Number,2}`: Complex array of input complex amplitude.
- `d::Real`: Distance to propagate in metres.
- `λ::Real`: Wavelength of light to propagate.
- `lx::Real`: The physical size of the input data along the x-axis.
- `ly::Real`: The physical size of the input data along the y-axis.

## Returns

- `::Array{<:Number,2}`: Complex amplitude data after propagation.
"""
function angularspectrum(Uin::AbstractArray{<:Number,2}, d::Real, λ::Real, lx::Real, ly::Real)
    (n, m) = size(Uin)
    ua = (-n/2:n/2-1)/lx*λ
    va = (-m/2:m/2-1)/ly*λ
    ifft(fft(Uin).*ifftshift([1.0-u^2-v^2>=0.0 ? exp(2im*pi*d/λ*sqrt(1.0-u^2-v^2)) : 0.0+0.0im for u in ua, v in va]))
end

"""
    angularspectrum(Uin::MonoLightField2D, d::Unitful.Length) -> MonoLightField2D

calculate the propagation light field for [`MonoLightField2D`](@ref) based on the angular spectrum.
"""
function angularspectrum(Uin::MonoLightField2D, d::Unitful.Length)
    Uout = angularspectrum(Uin.data, auval(d), auval(Uin.wavelength), auval(Uin.size[1]), auval(Uin.size[2]))
    MonoLightField2D(Uin, data=Uout)
end

"""
    angularspectrum(d)

represent an [`angularspectrum`](@ref) propagation functor with the specified distance.
"""
angularspectrum(d) = x->angularspectrum(x, d)


module InclusionsGenerator

export generate_inclusions

# sphere volume
vol(r) = 4/3*π*r^3

# random inclusoin center
new_center(lx, ly, lz) = (rand()*lx, rand()*ly, rand()*lz)

# 3D euclidean distance
distance(p1, p2) = sqrt((p1[1]-p2[1])^2 + (p1[2]-p2[2])^2 + (p1[3]-p2[3])^2)

# check if the new inclusion collides with any pre-existent inclusion
function sphere_collision(coords, new_coord, r; buffer = 0.2)
    for c in coords
        if distance(c, new_coord) ≤ 2*r*(1 + buffer)
            return true
        end
    end

    return false
end

function wall_collision(new_coord, r, lx, ly, lz; buffer = 0.2)
    x, y, z = new_coord
    buffer += 1
    if (x ≤ r*buffer) || (x ≥ (lx - r*buffer))
        return true

    elseif (y ≤ r*buffer) || (y ≥ (ly - r*buffer))
        return true

    elseif (z ≤ r*buffer) || (z ≥ (lz - r*buffer))
        return true
    end

    return false
end

function generate_inclusions(lx, ly, lz, ϕ, r;  buffer_ball = 0.2, buffer_wall = 0.2)
    # first sphere coordinate
    coords = [new_center(lx, ly, lz)]

    tot_vol = vol(r)
    vol_fraction = tot_vol/(lx*ly*lz)

    iter = 0
    while vol_fraction ≤ ϕ
        iter += 1
        # temptative coordinate
        new_coord = new_center(lx, ly, lz)
        collision1 = sphere_collision(coords, new_coord, r; buffer=buffer_ball)
        collision2 = wall_collision(new_coord, r, lx, ly, lz; buffer=buffer_wall)
        if !collision1 && !collision2
            push!(coords, new_coord)
            tot_vol += vol(r)
            vol_fraction = tot_vol/(lx*ly*lz)
            iter = 0
        end
        if iter > 500
            println("Forcing exit. Generator stalling")
            break
        end
    end

    println("Done with $(length(coords)) inclusions and $(vol_fraction*100) % volume fraction.")

    return coords, vol_fraction
end

end # module


using DrWatson
@quickactivate "StatReth"

# %%
using CSV
using DataFrames
using Turing
using Plots

include(srcdir("quap.jl"))
include(srcdir("tools.jl"))

# %%
# Load the data, don't forget to drop everything missing.
d = DataFrame(CSV.File(datadir("exp_raw/cherry_blossoms.csv"), missingstring = "NA"))
d2 = dropmissing(d, :doy)

# %%
using BSplines: BSplineBasis, basismatrix

num_knots = 15
knot_list = quantile(d2.year, range(0, 1, length = num_knots))

basis = BSplineBasis(4, knot_list)
B = basismatrix(basis, d2.year)

@model function spline(doy, B = B)
    α ~ Normal(100, 10)
    w ~ filldist(Normal(0, 2), size(B, 2))
    σ ~ Exponential(1)
    μ = α .+ B * w
    doy ~ MvNormal(μ, σ)
end

m = spline(d2.doy)

# %%
# Sample from the prior. Looks pretty reasonable.
prior = sample(m, Prior(), 100) |> DataFrame
w_str = ["w[$i]" for i in 1:length(basis)]

scatter(d2.year, d2.doy, alpha = 0.3)
for r in eachrow(prior)
    p = r.α .+ B * Array(r[w_str])
    plot!(d2.year, p, color = :black, alpha = 0.2)
end
plot!(legend = false)

# %%
"""
It looks like the prior on the weights "controls" the possible amplitude of the wiggle (as
opposed to the number of knots which controls its frequency).
"""


sqrt(sum(A.^2.) / length(A))


using SymPy
import PyCall
using Test

## Test of importing an external module using `pyimport_conda` and `import_from`.
## from issue #262

stats = PyCall.pyimport_conda("sympy.stats", "sympy")

# this brings in all functions into the session using introspection, specialized on a
# symbolic first argument
import_from(stats)

@testset "Test stats module" begin

    @vars mu z x
    @vars sigma positive=true
    X = stats.Normal("x", mu, sigma)

    # some operations:
    @test variance(X) == sigma^2
    @test E(X) == mu
    P(Gt(X,3)) ## or P(X ≫ 3)

    Z = stats.Normal("Z", 0, 1)  # needs `stats.` as "Z" is not symbolic,
    # could also do this:
    @vars Z
    Z = Normal(Z, 0, 1)
    @test E(Z) == 0
    @test variance(Z) == 1

    simplify(P(Gt(Z, 1)))

    @test 0.68 <= 1 - 2*N(P(Gt(Z, 1))) <= 0.69

    @vars Y
    @vars a b c
    Y = DiscreteUniform(Y, (a,b,c))
    density(Y).dict
    @test density(Y).dict[a] == 1//3

    # And density
    density(X)  #NormalDistribution(mu, sigma)
    density(X).pdf(z)  # need the `pdf` call here

end


using Nemo

# _Fast_ composed sums and composed products of polynomials,
# using the algorithm described in:
# "Fast computation of special resultants"
# by Bostan, Flajolet, Salvy, and Schost

# derivative of polynomial
derivative(c::Vector) = c[2:end] .* (1:length(c)-1)

function polyinv(coeffs::Vector, n)
	R, x = Nemo.PowerSeriesRing(Nemo.FlintQQ, n, "x")
	a = R(map(Nemo.FlintQQ, coeffs), length(coeffs), n, 0)
	ai = inv(a)
	return Nemo.fmpq[coeff(ai,i) for i=0:n-1]
end

# compute newton power series of polynomial given with coefficients coeff,
# in base field R,x.
# See fig.1 in reference
function to_newton(coeffs::Vector{BigInt},n,R,x)
	# first, make monic.
	coeffs = coeffs//coeffs[end]

	d = length(coeffs)-1
	a_cfs = reverse(derivative(coeffs))
	b_cfs = reverse(coeffs)

	# initialize power series polynomials
	a = R(map(Nemo.FlintQQ, a_cfs))
	b = R(map(Nemo.FlintQQ, b_cfs))
	b0 = R(polyinv(b_cfs, n))

	c  = truncate(a*b0, d)

	r = R()
	x_power = R(1)
	x_d = x^d

	l = round(Int64,floor(n/d))
	for j = 0 : l
		r += c*x_power
		x_power *= x_d
		c = -mullow(shift_right(b*c,d),b0,d)
	end
	return r
end

to_array(p) = Rational{BigInt}[Rational(coeff(p,i)) for i=0:Nemo.degree(p)]

# tr: traces i.e. newton series
# This algorithm is based on the Leverrier-Faddeev algorithm
# see: http://math.stackexchange.com/questions/405822/what-is-the-fastest-way-to-find-the-characteristic-polynomial-of-a-matrix
function from_newton(tr::Vector{T}) where {T<:Number}
	# special case
	if tr==[1]
		return [0,1]
	end
	n = length(tr)
	c = Array{T}(UndefInitializer(),n)
	c[end] = one(T)
	for k = 1 : n-1
		next_c = -sum(tr[2:(k+1)].*c[end-k+1:end])/k
		c[end-k] = next_c
	end
	return c
end

# Hadamard (element-wise) product of two polynomials
function hadm(p,q,R)
	n = max(Nemo.degree(p),Nemo.degree(q))
	R([Nemo.coeff(p,i)*Nemo.coeff(q,i) for i=0:n])
end

# composed product of two polynomials, given as coeffs p and q
function composed_product(p::Vector{BigInt},q::Vector{BigInt})
	# compute newton series
	n = (length(p)-1)*(length(q)-1)+1
	R, x = Nemo.PolynomialRing(Nemo.FlintQQ, "x")
	a = to_newton(p,n,R,x)
	b = to_newton(q,n,R,x)

	# multiply newton series and invert
	pq = from_newton(to_array(hadm(a,b,R)))

	# convert to integer and return
	return map(numerator, pq*lcm(map(denominator, pq)))
end

# composed sum of two polynomials, given as coeffs p and q
function composed_sum(p::Vector{BigInt},q::Vector{BigInt})
	# compute newton series
	n = (length(p)-1)*(length(q)-1)+1
	R, x = Nemo.PolynomialRing(Nemo.FlintQQ, "x")
	a = to_newton(p,n,R,x)
	b = to_newton(q,n,R,x)

	# exp series
	ee  = R([Nemo.FlintQQ(1//factorial(BigInt(i))) for i=0:(n-1)])
	eei = R([Nemo.FlintQQ(   factorial(BigInt(i))) for i=0:(n-1)])

	# multiply newton series and invert
	m = truncate(hadm(a,ee,R)*hadm(b,ee,R),n)
	pq = from_newton(to_array(hadm(m,eei,R)))

	# convert to integer and return
	return map(numerator, pq*lcm(map(denominator, pq)))
end


"""Julia program to implement Minimum XOR value.
Given an array of integers, we have to find the minimum XOR value, a pair in the array makes."""

function minimumXOR(arr, n)
    # Sort the array, so that elements with minimum bit difference are present consecutive.
    sort(arr)
    minXOR = typemax(Int64)
    val = 0

    # Compute the XOR values of the consecutive elements of the array.
    for i in 1:(n-1)
        val = arr[i] ⊻ arr[i+1]
        minXOR = min(minXOR, val)
    end

    return minXOR
end

print("How many numbers are present in the array? ")
n = readline()
n = parse(Int, n)
if (n <= 0)
    println("No numbers are present in the array!!!")
    exit()
end
arr = Int[]
print("Enter the numbers: ")
arr = [parse(Int, num) for num in split(readline())]
res = minimumXOR(arr, n)
print("The minimum XOR value present in the given array is $res")

"""
SAMPLE INPUT AND OUTPUT

SAMPLE I

How many numbers are present in the array? 8
Enter the numbers: 25 45 36 458 651 89 52 95
The minimum XOR value present in the given array is 9

SAMPLE II

How many numbers are present in the array? 5
Enter the numbers: 5 4 3 2 1
The minimum XOR value present in the given array is 1

"""


# ---
# title: 166. Fraction to Recurring Decimal
# id: problem166
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Hash Table, Math
# link: <https://leetcode.com/problems/fraction-to-recurring-decimal/description/>
# hidden: true
# ---
#
# Given two integers representing the `numerator` and `denominator` of a
# fraction, return _the fraction in string format_.
#
# If the fractional part is repeating, enclose the repeating part in
# parentheses.
#
# If multiple answers are possible, return **any of them**.
#
# It is **guaranteed** that the length of the answer string is less than `104`
# for all the given inputs.
#
#
#
# **Example 1:**
#
#
#
#     Input: numerator = 1, denominator = 2
#     Output: "0.5"
#
#
# **Example 2:**
#
#
#
#     Input: numerator = 2, denominator = 1
#     Output: "2"
#
#
# **Example 3:**
#
#
#
#     Input: numerator = 2, denominator = 3
#     Output: "0.(6)"
#
#
# **Example 4:**
#
#
#
#     Input: numerator = 4, denominator = 333
#     Output: "0.(012)"
#
#
# **Example 5:**
#
#
#
#     Input: numerator = 1, denominator = 5
#     Output: "0.2"
#
#
#
#
# **Constraints:**
#
#   * `-231 <= numerator, denominator <= 231 - 1`
#   * `denominator != 0`
#
#
## @lc code=start
using LeetCode

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


# ------------------------------------------------------------------- #
# CES Producer
# ------------------------------------------------------------------- #
immutable CESProducer <: AbstractProducer
    η::Float64
    ν::Float64
end

# call(p::CESProducer, k, l) = (p.η*k.^p.ν + (1-p.η)*l.^(p.ν)).^(1./p.ν)
Base.produce(p::CESProducer, k, l) = (p.η*k.^p.ν + (1-p.η)*l.^(p.ν)).^(1./p.ν)
f_k(p::CESProducer, k, l) = p.η * (produce(p, k, l) ./ k).^(1.-p.ν)

# ------------------------------------------------------------------- #
# Cobb-Douglas Producer
# ------------------------------------------------------------------- #
immutable CobbDouglassProducer <: AbstractProducer
    α_k::Float64
    α_l::Float64
end

is_crs(i::CobbDouglassProducer) = i.α_k == 1. - i.α_l


struct ShamirSecretSharing
    prime::BigInt
end

ShamirSecretSharing() = ShamirSecretSharing(BigInt(2)^127 - 1)

"""
n is the number of shares.
k is the threshold.

"""
function Base.split(scheme::ShamirSecretSharing, secret::BigInt, n::Int64, k::Int64)
    prime = scheme.prime
    polynomial = [secret]
    for i in 2:k
        push!(polynomial, rand(RandomDevice(), 0:(prime-1)))
    end

    points = []
    for i in 1:n
        push!(points, (i, eval_poly(polynomial, i, prime)))
    end
    return points
end

function Base.join(scheme::ShamirSecretSharing, points)
    prime = scheme.prime
    xs, ys = zip(points...)
    return lagrange_interpolate(0, [xs...], [ys...], prime)
end


# https://juliasmoothoptimizers.github.io/ADNLPModels.jl/stable/tutorial/

using ADNLPModels
using NLPModelsIpopt

f(x) = (x[1] - 1)^2 + 100*(x[2] - x[1]^2)^2

x0 = [-1.2; 1.0]
uvar = [10.0; 10.0]
lvar = [-10.0; -10.0]

c(x) = [x[1]^2 + x[2]^2; x[1]*x[2]]
ucon = [0.75; 0.3]
lcon = [0.0; 0.0]

nlp = ADNLPModel(f, x0, lvar, uvar, c, lcon, ucon)

#output = ipopt(nlp, print_level=0)
output = ipopt(nlp)

println(output.objective)
println(output.solution)


function Green(xr,xz,yr,yz; derivative=0)
    #=
    Fundamental solution (Green function) and derivatives of the Grad-Shafranov Delta^* operator
    -Delta_x^* G(x,y)=delta(x-y)
    y=(yr,yz) is the source point (a filament with unit current)
    x=(xr,xz) is the evaluation point
    Derivatives from Itagaki, Engineering Analysis with Boundary Elements, 33 (2009) 845-857

    derivative=0 only Green function value
    derivative=1 + gradient wrt to x and y
    derivative=2 + second order cross derivatives (see return outputs below)
    =#

    mu0=4*pi*10^-7
    #modulus at points x and y
    k_xy=sqrt(4*xr*yr/((xr+yr)^2+(xz-yz)^2))

    #Complete elliptic integrals of first and second kind
    K_xy,E_xy=Elliptic.ellipke(k_xy^2)

    #Green function
    G_xy = (mu0/pi*((xr*yr)^(1/2))/k_xy)*((1-0.5*k_xy^2)*K_xy - E_xy)

    #println("derivative=",derivative)

    if(derivative==0)
        return (G_xy=G_xy,)
        #return G_xy
    end

    if(derivative>0)
        # gradient with respect to y
        dyrG_xy = (mu0/(2*pi))*((yr   )/(sqrt( (xr+yr)^2 + (xz-yz)^2 )))*(K_xy - ((yr^2-xr^2+(xz-yz)^2 )/( (xr-yr)^2 + (xz-yz)^2))*E_xy)
        dyzG_xy = (mu0/(2*pi))*((yz-xz)/(sqrt( (xr+yr)^2 + (xz-yz)^2 )))*(K_xy - ((yr^2+xr^2+(xz-yz)^2 )/( (xr-yr)^2 + (xz-yz)^2))*E_xy)

        # gradient with respect to x
        dxrG_xy = (mu0/(2*pi))*((xr   )/(sqrt( (xr+yr)^2 + (xz-yz)^2 )))*(K_xy - ((xr^2-yr^2+(xz-yz)^2 )/( (xr-yr)^2 + (xz-yz)^2))*E_xy)
        dxzG_xy = (mu0/(2*pi))*((xz-yz)/(sqrt( (xr+yr)^2 + (xz-yz)^2 )))*(K_xy - ((xr^2+yr^2+(xz-yz)^2 )/( (xr-yr)^2 + (xz-yz)^2))*E_xy)

        if(derivative==1)
            #return G_xy, dyrG_xy, dyzG_xy, dxrG_xy, dxzG_xy
            return (G_xy=G_xy, dyrG_xy=dyrG_xy, dyzG_xy=dyzG_xy, dxrG_xy=dxrG_xy, dxzG_xy=dxzG_xy)
        end

    end

    if(derivative==2)

        # second order cross derivatives
        dxrdyrG_xy = -((xr+yr)/((xr+yr)^2+(xz-yz)^2))*dxrG_xy +
            (mu0/2*pi)*xr*(1/((xr+yr)^2+(xz-yz)^2)^(1/2))*(1/((xr-yr)^2+(xz-yz)^2))*
            ( ((xr^2-yr^2+(xz-yz)^2)/((xr+yr)^2+(xz-yz)^2))*((xr-yr)*K_xy+(xr+yr)*E_xy) -2*xr*E_xy + ((4*yr*(xz-yz)^2)/((xr-yr)^2+(xz-yz)^2))*E_xy )

        dxzdyrG_xy = -((xr+yr)/((xr+yr)^2+(xz-yz)^2))*dxzG_xy +
            (mu0/2*pi)*xr*(xz-yz)*(1/((xr-yr)^2+(xz-yz)^2)^(1/2))*(1/((xr-yr)^2+(xz-yz)^2))*
            ( ((xr^2-yr^2+(xz-yz)^2)/((xr+yr)^2+(xz-yz)^2))*(K_xy+E_xy) -2*E_xy - ((4*yr*(xr-yr))/((xr-yr)^2+(xz-yz)^2))*E_xy )

        dxrdyzG_xy = ((xz-yz)/((xr+yr)^2+(xz-yz)^2))*dxrG_xy +
            (mu0/pi)*yr*xr*(xz-yz)*
            (1/((xr+yr)^2+(xz-yz)^2)^(1/2))*(1/((xr-yr)^2+(xz-yz)^2))*
            ( (1/((xr+yr)^2+(xz-yz)^2))*((xr-yr)*K_xy+(xr+yr)*E_xy) +
            ((-2*(xr-yr))/((xr-yr)^2+(xz-yz)^2))*E_xy )

        dxzdyzG_xy = (((-1)/(xz-yz))+((xz-yz)/((xr+yr)^2+(xz-yz)^2)))*dxzG_xy +
            (mu0/pi)*yr*xr*(xz-yz)^2 *
            (1/((xr+yr)^2+(xz-yz)^2)^(1/2))*(1/((xr-yr)^2+(xz-yz)^2))*
            ( (1/((xr+yr)^2+(xz-yz)^2))*(K_xy+E_xy) +
            (-2/((xr-yr)^2+(xz-yz)^2))*E_xy )

        #return G_xy, dyrG_xy, dyzG_xy, dxrG_xy, dxzG_xy, dxrdyrG_xy, dxzdyrG_xy, dxrdyzG_xy, dxzdyzG_xy
        return (G_xy=G_xy, dyrG_xy=dyrG_xy, dyzG_xy=dyzG_xy, dxrG_xy=dxrG_xy, dxzG_xy=dxzG_xy, dxrdyrG_xy=dxrdyrG_xy, dxzdyrG_xy=dxzdyrG_xy, dxrdyzG_xy=dxrdyzG_xy, dxzdyzG_xy=dxzdyzG_xy)
    end

end


function nlp_cvx_206_010(
    optimizer,
    objective_tol,
    primal_tol,
    dual_tol,
    termination_target = TERMINATION_TARGET_LOCAL,
    primal_target = PRIMAL_TARGET_LOCAL,
)
    # Test Goals:
    # - linear objective
    # - intersection convex quadratic constraints
    # - power cones
    # Variants
    #   010 - intersection set

    model = Model(optimizer)

    # NOTE, starting any of these at 0.0 will segfault libcoinmumps
    @variable(model, x >= 0, start = 0.1)
    @variable(model, y >= 0, start = 0.1)
    @variable(model, z >= 0, start = 0.1)

    @objective(model, Max, 2 * x + y + z)
    @NLconstraint(model, z <= x^(0.3) * y^(0.7))
    @NLconstraint(model, x <= z^(0.7) * y^(0.3))
    @NLconstraint(model, x^2 + y^2 <= z + 1)

    optimize!(model)

    check_status(model, FEASIBLE_PROBLEM, termination_target, primal_target)
    check_objective(model, 4.0, tol = objective_tol)
    return check_solution([x, y, z], [1, 1, 1], tol = primal_tol)
end


# This function implements Kalman Filter on a state-space model of the form:
# α_{t} = T * α_{t-1} + η_{t}
# y_{t} = Z * α_{t} + ϵ_{t}
# E[η_{t}η'_{t}] = Q, E[ϵ_{t}ϵ'_{t}] = H

function KalmanFilter(T::Matrix, Z::Matrix, Q::Matrix, H::Matrix, n::Integer, α0, y)

    # Initialize state variables
    αhat = repeat([zeros(size(α0))], n);
    αhat[1] = T*α0;

    yhat = repeat([zeros(size(y[1]))], n);
    v = copy(yhat);

    P0 = Q * Q;
    d = 10;
    tol = 10^(-8);
    maxiter = 1000;
    iter = 1;
    while d > tol && iter <= maxiter
        P1 = T * P0 * T' + Q;
        d = maximum(abs.(P0 - P1));
        P0 = P1;
        iter = iter + 1;
    end

    P = repeat([zeros(size(Q))], n);
    P[1] = T * P0 * T' + Q;

    F = repeat([zeros(size(H))], n)
    G = repeat([zeros(size(Z * Q))], n)
    K = repeat([zeros(size(T * G[1]' * ones(size(H))))], n)

    for i in 1:n-1
        yhat[i] = Z * αhat[i];
        v[i] = y[i] - yhat[i];

        F[i] = Z * P[i] * Z' + H;
        G[i] = Z * P[i];
        K[i] = T * G[i]' * inv(F[i]);

        αhat[i + 1] = T * αhat[i] + K[i] * v[i];
        P[i + 1] = T * (P[i] - G[i]' * inv(F[i]) * G[i]) * T' + Q;
    end

    yhat[n] = Z * αhat[n];
    v[n] = y[n] - yhat[n];

    F[n] = Z * P[n] * Z' + H;
    G[n] = Z * P[n];
    K[n] = T * G[n]' * inv(F[n]);

    return αhat, v, yhat, P, F, G, K
end


# A tolerance graph is a graph whose vertices are represented by pairs
# (J,t) where J is a closed interval and t is a real number. Vertices
# are adjacent if and only if length(J * JJ) >= min(t,tt).

export ToleranceGraph

"""
`ToleranceGraph(Jlist, tlist)` creates a tolerance graph where `Jlist`
is a list of intervals and `tlist` is a list of tolerances.
"""
function ToleranceGraph(
    Jlist::Vector{ClosedInterval{S}},
    tlist::Vector{T},
) where {S<:Real,T<:Real}
    n = length(Jlist)
    G = IntGraph(n)

    for i = 1:n-1
        J = Jlist[i]
        t = tlist[i]
        for j = i+1:n
            JJ = Jlist[j]
            tt = tlist[j]

            K = J * JJ
            if !isempty(K) && length(K) >= min(t, tt)
                add!(G, i, j)
            end
        end
    end
    return G
end

"""
`ToleranceGraph(f)` creates a tolerance graph where `f` is a `Dict`
mapping vertices to pairs `(J,t)` where `J` is a `ClosedInterval` and
`t` is a `Real` tolerance.
"""
function ToleranceGraph(f::Dict{X,Tuple{ClosedInterval{S},T}}) where {S<:Real,T<:Real,X}
    vtcs = collect(keys(f))
    G = SimpleGraph{X}()
    for v in vtcs
        add!(G, v)
    end

    n = length(vtcs)
    for i = 1:n-1
        v = vtcs[i]
        J, t = f[v]
        for j = i+1:n
            w = vtcs[j]
            JJ, tt = f[w]

            K = J * JJ
            if !isempty(K) && length(K) >= min(t, tt)
                add!(G, v, w)
            end
        end
    end
    return G
end


# calculate varianve of sensor data

module CalcVariance
    using DataFrames, CSV, Statistics

    function main()
        data_path = joinpath(split(@__FILE__, "src")[1], "data/sensor_data_200.txt")
        df_200_mm = CSV.read(data_path, DataFrame,
                             header=["date", "time", "ir", "lidar"],
                             delim=' ')

        # calculate from definition
        zs = df_200_mm.lidar # observation
        mean_def = sum(zs) / length(zs) # mean
        diff_square = [(z - mean_def)^2 for z in zs]

        sampling_var = sum(diff_square) / length(zs) # sampling variance
        unbiased_var = sum(diff_square) / (length(zs) - 1) # unbiased variance

        println("Sampling Variance = $sampling_var")
        println("Unbiased Variance = $unbiased_var")

        # calculate by Statistics
        stats_sampling_var = Statistics.var(zs, corrected=false)
        stats_unbiased_var = Statistics.var(zs, corrected=true)

        println("Sampling Variance by Statistics = $stats_sampling_var")
        println("Unbiased Variance by Statistics = $stats_unbiased_var")
    end
end


# Linear Isotropic Covariance Function

@doc """
# Description
Constructor for the isotropic linear kernel (covariance)

k(x,x') = xᵀx'/ℓ²
# Arguments:
* `ll::Float64`: Log of the length scale ℓ
""" ->
type LinIso <: Kernel
    ll::Float64      # Log of Length scale
    LinIso(ll::Float64) = new(ll)
end

function cov(lin::LinIso, x::Vector{Float64}, y::Vector{Float64})
    ell = exp(lin.ll)
    K = dot(x,y)/ell^2
    return K
end

function cov(lin::LinIso, X::Matrix{Float64}, data::EmptyData)
    ell = exp(lin.ll)
    return ((1/ell^2).*X') * X
end

get_params(lin::LinIso) = Float64[lin.ll]
get_param_names(lin::LinIso) = [:ll]
num_params(lin::LinIso) = 1

function set_params!(lin::LinIso, hyp::Vector{Float64})
    length(hyp) == 1 || throw(ArgumentError("Linear isotropic kernel only has one parameter"))
    lin.ll = hyp[1]
end

function grad_kern(lin::LinIso, x::Vector{Float64}, y::Vector{Float64})
    ell = exp(lin.ll)

    dK_ell = -2.0*dot(x,y)/ell^2
    dK_theta = [dK_ell]
    return dK_theta
end

function grad_stack!(stack::AbstractArray, lin::LinIso, X::Matrix{Float64}, data::EmptyData)
    ell = exp(lin.ll)
    stack[:,:,1] = ((-2.0/ell^2).*X') * X
end


function CholeskyL(A::Array)

  #A = the matrix of coefficients 'A' must be squared

    m,n =size(A)

    if m != n
        error("Matrix must be squared")
    end

    # System of equations
    L=zeros(m,m)

    L[1,1]=A[1,1]^0.5

    for k=2:m

    for i=1:k-1
       L[k,i]=(A[k,i]-(L[i,1:i-1]'*L[k,1:i-1]))/(L[i,i])

    end
          L[k,k]=(A[k,k]-(L[k,1:k-1]'*L[k,1:k-1]))^0.5
    end

    return L

end


module FeynmanKleinert

# Bring out the major leagues... https://www.youtube.com/watch?v=_E6DDktoPhg
using Optim

using QuadGK # For numerical integration

export K,Va2,Wtilde,verboseWtilde,W # export all the things.

"Integrand of (4) in Feynman and Kleinert"
K(xp,x,a2,V)=1/sqrt(2*π*a2)*exp(-(x-xp)^2/(2*a2))*V(xp)
"(4) in Feynman and Kleinert"
Va2(x,a2,V)=quadgk(xp->K(xp,x,a2,V), -Inf, +Inf) #, reltol=0, abstol=1e-5)

# (5) naively as written, in Feynman and Kleinert
#Wtilde(x0,a2,Ω,g,β)=(1/β) * log( sinh(β*Ω/2)/(β*Ω/2) ) - (Ω^2/2) * a2 + Va2(x0,a2,g)[1]
# Large β limit; by taking sinh(x)=e^x-e^-x , and then dropping e^-x
#Wtilde(x0,a2,Ω,g,β)=(1/β) * (β*Ω/2 - log(β*Ω/2)) - (Ω^2/2) * a2 + Va2(x0,a2,g)[1]

# Branch depending on β; for accuracy + lack of infinities
"""
    Wtilde(x0,a2,Ω,V,β)

(5) in Feynman and Kleinert. Switches depending on D=β*Ω/2 to use either an
exponential approximation for sinh(x) (D>7.5) or an explicit evaluation and
then take logarithms. Without doing this you get collapse to infinity for large
β.
"""
function Wtilde(x0,a2,Ω,V,β)
    D=β*Ω/2
    if D>7.5
        F=(1/β) * (D - log(2*D)) # Large limit;  by taking sinh(x)=e^x-e^-x , and then dropping e^-x
    else
        F=(1/β)*( log(sinh(D)/D) ) # explicit evaluation, naively as written in (5)
    end
    V0=Va2(x0,a2,V)[1]
    mid=(Ω^2/2) * a2
    return F-mid+V0
end

"""
   verboseWtilde(x0,a2,Ω,g,β)

Expanded version of Wtilde, reporting the decomposition - this was used to explain what was going wrong
"""
function verboseWtilde(x0,a2,Ω,g,β)
    D=β*Ω/2
    println("D=β*Ω/2 = $D")
    F= (1/β)*( log(sinh(D)) - log(D) )
    #F= (1/β)*( (log(2D)+D^2/6+D^4/180) - log(D) ) # generalised Puiseux series
    # Large D limit, through sinh(D) --> e^D [through away e^-D part]
    F = (1/β) * (D - log(2*D))
    V=Va2(x0,a2,g)[1]
    mid=(Ω^2/2) * a2
    println("Wtilde(x0=$x0,a2=$a2,Ω=$Ω,g=$g,β=$β) \n\t= F - mid + V")
    println("\t= $F - $mid + $V = ",F-mid+V)
    return F-mid+V
end

"""
    W(x0,g,β)

    Implementing optimisation / minimisation described in Feynman-Kleinert (6)
    Calculates optimal W for x0, with given g and Beta
    Use's (7) (a2 in terms of omega) to fix numerical noise from trying to do
    it directly...
"""
function W(x0,g,β)
    #println("W(x0=$x0,g=$g,β=$β)")

    a2(Ω)=1/(β*Ω^2)*( (β*Ω)/2 * coth(β*Ω/2)-1 ) #(7)
    myf(x)=Wtilde(x0,a2(x[1]),x[1],g,β)

    # Define bounds on solution
    lower=[0.0]
    upper=[5.0]
    initial=[1.0]

    res=optimize(OnceDifferentiable(myf, initial; autodiff = :forward), initial, lower, upper, Fminbox();
    optimizer=GradientDescent)

    minimum=Optim.minimum(res)
    Ω=Optim.minimizer(res)[1]
    mya2=a2(Ω)[1]
    #println("Optimised for W(x0=$x0,g=$g,β=$β). \n\tΩ=$Ω, a2=$mya2, minimum=$minimum.")
    #show(res)
    return minimum
end

end # module


# A comparison between ℓ_0 and ℓ_1 trend filtering

using Plots
using Convex
using Mosek
using EllZeroTrendFiltering

g = snp500_data()[1:300]

N = length(g)

## ℓ_1 trend filtering, see "ℓ_1 Trend Filtering", Kim et al. (2009)
H =  spdiagm(0=>ones(N-2), 1=>-2*ones(N-2), 2=>ones(N-2))

x = Variable(N)

λ = 2.0
problem = minimize(sumsquares(x - g) + λ * norm(H*x, 1))

solve!(problem, Mosek.Optimizer)
#solve!(problem, SCS.Optimizer)
println(problem.status)

x_l1 = evaluate(x)[:]
f_l1 = sum((x_l1 - g).^2)

## ℓ_0 trend filtering
ζ = 0.02
I_l0, Y_l0, f_l0 = fit_pwl_regularized(g, ζ)

plot(g, label="SP500", size=(1000,550));
plot!(x_l1, lw=2.5, label="l1 reg. lambda = $λ");
plot!(I_l0, Y_l0, lw=2.5, label="l0 reg. zeta = $ζ")

# plot(H*x.value);
# plot!(I_l0[2:end], 0.1*diff(Y_l0) ./ diff(I_l0), m=:o, lw=0)
#


# Generate random unitaries and density matrices for testing

export random_unitary, random_densitymatrix

""" `U = random_unitary(dim, real=true/false, complex=true/false)`

Generate a random unitary of dimension *dim*. Depending on *real* and *complex* the matrix has real and complex parts.

"""

function random_unitary(dim::Integer; real::Bool = true, complex::Bool = false)

  	@assert dim > 1 "Dimension must be at least 2."

  	generated = randn(dim, dim)

  	if complex || !real
    		generated += im * randn(dim, dim)
  	end

	# Compute the QR decomposition
  	(Q, R) = qr(generated)

	# Get the upper diagonal part of R
  	F = diagm(diag(R))

  	R = sign(diagm(diag(R)))
  	R[find((x)->  x == 0, R)] = 1
  	F = diagm(diag(R))

  	return Q*F
end

""" `rho = random_densitymatrix(dim)`

Generate a random density matrix in dimension *dim*.

"""

function random_densitymatrix(dim::Integer)

	@assert dim > 1 "Dimension must be at least 2."

        # Draw random numbers
	rList = rand(dim);

	# Normalize to form a distribution
	s = sum(rList);
	nList = rList/s;

	# Construct a matrix with the numbers on the diagonal
	M = diagm(nList);

	# Draw a random unitary
   	U = random_unitary(dim, real = true, complex = true)

	# Roate the diagonal matrix
	return(U*M*U');
end


@testset "Minimum Volume Ellipsoid" begin
    X = [
        -1 -1 1 2
        1 -1 -1 2
    ]

    @testset "Centered" begin
        ϵ = minimum_volume_ellipsoid(X, 1e-10; centered=true)

        @test ϵ.x == [0, 0]
        @test ϵ.H ≈ [5/8 -3/8; -3/8 5/8] atol = 1e-10
    end

    @testset "Not Centered" begin
        ϵ = minimum_volume_ellipsoid(X, 1e-10)

        @test ϵ.x ≈ [0.5, 0.5] atol = 1e-10
        @test ϵ.H ≈ [2/3 -2/9; -2/9 2/3] atol = 1e-10
    end
end


# All matrices are DxN, where N is the number of positions and D is the dimensionality

# Here, P is the probe (to be rotated) and Q is the refereence
# https://en.wikipedia.org/wiki/Kabsch_algorithm
# This has been generalized to support weighted points:
# https://igl.ethz.ch/projects/ARAP/svd_rot.pdf

# assuming P and Q are already centered at the origin
# returns the rotation for alignment
function kabsch_centered(P,Q,w)
    @assert size(P) == size(Q)
    W = diagm(w/sum(w)) # here, the weights are assumed to sum to 1
    H = P*W*Q'
    D = Matrix{Float64}(I,size(H,1), size(H,2))
    U,Σ,V = svd(H)
    D[end] = sign(det(V*U'))
    return LinearMap(V * D * U')
end

# transform DxN matrices
function (tform::Translation)(A::AbstractMatrix)
    return hcat([tform(A[:,i]) for i=1:size(A,2)]...)
end

# P and Q are not necessarily centered
# returns the transformation for alignment
function kabsch(P, Q, w::AbstractVector=ones(size(P,2)))
    @assert !any(w.<0) && sum(w)>0
    wn = w/sum(w) # weights should sum to 1 for computing the centroid
    centerP, centerQ = center_translation(P,wn), center_translation(Q,wn)
    R = kabsch_centered(centerP(P), centerQ(Q), wn)
    return inv(centerQ) ∘ R ∘ centerP
end

function kabsch(P,Q,matches::AbstractVector{<:Tuple{Int,Int}},wp=ones(size(P,2)),wq=ones(size(Q,2)))
    matchedP, matchedQ = matched_points(P,Q,matches)
    w = [wp[i]*wq[j] for (i,j) in matches]
    return kabsch(matchedP, matchedQ, w)
end

# centroid of positions in A, weighted by weights in w (assumed to sum to 1)
centroid(A, w=fill(1/size(A,2), size(A,2))) = A*w

# translation moving centroid to origin
center_translation(A, w=fill(1/size(A,2), size(A,2))) = Translation(-centroid(A,w))


function rosenbrock(x, g=nothing)
    n = length(x)
    f = 0.0
    for i=1:(n-1)
        f += (1-x[i])^2 + 100*(x[i+1]-x[i]^2)^2
    end
    if g != nothing
        for i=1:n
            g[i] = ((i==n ? 0 : -2*(1-x[i]) - 400*(x[i+1]-x[i]^2)*x[i]) +
                    (i==1 ? 0 : 200*(x[i]-x[i-1]^2)))
        end
    end
    return f
end

function gtest(x=randn(10);eps=1e-5)
    g = similar(x)
    @show f0 = rosenbrock(x,g)
    for i=1:length(x)
        xi = x[i]
        x[i] = xi+eps; f1 = rosenbrock(x)
        x[i] = xi-eps; f2 = rosenbrock(x)
        x[i] = xi; df = (f1-f2)/(2*eps)
        @show (i,g[i],df)
    end
end

using Base.LinAlg

function eq10(f, x0; gtol=0.95, gamma=1e-8)
    g0, x1, rb, rr, r = [ zeros(x0) for i=1:5 ]
    f0 = f(x0, g0)
    n0 = vecnorm(g0)
    nf = cosa = 0
    while cosa < gtol
        f1 = f(axpy!(gamma, randn!(r), copy!(x1,x0)))
        nf += 1
        b = f1 - f0
        axpy!(b, r, rb)
        axpy!(1, r.*r, rr)
        g1 = rb ./ (gamma * rr)
        n1 = vecnorm(g1)
        cosa = dot(g0,g1)/(n0*n1)
    end
    return nf
end


# Bogumił Kamiński, 2021

# Codes for chapter 5

# Code for section 5.1.1

x = [1 2 3]
y = [1, 2, 3]
x * y

a = [1, 2, 3]
b = [4, 5, 6]
a * b

a .* b

map(*, a, b)
[a[i] * b[i] for i in eachindex(a, b)]

eachindex(a, b)

eachindex([1, 2, 3], [4, 5])

map(*, [1, 2, 3], [4, 5])

[1, 2, 3] .* [4, 5]

# Code for section 5.1.2

[1, 2, 3] .^ [2]

[1, 2, 3] .^ 2

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .* [1 2 3 4 5 6 7 8 9 10]

["x", "y", "z"] .=> [sum minimum maximum]

abs.([1, -2, 3, -4])

abs([1, 2, 3])

string(1, 2, 3)

string.("x", 1:10)

f(i::Int) = string("got integer ", i)
f(s::String) = string("got string ", s)
f.([1, "1"])

# Code for section 5.1.3

in(1, [1, 2, 3])
in(4, [1, 2, 3])

1 in [1, 2, 3]
4 in [1, 2, 3]

in([1, 3, 5, 7, 9], [1, 2, 3, 4])

in([1, 3, 5, 7, 9], [1, 2, 3, 4, [1, 3, 5, 7, 9]])

in.([1, 3, 5, 7, 9], [1, 2, 3, 4])

in.([1, 3, 5, 7, 9], Ref([1, 2, 3, 4]))

isodd.([1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .+ [1 2 3 4 5 6 7 8 9 10])

Matrix{Any}(isodd.([1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .* [1 2 3 4 5 6 7 8 9 10]))

# Code for section 5.1.4

aq = [10.0   8.04  10.0  9.14  10.0   7.46   8.0   6.58
       8.0   6.95   8.0  8.14   8.0   6.77   8.0   5.76
      13.0   7.58  13.0  8.74  13.0  12.74   8.0   7.71
       9.0   8.81   9.0  8.77   9.0   7.11   8.0   8.84
      11.0   8.33  11.0  9.26  11.0   7.81   8.0   8.47
      14.0   9.96  14.0  8.1   14.0   8.84   8.0   7.04
       6.0   7.24   6.0  6.13   6.0   6.08   8.0   5.25
       4.0   4.26   4.0  3.1    4.0   5.39  19.0  12.50
      12.0  10.84  12.0  9.13  12.0   8.15   8.0   5.56
       7.0   4.82   7.0  7.26   7.0   6.42   8.0   7.91
       5.0   5.68   5.0  4.74   5.0   5.73   8.0   6.89]
using Statistics

mean.(eachcol(aq))

mean(eachcol(aq))

function R²(x, y)
    X = [ones(11) x]
    model = X \ y
    prediction = X * model
    error = y - prediction
    SS_res = sum(v -> v ^ 2, error)
    mean_y = mean(y)
    SS_tot = sum(v -> (v - mean_y) ^ 2, y)
    return 1 - SS_res / SS_tot
end

function R²(x, y)
    X = [ones(11) x]
    model = X \ y
    prediction = X * model
    SS_res = sum((y .- prediction) .^ 2)
    SS_tot = sum((y .- mean(y)) .^ 2)
    return 1 - SS_res / SS_tot
end

# Code for section 5.2

[]
Dict()

Float64[1, 2, 3]

Dict{UInt8, Float64}(0 => 0, 1 => 1)

UInt32(200)

Real[1, 1.0, 0x3]

v1 = Any[1, 2, 3]
eltype(v1)
v2 = Float64[1, 2, 3]
eltype(v2)
v3 = [1, 2, 3]
eltype(v3)
d1 = Dict()
eltype(d1)
d2 = Dict(1 => 2, 3 => 4)
eltype(d2)

p = 1 => 2
typeof(p)

# Code for section 5.2.1

[1, 2, 3] isa AbstractVector{Int}
[1, 2, 3] isa AbstractVector{Real}

AbstractVector{<:Real} == AbstractVector{T} where T<:Real

# Code for section 5.2.2

using Statistics
function ourcov(x::AbstractVector{<:Real},
                y::AbstractVector{<:Real})
    len = length(x)
    @assert len == length(y) > 0
    return sum((x .- mean(x)) .* (y .- mean(y))) / (len - 1)
end

ourcov(1:4, [1.0, 3.0, 2.0, 4.0])
cov(1:4, [1.0, 3.0, 2.0, 4.0])

ourcov(1:4, Any[1.0, 3.0, 2.0, 4.0])

x = Any[1, 2, 3]
identity.(x)
y = Any[1, 2.0]
identity.(y)

# Code for section 5.3

using Random
Random.seed!(1234);
cluster1 = randn(100, 5) .- 1
cluster2 = randn(100, 5) .+ 1

data5 = vcat(cluster1, cluster2)

using PyCall
manifold = pyimport("sklearn.manifold")

# Optional code to run if the pyimport("sklearn.manifold") fails
# There is no need to run it if the above operation worked
using Conda
Conda.add("scikit-learn")

tsne = manifold.TSNE(n_components=2, init="random",
                     learning_rate="auto", random_state=1234)
data2 = tsne.fit_transform(data5)

using Plots
scatter(data2[:, 1], data2[:, 2];
        color=[fill("black", 100); fill("gold", 100)],
        legend=false)


module BernsteinEllipses

export jouk, ijouk

"""
    rsmo(x)

Evaluate `√(x^2-1)` with branch cut along `[-1,1]`.

The function name is the abbreviation of "root of square minus one".
"""
rsmo(x) = rsmo(float(x))
rsmo(x::Union{T,Complex{T}}) where {T<:AbstractFloat} = ifelse(!signbit(real(x)),1,-1)*sqrt(x^2-1)

"""
    jouk(z)

Joukowsky map `(z+z^-1)/2`.
"""
jouk(z) = (z+inv(z))/2

"""
    ijouk(x; halfplane=Val(false), branch=Val(true)) -> z

Inverse Joukowsky map `z = x ± √(x^2-1)`. The branch to evaluate is seleced as
follows.

+-----------+---------------+----------------+
|           | `!halfplane`  |  `halfplane`   |
+-----------+---------------+----------------+
| ` branch` | `abs(z) >= 1` | `imag(z) >= 0` |
| `!branch` | `abs(z) <= 1` | `imag(z) <= 0` |
+-----------+---------------+----------------+

"""
ijouk(x; halfplane=Val(false), branch=Val(true)) = ijouk(x,halfplane,branch)
ijouk(x,::Val{false},::Val{true} ) = x + rsmo(x)
ijouk(x,::Val{false},::Val{false}) = x - rsmo(x)
ijouk(x,::Val{true} ,::Val{true} ) = x + im*sqrt(1-x^2)
ijouk(x,::Val{true} ,::Val{false}) = x - im*sqrt(1-x^2)

"""
   radius(x; kwargs...) = abs(ijouk(x; kwargs...))
"""
radius(x; kwargs...) = abs(ijouk(x; kwargs...))

"""
   semimajor(x; kwargs...)

Semi-major axis of the Bernstein ellipse through `x`.

See [`ijouk`](@ref) regardings `kwargs`.
"""
semimajor(x; kwargs...) = (w = abs(ijouk(x; kwargs...)); (w+inv(w))/2)

"""
   semiminor(x; kwargs...)

Semi-minor axis of the Bernstein ellipse through `x`.

See [`ijouk`](@ref) regardings `kwargs`.
"""
semiminor(x; kwargs...) = (w = abs(ijouk(x; kwargs...)); (w-inv(w))/2)

"""
    semiminor(x,xr)

Imaginary component of the point in the upper half-plane
where the line `{ x̃ | real(x̃) = xr }` intersects the Bernstein
ellipse through `x`.
"""
function semiminor(x::Number,xr::Real; kwargs...)
    a = semimajor(x; kwargs...)
    b = semiminor(x; kwargs...)
    return b*sqrt(1 - (xr/a)^2)
end

end # module


using DynamicalSystems, Base.Test

println("\nTesting discrete system lyapunov exponents...")

@testset "Towel Map" begin
  @testset "lyapunovs" begin
    ds = Systems.towel()
    λ1 = lyapunovs(ds, 1e5)
    @test 0.42 < λ1[1] < 0.44
    @test 0.36 < λ1[2] < 0.38
    @test -3.4 < λ1[3] < -3.2
  end

  @testset "lyapunovs ForwardDiff" begin
    ds = Systems.towel()
    ds = DiscreteDS(ds.state, ds.eom)
    λ1 = lyapunovs(ds, 1e5)
    @test 0.42 < λ1[1] < 0.44
    @test 0.36 < λ1[2] < 0.38
    @test -3.4 < λ1[3] < -3.1
  end

  @testset "lyapunov" begin
    ds1 = Systems.towel()
    ds2 = DiscreteDS(ds1.state, ds1.eom)
    λ1 = lyapunov(ds1, 100000)
    λ2 = lyapunov(ds2, 100000)
    @test 0.42 < λ1[1] < 0.44
    @test 0.42 < λ2[1] < 0.44
    @test isapprox(λ1, λ2; rtol = 1e-3)
  end
end

@testset "Logistic Map" begin
  lg1 = Systems.logistic(0.1, r=4)
  lg2 = DiscreteDS1D(lg1.state, lg1.eom)
  lg3 = Systems.logistic(big(0.1), r=4)
  @test typeof(lg3.state) == BigFloat
  λ1 = lyapunov(lg1, 10000000; Ttr = 100)
  λ2 = lyapunovs(lg2, 10000000; Ttr = 100)
  λ3 = lyapunovs(lg3, 100000; Ttr = 100)
  @test typeof(λ3) == BigFloat
  @test isapprox(λ1, log(2); rtol = 1e-3)
  @test isapprox(λ2, log(2); rtol = 1e-3)
  @test isapprox(λ3, log(2); rtol = 1e-3)
end

@testset "Henon Map" begin
  @testset "lyapunovs" begin
    ds = Systems.henon()
    λ1 = lyapunovs(ds, 1e6)
    @test 0.418 < λ1[1] < 0.422
    @test -1.63 < λ1[2] < -1.61
  end

  @testset "lyapunovs ForwardDiff" begin
    ds = Systems.henon()
    ds = DiscreteDS(ds.state, ds.eom)
    λ1 = lyapunovs(ds, 1e6)
    @test 0.418 < λ1[1] < 0.422
    @test -1.63 < λ1[2] < -1.61
  end

  @testset "lyapunov" begin
    ds1 = Systems.henon()
    ds2 = DiscreteDS(ds1.state, ds1.eom)
    λ1 = lyapunov(ds1, 1000000)
    λ2 = lyapunov(ds2, 1000000)
    @test 0.418 < λ1[1] < 0.422
    @test 0.418 < λ2[1] < 0.422
    @test isapprox(λ1, λ2; rtol = 1e-3)
  end
end


module TestGegenbauer

export constant_ode, linear_ode, trig_ode

include("../src/CustomTypes.jl")
include("../src/Chebyshev.jl")
include("../src/Gegenbauer.jl")
using LinearAlgebra, SparseArrays
using SpecialFunctions: airyai
using Jacobi: jacobi
using .CustomTypes
import .Chebyshev as CH
import .Gegenbauer as GE

import Test: @test

const tolerance = 1e-8 ## tolerance we compare to

##============================================================

"""
Evaluate ODE u' = 0, with u(xmin) = c0.
"""
function constant_sol_ode(
      nx::myI,
      xmin::myF,
      xmax::myF,
      c0::myF
   )
   D1 = GE.compute_D(nx,tomyI(1))
   D1 = [transpose([(-1)^i for i=0:(nx-1)]);D1]

   f0 = [c0; [0.0 for i=1:nx]]

   numerical_sol = D1\f0
   analytic_sol  = [c0; [0.0 for i=1:(nx-1)]]

   println("Test: Gegenbauer constant solution ODE")
   @test norm(numerical_sol .- analytic_sol,1) < tolerance
   return nothing
end

"""
Evaluate ODE u'' = 0, with u(xmin) = c0, u'(xmax) = c1.
"""
function linear_sol_ode(
      nx::myI,
      xmin::myF,
      xmax::myF,
      c0::myF,
      c1::myF
   )
   D2 = GE.compute_D(nx,tomyI(2))
   D2 = [transpose([1      for i=0:(nx-1)]);D2]
   D2 = [transpose([(-1)^i for i=0:(nx-1)]);D2]

   f0 = [c0; c1; [0.0 for i=1:nx]]

   numerical_sol = D2\f0

   ## coordinate change
   numerical_sol[1] -= numerical_sol[2]*(xmax+xmin)/(xmax-xmin)
   numerical_sol[2] *= 2.0/(xmax-xmin)

   analytic_sol  = [
                    (c0*xmax - c1*xmin)/(xmax-xmin);
                    (c1-c0)/(xmax-xmin);
                    [0.0 for i=1:(nx-2)]
                   ]

   println("Test: Gegenbauer linear solution ODE")
   @test norm(numerical_sol .- analytic_sol,1) < tolerance
   return nothing
end

"""
Evaluate ODE u'' - x*u = 0, with u(xmin) = Airy(xmin), u(xmax) = Airy(xmax).
"""
function airy_sol_ode(
      nx::myI,
      xmin::myF,
      xmax::myF,
      om2::myF
   )
   D2  = GE.compute_D(nx,tomyI(2),xmin,xmax)
   X1  = GE.compute_MXp(nx,tomyI(2),tomyI(1),xmin,xmax)
   S0  = GE.compute_S(nx,tomyI(0))
   S1  = GE.compute_S(nx,tomyI(1))

   Op  = D2 - X1*S1*S0
   Op  = [transpose([1      for i=0:(nx-1)]);Op]
   Op  = [transpose([(-1)^i for i=0:(nx-1)]);Op]

   f0 = [airyai(xmin); airyai(xmax); [0.0 for i=1:nx]]

   numerical_sol = Op\f0

   pts = CH.cheb_pts(xmin,xmax,nx)
   analytic_sol = CH.to_cheb(airyai.(pts))

   println("Test: Gegenbauer Airy function solution ODE")
   @test norm(numerical_sol .- analytic_sol,1) < tolerance
   return nothing
end

"""
Evaluate ODE Jacobi equation
"""
function jacobi_sol_ode(
      nx::myI,
      n::myI,
      a::myF,
      b::myF
   )
   xmin = tomyF(-1)
   xmax = tomyF(+1)

   f(x) = jacobi(x, n, a, b)

   Id  = sparse(I,nx,nx)
   D1  = GE.compute_D(nx,tomyI(1),xmin,xmax)
   D2  = GE.compute_D(nx,tomyI(2),xmin,xmax)
   X1  = GE.compute_MXp(nx,tomyI(2),tomyI(1),xmin,xmax)
   X2  = GE.compute_MXp(nx,tomyI(2),tomyI(2),xmin,xmax)
   S0  = GE.compute_S(nx,tomyI(0))
   S1  = GE.compute_S(nx,tomyI(1))

   println(Id)
   println(X2)
   println(D2)
   println((Id .- X2)*D2 .+ ((b-a)*Id .- (a+b+2.0)*X1)*S1*D1 .+ n*(n+a+b+1)*S1*S0)

   Op  = (Id .- X2)*D2 .+ ((b-a)*Id .- (a+b+2.0)*X1)*S1*D1 #+ n*(n+a+b+1)*S1*S0

   println(Op)
   println(eigen(Matrix(Op)).values)

   f0 = [0.0 for i=1:nx]

   numerical_sol = Op\f0

   pts = CH.cheb_pts(xmin,xmax,nx)
   analytic_sol  = f.(pts)
   analytic_sol  = CH.to_cheb(analytic_sol)

   println(f0)
   println(Op)
   println(numerical_sol)
   println(analytic_sol)

   println("Test: Gegenbauer Jacobi function solution ODE")
   @test norm(numerical_sol .- analytic_sol,1) < tolerance
   return nothing
end

end


using StatsModelComparisons, StanSample
using CSV, Printf, StatsPlots

ProjDir = @__DIR__

df = CSV.read(joinpath(ProjDir, "roachdata.csv"), DataFrame)
df.roach1 = df.roach1 / 100

roaches1_stan = "
data {
  int<lower=0> N;
  int<lower=0> K;
  vector[N] exposure2;
  vector[N] roach1;
  vector[N] senior;
  vector[N] treatment;
  int y[N];
}
transformed data {
  vector[N] log_expo;
  log_expo = log(exposure2);
}
parameters {
  vector[K] beta;
}
transformed parameters {
   vector[N] eta;
   eta = log_expo + beta[1] + beta[2] * roach1 + beta[3] * treatment
                + beta[4] * senior;
}
model {
  y ~ poisson_log(eta);
}
generated quantities {
  vector[N] log_lik;
  for (i in 1:N)
    log_lik[i] = poisson_log_lpmf(y[i] | eta[i]);
}
";

data = (N = size(df, 1), K = 4, y = Int.(df.y), roach1=df.roach1,
  exposure2=df.exposure2, senior=df.senior, treatment=df.treatment)
n = size(df, 1)

tmpdir = mktempdir()
sm1 = SampleModel("roaches1", roaches1_stan; tmpdir)
rc1 = stan_sample(sm1; data)

if success(rc1)
  stan_summary(sm1, true)
  nt1 = read_samples(sm1)

  # Compute LOO and standard error
  log_lik1 = nt1.log_lik'
  loo1, loos1, pk1 = psisloo(log_lik1)
  elpd_loo1 = sum(loos1)
  se_elpd_loo1 = std(loos1) * sqrt(n)
  @printf(">> elpd_loo = %.1f, SE(elpd_loo) = %.1f\n", elpd_loo1, se_elpd_loo1)

  # Check the shape parameter k of the generalized Pareto distribution
  if all(pk1 .< 0.5)
      println("All Pareto k estimates OK (k < 0.5).")
  else
    pk_good = sum(pk1 .<= 0.5)
    pk_ok = length(pk1[pk1 .<= 0.7]) - pk_good
    pk_bad = length(pk1[pk1 .<= 1]) - pk_good - pk_ok
    println((good=pk_good, ok=pk_ok, bad=pk_bad, very_bad=sum(pk1 .> 1)))
  end
end

begin
  scatter(pk1, xlab="Datapoint", ylab="Pareto shape k",
    marker=2.5, lab="Pk points")
  hline!([0.5], lab="pk = 0.5");hline!([0.7], lab="pk = 0.7")
  hline!([1], lab="pk = 1.0")
  title!("PSIS diagnostic plot for poisson-log model.")
  savefig(joinpath(ProjDir, "diag_plot_1.png"))
end

#=
*Simple negative binomial regression example
*using the 2nd parametrization of the negative
*binomial distribution, see section 40.1-3 in the Stan
*reference guide
=#

roaches2_stan = "
data {
  int<lower=0> N;
  int<lower=0> K;
  vector[N] exposure2;
  vector[N] roach1;
  vector[N] senior;
  vector[N] treatment;
  int y[N];
}
transformed data {
  vector[N] log_expo;
  log_expo = log(exposure2);
}
parameters {
  real phi;
  vector[K] beta;
}
transformed parameters {
   vector[N] eta;
   eta = log_expo + beta[1] + beta[2] * roach1 + beta[3] * treatment
                + beta[4] * senior;
}
model {
  phi ~ normal(0, 10);
  beta[1] ~ cauchy(0,10);   //prior for the intercept following Gelman 2008
  for(i in 2:K)
   beta[i] ~ cauchy(0,2.5); //prior for the slopes following Gelman 2008
  y ~ neg_binomial_2_log(eta, phi);
}
generated quantities {
 vector[N] log_lik;
 for(i in 1:N){
  log_lik[i] <- neg_binomial_2_log_lpmf(y[i] | eta[i], phi);
 }
}
";

tmpdir=joinpath(ProjDir, "tmp")
sm2 = SampleModel("roaches2", roaches2_stan; tmpdir)
rc2 = stan_sample(sm2; data)

if success(rc2)
  read_summary(sm2, true)
  nt2 = read_samples(sm2)

  # Compute LOO and standard error
  log_lik2 = nt2.log_lik'
  loo2, loos2, pk2 = psisloo(log_lik2)
  elpd_loo2 = sum(loos2)
  se_elpd_loo2 = std(loos2) * sqrt(n)
  @printf(">> elpd_loo = %.1f, SE(elpd_loo) = %.1f\n", elpd_loo2, se_elpd_loo2)

  # Check the shape parameter k of the generalized Pareto distribution
  pk_qualify(pk2) |> display

end

begin
  scatter(pk2, xlab="Datapoint", ylab="Pareto shape k",
    marker=2.5, lab="Pk points")
  hline!([0.5], lab="pk = 0.5");hline!([0.7], lab="pk = 0.7")
  hline!([1], lab="pk = 1.0")
  title!("PSIS diagnostic plot for neg-binomial model.")
  savefig(joinpath(ProjDir, "diag_plot_2.png"))
end


"""
    isclockwise(polygon::Polygon)

The shoelace formula, also known as Gauss's area formula, is used to determine
whether polygon traversal is clockwise or counter-clockwise. If the sum over the
edges is greater than 0, the curve is clockwise. If the sum over the edges is
lesser than 0, the curve is counter-clockwise. When the sum over the edges
equals 0, the polygon traverses like a figure eight.

Note: The right-handed coordinate system is used.
"""
function isclockwise(polygon::Polygon)
    sum([(b[1] - a[1]) * (b[2] + a[2]) for (a, b) in edges(polygon)]) > 0
end


#############
# In this example, we explore integration of a harmonic function:
#
#   f(x,y) = (x^2-y^2+1)/[(x^2-y^2+1)^2+(2xy+1)^2],
#
# over the unit disk. In this case, we know from complex analysis that the
# integral of a holomorphic function is equal to π × f(0,0).
# We analyze the function on an N×M tensor product grid defined by:
#
#   rₙ = cos[(n+1/2)π/2N], for 0 ≤ n < N, and
#
#   θₘ = 2π m/M, for 0 ≤ m < M;
#
# we convert the function samples to Chebyshev×Fourier coefficients using
# `plan_disk_analysis`; and finally, we transform the Chebyshev×Fourier
# coefficients to disk harmonic coefficients using `plan_disk2cxf`.
#
# For the storage pattern of the arrays, please consult the documentation.
#############

using FastTransforms, LinearAlgebra

f = (x,y) -> (x^2-y^2+1)/((x^2-y^2+1)^2+(2x*y+1)^2)

N = 5
M = 4N-3

r = [sinpi((N-n-0.5)/(2N)) for n in 0:N-1]
θ = (0:M-1)*2/M # mod π.

# On the mapped tensor product grid, our function samples are:
F = [f(r*cospi(θ), r*sinpi(θ)) for r in r, θ in θ]

P = plan_disk2cxf(F)
PA = plan_disk_analysis(F)

# Its Zernike coefficients are:
U = P\(PA*F)

# The Zernike coefficients are useful for integration. The integral of f(x,y)
# over the disk should be π/2 by harmonicity. The coefficient of Z_0^0
# multiplied by √π is:
U[1, 1]*sqrt(π)

# Using an orthonormal basis, the integral of [f(x,y)]^2 over the disk is
# approximately the square of the 2-norm of the coefficients:
norm(U)^2


function chebyshev_extrema(n::S,domain = [1.0,-1.0]) where S <: Integer

  if n <= 0
    error("The number of nodes must be positive.")
  end

  nodes = zeros(n)

  if isodd(n)
    nodes[Int((n-1)/2)+1] = (domain[1]+domain[2])/2.0
  end

  for i = 1:div(n,2)
    x = -cos((i-1)*pi/(n-1))*(domain[1]-domain[2])/2.0
    nodes[i]       = (domain[1]+domain[2])/2.0  + x
    nodes[end-i+1] = (domain[1]+domain[2])/2.0  - x
  end

  return nodes

end


export resampleα, randomindex

"""
	resampleα(α::Float64, N::Int, K::Int)

Resample α given the approach by Escobar and West. (page 585)
"""
function resampleα(α::Float64, N::Int, K::Int; k = 1.0, Θ = 1.0, maxiter = 1)

    w = zeros(2)
    w[2] = N

    f0 = k + K
    f1 = Θ + K - 1

    newα = α

    for i in 1:maxiter

        η = rand(Beta(newα + 1, N))
        f3 = Θ - log(η)

        w[1] = f1 / f3

        z = randomindex(w)

        if z == 1
            newα = rand(Gamma(f0, 1/f3))
        else
            newα = rand(Gamma(f1, 1/f3))
        end
    end

    return newα
end

function resampleα(α::Float64, N::Array{Int}, K::Array{Int}; k = 1.0, Θ = 1.0, maxiter = 1)
    # implementation according to escobar - west page
    totalK = sum(K)
    num = length(N)

    η = zeros(num)

		newα = α
    for i in 1:maxiter

        for j in 1:num
            η[j] = rand(Beta(newα + 1, N[j]))
        end

        z = rand(num) .* (newα + N) .< N

        g_a = k + totalK - sum(z)
        g_b = Θ - sum(log(η))

        newα = rand(Gamma(g_a, 1/g_b))
    end

    newα
end

"""
	randomindex(p::Vector{Float64})

Randomly select an index propotional to its probability.
"""
function randomindex(p::Vector{Float64})

    max = sum(p)
    csum = 0.0
    thres = rand()

    @inbounds for i in 1:length(p)
        csum += p[i]
        if csum >= thres * max
            return convert(Int, i)
        end
    end

    return length(p)
end

function randnumtable(weights::Array{Float64}, table::Array{Int})

    numtable = zeros(Int, size(table))

    B = unique( table )
    w = log( weights )

    for i in 1:length(B)

        max = B[i]
        if max > 0

            m = 1:max

            stirnums = map( x -> abs(stirlings1(max, x)), m)
            stirnums /= maximum(stirnums)

            for (idx, j) in enumerate(findall(table .== max))

                clike = m .* w[idx]
                clike = cumsum(stirnums .* exp(clike - maximum(clike)))

                numtable[j] = 1+sum(rand() * clike[max] .> clike)
            end

        end

    end

    return numtable
end


using PolyChaos, GaussQuadrature, Test, LinearAlgebra

@testset "Test Fejer rule" begin
    for m = 1:20
        n, w = fejer(m)
        @test length(n) == length(w) == m
    end
    for m = 1:20
        n, w = golubwelsch(rm_hermite(m+1)...)
        @test length(n) == length(w) == m
    end
end

@testset "N+1 quadrature rules" begin
    @testset "Test Fejer2 rule" begin
        for m = 2:20
            n, w = fejer2(m)
            @test length(n) == length(w) == m + 1
        end
    end
    @testset "Test Clenshaw Curtis rule" begin
        for m = 2:20
            n, w = clenshaw_curtis(m)
            @test length(n) == length(w) == m + 1
        end
    end
    @testset "Test Radau rule" begin
        for m = 1:20
            n, w = radau(rm_hermite(m+2)...,4.)
            @test length(n) == length(w) == m + 1
        end
    end
end

@testset "N+2 quadrature rule (lobatto)" begin
    for m = 1:20
        n, w = lobatto(rm_legendre(m+3)...,-1.,1.)
        @test length(n) == length(w) == m + 2
    end
end


@testset "Sinc interpolation based on FFT" begin

    function test_interpolation_sum_fft(N_low, N)
	    x_min = 0.0
	    x_max = 16π

	    xs_low = range(x_min, x_max, length=N_low+1)[1:N_low]
	    xs_high = range(x_min, x_max, length=N)[1:end-1]
	    f(x) = sin(0.5*x) + cos(x) + cos(2 * x) + sin(0.25*x)
	    arr_low = f.(xs_low)
	    arr_high = f.(xs_high)

	    xs_interp = range(x_min, x_max, length=N+1)[1:N]
	    arr_interp = resample(arr_low, N)

	    xs_interp_s = range(x_min, x_max, length=N+1)[1:N]
	    arr_interp_s = FFTResampling.sinc_interpolate_sum(arr_low, N)

        @test ≈(arr_interp[2*N ÷10: N*8÷10], arr_high[2* N ÷10: N*8÷10], rtol=0.05)
        @test ≈(arr_high[2*N ÷10: N*8÷10], arr_interp_s[2*N ÷10: N*8÷10], rtol=0.05)
    end

    test_interpolation_sum_fft(128, 1000)
    test_interpolation_sum_fft(129, 1000)
    test_interpolation_sum_fft(120, 1531)
    test_interpolation_sum_fft(121, 1211)
end

@testset "Downsampling based on frequency cutting" begin
    function test_resample(N_low, N)
	    x_min = 0.0
	    x_max = 16π

	    xs_low = range(x_min, x_max, length=N_low+1)[1:N_low]
	    f(x) = sin(0.5*x) + cos(x) + cos(2 * x) + sin(0.25*x)
	    arr_low = f.(xs_low)

	    xs_interp = range(x_min, x_max, length=N+1)[1:N]
	    arr_interp = resample(arr_low, N)

	    xs_interp_s = range(x_min, x_max, length=N+1)[1:N]
	    arr_interp_s = FFTResampling.sinc_interpolate_sum(arr_low, N)

        arr_ds = FFTResampling.resample(arr_interp, N_low)
        @test ≈(arr_ds, arr_low)
        @test eltype(arr_low) === eltype(arr_ds)
        @test eltype(arr_interp) === eltype(arr_ds)
    end

    test_resample(128, 1000)
    test_resample(128, 1232)
    test_resample(128, 255)
    test_resample(253, 254)
    test_resample(253, 1001)
    test_resample(99, 100101)

end

@testset "Check if resample and interpolate return real result real input" begin
    function test_real(s, s_n, s_n2)
        x = randn(s)
        y = resample(x, s_n, take_real=false)
        y2 = resample(x, s_n2, take_real=false)
        @test all(imag.(y) .< 5e-15)
        @test all(imag.(y2) .< 5e-14)
    end

    test_real((12, 13, 14, 15), (12, 12, 12, 12), (20, 21, 22, 23))
    test_real((12, 13, 14, 15), (12, 13, 13, 13), (20, 22, 22, 23))
    test_real((12, 13, 14, 15), (12, 12, 13, 14), (21, 23, 21, 23))

    test_real((7,7), (6,6), (8, 8))
    test_real(100, 99, 101)
    test_real(101, 89, 101)
    test_real(100, 98, 120)
    test_real(101, 90, 120)

end

@testset "FFT sinc resample and upsample together in 2D" begin

    function test_2D(in_s, out_s)
        x = range(-10.0, 10.0, length=in_s[1] + 1)[1:end-1]
        y = range(-10.0, 10.0, length=in_s[2] + 1)[1:end-1]'
	    arr = abs.(x) .+ abs.(y) .+ sinc.(sqrt.(x .^2 .+ y .^2))
	    arr_interp = resample(arr[1:end, 1:end], out_s);
	    arr_ds = resample(arr_interp, in_s)
        @test arr_ds ≈ arr
    end

    test_2D((128, 128), (150, 150))
    test_2D((128, 128), (151, 151))
    test_2D((129, 129), (150, 150))
    test_2D((129, 129), (151, 151))

    test_2D((150, 128), (151, 150))
    test_2D((128, 128), (151, 153))
    test_2D((129, 128), (150, 153))
    test_2D((129, 128), (129, 153))

    x = range(-10.0, 10.0, length=129)[1:end-1]
    x2 = range(-10.0, 10.0, length=130)[1:end-1]
    x_exact = range(-10.0, 10.0, length=2049)[1:end-1]
    y = x'
    y2 = x2'
    y_exact = x_exact'
    arr = abs.(x) .+ abs.(y) .+sinc.(sqrt.(x .^2 .+ y .^2))
    arr2 = abs.(x) .+ abs.(y) .+sinc.(sqrt.(x .^2 .+ y .^2))
    arr_exact = abs.(x_exact) .+ abs.(y_exact) .+ sinc.(sqrt.(x_exact .^2 .+ y_exact .^2))
    arr_interp = resample(arr[1:end, 1:end], (131, 131));
    arr_interp2 = resample(arr[1:end, 1:end], (512, 512));
    arr_interp3 = resample(arr[1:end, 1:end], (1024, 1024));
    arr_ds = resample(arr_interp, (128, 128))
    arr_ds2 = resample(arr_interp, (128, 128))
    arr_ds23 = resample(arr_interp2, (512, 512))
    arr_ds3 = resample(arr_interp, (128, 128))

    @test ≈(arr_ds3, arr)
    @test ≈(arr_ds2, arr)
    @test ≈(arr_ds, arr)
    @test ≈(arr_ds23, arr_interp2)

end

@testset "FFT sinc resample and upsample together in 2D for a complex signal" begin

    function test_2D(in_s, out_s)
    	x = range(-10.0, 10.0, length=in_s[1] + 1)[1:end-1]
    	y = range(-10.0, 10.0, length=in_s[2] + 1)[1:end-1]'
    	f(x, y) = 1im * (abs(x) + abs(y) + sinc(sqrt(x ^2 + y ^2)))
    	f2(x, y) =  abs(x) + abs(y) + sinc(sqrt((x - 5) ^2 + (y - 5)^2))

    	arr = f.(x, y) .+ f2.(x, y)
    	arr_interp = resample(arr[1:end, 1:end], out_s);
    	arr_ds = resample(arr_interp, in_s)

        @test eltype(arr) === eltype(arr_ds)
        @test eltype(arr_interp) === eltype(arr_ds)
        @test imag(arr) ≈ imag(arr_ds)
        @test real(arr) ≈ real(arr_ds)
    end

    test_2D((128, 128), (150, 150))
    test_2D((128, 128), (151, 151))
    test_2D((129, 129), (150, 150))
    test_2D((129, 129), (151, 151))

    test_2D((150, 128), (151, 150))
    test_2D((128, 128), (151, 153))
    test_2D((129, 128), (150, 153))
    test_2D((129, 128), (129, 153))
end

@testset "FFT sinc resample and upsample together in 2D for a purely imaginary signal" begin
    function test_2D(in_s, out_s)
    	x = range(-10.0, 10.0, length=in_s[1] + 1)[1:end-1]
    	y = range(-10.0, 10.0, length=in_s[2] + 1)[1:end-1]'
    	f(x, y) = 1im * (abs(x) + abs(y) + sinc(sqrt(x ^2 + y ^2)))

    	arr = f.(x, y)
    	arr_interp = resample(arr[1:end, 1:end], out_s);
    	arr_ds = resample(arr_interp, in_s)

        @test imag(arr) ≈ imag(arr_ds)
        @test all(real(arr_ds) .< 1e-14)
        @test all(real(arr_interp) .< 1e-14)
    end

    test_2D((128, 128), (150, 150))
    test_2D((128, 128), (151, 151))
    test_2D((129, 129), (150, 150))
    test_2D((129, 129), (151, 151))

    test_2D((150, 128), (151, 150))
    test_2D((128, 128), (151, 153))
    test_2D((129, 128), (150, 153))
    test_2D((129, 128), (129, 153))

end

return


using Yields
using Test

@testset "Yields.jl" begin

    @testset "rate types" begin
        rs = Rate.([0.1, 0.02], Yields.Continuous())
        @test rs[1] == Rate(0.1, Yields.Continuous())
        @test rate(rs[1]) == 0.1
    end

    @testset "constructor" begin
        @test Yields.Continuous(0.05) == Rate(0.05, Yields.Continuous())
        @test Yields.Periodic(0.02, 2) == Rate(0.02, Yields.Periodic(2))
        @test Rate(0.02, 2) == Rate(0.02, Yields.Periodic(2))
        @test Rate(0.02, Inf) == Rate(0.02, Yields.Continuous())
    end

    @testset "rate conversions" begin
        m = Rate(0.1, Yields.Periodic(2))
        @test rate(convert(Yields.Continuous(), m)) ≈ rate(Rate(0.09758, Yields.Continuous())) atol = 1e-5
        c = Rate(0.09758, Yields.Continuous())
        @test convert(Yields.Continuous(), c) == c
        @test rate(convert(Yields.Periodic(2), c)) ≈ rate(Rate(0.1, Yields.Periodic(2))) atol = 1e-5
        @test rate(convert(Yields.Periodic(4), m)) ≈ rate(Rate(0.09878030638383972, Yields.Periodic(4))) atol = 1e-5

    end

    @testset "rate equality" begin
        a = Yields.Periodic(0.02, 2)
        b = Yields.Periodic(0.03, 2)
        c = Yields.Continuous(0.02)

        @test a == a
        @test a != b
        @test ~(a ≈ b)
        @test (a ≈ a)
        @test ~(a ≈ c)

    end

    @testset "constant curve and rate -> Constant" begin
        yield = Yields.Constant(0.05)
        rate = Yields.Rate(0.05, Yields.Periodic(1))

        @test Yields.zero(yield, 1) == Rate(0.05, Yields.Periodic(1))
        @test Yields.zero(Yields.Constant(0.05, Yields.Periodic(2)), 10) == Rate(0.05, Yields.Periodic(2))
        @test Yields.zero(yield, 5, Yields.Periodic(2)) == convert(Yields.Periodic(2), Rate(0.05, Yields.Periodic(1)))

        @testset "constant discount time: $time" for time in [0, 0.5, 1, 10]
            @test discount(yield, time) ≈ 1 / (1.05)^time
            @test discount(rate, time) ≈ 1 / (1.05)^time
            @test discount(rate, 0, time) ≈ 1 / (1.05)^time
        end
        @testset "constant discount scalar time: $time" for time in [0, 0.5, 1, 10]
            @test discount(0.05, time) ≈ 1 / (1.05)^time
        end

        @testset "constant accumulation scalar time: $time" for time in [0, 0.5, 1, 10]
            @test accumulation(0.05, time) ≈ 1 * (1.05)^time
        end

        @testset "broadcasting" begin
            @test all(discount.(yield, [1, 2, 3]) .== 1 ./ 1.05 .^ (1:3))
            @test all(accumulation.(yield, [1, 2, 3]) .== 1.05 .^ (1:3))
        end

        @testset "constant accumulation time: $time" for time in [0, 0.5, 1, 10]
            @test accumulation(yield, time) ≈ 1 * 1.05^time
            @test accumulation(rate, time) ≈ 1 * 1.05^time
            @test accumulation(rate, 0, time) ≈ 1 * 1.05^time
        end

        @testset "CompoundingFrequency" begin
            @testset "Continuous" begin
                cnst = Yields.Constant(Yields.Continuous(0.05))
                @test accumulation(cnst, 1) == exp(0.05)
                @test accumulation(cnst, 2) == exp(0.05 * 2)
                @test discount(cnst, 2) == 1 / exp(0.05 * 2)
            end

            @testset "Periodic" begin
                p = Yields.Constant(Rate(0.05, Yields.Periodic(2)))
                @test accumulation(p, 1) == (1 + 0.05 / 2)^(1 * 2)
                @test accumulation(p, 2) == (1 + 0.05 / 2)^(2 * 2)
                @test discount(p, 2) == 1 / (1 + 0.05 / 2)^(2 * 2)

            end
        end

        yield_2x = yield + yield
        yield_add = yield + 0.05
        add_yield = 0.05 + yield
        @testset "constant discount added" for time in [0, 0.5, 1, 10]
            @test discount(yield_2x, time) ≈ 1 / (1.1)^time
            @test discount(yield_add, time) ≈ 1 / (1.1)^time
            @test discount(add_yield, time) ≈ 1 / (1.1)^time
        end

        yield_1bps = yield - Yields.Constant(0.04)
        yield_minus = yield - 0.01
        minus_yield = 0.05 - Yields.Constant(0.01)
        @testset "constant discount subtraction" for time in [0, 0.5, 1, 10]
            @test discount(yield_1bps, time) ≈ 1 / (1.01)^time
            @test discount(yield_minus, time) ≈ 1 / (1.04)^time
            @test discount(minus_yield, time) ≈ 1 / (1.04)^time
        end
    end

    @testset "short curve" begin
        z = Yields.Zero([0.0, 0.05], [1, 2])
        @test rate(zero(z, 1)) ≈ 0.00
        @test discount(z, 1) ≈ 1.00
        @test rate(zero(z, 2)) ≈ 0.05

        # test no times constructor
        z = Yields.Zero([0.0, 0.05])
        @test rate(zero(z, 1)) ≈ 0.00
        @test discount(z, 1) ≈ 1.00
        @test rate(zero(z, 2)) ≈ 0.05
    end

    @testset "Step curve" begin
        y = Yields.Step([0.02, 0.05], [1, 2])

        @test rate(y, 0.5) == 0.02

        @test discount(y, 0.0) ≈ 1
        @test discount(y, 0.5) ≈ 1 / (1.02)^(0.5)
        @test discount(y, 1) ≈ 1 / (1.02)^(1)
        @test rate(y, 1) ≈ 0.02

        @test discount(y, 2) ≈ 1 / (1.02) / 1.05
        @test rate(y, 2) ≈ 0.05
        @test rate(y, 2.5) ≈ 0.05

        @test discount(y, 2) ≈ 1 / (1.02) / 1.05

        @test discount(y, 1.5) ≈ 1 / (1.02) / 1.05^(0.5)

        @testset "broadcasting" begin
            @test all(discount.(y, [1, 2]) .== [1 / 1.02, 1 / 1.02 / 1.05])
            @test all(accumulation.(y, [1, 2]) .== [1.02, 1.02 * 1.05])
        end

        y = Yields.Step([0.02, 0.07])
        @test rate(y, 0.5) ≈ 0.02
        @test rate(y, 1) ≈ 0.02
        @test rate(y, 1.5) ≈ 0.07

    end

    @testset "Salomon Understanding the Yield Curve Pt 1 Figure 9" begin
        maturity = collect(1:10)

        par = [6.0, 8.0, 9.5, 10.5, 11.0, 11.25, 11.38, 11.44, 11.48, 11.5] ./ 100
        spot = [6.0, 8.08, 9.72, 10.86, 11.44, 11.71, 11.83, 11.88, 11.89, 11.89] ./ 100

        # the forwards for 7+ have been adjusted from the table - perhaps rounding issues are exacerbated
        # in the text? forwards for <= 6 matched so reasonably confident that the algo is correct
        # fwd = [6.,10.2,13.07,14.36,13.77,13.1,12.55,12.2,11.97,11.93] ./ 100 # from text
        fwd = [6.0, 10.2, 13.07, 14.36, 13.77, 13.1, 12.61, 12.14, 12.05, 11.84] ./ 100  # modified

        y = Yields.Par(Rate.(par, Yields.Periodic(1)), maturity)

        @testset "UTYC Figure 9 par -> spot : $mat" for mat in maturity
            @test rate(zero(y, mat)) ≈ spot[mat] atol = 0.0001
            @test forward(y, mat - 1) ≈ Yields.Periodic(fwd[mat], 1) atol = 0.0001
        end

    end

    @testset "simple rate and forward" begin
        # Risk Managment and Financial Institutions, 5th ed. Appendix B

        maturity = [0.5, 1.0, 1.5, 2.0]
        zero = [5.0, 5.8, 6.4, 6.8] ./ 100
        curve = Yields.Zero(zero, maturity)

        @test discount(curve, 0) ≈ 1
        @test discount(curve, 1) ≈ 1 / (1 + zero[2])
        @test discount(curve, 2) ≈ 1 / (1 + zero[4])^2

        @test forward(curve, 0.5, 1.0) ≈ Yields.Periodic(6.6 / 100, 1) atol = 0.001
        @test forward(curve, 1.0, 1.5) ≈ Yields.Periodic(7.6 / 100, 1) atol = 0.001
        @test forward(curve, 1.5, 2.0) ≈ Yields.Periodic(8.0 / 100, 1) atol = 0.001

        y = Yields.Zero(zero)

        @test discount(y, 1) ≈ 1 / 1.05
        @test discount(y, 2) ≈ 1 / 1.058^2

        @testset "broadcasting" begin
            @test all(discount.(y, [1, 2]) .≈ [1 / 1.05, 1 / 1.058^2])
            @test all(accumulation.(y, [1, 2]) .≈ [1.05, 1.058^2])
        end

    end

    @testset "Forward Rates" begin
        # Risk Managment and Financial Institutions, 5th ed. Appendix B

        forwards = [0.05, 0.04, 0.03, 0.08]
        curve = Yields.Forward(forwards, [1, 2, 3, 4])

        @testset "discounts: $t" for (t, r) in enumerate(forwards)
            @test discount(curve, t) ≈ reduce((v, r) -> v / (1 + r), forwards[1:t]; init = 1.0)
        end

        # test constructor without times
        curve = Yields.Forward(forwards)

        @testset "discounts: $t" for (t, r) in enumerate(forwards)
            @test discount(curve, t) ≈ reduce((v, r) -> v / (1 + r), forwards[1:t]; init = 1.0)
        end

        @test accumulation(curve, 0, 1) ≈ 1.05
        @test accumulation(curve, 1, 2) ≈ 1.04
        @test accumulation(curve, 0, 2) ≈ 1.04 * 1.05

        @testset "broadcasting" begin
            @test all(accumulation.(curve, [1, 2]) .≈ [1.05, 1.04 * 1.05])
            @test all(discount.(curve, [1, 2]) .≈ 1 ./ [1.05, 1.04 * 1.05])
        end

        # addition / subtraction
        @test discount(curve + 0.1, 1) ≈ 1 / 1.15
        @test discount(curve - 0.03, 1) ≈ 1 / 1.02

        @testset "with specified timepoints" begin
            i = [0.0, 0.05]
            times = [0.5, 1.5]
            y = Yields.Forward(i, times)
            @test discount(y, 0.5) ≈ 1 / 1.0^0.5
            @test discount(y, 1.5) ≈ 1 / 1.0^0.5 / 1.05^1

        end

    end

    @testset "forwardcurve" begin
        maturity = [0.5, 1.0, 1.5, 2.0]
        zero = [5.0, 5.8, 6.4, 6.8] ./ 100
        curve = Yields.Zero(zero, maturity)

        fwd = Yields.ForwardStarting(curve, 1.0)
        @test discount(fwd, 0) ≈ 1
        @test discount(fwd, 0.5) ≈ discount(curve, 1, 1.5)
        @test discount(fwd, 1) ≈ discount(curve, 1, 2)
        @test accumulation(fwd, 1) ≈ accumulation(curve, 1, 2)
    end

    @testset "base + spread" begin
        riskfree_maturities = [0.5, 1.0, 1.5, 2.0]
        riskfree = [5.0, 5.8, 6.4, 6.8] ./ 100 # spot rates

        spread_maturities = [0.5, 1.0, 1.5, 3.0] # different maturities
        spread = [1.0, 1.8, 1.4, 1.8] ./ 100 # spot spreads

        rf_curve = Yields.Zero(riskfree, riskfree_maturities)
        spread_curve = Yields.Zero(spread, spread_maturities)

        yield = rf_curve + spread_curve

        @test discount(yield, 1.0) ≈ 1 / (1 + riskfree[2] + spread[2])^1
        @test discount(yield, 1.5) ≈ 1 / (1 + riskfree[3] + spread[3])^1.5
    end

    @testset "actual cmt treasury" begin
        # Fabozzi 5-5,5-6
        cmt = [5.25, 5.5, 5.75, 6.0, 6.25, 6.5, 6.75, 6.8, 7.0, 7.1, 7.15, 7.2, 7.3, 7.35, 7.4, 7.5, 7.6, 7.6, 7.7, 7.8] ./ 100
        mats = collect(0.5:0.5:10.0)
        curve = Yields.CMT(cmt, mats)
        targets = [5.25, 5.5, 5.76, 6.02, 6.28, 6.55, 6.82, 6.87, 7.09, 7.2, 7.26, 7.31, 7.43, 7.48, 7.54, 7.67, 7.8, 7.79, 7.93, 8.07] ./ 100
        target_periodicity = fill(2, length(mats))
        target_periodicity[2] = 1 # 1 year is a no-coupon, BEY yield, the rest are semiannual BEY
        @testset "Fabozzi bootstrapped rates" for (r, mat, target, tp) in zip(cmt, mats, targets, target_periodicity)
            @test rate(zero(curve, mat, Yields.Periodic(tp))) ≈ target atol = 0.0001
        end

        # Hull, problem 4.34
        adj = ((1 + 0.051813 / 2)^2 - 1) * 100
        cmt = [4.0816, adj, 5.4986, 5.8620] ./ 100
        mats = [0.5, 1.0, 1.5, 2.0]
        curve = Yields.CMT(cmt, mats)
        targets = [4.0405, 5.1293, 5.4429, 5.8085] ./ 100
        @testset "Hull bootstrapped rates" for (r, mat, target) in zip(cmt, mats, targets)
            @test rate(zero(curve, mat, Yields.Continuous())) ≈ target atol = 0.001
        end

        # test that showing the curve doesn't error
        @test isnothing(show(devnull, curve))

        #     # https://www.federalreserve.gov/pubs/feds/2006/200628/200628abs.html
        #     # 2020-04-02 data
        #     cmt = [0.0945,0.2053,0.4431,0.7139,0.9724,1.2002,1.3925,1.5512,1.6805,1.7853,1.8704,1.9399,1.9972,2.045,2.0855,2.1203,2.1509,2.1783,2.2031,2.2261,2.2477,2.2683,2.2881,2.3074,2.3262,2.3447,2.3629,2.3809,2.3987,2.4164] ./ 100
        #     mats = collect(1:30)
        #     curve = Yields.USCMT(cmt,mats)
        #     target = [0.0945,0.2053,0.444,0.7172,0.9802,1.2142,1.4137,1.5797,1.7161,1.8275,1.9183,1.9928,2.0543,2.1056,2.1492,2.1868,2.2198,2.2495,2.2767,2.302,2.3261,2.3494,2.372,2.3944,2.4167,2.439,2.4614,2.4839,2.5067,2.5297] ./ 100

        #     @testset "FRB data" for (t,mat,target) in zip(1:length(mats),mats,target)
        #         @show mat
        #         if mat >= 1
        #             @test rate(zero(curve,mat, Yields.Continuous())) ≈ target[mat] atol=0.001
        #         end
        #     end
    end

    @testset "OIS" begin
        ois = [1.8, 2.0, 2.2, 2.5, 3.0, 4.0] ./ 100
        mats = [1 / 12, 1 / 4, 1 / 2, 1, 2, 5]
        curve = Yields.OIS(ois, mats)
        targets = [0.017987, 0.019950, 0.021880, 0.024693, 0.029994, 0.040401]
        @testset "bootstrapped rates" for (r, mat, target) in zip(ois, mats, targets)
            @test rate(zero(curve, mat, Yields.Continuous())) ≈ target atol = 0.001
        end
    end

    @testset "InstrumentQuotes" begin

        maturities = [1.3, 2.7]
        prices = [1.1, 0.8]
        zcq = Yields.ZeroCouponQuote.(prices, maturities)
        @test isa(first(zcq), Yields.ZeroCouponQuote)

        @test_throws DimensionMismatch Yields.ZeroCouponQuote.([1.3, 2.4, 0.9], maturities)

        rates = [0.4, -0.7]
        swq = Yields.SwapQuote.(rates, maturities, 3)
        @test first(swq).frequency == 3

        @test_throws DimensionMismatch Yields.SwapQuote.([1.3, 2.4, 0.9], maturities, 3)
        @test_throws DomainError Yields.SwapQuote.(rates, maturities, 0)
        @test_throws DomainError Yields.SwapQuote.(rates, maturities, -2)

        rates = [0.4, -0.7]
        bbq = Yields.BulletBondQuote.(rates, prices, maturities, 3)
        @test first(bbq).frequency == 3
        @test first(bbq).yield == first(rates)

        @test_throws DimensionMismatch Yields.BulletBondQuote.([1.3, 2.4, 0.9], prices, maturities, 3)
        @test_throws DimensionMismatch Yields.BulletBondQuote.(rates, prices, [4.3, 5.6, 4.4, 4.4], 3)
        @test first(Yields.BulletBondQuote.(rates, [5.7], maturities, 3)).price == 5.7
        @test_throws DomainError Yields.BulletBondQuote.(rates, prices, maturities, 0)
        @test_throws DomainError Yields.BulletBondQuote.(rates, prices, maturities, -4)

    end

    @testset "SmithWilson" begin

        ufr = 0.03
        α = 0.1
        u = [5.0, 7.0]
        qb = [2.3, -1.2]

        # Basic behaviour
        sw = Yields.SmithWilson(u, qb; ufr = ufr, α = α)
        @test sw.ufr == ufr
        @test sw.α == α
        @test sw.u == u
        @test sw.qb == qb
        @test_throws DomainError Yields.SmithWilson(u, [2.4, -3.4, 8.9], ufr = ufr, α = α)

        # Empty u and Qb should result in a flat yield curve
        # Use this to test methods expected from <:AbstractYieldCurve
        # Only discount and zero are explicitly implemented, so the others should follow automatically
        sw_flat = Yields.SmithWilson(Float64[], Float64[], ufr = ufr, α = α)
        @test discount(sw_flat, 10.0) == exp(-ufr * 10.0)
        @test accumulation(sw_flat, 10.0) ≈ exp(ufr * 10.0)
        @test rate(convert(Yields.Continuous(), zero(sw_flat, 8.0))) ≈ ufr
        @test discount.(sw_flat, [5.0, 10.0]) ≈ exp.(-ufr .* [5.0, 10.0])
        @test rate(convert(Yields.Continuous(), forward(sw_flat, 5.0, 8.0))) ≈ ufr

        # A trivial Qb vector (=0) should result in a flat yield curve
        ufr_curve = Yields.SmithWilson(u, [0.0, 0.0], ufr = ufr, α = α)
        @test discount(ufr_curve, 10.0) == exp(-ufr * 10.0)

        # A single payment at time 4, zero interest
        curve_with_zero_yield = Yields.SmithWilson([4.0], reshape([1.0], 1, 1), [1.0], ufr = ufr, α = α)
        @test discount(curve_with_zero_yield, 4.0) == 1.0

        # In the long end it's still just UFR
        @test rate(convert(Yields.Continuous(), forward(curve_with_zero_yield, 1000.0, 2000.0))) ≈ ufr

        # Three maturities have known discount factors
        times = [1.0, 2.5, 5.6]
        prices = [0.9, 0.7, 0.5]
        cfs = [1 0 0
            0 1 0
            0 0 1]

        curve_three = Yields.SmithWilson(times, cfs, prices, ufr = ufr, α = α)
        @test transpose(cfs) * discount.(curve_three, times) ≈ prices

        # Two cash flows with payments at three times
        prices = [1.0, 0.9]
        cfs = [0.1 0.1
            1.0 0.1
            0.0 1.0]
        curve_nondiag = Yields.SmithWilson(times, cfs, prices, ufr = ufr, α = α)
        @test transpose(cfs) * discount.(curve_nondiag, times) ≈ prices

        # Round-trip zero coupon quotes
        zcq_times = [1.2, 4.5, 5.6]
        zcq_prices = [1.0, 0.9, 1.2]
        zcq = Yields.ZeroCouponQuote.(zcq_prices, zcq_times)
        sw_zcq = Yields.SmithWilson(zcq, ufr = ufr, α = α)
        @testset "ZeroCouponQuotes round-trip" for idx = 1:length(zcq_times)
            @test discount(sw_zcq, zcq_times[idx]) ≈ zcq_prices[idx]
        end

        # uneven frequencies
        swq_maturities = [1.2, 2.5, 3.6]
        swq_interests = [-0.02, 0.3, 0.04]
        frequency = [2, 1, 2]
        swq = Yields.SwapQuote.(swq_interests, swq_maturities, frequency)
        swq_times = 0.5:0.5:3.5   # Maturities are rounded down to multiples of 1/frequency, [1.0, 2.5, 3.5]
        swq_payments = [
            -0.01 0.3 0.02
            0.99 0 0.02
            0 0.3 0.02
            0 0 0.02
            0 1.3 0.02
            0 0 0.02
            0 0 1.02
        ]

        # Round-trip swap quotes
        swq_maturities = [1.2, 2.5, 3.6]
        swq_interests = [-0.02, 0.3, 0.04]
        frequency = 2
        swq = Yields.SwapQuote.(swq_interests, swq_maturities, frequency)
        swq_times = 0.5:0.5:3.5   # Maturities are rounded down to multiples of 1/frequency, [1.0, 2.5, 3.5]
        swq_payments = [-0.01 0.15 0.02
            0.99 0.15 0.02
            0.0 0.15 0.02
            0.0 0.15 0.02
            0.0 1.15 0.02
            0.0 0.0 0.02
            0.0 0.0 1.02]
        sw_swq = Yields.SmithWilson(swq, ufr = ufr, α = α)
        @testset "SwapQuotes round-trip" for swapIdx = 1:length(swq_interests)
            @test sum(discount.(sw_swq, swq_times) .* swq_payments[:, swapIdx]) ≈ 1.0
        end

        # Round-trip bullet bond quotes (reuse data from swap quotes)
        bbq_prices = [1.3, 0.1, 4.5]
        bbq = Yields.BulletBondQuote.(swq_interests, bbq_prices, swq_maturities, frequency)
        sw_bbq = Yields.SmithWilson(bbq, ufr = ufr, α = α)
        @testset "BulletBondQuotes round-trip" for bondIdx = 1:length(swq_interests)
            @test sum(discount.(sw_bbq, swq_times) .* swq_payments[:, bondIdx]) ≈ bbq_prices[bondIdx]
        end

        @testset "SW ForwardStarting" begin
            fwd_time = 1.0
            fwd = Yields.ForwardStarting(sw_swq, fwd_time)

            @test discount(fwd, 3.7) ≈ discount(sw_swq, fwd_time, fwd_time + 3.7)
        end

        # EIOPA risk free rate (no VA), 31 August 2021.
        # https://www.eiopa.europa.eu/sites/default/files/risk_free_interest_rate/eiopa_rfr_20210831.zip
        eiopa_output_qb = [-0.59556534586390800
            -0.07442224713453920
            -0.34193181987682400
            1.54054875814153000
            -2.15552046042343000
            0.73559290752221900
            1.89365225129089000
            -2.75927773116240000
            2.24893737130629000
            -1.51625404117395000
            0.19284859623817400
            1.13410725406271000
            0.00153268224642171
            0.00147942301778158
            -1.85022125156483000
            0.00336230229850928
            0.00324546553910162
            0.00313268874430658
            0.00302383083427276
            1.36047951448615000]
        eiopa_output_u = 1:20
        eiopa_ufr = log(1.036)
        eiopa_α = 0.133394
        sw_eiopa_expected = Yields.SmithWilson(eiopa_output_u, eiopa_output_qb; ufr = eiopa_ufr, α = eiopa_α)

        eiopa_eurswap_maturities = [1:12; 15; 20]
        eiopa_eurswap_rates = [-0.00615, -0.00575, -0.00535, -0.00485, -0.00425, -0.00375, -0.003145,
            -0.00245, -0.00185, -0.00125, -0.000711, -0.00019, 0.00111, 0.00215]   # Reverse engineered from output curve. This is the full precision of market quotes.
        eiopa_eurswap_quotes = Yields.SwapQuote.(eiopa_eurswap_rates, eiopa_eurswap_maturities, 1)
        sw_eiopa_actual = Yields.SmithWilson(eiopa_eurswap_quotes, ufr = eiopa_ufr, α = eiopa_α)

        @testset "Match EIOPA calculation" begin
            @test sw_eiopa_expected.u ≈ sw_eiopa_actual.u
            @test sw_eiopa_expected.qb ≈ sw_eiopa_actual.qb
        end
    end

end


# interpolate point at (x, y) from 4 nearby pixel values
function interp_bilinear{T <: Union(Real, Integer)}(dat::Array{T, 2},
                                                    x::Float64, y::Float64,
                                                    x1, x2, y1, y2)
    q11 = dat[y1, x1]
    q12 = dat[y2, x1]
    q21 = dat[y1, x2]
    q22 = dat[y2, x2]
    if x1 != x2
        r1 = (x2 - x) / (x2 - x1) * q11 + (x - x1) / (x2 - x1) * q21
        r2 = (x2 - x) / (x2 - x1) * q12 + (x - x1) / (x2 - x1) * q22
    else
        # special case of x1 == x2, no interpolation needed
        r1 = q11
        r2 = q12
    end
    if y1 != y2
        r = (y - y1) / (y2 - y1) * r1 + (y2 - y) / (y2 - y1) * r2
    else
        # special case of y1 == y2, no interpolation needed
        r = r1
    end
    if typeof(r) <: Real
        r = round(r)
    end
    r = convert(T, r)
    return r
end

function imresize{T <: Union(Real, Integer)}(dat::Array{T, 2},
                                             new_size::(Int, Int))
    new_dat = similar(dat, new_size)
    h, w = size(dat)
    new_h, new_w = new_size
    for new_j=1:new_w, new_i=1:new_h
        # coordinates in original image
        x = new_j * w / new_w
        y = new_i * h / new_h
        # coordinates of 4 points to interpolate from
        x1, x2 = max(1, floor(x)), min(w, ceil(x))
        y1, y2 = max(1, floor(y)), min(h, ceil(y))
        new_dat[new_i, new_j] = interp_bilinear(dat, x, y, x1, x2, y1, y2)
    end
    return new_dat
end

function imresize{T <: Union(Real, Integer)}(dat::Array{T, 2},
                                             new_size...)
    return imresize(dat, new_size)
end

## if isinteractive()
##     using Images
##     using ImageView
##     im = imread("../../data/CK/faces_aligned/S999_003_00000054.png")
##     dat = convert(Array, im)
## end

using Images, Cairo

function imresize_julia!(resized, original)
    scale1 = (size(original,1)-1)/(size(resized,1)-0.999f0)
    scale2 = (size(original,2)-1)/(size(resized,2)-0.999f0)
    for jr = 0:size(resized,2)-1
        jo = scale2*jr
        ijo = itrunc(jo)
        fjo = jo - oftype(jo, ijo)
        @inbounds for ir = 0:size(resized,1)-1
            io = scale1*ir
            iio = itrunc(io)
            fio = io - oftype(io, iio)
            tmp = (1-fio)*((1-fjo)*original[iio+1,ijo+1] +
                           fjo*original[iio+1,ijo+2])
            + fio*((1-fjo)*original[iio+2,ijo+1] +
                   fjo*original[iio+2,ijo+2])
            resized[ir+1,jr+1] = convertsafely(eltype(resized), tmp)
        end
    end
    resized
end
imresize_julia(original, new_size) =
    imresize_julia!(similar(original,
                            new_size), original)

convertsafely{T<:FloatingPoint}(::Type{T}, val) = convert(T, val)
convertsafely{T<:Integer}(::Type{T}, val::Integer) = convert(T, val)
convertsafely{T<:Integer}(::Type{T}, val::FloatingPoint) =
    itrunc(T,
           val+oftype(val, 0.5))

function imresize_cairo(dat::Array{Uint32, 2}, new_size::(Int, Int))
    cs = CairoImageSurface(dat, 0)
    new_dat = zeros(Uint32, new_size)
    new_cs = CairoImageSurface(new_dat, 0)
    pat = CairoPattern(cs)
    pattern_set_filter(pat, Cairo.FILTER_BILINEAR)
    c = CairoContext(new_cs)
    h, w = size(dat)
    new_h, new_w = new_size
    scale(c, new_h / h, new_w / w)
    set_source(c, pat)
    paint(c)
    return new_cs.data
end

## img = rand(0x00:0xff, 774, 512)
## new_size = (3096, 2048)
## imresize_cairo(convert(Array{Uint32}, img), new_size)
## println("Cairo:")
## @time imresize_cairo(convert(Array{Uint32}, img), new_size)
## imresize_julia(img, new_size)
## println("Julia:")
## @time imresize_julia(img, new_size)


using Replay

instructions = raw"""
using OhMyREPL

using LinearAlgebra
using Printf
using Statistics

using DataFrames
using RDatasets
using StatsPlots

# use UnicodePlots.jl as backend
unicodeplots();

iris = dataset("datasets", "iris");
@show names(iris);
N = size(iris, 1)
@show names(iris);
# Execute PCA
X = Matrix(select(iris, Not(:Species)))
X .= X .- mean(X, dims=1)
Σ = (X' * X)/N # will be the covariance matrix for iris dataset
e = eigen(Σ, sortby = -);
# display  eigen values in descending order
e.values
# eigen vectors
e.vectors

@printf "1st contribution ratio = %.2f percent\n" 100e.values[1]/sum(e.values)
@printf "2nd contribution ratio = %.2f percent\n" 100e.values[2]/sum(e.values)

# Visualize the reduced data via PCA
topk = 2;
reduced_data = DataFrame(X * e.vectors[:, 1:topk], [:X, :Y]);
@df reduced_data scatter(:X, :Y, group=iris.Species)
"""

replay(instructions, use_ghostwriter=true, julia_project=@__DIR__)


# p20.m - 2nd-order wave eq. in 2D via FFT (compare p19.m)

# Grid and initial data:
N = 24; x = y = cheb(N)[2];
dt = 6/N^2;
xx = yy = x[end:-1:1];
plotgap = round(Int,(1/3)/dt); dt = (1/3)/plotgap;
vv = @. exp(-40*((x'-.4)^2 + y^2));
vvold = vv;  clf();

# Time-stepping by leap frog formula:
for n = 0:3*plotgap
    t = n*dt;
    if rem(n+.5,plotgap)<1     # plots at multiples of t=1/3
          i = n/plotgap+1;
          subplot(2,2,i,projection="3d");
          xxx = yyy = -1:1/16:1;
          s = Spline2D(xx,yy,reduce(flipdim,vv,1:2));
          vvv = evalgrid(s,xxx,yyy);
          surf(xxx,yyy,vvv); xlim(-1,1); ylim(-1,1); zlim(-0.15,1);
          view(-37.5,30); title("t = $(signif(t,5))");
    end
    uxx = zeros(N+1,N+1); uyy = zeros(N+1,N+1);
    ii = 2:N;
    for i = 2:N                # 2nd derivs wrt x in each row
          v = vv[i,:]; V = [v;flipdim(v[ii],1)];
          U = real(fft(V));
          W1 = real(ifft(1im*[0:N-1;0;1-N:-1].*U)); # diff wrt theta
          W2 = real(ifft(-[0:N;1-N:-1].^2.*U));     # diff^2 wrt theta
          uxx[i,ii] = W2[ii]./(1-x[ii].^2) - x[ii].*W1[ii]./(1-x[ii].^2).^(3/2);
    end
    for j = 2:N                # 2nd derivs wrt y in each column
          v = vv[:,j]; V = [v; flipdim(v[ii],1)];
          U = real(fft(V));
          W1 = real(ifft(1im*[0:N-1;0;1-N:-1].*U));# diff wrt theta
          W2 = real(ifft(-[0:N;1-N:-1].^2.*U));    # diff^2 wrt theta
          uyy[ii,j] = W2[ii]./(1-y[ii].^2) - y[ii].*W1[ii]./(1-y[ii].^2).^(3/2);
    end
    vvnew = 2*vv - vvold + dt^2*(uxx+uyy);
    vvold = vv; vv = vvnew;
end


module FunctionsCollection

    function function1(x)
        return 2 * exp.(-2 * (x .- 1).^2) .- exp.(-(x .- 1).^2)
    end

    function function2(x)
        return x.^(1/2)
    end

    function function3(x)
        return exp.(-x) .* sin.(2 * x)
    end

    function function4(x)
        return log.(log.(x))
    end

    function function5(x)
        return 6 * exp.(-2 * x) + 2 * sin.(x) - cos.(x)
    end

end # FunctionsCollection