## Canonical Form of Normal distribution

immutable NormalCanon <: ContinuousUnivariateDistribution
    η::Float64       # σ^(-2) * μ
    λ::Float64    # σ^(-2)
    μ::Float64       # μ

    function NormalCanon(η::Real, λ::Real)
        @check_args(NormalCanon, λ > zero(λ))
    	new(η, λ, η / λ)
    end
    NormalCanon() = new(0.0, 1.0, 0.0)
end

@distr_support NormalCanon -Inf Inf

## conversion between Normal and NormalCanon

Base.convert(::Type{Normal}, d::NormalCanon) = Normal(d.μ, 1.0 / sqrt(d.λ))
Base.convert(::Type{NormalCanon}, d::Normal) = (λ = 1.0 / σ^2; NormalCanon(λ * d.μ, λ))
canonform(d::Normal) = convert(NormalCanon, d)

#### Parameters

params(d::NormalCanon) = (d.η, d.λ)

#### Statistics

mean(d::NormalCanon) = d.μ
median(d::NormalCanon) = mean(d)
mode(d::NormalCanon) = mean(d)

skewness(d::NormalCanon) = 0.0
kurtosis(d::NormalCanon) = 0.0

var(d::NormalCanon) = 1.0 / d.λ
std(d::NormalCanon) = sqrt(var(d))

entropy(d::NormalCanon) = 0.5 * (log2π + 1.0 - log(d.λ))

#### Evaluation

pdf(d::NormalCanon, x::Float64) = (sqrt(d.λ) / sqrt2π) * exp(-0.5 * d.λ * abs2(x - d.μ))
logpdf(d::NormalCanon, x::Float64) = 0.5 * (log(d.λ) - log2π - d.λ * abs2(x - d.μ))

zval(d::NormalCanon, x::Float64) = (x - d.μ) * sqrt(d.λ)
xval(d::NormalCanon, z::Float64) = d.μ + z / sqrt(d.λ)

cdf(d::NormalCanon, x::Float64) = normcdf(zval(d,x))
ccdf(d::NormalCanon, x::Float64) = normccdf(zval(d,x))
logcdf(d::NormalCanon, x::Float64) = normlogcdf(zval(d,x))
logccdf(d::NormalCanon, x::Float64) = normlogccdf(zval(d,x))

quantile(d::NormalCanon, p::Float64) = xval(d, norminvcdf(p))
cquantile(d::NormalCanon, p::Float64) = xval(d, norminvccdf(p))
invlogcdf(d::NormalCanon, lp::Float64) = xval(d, norminvlogcdf(lp))
invlogccdf(d::NormalCanon, lp::Float64) = xval(d, norminvlogccdf(lp))

#### Sampling

rand(cf::NormalCanon) = cf.μ + randn() / sqrt(cf.λ)
rand!{T<:Real}(cf::NormalCanon, r::AbstractArray{T}) = rand!(convert(Normal, cf), r)


# Map-MCMC for mixtures.

# This code assumes the following functions have been defined:
#   likelihood(x[j],theta)
#   log_v_prior(v)
#   log_theta_prior(theta)
#   theta_prop(theta)
#   log_theta_prop(theta,thetap)
#   new_thetas(m)

