#Wigner semicircle distribution

importall Distributions
export Semicircle

immutable Semicircle <: ContinuousUnivariateDistribution
    mean::Float64
    radius::Float64
    Semicircle(mu, r) = r > 0 ? new(float64(mu), float64(r)) :
        error("radius r must be positive") #Constructor
end

#Standardized distribution - mean 0, variance 1
Semicircle(mu) = Semicircle(mu, 2.0)
Semicircle() = Semicircle(0.0, 2.0)

# Distribution function methods
###############################

# cumulative distribution function
function cdf(X::Semicircle, x::Real)
    r, a = X.mean, X.radius
    insupport(x) ? 0.5 + (x-a)/(pi*r^2) * sqrt(r^2 - (x-a)^2) + 1/pi * asin((x-a)/r) : (x>a ? 1.0 : 0.0)
end

# probability density function
function pdf(X::Semicircle, x::Real)
    r, a = X.mean, X.radius
    insupport(x) ? 2/(pi*r^2) * sqrt(r^2 - (x-a)^2) : 0.0
end

# predicate is x in the support of the distribution?
insupport(X::Semicircle, x::Real)=abs(x)<=X.radius

#Entropy methods
################

# entropy of distribution in nats
entropy(X::Semicircle)=log(pi*X.radius) - 0.5

#Measures of central tendency methods
#####################################

# mean of distribution
mean(X::Semicircle)=X.mean

# median of distribution
median(X::Semicircle)=X.mean

# mode(s) of distribution as vector
modes(X::Semicircle)=[X.mean]

# kurtosis of the distribution
kurtosis(X::Semicircle)=2

# skewness of the distribution
skewness(X::Semicircle)=0

# standard deviation of distribution
std(X::Semicircle)=X.radius/2

# variance of distribution
var(X::Semicircle)=std(X)^2

# moment of distribution
if _HAVE_GSL
  function moment(X::Semicircle, order::Integer)
    a, r = X.mean, X.radius
    if X.mean != 0
      a^n*hypergeom([(1-n)/2, -n/2], 2, (r/a)^2)
    else
      order%2 ? (0.5*r)^(2n) * catalan(div(order,2)) : 0
    end
  end
else
  function moment(X::Semicircle, order::Integer)
    error("The moment of a semicircle distribution requires the GSL module to be loaded.")
  end
end

# cumulant of distribution
function cumulant(X::Semicircle, order::Integer)
  if X.mean != 0 error("not supported") end
  if order%2
    order==0 ? 1 : (0.5*r)^(2n) * lassalle(order/2)
  else
    0
  end
end

# free cumulant of distribution
function freecumulant(X::Semicircle, order::Integer)
  if order == 0
    return 1
  elseif order == 1
    return mean(X)
  elseif order == 2
    return var(X)
  else
    return 0
  end
end

#Generating function methods
############################

# characteristic function
function cf(X::Semicircle, t::Real)
  r = t * X.mean
  2 * besselj(1, r)/r
end

# moment generating function
function mgf(X::Semicircle, t::Real)
  r = t * X.mean
  2 * besseli(1, r)/r
end

#Sampling methods
#################

# random sampler
# Use relationship with beta distribution
function rand(X::Semicircle)
  Y = rand(Beta(1.5, 1.5))
  X.mean + 2 * X.radius * Y - X.radius
end


# ---
# title: 264. Ugly Number II
# id: problem264
# author: Indigo
# date: 2021-06-05
# difficulty: Medium
# categories: Math, Dynamic Programming, Heap
# link: <https://leetcode.com/problems/ugly-number-ii/description/>
# hidden: true
# ---
#
# Write a program to find the `n`-th ugly number.
#
# Ugly numbers are **positive numbers** whose prime factors only include `2, 3,
# 5`.
#
# **Example:**
#
#
#
#     Input: n = 10
#     Output: 12
#     Explanation:1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.
#
# **Note:**
#
#   1. `1` is typically treated as an ugly number.
#   2. `n` **does not exceed 1690**.
#
#
## @lc code=start
using LeetCode

function nth_ugly_number(n::Int)
    dp = Vector{Int}(undef, n)
    p2 = p3 = p5 = dp[1] = 1
    for i in 2:n
        nums = dp[p2] * 2, dp[p3] * 3, dp[p5] * 5
        dp[i] = minimum(nums)
        p2 += dp[i] == nums[1]
        p3 += dp[i] == nums[2]
        p5 += dp[i] == nums[3]
    end
    return dp[n]
end
## @lc code=end


function testDCT1d(N=32)
  Random.seed!(1235)
  x = zeros(ComplexF64, N^2)
  for i=1:5
    x .+= rand()*cos.(rand(1:N^2)*collect(1:N^2)) .+ 1im*rand()*cos.(rand(1:N^2)*collect(1:N^2))
  end
  D1 = DCTOp(ComplexF64,(N^2,),2)
  D2 = sqrt(2/N^2)*[cos(pi/(N^2)*j*(k+0.5)) for j=0:N^2-1,k=0:N^2-1]
  D2[1,:] .*= 1/sqrt(2)
  D3 = DCTOp(ComplexF64,(N^2,),4)
  D4 = sqrt(2/N^2)*[cos(pi/(N^2)*(j+0.5)*(k+0.5)) for j=0:N^2-1,k=0:N^2-1]

  y1 = D1*x
  y2 = D2*x

  @test norm(y1 - y2) / norm(y1) ≈ 0 atol=0.01

  y3 = D3*x
  y4 = D4*x
  @test norm(y3 - y4) / norm(y1) ≈ 0 atol=0.01

  x1 = adjoint(D1)*y1
  @test norm(x1 - x) / norm(x) ≈ 0 atol=0.01

  x2 = adjoint(D3)*y3
  @test norm(x2 - x) / norm(x) ≈ 0 atol=0.01
end

function testFFT1d(N=32,shift=true)
  Random.seed!(1234)
  x = zeros(N^2)
  for i=1:5
    x .+= rand()*cos.(rand(1:N^2)*collect(1:N^2))
  end
  D1 = FFTOp(ComplexF64,(N^2,),shift)
  D2 =  1.0/N*[exp(-2*pi*im*j*k/N^2) for j=0:N^2-1,k=0:N^2-1]

  y1 = D1*x
  if shift
    y2 = fftshift(D2*fftshift(x))
  else
    y2 = D2*x
  end
  @test norm(y1 - y2) / norm(y1) ≈ 0 atol=0.01

  x1 = adjoint(D1) * y1
  if shift
    x2 = ifftshift(adjoint(D2)*ifftshift(y2))
  else
    x2 = adjoint(D2)*y2
  end
  @test norm(x1 - x2) / norm(x1) ≈ 0 atol=0.01
end

function testFFT2d(N=32,shift=true)
  Random.seed!(1234)
  x = zeros(N^2)
  for i=1:5
    x .+= rand()*cos.(rand(1:N^2)*collect(1:N^2))
  end
  D1 = FFTOp(ComplexF64,(N,N),shift)

  idx = CartesianIndices((N,N))[collect(1:N^2)]
  D2 =  1.0/N*[ exp(-2*pi*im*((idx[j][1]-1)*(idx[k][1]-1)+(idx[j][2]-1)*(idx[k][2]-1))/N) for j=1:N^2, k=1:N^2 ]

  y1 = D1*x
  if shift
    y2 = D2*vec(fftshift(reshape(x,N,N)))
    y2 = vec(fftshift(reshape(y2,N,N)))
  else
    y2 = D2*x
  end
  @test norm(y1 - y2) / norm(y1) ≈ 0 atol=0.01

  x1 = adjoint(D1) * y1
  if shift
    x2 = adjoint(D2)*vec(ifftshift(reshape(y2,N,N)))
    x2 = vec(ifftshift(reshape(x2,N,N)))
  else
    x2 = adjoint(D2)*y2
  end
  @test norm(x1 - x2) / norm(x1) ≈ 0 atol=0.01
end

function testWeighting(N=512)
  Random.seed!(1234)
  x1 = rand(N)
  weights = rand(N)
  W = WeightingOp(weights)
  y1 = W*x1
  y = weights .* x1

  @test norm(y1 - y) / norm(y) ≈ 0 atol=0.01

  x2 = rand(2*N)
  W2 = WeightingOp(weights,2)
  y2 = W2*x2
  y = repeat(weights,2) .* x2

  @test norm(y2 - y) / norm(y) ≈ 0 atol=0.01
end

function testGradOp1d(N=512)
  x = rand(N)
  G = GradientOp(eltype(x),size(x))
  G0 = Bidiagonal(ones(N),-ones(N-1), :U)[1:N-1,:]

  y = G*x
  y0 = G0*x
  @test norm(y - y0) / norm(y0) ≈ 0 atol=0.001

  xr = transpose(G)*y
  xr0 = transpose(G0)*y0

  @test norm(y - y0) / norm(y0) ≈ 0 atol=0.001
end

function testGradOp2d(N=64)
  x = repeat(1:N,1,N)
  G = GradientOp(eltype(x),size(x))
  G_1d = Bidiagonal(ones(N),-ones(N-1), :U)[1:N-1,:]

  y = G*vec(x)
  y0 = vcat( vec(G_1d*x), vec(x*transpose(G_1d)) )
  @test norm(y - y0) / norm(y0) ≈ 0 atol=0.001

  xr = transpose(G)*y
  y0_x = reshape(y0[1:N*(N-1)],N-1,N)
  y0_y = reshape(y0[N*(N-1)+1:end],N,N-1)
  xr0 = transpose(G_1d)*y0_x + y0_y*G_1d
  xr0 = vec(xr0)

  @test norm(xr - xr0) / norm(xr0) ≈ 0 atol=0.001
end

function testSampling(N=64)
  x = rand(ComplexF64,N,N)
  idx = shuffle(collect(1:N^2)[1:N*div(N,2)])
  SOp = SamplingOp(idx,(N,N))
  y = SOp*vec(x)
  x2 = adjoint(SOp)*y
  # references
  y_ref = vec(x[idx])
  x2_ref = zeros(ComplexF64,N^2)
  x2_ref[idx] .= y_ref
  # perform tests
  @test norm(y - y_ref) / norm(y_ref) ≈ 0 atol=0.000001
  @test norm(x2 - x2_ref) / norm(x2_ref) ≈ 0 atol=0.000001
end

function testWavelet(M=64,N=60)
  x = rand(M,N)
  WOp = WaveletOp(Float64,(M,N))
  x_wavelet = WOp*vec(x)
  x_reco = reshape( adjoint(WOp)*x_wavelet, M, N)

  @test norm(x_reco - x) / norm(x) ≈ 0 atol=0.001
end

@testset "Linear Operators" begin
  @info "test DCT-II and DCT-IV"
  for N in [2,8,16,32]
    testDCT1d(N)
  end
  @info "test FFT"
  for N in [8,16,32]
    testFFT1d(N,false)
    testFFT1d(N,true)
    testFFT2d(N,false)
    testFFT2d(N,true)
  end
  @info "test Weighting"
  testWeighting(512)
  @info "test gradientOp"
  testGradOp1d(512)
  testGradOp2d(64)
  @info "test sampling"
  testSampling(64)
  @info "test WaveletOp"
  testWavelet(64,64)
  testWavelet(64,60)
end


# safe log(x) function, log 0 = 0
safelog(x) = x > zero(x) ? log2(x) : zero(x)
safediv(x, y) = y == zero(y) ? zero(y) : x / y
# general functions for entropy

"""
    make_joint_distribution(N::NT) where {NT<:AbstractEcologicalNetwork}

Returns a double stochastic matrix from the adjacency or incidence matrix.
Raises an error if the matrix contains negative values. Output in bits.
"""
function make_joint_distribution(N::NT) where {NT<:AbstractEcologicalNetwork}
    !(typeof(N) <: QuantitativeNetwork) || any(N.A .≥ 0) || throw(DomainError("Information only for nonnegative interaction values"))
    return N.A / sum(N.A)
end

"""
   entropy(P::AbstractArray)

Computes the joint entropy of a double stochastic matrix. Does not perform any
checks whether the matrix is normalized. Output in bits.
"""
function entropy(P::AbstractArray)
    return - sum(P .* safelog.(P))
end

