###
#   Set operations
###

# Envolpe/hull. Keeps it as a IntervalUnion
env(x :: IntervalU) = IntervalU([hull(x.v)])
hull(x :: IntervalU) = IntervalU([hull(x.v)])

# Computes the complement of a IntervalUnion
function complement(x :: IntervalU)

    v = sort(x.v)
    vLo = left.(v)
    vHi = right.(v)

    vLo[1] == -∞ ? popfirst!(vLo) : pushfirst!(vHi, -∞)
    vHi[end] == ∞ ? pop!(vHi) : push!(vLo, ∞)

    complements = interval.(vHi,vLo)

    return intervalU(complements)
end

complement(x :: Interval) = complement(intervalU(x))

# bisect x at α
function bisect( x :: IntervalU, α = 0)

    v = deepcopy(x.v)

    αIn = α .∈ v
    these = findall(αIn .== 1)

    if isempty(these); return x ; end

    this = popat!(v, these[1])

    β = (α - this.lo) /(this.hi - this.lo)

    bs = bisect(this, β)

    new = IntervalU(v ∪ bs[1] ∪ bs[2])
    new = remove_empties(new)
    sort!(new.v)
    return new

end

function intersect(x :: IntervalU, y :: IntervalU)
    intersects = [intersect(xv, yv) for xv in x.v, yv in y.v]
    return intervalU(intersects[:])
end

function intersect(x :: IntervalU, y :: Interval)
    intersects = [intersect(xv, y) for xv in x.v]
    return intervalU(intersects[:])
end

function intersect(x :: Interval, y :: IntervalU)
    intersects = [intersect(x, yv) for yv in y.v]
    return intervalU(intersects[:])
end

function setdiff(x :: IntervalU, y :: IntervalU)
    yc = complement(y)
    return intersect(x, yc)
end

\(x :: IntervalU, y :: IntervalU) = setdiff(x, y)
\(x :: Interval, y :: IntervalU) = setdiff(intervalU(x), y)
\(x :: IntervalU, y :: Interval) = setdiff(x, intervalU(y))

function ⊂(x :: IntervalU, y :: IntervalU)
    issubs = [xv .⊂ y.v for xv in x.v]
    return all(any.(issubs))
end

function ⊂(x :: Interval, y :: IntervalU)
    if length(y.v) == 1; return x ⊂ y; end
    issubs = x .⊆ y.v
    return any(issubs)
end

function ⊂( x::IntervalU, y :: Interval)
    issubs = x.v .⊂ y
    return all(issubs)
end

function ⊆(x :: IntervalU, y :: IntervalU)
    issubs = [xv .⊆ y.v for xv in x.v]
    return all(any.(issubs))
end

function ⊆(x :: Interval, y :: IntervalU)
    issubs = x .⊆ y.v
    return any(issubs)
end

function ⊆( x::IntervalU, y :: Interval)
    issubs = x.v .⊆ y
    return all(issubs)
end


## PART 1

println("Part 1");

# Define functions for this part
function f1(v̄)
    return v̄[:,1] ./ v̄[:,2];
end

function f2(v̄)
    return exp( v̄[:,3] - v̄[:,4] );
end

function f3(v̄)
    logs = Float64[];
    for vv in v̄[:,5]
        push!(logs,log(vv));
    end
    return ( v̄[:,1]./v̄[:,2] + v̄[:,3]./v̄[:,4] ) .* logs;
end

# Calculate f1, f2, and f3 for these means
println("RESUMING QUESTION 2");
println("\n\tCalculating functions of means f1v1, f2v1, ...");
f1N1 = f1(v̄1);
f2N1 = f2(v̄1);
f3N1 = f3(v̄1);

f1N2 = f1(v̄2);
f2N2 = f2(v̄2);
f3N2 = f3(v̄2);

# Find standard deviations
println("\n\tCalculating standard deviations of the functions");
σ̂truef1N1 = std(f1N1);
σ̂truef2N1 = std(f2N1);
σ̂truef3N1 = std(f3N1);

σ̂truef1N2 = std(f1N2);
σ̂truef2N2 = std(f2N2);
σ̂truef3N2 = std(f3N2);


@testset "Testing quadsums.jl" begin
    rough_kwargs = Dict(:atol => 1e-10, :rtol => 1e-10)

    @testset "test simple var sum" begin
        beta = .95
        A = 1.
        C = 0.
        H = 1.
        x0 = 1.

        val = var_quadratic_sum(A, C, H, beta, x0)

        @test isapprox(val, 20.0; rough_kwargs...)
    end

    @testset "test identity var sum" begin
        beta = .95
        A = eye(3)
        C = zeros(3, 3)
        H = eye(3)
        x0 = ones(3)

        val = var_quadratic_sum(A, C, H, beta, x0)

        @test isapprox(val, 60.0; rough_kwargs...)
    end

end  # facts


function chebyshev_polynomial_derivative(order::S,x::T) where {T<:AbstractFloat,S<:Integer}

  polynomial    = Array{T}(undef,1,order+1)
  poly_deriv    = Array{T}(undef,1,order+1)
  polynomial[1] = one(T)
  poly_deriv[1] = zero(T)

  for i = 2:order+1
    if i == 2
      polynomial[i] = x
      poly_deriv[i] = one(T)
    else
      polynomial[i] = 2*x*polynomial[i-1]-polynomial[i-2]
      poly_deriv[i] = 2*polynomial[i-1]+2*x*poly_deriv[i-1]-poly_deriv[i-2]
    end
  end

  return poly_deriv

end

function chebyshev_polynomial_derivative(order::S,x::Array{T,1}) where {T<:AbstractFloat,S<:Integer}

  poly_deriv = Array{T,2}(undef,length(x),order+1)

  for i = 1:length(x)
    poly_deriv[i,:] = chebyshev_polynomial_derivative(order,x[i])
  end

  return poly_deriv

end


import RationalApproximations: aaa_v1, aaa_v2, locate_extrema_idx
import RationalApproximations: RationalFunc, equioscillation!, minimax
using PyPlot

f = exp
S = range(-1, 1, length=201)
F = f.(S)
tol = 1e-6
max_m = 5
r, err = aaa_v1(F, S, max_m, tol)
m = length(r)

figure(1)
supp_pt = r[m].supp_pt
resid = F - r[m].(S)
plot(S, resid, supp_pt, f.(supp_pt)-r[m].(supp_pt), "o")
legend((L"$f-r$", "support points"))
title("Result using aaa_v1 (m = $m)")
grid(true)
xylims = axis()

figure(2)
r, err = aaa_v2(F, S, max_m, tol)
m = length(r)
supp_pt = r[m].supp_pt
resid = F - r[m].(S)
plot(S, resid, supp_pt, f.(supp_pt)-r[m].(supp_pt), "o")
legend((L"$f-r$", "support points"))
title("Result using aaa_v2 (m = $m)")
grid(true)
axis(xylims)

idx = locate_extrema_idx(resid, m)
x = S[idx]
figure(3)
plot(S, resid, x, f.(x)-r[m].(x), "o")
legend((L"$f-r$", "extrema"))
title("Locate extrema")
grid(true)
axis(xylims)

clamp = :no
α, β, λ = equioscillation!(f, x, supp_pt, clamp)
new_r = RationalFunc(α, β, supp_pt)
figure(4)
plot(S, f.(S) - new_r.(S),
     [-1.0, 1.0, NaN, -1.0, 1.0], [λ, λ, NaN, -λ, -λ], ":k",
     x, f.(x)-new_r.(x), "o")
grid(true)
title("Equioscillation procedure")
axis(xylims)

max_iterations = 4
x = S[idx]
r, zmin, zmax = minimax(f, x, supp_pt, (-1.0,1.0), max_iterations, clamp)
m = length(r)
figure(5)
plot(S, f.(S) - r[m].(S), x, f.(x) - r[m].(x), "o")
grid(true)
title("Result after $m Remez iterations")
axis(xylims)


module BigCombinatorics

using Combinatorics

export Fibonacci
export Factorial, DoubleFactorial, FallingFactorial, RisingFactorial
export Binomial, Catalan
export Derangements, MultiChoose, Multinomial
export Bell, Stirling1, Stirling2
export IntPartitions, IntPartitionsDistinct
export Euler, PowerSum

_master_table = Dict{Function,Dict}()

function _save(f::Function, x, val::BigInt)
    d = _master_table[f]
    d[x] = val
    nothing
end

function _has(f::Function, x)::Bool
    d = _master_table[f]
    return haskey(d,x)
end

function _get(f::Function, x)::BigInt
    d = _master_table[f]
    return d[x]
end

function _make(f::Function, T::Type)
    _master_table[f] = Dict{T,BigInt}()
    nothing
end

"""
`BigCombinatorics.cache_report()` reports how many
entries are saved for each function in the `BigCombinatorics`
module.
"""
function cache_report()
    total = 0
    for func in keys(_master_table)
        parse_func = split(string(func),".")
        func_name = last(parse_func)
        sz = length(_master_table[func])
        total += sz
        println("$sz\t$func_name")
    end
    println()
    println("$total\tTotal entries")
    nothing
end

"""
`BigCombinatorics.cache_clear(f)` clears the cached values for
the function `f`. Returns `false` if `f` values are not cached.

`BigCombinatorics.cache_clear()` clears all cached values.
"""
function cache_clear(f::Function)::Bool
    if !haskey(_master_table,f)
        return false
    end
    tab = _master_table[f]
    for k in keys(tab)
        delete!(tab,k)
    end
    return true
end

function cache_clear()
    for f in keys(_master_table)
        cache_clear(f)
    end
    true
end

"""
`Fibonacci(n)` returns the `n`-th Fibonacci number.
We begin with `Fibonacci(0)==0` and `Fibonacci(1)==1`.
"""
function Fibonacci(n::Integer)::BigInt
  if n<0
    throw(DomainError(n,"argument must be nonngative"))
  end
  if n==0
    return big(0)
  end
  if n==1
    return big(1)
  end
  if _has(Fibonacci,n)
      return _get(Fibonacci,n)
  end
  val = Fibonacci(n-1)+Fibonacci(n-2)
  _save(Fibonacci,n,val)
  return val
end

_make(Fibonacci,Integer)

"""
`Factorial(n)` returns `n!` for nonnegative integers `n`.
`Factorial(n,k)` returns `n!/k!` (to be consistent with Julia's
`factorial`.) Requires `0 <= k <= n`.

See also `FallingFactorial` and `RisingFactorial`.
"""
function Factorial(n::Integer)::BigInt
    if n < 0
        throw(DomainError(n,"argument must be nonngative"))
    end
    if n <= 1
        return big(1)
    end
    if _has(Factorial,n)
        return _get(Factorial,n)
    end
    val = big(n) * Factorial(n-1)
    _save(Factorial,n,val)
    return val
end

function Factorial(n::Integer,k::Integer)::BigInt
    if k>n
        throw(DomainError((n,k),"$k cannot exceed $n"))
    end
    return div(Factorial(n),Factorial(k))
end
_make(Factorial,Integer)

"""
`FallingFactorial(n,k)` returns `n*(n-1)*(n-2)*...*(n-k+1)`
(with a total of `k` factors). Requires `n,k >= 0`.
If `k>n` then `0` is returned.
"""
function FallingFactorial(n::Integer, k::Integer)::BigInt
  if n<0 || k<0
    throw(DomainError((n,k)," arguments must be nonnegative"))
  end
  if k>n
    return big(0)
  end
  return Factorial(n,n-k)
end

"""
`RisingFactorial(n,k)` returns `n*(n+1)*(n+2)*...*(n+k-1)`
(with a total of `k` factors). Requires `n,k >= 0`.
"""
function RisingFactorial(n::Integer,k::Integer)::BigInt
  if n<0 || k<0
    throw(DomainError((n,k),"arguments must be nonnegative"))
  end
  if k==0
    return big(1)
  end
  if n==0
    return big(0)
  end

  return FallingFactorial(n+k-1,k)
end

"""
`DoubleFactorial(n)` returns `n!!`, i.e.,
`n*(n-2)*...` with `(-1)!! == 0!! == 1!! == 1`.
"""
function DoubleFactorial(n::Integer)::BigInt
  if n<-1
    throw(DomainError(n,"argument must be at least -1"))
  end
  if n<2
    return big(1)
  end
  if _has(DoubleFactorial,n)
      return _get(DoubleFactorial,n)
  end
  val = n * DoubleFactorial(n-2)
  _save(DoubleFactorial,n,val)
  return val
end
_make(DoubleFactorial,Integer)

"""
`Binomial(n,k)` returns the binomial coefficient `n`-choose-`k`.
This is the number of `k`-element subsets of an `n`-element set.
"""
Binomial(n::Integer, k::Integer) = binomial(big(n),big(k))::BigInt

"""
`Multinomial(vec)` returns the multinomial coefficient whose
top index is the sum of `vec` (an array of `Int`s) and whose
bottom indices are given by `vec`.

This may also be called with a common-separated list of arguments,
that is, either of `Multinomial([1,2,3])` or `Multinomial(1,2,3)`.
The result is `60` in both cases as these equal `6!/(1! 2! 3!)`.

**Warning**: This is not the same as `MultiChoose`.
"""
function Multinomial(v...)::BigInt
  nv = length(v)
  for i=1:nv
    typeof(v[i])<:Integer || throw(DomainError(v,"arguments must be integers"))
    v[i]>=0 || throw(DomainError(v,"arguments must be nonngative"))
  end
  vals = [t for t in v]
  return Multinomial(vals)
end

Multinomial() = big(1)::BigInt

function Multinomial(vals::Vector{T})::BigInt where T<:Integer
  if any([t<0 for t in vals])
    throw(DomainError(vals,"arguments must be nonnegative"))
  end

  nv = length(vals)
  n  = sum(vals)
  # base cases
  if nv<=1 || n==0
    return big(1)
  end
  # reduce
  return Binomial(n,vals[end]) * Multinomial(vals[1:nv-1])
end

"""
`MultiChoose(n,k)` returns the number of `k`-element
*multisets* that can be formed using the elements of an
`n`-element set.

**Warning**: This is not the same as `Multinomial`.
"""
function MultiChoose(n::Integer,k::Integer)::BigInt
  return Binomial(n+k-1,k)
end