function sampler(x,n_samples,m,c,sigma,zeta)
    # x = data (array of datapoints)
    # n_samples = # of MCMC iterations
    # m = maximum # of mixture components
    # c = cutoff point for weight map
    # sigma = scale of weight proposals
    # zeta = power to raise likelihood to

    n = length(x)

    # initialize state
    v = zeros(m); v[1] = 2*c; v[2:end] = c/2 # latent weights
    q = max(v-c,0) # mapped weights (unnormalized)
    s = sum(q)
    theta = new_thetas(m)

    # initialize vars for computing likelihood
    L = [likelihood(x[j],theta[i]) for i=1:m, j=1:n]
    M = vec(q'*L)  # mixture density with unnormalized weights
    ll = sum(log(M)) - n*log(s)  # log-lik
    Mp = zeros(n)  # Mp and Lp will hold proposed values
    Lp = zeros(n)

    # record-keeping
    k_r = zeros(n_samples)
    m_r = zeros(n_samples,m)
    l_r = zeros(n_samples,m)
    v_r = zeros(n_samples,m)
    nta = 0  # number of theta proposals accepted
    nva = 0  # number of v proposals accepted

    # draw samples
    for iter = 1:n_samples
        # update parameters with Metropolis-Hastings moves
        for i = 1:m
            thetap = theta_prop(theta[i])
            llp = -n*log(s)
            for j = 1:n
                Lp[j] = likelihood(x[j],thetap)
                Mp[j] = max(M[j] + q[i]*(Lp[j] - L[i,j]), 0)
                # Note: max(.,0) prevents negative values due to roundoff error.
                llp += log(Mp[j])
            end
            # compute acceptance probability
            top = log_theta_prior(thetap) + zeta*llp + log_theta_prop(thetap,theta[i])
            bot = log_theta_prior(theta[i]) + zeta*ll + log_theta_prop(theta[i],thetap)
            p_accept = min(1, exp(top-bot))
            # accept or reject
            if rand() < p_accept
                theta[i] = copy(thetap)
                for j = 1:n; L[i,j] = Lp[j]; end
                M,Mp = Mp,M
                ll = llp
                nta += 1
            end
        end

        # update weights with Metropolis-Hastings moves
        for i = 1:m
            vp = v[i]*exp(randn()*sigma)
            qp = max(vp-c,0)
            sp = s + qp - q[i]
            if sp > 0
                llp = -n*log(sp)
                for j = 1:n
                    Mp[j] = max(M[j] + (qp - q[i])*L[i,j], 0)
                    llp += log(Mp[j])
                end
                # compute acceptance probability
                top = log_v_prior(vp) + zeta*llp - log(v[i])
                bot = log_v_prior(v[i]) + zeta*ll - log(vp)
                p_accept = min(1, exp(top-bot))
                # accept or reject
                if rand() < p_accept
                    v[i] = vp
                    q[i] = qp
                    s = sp
                    M,Mp = Mp,M
                    ll = llp
                    nva += 1
                end
            end
        end

        # record
        k_r[iter] = sum(q.>0)  # number of active components
        for i = 1:m
            m_r[iter,i] = theta[i][1]  # means
            l_r[iter,i] = theta[i][2]  # precisions
            v_r[iter,i] = v[i]  # latent weights
        end

        # visualize mixture components during sampling
        if false
            figure(10); clf(); hold(true)
            xs = linspace(-5,5,1000)
            for i = 1:m
                if q[i]>0
                    plot(xs,(q[i]/s)*normpdf(xs,theta[i][1],theta[i][2]))
                end
            end
            draw()
        end
    end
    art = nta/(m*n_samples)  # MH acceptance rate for theta proposals
    arv = nva/(m*n_samples)  # MH acceptance rate for v proposals
    return (q/s),theta,k_r,v_r,art,arv,m_r,l_r
end


# # Symmetry reducting a strong relaxation of the quadratic assigment problem
# Here, we are going to show how to load a QAP from QABLib, formulate
# a strong semidefinite relaxation of it, symmetry reduce it, and finally solve it.

# ## Quadratic assigment problems
# QAPs are given by two quadratic matrices $A$ and $B$. The objective is to permute
# the rows and columns of $B$, such that the inner product between the matrices is
# minimized.
#
# ``\mathrm{QAP}(A,B) = \min_{\phi\in S_n} \sum_{i,j=1}^n a_{ij}b_{\phi(i)\phi(j)}``
#
# QAPs are notoriously hard to solve exactly, but there exist strong polynomial time
# relaxations, such as the following semidefinite programming relaxation:
#
# ```math
# \begin{aligned}
# \min\enspace & \langle B\otimes A ,Y\rangle\\
# \mathrm{s.t.}\enspace & \langle I_n\otimes E_{jj},Y\rangle=1 \text{ for }j\in [n],\\
# & \langle E_{jj}\otimes I_n,Y\rangle=1 \text{ for }j\in [n],\\
# & \langle I_n\otimes (J_n-I_n)+(J_n-I_n)\otimes I_n,Y\rangle =0, \\
# & \langle J_{n^2},Y\rangle = n^2,\\
# & Y\in D^{n^2},
# \end{aligned}
# ```
#
# But in practice this relaxation is often too big to be solved directly.

# ## Loading the data of a QAP
using SparseArrays, LinearAlgebra
using Test #src

file = joinpath(@__DIR__, "esc16j.dat")
data = open(file) do file
    read(file, String)
end
data = split(data, [' ', '\n', '\r'], keepempty = false)

n = parse(Int64, data[1])
A = zeros(Int64, n, n)
B = zeros(Int64, n, n)

pos = 2
for x = 1:n
    for y = 1:n
        A[x, y] = parse(Int64, data[pos])
        global pos += 1
    end
end
for x = 1:n
    for y = 1:n
        B[x, y] = parse(Int64, data[pos])
        global pos += 1
    end
end

# ## Building the SDP (in vectorized standard form)
n = size(A, 1)

## Objective
CPrg = sparse(kron(B, A))

In = sparse(I, n, n)
Jn = ones(n, n)

## Vectorised constraint matrices as rows of large matrix APrg
APrg = spzeros(2n + 1, n^4)
bPrg = zeros(2n + 1)
currentRow = 1

for j = 1:n
    Ejj = spzeros(n, n)
    Ejj[j, j] = 1.0
    APrg[currentRow, :] = vec(kron(In, Ejj))
    bPrg[currentRow] = 1
    global currentRow += 1
    ## Last constraint is linearly dependent on others
    if (j < n)
        APrg[currentRow, :] = vec(kron(Ejj, In))
        bPrg[currentRow] = 1
        global currentRow += 1
    end
end

APrg[currentRow, :] = vec(kron(In, Jn - In) + kron(Jn - In, In))
bPrg[currentRow] = 0
currentRow += 1
APrg[currentRow, :] = vec(ones(n^2, n^2))
bPrg[currentRow] = n^2

CPrg = sparse(vec(0.5 * (CPrg + CPrg')));

# ## Symmetry reducing the SDP

# We first determine an optimal admissible partition subspace
using SDPSymmetryReduction
P = admPartSubspace(CPrg, APrg, bPrg, true)
P.n

@test P.n == 150 #src
# And then we block-diagonalize it
blkD = blockDiagonalize(P, true);
@test sort(blkD.blkSizes) == sort([7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) #src

# ## Determining the coefficients of the reduced SDP
PMat = hcat([sparse(vec(P.P .== i)) for i = 1:P.n]...)
newA = APrg * PMat
newB = bPrg
newC = CPrg' * PMat;

## Removing linearly dependent constraints #src
# using RowEchelon  #src
# T = rref!(Matrix(hcat(newA,newB))) #src
# r = rank(T) #src
# newA = T[1:r,1:end-1] #src
# newB = T[1:r,end] #src
# length(newB) #src

# ## Solving the reduced SDP with JuMP and CSDP

using JuMP, CSDP
m = Model(CSDP.Optimizer)

## Initialize variables corresponding parts of the partition P
## >= 0 because the original SDP-matrices are entry-wise nonnegative
x = @variable(m, x[1:P.n] >= 0)

@constraint(m, newA * x .== newB)
@objective(m, Max, newC * x)

psdBlocks = sum(blkD.blks[i] .* x[i] for i = 1:P.n)
for blk in psdBlocks
    if size(blk, 1) > 1
        @constraint(m, blk in PSDCone())
    else
        @constraint(m, blk .>= 0)
    end
end

optimize!(m)
#
termination_status(m)
#
objective_value(m)

@test objective_value(m) ≈ 48.3042 atol = 5#src


using OffsetArrays
using SpecialFunctions: gamma, erfcx
using PyPlot
using Polynomials
using Printf
using LinearAlgebra

Γ(z) = gamma(z)

function pade(α::T, β::T, m::Integer, n::Integer) where T <: AbstractFloat
    if mod(m+n,2) == 0
        throw(ArgumentError("m+n must be odd"))
    end
    r = div(m+n-1,2)
    a = OffsetArray{T}(undef, 0:m-1)
    b = OffsetArray{T}(undef, 0:n-1)
    pow = one(T)
    for k = 0:m-1
        a[k] = pow / Γ(β+k*α)
        pow = -pow
    end
    b[0] = zero(T)
    pow = one(T)
    for k = 1:n-1
        pow = -pow
        b[k] = pow * sinpi(k*α-β) * Γ(1+k*α-β) / π
    end
    A = zeros(T, 2r-1, 2r-1)
    rhs = zeros(T, 2r-1)
    if m ≥ r+1
        for k = 1:r-1
            A[k,k] = one(T)
            for j = 1:k
                A[k,r-1+j] = -a[k-j]
            end
            rhs[k] = a[k]
        end
        for k = r:m-1
            for j = 1:r
                A[k,r-1+j] = -a[k-j]
            end
            rhs[k] = a[k]
        end
        for k = r-n+1:r-1
            row = k+r
            A[row,k] = one(T)
            for j = k+1:r
                col = j + r - 1
                A[row,col] = -b[j-k]
            end
        end
    else
        for k = 1:m-1
            A[k,k] = one(T)
            for j = 1:k
                col = r - 1 + j
                A[k,col] = -a[k-j]
            end
            rhs[k] = a[k]
        end
        for k = 1:n-r-1
            row = k + m - 1
            for j = 1:r
                col = r - 1 + j
                A[row,col] = -b[j+k]
            end
            rhs[row] = b[k]
        end
        for j = 1:r
            col = r - 1 + j
            A[r,col] = -b[j]
        end
        rhs[r] = -a[0]
        for k = 1:r-1
            row = r + k
            A[row,k] = one(T)
            for j = k+1:r
                col = r - 1 + j
                A[row,col] = -b[j-k]
            end
        end
    end
    coeffs = A \ rhs
    p = OffsetArray{T}(undef, 0:r-1)
    q = OffsetArray{T}(undef, 0:r)
    p[0] = a[0]
    for k = 1:r-1
        p[k] = coeffs[k]
    end
    q[0] = one(T)
    for k = 1:r
        q[k] = coeffs[k+r-1]
    end
    return p, q, A, rhs
end

#T = BigFloat
T = Float64
r = 3
α = 1 / parse(T, "2")
β = parse(T, "1")
x = range(zero(T), parse(T, "10"), length=201)
y = erfcx.(x)
figure(1)
for m = r:5
    n = 2r + 1 - m
    @printf("%3d  %3d", m, n)
    p, q, A, rhs = pade(α, β, m, n)
    @printf("  %5.2f\n", cond(A))
    P = Polynomial(p[0:r-1])
    Q = Polynomial(q[0:r])
    err = P.(x) ./ Q.(x) - y
    plot(x, err, label="m, n = $m, $n")
end
legend()
grid(true)

figure(2)
#for (r, m, n) in [(4, 5, 4), (8, 9, 8), (12, 13, 12), (16, 17, 16)]
for (r, m, n) in [(4, 5, 4), (8, 9, 8), (12, 13, 12)]
    p, q, A, rhs = pade(α, β, m, n)
    println("cond(A) = ", cond(A))
    display(p)
    display(q)
    P = Polynomial(p[0:r-1])
    Q = Polynomial(q[0:r])
    err = P.(x) ./ Q.(x) - y
    semilogy(x, abs.(err), label="m, n = $m, $n")
end
legend()
grid(true)


function can_cut(c, k, as)
    sum([ceil(Int, a/c) - 1 for a in as]) ≤ k
end

function solve()
    n, k = [parse(Int, x) for x in split(readline())]
    as = [parse(Int, x) for x in split(readline())]

    lb, ub = 0, maximum(as)
    while lb + 1 < ub
        c = ceil(Int, (ub + lb) / 2)
        if can_cut(c, k, as)
            ub = c
        else
            lb = c
        end
    end
    ceil(Int, ub)
end

println(solve())


function read_Species_From_Text_File(file_name)
    return reaction_matrix
end

function calculate_concentration(c_0, λ, Δt)
    Λ = Diagonal(λ)
    R = assemble_transformation_matrix_inverse(λ)
    S = assemble_transformation_matrix(λ)
    A = S * Λ * R * Δt
    return exp(A) * c_0
end

function transformation_matrix_inverse_ij(i, j, λ)
    s_ij::eltype(λ) = 1.0
    # Loop over species ancestor index l for species i with (i-j) generations
    for l = j:(i-1)
        s_ij *= λ[l] / (λ[l] - λ[i])
    end
    return s_ij
end

function transformation_matrix_ij(i, j, λ)
    s_ij = λ[j] / (λ[i] - λ[j])
    # Loop over species ancestor index l for species i with (i-j) generations
    for l = (j+1):(i-1)
        s_ij *= λ[l] / (λ[l] - λ[j])
    end
    return s_ij
end

function assemble_transformation_matrix(λ)
    n = length(λ)
    S = zeros(eltype(λ), length(λ), length(λ))
    S = UnitLowerTriangular(S)
    for i = 2:n
        for j = 1:(i-1)
            S[i, j] = transformation_matrix_ij(i, j, λ)
        end
    end
    return S
end

function assemble_transformation_matrix_inverse(λ)
    n = length(λ)
    R = zeros(eltype(λ), length(λ), length(λ))
    R = UnitLowerTriangular(R)
    for i = 2:n
        for j = 1:i
            R[i, j] = transformation_matrix_inverse_ij(i, j, λ)
        end
    end
    return R
end

function retrieve_generation_number(i, j)
    return (i - j)
end

function transformation_matrix_5x5(λ = [1.0; 2.0; 3.0; 4.0; 5.0])

    s = zeros(5, 5)
    s[1, 1] = 1

    s[2, 1] = λ[1] / (λ[2] - λ[1])
    s[3, 1] = λ[1] / (λ[3] - λ[1]) * λ[2] / (λ[2] - λ[1])
    s[4, 1] = λ[1] / (λ[4] - λ[1]) * λ[2] / (λ[2] - λ[1]) * λ[3] / (λ[3] - λ[1])
    s[5, 1] =
        λ[1] / (λ[5] - λ[1]) * λ[2] / (λ[2] - λ[1]) *
        λ[3] / (λ[3] - λ[1]) * λ[4] / (λ[4] - λ[1])

    s[2, 2] = 1
    s[3, 2] = λ[2] / (λ[3] - λ[2])
    s[4, 2] = λ[2] / (λ[4] - λ[2]) * λ[3] / (λ[3] - λ[2])
    s[5, 2] = λ[2] / (λ[5] - λ[2]) * λ[3] / (λ[3] - λ[2]) * λ[4] / (λ[4] - λ[2])

    s[3, 3] = 1
    s[4, 3] = λ[3] / (λ[4] - λ[3])
    s[5, 3] = λ[3] / (λ[5] - λ[3]) * λ[4] / (λ[4] - λ[3])

    s[4, 4] = 1
    s[5, 4] = λ[4] / (λ[5] - λ[4])

    s[5, 5] = 1

    return s
end

function transformation_matrix_inverse_5x5(λ = [1.0; 2.0; 3.0; 4.0; 5.0])

    s = zeros(5, 5)
    s[1, 1] = 1

    s[2, 1] = λ[1] / (λ[1] - λ[2])
    s[3, 1] = λ[1] / (λ[1] - λ[3]) * λ[2] / (λ[2] - λ[3])
    s[4, 1] = λ[1] / (λ[1] - λ[4]) * λ[2] / (λ[2] - λ[4]) * λ[3] / (λ[3] - λ[4])
    s[5, 1] =
        λ[1] / (λ[1] - λ[5]) * λ[2] / (λ[2] - λ[5]) * λ[3] / (λ[3] - λ[5]) *
        λ[4] / (λ[4] - λ[5])

    s[2, 2] = 1
    s[3, 2] = λ[2] / (λ[2] - λ[3])
    s[4, 2] = λ[2] / (λ[2] - λ[4]) * λ[3] / (λ[3] - λ[4])
    s[5, 2] = λ[2] / (λ[2] - λ[5]) * λ[3] / (λ[3] - λ[5]) * λ[4] / (λ[4] - λ[5])

    s[3, 3] = 1
    s[4, 3] = λ[3] / (λ[3] - λ[4])
    s[5, 3] = λ[3] / (λ[3] - λ[5]) * λ[4] / (λ[4] - λ[5])

    s[4, 4] = 1
    s[5, 4] = λ[4] / (λ[4] - λ[5])

    s[5, 5] = 1

    return s
end


#=
Small illustation on the capacitated vehicle routing problem (CVRP)
Just run the script for an illustration

Mathematical model based on: https://www.researchgate.net/publication/323173028_Two_models_of_the_capacitated_vehicle_routing_problem
using modified indices to match Julia's 1-based indexing

In this model we eliminate all subtour explicitely, keep in mind that this is very expensive
as the number of subtours grows exponentially with the number of cities! A modified approach is to solve the
relaxed problem (i.e. without subtour constraints) and only add them when they occur (thus solving the model again),
adding the constraint that prohibits the subtour that was found during the previous iteration cf.
https://how-to.aimms.com/Articles/332/332-Implicit-Dantzig-Fulkerson-Johnson.html
for a clear explanation.

=#
using JuMP
using GLPK
using Plots
using Combinatorics:powerset # may require additional install

"""
Multiple Vehicle Routing problem (using the entire set of possible subtours for subtour elimination)
"""
function MVRP()
    # general problem setup #
    # --------------------- #
    # number of cities
    n = 6
    # pick random locations
    coord_x = rand(n+1)
    coord_y = rand(n+1)
    dist(x_1, x_2, y_1, y_2) = sqrt((x_1 - x_2)^2 + (y_1 - y_2)^2)
    # cost matrix (symmetrical, distances, depot is city 1)
    c = zeros(n+1, n+1)
    for i in 1:n+1
        for j in i+1:n+1
            c[i,j] = dist(coord_x[i], coord_x[j], coord_y[i], coord_y[j])
        end
    end
    c .+= c'
    # demand for each city (number of e.g. number of pallets)
    d = rand(1:10, n+1)
    d[1] = 0 # no demand @ depot
    # truck capacity (equal for all trucks, can easily be extended to different size trucks)
    Q = 20
    # minimum number of trucks for order to be feasible:
    p = ceil(Int,sum(d) / Q)
    @info "$(p) trucks required"

    # Linear programming setup #
    # ------------------------ #
    model = Model(with_optimizer(GLPK.Optimizer))
    # Add variables (x_{i,j,p}: edge from i > j by truck p, Boolean valued)
    @variable(model, x[1:n+1,1:n+1,1:p], Bin)
    # Add objective
    @objective(model, Min, sum(sum(x[:,:,i] .* c) for i in 1:p))
    # Add constraints
    for r in 1:p
        # each vehicle can leave the depot only once
        @constraint(model, sum(x[1, j, p] for j in 2:n+1) == 1)
        # sum of the demands of the customers visited in a route is less than or equal to the capacity of the vehicle
        @constraint(model, sum(d[j]*x[i,j,r] for i in 1:n+1 for j in 2:n+1 if i≠j) <= Q)
        # the number of the vehicles arriving at every customer and entering the depot is equal to the number of the vehicles leaving
        for j in 1:n+1
            @constraint(model, sum(x[i,j,r] for i in 1:n+1 if i≠j) == sum(x[j,i,r] for i in 1:n+1))
        end
    end
    # ensure that each customer is visited by exactly one vehicle
    for j in 2:n+1
        @constraint(model, sum(x[i,j,r] for r in 1:p for i in 1:n+1 if i≠j) == 1)
    end

    #=
    # eliminate sub-tours (essential but costly)
    for subset in powerset(2:n+1, 2, n)
        @constraint(model, sum(x[i,j,r] for r in 1:p for i in subset for j in subset if i≠j) <= length(subset) - 1 )
    end
    =#
    # Solve problem
    optimize!(model)
    @info "termination status: $(termination_status(model))"
    @info "objective_value $(objective_value(model))"

    #     Nice illustration    #
    # ------------------------ #
    marks = [:circle for _ in 1:n+1]
    marks[1] = :cross
    fig = plot()
    scatter!(fig, coord_x[1:1], coord_y[1:1], marker=:cross, label="Depot", color=:black,markersize=12)
    scatter!(fig, coord_x[2:end], coord_y[2:end], marker=:circle, label="Stores", color=:gray )
    for r in 1:p
        inds = findall(x-> !iszero(x), value.(x[:,:,r]))
        for ind in inds
            xx = [coord_x[ind[1]]; coord_x[ind[2]]]
            yy = [coord_y[ind[1]]; coord_y[ind[2]]]
            plot!(fig, xx, yy, color=r, label="")
        end
    end
    title!(fig, "lay-out for $(p) trucks with $(n) cities")
    display(fig)
end

"""
Multiple Trip Vehicle Routing problem

Implementation according to the 4-index formulation, also uses the entire set of subtours (similar to the other example)

Remark: since we are mimimising the total time of all trips, it can happen that a single truck gets two trips, while the
other one gets nothing at all. In a simulation context, you should take this into account.
"""
function MTVRP()
    # general problem setup #
    # --------------------- #
    # number of cities
    n = 7
    # pick random locations
    coord_x = rand(n+1)
    coord_y = rand(n+1)
    dist(x_1, x_2, y_1, y_2) = sqrt((x_1 - x_2)^2 + (y_1 - y_2)^2)
    # cost matrix (symmetrical, distances, depot is city 1)
    c = zeros(n+1, n+1)
    for i in 1:n+1
        for j in i+1:n+1
            c[i,j] = dist(coord_x[i], coord_x[j], coord_y[i], coord_y[j])
        end
    end
    c .+= c'
    # demand for each city (number of e.g. number of pallets)
    d = rand(1:10, n+1)
    d[1] = 0 # no demand @ depot
    # truck capacity (equal for all trucks, can easily be extended to different size trucks)
    Q = 20
    V = 2 # number of vehicles
    R = 3 # number of trips/vehicle
    TH = 5 # max trip length => results in "infeasible"
    # minimum number of trucks for order to be feasible:
    @info "$(ceil(Int,sum(d) / Q)) trips required for a solution"
    @info "$(V) trucks used, max $(R) trips per truck, total work time $(TH)"
    # Linear programming setup #
    # ------------------------ #
    model = Model(with_optimizer(GLPK.Optimizer))
    # Add variables
    # x_{i,j,v, r}: edge from i > j by truck v on trip r, Boolean valued
    @variable(model, x[1:n+1, 1:n+1, 1:V, 1:R], Bin)
    # y_{i, v, r}: 1 if trip r of vehicle v visits vertex i, Boolean valued
    @variable(model, y[1:n+1, 1:V, 1:R], Bin)

    # Add objective (slices)
    @objective(model, Min, sum(sum(x[:,:,v,r] .* c) for v in 1:V for r in 1:R)) # (1)

    # Add constraints
    for i in 2:n+1
        @constraint(model, sum(y[i,v,r] for v in 1:V for r in 1:R) == 1) # (2)
    end

    for v in 1:V
        for r in 1:R
            for i in 1:n+1
                @constraint(model, sum(x[i,j,v,r] for j in 1:n+1 if j≠i) == y[i,v,r]) # (3)
                @constraint(model, sum(x[j,i,v,r] for j in 1:n+1 if j≠i) == y[i,v,r]) # (3)
                @constraint(model, sum(x[j,i,v,r] for j in 1:n+1 if j≠i) == sum(x[i,j,v,r] for j in 1:n+1 if j≠i)) # (3)
            end

            @constraint(model, sum(d[i] * y[i,v,r] for i in 2:n+1) <= Q) # (4)
            for subset in powerset(2:n+1, 2, n)
                @constraint(model, sum(x[i,j,v,r] for i in subset for j in subset if i≠j) <= length(subset) - 1 ) # (5)
            end

        end
    end

    for v in 1:V
        @constraint(model, sum(sum(x[:,:,v,r] .* c) for r in 1:R) <= TH) # (6)
    end

    optimize!(model)
    @info "termination status: $(termination_status(model))"
    @info "objective_value $(objective_value(model))"

    #     Nice illustration    #
    # ------------------------ #
    marks = [:circle for _ in 1:n+1]
    marks[1] = :cross
    fig = plot()
    scatter!(fig, coord_x[1:1], coord_y[1:1], marker=:cross, label="Depot", color=:black,markersize=12)
    scatter!(fig, coord_x[2:end], coord_y[2:end], marker=:circle, label="Stores", color=:gray )
    for v in 1:V
        for r in 1:R
            inds = findall(x-> !iszero(x), value.(x[:,:,v,r]))
            for ind in inds
                xx = [coord_x[ind[1]]; coord_x[ind[2]]]
                yy = [coord_y[ind[1]]; coord_y[ind[2]]]
                plot!(fig, xx, yy, color=v, label="", line=r)
            end
            @info "truck $(v), route $(r): $(round(sum(c[inds]), digits=2)) kms"
        end
    end
    title!(fig, "lay-out for $(V) trucks with $(n) cities, max $(R) routes per truck\ncolor= truck, line thickness=route")
    display(fig)
end

MVRP()
#MTVRP()


# ------------------------------------------------------------
# Truncated cosine and its Fourier transform

# Support = [-1/2,1/2]
function tcos(x)
	if 0.0 <= x <= 0.5
		return cos(2*pi*x)
	else
		return 0.0
	end
end

function ftcos(xi)
	if abs(xi) == 1.0
		return 0.25 + 0.0*im
	else
		return im*xi*( 1 + exp(-pi*xi*im) ) / (2*pi*(1 - xi^2))
	end
end

# Overload ftcos to vector input
@vectorize_1arg Float64 ftcos

# ------------------------------------------------------------
# Reconstruct truncated cosine in Haar basis

using GeneralizedSampling

J = 5
M = 2^(J+2)
# Both GeneralizedSampling and Plots (below) have a grid function
xi = GeneralizedSampling.grid(M, 0.5)
f = ftcos(xi)

T = Freq2Wave(xi, "haar", J)
wcoef = T \ f

# ------------------------------------------------------------
# Plot reconstruction

using IntervalWavelets

x, yw = weval( real(wcoef), "haar", 10 )

using Plots

p = plot(x, tcos,
         linecolor=:red,
         linewidth=2,
         label="true"
        )

plot!(p, x, yw,
      linecolor=:black,
      linewidth=2,
      label="approximation"
     )

display(p)


using DifferentialEquations

function sirmodel(du, u, p, t)
    S = u[1]
    I = u[2]
    R = u[3]
    dS = -p[1]*S*I
    dI = p[1]*S*I-p[2]*I
    dR = p[2]*I
    du[1] = dS
    du[2] = dI
    du[3] = dR
end

function solveSIR(N, β, γ)

    tspan = (0.0, 160.0)
    u0 = [N-1.0, 1.0, 0.0]
    p = [β, γ]

    prob = ODEProblem(sirmodel, u0, tspan, p)

    sol = solve(prob)

    return sol
end

function calcEffectiveReproduction(sol)

    v = Array{Float64,1}()
    t = Array{Float64,1}()
    for index in 2:length(sol)
        push!(v, sol[index][2]/sol[index-1][2])
        push!(t, sol.t[index])
    end

    return [v,t]
end


function jaccard_similarity(set1::Array, set2::Array)

    intersec = 0.0
    set1 = unique(set1)
    set2 = unique(set2)

    for i in set1
        for j in set2
            if i == j
                intersec += 1.0
            end
        end
    end
    return intersec / (length(set1) + length(set2) - intersec)
end


"""
Elliptic Curve
==============

References
----------

- https://en.wikipedia.org/wiki/Elliptic_curve
- "An Introduction to the Theory of Elliptic Curves", Joseph H. Silverman
- https://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/
"""
module EllipticCurveCrypto

include("field.jl")

export
    Point, EllipticCurve, is_on_curve,
    ec_zero, ec_add, ec_neg, ec_scalar_mul, ec_scalar_mul_ml

struct Point{T}
    x::T
    y::T
end

# `T` might be mutable (e.g. `BigInt`) and compare using object identity by default.
function Base.:(==)(a::Point{T}, b::Point{T}) where T
    a.x == b.x && a.y == b.y
end

function Base.:(≈)(a::Point{T}, b::Point{T}) where T
    a.x ≈ b.x && a.y ≈ b.y
end

"""
Elliptic curve (with characteristic >= 5) `y ^ 2 = x ^ 3 + a * x + b`.
"""
struct EllipticCurve{T}
    field::Field{T}

    a::T
    b::T

    function EllipticCurve(f::F, a::T, b::T) where {T, F<:Field{T}}
        a = f_from(f, a)
        b = f_from(f, b)
        if f_add(f,
            f_mul(f, f_pow(f, a, 3), f_from(f, T(4))),
            f_mul(f, f_pow(f, b, 2), f_from(f, T(27)))) == f_zero(f)
            error("The curve is singular")
        end
        new{T}(f, a, b)
    end
end

function is_on_curve(ec::EllipticCurve{T}, p::Point{T})::Bool where T
    if p == ec_zero(ec)
        return true
    end

    f = ec.field
    a = ec.a
    b = ec.b
    x = p.x
    y = p.y
    lhs = f_pow(f, y, 2)
    rhs = f_add(f,
        f_add(f, f_pow(f, x, 3), f_mul(f, x, a)),
        b)
    lhs == rhs
end

# Zero, also the infinity.
function ec_zero(ec::EllipticCurve{T})::Point{T} where T
    z = f_zero(ec.field)
    Point(z, z)
end

function ec_add(ec::EllipticCurve{T}, p1::Point{T}, p2::Point{T})::Point{T} where T
    f = ec.field
    z = ec_zero(ec)

    s = if p1 == z
        return p2
    elseif p2 == z
        return p1
    elseif p1.x == p2.x
        if f_add(f, p1.y, p2.y) == f_zero(f)
            return z
        else
            # Doubling
            (x, y) = (p1.x, p1.y)
            numer = f_add(f, f_mul(f, f_from(f, T(3)), f_pow(f, x, 2)), ec.a)
            denom = f_mul(f, f_from(f, T(2)), y)
            f_div(f, numer, denom)
        end
    else
        f_div(f, f_sub(f, p2.y, p1.y), f_sub(f, p2.x, p1.x))
    end

    x = f_sub(f, f_mul(f, s, s), f_add(f, p1.x, p2.x))
    y = f_sub(f, f_mul(f, s, f_sub(f, p1.x, x)), p1.y)
    Point(x, y)
end

function ec_neg(ec::EllipticCurve{T}, p::Point{T})::Point{T} where T
    Point(p.x, f_neg(ec.field, p.y))
end

# Double-and-add algorithm
function ec_scalar_mul(ec::EllipticCurve{T}, p::Point{T}, s::Int)::Point{T} where T
    acc = ec_zero(ec)
    while s != 0
        if s & 1 != 0
            acc = ec_add(ec, acc, p)
        end
        p = ec_add(ec, p, p)
        s >>= 1
    end
    acc
end

# Side-channel attack resistent multiplication.
"Point multiplication using Montgomery ladder."
function ec_scalar_mul_ml(ec::EllipticCurve{T}, p::Point{T}, s::Int)::Point{T} where T
    # p * d = \sum_{i = 0..m} p * (d_i 2^i)          ...0-based
    #       = \sum_{i = 1..m} p * (d_i 2^(i - 1))    ...1-based
    d = digits(s, base=2)
    m = length(d)

    # Invariants:
    #
    #     r0 = p * t0 = p * (s >> (i - 1))
    #     r1 = p * t1 = p * (t0 + 1)
    r0 = ec_zero(ec)
    r1 = p
    for i in m:-1:1
        if d[i] == 0
            r1 = ec_add(ec, r0, r1)  # t0 + (t0 + 1) => (2 * t0) + 1
            r0 = ec_add(ec, r0, r0)  # 2 * t0
        else
            r0 = ec_add(ec, r0, r1)  # t0 + (t0 + 1) => 2 * t0 + 1
            r1 = ec_add(ec, r1, r1)  # 2 * (t0 + 1) => (2 * t0 + 1) + 1
        end
    end
    r0
end

end # module


function tensorproduct(x::Array{Float64,2},porder::Int)::Tuple{Array{Float64,2},Array{Float64,2},Array{Float64,2},Array{Float64,2}}
# #TENSORPRODUCT Vandermonde matrices for tensor product polynomials in [0,1]^d
# #
# #   [F,FX,FY,FZ] = TENSORPRODUCT(X,P)
# #
# #      X:         Coordinates of the points wherethe polynomials
# #                 are to be evaluated (npoints,dim)
# #      PORDER:    Maximum order of the polynomials consider. That
# #                 is all polynomials of complete degree up to p,
# #                 npoly = (PORDER+1)*(PORDER+1)*(PORDER+1)
# #      F:         Vandermonde matrix (npoints,npoly)
# #      FX:        Vandermonde matrix for the derivative of the Koornwinder
# #                 polynomials w.r.t. x (npoints,npoly)
# #      FY:        Vandermonde matrix for the derivative of the Koornwinder
# #                 polynomials w.r.t. y (npoints,npoly)
# #      FZ:        Vandermonde matrix for the derivative of the Koornwinder
# #                 polynomials w.r.t. z (npoints,npoly)
# #

n,dim = size(x);

if dim== 1 # 1D
        f,fx = legendrepolynomial(x,porder);  # Legendre basis
        fy=[0.0 0.0];
        fz=[0.0 0.0];
elseif dim == 2 # 2D
        g1,gx = legendrepolynomial(x[:,1],porder); # Legendre basis in x direction
        g2,gy = legendrepolynomial(x[:,2],porder); # Legendre basis in y direction
        f  = zeros(Float64,n,(porder+1)*(porder+1));
        fx = 0*f;
        fy = 0*f;
        fz=[0.0 0.0];
        # perform tensor product to obtain the shape functions and their
        # derivatives on the unit square
        for ii=1:n
            tm = kron(g2[ii,:],g1[ii,:]);
            f[ii,:] =  kron(g2[ii,:],g1[ii,:]);
            fx[ii,:] = kron(g2[ii,:],gx[ii,:]);
            fy[ii,:] = kron(gy[ii,:],g1[ii,:]);
        end
elseif dim==3
        g1,gx=legendrepolynomial(x[:,1],porder); # Legendre basis in x direction
        g2,gy=legendrepolynomial(x[:,2],porder); # Legendre basis in y direction
        g3,gz=legendrepolynomial(x[:,3],porder); # Legendre basis in z direction
        f  = zeros(Float64,n,(porder+1)*(porder+1)*(porder+1));
        fx = 0*f;
        fy = 0*f;
        fz = 0*f;
        # perform tensor product to obtain the shape functions and their
        # derivatives on the unit cube
        for ii=1:n
            f[ii,:] =  kron(g3[ii,:],kron(g2[ii,:],g1[ii,:]));
            fx[ii,:] = kron(g3[ii,:],kron(g2[ii,:],gx[ii,:]));
            fy[ii,:] = kron(g3[ii,:],kron(gy[ii,:],g1[ii,:]));
            fz[ii,:] = kron(gz[ii,:],kron(g2[ii,:],g1[ii,:]));
        end
else
        error("Only can handle dim=1, dim=2 or dim=3");
end

return f, fx, fy, fz;

end


include("getdigit_n.jl")
include("myprintf.jl")
using Printf
using .GetDigit_n
using .MyPrintf

const DIGIT = 100
const MAXN = 100

function factorial(m)
    return m < 2 ? BigFloat(1) : reduce(*, [BigFloat(i) for i in 2:m])
end

function ramanujan(n)
    res = BigFloat(0)
    for k = 0:n
        numerator = factorial(4 * k) * (BigFloat(1103) + BigFloat(26390 * k))
        denominator = (factorial(k) * (BigFloat(396))^(BigFloat(k)))^(BigFloat(4))

        res += numerator / denominator
    end

    return BigFloat(9801) / (res * BigFloat(2) * sqrt(BigFloat(2)))
end

function main()
    n = GetDigit_n.getn(ramanujan, MAXN, DIGIT, true)
    precision = GetDigit_n.getdigit(ramanujan, n, DIGIT, true)
    precision = GetDigit_n.getdigit_2(ramanujan, n, DIGIT, precision)

    setprecision(precision)
    MyPrintf.myprintf("π（計算値） = ", DIGIT, ramanujan(n))
    MyPrintf.myprintf("π（厳密値） = ", DIGIT, BigFloat(π))
    @printf("n = %d, 計算%d桁で、%d桁求まりました\n", n, precision, DIGIT)
end

main()


export ScaledBeta, TrGeometric, TrBinomial, StringCategorical, likelihood, log_likelihood
export normalising_const
## Scaled beta distribution
"""
Scaled Beta distribution. Instantiate with ScaledBeta() e.g.
```
d = ScaledBeta(α, β, 0.0, 10.0)
```
would make a ScaledBeta type d which is bounded by 0.0 and 10.0. Note
- (α,β) are usual parameters;
- lb = lower bound;
- ub = upper bound.
"""
struct ScaledBeta<:ContinuousUnivariateDistribution
    α::Float64
    β::Float64
    lb::Float64
    ub::Float64
end

Distributions.rand(d::ScaledBeta) = d.lb + (d.ub-d.lb) * rand(Beta(d.α, d.β))
Distributions.mean(d::ScaledBeta) = d.lb + (d.ub-d.lb) * ( d.α / (d.α + d.β) )
Distributions.pdf(d::ScaledBeta, x::Real) = (d.lb ≤ x ≤ d.ub) ? pdf(Beta(d.α, d.β), (x - d.lb)/(d.ub-d.lb))/(d.ub-d.lb) : 0
Distributions.minimum(d::ScaledBeta) = d.lb
Distributions.maximum(d::ScaledBeta) = d.ub

## Trucated geometric
"""
`TrGeomtric(p::Float64, lb::Int, ub::Int)`

Truncated Geometric distribution. Note:
- lb = lower bound = samllest possible value (default=0)
- ub = upper bound = largest possible value.
Instantiation:

1. `TrGeometric(p, ub)` -  here `lb` defaults to 0
2. `TrGeometric(p, lb, ub)` here `lb` is specified

"""
struct TrGeometric<:DiscreteUnivariateDistribution
    p::Float64 # Prob param
    lb::Int # Lower bound (smallest value)
    ub::Int  # Upper bound
end

function normalising_const(d::TrGeometric)
    return (1 - (1-d.p)^(d.ub-d.lb+1))
end

TrGeometric(p::Float64, ub::Int) = TrGeometric(p, 0, ub)

function Distributions.pdf(d::TrGeometric, x::Integer)
    _insupport = insupport(d, x)
    return _insupport ? d.p*(1-d.p)^(x-d.lb) / normalising_const(d) : 0.0
end

function Distributions.logpdf(d::TrGeometric, x::Integer)
    _insupport = insupport(d, x)
    return _insupport ? log(d.p) + (x-d.lb) * log(1-d.p) -  log(normalising_const(d)) : -Inf
end

function Distributions.cdf(d::TrGeometric, x::Number)
    if d.lb ≤ x ≤ d.ub
        return ( 1 - (1 - d.p)^(trunc(Int,x)-d.lb+1) )/normalising_const(d)
    else
        return float(x > d.ub)
    end
end

# Here I make use of closed form of generalised inverse of the TrGeom dist.
# Not sure if this is the fastest implementation
function Distributions.quantile(d::TrGeometric, q::Real)
    return 0.0<q<1.0 ? ceil(Int, log(1-q*normalising_const(d))/log(1-d.p) - 1 )+d.lb : d.ub*(p≥1.0) + d.lb*(q≤0.0)
end

Distributions.minimum(d::TrGeometric) = d.lb
Distributions.maximum(d::TrGeometric) = d.ub

#Truncated Binomial wrappper - solves the problem of when we have n=0

struct TrBinomial <: DiscreteUnivariateDistribution
    n::Int
    p::Real
    lb::Int
    ub::Int
    dist::Distribution
    function TrBinomial(n::Int, p::Real, lb::Int, ub::Int)
        @assert (lb ≤ ub) "Lower bound must be ≤ upper bound."
        if lb < ub
            tmp_dist = Truncated(Binomial(n, p), lb, ub)
        else
            tmp_dist = DiscreteUniform(lb, ub)
        end
        new(n, p, lb, ub, tmp_dist)
    end
end

function Distributions.rand(d::TrBinomial)
    return rand(d.dist)
end
function Distributions.rand(d::TrBinomial, n::Int)
    return rand(d.dist, n)
end

struct StringCategorical <: DiscreteUnivariateDistribution
    x::Vector{String}
    p::Vector{T} where T<:Real
    latent_dist::Distribution
    probability_map::Dict{String, Float64}
    function StringCategorical(x::Vector{String}, p::Vector{T} where T<: Real)
        latent_dist = Categorical(p)
        probability_map = Dict(val => prob for (val, prob) in zip(x, p))
        new(x, p, latent_dist, probability_map)
    end
end

Distributions.pdf(d::StringCategorical, val::String) = d.probability_map[val]
function likelihood(d::StringCategorical, val::Vector{String})
    z = 1.0
    for s in val
        z *= pdf(d, s)
    end
    return z
end

Distributions.logpdf(d::StringCategorical, val::String) = log(d.probability_map[val])
function log_likelihood(d::StringCategorical, val::Vector{String})
    z = 0.0
    for s in val
        z += logpdf(d, s)
    end
    return z
end

function Distributions.rand(d::StringCategorical)
    return d.x[rand(d.latent_dist)]
end

function Distributions.rand(d::StringCategorical, n::Int)
    return d.x[rand(d.latent_dist, n)]
end


using SymPy

@syms px1 py1 lambda1 vx1 vy1 px2 py2 lambda2 vx2 vy2

lx1 = px1 + lambda1*vx1
ly1 = py1 + lambda1*vy1

lx2 = px2 + lambda2*vx2
ly2 = py2 + lambda2*vy2

lx12 = solve(lx1-lx2, lambda2)[1]
l2i = subs(ly2, lambda2, lx12)

sol = solve(ly1-l2i, lambda1)[1]

xi = simplify(subs(lx1, lambda1, sol))
yi = simplify(subs(ly1, lambda1, sol))


abstract type PrimeField <: Number end

infield(x::Number,y::Number) = x >= 0 && x < y

"Represents FieldElement type in which 𝑛 ∈ 𝐹𝑝 and 𝑝 ∈ ℙ"
struct FieldElement <: PrimeField
    𝑛::Integer
    𝑝::Integer
    FieldElement(𝑛,𝑝) = !infield(𝑛,𝑝) ? throw(DomainError("𝑛 is not in field range")) : new(𝑛,𝑝)
end

"Formats PrimeField as 𝑛 : 𝐹ₚ"
function show(io::IO, z::PrimeField)
    print(io, z.𝑛, " : 𝐹", z.𝑝)
end

"Returns true if both 𝑛 and 𝑝 are the same"
==(𝑋₁::PrimeField,𝑋₂::PrimeField) = 𝑋₁.𝑝 == 𝑋₂.𝑝 && 𝑋₁.𝑛 == 𝑋₂.𝑛
==(::PrimeField,::Integer) = false

"Adds two numbers of the same field"
function +(𝑋₁::PrimeField,𝑋₂::PrimeField)
    if 𝑋₁.𝑝 != 𝑋₂.𝑝
        throw(DomainError("Cannot operate on two numbers in different Fields"))
    else
        𝑛 = mod(𝑋₁.𝑛 + 𝑋₂.𝑛, 𝑋₁.𝑝)
        return typeof(𝑋₁)(𝑛, 𝑋₁.𝑝)
    end
end

"Substracts two numbers of the same field"
function -(𝑋₁::PrimeField,𝑋₂::PrimeField)
    if 𝑋₁.𝑝 != 𝑋₂.𝑝
        throw(DomainError("Cannot operate on two numbers in different Fields"))
    else
        𝑛 = mod(𝑋₁.𝑛 - 𝑋₂.𝑛, 𝑋₁.𝑝)
        return typeof(𝑋₁)(𝑛, 𝑋₁.𝑝)
    end
end

"Multiplies two numbers of the same field"
function *(𝑋₁::PrimeField,𝑋₂::PrimeField)
    if 𝑋₁.𝑝 != 𝑋₂.𝑝
        throw(DomainError("Cannot operate on two numbers in different Fields"))
    else
        𝑛 = mod(𝑋₁.𝑛 * 𝑋₂.𝑛, 𝑋₁.𝑝)
        return typeof(𝑋₁)(𝑛, 𝑋₁.𝑝)
    end
end

"Multiplies a PrimeField by an Integer"
function *(𝑐::Integer,𝑋::PrimeField)
    𝑛 = mod(𝑐 * 𝑋.𝑛, 𝑋.𝑝)
    return typeof(𝑋)(𝑛, 𝑋.𝑝)
end

"Returns 𝑋ᵏ using Fermat's Little Theorem"
function ^(𝑋::PrimeField,𝑘::Int)
    𝑛 = powermod(𝑋.𝑛, mod(𝑘, (𝑋.𝑝 - 1)), 𝑋.𝑝)
    return typeof(𝑋)(𝑛, 𝑋.𝑝)
end

"Returns 1/𝑋 as a special case of exponentiation where 𝑘 = -1"
function inv(𝑋::PrimeField)
    𝑛 = powermod(𝑋.𝑛, mod(-1, (𝑋.𝑝 - 1)), 𝑋.𝑝)
    return typeof(𝑋)(𝑛, 𝑋.𝑝)
end

function div(𝑋₁::PrimeField,𝑋₂::PrimeField)
    return 𝑋₁ / 𝑋₂
end

"Returns 𝑋₁/𝑋₂ using Fermat's Little Theorem"
function /(𝑋₁::PrimeField,𝑋₂::PrimeField)
    if 𝑋₁.𝑝 != 𝑋₂.𝑝
        throw(DomainError("Cannot operate on two numbers in different Fields"))
    else
        𝑛 = mod(𝑋₁.𝑛 * powermod(𝑋₂.𝑛, 𝑋₁.𝑝 - 2, 𝑋₁.𝑝), 𝑋₁.𝑝)
        return typeof(𝑋₁)(𝑛, 𝑋₁.𝑝)
    end
end


# p28b.jl - eigenmodes of Laplacian on the disk

# r coordinate, ranging from -1 to 1 (N must be odd)
N = 25; N2 = Int((N-1)/2);
(D,r) = cheb(N); D2 = D^2;
D1 = D2[2:N2+1,2:N2+1]; D2 = D2[2:N2+1,N:-1:N2+2];
E1 =  D[2:N2+1,2:N2+1]; E2 =  D[2:N2+1,N:-1:N2+2];

# t = theta coordinate, ranging from 0 to 2*pi (M must be even):
M = 20; dt = 2*pi/M; t = dt*(1:M); M2 = Int(M/2);
D2t = toeplitz([-pi^2/(3*dt^2)-1/6; @. .5*(-1)^(2:M)/sin(dt*(1:M-1)/2)^2]);

# Laplacian in polar coordinates:
R = diagm(1./r[2:N2+1]);
Z = zeros(M2,M2); I = eye(M2);
L = kron(D1+R*E1,eye(M)) + kron(D2+R*E2,[Z I;I Z]) + kron(R^2,D2t);

# Compute 25 eigenmodes:
index = 1:25;
(Lam,V) = eig(-L); ii = sortperm(abs.(Lam))[index];
Lam = Lam[ii]; V = V[:,ii];
Lam = sqrt.(real(Lam/Lam[1]));

# Plot nodal lines:
(rr,tt) = (r[1:N2+1],[0;t]);
(xx,yy) = @. (cos(tt)*rr',sin(tt)*rr');
z = exp.(1im*pi*(-100:100)/100);  clf();
for i = 1:25
    subplot(5,5,i);
    u = reshape(real(V[:,i]),M,N2);
    u = [zeros(M+1) u[[M;1:M],:]];
    u = u/norm(u[:],Inf);
    plot(real(z),imag(z));
    xlim(-1.07,1.07); ylim(-1.07,1.07); axis("off"); axis("equal");
    contour(xx,yy,u,levels=[0]);
    title("$(signif(Lam[i],5))",fontsize=8);
end


#=
Starting with 1 and spiralling anticlockwise in the following way, a square spiral with side length 7 is formed.

37 36 35 34 33 32 31
38 17 16 15 14 13 30
39 18  5  4  3 12 29
40 19  6  1  2 11 28
41 20  7  8  9 10 27
42 21 22 23 24 25 26
43 44 45 46 47 48 49

It is interesting to note that the odd squares lie along the bottom right diagonal, but what is more interesting is that 8 out of the 13 numbers lying along both diagonals are prime; that is, a ratio of 8/13 ≈ 62%.

If one complete new layer is wrapped around the spiral above, a square spiral with side length 9 will be formed. If this process is continued, what is the side length of the square spiral for which the ratio of primes along both diagonals first falls below 10%?
=#
using Primes

function calc()
  start, step = 1,2
  iterations, currentsquaresize = 3,3
  cprimes, call = 0,1
  while true
    start += step
    for i in start:step:(start+(iterations*step))
      if isprime(i)
        cprimes = cprimes + 1
      end
      call = call + 1
    end
    ratio = cprimes / call
    if ratio < 0.1
      return currentsquaresize
    end
    start += (iterations*step)
    currentsquaresize += 2
    step += 2
  end
end
@time println(calc())


"""
    sphericalhankel2(nu, x)

Spherical Hankel function of the second kind, order `nu`
and argument `x`.
"""
function sphericalhankel2(nu, x)
    jn = SpecialFunctions.sphericalbesselj(nu, x)
    yn = SpecialFunctions.sphericalbessely(nu, x)
    return jn - im*yn
end

"""
    sphericalhankel1(nu, x)

Spherical Hankel function of the first kind, order `nu`
and argument `x`.
"""
function sphericalhankel1(nu, x)
    return conj(sphericalhankel2(nu, x))
end

"""
    sphericalhankel2_and_derivatives(nu::UnitRange, x)
    sphericalhankel2_and_derivatives(nu::Number, x)

Evaluates the spherical Hankel function of the second kind, its first and
second derivative, for orders `nu::UnitRange` and argument `x`.
"""
function sphericalhankel2_and_derivatives(nu::UnitRange, x)
    start = nu.start
    stop = nu.stop
    len = stop-start+1   # number of terms to compute
    sph = sphericalhankel2.(start:stop+2, x)  # compute two more terms
    # compute first derivative using recurrence
    # h_n'(x) = -h_{n+1}(x) + n/x*h_n(z)
    sph_d = similar(sph, len+1)   # compute one more term
    n = stop+1   # order
    for index in len+1:-1:1
        sph_d[index] = -sph[index+1] + n/x*sph[index]
        n -= 1
    end
    # compute second derivative using recurrence
    # h_n''(x) = n / x^2 * (x * h_n'(x) - h_n(x)) - h_{n+1}'(x)
    sph_dd = similar(sph, len)
    n = stop   # order
    for index in len:-1:1
        sph_dd[index] = n / x^2 * (x*sph_d[index] - sph[index]) - sph_d[index+1]
        n -= 1
    end
    return sph[1:len], sph_d[1:len], sph_dd
end
function sphericalhankel2_and_derivatives(nu::Number, x)
    sph, sph_d, sph_dd = sphericalhankel2_and_derivatives(nu:nu, x)
    return first(sph), first(sph_d), first(sph_dd)
end

"""
    sphericalhankel1_and_derivatives(nu::UnitRange, x)
    sphericalhankel1_and_derivatives(nu::Number, x)

Evaluates the spherical Hankel function of the first kind, its first and
second derivative, for orders `nu` and argument `x`.
"""
function sphericalhankel1_and_derivatives(nu, x)
    return conj.(sphericalhankel2_and_derivatives(nu, x))
end

"""
    riccatihankel2_and_derivatives(nu::UnitRange, x)

Evaluates the spherical Riccati-Bessel Hankel function of the second kind, its first and
second derivative, for orders `nu::UnitRange` and argument `x`.
"""
function riccatihankel2_and_derivatives(nu::UnitRange, x)
    sph, sph_d, sph_dd = sphericalhankel2_and_derivatives(nu, x)
    rh = x.*sph
    rh_d = sph + x.*sph_d
    rh_dd = 2*sph_d + x.*sph_dd
    return rh, rh_d, rh_dd
end


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

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ d2e14b19-98d7-4fba-8bd9-bbbb47e002f6
begin
	bigbreak = HTML("<br>"^4);
	using PlutoUI
	using Test
end

# ╔═╡ ce3c8fde-802a-11ec-1b1a-31be58ae3474
md"""
# Conditionals
with the if keyword
In Julia, the syntax

```julia
if *condition 1*
    *option 1*
elseif *condition 2*
    *option 2*
else
    *option 3*
end
```
allows us to conditionally evaluate one of our options.

For example, we might want to implement the FizzBuzz test:
given a number, N, print
-  "Fizz" if N is divisible by 3,
-  "Buzz" if N is divisible by 5, and
-  "FizzBuzz" if N is divisible by 3 and 5.

Otherwise just print the number itself! Enter your choice for N here:
"""

# ╔═╡ fd2ee11d-1734-457b-b587-4128b474b772
@bind N PlutoUI.Slider(1:20, show_value = true)

# ╔═╡ 7b619e85-abb8-4115-a9df-b5fedd04a8e4
md"N = $N"

# ╔═╡ da9796ce-4572-4d2d-bb96-5949f52d46d0
with_terminal() do

	if (N % 3 == 0) && (N % 5 == 0)
		# `&&` means "AND"; % computes the remainder after division
	    println("FizzBuzz")
	elseif N % 3 == 0
	    println("Fizz")
	elseif N % 5 == 0
	    println("Buzz")
	else
	    println(N)
	end

end

# ╔═╡ aca5d158-2902-4b7a-bf50-6f7a722f5ea7
md"""
with ternary operators
For this last block, we could instead use the ternary operator with the syntax
```julia
a ? b : c
```
which equates to
```julia
if a
    b
else
    c
end
```
"""

# ╔═╡ 8a3c8101-0c2a-438f-ba58-b80e9b14bc10
md"
Now let's say we want to return the larger of two numbers. Give x and y values here: "

# ╔═╡ 5732d2dc-c411-4c56-9720-ce6d979fc60f
x = 10

# ╔═╡ d993ffa0-c613-4b4b-baeb-a223ce408217
y = 30

# ╔═╡ 782a5034-9184-4a55-b877-bd0bb4e4aebb
md"Using the if and else keywords, we might write:
"

# ╔═╡ 14d69119-9a68-428b-a857-56298b42aee5
if x > y
    x
else
    y
end

# ╔═╡ 46533172-0fe1-406b-a52d-23305282d43b
md"and as a ternary operator, the conditional looks like this:"

# ╔═╡ b13c9cc4-3c4e-4416-896f-d5cbdc2d51c0
(x > y) ? x : y

# ╔═╡ 8b81523f-79dc-46ba-8e4e-5dbc19d5d434
true ? 5 : 2 * 6

# ╔═╡ 7925dfa8-63b1-4fc2-beae-922fa93aa081
( false  ?  1  : 2 )

# ╔═╡ fdfb1f2e-6105-43dd-92cf-4b78e1db8e73
bigbreak

# ╔═╡ 61aa9056-ebfe-4c06-a752-d081ece68167
md"""
## Exercises

5.1

Write a conditional statement that prints "negative", "zero" or "odd" according to the input number `N`.

"""

# ╔═╡ 75efd803-be55-4d0f-b8c8-6b63dd90d68c
md"""M = $(@bind M PlutoUI.Slider(-100:100, show_value = true)) """

# ╔═╡ 8f1298ab-309f-4d4d-8165-4ee522f3b1c8
with_terminal() do
	# modify this cell to return "negative", "zero" and "positive".
	println(M)
end

# ╔═╡ 602b3c77-e46e-4592-a787-4aeed77eb059
bigbreak

# ╔═╡ c7c1ec65-49ed-4ae9-b9a9-0d82a346af81
md"""
5.2

Use the ternary operator to construct a reasonable answer from Bob in the dialog below.
"""

# ╔═╡ 283c45af-47a3-4446-b873-205d8aa568e7
md"""
**Alice:** _Is the number $M even?_

**Bob:**   _I don't know._
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
PlutoUI = "~0.7.32"
"""

# ╔═╡ 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.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.4"

[[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.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

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

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

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

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"

[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

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

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[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.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[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.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

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

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "0b5cfbb704034b5b4c1869e36634438a047df065"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.1"

[[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.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "ae6145ca68947569058866e443df69587acc1806"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.32"

[[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.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

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

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

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

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

[[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:
# ╟─d2e14b19-98d7-4fba-8bd9-bbbb47e002f6
# ╟─ce3c8fde-802a-11ec-1b1a-31be58ae3474
# ╟─fd2ee11d-1734-457b-b587-4128b474b772
# ╟─7b619e85-abb8-4115-a9df-b5fedd04a8e4
# ╠═da9796ce-4572-4d2d-bb96-5949f52d46d0
# ╟─aca5d158-2902-4b7a-bf50-6f7a722f5ea7
# ╟─8a3c8101-0c2a-438f-ba58-b80e9b14bc10
# ╠═5732d2dc-c411-4c56-9720-ce6d979fc60f
# ╠═d993ffa0-c613-4b4b-baeb-a223ce408217
# ╟─782a5034-9184-4a55-b877-bd0bb4e4aebb
# ╠═14d69119-9a68-428b-a857-56298b42aee5
# ╟─46533172-0fe1-406b-a52d-23305282d43b
# ╠═b13c9cc4-3c4e-4416-896f-d5cbdc2d51c0
# ╠═8b81523f-79dc-46ba-8e4e-5dbc19d5d434
# ╠═7925dfa8-63b1-4fc2-beae-922fa93aa081
# ╟─fdfb1f2e-6105-43dd-92cf-4b78e1db8e73
# ╟─61aa9056-ebfe-4c06-a752-d081ece68167
# ╟─75efd803-be55-4d0f-b8c8-6b63dd90d68c
# ╠═8f1298ab-309f-4d4d-8165-4ee522f3b1c8
# ╟─602b3c77-e46e-4592-a787-4aeed77eb059
# ╟─c7c1ec65-49ed-4ae9-b9a9-0d82a346af81
# ╠═283c45af-47a3-4446-b873-205d8aa568e7
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.7.0-beta3
#     language: julia
#     name: julia-1.7
# ---

# %%
using Random, Statistics, Plots

function diff!(X)
    @inbounds for i in reverse(keys(X)[begin:end-1])
        X[i+1] -= X[i]
    end
    X
end

function randsimplex!(rng::AbstractRNG, X)
    X[end] = one(eltype(X))
    Y = @view X[begin:end-1]
    rand!(rng, Y)
    sort!(Y)
    X .*= length(X)
    diff!(X)
end
randsimplex(rng::AbstractRNG, N, T=Float64) = randsimplex!(rng, Vector{T}(undef, N))
randsimplex!(X) = randsimplex!(Random.default_rng(), X)
randsimplex(N, T=Float64) = randsimplex!(Vector{T}(undef, N))

n = 10^4
A = randsimplex(n)
@show mean(A), var(A)
histogram(A; norm=true, alpha=0.3, label="randsimplex($n)")
plot!(x->exp(-x), 0, 6; xlim=(-0.1, 6), lw=2, label="Exponential(1) dist")

# %%
struct Simplex{T} N::Int end
Simplex(N) = Simplex{Float64}(N)

function Random.rand!(rng::AbstractRNG,
        X::AbstractVector{T}, d::Random.SamplerTrivial{Simplex{T}}) where T
    @assert length(X) == d[].N
    randsimplex!(rng, X)
end
Base.rand(rng::AbstractRNG, d::Random.SamplerTrivial{Simplex{T}}) where T =
    rand!(rng, Vector{T}(undef, d[].N), d)

# %%
using BenchmarkTools

X = zeros(10)
d = Simplex(10)
@btime rand!($X, $d)

# %%
rand(Simplex(10))

# %%
using Random, LinearAlgebra, Statistics, Plots

function randsphere!(rng::AbstractRNG, X)
    randn!(X)
    X .*= √(length(X)) / norm(X)
end
randsphere(rng::AbstractRNG, N, T=Float64) = randsphere!(rng, Vector{T}(undef, N))
randsphere!(X) = randsphere!(Random.default_rng(), X)
randsphere(N, T=Float64) = randsphere!(Vector{T}(undef, N))

n = 10^4
A = randsphere(n)
@show mean(A), var(A)
histogram(A; norm=true, alpha=0.3, xlim=(-5, 5), label="randsphere($n)")
plot!(x->exp(-x^2/2)/√(2π), -5, 5; lw=2, label="standard normsl dist")

# %%


using OrdinaryDiffEq, ParameterizedFunctions, ODE, ODEInterfaceDiffEq, LSODA,
      Sundials, DiffEqDevTools

f = @ode_def LotkaVolterra begin
  dx = a*x - b*x*y
  dy = -c*y + d*x*y
end a b c d

p = [1.5,1.0,3.0,1.0]
prob = ODEProblem(f,[1.0;1.0],(0.0,10.0),p)

abstols = 1.0 ./ 10.0 .^ (6:13)
reltols = 1.0 ./ 10.0 .^ (3:10);
sol = solve(prob,Vern7(),abstol=1/10^14,reltol=1/10^14)
test_sol = TestSolution(sol)
using Plots; gr()

plot(sol)

setups = [Dict(:alg=>DP5())
          #Dict(:alg=>ode45()) # fail
          Dict(:alg=>dopri5())
          Dict(:alg=>Tsit5())
          Dict(:alg=>Vern6())
]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,save_everystep=false,maxiters=10000,numruns=100)
plot(wp)

setups = [Dict(:alg=>DP5())
          #Dict(:alg=>ode45())
          Dict(:alg=>Tsit5())
          Dict(:alg=>Vern6())
]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,maxiters=10000,error_estimate=:L2,dense_errors=true,numruns=100)
plot(wp)

setups = [Dict(:alg=>DP8())
          #Dict(:alg=>ode78()) # fails
          Dict(:alg=>Vern7())
          Dict(:alg=>Vern8())
          Dict(:alg=>dop853())
          Dict(:alg=>Vern6())
]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,save_everystep=false,maxiters=1000,numruns=100)
plot(wp)

setups = [Dict(:alg=>odex())
          Dict(:alg=>ddeabm())
          Dict(:alg=>Vern7())
          Dict(:alg=>Vern8())
          Dict(:alg=>CVODE_Adams())
          #Dict(:alg=>lsoda())
          Dict(:alg=>Vern6())
          Dict(:alg=>ARKODE(Sundials.Explicit(),order=6))
          ]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,save_everystep=false,maxiters=1000,numruns=100)
plot(wp)

setups = [Dict(:alg=>DP8())
          #Dict(:alg=>ode78())
          Dict(:alg=>Vern7())
          Dict(:alg=>Vern8())
          Dict(:alg=>Vern6())
]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,dense=true,maxiters=1000,error_estimate=:L2,numruns=100)
plot(wp)

using DiffEqBenchmarks
DiffEqBenchmarks.bench_footer(WEAVE_ARGS[:folder],WEAVE_ARGS[:file])


using StanSample, Distributions

N = 100;
df = DataFrame(
    height = rand(Normal(10, 2), N),
    leg_prop = rand(Uniform(0.4, 0.5), N),
);
df.leg_left = df.leg_prop .* df.height + rand(Normal(0, 0.02), N);
df.leg_right = df.leg_prop .* df.height + rand(Normal(0, 0.03), N);

stan6_1 = "
data {
  int <lower=1> N;
  vector[N] H;
  vector[N] LL;
  vector[N] LR;
}
parameters {
  real a;
  vector[2] b;
  real <lower=0> sigma;
}
model {
  vector[N] mu;
  mu = a + b[1] * LL + b[2] * LR;
  a ~ normal(10, 100);
  b ~ normal(2, 10);
  sigma ~ exponential(1);
  H ~ normal(mu, sigma);
}
";

m6_1s = SampleModel("m6.1s", stan6_1);
data = (H = df.height, LL = df.leg_left, LR = df.leg_right, N = size(df, 1));

rc6_1s = stan_sample(m6_1s; data);
if success(rc6_1s)
    nt6_1s = read_samples(m6_1s)
end

nt6_1s.b |> display
println()
mean(nt6_1s.b, dims=2) |> display

init = (a = 2.0, b = [1.0, 2.0], sigma = 1.0)
rc6_2s = stan_sample(m6_1s; data, init);
if success(rc6_1s)
    nt6_2s = read_samples(m6_1s)
end

nt6_2s.b |> display
println()
mean(nt6_2s.b, dims=2) |> display
println()

read_summary(m6_1s) |> display
println()

post6_1s_df = read_samples(m6_1s; output_format=:dataframe) |> display
println()

part6_1s = read_samples(m6_1s; output_format=:particles) |> display


# # Bound on Global Extremum

#md # [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/generated/Polynomial Optimization/bound_on_blobal_extremum.ipynb)
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/generated/Polynomial Optimization/bound_on_global_extremum.ipynb)
# **Adapted from**: SOSTOOLS' SOSDEMO3 (See Section 4.3 of [SOSTOOLS User's Manual](http://sysos.eng.ox.ac.uk/sostools/sostools.pdf))

using Test #src
using DynamicPolynomials
@polyvar x1 x2

# The Goldstein-Price function $f(x)$ is defined as follows:

f1 = x1 + x2 + 1
f2 = 19 - 14x1 + 3x1^2 - 14x2 + 6x1*x2 + 3x2^2
f3 = 2x1 - 3x2
f4 = 18 - 32x1 + 12x1^2 + 48x2 - 36x1*x2 + 27x2^2
f = (1 + f1^2 * f2) * (30 + f3^2 * f4)

# We need to pick an SDP solver, see [here](https://jump.dev/JuMP.jl/v0.21.6/installation/#Supported-solvers) for a list of the available choices.
# We use `SOSModel` instead of `Model` to be able to use the `>=` syntax for Sum-of-Squares constraints.

using SumOfSquares
using CSDP
solver = optimizer_with_attributes(CSDP.Optimizer, MOI.Silent() => true)
model = SOSModel(solver);

# We create the decision variable $\gamma$ that will be the lower bound to the Goldstein-Price function.
# We maximize it to have the highest possible lower bound.

@variable(model, γ)
@objective(model, Max, γ)

# We constrain $\gamma$ to be a lower bound with the following constraint
# that ensures that $f(x_1, x_2) \ge \gamma$ for all $x_1, x_2$.

@constraint(model, f >= γ)

JuMP.optimize!(model)

# We verify that the solver has found a feasible solution:

JuMP.primal_status(model)
@test JuMP.primal_status(model) == MOI.FEASIBLE_POINT || JuMP.primal_status(model) == MOI.NEARLY_FEASIBLE_POINT #src

# We can now obtain the lower bound either with `value(γ)` or `objective_value(model)`:

objective_value(model)
@test objective_value(model) ≈ 3 rtol=1e-2 #src


using DynamicAxisWarping, DSP, LPVSpectral, Distances, SpectralDistances

# Create two signals
N = 48_000
g(x,N) = exp(-10*(x-N/2)^2/N^2)
t = 1:N
f = range(0.01, stop=1, length=N)
y = sin.(t .* f) .* g.(t, N)
y1 = [y; 0y] .+ 0.1 .* randn.()
y2 = [0y; y] .+ 0.1 .* randn.()

plot([y1 y2])
#
M1,M2 = melspectrogram.((y1,y2), 2048)
plot(plot(M1), plot(M2))
# Calculate the Dynamic Time-Warping cost between them (I do this in √ domain since that appears to produce the nicest looking plots)
@btime dtw(.√(M1.power), .√(M2.power))[1] evals=1 samples=5
# Calculating this distance is rather fast.
# Visualize the coupling matrix. The green line should cross diagonally (since the two signals are the same) starting halfway along the axes (since one signal is shifted by 50% of the length copared to the other).
dtwplot(.√(M1.power), .√(M2.power), linecolor=:green)
#

# The code above used the squared Euclidean distance between spectra for each time point. We can replace that distance with a transport-based distance instead.
# We thus combine dynamic time warping with a transport-based cost along the frequency axis
n,m = size(M1.power)
dist = DiscreteGridTransportDistance(Cityblock(), n, n)
dtw(.√(M1.power), .√(M2.power), dist)[1]
#
dtwplot(.√(M1.power), .√(M2.power), dist, linecolor=:green)
# We now see that the valley through which the path can move is slightly wider. This happens since there is now an additional degree of freedom due to transport along the frequency axis. Since there is no need for that transport in this case, the resulting path remains almost the same.
# We can create the need for some transport along the frequency axis

f1 = range(0.01, stop=1, length=N)
f2 = range(0.1, stop=1, length=N).^2
y10 = sin.(t .* f1) .* g.(t, N)
y20 = sin.(t .* f2) .* g.(t, N)
y1 = [y10; 0y10] .+ 0.1 .* randn.()
y2 = [0y20; y20] .+ 0.1 .* randn.()
M1,M2 = melspectrogram.((y1,y2), 2048)
plot(plot(M1), plot(M2))
# We now redo the same stuff as above
dtw(.√(M1.power), .√(M2.power))[1]
#
dtwplot(.√(M1.power), .√(M2.power), linecolor=:green)
#
dtw(.√(M1.power), .√(M2.power), dist)[1]
#
dtwplot(.√(M1.power), .√(M2.power), dist, linecolor=:green)
# We expect to see less time warping (straighter green line) now when it's allowed to fudge the distance a bit by shuffling mass along the frequency axis.
# How long time does it take to compute such a distance? (on a laptop, expect about 3x improvement on desktop)
@btime dtw($(M1.power), $(M2.power), $dist)
@btime dtw($(M1.power), $(M2.power), radiuslimits(50,m,m)..., $dist)
# The size of the spectrogram
size(M1.power)
# The complexity is almost linear in the number of frequency bins, but quadratic in the number of time steps.


module proj
using LinearAlgebra
# ! indicates that the variable is modified

function infnorm(x,n,γ)
# compute ∞-norm of x
# ---
# x : input vector 				(real n-vector)
# n : length of x
# γ : temp storage variable		(real scalar)    !

	γ = 0.0
	for j=1:n
		γ = max(γ,abs(x[j]))
	end
	return γ
end

function hlfspace!(x,z,u,ζ,n,β)
# projection onto halfspace { x | u⋅x ≤ ζ }
# ---
# x : projected result					(real n-vector) !
# z : vector to be projected			(real n-vector)
# u : normal to supporting hyperplane 	(real n-vector)
# ζ : scalar on RHS 					(real scalar)
# β : temporary variable                (real scalar)   !

	@assert BLAS.nrm2(n,u,1)≈1 "Normal vector u should have unit norm."

	β = BLAS.dot(n,z,1,u,1) - ζ

	if β > 0
		BLAS.blascopy!(n,z,1,x,1)
		axpy!(-β,u,x)
	else
		BLAS.blascopy!(n,z,1,x,1)
	end

end

function hlfspace2!(x,z,u1,u2,ζ1,ζ2,n,β,α1,α2)
# projection onto intersection of two halfspaces { x | u1⋅x ≤ ζ1 } ∩ { x | u2⋅x ≤ ζ2 }
# ---
# x  : projected result					(real n-vector) !
# z  : vector to be projected			(real n-vector)
# u1 : normal to supporting hyperplane 	(real n-vector)
# u2 : normal to supporting hyperplane 	(real n-vector)
# ζ1 : scalar on RHS 					(real scalar)
# ζ1 : scalar on RHS 					(real scalar)
# β  : temporary variable               (real scalar)   !
# α1 : temporary variable               (real scalar)   !
# α2 : temporary variable               (real scalar)   !

	@assert BLAS.nrm2(n,u1,1)≈1 "Normal vector u1 should have unit norm."
	@assert BLAS.nrm2(n,u2,1)≈1 "Normal vector u2 should have unit norm."

	β = BLAS.dot(n,u1,1,u2,1)
	α1 = BLAS.dot(n,z,1,u1,1)
	α2 = BLAS.dot(n,z,1,u2,1)

	if β ≈ 1
		β = min(ζ1,ζ2)
		if α1 > β
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(β-α1,u1,x)
		else
			BLAS.blascopy!(n,z,1,x,1)
		end
	elseif β ≈ -1
		@assert ζ1+ζ2>0 "The two halfspaces do not intersect."
		if α1 < -ζ2
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(-α1-ζ2,u1,x)
		elseif α1 > ζ1
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(-α1+ζ1,u1,x)
		else
			BLAS.blascopy!(n,z,1,x,1)
		end
	else
		if α1-ζ1+β*(ζ2-α2)>0 && α2-ζ2+β*(ζ1-α1)>0
			BLAS.blascopy!(n,z,1,x,1)
			axpy!((α1-ζ1+β*(ζ2-α2))/(β*β-1),u1,x)
			axpy!((α2-ζ2+β*(ζ1-α1))/(β*β-1),u2,x)
		elseif α2>ζ2 && α1-ζ1+β*(ζ2-α2)≤0
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(ζ2-α2,u2,x)
		elseif α1>ζ1 || α2>ζ2
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(ζ1-α1,u1,x)
		else
			BLAS.blascopy!(n,z,1,x,1)
		end
	end

end

function box!(x,z,l,u,n)
# projection onto l∞-norm ball
# ---
# x : projected result			(real n-vector) !
# z : vector to be projected	(real n-vector)
# l : lower bound			 	(real scalar)
# u : upper bound 				(real scalar)
# n : length of z 				(integer)

	@assert u>l "Upper bound should be greater than lower bound."

	for j = 1:n
		x[j] = min(u,max(z[j],l))
	end

end

function ball!(x,z,r,n)
# projection onto l2-norm ball
# ---
# x : projected result			(real n-vector) !
# z : vector to be projected	(real n-vector)
# r : radius of l2-norm ball 	(real scalar)
# n : length of z 				(integer)

	@assert r>0 "Radius should be positive."

	BLAS.blascopy!(n,z,1,x,1)
	BLAS.scal!(n,r/max(BLAS.nrm2(n,x,1),r),x,1)

end

function soc!(x,z,α,n,β)
# projection onto second-order cone (aka soc, ice-cream, lorentz cone)
# type 1 : cone axis along last coordinate and arctan(α) is the cone angle
# ---
# x : projected result 		     (real n-vector) !
# z : vector to be projected	 (real n-vector)
# α : tan(cone angle)			 (real scalar)
# n : length of z (integer)		 (integer)
# β : temp storage variable  	 (real scalar)   !

	@assert α>0 "Cone angle arctan(α) must be positive."

	β = BLAS.nrm2(n-1,z,1)

	if β ≤ α*z[n]
		BLAS.blascopy!(n,z,1,x,1)
	elseif α*β + z[n] ≤ 0
		fill!(x,0.0)
	else
		BLAS.blascopy!(n-1,z,1,x,1)
		x[n] = (β*α+z[n])/(α*α+1)
		BLAS.scal!(n-1,α*x[n]/β,x,1)
	end

end

function soc2!(x,z,θ1,θ2,u,n,β,γ,κ)
# projection onto second-order cone (aka soc, ice-cream, lorentz cone)
# type 2 : cone axis along unit vector u and arccos(θ) is the cone angle
# ---
# x  : projected result 			(real n-vector) !
# z  : vector to be projected		(real n-vector)
# θ1 : cos(cone angle) 				(real scalar)
# θ2 : sin(cone angle) 				(real scalar)
# u  : unit vector along cone axis	(real n-vector)
# n  : length of z 					(integer)
# β  : temp storage variable 		(real scalar)   !
# γ  : temp storage variable 		(real scalar)   !
# κ  : temp storage variable 		(real n-vector) !

	@assert min(θ1,1-θ1)>0 && min(θ2,1-θ2)>0 "θ1,θ2 ∈ [0,1]"
	@assert BLAS.nrm2(n,u,1)≈1 "u must be a unit vector"

	β = BLAS.nrm2(n,z,1)
	γ = BLAS.dot(n,z,1,u,1)

	if θ1*β ≤ γ
		BLAS.blascopy!(n,z,1,x,1)
	elseif β*θ2 + γ ≤ 0
		fill!(x,0.0)
	else
		BLAS.blascopy!(n,u,1,x,1)
		BLAS.blascopy!(n,z,1,κ,1)
		axpy!(-γ,u,κ)
		axpby!(θ2/BLAS.nrm2(n,κ,1),κ,θ1,x)
		BLAS.scal!(n,BLAS.dot(n,z,1,x,1),x,1)
	end

end

function soc_ball!(x,z,α,r,n,β)
# projection onto intersection of soc cone and l2-norm ball
# ---
# x : projected result 			(real n-vector) !
# z : vector to be projected	(real n-vector)
# α : tan(cone angle) 			(real scalar)
# r : radius of l2-norm ball 	(real scalar)
# n : length of z 				(integer)
# β : temp storage variable 	(real scalar)   !

	soc!(x,z,α,n,β)
	ball!(x,x,r,n)

end

function soc2_ball!(x,z,θ1,θ2,u,r,n,β,γ,κ)
# projection onto intersection of soc cone and l2-norm ball
# ---
# x  : projected result 			(real n-vector) !
# z  : vector to be projected		(real n-vector)
# θ1 : cos(cone angle) 				(real scalar)
# θ2 : cos(cone angle) 				(real scalar)
# u  : unit vector along cone axis	(real n-vector)
# r  : radius of l2-norm ball 		(real scalar)
# n  : length of z 					(integer)
# β  : temp storage variable 		(real scalar)   !
# γ  : temp storage variable 		(real scalar)   !
# κ  : temp storage variable 		(real n-vector) !

	soc2!(x,z,θ1,θ2,u,n,β,γ,κ)
	ball!(x,x,r,n)

end

function admm!(x,y,u,z,f1,f2,α,β,ϵ,n,γ1,γ2,κ1,κ2)
# projection onto intersection of two sets with respective projections defined by f1 and f2
# ---
# x  : projected result (copy 1)	(real n-vector) !
# y  : projected result (copy 2)	(real n-vector) !
# u  : dual variable 				(real n-vector) !
# z  : vector to be projected		(real n-vector)
# f1 : projection function			(function: f!(θ,ψ) where θ is modified)
# f2 : projection function 			(function: f!(θ,ψ) where θ is modified)
# α  : ADMM step size				(real scalar)
# β  : 1/(1+α) 						(real scalar)
# ϵ  : ADMM termination criteria  	(real scalar)
# n  : length of z					(integer)
# γ  : temp storage variable        (real scalar)   !
# γ2 : temp storage variable 		(real scalar)   !
# κ1 : temp storage variable 		(real n-vector) !
# κ2 : temp storage variable 		(real n-vector) !

# x, y and u may hold values from previous call to admm! (warm-starting)

	f1(κ1,z)
	f2(κ2,z)
	κ1 .= κ1 .- z
	κ2 .= κ2 .- z
	γ1 = BLAS.nrm2(n,κ2,1)
	γ1 = max(γ1,BLAS.nrm2(n,κ1,1))
	if γ1 ≤ ϵ
	    BLAS.blascopy!(n,z,1,x,1)
		BLAS.blascopy!(n,z,1,y,1)
		# κ2[1] will be zero fortunately
		κ2[1] = 0.0 # inserted for type-stability
	else
		κ1 .= z .- u
		axpy!(α,y,κ1)
		BLAS.scal!(n,β,κ1,1)
		f1(x,κ1)
		κ2 .= z .+ u
		axpy!(α,x,κ2)
		BLAS.scal!(n,β,κ2,1)
		f2(y,κ2)
		κ1 .= x .- y
		axpy!(α,κ1,u)

		κ2[1] = infnorm(κ1,n,γ2)
		γ1 = 1.0
		while κ2[1]≥ϵ
			κ1 .= z .- u
			axpy!(α,y,κ1)
			BLAS.scal!(n,β,κ1,1)
			f1(x,κ1)
			κ2 .= z .+ u
			axpy!(α,x,κ2)
			BLAS.scal!(n,β,κ2,1)
			f2(y,κ2)
			κ1 .= x .- y
			axpy!(α,κ1,u)

			γ1 = γ1 + 1.0 # iteration counter

			κ2[1] = infnorm(κ1,n,γ2)
		end
		κ2[1] = γ1 # store the iteration count
	end

end

function multi_admm!(x,y,u,z,f,α,β,ϵ,n,N,γ1,γ2,ψ,κ)
# projection onto intersection of N sets with projections defined by f[i], i=1,...,N
# ---
# x  : projected results (copy 1)	(N-vector of real n-vectors) !
# y  : projected results (copy 2)	(N-vector of real n-vectors) !
# u  : dual variable 				(N-vector of real n-vectors) !
# z  : vector to be projected		(real n-vector)
# f  : N projection functions   	(N-vector of functions: f[i]!(θ,ψ) where θ is modified, i=1,...,N)
# α  : ADMM step-size				(positive scalar)
# β  : 1/(2α(N-1)+1)				(positive scalar)
# n  : length of z 					(integer)
# N  : no. of sets					(integer)
# γ1 : temp storage variable  		(real scalar)                !
# γ2 : temp storage variable  		(real scalar)                !
# ψ  : temp storage variable  		(real scalar)                !
# κ  : temp storage variable        (real n-vector)              !

	@assert length(f)==N "No. of projection functions is not N."
	# @assert N ≥ 3 "Use admm! for fewer than 3 sets."

	# γ1 = 100.0
	# ψ = 0.0 # iteration counter

	for i = 1:N
		κ .= x[i] # BLAS.blascopy!(n,x[i],1,κ,1)
		lmul!(N-2,κ)
		for j = 1:N
			κ .= κ .+ x[j]
		end
		lmul!(α,κ)
		κ .= κ .- u[i] .+ z
		lmul!(β,κ)
		f[i](y[i],κ)
	end

	γ1 = 0.0 # holds termination criteria value
	x[1] .= y[1]
	for i = 2:N
		x[i] .= y[i]
		κ .= x[1] .- x[i]

		γ1 = max(γ1,infnorm(κ,n,γ2))
	end

	for i = 1:N
		κ .= x[i]
		lmul!(N,κ)
		for j = 1:N
			κ .= κ .- x[j]
		end
		axpy!(α,κ,u[i])
	end

	ψ = 1.0

	while γ1≥ϵ
		for i = 1:N
			κ .= x[i] # BLAS.blascopy!(n,x[i],1,κ,1)
			lmul!(N-2,κ)
			for j = 1:N
				κ .= κ .+ x[j]
			end
			lmul!(α,κ)
			κ .= κ .- u[i] .+ z
			lmul!(β,κ)
			f[i](y[i],κ)
		end

		γ1 = 0.0 # holds termination criteria value
		x[1] .= y[1]
		for i = 2:N
			x[i] .= y[i]
			κ .= x[1] .- x[i]

			γ1 = max(γ1,infnorm(κ,n,γ2))
		end

		for i = 1:N
			κ .= x[i]
			lmul!(N,κ)
			for j = 1:N
				κ .= κ .- x[j]
			end
			axpy!(α,κ,u[i])
		end

		ψ = ψ + 1.0
	end
	κ[1] = ψ

end

end


"""
    Original Code Taken from GraphLayout.jl
    Use the spring/repulsion model of Fruchterman and Reingold (1991):
        Attractive force:  f_a(d) =  d^2 / k
        Repulsive force:  f_r(d) = -k^2 / d
    where d is distance between two vertices and the optimal distance
    between vertices k is defined as C * sqrt( area / num_vertices )
    where C is a parameter we can adjust
    Arguments:
    adj_matrix Adjacency matrix of some type. Non-zero of the eltype
               of the matrix is used to determine if a link exists,
               but currently no sense of magnitude
    C          Constant to fiddle with density of resulting layout
    MAXITER    Number of iterations we apply the forces
    INITTEMP   Initial "temperature", controls movement per iteration
"""
function layout_spring{T}(adj_matrix::Array{T,2}, dim=1; C=2.0, MAXITER=100, INITTEMP=2.0)

    size(adj_matrix, 1) != size(adj_matrix, 2) && error("Adj. matrix must be square.")
    const N = size(adj_matrix, 1)

    # Initial layout is random on the square [-1,+1]^2
    locs_x = 2*rand(N) .- 1.0
    locs_y = 2*rand(N) .- 1.0
    locs_z = 2*rand(N) .- 1.0

    # The optimal distance bewteen vertices
    const K = C * sqrt(4.0 / N)

    # Store forces and apply at end of iteration all at once
    force_x = zeros(N)
    force_y = zeros(N)
    force_z = zeros(N)

    # Iterate MAXITER times
    @inbounds for iter = 1:MAXITER
        # Calculate forces
        for i = 1:N
            force_vec_x = 0.0
            force_vec_y = 0.0
            force_vec_z = 0.0
            for j = 1:N
                i == j && continue
                d_x = locs_x[j] - locs_x[i]
                d_y = locs_y[j] - locs_y[i]
                d_z = locs_z[j] - locs_z[i]
                d   = sqrt(d_x^2 + d_y^2 + d_z^2)
                if adj_matrix[i,j] != zero(eltype(adj_matrix)) || adj_matrix[j,i] != zero(eltype(adj_matrix))
                    F_d = d / K - K^2 / d^2
                else
                    F_d = -K^2 / d^2
                end

                force_vec_x += F_d*d_x
                force_vec_y += F_d*d_y
                force_vec_z += F_d*d_z
            end
            force_x[i] = force_vec_x
            force_y[i] = force_vec_y
            force_z[i] = force_vec_z
        end

        TEMP = INITTEMP / iter
        # Now apply them, but limit to temperature
        for i = 1:N
            force_mag  = sqrt(force_x[i]^2 + force_y[i]^2 + force_z[i]^2)
            scale      = min(force_mag, TEMP)/force_mag
            locs_x[i] += force_x[i] * scale
            locs_y[i] += force_y[i] * scale
            locs_z[i] += force_y[i] * scale
        end
    end

    # Scale to unit square
    min_x, max_x = minimum(locs_x), maximum(locs_x)
    min_y, max_y = minimum(locs_y), maximum(locs_y)
    min_z, max_z = minimum(locs_z), maximum(locs_z)
    function scaler(z, a, b)
        2.0*((z - a)/(b - a)) - 1.0
    end
    map!(z -> scaler(z, min_x, max_x), locs_x)
    map!(z -> scaler(z, min_y, max_y), locs_y)
    map!(z -> scaler(z, min_z, max_z), locs_z)

    if dim == 0
        locs_z = zeros(size(locs_x))
    end

    return locs_x,locs_y,locs_z
end


function next_chebyshev(previous::Sum_Of_Functions, two_previous::Sum_Of_Functions, dim_name::Symbol)
    return PE_Function(2.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,1))) * previous - two_previous
end

function get_first_kind_Chebyshevs(num::Int, dim_name::Symbol)
    if num < 1
        error("Select a positive number of Chebyshevs to return")
    else
        first_kind_chebyshevs = Array{Sum_Of_Functions}(undef, num)
        first_kind_chebyshevs[1] = Sum_Of_Functions([PE_Function(1.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,0)))])
        if num > 1
            first_kind_chebyshevs[2] = Sum_Of_Functions([PE_Function(1.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,1)))])
        end
        for n in 3:num
            first_kind_chebyshevs[n] = next_chebyshev(first_kind_chebyshevs[n-1], first_kind_chebyshevs[n-2], dim_name)
        end
        return first_kind_chebyshevs
    end
end
function get_second_kind_Chebyshevs(num::Int, dim_name::Symbol)
    if num < 1
        error("Select a positive number of Chebyshevs to return")
    else
        second_kind_chebyshevs = Array{Sum_Of_Functions}(undef, num)
        second_kind_chebyshevs[1] = Sum_Of_Functions([PE_Function(1.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,0)))])
        if num > 1
            second_kind_chebyshevs[2] = Sum_Of_Functions([PE_Function(2.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,1)))])
        end
        for n in 3:num
            second_kind_chebyshevs[n] = next_chebyshev(second_kind_chebyshevs[n-1], second_kind_chebyshevs[n-2], dim_name)
        end
        return second_kind_chebyshevs
    end