"""
   entropy(P::AbstractArray, dims::I)

Computes the marginal entropy of a double stochastic matrix. `dims` indicates
whether to compute the entropy for the rows (`dims`=1) or columns (`dims`=2).
Does not perform any checks whether the matrix is normalized. Output in bits.
"""
function entropy(P::AbstractArray, dims::I) where I <: Int
    return entropy(sum(P', dims=dims))
end

"""
   entropy(N::AbstractEcologicalNetwork)

Computes the joint entropy of an ecological network. Output in bits.
"""
function entropy(N::NT) where {NT<:AbstractEcologicalNetwork}
    P = make_joint_distribution(N)
    return entropy(P)
end

"""
   entropy(N::AbstractEcologicalNetwork, dims::I)

Computes the marginal entropy of an ecological network. `dims` indicates
whether to compute the entropy for the rows (`dims`=1) or columns (`dims`=2).
Output in bits.
"""
function entropy(N::NT, dims::I) where {NT<:AbstractEcologicalNetwork, I <: Int}
    P = make_joint_distribution(N)
    return entropy(P, dims)
end

"""
   conditional_entropy(P::AbstractArray, given::I)

Computes the conditional entropy of double stochastic matrix. If `given = 1`,
it is the entropy of the columns, and visa versa when `given = 2`. Output in bits.
"""
function conditional_entropy(P::AbstractArray, given::I) where I <: Int
    dims = (given % 2) + 1
    return - sum(P .* safelog.(safediv.(P, sum(P, dims=dims))))
end

"""
   conditional_entropy(N::AbstractEcologicalNetwork, given::I)

Computes the conditional entropy of an ecological network. If `given = 1`,
it is the entropy of the columns, and visa versa when `given = 2`.
"""
function conditional_entropy(N::NT, given::I) where {NT<:AbstractEcologicalNetwork, I <: Int}
    P = make_joint_distribution(N)
    return conditional_entropy(P, given)
end

"""
    mutual_information(P::AbstractArray)

Computes the mutual information of a double stochastic matrix. Output in bits.
"""
function mutual_information(P::AbstractArray)
    I = entropy(P, 1) - conditional_entropy(P, 2)
    return max(I, zero(I))  # might give small negative value
end

"""
    mutual_information(P::AbstractArray)

Computes the mutual information of an ecological network. Output in bits.
"""
function mutual_information(N::NT) where {NT<:AbstractEcologicalNetwork}
    P = make_joint_distribution(N)
    return mutual_information(P)
end

"""
    variation_information(P::AbstractArray)

Computes the variation of information of a double stochastic matrix. Output in
bits.
"""
function variation_information(P::AbstractArray)
    return conditional_entropy(P, 1) + conditional_entropy(P, 2)
end

"""
    variation_information(N::AbstractEcologicalNetwork)

Computes the variation of information of an ecological network. Output in
bits.
"""
function variation_information(N::NT) where {NT <: AbstractEcologicalNetwork}
    P = make_joint_distribution(N)
    return variation_information(P)
end

"""
    diff_entropy_uniform(P::AbstractArray)

Computes the difference in entropy of the marginals compared to the entropy of
an uniform distribution. The parameter `dims` indicates which marginals are used,
with both if no value is provided. Output in bits.
"""
function diff_entropy_uniform(P::AbstractArray)
    D = log2(length(P)) - entropy(P, 1) - entropy(P, 2)
    return max(zero(D), D)
end

"""
    diff_entropy_uniform(P::AbstractArray, dims::I)

Computes the difference in entropy of the marginals compared to the entropy of
an uniform distribution. The parameter `dims` indicates which marginals are used,
with both if no value is provided. Output in bits.
"""
function diff_entropy_uniform(P::AbstractArray, dims::I) where {I <: Int}
    D = log2(size(P, dims)) - entropy(P, dims)
    return max(zero(D), D)
end

"""
    diff_entropy_uniform(N::AbstractEcologicalNetwork, dims::I=nothing)

Computes the difference in entropy of the marginals compared to the entropy of
an uniform distribution. The parameter `dims` indicates which marginals are used,
with both if no value is provided. Output in bits.
"""
function diff_entropy_uniform(N::NT, dims=nothing) where {NT <: AbstractEcologicalNetwork}
    if dims == nothing
        P = make_joint_distribution(N)
        return diff_entropy_uniform(P)
    else  # compute marginals
        typeof(dims) <: Int || throw(ArgumentError("dims should be an integer (1 or 2)"))
        P = make_joint_distribution(N)
        return diff_entropy_uniform(P, dims)
    end
end

"""
    information_decomposition(N::AbstractEcologicalNetwork; norm::Bool=false, dims::I=nothing)

Performs an information theory decomposition of a given ecological network, i.e.
the information content in the normalized adjacency matrix is split in:

- `:D` : difference in entropy of marginals compared to an uniform distribition
- `:I` : mutual information
- `:V` : variation of information / conditional entropy

If `norm=true`, the components are normalized such that their sum is equal to 1.
One can optinally give the dimision, indicating whether to compute the indices
for the rows (`dims=1`), columns (`dims=2`) or the whole matrix (default).

Result is returned in a Dict. Outputs in bits.
"""
function information_decomposition(N::NT; norm::Bool=false, dims::I=nothing) where {NT <: AbstractEcologicalNetwork, I <: Union{Int, Nothing}}
    decomposition = Dict{Symbol, Float64}()
    P = make_joint_distribution(N)
    if dims == nothing
        # difference marginal entropy
        decomposition[:D] = diff_entropy_uniform(P)
        # mutual information
        decomposition[:I] = 2mutual_information(P)
        # variance of information
        decomposition[:V] = variation_information(P)
    else
        decomposition[:D] = diff_entropy_uniform(P, dims)
        decomposition[:I] = mutual_information(P)
        decomposition[:V] = conditional_entropy(P, (dims % 2) + 1)
    end
    if norm
        potential_information = sum(values(decomposition))
        for key in keys(decomposition)
            decomposition[key] /= potential_information
        end
    end
    return decomposition
end

"""
    convert2effective(indice::Real)

Convert an information theory indices in an effective number (i.e. number of
corresponding interactions).
"""
function convert2effective(indice::R) where R <: Real
    return 2.0^indice
end

"""
    potential_information(N::NT)

Computes the maximal potential information in a network, corresponding to
every species interacting with every other species. Compute result for the
marginals using the optional parameter `dims`. Output in bits.
"""
function potential_information(N::NT) where NT <: AbstractEcologicalNetwork
    m, n = size(N)
    return log2(m) + log2(n)
end

"""
    potential_information(N::NT, dims::I)

Computes the maximal potential information in a network, corresponding to
every species interacting with every other species. Compute result for the
marginals using the optional parameter `dims`. Output in bits.
"""
function potential_information(N::NT, dims::I) where {NT <: AbstractEcologicalNetwork, I <: Int}
    return log2(size(N)[dims])
end


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# ╔═╡ feadb782-9905-4e05-a0c9-e1b1b54fb3b0
begin
	using Distributions
	n_samples = 100
	p_1 = 0.2
	x_data = rand(Binomial(1, p_1), n_samples)
end

# ╔═╡ c54da392-7d0b-11eb-278e-b3a7ec910fc7
begin
	using Gaston; set(showable="svg")
	p0 = range(0, 1;length=100)
	p1 = 1 .- p0
	plot(p0, p1, Axes(xrange = (-1, 1), yrange = (-1, 1), zeroaxis="", xtics="axis", ytics="axis"))
	plot!([0.1,1], [1,0.2], supp=["p₀","p₁"], w="labels")
end

# ╔═╡ e42579e4-5e98-4cca-a425-a3b356dd03ba
begin
	using LinearAlgebra
	p = [0.8, 0.2]
	norm(p, 1)
end

# ╔═╡ 6de3cf91-1739-49ac-aac5-0dcd72871461
begin
	using Yao
	q = zero_state(1)
end

# ╔═╡ 0ef307d4-84d3-11eb-10e7-07cc58485fba
include("./essentials.jl"); import .essentials

# ╔═╡ 4f69ca95-0111-40d3-b74b-34e999f4f185
md"""
Probability theory is a cornerstone for machine learning. We can think of quantum states as probability distributions with certain properties that make them different from our classical notion of probabilities. Contrasting these properties is an easy and straightforward introduction to the most basic concepts we need in quantum computing.

Apart from probability theory, linear algebra is also critical for many learning protocols. As we will see, geometry and probabilities are intrinsically linked in quantum computing, but geometric notions are also familiar in dealing with classical probability distributions. This notebook first talks about classical probabilities and stochastic vectors, and introduces quantum states as a natural generalization.

Throughout this course, we will assume finite probability distributions and finite dimensional spaces. This significantly simplifies notation and most quantum computers operate over finite dimensional spaces, so we do not lose much in generality.

# Classical probability distributions

Let us toss a biased coin. Without getting too technical, we can associate a random variable $X$ with the output: it takes the value 0 for heads and the value 1 for tails. We get heads with probability $P(X=0) = p_0$ and tails with $P(X=1) = p_1$ for each toss of the coin. In classical, Kolmogorovian probability theory, $p_i\geq 0$ for all $i$, and the probabilities sum to one: $\sum_i p_i = 1$. Let's sample this distribution
"""

# ╔═╡ c3898a67-d4b2-46a0-b5cc-69420762cd62
md"""
We naturally expect that the empirically observed frequencies also sum to one:
"""

# ╔═╡ 02a05ce8-35ad-4192-a9d6-e7d3cc29e6ba
begin
	frequency_of_zeros, frequency_of_ones = 0, 0
	for x in x_data
	    if x==1
	        frequency_of_ones += 1/n_samples
	    else
    	    frequency_of_zeros += 1/n_samples
		end
	end
	frequency_of_ones+frequency_of_zeros
end

# ╔═╡ a776d9b8-9adb-4985-9830-165acb8d805b
md"""
Since $p_0$ and $p_1$ must be non-negative, all possible probability distributions are restricted to the positive orthant. The normalization constraint puts every possible distribution on a straight line. This plot describes all possible probability distributions by biased and unbiased coins.
"""

# ╔═╡ 8c7034ce-8286-4d06-9fe1-17581695219c
md"""
We may also arrange the probabilities in a vector $\vec{p} = \begin{bmatrix} p_0 \\ p_1 \end{bmatrix}$. Here, for notational convenience, we put an arrow above the variable representing the vector, to distinguish it from scalars. You will see that quantum states also have a standard notation that provides convenience, but goes much further in usefulness than the humble arrow here.

A vector representing a probability distribution is called a *stochastic vector*. The normalization constraint essentially says that the norm of the vector is restricted to one in the $l_1$ norm. In other words, $||\vec{p}||_1 = \sum_i |p_i| = 1$. This would be the unit circle in the $l_1$ norm, but since $p_i\geq 0$, we are restricted to a quarter of the unit circle, just as we plotted above. We can easily verify this with numpy's norm function:
"""

# ╔═╡ 13533ef2-3be4-4aea-981d-0bc77ec5db57
md"""
We know that the probability of heads is just the first element in the $\vec{p}$, but since it is a vector, we could use linear algebra to extract it. Geometrically, it means that we project the vector to the first axis. This projection is described by the matrix $\begin{bmatrix} 1 & 0\\0 & 0\end{bmatrix}$. The length in the $l_1$ norm gives the sought probability:
"""

# ╔═╡ 0d931337-ad9b-45d1-85fa-2b89f052099d
begin
	Π₀ = [1 0;0 0]
	norm(Π₀ * p)
end

# ╔═╡ 29c91dc7-cfda-42d4-bd0f-f479c611339e
md"""
We can repeat the process to get the probability of tails:
"""

# ╔═╡ c1ec3e28-93fc-42d5-8ef7-454cdb7f7c33
begin
	Π₁ = [0 0; 0 1]
	norm(Π₁ * p)
end

# ╔═╡ de543889-d698-4926-a2a2-9aea48578fd8
md"""
The two projections play an equivalent role to the values 0 and 1 when we defined the probability distribution. In fact, we could define a new random variable called $\Pi$ that can take the projections $\Pi_0$ and $\Pi_1$ as values and we would end up with an identical probability distribution. This may sound convoluted and unnatural, but the measurement in quantum mechanics is essentially a random variable that takes operator values, such as projections.
"""

# ╔═╡ caa23a10-d43e-4a8c-b987-af69b55ed16e
md"""
What happens when we want to transform a probability distribution to another one? For instance, to change the bias of a coin, or to describe the transition of a Markov chain. Since the probability distribution is also a stochastic vector, we can apply a matrix on the vector, where the matrix has to fulfill certain conditions. A left *stochastic matrix* will map stochastic vectors to stochastic vectors when multiplied from the left: its columns add up to one. In other words, it maps probability distributions to probability distributions. For example, starting with a unbiased coin, the map $M$ will transform the distribution to a biased coin:
"""

# ╔═╡ cac5a2c0-6913-4997-b29d-154250be44dd
let
	p = [0.5, 0.5]
	M = [0.7 0.6; 0.3 0.4]
	norm(M * p)
end

# ╔═╡ 1dee601c-2f9f-46d8-be37-99d31e589ebe
md"""
One last concept that will come handy is entropy. A probability distribution's entropy is defined as $H(p) = - \sum_i p_i \log_2 p_i$. Let us plot it over all possible probability distributions of coin tosses:
"""

# ╔═╡ 15416de9-5fe1-4cb7-a5bb-b4227fd9ad4a
let
	ϵ = 10^-10
	p_0 = range(ϵ, 1-ϵ;length = 100)
	p_1 = 1 .- p_0
	H = -1 .* (p_0 .* log.(2,p_0) .+ p_1 .* log.(2, p_1))
	plot(p_0, H, Axes(ylabel="'H'", xlabel="'p₀'", xrange = (0,1), yrange = (0,1)))
	plot!([0.5, 0.5], [0,1], ls = :dash)
end

# ╔═╡ d803bf96-6cd4-4874-a3f7-ed7c0bf16267
md"""
Here we can see that the entropy is maximal for the unbiased coin. This is true in general: the entropy peaks for the uniform distribution. In a sense, this is the most unpredictable distribution: if we get heads with probability 0.2, betting tails is a great idea. On the other hand, if the coin is unbiased, then a deterministic strategy is of little help in winning. Entropy quantifies this notion of surprise and unpredictability.
"""

# ╔═╡ 4cf8cd38-ac26-48f5-8507-9ac56fb90867
md"""
# Quantum states

A classical coin is a two-level system: it is either heads or tails. At a first look a quantum state is a probability distribution, and the simplest case is a two-level state, which we call a qubit. Just like the way we can write the probability distribution as a column vector, we can write a quantum state as a column vector. For notational convenience that will become apparent later, we write the label of a quantum state in what is called a ket in the Dirac notation. So for instance, for some qubit, we can write

``|\psi\rangle = \begin{bmatrix} a_0 \\ a_1 \\ \end{bmatrix}.``

In other words, a ket is just a column vector, exactly like the stochastic vector in the classical case. Instead of putting an arrow over the name of the variable to express that it is a vector, we use the ket to say that it is a column vector that represents a quantum state. There's more to this notation, as we will see.

The key difference to classical probability distributions and stochastic vectors is the normalization constraint. The square sum of their absolute values adds up to 1:

$\sqrt{|a_0|^2+|a_1|^2}=1,$

where $a_0, a_1\in \mathbb{C}$. In other words, we are normalizing in the $l_2$ norm instead of the $l_1$ norm. Furthermore, we are no longer restricted to the positive orthant: the components of the quantum state vector, which we call *probability amplitudes*, are complex valued.

Let us introduce two special qubits, corresponding to the canonical basis vectors in two dimensions: $|0\rangle$ and $|1\rangle$.

$|0\rangle = \begin{bmatrix} 1 \\ 0 \\ \end{bmatrix}, \,\,\, |1\rangle = \begin{bmatrix} 0 \\ 1 \\ \end{bmatrix}.$

This basis is also called the computational basis in quantum computing.

We can expand an arbitrary qubit state in this basis:

$|\psi\rangle = \begin{bmatrix} a_0 \\ a_1 \\ \end{bmatrix}=a_0\begin{bmatrix} 1 \\ 0 \\ \end{bmatrix} + a_1 \begin{bmatrix} 0 \\ 1 \\ \end{bmatrix}= a_0|0\rangle+a_1|1\rangle.$

This expansion in a basis is called a superposition. If we sample the qubit state, we obtain the outcome 0 with probability $|a_0|^2$, and 1 with probability $|a_1|^2$. This is known as the Born rule; you will learn more about measurements and this rule in a subsequent notebook.

For now, let's take a look at how we can simulate classical coin tossing on a quantum computer. Let's start with a completely biased case where we get heads with probability 1. This means that our qubit $|\psi\rangle=|0\rangle$. We create a circuit of a single qubit and a single classical register where the results of the sampling (measurements) go.
"""

# ╔═╡ 1b623852-7074-4211-bbcd-a65ac55c3070
md"""
Any qubit is initialized in $|0\rangle$, so if we measure it right away, we should get our maximally biased coin.
"""

# ╔═╡ 9e2a3016-ac1a-447c-a223-3c4b80c62025
measure(q)

# ╔═╡ 3e692d90-58ce-4562-a3fa-4d73e24b7b62
md"""
Let us execute it a hundred times and study the result
"""

# ╔═╡ ee624636-c6fa-434a-bd6a-7048e755dffa
result = measure(q, nshots=100)

# ╔═╡ 0ebecb30-7db3-11eb-0dab-6ba6b1b2522a
begin
	using StatsBase
	[fit(Histogram, Int.(result), 0:2).weights string.(0:1)]
end

# ╔═╡ d6fc78fa-ba2e-45bf-af00-45fee2fd017e
md"""
As expected, all of our outcomes are $0$.
To understand the possible quantum states, we use the Bloch sphere visualization. Since the probability amplitudes are complex and there are two of them for a single qubit, this would require a four-dimensional space. Now since the vectors are normalized, this removes a degree of freedom, allowing a three-dimensional representation with an appropriate embedding. This embedding is the Bloch sphere. It is slightly different than an ordinary sphere in three dimensions: we identify the north pole with the state $|0\rangle$, and the south pole with $|1\rangle$. In other words, two orthogonal vectors appear as if they were on the same axis -- the axis Z. The computational basis is just one basis: the axes X and Y represent two other bases. Any point on the surface of this sphere is a valid quantum state. This is also true the other way around: every pure quantum state is a point on the Bloch sphere. Here it 'pure' is an important technical term and it essentially means that the state is described by a ket (column vector). Later in the course we will see other states called mix states that are not described by a ket (you will see later that these are inside the Bloch sphere).

To make it less abstract, let's plot our $|0\rangle$ on the Bloch sphere:
"""

# ╔═╡ ba3d9b32-88dd-11eb-3b2a-0f96968e5bcb
essentials.plot_blochsph(state(zero_state(1)))

# ╔═╡ 63946ad1-d28d-4759-8898-085f189e57db
md"""
Compare this sphere with the straight line in the positive orthant that describes all classical probability distributions of coin tosses. You can already see that there is a much richer structure in the quantum probability space.

Let us pick another point on the Bloch sphere, that is, another distribution. Let's transform the state $|0\rangle$ to $\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle)$. This corresponds to the unbiased coin, since we will get 0 with probability $|\frac{1}{\sqrt{2}}|^2=1/2$, and the other way around. There are many ways to do this transformation. We pick a rotation around the Y axis by $\pi/2$, which corresponds to the matrix $\frac{1}{\sqrt{2}}\begin{bmatrix} 1 & -1\\1 & 1\end{bmatrix}$.
"""

# ╔═╡ a9bfa0e3-b128-43b1-af54-579d2bdcb3b7
essentials.plot_hist(measure(zero_state(1) |> Ry(π/2), nshots=1024))

# ╔═╡ 0ee44bd4-e41f-4977-b71c-9bc04bba0385
md"""
To get an intuition why it is called a rotation around the Y axis, let's plot it on the Bloch sphere:
"""

# ╔═╡ 770ae76e-6521-4613-b781-3627847ffae9
essentials.plot_blochsph(state(zero_state(1) |> Ry(π/2)))

# ╔═╡ 85bea7c1-71ba-4b6b-9341-affd291c0a7c
md"""
It does exactly what it says: it rotates from the north pole of the Bloch sphere.

Why is interesting to have complex probability amplitudes instead of non-negative real numbers? To get some insight, take a look what happens if we apply the same rotation to $|1\rangle$. To achieve this, first we flip $|0\rangle$ to $|1\rangle$ by applying a NOT gate (denoted by X in quantum computing) and then the rotation.
"""

# ╔═╡ a2eaec0e-63a1-4771-899e-240289173b90
essentials.plot_blochsph(state(zero_state(1) |> chain(1, put(1=>X), put(1=>Ry(π/2)))))

# ╔═╡ d054d2e6-da5d-4070-8b93-c81f07af756d
md"""
You can verify that the result is $\frac{1}{\sqrt{2}}(-|0\rangle + |1\rangle)$. That is, the exact same state as before, except that the first term got a minus sign: it is a negative probability amplitude. Note that the difference cannot be observed from the statistics:
"""

# ╔═╡ 0c6bdade-da51-4442-90d4-f3c36aa06e4c
begin
	essentials.plot_hist(measure(zero_state(1) |> chain(1, put(1=>X), put(1=>Ry(π/2))), nshots=1024))
end

# ╔═╡ ca2b7743-b2b3-4335-8c4c-2647492072ed
md"""
It still looks like an approximately unbiased coin. Yet, that negative sign -- or any complex value -- is what models *interference*, a critically important phenomenon where probability amplitudes can interact in a constructive or a destructive way. To see this, if we apply the rotation twice in a row on $|0\rangle$, we get another deterministic output, $|1\rangle$, although in between the two, it was some superposition.
"""

# ╔═╡ 21a24eb3-6104-4f6a-bae5-d11e504aad62
essentials.plot_hist(measure(zero_state(1) |> chain(1, put(1=>Ry(π/2)), put(1=>Ry(π/2))), nshots=1024))

# ╔═╡ 5846d249-d73f-46d7-a1b9-09efc42ef331
md"""
Many quantum algorithms exploit interference, for instance, the seminal [Deutsch-Josza algorithm](https://en.wikipedia.org/wiki/Deutsch–Jozsa_algorithm), which is among the simplest to understand its significance.
"""

# ╔═╡ 2994d86d-c2ba-417b-aa0e-b0c7d0956008
md"""
# More qubits and entanglement

We have already seen that quantum states are probability distributions normed to 1 in the $l_2$ norm and we got a first peek at interference. If we introduce more qubits, we see another crucial quantum effect emerging. To do that, we first have to define how we write down the column vector for describing two qubits. We use a tensor product, which, in the case of qubits, is equivalent to the Kronecker product. Given two qubits, $|\psi\rangle=\begin{bmatrix}a_0\\a_1\end{bmatrix}$ and $|\psi'\rangle=\begin{bmatrix}b_0\\b_1\end{bmatrix}$, their product is $|\psi\rangle\otimes|\psi'\rangle=\begin{bmatrix}a_0b_0\\ a_0b_1\\ a_1b_0\\ a_1b_1\end{bmatrix}$. Imagine that you have two registers $q_0$ and $q_1$, each can hold a qubit, and both qubits are in the state $|0\rangle$. Then this composite state would be described by according to this product rule as follows:
"""

# ╔═╡ f98f70cf-aaf2-4710-9ef7-33c401b10c06
let
	q0 = [1, 0]
	q1 = [1, 0]
	kron(q0, q1)
end

# ╔═╡ 2f1a3452-a122-41f2-8ab7-df4e1cc01b02
md"""
This is the $|0\rangle\otimes|0\rangle$ state, which we often abbreviate as $|00\rangle$. The states $|01\rangle$, $|10\rangle$, and $|11\rangle$ are defined analogously, and the four of them give the canonical basis of the four dimensional complex space, $\mathbb{C}^2\otimes\mathbb{C}^2$.

Now comes the interesting and counter-intuitive part. In machine learning, we also work with high-dimensional spaces, but we never construct it as a tensor product: it is typically $\mathbb{R}^d$ for some dimension $d$. The interesting part of writing the high-dimensional space as a tensor product is that not all vectors in can be written as a product of vectors in the component space.

Take the following state: $|\phi^+\rangle = \frac{1}{\sqrt{2}}(|00\rangle+|11\rangle)$. This vector is clearly in $\mathbb{C}^2\otimes\mathbb{C}^2$, since it is a linear combination of two of the basis vector in this space. Yet, it cannot be written as $|\psi\rangle\otimes|\psi'\rangle$ for some $|\psi\rangle$, $|\psi'\rangle\in\mathbb{C}^2$.

To see this, assume that it can be written in this form. Then

$|\phi^+\rangle = \frac{1}{\sqrt{2}}(|00\rangle+|11\rangle) = \begin{bmatrix}a_0b_0\\ a_0b_1\\ a_1b_0\\ a_1b_1\end{bmatrix} = a_0b_0|00\rangle + a_0b_1|01\rangle + a_1b_0|10\rangle + a_1b_1|11\rangle.$

$|01\rangle$ and $|10\rangle$ do not appear on the left-hand side, so their coefficients must be zero: $a_1b_0=0$ and $a_0b_1=0$. This leads to a contradiction, since $a_1$ cannot be zero ($a_1b_1=1$), so $b_0$ must be zero, but $a_0b_0=1$. Therefore $|\phi^+\rangle$ cannot be written as a product.

States that cannot be written as a product are called entangled states. This is the mathematical form of describing a phenomenon of strong correlations between random variables that exceed what is possible classically. Entanglement plays a central role in countless quantum algorithms. A simple example is [quantum teleportation](https://en.wikipedia.org/wiki/Quantum_teleportation). We will also see its applications in quantum machine learning protocols.

We will have a closer look at entanglement in a subsequent notebook on measurements, but as a teaser, let us look at the measurement statistics of the $|\phi^+\rangle$ state. The explanation of the circuit preparing it will also come in a subsequent notebook.
"""

# ╔═╡ a7cdf304-48d2-4485-b473-dd2248bb7ce6
essentials.plot_hist(measure(zero_state(2) |> chain(2, put(1=>H), control(1, 2=>X)), nshots=1024))

# ╔═╡ 5a2944be-209c-4fbe-b1b1-19a5f9df4316
md"""
Notice that 01 or 10 never appear in the measurement statistics.
"""

# ╔═╡ ec3512fa-22de-4123-bb1f-440b899ac901
md"""
# Further reading

Chapter 9 in Quantum Computing since Democritus by Scott Aaronson describes a similar approach to understanding quantum states -- in fact, the interference example was lifted from there.
"""

# ╔═╡ Cell order:
# ╟─4f69ca95-0111-40d3-b74b-34e999f4f185
# ╠═feadb782-9905-4e05-a0c9-e1b1b54fb3b0
# ╟─c3898a67-d4b2-46a0-b5cc-69420762cd62
# ╠═02a05ce8-35ad-4192-a9d6-e7d3cc29e6ba
# ╟─a776d9b8-9adb-4985-9830-165acb8d805b
# ╠═c54da392-7d0b-11eb-278e-b3a7ec910fc7
# ╟─8c7034ce-8286-4d06-9fe1-17581695219c
# ╠═e42579e4-5e98-4cca-a425-a3b356dd03ba
# ╟─13533ef2-3be4-4aea-981d-0bc77ec5db57
# ╠═0d931337-ad9b-45d1-85fa-2b89f052099d
# ╟─29c91dc7-cfda-42d4-bd0f-f479c611339e
# ╠═c1ec3e28-93fc-42d5-8ef7-454cdb7f7c33
# ╟─de543889-d698-4926-a2a2-9aea48578fd8
# ╟─caa23a10-d43e-4a8c-b987-af69b55ed16e
# ╠═cac5a2c0-6913-4997-b29d-154250be44dd
# ╟─1dee601c-2f9f-46d8-be37-99d31e589ebe
# ╠═15416de9-5fe1-4cb7-a5bb-b4227fd9ad4a
# ╟─d803bf96-6cd4-4874-a3f7-ed7c0bf16267
# ╟─4cf8cd38-ac26-48f5-8507-9ac56fb90867
# ╠═6de3cf91-1739-49ac-aac5-0dcd72871461
# ╟─1b623852-7074-4211-bbcd-a65ac55c3070
# ╠═9e2a3016-ac1a-447c-a223-3c4b80c62025
# ╟─3e692d90-58ce-4562-a3fa-4d73e24b7b62
# ╠═ee624636-c6fa-434a-bd6a-7048e755dffa
# ╠═0ebecb30-7db3-11eb-0dab-6ba6b1b2522a
# ╟─d6fc78fa-ba2e-45bf-af00-45fee2fd017e
# ╠═0ef307d4-84d3-11eb-10e7-07cc58485fba
# ╠═ba3d9b32-88dd-11eb-3b2a-0f96968e5bcb
# ╟─63946ad1-d28d-4759-8898-085f189e57db
# ╠═a9bfa0e3-b128-43b1-af54-579d2bdcb3b7
# ╟─0ee44bd4-e41f-4977-b71c-9bc04bba0385
# ╠═770ae76e-6521-4613-b781-3627847ffae9
# ╟─85bea7c1-71ba-4b6b-9341-affd291c0a7c
# ╠═a2eaec0e-63a1-4771-899e-240289173b90
# ╟─d054d2e6-da5d-4070-8b93-c81f07af756d
# ╠═0c6bdade-da51-4442-90d4-f3c36aa06e4c
# ╟─ca2b7743-b2b3-4335-8c4c-2647492072ed
# ╠═21a24eb3-6104-4f6a-bae5-d11e504aad62
# ╟─5846d249-d73f-46d7-a1b9-09efc42ef331
# ╟─2994d86d-c2ba-417b-aa0e-b0c7d0956008
# ╠═f98f70cf-aaf2-4710-9ef7-33c401b10c06
# ╟─2f1a3452-a122-41f2-8ab7-df4e1cc01b02
# ╠═a7cdf304-48d2-4485-b473-dd2248bb7ce6
# ╟─5a2944be-209c-4fbe-b1b1-19a5f9df4316
# ╟─ec3512fa-22de-4123-bb1f-440b899ac901


# Test geometry.jl

println("testing geometry")

using Test

tol = 1e-8

# ray2raydist
p1 = [0,0,0]
p2 = [0,0,1]
v1 = [1,0,0]
v2 = [0,1,0]
d = ray2raydist(p1,v1,p2,v2) # basic test
@test abs(d-1) < tol

d = ray2raydist(p1,v1,p2,v1) # Test parallel rays
@test abs(d-1) < tol

d = ray2raydist(p1,v1,p1,v2) # Test intersecting rays
@test abs(d) < tol
d = ray2raydist(p1,v1,p1,v1) # Test identical rays
@test abs(d) < tol

# circleintersectionpts
c1 = [-1,0]
r1 = 2
c2 = [1,0]
r2 = 2
(i1, i2) = circleintersectionpts(c1, r1, c2, r2, "lr")
@test abs(i1[1] < eps())
@test abs(i1[2] - sqrt(3)) < eps()
@test abs(i2[1] < eps())
@test abs(i2[2] + sqrt(3)) < eps()

# Check left and right solutions
ileft = circleintersectionpts(c1, r1, c2, r2, "l")
@test all(abs.(ileft - i1) .< eps())
iright = circleintersectionpts(c1, r1, c2, r2, "r")
@test all(abs.(iright - i2) .< eps())

# no intersection
(i1, i2) = circleintersectionpts(c1, r1, c2 .+ 10, r2, "lr")
@test isempty(i1)
@test isempty(i2)

# circles just touching
r1 = 1
r2 = 1
il = circleintersectionpts(c1, r1, c2, r2, "l")
@test all(abs.(il) .< eps())

# circle of zero radius
c2 = [0,0]
r2 = 0
il = circleintersectionpts(c1, r1, c2, r2, "l")
@test all(abs.(il) .< eps())

# rectintersect
r1 = [0, 0, 2, 4]
r2 = [0, 0, 1, 1]
@test rectintersect(r1, r2) # r2 inside r1
r2 = [2, 4, 1, 1]
@test rectintersect(r1, r2) # r2 touching r1
r2 = [10, 20, 1, 1]
@test !rectintersect(r1, r2) # r2 outside r1
@test rectintersect(r1, r1)  # r2 same as r1

# pointinconvexpoly
poly = [0  1  2  2  0
        0  0  0  1  1]

@test pointinconvexpoly([2,1], poly) == 0      # on boundary
@test pointinconvexpoly([0.5,0.5], poly) == 1  # inside
@test pointinconvexpoly([10,10], poly) == -1   # outside

# isconvexpoly
@test isconvexpoly(poly)  # convex
poly2 = [0    1  2  3  2  0
         0  -10  0  0  1  1]
@test !isconvexpoly(poly2)  # non-convex

# convexpolyintersect
poly1 = [0  2  3  0
         0  0  1  1]

poly2 = [0  2  1
         0  0  1]
@test convexpolyintersect(poly1, poly2)         # intersect
@test !convexpolyintersect(poly1, poly2 .+ 10)  # not intersect

poly3 = [-2  0  -1
          0  0   1]
@test convexpolyintersect(poly2, poly3)      # touching
@test convexpolyintersect(poly1, poly1)      # identical


# indicator of nonpositive orthant

"""
  IndNonpositive()

Returns the indicator function the nonpositive orthant, that is

  `g(x) = 0 if x ⩽ 0, +∞ otherwise`
"""

immutable IndNonpositive <: IndicatorConvexCone end

function (f::IndNonpositive){R <: Real}(x::AbstractArray{R})
  for k in eachindex(x)
    if x[k] > 0
      return +Inf
    end
  end
  return 0.0
end

function prox!{R <: Real}(f::IndNonpositive, x::AbstractArray{R}, y::AbstractArray{R}, gamma::Real=1.0)
  for k in eachindex(x)
    if x[k] > 0
      y[k] = zero(R)
    else
      y[k] = x[k]
    end
  end
  return 0.0
end

fun_name(f::IndNonpositive) = "indicator of the Nonpositive cone"
fun_dom(f::IndNonpositive) = "AbstractArray{Real}"
fun_expr(f::IndNonpositive) = "x ↦ 0 if all(0 ⩾ x), +∞ otherwise"
fun_params(f::IndNonpositive) = "none"

function prox_naive{R <: Real}(f::IndNonpositive, x::AbstractArray{R}, gamma::Real=1.0)
  y = min.(zero(R), x)
  return y, 0.0
end


using StochasticDelayDiffEq
using Random
using SparseArrays

function sir_dde!(du,u,h,p,t)
    (S,I,R) = u
    (β,c,τ) = p
    N = S+I+R
    infection = β*c*I/N*S
    (Sd,Id,Rd) = h(p, t-τ) # Time delayed variables
    Nd = Sd+Id+Rd
    recovery = β*c*Id/Nd*Sd
    @inbounds begin
        du[1] = -infection
        du[2] = infection - recovery
        du[3] = recovery
    end
    nothing
end;

# Define a sparse matrix by making a dense matrix and setting some values as not zero
A = zeros(3,2)
A[1,1] = 1
A[2,1] = 1
A[2,2] = 1
A[3,2] = 1
A = SparseArrays.sparse(A);

# Make `g` write the sparse matrix values
function sir_delayed_noise!(du,u,h,p,t)
    (S,I,R) = u
    (β,c,τ) = p
    N = S+I+R
    infection = β*c*I/N*S
    (Sd,Id,Rd) = h(p, t-τ) # Time delayed variables
    Nd = Sd+Id+Rd
    recovery = β*c*Id/Nd*Sd
    du[1,1] = -sqrt(infection)
    du[2,1] = sqrt(infection)
    du[2,2] = -sqrt(recovery)
    du[3,2] = sqrt(recovery)
end;

function condition(u,t,integrator) # Event when event_f(u,t) == 0
  u[2]
end;
function affect!(integrator)
  integrator.u[2] = 0.0
end;
cb = ContinuousCallback(condition,affect!);

δt = 0.1
tmax = 40.0
tspan = (0.0,tmax)
t = 0.0:δt:tmax;
u0 = [990.0,10.0,0.0]; # S,I,R

function sir_history(p, t)
    [1000.0, 0.0, 0.0]
end;

p = [0.05,10.0,4.0]; # β,c,τ
Random.seed!(1234);

prob_sdde = SDDEProblem(sir_dde!,sir_delayed_noise!,u0,sir_history,tspan,p;noise_rate_prototype=A);
sol_sdde = solve(prob_sdde,LambaEM(),callback=cb);


"""
Abstract type for the Baseline, each subtype should define the method
genBaseline(p::Baseline,,Nlon,Nlat,Ntime )
"""
abstract Baseline
export Baseline

"This function generates the Baseline 3D Array of size Nlon, Nlat, Ntime."
genBaseline(m::Baseline,Nlon,Nlat,Ntime)=error("Method genBaseline not implemented for $(typeof(m))")
export genBaseline

"""
Most trivial baseline case, constant mean `m` without seasonal cycle
"""
type ConstantBaseline <: Baseline
    m::Float64
end
export ConstantBaseline
genBaseline(c::ConstantBaseline,Ntime,Nlat,Nlon)=fill(c.m,Ntime,Nlat,Nlon)

"""
very simple periodic BaseLine returning a sine oscillation in the time domain,
where `nOsc` is the number of oscillations and `amp` is the amplitude
"""
type SineBaseline <: Baseline
	nOsc::Float64 # Number of oscillations
	amp::Float64
end
export SineBaseline
genBaseline(m::SineBaseline,Ntime,Nlat,Nlon)=[m.amp*sin(k*m.nOsc/Ntime*2*pi) for k=1:Ntime,j=1:Nlat,i=1:Nlon]

"""
A trend baseline where the mean is 0 and there is a trend in Lon, Lat and Time dimension. The range of the trend will be
`[-tlon,tlon], [-tlat,tlat], [-ttime,ttime]`
"""
type TrendBaseline <: Baseline
	tlon::Float64
	tlat::Float64
	ttime::Float64
end
export TrendBaseline
genBaseline(m::TrendBaseline,Ntime,Nlat,Nlon)=[(i-1)/(Nlon-1)*2*m.tlon-m.tlon + (j-1)/(Nlat-1)*2*m.tlat-m.tlat + (k-1)/(Ntime-1)*2*m.ttime-m.ttime for k=1:Ntime,j=1:Nlat,i=1:Nlon]


using TensorMatrices_lemon
using Base.Test

# creating a tenmat
#=
		"""
			matrix = rand(6,8)
			rowindex = [3,1]
			colindex = [2,4]
			tensorsize = (2,4,3,2)
			tenmat = Tenmat(matrix, rowindex, colindex, tensorsize)
		"""
		""" [test]
			tensorSize(tenmat)  == (2,4,3,2)

"""

# initialization
"""
	A = rand(4,7,2,9,6)
	rowindex = [5,2]
	colindex = [3,4,1]
	tenmat = Tenmat(A,rowindex,colindex)

	A = rand(4,7,6);
	tenmat = Tenmat(A,[3,1],[2])
	size(tenmat) == (24,7) || println("wrong")
	size(tenmat,2) == 7 || println("wrong")
	getindex(tenmat, 2, 6) == getindex(tenmat.matrix, 2, 6) || println("wrong")
	setindex!(tenmat, 100.0 , 2, 6)
	getindex(tenmat, 2, 6) == 100.0 || println("wrong")
	similar(tenmat, Int)
	similar(tenmat, Int, (5,2,7))
	similar(tenmat, Int, [1,3],[2])
	similar(tenmat, Int, [4,2,5], [1,3], (2,4,3,1,6))
	similar(tenmat)
	similar(tenmat, (5,2,7))
	similar(tenmat, [1,3],[2])
	similar(tenmat, [4,2,5], [1,3], (2,4,3,1,6))

"""
=#


using StatisticalRethinking, Optim
#gr(size=(600,600));

p_grid = range(0, step=0.001, stop=1)
prior = ones(length(p_grid))
likelihood = [pdf(Binomial(9, p), 6) for p in p_grid]
posterior = likelihood .* prior
posterior = posterior / sum(posterior)
samples = sample(p_grid, Weights(posterior), length(p_grid));
samples[1:5]

N = 10000
samples = sample(p_grid, Weights(posterior), N);

chn = MCMCChains.Chains(reshape(samples, N, 1, 1), ["toss"]);

MCMCChains.describe(chn)

plot(chn)

p = Vector{Plots.Plot{Plots.GRBackend}}(undef, 2)
p[1] = scatter(1:N, samples, markersize = 2, ylim=(0.0, 1.3), lab="Draws")

w = 6
n = 9
x = 0:0.01:1
p[2] = density(samples, ylim=(0.0, 5.0), lab="Sample density")
p[2] = plot!( x, pdf.(Beta( w+1 , n-w+1 ) , x ), lab="Conjugate solution")

plot(p..., layout=(1, 2))

# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl


# indicator of the free cone

export IndFree

"""
**Indicator of the free cone**

    IndFree()

Returns the indicator function of the whole space, or "free cone", *i.e.*,
a function which is identically zero.
"""
struct IndFree <: ProximableFunction end

is_separable(f::IndFree) = true
is_convex(f::IndFree) = true
is_affine(f::IndFree) = true
is_cone(f::IndFree) = true
is_smooth(f::IndFree) = true
is_quadratic(f::IndFree) = true

const Zero = IndFree

function (f::IndFree)(x::AbstractArray{T}) where {R, T <: RealOrComplex{R}}
    return R(0)
end

function prox!(y::AbstractArray{T}, f::IndFree, x::AbstractArray{T}, args...) where {R, T <: RealOrComplex{R}}
    y .= x
    return R(0)
end

function gradient!(y::AbstractArray{T}, f::IndFree, x::AbstractArray{T}) where {R, T <: RealOrComplex{R}}
    y .= T(0)
    return R(0)
end

fun_name(f::IndFree) = "indicator of the free cone"
fun_dom(f::IndFree) = "AbstractArray{Real}, AbstractArray{Complex}"
fun_expr(f::IndFree) = "x ↦ 0"
fun_params(f::IndFree) = "none"

function prox_naive(f::IndFree, x::AbstractArray{R}, gamma=R(1)) where {R, T <: RealOrComplex{R}}
    return x, R(0)
end


# ---
# title: 991. Broken Calculator
# id: problem991
# author: Indigo
# date: 2021-05-31
# difficulty: Medium
# categories: Math, Greedy
# link: <https://leetcode.com/problems/broken-calculator/description/>
# hidden: true
# ---
#
# On a broken calculator that has a number showing on its display, we can
# perform two operations:
#
#   * **Double** : Multiply the number on the display by 2, or;
#   * **Decrement** : Subtract 1 from the number on the display.
#
# Initially, the calculator is displaying the number `X`.
#
# Return the minimum number of operations needed to display the number `Y`.
#
#
#
# **Example 1:**
#
#
#
#     Input: X = 2, Y = 3
#     Output: 2
#     Explanation: Use double operation and then decrement operation {2 -> 4 -> 3}.
#
#
# **Example 2:**
#
#
#
#     Input: X = 5, Y = 8
#     Output: 2
#     Explanation: Use decrement and then double {5 -> 4 -> 8}.
#
#
# **Example 3:**
#
#
#
#     Input: X = 3, Y = 10
#     Output: 3
#     Explanation: Use double, decrement and double {3 -> 6 -> 5 -> 10}.
#
#
# **Example 4:**
#
#
#
#     Input: X = 1024, Y = 1
#     Output: 1023
#     Explanation: Use decrement operations 1023 times.
#
#
#
#
# **Note:**
#
#   1. `1 <= X <= 10^9`
#   2. `1 <= Y <= 10^9`
#
#
## @lc code=start
using LeetCode

function broken_calc(x::Integer, y::Integer)
    cnt = 0
    while x < y
        isodd(y) ? (y += 1) : y >>= 1
        cnt += 1
    end
    return cnt + x - y
end
## @lc code=end


using Base.Iterators
using Zygote

"""
    xlogx(x::Number)

Compute `x * log(x)`, returning zero if `x` is zero. (Copied from StatsFuns.jl package.)
"""
function xlogx(x::Number)
    result = x * log(x)
    ifelse(iszero(x), zero(result), result)
end

"""
Compute the von Neumann entropy of a density matrix `ρ`.
"""
neumann_entropy(ρ::AbstractMatrix) = -sum(xlogx.(real(eigvals(Matrix(ρ)))))

"""
    binary_entropy(p::Number)

Compute the binary entropy of `p` (natural logarithm).
"""
binary_entropy(p::Number) = -(xlogx(p) + xlogx(1 - p))

eig_entropy(θlist) = -sum(xlogx.(sum(θlist .* eigvs)))

"""
Compute `-log(Z)`, with `Z` the partition function.
"""
thermal_logZ(βH::AbstractMatrix) = -log(tr(exp(-Matrix(βH))))

"""
Use representation of density matrix in terms of Pauli matrices; cos(θ) is Bloch vector coefficient of Pauli-Z for each qubit
"""
latent_density(θlist) = DensityMatrix(kron([[1, 0, 0, cos(θ)] for θ in θlist]...), length(θlist))

"""
Trace distance to target density matrix `σ`.
"""
trdistance(θlist, σ, cgc) = 0.5 * opnorm(real.(matrix(apply(latent_density(θlist), cgc))) - σ, 1)

"""
Calculate fidelity as defined in arxiv:1910.02071
"""
function fidelity(θlist, σtherm, cgc)
    ρsqrt = sqrt(real.(matrix(apply(latent_density(θlist), cgc))))
    return real(tr(sqrt(ρsqrt * σtherm * ρsqrt))^2)
end

function trans_mat(L::Int)
    A = [1 0; 0 0; 0 0; 0 1]
    Avec = fill(A, L)
    kron(Avec...)
end

function ngradient(f, xs::AbstractArray...)
    grads = zero.(xs)
    for (x, Δ) in zip(xs, grads), i in 1:length(x)
        δ = sqrt(eps())
        tmp = x[i]
        x[i] = tmp - δ/2
        y1 = f(xs...)
        x[i] = tmp + δ/2
        y2 = f(xs...)
        x[i] = tmp
        Δ[i] = (y2-y1)/δ
        if eltype(x) <: Complex
            # derivative with respect to imaginary part
            x[i] = tmp - im*δ/2
            y1 = f(xs...)
            x[i] = tmp + im*δ/2
            y2 = f(xs...)
            x[i] = tmp
            Δ[i] += im*(y2-y1)/δ
        end
    end
    return grads
end

sigmoid(z::Real) = 1.0 / (1.0 + exp(-z))


module NewtonsMethod

using ForwardDiff

function newtonroot(f, fp; x0, tol=1e-7, maxiter=1000)
    abserror = Inf
    iter = 1
    x = x0
    while abserror > tol && iter <= maxiter
        x_new = x - f(x)/fp(x)
        iter = iter +1
        abserror = abs(x_new - x)
        x = x_new
    end
    return x
end

function newtonroot(f; x0, tol = 1e-7, maxiter=1000)
    fp = x -> ForwardDiff.derivative(f, x)
    newtonroot(f, fp; x0=x0, tol=tol, maxiter=maxiter)
end

export newtonroot
end


# Successive Projection Algorithm (SPA) for separable NMF
#
#   Reference: N. Gillis and S. A. Vavasis, "Fast and robust recursive
#   algorithms for separable nonnegative matrix factorization,"
#   IEEE Transactions on Pattern Analysis and Machine Intelligence,
#   vol. 36, no. 4, pp. 698-714, 2013.

mutable struct SPA{T}
    obj::Symbol   # objective :mse or :div

    function SPA{T}(;obj=:mse) where T
        obj == :mse || obj == :div || throw(ArgumentError("Invalid value for obj."))
        new{T}(obj)
    end
end

"""
separable_data(m,n,k)
Generate a (m x n) matrix X of nonnegative, separable data
with nonnegative rank k. The rows of X correspond to
observations and the columns of X correspond to features.
The separability condition implies that the columns of H
can be permuted to form a (k x k) diagonal block, and
the sum of the entries of each column of H is at most one.
Thus, the (scaled) columns of W appear in X.
"""
function separable_data(m, n, k)
    W = rand(m, k)

    # impose separability
    V = rand(k, n-k)
    V ./= sum(V, dims=1)
    H = [Matrix(I, k, k) V]
    # permute columns of H
    H = H[:, randperm(n)]

    return W, H
end

# initialization
function spa(X::Matrix{T}, k::Integer; nnls_alg::Tuple{Symbol, Symbol}=(:pivot, :cache)) where T

    # Normalize data so that columns of X sum to one
    R = X ./ sum(X, dims=1)

    # W = R[:,ai], where ai are the "anchor indices"
    # (ai forms the convex hull of columns in R)
    ai = Vector{Int}(undef, k)

    # Add columns of X that are furthest from span(W)
    for j = 1:k
        # Add column with the largest residual
        ai[j] = argmax(vec(sum(R.^2, dims=1)))

        # Project R onto the selected column
        p = R[:,ai[j]]         	# column we're projecting on
        R -= p*(p'*R) ./(p'*p) 	# new residual matrix
    end

    # Estimate W as the anchor columns of X
    W = X[:,ai]

    # Estimate H by non-negative least squares: minimize ||X - W*H||
    H = nonneg_lsq(W, X, alg=:fnnls)
    projectnn!(H)

    return W, H
end

# calculate statistics for result
function solve!(alg::SPA{T}, X, W, H) where T
    if alg.obj == :mse
        objv = convert(T, 0.5) * sqL2dist(X, W*H)
    elseif alg.obj == :div
        objv = gkldiv(X, W*H)
    else
        error("Invalid value for obj.")
    end
    return Result{T}(W, H, 0, true, objv)
end


#=

  Gaussian mixture model.

  cf ~/blog/gaussian_mixture_model.blog
     ~/psi/gaussian_mixture_model.psi
     ~/webppl/gaussian_mixture_model.wppl

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

@model function gaussian_mixture_model()
    x = [0.2,1.0,0.5,0.6]
    n = length(x)

    p ~ Beta(0.5, 1)
    a ~ Uniform(-1, 1)
    b ~ Uniform(-1, 1)

    z = tzeros(n)
    x = tzeros(n)
    for i in 1:n
        z[i] ~ Bernoulli(p)
        if z[i] == 1
            x[i] ~ Normal(a, 1.0)
        else
            x[i] ~ Normal(b, 1.0)
        end
    end

    a_is_greater_than_b ~ Dirac(a > b)

end

model = gaussian_mixture_model()

num_chns = 4

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), MCMCThreads(), 100_000, num_chns)
# chns = sample(model, MH(), MCMCThreads(), 10_000, num_chns)
# chns = sample(model, MH(), 100_000)

