function subtract(d1::Dict, d2::Dict)
    res = Dict()
    for key in keys(d1)
        if key ∉ keys(d2)
            res[key] = nothing
        end
    end
    res
end

# using Sets instead
"""
    Subtract two sets.
    Result is a Set.
"""
function subtract(s1::Set, s2::Set)
    setdiff(s1, s2)
end

#   ---------- Duplicates ----------

# ... using dictionaries

function hasduplicates(t::Dict)
    d = Dict()
    for x in t
        if x ∈ d
            return true
        end
        d[x] = nothing
    end
    false
end

function usesonly(word, available)
    for letter in word
        if word ∉ available
            return false
        end
    end
    true
end

# ... using sets
function hasduplicates(t)
    length(Set(t)) < length(t)
end

function usesonly_set(word, available)
    Set(word) ⊆ Set(available)
end

# ---------- Math -----------

x = 0:0.1:2π

cos.(x) == 0.5*(ℯ.^(im*x) + ℯ.^(-im*x))

# ----------- Strings --------

function usesonly_regex(word::String, available::String)
    r = Regex("[^$(available)]")
    @show r;
    !occursin(r,word)
end

usesonly_regex("banana", "abn")
usesonly_regex("bananas", "abn")

match(r"[^abn]", "banana")

m = match(r"[^abn]", "bananas")

# ---------------- Matricies  -----------------

z = zeros(Float64, 2,3)

z[1,2] = 2

z[2,3] = 1
z

size(z)

# 1x3 row matrix
s = ones(String, 1, 3)
size(s)

# 3 x _ column matrix
s_col = ["", "", ""]
size(s_col)
s == s_col

# also a column matrix
s_col2 = ["";"";""]
size(s_col2)

s_col == s_col2

s == s_col2

a = [1 2 3; 4 5 6]

@show a
print(a)
size(a)

#slicing

u = z[:,2:end]

#broadcasting
@show im*u
size(im*u)
g = ℯ.^(im*u)
@show g
typeof(g)

typeof(u)

# ----------- Interfaces: p243 -----------------

#parametric type with no fields
struct Fibonacci{T<:Real} end

#outer constructor
Fibonacci(d::DataType) = d<:Real ? Fibonacci{d}() : error("Not Real type!")

# Iterator interface implementation: 2 parts:

# Part 1: called to initialize the iterator
Base.iterate(::Fibonacci{T}) where {T<:Real} = (zero(T), (one(T), one(T)))

# Part 2:
Base.iterate(::Fibonacci{T}, state::Tuple{T,T}) where {T<:Real} = (state[1], (state[2], state[1] + state[2]))

for e in Fibonacci(Int64)
    e > 100 && break
    print(e, " ")
end

#Equivalently:
f = Fibonacci(Int64)

#initialize state sequence
(a, (b,c)) = iterate(f)

#iterate ...
(a, (b,c)) = iterate(f, (b,c))

(a, (b,c)) = iterate(f, (b,c))

(a, (b,c)) = iterate(f, (b,c))

(a, (b,c)) = iterate(f, (b,c))

(a, (b,c)) = iterate(f, (b,c))

(a, (b,c)) = iterate(f, (b,c))

(a, (b,c)) = iterate(f, (b,c))

(a, (b,c)) = iterate(f, (b,c))

(a, (b,c)) = iterate(f, (b,c))

(a, (b,c)) = iterate(f, (b,c))

#= a for loop in julia:

for i in iter
    body
end

    equivalant to:

next = iterate(iter)
while next !== nothing
    (i, state) = next
    body
    next = iterate(iter, state)
end

=#


#This example has been adopted from https://github.com/IntelLabs/ParallelAccelerator.jl/blob/master/examples/black-scholes/black-scholes.jl

using ArrayFire

function blackscholes_serial(sptprice::AbstractArray{Float32},
                           strike::AbstractArray{Float32},
                           rate::AbstractArray{Float32},
                           volatility::AbstractArray{Float32},
                           time::AbstractArray{Float32})
    logterm = log10( sptprice ./ strike)
    powterm = .5f0 .* volatility .* volatility
    den = volatility .* sqrt(time)
    d1 = (((rate .+ powterm) .* time) .+ logterm) ./ den
    d2 = d1 .- den
    NofXd1 = cndf2(d1)
    NofXd2 = cndf2(d2)
    futureValue = strike .* exp(- rate .* time)
    c1 = futureValue .* NofXd2
    call_ = sptprice .* NofXd1 .- c1
    put  = call_ .- futureValue .+ sptprice
end

@inline function cndf2(in::AbstractArray{Float32})
    out = 0.5f0 .+ 0.5f0 .* erf(0.707106781f0 .* in)
    return out
end

function run(iterations)
    sptprice   = Float32[ 42.0 for i = 1:iterations ]
    initStrike = Float32[ 40.0 + (i / iterations) for i = 1:iterations ]
    rate       = Float32[ 0.5 for i = 1:iterations ]
    volatility = Float32[ 0.2 for i = 1:iterations ]
    time       = Float32[ 0.5 for i = 1:iterations ]

    sptprice_gpu = AFArray(sptprice)
    initStrike_gpu = AFArray(initStrike)
    rate_gpu = AFArray(rate)
    volatility_gpu = AFArray(volatility)
    time_gpu = AFArray(time)

    tic()
    put1 = blackscholes_serial(sptprice, initStrike, rate, volatility, time)
    t1 = toq()
    println("Serial checksum: ", sum(put1))
    tic()
    put2 = blackscholes_serial(sptprice_gpu, initStrike_gpu, rate_gpu, volatility_gpu, time_gpu)
    t2 = toq()
    println("Parallel checksum: ", sum(put2))
    return t1, t2
end

function driver()
    srand(0)
    tic()
    iterations = 10^7
    blackscholes_serial(Float32[], Float32[], Float32[], Float32[], Float32[])
    blackscholes_serial(AFArray(Float32[1., 2.]), AFArray(Float32[1., 2.]),
                        AFArray(Float32[1., 2.]), AFArray(Float32[1., 2.]), AFArray(Float32[1., 2.]))
    println("SELFPRIMED ", toq())
    tserial, tparallel = run(iterations)
    println("Time taken for CPU = $tserial")
    println("Time taken for GPU = $tparallel")
    println("Speedup = $(tserial / tparallel)")
    println("CPU rate = ", iterations / tserial, " opts/sec")
    println("GPU rate = ", iterations / tparallel, " opts/sec")
end
driver()


"""
```julia
rate(q::Integer, M::Integer, n::Integer) -> Real
```

Calculate the rate of a code.  That is, how efficient the code is.

Parameters:
  - `q::Integer`: the number of symbols in the code.
  - `M::Integer`: the size/number of elements in the code.
  - `n::Integer`: The word length.

Returns:
  - `Real`: Rate of the code.

---

### Examples

```julia
julia> rate(3, 5, 4) # the rate of the code which has 3 symbols, 5 words in the code, and word length of 4 (e.g., Σ = {A, B, C}, C = {ABBA,CABA,BBBB,CAAB,ACBB})
0.3662433801794817
```
"""
rate(q::T, M::T, n::T) where {T <: Integer} = log(q, M) / n

__spheres(q::T, n::T, r::T) where {T <: Integer} = sum(Integer[((big(q) - 1)^i) * binomial(big(n), big(i)) for i in 0:r])
__sphere_bound(round_func::Function, q::T, n::T, d::T) where {T <: Integer} = round_func((big(q)^n) / __spheres(q, n, d))

"""
```julia
sphere_covering_bound(q::Integer, n::Integer, d::Integer) -> Integer
```

Computes the sphere covering bound of a ``[n, d]_q``-code.

Parameters:
  - `q::Integer`: the number of symbols in the code.
  - `n::Integer`: the word length.
  - `d::Integer`: the distance of the code.

Returns:
  - `Integer`: the sphere covering bound.

---

### Examples

```julia
julia> sphere_covering_bound(5,7,3)
215
```
"""
sphere_covering_bound(q::T, n::T, d::T) where {T <: Integer} = __sphere_bound(ceil, q, n, d - 1)

"""
```julia
sphere_packing_bound(q::Integer, n::Integer, d::Integer) -> Integer
sphere_packing_bound(q::Integer, n::Integer, d::Integer, ::Rounding) -> Real
```

Computes the sphere packing bound of a ``[n, d]_q``-code.  The sphere packing bound is also known as the hamming bound.  You can use `hamming_bound` to compute the same thing.

Parameters:
  - `q::Integer`: the number of symbols in the code.
  - `n::Integer`: the word length.
  - `d::Integer`: the distance of the code.
  - `::Rounding`: use the argument `no_round` in this position to preserve the rounding of the code — which usually by default rounds down.

Returns:
  - `Integer`: the sphere packing bound.

---

### Examples

```julia
julia> sphere_packing_bound(5,7,3)
2693
```
"""
sphere_packing_bound(q::T, n::T, d::T) where T <: Integer =
	__sphere_bound(a -> floor(T, a), q, n, floor(T, (d - 1) / 2))
sphere_packing_bound(q::T, n::T, d::T, ::Rounding) where T <: Integer =
	__sphere_bound(identity, q, n, floor(T, (d - 1) / 2))
hamming_bound(q::T, n::T, d::T) where T <: Integer =
	sphere_packing_bound(q, n, d)
hamming_bound(q::T, n::T, d::T, ::Rounding) where T <: Integer =
	sphere_packing_bound(q, n, d, no_round)

"""
```julia
sphere_packing_bound(q::Integer, n::Integer, d::Integer) -> Real
```

Computes the Singleton bound of a ``[n, d]_q``-code.

Parameters:
  - `q::Integer`: the number of symbols in the code.
  - `n::Integer`: the word length.
  - `d::Integer`: the distance of the code.

Returns:
  - `Real`: the Singleton bound.  Can round down, as it is an equivalent to the Hamming bound in that it is an upper bound.
"""
# promote()
# _T = typeof(T)

singleton_bound(q::T, n::T, d::T) where T <: Integer =
	floor(T, float(big(q))^(big(n) - big(d) + 1))
singleton_bound(q::T, n::T, d::T, ::Rounding) where T <: Integer =
	float(big(q))^(big(n) - big(d) + 1)

gilbert_varshamov_bound(q::T, n::T, d::T) where T <: Integer =
	__sphere_bound(a -> floor(T, a), q, n, d - 1)
gilbert_varshamov_bound(q::T, n::T, d::T, ::Rounding) where T <: Integer =
	__sphere_bound(identity, q, n, d - 1)

function __plotkin_bound_core(round_func::Function, q::T, n::T, d::T) where T <: Integer
	if ! isequal(q, 2)
		throw(error("The Plotkin bound only works for the binary code."))
	end

	if iseven(d) && 2d > n
		return round_func((d) / (2d + 1 - n))
	elseif isodd(d) && 2d + 1 > n
		return round_func((d + 1) / (2d + 1 - n))
	elseif iseven(d)
		return T(4d)
		# return A_2(2d, d) ≤ 4d
	elseif isodd(d)
		return T(4d + 4)
		# return A_2(2d + 1, d) ≤ 4d + 4
	end
end

plotkin_bound(q::T, n::T, d::T) where T <: Integer =
	__plotkin_bound_core(a -> floor(T, a), q, n, d)
plotkin_bound(q::T, n::T, d::T, ::Rounding) where T <: Integer =
	__plotkin_bound_core(identity, q, n, d, no_round)

elias_bassalygo_bound(q::T, n::T, d::T) where T <: Integer =
elias_bassalygo_bound(q::T, n::T, d::T, ::Rounding) where T <: Integer =

function __johnson_bound_core(round_func::Function, q::T, n::T, d::T) where T <: Integer
	if isinteger((d - 1) / 2) # is odd
		t = T((d - 1) / 2)
		__sphere_bound(round_func, q, n, t) # if d = 2t + 1
	elseif isinteger(d / 2)
		t = T(d / 2)
		__sphere_bound(round_func, q, n, t)
	end
end

@doc raw"""
```julia
construct_ham_matrix(r::Int, q::Int) -> Matrix
```

Construct a Hamming parity-check matrix.

Parameters:
  - `r::Int`: number of rows of a parity check matrix.
  - `q:::Int`: The size of the alphabet of the code.

Returns:
  - `Matrix`: The Hamming matrix, denoted as ``\text{Ham}(r, q)``

---

### Examples

```julia
julia> construct_ham_matrix(3,2)
3×7 Array{Int64,2}:
 0  0  0  1  1  1  1
 0  1  1  0  0  1  1
 1  0  1  0  1  0  1
```
"""
function construct_ham_matrix(r::Int, q::Int)
    ncols = Int(floor((q^r - 1) / (q - 1)))
    M = Matrix{Int}(undef, r, ncols)

    for i in 1:ncols
        M[:, i] = reverse(digits(parse(Int, string(i, base = q)), pad = r), dims = 1)
    end

    return M
end

"""
```julia
isperfect(n::Int, k::Int, d::Int, q::Int) -> Bool
```

Checks if a code is perfect.  That is, checks if the number of words in the code is exactly the "Hamming bound", or the "Sphere Packing Bound".

Parameters:
  - `q:::Int`: The size of the alphabet of the code.
  - `n::Int`: The length of the words in the code (block length).
  - `d::Int`: The distance of the code.
  - `k::Int`: The dimension of the code.

Returns:
  - `Bool`: true or false

---

### Examples

```julia
julia> isperfect(11, 6, 5, 3)
true
```
"""
function isperfect(n::T, k::T, d::T, q::T) where T <: Int
	isprimepower(q) || throw(error("Cannot check if the code is perfect with q not a prime power."))
    M = q^k

    isequal(sphere_packing_bound(q, n, d), M) && return true
	return false
end

"""
```julia
ishammingbound(r::Int, q::Int) -> Bool
```

Checks if the code is a perfect code that is of the form of a generalised Hamming code.

Parameters:
  - `r::Int`: number of rows of a parity check matrix.
  - `q::Int`: The size of the alphabet of the code.

Returns:
  - `Bool`: true or false
"""
function ishammingperfect(r::Int, q::Int)
    n = 2^r - 1
    k = n - r
    M = q^k
    d = size(construct_ham_matrix(r, q), 1) # the number of rows of the hamming matrix (which is, by design, linearly independent)
    d = r
    # r is dim of dueal code; dim of code itself is block length minus r
    # println(n)
    # println((q^r - 1) / (q - 1))

    isequal(n, (q^r - 1) / (q - 1)) && \
		isequal(d, 3) && \
		isequal(M, q^(((q^r - 1) / (q - 1)) - r)) && \
        return true
    return false
