using DrWatson
@quickactivate "StatReth"

# %%
using DataFrames
using CSV
using Turing
using Plots

include(srcdir("quap.jl"))
include(srcdir("tools.jl"))

# %% 5.45
d = CSV.read(datadir("exp_raw/Howell_1.csv"), DataFrame)

# %% 5.46
μ_female = rand(Normal(178, 20), 10_000)
μ_male = rand(Normal(178, 20), 10_000) .+ rand(Normal(0, 10), 10_000)
DataFrame((; μ_female, μ_male)) |> precis

# %% 5.47
d.sex = ifelse.(d.male .== 1, 2, 1)

# %% 5.48
@model function categ(sex, height)
    σ ~ Uniform(0, 50)
    a ~ filldist(Normal(178, 20), 2)
    μ = a[sex]
    height ~ MvNormal(μ, σ)
end

q5_8 = quap(categ(d.sex, d.height), NelderMead())
# precis

# %% 5.49
post = DataFrame(rand(q5_8.distr, 1000)', q5_8.params)
post.diff_fm = post[:, "a[1]"] .- post[:, "a[2]"]
precis(post)

# %% 5.50
d = CSV.read(datadir("exp_raw/milk.csv"), DataFrame; missingstring = "NA")
d.clade |> unique |> sort

# %% 5.51
# You can't just turn Strings into Integers in Julia but hashing them should give the same
# result
d.clade_id = Int.(indexin(d.clade, unique(d.clade)))

# %% 5.52
d.K = zscore(d.kcal_per_g)

@model function clade(clade_id, K)
    σ ~ Exponential(1)
    α ~ filldist(Normal(0, 0.5), length(unique(clade_id)))
    μ = α[clade_id]
    K ~ MvNormal(μ, σ)
end

q5_9 = quap(clade(d.clade_id, d.K))
post = DataFrame(rand(q5_9.distr, 1000)', q5_9.params)
precis(post)

# TODO: plot

# %% 5.54
d.house = rand(1:4, nrow(d))

@model function clade_house(clade_id, house, K)
    σ ~ Exponential(1)
    a ~ filldist(Normal(0, 0.5), length(unique(clade_id)))
    h ~ filldist(Normal(0, 0.5), length(unique(house)))
    μ = a[clade_id] + h[house]
    K ~ MvNormal(μ, σ)
end

q5_10 = quap(clade_house(d.clade_id, d.house, d.K))
DataFrame(rand(q5_10.distr, 1000)', q5_10.params) |> precis


"""
Contains some functions for calculating cross-correlations, autocorrelations,
and Pearson correlations among rate units in a rate network
"""

using Statistics
import DSP.xcorr

"""
	xcorr_unbiased(x::Vector, y::Vector, maxlag::Int64)

Returns the cross-correlation of two signals normalized by the length of the
available convolution window.

Written by Rainer Engelken (re2365@columbia.edu).
"""
function xcorr_unbiased(x, y, maxlag)
	l = length(x) # we assume same length for x and y
	lags = -maxlag:maxlag
	scale = l .- abs.(lags)
	scale[scale .<= 0] .= 1 # avoid zero division
    xcorr(x, y)[l .+ lags] ./ scale
end

"""
	avg_autocorrs(A::Matrix{T}, maxlag::Int64, wait::Int64=0) where T <: AbstractFloat

Returns the average autocorrelation for the activity of each unit described in
the `N x NT` matrix `A`.

Note the autocorrelations are not mean-subtracted. The `maxlag` parameter
gives the maximum shift of the signal, and the `wait` parameter gives the number
of timesteps in the beginning to disregard.
"""
function avg_autocorrs(A::Matrix{T}, maxlag::Int64, wait::Int64=0) where T <: AbstractFloat

	(N, NT) = size(A)

	unit_autocorrs = mapslices(view(A, :, (wait + 1):NT), dims=2) do x
		return xcorr_unbiased(x, x, maxlag)
	end

	return vec(mean(unit_autocorrs, dims=1))
end


@doc raw"""
    Moments <: AbstractImageBinarizationAlgorithm
    Moments()

    binarize([T,] img, f::Moments)
    binarize!([out,] img, f::Moments)

The following rule determines the binarization threshold:  if one assigns all
observations below the threshold to a value z₀ and all observations above the
threshold to a value z₁, then the first three moments of the original histogram
must match the moments of this specially constructed bilevel histogram.

# Output

Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If
`T` is not specified, it is inferred from `out` and `img`.

# Details

Let ``f_i`` ``(i=1 \ldots I)`` denote the number of observations in the
``i``th bin of the histogram and ``z_i`` ``(i=1 \ldots I)`` the observed value
associated with the ``i``th bin.  Then the probability that an observation ``z_i``
belongs to the ``i``th bin is given by  ``p_i = \frac{f_i}{N}`` (``i = 1,
\ldots, I``), where ``N = \sum_{i=1}^{I}f_i``.

Moments can be computed from the histogram ``f`` in the following way:

```math
m_k = \frac{1}{N} \sum_i p_i (z_i)^k \quad k = 0,1,2,3, \ldots.
```
The principle of moment-preserving thresholding is to select a threshold value,
as well as two representative values ``z_0`` and ``z_1`` (``z_0 < z_1``),
such that if all below-threshold values in ``f`` are replaced by ``z_0`` and
all above-threshold values are replaced by ``z_1``, then this specially constructed
bilevel histogram ``g`` will have the same first three moments as ``f``.

Concretely, let ``q_0`` and ``q_1`` denote the fractions of observations below
and above the threshold in ``f``, respectively. The constraint that the first
three moments in ``g`` must equal the first three moments in ``f`` can be
expressed by the following system of four equations

```math
\begin{aligned}
   q_0 (z_0)^0 + q_1 (z_1)^0   & = m_0 \\
   q_0 (z_0)^1 + q_1 (z_1)^1   & = m_1 \\
   q_0 (z_0)^2 + q_1 (z_1)^2   & = m_2 \\
   q_0 (z_0)^3 + q_1 (z_1)^3   & = m_3 \\
\end{aligned}
```
where the left-hand side represents the moments of ``g`` and the right-hand side
represents the moments of ``f``. To find the desired treshold value, one first solves
the four equations to obtain ``q_0`` and ``q_1``, and then chooses the threshold
``t`` such that ``q_0 = \sum_{z_i \le t} p_i``.

# Arguments

The function argument is described in more detail below.

##  `img::AbstractArray`

The image that needs to be binarized. The image is automatically converted
to `Gray` in order to construct the requisite graylevel histogram.

# Example

Binarize the "cameraman" image in the `TestImages` package.

```julia
using TestImages, ImageBinarization

img = testimage("cameraman")
img_binary = binarize(img, Moments())
```

# Reference

[1] W.-H. Tsai, “Moment-preserving thresolding: A new approach,” Computer Vision, Graphics, and Image Processing, vol. 29, no. 3, pp. 377–393, Mar. 1985. [doi:10.1016/0734-189x(85)90133-1](https://doi.org/10.1016/0734-189x%2885%2990133-1)
"""
struct Moments <: AbstractImageBinarizationAlgorithm end

function (f::Moments)(out::GenericGrayImage, img::GenericGrayImage)
    edges, counts = build_histogram(img,  256)
    t = find_threshold(HistogramThresholding.Moments(), counts[1:end], edges)
    for i in CartesianIndices(img)
        out[i] = img[i] < t ? 0 : 1
    end
    out
end

(f::Moments)(out::GenericGrayImage, img::AbstractArray{<:Color3}) =
    f(out, of_eltype(Gray, img))


#=
An optimization oracle is a linear optimization oracle for the set S, i.e., it solves
    z^\ast(c) :=    min c^Tw
                    s.t. w \in S
The solution of the above problem returned by the oracle is referred to as w^\ast(\cdot) in
the paper

A specific implementation of an optimization oracle should take a single input argument c,
and should return the pair (z^\ast(c), w^\ast(c))

This file contains the code for the assignment problem optimization oracle, which
relies on the Hungarian solver package.
=#

using Hungarian

"""
    assignment_oracle(c)

Optimization oracle for the assignment problem. Uses Hungarian algorithm. Assumes
that the input c is a vector of length d^2 and must be reshaped into a matrix
"""
function assignment_oracle(c::Vector{Float64})
    d_sq = length(c)
    d = isqrt(d_sq)
    C = reshape(c, (d,d))
    assignment, z = hungarian(C)

    W = zeros(d, d)
    for i = 1:d
        W[i, assignment[i]] = 1
    end
    w = vec(W)
    return (z, w)
end

"""
    assignment_hamming_reduce(c)

Utility function that converts c to a cost matrix with 0 on the optimal edges and 1 on all
other possible edges. Therefore, the SPO loss becomes equal to the hamming loss between the
predicted assignment and the optimal one. Returns the new cost vector c associated with
the Hamming loss.
"""
function assignment_hamming_reduce(c::Vector{Float64})
    d_sq = length(c)
    d = isqrt(d_sq)
    C = reshape(c, (d,d))
    assignment, z = hungarian(C)

    new_C = ones(d, d)
    for i = 1:d
        new_C[i, assignment[i]] = 0
    end
    new_c = vec(new_C)
    return new_c
end


using LinearAlgebra, Statistics

function nelder_mead(f, S, ϵ; α=1.0, β=2.0, γ=0.5)
    Δ, y_arr = Inf, f.(S)
    iteractions = 0
    while Δ > ϵ
        p = sortperm(y_arr) # sort lowest to highest
        S, y_arr = S[p], y_arr[p]
        xl, yl = S[1], y_arr[1] # lowest
        xh, yh = S[end], y_arr[end] # highest
        xs, ys = S[end-1], y_arr[end-1] # second-highest
        xm = mean(S[1:end-1]) # centroid
        xr = xm + α*(xm - xh) # reflection point
        yr = f(xr)

        if yr < yl
            xe = xm + β*(xr-xm) # expansion point
            ye = f(xe)
            S[end],y_arr[end] = ye < yr ? (xe, ye) : (xr, yr)
        elseif yr > ys
            if yr ≤ yh
                xh, yh, S[end], y_arr[end] = xr, yr, xr, yr
            end
            xc = xm + γ*(xh - xm) # contraction point
            yc = f(xc)
            if yc > yh
                for i in 2 : length(y_arr)
                    S[i] = (S[i] + xl)/2
                    y_arr[i] = f(S[i])
                end
            else
                S[end], y_arr[end] = xc, yc
            end
        else
            S[end], y_arr[end] = xr, yr
        end

        Δ = std(y_arr, corrected=false)
        iteractions = iteractions + 1
    end
    println("Iteractions: ",iteractions)
    return S,iteractions
end

#valores dos parametros
ka = 9.0
kb = 2.0
La = 10.0
Lb = 10.0

#Caso 1
#F1 = 2.0; F2 = 4.0;

#Caso 2
#F1 = 0.0 ;F2 = 4.0;

#Caso 3
F1 = 0.0; F2 = 400.0;

#tolerancia
tol = 1e-8

#ponto inicial
u0 = [-0.01,0.0]
#u0 = [0.0,0.0]
#numero de pontos iniciais = dimensao do problema + 1
u1 = [u0[1]-0.00001,u0[2]+0.00001]
u2 = [u0[1]+0.00001,u0[2]+0.00001]
u = [u0,u1,u2]

#function
f(u) = 0.5e0 * ka * (sqrt(u[1] ^ 2 + (La - u[2]) ^ 2) - La) ^ 2 + 0.5e0 * kb * (sqrt(u[1] ^ 2 + (Lb + u[2]) ^ 2) - Lb) ^ 2 - F1 * u[1] - F2 * u[2];

#tolerancia
tol = 1e-8

@time u_est,iterac = nelder_mead(f, u, tol)
println("u*: ",mean(u_est))
println("f(u*): ",f(mean(u_est)))


using Revise
using DifferentialEquations
using DynamicalSystems
using Basins
using Plots
using Printf

# Equations of motion:
function forced_pendulum(u, p, t)
    @inbounds begin
    d = p[1]; F = p[2]; omega = p[3]
    du1 = u[2]
    du2 = -d*u[2] - sin(u[1])+ F*cos(omega*t)
    return SVector{2}(du1, du2)
    end
end

# We have to define a callback to wrap the phase in [-π,π]
function affect!(integrator)
    if integrator.u[1] < 0
        integrator.u[1] += 2*π
    else
        integrator.u[1] -= 2*π
    end
end

condition(u,t,integrator) = (integrator.u[1] < -π  || integrator.u[1] > π)

cb = DiscreteCallback(condition,affect!)

#d, F ,w
F = 1.66
ω = 1.
d=0.2
p=[d, F, ω]
#p=[0.15, 0.2, 0.1]
df = ODEProblem(forced_pendulum,rand(2),(0.0,20.0), p)
integ_df  = init(df, alg=AutoTsit5(Rosenbrock23()); reltol=1e-9, save_everystep=false, callback=cb)

xres=200
yres=200

# range for forced pend
xg = range(-pi,pi,length=xres)
yg = range(-2.,4.,length=yres)

# compute basin
@time bsn = Basins.basins_map2D(xg, yg, integ_df; T=2*pi/ω)

# Basin entropy
@show Sb,Sbb = basin_entropy(bsn; eps_x=20, eps_y=20)

# Wada merge Haussdorff distances
@time max_dist,min_dist = detect_wada_merge_method(xg, yg, bsn)
epsilon = xg[2]-xg[1]
@show dmax = max_dist/epsilon
@show dmin = min_dist/epsilon

# Wada grid
W = detect_wada_grid_method(integ_df, bsn; max_iter=8)
@show W[:,end]

# Uncertainty exponent for these parameter and grid
bd = box_counting_dim(xg, yg, bsn)
α = 2 - bd

D = uncertainty_exponent(bsn, integ_df)
@show 2-D

println("---------------")
println("---------------")
println("Basin Report: ")
println("---------------")
println("---------------")

@printf("Basin entropy %.2f \n", Sb)
@printf("Boundary Basin Entropy: %.2f\n", Sbb)
@printf("Uncertainty exponent: α= %.2f\n", α )
@printf("Box counting dim: bd= %.2f\n", bd)
@printf("Uncertainty dim estimator: d = %.2f\n", 2-D[1])
@printf("Number of basins: %d\n", bsn.Na)
@printf("Merge Method: Max fattening parameter: %.2f\n", dmax)
@printf("Wada Grid Method: W_Na = %.2f\n ", W[end,end] )

plot(xg, yg, bsn.basin', seriestype=:heatmap)


"""
        cocoso(decisionMat, weights, fns, lambda)

Apply CoCoSo (Combined Compromise Solution) method for a given matrix and weights.

# Arguments:
 - `decisionMat::DataFrame`: n × m matrix of objective values for n alternatives and m criteria
 - `weights::Array{Float64, 1}`: m-vector of weights that sum up to 1.0. If the sum of weights is not 1.0, it is automatically normalized.
 - `fns::Array{Function, 1}`: m-vector of functions to be applied on the columns.
 - `lambda::Float64`: joint criterion. 0<=lambda<=1, default=0.5.

# Description
cocoso() applies the CoCoSo method to rank n alterntives subject to m criteria which are supposed to be
either maximized or minimized.

# Output
- `::CoCoSoResult`: CoCoSoResult object that holds multiple outputs including scores, rankings, and best index.

# Examples
```julia-repl
julia> decmat = [3        12.5        2        120        14        3;
       5        15        3        110        38        4;
       3        13        2        120        19        3;
       4        14        2        100        31        4;
       3        15        1.5        125        40        4]
5×6 Array{Float64,2}:
 3.0  12.5  2.0  120.0  14.0  3.0
 5.0  15.0  3.0  110.0  38.0  4.0
 3.0  13.0  2.0  120.0  19.0  3.0
 4.0  14.0  2.0  100.0  31.0  4.0
 3.0  15.0  1.5  125.0  40.0  4.0

julia> df = makeDecisionMatrix(decmat)
5×6 DataFrame
 Row │ Crt1     Crt2     Crt3     Crt4     Crt5     Crt6
     │ Float64  Float64  Float64  Float64  Float64  Float64
─────┼──────────────────────────────────────────────────────
   1 │     3.0     12.5      2.0    120.0     14.0      3.0
   2 │     5.0     15.0      3.0    110.0     38.0      4.0
   3 │     3.0     13.0      2.0    120.0     19.0      3.0
   4 │     4.0     14.0      2.0    100.0     31.0      4.0
   5 │     3.0     15.0      1.5    125.0     40.0      4.0

julia> weights = [0.221, 0.159, 0.175, 0.127, 0.117, 0.201];

julia> fns = [maximum, minimum, minimum, maximum, minimum, maximum];

julia> lambda = 0.5;

julia> result = cocoso(df, weights, fns, lambda);

julia> result.scores
7-element Array{Float64,1}:
 2.0413128390265998
 2.787989783418825
 2.8823497955972495
 2.4160457689259287
 1.2986918936013303
 1.4431429073391682
 2.519094173200623

julia> result.bestIndex
3
```
# References

Yazdani, M., Zarate, P., Kazimieras Zavadskas, E. and Turskis, Z. (2019), "A combined compromise solution (CoCoSo) method for multi-criteria decision-making problems", Management Decision, Vol. 57 No. 9, pp. 2501-2519. https://doi.org/10.1108/MD-05-2017-0458

"""
function cocoso(decisionMat::DataFrame, weights::Array{Float64,1}, fns::Array{Function,1}, lambda::Float64=0.5):CoCoSoResult

    row, col = size(decisionMat)
    w = unitize(weights)
    colMax = colmaxs(decisionMat)
    colMin = colmins(decisionMat)

    A = similar(decisionMat)

    for i in 1:row
        for j in 1:col
            if fns[j] == maximum
                @inbounds A[i, j] = (decisionMat[i, j] - colMin[j]) / (colMax[j] - colMin[j])
            elseif fns[j] == minimum
                @inbounds A[i, j] = (colMax[j] - decisionMat[i, j]) / (colMax[j] - colMin[j])
            end
        end
    end

    scoreMat = similar(A)
    for i in 1:col
        scoreMat[:, i] = A[:, i].^w[i]
    end

    P = zeros(Float64, row)
    for i in 1:row
        P[i] = sum(scoreMat[i, :])
    end

    S = w * A |> rowsums

    scoreTable = [S P]

    kA = (S .+ P) ./ sum(scoreTable)

    kB = (S ./ minimum(S)) .+ (P ./ minimum(P))

    kC = ((lambda .* S) .+ ((1-lambda) .* P)) ./ ((lambda .* maximum(S)) .+ ((1-lambda) * maximum(P)))

    scores = (kA .+ kB .+ kC) ./ 3 .+ (kA .* kB .* kC) .^ (1/3)

    rankings = sortperm(scores)

    bestIndex = rankings |> last

    result = CoCoSoResult(
        decisionMat,
        w,
        scores,
        rankings,
        bestIndex
    )

    return result
end


using JuMP, Gurobi, Distributions, StatsFuns

const gurobi_env = Gurobi.Env()

# DIMENSIONS
const numAssets = 1000
const numRandomVariables = numAssets
const numVariables = numAssets

const returnLBD = 1.2 #lower bound on the return

# CASES
# number of samples, replicates for scenario approximations
const NumSamples = ceil.(Int64,logspace(1,5,50))
const NumReplicates = 20

#*============ OTHER MODEL PARAMETERS ================
const mean = Array{Float64}(numAssets)
const stdev = Array{Float64}(numAssets)
for i = 1:numAssets
	mean[i] = 1.05 + 0.3*(numAssets-i)/(numAssets-1)
	stdev[i] = (0.05 + 0.6*(numAssets-i)/(numAssets-1))/3
end
const sigma = zeros(Float64, numAssets, numAssets)
for i = 1:numAssets
	sigma[i,i] = stdev[i]
end
#*===============================================

# FILENAMES
const objFile = "objectiveValue.txt"
const solnFile = "solution.txt"
const riskFile = "riskLevel.txt"
const solnTimeFile = "solutionTime.txt"
const checkingTimeFile = "checkingTime.txt"
const sortingTimeFile = "sortingTime.txt"
const callbackFile = "numCallbacks.txt"
const numConstraintsFile = "numConstraintsEnforced.txt"

# compute negative probability of all constraints being satisfied
# for given scenarios of demands d and a given decision vector x
function computeRiskLevel(y_opt::Array{Float64})

	riskLevel::Float64 = 1.0 - normcdf((sum(mean[i]*y_opt[i] for i = 1:numAssets) - returnLBD)/norm(sigma*y_opt))

	return riskLevel
end

# generate random samples of the demands
function generateRandomSamples(numSamples::Int64)

	xi = Array{Float64}(numAssets,numSamples)
	for i = 1:numAssets
		xi[i,:] = rand(Normal(mean[i],stdev[i]), numSamples)
	end

	return xi
end

# determines if all constraints are satisfied for a given single scenario
# returns one if scenario constraints are all satisfied, zero otherwise
function getScenarioConstraintViolations(x::Array{Float64},xi::Array{Float64},pickFirstSetOfConstraints::Bool,maxNumConstraintsPerIteration::Int64)

	const numSamples = size(xi,2)
	numViolated::Int64 = 0
	constraintViolations = Float64[]
	constraintViolationIndices = Int64[]
	constraintViolationTolerance::Float64 = 1E-06

	for iter = 1:numSamples
		con::Float64 = returnLBD - xi[:,iter]'*x

		if(con >= constraintViolationTolerance)
			numViolated += 1
			push!(constraintViolations,con)
			push!(constraintViolationIndices,iter)
			if(pickFirstSetOfConstraints && numViolated == maxNumConstraintsPerIteration)
				break
			end
		end
	end

	return numViolated, constraintViolations, constraintViolationIndices
end

# write solution to file
function initializeFiles(details_file::String)

	writeMode::String = "w"

	open(details_file, writeMode) do f
		write(f,"Model: $modelName \n")
		write(f,"numAssets: $numAssets \n")
		write(f,"returnLBD: $returnLBD \n")
		write(f,"NumSamples: $NumSamples \n")
		write(f,"NumReplicates: $NumReplicates \n")
		write(f,"Method: Scenario approximations \n")
	end

end

# write solution to file
function writeSolutionToFile(dirName::String, status::Symbol, objvar_opt::Float64, x_opt::Array{Float64}, riskLevel::Float64, solutionTime::Float64, checkingTime::Float64, sortingTime::Float64, numCallbacks::Int64, numConstraintsAdded::Int64)

	if(status == Symbol("Optimal"))
		writeMode::String = "a"

		obj_file::String = dirName * objFile
		open(obj_file, writeMode) do f
			write(f,"$objvar_opt \n")
		end

		solution_file::String = dirName * solnFile
		open(solution_file, writeMode) do f
			for i = 1:numVariables
				write(f,"$(x_opt[i]) ")
			end
			write(f,"\n")
		end

		risk_file::String = dirName * riskFile
		open(risk_file, writeMode) do f
			write(f,"$riskLevel \n")
		end

		solntime_file::String = dirName * solnTimeFile
		open(solntime_file, writeMode) do f
			write(f,"$solutionTime \n")
		end

		checktime_file::String = dirName * checkingTimeFile
		open(checktime_file, writeMode) do f
			write(f,"$checkingTime \n")
		end

		sorttime_file::String = dirName * sortingTimeFile
		open(sorttime_file, writeMode) do f
			write(f,"$sortingTime \n")
		end

		callback_file::String = dirName * callbackFile
		open(callback_file, writeMode) do f
			write(f,"$numCallbacks \n")
		end

		constraints_file::String = dirName * numConstraintsFile
		open(constraints_file, writeMode) do f
			write(f,"$numConstraintsAdded \n")
		end
	end

end

# solve the scenario approximation model
function solveScenarioApproximationModel(numScenarios::Int64,dirName::String)

	tic()

	# sample the random variables
	xi = generateRandomSamples(numScenarios)

	maxNumConstraintsPerIteration::Int64 = 100000
	numConstraintsAdded::Int64 = 0

	pickFirstSetOfConstraints::Bool = false

	sortingTime::Float64 = 0.0
	checkingTime::Float64 = 0.0
	numCallbacks::Int64 = 0

	objvar_opt::Float64 = Inf
	y_opt = zeros(Float64,numVariables)
	status::Symbol = Symbol("Error")

	numViolations::Int64 = numScenarios
	constraintIndices = Int64[]

	while(numViolations > 0)

		numCallbacks += 1
		numViolations = 0

		numConstraintsConsidered::Int64 = size(constraintIndices)[1]

		# construct the scenario model
		mod = Model(solver=GurobiSolver(gurobi_env,Presolve=0,OutputFlag=0))

		@variable(mod, y[1:numAssets] >= 0)

		@objective(mod, Min, sum(abs2(50.0*sigma[i,i])*y[i]*y[i] for i = 1:numAssets))
		@constraint(mod, sum(y[i] for i = 1:numAssets) == 1)
		@constraint(mod, [scen=1:numConstraintsConsidered], sum(xi[i,scen]*y[i] for i = 1:numAssets) >= returnLBD)

		status = solve(mod)
		y_opt = getvalue(y)
		objvar_opt = sum(abs2(sigma[i,i]*y_opt[i]) for i = 1:numAssets)

		numViolatedConstraints::Int64 = 0
		constraintViolations = Float64[]
		constraintViolationIndices = Int64[]

		tic()

		numViolatedConstraints, constraintViolations, constraintViolationIndices = getScenarioConstraintViolations(y_opt,xi,pickFirstSetOfConstraints,maxNumConstraintsPerIteration)

		checkingTime += toq()

		numConstraintsEnforced::Int64 = min(size(constraintViolations,1),maxNumConstraintsPerIteration)

		tic()
		relevantIndices = 1:1:numConstraintsEnforced
		if(!pickFirstSetOfConstraints)
			relevantIndices = sortperm(constraintViolations, rev=true)
		end
		sortingTime += toq()

		for iter = 1:numConstraintsEnforced
			push!(constraintIndices,constraintViolationIndices[relevantIndices[iter]])
		end

		numConstraintsAdded += numConstraintsEnforced

		numViolations += numViolatedConstraints

	end

	riskLevel::Float64 = computeRiskLevel(y_opt)

	solutionTime::Float64 = toq()

	@printf "  Objective: %1.5f,  Risklevel: %.5f,  Time: %4.2f,  NumCallbacks: %d,  NumConstraints: %d \n" objvar_opt riskLevel solutionTime numCallbacks numConstraintsAdded

	writeSolutionToFile(dirName, status, objvar_opt, y_opt, riskLevel, solutionTime, checkingTime, sortingTime, numCallbacks, numConstraintsAdded)

	return status, objvar_opt, y_opt, riskLevel, solutionTime, checkingTime, sortingTime, numCallbacks, numConstraintsAdded
end


module jump

    using JuMP
    using Ipopt
    using Distributions
    srand(12345)
    normal = Normal(0,0.01)

    function run()

        # create a model
        m = Model(solver=IpoptSolver())

        # define constants (N, price, etc)
        N = 100
        price = collect(linspace(0.05,0.95,N))
        beta0 = 0.1
        demand0 = (1.0-price) / (2*beta0)
        demand = demand0 - rand(normal,N)

        #define JuMP variables
        @variable(m,eps[1:N])
        @variable(m,0 <= beta <= 1)

        # define constraints and objective
        @objective(m,Min,sum(eps[i]^2 for i in 1:N))
        @constraint(m,constr[i=1:N], 1.0 - 2*beta*(demand[i]-eps[i])-price[i] == 0)

        # solve
        status = solve(m)
        Dict(:obj=>getobjectivevalue(m),:beta=>getvalue(beta),:eps=>getvalue(eps))
    end
end

module nlopt

    using Distributions
    using NLopt
    srand(12345)
    normal = Normal(0,0.01)
    N = 100
    price = collect(linspace(0.05,0.95,N))
    beta0 = 0.1
    demand0 = (1.0-price) / (2*beta0)
    demand = demand0 - rand(normal,N)

    # objective function
    function obj(x,g)
        # x = [e_1,...,e_N,beta] i.e. (N+1,1)

        # gradient = [grad w.r.t e_1,
        #            ...,grad w.r.t e_N,
        #                grad w.r.t beta] i.e. (N+1,1)
        if length(g)>0
           g[:] = vcat(2.0*x[1:(end-1)],0.0)
        end
        # value of objective
        r = sum(x[1:(end-1)].^2)
        return r
    end

    # function test_constr()
    #     x = rand(N+1)
    #     my_c = zeros(N+1)
    #     my_g = zeros(N+1,N)
    #     constr(my_c,x,my_g,N,demand,price);
    #     @test all(my_g .== Calculus.hessian()
    # end

    #
    function constr(r::Vector,x::Vector,g::Matrix,n,q,p)
        if length(g) > 0
            # g has to be n by m for nlopt
            g[:,:] = cat(1,diagm(2*x[end]*ones(n)),-2*(q'.-x[1:(end-1)]'))
        end
        # value of contraints
        r[:] = 1.0 - 2*x[end].*(q.-x[1:(end-1)]) .- p
    end
    constr_clos(r::Vector,x::Vector,g::Matrix) = constr(r::Vector,x::Vector,g::Matrix,N,demand,price)

    function run()
        opt = Opt(:LD_SLSQP,N+1)
        lower_bounds!(opt,[[-Inf for i in 1:N]...,0.0])
        upper_bounds!(opt,[[Inf for i in 1:N]...,1.0])
        min_objective!(opt,obj)
        equality_constraint!(opt,constr_clos,[1e-10 for i in 1:N])
        xtol_rel!(opt,1e-4)
        ftol_rel!(opt,1e-6)

        res = optimize(opt, vcat(rand(normal,N),0.9))
        println("beta0 = $beta0")
        println("beta  = $(res[2][end])")
        r = zeros(N)
        g =zeros(N+1,N)
        constr(r,res[2],g,N,demand,price);
        println("maximal error of constraint at solution = $(maxabs(r))")
        return res
    end

end # module


export Gaussian_Peak

@doc raw"""
Gaussian peak

```math
p(x; θ = [h,μ,σ]) = h e^{-\frac{1}{2} \left(\frac{x-μ}{σ} \right)^2}
```
"""
struct Gaussian_Peak <: Abstract_Model2Fit_Peak
end

# Visit  ================
#
visit_submodel_size(model::Gaussian_Peak) = 0

# Interface  ================
#
parameter_size(::Gaussian_Peak) = 3

function accumulate_y!(m::Gaussian_Peak,Y::AbstractVector,X::AbstractVector,θ::AbstractVector)
    @assert length(θ) == parameter_size(m)
    @assert length(Y) == length(X)

    h=θ[1]
    μ=θ[2]
    σ=θ[3]

    @. Y += h*exp(-((X-μ)/σ)^2/2)

    Y
end

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

using StaticArrays: @SVector, SVector

struct Const_μ_Gaussian_Peak{μ_T <: Real} <: Abstract_Model2Fit_Peak
    _μ::μ_T
end

# Visit  ================
#
visit_submodel_size(model::Const_μ_Gaussian_Peak) = 0

# Interface  ================
#
parameter_size(::Const_μ_Gaussian_Peak) = 2

function accumulate_y!(m::Const_μ_Gaussian_Peak,Y::AbstractVector,X::AbstractVector,θ::AbstractVector{T}) where {T}
    @assert length(θ) == parameter_size(m)
    @assert length(Y) == length(X)

    h=θ[1]
    μ=m._μ
    σ=θ[2]

    θ̂ = @SVector T[h,μ,σ]

    accumulate_y!(Gaussian_Peak(),Y,X,θ̂)
end


# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors    #src
# This Source Code Form is subject to the terms of the Mozilla Public License   #src
# v.2.0. If a copy of the MPL was not distributed with this file, You can       #src
# obtain one at https://mozilla.org/MPL/2.0/.                                   #src

# # The correlation problem

# Given three random variables A, B, C and given bounds on two of the three
# correlation coefficients:
#
#     -0.2 <= ρ_AB <= -0.1
#     0.4 <= ρ_BC <=  0.5
#
# We can use the following property of the correlations to determine bounds on
# ρ_AC by solving a SDP:
#
#     |  1    ρ_AB  ρ_AC |
#     | ρ_AB   1    ρ_BC |  ≽ 0
#     | ρ_AC  ρ_BC   1   |

using JuMP
import SCS
import Test  #src

function example_corr_sdp()
    model = Model(SCS.Optimizer)
    set_silent(model)
    @variable(model, X[1:3, 1:3], PSD)
    ## Diagonal is 1s
    @constraint(model, X[1, 1] == 1)
    @constraint(model, X[2, 2] == 1)
    @constraint(model, X[3, 3] == 1)
    ## Bounds on the known correlations
    @constraint(model, X[1, 2] >= -0.2)
    @constraint(model, X[1, 2] <= -0.1)
    @constraint(model, X[2, 3] >=  0.4)
    @constraint(model, X[2, 3] <=  0.5)
    ## Find upper bound
    @objective(model, Max, X[1, 3])
    optimize!(model)
    println("An upper bound for X[1, 3] is $(value(X[1, 3]))")
    Test.@test value(X[1, 3]) ≈ 0.87195 atol = 1e-4  #src
    ## Find lower bound
    @objective(model, Min, X[1, 3])
    optimize!(model)
    println("A lower bound for X[1, 3] is $(value(X[1, 3]))")
    Test.@test value(X[1, 3]) ≈ -0.978 atol = 1e-3  #src
    return
end

example_corr_sdp()


struct Domain{T<:Real,P<:AbstractFloat,O<:Signed} <: TwoDim
    L       :: T
    N       :: O
    dx      :: P
    dy      :: P
    x       :: Vector
    y       :: Vector
    tspan   :: P
    n       :: O
    dt      :: P
    t       :: Vector
    v       :: P
    Δr      :: P
    rings1D :: O
    rings2D :: O

    # constructor
    function Domain{T,P,O}(L::T,N::O,tspan::P,n::O,v::P) where {T<:Real,P<:AbstractFloat,O<:Signed}
        # Space discretisation
        x_inf = -L/2 # Inferior lim in x
        x_sup = L/2  # Superior lim in x
        dx = (x_sup-x_inf)/N # x step
        dy = dx    # y step

        # Mesh grid
        x = collect(x_inf:dx:x_sup-dx)
        y = x

        # Time discretisation
        dt = tspan/n             # Time step
        t  = collect(0:dt:tspan) # Time discretized

        # Delay computations
        τ_max_1D = L/(2*v)              # Maximum delay 1D domain
        τ_max_2D = L/(sqrt(2)*v)        # Maximum delay 2D domain
        Δr    = max(1.0,(v*dt)/dx)      # Compute rings width
        rings1D = 1+floor(O,τ_max_1D/dt) # Number of delay rings (u_max)
        rings2D = 1+floor(O,τ_max_2D/dt) # Number of delay rings (u_max)

        new(L,N,dx,dy,x,y,tspan,n,dt,t,v,Δr,rings1D,rings2D)
    end
end


demand = [12, 14, 16, 18, 20, 22, 24, 26, 28, 30] # Demand of newspapers in each scenario
lengthS = length(demand)
vec_prob = [0.05, 0.10, 0.10, 0.10, 0.15, 0.15, 0.10, 0.10, 0.10, 0.05 ] # probability of scenario
c = 20 # purchase price
p = 70 # selling price
h = 10 # scrap value
y = 20
# 1. Solved by L-Shaped Benders Algorithm ------------------------------------------------------------------------------
n_x = 10
n_y = 1
vec_min_y = hcat([0])
vec_max_y = hcat([30])
vec_f = hcat(c - 10)
vec_pi = hcat(vec_prob)
mat_c = zeros(10, 1, 1)
for i = 1:10
    mat_c[i, :, :] = hcat([- 70 + 10])
end
mat_h = zeros(10, 2, 1)
for i = 1: 10
    mat_h[i, 1, :] = hcat(- demand[i])
    mat_h[i, 2, :] = hcat(0)
end
mat3_t = zeros(10, 2, 1)
for i = 1:10
    mat3_t[i, 1, :] = hcat(0)
    mat3_t[i, 2, :] = hcat(1)
end
mat3_w = - ones(10, 2, 1)
epsilon = 0.001
timesIterationMax = 500


using Plots

function khayyam_triangle(lentingle)
    output = []
    for i in 1:lentingle
        push!(output, [])
        for j in 1:i
            if j == 1
                append!(output[i], 1)
            elseif j == i
                append!(output[i], 1)
            else j != 1 && j != i
                add = output[i - 1][j] + output[i - 1][j - 1]
                append!(output[i], add)
            end
        end
    end
    return output
end

order = 2^11
triangle = khayyam_triangle(order)
x = []
y = []
for i in 1:order
    for j in 1:i
        if triangle[i][j]%2 != 0
            push!(x, j - i/2)
            push!(y, -i)
        end
    end
end
scatter(x,y,markersize = 0.000001, legend = false, border=:none, dpi=300)
savefig("C:\\Users\\Yaghoub\\Documents\\GitHub\\A-few-fractals-in-Julia\\Fractals-Fig\\SKT.png")


using PyPlot

# TeX labels
#rc('font',**{'family':'sans-serif','sans-serif':['Helvetica'],'size':6})
#rc('text', usetex=True)
#rc('xtick',labelsize=6)

g1=3.
g2=2.
g3=1.
eps=1.e-3

# calculations for plotting the POTATO stats here

function rmin(u)
    "minimal radius from zeros of the determinant of the Hessian of classical energy"
    u1=u[1]
    u2=u[2]
    u3=u[3]
    s2=g1 * u1^2 + g2 * u2^2 + g3 * u3^2;
    s1=-(g1*g2+g1*g3+g2*g3) + g1*g2*u3^2 + g1*g3*u2^2 + g2*g3*u1^2;
    s0=g1*g2*g3;
    discr=s1^2-4*s0*s2
    if discr > 0
        q0 = (-s1+(s1^2-4*s0*s2)^0.5)/2/s2
        q1 = (-s1-(s1^2-4*s0*s2)^0.5)/2/s2
        qq=min(q0,q1)
        ret=1/qq
        if  ret > 1/g2-eps && ret < 1/g3+eps
            return ret;
        end
    end
end

function phi1(X)
    theta=X[1]
    phi=X[2]
    u1=sin(theta)*cos(phi)
    u2=sin(theta)*sin(phi)
    u3=cos(theta)
    r=rmin([u1,u2,u3])
    return (r-1/g1)*u1;
end

function phi2(X)
    theta=X[1]
    phi=X[2]
    u1=sin(theta)*cos(phi)
    u2=sin(theta)*sin(phi)
    u3=cos(theta)
    r=rmin([u1,u2,u3])
    return (r-1/g2)*u2;
end

function phi3(X)
    theta=X[1]
    phi=X[2]
    u1=sin(theta)*cos(phi)
    u2=sin(theta)*sin(phi)
    u3=cos(theta)
    r=rmin([u1,u2,u3])
    return (r-1/g3)*u3;
end

# save data for potato in an array
NN=100
ths=linspace(0,pi/2,NN)
phs=linspace(0,2*pi,NN)

pp=Iterators.product(ths,phs)
P1=map(phi1,pp)

pp=Iterators.product(ths,phs)
P2=map(phi2,pp)

pp=Iterators.product(ths,phs)
P3=map(phi3,pp)

P1=reshape(P1,(NN,NN))
P2=reshape(P2,(NN,NN))
P3=reshape(P3,(NN,NN))

# calculate disc region as a surface
ph1d(X)=(1/g3-1/g1)*X[1]*cos(X[2]);
ph2d(X)=(1/g3-1/g2)*X[1]*sin(X[2]);
ph3d(X)=0;

rs=linspace(0,1,NN)
ps=linspace(0,2*pi,NN)

pp=Iterators.product(rs,ps)
P1a=map(ph1d,pp)

pp=Iterators.product(rs,ps)
P2a=map(ph2d,pp)

pp=Iterators.product(rs,ps)
P3a=map(ph3d,pp)

P1a=reshape(P1a,(NN,NN))
P2a=reshape(P2a,(NN,NN))
P3a=reshape(P3a,(NN,NN))

# Plotting starts here

fig2 = figure()
fig2[:set_size_inches]([3.487,2*3.487/1.618])
#plt.tick_params(labelsize=6)

#ax = fig2[:add_subplot](111),# projection="3d")
#ax[:set_xlabel]("phi_1^r")
#ax[:set_ylabel]("phi_2^r")
#ax[:set_zlabel]("phi_3^r")

# plot disc region
#plot_surface(P1a,P2a,P3a,alpha=1,color="r")
#ax[:plot_surface](P1a, P2a, P3a,alpha=1,color="r")
# plot potato
#ax.plot_wireframe(P1, P2, P3)
#ax.plot_wireframe(P1l, P2l, P3l)

#ax.plot_surface(P1l, P2l, P3l,rstride=1,cstride=1,color="w")
plot_surface(P1, P2, P3,rstride=4,cstride=2,color="w",alpha=0.8)

ax = gca()
ax[:set_xlim](1/g1-1/g3,1/g3-1/g1)
ax[:set_ylim](1/g2-1/g3,1/g3-1/g2)
ax[:set_zlim](1/g2-1/g3,1/g3-1/g2)

#ax[:view_init](100,30)

savefig("fig2bot.svg")


# skip the first prime (2) and try only odd numbers. 10000. item is now 10001. item :)

function buildPrimes(count)
    primes = Array(Int64, count)
    slot = 1

    function check(n)
        i = 1
        while i < slot
            if n%primes[i] == 0
                return false
            end
            i += 1
        end
        return true
    end

    n = 3
    while slot <= count
        if check(n)
            primes[slot] = n
            slot += 1
        end
        n += 2
    end

    return primes
end

println(buildPrimes(10000)[end])


"""
    bbands(x::Array{Float64}; n::Int64=10, sigma::Float64=2.0)::Matrix{Float64}

Bollinger bands (moving average with standard deviation bands)

*Output*
- Column 1: lower band
- Column 2: middle band
- Column 3: upper band
"""
function bbands(x::Array{Float64}; n::Int64=10, sigma::Float64=2.0, ma::Function=sma, args...)::Matrix{Float64}
    @assert n<size(x,1) && n>0 "Argument n is out of bounds."
    out = zeros(size(x,1), 3)  # cols := lower bound, ma, upper bound
    out[:,2] = ma(x, n=n, args...)
    sd = runsd(x, n=n, cumulative=false)
    out[:,1] = out[:,2] - sigma*sd
    out[:,3] = out[:,2] + sigma*sd
    return out
end

"""
    tr(hlc::Matrix{Float64})::Array{Float64}

True range
"""
function tr(hlc::Matrix{Float64})::Array{Float64}
    @assert size(hlc,2) == 3 "HLC array must have 3 columns."
    n = size(hlc,1)
    out = zeros(n)
    out[1] = NaN
    @inbounds for i=2:n
        out[i] = max(hlc[i,1]-hlc[i,2], hlc[i,1]-hlc[i-1,3], hlc[i-1,3]-hlc[i,2])
    end
    return out[:,1]
end

"""
    atr(hlc::Matrix{Float64}; n::Int64=14)::Array{Float64}

Average true range (uses exponential moving average)
"""
function atr(hlc::Matrix{Float64}; n::Int64=14, ma::Function=ema)::Array{Float64}
    @assert n<size(hlc,1) && n>0 "Argument n out of bounds."
    return [NaN; ma(tr(hlc)[2:end], n=n)]
end

"""
    keltner(hlc::Matrix{Float64}; nema::Int64=20, natr::Int64=10, mult::Int64=2)::Matrix{Float64}

Keltner bands

*Output*
Column 1: lower band
Column 2: middle band
Column 3: upper band
"""
function keltner(hlc::Array{Float64,2}; nema::Int64=20, natr::Int64=10, mult::Int64=2)::Matrix{Float64}
    @assert size(hlc,2) == 3 "HLC array must have 3 columns."
    out = zeros(size(hlc,1), 3)
    out[:,2] = ema(hlc[:,3], n=nema)
    out[:,1] = out[:,2] - mult*atr(hlc, n=natr)
    out[:,3] = out[:,2] + mult*atr(hlc, n=natr)
    return out
end


@doc raw"""
    gausshermite(n::Integer) -> x, w  # nodes, weights

Return nodes `x` and weights `w` of [Gauss-Hermite quadrature](https://en.wikipedia.org/wiki/Gauss%E2%80%93Hermite_quadrature).

```math
\int_{-\infty}^{+\infty} f(x) \exp(-x^2) dx \approx \sum_{i=1}^{n} w_i f(x_i)
```

# Examples
```jldoctest
julia> x, w = gausshermite(3);

julia> f(x) = x^4;

julia> I = dot(w, f.(x));

julia> I ≈ 3(√π)/4
true
```
"""
function gausshermite(n::Integer)
    x,w = unweightedgausshermite(n)
    w .*= exp.(-x.^2)
    x, w
end

function unweightedgausshermite(n::Integer)
    # GAUSSHERMITE(n) COMPUTE THE GAUSS-HERMITE NODES AND WEIGHTS IN O(n) time.
    if n < 0
        throw(DomainError(n, "Input n must be a non-negative integer"))
    elseif n == 0
        return Float64[],Float64[]
    elseif n == 1
        return [0.0],[sqrt(π)]
    elseif n ≤ 20
       # GW algorithm
       x = hermite_gw(n)
    elseif n ≤ 200
       # REC algorithm
       x = hermite_rec(n)
    else
       # ASY algorithm
       x = hermite_asy(n)
    end

    # fold out
    if isodd(n)
        _w = [reverse(x[2][:]); x[2][2:end]]
        _x = [-reverse(x[1]) ; x[1][2:end]]
    else
        _w = [reverse(x[2][:]); x[2][:]]
        _x = [-reverse(x[1]) ; x[1]]
    end
    _w .*= sqrt(π)/sum(exp.(-_x.^2).*_w)

    return _x, _w
end

function hermite_asy(n::Integer)
    # Compute Hermite nodes and weights using asymptotic formula

    x0 = hermite_initialguess(n)  # get initial guesses
    t0 = x0./sqrt(2n+1)
    θ = acos.(t0)  # convert to θ-variable
    val = x0
    for _ in 1:20
        val = hermpoly_asy_airy(n, θ)
        dθ = -val[1]./(sqrt(2).*sqrt(2n+1).*val[2].*sin.(θ))
        θ .-= dθ  # Newton update
        if norm(dθ,Inf) < sqrt(eps(Float64))/10
           break
        end
    end
    t0 = cos.(θ)
    x = sqrt(2n+1)*t0  #back to x-variable
    w = x.*val[1] .+ sqrt(2).*val[2]
    w .= 1 ./ w.^2  # quadrature weights

    return x, w
end

function hermite_rec(n::Integer)
    # Compute Hermite nodes and weights using recurrence relation.

    x0 = hermite_initialguess(n)
    x0 .*= sqrt(2)
    val = x0
    for _ in 1:10
        val = hermpoly_rec.(n, x0)
        dx = first.(val)./last.(val)
        dx[ isnan.( dx ) ] .= 0
        x0 .= x0 .- dx
        if norm(dx, Inf) < sqrt(eps(Float64))
            break
        end
    end
    x0 ./= sqrt(2)
    w = 1 ./ last.(val).^2  # quadrature weights

    return x0, w
end

function hermpoly_rec(n::Integer, x0)
    # HERMPOLY_rec evaluation of scaled Hermite poly using recurrence
    n < 0 && throw(DomainError(n, "Input n must be a non-negative integer"))
    # evaluate:
    w = exp(-x0^2 / (4*n))
    wc = 0 # 0 times we've applied wc
    Hold = one(x0)
    # n == 0 && return (Hold, 0)
    H = x0
    for k = 1:n-1
        Hold, H = H, (x0*H/sqrt(k+1) - Hold/sqrt(1+1/k))
        while abs(H) ≥ 100 && wc < n  # regularise
            H *= w
            Hold *= w
            wc += 1
        end
        k += 1
    end
    for _ = wc+1:n
        H *= w
        Hold *= w
    end

    return H, -x0*H + sqrt(n)*Hold
end

function hermpoly_rec(r::Base.OneTo, x0)
    isempty(r) && return [1.0]
    n = maximum(r)
    # HERMPOLY_rec evaluation of scaled Hermite poly using recurrence
    n < 0 && throw(DomainError(n, "Input n must be a non-negative integer"))
    n == 0 && return [exp(-x0^2 / 4)]
    p = max(1,floor(Int,x0^2/100))
    w = exp(-x0^2 / 4p)
    wc = 0 # 0 times we've applied wc
    ret = Vector{Float64}()
    Hold = one(x0)
    push!(ret, Hold)
    H = x0
    push!(ret, H)
    for k = 1:n-1
        Hold, H = H, (x0*H/sqrt(k+1) - Hold/sqrt(1+1/k))
        while abs(H) ≥ 100 && wc < p  # regularise
            ret .*= w
            H *= w
            Hold *= w
            wc += 1
        end
        push!(ret, H)
        k += 1
    end
    ret .*= w^(p-wc)

    return ret
end

hermpoly_rec(r::AbstractRange, x0) = hermpoly_rec(Base.OneTo(maximum(r)), x0)[r.+1]

function hermpoly_asy_airy(n::Integer, θ::AbstractVector)
    # HERMPOLY_ASY evaluation hermite poly using Airy asymptotic formula in θ-space.

    musq = 2n+1
    cosT = cos.(θ)
    sinT = sin.(θ)
    sin2T = 2 .* cosT.*sinT
    η = 0.5 .* θ .- 0.25 .* sin2T
    χ = -(3*η/2).^(2/3)
    φ = (-χ./sinT.^2).^(1/4)
    C = 2*sqrt(π)*musq^(1/6)*φ
    Airy0 = real.(airyai.(musq.^(2/3).*χ))
    Airy1 = real.(airyaiprime.(musq.^(2/3).*χ))

    # Terms in (12.10.43):
    a0 = 1
    b0 = 1
    a1 = 15/144
    b1 = -7/5*a1
    a2 = 5*7*9*11/2/144^2
    b2 = -13/11*a2
    a3 = 7*9*11*13*15*17/6/144^3
    b3 = -19/17*a3

    # u polynomials in (12.10.9)
    u0 = 1
    u1 = (cosT.^3-6*cosT)/24
    u2 = (-9*cosT.^4 + 249*cosT.^2 .+ 145)/1152
    u3 = (-4042*cosT.^9+18189*cosT.^7-28287*cosT.^5-151995*cosT.^3-259290*cosT)/414720

    # first term
    A0 = 1
    val = A0*Airy0

    # second term
    B0 = -(a0*u1.*φ.^6 .+ a1*u0) ./ χ.^2
    val .+=  B0.*Airy1./musq.^(4/3)

    # third term
    A1 = (b0*u2.*φ.^12 + b1*u1.*φ.^6 .+ b2*u0) ./ χ.^3
    val .+= A1.*Airy0/musq.^2

    # fourth term
    B1 = -(u3.*φ.^18 + a1*u2.*φ.^12 + a2*u1.*φ.^6 .+ a3*u0) ./ χ.^5
    val .+= B1.*Airy1./musq.^(4/3+2)

    val .= C.*val

    ## Derivative
    η = .5*θ - .25*sin2T
    χ = -(3*η/2).^(2/3)
    φ = (-χ./sinT.^2).^(1/4)
    C = sqrt(2*π)*musq^(1/3)./φ

    # v polynomials in (12.10.10)
    v0 = 1
    v1 = (cosT.^3+6cosT)/24
    v2 = (15*cosT.^4 - 327*cosT.^2 .- 143)/1152
    v3 = (259290*cosT + 238425*cosT.^3 - 36387*cosT.^5 + 18189*cosT.^7 - 4042*cosT.^9)/414720

    # first term
    C0 = -(b0*φ.^6 .* v1 .+ b1.*v0)./χ
    dval = C0.*Airy0/musq.^(2/3)

    # second term
    D0 =  a0*v0
    dval = dval + D0*Airy1

    # third term
    C1 = -(v3.*φ.^18 + b1*v2.*φ.^12 + b2*v1.*φ.^6 .+ b3*v0) ./ χ.^4
    dval = dval + C1.*Airy0/musq.^(2/3+2)

    # fourth term
    D1 = (a0*v2.*φ.^12 + a1*v1.*φ.^6 .+ a2*v0) ./ χ.^3
    dval = dval + D1.*Airy1/musq.^2

    dval = C.*dval

    return val, dval
end

function hermite_initialguess(n::Integer)
    # HERMITEINTITIALGUESSES(N), Initial guesses for Hermite zeros.
    #
    # [1] L. Gatteschi, Asymptotics and bounds for the zeros of Laguerre
    # polynomials: a survey, J. Comput. Appl. Math., 144 (2002), pp. 7-27.
    #
    # [2] F. G. Tricomi, Sugli zeri delle funzioni di cui si conosce una
    # rappresentazione asintotica, Ann. Mat. Pura Appl. 26 (1947), pp. 283-300.

    # Error if n < 20 because initial guesses are based on asymptotic expansions:
    @assert n ≥ 20

    # Gatteschi formula involving airy roots [1].
    # These initial guess are good near x = sqrt(n+1/2);
    if isodd(n)
        m = (n-1)>>1
        # bess = (1:m)*π
        a = .5
    else
        m = n>>1
        # bess = ((0:m-1) .+ 0.5)*π
        a = -.5
    end
    ν = 4m + 2a + 2

    T(t) = t^(2/3)*(1+5/48*t^(-2)-5/36*t^(-4)+(77125/82944)*t^(-6) -108056875/6967296*t^(-8)+162375596875/334430208*t^(-10))
    airyrts = -T.(3π/8*(4*(1:m) .- 1))

    airyrts[1:10] .= AIRY_ROOTS[1:10]  # correct first 10.

    x_init = sqrt.(abs.(ν .+ (2^(2/3)).*airyrts.*ν^(1/3) .+ (1/5*2^(4/3)).*airyrts.^2 .* ν^(-1/3) .+
        (11/35-a^2-12/175).*airyrts.^3 ./ ν .+ ((16/1575).*airyrts.+(92/7875).*airyrts.^4).*2^(2/3).*ν^(-5/3) .-
        ((15152/3031875).*airyrts.^5 .+ (1088/121275).*airyrts.^2).*2^(1/3).*ν^(-7/3)))
    x_init_airy = reverse(x_init)

    # Tricomi initial guesses. Equation (2.1) in [1]. Originally in [2].
    # These initial guesses are good near x = 0 . Note: zeros of besselj(+/-.5,x)
    # are integer and half-integer multiples of π.
    # x_init_bess =  bess/sqrt(ν).*sqrt((1+ (bess.^2+2*(a^2-1))/3/ν^2) );
    Tnk0 = fill(π/2,m)
    rhs = ((4m+3) .- 4*(1:m))/ν*π

    for k = 1:7
        val = Tnk0 .- sin.(Tnk0) .- rhs
        dval = 1 .- cos.(Tnk0)
        dTnk0 = val./dval
        Tnk0 = Tnk0 .- dTnk0
    end

    tnk = cos.(Tnk0/2).^2
    x_init_sin = sqrt.(ν*tnk - ((tnk.+1/4)./(tnk.-1).^2 .+ (3a^2-1))/3ν)

    # Patch together
    p = 0.4985+eps(Float64)
    x_init = [x_init_sin[1:convert(Int,floor(p*n))]
    x_init_airy[convert(Int,ceil(p*n)):end]]

    if isodd(n)
        x_init = [0 ; x_init]
        x_init = x_init[1:m+1]
    else
        x_init = x_init[1:m]
    end

    return x_init
end

function hermite_gw(n::Integer)
    # Golub--Welsch algorithm. Used here for n ≤ 20.

    beta = sqrt.((1:n-1)/2)  # 3-term recurrence coeffs
    T = SymTridiagonal(zeros(n), beta)  # Jacobi matrix
    D, V = eigen(T)  # Eigenvalue decomposition
    ind = sortperm(D)  # Hermite points
    x = D[ind]  # nodes
    w = sqrt(π)*V[1,ind].^2  # weights

    # Enforce symmetry:
    i = floor(Int, n/2)+1:n
    x = x[i]
    w = w[i]
    return x, exp.(x.^2).*w
end


"""
    initial_population(individues::Integer, lower_limits::AbstractArray, upper_limits::AbstractArray)
Return a `m*n` matrix which `m` is the number of `individues` and `n` the number of parameters. Each individue (row) has `n` parameters
with upper and lower limits given by `lower_limits, upper_limits` arrays.

# Examples
```julia-repl
julia> initial_population(4,[-1,-5],[1,5])
4×3 Array{Float64,2}:
 -0.997462  -3.461379
  0.056423   4.564237
  0.167943  -4.467215
  0.695743   0.561379
```
"""
function initial_population(individues::Integer, lower_limits::AbstractArray, upper_limits::AbstractArray)

    #Error checks
    if length(lower_limits) != length(upper_limits)
        throw(ErrorException("The limits must have the same length: lower and upper limits of length $(length(lower_limits)) and $(length(upper_limits))."))
    end

    return row_mul( (upper_limits .- lower_limits), rand(individues,length(lower_limits)) ) .+ lower_limits'

end


export shannon_entropy, von_neumann_entropy, joint_entropy, conditional_entropy
export holevo_bound, mutual_information
export success_probability, error_probability

"""
    shannon_entropy( probabilities :: AbstractVector ) :: Float64

The classical entropy of a probability distribution:

```math
S = -\\sum_{i=1}^n p_i \\log_2(p_i)
```

A `DomainError` is thrown if input `probabilities` does not satisfy [`is_probability_distribution`](@ref).
"""
function shannon_entropy( probabilities :: AbstractVector ) :: Float64
    is_probability_distribution(probabilities) || Probabilities(probabilities)

    entropy = -1*sum(map(
        (p) -> isapprox(p, 0, atol=1e-7) ? 0 : p*log2(p),
        probabilities
    ))

    entropy
end

"""
    von_neumann_entropy( ρ :: AbstractMatrix ) :: Float64

The von neumann entropy of a density matrix:

```math
S(\\rho) = - \\sum_j  \\lambda_j \\log_2(\\lambda_j)
```

where ``\\lambda_j`` are the eigenvalues of quantum  state ``\\rho``.

A `DomainError` is thrown if `ρ` does not satisfy [`is_density_matrix`](@ref).
"""
function von_neumann_entropy(ρ :: AbstractMatrix) :: Float64
    is_density_matrix(ρ) || State(ρ)

    λs = Probabilities(eigvals(ρ))
    entropy = shannon_entropy(λs)

    entropy
end

"""
    holevo_bound(
        priors :: AbstractVector,
        ρ_states :: Vector{<:AbstractMatrix}
    ) :: Float64

The Holevo theorem places a bound on the [`mutual_information`](@ref) ``I(X : Y) \\leq \\mathcal{X}``.
It places a limit on the amount information that can be decoded from a set of quantum states.
For a mixed state ``\\rho = \\sum_i p_i \\rho_i`` the Holevo bound ``\\mathcal{X}`` is

```math
\\mathcal{X}  := S(\\rho) - \\sum_i p_i S(\\rho_i)
```

where ``S(\\rho)`` is the [`von_neumann_entropy`](@ref).

A `DomainError` is thrown  if:
* `priors` does not satisfy [`is_probability_distribution`](@ref).
* Any `ρ ∈ ρ_states` does not satisfy [`is_density_matrix`](@ref).
"""
function holevo_bound(priors::AbstractVector, ρ_states::Vector{<:AbstractMatrix}) :: Float64
    is_probability_distribution(priors) || Probabilities(priors)
    all(is_density_matrix.(ρ_states)) || State.(ρ_states)

    ρ = mixed_state(priors, ρ_states)
    ρ_ent = von_neumann_entropy(ρ)

    ρ_states_ent = von_neumann_entropy.(ρ_states)
    ρ_sum_ent = sum( priors .* ρ_states_ent )

    bound = ρ_ent - ρ_sum_ent

    bound
end

"""
    joint_entropy(priors :: AbstractVector, conditionals :: AbstractMatrix) :: Float64

Returns the entropy for the union of pdf ``P(x,y)``. The joint entropy is the [`shannon_entropy`](@ref)
of the joint probability distribution:

```math
S(X,Y) = - \\sum_{x,y} p(x,y) \\log_2(p(x,y))
```
"""
function joint_entropy(priors::AbstractVector, conditionals::AbstractMatrix) :: Float64
    is_probability_distribution(priors) || Probabilities(priors)
    is_conditional_distribution(conditionals) || Conditionals(conditionals)

    joint_probabilities = JointProbabilities(priors, conditionals)
    shannon_entropy(joint_probabilities[:])
end

"""
    conditional_entropy(priors::AbstractVector, conditionals::AbstractMatrix) :: Float64

Returns the conditional entropy for the system with specified `priors` ``p(x)`` and `conditionals` ``p(y|x)``:

```math
S(Y|X) = - \\sum_{x,y} p(x,y)\\log_2\\left(\\frac{p(y|x)}{p(x)}\\right)
```
"""
function conditional_entropy(priors::AbstractVector, conditionals::AbstractMatrix) :: Float64
    is_probability_distribution(priors) || Probabilities(priors)
    is_conditional_distribution(conditionals) || Conditionals(conditionals)

    joint_probabilities = JointProbabilities(priors, conditionals)
    (num_row, num_col) = size(joint_probabilities)

    conditional_entropy = shannon_entropy(Probabilities(joint_probabilities[:])) + sum(sum(map( row -> map( col ->
        joint_probabilities[row,col]*( isapprox(priors[col], 0, atol=1e-7) ? 0 : log2(priors[col]) ),
    1:num_col), 1:num_row)))

    conditional_entropy
end

"""
    mutual_information(
        priors :: AbstractVector,
        conditionals :: AbstractMatrix
    ) :: Float64

The entropy of the overlap between ``p(x)`` and ``p(y)``. The mutual information
is directly computed from the [`shannon_entropy`](@ref) and [`joint_entropy`](@ref):

```math
I(X : Y) = S(X) + S(Y) - S(X,Y)
```
"""
function mutual_information(priors::AbstractVector, conditionals::AbstractMatrix) :: Float64
    is_probability_distribution(priors) || Probabilities(priors)
    is_conditional_distribution(conditionals) || Conditionals(conditional)

    prior_ent = shannon_entropy(priors)

    outcome_probs = outcome_probabilities(priors,conditionals)
    outcome_ent = shannon_entropy( outcome_probs )
    joint_ent = joint_entropy(priors, conditionals)

    mutual_information = prior_ent + outcome_ent - joint_ent

    mutual_information
end

"""
    mutual_information(
        priors :: AbstractVector,
        ρ_states :: Vector{<:AbstractMatrix},
        Π :: AbstractVector
    ) :: Float64

Computes the classical mutual information for a quantum state and measurement
encoding and decoding.
The conditional probabilities are obtained from quantum states and measurements
using [`measure`](@ref).

A `DomainError` is thrown if:
* `priors` does not satisfy [`is_probability_distribution`](@ref).
* Any `ρ ∈ ρ_states` does not satisfy [`is_density_matrix`](@ref).
* `Π` does not satisfy [`is_povm`](@ref).
"""
function mutual_information(
    priors::AbstractVector,
    states::Vector{<:AbstractMatrix},
    Π::AbstractVector{<:AbstractMatrix}
) :: Float64
    is_probability_distribution(priors) || Probabilities(priors)

    if !all(ρ -> ρ isa State, states)
        states = State.(states)
    end
    if !(Π isa POVM)
        Π = POVM(Π)
    end

    conditionals = measure(Π, states)
    mutual_information(priors, conditionals)
end

"""
    success_probability(
        priors::AbstractVector,
        states::Vector{<:AbstractMatrix},
        Π::Vector{<:AbstractMatrix}
    ) :: Float64

The probability of correctly distinguishing quantum states with the specifed POVM:

```math
P_{\\text{Success}} = \\sum_{i=1}^n p_i \\text{Tr}[\\Pi_i \\rho_i].
```

The number of states must match the number POVMs.
"""
function success_probability(priors::AbstractVector,states::Vector{<:AbstractMatrix}, Π::AbstractVector{<:AbstractMatrix}) :: Float64
    is_probability_distribution(priors) || Probabilities(priors)
    all(is_density_matrix.(states)) || State.(states)
    is_povm(Π) || POVM(Π)

    sum(priors .* tr.(states .* Π))
end

"""
    error_probability(
        priors::AbstractVector,
        ρ_states::Vector{<:AbstractMatrix},
        Π::AbstractVector{<:AbstractMatrix}
    ) :: Float64

The probability of incorrectly distinguishing quantum states with the specifed POVM.
This quantity is simply obtained as ``P_{\\text{Error}} = 1 - P_{\\text{Success}}``.
"""
function error_probability(priors::AbstractVector, ρ_states::Vector{<:AbstractMatrix}, Π::AbstractVector{<:AbstractMatrix}) :: Float64
    1 .- success_probability(priors, ρ_states, Π)
end


using LinearAlgebra

function so3ToVec(so3mat)
    if( size(so3mat)!=(3,3) )
        println("!Warning! : Input matrix should be 3x3.")
        return
    end
    if( !((so3mat[1,1]==so3mat[2,2]==so3mat[3,3]==0)) )
        println("!Warning! : Diagonals should be zero.")
        return
    end
    if( so3mat[2,1]!=-so3mat[1,2] ||
        so3mat[3,1]!=-so3mat[1,3] ||
        so3mat[3,2]!=-so3mat[2,3] )
        println("!Warning! : Entered matrix isn't a skew symmetric matrix.")
        return
    end
    ret_vec = [so3mat[3,2];
               so3mat[1,3];
               so3mat[2,1] ]
        return ret_vec
end

function vecToSo3(omg)
    if( size(omg)!=(3,) )
        println("!Warning! : Input matrix should be 3x1.")
        return
    end

    ret_ss_matrix = [0      -omg[3]   omg[2];
                     omg[3]    0     -omg[1];
                    -omg[2]  omg[1]      0];
    return ret_ss_matrix
end

using LinearAlgebra

function so3ToVec(so3mat)
    if( size(so3mat)!=(3,3) )
        println("!Warning! : Input matrix should be 3x3.")
        return
    end
    if( !((so3mat[1,1]==so3mat[2,2]==so3mat[3,3]==0)) )
        println("!Warning! : Diagonals should be zero.")
        return
    end
    if( so3mat[2,1]!=-so3mat[1,2] ||
        so3mat[3,1]!=-so3mat[1,3] ||
        so3mat[3,2]!=-so3mat[2,3] )
        println("!Warning! : Entered matrix isn't a skew symmetric matrix.")
        return
    end
    ret_vec = [so3mat[3,2];
               so3mat[1,3];
               so3mat[2,1] ]
        return ret_vec
end

function VecToso3(omg)
    if( size(omg)!=(3,) )
        println("!Warning! : Input matrix should be 3x1.")
        return
    end

    ret_ss_matrix = [0      -omg[3]   omg[2];
                     omg[3]    0     -omg[1];
                    -omg[2]  omg[1]      0];
    return ret_ss_matrix
end
#****************************************************************#
# VeysiADN 17 Apr 2021
# Advanced Robotics Homework 4 Codes
#****************************************************************#
using LinearAlgebra
const lAlgebra = LinearAlgebra
#****************************************************************#
# Returns normalized version of given matrix/array
function Normalize(N)
    return N/lAlgebra.norm(N)
end
#****************************************************************#
# Returns 4x4 twist matrix ‘se3mat’ (an element of Lie Algebra se3)
# from given twist vector (= 6x1 vector) ‘xi
function VecTose3( xi )
    w_skew = [ 0   -xi[3]   xi[2];
              xi[3]   0    -xi[1];
             -xi[2]  xi[1]    0 ]
    return vcat(hcat(w_skew,xi[4:6]), zeros(1, 4))
end
#****************************************************************#
# Returns twist vector ‘xi’ from given twist matrix ‘se3mat’
function se3ToVec( se3mat )
    omg = [se3mat[3, 2], se3mat[1, 3],se3mat[2, 1]]
    v = se3mat[1:3, 4]
    return vcat(omg,v)
end
#****************************************************************#
# Calculates and return rotation matrix ‘R’ by
# evaluating matrix exponential of 3x3 skew symmetric matrix ‘so3mat’

function MatrixExp3(so3mat)
        omg = [so3mat[3, 2], so3mat[1, 3],so3mat[2, 1]]
        theta = lAlgebra.norm(omg)
        omg_n = so3mat / theta
        return lAlgebra.I + sin(theta) * omg_n + (1 - cos(theta)) * omg_n * omg_n
end

#****************************************************************#
# Calculates and return transformation matrix ‘g’ by
# evaluating matrix exponential of 4x4 twist matrix ‘se3mat’
function MatrixExp6( se3mat )
    omg = [se3mat[3, 2], se3mat[1, 3],se3mat[2, 1]]
    theta = norm(omg)
    if theta==0
        return vcat(hcat(I, se3mat[1:3, 4]), [0 0 0 1])
    else
    omg_n = se3mat[1:3, 1:3] / theta
    temp  = hcat(MatrixExp3(se3mat[1:3, 1:3]),
                         (I * theta +
                          (1 - cos(theta)) * omg_n +
                          (theta - sin(theta)) * omg_n * omg_n) *
                         se3mat[1:3, 4] / theta)
    end
    return    vcat(temp,[0 0 0 1])
end

#****************************************************************#
# VeysiADN 12 May 2021
# Advanced Robotics Homework 5 Codes
#****************************************************************#
using LinearAlgebra
const lAlgebra = LinearAlgebra
#****************************************************************#
# Computes exponential coordinate ‘so3mat’
# (skew symmetric matrix) of the rotation matrix R

function MatrixLog3(R)
    acosinput = (lAlgebra.tr(R) - 1) / 2
    if acosinput >= 1
        return zeros(3, 3)
    else
        theta = acos(acosinput)
        return theta / 2 / sin(theta) * (R - R')
    end
end
#****************************************************************#
# Computes exponential coordinate ‘se3mat’
# (twist matrix) of the rigid body transformation matrix T
function MatrixLog6(T)
    if( size(T)!=(4,4) )
        println("!Warning! : Transformation matrix should be 4x4.")
        return
    end
    R = T[1:3,1:3]
    t = T[1:3,4]
    omg_h = MatrixLog3(R)
    w = [omg_h[3,2];omg_h[1,3];omg_h[2,1]]
    if omg_h == zeros(3, 3)
        return vcat(hcat(zeros(3, 3), T[1:3, 4]), [0 0 0 0])
    else
        theta = acos((lAlgebra.tr(R) - 1) / 2)
        return vcat(hcat(omg_h,
                         (I - omg_h / 2 +
                          (1 / theta - 1 / tan(theta / 2) / 2) *
                          omg_h * omg_h / theta) * T[1:3, 4]),
                    [0 0 0 0])
    end
end
#****************************************************************#

#****************************************************************#
# VeysiADN 12 May 2021
# Advanced Robotics Homework 5 Codes
#****************************************************************#
using LinearAlgebra
#****************************************************************#
# Returns Adjoint matrix with given rigid body
# transformation matrix g
function Adjoint(g)

    R=g[1:3,1:3];
    p=g[1:3,4];
    p_hat = [ 0       -p[3]     p[2];
             p[3]       0      -p[1];
            -p[2]      p[1]       0 ]

    return [R zeros(3,3);p_hat*R R]
end
#****************************************************************#
# VeysiADN 4 Apr 2021
# Advanced Robotics Homework 3 Codes
#****************************************************************#
using LinearAlgebra
#****************************************************************#
# Returns 3 x 3 rotation matrix representing
# rotation about unit vector w by angle theta.
# theta should be in radian
function Rodrigues(w,theta)
    if( length(w) != 3)
        println("!Warning! : W matrix should have 3 elements.")
        return
    end
    if(size(w)==(1,3))
        w=w';
    end
    w_hat = [0      -w[3]    w[2];
             w[3]     0     -w[1];
            -w[2]    w[1]      0];
    rotation_matrix = cos(theta)*Matrix(1I,3,3) + sin(theta)*w_hat+(1-cos(theta))*w*w';
    return rotation_matrix;
end
#****************************************************************#
# Builds rigid transformation matrix T from given
#rotation matrix R and translation t
function RpToTrans(R,t)
    if( size(R)!=(3,3) )
        println("!Warning! : Rotation matrix should be 3x3.")
        return
    end
    if( length(t) != 3 )
        println("!Warning! : Translation matrix should have 3 elements.")
        return
    end
    if(size(t)==(1,3))
        t=t';
    end
    R_B_T = hcat(R,t) ;
    R_B_T = vcat(R_B_T,[0 0 0 1]) ;
    return R_B_T
end
#****************************************************************#
#Extracts rotation matrix R and translation t from given /
# rigid transformation matrix T
function TransToRp(T)
    if( size(T)!=(4,4) )
        println("!Warning! : Transformation matrix should be 4x4.")
        return
    end
    R = T[1:3,1:3]
    t = T[1:3,4]
    return R,t;
end
#****************************************************************#
# Inverts rigid transformation matrix T
function TransInv(T)
    if( size(T)!=(4,4) )
        println("!Warning! : W matrix should be 4x4.")
        return
    end
    if( det(T)==0 )
        println("!Warning! : Inverse doesn't exist (Determinant is equal to zero).")
        return
    end
    R,t = TransToRp(T);
    r_inv =hcat(R',-(R'*t))
    r_inv = vcat(r_inv,[0 0 0 1]);
    return r_inv;
end
#****************************************************************#
#  Starting from here  is homework 7.
"""
    Calculates Spatial geometric Jacobian Js, given 6 x n matrix
    ‘Twists’ whose columns are Twist parameter of each joints
    and n x 1 vector of joint angles
"""
function JacobianSpace(Twists, JointAngles)
    T = I;
    Js = Twists
    for i = 2:length(JointAngles)
        T *= MatrixExp6(VecTose3(Twists[:, i - 1] * JointAngles[i - 1]))
        Js[:, i] = Adjoint(T) * Twists[:, i]
    end
    return Js
end
"""
Calculates Forward Kinematics given initial configuration M,
6 x n matrix ‘Twists’ whose columns are twist parameter
of each joints, and n x 1 joint angles
"""
function FwdKin(M,Twists,JointAngles)
    T=M
    for i = length(JointAngles):-1:1
        T = MatrixExp6(VecTose3(Twists[:,i]*JointAngles[i])) * T
    end
    return T
end
"""
Solves Paden-Kahan subproblem 1

"""
function PadenKahanFirst(w, x, y)
    p = x
    q = y
    u = p - (w'*p)*w;
    v = q - (w'*q)*w;
    theta = atan((w'*(cross(u,v)))/(u'*v))
    return theta
end
"""
Solves Paden-Kahan subproblem 2. th will be 2 x 2 matrix
containing solution pair (th1, th2) each row
"""

function PadenKahanSecond(w1, w2, x, y)
    p=x
    q=y

    A = [1 w1'*w2;
        w1'*w2 1];
    B=[w1'*q; w2'*p]
    alfa,beta = A\B

    gamma_sq = ((norm(p)^2)-(alfa^2)-(beta^2)-(2*alfa*beta*(w1'*w2)))/((norm(cross(w1,w2)))^2)
    gamma_1 = sqrt.(gamma_sq);
    gamma_2 = -sqrt.(gamma_sq);
    c_1 = alfa*w1+beta*w2+gamma_1*(cross(w1,w2));
    c_2 = alfa*w1+beta*w2+gamma_2*(cross(w1,w2));
    th_1 = PadenKahanFirst(w2,p,c_1)
    th_2 = PadenKahanFirst(w2,p,c_2)
    th_3 = PadenKahanFirst(-w1,q,c_1)
    th_4 = PadenKahanFirst(-w1,q,c_2)
    th = [th_1 th_3;th_4 th_2];
    return th
end


using LinearAlgebra

export Generic2DLattice

abstract type Abstract2DLattice end

"""

Structure containing the real and the reciprical basis vectors of a surface
"""
struct Generic2DLattice <: Abstract2DLattice
    a1
    a2
    b1
    b2
end

"""
# Arguments
* a1 first lattice vector
* a2 second lattice vector

# Example
` Generic2DLattice([1,0],[1,0]) `
"""
function Generic2DLattice(a1, a2)
    area = a2[2]*a1[1]-a2[1]*a1[2]
    b1 = 2π * [ a2[2],-a2[1]] / area
    b2 = 2π * [-a1[2], a1[1]] / area
    Generic2DLattice(a1,a2,b1,b2)
end

struct HexGrid <: Abstract2DLattice
    a
    l::Generic2DLattice
end

HexGrid(a) = HexGrid(a,Generic2DLattice([a,0],[cos(pi/180*120),sin(pi/180*120)].*a))

function rotate_reciprocal(lattice::Generic2DLattice,direction)
	unitx = [lattice.b1 lattice.b2] * direction
	ang = -atan(unitx[2],unitx[1])
	R = [cos(ang) -sin(ang)
	     sin(ang) cos(ang)]
	R*lattice.b1,R*lattice.b2
end


######### StanSample example  ###########

using StanSample

bernoulli_model = "
data {
  int<lower=1> N;
  int<lower=0,upper=1> y[N];
}
parameters {
  real<lower=0,upper=1> theta;
}
model {
  theta ~ beta(1,1);
  y ~ bernoulli(theta);
}
";

bernoulli_data = Dict("N" => 10, "y" => [0, 1, 0, 1, 0, 0, 0, 0, 0, 1])

# Keep tmpdir identical to prevent re-compilation
stanmodel = SampleModel(
  "bernoulli", bernoulli_model;
  method = StanSample.Sample(adapt=StanSample.Adapt(delta=0.85)))

stan_sample(stanmodel, data=bernoulli_data, n_chains=6, diagnostics=true)

# Fetch the same output in the `sdf` ChainDataFrame
sdf = read_summary(stanmodel)


function doubling(a::Array{T, 2}, b::Array{T, 2}, g::Array{T, 2}, tol::T, maxiters::S) where {T <: AbstractFloat, S <: Int}

  retcode = 0
  iters = 1
  len = Inf

  gn = similar(g)

  while len > tol && iters <= maxiters
    gn = g+a*g*b
    len = maximum(abs,gn-g)
    a = a*a
    b = b*b
    g = copy(gn)
    iters += 1
  end

  if iters > maxiters
    retcode = 1
  end

  return gn,retcode

end


"""
A simple optimal growth model, for testing the DiscreteDP class.

Filename: finite_dp_og.jl

"""

"""
Set up R, Q and beta, the three elements that define an instance of
the DiscreteDP object.
"""
mutable struct SimpleOG
	#-Paramters-#
	B::Int64
	M::Int64
	alpha::Float64
	beta::Float64
	#-Internal Variables-#
	n::Int64
	m::Int64
	R::Array{Float64}
	Q::Array{Float64}

	function SimpleOG(B::Int64, M::Int64, alpha::Float64, beta::Float64, u::Function)
		n = B + M + 1
		m = M + 1
		R = zeros(Float64, n, m)
		Q = zeros(Float64, n, m, n)
		#-Populate R-#
		populate_R!(n,m,R,u,alpha)
		populate_Q!(m,Q,B)
		return new(B,M,alpha,beta,n,m,R,Q)
	end
end

SimpleOG() = SimpleOG(10,5,0.5,0.9,u)
SimpleOG(B::Int64, M::Int64, alpha::Float64, beta::Float64) = SimpleOG(B,M,alpha,beta,u)

#-Support Functions-#

function u(c, alpha)
    return c^alpha
end

function populate_R!(n,m,R,u,alpha)
    for s in 1:n
        for a in 1:m
            if a <= s
                R[s, a] = u(s-a,alpha)
            else
                R[s, a] = -Inf
            end
        end
    end
end

function populate_Q!(m,Q,B)
    for a in 1:m
        Q[:, a, a:(a + B)] .= 1.0 / (B+1)
    end
end


"""
    chisquaretest(A, adjusted=false)

calculate chi-square statistic and the p-value of a two-way Row x Colums. `A` is a matrix return from `crossTable` (which conveniently includes row total and column total).

The following statistics are reported

- Expectation values table
- Chi-Square Test
- Likelihood-ratio test
- Continuity-Adjusted Chi-Square Test (if `adjusted=true`)
- Mantel-Haenszel Chi-Square Test (not implemented)
- Fisher’s Exact Test (not implemented)

## Examples
```julia
julia> using Epi

julia> gender = ["Female", "Male"][rand(1:2, 100)];

julia> age = ["0-5", "6-10", "11-15"][rand(1:3, 100)];

julia> tm_ = crossTable(age, gender);

julia> chisquaretest(tm_)
Chi-square: 0.247 with 2 degrees of freedom
	p-value: 0.8837
Likelihood-ratio Chi-square: 0.2466 with 2 degrees of freedom
	p-value: 0.8839
===================================
Expectation values table
Frequency (expected value)

-----------------------------------
3×2 Named Array{Text{String},2}
A ╲ B │       Male      Female
──────┼───────────────────────
11-15 │  16 (14.7)   14 (15.3)
0-5   │ 15 (17.14)  20 (17.85)
6-10  │ 18 (17.14)  17 (17.85)
```

"""
function chisquaretest(A; adjusted=false, expect=true)
	nr, nc = (size(A).-1)
	emat = Matrix{Float64}(undef, nr, nc)
	for i in 1:nr, j in 1:nc
		emat[i, j] = A[i, end] * A[end, j] / A[end, end]
	end
	cells = Array(A[1:nr, 1:nc]);
	kdf = prod(size(cells).-1)
	chi = sum((cells - emat).^2 .* (1 ./ emat))
	G2  = 2*sum(removeNaN(unlist(cells .* log.(cells .* (1 ./ emat) ))))
	if adjusted
		Adj = sum( max.(0, abs.(cells - emat).-0.5) .^2 .* (1 ./ emat) )
	end
	if (any(cells[i]==0 for i in eachindex(cells)))
		@info "Zero cells are omitted in calculating likelihood-ratio"
	end
	println("Chi-square: ", trunc(chi, digits=4), " with $kdf degrees of freedom", "\n\tp-value: ", trunc(ccdf(Chisq(kdf), chi), digits=4))
	if adjusted
		println("Continuity-Adjusted Chi-square: ", trunc(Adj, digits=4), " with $kdf degrees of freedom", "\n\tp-value: ", trunc(ccdf(Chisq(kdf), Adj), digits=4))
	end
	println("Likelihood-ratio Chi-square: ", trunc(G2, digits=4), " with $kdf degrees of freedom", "\n\tp-value: ", trunc(ccdf(Chisq(kdf), G2), digits=4))
	if expect
		println("\n", "="^35)
		println("Expectation values table")
		println("Frequency (expected value)")
		println("\n", "-"^35)
		NamedArray(Text.(string.(cells, " (", trunc.(emat, digits=2), ")")),
		           (names(A)[1][1:end-1], names(A)[2][1:end-1]))
	end
end


#=
dists.jl - Prior distributions and their moments (Author: Julian Johs)
need to calculate mean and standard deviation of used distributions to use them in Dynare

beta_moments calculates mean and standard deviation of Beta distribution
giving plot as argument plots the distribution

beta_solve uses beta_moments to calculate parameters α and β for given
mean and standard deviation - this should give you a feeling for what Dynare does
beta_solve takes 2 real numbers as inputs

the same applies for the functions for the Gamma distribution
=#
using Distributions, StatsPlots, NLsolve

function beta_moments(params::Tuple{Real, Real}; plot=nothing)#; plot::Bool=false
    α = params[1]
    β = params[2]
    dist = Beta(α, β)
    mu = mean(dist)
    sig = std(dist)
    if plot !== nothing
        display(plot(dist)) # you can comment this out
     end
    return (mu, sig)
end
beta_moments(α::Real, β::Real; args...) = beta_moments((α, β); args...)
beta_moments(params::AbstractArray{<:Real, 1}; args...) = beta_moments((params[1], params[2]); args...)

function beta_solve(m::Real, s::Real)
    solver = nlsolve(params -> beta_moments(params) .- (m, s), [1.0, 1.0])
    return solver.zero
end
beta_solve(params::AbstractArray{<:Real, 1}) = beta_solve(params[1], params[2])
beta_solve(params::Tuple{Vararg{<:Real}}) = beta_solve(params[1], params[2])

function gamma_moments(params::Tuple{Real, Real}; plot=nothing)
    α = params[1]
    β = params[2]
    dist = Gamma(α, β)
    mu = mean(dist)
    sig = std(dist)
    if plot !== nothing
        display(plot(dist)) # you can comment this out
    end
    return (mu, sig)
end
gamma_moments(α::Real, β::Real; args...) = gamma_moments((α, β); args...)
gamma_moments(params::AbstractArray{<:Real, 1}; args...) = gamma_moments((params[1], params[2]); args...)

function gamma_solve(m::Real, s::Real)
    solver = nlsolve(params -> gamma_moments(params) .- (m, s), [1.0, 1.0])
    return solver.zero
end
gamma_solve(params::AbstractArray{<:Real, 1}) = gamma_solve(params[1], params[2])
gamma_solve(params::Tuple{Vararg{<:Real}}) =    gamma_solve(params[1], params[2])

function invgamma_moments(params::Tuple{Real, Real}; plot=nothing)
    α = params[1]
    β = params[2]
    dist = InverseGamma(α, β)
    mu = mean(dist)
    sig = std(dist)
    if plot !== nothing
        display(plot(dist)) # you can comment this out
    end
    return (mu, sig)
end
invgamma_moments(α::Real, β::Real; args...) = invgamma_moments((α, β); args...)
invgamma_moments(params::AbstractArray{<:Real, 1}; args...) = invgamma_moments((params[1], params[2]); args...)

function invgamma_solve(m::Real, s::Real; ftol=1e-12)
    solver = nlsolve(params -> invgamma_moments(params) .- (m, s), [2.1, 0.2])
    return solver.zero
end
invgamma_solve(params::AbstractArray{<:Real, 1}) = invgamma_solve(params[1], params[2])
invgamma_solve(params::Tuple{Vararg{<:Real}}) =    invgamma_solve(params[1], params[2])


@doc """
Generate some data using
y = mat*x + n
where x = vector of interest
mat = some transformation matrix
n = noise
This assumes p(n) ~ iid Normal(0, sigma^2)
""" ->
function generate_data(x::Array{Float64, 1}, mat::Array{Float64, 2}, sigma=1.0)
	@assert length(x) == size(mat)[2]
	return mat*x + sigma*randn(size(mat)[1])
end


function evaluate(a::STaylor1{N,T}, dx::T) where {N, T<:Number}
    @inbounds suma = a[N-1]
    @inbounds for k in (N-1):-1:0
        suma = suma*dx + a[k]
    end
    suma
end
#=
function evaluate(a::STaylor1{N,T}, dx::S) where {N, T<:Number, S<:Number}
    suma = a[N-1]*one(dx)
    @inbounds for k in (N-1):-1:0
        suma = suma*dx + a[k]
    end
    suma
end
=#
evaluate(a::STaylor1{N,T}) where {N, T<:Number} = a[0]

evaluate(x::Union{Array{STaylor1{N,T}}, SubArray{STaylor1{N,T}}}, δt::S) where
        {N, T<:Number, S<:Number} = evaluate.(x, δt)
evaluate(a::Union{Array{STaylor1{N,T}}, SubArray{STaylor1{N,T}}}) where
        {N, T<:Number} = evaluate.(a, zero(T))

(p::STaylor1)(x) = evaluate(p, x)
(p::STaylor1)() = evaluate(p)

(p::Array{STaylor1{N,T}})(x) where {N,T<:Number} = evaluate.(p, x)
(p::SubArray{STaylor1{N,T}})(x) where {N,T<:Number} = evaluate.(p, x)
(p::Array{STaylor1{N,T}})() where {N,T<:Number} = evaluate.(p)
(p::SubArray{STaylor1{N,T}})() where {N,T<:Number} = evaluate.(p)


immutable Tradeoff{T} <: Model
    a::T
    k::T
    γx::T
    γt::T
end

Base.show(io::IO, ::Type{Tradeoff}) = print(io, "Heuristic Tradeoff")

function Base.show(io::IO, m::Tradeoff)
    @printf(
        io,
        "Heuristic Tradeoff (a = %.3f, k = %.3f, γx = %.3f, γt = %.3f)",
        m.a,
        m.k,
        m.γx,
        m.γt,
    )
end

function initial_parameters(::Type{Tradeoff})
    return Float64[log(1.0), log(1.0), log(1.0), log(1.0)]
end

canonical_parameters(m::Tradeoff) = (m.a, m.k, m.γx, m.γt)

function Tradeoff(parameters::Vector)
    a = exp(parameters[1])
    k = exp(parameters[2])
    γx = exp(parameters[3])
    γt = exp(parameters[4])
    return Tradeoff(a, k, γx, γt)
end

@inline ϕ(χ, γ) = log(1 + γ * χ) / γ

@inline ϕ′(χ, γ) = ((γ * χ) / (1 + γ * χ) - log(1 + γ * χ)) / γ^2

@inline function predict(m::Tradeoff, x2::Real, t2::Real, x1::Real, t1::Real)
    a, k, γx, γt = m.a, m.k, m.γx, m.γt
    δϕx = ϕ(x2, γx) - ϕ(x1, γx)
    δϕt = ϕ(t2, γt) - ϕ(t1, γt)
    z = a * (δϕx - k * δϕt)
    p = invlogit(z)
    return p
end

@inline function gradient_component(
    m::Tradeoff,
    x2::Real,
    t2::Real,
    x1::Real,
    t1::Real,
)
    a, k, γx, γt = m.a, m.k, m.γx, m.γt
    δϕx = ϕ(x2, γx) - ϕ(x1, γx)
    δϕt = ϕ(t2, γt) - ϕ(t1, γt)
    δϕ′x = ϕ′(x2, γx) - ϕ′(x1, γx)
    δϕ′t = ϕ′(t2, γt) - ϕ′(t1, γt)
    gr = (
        (δϕx - k * δϕt) * a,
        -a * δϕt * k,
        a * δϕ′x * γx,
        a * -k * δϕ′t * γt,
    )
    return gr
end


using Plots

f(x) = sin(x)

function trapezoidal_rule(a, b, n)
    h = (b - a) / n
    res = 0.0
    for i in 1:n
        if (i==0 || i==n)
            res += f(a+i*h)
        else
            res += 2 * f(a+i*h)
        end
    end

    res*h/2
end

println("Analytical=", 2)
println("Numerical=", trapezoidal_rule(0, pi, 100))


#testing for julia functions :)

using Statistics
using LinearAlgebra
#using InvertedIndices
#operations:
#const LiefiesMatrix = Union{Matrix{Missing}, Matrix{<:Real}}
LiefiesMatrix{T <: Union{Missing, Real}} = AbstractMatrix{T}
#function f=some_function(x::LiefiesMatrix) :D

# function closure(data::Union{Array{Union{Float64,Missing},2},Array{Union{Int64, Missing},2},Vector{Float64},Vector{Int64}}) #Union{Vector{Regex}, NTuple{N, Regex}}
#::LiefiesMatrix) #Union{Vector{Regex}, NTuple{N, Regex}}
function closure(data::T) where T
    cvec = Array{Union{Missing, Float64}}(undef, size(data)...)
    for i in 1:size(data,1)
      cvec[i,:] = data[i,:] ./ sum(skipmissing(data[i,:])) #work around for sum not accepting skipmissing with dims argument
    end
    return cvec
end

function center(data::T) where T #inherit type T from data :D
    #data must be compositions!
    gvec = Matrix{Union{Missing, Float64}}(undef, 1, size(data,2))
    data = closure(data) #just in case
    data = BigFloat.(data)
    for j in 1:size(gvec,2) #by columns
      gvec[j] = prod(skipmissing((data[:,j]))) ^ (1/size(collect(skipmissing(data[:,j])),1)) #work around for sum not accepting skipmissing with dims argument
    end
    gvec=closure(gvec)
    return gvec
end

function ait_var(data::T) where T
    cen = center(data)
    avar = zeros(Float64, size(data,2), size(data,2))
    #avar = Matrix{Float64}(undef, size(data,2), size(data,2))
    #avar = Matrix{Union{Missing, Float64}}(undef, size(data,2), size(data,2))
    for i=1:size(data,2)
        for j=i:size(data,2)
            n=min(length(collect(skipmissing(data[:,i]))),length(collect(skipmissing(data[:,j]))))
            avar[i,j]=(1/(n-1))*sum(skipmissing((log.(data[:,i]./data[:,j]) .- log(cen[i]/cen[j])).^2))
        end
    end
    #avar = Symmetric(avar) #to make symmetric :)
    return avar
end

function geo(x)
    geo_mean = prod(x) .^ (1 ./ length(x))
    return geo_mean
end

function ait_inner(x,y)
    ai=sum( log.(x ./ geo(x)) .* log.(y ./ geo(y))   )
    return ai

    # ai=0.0        #alternative calculation:
    # for i=1:length(x)
    #     ai = ai + sum((log.(x[i] ./ x) .* (log.(y[i] ./ y))))
    # end
    # ai = 1/(2*length(x)) * ai
    #return ai
end

function ait_norm(x)
    an=0.0
    for i=1:length(x)
        an= an + sum((log.(x[i] ./ x) .^2))
    end
    an = sqrt(1/(2*length(x)) * an)
    return an
end

function square_ait_norm(x)
    an=0.0
    for i=1:length(x)
        an= an + sum((log.(x[i] ./ x) .^2))
    end
    an = (1/length(x)) * 1/(2*length(x)) * an
    return an
end

function ait_dist(x,y)
    if length(x) == length(y)
        ad = 0.0
        for i=1:length(x)
            ad = ad + sum((log.(x[i] ./ x) .- log.(y[i] ./ y)).^2)
        end
        ad = sqrt(1/(2*length(x)) * ad)
        return ad
    else
        println("x and y must be the same length")
    end
end

function perturb(x,y)
    p=closure(x) .* closure(y) #works even if x is mulitple rows!
    p=closure(p)
    return p
end

function perturb_diff(x,y)
    p=closure(x) .* inverse(y) #works even if x is mulitple rows!
    p=closure(p)
    return p
end

function power(x,a)
    p=closure(x) .^ a
    p=closure(p)
    return p
end

function inverse(x)
    ix = closure(1 ./ x)
    return ix
end

function clr(data)
    cvec = Matrix{Union{Missing, Float64}}(undef, size(data))
    for i=1:size(data,1) #by rows
        gmean = prod(skipmissing(data[i,:])) ^ (1/size(collect(skipmissing(data[i,:])),1)) #work around for sum not accepting skipmissing with dims argument
        cvec[i,:]=log.(data[i,:]./gmean)
    end
    return cvec
end

function ilr(x,sbp::Matrix{<:Real})

    bal=zeros(size(sbp)) #create orthonormal basis based on partition
    for i=1:size(sbp,1) #number of rows of sbp
        rr=findall(sbp[i,:] .== 1)
        ss=findall(sbp[i,:] .== -1)
        r=length(rr)
        s=length(ss)
        bal[i,rr] .= (1/r).*sqrt((r*s)/(r+s))
        bal[i,ss] .= -(1/s).*sqrt((r*s)/(r+s))
    end

    ilr_x=log.(x)*bal'
    return ilr_x, bal
end

function invilr(ilr_x,bal)
    #balance refers to contrast matrix (it is not the sbp!)
    x=invclr(ilr_x*bal);
    return x
end

function invclr(x_clr)
    x=closure(exp.(x_clr));
    return x
end

function amalga(x,col1,col2)

    A = zeros(size(x,2),3)
    A[col1,1] = 1
    A[col2,2] = 1
    A[setdiff(collect(1:size(x,2)),[col1;col2]),3] .= 1

    y = x * A
    return y, A

end

function bayes_multi_zero_replace(c,method,prior)
    #c is a count vector!
    #tj is the prior estimate
    #s is the strength parameter

    samples, D = size(c) #number of rows then columns
    n=sum(c,dims=2)

    if prior == "uniform"
        t=1/D .* ones(size(c))
    elseif prior == "modified"
        println("Assuming prior based on ratios found within dataset")
        t=zeros(size(c))
        for q=1:samples
            global t
            to_use = setdiff(1:samples, q)
            t[q,:]=sum(c[to_use,:],dims=1)
        end
        t=t./sum(t,dims=2)
    end

    #common imprecise Dirichlet models considered here: Bayes-Laplace and Square-root
    if method == "Bayes-Laplace"
        s = D * ones(samples,1)
    elseif method == "Square-root"
        s = sqrt.(sum(c,dims=2)) #total trials per sample!
    elseif method == "Geometric"
        #if these are very small numbers, can result in a zero multiplicative product
        println("accounting for very small values here...")
        temp = 10 .^ ((1/D) .* sum(log10.(t),dims=2))
        s = 1 ./ temp
        #s = 1 ./ prod(closure(t),dims=2).^(1/D)
    elseif method == "Jeffreys"
        s = D/2 * ones(samples,1)
    elseif method == "Perks"
        s = ones(samples,1)
    else
        println("Not a recognized method; please choose from Bayes-Laplace, Square-root, Geomteric, Jeffreys, or Perks")
    end

    println(method)
    #@show s
    #multiplicative part: only adjust rows for which there is a zero
    #fieldnames()...
    x=closure(c)
    r=deepcopy(x)
    global r, x, t, s
    for i=1:samples
        if any(x[i,:] .== 0) #row contains zeros
            zeroind=findall(x[i,:] .== 0) #work on the columns
            nonzero=findall(x[i,:] .!= 0)
            r[i,zeroind] = t[i,zeroind] .* s[i]./(n[i] .+ s[i])
            r[i,nonzero] = x[i,nonzero] .* (1 - sum(r[i,zeroind]))
        end
    end

    return r
    # x=closure(c)
    # r=x
    # zeroind=findall(x .== 0)
    # nonzero=findall(x .!= 0)
    # colind=map(x->x.I[2], zeroind)
    # rowind=map(x->x.I[1], zeroind)
    #
    # r[zeroind] = t[zeroind] .* s[colind]./(n[colind] .+ s[colind])
    # r[nonzero] = x[nonzero] .* (1 - sum(r[zeroind]))

end

#To compare with R package- that one seems to be doing strange thins with BDL values
# g=Matrix{Union{Missing, Float64}}(undef, 1, size(y,2))
# for j in 1:size(y,2) #by columns
#   g[j] = prod(skipmissing(y[:,j])) ^ (1/size(collect(skipmissing(y[:,j])),1)) #work around for sum not accepting skipmissing with dims argument
# end
#
# #(0.3 * x).^(1/2) = nrm
# g0=sum(g[1,[1,2,3]])
# #sum(g[1,[1,2,4]]) + q = nrm
# #nrm = y[1,4] + g0
#
# r_cen=[0.5116703 0.2558351 0.1193897 0.1131049]
# mm=((g[1]./r_cen[1] - g0).^2)./y[1,4]


using Revise
using Random
using SpecialFunctions
using PyCall
using PyPlot
using StatsPlots

function mySliceSampler(pdf_log, x_0, w, m, N, burnIn = 1000)
    # REQUIRES MODULES:
    # Random
    #
    # INPUT:
    # pdf_log --> log-pdf of the target distribution (a function)
    # x_init --> inital vector of x (dimension 1xD), where D dimension of pdf
    # w --> typical slice size, for a window (dimension 1xD)
    # m --> integer limiting the slice size to m*w
    # N --> number of sample points
    # burnIn --> number of burn-in samples (optional, results will be discarded)
    #
    # OUTPUT:
    # x_s --> sampled x values (NxD), only keep values after full permutations along dimensions
    # pdflog_x_s --> log-pdf of sampled values

    D = length(x_0)  # the dimension of the distribution (nr of coordinates)
    x_s = Array{Float64}(undef,N,D)   # Samples (NxD), preallocate array, with undefined values
    pdflog_x_s = Array{Float64}(undef,N)   # (unnormalised) log-prob of sampled points (Nx1)
                                           # for reducing evaluations
    pdflog_x_1 = 0.0;

    # to be able to input scalars:
    if typeof(x_0) == Float64
        x_0 = [x_0]
    end

    for ii in 1:(N+burnIn)
        # Update the new x progressively for each dimension:
        L   = 1*x_0  # R and L need to be of same dimension, because we apply the pdf upon those!
        R   = 1*x_0
        x_1 = 1*x_0  # "1*" needed, otherwise x_0 suddendly takes mysterious values
        for d in randperm(D)
            ### 1) Make the Slice
            # random "vertical" position
            if (ii == 1)
                #vertical = rand() * pdf_log(x_0) # in normal space
                z = pdf_log(x_0) + log(rand()) # in log-space
            else
                # saving evaluations, pdflog_x_1 was updated for current permutation in the end
                z = pdflog_x_1 + log(rand())
            end

            ### 2) Make the window, "Stepping-out" alogrithm
            # Randomly place a window, containing x_0:
            L[d] = x_0[d] - rand() * w[d]
            R[d] = L[d] + w[d]

            # Randomly share the max window size among left/right:
            J = floor(m*rand());
            K = (m-1) - J;

            # Extend window to the left, until outside slice or allowance seizes:
            while ((J > 0) && (z < pdf_log(L)))
                #println("Lefting")
                L[d] -= w[d]
                J -= 1
            end

            # Extend window to the right, until outside slice or allowance seizes:
            while ((K > 0) && (z < pdf_log(R)))
                #println("Righting")
                R[d] += w[d]
                K -= 1
            end

            ### 3) Sample from window
            # finding an allowable point:
            while true
                x_1[d] = L[d] + rand() * (R[d] - L[d])

                # this + breaking out of loop reduces the amount of log-pdf evaluations:
                pdflog_x_1 = pdf_log(x_1)

                if (pdflog_x_1 >= z)     # new value found
                    x_0[d] = x_1[d]     # update value for this dimension
                    break
                # Value was not within slice, shrink the interval:
                elseif (x_1[d] < x_0[d])
                    L[d] = x_1[d]
                elseif (x_1[d] > x_0[d])
                    R[d] = x_1[d]
                else
                    throw(ErrorException("Error during shrinking the interval"))
                end
            end

        end # end of permutating through dimensions

        ### 4) Update the chain:
        # Just overwrite the burnIn by using modular arithmetic:
        i = 1 + (ii-1)%N    # gives 1,2,3,...N,1,2,3,...,(N+burnIn)
        x_s[i,:] = x_1    # sample point, after one round of permutations
        pdflog_x_s[i] = pdflog_x_1 # now contains log_pdf of all new coordinates of new point, since has been updated at each permutation
        # note that x_0 has been updated during the permutations

    end

    return x_s, pdflog_x_s
end

#%%
################################################################

function log_beta(θ,a::Any,b::Any)
    #using SpecialFunctions
    θ = θ[1]
    if 0 < θ < 1
        # Numerator
        num = log(θ)*(a-1) + log(1-θ)*(b-1)
        # The denominator (Beta-function) can be integrated or made up of Gamma-functions
        # Since we only treat integer values here, Γ(a) = (a-1)!
        #den = log(factorial(a-1) * factorial(b-1) / factorial(a+b-1))
        den = log(SpecialFunctions.beta(a,b))
        return num - den
    else
        return -Inf
    end
end

################################################################

# Log-Bernoulli PDF:
function log_bernoulli_pdf(θ,y)
    if 0 < θ < 1 && (y==0 || y==1)    # Only admissible values
        return y * log(θ) + (1-y) * log(1-θ)
    else
        return -Inf
    end
end

# Log-Likelihood fct of a (series of) coin tosses with bias:
function log_likelihood_fct(θ, y)
    # Make array s.t. multiple theta values can be input:
    #likelihood = Array{Float64}(undef,length(θ))
    # Do that for every theta value requested:
    #for ii in 1:length(θ)
    #    # Elementwise application of the log_beronoulli_pdf over outcome vector y, then SUM together:
    #    likelihood[ii] = sum(log_bernoulli_pdf.(θ[ii],y))
    #end

    # Un array-ise the output:
    #if length(θ) == 1
    #    likelihood = likelihood[1]
    #end

    θ = θ[1]
    likelihood = sum(log_bernoulli_pdf.(θ,y))

    return likelihood
end

#%%
################################################################

# Chain inputs:
N = 10^6
x_start = 0.5;
w = .5;  # typical window width
m = 100; # maximum number of window widths

# Final posteriors in one array:
posteriors = Array{Float64}(undef,N,3);

#%%
# First Column of Figure 6.4
a = 100
b = 100
y = [ones(1,17) zeros(1,3)]

log_prior(θ) = log_beta(θ,a,b)
log_lklhd(θ) = log_likelihood_fct(θ,y)
log_posterior(θ) = log_lklhd(θ) + log_prior(θ)

# Need to use "StatsPlots" or "Plots" histogram to add the density curve, PyPlot won't work
chain, pdf = mySliceSampler(log_prior,x_start,w,m,N);
histogram(chain, bins=100, normalize=:pdf, label="prior")   # Comes from StatsPlots now
density!(chain,linewidth=3,label="prior")

chain, pdf = mySliceSampler(log_lklhd,x_start,w,m,N)
histogram!(chain, bins=100, normalize=:pdf,label="likelihood")
density!(chain,linewidth=3,label="likelihood")

chain, pdf = mySliceSampler(log_posterior,x_start,w,m,N)
histogram!(chain, bins=100, normalize=:pdf,label="posterior")
density!(chain,linewidth=3,label="posterior")

posteriors[:,1] = chain # save the posterior

Plots.savefig("A4-A-C1_Slice.pdf")

#%%
# Second Column
a = 18.25
b = 6.75
y = [ones(1,17) zeros(1,3)]

log_prior(θ) = log_beta(θ,a,b)
log_lklhd(θ) = log_likelihood_fct(θ,y)
log_posterior(θ) = log_lklhd(θ) + log_prior(θ)

chain, pdf = mySliceSampler(log_prior,x_start,w,m,N);
histogram(chain, bins=100, normalize=:pdf, label="prior")   # Comes from StatsPlots now
density!(chain,linewidth=3,label="prior")

chain, pdf = mySliceSampler(log_lklhd,x_start,w,m,N)
histogram!(chain, bins=100, normalize=:pdf,label="likelihood")
density!(chain,linewidth=3,label="likelihood")

chain, pdf = mySliceSampler(log_posterior,x_start,w,m,N)
histogram!(chain, bins=100, normalize=:pdf,label="posterior")
density!(chain,linewidth=3,label="posterior")

posteriors[:,2] = chain # save the posterior

Plots.savefig("A4-A-C2_Slice.pdf")

#%%
# Third Column
a = 1
b = 1
y = [ones(1,17) zeros(1,3)]

log_prior(θ) = log_beta(θ,a,b)
log_lklhd(θ) = log_likelihood_fct(θ,y)
log_posterior(θ) = log_lklhd(θ) + log_prior(θ)

chain, pdf = mySliceSampler(log_prior,x_start,w,m,N);
histogram(chain, bins=100, normalize=:pdf, label="prior")   # Comes from StatsPlots now
density!(chain,linewidth=3,label="prior")

chain, pdf = mySliceSampler(log_lklhd,x_start,w,m,N)
histogram!(chain, bins=100, normalize=:pdf,label="likelihood")
density!(chain,linewidth=3,label="likelihood")

chain, pdf = mySliceSampler(log_posterior,x_start,w,m,N)
histogram!(chain, bins=100, normalize=:pdf,label="posterior")
density!(chain,linewidth=3,label="posterior")

posteriors[:,3] = chain # save the posterior

Plots.savefig("A4-A-C3_Slice.pdf")

#%%

density(posteriors, label=["a=100, b=100","a=18.25, b=6.75","a=1, b=1"])
Plots.savefig("A4-A-all_Slice.pdf")