end

"""
    get_chevyshevs_up_to(num::Int, first_kind::Bool = true; dim_name::Symbol = default_symbol)
    Output all chebyshev polynomials up to degree num.
"""
function get_chevyshevs_up_to(num::Int, first_kind::Bool = true; dim_name::Symbol = default_symbol)
    if first_kind
        return get_first_kind_Chebyshevs(num,dim_name)
    else
        return get_second_kind_Chebyshevs(num,dim_name)
    end
end


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

using Markdown
using InteractiveUtils

# ╔═╡ 03cc6704-70eb-11eb-2f5f-d727783a1f53
begin
	using Pkg
	Pkg.activate("MLJ_env", shared=true)
end

# ╔═╡ 6d52e124-70eb-11eb-2c56-5dcea746627e
begin
	using MLJ
	using CSV
	using DataFrames
	using PlutoUI
end

# ╔═╡ b738e190-70ef-11eb-04da-8105c8390b73
using Random

# ╔═╡ a9f436f6-70fc-11eb-394e-f7a9755bb779
using Test

# ╔═╡ 28cfd44c-70fc-11eb-0155-f984980e96f6
using Printf # Pkg.add("Printf")

# ╔═╡ 62c32226-70ea-11eb-3914-730e6d2ec0a4
md"""
## C02 - w01: Simple Linear Regression

In this notebook we will use data on house sales in King County to predict house prices using simple (one input) linear regression. we will
  - compute important summary statistics
  - Write a function to compute the Simple Linear Regression weights using the closed form solution
  - Write a function to make predictions of the output given the input feature
  - Turn the regression around to predict the input given the output
  - Compare two different models for predicting house prices
  - *Finally use a predefined MLJ solution*
"""

