"""
Convert a vector `w` to a symmetric matrix `P` as
P = [
    w[1] ⋅ ⋅ ⋅ ⋅ ...;
    w[2] w[n+1]  ...;
    w[3] w[n+2]  ...;
    ...  ...        ;
    w[n] ...        ;
]
# Notes
It should be compatible with `convert_quadratic_to_linear_basis`.
"""
function convert_to_matrix(w::AbstractVector)::AbstractMatrix
    # n(n+1)/2 x 1 -> n x n
    _l = length(w)
    l = Int((-1 + sqrt(1+4*2*_l)) / 2)  # P is l x l lower triangular matrix with elements of w
    P = zeros(l, l)
    idx = 1
    for i in 1:l
        for j in i:l
            if i == j
                P[j, i] = w[idx]
            else
                P[j, i] = 0.5 * w[idx]
                P[i, j] = 0.5 * w[idx]
            end
            idx += 1
        end
    end
    P
    # # n^2 x 1 -> n x n for convenience...
    # l = Int(sqrt(length(w)))
    # P = zeros(l, l)
    # idx = 1
    # for i in 1:l
    #     for j in 1:l
    #         P[j, i] = w[idx]
    #         idx += 1
    #     end
    # end
    # P
end

"""
Basis transformation for linear ADP and IRL;
xᵀ * P * x = wᵀ * ϕ(x)
where ϕ(x) = [x1^2, x2*x1, x3*x1, ..., xn*x1, x2^2, x3*x2, x4*x2, ..., xn*x2, x3^2, x4*x3, ...]
# Notes
It should be compatible with `convert_to_matrix`.
"""
function convert_quadratic_to_linear_basis(x::AbstractVector)
    ## Notes
    ## ϕ(x) = (I_m ⊗ x) x;
    ## Px = (I_m ⊗ xᵀ) w
    ## where P ∈ ℝ^{m×n}, w ∈ ℝ^{nm×1}, I_m: m×m identity matrix, ⊗: Kronecker product
    # n = length(x)
    # kron(Matrix(I, n, n), x) * x
    ## for dimension reduction
    n = length(x)
    ϕ = zeros(Int(n*(n+1)/2))
    idx = 1
    for i in 1:n
        for j in i:n
            ϕ[idx] = x[j] * x[i]
            idx += 1
        end
    end
    ϕ
end


######
# This example shows how to do function approximation in 2D using Padua points
# and in 3D by hand
######

using ApproxFun
    import ApproxFun: transform
f = (x,y) -> exp(x*y)

S = Chebyshev(0..1)^2

N = 10^2; p = points(S, N) # 105 padua points
f̃ = (xy) -> f(xy[1], xy[2])
F = f̃.(p)
F̌ = transform(S, F)  # 105 Chebyshev^2 coefficients ordered by polynomial degree
f̌ = Fun(S, F̌)
f̌(0.1,0.2) ≈ f(0.1,0.2)

S = Chebyshev(0..1)
p_x = points(S, 10)
p_y = points(S, 10)'
p_z = reshape(points(S, 10), 1, 1, 10)

f = (x,y,z) -> exp(x*y+z)

F = f.(p_x, p_y, p_z)

for k = 1:size(F,2),j=1:size(F,3)
    F[:,k,j] = transform(S,F[:,k,j])
end
for k = 1:size(F,1),j=1:size(F,3)
    F[k,:,j] = transform(S,F[k,:,j])
end
for k = 1:size(F,1),j=1:size(F,2)
    F[k,j,:] = transform(S,F[k,j,:])
end

f̌ = function(x,y,z)
    ret1 = Array{Float64}(undef, size(F,2), size(F,3))
    for j=1:size(F,2),l=1:size(F,3)
        ret1[j,l] = Fun(S,F[:,j,l])(x)
    end
    ret2 = Array{Float64}(undef, size(F,3))
    for l=1:size(F,3)
        ret2[l] = Fun(S,ret1[:,l])(y)
    end
    Fun(S, ret2)(z)
end

f̌(0.1,0.2,0.3) ≈ f(0.1,0.2,0.3)


#=

Solves the price function for the Lucas tree in a continuous state
setting, using piecewise linear approximation for the sequence of
candidate price functions.  The consumption endownment follows the
log linear AR(1) process

    log y' = alpha log y + sigma epsilon

where y' is a next period y and epsilon is an iid standard normal
shock. Hence

    y' = y^alpha * xi   where xi = e^(sigma * epsilon)

The distribution phi of xi is

    phi = LN(0, sigma^2) where LN means lognormal

@authors : Spencer Lyon <spencer.lyon@nyu.edu>, John Stachurski

References
----------

http://quant-econ.net/jl/markov_asset.html

=#

using QuantEcon
using Distributions

"""
A function that takes two arrays and returns a function that approximates the
data using continuous piecewise linear interpolation.

"""
function lin_interp(x_vals::Vector{Float64}, y_vals::Vector{Float64})
    # == linear interpolation inside grid, constant values outside grid == #
    w = LinInterp(x_vals, y_vals)
    return w
end

"""
The Lucas asset pricing model --- parameters and grid data
"""
type LucasTree
    gamma::Real       # coefficient of risk aversion
    beta::Real        # Discount factor in (0, 1)
    alpha::Real       # Correlation coefficient in the shock process
    sigma::Real       # Volatility of shock process
    phi::Distribution # Distribution for shock process
    grid::Vector      # Grid of points on which to evaluate prices.
    shocks::Vector    # Draws of the shock
    h::Vector         # The h function represented as a vector
end

"""
Constructor for the Lucas asset pricing model
"""
function LucasTree(;gamma=2.0,
                beta=0.95,
                alpha=0.9,
                sigma=0.1,
                grid_size=100)

    phi = LogNormal(0.0, sigma)
    shocks = rand(phi, 500)

    # == build a grid with mass around stationary distribution == #
    ssd = sigma / sqrt(1 - alpha^2)
    grid_min, grid_max = exp(-4 * ssd), exp(4 * ssd)
    grid = collect(linspace(grid_min, grid_max, grid_size))

    # == set h(y) = beta * int u'(G(y,z)) G(y,z) phi(dz) == #
    h = similar(grid)
    for (i, y) in enumerate(grid)
        h[i] = beta * mean((y^alpha .* shocks).^(1 - gamma))
    end

    return LucasTree(gamma,
                    beta,
                    alpha,
                    sigma,
                    phi,
                    grid,
                    shocks,
                    h)
end

"""
The approximate Lucas operator, which computes and returns updated function
Tf on the grid points.
"""
function lucas_operator(lt::LucasTree, f::Vector{Float64})

    # == unpack names == #
    grid, alpha, beta, h = lt.grid, lt.alpha, lt.beta, lt.h
    z = lt.shocks

    Tf = similar(f)
    Af = lin_interp(grid, f)

    for (i, y) in enumerate(grid)
        Tf[i] = h[i] + beta * mean(Af.(y^alpha .* z))
    end
    return Tf
end

"""
Compute the equilibrium price function associated with Lucas tree `lt`
"""
function compute_lt_price(lt::LucasTree, max_iter=500)

    # == Simplify names == #
    grid = lt.grid
    alpha, beta, gamma = lt.alpha, lt.beta, lt.gamma

    # == Create suitable initial vector to iterate from == #
    f_init = zeros(grid)

    func(f_vec) = lucas_operator(lt, f_vec)
    f = compute_fixed_point(func, f_init;
                                    max_iter=max_iter,
                                    err_tol=1e-4,
                                    verbose=false)

    # p(y) = f(y) * y^gamma
    price = f .* grid.^(gamma)

    return price
end


# memory_function.jl

"""
χ(Ω::Float64, α::Float64, v::Float64, w::Float64)

    Calculate the memory function χ(Ω) of the polaron at finite temperatures (equation (35a) in FHIP 1962) for a given frequency Ω. v and w are the variational polaron parameters that minimise the free energy, for the supplied α Frohlich coupling.
"""
function χ(Ω, β, α, v, w)

    # FHIP1962, page 1011, eqn (47c).
    R = (v^2 - w^2) / (w^2 * v)

    # FHIP1962, page 1009, eqn (35c).
    D(x) = w^2 / v^2 * (R * (1 - cos(v * x)) * coth(β * v / 2) + x^2 / β - 1im * (R * sin(v * x) + x))

    # FHIP1962, page 1009, eqn (36).
    S(x) = 2 * α / (3 * √π) * (exp(1im * x) + 2 * cos(x) / (exp(β) - 1)) / (D(x))^(3 / 2)

    # FHIP1962, page 1009, eqn (35a).
    integrand(x) = (1 - exp(-1im * Ω * x)) * imag(S(x))
    QuadGK.quadgk(x -> integrand(x), 0.0, Inf)[1]
end

"""
χ(Ω::Float64, α::Float64, v::Float64, w::Float64)

    Calculate the memory function χ(Ω) of the polaron at zero-temperatures (equation (35a) in FHIP 1962) for a given frequency Ω. v and w are the variational polaron parameters that minimise the free energy, for the supplied α Frohlich coupling.
"""
function χ(Ω, α, v, w)

    # FHIP1962, page 1011, eqn (47c).
    R = (v^2 - w^2) / (w^2 * v)

    # FHIP1962, page 1009, eqn (35c) with β → ∞.
    D(x) = w^2 / v^2 * (R * (1 - cos(v * x)) - 1im * (R * sin(v * x) + x))

    # FHIP1962, page 1009, eqn (36) with β → ∞.
    S(x) = 2 * α / (3 * √π) * exp(1im * x) / (D(x))^(3 / 2)

    # FHIP1962, page 1009, eqn (35a). Set upper limit < ∞ but very large so cos argument finite.
    integrand(x) = (1 - exp(-1im * Ω * x)) * imag(S(x))
    QuadGK.quadgk(x -> integrand(x), 0.0, 1e200)[1]
end

"""
χ_dc(β::Float64, α::Float64, v::Float64, w::Float64)

    Calculate the memory function lim(Ω → 0){χ(Ω) / Ω} of the polaron at finite temperatures (equation (35a) in FHIP 1962) at zero frequency. v and w are the variational polaron parameters that minimise the free energy, for the supplied α Frohlich coupling.
"""
function χ_dc(β, α, v, w)

    # FHIP1962, page 1011, eqn (47c).
    R = (v^2 - w^2) / (w^2 * v)

    # FHIP1962, page 1009, eqn (35c).
    D(x) = w^2 / v^2 * (R * (1 - cos(v * x)) * coth(β * v / 2) + x^2 / β - 1im * (R * sin(v * x) + x))

    # FHIP1962, page 1009, eqn (36).
    S(x) = 2 * α / (3 * √π) * (exp(1im * x) + 2 * cos(x) / (exp(β) - 1)) / (D(x))^(3 / 2)

    # Set frequency small enough to mimic Ω = 0 without generating numerical instabilities in integral.
    Ω = 1e-200

    # FHIP1962, page 1009, eqn (35a). Readily divided by Ω to get sinc function in integrand rather than sine which would give 0 all the time.
    integrand(x) = (1 - exp(-1im * Ω * x)) * imag(S(x)) / Ω
    QuadGK.quadgk(x -> integrand(x), 0.0, Inf)[1]
end


include("Kernals.jl")
include("gaussian_process.jl")

function uniform(a,b,N)
    rand(N)*(b-a)+a
end

using Distributions
n=50 #number of test points
N=10;#Number of training points
Xtest=linspace(-5,5,n); #Xtest are all of available points to check on the axis
X=uniform(-5,5,N); #X is the values we will sample at and train
noise_dist=Normal(0,10.0^(-6)) #Distribution of Noise
Y=sin(X)+rand(noise_dist) #Y is our function values with noise
K=cov_gen(std_exp_square_ker,Xtest,Xtest)+eye(length(Xtest))*1e-6
L=ctranspose(chol(K));
dist=MvNormal(zeros(n),eye(K)) # We draw f from N(0,I)L which is the same as N(0,K)
fprior=L*rand(dist,3); #Each column is a function

using PyPlot
fig = figure("pyplot_plot",figsize=(5,5))
ax = axes()
fill_between(Xtest,-2*diag(K),2*diag(K),facecolor="#a6a6a6")#This fills confindence interval for two standard
#deviations, currently the variance is one (as we take off diagonal)
plot(Xtest,fprior,alpha=0.75)
title("N=3 Multivariate Gaussians")
ylabel("f(x)")
xlabel("x")
grid("off")
show()

