using ComplexSystems
using Plots

the_walkers = walker_set(5000,-5,5)
unshift!(the_walkers,walker(0.,0.,0))

the_walkers = dla(the_walkers,0.1,-5,5)

## Plotting
the_x = [w.x for w in the_walkers]
the_y = [w.y for w in the_walkers]
p = scatter(the_x,the_y,markersize=3.,
            leg=false,ticks=nothing,
            markerstrokewidth=0.,border=false)
title!("Diffusion Limit Aggregation")
png(p,"/figs/dla")

## Minkowski dimension
min_x=-5.
max_x=5.
min_y=-5.
max_y=5.

walkers_p = map(x -> Point(x.x,x.y),the_walkers);
Δs = []
Dimension = []
for Δ ∈ 0.01:0.05:0.96
    the_grid = make_square_grid(min_x,max_x,min_y,max_y,Δ,true);
    for punto ∈ walkers_p
        map(x -> inside_box(punto,x),the_grid);
    end
    non_empty_ones = sum([!x.empty for x ∈ the_grid]);
    println(non_empty_ones)
    dimension = log(non_empty_ones)/log(1/Δ)
    push!(Δs,Δ)
    push!(Dimension,dimension)
end

mini = round(Dimension[1];digits=3)
p = scatter(Δs,Dimension,legend=false)
title!("Dimension vs box size")
yaxis!("Dimension",:log10)
xaxis!("Length of square side",:log10)

png(p,"/figs/dimension")


module RandomLinearModels

using LinearAlgebra

function isstabled(A)
    all(x->abs(x)<=1, eigvals(A))
end

function isstablec(A)
    all(x->real(x)<=0, eigvals(A))
end

function controllability(A,B)
    n,m = size(B)
    R = zeros(n,n*m)
    Ak = zero(A) .+ I(n)
    for k = 1:n
        Rk = view(R, :, (k-1)*m .+ (1:m))
        Rk .= Ak*B
        Ak = Ak * A
    end
    return R
end

iscontrollable(A,B) = rank(controllability(A,B)) == size(A,1)

function genA(v, n, m)
    # Generate a random orthogonal matrix
    X = randn(n,n)
    Q = qr(X).Q
    A = Q*Diagonal(v)*Q'
end

genB(n,m) = randn(n,m)

function gendiscrete(n, m, tol::Real=1e-4)
    v = randn(n)
    v = v ./ (norm(v,Inf) + tol)     # make it (marginally) discrete stable
    A = genA(v, n, m)
    B = genB(n,m)
    A,B
end

function gencontinuous(n, m, tol::Real=1e-4)
    v = randn(n)
    v = v .- (maximum(v) + tol)     # make it (marginally) continuous stable
    A = genA(v, n, m)
    B = genB(n,m)
    A,B
end

function gencontrollable(n, m, type::Symbol=:discrete; tol::Real=1e-4, maxiter=20)
    cnt = 0
    while true
        if type == :discrete
            A,B = gendiscrete(n, m, tol)
        elseif type == :continuous
            A,B = gencontinuous(n, m, tol)
        end
        iscontrollable(A,B) && (return A,B)
        if cnt > maxiter
            throw(ErrorException("exceeded max number of attempt to find a controllable system."))
        end
        cnt += 1
    end
end

end


"""
bound(estimator, δ, n::Int)
Give the theoritical bound B such that for the estimator
    #TODO! correction math formula
```math
\\mathbb{P}( |X- \\mathbb{E}(X)|/\\sigma \\leq B) \\leq \\delta
```

"""
bound(δ::Real, n, M::EmpiricalMean) = sqrt(1 / (n * δ))
bound(δ::Real, n, M::MedianOfMean) = sqrt(32log(1 / δ) / n)
bound(δ::Real, n, M::TrimmedMean) = 19sqrt(2log(8 / δ) / n) # Typo in the review by Lugosie. It seems that the ouragous 19 does th job. It is probably improvable.
bound(δ::Real, n, M::Catoni) = sqrt(2log(2 / δ) / (n - 2log(2 / δ)))
bound(δ::Real, n, M::Huber) = 8sqrt(2log(4 / δ) / n)
""""
sqrt(2log(1 / δ) / n) # Multiply by a factor (1 + o(1)) where
o(1) = (1+O(sqrt(log(1/δ)/n)))*(1+log(log(1/δ))/log(1/δ)) goes to zeros with (log(1/δ)/n, δ)→ (0, 0)
see https://www.youtube.com/watch?v=Kr0Kl_sXsJM Q&A
"""
bound(δ::Real, n, M::LeeValiant) = 8sqrt(2log(4 / δ) / n)


"""
    rref(A::AbstractMatrix{T}) where {T<:Interval}

Computes the reduced row echelon form of the interval matrix `A` using maximum
mignitude as pivoting strategy.

### Examples

```jldoctest
julia> A = [2..4 -1..1; -1..1 2..4]
2×2 Matrix{Interval{Float64}}:
  [2, 4]  [-1, 1]
 [-1, 1]   [2, 4]

julia> rref(A)
2×2 Matrix{Interval{Float64}}:
 [2, 4]  [-1, 1]
 [0, 0]       [1.5, 4.5]
```
"""
function rref(A::AbstractMatrix{T}) where {T<:Interval}
    A1 = copy(A)
    return rref!(A1)
end

"""
    rref!(A::AbstractMatrix{T}) where {T<:Interval}

In-place version of [`rref`](@ref).
"""
function rref!(A::AbstractMatrix{T}) where {T<:Interval}
    m, n = size(A)
    minmn = min(m,n)
    @inbounds for k = 1:minmn

        if k < m
            # find maximum index
            migmax, kp = _findmax_mig(view(A, k:m, k))
            iszero(migmax) && throw(ArgumentError("Could not find a pivot with non-zero mignitude in column $k."))
            kp += k - 1

            # Swap rows k and kp if needed
            k != kp && _swap!(A, k, kp)
        end

        # Scale first column
        _scale!(A, k)

        # Update the rest
        _eliminate!(A, k)
    end
    return A
end

@inline function _findmax_mig(v)

    @inbounds begin
        migmax = mig(first(v))
        kp = firstindex(v)

        for (i, vi) in enumerate(v)
            migi = mig(vi)
            if migi > migmax
                kp = i
                migmax = migi
            end
        end
    end
    return migmax, kp
end

@inline function _swap!(A, k, kp)
    @inbounds for i = 1:size(A, 2)
        tmp = A[k,i]
        A[k,i] = A[kp,i]
        A[kp,i] = tmp
    end
end

@inline function _scale!(A, k)
    @inbounds begin
        Akkinv = inv(A[k,k])
        for i = k+1:size(A, 1)
            A[i,k] *= Akkinv
        end
    end
end

@inline function _eliminate!(A, k)
    m, n = size(A)
    @inbounds begin
        for j = k+1:n
            for i = k+1:m
                A[i,j] -= A[i,k]*A[k,j]
            end
        end

        for i = k+1:m
            A[i, k] = zero(eltype(A))
        end
    end
end


# binocdf is the cdf for the Binomial distribution and binoccdf is its complement.
# Distributions.jl implements the cdf of its Binomial by calling out to Rmath which
# is a Julia package that wraps the corresponding functions used in R. Since we do not want to
# load the Distributions lib when starting Autotest we want a pure Julia implementation
# of binocdf. We base this code on the pure Ruby code for incomplete beta function available here:
#   http://rubydoc.info/gems/rubystats/0.2.3/Rubystats/SpecialMath#incomplete_beta-instance_method
#

# Cumulative Distribution Function evaluated at x of the Binomial(n, p) distribution:
function binocdf(x, n, p)
  x = ifloor(x)
  if x == n
    return 1.0
  else
    incompletebeta(1-p, n - x, x + 1)
  end
end

# Complementary cdf of Binomial distr func:
binoccdf(x, n, p) = 1.0 - binocdf(x, n, p)

# incompletebeta(x, a, b) evaluates incomplete beta function, here a, b > 0 and 0 <= x <= 1.
function incompletebeta(x, a, b)
  if x == 0
    return 0.0
  elseif x == 1
    return 1.0
  else
    beta_gam = exp( a * log(x) + b * log(1.0 - x) - log_beta(a, b) )
    if x < ((a+1) / (a+b+2))
      return (beta_gam * beta_fraction(x, a, b) / a)
    else
      return (1.0 - (beta_gam * beta_fraction(1.0 - x, b, a) / b))
    end
  end
end

const LOG_GAMMA_X_MAX_VALUE = 2.55e292

function log_beta(a, b)
  if (a <= 0.0) || (b <= 0.0) || (a + b) > LOG_GAMMA_X_MAX_VALUE
    return 0.0
  else
    return (lgamma(a) + lgamma(b) - lgamma(a + b))
  end
end

const XMININ = 2.23e-303
const PRECISION = 8.88e-016

function beta_fraction(x, a, b, MaxIterations = 200)
  c = 1.0
  sum_ab  = a + b
  a_plus  = a + 1.0
  a_minus = a - 1.0
  h = 1.0 - sum_ab * x / a_plus
  if abs(h) < XMININ
    h = XMININ
  end
  h     = 1.0 / h
  frac  = h
  m     = 1
  delta = 0.0

  while (m <= MaxIterations) && (abs(delta - 1.0) > PRECISION)
    m2 = 2 * m
    # even index for d
    d = m * (b - m) * x / ( (a_minus + m2) * (a + m2))
    h = 1.0 + d * h
    if abs(h) < XMININ
      h = XMININ
    end
    h = 1.0 / h
    c = 1.0 + d / c
    if abs(c) < XMININ
      c = XMININ
    end
    frac *= (h * c)
    # odd index for d
    d = -(a + m) * (sum_ab + m) * x / ((a + m2) * (a_plus + m2))
    h = 1.0 + d * h
    if abs(h) < XMININ
      h = XMININ
    end
    h = 1.0 / h
    c = 1.0 + d / c
    if abs(c) < XMININ
      c = XMININ
    end
    delta = h * c
    frac *= delta
    m += 1
  end

  return frac
end

# Compare to Distributions.jl
using Distributions

# call once to compile things
binocdf(1, 10, 0.3)
binocdf(1.0, 10.0, 0.3)
binocdf(2.0, 10, 0.3)

# Sample lower values with higher prob through a log transform.
# Often more realistic since people tend to use lower ranges/values
# frequently.
function logsample(min, max)
  shift = 1.0 - min
  logmin = log(min + shift)
  logmax = log(max + shift)
  exp(logmin + (logmax - logmin) * rand()) - shift
end

NumReps = 1e6
ourtimesum = 0.0
dtimesum = 0.0

for rep in 1:ifloor(NumReps)
  n = ifloor(logsample(2, 1e6))
  x = rand() * n # rand(0:n)
  p = rand()

  tic()
  ourres = binocdf(x, n, p)
  ourtimesum += toq()

  tic()
  dres = cdf(Binomial(n, p), x)
  dtimesum += toq()

  if !isapprox(ourres, dres)
    println("Failed for x = $x, n = $n, p = $p: our = $ourres, Distr.jl = $dres")
  end
end

println("Our average binocdf performance: ", ourtimesum / NumReps)
println("Distributions.jl average binocdf performance: ", dtimesum / NumReps)
println("Performance diff: ", round(ourtimesum / dtimesum, 2), "x")


"""
Returns the variance of data
"""
function Statistics.var(data1::VNamedD)
	σ=0.0
	μ=Statistics.mean(data1)
	n=0
	for iss in 1:length(data1)
		for ifield in names(data1[iss].d)[1]
			for ir = 1:data1[iss].n, it = 1:length(data1[iss].grid)
				n += 1
				σ += (data1[iss].d[ifield][it, ir]-μ)^2
			end
		end
	end
	return σ*inv(n)
end

function Statistics.mean(data1::VNamedD)
	n=0
	μ=0.0
	for iss in 1:length(data1)
		for ifield in names(data1[iss].d)[1]
			for ir = 1:data1[iss].n, it = 1:length(data1[iss].grid)
				n += 1
				μ += data1[iss].d[ifield][it, ir]
			end
		end
	end
	return μ*inv(n)
end

function addnoise!(dataN::VNamedD, data::VNamedD, SNR)

	σx=Statistics.var(data)

	σxN=sqrt(σx^2*inv(10^(SNR/10.)))

	# factor to be multiplied to each scalar
	α=sqrt(σxN)
	for iss in 1:length(data)
		for ifield in names(data[iss].d)[1]
			for ir = 1:data[iss].n, it = 1:length(data[iss].grid)
				dataN[iss].d[ifield][it, ir] = dataN[iss].d[ifield][it, ir] + α*Random.randn()
			end
		end
	end
end


using Pkg
Pkg.activate(pwd())

# # Optimal control
# ## Computing trajectories with no control

struct PMSM{T<:Real}
    ρ::T
    ω::T
    A::Matrix{T}
    invA::Matrix{T}

    function PMSM(ρ, ω)
        A = -ρ*[1 0; 0 1] -ω*[0 -1; 1 0]
        return new{eltype(A)}(ρ, ω, A, inv(A))
    end
end

#+

function expA(p::PMSM, t)
    ρ, ω = p.ρ, p.ω
    return exp(-ρ*t)*[cos(ω*t) sin(ω*t); -sin(ω*t) cos(ω*t)]
end

#+

ρ = 0.1
ω = 2
x0 = [0;-0.5]
q = [1;0]

ps = PMSM(ρ, ω)

# ### Exercise:
#
# Verify that the matrix exponential is computed correctly and that it is different from
# the elementwise exponential.
#
# **Hint**: The matrix exponential can also be computed directly by the `exp` function from
# the `LinearAlgebra` package.
#
# ---
# ### Solution:

# ---
#
# ### Exercise:
#
# Write two function `trajectory_fin_diff` and `trajectory_exact` which compute the
# trajectory. The first one should use the finite difference method to discretize the
# time, while the second one should use the closed-form formula.
#
# Plot both trajectories on time interval ``[0,10]`` with time discretization step
# ``\Delta t=0.01``. Since ``x(t)`` is a two-dimensional vector, plot each component
# on one axis.
#
# ---
# ### Solution:

# ---

ts = 0:0.0001:10

xs1 = trajectory_fin_diff(ps, x0, ts, q)
xs2 = trajectory_exact(ps, x0, ts, q)

plot(xs1[1,:], xs1[2,:], label="Finite differences");
plot!(xs2[1,:], xs2[2,:], label="True value")

