"""
# `SphericalGeom`

Calculate properties on a sphere.

By default, calculations are made in degrees, and all routines have a final
argument `degrees` which may be set to false for radians.  This is advantageous
when making repeated calculations or when using large arrays in terms of speed.
"""
module SphericalGeom

import StaticArrays: SVector

import Base.step

export
    azimuth,
    cart2geog,
    delta,
    geog2cart,
    great_circle,
    great_circle_azimuth,
    nearest_to_gcp,
    project_to_gcp,
    sample,
    step

"""
    cart2geog(x, y, z, degrees::Bool=true) -> lon, lat, r

Compute the longitude, latitude and radius given the cartesian coordinates `x`,
`y` and `z`, where `x` is at (lon,lat) = (0,0), `y` is at (90°,0) and `z` is
through lat = 90°.
"""
function cart2geog(x, y, z, degrees::Bool=true)
    r = sqrt(x^2 + y^2 + z^2)
    r == 0. && return zero(x), zero(x), zero(x)
    lon = atan(y, x)
    lat = asin(z/r)
    degrees ? (rad2deg(lon), rad2deg(lat), r) : (lon, lat, r)
end

function cart2geog(x::AbstractArray, y::AbstractArray, z::AbstractArray, degrees::Bool=true)
    dims = size(x)
    dims == size(y) == size(z) || throw(ArgumentError("All arrays must have same length"))
    T = promote_type(float.(eltype.((x, y, z)))...)
    lon, lat, r = Array{T}(dims), Array{T}(dims), Array{T}(dims)
    for i in eachindex(lon)
        lon[i], lat[i], r[i] = cart2geog(x[i], y[i], z[i], degrees)
    end
    lon, lat, r
end

"""
    delta(lon1, lat1, lon2, lat2, degrees::Bool=true) -> d

Compute the angular distance `d` on the sphere between two points, (`lon1`, `lat1`)
and (`lon2`, `lat2`).  Points and distance are read and returned in degrees
by default; use `degrees=false` for radians.
"""
function delta(lon1, lat1, lon2, lat2, degrees::Bool=true)
    if degrees
        lon1, lat1, lon2, lat2 = deg2rad(lon1), deg2rad(lat1), deg2rad(lon2), deg2rad(lat2)
    end
    d = atan(sqrt(
               (cos(lat2)*sin(lon2-lon1))^2 + (cos(lat1)*sin(lat2) -
                sin(lat1)*cos(lat2)*cos(lon2-lon1))^2),
               sin(lat1)*sin(lat2) + cos(lat1)*cos(lat2)*cos(lon2-lon1)
              )
    degrees ? rad2deg(d) : d
end

"""
    geog2cart(lon, lat, r, degrees::Bool=true) -> x, y, z
    geog2cart(lon, lat, degrees::Bool=true) -> x, y, z

Return the cartesian coordinates given the geographic longitude, latitude and
radius `lon`, `lat` and `r`.

If `r` is not given, points are returned on the unit sphere.
"""
function geog2cart(lon, lat, r, degrees::Bool=true)
    points_valid(lon, lat, degrees) || error("geog2cart: Points are not on the sphere")
    degrees && begin lon, lat = deg2rad(lon), deg2rad(lat) end
    x = r*cos(lon)*cos(lat)
    y = r*sin(lon)*cos(lat)
    z = r*sin(lat)
    x, y, z
end
function geog2cart(lon::AbstractArray, lat::AbstractArray, r::AbstractArray, degrees::Bool=true)
    size(lon) == size(lat) == size(r) ||
        throw(ArgumentError("Sizes of lon, lat and r must be the same"))
    T = promote_type(float.(eltype.((lon, lat, r)))...)
    x = Array{T}(undef, size(lon))
    y, z = similar(x), similar(x)
    for i in eachindex(lon)
        x[i], y[i], z[i] = geog2cart(lon[i], lat[i], r[i], degrees)
    end
    x, y, z
end
geog2cart(lon::AbstractArray, lat::AbstractArray, degrees::Bool=true) =
    geog2cart(lon, lat, fill(one(eltype(lon)), size(lon)), degrees)

"""
    step(lon, lat, az, delta, degrees::Bool=true) -> lon1, lat1

Compute the end point (`lon1`, `lat1`) reached by travelling on the sphere along
azimuth `az` for `delta` angular distance.  Points, angles and distance are read
and returned in degrees by default; use `degrees=false` for radians.
"""
function step(lon, lat, az, delta, degrees::Bool=true)
    if degrees
        delta <= 180 || error("step: delta cannot be more than 180°")
        lon, lat, az, delta = deg2rad(lon), deg2rad(lat), deg2rad(az), deg2rad(delta)
    else
        delta <= pi || error("step: delta cannot be more than π radians")
    end
    lat2 = asin(sin(lat)*cos(delta) + cos(lat)*sin(delta)*cos(az))
    lon2 = lon + atan(sin(az)*sin(delta)*cos(lat),
                      cos(delta)-sin(lat)*sin(lat2))
    if degrees
        lon2, lat2 = rad2deg(lon2), rad2deg(lat2)
    end
    lon2, lat2
end

"""
    azimuth(lon1, lat1, lon2, lat2, degrees::Bool=true) -> az

Compute the azimuth `az` from point (`lon1`, `lat1`) to (`lon2`, `lat2`) on the sphere.
Points and azimuth are read and returned in degrees by default; use `degrees=false`
for radians.
"""
function azimuth(lon1, lat1, lon2, lat2, degrees::Bool=true)
    if degrees
        lon1, lat1, lon2, lat2 = deg2rad(lon1), deg2rad(lat1), deg2rad(lon2), deg2rad(lat2)
    end
    azimuth = atan(sin(lon2-lon1)*cos(lat2),
                   cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(lon2-lon1))
    degrees ? rad2deg(azimuth) : azimuth
end

"""
    sample(d=5, degrees::Bool=true) -> lon[], lat[]

Create a set of points which approximately evenly sample the sphere, spaced about `d`
degrees apart (default 5°).  Returns arrays `lon` and `lat` containing the longitude
and latitude (degrees).  Spacing and points are read and returned in degrees by default;
use `false` as last argument to use radians.
"""
function sample(d=5, degrees::Bool=true)
    nmax = 50000
    lon = Array(Float64, nmax)
    lat = Array(Float64, nmax)
    n = 1
    dlat = d
    dlon = dlat
    lat[n] = 90.0
    lon[n] = 0.0
    lon_i = 0.0
    lat_i = lat[1] - dlat
    while lat_i > -90.0
        dlon_i = dlon/sind(90.0 - lat_i)
        n_i = round(Int, 360.0/dlon_i)
        for i = 1:n_i
            n += 1
            n <= nmax || error("Number of points greater than predetermined limits ($nmax)")
            lat[n] = lat_i
            lon[n] = lon_i
            lon_i = mod(lon_i + dlon_i, 360.0)
        end
        lon_i = mod(lon_i + dlon_i, 360.0)
        lat_i = lat_i - dlat
    end
    n += 1
    lat[n] = -90.0
    lon[n] = 0.0
    lon[1:n] = mod(lon[1:n] + 180.0, 360.0) - 180.0
    degrees ? (lon[1:n], lat[1:n]) : (rad2deg(lon[1:n], rad2deg(lat[1:n])))
end

"""
    project_to_gcp(lon_gc, lat_gc, lon_p, lat_p, degrees::Bool=true) -> lon, lat

Project the point at (`lon_p`, `lat_p`) onto the great circle defined by the
pole to the circle (`lon_gc`, `lat_gc`), returning the point (`lon`, `lat`).
"""
function project_to_gcp(long, latg, lonp, latp, degrees::Bool=true)
    # Convert to vectors
    g = SVector(geog2cart(long, latg, degrees)...)
    p = SVector(geog2cart(lonp, latp, degrees)...)
    # Pole to the gcp containing g and p
    gp = g × p
    # Check for point and pole being the same
    norm(gp) ≈ 0. && error("Point and pole to plane the same")
    gp = normalize(gp)
    pp = gp × g
    acos(p⋅pp) > π/2 && (pp *= -1)
    lon, lat, r = cart2geog(pp[1], pp[2], pp[3], degrees)
    lon, lat
end

"""
    nearest_to_gcp(lon_gc, lat_gc, lons, lats, degrees::Bool=trye) -> i, lon, lat

Find the nearest point to the great circle defined by the pole (`lon_gc`, `lat_gc`)
which occurs in the vectors `lons` and `lats`.  The index `i` and values at that index
`lon` and `lat` are returned.
"""
function nearest_to_gcp(long, latg, lons::AbstractArray, lats::AbstractArray, degrees::Bool=true)
    length(lons) == length(lats) ||
        throw(ArgumentError("lons and lats must have same number of points"))
    mindist = Inf
    imin = 0
    for (i, (lon, lat)) in enumerate(zip(lons, lats))
        lonp, latp = project_to_gcp(long, latg, lon, lat, degrees)
        dist = delta(lon, lat, lonp, latp, degrees)
        if dist < mindist
            mindist = dist
            imin = i
        end
    end
    imin, lons[imin], lats[imin]
end

"""
    great_circle(lon, lat, azimuth, degrees::Bool=true) -> lon_gc, lat_gc
    great_circle(lon1, lat1, lon2, lat2, degrees::Bool=true) -> lon_gc, lat_gc

Determine the great circle, defined by the pole at (`lon_gc`, `lat_gc`), given by
either (the first form), a point and an azimuth, or (the second) two points on the
sphere.
"""
function great_circle(lon, lat, azimuth, degrees::Bool=true)
    dist = degrees ? 45 : π/4
    lonp, latp = step(lon, lat, azimuth, dist, degrees)
    great_circle(lon, lat, lonp, latp, degrees)
end
function great_circle(lon1, lat1, lon2, lat2, degrees::Bool=true)
    p1 = SVector(geog2cart(lon1, lat1, degrees)...)
    p2 = SVector(geog2cart(lon2, lat2, degrees)...)
    p1 ≈ p2 && error("Two points overlap (are: $p1 and $p2)")
    delta(lon1, lat1, lon2, lat2, degrees) ≈ (degrees ? 180 : π) &&
        error("Points are antipodal (are: $p1 and $p2)")
    gp = p1 × p2
    lon, lat, r = cart2geog(gp[1], gp[2], gp[3], degrees)
    lon, lat
end

"""
    great_circle_azimuth(long, latg, lonp, latp, degrees::Bool=true) -> azimuth

Return the local `azimuth` of the great circle with pole (`long`, `latg`) where
the point (`lonp`, `latp`) projects onto the great circle.
"""
function great_circle_azimuth(long, latg, lonp, latp, degrees::Bool=true)
    lon, lat = project_to_gcp(long, latg, lonp, latp, degrees)
    mod(azimuth(lon, lat, long, latg, degrees) + (degrees ? 90 : π/2), (degrees ? 360 : 2π))
end

"""
    points_valid(lon, lat, degrees::Bool=true) -> ::Bool

Return `true` if all points in arrays `lon` and `lat` are on the sphere.
`lon` is ignored, but `lat` is checked to see if points are in the range
-90°–90° (-π–π).  Points are read and returned in degrees by default; use
`degrees=false` for radians.
"""
points_valid(lon, lat, degrees::Bool=true) =
    degrees ? !any(abs.(lat) .> 90.) : !any(abs.(lat) .> pi/2.)

end # module


using Distributions, DiffResults, ForwardDiff, DiffResults, AdvancedHMC

# Evaluates target distribution π(q)
function π_q(q)
    q₁ = q[1]
    q₂ = q[2]
    in_bound = (-π/2 < q₁ < π/2) & (-π/2 < q₂ < π/2)
    return in_bound*(2/π * exp(-2*(q₁^2 + q₂^2)) + (sin(q₁*q₂)*sin(q₁)*cos(q₂))^2)
end

# Initial sample
q₀ = [0.0, 0.0]

# State space size
Ω_size = length(q₀)

# Number of samples desired
num_samples = 25000

# Number of ϵ updates
num_adapts = 2000

# Evaluates log of target distribution
function ℓπ(q)
    return log(π_q(q))
end

# Evaluates gradient of log of target distribution
# This implementation of NUTS requires the gradient function to be defined this
# way
function ∂ℓπ_∂q(q)
    result = DiffResults.GradientResult(q)
    ForwardDiff.gradient!(result, ℓπ, q)
    return (DiffResults.value(result), DiffResults.gradient(result))
end

# Use Euclidean matrix. 2 here is dimension of sample space
metric = DiagEuclideanMetric(Ω_size)

# Define Hamiltonian function
hamiltonian = Hamiltonian(metric, ℓπ, ∂ℓπ_∂q)

# Initial step size
ϵ₀ = find_good_eps(hamiltonian, q₀)

# Define leapfrog symplectic integrator
lf_integrator = Leapfrog(ϵ₀)

# Define NUTS sampler
sampler = NUTS{MultinomialTS,GeneralisedNoUTurn}(lf_integrator)

# Define ϵ adapter. Used to find apatively determine optimal step size ϵ
adaptor = StanHMCAdaptor(num_adapts, Preconditioner(metric), NesterovDualAveraging(0.8, lf_integrator))

# Get samples. stats stores information for each sample (e.g., acceptance rate)
samples, stats = sample(hamiltonian, sampler, q₀, num_samples, adaptor, num_adapts; progress=true)

samples_mat = zeros(num_samples, 2)
for i=1:num_samples
    samples_mat[i,1] = samples[i][1]
    samples_mat[i,2] = samples[i][2]
end

using PyCall, Plots, Pandas
@pyimport seaborn as sns
using PyPlot
pygui(true)
data = Pandas.DataFrame(Dict(:q₁=>samples_mat[:,1], :q₂=>samples_mat[:,2]))
rc("axes",facecolor = "#f7ece2")
rc("figure",facecolor = "#f7ece2")
rc("savefig",facecolor = "#f7ece2")
sns.jointplot(x = "q₁", y = "q₂", data = data, s = 1, alpha = 0.18)


"""
    tracking_error(asset_returns, benchmark_returns; multiplier=1.0)

Calculates the ex-post Tracking Error based on the standard deviation of the active returns.

# Formula

    TE = std(asset_returns - benchmark_returns) * sqrt(multiplier)

# Arguments
- `asset_returns`:      Vector of asset returns.
- `benchmark_returns`:  Vector of benchmark returns.
- `multiplier`:         Optional scalar multiplier, i.e. use `12` to annualize monthly returns, and use `252` to annualize daily returns.
"""
function tracking_error(asset_returns, benchmark_returns; multiplier=1.0)
    std(asset_returns .- benchmark_returns) * sqrt(multiplier)
end


using Test

using StableRNGs
using Distributions
using BetaML

#TESTRNG = FIXEDRNG # This could change...
TESTRNG = StableRNG(123)

println("*** Testing the Stats module...")

# ==================================
# NEW TEST

println("Testing welchSatterthwaite ...")

d = welchSatterthwaite(2,2,20,20)
@test d == 38

obs                     = rand(copy(TESTRNG),Gamma(2,2),50000)
candidates              = 0:0.01:maximum(obs)
medianWithAbs           = mEstimationBruteForce(obs,candidates)
@test medianWithAbs     ≈ 3.35
medianWithHuberLoss     = mEstimationBruteForce(obs,candidates,x->huberLoss(x,0.0000000001))
@test medianWithAbs     ≈ 3.35
meanWithHuberLoss       = mEstimationBruteForce(obs,candidates,x->huberLoss(x,1000))
@test meanWithHuberLoss ≈ 3.98

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

lB = 100; uB = 200
obs = rand(copy(TESTRNG),Uniform(lB,uB),10000)
q0  = findQuantile(obs,0.2)
q0_2 = sort(obs)[Int(round(length(obs)*0.2))]
@test isapprox(q0,q0_2,atol=0.01)