end

"""
```julia
ishammingperfect(n::Int, k::Int, d::Int, q::Int) -> Bool
ishammingperfect(q::Int, n::Int, d::Int) -> Bool
```

Checks if the code is a perfect code that is of the form of a generalised Hamming code.

Parameters:
  - `q:::Int`: The size of the alphabet of the code.
  - `n::Int`: The length of the words in the code (block length).
  - `d::Int`: The distance of the code.
  - `k::Int`: The dimension of the code.

Returns:
  - `Bool`: true or false

---

### Examples

```julia
julia> isgolayperfect(11, 6, 5, 3) # this is one of golay's perfect codes
true
```
"""
function ishammingperfect(n::T, k::T, d::T, q::T) where T <: Int
    isprimepower(q) || return false

    M = q^k
    r = log(ℯ, ((n * log(ℯ, 1)) / (log(ℯ, 2))) + 1) / log(ℯ, 2)

    if isequal(n, (q^(r - 1)) / (q - 1)) && isequal(d, 3) && isequal(M, q^(((q^r - 1) / (q - 1)) - r))
        return true
    end

    return false
end
function ishammingperfect(q::Int, n::Int, d::Int)
	isprimepower(q) || return false # we are working in finite fields, so q must be a prime power
	d ≠ 3 && return false

	r = 1
	while ((q^r - 1) / (q - 1)) < n
		r = r + 1
	end

	return ifelse(isequal(((q^r - 1) / (q - 1)), n), true, false)
end

"""
```julia
isgolayperfect(n::Int, k::Int, d::Int, q::Int) -> Bool
```

Golay found two perfect codes.  `isgolayperfect` checks if a code of block length n, distance d, alphabet size q, and dimension k, is a perfect code as described by Golay.

Parameters:
  - `n::Int`: The block length of words in the code (e.g., word "abc" has block length 3).
  - `k::Int`: The dimension of the code.
  - `d::Int`: The distance of the code (i.e., the minimum distance between codewords in the code).
  - `q::Int`: An Int that is a prime power.  The modulus of the finite field.

Returns:
  - `Bool`: true or false.

---

### Examples

```julia
julia> isgolayperfect(11, 6, 5, 3) # this is one of golay's perfect codes
true
```
"""
function isgolayperfect(n::T, k::T, d::T, q::T) where T <: Int
	isprimepower(q) ||  false # we are working in finite fields, so q must be a prime power
    M = q^k
    (isequal(q, 2) && isequal(n, 23) && isequal(d, 7) && isequal(M, 2^12)) && return true
    (isequal(q, 3) && isequal(n, 11) && isequal(d, 5) && isequal(M, 3^6)) && return true
    return false
end


export HPolygon, add_point!, npoints, RandomHPolygon, sides
export polygon_check

"""
`HPolygon()` creates a new polygon (with no points).

`HPolygon(list)` creates a polygon whose points are specified in `list`.

See: `add_point!`
"""
struct HPolygon <: HObject
    plist::Array{HPoint,1}
    attr::Dict{Symbol,Any}
    function HPolygon()
        X = new(HPoint[],Dict{Symbol,Any}())
        set_color(X)
        set_thickness(X)
        set_line_style(X)
        return X
    end
end

"""
`add_point!(X::HPolygon, P::HPoint)` adds the point `P`
as the last point of the polygon `X`
"""
add_point!(X::HPolygon, P::HPoint) = push!(X.plist,P)

"""
`endpoints(X::HPolygon)` returns the list of vertices (in order)
of the polygon.
"""
endpoints(X::HPolygon) = deepcopy(X.plist)

"""
`sides(P:::HPolygon/HTriangle)` returns a list of the line segments
that are the sides of the polygon.
"""
function sides(X::HPolygon)::Array{HSegment,1}
    n = npoints(X)
    if n < 2
        return HSegment[]
    end

    result = Array{HSegment,1}(undef,n)
    for k=1:n-1
        a = X.plist[k]
        b = X.plist[k+1]
        result[k] = a+b
    end
    result[end] = X.plist[end] + X.plist[1]
    return result
end

sides(T::HTriangle) = sides(HPolygon(T))

"""
`npoints(X::HPolygon)` returns the number of points on the polygon.
"""
npoints(X::HPolygon) = length(X.plist)

function HPolygon(pts::Array{HPoint,1})
    X = HPolygon()
    for p in pts
        add_point!(X,p)
    end
    return X
end

HPolygon(pts...) = HPolygon(collect(pts))
HPolygon(X::HPolygon) = HPolygon(X.plist)  # copy constructor

"""
`polygon_check(X::HPolygon,quiet=true)` checks that the polygon is nondegenerate.
Possible degeneracies are:
+ Repeated vertices
+ Fewer than three distinct vertices
+ Angles that are either 0 degrees or 180 degrees
If `quiet` is `false`, then a reason for the failed check is printed.
"""
function polygon_check(X::HPolygon, quiet::Bool=true)::Bool

    # Check that the endpoints are all distinct
    n = npoints(X)

    C = HContainer(X.plist...)
    if length(C) != n
        quiet || println("The polygon has repeated vertices")
        return false
    end

    if length(C) < 3
        quiet || println("The polygon is degenerate (fewer than 3 distinct vertices)")
        return false
    end

    angs = angles(X)
    zero_angs = angs .< (THRESHOLD * eps(1.0))
    if any(zero_angs)
        quiet || println("The polygon has 0-degree angles")
        return false
    end

    big_angs = angs .> (pi - THRESHOLD*eps(1.0))
    if any(big_angs)
        quiet || println("The polygon has 180-degree angles")
        return false
    end

    return true
end

function HPolygon(T::HTriangle)
    a,b,c = endpoints(T)
    return HPolygon(a,b,c)
end

function HTriangle(X::HPolygon)
    @assert npoints(X)==3 "Can only convert a 3-point HPolygon into an HTriangle"
    return HTriangle(X.plist...)
end

"""
`RandomHPolygon(n::Int,simple::Bool=false)` create a new `HPolygon` with
`n` points chosen at random. With `simple` set to `true`, return a polygon
that does not self-intersect.
"""
function RandomHPolygon(n::Int, simple::Bool=false)
    @assert n>=0 "Number of vertices must be nonnegative"
    if simple && n>3
        P = RandomHPolygon(n)
        while !is_simple(P)
            P = RandomHPolygon(n)
        end
        return P
    end

    pts = [ RandomHPoint() for j=1:n ]
    return HPolygon(pts)
end

"""
`angles(P::HPolygon)` returns a list of the angles at the vertices of `P`.

+ The results are always in the interval `[0,pi]`.
+ The order of the angles is the order of the vertices in `P.plist`.
"""
function angles(P::HPolygon)::Array{Float64,1}
    n = npoints(P)
    result = zeros(Float64,n)
    if n < 3
        return result
    end

    # first angle
    result[1] = angle(P.plist[end],P.plist[1],P.plist[2])

    for j=2:n-1
        result[j] = angle(P.plist[j-1], P.plist[j], P.plist[j+1])
    end

    result[n] = angle(P.plist[n-1],P.plist[n],P.plist[1])

    return result
end

function perimeter(P::HPolygon)
    n = npoints(P)
    if n < 2
        return 0.0
    end
    result = dist(P.plist[1],P.plist[end])
    if n==2
        return 2*result
    end
    for j=1:n-1
        result += dist(P.plist[j],P.plist[j+1])
    end
    return result
end

function show(io::IO,X::HPolygon)
    print(io,"HPolygon with $(npoints(X)) points")
end

# require 0 <= k < n
"""
`_cycle(A,k)` returns a `k`-step shift of `A`. We require
`k` to be in the interval `[0,n-1]` where `n=length(A)`.
No checking is done.
"""
function _cycle(A::Array{T,1}, k::Int) where T
    n = length(A)
    B = Array{T,1}(undef,n)
    for t = 1:n-k
        @inbounds B[t] = A[t+k]
    end
    for t = 1:k
        @inbounds B[n-k+t] = A[t]
    end
    return B
end

"""
`_cyclic_equal(A,B)` checks if some cyclic shift of one list
equals the other.
"""
function _cyclic_equal(A::Array{S,1}, B::Array{T,1}) where {S,T}
    n = length(A)
    if length(B) != n
        return false
    end
    for s=0:n-1
        if A == _cycle(B,s)
            return true
        end
    end
    return false
end

(==)(X::HPolygon, Y::HPolygon) = _cyclic_equal(X.plist, Y.plist) ||
    _cyclic_equal(X.plist,reverse(Y.plist))

"""
`is_simple(X::HPolygon)` determines if the polygon edges do not self-intersect.
Be sure the polygon is legit using `polygon_check` first.
"""
function is_simple(X::HPolygon)
    n = npoints(X)
    if n <= 3
        return true   # triangles are fine
    end
    slist = sides(X)
    for i=1:n-2
        for j=i+2:n
            if i==1 && j==n
                continue
            end
            if meet_check(slist[i], slist[j])
                return false
            end
        end
    end
    return true
end


# # Example (Ch. 9 Bathe)

#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/models/example_9_1_Bathe.ipynb)
#

# The example considered next can be found in Chapter 9 of [[BATHE]](@ref).

using StructuralDynamicsODESolvers, Plots

# ## Problem formulation
M = [2 0; 0 1.]
K = [6 -2; -2 4.]
C = zeros(2, 2)
f = [0.0, 10.0]
example_9_1_Bathe = SecondOrderAffineContinuousSystem(M, C, K, f)

NSTEPS = 500
tdom = range(0, NSTEPS * 0.1, length=NSTEPS + 1)
U₀, U₀′ = zeros(2), zeros(2)
prob = InitialValueProblem(example_9_1_Bathe, (U₀, U₀′))

# ## Analytic solution
A = [1/√3  (1/2)*√(2/3);
     1/√3      -√(2/3)]
x₁(t) = (5 / √3) * (1 - cos(t*√2))
x₂(t) = (2 * √(2/3)) * (-1 + cos(t*√5))
U(t) = A * [x₁(t), x₂(t)]

# ## Central difference

sol = solve(prob, CentralDifference(Δt=0.1); NSTEPS=NSTEPS) |> displacements
ind = 150:170
fig = plot(xlab="time", ylab="x1(t)", legend=:outertopright)
fig2 = plot(xlab="time", ylab="x1(t)", legend=:outertopright)
plot!(fig, tdom, [s[1] for s in sol], lab="Central difference")
plot!(fig2, tdom[ind], [s[1] for s in sol[ind]], lab="Central difference")

# ## Houbolt

sol = solve(prob, Houbolt(Δt=0.1); NSTEPS=NSTEPS) |> displacements
plot!(fig, tdom, [s[1] for s in sol], lab="Houbolt")
plot!(fig2, tdom[ind], [s[1] for s in sol[ind]], lab="Houbolt")

# ## Newmark

sol = solve(prob, Trapezoidal(Δt=0.1); NSTEPS=NSTEPS) |> displacements
plot!(fig, tdom, [s[1] for s in sol], lab="Newmark")
plot!(fig2, tdom[ind], [s[1] for s in sol[ind]], lab="Newmark")

# ## Bathe

sol = solve(prob, Bathe(Δt=0.1); NSTEPS=NSTEPS) |> displacements
plot!(fig, tdom, [s[1] for s in sol], lab="Bathe")
plot!(fig2, tdom[ind], [s[1] for s in sol[ind]], lab="Bathe")

# ## Analytic solution

tdom = range(0, NSTEPS*0.1, length=1000)
plot!(fig, tdom, [U(t)[1] for t in tdom], lab="Analytic")
plot!(fig2, tdom[299:330], [U(t)[1] for t in tdom[299:330]], lab="Analytic")


export i_mean_sum, i_var_mean_sum, i_normal_logpdf, i_cor_cov

"""
    i_mean_sum(out!, sum!, x)

get the `mean` and `sum` of `x`.
"""
@i function i_mean_sum(out!, sum!, x)
    for i=1:length(x)
        sum! += x[i]
    end
    out! += sum!/length(x)
end

"""
    i_var_mean_sum(var!, varsum!, mean!, sum!, sqv)

Compute the variance, the accumulated variance, mean and sum.
"""
@i function i_var_mean_sum(var!, varsum!, mean!, sum!, v::AbstractVector{T}) where T
    i_mean_sum(mean!, sum!, v)
    for i=1:length(v)
        v[i] -= mean!
        varsum! += v[i] ^ 2
        v[i] += mean!
    end
    var! += varsum! / (length(v)-1)
end

"""
    i_normal_logpdf(out, x, μ, σ)

get the pdf of `Normal(μ, σ)` at point `x`.
"""
@i function i_normal_logpdf(out, x::T, μ, σ) where T
    @zeros T anc1 anc2 anc3

    @routine begin
        anc1 += x
        anc1 -= μ
        anc2 += anc1 / σ  # (x- μ)/σ
        anc3 += anc2^2 # (x-μ)^2/σ^2
    end

    out -= anc3 * 0.5 # -(x-μ)^2/2σ^2
    out -= log(σ) # -(x-μ)^2/2σ^2 - log(σ)
    out -= log(2π)/2 # -(x-μ)^2/2σ^2 - log(σ) - log(2π)/2

    ~@routine
end

"""
     i_cor_cov(rho!,cov!,a,b)

get Pearson correlation and covariance of two vectors `a` and `b`

"""

@i function i_cor_cov(rho!::T,cov!::T,a::AbstractVector{T},b::AbstractVector{T}) where T
    @safe @assert length(a) == length(b)
    @routine  @invcheckoff begin
        @zeros T var1 varsum1 mean1 sum1 std1
        i_var_mean_sum(var1, varsum1, mean1, sum1, a)
        std1 += sqrt(var1)
        @zeros T var2 varsum2 mean2 sum2 std2
        i_var_mean_sum(var2, varsum2, mean2, sum2, b)
        std2 += sqrt(var2)
        @zeros T anc3 anc4 anc5 anc6 anc7
        @inbounds for i=1:length(b)
            a[i] -= mean1
            anc3 += a[i]
            b[i] -= mean2
            anc4 += b[i]
            anc5 += anc3*anc4
            anc3 -= a[i]
            anc4 -= b[i]
            a[i] += mean1
            b[i] += mean2
        end
        anc6+=std1*std2
        anc7+=anc6*(length(b)-1)
    end
        cov! += anc5/(length(b)-1)
        rho! += anc5/anc7
    ~@routine
