# indicator of the L1 norm ball with given radius

export IndBallL1

"""
**Indicator of a ``L_1`` norm ball**

    IndBallL1(r=1.0)

Returns the indicator function of the set
```math
S = \\left\\{ x : ∑_i |x_i| \\leq r \\right\\}.
```
Parameter `r` must be positive.
"""
struct IndBallL1{R <: Real} <: ProximableFunction
  r::R
  function IndBallL1{R}(r::R) where {R <: Real}
    if r <= 0
      error("parameter r must be positive")
    else
      new(r)
    end
  end
end

is_convex(f::IndBallL1) = true
is_set(f::IndBallL1) = true

IndBallL1(r::R=1.0) where {R <: Real} = IndBallL1{R}(r)

function (f::IndBallL1)(x::AbstractArray{T}) where T <: RealOrComplex
  if norm(x,1) - f.r > 1e-12
    return +Inf
  end
  return zero(T)
end

function prox!(y::AbstractArray{T}, f::IndBallL1, x::AbstractArray{T}, gamma::R=one(R)) where {R<: Real, T <: RealOrComplex{R}}
  # TODO: a faster algorithm
  if norm(x,1) - f.r < 1e-14
    y .= x
    return zero(T)
  else # do a projection of abs(x) onto simplex then recover signs
    n = length(x)
    p = abs.(view(x,:))
    sort!(p, rev=true)
    s = zero(R)
    @inbounds for i = 1:n-1
      s = s + p[i]
      tmax = (s - f.r)/i
      if tmax >= p[i+1]
        @inbounds for j in eachindex(x)
          y[j] = sign(x[j])*max(abs(x[j])-tmax, zero(R))
        end
        return zero(T)
      end
    end
    tmax = (s + p[n] - f.r)/n
    @inbounds for j in eachindex(x)
      y[j] = sign(x[j])*max(abs(x[j])-tmax, zero(R))
    end
    return zero(T)
  end
end

fun_name(f::IndBallL1) = "indicator of an L1 norm ball"
fun_dom(f::IndBallL1) = "AbstractArray{Real}, AbstractArray{Complex}"
fun_expr(f::IndBallL1) = "x ↦ 0 if ‖x‖_1 ⩽ r, +∞ otherwise"
fun_params(f::IndBallL1) = "r = $(f.r)"

function prox_naive(f::IndBallL1, x::AbstractArray{T}, gamma::Real=1.0) where T <: RealOrComplex
  # do a simple bisection (aka binary search) on λ
  L = 0.0
  U = maximum(abs, x)
  λ = L
  v = 0.0
  maxit = 120
  for iter in 1:maxit
    λ = 0.5*(L + U)
    v = sum(max.(abs.(x) .- λ, 0.0))
    # modify lower or upper bound
    (v < f.r) ? U = λ : L = λ
    # exit condition
    if abs(L - U) < 1e-15
      break
    end
  end
  return sign.(x) .* max.(0.0, abs.(x) .- λ), 0.0
end


abstract IndexFormula{T<:Number}

"""
refindex(formula, λ_μm) computes the refractive index for a given wavelength in μm.
"""
refindex{T}(::IndexFormula{T}, λ_μm::T) = zero(T);

type SellmeierFormula{T<:Number} <: IndexFormula{T}
  n::Int8
  A::T
  B::Array{T,1}
  C::Array{T,1}
  λn::Bool
  function SellmeierFormula(A,B,C,λn=true)
    n = length(B)
    if length(C) != n
      error("Coefficients B and C of different length")
    else
      new(n,A,B,C,λn)
    end
  end
end

function refindex{T}(formula::SellmeierFormula{T}, λ_μm::T)
  nsq=one(T)+formula.A
  λsq = λ_μm^2
  for i = 1:formula.n
    num = i==formula.n && !formula.λn ? formula.B[i] : formula.B[i] * λsq
    nsq += num/(λsq - formula.C[i])
  end
  return sqrt(nsq)
end

bk7 = SellmeierFormula{Float64}(0, [1.03961212, 0.231792344, 1.01046945], [6.00069867e-3, 2.00179144e-2, 1.03560653e2])
refindex(bk7,0.5)

using Gadfly
plot(x->refindex(bk7,x),0.21,1.6)


