module BaryTrees

    using Plots, LinearAlgebra

    import Base.∈, Base.∉, Plots.Shape, Base.insert!

    export BaryTree, Point, insert!, ∈, ∉, Draw, Draw!, Query, BaryCentric,
           Cartesian2BaryCentric, BaryCentric2Cartesian

    const Point = Tuple{Float64,Float64}

    Point(x::Float64,y::Float64) = Point((x,y))

    struct BaryCentric
        """
            Represents the 3 cartesian (2D) points of a Barycentric
            coordinate system
        """
        r₁::Point
        r₂::Point
        r₃::Point
    end

    function BaryCentric2Cartesian(λ₁,λ₂,λ₃,p::BaryCentric)
        return Point(
            λ₁*p.r₁[1]+λ₂*p.r₂[1]+λ₃*p.r₃[1],
            λ₁*p.r₁[2]+λ₂*p.r₂[2]+λ₃*p.r₃[2],
        )
    end

    function Cartesian2BaryCentric(p::Point,b::BaryCentric)
        x,y = p
        x1,x2,x3 = b.r₁[1],b.r₂[1],b.r₃[1]
        y1,y2,y3 = b.r₁[2],b.r₂[2],b.r₃[2]

        l1 = ( (y2-y3)*(x-x3)+(x3-x2)*(y-y3) ) / ( (y2-y3)*(x1-x3) + (x3-x2)*(y1-y3) )
        l2 = ( (y3-y1)*(x-x3) + (x1-x3)*(y-y3) ) / ( (y2-y3)*(x1-x3) + (x3-x2)*(y1-y3) )
        return (
            l1,l2,1.0-l1-l2,
        )
    end

    function Medians(b::BaryCentric)::Vector{Point}
        """
            Returns the three mid points of each triangular edge of
            the triangle defining b in cartesian units
        """
        return [
            BaryCentric2Cartesian(1/2,0,1/2,b)
            BaryCentric2Cartesian(0,1/2,1/2,b)
            BaryCentric2Cartesian(1/2,1/2,0,b)
        ]
    end

    struct AxisAlignedTriangle
        """
            wrapper around BaryCentric
        """
        b::BaryCentric
    end

    Medians(t::AxisAlignedTriangle)::Vector{Point} = Medians(t.b)

    mutable struct BaryTree
        root::Bool
        boundary::AxisAlignedTriangle
        point::Union{Nothing,Point}
        c::Union{Nothing,BaryTree}
        t::Union{Nothing,BaryTree}
        l::Union{Nothing,BaryTree}
        r::Union{Nothing,BaryTree}
    end

    function ∈(p::Point,t::AxisAlignedTriangle)::Bool
        """
            Test if a cartesian point is inside a triangle
                using BaryCentric Coordinates of the triangle
        """
        b = Cartesian2BaryCentric(p,t.b)
        if (b[1] >= 0 && b[2] >= 0 && b[1]+b[2] < 1)
            return true
        else
            return false
        end
    end

    function points(a::AxisAlignedTriangle)::Vector{Point}
        """
            Short hand for the corners of an AxisAlignedTriangle
        """
        return  Point.([
                a.b.r₁,
                a.b.r₂,
                a.b.r₃
            ])
    end

    function intersects(a::AxisAlignedTriangle,b::AxisAlignedTriangle)::Bool
        """
            Tests if any of the corners of triangle a are in triangle b
        """
        for p in points(a)
            if p ∈ b
                return true
            end
        end
        return false
    end

    ∉(p::Point,t::AxisAlignedTriangle)::Bool = !∈(p,t)

    function subdivide(t::AxisAlignedTriangle)::Vector{AxisAlignedTriangle}
        """
            Subdivides a triangle (as in the Sierpinski construction) and returns
            them each initialised with their own coordinate systems
        """
        m1,m2,m3 = Medians(t)
        return AxisAlignedTriangle.([
            BaryCentric(t.b.r₁,m3,m1),
            BaryCentric(m3,m2,m1),
            BaryCentric(m3,t.b.r₂,m2),
            BaryCentric(m1,m2,t.b.r₃)
        ])

    end

    ∈(p::Point,q::BaryTree)::Bool = ∈(p,q.boundary)
    ∉(p::Point,q::BaryTree)::Bool = ∉(p,q.boundary)
    isempty(q::BaryTree)::Bool = q.point == nothing

    BaryTree(a::AxisAlignedTriangle)::BaryTree = BaryTree(false,a,nothing,nothing,nothing,nothing,nothing)
    BaryTree(r::Bool,a::AxisAlignedTriangle)::BaryTree = BaryTree(r,a,nothing,nothing,nothing,nothing,nothing)
    BaryTree(a::BaryCentric)::BaryTree = BaryTree(AxisAlignedTriangle(a))

    function subdivide!(q::BaryTree)::Nothing
        """
            Subdivide this tree node as in the Sierpinski construction
            and store it in t,c,l,r child nodes
        """
        trigs = subdivide(q.boundary)
        q.t = BaryTree(trigs[1])
        q.c = BaryTree(trigs[2])
        q.l = BaryTree(trigs[3])
        q.r = BaryTree(trigs[4])
        nothing
    end

    function insert!(p::Point,q::BaryTree)::Bool
        """
            Trial an insert of point p (cartesian) in q's
            triangular boundary
        """
        if (p ∉ q)
            return false
        end

        if isempty(q) && q.t == nothing
            subdivide!(q)
            if p ∈ q.t
                q.t.point = p
            end

            if p ∈ q.c
                q.c.point = p
            end
            if p ∈ q.l
                q.l.point = p
            end
            if p ∈ q.r
                q.r.point = p
            end
            return true
        end

        if (q.t == nothing)
            subdivide!(q)
        end

        if insert!(p,q.t)
            return true
        end
        if insert!(p,q.c)
            return true
        end
        if insert!(p,q.l)
            return true
        end
        if insert!(p,q.r)
            return true
        end

        return false
    end

    Shape(a::AxisAlignedTriangle) = Shape(
        Shape([a.b.r₁[1],a.b.r₂[1],a.b.r₃[1]],[a.b.r₁[2],a.b.r₂[2],a.b.r₃[2]])
    )

    function Draw!(a::AxisAlignedTriangle)
        plot!(Shape(a),label="",fillalpha=0.0)
    end

    function Draw(Q::BaryTree,points=true)
        p = plot(aspect_ratio=:equal,label="")
        Draw!(Q.boundary)
        for q in [Q.t,Q.c,Q.l,Q.r]
            q != nothing ? Draw!(q,points) : nothing
        end
        return p
    end

    function Draw!(Q::BaryTree,points=true)
        Draw!(Q.boundary)
        for q in [Q.t,Q.c,Q.l,Q.r]
            q != nothing ? Draw!(q) : nothing
        end
    end

    function query(a::AxisAlignedTriangle, Q::BaryTree)
        if ~intersects(a,Q.boundary)
            return Vector{Point}([])
        end

        if Q.point == nothing
            return Vector{Point}([])
        end

        result = Vector{Point}()

        if (Q.point ∈ a)
            push!(result,Q.point)
        end

        for q in [Q.t,Q.c,Q.l,Q.r]
            if q != nothing
                for p in query(a,q)
                    push!(result,p)
                end
            end
        end

        return result
    end

    function size(Q::BaryTree)
        s = 1
        for q in [Q.t,Q.c,Q.l,Q.r]
            if q != nothing
                s += size(q)
            end
        end
        return s
    end
end


#using Parameters
@with_kw mutable struct Params
# BWH parameters

# Mathematical model parameters
    α::Float64 = 33.3333         # infiltration rate in vegetated soil
    f::Float64 = 0.1             # infiltration contrast
    η::Float64 = 3.5             # root augmentation
    γ::Float64 = 16.6667         # soil water consumption rate
    ρ::Float64 = 0.95            # shading parameter
    ν::Float64 = 3.3333          # soil water evaporation rate
    q::Float64 = 0.05            # infiltration shape parameter
    db::Float64 = 0.0333333      # b diffusivity
    dw::Float64 = 3.33333        # w diffusivity
    dh::Float64 = 333.333        # h diffusivity
    p::Float64 = 1.2             # precipitation rate

# Domain size
    Lx::Float64 = 28             # nondimensional X Domain size
    Ly::Float64 = 28             # nondimensional Y domain size

# Numerical code options
    nx::Int64 = 128              # X resolution
    ny::Int64 = 128              # Y resolution
    dx::Float64 = Lx/nx
    nsigma::Int64 = 12           # Number of sigmas in the integral approximation (12-16 probably ok)
    nint::Int64 = 3              # Frequency for b and w integral calculation (could be 1, but not much more than 3)

# Run control options
    outfile = "bwh.dat"          # Name of output file
    initfile = "bwh.init.dat"    # Name of initialization file (used if freadinit==true)
    finalfile = "bwh.final.dat"  # Name of final state
    freadinit::Bool = false      # Start from restart
    fplot::Bool = false          # if to show plots during run
    fsave::Bool = true           # if to save intermediate results
    dt::Float64 = 0.1            # Length of a single integration chunk (this value seems ok)
    nsave::Int64 = 10            # How often (in chunks) to save
    nstep::Int64 = 100           # How many chuncks to run  (total runtime = nstep*dt)
end


"""
    belief_ellipse(b::GaussianBelief, P::Float=0.95; δ::Number=5)

Construct and return the x and y points of a 2D gaussian belief,
with P being the total probability captured by the ellipse (P ∈ (0,1)),
and δ the degree increment between points.
"""
function belief_ellipse(b::GaussianBelief, P::Number=0.95; δ::Number=5)
    @assert 0<P<1
    @assert 0<δ<360

    θ = (pi/180)*collect(0:δ:360)
    rad_w = (-2*log(1-P))^0.5

    w1 = rad_w*cos.(θ)
    w2 = rad_w*sin.(θ)

    Sig12 = cholesky(b.Σ)
    x=Sig12.L*[w1 w2]' .+ b.μ
    return x[1,:], x[2,:]
end

"""
    belief_ellipse(μ::AbstractVector, Σ::AbstractMatrix, P::Float=0.95; δ::Number=5)

Construct and return the x and y points of a 2D gaussian belief,
with P being the total probability captured by the ellipse (P ∈ (0,1)),
and δ the degree increment between points.
"""

function belief_ellipse(μ::AbstractVector, Σ::AbstractMatrix, P::Number=0.95; δ::Number=5)
    @assert 0<P<1
    @assert 0<δ<360

    θ = (pi/180)*collect(0:δ:360)
    rad_w = (-2*log(1-P))^0.5

    w1 = rad_w*cos.(θ)
    w2 = rad_w*sin.(θ)

    Sig12 = cholesky(Σ)
    x = Sig12.L*[w1 w2]' .+ μ
    return x[1,:], x[2,:]
end


using Match
using Test

input = joinpath(@__DIR__, "input")
raw = readlines(input)

struct Operation
    name::String
    value::Int
end

function operations(lines)
    acc = []
    for line in lines
        if startswith(line, "cut")
            _, n = split(line)
            push!(acc, Operation("cut", parse(Int, n)))
        elseif startswith(line, "deal into")
            push!(acc, Operation("reverse", 0))
        elseif startswith(line, "deal with")
            _, _, _, n = split(line)
            push!(acc, Operation("deal", parse(Int, n)))
        end
    end
    return acc
end

deal = (c, n) -> return mod(-1 * c - 1, n)
inc = (c, n, i) -> return mod(c * i, n)
cut = (c, n, i) -> return mod(c - i, n)

function shuffle(idx, deck, ops)
    c = idx
    n = deck
    for op in ops
        @match op.name begin
            "cut" => begin
                c = cut(c, n, op.value)
            end
            "reverse" => begin
                c = deal(c, n)
            end
            "deal" => begin
                c = inc(c, n, op.value)
            end
        end
    end
    return c
end

function fshuffle(idx, deck, ops, iterations)
    offset, increment = BigInt(0), BigInt(1)
    for op in ops
        @match op.name begin
            "cut" => begin
                n = mod(op.value, deck)
                offset = mod(offset + n, deck)
            end
            "reverse" => begin
                increment = mod(-1 * increment, deck)
                offset = mod(-1 * offset - 1, deck)
            end
            "deal" => begin
                n = invmod(mod(op.value, deck), deck)
                increment = mod(increment * n, deck)
                offset = mod(offset * n, deck)
            end
        end
    end
    i = powermod(increment, iterations, deck)
    o = if increment != 1
        (i - 1) * invmod(increment - 1, deck)
    else
        iterations
    end
    return mod(i * idx + o * offset, deck)
end

ops = operations(raw)
p1 = shuffle(2019, 10007, ops)
@assert p1 == 3589

reverse!(ops)
p2 = fshuffle(2020, 119315717514047, ops, 101741582076661)
@assert p2 == 4893716342290

print("-----------------------------------------------------------------------\n")
print("slam shuffle -- part one\n    card: $p1\n")
print("slam shuffle -- part two\n    card: $p2\n")
print("-----------------------------------------------------------------------\n")


using Random
using Soss
using DataFrames
using StatsPlots
using BenchmarkTools

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

sir_markov = @model state,p begin
    # Unpack parameters
    β = p.β
    c = p.c
    γ = p.γ
    δt = p.δt

    # Unpack starting counts
    t0 = state.t
    S0 = state.S
    I0 = state.I
    R0 = state.R
    N = S0 + I0 + R0

    # Transitions between states
    S_I ~ Binomial(S0, rate_to_proportion(β*c*I0/N,δt))
    I_R ~ Binomial(I0, rate_to_proportion(γ,δt))

    # Updated state
    t = t0 + δt
    S = S0 - S_I
    I = I0 + S_I - I_R
    R = R0 + I_R

    next = (p=p, state=(t=t,S=S,I=I,R=R))