# ╔═╡ ec3daae4-70eb-11eb-08df-416f041130f6

# ╔═╡ fa6be52e-70eb-11eb-2050-0bd51777d5d0
sales = CSV.File("../../ML_UW_Spec/C02/w01/data/kc_house_test_data.csv";
	header=true) |> DataFrame;

# ╔═╡ 39ca1334-70ed-11eb-2ee9-5bda94ba96f5
first(sales, 7)

# ╔═╡ 53b101ac-70f1-11eb-3844-3d5f21d9b7aa
size(sales)

# ╔═╡ d2a6fa36-70f7-11eb-3817-b36402af12f2
eltype.(eachcol(sales))

# ╔═╡ 299273f6-70f4-11eb-11c9-035af4af7032
md"""
### Split data into training and testing set
"""

# ╔═╡ 55c029d2-70f4-11eb-00ac-0b7698d7b6e2
function train_test_split(df; split=0.8, seed=42)
	Random.seed!(seed)
	(nr, nc) = size(df)
	nrp = round(Int, nr * split)
	row_ixes = shuffle(1:nr)
	df_train = view(df[row_ixes, :], 1:nrp, 1:nc)
	df_test = view(df[row_ixes, :], nrp+1:nr, 1:nc)
	(df_train, df_test)
end

