# functions for building various standard types of graphs

export Complete, Path, Cycle, RandomGraph, RandomRegular, RandomSBM
export RandomTree, code_to_tree
export Grid, Wheel, Cube, BuckyBall, Johnson
export Petersen, Kneser, Paley, Knight, Frucht, Hoffman, HoffmanSingleton

"""
`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`.
"""
function Complete(n::Int)
    G = IntGraph(n)

    for k=1:n-1
        for j=k+1:n
            add!(G,j,k)
        end
    end
    name(G,"Complete graph K($n)")
    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
    name(G,"Complete bipartite graph K($n,$m)")
    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}(undef,np,2)   # old 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
    name(G,"Complete multipartite graph K$parts")
    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
    name(G,"Path graph P($n)")
    return G
end

# Create a path graph from a list of vertices
function Path(verts::Array{T}) where {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
    name(G,"Path graph with $n vertices")
    return G
end

# Create a cycle graph on n vertices
"""
`Cycle(n)` creates a cycle with vertex set `1:n`.
"""
function Cycle(n::Int)
    if n<3
        error("Cycle requires 3 or more vertices")
    end
    G = Path(n)
    add!(G,1,n)
    name(G,"Cycle graph C($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
    name(G,"Wheel graph with $n vertices")
    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

    # embedding
    d = Dict{Any, Array{Float64,1}}()
    for v in G.V
        x,y = v
        d[v] = [Float64(x),Float64(y)]
    end
    embed(G,d)
    recenter(G)

    name(G,"Grid graph of size $n-by-$m")
    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
    name(G,"Erdos-Renyi random graph, p=$p")
    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 ]
    G =  code_to_tree(code)
    name(G,"Random tree")
    return G
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 = findall(x->x!=0,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 = xor( (1<<shift), u)
            add!(G,string(u,base=2,pad=n), string(v,base=2,pad=n))
        end
    end
    name(G,"Cube graph Q($n)")
    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
    name(G,"Buckyball graph")
    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).

# import IterTools.subsets

"""
`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 IterTools.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
    name(G,"Kneser($n,$k) graph")
    return G
end

"""
`Johnson(n,k)` creates the Johnson graph whose vertices
and the `k`-element subsets of `{1,2,...,n}`. Vertices `v`
and `w` are adjacent if their intersection has size `k-1`.
"""
function Johnson(n::Int, k::Int)
    if k<0 || n<0
        error("n,k must be nonnegative")
    end

    if k>n
        error("k must not be greater than n")
    end

    A = collect(1:n)
    vtcs = [Set{Int}(v) for v in IterTools.subsets(A,k)]
    G = SimpleGraph{Set{Int}}()

    for v in vtcs
        add!(G,v)

        for j=1:n
            if !in(j,v)
                for x in v
                    w = deepcopy(v)
                    push!(w,j)
                    pop!(w,x)
                    add!(G,v,w)
                end
            end
        end
    end

    name(G,"Johnson($n,$k) graph")
    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`.
"""
function Petersen()
  G = Kneser(5,2)
  name(G,"Petersen graph")
  return G
end

# 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
    name(G,"Paley($p) graph")
    return G
end

"""
`Frucht()` returns the Frucht graph: A 12-vertex, 3-regular
graph with no non-nontrivial automorphisms.
"""
function Frucht()
  G = Cycle(12)
  more_edges = [ (1,6), (2,4), (3,11), (5,7), (8,10), (9,12) ]
  add_edges!(G,more_edges)
  name(G,"Frucht graph")
  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
            name(g,"Random regular graph, d=$d")
            return g
        end
        if verbose
            println("Failed; trying again")
        end
    end
end

"""
`RandomSBM(bmap,pmat)` creates a random stochastic block model random graph.
The vector `bmap` is a list of `n` positive integers giving the block number
of vertices `1:n`. The `i,j`-entry of the matrix `pmat` gives the probability
of an edge from a vertex in block `i` to a vertex in block `j`.

`RandomSBM(n,pvec,pmat)` creates such a graph with `n` vertices. The vector
`pvec` gives the probabilities that vertices fall into a given block.
"""
function RandomSBM(bmap::Vector{Int}, pmat::Array{S,2}) where {S<:Real}
  n = length(bmap)    # no of vertices
  b = maximum(bmap)   # no of blocks

  @assert minimum(bmap)>0 "Block numbers must be positive"
  @assert pmat == pmat' "Probability matrix must be square and symmetrical"
  r = size(pmat,1)
  @assert b<=r "Insufficient rows/cols in probability matrix"
  @assert minimum(pmat)>=0 && maximum(pmat)<=1 "Entries in probability matrix out of range"

  G = IntGraph(n)
  for u=1:n-1
    bu = bmap[u]
    for v=u+1:n
      bv = bmap[v]
      p = pmat[bu,bv]
      if rand() <= p
        add!(G,u,v)
      end
    end
  end
  name(G,"Random stochastic block model graph")
  return G
end

function RandomSBM(n::Int, pvec::Vector{S}, pmat::Array{T,2}) where {S<:Real,T<:Real}
  @assert minimum(pvec)>=0 "Entries in pvec must be nonnegative"
  @assert sum(pvec)==1 "Entries in pvec must sum to 1"
  bmap = [ random_choice(pvec) for _=1:n]
  return RandomSBM(bmap,pmat)
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(Base.Iterators.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
    name(G,"Knight's tour graph on $r-by-$c chess board")
    return G
end

"""
`HoffmanSingleton()` creates the Hoffman-Singleton graph. This is
a 7-regular graph whose diameter is 2 and whose girth is 5.
"""
function HoffmanSingleton()
  G = StringGraph()
  # P-pentagons
  for i=0:4
    for j=0:4
      jj = mod(j+1,5)
      v = "P$i$j"
      w = "P$i$jj"
      add!(G,v,w)
    end
  end

  # Q-pentagrams
  for i=0:4
    for j=0:4
      jj = mod(j+2,5)
      v = "Q$i$j"
      w = "Q$i$jj"
      add!(G,v,w)
    end
  end

  # Connections
  for i=0:4
    for j=0:4
      for k=0:4
        v = "P$i$j"
        x = mod(i*k+j,5)
        w = "Q$k$x"
        add!(G,v,w)
      end
    end
  end
  name(G,"Hoffman-Singleton graph")
  return G
end

"""
`Hoffman()` creates the Hoffman graph which is cospectral with,
but not isomorphic to, `Cube(4)`.
"""
function Hoffman()
  D = [
  1 1 1 1 0 0 0 0
  1 1 1 0 1 0 0 0
  1 0 0 1 0 1 1 0
  0 1 0 1 0 1 0 1
  0 0 1 1 0 0 1 1
  1 0 0 0 1 1 1 0
  0 1 0 0 1 1 0 1
  0 0 1 0 1 0 1 1]

  A = [0*D D; D' 0*D]

  G = SimpleGraph(A)
  name(G,"Hoffman graph")
  return G
end


using NumericalMethodsforEngineers, Test

sv = [16., 4., 5., 8., -4., 22.]
kdiag = [1, 3, 6]

amat = fromskyline(kdiag, sv)
amat |> display
println()

println("\n'Julia' version:\n")
a = amat
b = [4., 2., 5.]

a[2, 2] += 1.0e20
b[2] = a[2, 2] * 5.0

a = sparse(a)

f = cholesky(a)
y = f.L \ b
c = f.U \ y

println("Solution Vector: \n $c")

@test c == a \ b

#------------------------------------

println("\n'Fortran' version:\n")
b = [4., 2., 5.]

nfix = 1
no = [2;]
val = [5.0;]
penalty = 1.0e20

sv[kdiag[no]] .+= penalty
b[no] = sv[kdiag[no]] .* val

sparin!(sv, kdiag)
spabac!(sv, b, kdiag)
println("Solution Vector: \n $b")

sv = [16., 4., 5., 8., -4., 22.]
b = [4., 2., 5.]
sv[kdiag[no]] .+= penalty
b[no] = sv[kdiag[no]] .* val

svt = sparin(sv, kdiag)
bt = spabac(svt, b, kdiag)
println("Solution Vector 2: \n $bt")


# I implement several simple but super loose bounds
# These only apply to closed tours

# the cost of a tour must be >= the sum over all vertices of
# the cost of the cheapest edge leaving that vertex
# likewise for the cheapest edge entering that vertex
# since we must go to and leave each vertex
function vertwise_bound(distmat::AbstractMatrix{T}) where {T<:Real}
	# the simple code below would tend to pick out the 0 costs on the diagonal
	# so make a doctored copy of the distance matrix with high costs on the diagonal
	m = maximum(distmat)
	distmat_nodiag = distmat + m * I
	leaving = sum(minimum(distmat_nodiag, dims = 2))
	entering = sum(minimum(distmat_nodiag, dims = 1))
	return maximum([leaving, entering])
end

# helper to get min spanning trees
# returns a (n-1) long Vector of Tuple{Int, Int} where each tuple is an edge in the MST
# and the total weight of the tree
# the matrix passed in must be symmetric or you won't get out the minimum spanning tree
function minspantree(dm::AbstractMatrix{T}) where {T<:Real} # accepts views
	mst_edges = Vector{Tuple{Int, Int}}()
	mst_cost = zero(T)
	n = size(dm, 1)

	# we keep a running list of the distance from each vertex to the partly formed tree
	# rather than using 0 for vertices already in the tree, we use a large value so that we
	# can find the closest non-tree vertex via call to Julia's `findmin`.
	bigval = maximum(dm) + one(T)
	tree_dists = dm[1,:] # distance to tree
	closest_tree_verts = ones(Int, n)
	tree_dists[1] = bigval # vert 1 is in tree now

	for _ in 1:(n-1) # need to add n - 1 other verts to tree
		cost, newvert = findmin(tree_dists)
		treevert = closest_tree_verts[newvert]
		# add costs and edges
		mst_cost += cost
		if treevert < newvert
			push!(mst_edges, (treevert, newvert))
		else
			push!(mst_edges, (newvert, treevert))
		end
		# update distances to tree
		tree_dists[newvert] = bigval
		for i in 1:n
			c = tree_dists[i]
			if c >= bigval # already in tree
				continue
			end
			# maybe this vertex is closer to the new vertex than the prior iteration's tree
			if c > dm[i, newvert]
				tree_dists[i] = dm[i, newvert]
				closest_tree_verts[i] = newvert
			end
		end
	end

	return mst_edges, mst_cost
end

# a simplified/looser version of Held-Karp bounds
# any tour is a spanning tree on (n-1) verts plus two edges
# from the left out vert
# so the maximum over all verts (as the left out vert) of
# MST cost on remaining vertices plus 2 cheapest edges from
# the left out vert is a lower bound
# for extra simplicity, the distance matrix is modified to be symmetric so we can treat
# the underlying graph as undirected. This also doesn't help the bound!
function hkinspired_bound(distmat::AbstractMatrix{T}) where {T<:Real}
	n = size(distmat, 1)
	if size(distmat, 2) != n
		error("Must pass square distance matrix to hkinspired_bound")
	end

	# make a symmetric copy of the distance matrix
	distmat = copy(distmat)
	for i in 1:n, j in 1:n
		if i == j
			continue
		end
		d = min(distmat[i,j], distmat[j,i])
		distmat[i,j] = d
		distmat[j,i] = d
	end

	# get a view of the distmat with one vertex deleted
	function del_vert(v)
		keep = [1:(v-1) ; (v+1):n]
		return view(distmat, keep, keep)
	end

	# make sure min(distmat[v,:]) doesn't pick diagonal elements
	m = maximum(distmat)
	distmat_nodiag = distmat + m * I

	# lower bound the optimal cost by leaving a single vertex out
	# forming spanning tree on the rest
	# connecting the left-out vertex
	function cost_leave_out(v)
		dmprime = del_vert(v)
		_, c = minspantree(dmprime)
		c += minimum(distmat_nodiag[v,:])
		c += minimum(distmat_nodiag[:,v])
		return c
	end

	return maximum(map(cost_leave_out, 1:n))
end

# best lower bound we have
"""
    lowerbound(distmat)

Lower bound the cost of the optimal TSP tour. At present, the bounds considered
are a simple bound based on the minimum cost of entering and exiting each city and
a slightly better bound inspired by the Held-Karp bounds; note that the implementation
here is simpler and less tight than proper HK bounds.

The Held-Karp-inspired bound requires computing many spanning trees. For a faster but
typically looser bound, use `TravelingSalesmanHeuristics.vertwise_bound(distmat)`.

!!! note
    The spanning tree bounds are only correct on symmetric problem instances and will not be
    used if the passed `distmat` is not symmetric. If you wish to use these bounds anyway,
    (e.g. for near-symmetric instances), use `TravelingSalesmanHeuristics.hkinspired_bound`.
"""
function lowerbound(distmat::AbstractMatrix{T} where {T<:Real})
	vb = vertwise_bound(distmat)
	if !issymmetric(distmat)
		return vb
	end
	return max(vb, hkinspired_bound(distmat))
end


#=
# 405: Generic Operator: 1D Nonlinear Poisson equation
([source code](SOURCE_URL))

Solve the nonlinear Poisson equation

```math
-\nabla \varepsilon \nabla u + e^{u}-e^{-u} = f
```
in $\Omega=(0,1)$ with boundary condition $u(0)=0$ and $u(1)=1$ with
```math
f(x)=
    \begin{cases}
    1&,x>0.5\\
    -1&, x<0.5
    \end{cases}.
```

This stationary problem is an example of a nonlinear Poisson equation or Poisson-Boltzmann equation.
Such equation occur e.g. in simulations of electrochemical systems and semicondutor devices.

=#

module Example405_GenericOperator
using Printf
using VoronoiFVM
using ExtendableGrids
using GridVisualize

function main(;n=10,Plotter=nothing,verbose=false, unknown_storage=:sparse)

    ## Create a one-dimensional discretization
    h=1.0/convert(Float64,n)
    X=collect(0:h:1)
    grid=VoronoiFVM.Grid(X)

    ## A parameter which is "passed" to the flux function via scope
    ϵ=1.0e-3

    ## This generic operator works on the full solution seen as linear vector, and indexing
    ## into it needs to be performed with the help of idx (defined below for a solution vector)
    ## Here, instead of the flux function we provide a "generic operator"
    ## which provides the stiffness part of the problem. Its sparsity is detected automatically
    ## using SparsityDetection.jl
    function generic_operator!(f,u,sys)
        f.=0.0
        for i=1:length(X)-1
            du=ϵ*(u[idx[1,i]]-u[idx[1,i+1]])/(X[i+1]-X[i])
            f[idx[1,i]]+=du
            f[idx[1,i+1]]-=du
        end
    end

    ## Source term
    function source!(f,node)
        if node[1]<=0.5
            f[1]=1
        else
            f[1]=-1
        end
    end

    ## Reaction term
    function reaction!(f,u,node)
        f[1]=exp(u[1]) - exp(-u[1])
    end

    ## Create a physics structure
    physics=VoronoiFVM.Physics(
        generic=generic_operator!,
        source=source!,
        reaction=reaction!)

    ## Create a finite volume system - either
    ## in the dense or  the sparse version.
    ## The difference is in the way the solution object
    ## is stored - as dense or as sparse matrix

    sys=VoronoiFVM.System(grid,physics,unknown_storage=unknown_storage)

    ## Add species 1 to region 1
    enable_species!(sys,1,[1])

    ## Set boundary conditions
    boundary_dirichlet!(sys,1,1,0.0)
    boundary_dirichlet!(sys,1,2,1.0)

    ## Create a solution array
    inival=unknowns(sys,inival=0.5)
    solution=unknowns(sys)

    idx=unknown_indices(solution)
    ## Create solver control info
    control=VoronoiFVM.NewtonControl()
    control.verbose=verbose

    ## Stationary solution of the problem
    solve!(solution,inival,sys, control=control)

    scalarplot(grid,solution[1,:],title="Nonlinear Poisson",Plotter=Plotter)
    return sum(solution)
end

function test()
    testval=1.5247901344230088
    main(unknown_storage=:sparse) ≈ testval && main(unknown_storage=:dense) ≈ testval
end

end


function simpson(f::Function, a::Number, b::Number, n::Integer)
    h = (b - a) / n
    s = f(a + h / 2)
    for i in 1:(n-1)
        s += f(a + h * i + h / 2) + f(a + h * i) / 2
    end
    return h/6 * (f(a) + f(b) + 4*s)
end

rst =
    simpson(x -> x ^ 3, 0, 1, 100),
    simpson(x -> 1 / x, 1, 100, 1000),
    simpson(x -> x, 0, 5000, 5_000_000),
    simpson(x -> x, 0, 6000, 6_000_000)

@show rst


using Distributions, Plots; pyplot()

n1, n2 = 10, 15
N = 10^6
mu, sigma = 10, 4
normDist = Normal(mu,sigma)

fValues = Array{Float64}(undef, N)

for i in 1:N
    data1 = rand(normDist,n1)
    data2 = rand(normDist,n2)
    fValues[i] = var(data1)/var(data2)
end

fRange = 0:0.1:5
stephist(fValues, bins=400, c=:blue, label="Simulated", normed=true)
plot!(fRange, pdf.(FDist(n1-1, n2-1), fRange),
	c=:red, label="Analytic", xlims=(0,5), ylims=(0,0.8),
    	xlabel = "F", ylabel = "Density")


### A Pluto.jl notebook ###
# v0.12.6

using Markdown
using InteractiveUtils

# ╔═╡ 61a5b548-1ddc-11eb-2f7a-0d1b24e3cf48
using Pkg, DrWatson

# ╔═╡ 61d2b2c8-1ddc-11eb-3ae7-a3c92d8a64b3
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Plots
end

# ╔═╡ 0fdee93c-1ddc-11eb-2bde-712854f4263e
md"## Listing 1.11"

# ╔═╡ 61d33860-1ddc-11eb-1c60-eda2aa584f64
function hailLength(x::Int)
    n = 0
    while x != 1
        if x % 2 == 0
            x = Int(x/2)
        else
            x = 3x +1
        end
        n += 1
    end
    return n
end

# ╔═╡ 61dc8cb4-1ddc-11eb-065a-fdfe7782833d
lengths = [hailLength(x0) for x0 in 2:10^7];

# ╔═╡ 61dda098-1ddc-11eb-19ba-c3b600208626
histogram(lengths, bins=1000, normed=:true,
    fill=(:blue, true), la=0, legend=:none,
    xlims=(0, 500), ylims=(0, 0.012),
    xlabel="Length", ylabel="Frequency")

# ╔═╡ 61e6709c-1ddc-11eb-392c-97a365b3a46d
md"## End of listing 1.11"

# ╔═╡ Cell order:
# ╟─0fdee93c-1ddc-11eb-2bde-712854f4263e
# ╠═61a5b548-1ddc-11eb-2f7a-0d1b24e3cf48
# ╠═61d2b2c8-1ddc-11eb-3ae7-a3c92d8a64b3
# ╠═61d33860-1ddc-11eb-1c60-eda2aa584f64
# ╠═61dc8cb4-1ddc-11eb-065a-fdfe7782833d
# ╠═61dda098-1ddc-11eb-19ba-c3b600208626
# ╟─61e6709c-1ddc-11eb-392c-97a365b3a46d


using DiffEqBiological, Plots
gr(); default(fmt = :png);

bistable_switch = @reaction_network begin
    d,    (X,Y) → ∅
    hillR(Y,v1,K1,n1), ∅ → X
    hillR(X,v2,K2,n2), ∅ → Y
end d v1 K1 n1 v2 K2 n2
d = 0.01;
v1 = 1.5; K1 = 30; n1 = 3;
v2 = 1.; K2 = 30; n2 = 3;
bistable_switch_p = [d, v1 ,K1, n1, v2, K2, n2];

ss = steady_states(bistable_switch, bistable_switch_p)

stability(ss,bistable_switch, bistable_switch_p)

rn1 = @reaction_network begin
    p, ∅ → X
    hill(X,v,K,n), X → ∅
end p v K n
p1 = [1.,2.5,1.5,1.5]
steady_states(rn1,p1)

rn2 = @reaction_network begin
    p, ∅ → X
    log(X), X → ∅
end p
p2 = [1.]
steady_states(rn2,p2)

bif = bifurcations(bistable_switch, bistable_switch_p, :v1, (.1,5.))
plot(bif,ylabel="[X]",label="")
plot!([[],[]],color=[:blue :red],label = ["Stable" "Unstable"])

plot(bif,2,ylabel="[Y]")
plot!([[],[]],color=[:blue :red],label = ["Stable" "Unstable"])

bif = bifurcations(bistable_switch, bistable_switch_p,:v1,(.1,10.))
plot(bif,linewidth=1.,title="A bifurcation diagram",ylabel="Steady State concentration")
plot!([[],[]],color=[:blue :red],label = ["Stable" "Unstable"])

bif = bifurcation_grid(bistable_switch, bistable_switch_p,:n1,1.:5.)
plot(bif)
scatter!([[],[]],color=[:blue :red],label = ["Stable" "Unstable"])

bif = bifurcation_grid_diagram(bistable_switch, bistable_switch_p,:n1,0.:4.,:v1,(.1,5.))
plot(bif)
plot!([[],[]],color=[:blue :red],label = ["Stable" "Unstable"])

bif = bifurcation_grid_2d(bistable_switch, bistable_switch_p,:n1,1.:3.,:n2,1.:10.)
plot(bif)
scatter!([[],[]],color=[:blue :red],label = ["Stable" "Unstable"])

brusselator = @reaction_network begin
    A, ∅ → X
    1, 2X + Y → 3X
    B, X → Y
    1, X → ∅
end A B;
A = 0.5; B = 4.;
brusselator_p = [A, B];

bif = bifurcations(brusselator,brusselator_p,:B,(0.1,2.5))
plot(bif,2)
plot!([[],[],[],[]],color=[:blue :cyan :orange :red],label = ["Stable Real" "Stable Complex" "Unstable Complex" "Unstable Real"])

bif = bifurcation_grid_diagram(brusselator,brusselator_p,:B,0.5:0.02:5.0,:A,(0.2,5.0))
plot(bif)
plot!([[],[],[],[]],color=[:blue :cyan :orange :red],label = ["Stable Real" "Stable Complex" "Unstable Complex" "Unstable Real"])

using DiffEqTutorials
DiffEqTutorials.tutorial_footer(WEAVE_ARGS[:folder],WEAVE_ARGS[:file], remove_homedir=true)


@testset "basic operations" begin

# *, +, sin
x = Diffable(15)
y = Diffable(π/2)
z = x * y + sin(x)

@test backwards!(z) == 1.0
@test backwards!(y) == x.value
@test backwards!(x) == y.value + cos(x.value)

# negation
x = Diffable(4.0)
y = -x

@test backwards!(x) == -1.0

# division

# multiple instances of one variable
x = Diffable(3.0)
y = Diffable(2.0)
z = y * (x * x)
@test backwards!(z) == 1.0
@test backwards!(y) == x.value * x.value
@test backwards!(x) == y.value * 2 * x.value

# cos
x = Diffable(2.0)
z = Diffable(2.0) * cos(x)
@test backwards!(x) == -2.0 * sin(x.value)

# using a function
f(x) = Diffable(3.0) * x
x_1 = Diffable(2.0)
z = f(x_1)
@test backwards!(x_1) == 3.0

# nested functions

# identity

# power

# exponent

# relu

end #basic operations

@testset "interop with base types" begin

x = Diffable(15)
y = π/2
z = x * y + sin(x)

@test backwards!(x) == y + cos(x.value)

x = Diffable(15)
y = π/2
z = x * y + sin(x+2)

@test backwards!(x) == y + cos(x.value+2)

end #interop with base types

@testset "powers & natural log" begin

# x^n alone
a = Diffable(4.0)
y = a^3

@test backwards!(a) == 3 * 4.0^2

# b^x alone

# x^n chained

# b^x chained

# ℯ^x

x = Diffable(8.9)
y = 3 * exp(x)

@test backwards!(x) == 3 * exp(8.9)

x = Diffable(5.1)
y = exp(x^3)

@test backwards!(x) ≈ exp(5.1^3) * 3 * 5.1^2

# ln
x = Diffable(3.2)
y = log(x)

@test backwards!(x) == 1/3.2

x = Diffable(1.0)
y = exp(log(x))

@test backwards!(x) == 1.0

x = Diffable(-3.2)
@test_throws DomainError log(x)

end #powers & natural log


#Running mean
"""
    running_mean!(m, x)

Compute (in-place) the mean, `m`, of a set of vectors, `x`.
* `x` must be a matrix of Floats, with each column of `x` corresponding to a vector.
* `m` must be a vector of Floats of appropriate length (equal to the number of rows in `x`).
"""
function running_mean!(m::AbstractVector{T}, x::AbstractMatrix{T}) where T<:AbstractFloat
    k = zero(T)
    fill!(m,zero(T))
    @inbounds for j ∈ axes(x,2)
        k = 1/j
        @turbo for i ∈ axes(x,1)
            temp = (x[i,j] - m[i])
            m[i] += temp*k
        end
    end
end

"""
    running_mean(x)

Compute the mean of a set of vectors `x`.
* `x` must be a matrix of Floats, with each column of `x` corresponding to a vector.
"""
function running_mean(x::AbstractMatrix{T}) where T<:AbstractFloat
    m = zeros(T,size(x,1))
    running_mean!(m, x)
    return m
end

#Update running mean with a nth data point where n is the running index
"""
    update_running_mean!(m, x, n)

Update the mean, `m`, of some set of `n-1` vectors to be the mean of the set of `n` vectors where `x` is the ``n^th`` vector.
* `m` and `x` must be vectors of equal length.
* Typically used when the mean of a part of a dataset has been previously computed and needs to be updated with a new data point (vector).
"""
function update_running_mean!(mean::AbstractVector{T}, data::AbstractVector{T}, n::Int) where T<:AbstractFloat
    k = 1/n
    @inbounds @turbo for i ∈ eachindex(mean)
        temp = (data[i] - mean[i])*k
        mean[i] += temp
    end
end

#Running mean and variance
"""
    running_meanvar!(m, v, x)

Compute (in-place) the mean, `m`, and variance along each dimension, `v`, of a set of vectors, `x`.
* `x` must be a matrix of Floats, with each column of `x` corresponding to a vector.
* `m` must be a vector of Floats of appropriate length (equal to the number of rows in `x`).
* `v` must be a vector of Floats of appropriate length (equal to the number of rows in `x`).
"""
function running_meanvar!(m::AbstractVector{T}, v::AbstractVector{T}, x::AbstractMatrix{T}) where T<:AbstractFloat
    k = zero(T)
    fill!(m,zero(T))
    fill!(v,zero(T))
    @inbounds for j ∈ axes(x,2)
        k = 1/j
        @inbounds for i ∈ axes(x,1)
            temp = (x[i,j] - m[i])
            m[i] += temp*k
            v[i] += temp*(x[i,j] - m[i])
        end
    end
    N = 1/(size(x,2)-1)
    @turbo for i ∈ eachindex(v)
        v[i] *= N
    end
end

"""
    running_meanvar(x)

Compute the mean and variance along each dimension of a set of vectors, `x`.
* `x` must be a matrix of Floats, with each column of `x` corresponding to a vector.
"""
function running_meanvar(x::AbstractMatrix{T}) where T<:AbstractFloat
    m = zeros(T,size(x,1))
    v = zeros(T,size(x,1))
    running_meanvar!(m,v,x)
    return m, v
end

#Update running mean and variance with a nth data point where n is the running index
"""
    update_running_meanvar!(m, v, x, n)

Update the mean, `m`, and variance along each dimension, `v`, of some set of `n-1` vectors to be the mean and variance of the set of `n` vectors where `x` is the ``n^th`` vector (for `n>=3`).
* `m`, `v` and `x` must be vectors of equal length.
* Typically used when the mean and variance of a part of a dataset has been previously computed and needs to be updated with a new data point (vector).

!!! note
    `n` must be 3 or greater.
    [`running_meanvar()`](@ref) or [`running_meanvar!()`](@ref) can be used to obtain an initial estimate of `m` and `v` from at least two vectors.
"""
function update_running_meanvar!(m::AbstractVector{T}, v::AbstractVector{T}, x::AbstractVector{T}, n::Int) where T<:AbstractFloat
    n >=3 || error("n must be greater than 2. Use running_meanvar() to obtain an initial estimate of mean and variance from at least two vectors before using update_running_meanvar() to update the estimates.")
    k = 1/n
    k1 = 1/(n-1)
    @inbounds for i ∈ eachindex(m,v,x)
        temp = (x[i] - m[i])
        m[i] += temp*k
        v[i] *= (n-2)
        v[i] += temp*(x[i] - m[i])
        v[i] *= k1
    end
end

#running mean and covariance
"""
    running_meancov!(m, C, x)

Compute (in-place) the mean, `m`, and covariance matrix, `C`, of a set of vectors, `x`.
* `x` must be a matrix of Floats, with each column of `x` corresponding to a vector.
* `m` must be a vector of Floats of appropriate length (equal to the number of rows in `x`).
* `C` must be a square matrix of Floats of appropriate size (side equal to the number of rows in `x`).
"""
function running_meancov!(m::AbstractVector{T}, C::AbstractMatrix{T}, x::AbstractMatrix{T}) where T<:AbstractFloat
    k = zero(T)
    temp = Vector{T}(undef,length(m))
    fill!(m,zero(T))
    fill!(C,zero(T))
    @inbounds for n ∈ axes(x,2)
        k = 1/n
        @turbo for i ∈ axes(x,1)
            temp[i] = (x[i,n] - m[i])
            m[i] += temp[i]*k
        end
        @turbo for i ∈ axes(x,1), j ∈ axes(x,1)
            C[j,i] += (x[j,n] - m[j])*temp[i]
        end
    end
    N = 1/(size(x,2)-1)
    @turbo for i ∈ eachindex(C)
        C[i] *= N
    end
    C += C'
    C /= 2
end

"""
    running_meancov(x)

Compute the mean and covariance matrix a set of vectors, `x`.
* `x` must be a matrix of Floats, with each column of `x` corresponding to a vector.
"""
function running_meancov(x::AbstractMatrix{T}) where T<:AbstractFloat
    m = Vector{T}(undef,size(x,1))
    C = Matrix{T}(undef,size(x,1),size(x,1))
    running_meancov!(m,C,x)
    return m, C
end

#Update running mean and covariance with a nth data point where n is the running index
"""
    update_running_meancov!(m, C, x, n)

Update the mean, `m`, and covariance, `C`, of some set of `n-1` vectors to be the mean and variance of the set of `n` vectors where `x` is the ``n^th`` vector (for `n>=3`).
* `m` and `x` must be vectors of equal length.
* `C` must be a square matrix of side equal to the length of `x`.
* Typically used when the mean and covariance of a part of a dataset has been previously computed and needs to be updated with a new data point (vector).

!!! note
    `n` must be 3 or greater.
    [`running_meancov()`](@ref) or [`running_meanvcov!()`](@ref) can be used to obtain an initial estimate of `m` and `v` from at least two vectors.
"""
function update_running_meancov!(m::AbstractVector{T}, C::AbstractMatrix{T}, x::AbstractVector{T}, n::Int) where T<:AbstractFloat
    n >=3 || error("n must be greater than 2. Use running_meanvar() to obtain an initial estimate of mean and variance from at least two vectors before using update_running_meanvar() to update the estimates.")
    k = 1/n
    k1 = 1/(n-1)
    temp = Vector{T}(undef,length(m))
    @turbo for i ∈ eachindex(x)
        temp[i] = (x[i] - m[i])
        m[i] += temp[i]*k
    end
    @turbo for i ∈ eachindex(x), j ∈ eachindex(x)
        C[j,i] *= (n-2)
        C[j,i] += (x[j] - m[j])*temp[i]
        C[j,i] *= k1
    end
    C += C'
    C /= 2
end


"""
    newtonraphson(f::Function, x0::Number, fprime::Function, args::Tuple(); tol=1e-8, maxiter=50, eps0=1e-10)

Find solution of f(x) = 0 (x is scalar) using Newton-Raphson iterations.

This code is from https://mmas.github.io/newton-julia. There was no license

"""
function newtonraphson(f::Function, x0::Number, fprime::Function, args::Tuple=();
                tol::AbstractFloat=1e-8, maxiter::Integer=50, eps0::AbstractFloat=1e-10)
    for _ in 1:maxiter
        yprime = fprime(x0, args...)
        if abs(yprime) < eps0
            @warn "First derivative is zero"
            return x0
        end
        y = f(x0, args...)
        x1 = x0 - y/yprime
        if abs(x1-x0) < tol
            return x1
        end
        x0 = x1
    end
    error("Max iteration exceeded")
end

"""
    newtonraphson(f,x0,jac,args; tol=1e-8, maxiter=50, eps0=1e-10)

NR solution for multidimensional problem f(x)=0. Requires jacobian.

"""
function newtonraphson(f::Function, x0::AbstractVector, jac::Function, args::Tuple=(); tol::AbstractFloat=1e-8, maxiter::Integer=50, eps0::AbstractFloat=1e-10)
    xc = copy(x0)
    for _ in 1:maxiter
        J = jac(xc, args...)
        if cond(J) > 1/eps0
            @warn "Jacobian is ill-conditioned"
            return xc
        end
        y = f(xc, args...)
        x1 = xc - J\y
        if maximum(abs.(x1-xc)) < tol
            return x1
        end
        xc = x1
    end
    error("Max iteration exceeded")
end

"""
    newtonraphson(f,x0,args; tol=1e-8, maxiter=50, eps0=1e-10)

    Method without explicit jacobian, using automatic differentiation.
"""
function newtonraphson(f::Function, x0::AbstractVector, args::Tuple=(); tol::AbstractFloat=1e-8, maxiter::Integer=50, eps0::AbstractFloat=1e-10)
    xc = copy(x0)
    for _ in 1:maxiter
        J = jacobian(x->f(x,args...), xc)
        if cond(J) > 1/eps0
            @warn "Jacobian is ill-conditioned"
            return xc
        end
        y = f(xc, args...)
        x1 = xc - J\y
        if maximum(abs.(x1-xc)) < tol
            return x1
        end
        xc = x1
    end
    error("Max iteration exceeded")
end

"""
    quasinewton(dobs::AbstractVector, g::Function, mprior::AbstractVector, jac::Function, CMi=0.0I, CDi=I, args::Tuple=();
                step=1,tol=1e-8,maxiter=50,eps0=1e-10)

Solution of inverse problem dobs = g(m) using Quasi-Newton method. Direct implementation of Tarantola 2005, p. 69.

Return mean solution mpost and posterior covariance CMpost.
"""
function quasinewton(dobs::AbstractVector, g::Function,
                     mprior::AbstractVector, jac::Function,
                     CMi=0.0I, CDi=I, args::Tuple=();
                     step::AbstractFloat=1.0, tol::AbstractFloat=1e-8,
                     maxiter::Integer=50, eps0::AbstractFloat=1e-10)

    m = copy(mprior)

    for _ in 1:maxiter
        G = jac(m, args...)
        A = G'*CDi*G + CMi
        if cond(A)>1/eps0
            @warn "Operator is ill conditioned. Consider Smoothing."
            return m, inv(A)
        end
        d = g(m, args...)
        b = G'*CDi*(d - dobs) + CMi*(m-mprior)
        dm = -step*(A\b)
        m = m+dm
        if maximum(abs.(dm)) < tol
            CMpost = inv(A)
            return m, CMpost
        end
    end
    error("Max iteration exceeded")

end

"""
    quasinewton(dobs, g, mprior, CMi=0.0I, CDi=I, args=();
                step=1,tol=1e-8,maxiter=50,eps0=1e-10)

Method without direct specification of jacobian, computed using forwarddiff.
"""
function quasinewton(dobs::AbstractVector, g::Function,
                     mprior::AbstractVector,
                     CMi=0.0I, CDi=I, args::Tuple=();
                     step::AbstractFloat=1.0, tol::AbstractFloat=1e-8,
                     maxiter::Integer=50, eps0::AbstractFloat=1e-10)

    m = copy(mprior)

    for _ in 1:maxiter
        G = jacobian(x-> g(x,args...), m)
        A = G'*CDi*G + CMi
        if cond(A)>1/eps0
            @warn "Operator is ill conditioned. Consider Smoothing."
            return m, inv(A)
        end
        d = g(m, args...)
        b = G'*CDi*(d - dobs) + CMi*(m-mprior)
        dm = -step*(A\b)
        m = m+dm
        if maximum(abs.(dm)) < tol
            CMpost = inv(A)
            return m, CMpost
        end
    end
    error("Max iteration exceeded")

end

"""
    correlationmatrix(CM)

Compute correlation matrix from covariance matrix.
"""
function correlationmatrix(CM)
    c = copy(CM)
    for b in 1:size(CM,2), a in 1:size(CM,1)
        c[a,b] /= sqrt(CM[a,a]*CM[b,b])
    end
    return c
end


"""

    E1func(x)

Compute the exponential integral function ``E_{1}(x)`` for a real number `x`. This function
returns zero if `x` is larger than 700, or `Inf + pi*1im` if `x` is smaller than -700. (This
definition also defines the branch taken in the complex plane for ``x < 0``.) Note that even
though ``E_{1}(x)`` is real for ``x > 0``, the result of `E1func(x)` is cast as a complex
type, based on the type of `x`, for consistency.

This is a reimplementation of code at
`https://github.com/mitmath/18S096/blob/iap2017/pset3/pset3-solutions.ipynb` by Steven G.
Johnson, based on a combination of Taylor series and continued fraction expansions.

See also: [`E1taylor`](@ref), [`E1cf`](@ref)

"""
function E1func(x::AbstractFloat)

    if (x < -700*one(x))
        return Inf + pi*1im;
    elseif (x > 700*one(x))
        return zero(complex(x));
    else

        currfunc = E1taylor;
        if (x < -30*one(x) || x > 3*one(x))
            currfunc = E1cf;
        end
        mymaxorder = round(Int, 2 + 23/(1 + ((log10(abs(x)) - 0.4)/1.0)^2)); # useful empirical rule of thumb for largest order needed for positive x
        if (x < zero(x))
            mymaxorder = round(Int, 1 + 70/(1 + (log10(abs(x)) - 1.5)^2)); # useful empirical rule of thumb for largest order needed for negative x
        end
        myE1 = currfunc(x, mymaxorder);
        if (x < zero(x))
            return (complex(myE1) + pi*1im);
        end
        return complex(myE1);

    end

end

E1func(x::Real) = E1func(float(x));

"""

    E1taylor(x, maxorder)

Compute the exponential integral function ``E_{1}(x)`` for a real number `x`, using the
Taylor series expansion up to a maximum order `maxorder`. This function works best for
``-30 \\leq x \\leq 3``.

This is a reimplementation of code at
`https://github.com/mitmath/18S096/blob/iap2017/pset3/pset3-solutions.ipynb` by Steven G.
Johnson.

See also: [`E1func`](@ref), [`E1cf`](@ref)

"""
function E1taylor(x::AbstractFloat, maxorder::Integer)

    E1 = -1*MathConstants.eulergamma - log(abs(x));
    currterm = -1*x;
    E1 = E1 - currterm;

    for nn=2:maxorder

        currterm = -1*x*(nn-1)/(nn^2) * currterm;
        E1 = E1 - currterm;

    end

    return E1;

end

E1taylor(x::Real, maxorder::Integer) = E1taylor(float(x), maxorder);

"""

    E1cf(x, maxorder)

Compute the exponential integral function ``E_{1}(x)`` for a real number `x`, using the
continued fraction expansion up to a maximum order `maxorder`. This function works best for
``x < -30`` or ``x > 3``.

This is a reimplementation of code at
`https://github.com/mitmath/18S096/blob/iap2017/pset3/pset3-solutions.ipynb` by Steven G.
Johnson.

See also: [`E1func`](@ref), [`E1taylor`](@ref)

"""
function E1cf(x::AbstractFloat, maxorder::Integer)

    currcf = x;
    for nn=maxorder:-1:1
        currcf = x + (1+nn)/currcf;
        currcf = 1 + nn/currcf;
    end
    return (exp(-1*x)/(x + 1/currcf));

end

E1cf(x::Real, maxorder::Integer) = E1cf(float(x), maxorder);


using NumericalMethodsforEngineers

A = Float64[16 4 8;4 5 -4;8 -4 22]
b = Float64[4, 2, 5]
n = size(A, 1)
d = zeros(n)

ldlt!(A, d)
lower = zeros(3, 3)
for i in 1:n
  for j in 1:i
    lower[i, j] = A[i, j] / d[j]
  end
end

println("\nLower Triangular Factors: \n $lower")
println("\nDiagonal Terms: \n $d")

ldlfor!(A, b)
for i in 1:n
  A[i, :] = A[i, :] / d[i]
end

subbac!(A, b)
println("\nSolution Vector: \n $b")


include("linearsystem.jl")
using BenchmarkTools

function tbt(δ)
    u22 = 1.0 - 1.0/δ
    b2 = 2.0 - (1.0 + δ)/δ
    x2 = b2/u22
    e2 = x2-1

    b1 = 1 + δ
    x1 = (b1-x2)/δ
    e1 = x1 - 1
    return e1, e2
end

function tbtp(δ)
    u22 = 1.0 - δ
    b2 =  (1.0 + δ) - 2.0*δ
    x2 = b2/u22
    e2 = x2-1

    b1 = 2
    x1 = (b1-x2)
    e1 = x1 - 1
    return e1, e2
end

δ = eps(1.0)/4

e1,e2 = tbt(δ)
println((e1,e2))
e1,e2 = tbtp(δ)
println((e1,e2))

# Test LU factorization
#A = [1.0 2 3; 4 5 6; 7 8 9]
#A = rand(1000,1000)
#B = deepcopy(A)
#@time L, U, p = lu!(A)
#L*U - B[p, :]

# Test Cholesky factorization
A = rand(20,20)
C = A'*A
D = deepcopy(C)
@time L = chol(C)
#@benchmark L = chol3!(D)

#@benchmark L2 = cholesky(C)

# test gaussian elimination with partial pivot
n = 10
A = rand(n,n)
x = rand(n)
b = A*x
@time xt = gepp!(A,b)
err = norm(xt-x)
println(err)

A = [2.0 -1.0 0; -1.0 2.0 -1.0; 0 -1.0 2.0]
x = rand(3)
b = A*x
x0 = rand(3)
@time x1, it = gaussseidel(A, b, x0)
err = norm(x1-x)
println(err)
println(x1)
println(it)


### A Pluto.jl notebook ###
# v0.12.18

using Markdown
using InteractiveUtils

# ╔═╡ 330763c4-f1e2-11ea-071a-b13a3bea37e9
md"# Arrays: Slices and views"

# ╔═╡ 50e2aaa2-f1e2-11ea-37b2-9d23357ab558
md"Since arrays are a very common way of storing data (but by no means the only way!), it is important to know how to manipulate them."

# ╔═╡ 614126a8-f1e2-11ea-08ef-3d787d155416
md"Suppose we start with a vector (1-dimensional array) of all ones:"

# ╔═╡ 6d6e5194-f1e2-11ea-116c-c191e52dd2af
v = ones(Int, 10)

# ╔═╡ 79332324-f1e2-11ea-3c82-39cad3d2391c
md"""We can **index** into the array to extract a certain element using square brackets ("`[`" and "`]`"):"""

# ╔═╡ 8b0fca3e-f1e2-11ea-2b70-011aaeeb94cc
v[3]

# ╔═╡ 8cbf61b6-f1e2-11ea-3b49-cb59792e1fc1
md"And we can modify that element using the same syntax:"

# ╔═╡ a87a0238-f1e2-11ea-272d-0d8cd4795df6
v[3] = 2

# ╔═╡ abbf2a72-f1e2-11ea-3e76-2bbe6e068c59
v

# ╔═╡ b0bafcae-f1e2-11ea-1c5a-a98c25cf6bd4
md"If we want to extract a subset of the array, we can do so by indexing using a **range**:"

# ╔═╡ c8ec70ac-f1e2-11ea-30d6-7fc4e1f3cf63
v[3:5]

# ╔═╡ d6903d5e-f1e2-11ea-1525-35f7f6aa2f66
md"We call this a **slice** of the array."

# ╔═╡ 7c447b7a-f1e3-11ea-0ef2-c996690ae871
md"## Modifying a slice: Broadcasting"

# ╔═╡ cd2c8f1a-f1e2-11ea-1449-73d40f742c60
md"How can we *modify* this slice, say to replace them all by the value 4? Intuitively you might expect to be able to write"

# ╔═╡ edec3322-f1e2-11ea-32b8-ebd1445b18e2
v[3:5] = 4

# ╔═╡ 0ed6e116-f1e3-11ea-38ed-693262492cf2
md"""
But Julia does *not* allow this. The problem is that the two objects on the two sides of the assignment are different types: an array on the left, and a number on the right. What we want to do is modify the array *element by element*; for this we need to **broadcast** the assignment using "`.`":
"""

# ╔═╡ 446a93ba-f1e3-11ea-2957-e9c3bb7b66cd
v[3:5] .= 4

# ╔═╡ 4974c36c-f1e3-11ea-3543-63fce86af871
md"Let's check if this modified the original array:"

# ╔═╡ 86be1e4e-f1e3-11ea-1212-6949dd55b8e8
v

# ╔═╡ 887640e0-f1e3-11ea-2d35-a9a9d1ae1897
md"Success!"

# ╔═╡ 8d6fdcfa-f1e3-11ea-2f50-9783f4cd6339
md"Following the above idea, if we provide a new array of the correct length, we can also modify the array:"

# ╔═╡ 9b504332-f1e3-11ea-35a2-8d33db468a83
v[3:5] = [5, 6, 7]

# ╔═╡ a1685124-f1e3-11ea-396c-cd12b8cdfdbd
v

# ╔═╡ b6e74bf4-f1e3-11ea-1eab-7db865915fe8
md"## Array slices"

# ╔═╡ c1562b4e-f1e3-11ea-2b73-0d18fc361323
md"Now suppose we give a name to that array slice:"

# ╔═╡ d69da0d8-f1e3-11ea-092b-7b7e9fd262ce
w = v[3:5]

# ╔═╡ afbeb970-f1e3-11ea-3658-c973f6808f6d
md"An important question is the following:

> *What happens if we modify `w`*?

Take a moment to think about this before carrying on. What do you expect to happen? What are the possibilities?
"

# ╔═╡ fc774f0c-f1e3-11ea-0e5a-3141ec04348d
md"(Seriously, think about it!)"

# ╔═╡ ffa05138-f1e3-11ea-280e-bf7da83de269
md"Of course, after thinking about it we should ask Julia what actually happens!"

# ╔═╡ 12dbdf38-f1e4-11ea-0449-b39ed2f5f12d
md"Here's the current situation:"

# ╔═╡ 1123aade-f1e4-11ea-3cce-4534d7a72a60
w

# ╔═╡ 19ca7c32-f1e4-11ea-39a5-3dcf97e6fbcf
md"Now let's change the first element of `w`:"

# ╔═╡ 21a48678-f1e4-11ea-1143-c709ee295e0d
w[1] = 8

# ╔═╡ 25abc85a-f1e4-11ea-156b-7f15f0474e3b
w

# ╔═╡ 2765efc0-f1e4-11ea-1324-ede5df51f047
md"As expected, we modified `w`'s first element. But what happened to `v`?"

# ╔═╡ 3eb46116-f1e4-11ea-29e9-c74e8478d803
v

# ╔═╡ 4084ffca-f1e4-11ea-2d5d-abb022cd41cc
md"We see that *`v` did not get modified!* This is surprising to many people.

What is going on?"

# ╔═╡ 59e92208-f1e4-11ea-30a0-4d6d44bb9ae6
md"What is happening is that the slice syntax `v[3:5]` actually creates a *copy* of the data in a new place in memory. Then `w` is a label, or pointer, for that new piece of memory. So modifying it does not touch the original values at all!

We should be aware of this whenever we are tempted to modify slices."

# ╔═╡ aa48f298-f1e4-11ea-19ef-f120e959faa4
md"## Array views"

# ╔═╡ addcc056-f1e4-11ea-0090-eb82f5545c37
md"Of course, there are times when we do *not* want to make a copy. We might want to give a name to a slice of an array and manipulate the *original* array using the new name.

To do so we need to make a **view** of the array. This is literally like a window that allows us to view (read and write) to the *same data*.

To do this we use the `view` function:
"

# ╔═╡ f5cc5e80-f1e4-11ea-027c-9bef1852423d
z = view(v, 3:5)

# ╔═╡ 218aa662-f1e5-11ea-1516-15ffd9c9ba08
md"Now what happens if we modify `z` element by element using broadcasting?"

# ╔═╡ 2b955ddc-f1e5-11ea-1ca1-e91c81100478
z .= 9

# ╔═╡ 3a957e84-f1e5-11ea-2131-21fdd5d8c5d5
z

# ╔═╡ 3c96558c-f1e5-11ea-2109-79717714c977
md"`z` changed as we expected. But what about the original vector `v`?"

# ╔═╡ 4ed5d79a-f1e5-11ea-1515-3be1b3fa9a2b
v

# ╔═╡ 50259ee6-f1e5-11ea-317b-11a542b6cd9e
md"`v` also changed! This confirms that `z` is a window onto the same underlying data stored inside the block of memory labelled with the name `v`."

# ╔═╡ ff65101a-f1e4-11ea-1b51-f5b09ae1f0eb
md"Let's compare the types of `w` and `z`:"

# ╔═╡ fbd656aa-f1e4-11ea-2de2-61abc56ea71d
typeof(w), typeof(z)

# ╔═╡ 0ab7d32e-f1e5-11ea-048b-a74d7515b0c5
md"We see that they are of similar, but different types. `w` looks like other arrays we have seen, but `z` is a new type, called a `SubArray`."

# ╔═╡ 781e93da-f1e5-11ea-3d3f-57842f69c032
md"## Nicer syntax for views"

# ╔═╡ 83c8711a-f1e5-11ea-30c4-87104a7c1c2a
md"Now we know how to produce views / `SubArray`s. But to do so we had to move away from the nice, compact array slicing syntax, from `v[3:5]` to `view(v, 3:5)`. Could there be a way to combine the two?"

# ╔═╡ fb50b922-f1e5-11ea-0049-773084392b80
md"In fact there is: Julia provides a **macro** `@view` that converts the slicing syntax into a view:"

# ╔═╡ 0d25ffae-f1e6-11ea-05c4-49b25532ae49
z2 = @view v[3:5]

# ╔═╡ 27c47f2a-f1e6-11ea-3aa2-bb05fcd0e566
typeof(z2)

# ╔═╡ 2998132a-f1e6-11ea-3ccc-7951c392e9f3
z2 .= 10

# ╔═╡ 2c010994-f1e6-11ea-0022-79d21e0764b8
v

# ╔═╡ 2d6f84e2-f1e6-11ea-2683-8be3ea0d39ac
md"What is going on here? The macro `@view` literally takes the piece of Julia code `v[3:5]` and *replaces* it with the new piece of code `view(v, 3:5)`. Please see the separate video on macros for more detail!"

# ╔═╡ 4ff57544-f1e6-11ea-0274-15cc7917c3e3
md"Finally, there is also a macro `@views`, which replaces *all* slices by views in an entire expression."

# ╔═╡ ba6e586c-f1e8-11ea-1aba-ab1ba56ace6d
md"## Matrices: slices and views"

# ╔═╡ f0753bec-f1e8-11ea-0776-99b556737a05
md"Slices and views work the same way for matrices. For example, let's start with a matrix:"

# ╔═╡ cdf780b6-f1e8-11ea-2c90-bdae4d1dacaf
M = [10i + j for i in 0:5, j in 1:4]

# ╔═╡ 3464d66e-f1e9-11ea-0e53-e99f5c962594
md"We can make slices and views of matrices using extensions of the syntax we saw above for vectors:"

# ╔═╡ 4f3451e0-f1e9-11ea-07b4-456c5a6fa4c5
M[3:5, 1:2]

# ╔═╡ 5cb68536-f1e9-11ea-272e-179f8dfe8acc
view(M, 3:5, 1:2)

# ╔═╡ 615a74ee-f1e9-11ea-1f71-3be78c5545b2
@view M[3:5, 1:2]

# ╔═╡ 670bbc0e-f1e9-11ea-01a1-93cb78e8cb90
md"You should check that slices and views have the same behaviour that we described above."

# ╔═╡ 94c85c38-f1e9-11ea-3ab8-976569b36e23
md"## Reshaping matrices"

# ╔═╡ eb96a16a-f1e8-11ea-0398-1715aac5fb2d
md"There's something else we would like to be able to do with matrices: **reshape** them. We can do so using the `reshape` function:"

# ╔═╡ 24cefdea-f1e9-11ea-3c37-c3626e555e4e
M2 = reshape(M, 3, 8)

# ╔═╡ c0dca20e-5d6f-11eb-16dc-efa62e027511
typeof(M2)

# ╔═╡ c9e36c90-5d6f-11eb-0b8e-6538834cc678
M2[1, 2] = 42

# ╔═╡ dec30d50-5d6f-11eb-2b8a-6d3bb29422e4
M

# ╔═╡ e40f19c0-5d6f-11eb-3989-490c5629d307
M2[2, 1] = 42

# ╔═╡ 0a056c60-5d70-11eb-11fc-85569c278285
M

# ╔═╡ cae01608-f1e9-11ea-0878-a7b281aa6113
md"(Note the order in which the elements were taken.)"

# ╔═╡ 2b3cbb88-f1e9-11ea-16f1-436de44841de
md"Given the discussion so far, you are hopefully asking yourself: Is this a copy or a view? We'll leave it to you to find out!"

# ╔═╡ c36d3770-f1e9-11ea-2fa5-5ddb60c2adce
md"Similarly, we might want to turn the matrix into a vector. For this we can use the `vec` function:"

# ╔═╡ ecb7395a-f1e9-11ea-2346-abbd7f1bd73e
vv = vec(M)

# ╔═╡ 663c99e0-5d70-11eb-3e33-0fa3b4241e9e
vv[7] = 42

# ╔═╡ 785615be-5d70-11eb-1089-236000da5068
M

# ╔═╡ f0eb9eaa-f1e9-11ea-1bd5-3f47ff24cbf6
md"Again you should check if this is a copy or a view. The order of the elements here is showing us the **storage order** of a matrix, i.e. in which order in the (linear) memory inside the computer the elements of the 2D matrix are being stored."

# ╔═╡ 648b17b6-f1e6-11ea-3351-23b46a2e265d
md"## Summary"

# ╔═╡ 689e4c60-f1e6-11ea-3782-43decd275b5f
md"In summary, we need to be aware of when we want to copy data from an array, and when we actually want to avoid copying and make views instead. A slice like `v[1:3]` makes a copy *unless* there's an `@view` or `@views` macro telling you otherwise.

For other operations like `reshape` and `vec` it's also critical to know if they make copies or views.
"

# ╔═╡ Cell order:
# ╟─330763c4-f1e2-11ea-071a-b13a3bea37e9
# ╟─50e2aaa2-f1e2-11ea-37b2-9d23357ab558
# ╟─614126a8-f1e2-11ea-08ef-3d787d155416
# ╠═6d6e5194-f1e2-11ea-116c-c191e52dd2af
# ╟─79332324-f1e2-11ea-3c82-39cad3d2391c
# ╠═8b0fca3e-f1e2-11ea-2b70-011aaeeb94cc
# ╟─8cbf61b6-f1e2-11ea-3b49-cb59792e1fc1
# ╠═a87a0238-f1e2-11ea-272d-0d8cd4795df6
# ╠═abbf2a72-f1e2-11ea-3e76-2bbe6e068c59
# ╟─b0bafcae-f1e2-11ea-1c5a-a98c25cf6bd4
# ╠═c8ec70ac-f1e2-11ea-30d6-7fc4e1f3cf63
# ╟─d6903d5e-f1e2-11ea-1525-35f7f6aa2f66
# ╟─7c447b7a-f1e3-11ea-0ef2-c996690ae871
# ╟─cd2c8f1a-f1e2-11ea-1449-73d40f742c60
# ╠═edec3322-f1e2-11ea-32b8-ebd1445b18e2
# ╟─0ed6e116-f1e3-11ea-38ed-693262492cf2
# ╠═446a93ba-f1e3-11ea-2957-e9c3bb7b66cd
# ╟─4974c36c-f1e3-11ea-3543-63fce86af871
# ╠═86be1e4e-f1e3-11ea-1212-6949dd55b8e8
# ╟─887640e0-f1e3-11ea-2d35-a9a9d1ae1897
# ╟─8d6fdcfa-f1e3-11ea-2f50-9783f4cd6339
# ╠═9b504332-f1e3-11ea-35a2-8d33db468a83
# ╠═a1685124-f1e3-11ea-396c-cd12b8cdfdbd
# ╟─b6e74bf4-f1e3-11ea-1eab-7db865915fe8
# ╟─c1562b4e-f1e3-11ea-2b73-0d18fc361323
# ╠═d69da0d8-f1e3-11ea-092b-7b7e9fd262ce
# ╟─afbeb970-f1e3-11ea-3658-c973f6808f6d
# ╟─fc774f0c-f1e3-11ea-0e5a-3141ec04348d
# ╟─ffa05138-f1e3-11ea-280e-bf7da83de269
# ╟─12dbdf38-f1e4-11ea-0449-b39ed2f5f12d
# ╠═1123aade-f1e4-11ea-3cce-4534d7a72a60
# ╟─19ca7c32-f1e4-11ea-39a5-3dcf97e6fbcf
# ╠═21a48678-f1e4-11ea-1143-c709ee295e0d
# ╠═25abc85a-f1e4-11ea-156b-7f15f0474e3b
# ╟─2765efc0-f1e4-11ea-1324-ede5df51f047
# ╠═3eb46116-f1e4-11ea-29e9-c74e8478d803
# ╟─4084ffca-f1e4-11ea-2d5d-abb022cd41cc
# ╟─59e92208-f1e4-11ea-30a0-4d6d44bb9ae6
# ╟─aa48f298-f1e4-11ea-19ef-f120e959faa4
# ╟─addcc056-f1e4-11ea-0090-eb82f5545c37
# ╠═f5cc5e80-f1e4-11ea-027c-9bef1852423d
# ╟─218aa662-f1e5-11ea-1516-15ffd9c9ba08
# ╠═2b955ddc-f1e5-11ea-1ca1-e91c81100478
# ╠═3a957e84-f1e5-11ea-2131-21fdd5d8c5d5
# ╟─3c96558c-f1e5-11ea-2109-79717714c977
# ╠═4ed5d79a-f1e5-11ea-1515-3be1b3fa9a2b
# ╟─50259ee6-f1e5-11ea-317b-11a542b6cd9e
# ╟─ff65101a-f1e4-11ea-1b51-f5b09ae1f0eb
# ╠═fbd656aa-f1e4-11ea-2de2-61abc56ea71d
# ╟─0ab7d32e-f1e5-11ea-048b-a74d7515b0c5
# ╟─781e93da-f1e5-11ea-3d3f-57842f69c032
# ╟─83c8711a-f1e5-11ea-30c4-87104a7c1c2a
# ╟─fb50b922-f1e5-11ea-0049-773084392b80
# ╠═0d25ffae-f1e6-11ea-05c4-49b25532ae49
# ╠═27c47f2a-f1e6-11ea-3aa2-bb05fcd0e566
# ╠═2998132a-f1e6-11ea-3ccc-7951c392e9f3
# ╠═2c010994-f1e6-11ea-0022-79d21e0764b8
# ╟─2d6f84e2-f1e6-11ea-2683-8be3ea0d39ac
# ╟─4ff57544-f1e6-11ea-0274-15cc7917c3e3
# ╟─ba6e586c-f1e8-11ea-1aba-ab1ba56ace6d
# ╟─f0753bec-f1e8-11ea-0776-99b556737a05
# ╠═cdf780b6-f1e8-11ea-2c90-bdae4d1dacaf
# ╟─3464d66e-f1e9-11ea-0e53-e99f5c962594
# ╠═4f3451e0-f1e9-11ea-07b4-456c5a6fa4c5
# ╠═5cb68536-f1e9-11ea-272e-179f8dfe8acc
# ╠═615a74ee-f1e9-11ea-1f71-3be78c5545b2
# ╟─670bbc0e-f1e9-11ea-01a1-93cb78e8cb90
# ╟─94c85c38-f1e9-11ea-3ab8-976569b36e23
# ╟─eb96a16a-f1e8-11ea-0398-1715aac5fb2d
# ╠═24cefdea-f1e9-11ea-3c37-c3626e555e4e
# ╠═c0dca20e-5d6f-11eb-16dc-efa62e027511
# ╠═c9e36c90-5d6f-11eb-0b8e-6538834cc678
# ╠═dec30d50-5d6f-11eb-2b8a-6d3bb29422e4
# ╠═e40f19c0-5d6f-11eb-3989-490c5629d307
# ╠═0a056c60-5d70-11eb-11fc-85569c278285
# ╟─cae01608-f1e9-11ea-0878-a7b281aa6113
# ╟─2b3cbb88-f1e9-11ea-16f1-436de44841de
# ╟─c36d3770-f1e9-11ea-2fa5-5ddb60c2adce
# ╠═ecb7395a-f1e9-11ea-2346-abbd7f1bd73e
# ╠═663c99e0-5d70-11eb-3e33-0fa3b4241e9e
# ╠═785615be-5d70-11eb-1089-236000da5068
# ╟─f0eb9eaa-f1e9-11ea-1bd5-3f47ff24cbf6
# ╟─648b17b6-f1e6-11ea-3351-23b46a2e265d
# ╟─689e4c60-f1e6-11ea-3782-43decd275b5f


# Multinomial theorem
# https://en.wikipedia.org/wiki/Multinomial_theorem

export multiexponents

struct MultiExponents{T}
    c::T
    nterms::Int
end

# Standard stars and bars:
# https://en.wikipedia.org/wiki/Stars_and_bars_(combinatorics)
function Base.iterate(m::MultiExponents, s = nothing)
    next = s === nothing ? iterate(m.c) : iterate(m.c, s)
    next === nothing && return
    stars, ss = next

    # stars minus their consecutive
    # position becomes their index
    result = zeros(Int, m.nterms)
    for (i, s) in enumerate(stars)
        result[s-i+1] += 1
    end

    result, ss
end

Base.length(m::MultiExponents) = length(m.c)

"""
    multiexponents(m, n)

Returns the exponents in the multinomial expansion (x₁ + x₂ + ... + xₘ)ⁿ.

For example, the expansion (x₁ + x₂ + x₃)² = x₁² + x₁x₂ + x₁x₃ + ...
has the exponents:

    julia> collect(multiexponents(3, 2))

    6-element Array{Any,1}:
     [2, 0, 0]
     [1, 1, 0]
     [1, 0, 1]
     [0, 2, 0]
     [0, 1, 1]
     [0, 0, 2]
"""
function multiexponents(m, n)
    # number of stars and bars = m+n-1
    c = combinations(1:m+n-1, n)

    MultiExponents(c, m)
end


using LightGraphs, SimpleWeightedGraphs, LinearAlgebra
"""
    SunFlowerGraph(; N = 400)

SUNFLOWERGRAPH construct a simple weighted sunflower graph with N vertices. Edge weights are the reciprocal of Euclidean distances.

# Input Arguments
- `N::Int64`: default is 400, the number of vertices. Requires N > 26.

# Output Argument
- `G::SimpleWeightedGraph{Int64,Float64}`: a simple weighted graph of the sunflower.
- `L::Matrix{Float64}`: the weighted unnormalized graph Laplacian matrix.
- `X::Matrix{Float64}`: a matrix whose i-th row represent the 2D coordinates of the i-th node.

"""
function SunFlowerGraph(; N = 400)
    c=1.0/N; θ=(sqrt(5.0)-1)*π;
    X = zeros(N,2); for k=1:N X[k,:]=c*(k-1)*[cos((k-1)*θ) sin((k-1)*θ)]; end

    G = SimpleWeightedGraph(N)
    for k = 2:8
        G.weights[1,k] = 1/norm(X[1,:] - X[k,:])
        G.weights[k,1] = 1/norm(X[1,:] - X[k,:])
    end

    for k = 1:N
        if k+8 <= N
            G.weights[k,k+8] = 1/norm(X[k,:] - X[k+8,:])
            G.weights[k+8,k] = 1/norm(X[k,:] - X[k+8,:])
        end
        if k+13 <= N
            G.weights[k,k+13] = 1/norm(X[k,:] - X[k+13,:])
            G.weights[k+13,k] = 1/norm(X[k,:] - X[k+13,:])
        end
    end
    W = weights(G) #weighted adjacency_matrix
    Lw = Diagonal(sum(W, dims = 2)[:]) - W #weighted laplacian_matrix
    return G, Lw, X
end


using NumericalMethodsforEngineers

a = [16. 4. 8.; 4. 5. -4.; 8. -4. 22.]
b = [4., 2., 5.]

f = cholesky(a)
f.U |> display

y = f.L \ b
c = f.U \ y

maxiters = 5
x = [(i=i, cg=cg(a, b; maxiter=i)) for i in 1:maxiters]
x |> display

@test round.(x[3].cg; digits=9) == round.(c; digits=9)


#  C:\dev\GitHub\autodiff\logsumexp.jl
#  AUTOGENERATED FROM C:\dev\GitHub\autodiff\logsumexp.ipynb on 2015-08-03T13:16:36
using Base.Test

# logsumexp()
function logsumexp(x::Array{Float64,1})
  A = maximum(x);
  ema = exp(x-A);
  sema = sum(ema);
  log(sema) + A;
end

r = rand(5);
@test_approx_eq_eps logsumexp(r) log(sum(exp(r))) 1.0e-8
@printf("logsumexp: %f = %f\n", logsumexp(r), log(sum(exp(r))));

# logsumexp()
function logsumexp_both(x::Array{Float64,1})
  A = maximum(x);
  ema = exp(x-A);
  sema = sum(ema);
  l = log(sema) + A;
  Jacobian = ema/sema;
  return (l, Jacobian);
end

#logsumexp(x::Array{Float64,1}) = logsumexp_both(x)[1]


function tau(n::Integer)::Integer
    ans = 0
    for i = 1:n
        if n % i == 0
            ans = 1 + ans
        end
    end
    return ans
end

τf = tau

function sigma(n::Integer)::Integer
    ans = 0
    for i = 1:n
        if n % i == 0
            ans = i + ans
        end
    end
    return ans
end

σf = sigma

function s(n::Integer)::Integer
    return sigma(n) - n
end

# Adding cache to this function can make thing easier
function getMappingFromSFunction(n::Integer)::Array{Bool}
    sn = s(n)
    mappingSofNProperty = [sn < n, sn == n, sn > n]
    return mappingSofNProperty
end

function isDeficient(n::Integer)::Bool
    return getMappingFromSFunction(n)[1]
end

function isPerfect(n::Integer)::Bool
    return getMappingFromSFunction(n)[2]
end

function isAbundant(n::Integer)::Bool
    return getMappingFromSFunction(n)[3]
end

# Including N
function pic(n::Integer)::Integer
    numberOfPrimesTillN = 0
    for i = 2:n
        isPrime = true
        for j = 1:i
            if (i % j == 0 && i != j) || j == 1
                isPrime = false
                break
            end
        end
        if isPrime
            numberOfPrimesTillN += 1
        end
    end
    return numberOfPrimesTillN
end

# Not Including N
function pi(n::Integer)::Integer
    numberOfPrimesTillN = 0
    for i = 2:n-1
        isPrime = true
        for j = 2:i
            if (i % j == 0 && i != j)
                isPrime = false
                break
            end
        end
        if isPrime && (i != n - 1)
            numberOfPrimesTillN += 1
        end
    end
    return numberOfPrimesTillN
end

πf = pi
πfc = pic

function 𝞋(n::Integer)::Float64
    value = 0.0
    for i = 2:n
        isPrime = true
        for j = 2:i
            if (i % j == 0 && i != j)
                isPrime = false
                break
            end
        end
        if isPrime && (i != n - 1)
            value += log(ℯ, p)
        end
    end
    return value
end

chebyshev_theta = 𝞋

function ψ(n::Integer)::Float64
    value = 0.00
    for p = 2:n
        isPrime = true
        for j = 2:p
            if (p % j == 0 && p != j)
                isPrime = false
                break
            end
        end
        if isPrime
            pk = p^1
            sum = 0.0
            while pk <= n
                sum += log(ℯ, p)
                pk *= p
                println("ln $p")
            end
            value += sum
        end
    end
    return value
end

chebyshev_psi = ψ


"""
    lagrange_polynomials(nodes::Array{Float64, 1}, evalPt::Float64)

Evaluate the lagrange interpolating polynomials defied by `nodes` at the point `evalPt`.
"""
function lagrange_polynomials(nodes::Array{Float64,1}, evalPt::Float64)
    order = length(nodes)
    poly_coefs = zeros(order)
    if order > 1
        for n in 1:order
            notn = [1:n-1;n+1:order]
            poly_coefs[n] = prod(evalPt.-nodes[notn])./prod(nodes[n].-nodes[notn])
        end
    else
        poly_coefs = [1.0]
    end
    return poly_coefs
end

"""
    gauss_lobatto_points(a::Float64, b::Float64, n_evals::Int)

Return array containing `n_evals` gauss lobatto points in the interval `[a,b]`.
"""
function gauss_lobatto_points(a::Float64,b::Float64,n_evals::Int)
    (b<a)&&throw(DomainError("must have a<b"))
    (n_evals<1)&&throw(DomainError("n_evals must be > 0"))
    if n_evals > 1
        # the GL nodes
        # nodes in [-1,1]
        nodes = Jacobi.zglj(n_evals, 0, 0)
        # shift nodes to [a,b]
        nodes *= 0.5*(b-a)
        nodes .+= 0.5*(a+b)
    else
        nodes = [0.5*(a+b)]
    end
    return nodes
end

"""
    gauss_lobatto_weights(a::Float64, b::Float64, n_evals::Int)

Return array containing `n_evals` weights of the polynomials associated with the
gauss lobatto points in the interval `[a,b]`. i.e. the itegrals over the interval `[a,b]`
of the lagrange polynomials defined by the nodes given by `nodes = gauss_lobatto_points(a,b,n_evals)`
"""
function gauss_lobatto_weights(a::Float64,b::Float64,n_evals::Int)
    (b<a)&&throw(DomainError("must have a<b"))
    (n_evals<1)&&throw(DomainError("n_evals must be > 0"))
    if n_evals > 1
        nodes = Jacobi.zglj(n_evals, 0, 0)
        weights = Jacobi.wglj(nodes,0,0)*(b-a)/2.0
    else
        weights = [(b-a)]
    end
    return weights
end

"""
    lagrange_interpolation(fun::Function, a::Float64, b::Float64, n_evals::Int)

Return a polynomial approximation to `fun` of order `n_evals-1` on the interval `[a,b]` using
the `gauss_lobatto_points` as the nodes of the lagrange polynomials.
"""
function lagrange_interpolation(fun::Function,a::Float64,b::Float64,n_evals::Int)
    nodes = gauss_lobatto_points(a,b,n_evals)

    fun_vals = fun.(nodes)
    interpolant(x) = LinearAlgebra.dot(fun_vals,lagrange_polynomials(nodes,x))
    return interpolant
end

"""
    gauss_lobatto_quadrature(fun::Function, a::Float64, b::Float64, n_evals::Int)

Compute a quadrature approximation of `fun` on the interval `[a,b]` with `n_evals` function
evaluations using the `gauss_lobatto_points`
"""
function gauss_lobatto_quadrature(fun::Function,a::Float64,b::Float64,n_evals::Int)
    nodes = gauss_lobatto_points(a,b,n_evals)
    weights = gauss_lobatto_weights(a,b,n_evals)
    fun_vals = fun.(nodes)
    quad = LinearAlgebra.dot(fun_vals,weights)

    return quad
end
"""
    gauss_lobatto_quadrature(fun::Function, nodes::Array{Float64,1})

Compute a quadrature approximation of `fun` on the interval `nodes[1]` to `nodes[end]`
evaluating the function `fun` at `nodes`
"""
function gauss_lobatto_quadrature(fun::Function,nodes::Array{Float64,1})
    weights = gauss_lobatto_weights(a,b,length(nodes))
    fun_vals = fun.(nodes)
    quad = LinearAlgebra.dot(fun_vals,weights)

    return quad
end

"""
Construct a generalised vandermonde matrix.

    vandermonde( nBases::Int)

Note: requires Jacobi package Pkg.add("Jacobi")

# Arguments
- `nBases::Int`: the degree of the basis

# Output
- a tuple with keys
    - `:V::Array{Float64,2}`: where `:V[:,i]` contains the values of the `i`th
        legendre polynomial evaluate at the GLL nodes.
    - `:inv`: the inverse of :V
    - `:D::Array{Float64,2}`: where `V.D[:,i]` contains the values of the derivative
        of the `i`th legendre polynomial evaluate at the GLL nodes.
"""
function vandermonde(nBases::Int)
    if nBases > 1
        z = Jacobi.zglj(nBases, 0, 0) # the LGL nodes
    elseif nBases == 1
        z = 0.0
    end
    V = zeros(Float64, nBases, nBases)
    DV = zeros(Float64, nBases, nBases)
    if nBases > 1
        for j = 1:nBases
            # compute the polynomials at gauss-labotto quadrature points
            V[:, j] = Jacobi.legendre.(z, j - 1) .* sqrt((2 * (j - 1) + 1) / 2)
            DV[:, j] = Jacobi.dlegendre.(z, j - 1) .* sqrt((2 * (j - 1) + 1) / 2)
        end
        # Compute the Gauss-Lobatto weights for numerical quadrature
        w =
            2.0 ./ (
                nBases *
                (nBases - 1) *
                Jacobi.legendre.(Jacobi.zglj(nBases, 0, 0), nBases - 1) .^ 2
            )
    elseif nBases == 1
        V .= [1.0/sqrt(2)]
        DV .= [0.0]
        w = [2.0]
    end
    return (V = V, inv = inv(V), D = DV, w = w)
end

function legendre_to_lagrange(coeffs::Vector{Float64})
    order = length(coeffs)
    V = vandermonde(order)
    return V.V*coeffs
end

function lagrange_to_legendre(coeffs::Vector{Float64})
    order = length(coeffs)
    V = vandermonde(order)
    return V.inv*coeffs
end

function legendre_to_lagrange(coeffs::Array{Float64,3})
    order = size(coeffs,1)
    V = vandermonde(order)
    for  i in 1:size(coeffs,3)
        coeffs[:,:,i] = V.V*coeffs[:,:,i]
    end
    return coeffs
end

function lagrange_to_legendre(coeffs::Array{Float64,3})
    order = size(coeffs,1)
    V = vandermonde(order)
    for  i in 1:size(coeffs,3)
        coeffs[:,:,i] = V.inv*coeffs[:,:,i]
    end
    return coeffs
end


doc"""
apply Hodrick-Prescott filter to `AbstractVector`.

##### Arguments
- `y::AbstractVector` : data to be detrended
- `λ::Real` : penalty on variation in trend

##### Returns
- `y_cyclical::Vector`: cyclical component
- `y_trend::Vector`: trend component
"""
function hp_filter(y::AbstractVector{T}, λ::Real) where T <: Real
    y = Vector(y)
    N = length(y)
    H = spdiagm(-2 => fill(λ, N-2),
                -1 => vcat(-2λ, fill(-4λ, N - 3), -2λ),
                 0 => vcat(1 + λ, 1 + 5λ, fill(1 + 6λ, N-4),
                           1 + 5λ, 1 + λ),
                 1 => vcat(-2λ, fill(-4λ, N - 3), -2λ),
                 2 => fill(λ, N-2))
    y_trend = H \ y
    y_cyclical = y - y_trend
    return y_cyclical, y_trend
end

doc"""
This function applies "Hamilton filter" to `AbstractVector`.

http://econweb.ucsd.edu/~jhamilto/hp.pdf

##### Arguments
- `y::AbstractVector` : data to be filtered
- `h::Integer` : Time horizon that we are likely to predict incorrectly.
                 Original paper recommends 2 for annual data, 8 for quarterly data,
                 24 for monthly data.
- `p::Integer` : Number of lags in regression. Must be greater than `h`.
Note: For seasonal data, it's desirable for `p` and `h` to be integer multiples
      of the number of obsevations in a year.
      e.g. For quarterly data, `h = 8` and `p = 4` are recommended.
##### Returns
- `y_cycle::Vector` : cyclical component
- `y_trend::Vector` : trend component
"""
function hamilton_filter(y::AbstractVector, h::Integer, p::Integer)
    y = Vector(y)
    T = length(y)
    y_cycle = fill(NaN, T)

    # construct X matrix of lags
    X = ones(T-p-h+1)
    for j = 1:p
        X = hcat(X, y[p-j+1:T-h-j+1])
    end

    # do OLS regression
    b = (X'*X)\(X'*y[p+h:T])
    y_cycle[p+h:T] = y[p+h:T] - X*b
    y_trend = vcat(fill(NaN, p+h-1), X*b)
    return y_cycle, y_trend
end

doc"""
This function applies "Hamilton filter" to `<:AbstractVector`
under random walk assumption.

http://econweb.ucsd.edu/~jhamilto/hp.pdf

##### Arguments
- `y::AbstractVector` : data to be filtered
- `h::Integer` : Time horizon that we are likely to predict incorrectly.
                 Original paper recommends 2 for annual data, 8 for quarterly data,
                 24 for monthly data.
Note: For seasonal data, it's desirable for `h` to be an integer multiple
      of the number of obsevations in a year.
      e.g. For quarterly data, `h = 8` is recommended.
##### Returns
- `y_cycle::Vector` : cyclical component
- `y_trend::Vector` : trend component
"""
function hamilton_filter(y::AbstractVector, h::Integer)
    y = Vector(y)
    T = length(y)
    y_cycle = fill(NaN, T)
    y_cycle[h+1:T] = y[h+1:T] - y[1:T-h]
    y_trend = y - y_cycle
    return y_cycle, y_trend
end


# 2^15 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26.
#
# What is the sum of the digits of the number 2^1000?

using ProjectEulerSolutions

# Simple with Julia's built-in BigInt and the digits function.
function p016solution(mantissa::Integer=2, exponent::Integer=3)::Integer
    return sum(digits(BigInt(mantissa) ^ exponent))
end

p016 = Problems.Problem(p016solution)

Problems.benchmark(p016, 2, 1000)


######### function for a sigmoid curve
### dose input
# x is a vector of concentrations
### parameters for a sigmoid curve
# slope
# upper asymptote (fixed)
# lower asymptote (varying)
# inflection point (xmid)

function log_logistic(upper = 1.0; dose, slope::Float64, lower::Float64, xmid::Float64)
    num = upper - lower
    denom = 1 .+ ((dose ./ xmid) .^ slope)
    return lower .+ num ./ denom
end

doses = [0:1:100.0;]

# example to test
responses = log_logistic(dose = doses, slope = 5.0, lower = 0.0, xmid = 50.0)
plot(doses, responses, ylim = 1.0, label = "b=5, c=0, d=1, e=50")

### Vary slope ###
plt1 = plot([NaN], [NaN], label = "", title = "Varying slopes", xlabel = "dose", ylabel = "response")

# range of slope values
slopes = [4.0, 8.0, 16.0, 32.0, 64.0]
for sl in slopes
    responses = log_logistic(dose = doses, slope = sl, lower = 0.0, xmid = 50.0)
    plot!(doses, responses, label = "$sl")
end
plt1

### Vary asymptote
plt2 = plot([NaN], [NaN], label = "", title = "Varying asymptotes"
, xlabel = "dose", ylabel = "response")

# range of asymptote values
asymptotes = [0.0, 0.1, 0.2, 0.3, 0.4]
for as in asymptotes
    responses = log_logistic(dose = doses, slope = 8.0, lower = as, xmid = 50.0)
    plot!(doses, responses, label = "$as")
end
plt2

### Vary inflection point
plt3 = plot([NaN], [NaN], label = "", title = "Varying inflection points"
, xlabel = "dose", ylabel = "response")

# range of xmid values
inflections = [20.0, 30.0, 40.0, 50.0, 60.0]
for infl in inflections
    responses = log_logistic(dose = doses, slope = 8.0, lower = 0.0, xmid = infl)
    plot!(doses, responses, label = "$infl")
end
plt3

plot(plt1, plt2, plt3)

### loop for high low combos
plt4 = plot([NaN], [NaN], label = "", title = "High/low parameters")

slopes2 = [5.0, 50.0]
asymps2 = [0.0, 0.5]
inflex2 = [10.0, 50.0]

for b in slopes2
    for d in asymps2
        for e in inflex2
            responses = log_logistic(dose = doses, slope = b, lower = d, xmid = e)
            plot!(doses, responses, label = "b = $b, d = $d, e = $e")
        end
    end
end
plt4

# function Hill_equation(; dose::Vector{Float64}, slope::Float64, asym::Float64, xmid::Float64)
#     xb = dose .^ (-slope)
#     num = (1-asym) .* xb
#     denom = xb .+ (xmid .^ (-slope))
#     sigmoid = num ./ denom
#     return sigmoid
# end

# responses = Hill_equation(x = doses, slope = 10.0, asym = 1.0, xmid = 30.0)
# plot(doses, responses, ylim = 1.0)


using ForwardDiff, Roots, Plots, LinearAlgebra, Optim

function backtracking_line_search(f, ∇f, x, d, α; p=0.5, β=0.1)
    y, g = f(x), ∇f(x)
    aux = 0
    while f(x + α*d) > y + β*α*(g'⋅d)
        α *= p
        aux = α
    end
    α
    return aux
end

f(x) = ((x[1]-1.0)^2) + (0.1*(x[2]-4)^2) + sin(5*pi*x[1])+sin(5*pi*x[2])
grad_f(x) = ForwardDiff.gradient(f,x)

# Ponto inicial
xk = [-1.0,1.0]

#direcao de busca
dk = -grad_f(xk)/norm(grad_f(xk))
beta = 0.1
alpha_f = 1.0

phi(alpha) = f(xk+alpha*dk)
d_phi(alpha) = grad_f(xk+alpha*dk)'dk
l_alpha(alpha) = phi(0)+alpha*beta*d_phi(0)

#condicao de Armijo
armj = []
for alpha = 0:0.01:alpha_f
    if phi(alpha) <= l_alpha(alpha)
        push!(armj,alpha)
    end
end

#Armijo com Backtracking
armjbt = backtracking_line_search(f,grad_f,xk,dk,alpha_f)

plot(phi,0,alpha_f,lw=2,lab="phi")
plot!(l_alpha,0,alpha_f,lw=2,lab="L_phi",c=:black)
scatter!(armj,phi.(armj),lab="Armijo",ms=3,c=:red)
scatter!([armjbt],[phi(armjbt)],lab="Armijo - BT",c=:yellow)
plot!(legend=:outerbottomright)
plot!(title="beta = 0.1",xlabel = "alpha", ylabel = "phi(alpha), l_alpha(alpha")


function ffdiff(f::Function, x1::Number, h::Number)
  # Estimates the derivative of a function using a forward finite difference
  # approximation
  (f(x1 + h) - f(x1)) / (x1+h - x1);
end

function bfdiff(f::Function, x1::Number, h::Number)
  # Estimates the derivative of a function using a backwards finite difference
  # approximation
  (f(x1) - f(x1-h)) / (x1 + h - x1);
end

function cfdiff(f::Function, x1::Number, h::Number)
  # Estimates the derivative of a function using a centred finite difference
  # approximation
  (f(x1 + h)  - f(x1 - h)) / 2(x1 + h - x1);
end


@doc raw"""
    SIR(du,u,p,t)

Define classic SIR(suspected-infected-recovered) model.

Parameters: (birth rate, natural death rate, disaese induced death rate, population, infection rate, recovery rate)

```math
\begin{aligned}
& \frac{\rm{d}S}{\rm{dt}} = \Lambda -\beta S I/N - d S,\\
&\frac{\rm{d}I}{\rm{dt}} = \beta  S I/N - \gamma I - d I - \alpha I,\\
&\frac{\rm{d}R}{\rm{dt}} = \gamma I - d R,\\
\end{aligned}
```
"""
function SIRbasic(du, u, p, t)
    Λ, d, α, N, β, γ = p
    pop = (Λ > 0 & d > 0) ? Λ / d : N
    S, I, R = u
    du[1] = Λ - β * S * I / pop - d * S
    du[2] = β * S * I / pop - γ * I - d * I - α * I
    du[3] = γ * I - d * R
end

@doc raw"""
    SEIR(du,u,p,t)

Define classic SEIR(suspected-exposed-infected-recovered) model.

Parameters: (birth rate, natural death rate, disaese induced death rate, population, infection rate, recovery rate, incubation period,
exposed decreasing infection ratio)

```math
\begin{aligned}
& \frac{\rm{d}S}{\rm{dt}} = \Lambda -\beta S (I+k_{E}E)/N - d S,\\
&\frac{\rm{d}E}{\rm{dt}} = \beta  S (I+k_{E}E)/N - \sigma E -d E,\\
&\frac{\rm{d}I}{\rm{dt}} =\sigma E - \gamma I - d I - \alpha I,\\
&\frac{\rm{d}R}{\rm{dt}} = \gamma I - d R,\\
\end{aligned}
```
"""
function SEIRbasic(du, u, p, t)
    Λ, d, α, N, β, γ, σ, ke = p
    pop = (Λ > 0 & d > 0) ? Λ / d : N
    S, E, I, R = u
    du[1] = Λ - β * S * (I + ke * E) / pop - d * S
    du[2] = β * S * (I + ke * E) / pop - σ * E - d * E
    du[3] = σ * E - γ * I - d * I - α * I
    du[4] = γ * I - d * R
end

@doc raw"""
    SEIAR(du,u,p,t)

Define classic SEIR(suspected-exposed-infected-asymptomatic-recovered) model.

Parameters: (birth rate, natural death rate, disaese induced death rate, population, infection rate, recovery rate, incubation period,
exposed decreasing infection ratio, asymptomatic infection decreasing infection ratio, asymptomatic rate,recovery rate of asymptomatic)

```math
\begin{aligned}
& \frac{\rm{d}S}{\rm{dt}} = \Lambda -\beta S (I+k_{E}E+k_{A}A)/N - d S,\\
&\frac{\rm{d}E}{\rm{dt}} = \beta  S (I+k_{E}E++k_{A}A)/N - \sigma E -d E,\\
&\frac{\rm{d}I}{\rm{dt}} =(1-\rho)\sigma E - \gamma I - d I - \alpha I,\\
&\frac{\rm{d}A}{\rm{dt}} =\rho\sigma E - \gamma_{A} A - d A,\\
&\frac{\rm{d}R}{\rm{dt}} = \gamma I +\gamma_{A} A - d R,\\
\end{aligned}
```
"""
function SEIARbasic(du, u, p, t)
    Λ, d, α, N, β, γ, σ, ke, ka, ρ, γA= p
    pop = (Λ > 0 & d > 0) ? Λ / d : N
    S, E, I, A, R = u
    du[1] = Λ - β * S * (I + ke * E + ka * A) / pop - d * S
    du[2] = β * S * (I + ke * E + ka * A) / pop - σ * E - d * E
    du[3] = (1-ρ)*σ * E - γ * I - d * I - α * I
    du[3] = ρ * σ * E - γA * A - d * A
    du[4] = γ * I +  γA * A - d * R
end

@doc raw"""
    SIS(du,u,p,t)

Define classic SIR(suspected-infected-suspected) model.

Parameters: (population, infection rate, recovery rate)

```math
\begin{aligned}
& \frac{\rm{d}S}{\rm{dt}} = -\beta S I/N + \gamma I,\\
&\frac{\rm{d}I}{\rm{dt}} = \beta  S I/N - \gamma I,\\
\end{aligned}
```
"""
function SISbasic(du, u, p, t)
    pop, β, γ = p
    S, I = u
    du[1] = - β * S * I / pop + γ * I
    du[2] = β * S * I / pop - γ * I
end


module PhaseSpaces

export Q_of_θφ,Q_of_θϕ,P_of_θφ,P_of_θϕ,θ_of_QP,φ_of_QP,ϕ_of_QP,jz,jx,jy,arc_between_θφ,arc_between_θϕ,arc_between_QP
    """
    ```julia
    function Q_of_θϕ(θ,ϕ)
    ```

    Returns
    ```math
        Q=\\sqrt{2 (1 - \\cos(\\theta))}\\cos(\\phi).
    ```

    Also [`Q_of_θφ(θ,φ)`](@ref Q_of_θφ).
    """
    Q_of_θϕ(θ::Real,ϕ::Real)=sqrt(2*(1-cos(θ)))*cos(ϕ)

    """
    See [`Q_of_θϕ`](@ref).
    """
    Q_of_θφ(θ::Real,φ::Real)=Q_of_θϕ(θ,φ)

    """
    ```julia
    function P_of_θϕ(θ,ϕ)
    ```

    Returns
    ```math
        P=- \\sqrt{2 (1 - \\cos(\\theta))}\\sin(\\phi).
    ```
    Also [`P_of_θφ(θ,φ)`](@ref P_of_θφ).
    """
    P_of_θϕ(θ::Real,ϕ::Real)=-sqrt(2*(1-cos(θ)))*sin(ϕ)

    """
    See [`P_of_θϕ`](@ref).
    """
    P_of_θφ(θ::Real,φ::Real)=P_of_θϕ(θ,φ)
    """
    ```julia
    function θ_of_QP(Q,P)
    ```
    Returns
    ```math
        \\theta =- \\arccos\\left(1 - \\frac{Q^2 + P^2}{2}\\right).
    ```
    """
    θ_of_QP(Q::Real,P::Real)=acos(1-(Q^2+P^2)/2)
    """
    ```julia
    function ϕ_of_QP(Q,P)
    ```

    Returns
    ```math
        \\phi = \\text{arctan2}(-P,Q)\\in [0,2 \\pi ],
    ```
    where ``\\text{arctan2}`` is the [2-argument arctangent](https://en.wikipedia.org/wiki/Atan2).

    Also [`φ_of_QP(Q,P)`](@ref φ_of_QP).
    """
    ϕ_of_QP(Q::Real,P::Real)=mod(atan(-P,Q),2*pi)

    """
    See [`ϕ_of_QP`](@ref).
    """
    φ_of_QP(Q::Real,P::Real)=ϕ_of_QP(Q,P)

    """
    ```julia
    function jz(Q,P)
    ```
    Returns
    ```math
        j_z = -  \\cos(\\theta(Q,P)).
    ```
    where ``\\text{arctan2}`` is the [2-argument arctangent](https://en.wikipedia.org/wiki/Atan2).
    """
    jz(Q::Real,P::Real)=-cos(θ_of_QP(Q,P))
    """
    ```julia
    function jx(Q,P)
    ```
    Returns
    ```math
        j_x = \\sin(\\theta(Q,P))\\cos(\\phi(Q,P)).
    ```
    """
    jx(Q::Real,P::Real)=sin(θ_of_QP(Q,P))*cos(φ_of_QP(Q,P))
    """
    ```julia
    function jy(Q,P)
    ```
    Returns
    ```math
        j_y = \\sin(\\theta(Q,P))\\sin(\\phi(Q,P)).
    ```
    """
    jy(Q::Real,P::Real)=sin(θ_of_QP(Q,P))*sin(φ_of_QP(Q,P))
    """
    ```julia
    function arc_between_θϕ(θ1,φ1,θ2,φ2)
    ```
    Returns
    ```math
        \\Theta = \\arccos(\\cos(\\theta_1)\\cos(\\theta_2)+ \\sin(\\theta_1)\\sin(\\theta_2)\\cos(\\phi_1 - \\phi_2)).
    ```
    Also [`arc_between_θφ(θ1,φ1,θ2,φ2)`](@ref arc_between_θφ).

    """
    arc_between_θϕ(θ1::Real,ϕ1::Real,θ2::Real,ϕ2::Real)=acos(clamp(cos(θ1)*cos(θ2)+ sin(θ1)*sin(θ2)*cos(ϕ1 - ϕ2),-1,1))

    """
    See [`arc_between_θϕ`](@ref).
    """
    arc_between_θφ(θ1::Real,φ1::Real,θ2::Real,φ2::Real)=arc_between_θϕ(θ1,φ1,θ2,φ2)
    """
    ```julia
    function arc_between_QP(Q1,P1,Q2,P2)
    ```
    Returns
    ```julia
        arc_between_θϕ(θ_of_QP(Q1,P1),ϕ_of_QP(Q1,P1),θ_of_QP(Q2,P2),ϕ_of_QP(Q2,P2)).
    ```
    """
    arc_between_QP(Q1::Real,P1::Real,Q2::Real,P2::Real)=arc_between_θϕ(θ_of_QP(Q1,P1),ϕ_of_QP(Q1,P1),θ_of_QP(Q2,P2),ϕ_of_QP(Q2,P2))
end


"""
    findiff(p,q,r,a,b,alpha,beta,M)
berisi program untuk mencari solusi persamaan differensial
`x''=f(t,x,x')` dengan masalah nilai batas `x(a)=alpha` dan `x(b)=beta` pada interval `[a,b]`.
Program ini secara default berisi 7 masukan, yaitu fungsi `p(t)`, `q(t)` dan `r(t)` dari persamaan
`x''=p(t)x'+q(t)x+r(t)`, titik ujung interval `[a,b]`, nilai batas `[alpha,beta]`, dan
jumlah sub-interval `M`.
# Example
```jl
julia> p(t)= 2*t/(1+t^2);
       q(t)= -2/(1+t^2);
       r(t)= 1 + 0*t;

julia> solusi = findiff(p,q,r,0,4,1.25,-0.95,40)
41×2 Array{Float64,2}:
 0.0   1.25
 0.1   1.29077
 0.2   1.31665
 0.3   1.32791
 ⋮
 3.7  -1.03501
 3.8  -1.01924
 3.9  -0.991063
 4.0  -0.95
```
return solusi masalah nilai batas `sol`.
"""
function findiff(p,q,r,a,b,alpha,beta,M)
  h = (b-a)/M;
  T = a:h:b;
  T = T[2:end-1];
  #% Bangun Matriks B
  B = -h^2*r.(T);
  B[1] = B[1] + (1+h/2*p(T[1]))*alpha;
  B[end] = B[end] + (1-h/2*p(T[end]))*beta;
  # Bangun Matriks A - Bagian Diagonal
  Ad = 2 .+h^2*q.(T);
  #% Bangun Matriks A - Bagian Bawah Diagonal
  Tbawah = T[2:end];
  Abawah = -1 .-h/2*p.(Tbawah);
  #% Bangun Matriks A - Bagian Atas Diagonal
  Tatas = T[1:end-1];
  Aatas = -1 .+h/2*p.(Tatas);
  A = Tridiagonal(Abawah,Ad,Aatas)
  #% Selesaikan AX=B
  X = A\B;
  T = [a; T; b];
  X = [alpha; X ;beta];
  solusi = [T X];
end


### A Pluto.jl notebook ###
# v0.12.16

using Markdown
using InteractiveUtils

# ╔═╡ e077ab16-22e5-11eb-15ed-5f0654bf7328
using Pkg, DrWatson

# ╔═╡ e75a9a06-22e5-11eb-2e36-8d4f62b830ed
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Random, Distributions, Plots, LaTeXStrings
	using SpecialFunctions, Roots
	Random.seed!(0)
end

# ╔═╡ ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
md"## Listing5.10"

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
eq(alpha, xb, xbl) = log(alpha) - digamma(alpha) - log(xb) + xbl

# ╔═╡ 06e24b20-3a9d-11eb-3557-97a1adc9f46e
begin
	actualAlpha, actualLambda = 2, 3
	gammaDist = Gamma(actualAlpha,1/actualLambda)
end

# ╔═╡ 06e27366-3a9d-11eb-211e-df469e196f9d
function mle(sample)
    alpha  = find_zero( (a)->eq(a,mean(sample),mean(log.(sample))), 1)
    lambda = alpha/mean(sample)
    return [alpha,lambda]
end

# ╔═╡ 7f6ea3be-3a9e-11eb-1287-4f1bc371585a
alpha

# ╔═╡ 06e2dc20-3a9d-11eb-2aed-6745e3221343
begin
	N = 10^4
	mles10   = [mle(rand(gammaDist,10)) for _ in 1:N]
	mles100  = [mle(rand(gammaDist,100)) for _ in 1:N]
	mles1000 = [mle(rand(gammaDist,1000)) for _ in 1:N]
end

# ╔═╡ 06ef2bc2-3a9d-11eb-3563-8b81e34087da
begin
	scatter(first.(mles10), last.(mles10),
		c=:blue, ms=1, msw=0, label="n = 10")
	scatter!(first.(mles100), last.(mles100),
		c=:red, ms=1, msw=0, label="n = 100")
	scatter!(first.(mles1000), last.(mles1000),
		c=:green, ms=1, msw=0, label="n = 1000",
		xlims=(0,6), ylims=(0,8), xlabel=L"\alpha", ylabel=L"\lambda")
end

# ╔═╡ 475ff888-22e6-11eb-2354-09f8f40a8e12
md"## End of listing5.10"

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═06e24b20-3a9d-11eb-3557-97a1adc9f46e
# ╠═06e27366-3a9d-11eb-211e-df469e196f9d
# ╠═7f6ea3be-3a9e-11eb-1287-4f1bc371585a
# ╠═06e2dc20-3a9d-11eb-2aed-6745e3221343
# ╠═06ef2bc2-3a9d-11eb-3563-8b81e34087da
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


# Low Rank Singular Value Decomposition
"""
    get_approximate_basis(A, l::Int; niter::Int = 2, M = nothing) -> Q
Return Matrix ``Q`` with ``l`` orthonormal columns such that ``Q Q^H A`` approximates ``A``. If ``M`` is specified, then ``Q`` is such that ``Q Q^H (A - M)`` approximates ``A - M``.
"""

struct Normal_QR{QT, RT}
    Q::QT
    R::RT
    function Normal_QR(Q::QT, R::RT) where {QT<:AbstractMatrix, RT<:AbstractMatrix}
        size(Q, 2) == size(R, 1) || throw(DimensionMismatch("$(size(Q)), $(length(R)) not compatible"))
        new{QT, RT}(Q, R)
    end
end

function private_qr(A::AbstractMatrix{T}) where T
	res = LinearAlgebra.qr(A)
	Normal_QR(Matrix(res.Q), res.R)
end

function get_approximate_basis(
    A::AbstractSparseMatrix{T}, l::Int, niter::Int = 2, M::Union{AbstractMatrix{T}, Nothing} = nothing) where T
    m, n = size(A)
    Ω = rand(T, (n, l))
    if M === nothing
        F_j = private_qr(A * Ω)
        for j = 1:niter
            F_H_j = private_qr(A' * Matrix(F_j.Q))
            F_j = qr(A * Matrix(F_H_j.Q))
        end
    else
        F_j = qr(A * Ω .- M * Ω)
        for j = 1:niter
            F_H_j = private_qr(A' * Matrix(F_j.Q) - M' * sum(Matrix(F_j.Q), dims=1))
            F_j = private_qr(A * Matrix(F_H_j.Q) .- M * Matrix(F_H_j.Q))
        end
    end
    Matrix(F_j.Q)
end

"""
    low_rank_svd(A, l::Int; niter::Int = 2, M = nothing) -> U, S, Vt
Return the singular value decomposition of a sparse matrix ``A`` with estimated rank ``l`` such that ``A ≈ U diag(S) Vt``. In case row vector ``M`` is given, then SVD is computed for the matrix ``A - M``.
"""

function low_rank_svd(A::AbstractSparseMatrix{T}, l::Int, niter::Int = 2, M::Union{AbstractMatrix{T}, Nothing} = nothing) where T

    if M === nothing
        Q = get_approximate_basis(A, l, niter, M)
        B = Q' * A
    else
        size(M, 2) == size(A, 2) || throw(DimensionMismatch())
        Q = get_approximate_basis(A, l, niter, M)
        B = Q' * A - sum(Q', dims=2) * M
    end

    dense_svd = svd(B)
    U = Q * dense_svd.U
    return U, dense_svd.S, dense_svd.Vt
end


# Julia translation of http://nbviewer.jupyter.org/github/barbagroup/AeroPython/blob/master/lessons/01_Lesson01_sourceSink.ipynb
# Lession 2 Source and Sink in a Freestream

using PyPlot
using Distributions

close("all")
meshgrid(x,y) = (repmat(x',length(y),1),repmat(y,1,length(x)))

N = 200                                # number of points in each direction
x_start, x_end = -4.0, 4.0            # boundaries in the x-direction
y_start, y_end = -2.0, 2.0            # boundaries in the y-direction
x = linspace(x_start, x_end, N)    # creates a 1D-array with the x-coordinates
y = linspace(y_start, y_end, N)    # creates a 1D-array with the y-coordinates

X,Y=meshgrid(x,y)

u_inf = 1.0

# computes the freestream velocity field
u_freestream = u_inf * ones((N, N))
v_freestream = zeros((N, N))

# computes the stream-function
psi_freestream = u_inf * Y

function get_velocity(strength, xs, ys, X, Y):
    """Returns the velocity field generated by a source/sink.

    Arguments
    ---------
    strength -- strength of the source/sink.
    xs, ys -- coordinates of the source/sink.
    X, Y -- mesh grid.
    """
    u = strength/(2*pi).*(X-xs)./((X-xs).^2+(Y-ys).^2)
    v = strength/(2*pi).*(Y-ys)./((X-xs).^2+(Y-ys).^2)

    return u, v
  end

function get_stream_function(strength, xs, ys, X, Y):
      """Returns the stream-function generated by a source/sink.

      Arguments
      ---------
      strength -- strength of the source/sink.
      xs, ys -- coordinates of the source/sink.
      X, Y -- mesh grid.
      """
      psi = strength/(2*pi).*atan2((Y-ys), (X-xs))

      return psi
    end

strength_source = 5.0            # strength of the source
x_source, y_source = -1.0, 0.0   # location of the source

# computes the velocity field
u_source, v_source = get_velocity(strength_source, x_source, y_source, X, Y)

# computes the stream-function
psi_source = get_stream_function(strength_source, x_source, y_source, X, Y)

  # superposition of the source on the freestream
  u = u_freestream + u_source
  v = v_freestream + v_source
  psi = psi_freestream + psi_source

  # plots the streamlines

  size = 10
  PyPlot.figure(figsize=(size, (y_end-y_start)/(x_end-x_start)*size))
  PyPlot.grid(true)
  PyPlot.xlabel("x", fontsize=16)
  PyPlot.ylabel("y", fontsize=16)
  PyPlot.xlim(x_start, x_end)
  PyPlot.ylim(y_start, y_end)
  PyPlot.streamplot(X, Y, u, v, density=2, linewidth=1, arrowsize=1, arrowstyle="->")
  PyPlot.scatter(x_source, y_source, color="#CD2305", s=80, marker="o")

  # calculates the stagnation point
  x_stagnation = x_source - strength_source/(2*pi*u_inf)
  y_stagnation = y_source

  # adding the stagnation point to the figure
  PyPlot.scatter(x_stagnation, y_stagnation, color="g", s=80, marker="o")

  # adds the dividing line to the figure
  PyPlot.contour(X, Y, psi,
              levels=[-strength_source/2, +strength_source/2],
              colors="#CD2305", linewidths=2, linestyles="solid");

strength_sink = -5.0        # strength of the sink
x_sink, y_sink = 1.0, 0.0   # location of the sink

# computes the velocity field on the mesh grid
u_sink, v_sink = get_velocity(strength_sink, x_sink, y_sink, X, Y)

# computes the stream-function on the grid mesh
psi_sink = get_stream_function(strength_sink, x_sink, y_sink, X, Y)

# superposition of a source and a sink on the freestream
u = u_freestream + u_source + u_sink
v = v_freestream + v_source + v_sink
psi = psi_freestream + psi_source + psi_sink

# plots the streamlines
size = 10
PyPlot.figure(figsize=(size, (y_end-y_start)/(x_end-x_start)*size))
PyPlot.xlabel("x", fontsize=16)
PyPlot.ylabel("y", fontsize=16)
PyPlot.xlim(x_start, x_end)
PyPlot.ylim(y_start, y_end)
PyPlot.streamplot(X, Y, u, v, density=2, linewidth=1, arrowsize=1, arrowstyle="->")
PyPlot.scatter([x_source, x_sink], [y_source, y_sink], color="#CD2305", s=80, marker="o")
PyPlot.contour(X, Y, psi, levels=[0.], colors="#CD2305", linewidths=2, linestyles="solid");

# computes the pressure coefficient field
cp = 1.0 - (u.^2+v.^2)./u_inf^2

# plots the pressure coefficient field
size = 10
PyPlot.figure(figsize=(1.1*size, (y_end-y_start)/(x_end-x_start)*size))
PyPlot.xlabel("x", fontsize=16)
PyPlot.ylabel("y", fontsize=16)
PyPlot.xlim(x_start, x_end)
PyPlot.ylim(y_start, y_end)
contf = PyPlot.contourf(X, Y, cp, levels=linspace(-2.0, 1.0, 100), extend="both")
cbar = PyPlot.colorbar(contf)
cbar[:set_label](L"$C_p$", fontsize=16)
cbar[:set_ticks]([-2.0, -1.0, 0.0, 1.0])
PyPlot.scatter([x_source, x_sink], [y_source, y_sink], color="#CD2305", s=80, marker="o")
PyPlot.contour(X, Y, psi, levels=[0.], colors="#CD2305", linewidths=2, linestyles="solid");