D=[(X[i],Y[i]) for i=1:length(X)];
mu,sigma,D = gaussian_process(std_exp_square_ker,D,1e-6,Xtest);
mu=reshape(mu,length(mu));
sigma=reshape(sigma,length(sigma));
y=map(x->x[2],D);   # these are our y noisy function
x=map(x->x[1],D); #These are our x training points
using PyPlot
fig = figure("pyplot_plot",figsize=(5,5))
ax = axes()
fill_between(Xtest,mu-2*sigma,mu+2*sigma,facecolor="#a6a6a6")#This fills confindence interval for two standard
#deviations, currently the variance is one (as we take off diagonal)
plot(x,y,linewidth=0,marker="o")

title("N=3 Multivariate Gaussians")
ylabel("f(x)")
xlabel("x")
grid("off")
show()


function circle_len1(θ; n_pts=1000000)
    xmin, xmax = cos(θ), 1
    ts = sort(rand(n_pts))
    xs = @. (xmax-xmin)/(1. - 0.) * ts + xmin
    ys = @. sqrt(1 - xs * xs)
    d = zero(eltype(xs))
    for i in 1:(length(xs)-1)
        d += sqrt((xs[i+1]-xs[i])^2+(ys[i+1]-ys[i])^2)
    end
    println(d)
    println(θ)
end

function circle_len2(θ; n_pts=1000000)
    xmin, xmax = cos(θ), 1
    t = sort(rand(n_pts))
    x = @. (xmax-xmin)/(1. - 0.) * t + xmin
    y = @. sqrt(1 - x * x)
    ds = @. sqrt((x[2:end] - x[1:end-1])^2+(y[2:end] - y[1:end-1])^2)
    d=sum(ds)
    println(d)
    println(θ)
end

function circle_len3(θ; n_pts=1000000)
    ts = θ * sort(rand(n_pts))
    xs = sin.(ts)
    ys = cos.(ts)
    d = zero(eltype(xs))
    for i in 1:(length(xs)-1)
        d += sqrt((xs[i+1]-xs[i])^2+(ys[i+1]-ys[i])^2)
    end
    println(d)
    println(θ)
end

circle_len3(2π)


#############################
##  Numerical experiments associated with section 5.1.2
#############################
## While the paper does not explicitly treat third kind Volterra equations, an extension to these
## cases is relatively straightforward. Note that while in general there won't be exponential convergence
## unless the involved powers are favorably smooth, our method still achieves higher accuracy than many competitor methods.
##

using ApproxFun, MultivariateOrthogonalPolynomials, BandedMatrices, BlockBandedMatrices, SpecialFunctions, Plots
using SparseVolterraExamples

#############################
## Problem in Equation (17)
#############################
####
## The following block computes the coefficient vector of the approximation for a given polynomial order of approximation n.
## The accuracy obtained depends on both the polynomial order for the multiplication as well as the solution.
## Choosing the same order is fine in most cases but better accuracy can be obtained by adjusting for a given problem.
function solveSec512A(n,multin)
    gf(x) = x^(2/3)*(10/3*x^(7/3)-3/16*x^(14/3));
    Kfun(x,y) = y;
    V = triVolterraFullKernelOpP01(Kfun,n,true);
        V = reflectPabtoPba(n)*WLoweringP01P00(n)*V;
        V = Conversion(Jacobi(0,0,0..1),Jacobi(1,2,0..1))[1:n,1:n]*V;
        V = Multiplication(Fun(x->x^(2/3),Jacobi(1,2,0..1),multin),Jacobi(1,2,0..1))[1:n,1:n]*Derivative(Jacobi(0,1,0..1),1)[1:n,1:n]-V
    coeff = [DirectEvalLHSP10at0(n);V[1:n-1,1:n]] \ [0;pad(Fun(x->gf(x),Jacobi(1,2, 0..1)).coefficients,n-1)]
    return coeff
end
####
## Plot the analytic solution against the computed solution
coeff = solveSec512A(50,40)
plot(Fun(Jacobi(0,1,0..1),coeff),grid=false,xlabel="x",ylabel="u(x)",label="sparse method")
plot!(x->(x)^(10/3),0,1,grid=false,xlabel="x",ylabel="u(x)",label="analytic solution")
####
## Check the numerical error.
plot(x->(Fun(Jacobi(0,1,0..1),coeff)(x)-(x)^(10/3)),0,1,grid=false,xlabel="x",ylabel="error",label=false)

#############################
## Problem in Equation (18)
#############################
####
## The following block computes the coefficient vector of the approximation for a given polynomial order of approximation n.
## The accuracy obtained depends on both the polynomial order for the multiplication as well as the solution.
## Choosing the same order is fine in most cases but better accuracy can be obtained by adjusting for a given problem.
function solveSec512B(n,multin)
    gf(x) = 9/2*x^4-1/20*x^(11/2)-1/6*x^6;
    Kfun(x,y) = sqrt(y);
    V = triVolterraFullKernelOpP01(Kfun,n,true,n);
        V = reflectPabtoPba(n)*WLoweringP01P00(n)*V;
        V = Conversion(Jacobi(0,0,0..1),Jacobi(1,2,0..1))[1:n,1:n]*V;
        V = Multiplication(Fun(x->sqrt(x),Jacobi(1,2,0..1),multin),Jacobi(1,2,0..1))[1:n,1:n]*Derivative(Jacobi(0,1,0..1),1)[1:n,1:n]-(1/20)*Conversion(Jacobi(0,1,0..1),Jacobi(1,2,0..1))[1:n,1:n]*Multiplication(Fun(x->x,Jacobi(0,1,0..1),n),Jacobi(0,1,0..1))[1:n,1:n]-V
    coeff = [DirectEvalLHSP10at0(n);V[1:n-1,1:n]] \ [0;pad(Fun(x->gf(x),Jacobi(1,2, 0..1)).coefficients,n-1)]
    return coeff
end
####
## Plot the analytic solution against the computed solution
coeff = solveSec512B(80,30)
plot(Fun(Jacobi(0,1,0..1),coeff),grid=false,xlabel="x",ylabel="u(x)",label="sparse method")
plot!(x->(x)^(9/2),0,1,grid=false,xlabel="x",ylabel="u(x)",label="analytic solution")
####
## Check the numerical error.
plot(x->(Fun(Jacobi(0,1,0..1),coeff)(x)-(x)^(9/2)),0,1,grid=false,xlabel="x",ylabel="error",label=false)

examples/Section 5-1-2 - Methods for third kind VIDEs.jl


module Flatland

using LinearAlgebra

abstract type Shape end

abstract type AbstractRectangle <: Shape end

mutable struct Rectangle <: AbstractRectangle
    x::Float64
    y::Float64
    l::Float64
    w::Float64
    function Rectangle((x, y); l=1.0, w=1.0)
        return new(x, y, l, w)
    end
end

function Rectangle((xmin, xmax), (ymin, ymax))
    @assert xmin < xmax && ymin < ymax "Corners have to be ordered: `xmin < xmax && ymin < ymax `"
    x = (xmin + xmax) / 2
    y = (ymin + ymax) / 2
    l = xmax - xmin
    w = ymax - ymin
    return Rectangle((x, y), l=l, w=w)
end

mutable struct Square <: AbstractRectangle
    x::Float64
    y::Float64
    l::Float64
    function Square((x, y); l=1.0)
        return new(x, y, l)
    end
end

lw(shape::Rectangle) = shape.l, shape.w
lw(shape::Square) = shape.l, shape.l

mutable struct RegularPolygon{N} <: Shape
    x::Float64
    y::Float64
    R::Float64
    θ::Float64  # angle
    function RegularPolygon((x, y), n::Int; R=1.0, θ=0.0)
        @assert n ≥ 3 "polygons need a minimum of three corners"
        return new{n}(x, y, R, θ)
    end
end

#=
`Circle`s are pretty straightforward, having a center and a radius.
=#

mutable struct Circle <: Shape
    x::Float64
    y::Float64
    R::Float64
    function Circle((x, y); R=1.0)
        return new(x, y, R)
    end
end

abstract type AbstractTriangle <: Shape end

mutable struct Triangle <: AbstractTriangle
    x1::Float64
    x2::Float64
    x3::Float64
    y1::Float64
    y2::Float64
    y3::Float64
    Triangle((x1, y1), (x2, y2), (x3, y3)) = new(x1, x2, x3, y1, y2, y3)
end

ncorners(::AbstractRectangle) = 4
ncorners(::AbstractTriangle) = 3
ncorners(::Circle) = 0
ncorners(::RegularPolygon{N}) where {N} = N

function corners(shape::AbstractRectangle)
    x, y = center(shape)
    l, w = lw(shape)
    return [(x+l/2, y+w/2), (x-l/2, y+w/2), (x-l/2, y-w/2), (x+l/2, y-w/2)]
end

function corners(shape::RegularPolygon)
    x, y = center(shape)
    θ = shape.θ
    R = shape.R
    n = ncorners(shape)
    return [(x+R*cos(t+θ), y+R*sin(t+θ)) for t in range(0, step=2π/n, length=n)]
end

function corners(shape::Triangle)
    return [(shape.x1, shape.y1), (shape.x2, shape.y2), (shape.x3, shape.y3)]
end

center(shape::Shape) = shape.x, shape.y

function center(shape::Triangle)
    (x1, y1), (x2, y2), (x3, y3) = corners(shape)
    return ((x1 + x2 + x3) / 3, (y1 + y2 + y3) / 3)
end

# xycoords returns two of the outline vectors: ycoords and ycoords, for `Circle`, you can specify the number of points to take
# (50 by default).

xycoords(s::Shape) = [first(p) for p in corners(s)], [last(p) for p in corners(s)]

function xycoords(shape::Circle; n=50)
    ts = range(0, 2π, length=n)
    return shape.x .+ shape.R * cos.(ts), shape.y .+ shape.R * sin.(ts)
end

# x,y-bounding

#=
The fuctions below yield the outer limits of the x and y axes of your shape. Can you complete the method as a oneliner?

Hint: The function `extrema` could be useful here...
=#

xlim(shape::Shape) = extrema(xycoords(shape)[1])
xlim(shape::Circle) = (shape.x - shape.R, shape.x + shape.R)
xlim(shape::AbstractRectangle) = (shape.x - 0.5shape.l, shape.x + 0.5shape.l)

ylim(shape::Shape) = extrema(xycoords(shape)[2])
ylim(shape::Circle) = (shape.y - shape.R, shape.y + shape.R)
ylim(shape::AbstractRectangle) = (shape.y - 0.5lw(shape)[2], shape.y + 0.5lw(shape)[2])

boundingbox(shape::Shape) = Rectangle(xlim(shape), ylim(shape))

# # Area

#=
Next, we compute the area of our shapes.
=#

area(shape::AbstractRectangle) = prod(lw(shape))

function area(shape::RegularPolygon)
    R = shape.R
    n = ncorners(shape)
    dθ = 2π / n  # angle of a triangle
    h = R * cos(dθ/2)
    b = R * sin(dθ/2)
    return b * h * n
end

function area(shape::Triangle)
    (x1, y1), (x2, y2), (x3, y3) = corners(shape)
    return abs(x1*y2 + x2*y3 + x3*y1 - y1*x2 - y2*x3 - y3*x1) / 2
end

area(shape::Circle) = shape.R^2 * π

# move, rotate and scale

#=
Moving, rotating and scaling should also work.
Important, the functions work in-place, meaning that the modify your structure (that is why use use `mutable` structures).

For `Circle` and `AbstractRectangle` types, `rotate!` leaves them unchanged.

Hint: Rotations are in radials, so between $0$ and $2\pi$.
=#

function move!(shape::Shape, (dx, dy))
    shape.x += dx
    shape.y += dy
end

function move!(shape::Triangle, (dx, dy))
    shape.x1 += dx
    shape.x2 += dx
    shape.x3 += dx
    shape.y1 += dy
    shape.y2 += dy
    shape.y3 += dy
    shape
end

rotate!(shape::Union{Circle,AbstractRectangle}, dθ) = shape

function rotate!(shape::RegularPolygon, dθ)
    shape.θ += dθ
    shape
end

function rotate!(shape::Triangle, dθ)
    xc, yc = center(shape)
    # center triangle
    move!(shape, (-xc, -yc))
    (x1, y1), (x2, y2), (x3, y3) = corners(shape)
    cosdθ = cos(dθ)
    sindθ = sin(dθ)
    shape.x1 = x1 * cos(dθ) - y1 * sin(dθ)
    shape.x2 = x2 * cos(dθ) - y2 * sin(dθ)
    shape.x3 = x3 * cos(dθ) - y3 * sin(dθ)
    shape.y1 = y1 * cos(dθ) + x1 * sin(dθ)
    shape.y2 = y2 * cos(dθ) + x2 * sin(dθ)
    shape.y3 = y3 * cos(dθ) + x3 * sin(dθ)
    # set to original position
    move!(shape, (xc, yc))
    shape
end

function scale!(shape::Union{Circle,RegularPolygon}, a)
    @assert a > 0 "scaling has to be a positive number"
    shape.R *= a