########################################################################
function ppca(X; Q=1)
########################################################################

    D, N = size(X)

    μ = vec(mean(X, dims=2))

    JITTER = 1e-8

    @printf("Running PPCA for %d number of data items of dimension %d, projected to %d components\n", N, D, Q)

    @printf("\t Fixing jitter to %f\n", JITTER)

    #---------------------------------------------
    function marginalLogLikelihood(W, σ)
    #---------------------------------------------

        sum(logpdf(MvNormal(μ, W*W' + σ*σ*I + JITTER*I), X))

    end

    #---------------------------------------------
    function unpack(param)
    #---------------------------------------------

        local W = reshape(param[1:end-1], D, Q)

        local σ = exp(param[end]) + 1e-8

        return W, σ

    end

    #---------------------------------------------
    function objective(param)
    #---------------------------------------------

        @assert(length(param) == D*Q + 1)

        local W, σ = unpack(param)

        try

            return -1.0 * marginalLogLikelihood(W, σ)

        catch err

            if isa(err, PosDefException)

                @warn("covariance is not positive definite, returning Inf")

                return Inf

            else

                throw(err)

            end

        end

    end

    #---------------------------------------------
    # Run optimiser
    #---------------------------------------------

    opt    = Optim.Options(show_trace = true, iterations = 100_000)

    result = optimize(objective, [randn(D*Q); randn()*3], NelderMead())# LBFGS(), opt, autodiff=:forward)

    W, σ   = unpack(result.minimizer)

    # @show W, μ, σ
    #
    # #---------------------------------------------
    # # Define projections
    # #---------------------------------------------
    #
    # M = W'*W  + σ*σ*I # eq. (12.41) in Bishop
    #
    # proj(x::Array{T,1} where T<:Real) = vec(M \ (W'*(x - μ)))[1]
    #
    # fwd(z::Real) = vec(W*z + μ)

    return W, μ, σ#, proj, fwd

end


"""
    QuadraticFit{T} <: UnivariateAlgorithm

The quadratic fit algorithm constructs a quadratic function from three points at each
iteration as an approximation to the objective. The analytical solution to the quadratic
model is examined as the new minimizer candidate to update the three points.
"""
struct QuadraticFit{T} <: UnivariateAlgorithm
    ϵ::T

    function QuadraticFit{T}(ϵ) where {T}
        0 < ϵ < 1 ? new{T}(ϵ) : error("`ϵ` must be in the interval (0, 1)")
    end
end

"""
    QuadraticFit([ϵ=0.01])

Initialize quadratic fit search algorithm with optional argument `ϵ` in the interval
(0, 1) to choose a midpoint as `mid = ϵ * lower + (1 - ϵ) * upper`.
"""
QuadraticFit(ϵ=0.5) = QuadraticFit{typeof(ϵ)}(ϵ)

function _optimize(f, lower::T, upper::T, alg::QuadraticFit;
        reltol, abstol, maxiter) where {T}
    ϵ = alg.ϵ
    mid = T(ϵ * lower + (1 - ϵ) * upper)
    y_lower, y_upper, y_mid = f(lower), f(upper), f(mid)
    converged = false
    iter = 0
    x = T(NaN)
    yx = (typeof(y_lower))(NaN)
    while iter < maxiter
        iter += 1
        x = T(0.5 * (y_lower * (mid^2 - upper^2) + y_mid * (upper^2 - lower^2) +
            y_upper * (lower^2 - mid^2)) /
            (y_lower * (mid - upper) + y_mid * (upper - lower) + y_upper * (lower - mid)))
        yx = f(x)
        x_tol = reltol * abs(x) + abstol
        if max(upper - x, x - lower) < 2x_tol
            converged = true
            break
        end
        if x > mid
            if yx > y_mid
                upper, y_upper = x, yx
            else
                lower, y_lower, mid, y_mid = mid, y_mid, x, yx
            end
        elseif x < mid
            if yx > y_mid
                lower, y_lower = x, yx
            else
                upper, y_upper, mid, y_mid = mid, y_mid, x, yx
            end
        end
    end
    return Solution(converged, iter, x, yx)
end


"""
        topsis(decisionMat, weights, fns)

Apply TOPSIS (Technique for Order of Preference by Similarity to Ideal Solution) method
for a given matrix and weights.

# Arguments:
 - `decisionMat::DataFrame`: n × m matrix of objective values for n candidate (or strategy) and m criteria
 - `weights::Array{Float64, 1}`: m-vector of weights that sum up to 1.0. If the sum of weights is not 1.0, it is automatically normalized.
 - `fns::Array{Function, 1}`: m-vector of function that are either minimize or maximize.

# Description
topsis() applies the TOPSIS method to rank n strategies subject to m criteria which are supposed to be either maximized or minimized.

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

# Examples
```julia-repl
julia> df = DataFrame();
julia> df[:, :x] = Float64[9, 8, 7];
julia> df[:, :y] = Float64[7, 7, 8];
julia> df[:, :z] = Float64[6, 9, 6];
julia> df[:, :q] = Float64[7, 6, 6];

julia> w = Float64[4, 2, 6, 8];

julia> df
3×4 DataFrame
 Row │ x        y        z        q
     │ Float64  Float64  Float64  Float64
─────┼────────────────────────────────────
   1 │     9.0      7.0      6.0      7.0
   2 │     8.0      7.0      9.0      6.0
   3 │     7.0      8.0      6.0      6.0

julia> fns = makeminmax([maximum, maximum, maximum, maximum]);
julia> result = topsis(df, w, fns);

julia> result.bestIndex
2

julia> result.scores
3-element Array{Float64,1}:
 0.38768695492211824
 0.6503238218850163
 0.08347670030339041
```

# References
Hwang, C.L.; Yoon, K. (1981). Multiple Attribute Decision Making: Methods and Applications. New York: Springer-Verlag

Celikbilek Yakup, Cok Kriterli Karar Verme Yontemleri, Aciklamali ve Karsilastirmali
Saglik Bilimleri Uygulamalari ile. Editor: Muhlis Ozdemir, Nobel Kitabevi, Ankara, 2018

İşletmeciler, Mühendisler ve Yöneticiler için Operasyonel, Yönetsel ve Stratejik Problemlerin
Çözümünde Çok Kriterli Karar verme Yöntemleri, Editörler: Bahadır Fatih Yıldırım ve Emrah Önder,
Dora, 2. Basım, 2015, ISBN: 978-605-9929-44-8
"""
    function topsis(decisionMat::DataFrame, weights::Array{Float64,1}, fns::Array{Function,1})::TopsisResult

    w = unitize(weights)
    nalternatives, ncriteria = size(decisionMat)

    normalizedMat = normalize(decisionMat)

    weightednormalizedMat = w * normalizedMat

    # col_max = colmaxs(weightednormalizedMat)
    # col_min = colmins(weightednormalizedMat)
    col_max = apply_columns(fns, weightednormalizedMat)
    col_min = apply_columns(reverseminmax(fns), weightednormalizedMat)

    distances_plus  = zeros(Float64, nalternatives)
    distances_minus = zeros(Float64, nalternatives)

    scores = zeros(Float64, nalternatives)

    @inbounds for i in 1:nalternatives
        ithrow = weightednormalizedMat[i,:] |> Array{Float64,1}
		distances_plus[i]  = euclidean(col_max, ithrow)
		distances_minus[i] = euclidean(col_min, ithrow)
		scores[i] = distances_minus[i] / (distances_minus[i] + distances_plus[i])
    end

    best_index = sortperm(scores) |> last

    topsisresult = TopsisResult(
        decisionMat,
        w,
        normalizedMat,
        weightednormalizedMat,
        best_index,
        scores
    )

    return topsisresult
end


#   Unit examples of the FourierAnalysis Package for julia language
#   v 0.0.1 - last update 24th of September 2019
#
#   MIT License
#   Copyright (c) 2019, Marco Congedo, CNRS, Grenobe, France:
#   https://sites.google.com/site/marcocongedo/home

# ? CONTENTS :
#   This example show how to compute coherence
#   and how to extract inforation from them.

#   ~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~  #
#                                                                             #
#   ~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~  #

using FourierAnalysis, FFTW, LinearAlgebra, Statistics, Plots

# add module for reading the two EEG text files to be used ater
push!(LOAD_PATH, @__DIR__)
using IOtxt

# Get EEG file names with complete path (they have extension .txt)
S=getFilesInDir(@__DIR__; ext=(".txt",))

# read the two EEG data files and put them in a Matrix object
X1=readEEG(S[1])
X2=readEEG(S[2])

#############################################################
# test coherence using sinusoids with same frequency and different phase.
using FourierAnalysis, FFTW, LinearAlgebra, Statistics, Plots
t=32
sr=32
f=2
noise=1000
tapering=rectangular
v=sinusoidal(10, f, sr, t*4, 0).+[randn()/noise for i=1:t*4]
w=sinusoidal(10, f, sr, t*4, π/4).+[randn()/noise for i=1:t*4]
y=sinusoidal(10, f, sr, t*4, π/2).+[randn()/noise for i=1:t*4]
z=sinusoidal(10, f, sr, t*4, π).+[randn()/noise for i=1:t*4]
X=[v w y z]
# as compared to v, w is out-of-phase, y is in between and z is at phase opposition
plot(v)
plot!(w)
plot!(y)
plot!(z)

𝘾=coherence(X, sr, t; tapering=tapering, tril=true)
𝘾=coherence(X, sr, t; tapering=tapering, tril=false)
# get all five kinds of coherences
𝘾₁, 𝘾₂, 𝘾₃, 𝘾₄, 𝘾₅=coherence(X, sr, t; tapering=tapering, tril=true, allkinds=true)
𝘾₁, 𝘾₂, 𝘾₃, 𝘾₄, 𝘾₅=coherence(X, sr, t; tapering=tapering, tril=false, allkinds=true)
# check result against cross-spectra
𝐒=crossSpectra(X, sr, t; tapering=tapering, tril=true)
# compute coherence from a CrossSpectra object (lower triangles only)
𝘾=coherence(𝐒)

𝐒=crossSpectra(X, sr, t; tapering=tapering, tril=false)
# compute coherence from a CrossSpectra object (full matrices)
𝘾=coherence(𝐒)

# compute all five kinds of coherence from a CrossSpectra object
𝘾₁, 𝘾₂, 𝘾₃, 𝘾₄, 𝘾₅=coherence(𝐒, allkinds=true)

# average full coherence in range 4:8Hz
Y=mean(𝘾, (4, 8))

# full coherence matrices in range 4:8Hz
E=extract(𝘾, (4, 8))

# average coherence in 1Hz-bands
Z=bands(𝘾, 1)

# smooth coherence a-posteriori
𝘾2=smooth(blackmanSmoother, 𝘾)
C=𝘾.y
D=𝘾2.y

# or get directly smoothed coherence
𝘾3=coherence(X, sr, t; tapering=tapering, smoothing=hannSmoother, tril=true)

############################################
# coherence of several data matrices at once

# Compute the coherence altogether
𝓒=coherence(𝐗, sr, t; tapering=slepians(sr, t))

# get all five kinds of coherences
𝓒₁, 𝓒₂, 𝓒₃, 𝓒₄, 𝓒₅=coherence(𝐗, sr, t; tapering=slepians(sr, t), allkinds=true)


export minkowski_difference, pontryagin_difference

"""
    minkowski_difference(P::LazySet{N}, Q::LazySet{N}) where {N<:Real}

Concrete Minkowski difference (geometric difference) for a pair of
convex sets.

### Input

- `P` -- polytopic set
- `Q` -- compact convex set that is subtracted from `P`

### Output

An `HPolytope` that corresponds to the Minkowski difference of `P` minus `Q` if
`P` is bounded, and an `HPolyhedron` if `P` is unbounded.

### Notes

This function requires that the list of constraints of the set `P` is
available and that the set `Q` is bounded.

### Algorithm

This function implements Theorem 2.3 in [1], which we state next.

Suppose ``P`` is a polyhedron
```math
P = \\{z ∈ ℝ^n: sᵢᵀz ≤ rᵢ,~i = 1, …, N\\}.
```
where ``sᵢ ∈ ℝ^n, sᵢ ≠ 0``, and ``rᵢ ∈ ℝ``.
Assume ``ρ(sᵢ,Q)`` is defined for ``i = 1, …, N``. Then,

```math
P ⊖ Q = \\{z ∈ ℝ^n: sᵢᵀz ≤ rᵢ - ρ(sᵢ,Q),~i = 1, …, N\\}.
```

where ``⊖`` is defined as ``P ⊖ Q = \\{z ∈ ℝ^n: z + v ∈ P  ~∀~v ∈ Q\\}`` and is called
the *Minkowski difference* (also referenced as *Pontryagin difference*, or geometric difference).
It is denoted in [1] as the operation `P ~ Q`.

[1] Ilya Kolmanovsky and Elmer G. Gilbert (1997). *Theory and computation
of disturbance invariant sets for discrete-time linear systems.*
[Mathematical Problems in Engineering Volume 4, Issue 4, Pages
317-367.](http://dx.doi.org/10.1155/S1024123X98000866)
"""
function minkowski_difference(P::LazySet{N}, Q::LazySet{N}) where {N<:Real}

    @assert applicable(constraints_list, P)  "this function " *
        "requires that the list of constraints of its first argument is applicable, but it is not; " *
        "if it is bounded, try overapproximating with an `HPolytope` first"
    @assert isbounded(Q) "this function requires that its second argument is bounded, but it is not"

    A, b = tosimplehrep(P)
    g_PminusQ = [b[i] - ρ(A[i, :], Q) for i in eachindex(b)]
    if isbounded(P)
        return HPolytope(A, g_PminusQ)
    else
        return HPolyhedron(A, g_PminusQ)
    end
end

"""
    pontryagin_difference(P::LazySet{N}, Q::LazySet{N}) where {N<:Real}

An alias for the function `minkowski_difference`.

### Notes

Due to inconsistent naming conventions, both the name *Minkowski difference* and
*Pontryagin difference* are used to refer to the geometric difference of two sets.

"""
const pontryagin_difference = minkowski_difference

# concrete minkowski difference with singleton
minkowski_difference(X::LazySet, S::AbstractSingleton) = translate(X, -element(S))
minkowski_difference(X::LazySet, ::ZeroSet) = X


plan_clenshawcurtis(μ) = length(μ) > 1 ? FFTW.plan_r2r!(μ, FFTW.REDFT00) : fill!(similar(μ),1)'

"""
Compute nodes of the Clenshaw—Curtis quadrature rule.
"""
clenshawcurtisnodes(::Type{T}, N::Int) where T = chebyshevpoints(T, N; kind = 2)

"""
Compute weights of the Clenshaw—Curtis quadrature rule with modified Chebyshev moments of the first kind ``\\mu``.
"""
clenshawcurtisweights(μ::Vector) = clenshawcurtisweights!(copy(μ))
clenshawcurtisweights!(μ::Vector) = clenshawcurtisweights!(μ, plan_clenshawcurtis(μ))
function clenshawcurtisweights!(μ::Vector{T}, plan) where T
    N = length(μ)
    rmul!(μ, inv(N-one(T)))
    plan*μ
    μ[1] *= half(T); μ[N] *= half(T)
    return μ
end


using Plots, LaTeXStrings, Statistics,  Distributions,StatsPlots, JLD
TotalNum=10000
NList=[5,10,100,1000]
Data=[[] for i in 1:length(NList)]
for N in 1:length(NList)
    for i in 1:TotalNum
        push!(Data[N], sum(rand(NList[N])))
    end
end
plots=[]
colors=[:silver, :lightblue,:pink2,:mediumpurple3]
for i in 1:length(NList)
    plt=begin
        histogram(Data[i],color=colors[i],normalize = true, label=L"Histogram\ of\ Samples", alpha=0.9)
        plot!(Normal(mean(Data[i]),std(Data[i])), c = :black, label= L"Normal(\mu = %$(round(mean(Data[i]),digits = 2)), \sigma = %$(round(std(Data[i]),digits = 2)))")
    end
    push!(plots, plt)
end
plot(plots..., size = (1000,1000), plot_title = "Normalized Distribution of Samples", dpi=600)
save("../../chapter6/6.3/samples.jld","Samples", Data)
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter6\\6.3\\Figs\\N_Distribution.png")


"""
    sphere(D)

The well-known D-dimensional Sphere function.
"""
function sphere(D=10)
    # Objective function
    f(x) = sum(x.*x)

    bounds = Array([-100.0ones(D) 100.0ones(D)]')

    x = zeros(D)
    return f, bounds, [generateChild(x, f(x)) ]

end

"""
    discus(D)

The well-known D-dimensional Discus function.
"""
function discus(D = 10)
    # Objective function
    f(x) = 1e6x[1].^2 + sum(x[2:end] .^2)

    bounds = Array([-10.0ones(D) 10.0ones(D)]')

    x = zeros(D)
    return f, bounds, [generateChild(x, f(x))]
end

"""
    rastrigin(D)

The well-known D-dimensional Rastrigin function.
"""
function rastrigin(D = 10)

    # Objective function
    f(x) = 10D+ sum(x.*x - 10cos.(2π*x))

    bounds = Array([-5.0ones(D) 5.0ones(D)]')

    x = zeros(D)
    return f, bounds, [generateChild(x, f(x)) ]

end


module TestSpectralRadius
using Test
using EcologicalNetworks

# All examples are from Figure 1 of SK&A 2013

A = [1 1 1 1; 1 1 1 1; 1 1 1 0; 1 1 0 0; 1 1 0 0; 1 1 0 0]
N = BipartiteNetwork(A.>0)
@test ρ(N; range=EcologicalNetworks.ρ_raw) ≈ 3.82 atol = 0.01

A = [0 1 1 1; 1 0 1 1; 1 1 0 1; 1 1 1 0; 1 1 1 0; 1 0 0 1]
N = BipartiteNetwork(A.>0)
@test ρ(N; range=EcologicalNetworks.ρ_raw) ≈ 3.515 atol = 0.01

A = [1 1 1 1; 1 1 1 0; 1 1 1 0; 1 1 1 0; 1 1 1 0; 1 0 0 0]
N = BipartiteNetwork(A.>0)
@test ρ(N; range=EcologicalNetworks.ρ_raw) ≈ 3.944 atol = 0.01

A = [1 0 1 1; 1 1 0 1; 1 1 1 0; 1 1 1 0; 1 1 1 0; 0 1 0 1]
N = BipartiteNetwork(A.>0)
@test ρ(N; range=EcologicalNetworks.ρ_raw) ≈ 3.595 atol = 0.01

end


# Test Fishing Problem - Switched Nonlinear System
function nonlinsystest(solver=IpoptSolver(tol=1e-04, max_iter = 25); objtol = 1e-3, primaltol = 1e-3)
  println("Testing Switched Nonlinear Systems Optimization ", string(typeof(solver)))

  # Define Time Interval
  t0 = 0.0; tf = 12.0

  uvec = [repmat([0.0; 1.0], 4, 1); 0.0]'  # Input Vector

  # Cost Funcction Matrix
  C = [1.0 0.0 -1.0 0.0;
       0.0 1.0 0.0  -1.0]
  Q = C'*C

  # Define Initial State
  x0 = [0.5; 0.7; 1; 1]

  ### Define System Dynamics
  function nldyn(x::Array{Float64,1}, u::Array{Float64,1})
    n = length(x)
    f = zeros(n)
    f[1] = x[1] - x[1]*x[2] - 0.4*x[1]*u[1]
    f[2] = -x[2] + x[1]*x[2] - 0.2*x[2]*u[1]
    return f

  end

  function nldyn_deriv(x::Array{Float64,1}, u::Array{Float64,1})
    df = [1.0-x[2]-0.4*u[1]       -x[1]                   0    0;
          x[2]                     -1+x[1]-0.2*u[1]       0    0;
          0                        0                      0    0;
          0                        0                      0    0]
  end

  m = stoproblem(x0, nldyn, nldyn_deriv, uvec, ngrid = 300, t0=t0, tf=tf, Q=Q, solver=solver)

  solve!(m)
  # @test getstat(m) == :Optimal

  # Test Optimal Solution
  tauopt = gettau(m)
  @test_approx_eq_eps tauopt[1] 2.4434718158206916 primaltol
  @test_approx_eq_eps tauopt[2] 4.122362522221089 primaltol
  @test_approx_eq_eps tauopt[3] 4.433072844726073 primaltol
  @test_approx_eq_eps tauopt[4] 4.68252002334405  primaltol
  @test_approx_eq_eps tauopt[5] 5.201667827750571 primaltol
  @test_approx_eq_eps tauopt[6] 5.369908894975762 primaltol
  @test_approx_eq_eps tauopt[7] 6.376845190917198 primaltol
  @test_approx_eq_eps tauopt[8] 6.47160340206027 primaltol

  # Test Optimal Value
  @test_approx_eq_eps getobjval(m) 1.3454355602054182 objtol

  println("Passed")
end


# Special Pythagorean triplet

TRIPLET_SUM = 1000

function solve()
    for b in floor(Int, TRIPLET_SUM / (2 + 2^0.5)):(TRIPLET_SUM // 2)
        for a in 1:(b - 1)
            c = (a^2 + b^2)^0.5
            if a + b + c == TRIPLET_SUM
                return a * b * c
            end
        end
    end
end

print(Int(solve()))


import RowEchelon
import LinearAlgebra

#=
Homework 7
Section 3.1
Problems 13
=#

A = [ 4 0 -7 3 -5 ;
     0 0 2 0 0;
     7 3 -6 4 -8;
     5 0 5 2 -3;
     0 0 9 -1 2]

detA = LinearAlgebra.det(A)

println("Original Matrix:")
println(A)
println("Determinate:")
println(detA)


# ---
# title: 413. Arithmetic Slices
# id: problem413
# author: Indigo
# date: 2021-06-20
# difficulty: Medium
# categories: Math, Dynamic Programming
# link: <https://leetcode.com/problems/arithmetic-slices/description/>
# hidden: true
# ---
#
# A sequence of numbers is called arithmetic if it consists of at least three
# elements and if the difference between any two consecutive elements is the
# same.
#
# For example, these are arithmetic sequences:
#
#
#
#     1, 3, 5, 7, 9
#     7, 7, 7, 7
#     3, -1, -5, -9
#
# The following sequence is not arithmetic.
#
#
#
#     1, 1, 2, 5, 7
#
#
#
# A zero-indexed array A consisting of N numbers is given. A slice of that array
# is any pair of integers (P, Q) such that 0 <= P < Q < N.
#
# A slice (P, Q) of the array A is called arithmetic if the sequence:
# A[P], A[P \+ 1], ..., A[Q - 1], A[Q] is arithmetic. In particular, this means
# that P + 1 < Q.
#
# The function should return the number of arithmetic slices in the array A.
#
#
#
# **Example:**
#
#
#
#     A = [1, 2, 3, 4]
#
#     return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself.
#
#
#
## @lc code=start
using LeetCode

function number_of_arithmetic_slices(nums::Vector{Int})
    dp, res = 0, 0
    for i in 3:length(nums)
        if nums[i] + nums[i - 2] == (nums[i - 1] << 1)
            res += (dp += 1)
        else
            dp = 0
        end
    end
    return res
end
## @lc code=end


# A CKAR(2) model
# Here we demonstrate the package's utilities in the context of a simple censored and kinked AR(2) model with two regimes.
#
# Consider the model
# $$\begin{align}
# y_t^*&=\phi_1^*y_{t-1}^*+\phi_1y_{t-1}+\phi_2^*y_{t-1}^*+\phi_2y_{t-1}+\epsilon_t,\\
# y_t&=\max\{y_t^*,0\}.
# \end{align}$$
#
# This features a 'positive regime' ($y^*\geq0$) and a 'negative regime' ($y^*<0$).
##
# Noting $y_t=\mathbf{1}\{y_t^*\geq0\}y_t^*$, we can rewrite the equation for $y_t^*$ as $$y_t^*=(\phi_1^*+\phi_1\mathbf{1}\{y_{t-1}\geq0\})y_{t-1}^*+(\phi_2^*+\phi_2\mathbf{1}\{y_{t-2}\geq0\})y_{t-2}^*+\epsilon_t,$$
# where $\mathbf{1}\{y\geq0\}$ is an indicator variable.
##
using ThresholdStability

function AR2_to_TAR(ϕs, ϕ_stars)
    ϕ1, ϕ2 = ϕs
    ϕ1_star, ϕ2_star = ϕ_stars
    Σ = []
    vals = [1, -1]
    for val1 in vals
        for val2 in vals
            A = zeros(2, 2); A[2, 1] = 1
            A[1, 1] = ϕ1_star + ϕ1 * indicator(val1, 0)
            A[1, 2] = ϕ2_star + ϕ2 * indicator(val2, 0)
            push!(Σ, A)
        end
    end
    return Vector{Array{Float64, 2}}(Σ)
    # NOTE Σ is of form where Σ[1] is with yₜ₋₁,yₜ₋₂ ≥ 0, Σ[2] is with yₜ₋₁ ≥ 0 and yₜ₋₂ < 0, Σ[3] is with yₜ₋₁ < 0 and yₜ₋₂ ≥ 0
    # and Σ[4] is with yₜ₋₁,yₜ₋₂ < 0.
end
##
# This yields a set of four $2\times2$ matrices, with each matrix corresponding to a different regime pair (positive-positive, positive-negative, negative-positive and negative-negative.)
#
# For exposition, consider $\phi_1 = 0.4$, $\phi_1^* = 0.2$, $\phi_2=0.2$, and $\phi_2^*=0.1$:
##
Σ4 = AR2_to_TAR([0.4, 0.2], [0.2, 0.1])
##
# The state space constraints for this model are given by `X4`:
##
E1, E2, E3, E4 = [1 0.; 0 1.], [1 0.; 0 -1.], [-1 0.; 0 1.], [-1 0.; 0 -1.]
D1 = zeros(1,2); D2, D3, D4 = copy(D1), copy(D1), copy(D1)
X4 = [[E1, D1], [E2, D2], [E3, D3], [E4, D4]]
##
# and the automaton can be constructed using
##
G = automaton_constructor(Σ4)
##
# We then have the discrete system
s4 = discreteswitchedsystem(Σ4, G, X4)
##
# Alternatively, by substituting for $y_t$ on the first lag only we can produce a set of two $3\times3$ matrices:
##
function AR2_to_companion(ϕs, ϕ_stars)
    ϕ1, ϕ2 = ϕs
    ϕ1_star, ϕ2_star = ϕ_stars
    Σ = []
    vals = [1, -1]
    for val in vals
        A = zeros(3, 3); A[2, 1] = 1; A[1, 2] = ϕ2_star; A[1, 3] = ϕ2
        A[1, 1] = ϕ1_star + ϕ1 * indicator(val, 0)
        A[3, 1] = indicator(val, 0)
        push!(Σ, A)
    end
    return Vector{Array{Float64, 2}}(Σ)

    # NOTE Σ has form s.t. Σ[1] is with yₜ₋₁ ≥ 0 and Σ[2] is with yₜ₋₁ < 0
end

Σ2 = AR2_to_companion([0.4, 0.2], [0.2, 0.1])
Σ2st = [Σ2[1], Σ2[2], Σ2[1], Σ2[2]]
##
# `Σ2` consists of the two matrices and `Σ2st` corresponds to the same partitioning of the state space as for `Σ4`.

In this latter case, the automaton is again `G` but the state space constraints are now given by `X2`:
##
E1, E2, E3, E4 = [1 0 0.; 0 1 0.], [1 0 0.; 0 -1 0.], [-1 0 0.; 0 1 0.], [-1 0 0.; 0 -1 0.]
D1, D3 = [0 1 -1.], [0 1 -1.]; D2, D4 = [0 0 1.], [0 0 1.]
X2 = [[E1, D1], [E2, D2], [E3, D3], [E4, D4]]
##
# and we construct the discrete switched systems
##
s2 = discreteswitchedsystem(Σ2)
s2st = discreteswitchedsystem(Σ2st, G, X2)
##
# For our choice of parameter values, this model is stable. In particular, the upper bounds on the joint spectral radius (JSR) of `Σ2`, the constrained joint spectral radius (CJSR) of `(Σ4, G)`, and the state-constrained joint spectral radii (SCJSR) of `(Σ4, G, X4)` and `(Σ2st, G, X2)` all agree in value, up to 4 significant figures:
##
@show γ_jsr2 = jsr(s2)
@show γ_cjsr4 = cjsr(s4)
@show γ_scjsr4 = sosbound_γ(s4, 2)
@show γ_scjsr2 = sosbound_γ(s2st, 2)
##
# Indeed, if $\phi_i,\phi_i^*\geq0$ for $i=1,2$, then the model will be stable if and only if
# $\sum_{i=1}^2(\phi_i+\phi_i^*)<1$.
#
# As expected, the model appears stable when plotted:
##
using Plots, Distributions
using LaTeXStrings
pyplot()
function simulate_AR2(y0, Σ, T, σ)  # for companion form
    y = zeros(3, T)
    y[:, 1] = y0
    for t in 1:T-1
        ϵ_t = [rand(Normal(0, σ)), 0., 0.]
        if y[1, t] ≥ 0.
            y[:, t+1] = Σ[1]*y[:, t] + ϵ_t
        else
            y[:, t+1] = Σ[2]*y[:, t] + ϵ_t
        end
    end
    return y
end

function plot_AR2(y0, Σ, T, σ; N = 20, row=1)
    ys = []
    ens_means = zeros(T)
    for i in 1:N
        y = simulate_AR2(y0, Σ, T, σ)
        y = y[row,:]
        push!(ys, y)
        ens_means .+= y
    end
    ens_means ./= N  # ensemble means

    E_y = simulate_AR2(y0, Σ, T, 0.)  # calculating deterministic results
    E_y = E_y[row,:]

    plot(ys, color = :grey, alpha = 0.1, label = "")
    plot!(ens_means, color = :grey, linewidth = 2, label = "Ensemble mean")
    plot!(E_y, color = :blue, linewidth = 2, linestyle = :dash, label = "Deterministic")
    plot!(xlabel=L"t", legend=:topright)
end

plot_AR2(3*ones(3), Σ2, 200, 1)
plot!(ylabel=L"y^*", yguidefontrotation=-90)
##
# Introducing negative parameters, we see that the different upper bounds can diverge.
#
# Consider $\phi_1=0.5$, $\phi_1^*=0.5$, $\phi_2=-0.47$, $\phi_2^*=-0.5$. When plotted, the system appears stable:
##
Σ = AR2_to_companion([0.5, -0.47], [0.5, -0.5])
plot_AR2(3*ones(3), Σ, 200, 1, row=1)
plot!(ylabel=L"y^*", yguidefontrotation=-90)
##
# However, this is a case where the conservatism of (the upper bounds on) the JSR and CJSR would prevent us from concluding the system is stable.
##
Σ = AR2_to_TAR([0.5, -0.47], [0.5, -0.5])
E1, E2, E3, E4 = [1 0.; 0 1.], [1 0.; 0 -1.], [-1 0.; 0 1.], [-1 0.; 0 -1.]
D1 = zeros(1,2); D2, D3, D4 = copy(D1), copy(D1), copy(D1)
X = [[E1, D1], [E2, D2], [E3, D3], [E4, D4]]  # state space constraints
G = automaton_constructor(Σ)
s = discreteswitchedsystem(Σ, G, X)
@show γ_jsr = jsr(s)  # > 1
@show γ_cjsr = cjsr(s)  # > 1
##
# However, the upper bound on the SCJSR is less conservative, yielding a bound below 1. We can thus conclude the system is stable.
##
@show γ_scjsr = sosbound_γ(s, 2)  # < 1
##
# Note that (the upper bound on) the SCJSR remains conservative, even if less so than the CJSR or JSR.
#
# For example, consider $\phi_1=\phi_1^*=0.6$ and $\phi_2=\phi_2^*=-0.6$. This appears stable:
##
Σ = AR2_to_companion([0.6, -0.6], [0.6, -0.6])
plot_AR2(3*ones(3), Σ, 200, 1, row=1)
plot!(ylabel=L"y^*", yguidefontrotation=-90)
##
# Plots for other initial values $y_0$ follow a similar pattern.
#
# However, the bound on the SCJSR exceeds 1 in this case (as does the bound for the CJSR)
##
Σ = AR2_to_TAR([0.6, -0.6], [0.6, -0.6])
s = discreteswitchedsystem(Σ, G, X)
@show γ_cjsr = cjsr(s)
@show γ_scjsr = sosbound_γ(s, 2)


export uncertainty_exponent, basins_fractal_dimension

"""
    basins_fractal_dimension(basins; kwargs...) -> V_ε, N_ε ,d
Estimate the [Fractal Dimension](@ref) `d` of the boundary between basins of attraction using
the box-counting algorithm.

The output `N_ε` is a vector with the number of the balls of radius `ε` (in pixels)
that contain at least two initial conditions that lead to different attractors. `V_ε`
is a vector with the corresponding size of the balls. The ouput `d` is the estimation
of the box-counting dimension of the boundary by fitting a line in the `log.(N_ε)`
vs `log.(1/V_ε)` curve. However it is recommended to analyze the curve directly
for more accuracy.

## Keyword arguments
* `range_ε = 2:maximum(size(basins))÷20` is the range of sizes of the ball to
  test (in pixels).

## Description

It is the implementation of the popular algorithm of the estimation of the box-counting
dimension. The algorithm search for a covering the boundary with `N_ε` boxes of size
`ε` in pixels.
"""
function basins_fractal_dimension(basins::AbstractArray; range_ε = 3:maximum(size(basins))÷20)

    dims = size(basins)
    num_step = length(range_ε)
    N_u = zeros(Int, num_step) # number of uncertain box
    N = zeros(Int, num_step) # number of boxes
    V_ε = zeros(1, num_step) # resolution

    # Naive box counting estimator
    for (k,eps) in enumerate(range_ε)
        Nb, Nu = 0, 0
        # get indices of boxes
        bx_tuple = ntuple(i -> range(1, dims[i] - rem(dims[i],eps), step = eps), length(dims))
        box_indices = CartesianIndices(bx_tuple)
        for box in box_indices
            # compute the range of indices for the current box
            ind = CartesianIndices(ntuple(i -> range(box[i], box[i]+eps-1, step = 1), length(dims)))
            c = basins[ind]
            if length(unique(c))>1
                Nu = Nu + 1
            end
            Nb += 1
        end
        N_u[k] = Nu
        N[k] = Nb
        V_ε[k] = eps
    end
    N_ε = N_u
    # remove zeros in case there are any:
    ind = N_ε .> 0.0
    N_ε = N_ε[ind]
    V_ε = V_ε[ind]
    # get exponent via liner regression on `f_ε ~ ε^α`
    b, d = linreg(vec(-log10.(V_ε)), vec(log10.(N_ε)))
    return V_ε, N_ε, d
end

"""
    uncertainty_exponent(basins; kwargs...) -> ε, N_ε ,α
Estimate the uncertainty exponent[^Grebogi1983] of the basins of attraction. This exponent
is related to the final state sensitivity of the trajectories in the phase space.
An exponent close to `1` means basins with smooth boundaries whereas an exponent close
to `0` represent complety fractalized basins, also called riddled basins.

The output `N_ε` is a vector with the number of the balls of radius `ε` (in pixels)
that contain at least two initial conditions that lead to different attractors.
The ouput `α` is the estimation of the uncertainty exponent using the box-counting
dimension of the boundary by fitting a line in the `log.(N_ε)` vs `log.(1/ε)` curve.
However it is recommended to analyze the curve directly for more accuracy.

## Keyword arguments
* `range_ε = 2:maximum(size(basins))÷20` is the range of sizes of the ball to
  test (in pixels).

## Description

A phase space with a fractal boundary may cause a uncertainty on the final state of the
dynamical system for a given initial condition. A measure of this final state sensitivity
is the uncertainty exponent. The algorithm probes the basin of attraction with balls
of size `ε` at random. If there are a least two initial conditions that lead to different
attractors, a ball is tagged "uncertain". `f_ε` is the fraction of "uncertain balls" to the
total number of tries in the basin. In analogy to the fractal dimension, there is a scaling
law between, `f_ε ~ ε^α`. The number that characterizes this scaling is called the
uncertainty exponent `α`.

Notice that the uncertainty exponent and the box counting dimension of the boundary are
related. We have `Δ₀ = D - α` where `Δ₀` is the box counting dimension computed with
[`basins_fractal_dimension`](@ref) and `D` is the dimension of the phase space.
The algorithm first estimates the box counting dimension of the boundary and
returns the uncertainty exponent.

[^Grebogi1983]: C. Grebogi, S. W. McDonald, E. Ott and J. A. Yorke, Final state sensitivity: An obstruction to predictability, Physics Letters A, 99, 9, 1983
"""
function uncertainty_exponent(basins::AbstractArray; range_ε = 2:maximum(size(basins))÷20)
    V_ε, N_ε, d = basins_fractal_dimension(basins; range_ε)
    return V_ε, N_ε, length(size(basins)) - d
end


# export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
using TensorFlowDiffEq
using Base.Test

using Plots; plotly()
using DiffEqBase, ParameterizedFunctions
using DiffEqProblemLibrary, DiffEqDevTools

# Toy problem 1

f = (t,u) -> -u/5 + exp(-t/5).*cos(t)
(::typeof(f))(::Type{Val{:analytic}},t,u0) =  exp(-t/5)*(u0 + sin(t))
prob = ODEProblem(f,Float32(0.0),(Float32(0.0),Float32(2.0)))
sol = solve(prob,odetf(),dt=0.02)

sol(0.5)
sol(0.5,idxs=1)
plot(sol,plot_analytic=true)

prob = ODEProblem(f,0.0,(0.0,2.0))
sol = solve(prob,odetf(),dt=0.02)

sol = solve(prob,odetf(hl_width=[128,64,32,64,128]),dt=0.02,maxiters=Int(1e5))

dts = 1./2.^(8:-1:2) #14->7 good plot
prob = ODEProblem(f,0.0,(0.0,1.0))
sim  = test_convergence(dts,prob,odetf(hl_width=[128,64,32,64,128]))
@test abs(sim.𝒪est[:l2]-1) < 0.2

# Standard Convergence Problem

dts = 1./2.^(14:-1:7) #14->7 good plot
sim_linear  = test_convergence(dts,prob_ode_linear,odetf(),maxiters=Int(1e5))
@test abs(sim_linear.𝒪est[:l2]-1) < 0.2

# Problem 2

function lotka_voltera_tf(t,u)
  du1 = 1.5 .* u[:,1] - 1.0 .* u[:,1].*u[:,2]
  du2 = -3 .* u[:,2] + u[:,1].*u[:,2]
  [du1 du2]
end
prob = ODEProblem(lotka_voltera_tf,Float32[1.0,1.0],(Float32(0.0),Float32(10.0)))
sol = solve(prob,odetf(hl_width=[256]),dt=0.01,maxiters=Int(1e4),progress_steps=500)

sol(0.5,idxs=1)
sol([0.5,0.52])
plot(sol)

sol = solve(prob,odetf(hl_width=[128,128]),dt=0.01,maxiters=Int(1e4),progress_steps=500)
plot(sol)

sol = solve(prob,odetf(hl_width=[128,64, 64]),dt=0.01,maxiters=Int(1e4),progress_steps=500)
plot(sol)

# Toy problem 3

function lorenz_tf(t,u)
    du1 = 10.0(u[:, 2]-u[:,1])
    du2 = u[:,1].*(28.0-u[:, 3]) - u[:, 2]
    du3 = u[:,1].*u[:,2] - (8/3)*u[:,3]

    [du1 du2 du3]
end
prob = ODEProblem(lorenz_tf,Float32[1.0,0.0,0.0],(Float32(0.0),Float32(100.0)))
sol = solve(prob,odetf(hl_width=256),dt=0.1,maxiters=Int(1e5),progress_steps=500)

plot(sol,vars=(1,2,3))

# Macro Usage

g = @ode_def LorenzExample begin
  dx = σ*(y-x)
  dy = x*(ρ-z) - y
  dz = x*y - β*z
end σ=10.0 ρ=28.0 β=(8/3)

# g.vector_ex and g.vector_ex_return are vectorized forms
# g(Val{:vec},t,u,du) and g(Val{:vec},t,u)


using Plots
using BenchmarkTools
#2(a)
function vfsolvex(vnew, kgrid, tolerance, imax, σ=1.5)
    β=0.9

    v=vnew .+ 2*tolerance
    cartesianindex = Array{CartesianIndex{2}, length(v)}
    i=1

    c = kgrid .- kgrid'
    c[c .< 0] .= 0
    if σ==1
      u = log.(c)
    else
      u = (c.^(1-σ).-1)./(1-σ)
    end
    u[kgrid .- kgrid' .< 0] .= -Inf

    while maximum(abs.(v-vnew)) > tolerance && i <= imax
      v = vnew;

      (vnew, cartesianindex) = findmax(u .+ β*v', dims = 2);
      i += 1;
    end
    kprimeindex = getindex.(cartesianindex, 2)
    return (v=vnew, kprime=kgrid[kprimeindex], kprimeindex=kprimeindex)
  end

  kupper = 5
  klower = 0.01
  n = 500
  kgrid = collect(range(klower, stop=kupper, length = n))
  (v, kprime, kprimeindex) = vfsolvex(zeros(n), kgrid, 0.001, 1000)
  #(b)
  (v, kprime, kprimeindex) = vfsolvex(zeros(500), kgrid, 0.001, 1000);
   plot(kgrid, v, label = "v, σ=1.5")

  #(c)
  plot()
  for x in [1.25, 1.1, 1.001, 1.00001, 1]
    kgrid = collect(range(klower, stop=kupper, length=500))
    (v, kprime, kprimeindex) = vfsolvex(zeros(500), kgrid, 0.001, n, x);
    display(plot!(kgrid, v, label = "v, σ=$x"))
  end
#as σ->1, the value function is getting closer to log utility value function
#the vfsolvex autometically compute log utility solution when σ=1.
#We also solve the log utility problem analytically in PS 5, and the numerical and
#analytical solution coinside

#(d)
function policy(x)
    e=getindex(findall(kgrid .== x),1)
    return kprime[e]
end

function findindex(x)
    a=findmin(abs.(x .- kgrid), dims=1)
    return a[2]
end

function findkpath(T, k_0)
    w = findindex(k_0)
    kpath = zeros(T)
    k = kgrid[w]
        for i in 1:T
            k = policy(k)
            kpath[i] = k
        end
    return kpath
end

function plotkpath(T, k_0)
    w = findindex(k_0)
    kpath = zeros(T)
    k = kgrid[w]
        for i in 1:T
            k = policy(k)
            kpath[i] = k
        end
    time = collect(range(1, T, length = T))
    plot(time, kpath, label = "kpath")
end

findkpath(100, 5)
plotkpath(100, 5)
# this is a cake eating problem with no production, so capital always decline


module TSExplore

### Work through of Ch. 8 Brockwell and Davis 2002,
### "Introduction to Time Series and Forecasting," 2nd ed.

"""
The notation `WN(0, Rt²)` denotes an uuncorrelated sequence of random variables with mean
zero and standard deviation `Rt` at each time index `t`.
"""

## Observation equation p. 260
export obs_eq

"""
    Yt = Gt Xt + Wt

The variable `Wt` is independent zero mean white noise with standard deviation `Rt` for each
`t`.

"""
obs_eq(Gt, Xt, Wt) = Gt*Xt + Wt

## State equation p. 260
export state_eq

"""
    X(t+1) = Ft Xt + Vt

The variable `Vt` is independent zero mean white noise with standard deviation `Qt` for each
`t`.

"""
state_eq(Ft, Xt, Vt) = Ft*Xt + Vt

## Example 8.1.1 AR(1) process

export ar1_eq_direct, AR1Model

"""
    y(t+1) = ϕYt + Zt

The process `Zt ~ WN(0, σ²)`

"""
ar1_eq_direct(ϕ, y, z) = ϕ*y + z

# In state space notation.
struct AR1Model
    ϕ
    F
    G

    function AR1Model(ϕ)
        F = reshape([ϕ], 1, 1)
        G = ones(eltype(ϕ), 1, 1)
        return new(ϕ, F, G)
    end
end

state_eq(m::AR1Model, x, z) = state_eq(m.F, x, [z])
obs_eq(m::AR1Model, x, z) = obs_eq(m.G, x, zeros(eltype(z), 1))

## Example 8.1.2 ARMA(1,1) process
export ARMA11Model, arma11_eq_direct

"""
    y(t+1) = ϕYt + Zt + θZ(t-1)

The process `Zt ~ WN(0, σ²)`

"""
arma11_eq_direct(ϕ, θ, y, zt, ztm1) = ϕ*y + z + θ*ztm1

# In State Space notation
struct ARMA11Model
    ϕ  # AR param
    θ  # MA param
    F  # state transition matrix
    G  # observation transition matrix

    function ARMA11Model(ϕ, θ)
        F = [0 1; 0 ϕ]
        G = [θ 1]
        return new(ϕ, θ, F, G)
    end
end

state_eq(m::ARMA11Model, x, z) = state_eq(m.F, x, [zero(first(x)), z])
obs_eq(m::ARMA11Model, x, z) = obs_eq(m.G, x, zeros(eltype(x), 1))

### Simulate utilities for exploring processes.
include("simulate.jl")

end # module


"""
    add_intercept(A)

Insert an intercept column (column of ones) at the beginning of a 2d array.

# Arguments

- A = 2d array of floats

# Value

Returns A with an intercept column

"""
function add_intercept(A::AbstractArray{Float64,2})

    # Check that the first column isn't a column of ones
    if all(A[:,1].==1)
        print("The first column of this array is already a column of ones. Are you sure you wanted to add an intercept?")
    end

    # Concatenate a column of ones to the beginning of A
    return hcat(ones(size(A,1)), A)
end

"""
    remove_intercept(A)

Remove the intercept column, assumed to be the first column of a 2d array.

# Arguments

- A = 2d array of floats

# Value

Returns A without the intercept column

"""
function remove_intercept(A::AbstractArray{Float64,2})

    # Check that the first column is a column of ones
    if any(A[:,1].!=1)
        print("The first column of this array does not look like an itercept
               since it is not a column of ones. Are you sure you wanted to
               remove it?")
    end

    # Remove the first column of A
    return A[:,2:end]
end

"""
    shuffle_rows(A)

Shuffles rows of a 2d array

# Arguments

- A = 2d array of floats

# Value

Returns A with rows shuffled

"""
function shuffle_rows(A::AbstractArray{Float64,2})

    return A[Random.shuffle(1:size(A,1)),:]
end

"""
    shuffle_cols(A)

Shuffles columns of a 2d array

# Arguments

- A = 2d array of floats

# Value

Returns A with columns shuffled

"""
function shuffle_cols(A::AbstractArray{Float64,2})

    return A[:,Random.shuffle(1:size(A,2))]
end


# Kernel definition is taken from here: https://en.wikipedia.org/wiki/Kernel_(statistics)#Kernel_functions_in_common_use

export Epanechnikov_kernel, Uniform_kernel, Triangular_kernel, Quartic_Kernel, Triweight_Kernel, Tricube_Kernel, Gaussian_Kernel, Cosine_Kernel, Logistic_Kernel, Sigmoid_Kernel, Silverman_Kernel

function Epanechnikov_kernel(t)
    if abs(t) > 1
        return 0
    else
        return 0.75*(1-t^2)
    end
end

function Uniform_kernel(t)
    if abs(t) > 1
        return 0
    else
        return 0.5
    end
end

function Triangular_kernel(t)
    if abs(t) > 1
        return 0
    else
        return (1-abs(t))
    end
end

function Quartic_Kernel(t)
  if abs(t)>0
    return 0
  else
    return (15*(1-t^2)^2)/16
  end
end

function Triweight_Kernel(t)
  if abs(t)>0
    return 0
  else
    return (35*(1-t^2)^3)/32
  end
end

function Tricube_Kernel(t)
  if abs(t)>0
    return 0
  else
    return (70*(1-abs(t)^3)^3)/80
  end
end

function Gaussian_Kernel(t)
  exp(-0.5*t^2)/(sqrt(2*π))
end

function Cosine_Kernel(t)
  if abs(t)>0
    return 0
  else
    return (π*cos(π*t/2))/4
  end
end

function Logistic_Kernel(t)
  1/(exp(t)+2+exp(-t))
end

function Sigmoid_Kernel(t)
  2/(π*(exp(t)+exp(-t)))
end

function Silverman_Kernel(t)
  sin(abs(t)/2+π/4)*0.5*exp(-abs(t)/sqrt(2))
end


s1 = StreamingStats(Float64)
s2 = StreamingStats(10,Float64)

ntestelements = 1000
x1 = randn(ntestelements)
x2 = randn(10,ntestelements)
m1,v1 = streamingstats(x1)
m2,v2 = streamingstats(x2)

for i=1:ntestelements
    update!(s1,x1[i])
    update!(s2,x2[:,i])
    #test the results of the update function against ground truth
    @test_approx_eq_eps mean(s1) mean(x1[1:i]) 1e-10
    @test_approx_eq_eps var(s1) var(x1[1:i]) 1e-10
    @test_approx_eq_eps std(s1) std(x1[1:i]) 1e-10
    @test_approx_eq_eps mean(s2) mean(x2[:,1:i],2) 1e-10
    @test_approx_eq_eps var(s2) var(x2[:,1:i],2) 1e-10
    @test_approx_eq_eps std(s2) std(x2[:,1:i],2) 1e-10
    #test the results of the streamstats function
    @test_approx_eq_eps m1[i] mean(s1) 1e-10
    @test_approx_eq_eps v1[i] var(s1) 1e-10
    @test_approx_eq_eps m2[:,i] mean(s2) 1e-10
    @test_approx_eq_eps v2[:,i] var(s2) 1e-10
end

println("==================")
println("Test show function")
println("==================")
show(s1)
show(s2)
println("=============")
println("End test show")
println("=============")


using ProximalOperators

A = [  1.0  -2.0   3.0  -4.0  5.0;
       2.0  -1.0   0.0  -1.0  3.0;
      -1.0   0.0   4.0  -3.0  2.0;
      -1.0  -1.0  -1.0   1.0  3.0]
b = [1.0, 2.0, 3.0, 4.0]

m, n = size(A)

f = Translate(LogisticLoss(ones(m)), -b)
lam = 0.1
g = NormL1(lam)

x_star = [0, 0, 2.114635341704963e-01, 0, 2.845881348733116e+00]

# Nonfast/Adaptive

x0 = zeros(n)
@time it, x, sol = ProximalAlgorithms.FBS(x0; fs=f, As=A, g=g, tol=1e-6, adaptive=true)
@test norm(x - x_star, Inf) <= 1e-4
@test it < 1700
println(sol)

# Fast/Adaptive

x0 = zeros(n)
@time it, x, sol = ProximalAlgorithms.FBS(x0; fs=f, As=A, g=g, tol=1e-6, adaptive=true, fast=true)
@test norm(x - x_star, Inf) <= 1e-4
@test it < 500
println(sol)

# ZeroFPR/Adaptive

x0 = zeros(n)
@time it, x, sol = ProximalAlgorithms.ZeroFPR(x0; fs=f, As=A, g=g, tol=1e-6, adaptive=true)
@test norm(x - x_star, Inf) <= 1e-4
@test it < 25
println(sol)

# PANOC/Adaptive

x0 = zeros(n)
@time it, x, sol = ProximalAlgorithms.PANOC(x0; fs=f, As=A, g=g, tol=1e-6, adaptive=true)
@test norm(x - x_star, Inf) <= 1e-4
@test it < 35
println(sol)


# Various simple graph functions associated with graph coloring.

export bipartition, two_color, greedy_color, random_greedy_color

# Create a two-coloring of a graph or die trying. Returns a map from
# the vertex set to the set {1,2}, or error if no such mapping exists.

"""
`two_color(G)` creates a two-coloring of the graph or throws an error
if tghe graph is not bipartite. The output is a `Dict` mapping the
vertex set to the values 1 and 2.
"""
function two_color{T}(G::SimpleGraph{T})
    f = Dict{T,Int}()
    for A in components(G)
        a = first(A)
        f[a] = 1
        Q = Deque{T}()
        push!(Q,a)
        while length(Q)>0
            v = pop!(Q)
            Nv = G[v]
            for w in Nv
                if haskey(f,w)
                    if f[w]==f[v]
                        error("Graph is not bipartite")
                    end
                else
                    f[w] = 3-f[v]
                    push!(Q,w)
                end
            end
        end
    end
    return f
end

# Create a bipartition of a graph or die trying. Returns a set {X,Y}
# that is a bipartition of the vertex set of G.

using SimplePartitions
"""
`bipartition(G)` creates a bipartition of the graph (or returns an
error if the graph is not bipartite. Output is a `Partition`.
"""
function bipartition{T}(G::SimpleGraph{T})
  f = two_color(G)
  return Partition(f)
end

# Color a graph by the greedy algorithm in the sequence specified by
# seq. The array seq must be a permutation of G.V. We don't check
# that's true!

"""
`greedy_color(G,seq)` creates a greedy proper coloring of the
graph. The argument `seq` should be a 1-dimensional array containing
every vertex exactly once (the function does not check this for
you). The function follows that order in creating the coloring which
is returned to you as a `Dict` mapping vertices to positive integers
(representing the colors).

If `seq` is omitted, a random permutation of the vertices is used.
"""
function greedy_color{T}(G::SimpleGraph{T}, seq::Array{T,1})
    f = Dict{T,Int}()  # this is the mapping from V to colors
    maxf::Int = 0      # largest color used

    for v in seq
        colors_used = falses(maxf)  # array if color is used by N[v]
        for w in G[v]
            if haskey(f,w)  # w already colored
                colors_used[f[w]]=true  # mark that color is used
            end
        end
        # give first unused color to v
        for k in 1:maxf
            if colors_used[k] == false
                f[v] = k
                break
            end
        end
        # but if that fails, extend the number of colors available
        if !haskey(f,v)
            maxf += 1
            f[v] = maxf
        end
    end
    return f
end

# This function returns a list of the vertices of G in descending
# order by degree. The order of vertices of the same degree is
# indeterminate. NOTE: This is not exported from this module. Should
# it be?
function deg_sorted_vlist(G::SimpleGraph)
    bye = x -> -x[1]
    list = [ (deg(G,v) , v) for v in G.V ]
    sort!(list, by=bye)
    outlist = [ item[2] for item in list ]
    return outlist
end

# Apply greedy_color to the graph visiting the vertices in decreasing
# order of degree.
function greedy_color{T}(G::SimpleGraph{T})
    seq = deg_sorted_vlist(G)
    return greedy_color(G,seq)
end

# Generate multiple random orders of the vertex set and apply
# greedy_color; return one that uses the fewest colors. This do as
# well as or better than greedy_color on some decreasing order of
# degree.

"""
`random_greedy_color(G, reps)` repeatedly invokes `greedy_color` using
random permutations of the vertex set. After `reps` iterations, the
best coloring found is returned.
"""
function random_greedy_color{T}(G::SimpleGraph{T}, reps::Int=1)
    n = NV(G)
    bestf = greedy_color(G)  # degree order default start
    best  = maximum(values(bestf))
    seq = vlist(G)
    println("Initial coloring uses ", best, " colors")

    for k in 1:reps
        shuffle!(seq)
        f = greedy_color(G,seq)
        mx = maximum(values(f))
        if mx < best
            bestf = f
            best = mx
            println("Reduced to ", best, " colors")
        end
    end
    return bestf
end


# ------------------------------------------------------------------------------------------
# ## Functions
#
#
# In the last notebook, we talked about modeling data with functions. A **function** is one
# of the most fundamental concepts in computing (and also in mathematics).
#
# A function is a piece of a program that receives **input arguments**, processes them by
# doing certain calculations on them, and returns **outputs**.
#
# For example, we might have a function `g` that takes a number as an input and returns the
# square of that number as an output. How can we define this function `g` on a computer?
# Julia gives us a few different ways to do this.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ### Defining functions
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# Firstly, we could write `g` as follows:
# ------------------------------------------------------------------------------------------

g(x) = x^2

a = "Machine is learning is fun "
g(a)

# ------------------------------------------------------------------------------------------
# Alternatively, we could declare this function using the `function` and `end` keywords:
# ------------------------------------------------------------------------------------------

function g1(x)
    return x^2
end

# ------------------------------------------------------------------------------------------
# The third way we could have declared this function is as an "anonymous" or "lambda"
# function. "Anonymous" functions are functions that truly don't need names! For example, we
# could have declared a function that squares its input as
# ------------------------------------------------------------------------------------------

(x -> x^2)("I ♡ Julia. ") # \heartsuit + <tab>

# ------------------------------------------------------------------------------------------
# Now that we've done that, we can't access the function `x -> x^2` again because we have no
# name to call! That seems a little silly, doesn't it?
#
# Actually, there are times where functions without names are useful to us. We'll see that
# later in this notebook. For now, note that you have the *option* to access an "anonymous"
# function later by binding a variable to it when you declare it. For example,
# ------------------------------------------------------------------------------------------

g = x -> x^2

# ------------------------------------------------------------------------------------------
# This syntax says, "I want to use the variable g to access a function that takes some input
# called `x` and maps that input to the square of `x`.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## An important sigmoidal function
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# A particular function that is used a lot in machine learning is a so-called "sigmoidal"
# function (meaning a function that is S-shaped, i.e. the graph of the function looks like
# an `S`).
#
# The sigmoid function that we will use is given the name $\sigma$, and is defined by the
# following mathematical expression:
#
# $$\sigma(x) := \frac{1}{1 + \exp(-x)}.$$
# ------------------------------------------------------------------------------------------

σ(w*x) = 1/ (1+e^(-w*x))

# ------------------------------------------------------------------------------------------
# #### Exercise 1
#
# Use the first syntax given above to define the function `σ` in Julia. Note that Julia
# actually allows us to use the symbol σ as a variable name! To do so, type `\sigma<TAB>` in
# the code cell.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Plotting functions
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# Let's draw the function σ to see what it looks like. Throughout this course, we'll use the
# Julia package `Plots.jl` for all of the graphics. This package provides a flexible syntax
# for plotting, in which options to change attributes like the width of the lines used in
# the figure are given as named keyword arguments.
#
# In addition, it allows us to use different "backends", which are the other libraries that
# actually carry out the plotting following the instructions from `Plots.jl`.
# ------------------------------------------------------------------------------------------

using Plots
gr()   # use the PlotlyJS "backend" (plotting library)

plot(σ, -5, 5)

hline!([0, 1], ls=:dash, lw=3)  # add horizontal lines at 0 and 1, with dashed style and linewidth 3
vline!([0], ls=:dash, lw=3)     # add a vertical line at 0

# ------------------------------------------------------------------------------------------
# We can think of $\sigma$ as a smooth version of a step or threshold function (often called
# a "Heaviside" function). To see this, let's modify the steepness of the jump in $\sigma$
# and compare it to the Heaviside function; we'll see how all this works in more detail
# later:
# ------------------------------------------------------------------------------------------

using Interact

heaviside(x) = x < 0 ? 0.0 : 1.0

@manipulate for w in 0.1:0.1:20
    plot(x -> σ(w*x), -5, 5, label="sigma", lw=2)
    plot!(heaviside, ls=:dash, label="step")
end

# ------------------------------------------------------------------------------------------
# This particular function takes any real number as input, and gives an output between $0$
# and $1$. It is continuous and smooth.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# #### Exercise 2
#
# Declare the sigmoid function above as an anonymous function with a different name.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ### Mutating functions: `...!`
#
# To generate our plot of σ above, we used some functions that end with `!`. What does a `!`
# at the end of a function name mean in Julia?
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# Functions that change or modify their inputs are called **mutating functions**. But wait,
# don't all functions do that?
#
# No, actually. Functions typically take *inputs* and use those *inputs* to generate
# *outputs*, but the inputs themselves usually don't actually get changed by a function. For
# example, copy and execute the following code:
#
# ```julia
# v1 = [9, 4, 7, 11]
# v2 = sort(v1)
# ```
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# `v2` is a sorted version of `v1`, but after calling `sort`, `v1` is still unsorted.
#
# However, now trying adding an exclamation point after `sort` and executing the following
# code:
#
# ```julia
# sort!(v1)
# ```
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# Look at the values in `v1` now!
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# This time, the original vector itself was changed (mutated), and is now sorted. Unlike
# `sort`, `sort!` is a mutating function. Did the `!` make `sort!` mutating? Well, no, not
# really. In Julia, `!` indicates mutating functions by convention. When the author of
# `sort!` wrote `sort!`, they added a `!` to let you to know that `sort!` is mutating, but
# the `!` isn't what makes a function mutating or non-mutating in the first place.
#
# #### Exercise
#
# Some of our plotting commands end with `!`. Copy and execute the following code:
#
# ```julia
# r = -5:0.1:5
# g(x) = x^2
# h(x) = x^3
# plot(r, g, label="g")
# plot!(r, h, label="h")
# ```
#
# Then change the code slightly to remove the `!` after `plot!(r, h)`. How does this change
# your output? What do you think it means to add `!` after plotting commands?
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Pointwise application of functions, `.(...)` (known as "broadcasting")
#
# We saw in a previous notebook that we needed to add `.` after the names of some functions,
# as in
#
# ```julia
# green_amount = mean(Float64.(green.(apple)))
# ```
#
# What are those extra `.`s really doing?
#
# When we add a `.` after a function's name, we are telling Julia that we want to
# "**broadcast**" that function over the inputs passed to the function. This means that we
# want to apply that function *element-wise* over the inputs; in other words, it will apply
# the function to each element of the input, and return an array with the newly-calculated
# values.
#
# For example, copy and execute the following code:
# ```julia
# g.(r)
# ```
# Since the function `g` squares it's input, this squares all the elements of the range `r`.
#
# What happens if instead we just call `g` on `r` via
#
# ```julia
# g(r)
# ```
# ? Try this out and see what happens.
# ------------------------------------------------------------------------------------------

x = [1 2 3;4 5 6]
f(t) = √t + 5
f.(x)

# ------------------------------------------------------------------------------------------
# You should see an error message after calling `g(r)`, which says that Julia cannot
# multiply two vectors. When we call `g(r)`, we ask Julia to multiply `r` by `r`. When we
# call `g.(r)`, we ask Julia to multiply *each element* in `r` by itself.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# #### Exercise 3
#
# Copy and execute the following code to get the type of the object `numbers = [1, 2,
# "three", 4.0]`:
#
# ```julia
# numbers = [1, 2, "three", 4.0]
# typeof(numbers)
# ```
#
# What is the type of `numbers`?
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# #### Exercise 4
#
# Broadcast `typeof` over `numbers` to see what the types of the elements stored inside
# `numbers` are.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# #### Exercise 5
#
# Write a `for` loop that applies `g` to each of the elements of `r` and prints the results.
# Verify that the numbers printed by this `for` loop are equal to the entries of the output
# of `g.(r)`.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# #### Exercise 6
#
# Define a range `xs` between -5 and 5 with steps of 0.5.
# Apply the $\sigma$ function pointwise to this range and define `ys` as the result.
# What does the result look like? Plot these as points and join them with lines.
#
# Make the plot interactive where you can vary the step size. Fix the range of the plot in
# the `x` and `y` directions using the functions `xlims!` and `ylims!`.
# ------------------------------------------------------------------------------------------

.nbexports/introductory-tutorials/broader-topics-and-ecosystem/intro-to-ml/04. Tools - Functions.jl


# functions for building various standard types of graphs

export Complete, Path, Cycle, RandomGraph, RandomRegular
export RandomTree, code_to_tree
export Grid, Wheel, Cube, BuckyBall
export Petersen, Kneser, Paley, Knight

"""
`Complete(n)` returns a complete graph with `n` vertices `1:n`.

`Complete(n,m)` returns a complete bipartite graph with `n` vertices
in one part and `m` vertices in the other.

`Complete([n1,n2,...,nt])` returns a complete multipartite graph with
parts of size `n1`, `n2`, ..., `nt`.
"""
# Create a complete graph
function Complete(n::Int)
    G = IntGraph(n)

    for k=1:n-1
        for j=k+1:n
            add!(G,j,k)
        end
    end
    return G
end

# Create a complete bipartite graph
function Complete(n::Int, m::Int)
    G = IntGraph(n+m)
    for u=1:n
        for v=n+1:n+m
            add!(G,u,v)
        end
    end
    return G
end

# Create the complete multipartite graph with given part sizes
function Complete(parts::Array{Int,1})
    # check all part sizes are positive
    for p in parts
        if p < 1
            error("All part sizes must be positive")
        end
    end

    n = sum(parts)
    G = IntGraph(n)

    np = length(parts)
    if np < 2
        return G
    end

    # create table of part ranges
    ranges = Array(Int,np,2)
    ranges[1,1] = 1
    ranges[1,2] = parts[1]
    for k = 2:np
        ranges[k,1] = ranges[k-1,2] + 1
        ranges[k,2] = ranges[k,1] + parts[k] - 1
    end

    # Add all edges between all parts
    for i=1:np-1
        for j=i+1:np
            for u=ranges[i,1]:ranges[i,2]
                for v=ranges[j,1]:ranges[j,2]
                    add!(G,u,v)
                end
            end
        end
    end

    return G
end

# Create a path graph on n vertices
"""
`Path(n)` creates a path graph with `n` vertices named `1:n`.

`Path(array)` creates a path graph with vertices `array[1]`,
`array[2]`, etc.
"""

function Path(n::Int)
    G = IntGraph(n)
    for v = 1:n-1
        add!(G,v,v+1)
    end
    return G
end

# Create a path graph from a list of vertices
function Path{T}(verts::Array{T})
    G = SimpleGraph{T}()
    n = length(verts)

    if n==1
        add!(G,verts[1])
    end
    for k = 1:n-1
        add!(G,verts[k],verts[k+1])
    end
    return G
end

# Create a cycle graph on n vertices
function Cycle(n::Int)
    if n<3
        error("Cycle requires 3 or more vertices")
    end
    G = Path(n)
    add!(G,1,n)
    return G
end

# Create the wheel graph on n vertices: a cycle on n-1 vertices plus
# an additional vertex adjacent to all the vertices on the wheel.
"""
`Wheel(n)` creates a wheel graph with `n` vertices. That is, a cycle
with `n-1` vertices `1:(n-1)` all adjacent to a common single vertex,
`n`.
"""
function Wheel(n::Int)
    if n < 4
        error("Wheel graphs must have at least 4 vertices")
    end
    G = Cycle(n-1)
    for k=1:n-1
        add!(G,k,n)
    end
    return G
end

# Create a grid graph
"""
`Grid(n,m)` creates an `n`-by-`m` grid graph. For other grids, we
suggest `Path(n1)*Path(n2)*Path(n3)` optionally wrapped in
`relabel`. See also: `Cube`.
"""
function Grid(n::Int, m::Int)
    G = SimpleGraph{Tuple{Int,Int}}()

    # add the vertices
    for u=1:n
        for v=1:m
            add!(G,(u,v))
        end
    end

    #horizontal edges
    for u=1:n
        for v=1:m-1
            add!(G,(u,v),(u,v+1))
        end
    end

    # vertical edges
    for v=1:m
        for u=1:n-1
            add!(G,(u,v),(u+1,v))
        end
    end
    return G
end

# Create an Erdos-Renyi random graph
"""
`RandomGraph(n,p=0.5)` creates an Erdos-Renyi random graph with `n`
vertices and edge probability `p`.
"""
function RandomGraph(n::Int, p::Real=0.5)
    G = IntGraph(n)

    # guess the size of the edge set to preallocate storage
    m = round(Int,n*n*p)+1

    # generate the edges
    for v=1:n-1
        for w=v+1:n
            if (rand() < p)
                add!(G,v,w)
            end
        end
    end
    return G
end

# Generate a random tree on vertex set 1:n. All n^(n-2) trees are
# equally likely.

"""
`RandomTree(n)` creates a random tree on `n` vertices each with
probability `1/n^(n-2)`.
"""
function RandomTree(n::Int)
    if n<0   # but we allow n==0 to give empty graph
        error("Number of vertices cannot be negative")
    end

    if n<2
        return IntGraph(n)
    end

    code = [ mod(rand(Int),n)+1 for _ in 1:n-2 ]
    return code_to_tree(code)
end

# This is a helper function for RandomTree that converts a Prufer code
# to a tree. No checks are done on the array fed into this function.
function code_to_tree(code::Array{Int,1})
    n = length(code)+2
    G = IntGraph(n)
    degree = ones(Int,n)  # initially all 1s

    #every time a vertex appears in code[], up its degree by 1
    for c in code
        degree[c]+=1
    end

    for u in code
        for v in 1:n
            if degree[v]==1
                add!(G,u,v)
                degree[u] -= 1
                degree[v] -= 1
                break
            end
        end
    end

    last = find(degree)
    add!(G,last[1],last[2])

    return G
end

# Create the Cube graph with 2^n vertices
"""
`Cube(n)` creates the `n`-dimensional cube graph. This graph has `2^n`
vertices named by all possible length-`n` strings of 0s and 1s. Two
vertices are adjacent iff they differ in exactly one position.
"""
function Cube(n::Integer=3)
    G = StringGraph()
    for u=0:2^n-1
        for shift=0:n-1
            v = (1<<shift) $ u
            add!(G,bin(u,n), bin(v,n))
        end
    end
    return G
end

# Create the BuckyBall graph

"""
`BuckyBall()` returns the Bucky ball graph.
"""
function BuckyBall()
    G = IntGraph()
    edges = [(1,3), (1,49), (1,60), (2,4), (2,10), (2,59),
	     (3,4), (3,37), (4,18), (5,7), (5,9), (5,13),
	     (6,8), (6,10), (6,17), (7,8), (7,21), (8,22),
	     (9,10), (9,57), (11,12), (11,13), (11,21), (12,28),
	     (12,48), (13,14), (14,47), (14,55), (15,16), (15,17),
	     (15,22), (16,26), (16,42), (17,18), (18,41), (19,20),
	     (19,21), (19,27), (20,22), (20,25), (23,24), (23,32),
	     (23,35), (24,26), (24,39), (25,26), (25,31), (27,28),
	     (27,31), (28,30), (29,30), (29,32), (29,36), (30,45),
	     (31,32), (33,35), (33,40), (33,51), (34,36), (34,46),
	     (34,52), (35,36), (37,38), (37,41), (38,40), (38,53),
	     (39,40), (39,42), (41,42), (43,44), (43,47), (43,56),
	     (44,46), (44,54), (45,46), (45,48), (47,48), (49,50),
	     (49,53), (50,54), (50,58), (51,52), (51,53), (52,54),
	     (55,56), (55,57), (56,58), (57,59), (58,60), (59,60),
	     ]
    for e in edges
        add!(G,e[1],e[2])
    end
    return G
end

# The Kneser graph Kneser(n,k) has C(n,k) vertices that are the
# k-element subsets of 1:n in which two vertices are adjacent if (as
# sets) they are disjoint. The Petersen graph is Kneser(5,2).

"""
`Kneser(n,m)` creates the Kneser graph whose vertices are all the
`m`-element subsets of `1:n` in which two vertices are adjacent iff
they are disjoint.
"""
function Kneser(n::Int,k::Int)
    A = collect(1:n)
    vtcs = [Set(v) for v in subsets(A,k)]
    G = SimpleGraph{Set{Int}}()

    for v in vtcs
        add!(G,v)
    end

    n = length(vtcs)
    for i=1:n-1
        u = vtcs[i]
        for j=i+1:n
            v = vtcs[j]
            if length(intersect(u,v))==0
                add!(G,u,v)
            end
        end
    end

    return G
end

# Create the Petersen graph.
"""
`Petersen()` returns the Petersen graph. The vertices are labeled as
the 2-element subsets of `1:5`. Wrap in `relabel` to have vertices
named `1:10`. See also: `Kneser`.
"""

Petersen() = Kneser(5,2)

# Create Paley graphs

"""
`Paley(p)` creates the Paley graph with `p` vertices named
`0:(p-1)`. Here `p` must be a prime with `p%4==1`. Vertices `u` and
`v` are adjacent iff `u-v` is a quadratic residue (perfect square)
modulo `p`.
"""
function Paley(p::Int)
    if mod(p,4) != 1 || ~isprime(p)
        error("p must be a prime congruent to 1 mod 4")
    end

    # Quadratic residues mod p
    qrlist = unique( [ mod(k*k,p) for k=1:p ] )

    G = IntGraph()
    for u = 0:p-1
        for k in qrlist
            v = mod(u+k,p)
            add!(G,u,v)
        end
    end
    return G
end

# Called by RandomRegular ... one step
function RandomRegularBuilder(n::Int, d::Int)
    # caller has already checked the values of n,d are legit
    vlist = randperm(n*d)
    G = IntGraph(n*d)
    for v=1:2:n*d
        add!(G,vlist[v], vlist[v+1])
    end

    for v = n:-1:1
        mushlist = collect( d*(v-1)+1 : v*d )
        for k=d-1:-1:1
            contract!(G,mushlist[k],mushlist[k+1])
        end
    end
    return relabel(G)
end

"""
`RandomRegular(n,d)` creates a random `d`-regular graph on `n`
vertices. This can take a while especially if the arguments are
large. Call with an optional third argument to activate verbose
progress reports: `RandomRegular(n,p,true)`.
"""
function RandomRegular(n::Int, d::Int, verbose::Bool=false)
    # sanity checks
    if n<1 || d<1 || (n*d)%2==1
        error("n,d must be positive integers and n*d even")
    end
    if verbose
        println("Trying to build ", d, "-regular graph on ",
                n, " vertices")
        count::Int = 0
    end

    while true
        if verbose
            count += 1
            println("Attempt ", count)
            tic()
        end
        g = RandomRegularBuilder(n,d)
        if verbose
            toc();
        end
        dlist = deg(g)
        if dlist[1] == dlist[n]
            if verbose
                println("Success")
            end
            return g
        end
        if verbose
            println("Failed; trying again")
        end
    end
end

"""
`Knight(r::Int=8,c::Int=8)` creates a Knight's Moves graph on a
`r`-by-`c` grid. That is, the vertices of this graph are the squares
of an `r`-by-`c` chess board. Two vertices are adjacent if a Knight
can go from one of these squares to the other in a single move.
"""
function Knight(r::Int=8,c::Int=8)
    vtcs = collect(product(1:r,1:c))
    G = SimpleGraph{Tuple{Int64,Int64}}()
    for v in vtcs
        add!(G,v)
    end

    for v in vtcs
        for w in vtcs
            xv = collect(v)
            xw = collect(w)
            z = sort(map(abs,xv - xw))
            if z==[1,2]
                add!(G,v,w)
            end
        end
    end

    return G
end


@testset "646.maximum-length-of-pair-chain.jl" begin
    @test find_longest_chain([(1, 2), (2, 3), (3, 4), (1, 3)]) == 2
    @test find_longest_chain([(1, 2), (1, 2), (1, 2)]) == 1
    @test find_longest_chain([(1, 2), (2, 3)]) == 1
    @test find_longest_chain([(1, 2), (2, 3), (3, 4)]) == 2
    @test find_longest_chain(Tuple{Int, Int}[]) == 0
end


#TODO make it a function on ExplicitODE and Options

"""

Chooses an initial step-size basing on the equation, initial data,
time span and the order of the method of integration.

"""
function dtinit{T,S}(F, y0::Vector{S}, tspan::Vector{T}, reltol, abstol; order = 1)
    t0 = abs(tspan[1])
    tstop = abs(tspan[end])
    tau = max(reltol*norm(y0, Inf), abstol)
    d0 = norm(y0, Inf)/tau
    f0 = F(t0, y0)
    d1 = norm(f0, Inf)/tau
    if min(d0,d1) < eps(T)^(1/3)
        dt0 = eps(T)^(1/3)/10
    else
        dt0 = (d0/d1)/100
    end
    # perform Euler step
    y1 = similar(y0)
    for d = 1:length(y1)
        y1[d] = y0[d]+dt0*f0[d]
    end
    f1 = F(t0 + dt0, y1)
    # estimate second derivative
    d2 = norm(f1 - f0, Inf)/(tau*dt0)
    if max(d1, d2) <= 10*eps(T)
        dt1 = max(eps(T)^(1/3)/10, dt0/10^3)
    else
        pow = -(2 + log10(max(d1, d2)))/(order+1)
        dt1 = 10^pow
    end
    return min(100*dt0, dt1, abs(tstop-t0))
end

# a scalar version of the above
dtinit(F, y0::Number, args...; kargs...) = dtinit((t,y)->[F(t,y[1])], [y0], args...; kargs...)

"""

A simple bisection algorithm for finding a root of a solution f(x)=0
starting within the range x∈rng, the result is a point x₀ which is
located within the distance eps from the true root of f(x)=0.  For
this algorithm to work we need f(rng[1]) to have a different sign then
f(rng[2]).

"""
function findroot(f,rng,eps)
    xl, xr = rng
    fl, fr = f(xl), f(xr)

    if fl*fr > 0 || xl > xr
        error("Inconsistent bracket")
    end

    while xr-xl > eps
        xm = (xl+xr)/2
        fm = f(xm)

        if fm*fr > 0
            xr = xm
            fr = fm
        else
            xl = xm
            fl = fm
        end
    end

    return (xr+xl)/2
end


using FractalTools
using Makie
using Makie
import Makie.GeometryBasics: TriangleFace

# Construct delaunay
p1, p2, p3 = [-1., √3.], [-1., -√3], [2., 0.]
tridln = TriDelaunay(p1, p2, p3, addboundarypoints=true)

while npoints(tridln) ≤ 1000
    addpoint!(tridln)
end

finegrain!(tridln, 100)

# Define function
f(x, y) = x^2 + y^2

# Plot the result
fig, ax, plt = tridelaunayplotf(tridln, f, vmarkersize=10)
wireframe!(tomesh(tridln.delaunay), linewidth=3)
display(fig)


@show [1 2 3; 3 2 1] .+ [2 1 2; 0 2 1]
@show [1 2 3; 2 1 2] .+ 1
@show [1 2 3; 2 2 1] .- [1 1 1; 2 1 0]
@show [1 2 1; 1 2 3] .* [3 2 1; 1 0 1]
@show [1 2 3; 3 2 1] .* 2
@show [9 8 6; 3 2 3] ./ [3 1 2; 2 1 2]
@show [3 2 2; 1 2 3] .^ [1 2 3; 2 1 2]


#=
Author: Shuangchi He / Yulv
Email: yulvchi@qq.com
Date: 2022-02-15 12:32:43
LastEditors: Shuangchi He
LastEditTime: 2022-02-25 21:45:58
Description: Statistical Analysis for Pearson Correlation and Bland-Altman Agreement
=#
using Polynomials
using PyPlot
using Statistics

function Pearson_Correlation_Bland_Altman_Agreement(X, Y)
    # Pearson Correlation
    linearfit=fit(X, Y, 1)  # Linear fit.
    k = linearfit[1]
    b = linearfit[0]

    scatter(X, Y, label="Num: $(length(X))", color="r", s=8)
    plot(sort(X), sort(X * k .+ b), color="b", linestyle="--",
        label="linear_fit: y = $(round(k, digits=5)) x + $(round(b, digits=5))\npearson coefficient: $(round(cor(X, Y), digits=5))")
    plot([findmin([findmin(X)[1], findmin(Y)[1]])[1], findmax([findmax(X)[1], findmax(Y)[1]])[1]], [findmin([findmin(X)[1], findmin(Y)[1]])[1], findmax([findmax(X)[1], findmax(Y)[1]])[1]],
        color="k", linestyle="--", label="Equal Line")

    xlabel("Measurement_predict (mm)")
    ylabel("Measurement_GT (mm)")
    title("Pearson Correlation")
    legend(loc=0)
    savefig("Measurement_Pearson_Correlation.png")
    close()

    # Bland-Altman Agreement
    data_mean = (X + Y) / 2.
    data_diff = X - Y
    mean_diff = mean(data_diff)
    std_diff = std(data_diff)  # Julia std defaults to correcting for bias in sample variance by dividing by N-1.

    linearfit2=fit(data_mean, data_diff, 1)  # Linear fit.
    k2 = linearfit2[1]
    b2 = linearfit2[0]

    scatter(data_mean, data_diff, label="Diff num: $(length(data_mean))", color="r", s=8)
    plot(sort(data_mean), sort(mean_diff * ones(length(data_mean))), color="k", linestyle="--",
        label="Mean_Diff: $(round(mean_diff, digits=5))")
    plot(sort(data_mean), sort((mean_diff .+ 1.96 * std_diff) * ones(length(data_mean))), color="b", linestyle="--",
        label="Mean_Diff + 1.96 Std_Diff: $(round(mean_diff + 1.96 * std_diff, digits=5))")
    plot(sort(data_mean), sort((mean_diff .- 1.96 * std_diff) * ones(length(data_mean))), color="b", linestyle="-.",
        label="Mean_Diff - 1.96 Std_Diff: $(round(mean_diff - 1.96 * std_diff, digits=5))")
    plot(sort(data_mean), sort(data_mean * k2 .+ b2), color="y", linestyle="--",
        label="linear_fit: y = $(round(k2, digits=5)) x + $(round(b2, digits=5))")

    xlabel("Mean of Measurement_predict and Measurement_GT (mm)")
    ylabel("Diff between Measurement_predict and Measurement_GT (mm)")
    title("Bland-Altman Agreement")
    legend(loc=0)
    savefig("Measurement_Bland-Altman_Agreement.png")
end

X = [0.125, 0.95, 0.55, 0.60, 0.78, 0.46, 0.88, 0.50, 0.93, 0.35, 0.975, 0.725, 0.285, 0.166, 0.666, 0.888, 0.233]
Y = [0.127, 0.97, 0.53, 0.57, 0.72, 0.49, 0.91, 0.52, 0.90, 0.37, 0.982, 0.718, 0.277, 0.175, 0.666, 0.88, 0.2333]
Pearson_Correlation_Bland_Altman_Agreement(X, Y)


using PolyFit
using Test,Polynomials,LinearAlgebra

@testset "no errors" begin
    # Should get the same answer as Polynomials.polyfit
    x = 1:10

    for m = 1:5
	    y = x .^ m
	    for n = 1:m
		    P = polyfit(PolyFit.linear_system_type(length(x),n),x,y,n)
		    P2 = Polynomials.polyfit(x,y,n)
		    @test P ≈ P2
		end
	end
end

@testset "same errors" begin
    # Should get the same answer as Polynomials.polyfit
    x = 1:5
	y = x .^ 2

    P = polyfit(x,y,2,1)
    P2 = Polynomials.polyfit(x,y,2)
    @test P ≈ P2

    P = polyfit(x,y,2,I)
	@test P ≈ P2
end

@testset "independent errors" begin
    # Should get the same answer as Polynomials.polyfit
    x = 1:5
	y = x .^ 2

    P = polyfit(x,y,2,ones(length(x)))
    P2 = Polynomials.polyfit(x,y,2)
    @test P ≈ P2

	P = polyfit(x,y,2,Diagonal(ones(length(x))))
	@test P ≈ P2

	P = polyfit(x,y,2,Diagonal(2ones(length(x))))
	@test P ≈ P2
end

@testset "covariance matrix" begin
    x = 1:5
	y = x .^ 2

	# Should get the same answer as Polynomials.polyfit
	P = polyfit(x,y,2,Array(Diagonal(ones(length(x)))))
	P2 = Polynomials.polyfit(x,y,2)
    @test P ≈ P2
end


########################################################################
function ppca_given_b(X, b; Q=1)
########################################################################

    D, N = size(X)

    @assert(length(b) == D)

    JITTER = 1e-8

    numparam = D*Q        +   1     +  1
    #    linear mapping    scale    noise

    @printf("Running PPCA_given_b for %d number of data items of dimension %d, projected to %d components\n", N, D, Q)

    #---------------------------------------------
    function marginalLogLikelihood(W, μ, σ)
    #---------------------------------------------

        sum(logpdf(MvNormal(μ, W*W' + σ*σ*I + JITTER*I), X))

    end

    #---------------------------------------------
    function unpack(param)
    #---------------------------------------------

        local W = reshape(param[1:end-2], D, Q)

        local s = exp(param[end-1])

        local σ = exp(param[end])

        return W, s, σ

    end

    #---------------------------------------------
    function objective(param)
    #---------------------------------------------

        @assert(length(param) == numparam)

        local W, s, σ = unpack(param)

        return -1.0 * marginalLogLikelihood(W, s * b, σ)

    end

    #---------------------------------------------
    # Run optimiser
    #---------------------------------------------

    opt    = Optim.Options(show_trace = true, iterations = 10_000)

    result = optimize(objective, [randn(D*Q); randn()*3; randn()*3], LBFGS(), opt, autodiff=:forward)

    W, s, σ = unpack(result.minimizer)

    #---------------------------------------------
    # Return results
    #---------------------------------------------

    return W, s, σ

end


using StatsBase
using Base.Test

a = rand(5, 6)
b = rand(5, 6)

@test_approx_eq sqL2dist(a, b) sum(abs2(a - b))
@test_approx_eq L2dist(a, b)   sqrt(sqL2dist(a, b))
@test_approx_eq L1dist(a, b)   sum(abs(a - b))
@test_approx_eq Linfdist(a, b) maximum(abs(a - b))

@test_approx_eq gkldiv(a, b) sum(a .* log(a ./ b) - a + b)

@test_approx_eq meanad(a, b) mean(abs(a - b))
@test_approx_eq maxad(a, b)  maximum(abs(a - b))
@test_approx_eq msd(a, b)    mean(abs2(a - b))
@test_approx_eq rmsd(a, b)   sqrt(msd(a, b))
@test_approx_eq rmsd(a, b; normalize=true)  rmsd(a, b) / (maximum(a) - minimum(a))
@test_approx_eq psnr(a, b, 2)  10 * log10(4 / msd(a, b))


module problem131

description = """
Prime cube partnership

There are some prime values, p, for which there exists a positive integer, n, such that the expression n^3 + n^2 * p is a perfect cube.
For example, when p = 19, 8^3 + 8^2 × 19 = 12^3.
What is perhaps most surprising is that for each prime with this property the value of n is unique, and there are only four such primes below one-hundred.

How many primes below one million have this remarkable property?
"""

# By exhaustive search, I found the first few primes are 7, 19, 37, 61
# These are known as Cuban primes: primes of the form n^3 - (n-1)^3
#
# Insight after reading solutions from the ProjectEuler forums:
#
# n^3 + p * n^2 = k^3
# n^3 * (p/n + 1) = k^3
# n^3 * ((p+n)/n) = k^3
# n * cuberoot((p+n)/n) = k
# n * cuberoot(p+n) / cuberoot(n) = k
# For the cuberoot expressions to have integer results, p+n and n must be cubes.
# let n = x^3, p+n = y^3
# p = y^3 - x^3
# p = (y - x)(y^2 + xy + x^2)
# But p is required to be prime, so (y-x) must be 1 - otherwise (y-x) would be a factor of p.
# Therefore, p is the difference of consecutive cubes:
# p = y^3 - (y-1)^3
function cubans(n = 1000000)
  c = 0
  for y = 2:Inf
    p = y^3 - (y-1)^3
    if p > n
      return c
    elseif isprime(p)
      c += 1
    end
  end
end

using Base.Test
@test cubans(100) == 4

end


module BestApproximation

export best_approx

@doc raw"""
```julia
best_approx(n::Number, R::AbstractRange)
best_approx(n::Number, R_max::Integer)
```

For a given number ``n``, given a range ``R``, finds the "nicest" solution of ``m`` such that

```math
r^m \approx n \text{ for } r \in R.
```

Returns `(r, m)` as a tuple.

Given an integer ``R_{\text{max}}`` it will assume the range between ``R = \left\lbrack 1, R_{\text{max}}\right\rbrack``.
"""
function best_approx(n::Number, R::AbstractRange)
    n, res = big(n), (0, 0)
    for i in R
        j = round(Integer, log(i, n))
        difference = (n - big(i)^(j))
        if (difference > 0) && (difference < (n - big(first(res))^(last(res))))
            res = (i, j)
        end
    end

    return res
end
best_approx(n::Number, R_max::Integer) = best_approx(n, 1:R_max)

@doc raw"""
```julia
best_approx(n::Number, I::AbstractRange, J::AbstractRange)
best_approx(n::Number, I_max::Integer, J::Integer)
```

For a given number ``n``, given two ranges ``I`` and ``J``, finds the nicest solution such that

```math
i^j \approx n \text{ for } i \in I, j \in j.
```

Returns `(i, j)` as a tuple.

Given integers ``I_{\text{max}}`` and ``J_{\text{max}}`` it will assume the ranges between ``I =  \left\lbrack 1, I_{\text{max}}\right\rbrack`` and ``J =  \left\lbrack 1, J_{\text{max}}\right\rbrack``.

!!! note

    This is a much slower variant than the other method for `best_approx`.  It is recommended that you use the "cleverer" method unless you need to constrict the exponent.
"""
function best_approx(n::Number, I::AbstractRange, J::AbstractRange)
    n, res = big(n), (0, 0)
    for i in I, j in J
        difference = (n - big(i)^(j))
        if (difference > 0) && (difference < (n - big(first(res))^(last(res))))
            res = (i, j)
        end
    end

    return res
end
best_approx(n::Number, I_max::Integer, J_max::Integer) = best_approx(n, 1:I_max, 1:J_max)

end