end;

sir_model = @model u0,p begin
    x ~ MarkovChain(p, sir_markov(state=u0,p=p))
end;

δt = 0.1
nsteps = 400
tmax = nsteps*δt;

u0 = (t=0.0, S=990, I=10, R=0); # t,S,I,R

p = (β=0.05, c=10.0, γ=0.25, δt=δt);

Random.seed!(1234);

r = rand(sir_model(u0=u0,p=p));
data = [u0]
for (n,s) in enumerate(r.x)
    n>nsteps && break
    push!(data,s)
end;

df_markov = DataFrame(data);

@df df_markov plot(:t,
    [:S :I :R],
    label=["S" "I" "R"],
    xlabel="Time",
    ylabel="Number")

@benchmark begin
    r = rand(sir_model(u0=u0,p=p));
    data = [u0]
    for (n,s) in enumerate(r.x)
        n>nsteps && break
        push!(data,s)
    end
end


# Description:
# Dimensions: d
#
# The Styblinski-Tang function is shown here in its two-dimensional form.
#
# Input Domain:
# The function is usually evaluated on the hypercube xi ∈ [-5, 5], for all i = 1, …, d.
#
# Global Minimum:
# https://www.sfu.ca/~ssurjano/stybtang3.png

function styblinskiTang(xx)
    d = length(xx);
    sum = 0;
    for ii = 1:d
       	xi = xx[ii];
       	new = xi^4 - 16 * xi^2 + 5 * xi;
       	sum = sum + new;
    end

    y = sum / 2;

    return [y]
end


export nrmsd

"""
    nrmsd(I,Ireco)

computes the normalized root mean squared error of the image `Ireco`
with respect to the image `I`.
"""
function nrmsd(I,Ireco)
  N = length(I)

  # This is a little trick. We usually are not interested in simple scalings
  # and therefore "calibrate" them away
  alpha = (dot(vec(I),vec(Ireco))+dot(vec(Ireco),vec(I))) /
          (2*dot(vec(Ireco),vec(Ireco)))
  Ireco[:] .*= alpha

  RMS =  1.0/sqrt(N)*norm(vec(I)-vec(Ireco))
  NRMS = RMS/(maximum(abs.(I))-minimum(abs.(I)) )
  return NRMS
end


doc"""
    Exponential(θ)

The *Exponential distribution* with scale parameter `θ` has probability density function

$f(x; \theta) = \frac{1}{\theta} e^{-\frac{x}{\theta}}, \quad x > 0$

```julia
Exponential()      # Exponential distribution with unit scale, i.e. Exponential(1)
Exponential(b)     # Exponential distribution with scale b

params(d)          # Get the parameters, i.e. (b,)
scale(d)           # Get the scale parameter, i.e. b
rate(d)            # Get the rate parameter, i.e. 1 / b
```

External links

* [Exponential distribution on Wikipedia](http://en.wikipedia.org/wiki/Exponential_distribution)

"""
immutable Exponential{T<:Real} <: ContinuousUnivariateDistribution
    θ::T		# note: scale not rate

    Exponential(θ::Real) = (@check_args(Exponential, θ > zero(θ)); new(θ))
end

Exponential{T<:Real}(θ::T) = Exponential{T}(θ)
Exponential(θ::Integer) = Exponential(Float64(θ))
Exponential() = Exponential(1.0)

@distr_support Exponential 0.0 Inf

### Conversions
convert{T <: Real, S <: Real}(::Type{Exponential{T}}, θ::S) = Exponential(T(θ))
convert{T <: Real, S <: Real}(::Type{Exponential{T}}, d::Exponential{S}) = Exponential(T(d.θ))

#### Parameters

scale(d::Exponential) = d.θ
rate(d::Exponential) = 1 / d.θ

params(d::Exponential) = (d.θ,)
@inline partype{T<:Real}(d::Exponential{T}) = T

#### Statistics

mean(d::Exponential) = d.θ
median(d::Exponential) = logtwo * d.θ
mode{T<:Real}(d::Exponential{T}) = zero(T)

var(d::Exponential) = d.θ^2
skewness{T<:Real}(d::Exponential{T}) = T(2)
kurtosis{T<:Real}(d::Exponential{T}) = T(6)

entropy(d::Exponential) = 1 + log(d.θ)

#### Evaluation

zval(d::Exponential, x::Real) = x / d.θ
xval(d::Exponential, z::Real) = z * d.θ

pdf(d::Exponential, x::Real) = (λ = rate(d); x < 0 ? zero(λ) : λ * exp(-λ * x))
function logpdf{T<:Real}(d::Exponential{T}, x::Real)
    (λ = rate(d); x < 0 ? -T(Inf) : log(λ) - λ * x)
end

cdf{T<:Real}(d::Exponential{T}, x::Real) = x > 0 ? -expm1(-zval(d, x)) : zero(T)
ccdf{T<:Real}(d::Exponential{T}, x::Real) = x > 0 ? exp(-zval(d, x)) : zero(T)
logcdf{T<:Real}(d::Exponential{T}, x::Real) = x > 0 ? log1mexp(-zval(d, x)) : -T(Inf)
logccdf{T<:Real}(d::Exponential{T}, x::Real) = x > 0 ? -zval(d, x) : zero(T)

quantile(d::Exponential, p::Real) = -xval(d, log1p(-p))
cquantile(d::Exponential, p::Real) = -xval(d, log(p))
invlogcdf(d::Exponential, lp::Real) = -xval(d, log1mexp(lp))
invlogccdf(d::Exponential, lp::Real) = -xval(d, lp)

gradlogpdf{T<:Real}(d::Exponential{T}, x::Real) = x > 0 ? -rate(d) : zero(T)

mgf(d::Exponential, t::Real) = 1/(1 - t * scale(d))
cf(d::Exponential, t::Real) = 1/(1 - t * im * scale(d))

#### Sampling

rand(d::Exponential) = xval(d, randexp())

#### Fit model

immutable ExponentialStats <: SufficientStats
    sx::Float64   # (weighted) sum of x
    sw::Float64   # sum of sample weights

    ExponentialStats(sx::Real, sw::Real) = new(sx, sw)
end

suffstats{T<:Real}(::Type{Exponential}, x::AbstractArray{T}) = ExponentialStats(sum(x), length(x))
suffstats{T<:Real}(::Type{Exponential}, x::AbstractArray{T}, w::AbstractArray{Float64}) = ExponentialStats(dot(x, w), sum(w))

fit_mle(::Type{Exponential}, ss::ExponentialStats) = Exponential(ss.sx / ss.sw)


"""
Graph Laplacian of a `m×n` lattice.
"""
function gridlaplacian(T, m, n)
    S = sparse(T(0.0)I, n*m, n*m)
    linear = LinearIndices((1:m, 1:n))
    for i in 1:m
        for j in 1:n
            for (i2, j2) in ((i + 1, j), (i, j + 1))
                if i2 <= m && j2 <= n
                    S[linear[i, j], linear[i2, j2]] -= 1
                    S[linear[i2, j2], linear[i, j]] -= 1

                    S[linear[i, j], linear[i, j]] += 1
                    S[linear[i2, j2], linear[i2, j2]] += 1
                end
            end
        end
    end
    S
end


using DifferentialEquations
using Plots
plotlyjs()

#constants
m = 1.0                             # mass[Kg]
g = 9.81                            # gravitational acceleration [m/s²]

#function to compute the ODEs
function pendulum!(du,u,p,t)
    r,v,θ,ω=u
    du[1]=dr=v
    du[2]=dv=r*ω^2+g*cos(θ)
    du[3]=dθ=ω
    du[4] =dω=-(g\r)*sin(θ) - (2*v*ω)/r
end

#initial conditions
r₀=4                            #initial radial distance
v₀=-0.1                          #initial radial velocity
θ₀ = 1π/18                        # initial angular deflection [rad]
ω₀ = 1                       # initial angular velocity [rad/s]
u₀ = [r₀,v₀,θ₀, ω₀]                   # initial state vector
tspan = (0.0,5.0)             # time interval

#defining the problem and solving
prob = ODEProblem(pendulum!,u₀,tspan)
sol = solve(prob,saveat=0.1)

#plotting
p1=plot(sol,vars=(0,1),label="r",
    color="purple", xaxis="t (s)", yaxis="r (m)")
p2=plot(sol,vars=(0,2),label="v",
    color="blue", xaxis="t (s)", yaxis="v (m/s)")
p3=plot(sol,vars=(0,3),label="θ",
    color="green", xaxis="t (s)", yaxis="θ (rad)")
p4=plot(sol,vars=(0,4),label="ω",
    color="orange", xaxis="t (s)", yaxis="ω (rad/s)")
display(plot(p1,p2,p3,p4,layout=(2,2)))
savefig("./plots/simple length changing pendulum-plot.png")

#trajectory
t=sol.t
U=sol[1:end,:]
(r,v,θ,ω)=[U[x,:] for x in 1:size(U,1)]
x=r.*sin.(θ)
y=zeros(size(t))
z=r.*cos.(θ)
display(plot(x,y,z,linewidth=2,label="trajectory",
    xaxis="x (m)",yaxis="y (m)",zaxis="z (m)",grid=(:on,:black)))
savefig("./plots/simple length changing pendulum-trajectory.png")

Modeling the Dragon Module as a Pendulum/Scripts/length changing pendulum.jl


#!/usr/bin/julia

# Trizen
# Date: 19 August 2016
# https://github.com/trizen

# Count the number of partitions of n, using a recursive relation.

# See also: https://oeis.org/A000041
#           https://en.wikipedia.org/wiki/Partition_(number_theory)

function partitions_count(n::Int64, cache::Dict{Int,Int})

    n <= 1 && return n

    if haskey(cache, n)
        return cache[n]
    end

    sum_1 = 0
    for i in 1:Int64(floor((sqrt(24*n + 1) + 1)/6))
        sum_1 += ((-1)^(i-1) * partitions_count(n - div(i*(3*i - 1), 2), cache))
    end

    sum_2 = 0
    for i in 1:Int64(ceil((sqrt(24*n + 1) - 7)/6))
        sum_2 += ((-1)^(i-1) * partitions_count(n - div((-i) * (-3*i - 1), 2), cache))
    end

    x = (sum_1 + sum_2)
    cache[n] = x
    x
end

println("P(200) = ", partitions_count(200+1, Dict{Int64, Int64}()))        # 3972999029388


"""
    min_even_theta_char(R)

Compute the even theta characteristic for which the theta constant at the input matrix in R has the smallest absolute value.
"""
function min_even_theta_char(R::RiemannMatrix)
    z = zeros(R.g);
    even_chars = even_theta_char(R.g);
    min_theta, char_index = findmin([abs(theta(z, R, char=c)) for c in even_chars])
    char = even_chars[char_index];
    return char
end

"""
    hessian(z, R, char=[])

Compute the hessian matrix of the theta function at input z and input matrix in R, with optional characteristics.
"""
function hessian(z::Array{<:Number}, R::RiemannMatrix, char::Array{}=[])
    M = zeros(Complex{Float64}, R.g, R.g);
    for i=1:R.g
        for j=i:R.g
            derivs = [zeros(R.g), zeros(R.g)];
            derivs[1][i] = 1;
            derivs[2][j] = 1;
            M[i,j] = theta(z, R, char=char, derivs=derivs);
            M[j,i] = M[i,j];
        end
    end
    return M
end

"""
    hessian(z, τ, char=[], siegel=true, ϵ=1.0e-12)

Compute the hessian matrix of the theta function at inputs z and τ, with optional characteristics, and optional inputs specifying whether to compute the Siegel transformation and the error in the value of the theta function.
"""
function hessian(z::Array{<:Number}, τ::Array{<:Number}, char::Array{}=[]; siegel::Bool=true, ϵ::Real=1.0e-12)
    R = RiemannMatrix(τ, siegel=siegel, ϵ=ϵ, nderivs=2);
    return hessian(z, R, char)
end

"""
    schottky_null(R, tol=1.0e-8)

Compute the even theta characteristic where the theta constant vanishes and the hessian matrix at the characteristic, as well as its nonzero eigenvalues, up to the input tolerance. Returns an array containing the characteristic, the hessian and the nonzero eigenvalues. If there is no such characteristic, return the absolute value of the theta constant at the smallest even characteristic.
"""
function schottky_null(R::RiemannMatrix, tol::Real=1.0e-8)
    char = min_even_theta_char(R);
    z = zeros(R.g);
    theta_value = abs(theta(z, R, char=char));
    if theta_value < tol # check that there is a vanishing theta null
        H = hessian(z, R, char);
        hessian_eigvals = filter(x -> abs(x) > tol, eigvals(H));
        return [char, H, hessian_eigvals]
    end
    return theta_value
end

"""
    schottky_null(τ, tol=1.0e-8)

Compute the even theta characteristic where the theta constant vanishes and the hessian matrix at the characteristic, as well as its nonzero eigenvalues, up to the input tolerance. Returns an array containing the characteristic, the hessian and the nonzero eigenvalues. If there is no such characteristic, return the absolute value of the theta constant at the smallest even characteristic.
"""
function schottky_null(τ::Array{<:Number}, tol::Real=1.0e-8)
    R = RiemannMatrix(τ, ϵ=tol, nderivs=2);
    return schottky_null(R, tol)
end


module cheb
using LinearAlgebra