"""
`Catalan(n)` returns the `n`-th Catalan number.
"""
function Catalan(n::Integer)::BigInt
  n >= 0 || throw(DomainError(n,"argument must be nonnegative"))
  return div(Binomial(2n,n),n+1)
end

"""
`Derangements(n)` returns the number of permutations of
an `n`-set that have no fixed point.
"""
function Derangements(n::Integer)::BigInt
  if n<0
    throw(DomainError(n,"argument must be nonnegative"))
  end
  if n==0
    return big(1)
  end
  if n==1
    return big(0)
  end
  if _has(Derangements,n)
      return _get(Derangements,n)
  end

  val = (n-1)*(Derangements(n-1)+Derangements(n-2))
  _save(Derangements,n,val)
  return val
end

_make(Derangements,Integer)

"""
`Bell(n)` gives the `n`-th Bell number, that is,
the number of partitions of an `n`-element set.
"""
function Bell(n::Integer)::BigInt
  if n<0
    throw(DomainError(n,"argument must be nonnegative"))
  end
  if n==1 || n==0
    return big(1)
  end
  if _has(Bell,n)
      return _get(Bell,n)
  end
  N1 = n-1
  result = big(0)
  for k=0:n-1
    result += Binomial(n-1,k) * Bell(k)
  end
  _save(Bell,n,result)
  return result
end
_make(Bell,Integer)

"""
`Stirling2(n,k)` gives the Stirling number of the second kind,
that is, the number of paritions of an `n`-set into `k`-parts."
"""
function Stirling2(n::Integer,k::Integer)::BigInt
  # special cases
  if k<0 || n<0
    throw(DomainError((n,k),"arguments must be nonnegative"))
  end

  if k>n
    return big(0)
  end

  if n==0  # and by logic, k==0
    return big(1)
  end

  if k==0
    return big(0)
  end

  if n==k
    return big(1)
  end
  # END OF SPECIAL CASES, invoke recursion
  if _has(Stirling2,(n,k))
      return _get(Stirling2,(n,k))
  end

  val = Stirling2(n-1,k-1) + Stirling2(n-1,k)*k
  _save(Stirling2,(n,k),val)
  return val
end
_make(Stirling2,Tuple{Integer,Integer})

"""
`Stirling1(n,k)` gives the (signed) Stirling number
of the first kind, that is, the coefficient of `x^k`
in the poynomial `x(x-1)(x-2)...(x-n+1)`.
"""
function Stirling1(n::Integer,k::Integer)::BigInt
  # special cases
  if k<0 || n<0
    throw(DomainError((n,k),"arguments must be nonnegative"))
  end

  if k>n
    return big(0)
  end

  if n==0  # and, by logic, k==0
    return big(1)
  end

  if k==0  # and, by logic, n>0
    return big(0)
  end

  if _has(Stirling1,(n,k))
      return _get(Stirling1,(n,k))
  end

  # end of special cases, invoke recursion

  val = Stirling1(n-1,k-1) - (n-1)*Stirling1(n-1,k)
  _save(Stirling1,(n,k),val)
  return val
end

_make(Stirling1,Tuple{Integer,Integer})

"""
`IntPartitions(n)` is the number of partitions of the integer `n`.

`IntPartitions(n,k)` is the number of partitions of the integer
`n` with exactly `k` (nonzero) parts.
"""
function IntPartitions(n::Integer,k::Integer)::BigInt
  if n<0 || k<0
    throw(DomainError((n,k),"arguments must be nonnegative"))
  end
  # lots of special cases
  if k>n
    return big(0)
  end
  if n==0
    return big(1)
  end
  if k==0
    return big(0)
  end
  if k==n || k==1
    return big(1)
  end
  if _has(IntPartitions,(n,k))
      return _get(IntPartitions,(n,k))
  end

  val =  sum([IntPartitions(n-k,i) for i=0:k])
  _save(IntPartitions,(n,k),val)
  return val
end

function IntPartitions(n::Integer)::BigInt
    if n<0
        throw(DomainError(n,"argument must be nonnegative"))
    end
    if _has(IntPartitions,n)
        return _get(IntPartitions,n)
    end
    val = sum([IntPartitions(n,k) for k=0:n])
    _save(IntPartitions,n,val)
    return val
end

_make(IntPartitions,Union{Tuple{Integer,Integer},Integer})

"""
`IntPartitionsDistinct(n,k)` is the number of partitions of
the integer `n` into exactly `k` *distinct* parts.

`IntPartitionsDistinct(n)` is the number of partitions of `n`
into *distinct* parts.
"""
function IntPartitionsDistinct(n::Integer,k::Integer)::BigInt
  if n<0 || k<0
    throw(DomainError((n,k),"arguments must be nonnegative"))
  end
  Ck2 = div(k*(k-1),2)
  if n < Ck2
    return big(0)
  end
  return IntPartitions(n-Ck2,k)
end

function IntPartitionsDistinct(n::Integer)::BigInt
  if n<0
    throw(DomainError(n,"argument must be nonngative"))
  end
  result = big(0)
  for k=1:n
    s = IntPartitionsDistinct(n,k)
    if s==0
      break
    end
    result += s
  end
  return result
end

"""
`Euler(n)` returns the `n`-th Euler number. Starting with `n=0`
this is the sequence
1, 0, -1, 0, 5, 0, -61, 0, 1385 and so on.
"""
function Euler(n::Integer)::BigInt
  n>=0 || throw(DomainError(n,"argument must be nonngative"))
  if n%2 == 1
    return big(0)
  end
  if n==0
    return big(1)
  end
  if _has(Euler,n)
      return _get(Euler,n)
  end

  last = div(n,2)-1
  val =  -sum([ Binomial(n,2k)*Euler(2k) for k=0:last])
  _save(Euler,n,val)
  return val
end
_make(Euler,Integer)

"""
`PowerSum(n,k)` returns the sum of the `k`-th powers of the
integers `1` through `n`, i.e.,
`1^k + 2^k + 3^k + ... + n^k`.
"""
function PowerSum(n::Integer, k::Integer)::BigInt
  (n>=0 && k>=0) || throw(DomainError((n,k),"arguments must be nonngative"))
  # Base and special cases
  if n==0
    return big(0)
  end
  if k==0
    return big(n)
  end
  if k==1
    return Binomial(n,2)
  end
  if _has(PowerSum,(n,k))
      return _get(PowerSum,(n,k))
  end

  val =  big(n)^k + PowerSum(n-1,k)
  _save(PowerSum,(n,k),val)
  return val
end
_make(PowerSum,Tuple{Integer,Integer})

include("eulerian.jl")

end  #end of module


# Testing precomposition by nonlinear operator

b = randn(10)
g = ProximalOperators.SqrNormL2(3.0)
G = AffineAdd(AbstractOperators.Sigmoid((10,), 1.0),b,false)
f = StructuredOptimization.PrecomposeNonlinear(g, G)

x = randn(10)

grad_f_x, f_x = ProximalOperators.gradient(f, x)

@test size(grad_f_x) == size(x)
@test abs(f_x - 3.0/2 * norm(1.0 ./ (1.0 .+ exp.(-x)) - b)^2) <= 1e-10
expx = exp.(x)
expmx = 1.0./expx
grad_f_x_ref = 3.0 * ( expx ./ (1 .+ expx).^2 ) .* (1.0 ./ (1.0 .+ expmx) - b)
@test norm(grad_f_x - grad_f_x_ref) <= 1e-10

## with compose
#with vectors
l,m1,m2,n1,n2 = 2,3,4,5,6
x = (randn(m1,m2),randn(n1,n2))
A = randn(l,m1)
B = randn(m2,n1)
r = randn(l,n2)

b = randn(l,n2)
G = AffineAdd(NonLinearCompose( MatrixOp(A,m2), MatrixOp(B,n2) ),b,false)

g = ProximalOperators.SqrNormL2(3.0)
f = StructuredOptimization.PrecomposeNonlinear(g, G)

x = (randn(m1,m2),randn(n1,n2))

grad_f_x, f_x = ProximalOperators.gradient(f, x)

r = G*x
grad_f_x2, f_x2 = ProximalOperators.gradient(g, r)
grad_f_x2 = jacobian(G,x)'*grad_f_x2

@test norm(f_x-f_x2) < 1e-8
@test blockvecnorm(grad_f_x2.-grad_f_x2) < 1e-8


# Undergraduate Student: Arturo Burgos
# Professor: João Rodrigo Andrade
# Federal University of Uberlândia - UFU, Fluid Mechanics Laboratory - MFLab, Block 5P, Uberlândia, MG, Brazil

# Fourth exercise: Solving a Linear System --> ax = b

using Printf # Just the same as import a module
using Statistics
using LinearAlgebra # Import this module to use the norm function
using CPUTime

n = 324
k = Int32(sqrt(324))

# Here I set the A matrix

a = rand([0,0],(n,n))

for i = 1:n
  for j = 1:n
    if i==j

      a[i,j] = -4

      elseif i == (j-3) || i == (j+3)

        a[i,j] = 1

      elseif (mod(i,3) != 0 && j == (i+1))

        a[i,j] = 1

      elseif (mod(i,3) != 1 && j == (i-1))

        a[i,j] = 1

      end
  end
end

# Here I set the b matrix

b = ones(n)

for i = 1:k
  if  i < k

    b[i] = -50

  else

    b[i] = -150

  end
end

for i = k+1:n-k
  if i%k != 0

    b[i] = 0

  else

    b[i] = -100

  end
end

for i = n-k+1:n
  if  i < n

    b[i] = -50

  else

    b[i] = -150

  end
end

println("\n")

println("The B matrix is: ")
println(b)

R = zeros(n)

function  linearsystem(a,b,x_k1)
  # Initial x_k and x_k1 value
  x_k = zeros(n)

  x_k1 = ones(n)

  # Here I set the tolerance

  e = 1e-9

  # Here I set the iterations
  ite = 0

  # Here I set the error based in the Infinite norm
  #erro = (x_k1 - x_k)/x_k1; # ---> Why the relative error has the same result?
  global erro = norm((x_k1 - x_k),Inf)

  while erro>e
    for i = 1:n

      global x_k1[i] = b[i]

      for j = 1:n
        if j != i

          global x_k1[i] =  x_k1[i] - a[i,j]*x_k[j]

        end
      end

      global x_k1[i] = x_k1[i]/a[i,i]

    end

    global erro = norm((x_k1 - x_k),Inf)
    global x_k[:] = x_k1[:]

    ite = ite + 1

  end

  return x_k1

end

R = linearsystem(a,b,R);
println("\n\nThe R Matrix is: \n\n",R)
println("\n\n")

@time @CPUtime linearsystem(a,b,R)


function variable_degrees{T<:Real}(num_variables::Integer, poly_degrees::AbstractVector{T})
    max_poly_degree = maximum(poly_degrees)
    poly_degrees = Set(poly_degrees)
    all_variable_degrees = Vector{Int}[]
    var_degrees = zeros(Int, num_variables)
    total = 0
    while var_degrees[1] <= max_poly_degree
        if in(total, poly_degrees)
            push!(all_variable_degrees, copy(var_degrees))
        end
        var_degrees[end] += 1
        total += 1
        for j = length(var_degrees):-1:2
            if var_degrees[j] > max_poly_degree || total > max_poly_degree
                total -= var_degrees[j] - 1
                var_degrees[j] = 0
                var_degrees[j-1] += 1
            else
                break
            end
        end
    end
    return all_variable_degrees
end

function monomial(variable::Symbol, degree::Integer)
    MPoly{Float64}(OrderedDict([degree]=>1.0), [variable])
end

function chebyshev_polynomial_first_kind(variable::Symbol, degree::Integer)
    x = generator(variable)
    @assert degree >= 0
    if degree == 0
        return 1 + 0 * x
    elseif degree == 1
        return x
    else
        return (2x * chebyshev_polynomial_first_kind(variable, degree - 1)
                - chebyshev_polynomial_first_kind(variable, degree - 2))
    end
end

function polynomial_basis(variables, degrees=0:2, generator::Function=monomial)
    map(d -> prod(map(i -> generator(variables[i], d[i]),
                  1:length(variables))),
        variable_degrees(length(variables), degrees))
end

monomials(variables, degrees=0:2) = polynomial_basis(variables, degrees, monomial)
chebyshev_basis_first_kind(variables, degrees=0:2) = polynomial_basis(variables, degrees, chebyshev_polynomial_first_kind)


# -*- coding: utf-8 -*-
# $$
# \frac{d f}{dt} +  (v \frac{d f}{dx} - x \frac{d f}{dv}) = 0
# $$

# $$
# x \in [-\pi, \pi],\qquad y \in [-\pi, \pi] \qquad \mbox{ and } \qquad t \in [0, 200\pi]
# $$

using  FFTW
using  LinearAlgebra
using  Plots, ProgressMeter
using  BenchmarkTools

# ## Mesh parameters (matlab code)
#
# ```m
# Nx=128;Ny=256;
# xmin=-pi; xmax=pi;
#
# dx=(xmax-xmin)/Nx;
# x=xmin:dx:xmax-dx;
#
# ymin=-pi; ymax=pi;
# dy=(ymax-ymin)/Ny;
# y=ymin:dy:ymax-dy;
# ```

# ## Julia type for mesh information
#
# ```jl
# struct Mesh
#     nx   :: Int
#     ny   :: Int
#     xmin :: Float64
#     xmax :: Float64
#     ymin :: Float64
#     ymax :: Float64
#     dx   :: Float64
#     dy   :: Float64
#     x    :: Vector{Float64}
#     y    :: Vector{Float64}
# end
#
# mesh = Mesh( 128, 256, -π, π, -π, π, 2π/128, 2π/256, ...)
#
# ```
#

struct Mesh

    nx   :: Int
    ny   :: Int
    xmin :: Float64
    xmax :: Float64
    ymin :: Float64
    ymax :: Float64
    dx   :: Float64
    dy   :: Float64
    x    :: Vector{Float64}
    y    :: Vector{Float64}

    function Mesh( xmin, xmax, nx, ymin, ymax, ny)
        dx, dy = (xmax-xmin)/nx, (ymax-ymin)/ny
        x = range(xmin, stop=xmax, length=nx+1)[1:end-1]  ## we remove the end point
        y = range(ymin, stop=ymax, length=ny+1)[1:end-1]  ## periodic boundary condition
        new( nx, ny, xmin, xmax, ymin, ymax, dx, dy, x, y)
    end