# chns = sample(model, PG(15), MCMCThreads(), 10_000, num_chns)

# chns = sample(model, SMC(1000), MCMCThreads(), 10_000, num_chns)
chns = sample(model, SMC(), 40_000)
# chns = sample(model, IS(), 10_000)

#
# chns = sample(model, Gibbs(HMC(0.1,5,:a,:b),PG(15,:p,:a_is_greater_than_b)), 10_000)
# chns = sample(model, Gibbs(NUTS(1000,0.65,:a,:b),PG(15,:p,:a_is_greater_than_b)), 10_000)

display(chns)
# display(plot(chns))

# show_var_dist_pct(chns,:a,20)
# show_var_dist_pct(chns,:b,20)
# show_var_dist_pct(chns,:p,20)

show_var_dist_pct(chns,:a_is_greater_than_b)


"""
#### function ```get_area(x1::Float64, x2::Float64, a::Float64, b::Float64)```

    Description of ```get_area```
    ----------------------------------------
Returns the area under trapezoid, where "roof" is defined by a linear function y = ax +b.

"""
function get_area(x1::Float64, x2::Float64, a::Float64, b::Float64)
    # area under the trapezoid is A = Δx * ⟨y⟩
    # where Δx is the width of the base of trapezoid
    # and ⟨y⟩ is mean height

    Δx = abs(x2-x1)
    mean_y = abs( get_line_point(x2,a,b) + get_line_point(x1,a,b)) / 2
    return Δx * mean_y
