# Computes graph Stein discrepancy based on the Riemannian-Langevin operator.
# Our parametrization is of the form
#
# dXt = b(Xt) dt + sigma(Xt) dBt
#
# where Xt is the diffusion process and Bt is d-dimensional Brownian Motion.
# Since this is a Riemannian-Langevin diffusion, b and sigma are linked via
# the relationship
#
# a(Xt) = (1/2) sigma(Xt) sigma(Xt)^T,
# grad a(Xt) = <grad, a^T> (e.g. the grad operator applied to each column of M^T),
# b(Xt) = a(Xt) grad log p(Xt) + grad a(Xt).
#
# This ensures that the diffusion has p as its invariant distribution.
#
# In this case, the infinitesmal operator has the form
#
# T g = 2 <g, b> + 2 <a, grad g>.
#
# Args:
#  points - n x d array of sample points
#  weights - n x 1 array of real-valued weights associated with sample points
#    (default: equal weights)
#  gradlogdensity - the gradlogdensity of the target distribution
#  volatility_covariance - the matrix-valued function a = (1/2) sigma sigma^T
#  grad_volatility_covariance - the vector-valued function grad a = <grad, a^t>
#  solver - optimization program solver supported by JuMP
function riemannian_langevin_graph_discrepancy(; points=[],
                                               weights=fill(1/size(points,1), size(points,1)),
                                               gradlogdensity=nothing,
                                               volatility_covariance=nothing,
                                               grad_volatility_covariance=nothing,
                                               solver=nothing,
                                               kwargs...)
    # make sure solver is defined
    isa(solver, AbstractMathProgSolver) ||
        error("Must specify solver of type AbstractMathProgSolver")
    ## Extract inputs
    sample = SteinDiscrete(points, weights)
    points = sample.support
    weights = sample.weights
    n = length(weights)
    d = size(points,2)
    # make sure the volatility matrices are non-nothing
    isa(volatility_covariance, Function) ||
        error("For riemannian langevin methods, you must supply the volatility_covariance")

    isa(grad_volatility_covariance, Function) ||
        error("For riemannian langevin methods, you must supply the grad_volatility_covariance")

    # Now prepare the coefficients for g and grad g
    gobjcoefficients = Array{Float64}(undef, n, d)
    gradgobjcoefficients = Array{Float64}(undef, n, d, d)
    for i in 1:n
        xi = points[i,:]
        wi = weights[i]
        gradlogpxi = gradlogdensity(points[i,:])
        a = volatility_covariance(xi)
        grad_a = grad_volatility_covariance(xi)
        # Objective coefficients for each g(x_i)
        gobjcoefficients[i,:] = 2 * wi * (a * gradlogpxi + grad_a)
        # Objective coefficients for each grad g(x_i)
        gradgobjcoefficients[i,:,:] .= 2 * wi * a
    end
    # solve the optimization problem
    result = affine_graph_discrepancy(sample,
                                      gobjcoefficients,
                                      gradgobjcoefficients,
                                      solver,
                                      "riemannian-langevin";
                                      kwargs...)
    result
end


module Projections

import LinearAlgebra, Statistics, DataFrames, ProgressMeter
import JuMP, Ipopt, CPLEX
import Convex, ECOS

export DRO, Simplex, solve, benchmark
export Solver, Our, General, Philpott
export Constraint
export Divergence, KullbackLeibler, Burg, Hellinger, ChiSquare, ModifiedChiSquare
export Norm, Linf, Lone, Ltwo, Philpott

abstract type Model end

abstract type Solver end
struct Our <: Solver end
struct General <: Solver end
struct Philpott <: Solver end

abstract type Constraint end
abstract type Divergence <: Constraint end
abstract type Norm <: Constraint end

include("model.jl")
include("divergences.jl")
include("norms.jl")
include("simplex.jl")
include("utilities.jl")
include("findroot.jl")
include("generalsolvers.jl")
include("solve.jl")
include("benchmarks.jl")

end # module


######################################################################
# smc_multi.jl
#
# Like smc.jl, but for multi-agent trajectories
#
# In theory, this shouldn't be so different from single version
######################################################################

function smc_trajectory(em::ErgodicManagerR2, vtm::VTM; verbose::Bool=true, umax::Float64=1.0)

	num_agents = length(vtm)
	N = vtm[1].N
	dt = vtm[1].h
	#ud = VVF(tm.N)
	#xd = VVF(tm.N+1)
	xds = VVVF(num_agents)
	uds = VVVF(num_agents)
	for j = 1:num_agents
		xds[j] = VVF(N+1)
		uds[j] = VVF(N)
		xds[j][1] = deepcopy(vtm[j].x0)
	end

	ck = zeros(em.K+1, em.K+1)
	for n = 1:N

		# update ck
		update_ck(em, xds, n, ck)

		# compute B
		for j = 1:num_agents
			Bx, By = compute_Bj(em, xds[j], n, dt, ck, num_agents)

			# normalize
			den = sqrt(Bx*Bx + By*By)
			ux = -umax * Bx / den
			uy = -umax * By / den

			uds[j][n] = [ux, uy]
			xds[j][n+1] = integrate(vtm[j], xds[j][n], uds[j][n])
		end
	end

	return vvvf2vvf(xds, uds)
end

function update_ck(em::ErgodicManagerR2, xds::VVVF, n::Int, ck::MF)
	num_agents = length(xds)
	Lx = em.domain.lengths[1]
	Ly = em.domain.lengths[2]
	for k1 = 0:em.K
		for k2 = 0:em.K
			hk = em.hk[k1+1, k2+1]
			ck[k1+1, k2+1] *= (n-1)
			for j = 1:num_agents
				dxn = xds[j][n][1] - x_min(em)
				dyn = xds[j][n][2] - y_min(em)

				cx = cos(k1*pi*dxn/Lx)
				cy = cos(k2*pi*dyn/Ly)

				# compute ck
				ck[k1+1, k2+1] += cx*cy/(hk*num_agents)
			end
			ck[k1+1, k2+1] /= n
		end
	end
end

# TODO: this can be done a lot quicker
function compute_Bj(em::ErgodicManagerR2, xd::VVF,n::Int,h::Float64,ck::MF, num_agents::Int)
	Lx = em.domain.lengths[1]
	Ly = em.domain.lengths[2]
	dxn = xd[n][1] - x_min(em)
	dyn = xd[n][2] - y_min(em)
	Bx = By = 0.0
	for k1 = 0:em.K
		for k2 = 0:em.K
			hk = em.hk[k1+1, k2+1]
			cx = cos(k1*pi*dxn/Lx)
			cy = cos(k2*pi*dyn/Ly)

			# compute ck
			#fk = ( (n-1)*ck[k1+1,k2+1] + cx*cy/hk ) / n
			#ck[k1+1, k2+1] = fk

			# t * Lambda_k * (c_k - phi_k)
			de = ck[k1+1, k2+1] - em.phik[k1+1,k2+1]
			LS = em.Lambda[k1+1,k2+1] * h*n * num_agents * de

			# multiplying by gradient
			Bx += -LS * k1*pi * sin(k1*pi*dxn/Lx) * cy / (hk*Lx)
			By += -LS * k2*pi * cx * sin(k2*pi*dyn/Ly) / (hk*Ly)
		end
	end
	return Bx, By
end


using GuessworkQuantumSideInfo
using SDPAFamily
using Test
using LinearAlgebra, GenericLinearAlgebra

# Fix a compatibility problem
LinearAlgebra.eigmin(A::Hermitian{Complex{BigFloat},Array{Complex{BigFloat},2}}) =
    minimum(real.(eigvals(A)))

function testPOVM(Es; tol = 1e-25)
    dB = size(first(Es), 1)
    @test sum(Es) ≈ complex(1.0) * I(dB) atol = tol

    for E in Es
        @test E ≈ E' atol = tol
    end
    @test all(isposdef, Hermitian(E) + tol * I(dB) for E in Es)
end

T = BigFloat
default_sdp_solver(T) = SDPAFamily.Optimizer{T}(presolve = true)

@testset "BB84" begin
    ρBs = GuessworkQuantumSideInfo.BB84_states(T)

    p = ones(T, 4) / 4
    output = guesswork(p, ρBs; solver = default_sdp_solver(T))
    testPOVM(output.Es)
    relaxed_output =
        guesswork_upper_bound(p, ρBs; num_constraints = 4, make_solver = () -> default_sdp_solver(T))

    true_val = (big(1) / big(4)) * (10 - sqrt(big(10)))
    @test output.optval ≈ true_val rtol = 1e-25

    @test output.optval ≈ relaxed_output.optval rtol = 1e-4

    lb = guesswork_lower_bound(p, ρBs; solver = default_sdp_solver(T)).optval
    @test lb <= output.optval + 1e-4
end


# ---
# title: 526. Beautiful Arrangement
# id: problem526
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Backtracking
# link: <https://leetcode.com/problems/beautiful-arrangement/description/>
# hidden: true
# ---
#
# Suppose you have **N** integers from 1 to N. We define a beautiful arrangement
# as an array that is constructed by these **N** numbers successfully if one of
# the following is true for the ith position (1 <= i <= N) in this array:
#
#   1. The number at the ith position is divisible by **i**.
#   2. **i** is divisible by the number at the ith position.
#
#
#
# Now given N, how many beautiful arrangements can you construct?
#
# **Example 1:**
#
#
#
#     Input: 2
#     Output: 2
#     Explanation:
#
#     The first beautiful arrangement is [1, 2]:
#
#     Number at the 1st position (i=1) is 1, and 1 is divisible by i (i=1).
#
#     Number at the 2nd position (i=2) is 2, and 2 is divisible by i (i=2).
#
#     The second beautiful arrangement is [2, 1]:
#
#     Number at the 1st position (i=1) is 2, and 2 is divisible by i (i=1).
#
#     Number at the 2nd position (i=2) is 1, and i (i=2) is divisible by 1.
#
#
#
#
# **Note:**
#
#   1. **N** is a positive integer and will not exceed 15.
#
#
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


### A Pluto.jl notebook ###
# v0.11.14

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 46f5b944-052e-11eb-0b37-d5300eed0dcf
begin
	using Pkg
	Pkg.add("PlutoUI")
	Pkg.add("Formatting")
	using PlutoUI
	using CSV
	using DataFrames
	using Formatting
end

# ╔═╡ ec894576-02f2-11eb-35a1-b54a763d5020
md"# 🧬 Length of the Unrolled Human DNA