end

mesh = Mesh(-π, π, 128, -π, π, 256)

@show mesh.xmin, mesh.xmax, mesh.nx, mesh.dx

# # Initialization of f : 2d array of double float
#
# ```m
# f=zeros(Nx,Ny);
# for i=1:Nx
#     xx=xmin+(i-1)*dx;
#     for j=1:Ny
#         yy=ymin+(j-1)*dy;
#         f(i,j)=exp(-(xx-1)*(xx-1)/0.1)*exp(-(yy-1)*(yy-1)/0.1);
#     end
# end
# ```

# ```jl
# f = zeros(Float64,(mesh.nx,mesh.ny))
#
# for (i, x) in enumerate(mesh.x), (j, y) in enumerate(mesh.y)
#
#     f[i,j] = exp(-(x-1)*(x-1)/0.1)*exp(-(y-1)*(y-1)/0.1)
#
# end
# ```

# ### Julia function to compute exact solution

function exact(tf, mesh)

    f = zeros(Float64,(mesh.nx, mesh.ny))
    for (i, x) in enumerate(mesh.x), (j, y) in enumerate(mesh.y)
        xn = cos(tf)*x - sin(tf)*y
        yn = sin(tf)*x + cos(tf)*y
        f[i,j] = exp(-(xn-1)*(xn-1)/0.1)*exp(-(yn-1)*(yn-1)/0.1)
    end

    f
end

f = exact(0.0, mesh)
surface(f)

# ## Create the gif to show what we are computing

function create_gif()
    x = y = range(-π, stop=π, length=40)
    n = 100

    prog = Progress(n,1) ## progress bar

    @gif for t in range(0, stop=2π, length=n)
        f(x,y) = exp(-((cos(t)*x-sin(t)*y)-1)^2/0.2)*exp(-((sin(t)*x+cos(t)*y)-1)^2/0.2)

        p = plot(x, y, f, st = [:surface])

        plot!(p[1])
        plot!(zlims=(-0.01,1.01))

        next!(prog) ## increment the progress bar
    end
end

create_gif();

# ![](tmp.gif)

# ## Advection and loop over time
# ```m
# tf=200*pi;Nt=1000;dt=tf/Nt;
# kx=2*pi/(xmax-xmin)*[0:Nx/2-1,Nx/2-Nx:Nx-1-Nx];
# ky=2*pi/(ymax-ymin)*[0:Ny/2-1,Ny/2-Ny:Ny-1-Ny];
#
# fnx=zeros(1,Nx);ffx=zeros(1,Nx);fny=zeros(1,Ny);ffy=zeros(1,Ny);
#
# for n=1:Nt
#     for i=1:Nx
#         xx=xmin+(i-1)*dx;
#         ffy=fft(f(i,:));
#         fny=real(ifft(exp(sqrt(-1)*xx*ky*tan(dt/2)).*ffy));
#         f(i,:)=fny;
#     end
#
#     for j=1:Ny
#         yy=ymin+(j-1)*dy;
#         ffx=fft(f(:,j));
#         fnx=real(ifft(exp(-sqrt(-1)*yy*kx*sin(dt)).*transpose(ffx)));
#         f(:,j)=fnx;
#     end
#
#     for i=1:Nx
#         xx=xmin+(i-1)*dx;
#         ffy=fft(f(i,:));
#         fny=real(ifft(exp(sqrt(-1)*xx*ky*tan(dt/2)).*ffy));
#         f(i,:)=fny;
#     end
# end
# ```

# ## Function to compute error
#
# ```m
#
# % compute errors in Linfty norm
# error1=max(max(f-f_exact))
#
# ```
#
# - In julia the max value of an array is `maximum`.

function error1(f, f_exact)
    maximum(abs.(f .- f_exact))
end
#----------------------------------------------------------------------------

# ## Naive translation of the matlab code

function naive_from_matlab(tf, nt, mesh::Mesh)

    dt = tf/nt

    kx = 2π/(mesh.xmax-mesh.xmin)*[0:mesh.nx÷2-1;mesh.nx÷2-mesh.nx:-1]
    ky = 2π/(mesh.ymax-mesh.ymin)*[0:mesh.ny÷2-1;mesh.ny÷2-mesh.ny:-1]

    f = exact(0.0, mesh)

    for n=1:nt

       for (i, x) in enumerate(mesh.x)
           f[i,:]=real(ifft(exp.(1im*x*ky*tan(dt/2)).*fft(f[i,:])))
       end

       for (j, y) in enumerate(mesh.y)
           f[:,j]=real(ifft(exp.(-1im*y*kx*sin(dt)).*fft(f[:,j])))
       end

       for (i, x) in enumerate(mesh.x)
           f[i,:]=real(ifft(exp.(1im*x*ky*tan(dt/2)).*fft(f[i,:])))
       end
   end

    f
end
#----------------------------------------------------------------------------

nt, tf = 1000, 200
println( " error = ", error1(naive_from_matlab(tf, nt, mesh), exact(tf, mesh)))
@btime naive_from_matlab(tf, nt, mesh);
#----------------------------------------------------------------------------

# ###  Vectorized version
#
# - We remove the for loops over direction x and y by creating the 2d arrays `exky` and `ekxy`.
# - We save cpu time by computing them before the loop over time