# ----------------------------------------
out = goodnessOfFitDiscrete([205,26,25,19],[0.72,0.07,0.12,0.09],α=0.05)
@test out.testValue ≈ 5.889610389610388
@test out.p_value ≈  0.1171061913085063

# ----------------------------------------
data    = ["a","y","y","b","b","a","b"]
support = ["a","b","y"]
@test computeDensity(data,support) == [2,3,2]

# ----------------------------------------
support = [0,1,2,3]
data    = [339,455,180,26]
θhat    = sum(data .* support)/(sum(data)*3)
out     = goodnessOfFitDiscrete(data,support,Binomial(3,θhat),compressedData=true,α=0.05,d=1)
@test out.testValue  ≈  0.8828551921498722
@test out.p_value  ≈  0.643117653187048

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

f₀   = Uniform(0,1)
data = [0.8,0.7,0.4,0.7,0.2]
out  = ksTest(data,f₀;α=0.05)
@test out.testValue  ≈  0.6708203932499368
@test out.p_value    ≈  0.009598291426747618

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

#f₀ = Exponential(10)
#f₀ = Normal(0,1)
#f₀ = Uniform(0,10)
f₀ = Normal(0,1)
repetitions = 1000
outs = fill(false,repetitions)
for rep in 1:repetitions
    local data = rand(f₀ ,31)
    local out  = ksTest(data,f₀;α=0.05)
    outs[rep] = out.rejectedH₀
end
@test isapprox(sum(outs)/repetitions,0.05,atol=0.05)

#=
# -------------------------
function computeKSTableValue(f₀,N,α,repetitions=1000)
    Ts = Array{Float64,1}(undef,repetitions)
    for rep in 1:repetitions
        data       = sort(rand(f₀,N))
        N          = length(data)
        cdfhat     = collect(0:N) ./ N
        maxDist    = 0.0
        for (n,x) in enumerate(data)
            dist = max(abs(cdfhat[n]-cdf(f₀,x)), abs(cdfhat[n+1]-cdf(f₀,x)))
            if dist > maxDist
                maxDist = dist
            end
        end
        T          = sqrt(N) * maxDist
        Ts[rep]    = T
    end
    Ts = sort(Ts)
    return Ts[Int(ceil((1-α)*repetitions))]/sqrt(N)
end
(N,α,f₀) = 7,0.05,Normal(20,20)
computeKSTableValue(f₀,N,α,1000000) * sqrt(N)
quantile(Kolmogorov(),1-α)
Distributions.quantile_bisect(KSDist(N),1-α) *sqrt(N)
=#


# this is the funtion that appears in the
# FoggySurface and FoggyContour graphics,
# It is the Octave sombrero, tilted a bit
function FoggyMountainObj(θ₁, θ₂)
   	r = sqrt(θ₁^2.0 + θ₂^2.0) + 1e-10
	z = sin(r) / (2*r)
	z = z + θ₁/40.0 - 2*(θ₁/40.0)^2.0 + θ₂/40.0 - 2*(θ₂/40.0)^2.0
	z = -z/10.0 # switch to minimization
end

function FoggyMountainObj(θ)
	θ₁ = θ[1]
	θ₂ = θ[2]
    FoggyMountainObj(θ₁, θ₂)
end


# # Specifying and fine-tuning prior distributions
#
# This example shows how to specify priors in `ParameterEstimocean`
# and illustrates some of their properties.
#
# ## Install dependencies
#
# First let's make sure we have all required packages installed.

# ```julia
# using Pkg
# pkg"add ParameterEstimocean, Oceananigans, Distributions, CairoMakie"
# ```
#
# Then we import packages

using ParameterEstimocean
using CairoMakie
using Distributions

# # What's a prior?
#
# A "prior" is a probability distribution for an known parameter that
# represents "prior knowledge" of the problem at hand, before we generate
# new knowledge with a new model-data comparison via ensemble Kalman inversion (EKI).
#
# In the (EKI) context, a prior distribution determines (i) the initial distribution
# of parameter values in the ensemble and (ii) the mapping between physical space
# where constraints on parameter values are applied, and the "unconstrained" space
# in which the EKI dynamics evolve (and where parameters are assumed normally-distributed).
#
# # Prior flavors
#
# `ParameterEstimocean` supports three types of prior
# distributions. The normal prior distribution is provided by `Distributions.jl`,

normal_prior = Normal(0.1, 0.1)

# For `LogNormal` priors, we provide a special constructor that takes the `mean` and
# standard deviation `std` of the prior distribution in _constrained_ space:

lognormal_prior = lognormal(mean=0.1, std=0.1)

# `lognormal` calculates `μ` and `σ` for `Distributions.Lognormal(μ, σ)`.
# Parameters with a `Lognormal` prior distribution are _strictly_ positive.
# Finally, the "scaled" logit-normal prior,

logitnormal_prior = ScaledLogitNormal(bounds=(0, 0.2))

# can be used to provide strict upper and lower bounds for parameter values.
#
# ## Samples
#
# Sampling the distributions reveals their properties. We define a function to use
# later, too.

function visualize_priors(priors; samples = 10^6)
    fig = Figure()
    ax = Axis(fig[1, 1:9], xlabel="Random samples of priors", ylabel="Density")

    for (prior, name) in zip(values(priors), keys(priors))
        density!(ax, rand(prior, samples), label=replace(string(name), "_" => " "))
    end

    axislegend(ax)

    return fig, ax
end

fig, ax = visualize_priors(Dict("Normal" => normal_prior,
                                "Log normal" => lognormal_prior,
                                "Scaled logit-normal" => logitnormal_prior))

xlims!(ax, -0.5, 1.5)
save("prior_flavors.svg", fig); nothing # hide

# ![](prior_flavors.svg)

# We note three important features:
#
# 1. `Normal` samples can be negative.
# 2. `Lognormal` samples cannot be negative, but can have large positive values.
# 3. `ScaledLogitNormal` samples have compact support, and thus strict upper and lower bounds.
#
# Boundedness is a very useful property of `ScaledLogitNormal`, so we
# explore specifying `ScaledLogitNormal` priors in more detail.
#
# # Fine-tuning a `ScaledLogitNormal` prior
#
# We can tune the "width" of a `ScaledLogitNormal` by supplying the
# standard-deviation `σ`:

logit_priors = Dict("Narrow (σ = 0.1)"                 => ScaledLogitNormal(σ = 0.1),
                    "Default (σ = 1)"                  => ScaledLogitNormal(σ = 1),
                    "Weird (σ = 4 with bounds=(0, 1))" => ScaledLogitNormal(σ = 4))

fig, ax = visualize_priors(logit_priors)
xlims!(ax, -0.1, 1.1)
save("logit_normal_widths.svg", fig); nothing #hide

# ![](logit_normal_widths.svg)

# Smaller standard deviations produce narrower prior distributions.
# However, weird shapes can result when a standard deviation is specified
# that's larger than the bounds interval (eg `σ > diff(bounds)`).
# In the above example we're using the default `bounds = (0, 1)`.
#
# Another way to build `ScaledLogitNormal` prior is to specify
# a central `interval` and `mass`. This allows us to shift the center of
# mass relative to the bounds (here we use the default `bounds=(0, 1)`),
# and provides a slightly safer way to tune a distribution.

shifted_priors = Dict("Shifted left"  => ScaledLogitNormal(interval=(0.2, 0.3), mass=0.9),
                      "Shifted right" => ScaledLogitNormal(interval=(0.7, 0.8), mass=0.9),
                      "Centered"      => ScaledLogitNormal(interval=(0.2, 0.8), mass=0.6))

fig, ax = visualize_priors(shifted_priors)
xlims!(ax, -0.1, 1.1)
save("logit_normal_shifting.svg", fig); nothing #hide

# ![](logit_normal_shifting.svg)
#
# The "Centered" case uses a `mass` that's proportional to the relative width spanned
# by `interval` (eg `mass = diff(interval) / diff(bounds)`) to produce a nearly
# uniform distribution within the bounds.


## Basic weight functions

export LebesgueMeasure,
    Lebesgue,
    LebesgueDomain,
    lebesguemeasure,
    dx,
    LegendreWeight,
    JacobiWeight,
    ChebyshevWeight,
    ChebyshevTWeight,
    ChebyshevUWeight,
    LaguerreWeight,
    HermiteWeight,
    GaussianWeight,
    DiracWeight,
    point,
    jacobi_α,
    jacobi_β,
    laguerre_α

####################
# Lebesgue measures
####################

"Supertype of Lebesgue measures"
abstract type LebesgueMeasure{T} <: Weight{T} end

unsafe_weightfun(μ::LebesgueMeasure, x) = one(codomaintype(μ))

islebesguemeasure(m::Measure) = false
islebesguemeasure(m::LebesgueMeasure) = true

==(μ1::LebesgueMeasure, μ2::LebesgueMeasure) = support(μ1) == support(μ2)

"The Lebesgue measure on the space `FullSpace{T}`."
struct Lebesgue{T} <: LebesgueMeasure{T}
end

Lebesgue() = Lebesgue{Float64}()
similar(μ::Lebesgue, ::Type{T}) where {T} = Lebesgue{T}()

Base.show(io::IO, μ::Lebesgue) = print(io, "dx")

"The Lebesgue measure on the unit interval `[0,1]`."
struct LebesgueUnit{T} <: LebesgueMeasure{T}
end

LebesgueUnit() = LebesgueUnit{Float64}()
similar(μ::LebesgueUnit, ::Type{T}) where {T <: Real} = Lebesgue{T}()
support(μ::LebesgueUnit{T}) where {T} = UnitInterval{T}()

isnormalized(μ::LebesgueUnit) = true

Base.show(io::IO, μ::LebesgueUnit) = print(io, "dx(0..1)")

"Lebesgue measure supported on a general domain."
struct LebesgueDomain{T} <: LebesgueMeasure{T}
    domain  ::  Domain{T}
end

similar(μ::LebesgueDomain, ::Type{T}) where {T} = LebesgueDomain{T}(μ.domain)
support(m::LebesgueDomain) = m.domain

Base.show(io::IO, mime::MIME"text/plain", μ::LebesgueDomain) = composite_show(io, mime, μ)
Display.displaystencil(μ::LebesgueDomain) = ["dx(", support(μ), ")"]

###############
# Dirac weight
###############

"A continuous Dirac measure at a point"
struct DiracWeight{T} <: Weight{T}
    point   ::  T
end

similar(μ::DiracWeight, ::Type{T}) where {T} = DiracWeight{T}(μ.point)

point(μ::DiracWeight) = μ.point
support(μ::DiracWeight) = Point(μ.point)
isnormalized(μ::DiracWeight) = true
unsafe_weightfun(μ::DiracWeight, x) = convert(codomaintype(μ), Inf)

Base.show(io::IO, μ::DiracWeight) = iszero(point(μ)) ? print(io, "δ(x)dx") : print(io, "δ(x-$(repr(point(μ))))dx")

###################################
# Classical orthogonal polynomials
###################################

"The Legendre weight is a Lebesgue measure on the interval `[-1,1]`."
struct LegendreWeight{T} <: LebesgueMeasure{T}
end
LegendreWeight() = LegendreWeight{Float64}()

similar(μ::LegendreWeight, ::Type{T}) where {T <: Real} = LegendreWeight{T}()
support(μ::LegendreWeight{T}) where {T} = ChebyshevInterval{T}()

Base.show(io::IO, μ::LegendreWeight) = print(io, "dx(-1..1)  (Legendre)")
Display.object_parentheses(μ::LegendreWeight) = true

"Supertype of Jacobi weights."
abstract type AbstractJacobiWeight{T} <: Weight{T} end

==(μ1::AbstractJacobiWeight, μ2::AbstractJacobiWeight) =
    jacobi_α(μ1) == jacobi_α(μ2) && jacobi_β(μ1) == jacobi_β(μ2)

"The Jacobi weight on the interval `[-1,1]`."
struct JacobiWeight{T} <: AbstractJacobiWeight{T}
    α   ::  T
    β   ::  T

    JacobiWeight{T}(α = zero(T), β = zero(T)) where {T} = new(α, β)
end
JacobiWeight() = JacobiWeight{Float64}()
JacobiWeight(α, β) = JacobiWeight(promote(α, β)...)
JacobiWeight(α::T, β::T) where {T<:AbstractFloat} = JacobiWeight{T}(α, β)
JacobiWeight(α::N, β::N) where {N<:Number} = JacobiWeight(float(α), float(β))

jacobi_weightfun(x, α, β) = (1+x)^α * (1-x)^β

similar(μ::JacobiWeight, ::Type{T}) where {T <: Real} = JacobiWeight{T}(μ.α, μ.β)
support(μ::JacobiWeight{T}) where {T} = ChebyshevInterval{T}()
unsafe_weightfun(μ::JacobiWeight, x) = jacobi_weightfun(x, μ.α, μ.β)

jacobi_α(μ::JacobiWeight) = μ.α
jacobi_β(μ::JacobiWeight) = μ.β

Base.show(io::IO, μ::JacobiWeight) = print(io, "(1+x)^$(jacobi_α(μ))(1-x)^$(jacobi_β(μ)) dx  (Jacobi)")
Display.object_parentheses(μ::JacobiWeight) = true

"""
The `Chebyshev` or `ChebyshevT` weight is the measure on `[-1,1]` with the
Chebyshev weight function `w(x) = 1/√(1-x^2)`.
"""
struct ChebyshevTWeight{T} <: AbstractJacobiWeight{T}
end
ChebyshevTWeight() = ChebyshevTWeight{Float64}()

const ChebyshevWeight = ChebyshevTWeight

chebyshev_weight_firstkind(x) = 1/sqrt(1-x^2)

jacobi_α(μ::ChebyshevTWeight{T}) where {T} = -one(T)/2
jacobi_β(μ::ChebyshevTWeight{T}) where {T} = -one(T)/2

similar(μ::ChebyshevTWeight, ::Type{T}) where {T <: Real} = ChebyshevTWeight{T}()
support(μ::ChebyshevTWeight{T}) where {T} = ChebyshevInterval{T}()
unsafe_weightfun(μ::ChebyshevTWeight, x) = chebyshev_weight_firstkind(x)

weightfunction(μ::ChebyshevTWeight) = chebyshev_weight_firstkind

Base.show(io::IO, μ::ChebyshevTWeight) = print(io, "1/√(1+x)^2 dx  (ChebyshevT)")
Display.object_parentheses(μ::ChebyshevTWeight) = true

"""
The ChebyshevU weight is the measure on `[-1,1]` with the Chebyshev weight
function of the second kind `w(x) = √(1-x^2).`
"""
struct ChebyshevUWeight{T} <: AbstractJacobiWeight{T}
end
ChebyshevUWeight() = ChebyshevUWeight{Float64}()

chebyshev_weight_secondkind(x) = sqrt(1-x^2)

jacobi_α(μ::ChebyshevUWeight{T}) where {T} = one(T)/2
jacobi_β(μ::ChebyshevUWeight{T}) where {T} = one(T)/2

similar(μ::ChebyshevUWeight, ::Type{T}) where {T <: Real} = ChebyshevUWeight{T}()
support(μ::ChebyshevUWeight{T}) where {T} = ChebyshevInterval{T}()
unsafe_weightfun(μ::ChebyshevUWeight, x) = chebyshev_weight_secondkind(x)

weightfunction(μ::ChebyshevUWeight) = chebyshev_weight_secondkind

Base.show(io::IO, μ::ChebyshevUWeight) = print(io, "√(1+x)^2 dx  (ChebyshevU)")
Display.object_parentheses(μ::ChebyshevUWeight) = true