end

"""
#### function ```get_row_volume(df_row::DataFrame, thickness = 0.001)```

    Description of ```get_row_volume```
    ----------------------------------------
Returns the volume of trapezoid, where "roof" is defined by a linear function y = ax +b and thickness is the step between rows - ΔE.
```df_row``` is the df subset where df.E1 .== e1, e1 is the constant energy for which the linear approximation was made.

"""
function get_row_volume(df_row::DataFrame, thickness = 0.001)
    totalArea = sum(get_area.(df_row.minE, df_row.maxE, df_row.a, df_row.b))
    volume = totalArea*thickness
    return volume
end

"""
#### function ```get_total_volume(df_rebinned::DataFrame, thickness = 0.001)```

    Description of ```get_total_volume```
    ----------------------------------------
Returns the total volume as a sum of all rows. Thickness is the step between energies, ΔE.

"""
function get_total_volume(df_rebinned::DataFrame, thickness = 0.001)
    volume = 0.0
    for e in unique(df_rebinned.E1)
        volume += get_row_volume(df_rebinned[df_rebinned.E1 .== e, :], thickness)
    end
    return volume
end

"""
#### function ```get_segment_volume(df::DataFrame, X1::Float64, X2::Float64, thickness = 0.001)```

    Description of ```get_segment_volume```
    ----------------------------------------
Returns the volume of a segment enclosing x1, x2.

"""
function get_segment_volume(df::DataFrame, X1::Float64, X2::Float64, thickness = 0.001)
    X1 < minimum(df.minE) && (X1 = minimum(df.minE))  #if x1 is less than the lowest minE value in df, set X1 to the lowest value
    X2 > maximum(df.maxE) && (X2 = maximum(df.maxE))  #if x2 is more than the highest minE value in df, set X2 to the highest value

    df_segment = subset(df, :minE => e -> e .<= X1, :maxE => e -> e .> X1, view = :true) # return df subset where minE < X1 < maxE

    if nrow(df_segment) == 0
        return 0.0
    end

    A = 0.0

    if(X1 > df_segment.minE[1]) && (X2 < df_segment.maxE[1] )                     # if the bounds are within one line region
        A += get_area(X1, X2, df_segment.a[1], df_segment.b[1])
        return A*thickness
    end

    while df_segment.maxE[1] - X2 < 0
        A += get_area(X1, df_segment.maxE[1], df_segment.a[1], df_segment.b[1])
        X1 = df_segment.maxE[1]
        df_segment = subset(df, :minE => e -> e .<= X1, :maxE => e -> e .> X1, view = :true)

    end
    A += get_area(df_segment.minE[1], X2, df_segment.a[1], df_segment.b[1])
    return A*thickness