# ╔═╡ d403506c-70f4-11eb-386f-ad0152978098
sales_train, sales_test = train_test_split(sales);

# ╔═╡ fff80614-70f3-11eb-137d-cbf3fcbaf98f
first(sales_train, 7)

# ╔═╡ 24949900-70f0-11eb-24f4-bd5c33121397
first(sales_test, 3)

# ╔═╡ 11aafb94-70f4-11eb-2438-8320f2e6cb5b
size(sales_train), size(sales_test)

# ╔═╡ 1fb75280-70f8-11eb-1cc3-29a08a026bcc
# mean and sum of a column
mean(sales_train[!, :bedrooms]), sum(sales_train[!, :bedrooms])

# ╔═╡ 799514b8-70f8-11eb-0d03-e5fe5b1c0cb4
sales_train[!, :bedrooms] .* sales_train[!, :bedrooms]

# ╔═╡ 7c9c2c1a-70f9-11eb-3ddc-d90788056518
length(sales_train[!, :bedrooms])

# ╔═╡ cc20e6e0-70f9-11eb-2b0d-7bd7f07d4733
typeof(sales_train[!, :bedrooms])

# ╔═╡ 416a6240-70f9-11eb-1962-e77cb5215607
md"""
### Build a generic simple linear regression function
"""