convert(::Type{JacobiWeight}, μ::LegendreWeight{T}) where {T} =
    JacobiWeight{T}(0, 0)
convert(::Type{JacobiWeight}, μ::ChebyshevTWeight{T}) where {T} =
    JacobiWeight{T}(-one(T)/2, -one(T)/2)
convert(::Type{JacobiWeight}, μ::ChebyshevUWeight{T}) where {T} =
    JacobiWeight{T}(one(T)/2, one(T)/2)

function convert(::Type{ChebyshevTWeight}, μ::JacobiWeight{T}) where {T}
    (jacobi_α(μ) ≈ -one(T)/2 && jacobi_β(μ) ≈ -one(T)/2) || throw(InexactError(:convert, ChebyshevTWeight, μ))
    ChebyshevTWeight{T}()
end

function convert(::Type{ChebyshevUWeight}, μ::JacobiWeight{T}) where {T}
    (jacobi_α(μ) ≈ one(T)/2 && jacobi_β(μ) ≈ one(T)/2) || throw(InexactError(:convert, ChebyshevUWeight, μ))
    ChebyshevUWeight{T}()
end

function convert(::Type{LegendreWeight}, μ::JacobiWeight{T}) where {T}
    (μ.α ≈ 0 && μ.β ≈ 0) || throw(InexactError(:convert, LegendreWeight, μ))
    LegendreWeight{T}()
end

jacobi_α(μ::LegendreWeight{T}) where {T} = zero(T)
jacobi_β(μ::LegendreWeight{T}) where {T} = zero(T)

==(μ1::AbstractJacobiWeight, μ2::LegendreWeight) =
    jacobi_α(μ1) == jacobi_α(μ2) && jacobi_β(μ1) == jacobi_β(μ2)
==(μ1::LegendreWeight, μ2::AbstractJacobiWeight) =
    jacobi_α(μ1) == jacobi_α(μ2) && jacobi_β(μ1) == jacobi_β(μ2)

"The generalised Laguerre measure on the halfline `[0,∞)`."
struct LaguerreWeight{T} <: Weight{T}
    α   ::  T

    LaguerreWeight{T}(α = zero(T)) where {T} = new(α)
end
LaguerreWeight() = LaguerreWeight{Float64}()
LaguerreWeight(α::T) where {T<:AbstractFloat} = LaguerreWeight{T}(α)
LaguerreWeight(α) = LaguerreWeight(float(α))

laguerre_weightfun(x, α) = exp(-x) * x^α

similar(μ::LaguerreWeight, ::Type{T}) where {T <: Real} = LaguerreWeight{T}(μ.α)
support(μ::LaguerreWeight{T}) where {T} = HalfLine{T}()
isnormalized(m::LaguerreWeight) = m.α == 0

unsafe_weightfun(μ::LaguerreWeight, x) = laguerre_weightfun(x, μ.α)

laguerre_α(μ::LaguerreWeight) = μ.α

Base.show(io::IO, μ::LaguerreWeight) =
    laguerre_α(μ) == 0 ? print(io, "exp(-x)dx  (Laguerre)") : print(io, "x^$(laguerre_α(μ))exp(-x)dx  (Laguerre)")
Display.object_parentheses(μ::LaguerreWeight) = true

"The Hermite measure with weight exp(-x^2) on the real line."
struct HermiteWeight{T} <: Weight{T}
end
HermiteWeight() = HermiteWeight{Float64}()

hermite_weightfun(x) = exp(-x^2)

similar(μ::HermiteWeight, ::Type{T}) where {T <: Real} = HermiteWeight{T}()
unsafe_weightfun(μ::HermiteWeight, x) = hermite_weightfun(x)

weightfunction(μ::HermiteWeight) = hermite_weightfun

Base.show(io::IO, μ::HermiteWeight) = print(io, "exp(-x^2)dx  (Hermite)")
Display.object_parentheses(μ::HermiteWeight) = true

"The Gaussian measure with weight exp(-|x|^2/2)."
struct GaussianWeight{T} <: Weight{T}
end
GaussianWeight() = GaussianWeight{Float64}()

gaussian_weightfun(x, ::Type{T} = prectype(x)) where {T} =
    1/(2*convert(T, pi))^(length(x)/2) * exp(-norm(x)^2)

similar(μ::GaussianWeight, ::Type{T}) where {T} = GaussianWeight{T}()
isnormalized(μ::GaussianWeight) = true
unsafe_weightfun(μ::GaussianWeight, x) = gaussian_weightfun(x, prectype(μ))

Base.show(io::IO, μ::GaussianWeight{T}) where {T<:StaticTypes} =
    print(io, "1/(2π)^($(euclideandimension(T)/2)) exp(-|x|^2)dx  (Gauss)")
Base.show(io::IO, μ::GaussianWeight) = print(io, "C(d) exp(-|x|^2)dx  (Gauss)")
Display.object_parentheses(μ::GaussianWeight) = true

"The Lebesgue measure associated with the given domain"
lebesguemeasure(domain::UnitInterval{T}) where {T} = LebesgueUnit{T}()
lebesguemeasure(domain::ChebyshevInterval{T}) where {T} = LegendreWeight{T}()
lebesguemeasure(domain::FullSpace{T}) where {T} = Lebesgue{T}()
lebesguemeasure(domain::Domain{T}) where {T} = LebesgueDomain{T}(domain)

dx(domain::Domain) = lebesguemeasure(domain)


# # [Sparse Identification with noisy data](@id noisy_sindy)
#
# Many real world data sources are corrupted with measurment noise, which can have
# a big impact on the recovery of the underlying equations of motion. This example show how we can
# use [`collocation`](@ref collocation) and [`batching`](@ref DataSampler) to perform SINDy in the presence of
# noise.

using DataDrivenDiffEq
using LinearAlgebra
using ModelingToolkit
using OrdinaryDiffEq
#md using Plots
#md gr()

function pendulum(u, p, t)
    x = u[2]
    y = -9.81sin(u[1]) - 0.3u[2]^3 -3.0*cos(u[1]) - 10.0*exp(-((t-5.0)/5.0)^2)
    return [x;y]
end

u0 = [0.99π; -1.0]
tspan = (0.0, 15.0)
prob = ODEProblem(pendulum, u0, tspan)
sol = solve(prob, Tsit5(), saveat = 0.01);

# We add random noise to our measurements.

X = sol[:,:] + 0.2 .* randn(size(sol));
ts = sol.t;

#md plot(ts, X', color = :red)
#md plot!(sol, color = :black)

# To estimate the system, we first create a [`DataDrivenProblem`](@ref) via feeding in the measurement data.
# Using a [Collocation](@ref) method, it automatically provides the derivative and smoothes the trajectory. Control signals can be passed
# in as a function `(u,p,t)->control` or an array of measurements.

prob = ContinuousDataDrivenProblem(X, ts, GaussianKernel() ,
    U = (u,p,t)->[exp(-((t-5.0)/5.0)^2)], p = ones(2))

#md plot(prob, size = (600,600))

# Now we infer the system structure. First we define a [`Basis`](@ref) which collects all possible candidate terms.
# Since we want to use SINDy, we call `solve` with an [`Optimizer`](@ref sparse_optimization), in this case [`STLSQ`](@ref) which iterates different sparsity thresholds
# and returns a pareto optimal solution of the underlying [`sparse_regression!`](@ref). Note that we include the control signal in the basis as an additional variable `c`.

@variables u[1:2] c[1:1]
@parameters w[1:2]
u = collect(u)
c = collect(c)
w = collect(w)

h = Num[sin.(w[1].*u[1]);cos.(w[2].*u[1]); polynomial_basis(u, 5); c]

basis = Basis(h, u, parameters = w, controls = c)

# To solve the problem, we also define a [`DataSampler`](@ref) which defines randomly shuffled minibatches of our data and selects the
# best fit.

sampler = DataSampler(Batcher(n = 5, shuffle = true, repeated = true))
λs = exp10.(-10:0.1:-1)
opt = STLSQ(λs)
res = solve(prob, basis, opt, progress = false, sampler = sampler, denoise = false, normalize = false, maxiter = 5000)
println(res) #hide

# !!! info
#     A more detailed description of the result can be printed via `print(res, Val{true})`, which also includes the discovered equations and parameter values.
#
# Where the resulting [`DataDrivenSolution`](@ref) stores information about the inferred model and the parameters:

system = result(res)
params = parameters(res)
println(system) #hide
println(params) #hide

# And a visual check of the result can be perfomed via plotting the result

#md plot(
#md     plot(prob), plot(res), layout = (1,2)
#md )

#md # ## [Copy-Pasteable Code](@id autoregulation_copy_paste)
#md #
#md # ```julia
#md # @__CODE__
#md # ```

## Test #src
for r_ in [res] #src
    @test all(aic(r_) .> 1e3) #src
    @test all(determination(r_) .>= 0.9) #src
end #src


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

The radical of n, rad(n), is the product of distinct prime factors of n. For
example, 504 = 2^3 × 3^2 × 7, so rad(504) = 2 × 3 × 7 = 42.

We shall define the triplet of positive integers (a, b, c) to be an abc-hit if:

    gcd(a, b) = gcd(a, c) = gcd(b, c) = 1
    a < b
    a + b = c
    rad(abc) < c

For example, (5, 27, 32) is an abc-hit, because:

    gcd(5, 27) = gcd(5, 32) = gcd(27, 32) = 1
    5 < 27
    5 + 27 = 32
    rad(4320) = 30 < 32

It turns out that abc-hits are quite rare and there are only thirty-one abc-hits
for c < 1000, with ∑c = 12523.

Find ∑c for c < 120000.

--------------------------------------------------------------------------------

Analyse
-------

1) a < b; a + b = c < bound
    a)  ⇒ 2a < a + b = c < bound - 1 ⇒ 1 ≤ a < bound ÷ 2
    b)  ⇒ b < bound - a

2) if a and b are coprime, then c = a+b is coprime with a and with b
    => gcd(a, b) = 1 ⇒ gcd(a, c) = gcd(b, c) = 1

3) gcd(a, b) == 1 ⇔ gcd(rad(a), rad(b)) == 1

4) if a and b are coprime, the rad(ab) = rad(a) rad(b)

5) rad(n) = 1 if n = 1
          ≥ 2 if n > 1

6) So the conditions can be simplifed to
        gcd(a, b) = 1
        1 ≤ a < bound ÷ 2
        a < b < bound - a
        c = a + b
        rad(a) rad(b) rad(c) < c

7) Precomputing rad(n) for all n in 2..bound-2

case a=1
--------
    gcd(1,b) = 1 is granted
    2 ≤ b ≤ bound-2
    c = 1+b
    rad(b) rad(c) < c ⇒ rad(b) < (1+b) ÷ 2
                      ⇒ rad(b) rad(1+b) < 1+b

    Therefore the constraints are:
        1 < b < bound-1
        c = b+1
        rad(b) rad(b+1) < (b+1)

case a>1
--------
    a < b => a+1 ≤ b,
    2a+1 ≤ a + b = c < bound => a < (bound-1)÷2
                             => a ≤ (bound-1)÷2 -1
    rad(a) ≥ 2, rad(b) ≥ 2, rad(c) ≥ 2
    rad(a) rad(b) rad(a+b) < a+b ⇒ rad(a) < c ÷ 4
                                 ⇒ rad(b) < c ÷ (2 rad(a))

    Therefore the constraints are:
        2   ≤ a ≤ (bound-1)÷2 -1
        a+1 ≤ b ≤ bound - 1 - a
        gcd(a,b) = 1
        c = a + b
        rad(a) < c ÷ 4                  < (bound-1) ÷ 4
        rad(b) < c ÷ (2 rad(a))         < (bound-1) ÷ 4
        rad(c) < c ÷ (rad(a) rad(b))    < (bound-1) ÷ 4

Conclusion
==========

The principle behind the algorithm is to precompute rad(n) for 2 ≤ n ≤ bound-, and to proceed to all possible tests before computing gcd(rad(a), rad(b)).

Note that precomputing gcd(rad(a), rad(b)) for all possible a and b is not efficient.

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

import Primes

function euler(bound::Int)
    #precimpute the radicals
    rad = Array{Int}(bound-1)
    for n in 1:bound-1
        rad[n]=prod(keys(Primes.factor(n)))
    end

    res::Int = 0

    # case a=1
    for b in 2:bound-2
        c=b+1
        if (rad[b]*rad[c] < c) res+=c end
    end

    # case a>1
    for a in 2:(bound-1) ÷ 2 -1
        rad_a = rad[a]
        if (rad_a ≥ (bound-1) ÷ 4) continue end

        for b in a+1:bound-1-a
            rad_b = rad[b]
            c=a+b
            if (rad_a * rad_b * rad[c] ≥ c) continue end
            if (gcd(rad_a, rad_b) == 1) res += c end
        end
    end

    res
end

println("  1_000: $(euler(  1_000))")
@time println("120_000: $(euler(120_000))")


if VERSION >= v"0.6.0-"
    using SpecialFunctions: besseli
end

# This file contains different window functions.
# The function getWindow returns a pair of window functions based on a string

function getWindow(window::Symbol)
    if window == :gauss
        return window_gauss, window_gauss_hat
    elseif window == :spline
        return window_spline, window_spline_hat
    elseif window == :kaiser_bessel_rev
        return window_kaiser_bessel_rev, window_kaiser_bessel_rev_hat
    else # default to kaiser_bessel
        return window_kaiser_bessel, window_kaiser_bessel_hat
    end
end

function window_kaiser_bessel(x,n,m,sigma)
    b = pi*(2-1/sigma)
    arg = m^2-n^2*x^2
    if abs(x) < m/n
        y = sinh(b*sqrt(arg))/sqrt(arg)/pi
    elseif abs(x) > m/n
        y = zero(x)
    else
        y = b/pi
    end
    return y
end

function window_kaiser_bessel_hat(k,n,m,sigma)
    b = pi*(2-1/sigma)
    return besseli(0,m*sqrt(b^2-(2*pi*k/n)^2))
end

function window_kaiser_bessel_rev(x,n,m,sigma)
    b = pi*(2-1/sigma)
    if abs(x) < m/n
        arg = m*b*sqrt(1-(n*x/m)^2)
        y = 0.5/m*besseli(0,arg)
    else
        y = zero(x)
    end
    return y
end

function window_kaiser_bessel_rev_hat(k,n,m,sigma)
    b = pi*(2-1/sigma)

    arg = sqrt(complex((2*pi*m*k/n)^2-(m*b)^2))
    return sinc(arg/pi)
end

function window_gauss(x,n,m,sigma)
    b = m / pi
    if abs(x) < m/n
        y = 1 / sqrt(pi*b) * exp(-(n*x)^2 / b)
    else
        y =  zero(x)
    end
    return y
end

function window_gauss_hat(k,n,m,sigma)
    b = m / pi
    return exp(-(pi*k/(n))^2 * b)
end

function cbspline(m,x)
    if m == 1
        if x>=0 && x<1
            y = one(x)
        else
            y = zero(x)
        end
    else
        y = x/(m-1)*cbspline(m-1,x) + (m-x)/(m-1)*cbspline(m-1,x-1)
    end
    return y
end

function window_spline(x,n,m,sigma)
    if abs(x) < m/n
        y = cbspline(2*m, n*x+m)
    else
        y = zero(x)
    end
    return y
end

function window_spline_hat(k,n,m,sigma)
    return (sinc(k/n))^(2*m)
end


d=2;
A=Array{Int64,3}(d,d,d); B=Array{Int64,3}(d,d,d); C=zeros(Int64,d,d)
for i in 1:d,j in 1:d,k in 1:d
    A[i,j,k] = 2i+j^2-k^3
    B[i,j,k] = i+2j+4k