# ## Solving the optimal control problem
# ### Exercise:
#
# Solve the optimal time for ``x_{\rm tar}= (0.25, -0.5)`` with the maximum voltage
# ``U_{\rm max} = 0.1``.
#
# **Hint**: To solve the equation above for ``t``, use the bisection method.

function bisection(f, a, b; tol=1e-6)
    fa = f(a)
    fb = f(b)
    fa == 0 && return a
    fb == 0 && return b
    fa*fb > 0 && error("Wrong initial values for bisection")
    while b-a > tol
        c = (a+b)/2
        fc = f(c)
        fc == 0 && return c
        if fa*fc > 0
            a = c
            fa = fc
        else
            b = c
            fb = fc
        end
    end
    return (a+b)/2
end

# ---
# ### Solution:

# ---

function trajectory_control(p::PMSM, x0, ts, q, U_max, p0)
    xs = zeros(length(x0), length(ts))

    for (i, t) in enumerate(ts)
        eAt  = expA(p, t)
        emAt = expA(p, -t)
        xs[:, i] = eAt*(x0 + p.invA * (I - emAt)*q + U_max/ρ*(exp(ρ*t) - 1)*p0)
    end
    return xs
end

#+

p0 = ps.ρ/(U_max*(exp(ps.ρ*τ)-1))*(expA(ps, -τ)*x_t - x0 - ps.invA*(I-expA(ps, -τ))*q)
p0 /= norm(p0)

#+

ts = range(0, τ; length=100)
traj = trajectory_control(ps, x0, ts, q, U_max, p0)

#+

plot(traj[1,:], traj[2,:], label="Optimal trajectory");
scatter!([x0[1]], [x0[2]], label="Starting point");
scatter!([x_t[1]], [x_t[2]], label="Target point")

#+

ts = 0:0.01:10
plt = plot();

for α = 0:π/4:2*π
    trj = trajectory_control(ps, x0, ts, q, U_max, [sin(α); cos(α)])
    plot!(plt, trj[1,:], trj[2,:], label="")
end

display(plt)


"""
    Berlage(; <keyword arguments>)

Create a Berlage wavelet.

# Arguments
- `dt::AbstractFloat=0.002`: sampling interval in secs.
- `f0::Real=20.0`: central frequency in Hz.
- `m::Real=2`: exponential parameter of Berlage wavelet.
- `alpha::Real=180.0`: alpha parameter of Berlage wavelet in rad/secs.
- `phi0::Real=0.0`: phase rotation in radians.

# Example
```julia
julia> w = Berlage(); plot(w);
```
**Reference**
* Aldridge, David F., 1990, The berlage wavelet: GEOPHYSICS, 55, 1508--1511.
"""
function Berlage(; dt::AbstractFloat=0.002, f0::Real=20.0, m::Real=2, alpha::Real=180.0,
                 phi0::Real=0.0)

    nw = floor(Int, 2.0/(f0*dt))
    t = dt*collect(0:1:nw-1)
    w = [ (t[i] ^ m) * exp(-alpha*t[i]) * cos(2*pi*f0*t[i] + phi0) for i in eachindex(t) ]

    w = w/maximum(w)

end


using ForwardDiff, Roots, Plots, LinearAlgebra, Optim,Statistics

function strong_backtracking(f, ∇, x, d; α=1, β=0.1, σ=0.4)
    y0, g0, y_prev, α_prev = f(x), ∇(x)⋅d, NaN, 0
    αlo, αhi = NaN, NaN
    # bracket phase
    while true
        y = f(x + α*d)
        if y > y0 + β*α*g0 || (!isnan(y_prev) && y ≥ y_prev)
            αlo, αhi = α_prev, α
            break
        end
        g = ∇(x + α*d)⋅d
        if abs(g) ≤ -σ*g0
            return α
        elseif g ≥ 0
            αlo, αhi = α, α_prev
            break
        end
        y_prev, α_prev, α = y, α, 2α
    end

    # zoom phase
    ylo = f(x + αlo*d)
    while true
        α = (αlo + αhi)/2
        y = f(x + α*d)
        if y > y0 + β*α*g0 || y ≥ ylo
            αhi = α
        else
            g = ∇(x + α*d)⋅d
            if abs(g) ≤ -σ*g0
                return α
            elseif g*(αhi - αlo) ≥ 0
                αhi = αlo
            end
            αlo = α
        end
    end
end