end

function scale!(shape::Rectangle, a)
    @assert a > 0 "scaling has to be a positive number"
    shape.w *= a
    shape.l *= a
end

function scale!(shape::Square, a)
    @assert a > 0 "scaling has to be a positive number"
    shape.w *= a
end

function scale!(shape::Triangle, a)
    @assert a > 0 "scaling has to be a positive number"
    xc, yc = center(shape)
    move!(shape, (-xc, -yc))
    shape.x1 *= a
    shape.x2 *= a
    shape.x3 *= a
    shape.y1 *= a
    shape.y2 *= a
    shape.y3 *= a
    move!(shape, (xc, yc))
    return shape
end

# plotting utilities

using RecipesBase

#=
OK, let's take a look at our shapes! We use `RecipesBase` to allow plotting.
This falls back on `xycoords` (can you see how it works?), so make sure this method is operational.
=#

@recipe function f(s::Shape)
    xguide --> "x"
    yguide --> "y"
    label --> ""
    aspect_ratio := :equal
    seriestype := :shape
    x, y = xycoords(s)
    return x, y
end

Base.in((x, y), s::Circle) = (s.x - x)^2 + (s.y - y)^2 ≤ s.R^2

function Base.in((x, y), shape::AbstractRectangle)
    xc, yc = center(shape)
    l, w = lw(shape)
    return (xc - 0.5l ≤ x ≤ xc + 0.5l) && (yc - 0.5w ≤ y ≤ yc + 0.5w)
end

crossprod((x1, y1), (x2, y2)) = x1 * y2 - x2 * y1

"""
    same_side((a, b), p, q)

Given a line described by two points, `a` and `b`, check whether two points
`p` and `q` are on the same side.
"""
function same_side((a, b), p, q)
    # normal vector on the line
    n = (a[2] - b[2], b[1] - a[1])
    # check if they are on both sides by projection
	return sign(n ⋅ (p .- a)) == sign(n ⋅ (q .-a ))
end

"""
    linecross((p1, p2), (q1, q2))

Check whether line segments `(p1, p2)` and `(q1, q2)` intersect.
"""
function linecross((p1, p2), (q1, q2))
    v = p2 .- p1
    w = q2 .- q1
    vw = crossprod(v, w)
    t = crossprod(q1 .- p1, w) / vw
    s = crossprod(q1 .- p1, v) / vw
    return 0.0 ≤ t ≤ 1.0 &&  0.0 ≤ s ≤ 1.0
end

function Base.in(q, s::Triangle)
    p1, p2, p3 = corners(s)
    return same_side((p1, p2), p3, q) &&
            same_side((p2, p3), p1, q) &&
            same_side((p3, p1), p2, q)
end

function Base.in(q, shape::Shape)
    corns = corners(shape)
    n = ncorners(shape)
    c = center(shape)
    # check if q is always on the same side as the center
    for i in 1:n-1
        !same_side((corns[i], corns[i+1]), c, q) && return false
    end
    !same_side((corns[end], corns[1]), c, q) && return false
    return true
end

@inline function boundboxes_overlap(shape1::Shape, shape2::Shape)
    (xmin1, xmax1), (xmin2, xmax2) = xlim(shape1), xlim(shape2)
    (ymin1, ymax1), (ymin2, ymax2)  = ylim(shape1), ylim(shape2)
    # check for x and y overlap
    return (xmin1 ≤ xmin2 ≤ xmax1 || xmin1 ≤ xmax2 ≤ xmax1 || xmin2 ≤ xmin1 ≤ xmax2) &&
            (ymin1 ≤ ymin2 ≤ ymax1 || ymin1 ≤ ymax2 ≤ ymax1 ||ymin2 ≤ ymin1 ≤ ymax2)
end

Base.intersect(shape1::AbstractRectangle, shape2::AbstractRectangle) = boundboxes_overlap(shape1, shape2)

function Base.intersect(shape1::T, shape2::T) where {T<:Shape}
    (shape1.x - shape2.x)^2 + (shape1.y - shape2.y)^2 > (shape1.R + shape2.R)^2 && return false
    return center(shape1) ∈ shape2 ||
            center(shape2) ∈ shape1 ||
            any(c->c ∈ shape2, corners(shape1)) ||
            any(c->c ∈ shape1, corners(shape2))
end

function Base.intersect(shape1::Triangle, shape2::Triangle)
    # first check if the bounding boxes overlap
    boundboxes_overlap(shape1, shape2) || return false
    # yes?
    # now check if one shape is within the other one
    p1, p2, p3 = corners(shape1)
    q1, q2, q3 = corners(shape2)
    (p1 ∈ shape2 || q1 ∈ shape1) && return true
    # if not, we have to check whether two lines intersect
    return linecross((p1, p2), (q1, q2)) ||
            linecross((p1, p2), (q2, q3)) ||
            linecross((p1, p2), (q1, q3)) ||
            linecross((p3, p2), (q1, q2)) ||
            linecross((p3, p2), (q2, q3)) ||
            linecross((p3, p2), (q1, q3))
end

function Base.intersect(shape1::Circle, shape2::Circle)
    c1 = center(shape1)
    c2 = center(shape2)
    d = sum(abs2, c1 .- c2) |> sqrt
    return d < shape1.R + shape2.R
end

# random generation

#=
Finally, `randplace!` takes a shape, rotates it randomly and moves it randomly within the bounds of the limits
`(xmin, xmax)` and `(ymin, ymax)`.
=#

function randplace!(shape::Shape, (xmin, xmax), (ymin, ymax); rotate=true)
    # random rotation
    rotate && rotate!(shape, 2π * rand())
    # random tranlation within bound
    dxmin, dxmax = (xmin, xmax) .- xlim(shape)
    dymin, dymax = (ymin, ymax) .- ylim(shape)
    dx = (dxmax - dxmin) * rand() + dxmin
    dy = (dymax - dymin) * rand() + dymin
    move!(shape, (dx, dy))
    return shape
end

function rejection_sampling!(shapes::Vector{<:Shape}, xlims, ylims)
    trials = 0
    n = length(shapes)
    while true
        trials += 1
        for (i, shape) in enumerate(shapes)
            randplace!(shape, xlims, ylims)
            # any intersection with previous shapes: start again
            overlap = false
            for j in 1:i-1
                if intersect(shape, shapes[j])
                    overlap = true
                    break
                end
            end
            overlap && break
            i==n && return shapes, trials
        end
    end
end

# function to place `n` copies of a given `shape`.

function rejection_sampling(shape, n, xlims, ylims)
    shapes = [deepcopy(shape) for i in 1:n]
    trials = rejection_sampling!(shapes, xlims, ylims)
    return shapes, trials
end

end


using Distributions
using StatsBase: var

########## GDEMO ##################
# Example partially based on https://stats.stackexchange.com/a/266672/234110 and
# http://www2.stat.duke.edu/~rcs46/modern_bayes17/lecturesModernBayes17/lecture-7/07-gibbs.pdf.
# We follow the model used in https://github.com/TuringLang/Turing.jl/blob/60724e22a9831066fc2e0e82d428bd4922bda6e8/test/test_utils/models.jl#L2,
# with normally distributed observations and conjugate priors for both parameters:
# λ ~ Gamma(α₀, θ₀)
# m ~ Normal(0, √(1 / λ))
# Xᵢ ~ Normal(m, √(1 / λ)) (iid)

function gdemo_forward(N, α₀, θ₀)
    λ = rand(Gamma(α₀, θ₀))
    σ = √(1 / λ)
    m = rand(Normal(0, σ))
    x = rand(Normal(m, σ), N)
    return (m=m, λ=λ), x
end

function gdemo_logjoint(α₀, θ₀, x, m, λ)
    σ = √(1 / λ)
    return logpdf(Gamma(α₀, θ₀), λ) +
        logpdf(Normal(0, σ), m) +
        sum(logpdf.(Normal(m, σ), x))
end

function gdemo_statistics(x)
    # The conditionals and posterior can be formulated in terms of the following statistics:
    N = length(x) # number of samples
    x̄ = mean(x) # sample mean
    s² = var(x; mean=x̄, corrected=false) # sample variance
    return N, x̄, s²
end

function gdemo_cond_m(α₀, θ₀, x, λ)
    N, x̄, s² = gdemo_statistics(x)
    mₙ = N * x̄ / (N + 1)
    λₙ = λ * (N + 1)
    σₙ = √(1 / λₙ)
    return Normal(mₙ, σₙ)
    # return Normal(7/6, 0.00001)
end

function gdemo_cond_λ(α₀, θ₀, x, m)
    N, x̄, s² = gdemo_statistics(x)
    αₙ = α₀ + (N - 1) / 2 + 1
    βₙ = (s² * N / 2 + m^2 / 2 + inv(θ₀))
    return Gamma(αₙ, inv(βₙ))
end

function gdemo(x; α₀=2.0, θ₀=inv(3.0))
    cond_m((λ,)) = gdemo_cond_m(α₀, θ₀, x, λ)
    cond_λ((m,)) = gdemo_cond_λ(α₀, θ₀, x, m)
    conditionals = (m=cond_m, λ=cond_λ)
    return conditionals
end

############### Mixture of two Gaussians ####################
# see http://www.cs.columbia.edu/~blei/fogm/2015F/notes/mixtures-and-gibbs.pdf
# μ ~ arraydist(Normal.(fill(m, K), fill(λ, K)))
# z ~ arraydist(Categorical.(fill(π, N)))
# x ~ arraydist(Normal.(μ[z], σ))

function mixture_cond_z(π, K, m, λ, σ, x, μ)
    function mixtureweight(x)
        p = π .* pdf.(Normal.(μ, σ), Ref(x))
        return p ./ sum(p)
    end
    return Product(Categorical.(mixtureweight.(x)))
end

function mixture_cond_μ(π, K, m, λ, σ, x, z)
    n = [count(z .== k) for k = 1:K]

    # If there were no observations assigned to center `k`, `n[k] == 0`, and
    # we use the prior instead.
    x_bar = [(n[k] != 0) ? (sum(x[z .== k]) / n[k]) : m for k = 1:K]
    λ_hat = [(n[k] != 0) ? inv(n[k] / σ^2 + 1/λ^2) : λ for k = 1:K]
    μ_hat = [(n[k] != 0) ? x_bar[k] * (n[k] / σ^2) * λ_hat[k] : m for k = 1:K]

    return Product(Normal.(μ_hat, λ_hat))
end

function mixture(π, K, m, λ, σ, x)
    cond_z((μ,)) = mixture_cond_z(π, K, m, λ, σ, x, μ)
    cond_μ((z,)) = mixture_cond_μ(π, K, m, λ, σ, x, z)
    return (z=cond_z, μ=cond_μ)
end

########### 1D ISING MODEL #########################
function ising_conditional(β, i, M)
    return function (neighbours)
        x_prev = neighbours[mod1(i - 1, M - 1)]
        x_next = neighbours[mod1(i, M - 1)]
        p₁ = exp(-β * (x_prev + x_next))
        p₂ = exp(-β * (abs(x_prev - 1) + abs(x_next - 1)))
        return Bernoulli(p₁ / (p₁ + p₂))
    end
end

function isingdemo(β, M)
    # One dimensional Ising model on a circle, without external field;
    # see https://stats.stackexchange.com/a/312044/234110
    conditional_dists = [Symbol(:x, i) => ising_conditional(β, i, M) for i = 1:M]
    conditionals = merge(NamedTuple(), conditional_dists)
end

function ising_conditional(β, i, j, M, N)
    return function (neighbours)
        x_prev = neighbours[mod1(i - 1, M - 1)]
        x_next = neighbours[mod1(i, M - 1)]
        x_down = neighbours[mod1(j - 1, N - 1)]
        x_up = neighbours[mod1(j, N - 1)]
        p₁ = exp(-β * (x_prev + x_next + x_up + x_down))
        p₂ = exp(-β * (abs(x_prev - 1) + abs(x_next - 1) + abs(x_up - 1) + abs(x_down - 1)))
        return Bernoulli(p₁ / (p₁ + p₂))
    end
end

function isingdemo(β, M, N)
    # One dimensional Ising model on a circle, without external field;
    # see https://stats.stackexchange.com/a/312044/234110
    conditional_dists = [Symbol(:x, i, j) => ising_conditional(β, i, j, M, N) for i = 1:M, j = 1:N]
    conditionals = merge(NamedTuple(), conditional_dists)
end

function visualize_ising(θ::AbstractVector{<:Real})
    join(v == 1 ? '\u2588' : ' ' for v in values(θ))
end

function visualize_ising(θ::AbstractMatrix{<:Real})
    join((join(v == 1 ? '\u2588' : ' ' for v in row) for row in eachrow(θ)), '\n')
end


#=
Author: Stephan Scholz
Year: 2021