end

tic()
for i in 1:size(A,1)
    for j in 1:size(A,3)
        for k in 1:size(A,1)
            for n in 1:size(B,3)
                C[j,n] += A[i,j,k]*B[k,i,n]
            end
        end
    end
end
toc()
print("Matrix C is ", C)

A_permuted = permutedims(A,[2,1,3]); B_permuted = permutedims(B,[2,1,3]);
A_reshaped = reshape(A_permuted,2,4); B_reshaped = reshape(B_permuted,4,2);
tic()
C_reshaped = A_reshaped*B_reshaped;
toc()
print("Matrix C is ", C_reshaped)

A=rand(Int64,100,100,100); B=rand(Int64,100,100,100);
C=zeros(Int64,100,100);

tic()
for i in 1:size(A,1)
    for j in 1:size(A,3)
        for k in 1:size(A,1)
            for n in 1:size(B,3)
                C[j,n] += A[i,j,k]*B[k,i,n]
            end
        end
    end
end
toc()

A_reshaped=reshape(permutedims(A,[2,1,3]),100,10000);
B_reshaped=reshape(permutedims(B,[2,1,3]),10000,100);
tic()
C_reshaped = A_reshaped*B_reshaped;
toc()

X=Complex{Float64}[0.0 1.0;1.0 0.0];
Y=Complex{Float64}[0.0im -1.0im;1.0im 0.0im];
Z=Complex{Float64}[1.0 0.0;0.0 -1.0];
h=kron(X,X)+kron(Y,Y)+kron(Z,Z);
N=4; C=2; d=2;
H=zeros(Complex{Float64},d^N,d^N);

for l in 0:N-C
    H+=kron(kron(eye(d^l),h),eye(d^(N-C-l)))
end
E,V = eig(H)
print("The first eigenvalues are: ", E[1:11])

function diagonalization_model(h::Matrix{T},N::Int64;C::Int64=2,
        d::Int64=2,method_sparse::Bool=true) where {T}
    H=zeros(T,d^N,d^N)
    for l in 0:N-C
        H+=kron(kron(eye(d^l),h),eye(d^(N-C-l),d^(N-C-l)))
    end
    if method_sparse
        E,V = eigs(H;nev=10,which=:SR)
    else
        E,V = eig(H)
    end
    return E
end

@time diagonalization_model(h,12)

@time diagonalization_model(h,12,method_sparse=false)

function permR2(i::Int64,P::Int64)
    return mod.(collect(1:P)+i-2,P)+1
end

function permL2(i::Int64,P::Int64)
    return mod.(collect(1:P)-i,P)+1
end

function H_wavefunction(h_tilde::Matrix{T},phi::Array{T,P},N::Int64;
        OBC::Int64=1,d::Int64=2,C::Int64=2) where {T,P}
    H_phi = zeros(T,fill(d,C+1)...,d^(N-C-1))
    for i in 1:N-OBC
        if N == 1
            phi = permutedims(phi,permR2(1,P))
            H_phi = permutedims(H_phi,permR2(1,P))
            phi_tmp = h_tilde*reshape(phi,d^C,d^(N-C))
            phi_tmp = reshape(phi_tmp,fill(d,C+1)...,d^(N-C-1))
            H_phi = reshape(H_phi,fill(d,C+1)...,d^(N-C-1))
            H_phi += phi_tmp
        else
            phi = permutedims(phi,permR2(2,P))
            H_phi = permutedims(H_phi,permR2(2,P))
            phi_tmp = h_tilde*reshape(phi,d^C,d^(N-C))
            phi_tmp = reshape(phi_tmp,fill(d,C+1)...,d^(N-C-1))
            H_phi = reshape(H_phi,fill(d,C+1)...,d^(N-C-1))
            phi = reshape(phi,fill(d,C+1)...,d^(N-C-1))
            H_phi += phi_tmp
        end
    end
    if OBC == 1
        return(reshape(permutedims(H_phi,permR2(2,P)),d^N))
    elseif OBC == 0
        return(reshape(H_phi,d^N))
    end
end

function power_method(h::Matrix{T},N::Int64;OBC::Int64=1,d::Int64=2,
        C::Int64=2,tol::Float64=1e-4) where {T}
    #phi = rand(T,fill(d,C+1)...,d^(N-C-1))
    #phi = collect(Complex{Float64},1:2^N)
    phi = rand(T,d^N)
    phi = phi/sqrt(dot(conj(phi),phi))
    phi = reshape(phi,fill(d,C+1)...,d^(N-C-1))
    list_E_tmp = []
    E,V = eig(h)
    alpha=max(E...)
    h_tilde = h - alpha*kron(eye(d),eye(d))
    i = 1
    bool = true
    while bool
        H_phi = H_wavefunction(h_tilde,phi,N,OBC=OBC)
        #H_phi = H_phi/dot(conj(H_phi),H_phi)
        phi = reshape(phi,d^N)
        #E = dot(conj(phi),H_phi)/dot(conj(phi),phi) + α*(N-OBC)
        E = dot(conj(phi),H_phi) + alpha*(N-OBC)
        #println("E = ", E)
        push!(list_E_tmp,E)
        if i>2 && abs(list_E_tmp[i]-list_E_tmp[i-1])<tol
           bool = false
           println("Desired precision reached!")
        end
        i+=1
        phi = reshape(phi,fill(d,C+1)...,d^(N-C-1))
        phi = reshape(H_phi/sqrt(dot(conj(H_phi),H_phi)),fill(d,C+1)...,d^(N-C-1))
    end
    println("E = ",E)
    println("Number of iterations = ", i)
end

@time power_method(h,4,tol=1e-9)

@time power_method(h,12,tol=1e-9)

@time power_method(h,16,OBC=0,tol=1e-9)

@time power_method(h,16,tol=1e-9)

@time power_method(h,25)

@time power_method(h,25,OBC=0)


@doc """
  Splits a floating-point number `a` into two parts `a = h + l` such that the
  halves `h` and `l` are non-overlapping and `|x| ≥ |y|`. That is, both halves
  have at most `s-1` nonzero bits, where `s = ceil(p/2)` and `p` is the
  precision (`p = 24` for `Float32` and `p = 53` for `Float64`).

  References:

    * T.J. Dekker, A floating-point technique for extending the available
      precision, Numerische Mathematik 18, pp. 224–242, 1971.
  """ ->
# TODO: Extend the definition to floating-point arrays
for T in (Float32, Float64)
  @eval begin
    function fsplit(a::$T)
      # Float32: factor = 2^12 + 1 = 4097
      # Float64: factor = 2^27 + 1 = 134217729
      f = $(convert(T, 2^ceil(precision(T)/2)+1));
      c = a * f
      h = c - (c-a)
      l = a - h
      h, l
    end
  end
end

@doc """
  Computes the error-free multiplication `a * b = x + y`, where `x = fl(a * b)`,
  for floating-point numbers `a` and `b`. This algorithm uses all cross products
  between the halves generated by `fsplit` from `a` and `b`. It requires 17
  flops.

  References:

    * T.J. Dekker, A floating-point technique for extending the available
      precision, Numerische Mathematik 18, pp. 224–242, 1971.
  """ ->
# TODO: Extend the definition to floating-point arrays
function err_mul{T<:IEEE754}(a::T, b::T)
  x = a * b
  ah, al = fsplit(a)
  bh, bl = fsplit(b)
  y = al*bl - (((x - ah*bh) - al*bh) - ah*bl)
  x, y
end

@doc """
  Computes the error-free transformation `a * b = x + y`, where `x = fl(a * b)`,
  for floating-point numbers `a` and `b`. By using the fused multiply-add (FMA)
  operation, it only requires 2 flops instead of the 17 flops required by
  `err_mul` but the improvement in execution time typically a less than that.

  References:

    * T. Ogita, S.M. Rump, and S. Oishi, Accurate sum and dot product, SIAM
      Journal on Scientific Computing 26, pp. 1955–1988, 2005.
  """ ->
# TODO: Extend the definition to floating-point arrays
function err_fast_mul{T<:IEEE754}(a::T, b::T)
  x = a * b
  y = fma(a, b, -x)
  x, y
end


x0 = 10
y0 = 10
r = 1
x = r.*[-1.1,0,0,1] .+ x0
y = r.*[0.0,1,-1,0] .+ y0
result = Circle([x0,y0],r,[x y],:kasa)
coefs = coef(result)
@test x0 ≈ coefs[1]
@test y0 ≈ coefs[2]
@test r ≈ coefs[3]
@test dof(result) == 1
@test all(coef(result) .== [result.position...,result.radius])
@test length(coefnames(result)) == length(coef(result))
@test residuals(result) ≈ [0.1,0,0,0]
@test rss(result) ≈ 0.1^2

x = r.*[-1,0,0,1] .+ x0
result = fit(Circle,x,y)
coefs = coef(result)
@test x0 ≈ coefs[1]
@test y0 ≈ coefs[2]
@test r ≈ coefs[3]
@test algorithm(result) == :kasa

result = fit(Circle,x,y,alg=:pratt)
coefs = coef(result)
@test x0 ≈ coefs[1]
@test y0 ≈ coefs[2]
@test r ≈ coefs[3]
@test algorithm(result) == :pratt

result = fit(Circle,x,y,alg=:taubin)
coefs = coef(result)
@test x0 ≈ coefs[1]
@test y0 ≈ coefs[2]
@test r ≈ coefs[3]
@test algorithm(result) == :taubin


using Choices
using Test
using Base.Iterators: take, countfrom

# test iterating over the first few emirp numbers
# https://en.wikipedia.org/wiki/Emirp

# first some common definitions:
divides(x::Integer, y::Integer) = isinteger(x / y)
isprime(x::Integer) = !any(y -> divides(x, y), 2:trunc(Integer, √x))
reverse_digits(x::Int) = parse(Int, reverse(string(x)))

# the conventional way to do it with a Generator:
function isemirp(x::Int)
  isprime(x) || return false
  rev_x = reverse_digits(x)
  return (x != rev_x && isprime(rev_x))
end
generated_emirps = [take((x for x ∈ countfrom() if isemirp(x)), 10)...]

# a way to do it with Choices:
emirps_chooser =
  choose(countfrom()) |> x ->
  (isprime(x) ? reverse_digits(x) : fail()) |> rev_x ->
  ((x != rev_x && isprime(rev_x)) ? x : fail())
chosen_emirps = [take(emirps_chooser, 10)...]
cut()

expected_emirps = [13, 17, 31, 37, 71, 73, 79, 97, 107, 113]
@test generated_emirps == expected_emirps
@test chosen_emirps == expected_emirps


using Turing
using DifferentialEquations

# Load StatsPlots for visualizations and diagnostics.
using StatsPlots

using LinearAlgebra

# Set a seed for reproducibility.
using Random
Random.seed!(14);

# Define Lotka-Volterra model.
function lotka_volterra(du, u, p, t)
    # Model parameters.
    α, β, γ, δ = p
    # Current state.
    x, y = u

    # Evaluate differential equations.
    du[1] = (α - β * y) * x # prey
    du[2] = (δ * x - γ) * y # predator

    return nothing
end

# Define initial-value problem.
u0 = [1.0, 1.0]
p = [1.5, 1.0, 3.0, 1.0]
tspan = (0.0, 10.0)
prob = ODEProblem(lotka_volterra, u0, tspan, p)

# Plot simulation.
plot(solve(prob, Tsit5()))

sol = solve(prob, Tsit5(); saveat=0.1)
odedata = Array(sol) + 0.8 * randn(size(Array(sol)))