end

"""
#### function ```get_volume_matrix(df::DataFrame, eMin, eMax, dE, thickness = 0.001)```

    Description of ```get_volume_matrix```
    ----------------------------------------
Returns a square matrix of volumes. Each cell defines volume of a segment defined by dE1*dE2 = dE^2.

"""
function get_volume_matrix(df::DataFrame, eMin, eMax, dE, thickness = 0.001) #returns square matrix with volumes
    dims = length(minimum(df.E1):dE:maximum(df.E1))  # dimension of the square matrix

    volumesTruncated = zeros(dims,dims) # final volume to output with dimensions n x n
    temp_volumes = zeros(dims, length(unique(df.E1)))  # 30x2997

    for (idxE1, uniqueE1) in enumerate(unique(df.E1))  #1:2997
        df_e1 = subset(df, :E1 => e -> e .== uniqueE1)

        for (idxEnergy, energy) in enumerate(eMin:dE:eMax) # 1:30
            temp_volumes[idxEnergy, idxE1] = get_segment_volume(df_e1, energy, energy+dE, thickness)
        end
    end

    n = floor(Int,length(unique(df.E1))/ dims )  # how many cols to truncate
    for r in 1:length(temp_volumes[:,1])              # truncating n cols into 1
        volumesTruncated[r,:] = [sum(temp_volumes[r,i:i+n]) for i in 1:n:length(temp_volumes[r,:])-n]
    end
    return volumesTruncated
end


######################################################################
# weierstrasspoints.jl: Addition laws for projective points on Weierstrass curves
######################################################################

export infinity, projective_add, projective_scalar_mul

######################################################################
# Basic methods
######################################################################

"""
Get the point at infinity on an elliptic curve in Weierstrass form.
"""
function infinity(E::AbstractWeierstrass)
    R = base_ring(E)
    return EllipticPoint(Nemo.zero(R), Nemo.one(R), Nemo.zero(R), E)
end

function zero(E::AbstractWeierstrass)
	return infinity(E)
end

######################################################################
# Addition law
######################################################################

"""
Get the opposite of a point on an elliptic curve in Weierstrass form.
"""
function -(P::EllipticPoint)
	E = P.curve
	x, y, z = coordinates(P)
	a1, _, a3, _, _ = a_invariants(E)
    return EllipticPoint(x, - y - a1 * x - a3 * z, z, E)
end

"""
Get the sum of two normalized projective points on the same Weierstrass curve, assuming they are not equal and not inverse of each other.
"""
function _addgeneric(P::EllipticPoint{T}, Q::EllipticPoint{T}) where T<:Nemo.FieldElem
    E = P.curve
	a1, a2, a3, _, _ = a_invariants(E)

	xq, yq, zq = coordinates(Q)
	@assert zq == 1
	xp, yp, zp = coordinates(P)
	@assert zp == 1

	#sanity check
	@assert xp != xq

    denom = xq - xp
	inverse = 1//denom
    lambda = (yq - yp) * inverse
    nu = (yp * xq - xp * yq) * inverse

    Xplus = lambda^2 + a1 * lambda - a2 - xp - xq
    Yplus = -(lambda + a1) * Xplus - nu - a3
    Zplus = Nemo.one(base_ring(P))
    return EllipticPoint(Xplus, Yplus, Zplus, E)
end

"""
Get the sum of two normalized projective points on the same Weierstrass curve, assuming they have equal x-coordinate.
"""
function _addequalx(P::EllipticPoint{T}, Q::EllipticPoint{T}) where T<:Nemo.FieldElem
    E = P.curve
	a1, a2, a3, a4, a6 = a_invariants(E)

	xp, yp, zp = coordinates(P)
	@assert zp == 1
	xq, yq, zq = coordinates(Q)
	@assert zq == 1

	#sanity check
	@assert xp == xq

    denom = yp + yq + a1 * xq + a3
    if iszero(denom)
	    return infinity(E)
    else
		inverse = 1//denom
	    lambda = (3 * xp^2 + 2 * a2 * xp + a4 - a1 * yp) * inverse
	    nu = (- xp^3 + a4 * xp + 2 * a6 - a3 * yp) * inverse
	    Xplus = lambda^2 + a1 * lambda - a2 - xp - xq
        Yplus = -(lambda + a1) * Xplus - nu - a3
        Zplus = one(base_ring(P))
	    return EllipticPoint(Xplus, Yplus, Zplus, E)
    end
end

"""
Get the double of a normalized point.
"""
function _double(P::EllipticPoint)
	if isinfinity(P)
		return infinity(P.curve)
	else
		return _addequalx(P, P)
	end
end

"""
Get the sum of two projective points on the same Weierstrass curve.

"""
function +(P::EllipticPoint{T}, Q::EllipticPoint{T}) where T<:Nemo.FieldElem
    P = normalized(P)
    Q = normalized(Q)
	xp, _, _ = coordinates(P)
	xq, _, _ = coordinates(Q)
    if isinfinity(P)
        return Q
    elseif isinfinity(Q)
        return P
    elseif xp == xq
		return _addequalx(P,Q)
    else
		return _addgeneric(P,Q)
    end
end

function -(P::EllipticPoint{T}, Q::EllipticPoint{T}) where T<:FieldElem
	return P + (-Q)
end

"""
Get a scalar multiple of a point on a Weierstrass curve.
"""

function *(k::Integer, P::EllipticPoint)
	E = P.curve
	P = normalized(P)
	if k == 0
		return infinity(E)
	elseif k<0
		return (-k) * (-P)
	else
		if isinfinity(P)
			return infinity(E)
		else
			return _ladder(k, P)
		end
	end
end

# function *(k::Integer, P::EllipticPoint)
# 	return k * P
# end

function _ladder(m::Integer, P::EllipticPoint)
    p0 = P
    for b in Iterators.drop(Iterators.reverse(digits(Int8, m, base=2)), 1)
        if (b == 0)
            p0 = _double(p0)
        else
            p0 = P + _double(p0)
        end
    end
    return p0
end

######################################################################
# Projective addition law for Short Weierstrass curves
######################################################################