end


# Represents an N-dimensional ellipsoid
struct Ellipsoid
    ctr::Vector{Float64}  # center coordinates
    cov::Array{Float64, 2}
    icov::Array{Float64, 2}  # inverse of cov
    vol::Float64
end

# Draw a random point from within a unit N-ball
function randnball(ndim)
    z = randn(ndim)
    r2 = 0.
    for i=1:ndim
        r2 += z[i]*z[i]
    end
    factor = rand()^(1. /ndim) / sqrt(r2)
    for i=1:ndim
        z[i] *= factor
    end
    return z
end

# proportionality constant depending on dimension
# for n even:      (2pi)^(n    /2) / (2 * 4 * ... * n)
# for n odd :  2 * (2pi)^((n-1)/2) / (1 * 3 * ... * n)
function nball_vol_factor(ndim::Int)
    if ndim % 2 == 0
        c = 1.
        for i=2:2:ndim
            c *= 2pi / i
        end
        return c
    else
        c = 2.
        for i = 3:2:ndim
            c *= 2pi / i
        end
        return c
    end
end

function ellipsoid_volume(scaled_cov::Matrix{Float64})
    ndim = size(scaled_cov, 1)
    return nball_vol_factor(ndim) * sqrt(det(scaled_cov))
end

# find the bounding ellipsoid of points x where
function bounding_ellipsoid(x::Matrix{Float64}, enlarge=1.0)

    ndim, npoints = size(x)

    ctr = mean(x, dims=2)[:, 1]
    delta = x .- ctr
    cov = unscaled_covzm(delta, 2)
    icov = inv(cov)

    # Calculate expansion factor necessary to bound each point.
    # This finds the maximum of (delta_i' * icov * delta_i)
    fmax = -Inf
    for k in 1:npoints
        f = 0.0
        for j=1:ndim
            for i=1:ndim
                f += icov[i, j] * delta[i, k] * delta[j, k]
            end
        end
        fmax = max(fmax, f)
    end

    fmax *= enlarge
    cov .*= fmax
    icov .*= 1. /fmax
    vol = ellipsoid_volume(cov)

    return Ellipsoid(ctr, cov, icov, vol)
end

function sample_ellipsoid(ell::Ellipsoid)
    ndim = length(ell.ctr)

    # Get scaled eigenvectors (in columns): vs[:,i] is the i-th eigenvector.
    f = eigen(ell.cov)
    v, w = f.vectors, f.values
    for j=1:ndim
        tmp = sqrt(abs(w[j]))
        for i=1:ndim
            v[i, j] *= tmp
        end
    end

    return v*randnball(ndim) + ell.ctr
end


module HermiteNormalForm

using LinearAlgebra
export hnf!, hnf, ishnf

"""
    ishnf(A)

Check if matrix `A` is in Hermite norm form.
"""
function ishnf(A)
    m, n = size(A)
    for j in 1:n
        for i in 1:min(m, j-1)
            A[i, j] == 0 || return false
        end
        if m >= j
            A[j, j] > 0 || return false
        end
    end
    for i in 1:min(m, n)
        p = A[i, i]
        for j in 1:min(n, i-1)
            0 <= A[i, j] < p || return false
        end
    end
    return true
end

"""
    H, U = hnf(A)

`U` is unimodular and `H` is the Hermite normal form of `A`. If `A` is singular,
then it returns `nothing`.
"""
hnf(A) = hnf!(copy(A))

function hnf!(A)
    m, n = size(A)
    T = eltype(A)
    U = Matrix{T}(I, n, n)
    zz = zero(T)
    for k in 1:m
        # Pivot: A[k, k] should not be 0
        if n >= k && A[k, k] == zz
            pivot = k
            for j in k+1:n
                if A[k, j] != zz
                    pivot = j
                end
            end
            pivot == k && return nothing # rank deficient
            for i in 1:m
                A[i, k], A[i, pivot] = A[i, pivot], A[i, k]
            end
            for i in 1:n
                U[i, k], U[i, pivot] = U[i, pivot], U[i, k]
            end
        end
        # [A11   0 ] k-1 rows
        # [A21  A22]
        # Zero out A[k, k+1:n] === A22[1, 2:end] by multiplying
        # [p  -A[k, j]/d]
        # [q   A[k, k]/d]
        for j in k+1:n
            Akk, Akj = A[k, k], A[k, j]
            d, p, q = gcdx(Akk, Akj)
            Akkd, Akjd = div(Akk, d), div(Akj, d)
            for i in 1:m
                Aik, Aij = A[i, k], A[i, j]
                A[i, k] = Aik * p + Aij * q
                A[i, j] = -Aik * Akjd + Aij * Akkd
            end
            for i in 1:n
                Uik, Uij = U[i, k], U[i, j]
                U[i, k] = Uik * p + Uij * q
                U[i, j] = -Uik * Akjd + Uij * Akkd
            end
        end
        n >= k || continue
        # Ensure the positivity of A[k, k]
        if A[k, k] < zz
            @. A[:, k] = -A[:, k]
            @. U[:, k] = -U[:, k]
        end
        # Minimize A[k, 1:k-1] === A21[1, :]
        for j in 1:k-1
            mul = fld(A[k, j], A[k, k])
            @. A[:, j] -= mul * A[:, k]
            @. U[:, j] -= mul * U[:, k]
        end
    end
    A, U
end

end


# Hessian Eigenmaps (HLLE)
# ---------------------------
# Hessian eigenmaps: Locally linear embedding techniques for high-dimensional data,
# D. Donoho and C. Grimes, Proc Natl Acad Sci U S A. 2003 May 13; 100(10): 5591–5596

#### HLLE type
immutable HLLE{T <: Real} <: SpectralResult
    k::Int
    λ::AbstractVector{T}
    proj::Projection{T}

    HLLE{T}(k::Int, λ::AbstractVector{T}, proj::Projection{T}) = new(k, λ, proj)
end

## properties
outdim(M::HLLE) = size(M.proj, 1)
projection(M::HLLE) = M.proj

eigvals(M::HLLE) = M.λ
neighbors(M::HLLE) = M.k

## show & dump
function show(io::IO, M::HLLE)
    print(io, "Hessian Eigenmaps(outdim = $(outdim(M)), neighbors = $(neighbors(M)))")
end

