using MacroTools, SymEngine
import MacroTools.postwalk

"""
    abs_to_relu!(ex::Expr)
    abs_to_relu(ex::Expr)
function to convert an `abs(x)` expression to `relu(x) + relu(-x)`. Mutating and nonmutating versions.
"""
function abs_to_relu(ex::Expr)
    check_for_abs(ex)
    x = ex.args[2] # the thing getting abs'ed
    return :(relu($x) + relu(-$x))
end
function abs_to_relu!(ex::Expr)
    check_for_abs(ex)
    x = ex.args[2] # the thing getting abs'ed
    ex.args = [:+, :(relu($x)), :(relu(-$x))]
end

function check_for_abs(ex::Expr)
    ex.args[1] == :abs || throw(ArgumentError("Not an absolute value expression"))
    @assert length(ex.args) == 2 "Malformed expression. `abs` can take only one argument. Got $ex"
    return nothing
end

function check_for_maxmin(ex::Expr)
    ex.args[1] ∈ (:max, :min) || throw(ArgumentError("function is neither max nor min. Got $(ex.args[1])"))
    @assert length(ex.args) == 3 "max/min_to_abs can't handle more than two inputs at the moment."
    return nothing
end

function maxmin_to_abs(ex::Expr)
    check_for_maxmin(ex)
    a, b = ex.args[2:3]
    if ex.args[1] == :max
        return :(0.5*($a + $b + abs($a-$b)))
    elseif ex.args[1] == :min
        return :(0.5*($a + $b - abs($a-$b)))
    end
end

function max0torelu(ex::Expr)
    @assert(length(ex.args)==3)
    a, b = ex.args[2:3]
    if (a == 0)
        return :(relu($b))
    elseif (b == 0)
        return :(relu($a))
    end
end

to_relu_expression(not_ex) = not_ex
function to_relu_expression(ex::Expr)
    ex.head != :call && return ex
    if ex.args[1] ∈ (:max, :min)
        if (ex.args[1] == :max) & ((ex.args[2] == 0) | (ex.args[3] == 0))
            ex = max0torelu(ex)
        else
            ex = maxmin_to_abs(ex)
        end
    elseif ex.args[1] == :abs
        ex = abs_to_relu(ex) end
    for i in 1:length(ex.args)
        ex.args[i] = to_relu_expression(ex.args[i])
    end
    return ex
end

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

"""
Construct a symbolic expression for a line between the points (x₀, 1.0) on the left and (x₁, 0) on the right.
`pos_unit` has positive slope while `neg_unit` has negative slope. Note that due to the left-to-right assumption

    neg_unit(x₀, x₁) == pos_unit(x₁, x₀)
"""
neg_unit(x0, x1) = :($(1/(x0-x1)) * (x - $x1))
"""
Construct a symbolic expression for a line between the points (x₀, 0.0) on the left and (x₁, 1.0) on the right.
`pos_unit` has positive slope while `neg_unit` has negative slope. Note that due to the left-to-right assumption

    neg_unit(x₀, x₁) == pos_unit(x₁, x₀)
"""
pos_unit(x0, x1) = :($(1/(x1-x0)) * (x - $x0))

"""
    closed_form_piecewise_linear(pts)::Expr

Constructs a closed-form piecewise linear expression from an ordered (left to right) sequence of points.
The method is inspired by the paper by Lum and Chua (cite) that considers a piecewise linear function of the form:
`f(x) = Σᵢ(yᵢ⋅gᵢ(xᵢ))` where `gᵢ(xⱼ) = δᵢⱼ`
Here in the 1D case, `gᵢ = max(0, yᵢ*min(L1, L2))`, where `L1` and `L2` are the lines "ramping up" towards xᵢ
and "ramping down" away from xᵢ. The function returns an `Expr` based on a variable `x`.
This can be turned into a callable function `f` by running something like `eval(:(f(x) = \$expression_of_x))`.

# Example
    julia> pts = [(0,0), (1,1), (2, 0)]
    3-element Array{Tuple{Int64,Int64},1}:
     (0, 0)
     (1, 1)
     (2, 0)

    julia> closed_form_piecewise_linear(pts)
    :(max(0, 0 * (-1.0 * (x - 1))) + max(0, 1 * min(1.0 * (x - 0), -1.0 * (x - 2))) + max(0, 0 * (1.0 * (x - 1))))
"""
function closed_form_piecewise_linear(pts)
    n = length(pts)
    x, y = first.(pts), last.(pts) # split the x and y coordinates
    G = []
    for i in 2:n-1
        x0, x1, x2 = x[i-1:i+1] # consider the "triangulation" of points x0,x1,x2
        L1 = pos_unit(x0, x1) # x0-x1 is an increasing linear unit
        L2 = neg_unit(x1, x2) # x1-x2 is a decreasing linear unit
        gᵢ = :($(y[i]) * max(0, min($L1, $L2)))
        push!(G, gᵢ)
    end
    # first and last points are special cases that ignore the min
    g₀ = :($(y[1]) * max(0, $(neg_unit(x[1], x[2]))))
    gᵣ = :($(y[end]) * max(0, $(pos_unit(x[end-1], x[end]))))
    # Order doesn't matter now but for our debugging purposes earlier we enforce sequential ordering.
    pushfirst!(G, g₀)
    push!(G, gᵣ)
    return :(+$(G...))
end

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

function make_expr_dict(ex)
    D = Dict()
    ex = postwalk(ex) do e
        if is_relu_expr(e)
            return get!(D, e, Symbol("z$(length(D)+1)"))
        end
        return e
    end
    D[ex] = Symbol("z$(length(D)+1)")
    return D
end

is_relu_expr(ex) = ex isa Expr && ex.head == :call && ex.args[1] == :relu

simplify(ex::Expr) = postwalk(e -> _simplify(e), ex)
_simplify(s) = s
_simplify(e::Expr) = Meta.parse(string(expand(Basic(e))))

# thinking/scripting:
Base.occursin(x, y) = x == y
function Base.occursin(needle::Union{Symbol, Expr}, haystack::Expr)
    needle == haystack && return true
    for arg in haystack.args
        occursin(needle, arg) && return true
    end
    return false
end

# Type piracy again:
Base.Expr(B::Basic) = Meta.parse(string(B))

function get_symbols(ex::Union{Expr, Symbol})
    syms = Symbol[]
    ops = (:*, :+, :-, :relu)
    postwalk(e -> e isa Symbol && e ∉ ops ? push!(syms, e) : nothing, ex)
    unique(syms)
end

function count_min_max(expr)
    """ count number of min and max in expr"""
    c = [0, 0]
    if expr == :min
        c[1] = 1
    elseif expr == :max
        c[2] = 1
    elseif expr isa Expr
        for arg in expr.args
            c += count_min_max(arg)
        end
    end
    return c
end


# -----------------------------------------------------------
#
# An example of contructing the initial data for a traveling
# wave in D-dimensions using the tensor_construct method
# and the wave_evolve in PDEs.jl
#
# -----------------------------------------------------------

using GalerkinSparseGrids
using LinearAlgebra

# -----------------------------------------------------
# Generates the coefficients for a waveform
# A cos(\vec{k} \cdot \vec{x} + \phi), \vec{k} = 2 \pi \vec{m}
# using an interpolation of type (k,n)
# and periodic boundary conditions in D-dimensionss
# -----------------------------------------------------
function cos_coeffs(k::Int, n::Int, m::AbstractArray{T,1};
                    scheme="sparse", phase = 0.0, A = 1.0) where T
    D = length(m)
    wavenumber = 2*pi*m

    # Begin with writing cos(\sum_i k_i x_i) as a sum of products
    # of sines/cosines of individual k_j x_j
    sines   = [i==1 ? x->sin(wavenumber[i]*x[1]+phase) : x->sin(wavenumber[i]*x[1]) for i in 1:D]
    cosines = [i==1 ? x->cos(wavenumber[i]*x[1]+phase) : x->cos(wavenumber[i]*x[1]) for i in 1:D]

    sine_dicts   = [coeffs_DG(1, k, n, sines[i]) for i in 1:D]
    cosine_dicts = [coeffs_DG(1, k, n, cosines[i]) for i in 1:D]

    ansVect = zeros(get_size(Val(D), k, n, Val(Symbol(scheme))))

    for SCs in CartesianIndices(ntuple(q->2, D))
        num_sines = sum([SCs[i]-1 for i in 1:D])
        if num_sines % 2 == 1
            continue
        end
        sign = num_sines%4==0 ? 1 : -1

        coeff_array = [SCs[i]==1 ? cosine_dicts[i] : sine_dicts[i] for i in 1:D]
        productDict = tensor_construct(D, k, n, coeff_array; scheme=scheme)
        productVect = D2V(D, k, n, productDict; scheme=scheme)

        ansVect += sign * productVect
    end

    return A * ansVect
end

# -----------------------------------------------------
# The same as above, but using sin
# -----------------------------------------------------
function sin_coeffs(k::Int, n::Int, m::Array{Int,1};
                    scheme="sparse", phase=0.0, A=1.0)
    return cos_coeffs(k, n, m; scheme=scheme, phase=phase-pi/2, A=A)
end

# -----------------------------------------------------
# Returns the data for a traveling wave
# using the above methods
# ----------------------------------------------------
function traveling_wave(k::Int, n::Int, m::Array{Int,1};
                        scheme="sparse", phase=0.0, A=1.0)
    wavenumber = 2*pi*m
    frequency = sqrt(dot(wavenumber,wavenumber))

    # u(x) = A * cos(dot(wavenumber,x) + phase)
    # v(v) = A * frequency * sin(dot(k,x) + phase)
    u0_coeffs = cos_coeffs(k, n, m; scheme=scheme, phase=phase, A=A)
    v0_coeffs = sin_coeffs(k, n, m; scheme=scheme, phase=phase, A=A*frequency)
    return (u0_coeffs, v0_coeffs)
end

# -----------------------------------------------------
# Main routine:
# -----------------------------------------------------

# Modify m to change the wavenumber.
# We use integer entries here because of periodic boundary conditions
m = [1,2,-1]
truesoln = x -> cos(2*pi*(dot(m,x) - sqrt(dot(m,m))*0.54))
k_max = 5
n_max = 6
D = length(m)

t0 = 0;
t1 = 0.54;

println("Wave Evolution in ", D, "D.")
println("Going to k_max = ", k_max, ", n_max = ", n_max, ":")

for k_used in 1:k_max
    for n_used in 1:n_max
        f0coeffs, v0coeffs = traveling_wave(k_used, n_used, m)
        soln = wave_evolve(D, k_used, n_used, f0coeffs, v0coeffs, t0, t1)
        dict = V2D(D, k_used, n_used, soln[2][end])

        err = mcerr(x->reconstruct_DG(dict, [x...]), truesoln, D)
        println("(k = ", k_used, ", n = ", n_used, ") : err = ", err)
    end
end


"""
idwi(args...)  -->Plot

iwm is a Plot Recipe in which makes a spatial interpolation of data provided applying Inverse Weight Mapping.

General equation for spatial interpolation is:
``{ Z }^{ * }=\\sum _{ i=1 }^{ n }{ { \\lambda  }_{ i }{ z }_{ i } } ``

where ``\\lambda`` is the weight Parameter

Applying the Inverse‐Distance Weighted Interpolation

``{ Z }\\left( x \\right) =\\frac { \\sum _{ i=1 }^{ n }{ { w }_{ i }{ z }_{ i } }  }{ \\sum _{ i=1 }^{ n }{ { z }_{ i } }  } \\\\ \\\\ { w }_{ i }=\\frac { 1 }{ { d }_{ i }^{ n } } ``

<br> The next table show the list of variables allowed:

|PropertyName|Args|Default|Input|Description
|---|---|---|---|---|
|x|Mandatory|--|Array{Number,1}| X values|
|y|Mandatory|--|Array{Number,1}| Y values|
|z|Mandatory|--|Array{Number,1}| Z values|
|m|Optional|m=100|m=number| Number of grids in y axis|
|n|Optional|n=100|n=number| Number of grids in x axis|
|p|Optional|p=2|p=number| IDWI exponent. The greater more influenced by near points|
"""
@userplot idwi

@recipe function f(h::idwi; m=100, n=100, p=2, surf=false)

    ylabel --> "North [m]"
    xlabel --> "East [m]"
    seriescolor --> :Spectral

x, y, z = h.args