# ╔═╡ 5b5efd00-70f9-11eb-36f1-75d629b148e6
function slope(X, y)::Float64
	sx, sy = sum(X), sum(y)
	n = length(X)
	num = sum(X .* y) - (1/n) * sx * sy
	den = sum(X .* X) - (1/n) * sx * sx
	num / den
end

# ╔═╡ c40f3ea0-70f9-11eb-2ff0-9b1e9c4f3084
intercept(X, y, slope) = mean(y) - slope * mean(X)

# ╔═╡ 04b3c642-70fa-11eb-3e0b-b9b86108e1af
function simple_linear_regression(X, y)::Tuple{Real, Real}
	_slope = slope(X, y)
	_intercept = intercept(X, y, _slope)
	(_intercept, _slope)
end

# ╔═╡ 4e0f51e4-70fa-11eb-2511-b9813e9c82bb
begin
	test_feature = collect(1:5)
	test_output = 1 .+ test_feature

	(test_intercept, test_slope) = simple_linear_regression(test_feature, test_output)

	@test test_intercept ≈ 1.0
	@test test_slope ≈ 1.0
end

# ╔═╡ 64258a92-70fb-11eb-08d4-b11d5f75639f
begin
	sqft_intercept, sqft_slope = simple_linear_regression(
		sales_train[!, :sqft_living], sales_train[!, :price])

	with_terminal() do
		@printf("Intercept: %.4f\n", sqft_intercept)
		@printf("Slope:  %.4f\n", sqft_slope)
	end