function dump(io::IO, M::HLLE)
    show(io, M)
    println(io, "eigenvalues: ")
    Base.showarray(io, M.λ', header=false, repr=false)
    println(io)
    println(io, "projection:")
    Base.showarray(io, M.proj, header=false, repr=false)
end

## interface functions
function transform{T<:Real}(::Type{HLLE}, X::DenseMatrix{T}; d::Int=2, k::Int=12)
    n = size(X, 2)

    # Identify neighbors
    D, I = find_nn(X, k)

    # Obtain tangent coordinates and develop Hessian estimator
    hs = Int(d*(d+1)/2)
    W = spzeros(hs*n,n)
    for i=1:n
        # re-center points in neighborhood
        μ = mean(X[:,I[:,i]],2)
        N = X[:,I[:,i]] .- μ
        # calculate tangent coordinates
        #tc = svdfact(N')[:U][:,1:d]
        tc = svdfact(N)[:V][:,1:d]

        # Develop Hessian estimator
        Yi = [ones(k) tc zeros(k,hs)]
        for ii=1:d
            Yi[:,d+ii+1] = tc[:,ii].^2
        end
        yi = 2(1+d)
        for (ii,jj) in combinations(1:d,2)
            Yi[:, yi] = tc[:, ii] .* tc[:, jj]
            yi += 1
        end
        F = qrfact(Yi)
        H = full(F[:Q])[:,d+2:end]'
        W[(i-1)*hs+(1:hs),I[:,i]] = H
    end

    # decomposition
    λ, V = decompose(W'*W, d)
    return HLLE{T}(k, λ, V' .* sqrt(n))
end


using SatisfiabilityInterface, Symbolics

"Constraints representing different colours for each edge of a graph"
different_colours(E, c) = [c[i] ≠ c[j] for (i, j) in E]

"k is the number of colours"
function graph_colouring_problem(V, E, k=3)

    colours = [:red, :green, :yellow, :blue, :black][1:k]
    # c = [Num(Variable(:c, i)) for i in 1:length(V)]   # colour variables

    @variables c[1:length(V)]

    constraints =
    [
        [c[i] ∈ colours for i in 1:length(V)]

        [c[i] ≠ c[j] for (i, j) in E]  #  different_colours(E, c)
    ]

    return DiscreteCSP(constraints)
end

# cs = [Num(Variable(:c, i)) for i in 1:length(V)]   # colour variables

# constraints = [
#     [c ∈ colours for c in cs]
#     different_colours(E, cs)
# ]

V = [1, 2, 3]  # vertices
E = [(1, 2), (2, 3)]  # edges

prob = graph_colouring_problem(V, E, 2)
status, results = solve(prob)

# final_colours = [second(results[k]) for k in prob.original_vars]

# # check that it satisfies the constraint:
# all(different_neighbours(E, final_colours))

## Ring graph

function ring_graph(n=11)
    V = 1:n
    E = [(i, mod1((i+1), n)) for i in 1:n]

    return V, E
end

V, E = ring_graph(11)

prob = graph_colouring_problem(V, E, 2)
status, results = solve(prob)

prob = graph_colouring_problem(V, E, 3)
status, results = solve(prob)
status==:sat

# final_colours = [results[k] for k in prob.variables]
# all(different_neighbours(E, final_colours))

k = 3
colours = [:red, :green, :yellow, :blue, :black][1:k]

c = [Num(Variable(:c, i)) for i in 1:length(V)]   # colour variables

constraints =
    [
        [c[i] ∈ colours for i in 1:length(V)]

        [c[i] ≠ c[j] for (i, j) in E]  #  different_colours(E, c)
    ]

constraints

prob = ConstraintSatisfactionProblem(constraints)
prob2 = DiscreteCSP(prob)

solve(prob2)

k = 3


cigar(x) = x[1]^2 + 1 * sum(abs2, view(x, 2:length(x)))   # 1e6

cigtab(x) = x[1]^2 + 1 * x[end]^2 + 1e4 * sum(abs2, view(x, 2:length(x)-1)) # 1e8

function griewank(x)
  n = length(x)
  1 + (1/4000)*sum(abs2, (x.+7)) - prod(cos.((x.+7) ./ sqrt.(1:n)))
end

function rosenbrock2d(x)
  return sum((x).*(x).*cos.((x)).*sin.((x.-5)))+200
end

function quartic(x)
  D = length(x)
  sum( (1:D) .* (x.-2).^4 )
end

function schwefel1_2(x)
  D = length(x)
  partsums = zeros(D)
  partsum = 0
  for i in 1:D
    partsum += x[i]-9
    partsums[i] = partsum
  end
  sum(abs2, partsums)
end

function ellipsoid(x)
    res = 0.0
    cumsum = 0.0
    for xx in x
        cumsum += xx-sqrt(2)
        res += cumsum^2
    end
    res
end

function elliptic(x)
  D = length(x)
  condition = 1e+3
  if D==1
      coefficients=1
  else
      coefficients = condition .^ range(0,stop=1,length=D)
  end
  sum(coefficients .* (x.+3/2).^2)
end

function rastrigin(x)
  D = length(x)
  10 * D + sum(abs2, (x.+0.7)) - 10 * sum(xx -> cos.(2π * xx), (x.+0.7))
end

function sphere(x)
  sum(abs2, (x.-1.3))
end

function sin_01_x_2(x)
  sum(sin.((x.+0.7))+0.01.*(x.+0.7).^2)
end

function x_i(x)
  ress=0
  for i=1:length(x)
    ress+=(x[i]-i-2.1)^2
  end
  return ress
end

function x_5_sq(x)
  sum((x.-5).^2).-5
end

function x_plus_y(x)
    ress=0
    for i=1:length(x)
      ress+=x[i]
    end
    return ress
end

#

function rand_poly(x)
    return sum(rand(length(x)).*x)
end

# rand_poly(rand(10))
# x=-10:0.001:10
# y=sin.(10rand()x).+(rand()/1000)x.^2
# plot(x,y)

# function limited_domain(x::Vector)::Float64
#   sum( sqrt.(1 ./ (x.+1) .+ (x.+1) ) )  ## min is at 1,1,1...
# end#function
# limited_domain(rand(9))

# x=-10:0.01:10

# y=rand()rastrigin.(x)-.010rand()elliptic.(x)
# scatter(x,y)

function alpine_1(x)
  return sum(abs.(x.*sin.(x)+0.1x))
end


# Example that computes the full inverse wavelet transform using all the
# scales from Jmin=1, to Jmax=log2(n)-1 the coarsest scale

#push path
push!(LOAD_PATH,pwd()*"/src") # for julia 0.4 you need to put your path
#import libraries
using FWT
using PyPlot
using Images

n = 1024;
name = "data_samples/ernst_reuter_haus.bmp";
f = load_image(name, n);
f = rescale(sum(f,3));
f = f[:,:,1];

# filter call
h=filt_gen(WT.db2)
# high pass filter
g = cat(1, 0, h[length(h):-1:2]) .* ( (-1).^(1:length(h)) )
g = cat(1, 0, h[length(h):-1:2]) .* ( (-1).^(1:length(h)) )

# First compute wavelet transform
# Perform of the wavelet transform
Jmax = round(Int64,log2(n))-1;
Jmin = 1;
fW = copy(f);
clf;
for j=Jmax:-1:Jmin
    A = fW[1:2^(j+1),1:2^(j+1)];
    for d=1:2
        Coarse = subsampling(cconvol(A,h,d),d);
        Detail = subsampling(cconvol(A,g,d),d);
        A = cat(d, Coarse, Detail );
    end
    fW[1:2^(j+1),1:2^(j+1)] = A;
    j1 = Jmax-j;
    if j1<4
        imageplot(A[1:2^j,2^j+1:2^(j+1)], "Horizontal, j=$j", 3,4, j1 + 1);
        imageplot(A[2^j+1:2^(j+1),1:2^j], "Vertical, j=$j", 3,4, j1 + 5);
        imageplot(A[2^j+1:2^(j+1),2^j+1:2^(j+1)], "Diagonal, j=$j", 3,4, j1 + 9);
    end
end

# Now the inverse

f1 = copy(fW);
clf;
for j=Jmin:Jmax
    A = f1[1:2^(j+1),1:2^(j+1)];
    for d=1:2
        if d==1
            Coarse = A[1:2^j,:];
            Detail = A[2^j+1:2^(j+1),:];
        else
            Coarse = A[:,1:2^j];
            Detail = A[:,2^j+1:2^(j+1)];
        end
        Coarse = cconvol(upsampling(Coarse,d),FWT.reverse(h),d);
        Detail = cconvol(upsampling(Detail,d),FWT.reverse(g),d);
        A = Coarse + Detail;
        j1 = Jmax-j;
        if j1>0 && j1<5
            imageplot(A, "Partial reconstruction, j=$j", 2,2,j1);
        end
    end
    f1[1:2^(j+1),1:2^(j+1)] = A;
end


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

using Markdown
using InteractiveUtils

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

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

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

# ╔═╡ 1aa41898-3afa-11eb-2f49-570853ce6435
begin
	data = CSV.read(datadir("machine1.csv"), DataFrame; header=false)[:, 1]
	xBar, n = mean(data), length(data)
	sig = 1.2
	alpha = 0.1
	z = quantile(Normal(),1-alpha/2)

	Text("Calculating formula: $((xBar - z*sig/sqrt(n), xBar + z*sig/sqrt(n)))")
end

# ╔═╡ 07d8c2c0-3b2e-11eb-344d-c14ab2a82d0d
Text("Using confint() function: $(confint(OneSampleZTest(xBar,sig,n),alpha))")

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

# ╔═╡ Cell order:
# ╠═ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═1aa41898-3afa-11eb-2f49-570853ce6435
# ╠═07d8c2c0-3b2e-11eb-344d-c14ab2a82d0d
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


#=
Catenary Problem

The problem is to find the shape of a hanging chain. It can be defined as the configuration that minimizes the potential energy.

Objective: linear
Constraints: convex quadratic
Feasible set: convex

This model finds the shape of a hanging chain
The solution is known to be y = cosh(a*x) + b
for appropriate a and b.

This problem is a port to Julia/JuMP of a model by Professor Robert Vanderbei
at Princeton University, originally written in AMPL.
Source: http://orfe.princeton.edu/~rvdb/ampl/nlmodels/
=#

using JuMP
using Ipopt  # Nonlinear solver
using Gadfly # Graphing support

N = 100   # number of chainlinks
L = 1     # difference in x-coords of endlinks
h = 2*L/N # length of each link

m = Model(solver=IpoptSolver())

@defVar(m, x[0:N])
@defVar(m, y[0:N])

# Minimize potential energy from center of mass for link
@setObjective(m, Min, sum{(y[j-1] + y[j])/2, j=1:N})

# Anchor ends
@addConstraint(m, x[0] == 0)
@addConstraint(m, y[0] == 0)
@addConstraint(m, x[N] == L)
@addConstraint(m, y[N] == 0)

# Set starting values
for j in 0:N
    setValue(x[j], 0)
    setValue(y[j], 0)
end

# Link together pieces
for j in 1:N
    @addNLConstraint(m,
        (x[j] - x[j-1])^2 + (y[j] - y[j-1])^2 <= h^2
    )
end

solve(m)

# Graph the data

x_clean = Float64[]
y_clean = Float64[]
for j in 0:N
    push!(x_clean, getValue(x)[j])
    push!(y_clean, getValue(y)[j])
end

catenary = plot(x=x_clean, y=y_clean, Coord.Cartesian(xmin=0, xmax=1,))
draw(SVG("catenary.svg", 6inch, 6inch), catenary)


function Δ(model::LogReturnComputationModel; multiplier::Float64 = 1.0)::DataFrame

    # get stuff from the computational model -
    raw_data = model.data
    from = model.from
    to = model.to
    map = model.map

    # ok, so need to filter the data (if we have a from and to date set)
    data = raw_data
    if (isnothing(to) == false && isnothing(from) == false)

        # get the date key from the map -
        date_key = map.first

        # filter to the specified date range -
        data = filter(date_key => d -> (d >= from && d <= to), raw_data)
    end

    # initialize -
    (number_of_rows, _) = size(data)
    return_table = DataFrame(date = Date[], P₁ = Float64[], P₂ = Float64[], Δ = Float64[], Δ₍μ₎ = Float64[], Z = Float64[])

    # main loop -
    for row_index ∈ 2:number_of_rows

        # grab the date -
        tmp_date = data[row_index, map.first]

        # grab the price data -
        yesterday_close_price = data[row_index-1, map.second]
        today_close_price = data[row_index, map.second]

        # compute the diff -
        δ_value = multiplier * log(today_close_price / yesterday_close_price)

        # push! -
        push!(return_table, (tmp_date, yesterday_close_price, today_close_price, δ_value, 0.0, 0.0))
    end

    # compute the mean -
    μ = mean(return_table[!, :Δ])

    # compute the std -
    σ = std(return_table[!, :Δ])

    # add values to the δ - μ col -
    (number_of_rows, _) = size(return_table)
    for row_index ∈ 1:number_of_rows
        value = return_table[row_index, :Δ] - μ
        return_table[row_index, :Δ₍μ₎] = value
    end

    # add the Z column -
    for row_index ∈ 1:number_of_rows
        value = (1 / σ) * (return_table[row_index, :Δ] - μ)
        return_table[row_index, :Z] = value
    end

    # return -
    return return_table
end

function Δ(model::LinearReturnComputationModel; multiplier::Float64 = 1.0)::DataFrame

    # get stuff from the computational model -
    raw_data = model.data
    from = model.from
    to = model.to
    map = model.map

    # ok, so need to filter the data (if we have a from and to date set)
    data = raw_data
    if (isnothing(to) == false && isnothing(from) == false)

        # get the date key from the map -
        date_key = map.first

        # filter to the specified date range -
        data = filter(date_key => d -> (d >= from && d <= to), raw_data)
    end

    # initialize -
    (number_of_rows, _) = size(data)
    return_table = DataFrame(date = Date[], P₁ = Float64[], P₂ = Float64[], Δ = Float64[], Δ₍μ₎ = Float64[], Z = Float64[])

    # main loop -
    for row_index ∈ 2:number_of_rows

        # grab the date -
        tmp_date = data[row_index, map.first]

        # grab the price data -
        yesterday_close_price = data[row_index-1, map.second]
        today_close_price = data[row_index, map.second]

        # compute the diff -
        δ_value = multiplier * ((today_close_price - yesterday_close_price) / (yesterday_close_price))

        # push! -
        push!(return_table, (tmp_date, yesterday_close_price, today_close_price, δ_value, 0.0, 0.0))
    end

    # compute the mean -
    μ = mean(return_table[!, :Δ])

    # compute the std -
    σ = std(return_table[!, :Δ])

    # add values to the δ - μ col -
    (number_of_rows, _) = size(return_table)
    for row_index ∈ 1:number_of_rows
        value = return_table[row_index, :Δ] - μ
        return_table[row_index, :Δ₍μ₎] = value
    end

    # add the Z column -
    for row_index ∈ 1:number_of_rows
        value = (1 / σ) * (return_table[row_index, :Δ] - μ)
        return_table[row_index, :Z] = value
    end

    # return -
    return return_table
end

function Δ(models::Array{T,1};
    multiplier::Float64 = 1.0)::Dict{String,DataFrame} where {T<:AbstractBaseCampComputation}

    # initialize -
    Δ_dictionary = Dict{String,DataFrame}()

    # compute the returns for each model in the array -
    for model ∈ models
        ticker = model.ticker
        Δ_dictionary[ticker] = Δ(model; multiplier = multiplier)
    end

    # return -
    return Δ_dictionary
end

function 𝒟(distribution::Type{T}, ticker_symbol_array::Array{String,1}, data::Dict{String, DataFrame};
    colkey::Symbol = :Δ)::ContinuousMultivariateDistribution where {T <: ContinuousMultivariateDistribution}

    # how many keys and rows do we have?
    number_of_ticker_symbols = length(ticker_symbol_array)
    number_of_rows = length(data[first(ticker_symbol_array)][!,colkey])

    # initialize -
    tmp_array = Array{Float64,2}(undef, number_of_rows, number_of_ticker_symbols)

    # build the data array -
    for (ticker_index, ticker_symbol) ∈ enumerate(ticker_symbol_array)

        # grab the data for this ticker -
        tmp_data_col = data[ticker_symbol][!,colkey]

        # copy into the tmp array -
        for row_index ∈ 1:number_of_rows
            tmp_array[row_index, ticker_index] = tmp_data_col[row_index]
        end
    end

    # fit the distribution -
    return fit(distribution, transpose(tmp_array))
end

function 𝒟(distribution::Type{T}, data::DataFrame;
    colkey::Symbol = :Δ)::UnivariateDistribution where {T<:ContinuousUnivariateDistribution}

    # get the array of data from the data frame -
    data_array = data[!, colkey]

    # do the fit -
    return fit(distribution, data_array)
end

function 𝒟(distribution::Type{T}, data::DataFrame, weights::Union{Nothing,Array{Float64,1}};
    colkey::Symbol = :Δ)::UnivariateDistribution where {T<:ContinuousUnivariateDistribution}

    # get the array of data from the data frame -
    data_array = data[!, colkey]

    # check: do we have a weight array?
    if (isnothing(weights) == false)

        # do the fit w/weights -
        return fit(distribution, data_array, weights)
    else
         # do the fit w/o weights -
        return fit(distribution, data_array)
    end
end

function 𝒟(distribution::Type{T}, data::Dict{String, DataFrame};
    colkey::Symbol = :Δ, weights::Union{Nothing, Dict{String, Union{Nothing,Array{Float64,1}}}} = nothing)::Dict{String, T} where {T<:ContinuousUnivariateDistribution}

    # initialize -
    distribution_dictionary = Dict{String, T}()

    # call the single 𝒟 -
    for (key,value) ∈ data

        # get the array of data from the data frame -
        data_array = value[!, colkey]

        # check: do we have weights?
        d = nothing
        if (isnothing(weights) == false &&
            isnothing(weights[key]) == false)

            # we have a weights -
            w = weights[key]

            # fit -
            d = fit(distribution, data_array, w);
        else
            # fit a distribution -
            d = fit(distribution, data_array);
        end

        # capture -
        distribution_dictionary[key] = d;
    end

    # return data -
    return distribution_dictionary
end

function 𝒫(compare::Function, samples::Array{Float64})::Float64

    # initialize -
    number_of_samples = length(samples)
    tmp_array = BitArray(undef, (number_of_samples, 1))

    # main -
    for sample_index ∈ 1:number_of_samples

        # get the sample price -
        sample_price = samples[sample_index]

        # check: which is larger, sample or target price?
        compare(sample_price) ? tmp_array[sample_index] = 1 : tmp_array[sample_index] = 0
    end

    # sum the tmp_array -
    number_of_larger_values = sum(tmp_array)

    # compute the probability -
    return (number_of_larger_values / number_of_samples)
end

function covariance(tickers::Array{String,1}, data::Dict{String,DataFrame};
    key::Symbol = :Δ)::Array{Float64,2}

    # build a return matrix -
    number_of_tickers = length(tickers)

    # get the first data table so we can get the number of rows -
    (number_of_rows, _) = size(data[first(tickers)])

    # initialize -
    price_return_array = Array{Float64,2}(undef, number_of_rows, number_of_tickers)

    # populate the price return array -
    for col_index ∈ 1:number_of_tickers

        # get the ticker -
        ticker_symbol = tickers[col_index]

        # get data -
        df = data[ticker_symbol]

        for row_index ∈ 1:number_of_rows
            price_return_array[row_index, col_index] = df[row_index, key]
        end
    end

    # compute the cov array -
    return Statistics.cov(price_return_array)
end

function β(tickers::Array{String,1}, data::Dict{String,DataFrame};
    key::Symbol = :Δ, base::String = "SPY")::Array{Float64,1}

    # initialize -
    number_of_tickers = length(tickers)
    β_array = Array{Float64,1}(undef, number_of_tickers)

    # get the return data for the base -
    base_return_array = data[base][!,key]
    var_base = var(base_return_array)

    # compute β -
    for ticker_index ∈ 1:number_of_tickers

        # what ticker?
        ticker_value = tickers[ticker_index]
        return_array_ticker = data[ticker_value][!,key]

        # compute the β_value -
        β_value = cov(return_array_ticker, base_return_array) * (1 / var_base)

        # capture -
        β_array[ticker_index] = β_value
    end

    # return -
    return β_array
end

function sample(model::T, number_of_steps::Int64;
    number_of_sample_paths = 100)::Array{Array{Float64,2},1} where {T<:ContinuousMultivariateDistribution}

    # initialize -
    number_of_steps = number_of_steps + 1
    number_of_dimensions = length(model)
    sample_return_data = Array{Array{Float64,2},1}(undef, number_of_steps)

    for time_step_index ∈ 1:number_of_steps

        # generate a random block -
        B = rand(model, number_of_sample_paths)

        # capture -
        sample_return_data[time_step_index] = B
    end

    # return -
    return sample_return_data
end

function sample(model::T, number_of_steps::Int64;
    number_of_sample_paths = 100, number_of_strata = 1)::Array{Float64,2} where {T<:ContinuousUnivariateDistribution}

    # initialize -
    number_of_steps = number_of_steps + 1
    sample_return_data = Array{Array{Float64,1},1}(undef, number_of_steps)

	# Let's use stratefied sampling to generate the return samples -
    for time_step_index ∈ 1:number_of_steps

		tmp_vector = Array{Float64,1}()

        # sample the strata ...
        for strata_index ∈ 1:number_of_strata

            # compute a number_of_sample_paths draws from tis strata?
            for _ ∈ 1:number_of_sample_paths

                # role a random number -
                r = rand()

                # compute V -
                V₁ = (strata_index - 1)/number_of_strata + r/number_of_strata
                V₂ = (strata_index - 1)/number_of_strata + (1 - r)/number_of_strata

                # compute the quantile for this V -
                q₁ = quantile(model, V₁)
                q₂ = quantile(model, V₂)

                # grab this value -
                push!(tmp_vector, q₁)
                push!(tmp_vector, q₂)
            end
        end

        sample_return_data[time_step_index] = tmp_vector
    end

    # crunch the data together -
    sample_return_array = transpose(hcat(sample_return_data...))

    # return -
    return sample_return_array
end

function sample(models::Dict{String,T}, number_of_steps::Int64;
    number_of_sample_paths = 100, number_of_strata = 1)::Dict{String,Array{Float64,2}} where {T<:ContinuousUnivariateDistribution}

    # initialize -
    sample_dictionary = Dict{String,Array{Float64,2}}()

    # compute -
    for (ticker_symbol, model) ∈ models

        # sample this model -
        tmp_array = sample(model, number_of_steps;
            number_of_sample_paths = number_of_sample_paths, number_of_strata = number_of_strata)

        # store the samples -
        sample_dictionary[ticker_symbol] = tmp_array
    end

    # return -
    return sample_dictionary
end


struct Margins
    # critical frequency
    ω_c::Float64
    # gain cross-over frequency
    ω_g::Float64
    # gain margin
    gain_margin::Float64
    # phase margin (radians)
    phase_margin::Float64
end

"""
    margins = gain_phase_margins(g_ol, ω_c_guess=0.001, ω_g_guess=0.001)

compute critical frequency (radians / time), gain crossover frequency (radians / time),
gain margin, and phase margin (radians) of a closed loop, given its
closed loop transfer function `g_ol::TransferFunction`.

if ω_c or ω_g is not found (i.e. if either are `NaN`), but the `bode_plot` clearly shows
a critical/gain crossover frequency, adjust `ω_c_guess` or `ω_g_guess` to find the root.

# Example
```
g_ol = 2 * exp(-s) / (5 * s + 1)
margins = gain_phase_margins(g_ol)
margins.ω_c # critical freq. (radians / time)
margins.ω_g # gain crossover freq. (radians / time)
margins.gain_margin # gain margin
margins.phase_margin # phase margin (radians)
```
"""
function gain_phase_margins(g_ol::TransferFunction; ω_c_guess::Float64=0.001, ω_g_guess::Float64=0.001)
    # critical frequency ∠ G(i ω_c) = -π
    ω_c = NaN
    try
        ω_c = fzero(ω -> angle(evaluate(g_ol, im * ω)) + π, ω_c_guess, atol=2*sqrt(eps()))
    catch da_error
        if isa(da_error, Roots.ConvergenceFailed)
            ω_c = NaN
        else
            println(da_error)
            error("something went wrong when computing ω_c")
        end
    end
    # check for spurious solution (often ω_c d.n.e.) TODO check issue on Roots.jl
    if ! isapprox(angle(evaluate(g_ol, im * ω_c)), -π, rtol=0.01)
        ω_c = NaN
    end

    # gain cross over frequency | G(i ω_g) | = 1
    ω_g = NaN
    try
        ω_g = fzero(ω -> abs(evaluate(g_ol, im * ω)) - 1.0, ω_g_guess, atol=2*sqrt(eps()))
    catch da_error
        if isa(da_error, Roots.ConvergenceFailed)
            ω_g = NaN
        else
            println(da_error)
            error("something went wrong when computing ω_g")
        end
    end
    # check for spurious solution (often ω_c d.n.e.) TODO check issue on Roots.jl
    if ! isapprox(abs(evaluate(g_ol, im * ω_g)), 1.0, rtol=0.01)
        ω_g = NaN
    end
    # gain margin = 1 / | G(i ω_c) |
    gm = 1 / abs(evaluate(g_ol, im * ω_c))
    # phase margin = ∠ G(i ω_g) + π
    pm = π + angle(evaluate(g_ol, im * ω_g))
    return Margins(ω_c, ω_g, gm, pm)
end


"""
$(TYPEDEF)

A system of partial differential equations.

# Fields
$(FIELDS)

# Example

```julia
using ModelingToolkit

@parameters t x
@variables u(..)
Dxx = Differential(x)^2
Dtt = Differential(t)^2
Dt = Differential(t)

#2D PDE
C=1
eq  = Dtt(u(t,x)) ~ C^2*Dxx(u(t,x))

# Initial and boundary conditions
bcs = [u(t,0) ~ 0.,# for all t > 0
       u(t,1) ~ 0.,# for all t > 0
       u(0,x) ~ x*(1. - x), #for all 0 < x < 1
       Dt(u(0,x)) ~ 0. ] #for all  0 < x < 1]

# Space and time domains
domains = [t ∈ IntervalDomain(0.0,1.0),
           x ∈ IntervalDomain(0.0,1.0)]

pde_system = PDESystem(eq,bcs,domains,[t,x],[u])
```
"""
struct PDESystem <: ModelingToolkit.AbstractSystem
    "The equations which define the PDE"
    eqs
    "The boundary conditions"
    bcs
    "The domain for the independent variables."
    domain
    "The independent variables"
    indvars
    "The dependent variables"
    depvars
    "The parameters"
    ps
    """
    defaults: The default values to use when initial conditions and/or
    parameters are not supplied in `ODEProblem`.
    """
    defaults::Dict
    @add_kwonly function PDESystem(eqs, bcs, domain, indvars, depvars, ps = SciMLBase.NullParameters(), defaults = Dict())
        new(eqs, bcs, domain, indvars, depvars, ps, defaults)
    end
end

Base.getproperty(x::PDESystem, sym::Symbol) = getfield(x, sym)

Base.summary(prob::PDESystem) = string(nameof(typeof(prob)))
function Base.show(io::IO, ::MIME"text/plain", sys::PDESystem)
    println(io,summary(sys))
    println(io,"Equations: ", get_eqs(sys))
    println(io,"Boundary Conditions: ", get_bcs(sys))
    println(io,"Domain: ", get_domain(sys))
    println(io,"Dependent Variables: ", get_depvars(sys))
    println(io,"Independent Variables: ", get_indvars(sys))
    println(io,"Parameters: ", get_ps(sys))
    print(io,"Default Parameter Values", get_defaults(sys))
    return nothing
end


"""
    get_neg_binomial_prn(p, r, shape=1; seed=nothing)

Generate a random variable from a Negative Binomial(`p`, `r`) distribution. Optionally you can set a specific seed.

# Examples

```julia-repl
julia> get_neg_binomial_prn(.2, 2)
 16

julia> get_neg_binomial_prn(.2, 2, seed=42)
 6

```

# References

Walk, C. Handbook on statistical distributions for experimentalists. 2007.
"""
function get_neg_binomial_prn(p::Real, r::Int; seed::Union{Int, Nothing}=nothing)
    check_p(p)
    n = convert(Int, r/p * 10)
    U = bernoulli_rng(p, n, seed=seed)
    X = sum(cumsum(U, dims=1) .< r) + 1   # TODO CHECK THIS AGAINST ANOTHER METHOD (e.g. convolution)
    return X
end

"""
    neg_binomial_rng(p, r, shape=1; seed=nothing)

Generate a `shape` element array of random variables from a Negative Binomial(`p`, `r`) distribution. Optionally you can set a specific seed.

# Notes

The Negative Binomial distribution is given:

``f(x,p,r) = \\binom{x-1}{r-1} (1-p)^{x-r} p^r \\quad x = 0,1,\\dots, n``

# Examples

```julia-repl
julia> neg_binomial_rng(.5, 2)
1-element Vector{Float64}:
 3.0

julia> neg_binomial_rng(.5, 5, 5)
5-element Vector{Float64}:
  8.0
 10.0
  8.0
 13.0
 10.0

julia> neg_binomial_rng(.5, 2, (2,2))
2×2 Matrix{Float64}:
 3.0  4.0
 4.0  2.0

```

# References

Walk, C. Handbook on statistical distributions for experimentalists. 2007.
"""
function neg_binomial_rng(p::Real, r::Int, shape::Union{Int, Tuple{Vararg{Int}}}=1; seed::Union{Int, Nothing}=nothing)
    check_p(p)
    X = zeros(shape)
    X .= get_neg_binomial_prn.(p, r, seed=seed)
    return X
end

"""
    conv_neg_binomial_rng(p, r, shape=1; seed=nothing)

Generate a `shape` element array of random variables from a Negative Binomial(`p`, `r`) distribution. Optionally you can set a specific seed.

# Notes

The Negative Binomial distribution is given:

``f(x,p,r) = \\binom{x-1}{r-1} (1-p)^{x-r} p^r \\quad x = 0,1,\\dots, n``

Uses a convolution algorithm to generate random variables, which is slightly slower than [`neg_binomial_rng`](@ref).

# Examples

```julia-repl
julia> conv_neg_binomial_rng(.4, 5, 1)
1×1 Matrix{Int64}:
 8

julia> conv_neg_binomial_rng(.4, 5, 5)
5×1 Matrix{Int64}:
 11
 14
 8
 10
 13

julia> conv_neg_binomial_rng(.4, 5, (2,2))
2×2×1 Array{Int64, 3}:
[:, :, 1] =
 20  11
 7  10
```

# References

Law, A. Simulation modeling and analysis, 5th Ed. McGraw Hill Education, Tuscon, 2013.
"""
function conv_neg_binomial_rng(p::Real, r::Int, shape::Union{Int, Tuple{Vararg{Int}}}=1; seed::Union{Int, Nothing}=nothing)
    check_p(p)
    Y = geometric_rng(p, (shape..., r), seed=seed)
    X = sum(Y, dims=ndims(Y))
    return X
end


using EngEconomics, Roots

# Given
aInit = -200000
aAnnual = 30000
aScrap = 45000
aLife = 20

bInit = -180000
bAnnual = 24000
bScrap = 20000
bLife = 15

MARR = 0.09

# Find Annual Worth Analysis
aAW = aInit * capitalRecoveryFactor(MARR, aLife) + aAnnual + aScrap * sinkingFundFactor(MARR, aLife)

bAW = bInit * capitalRecoveryFactor(MARR, bLife) + bAnnual + bScrap * sinkingFundFactor(MARR, bLife)

if aAW > bAW
	println("A")
else
	println("B")
end


using ApproxFun

#The following solves the Airy ODE with dirichlet boundary conditions

x=Fun(identity,[-1000.,15.])   # Fun corresponding to multiplication by x, on [-100,15]
d=domain(x)
D=Derivative()             # The derivative operator
B=dirichlet()              # Dirichlet boundary conditions, [u(-100),u(15)]

#Construct operator

A=[B;D^2-x]                # This is dirichlet conditions and u'' - x u
b=[airyai(first(d)),0.]         # We want it to equal airyai(-100) at -100, and 0 at
                           # 10, with 0 rhs
#Solve ODE

u=A\b                      # u satisfies A*u = b, or in other words,
                           # B*u = [airyai(-100.),0.] and (D2 - x)*u = 0.

# Check the accuracy
norm(u - Fun(airyai,d))

## We now solve with Neumann conditions

B=neumann()
A=[B;D^2-x]
b=[airyaiprime(first(d)),0.,0.]

u=A\b

# Check the accuracy
norm(u - Fun(airyai,d))


function romberg(f, a, b, n)
	r = zeros(2, n)
	h = b - a
	r[1, 1] = (h/2) * (f(a) + f(b))
	for i ∈ 2:n
		tsum = 0
		for k ∈ 1:(2^(i - 2))
			tsum += f(a + (k - 0.5) * h)
		end
		r[2, 1] = (1/2)*(h*tsum + r[1, 1])
		for j ∈ 2:i
			r[2, j] = r[2, j - 1] + (r[2, j - 1] - r[1, j - 1])/(4^(j - 1) - 1)
		end
		h = h/2
		for j ∈ 1:i
			r[1, j] = r[2, j]
		end
	end
	return r[2, n]
end

f(x) = sin(x)
println(romberg(f, 0, 3, 20))


##
import LinearAlgebra as linalg
import Plots as plt

include("KalmanFilter.jl")
import .KalmanFilter as kf

##
function get_data()
    # Observations
    x = [4000.0 4260.0 4550.0 4860.0 5110.0] # position [m]
    v = [ 280.0  282.0  285.0  286.0  290.0] # velocity [m/s]
    # x = [4000.0 4260.0 4550.0 4860.0 5110.0 5220.0 5115.0 4995.0 4871.0] # position [m]
    # v = [ 280.0  282.0  285.0  286.0  290.0  295.0  292.0  287.0  284.0] # velocity [m/s]
    X = [x; v]

    # Initial values
    a = 2.0 # acceleration
    Δt = 1.0 # Time step
    ΔP = [20.0; 5.0] # Process errors in process covariance matrix
    Δ = [25.0; 6.0] # Observation errors
    return X, a, Δ, ΔP, Δt
end

## Define the state space model

function define_model(X, a, Δ, ΔP, Δt)
    m, n = size(X)
    Q = zeros(m, m) # Process noise covariance matrix
    F = [1.0 Δt; 0.0 1.0] # Transition matrix
    B = [Δt^2/2.0 Δt]' # Control-input matrix
    u = 2.0 # # Control vector, acceleration
    R = linalg.Diagonal([Δ[1]^2 0.0; 0.0 Δ[2]^2]) # Sensor noise covariance matrix
    H = kf.eye(m) # Measurement matrix
    w = zeros(m)
    v = zeros(m)
    # Initial predicted process covariance matrix
    P = linalg.Diagonal([ΔP[1]^2 ΔP[1]*ΔP[2]; ΔP[2]*ΔP[1] ΔP[2]^2])
    return P, Q, R, F, H, B, u, m, n
end

## Data input
function data_input(X, n)
    X = [ [X[1,i], X[2,i]] for i in 1:n ]
    Xp = [ X[1] ] # Initialize predicted state
    Xk = copy(Xp) # Initialized new state matrix
    return Xp, Xk, X
end

## Run Kalman Filter
function run_kf(X, P, F, H, Q, R, B, u; filter=kf.CholeskySqrt())
    n = size(X, 2)
    _, _, Xx = data_input(X, n)
    k = 1
    S = kf._innovation_covariance(P, H, R)
    K = kf._kalman_gain(P, H, S)
    skf = [ kf.LinearKF(Xx[k], Xx[k], P, K, S, Xx[1], filter) ]
    while k < n
        s = kf.linear_kalman(
            Xx[k+1], skf[k].xk, skf[k].P, F, H, Q, R;
            B=B, u=u, sqrt_filter=filter,
        )
        push!(skf, s)
        k += 1
    end
    return skf
end

## Smoothers
function run_smoothers_bts(Xk, F, Σ)
    k = length(Xk)
    sm = Vector{kf.RTSSmoother}(undef, k)
    sm[k] = kf.RTSSmoother(Xk[k], Σ[k])
    ksmoother = kf.RauchTungStriebel()
    while k > 1
        s = kf.kalman_smooth(ksmoother, sm[k].xk, Xk[k-1], Xk[k], Σ[k-1], Σ[k], F)
        k -= 1
        sm[k] = s
    end
    return sm
end

function run_smoothers_mbf(skf, F, H)
    m = size(skf[1].xk, 1)
    k = length(skf)
    sm = Vector{kf.MBFSmoother}(undef, k)
    ksmoother = kf.ModifiedBrysonFrazier()
    # Λ, λ = zeros(size(lsm.F)), zeros(size(lsm.F,1))
    SH = skf[k].S \ H'
    Λ, λ = SH*H, -SH*skf[k].y
    sm[k] = kf.MBFSmoother(skf[k].xk, skf[k].P, Λ, λ)
    while k > 1
        s = kf.kalman_smooth(
            ksmoother,
            skf[k-1].xk,
            skf[k-1].P,
            skf[k-1].K,
            F,
            H,
            sm[k].L,
            sm[k].l,
            skf[k-1].S,
            skf[k-1].y,
        )
        k -= 1
        sm[k] = s
    end
    return sm
end

## Plots
function plot_kf(Xp, Xk, Xx, Δ)
    # Xp, Xk, Xx = reduce(hcat, Xp), reduce(hcat, Xk), reduce(hcat, Xx)
    p1 = plt.plot(Xp[1,:], m=:o, label="Predicted", legend=:topleft, ylabel="Position [m]", xlabel="Time [s]")
    plt.plot!(p1, Xx[1,:], m=:s, label="Measured", ribbon=Δ[1])
    plt.plot!(p1, Xk[1,:], m=:p, label="KF")
    p2 = plt.plot(Xp[2,:], m=:o, label="Predicted", legend=:topleft, ylabel="Velocity [m/s]", xlabel="Time [s]")
    plt.plot!(p2, Xx[2,:], m=:s, label="Measured", ribbon=Δ[2])
    plt.plot!(p2, Xk[2,:], m=:p, label="KF")
    p = plt.plot(p1, p2, size=(700,350))
    return p
end

function plot_kf_smooth(Xp, Xk, Xx, Xks, Δ)
    # Xp, Xk = reduce(hcat, Xp), reduce(hcat, Xk)
    # Xx, Xks = reduce(hcat, Xx), reduce(hcat, Xks)
    p1 = plt.plot(Xp[1,:], m=:o, label="Predicted", legend=:topleft, ylabel="Position [m]", xlabel="Time [s]")
    plt.plot!(p1, Xx[1,:], m=:s, label="Measured", ribbon=Δ[1])
    plt.plot!(p1, Xk[1,:], m=:p, label="KF")
    plt.plot!(p1, Xks[1,:], m=:h, label="Smooth")
    p2 = plt.plot(Xp[2,:], m=:o, label="Predicted", legend=:topleft, ylabel="Velocity [m/s]", xlabel="Time [s]")
    plt.plot!(p2, Xx[2,:], m=:s, label="Measured", ribbon=Δ[2])
    plt.plot!(p2, Xk[2,:], m=:p, label="KF")
    plt.plot!(p2, Xks[2,:], m=:h, label="Smooth")
    p = plt.plot(p1, p2, size=(700,350))
    return p
end

## Run KFs
X, a, Δ, ΔP, Δt = get_data()
P, Q, R, F, H, B, u, m, n = define_model(X, a, Δ, ΔP, Δt)

## Simple Kalman filter
skf = run_kf(X, P, F, H, Q, R, B, u; filter=kf.CholeskyModSqrt())
Xp, Xk = kf.unpack(skf, [:xp, :xk])
p = plot_kf(Xp, Xk, X, Δ)

## Rauch Tung Striebel Smoother
Σ, Xk2m = kf.unpack_matrix(skf, [:P, :xk])
skfm = run_smoothers_bts(Xk2m, F, Σ)
Xk2m = kf.unpack(skfm, :xk)
p2m = plot_kf_smooth(Xp, Xk, X, Xk2m, Δ)

## Modified Bryson–Frazier Smoother
skfmb = run_smoothers_mbf(skf, F, H)
Xk2mb = kf.unpack(skfmb, :xk)
p2m = plot_kf_smooth(Xp, Xk, X, Xk2mb, Δ)


N = 100
h = 1.0/N
y = zeros(N)
y[1] = 0
t = linrange(0,2,N)

function func(t,x)
    return x^(1/3.0)
end

for i=1:N-1
    y[i+1] = y[i] + h*func(t[i], y[i])
end
println("y", "  ", "y(t)", "  ", "Error")
for i=1:N
    println(t[i]^(3/2.0), "  ", y[i], " ", abs(t[i]^1.5-y[i])/t[i]^1.5)
end


using LinearAlgebra
using Plots

include("cheb.jl")
include("parameters.jl")
include("geometry.jl")
include("profile.jl")
include("build_A.jl")
include("compute_spectrum.jl")
include("mesh.jl")
include("plot_fields.jl")

Omega = 2*π/(24*3600)
lat   = π/4

params=parameters(
     H = 2e3,
    Ly = 600e3,
    f₀ = 2*Omega*cos(lat),
     g = 10,
    Lj = 60e3,
    Uj = 1.0,
    Ny = 200,
    dk = 5e-2,
    kₘ = 2e0
    );

params.Fr = (params.f₀ * params.Lj)^2/(params.g * params.H);
params.Ro =  params.Uj/(params.f₀ * params.Lj)

print("\n")
print("Linear-Stability-Caluculator\n")
print("============================\n\n")
print("Repo:      Linear-Stability-Calculators\n")
print("Code:      ShallowWater/Julia/linear_stability_shallow_water.jl\n")
print("Model:     Rotating Shallow Water\n")
print("Geometry:  Cartesian\n")
print("Structure: Bickley Jet\n")
print("\n")
print("Nondimensional Parameters\n")
print("=========================\n")
print("Fr = ", params.Fr, "\n")
print("Ro = ", params.Ro, "\n")

# Wavenumbers
ks = collect(params.dk:params.dk:params.kₘ) / params.Lj;
Nk = length(ks);

# Grid and basic state
Dy, Dy2, y = geometry(params);
U,  E      = profile(         y, params);

plot_basic_state(y, U, E, params.Ly, "basic_state.png")

# initialize fields to store
Nmodes = 2
     σ = zeros(Nmodes, Nk);
     ω = zeros(Nmodes, Nk);
σmodes = zeros(ComplexF64, 3*params.Ny+1, Nmodes, Nk);

# Compute growth rates
for cnt in 1:Nk
    local k = ks[cnt]
    local A = build_A(k, U, E, Dy, y, params);
    local σ[:,cnt], ω[:,cnt], σmodes[:,:,cnt] = compute_spectrum(A, k, params, Nmodes);
end

plot_growth_rates(ks, σ, Nmodes, "growth_rates_Cartesian_Bickley_jet.png")

mode_number = 1;
      σ_max = maximum(σ[mode_number,:]);
    k_index = sortperm(σ[mode_number,:],rev=true)[1];
          k = ks[k_index]; # pick wavenumber

plot_1D_streamfunction(k_index, k, y, σmodes, mode_number, "modes_1D_streamfunction.png")
plot_2D_streamfunction(k_index, k, y, σmodes, mode_number, "modes_2D_streamfunction.png")
plot_2D_vorticity( Dy, k_index, k, y, σmodes, mode_number, "modes_2D_vorticity.png")


export  w_legendre,
        build_w_jacobi,
        w_jacobi,
        w_laguerre,
        w_hermite,
        build_w_genhermite,
        build_w_genlaguerre,
        w_meixner_pollaczek,
        build_w_meixner_pollaczek,

        w_gaussian,
        w_uniform01,
        w_uniform_11,
        w_logistic,
        w_genhermite,
        build_w_beta,
        build_w_gamma

_throwError(t) = throw(DomainError(t, "not in support"))

function w_legendre(t)
    -1. <= t <= 1. ? 1. : _throwError(t)
end

function build_w_jacobi(a,b)
    return t->w_jacobi(t,a,b)
end

function w_jacobi(t,a,b)
    -1. <= t <= 1. ? (1-t)^a*(1+t)^b : _throwError(t)
end

function w_hermite(t)
    exp(-t^2)
end

function build_w_genhermite(mu)
    return t->w_genhermite(t,mu)
end

function w_genhermite(t,μ)
    abs(t)^(2*μ)*exp(-t^2)
end

function build_w_genlaguerre(a)
    return t -> w_genlaguerre(t,a)
end

function w_laguerre(t)
    t >= 0. ? exp(-t) : _throwError(t)
end

function w_meixner_pollaczek(t,lambda,phi)
    1 / (2pi) * exp((2*phi - pi)*t) * abs(gamma(lambda+im*t))^2
end

function build_w_meixner_pollaczek(lambda,phi)
    t->w_meixner_pollaczek(t,lambda,phi)
end

##################################################
# probability density functions
function w_gaussian(t)
    1 / (sqrt(2*pi))*exp(-0.5*t^2)
end

function build_w_beta(α,β)
    return t->w_beta(t,α,β)
end

function w_beta(t,α,β)
    -1 <= t <= 1 ? t^(α-1)*(1-t)^(β-1)/beta(α,β) : _throwError(t)
end

function build_w_gamma(α)
    return t->w_gamma(t,α)
end

function w_gamma(t,α)
    t >= 0. ? (1/gamma(α)*Float64(t)^(α-1)*exp(-t)) : _throwError(t)
end

function w_uniform01(t)
    0. <= t <= 1. ? 1. : _throwError(t)
end

function w_uniform_11(t)
    -1. <= t <= 1. ? 0.5 : _throwError(t)
end

function w_logistic(t)
    0.25*sech(0.5t)^2
end


using EngEconomics

# Given
# Both
i = 0.08
# Option 1: Get 100,000 -> 120,000 -> 140,000 etc.
n = 30
A = 100000
G = 10000

totalOption1 = G * singlePaymentPresentWorthArithmetic(i, n) + A * seriesPresentAmountFactor(i, n)
totalOption2 = 2500000

if totalOption1 > totalOption2
	println("Don\'t take the money from Gill")
else
	println("Take the money from Gill")
end


# this function shows the behavior of the
# likelihood, the log-likelihood and the average
# log-likelihood as the sample size increases
using Distributions
function main(n)
p = 0.3
y = rand(Bernoulli(p),n)
f =  pdf(Bernoulli(p), y)
@show ℒ = prod(f)
@show lnℒ = sum(log.(f))
@show avglnℒ = (1/n)*sum(log.(f))
nothing
end


"""
Compute the stopping times of numbers subjected to the Collatz sequence.
https://en.wikipedia.org/wiki/Collatz_conjecture
"""

include("../ragamuffin.jl")

function collatz_length(x)
    if x == 1
        return 0
    elseif x % 2 == 0
        return 1 + collatz_length(x / 2)
    else
        return 1 + collatz_length(3 * x + 1)
    end
end
N = 100
range = 1:N
expected = @time collect(map(collatz_length, range))

function ragamuffin_collatz()
    T = Int32
    program = initialize(map(T, range) |> collect, T(-1), Frame, 32, T)
    add!(program, :root, (:end, :increment), value_function=x -> x.value == 1)
    add!(program, :increment, :parity, dest_function = x -> 1)
    add!(program, :parity, (:even, :odd), value_function = x -> x.value % 2 == 0)
    add!(program, :even, value_function = x -> x.value > 0 ? Cell{T}(T(x.value / 2), x.depth, x.output_index) : x)
    add!(program, :odd, value_function = x -> Cell{T}(T(x.value * 3 + 1), x.depth, x.output_index))
    @time run(program)
    return program.dest
end

batched = ragamuffin_collatz()
@info batched
@assert expected == batched


# Matern 3/2 isotropic covariance function

"""
    Mat32Iso <: MaternIso

Isotropic Matern 3/2 kernel (covariance)
```math
k(x,x') = σ²(1 + √3|x-x'|/ℓ)\\exp(-√3|x-x'|/ℓ)
```
with length scale ``ℓ`` and signal standard deviation ``σ``.
"""
mutable struct Mat32Iso{T<:Real} <: MaternIso
    "Length scale"
    ℓ::T
    "Signal variance"
    σ2::T
    "Priors for kernel parameters"
    priors::Array
end

"""
Matern 3/2 isotropic covariance function

    Mat32Iso(ll::T, lσ::T)

# Arguments
  - `ll::Real`: length scale (given on log scale)
  - `lσ::Real`: signal standard deviation (given on log scale)
"""
Mat32Iso(ll::T, lσ::T) where T = Mat32Iso{T}(exp(ll), exp(2 * lσ), [])

function set_params!(mat::Mat32Iso, hyp::AbstractVector)
    length(hyp) == 2 || throw(ArgumentError("Matern 3/2 has two parameters, received $(length(hyp))."))
    mat.ℓ, mat.σ2 = exp(hyp[1]), exp(2 * hyp[2])
end

get_params(mat::Mat32Iso{T}) where T = T[log(mat.ℓ), log(mat.σ2) / 2 ]
get_param_names(mat::Mat32Iso) = [:ll, :lσ]
num_params(mat::Mat32Iso) = 2

cov(mat::Mat32Iso, r::Number) =
    (s = √3 * r / mat.ℓ; mat.σ2 * (1 + s) * exp(-s))

@inline dk_dll(mat::Mat32Iso, r::Real) =
    (s = √3 * r / mat.ℓ; mat.σ2 * s^2 * exp(-s))


module Exponential

using ..Auxiliary, ..TensorTrain

export trigevalmask, trigeval, trigrefmask, trigrefmask2, trigdiffmask, trigdiff, trigdec, trigdeceval!
export cosfactor, cosdec

function trigevalmask(t::Vector{T}, ν::Vector{T}) where {T<:AbstractFloat}
	n = length(t)
	r = length(ν)
	if r == 0
		throw(ArgumentError("the number of frequencies should be positive"))
	end
	V = zeros(T, n, 2*r)
	for α ∈ 1:r
		for i ∈ 1:n
			V[i,2*α-1] = cos(π*ν[α]*t[i])
			V[i,2*α  ] = sin(π*ν[α]*t[i])
		end
	end
	return V
end

function trigeval(t::Vector{T}, ν::Vector{T}, c::Vector{T}) where {T<:AbstractFloat}
	n = length(t)
	r = length(ν)
	if r == 0
		throw(ArgumentError("the number of frequencies should be positive"))
	end
	if length(c) ≠ 2*r
		throw(ArgumentError("ν and c are incompatible in size"))
	end
	u = zeros(T, length(t))
	for i ∈ 1:n
		for α ∈ 1:r
			u[i] += c[2*α-1]*cos(π*ν[α]*t[i])+c[2*α]*sin(π*ν[α]*t[i])
		end
	end
	return u
end

function trigrefmask(η::T, ν::Vector{T}) where {T<:AbstractFloat}
	r = length(ν)
	if r == 0
		throw(ArgumentError("the number of frequencies should be positive"))
	end
	W = zeros(T, 2*r, 2*r)
	for α ∈ 1:r
		W[2*α-1,2*α-1] =  cos(π*ν[α]*η)
		W[2*α,  2*α-1] = -sin(π*ν[α]*η)
		W[2*α-1,2*α  ] =  sin(π*ν[α]*η)
		W[2*α,  2*α  ] =  cos(π*ν[α]*η)
	end
	return W
end

function trigrefmask2(ν::Vector{T}) where {T<:AbstractFloat}
	r = length(ν)
	if r == 0
		throw(ArgumentError("the number of frequencies should be positive"))
	end
	W1 = trigrefmask(convert(T, -1/2), ν)
	W2 = trigrefmask(convert(T, 1/2), ν)
	W = [W1[:] W2[:]]
	W = reshape(W, 2*r, 2*r, 2)
	return W
end

function trigdiffmask(ν::Vector{T}) where {T<:AbstractFloat}
	r = length(ν)
	if r == 0
		throw(ArgumentError("the number of frequencies should be positive"))
	end
	W = zeros(T, 2*r, 2*r)
	for α ∈ 1:r
		W[2*α-1,2*α  ] =  π*ν[α]
		W[2*α,  2*α-1] = -π*ν[α]
	end
	return W
end

function trigdiff(ν::Vector{T}, c::Vector{T}) where {T<:AbstractFloat}
	r = length(ν)
	if length(c) ≠ 2*r
		throw(ArgumentError("ν and c are incompatible in size"))
	end
	d = zeros(T, 2*r)
	for α ∈ 1:r
		d[2*α-1] =  π*ν[α]*c[2*α]
		d[2*α]   = -π*ν[α]*c[2*α-1]
	end
	return d
end

function cosfactor(τ::Vector{T}, c::T) where {T<:AbstractFloat}
	n = length(τ)
	if n == 0
		throw(ArgumentError("τ should be nonempty"))
	end
	U = zeros(T, 2, n, 2)
	for i ∈ 1:n
		α = c*cos(τ[i])
		β = c*sin(τ[i])
		U[1,i,1] =  α
		U[2,i,1] =  β
		U[1,i,2] = -β
		U[2,i,2] =  α
	end
	return U
end

function cosdec(τ::Vector{Vector{T}}, c::Vector{T}) where {T<:AbstractFloat}
	L = length(τ)
	if length(c) ≠ L
		throw(ArgumentError("τ and c should be of the same length"))
	end
	n = [ size(τ[ℓ]) for ℓ ∈ 1:L ]
	if any(τ .== 0)
		throw(ArgumentError("all elements of τ should be nonempty"))
	end
	U = [ cosfactor(τ[ℓ], c[ℓ]) for ℓ ∈ 1:L ]
	U[1] = U[1][1:1,:,:]
	U[L] = U[L][:,:,1:1]
	U = dec(U)
	return U
end

function trigdec(ν::Vector{T}, c::Vector{T}, L::Int; major::String="last") where {T<:AbstractFloat}
	if major ∉ ("first", "last")
		throw(ArgumentError("major should be either \"last\" (default) or \"first\""))
	end
	r = length(ν)
	if length(c) ≠ 2*r
		throw(ArgumentError("ν and c are incompatible in size"))
	end
	U = dec(T, 1, L+1)
	U[1] = factor(Matrix{T}(c'), 1, [])
	for ℓ ∈ 1:L
		U[ℓ+1] = permutedims(trigrefmask2(ν/2^(ℓ-1)), (2,3,1))
	end
	(major == "last") && decreverse!(U)
	return U
end

function trigdeceval!(U::Dec{T,N}, t::Vector{T}, ν::Vector{T}; major::String="last") where {T<:AbstractFloat,N}
	if major ∉ ("first", "last")
		throw(ArgumentError("major should be either \"last\" (default) or \"first\""))
	end
	if N ≠ 3
		throw(ArgumentError("only one mode index per factor is currently supported"))
	end
	n = length(t)
	r = length(ν)
	L = declength(U)
	p = decrank(U)
	p = (major == "last") ? p[1] : p[L+1]
	if p ≠ 2*r
		throw(ArgumentError("the ", (major == "last") ? "first" : "last", " rank of U and the size of ν are incompatible"))
	end
	W = trigevalmask(t, ν)
	if major == "last"
		W = reshape(W, 1, n, 2*r)
		W = Array{T,N}(W)
		decpushfirst!(U, W)
	else
		W = reshape(transpose(W), 2*r, n, 1)
		W = Array{T,N}(W)
		decpush!(U, W)
	end
	return U
end

end


"""
    planepoint(point::Point, triangle::Polygon)

Take a triangular plane as a Polygon and a Point within that triangle
and returns the z-value at that point.

# Examples
```jldoctest
julia> point = Point([-175, 22])
Point([-175.0, 22.0])

julia> triangle = Polygon([[-174.55, 32.54, 55], [-186.94, 17.64, 24.5], [-167.95, 17.81, 33.6], [-174.55, 32.54, 55]])
Polygon(Array{Array{Float64,1},1}[[[-174.55, 32.54, 55.0], [-186.94, 17.64, 24.5], [-167.95, 17.81, 33.6], [-174.55, 32.54, 55.0]]])

julia> planepoint(point, triangle)
37.28550123965308
```
"""
function planepoint(point::Point, triangle::Polygon)
    pCoords = point.coordinates
    tCoords = triangle.coordinates

    outer = tCoords[1]
    length(outer) < 4 && throw(error("The outer ring of a Polygon must have 4 or more Points."))

    x = pCoords[1]
    y = pCoords[2]

    x1 = outer[1][1]
    y1 = outer[1][2]
    z1 = outer[1][3]

    x2 = outer[2][1]
    y2 = outer[2][2]
    z2 = outer[2][3]

    x3 = outer[3][1]
    y3 = outer[3][2]
    z3 = outer[3][3]

    return (z3 * (x - x1) * (y - y2) + z1 * (x - x2) * (y - y3) + z2 * (x - x3) * (y - y1) -
            z2 * (x - x1) * (y - y3) - z3 * (x - x2) * (y - y1) - z1 * (x - x3) * (y - y2)) /
           ((x - x1) * (y - y2) + (x - x2) * (y - y3) + (x - x3) * (y - y1) -
           (x - x1) * (y - y3) - (x - x2) * (y - y1) - (x - x3) * (y - y2))
end


# Locally Linear Embedding (LLE)
# ------------------------
# Nonlinear dimensionality reduction by locally linear embedding,
# Roweis, S. & Saul, L., Science 290:2323 (2000)

"""

    LLE{NN <: AbstractNearestNeighbors, T <: Real} <: AbstractDimensionalityReduction

The `LLE` type represents a locally linear embedding model constructed for `T` type data constructed with a help of the `NN` nearest neighbor algorithm.
"""
struct LLE{NN <: AbstractNearestNeighbors, T <: Real} <: AbstractDimensionalityReduction
    nearestneighbors::NN
    component::AbstractVector{Int}
    λ::AbstractVector{T}
    proj::Projection{T}
end

## properties
outdim(R::LLE) = size(R.proj, 1)
eigvals(R::LLE) = R.λ
neighbors(R::LLE) = R.nearestneighbors.k
vertices(R::LLE) = R.component

## show
summary(io::IO, R::LLE) = print(io, "LLE(outdim = $(outdim(R)), neighbors = $(neighbors(R)))")

## interface functions
"""
    fit(LLE, data; k=12, maxoutdim=2, nntype=BruteForce, tol=1e-5)

Fit a locally linear embedding model to `data`.

# Arguments
* `data`: a matrix of observations. Each column of `data` is an observation.

# Keyword arguments
* `k`: a number of nearest neighbors for construction of local subspace representation
* `maxoutdim`: a dimension of the reduced space.
* `nntype`: a nearest neighbor construction class (derived from `AbstractNearestNeighbors`)
* `tol`: an algorithm regularization tolerance

# Examples
```julia
M = fit(LLE, rand(3,100)) # construct LLE model
R = transform(M)          # perform dimensionality reduction
```
"""
function fit(::Type{LLE}, X::AbstractMatrix{T};
             k::Int=12, maxoutdim::Int=2, nntype=BruteForce, tol::Real=1e-5) where {T<:Real}
    # Construct NN graph
    NN = fit(nntype, X, k)
    D, E = knn(NN, X)
    _, C = largest_component(SimpleWeightedGraph(adjmat(D,E)))

    X = @view X[:, C]
    n = length(C)

    # Correct indexes of neighbors if more then one connected component
    Ec = E
    if size(E,2) != n
        R = Dict(zip(C, collect(1:n)))
        Ec = zeros(Int,k,n)
        for i in 1 : n
            Ec[:,i] = map(j->get(R,j,C[i]), E[:,C[i]])
        end
    end

    if k > maxoutdim
        @warn("k > maxoutdim: regularization will be used")
    else
        tol = 0
    end

    # Reconstruct weights and compute embedding:
    # M = (I - w)'(I - w) = I - w'I - Iw + w'w
    M = spdiagm(0 => fill(one(T), n))
    Ones = fill(one(T), k, 1)
    for i in 1 : n
        J = Ec[:,i]
        Z = view(X, :, J) .- view(X, :, i)
        G = transpose(Z)*Z
        G += I * tol # regularize
        w = vec(G \ Ones)
        w ./= sum(w)
        ww = w*transpose(w)
        for (l, j) in enumerate(J)
            M[i,j] -= w[l]
            M[j,i] -= w[l]
            for (m, jj) in enumerate(J)
                M[j,jj] = ww[l,m]
            end
        end
    end

    λ, V = decompose(M, maxoutdim)
    return LLE{nntype, T}(NN, C, λ, rmul!(transpose(V), sqrt(n)))
end

"""
    transform(R::LLE)

Transforms the data fitted to the LLE model `R` into a reduced space representation.
"""
transform(R::LLE) = R.proj


using GLM, QuantileRegression

# Load data
url = "http://vincentarelbundock.github.io/Rdatasets/csv/quantreg/engel.csv"
# TODO: Make automatic URL downloads work
dat = readtable("engel.csv")

# Fit least absolute deviation model (quantile =.5)
res = qreg(:(foodexp~income), dat, .5)
describe(res)

# Fit quantile regression for a bunch of different quantiles
dat_plot = [summary(qreg(:(foodexp ~ income), dat, i/10))[2,:] for i in 1:9]
dat_plot = reduce(vcat, dat_plot)

# Fit OLS model to compare
res_lm = lm(:(foodexp~income), dat)
ols = coeftable(res_lm)[2,1]
ols = rep(ols, 9)

# Plot results
using Winston

x = [i/10 for i=1:9]
y = dat_plot["Estimate"]
p = FramedPlot()
add(p, Curve(x, dat_plot["Estimate"]))
add(p, Curve(x, dat_plot["2.5%"], "type", "dash"))
add(p, Curve(x, dat_plot["97.5%"], "type", "dash"))
add(p, Curve(x, ols, "color", "red"))
setattr(p, "title", "Quantile regression: Food Expenditure ~ Income\n Red bar: OLS coefficient")
setattr(p, "xlabel", "Quantiles")
setattr(p, "ylabel", "Coefficient on Income")
file(p, "/Users/USERNAME/qreg_example_plot.png")

# > summary(res)
# 2x5 DataFrame:
#         Estimate Std.Error t value     2.5%    97.5%
# [1,]     81.4823   14.6345 5.56783  52.7987  110.166
# [2,]    0.560181 0.0131756 42.5164 0.534356 0.586005
# insheet using engel.csv
# qreg foodexp income, vce(iid, kernel(epan2))

# . qreg foodexp income, vce(iid, kernel(epan2))
# Iteration  1:  WLS sum of weighted deviations =  18051.196
#
# Iteration  1: sum of abs. weighted deviations =  18035.127
# Iteration  2: sum of abs. weighted deviations =  17815.249
# Iteration  3: sum of abs. weighted deviations =  17567.085
# Iteration  4: sum of abs. weighted deviations =  17559.932
#
# Median regression                                    Number of obs =       235
#   Raw sum of deviations 46278.06 (about 582.54126)
#   Min sum of deviations 17559.93                     Pseudo R2     =    0.6206
#
# ------------------------------------------------------------------------------
#      foodexp |      Coef.   Std. Err.      t    P>|t|     [95% Conf. Interval]
# -------------+----------------------------------------------------------------
#       income |   .5601805   .0131763    42.51   0.000     .5342206    .5861403
#        _cons |   81.48233   14.63518     5.57   0.000     52.64815    110.3165
# ------------------------------------------------------------------------------


immutable BetaPrime <: ContinuousUnivariateDistribution
    betad::Beta

    BetaPrime(α::Float64, β::Float64) = new(Beta(α, β))
    BetaPrime(α::Float64) = new(Beta(α))
    BetaPrime() = new(Beta())
end

@distr_support BetaPrime 0.0 Inf

#### Parameters

params(d::BetaPrime) = params(d.betad)

#### Statistics

mean(d::BetaPrime) = ((α, β) = params(d); β > 1.0 ? α / (β - 1.0) : NaN)

mode(d::BetaPrime) = ((α, β) = params(d); α > 1.0 ? (α - 1.0) / (β + 1.0) : 0.0)

function var(d::BetaPrime)
    (α, β) = params(d)
    β > 2.0 ? α * (α + β - 1.0) / ((β - 2.0) * (β - 1.0)^2) : NaN
end

function skewness(d::BetaPrime)
    (α, β) = params(d)
    if β > 3.0
        s = α + β - 1.0
        2.0 * (α + s) / (β - 3.0) * sqrt((β - 2.0) / (α * s))
    else
        return NaN
    end
end

#### Evaluation

function logpdf(d::BetaPrime, x::Float64)
    (α, β) = params(d)
    (α - 1.0) * log(x) - (α + β) * log1p(x) - lbeta(α, β)
end

pdf(d::BetaPrime, x::Float64) = exp(logpdf(d, x))

cdf(d::BetaPrime, x::Float64) = cdf(d.betad, x / (1.0 + x))
ccdf(d::BetaPrime, x::Float64) = ccdf(d.betad, x / (1.0 + x))
logcdf(d::BetaPrime, x::Float64) = logcdf(d.betad, x / (1.0 + x))
logccdf(d::BetaPrime, x::Float64) = logccdf(d.betad, x / (1.0 + x))

quantile(d::BetaPrime, p::Float64) = (x = quantile(d.betad, p); x / (1.0 - x))
cquantile(d::BetaPrime, p::Float64) = (x = cquantile(d.betad, p); x / (1.0 - x))
invlogcdf(d::BetaPrime, p::Float64) = (x = invlogcdf(d.betad, p); x / (1.0 - x))
invlogccdf(d::BetaPrime, p::Float64) = (x = invlogccdf(d.betad, p); x / (1.0 - x))

#### Sampling

function rand(d::BetaPrime)
    (α, β) = params(d)
    rand(Gamma(α)) / rand(Gamma(β))
end


using LinearAlgebra, Plots

Rx(θ::Real) = [1 0 0 0;
        0 cos(θ) -sin(θ) 0;
        0 sin(θ) cos(θ) 0;
        0 0 0 1]

Ry(θ::Real) = [cos(θ) 0 -sin(θ) 0;
        0 1 0 0;
        sin(θ) 0 cos(θ) 0;
        0 0 0 1]

Rz(θ::Real) = [cos(θ) -sin(θ) 0 0;
        sin(θ) cos(θ) 0 0;
        0 0 1 0;
        0 0 0 1]

Tx(d::Real) = [1 0 0 d;
        0 1 0 0;
        0 0 1 0;
        0 0 0 1]

π = pi
d = 2
angToRad(ang::Real) = (π/180)*ang
θ(ang::Real = 150) = angToRad(ang) #70
ω(ang::Real = 160) = angToRad(ang)

B1 = I(4) #nada a fazer

#B2 =  Ry(π)*Tx(d)

B2 = zeros(4,4)
B2[1,1] = -1.0
B2[2,2] = 1.0
B2[3,3] = -1.0
B2[4,4] = 1.0
B2[1,4] = -d

#B3 = Rx(π)*Rz(θ())*Ry(π)*Tx(d)

# tird atom
cθ,sθ = (cos(θ()),sin(θ()))
B3 = zeros(4,4)
B3[1,1] = -cθ
B3[1,2] = -sθ
B3[1,4] = -d*cθ
B3[2,1] = sθ
B3[2,2] = -cθ
B3[2,4] = d*sθ
B3[3,3] = 1.0
B3[4,4] = 1.0

function torsionmatrix(cosθ,sinθ,cosω,sinω,d34,sign::Bool)
	if sign == true

		B=zeros(4,4)
		B[1,1] = -cosθ
		B[1,2] = -sinθ
		B[1,4] = -d34*cosθ
		B[2,1] = sinθ*cosω
		B[2,2] = -cosθ*cosω
		B[2,3] = -sinω
		B[2,4] = d34*sinθ*cosω
		B[3,1] = sinθ*sinω
		B[3,2] = -cosθ*sinω
		B[3,3] = cosω
		B[3,4] = d34*sinθ*sinω
		B[4,4] = 1
	else

		B=zeros(4,4)
		B[1,1] = -cosθ
		B[1,2] = -sinθ
		B[1,4] = -d34*cosθ
		B[2,1] = sinθ*cosω
		B[2,2] = -cosθ*cosω
		B[2,3] = sinω
		B[2,4] = d34*sinθ*cosω
		B[3,1] = -sinθ*sinω
		B[3,2] = cosθ*sinω
		B[3,3] = cosω
		B[3,4] = -d34*sinθ*sinω
		B[4,4] = 1

	end
	return B
end

Bi(ω = ω(), θ = θ(), d = d) = torsionmatrix(cos(angToRad(θ)), sin(angToRad(θ)), cos(angToRad(ω)), sin(angToRad(ω)), d, false) #Bi(ω = ω(), θ = θ(), d = d) = Rx(ω)*Rx(π)*Rz(θ)*Ry(π)*Tx(d)

Bi′(B) = B*inv(Tx(d))

unk = [1 0 0 0;
        0 1 0 0;
        0 0 1 0;
        1 1 1 1]

function sysGenerate(B, α = 1; title = true)
    if title
		return ["x/|x|" "y/|y|" "z/|z|" "i"; B*unk*α]
	else
		return B*unk*α
	end
end

function flatProjection(A, x̂ = [cos(π/4) -cos(π/4)]'*0.5; x :: Bool = true)
	if x
		return [A[2:3,1]+x̂*A[1,1] A[2:3,2]+x̂*A[1,2] A[2:3,3]+x̂*A[1,3] A[2:3,4]+x̂*A[1,4]]
	else
		return [A[1:2,1]+x̂*A[3,1] A[1:2,2]+x̂*A[3,2] A[1:2,3]+x̂*A[3,3] A[1:2,4]+x̂*A[3,4]]
	end
end

function plotFlatProjection(B; sprain=1.01, static = false)
	param = [B[1,4]+B[2,4]im, B[1,4]+B[2,4]im+B[1,1]+B[2,1]im, Inf, B[1,4]+B[2,4]im, B[1,4]+B[2,4]im+B[1,2]+B[2,2]im, Inf, B[1,4]+B[2,4]im, B[1,4]+B[2,4]im+B[1,3]+B[2,3]im]
	if static
		plot!(param, arrow = 2)
	else
		plot(param, arrow = 2)
	end
	annotate!(B[1,4]*sprain, B[2,4]*sprain, text("v", :black, :right, 10))
	annotate!(B[1,4]+B[1,1]*sprain, B[2,4]+B[2,1]*sprain, text("x", :black, :right, 10))
	annotate!(B[1,4]+B[1,2]*sprain, B[2,4]+B[2,2]*sprain, text("y", :black, :right, 10))
	annotate!(B[1,4]+B[1,3]*sprain, B[2,4]+B[2,3]*sprain, text("z", :black, :right, 10))
	# ylims!((-1.5,2)); xlims!((-1.5,1.5))
end

print("DmdgpRealization included\n")

#rotationFactor = Rz(π/2)*Rx(π/2)*Rz(-θ(30))*Rx(π)
#sysGenerate(flatProjection(rotationFactor*B2*B3*Bi(ω(230),θ(120))), 20)
#Bo = B2*B3*Bi(ω(230),θ(120))
#B = Bi(ω(230),θ(120))
#sysGenerate(flatProjection(rotationFactor*Bo*B*B*B), 20)

# a good initial image

# plotFlatProjection(flatProjection(B1, [-cos(π/4) -cos(π/4)]'*0.707))
# plotFlatProjection(flatProjection(B1*Ry(π)*Tx(2.5), [-cos(π/4) -cos(π/4)]'*0.707), static = true)
# plotFlatProjection(flatProjection(B1*B2*Rx(π)*Rz(θ(75))*Ry(π)*Tx(2.5), [-cos(π/4) -cos(π/4)]'*0.707), static = true)
# ylims!((-1,2)); xlims!((-1,4))


# Define and register smooth functions
_cos_wave(t, f, A, st, ϕ) = A*cos(2*π*f*(t - st) + ϕ)
_sin_wave(t, f, A, st, ϕ) = A*sin(2*π*f*(t - st) + ϕ)
_damped_sine_wave(t, f, A, st, ϕ, d) = exp((st-t)*d)*A*sin(2*π*f*(t-st) + ϕ)
_ramp(t, δ, st, et, h) = h/(et-st)*(_xH(t, δ, st) - _xH(t, δ, et))
_square_wave(t, δ, f, A, st) = A*2atan(sin(2π*(t-st)*f)/δ)/π
_step(t, δ, h, a) = h*(atan((t-a)/δ)/π + 0.5)
_triangular_wave(t, δ, f, A, st) = A*(1-2acos((1 - δ)sin(2π*(t-st)*f))/π)
_xH(t, δ, tₒ) = (t-tₒ)*(1+((t-tₒ)/sqrt((t-tₒ)^2+δ^2)))/2

@register_symbolic _cos_wave(t, f, A, st, ϕ)
@register_symbolic _sin_wave(t, f, A, st, ϕ)
@register_symbolic _damped_sine_wave(t, f, A, st, ϕ, damping)
@register_symbolic _ramp(t, δ, st, et, h)
@register_symbolic _square_wave(t, δ, f, A, st)
@register_symbolic _step(t, δ, h, a)
@register_symbolic _triangular_wave(t, δ, f, A, st)

"""
    ConstantVoltage(;name, V = 1.0)

Source for constant voltage,

# Parameters:
- `V`: [V] Voltage
"""
function ConstantVoltage(;name, V = 1.0)
    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters V=V
    eqs = [
        v ~ V
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0)

Generate cosine voltage.

# Parameters:
- `frequency`: [Hz] Frequency of sine wave
- `amplitude`: [V] Amplitude of sine wave
- `phase`: [rad] Phase of sine wave
- `offset`: [V] Offset of output voltage
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0)
    δ = 0.00001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        frequency=frequency
        start_time=start_time
        phase=phase
    end
    eqs = [
        v ~ _cos_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
Generate damped sine voltage.

# Parameters:
- `frequency`: [Hz] Frequency of sine wave
- `amplitude`: [V] Amplitude of sine wave
- `damping`: [1/s] Damping coefficient of sine wave
- `phase`: [rad] Phase of sine wave
- `offset`: [V] Offset of output voltage
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function ExpSineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0, damping=0.0)
    δ = 0.00001
    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        frequency=frequency
        start_time=start_time
        phase=phase
        damping=damping
    end
    eqs = [
        v ~ _damped_sine_wave(t, frequency, amplitude, start_time, phase, damping) * _step(t, δ, 1.0, start_time)
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    RampVoltage(;name, offset=0.0, start_time=0.0, duration=1.0, height=1.0)

Generate ramp voltage.

# Parameters:
- `height`: [V] Height of ramp
- `duration`: [s] Duration of ramp (= 0.0 gives a Step)
- `offset`: [V] Offset of output voltage
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function RampVoltage(;name, offset=0.0, start_time=0.0, duration=1.0, height=1.0)
    δ = 0.00001
    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        height=height
        start_time=start_time
        duration=duration
    end
    eqs = [
        v ~ _ramp(t, δ, start_time, start_time + duration, height) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    SineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0)

Generate sine voltage.

# Parameters:
- `frequency`: [Hz] Frequency of sine wave
- `amplitude`: [V] Amplitude of sine wave
- `phase`: [rad] Phase of sine wave
- `offset`: [V] Offset of output voltage
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function SineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0)
    δ = 0.00001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        frequency=frequency
        start_time=start_time
        phase=phase
    end
    eqs = [
        v ~ _sin_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0)

Generate square voltage.
"""
function SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0)
    δ = 0.0001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        start_time=start_time
    end
    eqs = [
        v ~ _square_wave(t, δ, frequency, amplitude, start_time) * _step(t, δ, 1.0, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    StepVoltage(;name, offset=0.0, start_time=0.0, height=1.0)

Generate step voltage.

# Parameters:
- `height`: [V] Height of step
- `offset`: [V] Offset of output voltage
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function StepVoltage(;name, offset=0.0, start_time=0.0, height=1.0)
    δ = 0.0001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        height=height
        start_time=start_time
    end
    eqs = [
        v ~ _step(t, δ, height, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

function TriangularVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0)
    δ = 0.00001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        frequency=frequency
        start_time=start_time
    end
    eqs = [
        v ~ _triangular_wave(t, δ, frequency, amplitude, start_time) * _step(t, δ, 1.0, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

# Current Sources ######################################################################################################
"""
    ConstantCurrent(;name, I = 1.0)

Source for constant current.

# Parameters:
- `I`: [A] Current
"""
function ConstantCurrent(;name, I = 1.0)
    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters I=I
    eqs = [
        i ~ I
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    CosineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0)

Generate cosine current.

# Parameters:
- `frequency`: [Hz] Frequency of sine wave
- `amplitude`: [A] Amplitude of sine wave
- `phase`: [rad] Phase of sine wave
- `offset`: [A] Offset of output current
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function CosineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0)
    δ = 0.00001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        frequency=frequency
        start_time=start_time
        phase=phase
    end
    eqs = [
        i ~ _cos_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    ExpSineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0, damping=0.0)

Generate damped sine current.

# Parameters:
- `frequency`: [Hz] Frequency of sine wave
- `amplitude`: [A] Amplitude of sine wave
- `damping`: [1/s] Damping coefficient of sine wave
- `phase`: [rad] Phase of sine wave
- `offset`: [A] Offset of output current
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function ExpSineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0, damping=0.0)
    δ = 0.00001
    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        frequency=frequency
        start_time=start_time
        phase=phase
        damping=damping
    end
    eqs = [
        i ~ _damped_sine_wave(t, frequency, amplitude, start_time, phase, damping) * _step(t, δ, 1.0, start_time)
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    RampCurrent(;name, offset=0.0, start_time=0.0, duration=1.0, height=1.0)

Generate ramp current.

# Parameters:
- `height`: [A] Height of ramp
- `duration`: [s] Duration of ramp (= 0.0 gives a Step)
- `offset`: [A] Offset of output current
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function RampCurrent(;name, offset=0.0, start_time=0.0, duration=1.0, height=1.0)
    δ = 0.00001
    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        height=height
        start_time=start_time
        duration=duration
    end
    eqs = [
        i ~ _ramp(t, δ, start_time, start_time + duration, height) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    SineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0)

Generate sine current.

# Parameters:
- `frequency`: [Hz] Frequency of sine wave
- `amplitude`: [A] Amplitude of sine wave
- `phase`: [rad] Phase of sine wave
- `offset`: [A] Offset of output current
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function SineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0)
    δ = 0.00001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        frequency=frequency
        start_time=start_time
        phase=phase
    end
    eqs = [
        i ~ _sin_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

function SquareCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0)
    δ = 0.0001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        start_time=start_time
    end
    eqs = [
        i ~ _square_wave(t, δ, frequency, amplitude, start_time) * _step(t, δ, 1.0, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    StepCurrent(;name, offset=0.0, start_time=0.0, height=1.0)

Generate step current.

# Parameters:
- `height`: [A] Height of step
- `offset`: [A] Offset of output current
- `start_time`: [s] Output `y = offset` for `t < start_time`
"""
function StepCurrent(;name, offset=0.0, start_time=0.0, height=1.0)
    δ = 0.0001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        height=height
        start_time=start_time
    end
    eqs = [
        i ~ _step(t, δ, height, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

function TriangularCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0)
    δ = 0.00001

    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters begin
        offset=offset
        amplitude=amplitude
        frequency=frequency
        start_time=start_time
    end
    eqs = [
        i ~ _triangular_wave(t, δ, frequency, amplitude, start_time) * _step(t, δ, 1.0, start_time) + offset
    ]

    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end