This file contains a machine learning approach (SciML) for the van der Pol oscillator
=#

# ODE function of van der Pol oscillator
function vdp(dx, x, p, t)

    μ = p[1]

    dx[1] = μ * (x[1] - 1/3 * x[1]^3 - x[2] )
    dx[2] = 1/μ * x[1]
end

tspan = (0.0, 100.0) # time span
x0    = [0.5, 0.5]   # initial values
p     = [1.0]        # parameter: μ
tsteps= 0.1          # saved time steps

using DifferentialEquations
prob = ODEProblem( vdp, x0, tspan, p )      # Build ODEProblem
sol = solve(prob, Tsit5(), saveat = tsteps) # Solve ODEProblem

# using Plots
# plot(sol, title="Solution of the van der Pol oscillator")

# MACHINE LEARNING
function loss(p)
    sol_pred = solve(prob, Tsit5(), p=p, saveat = tsteps)
    loss = sum(abs2, sol - sol_pred)
    return loss, sol_pred
end

pred_param = [] # Stores all found parameters

callback = function (p, l, pred)
    display(l)
    append!(pred_param, p)

    # Tell sciml_train to not halt the optimization. If return true, then
    # optimization stops.
    return false
end

using DiffEqFlux

p_ml = [0.1] # Initial parameter
loss(p_ml)   # Test whether loss function works

# Run ML training
result_ode = DiffEqFlux.sciml_train(loss, p_ml, ADAM(0.1), cb = callback, maxiters=100)

# Build and plot Cost function
x_min = 0.1
x_max = 2
x_step = 0.01
x_range = range(x_min,x_max,step=x_step)
Nlength = length(x_range)

loss_results = zeros(Nlength)
idx = 1;
for i in x_range
   loss_results[idx],_ = loss([i])
   idx += 1;
end

# Cost function
plot(x_range, loss_results, label="loss", title="Cost function", xaxis=("Parameter μ"))

# Save Machine Learning results as gif
@gif for pp in pred_param
    sol_remade = solve(remake(prob, p=[pp]), Tsit5(), saveat=tsteps)

    # Phase portrait
    pp_text = string( "μ = ", string(round(pp; digits=3)) )
    plot(sol_remade[1,:], sol_remade[2,:], annotations=(0.1, 0, Plots.text(pp_text, :left)), title="Phase portrait of van der Pol oscillator", label=false)

    # Oscillation
    # plot(sol_remade, title="Machine Learning of van der Pol oscillator", label=["x1" "x2"], legend=:bottomright)

end


#Demo 2: Reflection coefficients
#-------------------------------------------------------------------------------
using InspectDR
using Colors
import Graphics: width, height

#==Input
===============================================================================#

#Constants
#-------------------------------------------------------------------------------
black = RGB24(0, 0, 0)
white = RGB24(1, 1, 1)
red = RGB24(1, 0, 0)
green = RGB24(0, 1, 0)
blue = RGB24(0, 0, 1)
μ0 = 4pi*1e-7 #F/m
ϵ0 = 8.854e-12 #H/m

#Input data
#-------------------------------------------------------------------------------
fmax = 10e9
fstep = 50e6
f = collect(0:fstep:fmax)
ℓ = 50e-3 #Meters
ZL = Float64[1e6, 377, 60.0]
_colors = [blue, red, green]

#==Equations
===============================================================================#
Γ(Z; Zref::Real=50.0) = (Z - Zref) ./ (Z + Zref)
#ZC: Characteristic impedance
#ZL: Load impendance (termination)
function Zline(ℓ::Real, f::Vector, ZL::Number; ZC::Number=50.0, α::Real=0, μ::Real=μ0, ϵ::Real=ϵ0)
	j = im
	β = f*(2pi*sqrt(μ*ϵ))
	γ = α+j*β
	tanh_γℓ = tanh(γ*ℓ)
	return ZC*(ZL+ZC*tanh_γℓ)./(ZC+ZL*tanh_γℓ)
end
function Γline(ℓ::Real, f::Vector, ZL::Number; ZC::Number=50.0, Zref::Number=50.0, α::Real=0, μ::Real=μ0, ϵ::Real=ϵ0)
	return Γ(Zline(ℓ, f, ZL; ZC=ZC, α=α, μ=μ, ϵ=ϵ), Zref=Zref)
end

#Calculations
#-------------------------------------------------------------------------------
Γload = []
for ZLi in ZL
	_Γ = Γline(ℓ, f, ZLi, ZC=40)
	push!(Γload, _Γ)
end

#==Generate plot
===============================================================================#
mplot = InspectDR.Multiplot(title="Transmission Line Example")
mplot.ncolumns = 2

plot_linf = InspectDR.Plot2D()
	plot_linf.axes = InspectDR.axes(:lin, :dB20)
	plot_linf.ext_full = InspectDR.PExtents2D(ymax=5)
#	plot_linf.layout.legend.enabled=true
plot_logf = InspectDR.Plot2D()
	plot_logf.axes = InspectDR.axes(:log10, :dB20)
	plot_logf.ext_full = InspectDR.PExtents2D(xmin=10e6,ymax=5)
	plot_logf.layout.grid = grid(vmajor=true, vminor=true, hmajor=true)
plot_ysmith = InspectDR.Plot2D()
	plot_ysmith.axes = InspectDR.axes(:smith, :Y)
	plot_ysmith.ext_full = InspectDR.PExtents2D(xmin=-1.2,xmax=1.2,ymin=-1.2,ymax=1.2)
	plot_ysmith.layout.legend.enabled=true
plot_smith = InspectDR.Plot2D()
	plot_smith.axes = InspectDR.axes(:smith, :Z, ref=50)
	plot_smith.ext_full = InspectDR.PExtents2D(xmin=-1.2,xmax=1.2,ymin=-1.2,ymax=1.2)
	plot_smith.layout.legend.enabled=true

for plot in [plot_linf, plot_logf]
	a = plot.annotation
	a.title = "Reflection Coefficient (Γ)"
	a.xlabel = "Frequency (Hz)"
	a.ylabel = "Magnitude (dB)"
end

a = plot_ysmith.annotation
	a.title = "Y-Smith Chart"
	a.xlabel = "Real(Γ)"
	a.ylabel = "Imaginary(Γ)"

a = plot_smith.annotation
	a.title = "Z-Smith Chart"
	a.xlabel = "Real(Γ)"
	a.ylabel = "Imaginary(Γ)"

#Select which plots to actually display:
plotlist = [plot_linf, plot_logf, plot_ysmith, plot_smith]
#plotlist = [plot_smith]

for plot in plotlist
	for i in 1:length(Γload)
		wfrm = add(plot, f, Γload[i], id="ZL=$(ZL[i])")
		wfrm.line = line(color=_colors[i], width=2)
	end

	add(mplot, plot)
end

gplot = display(InspectDR.GtkDisplay(), mplot)

#==Save multi-plot to file
===============================================================================#

maximize_square = true
if maximize_square
	#Target plot size to get square Smith plots without gaps:
	lyt = plot_smith.layout
		lyt.wdata = 500
		lyt.hdata = 500
	bb = InspectDR.plotbounds(lyt, plot_smith.axes) #Required
		mplot.wplot = width(bb)
		mplot.hplot = height(bb)
end

InspectDR.write_png("export_multiplot.png", mplot)
InspectDR.write_svg("export_multiplot.svg", mplot)
InspectDR.write_eps("export_multiplot.eps", mplot)
InspectDR.write_pdf("export_multiplot.pdf", mplot)

:DONE


"""
    eom_mediated_link(u, p, t) -> SVector{9}

Equations of motion for a three-subsystem dynamical system where `X` and `Y` are
driven by `Z`. At the default value of the coupling constant `c = 0`, the
responses `X` and `Y` are already synchronized to the driver `Z`.
The frequencies `ωx`, `ωy` and ``ωz` have near unit ratios by default.

# References
Krakovská, Anna, et al. "Comparison of six methods for the detection of causality in a bivariate time series." Physical Review E 97.4 (2018): 042207
"""
function eom_mediated_link(u, p, t)
    ωx, ωy, ωz, k, l, m, c = (p...,)
    x₁, x₂, x₃, y₁, y₂, y₃, z₁, z₂, z₃ = (u...,)

    dx₁ = -ωx*x₂ - x₃ + c*(z₁ - x₁)
	dx₂ = ωx*x₁ + k*x₂
	dx₃ = l + x₃*(x₁ - m)

	dy₁ = -ωy*y₂ - y₃ + c*(z₁ - y₁)
	dy₂ = ωy*y₁ + k*y₂
	dy₃ = l + y₃*(y₁ - m)

	dz₁ = -ωz*z₂ - z₃
	dz₂ = ωz*z₁ + k*z₂
	dz₃ = l + z₃*(z₁ - m)

    SVector{9}(dx₁, dx₂, dx₃, dy₁, dy₂, dy₃, dz₁, dz₂, dz₃)
end

function mediated_link(u₀, ωx, ωy, ωz, k, l, m, c)
    p = [ωx, ωy, ωz, k, l, m, c]
    ContinuousDynamicalSystem(eom_mediated_link, u₀, p)
end

"""
    mediated_link(;u₀ = rand(9), ωx = 1, ωy = 1.015, ωz = 0.985,
        k = 0.15, l = 0.2, m = 10.0, c = 0.06) -> ContinuousDynamicalSystem

Equations of motion for a three-subsystem dynamical system where `X` and `Y` are
driven by `Z`. At the default value of the coupling constant `c = 0`, the
responses `X` and `Y` are already synchronized to the driver `Z`.

The dynamics is generated by the vector field:

```math
\\begin{aligned}
dx_1 &= -\\omega_x x_2 - x_3 + c*(z_1 - x_1) \\
dx_2 &= \\omega_x x_1 + k*x_2 \\
dx_3 &= l + x_3(x_1 - m) \\
dy_1 &= -\\omega_y y_2 - y_3 + c*(z_1 - y_1) \\
dy_2 &= \\omega_y y_1 + k*y_2 \\
dy_3 &= l + y_3(y_1 - m) \\
dz_1 &= -\\omega_z z_2 - z_3 \\
dz_2 &= \\omega_z z_1 + k*z_2 \\
dz_3 &= l + z_3(z_1 - m) \\
\end{aligned}
```

# References
Krakovská, Anna, et al. "Comparison of six methods for the detection of causality in a bivariate time series." Physical Review E 97.4 (2018): 042207
"""
mediated_link(;u₀ = rand(9), ωx = 1, ωy = 1.015, ωz = 0.985,
            k = 0.15, l = 0.2, m = 10.0, c = 0.06) =
    mediated_link(u₀, ωx, ωy, ωz, k, l, m, c)


"""
Log of the InverseGamma PDF. Equivalent to using Distributions; logpdf(InverseGamma(α, β), x)
https://en.wikipedia.org/wiki/Inverse-gamma_distribution
"""
function log_inverse_gamma(x::Real, α::Real=1., β::Real=1.; d::Integer=0)
    @assert 0 <= d <= 2
    if d == 0
        x > 0 ? val = -(β / x) - (1 + α) * log(x) + α * log(β) - loggamma(α) : val = -Inf
    elseif d == 1
        x > 0 ? val = (β / x - (1 + α)) / x : val = 0
    else
        x > 0 ? val = (-2 * β / x + (1 + α)) / (x * x) : val = 0
    end
    return val
end

function gamma_mode_std_2_alpha_theta(m::Real, s::Real)
    θ = (sqrt(m ^ 2 + 4 * s ^ 2) - m) / 2
    α = m / θ + 1
    return [α, θ]
end

"""
Log of the Gamma PDF. Equivalent to using Distributions; logpdf(Gamma(α, β), x)
https://en.wikipedia.org/wiki/Gamma_distribution
"""
function log_gamma(x::Real, parameters::Vector{<:Real}; d::Integer=0, passed_mode_std::Bool=false)
    @assert 0 <= d <= 2
    @assert length(parameters) == 2
    assert_positive(parameters)
    if passed_mode_std
        parameters = gamma_mode_std_2_alpha_theta(parameters[1], parameters[2])
    end
    α = parameters[1]
    θ = parameters[2]
    if d == 0
        x > 0 ? val = -(x / θ) + (α - 1) * log(x) - α * log(θ) - loggamma(α) : val = -Inf
    elseif d == 1
        x > 0 ? val = (α - 1) / x - 1 / θ : val = 0
    else
        x > 0 ? val = -(α - 1) / (x * x) : val = 0
    end
    return val
end

gauss_cdf(x::Real) = (1 + erf(x))/2

