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


# 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


# 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


#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


@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)


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


### A Pluto.jl notebook ###
# v0.16.1

using Markdown
using InteractiveUtils

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

# ╔═╡ 60941eaa-1aea-11eb-1277-97b991548781
begin
    using PlutoUI,GridVisualize,SimplexGridFactory,Triangulate,ExtendableGrids,VoronoiFVM,PlutoVista,PlutoUI,PyPlot
end

# ╔═╡ de468cb9-b34d-4d2e-b911-9b93920caca1
md"""
# Flux reconstruction and visualization for the Laplace operator
"""

# ╔═╡ 928a70c5-4706-40a1-9387-abcb71c09443
"""
Define a "Swiss cheese domain" with punched-out holes, where each hole boundary corresponds to a different boundary condition.
"""
function swiss_cheese_2d()

    function circlehole!(builder, center, radius; n=20)
        points=[point!(builder, center[1]+radius*sin(t),center[2]+radius*cos(t)) for t in range(0,2π,length=n)]
        for i=1:n-1
            facet!(builder,points[i],points[i+1])
        end
        facet!(builder,points[end],points[1])
        holepoint!(builder,center)
    end

    builder=SimplexGridBuilder(Generator=Triangulate)
    cellregion!(builder,1)
    maxvolume!(builder,0.1)
    regionpoint!(builder,0.1,0.1)

    p1=point!(builder,0,0)
    p2=point!(builder,10,0)
    p3=point!(builder,10,10)
    p4=point!(builder,0,10)

    facetregion!(builder,1)
    facet!(builder,p1,p2)
    facet!(builder,p2,p3)
    facet!(builder,p3,p4)
    facet!(builder,p4,p1)

    holes=[1.0 2.0;
           8.0 9.0;
           2.0 8.0;
  		   8.0 4.0;
           9.0 1.0;
           3.0 4.0;
           4.0 6.0;
           7.0 9.0;
           4.0 7.0;
           7.0 5.0;
           2.0 1.0;
           4.0 1.0;
           4.0 8.0;
           3.0 6.0;
           4.0 9.0;
           6.0 9.0;
           3.0 5.0;
           1.0 4.0]'

    radii=[0.15, 0.15, 0.1, 0.35, 0.2, 0.3, 0.1, 0.4, 0.1, 0.4,  0.2, 0.2, 0.2, 0.35, 0.15, 0.25, 0.15, 0.25]

    for i=1:length(radii)
        facetregion!(builder,i+1)
        circlehole!(builder,holes[:,i], radii[i])
    end

    simplexgrid(builder)
end

# ╔═╡ 49f425a4-455c-40f5-b939-da704032cb88
md"""
Generate & plot grid
"""

# ╔═╡ bc304085-69c3-4974-beb4-6f2b981ac0f1
grid=swiss_cheese_2d()

# ╔═╡ ad0367e6-7308-46bc-a7ff-9c17dcfe470d
gridplot(grid,Plotter=PlutoVista)

# ╔═╡ 236d6cd5-c190-49c7-98fe-021fae224455
"""
Simple flux function for Laplace operator
"""
flux(y,u,edge)= y[1]=u[1,1]-u[1,2]

# ╔═╡ f4ebe6ad-4e04-4f33-9a66-6bec977adf4d
md"""
Define a finite volume system with Dirichlet boundary conditions at some of the holes
"""

# ╔═╡ 934a98f1-9734-45bc-848e-4d4d8ad80e4e
physics=VoronoiFVM.Physics(flux=flux)

# ╔═╡ f3af20fb-c3cc-41af-9782-d40adf2371f7
system=VoronoiFVM.System(grid,physics)

# ╔═╡ 50ea0a7e-97e0-428d-b4ec-6bd4d7aef31f
enable_species!(system,1,[1])

# ╔═╡ 49d3c96b-f6b4-4581-8600-8aaf50bb07fc
boundary_dirichlet!(system,1,2,10.0)

# ╔═╡ fa5200ff-713e-48fb-b452-c6ee96935cfc
boundary_dirichlet!(system,1,3,0.0)

# ╔═╡ 6b09f75a-521b-40d9-9636-155e62ab0cac
md"""
At hole #11, the value will be bound to a slider defined below
"""

# ╔═╡ d86c43f3-ec2f-4fae-88d2-1068603e7044
md"""
Solve, and trigger solution upon boundary value change
"""

# ╔═╡ 41bd1230-c87c-47b0-8e58-67ad55609fd3
md"""
Reconstruct the node flux. It is a ``d\times n_{spec}\times n_{nodes}`` tensor.
`nf[:,ispec,:]` then is a vector function representing the flux density of species `ispec` in each node of the domain. This readily can be fed into `GridVisualize.vectorplot`.
"""

# ╔═╡ 17be52fb-f55b-4b3d-85e5-33f36134046b
vis=GridVisualizer(Plotter=PlutoVista,dim=2,resolution=(400,400));vis

# ╔═╡ 03f582ec-4e95-4ca4-8482-9c797027810d
md"""
``v_{11}:`` $(@bind  val11 Slider(0:0.1:10,default=5,show_value=true))
"""

# ╔═╡ e8ae2b22-ac60-449b-952f-d2c55852677c
boundary_dirichlet!(system,1,11,val11)

# ╔═╡ 27efdcac-8ee4-47e4-905d-8d8c7313ddd1
val11; sol=solve(unknowns(system,inival=0),system)

# ╔═╡ 41f427c1-b6ad-46d4-9151-1d872b4efeb6
nf=nodeflux(system,sol)

# ╔═╡ c9b9fdb1-5734-430f-949a-6e6126d2f091
"""
Joint plot of solution and nodal flux of the solution
"""
function myplot(vis)
	scalarplot!(vis,grid,sol[1,:],levels=9,colormap=:summer,clear=true)
	vectorplot!(vis,grid,nf[:,1,:],clear=false,spacing=0.5,vscale=1.5)
	reveal(vis)
end

# ╔═╡ 531edb71-6d32-4231-b117-5e36416d2fb1
myplot(vis)

# ╔═╡ e0b92e00-dd0b-448b-baae-5cd4e0a2059a
md"""
Plot with PyPlot: $(@bind use_pyplot CheckBox(default=false))
"""

# ╔═╡ 1495eb67-12ec-4662-b0e7-049ffef569c0
if use_pyplot
	pyvis=GridVisualizer(Plotter=PyPlot,resolution=(400,400))
	myplot(pyvis)
end

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
ExtendableGrids = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
GridVisualize = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
PlutoVista = "646e1f28-b900-46d7-9d87-d554eb38a413"
PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee"
SimplexGridFactory = "57bfcd06-606e-45d6-baf4-4ba06da0efd5"
Triangulate = "f7e6ffb2-c36d-4f8f-a77e-16e897189344"
VoronoiFVM = "82b139dc-5afc-11e9-35da-9b9bdfd336f3"

[compat]
ExtendableGrids = "~0.8.3"
GridVisualize = "~0.3.5"
PlutoUI = "~0.7.16"
PlutoVista = "~0.8.2"
PyPlot = "~2.10.0"
SimplexGridFactory = "~0.5.8"
Triangulate = "~2.1.0"
VoronoiFVM = "~0.13.1"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.0-rc1"
manifest_format = "2.0"

[[deps.AbstractTrees]]
git-tree-sha1 = "03e0550477d86222521d254b741d470ba17ea0b5"
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
version = "0.3.4"