export chebdif, projmat

"""
flippy():
    Utility function that flips matrix up-down and left-right
        (or, equivalently, rotates it by 180deg).
"""
function flippy(M)
    M[end:-1:1,end:-1:1]
end

"""
chebdif():

Julia translation of the MATLAB routine by J.A.C. Weideman & S.C. Reddy (1998).

Original MATAB documentation:

%  The function DM =  chebdif(N,M) computes the differentiation
%  matrices D1, D2, ..., DM on Chebyshev nodes.
%
%  Input:
%  N:        Size of differentiation matrix.
%  M:        Number of derivatives required (integer).
%  Note:     0 < M <= N-1.
%
%  Output:
%  DM:       DM(1:N,1:N,ell) contains ell-th derivative matrix, ell=1..M.
%
%  The code implements two strategies for enhanced
%  accuracy suggested by W. Don and S. Solomonoff in
%  SIAM J. Sci. Comp. Vol. 6, pp. 1253--1268 (1994).
%  The two strategies are (a) the use of trigonometric
%  identities to avoid the computation of differences
%  x(k)-x(j) and (b) the use of the "flipping trick"
%  which is necessary since sin t can be computed to high
%  relative precision when t is small whereas sin (pi-t) cannot.
%
% J.A.C. Weideman, S.C. Reddy 1998.

"""
function chebdif(N::Integer,M::Integer)
    I  = diagm(ones(N))             # Identity matrix
    L  = I.!=0                      # Logical identity matrix

    n1 = N÷2;    n2 = n1 + N%2      # Indices used for flipping trick.

    k  = collect(0:N-1)             # Compute theta vector.
    th = k * π/(N-1)

    temp = collect(N-1:-2:1-N)
    x  = sin.(π*temp/(2.0*(N-1)))   # Compute Chebyshev points

    T  = repeat(th/2,1,N)
    DX = 2*sin.(T'+T) .* sin.(T'-T) # Trigonometric identity.

    DX = [DX[1:n1,:]; -flippy(DX[1:n2,:])]
    DX[L] .= 1.0

    temp = (-1.0).^k
    C = temp * temp'  # generates a Toeplitz matrix with diagonal of +1 and alternating signs on diags
    C[1,:]   *= 2;      C[end,:] *= 2
    C[:,1]   *= 0.5;    C[:,end] *= 0.5

    Z = 1.0./DX
    Z[L] .= 0.0
    D = diagm(ones(N))

    s = size(D)
    DM = zeros(s...,M)
    De = zeros(n2,n2,M)
    Do = zeros(n2,n2,M)
    (Pe,Po,Qe,Qo) = projmat(N)

    for ell in 1:M
        D = ell * Z .* (C .* repeat(diag(D),1,N) - D)
        D[L] = -sum(D,dims=2)
        DM[:,:,ell] = D
        if ell%2 == 0 # 2nd, 4th, 6th etc derivatives do not change parity
            De[:,:,ell] = Pe * D * Qe
            Do[:,:,ell] = Po * D * Qo
        else # 1st, 3rd, 5th etc derivatives change parity
            De[:,:,ell] = Po * D * Qe
            Do[:,:,ell] = Pe * D * Qo
        end
    end
    return (x,DM,De,Do)
end

"""
projmat():

    Constructs projection matrices to project (and de-project) onto space of
    even and odd functions, from general space of functions.

    I could try to be clever here, dealing with N odd or even, but I forgo
    cleverness in favor of readability. (In all likelihood this could be done
    in far fewer lines, I'm sure.)

    2*Peve' de-projects an even function back onto the full function space.
    2*Podd' de-projects an odd function back onto the full function space.

    Peve is the inverse of dPeve
    Podd is the inverse of dPodd
"""
function projmat(N::Integer)
    n1 = N÷2;  n2 = n1 + N%2
    isNodd = (n2>n1) # is N odd?
    top = diagm(ones(n2))
    if isNodd
        Qodd = [ top[1:n2,:] ; -top[n1:-1:1,:] ]
        Qodd[n2,:] .*= 0
        Podd = Qodd' *0.5
        Qeve = [ top[1:n2,:] ; top[n1:-1:1,:] ]
        Peve = Qeve' *0.5
        Peve[:,n2] .*= 2
    else
        Qodd = [ top[1:n1,:] ; -top[n1:-1:1,:] ]
        Qeve = [ top[1:n1,:] ;  top[n1:-1:1,:] ]
        Podd = Qodd' *0.5
        Peve = Qeve' *0.5
    end
    return (Peve,Podd,Qeve,Qodd)
end

end#module


using Base.Threads

"""
    euler_matrix(α::Real, β::Real, γ::Real)

Returns the rotation matrix A based on rotation along the euler angles α, β and γ corresponding to rotations around the x, y, and z-axis respectively.
"""
function euler_matrix(α::Real, β::Real, γ::Real)
    [
        (cos(β)*cos(γ))                         (-cos(β)*sin(γ))                        sin(β)
        (cos(α)*sin(γ) + cos(γ)*sin(α)*sin(β))  (cos(α)*cos(γ) - sin(α)*sin(β)*sin(γ))  (-cos(β)*sin(α))
        (sin(α)*sin(γ) - cos(α)*cos(γ)*sin(β))  (cos(γ)*sin(β) + cos(α)*sin(β)*sin(γ))  (cos(α)*cos(β))

    ]
end

"""
    rotate_3D_quantity(x, α, β, γ)

Rotates a 3D vector along the x-axis with angle α, y-axis with angle β and z-axis with angle γ.
"""
function rotate_3D_quantity(x::Array{<:Real}, α::Real, β::Real, γ::Real)

    A = euler_matrix(α, β, γ)

    return A * x
end

"""
    rotate_3D(x::Array{<:Real}, alpha::Real, beta::Real, gamma::Real)

Rotates and array of 3D positions around the euler angles α, β and γ corresponding to rotations around the x, y, and z-axis respectively.
α, β and γ need to be given in degrees.
"""
function rotate_3D(x::Array{<:Real}, alpha::Real, beta::Real, gamma::Real)

    α = deg2rad(alpha)
    β = deg2rad(beta)
    γ = deg2rad(gamma)

    N = size(x,2)
    ret = Array{eltype(x[1]),2}(undef, 3, N)
    @threads for i = 1:N
        @inbounds ret[:,i] = rotate_3D_quantity(x[:,i], α, β, γ)
    end

    return ret
end

"""
    rotate_3D!(x::Array{<:Real}, alpha::Real, beta::Real, gamma::Real)

Rotates and array of 3D positions around the euler angles α, β and γ corresponding to rotations around the x, y, and z-axis respectively.
α, β and γ need to be given in degrees.
"""
function rotate_3D!(x::Array{<:Real}, alpha::Real, beta::Real, gamma::Real)

    α = deg2rad(alpha)
    β = deg2rad(beta)
    γ = deg2rad(gamma)

    @threads for i = 1:size(x,2)
        @inbounds x[:,i] = rotate_3D_quantity(x[:,i], α, β, γ)
    end

    return x
end

"""
    rotate_to_xz_plane!(x::Array{<:Real})

Rotates an array of 3D positions into the xz-plane.
"""
function rotate_to_xz_plane!(x::Array{<:Real})

    @inbounds for i = 1:size(x,2)
        pos3   = copy(x[2,i])
        x[2,i] = x[3,i]
        x[3,i] = pos3
    end
    x
end

"""
    rotate_to_xz_plane!(x::Array{<:Real})

Rotates an array of 3D positions into the xz-plane.
"""
function rotate_to_xz_plane!(x::Array{<:Real}, x_in::Array{<:Real})

    @inbounds for i = 1:size(x,2)
        x[1,i] = x_in[1,i]
        x[2,i] = x_in[3,i]
        x[3,i] = x_in[2,i]
    end
    x
end

"""
    rotate_to_yz_plane(x::Array{<:Real})

Rotates an array of 3D positions into the yz-plane.
"""
function rotate_to_yz_plane!(x::Array{<:Real})

    @inbounds for i = 1:size(x,2)
        pos3   = copy(x[1,i])
        x[1,i] = x[2,i]
        x[2,i] = x[3,i]
        x[3,i] = pos3
    end
    x
end

"""
    rotate_to_yz_plane(x::Array{<:Real}, x_in::Array{<:Real})

Rotates an array of 3D positions into the yz-plane.
"""
function rotate_to_yz_plane!(x::Array{<:Real}, x_in::Array{<:Real})

    @inbounds for i = 1:size(x,1)
        x[1,i] = x_in[2,i]
        x[2,i] = x_in[3,i]
        x[3,i] = x_in[1,i]
    end
    x
end

"""
    project_along_axis!(x::Array{<:Real}, projection_axis::Integer=3)

Projects and array of 3D along one of the principle axes.
projection_axis ∈ {1, 2, 3} => x, y, z axis.
"""
function project_along_axis!(x::Array{<:Real}, projection_axis::Integer=3)

    # rotation to xy-plane -> nothing is done
    if projection_axis == 3
        return x
    end

    # rotation to xz-plane
    if projection_axis == 2
        return rotate_to_xz_plane!(x)
    end

    # rotation to yz-plane
    if projection_axis == 1
        return rotate_to_yz_plane!(x)
    end
end

"""
    project_along_axis!(x::Array{<:Real}, projection_axis::Integer=3)

Projects and array of 3D along one of the principle axes.
projection_axis ∈ {1, 2, 3} => x, y, z axis.
"""
function project_along_axis!(x::Array{<:Real}, x_in::Array{<:Real}, projection_axis::Integer=3)

    # rotation to xy-plane -> nothing is done
    if projection_axis == 3
        return x_in
    end

    # rotation to xz-plane
    if projection_axis == 2
        return rotate_to_xz_plane!(x, x_in)
    end

    # rotation to yz-plane
    if projection_axis == 1
        return rotate_to_yz_plane!(x, x_in)
    end
end

"""
    project_along_axis!(x::Array{<:Real}, projection_axis::Integer=3)

Projects and array of 3D along one of the principle axes.
projection_axis ∈ {1, 2, 3} => x, y, z axis.
"""
function project_along_axis(x::Array{<:Real}, projection_axis::Integer=3)

    # allocate new array
    ret = Array{eltype(x[1]),2}(undef, 3, size(x,2))

    # rotation to xy-plane -> nothing is done
    if projection_axis == 3
        return x
    end

    # rotation to xz-plane
    if projection_axis == 2
        return rotate_to_xz_plane!(ret, x)
    end

    # rotation to yz-plane
    if projection_axis == 1
        return rotate_to_yz_plane!(ret, x)
    end
end


#    Problem : GROUP A
#    *********
#	 Shanno's TRIDIA quadratic tridiagonal problem
#
#    Origonal SIF Source:problem 8 in
#    Ph.L. Toint,
#    "Test problems for partially separable optimization and results
#    for the routine PSPMIN",
#    Report 83/4, Department of Mathematics, FUNDP (Namur, B), 1983.
#
#    See also Buckley#40 (p.96)
#
#    TRIDIA.SIF classification QUR2-AN-V-0
#
#	 This problem is decomposed in n linear groups, the last n-1 of which
#    are 2 x 2 and singular.
#
#    Number of variables is variable

f = (x) -> begin
	fx   = (x[1]-1)^2
	for i in firstindex(x)+1:lastindex(x)
		item    = 2x[i]-x[i-1]
    	fx     += item^2*i
	end
    return fx
end

g! = (g, x) -> begin
	g[1] = 2(x[1]-1)
	for i in firstindex(x)+1:lastindex(x)
		item    = 2x[i]-x[i-1]
        g[i]   += 4item*i
	    g[i-1] -= 2item*i
	end
    return g
end

fg! = (g, x) -> begin
	fx   = (x[1]-1)^2
	g[1] = 2(x[1]-1)
	for i in firstindex(x)+1:lastindex(x)
		item    = 2x[i]-x[i-1]
    	fx     += item^2*i
        g[i]   += 4item*i
	    g[i-1] -= 2item*i
	end
    return fx, g
end

init = (n::Int=5000) -> begin
    return n, ones(n)
end

TestSet["TRIDIA"] = UncProgram("TRIDIA",f, g!, fg!, init)


import RowEchelon

#=
Homework 5
Section 2.1
Problem 2
=#

# Initialize all Matrices
A = [2 0 -1 ; 4 -5 2]
B = [7 -5 1 ; 1 -4 -3]
C = [1 2 ;-2 1]
D = [3 5 ; -1 4]
E = [-5 ; 3]

println("A + 3B: ")
println(A+(3*B))

# After this point, products may not exist
# Use try/catch to find nonexistent products.
println("2C - 3E:")
try
	println((2*C) - (3*E))
catch
	println("2C - 3E cannot be computed.")
end

println("DB:")
try
	println(D*B)
catch
	println("DB cannot be computed.")
end

println("EC:")
try
	println(E*C)
catch
	println("EC cannot be computed.")
end


module FameSVD
using LinearAlgebra

export fsvd