end

# ╔═╡ beee3bd2-70fc-11eb-38cd-e7e8fda2f883
md"""
### Predicting Values

Now that we have the model parameters: intercept and slope we can make predictions.

Create a function to return the predicted output given the input_feature, slope and intercept.

"""

# ╔═╡ deda4470-70fc-11eb-20a2-15820ab34231
function get_regression_predictions(input_feature, intercept::Real,
		slope::Real)
    slope .* input_feature .+ intercept
end

# ╔═╡ 1ed980ae-70fd-11eb-07ed-09b7648af658
md"""
Now that we can calculate a prediction given the slope and intercept let's make a prediction.

**Quiz Question**: Using your Slope and Intercept from above, what is the predicted price for a house with 2650 sqft?
"""

# ╔═╡ 490bda66-70fd-11eb-3cd3-e7ccd064c7ec
with_terminal() do
	house_sqft = 2650
	estimated_price = get_regression_predictions(house_sqft,
		sqft_intercept, sqft_slope)

	@printf("The estimated price for a house with %d squarefeet is %.2f\n",
		house_sqft,
		estimated_price)
end

# ╔═╡ 96f5469a-70fd-11eb-1f17-675eeff21970
md"""
### Residual Sum of Squares

Now that we have a model and can make predictions let's evaluate our model using Residual Sum of Squares (RSS). Recall that RSS is the sum of the squares of the residuals and the residuals is just a fancy word for the difference between the predicted output and the true output.

Create a function (`get_residual_sum_of_squares`) to compute the RSS of a simple linear regression model given the input_feature, output, intercept and slope.

"""

# ╔═╡ b5e12f56-70fd-11eb-1bf7-cbdfa36fc673
function get_residual_sum_of_squares(input_feature, output,
		intercept::Real, slope::Real)
    ## 1. get the predictions
    yhat = get_regression_predictions(input_feature, intercept, slope)
    diff = (yhat .- output)   ## 2. compute the residuals
    rss = sum(diff .* diff)  ## 3. square the residuals and add them up
    return rss
end

# ╔═╡ 25479c96-70ff-11eb-2b86-29c052eb2f87
test_slope

# ╔═╡ 064ec37c-70fe-11eb-3bce-a79f7208c876
md"""
Let's test our `get_residual_sum_of_squares` function by applying it to the test model where the data lie exactly on a line. Since they lie exactly on a line the residual sum of squares should be zero!
"""

# ╔═╡ 1b0643d0-70fe-11eb-250c-53e54279e747
@test get_residual_sum_of_squares(test_feature, test_output,
	test_intercept, test_slope) ≈ 0.0

# ╔═╡ 5e2a432e-70ff-11eb-3e61-bffdc0ebd46e
md"""
Now use your function to calculate the RSS on training data from the squarefeet model calculated above.

**Quiz Question**: According to this function and the slope and intercept from the squarefeet model What is the RSS for the simple linear regression using squarefeet to predict prices on *training* data?

"""

# ╔═╡ 7e8bcf82-70ff-11eb-1c79-a56db6635447
begin
	rss_prices_on_sqft = get_residual_sum_of_squares(sales_train[!, :sqft_living],
		sales_train[!, :price],
        sqft_intercept,
		sqft_slope)
	with_terminal() do
		@printf("The RSS of predicting Prices based on Square Feet is: %1.3e\n", rss_prices_on_sqft)
	end
end

# ╔═╡ 0afc9a02-7100-11eb-0578-8b470fbb1d32
md"""
### Predict the squarefeet given price

What if we want to predict the squarefoot given the price?

Since we have an equation $y = a + b \times x$ we can solve the function for x, so that if we have the intercept (a) and the slope (b) and the price (y) we can solve for the estimated squarefeet (x) with:
$x = \frac{y - a}{b}$.

Create a function (`inverse_regression_predictions`) to compute the inverse regression estimate, i.e. predict the input_feature given the output.
"""

# ╔═╡ 5936bff2-7100-11eb-3673-03eb5174f2b0
function inverse_regression_predictions(output, intercept::Real, slope::Real)
    (output .- intercept) ./ slope
end

# ╔═╡ 7f548db8-7100-11eb-1a71-cfa1b2817f83
md"""
Now that we have a function to compute the squarefeet given the price from our simple regression model let's see how big we might expect a house that costs \$800,000 to be.

**Quiz Question**: According to this function and the regression slope and intercept from (3) what is the estimated square-feet for a house costing \$800,000?

"""

# ╔═╡ 967a4d8e-7100-11eb-369c-a7eef43335a2
begin
	house_price = 800000
	estimated_squarefeet = inverse_regression_predictions(house_price,
		sqft_intercept,
		sqft_slope)

	with_terminal() do
		@printf("The estimated squarefeet for a house worth %.2f is %.3f\n", house_price, estimated_squarefeet)
	end
end