"log of the Gaussian PDF. Equivalent to using Distributions; logpdf(Gaussian(μ, σ), x)"
function log_gaussian(x::Real, parameters::Vector{<:Real}; d::Integer=0, min::Real=-Inf, max::Real=Inf)
    @assert 0 <= d <= 2
    @assert length(parameters) == 2
    μ = parameters[1]
    σ = parameters[2]
    assert_positive(σ)
    @assert min < max
    normalization = 1 - gauss_cdf(min - μ) - gauss_cdf(μ - max)
    if d == 0
        (min < x < max) ? val = -((x - μ)^2/(2 * σ * σ)) - log(sqrt(2 * π) * σ) - log(normalization) : val = -Inf
    elseif d == 1
        (min < x < max) ? val = -(x - μ)/(σ * σ) : val = 0
    else
        (min < x < max) ? val = -1 / (σ * σ) : val = 0
    end
    return val
end

"Log of the Uniform PDF."
function log_uniform(x::Real, min_max::Vector{<:Real}=[0,1]; d::Integer=0)
    @assert 0 <= d <= 2
    @assert length(min_max) == 2
    min, max = min_max
    @assert min < max
    if d == 0
        min <= x <= max ? -log(max - min) : -Inf
    else
        return 0
    end
end

"""
Log of the log-Uniform PDF.
Flattens out in log space starting at shift
Also known as a (modified in shifted case) Jeffrey's prior
"""
function log_loguniform(x::Real, min_max::Vector{<:Real}; d::Integer=0, shift::Real=0)
    @assert 0 <= d <= 2
    @assert length(min_max) == 2
    min, max = min_max
    @assert 0 < min + shift < max + shift
    xpshift = x + shift
    if d == 0
        min <= x <= max ? val = -log(xpshift) - log(log((max + shift)/(min + shift))) : val = -Inf
    elseif d == 1
        min <= x <= max ? val = -1 / xpshift : val = 0
    elseif d == 2
        min <= x <= max ? val = 1 / (xpshift * xpshift) : val = 0
    end
    return val
end

"""
Log of the Rayleigh PDF.
"""
function log_Rayleigh(x::Real, σ::Real; d::Integer=0)
    @assert 0 <= d <= 2
    if d == 0
        0 <= x <= 1 ? val = -(x * x / (2 * σ * σ)) + log(x / σ / σ) -  log(1 - exp(-1 / (2 * σ * σ))) : val = -Inf
    elseif d == 1
        0 <= x <= 1 ? val = 1 / x - x / σ / σ : val = 0
    elseif d == 2
        0 <= x <= 1 ? val = -1 / x / x - 1 / σ / σ : val = 0
    end
    return val
end

"""
Log of the 2D circle PDF
"""
function log_circle(x::Vector{<:Real}, min_max_r::Vector{<:Real}; d::Vector{<:Integer}=[0,0])
    @assert minimum(d) == 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(x) == length(min_max_r) == length(d) == 2
    min_r, max_r = min_max_r

    # @assert min_r < sqrt(dot(x, x)) < max_r
    # @assert min_θ < atan(x[1], x[2]) < max_θ
    if all(d .== 0)
        min_r < sqrt(dot(x, x)) < max_r ? val = -log(2 * π * (max_r ^ 2 - min_r ^ 2)) : val = -Inf
    else
        return 0
    end
    return val
end