# Plot simulation and noisy observations.
plot(sol; alpha=0.3)
scatter!(sol.t, odedata'; color=[1 2], label="")

@model function fitlv(data, prob)
    # Prior distributions.
    σ ~ InverseGamma(2, 3)
    α ~ truncated(Normal(1.5, 0.5), 0.5, 2.5)
    β ~ truncated(Normal(1.2, 0.5), 0, 2)
    γ ~ truncated(Normal(3.0, 0.5), 1, 4)
    δ ~ truncated(Normal(1.0, 0.5), 0, 2)

    # Simulate Lotka-Volterra model.
    p = [α, β, γ, δ]
    predicted = solve(prob, Tsit5(); p=p, saveat=0.1)

    # Observations.
    for i in 1:length(predicted)
        data[:, i] ~ MvNormal(predicted[i], σ^2 * I)
    end

    return nothing
end

model = fitlv(odedata, prob)

# Sample 3 independent chains with forward-mode automatic differentiation (the default).
chain = sample(model, NUTS(0.65), MCMCSerial(), 1000, 3; progress=false)

plot(chain)

plot(; legend=false)
posterior_samples = sample(chain[[:α, :β, :γ, :δ]], 300; replace=false)
for p in eachrow(Array(posterior_samples))
    sol_p = solve(prob, Tsit5(); p=p, saveat=0.1)
    plot!(sol_p; alpha=0.1, color="#BBBBBB")
end

# Plot simulation and noisy observations.
plot!(sol; color=[1 2], linewidth=1)
scatter!(sol.t, odedata'; color=[1 2])

@model function fitlv2(data::AbstractVector, prob)
    # Prior distributions.
    σ ~ InverseGamma(2, 3)
    α ~ truncated(Normal(1.5, 0.5), 0.5, 2.5)
    β ~ truncated(Normal(1.2, 0.5), 0, 2)
    γ ~ truncated(Normal(3.0, 0.5), 1, 4)
    δ ~ truncated(Normal(1.0, 0.5), 0, 2)

    # Simulate Lotka-Volterra model but save only the second state of the system (predators).
    p = [α, β, γ, δ]
    predicted = solve(prob, Tsit5(); p=p, saveat=0.1, save_idxs=2)

    # Observations of the predators.
    data ~ MvNormal(predicted.u, σ^2 * I)

    return nothing
end

model2 = fitlv2(odedata[2, :], prob)

# Sample 3 independent chains.
chain2 = sample(model2, NUTS(0.45), MCMCSerial(), 5000, 3; progress=false)

plot(; legend=false)
posterior_samples = sample(chain2[[:α, :β, :γ, :δ]], 300; replace=false)
for p in eachrow(Array(posterior_samples))
    sol_p = solve(prob, Tsit5(); p=p, saveat=0.1)
    plot!(sol_p; alpha=0.1, color="#BBBBBB")
end

# Plot simulation and noisy observations.
plot!(sol; color=[1 2], linewidth=1)
scatter!(sol.t, odedata'; color=[1 2])

function delay_lotka_volterra(du, u, h, p, t)
    # Model parameters.
    α, β, γ, δ = p

    # Current state.
    x, y = u
    # Evaluate differential equations
    du[1] = α * h(p, t - 1; idxs=1) - β * x * y
    du[2] = -γ * y + δ * x * y

    return nothing
end

# Define initial-value problem.
p = (1.5, 1.0, 3.0, 1.0)
u0 = [1.0; 1.0]
tspan = (0.0, 10.0)
h(p, t; idxs::Int) = 1.0
prob_dde = DDEProblem(delay_lotka_volterra, u0, h, tspan, p);

sol_dde = solve(prob_dde; saveat=0.1)
ddedata = Array(sol_dde) + 0.5 * randn(size(sol_dde))

# Plot simulation and noisy observations.
plot(sol_dde)
scatter!(sol_dde.t, ddedata'; color=[1 2], label="")

@model function fitlv_dde(data, prob)
    # Prior distributions.
    σ ~ InverseGamma(2, 3)
    α ~ Truncated(Normal(1.5, 0.5), 0.5, 2.5)
    β ~ Truncated(Normal(1.2, 0.5), 0, 2)
    γ ~ Truncated(Normal(3.0, 0.5), 1, 4)
    δ ~ Truncated(Normal(1.0, 0.5), 0, 2)

    # Simulate Lotka-Volterra model.
    p = [α, β, γ, δ]
    predicted = solve(prob, MethodOfSteps(Tsit5()); p=p, saveat=0.1)

    # Observations.
    for i in 1:length(predicted)
        data[:, i] ~ MvNormal(predicted[i], σ^2 * I)
    end
end

model_dde = fitlv_dde(ddedata, prob_dde)

# Sample 3 independent chains.
chain_dde = sample(model_dde, NUTS(0.65), MCMCSerial(), 300, 3; progress=false)

plot(chain_dde)

plot(; legend=false)
posterior_samples = sample(chain_dde[[:α, :β, :γ, :δ]], 300; replace=false)
for p in eachrow(Array(posterior_samples))
    sol_p = solve(prob_dde, MethodOfSteps(Tsit5()); p=p, saveat=0.1)
    plot!(sol_p; alpha=0.1, color="#BBBBBB")
end

# Plot simulation and noisy observations.
plot!(sol_dde; color=[1 2], linewidth=1)
scatter!(sol_dde.t, ddedata'; color=[1 2])

using Zygote, DiffEqSensitivity

# Sample a single chain with 1000 samples using Zygote.
setadbackend(:zygote)
sample(model, NUTS(0.65), 1000; progress=false)

@model function fitlv_sensealg(data, prob)
    # Prior distributions.
    σ ~ InverseGamma(2, 3)
    α ~ truncated(Normal(1.5, 0.5), 0.5, 2.5)
    β ~ truncated(Normal(1.2, 0.5), 0, 2)
    γ ~ truncated(Normal(3.0, 0.5), 1, 4)
    δ ~ truncated(Normal(1.0, 0.5), 0, 2)

    # Simulate Lotka-Volterra model and use a specific algorithm for computing sensitivities.
    p = [α, β, γ, δ]
    predicted = solve(
        prob;
        p=p,
        saveat=0.1,
        sensealg=InterpolatingAdjoint(; autojacvec=ReverseDiffVJP(true)),
    )

    # Observations.
    for i in 1:length(predicted)
        data[:, i] ~ MvNormal(predicted[i], σ^2 * I)
    end

    return nothing
end;

model_sensealg = fitlv_sensealg(odedata, prob)

# Sample a single chain with 1000 samples using Zygote.
setadbackend(:zygote)
sample(model_sensealg, NUTS(0.65), 1000; progress=false)

u0 = [1.0, 1.0]
tspan = (0.0, 10.0)
function multiplicative_noise!(du, u, p, t)
    x, y = u
    du[1] = p[5] * x
    return du[2] = p[6] * y
end
p = [1.5, 1.0, 3.0, 1.0, 0.1, 0.1]

function lotka_volterra!(du, u, p, t)
    x, y = u
    α, β, γ, δ = p
    du[1] = dx = α * x - β * x * y
    return du[2] = dy = δ * x * y - γ * y
end

prob_sde = SDEProblem(lotka_volterra!, multiplicative_noise!, u0, tspan, p)

ensembleprob = EnsembleProblem(prob_sde)
data = solve(ensembleprob, SOSRI(); saveat=0.1, trajectories=1000)
plot(EnsembleSummary(data))

@model function fitlv_sde(data, prob)
    # Prior distributions.
    σ ~ InverseGamma(2, 3)
    α ~ truncated(Normal(1.3, 0.5), 0.5, 2.5)
    β ~ truncated(Normal(1.2, 0.25), 0.5, 2)
    γ ~ truncated(Normal(3.2, 0.25), 2.2, 4.0)
    δ ~ truncated(Normal(1.2, 0.25), 0.5, 2.0)
    ϕ1 ~ truncated(Normal(0.12, 0.3), 0.05, 0.25)
    ϕ2 ~ truncated(Normal(0.12, 0.3), 0.05, 0.25)

    # Simulate stochastic Lotka-Volterra model.
    p = [α, β, γ, δ, ϕ1, ϕ2]
    predicted = solve(prob, SOSRI(); p=p, saveat=0.1)

    # Early exit if simulation could not be computed successfully.
    if predicted.retcode !== :Success
        Turing.@addlogprob! -Inf
        return nothing
    end

    # Observations.
    for i in 1:length(predicted)
        data[:, i] ~ MvNormal(predicted[i], σ^2 * I)
    end

    return nothing
end;

model_sde = fitlv_sde(odedata, prob_sde)

setadbackend(:forwarddiff)
chain_sde = sample(
    model_sde,
    NUTS(0.25),
    5000;
    init_params=[1.5, 1.3, 1.2, 2.7, 1.2, 0.12, 0.12],
    progress=false,
)
plot(chain_sde)


"""
````
simple!(u,A,b;
                 abstol::Real = zero(real(eltype(b))),
                 reltol::Real = sqrt(eps(real(eltype(b)))),
                 log=false,
                 maxiter=100,
                 P=nothing
                 ) -> solution, [history]
````

Simple iteration scheme ``u_{i+1}= u_i - P^{-1} (A u_i -b)`` with similar API as the methods in IterativeSolvers.jl.

"""
function simple!(u,A,b;
                 abstol::Real = zero(real(eltype(b))),
                 reltol::Real = sqrt(eps(real(eltype(b)))),
                 log=false,
                 maxiter=100,
                 Pl=nothing
                 )
    res=A*u-b # initial residual
    upd=similar(res)
    r0=norm(res) # residual norm
    history=[r0]  # intialize history recording
    for i=1:maxiter
        ldiv!(upd,Pl,res)
	u.-=upd # solve preconditioning system and update solution
	mul!(res,A,u)    # calculate residual
        res.-=b
	r=norm(res)       # residual norm
	push!(history,r)  # record in history
	if (r/r0)<reltol || r<abstol    # check for tolerance
            break
	end
    end
    if log
	return u,Dict( :resnorm =>history )
    else
        return u
    end
end

simple(A,b;kwargs...)=simple!(zeros(eltype(b),length(b)),A,b;kwargs...)


### A Pluto.jl notebook ###
# v0.12.20

using Markdown
using InteractiveUtils

# ╔═╡ c4e34a90-77b8-11eb-108e-93d91efc7480
md"# Solving Tic Tac Toe With MiniMax Trees!"

# ╔═╡ a6e0b518-4f39-45d0-b2a3-a95cb1de7114
md"First we need to build out our minimax tree for the game"

# ╔═╡ 72fc2712-61b8-43a6-b68e-b3c5c87ed0a1
struct GameState
	turn::Int8
	tiles::Array{Int8, 2}
end

# ╔═╡ e6e3a756-6526-483d-af9e-9e53cf6d9f52
Base.:(==)(x::GameState, y::GameState) = x.turn == y.turn && all(x.tiles .== y.tiles)

# ╔═╡ b3d7a96e-f3fc-4dbd-94a9-552b21426396
Base.hash(x::GameState) = hash(x.tiles)

# ╔═╡ 82323b7f-1e92-46b0-8cff-5d85152d7d5c
md"0's are empty, 1's are player 1 (x), and 2's are player 2 (o)

Our objective is to optimize the policy of player 1"

# ╔═╡ a51583a3-0faa-4bac-a60b-e763644f674d
EMPTY = 0x00

# ╔═╡ eb2f779f-c651-4444-a34d-42681de9f3c6
PLAYER_1 = Int8(1)

# ╔═╡ f0e3fa72-9acf-4325-9173-622f93497b68
PLAYER_2 = Int8(-1)

# ╔═╡ 2a78b1ad-63cb-4cfa-98f4-a11ab05a9022
next_player(p) = p == PLAYER_1 ? PLAYER_2 : PLAYER_1

# ╔═╡ d1efdbe8-1a18-4a1c-84fc-f80dc3b9272d
initial_tiles = fill(EMPTY, 3, 3)

# ╔═╡ e0e9cbb2-01a8-4523-a6a9-ec7cfdb351d5
initial_state = GameState(PLAYER_1, initial_tiles)

# ╔═╡ e9dd6424-1031-44fb-970c-15aa3a4a1385
struct MinimaxNode
	state::GameState
	children::Vector{MinimaxNode}
	winner::Int8
	leaf::Bool
end

# ╔═╡ 5feafefc-740c-4450-838d-9e2ac47ceb32
function get_winner(state::GameState)
	# Really ugly check for game winners
	# Rows
	rows = [all(state.tiles[i, :] .== state.tiles[i, 1]) ? state.tiles[i, 1] : 0 for i ∈ 1:size(state.tiles)[1]]
	cols = [all(state.tiles[:, j] .== state.tiles[1, j]) ? state.tiles[1, j] : 0 for j ∈ 1:size(state.tiles)[2]]
	diag1 = state.tiles[1, 1] == state.tiles[2, 2] && state.tiles[1, 1] == state.tiles[3, 3] ? state.tiles[1, 1] : 0
	diag2 = state.tiles[3, 1] == state.tiles[2, 2] && state.tiles[3, 1] == state.tiles[1, 3] ? state.tiles[3, 1] : 0

	max_rows = max(rows..., cols..., diag1, diag2)
	min_rows = min(rows..., cols..., diag1, diag2)

	return max_rows > 0 ? max_rows : min_rows
end

# ╔═╡ 7b143cf1-920d-4626-a927-bf33885ae7a9
get_winner(GameState(PLAYER_1, Int8[
	1 -1 1;
	-1 -1 1;
	-1 -1 0;
]))

# ╔═╡ 31ff651f-b275-46d2-97e9-206c9926dfb8
function possible_moves(state::GameState)
	moves = []
	for i ∈ 1:length(state.tiles)
		if state.tiles[i] == EMPTY
			new_tiles = copy(state.tiles)
			new_tiles[i] = state.turn
			push!(moves, GameState(next_player(state.turn), new_tiles))
		end
	end

	moves
end

# ╔═╡ 258017ed-4bf0-4fdd-8239-2fc381e94d0c
function build_minimax(state::GameState)
	winner = get_winner(state)
	nodes = []
	for pm ∈ possible_moves(state)
		push!(nodes, build_minimax(pm))
	end

	if length(nodes) > 0 && winner == EMPTY
		return MinimaxNode(state, nodes, (state.turn == PLAYER_1 ? max : min)((x->x.winner).(nodes)...), true)
	else
		return MinimaxNode(state, [], winner, true)
	end
end

# ╔═╡ 45d2cddd-adfe-470a-89bc-56fb5d5982dc
begin
	tree = build_minimax(initial_state)
	nothing
end

# ╔═╡ d607a5e3-78f5-4308-999f-bf49858004a7
(x->x.winner).(tree.children[1].children[4].children[4].children[2].children)

# ╔═╡ c9254d06-75b9-47dc-8d29-495c80c9215d
tree.children[1].children[2].state

# ╔═╡ 15fd5bd7-d2c4-4337-a2ad-0fc43ad2b4e0
md"## Flattening our Tree
Tic Tac Toe has the nice property that if a game reaches a particular state, regardless of how that state was obtained, the minimax tree from that state will be the same. Therefore we can build up a dictionary of game states for keys, and the corresponding value is yet another game state representing the best move."

# ╔═╡ 248c158c-41bc-478f-be12-b7e4ab43544d
function flatten_minimax!(flat_tree, tree)
	flat_tree[tree.state] = tree.children[argmin((x->x.winner).(tree.children))].state
	for node ∈ tree.children
		if get_winner(node.state) == EMPTY && length(node.children) > 0
			flatten_minimax!(flat_tree, node)
		end
	end
end

# ╔═╡ 72c6bdcd-6d6b-4bf4-a759-1c4256384352
begin
	flat_tree = Dict()
	flatten_minimax!(flat_tree, tree)
end

# ╔═╡ c0634c74-9228-4d05-8146-3492aaf3a8ef
flat_tree[initial_state]

# ╔═╡ decac4e3-d5d3-4a51-a5bb-fdb658dec971
md"Fantastic! As can be seen above, the minimax tree has determined that the best move for player 1 is the center square!"

# ╔═╡ 2bb59351-cb34-45a7-bd58-72409adf48fa
html"

"

# ╔═╡ 4aa39de7-db17-42d7-924f-abb486c03eed
md"## Building the backend"

# ╔═╡ d5ba2170-d687-4ef8-a7fc-e8fef48b3a2c
md"Set **tiles** and **player** from the frontend"

# ╔═╡ b385f71f-c608-4be7-a0b6-30a39397451e
player = PLAYER_2

# ╔═╡ c1480936-bc64-481d-a754-31d5a35a2d6d
md"Note that tiles will be a vector of vectors, not an array"

# ╔═╡ c1cd29c9-b3dc-497b-b200-e6ad08356b39
tiles = [[0, 0, 0], [0, 1, 0], [0, 0, 0]]

# ╔═╡ 7d6a905a-2445-4569-8fe1-8e703b635218
md"And request **next_state**"

# ╔═╡ 4b879529-0359-462e-b032-3bb95a11ac36
next_state = flat_tree[GameState(player, hcat(tiles...))].tiles

# ╔═╡ 3e54ccec-d960-4cc2-a34a-7fce0964addb
md"Viola! Our \"backend\" is complete!"

# ╔═╡ Cell order:
# ╟─c4e34a90-77b8-11eb-108e-93d91efc7480
# ╟─a6e0b518-4f39-45d0-b2a3-a95cb1de7114
# ╠═72fc2712-61b8-43a6-b68e-b3c5c87ed0a1
# ╠═e6e3a756-6526-483d-af9e-9e53cf6d9f52
# ╠═b3d7a96e-f3fc-4dbd-94a9-552b21426396
# ╟─82323b7f-1e92-46b0-8cff-5d85152d7d5c
# ╠═a51583a3-0faa-4bac-a60b-e763644f674d
# ╠═eb2f779f-c651-4444-a34d-42681de9f3c6
# ╠═f0e3fa72-9acf-4325-9173-622f93497b68
# ╠═2a78b1ad-63cb-4cfa-98f4-a11ab05a9022
# ╠═d1efdbe8-1a18-4a1c-84fc-f80dc3b9272d
# ╠═e0e9cbb2-01a8-4523-a6a9-ec7cfdb351d5
# ╠═e9dd6424-1031-44fb-970c-15aa3a4a1385
# ╠═5feafefc-740c-4450-838d-9e2ac47ceb32
# ╠═7b143cf1-920d-4626-a927-bf33885ae7a9
# ╠═31ff651f-b275-46d2-97e9-206c9926dfb8
# ╠═258017ed-4bf0-4fdd-8239-2fc381e94d0c
# ╠═45d2cddd-adfe-470a-89bc-56fb5d5982dc
# ╠═d607a5e3-78f5-4308-999f-bf49858004a7
# ╠═c9254d06-75b9-47dc-8d29-495c80c9215d
# ╟─15fd5bd7-d2c4-4337-a2ad-0fc43ad2b4e0
# ╠═248c158c-41bc-478f-be12-b7e4ab43544d
# ╠═72c6bdcd-6d6b-4bf4-a759-1c4256384352
# ╠═c0634c74-9228-4d05-8146-3492aaf3a8ef
# ╟─decac4e3-d5d3-4a51-a5bb-fdb658dec971
# ╟─2bb59351-cb34-45a7-bd58-72409adf48fa
# ╟─4aa39de7-db17-42d7-924f-abb486c03eed
# ╟─d5ba2170-d687-4ef8-a7fc-e8fef48b3a2c
# ╠═b385f71f-c608-4be7-a0b6-30a39397451e
# ╟─c1480936-bc64-481d-a754-31d5a35a2d6d
# ╠═c1cd29c9-b3dc-497b-b200-e6ad08356b39
# ╟─7d6a905a-2445-4569-8fe1-8e703b635218
# ╠═4b879529-0359-462e-b032-3bb95a11ac36
# ╟─3e54ccec-d960-4cc2-a34a-7fce0964addb


### A Pluto.jl notebook ###
# v0.12.7

using Markdown
using InteractiveUtils

# ╔═╡ 6fd616f6-1ea1-11eb-3814-8bfb4a096c49
using Pkg, DrWatson

# ╔═╡ 6ffe2628-1ea1-11eb-24ea-57f985146a72
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Distributions, Random, StatsBase, DataFrames, Plots
	Random.seed!(1)
end;

# ╔═╡ ed174bc4-1ea0-11eb-1e2f-a32874cec549
md"## Listing 3.19"

# ╔═╡ 70179b62-1ea1-11eb-13ce-7fb4d54bad4b
function prn(lambda)
    k, p = 0, 1
    while p > MathConstants.e^(-lambda)
        k += 1
        p *= rand()
    end
    return k-1
end

# ╔═╡ 0cbc946e-2164-11eb-1b0c-7d4c7a607b1e
begin
	xGrid, lambda, N = 0:16, 5.5, 10^6

	pDist = Poisson(lambda)
	bPmf = pdf.(pDist,xGrid)
	data = counts([prn(lambda) for _ in 1:N],xGrid)/N
end

# ╔═╡ 0cbcbdf2-2164-11eb-193d-2baa8a6bce0b
begin
	plot( xGrid, data,
		line=:stem, marker=:circle,
		c=:blue, ms=10, msw=0, lw=4, label="MC estimate")
	plot!( xGrid, bPmf, line=:stem,
		marker=:xcross, c=:red, ms=6, msw=0, lw=2, label="PMF",
		ylims=(0,0.2), xlabel="x", ylabel="Probability of x events")
end

# ╔═╡ 70182cd8-1ea1-11eb-094d-8d0d49cf15f3
md"## End of listing 3.19"

# ╔═╡ Cell order:
# ╟─ed174bc4-1ea0-11eb-1e2f-a32874cec549
# ╠═6fd616f6-1ea1-11eb-3814-8bfb4a096c49
# ╠═6ffe2628-1ea1-11eb-24ea-57f985146a72
# ╠═70179b62-1ea1-11eb-13ce-7fb4d54bad4b
# ╠═0cbc946e-2164-11eb-1b0c-7d4c7a607b1e
# ╠═0cbcbdf2-2164-11eb-193d-2baa8a6bce0b
# ╟─70182cd8-1ea1-11eb-094d-8d0d49cf15f3


module TestOperators
using Test
using Operators
using LinearAlgebra

@testset "basic operators" begin
    # evaluation functional
    f = x->sin(sum(x))
    x = randn()
    δ = Operators.Evaluation(x)
    @test δ(f) ≈ f(x)

    # 2d evaluation functional test
    x = randn(2)
    δ = Operators.Evaluation(x)
    @test δ(f) ≈ f(x)
    @test Operators.islinear(δ)

    # sum
    δ = Operators.Evaluation.(x)
    δ = Operators.Sum(δ...)
    @test δ(f) ≈ sum(f.(x))

    # scaling
    δ = 2δ
    @test Operators.isfunctional(δ)
    @test Operators.islinear(δ)
    @test δ(f) ≈ 2sum(f.(x))

    # dot

    # shift
    x = randn()
    y = randn()
    S = Operators.Shift(x)
    δ = Operators.Evaluation(y)
    @test δ(S(f)) ≈ f(y-x)

# composition
# println(Operators.Composition(δ, S)(f))

end

@testset "scalar differential operators" begin
    f(x) = sin(sum(x))
    # del
    ∇ = Operators.del
    # derivative
    x = randn()
    δ = Operators.δ(x)
    @test δ(∇(f)) ≈ cos(x)
    # gradient
    x = randn(2)
    δ = Operators.Evaluation(x)
    @test  δ(∇(f)) ≈ cos(sum(x)) * ones(2)

    # laplacian
    # Δ = Operators.Laplacian()
    Δ = Operators.Δ
    x = randn()
    δ = Operators.Evaluation(x)
    @test δ(Δ(f)) ≈ -sin(x)

    x = randn(2)
    δ = Operators.Evaluation(x)
    @test  δ(Δ(f)) ≈ sum(-sin(sum(x)) * ones(2))
    # ODE operator
end

@testset "vector differential operators" begin

    g(x) = [sin(sum(x)), cos(sum(x))]
    Operators.outputvalue(::typeof(g)) = Operators.VectorValued()

    # vector del, scalar input
    ∇ = Operators.∇
    @test ∇(g)(0.) ≈ [1, 0]

    # vector del, vector input
    x = randn(2)
    @test ∇(g)(x) ≈ [[cos(sum(x)) cos(sum(x))]; [-sin(sum(x)) -sin(sum(x))]]

    # vector laplacian, scalar input
    Δ = Operators.Δ
    println(Δ(g)(1.))

    # vector laplacian, vector input
    # println(Δ(g)(randn(2)))

    # curl
    f(x, y, z) = [y, -x, zero(z)]
    f(x::AbstractVector) = f(x...)
    x = randn(3)
    @test (∇ × f)(x) ≈ [0, 0, -2]

    f(x, y, z) = [zero(x), -x^2, zero(z)]
    f(x::AbstractVector) = f(x...)
    x = randn(3)
    @test (∇ × f)(x) ≈ [0, 0, -2x[1]]
end

end # TestOperators


using Base: Int64
using BioCCP
@testset "BioCCP" begin

    n = 20

    # Equal probabilities
    p_uniform = ones(n)/n

    # Probabilities following Zipf's law
    ρ = 10
	α = exp(log(ρ)/(n-1))
	p_zipf = collect(α.^-(1:n))
    p_zipf = p_zipf ./ sum(p_zipf)

    @testset "Expectation" begin
        @test expectation_minsamplesize(n; p = p_uniform) isa Int64
        @test expectation_minsamplesize(n; p = p_uniform) == Int(ceil(n*sum(1 ./ (1:n))))
        @test expectation_minsamplesize(n; p = p_uniform) < expectation_minsamplesize(n; p = p_zipf)
        @test expectation_minsamplesize(n; p = p_uniform, m = 1) < expectation_minsamplesize(n; p = p_uniform, m = 2)
        @test Int(ceil(expectation_minsamplesize(n; p = p_uniform, m = 1, r = 1)/3)) == expectation_minsamplesize(n; p = p_uniform, m = 1, r = 3)
    end

    @testset "Standard deviation" begin
        @test std_minsamplesize(n; p = p_uniform) isa Int64
        @test std_minsamplesize(n; p = p_uniform) < std_minsamplesize(n; p = p_zipf)
        @test std_minsamplesize(n; p = p_uniform, m = 1) < std_minsamplesize(n; p = p_uniform, m = 2)
    end

    t = 500

    @testset "Success probability" begin
        @test success_probability(n, t; p = p_uniform) isa Float64
        @test success_probability(n, t; p = p_uniform) > success_probability(n, t; p = p_zipf)
        @test success_probability(n, t; p = p_uniform, m = 1) > success_probability(n, t; p = p_uniform, m = 2)
        @test all(success_probability(n, t; p = p_uniform) .< success_probability.(n, [t+10, t+100, t+1000]; p = p_uniform))
        @test all(success_probability.(n, [1, 5, 10, 100, 500]) .<= 1)
        @test all(success_probability.(n, [1, 5, 10, 100, 500]) .>= 0)

    end

    @testset "Expected fraction collected" begin
        @test expectation_fraction_collected(n, t; p = p_uniform) isa Float64
        @test expectation_fraction_collected(n, t; p = p_uniform) > expectation_fraction_collected(n, t; p = p_zipf)
        @test all(expectation_fraction_collected(n, t; p = p_uniform) .< expectation_fraction_collected.(n, [t+10, t+100, t+1000]; p = p_uniform))
        @test all(expectation_fraction_collected.(n, [1, 10, 100, 1000, 10000]) .<= 1)
        @test all(expectation_fraction_collected.(n, [1, 10, 100, 1000, 10000]) .>= 0)

    end

    k = 2
    pᵢ = 0.005
    r = 3

    @testset "Probability occurence" begin
        @test prob_occurrence_module(pᵢ, t, r, k) isa Float64
        @test all(prob_occurrence_module.(pᵢ, [0, 10, 100, 1000, 10000], r, k) .>= 0)
        @test all(prob_occurrence_module.(pᵢ, [0, 10, 100, 1000, 10000], r, k) .<= 1)
    end
end


using DelayEmbeddings
using Test
using DynamicalSystemsBase

println("\nTesting traditional optimal embedding dimension...")
test_value = (val, vmin, vmax) -> @test vmin <= val <= vmax

@testset "Embedding dimension estimation" begin
diffeq = (atol = 1e-9, rtol = 1e-9, maxiters = typemax(Int))
s_sin = sin.(0:0.1:1000)
ro = Systems.roessler(ones(3));
data = trajectory(ro,1000;Δt=0.1,diffeq)
s_roessler = data[:,1]
lo = Systems.lorenz96(4, [0.1, 0.2, 0.5, 0.1]; F = 16.0);
data = trajectory(lo,5000.0;Δt=0.05, Ttr = 100.0, diffeq)
s_lorenz = data[:,1]

@testset "Caos method" begin
    𝒟, τ, x = optimal_traditional_de(s_roessler, "afnn")
    @test 3 ≤ size(𝒟, 2) ≤ 5

    E2s = DelayEmbeddings.stochastic_indicator(s_roessler, τ, 1:6)
    @test minimum(E2s) < 0.3

    𝒟, τ, x = optimal_traditional_de(s_roessler, "afnn"; metric = Chebyshev())
    @test 3 ≤ size(𝒟, 2) ≤ 5

    𝒟, τ, x = optimal_traditional_de(s_lorenz, "afnn")
    @test 4 ≤ size(𝒟, 2) ≤ 8

    #Test against random signal
    E2s = DelayEmbeddings.stochastic_indicator(rand(10000), 1, 1:5)
    @test minimum(E2s) > 0.9
end

@testset "fnn method" begin
    𝒟, τ, x = optimal_traditional_de(s_sin, "fnn")
    @test 1 ≤ size(𝒟, 2) ≤ 3

    𝒟, τ, x = optimal_traditional_de(s_roessler, "fnn")
    @test 3 ≤ size(𝒟, 2) ≤ 5

    𝒟, τ, x = optimal_traditional_de(s_lorenz, "fnn")
    @test 4 ≤ size(𝒟, 2) ≤ 8
end

@testset "ifnn method" begin
    𝒟, τ, x = optimal_traditional_de(s_sin, "ifnn")
    @test 1 ≤ size(𝒟, 2) ≤ 4

    𝒟, τ, x = optimal_traditional_de(s_roessler, "ifnn")
    @test 3 ≤ size(𝒟, 2) ≤ 5

    𝒟, τ, x = optimal_traditional_de(s_roessler, "ifnn"; metric = Chebyshev())
    @test 3 ≤ size(𝒟, 2) ≤ 5

    𝒟, τ, x = optimal_traditional_de(s_lorenz, "ifnn")
    @test 4 ≤ size(𝒟, 2) ≤ 8
end

end


"""
    forwardsub(L,b)

Solve the lower-triangular linear system with matrix `L` and right-hand side
vector `b`.
"""
function forwardsub(L,b)

n = size(L,1)
x = zeros(n)
for i = 1:n
    s = i > 1 ? sum( L[i,j]*x[j] for j=1:i-1 ) : 0
    x[i] = ( b[i] - s ) / L[i,i]
end

return x
end

"""
    backsub(U,b)

Solve the upper-triangular linear system with matrix `U` and right-hand side
vector `b`.
"""
function backsub(U,b)

n = size(U,1)
x = zeros(n)
for i = n:-1:1
    s = i < n ? sum( U[i,j]*x[j] for j=i+1:n ) : 0
    x[i] = ( b[i] - s ) / U[i,i]
end

return x
end

"""
    lufact(A)

Compute the LU factorization of square matrix `A`, returning the factors.
"""
function lufact(A)

n = size(A,1)
L = Matrix(Diagonal(ones(n)))    # puts ones on diagonal
U = copy(A)

# Gaussian elimination
for j = 1:n-1
  for i = j+1:n
    L[i,j] = U[i,j] / U[j,j]   # row multiplier
    U[i,j:n] = U[i,j:n] - L[i,j]*U[j,j:n]
  end
end

return L,triu(U)
end


using Pkg
using Plots
using DataFrames
pyplot()

### functions for simulations
function calc_new_state(y::AbstractArray,
    a::Dict,
    b::Dict,
    l::Dict;
    from::Float64=0.01)
    if length(y)==0
        y = [from]
    end
    y[end]+(1-y[end])*(a["walk"]*(1-exp(-b["walk"]*l["walk"]*a["walk"]*y[end]))+a["TTC"]*(1-exp(-b["TTC"]*l["TTC"]*a["TTC"]*y[end])))
end

function simul(s::Dict,
               b::Dict,
               l::Dict;
               to_perc::Bool=true,
               from::Float64=0.01,
               to::Union{Float64, Int}=0.99)

               # initial calculations
                e1 = (exp(-(s["walk"])/min(s["walk"], s["TTC"])))
                e2 = (exp(-(s["TTC"])/min(s["walk"], s["TTC"])))
                a = Dict("walk" => e1/sum([e1,e2]), "TTC" => e2/sum([e1,e2]))
                # simulation loop
                y = []
                append!(y, from)
                if to_perc
                    while y[end] <= to
                        append!(y, calc_new_state(y,a,b,l))
                    end
                else
                    for i in 1:to
                        append!(y, calc_new_state(y,a,b,l))
                    end
                end
                y
end

#####################################
### scenario 1:
s = Dict("walk" => 15, "TTC" => 10) # time of travel
l = Dict("walk" => 0.01, "TTC" => 0.02) # contagion
b = Dict("walk" => 5, "TTC" => 5) # interaction frequency

x1 = simul(s,b,l)
length(x1) - 1

### scenario 2:
s = Dict("walk" => 15, "TTC" => 10) # time of travel
l = Dict("walk" => 0.01, "TTC" => 0.02) # contagion
b = Dict("walk" => 3, "TTC" => 5) # interaction frequency

x2 = simul(s,b,l)
length(x2) - 1

# PLOT1
Plots.plot(1:length(x1), x1, linecolor="purple", lw = 2, legend=false, grid=false)
ylabel!("% infected")
xlabel!("days")
savefig("plot1.eps")

# PLOT2
Plots.plot(1:length(x2), x2, linecolor="purple", lw = 2, legend=false, grid=false)
ylabel!("% infected")
xlabel!("days")
savefig("plot2.eps")

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

#PLOT3
Plots.plot(1:length(x1), x1, linecolor="purple", label="Scenario 1", lw=2, grid = false, legend=:topleft)
Plots.plot!(1:length(x2), x2, linecolor="black", label="Scenario 2", lw=2 #linestyle = :dot, )
)
ylabel!("% infected")
xlabel!("days")
savefig("plot3.eps")

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

# PLOT 4
s = Dict("walk" => 15, "TTC" => 10) # time of travel
l = Dict("walk" => 0.01, "TTC" => 0.02) # contagion
b = Dict("walk" => 3, "TTC" => 5) # interaction frequency

results_full = Dict()
results_short = DataFrame()
for i in 1:50
    s["TTC"] = i
    results_full[i] = simul(s,b,l)
    append!(results_short, DataFrame(s_ttc = [i], days = [length(results_full[i])-1]))
end

results_short # ok

Plots.plot(results_short[!,:s_ttc], results_short[!,:days], linecolor="purple", lw = 2, legend=false, grid=false)
ylabel!("days")
xlabel!("time when taking TTC")
savefig("plot4.eps")

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

# PLOT 5
s = Dict("walk" => 15, "TTC" => 10) # time of travel
l = Dict("walk" => 0.01, "TTC" => 0.02) # contagion
b = Dict("walk" => 3, "TTC" => 5) # interaction frequency

results_full2 = Dict()
results_short2 = DataFrame()
for i in 1:50
    s["TTC"] = i
    results_full2[i] = simul(s,b,l, to_perc=false, to=101)
    append!(results_short2, DataFrame(s_ttc = [i], infected = results_full2[i][101]))
end

results_short2 # ok

Plots.plot(results_short2[!,:s_ttc], results_short2[!,:infected], linecolor="purple", lw = 2, legend=false, grid=false)
ylabel!("% infected after 100 days")
xlabel!("time when taking TTC")
savefig("plot5.eps")


@model AHMCGaussian(y, N) = begin
    mu ~ Normal(0, 1)
    sigma ~ Truncated(Cauchy(0, 5), 0, Inf)
    y ~ MvNormal(Fill(mu, N), sigma)  # use `Fill(mu, N)` is memory freiendly
end

AHMCconfig = Turing.NUTS(1000, .85)

CmdStanGaussian = "
data {
  int<lower=0> N;
  vector[N] y;
}
parameters {
  real mu;
  real<lower=0> sigma;
}
model {
  mu ~ normal(0,1);
  sigma ~ cauchy(0,5);
  y ~ normal(mu,sigma);
}
"

CmdStanConfig = Stanmodel(
  name = "CmdStanGaussian", model=CmdStanGaussian, nchains=1,
  Sample(
    num_samples=1000, num_warmup=1000, adapt=CmdStan.Adapt(delta=0.8),
    save_warmup=true
  )
)

struct GaussianProb{TY <: AbstractVector}
    "Observations."
    y::TY
end

function (problem::GaussianProb)(θ)
    @unpack y = problem   # extract the data
    @unpack mu, sigma = θ
    N = length(y)
    logpdf(MvNormal(Fill(mu, N), sigma), y) + logpdf(Normal(0, 1), mu) +
      logpdf(Truncated(Cauchy(0, 5), 0, Inf), sigma)
end

# Define problem with data and inits.
function sampleDHMC(obs, N, nsamples, autodiff)
  p = GaussianProb(obs);
  p((mu=0.0, sigma=1.0))

  # Write a function to return properly dimensioned transformation.
  trans = as((mu=as(Real, -25, 25), sigma=asℝ₊), )

  # Use Flux for the gradient.

  P = TransformedLogDensity(trans, p)
  ∇P = ADgradient(autodiff, P)

  # Sample from the posterior.
  results = mcmc_with_warmup(Random.GLOBAL_RNG, ∇P, nsamples; reporter = NoProgressReport())

  # Undo the transformation to obtain the posterior from the chain.
  posterior = transform.(trans, results.chain)

  chns = nptochain(results, posterior)
  return chns
end

simulateGaussian(; μ=0, σ=1, Nd, kwargs...) = (y=rand(Normal(μ, σ), Nd), N=Nd)


"""
    Module Basis2DQuad

Includes DG core functions.
"""

module Basis2DQuad

export vandermonde_2D, grad_vandermonde_2D
export nodes_2D, equi_nodes_2D, quad_nodes_2D

using Basis1D
using CommonUtils
using LinearAlgebra

"""
    vandermonde_2D(N, r)

Initialize the 2D Vandermonde matrix of order N "Legendre" polynomials at
nodes (r,s)

# Examples
```jldoctest
"""
function vandermonde_2D(N, r, s)

    Np = convert(Int,(N+1)*(N+1))
    sk = 1
    V = zeros(length(r), Np);
    for i=0:N
        for j=0:N
            V[:,sk] = jacobiP(r, 0, 0, i).*jacobiP(s, 0, 0, j)
            sk += 1
        end
    end
    return V
end

"""
    gradV_2D(N, Np, r, s)

Initilize 2D gradient Vandermonde matrix of derivatives 2D "Legendre" polynomials
of order N at (r,s)

# Examples
```jldoctest
"""
function grad_vandermonde_2D(N, r, s)

    Np = convert(Int,(N+1)*(N+1))
    sk = 1
    V2Dr = zeros(length(r), Np);
    V2Ds = zeros(length(r), Np);
    for i=0:N
        for j=0:N
            V2Dr[:,sk] = grad_jacobiP(r, 0, 0, i).*jacobiP(s, 0, 0, j)
            V2Ds[:,sk] = jacobiP(r, 0, 0, i).*grad_jacobiP(s, 0, 0, j)
            sk += 1
        end
    end

    return V2Dr, V2Ds
end

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

"""
    nodes_2D(N)

Compute optimized interpolation nodes using blend & warp method on equilateral
triangles for polynomial of order N, with Np points

# Examples
```jldoctest
"""

function nodes_2D(N)
    r1D,w1D = gauss_lobatto_quad(0,0,N)
    s,r = meshgrid(r1D)
    return r[:], s[:]
end

"""
    equi_nodes_2D(N)

Compute optimized interpolation nodes using blend & warp method on equilateral
triangles for polynomial of order N, with Np points

# Examples
```jldoctest
"""

function equi_nodes_2D(N)
    r1D = LinRange(-1,1,N+1)
    s,r = meshgrid(r1D)

    return r[:], s[:]
end

"""
    quad_nodes_2D(N)

Compute optimized interpolation nodes using blend & warp method on equilateral
triangles for polynomial of order N, with Np points

# Examples
```jldoctest
"""

function quad_nodes_2D(N)
    r1D,w1D = gauss_quad(0,0,N)
    s,r = meshgrid(r1D)
    ws,wr = meshgrid(w1D)
    w = @. wr*ws
    return r[:], s[:], w[:]
end

end


import CompEcon, BasisMatrices
using PyPlot
using PyCall

f1(x) = @. exp(-2x)
g1(x) = @. -2exp(-2x)

function univariate_interp(f=f1, g=g1)
    # fit approximant
    n = 10; a = -1; b = 1
    basis = CompEcon.fundefn(:cheb, n, a, b)
    c = CompEcon.funfitf(basis, f1)

    # graph approximation error for function and derivative
    x = BasisMatrices.nodeunif(1001, a, b)[1]
    yact = f(x)
    dact = g(x)
    yfit = CompEcon.funeval(c, basis, x)[1]
    dfit = CompEcon.funeval(c, basis, x, 1)[1]

    # Nice plot of function approximation error
    fig, ax = plt.subplots()
    ax[:plot](x, yfit-yact)
    ax[:plot](x, 0*x, "k--", linewidth=2)
    ax[:set_xlabel]("x")
    ax[:set_ylabel]("Error")
    ax[:set_title]("Error approximating function using Chebyshev")

    # Nice plot of derivative approximation error
    fig, ax = plt.subplots()
    ax[:plot](x, dfit-dact);
    ax[:plot](x, 0*x, "k--", linewidth=2)
    ax[:set_xlabel]("x")
    ax[:set_ylabel]("Error")
    ax[:set_title]("Error approximating derivative using Chebyshev")
end

f2(x) = cos.(x[:, 1]) ./ exp.(x[:, 2])
function bivaraite_interp(f=f2)
    n = [7 7]
    a = [0 0]
    b = [1 1]
    basis = CompEcon.fundefn(:cheb, n, a, b)
    c = CompEcon.funfitf(basis, f2)

    fig = plt.figure()
    nplot = [101 101]
    x, xcoord = BasisMatrices.nodeunif(nplot, a, b)
    yfit = CompEcon.funeval(c, basis, x)[1]
    error = reshape(yfit - f2(x), nplot...)
    poly3d = surf(xcoord[1], xcoord[2], error)

end

univariate_interp()
bivaraite_interp()


"""
    UtilFuncs

This module collects many conventional utility functions
which are provided within the general form ``u(X|\\theta):\\mathbb{R}^{k}\\to\\mathbb{R}``.
where ``\\theta`` is `tuple` of parameters (in specific types of utility function, they are defined as keyword parameters), and ``k`` is the dimension of utilizable goods.

There is the contents of utility functions:
1. `u_log` - logarithm (risk netural, 1 good)
2. `u_crra` - CRRA
3. `u_cara` - CARA
4. `u_ces2` - CES (2 goods)
5. `u_cesk` - CES (k goods)
6. `u_cd1` - Cobb-Douglas (1 good)
7. `u_cd2` - Cobb-Douglas (2 goods)
8. `u_cdk` - Cobb-Douglas (k goods)
9. `u_logcd2` - Logarithm Cobb-Douglas (2 goods)
10. `u_logcdk` - Logarithm Cobb-Douglas (k goods)
11. `u_leontiefk` - Leontief (k goods)

Please refer to each specific function for detailed documentations.

This module depends on:
1. nothing

@author: Clpr @ GitHub
"""
module UtilFuncs
    export u_log, u_crra, u_cara
    export u_ces2, u_cesk
    export u_cd1, u_cd2, u_cdk
    export u_logcd2, u_logcdk
    export u_leontiefk
# =================================
"""
    u_log(x::Real)

Logarithm (risk-netural) utility function for one good
where `x` is naturally required to be positive.
The expression of logarithm utility function is:
``u(x)=\\log(x),x>0``
"""
function u_log(x::Real)
    return log(x)::Real
end # u_log

# ----------------
"""
    u_crra(x::Real ; η::Real)

CRRA (constant relative risk aversion) utility function of one good
whose expression is:
``u(x|\\eta>0) = \\frac{x^{1-\\eta}}{1-\\eta}``.
The relative risk aversion coefficient ``\\eta`` must be positive.
When ``\\eta`` is one, this function degenerates to logarithm utility function.
"""
function u_crra(x::Real ; η::Real = 1.1)
    if η<0; throw(ErrorException("requires positive eta (RRA)")); end
    η == 1 ? (return u_log(x)::Real)  :  (return (x^(1-η)/(1-η))::Real)
end # u_crra

# ----------------
"""
    u_cara(x::Real ; α::Real = 1.0)

CARA (constant absolute risk aversion) utility function of one good.
whose expression is:
``u(x|\\alpha>0) = -e^{-\\alpha x}``.
The absolute risk aversion coefficient ``\\alpha`` must be positive.
"""
function u_cara(x::Real ; α::Real = 1.0)
    if α<0; throw(ErrorException("requires positive alpha (ARA)")); end
    return (-exp(-α * x))::Real
end # u_cara

# ----------------
"""
    u_ces2(x1::Real, x2::Real ; ρ::Real = 1.0, β::Real = 1.0, α::Real = 0.5 )

CES (constant elasticity of substitution) utility function whose expression is:
``u(x_1,x_2)=(\\alpha x_1^{\\rho} + \\(1-alpha) x_2^{\\rho})^{\\beta/\\rho}``
The elasticity of substitution is ``\\frac{1}{1-\\rho}``.

Special cases:
1. when ``\\rho=1``, the function has linear/perfect substitution.
2. when ``\\rho=0``, the function becomes Cobb-Douglas function of two goods, i.e. ``u(x_1,x_2)=(x_1^\\alpha x_2^{1-\\alpha})^{\\beta}``
3. when ``\\rho=-\\infty``, the function becomes Leontief (perfect complimentary), i.e. ``u(x_1,x_2)=\\beta \\min\\{ x_1,x_2 \\}``
"""
function u_ces2(x1::Real, x2::Real ; ρ::Real = 1.0, β::Real = 1.0, α::Real = 0.5 )
    if ρ == 0
        return ( ( x1^α * x2^(1-α) )^β )::Real
    elseif ρ == -Inf
        return ( β * min(x1,x2) )::Real
    end # if
    return ( ( α*x1^ρ + (1-α)*x2^ρ )^(β/ρ) )::Real
end # u_ces2

# ----------------
"""
    u_cesk(X::Vector ; W::Vector = ones(length(X)) , β::Real = 1.0, ρ::Real = 1.0 )

CES utility function of k goods whose expression is:
``u(X)=(\\sum w_i \\log(x_i)^{\\rho})^{\\beta/\\rho}``.
requiring the length of `W` must be equal to the length of `X`.
This function does not require the summation of `W` must be 1.

Special cases:
1. when ``\\rho=1``, the function has linear/perfect substitution.
2. when ``\\rho=0``, the function becomes Cobb-Douglas function
3. when ``\\rho=-\\infty``, the function becomes Leontief (perfect complimentary)

"""
function u_cesk(X::Vector ; W::Vector = ones(length(X)) , β::Real = 1.0, ρ::Real = 1.0 )
    if ρ == 0
        return ( ( u_cdk(X, W=W) )^β )::Real
    elseif ρ == -Inf
        return ( β * u_leontiefk(X) )::Real
    end # if
    local U::Real = 0.0
    for (x,w) in zip(X,W)
        U += w * x ^ ρ
    end # for
    return ( U^(β/ρ) )::Real
end # u_cesk

# ----------------
"""
    u_cd1(x::Real ; θ::Real = 1.0)

Cobb-Douglas utility function of one good whose expression is:
``u(x)=x^\\theta``
"""
function u_cd1(x::Real ; θ::Real = 1.0)
    return (x^θ)::Real
end # u_cd1

# ----------------
"""
    u_cd2(x::Real ; θ::Real = 0.5)

Cobb-Douglas utility function of two good whose expression is:
``u(x_1,x_2)=x_1^\\theta\\cdot x_2^{1-\\theta}``
"""
function u_cd2(x1::Real, x2::Real ; θ::Real = 0.5)
    return (x1^θ * x2^(1-θ))::Real
end # u_cd2

# ----------------
"""
    u_cdk(X::Vector ; W::Vector = ones(length(X)))

Cobb-Douglas utility function of k goods whose expression is:
``u(X)=\\prod \\log(x_i)^{w_i}``.
requiring the length of `W` must be equal to the length of `X`.
This function does not require the summation of `W` must be 1.
"""
function u_cdk(X::Vector ; W::Vector = ones(length(X)) )
    return exp(u_logcdk(X, W = W))::Real
end # u_cdk

# ----------------
"""
    u_logcd2(x1::Real, x2::Real ; β::Real = 1.0 )

Logarithm Cobb-Douglas utility function of k goods whose expression is:
``u(x_1,x_2)=\\log(x_1)+\\beta\\log(x_2)``
"""
function u_logcd2(x1::Real, x2::Real ; β::Real = 1.0 )
    return ( log(x1) + β * log(x2) )::Real
end

# ----------------
"""
    u_logcdk(X::Vector ; W::Vector = ones(length(X)))

Logarithm Cobb-Douglas utility function whose expression is:
``u(X)=\\sum w_i\\log(x_i)``.
requiring the length of `W` must be equal to the length of `X`.
"""
function u_logcdk(X::Vector ; W::Vector = ones(length(X)) )
    local U::Real = 0.0
    for (x,w) in zip(X,W); U += w * x; end # for
    return U::Real
end # u_logcdk

# ----------------
"""
    u_leontiefk(X::Vector)

Leontief (perfect complimentary) utility function of k goods:
``u(\\mathbf{x})=\\min\\{x_1,x_2,\\dots\\}``
"""
function u_leontiefk(X::Vector)
    return findmin(X)
end # u_leontiefk

# =================================
end # UtilFuncs


using Printf

vandercorput(num::Integer, base::Integer) = sum(d * Float64(base) ^ -ex for (ex, d) in enumerate(digits(num, base)))

for base in 2:9
    @printf("%10s %i:", "Base", base)
    for num in 0:9 @printf("%7.3f", vandercorput(num, base)) end
    println(" [...]")
end


using LinearAlgebra

function matcond(n::Int, c::Float64)
# Function generates a random square matrix A of size n with
# a given condition number c.
# Inputs:
#	n: size of matrix A, n>1
#	c: condition of matrix A, c>= 1.0
#
# Usage: matcond(10, 100.0)
#
# Pawel Zielinski
        if n < 2
         error("size n should be > 1")
        end
        if c< 1.0
         error("condition number  c of a matrix  should be >= 1.0")
        end
        (U,S,V)=svd(rand(n,n))
        return U*diagm(0 =>[LinRange(1.0,c,n);])*V'
end


#Minimum cover EMS method
using JuMP,Gurobi

#define the model
m =Model(Gurobi.Optimizer)

#Let n represents the number of decision variables
n=10

#declaring variables
@variable(m, x[1:n], Bin) # binary constraint

#define the objective function
@objective(m , Min, sum(x[i] for i =1:n)) # objective is to minimize the coverage

#Since decision variables x2,x3,x8 and x10>=1, I declared them in a for-loop together
for i in [2,3,8,10]
	@constraint(m,x[i]>=1)    #(x[i] for i in [2,3,8,10]) >=1)
end

#additional constraint
@constraint(m, x[1]+x[2]>=1)    #district 2
@constraint(m, x[1]+x[3]>=1)    #district 3
@constraint(m, x[2]+x[4]>=1)	#district 7
@constraint(m, x[3]+x[4]>=1)	#district 8
@constraint(m, x[4]+x[6]>=1)	#district 10
@constraint(m, x[4]+x[5]>=1)	#district 11
@constraint(m, x[4]+x[5]+x[7]>=1)	#distict 13
@constraint(m, x[4]+x[5]+x[6]>=1)	#distict 12
@constraint(m, x[8]+x[9]>=1)		#distict 14, district 18
@constraint(m, x[6]+x[9]>=1)		#distict 15
@constraint(m, x[5]+x[6]>=1)		#distict 16
@constraint(m, x[5]+x[7]+x[10]>=1)	#distict 17
@constraint(m, x[8]+x[10]>=1)		#distict 19

print(m)

#Printing the optimal solutions obtained
println("Optimal Solutions:")
status = JuMP.optimize!(m)
println(" Objective value: ",JuMP.objective_value(m))

println("Decision variable values:")
for i=1:10
  println("x[$i] = ",   JuMP.value(x[i]))
end


function dayNumber(inDate::Date)
    return Dates.date2epochdays(inDate)::Integer
end

excelcostant = 693959;

"""
From Excel Number Format to Date

		Date=fromExcelNumberToDate(ExcelNumber)

Where:\n
		ExcelNumber = Integer representing a date in the excel format.

		Date      = Date representing the input in the Julia object format.

# Example
```julia-repl
julia> fromExcelNumberToDate(45000)
2023-03-15
```
"""
function fromExcelNumberToDate(num::Integer)::Date
    return Dates.Dates.epochdays2date(excelcostant + num)
end

"""
Actual Number of days between two dates

		ndays=daysact(Date1,Date2)

Where:\n
		Date1 = Start date.
		Date2 = End date.

		ndays      = Actual Number of days between Start Date and End Date.

# Example
```julia-repl
julia> daysact(Date(1996,10,12),Date(1998,1,10))
455
```
"""
function daysact(Date1::Date, Date2::Date)::Integer
    D1 = dayNumber(Date1)
    D2 = dayNumber(Date2)
    dayCount = D2 - D1
    return dayCount
end

function isLastOfFebruary(inDate::Date)::Bool
    return (Dates.isleapyear(Dates.year(inDate)) && (Dates.day(inDate) == 29) && (Dates.month(inDate) == 2)) || ((!Dates.isleapyear(Dates.year(inDate))) && (Dates.day(inDate) == 28) && (Dates.month(inDate) == 2))
end

currMaxImplemented = 7;

"""
Fraction of year between two Dates according the following convention

		yfr=yearfrac(Date1,Date2,basis)

Where:\n
		Date1 = Start date.
		Date2 = End date.
		basis = Integer representing the following conventions:
				- 0 = (ACT/ACT)
				- 1 = (30/360 SIA)
				- 2 = (ACT/360)
				- 3 = (ACT/365)
				- 4 = (30/360 PSA)
				- 5 = (30/360 ISDA)
				- 6 = (30E/360)
				- 7 = (ACT/365 JPN)

		yfr      = fraction of year between start and end date according to basis.

# Example
```julia-repl
julia> yearfrac(Date(1996,10,12),Date(1998,1,10),1)
1.2444444444444445
```
"""
function yearfrac(startDate::Date, endDate::Date, convention::Integer)::Real
    if (convention < 0)
        error("Negative basis are not defined, check the help")
    end
    if (convention > currMaxImplemented)
        error("Convention not implemented yet")
    end
    if (startDate > endDate)
        return -yearfrac(endDate, startDate, convention)
    elseif (startDate == endDate)
        return 0.0
    end
    yearFrac = 0.0
    y1 = Dates.year(startDate)
    m1 = Dates.month(startDate)
    y2 = Dates.year(endDate)
    m2 = Dates.month(endDate)
    d1 = Dates.day(startDate)
    d2 = Dates.day(endDate)
    if (convention == 0)#(ACT/ACT)
        Nday = daysact(startDate, endDate)
        EndOFYear = Date(1)
        if (isLastOfFebruary(startDate) && (Dates.day(startDate) == 29))
            EndOFYear = Date(Dates.year(startDate) + 1, 3, 1)
        else
            EndOFYear = Date(Dates.year(startDate) + 1, Dates.month(startDate), Dates.day(startDate))
        end
        yearFrac = (Nday) / daysact(startDate, EndOFYear)
    elseif (convention == 1)  #(30/360 SIA)
        if (isLastOfFebruary(startDate) && isLastOfFebruary(endDate))
            d2 = 30
        end
        if (isLastOfFebruary(startDate) || Dates.day(startDate) == 31)
            d1 = 30
        end
        if (d1 == 30 && d2 == 31)
            d2 = 30
        end
        dy = y2 - y1
        dm = m2 - m1
        dd = d2 - d1
        yearFrac = (360.0 * dy + 30.0 * dm + dd) / 360.0
    elseif (convention == 2)#(ACT/360)
        Nday = daysact(startDate, endDate)
        yearFrac = (Nday) / 360.0
    elseif (convention == 3)# (ACT/365)
        Nday = daysact(startDate, endDate)
        yearFrac = (Nday) / 365.0
    elseif (convention == 4)# (30/360 PSA)
        if ((Dates.day(startDate) == 31) || isLastOfFebruary(startDate))
            d1 = 30
        end
        if ((Dates.day(startDate) == 30 || isLastOfFebruary(startDate)) && Dates.day(endDate) == 31)
            d2 = 30
        end
        dy = y2 - y1
        dm = m2 - m1
        dd = d2 - d1
        yearFrac = (360.0 * dy + 30.0 * dm + dd) / 360.0

    elseif (convention == 5)#(30/360 ISDA)
        y1 = Dates.year(startDate)
        m1 = Dates.month(startDate)
        y2 = Dates.year(endDate)
        m2 = Dates.month(endDate)
        if (Dates.day(startDate) < 31)
            d1 = Dates.day(startDate)
        else
            d1 = 30
        end
        if ((Dates.day(endDate) == 31) && (d1 > 29))
            d2 = 30
        else
            d2 = Dates.day(endDate)
        end
        yearFrac = (360.0 * ((y2 - y1)) + 30.0 * ((m2 - m1)) + (d2 - d1)) / 360.0

    elseif (convention == 6)#(30E/360)
        if (d1 == 31)
            d1 = 30
        end
        if (d2 == 31)
            d2 = 30
        end
        dy = y2 - y1
        dm = m2 - m1
        dd = d2 - d1
        yearFrac = (360.0 * dy + 30.0 * dm + dd) / 360.0
    elseif (convention == 7)#(ACT/365 JPN)
        daydistance = [0; 31; 59; 90; 120; 151; 181; 212; 243; 273; 304; 334]
        dy = y2 - y1
        dd = d2 - d1
        dayCount = (365.0 * dy + daydistance[m2] - daydistance[m1] + dd)
        yearFrac = dayCount / 365.0
    end

    return yearFrac
end


A = [[+4.  +5.  -2.]
     [+7.  -1.  +2.]
     [+3.  +1.  +4.]]
b = [-14., +42., +28.]
x = [+4., -4., +5.]

lu = LUSolverLAPACK(A, b)

factorize!(lu)
solve!(lu)

@test lu.b == x


doc"""
    TDist(ν)

The *Students T distribution* with `ν` degrees of freedom has probability density function

$f(x; d) = \frac{1}{\sqrt{d} B(1/2, d/2)}
\left( 1 + \frac{x^2}{d} \right)^{-\frac{d + 1}{2}}$

```julia
TDist(d)      # t-distribution with d degrees of freedom

params(d)     # Get the parameters, i.e. (d,)
dof(d)        # Get the degrees of freedom, i.e. d
```

External links

[Student's T distribution on Wikipedia](https://en.wikipedia.org/wiki/Student%27s_t-distribution)

"""
immutable TDist <: ContinuousUnivariateDistribution
    ν::Float64

    TDist(ν::Real) = (@check_args(TDist, ν > zero(ν)); new(ν))
end

@distr_support TDist -Inf Inf

#### Parameters

dof(d::TDist) = d.ν
params(d::TDist) = (d.ν,)

#### Statistics

mean(d::TDist) = d.ν > 1.0 ? 0.0 : NaN
median(d::TDist) = 0.0
mode(d::TDist) = 0.0

function var(d::TDist)
    ν = d.ν
    ν > 2.0 ? ν / (ν - 2.0) :
    ν > 1.0 ? Inf : NaN
end

skewness(d::TDist) = d.ν > 3.0 ? 0.0 : NaN

function kurtosis(d::TDist)
    ν = d.ν
    ν > 4.0 ? 6.0 / (ν - 4.0) :
    ν > 2.0 ? Inf : NaN
end

function entropy(d::TDist)
    h = 0.5 * d.ν
    h1 = h + 0.5
    h1 * (digamma(h1) - digamma(h)) + 0.5 * log(d.ν) + lbeta(h, 0.5)
end

#### Evaluation & Sampling

@_delegate_statsfuns TDist tdist ν

rand(d::TDist) = StatsFuns.Rmath.tdistrand(d.ν)

function cf(d::TDist, t::Real)
    t == 0 && return complex(1.0)
    h = d.ν * 0.5
    q = d.ν * 0.25
    t2 = t*t
    complex(2*(q*t2)^q*besselk(h,sqrt(d.ν)*abs(t))/gamma(h))
end

gradlogpdf(d::TDist, x::Float64) = -((d.ν + 1.0) * x) / (x^2 + d.ν)


using DifferentialEquations, Plots, LaTeXStrings

##
function Hénon_Heiles(du,u,p,t)
    x  = u[1]
    y  = u[2]
    dx = u[3]
    dy = u[4]
    du[1] = dx
    du[2] = dy
    du[3] = -x - 2x*y
    du[4] = y^2 - y -x^2
end

##
initial1 = [1/6,0.0,-0.2,0.0]
initial2 = [1/6,0.0 + 0.01,-0.2+0.01,0.0]

##
e,x,y,ydot = initial1
xdot = sqrt(2*e - ydot^2 - y^2 + (2//3)*y^3)
ini = [x,y,xdot,ydot]
tspan = (0,150.)
prob = ODEProblem(Hénon_Heiles, ini, tspan)
sol = solve(prob, Vern9(), abs_tol=1e-16, rel_tol=1e-16,saveat = 0.1);
plot(sol, vars=(1,2), title = "The orbit of the Hénon-Heiles system", xaxis = "x", yaxis = "y", label = false,linecolor = :blue)
scatter!([x],[y],label = false,markercolor = :blue)

##
e2,x2,y2,ydot2 = initial2
xdot2 = sqrt(2*e2 - ydot2^2 - y2^2 + (2//3)*y2^3)
ini2 = [x2,y2,xdot2,ydot2]
prob2 = ODEProblem(Hénon_Heiles, ini2, tspan)
sol2 = solve(prob2, Vern9(), abs_tol=1e-16, rel_tol=1e-16,saveat = 0.1);
plot!(sol2, vars=(1,2), label = false,linecolor = :red)
scatter!([x2],[y2],label = false,markercolor = :red)


#=
You are given an array of non-negative integers that represents a two-dimensional elevation map where each element is unit-width wall and the integer is the height. Suppose it will rain and all spots between two walls get filled up.

Compute how many units of water remain trapped on the map in O(N) time and O(1) space.

For example, given the input [2, 1, 2], we can hold 1 unit of water in the middle.

Given the input [3, 0, 1, 3, 0, 5], we can hold 3 units in the first index, 2 in the second, and 3 in the fourth index (we cannot hold 5 since it would run off to the left), so we can trap 8 units of water.
=#

function watertrapped(elevation_map::Array{Int64, 1})
    sum_water = 0
    for map_unit in 2:length(elevation_map)-1
        max_left = maximum(elevation_map[1:map_unit-1])
        max_right = maximum(elevation_map[map_unit+1:end])
        water_trapped = maximum([minimum([max_left, max_right]) - elevation_map[map_unit], 0])
        sum_water = sum_water + water_trapped
    end
    return sum_water
end


"""
    Compression(c, d, i; σ = 1.0, μ = 0.0)

Maps ``ℝ → (-1, 1)``.

# Arguments
- `c`: the "compression", i.e., the map ``ℝ → (-1, 1)``,
- `d`: the "decompression", i.e., the map ``(-1, 1) → ℝ``,
- `i`: the jacobian of the map, i.e., ``∂d(τ) /∂τ``,
- `σ`, `μ`: control the width and offset in the numerical integrations
"""
struct Compression
	comp::Function
	decomp::Function
	inv_jacobian::Function

	σ::Float64
	μ::Float64
	function Compression(c, d, i; σ = 1.0, μ = 0.0)
		σ ≤ 0.0 && error("σ needs to be positive")
		new(c, d, i, σ, μ)
	end
end

"""
    Algebraic(; σ = 1.0, μ = 0.0)

Maps ``ℝ → (-1, 1)`` by ``t ↦ \\frac{t}{1 + |t|}``.
Equivalent symbol is `:algebraic`.
"""
Algebraic(; σ = 1.0, μ = 0.0) = Compression(
	t ->   t / (1.0 + abs(t)),
	τ ->   τ / (1.0 - abs(τ)),
	τ -> 1.0 / (1.0 - abs(τ))^2, # == (1.0 + abs( τ / (1.0 - abs(τ)) ) )^2
	σ = σ, μ = μ
)

"""
    Exponential(; σ = 1.0, μ = 0.0)

Maps ``ℝ → (-1, 1)`` by ``t ↦ \\mathrm{tanh}(t)``.
Equivalent symbol is `:exponential`.
"""
Exponential(; σ = 1.0, μ = 0.0) = Compression(
	t -> tanh(t),
	τ -> atanh(τ),
	τ -> 1.0 / (1.0 - τ^2), # == cosh(atanh(τ))^2
	σ = σ, μ = μ
)

"""
    Trigonometric(; σ = 1.0, μ = 0.0)

Maps ``ℝ → (-1, 1)`` by ``t ↦ \\frac{2}{π} \\mathrm{tan}^{-1}(t)``.
Equivalent symbol is `:trigonometric`.
"""
Trigonometric(; σ = 1.0, μ = 0.0) = Compression(
	t -> 2.0 * atan(t) / π,
	τ -> tan(π*τ/2.0),
	τ -> π * (1.0 + tan(π*τ/2.0)^2) / 2.0,
	σ = σ, μ = μ
)

Compressions = Dict(
	:algebraic     => Algebraic(),
	:exponential   => Exponential(),
	:trigonometric => Trigonometric()
)