# P = (x1, y1, z1)
# Q = (x2, y2, z2)
# P + Q = (x3, y3, z3)
# if P != Q then
# x3 = (x2 z1 - x1 z2) [ (y2 z1 - y1 z2)^2 z1 z2 - (x2 z1 - x1 z2)^2 (x2 z1 + x1 z2) ]
# y3 = (y2 z1 - y1 z2) [ (x2 z1 - x1 z2)^2 (x2 z1 + 2 x1 z2) - (y2 z1 - y1 z2)^2 z1 z2 ] - (x2 z1 - x1 z2)^3 y1 z2
# z3 = (x2 z1 - x1 z2)^3 z1 z2
# if P = Q then
# x3 = 2 y1 z1 [ (a z1^2 + 3 x1^2)^2 - 8 x1 y1^2 z1 ]
# y3 = (a z1^2 + 3 x1^2 ) [ 12 x1 y1^2 z1 - a^2 (z1^2 + 3 x1^2)^2 ] - 8 y1^4 z1^2
# z3 = (2 y1 z1)^3

#This function is only to be used with distinct points on the same short Weierstrass curve
#xdet is x2 z1 - x1 z2, and ydet is y2 z1 - y1 z2
function _projective_add_neq(P::EllipticPoint{T}, Q::EllipticPoint{T}, xdet::T, ydet::T) where T
	x1, y1, z1 = coordinates(P)
	x2, y2, z2 = coordinates(Q)
	xdet2 = xdet^2
	xdet3 = xdet2 * xdet
	ydet2 = ydet^2
	z1z2 = z1 * z2
	#we could save a few multiplications in what follows
	x3 = xdet * ( ydet2 * z1z2 - xdet2 * (x2 * z1 + x1 * z2) )
	y3 = ydet * ( xdet2 * (x2 * z1 + 2 * x1 * z2) - ydet2 * z1z2 ) - xdet3 * y1 * z2
	z3 = xdet3 * z1z2
	res = Point(x3, y3, z3, base_curve(P))
	#@assert isvalid(res) #sanity check
	return res
end

#This function is only to be used with a point on a short Weierstrass curve
function _projective_dbl(P::EllipticPoint{T}) where T
	x, y, z = coordinates(P)
	_, _, _, a, b = a_invariants(base_curve(P))
	factor = a * z^2 + 3 * x^2
	factor2 = factor^2
	yz = y * z
	xy2z = x * y * yz
	#we could save again a few multiplications in what follows
	xprime = 2 * yz * ( factor2 - 8 * xy2z )
	yprime = factor * ( 12 * xy2z - factor2 ) - 8 * (yz)^2 * y^2
	zprime = 8 * (yz)^3
	res = Point(xprime, yprime, zprime, base_curve(P))
	#@assert isvalid(res) #sanity check
	return res
end

#This function is only to be used with two points on the same short Weierstrass curve
#Compute xdet and ydet, if they are both zero go to _projective_dbl
function projective_add(P::EllipticPoint{T}, Q::EllipticPoint{T}) where T
	x1, y1, z1 = coordinates(P)
	x2, y2, z2 = coordinates(Q)
	xdet = x2 * z1 - x1 * z2
	ydet = y2 * z1 - y1 * z2
	if ((xdet == 0) & (ydet == 0))
		return _projective_dbl(P)
	else
		return _projective_add_neq(P, Q, xdet, ydet)
	end
end

#Here we assume v is a positive integer and P lives on a short Weierstrass curve
function _projective_scalar_mul(P::EllipticPoint, v::Integer)
    P0 = P
    for b in Iterators.drop(Iterators.reverse(digits(Int8, v, base=2)), 1)
        P0 = _projective_dbl(P0)
        if (b == 1)
            P0 = projective_add(P0, P)
        end
    end
    return P0
end

function projective_scalar_mul(P::EllipticPoint, v::Integer)
	if v == 0
		return infinity(base_curve(E))
	elseif v < 0
		return - _projective_scalar_mul(P, -v)
	else
		return _projective_scalar_mul(P, v)
	end
end


#################################
## Bayesian model selection demo
## for polynomial regression

using PyPlot, PyCall
using Distributions

function poly(X_raw, M)
    N = size(X_raw, 1)
    X = zeros(M, N)
    for m in 0 : M - 1
        X[m+1,:] = X_raw.^m
    end
    return X
end