function vectorized(tf, nt, mesh::Mesh)

    dt = tf/nt

    kx = 2π/(mesh.xmax-mesh.xmin)*[0:mesh.nx÷2-1;mesh.nx÷2-mesh.nx:-1]
    ky = 2π/(mesh.ymax-mesh.ymin)*[0:mesh.ny÷2-1;mesh.ny÷2-mesh.ny:-1]

    f = exact(0.0, mesh)

    exky = exp.( 1im*tan(dt/2) .* mesh.x  .* ky')
    ekxy = exp.(-1im*sin(dt)   .* mesh.y' .* kx )

    for n = 1:nt
        f = real(ifft(exky .* fft(f, 2), 2))
        f = real(ifft(ekxy .* fft(f, 1), 1))
        f = real(ifft(exky .* fft(f, 2), 2))
    end

    f
end
#----------------------------------------------------------------------------

nt, tf = 1000, 200
println( " error = ", error1(vectorized(tf, nt, mesh), exact(tf, mesh)))
@btime vectorized(tf, nt, mesh);
#----------------------------------------------------------------------------

# ## Inplace computation
# - We remove the Float64-Complex128 conversion by allocating the distribution function `f` as a Complex array
# - Note that we need to use the inplace assignement operator ".="  to initialize the `f` array.
# - We use inplace computation for fft with the "bang" operator `!`

function inplace(tf, nt, mesh::Mesh)

    dt = tf/nt

    kx = 2π/(mesh.xmax-mesh.xmin)*[0:mesh.nx÷2-1;mesh.nx÷2-mesh.nx:-1]
    ky = 2π/(mesh.ymax-mesh.ymin)*[0:mesh.ny÷2-1;mesh.ny÷2-mesh.ny:-1]

    f  = zeros(Complex{Float64},(mesh.nx,mesh.ny))
    f .= exact(0.0, mesh)

    exky = exp.( 1im*tan(dt/2) .* mesh.x  .* ky')
    ekxy = exp.(-1im*sin(dt)   .* mesh.y' .* kx )

    for n = 1:nt
        fft!(f, 2)
        f .= exky .* f
        ifft!(f,2)
        fft!(f, 1)
        f .= ekxy .* f
        ifft!(f, 1)
        fft!(f, 2)
        f .= exky .* f
        ifft!(f,2)
    end

    real(f)
end
#----------------------------------------------------------------------------

nt, tf = 1000, 200
println( " error = ", error1(inplace(tf, nt, mesh), exact(tf, mesh)))
@btime inplace(tf, nt, mesh);
#----------------------------------------------------------------------------

# ### Use plans for fft

# - When you apply multiple fft on array with same shape and size, it is recommended to use fftw plan to improve computations.
# - Let's try to initialize our two fft along x and y with plans.

function with_fft_plans(tf, nt, mesh::Mesh)

    dt = tf/nt

    kx = 2π/(mesh.xmax-mesh.xmin)*[0:mesh.nx÷2-1;mesh.nx÷2-mesh.nx:-1]
    ky = 2π/(mesh.ymax-mesh.ymin)*[0:mesh.ny÷2-1;mesh.ny÷2-mesh.ny:-1]

    f  = zeros(Complex{Float64},(mesh.nx,mesh.ny))
    f .= exact(0.0, mesh)
    f̂  = similar(f)

    exky = exp.( 1im*tan(dt/2) .* mesh.x  .* ky')
    ekxy = exp.(-1im*sin(dt)   .* mesh.y' .* kx )

    Px = plan_fft(f, 1)
    Py = plan_fft(f, 2)

    for n = 1:nt

        f̂ .= Py * f
        f̂ .= f̂  .* exky
        f .= Py \ f̂

        f̂ .= Px * f
        f̂ .= f̂  .* ekxy
        f .= Px \ f̂

        f̂ .= Py * f
        f̂ .= f̂  .* exky
        f .= Py \ f̂

    end

    real(f)
end
#----------------------------------------------------------------------------

nt, tf = 1000, 200
println( " error = ", error1(with_fft_plans(tf, nt, mesh), exact(tf, mesh)))
@btime with_fft_plans(tf, nt, mesh);
#----------------------------------------------------------------------------

# ## Inplace computation and fft plans
#
# To apply fft plan to an array A, we use a preallocated output array Â by calling `mul!(Â, plan, A)`.
# The input array A must be a complex floating-point array like the output Â.
# The inverse-transform is computed inplace by applying `inv(P)` with `ldiv!(A, P, Â)`.

function with_fft_plans_inplace(tf, nt, mesh::Mesh)

    dt = tf/nt

    kx = 2π/(mesh.xmax-mesh.xmin)*[0:mesh.nx÷2-1;mesh.nx÷2-mesh.nx:-1]
    ky = 2π/(mesh.ymax-mesh.ymin)*[0:mesh.ny÷2-1;mesh.ny÷2-mesh.ny:-1]

    f  = zeros(Complex{Float64},(mesh.nx,mesh.ny))
    f .= exact(0.0, mesh)
    f̂  = similar(f)

    exky = exp.( 1im*tan(dt/2) .* mesh.x  .* ky')
    ekxy = exp.(-1im*sin(dt)   .* mesh.y' .* kx )

    Px = plan_fft(f, 1)
    Py = plan_fft(f, 2)

    for n = 1:nt

        mul!(f̂, Py, f)
        f̂ .= f̂ .* exky
        ldiv!(f, Py, f̂)

        mul!(f̂, Px, f)
        f̂ .= f̂ .* ekxy
        ldiv!(f, Px, f̂)

        mul!(f̂, Py, f)
        f̂ .= f̂ .* exky
        ldiv!(f, Py, f̂)

    end

    real(f)
end
#----------------------------------------------------------------------------

nt, tf = 1000, 200
println( " error = ", error1(with_fft_plans_inplace(tf, nt, mesh), exact(tf, mesh)))
@btime with_fft_plans_inplace(tf, nt, mesh);
#----------------------------------------------------------------------------

# ## Explicit transpose of `f`
#
# - Multidimensional arrays in Julia are stored in column-major order.
# - FFTs along y are slower than FFTs along x
# - We can speed-up the computation by allocating the transposed `f`
# and transpose f for each advection along y.

function with_fft_transposed(tf, nt, mesh::Mesh)

    dt = tf/nt

    kx = 2π/(mesh.xmax-mesh.xmin)*[0:mesh.nx÷2-1;mesh.nx÷2-mesh.nx:-1]
    ky = 2π/(mesh.ymax-mesh.ymin)*[0:mesh.ny÷2-1;mesh.ny÷2-mesh.ny:-1]

    f  = zeros(Complex{Float64},(mesh.nx,mesh.ny))
    f̂  = similar(f)
    fᵗ = zeros(Complex{Float64},(mesh.ny,mesh.nx))
    f̂ᵗ = similar(fᵗ)

    exky = exp.( 1im*tan(dt/2) .* mesh.x' .* ky )
    ekxy = exp.(-1im*sin(dt)   .* mesh.y' .* kx )

    FFTW.set_num_threads(4)
    Px = plan_fft(f,  1, flags=FFTW.PATIENT)
    Py = plan_fft(fᵗ, 1, flags=FFTW.PATIENT)

    f .= exact(0.0, mesh)

    for n = 1:nt
        transpose!(fᵗ,f)
        mul!(f̂ᵗ, Py, fᵗ)
        f̂ᵗ .= f̂ᵗ .* exky
        ldiv!(fᵗ, Py, f̂ᵗ)
        transpose!(f,fᵗ)

        mul!(f̂, Px, f)
        f̂ .= f̂ .* ekxy
        ldiv!(f, Px, f̂)

        transpose!(fᵗ,f)
        mul!(f̂ᵗ, Py, fᵗ)
        f̂ᵗ .= f̂ᵗ .* exky
        ldiv!(fᵗ, Py, f̂ᵗ)
        transpose!(f,fᵗ)
    end
    real(f)
end
#----------------------------------------------------------------------------

nt, tf = 1000, 200
println( " error = ", error1(with_fft_transposed(tf, nt, mesh), exact(tf, mesh)))
@btime with_fft_transposed(tf, nt, mesh);
#----------------------------------------------------------------------------

tf, nt = 400π, 1000
mesh = Mesh(-π, π, 512, -π, π, 256)

inplace_bench = @benchmark inplace(tf, nt, mesh)
vectorized_bench = @benchmark vectorized(tf, nt, mesh)
with_fft_plans_bench = @benchmark with_fft_plans(tf, nt, mesh)
with_fft_plans_inplace_bench = @benchmark with_fft_plans_inplace(tf, nt, mesh)
with_fft_transposed_bench = @benchmark with_fft_transposed(tf, nt, mesh)
#----------------------------------------------------------------------------

d = Dict()
d["vectorized"] = minimum(vectorized_bench.times) / 1e6
d["inplace"] = minimum(inplace_bench.times) / 1e6
d["with_fft_plans"] = minimum(with_fft_plans_bench.times) / 1e6
d["with_fft_plans_inplace"] = minimum(with_fft_plans_inplace_bench.times) / 1e6
d["with_fft_transposed"] = minimum(with_fft_transposed_bench.times) / 1e6;
#----------------------------------------------------------------------------

for (key, value) in sort(collect(d), by=last)
    println(rpad(key, 25, "."), lpad(round(value, digits=1), 6, "."))
end
#----------------------------------------------------------------------------

# ## Conclusion
# - Use pre-allocations of memory and inplace computation are very important
# - Try to always do computation on data contiguous in memory
# - In this notebook, use btime to not taking account of time consumed in
# compilation.


import RowEchelon
import LinearAlgebra

#=
Homework 7
Section 3.2
Problems 29
=#

# Create B
B = [1 0 1 ; 1 1 2 ; 1 2 1]
# Create B^5
B5 = B^5
# Get detB5
detB5 = LinearAlgebra.det(B5)

println("Original Matrix, B:")
println(B)
println("B^5:")
println(B5)
println("Determinate of B^5")
println(detB5)


using LsqFit

model(t, p) = p[1] * exp.(-p[2] * t)

tdata = range(0,10, length=20)
ydata = model(tdata, [1.0, 2.0]) + 0.01*randn(length(tdata))

p0 = [0.5, 0.5]

fit = curve_fit(model, tdata, ydata, p0)

using Plots
plotly()

pt = plot(title="Nonlinear Regression", size=(600,400))
plot!(pt, tdata, ydata, seriestype=:scatter)

a = fit.param[1]
b = fit.param[2]
fitmodel(t) = a * exp.(-b*t)
#plot!(pt, fitmodel, extrema(tdata)..., seriestype=:line)
x = range(0,10,length=100)
y = @. fitmodel(x)
plot!(pt, x, y, seriestype=:line)
gui(pt)
nothing


"""
celsius_to_fahrenheit(celsius, ndigits::Int = 2)

Converts celsius to fahrenheit and round to 2 decimal places

# Example

```julia
celsius_to_fahrenheit(273.354, 3) == 524.037  # returns true
celsius_to_fahrenheit(273.354, 0) == 524.0    # returns true
celsius_to_fahrenheit(-40.0) == -40.0         # returns true
celsius_to_fahrenheit(-20.0) == -4.0          # returns true
celsius_to_fahrenheit(0) == 32.0              # returns true
celsius_to_fahrenheit(20) == 68.0             # returns true
```
"""
function celsius_to_fahrenheit(celsius, ndigits::Int = 2)
    return round((float(celsius) * 1.8) + 32.0; digits = ndigits)
end

"""
function celsius_to_kelvin(celsius, ndigits::Int = 2)

Converts celsius to kelvin and round to 2 decimal places

# Example

```julia
celsius_to_kelvin(273.354, 3) == 546.504    # returns true
celsius_to_kelvin(273.354, 0) == 547.0      # returns true
celsius_to_kelvin(0.0) == 273.15            # returns true
celsius_to_kelvin(20.0) == 293.15           # returns true
```

"""
function celsius_to_kelvin(celsius, ndigits::Int = 2)
    return round(float(celsius) + 273.15; digits = ndigits)
end

"""
fahrenheit_to_celsius(fahrenheit, ndigits::Int = 2)

Converts fahrenheit to celsius and round to 2 decimal places

# Example

```julia
fahrenheit_to_celsius(273.354, 3) == 134.086 # returns true
fahrenheit_to_celsius(273.354, 0) == 134.0   # returns true
fahrenheit_to_celsius(0.0) == -17.78         # returns true
fahrenheit_to_celsius(20.0) == -6.67         # returns true
fahrenheit_to_celsius(40.0) == 4.44          # returns true
fahrenheit_to_celsius(60.0) == 15.56         # returns true
fahrenheit_to_celsius(80.0) == 26.67         # returns true
```
"""
function fahrenheit_to_celsius(fahrenheit, ndigits::Int = 2)
    return round((float(fahrenheit) - 32.0) * 5 / 9; digits = ndigits)
end

"""
fahrenheit_to_kelvin(fahrenheit, ndigits::Int = 2)

Converts fahrenheit to kelvin and round to 2 decimal places

# Example

```julia
fahrenheit_to_kelvin(273.354, 3) == 407.236 # returns true
fahrenheit_to_kelvin(273.354, 0) == 407.0   # returns true
fahrenheit_to_kelvin(0) == 255.37           # returns true
fahrenheit_to_kelvin(20.0) == 266.48        # returns true
fahrenheit_to_kelvin(40.0) == 277.59        # returns true
fahrenheit_to_kelvin(60.0) == 288.71        # returns true
fahrenheit_to_kelvin(80.0) == 299.82        # returns true
```
"""
function fahrenheit_to_kelvin(fahrenheit, ndigits::Int = 2)
    return round(
        ((float(fahrenheit) - 32.0) * 5 / 9) + 273.15;
        digits = ndigits,
    )
end

"""
function kelvin_to_celsius(kelvin, ndigits::Int = 2)

Converts kelvin to celsius and round to 2 decimal places

# Example

```julia
kelvin_to_celsius(273.354, 3) == 0.204   # returns true
kelvin_to_celsius(273.354, 0) == 0.0     # returns true
kelvin_to_celsius(273.15) == 0.0         # returns true
kelvin_to_celsius(300) == 26.85          # returns true
```
"""
function kelvin_to_celsius(kelvin, ndigits::Int = 2)
    return round(float(kelvin) - 273.15; digits = ndigits)
end

"""
function kelvin_to_fahrenheit(kelvin, ndigits::Int = 2)

Converts kelvin to fahrenheit and round to 2 decimal places

# Example

```julia
kelvin_to_fahrenheit(273.354, 3) == 32.367  # returns true
kelvin_to_fahrenheit(273.354, 0) == 32.0    # returns true
kelvin_to_fahrenheit(273.15) == 32.0        # returns true
kelvin_to_fahrenheit(300) == 80.33          # returns true
```
"""
function kelvin_to_fahrenheit(kelvin, ndigits::Int = 2)
    return round(((float(kelvin) - 273.15) * 1.8) + 32.0; digits = ndigits)
end


mutable struct GaussianFeature
    _means::AbstractArray{Float64,1} # the list of means
    _var::Float64 # the variance of gaussian
    _n_kernels::Int64 # cache the number of kernels
    function GaussianFeature(means::AbstractArray{Float64,1}, var::Float64)
        new(means, var, size(means)[1])
    end
end

function _gauss(mean::Float64, var::Float64, x::AbstractArray{Float64,1})
    return exp.(-(0.5 / var) .* (x .- mean) .^ 2)
end

function transform(feature::GaussianFeature, x::AbstractArray{Float64,1})
    # this feature transforms R^1 to R^1
    # x is an array of size n_samples
    # returns Phi = [phi_1(x),,,, phi_N(x)] where phi_i(x) is a gaussian centered around means[i]
    n_samples = size(x)[1]
    n_kernels = feature._n_kernels
    Phi = zeros(n_samples, n_kernels)
    for i = 1:n_kernels
        mean = feature._means[i]
        var = feature._var
        Phi[:, i] = _gauss(mean, var, x)
    end

    return Phi
end


using JuMP, GLPK

# wrap all of our code inside a function (for better performance)
function example_basic()

    # define model and optimizer
    model = Model(GLPK.Optimizer)

    # define variables
    @variable(model, 0 <= x <= 2)
    @variable(model, 0 <= y <= 30)

    # define objective function
    @objective(model, Max, 5x + 3y)

    # add additional constraints
    @constraint(model, 1x + 5y <= 3.0)

    # display the model
    print(model)

    # optimize the model
    JuMP.optimize!(model)

    # return and print objective function and optimal values of variables
    obj_value = JuMP.objective_value(model)
    x_value = JuMP.value(x)
    y_value = JuMP.value(y)
    println("Objective value: ", obj_value)
    println("x = ", x_value)
    println("y = ", y_value)
end

# call the function defined above
example_basic()

using JuMP, GLPK
function example_sudoku()

    # input the initial puzzle board (0s mean blanks)
    initial_grid = [
                    3 1 0 0 5 8 0 0 4;
                    0 0 9 3 2 0 0 0 0;
                    0 2 5 1 0 4 0 9 0;
                    0 0 0 0 0 0 3 8 9;
                    0 0 8 0 0 0 5 0 0;
                    5 4 6 0 0 0 0 0 0;
                    0 8 0 2 0 3 6 5 0;
                    0 0 0 0 7 1 4 0 0;
                    7 0 0 4 8 0 0 2 1
                    ]

    # use GLPK Optimizer
    model = Model(GLPK.Optimizer)

    # Set up the variables: each one can only take on binary values, so we add "Bin" to the end as a constraint
    @variable(model, x[1:9, 1:9, 1:9], Bin)

    # Add the constraints
    @constraints(model, begin
                     # Constraint 1 - Only one value appears in each cell
                     cell[i in 1:9, j in 1:9], sum(x[i, j, :]) == 1
                     # Constraint 2 - Each value appears in each row once only
                     row[i in 1:9, k in 1:9], sum(x[i, :, k]) == 1
                     # Constraint 3 - Each value appears in each column once only
                     col[j in 1:9, k in 1:9], sum(x[:, j, k]) == 1
                     # Constraint 4 - Each value appears in each 3x3 subgrid once only
                     subgrid[i=1:3:7, j=1:3:7, val=1:9], sum(x[i:i + 2, j:j + 2, val]) == 1
                 end)

    # Add additional constraints that reflect the starting point of the puzzle board
    # (i.e. don't attempt to update the numbers that were given as part of the puzzle)
    for row in 1:9, col in 1:9
        if initial_grid[row, col] != 0
            @constraint(model, x[row, col, initial_grid[row, col]] == 1)
        end
    end

    # Solve it
    JuMP.optimize!(model)

    term_status = JuMP.termination_status(model)
    primal_status = JuMP.primal_status(model)
    is_optimal = term_status == MOI.OPTIMAL

    # Check solution
    if is_optimal
        mip_solution = JuMP.value.(x)
        sol = zeros(Int, 9, 9)
        for row in 1:9, col in 1:9, val in 1:9
            if mip_solution[row, col, val] >= 0.9
                sol[row, col] = val
            end
        end
        return sol
    else
        error("The solver did not find an optimal solution.")
    end
end

function print_sudoku_solution(solution)
    println("Solution:")
    println("[-----------------------]")
    for row in 1:9
        print("[ ")
        for col in 1:9
            print(solution[row, col], " ")
            if col % 3 == 0 && col < 9
                print("| ")
            end
        end
        println("]")
        if row % 3 == 0
            println("[-----------------------]")
        end
    end
end

sol = example_sudoku()
print_sudoku_solution(sol)

using HTTP, JuliaDB, JuMP, GLPK

# first function: read in the player data from the class GitHub repository
function read_in_data(url)
    newtable  = csvread(IOBuffer(HTTP.get(url).body), skiplines_begin=0, header_exists=true)
    players   = newtable[1][2]
    salaries  = newtable[1][4]./1000000
    ppg       = newtable[1][11]
    return players,salaries,ppg
end

function SolveModel(players,salary,points)
    N = length(salary)

    m = Model(GLPK.Optimizer)

    # define the variables: they are 0 if the player did not make the team, 1 if the player did make the team
    @variable(m, picked[1:N], Bin)

    # categories:
    @objective(m, Max, sum( points[i] * picked[i] for i in 1:N))

    @constraints m begin
        # Constraint 1 - payroll <= 132.6m
        sum(salary[i] * picked[i] for i in 1:N) <= 132.6
        # Constraint 2 - must have exactly 15 players on roster
        sum(picked[i] for i in 1:N) == 15
    end

    # Solve it
    JuMP.optimize!(m);
    pck     = convert(BitArray,JuMP.value.(picked))
    lineup  = players[pck]
    points  = JuMP.objective_value(m)
    payroll = sum(salary[pck])
    return lineup,points,payroll
end

# call first function (to import data)
players,salaries,pts = read_in_data("https://raw.githubusercontent.com/tyleransom/DScourseS20/master/WebData/playerSalaryStats.csv")

# pass data into second function to get optimal lineup
lineup,total_points,payroll = SolveModel(players,salaries,pts)
println("team: ",lineup)
println("total points scored per game: ",total_points)
println("payroll: ",payroll)

# first function: read in the player data from the class GitHub repository
function read_in_data(url)
    newtable = csvread(IOBuffer(HTTP.get(url).body), skiplines_begin=0, header_exists=true)
    players  = newtable[1][2]
    salaries = newtable[1][4]./1000000
    mpg      = newtable[1][5]
    fgaG5    = 1.0.*((newtable[1][9]).>5)
    fga      = newtable[1][9]
    ppg      = newtable[1][11]
    return players,salaries,mpg,fgaG5,fga,ppg
end

function SolveModel(players,salary,minutes,field_goals_over5,field_goals,points)
    N = length(salary)

    m = Model(GLPK.Optimizer)

    # define the variables: they are 0 if the player did not make the team, 1 if the player did make the team
    @variable(m, picked[1:N], Bin)

    # categories:
    @objective(m, Max, sum( points[i] * picked[i] for i in 1:N))

    @constraints m begin
        # Constraint 1 - payroll <= 132.6m
        sum(salary[i] * picked[i] for i in 1:N) <= 132.6
        # Constraint 2 - must have exactly 15 players on roster
        sum(picked[i] for i in 1:N) == 15
        # Constraint 3 - total minutes must not exceed 240
        sum(minutes[i] * picked[i] for i in 1:N) <= 240
        # Constraint 4 - total shot attempts must be lower than 80
        sum(field_goals[i] * picked[i] for i in 1:N) <= 80
    end

    # Solve it
    JuMP.optimize!(m);
    pck      = convert(BitArray,JuMP.value.(picked))
    lineup   = players[pck]
    totmin   = sum(minutes[pck])
    payroll  = sum(salary[pck])
    totshots = sum(field_goals[pck])
    points   = JuMP.objective_value(m)
    return lineup,points,totmin,payroll,totshots
end

# call first function (to import data)
players,salaries,minutes,over5fg,fga,pts = read_in_data("https://raw.githubusercontent.com/tyleransom/DScourseS20/master/WebData/playerSalaryStats.csv")

# pass data into second function to get optimal lineup
lineup,total_points,total_minutes,payroll,totshots = SolveModel(players,salaries,minutes,over5fg,fga,pts)
println("team: ",lineup)
println("total points scored per game: ",total_points)
println("payroll: ",payroll)
println("total shots per game: ",totshots)
println("total minutes per game: ",total_minutes)

using HTTP, JuliaDB, JuMP, Ipopt
function import_auto(url)
    newtable  = csvread(IOBuffer(HTTP.get(url).body), skiplines_begin=0, header_exists=true)
    depvar    = log.(newtable[1][2]) # log price
    indepvars = cat(ones(size(depvar)),newtable[1][3],newtable[1][5],newtable[1][6]; dims=2) # constant, mpg, headroom, trunk
    return depvar,indepvars
end

Y,X = import_auto("https://tyleransom.github.io/teaching/MetricsLabs/auto.csv")

function jumpOLS(Y,X,startval=zeros(size(X,2),1))
    OLS = Model(Ipopt.Optimizer)

    # Declare the variables you are optimizing over
    @variable(OLS, b[i=1:size(X,2)], start = startval[i])

    # Write your objective function
    @NLobjective(OLS, Min, sum( (Y[i]-sum( X[i,k]*b[k] for k in 1:size(X,2) ))^2 for i in 1:size(X,1) ) )

    # Solve the objective function
    JuMP.optimize!(OLS)

    SSR = JuMP.objective_value(OLS)
    b_value = JuMP.value.(b)
    println("Objective value: ", SSR)
    println("beta hat = ", b_value)
    println("RMSE = ", sqrt(SSR/(size(X,1)-size(X,2))))
end

jumpOLS(Y,X)


using AlgebraicPetri
using OrdinaryDiffEq
using Plots
using Catlab.Meta
using JSON

import OrdinaryDiffEq: ODEProblem
ODEProblem(p::LabelledReactionNet, t) = ODEProblem(vectorfield(p), concentrations(p), t, rates(p))

# help capture JSON of defined functions
macro capture(funcname, exname, ex)
    quote
        $(esc(exname)) = $(repr(strip_lines(ex, recurse=true)))
        $(esc(funcname)) = $ex
    end
end

@capture γ γ_text 1/14
@capture β β_text t->begin
    policy_days = [20,60,120] .+ 17
    contact_rate = 0.05
    pol = findfirst(x->t<=x, policy_days) # array of days when policy changes
    growth_rate = pol == 1 ? 0.0 : (2^(1/((pol-1)*5)) - 1) # growth rate depending on policy
    return (growth_rate + γ) / 990 * (1-contact_rate) # calculate rate of infection
end

sir_cset= LabelledReactionNet{Function, Float64}((:S=>990, :I=>10, :R=>0), (:inf, β)=>((:S, :I)=>(:I,:I)), (:rec, t->γ)=>(:I=>:R))

Graph(sir_cset)

prob = ODEProblem(sir_cset, (17.0, 120.0))
sol = OrdinaryDiffEq.solve(prob,Tsit5())
plot(sol)

## Getting Sharable JSON
sir_cset_string = LabelledReactionNet{String, Int}((:S=>990, :I=>10, :R=>0), (:inf, β_text)=>((:S, :I)=>(:I,:I)), (:rec, γ_text)=>(:I=>:R))
JSON.print(sir_cset_string.tables, 2)


"""
    `generatePoint(dims::Int, range::AbstractFloat, offset::AbstractFloat = 0)`

Generates a random `dims`-dimensional point represented as an array.

d ∈ (offset - range / 2, offset + range / 2) for each dimension of the point.
"""
function generatePoint(dims::Int, range::AbstractFloat, offset::AbstractFloat = 0.0)
  return range * rand(dims) - range / 2 + offset
end

"""
Generates `n` random points without outputs. The rest of parameters are same as parameters for `generatePoint`.
"""
function generatePoints(n::Int, dims::Int, range::AbstractFloat, offset::AbstractFloat = 0.0)
  return [generatePoint(dims, range, offset) for i in 1:n]
end

"""
# Arguments
- `amount`: the amount of points to be generated,
- `polyDegree`: the degree of polynomial used for data generation,
- `polyDim`: the spatial dimension of each term in the polynomial used for data generation,
- `ptRange` and `ptOffset`: the same as `generatePoint`,
- `polyRange` and `polyOffset`: the same as `generatePoint`, but for each term in the polynomial used for data generation,
- `outputFile`: name of the output file
"""
function generateRandomData(amount::Int, polyDegree::Int, polyDim::Int, ptRange::Float64, ptOffset::Float64, polyRange::Float64, polyOffset::Float64, outputFile::String)
  vars, mons = generateMonomials(polyDegree, polyDim)
  coeffs = polyRange * rand(length(mons)) - (polyRange / 2 + polyOffset)
  poly = polynomial(coeffs, mons)
  res = generatePoints(amount, polyDegree, ptRange, ptOffset)
  for p in res
    push!(p, poly(vars => p))
  end
  writecsv(outputFile, res)
  println("Polynomial '$poly' was used for data generation")
  println("Done")
end


# Minimum variance analysis

export mva

"""
    mva(Bx, By, Bz; verbose=false)

Perform minimum variance analysis to vector components defined in orthogonal
coordinates `Bx`, `By` and `Bz`.
If λ₁ ≥ λ₂ ≥ λ₃ are 3 eigenvalues of the constructed matrix M, then a good
indicator of nice fitting LMN coordinate system should have λ₂/λ₃ > 5. Set
`verbose=true` to turn on the check.
"""
function mva(Bx, By, Bz; verbose=false)

   B̄1 = mean(Bx)
   B̄2 = mean(By)
   B̄3 = mean(Bz)
   B̄11= mean(Bx.*Bx) - B̄1*B̄1
   B̄22= mean(By.*By) - B̄2*B̄2
   B̄33= mean(Bz.*Bz) - B̄3*B̄3
   B̄12= mean(Bx.*By) - B̄1*B̄2
   B̄23= mean(By.*Bz) - B̄2*B̄3
   B̄31= mean(Bz.*Bx) - B̄3*B̄1
   # Construct the matrix
   M = [B̄11 B̄12 B̄31; B̄12 B̄22 B̄23; B̄31 B̄23 B̄33]

   # Compute the eigen values and ratios (descending order)
   F = eigen(M, sortby = x -> -abs(x))

   if verbose
      println("Eigenvalues:", F.values)
      println("Eigenvectors:")
      println(F.vectors[:,1])
      println(F.vectors[:,2])
      println(F.vectors[:,3])
      r = F.values[2] / F.values[3]
      println("Ratio of intermediate variance to minimum variance = ",
         round(r, digits=3))
      if r ≥ 5
         println("Seems to be a proper MVA attempt!")
      else
         @warn "Take the MVA result with a grain of salt!"
      end
   end
   F
end


# V-measure of contingency table
function _vmeasure(A::AbstractMatrix{<:Integer}; β::Real)
    (β >= 0) || throw(ArgumentError("β should be nonnegative"))

    N = sum(A)
    (N == 0.0) && return 0.0

    entA = entropy(A)
    entArows = entropy(sum(A, dims=2))
    entAcols = entropy(sum(A, dims=1))

    hck = (entA - entAcols)/N
    hkc = (entA - entArows)/N
    hc = entArows/N + log(N)
    hk = entAcols/N + log(N)

    # Homogeneity
    h = hc == 0.0 ? 1.0 : 1.0 - hck/hc
    # Completeness
    c = hk == 0.0 ? 1.0 : 1.0 - hkc/hk

    # V-measure
    V_β = (1 + β)*h*c/(β*h + c)
    return V_β
end

"""
    vmeasure(assign1, assign2; β = 1.0)

V-measure between two clustering assignments.

`assign1` and `assign2` can be either `ClusteringResult` objects or
assignments vectors (`AbstractVector{<:Integer}`).

The `β` parameter defines trade-off between _homogeneity_ and _completeness_:
 * if `β` is greater than 1, _completeness_ is weighted more strongly,
 * if `β` is less than 1, _homogeneity_ is weighted more strongly.

*Ref:* Andrew Rosenberg and Julia Hirschberg, 2007. "V-Measure: A conditional entropy-based external cluster evaluation measure"
"""
function vmeasure(assign1::Union{AbstractVector{<:Integer}, ClusteringResult},
                  assign2::Union{AbstractVector{<:Integer}, ClusteringResult};
                  β::Real = 1.0)
    _assign1 = isa(assign1, AbstractVector) ? assign1 : assignments(assign1)
    _assign2 = isa(assign2, AbstractVector) ? assign2 : assignments(assign2)
    return _vmeasure(counts(_assign1, _assign2,
                            (1:maximum(_assign1), 1:maximum(_assign2))), β=β)
end


using KrylovMethods
using Base.Test
using MatrixDepot

println("=== Testing Lanczos helpers ===")
symOrth = KrylovMethods.symOrtho

v     = ([0;randn()],[randn();0],randn(2))
for k=1:3
	println("Givens rotation for v=[$(v[k][1]),$(v[k][2])]")
	c,s,r = symOrth(v[k][1],v[k][2])
	@test abs(c.^2 + s.^2 - 1) < 1e-14
	Q = [c s; -s c]
	w = [norm(v[k]);0]
	@test norm(Q*v[k]-w) < 1e-13
end

println("=== Testing Lanczos Bidiagonalization ===")
A = sprandn(10,10,.4) + speye(10)
p = randn(10)
k = 9

U,B,V = lanczosBidiag(A,p,k)

@test norm(full(U'*A*V-B))/norm(full(A)) < 1e-15

println("=== Testing Lanczos Tridiagonalization ===")

# test stopping for low-rank matrix
A = randn(100,10)
H = A*A'
T,V = lanczosTridiag(H,ones(100),100,doReorth=true)
@test size(T,1)==11
@test size(V,2)==11

A   = matrixdepot("rosser",4)
Aff  = x -> A*x
T1,V1 = lanczosTridiag(A,ones(size(A,1)),size(A,1),tol=1e-10,doReorth=true)
T2,V2 = lanczosTridiag(Aff,ones(size(A,1)),size(A,1),tol=1e-10,doReorth=false)
sT1  = svd(full(T1))[2]
sT2  = svd(full(T2))[2]
sA   = svd(full(A))[2]

@test abs(sT1[1]-sA[1])/sA[1] < 1e-4
@test abs(sT2[1]-sA[1])/sA[1] < 1e-4
B = V1'*V1
@test norm(B - diagm(diag(B))) < 1e-10
@test norm(diag(B)- ones(size(B,1))) < 1e-10
println("=== Lanczos: All tests passed. ===")


#=
Given an array of numbers, find the maximum sum of any contiguous subarray of the array.

For example, given the array [34, -50, 42, 14, -5, 86], the maximum sum would be 137, since we would take elements 42, 14, -5, and 86.

Given the array [-5, -1, -8, -9], the maximum sum would be 0, since we would not take any elements.

Do this in O(N) time.
=#

function maximum_contiguous_sum(X::Array{Int,1})
    max_sum = 0
    current_subarray = []
    current_sum = 0
    for index in eachindex(X)
        if current_sum + X[index] > 0
            push!(current_subarray, X[index])
            current_sum = current_sum + X[index]
            if current_sum > max_sum
                max_sum = current_sum
            end
        else
            current_sum = 0
            current_subarray = []
        end
    end
    return max_sum
end


# Implement code blocks 3.11 through 3.19 in Statistical Rethinking, 2nd edition.

using Distributions
using Statistics
using StatsBase
using LinearAlgebra

grid_size = 1000
num_observations = 3
num_samples = 1000

# Code block 3.11
param_grid = range(0, 1, length=grid_size)
prior = ones((grid_size))
likelihood = [pdf(Binomial(3, p), num_observations) for p in param_grid]
posterior = likelihood .* prior
posterior = posterior / sum(posterior)

@assert abs(sum(posterior) - 1) < 1e-4 "Posterior does not sum to 1."
@assert minimum(posterior) >= 0
@assert maximum(posterior) <= 1

samples = sample(param_grid, Weights(posterior), num_samples; replace=true, ordered=false)

# Code block 3.12
function central_interval(samples, proba::AbstractFloat)
    @assert proba > 0
    @assert proba < 1
    quantile(samples, [0.5 - proba / 2, 0.5 + proba / 2])
end

central_interval(samples, 0.5)

# Code block 3.13

# UPDATE: all the prob. prog. packages have it right. Because they *sort* the samples
# first, they're really working with the ECDF. Subtracting in the domain of the ECDF
# such that the range always has at least `proba` mass.

# - I'm 85% sure the StatisticalRethinking.jl package has this wrong, it assumes
#   some mass on both sides of the tail, which is not correct.

function high_density_interval(samples, proba::AbstractFloat)
    @assert proba >= 0
    @assert proba <= 1

    count_threshold = proba * length(samples)
    hist = fit(Histogram, samples; closed=:right, nbins=100)

    interval_mass = 0
    start_index = 1
    min_interval_length = Inf
    candidate = (NaN, NaN)
    candidate_mass = 0

    for (i, w) in enumerate(hist.weights)
        interval_mass += w

        while interval_mass >= count_threshold
            interval_length = i - start_index

            if (interval_length < min_interval_length) ||
                    ((interval_length == min_interval_length) &&
                     interval_mass > candidate_mass)

                candidate = (start_index, i)
                candidate_mass = interval_mass
                min_interval_length = interval_length
            end

            interval_mass = interval_mass - hist.weights[start_index]
            start_index += 1
        end
    end

    interval = [hist.edges[1][candidate[1]], hist.edges[1][candidate[2]]]
end

high_density_interval(samples, 0.5)

# Code block 3.14 - MAP of the explicit posterior
_, i = findmax(posterior)
posterior_mode = param_grid[i]

# Code block 3.15 - MAP based on posterior samples
# - Based on a density estimate again

# Code block 3.16
mean(samples)
median(samples)

# Code block 3.17 - mean absolute error loss for a point estimate
function mae_loss(point_estimate, param_grid, posterior)
    sum(posterior .* abs.(point_estimate .- param_grid))
end

loss = mae_loss(0.5, param_grid, posterior)

# Code blocks 3.18 and 3.19 - mean absolute error for all param values
# - The book uses `sapply` in R. I don't know how to vectorize like that yet in Julia,
#   so let's just loop.
losses = [mae_loss(p, param_grid, posterior) for p in param_grid]

_, i = findmin(losses)

best_point_estimate = param_grid[i]


"""
  Compute the nth Pernicious number
"""

using BenchmarkTools
using Printf
using Primes

# Get the value of of an integer from the command line.
n, = size(ARGS)
if n < 1
    println("Usage: test_pernicious_numbers.jl max_num")
    println("       --->      Please specify a positive integer.")
    exit()
end

max_num = parse(Int64, ARGS[1])

ispernicious(n::Int64) = isprime(count_ones(n))

nextpernicious(n::Int64) = begin n += 1; while !ispernicious(n) n += 1 end; return n end

function perniciouses(n::Int64)
    rst = Vector{Int64}(undef, n)
    old_p = 3
    for i in 2:n
        new_p = nextpernicious(old_p)
        old_p = new_p
    end
    return old_p
end

perniciouses(a::Integer, b::Integer) = filter(ispernicious, a:b)

println("Pernicious number of order: ", max_num)

@btime perniciouses(max_num)

#println("Pernicious number of order ", max_num, ": ", perniciouses(max_num))


"""
    kruskal_mst(g, distmx=weights(g); minimize=true)

Return a vector of edges representing the minimum (by default) spanning tree of a connected,
undirected graph `g` with optional distance matrix `distmx` using [Kruskal's algorithm](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm).

### Optional Arguments
- `minimize=true`: if set to `false`, calculate the maximum spanning tree.
"""
function kruskal_mst end
# see https://github.com/mauro3/SimpleTraits.jl/issues/47#issuecomment-327880153 for syntax
@traitfn function kruskal_mst(g::AG::(!IsDirected),
    distmx::AbstractMatrix{T}=weights(g); minimize=true) where {T <: Real, U, AG <: AbstractGraph{U}}

    connected_vs = IntDisjointSets(nv(g))

    mst = Vector{edgetype(g)}()
    sizehint!(mst, nv(g) - 1)

    weights = Vector{T}()
    sizehint!(weights, ne(g))
    edge_list = collect(edges(g))
    for e in edge_list
        push!(weights, distmx[src(e), dst(e)])
    end

    weight = 0
    for e in edge_list[sortperm(weights; rev=!minimize)]
        if !in_same_set(connected_vs, src(e), dst(e))
            union!(connected_vs, src(e), dst(e))
            weight += distmx[src(e), dst(e)]
            push!(mst, e)
            (length(mst) >= nv(g) - 1) && break
        end
    end

    return (mst=mst, weight=weight)
end


# back-substitution jl

"""
Performs Rx = b by back_substitution

# Arguments
- `R::Array{Float64,2}`: diagonal matrix of size m x m
- `x::Array{Float64,1}`: vector of size m. The result is stored in x.
- `b::Array{Float64,1}`: vector of size m.

# Complexity

O(m^2)

"""
function back_substitution_v!(R, x, b)
    (_, m) = size(R)

    x .= 0

    # O(m^2)
    @inbounds for i = m:-1:1
        b_i = b[i]
        for k = i + 1:m
            b_i = b_i - R[i,k] * x[k]
        end
        x[i] = b_i / R[i,i]
    end

end

"""
Performs RW = V by back_substitution

# Arguments
- `R::Array{Float64,2}`: diagonal matrix of size m x m
- `V::Array{Float64,2}`: matrix of size m x k
- `W::Array{Float64,2}`: matrix of size m x k. The result is stored in W.

# Complexity

O(km^2)

"""
function back_substitution!(R,V,W)

    (_,k) = size(V)
    (m,_) = size(R)

    #Complexity: O(km^2)
    @views @inbounds for i = 1 : k
        V[:,i] .= 0
        for j = m:-1:1
            V[j,i] = W[j,i]/R[j,j];
            @. W[1:j-1,i] = W[1:j-1,i] - R[1:j-1,j]*V[j,i];
        end
    end

end


function gen_β(β, η, D, R)
    α = 2.0 .- β .^ (-  η - 1.0 )
    mask = R .<= 1.0 ./ α
    s = 1.0 / (η + 1.0)
    βq = [ mask[i] ?  (R[i] * α[i])^s : (1.0 / (2.0 - R[i]*α[i]))^s for i in 1:D]
    βq
end

"""
    SBX_crossover(vector1, vector2, bounds, η=15, p_variable = 0.9)

Simulated binomial crossover for given two `Vectors{Real}`.
"""
function SBX_crossover(vector1, vector2, bounds, η=15, p_variable = 0.9)
    xu = view(bounds, 2,:)
    xl = view(bounds, 1,:)
    D = length(vector1)

    do_crossover = ones(Bool, D)
    do_crossover[rand(D) .> p_variable] .= false
    do_crossover[ abs.( vector2 - vector1 ) .<= eps() ] .= false

    y1 = min.( vector1, vector2 )
    y2 = max.( vector1, vector2 )
    Δ = max.(eps(), y2 - y1)

    R = rand(D)

    β = @. 1.0 + (2.0 * (y1 - xl) / Δ)
    βq = gen_β(β, η, D, R)
    c1 = @. 0.5*(y1 + y2 -  βq*Δ)

    β = @. 1.0 + (2.0 * (y1 - xl) / Δ)
    βq = gen_β(β, η, D, R)
    c2 = @. 0.5*(y1 + y2 +  βq*Δ)

    # swap
    mask = rand(Bool, D)
    cc = copy(c1)
    c1[mask] = c2[mask]
    c2[mask] = cc[mask]

    cc1 = copy(vector1)
    cc1[do_crossover] = c1[do_crossover]
    cc2 = copy(vector2)
    cc2[do_crossover] = c2[do_crossover]

    reset_to_violated_bounds!(cc1, bounds)
    reset_to_violated_bounds!(cc2, bounds)

    return cc1, cc2
end

"""
    DE_crossover(x, u, CR)

Binomial crossover between x and u for Differential Evolution with probability CR, i.e.,
`v[j] = u[j]` if `rand() < CR`, otherwise `v[j] = x[j]`. Return `v`.
"""
function DE_crossover(x, u, CR)
    D = length(x)
    # binomial crossover
    v = zeros(D)
    j_rand = rand(1:D)

    # binomial crossover
    for j = 1:D
        if rand() < CR || j == j_rand
            v[j] = u[j]
        else
            v[j] = x[j]
        end
    end

    return v
end


# Ranking based Metrics for Recommender Systems

"""
    ranking_stats_k(y_rec, y_rel, k = 10)

Evaluates the relevancy of top k recommendations using `precison@k`, `recall@k` and `f1_score@k`. Returns result as a `Dict`.

Here, `y_rec` are predicted probabilities for recommendation and `y_rel` defines as `1` if particular result is relevant, else `0`.
The shape of `y_rec` and `y_rel` are expected to be (1, N_elements).<br>

* `precison_k` is evaluated as `Recommended_items_that_are_relevant / Total_Recommended_items`.
* `recall_l` is evaluated as `Recommended_items_that_are_relevant / Total_Relevant_items`.
* `f1_k` is evaluated as `2 * Recommended_items_that_are_relevant / (Total_Recommended_items + Total_Relevant_items)`.
"""
function ranking_stats_k(y_rec, y_rel; k = 10)
    @assert size(y_rec) == size(y_rel)
    if size(y_rec,1) !=1
       y_rec = reshape(y_rec, 1, :)
       y_rel = reshape(y_rel, 1, :)
    end
    top_k = partialsortperm(y_rec[1,:], 1:k, lt= >)
    y_rec_k, y_rel_k = y_rec[:, top_k], y_rel[:, top_k]
    tp = sum(y_rel_k .== 1)
    total_positive = sum(y_rel.==1)
    precision_k = k!=0 ? tp / k : 1
    recall_k = total_positive!=0 ? tp / total_positive : 1
    f1_k = (total_positive + k!=0) ? (2 * tp / (total_positive + k)) : 1
    return Dict("precision_k" => precision_k,
                "recall_k" => recall_k,
                "f1_k" => f1_k)
end

"""
    avg_precision(y_rec, y_rel, k = 10)

Evaluates how much of the relevant documents are concentrated in the highest ranked predictions.

Calculated as
    `∑(Recall@i - Recall@i-1)* Precision@i for i = (1, 2, 3....k)`

Here, `y_rec` are predicted probabilities for recommendation and `y_rel` defines as `1` if particular result is relevant, else `0`.
The shape of `y_rec` and `y_rel` are expected to be (1, N_elements)
"""
function avg_precision(y_rec, y_rel; k = 10)
    @assert size(y_rec) == size(y_rel)
    if size(y_rec,1) !=1
       y_rec = reshape(y_rec, 1, :)
       y_rel = reshape(y_rel, 1, :)
    end
    top_k = partialsortperm(y_rec[1,:], 1:k, lt= >)
    y_rec_k, y_rel_k = y_rec[:, top_k], y_rel[:, top_k]
    correct_prediction = 0
    running_sum = 0
    for i in 1:k
        if y_rel_k[i] == 1
            correct_prediction +=1
            running_sum += correct_prediction / i
        end
    end
    return running_sum / sum(y_rel .== 1)
end

# TODO

# DCG
# NDCG


import Distributions: cdf, Normal

N(x) = cdf(Normal(), x)

function d1(S, K, τ, r, σ, q)
    return (log(S / K) + (r - q + σ^2 / 2) * τ) / (σ * √(τ))
end

function d2(S, K, τ, r, σ, q)
    return d1(S, K, τ, r, σ, q) - σ * √(τ)
end

"""
    eurocall(;S=1.,K=1.,τ=1,r,σ,q=0.)

Calculate the Black-Scholes implied option price for a european call, where:

- `S` is the current asset price
- `K` is the strike or exercise price
- `τ` is the time remaining to maturity (can be typed with \\tau[tab])
- `r` is the continuously compounded risk free rate
- `σ` is the (implied) volatility (can be typed with \\sigma[tab])
- `q` is the continuously paid dividend rate

Rates should be input as rates (not percentages), e.g.: `0.05` instead of `5` for a rate of five percent.

!!! Experimental: this function is well-tested, but the derivatives functionality (API) may change in a future version of ActuaryUtilities.

# Extended Help

This is the same as the formulation presented in the [dividend extension of the BS model in Wikipedia](https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model#Black%E2%80%93Scholes_equation).

## Other general comments:

- Swap/OIS curves are generally better sources for `r` than government debt (e.g. US Treasury) due to the collateralized nature of swap instruments.
- (Implied) volatility is characterized by a curve that is a function of the strike price (among other things), so take care when using
- Yields.jl can assist with converting rates to continuously compounded if you need to perform conversions.

"""
function eurocall(; S = 1.0, K = 1.0, τ = 1, r, σ, q = 0.0)
    d₁ = d1(S, K, τ, r, σ, q)
    d₂ = d2(S, K, τ, r, σ, q)
    return (N(d₁) * S * exp(τ * (r - q)) - N(d₂) * K) * exp(-r * τ)
end

"""
    europut(;S=1.,K=1.,τ=1,r,σ,q=0.)

Calculate the Black-Scholes implied option price for a european call, where:

- `S` is the current asset price
- `K` is the strike or exercise price
- `τ` is the time remaining to maturity (can be typed with \\tau[tab])
- `r` is the continuously compounded risk free rate
- `σ` is the (implied) volatility (can be typed with \\sigma[tab])
- `q` is the continuously paid dividend rate

Rates should be input as rates (not percentages), e.g.: `0.05` instead of `5` for a rate of five percent.

!!! Experimental: this function is well-tested, but the derivatives functionality (API) may change in a future version of ActuaryUtilities.

# Extended Help

This is the same as the formulation presented in the [dividend extension of the BS model in Wikipedia](https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model#Black%E2%80%93Scholes_equation).

## Other general comments:

- Swap/OIS curves are generally better sources for `r` than government debt (e.g. US Treasury) due to the collateralized nature of swap instruments.
- (Implied) volatility is characterized by a curve that is a function of the strike price (among other things), so take care when using
- Yields.jl can assist with converting rates to continuously compounded if you need to perform conversions.

"""
function europut(; S = 1.0, K = 1.0, τ = 1, r, σ, q = 0.0)
    d₁ = d1(S, K, τ, r, σ, q)
    d₂ = d2(S, K, τ, r, σ, q)
    return (N(-d₂) * K - N(-d₁) * S * exp(τ * (r - q))) * exp(-r * τ)
end


function KalmanFilter(T::Matrix, Z::Matrix, Q::Matrix, H::Matrix, n::Integer, α0, y)

    # Initialize state variables
    αhat = repeat([zeros(size(α0))], n);
    αhat[1] = T*α0;

    println("I'm here 1")

    yhat = repeat([zeros(size(y[1]))], n);
    v = copy(yhat);

    P0 = [2.0]; # Initial guess given in the problem

    P = repeat([zeros(size(Q))], n);
    P[1] = T * P0 * T' + Q;

    println("I'm here 2, P[1] = $(P[1])")

    F = repeat([zeros(size(H))], n)
    G = repeat([zeros(size(Z * Q))], n)
    K = repeat([zeros(size(T * G[1]' * ones(size(H))))], n)

    for i in 1:n-1
        yhat[i] = Z * αhat[i];
        v[i] = y[i] - yhat[i];

        F[i] = Z * P[i] * Z' + H;
        G[i] = Z * P[i];
        K[i] = T * G[i]' * inv(F[i]);

        αhat[i + 1] = T * αhat[i] + K[i] * v[i];
        P[i + 1] = T * (P[i] - G[i]' * inv(F[i]) * G[i]) * T' + Q;
    end

    yhat[n] = Z * αhat[n];
    v[n] = y[n] - yhat[n];

    F[n] = Z * P[n] * Z' + H;
    G[n] = Z * P[n];
    K[n] = T * G[n]' * inv(F[n]);

    return αhat, v, yhat, P, F, G, K
end


###########################################
# Unconstrained optimization
###########################################

f(x) = sin(x[1] + x[2]) + cos(x[1])^2
g(x) = [cos(x[1] + x[2]) - 2*cos(x[1])*sin(x[1]); cos(x[1] + x[2])]

f(x1,x2) = f([x1;x2])

f([0; 0])
f(0, 0)

#### Exercise

using Plots

xs = range(-3, 1, length = 40)
ys = range(-2, 1, length = 40)

contourf(xs, ys, f, color = :jet)

#### Exercise

finite_difference(f, x::Real; h=1e-8) = (f(x+h) - f(x)) / h

#### Exercise

x = [-2; -1]
fin_diff(h) = finite_difference(y -> f(x[1], y), x[2]; h=h)

true_grad = g(x)[2]

hs = 10. .^ (-15:0.01:-1)

plot(hs, fin_diff,
    xlabel = "h",
    ylabel = "Partial gradient wrt y",
    label = ["Approximation" "True gradient"],
    xscale = :log10,
)

hline!([true_grad]; label =  "True gradient")

#### Numerical errors

x = 1
h = 1e-13

(x+h)^2 - x^2
2*x*h + h^2

#### Exercise

x = [-2; -1]
α = 0.25
x_grad = [x x.+α.*g(x)]

contourf(xs, ys, f; color = :jet)
plot!(x_grad[1, :], x_grad[2, :];
    line = (:arrow, 4, :black),
    label = "",
)

#### Exercise

function optim(f, g, x, α; max_iter=100)
    xs = zeros(length(x), max_iter+1)
    xs[:,1] = x
    for i in 1:max_iter
        x -= α*g(x)
        xs[:,i+1] = x
    end
    return xs
end

#### Animation

using Random

function create_anim(
    f,
    path,
    xlims,
    ylims,
    file_name = joinpath(pwd(), randstring(12) * ".gif");
    xbounds = xlims,
    ybounds = ylims,
    fps = 15,
)
    xs = range(xlims...; length = 100)
    ys = range(ylims...; length = 100)
    plt = contourf(xs, ys, f; color = :jet)

    # add constraints if provided
    if !(xbounds == xlims && ybounds == ylims)
        x_rect = [xbounds[1]; xbounds[2]; xbounds[2]; xbounds[1]; xbounds[1]]
        y_rect = [ybounds[1]; ybounds[1]; ybounds[2]; ybounds[2]; ybounds[1]]

        plot!(x_rect, y_rect; line = (2, :dash, :red), label="")
    end

    # add an empty plot
    plot!(Float64[], Float64[]; line = (4, :arrow, :black), label = "")

    # extract the last plot series
    plt_path = plt.series_list[end]

    # create the animation and save it
    anim = Animation()
    for x in eachcol(path)
        push!(plt_path, x[1], x[2]) # add a new point
        frame(anim)
    end
    gif(anim, file_name; fps = fps, show_msg = false)
    return nothing
end

#### Exercise

x_gd = optim([], g, [0; -1], 0.1)

xlims = (-3, 1)
ylims = (-2, 1)
create_anim(f, x_gd, xlims, ylims, "anim1.gif")

f_gd = [f(x) for x in eachcol(x_gd)]

plot(f_gd, label="", xlabel="Iteration", ylabel="Function value")

#### Different stepsizes

x_gd = optim([], g, [0; -1], 0.01)

create_anim(f, x_gd, xlims, ylims, "anim2.gif")

x_gd = optim([], g, [0; -1], 1)

create_anim(f, x_gd, xlims, ylims, "anim3.gif")

#### Stepsize selection

abstract type Step end

struct GD <: Step
    α::Float64
end

#### Gradient descent

optim_step(s::GD, f, g, x) = -s.α*g(x)

function optim(f, g, x, s::Step; max_iter=100)
    xs = zeros(length(x), max_iter+1)
    xs[:,1] = x
    for i in 1:max_iter
        x += optim_step(s, f, g, x)
        xs[:,i+1] = x
    end
    return xs
end

gd = GD(0.1)
x_opt = optim(f, g, [0;-1], gd)

create_anim(f, x_opt, xlims, ylims, "anim4.gif")

#### Exercise

struct Armijo <: Step
    c::Float64
    α_max::Float64
end

function optim_step(s::Armijo, f, g, x)
    fun = f(x)
    grad = g(x)
    α = s.α_max
    while f(x .- α*grad) > fun - s.c*α*(grad'*grad)
        α /= 2
        if α <= 1e-6
            warning("Armijo line search failed.")
            break
        end
    end
    return -α*grad
end

gd = Armijo(1e-4, 1)
x_opt = optim(f, g, [0;-1], gd)

create_anim(f, x_opt, xlims, ylims, "anim5.gif")

###########################################
# Constrained optimization
###########################################

f(x) = sin(x[1] + x[2]) + cos(x[1])^2
g(x) = [cos(x[1] + x[2]) - 2*cos(x[1])*sin(x[1]); cos(x[1] + x[2])]

f(x1,x2) = f([x1;x2])

#### Projected gradients

function optim(f, g, P, x, α; max_iter=100)
    xs = zeros(length(x), max_iter+1)
    ys = zeros(length(x), max_iter)
    xs[:,1] = x
    for i in 1:max_iter
        ys[:,i] = xs[:,i] - α*g(xs[:,i])
        xs[:,i+1] = P(ys[:,i])
    end
    return xs, ys
end

P(x, x_min, x_max) = min.(max.(x, x_min), x_max)

x_min = [-1; -1]
x_max = [0; 0]

xs, ys = optim(f, g, x -> P(x,x_min,x_max), [0;-1], 0.1)

#### Plot 1

xlims = (-3, 1)
ylims = (-2, 1)

create_anim(f, xs, xlims, ylims, "anim6.gif";
    xbounds=(x_min[1], x_max[1]),
    ybounds=(x_min[2], x_max[2]),
)

#### Plot 2

xys = hcat(reshape([xs[:,1:end-1]; ys][:], 2, :), xs[:,end])

create_anim(f, xys, xlims, ylims, "anim7.gif";
    xbounds=(x_min[1], x_max[1]),
    ybounds=(x_min[2], x_max[2]),
)


@doc raw"""
    gauss_quadrature(q)

Return the Gauss-Legendre quadrature rule with `q` points (integrates polynomials of degree
$2q-1$ exactly).

A tuple `(x,w)` is returned.
"""
function gauss_quadrature(q)
    x = zeros(CeedScalar, q)
    w = zeros(CeedScalar, q)
    C.CeedGaussQuadrature(q, x, w)
    x, w
end

struct QuadratureMode{T} end
const Abscissa = QuadratureMode{:Abscissa}()
const AbscissaAndWeights = QuadratureMode{:AbscissaAndWeights}()

@doc raw"""
    lobatto_quadrature(q, mode::Mode=Abscissa)

Return the Gauss-Lobatto quadrature rule with `q` points (integrates polynomials of degree
$2q-3$ exactly).

If `mode` is `AbscissaAndWeights`, then both the weights and abscissa are returned as a
tuple `(x,w)`.

Otherwise, (if `mode` is `Abscissa`), then only the abscissa `x` are returned.
"""
function lobatto_quadrature(q, mode::Mode=Abscissa) where {Mode}
    return_weights = (mode == AbscissaAndWeights)
    x = zeros(CeedScalar, q)
    w = (return_weights) ? zeros(CeedScalar, q) : C_NULL
    C.CeedLobattoQuadrature(q, x, w)
    return_weights ? (x, w) : x
end


# ---
# title: 50. Pow(x, n)
# id: problem50
# author: zhwang
# date: 2022-03-02
# difficulty: Medium
# categories: Math, Binary Search
# link: <https://leetcode.com/problems/powx-n/description/>
# hidden: true
# ---
#
# Implement [pow( _x_ , _n_
# )](http://www.cplusplus.com/reference/valarray/pow/), which calculates  _x_
# raised to the power _n_ (i.e. xn).
#
#
#
# **Example 1:**
#
#
#
#     Input: x = 2.00000, n = 10
#     Output: 1024.00000
#
#
# **Example 2:**
#
#
#
#     Input: x = 2.10000, n = 3
#     Output: 9.26100
#
#
# **Example 3:**
#
#
#
#     Input: x = 2.00000, n = -2
#     Output: 0.25000
#     Explanation: 2-2 = 1/22 = 1/4 = 0.25
#
#
#
#
# **Constraints:**
#
#   * `-100.0 < x < 100.0`
#   * `-231 <= n <= 231-1`
#   * `-104 <= xn <= 104`
#
#
## @lc code=start
using LeetCode

function my_pow(x::Float64, n::Int)::Float64
    n == 0 && return 1
    if n < 0
        x, n = 1 / x, -n
    end
    res, extra = x, 1
    while n > 1
        n & 1 == 1 && (extra *= res)
        res *= res
        n >>= 1
    end
    return res * extra
end
## @lc code=end


function EuropeanDownAndOutCallMC(K, T, S, sig, r, div, H, N, M)

dt = T/N
nudt = (r - div - 0.5 * sig^2) * dt
sigsdt = sig * sqrt(dt)

## variables to store the sums of the option values

sum_CT = 0
sum_CT2 = 0

## outer loop is the number of simulations bigger is better

for j = 1:M

        St = S
        BARRIERCROSSED = false
    ## inner loop is simulating each price path of length N
        for i = 1:N

        St = St * exp( nudt + sigsdt * randn()) ## note, it only add the changes, does not store the path

        if St <= H

                BARRIERCROSSED = true
                break
        end

        end

    ## wraps up and stores all the values of each path

if BARRIERCROSSED == true
            CT = 0

        else

            CT = max(0, St - K)
        end

sum_CT = sum_CT + CT
sum_CT2 = sum_CT2 + CT^2

    end

  ## discounting the average call value
call_value = sum_CT / M * exp(-r*T)

SD = (sqrt(sum_CT2 - 1/M * (sum_CT^2)) * exp(-2*r*T)) / (M-1)
SE = SD / sqrt(M)

println("The Call Value is $call_value")
println("The SD is $SD")
println("The SE is $SE")

MC = [call_value, SD, SE]

end


function MultBinTreeAMPut(K, T, S, r, N, u, d)
        dt = T/N ## Time to Maturity/ Number of Steps
        p = (exp(r * dt) - d) / (u - d)
        disc = exp(-r * dt)

        #St = linspace(1,N+1,N+1)

        St = Array(Float64, (N+1, N+1))

        # Initialising asset prices at maturity

        St[1 , N+1] = S * u^N
        for j in 2:(N+1)
                St[j, N+1] = St[j-1, N+1] * d/u
        end

        #St = flipdim(St, 1)

        # create Array for options value tree

        P = Array(Float64, (N+1, N+1))#linspace(1, N+1, N+1)

        # Initialise Option Values at maturity

        for j in 1:(N+1)
                P[j, N+1] = max(0.0, K - St[j, N+1])
        end

        # Stepping back through the tree

        for i = linrange(N, 1, N)
                for j in 1:i
                      P[j, i] = disc * (p * P[j, i+1] + (1-p) * P[j+1, i+1])
                      St[j,i] = St[j,i+1] * d
                      P[j, i] = max(P[j, i], K - St[j,i])
                end
        end

P

end

MultBinTreeAMPut(100,1,100,0.06,3,1.1,0.9091)


export factors
export primefactors
export totient

"""
Return sorted list of unique factors for a number n.
"""
function factors(n::Integer)::Array{Integer}
    factors = Set{Integer}(1)
    for i in 2:Integer(floor(sqrt(n)))
        if n % i == 0
            push!(factors, i)
            push!(factors, div(n, i))
        end
    end
    return sort(collect(factors))
end

"""
Return sorted list of prime factors for number n.
"""
function primefactors(n::Integer)::Array{Integer}
    factors = ones(Integer, 0)

    # Store all of the factors of 2.
    while n % 2 == 0
        push!(factors, 2)
        n = fld(n, 2)
    end

    # Store all other prime factors, increasing to sqrt(n).
    for i in 3:2:Integer(floor(sqrt(n)))
        while n % i == 0
            push!(factors, i)
            n = fld(n, i)
        end
    end

    # Store last prime factor.
    if n > 1
        push!(factors, n)
    end

    return sort(factors)
end

"""
Euler's totient function returns the number of positive integers up to n that
are releatively prime to n.  Pull out the prime factors f and take a produce of
n and all (1 - 1/f).
"""
function totient(n::Integer)::Integer
    factors = primefactors(n)
    last_prime = 0
    result = Float64(n)
    for f in factors
        if f == last_prime
            continue
        else
            result *= (1 - 1 / f)
            last_prime = f
        end
    end
    return Integer(round(result))
end


half(L) = div(L - 1, 2)

logZ(M::Matrix) = logdet(I + M)
Z(M::Matrix) = det(I + M)

function pairing(L, h)
    E(k) = sqrt(1 + h^2 + 2h * cospi((2k + 1) / L))
    ζ(k) = - (h + cospi((2k + 1) / L))
    u(k) = sqrt((1 + ζ(k) / E(k)) / 2)
    v(k) = sqrt((1 - ζ(k) / E(k)) / 2)
    g(r) = 2 / L * sum(sinpi((2k + 1) / L * r) * v(k) / u(k) for k in 0:div(L - 1, 2))

    M = zeros(L, L)
    for i in 1:L, j in 1:L
        M[i,j] = g(i -  j)
    end
    return M
end

function get_prob(x::BitVector, M::Matrix)
    amp = log(abs.(det(M[x .> 0, x .> 0])))
    return amp
end

function get_all_probs(M::Matrix)
    L = size(M, 1)
    logZ = log(abs(det(M)))
    x = i->bitarray(i, L)
    amps = [get_prob(x(i), M) for i in 0:2^L - 1]
    amps = exp.(amps)
    amps2 = normalize(amps, 1)
    return round.(amps, digits = 7), round.(amps2, digits = 7)
end

# for L in [2, 4,8,16,20, 32]
#     P = pairing(L, 1.0)
#     Z1 = logdet(I + P)
#     Z2 = log(sum(get_all_probs(P)[1]))
#     println("L is $L Z is $Z1 and $Z2")
# end


using Element
using Base.Test

using Element: lingrid, integrate, findsteps, eig, loggrid
using Element: SphericalHarmonicTable, radschrod1, cart2sph, realsphharm, enumeratelm, randp, radpoisson

# findsteps

f = (x,c) -> round(Int,x^2+c)
intervals = findsteps(f,0.0,2.0,args=1.0)
@test intervals == [(0.0,1.0),(1.0,1.5),(1.5,1.75),(1.75,2.0)]

# eig

x = lingrid(0,1,1000)
dx = x[2]-x[1]
v = 0*x
aE = E -> 2*(E-v)
qmin, qmax = 0,10

evs,efs,q = eig(aE,dx,qmin,qmax,Emin=0.0,Emax=20.0)
n = length(evs)
efs ./= sqrt(integrate(x, efs.^2))'  # normalization

exact_evs = [j^2*pi^2/2 for j in 1:n]
exact_efs = hcat([sqrt(2)*sin(j*pi*x) for j in 1:length(evs)]...)

@test q == collect(qmin:qmax)
@test_approx_eq integrate(x,efs.^2) fill(1.0,n)
@test_approx_eq_eps integrate(x, efs.^2 - exact_efs.^2) fill(0.0,n) 1e-12
@test_approx_eq_eps evs exact_evs 1e-5

# radschrod1

r = loggrid(-22,4,1000)
v = -1./r

n,l = 1,0
ev,ef = radschrod1(r,v,n,l)
@test_approx_eq_eps ev -1/(2*n^2) 1e-8
@test_approx_eq_eps integrate(r, (ef.^2 - (2r.*exp(-1*r)).^2).^2) 0.0 1e-12

n,l = 2,0
ev,ef = radschrod1(r,v,n,l)
@test_approx_eq_eps ev -1/(2*n^2) 1e-8
@test_approx_eq_eps integrate(r, (ef.^2 - (-1*r/sqrt(2).*(1-0.5*r).*exp(-0.5*r)).^2).^2) 0.0 1e-12

n,l = 2,1
ev,ef = radschrod1(r,v,n,l)
@test_approx_eq_eps ev -1/(2*n^2) 1e-8
@test_approx_eq_eps integrate(r, (ef.^2 - (r/3^0.5/2^(3./2).*r.*exp(-0.5*r)).^2).^2) 0.0 1e-12

n,l = 3,1
ev,ef = radschrod1(r,v,n,l)
@test_approx_eq_eps ev -1/(2*n^2) 1e-8
@test_approx_eq_eps integrate(r, (ef.^2 - (r*8./27/sqrt(6).*(1-r/6).*r.*exp(-1.*r/3)).^2).^2) 0.0 1e-12

# radpoisson
r = loggrid(-13,5,500)
rho = exp(-2r)

l = 0
vh_exact = 1/4*exp(-2r).*(1 + 2r) + (1 + exp(-2r).*(-1 - 2r.*(1 + r)))./(4r)  # l = 0
vh = radpoisson(r,rho,l)

@test_approx_eq_eps vh vh_exact 1e-5
@test_approx_eq_eps integrate(r, rho.*vh_exact/2) integrate(r, rho.*vh/2) 1e-6

l = 1
vh_exact = (3*exp(-2r).*(-1 + exp(2r) - 2r - 2r.^2))./(8r.^2)  # l = 1
vh = radpoisson(r,rho,l)

@test_approx_eq_eps vh vh_exact 1e-5
@test_approx_eq_eps integrate(r, rho.*vh_exact/2) integrate(r, rho.*vh/2) 1e-6

# spherical harmonics table

p = randp(3)[]
p = p/norm(p)
r,θ,ϕ = cart2sph(p)

lmax = 8
Y = SphericalHarmonicTable(lmax)
Y(p)  # precompute spherical harmonics

a = [realsphharm(l,m,θ,ϕ) for (l,m) in enumeratelm(lmax)]
b = [Y[l,m] for (l,m) in enumeratelm(lmax)]

@test_approx_eq a b


#=
Project:      MIRTdev
File:         mri_geom_compress.jl
Description:  MRI Coil Compression
Author:       Rajas Gupta
Date Created: October 8th 2019
Date Update:  October 8th 2019
=#

export ir_mri_coil_compress

using LinearAlgebra: svd, norm

"""
    (odata, σ, Vr) = ir_mri_coil_compress(idata ; ncoil)

MRI coil compression via PCA.
Given multiple MRI surface coil images (idata), use SVD/PCA
to find a smaller number of virtual coil images (odata).

In:
* `idata` `[(N) n_in]`: noisy complex images (2D or 3D) for each coil

Option:
* `ncoil` Desired # of virtual coils (default: 1)

Out:
* `odata` `[(N) ncoil]`: virtual coil images
* `σ`     `[n_in]`: singular values.
* `Vr`    `[n_in, ncoil]`: compression matrix for reducing other data.

todo: currently ignores noise correlations

"""
function ir_mri_coil_compress(idata::AbstractArray{<:Number} ; ncoil::Int = 1)

    idim = size(idata)
    n_in = idim[end]
    idata = reshape(idata, :, n_in)

    (_, σ, V) = svd(idata)

    Vr = V[:, 1:ncoil]
    odata = idata * Vr
    odata = reshape(odata, idim[1:end-1]..., ncoil)
    return (odata, σ, Vr)
end

"""
    snr2sigma(db, yb)
convert SNR in dB to noise σ for complex gaussian noise
"""
function snr2sigma(db, yb::AbstractArray{<:Complex})
    10^(-db/20) * norm(yb) / sqrt(length(yb)) / sqrt(2)
end


const ykow = [1.957e-1, 1.947e-1, 1.735e-1, 1.600e-1, 8.44e-2, 6.27e-2,
              4.56e-2,  3.42e-2, 3.23e-2, 2.35e-2, 2.46e-2]

const ukow = [4.0e0, 2.0e0, 1.0e0, 5.0e-1, 2.5e-1, 1.67e-1,
              1.25e-1, 1.0e-1, 8.33e-2, 7.14e-2, 6.25e-2]

const kowalik_osborne = let res_init=zeros(11), jac_init=zeros(11,4), x_init=[.25, .39, .415, .39]

    function res(x, r)
        for i = 1:11
            r[i] = ykow[i] - x[1]*(ukow[i] ^ 2 + x[2] * ukow[i]) / (ukow[i] ^ 2 + x[3]*ukow[i] + x[4])
        end
        return r
    end

    function jac(x, j)
        for i = 1:11
            t = -1.e0 / (ukow[i]^2 + x[3] * ukow[i] + x[4])
            j[i, 1] = t * (ukow[i]^2 + x[2] * ukow[i])
            j[i, 2] = x[1] * ukow[i]*t
            t = t * j[i, 1] * x[1]
            j[i, 3] = ukow[i] * t
            j[i, 4] = t
        end
        return j
    end

    f(;scale=1, verbose=false, print_steps=false) = testone("kowalik_osborne", res, jac,
                                                            res_init, jac_init, x_init;
                                                            scale=scale, verbose=verbose,
                                                            print_steps=print_steps)
end


using Statistics

export testchaos01

"""
    testchaos01(φ::Vector [, cs, N0]) -> chaotic?
Perform the so called "0-1" test for chaos introduced by Gottwald and
Melbourne [1] on the timeseries `φ`.
Return `true` if `φ` is chaotic, `false` otherwise.

## Description
This method tests if the given timeseries is chaotic or not by transforming
it into a two-dimensional diffusive process. If the timeseries is chaotic,
the mean square displacement of the process grows as `sqrt(length(φ))`,
while it stays constant if the timeseries is regular.
The implementation here computes `K`, the correlation coefficient (median
of `Kc for c ∈ cs`), and simply checks if `K > 0.5`.

If you want to access the various `Kc` you should call the method
`testchaos01(φ, c::Real, N0)` which returns `Kc`.

`cs` defaults to `3π/5*rand(10) + π/4` and `N0`, the length
of the two-dimensional process, is `N0 = length(φ)/10`.

Notice that for data sampled from continous dynamical systems, some
care must be taken regarding the values of `cs`, see [1].

## References

[1] : Gottwald & Melbourne, “The 0-1 test for chaos: A review”
[Lect. Notes Phys., vol. 915, pp. 221–247, 2016.](www.doi.org/10.1007/978-3-662-48410-4_7)
"""
function testchaos01(φ::Vector, cs = 3π/5*rand(10) .+ π/4, N0 = Int(length(φ)÷10))
    K = median(testchaos01(φ, c, N0) for c in cs)
    return K > 0.5
end

function testchaos01(φ::Vector, c::Real, N0 = Int(length(φ)÷10))
    N, E = length(φ), mean(φ)
    @assert N0 ≤ N/10
    pc, qc = trigonometric_decomposition(φ, c)
    Dc = mmsd(E, pc, qc, N0, c)
    Kc = cor(Dc, 1:N0)
    return Kc
end

function trigonometric_decomposition(φ, c)
    X = promote_type(eltype(φ), typeof(c))
    N = length(φ)
    pc, qc = zeros(X, N), zeros(X, N)
    @inbounds for n in 1:N-1
        si, co = sincos(c*n)
        pc[n+1] = pc[n] + φ[n]*co
        qc[n+1] = qc[n] + φ[n]*si
    end
    return pc, qc
end

"modified mean square displacement"
function mmsd(mf, pc::Vector{T}, qc, N0, c) where T
    N, Dc = length(pc), zeros(T, N0)
    f = mf^2/(1-cos(c)) # constant factor for Dc
    for n in 1:N0
        Mc = sum((pc[n+j] - pc[j])^2 + (qc[n+j] - qc[j])^2 for j in 1:N-n)/(N-n)
        Dc[n] = Mc - f*(1 - cos(n*c))
    end
    return Dc
end


#=
@author Alvaro Lucas Castillo Calabacero
contact alvarolucascc96@gmail.com
=#

#=
Recursive algorithm for resolve MergeSort
=#
function mergeSort!(array, start = 1, finish = length(array))
    if start < finish
        middle = div(start+finish, 2)
        mergeSort!(array, start, middle)
        mergeSort!(array, middle+1, finish)
        merge(array, start, middle, finish)
    end
    return array
end

#=
algorithm for merge
 =#
function merge(array, start, half, finish)
    ward = typemax(eltype(array))
    leftSubArray = array[start:half]
    rightSubArray = array[(half+1):finish]
    push!(leftSubArray, ward)
    push!(rightSubArray, ward)
    auxLeft=1
    auxRight = 1
    for i in start:finish
      if leftSubArray[auxLeft] <= rightSubArray[auxRight]
          array[i] = leftSubArray[auxLeft]
          auxLeft += 1
      else
          array[i] = rightSubArray[auxRight]
          auxRight += 1
      end
    end
end
#can change this total numbers for process of sort
totalNumbers = 10000000;
list = [rem(rand(Int128),100000) for i =1:totalNumbers]

print("Sorted List in :")
#get time to solve whit secuencial algorithm
@time mergeSort!(list)
print(list)


# Implement code blocks 3.2 - 3.10 in McElreath's Statistical Rethinking, 2nd edition.

using Distributions
using Plots
using Statistics
using StatsBase

grid_size = 1000
num_samples = 1000

## Code block 3.2
param_grid = range(0, 1, length=grid_size)
prior = ones((grid_size))
likelihood = [pdf(Binomial(9, p), 6) for p in param_grid]
posterior = likelihood .* prior
posterior = posterior / sum(posterior)

@assert abs(sum(posterior) - 1) < 1e-4 "Posterior does not sum to 1."
@assert minimum(posterior) >= 0
@assert maximum(posterior) <= 1

## Code block 3.3
samples = sample(param_grid, Weights(posterior), num_samples; replace=true, ordered=false)

## Code block 3.4
display(scatter(samples, xlabel="Sample number", ylabel="Proportion water (p)"))

## Code block 3.5
# use a histogram, because I don't yet know a comparable tool for density estimation in
# Julia.
display(histogram(samples, bins=:40))

## Code block 3.6
sum(posterior[param_grid .< 0.5])

## Code block 3.7
sum(samples .< 0.5) / num_samples

## Code block 3.8
sum(0.5 .< samples .< 0.75) / num_samples

## Code block 3.9
quantile(samples, 0.8)

## Code block 3.10
quantile(samples, [0.1, 0.9])


function gss(arr::Vector{<:Number})
    smax = hmax = tmax = 0
    for head in eachindex(arr), tail in head:length(arr)
        s = sum(arr[head:tail])
        if s > smax
            smax = s
            hmax, tmax = head, tail
        end
    end
    return arr[hmax:tmax]
end

arr = [-1, -2, 3, 5, 6, -2, -1, 4, -4, 2, -1]
subseq = gss(arr)
s = sum(subseq)

println("Greatest subsequential sum of $arr:\n → $subseq with sum $s")


"""
    fresnels(x)

Compute the Fresnel S integral of type 0 (integral of `sin(πt²/2)`).
"""
fresnels(x) = (1 + im) / 4 * (erf((1 + im)√π / 2 * x) - im * erf((1 - im)√π / 2 * x))

"""
    fresnelc(x)

Compute the Fresnel C integral of type 0 (integral of `cos(πt²/2)`).
"""
fresnelc(x) = (1 - im) / 4 * (erf((1 + im)√π / 2 * x) + im * erf((1 - im)√π / 2 * x))


using JuMP
using Cbc
# cria o modelo para a otimização linear, utiliza o cbc como solver
m = Model(with_optimizer(Cbc.Optimizer))

set_time_limit_sec(m , 1)

n = 6

# define as variaveis binárias xij e o u como inteiro
@variable(m , x[1:n , 1:n] , Bin)
@variable(m , u[1:n] , integer=true)

fix(u[1] , 0)

# cria o grafo de pesos para fazer conexão
y = zeros(Int64 , n , n)

# dados do exemplo do slide da prof(trabalho -  material auxiliar)
# é o exemplo das cidades que está logo depois do MTZ
y = [ 0  186 105 208 177 94 ;
     187 0 89.8 223 255 254 ;
     99.9 89 0  203 204 172 ;
     206 220 203 0 377 295 ;
     168 251 201 376 0 156 ;
     86.8 255 173  293 159  0]

#for i in 1:5
#    for j in 1:5
#        y[i,j] = i+j
#    end
#end

# função objetiva
@objective(m , Min , sum(x.*y))

# constraint de que a soma de uma linha vai ser 1
@constraint(m , [i= 1:n] , sum(x[i,1:end .!=i]) == 1)
# constraint de que a soma de uma coluna vai ser 1
@constraint(m , [i= 1:n] , sum(x[1:end.!=i,i]) == 1)

# constraint do u
@constraint(m , [i = 1:n , j =2:n , i != j] , u[j] >= u[i] + x[i,j] - n *(1-x[i,j]))

#printa os dados do modelo
println(m)

# resolve e printa os resultados
optimize!(m)

uAux = value.(u)
xAux = value.(x)

for i in 1:n
    println("u$(i) $(uAux[i])")
end

print("\n\n Xij:\n")

for i in 1:n
    for j in 1:n
        print("$(xAux[i,j]) ")
    end
    println()
end