# ╔═╡ f8cb4844-7100-11eb-2036-f1691f9277ba
md"""
### New Model: estimate prices from bedrooms

We have made one model for predicting house prices using squarefeet, but there are many other features in the sales DataFrame.

Use your simple linear regression function to estimate the regression parameters from predicting Prices based on number of bedrooms. Use the *training* data!

"""

# ╔═╡ 1a89af3e-7101-11eb-274d-43bbcf50db13
bedr_intercept, bedr_slope = simple_linear_regression(
	sales_train[!, :bedrooms],
	sales_train[!, :price]
)

# ╔═╡ 4d0ca242-7101-11eb-1001-7bb71d3b8de6
md"""
### Test your Linear Regression Algorithm

Now we have two models for predicting the price of a house. How do we know which one is better? Calculate the RSS on the *test* data (remember this data wasn't involved in learning the model). Compute the RSS from predicting prices using bedrooms and from predicting prices using squarefeet.

**Quiz Question**: Which model (square feet or bedrooms) has lowest RSS on TEST data? Think about why this might be the case.

"""

# ╔═╡ 69a38e9e-7101-11eb-2e57-83829ceb7ab9
begin
	## Compute RSS when using bedrooms on test data:
	rss_prices_on_bedr = get_residual_sum_of_squares(sales_test[!, :bedrooms],
        sales_test[!, :price],
        bedr_intercept,
		bedr_slope)
	with_terminal() do
		@printf("The RSS of predicting Prices based on number of Bedrooms is: %1.3e\n ", rss_prices_on_bedr)
	end
end

# ╔═╡ d043648a-7101-11eb-1b30-8d7ff3b37b7a
begin
	## Compute RSS when using squarefeet on test data:
	rss_prices_on_sqft_t = get_residual_sum_of_squares(sales_test[!, :sqft_living],
        sales_test[!, :price],
        sqft_intercept,
		sqft_slope)
	with_terminal() do
		@printf("The RSS of predicting Prices based on Squarefeet is: %1.3e\n ", rss_prices_on_sqft_t)
	end
end

# ╔═╡ Cell order:
# ╟─62c32226-70ea-11eb-3914-730e6d2ec0a4
# ╠═03cc6704-70eb-11eb-2f5f-d727783a1f53
# ╠═6d52e124-70eb-11eb-2c56-5dcea746627e
# ╟─ec3daae4-70eb-11eb-08df-416f041130f6
# ╠═fa6be52e-70eb-11eb-2050-0bd51777d5d0
# ╠═39ca1334-70ed-11eb-2ee9-5bda94ba96f5
# ╠═53b101ac-70f1-11eb-3844-3d5f21d9b7aa
# ╠═d2a6fa36-70f7-11eb-3817-b36402af12f2
# ╟─299273f6-70f4-11eb-11c9-035af4af7032
# ╠═b738e190-70ef-11eb-04da-8105c8390b73
# ╠═55c029d2-70f4-11eb-00ac-0b7698d7b6e2
# ╠═d403506c-70f4-11eb-386f-ad0152978098
# ╠═fff80614-70f3-11eb-137d-cbf3fcbaf98f
# ╠═24949900-70f0-11eb-24f4-bd5c33121397
# ╠═11aafb94-70f4-11eb-2438-8320f2e6cb5b
# ╠═1fb75280-70f8-11eb-1cc3-29a08a026bcc
# ╠═799514b8-70f8-11eb-0d03-e5fe5b1c0cb4
# ╠═7c9c2c1a-70f9-11eb-3ddc-d90788056518
# ╠═cc20e6e0-70f9-11eb-2b0d-7bd7f07d4733
# ╟─416a6240-70f9-11eb-1962-e77cb5215607
# ╠═5b5efd00-70f9-11eb-36f1-75d629b148e6
# ╠═c40f3ea0-70f9-11eb-2ff0-9b1e9c4f3084
# ╠═04b3c642-70fa-11eb-3e0b-b9b86108e1af
# ╠═a9f436f6-70fc-11eb-394e-f7a9755bb779
# ╠═4e0f51e4-70fa-11eb-2511-b9813e9c82bb
# ╠═28cfd44c-70fc-11eb-0155-f984980e96f6
# ╠═64258a92-70fb-11eb-08d4-b11d5f75639f
# ╟─beee3bd2-70fc-11eb-38cd-e7e8fda2f883
# ╠═deda4470-70fc-11eb-20a2-15820ab34231
# ╟─1ed980ae-70fd-11eb-07ed-09b7648af658
# ╠═490bda66-70fd-11eb-3cd3-e7ccd064c7ec
# ╟─96f5469a-70fd-11eb-1f17-675eeff21970
# ╠═b5e12f56-70fd-11eb-1bf7-cbdfa36fc673
# ╠═25479c96-70ff-11eb-2b86-29c052eb2f87
# ╟─064ec37c-70fe-11eb-3bce-a79f7208c876
# ╠═1b0643d0-70fe-11eb-250c-53e54279e747
# ╟─5e2a432e-70ff-11eb-3e61-bffdc0ebd46e
# ╠═7e8bcf82-70ff-11eb-1c79-a56db6635447
# ╟─0afc9a02-7100-11eb-0578-8b470fbb1d32
# ╠═5936bff2-7100-11eb-3673-03eb5174f2b0
# ╟─7f548db8-7100-11eb-1a71-cfa1b2817f83
# ╠═967a4d8e-7100-11eb-369c-a7eef43335a2
# ╟─f8cb4844-7100-11eb-2036-f1691f9277ba
# ╠═1a89af3e-7101-11eb-274d-43bbcf50db13
# ╟─4d0ca242-7101-11eb-1001-7bb71d3b8de6
# ╠═69a38e9e-7101-11eb-2e57-83829ceb7ab9
# ╠═d043648a-7101-11eb-1b30-8d7ff3b37b7a


"""
    find_maxlength_subarray(array::Array{Int64,1})

Find the maximum length of a subarray with a given continuous depending sequence of numbers.
For this reason, the minimum and maximum element in every subarray will be tracked and
compared with the incremental distance. So if the difference of the value is equal to the
difference of the increment in the array, the length will be updated.

# Arguments
- `array::Array{Int64,1}`: Unsorted array

# Examples
```julia-repl
julia> import ClassicAlgorithmsCollections
julia> arr = [10, 12, 11, 9, 13, 14, 17, 18, 15]
julia> ClassicAlgorithmsCollections.find_maxlength_subarray(arr)
5
```
"""
function find_maxlength_subarray(array::Array{Int64,1})

    # Initialize result
    max_len = 1
    n = length(array)
    for i in 1:n
        min_array = array[i]
        max_array = array[i]

        for j in (i+1):n
            if min_array > array[j]
                min_array = array[j]
            end
            if max_array < array[j]
                max_array = array[j]
            end
            if ((max_array - min_array) == (j - i))
                if max_len < (max_array - min_array)
                    max_len = max_array - min_array + 1
                end
            end
        end
    end
    return max_len
end


using Plots
using Random
using NearestNeighbors

global my_seed = 1001
Random.seed!(my_seed)

function inv_dist_itp_test(idxs, dists, p, values)
    avg_inv_dist = 0
    f = 0
    for i in 1:length(idxs)
        idx = idxs[i]
        inv_dist = 1/dists[i]^p
        avg_inv_dist += inv_dist
        f += values[idx]*inv_dist
    end
    f = f/avg_inv_dist
end

function inv_dist_itp(idxs::AbstractVector,
                      dists::AbstractVector,
                      p::AbstractFloat,
                      values::AbstractVector)
    avg_inv_dist = 0
    f = 0
    for i in 1:length(idxs)
        idx = idxs[i]
        if idx > 0
            inv_dist = 1/dists[i]^p
            avg_inv_dist += inv_dist
            f += values[idx]*inv_dist
        elseif idx == -5
            # lower boundary
            f+=0
        elseif idx == -6
            # upper boundary
            f+=0
        end
    end
    f = f/avg_inv_dist
end

function sample_function(x, y)
    return exp(-x^2 - y^2)
end

n_samples = 100

samples = rand(2, n_samples).*2 .-1
x_samples = samples[1,:]
y_samples = samples[2,:]

# Create a tree
tree = KDTree(samples)

k = 15

function_values = sample_function.(x_samples, y_samples)

raster_size = 500
x_range = LinRange(-1,1,raster_size)
y_range = LinRange(-1,1,raster_size)

gr()

function plot_the_itp(p)

    raster_values = Matrix{Float64}(undef, (raster_size, raster_size))

    for i in 1:raster_size
        for j in 1:raster_size
            raster_site = [x_range[i], y_range[j]]
            nearest_neighbours = knn(tree, raster_site, k)
            raster_values[i, j] = inv_dist_itp_test(nearest_neighbours[1],
                                                    nearest_neighbours[2],
                                                    p,
                                                    function_values)
        end
    end
    heatmap(x_range,
            y_range,
            raster_values,
            title="p=$p, $k neighbours",
            dpi=350,
            aspect_ratio=:equal,
            xlim=[-1,1])
    savefig("../img/InverseDistance/inv_dist_$(Int(p*100))_$(k)")
end

p = [3]
for i in p
    plot_the_itp(i)
end

true_values = Matrix{Float64}(undef, (raster_size, raster_size))
for i in 1:raster_size
    for j in 1:raster_size
        true_values[i, j] = sample_function(x_range[i], y_range[j])
    end
end

heatmap(x_range,
        y_range,
        true_values,
        title="True Function",
        dpi=350,
        aspect_ratio=:equal,
        xlim=[-1,1])
savefig("../img/InverseDistance/inv_dist_comparison")

scatter(x_samples,
        y_samples,
        title="samples",
        dpi=350,
        aspect_ratio=:equal,
        xlim=[-1,1],
        xlabel=raw"$x$",
        ylabel=raw"$y$")
savefig("../img/InverseDistance/sample_points")


"""
x = admm(A, B; ...)

Solves non-negative least-squares problem by the Alternating Direction Method
of Multipliers (ADMM).

Optional arguments:
    ρ: penalty parameter (set heuristically by default)
    ε: tolerance for stopping (small number times sqrt[m*n] by default)

References:
    S. Boyd, N. Parikh, E. Chu, B. Peleato, and J. Eckstein (2011). Distributed
    Optimization and Statistical Learning via the Alternating Direction Method
    of Multipliers. Foundations and Trends in Machine Learning.

    http://stanford.edu/~eryu/nnlsqr.html
"""
function admm(A::Matrix{Float64},
	          B::Matrix{Float64};
	          ρ=max(0.1,vecnorm(A)^2/size(A,2)),
	          ε=sqrt(size(A,2)*size(B,2))*1e-15,
	          kwargs...)

	# Dimensions
	m,k = size(A)
	n = size(B,2)

	# Cache matrices
	AtB = A'*B
	AtAρ = A'*A + eye(k)*ρ

	# Cache cholesky factorization
	L = cholfact(AtAρ)

	# Matrix storing the solutions
	X = zeros(k,n)

	# Initialize variables
	Z,U = zeros(k,n),zeros(k,n)
	X = L \ (AtB+ρ*(Z-U))

	# Solve
	while vecnorm(X-Z) > ε
		Z = max(0,X+U)
		U = U+X-Z
		X = L \ (AtB+ρ*(Z-U))
	end

	# Z ≈ X, return Z because nonnegativity is strictly enforced
	return Z
end


const P = big(2)^256 - 2^32 - 977
const N = big"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"

infield(x::Integer) = x >= 0 && x < P

"`FieldElement` represents an element in 𝐹ₚ where `P = 2²⁵⁶ - 2³² - 977`"
struct FieldElement <: Number
     𝑛::BigInt
     FieldElement(𝑛) = !infield(𝑛) ? throw(NotInField()) : new(𝑛)
end

FieldElement(x::FieldElement) = x
FieldElement(x::Union{Int128, Int64, Int32, Int16, Int8, Unsigned}) = FieldElement(big(x))
𝐹 = FieldElement

"Formats FieldElement showing 𝑛 in hexadecimal format"
function show(io::IO, z::FieldElement)
    print(io, string(z.𝑛, base = 16),"\n(in scep256k1 field)")
end

==(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝑋₁.𝑛 == 𝑋₂.𝑛
==(::FieldElement, ::Integer) = false
+(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝐹(mod(𝑋₁.𝑛 + 𝑋₂.𝑛, P))
-(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝐹(mod(𝑋₁.𝑛 - 𝑋₂.𝑛, P))
*(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝐹(mod(𝑋₁.𝑛 * 𝑋₂.𝑛, P))
*(𝑐::Integer, 𝑋::FieldElement) = 𝐹(mod(𝑐 * 𝑋.𝑛, P))
*(𝑋::FieldElement, 𝑐::Integer) = *(𝑐, 𝑋)
^(𝑋::FieldElement, 𝑘::Int) = 𝐹(powermod(𝑋.𝑛, mod(𝑘, (P - 1)), P))
/(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝐹(mod(𝑋₁.𝑛 * powermod(𝑋₂.𝑛, P - 2, P), P))
div(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝑋₁ / 𝑋₂
inv(𝑋::FieldElement) = 𝐹(powermod(𝑋.𝑛, mod(-1, (P - 1)), P))
sqrt(𝑋::FieldElement) = 𝑋^fld(P + 1, 4)


# This file illustrates calculation of energy spectral density and total enery of a rectangular pulse.

using FFTW
using Plots; plotlyjs()

# Define pulse
T = 1 / 10
p(t) = 0 ≤ t ≤ T ? 1 : 0

# Construct the signal
fs = 1000
ts = 1 / fs
l = 2^10
t = (0 : l - 1) * ts
x = p.(t)

# Compute fft
xf = fft(x)
psd = abs.(xf).^2
ff = (0 : 1 / (l - 1) : 1) * fs

# Compute energies
Δf = fs / (l - 1)
Δt = ts
Et = sum(abs.(x).^2) * Δt
Ef = sum(psd) * Δf / (fs^2)
@show Et, Ef

# Plots
plt = plot(layout=2)
plot!(t[1:500], x[1:500], subplot=1)
plot!(ff, psd, subplot=2)
display(plt)


#=
There are exactly ten ways of selecting three from five, 12345:

123, 124, 125, 134, 135, 145, 234, 235, 245, and 345

In combinatorics, we use the notation, 5C3 = 10.

In general,

nCr =
n!
r!(n−r)!
,where r ≤ n, n! = n×(n−1)×...×3×2×1, and 0! = 1.
It is not until n = 23, that a value exceeds one-million: 23C10 = 1144066.

How many, not necessarily distinct, values of  nCr, for 1 ≤ n ≤ 100, are greater than one-million?
=#
function combine(n, r)
  factorial(n)/(factorial(r)factorial(n-r))
end

function calc()
  sum = 0
  for n in 1:100, r in reverse(1:n)
    if combine(BigInt(n), BigInt(r)) > 1_000_000
      sum = sum + 1
    end
  end
  sum
end
@time println(calc())