function learn_bayes(X_raw, Y, M, sig2_y, Sig_w, X_lin)
    X = poly(X_raw, M)
    N = size(X_raw, 1)

    # calc posterior
    Sig_w_h = inv(X*inv(sig2_y*eye(N))*X' + inv(Sig_w))
    mu_w_h = Sig_w_h * (X * inv(sig2_y * eye(N)) * Y)

    # calc predictive
    X_test = poly(X_lin, M)
    Y_est = (mu_w_h'*X_test)'
    sig2_y_prd = sig2_y + diag(X_test'Sig_w_h*X_test)

    # calc evidence
    evidence = -0.5*(sum(Y)*inv(sig2_y) +N*log.(sig2_y) + N*log.(2*pi)
                     + logdet(Sig_w)
                     - (mu_w_h'*inv(Sig_w_h)*mu_w_h)[1] - logdet(Sig_w_h)
                     )
    return Y_est, sqrt.(sig2_y_prd), evidence
end

function test()
    # linspace
    X_lin = linspace(-1, 7, 200)

    # generate data
    N = 10
    sig2_y = 0.1
    X = 2*pi*rand(N)
    Y_true = [sin.(x) for x in X_lin]
    Y_obs = [sin.(x) + sig2_y * randn() for x in X]

    dims = [1, 2, 3, 4, 5, 10]

    # learning via Bayes
    sig2_w = 1.0
    Y_bayes = [learn_bayes(X, Y_obs, m, sig2_y, sig2_w*eye(m), X_lin) for m in dims]

    #############
    # compute evidences
    evidence = [learn_bayes(X, Y_obs, m, sig2_y, sig2_w*eye(m), X_lin)[3] for m in dims]
    figure("evidence")
    clf()
    plot(1:length(dims), evidence)
    xticks(1:length(dims),dims)
    ylabel(("\$\\ln p(\\bf{Y}|\\bf{X})\$"), fontsize=20)
    xlabel(("\$M\$"), fontsize=20)

    #############
    # visualize
    x_min = X_lin[1]
    x_max = X_lin[end]
    y_min = -4
    y_max = 4

    figure("prediction")
    clf()
    for k in 1 : 6
        subplot(230 + k)
        plot(X_lin, Y_bayes[k][1])
        plot(X_lin, Y_bayes[k][1] + Y_bayes[k][2], "c--")
        plot(X_lin, Y_bayes[k][1] - Y_bayes[k][2], "c--")
        plot(X, Y_obs, "ko")
        xlim([x_min, x_max])
        ylim([y_min, y_max])
        text(x_max - 2.5, y_max - 1, @sprintf("M=%d", dims[k]))
    end
    show()
end

test()


"""
Store the singular value decomposition of a matrix:

    A = UΣV'

"""
struct LowRankMatrix{T} <: AbstractMatrix{T}
    U::Matrix{T}
    Σ::Matrix{T}
    V::Matrix{T}
end

size(L::LowRankMatrix) = size(L.U, 1), size(L.V, 1)
rank(L::LowRankMatrix) = size(L.Σ,1)
norm(L::LowRankMatrix) = first(L.Σ)
cond(L::LowRankMatrix) = ifelse(rank(L) < minimum(size(L)), Inf, first(L.Σ)/last(L.Σ))

istriu(L::LowRankMatrix) = false
istril(L::LowRankMatrix) = false
issymmetric(L::LowRankMatrix) = false
ishermitian(L::LowRankMatrix) = false

function getindex(L::LowRankMatrix{T},i::Integer,j::Integer) where T
    ret = zero(T)
    U, Σ, V, r = L.U, L.Σ, L.V, rank(L)
    # for k = r:-1:1
    #     ret += U[i,k]*Σ[k,k]*V[j,k]
    # end
    U[i,:]'*Σ*V[j,:]

    # ret
end


# Counts the number of local maxima.
function count_maxima(histogram::AbstractArray)
    maxima_count = 0
    valid_range = firstindex(histogram)+1:lastindex(histogram)-1
    for i in valid_range
        if  histogram[i-1] < histogram[i] > histogram[i+1]
            maxima_count += 1
        end
    end
    return maxima_count
end

function find_maxima_indices(histogram::AbstractArray)
    indices = Array{Int, 1}()
    t = 0
    valid_range = firstindex(histogram)+1:lastindex(histogram)-1
    for i in valid_range
        if histogram[i-1] < histogram[i] > histogram[i+1]
            push!(indices, i)
        end
    end
    return indices
end

function smooth_histogram(histogram::AbstractArray, max_iterations::Int)
    histogram_local = convert.(Float32, histogram)
    maxima_count = count_maxima(histogram)
    smooth_histogram = similar(histogram_local)
    lb = firstindex(histogram_local)
    ub = lastindex(histogram_local)
    iterations = 0
    # Smooth histogram until at most two peaks remain or max_iterations is reached.
    while maxima_count > 2 && iterations < max_iterations
        smooth_histogram[lb] = (2*histogram_local[lb] + histogram_local[lb+1]) / 3
        smooth_histogram[ub] = (2*histogram_local[ub] + histogram_local[ub-1]) / 3
        for i in lb+1:ub-1
            smooth_histogram[i] = (histogram_local[i-1] + histogram_local[i] + histogram_local[i+1]) / 3
        end
        maxima_count = count_maxima(smooth_histogram)
        copyto!(histogram_local, smooth_histogram)
        iterations += 1
    end
    return histogram_local
end


# basic one
Y = [
     0  3;
     1  0;
     1 -3;
    -2  3;
     0  0;
]

D, X = ksvd(Y, 8, max_iter_mp = 600, sparsity_allowance = 1.0)
@test norm(Y-D*X) < 1e-4

# sparsity_allowance must be > 0
@test_throws ArgumentError ksvd(Y, 20, sparsity_allowance = -0.1)
@test_throws ArgumentError ksvd(Y, 20, sparsity_allowance = 1.1)

# should work normally
ksvd(Y, 20, max_iter = 1)
ksvd(Y, 20, sparsity_allowance = 0.0)
ksvd(Y, 20, sparsity_allowance = 1.0)

# But should work well when size(Y, 1) == n_atoms
Y = [
    -1 1 2;
     1 0 1
]
D, X = ksvd(Y, 2, max_iter_mp = 4000)
@test norm(Y-D*X) < 0.001  # relax the constraint since the dictionary is small

# Return only if X is sparse enough
Y = [
	0  2  3 -1  1;
	1 -3  1  3  0
]

# More than 20% of elements in X must be zeros
sparsity_allowance = 0.2
D, X = ksvd(Y, 5, max_iter = Int(1e10), sparsity_allowance = sparsity_allowance)
@test sum(iszero, X) / length(X) > sparsity_allowance


# This file contains additional custom distibutions using the interface of Distibutions.jl

"""
    Sine()

A custom univariate distribution.
The pdf is a sine function defined between 0 and π.
This is a common prior distribution used when fitting orbits to astrometry.

The full Distributions.jl interface is not yet defined for this distribution,
but the following methods work:
pdf, logpdf, minimum, maximum, insupport, mean, var, cdf, quantile
"""
struct Sine <: ContinuousUnivariateDistribution end
export Sine

function Distributions.pdf(d::Sine, x::Real)
    if 0 < x < π
        return sin(x)/2
    else
        return 0
    end
end
function Distributions.logpdf(d::Sine, x::Real)
    if 0 < x < π
        return log(sin(x)/2)
    else
        return -Inf
    end
end
Distributions.minimum(d::Sine) = 0
Distributions.maximum(d::Sine) = π
Distributions.insupport(d::Sine, x::Real) = 0 < x < π
Distributions.mean(d::Sine) = π/2

# See https://stats.libretexts.org/Bookshelves/Probability_Theory/Probability_Mathematical_Statistics_and_Stochastic_Processes_(Siegrist)/05%3A_Special_Distributions/5.27%3A_The_Sine_Distribution
Distributions.var(d::Sine) = 1/4 - 2/pi^2
Distributions.cdf(d::Sine, x::Real)= 1/2*(1-cos(x))
Distributions.quantile(d::Sine, p::Real) = acos(1-2p)


## This is to test the first derivative of sin
using MAT,Plots,LinearAlgebra
const USE_GPU=false;  # Use GPU? If this is set false, then no GPU needs to be available
using ParallelStencil
using ParallelStencil.FiniteDifferences1D
@static if USE_GPU
    @init_parallel_stencil(CUDA,Float64,3);
else
    @init_parallel_stencil(Threads,Float64,3);
end
dx=.001;
x=0:dx:6*pi;
nx=length(x);
y=cos.(x);
## analytical expression of d^2y/dx^2
d2y_dx2=-cos.(x);
## d^2y/dx^2 2nd-order accuracy
@parallel function Dx_inn_2nd_order_accuracy(in,out,dx)
    @inn(out)=@d_1(in)/dx;
    return nothing
end

@parallel_indices (iy) function u_1_plus_2nd_order_accuracy(in,out)
out[2:end-1]=in[:];
return nothing
end

@parallel_indices (iy) function u_1_minus_2nd_order_accuracy(in,out)
out[1:end-2]=in[:];
return nothing
end

dtt=@zeros(nx-2,);
tt=@zeros(nx,);
d2y_dx2_2nd_order_accuracy=@zeros(nx,);

@parallel Dx_inn_2nd_order_accuracy(y,dtt,dx);
@parallel (1:1) u_1_minus_2nd_order_accuracy(dtt,tt);
@parallel Dx_inn_2nd_order_accuracy(tt,dtt,dx);
@parallel (1:1) u_1_plus_2nd_order_accuracy(dtt,d2y_dx2_2nd_order_accuracy);
## d^2y/dx^2 8th-order accuracy
@parallel function Dx_inn_8th_order_accuracy(in,out,dx)
    @inn(out)=@d_8(in)/dx;
    return nothing
end

@parallel_indices (iy) function u_1_plus_8th_order_accuracy(in,out)
out[4:end-2]=in[:];
return nothing
end

@parallel_indices (iy) function u_1_minus_8th_order_accuracy(in,out)
out[3:end-3]=in[:];
return nothing
end

dtt=@zeros(nx-5,);
tt=@zeros(nx,);
d2y_dx2_8th_order_accuracy=@zeros(nx,);

@parallel Dx_inn_8th_order_accuracy(y,dtt,dx);
@parallel (1:1) u_1_minus_8th_order_accuracy(dtt,tt);
@parallel Dx_inn_8th_order_accuracy(tt,dtt,dx);
@parallel (1:1) u_1_plus_8th_order_accuracy(dtt,d2y_dx2_8th_order_accuracy);
## d^2y/dx^2 12th-order accuracy
@parallel function Dx_inn_12th_order_accuracy(in,out,dx)
    @inn(out)=@d_12(in)/dx;
    return nothing
end

@parallel_indices (iy) function u_1_plus_12th_order_accuracy(in,out)
out[6:end-4]=in[:];
return nothing
end

@parallel_indices (iy) function u_1_minus_12th_order_accuracy(in,out)
out[5:end-5]=in[:];
return nothing
end

dtt=@zeros(nx-9,);
tt=@zeros(nx,);
d2y_dx2_12th_order_accuracy=@zeros(nx,);

@parallel Dx_inn_12th_order_accuracy(y,dtt,dx);
@parallel (1:1) u_1_minus_12th_order_accuracy(dtt,tt);
@parallel Dx_inn_12th_order_accuracy(tt,dtt,dx);
@parallel (1:1) u_1_plus_12th_order_accuracy(dtt,d2y_dx2_12th_order_accuracy);
## plot derivatives
IND=30:nx-30;

plot(x[IND],d2y_dx2[IND],label="analytical",color="red",title="d^2y/dx^2",lw=3);
plot!(x[IND],d2y_dx2_2nd_order_accuracy[IND],label="2nd-order",color="blue",lw=3)
plot!(x[IND],d2y_dx2_8th_order_accuracy[IND],label="8th-order",color="green",lw=3)
plot!(x[IND],d2y_dx2_12th_order_accuracy[IND],label="12th-order",color="yellow",lw=3)
## plot error
plot(x[IND],d2y_dx2_2nd_order_accuracy[IND]-d2y_dx2[IND],label="2nd-order",color="blue",lw=3);
plot!(x[IND],d2y_dx2_8th_order_accuracy[IND]-d2y_dx2[IND],label="8th-order",color="green",lw=3);
plot!(x[IND],d2y_dx2_12th_order_accuracy[IND]-d2y_dx2[IND],label="12th-order",color="yellow",lw=3)
## norm error
e1=norm(d2y_dx2_2nd_order_accuracy[IND]-d2y_dx2[IND],2)
e2=norm(d2y_dx2_8th_order_accuracy[IND]-d2y_dx2[IND],2)
e3=norm(d2y_dx2_12th_order_accuracy[IND]-d2y_dx2[IND],2)


using InstantiateFromURL
# optionally add arguments to force installation: instantiate = true, precompile = true
github_project("QuantEcon/quantecon-notebooks-julia", version = "0.8.0")

# Exercise 1

# Part 1: compute inner product using zip()

A = [1 2 3]
B = [4 5 6]
@show sum(a * b for (a,b) ∈ zip(A,B))

# part 2: count even numbers between 0,99 using comprehension
@show even = sum(iseven, 1:99)

# part 3: count number of pairs where both elements are even using comprehension
A = [2 4 9 12]
B = [5 2 8 10]
@show sum( iseven(a)&&iseven(b) for (a,b) ∈ zip(A,B))

# or
pairs = ((2, 5), (4, 2), (9, 8), (12, 10))
@show sum(xy -> all(iseven, xy), pairs);

# Exercise 2

function p(x,coef)
    values = zeros(0)
    for (i, α) in enumerate(coef)
        val = α * x^(i-1)
        push!(values,val)
    end
    return sum(values)
end

coef = [12 0.5 3]
x = 2

@show sol = p(x,coef)

# or

p(x,coef) = sum( α * x^(i-1) for (i,α) in enumerate(coef))

@show sol2 = p(x,coef);

# Exercise 3

uppercase("foo")

function countuppercase(string)
    count = 0
    for letter in string
        if letter == uppercase(letter) && isletter(letter)
            count += 1
        end
    end
    return count
end

@show sol = countuppercase("Romulus and Remus founded Rome")

# or

countuppercase(string) = sum( letter == uppercase(letter) && isletter(letter) for letter in string)

@show sol2 = countuppercase("Romulus and Remus founded Rome");

# Exercise 4

sameset(seq_a,seq_b) = all( a ∈ seq_b for a in seq_a )

A = ["apple" , 4, 2]
B = [2, "apple", 4]

sameset(A,B)

# Exercise 5 piecewise linear interpolation

function linapprox(f,a,b,n,x)

    interval_length = b-a # total length of interval

    no_subintervals = n-1 # number of subintervals = gridpoints - 1

    subinterval_length = interval_length/no_subintervals

    step = subinterval_length

    # find grid point larger than x
    point = a
    while point < x
        point+= step
    end

    # x must lie between the gridpoints (point - step) and point
    u, v = point - step, point

    # computer interpolation polynomial
    return f(u) + (x-u) * (f(v)-f(u)) / (v-u)
end

f(x) = exp(x) + x^2
f_approx(x) = linapprox(f, -1, 1, 3, x)

using Plots

x_grid = range(-5.0, 5.0, length = 400)
y_vals = f.(x_grid)
y = f_approx.(x_grid)
plot(x_grid, y_vals, label = "true")
plot!(x_grid, y, label = "approximation")

# Exercise 6

open("us_cities.txt", "w") do f
  write(f,
"new york: 8244910
los angeles: 3819702
chicago: 2707120
houston: 2145146
philadelphia: 1536471
phoenix: 1469471
san antonio: 1359758
san diego: 1326179
dallas: 1223229")
end

pop = open("us_cities.txt", "r")

total_pop =0
for line in eachline(pop)
    city, population = split(line, ":")
    total_pop += parse(Int, population)
end

close(pop)
println("total population = $total_pop")


"""
    partition(array::Array{Int64,1}, low::Int64, high::Int64)

The partion algorithm is shuffeling the array for a given interval of low and high
boundaries.

# Arguments
- `array::Array{Int64,1}`: Unsorted array of integers
- `low::Int64`: Lowest index of the unsorted array or subarray
- `high::Int64`: Highes index of the unsorted array or subarray

"""
function partition(array::Array{Int64,1}, low::Int64, high::Int64)
    # index of smaller element
    i = low - 1
    # pivot
    pivot = array[high]

    for j in low:high

        # If current element is smaller than the pivot
        if array[j] < pivot

            # increment index of smaller element
            i = i + 1
            # Swap if the element found is greater than the next element
            array[i], array[j] = array[j], array[i]
        end
    end
    array[i+1], array[high] = array[high], array[i+1]
    return (i + 1)
end

"""
    quick_sorting(array::Array{Int64,1}, low = nothing, high = nothing)

The quick sort algorithm (QSA) works by selecting a pivot element from the array and
partitioning the other elements into two subarrays, according to whether they are less than
or greater than the pivot-window. Then the sorting of subarrays is recursively organized.
This procedure repeatedly happens until each subarray is organized; consequently, the
subarrays' merging is an organized array. For more information see:
[https://en.wikipedia.org/wiki/Quicksort#Parallelization](https://en.wikipedia.org/wiki/Quicksort#Parallelization)

# Arguments
- `array::Array{Int64,1}`: Unsorted array of integers
- `low::Int64`: Lowest index of the unsorted array or subarray
- `high::Int64`: Highes index of the unsorted array or subarray

# Examples
```julia-repl
julia> import ClassicAlgorithmsCollections
julia> arr = [64, 34, 25, 12, 22, 11, 90]
julia> ClassicAlgorithmsCollections.quick_sorting(arr)
[11, 12, 22, 25, 34, 64, 90]
```
"""
function quick_sorting(array::Array{Int64,1}, low = nothing, high = nothing)
    # If nothing, then the intial run, so low and high are set to array size
    if isnothing(low) && isnothing(high)
        low = 1
        high = length(array)
    end
    if low < high

        # p_i is partitioning index, array[p_i] is now at right place
        p_i = partition(array, low, high)

        # Separately sort elements before partition and after partition
        array = quick_sorting(array, low, p_i - 1)
        array = quick_sorting(array, p_i + 1, high)
    else
        return array
    end
end


include("../src/eis_series.jl")

function test_eis_series_length()
	print("eis_series.length...")

  @test length(eisenstein_series_poly(4, 5)) == 5
  @test length(eisenstein_series_poly(6, 500)) == 500
  @test length(eisenstein_series_poly(12, 1)) == 1
	println("PASS")
end

function test_eis_series_normalisation()
	print("eis_series.normalisation...")

  e4 = eisenstein_series_qexp(4, 5, QQ, "q", "linear")
  @test coeff(e4, 1) == 1
  @test coeff(e4, 0) == 1//240
  e6 = eisenstein_series_qexp(6, 5, QQ, "q", "constant")
  @test coeff(e6, 0) == 1
  @test coeff(e6, 1) == -504
  e12 = eisenstein_series_qexp(12, 5, ZZ, "q", "integral")
  @test coeff(e12, 0) == 691
  @test coeff(e12, 1) == 65520
	println("PASS")
end

function test_eis_series_coefficients()
	print("eis_series.coefficients...")

  e4 = eisenstein_series_qexp(4, 11, QQ, "q", "constant")
  S = parent(e4)
  q = gen(S)
  @test e4 - (1+240*q+2160*q^2+6720*q^3+17520*q^4+30240*q^5+60480*q^6+82560*q^7+140400*q^8+181680*q^9+272160*q^10+O(q^11)) == 0
  e6 = eisenstein_series_qexp(6, 11, ZZ, "q", "integral")
  S = parent(e6)
  q = gen(S)
  @test e6 - (1-504*q-16632*q^2-122976*q^3-532728*q^4-1575504*q^5-4058208*q^6-8471232*q^7-17047800*q^8-29883672*q^9-51991632*q^10+O(q^11)) == 0
  e12 = eisenstein_series_qexp(12, 11, QQ, "q", "linear")
  S = parent(e12)
  q = gen(S)
  @test e12 - (691//65520+q+2049*q^2+177148*q^3+4196353*q^4+48828126*q^5+362976252*q^6+1977326744*q^7+8594130945*q^8+31381236757*q^9+100048830174*q^10+O(q^11)) == 0
	println("PASS")
end

function test_eis_series()
	test_eis_series_length()
  test_eis_series_normalisation()
  test_eis_series_coefficients()

	println("")
end


using Test

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

# our boarding pass is a thinly disguised 10 bit integer
const factors = [512, 256, 128, 64, 32, 16, 8, 4, 2, 1]
const translator = Dict('F' => 0, 'L' => 0, 'B' => 1, 'R' => 1)

function seat(pass)
    bin = map(c -> translator[c], collect(pass))
    return sum(bin .* factors)
end

function findseat(seatids)
    allpossible = collect(minimum(seatids):maximum(seatids))
    return only(filter(i -> !(i in seatids), allpossible))
end

seatids = map(p -> seat(p), passes)

p1 = maximum(seatids)
p2 = findseat(seatids)

@assert(p1 == 994)
@assert(p2 == 741)

@test seat("BFFFBBFRRR") == 567
@test seat("FFFBBBFRRR") == 119
@test seat("BBFFBBFRLL") == 820

println("-----------------------------------------------------------------------")
println("binary boarding -- part one :: $p1")
println("binary boarding -- part two :: $p2")
println("-----------------------------------------------------------------------")


using DelimitedFiles

additiveFactorial(n::Int) :: Int = sum(1:n)

distance(pos1::Int, pos2::Int) :: Int = abs(pos1-pos2)

# Cost of a distance for problem 1
cost(t::Int,positions::Vector{Int}) :: Int = sum(map(p -> distance(t,p), positions))

# Cost of a distance for problem 2
function cost2(t::Int,positions::Vector{Int}) :: Int
  distances = map(p -> distance(t,p), positions)
  return sum( map( d -> additiveFactorial(d) , distances))
end

# Calculates least-cost alignment for problem 1.
function minimalAlignment(positions::Vector{Int}) :: Tuple{Int,Int}
  targets = Vector(0:maximum(positions))
  costs = map(t -> cost(t,positions), targets)
  i_best = argmin(costs)
  return (i_best, costs[i_best])
end

# Calculates least-cost alignment for problem 2
function minimalAlignment2(positions::Vector{Int}) :: Tuple{Int,Int}
  targets = Vector(0:maximum(positions))
  costs = map(t -> cost2(t,positions), targets)
  i_best = argmin(costs)
  return (i_best, costs[i_best])
end

# Read input
function readInput() :: Vector{Int}
  data = readdlm("./inputs/day7.txt", ',', Int)
  return reshape(data,length(data))
end

println("Problem 1: ")
positions = readInput()
res = minimalAlignment(positions)
display(res)

println("Problem 2: ")
positions = readInput()
res = minimalAlignment2(positions)
display(res)


using LinearAlgebra

"""
    lattice(s, N, ispbc=true)

Create a adjacency matrix of lattice `s` with size ``N``.
The periodic boundary condition is imposed by default.
Admissible values for `s` are:

| `s`                      | graph type                       |
| :------------------------|:---------------------------------|
| :chain                   | A one dimensional chain.         |
| :square                  | A two dimensional square lattice.|

# Examples
```julia-repl
julia> lattice(:chain, 3)
[0 1 1
 1 0 1
 1 1 0]

julia> lattice(:square, 4, ispbc=false)
[0 1 1 0
 1 0 0 1
 1 0 0 1
 0 1 1 0]
```
"""
function lattice(s::Symbol, N::Int; ispbc=true)
    graphmap = Dict(
        :chain => chain_graph,
        :square => square_graph,
    )

    if (s in keys(graphmap))
        return graphmap[s](N, ispbc)
    end
    throw(ArgumentError("$s is not a valid graph"))
end

"""
    chain_graph(N, ispbc)

An adjacency matrix of a chain graph with length ``N``.

# Examples
```julia-repl
julia> chain_graph(3)
[0 1 1
 1 0 1
 1 1 0]
```
"""
function chain_graph(N, ispbc)
    @assert N > 0 "N must be larger than 1."
    if N == 1
        return [0]
    end
    A = Matrix(SymTridiagonal(zeros(Int, N), ones(Int, N-1)))
    if ispbc
        A[1, N] += 1
        A[N, 1] += 1
    end
    A
end

"""
    issquare(N)

Check whether the number is perfect square or not.

# Examples
```julia-repl
julia> issquare(2)
false

julia> issquare(4)
true
```
"""
function issquare(N)
    isqrt(N)^2 == N
end

"""
    square_graph(N, ispbc)

An adjacency matrix of a square lattice whose number of vertices is ``N``.

# Examples
```julia-repl
julia> square_graph(4)
[0 2 2 0
 2 0 0 2
 2 0 0 2
 0 2 2 0]

julia> square_graph(4, ispbc=false)
[0 1 1 0
 1 0 0 1
 1 0 0 1
 0 1 1 0]
```
"""
function square_graph(N, ispbc)
    @assert N > 0 "N must be larger than 1."
    @assert issquare(N) "N must be a perfect square number."
    L = isqrt(N)
    if N == 1
        return [0]
    end

    A = transpose(reshape(1:N, L,L))
    h = [(A[CartesianIndex(i,j)], A[CartesianIndex(i,pbc(j+1,L))]) for i in 1:L for j in 1:L]
    v = [(A[CartesianIndex(i,j)], A[CartesianIndex(pbc(i+1,L),j)]) for i in 1:L for j in 1:L]
    bonds = vcat(v, h)

    h = [(A[CartesianIndex(i,1)], A[CartesianIndex(i,L)]) for i in 1:L]
    v = [(A[CartesianIndex(1,j)], A[CartesianIndex(L,j)]) for j in 1:L]
    pbcbonds = vcat(v, h)

    Res = zeros(Int, N, N)
    for b in bonds
        Res[b[1], b[2]] += 1
        Res[b[2], b[1]] += 1
    end
    if !ispbc
        for b in pbcbonds
            Res[b[1], b[2]] -= 1
            Res[b[2], b[1]] -= 1
        end
    end
    Res
end


"""
    circmean(x)

Compute the circular mean of `x` in radians.

[1] N. I. Fisher, Statistical Analysis of Circular Data. Cambridge University Press, 1993.
"""
function circmean(x)
    s = mean(sin, x)
    c = mean(cos, x)

    atan(s, c)
end

"""
    circmeand(x)

Compute the circular mean of `x` in degrees.

[1] N. I. Fisher, Statistical Analysis of Circular Data. Cambridge University Press, 1993.
"""
function circmeand(x)
    s = mean(sind, x)
    c = mean(cosd, x)

    atand(s, c)
end

"""
    circstd(x)

Compute the circular standard deviation of `x` in radians.

[1] N. I. Fisher, Statistical Analysis of Circular Data. Cambridge University Press, 1993.
"""
function circstd(x)
    s = mean(sin, x)
    c = mean(cos, x)

    sqrt(-2*log(hypot(c, s)))
end

"""
    circstdd(x)

Compute the circular standard deviation of `x` in degrees.

[1] N. I. Fisher, Statistical Analysis of Circular Data. Cambridge University Press, 1993.
"""
function circstdd(x)
    s = mean(sind, x)
    c = mean(cosd, x)

    rad2deg(sqrt(-2*log(hypot(c, s))))
end

"""
    mean_std_range(x, events) -> Tuple(avg, std)

Find the average range and range variability of `x` for the set of all intervals given by
`events`.
"""
function mean_std_range(x, events::AbstractVector{Int})
    mi, mx = intervalextrema(x, events)
    roms = mx - mi

    return mean(roms), std(roms)
end

"""
    avgextrema(x, events) -> Tuple(min, max)

Find the average minima and maxima for the set of all intervals given by `events`.
"""
function avgextrema(x, events::AbstractVector{Int})
    mi, mx = intervalextrema(x, events)

    return mean(mi), mean(mx)
end

function intervalextrema(x::AbstractVector{T}, events) where T
    rgs = intervals(events; endincluded=false)
    mi = Vector{T}(undef, length(rgs))
    mx = Vector{T}(undef, length(rgs))

    intervalextrema!(mi, mx, x, rgs)
end

function intervalextrema!(mi, mx, x, rgs)
    @inbounds for (i, rg) in enumerate(rgs)
        mi[i], mx[i] = extrema(view(x, rg))
    end

    return mi, mx
end


"""get all groups"""
function getgrp(node_memory::Vector{Dict{Int,Int}})
    groups = Dict{Int,Vector{Int}}()
    for i=1:length(node_memory)
        for k in keys(node_memory[i])
            if haskey(groups, k)
                push!(groups[k], i)
            else
                groups[k] = [i]
            end
        end
    end
    collect(values(groups))
end

"""
calculate the entropy of a probility distribution
"""
function entropy(P::Vector{Float64})
    H = 0.0
    for i=1:length(P)
        # treat 0*log(0) as being equal to zero
        H += P[i] > 0.0 ? P[i]*log(P[i]) : 0.0
    end
    -H > 0 ? -H : 0.0
end

"""read groups from file"""
function readgrp(filename)
    groups = Vector{Int}[]
    f = open(filename, "r")
    for ll in eachline(f)
        push!(groups, [parse(Int, i) for i in split(chomp(ll))])
    end
    groups
end

"""write groups to file"""
function writegrp(filename, groups::Vector{Vector{Int}})
    f = open(filename, "w")
    for i=1:length(groups)
        for j=1:length(groups[i])-1
            print(f, groups[i][j],' ')
        end
        print(f, groups[i][end],'\n')
    end
    close(f)
end

"""read membership from file"""
function readmsp(filename)
    membership = Dict{Int, Vector{Int}}()
    f = open(filename, "r")
    for ll in eachline(f)
        entries = [parse(Int, i) for i in split(chomp(ll))]
        membership[entries[1]] = entries[2:end]
    end
    membership
end

"""write membership to file"""
function writemsp(filename, membership::Dict{Int, Vector{Int}})
    f = open(filename, "w")
    for k in sort(collect(keys(membership)))
        print(f, k, '\t')
        for j=1:length(membership[k])-1
            print(f, membership[k][j], ' ')
        end
        print(f, membership[k][end], '\n')
    end
    close(f)
end

"""transform membership to groups"""
function msp2grp(membership::Dict{Int, Vector{Int}})
    groups = Dict{Int, Vector{Int}}()
    for (k,v) in membership
        for i in v
            if haskey(groups, i)
                push!(groups[i], k)
            else
                groups[i] = [k]
            end
        end
    end
    collect(values(groups))
end

"""transform groups to membership"""
function grp2msp(groups::Vector{Vector{Int}})
    membership = Dict{Int, Vector{Int}}()
    for i=1:length(groups)
        for j=1:length(groups[i])
            if haskey(membership, groups[i][j])
                push!(membership[groups[i][j]], i)
            else
                membership[groups[i][j]] = [i]
            end
        end
    end
    membership
end


@doc raw"""
    PolynomialKernel([a=1 [,c=1 [,d=3]]])

The polynomial kernel is a Mercer kernel given by:

```
    κ(x,y) = (a⋅xᵀy + c)ᵈ   α > 0, c ≧ 0, d ∈ ℤ⁺
```

# Examples

```jldoctest; setup = :(using MLKernels)
julia> PolynomialKernel(2.0f0)
PolynomialKernel{Float32}(2.0,1.0,3)

julia> PolynomialKernel(2.0f0, 2.0)
PolynomialKernel{Float64}(2.0,2.0,3)

julia> PolynomialKernel(2.0f0, 2.0, 2)
PolynomialKernel{Float64}(2.0,2.0,2)
```
"""
struct PolynomialKernel{T<:AbstractFloat} <: MercerKernel{T}
    a::T
    c::T
    d::T
    function PolynomialKernel{T}(
            a::Real=T(1),
            c::Real=T(1),
            d::Real=T(3)
        ) where {T<:AbstractFloat}
        @check_args(PolynomialKernel, a, a >  zero(a), "a > 0")
        @check_args(PolynomialKernel, c, c >= zero(c), "c ≧ 0")
        @check_args(PolynomialKernel, d, d >= one(d) && d == trunc(d), "d ∈ ℤ₊")
        return new{T}(a, c, d)
    end
end

function PolynomialKernel(
        a::T₁=1.0,
        c::T₂=T₁(1),
        d::T₃=convert(promote_float(T₁,T₂), 3)
    ) where {T₁<:Real,T₂<:Real,T₃<:Real}
    T = promote_float(T₁,T₂,T₃)
    return PolynomialKernel{T}(a, c, d)
end

@inline basefunction(::PolynomialKernel) = ScalarProduct()

@inline function kappa(κ::PolynomialKernel{T}, xᵀy::T) where {T}
    return (κ.a*xᵀy + κ.c)^(κ.d)
end

function convert(::Type{K}, κ::PolynomialKernel) where {K>:PolynomialKernel{T}} where T
    return PolynomialKernel{T}(κ.a, κ.c, κ.d)
end


  # Function to be minimized
  f(x::Vector{Float64}) = x[1]^2 + x[2]^2

  # Minimizer by random search
  function randomsearch(f,ntrial)
    fbest = 1.e30
    x = Vector{Float64}(undef,2)
    xbest = Vector{Float64}(undef,2)
    xbest[1] = -10. + 20. * rand()
    xbest[2] = -10. + 20. * rand()
    for i in 1:ntrial
      x[1] = xbest[1] + 1.e-3*(-1.e0 + 2.e0 * rand())
      x[2] = xbest[2] + 1.e-3*(-1.e0 + 2.e0 * rand())
      fx = f(x)
      if fx < fbest
        fbest = fx
        xbest[1] = x[1]
        xbest[2] = x[2]
        println(i," New best point: ", x," f(x) = ", fx)
      end
    end
    println(" Best point found: ",xbest," f = ", fbest)
  end


# Slope Limiters
# note that r = (u - ul)/(ur - u)
# and we set a = u - ul, b = ur - u to define the limiters

function minmod(a,b,c)
  if (a > 0 && b > 0 && c > 0)
    min(a,b,c)
  elseif (a < 0 && b < 0 && c < 0)
    max(a,b,c)
  else
    zero(a)
  end
end

struct GeneralizedMinmodLimiter <: AbstractSlopeLimiter
    θ::Float64
end

GeneralizedMinmodLimiter(;θ=1.0) = GeneralizedMinmodLimiter(θ)
(limiter::GeneralizedMinmodLimiter)(a,b) =minmod(limiter.θ*a,0.5*(a+b),limiter.θ*b)

function minmod(a,b)
  0.5*(sign(a)+sign(b))*min(abs(a),abs(b))
end

struct MinmodLimiter <: AbstractSlopeLimiter end
(::MinmodLimiter)(a,b) = minmod(a,b)

struct OsherLimiter <: AbstractSlopeLimiter
    β::Float64
end
OsherLimiter(;β=1.0) = OsherLimiter(β)
(limiter::OsherLimiter)(a,b) = max(zero(a),min(a,limiter.β*b))

struct SuperbeeLimiter <: AbstractSlopeLimiter end
(::SuperbeeLimiter)(a,b) = max(zero(a),min(2*a,b),min(a,2*b))

"""
function compute_slopes(u, mesh, θ, M, ::Type{Val{true}})
  Estimate slopes of the discretization of function u,
      using a generalized minmod limiter
  inputs:
  `u` discrete approx of function u
  'nonscalar' bool
  `θ` parameter of generalized minmod limiter
  `mesh` problem mesh
  `Type{Val}` bool to choose threaded version
"""
function compute_slopes(u::AbstractArray, mesh::AbstractFVMesh, slopeLimiter::AbstractSlopeLimiter, nonscalar::Bool, ::Type{Val{true}})
  ∇u = similar(u)
  Threads.@threads for j in cell_indices(mesh)
      inner_slopes_loop!(∇u,j,u,mesh,slopeLimiter,nonscalar)
  end
  ∇u
end

function compute_slopes(u::AbstractArray, mesh::AbstractFVMesh, slopeLimiter::AbstractSlopeLimiter, nonscalar::Bool, ::Type{Val{false}})
  ∇u = similar(u)
  for j in cell_indices(mesh)
      inner_slopes_loop!(∇u,j,u,mesh,slopeLimiter,nonscalar)
  end
  ∇u
end

function inner_slopes_loop!(∇u,j,u,mesh,slopeLimiter::AbstractSlopeLimiter,nonscalar)
  ul = cellval_at_left(j,u,mesh)
  ur = cellval_at_right(j+1,u,mesh)
  if nonscalar
    @inbounds for i = 1:size(u,1)
      ∇u[i,j] = slopeLimiter(u[i,j]-ul[i], ur[i]-u[i,j])
    end
  else
    ∇u[j] = slopeLimiter(u[j]-ul, ur-u[j])
  end
end


#=

Filename: asset_pricing.jl

@authors: Spencer Lyon, Tom Sargent, John Stachurski

Computes asset prices with a Lucas style discount factor when the endowment
obeys geometric growth driven by a finite state Markov chain.  That is,

.. math::
    d_{t+1} = g(X_{t+1}) d_t

where

    * :math:`\{X_t\}` is a finite Markov chain with transition matrix P.

    * :math:`g` is a given positive-valued function

References
----------

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

=#

using QuantEcon

# A default Markov chain for the state process
rho = 0.9
sigma = 0.02
n = 25
default_mc = tauchen(n, rho, sigma)

type AssetPriceModel
    beta :: Float64    # Discount factor
    gamma :: Float64   # Coefficient of risk aversion
    mc :: MarkovChain  # State process
    n :: Int           # Number of states
    g :: Function      # Function mapping states into growth rates
end

function AssetPriceModel(;beta=0.96, gamma=2.0, mc=default_mc, g=exp)
    n = size(mc.p)[1]
    return AssetPriceModel(beta, gamma, mc, n, g)
end

"""
Stability test for a given matrix Q.
"""
function test_stability(ap::AssetPriceModel, Q::Matrix)
    sr = maximum(abs(eigvals(Q)))
    if sr >= 1 / ap.beta
        msg = "Spectral radius condition failed with radius = $sr"
        throw(ArgumentError(msg))
    end
end

"""
Computes the price-dividend ratio of the Lucas tree.

"""
function tree_price(ap::AssetPriceModel)
    # == Simplify names, set up matrices  == #
    beta, gamma, P, y = ap.beta, ap.gamma, ap.mc.p, ap.mc.state_values
    y = reshape(y, 1, ap.n)
    J = P .* ap.g(y).^(1 - gamma)

    # == Make sure that a unique solution exists == #
    test_stability(ap, J)

    # == Compute v == #
    I = eye(ap.n)
    Ones = ones(ap.n)
    v = (I - beta * J) \ (beta * J * Ones)

    return v
end

"""
Computes price of a consol bond with payoff zeta

"""
function consol_price(ap::AssetPriceModel, zeta::Float64)
    # == Simplify names, set up matrices  == #
    beta, gamma, P, y = ap.beta, ap.gamma, ap.mc.p, ap.mc.state_values
    y = reshape(y, 1, ap.n)
    M = P .* ap.g(y).^(- gamma)

    # == Make sure that a unique solution exists == #
    test_stability(ap, M)

    # == Compute price == #
    I = eye(ap.n)
    Ones = ones(ap.n)
    p = (I - beta * M) \ ( beta * zeta * M * Ones)

    return p
end

"""
Computes price of a perpetual call option on a consol bond.

"""
function call_option(ap::AssetPriceModel, zeta::Float64, p_s::Float64, epsilon=1e-7)

    # == Simplify names, set up matrices  == #
    beta, gamma, P, y = ap.beta, ap.gamma, ap.mc.p, ap.mc.state_values
    y = reshape(y, 1, ap.n)
    M = P .* ap.g(y).^(- gamma)

    # == Make sure that a unique console price exists == #
    test_stability(ap, M)

    # == Compute option price == #
    p = consol_price(ap, zeta)
    w = zeros(ap.n, 1)
    error = epsilon + 1
    while (error > epsilon)
        # == Maximize across columns == #
        w_new = max(beta * M * w, p - p_s)
        # == Find maximal difference of each component and update == #
        error = maximum(abs(w-w_new))
        w = w_new
    end

    return w
end