function fsvd(A::Array{R, 2}) where R<:AbstractFloat
  dim = size(A)

  V = Array{R, 2}(undef, dim[2], dim[2])
  U = Array{R, 2}(undef, dim[1], dim[2])
  S = Array{R, 1}(undef, dim[2])

  V = BLAS.syrk('U', 'T', 1.0, A)
  (S, V) = LAPACK.syevr!('V', 'A', 'U', V, 0., 0., 0, 0, 0.)

  reverse!(S)
  @inbounds for i = 1:dim[2]
    @fastmath S[i] = sqrt(S[i])
  end

  V = reverse(V; dims = 2)
  U = BLAS.gemm('N', 'N', A, V)

  # TODO check if the loop order is optimal
  @inbounds for i = 1:dim[2]
    @inbounds for j = 1:dim[1]
      U[j, i] /= S[i]
    end
  end

  return LinearAlgebra.SVD(U, S, V')
end

end # module


using Flux
println("NNPDE_tests")
using DiffEqFlux
println("Starting Soon!")
using ModelingToolkit
using DiffEqBase
using Test, NeuralPDE
using GalacticOptim
using Optim
using CUDA

using Random
Random.seed!(100)

cb = function (p,l)
    println("Current loss is: $l")
    return false
end

## Example 1, 3D PDE
@parameters x y t θ
@variables u(..)
@derivatives Dxx''~x
@derivatives Dyy''~y
@derivatives Dt'~t

# 3D PDE
eq  = Dt(u(x,y,t,θ)) ~ Dxx(u(x,y,t,θ)) + Dyy(u(x,y,t,θ))
# Initial and boundary conditions
bcs = [u(x,y,0,θ) ~ exp(x+y)*cos(x+y) ,
      # u(x,y,2,θ) ~ exp(x+y)*cos(x+y+4*2) ,
      u(0,y,t,θ) ~ exp(y)*cos(y+4t),
      u(2,y,t,θ) ~ exp(2+y)*cos(2+y+4t) ,
      u(x,0,t,θ) ~ exp(x)*cos(x+4t),
      u(x,2,t,θ) ~ exp(x+2)*cos(x+2+4t)]
# Space and time domains
domains = [x ∈ IntervalDomain(0.0,2.0),
          y ∈ IntervalDomain(0.0,2.0),
          t ∈ IntervalDomain(0.0,2.0)]

# Discretization
dx = 0.25; dy= 0.25; dt = 0.25
# Neural network
chain = FastChain(FastDense(3,16,Flux.σ),FastDense(16,16,Flux.σ),FastDense(16,1)) |>gpu

discretization = NeuralPDE.PhysicsInformedNN([dx,dy,dt],
                                             chain,
                                             strategy = NeuralPDE.GridTraining())
pde_system = PDESystem(eq,bcs,domains,[x,y,t],[u])
prob = NeuralPDE.discretize(pde_system,discretization)

res = GalacticOptim.solve(prob, ADAM(0.1), progress = false; cb = cb, maxiters=1000)
phi = discretization.phi

xs,ys,ts = [domain.domain.lower:dx:domain.domain.upper for (dx,domain) in zip([dx,dy,dt],domains)]
analytic_sol_func(x,y,t) = exp(x+y)*cos(x+y+4t)
u_real = [reshape([analytic_sol_func(x,y,t) for x in xs  for y in ys], (length(xs),length(ys)))  for t in ts ]
u_predict = [reshape([first(phi([x,y,t],res.minimizer)) for x in xs  for y in ys], (length(xs),length(ys)))  for t in ts ]
@test u_predict ≈ u_real atol = 200.0

# p1 =plot(xs, ys, u_predict, st=:surface);
# p2 = plot(xs, ys, u_real, st=:surface);
# plot(p1,p2)

## Example 2, ## Fokker-Planck equation
# the example took from this article https://arxiv.org/abs/1910.10503
@parameters x θ
@variables p(..)
@derivatives Dx'~x
@derivatives Dxx''~x

#2D PDE
α = 0.3
β = 0.5
_σ = 0.5
# Discretization
dx = 0.05
# here we use normalization condition: dx*p(x,θ) ~ 1, in order to get non-zero solution.
eq  = [(α - 3*β*x^2)*p(x,θ) + (α*x - β*x^3)*Dx(p(x,θ)) ~ (_σ^2/2)*Dxx(p(x,θ)),
       dx*p(x,θ) ~ 1.]

# Initial and boundary conditions
bcs = [p(-2.2,θ) ~ 0. ,p(2.2,θ) ~ 0. , p(-2.2,θ) ~ p(2.2,θ)]

# Space and time domains
domains = [x ∈ IntervalDomain(-2.2,2.2)]

# Neural network
chain = FastChain(FastDense(1,12,Flux.σ),FastDense(12,12,Flux.σ),FastDense(12,1)) |>gpu

discretization = NeuralPDE.PhysicsInformedNN(dx,
                                             chain,
                                             strategy= NeuralPDE.GridTraining())

pde_system = PDESystem(eq,bcs,domains,[x],[p])
prob = NeuralPDE.discretize(pde_system,discretization)

res = GalacticOptim.solve(prob, BFGS(); cb = cb, maxiters=8000)
phi = discretization.phi

analytic_sol_func(x) = 28*exp((1/(2*_σ^2))*(2*α*x^2 - β*x^4))

xs = [domain.domain.lower:dx:domain.domain.upper for domain in domains][1]
u_real  = [analytic_sol_func(x) for x in xs]
u_predict  = [first(phi(x,res.minimizer)) for x in xs]

@test u_predict ≈ u_real atol = 20.0

# plot(xs ,u_real, label = "analytic")
# plot!(xs ,u_predict, label = "predict")


function L2norm(x)
    local l2::eltype(x) = 0
    for xᵢ in x
        l2 += xᵢ^2
    end
    l2
end

function l2norm(x)
    sqrt(L2norm(x))
end


#= matrixlib.jl
=#

function matrixlib(::Type{T}, name::Symbol, args...) where T
  if     name == :cauchy   return matrixlib_cauchy(T, args...)
  elseif name == :fourier  return matrixlib_fourier(T, args...)
  elseif name == :hilb     return matrixlib_hilb(T, args...)
  else                     throw(ArgumentError("name"))
  end
end
matrixlib(name::Symbol, args...) = matrixlib(Float64, name, args...)

function matrixlib_cauchy(::Type{T}, x::AbstractVector, y::AbstractVector) where T
  m = length(x)
  n = length(y)
  A = Array{T}(undef, m, n)
  @inbounds for j = 1:n
    @simd for i = 1:m
      A[i,j] = 1/(x[i] - y[j])
    end
  end
  A
end

function matrixlib_fourier(::Type{T}, x::AbstractVector, y::AbstractVector) where T
  S = eltype(complex(zero(T)))
  m = length(x)
  n = length(y)
  A = Array{S}(undef, m, n)
  @inbounds for j = 1:n, i = 1:m
    A[i,j] = exp(-2im*pi*x[i]*y[j])
  end
  A
end
matrixlib_fourier(::Type{T}, m::Integer, n::Integer) where {T} =
  matrixlib_fourier(T, 0:m-1, (0:n-1)/n)
matrixlib_fourier(::Type{T}, n::Integer) where {T} = matrixlib_fourier(T, n, n)

function matrixlib_hilb(::Type{T}, m::Integer, n::Integer) where T
  m >= 0 || throw(ArgumentError("m"))
  n >= 0 || throw(ArgumentError("n"))
  A = Array{T}(undef, m, n)
  @inbounds for j = 1:n, i = 1:m
    A[i,j] = 1/(i + j - 1)
  end
  A
end
matrixlib_hilb(::Type{T}, n::Integer) where {T} = matrixlib_hilb(T, n, n)


# 1d climate
using DrWatson
@quickactivate "NonlinearDynamicsTextbook"
include(srcdir("style.jl"))
using DynamicalSystems, PyPlot, Random

using DynamicalSystems, PyPlot, Roots

αtan(T) = 0.5 - 0.2*tanh((T-263)/4)
dTdt(T, ε = 0.65, α=αtan, s= 1.0) = s*(1 - α(T)) - 1.6e-10 * ε * T^4
dTdt(T; ε = 0.65, α=αtan, s = 1.0) = dTdt(T, ε, α, s)

# Ts = 200:400.0
# plot(Ts, dTdt.(Ts))
# plot(Ts, dTdt.(Ts, 0.2))
# plot(Ts, dTdt.(Ts, 0.9))
# axhline(0)

fig = figure(figsize = (figx/2, figy))
Ts = 200:0.5:320.0
arrows = 210:10:300 |> collect
deleteat!(arrows, findfirst(isequal(260), arrows))
roots = Roots.find_zeros(dTdt, Ts[1], Ts[end])
plot(Ts, dTdt.(Ts), color = "C0", label = "\$dT/dt\$")
axhline(0; lw = 2.0, zorder = 1, color = "C2", ls = "--")
xlim(Ts[1], Ts[end])
ylim(-0.2, 0.2)
yticks([-0.1, 0, 0.1])
tight_layout()
for (i, r) in enumerate(roots)
    plot(r, 0, marker = "o", markeredgecolor = "k", markerfacecolor = iseven(i) ? "w" : "k",
    markersize = 15, mew = 2)
end
for r in arrows
    f = dTdt(r)
    x, dx = f > 0 ? (r - 5, 5) : (r+5, -5)
    ff = abs(1.2f)^2 + 0.01
    arrow(x, 0, dx, 0; color = "C1", width = ff, length_includes_head = false,
    head_width = 1.5ff, head_length = 100ff, zorder = 99
    )
end
legend()
xticks(200:40:320)
xlabel("\$T\$"; labelpad = -15)
tight_layout(pad=0.3)
wsave(plotsdir("2", "1dstatespace"), fig)


# ---
# layout: post
# title: "π day"
# date: 2019-03-13 00:00:00 +0000
# categories: blog
# mathjax: true
# ---

# >In the UK we have started to celebrate π day (the 3rd month's 14th day) every year, even though we don't use the USA's date formatting convention of `monthnumber` followed by `daynumber`. But we can't really celebrate the 31st of April (31/4) or the 3rd of Quatember (?) (3/14), so we'll happily celebrate π day on 14/3 along with everyone else!

# >I set myself a challenge at the beginning of March: make a π-related image using Julia and the Luxor.jl package every day until π day. Some days it worked out well, others didn't, but I've gathered them all here anyway. This post has a fair few images, but not very much code or mathematical content.

# The images here are in low-resolution: they should be available on my [Flickr page](https://www.flickr.com/photos/153311384@N03/) at their full resolution if you want to download or re-use them.

# ### Day 1: Circle packing

# Circle packing may be a well-trodden path, but it always looks neat, and it's a nice easy start. You maintain a list of circles (center point and radius). Then you create a random circle, check it against all the other ones, draw it if it doesn't overlap, or reduce the radius and try again. It's not very efficient but you can set it going and go and make some coffee.

# To make the π shape appear, the code creates a path:

#md fontsize(480)
#md textoutlines("π", O, :path, halign=:center, valign=:middle)
#md πoutline = first(pathtopoly())

# then checks whether each circle's centerpoint is inside or outside the outline of the π shape:

#md isinside(pt, πoutline)

# and colors it accordingly.

# ![image label](IMAGEFOLDER/t-800.png)

# ### Day 2: Dry and wet

# I repeated myself today, thinking I could develop the circles a bit more, and ended up with this glossier wet-look version. The apparently random-looking shapes in the background are Bézier splodges that are supposed to be splashes...

# ![image label](IMAGEFOLDER/pi-reds-balls-wet-800.png)

# ### Day 3: π packing

# This is π packing rather than circle packing, although the code is again quite similar in outline: choose a point at random, find the largest font size at which the π character fits without overlapping others in the list, and then place it and add it to the list. The colors are a bit murky though.

# ![image label](IMAGEFOLDER/pi-swarm-3-800.png)

# ### Day 4: Rainbow

# Combining concentric circles and rainbow colors, this image shows about 350 digits of π.

# ![image label](IMAGEFOLDER/digits-of-pi-avenir-800.png)

# To generate the digits of π, I use this function:

function pidigits(n)
    result = BigInt[]
    k, a, b, a1, b1 = big.([2, 4, 1, 12, 4])
    while n > 0
        p, q, k = k^2, 2k + 1, k + 1
        a, b, a1, b1 = a1, b1, p * a  +  q * a1, p * b  +  q * b1
        d, d1 = a ÷ b, a1 ÷ b1
        while d == d1
            push!(result, d)
            n -= 1
            a, a1 = 10(a % b), 10(a1 % b1)
            d, d1 = a ÷ b, a1 ÷ b1
        end
    end
    return result
end

# It looks like witchcraft to me, but I understand that it's a "spigot" algorithm. I was hoping for a while that it was named after a Professor Spigot, but in fact it's describing the way the digits trickle out one by one like drops of water. It's quick enough for a thousand digits or so, but slows down a lot when you ask for 100_000 or more, probably due to the hard work that the big integer library has to do: even when you're just calculating the first 15 digits of π, the values of `a1` and `b1` are way over the limits of Int64s.

#md julia-1.1> @time pidigits(1000);
#md  0.014522 seconds (44.90 k allocations: 9.425 MiB, 28.97% gc time)

# The image might work better on white:

# ![image label](IMAGEFOLDER/digits-of-pi-avenir-on-white-800.png)

# Sometimes I wanted to check where certain sequences of digits appeared. I couldn't find a built-in function that looked for a sequence of digits in an array, but this worked well enough for my purposes:

function findsubsequence(needle, haystack)
    result = Int64[]
    for k in 1:length(haystack) - length(needle)
        if needle == view(haystack, k:k + length(needle) - 1)
            push!(result, k)
        end
    end
    return result
end

findsubsequence(str::String, digits) =
    findsubsequence(map(x -> parse(Int, x), split(str, "")), digits)

findsubsequence("999999", pidigits(2000)) # => [763]

# ### Day 5: Low-fat

# A chunky typeface like the Avenir Heavy I used yesterday is good for masking and clipping. But I wondered how the narrowest typeface would look. I found Briem Akademi, designed by Gunnlaugur Briem at the Royal Academy of Fine Arts in Copenhagen. Adobe's description says:

# >The most compressed version works best where legibility is less important than dramatic visual effect.

# and I like the abstract look even though it's almost illegible... Would this make nice bathroom tiles?

# ![image label](IMAGEFOLDER/many-digits-of-pi-briem-800.png)

# ### Day 6 Breakfast and Tiffany

# I'm still thinking about using typefaces. I'm a fan of Ed Benguiat's ITC Tiffany font, his nostalgic look back from the 1970s to the age of Edwardian elegance.

# ![image label](IMAGEFOLDER/pi-digits-appearing-800.png)

# It's easy to do this with tables. Like the circle packing, the code checks whether the coordinates of each table cell fall within a given perimeter, and changes the font accordingly.

# ### Day 7 Distinguished

# The excellent Colors.jl package has a function called `distinguishable_colors()` (which fortunately tab-completes). The help text says:

# > This uses a greedy brute-force approach to choose `n` colors that are maximally distinguishable. Given `seed` color(s), and a set of possible hue, chroma, and lightness values (in LCHab space), it repeatedly chooses the next color as the one that maximizes the minimum pairwise distance to any of the colors already in the palette.

# Much to do with color depends on the viewer's perception, but I think it works well here. It starts at the top left, and works from left to right. (That pesky decimal point defaults to using the previous color...) You can spot the Feynman point (`999999`) halfway down on the left (look for the six consecutive sandy brown squares), or the four purple sevens on the bottom row.

# ![image label](IMAGEFOLDER/pi-distinguishable_colors-800.png)

# I remembered to try to choose the color for the small labels (probably unreadable in the low-resolution PNG you see here) so that they're either light on dark, or dark on light.

#md ... r, g, b = color of square
#md gamma = 2.2
#md luminance = 0.2126 * r^gamma + 0.7152 * g^gamma + 0.0722 * b^gamma
#md (luminance > 0.5^gamma) ? sethue("black") : sethue("white")

# ### Day 8 Candy crush edition

# ![image label](IMAGEFOLDER/candy-crush.png)

# I must have seen an advert for CandyCrush yesterday, or perhaps all that talk of gamma and LCHab spaces caused a reaction, but this sugar rush of an image was the result. The SVG version looks tasty but is too big for this web page.

# ### Day 9 Like a circle in a spiral, a wheel within a wheel

# Arranging the sweets in a spiral looks tidy.

# ![image label](IMAGEFOLDER/pi-digits-in-spiral-balls-800.png)

# ### Day 10 π into circumference

# Luxor's `polysample()` function takes a polygon and samples it at regular intervals. This allows the following idea, where each point on a shape (here, the outline of the π character) is slowly moved to a matching location on the circular shape around the outside.

# ![image label](IMAGEFOLDER/pi-to-circle-800.png)

# For a point on the π border `p1`, and a matching point on the circumference polygon `p2`, the intermediate point is given by `between(p1, p2, n)`, where `n` is between 0 and 1.

# I like the almost 3D effect you get from this.

# ### Day 11 Charcoal

# Time for a charcoal sketch:

# ![image label](IMAGEFOLDER/pi-charcoal-1-800.png)

# The crinkly edges of the paper are made by the `polysample()` function on a rectangle then applying simplex-`noise()`-y nudges to the vertices. The paper is textured with `rule()`d lines, and there's some very low values for `setopacity()` smudges. Shifting the Bézier curve handles slightly for each iteration gives a brushy/sketchy feel. (It's fortunate I can copy and paste some of this code from drawings I've made before: I've learnt the hard way that it's better keep things than throw them away...)

# ### Day 12

# I ran out of time on this one, and there are still some problems with the text spacing. The idea is to have the infinite digits of π spiral into some fiery star with some space-y stuff. Probably not the sort of image I should be attempting at all with simple vector-based 2D graphics tools, but it feels like a challenge. Those wispy trails are the same as yesterday's brush strokes, but using custom `setdash()` dashing patterns.

# ![image label](IMAGEFOLDER/pi cosmic spiral-800.png)

# ### Day 13

# The idea here is to show which digit of π is the current leader, in terms of how many times that digit has appeared already. (Yes, a stupid idea, I know!) Then I couldn't decide on how many digits to show, so it's going to be an animated GIF showing the first 1000 digits. At the 200 digit mark poor old "7" is struggling at the back of the field, but the glory days are ahead - after 1000 digits, it's overtaken 0, 4, and 6.

# ![image label](IMAGEFOLDER/200-digits-of-pi-800.png)

# The animation turned into a video rather than a GIF, because I don't like the low resolution of GIFs today.

# And now of course I have to add a suitable audio soundtrack. Luckily I've recently been playing with George Datseris' [MIDI interface for Julia](https://github.com/JuliaMusic), so it was easy enough to make a musical version of the first 1000 digits of π, where the digits from 0 to 9 choose the appropriate note from a reasonably harmonious scale.

using MIDI

function savetrack(track, notes)
    file = MIDIFile()
    addnotes!(track, notes)
    addtrackname!(track, "a track")
    push!(file.tracks, track)
    writeMIDIFile("/tmp/sound-of-pi.mid", file)
end

scales = [46, 48, 51, 53, 55, 57, 58, 60, 62, 65, 67]

function generatetune!(notes)
    pos = 1
    dur = 80
    k = 1
    manypidigits = pidigits(1000)
    for i in manypidigits
        dur = k * 960
        pos += k * 960
        n = scales[i + 1]
        note = Note(n, 76, pos, dur)
        push!(notes, note)
    end
end

notes = Notes()
track = MIDITrack()
generatetune!(notes)
savetrack(track, notes)

# ![image label](IMAGEFOLDER/music-credits.png)

# This "sonification" (or "audification") is just for fun. For a more convincing critique of these sonifications than I can provide, watch the always entertaining [Tantacrul](https://www.youtube.com/watch?v=Ocq3NeudsVk)'s presentation on YouTube.

# And while you're on YouTube, the π video is on [my YouTube channel](https://www.youtube.com/channel/UCfd52kTA5JpzOEItSqXLQxg), and it's my entry for YouTube's Most Boring Video of 2019 competition, but I suspect it won't do very well—competition in this category is fierce, even if sometimes the contestants are unwilling participants.

# Happy π day!

# [2019-03-13]

# ![cormullion signing off](http://steampiano.net/cormullionknot.gif?piday){: .center-image}

using Literate                                                                 #src
# preprocess for notebooks                                                     #src
function setimagefolder(content)                                               #src
    content = replace(content, "IMAGEFOLDER" => "$IMAGEFOLDER")                #src
    return content                                                             #src
end                                                                            #src

# for Jupyter notebook, put images in subfolder                                #src
#IMAGEFOLDER = "images/piday"                                                   #src

#Literate.notebook("source/piday.jl", "notebooks", preprocess = setimagefolder) #src

# for Markdown/Jekyll notebook, put images in "/images"                        #src

IMAGEFOLDER = "/images/piday"                                                  #src

Literate.markdown("source/piday.jl", ".", name="_posts/2019-03-13-piday",      #src
 preprocess = setimagefolder,                                                  #src
 codefence = "{% highlight julia %}" => "{% endhighlight julia %}",            #src
 documenter=false)                                                             #src

#src


### A Pluto.jl notebook ###
# v0.17.4

using Markdown
using InteractiveUtils

# ╔═╡ 57ed2d88-970d-4b8d-9f36-7115f50622de
begin
	using LinearAlgebra # for the norm and dot function
	using ForwardDiff # for automatic differentiation
end

# ╔═╡ 3b36fb00-833f-11ec-0d4d-451fa6f0aaee
md"""
# Programming tapas (I)

These are a few, hopefully small, tasks to practice Julia.

- **Gradient decent**
- Array interface
"""

# ╔═╡ 0efbf532-66cb-41fd-98a5-6df89e364b84
md"""## Gradient decent with backtracking line search

Write a function `gradient_decent(f, x0, α, β)` which implements the gradient decent with backtracking line search.

You can follow the notation form this slides (or your own).

The pseudo algorithms are:
"""

# ╔═╡ 67ab9ac3-93e0-4d9a-84aa-131aaf543198
md"""
```julia
Gradient decent:
given a starting point x_0

x = x_0
repeat until stopping criterium
	Compute decent direction Δx = ∇f(x).
 	Line search: Choose a step size t > 0.
    Update: x = x + t * Δx

 	Stopping criterium: ‖ ∇f(x) ‖² < ε
end
```
"""

# ╔═╡ a5e2ee29-8de1-41be-8b05-8427b1a65bce
md"""
```julia
Backtracking line search:
given x, f, Δx, α, β

t = 1
repeat until f(x + t Δx) < f(x) + α t dot( ∇f(x), Δx )
 	Udate: t = β t
end

```

Source: [https://web.stanford.edu/class/ee364a/lectures/unconstrained.pdf](https://web.stanford.edu/class/ee364a/lectures/unconstrained.pdf)
"""

# ╔═╡ 304398ad-f680-4f8c-8a76-5768c0fbfa5e

# ╔═╡ 2c48c94e-7d31-450c-b3c7-0194894c38b7
function linesearch(f, Δx, x, α, β; t_min = 1e-5)
	t = 1
	fx = f(x)
	m = norm(Δx)
	while t > t_min
		if f(x .+ t*Δx) <= fx + α * t * m^2
			return t
		end
		t = t*β
		cond || return t
	end

	return NaN
end

# ╔═╡ ae9e20b2-4f88-4c28-8856-ce61368445a3
function gradientdecent(f, x0, α, β, ε; max_steps = 1e4)
	x = copy(x0)
	i = 0
	∇f = similar(x0)

	@assert β < 1
	@assert α < 0.5

	while i < max_steps
		ForwardDiff.gradient!(∇f, f, x)
		Δx = -∇f
		if norm(∇f)^2 < ε
			return x
		end

		t = linesearch(f, Δx, x, α, β)
		x .+= t * Δx

		i += 1
	end

	return NaN
end

# ╔═╡ e331926f-6e11-434c-a7d3-02d0e9f0f269
begin
	f(x) = exp(x[1]+3x[2]-0.1) + exp(x[1]-3x[2]-0.1) + exp(-x[1]-0.1)
	α = 0.25
	β = 0.5
	ε = 1e-8
	x0 = [0.0, 0.0]

	gradientdecent(f, x0, α, β, ε)
end

# ╔═╡ 7a5cfa1b-9b47-4741-8656-210a3515b6f3

# ╔═╡ 81361eb6-092c-4cd1-96a0-eb9e167dfd3f

# ╔═╡ b04a090b-e9d7-4032-b956-1238fac26f94
md"## Array interpolation"

# ╔═╡ 3413f532-50ec-47f5-9547-4e9474be174e
md"""
We want to take regular vectors, but have an automated way to compute in between values with the syntax
```julia
x = [5, 7, 5]
x[1.5] == 6.0  # linear interpolation between 5 and 7
```

Implement
`Base.getindex(x::Vector{Float64}, p::Float64) = `
accordingly.
"""

# ╔═╡ 641f85f8-368a-45eb-9912-284eca29d83b
x = [5.0, 7.0, 5.0]

# ╔═╡ 9f62d306-25f2-4b8f-a691-7a0fa3f4aa03
# x[1.5]

# ╔═╡ 61eaf1c1-f033-4e36-8171-19ff6621cc82

# ╔═╡ 84c50e3b-b836-477f-81bb-121a2e5d055f
md"
## Merge sort

Implement (any variant) of merge sort.

You can for example try this strategy
```
MergeSort(arr[], l,  r)
If r > l
     1. Find the middle point to divide the array into two halves:
             middle m = l+ (r-l)/2
     2. Call mergeSort for first half:
             Call mergeSort(arr, l, m)
     3. Call mergeSort for second half:
             Call mergeSort(arr, m+1, r)
     4. Merge the two halves sorted in step 2 and 3:
             Call merge(arr, l, m, r)
```
Source: https://www.geeksforgeeks.org/merge-sort/
"

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[compat]
ForwardDiff = "~0.10.25"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.1"
manifest_format = "2.0"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "f9982ef575e19b0e5c7a98c6e75ee496c0f73a93"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.12.0"

[[deps.ChangesOfVariables]]
deps = ["ChainRulesCore", "LinearAlgebra", "Test"]
git-tree-sha1 = "bf98fa45a0a4cee295de98d4c1462be26345b9a1"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.2"

[[deps.CommonSubexpressions]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
version = "0.3.0"

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "44c37b4636bc54afac5c574d2d02b625349d6582"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.41.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[deps.DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.3"

[[deps.DiffRules]]
deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "84083a5136b6abf426174a58325ffd159dd6d94f"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.9.1"

[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"

[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[deps.ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "1bd6fc0c344fc0cbee1f42f8d2e7ec8253dda2d2"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "0.10.25"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.2"

[[deps.IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "abc9885a7ca2052a736a600f7fa66209f96506e1"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.4.1"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.LogExpFunctions]]
deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "e5718a00af0ab9756305a0392832c8952c7426c1"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.6"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.9"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[deps.NaNMath]]
git-tree-sha1 = "b086b7ea07f8e38cf122f5016af580881ac914fe"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.7"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"

[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "2cf929d64681236a2e074ffafb8d568733d2e6af"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.3"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "e6bf188613555c78062842777b116905a9f9dd49"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "2.1.0"

[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "2884859916598f974858ff01df7dfc6c708dd895"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.3.3"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─3b36fb00-833f-11ec-0d4d-451fa6f0aaee
# ╟─0efbf532-66cb-41fd-98a5-6df89e364b84
# ╟─67ab9ac3-93e0-4d9a-84aa-131aaf543198
# ╟─a5e2ee29-8de1-41be-8b05-8427b1a65bce
# ╟─304398ad-f680-4f8c-8a76-5768c0fbfa5e
# ╠═57ed2d88-970d-4b8d-9f36-7115f50622de
# ╠═2c48c94e-7d31-450c-b3c7-0194894c38b7
# ╠═ae9e20b2-4f88-4c28-8856-ce61368445a3
# ╠═e331926f-6e11-434c-a7d3-02d0e9f0f269
# ╟─7a5cfa1b-9b47-4741-8656-210a3515b6f3
# ╟─81361eb6-092c-4cd1-96a0-eb9e167dfd3f
# ╟─b04a090b-e9d7-4032-b956-1238fac26f94
# ╟─3413f532-50ec-47f5-9547-4e9474be174e
# ╠═641f85f8-368a-45eb-9912-284eca29d83b
# ╠═9f62d306-25f2-4b8f-a691-7a0fa3f4aa03
# ╟─61eaf1c1-f033-4e36-8171-19ff6621cc82
# ╟─84c50e3b-b836-477f-81bb-121a2e5d055f
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# f(x) = x^2 - 2x + 2

using LaTeXStrings, Measures
gr()

xf(x) = x^2 - 2x + 2

Plots.plot(xf,
    -3, 3,
    marker=:circle,
    c=:darkorange3,
    linewidth= 2,
    xlabel=L"n",
    ylabel=L"F_n",
    annotation=(2, 10, L"F_n = x^2 - 2x + 2"))


function GenEuropeanBinomialCall(K, T, S, sig, r, N)

        dt = T/N ## Time to Maturity/ Number of Steps
        nu = r - 0.5*sig^2
        dxu = sqrt(sig^2*dt + (nu*dt)^2)
        dxd = -dxu
        pu = 1/2 + 1/2*(nu*dt/dxu)
        pd = 1 - pu

        disc = exp(-r*dt)

        St = zeros(Float64, (N+1, N+1))

        # Initialising asset prices at maturity

        St[1 , N+1] = S / exp(N * dxd)
        for j in 2:(N+1)
                St[j, N+1] = St[j-1, N+1] / exp(dxu - dxd)
        end

        # create Array for options value tree

        C = zeros(Float64, (N+1, N+1))#linspace(1, N+1, N+1)

        # Initialise Option Values at maturity

        for j in 1:(N+1)
                C[j, N+1] = max(0.0, St[j, N+1] - K)
        end

        # Stepping back through the tree

        for i = linrange(N, 1, N)
                for j in 1:i
                      C[j, i] = disc * (pu * C[j, i+1] + pd * C[j+1, i+1])
                      #St[j,i] = St[j,i+1] / d
                      #P[j, i] = max(P[j, i], K - St[j,i])
                end
        end

C
end


# ==============================================================================
# Xavier Gandibleux - November 2021
#   Implemented in Julia 1.6

# ==============================================================================
# Parser of bi-01BKP instances generated by O. Perederieieva and X. Gandibleux
#   All coefficients P, W, and  ω are integer, all variables are binary

# ==============================================================================
# Datastructure of a multi-objective multi-dimensionnal KP with 0/1 variables
struct _MOMKP
    P  :: Matrix{Int} # profit of items for the objectives, k=1..p, j=1..n
    W  :: Matrix{Int} # weight of items for the constraints, i=1..m, j=1..n
    ω  :: Vector{Int} # capacity of knapsacks, i=1..m
end

# ==============================================================================
function readInstanceMOMKPformatPG(verbose::Bool, fname::String)

    f=open(fname)

    n = parse(Int, readline(f) )
    m = 2  # 2 constraints
    p = 2  # 2 objectives

    momkp = _MOMKP(zeros(Int,p,n),zeros(Int,m,n),zeros(Int,m))
    !verbose ? nothing : println("Instance of bi-01BKP : ",n," variables")

    momkp.ω[1] = parse(Int, readline(f) )
    momkp.ω[2] = parse(Int, readline(f) )
    !verbose ? nothing : println("ω[1]=",momkp.ω[1])
    !verbose ? nothing : println("ω[2]=",momkp.ω[2])

    # Extract the data of the MKP from the following lines ---------------------
    for j = 1:n
        momkp.P[1,j],
        momkp.P[2,j],
        momkp.W[1,j],
        momkp.W[2,j] = parse.(Int, split(readline(f)) )

        !verbose ? nothing :
          println("j=",j, "  |  ",
                  "p[1,",j,"]=",momkp.P[1,j], "   ",
                  "p[2,",j,"]=",momkp.P[2,j], "  |  ",
                  "w[1,",j,"]=",momkp.W[1,j], "   ",
                  "w[2,",j,"]=",momkp.W[2,j]
                 )
    end
    close(f)

    return momkp
end

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

# Example on how to use it :
# fname="set1/ZL28.DAT"; verbose = true
# instancePG = readInstanceMOMKPformatPG(verbose,fname)


#=

  Assignment 1:
  https://edu.swi-prolog.org/mod/assign/view.php?id=242
  """
  http://cplint.eu/p/urns_and_balls.swinb

  Urns and balls

  Suppose you have two urns: urn1 contains 40 blue balls and 20 red balls and urn2 contains 25
  blue balls and 30 red balls.

  You throw an unbiased coin and, if it turns out head, you draw a ball from the first urn,
  it it turns out tails you draw a ball from the second urn.

  Write a program modeling this process and a query for answering the question
  "What is the probability of drawing a blue ball?"

  Write the program and the query in the cells below. Then save the notebook and submit the url.

  """
  The exact answer of drawing a blue call is
     0.5*40/60 + 0.5*25/55 = 0.56060606060606060606

  Summary Statistics
  parameters      mean       std   naive_se      mcse          ess      rhat   ess_per_sec
      Symbol   Float64   Float64    Float64   Float64      Float64   Float64       Float64

        coin    1.5019    0.5000     0.0025    0.0025   40549.0446    1.0000    28840.0032
        draw    1.4389    0.4963     0.0025    0.0023   40570.3731    1.0000    28855.1729

  Distributions of variable draw
  blue       =>   22442  (0.561050)
  red        =>   17558  (0.438950)

  Distributions of variable coin
  head       =>   20078  (0.501950)
  tail       =>   19922  (0.498050)

  Cf ~/cplint/course_urns_and_balls.pl
     ~/blog/urns_and_balls.blog
     ~/psi/urns_and_balls.blog
     ~/webppl/urns_and_balls.wppl

=#

using Turing, StatsPlots, DataFrames
using ReverseDiff, Zygote, Tracker
# Turing.setadbackend(:reversediff)
# Turing.setadbackend(:zygote)
# Turing.setadbackend(:tracker)
include("jl_utils.jl")

@model function urns_and_balls()
    tail = 1
    head = 2
    coin ~ Categorical(simplex([0.5,0.5]))

    blue = 1
    red = 2
    draw ~ coin == head ? Categorical(simplex([40,20])) : Categorical(simplex([25,30]))
end

model = urns_and_balls()

num_chns = 4
# chns = sample(model, Prior(), MCMCThreads(), 1000, num_chns)

# chns = sample(model, MH(), 40_000)
# chns = sample(model, MH(), MCMCThreads(), 40_000, num_chns)

# chns = sample(model, PG(20), MCMCThreads(), 10_000, num_chns)
# chns = sample(model, PG(20), 40_000)
# chns = sample(model, IS(), MCMCThreads(), 10_000, num_chns)
chns = sample(model, IS(), 40_000)

display(chns)

show_var_dist_pct(chns,:draw,["blue","red"])
show_var_dist_pct(chns,:coin,["tail","head"])


function compute(n::Int)::Int
    for c ∈ n ÷ 3 + 1:n ÷ 2
        s = c * c - n * n + 2n * c
        if s > 0
            t = trunc(Int, √s)
            if t * t == s
                b = (n - c + t) / 2
                a = n - c - b
                return a * b * c
            end
        end
    end
end


@doc raw"""
    GibbsKernel(; lengthscale)

Gibbs Kernel with lengthscale function `lengthscale`.

The Gibbs kernel is a non-stationary generalisation of the squared exponential
kernel. The lengthscale parameter ``l`` becomes a function of
position ``l(x)``.

# Definition

For inputs ``x, x'``, the Gibbs kernel with lengthscale function ``l(\cdot)``
is defined as
```math
k(x, x'; l) = \sqrt{\left(\frac{2 l(x) l(x')}{l(x)^2 + l(x')^2}\right)}
\quad \exp{\left(-\frac{(x - x')^2}{l(x)^2 + l(x')^2}\right)}.
```

For a constant function ``l \equiv c``, one recovers the [`SqExponentialKernel`](@ref)
with lengthscale `c`.

# References

Mark N. Gibbs. "Bayesian Gaussian Processes for Regression and Classication." PhD thesis, 1997

Christopher J. Paciorek and Mark J. Schervish. "Nonstationary Covariance Functions
for Gaussian Process Regression". NeurIPS, 2003

Sami Remes, Markus Heinonen, Samuel Kaski. "Non-Stationary Spectral Kernels". arXiV:1705.08736, 2017

Sami Remes, Markus Heinonen, Samuel Kaski. "Neural Non-Stationary Spectral Kernel". arXiv:1811.10978, 2018
"""
struct GibbsKernel{T} <: Kernel
    lengthscale::T
end

GibbsKernel(; lengthscale) = GibbsKernel(lengthscale)

function (k::GibbsKernel)(x, y)
    lengthscale = k.lengthscale
    lx = lengthscale(x)
    ly = lengthscale(y)
    l = invsqrt2 * hypot(lx, ly)
    kernel = (sqrt(lx * ly) / l) * with_lengthscale(SqExponentialKernel(), l)
    return kernel(x, y)
end


export FiniteDiff

#TODO add boundary conditions

"""
`FiniteDiff([domainType=Float64::Type,] dim_in::Tuple, direction = 1)`

`FiniteDiff(x::AbstractArray, direction = 1)`

Creates a `LinearOperator` which, when multiplied with an array `x::AbstractArray{N}`, returns the discretized gradient over the specified `direction` obtained using forward finite differences.

```julia
julia> FiniteDiff(Float64,(3,))
δx  ℝ^3 -> ℝ^2

julia> FiniteDiff((3,4),2)
δy  ℝ^(3, 4) -> ℝ^(3, 3)

julia> all(FiniteDiff(ones(2,2,2,3),1)*ones(2,2,2,3) .== 0)
true

```

"""
struct FiniteDiff{T,N,D,C <: CartesianIndices{N}} <: LinearOperator
	dim_in::NTuple{N,Int}
    idx::C
	function FiniteDiff{T,N,D}(dim_in) where {T,N,D}
		D > N && error("direction is bigger the number of dimension $N")
        idx = CartesianIndices(([i == D ? (2:d) : (1:d) for (i,d) in enumerate(dim_in)]...,))
        new{T,N,D,typeof(idx)}(dim_in,idx)
	end
end

# Constructors
#default constructor
FiniteDiff(domainType::Type, dim_in::NTuple{N,Int}, dir::Int64 = 1) where {N} =
FiniteDiff{domainType,N,dir}(dim_in)

FiniteDiff(dim_in::NTuple{N,Int}, dir::Int64 = 1) where {N} =
FiniteDiff(Float64, dim_in, dir)

FiniteDiff(x::AbstractArray{T,N}, dir::Int64 = 1) where {T,N}  = FiniteDiff(eltype(x), size(x), dir)

# Mappings

@generated function mul!(y::AbstractArray{T,N},
                         L::FiniteDiff{T,N,D},
                         b::AbstractArray{T,N}) where {T,N,D}
	z = zeros(Int,N)
	z[D] = 1
	idx = CartesianIndex(z...)
	ex = quote
        for I in L.idx
			y[I-$idx] = b[I]-b[I-$idx]
		end
		return y
	end
end

@generated function mul!(y::AbstractArray{T,N},
                         L::AdjointOperator{FiniteDiff{T,N,D,C}},
                         b::AbstractArray{T,N}) where {T,N,D,C}
	z = zeros(Int,N)
	z[D] = 1
	idx = CartesianIndex(z...)
	ex = quote
		for I in CartesianIndices(size(y))
			y[I] =
			I[$D] == 1 ? -b[I]  :
			I[$D] == size(y,$D) ?   b[I-$idx]  : -b[I]+b[I-$idx]
		end
		return y
	end
end

# Properties

domainType(L::FiniteDiff{T, N}) where {T, N} = T
codomainType(L::FiniteDiff{T, N}) where {T, N} = T

function size(L::FiniteDiff{T,N,D}) where {T,N,D}
	dim_out = [L.dim_in...]
	dim_out[D] = dim_out[D]-1
	return ((dim_out...,), L.dim_in)
end

fun_name(L::FiniteDiff{T,N,1}) where  {T,N} = "δx"
fun_name(L::FiniteDiff{T,N,2}) where  {T,N} = "δy"
fun_name(L::FiniteDiff{T,N,3}) where  {T,N} = "δz"
fun_name(L::FiniteDiff{T,N,D}) where {T,N,D}  = "δx$D"

is_full_row_rank(L::FiniteDiff) = true


using JuAFEM
using Tensors

# Stiffness using normal matrices
function ke_element_mat!(Ke, X::Vector{Vec{dim, T}}, fe_values::CellScalarValues{dim}, Ee, B, DB, BDB) where {T, dim}
    n_basefuncs = getnbasefunctions(fe_values)
    @assert length(X) == n_basefuncs

    reinit!(fe_values, X)
    for q_point in 1:getnquadpoints(fe_values)
        for i in 1:n_basefuncs
            dNdx = shape_gradient(fe_values, q_point, i)[1]
            dNdy = shape_gradient(fe_values, q_point, i)[2]
            dNdz = shape_gradient(fe_values, q_point, i)[3]

            B[1, i * 3-2] = dNdx
            B[2, i * 3-1] = dNdy
            B[3, i * 3-0] = dNdz
            B[4, 3 * i-1] = dNdz
            B[4, 3 * i-0] = dNdy
            B[5, 3 * i-2] = dNdz
            B[5, 3 * i-0] = dNdx
            B[6, 3 * i-2] = dNdy
            B[6, 3 * i-1] = dNdx
        end

        A_mul_B!(DB, Ee, B)
        At_mul_B!(BDB, B, DB)
        scale!(BDB, getdetJdV(fe_values, q_point))
        for p in 1:size(Ke,1)
            for q in 1:size(Ke,2)
                Ke[p, q] += BDB[p, q]
            end
        end
    end

    return Ke
end;

# Stiffness using scalar values
function ke_element!(Ke, X::Vector{Vec{dim, T}}, fe_values::CellScalarValues{dim}, C) where {T,dim}
    n_basefuncs = getnbasefunctions(fe_values)
    @assert length(X) == n_basefuncs
    reinit!(fe_values, X)
    @inbounds for q_point in 1:getnquadpoints(fe_values)
        for a in 1:n_basefuncs
            for b in 1:n_basefuncs
                ∇ϕa = shape_gradient(fe_values, q_point, a)
                ∇ϕb = shape_gradient(fe_values, q_point, b)
                Ke_e = dotdot(∇ϕa, C, ∇ϕb) * getdetJdV(fe_values, q_point)
                for d1 in 1:dim, d2 in 1:dim
                    Ke[dim*(a-1) + d1, dim*(b-1) + d2] += Ke_e[d1,d2]
                end
            end
        end
    end
    return Ke
end;

# Stiffness using vector values
function ke_element2!(Ke, X::Vector{Vec{dim, T}}, fe_values::CellVectorValues{dim}, C) where {T,dim}
    n_basefuncs = getnbasefunctions(fe_values)
    @assert length(X) * dim == n_basefuncs
    reinit!(fe_values, X)
    ɛ = [zero(SymmetricTensor{2, dim, T}) for i in 1:n_basefuncs]
    @inbounds for q_point in 1:getnquadpoints(fe_values)
        for i in 1:n_basefuncs
            ɛ[i] = symmetric(shape_gradient(fe_values, q_point, i))
        end
        dΩ = getdetJdV(fe_values, q_point)
        for i in 1:n_basefuncs
            ɛC = ɛ[i] ⊡ C
            for j in 1:n_basefuncs
                Ke[i, j] += (ɛC ⊡ ɛ[j]) * dΩ
            end
        end
    end
    return Ke
end;

E = 200e9
ν = 0.3
λ = E*ν / ((1 + ν) * (1 - 2ν))
μ = E / (2(1 + ν))
δ(i,j) = i == j ? 1.0 : 0.0
g(i,j,k,l) = λ*δ(i,j)*δ(k,l) + μ*(δ(i,k)*δ(j,l) + δ(i,l)*δ(j,k))

C = SymmetricTensor{4, 3}(g)

M = λ/ν * (1 - ν)

Cmat = [ M      λ      λ    0.0    0.0   0.0;
         λ      M      λ    0.0    0.0   0.0;
         λ      λ      M    0.0    0.0   0.0;
        0.0    0.0    0.0    μ     0.0   0.0;
        0.0    0.0    0.0   0.0     μ    0.0;
        0.0    0.0    0.0   0.0    0.0    μ]

interpolation = Lagrange{3, RefCube, 1}()
quad_rule = QuadratureRule{3, RefCube}(1)
values = CellScalarValues(quad_rule, interpolation);
vector_values = CellVectorValues(quad_rule, interpolation);

# Generate some coordinates
x = [-1.0 -1.0 -1.0;
      1.0 -1.0 -1.0;
      1.0  1.0 -1.0;
     -1.0  1.0 -1.0;
     -1.0 -1.0  1.0;
      1.0 -1.0  1.0;
      1.0  1.0  1.0;
     -1.0  1.0  1.0;]
x = x .+ 0.05 * rand()
x_vec = reinterpret(Vec{3, Float64}, x, (8,));

n_basefunctions = getnbasefunctions(vector_values)
Ke = zeros(n_basefunctions, n_basefunctions)
Ke2 = copy(Ke)
Ke3 = copy(Ke)

B   =  zeros(6, n_basefunctions)
DB  =  zeros(6, n_basefunctions)
BDB =  zeros(n_basefunctions, n_basefunctions);

fill!(Ke, 0)
fill!(Ke2, 0)
fill!(Ke3, 0)
ke_element!(Ke2, x_vec, values, C)
ke_element2!(Ke3, x_vec, vector_values, C);
ke_element_mat!(Ke, x_vec, values, Cmat, B, DB, BDB);

using Test
@test norm(Ke - Ke2) / norm(Ke) < 1e-14
@test norm(Ke - Ke3) / norm(Ke) < 1e-14

println("Stiffness successful")

Ke


# Chebyshev regression: least-square fits of data
# to multidimensional Chebyshev polynomials.

function chebregression(x::AbstractVector{SVector{N,Td}}, y::AbstractVector{T},
                        lb::SVector{N,Td}, ub::SVector{N,Td}, order::NTuple{N,Int}) where {N,Td<:Real,T<:Union{SVector,Number}}
    length(x) == length(y) || throw(DimensionMismatch())
    length(x) ≥ prod(order .+ 1) || throw(ArgumentError("not enough data points $(length(x)) to fit to order $order"))

    # assemble rhs as matrix
    Y = Array{float(eltype(T))}(undef, length(y), length(first(y)))
    for j = 1:length(y)
        Y[j,:] .= y[j + (firstindex(y)-1)]
    end

    # assemble lhs matrix
    # (TODO: this algorithm is O(length(x) * length(c)²),
    #  but it should be possible to do it in linear time.
    #  However, the A \ Y step is also O(mn²), so this
    #  only affects the constant factor in the complexity.)
    c = ChebPoly{N,Td,Td}(zeros(Td, order .+ 1), lb, ub)
    A = Array{Td}(undef, length(x), length(c.coefs))
    for i = 1:length(c.coefs)
        c.coefs[i] = 1 # basis function
        for j = 1:length(x)
            A[j,i] = c(x[j + (firstindex(x)-1)])
        end
        c.coefs[i] = 0 # reset
    end

    # least-square solution
    C = A \ Y

    # rearrange C into a ChebPoly
    Tc = typeof(zero(T) * one(eltype(Y)))
    coefs = Array{Tc,N}(reshape(reinterpret(Tc, vec(transpose(C))), order .+ 1))
    return ChebPoly{N,Tc,Td}(coefs, lb, ub)
end

# convert arrays to vectors of svectors or scalars
to_svectors(x::AbstractVector{<:Number}) = x
to_svectors(x::AbstractVector{<:Number}, ::Val{1}) = SVector{1}.(x)
to_svectors(x::AbstractVector{<:SVector{N}}) where {N} = x
to_svectors(x::AbstractVector{<:SVector{N}}, ::Val{N}) where {N} = x
to_svectors(x::AbstractVector{<:AbstractVector{T}}, ::Val{N}=Val(length(first(x)))) where {T<:Number,N} =
    SVector{N,T}.(x)
to_svectors(x::AbstractMatrix{T}, ::Val{N}=Val(size(x,2))) where {T<:Number,N} =
    SVector{N,T}[row for row in eachrow(x)]

# normalize x and y arguments to vectors of svectors or scalars
chebregression(x::AbstractVecOrMat, y::AbstractVecOrMat, lb::AbstractVector, ub::AbstractVector, order::NTuple{N}) where {N} =
    chebregression(to_svectors(x, Val{N}()), to_svectors(y), SVector{N}(lb), SVector{N}(ub), order)

chebregression(x::AbstractVecOrMat, y::AbstractVecOrMat, order::NTuple{N}) where {N} =
    chebregression(to_svectors(x, Val{N}()), to_svectors(y), order)

# accept scalar bounds and order in 1d case
chebregression(x::AbstractVector{<:Real}, y::AbstractVecOrMat, lb::Real, ub::Real, order::Integer) =
    chebregression(x, y, SVector(lb), SVector(ub), (order,))
chebregression(x::AbstractVector{<:Real}, y::AbstractVecOrMat, order::Integer) =
    chebregression(x, y, minimum(x), maximum(x), order)

# construct lb and ub if omitted
chebregression(x::AbstractVector{<:SVector{N}}, y::AbstractVector, order::NTuple{N}) where {N} =
    chebregression(x, y, reduce((a,b) -> min.(a,b), x), reduce((a,b) -> max.(a,b), x), order)

# promote arguments to common types
function chebregression(x::AbstractVector{SVector{N,Tx}}, y::AbstractVector{Ty},
    lb::SVector{N,Tlb}, ub::SVector{N,Tub}, order::NTuple{N,<:Integer}) where {N,Tx<:Real,Tlb<:Real,Tub<:Real,Ty<:Union{SVector,Number}}
    Td = float(promote_type(Tx,Tub,Tlb))
    return chebregression(AbstractVector{SVector{N,Td}}(x), y, SVector{N,Td}(lb), SVector{N,Td}(ub), NTuple{N,Int}(order))
end

"""
    chebregression(x, y, [lb, ub,] order)

Return a Chebyshev polynomial (`ChebPoly`) constructed by
performing a least-square fit of Chebyshev polnomials of the
given `order`, where `x` are the coordinates of the data
points `y`.  `lb` and `ub` are the lower and upper bounds,
respectively, of the Chebyshev domain; these should normally
enclose all of the points in `x`, and default to the minimum
and maximum coordinates in `x` if they are omitted.

In the 1d case, `x` is an array of scalars, `lb < ub`
are scalars, and `order` is an integers.   In the `N`-dimensional
case, `order` is an `N`-tuple of integers (the order in each
dimension), `lb` and `ub` are `N`-component vectors, and
`x` is an array of `N`-component vectors (or a matrix with
`N` columns, interpreted as the vector components).

`y` can be a vector or numbers or a vector of vectors (for vector-
valued Chebyshev fits).  The latter case can also be input
as a matrix whose columns are the vector componnents.  `size(x,1)`
and `size(y,1)` must match, and must exceed `prod(order .+ 1)`.
"""
function chebregression end


export MvNormalModel, MvNormalData, MvNormalParams

"""
Model observations as drawn from a multivariate normal distribution. See
`MvNormalData` for summarizing data for estimation and likelihood calculations.
"""
struct MvNormalModel end

"""
    $SIGNATURES

Test if `matrix` is square and conformable with `vector`.
"""
function is_conformable_square(vector::AbstractVector, matrix::AbstractMatrix)
    k = length(vector)
    size(matrix) == (k, k)
end

struct MvNormalData{TW <: Real, Tm <: AbstractVector, TS <: AbstractMatrix}
    "sum of the weights (alternatively, total number of observations)"
    W::TW
    "(weighted) sample mean"
    m::Tm
    "(weighted) sample covariance matrix"
    S::TS
    @doc """
        MvNormalData(n, m, S)

    Multivariate normal model summary statistics with `n` observations, mean `m`
    and sample covariance `S`. Only saves the summary statistics.

    !!! usage

        Use `MvNormalData(X, [wv])` to construct from data.
    """ ->                      # FIXME workaround, remove -> in v0.7
    function MvNormalData(W::TW, m::Tm, S::TS) where {TW <: Real,
                                                      Tm <: AbstractVector,
                                                      TS <: AbstractMatrix}
        @argcheck is_conformable_square(m, S)
        new{TW, Tm, TS}(W, m, S)
    end
end

size(ss::MvNormalData) = ss.W, length(ss.m)

function show(io::IO, ss::MvNormalData)
    W, k = size(ss)
    println(io, "Summary statistics for multivariate normal, $(W) × $(k) samples")
    println(io, "sample mean: $(ss.m)")
    println(io, "sample covariance: $(ss.S)")
end

"""
    $SIGNATURES

Multivariate normal summary statistics from observations (each row of `X` is an
observation).
"""
function MvNormalData(X::AbstractMatrix)
    m, S = mean_and_cov(X, 1; corrected = false)
    MvNormalData(size(X, 1), vec(m), S)
end

"""
    $SIGNATURES

Multivariate normal summary statistics from observations (each row of `X` is an
observation), with weights.
"""
function MvNormalData(X::AbstractMatrix, wv::AbstractWeights)
    m, S = mean_and_cov(X, wv, 1; corrected = false)
    MvNormalData(sum(wv), vec(m), S)
end

struct MvNormalParams{Tμ, TΣ}
    "mean"
    μ::Tμ
    "variance"
    Σ::TΣ
    @doc """
        MvNormalParams(μ, Σ)

    Parameters for the multivariate normal model ``x ∼ MvNormal(μ, Σ)``.

    !!! usage

        Construct using [`MLE(::MvNormalModel, ::MvNormalData)`](@ref).
    """ ->                      # FIXME workaround, remove -> in v0.7
    function MvNormalParams(μ::Tμ, Σ::TΣ) where {Tμ, TΣ}
        @argcheck is_conformable_square(μ, Σ)
        new{Tμ, TΣ}(μ, Σ)
    end
end

MLE(::MvNormalModel, ss::MvNormalData) = MvNormalParams(ss.m, ss.S)

function loglikelihood(::MvNormalModel, ss::MvNormalData, params::MvNormalParams)
    @unpack W, m, S = ss
    @unpack μ, Σ = params
    K = length(m)
    @argcheck length(μ) == K
    C = cholfact(Σ)
    d = m-μ
    -W/2*(K*log(2*π) + logdet(C) + dot(vec(S) + vec(d * d'), vec(inv(Σ))))
end

vec_parameters(ϕ::MvNormalParams) = vec_parameters((ϕ.μ, ϕ.Σ))


"""
    project(u::AbstractVector, v::AbstractVector) -> AbstractVector

Project vector `u` onto vector `v`.

# Examples

```jldoctest
julia> project([1, 1], [1, 0])
2-element Base.Vector{Float64}:
 1.0
 0.0

julia> project([5, 5], [1, 0])
2-element Base.Vector{Float64}:
 5.0
 0.0

julia> project([5, -5], [0, 1])
2-element Base.Vector{Float64}:
 -0.0
 -5.0
```

"""
function project(u::AbstractVector, v::AbstractVector)
    return (u ⋅ v) / (v ⋅ v) * v
end

"""
    project(point::AbstractVector, line::AbstractLine) -> StaticArrays.SVector

Project a point onto a line.

# Examples

```jldoctest
julia> project([1, 1], Line([0, 0], [1, 0]))
2-element StaticArrays.SVector{2, Float64} with indices SOneTo(2):
 1.0
 0.0

julia> project([5, -1], Line([0, 0], [1, 0]))
2-element StaticArrays.SVector{2, Float64} with indices SOneTo(2):
 5.0
 0.0

julia> project([1, 0], Line([0, 0], [1, 1]))
2-element StaticArrays.SVector{2, Float64} with indices SOneTo(2):
 0.5
 0.5

julia> point = project([1, 0, 0], Line([0, 0, 0], [1, 1, 1]));

julia> round.(point, digits=3)
3-element StaticArrays.SVector{3, Float64} with indices SOneTo(3):
 0.333
 0.333
 0.333
```

"""
function project(point::AbstractVector, line::AbstractLine)

    # Vector from the point on the line to the point in space.
    vector_to_point = Vector(line.point, point)

    # Vector projected onto the line.
    vector_projected = project(vector_to_point, line.direction)

    return line.point + vector_projected
end

"""
    project(point::AbstractVector, plane::AbstractPlane) -> StaticArrays.SVector

Project a point onto a plane.

# Examples

```jldoctest
julia> project([0, 0, 5], Plane([0, 0, 0], [0, 0, 1]))
3-element StaticArrays.SVector{3, Float64} with indices SOneTo(3):
 0.0
 0.0
 0.0

julia> plane = Plane([1,2,3], [1, 3, -2]);

julia> point_projected = project([5, 1, 3], plane);

julia> round.(point_projected, digits=3)
3-element StaticArrays.SVector{3, Float64} with indices SOneTo(3):
 4.929
 0.786
 3.143
```

"""
function project(point::AbstractVector, plane::AbstractPlane)

    # Vector from the point in space to the point on the plane.
    vector_to_plane_point = Vector(point, plane.point)

    # Perpendicular vector from the point in space to the plane.
    vector_to_plane = project(vector_to_plane_point, plane.normal)

    return point + vector_to_plane
end


# The weight_space_approx function needed for ApproximateGPs.pathwise_sample
# Returns both the sampled basis and the distribution over w needed to approximate the GP
function gp_rff_approx(rng, kernel, input_dims, feature_dims)
    ϕ = sample_basis(rng, kernel, input_dims, feature_dims)
    p_w = MvNormal(Diagonal(Fill(1., feature_dims)))
    return ϕ, p_w
end

# Everything necessary to create a RFF approximation to a stationary kernel
# Currently ony supports SqExponentialKernel with variance and lengthscale
struct RFFBasis{Tinner, Touter, Tω, Tτ, Tsample}
    inner_weights::Tinner  # lengthscale
    outer_weights::Touter  # variance (scaled)
    ω::Tω  # Sampled frequencies;               size(ω): (input_dims, num_features)
    τ::Tτ  # Sampled phases;                    size(τ): (num_features,)
    sample_params::Tsample  # Returns a new sample of ω & τ
end

function (ϕ::RFFBasis)(x)
    # ϕ: R^{input_dims} -> R^{num_features}
    x_ = x ./ ϕ.inner_weights
    ωt_x = ϕ.ω'x_
    return ϕ.outer_weights * cos.(ωt_x .+ ϕ.τ)
end

function resample!(ϕ::RFFBasis)
    ω, τ = ϕ.sample_params()
    ϕ.ω .= ω
    ϕ.τ .= τ
end

# Currently need to pass `input_dims` explicitly - will eventually be in KernelFunctions
# https://github.com/JuliaGaussianProcesses/KernelFunctions.jl/issues/16
function sample_basis(rng, kernel, input_dims, num_features=100)
    inner, outer = spectral_weights(kernel)
    outer_scaled = outer * √(2/num_features)
    p_ω = spectral_distribution(kernel, input_dims)

    function sample_params()
        ω = rand(rng, p_ω, num_features)  #
        τ = rand(rng, Uniform(0, 2π), num_features)  #
        return ω, τ
    end

    return RFFBasis(inner, outer_scaled, sample_params()..., sample_params)
end

function spectral_distribution(::SqExponentialKernel, input_dims)
    return MvNormal(Diagonal(Fill(1., input_dims)))
end

spectral_distribution(k::ScaledKernel, args...) = spectral_distribution(k.kernel, args...)
spectral_distribution(k::TransformedKernel, args...) = spectral_distribution(k.kernel, args...)

spectral_distribution(k::Kernel) =  error("Spectral distribution not implemented for kernel:\n$k")

function spectral_weights(::SqExponentialKernel)
    return 1.0, 1.0
end

function spectral_weights(k::ScaledKernel)
    σ² = only(k.σ²)
    inner, outer = spectral_weights(k.kernel)
    return inner, outer * √σ²
end

function spectral_weights(k::TransformedKernel{<:Any, <:ScaleTransform})
    s = only(k.transform.s)
    inner, outer = spectral_weights(k.kernel)
    return inner / s, outer
end

spectral_weights(k::Kernel) = error("Spectral weights not implemented for kernel:\n$k")

# TODO:
# ARDTransform
# ProductKernel
# SumKernel
# MaternKernel


using StaticArrays, OrdinaryDiffEq, BenchmarkTools
tspan = (0.0,10.0)
println("Testing speed of solving ODEs\n")
println("---Using StaticArrays: f(t, u) = du::SVector")
#--------------------#
@inline eom(t, u) = SVector{5}(10.0(u[2]-u[1]), u[1]*(28.0-u[3]) - u[2],
u[1]*u[2] - (8/3)*u[3], 2.0u[4]*(1-u[4]), 0.15*u[4]*(1-u[5]))

# println("ODEProblem creation:")
# display(@benchmark ODEProblem(eom,SVector{3}(rand(3)),$tspan))
prob = ODEProblem(eom,SVector{5}(10rand(5)),tspan)
println("Solution (for total time 10)")
sol = solve(prob,Tsit5(),reltol=1e-8,abstol=1e-8)
display(@benchmark solve($prob,Tsit5(),reltol=1e-8,abstol=1e-8))
sleep(0.1)

println("\n---Using Base Arrays: f(t, u) = du::Vector")
#--------------------#
@inline eomB(t, u) = [10.0(u[2]-u[1]), u[1]*(28.0-u[3]) - u[2],
u[1]*u[2] - (8/3)*u[3], 2.0u[4]*(1-u[4]), 0.15*u[4]*(1-u[5])]

# println("ODEProblem creation:")
# display(@benchmark ODEProblem(eom,rand(3),$tspan))
prob = ODEProblem(eomB,10rand(5),tspan)
println("Solution (for total time 10)")
sol = solve(prob,Tsit5(),reltol=1e-8,abstol=1e-8)
display(@benchmark solve($prob,Tsit5(),reltol=1e-8,abstol=1e-8))
sleep(0.1)

println("\n---Using Base & in-place: f!(t, u, du)::Vector")

@inline function eom!(t, u, du)
    du[1] = 10.0(u[2]-u[1])
    du[2] = u[1]*(28.0-u[3]) - u[2]
    du[3] = u[1]*u[2] - (8/3)*u[3]
    du[4] = 2.0u[4]*(1-u[4])
    du[5] = 0.15*u[4]*(1-u[5])
end

# println("ODEProblem creation:")
# display(@benchmark ODEProblem(eom!,rand(3),$tspan))
prob = ODEProblem(eom!,10rand(5),tspan)
println("Solution (for total time 10)")
sol = solve(prob,Tsit5(),reltol=1e-8,abstol=1e-8)
display(@benchmark solve($prob,Tsit5(),reltol=1e-8,abstol=1e-8))
sleep(0.1)

# println("\n---Using MVector & in-place: f!(t, u, du)::MVector")
# @inline function eomM!(t, u::MVector, du::MVector)
#     du[1] = 10.0(u[2]-u[1])
#     du[2] = u[1]*(28.0-u[3]) - u[2]
#     du[3] = u[1]*u[2] - (8/3)*u[3]
# end
#
# probM = ODEProblem(eomM!,MVector{3}(10rand(3)),tspan)
# println("Solution (for total time 1)")
# sol = solve(probM,Tsit5(),reltol=1e-8,abstol=1e-8)
# display(@benchmark solve($probM,Tsit5(),reltol=1e-8,abstol=1e-8))
# # #this doesnt work currently


module Problems
export gen_matching_pennies, manh_dist, hide_and_seek, nemirovski1, nemirovski2, policeman_and_burglar_matrix

import LinearAlgebra
const LA = LinearAlgebra
using Distributions
using Random

"""
   Two test problems from Nemirovski et al. "Robust stochastic approximation approach to stochastic programming"
"""
function nemirovski1(n, α=1)
    A = zeros(n, n)
    for i in 1:n
        for j in 1:n
            A[i, j] = ((i + j - 1) / ( 2n - 1 ))^α
        end
    end
    return A
end

function nemirovski2(n, α=1)
    A = zeros(n, n)
    for i in 1:n
        for j in 1:n
            A[i, j] = ((abs(i - j) + 1) / ( 2n - 1 ))^α
        end
    end
    return A
end

"""
    From Juditski & Nemirovki tutorial. Problem Policemen and Burglar.
"""
function policeman_and_burglar_matrix(n, th=0.8; seed="false")
    if seed != "false"
        Random.seed!(parse(Int, seed))
    end
    w = abs.(randn(n))
    th = 0.8
    C = reshape(abs.([i - j for i in 1:n, j in 1:n]), (n, n))
    A = w .*(1 .- exp.(-th .* C))
    return A
end

function randunif(m, n; seed="false")
    if seed != "false"
        Random.seed!(parse(Int, seed))
    end

    A = rand(m, n)
    return A
end

"""
Problem 2.24
"""
function manh_dist(m=100, n=100)
    A = zeros(m, n)
    for i in 1:m
        for j in 1:n
            y = (i - 1) / (m - 1)
            x = (j - 1) / (n - 1)
            A[i,j] = abs(x - y)
        end
    end
    return A
end

"""
    'Hide and Seek' game
"""
function hide_and_seek(m, n, param=0.2; seed="false")
    if seed != "false"
        Random.seed!(parse(Int, seed))
    end
    A = 1.0 * reshape(rand(Bernoulli(param),  m * n), (m,n))
    return A
end

end


using Distributions, Statistics, Plots, Random; pyplot()
Random.seed!(0)

mu1, sig1, n1 = 0, 2, 8
mu2, sig2, n2 = 0, 30, 15
dist1 = Normal(mu1, sig1)
dist2 = Normal(mu2, sig2)

N = 10^6
tdArray = Array{Tuple{Float64,Float64}}(undef,N)

df(s1,s2,n1,n2) =
    (s1^2/n1 + s2^2/n2)^2 / ( (s1^2/n1)^2/(n1-1) + (s2^2/n2)^2/(n2-1) )

for i in 1:N
    x1Data = rand(dist1, n1)
    x2Data = rand(dist2, n2)
    x1Bar,x2Bar = mean(x1Data),mean(x2Data)
    s1,s2 = std(x1Data),std(x2Data)
    tStat = (x1Bar - x2Bar) / sqrt(s1^2/n1 + s2^2/n2)
    tdArray[i] = (tStat , df(s1,s2,n1,n2))
end
sort!(tdArray, by = first)

invVal(v,i) = quantile(TDist(v),i/(N+1))

xCoords  = Array{Float64}(undef,N)
yCoords1 = Array{Float64}(undef,N)
yCoords2 = Array{Float64}(undef,N)

for i in 1:N
    xCoords[i] = first(tdArray[i])
    yCoords1[i] = invVal(last(tdArray[i]), i)
    yCoords2[i] = invVal(n1+n2-2, i)
end

scatter(xCoords, yCoords1, c=:blue, label="Calculated v", msw=0)
scatter!(xCoords, yCoords2, c=:red, label="Fixed v", msw=0)
plot!([-10,10], [-10,10],
	 c=:black, lw=0.3, xlims=(-8,8), ylims=(-8,8), ratio=:equal, label="",
	 xlabel="Theoretical t-distribution quantiles",
	 ylabel="Simulated t-distribution quantiles", legend=:topleft)