[[deps.Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[deps.ArnoldiMethod]]
deps = ["LinearAlgebra", "Random", "StaticArrays"]
git-tree-sha1 = "f87e559f87a45bece9c9ed97458d3afe98b1ebb9"
uuid = "ec485272-7323-5ecc-a04f-4719b315124d"
version = "0.1.0"

[[deps.ArrayInterface]]
deps = ["Compat", "IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"]
git-tree-sha1 = "b8d49c34c3da35f220e7295659cd0bab8e739fed"
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
version = "3.1.33"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.Cassette]]
git-tree-sha1 = "6ce3cd755d4130d43bab24ea5181e77b89b51839"
uuid = "7057c7e9-c182-5462-911a-8362d720325c"
version = "0.3.9"

[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "2f294fae04aa5069a67964a3366e151e09ea7c09"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.9.0"

[[deps.ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "a851fec56cb73cfdf43762999ec72eff5b86882a"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.15.0"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[deps.Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[deps.CommonSubexpressions]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
version = "0.3.0"

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "31d0151f5716b655421d9d75b7fa74cc4e744df2"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.39.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[deps.Conda]]
deps = ["JSON", "VersionParsing"]
git-tree-sha1 = "299304989a5e6473d985212c28928899c74e9421"
uuid = "8f4d0f93-b110-5947-807f-2305c1781a2d"
version = "1.5.2"

[[deps.DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[deps.DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.3"

[[deps.DiffRules]]
deps = ["NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "7220bc21c33e990c14f4a9a319b1d242ebc5b269"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.3.1"

[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.5"

[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[deps.EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[deps.ElasticArrays]]
deps = ["Adapt"]
git-tree-sha1 = "a0fcc1bb3c9ceaf07e1d0529c9806ce94be6adf9"
uuid = "fdbdab4c-e67f-52f5-8c3f-e7b388dad3d4"
version = "1.2.9"

[[deps.ExtendableGrids]]
deps = ["AbstractTrees", "Dates", "DocStringExtensions", "ElasticArrays", "InteractiveUtils", "LinearAlgebra", "Printf", "Random", "SparseArrays", "Test"]
git-tree-sha1 = "85dbe70afc7153ad510577f158214b45be22593b"
uuid = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
version = "0.8.3"

[[deps.ExtendableSparse]]
deps = ["DocStringExtensions", "LinearAlgebra", "Printf", "Requires", "SparseArrays", "SuiteSparse", "Test"]
git-tree-sha1 = "0341e41e45e6c5e46be89c33543082a0a867707c"
uuid = "95c220a8-a1cf-11e9-0c77-dbfce5f500b3"
version = "0.6.5"

[[deps.FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "3c041d2ac0a52a12a27af2782b34900d9c3ee68c"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.11.1"

[[deps.FillArrays]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "8756f9935b7ccc9064c6eef0bff0ad643df733a3"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "0.12.7"

[[deps.FiniteDiff]]
deps = ["ArrayInterface", "LinearAlgebra", "Requires", "SparseArrays", "StaticArrays"]
git-tree-sha1 = "8b3c09b56acaf3c0e581c66638b85c8650ee9dca"
uuid = "6a86dc24-6348-571c-b903-95158fe2bd41"
version = "2.8.1"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "63777916efbcb0ab6173d09a658fb7f2783de485"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "0.10.21"

[[deps.GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "58bcdf5ebc057b085e58d95c138725628dd7453c"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.1"

[[deps.GridVisualize]]
deps = ["ColorSchemes", "Colors", "DocStringExtensions", "ElasticArrays", "ExtendableGrids", "GeometryBasics", "LinearAlgebra", "Observables", "OrderedCollections", "PkgVersion", "Printf", "Requires", "StaticArrays"]
git-tree-sha1 = "063aa184cec6aff22f7c2f6a498075a93b5fa304"
uuid = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
version = "0.3.5"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
git-tree-sha1 = "f6532909bf3d40b308a0f360b6a0e626c0e263a8"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.1"

[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[deps.IfElse]]
git-tree-sha1 = "28e837ff3e7a6c3cdb252ce49fb412c8eb3caeef"
uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
version = "0.1.0"

[[deps.Inflate]]
git-tree-sha1 = "f5fc07d4e706b84f72d54eedcc1c13d92fb0871c"
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
version = "0.1.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[deps.IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[deps.IterativeSolvers]]
deps = ["LinearAlgebra", "Printf", "Random", "RecipesBase", "SparseArrays"]
git-tree-sha1 = "1a8c6237e78b714e901e406c096fc8a65528af7d"
uuid = "42fd0dbc-a981-5370-80f2-aaf504508153"
version = "0.9.1"

[[deps.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[deps.JLD2]]
deps = ["DataStructures", "FileIO", "MacroTools", "Mmap", "Pkg", "Printf", "Reexport", "TranscodingStreams", "UUIDs"]
git-tree-sha1 = "46b7834ec8165c541b0b5d1c8ba63ec940723ffb"
uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
version = "0.4.15"

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[deps.LaTeXStrings]]
git-tree-sha1 = "c7f1c695e06c01b95a67f0cd1d34994f3e7db104"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.2.1"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LightGraphs]]
deps = ["ArnoldiMethod", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"]
git-tree-sha1 = "432428df5f360964040ed60418dd5601ecd240b6"
uuid = "093fc24a-ae57-5d10-9952-331d41423f4d"
version = "1.3.5"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.LogExpFunctions]]
deps = ["ChainRulesCore", "DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "34dc30f868e368f8a17b728a1238f3fcda43931a"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.3"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "5a5bc6bf062f0f95e62d0fe0a2d99699fed82dd9"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.8"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[deps.NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.Observables]]
git-tree-sha1 = "fe29afdef3d0c4a8286128d4e45cc50621b1e43d"
uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
version = "0.4.0"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"

[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[deps.OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "98f59ff3639b3d9485a03a72f3ab35bab9465720"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.6"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[deps.PkgVersion]]
deps = ["Pkg"]
git-tree-sha1 = "a7a7e1a88853564e551e4eba8650f8c38df79b37"
uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
version = "0.1.1"

[[deps.PlutoUI]]
deps = ["Base64", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "4c8a7d080daca18545c56f1cac28710c362478f3"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.16"

[[deps.PlutoVista]]
deps = ["ColorSchemes", "Colors", "DocStringExtensions", "GridVisualize", "UUIDs"]
git-tree-sha1 = "ce59550b517ab41b5e687174f93f57c76f872b6d"
uuid = "646e1f28-b900-46d7-9d87-d554eb38a413"
version = "0.8.2"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.PyCall]]
deps = ["Conda", "Dates", "Libdl", "LinearAlgebra", "MacroTools", "Serialization", "VersionParsing"]
git-tree-sha1 = "169bb8ea6b1b143c5cf57df6d34d022a7b60c6db"
uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
version = "1.92.3"

[[deps.PyPlot]]
deps = ["Colors", "LaTeXStrings", "PyCall", "Sockets", "Test", "VersionParsing"]
git-tree-sha1 = "14c1b795b9d764e1784713941e787e1384268103"
uuid = "d330b81b-6aea-500a-939a-2ce795aea3ee"
version = "2.10.0"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.RecipesBase]]
git-tree-sha1 = "44a75aa7a527910ee3d1751d1f0e4148698add9e"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.2"

[[deps.RecursiveArrayTools]]
deps = ["ArrayInterface", "ChainRulesCore", "DocStringExtensions", "FillArrays", "LinearAlgebra", "RecipesBase", "Requires", "StaticArrays", "Statistics", "ZygoteRules"]
git-tree-sha1 = "ff7495c78a192ff7d59531d9f14db300c847a4bc"
uuid = "731186ca-8d62-57ce-b412-fbd966d074cd"
version = "2.19.1"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[deps.SimpleTraits]]
deps = ["InteractiveUtils", "MacroTools"]
git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
version = "0.9.4"

[[deps.SimplexGridFactory]]
deps = ["DocStringExtensions", "ElasticArrays", "ExtendableGrids", "GridVisualize", "LinearAlgebra", "Printf", "Test"]
git-tree-sha1 = "e55646af1d0f68763eaca28f666d381982eeb65f"
uuid = "57bfcd06-606e-45d6-baf4-4ba06da0efd5"
version = "0.5.8"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.SparseDiffTools]]
deps = ["Adapt", "ArrayInterface", "Compat", "DataStructures", "FiniteDiff", "ForwardDiff", "LightGraphs", "LinearAlgebra", "Requires", "SparseArrays", "StaticArrays", "VertexSafeGraphs"]
git-tree-sha1 = "36a4d27a02af48a1eafd2baff58b32deeeb68926"
uuid = "47a9eef4-7e08-11e9-0b38-333d64bd3804"
version = "1.16.5"

[[deps.SparsityDetection]]
deps = ["Cassette", "DocStringExtensions", "LinearAlgebra", "SparseArrays", "SpecialFunctions"]
git-tree-sha1 = "9e182a311d169cb9fe0c6501aa252983215fe692"
uuid = "684fba80-ace3-11e9-3d08-3bc7ed6f96df"
version = "0.3.4"

[[deps.SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "793793f1df98e3d7d554b65a107e9c9a6399a6ed"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.7.0"

[[deps.Static]]
deps = ["IfElse"]
git-tree-sha1 = "a8f30abc7c64a39d389680b74e749cf33f872a70"
uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
version = "0.3.3"

[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3c76dde64d03699e074ac02eb2e8ba8254d428da"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.13"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "2ce41e0d042c60ecd131e9fb7154a3bfadbf50d3"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.3"

[[deps.SuiteSparse]]
deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "fed34d0e71b91734bf0a7e10eb1bb05296ddbcd0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.0"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

[[deps.Triangle_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "bfdd9ef1004eb9d407af935a6f36a4e0af711369"
uuid = "5639c1d2-226c-5e70-8d55-b3095415a16a"
version = "1.6.1+0"

[[deps.Triangulate]]
deps = ["DocStringExtensions", "Libdl", "Printf", "Test", "Triangle_jll"]
git-tree-sha1 = "2b4f716b192c0c615d96d541ee029e85666388cb"
uuid = "f7e6ffb2-c36d-4f8f-a77e-16e897189344"
version = "2.1.0"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.VersionParsing]]
git-tree-sha1 = "80229be1f670524750d905f8fc8148e5a8c4537f"
uuid = "81def892-9a0e-5fdd-b105-ffc91e053289"
version = "1.2.0"

[[deps.VertexSafeGraphs]]
deps = ["LightGraphs"]
git-tree-sha1 = "b9b450c99a3ca1cc1c6836f560d8d887bcbe356e"
uuid = "19fa3120-7c27-5ec5-8db8-b0b0aa330d6f"
version = "0.1.2"

[[deps.VoronoiFVM]]
deps = ["DiffResults", "DocStringExtensions", "ExtendableGrids", "ExtendableSparse", "ForwardDiff", "GridVisualize", "IterativeSolvers", "JLD2", "LinearAlgebra", "Printf", "RecursiveArrayTools", "SparseArrays", "SparseDiffTools", "SparsityDetection", "StaticArrays", "SuiteSparse", "Test"]
git-tree-sha1 = "db381645bec1d798c560fe415593663d4a2194ac"
uuid = "82b139dc-5afc-11e9-35da-9b9bdfd336f3"
version = "0.13.1"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[deps.ZygoteRules]]
deps = ["MacroTools"]
git-tree-sha1 = "8c1a8e4dfacb1fd631745552c8db35d0deb09ea0"
uuid = "700de1a5-db45-46bc-99cf-38207098b444"
version = "0.2.2"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─de468cb9-b34d-4d2e-b911-9b93920caca1
# ╠═60941eaa-1aea-11eb-1277-97b991548781
# ╠═928a70c5-4706-40a1-9387-abcb71c09443
# ╟─49f425a4-455c-40f5-b939-da704032cb88
# ╠═bc304085-69c3-4974-beb4-6f2b981ac0f1
# ╠═ad0367e6-7308-46bc-a7ff-9c17dcfe470d
# ╠═236d6cd5-c190-49c7-98fe-021fae224455
# ╟─f4ebe6ad-4e04-4f33-9a66-6bec977adf4d
# ╠═934a98f1-9734-45bc-848e-4d4d8ad80e4e
# ╠═f3af20fb-c3cc-41af-9782-d40adf2371f7
# ╠═50ea0a7e-97e0-428d-b4ec-6bd4d7aef31f
# ╠═49d3c96b-f6b4-4581-8600-8aaf50bb07fc
# ╠═fa5200ff-713e-48fb-b452-c6ee96935cfc
# ╟─6b09f75a-521b-40d9-9636-155e62ab0cac
# ╠═e8ae2b22-ac60-449b-952f-d2c55852677c
# ╟─d86c43f3-ec2f-4fae-88d2-1068603e7044
# ╠═27efdcac-8ee4-47e4-905d-8d8c7313ddd1
# ╟─41bd1230-c87c-47b0-8e58-67ad55609fd3
# ╠═41f427c1-b6ad-46d4-9151-1d872b4efeb6
# ╠═c9b9fdb1-5734-430f-949a-6e6126d2f091
# ╠═17be52fb-f55b-4b3d-85e5-33f36134046b
# ╟─03f582ec-4e95-4ca4-8482-9c797027810d
# ╠═531edb71-6d32-4231-b117-5e36416d2fb1
# ╟─e0b92e00-dd0b-448b-baae-5cd4e0a2059a
# ╠═1495eb67-12ec-4662-b0e7-049ffef569c0
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# Julia function to load CSV and compute
# a couple of SMA.  written to be identical
# to lua version.
using DataFrames

# Note: There are faster ways to compute SMA but this
#  is closest to the method shown in basic tutorials
#  and is a valid test of a tight loop that spends a
#  a lot of time indexing into an array.   We show it
#  as a nested loop instead of slice
function sma(avect, numPer)
    print(length(avect), " numper=", numPer)
    numEle = length(avect)
    tout = Array(Float32, numEle)
    for ndx = 1:numEle
      tsum = 0.0
      begndx = max(1, ndx - numPer)
      for slicendx = begndx:ndx
        tsum += avect[slicendx]
      end
      tout[ndx] = tsum / float32(numPer)
    end
    return tout
end

## Showing the more common Julia version of
## slicing out what we need and applying builtin
## operator mean instead of manual sub loop.
function sma_slice(avect, numPer)
    print(length(avect), " numper=", numPer)
    numEle = length(avect)
    tout = Array(Float32, numEle)
    for ndx = 1:numEle
      begndx = max(1, ndx - numPer)
      tout[ndx] = mean(avect[begndx:ndx])
    end
    return tout
end

function runTest()
   print ("read table")
   tic()
   dta = readtable("2014.M1.csv")
   toc()
   println("finished read table")

   closeVect = dta[:close]
   println("compute sma(14)")
   tic()
   sma14 = sma(closeVect, 14)
   toc()
   println("finished sma(14)")

   println("compute sma (600)")
   tic()
   sma600 = sma(closeVect, 600)
   toc()
   println("finished sma(600)")

   closeVect = dta[:close]
   println("compute sma_slice(14)")
   tic()
   sma14 = sma_slice(closeVect, 14)
   toc()
   println("finished sma_slice(14)")

   println("compute sma_slice(600)")
   tic()
   sma600 = sma_slice(closeVect, 600)
   toc()
   println("finished sma_slice(600)")

   println("\n\n Convert to Typed vector and try again \n\n")
   tic()
   tlen = length(closeVect)
   tvect = Array(Float32, tlen)
   for ndx = 1:tlen
     tvect[ndx] = closeVect[ndx]
   end
   toc()
   println("vector convertion complete")

   println("compute sma(14)")
   tic()
   sma14 = sma(tvect, 14)
   toc()
   println("finished sma(14)")

   println("compute sma (600)")
   tic()
   sma600 = sma(tvect, 600)
   toc()
   println("finished sma(600)")

   closeVect = dta[:close]
   println("compute sma_slice(14)")
   tic()
   sma14 = sma_slice(tvect, 14)
   toc()
   println("finished sma_slice(14)")

   println("compute sma_slice(600)")
   tic()
   sma600 = sma_slice(tvect, 600)
   toc()
   println("finished sma_slice(600)")

end

runTest()


# An implementation of CGLS for the solution of the
# over-determined linear least-squares problem
#
#  minimize ‖Ax - b‖
#
# equivalently, of the normal equations
#
#  A'Ax = A'b.
#
# CGLS is formally equivalent to applying the conjugate gradient method
# to the normal equations but should be more stable. It is also formally
# equivalent to LSQR though LSQR should be expected to be more stable on
# ill-conditioned or poorly scaled problems.
#
# This implementation is the standard formulation, as recommended by
# A. Björck, T. Elfving and Z. Strakos, Stability of Conjugate Gradient
# and Lanczos Methods for Linear Least Squares Problems.
#
# Dominique Orban, <dominique.orban@gerad.ca>
# Princeton, NJ, March 2015.

export cgls

"""Solve the regularized linear least-squares problem

  minimize ‖b - Ax‖₂² + λ ‖x‖₂²

using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization
parameter. This method is equivalent to applying CG to the normal equations

  (A'A + λI) x = A'b

but is more stable.

CGLS produces monotonic residuals ‖r‖₂ but not optimality residuals ‖A'r‖₂.
It is formally equivalent to LSQR, though can be slightly less accurate,
but simpler to implement.
"""
function cgls(A :: AbstractLinearOperator, b :: AbstractVector{T};
              M :: AbstractLinearOperator=opEye(size(b,1)), λ :: Float64=0.0,
              atol :: Float64=1.0e-8, rtol :: Float64=1.0e-6, radius :: Float64=0.0,
              itmax :: Int=0, verbose :: Bool=false) where T <: Number

  m, n = size(A);
  size(b, 1) == m || error("Inconsistent problem size");
  verbose && @printf("CGLS: system of %d equations in %d variables\n", m, n);

  x = zeros(T, n);
  r = copy(b)
  bNorm = @knrm2(m, r)   # Marginally faster than norm(b);
  bNorm == 0 && return x, SimpleStats(true, false, [0.0], [0.0], "x = 0 is a zero-residual solution");
  s = A' * M * r;
  p = copy(s);
  γ = @kdot(n, s, s)  # Faster than γ = dot(s, s);
  iter = 0;
  itmax == 0 && (itmax = m + n);

  rNorm  = bNorm;
  ArNorm = sqrt(γ);
  rNorms = [rNorm;];
  ArNorms = [ArNorm;];
  ε = atol + rtol * ArNorm;
  verbose && @printf("%5s  %8s  %8s\n", "Aprod", "‖A'r‖", "‖r‖")
  verbose && @printf("%5d  %8.2e  %8.2e\n", 1, ArNorm, rNorm);

  status = "unknown";
  on_boundary = false
  solved = ArNorm <= ε;
  tired = iter >= itmax;

  while ! (solved || tired)
    q = A * p;
    δ = @kdot(m, q, M * q)   # Faster than α = γ / dot(q, q);
    λ > 0 && (δ += λ * @kdot(n, p, p))
    α = γ / δ;

    # if a trust-region constraint is give, compute step to the boundary
    σ = radius > 0.0 ? maximum(to_boundary(x, p, radius)) : α
    if (radius > 0.0) & (α > σ)
      α = σ
      on_boundary = true
    end

    @kaxpy!(n,  α, p, x)     # Faster than x = x + α * p;
    @kaxpy!(m, -α, q, r)     # Faster than r = r - α * q;
    s = A' * M * r;
    λ > 0 && @kaxpy!(n, -λ, x, s)   # s = A' * r - λ * x;
    γ_next = @kdot(n, s, s)  # Faster than γ_next = dot(s, s);
    β = γ_next / γ;
    @kscal!(n, β, p)
    @kaxpy!(n, 1.0, s, p)    # Faster than p = s + β * p;
    # The combined BLAS calls tend to trigger some gc.
    #  BLAS.axpy!(n, 1.0, s, 1, BLAS.scal!(n, β, p, 1), 1);
    γ = γ_next;
    rNorm = @knrm2(m, r)  # Marginally faster than norm(r);
    ArNorm = sqrt(γ);
    push!(rNorms, rNorm);
    push!(ArNorms, ArNorm);
    iter = iter + 1;
    verbose && @printf("%5d  %8.2e  %8.2e\n", 1 + 2 * iter, ArNorm, rNorm);
    solved = (ArNorm <= ε) | on_boundary
    tired = iter >= itmax;
  end

  status = on_boundary ? "on trust-region boundary" : (tired ? "maximum number of iterations exceeded" : "solution good enough given atol and rtol")
  stats = SimpleStats(solved, false, rNorms, ArNorms, status);
  return (x, stats);
end


const number_of_chebyshevs_to_compile_into_binaries = 20

function next_chebyshev(previous::Sum_Of_Functions, two_previous::Sum_Of_Functions)
    return PE_Function(2.0,0.0,0.0,1) * previous - two_previous
end

first_kind_chebyshevs = Array{Sum_Of_Functions}(undef, number_of_chebyshevs_to_compile_into_binaries)
first_kind_chebyshevs[1] = Sum_Of_Functions([PE_Function(1.0,0.0,0.0,0)])
first_kind_chebyshevs[2] = Sum_Of_Functions([PE_Function(1.0,0.0,0.0,1)])
for i in 3:number_of_chebyshevs_to_compile_into_binaries
    first_kind_chebyshevs[i] = next_chebyshev(first_kind_chebyshevs[i-1], first_kind_chebyshevs[i-2])
end

second_kind_chebyshevs = Array{Sum_Of_Functions}(undef, number_of_chebyshevs_to_compile_into_binaries)
second_kind_chebyshevs[1] = Sum_Of_Functions([PE_Function(1.0,0.0,0.0,0)])
second_kind_chebyshevs[2] = Sum_Of_Functions([PE_Function(2.0,0.0,0.0,1)])
for i in 3:number_of_chebyshevs_to_compile_into_binaries
    second_kind_chebyshevs[i] = next_chebyshev(second_kind_chebyshevs[i-1], second_kind_chebyshevs[i-2])
end

"""
    get_chevyshevs_up_to(N::Integer, first_kind::Bool = true)

Get the first N chebyshev polynomials returned as a vector of `UnivariateFunction`s.
The first 20 polynomials of each are precompiled into the binaries for speed. If
you need more than that they will be calculated at runtime.

These can be from either the first kind or second kind polynomial sequence.
### Inputs
* `N` - How many chebyshev polynomials do you want.
* `first_kind` - A Bool. If true you get first kind polynomials. If false you get second kind.
### Returns
* A `Vector` of `UnivariateFunction`s for each polynomial.
"""
function get_chevyshevs_up_to(N::Integer, first_kind::Bool = true)
    chebyshevs = Array{Sum_Of_Functions}(undef, N)
    if N >= number_of_chebyshevs_to_compile_into_binaries
        if first_kind
            chebyshevs[1:number_of_chebyshevs_to_compile_into_binaries] = first_kind_chebyshevs
        else
            chebyshevs[1:number_of_chebyshevs_to_compile_into_binaries] = second_kind_chebyshevs
        end
        for i in (number_of_chebyshevs_to_compile_into_binaries+1):N
            chebyshevs[i] = next_chebyshev(chebyshevs[i-1], chebyshevs[i-2])
        end
    else
        if first_kind
            chebyshevs[1:N] = first_kind_chebyshevs[1:N]
        else
            chebyshevs[1:N] = second_kind_chebyshevs[1:N]
        end
    end
    return chebyshevs
end


"""
Reverse plus
"""
function plus_rev(a::Interval, b::Interval, c::Interval)  # a = b + c
    # a = a ∩ (b + c)  # add this line for plus contractor (as opposed to reverse function)
    b_new = b ∩ (a - c)
    c_new = c ∩ (a - b)

    return a, b_new, c_new
end

plus_rev(a,b,c) = plus_rev(promote(a,b,c)...)

"""
Reverse minus
"""
function minus_rev(a::Interval, b::Interval, c::Interval)  # a = b - c

    b_new = b ∩ (a + c)
    c_new = c ∩ (b - a)

    return a, b_new, c_new
end

minus_rev(a,b,c) = minus_rev(promote(a,b,c)...)

function minus_rev(a::Interval, b::Interval)  # a = -b
    b_new = b ∩ (-a)
    return (a, b_new)
end

"""
Reverse multiplication
"""
function mul_rev(a::Interval, b::Interval, c::Interval)  # a = b * c

    # ((0.0 ∉ a) || (0.0 ∉ b)) && (c = c ∩ (a / b))
    # ((0.0 ∉ a) || (0.0 ∉ c)) && (b = b ∩ (a / c))

    # a = a ∩ (b * c)  # ?

    if 0 ∈ b
        temp = c .∩ extended_div(a, b)
        c′ = union(temp[1], temp[2])

    else
        c′ = c ∩ (a / b)
    end

    if 0 ∈ c
        temp = b .∩ extended_div(a, c)
        b′ = union(temp[1], temp[2])

    else
        b′ = b ∩ (a / c)
    end

    return a, b′, c′
end

mul_rev(a,b,c) = mul_rev(promote(a,b,c)...)

"""
Reverse division
"""
function div_rev(a::Interval, b::Interval, c::Interval)  # a = b / c

    b = b ∩ (a * c)
    c = c ∩ (b / a)

    return a, b, c
end

div_rev(a,b,c) = div_rev(promote(a,b,c)...)

"""
Reverse inverse
"""
function inv_rev(a::Interval, b::Interval)  # a = inv(b)

    b_new = b ∩ inv(a)

    return a, b_new
end

inv_rev(a,b) = inv_rev(promote(a,b)...)

"""
Reverse power
"""
function power_rev(a::Interval, b::Interval, n::Integer)  # a = b^n,  log(a) = n.log(b),  b = a^(1/n)

    if n == 2  # a = b^2
        root = √a
        b1 = b ∩ root
        b2 = b ∩ (-root)

    elseif iseven(n)
        root = a^(1//n)

        b1 = b ∩ root
        b2 = b ∩ (-root)

    elseif isodd(n)
        pos_root = (a ∩ (0..∞)) ^ (1//n)
        neg_root = -( ( (-a) ∩ (0..∞) ) ^ (1//n) )

        b1 = b ∩ pos_root
        b2 = b ∩ neg_root

    end

    b = hull(b1, b2)

    return (a, b, n)
end

function power_rev(a::Interval, b::Interval, c::Interval)  # a = b^c

    if isinteger(c)
        temp = power_rev(a, b, Int(inf(c)))  # use version with integer
        return (temp[1], temp[2], interval(temp[3]))
    end

    b_new = b ∩ ( a^(inv(c) ))
    c_new = c ∩ (log(a) / log(b))

    return a, b_new, c_new
end

power_rev(a, b, c) = power_rev(promote(a, b, c)...)

"""
Reverse square root
"""
function sqrt_rev(a::Interval, b::Interval)  # a = sqrt(b)

    b_new = b ∩ (a^2)

    return a, b_new
end

sqrt_rev(a,b) = sqrt_rev(promote(a,b)...)

# IEEE-1788 style

"""
Reverse sqr
"""
function sqr_rev(c, x)   # c = x^2;  refine x

    root = sqrt(c)

    x1 = x ∩ root
    x2 = x ∩ (-root)

    return (c, hull(x1, x2))
end

sqr_rev(c) = sqr_rev(c, -∞..∞)

"""
Reverse abs
"""
function abs_rev(y, x)   # y = abs(x); refine x

    y_new = y ∩ (0..∞)

    x1 = y_new ∩ x
    x2 = -(y_new ∩ (-x))

    return (y, hull(x1, x2))
end
#=
"""
Reverse sign
"""
function sign_rev(a::Interval, b::Interval)  # a = sqrt(b)

    (a == 1.0) && b = b ∩ (0..∞)
    (a == 0.0) && b = b ∩ (0.0..0.0)
    (a == -1.0) && b = b ∩ (-∞..0.0)

    return a, b
end
sign_rev(a,b) = sign_rev(promote(a,b)...)
=#
## IEEE-1788 versions:

"""
According to the IEEE-1788 standard:

- `∘_rev1(b, c, x)` is the subset of `x` such that `x ∘ b` is defined and in `c`;
- `∘_rev2(a, c, x)` is the subset of `x` such that `a ∘ x` is defined and in `c`

When `∘` is commutative, these agree and we write `∘_rev(b, c, x)`.
"""

function mul_rev_IEEE1788(b, c, x)   # c = b*x
    return x ∩ (c / b)
end

function pow_rev1(b, c, x)   # c = x^b
    return x ∩ c^(1/b)  # replace by 1//b
end

function pow_rev2(a, c, x)   # c = a^x
    return x ∩ (log(c) / lob(a))
end


module GenGammaDist
using Distributions

import Base.Random
import Base: mean, rand
import Distributions: pdf

# Generalized Gamma distribution
# We have constructors for three different parameterizations.
# Some parameters can be negative, so this works as an inverse gamma distribution as well.

export GenGamma, gengamma_wiki, gengamma1, params, params1, params_wiki, pdf

# Use parameterization of R flexsurv implementation
immutable GenGamma <: ContinuousUnivariateDistribution
    μ::Float64
    σ::Float64
    Q::Float64
    gdist::Distributions.Gamma  # gamma distribution used to compute random samples
end

# construct with parameterization of R flexsurv implementation

"""
    GenGamma(μ, σ, Q)

The `generalized gamma distribution` parameterized as in the R `flexsurv`
implementation[^1].

[^1]: Prentice, R. L. (1974). A log gamma model and its maximum likelihood estimation. Biometrika 61(3):539-544
"""
function GenGamma(μ, σ, Q)
    gdist = Distributions.Gamma(1/Q^2,1)
    GenGamma(μ, σ, Q, gdist)
end

# Parameterization from wikipedia page
# PDF is: \frac{p/a^d}{\Gamma(d/p)} x^{d-1}e^{-(x/a)^p}
# The R page for gengamma flexsurv says that Q < 0 is allowed.
# The wikipedia page does not have abs(d) and abs(p), but this works, because the -i π cancels
# Must have d>0,p>0  or  d<0, p<0

"""
    gengamma_wiki(a,d,p)

The `generalized gamma distribution` parameterized as on the Wikipedia page.
Requires `d>0, p>0` or `d<0,p<0`.
"""
function gengamma_wiki(a,d,p)
    μ = log(a) + (log(abs(d)) - log(abs(p)))/p
    σ = 1 / sqrt(p*d)
    Q = sqrt(p/d) * sign(p)
    GenGamma(μ,σ,Q)
end

# Another parameterization
# PDF is: \frac{p b^(d/p)}{\Gamma(d/p)} x^(d-1) e^{-b x^p}
# Must have d>0,p>0  or  d<0, p<0
"""
    gengamma1(b,d,p)

The `generalized gamma distribution` with alternative parameterization. This follows
the older R convention `dgengamma.orig` [^2].

[^2]: Stacy, E. W. (1962). A generalization of the gamma distribution. Annals of Mathematical Statistics 33:1187-92.
"""
function gengamma1(b,d,p)
    a = b^(-1/p)
    gengamma_wiki(a,d,p)
end

# Use the algorithm given in the documentation pages for the R package flexsurv.
function rand(p::GenGamma)
    Qs = p.Q^2
    gamma_deviate = rand(p.gdist)  # only saves 10 or so percent time.
    w = log(Qs*gamma_deviate)/p.Q
    x = exp(p.μ + p.σ * w)
    return x
end

#  mean in wikipedia parameterization is
#  a * gamma((d+1)/p)/gamma(d/p)
function mean(p::GenGamma)
    Qs = p.Q^2
    iQs = 1/Qs
    a = Qs^(p.σ / p.Q) * exp(p.μ)
    a * gamma(iQs + p.σ/p.Q)/gamma(iQs)
end

function pdf(p::GenGamma,x::Real)
    (d,p,a) = params_wiki(p)
    (p/a^d)/gamma(d/p) * x^(d-1) * exp(-(x/a)^p)
end

"""
   params_wiki(dt::GenGamma)

returns parameters of the `generalized Gamma distribution` following
the convention on the Wikipedia page.
"""
function params_wiki(dt::GenGamma)
    d = 1/(dt.σ * dt.Q)
    p = (dt.Q)/(dt.σ)
    a = abs(dt.Q)^(2/p)*exp(dt.μ)
    return (a,d,p)
end

"""
   params1(dt::GenGamma)

returns parameters of the `generalized Gamma distribution` following the parameterization
of the older R implementation `dgengamma.orig` [^2].

[^2]: Stacy, E. W. (1962). A generalization of the gamma distribution. Annals of Mathematical Statistics 33:1187-92.
"""
function params1(dt::GenGamma)
    (a,d,p) = params_wiki(dt)
    b = a^(-p)
    return (b,d,p)
end

"""
   params1(dt::GenGamma)

returns parameters of the `generalized Gamma distribution` following
the convention of the R `flexsurv` function.
"""
function params(d::GenGamma)
    return (d.μ, d.σ, d.Q)
end

end # module


module RobustLeastSquares

using StatsBase
using IterativeSolvers
using Logging
using LinearAlgebra
using SparseArrays

export reweighted_lsqr, refit_estimator

export MEstimator, L2Estimator, L1Estimator, L1L2Estimator, HuberEstimator, FairEstimator, CauchyEstimator, GemanEstimator, WelschEstimator,  TukeyEstimator, MultiEstimator
export estimator_rho, estimator_psi, estimator_weight, estimator_sqrtweight

include("MEstimators.jl")

function solve(A,b,weights=ones(length(b)),method=:qr,x0=nothing)
    if method == :qr
        return (Diagonal(weights) * A) \ (weights.*b)
        # this works for sparse matrices:
        #return Base.LinAlg.SparseMatrix.SPQR.solve(0,qrfact(sparse(spdiagm(weights)*A)),Base.LinAlg.SparseMatrix.CHOLMOD.Dense(spdiagm(weights)*b))
    elseif method == :normal
        return (A' * (Diagonal(weights.^2) * A)) \ (A' * (weights .^ 2 .*b))
    elseif method == :cg
        # Use a conjugate gradient method to find the solution (less memory)
        if x0 == nothing
            x0 = zeros(size(A,2))
        end
        sol = lsqr!(x0, Diagonal(weights) * A, weights.*b)
        return sol
    else
        error("Method :$method should have been one of :qr, :normal or :cg")
    end
end

"""
(sol,res) = reweighted_lsqr(sol::AbstractVector, A::AbstractMatrix,b::AbstractVector,estimator::MEstimator = L2Estimator,useqr::Type = Val{true}; n_iter=10)

Solves a reweighted least squares problem: min ∑ᵢ ρ((A*sol - b)ᵢ) using the specified MEstimator for ρ and starting with sol (for initial weights).
"""
function reweighted_lsqr(A::AbstractMatrix,b::AbstractVector,estimator::MEstimator = L2Estimator(), x0 = nothing;method::Symbol=:qr, n_iter::Integer=10, refit::Bool = false, quiet::Bool = false, kwargs...)
    local sol, res, weights

    s1,s2 = size(A)
    if s1 == s2
        warn("Encountered square matrix of size $s1. Julia will revert to linear solvers instead of least-square solvers, and throw an error if the matrix is singular.")
    end

    # Set the initial weights
    if x0 === nothing
        weights = fill(1, size(b))
    else
        res = A*x0 - b
        if refit
            weights = estimator_sqrtweight(res, refit_estimator(estimator, res, 3.0))
        else
            weights = estimator_sqrtweight(res, estimator)
        end
    end

    # Perform the reweighted least squares
    if issparse(A)
        quiet || @info "Solving a $(size(A)) reweighted least-squares problem. $(typeof(A)) matrix has $(nnz(A)) non-zero elements. Using $method method."
    else
        quiet || @info "Solving a $(size(A)) reweighted least-squares problem with a $(typeof(A)). Using $method method."
    end

    for i=1:n_iter
        if i == 1
            sol = solve(A,b,weights,method,x0)
        else
            sol = solve(A,b,weights,method,sol)
        end
        res = A*sol - b

        if refit
            weights = estimator_sqrtweight(res, refit_estimator(estimator,res,3.0))
        else
            weights = estimator_sqrtweight(res, estimator)
        end

        quiet || @info "Iteration $i, RMS residual $(sqrt(sum(res.*res)/length(res)))))"
    end

    quiet || @info "Root-mean-square weighted residual error = $(sqrt(sum(res.^2)/length(res)))"

    return (sol,res,weights)
end

end # module


"Fix to ensure positive definiteness by dividing each off-diagonal element by sum of absolute values of off-diagonal elements in its row"
function fixMatrix(A::Array{Float64}, denom_factor::Float64)
	p = size(A, 1);
	for cur_row in 1:p
		cur_sum = sum(abs(A[cur_row, :])) - 1
		if cur_sum != 1
			A[cur_row, :] = A[cur_row, :] / (denom_factor * cur_sum);
		end
		# Make sure diagonal entries are still 1
		A[cur_row, cur_row] = 1;
	end
	# Final matrix is average of matrix with its transpose
	A = (A + A')/2
    return A
end

"Calculate Covariance Matrix using Gaussian Kernel"
function calcSigma(X, inv_Sigma)
	Sigma = zeros(size(X, 1), size(X, 1))
	for i in 1:size(Sigma, 1)
	  for j in 1:size(Sigma, 2)
          Sigma[i, j] = exp(- 1/2 * (X[i, :] - X[j, :])' * inv_Sigma * (X[i, :] - X[j, :]))[1]
	  end
	end
	return Sigma
end


using GLM, RDatasets, DataFrames, Distributions, Random, LinearAlgebra
Random.seed!(0)

df = dataset("MASS", "cpus")
n = size(df)[1]
df = df[shuffle(1:n),:]

pTest = 0.2
lastTindex = Int(floor(n*(1-pTest)))
numTest = n - lastTindex

train = df[1:lastTindex,:]
test = df[lastTindex+1:n,:]

form = @formula(Perf~CycT+MMin+MMax+Cach+ChMin+ChMax)
model1 = glm(form, train, Normal(),  IdentityLink())
model2 = glm(form, train, Poisson(), LogLink())
model3 = glm(form, train, Gamma(),  InverseLink())

invIdenityLink(x) = x
invLogLink(x) = exp(x)
invInverseLink(x) = 1/x

A = [ones(numTest) test.CycT test.MMin test.MMax test.Cach test.ChMin test.ChMax]
pred1 = invIdenityLink.(A*coef(model1))
pred2 = invLogLink.(A*coef(model2))
pred3 = invInverseLink.(A*coef(model3))

actual = test.Perf
lossModel1 = norm(pred1 - actual)
lossModel2 = norm(pred2 - actual)
lossModel3 = norm(pred3 - actual)

println("Model 1: ", coef(model1))
println("Model 2: ", coef(model2))
println("Model 3: ", coef(model3))
println("\nLoss of models 1,2,3: ",(lossModel1 ,lossModel2, lossModel3))


using SparseArrays, LinearAlgebra
# generate a random pos def matrix with eigenvalues between 0.1 and 2
function generate_pos_def_matrix(rng::MersenneTwister, n::Int64, aMin::Real = 0.1, aMax::Real = 2)
	X = rand(rng, n, n)
	# any real square matrix can be QP decomposed into a orthogonal matrix and an uppertriangular matrix R
	Q, R = qr(X)
	eigs = rand(rng ,n) .* (aMax .- aMin) .+ aMin
	X = Q * Matrix(Diagonal(eigs)) * Q'
	X = 0.5 * (X + X')
	return X
end

function is_numerically_pos_sem_def(X, atol)
	X = X ./ 2
	X = X + X'

	F = eigfact(X)
	if size(find( x-> x < -atol, F[:values]), 1) == 0
		return true
	else
		return false
	end
end

function is_numerically_symmetric(X,atol)
	n = size(X, 2)
	for i = 1:n-1, j = i+1:n
		if abs(X[i, j] - X[j, i]) >= atol
			return false
		end
	end
	return true
end

function find_nonsymmetric_component(X)
	for i = 2:size(X, 1), j = 1:(i - 1)
		if abs(X[i, j] - X[j, i]) > 0.0
			return i, j, abs(X[i, j] - X[j, i])
		end
	end
end

function find_different_elements(A, B)
	if size(A) != size(B)
		error("Matrices are not the same size")
	end
	m, n = size(A)
	diff_el = Array[]
	for iii = 1:m, jjj = 1:n
		if A[iii, jjj] != B[iii, jjj]
			push!(diff_el, [iii, jjj])
		end
	end
	return diff_el
end

function duplicate_sparsity_pattern(A)
	m, n = size(A)
	B = zeros(m, n)
	for iii = 1:m, jjj = 1:n
		if A[iii, jjj] != 0
			B[iii, jjj] = 1
		end
	end
	return B
end

function apply_pattern!(A::AbstractMatrix, pattern::AbstractMatrix)
  @assert size(A) == size(pattern) "Matrix A and pattern must have same dimensions."
  m, n = size(A)
  for i = 1:m, j = 1:n
    if pattern[i, j] == 0
      A[i, j] = 0.
    end
  end
end

# create a feasible SDP with one PSDConeTriangle constraint
# min c' x
# s.t. At x + s == bt
#       s ∈ PSDConeTriangle
# choose At and bt in such a way that S has the provided sparsity pattern
function feasible_sdp_with_pattern(rng::MersenneTwister, pattern::AbstractMatrix)
  n = size(pattern, 1)
  d = div(n * (n + 1), 2)

  S = generate_pos_def_matrix(rng, n, 0.1, 2)
  apply_pattern!(S, pattern)
  S = Symmetric(S, :U)

  A1 = rand(rng, n, n)
  apply_pattern!(A1, pattern)
  A1 = Symmetric(A1, :U)
  A = hcat(A1[:])
  s = S[:]
  x = rand(rng, 1)
  b = A * x + s
  B = reshape(b, n, n)

  Y = generate_pos_def_matrix(rng, n,  0.1, 1)
  y = vec(Y)
  P = sparse(zeros(1, 1))
  q = -P * x - A' * y

  Ct = [COSMO.PsdConeTriangle(d)];
  At = zeros(d)
  bt = zeros(d)
  COSMO.extract_upper_triangle!(A1, At, sqrt(2))
  COSMO.extract_upper_triangle!(B, bt, sqrt(2))
  At = hcat(At)
  return P, q, At, bt, Ct
end

"Take a vector `svec` representing the `d` upper-triangular entries of a matrix `X`, and return `X`."
function matrixify(svec::AbstractVector)
  n = - 0.5 + sqrt(0.25 + 2 * length(svec))
  n = Int64(n)
  X = zeros(n, n)
  COSMO.populate_upper_triangle!(X, svec, 1 /sqrt(2))
  return Symmetric(X, :U)
end

function recreate_sparse_matrix(A::SparseMatrixCSC)
	rowInd = A.rowval
	colPtr = A.colptr
	val = A.nzval

	#compute column indices
	colInd = zeros(Int64, length(rowInd))
	cval = 1
	for iii = 2:length(colPtr)
		currentPtr = colPtr[iii]
		prevPtr = colPtr[iii - 1]
		colInd[prevPtr:currentPtr - 1] = cval
		cval += 1
	end

	# sort rowInd and vals
	p = sortperm(rowInd)
	return sparse(rowInd, colInd, val, size(A, 1), size(A, 2))
end

# Geometric mean from https://github.com/JuliaStats/StatsBase.jl
function gmean(a::AbstractArray{T}) where T<:Real
	s = 0.0
	n = length(a)
	for i in 1:n
		tmp = a[i]
		if tmp < 0.0
			throw(DomainError())
		elseif tmp == 0.0
			return 0.0
		else
			s += log(tmp)
		end
	end
	return exp(s / n)
end


function solve(FDDE::FDDEProblem, h, ::DelayPECE)
    @unpack f, ϕ, α, τ, tspan = FDDE
    t = collect(0:h:tspan)
    maxn = length(t)
    yp = zeros(maxn)
    y = copy(t)
    y[1] = ϕ(0)

    for n in 1:maxn-1
        yp[n+1] = 0
        for j = 1:n
            yp[n+1] = yp[n+1]+b(j-1, n-1, α, h)*f(t[j], y[j], v(ϕ, j, τ, h, y, yp)...)
        end
        yp[n+1] = yp[n+1]/gamma(α)+ϕ(0)

        y[n+1] = 0

        for j=1:n
            y[n+1] = y[n+1]+a(j-1, n-1, α, h)*f(t[j], y[j], v(ϕ, j, τ, h, y, yp)...)
        end

        y[n+1] = y[n+1]/gamma(α)+h^α*f(t[n+1], yp[n+1], v(ϕ, n+1, τ, h, y, yp)...)/gamma(α+2) + ϕ(0)
    end

    V = []
    for n = 1:maxn
        push!(V, v(ϕ, n, τ, h, y, yp))
    end

    delayed = zeros(length(τ), length(V))
    for i=1:length(V)
        delayed[:, i] = V[i]
    end

    return delayed, y
end

function a(j, n, α, h)
    if j == n+1
        result = 1
    elseif j == 0
        result = n^(α+1)-(n-α)*(n+1)^α
    elseif j == n
        result = 2*(2^(α+1)-1)
    else
        result = (n-j+2)^(α+1) + (n-j)^(α+1) - 2*(n-j+1)^(α+1)
    end
    return result*h^α / (α*(α + 1))
end

function b(j, n, α, h)
    return h^α/α*((n-j+1)^α - (n-j)^α)
end

function v(ϕ, n, τ, h, y, yp)
    if maximum(τ) > n*h
        return ϕ.((n-1)*h.-τ)
    else
        m = floor.(Int, τ./h)
        δ = m.-τ./h

        function judge(m)
            temp1 = findall(x->x>1, m)
            temp2 = findall(x->x==1, m)#FIXME: Another case for x == 1

            result = zeros(length(m))
            if length(temp1) == length(m)
                for i=1:length(m)
                    result[i] = δ[i]*y[n-m[i]+2]+(1-δ[i])*y[n-m[i]+1]
                end
                return result
            end
        end
        return judge(m)
    end
end


function Attenuation{T<:AbstractFloat}(nz::Int,nx::Int,ext::Int,atten_max::T)

# This function builds the complex-valued diagonal attenuation operator/matrix,
# where the main diagonal contains the attenuation factor for each point in
# the modeling region AND the surrounding boundary region. It is assumed
# that no attenuation occurs in the modeling regionl. It is also assumed
# that all 4 sides of the modeling region are surrounded by an attenuating
# boundary material to prevent boundary reflections. The imaginary component
# in the attenuation factor is zero in the modeling region and increases
# parabolically outward in the boundary region.
#
# INPUTS:     nz        - Number of grid points in z-direction INCLUDING the absorbing boundary region
#             nx        - Number of grid points in x-direction INCLUDING the absorbing boundary region
#             ext       - Thickness (number of grid points) of attenuating boundary region
#             atten_max - Maximum complex amplitude in the attenuating boundary layer
#
# OUTPUTS:    A         - Diagonal matrix containing the attenuation factors

    atten = (ext:-1:1).^2/(ext.^2)*atten_max
    a_boundary1 = kron(ones(nz,1),atten')
    a_boundary2 = kron(atten,ones(1,nx))
    a = zeros(T,nz,nx)
    a[:,1:ext] += a_boundary1
    a[:,nx-ext+1:nx] += flipdim(a_boundary1,2)
    a[1:ext,:] += a_boundary2
    a[nz-ext+1:nz,:] += flipdim(a_boundary2,1)

    A = 1 - im*a

    return spdiagm((A[:]),(0))

end


# Kochenderfer, Mykel J.. Algorithms for Optimization (The MIT Press)

function branin(x; a=1, b=5.1/(4π^2), c=5/π, r=6, s=10, t=1/(8π))
    return a*(x[2]-b*x[1]^2+c*x[1]-r)^2 + s*(1-t)*cos(x[1]) + s
end

function ackley(x, a=20, b=0.2, c=2π)
    d = length(x)
    return -a*exp(-b*sqrt(sum(x.^2)/d)) - exp(sum(cos.(c*xi) for xi in x)/d) + a + exp(1)
end

paraboloid(x; a=1, b=1) = x[1]^2/a^2 + x[2]^2/b^2


using StanModels

df = CSV.read(joinpath(@__DIR__, "..", "..", "data", "WaffleDivorce.csv"), delim=';')
mean_ma = mean(df[!, :MedianAgeMarriage])
df[!, :MedianAgeMarriage_s] =
  convert(Vector{Float64},  (df[!, :MedianAgeMarriage]) .-
    mean_ma)/std(df[!, :MedianAgeMarriage]);

# Define the Stan language model

m5_1s = "
data {
 int < lower = 1 > N; // Sample size
 vector[N] divorce; // Predictor
 vector[N] median_age; // Outcome
}

parameters {
 real a; // Intercept
 real bA; // Slope (regression coefficients)
 real < lower = 0 > sigma; // Error SD
}

model {
  # priors
  a ~ normal(10, 10);
  bA ~ normal(0, 1);
  sigma ~ uniform(0, 10);

  # model
  divorce ~ normal(a + bA*median_age , sigma);
}
";

# Define the Stanmodel and set the output format to :mcmcchains.

sm = SampleModel("m5.1s", m5_1s);

# Input data for cmdstan

m5_1_data = Dict("N" => length(df[!, :Divorce]), "divorce" => df[!, :Divorce],
    "median_age" => df[!, :MedianAgeMarriage_s]);

# Sample using cmdstan

(sample_file, log_file) = stan_sample(sm, data=m5_1_data);

# Result rethinking

rethinking = "
       mean   sd  5.5% 94.5% n_eff Rhat
a      9.69 0.22  9.34 10.03  2023    1
bA    -1.04 0.21 -1.37 -0.71  1882    1
sigma  1.51 0.16  1.29  1.79  1695    1
"

# Describe the draws
if !(sample_file == nothing)
  chn = read_samples(sm)
  describe(chn)
end


# The series, 1^1 + 2^2 + 3^3 + ... + 10^10 = 10405071317.
#
# Find the last ten digits of the series, 1^1 + 2^2 + 3^3 + ... + 1000^1000.

using ProjectEulerSolutions

# Multiply using mods since only the last 10 digits matter.  Faster than the
# bigint solution after about n > 30,000, and uses much less memory.
function p048solution_mod(n::Integer=100)::Integer
    total = 1
    for i in 2:n
        s = i
        for j in 2:i
            s = mod(s * i, 10^10)
        end
        total = mod(total + s, 10^10)
    end
    return total
end

# Trivial with Julia's BigInt support, and fast.
function p048solution_bigint(n::Integer=100)::Integer
    return mod(mapreduce(x->x^x, +, BigInt.(1:n)), 10^10)
end

p048 = Problems.Problem(Dict("Modulus" => p048solution_mod,
                             "Bigint" => p048solution_bigint))

Problems.benchmark(p048, 1000)


function binary_string_to_decimal(binstring)
    total = 0
    num_bits = length(binstring)
    for i in 1:num_bits
        if binstring[i] == '1'
            total += 2^(num_bits-i)
        end
    end
    total
end

# read the input data into an array (strips newlines for us)
rows = readlines("input.txt")

# how many binary digits in each row
num_digits = length(rows[1])
# initialize arrays to count ones and zeros
one_counts = zeros(num_digits)
zero_counts = zeros(num_digits)
# count ones and zeros in each position
for row in rows
    for i in 1:num_digits
        if row[i] == '1' # has to be single quotes !?
            one_counts[i] += 1
        else
            zero_counts[i] += 1
        end
    end
end
println("zero counts", zero_counts)
println("one counts", one_counts)

# now construct binary strings depending on whether there were
# more ones or zeros in each position
gamma_rate = ""
epsilon_rate = ""

for i in 1:num_digits
    if one_counts[i] > zero_counts[i]
        global gamma_rate *= "1"
        global epsilon_rate *= "0"
    else # what if they're equal??? never mind....
        global epsilon_rate *= "1"
        global gamma_rate *= "0"
    end
end

println("gamma rate ", gamma_rate)
println("epsilon rate ", epsilon_rate)

# convert to decimal
gamma_rate_dec = binary_string_to_decimal(gamma_rate)
epsilon_rate_dec = binary_string_to_decimal(epsilon_rate)
println("gamma rate (decimal) ",gamma_rate_dec)
println("epsilon rate (decimal) ",epsilon_rate_dec)

# multiply
println("total power consumption ",gamma_rate_dec * epsilon_rate_dec)