# Build the grid of prediction points

    ## X & Y range
  Xrange=range(minimum(x),stop=maximum(x),length=n)
  Yrange=range(minimum(y),stop=maximum(y),length=m)

    ## X & Y grids
  X=repeat(Xrange',length(Yrange),1)
  Y=repeat(Yrange,1,length(Xrange))

    # X & Y grids converted to vectors
    Xvec=vec(X)
    Yvec=vec(Y)

    #Number of Sample Points and Prediction Points
    Nsp=length(x)                           #Numbers of Sample Points
    Npp=length(Xrange)*length(Yrange)       #Number of Prediction Points

    #DISTANCE BETWEEN PREDICTION AND SAMPLE POINTS---------------------------
    #Matrix of distance   I x J    Sample Points x Prediction Points

    d=zeros(Nsp,Npp)
    for i=1:Nsp
        for j=1:Npp
            d[i,j]=sqrt((x[i]-Xvec[j]).^2+(y[i]-Yvec[j]).^2)
        end
    end

    #Estimate the matrix of weights

    W=map(dis->(1 ./(dis.^p)),d)

    #Calculate value in prediction point in a vector

    Zvec=zeros(Npp)
    for i=1:Npp
        Zvec[i]=W[:,i]'*z ./ sum(W[:,i])
    end

    Z=reshape(Zvec,m,n)

#Coutour series
if surf==true
    @series begin
        seriestype := :surface
        zflip := true
        Xrange, Yrange, Z
        end
else
@series begin
    seriestype := :contour
    clabels := true
    fill --> true

    Xrange, Yrange, Z
end
end
end


### A Pluto.jl notebook ###
# v0.16.0

using Markdown
using InteractiveUtils

# ╔═╡ 0bdd0fc6-9203-11eb-1ea3-9fe58aca7da0
using SymPy, LinearAlgebra

# ╔═╡ aa073e00-323c-11eb-0297-83e3db12248d
md"
# Gradijent, divergencija i rotacija

__Nabla__ ili __Hamiltonov operator__  je

$$\nabla = \frac{\partial}{\partial x} \vec{\imath}+\frac{\partial}{\partial y} \vec{\jmath} +\frac{\partial}{\partial z} \vec{k}.$$

Nabla ima svojstva derivacije i vektora.

__Gradijent__ skalarnog polja $f(x,y,z)$ je

$\mathop{\mathrm{grad}} f = \nabla \cdot f=\frac{\partial f}{\partial x} \vec{\imath}+\frac{\partial f}{\partial y} \vec{\jmath} +\frac{\partial f}{\partial z} \vec{k}.$

__Divergencija__ vektorskog polja

$\vec{w}(x,y,z)=w_x(x,y,z)\vec{\imath} + w_y(x,y,z) \vec{\jmath} + w_z(x,y,z)\vec{k}$

je

$\mathop{\mathrm{div}} \vec{w} = \nabla \cdot \vec{w}= \frac{\partial w_x}{\partial x} +\frac{\partial w_y}{\partial y} +\frac{\partial w_z}{\partial z}.$

__Rotacija__ vektorskog polja $\vec{w}(x,y,z)$ je

$\mathop{\mathrm{rot}} \vec{w} = \nabla \times \vec{w}= \begin{vmatrix} \vec{\imath} & \vec{\jmath} & \vec{k} \\ \frac{\partial}{\partial x} & \frac{\partial}{\partial y} & \frac{\partial}{\partial z} \\ w_x & w_y &w_z \end{vmatrix}.$

__Usmjerena derivacija__ skalarnog polja $f(x,y,z)$ u smjeru vektora $\vec a$ je

$$\frac{\partial f}{\partial \vec a}(x,y,z)=\vec a_0 \cdot \mathop{\mathrm{grad}} f(x,y,z).$$

__Usmjerena derivacija__ vektorskog polja $\vec w(x,y,z)$ u smjeru vektora $\vec a$ je

$$\frac{\partial \vec w}{\partial \vec a}(x,y,z)=(\vec a_0 ∇) \vec w(x,y,z).$$
"

# ╔═╡ c75d36c0-923c-11eb-0477-c3101e21f880
x,y,z=symbols(:x),symbols(:y),symbols(:z)

# ╔═╡ 1b261092-923a-11eb-01d6-7f705b30d46e
begin
	∂x(f::Sym) = diff(f,x)
	∂y(f::Sym) = diff(f,y)
	∂z(f::Sym) = diff(f,z)
	∇=[∂x,∂y,∂z]
end

# ╔═╡ 1ca28980-925d-11eb-2be1-815a5df595ff
begin
	import Base.*
	*(∂x::typeof(∂x),f::Sym)=∂x(f)
	*(∂y::typeof(∂y),f::Sym)=∂y(f)
	*(∂z::typeof(∂z),f::Sym)=∂z(f)
	*(∇::Function,f::Sym)=∇(f)
	grad(f)=∇*f
	rot(u)=∇×u
	import LinearAlgebra.⋅
	⋅(∇::Array{Function,1},w::Array{Sym,1})=∇[1]*w[1]+∇[2]*w[2]+∇[3]*w[3]
	div(u)=∇⋅u
	⋅(a::Vector,∇::Array{Function,1})=a[1]*∇[1]+a[2]*∇[2]+a[3]*∇[3]
end

# ╔═╡ c2a476e6-e948-485f-9b2e-b6f821504263
# Izvedeni operator a∇
begin
	a∂x(a::Vector,f::Sym) = a[1]*diff(f,x)
	a∂y(a::Vector,f::Sym) = a[2]*diff(f,y)
	a∂z(a::Vector,f::Sym) = a[3]*diff(f,z)
	a∇(a::Vector,f::Sym)=a∂x(a,f)+a∂y(a,f)+a∂z(a,f)
	a∇(a::Vector,w::Array{Sym,1})=[a∇(a,w[1]),a∇(a,w[2]),a∇(a,w[3])]
end

# ╔═╡ bd70ec2e-ad07-4526-aa70-5f25c86b444e
md"
## Primjeri
"

# ╔═╡ fd3305b2-923a-11eb-15c4-5b79db40bf46
begin
	# Skalarna polja
	f=x^2+3*y*z+5
	g=x*y*z
	# Vektorska polja
	w=[y*z,z*x,x*y]
	u=[x^2*y,x*y,z/x]
	# Vektor
	a=[1,1,-2]
	# Točka
	T=(1,-1/2,2)
end

# ╔═╡ 2b00c286-0807-4fa0-a9c2-6b1d56ae3716
f

# ╔═╡ 66b9df0a-06c4-409c-9ae4-79b3cc3833b5
∂x(f)

# ╔═╡ 9b816a31-675a-4dea-83e7-67430eeb741e
∂x*f

# ╔═╡ ec770eca-54d2-4394-b359-ad5e47218e92
a∇(a,f)

# ╔═╡ 3577e5e0-8fa8-4938-9119-b7c14f31e5be
a∇(a,u)

# ╔═╡ 2a47ed85-0691-4cb2-84b5-52facb4eda0b
# Gradijent
∇*f

# ╔═╡ 26292a50-9627-40a3-bacc-034de0882ac2
grad(f)

# ╔═╡ 8d4a98d1-414e-4714-9c94-6b6426388bc0
# Gradijent u zadanoj točki
subs.(∇*f,x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ c62f2abe-b330-4e5b-8a73-c2ac5fe32284
# Divergencija
∇⋅u

# ╔═╡ f141e418-574d-4a0f-903c-35ad8d3e6883
div(u)

# ╔═╡ 0e012c70-afc3-410f-a73c-978783948cc3
# Divergencija u zadanoj točki
subs(∇⋅u,x=>T[1],y=>T[2],z=>T[2])

# ╔═╡ faea848e-f4d7-4476-993f-d9db5c6ca2ee
# Rotor
∇×u

# ╔═╡ e3c9e5d1-8542-4940-8dd2-c60849b152c3
rot(u)

# ╔═╡ 0496ef19-9e5c-4f5b-aa4b-22d98cfcf9a0
# Rotor u zadanoj točki
subs.(∇×u,x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ ad3b7955-cd19-4aaf-8a98-b4727bafe572
# Primjer svojstva
rot(f*grad(g))

# ╔═╡ 93446240-2427-4964-9e3a-75f2de6b67e6
grad(f)×grad(g)

# ╔═╡ 44c1a521-657b-43c4-ac59-7a8e24bf25cc
# Drugi primjer svojstva
rot(w×u)

# ╔═╡ 54692583-1661-4b04-8f2c-94689630cff8
expand.(w*div(u)-u*div(w)+a∇(u,w)-a∇(w,u))

# ╔═╡ fadb40e2-6c5e-48b2-a45f-88ea4f8cc4e5
# Usmjerena derivacija skalarnog polja
a/norm(a)⋅grad(f)

# ╔═╡ 180512f7-c8e8-4aee-9872-0057d6fe9c5d
# Usmjerena derivacija skalarnog polja u točki
subs(a/norm(a)⋅grad(f),x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ 258fbdc7-7c24-4ecf-bc5a-468069f5c482
# Skalarno polje najbrže raste u smjeru gradijenta
subs(grad(f)/norm(grad(f))⋅grad(f),x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ eeb8e757-668e-48c1-b560-076b3f47b5bf
# Usmjerena derivacija vektorskog polja
a∇(a/norm(a),u)

# ╔═╡ 694962cc-cca2-4a4d-939d-7eb329de9917
# Usmjerena derivacija vektorskog polja u točki
subs.(a∇(a/norm(a),u),x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"

[compat]
SymPy = "~1.0.52"
"""

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

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

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

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

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

[[CommonEq]]
git-tree-sha1 = "d1beba82ceee6dc0fce8cb6b80bf600bbde66381"
uuid = "3709ef60-1bee-4518-9f2f-acd86f176c50"
version = "0.2.0"

[[CommonSolve]]
git-tree-sha1 = "68a0743f578349ada8bc911a5cbd5a2ef6ed6d1f"
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
version = "0.2.0"

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

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

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

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

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

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

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

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

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

[[IrrationalConstants]]
git-tree-sha1 = "f76424439413893a832026ca355fe273e93bce94"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.0"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "a8709b968a1ea6abc2dc1967cb1db6ac9a00dfb6"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.5"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[[SymPy]]
deps = ["CommonEq", "CommonSolve", "LinearAlgebra", "Markdown", "PyCall", "RecipesBase", "SpecialFunctions"]
git-tree-sha1 = "1ef257ecbcab8058595a68ca36a6844b41babcbd"
uuid = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"
version = "1.0.52"

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

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

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

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

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

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

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

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

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

# ╔═╡ Cell order:
# ╟─aa073e00-323c-11eb-0297-83e3db12248d
# ╠═0bdd0fc6-9203-11eb-1ea3-9fe58aca7da0
# ╠═c75d36c0-923c-11eb-0477-c3101e21f880
# ╠═1b261092-923a-11eb-01d6-7f705b30d46e
# ╠═1ca28980-925d-11eb-2be1-815a5df595ff
# ╠═c2a476e6-e948-485f-9b2e-b6f821504263
# ╟─bd70ec2e-ad07-4526-aa70-5f25c86b444e
# ╠═fd3305b2-923a-11eb-15c4-5b79db40bf46
# ╠═2b00c286-0807-4fa0-a9c2-6b1d56ae3716
# ╠═66b9df0a-06c4-409c-9ae4-79b3cc3833b5
# ╠═9b816a31-675a-4dea-83e7-67430eeb741e
# ╠═ec770eca-54d2-4394-b359-ad5e47218e92
# ╠═3577e5e0-8fa8-4938-9119-b7c14f31e5be
# ╠═2a47ed85-0691-4cb2-84b5-52facb4eda0b
# ╠═26292a50-9627-40a3-bacc-034de0882ac2
# ╠═8d4a98d1-414e-4714-9c94-6b6426388bc0
# ╠═c62f2abe-b330-4e5b-8a73-c2ac5fe32284
# ╠═f141e418-574d-4a0f-903c-35ad8d3e6883
# ╠═0e012c70-afc3-410f-a73c-978783948cc3
# ╠═faea848e-f4d7-4476-993f-d9db5c6ca2ee
# ╠═e3c9e5d1-8542-4940-8dd2-c60849b152c3
# ╠═0496ef19-9e5c-4f5b-aa4b-22d98cfcf9a0
# ╠═ad3b7955-cd19-4aaf-8a98-b4727bafe572
# ╠═93446240-2427-4964-9e3a-75f2de6b67e6
# ╠═44c1a521-657b-43c4-ac59-7a8e24bf25cc
# ╠═54692583-1661-4b04-8f2c-94689630cff8
# ╠═fadb40e2-6c5e-48b2-a45f-88ea4f8cc4e5
# ╠═180512f7-c8e8-4aee-9872-0057d6fe9c5d
# ╠═258fbdc7-7c24-4ecf-bc5a-468069f5c482
# ╠═eeb8e757-668e-48c1-b560-076b3f47b5bf
# ╠═694962cc-cca2-4a4d-939d-7eb329de9917
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:AbstractArray, TBCL<:PeriodicBC, TBCR<:PeriodicBC}

    # build eigenvalues
    @. problem.λ = ((2*π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs
    @. problem.rhs = problem.f;

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:Function, TBCL<:PeriodicBC, TBCR<:PeriodicBC}

    # build eigenvalues
    @. problem.λ = ((2*π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs
    @. problem.rhs = problem.f(problem.x);

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:AbstractArray, TBCL<:DirichletBC, TBCR<:DirichletBC}

    # build eigenvalues
    @. problem.λ = ((π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs
    @. problem.rhs = problem.f;

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:Function, TBCL<:DirichletBC, TBCR<:DirichletBC}

    # build eigenvalues
    @. problem.λ = ((π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs with odd extension
    @. problem.rhs[problem.N÷2+1:end] = problem.f([problem.a; problem.x]);
    problem.rhs[1:problem.N÷2] .= -reverse(problem.f.([problem.x; problem.b]));

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:AbstractArray, TBCL<:NeumannBC, TBCR<:NeumannBC}

    # build eigenvalues
    @. problem.λ = ((π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs
    @. problem.rhs = problem.f;

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:Function, TBCL<:NeumannBC, TBCR<:NeumannBC}

    # build eigenvalues
    @. problem.λ = ((π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs with even extension
    @. problem.rhs[problem.N÷2+1:end] = problem.f(problem.x[1:end-1]);
    problem.rhs[1:problem.N÷2] .= reverse(problem.f.(problem.x[2:end]));

    problem
end


""" package  -------------------------------------------------------

   Performs several verification calculations given a file of grid spacings
   and some observed quantity corresponding to each grid spacing.

   Computes:
   - order of convergence
   - Richardson extrapolation to zero grid spacing
   - grid convergence indices (GCI)

 --------------------------------------------------------------------------

Adapted from:

    NPARC Alliance CFD Verification and Validation Web Site
    Examining Spatial (Grid) Convergence
    verify.f90
    URL: http://www.grc.nasa.gov/WWW/wind/valid/tutorial/spatconv.html

    Nov '11: Updated to reflect Celik et al 2008.
    Sept '18: Translated to Julia

    Credit to Matthew Topper.
"""

""" Calculate the order of convergence values generated with three
grids of reducing resolution (ie grid_1 is finest). The values of the grids
are needed along with the ratios between them.

An iterative method with under-relaxation is used to calculate the order
of convergence as the refinement ratio is not necessarily constant.

This has been modified to the method of Celik (2008).
"""
function order_of_convergence(value_1, value_2, value_3, ratio_21, ratio_32, omega=0.5, tol=1.E-6)
    # Set a maximum residual and number of iterations
    max_res = 1.E6
    # calculate the epsilons.
    epsilon32 = float(value_3 - value_2)
    epsilon21 = float(value_2 - value_1)
    # Calculate the fraction
    epfrac = epsilon32 / epsilon21
    # Get the signed unit, s
    s = epfrac / abs(epfrac)
    # Initial guess at order of convergence, p
    p1 = (1. / log(ratio_21)) * abs(log(abs(epfrac))) # start_p
    # Initialise the residual and number of iterations
    residual = 1.0
    iterations = 0
    while abs(residual) > tol
        # Break if it's all gone bad
        if float(iterations) > max_res && residual > max_res
            println("Residual out of range or too many iterations")
        end
        # Get the last value
        p0 = p1
        # Calculate q
        q = log((ratio_21^p0 - s) / (ratio_32^p0 - s))
        # Calculate the p iteration
        pnew = (1. / log(ratio_21)) * abs(log(abs(epfrac)) + q)
        # Calculate the relaxation step.
        p1 = (1. - omega) * p0 + omega * pnew
        residual = p1 - p0
        iterations += 1
    end
    # if abs(p1) > 1.0
    #     return 1.0
    # else
    return abs(p1)
    # end
end

""" Estimate the zero grid spacing value using richardsons extrapolation and
two grids of reducing resolution (ie grid_1 is finest). The refinement ratio
is needed. The order of convergence, p, is also required.
"""
function richardson_extrapolate(value_1, value_2, ratio_21, p)
    f_exact = ( ratio_21^p * value_1 - value_2 ) / ( ratio_21^p - 1.0 )
    return f_exact
end

""" This routine returns the relative error and extrapolated
relative error. The values of the grids and needed along
with the extrapolated value.
"""
function error_estimates(value_1, value_2, f_exact)
    # Get the approximate relative error
    e21a =  abs( (value_1 - value_2) / value_1 )
    # Get the extrapolated relative error
    e21ext = abs( ( f_exact - value_1 ) / f_exact )
    return e21a, e21ext
end

""" Calculate the fine and coarse grid convergence index for two grids of
reducing resolution (ie grid_1 is finest). The refinement ration between the
grids is required along with the approximate relative error (e21_approx) and
the order of convergence, p.
"""
function gci(ratio_21, e21_approx, p)
    # Using a fixed safety factor as per Celik (2008)
    safety_factor = 1.25
    # Calculate the gci
    gci_fine = safety_factor * e21_approx / (ratio_21^p - 1.0)
    gci_coarse = ratio_21^p * gci_fine
    return gci_fine, gci_coarse
end

""" Calculate the ratio in succesive Eps as defined at the bottom of page
129 in Roache. If the ration is close to one then the asymptotic range has
been reached.
"""
function asymptotic_ratio(gci_fine_21, gci_fine_32, ratio_21, p)
    ratio = ratio_21^p * ( gci_fine_21 / gci_fine_32)
    return ratio
end

"""Return the fine GCI value"""
function GCI_calc(value_1, value_2, value_3, h1, h2, h3)
    ratio_21 = h2/h1
    ratio_32 = h3/h2
    ooc = order_of_convergence(value_1, value_2, value_3, ratio_21, ratio_32)
    f_exact_21 = richardson_extrapolate(value_1, value_2, ratio_21, ooc)
    f_exact_32 = richardson_extrapolate(value_2, value_3, ratio_32, ooc)
    e21a, e21ext = error_estimates(value_1, value_2, f_exact_21)
    e32a, e32ext = error_estimates(value_2, value_3, f_exact_32)
    gci_fine_21, gci_coarse_21 = gci(ratio_21, e21a, ooc)
    gci_fine_32, gci_coarse_32 = gci(ratio_32, e32a, ooc)
    ratio = asymptotic_ratio(gci_fine_21, gci_fine_32, ratio_21, ooc)
    return abs(gci_fine_21)
end

"""Return the fine GCI value"""
function extrap_value(value_1, value_2, value_3, h1, h2, h3)
    ratio_21 = h2/h1
    ratio_32 = h3/h2
    ooc = order_of_convergence(value_1, value_2, value_3, ratio_21, ratio_32)
    f_exact_21 = richardson_extrapolate(value_1, value_2, ratio_21, ooc)
    return abs(f_exact_21)
end

"""Return the order of convergence."""
function ooc_value(value_1, value_2, value_3, h1, h2, h3)
    ratio_21 = h2/h1
    ratio_32 = h3/h2
    ooc = order_of_convergence(value_1, value_2, value_3, ratio_21, ratio_32)
    return ooc
end


### A Pluto.jl notebook ###
# v0.12.10

using Markdown
using InteractiveUtils

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

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

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

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
begin
	mu = 20
	d1, d2 = Normal(mu,mu), Exponential(mu)

	n = 100
	data1 = rand(d1,n)
	data2 = rand(d2,n)
end

# ╔═╡ bdabe54e-250a-11eb-1714-25afa772347e
begin
	qqnorm(data1, c=:blue, ms=3, msw=0, label="Normal Data")
	qqnorm!(data2, c=:red, ms=3, msw=0, label="Exponential Data",
			xlabel="Normal Theoretical Quantiles",
			ylabel="Data Quantiles", legend=true)
end

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

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═bdabe54e-250a-11eb-1714-25afa772347e
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


# ### parametric function plot
# http://docs.juliaplots.org/latest/examples/pyplot/

#md # ![FILE_NAME.png](images/FILE_NAME.png)

xₜ(t) = sin(t)
yₜ(t) = sin(2t)

plot(xₜ, yₜ, 0, 2π, leg=false, fill=(0,:orange))


# Reset the per-thread random seeds to make results reproducible
reseed!() = for i in 1:Threads.nthreads() Random.seed!(TRNG[i], i) end

"Per-thread rand()"
@inline function trand()
	@inbounds rng = TRNG[Threads.threadid()]
	rand(rng)
end

@inline function trand(::Type{T}) where T
	@inbounds rng = TRNG[Threads.threadid()]
	rand(rng, T)
end

@inline function random_vec3_in_sphere(::Type{T}) where T # equiv to random_in_unit_sphere()
	while true
		p = random_vec3(T(-1), T(1))
		if p⋅p <= 1
			return p
		end
	end
end

@inline random_between(min::T=0, max::T=1) where T = trand(T)*(max-min) + min # equiv to random_double()
@inline random_vec3(min::T, max::T) where T = @inbounds @SVector[random_between(min, max) for i ∈ 1:3]
@inline random_vec2(min::T, max::T) where T = @inbounds @SVector[random_between(min, max) for i ∈ 1:2]

"Random unit vector. Equivalent to C++'s `unit_vector(random_in_unit_sphere())`"
@inline random_vec3_on_sphere(::Type{T}) where T = normalize(random_vec3_in_sphere(T))

@inline function random_vec2_in_disk(::Type{T}) where T # equiv to random_in_unit_disk()
	while true
		p = random_vec2(T(-1), T(1))
		if p⋅p <= 1
			return p
		end
	end
end


using SimpleDrawing, LinearFractionalTransformations

export Circle

"""
    Circle

Represents a circle in the plane. Constructors:
* `circle(x,y,r)` based on center coordinates and radius
* `circle(z,r)` based on center given as a complex number and a radius
* `circle(a,b,c)` based on three complex numbers that represent points on the circle
"""
struct Circle <: Cline
    x::Float64
    y::Float64
    r::Float64
    function Circle(a::Real, b::Real, rad::Real)
        rad >= 0 || error("Radius must be nonnegative")
        new(a, b, rad)
    end
end

Circle(z::Complex, r::Real) = Circle(real(z), imag(z), r)

function Circle(a::Complex, b::Complex, c::Complex)
    z = find_center(a, b, c)
    !isinf(z) || error("Cannot construct a circle through three collinear points")
    r = abs(a - z)
    return Circle(z, r)
end

# copy constructor
Circle(C::Circle) = Circle(C.x, C.y, C.r)

# standard stuff

export radius, area, circumference, center

"""
    radius(C)
Return the radius of the circle.
"""
radius(C::Circle) = C.r

"""
    area(C)
Return the area of the circle.
"""
area(C::Circle) = π * C.r * C.r

"""
    circumference(C)
Return the circumference of the circle.
"""
circumference(C::Circle) = 2 * π * C.r

import Plots: center
"""
    center(C)
Return the center of the circle as a complex number.
"""
center(C::Circle) = Complex(C.x, C.y)

"""
    three_points(C)
Return a list of three points on the circle `C`.
"""
function three_points(C::Circle)
    z = center(C)
    r = radius(C)
    return [z + r * exp(k * (2π * im / 3)) for k = 0:2]
end
export three_points

import SimpleDrawing: draw
const _FILL_COLOR = :lightgray
const _LINE_COLOR = :black

"""
    draw(C::Circle, fill::Bool=false; args...)
Draw the circle `C`. With `fill=true`, fill in the interior in color.

Example:
`draw(C,true,color=:yellow, linecolor=:red, fillalpha=0.3)`
"""
function draw(C::Circle, fill::Bool = false; args...)
    if fill
        draw_disc(
            center(C),
            radius(C);
            linecolor = _LINE_COLOR,
            color = _FILL_COLOR,
            args...,
        )
    else
        draw_circle(center(C), radius(C); linecolor = _LINE_COLOR, args...)
    end
end

include("operations.jl")
include("kiss.jl")
include("inversion.jl")


# used when thinking about the `alpha` exponent for xQ
using PyPlot
using LaTeXStrings

x = collect(linspace(0.,1.,1000))
y(r) = x.^r
rs = [1/4,1/3,1/2,2/3,3/4,1.]

fig = plt[:figure](figsize=(5.0,4.0))
ax = fig[:subplots](1,1)
fs = 10

for r in rs
    lw = 1
    if r == 1/2
        lw = 3
    end
    ax[:plot](x,y(r),label="$(@sprintf("r=%.2f",r))",lw = lw)
end

ax[:set_title](L"$y=x^r$ for different $r$",fontsize=fs)
ax[:set_xlabel](L"x",fontsize=fs)
ax[:set_ylabel](L"y=x^r",fontsize=fs)
ax[:legend]()

savefig("figs/power_comparison.pdf",dpi=300,transparent=true)


# Verify discretization technique by simulating income generating process
# and markov chain simulation for T_ret periods and then comparing distributions

using Plots, LinearAlgebra, Statistics, Random, Roots, QuantEcon, Interpolations
using BenchmarkTools

# Simulate income generating process
# Parameters
## Demographics
N = 1000000
T_ret = 10
κ = zeros(T_ret)

# Directly from KV2010
σ_η = 0.01
σ_z0 = 0.15
σ_ε = 0.05

function net_labor_income(N, T_ret, σ_ε, σ_η, σ_z0, κ)
    Y = zeros(Float64, N, T_ret-1)
    z_t1 = sqrt(σ_z0) .* randn(N)

    for t in 1:(T_ret-1)
        z_t = z_t1 + sqrt(σ_η) .* randn(N)
        Y[:,t] = exp.( κ[t] .+ z_t + sqrt(σ_ε) .* randn(N) )
        z_t1 = copy(z_t)
    end
    return Y
end

Y_igp = net_labor_income(N, T_ret, σ_ε, σ_η, σ_z0, κ)
histogram(Y_igp[:,1], alpha = 0.3, bins = 100)
histogram!(Y_igp[:,end], alpha = 0.3, bins = 100)
means = mean(Y_igp,dims=1)'
vars = var(Y_igp,dims=1)'
plot(1:(T_ret-1), means, label="means")
plot!(1:(T_ret-1), vars, label="vars", xlabel="t")

# Tauchen discretization
tauc_ε = tauchen(19,0.,sqrt(σ_ε))
tauc_η = tauchen(39,0.99999,sqrt(σ_η))

tauc_ε.p


module DGHeatEquationTests

using Gridap
using ForwardDiff
using LinearAlgebra
using Test
using GridapODEs.ODETools
using GridapODEs.TransientFETools
using Gridap.FESpaces: get_algebraic_operator

θ = 0.2

u(x,t) = (1.0-x[1])*x[1]*(1.0-x[2])*x[2]*t
u(t::Real) = x -> u(x,t)
f(t) = x -> ∂t(u)(x,t)-Δ(u(t))(x)

L= 1.0
n = 2
domain = (0,L,0,L)
partition = (n,n)
model = CartesianDiscreteModel(domain,partition)

order = 2

reffe = ReferenceFE(lagrangian,Float64,order)
V0 = FESpace(
  model,
  reffe,
  conformity=:L2
)
U = TransientTrialFESpace(V0)

Ω = Triangulation(model)
degree = 2*order
dΩ = Measure(Ω,degree)

Γ = BoundaryTriangulation(model)
dΓ = Measure(Γ,degree)
nb = get_normal_vector(Γ)

Λ = SkeletonTriangulation(model)
dΛ = Measure(Λ,degree)
ns = get_normal_vector(Λ)

a(u,v) = ∫(∇(v)⋅∇(u))dΩ
b(v,t) = ∫(v*f(t))dΩ
m(u,v) = ∫(v*u)dΩ

h = 1.0 / n
γ = order*(order+1)
a_Γ(u,v) = ∫( (γ/h)*v*u - v*(∇(u)⋅nb) - (∇(v)⋅nb)*u )dΓ
b_Γ(v,t) = ∫( (γ/h)*v*u(t) - (∇(v)⋅nb)*u(t) )dΓ

a_Λ(u,v) = ∫( (γ/h)*jump(v*ns)⊙jump(u*ns) - jump(v*ns)⊙mean(∇(u)) - mean(∇(v))⊙jump(u*ns) )dΛ

res(t,u,v) = a(u,v) + m(∂t(u),v) + a_Γ(u,v) + a_Λ(u,v) - b(v,t) - b_Γ(v,t)
jac(t,u,du,v) = a(du,v) + a_Γ(du,v) + a_Λ(du,v)
jac_t(t,u,dut,v) = m(dut,v)

op = TransientFEOperator(res,jac,jac_t,U,V0)

t0 = 0.0
tF = 1.0
dt = 0.1

U0 = U(0.0)
uh0 = interpolate_everywhere(u(0.0),U0)

ls = LUSolver()
using Gridap.Algebra: NewtonRaphsonSolver
ode_solver = ThetaMethod(ls,dt,θ)

sol_t = solve(ode_solver,op,uh0,t0,tF)

l2(w) = w*w

tol = 1.0e-6
_t_n = t0

for (uh_tn, tn) in sol_t
  global _t_n
  _t_n += dt
  e = u(tn) - uh_tn
  el2 = sqrt(sum( ∫(l2(e))dΩ ))
  @test el2 < tol
end

end #module


@testset "GramMatrix tests" begin
    @testset "GramMatrix" begin
        @polyvar x y
        P = GramMatrix{Int}((i,j) -> i + j, [x^2, x*y, y^2])
        @test nvariables(P) == 2
        @test variables(P) == [x, y]
        Pp1 = 2x^4 + 6x^3*y + 12x^2*y^2 + 10x*y^3 + 6y^4 + 1
        @test P + 1   == Pp1
        @test P + 1.0 == Pp1
        zP = zero(typeof(P))
        @test isempty(zP.Q)
        @test zP == 0
        p = polynomial(P)
        @test coefficients(p) == [2, 6, 12, 10, 6]
        @test monomialtype(p) == typeof(x*y)
        @test monomialtype(typeof(p)) == typeof(x*y)
        @test monomials(p) == [x^4, x^3*y, x^2*y^2, x*y^3, y^4]
        for i in 1:3
            for j in 1:3
                @test P[i, j] == i + j
            end
        end
        for P in (GramMatrix((i,j) -> i * j, [y, x]),
                  GramMatrix((i,j) -> (3-i) * (3-j), monovec([y, x])),
                  GramMatrix([1 2; 2 4], [y, x]),
                  GramMatrix([4 2; 2 1], monovec([y, x])))
            @test P.Q.Q == [4, 2, 1]
            @test P.x[1] == x
            @test P.x[2] == y
        end
        P = GramMatrix((i,j) -> ((i,j) == (1,1) ? 2 : 0), [x*y, x^2, y^2])
        Q = GramMatrix([0 1; 1 0], [x^2, y^2])
        @test P == Q
        p = GramMatrix([2 3; 3 2], [x, y])
        @test polynomial(p) isa AbstractPolynomial{Int}
        @test polynomial(p, Float64) isa AbstractPolynomial{Float64}

        @test -2*x + dot(-x - x^2, 0) + GramMatrix{Int}((i,j)->1, [1,x]) == -(-x^2 - 1)
        P = GramMatrix{Int}((i,j) -> i + j, [x^2, x*y, y^2])
        p = polynomial(P)
        @test !iszero(P)
        @test iszero(P-P)
        @test iszero(P-p)
        @test iszero(p-P)
        @test P + P == P + p
        @test x * P == x * p
        @test 2 * P == 2 * p
        @test P * (2x) == (2x) * p

        @test differentiate(GramMatrix{Int}((i,j)->1, [x]), y) == 0
        @test differentiate(GramMatrix{Int}((i,j)->1, [x, y]), y, 1) == 2x + 2y
        #@inferred differentiate(GramMatrix{Int}((i,j)->1, [x, y]), y, 0) # FIXME failing at the moment

        @test GramMatrix([2 3; 3 2], [x, y]) == 2x^2 + 2y^2 + 6x*y

        mp = GramMatrix{Int}((i,j) -> i+j, [x])
        @test norm(mp) == norm(mp, 2) == 2.0

        @polyvar v[1:3]
        P = [1 2 3; 2 4 5; 3 5 6]
        p = GramMatrix(P, v)
        #@inferred p(x => ones(3))
        @test p(v => ones(3)) == 31
        #@inferred subs(p, x => ones(3))
        @test subs(p, v => ones(3)) == 31
    end
    @testset "SOSDecomposition" begin
        @polyvar x y
#       @test isempty(SOSDecomposition(typeof(x)[]))
#       ps = [1, x + y, x^2, x*y, 1 + x + x^2]
#       P = GramMatrix(SOSDecomposition(ps))
#       P.Q == [2 0 1 0 1; 0 1 0 0 0; 1 0 2 1 1; 0 0 1 1 0; 1 0 1 0 2]
#       P.x == [x^2, x*y, x, y, 1]
#       @test P == P
#       @test isapprox(GramMatrix(SOSDecomposition(P)), P)
        @test sprint(show, SOSDecomposition([x+y, x-y])) == "(x + y)^2 + (x - y)^2"
        @testset "SOSDecomposition equality" begin
            @polyvar x y
            @test !isapprox(SOSDecomposition([x+y, x-y]), SOSDecomposition([x+y]))
            @test !isapprox(SOSDecomposition([x+y, x-y]), SOSDecomposition([x+y, x+y]))
            @test isapprox(SOSDecomposition([x+y, x-y]), SOSDecomposition([x+y, x-y]))
            @test isapprox(SOSDecomposition([x+y, x-y]), SOSDecomposition([x-y, x+y+1e-8]), ztol=1e-7)
        end
    end
end


```
Central difference

```
function central_diff(
    y::AbstractArray{<:AbstractFloat,1},
    x::AbstractArray{<:AbstractFloat,1},
)

    dy = zeros(eltype(y), axes(y))

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    dy[i0] = (y[i0+1] - y[i0]) / (x[i0+1] - x[i0])
    dy[i1] = (y[i1] - y[i1-1]) / (x[i1] - x[i1-1])
    for i = i0+1:i1-1
        dy[i] = (y[i+1] - y[i-1]) / (x[i+1] - x[i-1])
    end

    return dy

end

function central_diff(y::AbstractArray{<:AbstractFloat,1}, dx::Real)
    x = ones(eltype(y), axes(y)) .* dx
    dy = central_diff(y, x)

    return dy
end

function central_diff!(
    dy::AbstractArray{<:AbstractFloat,1},
    y::AbstractArray{<:AbstractFloat,1},
    x::AbstractArray{<:AbstractFloat,1},
)

    @assert axes(dy) == axes(y) == axes(x)

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    dy[i0] = (y[i0+1] - y[i0]) / (x[i0+1] - x[i0])
    dy[i1] = (y[i1] - y[i1-1]) / (x[i1] - x[i1-1])
    for i = i0+1:i1-1
        dy[i] = (y[i+1] - y[i-1]) / (x[i+1] - x[i-1])
    end

end

function central_diff!(
    dy::AbstractArray{<:AbstractFloat,1},
    y::AbstractArray{<:AbstractFloat,1},
    dx::Real,
)
    x = ones(eltype(y), axes(y)) .* dx
    central_diff!(dy, y, x)
end

```
Upwind difference

```
function upwind_diff(
    y::AbstractArray{<:Real,1},
    x::AbstractArray{<:Real,1};
    stream = :right::Symbol,
)

    dy = zeros(eltype(y), axes(y))

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    if stream == :right
        dy[i0] = 0.0
        for i = i0+1:i1
            dy[i] = (y[i] - y[i-1]) / (x[i] - x[i-1])
        end
    elseif stream == :left
        dy[i1] = 0.0
        for i = i0:i1-1
            dy[i] = (y[i+1] - y[i]) / (x[i+1] - x[i])
        end
    else
        throw("streaming direction should be :left or :right")
    end

    return dy

end

function upwind_diff(y::AbstractArray{<:AbstractFloat,1}, dx::Real; stream = :right::Symbol)
    x = ones(eltype(y), axes(y)) .* dx
    dy = upwind_diff(y, x, stream = stream)

    return dy
end

function upwind_diff!(
    dy::AbstractArray{<:AbstractFloat,1},
    y::AbstractArray{<:AbstractFloat,1},
    x::AbstractArray{<:AbstractFloat,1};
    stream = :right::Symbol,
)

    @assert axes(dy) == axes(y) == axes(x)

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    if stream == :right
        dy[i0] = 0.0
        for i = i0+1:i1
            dy[i] = (y[i] - y[i-1]) / (x[i] - x[i-1])
        end
    elseif stream == :left
        dy[i1] = 0.0
        for i = i0:i1-1
            dy[i] = (y[i+1] - y[i]) / (x[i+1] - x[i])
        end
    else
        throw("streaming direction should be :left or :right")
    end

    return dy

end

function upwind_diff!(
    dy::AbstractArray{<:AbstractFloat,1},
    y::AbstractArray{<:AbstractFloat,1},
    dx::Real;
    stream = :right::Symbol,
)
    x = ones(eltype(y), axes(y)) .* dx
    upwind_diff!(dy, y, x, stream = stream)
end


# ---
# title: 1680. Concatenation of Consecutive Binary Numbers
# id: problem1680
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Math
# link: <https://leetcode.com/problems/concatenation-of-consecutive-binary-numbers/description/>
# hidden: true
# ---
#
# Given an integer `n`, return _the **decimal value** of the binary string
# formed by concatenating the binary representations of _`1` _to_`n` _in order,
# **modulo**_`109 + 7`.
#
#
#
# **Example 1:**
#
#
#
#     Input: n = 1
#     Output: 1
#     Explanation: "1" in binary corresponds to the decimal value 1.
#
#
# **Example 2:**
#
#
#
#     Input: n = 3
#     Output: 27
#     Explanation: In binary, 1, 2, and 3 corresponds to "1", "10", and "11".
#     After concatenating them, we have "11011", which corresponds to the decimal value 27.
#
#
# **Example 3:**
#
#
#
#     Input: n = 12
#     Output: 505379714
#     **Explanation** : The concatenation results in "1101110010111011110001001101010111100".
#     The decimal value of that is 118505380540.
#     After modulo 109 + 7, the result is 505379714.
#
#
#
#
# **Constraints:**
#
#   * `1 <= n <= 105`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


"""
    laplace_to_z(rho, n, N, dt, A, b)

Returns the complex matrix valued Laplace variable s that correspond to the
variable z = rho*exp(2*im*pi*n/N) for a given Butcher tableau (A,b,c) and a time step dt.
"""
function laplace_to_z(rho, n, N, dt, A, b)
	z = rho * exp(2*im*pi*n/N);
	s = inv(dt * (A + ones(b) * b' / (z-1)));
	return s;
end

"""
    inverse_z_transform(k, rho, N, X)

Returns the k-th term of the inverse z-transform. X is an array of the z-transform
evaluated in the points z=rho*exp(2*im*pi*n/N) for n in 0:(N-1).
"""
function inverse_z_transform(k, rho, N, X::AbstractArray{T,1}) where T
	return ((rho^k) / N) * sum(n -> X[n+1] * exp(2*im*pi*k*n/N), 0:(N-1));
end

"""
    real_inverse_z_transform(k, rho, N, X)

Returns the k-th term of the inverse z-transform.
It is assumed that X[n+1] = conj(X[N-n]) for each n in 1:(N-1)
so that Nmax = N/2+1 or (N+1)/2 (resp. if N%2==0 or N%2==1) terms are used in X
X is an array of the z-transform
evaluated in the points z=rho*exp(2*im*pi*n/N) for n in 0:(Nmax-1).
"""
function real_inverse_z_transform(k, rho, N, X::AbstractArray{T,1}) where T
	Nmax = (N+1)>>1;
	realTerms = (N%2==0) ? real(X[1]) + (-1)^k * real(X[Nmax+1]) : real(X[1]);
	return ((rho^k) / N) * (realTerms + 2*sum(n -> real(X[n+1] * exp(2*im*pi*k*n/N)), 1:(Nmax-1)));
end


using PyPlot, PyCall

np = pyimport("numpy")

origin = "lower"

delta = 0.025

x = y = np.arange(-3.0, 3.01, delta)
X, Y = np.meshgrid(x, y)
Z1 = @. np.exp(-X^2 - Y^2)
Z2 = @. np.exp(-(X - 1)^2 - (Y - 1)^2)
Z = (Z1 .- Z2) .* 2

nr, nc = size(Z)

# mask a circle in the middle:
interior = @. sqrt(X^2 + Y^2) < 0.5
Z[interior] .= NaN

# We are using automatic selection of contour levels;
# this is usually not such a good idea, because they don't
# occur on nice boundaries, but we do it here for purposes
# of illustration.

fig1, ax2 = plt.subplots(constrained_layout=true)
CS = ax2.contourf(X, Y, Z, 10, cmap=plt.cm.bone, origin=origin)

# Note that in the following, we explicitly pass in a subset of
# the contour levels used for the filled contours.  Alternatively,
# We could pass in additional levels to provide extra resolution,
# or leave out the levels kwarg to use all of the original levels.

CS2 = ax2.contour(CS, levels=CS.levels[1:2:end], colors="r", origin=origin)

ax2.set_title("1 masked region")
ax2.set_xlabel("word length anomaly")
ax2.set_ylabel("sentence length anomaly")

# Make a colorbar for the ContourSet returned by the contourf call.
cbar = fig1.colorbar(CS)
cbar.ax.set_ylabel("verbosity coefficient")
# Add the contour line levels to the colorbar
cbar.add_lines(CS2)

plt.show()


using ImageFeatures

function random_circle_example()
    dims = rand(100:200,2)
    img = zeros(dims...)
    r = rand(10:20)
    x,y = rand(20:80,2)
    for i in axes(img)[1]
        for j in axes(img)[2]
            if (x-i)^2 + (y-j)^2 <= r^2
                img[i,j] = 1
            end
        end
    end
    img, [x,y], r
end

@testset "Hough_Transform" begin
    @testset "Hough Line Transform" begin

    #For images containing a straight line parallel to axes
        for i in 1:9
            img = zeros(Bool,9,9)
            img[i,:] .= true
            h = hough_transform_standard(img)
            @test length(h) == 1
            line = first(h)
            @test line == (i, pi/2)
        end

    #For images with diagonal line
        img = Matrix(Diagonal([true, true ,true]))
        h = hough_transform_standard(img, angles=range(0,stop=pi,length=100))
        @test length(h) == 1
        @test h[1][1] == 0

    #For a square image
        img = zeros(Bool,10,10)
        for i in 1:10
            img[2,i] = img[i,2] = img[7,i] = img[i,9] = true
        end
        # h = hough_transform_standard(img,1,
        h = hough_transform_standard(img, angles=range(0,stop=π/2,length=100))
        @test length(h) == 4
        r = [h[i][1] for i in CartesianIndices(size(h))]
        @test all(r .== [2,2,7,9])
        theta = [h[i][2] for i in CartesianIndices(size(h))]
        er = sum(map((t1,t2) -> abs(t1-t2), theta, [0, π/2, π/2, 0]))
        @test er <= 0.1
    end

    @testset "Hough Circle Gradient" begin
        for _ in 1:10
            img, c_truth, r_truth = random_circle_example()
            img_edges = canny(img, (Percentile(80), Percentile(20)))
            dx, dy=imgradients(img, KernelFactors.ando5)
            img_phase = phase(dx, dy)
            radii = 10:20
            centers, rs = hough_circle_gradient(img_edges, img_phase, radii)
            @test length(centers) == length(rs) == 1
            c_hough = [Tuple(first(centers))...]
            r_hough = first(rs)
            @test r_hough ≈ r_truth atol=4
            @test c_hough ≈ c_truth atol=4
        end
    end
end


@doc raw"""
PathIntegralTrigonometric is a path integral along a cos^2/sin^2 path

```math
\phi (\tau; q^-, q^+) = \cos^2 (\pi \tau / 2) q^- + \sin^2 (\pi \tau / 2) q^+ .
```
"""
struct PathIntegralTrigonometric <: PathIntegral end

evaluate_l(path::PathIntegralTrigonometric, τ::T) where {T} = cos(π/2*τ)^2
evaluate_r(path::PathIntegralTrigonometric, τ::T) where {T} = sin(π/2*τ)^2

derivative_l(path::PathIntegralTrigonometric, τ::T) where {T} = -π*sin(π/2*τ)*cos(π/2*τ)
derivative_r(path::PathIntegralTrigonometric, τ::T) where {T} = +π*sin(π/2*τ)*cos(π/2*τ)


export awa

"""
    awa(twa, v_s, v_t)

Calculate the apparent wind angle given true wind angle, boat speed and wind speed.

# Example

```jldoctest
using sail_route
c_awa = sail_route.awa(60, 3.086, 5.144)
isapprox(0.6669807044553968, c_awa, rtol=3)

# output

true
```
"""
function awa(twa::Float64, v_s::Float64, v_t::Float64)
    return atan(sind(twa)/(cosd(twa) + v_s/v_t))
end


# Distance metrics for kernel functions

δ(Φ, z) = diff(Φ) ./ diff(z)

"""
    sq_mag(a, b)

||a - b||^2 = norm(a - b)^2 but more efficient.
"""
function sq_mag(a, b)
    ll = 0.0
    @inbounds for k in 1:length(a)
        ll += (a[k] - b[k])^2
    end
    return ll
end

"""
    euclidean_distance(a, b, z)

Computes the Euclidean distance (l²-norm) between two vectors:

    d(x, x') = || x - x' ||
"""
@inline euclidean_distance(a, b, z) = sqrt(sq_mag(a, b))

@inline euclidean_distance(a, b) = sqrt(sq_mag(a, b))

"""
    derivative_distance(a, b, z)

Computes the H¹-norm with respect to z of two vectors:

    d(x, x') = || diff(x) / diff(z) - diff(x') / diff(z) ||
"""
@inline derivative_distance(a, b, z) = sqrt(sq_mag( δ(a, z), δ(b, z) ))

"""
    antiderivative_distance(a, b, z)

Computes the H⁻¹-norm with respect to z of two vectors:

    d(x, x′) = || diff(x) * diff(z) - diff(x') * diff(z) ||
"""
@inline antiderivative_distance(a,b,z) = sqrt(sq_mag(diff(a) .* diff(z), diff(b) .* diff(z)))


include("misc.jl")

function leastSquares(X,y)

	# Add bias column
	n = size(X,1)
	Z = [ones(n,1) X]

	# Find regression weights minimizing squared error
	w = (Z'*Z)\(Z'*y)

	# Make linear prediction function
	predict(Xtilde) = [ones(size(Xtilde,1),1) Xtilde]*w

	# Return model
	return LinearModel(predict,w)
end


module Wynn
    using SymPy

    # data structure to store the epsilon table
    # ϵ_ij indexed by ints i & j, stored within dict.
    struct EpsilonTable{T}
        series::T
        terms::Vector{T}
        etable::Dict{Tuple{Int,Int},T}
    end

    function EpsilonTable(terms::Vector{T}; simplified::Bool = true) where T<:Union{Float64,Sym}
        # maximum occuring i & j index
        max_ind = length(terms)

        # setting base case j = -1
        etable = Dict((i, -1) => eltype(terms)(0) for i in 0:max_ind)

        # setting base case j = 0, i = 0:max_ind-1
        merge!(etable, Dict((i-1, 0) => sum(terms[1:i]) for i in 1:max_ind))

        # setting base case i = -j-1, j even
        merge!(etable, Dict((-j-1, 2j) => eltype(terms)(0) for j in 0:max_ind))

        # setting base case for odd j
        merge!(etable, Dict((-j-1, 2j-1) => eltype(terms)(0) for j in 0:max_ind))

        # recursive calculations, j>=1, i = floor(Int,-j/2):(max_ind-j-1)
        for j in 1:2*(max_ind-1), i in floor(Int,-j/2):(max_ind-j-1)#j-1
            ϵ_ij = etable[i+1, j-2] + 1 / (etable[i+1, j-1] - etable[i, j-1])
            eltype(terms) <: Sym && simplified ? push!(etable, (i, j) =>
                simplify(ϵ_ij)) : push!(etable, (i, j) => ϵ_ij)
        end
        series = sum(terms)
        EpsilonTable(series, terms, etable)
    end

    EpsilonTable(terms::Vector{Int}; simplified::Bool = true) = EpsilonTable(convert(Vector{Float64}, terms); simplified = simplified)

    # recursive function to generate just one term of the epsilon table, much more efficient.
    function epsilon(terms::Vector{T}, order::Tuple{Int,Int}; simplified::Bool = true) where T<:Union{Real,Sym}
        max_ind = length(terms)
        if order[2] == -1
            return 0
        elseif (0 <= order[1] < max_ind) && (order[2] == 0)
            return sum(terms[1:order[1]+1])
        elseif iseven(order[2]) && (order[1] == -order[2]/2-1)
            return 0
        elseif isodd(order[2]) && (order[1] == -(order[2]+1)/2-1)
            return 0
        end
        max_ind = length(terms)
        ϵ_order = epsilon(terms, (order[1]+1, order[2]-2)) + 1 / (epsilon(terms, (order[1]+1, order[2]-1)) - epsilon(terms, (order[1], order[2]-1)))
        if (eltype(terms) <: Sym) && simplified
            return simplify(ϵ_order)
        else
            return ϵ_order
        end
    end

    export EpsilonTable, epsilon
end


"""
ap_test_fm(re, factors)

CROSS-SECTIONAL TESTS USING THE FAMA-MACBETH REGRESSION

Fama-MacBeth standard errors do not include corrections for the fact that the betas are also estimated.

INPUTS
`re': T x N matrix of excess returns, where T is the number of periods for each test assets and N is the number of test assets
`factors': T x K matrix of factors. Factors must be excess returns for time-series tests

OUTPUTS
`lambda': T x K vector of risk premiums for the factors
`alpha': T x N vector of alphas for each test assets

`test_statistics': Wald-statistics of the asset pricing test using Fama-MacBeth procedure
`pvalue': pvalue of the test statistics. The test statistics has a Chi-square distribution.

"""

function ap_test_fm(re, factors)
    (T, K) = size(factors);
    N = size(re, 2);
    cov_f = cov(factors); # coviance matrix for the factors K x K

    X = [ones(T,1) factors];
    (a, beta, resid, cov_resid) = OLSFn(re, X) # First-stage time-series regressions to obatin betas N x K

    lambda = fill(NaN, (T, K))
    alpha = fill(NaN, (T, N))

    for t = 1:T # run cross-sectional regressions for each period
        lambda[t, :] = ((beta' * beta) \  (beta' * vec(re[t, :])))'; # 1 x K
        alpha[t, :] = re[t, :]' - lambda[t, :]' * beta';
    end

    alpha_mean = mean(alpha, dims = 1)'; # time-series average of alphas N x 1
    alpha_mean_cov = zeros(N, N);

    for t = 1:T
        alpha_mean_cov = alpha_mean_cov + (alpha[t, :] - alpha_mean) * (alpha[t, :] - alpha_mean)';
    end
    alpha_mean_cov = alpha_mean_cov ./ T^2;

    test_statistics = alpha_mean' * (alpha_mean_cov \ alpha_mean);
    pvalue = 1.0 .- cdf.(Chisq(N - K), test_statistics);

    return (alpha_mean, test_statistics, pvalue)
end


# This script solves a parabolic partial differential equation

using LinearAlgebra

# Define the paths
CURRENT_DIR = @__DIR__
ROOT_DIR = basename(CURRENT_DIR) == "scripts" ? dirname(CURRENT_DIR) : CURRENT_DIR
TABLES = joinpath(ROOT_DIR, "tables")

# Prepare the output directories
mkpath(TABLES)

# Define the maximum time
T = 0.1

# Define the differential equation's functions
p(x) = x + 3
b(_, _) = 0
c(x, _) = -x
α₁(t) = 0
α₂(t) = -1
β₁(t) = 1
β₂(t) = 0

# Define the first problem's functions
u(x, t) = x + 3t
f(x, t) = x^2 + 3 * x * t + 2
φ(x) = x
α(t) = 1
β(t) = 1 + 3t

"Check if a solution will be stable on the specified grid"
function is_stable(N, M)::Bool
    A = maximum(p, 0:0.0001:1)
    h = 1 / N
    τ = T / M
    ν = τ / h^2
    return A * ν ≤ 0.5
end

"Using provided N, find M, which gives a stable solution"
function find_stable(N)::Int
    M = 5
    while !is_stable(N, M)
        M *= 2
    end
    return M
end

"Calculate the value of the differential operator L"
function L(um, x, t, h, i, k)::Float64
    return p(x[i] + h / 2) * (um[k, i+1] - um[k, i]) / h^2 -
           p(x[i] - h / 2) * (um[k, i] - um[k, i-1]) / h^2 +
           b(x[i], t[k]) * (um[k, i+1] - um[k, i-1]) / (2h) +
           c(x[i], t[k]) * um[k, i]
end

"Solve the problem for the specified grid, return the solution matrix"
function solve(N, M, h, τ, x, t; σ = 0)::Matrix{Float64}
    # Prepare a matrix for the solution
    # (number of columns is the number of nodes for x's)
    um = Matrix{Float64}(undef, M + 1, N + 1)
    # Compute the first layer
    um[1, :] .= φ.(x)
    if σ == 0
        # Use the explicit scheme for other layers
        for k = 2:M+1
            # Compute for i in 2:N
            for i = 2:N
                um[k, i] = um[k-1, i] +
                           τ * (L(um, x, t, h, i, k - 1) +
                                f(x[i], t[k-1]))
            end
            # Compute the rest
            um[k, 1] = (α(t[k]) + α₂(t[k]) * (4 * um[k, 2] - um[k, 3]) / (2h)) /
                       (α₁(t[k]) + 3 * α₂(t[k]) / (2h))
            um[k, N+1] = (β(t[k]) + β₂(t[k]) * (4 * um[k, N] - um[k, N-1]) / (2h)) /
                         (β₁(t[k]) + 3 * β₂(t[k]) / (2h))
        end
    else
        # Use the implicit scheme for other layers
        for k = 2:M+1
            # Compute the linear system's matrix
            tm = Tridiagonal(
                # A's
                [
                    [σ * (p(x[i] - h / 2) / h^2 - b(x[i], t[k]) / (2h)) for i = 2:N]
                    -β₂(t[k]) / h
                ],
                # B's
                [
                    α₁(t[k]) + α₂(t[k]) / h
                    -[1 / τ + σ * ((p(x[i] + h / 2) + p(x[i] - h / 2)) / h^2 - c(x[i], t[i])) for i = 2:N]
                    β₁(t[k]) + β₂(t[k]) / h
                ],
                # C's
                [
                    -α₂(t[k]) / h
                    [σ * (p(x[i] + h / 2) / h^2 + b(x[i], t[k]) / (2h)) for i = 2:N]
                ],
            )
            # Compute the linear system's right-hand side vector
            g = [
                α(t[k])
                [-um[k-1, i] / τ - (1 - σ) * L(um, x, t, h, i, k - 1) - f(x[i], t[k] - (1 - σ) * τ) for i = 2:N]
                β(t[k])
            ]
            # Compute the solution of the linear system
            um[k, :] = tm \ g
        end
    end
    return um
end

"""
Compare the approximate solution to the exact one,
find the biggest absolute difference between the nodes
"""
function max_difference(um, x, t)::Float64
    M1, N1 = size(um)
    # Find the maximum
    Δu = 0
    for i = 1:N1, k = 1:M1
        _Δu = abs(u(x[i], t[k]) - um[k, i])
        if _Δu > Δu
            Δu = _Δu
        end
    end
    return Δu
end

"Create the TeX tables and write them to disk"
function tables(dir, digits, um, N, M)
    # Prepare the output directories
    OUTPUT_DIR = joinpath(TABLES, dir)
    mkpath(OUTPUT_DIR)
    # Create and write the table with the solution grid
    open(joinpath(OUTPUT_DIR, "$N, $M.tex"), "w") do io
        for k in Int.([1:M/5:M+1]...)
            s = ""
            for i in Int.([1:N/5:N+1]...)
                s = "$(s)& \$ $(sprint(show, round(um[k, i]; digits))) \$ "
            end
            s = "$(s)\\\\"
            println(io, s)
        end
    end
end

"""
Solve the specified problem for different grids,
create the TeX tables and write them to disk
"""
function solve_all(dir, digits)
    # Print the output directory
    println('\n', " "^5, "> Output for u(x, t) = ", dir)
    # Define grids
    N = [5, 10, 20]
    Mₑ = find_stable.(N)
    Mᵢ = repeat([Int(1 / (0.1 / 100))], length(N))
    # For each grid
    for l in eachindex(N)
        # Compute the steps
        h = 1 / N[l]
        τₑ = T / Mₑ[l]
        τᵢ = T / Mᵢ[l]
        # Compute the nodes
        x = [j * h for j = 0:N[l]]
        tₑ = [j * τₑ for j = 0:Mₑ[l]]
        tᵢ = [j * τᵢ for j = 0:Mᵢ[l]]
        # Solve the problem with the explicit scheme
        um = solve(N[l], Mₑ[l], h, τₑ, x, tₑ)
        # Print info about the solution
        println(
            '\n',
            " "^5, "σ: 0", '\n',
            " "^5, "N: ", N[l], '\n',
            " "^5, "M: ", Mₑ[l], '\n',
            " "^5, "Δu: ", max_difference(um, x, tₑ)
        )
        # Create and write the tables
        tables(joinpath(dir, "σ = 0"), digits, um, N[l], Mₑ[l])
        # Solve the problem with the implicit scheme (σ = 0.5)
        um = solve(N[l], Mᵢ[l], h, τᵢ, x, tᵢ; σ = 0.5)
        # Print info about the solution
        println(
            '\n',
            " "^5, "σ: 0.5", '\n',
            " "^5, "N: ", N[l], '\n',
            " "^5, "M: ", Mᵢ[l], '\n',
            " "^5, "Δu: ", max_difference(um, x, tᵢ)
        )
        # Create and write the tables
        tables(joinpath(dir, "σ = 0.5"), digits, um, N[l], Mᵢ[l])
        # Solve the problem with the implicit scheme (σ = 1)
        um = solve(N[l], Mᵢ[l], h, τᵢ, x, tᵢ; σ = 1.0)
        # Print info about the solution
        println(
            '\n',
            " "^5, "σ: 1.0", '\n',
            " "^5, "N: ", N[l], '\n',
            " "^5, "M: ", Mᵢ[l], '\n',
            " "^5, "Δu: ", max_difference(um, x, tᵢ)
        )
        # Create and write the tables
        tables(joinpath(dir, "σ = 1.0"), digits, um, N[l], Mᵢ[l])
    end
end

# Solve the first problem
solve_all("x + 3t", 2)

# Define the second problem's functions
u(x, t) = x^3 + t^3
f(x, t) = x^4 + x * t^3 - 9 * x^2 + 3 * t^2 - 18 * x
φ(x) = x^3
α(t) = 0
β(t) = 1 + t^3

# Solve the second problem
solve_all("x^3 + t^3", 6)

println()


# Julia GARCH package
# Copyright 2013 Andrey Kolev
# Distributed under MIT license (see LICENSE.md)

type GarchFit
  data::Vector
  params::Vector
  llh::Float64
  status::Symbol
  converged::Bool
  sigma::Vector
  hessian::Array{Float64,2}
  cvar::Array{Float64,2}
  secoef::Vector
  tval::Vector
end

function Base.show(io::IO ,fit::GarchFit)
  pnorm(x) = 0.5*(1+erf(x/sqrt(2)))
  prt(x) = 2*(1-pnorm(abs(x)))
  @printf io "Fitted garch model \n"
  @printf io " * Coefficient(s): \tomega \t\talpha \t\tbeta\n"
  @printf io "   \t\t\t%f\t%f\t%f\n" fit.params[1] fit.params[2] fit.params[3]
  @printf io " * Log Likelihood: %f\n" fit.llh
  @printf io " * Converged: %s\n" fit.converged
  @printf io " * Solver status: %s\n\n" fit.status
  println(io," * Standardised Residuals Tests:")
  println(io,"   \t\t\t\tStatistic\tp-Value")
  jbstat,jbp = jbtest(fit.data./fit.sigma);
  @printf io "   Jarque-Bera Test\t\U1D6D8\u00B2\t%.6f\t%.6f\n\n" jbstat jbp
  println(io," * Error Analysis:")
  println(io,"   \t\tEstimate\t\Std.Error\tt value \tPr(>|t|)")
  @printf io "   omega\t%f\t%f\t%f\t%f\n" fit.params[1] fit.secoef[1] fit.tval[1] prt(fit.tval[1])
  @printf io "   alpha\t%f\t%f\t%f\t%f\n" fit.params[2] fit.secoef[2] fit.tval[2] prt(fit.tval[2])
  @printf io "   beta \t%f\t%f\t%f\t%f\n"  fit.params[3] fit.secoef[3] fit.tval[3] prt(fit.tval[3])
end

function cdHessian(par,LLH)
  eps = 1e-4 * par
  n = length(par)
  H = zeros(n,n)
  for(i = 1:n)
    for(j = 1:n)
      x1 = copy(par)
      x1[i] += eps[i]
      x1[j] += eps[j]
      x2 = copy(par)
      x2[i] += eps[i]
      x2[j] -= eps[j]
      x3 = copy(par)
      x3[i] -= eps[i]
      x3[j] += eps[j]
      x4 = copy(par)
      x4[i] -= eps[i]
      x4[j] -= eps[j]
      H[i,j] = (LLH(x1)-LLH(x2)-LLH(x3)+LLH(x4)) / (4.*eps[i]*eps[j])
    end
  end
  H
end

function garchLLH(rets::Vector,x::Vector)
  rets2   = rets.^2;
  T = length(rets);
  ht = zeros(T);
  omega,alpha,beta = x;
  ht[1] = sum(rets2)/T;
  for i=2:T
    ht[i] = omega + alpha*rets2[i-1] + beta * ht[i-1];
  end
  -0.5*(T-1)*log(2*pi)-0.5*sum( log(ht) + (rets./sqrt(ht)).^2 );
end

function predict(fit::GarchFit)
 omega, alpha, beta = fit.params;
 rets = fit.data
 rets2   = rets.^2;
 T = length(rets);
 ht    = zeros(T);
 ht[1] = sum(rets2)/T;
 for i=2:T
    ht[i] = omega + alpha*rets2[i-1] + beta * ht[i-1];
 end
 sqrt(omega + alpha*rets2[end] + beta*ht[end]);
end

function garchFit(data::Vector)
  rets = data
  rets2   = rets.^2;
  T = length(rets);
  ht = zeros(T);
  function garchLike(x::Vector, grad::Vector)
    omega,alpha,beta = x;
    ht[1] = sum(rets2)/T;
    for i=2:T
      ht[i] = omega + alpha*rets2[i-1] + beta * ht[i-1];
    end
    sum( log(ht) + (rets./sqrt(ht)).^2 );
  end
  opt = Opt(:LN_SBPLX,3)
  lower_bounds!(opt,[1e-10, 0.0, 0.0])
  upper_bounds!(opt,[1; 0.3; 0.99])
  min_objective!(opt, garchLike)
  (minf,minx,ret) = NLopt.optimize(opt, [1e-5, 0.09, 0.89])
  converged = minx[1]>0 && all(minx[2:3].>=0) && sum(minx[2:3])<1.0
  H = cdHessian(minx,x->garchLLH(rets,x))
  cvar = -inv(H)
  secoef = sqrt(diag(cvar))
  tval = minx./secoef
  out = GarchFit(data, minx, -0.5*(T-1)*log(2*pi)-0.5*minf, ret, converged, sqrt(ht),H,cvar,secoef,tval)
end

# function garchPkgTest()
#   println("Running GARCH package test...")
#   try
#     include(Pkg.dir("GARCH", "test","GARCHtest.jl"))
#     println("All tests passed!")
#   catch err
#     throw(err)
#   end
# end


"""
    ellipse(; center::Point, xAxis::Real, yAxis::Real, steps::Integer=64, angle::Real=0., pivot::Union{Point, Nothing}=nothing, units::String="kilometers")

Take a Point and calculates the ellipse polygon given two semi-axes expressed in variable units and steps for precision.

# Examples
```jldoctest
julia> center = Point([10, 45])
Point([10.0, 45.0])

julia> ellipse(center=center, xAxis=20, yAxis=7.5, steps=10)
Polygon(Array{Array{Float64,1},1}[[[10.0872, 45.0634], [10.0218, 45.0672], [9.97817, 45.0672], [9.91279, 45.0634], [9.74563, 45.0], [9.91279, 44.9366], [9.97817, 44.9328], [10.0218, 44.9328], [10.0872, 44.9366], [10.2544, 45.0], [10.0872, 45.0634]]])
```
"""
function ellipse(; center::Point, xAxis::Real, yAxis::Real, steps::Integer=64, angle::Real=0., pivot::Union{Point, Nothing}=nothing, units::String="kilometers")
    pivot == nothing && (pivot = center)

    coords = center.coordinates

    if units === "degrees"
        angle = deg2rad(angle)
    else
        xAxis = rhumb_destination(coords, xAxis, 90, units).coordinates
        yAxis = rhumb_destination(coords, yAxis, 0, units).coordinates
        xAxis = xAxis[1] - coords[1]
        yAxis = yAxis[2] - coords[2]
    end

    elCoords = []

    for i in 1:steps
        stepAngle = i * (-360 / steps)
        x = (xAxis * yAxis) / sqrt(yAxis^2 + xAxis^2 * tan(deg2rad(stepAngle))^2)
        y = (xAxis * yAxis) / sqrt(xAxis^2 + yAxis^2 / tan(deg2rad(stepAngle))^2)

        (stepAngle < -90 && stepAngle >= -270) && (x = -x)
        (stepAngle < -180 && stepAngle >= -360) && (y = -y)

        if units === "degrees"
            newx = x * cos(angle) + y * sin(angle)
            newy = y * cos(angle) - x * sin(angle)
            x = newx
            y = newy
        end

        push!(elCoords, [x + coords[1], y + coords[2]])
    end
    push!(elCoords, elCoords[1])

    units === "degrees" && return Polygon([elCoords])

    return transform_rotate(geojson=Polygon([elCoords]), angle=angle, pivot=pivot)

end


using SimplePolynomials, BigCombinatorics

function sine_coeffs(n::Int)
    T = Rational{BigInt}
    result = zeros(T,2n)
    for j=1:n
        result[2j] = (-1)^(j-1)//Factorial(2j-1)
    end

    return result
end

"""
`sine_poly(n)` create exact sine power series of degree `2n`
(actually `2n-1` because `sin` is odd).
"""
function sine_poly(n::Int)
    return SimplePolynomial(sine_coeffs(n))
end


using AccretionFormulae
using Plots

function r_hor(a, M)
    (M +  √(M^2 - (a*M)^2))/M
end

M = 10*1.99e30
spin_vals_pos = LinRange(0, 1, 1000)
spin_vals_neg = LinRange(-1, 0 , 1000)
ISCO_pos_vals = AccretionFormulae.r_isco.(spin_vals_pos, M) ./ M
ISCO_neg_vals = reverse(AccretionFormulae.r_isco.(spin_vals_neg, M)) ./ M
EH_pos_vals = r_hor.(spin_vals_pos, M)

plt = plot(
            spin_vals_pos,
            ISCO_pos_vals,
            legend=:topleft,
            label="\$r_{ISCO} \\; \\textrm{Prograde}\$",
            xlabel="a (M)",
            ylabel="R (M)",
            grid=false,
            framestyle=:box
            )
plot!(spin_vals_pos, ISCO_neg_vals, label="\$r_{ISCO} \\; \\textrm{Retrograde}\$")
plot!(spin_vals_pos, EH_pos_vals, label="\$r_{s}\$")
png(plt, "ISCO_EH_spin_comp.png")


## R code 7.3
m7.1 <- quap(
    alist(
        brain_std ~ dnorm( mu , exp(log_sigma) ),
        mu <- a + b*mass_std,
        a ~ dnorm( 0.5 , 1 ),
        b ~ dnorm( 0 , 10 ),
        log_sigma ~ dnorm( 0 , 1 )
    ), data=d )

## R code 7.7
m7.2 <- quap(
    alist(
        brain_std ~ dnorm( mu , exp(log_sigma) ),
        mu <- a + b[1]*mass_std + b[2]*mass_std^2,
        a ~ dnorm( 0.5 , 1 ),
        b ~ dnorm( 0 , 10 ),
        log_sigma ~ dnorm( 0 , 1 )
    ), data=d , start=list(b=rep(0,2)) )

## R code 7.8
m7.3 <- quap(
    alist(
        brain_std ~ dnorm( mu , exp(log_sigma) ),
        mu <- a + b[1]*mass_std + b[2]*mass_std^2 +
                  b[3]*mass_std^3,
        a ~ dnorm( 0.5 , 1 ),
        b ~ dnorm( 0 , 10 ),
        log_sigma ~ dnorm( 0 , 1 )
    ), data=d , start=list(b=rep(0,3)) )

m7.4 <- quap(
    alist(
        brain_std ~ dnorm( mu , exp(log_sigma) ),
        mu <- a + b[1]*mass_std + b[2]*mass_std^2 +
                  b[3]*mass_std^3 + b[4]*mass_std^4,
        a ~ dnorm( 0.5 , 1 ),
        b ~ dnorm( 0 , 10 ),
        log_sigma ~ dnorm( 0 , 1 )
    ), data=d , start=list(b=rep(0,4)) )

m7.5 <- quap(
    alist(
        brain_std ~ dnorm( mu , exp(log_sigma) ),
        mu <- a + b[1]*mass_std + b[2]*mass_std^2 +
                  b[3]*mass_std^3 + b[4]*mass_std^4 +
                  b[5]*mass_std^5,
        a ~ dnorm( 0.5 , 1 ),
        b ~ dnorm( 0 , 10 ),
        log_sigma ~ dnorm( 0 , 1 )
    ), data=d , start=list(b=rep(0,5)) )

## R code 7.9
m7.6 <- quap(
    alist(
        brain_std ~ dnorm( mu , 0.001 ),
        mu <- a + b[1]*mass_std + b[2]*mass_std^2 +
                  b[3]*mass_std^3 + b[4]*mass_std^4 +
                  b[5]*mass_std^5 + b[6]*mass_std^6,
        a ~ dnorm( 0.5 , 1 ),
        b ~ dnorm( 0 , 10 )
    ), data=d , start=list(b=rep(0,6)) )

## R code 7.19
data(cars)
m <- quap(
    alist(
        dist ~ dnorm(mu,sigma),
        mu <- a + b*speed,
        a ~ dnorm(0,100),
        b ~ dnorm(0,10),
        sigma ~ dexp(1)
    ) , data=cars )

m5.1 <- quap(
    alist(
        D ~ dnorm( mu , sigma ) ,
        mu <- a + bA * A ,
        a ~ dnorm( 0 , 0.2 ) ,
        bA ~ dnorm( 0 , 0.5 ) ,
        sigma ~ dexp( 1 )
    ) , data = d )

m5.2 <- quap(
    alist(
        D ~ dnorm( mu , sigma ) ,
        mu <- a + bM * M ,
        a ~ dnorm( 0 , 0.2 ) ,
        bM ~ dnorm( 0 , 0.5 ) ,
        sigma ~ dexp( 1 )
    ) , data = d )

m5.3 <- quap(
    alist(
        D ~ dnorm( mu , sigma ) ,
        mu <- a + bM*M + bA*A ,
        a ~ dnorm( 0 , 0.2 ) ,
        bM ~ dnorm( 0 , 0.5 ) ,
        bA ~ dnorm( 0 , 0.5 ) ,
        sigma ~ dexp( 1 )
    ) , data = d )

m5.3t <- quap(
    alist(
        D ~ dstudent( 2 , mu , sigma ) ,
        mu <- a + bM*M + bA*A ,
        a ~ dnorm( 0 , 0.2 ) ,
        bM ~ dnorm( 0 , 0.5 ) ,
        bA ~ dnorm( 0 , 0.5 ) ,
        sigma ~ dexp( 1 )
    ) , data = d )


#---------------------------------------------------------------------#
#This function computes the Initial & Exact Solutions.
#Written by F.X. Giraldo on April 19, 2019
#           Department of Applied Mathematics
#           Naval Postgraduate School
#           Monterey; CA 93943-5216
#---------------------------------------------------------------------#
function exact_solution(coord,Npoin,time,case,DFloat)

    #Set some constants
    xmin=-1
    xmax=+1
    xc=0.5*(xmax+xmin)
    xl=xmax-xmin
    rc=0.125
    sigma=1/16; #best for right convergence rates
    # sigma=1/8
    # sigma=1/4; #fatter Gaussian to compare against 2D code
    u=DFloat(2.0)

    #Initialize
    qe=zeros(DFloat,Npoin)

    #timec=time - floor(time)
    timec=time
    #Generate Grid Points
    for I=1:Npoin
        x=coord[I]
        xbar=xc + u*timec
        if (xbar > xmax)
            xbar=xmin + (xbar-xmax)
        end
        r=x-xbar
        if (case == 1) #Gaussian
            qe[I]=exp( -(x-xbar)^2/(2*sigma)^2 )
        elseif (case == 2) #Square Wave
      	    if ( abs(r) <= rc )
                qe[I]=1
            end
        end
    end #I
    return (qe,u)
end


# This is based on Higham's own Matlab implementation based on his paper:
#   By N. J. Higham, 13/6/01, updated 30/1/13.
#   Reference:  N. J. Higham, Computing the nearest correlation
#   matrix---A problem from finance. IMA J. Numer. Anal.,
#   22(3):329-343, 2002.
#
# as presented on: http://nickhigham.wordpress.com/2013/02/13/the-nearest-correlation-matrix/
# It is typically not the fastest algorithm but should be ok for small (<500) dimensions...

# NEARCORR    Nearest correlation matrix.
#    (X, iter) = NEARCORR(A,TOL,FLAG,MAXITS,N_POS_EIG,W,PRNT)
#    finds the nearest correlation matrix to the symmetric matrix A.
#    TOL is a convergence tolerance, which defaults to 16*EPS.
#    If using FLAG == 1, TOL must be a 2-vector, with first component
#    the convergence tolerance and second component a tolerance
#    for defining "sufficiently positive" eigenvalues.
#    FLAG = 0: solve using full eigendecomposition (EIG).
#    FLAG = 1: treat as "highly non-positive definite A" and solve
#              using partial eigendecomposition (EIGS).
#    MAXITS is the maximum number of iterations (default 100, but may
#    need to be increased).
#    N_POS_EIG (optional) is the known number of positive eigenvalues of A.
#    W is a vector defining a diagonal weight matrix diag(W).
function nearcorr(A, tol = [16*Base.eps()], flag = 0, maxits = 100,
   n_pos_eig = false, w = ones(size(A, 1),1))

   if A != A'
      throw( ArgumentError("The matrix must be symmetric.") )
   end

   if length(tol) == 1 && flag == 1
      tol = size(A, 1) * Base.eps() * [1.0, 1.0]
   end

   n = size(A, 1)
   if flag == 1 && n_pos_eig == false
      d, V = eig(A)
      n_pos_eig = sum(d >= tol[2] * d[n])
   end

   X = copy(A)
   Y = copy(A)
   iter = 1
   rel_diffXY = rel_diffY = rel_diffX = Inf
   dS = zeros(size(A))
   w = w[:]
   Whalf = sqrt(w*w')

   while max(rel_diffX, rel_diffY, rel_diffXY) > tol[1]

      Xold = X
      R = X - dS
      R_wtd = Whalf .* R

      if flag == 0
         X = proj_spd(R_wtd)
      elseif flag == 1
         X, np = proj_spd_eigs(R_wtd, n_pos_eig, tol[2])
      end

      X =  X ./ Whalf
      dS = X - R
      Yold = Y
      Y = proj_unitdiag(X)
      normy = normfro(Y)
      rel_diffX = normfro(X-Xold)/normfro(X)
      rel_diffY = normfro(X-Xold)/normy
      rel_diffXY = normfro(Y-X)/normy

      iter = iter + 1;
      if iter > maxits
         throw( OverflowError("Stopped after $(maxits) iterations. Try increasing maxits.") )
      end

      X = Y

   end

   return X, iter
end

function proj_spd(A)
   d, V = eig(A)
   d[d .< 0.0] = 0.0 # Set negative eigen values to 0
   B = V * diagm(d) * V'
   (B+B')/2 # Ensure symmetric
end

function proj_spd_eigs(A, n_pos_eig, tol)
   k = n_pos_eig + 10 # 10 is safety factor.
   if k > size(A, 1)
      k = n_pos_eig
   end
   d, V = eigs(A, k; which = "LM")
   j = d .> tol*maximum(d)
   n_pos_eig_found = sum(j)
   B = V * diagm(d[j]) * V'
   B = (B+B')/2 # Ensure symmetric
   return B, n_pos_eig_found
end

function proj_unitdiag(A)
   B = copy(A)
   B[diagind(B)] = 1.0
   B
end


"""
    a = rand_regular_bipartite(n,k)

Random k-regular bipartite graph between two sets of n vertices.
No repeat edges, so can take a long time to build of k is close to n.

Returns a (possibly) asymmetric matrix that contains the upper-right block.
"""
function rand_regular_bipartite(n,k)
    @assert k <= div(n,2)

    p = randperm(n)

    # start by building the upper-right block
    b = sparse(1:n, p, 1, n, n)

    for i in 2:k
        done = false
        while !done
            p = randperm(n)
            done = true
            for i in 1:n
                if b[i,p[i]] > 0
                    done = false
                end
            end
            if done
                b = b + sparse(1:n, p, 1, n, n)
            end
        end

    end

    return b
end

function test_regular_bipartite(B)
    d = maximum(sum(B,dims=1))
    d == minimum(sum(B,dims=1)) &&
    d == minimum(sum(B,dims=2)) &&
    d == maximum(sum(B,dims=2)) &&
    length(unique(B)) <= 2
end

function regular_bipartite_matching(B)
    @assert test_regular_bipartite(B)

    n = size(B,1)
    match = zeros(Int,n)

    # match is indexed by right vertices, and gives name of match on left

    matched = zeros(Bool,n) # indicates which vertices on left are matched

    d = maximum(sum(B,dims=1))
    if d == 1
        return invperm(B.rowval)
    end

    for i in 1:n
        ops = match_walk(B, match, matched, n, d)

        # eliminate loops in ops
        us = [op[1] for op in ops]

        firsts = indexin(us,us)
        i = length(ops)
        while i > 0
            (u,v) = ops[i]
            match[v] = u
            matched[u] = true

            if firsts[i] < i
                i = firsts[i] - 1
            else
                i = i - 1
            end
        end

    end

    return match
end

function match_walk(B, match, matched, n, d)

    ops = Tuple{Int64,Int64}[]

    u = rand(findall(.!matched))
    v = 0

    done = false
    while !done

        foundv = false
        while !foundv
            i = rand(1:d)
            v = B.rowval[B.colptr[u]+i-1]
            foundv = (u != match[v])
        end

        push!(ops,(u,v))

        if match[v] == 0
            done = true
        else
            u = match[v]
        end

    end

    return ops
end

"""
    S = latin_square(n)

Computes a random (but not uniformly random) n-by-n latin square.
"""
function latin_square(n)

    S = zeros(Int,n,n)

    B = ones(Int,n,n)

    for j in 1:n

        match = regular_bipartite_matching(sparse(B))
        for i in 1:n
            S[i,match[i]] = j
            B[i,match[i]] = 0
        end
    end

    return S
end

function test_latin_square(S)
    n = size(S,1)

    b = true
    b = b && size(S,2) == n

    for i in 1:n
        b = b && length(unique(S[:,i])) == n
        b = b && length(unique(S[i,:])) == n
    end

    b = b && length(unique(S)) == n
    return b
end

"""
    a = latin_square_graph(S::Matrix{Int})
    a = latin_square_graph(n::Int)

Construct the adjacency matrix of the latin square graph for the latin square S.
If only n is provided, construct a latin square graph of dimension n.
"""
function latin_square_graph(S::Matrix{Int})

    @assert test_latin_square(S)

    n = size(S,1)

    ijv = Laplacians.complete_graph_ijv(n)
    ci = copy(ijv.i)
    cj = copy(ijv.j)

    lsgi = Int[]
    lsgj = Int[]

    # construct the column cliques
    for i in 1:n
        append!(lsgi, ci .+ n*(i-1))
        append!(lsgj, cj .+ n*(i-1))
    end

    # construct the row cliques
    for i in 1:n
        append!(lsgi, n*ci .+ (i-n))
        append!(lsgj, n*cj .+ (i-n))
    end

    # construct the value cliques
    nums = reshape([1:n^2;],n,n)

    for i in 1:n
        ind = nums[S .== i]
        append!(lsgi, ind[ci])
        append!(lsgj, ind[cj])
    end

    return sparse(lsgi, lsgj, 1.0, n^2, n^2)
end

latin_square_graph(n::Int) = latin_square_graph(latin_square(n))


using Random, Statistics, FreqTables
using LinearAlgebra, Optim, GLM
using DataFrames, CSV, HTTP

function ps2()
#:::::::::::::::::::::::::::::::::::::::::::::::::::
# question 1
#:::::::::::::::::::::::::::::::::::::::::::::::::::
f(x) = -x[1]^4-10x[1]^3-2x[1]^2-3x[1]-2
minusf(x) = x[1]^4+10x[1]^3+2x[1]^2+3x[1]+2
startval = rand(1)   # random starting value
result = optimize(minusf, startval, BFGS())

#:::::::::::::::::::::::::::::::::::::::::::::::::::
# question 2
#:::::::::::::::::::::::::::::::::::::::::::::::::::
url = "https://raw.githubusercontent.com/OU-PhD-Econometrics/fall-2020/master/ProblemSets/PS1-julia-intro/nlsw88.csv"
df = CSV.read(HTTP.get(url).body)
X = [ones(size(df,1),1) df.age df.race.==1 df.collgrad.==1]
y = df.married.==1

function ols(beta, X, y)
    ssr = (y.-X*beta)'*(y.-X*beta)
    return ssr
end

beta_hat_ols = optimize(b -> ols(b, X, y), rand(size(X,2)), LBFGS(), Optim.Options(g_tol=1e-6, iterations=100_000, show_trace=true))
println(beta_hat_ols.minimizer)

bols = inv(X'*X)*X'*y
df.white = df.race.==1
bols_lm = lm(@formula(married ~ age + white + collgrad), df)
println("β_lm estimates:", bols_lm)

#:::::::::::::::::::::::::::::::::::::::::::::::::::
# question 3
#:::::::::::::::::::::::::::::::::::::::::::::::::::
function logit(β, X, y)

    # your turn
    diff_u = X*β
    loglike = sum(y.*diff_u - log.(1 .+ exp.(diff_u)))
    loglike = -loglike

    return loglike
end

β_llk = optimize(β -> logit(β, X,y),
                 rand(size(X,2)),
                 # LBFGS(),
                 Newton(),
                 Optim.Options(g_tol=1e-6, iterations=100_000, show_trace=true))
println(β_llk.minimizer)

#:::::::::::::::::::::::::::::::::::::::::::::::::::
# question 4
#:::::::::::::::::::::::::::::::::::::::::::::::::::
b_glm = glm(@formula(married ~ age + white + collgrad), df, Binomial(), LogitLink())
println("β_glm estimates:", b_glm)

#:::::::::::::::::::::::::::::::::::::::::::::::::::
# question 5
#:::::::::::::::::::::::::::::::::::::::::::::::::::
freqtable(df, :occupation) # note small number of obs in some occupations
df = dropmissing(df, :occupation)
df[df.occupation.==10,:occupation] .= 9
df[df.occupation.==11,:occupation] .= 9
df[df.occupation.==12,:occupation] .= 9
df[df.occupation.==13,:occupation] .= 9
freqtable(df, :occupation) # problem solved

X = [ones(size(df,1),1) df.age df.race.==1 df.collgrad.==1]
df.white = df.race.==1
y = df.occupation

function mlogit(β, X, y)

    K = size(X,2)
    J = length(unique(y))
    N = length(y)
    bigY = zeros(N,J)

    for j=1:J
        bigY[:,j] = y.==j
    end

    β_big = [reshape(β,K,J-1) zeros(K)]

    numerator = zeros(N,J)
    denominator = zeros(N)

    for j=1:J
        numerator[:,j] = exp.(X*β_big[:,j])
        denominator .+= numerator[:,j]
    end

    P = numerator./repeat(denominator, 1, J)
    loglike = sum(bigY.*log.(P))

    return -loglike
end

    alpha_zero = zeros(6*size(X,2))
    alpha_rand = rand(6*size(X,2))
    alpha_true = [.1910213,-.0335262,.5963968,.4165052,-.1698368,-.0359784,1.30684,-.430997,.6894727,-.0104578,.5231634,-1.492475,-2.26748,-.0053001,1.391402,-.9849661,-1.398468,-.0142969,-.0176531,-1.495123,.2454891,-.0067267,-.5382892,-3.78975]
    alpha_start = alpha_true.*rand(size(alpha_true))
    println(size(alpha_true))
    alpha_hat_optim = optimize(a -> mlogit(a, X, y), alpha_start, LBFGS(), Optim.Options(g_tol = 1e-5, iterations=100_000, show_trace=true, show_every=50))
    alpha_hat_mle = alpha_hat_optim.minimizer
    println(alpha_hat_mle)

    #:::::::::::::::::::::::::::::::::::::::::::::::::::
    # bonus: how to get standard errors?
    # need to obtain the hessian of the obj fun
    #:::::::::::::::::::::::::::::::::::::::::::::::::::
    # first, we need to slightly modify our objective function
    function mlogit_for_h(alpha, X, y)

        K = size(X,2)
        J = length(unique(y))
        N = length(y)
        bigY = zeros(N,J)
        for j=1:J
            bigY[:,j] = y.==j
        end
        bigAlpha = [reshape(alpha,K,J-1) zeros(K)]

        T = promote_type(eltype(X),eltype(alpha)) # this line is new
        num   = zeros(T,N,J)                      # this line is new
        dem   = zeros(T,N)                        # this line is new
        for j=1:J
            num[:,j] = exp.(X*bigAlpha[:,j])
            dem .+= num[:,j]
        end

        P = num./repeat(dem,1,J)

        loglike = -sum( bigY.*log.(P) )

        return loglike
    end

    # declare that the objective function is twice differentiable
    td = TwiceDifferentiable(b -> mlogit_for_h(b, X, y), alpha_start; autodiff = :forward)
    # run the optimizer
    alpha_hat_optim_ad = optimize(td, alpha_zero, LBFGS(), Optim.Options(g_tol = 1e-5, iterations=100_000, show_trace=true, show_every=50))
    alpha_hat_mle_ad = alpha_hat_optim_ad.minimizer
    # evaluate the Hessian at the estimates
    H  = Optim.hessian!(td, alpha_hat_mle_ad)
    # standard errors = sqrt(diag(inv(H))) [usually it's -H but we've already multiplied the obj fun by -1]
    alpha_hat_mle_ad_se = sqrt.(diag(inv(H)))
    println([alpha_hat_mle_ad alpha_hat_mle_ad_se]) # these standard errors match Stata

 return nothing

end
#:::::::::::::::::::::::::::::::::::::::::::::::::::
# question 6
#:::::::::::::::::::::::::::::::::::::::::::::::::::
ps2()


"""
    Exponential

Exponential likelihood is to be used if we assume that the
uncertainity associated with the data follows an Exponential distribution.

```math
    p(y|f) = Exponential(y | f)
```
On calling, this would return an Exponential distribution with rate parameter `f`.
"""
struct ExponentialLikelihood end

(l::ExponentialLikelihood)(f::Real) = Exponential(exp(f))

(l::ExponentialLikelihood)(fs::AbstractVector{<:Real}) = Product(Exponential.(exp.(fs)))


using Test, OMEinsum
using OMEinsum: IndexGroup, NestedEinsum, parse_nested
@testset "einsequence" begin
    @test push!(IndexGroup([],1), 'c').inds == IndexGroup(['c'], 1).inds
    @test isempty(IndexGroup([],1))

    @test_throws ArgumentError parse_nested("((ij,jk),km")
    @test_throws ArgumentError parse_nested("((ij,jk),k1)")

    a, b, c = rand(2,2), rand(2,2), rand(2,2)
    abc1 = ein"(ij,jk),km -> im"(a,b,c)
    abc2 = ein"((ij,jk),km) -> im"(a,b,c)
    abc3 = ein"ij,jk,km -> im"(a,b,c)

    @test abc1 ≈ abc2 ≈ abc3
end

@testset "macro" begin
    b, c, d = rand(2,2), rand(2,2,2), rand(2,2,2,2)
    @ein a[i,j] := b[i,k] * c[k,k,l] * d[l,m,m,j]
    @ein a2[i,j] := b[i,k] * (c[k,k,l] * d[l,m,m,j])
    @test a ≈ a2
end


abstract type AbstractSimplicialComplex <: AbstractFiniteSetCollection end

function show(io::IO, K::AbstractSimplicialComplex)
    println(io, "$(dim(K))-dimensional simplcial complex")
    println(io, "V = {$(join(",",vertices(K)))}")
    println(io, "max K = {$(join(",",facets(K)))}")
end

################################################################################
# General functions, defined for any subtype. Not always the most efficient
# implementations.
################################################################################
"""
    dim(K)

The dimension of `K`, defined as the maximum size of a face of `K` minus 1. If
`K` is the void complex (i.e. `K` has no faces), throws an `ArgumentError`.

"""
dim(K::AbstractSimplicialComplex) = maximum(map(length, facets(K))) - 1

"""
    link(sigma, K)

The link of `sigma` in `K`.

``link_σ(K) = {τ ∈ K : σ ∩ τ = ∅, σ ∪ τ ∈ K}``
"""
function link{T<:AbstractSimplicialComplex}(sigma,K::T)
    newFacets = Vector()
    for F in facets(K)
        if issubset(sigma, F)
            push!(newFacets, setdiff(F,sigma))
        end
    end
    return T(newFacets)
end

"""
    del(tau, K)

The deletion of `tau` from `K`. This is the set faces in `K` which are _not_
cofaces of `tau`. For "deletion" to mean "faces which do not intersect `tau`",
compute the complement of `tau` in `K`'s vertex set and use `res` to restrict to
that set.

``del_τ(K) = {σ ∈ K : τ ⊏̸ σ}``
"""
function del{T<:AbstractSimplicialComplex}(tau, K::T)
    return T(map(F -> setdiff(F,tau), collect(facets(K))))
end

"""
    res(Vprime, K)

The restriction of `K` to `Vprime`.

``res_{V'}(K) = {σ ∈ K : σ ⊆ V'}``
"""
function res{T<:AbstractSimplicialComplex}(Vprime, K::T)
    return T(map(F -> intersect(F,Vprime),collect(facets(K))))
end

################################################################################
# BEGIN CONCRETE SUBTYPES OF ASC
################################################################################

# FACETLIST
"""
    FacetList

A simplicial complex, stored as a list of its facets. Low storage, high
computation time (generally).
"""
struct FacetList{V} <: AbstractSimplicialComplex
    vertices::Vector{V}
    facets::BitMatrix # rows as facets
end
function FacetList(L)
    V = union(L...)
    B = falses(length(L),length(V))
    keep = falses(length(L))
    for i = 1:length(L)
        keep[i] = all(map(G -> !issubset(L[i],G), L[i+1:end]))
        if keep[i]
            B[i,indexin(L[i],V)] = true
        end
    end
    B = B[keep,:]
    return FacetList(V,B)
end
function FacetList(C::AbstractFiniteSetCollection)
    return FacetList(collect(facets(C)))
end

vertices(K::FacetList) = K.V

#TODO iteration functions to iterate over *all* faces of K
function start(K::FacetList)
end
function next(K::FacetList, state)
end
function done(K::FacetList, state)
end

# Functions for iterating over the facets of K.
start{V}(maxK::MaximalSetIterator{FacetList{V}}) = 0
next{V}(maxK::MaximalSetIterator{FacetList{V}}, state) = (maxK.C.vertices[maxK.C.facets[state+1,:]], state+1)
done{V}(maxK::MaximalSetIterator{FacetList{V}}, state) = state >= size(maxK.C.facets,1)


##################################################
#  Syntax testing  (uses examples in the doc)
##################################################

#########  rdiff ###########

m.rdiff( :(x^3) , x=2.)             # first order
m.rdiff( :(x^3) , order = 3, x=2.)  # orders up to 3

m.rdiff( :(sin(x)) , order=10, x=2.)  # derivatives up to order 10

res = m.rdiff( :(sin(x)) , order=10, x=2.)
@eval foo(x) = $res
foo(2.)

ex = :( (1 - x[1])^2 + 100(x[2] - x[1]^2)^2 )  # the rosenbrock function
res = m.rdiff(ex, x=zeros(2), order=2)
m.@eval foo(x) = $res
foo([0.5, 2.])

#########  rdiff (function) ###########

rosenbrock(x) = (1 - x[1])^2 + 100(x[2] - x[1]^2)^2   # function to be derived
rosen2 = m.rdiff(rosenbrock, (ones(2),), order=2)       # orders up to 2
rosen2([1,2])
rosen2([0.5,2])

test(x) = exp(x)
m.rdiff(test, (1.,), order=5)

m.rdiff( :(x^3) , x=2.)             # first order
m.rdiff( :(x^3) , order = 3, x=2.)  # orders up to 3

m.rdiff( :(sin(x)) , order=10, x=2.)  # derivatives up to order 10

res = m.rdiff( :(sin(x)) , order=10, x=2.)
@eval foo(x) = $res
foo(2.)

ex = :( (1 - x[1])^2 + 100(x[2] - x[1]^2)^2 )  # the rosenbrock function
res = m.rdiff(ex, x=zeros(2), order=2)
m.@eval foo(x) = $res
foo([0.5, 2.])

#########  @deriv_rule  ###########

m.@deriv_rule *(x::Real         , y::Real )            y     x * ds
m.@deriv_rule *(x::Real         , y::AbstractArray)    y     x .* ds
m.@deriv_rule *(x::AbstractArray, y::Real )            y     sum(x .* ds)
m.@deriv_rule *(x::AbstractArray, y::AbstractArray)    y     x' * ds

foo(x) = log(1+sin(x))

m.@deriv_rule foo(x)   x   cos(x) / ( 1 + sin(x)) * ds

res = m.rdiff( :( 2 ^ foo(x) ) , x=1)
@eval myf(x) = $res

(myf(1.0)[1] - myf(0.999)[1]) * 1000

######### @typeequiv ############

module Sandbox2
    type Bar
        x
        y
    end

    norm(z::Bar) = z.x*z.x + z.y*z.y
end

ex = quote
    z = Sandbox2.Bar(2^a, sin(a))
    Sandbox2.norm(z)
end

m.@deriv_rule  Sandbox2.Bar(x,y)      x  ds[1]   # Derivative accumulator of x is increased by ds[1]
m.@deriv_rule  Sandbox2.Bar(x,y)      y  ds[2]   # Derivative accumulator of y is increased by ds[2]

m.@deriv_rule  Sandbox2.norm(z::Sandbox2.Bar)  z  Any[ 2*z.x*ds , 2*z.y*ds ]  # Note : produces a 2-vector since z is a Bar

res = m.rdiff(ex, a=0.)
@eval df(a) = $res

df(1)

###### internals - tograph example  ######

m.plot( m.tograph( :(a = sin(x+1) ; 2exp(a)) ))

###### higher order   ######

ex = :( (1 - x[1])^2 + 100(x[2] - x[1]^2)^2 )  # the rosenbrock function

res = m.rdiff(ex, x=zeros(2), order=2)
res = m.rdiff(ex, x=zeros(2), order=3)


"""
    haversine(lon1, lat1, lon2, lat2)

Calculate the haversine distance and bearing. Distance is in nm.
"""
@inline @fastmath function haversine(lon1, lat1, lon2, lat2)
    R = 6372.8  # Earth radius in kilometers

    dLat = deg2rad(lat2 - lat1)
    dLon = deg2rad(lon2 - lon1)
    lat1 = deg2rad(lat1)
    lat2 = deg2rad(lat2)
    lon1 = deg2rad(lon1)
    lon2 = deg2rad(lon2)
    a = sin(dLat/2)^2 + cos(lat1)*cos(lat2)*sin(dLon/2)^2
    c = 2*asin(sqrt(a))
    theta = atan(sin(dLon)*cos(lat2),
                 cos(lat1)*sin(lat2)-sin(lat1)*cos(lat2)*cos(dLon))
    theta = (rad2deg(theta) + 360) % 360
    return R*c*0.5399565, theta
end

function rotate_point(ox, oy, px, py, angle,ret_x)
    """
    Rotate a point counterclockwise by a given angle around a given origin.

    The angle should be given in radians.
    """
    qx = ox + cos(angle) * (px - ox) - sin(angle) * (py - oy)
    qy = oy + sin(angle) * (px - ox) + cos(angle) * (py - oy)
    if ret_x == true
        return qx
    else
        return qy
    end
end

function generate_grid(start_lon, start_lat, finish_lon, finish_lat, nodes)
    dist = haversine(start_lon, start_lat, finish_lon, finish_lat)
    spacing = dist[1]/(nodes+1)
    alpha = dist[2]
    x_dist = spacing
    y_dist = spacing
    grid_x = reshape(start_lon.+[i*x_dist for i in range(1, length=nodes) for j in range(0, length=nodes).-(nodes-1)/2], (nodes, nodes))
    grid_y = reshape(start_lat.+[j*y_dist for i in range(1, length=nodes) for j in range(0, length=nodes).-(nodes-1)/2], (nodes, nodes))
    rot_grid_x = [rotate_point(start_lon, start_lat, x, y, alpha, true) for (x, y) in zip(grid_x,grid_y)]
    rot_grid_y = [rotate_point(start_lon, start_lat, x, y, alpha, false) for (x, y) in zip(grid_x,grid_y)]
    return transpose(rot_grid_x), rot_grid_y
end

"""
    euclidean(x1, y1, x2, y2)

Calculate the distance and argument between the vector and north between points 1 and 2.
euclidean(0.0, 0.0, 10.0, 10.0) = (14.142135623730951, 45.0)
euclidean(0.0, 0.0, 10.0, 10.0) = (14.142135623730951, 45.0)
euclidean(0.0, 0.0, 10.0, 10.0) = (14.142135623730951, 45.0)
euclidean(0.0, 0.0, 10.0, 10.0) = (14.142135623730951, 45.0)
"""
@inline @fastmath function euclidean(x1::Float64, y1::Float64, x2::Float64, y2::Float64)
    dx = x2 - x1
    dy = y2 - y1
    dist = (dx^2 + dy^2)^(0.5)
    theta = 0.0
    if dy > 0.0 && dx > 0.0
        theta = 90.0 - rad2deg(atan(dy, dx))
    elseif dy == 0 && dx > 0.0
        theta = 90.0
    elseif dx == 0 && dy < 0.0
        theta = 180.0
    elseif dy < 0.0 && dx > 0.0
        theta = 90.0 + rad2deg(atan(abs(dy), dx))
    elseif dy < 0.0 && dx < 0.0
        theta = 180 + rad2deg(atan(abs(dy), abs(dx)))
    elseif dy > 0.0 && dx < 0.0
        theta = 360 - rad2deg(atan(dy, abs(dx)))
    end
    return dist, theta
end

"""Calculate the number of nodes for a specific distance in nm."""
function calc_nodes(lon1, lon2, lat1, lat2, req_d)
    d = haversine(lon1, lon2, lat1, lat2)[1]
    return round(Int, d/req_d)
end


@testset "SVD" begin
    for n in [1,3,10]

m = n + rand(0:5)
A = rand(m, n)

r = polarfact(A, alg = :svd);

# Test unitary matrix U

U = r.U
H = r.H

@test U'*U ≈ Matrix(I,n,n) atol=1e-7

# Test Hermitian positive semifefinite matrix H

@test issymmetric(H)

for i in eigvals(H)
    @test i >= 0.
end

@test A ≈ U*H

    end
end