"""
Log of the 2D unit cone PDF
"""
function log_cone(x::Vector{<:Real}; d::Vector{<:Integer}=[0,0])
    @assert minimum(d) >= 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(x) == length(d) == 2

    r_sq = dot(x, x)  # x^2 + y^2
    r = sqrt(r_sq)
    if d == [0,0]
        0 <= r < 1 ? val = log(3 / π * (1 - r)) : val = -Inf
    elseif d == [0,1]
        0 <= r < 1 ? val = x[2] / (r_sq - r) : val = 0
    elseif d == [0,2]
        0 <= r < 1 ? val = (-x[2] ^ 2 * r + x[1] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [1,0]
        0 <= r < 1 ? val = x[1] / (r_sq - r) : val = 0
    elseif d == [1,1]
        0 <= r < 1 ? val = (x[1] * x[2] * (1 - 2 * r)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [2,0]
        0 <= r < 1 ? val = (-x[1] ^ 2 * r + x[2] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    end
    return val
end

"""
Log of the 2D unit quadratic cone PDF
"""
function log_quad_cone(x::Vector{<:Real}; d::Vector{<:Integer}=[0,0])
    @assert minimum(d) >= 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(x) == length(d) == 2

    r_sq = dot(x, x)  # x^2 + y^2
    r = sqrt(r_sq)
    if d == [0,0]
        0 <= r < 1 ? val = log(6 / π * (1 - 2 * r + r_sq)) : val = -Inf
    elseif d == [0,1]
        0 <= r < 1 ? val = 2 * x[2] / (r_sq - r) : val = 0
    elseif d == [0,2]
        0 <= r < 1 ? val = 2 * (-x[2] ^ 2 * r + x[1] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [1,0]
        0 <= r < 1 ? val = 2 * x[1] / (r_sq - r) : val = 0
    elseif d == [1,1]
        0 <= r < 1 ? val = (2 * x[1] * x[2] * (1 - 2 * r)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [2,0]
        0 <= r < 1 ? val = 2 * (-x[1] ^ 2 * r + x[2] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    end
    return val
end

"""
Log of the 2D unit cubic cone PDF
"""
function log_cubic_cone(x::Vector{<:Real}; d::Vector{<:Integer}=[0,0])
    @assert minimum(d) >= 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(x) == length(d) == 2

    r_sq = dot(x, x)  # x^2 + y^2
    r = sqrt(r_sq)
    if d == [0,0]
        0 <= r < 1 ? val = log(10 / π * (1 - r)^3) : val = -Inf
    elseif d == [0,1]
        0 <= r < 1 ? val = 3 * x[2] / (r_sq - r) : val = 0
    elseif d == [0,2]
        0 <= r < 1 ? val = 3 * (-x[2] ^ 2 * r + x[1] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [1,0]
        0 <= r < 1 ? val = 3 * x[1] / (r_sq - r) : val = 0
    elseif d == [1,1]
        0 <= r < 1 ? val = (3 * x[1] * x[2] * (1 - 2 * r)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [2,0]
        0 <= r < 1 ? val = 3 * (-x[1] ^ 2 * r + x[2] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    end
    return val
end

"""
Log of the 2D rotated Rayleigh PDF that is cutoff at r=1
ONLY ROUGHLY NORMALIZED according to σ = 1/5
"""
function log_rot_Rayleigh(x::Vector{<:Real}; d::Vector{<:Integer}=[0,0], σ=1/5)
    @assert minimum(d) >= 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(x) == length(d) == 2
    r_sq = dot(x, x)  # x^2 + y^2
    r = sqrt(r_sq)
    σ_sq = σ ^ 2
    log_norm = -2 * log(σ) - 0.454215
    if d == [0,0]
        0 <= r < 1 ? val = -r_sq / (2 * σ_sq) + log(r) + log_norm : val = -Inf
    elseif d == [0,1]
        0 <= r < 1 ? val = -x[2] * (r_sq - σ_sq) / (r_sq * σ_sq) : val = 0
    elseif d == [0,2]
        0 <= r < 1 ? val = -(x[1] ^ 4 + x[1] ^ 2 * (2 * x[2] ^ 2 - σ_sq) + x[2] ^ 2 * (x[2] ^ 2 + σ_sq)) / (r_sq ^ 2 * σ_sq) : val = 0
    elseif d == [1,0]
        0 <= r < 1 ? val = -x[1] * (r_sq - σ_sq) / (r_sq * σ_sq) : val = 0
    elseif d == [1,1]
        0 <= r < 1 ? val = -2 * x[1] * x[2] / r_sq ^ 2 : val = 0
    elseif d == [2,0]
        0 <= r < 1 ? val = -(x[2] ^ 4 + x[2] ^ 2 * (2 * x[1] ^ 2 - σ_sq) + x[1] ^ 2 * (x[1] ^ 2 + σ_sq)) / (r_sq ^ 2 * σ_sq) : val = 0
    end
    return val
end

"""
Log of the bivariate normal PDF
NOTE THAT THAT WHEN USING lows!=[-∞,...], THIS IS NOT PROPERLY NORMALIZED
"""
function log_bvnormal(x::Vector{T}, Σ::Cholesky{T,Matrix{T}}; μ::Vector{T}=zeros(T, length(x)), d::Vector{<:Integer}=[0,0], lows::Vector{T}=zeros(T, length(x)) .- Inf) where {T<:Real}
    @assert minimum(d) >= 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(x) == length(d) == length(μ) == 2
    y = x - μ

    if sum(d) == 0
        all(x .>= lows) ? val = -nlogL(Σ, y) : val = -Inf
    elseif sum(d) == 1
        y1 = Float64.(d)
        all(x .>= lows) ? val = -dnlogLdθ(y1, Σ \ y) : val = 0
    elseif d == [0,2]
        y1 = y2 = [0, 1.]
        all(x .>= lows) ? val = -d2nlogLdθ(y2, [0,0.], Σ \ y, Σ \ y1) : val = 0
    elseif d == [1,1]
        y1 = [1, 0.]
        y2 = [0, 1.]
        all(x .>= lows) ? val = -d2nlogLdθ(y2, [0,0.], Σ \ y, Σ \ y1) : val = 0
    elseif d == [2,0]
        y1 = y2 = [1, 0.]
        all(x .>= lows) ? val = -d2nlogLdθ(y2, [0,0.], Σ \ y, Σ \ y1) : val = 0
    end
    return val
end
function bvnormal_covariance(σ11::Real, σ22::Real, ρ::Real)
    assert_positive(σ11, σ22)
    @assert 0 <= ρ <= 1
    v12 = σ11*σ22*ρ
    return ridge_chol([σ11^2 v12;v12 σ22^2])
end


import Distributions
import SpecialFunctions

"""    gaussianMechConstant(ϵ::Real,δ::Real)
Compute the proportionality constant κ(ϵ,δ) for the Gaussian mechanism.
"""
function gaussianMechConstant(ϵ::Real, δ::Real)
    K = sqrt(2) * SpecialFunctions.erfinv(1-2δ)
    return (K+sqrt(K^2+2ϵ))/(2ϵ)
end

"""    (k₁, k₂) = gaussianMechConstant2(ϵ::Real,δ::Real)
Compute the proportionality constant κ(ϵ,δ) for the Gaussian mechanism,
both with the formula using the inverse Q-function (k₁), and according to the
approximation of Theorem A.1 in Dwork and Roth's book (k₂, higher value).
"""
function gaussianMechConstant2(ϵ::Real, δ::Real)
    K = sqrt(2) * SpecialFunctions.erfinv(1-2δ)
    return ((K+sqrt(K^2+2ϵ))/(2ϵ), sqrt(2log(1.25/δ))/ϵ)
end

"""    mean_dp(x::Vector,epsilon::Float64,delta::Float64=0.0)
Compute the average of a set of numbers contained in the vector x,
in an (ϵ,δ)-differentially private way. It is assumed that each entry
x_i of the vector is in [0,1], and the adjacency relation looks at
arbitrary variations in one entry x_i, within this interval.
"""
function mean_dp(x::Vector, ϵ::Real, δ::Real=0.0)
    if δ == 0.0
        d = Distributions.Laplace(0, 1/(ϵ*length(x)))
    else
        d = Distributions.Normal(0, gaussianMechConstant(ϵ,δ)/length(x))
    end
    return Distributions.mean(x) + Distributions.rand(d)
end

"""    laplaceMech(x,f,l1sens::Real,ϵ::Real)
Computes a randomized version to f(x) according to the
Laplace mechanism, for ϵ-differential privacy. l1sens is
the l1-sensitivity of f, which must be computed and provided by the user.
f must take values in R^k, for some k, i.e., return an array of k real values.
"""
function laplaceMech(x, f, l1sens::Real, ϵ::Real)
    t = f(x)
    d = Distributions.Laplace(0, l1sens/ϵ)
    return (t .+ Distributions.rand(d, length(t)))
end

"""    gaussianMech(x,f,l2sens::Real,ϵ::Real,δ::Real)
Computes a randomized version to f(x) according to the
Gaussian mechanism, for (ϵ,δ)-differential privacy. l2sens is
the l2-sensitivity of f, which must be computed by the user.
f must take values in R^k, from some k, i.e., return an array
of k real values.
"""
function gaussianMech(x, f, l2sens::Real, ϵ::Real, δ::Real)
    t = f(x)
    d = Distributions.Normal(0, gaussianMechConstant(ϵ, δ) * l2sens)
    return (t .+ Distributions.rand(d, length(t)))
end

"""    truncatedLaplaceMech(x,f,l1sens::Real,ϵ::Real,δ::Real)
Computes a randomized version to f(x) according to the
truncated Laplace mechanism, for (ϵ,δ)-differential privacy.
This scheme adds bounded noise.
l1sens is the l1-sensitivity of f, which must be computed and provided by the
user. f must take values in R^k, for some k, i.e., return an array of k real
values.

Returns: (r, a) where r is a noisy version of f(x), and a defines the
support of the noise distribution (in the interval [f(x)-a, f(x)+a])
"""
function truncatedLaplaceMech(x, f, l1sens::Real, ϵ::Real, δ::Real)
    t = f(x)
    k = length(t)
    λ = l1sens/ϵ
    a = λ * log( 1 + exp(ϵ) * (k*(1-exp(-ϵ/k)))/(2*δ) )
    # alternative, more conservative but indpt of k
    # a = λ * log(1 + ϵ * exp(ϵ) / (2*δ))
    d1 = Distributions.Laplace(0, λ)
    d = Distributions.truncated(d1, -a, a)
    return (t .+ Distributions.rand(d, length(t)), a)
end


### A Pluto.jl notebook ###
# v0.17.0

using Markdown
using InteractiveUtils

# ╔═╡ 2d6d0588-f4e4-4cad-8353-9c0ba020fec6
begin
	using FileIO
	using ImageShow
	using ImageIO
	using PlutoUI
	using Cbc
	using DataFrames
	using Distances
	using JuMP
	using LinearAlgebra
end

# ╔═╡ d44f3f70-705b-43a2-85c0-a441e08fbbe6
load("Pydata_Jeddah_22_new-06.png")

# ╔═╡ a12d1135-ce89-414c-866e-14b4415e1990
md"""
# Geographical Clustering of Saudi Cities With Additional Constraint
**Originally Contributed by**: Matthew Helm ([with help from Mathieu Tanneau on Julia Discourse](https://discourse.julialang.org/t/which-jump-jl-solver-for-this-problem/43350/17?u=mthelm85))
**Customized by**: Hassan Alsawadi
"""

# ╔═╡ d35c109a-e5b6-4c01-a9c2-333b883783f2
md"""
#### Intializing packages

_When running this notebook for the first time, this could take up to 5 minutes. Hang in there!_
"""

# ╔═╡ 2877f488-1ab3-46da-8a49-7db65c3d8a45
md"""
## Objective
The goal of this exercise is to cluster 𝑛 cities into 𝑘 groups, minimizing the total pairwise distance between cities and ensuring that the variance in the total populations of each group is relatively small.

For this example, we'll use the 20 most populous cities in Saudi Arabia.
"""

# ╔═╡ cf6d3f77-781d-4cd0-ad45-9c5c58380354
cities = DataFrame(
    city=["Riyadh", "Jeddah", "Mecca", "Medina", "Dammam", "Abha", "Ha'il", "Hofuf", "Al-Mubarraz", "Sakakah", "Taif", "Khamis Mushait", "Jubail", "Buraydah", "Tabuk", "Najran", "Qatif", "Khobar", "Al Bahah", "Hafr Al-Batin"],
    population=[7676654, 4697000, 2042000, 1488782, 1252523, 1093705, 936465, 858395, 837000, 779480, 695000, 667000, 684531, 669000, 667000, 505652, 474573, 455541, 366000, 359000],
    lat=[24.633333, 21.543333, 21.4225, 24.466667, 26.433333, 18.216944, 27.516667, 25.383333, 25.383333, 29.969722, 21.275094, 18.3, 27, 26.333333, 28.397222, 17.491667, 26.556, 26.283333, 20.0125, 28.434167],
    lon=[46.716667, 39.172778, 39.823333, 39.6, 50.1, 42.505278, 41.683333, 49.583333, 49.6, 40.2, 40.406156, 42.733333, 49.666667, 43.966667, 36.578889, 44.132222, 49.996, 50.2, 41.465278, 45.963611])

# ╔═╡ 11c8323a-9cd5-4580-93d9-ebcd085b4551
md"""
## Model Specifics
We will cluster these 20 cities into 3 different groups and we will assume that the ideal or target population  𝑃  for a group is simply the total population of the 20 cities divided by 3:
"""

# ╔═╡ 0a9212cd-4c79-4752-930b-45dce6a1f3e8
begin
	n = size(cities,1)
	k = 3
	P = sum(cities.population) / k
end

# ╔═╡ 7f7439d3-3d3b-4098-8be5-ca9aa59373ce
md"""### Obtaining the distances between each city
Let's leverage the *Distances.jl* package to compute the pairwise Haversine distance between each of the cities in our data
set and store the result in a variable we'll call `dm`:
"""

# ╔═╡ 3e708ffa-05d9-4cf7-ba4c-11ca5a3e43e7
dm = Distances.pairwise(Haversine(6372.8), Matrix(cities[:, [3,4]])', dims=2)

# ╔═╡ 54082ac4-c31d-4d27-9cc3-5248018c6838
md"""Our distance matrix is symmetric so we'll convert it to a `LowerTriangular` matrix so that we can better interpret the
objective value of our model (if we don't do this the total distance will be doubled):"""

# ╔═╡ 6bc42cc7-e3d4-4470-bece-6af950280372
dm2 = LowerTriangular(dm)

# ╔═╡ a5f3abe7-a843-4ea5-8e77-dfd075b243a5
md"""
## Build the model
Now that we have the basics taken  care of, we can set up our model, create decision variables, add constraints, and then
solve.

First, we'll set up a model that leverages the [Cbc](https://github.com/coin-or/Cbc) solver. Next, we'll set up a binary
variable $x_{i,k}$ that takes the value $1$ if city $i$ is in group $k$ and $0$ otherwise. Each city must be in a group, so
we'll add the constraint $\sum_kx_{i,k} = 1$ for every $i$.
"""

# ╔═╡ ee41a6b3-fb13-4269-ab22-ad2a85bf26e5
begin
	model = Model(Cbc.Optimizer)

	@variable(model, x[1:n, 1:k], Bin)

	for i in 1:n
	    @constraint(model, sum(x[i,:]) == 1)
	end
end

# ╔═╡ dd262e8b-4697-4aa7-9bc7-a32ac25c96dc
md"""
The total population of a group $k$ is $Q_k = \sum_ix_{i,k}q_i$ where $q_i$ is simply the $i$th value from the `population`
column in our `cities` DataFrame. Let's add constraints so that $\alpha \leq (Q_k - P) \leq \beta$. We'll set $\alpha$
equal to -2,500,000 and $\beta$ equal to 2,500,000. By adjusting these thresholds you'll find that there is a tradeoff
between having relatively even populations between groups and having geographically close cities within each group. In
other words, the larger the absolute values of $\alpha$ and $\beta$, the closer together the cities in a group will be but
the variance between the group populations will be higher.
"""

# ╔═╡ d5fc7ca7-33cd-4f8a-aecf-8897bcd40e15
begin
	α = -2_500_000
	β = 2_500_000
	for i in 1:k
	    @constraint(model, (x' * cities.population)[i] - P <= β)
	    @constraint(model, (x' * cities.population)[i] - P >= α)
	end
end

# ╔═╡ 4ab84877-f5d8-4c1b-b0d2-346f0048bb20
md"""
Now we need to add one last binary variable $z_{i,j}$ to our model that we'll use to compute the total distance between the
cities in our groups, defined as  $\sum_{i,j}d_{i,j}z_{i,j}$. Variable $z_{i,j}$ will equal $1$ if cities $i$ and $j$ are
in the same group, and $0$ if they are not in the same group.

To ensure that $z_{i,j} = 1$ if and only if cities $i$ and $j$ are in the same group, we add the constraints $z_{i,j} \geq
x_{i,k} + x_{j,k} - 1$ for every pair $i,j$ and every $k$:
"""

# ╔═╡ 1054b6a4-1c28-46b6-88a4-6f7cae84edee
begin
	@variable(model, z[1:n,1:n], Bin)

	for k in 1:k, i in 1:n, j in 1:n
	    @constraint(model, z[i,j] >= x[i,k] + x[j,k] - 1)
	end
end

# ╔═╡ 07f58656-4826-4ce5-8c97-bf5570f0fc60
md"""
We can now add an objective to our model which will simply be to minimize the dot product of $z$ and our distance matrix,
`dm`. We can then call `optimize!` and review the results.
"""

# ╔═╡ 17088a0b-a568-431c-8d01-13f957e2a4c0
begin
	@objective(model, Min, dot(z,dm));

	optimize!(model)
end

# ╔═╡ 4ded521e-c41f-4927-854b-86f8912269db
md"""## Reviewing the Results
Now that we have results, we can add a column to our `cities` DataFrame for the group and then loop through our $x$
variable to assign each city to its group. Once we have that, we can look at the total population for each group and also
plot the cities and their groups to verify visually that they are grouped by geographic proximity.
"""

# ╔═╡ dab4e559-9dc6-4a72-8d38-9a0487b6251a
PlutoUI.with_terminal() do
	cities.group = zeros(n)

	for i in 1:n, j in 1:k
	    if round(value.(x)[i,j]) == 1.0
	        cities.group[i] = j
	    end
	end

	for group in groupby(cities, :group)
	    @show group
	    println("")
	    @show sum(group.population)
	    println("")
	end
end

# ╔═╡ aa340dc7-8833-4e40-b63e-c602096e40e3
md"""
The populations of each group are fairly even and we can see from the plot below that the groupings look good in terms of
geographic proximity:
"""

# ╔═╡ a2fad4bc-66fa-4466-ae29-b495590f035f
load("output.png")

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19"
ImageShow = "4e3cecfd-b093-5904-9786-8bbb286a6a31"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
Cbc = "~0.9.1"
DataFrames = "~1.3.2"
Distances = "~0.10.7"
FileIO = "~1.13.0"
ImageIO = "~0.6.1"
ImageShow = "~0.3.3"
JuMP = "~0.22.3"
PlutoUI = "~0.7.34"
"""

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

[[ASL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "6252039f98492252f9e47c312c8ffda0e3b9e78d"
uuid = "ae81ac8f-d209-56e5-92de-9978fef736f9"
version = "0.1.3+0"

[[AbstractFFTs]]
deps = ["ChainRulesCore", "LinearAlgebra"]
git-tree-sha1 = "6f1d9bc1c08f9f4a8fa92e3ea3cb50153a1b40d4"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.1.0"

[[AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.4"

[[Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "af92965fb30777147966f58acb05da51c5616b5f"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.3"

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

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

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

[[BenchmarkTools]]
deps = ["JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"]
git-tree-sha1 = "4c10eee4af024676200bc7752e536f858c6b8f93"
uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
version = "1.3.1"

[[BinaryProvider]]
deps = ["Libdl", "Logging", "SHA"]
git-tree-sha1 = "ecdec412a9abc8db54c0efc5548c64dfce072058"
uuid = "b99e7846-7c00-51b0-8f62-c81ae34c0232"
version = "0.5.10"

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.8+0"

[[CEnum]]
git-tree-sha1 = "215a9aa4a1f23fbd05b92769fdd62559488d70e9"
uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
version = "0.4.1"

[[Calculus]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad"
uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9"
version = "0.5.1"

[[Cbc]]
deps = ["BinaryProvider", "CEnum", "Cbc_jll", "Libdl", "MathOptInterface", "SparseArrays"]
git-tree-sha1 = "6656166f484075dd146c9f452b1428116eaf76d4"
uuid = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
version = "0.9.1"

[[Cbc_jll]]
deps = ["ASL_jll", "Artifacts", "Cgl_jll", "Clp_jll", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Osi_jll", "Pkg"]
git-tree-sha1 = "a3c5986d7713bce4260d9826deead060a17c8e2d"
uuid = "38041ee0-ae04-5750-a4d2-bb4d0d83d27d"
version = "200.1000.501+0"

[[Cgl_jll]]
deps = ["Artifacts", "Clp_jll", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Osi_jll", "Pkg"]
git-tree-sha1 = "11eb7b7688925e9751b5d7a187aaa4291eae2664"
uuid = "3830e938-1dd0-5f3e-8b8e-b3ee43226782"
version = "0.6000.300+0"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "7dd38532a1115a215de51775f9891f0f3e1bac6a"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.12.1"

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

[[Clp_jll]]
deps = ["Artifacts", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "METIS_jll", "MUMPS_seq_jll", "OpenBLAS32_jll", "Osi_jll", "Pkg"]
git-tree-sha1 = "b1031dcfbb44553194c9e650feb5ab65e372504f"
uuid = "06985876-5285-5a41-9fcb-8948a742cc53"
version = "100.1700.601+0"

[[CodecBzip2]]
deps = ["Bzip2_jll", "Libdl", "TranscodingStreams"]
git-tree-sha1 = "2e62a725210ce3c3c2e1a3080190e7ca491f18d7"
uuid = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd"
version = "0.7.2"

[[CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"

[[CoinUtils_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Pkg"]
git-tree-sha1 = "44173e61256f32918c6c132fc41f772bab1fb6d1"
uuid = "be027038-0da8-5614-b30d-e42594cb92df"
version = "200.1100.400+0"

[[ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[ColorVectorSpace]]
deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "SpecialFunctions", "Statistics", "TensorCore"]
git-tree-sha1 = "3f1f500312161f1ae067abe07d13b40f78f32e07"
uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
version = "0.9.8"

[[Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

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

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

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

[[Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.1.1"

[[DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "ae02104e835f219b8930c7664b8012c93475c340"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.3.2"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "3daef5523dd2e769dad2365274f760ff5f282c7d"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.11"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

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

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

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

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

[[Distances]]
deps = ["LinearAlgebra", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "3258d0659f812acde79e8a74b11f17ac06d0ca04"
uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
version = "0.10.7"

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

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

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

[[FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "80ced645013a5dbdc52cf70329399c35ce007fae"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.13.0"

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

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

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

[[Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[Graphics]]
deps = ["Colors", "LinearAlgebra", "NaNMath"]
git-tree-sha1 = "1c5a84319923bea76fa145d49e93aa4394c73fc2"
uuid = "a2bd30eb-e257-5431-a919-1863eab51364"
version = "1.1.1"

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

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

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

[[ImageBase]]
deps = ["ImageCore", "Reexport"]
git-tree-sha1 = "b51bb8cae22c66d0f6357e3bcb6363145ef20835"
uuid = "c817782e-172a-44cc-b673-b171935fbb9e"
version = "0.1.5"

[[ImageCore]]
deps = ["AbstractFFTs", "ColorVectorSpace", "Colors", "FixedPointNumbers", "Graphics", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "Reexport"]
git-tree-sha1 = "9a5c62f231e5bba35695a20988fc7cd6de7eeb5a"
uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
version = "0.9.3"

[[ImageIO]]
deps = ["FileIO", "JpegTurbo", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs"]
git-tree-sha1 = "464bdef044df52e6436f8c018bea2d48c40bb27b"
uuid = "82e4d734-157c-48bb-816b-45c225c6df19"
version = "0.6.1"

[[ImageShow]]
deps = ["Base64", "FileIO", "ImageBase", "ImageCore", "OffsetArrays", "StackViews"]
git-tree-sha1 = "d0ac64c9bee0aed6fdbb2bc0e5dfa9a3a78e3acc"
uuid = "4e3cecfd-b093-5904-9786-8bbb286a6a31"
version = "0.3.3"

[[Imath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "87f7662e03a649cffa2e05bf19c303e168732d3e"
uuid = "905a6f67-0a94-5f89-b386-d35d92009cd1"
version = "3.1.2+0"

[[IndirectArrays]]
git-tree-sha1 = "012e604e1c7458645cb8b436f8fba789a51b257f"
uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959"
version = "1.0.0"

[[Inflate]]
git-tree-sha1 = "f5fc07d4e706b84f72d54eedcc1c13d92fb0871c"
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
version = "0.1.2"

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

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

[[InvertedIndices]]
git-tree-sha1 = "bee5f1ef5bf65df56bdd2e40447590b272a5471f"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.1.0"

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

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

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

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.3"

[[JpegTurbo]]
deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"]
git-tree-sha1 = "a77b273f1ddec645d1b7c4fd5fb98c8f90ad10a5"
uuid = "b835a17e-a41a-41e7-81f0-2f016b05efe0"
version = "0.1.1"

[[JpegTurbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b53380851c6e6664204efb2e62cd24fa5c47e4ba"
uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
version = "2.1.2+0"

[[JuMP]]
deps = ["Calculus", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "NaNMath", "OrderedCollections", "Printf", "Random", "SparseArrays", "SpecialFunctions", "Statistics"]
git-tree-sha1 = "fe0f87cc077fc6a23c21e469318993caf2947d10"
uuid = "4076af6c-e467-56ae-b986-b466b2749572"
version = "0.22.3"

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

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

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

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

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

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

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

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

[[METIS_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "1d31872bb9c5e7ec1f618e8c4a56c8b0d9bddc7e"
uuid = "d00139f3-1899-568f-a2f0-47f597d42d70"
version = "5.1.1+0"

[[MUMPS_seq_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "METIS_jll", "OpenBLAS32_jll", "Pkg"]
git-tree-sha1 = "29de2841fa5aefe615dea179fcde48bb87b58f57"
uuid = "d7ed1dd3-d0ae-5e8e-bfb4-87a502085b8d"
version = "5.4.1+0"

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

[[MappedArrays]]
git-tree-sha1 = "e8b359ef06ec72e8c030463fe02efe5527ee5142"
uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
version = "0.4.1"

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

[[MathOptInterface]]
deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "JSON", "LinearAlgebra", "MutableArithmetics", "OrderedCollections", "Printf", "SparseArrays", "Test", "Unicode"]
git-tree-sha1 = "e8c9653877adcf8f3e7382985e535bb37b083598"
uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
version = "0.10.9"

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

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.2"

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

[[MosaicViews]]
deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"]
git-tree-sha1 = "b34e3bc3ca7c94914418637cb10cc4d1d80d877d"
uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389"
version = "0.3.3"

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

[[MutableArithmetics]]
deps = ["LinearAlgebra", "SparseArrays", "Test"]
git-tree-sha1 = "842b5ccd156e432f369b204bb704fd4020e383ac"
uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
version = "0.3.3"

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

[[Netpbm]]
deps = ["FileIO", "ImageCore"]
git-tree-sha1 = "18efc06f6ec36a8b801b23f076e3c6ac7c3bf153"
uuid = "f09324ee-3d7c-5217-9330-fc30815ba969"
version = "1.0.2"

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

[[OffsetArrays]]
deps = ["Adapt"]
git-tree-sha1 = "043017e0bdeff61cfbb7afeb558ab29536bbb5ed"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.10.8"

[[OpenBLAS32_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "ba4a8f683303c9082e84afba96f25af3c7fb2436"
uuid = "656ef2d0-ae68-5445-9ca0-591084a874a2"
version = "0.3.12+1"

[[OpenEXR]]
deps = ["Colors", "FileIO", "OpenEXR_jll"]
git-tree-sha1 = "327f53360fdb54df7ecd01e96ef1983536d1e633"
uuid = "52e1d378-f018-4a11-a4be-720524705ac7"
version = "0.3.2"

[[OpenEXR_jll]]
deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "923319661e9a22712f24596ce81c54fc0366f304"
uuid = "18a262bb-aa17-5467-a713-aee519bc75cb"
version = "3.1.1+0"

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

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

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[Osi_jll]]
deps = ["Artifacts", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Pkg"]
git-tree-sha1 = "28e0ddebd069f605ab1988ab396f239a3ac9b561"
uuid = "7da25872-d9ce-5375-a4d3-7a845f58efdd"
version = "0.10800.600+0"

[[PNGFiles]]
deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"]
git-tree-sha1 = "eb4dbb8139f6125471aa3da98fb70f02dc58e49c"
uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
version = "0.3.14"

[[PaddedViews]]
deps = ["OffsetArrays"]
git-tree-sha1 = "03a7a85b76381a3d04c7a1656039197e70eda03d"
uuid = "5432bcbf-9aad-5242-b902-cca2824c8663"
version = "0.5.11"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "13468f237353112a01b2d6b32f3d0f80219944aa"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.2"

[[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"

[[PkgVersion]]
deps = ["Pkg"]
git-tree-sha1 = "a7a7e1a88853564e551e4eba8650f8c38df79b37"
uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
version = "0.1.1"

[[PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "8979e9802b4ac3d58c503a20f2824ad67f9074dd"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.34"

[[PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "db3a23166af8aebf4db5ef87ac5b00d36eb771e2"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.4.0"

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

[[PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "dfb54c4e414caa595a1f2ed759b160f5a3ddcba5"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.3.1"

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

[[Profile]]
deps = ["Printf"]
uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"

[[ProgressMeter]]
deps = ["Distributed", "Printf"]
git-tree-sha1 = "afadeba63d90ff223a6a48d2009434ecee2ec9e8"
uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
version = "1.7.1"

[[QOI]]
deps = ["ColorTypes", "FileIO", "FixedPointNumbers"]
git-tree-sha1 = "18e8f4d1426e965c7b532ddd260599e1510d26ce"
uuid = "4b34888f-f399-49d4-9bb3-47ed5cae4e65"
version = "1.0.0"

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

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

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"

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

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

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

[[Sixel]]
deps = ["Dates", "FileIO", "ImageCore", "IndirectArrays", "OffsetArrays", "REPL", "libsixel_jll"]
git-tree-sha1 = "8fb59825be681d451c246a795117f317ecbcaa28"
uuid = "45858cf5-a6b0-47a3-bbea-62219f50df47"
version = "0.1.2"

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

[[SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

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

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

[[StackViews]]
deps = ["OffsetArrays"]
git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c"
uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15"
version = "0.1.1"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "95c6a5d0e8c69555842fc4a927fc485040ccc31c"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.3.5"

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

[[StatsAPI]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "c3d8ba7f3fa0625b062b82853a7d5229cb728b6b"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.2.1"

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

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "bb1064c9a84c52e277f1096cf41434b675cd368b"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.1"

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

[[TensorCore]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6"
uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50"
version = "0.1.1"

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

[[TiffImages]]
deps = ["ColorTypes", "DataStructures", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "OffsetArrays", "PkgVersion", "ProgressMeter", "UUIDs"]
git-tree-sha1 = "991d34bbff0d9125d93ba15887d6594e8e84b305"
uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
version = "0.5.3"

[[TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

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

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

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

[[libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+0"

[[libsixel_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "78736dab31ae7a53540a6b752efc61f77b304c5b"
uuid = "075b6546-f08a-558a-be8f-8157d0f608a5"
version = "1.8.6+1"

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

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

# ╔═╡ Cell order:
# ╟─d44f3f70-705b-43a2-85c0-a441e08fbbe6
# ╟─a12d1135-ce89-414c-866e-14b4415e1990
# ╟─d35c109a-e5b6-4c01-a9c2-333b883783f2
# ╠═2d6d0588-f4e4-4cad-8353-9c0ba020fec6
# ╟─2877f488-1ab3-46da-8a49-7db65c3d8a45
# ╠═cf6d3f77-781d-4cd0-ad45-9c5c58380354
# ╟─11c8323a-9cd5-4580-93d9-ebcd085b4551
# ╠═0a9212cd-4c79-4752-930b-45dce6a1f3e8
# ╟─7f7439d3-3d3b-4098-8be5-ca9aa59373ce
# ╠═3e708ffa-05d9-4cf7-ba4c-11ca5a3e43e7
# ╟─54082ac4-c31d-4d27-9cc3-5248018c6838
# ╠═6bc42cc7-e3d4-4470-bece-6af950280372
# ╟─a5f3abe7-a843-4ea5-8e77-dfd075b243a5
# ╠═ee41a6b3-fb13-4269-ab22-ad2a85bf26e5
# ╟─dd262e8b-4697-4aa7-9bc7-a32ac25c96dc
# ╠═d5fc7ca7-33cd-4f8a-aecf-8897bcd40e15
# ╟─4ab84877-f5d8-4c1b-b0d2-346f0048bb20
# ╠═1054b6a4-1c28-46b6-88a4-6f7cae84edee
# ╟─07f58656-4826-4ce5-8c97-bf5570f0fc60
# ╠═17088a0b-a568-431c-8d01-13f957e2a4c0
# ╟─4ded521e-c41f-4927-854b-86f8912269db
# ╠═dab4e559-9dc6-4a72-8d38-9a0487b6251a
# ╟─aa340dc7-8833-4e40-b63e-c602096e40e3
# ╟─a2fad4bc-66fa-4466-ae29-b495590f035f
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


@doc raw"""
    galerkin_projection(
        f, B::AbstractBSplineBasis,
        [deriv = Derivative(0)], [VectorType = Vector{Float64}],
    )

Perform Galerkin projection of a function `f` onto the given basis.

By default, returns a vector with values

```math
φ_i = ⟨ b_i, f ⟩
= ∫_a^b b_i(x) \, f(x) \, \mathrm{d}x,
```

where ``a`` and ``b`` are the boundaries of the B-spline basis
``\{ b_i \}_{i = 1}^N``.

The integrations are performed using Gauss--Legendre quadrature.
The number of quadrature nodes is chosen so that the result is exact when ``f``
is a polynomial of degree ``k - 1`` (or, more generally, a spline belonging to
the space spanned by the basis `B`).
Here ``k`` is the order of the B-spline basis.
In the more general case, this function returns a quadrature approximation of
the projection.

See also [`galerkin_projection!`](@ref) for the in-place operation, and
[`galerkin_matrix`](@ref) for more details.
"""
function galerkin_projection(
        f, B::AbstractBSplineBasis,
        deriv = Derivative(0),
        ::Type{V} = Vector{Float64},
    ) where {V <: AbstractVector}
    N = length(B)
    φ = V(undef, N)
    galerkin_projection!(f, φ, B, deriv)
end

"""
    galerkin_projection!(
        f, φ::AbstractVector, B::AbstractBSplineBasis, [deriv = Derivative(0)],
    )

Compute Galerkin projection ``φ_i = ⟨ b_i, f ⟩``.

See [`galerkin_projection`](@ref) for details.
"""
function galerkin_projection!(
        f, φ::AbstractVector, B::AbstractBSplineBasis, deriv = Derivative(0),
    )
    N = length(B)
    if length(φ) != N
        throw(DimensionMismatch("incorrect length of output vector φ"))
    end
    Base.require_one_based_indexing(φ)

    k = order(B)
    ts = knots(B)

    # Quadrature information (nodes, weights).
    quadx, quadw = _quadrature_prod(Val(2k - 2))
    @assert length(quadx) == k  # we need k quadrature points per knot segment

    fill!(φ, 0)

    nlast = last(eachindex(ts))

    # We loop over all knot segments Ω[n] = (ts[n], ts[n + 1]).
    # For all B-splines with support in this segment, we integrate the product
    # B[i] * f over this segment, adding the result to φ[i].
    @inbounds for n in eachindex(ts)
        n == nlast && break
        tn, tn1 = ts[n], ts[n + 1]
        tn1 == tn && continue  # interval of length = 0

        metric = QuadratureMetric(tn, tn1)

        # Unnormalise quadrature nodes, such that xs ∈ [tn, tn1]
        xs = metric .* quadx
        # @assert all(x -> tn ≤ x ≤ tn1, xs)

        is = nonzero_in_segment(B, n)

        # This is a property of B-spline bases, which should be preserved by
        # derived (recombined) bases.
        @assert 0 < length(is) ≤ k

        # Evaluate all required basis functions on quadrature nodes.
        bis = eval_basis_functions(B, is, xs, deriv)

        fs = ntuple(i -> f(xs[i]), Val(length(xs)))  # this works fine!

        # For some reason, the alternatives below allocate and give bad
        # performance... (on Julia 1.7-beta2)
        # fs = map(f, xs)
        # fs = f.(xs)

        for (ni, i) in enumerate(is)
            bs = bis[ni]
            φ[i] += metric.α * ((bs .* fs) ⋅ quadw)
        end
    end

    φ
end


#=
  From https://probmods.org/chapters/conditioning.html
  (WebPPL):
  """
  This classic Bayesian inference task is a special case of conditioning. Kahneman and Tversky,
  and Gigerenzer and colleagues, have studied how people make simple judgments like the following:

  The probability of breast cancer is 1% for a woman at 40 who participates in a routine screening.
  If a woman has breast cancer, the probability is 80% that she will have a positive mammography.
  If a woman does not have breast cancer, the probability is 9.6% that she will also have a
  positive mammography. A woman in this age group had a positive mammography in a routine
  screening. What is the probability that she actually has breast cancer?

  What is your intuition? Many people without training in statistical inference judge the
  probability to be rather high, typically between 0.7 and 0.9. The correct answer is much lower,
  less than 0.1, as we can see by running this WebPPL inference:
  """

  p(breastCancer): 0.0737

  According to the WebPPL model:
    breastCancer:
    false : 0.922360248447205
    true : 0.07763975155279507

=#

using Turing
include("jl_utils.jl")

@model function medical_diagnosis2()
    breastCancer ~ flip(0.01)
    positiveMammogram ~ breastCancer ? flip(0.8) : flip(0.096)

    true ~ Dirac(positiveMammogram)

end

model = medical_diagnosis2()

chns = sample(model, MH(), 10_000)
# chns = sample(model, PG(15), 10_000)
# chns = sample(model, IS(), 10_000)
# chns = sample(model, SMC(), 10_000)

display(chns)


using Pkg; Pkg.add("Plots")
using Plots
# IJulia.clear_output(true)

# Define functions and parameters
H=4000.;L=H/10.;A=20.;C=50.;D=2.5;B=8.;E=5.e-4;
ft(x,L)=exp(-x/L)
th1(z)=A*ft.(-z .+ L, L);
th2(z)=C*ft(D*(-z .+ L), L);

# Plot for equally spaced discrete points
npt=1000;
z=-collect(range(0, 1, length=npt)) .* H
phi1=th1.(z)
phi2=th2.(z)
theta=phi1 .- phi2 .+ B .+ E .* z;
plot( theta ,z,label=false, lw=3, color = :black )
plot!(phi1 .+ B ,z,label=false , line=(:dash) )
plot!(phi2 .+ B ,z,label=false , line=(:dash) )

# Plot for ECCO 50-level discrete points
dzECCO50l=[10.00, 10.00, 10.00, 10.00, 10.00, 10.00, 10.00, 10.01,
    10.03, 10.11, 10.32, 10.80, 11.76, 13.42, 16.04 , 19.82,
    24.85,31.10, 38.42, 46.50, 55.00, 63.50, 71.58, 78.90,
     85.15, 90.18, 93.96, 96.58, 98.25, 99.25,100.01,101.33,
    104.56,111.33,122.83,139.09,158.94,180.83,203.55,226.50,
     249.50,272.50,295.50,318.50,341.50,364.50,387.50,410.50,
    433.50,456.50];
dz=dzECCO50l;
z=-(cumsum(dz).-dz./2);
phi1=th1.(z)
phi2=th2.(z)
theta=phi1 .- phi2 .+ B .+ E .* z;
plot( theta ,z,label=false, lw=3, color = :black )
plot!(phi1 .+ B ,z,label=false , line=(:dash) )
plot!(phi2 .+ B ,z,label=false , line=(:dash) )


#!/usr/bin/julia

# Date: 21 August 2016
# https://github.com/trizen

# An efficient algorithm for computing large Fibonacci numbers, modulus some n.
# Algorithm from: http://codeforces.com/blog/entry/14516

function fibmod(n::Int64, mod::Int64)

    n <= 1 && return n
    cache = Dict{Int64, Int64}()

    function f(n::Int64)

        n <= 1           && return 1
        haskey(cache, n) && return cache[n]

        k = div(n, 2)

        cache[n] =
            if n % 2 == 0
                (f(k) * f(k  ) + f(k-1) * f(k-1)) % mod
            else
                (f(k) * f(k+1) + f(k-1) * f(k  )) % mod
            end
    end

    f(n-1)
end

println(fibmod(1000, 10^4))


data = [
    8.9 14.0 4.3 19.9 2.1 28.0 3.6 1.3 4.3;
    13.5 9.3 4.1 17.5 4.5 26.6 5.7 2.1 4.0;
    18.0 9.9 3.3 19.5 5.7 28.1 4.8 2.4 6.5;
    13.9 10.0 4.7 25.8 2.2 24.0 6.2 1.6 2.9;
    9.5 13.6 3.6 23.4 2.5 22.4 4.2 1.8 3.7;
    13.1 10.1 3.1 23.8 2.3 25.6 2.8 2.4 4.9;
    17.4 5.7 4.7 20.6 4.3 24.3 4.7 3.4 3.3;
    11.4 12.5 4.1 18.8 3.4 18.6 5.2 1.5 3.8
]

N = size(data)[1]
prototypes = copy(data)
clusters = [[i] for i = 1:N]
C = length(clusters)

d²(x⃗, y⃗) = sum((x⃗ - y⃗).^2)
d′(x⃗ᵢ, x⃗ⱼ, nᵢ, nⱼ) = ((nᵢ * nⱼ) / (nᵢ + nⱼ)) * sum((x⃗ᵢ - x⃗ⱼ).^2)

T = 2 # C

for t = 1:T
    D = [i<j ? d′(prototypes[i, :], prototypes[j, :], length(clusters[i]), length(clusters[j])) : (i == j ? Inf : Inf) for i = 1:C, j = 1:C]
    _, idx = findmin(D)
    i, j = idx[1], idx[2]

    append!(clusters[i], clusters[j])
    deleteat!(clusters, j)
    C = length(clusters)

    prototypes = Matrix{Float64}(undef, C, 9)
    for c = 1:C
        vectors = [data[k, :] for k in clusters[c]]
        mean = sum(vectors) / length(vectors)
        prototypes[c, :] = mean
    end

    variances = zeros(C)
    for r = 1:C
        for k in clusters[r]
            variances[r] += d²(data[k, :], prototypes[r, :])
        end
    end
    total_variance = sum(variances)
    println("total variance = $total_variance")
end


"""
```
function interp(x::Vector{T}, x_knot::Vector{T}) where T<:AbstractFloat
```
Compute linear interpolation over 1D grid. It extrapolates outside of
knot points. (SeHyoun Ahn, March 2017)

# Arguments:
- `x_fine`: x grid to interpolate to
- `x_knot`: x knot points to interpolate from

# Output:
`V`: matrix giving interpolation values

# Example:
```
x_fine = linspace(-1,1,300)'
x_knot = linspace(0,2,20)'
V = interp(x_fine, x_knot)
plot(V * (x_knot.^5 + x_knot * 0.5))
```
"""
function interp(x::Vector{T}, x_knot::Vector{T}) where {T<:AbstractFloat}
    loc = sum(broadcast(-, x, x_knot') .>= 0, dims=2)
    loc = max.(min.(loc, length(x_knot) - 1), 1)

    t = (x - x_knot[loc]) ./ (x_knot[loc+1] - x_knot[loc])
    ind_x = 1:length(x)

    i = vec(repeat(ind_x,2,1))
    j = [loc; loc + 1]
    v = [(1 - t); t]

    return SparseArrays.sparse(i, j, v, length(x), length(x_knot))
end

"""
```
function interp(x_fine::Vector{T}, y_fine::Vector{T},
                x_knot::Vector{T}, y_knot::Vector{T}) where {T<:Float64}
```
Compute linear interpolation over 2D grid. It extrapolates outside of
knot points. (SeHyoun Ahn, March 2017)

# Arguments
 - `x_fine::Vector{T}`: x grid to interpolate to
 - `y_fine::Vector{T}`: y grid to interpolate to
 - `x_knot::Vector{T}`: x knot points to interpolate from
 - `y_knot::Vector{T}`: y knot points to interpolate from

# Output
 - `V`: sparse matrix giving interpolation values

# Example
```
x_fine = linspace(0,2,80)'
y_fine = linspace(-1,1,100)'
x_knot = linspace(0,1,10)'
y_knot = linspace(0,1,10)'

V = interp(x_fine, y_fine, x_knot, y_knot)

z = broadcast(+, x_knot.^3 + exp(-x_knot), (y_knot' - 0.5).^2)
surf(x_fine,y_fine,reshape(V * vec(z), 80, 100)')
```
"""
function interp(x_fine::Vector{T}, y_fine::Vector{T},
                x_knot::Vector{T}, y_knot::Vector{T}) where {T<:Float64}

    n_x      = length(x_knot)
    ind_fine = 1:length(x_fine) * length(y_fine)

    loc_x = sum(broadcast(-, x_fine, x_knot') .>= 0, dims=2)
    loc_x = vec(max.(min.(loc_x, n_x - 1), 1))

    loc_y = sum(broadcast(-, y_fine, y_knot') .>= 0, dims=2)
    loc_y = max.(min.(loc_y, length(y_knot) - 1), 1)'

    t_x = (x_fine .- x_knot[loc_x]) ./ (x_knot[loc_x .+ 1] .- x_knot[loc_x])
    t_y = ((y_fine .- vec(y_knot[loc_y])) ./
           (vec(y_knot[loc_y .+ 1]) .- vec(y_knot[loc_y])))'

    j_SW = broadcast(+, loc_x,      n_x.*(loc_y .- 1))
    j_SE = broadcast(+, loc_x .+ 1, n_x.*(loc_y .- 1))
    j_NW = broadcast(+, loc_x,      n_x.*(loc_y))
    j_NE = broadcast(+, loc_x .+ 1, n_x.*(loc_y))
    v_SW = broadcast(*, (1 .- t_x), (1 .- t_y))
    v_SE = broadcast(*, (t_x),      (1 .- t_y))
    v_NW = broadcast(*, (1 .- t_x), (t_y))
    v_NE = broadcast(*, (t_x),      (t_y))

    i = vec(repeat(ind_fine, 4, 1))
    j = [vec(j_SW); vec(j_SE); vec(j_NW); vec(j_NE)]
    v = [vec(v_SW); vec(v_SE); vec(v_NW); vec(v_NE)]

    return SparseArrays.sparse(i, j, v, length(x_fine) * length(y_fine), n_x * length(y_knot))
end