There are approx. 30 trillion cells in the human body. Every cell contains the DNA in its nucleus ([except some](https://wtamu.edu/~cbaird/sq/2013/08/22/why-does-every-cell-in-our-body-contain-dna/)). The unrolled DNA is around 2 meters long 🤯. Now, that is one hell of compaction.

Nature is the ultimate computer scientist.

"

# ╔═╡ ea2d2a0e-02f2-11eb-3db2-53747e1c5fef
# We setup some data in this block
begin
	num_cells = 70e12
	dna_length_metres = 2
	dna_length_km = 2/1000
	human_total_dna_km = dna_length_km * num_cells
	world_population = 7.8e12
end

# ╔═╡ d9c30548-02f3-11eb-0a52-47a9743386fb
md"The average number of cells in the human body is $(format(num_cells, commas=true))"

# ╔═╡ f6f903ec-02f3-11eb-1369-adddbbc08448
md"Total Human DNA ~ DNA Length x Cells in human body:  $(format(human_total_dna_km, commas=true)) KM"

# ╔═╡ a794ceae-0563-11eb-216a-53c433d9efe5
md"\"Amazing isn't it\", I said and expected it to end.

But, share this fact with the kids and see if it ends there.

Almost immediately, the questions will start to roll in....
"

# ╔═╡ 22321d50-02f4-11eb-37e4-9919d5e2d8f2
md"#### Q: If we unrolled the DNA from every cell of the living human and joined together, would it reach the Andromeda galaxy?
"

# ╔═╡ 06b7da50-0552-11eb-174e-2d8e1bd5fe8e
md"[Andromeda](https://en.wikipedia.org/wiki/Andromeda_Galaxy) is about 2.537 million light years from our home planet (and is on the [collision path](https://en.wikipedia.org/wiki/Andromeda%E2%80%93Milky_Way_collision) with our galaxy, in some 4.5 billion years)."

# ╔═╡ 3aeb5032-053f-11eb-2946-a7b9007195d4
md"
![](https://apod.nasa.gov/apod/image/1407/m31_bers_1824.jpg)
Image: NASA
"

# ╔═╡ e142105e-0300-11eb-347a-3b7b00da92fe
md"Let's find out."

# ╔═╡ bb038ac8-02fe-11eb-26a4-2d9291eed60f
begin
	light_year_km=9.461e12
	andromeda_distance=2.537e6
	andromeda_distance_km = light_year_km * andromeda_distance
end	;

# ╔═╡ a2cedd56-054f-11eb-0fa9-ed4a9cd82a94
md"Is the distance between Earth and Andromeda greater than the length of DNA from a single human?"

# ╔═╡ 2b3b24e0-02ff-11eb-2acb-af4c5180971f
andromeda_distance_km > human_total_dna_km

# ╔═╡ a87b8686-02f3-11eb-2a4b-156f41effb1d
mankind_dna_km = world_population * human_total_dna_km;

# ╔═╡ 6a8b0d82-02f4-11eb-37ad-9d1034b9b61f
md"Is the total Human DNA from all living Humans long enough to reach the Andromeda galaxy ?

Answer: $(mankind_dna_km >= andromeda_distance_km)"

# ╔═╡ ea38596e-02f8-11eb-0be4-836ed35af793
md"In fact, it is **$(format(round(mankind_dna_km/andromeda_distance_km), commas=true))** times the distance of Andromeda."

# ╔═╡ 7218783a-0538-11eb-2450-d7a9cfd7d91e
md"---"

# ╔═╡ db135b2a-054e-11eb-34c5-9bdd1526d5f4
md"Alright, so we know that the sum total length of every living human DNA is quite large. Some might say its Bigly."

# ╔═╡ 85e33882-053f-11eb-3893-85e54bfb6008
md"## Milky Way

#### Q: Is it longer than the length of our Galaxy?
"

# ╔═╡ 288b1584-053e-11eb-3bb4-898304daa9e2
md"
![](https://asd.gsfc.nasa.gov/blueshift/wp-content/uploads/2015/07/eso0932a.jpg)
Image: NASA
"

# ╔═╡ 77f90bde-053f-11eb-33aa-1d0485c98c9e
md"## Observable Universe

#### Q: It surely is going to be a lot less than the Universe?
"

# ╔═╡ 642f67b0-053f-11eb-1c04-41eab0f26fff
md"
![](https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Observable_Universe_Logarithmic_Map_%28horizontal_layout_english_annotations%29.png/2250px-Observable_Universe_Logarithmic_Map_%28horizontal_layout_english_annotations%29.png)
Image: Wikipedia
"

# ╔═╡ 9025091a-053f-11eb-38c0-a1754bcc23bf
md"## Extras

At this point, the tiny inbestigators started asking all sorts of question such as what if you only take the population of Maldives or India. How far will it take us?
"

# ╔═╡ a5aefac2-0565-11eb-29a5-45c409441b18
md"So, I gave them this. Go Play."

# ╔═╡ c3d2c5fa-0534-11eb-1ce2-f11fc7384a7c
md"Total length of DNA from the population of"

# ╔═╡ 6ab56e86-05ee-11eb-3a30-772a9e9a28ae
md"*Note: The interactive version will only work in the Pluto notebook*"

# ╔═╡ eed77dfa-053d-11eb-3ee0-6dc3cb973fbe
md"---"

# ╔═╡ dd33494e-053d-11eb-0c78-9f9ce7c643fe
md"**Setup Section**"

# ╔═╡ 50a55d3c-052e-11eb-0e94-f9562da4db97
# Datasource: https://data.worldbank.org/indicator/SP.POP.TOTL
world_pop = DataFrame(CSV.File("world_population_2019.csv"));

# ╔═╡ 888e99a4-0531-11eb-3bc3-8d847053be75
@bind country Select(vcat(world_pop[:,"Country Name"], "1 Human"))

# ╔═╡ 20df1f62-0534-11eb-275b-4b15029abe44
function pop_to_dna(population)
	 return population * human_total_dna_km
end

# ╔═╡ 7a16cfac-0533-11eb-3868-c36178506bc3
planet_distances_km = DataFrame(CSV.File("earth_distance.csv"));

# ╔═╡ b0a7118c-0534-11eb-04cb-3f273d884de3
@bind planet Select(planet_distances_km[:, "Name"])

# ╔═╡ 1fcf05c4-0535-11eb-2a29-fdd822b3f685
begin
	if country == "1 Human"
		current_pop = 1
	else
		idx = findfirst(x -> x == country, world_pop[:, "Country Name"])
		current_pop = world_pop[idx,"2019"]
	end
	planet_idx = findfirst(x -> x == planet, planet_distances_km[:, "Name"])
	planet_distance = planet_distances_km[planet_idx, "Distance_KM"]
	distance_times = pop_to_dna(current_pop)/planet_distance
end;

# ╔═╡ d0c06a54-0534-11eb-2f55-134fe93a4e20
md" is $(format(distance_times, commas=true)) times the distance from Earth to "

# ╔═╡ a2caf1bc-0531-11eb-28ac-41ab5b064e33
md"Population of $(uppercase(country)) is $(current_pop)"

# ╔═╡ f90bb6ce-0533-11eb-02b5-95ddddb853d6
# Width of Milky Way
milkyway_width_km = 105700 * light_year_km

# ╔═╡ 042f4fc0-02f5-11eb-05b5-b5452bc3d81c
md"

The width of Milky Way is **$(format(milkyway_width_km, commas=true))** KM.

We could go from one end of the Milky way to another $(format(mankind_dna_km/milkyway_width_km, commas=true)) times, for that amount of DNA.
"

# ╔═╡ 49e46e4a-0536-11eb-1d2a-cfcb1dbbf9fc
observable_universe_km = 93e9 * light_year_km

# ╔═╡ 0744796a-0539-11eb-0f1c-6bbcd56c3e02
md"The observable Universe is $(observable_universe_km) KM. And it is approximately the same length as the total length of entire world population DNA."

# ╔═╡ e6d30c24-053c-11eb-0226-b94f320cdeb1
md"Approximately ~ $(format( mankind_dna_km/observable_universe_km, commas=true )) times"

# ╔═╡ Cell order:
# ╟─ec894576-02f2-11eb-35a1-b54a763d5020
# ╠═ea2d2a0e-02f2-11eb-3db2-53747e1c5fef
# ╟─d9c30548-02f3-11eb-0a52-47a9743386fb
# ╟─f6f903ec-02f3-11eb-1369-adddbbc08448
# ╟─a794ceae-0563-11eb-216a-53c433d9efe5
# ╟─22321d50-02f4-11eb-37e4-9919d5e2d8f2
# ╟─06b7da50-0552-11eb-174e-2d8e1bd5fe8e
# ╟─3aeb5032-053f-11eb-2946-a7b9007195d4
# ╟─e142105e-0300-11eb-347a-3b7b00da92fe
# ╟─bb038ac8-02fe-11eb-26a4-2d9291eed60f
# ╟─a2cedd56-054f-11eb-0fa9-ed4a9cd82a94
# ╠═2b3b24e0-02ff-11eb-2acb-af4c5180971f
# ╟─a87b8686-02f3-11eb-2a4b-156f41effb1d
# ╟─6a8b0d82-02f4-11eb-37ad-9d1034b9b61f
# ╟─ea38596e-02f8-11eb-0be4-836ed35af793
# ╟─7218783a-0538-11eb-2450-d7a9cfd7d91e
# ╟─db135b2a-054e-11eb-34c5-9bdd1526d5f4
# ╟─85e33882-053f-11eb-3893-85e54bfb6008
# ╟─288b1584-053e-11eb-3bb4-898304daa9e2
# ╟─042f4fc0-02f5-11eb-05b5-b5452bc3d81c
# ╟─77f90bde-053f-11eb-33aa-1d0485c98c9e
# ╟─642f67b0-053f-11eb-1c04-41eab0f26fff
# ╟─0744796a-0539-11eb-0f1c-6bbcd56c3e02
# ╟─e6d30c24-053c-11eb-0226-b94f320cdeb1
# ╟─9025091a-053f-11eb-38c0-a1754bcc23bf
# ╟─a5aefac2-0565-11eb-29a5-45c409441b18
# ╟─c3d2c5fa-0534-11eb-1ce2-f11fc7384a7c
# ╟─888e99a4-0531-11eb-3bc3-8d847053be75
# ╟─d0c06a54-0534-11eb-2f55-134fe93a4e20
# ╟─b0a7118c-0534-11eb-04cb-3f273d884de3
# ╟─a2caf1bc-0531-11eb-28ac-41ab5b064e33
# ╟─6ab56e86-05ee-11eb-3a30-772a9e9a28ae
# ╟─eed77dfa-053d-11eb-3ee0-6dc3cb973fbe
# ╟─dd33494e-053d-11eb-0c78-9f9ce7c643fe
# ╠═46f5b944-052e-11eb-0b37-d5300eed0dcf
# ╠═50a55d3c-052e-11eb-0e94-f9562da4db97
# ╠═1fcf05c4-0535-11eb-2a29-fdd822b3f685
# ╠═20df1f62-0534-11eb-275b-4b15029abe44
# ╠═7a16cfac-0533-11eb-3868-c36178506bc3
# ╠═f90bb6ce-0533-11eb-02b5-95ddddb853d6
# ╠═49e46e4a-0536-11eb-1d2a-cfcb1dbbf9fc


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

using Markdown
using InteractiveUtils

# ╔═╡ 7608d6b8-0dee-4890-9d82-82ceb43e2399
using LinearAlgebra

# ╔═╡ f647cff6-88e1-11eb-3085-d31e4f22c78c
using Yao

# ╔═╡ 45d06384-88d7-11eb-1c35-03e184c98c8b
include("./essentials.jl")

# ╔═╡ 33ab855e-0e9d-49ba-bc05-cf758fbf8674
md"""
A measurement is a central concept in quantum mechanics. An easy way to think about it as a sample from a probability distribution: it is a random variable with a number of outcomes, each outcome is produced with a certain probability.

Measurement connect the quantum world to our classical one: we cannot directly observe the quantum state in nature, we can only gather statistics about it with measurements. It sounds like a harsh boundary between a quantum and a classical system that can only be bridged by measurement. The reality is more subtle: unless a quantum system is perfectly isolated, it interacts with its surrounding environment. This leads to introduction of mixed states, which in one limit recover classical probabilities.

# More on the bra-ket notation

Before we take a deep dive into what measurements are, we need to introduce one more notation to complement the ket: it called a bra and it is denoted by $\langle\psi|$ for some quantum state $|\psi\rangle$. Together they form the bra-ket or Dirac notation. A bra is the conjugate transpose of a ket, and the other way around. This also means that a bra is a row vector. For instance, this is the bra for $|0\rangle$
"""

# ╔═╡ 59f6626a-88d3-11eb-2a06-2982fa6594fc
begin
	zero_ket = hcat([1, 0])
	Text("|0〉 ket: "), zero_ket
end

# ╔═╡ 57c49654-88d0-11eb-3856-1d2468c2a406
Text("〈0| bra: "), (conj(zero_ket'ᵀ))

# ╔═╡ ab6ec464-989d-45b0-b70e-a55e89a705ae
md"""
This makes it very easy to write dot products: if we write a bra followed by a ket, that is exactly what the dot product is. This is so common that we often drop one of the vertical bars, and just write $\langle 0|0\rangle$, for instance. Since quantum states are normalized, the inner product of any quantum state with itself is always one:
"""

# ╔═╡ 695cbee6-6980-462c-a537-9ee803109066
conj(zero_ket'ᵀ) * zero_ket

# ╔═╡ abe6fb03-d5fd-41a3-9349-8383b4f4cdd3
md"""
Similarly, orthogonal vectors always give 0. E.g. $\langle 0|1\rangle$:
"""

# ╔═╡ 33404c49-bdba-4fb7-a4ec-1109d139c656
begin
	one_ket = hcat([0, 1])
	conj(zero_ket'ᵀ) * one_ket
end

# ╔═╡ ff813c16-f62f-4083-849f-033b8b20baf9
md"""
What about a ket and a bra? That is going to be a matrix: essentially the outer product of the two vectors. Here's $|0\rangle\langle 0|$:
"""

# ╔═╡ 6f8bdaed-0dc3-4c50-ac9e-a357e5633350
zero_ket * conj(zero_ket'ᵀ)

# ╔═╡ b916a917-a1ed-472a-b8de-15df3e73a964
md"""
This should look familiar: it is a projection to the first element of the canonical basis. It is true in general that $|\psi\rangle\langle\psi|$ is going to be a projector to $|\psi\rangle$. It is very intuitive: take some other quantum state $|\phi\rangle$ and apply the matrix $|\psi\rangle\langle\psi|$ on it: $|\psi\rangle\langle\psi|\phi\rangle$. Now the right-most two terms are a bra and a ket, so it is a dot product: the overlap between $|\phi\rangle$ and $|\psi\rangle$. Since this is a scalar, it just scales the left-most term, which is the ket $|\psi\rangle$, so in effect, we projected $|\phi \rangle$ on this vector.
"""

# ╔═╡ e7916dcd-991c-4b6f-8f3c-43f52bf51ad5
md"""
# Measurements

A measurement in quantum mechanics is an operator-valued random variable. The theory of measurements is rich and countless questions about them are still waiting to be answered. Most quantum computers that we have today, however, only implement one very specific measurement, which makes our discussion a lot simpler. This measurement is in the canonical basis. In other words, the measurement contains two projections, $|0\rangle\langle 0|$ and $|1\rangle\langle 1|$, and this measurement can be applied to any of the qubits of the quantum computer.

We already saw how applying a projection on a vector works. If we want to make a scalar value of that, we need to add a bra to the left. For instance, for some state $|\psi\rangle$, we get a scalar for $\langle\psi|0\rangle\langle 0|\psi\rangle$. This is called the expectation value of the operator $|0\rangle\langle 0|$. To put this in context, let us apply the projection $|0\rangle\langle 0|$ on the superposition $\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle)$, which is the column vector $\frac{1}{\sqrt{2}}\begin{bmatrix} 1\\ 0\end{bmatrix}$.
"""

# ╔═╡ 4ff913cd-0717-40fd-a1f4-d099678ff933
let
	ψ = hcat([1,0])/√2
	Π_0 = zero_ket * conj(zero_ket'ᵀ)
	conj(ψ'ᵀ) * Π_0 * ψ
end

# ╔═╡ f8bb6198-0c97-4b62-aa77-af71dcfbe3e6
md"""
That is exactly one half, the square of the absolute value of the probability amplitude corresponding to $|0\rangle$ in the superposition! This is the mathematical formalism of what we had said earlier: given a state $|\psi\rangle = a_0|0\rangle + a_1|1\rangle$, we get an output $i$ with probability $|a_i|^2$. This is known as the *Born rule*. Now we have a recipe to extract probabilities with projections. This is exactly what is implemented in the quantum simulator. The measurement in the simulator is what we described here. Let's create an equal superposition with the Hadamard gate (see a later notebook for quantum circuits), apply the measurement, and observe the statistics:
"""

# ╔═╡ c5bbbc1d-f298-4f68-82d4-c8ddec2c9b7b
let
	q = zero_state(1)
	essentials.plot_hist(measure(q |> put(1,1=>H), nshots=100))
end

# ╔═╡ f67899cf-3bf5-4b9b-bdc0-e43576eb28bc
md"""
You see that the outcome is random, with roughly half of the outcomes being 0.

There is something additional happening. The measurement has a random outcome, but once it is performed, the quantum state is in the corresponding basis vector. That is, the superposition is destroyed. This is referred to as the collapse of the wavefunction. It is the subject of many ongoing debates and research results how and why it happens, but what matters to us is that we can easily calculate the quantum state after the measurement. Just projecting it to the basis vector is insufficient, since that would not be normalized, so we have to renormalize it. Mathematically it is expressed by the somewhat convoluted expression $\frac{|i\rangle\langle i|\psi\rangle}{\sqrt{\langle\psi|i\rangle\langle i|\psi\rangle}}$ if we observe the output $i$. For instance, if we observe zero after measuring the superposition $\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle)$, the state after the measurement will be
"""

# ╔═╡ 5e599569-48e9-477d-9020-ba546ae158ce
let
	ψ = hcat([√2/2, √2/2])
	Π_0 = zero_ket * conj(zero_ket'ᵀ)
	probability_0 = conj(ψ'ᵀ) * Π_0 * ψ
	Π_0 * ψ/√probability_0
end

# ╔═╡ 9e433703-d4b9-439d-af4c-50f06fa4e241
md"""
which is just a very long way of saying we get $|0\rangle$.

You can easily see this by putting two measurements in a sequence on the same qubit. The second one will always give the same outcome as the first. The first one is random, but the second one will be determined, since there will be no superposition in the computational basis after the first measurement. Let's simulate this by writing out the results of the two measurements into two different classical registers:
"""

# ╔═╡ b8480514-54a0-44e1-906f-ec891395dc36
let
	q = zero_state(1)
	q = q |> put(1, 1=>H)
	a = measure!(q)
	b = measure(q, nshots=100)
	a, sum(b .== a)
end

# ╔═╡ e63a1c5f-e06b-4880-a735-a09b741c5688
md"""
There is no output like 01 or 10.
"""

# ╔═╡ 31832051-7c32-4f49-8751-2d79fc99076a
md"""
# Measuring multiqubit systems

Most quantum computers implement local measurements, which means that each qubit is measured separately. So if we have a two qubit system where the first qubit is in the equal superposition and the second one is in $|0\rangle$, that is, we have the state $\frac{1}{\sqrt{2}}(|00\rangle + |01\rangle)$, we will observe 0 and 0 as outcomes of the measurements on the two qubits, or 0 and 1.
"""

# ╔═╡ 917037ea-4132-4816-8d0c-0004781d3777
let
	q = zero_state(2)
	circuit = put(2, 1=>H)
	essentials.plot_hist(measure(q |> circuit, nshots=1024))
end

# ╔═╡ daf68859-d5fc-4df0-9587-950b4c543d54
md"""
What happens if we make measurements on an entangled state? Let's look at the statistics again on the $|\phi^+\rangle$ state:
"""

# ╔═╡ 7129a922-2061-4ecc-a247-3db7903c5f84
let
	q = zero_state(2)
	circuit = chain(2, put(1=>H), control(1, 2=>X))
	essentials.plot_hist(measure(q |> circuit, nshots=1024))
end

# ╔═╡ d7de4321-daee-404b-b408-975928463eea
md"""
We only observe 00 and 11. Since the state is $\frac{1}{\sqrt{2}}(|00\rangle+|11\rangle)$, this should not come as a shock. Yet, there is something remarkable going on here. At the end of the last section, we saw the same statistics, but from measurements on the same qubit. Now we have two, spatially separate qubits exhibiting the same behaviour: this is a very strong form of correlations. This means that if we measure just one qubit, and get, say, 0 as the outcome, we *know* with certainty that if we measured the other qubit, we would also get 0, even though the second measurement is also a random variable.

To appreciate this better, imagine that your are tossing two unbiased coins. If you observe heads on one, there is absolutely nothing that you can say about what the other one might be other than a wild guess that holds with probability 0.5. If you play foul and you biased the coins, you might improve your guessing accuracy. Yet you can never say with certainty what the other coin will be based on the outcome you observed on one coin, except for the trivial case when the other coin deterministically gives the same face always.

Remarkable as it is, there is no activation or instantaneous (faster than the speed of light) signalling happening between the qubits, though. Your measurement was local to the qubit and so is your information. If there is somebody else doing the measurement on the other qubit, you would have to inform the person through classical communication channels that you happen to know what the outcome will be. So while we certainly cannot violate the theory of relativity with entanglement, this strong form of correlation is still central to many quantum algorithms.
"""

# ╔═╡ a9c7d4bc-a74d-4a64-8c75-19f1f6fec9f1
md"""
# Mixed states

If all was clear until now, this is where it gets messy. A ket and a bra is a projection, as we explained above. More than that, it is also a density matrix. A density matrix is another way of writing a quantum state, instead of kets. So, for instance we could write $\rho = |\psi\rangle\langle\psi|$, where $\rho$ is the density matrix for $|\psi\rangle$. The Born rule still applies, but now we have to take the trace of the result: for instance, $\mathrm{Tr}[|0\rangle\langle 0|\rho]$ would be the probability of seeing 0. See it in action:
"""

# ╔═╡ ddfb368c-d940-4146-9020-7e22d9f66ef1
begin
	ψ = hcat([1, 1])/√2
	ρ = ψ * conj(ψ'ᵀ)
	Π_0 = zero_ket * conj(zero_ket'ᵀ)
	tr(Π_0 * ρ)
end

# ╔═╡ c50ecff4-5234-49e7-8fa0-b29049443545
md"""
We get one half again. The renormalization after a measurement happens in a similar way: $\frac{|0\rangle\langle 0|\rho|0\rangle\langle 0|}{\mathrm{Tr}[|0\rangle\langle 0|\rho]}$.
"""

# ╔═╡ 26300e25-f24f-4392-b963-97ee1b04666c
begin
	probability_0 = tr(Π_0 * ρ)
	Π_0 * ρ * Π_0/probability_0
end

# ╔═╡ 2b5d8fe2-9222-4193-98c8-390364473a5a
md"""
So why do we need this at all? Every state we have mentioned so far is called a *pure state*: these are kets or a density matrix created as a ket and a bra. There are other states called *mixed states*: these are *classical* probability distributions over pure states. Formally, a mixed state is written as $\sum_i p_i |\psi_i\rangle\langle\psi_i|$, where $\sum_i p_i=1$, $p_i\geq 0$. This reflects our classical ignorance over the underlying quantum states. Compare the density matrix of the equal superposition $\frac{1}{\sqrt{2}}(|0\rangle+|1\rangle)$ and the mixed state $0.5(|0\rangle\langle 0|+|1\rangle\langle 1|)$:
"""

# ╔═╡ dd88aef3-5954-47e7-8883-7e4c03b9df10
let
	zero_ket = hcat([1, 0])
	one_ket = hcat([0, 1])
	ψ = (zero_ket + one_ket)/√2
	global t2 = "Density matrix of the equally mixed state of |0〉〈0| and |1〉〈1| :", (zero_ket * conj(zero_ket'ᵀ) + one_ket * conj(one_ket'ᵀ))/2
	"Density matrix of the equal superposition: ", (ψ * conj(ψ'ᵀ))
end

# ╔═╡ 68da7bd4-88dd-11eb-0914-4b8279395d1b
t2

# ╔═╡ b365c769-07aa-4df9-b933-3ce55cbb6ab4
md"""
The off-diagonal elements are gone in the second case. The off-diagonal elements are also called coherences: their presence indicates that the state is quantum. The smaller these values are, the closer the quantum state is to a classical probability distribution.

The second density matrix above has only diagonal elements and they are equal: this is the equivalent way of writing a uniform distribution. We know that the uniform distribution has maximum entropy, and for this reason, a density matrix with this structure is called a maximally mixed state. In other words, we are perfectly ignorant of which elements of the canonical basis constitute the state.

We would like a quantum state to be perfectly isolated from the environment, but in reality, the quantum computers we have today and for the next couple of years cannot achieve a high degree of isolation. So coherences are slowly lost to the environment -- this is a process called decoherence. The speed at which this happens determines the length of the quantum algorithms we can run on the quantum computer: if it happens fast, we have time to apply a handful gates or do any other form calculation, and then we quickly have to pull out (measure) the results.
"""

# ╔═╡ Cell order:
# ╟─33ab855e-0e9d-49ba-bc05-cf758fbf8674
# ╠═7608d6b8-0dee-4890-9d82-82ceb43e2399
# ╠═59f6626a-88d3-11eb-2a06-2982fa6594fc
# ╠═57c49654-88d0-11eb-3856-1d2468c2a406
# ╟─ab6ec464-989d-45b0-b70e-a55e89a705ae
# ╠═695cbee6-6980-462c-a537-9ee803109066
# ╟─abe6fb03-d5fd-41a3-9349-8383b4f4cdd3
# ╠═33404c49-bdba-4fb7-a4ec-1109d139c656
# ╟─ff813c16-f62f-4083-849f-033b8b20baf9
# ╠═6f8bdaed-0dc3-4c50-ac9e-a357e5633350
# ╟─b916a917-a1ed-472a-b8de-15df3e73a964
# ╟─e7916dcd-991c-4b6f-8f3c-43f52bf51ad5
# ╠═4ff913cd-0717-40fd-a1f4-d099678ff933
# ╟─f8bb6198-0c97-4b62-aa77-af71dcfbe3e6
# ╠═45d06384-88d7-11eb-1c35-03e184c98c8b
# ╠═f647cff6-88e1-11eb-3085-d31e4f22c78c
# ╠═c5bbbc1d-f298-4f68-82d4-c8ddec2c9b7b
# ╟─f67899cf-3bf5-4b9b-bdc0-e43576eb28bc
# ╠═5e599569-48e9-477d-9020-ba546ae158ce
# ╟─9e433703-d4b9-439d-af4c-50f06fa4e241
# ╠═b8480514-54a0-44e1-906f-ec891395dc36
# ╟─e63a1c5f-e06b-4880-a735-a09b741c5688
# ╟─31832051-7c32-4f49-8751-2d79fc99076a
# ╠═917037ea-4132-4816-8d0c-0004781d3777
# ╟─daf68859-d5fc-4df0-9587-950b4c543d54
# ╠═7129a922-2061-4ecc-a247-3db7903c5f84
# ╟─d7de4321-daee-404b-b408-975928463eea
# ╟─a9c7d4bc-a74d-4a64-8c75-19f1f6fec9f1
# ╠═ddfb368c-d940-4146-9020-7e22d9f66ef1
# ╟─c50ecff4-5234-49e7-8fa0-b29049443545
# ╠═26300e25-f24f-4392-b963-97ee1b04666c
# ╟─2b5d8fe2-9222-4193-98c8-390364473a5a
# ╠═dd88aef3-5954-47e7-8883-7e4c03b9df10
# ╠═68da7bd4-88dd-11eb-0914-4b8279395d1b
# ╟─b365c769-07aa-4df9-b933-3ce55cbb6ab4


;; QuickSort in JL.

(define filter (lambda (f lst)
   (if lst
      (if (f (head lst))
         (cons (head lst) (filter f (rest lst)))
         (filter f (rest lst)))
      nil)))

(define combine (lambda (as bs)
   (if as
      (cons (head as) (combine (rest as) bs))
      bs)))

(define qsort (lambda (lst)
   (if lst
      (begin
         (define pivot (head lst))
         (define less (filter (lambda (a) (< a pivot)) (rest lst)))
         (define greater (filter (lambda (a) (>= a pivot)) (rest lst)))
         (combine (qsort less) (cons pivot (qsort greater))))
      nil)))

(print (qsort (list 7 3 2 6 9 1 8 4 5)) "\n")


# ---
# title: 528. Random Pick with Weight
# id: problem528
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Binary Search, Random
# link: <https://leetcode.com/problems/random-pick-with-weight/description/>
# hidden: true
# ---
#
# You are given an array of positive integers `w` where `w[i]` describes the
# weight of `i``th` index (0-indexed).
#
# We need to call the function `pickIndex()` which **randomly** returns an
# integer in the range `[0, w.length - 1]`. `pickIndex()` should return the
# integer proportional to its weight in the `w` array. For example, for `w = [1,
# 3]`, the probability of picking the index `0` is `1 / (1 + 3) = 0.25` (i.e
# 25%) while the probability of picking the index `1` is `3 / (1 + 3) = 0.75`
# (i.e 75%).
#
# More formally, the probability of picking index `i` is `w[i] / sum(w)`.
#
#
#
# **Example 1:**
#
#
#
#     **Input**
#     ["Solution","pickIndex"]
#     [[[1]],[]]
#     **Output**
#     [null,0]
#
#     **Explanation**
#     Solution solution = new Solution([1]);
#     solution.pickIndex(); // return 0. Since there is only one single element on the array the only option is to return the first element.
#
#
# **Example 2:**
#
#
#
#     **Input**
#     ["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"]
#     [[[1,3]],[],[],[],[],[]]
#     **Output**
#     [null,1,1,1,1,0]
#
#     **Explanation**
#     Solution solution = new Solution([1, 3]);
#     solution.pickIndex(); // return 1. It's returning the second element (index = 1) that has probability of 3/4.
#     solution.pickIndex(); // return 1
#     solution.pickIndex(); // return 1
#     solution.pickIndex(); // return 1
#     solution.pickIndex(); // return 0. It's returning the first element (index = 0) that has probability of 1/4.
#
#     Since this is a randomization problem, multiple answers are allowed so the following outputs can be considered correct :
#     [null,1,1,1,1,0]
#     [null,1,1,1,1,1]
#     [null,1,1,1,0,0]
#     [null,1,1,1,0,1]
#     [null,1,0,1,0,0]
#     ......
#     and so on.
#
#
#
#
# **Constraints:**
#
#   * `1 <= w.length <= 10000`
#   * `1 <= w[i] <= 10^5`
#   * `pickIndex` will be called at most `10000` times.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


# ---
# title: 1049. Last Stone Weight II
# id: problem1049
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/last-stone-weight-ii/description/>
# hidden: true
# ---
#
# We have a collection of rocks, each rock has a positive integer weight.
#
# Each turn, we choose **any two rocks**  and smash them together.  Suppose the
# stones have weights `x` and `y` with `x <= y`.  The result of this smash is:
#
#   * If `x == y`, both stones are totally destroyed;
#   * If `x != y`, the stone of weight `x` is totally destroyed, and the stone of weight `y` has new weight `y-x`.
#
# At the end, there is at most 1 stone left.  Return the **smallest possible**
# weight of this stone (the weight is 0 if there are no stones left.)
#
#
#
# **Example 1:**
#
#
#
#     Input: [2,7,4,1,8,1]
#     Output: 1
#     Explanation:
#     We can combine 2 and 4 to get 2 so the array converts to [2,7,1,8,1] then,
#     we can combine 7 and 8 to get 1 so the array converts to [2,1,1,1] then,
#     we can combine 2 and 1 to get 1 so the array converts to [1,1,1] then,
#     we can combine 1 and 1 to get 0 so the array converts to [1] then that's the optimal value.
#
#
#
#
# **Note:**
#
#   1. `1 <= stones.length <= 30`
#   2. `1 <= stones[i] <= 100`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


#=

  Sicherman Dice in Julia ConstraintSolver.jl

  From http://en.wikipedia.org/wiki/Sicherman_dice
  """
  Sicherman dice are the only pair of 6-sided dice which are not normal dice,
  bear only positive integers, and have the same probability distribution for
  the sum as normal dice.

  The faces on the dice are numbered 1, 2, 2, 3, 3, 4 and 1, 3, 4, 5, 6, 8.
  """

  I read about this problem in a book/column by Martin Gardner long
  time ago, and got inspired to model it now by the WolframBlog post
  "Sicherman Dice": http://blog.wolfram.com/2010/07/13/sicherman-dice/

  This model gets the two different ways, first the standard way and
  then the Sicherman dice:

  x1 = array1d(1..6, [1, 2, 3, 4, 5, 6]);
  x2 = array1d(1..6, [1, 2, 3, 4, 5, 6]);
  ----------
  x1 = array1d(1..6, [1, 2, 2, 3, 3, 4]);
  x2 = array1d(1..6, [1, 3, 4, 5, 6, 8]);

  Extra: If we also allow 0 (zero) as a valid value then the
  following two solutions are also valid:

  x1 = array1d(1..6, [0, 1, 1, 2, 2, 3]);
  x2 = array1d(1..6, [2, 4, 5, 6, 7, 9]);
  ----------
  x1 = array1d(1..6, [0, 1, 2, 3, 4, 5]);
  x2 = array1d(1..6, [2, 3, 4, 5, 6, 7]);

  These two extra cases are mentioned here:
  http://mathworld.wolfram.com/SichermanDice.html

  Note: This model currently only show one solution.

  Model created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#
using ConstraintSolver, JuMP
using Cbc, GLPK, Ipopt
const CS = ConstraintSolver
include("constraints_utils.jl")

function sicherman_dice(n=6,max_val=10,min_val=1,print_solutions=true,all_solutions=true)

    cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,   "all_solutions"=> all_solutions,
                                                            # "all_optimal_solutions"=>all_solutions,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            # "traverse_strategy"=>:DFS,
                                                            # "traverse_strategy"=>:DBFS,

                                                            # "branch_split"=>:Smallest,
                                                            # "branch_split"=>:Biggest,
                                                            "branch_split"=>:InHalf,

                                                            # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
                                                            "branch_strategy" => :IMPS, # default
                                                            # "branch_strategy" => :ABS, # Activity Based Search
                                                            # "activity.decay" => 0.999, # default 0.999
                                                            # "activity.max_probes" => 10, # default, 10
                                                            # "activity.max_confidence_deviation" => 20, # default 20

                                                            # "simplify"=>false, # default true

                                                            "time_limit"=>6,

                                                            # "backtrack" => false, # default true
                                                            # "backtrack_sorting" => false, # default true

                                                            # "lp_optimizer" => cbc_optimizer,
                                                            # "lp_optimizer" => glpk_optimizer,
                                                            "lp_optimizer" => ipopt_optimizer,
                                        ))
    # standard distribution of 2 pair of dice
    standard_dist = [1,2,3,4,5,6,5,4,3,2,1]

    # The two dice
    @variable(model, min_val <= x1[1:n] <= max_val, Int)
    @variable(model, min_val <= x2[1:n] <= max_val, Int)

    # ensure standard distributions of the sums
    for k in 1:11
        # Number of occurrence for k+1 when throwing 2 dice
        #=
        b = @variable(model, [1:n,1:n], Bin)
        for i in 1:n, j in 1:n
            @constraint(model,b[i,j] := {x1[i] + x2[j] == k+1})
        end
        @constraint(model,standard_dist[k] == sum(b))
        =#
        # This works
        count_ctr(model, [x1[i]+x2[j] for i in 1:n, j in 1:n], :(==), k+1, standard_dist[k])

        # The following might work in a future version of ConstraintSolver.jl
        # @constraint(model,standard_dist[k] === sum([(x1[i]+x2[j] == k+1) for i in 1:n, j in 1:n]))

    end

    # Symmetry breaking: x1 is less <= to x2
    for i in 1:n
        @constraint(model,x1[i] <= x2[i])
    end

    # Symmetry breaking
    increasing(model,x1)
    increasing(model,x2)

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    if status == MOI.OPTIMAL
        num_sols = MOI.get(model, MOI.ResultCount())
        println("num_sols:$num_sols\n")
        if print_solutions
            for sol in 1:num_sols
                # println("solution #$sol")
                x1_val = convert.(Integer,JuMP.value.(x1; result=sol))
                x2_val = convert.(Integer,JuMP.value.(x2; result=sol))
                println("x1:$x1_val")
                println("x2:$x2_val")
                println()

            end
        end
    else
        println("status:$status")
    end

    return status
end

n_dice=6
max_val=9
min_val=1
# Currently only one solution
println("\nmin_val: 1")
@time sicherman_dice(n_dice,max_val,min_val,true,false)
println("\nmin_val: 0")
@time sicherman_dice(n_dice,max_val,0,true,false)


# Chapter 6

using BenchmarkTools

# #Array Internals

# ## Column wise storage

function col_iter(x)
    s=zero(eltype(x))
    for i = 1:size(x, 2)
       for j = 1:size(x, 1)
          s = s + x[j, i] ^ 2
          x[j, i] = s
       end
    end
end

function row_iter(x)
   s=zero(eltype(x))
   for i = 1:size(x, 1)
      for j = 1:size(x, 2)
       s = s + x[i, j] ^ 2
         x[i, j] = s
      end
   end
end

a = rand(1000, 1000);

@btime col_iter($a)

@btime row_iter($a)

# ### Adjoints

a = rand(1000, 1000);

b=a'

@btime col_iter($b)

@btime row_iter($b)

# ## Array initialization

a = fill(1, 4, 4)

@btime fill(1, 1000, 1000);

@btime Array{Int64}(undef, 1000, 1000);

a=Array{Int}(undef, 2, 2)

b=Array{String}(undef, 2, 2)

b[1,1]  # Will throw UndefRefError

 ## Bounds Checking

 function prefix_bounds(a, b)
      for i in 2:size(a, 1)
            a[i] = b[i-1] + b[i]
      end
end

function prefix_inbounds(a, b)
    @inbounds for i in 2:size(a, 1)
         a[i] = b[i-1] + b[i]
    end
end

a=zeros(Float64, 1000);

b=rand(1000);

@btime prefix_bounds($a, $b)

@btime prefix_inbounds($a, $b)

# # In place operations

function xpow(x)
   return [x x^2 x^3 x^4]
end

function xpow_loop(n)
    s = 0
    for i = 1:n
      s = s + xpow(i)[2]
    end
   return s
end

@btime xpow_loop($1000000)

# ## Preallocating function output

function xpow!(result::Array{Int, 1}, x)
    @assert length(result) == 4
    result[1] = x
    result[2] = x^2
    result[3] = x^3
    result[4] = x^4
end

function xpow_loop_noalloc(n)
    r = [0, 0, 0, 0]
    s = 0
    for i = 1:n
       xpow!(r, i)
       s = s + r[2]
    end
    s
end
@btime xpow_loop_noalloc($1000000)

@time xpow_loop(1_000_000)

@time xpow_loop_noalloc(1_000_000)

@btime sort(a);
@btime sort!(a);

# ## Mutating FUnctions

 @btime sort(a);

 @btime sort!(a);

# # Broadcasting

a=collect(1:4);

sqrt.(a)

b=reshape( 1:8, 4, 2)

b .+ a

a = collect(1:10);

b = fill(0.0, 10);

b .= cos.(sin.(A))

@time b .= cos.(sin.(a));

@btime c = cos.(sin.(a));

# # Array Views

function sum_vector(x::Array{Float64, 1})
   s = zero(eltype(x))
   for i in 1:length(x)
      s = s + x[i]
   end
   return s
end

function sum_cols_matrix(x::Array{Float64, 2})
   num_cols = size(x, 2)
   s = zeros(num_cols)
   for i = 1:num_cols
     s[i] = sum_vector(x[:, i])
   end
   return s
end

@benchmark sum_cols_matrix($a)

function sum_vector(x::AbstractArray)
   s = zero(eltype(x))
   for i in 1:length(x)
      s = s + x[i]
   end
   return s
end

function sum_cols_matrix_views(x::Array{Float64, 2})
   num_cols = size(x, 2); num_rows = size(x, 1)
   s = zeros(num_cols)
   for i = 1:num_cols
     s[i] = sum_vector(@view(x[:, i]))
   end
   return s
end

@benchmark sum_cols_matrix_views($a)

# # SIMD Parallelization

function sum_vectors!(x, y, z)
    n = length(x)
    for i in 1:n
        x[i] = y[i] + z[i]
    end
end

function sum_vectors_simd!(x, y, z)
    n = length(x)
    @inbounds @simd for i in 1:n
          x[i] = y[i] + z[i]
    end
end

a=zeros(Float32, 1_000_000);
b= rand(Float32, 1_000_000);
c= rand(Float32, 1_000_000);

@btime sum_vectors!($a, $b, $c)

@btime sum_vectors_simd!($a, $b, $c)

@code_llvm sum_vectors_simd!(a, b, c)

@code_native sum_vectors_simd!(a, b, c)

# ## SIMD.jl

using Pkg
Pkg.add("SIMD.jl")

using SIMD

a=Vec{4, Float64}((1.0, 2.0, 3.0, 4.0))

@btime sum($a)

@code_native sum(a)

b=[1.,2.,3.,4.]

function naive_sum(x::Vector{Float64})
  s = 0.0
  for i=1:length(x)
     s=s+x[i]
  end
  return s
end

@btime naive_sum($b)

function vadd!(xs::Vector{T}, ys::Vector{T}, ::Type{Vec{N,T}}) where {N, T}
    @assert length(ys) == length(xs)
    @assert length(xs) % N == 0
    lane = VecRange{N}(0)
    @inbounds for i in 1:N:length(xs)
        xs[lane + i] += ys[lane + i]
    end
end

# # Specialised Array Types

# ## Static Arrays

using Pkg
Pkg.add("StaticArrays")

using StaticArrays
a=SVector(1, 2, 3, 4)
b = @SVector [3, 4, 5, 6

c=[1,2,3,4];

@btime $c*$c'

 @btime $a*$a'

 @btime $(Ref(a))[] * $(Ref(a'))[]

# ## Struct Of Arrays

using Pkg
Pkg.add("StructArrays")

a=Complex.(rand(1000000), rand(1000000))

b = StructArray(a)

c = StructArray(i + 2*i*im for i in 1:10)

d = StructArray{ComplexF64}(undef, 10)

using Random
Random.rand!(d)

d.re

d[5]

typeof(d[5])

function accum(x, z)
   s = zero(eltype(x))
   @simd for i in 1:length(x)
       @inbounds s += x[i] * z
   end
   s
end

@btime accum($a, 1.5 + 2.5im)

@btime accum($b, 1.5 + 2.5im)

# # Yeppp

using Pkg
Pkg.add("Yeppp")

using Yeppp
a=rand(1_000_000);

@btime log.($a);

@btime Yeppp.log($a)

@btime Yeppp.log!($a)

# # Generic array functions

function mysum_linear(a::AbstractArray)
    s=zero(eltype(a))
    for i in 1:length(a)
        s=s + a[i]
    end
    return s
end

mysum_linear(1:1000000)

mysum_linear(reshape(1:1000000, 100, 100, 100))

mysum_linear(reshape(1:1000000, 1000, 1000))

mysum_linear(@view reshape(1:1000000, 1000, 1000)[1:500, 1:500] )

@btime mysum_linear(reshape(1:1000000, 1000, 1000))

@btime mysum_linear(@view reshape(1:1000000, 1000, 1000)[1:500, 1:500] )

function mysum_in(a::AbstractArray)
   s = zero(eltype(a))
   for i in a
      s = s + i
   end
end

@btime mysum_in(@view reshape(1:1000000, 1000, 1000)[1:500, 1:500] )

function mysum_eachindex(a::AbstractArray)
    s = zero(eltype(a))
    for i in eachindex(a)
        s = s + a[i]
    end
end

@btime mysum_eachindex(@view reshape(1:1000000, 1000, 1000)[1:500, 1:500] )


"""
    RemotelySensedReflectance(rirr_in,wvbd_in,wvbd_out)

Compute remotely sensed reflectance at wvbd_out from
irradiance reflectance at wvbd_in.

```
wvbd_out=Float64.([412, 443, 490, 510, 555, 670])
wvbd_in=Float64.([400,425,450,475,500,525,550,575,600,625,650,675,700])
rirr_in=1e-3*[23.7641,26.5037,27.9743,30.4914,28.1356,
    21.9385,18.6545,13.5100,5.6338,3.9272,2.9621,2.1865,1.8015]

rrs_out=RemotelySensedReflectance(rirr_in,wvbd_in,wvbd_out)

using Plots
plot(vec(rrs_out),linewidth=4,lab="recomputed RRS")
rrs_ref=1e-3*[4.4099, 4.8533, 5.1247, 4.5137, 3.0864, 0.4064]
plot!(rrs_ref,linewidth=4,ls=:dash,lab="reference result")
```
"""
function RemotelySensedReflectance(rirr_in,wvbd_in,wvbd_out)
    rrs_out=similar(wvbd_out)

    nin=length(wvbd_in)
    nout=length(wvbd_out)

    jj=Array{Int64,1}(undef,nin)
    ww=Array{Float64,1}(undef,nin)
    for ii=1:6
        tmp=wvbd_out[ii].-wvbd_in
        kk=maximum(findall(tmp.>=0))
        jj[ii]=kk
        ww[ii]=tmp[kk]/(wvbd_in[kk+1]-wvbd_in[kk])
    end

    tmp=rirr_in/3
    rrs_tmp=(0.52*tmp)./(1.0 .-1.7*tmp);

    rrs_out=Array{Float64,1}(undef,nout)
    for vv=1:6
        tmp0=rrs_tmp[jj[vv]]
        tmp1=rrs_tmp[jj[vv]+1]
        rrs_out[vv]=tmp0.*(1-ww[vv])+tmp1.*ww[vv]
    end

    return rrs_out
end

"""
    RrsToChla(Rrs; Eqn="OC4")

Satellite `Chl_a` estimates typicaly derive from remotely sensed reflectances
using the blue/green reflectance ratio method and a polynomial formulation.

```
wvbd_out=Float64.([412, 443, 490, 510, 555, 670])
wvbd_in=Float64.([400,425,450,475,500,525,550,575,600,625,650,675,700])
rirr_in=1e-3*[23.7641,26.5037,27.9743,30.4914,28.1356,
    21.9385,18.6545,13.5100,5.6338,3.9272,2.9621,2.1865,1.8015]

rrs_out=RemotelySensedReflectance(rirr_in,wvbd_in,wvbd_out)
chla_out=RrsToChla(rrs_out)
```
"""
function RrsToChla(Rrs;Eqn="OC4")
    RRSB=max.(Rrs[2],Rrs[3]) #blue
    RRSG=Rrs[5] #green
    X = log10.(RRSB./RRSG) #ratio of blue to green

    if Eqn=="OC4"
        C=[0.3272, -2.9940, +2.7218, -1.2259, -0.5683] #OC4 algorithms (SeaWifs, CCI)
    elseif Eqn=="OC3M-547"
        C=[0.2424, -2.7423, 1.8017, 0.0015, -1.2280] #OC3M-547 algorithm (MODIS)
    else
        error("this case has not been implemented yet...")
    end

    a0=C[1]; a1=C[2]; a2=C[3]; a3=C[4]; a4=C[5];
    chld=exp10.(a0.+a1*X+a2*X.^2+a3*X.^3+a4*X.^4); #apply polynomial recipe
end


using TopOpt, LinearAlgebra, StatsFuns
using TopOptMakie
using VTKDataIO
using PyCall
using VTKDataTypes
using DashVtk
using Dash
using DashHtmlComponents

#dash = pyimport("dash")
#dash_html = pyimport("dash_html_components")
dash_vtk = pyimport("dash_vtk")

E = 1.0 # Young’s modulus
v = 0.3 # Poisson’s ratio
f = 1.0 # downward force
rmin = 4.0 # filter radius
xmin = 0.0001 # minimum density
problem_size = (60, 20)
V = 0.5 # maximum volume fraction
p = 4.0 # penalty
x0 = fill(V, prod(problem_size)) # initial design

problem = HalfMBB(Val{:Linear}, problem_size, (1.0, 1.0), E, v, f)

solver = FEASolver(Displacement, Direct, problem, xmin = xmin)
cheqfilter = DensityFilter(solver, rmin = rmin)
comp = TopOpt.Compliance(problem, solver)

function obj(x)
    # minimize compliance
    return comp(cheqfilter(x))
end
function constr(x)
    # volume fraction constraint
    return sum(cheqfilter(x)) / length(x) - V
end

m = Model(obj)
addvar!(m, zeros(length(x0)), ones(length(x0)))
Nonconvex.add_ineq_constraint!(m, constr)

options = MMAOptions(
    maxiter=1000, tol = Tolerance(kkt = 1e-4, x = 1e-4, f = 1e-4),
)
TopOpt.setpenalty!(solver, p)
# Method of Moving Asymptotes
@time r = Nonconvex.optimize(m, MMA87(), x0, options = options);

@show obj(r.minimizer)
@show constr(r.minimizer)
topology = cheqfilter(r.minimizer)

#function GeometryBasics.Mesh(problem, topology)
#    mesh = VTKUnstructuredData(problem)
#    topology = round.(topology)
#    inds = findall(isequal(0), topology)
#    deleteat!(mesh.cell_connectivity, inds)
#    deleteat!(mesh.cell_types, inds)
#    return GeometryBasics.Mesh(mesh)
#end

mesh = VTKUnstructuredData(problem)
topology = round.(topology)
inds = findall(isequal(0), topology)
deleteat!(mesh.cell_connectivity, inds)
deleteat!(mesh.cell_types, inds)

pymesh = PyVTK(mesh)
dash_vtk_utils = pyimport("dash_vtk.utils")
dash_vtk_utils.to_mesh_state
state = dash_vtk_utils.to_mesh_state(pymesh)

content = vtk_view([
        vtk_geometryrepresentation([
        vtk_mesh(state=state)
    ]),
])

app = dash()

app.layout = html_div() do
    html_div(
    style = Dict(
        "width" => "100%",
        "height" => "400px"
    ),
    children=[content],
    )
end

run_server(app, "0.0.0.0", debug = true)


let
    for (name, prob) in Optim.UnivariateProblems.examples
        results = optimize(prob.f, prob.bounds..., method = GoldenSection())

        @assert Optim.converged(results)
        @assert norm(Optim.minimizer(results) - prob.minimizers) < 1e-7
    end
end


using Optim
using HTTP
using GLM
using LinearAlgebra
using Random
using Statistics
using DataFrames
using CSV
using FreqTables
using CategoricalArrays
using Tables
using StatsModels
cd("C:/Users/jakek/Documents/econ_phd/structural_modeling_course/forked_class/ProblemSets/PS2-optimization-intro")

function fullquestions()
	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	# question 1
	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	f(x) = -x[1]^4-10x[1]^3-2x[1]^2-3x[1]-2
	minusf(x) = x[1]^4+10x[1]^3+2x[1]^2+3x[1]+2
	startval = rand(1)   # random starting value
	result = optimize(minusf, startval, BFGS())

	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	# question 2
	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	url = "https://raw.githubusercontent.com/OU-PhD-Econometrics/fall-2020/master/ProblemSets/PS1-julia-intro/nlsw88.csv"
	df = CSV.read(HTTP.get(url).body)
	X = [ones(size(df,1),1) df.age df.race.==1 df.collgrad.==1]
	y = df.married.==1

	function ols(beta, X, y)
		ssr = (y.-X*beta)'*(y.-X*beta)
		return ssr
	end

	beta_hat_ols = optimize(b -> ols(b, X, y), rand(size(X,2)), LBFGS(), Optim.Options(g_tol=1e-6, iterations=100_000, show_trace=true))
	println(vec(beta_hat_ols.minimizer))

	bols = inv(X'*X)*X'*y
	df.white = df.race.==1
	bols_lm = lm(@formula(married ~ age + white + collgrad), df)

	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	# question 3
	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	function logit(alpha, X, d)
		# your turn
		loglike = sum(X*alpha.*d .- log.(1 .+exp.(X*alpha)))
		return -loglike
	end

	beta_hat_logit = optimize(b -> logit(b, X, y),beta_hat_ols.minimizer , LBFGS(), Optim.Options(g_tol=1e-6, iterations=100_000, show_trace=true))
	println(beta_hat_logit.minimizer)

	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	# question 4
	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	# check it
	df.raceind = df.race .==1
	beta_glm = glm(@formula(married ~ age + raceind+collgrad), df, Binomial(), LogitLink())
	if !all(round.(coef(beta_glm) .- beta_hat_logit.minimizer,digits=5).==0)
		error()
	end

	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	# question 5
	#:::::::::::::::::::::::::::::::::::::::::::::::::::
	freqtable(df, :occupation) # note small number of obs in some occupations
	df = dropmissing(df, :occupation)
	df[df.occupation.==8 ,:occupation] .= 7
	df[df.occupation.==9 ,:occupation] .= 7
	df[df.occupation.==10,:occupation] .= 7
	df[df.occupation.==11,:occupation] .= 7
	df[df.occupation.==12,:occupation] .= 7
	df[df.occupation.==13,:occupation] .= 7
	freqtable(df, :occupation) # problem solved

	X = [ones(size(df,1),1) df.age df.race.==1 df.collgrad.==1]
	y = df.occupation

	# set base occ to be first one.
	function mlogit(alpha, X, d)
		u = reduce(hcat,[X*alpha[(4*i-3):(4*i)] for i in 1:6])
		help = reduce(hcat,[d.==i for i in 2:7])
		loglike = sum(help .*u) - sum(log.(1 .+sum(exp.(u), dims=2)))
		return -loglike
	end

	# starting values - set intercepts to fraction present in data.
	starting = Vector(prop(freqtable(df, :occupation)))
	starting = repeat(starting[Not(1)], inner = 4)
	starting[Not((1:6) .*4 .-3)] .= 0

	beta_hat_mlogit = optimize(b -> mlogit(b, X, y),starting, BFGS(), Optim.Options(g_tol=1e-6, iterations=100_000, show_trace=true))
	println(vec(beta_hat_mlogit.minimizer))
end

fullquestions()


"""
	lsa(dtm::DocumentTermMatrix)
	lsa(crps::Corpus)

Performs Latent Semantic Analysis or LSA on a corpus.

"""
lsa(dtm::DocumentTermMatrix) = svd(Matrix(tf_idf(dtm)))
function lsa(crps::Corpus)
	update_lexicon!(crps)
	svd(Matrix(tf_idf(DocumentTermMatrix(crps))))
end


const ∇ = ContMechTensors.gradient
const Δ = ContMechTensors.hessian

function Ψ(C, μ, Kb)
    detC = det(C)
    J = sqrt(detC)
    Ĉ = detC^(-1/3)*C
    return 1/2*(μ * (trace(Ĉ)- 3) + Kb*(J-1)^2)
end

function S(C, μ, Kb)
    I = one(C)
    J = sqrt(det(C))
    invC = inv(C)
    return μ * det(C)^(-1/3)*(I - 1/3*trace(C)*invC) + Kb*(J-1)*J*invC
end

const μ = 1e10;
const Kb = 1.66e11;
Ψ(C) = Ψ(C, μ, Kb)
S(C) = S(C, μ, Kb)

@testset "AD" begin
for dim in 1:3
    println("Testing AD for dim = $dim")

    F = one(Tensor{2,dim}) + rand(Tensor{2,dim});
    C = tdot(F);
    C2 = F' ⋅ F;

    @test 2∇(Ψ, C) ≈ S(C)
    @test 2∇(Ψ, C2) ≈ S(C2)

    b = rand(SymmetricTensor{2, dim})
    @test 2 * Δ(Ψ, C) ⊡ b ≈ ∇(S, C) ⊡ b
    @test 2 * Δ(Ψ, C2) ⊡ b ≈ ∇(S, C2) ⊡ b

    @test ∇(Ψ, C) ≈ ∇(Ψ, C2)
    @test ∇(S, C) ⊡ b ≈ ∇(S, C2) ⊡ b
    @test Δ(Ψ, C) ⊡ b ≈ Δ(Ψ, C2) ⊡ b

    for T in (Float32, Float64)
        A = rand(Tensor{2, dim, T})
        B = rand(Tensor{2, dim, T})
        A_sym = rand(SymmetricTensor{2, dim, T})
        B_sym = rand(SymmetricTensor{2, dim, T})
        v = rand(Vec{dim, T})
        II = one(Tensor{4, dim, T})
        II_sym = one(SymmetricTensor{4, dim, T})

        # Gradient of scalars
        @test ∇(norm, v) ≈ ∇(norm, v, :all)[1] ≈ v / norm(v)
        @test ∇(norm, v, :all)[2] ≈ norm(v)
        @test ∇(norm, A) ≈ ∇(norm, A, :all)[1] ≈ A / norm(A)
        @test ∇(norm, A, :all)[2] ≈ norm(A)
        @test ∇(norm, A_sym) ≈ ∇(norm, A_sym, :all)[1] ≈ A_sym / norm(A_sym)
        @test ∇(norm, A_sym, :all)[2] ≈ norm(A_sym)
        @test ∇(v -> 3*v, v) ≈ ∇(v -> 3*v, v, :all)[1] ≈ diagm(Tensor{2, dim}, 3.0)
        @test ∇(v -> 3*v, v, :all)[2] ≈ 3*v
        # https://en.wikipedia.org/wiki/Tensor_derivative_(continuum_mechanics)#Derivatives_of_the_invariants_of_a_second-order_tensor
        I1, DI1 = A -> trace(A), A -> one(A)
        I2, DI2 = A -> 1/2 * (trace(A)^2 - trace(A⋅A)), A -> I1(A) * one(A) - A'
        I3, DI3 = A -> det(A), A -> det(A) * inv(A)'

        @test ∇(I1, A) ≈ ∇(I1, A, :all)[1] ≈ DI1(A)
        @test ∇(I1, A, :all)[2] ≈ I1(A)
        @test ∇(I2, A) ≈ ∇(I2, A, :all)[1] ≈ DI2(A)
        @test ∇(I2, A, :all)[2] ≈ I2(A)
        @test ∇(I3, A) ≈ ∇(I3, A, :all)[1] ≈ DI3(A)
        @test ∇(I3, A, :all)[2] ≈ I3(A)
        @test ∇(I1, A_sym) ≈ ∇(I1, A_sym, :all)[1] ≈ DI1(A_sym)
        @test ∇(I1, A_sym, :all)[2] ≈ I1(A_sym)
        @test ∇(I2, A_sym) ≈ ∇(I2, A_sym, :all)[1] ≈ DI2(A_sym)
        @test ∇(I2, A_sym, :all)[2] ≈ I2(A_sym)
        @test ∇(I3, A_sym) ≈ ∇(I3, A_sym, :all)[1] ≈ DI3(A_sym)
        @test ∇(I3, A_sym, :all)[2] ≈ I3(A_sym)

        # Gradient of second order tensors
        @test ∇(identity, A) ≈ ∇(identity, A, :all)[1] ≈ II
        @test ∇(identity, A, :all)[2] ≈ A
        @test ∇(identity, A_sym) ≈ ∇(identity, A_sym, :all)[1] ≈ II_sym
        @test ∇(identity, A_sym, :all)[2] ≈ A_sym
        @test ∇(transpose, A) ⊡ B ≈ ∇(transpose, A, :all)[1] ⊡ B ≈ B'
        @test ∇(transpose, A, :all)[2] ≈ A'
        @test ∇(transpose, A_sym) ⊡ B_sym ≈ ∇(transpose, A_sym, :all)[1] ⊡ B_sym ≈ B_sym'
        @test ∇(transpose, A_sym, :all)[2] ≈ A_sym'
        @test ∇(inv, A) ⊡ B ≈ ∇(inv, A, :all)[1] ⊡ B ≈ - inv(A) ⋅ B ⋅ inv(A)
        @test ∇(inv, A, :all)[2] ≈ inv(A)
        @test ∇(inv, A_sym) ⊡ B_sym ≈ ∇(inv, A_sym, :all)[1] ⊡ B_sym ≈ - inv(A_sym) ⋅ B_sym ⋅ inv(A_sym)
        @test ∇(inv, A_sym, :all)[2] ≈ inv(A_sym)

        # Hessians of scalars
        @test Δ(norm, A).data ≈ Δ(norm, A, :all)[1].data ≈ vec(ForwardDiff.hessian(x -> sqrt(sumabs2(x)), A.data))
        @test Δ(norm, A, :all)[2].data ≈ vec(ForwardDiff.gradient(x -> sqrt(sumabs2(x)), A.data))
        @test Δ(norm, A, :all)[3] ≈ norm(A)
    end
end
end # testset


function kpm( A, Vinit, M::Integer )

    nvec = size(Vinit,2)
    n = size(A,1);
    zeta = zeros(M,1);
    mu = zeros(M,1);
    V = zeros(n,M+1) + zeros(n,M+1)*im;

    for l = 1:nvec
        V[:,1] = Vinit[:,l];
        for k = 1:M
            zeta[k] = zeta[k] + real(dot(V[:,1],V[:,k]));
            if ( k == 1 )
                V[:,k+1] = A*V[:,k];
            else
                V[:,k+1] = 2*(A*V[:,k]) - V[:,k-1];
            end
        end
    end

    zeta = zeta ./ nvec;
    for k = 1:M
        kd = 2;
        if( k == 1 ) kd = 1; end
        mu[k] = kd / n / pi * zeta[k];
    end

    p = Polynomials.Polynomial(0.0)
    for k=1:M
        p += chebyshevpoly(k-1) * mu[k]
    end

    return p

end


@testset "mixed complementarity problem" begin
    M = [0  0 -1 -1 ;
         0  0  1 -2 ;
         1 -1  2 -2 ;
         1  2 -2  4 ]

    q = [2; 2; -2; -6]

    myfunc(x) = M*x + q

    n = 4
    lb = zeros(n)
    ub = 100*ones(n)

    status, z, f = solveMCP(myfunc, lb, ub)
    @show status
    @show z
    @show f

    @test isapprox(z, [2.8, 0.0, 0.8, 1.2])
    @test status == :Solved

    println("-------------------------------------------------------")

    M = [0  0 -1 -1 ;
         0  0  1 -2 ;
         1 -1  2 -2 ;
         1  2 -2  4 ]

    q = [2; 2; -2; -6]

    myfunc(x) = M*x + q

    n = 4
    lb = zeros(n)
    ub = 100*ones(n)

    options(convergence_tolerance=1e-2, output=:no, time_limit=3600)

    status, z, f = solveMCP(myfunc, lb, ub)

    @show status
    @show z
    @show f

    @test isapprox(z, [2.8, 0.0, 0.8, 1.2])
    @test status == :Solved

    println("-------------------------------------------------------")

    function elemfunc(x)
        val = similar(x)
        val[1] = -x[3]-x[4] + q[1]
        val[2] = x[3] -2x[4] + q[2]
        val[3] = x[1]-x[2]+2x[3]-2x[4] + q[3]
        val[4] = x[1]+2x[2]-2x[3]+4x[4] + q[4]
        return val
    end

    n = 4
    lb = zeros(n)
    ub = 100*ones(n)

    var_name = ["first var", "second var", "third var", "fourth var"]
    con_name = ["func 1", "func 2", "func 3", "func 4"]

    options(convergence_tolerance=1e-2, output=:yes, time_limit=3600)

    jacfunc(x) = M

    status, z, f = solveMCP(elemfunc, lb, ub)
    status, z, f = solveMCP(elemfunc, lb, ub, var_name)
    status, z, f = solveMCP(elemfunc, lb, ub, var_name, con_name)
    status, z, f = solveMCP(elemfunc, jacfunc, lb, ub)
    status, z, f = solveMCP(elemfunc, jacfunc, lb, ub, var_name)
    status, z, f = solveMCP(elemfunc, jacfunc, lb, ub, var_name, con_name)

    @show status
    @show z
    @show f

    @test isapprox(z, [2.8, 0.0, 0.8, 1.2])
    @test status == :Solved

    function test_in_local_scope()
        # Verify that we can solve MCPs in local scope. Surprisingly, this is
        # is relevant because it affects the way closures are generated. To be
        # specific, you can do the following in global scope:
        #
        # julia> y = [1]
        # julia> cfunction(x -> x + y[1], Int, (Int,))
        #
        # but running the same code inside a function will fail with:
        #   ERROR: closures are not yet c-callable

        M = [0  0 -1 -1 ;
             0  0  1 -2 ;
             1 -1  2 -2 ;
             1  2 -2  4 ]

        q = [2; 2; -2; -6]

        myfunc(x) = M*x + q

        n = 4
        lb = zeros(n)
        ub = 100*ones(n)

        options(convergence_tolerance=1e-2, output=:yes, time_limit=3600, lemke_start=:first, nms_searchtype=:line)

        status, z, f = solveMCP(myfunc, lb, ub)

        @show status
        @show z
        @show f

        @test isapprox(z, [2.8, 0.0, 0.8, 1.2])
        @test status == :Solved
    end

    test_in_local_scope()
end


using Test
using Distributions, Random

rng = MersenneTwister(123)

@testset "Truncated normal, mean and variance" begin
    d = Distributions.truncated(Normal(0,1),100,115); @test mean(d) ≈ 100.00999800099926070518490239457545847490332879043
    d = Distributions.truncated(Normal(0,1),50,70); @test var(d) ≈ 0.00039904318680389954790992722653605933053648912703600
    d = Distributions.truncated(Normal(-2,3),50,70); @test mean(d) ≈ 50.171943499898757645751683644632860837133138152489
    d = Distributions.truncated(Normal(-2,3),50,70); @test var(d) ≈ 0.029373438107168350377591231295634273607812172191712
end
@testset "Truncated normal $trunc" begin
    trunc = truncated(Normal(0, 1), -2, 2)
    @testset "Truncated normal $trunc with $func" for func in
        [(r = rand, r! = rand!),
         (r = ((d, n) -> rand(rng, d, n)), r! = ((d, X) -> rand!(rng, d, X)))]
        repeats = 1000000

        @test abs(mean(func.r(trunc, repeats))) < 0.01
        @test abs(median(func.r(trunc, repeats))) < 0.01
        @test abs(var(func.r(trunc, repeats)) - var(trunc)) < 0.01

        X = Matrix{Float64}(undef, 1000, 1000)
        func.r!(trunc, X)
        @test abs(mean(X)) < 0.01
        @test abs(median(X)) < 0.01
        @test abs(var(X) - var(trunc)) < 0.01
    end
end


@polyvar x y
@variable(model, t)
p1 = t*(1 + x*y)^2 - x*y + (1 - y)^2
p2 = (1 - x*y)^2 + x*y + t*(1 + y)^2
@constraint(model, p1 >= 0)
@constraint(model, p2 >= 0)
@objective(model, Min, t)
optimize!(model)
println("Solution: $(value(t))")
# Returns t ~ 0.25


"""
    struct ScaledMeasure{R,M,X} <: AbstractMeasure{X}
        logscale :: R
        base :: M
    end

"""
struct ScaledMeasure{R,M,X} <: AbstractMeasure{X}
    logscale :: R
    base :: M
end

function logdensity(sm::ScaledMeasure{R,M,X}, x::X) where {X, R <: Real, M <: AbstractMeasure{X}}
    logdensity(sm.base, x) + sm.logscale
end

function Base.:*(k::Number, m::AbstractMeasure{X}) where {X}
    if iszero(k)
        return TrivialMeasure(X)
    else
        return ScaledMeasure{typeof(k), typeof(m),X}(log(k),m)
    end
end

Base.:*(m::AbstractMeasure{X}, k::Real) where {X} = k * m

≪(::M, ::ScaledMeasure{R,M,X}) where {R,M,X} = true
≪(::ScaledMeasure{R,M,X}, ::M) where {R,M,X} = true


"""
    jointoperator_bc(operators, Q::Array)

Returns a discretized operator that solves systems of differential equations defined by
`operators` with transitions by `Q` where `operators` is an N-length collection of
discretized operators with boundary conditions applied and `Q` is N by N intensity matrix.

# Examples
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 0:5
0:5

julia> L1bc = L₁₋bc(x̄, (Reflecting(), Reflecting()))
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
  0.0   0.0    ⋅    ⋅
 -1.0   1.0   0.0   ⋅
   ⋅   -1.0   1.0  0.0
   ⋅     ⋅   -1.0  1.0

julia> L2bc = L₂bc(x̄, (Reflecting(), Reflecting()))
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
 -1.0   1.0    ⋅     ⋅
  1.0  -2.0   1.0    ⋅
   ⋅    1.0  -2.0   1.0
   ⋅     ⋅    1.0  -1.0

julia> Q = [-0.5 0.5; 0.3 -0.3]
2×2 Array{Float64,2}:
 -0.5   0.5
  0.3  -0.3

julia> jointoperator_bc((L1bc, L2bc), Q)
2×2-blocked 8×8 BlockBandedMatrices.BandedBlockBandedMatrix{Float64,BlockArrays.PseudoBlockArray{Float64,2,Array{Float64,2},BlockArrays.BlockSizes{2,Tuple{Array{Int64,1},Array{Int64,1}}}}}:
 -0.5   0.0    ⋅    ⋅   │   0.5   0.0    ⋅     ⋅
 -1.0   0.5   0.0   ⋅   │   0.0   0.5   0.0    ⋅
   ⋅   -1.0   0.5  0.0  │    ⋅    0.0   0.5   0.0
   ⋅     ⋅   -1.0  0.5  │    ⋅     ⋅    0.0   0.5
 ───────────────────────┼────────────────────────
  0.3   0.0    ⋅    ⋅   │  -1.3   1.0    ⋅     ⋅
  0.0   0.3   0.0   ⋅   │   1.0  -2.3   1.0    ⋅
   ⋅    0.0   0.3  0.0  │    ⋅    1.0  -2.3   1.0
   ⋅     ⋅    0.0  0.3  │    ⋅     ⋅    1.0  -1.3
```
"""
function jointoperator_bc(operators, Q::Array)
    M = size(operators[1],1)
    N = length(operators)

    # check if all operators are square
    @assert all(operator->(size(operator,1) == size(operator,2)),
                operators)

    # check if all operators have same size
    @assert all(operator->(size(operator) == size(operators[1])),
                operators)

    # check if the size of transition matrix is
    # same as the number of operators
    @assert size(Q,1) == size(Q,2) == N

    # extract operators and append them to form a diagonal block tridiagonal
    Ls = blockdiag(sparse.(operators)...)
    Ls = BandedBlockBandedMatrix(Ls, (M*ones(Int64, N), M*ones(Int64, N)), (0,0), (1,1))

    # construct a kronecker product of Q times I_M
    Qs = BandedBlockBandedMatrix(Kron(Q, Eye(M)))

    return (Ls + Qs)
end


function DFPermute(A::DF,v::Vector{Int})
    if length(A.var)!=length(v)
        error("Permute vector's dimension mismatch!")
    end

    valuespace = reshape(A.value, A.card...)
    newvaluespace = permutedims(valuespace, v)
    var = A.var[v]
    card = A.card[v]
    DF(var,card,newvaluespace[:])
end

function DFProduct(A::DF, B::DF)
    common_var = intersect(A.var, B.var)
    A_common_idx = indexin(common_var, A.var)
    B_common_idx = indexin(common_var, B.var)
    if A.card[A_common_idx] != B.card[B_common_idx]
        error("Inputs' common variables have mismatch cardinality!")
    end

    # Set the variables of C
    Cvar = union(A.var, B.var)

    Cvar_length = length(Cvar)

    mapA = indexin(A.var, Cvar)
    mapB = indexin(B.var, Cvar)

    # Set the cardinality of variables in C
    Ccard = zeros(Int, Cvar_length)
    Ccard[mapA] = A.card
    Ccard[mapB] = B.card

    Aarr0 = indexin(Cvar, A.var)
    Barr0 = indexin(Cvar, B.var)
    Aarr1 = Aarr0[Aarr0.>0]
    Barr1 = Barr0[Barr0.>0]

    A1 = DFPermute(A, Aarr1)
    B1 = DFPermute(B, Barr1)

    A1card = ones(Int, Cvar_length)
    B1card = ones(Int, Cvar_length)
    A1card[mapA] = A.card
    B1card[mapB] = B.card

    Avaluespace = reshape(A1.value, A1card...)
    Bvaluespace = reshape(B1.value, B1card...)

    Cvaluespace = Avaluespace .* Bvaluespace

    DF(Cvar, Ccard, Cvaluespace[:])
end

*(A::DF, B::DF) = DFProduct(A::DF, B::DF)

function DFMargin(A::DF, Remove_var::Vector{ASCIIString}, Remain_var::Vector{ASCIIString}, Remove_dims::Vector{Int}, Remain_dims::Vector{Int})
    Remain_card = A.card[Remain_dims]

    valuespace = reshape(A.value, A.card...)
    permute_dims = [Remain_dims,Remove_dims]
    permuted_valuespace = permutedims(valuespace, permute_dims)

    squeeze_dims = [length(Remain_dims)+1:length(permute_dims)]

    sumvaluespace = sum(permuted_valuespace, squeeze_dims)
    Remain_valuespace = squeeze(sumvaluespace, squeeze_dims)

    DF(Remain_var, Remain_card, Remain_valuespace[:])
end

function DFMarginDrop(A::DF, Remove_var::Vector{ASCIIString})
    Remove_dims = indexin(Remove_var, A.var)
    if any(Remove_dims==0)
        error("Wrong variable!")
    end

    Remain_var = symdiff(A.var, Remove_var)
    Remain_dims = indexin(Remain_var, A.var)

    DiscreteFactor.DFMargin(A, Remove_var, Remain_var, Remove_dims, Remain_dims)
end

function DFMarginKeep(A::DF, Remain_var::Vector{ASCIIString})
    Remain_dims = indexin(Remain_var, A.var)
    if any(Remain_dims==0)
        error("Wrong variable!")
    end

    Remove_var = symdiff(A.var, Remain_var)
    Remove_dims = indexin(Remove_var, A.var)

    DiscreteFactor.DFMargin(A, Remove_var, Remain_var, Remove_dims, Remain_dims)
end

function DFReduce(A::DF, Reduce_var::Vector{ASCIIString}, Reduce_idx::Vector{Int})
    Reduce_dims = indexin(Reduce_var, A.var)
    if any(Reduce_dims.==0)
        error("Wrong variable!")
    end
    if any(A.card[Reduce_dims].<Reduce_idx)
        error("Index larger than cardinality!")
    end

    Bvar = deepcopy(A.var)
    Bcard = deepcopy(A.card)
    Bvar[Reduce_dims] = Bvar[Reduce_dims].*["_{$i}" for i in Reduce_idx]
    Bcard[Reduce_dims] = 1

    valuespace = reshape(A.value, A.card...)

    for i = 1:length(Reduce_dims)
        valuespace = slicedim(valuespace, Reduce_dims[i], Reduce_idx[i])
    end
    DF(Bvar, Bcard, valuespace[:])
end

function DFNormalize(A::DF)
    Bvar = deepcopy(A.var)
    Bcard = deepcopy(A.card)
    tmpvalue = deepcopy(A.value)
    Z =  sum(tmpvalue)
    Bvalue = tmpvalue./Z
    DF(Bvar, Bcard, Bvalue)
end


using ABCDBeamTrace
@static if VERSION < v"0.7.0-DEV.2005"
    using Base.Test
else
    using Test
end

@testset "beamtrace" begin
    f = 100e-3
    L = 1000e-3
    expander_2x = [ThinLens(f), FreeSpace(3f), ThinLens(2f)]
    system = [expander_2x; FreeSpace(L); reverse(expander_2x)]
    beam = Beam(1000e-9, 1.0e-3)
    @test beamtrace(system, beam) isa Vector{Beam}
end

@testset "discretize" begin
    f = 100e-3
    expander_2x = [ThinLens(f), FreeSpace(3f), ThinLens(2f)]
    @test length(discretize(expander_2x,10)) == 12
end

@testset "waistradiusfunc" begin
    w0 = 1e-3
    f = 100e-3
    expander_2x = [ThinLens(f), FreeSpace(3f), ThinLens(2f)]
    beam = Beam(1000e-9, w0)
    @test waistradiusfunc(expander_2x, beam) isa Function
    @test waistradiusfunc(expander_2x, beam)(0) == w0
    @test waistradiusfunc(expander_2x, beam)(3f) ≈ 2w0 rtol=0.01
end


function miscCombineTimeSteps(base_expr::Basic, base_radius::Int,
                            expr::Basic, radius::Int)

    b_sym, b_off = CreateSymOffMatrices(base_radius)
    sym,off = CreateSymOffMatrices(radius)
    new_sym, new_off = CreateSymOffMatrices(radius+base_radius)

    new_eq = 0

    for i = 1:(2*radius+1), j = 1:(2*radius+1),k = 1:(2*radius+1)
        c1 = coeff(expr, sym[i,j,k])
        (c1 == 0) && continue
        for ii = 1:(2*base_radius+1), jj = 1:(2*base_radius+1),kk = 1:(2*base_radius+1)
            c2 = coeff(base_expr, b_sym[ii,jj,kk])
            (c2 == 0) && continue
            off2 = b_off[ii,jj,kk] + [i;j;k] .+ base_radius
            new_eq += c1*c2*new_sym[off2...]
        end
    end
    return new_eq
end

function countComp(expr, radius, m_step)
    count_arr = zeros(Int, m_step)
    r = radius
    new_sym = expr
    count_arr[1] = count_coeffs(expr, radius)
    for i = 2:m_step
        new_sym = CombineTimeSteps(expr, r, new_sym, r*i)
        count_arr[i] = count_coeffs(new_sym, r*i)
    end
    return count_arr
end

function count_coeffs(expr, radius)
    Dsym = [symbols("w$(i)_$(j)_$(k)") for i = 1:(2radius+1),
                                        j = 1:(2radius+1),
                                        k = 1:(2radius+1)]
    s = 0
    for d in Dsym
        c = coeff(expr, d)
        if c  != 0
            s += 1
        end
    end
    return s

end


using Quaternions
using LinearAlgebra
using QuaternionArgs
using Test

const N=100
@testset "QuaternionArgs Tests" begin
    @testset "getter" begin
        for typ in [QuaternionArgF16, QuaternionArgF32, QuaternionArgF64]
            a = rand(typ)
            @test amp(a)==a.q
            @test phase1(a)==a.phi
            @test phase2(a)==a.theta
            @test phase3(a)==a.psi
        end
    end

    for typ in [QuaternionF16, QuaternionF32, QuaternionF64]
        @testset "$typ" begin
            @testset "conversion" begin
                for i in 1:N
                    a = QuaternionArg(randn(typ))
                    @test isapprox(a,Quaternion(a))
                end
            end

            @testset "four arithmetic" begin
                for i in 1:N
                    a = randn(typ)
                    b = randn(typ)
                    ag = QuaternionArg(a)
                    bg = QuaternionArg(b)

                    anorm = normalize(ag)
                    @test amp(anorm)==one(anorm.q)
                    @test isapprox(a+b, Quaternion(ag+bg))
                    @test isapprox(a-b, Quaternion(ag-bg))
                    @test isapprox(a*b, Quaternion(ag*bg))
                    @test isapprox(a/b, Quaternion(ag/bg))
                end
            end
        end
    end
end


using Statistics
using Dates
using Insolation

using CLIMAParameters
using CLIMAParameters.Planet
struct EarthParameterSet <: AbstractEarthParameterSet end
const param_set = EarthParameterSet()

atol = 1e-6
rtol = 1e-2

## Test zero insolation at night
# sunrise at equator
date = Dates.DateTime(2020, 1, 1, 6, 0, 0)
lon, lat = [0.0, 0.0]
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
F = insolation(sza, d, param_set)
@test F ≈ 0.0 atol=atol

S, mu = solar_flux_and_cos_sza(date, lon, lat, param_set)
@test S ≈ tot_solar_irrad(param_set) rtol=4e-2
@test mu ≈ 0.0 atol=atol

# polar night NH 1
date = Dates.DateTime(2020, 12, 20, 11, 0, 0)
lon, lat = [0.0, 80.0]
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
F = insolation(sza, d, param_set)
@test F ≈ 0.0 atol=atol

S, mu = solar_flux_and_cos_sza(date, lon, lat, param_set)
@test S ≈ tot_solar_irrad(param_set) rtol=4e-2
@test mu ≈ 0.0 atol=atol

# polar night NH 2
date = Dates.DateTime(2020, 12, 20, 23, 0, 0)
lon, lat = [0.0, 80.0]
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
F = insolation(sza, d, param_set)
@test F ≈ 0.0 atol=atol

S, mu = solar_flux_and_cos_sza(date, lon, lat, param_set)
@test S ≈ tot_solar_irrad(param_set) rtol=4e-2
@test mu ≈ 0.0 atol=atol

## Test globally averaged insolation ≈ TSI
ndays, nlats = [365, 181]
d_arr = Array{Int}(round.(collect(range(0, stop = 365, length = ndays))))
l_arr = collect(range(-90, stop = 90, length = nlats))
F_arr = zeros(ndays, nlats)

for (i, d) in enumerate(d_arr)
    for (j, lat) in enumerate(l_arr)
        datei = Dates.DateTime(2020,1,1) + Dates.Day(d)
        θ, dist = daily_zenith_angle(datei, lat, param_set)
        F_arr[i, j] = insolation(θ, dist, param_set)
    end
end

zonal_mean_insol = mean(F_arr, dims=1)
area_fac = abs.(cosd.(l_arr))
global_mean_insol = sum(zonal_mean_insol * area_fac) / sum(area_fac)
@test global_mean_insol ≈ tot_solar_irrad(param_set) / 4 rtol=rtol

## Test symmetry of insolation at equinox
date = Dates.DateTime(2021,3,20,9,34)
F_arr = zeros(nlats)
for (i, lat) in enumerate(l_arr)
    θ, dist = daily_zenith_angle(date, lat, param_set)
    F_arr[i] = insolation(θ, dist, param_set)
end
F_NH = sort(F_arr[l_arr .>= 0])
F_SH = sort(F_arr[l_arr .<= 0])
@test F_NH ≈ F_SH rtol=rtol

## Test invariance of zonal-mean insolation under rotation of ϖ
CLIMAParameters.Planet.lon_perihelion(::EarthParameterSet) = lon_perihelion_epoch(param_set) + π

ndays, nlats = [365, 181]
d_arr = Array{Int}(round.(collect(range(0, stop = 365, length = ndays))))
l_arr = collect(range(-90, stop = 90, length = nlats))
F_arr = zeros(ndays, nlats)

for (i, d) in enumerate(d_arr)
    for (j, lat) in enumerate(l_arr)
        datei = Dates.DateTime(2020,1,1) + Dates.Day(d)
        θ, dist = daily_zenith_angle(datei, lat, param_set)
        F_arr[i, j] = insolation(θ, dist, param_set)
    end
end

zonal_mean_insol_rotate = mean(F_arr, dims=1)
@test zonal_mean_insol_rotate ≈ zonal_mean_insol rtol=rtol


import StatsBase: standardize
import Distributions: scale

function standardize(x::AbstractArray)
  x = convert(Vector{Float64}, (x .- mean(x)))/std(x)
end

scale(x) = standardize(x)


using Statistics

export blackScholesFormula

"Price of a vanilla option under the Black-Scholes model"
function blackScholesFormula(isCall::Bool, strike::T, spot::T, variance::T, driftDf::T, discountDf::T) where {T}

    sign = 1.0
    if !isCall
        sign = -1.0
    end
    forward = spot / driftDf
    if variance < eps()
        price = discountDf * max(sign * (forward - strike), 0.0)
        return price
    elseif spot < eps()
        if isCall
            return 0.0
        else
            return discountDf * strike
        end
    elseif strike < eps()
        if isCall
            return discountDf * forward
        else
            return 0.0
        end
    else
        sqrtVar = sqrt(variance)
        d1 = 1.0 / sqrtVar * log(forward / strike) + 0.5 * sqrtVar
        d2 = d1 - sqrtVar
        normal = Normal()
        nd1 = cdf(normal, sign * d1)
        nd2 = cdf(normal, sign * d2)
        price = sign * discountDf * (forward * nd1 - strike * nd2)
        return price
    end
end


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:4] <= 1)

                     @variable(m, -6.019960436071491 <= q <= 11.696817216382863)

                     add_NL_constraint(m, :(swish(0.9042935565441375 + 0.31236426273908524*swish(0.5868252904954097 + 0.04712499510197299*$(x[1]) + 0.4005456947892885*$(x[2]) + 0.34544434364772814*$(x[3]) + 0.48957180414623247*$(x[4])) + -0.4128166127064836*swish(0.5797949667252156 + -0.7245574143823843*$(x[1]) + -0.08207866517990237*$(x[2]) + 0.4631961331947827*$(x[3]) + 0.9179781125579716*$(x[4])) + -0.2476169775927386*swish(0.8773151656273224 + 0.003673243009740812*$(x[1]) + 0.698013040735662*$(x[2]) + 0.6787695040522581*$(x[3]) + -0.9128403322093082*$(x[4]))) + swish(0.8600467319105785 + -0.4006275013725942*swish(0.5868252904954097 + 0.04712499510197299*$(x[1]) + 0.4005456947892885*$(x[2]) + 0.34544434364772814*$(x[3]) + 0.48957180414623247*$(x[4])) + 0.22106887260345687*swish(0.5797949667252156 + -0.7245574143823843*$(x[1]) + -0.08207866517990237*$(x[2]) + 0.4631961331947827*$(x[3]) + 0.9179781125579716*$(x[4])) + 0.7210213948433983*swish(0.8773151656273224 + 0.003673243009740812*$(x[1]) + 0.698013040735662*$(x[2]) + 0.6787695040522581*$(x[3]) + -0.9128403322093082*$(x[4]))) + swish(0.982756228863964 + 0.34792310275142935*swish(0.5868252904954097 + 0.04712499510197299*$(x[1]) + 0.4005456947892885*$(x[2]) + 0.34544434364772814*$(x[3]) + 0.48957180414623247*$(x[4])) + 0.7924515917531365*swish(0.5797949667252156 + -0.7245574143823843*$(x[1]) + -0.08207866517990237*$(x[2]) + 0.4631961331947827*$(x[3]) + 0.9179781125579716*$(x[4])) + -0.9399735455074207*swish(0.8773151656273224 + 0.003673243009740812*$(x[1]) + 0.698013040735662*$(x[2]) + 0.6787695040522581*$(x[3]) + -0.9128403322093082*$(x[4]))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


using MultimodalNestedSampling
using SpecialFunctions

invphi(p) = sqrt(2) * erfinv(2 * p - 1.0)

function generate_data(α,β,n=100)
    θ = -pi/2 .+ pi*rand(n)
    x = α .+ β*tan.(θ)
    return x
end

n = 5
d = zeros(n)

d .= generate_data(0.5,2.0,n)

lighthouse_log_like_all = function loglike_all(cube)
    alpha = 2*invphi(cube[1])
    beta = 5*cube[2]

    l = sum(log(beta/pi) .- log.(beta^2 .+ (d .- alpha).^2))
    return l
end

problem = MultiNest(lighthouse_log_like_all, 2, "lighthouse")

run(problem,nlive=100)


module IntegralsModule
export Overlap, doublefactorial, computeElectronRepulsionIntegral, KineticIntegral, NuclearAttractionIntegral
using ..BaseModule
using ..BasisFunctionsModule
using ..AtomModule
using ..GeometryModule
using ProgressMeter

ProgressMeter.printover(STDOUT," + (GSL........................")
using GSL
ProgressMeter.printover(STDOUT," + IntegralsModule.............")

doublefactorial(n::Int) = prod(n:-2:1)

function GaussianIntegral1D(mqn::Int,exponent::Float64)
  # I_x = Integrate[x^m Exp[-ζ x^2], {x,-∞,∞}] (acc. to Fundament. of Mol. Integr. Eval. by Fermann, Valeev)
  m = mqn
  ζ = exponent

  if (mqn%2!=0) # integral over uneven function
    return 0
  else
    return (doublefactorial(m-1)*sqrt(π)) / ((2ζ)^(m/2)*sqrt(ζ))
  end
end

function GaussianIntegral1D(mqn::Int,exponent::Float64)
  # I_x = Integrate[x^m Exp[-ζ x^2], {x,-∞,∞}] (acc. to Mathematica 9)
  m = mqn
  ζ = exponent

  if (mqn%2!=0) # integral over uneven function
    return 0
  else
    t=(m+1)/2
    return ζ^(-t) * gamma(t)
  end
end

#function FundamentalIntegral(
#  pgb1::PrimitiveGaussianBasisFunction,
#  pgb2::PrimitiveGaussianBasisFunction,
#  func::Function,
#  pgb3::PrimitiveGaussianBasisFunction,
#  pgb4::PrimitiveGaussianBasisFunction)
#  # (pgb1(x1) pgb2(x1) | func(x1,x2) | pgb3(x2) pgb4(x2) )
#  # pgb1..4 are considered s-Functions
#  # acc. to Molecular Integrals of Gaussian Basis Functions by Gill
#
#  # I = (π G_AB G_CD) / ( 2(ζ η)^(3/2) R^3 ) Integrate[u Sin[u] Exp[-u^2/(4T)] FT[f][u/R],{u,0,∞}]
#end

function GaussProductFundamental(
  pgb1::PrimitiveGaussianBasisFunction,
  pgb2::PrimitiveGaussianBasisFunction)
  # Fundamental means primitives are taken as s-Functions
  # K Exp[-γ r^2_P] = pgb1 * pgb2 (acc. to Fundament. of Mol. Integr. Eval. by Fermann, Valeev)

  α1 = pgb1.exponent
  α2 = pgb2.exponent
  A  = pgb1.center
  B  = pgb2.center

  γ = α1 + α2
  P = (α1*A + α2*B) / γ
  AB = distance(A,B)
  K = exp(-α1*α2*AB^2/γ)

  return (K,P,γ)
end

type PolynomialFactors
  x::Array{Tuple{Real,Int},1} # Real*x^Int
  y::Array{Tuple{Real,Int},1} # Real*y^Int
  z::Array{Tuple{Real,Int},1} # Real*z^Int
end

function GaussProductPolynomialFactor(
  pgb1::PrimitiveGaussianBasisFunction,
  pgb2::PrimitiveGaussianBasisFunction)
  # _Sum_K[f_k r^k]_ K Exp[-γ r^2_P] = pgb1 * pgb2 (acc. to Fundament. of Mol. Integr. Eval. by Fermann, Valeev)

  A  = pgb1.center
  B  = pgb2.center
  (K,P,γ) = GaussProductFundamental(pgb1,pgb2)

  factors = PolynomialFactors([],[],[])
  for (xyz in [:x,:y,:z])
    l1 = pgb1.mqn.(xyz)
    l2 = pgb2.mqn.(xyz)
    for k in 0:l1+l2
      ij = [((k+q) ÷ 2,(k-q) ÷ 2) for q in max(-k,k-2l2):2:min(k,2l1-k)]
      f = sum([binomial(l1,i) * binomial(l2,j) * (P-A).(xyz)^(l1-i) * (P-B).(xyz)^(l2-j) for(i,j) in ij])
      append!(factors.(xyz),[(f,k)])
    end
  end

  return factors
end

function Overlap(
  pgb1::PrimitiveGaussianBasisFunction,
  pgb2::PrimitiveGaussianBasisFunction)
  # S_ij = Integrate[phi_i(r) phi_j(r),{r el R}] (acc. to Fundament. of Mol. Integr. Eval. by Fermann, Valeev)

  (K,P,γ) = GaussProductFundamental(pgb1,pgb2)
  factors = GaussProductPolynomialFactor(pgb1,pgb2)

  Ix = sum([f*GaussianIntegral1D(i,γ) for (f,i) in factors.x])
  Iy = sum([f*GaussianIntegral1D(i,γ) for (f,i) in factors.y])
  Iz = sum([f*GaussianIntegral1D(i,γ) for (f,i) in factors.z])
  return K*Ix*Iy*Iz::Float64
end

function Overlap(
  cgb1::ContractedGaussianBasisFunction,
  cgb2::ContractedGaussianBasisFunction)

  integral = 0.::Float64
  for (coeff1,pgb1) in zip(cgb1.coefficients,cgb1.primitiveBFs)
    for (coeff2,pgb2) in zip(cgb2.coefficients,cgb2.primitiveBFs)
      integral += coeff1*coeff2*Overlap(pgb1,pgb2)
    end
  end
  return integral::Float64
end

function incrmqn(mqn::MQuantumNumber,xyz::Symbol,inc::Int)
  mqn_t = [mqn.x,mqn.y,mqn.z]
  xyznum = Dict(:x => 1, :y => 2, :z => 3)
  num = xyznum[xyz]
  mqn_t[num] += inc
  return MQuantumNumber(mqn_t[1],mqn_t[2],mqn_t[3])
end

function KineticIntegral(
  pgb1::PrimitiveGaussianBasisFunction,
  pgb2::PrimitiveGaussianBasisFunction)
  #Ix + Iy + Iz = (pgb1 | 1/2 ∇^2 | pgb2)
  #Ix = 1/2 l1 l2 <-1|-1> + 2 α1 α2 <+1|+1> - α1 l2 <+1|-1> - α2 l1 <-1|+1>
  #(acc. to Fundamentals of Mol. Integr. Eval. by Fermann, Valeev (eq. 4.1 + 4.13))

  integral = 0.::Float64

  for (xyz in (:x,:y,:z))
    pgb1decr = deepcopy(pgb1)
    pgb1decr.mqn = incrmqn(pgb1.mqn,xyz,-1)
    pgb1incr = deepcopy(pgb1)
    pgb1incr.mqn = incrmqn(pgb1.mqn,xyz,1)

    pgb2decr = deepcopy(pgb2)
    pgb2decr.mqn = incrmqn(pgb2.mqn,xyz,-1)
    pgb2incr = deepcopy(pgb2)
    pgb2incr.mqn = incrmqn(pgb2.mqn,xyz,1)

    l1 = pgb1.mqn.(xyz)
    l2 = pgb2.mqn.(xyz)
    α1 = pgb1.exponent
    α2 = pgb2.exponent

    if(l1*l2!=0) integral += (1/2*l1*l2) * Overlap(pgb1decr,pgb2decr) end
                 integral += (2*α1*α2)   * Overlap(pgb1incr,pgb2incr)
    if(l2   !=0) integral += (-α1*l2)    * Overlap(pgb1incr,pgb2decr) end
    if(l1   !=0) integral += (-l1*α2)    * Overlap(pgb1decr,pgb2incr) end
  end

  return integral::Float64
end

function KineticIntegral(
  cgb1::ContractedGaussianBasisFunction,
  cgb2::ContractedGaussianBasisFunction)

  integral = 0.::Float64
  for (coeff1,pgb1) in zip(cgb1.coefficients,cgb1.primitiveBFs)
    for (coeff2,pgb2) in zip(cgb2.coefficients,cgb2.primitiveBFs)
      integral += coeff1*coeff2*KineticIntegral(pgb1,pgb2)
    end
  end
  return integral::Float64
end

function OverlapFundamental(
  pgb1::PrimitiveGaussianBasisFunction,
  pgb2::PrimitiveGaussianBasisFunction)
  (K,P,γ) = GaussProductFundamental(pgb1,pgb2)
  # Overlap(s-type pgb1, s-type pgb2) (acc. to. Mathematica)
  return K*(π/γ)^(3/2)::Float64
end

function FIntegral(m::Integer,x::Real,APPROX_ZERO::Float64=1e-5)
  # F[m,x] = Integrate[u^2m Exp[-x u^2],{u,0,1}
  #         = 1/2 x^(-0.5-m) ( Gamma[1/2 + m] - Gamma[1/2 + m, x] ) # acc. to Mathematica
  x = max(x,APPROX_ZERO) # for x -> 0
  if (x<=APPROX_ZERO) return 1/(1+2*m) end
  return 1/2 * x^(-1/2-m) * ( GSL.sf_gamma(1/2+m) - GSL.sf_gamma_inc(1/2+m,x) )
end

function ASummand(
  f::Real,
  l1::Integer,
  l2::Integer,
  Ax::Real,
  Bx::Real,
  PCx::Real,
  γ::Real,
  l::Integer,
  r::Integer,
  i::Integer)
  # A[l1,l2,Ax,Bx,Cx,γ] = (-1)^l f_l(l1,l2,PAx,PBx) ((-1)^i l! PCx^(l-2r-2i) (1/(4γ))^(r+i)) / (r! i! (l-2r-2i)!)
  return (-1)^l * f * (-1)^i * factorial(l) * PCx^(l-2r-2i) * (1/(4γ))^(r+i) / (factorial(r) * factorial(i) * factorial(l - 2r - 2i))
end

function NuclearAttractionIntegral(
  pgb1::PrimitiveGaussianBasisFunction,
  pgb2::PrimitiveGaussianBasisFunction,
  atom::Atom)
  # V = K * A(l1,l2,Ax,Bx,Cx,γ) * A(m1,m2,Ay,By,Cy,γ) * A(n1,n2,Az,Bz,Cz,γ) * F[l+m+n-2(r+s+t) - (i+j+k),γ PC^2]
  #acc. to. Handbook of Comput. Quant. Chem. by Cook, chap. 7.7.3 final formula
  K,P,γ = GaussProductFundamental(pgb1,pgb2)
  C = atom.position

  result = 0
  for ((fx,l) in GaussProductPolynomialFactor(pgb1,pgb2).x)
    for (r in 0:floor(Integer,(l/2)))
      for (i in 0:floor(Integer,((l-2r)/2)))
	Ax = ASummand(fx,pgb1.mqn.x,pgb2.mqn.x,pgb1.center.x,pgb2.center.x,(P-C).x,γ,l,r,i)
	for ((fy,m) in GaussProductPolynomialFactor(pgb1,pgb2).y)
	  for (s in 0:floor(Integer,(m/2)))
	    for (j in 0:floor(Integer,((m-2s)/2)))
	      Ay = ASummand(fy,pgb1.mqn.y,pgb2.mqn.y,pgb1.center.y,pgb2.center.y,(P-C).y,γ,m,s,j)
	      for ((fz,n) in GaussProductPolynomialFactor(pgb1,pgb2).z)
		for (t in 0:floor(Integer,(n/2)))
		  for (k in 0:floor(Integer,((n-2t)/2)))
		    Az = ASummand(fz,pgb1.mqn.z,pgb2.mqn.z,pgb1.center.z,pgb2.center.z,(P-C).z,γ,n,t,k)
		    result += 2π/γ * K * Ax * Ay * Az * FIntegral(l+m+n-2*(r+s+t)-(i+j+k),γ*distance(P,C)^2)
		  end
		end
	      end
	    end
	  end
	end
      end
    end
  end

  return -atom.element.atomicNumber * result
end

function NuclearAttractionIntegral(
  cgb1::ContractedGaussianBasisFunction,
  cgb2::ContractedGaussianBasisFunction,
  atom::Atom)
  integral = 0.::Float64
  for (coeff1,pgb1) in zip(cgb1.coefficients,cgb1.primitiveBFs)
    for (coeff2,pgb2) in zip(cgb2.coefficients,cgb2.primitiveBFs)
      integral += coeff1*coeff2*NuclearAttractionIntegral(pgb1,pgb2,atom)
    end
  end
  return integral::Float64
end

type θfactors
  x::Array{Tuple{Real,Int,Int},1} # θ,l,r
  y::Array{Tuple{Real,Int,Int},1} # θ,l,r
  z::Array{Tuple{Real,Int,Int},1} # θ,l,r
end

function θFactors(
  μ::PrimitiveGaussianBasisFunction,
  ν::PrimitiveGaussianBasisFunction)

  K,P,γ = IntegralsModule.GaussProductFundamental(μ,ν)

  factors = θfactors([],[],[])
  for (xyz in [:x,:y,:z])
    for ((f,l) in IntegralsModule.GaussProductPolynomialFactor(μ,ν).(xyz))
      for (r in 0:floor(Integer,(l/2)))
	θ = f * (factorial(l)*γ^(r-l))/(factorial(r)*factorial(l-2r))
	append!(factors.(xyz),[(θ,l,r)])
      end
    end
  end
  return factors
end

type Bfactors
  x::Array{Tuple{Real,Int,Int,Int,Int,Int},1}	# B,l12,r12,i,l34,r34
  y::Array{Tuple{Real,Int,Int,Int,Int,Int},1}	# B,l12,r12,i,l34,r34
  z::Array{Tuple{Real,Int,Int,Int,Int,Int},1}	# B,l12,r12,i,l34,r34
end

function BFactors(
  μ::PrimitiveGaussianBasisFunction,
  ν::PrimitiveGaussianBasisFunction,
  λ::PrimitiveGaussianBasisFunction,
  σ::PrimitiveGaussianBasisFunction)

  K1,P,γ1 = IntegralsModule.GaussProductFundamental(μ,ν)
  K2,Q,γ2 = IntegralsModule.GaussProductFundamental(λ,σ)
  δ = 1/(4γ1) + 1/(4γ2)
  #p = (P-Q)	# this might be a typo in the book
  p = (Q-P)

  factors = Bfactors([],[],[])
  for (xyz in (:x,:y,:z))
    for ((θ12,l12,r12) in θFactors(μ,ν).(xyz))
      for ((θ34,l34,r34) in θFactors(λ,σ).(xyz))
	#for (i in 0:floor(Integer,((l12-2r12)/2)))    # this might be a typo in the book (otherwise e.g. ERI(s,s,px,px2) != ERI(px,px2,s,s) (where the 2 denotes a second center moved by 0.5 in x direction)
	for (i in 0:floor(Integer,((l12+l34-2r12-2r34)/2)))
	  B = (-1)^l34 * θ12 * θ34 * ((-1)^i*(2δ)^(2(r12+r34))*factorial(l12+l34-2r12-2r34)*δ^i*p.(xyz)^(l12+l34-2*(r12+r34+i)))/((4δ)^(l12+l34)*factorial(i)*factorial(l12+l34-2*(r12+r34+i)))
	  append!(factors.(xyz),[(B,l12,r12,i,l34,r34)])
	end
      end
    end
  end
  return factors
end

function computeElectronRepulsionIntegral(
  μ::PrimitiveGaussianBasisFunction,
  ν::PrimitiveGaussianBasisFunction,
  λ::PrimitiveGaussianBasisFunction,
  σ::PrimitiveGaussianBasisFunction)
  # compute (μν|λσ) (Mulliken notation) = Integrate[μ(1) ν(1) 1/Abs(1-2) λ(2) σ(2), d1 d2]
  # in the most straightforward but primitive way (acc. to. Handbook of Comp. Quant. Chem. by Cook eq.7.1)
  A = μ.center
  B = ν.center
  C = λ.center
  D = σ.center
  α1 = μ.exponent
  α2 = ν.exponent
  α3 = λ.exponent
  α4 = σ.exponent
  K1,P,γ1 = IntegralsModule.GaussProductFundamental(μ,ν)
  K2,Q,γ2 = IntegralsModule.GaussProductFundamental(λ,σ)
  δ = 1/(4γ1) + 1/(4γ2)
  p = (P-Q)

  Bfactors = BFactors(μ,ν,λ,σ)
  Ω = 2π^2/(γ1 * γ2) * sqrt(π/(γ1 + γ2)) * exp(-α1*α2*distance(A,B)^2/γ1 - α3*α4*distance(C,D)^2/γ2)

  result = 0.::Float64
  for ((Bx,l12,r12,i,l34,r34) in Bfactors.x)
    for ((By,m12,s12,j,m34,s34) in Bfactors.y)
      for ((Bz,n12,t12,k,n34,t34) in Bfactors.z)
	V = (l12+l34+m12+m34+n12+n34) - 2*(r12+r34+s12+s34+t12+t34) - (i+j+k)
	result += Ω * Bx * By * Bz * IntegralsModule.FIntegral(V,distance(P,Q)^2/(4δ))
	#println("Bx*By*Bz*FIntegral[$V] = $Bx * $By * $Bz * $(IntegralsModule.FIntegral(V,distance(P,Q)^2/(4δ)))")
      end
    end
  end
  return result
end

function computeElectronRepulsionIntegral(
  μ::ContractedGaussianBasisFunction,
  ν::ContractedGaussianBasisFunction,
  λ::ContractedGaussianBasisFunction,
  σ::ContractedGaussianBasisFunction)

  integral::Real = 0.
  for (coeff1,pgb1) in zip(μ.coefficients,μ.primitiveBFs)
    for (coeff2,pgb2) in zip(ν.coefficients,ν.primitiveBFs)
      for (coeff3,pgb3) in zip(λ.coefficients,λ.primitiveBFs)
	for (coeff4,pgb4) in zip(σ.coefficients,σ.primitiveBFs)
	  integral += coeff1*coeff2*coeff3*coeff4*computeElectronRepulsionIntegral(pgb1,pgb2,pgb3,pgb4)
	end
      end
    end
  end
  return integral::Float64
end

end # module


using Roots

# Given
i = 0.09
# compounded annually
# double the amount
log(2) / log(1 + i)

f(x) = 2 - (1 + i)^x
find_zero(f, 1)


using ModelingToolkit
using Test

@Param t σ ρ β
@Unknown x(t) y(t) z(t)
@Deriv D'~t # Default of first derivative, Derivative(t,1)
@Const c=0

# Default values
p = Parameter(:p)
u = Unknown(:u, dependents = [t])

σ*(y-x)
D(x)
D(x) ~ -σ*(y-x)
D(y) ~ x*(ρ-z)-sin(y)

@test D(t) == Constant(1)


f(x) = (x - 3.0)^2.0
f2(x) = 2.0* (x -3.0)

x0 = 1

for i = 1:40
	@printf("%i\t%.4e\n",i,3.0 - x0)
	x0 = x0 - f(x0) / f2(x0)
end