function bfgs(f,grad_f,x0;tol=1e-6,maxitr=10000)

    #init data
    ite = 0;x=x0;conv=false;err=0;
    xh = zeros(length(x0),maxitr+1);xh[:,1] = x0;

    #init bfgs parameters
    m = length(x)
    D = Matrix(1.0I,m,m)
    g = grad_f(x)

    #iteractions
    while ite < maxitr
        #line search
        d = -D*g

        #Inexact line search (strongWolf)
        alpha = strong_backtracking(f,grad_f,x,d;α=1, β=1e-4, σ=0.1)

        #update x
        s = alpha*d;
        x += s

        gl = grad_f(x)
        y = gl - g
        D = D - (s*y'*D + D*y*s')/(s'*y) + (1+(y'*D*y)/(s'*y))[1]*(s*s')/(s'*y)

        #update bfgs parameters
        g = gl

        #update ite and x history
        ite += 1
        xh[:,ite+1] = x

        #local minimum point
        err = norm(s);
        if err < tol ; conv=true; break; end
    end

    println(ite+1)
    #return x history. convergence and error
    return xh[:,1:ite+1], conv, err
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; #nao ta convergindo

#Caso 3
F1 = 0.0; F2 = 400.0;

#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];
grad_F(u) = ForwardDiff.gradient(F,u)

#tolerancia
tol = 1e-8

#numero maximo de iteracoes
iterac_max = 10000

#ponto inicial
#u0 = [-0.01,0.0]
u0 = [ 0.00,0.0]

#u*
@time u_est = bfgs(F,grad_F,u0;tol=tol,maxitr=iterac_max)
u_est = u_est[1][:,end]
println("u*: ",u_est)
println("f(u*): ",F(u_est))

#gradiente no ponto de minimo
println("∇f(u*): ",grad_F(u_est))

#verificar a hessiana no ponto de minimo
hess_F(u) = ForwardDiff.hessian(F,u)
println("H(u*): ",hess_F(u_est))
println("λ: ",eigvals(hess_F(u_est)))


export newton

"""
newton(f,df,H,x)

Newton method for solving min_x f(x)

"""
function newton(f::Function,df::Function,H::Function,x::Vector;maxIter=20,atol=1e-8,out::Int=0,storeInterm::Bool=false)

    his = zeros(maxIter,2)
    X = (storeInterm) ? zeros(length(x),maxIter) : []
    i = 1; flag = -1; LL = []
    while i<=maxIter
        fk  = f(x)
        dfk = df(x)
        his[i,:] = [fk norm(dfk)]
        if storeInterm; X[:,i] = x; end;

        if(norm(dfk)<atol)
            flag = 0
            his = his[1:i,:]
            break
        end

        # get search direction
        d2f = H(x)
        try
            LL    = cholfact(d2f)
        catch err
            if isa(err,Base.LinAlg.PosDefException)
                flag = -2
                his = his[1:i,:]
                break
            else
                throw(err)
            end
        end
        pk    = - (LL\dfk)
        if out>0
            @printf "iter=%04d\tf=%1.2e\t|df|=%1.2e\n" i his[i,1] his[i,2]
        end
        # update
        x  += pk
        i+=1
    end
    i = min(maxIter,i)

    if out>=0
        if flag==-1
            println(@sprintf("newton iterated maxIter (=%d) times but reached only atol of %1.2e instead of tol=%1.2e",i,his[i,2],atol))
        elseif flag==-2
            println(@sprintf("newton stopped because the Hessian at iteration %d was not positive definite.",i))
        elseif out>1
            println(@sprintf("newton achieved desired atol of %1.2e at iteration %d.",atol,i))
        end
    end
    if storeInterm; X = X[:,1:i]; end
    return x,flag,his,X
end


export all_partitions
"""
`all_partitions(A::Set)` creates a `Set` containing all possible
partitions of the set `A`.

`all_partitions(n::Int)` creates the `Set` of all partitions of
the set `{1,2,...,n}`.

Both of these take an optional second argument `k` to specify that
only partitions with exactly `k` parts should be returned.
"""
function all_partitions(A::Set{T}) where T
    if length(A) < 2
        P = Partition(A)
        return Set([P])
    end
    # Set aside one element of A and recurse
    x = first(A)
    B = deepcopy(A)
    delete!(B,x)

    PB = all_partitions(B)
    PA = Set{Partition{T}}()  # place to hold partitions we create

    for P in PB
        # case 1: include x as a singleton
        P_parts = parts(P)
        push!(P_parts, Set([x]))
        Q = PartitionBuilder(P_parts)
        push!(PA,Q)

        # case 2: insert x into existing parts
        parts_list = collect(parts(P))
        np = length(parts_list)
        for k=1:np
            push!(parts_list[k],x)  # insert x into k'th part
            Q = PartitionBuilder(Set(parts_list)) # build the partition
            push!(PA,Q)
            delete!(parts_list[k],x) # take it back out
        end

    end
    return PA
end

function all_partitions(n::Int)
    if n < 0
        error("argument must be a nonnegative integer")
    end
    A = Set{Int}(collect(1:n))
    return all_partitions(A)
end

function all_partitions(A::Set{T},k::Int) where T
    if k<0
        error("Number of partitions (k) must be nonnegative")
    end
    n = length(A)
    PA = Set{Partition{T}}()
    if k>n  # no partitions possible
        return PA
    end
    if n==0 # special case when A is empty
        push!(PA, Partition(A))
        return PA
    end
    if k==0 # special case, no partitions
        return PA
    end

    x = first(A)
    B = deepcopy(A)
    delete!(B,x)

    # Step 1: partitions in which x is a singleton
    PB = all_partitions(B,k-1)
    for P in PB
        parts_set = parts(P)
        push!(parts_set, Set([x]))  # {x} as a single part
        Q = PartitionBuilder(parts_set)
        push!(PA,Q)
    end

    # Step 2: partitions in which x is with others
    PB = all_partitions(B,k)
    for P in PB
        for j=1:k  # each P has exactly k parts
            parts_list = collect(parts(P))
            push!(parts_list[j], x)  # add x to part j
            Q = PartitionBuilder(Set(parts_list))
            push!(PA,Q)
        end
    end

    return PA
end

function all_partitions(n::Int,k::Int)
    if n < 0
        error("argument must be a nonnegative integer")
    end
    A = Set{Int}(collect(1:n))
    return all_partitions(A,k)
end


# Newey-West covariance estimator
# from https://github.com/mcreel/Econometrics/blob/508aee681ca42ff1f361fd48cd64de6565ece221/src/NP/NeweyWest.jl
# under MIT licence https://github.com/mcreel/Econometrics/blob/508aee681ca42ff1f361fd48cd64de6565ece221/LICENSE
# and adapted

"""
    function newey_west(Z,nlags=0)

Returns the Newey-West estimator of the asymptotic variance matrix
INPUTS: Z, a nxk matrix with rows the vector zt'
        nlags, the number of lags
OUTPUTS: omegahat, the Newey-West estimator of the covariance matrix
"""
function newey_west(Z,nlags=0)
    n,k = size(Z)
    # de-mean the variables
    Z = Z .- mean(Z,dims=1)
    omegahat = Z'*Z/n       # sample variance
    # automatic lags?
    if nlags == 0
        nlags = max(1, round(Int, n^0.25))
    end
    # sample autocovariances
    for i = 1:nlags
       Zlag = @view(Z[1:n-i,:])
       ZZ = @view(Z[i+1:n,:])
       gamma = (ZZ'*Zlag)/n
       weight = 1.0 - (i/(nlags+1.0))
       omegahat += weight*(gamma + gamma')
    end
    return omegahat
end


"""
dₖ = solve_tCG(M, x, gradf_k, hessf_x_h, ν=1e-2, ϵ_residual = 1e-5)

Conjugate gradient method to solve the linear problem:
find d s.t. hessf_x_h(d) + gradf_k = 0

Note that we are looking for a direction d in the tangent space of M at x, and the kernel of
∇²f_M contains at least the orthogonal to this set. CG should naturally give a solution
living in the tangent space.
"""
function solve_tCG(M, x, gradfₖ, hessf_x_h; ν=1e-3, ϵ_residual = 1e-13, maxiter=1e5, safe_projection=false, printlev=0)
    norm_x(η) = norm(M, x, η)

    dₖ = zero_tangent_vector(M, x)
    rⱼ, rⱼ_prev = zero_tangent_vector(M, x), zero_tangent_vector(M, x)
    vⱼ, vⱼ_prev = zero_tangent_vector(M, x), zero_tangent_vector(M, x)

    d_type = :Unsolved
    j = 0

    (printlev>0) && @printf "\nj     norm(rⱼ)             norm(vⱼ)             ⟨vⱼ, hessf(x)[vⱼ]⟩   ν * norm(vⱼ)^2\n"
    while true
        # current residual, conjugated direction
        rⱼ = hessf_x_h(dₖ) + gradfₖ
        if safe_projection
            rⱼ = project(M, x, rⱼ)
        end

        vⱼ = - rⱼ
        if j ≥ 1
            βⱼ = norm_x(rⱼ)^2 / norm_x(rⱼ_prev)^2
            vⱼ += βⱼ * vⱼ_prev
        end

        hessf_x_vⱼ = hessf_x_h(vⱼ)
        (printlev>0) && @printf "%5i %.10e    %.10e    % .10e     %.10e\n" j norm_x(rⱼ) norm_x(vⱼ) inner(M, x, vⱼ, hessf_x_vⱼ) ν * norm_x(vⱼ)^2
        if norm_x(rⱼ) < ϵ_residual || inner(M, x, vⱼ, hessf_x_vⱼ) < ν * norm_x(vⱼ)^2 || j > maxiter
            ## Satisfying point obtained
            if j == 0
                dₖ = -gradfₖ
            end

            if norm_x(rⱼ) < ϵ_residual
                d_type = :Solved
                # printstyled("Exiting: ||rⱼ|| < ϵ : $(norm_x(rⱼ)) < $ϵ_residual\n", color=:red)
            elseif j > maxiter
                d_type = :MaxIter
                # printstyled("Exiting: j > maxiter : $j > $maxiter\n", color=:red)
            else
                d_type = :QuasiNegCurvature
                a = inner(M, x, vⱼ, hessf_x_vⱼ)
                b = ν * norm_x(vⱼ)^2
                # printstyled("Exiting: ⟨vⱼ, hessf_x(vⱼ)⟩ < ν * ||vⱼ||² : $a < $b\n", color=:red)
            end

            break
        end

        tⱼ = - inner(M, x, rⱼ, vⱼ) / inner(M, x, vⱼ, hessf_x_vⱼ)
        dₖ += tⱼ * vⱼ

        rⱼ_prev = deepcopy(rⱼ)
        vⱼ_prev = deepcopy(vⱼ)

        j += 1
    end

    return dₖ, j, d_type
end


# Get the summary of some numbers in fibonacci sequence.
v = [1,1]
s = 0
while (v[end] < 4000000)
    if v[end]%2==0 s+=v[end] end
    push!(v, v[end]+v[end-1])
end
println(s)
# 4613732


# ------------------------------------------------------------------------------------------
# # Orbit Diagrams, Surfaces of Section
# In this tutorial we will be using a `DynamicalSystem` instance to visualize aspects of the
# system.
#
# Topics:
# * `orbitdiagram`
# * `poincaresos`
# * `produce_orbitdiagram`
# * Docstrings
#
# # Orbit Diagram
# An "orbit diagram" is simply a plot that shows the long term behavior of a discrete system
# when a parameter is varied.
#
# This is exactly what the function `orbitdiagram` does:
# 1. Evolves the system for a transient amount of time.
# 2. Evolves & saves the output of the system for a chosen variable.
# 3. Changes/increments a parameter of the equations of motion.
# 4. Repeat steps 1-3 for all given parameter values!
#
# This approach is also related with the (much more advanced) [sensitivity
# analysis](http://docs.juliadiffeq.org/latest/analysis/sensitivity.html) from
# DifferentialEquations.jl
#
# ---
#
# Let's make the super-ultra-famous orbit diagram of the logistic map:
#
# $$x_{n+1} = rx_n(1-x_n)$$
# ------------------------------------------------------------------------------------------

using DynamicalSystems, PyPlot

logimap = Systems.logistic() # Systems module contains pre-defined well-known systems

# ------------------------------------------------------------------------------------------
# ---
#
# The call signature of `orbitdiagram` is:
#
# ```julia
# orbitdiagram(discrete_system, i, p_index, pvalues; n, Ttr, ...)
# ```
# * `i` is the index of the variable we want to save.
# * `p_index` is the index of the parameter we want to change.
# * `pvalues` are the values of the parameter that will change.
# * Keywords `Ttr` and `n` denote for how much transient time to evolve the system and how
# many states to save.
#
# ------------------------------------------------------------------------------------------

i = 1 # which variable to save (of course logistic map is 1D !)
n = 50 # how many values to save
Ttr = 5000 # transient iterations
p_index = 1
pvalues = 2:0.001:4  # parameter values
output = orbitdiagram(logimap, i, p_index, pvalues; n = n, Ttr = Ttr)
typeof(output)

# ------------------------------------------------------------------------------------------
# * The output is a vector of vectors. Each inner vector has length `n` and contains the
# values of the variable `i` at the given parameter value.
#
# Let's plot interactively!
# ------------------------------------------------------------------------------------------

function bf(pvalues, n, Ttr)
    logi = Systems.logistic()
    output = orbitdiagram(logi, 1, 1, pvalues; n = n, Ttr = Ttr)

    figure(figsize=(6,4))
    for (j, p) in enumerate(pvalues)
        plot(p .* ones(output[j]), output[j], linestyle = "None", # linestyle = None
        marker = "o", ms = 0.2, color = "black")
    end
    xlabel("\$r\$"); ylabel("\$x\$");
    xlim(pvalues[1], pvalues[end])
    # ylim(0, 1)
    return
end

bf(linspace(2.0, 4.0, 1000), 200, 2000)

bf(linspace(3.5, 3.6, 1000), 200, 2000)

# ------------------------------------------------------------------------------------------
# We can use `BenchmarkTools` to see how much time it takes to generate this orbit diagram
# ------------------------------------------------------------------------------------------

using BenchmarkTools
@btime output = orbitdiagram($logimap, $i, $p_index, $pvalues; n = $n, Ttr = $Ttr);
println("for total points: $(length(pvalues)*(Ttr+n)), out of which $(length(pvalues)*n) are saved")

# ------------------------------------------------------------------------------------------
# ---
#
# * `orbitdiagram` works with *any* discrete system! Check out the [documentation
# page](https://juliadynamics.github.io/DynamicalSystems.jl/latest/chaos/orbitdiagram/) for
# more!
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# # Poincaré Surface of Section
# This is a technique to reduce a continuous system into a discrete map with 1 fewer
# dimension.
# The wikipedia entry on [Poincaré map](https://en.wikipedia.org/wiki/Poincar%C3%A9_map) has
# a lot of useful info, but the technique itself is very simple:
#
# 1. Define a hyperplane in the phase-space of the system.
# 2. Evolve the continuous system for long times. Each time the trajectory crosses this
# plane, record the state of the system.
# 3. Only crossings with a specific `direction` (either positive or negative) are allowed.
#
# And that's it! The recorded crossings are the Poincaré Surface of Section!
#
# ## Defining a hyperplane
# Let's say that our phase-space is $D$ dimensional. If the state of the system is
# $\mathbf{u} = (u_1, \ldots, u_D)$ then the equation for a hyperplane is
#
# $$
# a_1u_1 + \dots + a_Du_D = \mathbf{a}\cdot\mathbf{u}=b
# $$
# where $\mathbf{a}, b$ are the parameters that define the hyperplane.
#
# ---
#
# Here is the call signature for a function that does this:
#
# ```julia
# poincaresos(continuous_system, plane, tfinal = 100.0; direction = 1, ...)
# ```
# In code, `plane` can be either:
#
# * A `Tuple{Int, <: Number}`, like `(j, r)` : the hyperplane is defined as when the `j`
# variable of the system crosses the value `r`.
# * An `AbstractVector` of length `D+1`. The first `D` elements of the vector correspond to
# $\mathbf{a}$ while the last element is $b$. The hyperplane is defined with its formal
# equation.
#
# ---
#
# As an example, let's see a section of the Lorenz system:
# $$
# \begin{aligned}
# \dot{X} &= \sigma(Y-X) \\
# \dot{Y} &= -XZ + \rho X -Y \\
# \dot{Z} &= XY - \beta Z
# \end{aligned}
# $$
#
# ------------------------------------------------------------------------------------------

lor = Systems.lorenz()

tr = trajectory(lor, 100.0, dt = 0.01)
figure(figsize = (8,6))
plot3D(columns(tr)...);
xlabel("X"); ylabel("Y"); zlabel("Z");

psos = poincaresos(lor, (2, 0.0), 2000.0) # find where 2nd variable crosses 0.0

figure(figsize = (6,4))
plot(psos[:, 1], psos[:, 3], lw=0.0, marker ="o", ms = 1.0, color = "C1");
xlabel("X"); ylabel("Z");

# ------------------------------------------------------------------------------------------
# * We see that the surface of section is some kind of 1-dimensional object.
# * This is expected, because as we will show in the tutorial "Entropies & Dimensions" the
# Lorenz system (at least for the default parameters) lives in an almost 2-dimensional
# attractor.
#
# * This means that when you take a cut through this object, the result should be
# 1-dimensional!
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# **Next, let's visualize the Poincaré Surface of Section in 3D**
# ------------------------------------------------------------------------------------------

"""
    meshgrid(x, y) -> X, Y
Create a meshgrid, such as that used in PyPlot's countour plots.
"""
function meshgrid(vx, vy)
    m, n = length(vy), length(vx)
    vx = reshape(vx, 1, n)
    vy = reshape(vy, m, 1)
    (repmat(vx, m, 1), repmat(vy, 1, n))
end

# ------------------------------------------------------------------------------------------
# We can get the attractor by calculating trajectories:
# ------------------------------------------------------------------------------------------

tr = trajectory(lor, 200.0, dt=0.01)
x, y, z = columns(tr);

# ------------------------------------------------------------------------------------------
# Next, let's make sure we can color points on our 3D plot based on where they are relative
# to a cut we'll make in the plot.
#
# To do this, we'll create a vector, `c`, that will store labels for the colors of all
# points:
# ------------------------------------------------------------------------------------------

c = Vector{String}(length(y))
for i in 1:length(y) # cut points: red
    if -0.1 < y[i] < 0.1
        c[i] = "C3"
    elseif y[i] < 0
        c[i] = "C0" # in front of cut: blue
    else
        c[i] = "C2" # behind cut: green
    end
end

figure(figsize = (8,6))
# First let's plot the attractor
plot3D(x,y,z, color = "black", lw = 0.2, alpha = 0.25)
scatter3D(x, y, z, color = c, s = 5.0)

# And then plot the PSOS plane:
x = [-20, 20]; z = [0, 40];
X, Z = meshgrid(x, z)
Y = zeros(X)

plot_surface(X, Y, Z, alpha = 0.25, color = "C1");
xlabel("X"); ylabel("Y"); zlabel("Z");

# ------------------------------------------------------------------------------------------
# # Producing an orbit diagram
#
# 1. The `orbitdiagram` function does not make sense for continuous systems. In order for
# `orbitdiagram` to have meaning one must have a map.
#
# 2. We can take advantage of the `poincaresos` function, and reduce a continuous system to
# a map.
# 3. Then, we can formally calculate an orbit diagram for a continuous system!
#
# In this example I will use the Shinriki oscillator, which displays a period-doubling route
# to chaos like the logistic map!
# ------------------------------------------------------------------------------------------

shinriki_voltage(V) = 2.295e-5*(exp(3.0038*V) - exp(-3.0038*V))
function shinriki_eom(u, p, t)
    R1 = p[1]

    du1 = (1/0.01)*(
    u[1]*(1/6.9 - 1/R1) - shinriki_voltage(u[1] - u[2]) - (u[1] - u[2])/14.5
    )

    du2 = (1/0.1)*(
    shinriki_voltage(u[1] - u[2]) + (u[1] - u[2])/14.5 - u[3]
    )

    du3 = (1/0.32)*(-u[3]*0.1 + u[2])
    return SVector{3}(du1, du2, du3)
end

# Be sure to give a mutable container for the parameter container!
shi = ContinuousDynamicalSystem(shinriki_eom, [-2, 0, 0.2], [22.0])

# ------------------------------------------------------------------------------------------
# Now we can use `shi`, our dynamical system, to calculate trajectory steps and then plot
# the result.
# ------------------------------------------------------------------------------------------

set_parameter!(shi, 1, 20.0) # parameter from 19 to 22
tr = trajectory(shi, 2000.0)
figure(figsize = (8,6))
plot3D(columns(tr[1000:end, :])..., color = "C3", alpha = 0.5, marker = "o", ms = 0.5);

# ------------------------------------------------------------------------------------------
# To get a feeling for the system, let's look at a couple `poicaresos`
# ------------------------------------------------------------------------------------------

figure(figsize=(8,4))
subplot(1,2,1)
# the function set_parameter! is useful here! (see docstring!)
R1 = 19.5
set_parameter!(shi, 1, R1)

# here I use `direction = -1` (makes more sense for the specific system)
psos = poincaresos(shi, (2, 0.0), 1000.0, Ttr = 100.0, direction = -1)

plot(psos[:, 1], psos[:, 3], lw=0.0, marker ="o", ms = 2.0, color = "C3");
xlabel("\$V_1\$"); ylabel("\$I_3\$")
title("R1 = $R1")

R1 = 21.0
set_parameter!(shi, 1, R1)
subplot(1,2,2)
psos = poincaresos(shi, (2, 0.0), 1000.0, Ttr = 100.0, direction = -1)
plot(psos[:, 1], psos[:, 3], lw=0.0, marker ="o", ms = 2.0, color = "C3");
xlabel("\$V_1\$");
title("R1 = $R1");

# ------------------------------------------------------------------------------------------
# Hm, this is interesting!
# 1. For some parameters the motion is clearly periodic (due to the distinct number of
# points)
# 2. But at other parameters the motion ***seems to be*** on a 2-dimensional manifold.
#
# **Don't be too quick to judge the second as chaotic though! In the next tutorial
# "Quantifying Chaos" I will show you tools to quantify chaotic behavior!**
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ---
#
# * We would like to see the behavior of the system while varying the parameter, like an
# orbit diagram.
#
# * We can do this by performing successive surfaces of section and then recording the
# values of a chosen variable!
#
# This procedure is bundled in the very convenient function `produce_orbitdiagram`:
#
# ```julia
# produce_orbitdiagram(continuous_system, plane, i, p_index, pvalues; kwargs...)
# ```
#
# The function produces successive `poincaresos` for the `plane`, and records the values of
# the `i`-th variable at the section.
#
# Most other arguments are simply propagated to `poincaresos`.
#
# *Lets use this function for the Shinriki oscillator example:*
#
# First we can store our input arguments for `produce_orbitdiagram` in a few input vars.
# ------------------------------------------------------------------------------------------

pvalues = linspace(19,22,201) # which parameter values to use
p_index = 1 # which parameter to change

i = 1                  # record 1st variable
plane = (2, 0.0)       # find PSOS of 2nd variable when crossing zero
tf = 200.0             # argument passed to poincaresos

# ------------------------------------------------------------------------------------------
# Now we're ready to call `produce_orbitdiagram` and plot the result!
#
# (NOTE: This computation takes some seconds, since it makes 201 PSOS!)
# ------------------------------------------------------------------------------------------

output = produce_orbitdiagram(shi, plane, i, p_index, pvalues;
                              # all keywords are passed to poincaresos:
                              tfinal = tf, Ttr = 200.0, direction = -1)

figure(figsize=(6,4))
for (j, p) in enumerate(pvalues)
    plot(p .* ones(output[j]), output[j], ls = "None",
    marker = "o", ms = 0.5, color = "black")
end
xlabel("\$R_1\$"); ylabel("\$V_1\$");

# ------------------------------------------------------------------------------------------
# ---
#
# ## Example with parameter-dependent plane
#
# Let's see one more case, using the Gissinger system:
#
# $$
# \begin{aligned}
# \dot{Q} &= \mu Q - VD \\
# \dot{D} &= -\nu D + VQ \\
# \dot{V} &= \Gamma -V + QD
# \end{aligned}
# $$
#
# ------------------------------------------------------------------------------------------

gis = Systems.gissinger([2.32865, 2.02514, 1.98312]) # initial condition necessary to see structure

set_parameter!(gis, 1, 0.112) # parameter from 0.1 to 0.15
tr = trajectory(gis, 1000.0)
figure(figsize = (8,6))
plot3D(columns(tr)...);

pvalues = linspace(0.1,0.15,201) # which parameter values to use
p_index = 1 # change 2nd parameter, which is μ

i = 2            # record i variable
j = 1            # find PSOS of j variable
offset = -0.75   # offset is important here

tf = 5000.0

output = produce_orbitdiagram(gis, (j, offset), i, p_index, pvalues;
                              # all keywords are passed to poincaresos:
                              tfinal = tf, Ttr = 2000.0)

figure(figsize=(8,6))
for (j, p) in enumerate(pvalues)
    length(output[j]) == 0 && continue
    plot(p .* ones(output[j]), output[j], ls = "None",
    marker = "o", ms = 0.5, color = "black", alpha = 0.5)
end
ylim(1,3)
xlabel("\$\\mu\$"); ylabel("\$D\$");

# ------------------------------------------------------------------------------------------
# ---
#
# 1. The above is quite cool, but for this specific system, the "optimal" hyperplane to use
# depends on the parameter `μ`.
#
# 2. In addition, it is not "optimal" to record a specific variable during the crossing, but
# instead a function of the variables.
#
# 3. For this approach, it is better to get the entire `poincaresos`s for each parameter,
# like I show in the following example:
# ------------------------------------------------------------------------------------------

# Define appropriate hyperplane for gissinger system
const ν = 0.1
const Γ = 0.9 # default parameters of the system

# I want hyperperplane defined by these two points:
Np(μ) = SVector{3}(sqrt(ν + Γ*sqrt(ν/μ)), -sqrt(μ + Γ*sqrt(μ/ν)), -sqrt(μ*ν))
Nm(μ) = SVector{3}(-sqrt(ν + Γ*sqrt(ν/μ)), sqrt(μ + Γ*sqrt(μ/ν)), -sqrt(μ*ν))

# Create hyperplane using normal vector to vector connecting points:
gis_plane(μ) = (d = (Np(μ) - Nm(μ)); [d[2], -d[1], 0, 0])

gis_plane(0.112)

μ = 0.12
set_parameter!(gis, 1, μ)
figure(figsize = (8,6))
psos = poincaresos(gis, gis_plane(μ), 2000.0, Ttr = 2000.0)
plot3D(columns(psos)..., marker = "o", ls = "None", ms = 2.0);

pvalues = linspace(0.1,0.14,201) # which parameter values to use

xs = Vector{Float64}[] # empty vector of vectors

for (i, μ) in enumerate(pvalues)
    set_parameter!(gis, 1, μ)
    N = Np(μ)
    psos = poincaresos(gis, gis_plane(μ), 4000.0, Ttr = 2000.0)
    push!(xs, [norm(N - k) for k in psos])
end

figure(figsize=(8,6))
for (j, p) in enumerate(pvalues)
    length(xs[j]) == 0 && continue
    plot(p .* ones(xs[j]), xs[j], ls = "None",
    marker = "o", ms = 0.2, color = "black", alpha = 0.5)
end
xlabel("\$\\mu\$"); ylabel("\$x\$");

figure(figsize=(8,6))
for (j, p) in enumerate(pvalues)
    length(xs[j]) == 0 && continue
    plot(p .* ones(xs[j]), xs[j], ls = "None",
    marker = "o", ms = 0.2, color = "black", alpha = 0.5)
end
ylim(2.7, 2.9)
xlabel("\$\\mu\$"); ylabel("\$x\$");

# ------------------------------------------------------------------------------------------
# # Docstrings
# ------------------------------------------------------------------------------------------

?orbitdiagram

?poincaresos

?produce_orbitdiagram

.nbexports/introductory-tutorials/broader-topics-and-ecosystem/introduction-to-dynamicalsystems.jl/2. Orbit diagrams, Surfaces of Section.jl


function euclideanDistance(point1::Array, point2::Array)

    nVar = length(point1)
    acc = 0
    for i=1:nVar
        acc += (point1[i]-point2[i])^2
    end
    return sqrt(acc)
end

function pairwiseDistance(array1::Array, array2::Array, metric::Function)

    len = size(array1)[1]

    matrix = zeros(len, len)

    for i=1:len
        for j=i+1:len
            matrix[i,j] = euclideanDistance(array1[i,:], array2[j,:])
            matrix[j,i] = matrix[i,j]
        end
    end

    return matrix
end

function getKnearest(row::Array, k::Integer)

    Knearest = collect(1:k)
    len = length(row)

    for i=1:k
        for j=i+1:k
            if row[Knearest[i]] < row[Knearest[j]]
                Knearest[j], Knearest[i] = Knearest[i],  Knearest[j]
            end
        end
    end

    for i=k+1:len
        if row[i] < row[Knearest[1]]
            Knearest[1] = i
            for j=1:k-1
                if row[Knearest[j]] < row[Knearest[j+1]] || break
                    Knearest[j], Knearest[j+1] = Knearest[j+1],  Knearest[j]
                end
            end
        end
    end

    return Knearest
end

function randomIndexSelection(len::Integer, n::Integer, rng::Random.AbstractRNG)

    if n>len
        n = len;
    end

    indexes = Array{UInt32}(undef, n)

    i=1
    while i<=n
        inside = false
        indexes[i] = (rand(rng, UInt32)%len) + 1;
        for j=1:i
            if i!=j
                inside = indexes[i] == indexes[j]
                inside && break
            end
        end
        if !inside
            i+=1
        end
    end
    return indexes
end

function randomIndexSelection2(len::Integer, n::Integer, rng::Random.AbstractRNG)

    if n>len
        n = len;
    end

    indexes = collect(UInt32,1:len)
    selected = Array{UInt32}(undef, n)

    i=1
    for i=1:n
        inside = false
        index = (rand(rng, UInt32)%len) + 1;
        len = len - 1
        selected[i] = indexes[index]
        indexes[index] = indexes[end]
        pop!(indexes)

    end
    return selected
end

function findprevIndexes(predicate::Function, A::Array, i::Integer)
    previous = Array{Integer}(undef, 0)

    while (aux = findprev(predicate, A, i)) != nothing
        push!(previous, aux)
        i -= 1
    end

    return previous
end

function findnextIndexes(predicate::Function, A::Array, i::Integer)
    next = Array{Integer}(undef, 0)

    while (aux = findnext(predicate, A, i)) != nothing
        push!(next, aux)
        i += 1
    end

    return next
end


using DrWatson
@quickactivate "StatReth"

# %%
using StatsBase
using Distributions
using Plots

# %% 3.20 - 3.23
pdf.(Binomial(2, 0.7), 0:2)

rand(Binomial(2, 0.7), 1)

rand(Binomial(2, 0.7), 10)

N = 10_000
dummy_w = rand(Binomial(2, 0.7), N)
counts(dummy_w) ./ N |> display   # either
countmap(dummy_w)                 # or, depending on what you want

# %% 3.24, 3.25
dummy_w = rand(Binomial(9, 0.7), N)
histogram(dummy_w)

w = rand(Binomial(9, 0.6), N)

# %% 3.26
samples = let
    # %% 3.2 - 3.5
    n = 1000
    p_grid = range(0, 1, length = n)
    prob_p = ones(n)
    prob_data = @. pdf(Binomial(9, p_grid), 6)
    posterior = prob_data .* prob_p
    posterior ./= sum(posterior)
    weights = pweights(posterior)
    sample(p_grid, weights, 10_000)
end

# Version 1: Imagine this as having all the binomial distributions from the p of the
# samples added up. The random numbers are drawn from the resulting distribution.
mix = MixtureModel(Binomial.(9, samples))
w1 = rand(mix, 10_000)

# Version 2: From each p in the sample you make a binomial distribution and draw 1 value
# from it. Then you put them all into one big array.
w2 = rand.(Binomial.(9, samples), 1)
w2 = vcat(w2...)

# These are two different approaches but give the same result for lots of values drawn.
# However, if you are only drawing a few values (less than samples) the first version
# is preferrable since you can still use all the information in the samples.

histogram(w1, normalize = :probability, alpha = 0.6)
histogram!(w2, normalize = :probability, alpha = 0.6)


# Routines having to do with the normalizing function values on a grid with respect
# to a given measure.

normalizing_weights(grid, measure) = sqrt.(quadweights(grid, measure))

"""
Return a normalization for a sampling operator on a grid. The normalization ensures
a norm equivalence between the continuous norm of the function space associated
with the measure, and the discrete norm of the sampling vector.

In practice, this amounts to a diagonal scaling with the square roots of the
weights of a quadrature rule on the grid that is exact on the span of the dictionary
in the space.
"""
sampling_normalization(gb::GridBasis{T}, measure) where T =
	DiagonalOperator(gb, gb, sqrt.(quadweights(grid(gb), measure)))


using ComponentArrays
using DifferentialEquations
using UnPack: @unpack

tspan = (0.0, 20.0)

## Lorenz system
function lorenz!(D, u, p, t; f=0.0)
    @unpack σ, ρ, β = p
    @unpack x, y, z = u

    D.x = σ*(y - x)
    D.y = x*(ρ - z) - y - f
    D.z = x*y - β*z
    return nothing
end
function lorenz_jac!(D, u, p, t)
    @unpack σ, ρ, β = p
    @unpack x, y, z = u

    D[:x,:x] = -σ
    D[:x,:y] = σ

    D[:y,:x] = ρ
    D[:y,:y] = -1
    D[:y,:z] = -x

    D[:z,:x] = y
    D[:z,:y] = x
    D[:z,:z] = -β
    return nothing
end

lorenz_p = (σ=10.0, ρ=28.0, β=8/3)
lorenz_ic = ComponentArray(x=0.0, y=0.0, z=0.0)
lorenz_fun = ODEFunction(lorenz!, jac=lorenz_jac!)
lorenz_prob = ODEProblem(lorenz_fun, lorenz_ic, tspan, lorenz_p)

## Lotka-Volterra system
function lotka!(D, u, p, t; f=0.0)
    @unpack α, β, γ, δ = p
    @unpack x, y = u

    D.x =  α*x - β*x*y + f
    D.y = -γ*y + δ*x*y
    return nothing
end
function lotka_jac!(D, u, p, t)
    @unpack α, β, γ, δ = p
    @unpack x, y = u

    D[:x,:x] = α - β*y
    D[:x,:y] = -β*x

    D[:y,:x] = δ*y
    D[:y,:y] = -γ + δ*x
    return nothing
end

lotka_p = (α=2/3, β=4/3, γ=1.0, δ=1.0)
lotka_ic = ComponentArray(x=1.0, y=1.0)
lotka_fun = ODEFunction(lotka!, jac=lotka_jac!)
lotka_prob = ODEProblem(lotka_fun, lotka_ic, tspan, lotka_p)

## Composed Lorenz and Lotka-Volterra system
function composed!(D, u, p, t)
    c = p.c #coupling parameter
    @unpack lorenz, lotka = u

    lorenz!(D.lorenz, lorenz, p.lorenz, t, f=c*lotka.x)
    lotka!(D.lotka, lotka, p.lotka, t, f=c*lorenz.x)
    return nothing
end
function composed_jac!(D, u, p, t)
    c = p.c
    @unpack lorenz, lotka = u

    lorenz_jac!(@view(D[:lorenz,:lorenz]), lorenz, p.lorenz, t)
    lotka_jac!(@view(D[:lotka,:lotka]), lotka, p.lotka, t)

    @view(D[:lorenz,:lotka])[:y,:x] = -c
    @view(D[:lotka,:lorenz])[:x,:x] = c
    return nothing
end

comp_p = (lorenz=lorenz_p, lotka=lotka_p, c=0.01)
comp_ic = ComponentArray(lorenz=lorenz_ic, lotka=lotka_ic)
comp_fun = ODEFunction(composed!, jac=composed_jac!)
comp_prob = ODEProblem(comp_fun, comp_ic, tspan, comp_p)

## Solve problem
# We can solve the composed system...
comp_sol = solve(comp_prob, Rodas5())

# ...or we can unit test one of the component systems
lotka_sol = solve(lotka_prob, Rodas5())


## analytic.jl :special cases where eigenfunctions are known analytically

# (separable) exponential covariance function with p = 0.5
function compute_analytic(cov::CovarianceFunction{1,Exponential{T}} where {T},n::N where {N<:Integer},pts::V where {V<:AbstractVector})
	λ = cov.cov.λ
    ω = findroots(λ, n)
    ev = @. 2*λ/(λ^2*ω^2+1)
    n = @. sqrt(2)/2*sqrt(1/ω*(λ^2*ω^2*cos(ω)*sin(ω)+λ^2*ω^3-2*λ*ω*cos.(ω)^2-cos(ω)*sin(ω)+ω)+2*λ)
	ef = diagm(0 => 1.0/n)*( sin.(ω*pts') + λ*diagm(0 => ω)*cos.(ω*pts') )

	SpectralData(sqrt.(ev),ef')
end

# find all positive (>0) zeros of the transcendental function tan(ω) = 2*λ*ω/(λ^2*ω^2-1)
function findroots(λ::T, n::N) where {T <: AbstractFloat, N <: Integer}

	# define the transcendental function
	f(ω) = (λ^2*ω^2-1)*sin(ω)-2*λ*ω*cos(ω)

	# find range around singularity 1/λ
	left_point_of_range = (2*floor(1/(π*λ)-1/2))*π/2 # left odd multiple of π/2
	right_point_of_range = (2*ceil(1/(π*λ)-1/2)+1)*π/2 # right odd multiple of π/2

	# find roots before 1/λ, if any
	if left_point_of_range ≠ π/2
		roots = zeros(min(n,round(UInt64,floor(abs(1/λ/π-1/2)))))
		left_point = π/2
		right_point = 1*π
		for i = 1:length(roots)
			@inbounds roots[i] = bisect_root(f,left_point,right_point)[1]
			right_point = roots[i] + π
			left_point = left_point + π
		end
	else
		roots = zeros(0)
	end

	# find roots inside range around 1/λ
	( length(roots) ≥ n || floor(1/(π*λ)-1/2) < 0 ) ||
	push!(roots,bisect_root(f,left_point_of_range+eps(T),1/λ)[1]) # first intersection point
	( length(roots) ≥ n || ceil(1/(π*λ)-1/2) < 0 ) ||
	push!(roots,bisect_root(f,1/λ,right_point_of_range)[1]) # second intersection point

	# if the first root is zero, cut it off
	roots[1] == 0 ? shift!(roots) : [] # empty expression

	# find roots after 1/λ
	startindex = 1 + length(roots)
	if n-length(roots) > 0
		roots = [roots; zeros(n-length(roots))]
		left_point = (2*ceil(1/(π*λ)-1/2)+2)*π/2
		right_point = (2*ceil(1/(π*λ)-1/2)+3)*π/2
		for i = startindex:length(roots)
			@inbounds roots[i] = bisect_root(f,left_point,right_point)[1]
			right_point = roots[i] + π
			left_point = left_point + π
		end
	end
	return roots
end

# bissection method to find the zeros of a function in a particular interval [x1,x2]
function bisect_root(fn::Function, x1::T, x2::T) where {T<:Real}
	xm = middle(x1, x2)
	s1 = sign(fn(x1))
	s2 = sign(fn(x2))
	while x1 < xm < x2
		sm =  sign(fn(xm))

		if s1 != sm
			x2 = xm
			s2 = sm
		else
			x1 = xm
			s1 = sm
		end

		xm = middle(x1, x2)
	end

	return x1, x2
end

# helper function to find the real "mid point" of two given floating point numbers
function middle(x1::Float64, x2::Float64)
	# use the usual float rules for combining non-finite numbers
	if !isfinite(x1) || !isfinite(x2)
		return x1 + x2
	end

	# always return 0.0 when inputs have opposite sign
	if sign(x1) != sign(x2) && x1 != 0.0 && x2 != 0.0
		return 0.0
	end

	negate = x1 < 0.0 || x2 < 0.0

	x1_int = reinterpret(UInt64, abs(x1))
	x2_int = reinterpret(UInt64, abs(x2))
	unsigned = reinterpret(Float64, (x1_int + x2_int) >> 1)

	negate ? -unsigned : unsigned
end


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:light
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.4.2
#   kernelspec:
#     display_name: Julia 1.4.0
#     language: julia
#     name: julia-1.4
# ---

using Plots
using LaTeXStrings

# # Circle

# +
θ = 0:0.01:2π
x = @. cos(θ)
y = @. sin(θ)

p = plot(
    xlabel = L"x",
    ylabel = L"y",
    xlim = [-1, 1],
    ylim = [-1, 1],
    aspect_ratio = :equal,
)

plot!(p, x, y, label = L"x^2+y^2 = 1")
# -

# # Astroid

# +
θ = 0:0.01:2π
a = 2
x = @. a * cos(θ)^3
y = @. a * sin(θ)^3

eq = latexstring("x^{2/3}+y^{2/3} = $a^{2/3}")

p = plot(
    title = eq,
    xlabel = L"x",
    ylabel = L"y",
    xlim = [-2, 2],
    ylim = [-2, 2],
    aspect_ratio = :equal,
)

plot!(p, x, y, label = :false)
# -

# # Lissajous

# +
θ = 0:0.01:2π
x = @. sin(θ)
y = @. sin(2θ)

eq = latexstring("y^2=4x^2(1-x^2)")

p = plot(
    title = eq,
    xlabel = L"x",
    ylabel = L"y",
    xlim = [-1, 1],
    ylim = [-1, 1],
    aspect_ratio = :equal,
)

plot!(p, x, y, label=false)

# +
θ = -π/4:0.01:π/4

x = @. tan(θ)
y = @. cos(2θ)

eq = L"$y=\frac{1-x^2}{1+x^2}$"

p = plot(
    title = eq,
    xlabel = L"x",
    ylabel = L"y",
    xlim = [-1, 1],
    ylim = [0, 1],
    aspect_ratio = :equal,
)

plot!(p, x, y, label = :false)
# -

# # Archimedean Spiral

# +
θ = 0:0.01:2π
a = 2
r = @. a*θ
x = @. r * cos(θ)
y = @. r * sin(θ)

eq = L"r=a\theta"

p = plot(
    title = eq,
    xlabel = L"x",
    ylabel = L"y",
    aspect_ratio = :equal,
)

plot!(p, x, y, label = :false)
# -

# # Cardioid

# +
θ = 0:0.01:2π
a = 2
r = @. a*(1+cos(θ))
x = @. r * cos(θ)
y = @. r * sin(θ)

eq = L"r=a(1+\cos(\theta)"

p = plot(
    title = eq,
    xlabel = L"x",
    ylabel = L"y",
    aspect_ratio = :equal,
)

plot!(p, x, y, label = :false)
# -

# # Rose Curve

# ## n = 2

# +
θ = 0:0.01:2π
a = 2
n = 2
r = @. a*sin(n*θ)
x = @. r * cos(θ)
y = @. r * sin(θ)

eq = latexstring("r=a\\sin($n\\theta)")

p = plot(
    title = eq,
    xlabel = L"x",
    ylabel = L"y",
    aspect_ratio = :equal,
)

plot!(p, x, y, label = :false)
# -

# ## n = 3

# +
θ = 0:0.01:2π
a = 2
n = 3
r = @. a*sin(n*θ)
x = @. r * cos(θ)
y = @. r * sin(θ)

eq = latexstring("r=a\\sin($n\\theta)")

p = plot(
    title = eq,
    xlabel = L"x",
    ylabel = L"y",
    aspect_ratio = :equal,
)

plot!(p, x, y, label=false)
# -

# ## n = 4

# +
θ = 0:0.01:2π
a = 2
n = 4
r = @. a*sin(n*θ)
x = @. r * cos(θ)
y = @. r * sin(θ)

eq = latexstring("r=a\\sin($n\\theta)")

p = plot(
    title = eq,
    xlabel = L"x",
    ylabel = L"y",
    aspect_ratio = :equal,
)

plot!(p, x, y, label = :false)
# -

# ## Rose (gif)

# +
θ = 0:0.01:2π
a = 2
anim = @animate for n in 2:10
    r = @. a*sin(n*θ)
    x = @. r * cos(θ)
    y = @. r * sin(θ)

    eq = latexstring("r=a\\sin($n\\theta)")

    p = plot(
        title = eq,
        xlabel = L"x",
        ylabel = L"y",
        xlim=[-3,3],
        ylim=[-3,3],
        aspect_ratio = :equal,
    )

    plot!(p, x, y, label = :false)
end

anim;
# -

gif(anim, "roses.gif", fps=2)


using SymPy

N = 11

b = [symbols("b_$i", real=true) for i in 1:N]
B = diagm(b)

# 2nd order dissipation
D1 = zeros(Int, N, N)
for i in 1:N
    D1[i,i] = 1
end
for i in 1:N-1
    D1[i+1,i] = -1
end
D1[1,2] = 1
display(D1)

B1 = eye(Int, N); B1[1,1] = 0
display(B1)

display(D1' * B * D1)
display(D1' * B1 * D1)

# 4th order dissipation
D2 = zeros(Int, N, N)
for i in 1:N
    D2[i,i] = -2
end
for i in 1:N-1
    D2[i+1,i] = 1
    D2[i,i+1] = 1
end
boundary_width = 1
for i in 1:boundary_width
    D2[i,:] = D2[boundary_width+1,:]
    D2[end-i+1,:] = D2[end-boundary_width,:]
end
display(D2)

B2 = eye(Int, N); B2[1,1] = B2[end,end] = 0
display(B2)

display(D2' * B * D2)
display(D2' * B2 * D2)

# 6th order dissipation
D3 = zeros(Int, N, N)
for i in 1:N
    D3[i,i] = -3
end
for i in 1:N-1
    D3[i+1,i] = 3
    D3[i,i+1] = 1
end
for i in 1:N-2
    D3[i+2,i] = -1
end
boundary_width = 2
for i in 1:boundary_width
    D3[i,:] = D3[boundary_width+1,:]
end
for i in 1:boundary_width-1
    D3[end-i+1,:] = D3[end-boundary_width+1,:]
end
display(D3)

B3 = eye(Int, N); B3[1,1] = B3[2,2]= B3[end,end] = 0
display(B3)

display(D3' * B * D3)
display(D3' * B3 * D3)

# 8th order dissipation
D4 = zeros(Int, N, N)
for i in 1:N
    D4[i,i] = 6
end
for i in 1:N-1
    D4[i+1,i] = -4
    D4[i,i+1] = -4
end
for i in 1:N-2
    D4[i+2,i] = 1
    D4[i,i+2] = 1
end
boundary_width = 2
for i in 1:boundary_width
    D4[i,:] = D4[boundary_width+1,:]
    D4[end-i+1,:] = D4[end-boundary_width,:]
end
display(D4)

B4 = eye(Int, N); B4[1,1] = B4[2,2]= B4[end,end] = B4[end-1,end-1] = 0
display(B4)

display(D4' * B * D4)
display(D4' * B4 * D4)


using Plots

#graphs a cylindrical aprx with n planes, radius =r, height = h
function cylinder(n,r,h)
	p = plot(xlims=[-h,h],ylims=[-2*r,2*r],zlims=[-2*r,2*r],legend=false)
	x = range(0,h,length=10)
	alpha = range(0,2*pi,length=n)

	for theta in alpha
		if theta == alpha[end]
			break
		end
		y=r*cos(theta)*ones(length(x))
		z=r*sin(theta)*ones(length(x))
		plot!(x,y,z,seriescolor=:black)
	end
	display(p)
end

#graphs a function to be revolved, at angles theta_i in alpha
#use at least n=10,15
function PolarLines(a,b,n)

	#f is the function to revolve
	f(u) = 1+sin(u)cos(u)
	p = plot(legend=false)
	x=range(a,b,length=10)
	alpha = range(0,2*pi,length=n)
	for theta in alpha
		if theta == alpha[end]
			break
		end
		y=cos(theta)*f.(x)
		z=sin(theta)*f.(x)
		plot!(x,y,z,seriescolor=:black)
	end
	savefig(p,"fig.html")
	p
end

#graphs a function to be revolved, at vertical slices, and with PolarLines
#use at least n=10
function Rings(a,b,n,m)

	#f is the function to revolve
	f(u) = 1+sin(u)cos(u)
	q = plot([f,0],a,b)

	p = plot(legend=false)
	x=range(a,b,length=n)
	alpha = range(0,2*pi,length=m)

	for xi in x
		y = Float64[]
		z = Float64[]
		xp = xi*ones(m)
		for theta in alpha
			push!(y,cos(theta)*f(xi))
			push!(z,sin(theta)*f(xi))

		end
		plot!(p,xp,y,z,seriescolor=:black)

	end
	p1 = PolarLines(a,b,2*m)

	f = plot(
	p,p1,q,
	layout=@layout([a b;c]),
	legend=false,
	titlelocation=:left,
	bottom_margin=10px,
	window_title="Solid of revolution")
	savefig(f,"figures.html")
	f
end


### A Pluto.jl notebook ###
# v0.12.7

using Markdown
using InteractiveUtils

# ╔═╡ de7a5e8a-1ee4-11eb-2bbf-0f0eb3e489ec
using Pkg, DrWatson

# ╔═╡ e4eeb13a-1ee4-11eb-0113-91f9a9c3b659
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Random
	Random.seed!(1)
end

# ╔═╡ ac3ceabe-1ee4-11eb-3935-313e96aafd62
md"## Listing 2.13"

# ╔═╡ e9fa266e-1eeb-11eb-2630-59930c474ed3
function montyHall(switchPolicy)
    prize, choice = rand(1:3), rand(1:3)
    if prize == choice
        revealed = rand(setdiff(1:3,choice))
    else
        revealed = rand(setdiff(1:3,[prize,choice]))
    end

    if switchPolicy
        choice = setdiff(1:3,[revealed,choice])[1]
    end
    return choice == prize
end

# ╔═╡ 9046a9c8-1f64-11eb-067f-6552cbd8919e
N = 10^6

# ╔═╡ 9046fdce-1f64-11eb-3888-651b7d2d17ac
Text("Success probability with policy I (stay): $(sum([montyHall(false) for _ in 1:N])/N)")

# ╔═╡ 90479df6-1f64-11eb-3e06-af4adc28a9d5
Text("Success probability with policy II (switch): $(sum([montyHall(true) for _ in 1:N])/N)")

# ╔═╡ ea086874-1eeb-11eb-2cb0-4186697d4294
md"## End of listing 2.13"

# ╔═╡ Cell order:
# ╟─ac3ceabe-1ee4-11eb-3935-313e96aafd62
# ╠═de7a5e8a-1ee4-11eb-2bbf-0f0eb3e489ec
# ╠═e4eeb13a-1ee4-11eb-0113-91f9a9c3b659
# ╠═e9fa266e-1eeb-11eb-2630-59930c474ed3
# ╠═9046a9c8-1f64-11eb-067f-6552cbd8919e
# ╠═9046fdce-1f64-11eb-3888-651b7d2d17ac
# ╠═90479df6-1f64-11eb-3e06-af4adc28a9d5
# ╟─ea086874-1eeb-11eb-2cb0-4186697d4294


# Fractal Surface Interpolation demo

using Makie
using Random
using FractalTools
using GeometryBasics
using LinearAlgebra

# Get interpolation data
trig  = Triangle(
    Point(BigFloat(-1.), BigFloat(-1)),
    Point(BigFloat(1.), BigFloat(-1.)),
    Point(BigFloat(0.), BigFloat(2.))
)
f(x, y) = x^2 + y^2 + 1
pnts3d = getdata(f, trig, 100)

# Construct test points
tpnts3d = getdata(f, trig, 100)
tpnts = project(tpnts3d)

α = Node(0.001)
niter = Node(1)
err = lift(niter, α) do n, α
    itp = interpolate(pnts3d, α=α, maxiters=n)
    fval = map(pnt -> f(pnt...), tpnts)
    ival = map(pnt -> itp(pnt...), tpnts)
    val = abs.(fval - ival) ./ abs.(fval) * 100
    val
end

fig = Figure(title="Fractal Interpolation")
ax = Axis(fig[1,1], xlabel="npoints", ylabel="Relative Error (%)", title="Fractal Surface Interpolation Error Demo")
plt = lines!(ax, err)

gl = GridLayout()
gl2 = GridLayout()
gl[1, 1] = slider = Slider(fig, range=0.001 : 0.0001 : 0.01, startvalue=0.001, tellwidth=false)
gl2[1, 1] = button1 = Button(fig, label="Increment", tellwidth=false)
gl2[1, 2] = button2 = Button(fig, label="Decrement", tellwidth=false)
gl[2, 1] = gl2
gl[1, 2] = label1 = Label(fig, "alpha = $(slider.value[])")
gl[2, 2] = label2 = Label(fig, "niter = $(niter[])")
fig[2, 1] = gl

on(button1.clicks) do n
    niter[] += 1
    label2.text[] = "niter = " * string(niter[])
    autolimits!(ax)
end

on(button2.clicks) do n
    niter[] -= 1
    label2.text[] = "niter = " * string(niter[])
    autolimits!(ax)
end

on(slider.value) do val
    α[] = val
    label1.text[] = "alpha = " * string(val)
    autolimits!(ax)
end

display(fig)

# # For video recording.
# fps = 60
# record(fig.scene, "demo.mp4"; framerate = fps) do io
#     for i = 1 : 50 * fps
#         sleep(1/fps)
#         recordframe!(io)
#     end
# end


# https://projecteuler.net/problem=2
# Each new term in the Fibonacci sequence is generated by adding the previous two terms.
# By starting with 1 and 2, the first 10 terms will be:
# 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
# By considering the terms in the Fibonacci sequence whose values do not exceed four million,
# find the sum of the even-valued terms.

result = 0
a = 1
b = 2

while a <= 4000000
    if a % 2 == 0
        result += a
    end
    a = a + b
    a, b = b, a
end

println(result)


# Based on the introductory discussion on
# https://www.maa.org/press/periodicals/loci/joma/the-sir-model-for-spread-of-disease-the-differential-equation-model

## Uncomment the following lines if used as a standalone
# using DifferentialEquations
# using Plots

function SIR(du,u,p,t)
    s,i,r = u
    b,k = p
    du[1] = -b*s*i
    du[2] = b*s*i - k*i
    du[3] = k*i
end

# TODO: make the following a doctest example
    # #initian conditions
    # p = [0.5/7900000.0,0.33]
    # u0 = [7900000.0,10.0,0.0]
    # tspan = (0.0,140.0)

    # #solve
    # sir = ODEProblem(SIR,u0,tspan,p)
    # sol = solve(sir)

    # #plot
    # plot(sol)


function gcd{T<:Integer}(a::T, b::T)
    neg = a < 0
    while b != 0
        t = b
        b = rem(a, b)
        a = t
    end
    g = abs(a)
    neg ? -g : g
end


"
Return the k'th moment of a truncated normal distribution.
"
function moment(d::Truncated{Normal{T},Continuous}, k::Int) where T
    k == 0 ? one(T) : last(moments(d,k))
end

"
Compute the 1'st to k'th moment of a truncated normal distribution. Uses a recursive formula.
"
function moments(d::Truncated{Normal{T},Continuous}, k::Int) where T
    k == 0 && return 1
    m = Array{T}(undef,k+2) #Array of moments with index i being the i-2's moment
                            #(treating the -1's moment as 0 and 0'ths moment as 1)
    m[1], m[2] = 0, 1
    pars = params(d)
    μ, σ, σ² =pars[1], pars[2], pars[2]^2
    L, U = pars[3], pars[4]
    zL, zU = (L - μ)/σ, (U - μ)/σ
    ϕL, ϕU = pdf.(Normal(),(zL, zU))
    ΦUL = cdf(Normal(),zU) - cdf(Normal(),zL)
    for i in 3:(k+2)
        kk = i-2
        #recursive formula for kk'th moment as a function of the previous two moments (if kk=-1 it uses 0)
        m[i] = (kk-1)*σ²*m[i-2] + μ*m[i-1] - σ*(U^(kk-1)*ϕU - L^(kk-1)*ϕL)/ΦUL
    end
    return m[3:(k+2)]
end


module lorenz_model

using DifferentialEquations, Flux, Optim, DiffEqFlux, Plots

model_params=[10.0,28.0,8.0/3]
all_ic=[[1.0,0.0,0.0],[0.5,1.0,0.0], [3,0.2,0.1]];
tend=10.0
tstart=0.0
sampling=0.05

function model(du,u,p,t)
    x,y,z = u
    σ,ρ,β = p
    du[1] = dx = σ*(y-x)
    du[2] = dy = x*(ρ-z) - y
    du[3] = dz = x*y - β*z
end

function InitPlot()

    mycolor=[:blue, :red, :green, :purple, :black, :cyan, :orange]
    t=tstart:sampling:100.0
    odedata=[ solve(ODEProblem(model,η,(tstart,100.0), model_params), Tsit5(), saveat=t, abstol=1e-8,reltol=1e-6) for η in all_ic];
    pl = plot(t,odedata[1][1,:],label="data 1", color=mycolor[1], lw=2, legend=false)

    for k in 2:length(all_ic)
        plot!(pl,t,odedata[k][1,:],label="data $k", color=mycolor[k], lw=2)
    end
    title!(pl, "Lorenz Chaos")
    xlabel!(pl, "Time")
    ylabel!(pl, "Variable")
    savefig("Lorenz_init.png")
    display(pl)

end

function predict_adjoint(param) # Our 1-layer neural network
    prob=ODEProblem(model,[1.0,0.0,0.0],(tstart,tend), model_params)
    Array(concrete_solve(prob,Tsit5(),param[1:3],param[4:end],saveat=tstart:sampling:tend,abstol=1e-8,reltol=1e-6))
end

# Generate some data to fit, and add some noise to it
data=predict_adjoint([1.0,0.0,0.0,10.0,28.0,8.0/3])
σN=0.05
data+=σN*randn(size(data))

#Init guess for the parameters
pguess=[0.8,0.1,0.1,10.5,28.5,9.0/3]

function loss_adjoint(param)
  prediction = predict_adjoint(param)
  loss = sum(abs2,prediction - data)
  loss
end

function train_model(;pguess=[1.1,0.05,-0.05,10.2,28.2,9.0/3])
    println("The initial loss function is $(loss_adjoint(pguess)[1])")
    resinit = DiffEqFlux.sciml_train(loss_adjoint,pguess,ADAM(), maxiters=4000)
    res = DiffEqFlux.sciml_train(loss_adjoint,resinit.minimizer,BFGS(initial_stepnorm=0.0001), maxiters=4000)
    println("The parameters are $(res.minimizer) with final loss value $(res.minimum)")
    return(res)

end

function plotFit(param; vars=(1,2,3), tend=100.0)

    validationPlot(param, param[1:3], vars=vars, tend=tend)

end

function validationPlot(param, ic; vars=(1,2,3), tend=100.0)

    sol_fit=solve(ODEProblem(model,ic,(0.0,tend),param[4:end]), Tsit5())
    sol_actual=solve(ODEProblem(model,ic,(0.0,tend),model_params), Tsit5())
    pl=plot(sol_fit, lw=2, legend=false, vars=vars, color=:green)
    plot!(pl,sol_actual,vars=vars,color=:blue)
    title!(pl,"Model Parameter Fits")
    savefig("valid_plot_lorenz2.png")
    display(pl)

end

end #module

lorenz_model.InitPlot()

resL=lorenz_model.train_model()

lorenz_model.plotFit(resL.minimizer; vars=(1,2,3), tend=30.0)

lorenz_model.plotFit(resL.minimizer; vars=(0,1), tend=30.0)


function factorial4(n)
  if n == 0
    return BigInt(1)
  else
    return n * factorial4(n-1)
  end
end

nCr :: Int16 -> Int16 -> BigInt
function nCr(n, r)
  return BigInt(factorial4(n)/(factorial4(n-r) * factorial4(r)))
end

get_draw :: Int16 -> Float64 -> Int16 -> Float64
get_draw(n, p, x) = Float64(nCr(n, x) * p^x * (1-p)^(n-x))
println(nCr(100, 10))
println(get_draw(5, 1/2, 1))

function binomial_rv2(n, p)
  m = 0
  for i in range(1, n)
    if p < rand()
      m += 1
    end
  end
  return m
end

function binomial_rv3(n, p)
  rands = [rand() for i in range(1, n)]
  return length(rands[rands.<p])
end

binomial_rv4 = (n, p) -> length(filter(x -> x < p, [rand() for i in range(1, n)]))
#binomial_rv3 = (n, p) -> sum(filter(_ -> 1/2 < rand(), [1]*n))
#println(binomial(5, 2))
#println(filter(prob(1/2), [1, 1]))
#println(binomial_rv2(5, 1/2))
#println(binomial_rv3(5, 1/2))
println(binomial_rv4(5, 1/2))


# Equation 33 of https://www.mdpi.com/2227-7390/6/2/16/htm

# Loaded Packages
using FdeSolver
using Plots
using SpecialFunctions

# Parameters
tSpan = [0, 5]         # Time Span
y0 = [1, 0.5, 0.3]     # Initial values
β = [0.5, 0.2, 0.6]    # Order of derivation

# Definition of the System
function F(t, y)

    F1 = 1 / sqrt(pi) * (((y[2] - 0.5) * (y[3] - 0.3))^(1 / 6) + t^(1 / 2))
    F2 = gamma(2.2) * (y[1] - 1)
    F3 = gamma(2.8) / gamma(2.2) * (y[2] - 0.5)

    return [F1, F2, F3]

end

function JF(t,y)
    # System equation
    J11 = 0
    J12 = (y[2]-0.5).^(-5/6).*(y[3]-0.3).^(1/6)/6/sqrt(pi)
    J13 = (y[2]-0.5).^(1/6).*(y[3]-0.3).^(-5/6)/6/sqrt(pi)
    J21 =  gamma(2.2)
    J22 =  0
    J23 =  0
    J31 =  0
    J32 =  gamma(2.8)/gamma(2.2)
    J33 =  0

    J = [J11 J12 J13
         J21 J22 J23
         J31 J32 J33]
    return J
end

# Numerical Solution
t, Yapp = FDEsolver(F, tSpan, y0, β, nc = 5)
t_J, Yapp_J = FDEsolver(F, tSpan, y0, β, JF = JF)

# Plot
plot(t, Yapp, linewidth = 5, title = "Solution of system 33",
     xaxis = "Time (t)", yaxis = "y(t)", label = "Approximation")
# plot!(t_J, Yapp_J, linewidth = 5, ls= :dot, label = "Approximation with Jacob")
plot!(t, t -> (t .+ 1), lw = 3, ls = :dash, color= "red", label = "Exact solution")
plot!(t, t -> (t.^1.2 .+ 0.5), lw = 3, ls = :dash, color= "cyan", label = "Exact solution")
plot!(t, t -> (t.^1.8 .+ 0.3), lw = 3, ls = :dash, color= "black" ,label = "Exact solution")


import FractionalTimeDG
using Printf
using ArgCheck

sample = [1, 2, 10, 100, 1000]
α = 3/4
rmax = 6
Mmax = 2rmax
store = FractionalTimeDG.Store(α, rmax, Mmax)
version = 2

function coef_uniform(sample, r, α, M, version)
    Ns = length(sample)
    H_sample = Matrix{Matrix{Float64}}(undef, Ns, M)
    N = maximum(sample) + 1
    for m = 1:M
        H = FractionalTimeDG.coef_H_uniform!(r, N, m, store, version)
        for n = 1:Ns
            H_sample[n, m] = H[sample[n]][:,:]
        end
    end
    return H_sample
end

function latex_display(A::Matrix{Float64})
    m, n = size(A)
    maxsz = maximum(abs.(A))
    p = min(0, ceil(Integer, log10(maxsz)))
    @printf("10^{%0d}\n", p)
    for i = 1:m
        for j = 1:n-1
            @printf("%8.5f & ", 10.0^(-p) * A[i,j])
        end
        if i < m
            @printf("%8.5f\\\\\n", 10.0^(-p) * A[i,n])
        else
            @printf("%8.5f\n", 10.0^(-p) * A[i,n])
        end
    end
end

H0 = FractionalTimeDG.coef_H0_uniform!(rmax, store)
println("H_0:")
latex_display(H0)
Hs = coef_uniform(sample, rmax, α, Mmax, version)
for n = 1:length(sample)
    ℓ = sample[n]
    println("H_$ℓ:")
    latex_display(Hs[n,Mmax])
end

function pts_needed(Hs, sample, rmax, α, atol)
    Ns, M = size(Hs)
    @argcheck length(sample) == Ns
    first_M = fill(-1, rmax, Ns)
    err = zeros(rmax, M)
    for n = 1:Ns
        ℓ = sample[n]
        for m = 1:M-1
            ΔH = Hs[n,m] - Hs[n,M]
            for r = 1:rmax
                err[r,m] = maximum(abs.(ΔH[1:r,1:r]))
            end
        end
        for r = 1:rmax
            for m = 1:M-1
                if abs(err[r,m]) < atol
                    first_M[r,n] = m
                    break
                end
            end
        end
    end
    return first_M
end

atol = 1e-14
println("\nQuadrature points needed using version = ", version)
first_M = pts_needed(Hs, sample, rmax, α, atol)
@printf("%4s", "r")
for ℓ in sample
    @printf("  ℓ=%4d", ℓ)
end
@printf("\n\n")
for r = 1:rmax
    @printf("%3d&", r)
    for n = 1:length(sample)
        @printf("%7d&", first_M[r,n])
    end
    @printf("\n")
end


"""
    create_fibonacci_sphere(npoint)

Create `npoint` evenly distributed points on the sphere.
"""
function create_fibonacci_sphere(npoint)
    φ = (1 + √5) / 2
    inds = collect(0:npoint-1)'
    θ = 2π / φ * inds
    ϕ = acos.(1 .- 2 .* (inds .+ 0.5) ./ npoint)

    # Create Fibonacci directions (uniformly distributed on unit sphere)
    [
        cos.(θ) .* sin.(ϕ)
        sin.(θ) .* sin.(ϕ)
        cos.(ϕ)
    ]
end


# [function](https://docs.julialang.org/en/v1/manual/functions/#man-functions-1) to calculate the volume of a sphere
function sphere_vol(r)
    # julia allows [Unicode names](https://docs.julialang.org/en/v1/manual/unicode-input/#Unicode-Input-1) (in UTF-8 encoding)
    # so either "pi" or the symbol π can be used
    return 4/3*pi*r^3
end

# functions can also be defined more succinctly
quadratic(a, sqr_term, b) = (-b + sqr_term) / 2a

# calculates x for 0 = a*x^2+b*x+c, [arguments types](https://docs.julialang.org/en/v1/manual/functions/#Further-Reading-1) can be defined in function definitions
function quadratic2(a::Float64, b::Float64, c::Float64)
    # unlike other languages 2a is equivalent to 2*a
    # a^2 is used instead of a**2 or pow(a,2)
    sqr_term = sqrt(b^2-4a*c)
    r1 = quadratic(a, sqr_term, b)
    r2 = quadratic(a, -sqr_term, b)
    # multiple values can be returned from a function using tuples
    # if the [return](https://docs.julialang.org/en/v1/manual/functions/#The-return-Keyword-1) keyword is omitted, the last term is returned
    r1, r2
end

vol = sphere_vol(3)
# @printf allows number formatting but does not automatically append the \n to statements, see below
using Printf
@printf "volume = %0.3f\n" vol
#> volume = 113.097

quad1, quad2 = quadratic2(2.0, -2.0, -12.0)
println("result 1: ", quad1)
#> result 1: 3.0
println("result 2: ", quad2)
#> result 2: -2.0


using Statistics

"""
Calculate the autocorrelation of `data` at the given distance `dist`
"""
function autocorrelation(data::Vector{<:Real}, dist::Integer)
    xᵢ = data[1:(length(data) - dist)]
    xᵢ₊ⱼ = data[(1 + dist):end]
    xᵢxᵢ₊ⱼavg = mean(xᵢ .* xᵢ₊ⱼ)
    return (xᵢxᵢ₊ⱼavg - mean(xᵢ) * mean(xᵢ₊ⱼ)) / var(data)
end


import LinearAlgebra: norm

""" Gradient descent for multidimensional functions Stops when the gradient is smaller than `TOL`,
    or when the maximum number of iterations `maxiter` has been reached"""
function gradient_descent(f, Df, x; α = 0.1, TOL = 1e-10, maxiter = 1000, verbose = false)
    N_iter = 0
    grad = Df(x)

    xn = x
    fn = [f(x)]
    while (N_iter < maxiter) & (norm(grad) > TOL)
        x = x - α*grad

        xn = [xn x]
        fn = [fn f(x)]

        grad = Df(x)
        N_iter += 1

        if (N_iter % 100 == 0) & (verbose == true)# print progress
            fx = fn[end]
            println("Iter. $N_iter,\tx = $x,\tf(x) = $fx")
        end
    end

    return (xn', reshape(fn,:)) # to have the components in the columns
end


"""
Inner product (element-wise product) between two matrices
"""
function innerprod(A::AbstractMatrix{Float64},B::AbstractMatrix{Float64})::Float64
    if size(A) != size(B)
        throw(DimensionMismatch("size of A not equal to size of B"))
    end
    return sum(A.*B)/size(A)[1]
end

"""
Return a copy of the off-diagonal part of matrix
"""
function get_offdiag(A::AbstractMatrix{Float64})::AbstractMatrix{Float64}
    return A-Diagonal(A)
end

"""
Create center finite difference matrix
"""
function get_cfd_mat(ord::Int,n::Int)::SparseMatrixCSC{Float64,Int}
    if ord == 2
        arr = circshift(vcat(Vector{Float64}([1,-2,1]),zeros(Float64,n-3)),-1)
    elseif ord == 4
        arr = circshift(vcat(Vector{Float64}([-1/12,4/3,-5/2,4/3,-1/12]),zeros(Float64,n-5)),-2)
    elseif ord == 8
        arr = circshift(vcat(Vector{Float64}([1/90,-3/20,3/2,-49/18,3/2,-3/20,1/90]),zeros(Float64,n-7)),-3)
    else
        throw("unimplemented")
    end

    A = Matrix{Float64}(undef,n,n)
    for i in 1:n
        A[i,:] = arr
        arr = circshift(arr,1)
    end

    return A
end

function get_stagger_grids(ord::Int,n::Int,a::Vector{Float64})::Matrix{Float64}
    if length(a) != n
        throw("size of vector a not equal to n")
    end

    if ord == 2
        coefs = Float64[  1  0;
                         -1 -1;
                          0  1;]

        h = 1.0/n
        A = zeros(Float64,n,n)

        for i in 1:n
            arr = circshift(a,2-i)[1:2]
            A[i,:] = circshift(vcat(coefs*arr,zeros(Float64,n-3)),i-2)
        end

    elseif ord == 4
        coefs = Float64[ 1/576      0      0      0;
                         -3/64  -3/64      0      0;
                          3/64  81/64   3/64      0;
                        -1/576 -81/64 -81/64 -1/576;
                             0   3/64  81/64   3/64;
                             0      0  -3/64  -3/64;
                             0      0      0  1/576;]

        h = 1.0/n
        A = zeros(Float64,n,n)

        for i in 1:n
            arr = circshift(a,3-i)[1:4]
            A[i,:] = circshift(vcat(coefs*arr,zeros(Float64,n-7)),i-4)
        end

    elseif ord == 6
        coefs = Float64[ 9/409600           0          0          0           0        0;
                         -5/16384    -5/16384          0          0           0        0;
                          45/8192  625/147456    45/8192          0           0        0;
                         -45/8192   -625/8192  -625/8192   -45/8192           0        0;
                          5/16384    625/8192  5625/4096   625/8192     5/16384        0;
                        -9/409600 -625/147456 -5625/4096 -5625/4096 -625/147456 -9/409600;
                                0     5/16384   625/8192  5625/4096    625/8192   5/16384;
                                0           0   -45/8192  -625/8192   -625/8192  -45/8192;
                                0           0          0    45/8192  625/147456   45/8192;
                                0           0          0          0    -5/16384  -5/16384;
                                0           0          0          0           0  9/409600;]

        h = 1.0/n
        A = zeros(Float64,n,n)

        for i in 1:n
            arr = circshift(a,4-i)[1:6]
            A[i,:] = circshift(vcat(coefs*arr,zeros(Float64,n-11)),i-6)
        end
    else
        throw("unimplemented")
    end
    return A
end

"""
Check if a matrix is strictly diagonally dominant
"""
function is_diagally_dominant(A::AbstractMatrix{Float64})
    diff = abs.(diag(A)).-collect(Iterators.flatten(sum(abs.(get_offdiag(A)),dims=1)))
    return minimum(diff)
end

"""
Get 2D meshgrid
"""
function meshgrid(x,y)
    return (reshape(repeat(x,outer=length(y)),(length(x),length(y))),
        reshape(repeat(y,inner=length(x)),(length(x),length(y))))
end

"""
Get index for repartition of girds
"""
function get_repartiton_index(n::Int)::Vector{Int}
    idx = transpose(reshape(collect([1:n*n;]),(n,n)))
    return vcat(collect(Iterators.flatten(idx[1:n÷2,1:n÷2])),
        collect(Iterators.flatten(idx[n÷2+1:n,1:n÷2])),
        collect(Iterators.flatten(idx[1:n÷2,n÷2+1:n])),
        collect(Iterators.flatten(idx[n÷2+1:n,n÷2+1:n]))
    )
end


# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.8.0-DEV
#     language: julia
#     name: julia-1.8
# ---

# %%
using ForwardDiff
using Plots

f(x, y) = exp(-(x^2 + y^2))
u(x, y) = -ForwardDiff.derivative(Base.Fix2(f, y), x) # = f_x(x, y)
v(x, y) = -ForwardDiff.derivative(Base.Fix1(f, x), y) # = f_y(x, y)

xlim = (-1.7, 1.7)
ylim = (-1.7, 1.7)
xs = range(xlim...; length=200)
ys = range(ylim...; length=200)

c = 0.5
x = range(-1.5, 1.5; length=11)
y = range(-1.5, 1.5; length=11)
X, Y = reim(complex.(x', y)) # meshgrid
U, V = c*u.(x', y), c*v.(x', y)

heatmap(xs, ys, f)
quiver!(vec(X-U/2), vec(Y-V/2); quiver=(vec(U), vec(V)), color=:cyan)
plot!(; xlim, ylim, size=(450, 400))

# %%
using ForwardDiff
using Plots

f(x, y) = y * exp(-(x^2 + y^2))
u(x, y) = -ForwardDiff.derivative(Base.Fix2(f, y), x) # = f_x(x, y)
v(x, y) = -ForwardDiff.derivative(Base.Fix1(f, x), y) # = f_y(x, y)

xlim = (-2.2, 2.2)
ylim = (-2.2, 2.2)
xs = range(xlim...; length=200)
ys = range(ylim...; length=200)

c = 0.5
x = range(-2.0, 2.0; length=13)
y = range(-2.0, 2.0; length=13)
X, Y = reim(complex.(x', y)) # meshgrid
U, V = c*u.(x', y), c*v.(x', y)

heatmap(xs, ys, f)
quiver!(vec(X-U/2), vec(Y-V/2); quiver=(vec(U), vec(V)), color=:cyan)
plot!(; xlim, ylim, size=(450, 400))

# %%


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

using Markdown
using InteractiveUtils

# ╔═╡ 006a6698-f9dd-11ea-3f64-152c31c749ef
using Pkg, DrWatson

# ╔═╡ 0090af24-f9dd-11ea-0eb4-eb1758ba5442
begin
	using Distributions
	using StatsPlots, Plots
	using Turing
	using Logging
	using LaTeXStrings
nd

# ╔═╡ 9453eb8e-f9dc-11ea-1865-f168816cdb46
md"## Clip-02-06-07.jl"

# ╔═╡ 009dc1c8-f9dd-11ea-193f-3f1baf487786
md"## snippet 2.6"

# ╔═╡ 009f1ad2-f9dd-11ea-2abc-0579fc5057d2
@model globethrowing(w, l) = begin
    p ~ Uniform(0, 1)
    w ~ Binomial(w + l, p)
end

# ╔═╡ 00a7e61c-f9dd-11ea-028c-ddfdbdd620cb
m = globethrowing(6, 3);

# ╔═╡ 00a87104-f9dd-11ea-2ef2-bf5a7c31c607
r = quap(m)

# ╔═╡ ea69e428-f9e0-11ea-0c97-e3b70306a220
begin
	p_grid = range(0, 1, length = 20)
	prior = ones(20)
	likelihood = pdf.(Binomial.(9, p_grid), 6)
	posterior = likelihood .* prior
	posterior ./= sum(posterior)

	x = 0:0.01:1
	w = 6
	l = 3

	f = plot( x, pdf.(Normal(mean(r.coef.p), √r.vcov[1]), x ), lab="quap")
	plot!( x, pdf.(Beta( w+1 , l+1 ) , x ), lab="exact", leg=:topleft, title="n = $(w+l)")
end

# ╔═╡ 00dd7c78-f9dd-11ea-29ee-b9fb0b25bd56
md"## End clip-02-06-07t.jl"

# ╔═╡ Cell order:
# ╟─9453eb8e-f9dc-11ea-1865-f168816cdb46
# ╠═006a6698-f9dd-11ea-3f64-152c31c749ef
# ╠═0090af24-f9dd-11ea-0eb4-eb1758ba5442
# ╟─009dc1c8-f9dd-11ea-193f-3f1baf487786
# ╠═009f1ad2-f9dd-11ea-2abc-0579fc5057d2
# ╠═00a7e61c-f9dd-11ea-028c-ddfdbdd620cb
# ╠═00a87104-f9dd-11ea-2ef2-bf5a7c31c607
# ╠═ea69e428-f9e0-11ea-0c97-e3b70306a220
# ╟─00dd7c78-f9dd-11ea-29ee-b9fb0b25bd56


module HyperbolicDrawSimpleGraphs

using SimpleGraphs, HyperbolicPlane, SimpleDrawing

import Base: show
import HyperbolicPlane: polar

export HyperbolicGraphEmbedding, hdraw, hembed

mutable struct HyperbolicGraphEmbedding
    G::SimpleGraph
    locs::Dict{Any,HPoint}
end

function show(io::IO, X::HyperbolicGraphEmbedding)
    print(io, "Hyperbolic embedding of $(X.G)")
end

polar(x::Real,y::Real) = (sqrt(x*x+y*y), atan(x,y) )

# convert SimpleGraph to Hyperbolic embedding
function h_convert(GG::SimpleGraph)::HyperbolicGraphEmbedding
    loc = getxy(GG)
    d = Dict{Any,HPoint}()

    for v in GG.V
        xy = loc[v]
        (r,theta) = polar(xy[1],xy[2])
        P = HPoint(r,theta)
        d[v] = P
    end
    return HyperbolicGraphEmbedding(GG,d)
end

function h_circular(GG::SimpleGraph)::HyperbolicGraphEmbedding
    n = NV(GG)
    VV = vlist(GG)
    r = sqrt(n)
    angs = [2*pi*t/n for t=0:n-1]
    d = Dict{Any,HPoint}()
    for j=1:n
        v = VV[j]
        P = HPoint(r,angs[j])
        d[v] = P
    end
    return HyperbolicGraphEmbedding(GG,d)
end

function h_random(G::SimpleGraph)::HyperbolicGraphEmbedding
    d = Dict{Any,HPoint}()
    for v in G.V
        d[v] = RandomHPoint()
    end
    return HyperbolicGraphEmbedding(G,d)
end

function private_adj(G::SimpleGraph)
    n = NV(G)
    A = zeros(Int,n,n)
    vv = vlist(G)
    for i=1:n-1
        u = vv[i]
        for j=(i+1):n
            w = vv[j]
            if has(G,u,w)
                A[i,j] = 1
                A[j,i] = 1
            end
        end
    end
    return A,vv
end

include("myspring.jl")

"""
`spring!(X)` gives the graph held in `X` with a spring embedding
(based on code in the `GraphLayout` module). If runs a default number of
iterations (100) of that algorithm. To change the number of
iterations, use `spring!(X,nits)`.
"""
function h_spring(G::SimpleGraph, nits::Int=100)::HyperbolicGraphEmbedding
    GG = deepcopy(G)
    embed(GG,:spring)
    X = h_convert(GG)
    n = NV(GG)
    locs_r = zeros(n)
    angs = zeros(n)
    i = 1
    for v in G.V
        xy = X.locs[v]
        (r,theta) = polar(xy)
        locs_r[i] = r
        angs[i] = theta
        i = i + 1
    end
    n = NV(G)
    A,vv = private_adj(G)

    locs_r, angs = h_layout_spring_adj(locs_r,angs,A,MAXITER=nits)

    d = Dict{Any,HPoint}()
    for i = 1:n
        v = vv[i]
        P = HPoint(locs_r[i],angs[i])
        d[v] = P
    end

    return HyperbolicGraphEmbedding(G,d)
end

"""
`hembed(G,method)` embeds `G` in the hyperbolic plane by placing vertices
around a circle.

Options for `method`:
+ `:circular` places vertices around a circle
+ `:random` places vertices at random
+ `:convert` converts the Euclidean embedding of `G` into a hyperbolic embedding
"""
function hembed(G::SimpleGraph, method::Symbol = :circular)

    if method == :circular
        X = h_circular(G)
    elseif method == :random
        X = h_random(G)
    elseif method == :convert
        X = h_convert(G)
    elseif method == :spring
        X = h_spring(G)
    else
        error("Unknown method $method")
    end
    cache_save(G,:HyperbolicGraphEmbedding,X)
end

"""
`hdraw(G)` draws the graph `G` in its current hyperoblic embedding
(or creates a default, circular embedding if need be).
"""
function hdraw(G::SimpleGraph)
    if !cache_check(G,:HyperbolicGraphEmbedding)
        hembed(G)
    end
    X = cache_recall(G,:HyperbolicGraphEmbedding)
    newdraw()
    for e in G.E
        u,v = e
        A = X.locs[u]
        B = X.locs[v]
        draw(A+B)
    end
    for v in G.V
        A = X.locs[v]
        A = HPoint(A)
        set_radius(A,2)
        draw(A)
    end
    # draw(HPlane()) # let the user decide to show the line at infinity
    finish()
end

end  # end of module


"""
    tensordecomp(k::Int, d::Int)

Decomposition of the integers  0:(k^p - 1) in powers of k.

"""
function tensordecomp(k::Int, d::Int)

    sequences = zeros(Int, k^d, d)

    for n = 0:k^d-1
        i = d
        m = n

        while i > 0
            i = i - 1
            j = i
            f = floor(Int, m / k^i)

            while j > 0 && f == 0
                j = j - 1
                f = floor(Integer, m / k^j)
            end

            if f > 0
                sequences[n + 1, j + 1] = f
                i = j
            elseif f == 0
                sequences[n + 1, 1] = m
                i = 0
            end
            m = m - f * k^i
        end
    end

    return sequences
end

"""
    even_sampling_rules(dim::Int, split_factor::Int) -> Array{Float64, 2}

Generate rules for evenly distributed points within a simplex. To do this,
we perform a shape-preserving splitting of the simplex, given a splitting
factor.

Returns the convex expansion coefficients of the points of the resulting
subsimplices in terms of the vertices of the original simplex.
"""
function even_sampling_rules(dim::Int, split_factor::Int)

    sequences::Array{Int, 2} = tensordecomp(split_factor, dim)
    n_seq = size(sequences, 1)

    χ1 = sequences .* (dim + 1)
    χ2 = repeat(transpose(collect(1:dim)), n_seq, 1)
    χ::Array{Int, 2} = χ1 .+ χ2
    χ = sort(χ, dims=2)

    # Define multiplicity matrix M
    M = zeros(Float64, size(χ, 1), size(χ, 2) + 1)
    M[:, 1] = χ[:, 1]
    M[:, 2:(end - 1)] = χ[:, 2:end] - χ[:, 1:(end - 1)]
    M[:, end] = (dim+1)*split_factor * ones(size(χ, 1)) - χ[:, end]

    M = M ./ (split_factor * (dim + 1))

    return copy(transpose(M))
end

"""
Evenly sample points within a simplex by performing a shape-preserving
subdivision of the simplex with a given `split_factor`. If the simplex
lives in a space of dimension `dim`, the resulting number of points is
`split_factor`^(dim).
"""
function evenly_sample(simplex::AbstractArray{Float64, 2}, split_factor::Int)
    dim = size(simplex, 2)
    centroids_exp_coeffs = copy(transpose(even_sampling_rules(dim, split_factor)))
    centroids_exp_coeffs * simplex
end


export Exp

"""
`Exp([domainType=Float64::Type,] dim_in::Tuple)`

Creates the exponential non-linear operator with input dimensions `dim_in`:
```math
e^{ \\mathbf{x} }.
```

"""
struct Exp{T,N} <: NonLinearOperator
	dim::NTuple{N,Int}
end

function Exp(DomainType::Type, DomainDim::NTuple{N,Int}) where {N}
	Exp{DomainType,N}(DomainDim)
end

Exp(DomainDim::NTuple{N,Int}) where {N} = Exp{Float64,N}(DomainDim)
Exp(DomainDim::Vararg{Int}) = Exp{Float64,length(DomainDim)}(DomainDim)

function mul!(y::AbstractArray{T,N}, L::Exp{T,N}, x::AbstractArray{T,N}) where {T,N}
	y .= exp.(x)
end

function mul!(y::AbstractArray,
              J::AdjointOperator{Jacobian{A,TT}},
              b::AbstractArray) where {T,N, A<: Exp{T,N}, TT <: AbstractArray{T,N} }
    L = J.A
    y .= conj.(exp.(L.x)).*b
end

fun_name(L::Exp) = "e"

size(L::Exp) = (L.dim, L.dim)

domainType(L::Exp{T,N}) where {T,N} = T
codomainType(L::Exp{T,N}) where {T,N} = T


###################################################################################
#                             load dependencies
###################################################################################
cd(@__DIR__)
using Revise, DifferentialEvolutionMCMC, Random, Distributions
Random.seed!(50514)
###################################################################################
#                             define functions
###################################################################################
# returns prior log likelihood
function prior_loglike(μ, σ)
    LL = 0.0
    LL += logpdf(Normal(0, 1), μ)
    LL += logpdf(truncated(Cauchy(0, 1), 0, Inf), σ)
    return LL
end

# function for initial values
function sample_prior()
    μ = rand(Normal(0, 1))
    σ = rand(truncated(Cauchy(0, 1), 0, Inf))
    return [μ,σ]
end

# likelihood function
function loglike(data, μ, σ)
    return sum(logpdf.(Normal(μ, σ), data))
end
###################################################################################
#                             generate data
###################################################################################
data = rand(Normal(0.0, 1.0), 50)
###################################################################################
#                             configure sampler
###################################################################################
# parameter names
names = (:μ,:σ)
# parameter bounds
bounds = ((-Inf,Inf),(0.0,Inf))

# model object
model = DEModel(;
    sample_prior,
    prior_loglike,
    loglike,
    data,
    names
)

# DEMCMC sampler object
de = DE(;sample_prior, bounds, burnin = 1000, Np = 6)
# number of interations per particle
n_iter = 2000
###################################################################################
#                             estimate parameters
###################################################################################
chains = sample(model, de, MCMCThreads(), n_iter, progress=true)


poisson(T, n) = sparse(Tridiagonal(fill(T(-1), n-1),
                        fill(T(2), n), fill(T(-1), n-1)))
poisson(n) = poisson(Float64, n)

function stencil_grid(T,stencil,sz)
    # upper-bound for storage
    n = prod(sz) * sum(.!iszero,stencil)

    # indices and value of nonzero elements
    Si = zeros(Int,n)
    Sj = zeros(Int,n)
    Ss = zeros(T,n)

    linindices = LinearIndices(sz)
    nnz = 0

    stencil_sz = size(stencil)
    offset = CartesianIndex((stencil_sz .+ 1) .÷ 2)

    for i in CartesianIndices(sz)
        for k in CartesianIndices(stencil_sz)
            if stencil[k] != 0
                j = i + k - offset
                if checkbounds(Bool,linindices,j)
                    nnz = nnz + 1
                    Si[nnz] = linindices[i]
                    Sj[nnz] = linindices[j]
                    Ss[nnz] = stencil[k]
                end
            end
        end
    end

    sparse((@view Si[1:nnz]),
           (@view Sj[1:nnz]),
           (@view Ss[1:nnz]),
           prod(sz),prod(sz))
end

function poisson(T,sz::NTuple{N,Int}) where N
    #=
    In 2D stencil has the value:
    stencil = [0  -1   0;
               -1  4  -1;
               0  -1   0]

    =#
    stencil = zeros(T,ntuple(i -> 3,Val(N)))
    for i = 0:N-1
        Ipre = CartesianIndex(ntuple(l -> 2,i))
        Ipost = CartesianIndex(ntuple(l -> 2,N-i-1))
        stencil[Ipre, 1, Ipost] = -1
        stencil[Ipre, 3, Ipost] = -1
    end
    Icenter = CartesianIndex(ntuple(l -> 2,Val(N)))
    stencil[Icenter] = 2*N

    stencil_grid(T,stencil,sz)
end

poisson(sz::NTuple{N,Int}) where N = poisson(Float64,sz)


module Quadrature

function simps(func::Function, a::Real, b::Real, n::Int)::Float64
    if n % 2 !=0
         throw(ArgumentError("The number of intervals n must be even! You have choosen $n"))
     end
    dx = (b-a)/n
    res = func(a)

    w = 4
    x = 0.
    for i in 1:(n-1)
        x = a + dx*i
        res += func(x)*w
        w = 6 - w
    end
    res += func(b)
    return res * dx/3
end

function simps(func::Function, a::Real, b::Real; tol=1e-5, limit=100)::Float64
    n = 2
    iter=0
    res=0.

    while true
        res=simps(func, a, b, n)
        res1=simps(func, a, b, n*2)
        if abs(res1-res)<tol
            res=res1
            return res, n*2
        end
        n*=2
        iter+=1
        if iter>limit
            print("limit reached!")
            return NaN
        end
    end
    return NaN
end

function gauss_3(func::Function, a::Real, b::Real)::Float64
    w_ = [8 // 9, 5 // 9, 5 // 9]
    x_ = [0., sqrt(3// 5), -sqrt(3//5)]
    res = 0.
    c1 = (b - a) / 2
    c2 = (a + b) / 2

    for i in 1:3
        res += w_[i] * func(c1 * x_[i] + c2)
    end
    res *= c1;
    return res;
end

function gauss(func::Function, a::Real, b::Real, n::Int)::Float64
    dx = (b - a) / n
    res = 0.;
    xa = 0.;
    xb = 0;
    for i in 0:(n-1)
        xa = a + i * dx
        xb = xa + dx
        res += gauss_3(func, xa, xb)
    end
    return res;
end

function gauss(func::Function, a::Real, b::Real; tol=1e-5, verbose=false)::Float64
    n = 2
    while true
        res = gauss(func, a, b, n);
        res1 = gauss(func, a, b, n * 2);

        if abs(res1 - res) < tol
            if verbose == true
                print("n iter: $(n*2)\n")
            end
            return res1
        end

        n *= 2;
    end
end

export gauss
#end module
end


# basisfunctions.jl

"""
    fourier(k::Int)

Implements the Fourier basis of functions ϕ_k, defined, if k is odd, by
ϕ_k(x)=sqrt(2)sin((k+1)π*x) and if k is even, by ϕ_k(x)=sqrt(2)cos(k*x).

# Examples

```julia
using PyPlot
clf()
x = -1.0:0.01:2.0
y = fourier(3).(x)
plot(x,y)
```
"""
function fourier(k::Int)
  k ≥ 1 || error("k should be positive")
  sqrttwo = sqrt(2.0)
  if k % 2 == 1
    return x -> sqrttwo * sinpi((k+1)*x)
  else
    return  x -> sqrttwo * cospi(k*x)
  end
end

"""
    faberschauderone(x::Float64)

Implements the first Faber-Schauder function defined by 1-2x for 0≤x≤1/2 and
-1+2x for 0.5≤x≤1, and is 1-periodic extended to all x∈R.

See also: faberschauder

#Example
```julia
x=-2.:0.001:2
y=faberschauderone.(x)
using PyPlot
clf()
plot(x,y)
```
"""
function faberschauderone(x::Float64)
    y = mod(x, 1.0)
    if 0≤y≤0.5
        return 1.0 - 2*y
    else
        return -1.0 + 2*y
    end
end

"""
    faberschauder(j::Int, k::Int)

Implements the k-th Faber-Schauder function of level j. Here, j≥0 and 1≤k≤2^j.
It is a one periodic function and defined on [0,1] by 2^(j+1)(x-(k-1)2^j) on
(k-1)2^j≤x≤(2k-1)2^(j+1) and 1 - 2^(j+1)(x-(2k-1)2^(j+1)) on
[(2k-1)2^(j+1), k2^j] and zero outside these intervals.

#Example
```julia
j=3
x=-2.:0.001:2.
using PyPlot
clf()
for k in 1:2^j
    y = faberschauder(j,k).(x)
    plot(x,y)
end
```
"""
function faberschauder(j::Int,k::Int)
  j ≥ 0 || error("j should be a nonnegative integer.")
  1≤k≤2^j || error("k should be an integer between 1 and 2^j.")
  j_float = Float64(j)
  return function(x::Float64)
    y = mod(x,1.0)
    if y ≤ (k-1)*2.0^(-j_float) || y ≥ k*2.0^(-j_float)
      return 0.0
    elseif ((k-1)*2.0^(-j_float) < y ≤ (2k-1)*2.0^(-j_float-1.0))
      return 2^(j+1)*(y-(k-1)*2.0^(-j_float))
    else
      return 1.0 - 2^(j+1)*(y-(2k-1)*2.0^(-j_float-1.0))
    end
  end
end


"""
    number_of_steps(distance::Int64, step_size::Int64)

Number of step_size is calculating the total number of combinations for walking a `distance`
for a given number of step_size. For example, for a `distance` of 3 with a maximum
`step_size` of 3 will provide for possible combinations:

    - 1 step + 1 step + 1 step
    - 1 step + 2 step
    - 2 step + 1 step
    - 3 step

# Arguments
- `distance::Int64`: Distance to go
- `step_size::Int64`: Maximum step size of walking a distance

# Examples
```julia-repl
julia> import ClassicAlgorithmsCollections
julia> ClassicAlgorithmsCollections.number_of_steps(8,3)
81
```
"""
function number_of_steps(distance::Int64, step_size::Int64)

    if distance < 0
        return 0
    end
    if distance == 0
        return 1
    end
    result = 0
    for i in 1:step_size
        result += number_of_steps(distance - i, step_size)
    end
    return result
end