function bubblesort!(arr::AbstractVector)
    for _ = 2:length(arr), j = 1:length(arr)-1
        if arr[j] > arr[j+1]
            arr[j], arr[j+1] = arr[j+1], arr[j]
        end
    end
    return arr
end

function bubblesort(OriginalArr::AbstractVector)
    arr = copy(OriginalArr)
    for _ = 2:length(arr), j = 1:length(arr)-1
        if arr[j] > arr[j+1]
            arr[j], arr[j+1] = arr[j+1], arr[j]
        end
    end
    return arr
end

function quicksort!(arr::AbstractVector, i=1, j=length(arr))
    if i < j
        pivot = arr[rand(i:j)]
        left, right = i, j
        while left <= right
            while arr[left] < pivot
                left+=1
            end
            while arr[right] > pivot
                right-=1
            end
            if left <= right
                arr[left], arr[right] = arr[right], arr[left]
                left+=1
                right-=1
            end
            quicksort!(arr, i, right)
            quicksort!(arr, left, j)
        end
    end
    return arr
end

function quicksort(OriginalArr::AbstractVector, i=1, j=length(OriginalArr))
    arr = copy(OriginalArr)
    if i < j
        pivot = arr[rand(i:j)]
        left, right = i, j
        while left <= right
            while arr[left] < pivot
                left+=1
            end
            while arr[right] > pivot
                right-=1
            end
            if left <= right
                arr[left], arr[right] = arr[right], arr[left]
                left+=1
                right-=1
            end
            quicksort!(arr, i, right)
            quicksort!(arr, left, j)
        end
    end
    return arr
end

function removeDuplicates(arr::AbstractVector)
    filterArr = Vector{eltype(arr)}()
    if length(arr) > 0
        last_element = arr[1]
        push!(filterArr, last_element)
        for element in arr
            if element != last_element
                last_element = element
                push!(filterArr, last_element)
            end
        end
    end
    return filterArr
end

function convertArrInt(a::Array{String, 1})
    a1 = Array{Int, 1}()
    if length(a) > 0
        for element in a
            element_n = parse(Int, element)
            push!(a1, element_n)
        end
    end
    return a1
end

function main(args::Array{String, 1})
    v = convertArrInt(args)
    println(v)
    v = quicksort(v)
    println(v)
    v = removeDuplicates(v)
    println(v)
end

function main()
    v = [4, 1, -3, 6, 10, 4]

    println(v)

    v = quicksort(v)

    println(v)

    v = removeDuplicates(v)

    println(v)
end

if length(ARGS) > 0
    main(ARGS)
else
    main()
end


module NewtonsMethod

using Distributions, LinearAlgebra, Statistics

function newtonroot(f, f_prime; x_0=0.5, tolerance=1E-7, maxiter=1000)
    normdiff=Inf
    iter=1
    x_old=x_0

    while normdiff > tolerance && iter <= maxiter
        x_new=x_old-f(x_old)/f_prime(x_old)
        normdiff = norm(x_new - x_old)
        x_old=x_new
        iter+=1
    end

    if iter <= maxiter
        return (value = x_old, normdiff=normdiff, iter=iter)
    else
        return (value =nothing, normdiff=normdiff, iter=iter)
    end
end

export(newtonroot)

using ForwardDiff

function newtonroot1(f; x_0=0.5, tolerance=1E-7, maxiter=1000)

    D(f) = x -> ForwardDiff.derivative(f, x)
    f_autoprime = D(f)

    normdiff=Inf
    iter=1
    x_old=x_0

    while normdiff > tolerance && iter <= maxiter
        x_new=x_old-f(x_old)/f_prime(x_old)
        normdiff = norm(x_new - x_old)
        x_old=x_new
        iter+=1
    end

    if iter <= maxiter
        return (value = x_old, normdiff=normdiff, iter=iter)
    else
        return (value =nothing, normdiff=normdiff, iter=iter)
    end

end

export(newtonroot1)

end # module


## Exercise 6-2
## Use incremental development to write a function called hypotenuse that returns the length of the hypotenuse of a right triangle given the lengths of the other two legs as arguments. Record each stage of the development process as you go.
println("Ans: ")

function hypotenuse(a, b)
    a² = a ^ 2
    b² = b ^ 2
    c² = a² + b²
    c = √c²
    return c
end

println(hypotenuse(3, 4))

println("End.")


# # Getting started

#md # [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/generated/Getting started.ipynb)
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/generated/Getting started.ipynb)
# **Adapted from**: SOSTOOLS' SOSDEMO1 (See Section 4.1 of [SOSTOOLS User's Manual](http://sysos.eng.ox.ac.uk/sostools/sostools.pdf)) and Example 2.4 of [PJ08]
#
# P. Parrilo and A. Jadbabaie
# *Approximation of the joint spectral radius using sum of squares*.
# Linear Algebra and its Applications, Elsevier (2008), 428, 2385-2402

using Test #src
using DynamicPolynomials
@polyvar x y
p = 2*x^4 + 2*x^3*y - x^2*y^2 + 5*y^4

# We need to pick an SDP solver, see [here](https://jump.dev/JuMP.jl/v0.21.6/installation/#Supported-solvers) for a list of the available choices.
# We use `SOSModel` instead of `Model` to be able to use the `>=` syntax for Sum-of-Squares constraints.

using SumOfSquares
import CSDP
solver = optimizer_with_attributes(CSDP.Optimizer, MOI.Silent() => true)
model = SOSModel(solver)
con_ref = @constraint(model, p >= 0)
optimize!(model)
@test primal_status(model) == MOI.FEASIBLE_POINT #src
primal_status(model)

# We see above that the solver found a feasible solution.
# We now inspect this solution:

q = gram_matrix(con_ref)
Q = getmat(q) #src
@test isapprox(Q[1, 1], 2, rtol=1e-5) #src
@test isapprox(Q[1, 2], 1, rtol=1e-5) #src
@test isapprox(Q[3, 3], 5, rtol=1e-5) #src
@test abs(Q[2, 3]) < 1e-5 #src
@test isapprox(Q[2, 2] + 2Q[1, 3], -1, rtol=1e-5) #src

# We can get the SOS decomposition from the gram matrix as follows:

sosdec = SOSDecomposition(q)
@test isapprox(sosdec, sos_decomposition(con_ref)) #src
@test isapprox(sum(sosdec.ps.^2), p; rtol=1e-4, ztol=1e-6) #src

# We now seek for the SOS decomposition of the following polynomial:

p = 4*x^4*y^6 + x^2 - x*y^2 + y^2

# We build the same model as previously with this new polynomial.
# Here we can use `Model` instead of `SOSModel` as we explicitly constrain
# `p` to belong to the SOS cone with `p in SOSCone()`.

model = Model(solver)
con_ref = @constraint(model, p in SOSCone())
optimize!(model)
@test primal_status(model) == MOI.FEASIBLE_POINT #src
primal_status(model)

# We can query the SOS decomposition direction from the constraint reference
# as follows:

sos_decomposition(con_ref)
# p should be GramMatrix([1, 0, -1/2, 0, -1, 1, 0, -2/3, 0, 4/3, 0, 0, 2, 0, 4], [y, x, x*y, x*y^2, x^2*y^3]) #src


using CompScienceMeshes
using SauterSchwabQuadrature

pI = point(1,5,3)
pII = point(2,5,3)
pIII = point(7,1,0)
pVI = point(10,11,12)
pVII = point(10,11,13)
pVIII = point(11,11,12)

Sourcechart = simplex(pI,pII,pIII)
Testchart = simplex(pVIII,pVII,pVI)

Accuracy = 12

function integrand(x,y)
			return(((x-pI)'*(y-pVII))*exp(-im*1*norm(x-y))/(4pi*norm(x-y)))
end

result = sauterschwabintegral(Sourcechart, Testchart, integrand, Accuracy, Accuracy)
println(result)

#=For those who want to test the sauterschwab_nonparameterized() function,
may uncomment the following three lines=#

#pd = PositiveDistance(Accuracy)
#result2 = sauterschwab_nonparameterized(Sourcechart, Testchart, integrand, pd)
#println(result2)

#=In this case the two charts from above can be used and the order of the points
within the simplex() fucntions can be changed arbitrarily. The user may also
compare the two results and see that both are equal.=#


@test [combinations([])...] == []
@test [combinations(['a', 'b', 'c'])...] == [['a'],['b'],['c'],['a','b'],['a','c'],['b','c'],['a','b','c']]

@test [combinations("abc",3)...] == [['a','b','c']]
@test [combinations("abc",2)...] == [['a','b'],['a','c'],['b','c']]
@test [combinations("abc",1)...] == [['a'],['b'],['c']]
@test [combinations("abc",0)...] == [[]]
@test [combinations("abc",-1)...] == []

@test filter(x->iseven(x[1]),[combinations([1,2,3],2)...]) == Any[[2,3]]

# multiset_combinations
@test [multiset_combinations("aabc", 5)...] == Any[]
@test [multiset_combinations("aabc", 2)...] == Any[['a','a'],['a','b'],['a','c'],['b','c']]
@test [multiset_combinations("aabc", 1)...] == Any[['a'],['b'],['c']]
@test [multiset_combinations("aabc", 0)...] == Any[Char[]]
@test [multiset_combinations("aabc", -1)...] == Any[]
@test [multiset_combinations("", 1)...] == Any[]
@test [multiset_combinations("", 0)...] == Any[Char[]]
@test [multiset_combinations("", -1)...] == Any[]

# with_replacement_combinations
@test [with_replacement_combinations("abc", 2)...] == Any[['a','a'],['a','b'],['a','c'],
                                                              ['b','b'],['b','c'],['c','c']]
@test [with_replacement_combinations("abc", 1)...] == Any[['a'],['b'],['c']]
@test [with_replacement_combinations("abc", 0)...] == Any[Char[]]
@test [with_replacement_combinations("abc", -1)...] == Any[]
@test [with_replacement_combinations("", 1)...] == Any[]
@test [with_replacement_combinations("", 0)...] == Any[Char[]]
@test [with_replacement_combinations("", -1)...] == Any[]

#cool-lex iterator
@test_throws DomainError [CoolLexCombinations(-1, 1)...]
@test_throws DomainError [CoolLexCombinations(5, 0)...]
@test [CoolLexCombinations(4,2)...] == Vector[[1,2], [2,3], [1,3], [2,4], [3,4], [1,4]]
@test isa(iterate(CoolLexCombinations(1000, 20))[2], Combinatorics.CoolLexIterState{BigInt})

# multichoose
@test multichoose(0, 1) == 0
@test multichoose(0, 2) == 0
@test multichoose(0, 3) == 0
@test multichoose(1, 1) == 1
@test multichoose(1, 2) == 1
@test multichoose(1, 3) == 1
@test multichoose(2, 1) == 2
@test multichoose(2, 2) == 3
@test multichoose(2, 3) == 4
@test multichoose(3, 0) == 1
@test multichoose(3, 1) == 3
@test multichoose(3, 2) == 6
@test multichoose(3, 3) == 10
@test multichoose(10, 8) == 24310

# multicombinations
@test [multicombinations("abc", 0)...] == [Char[]]
@test [multicombinations("abc", 1)...] == [['a'], ['b'], ['c']]
@test [multicombinations("abc", 2)...] == [['a','a'], ['a','b'], ['a','c'], ['b','b'], ['b','c'], ['c','c']]
@test [multicombinations("abc", 3)...] == [['a','a','a'], ['a','a','b'], ['a','a','c'], ['a','b','b'],
    ['a','b','c'], ['a','c','c'], ['b','b','b'], ['b','b','c'], ['b','c','c'], ['c','c','c']]

# Power set
@test collect(powerset([])) == Any[[]]
@test collect(powerset(['a', 'b', 'c'])) == Any[[],['a'],['b'],['c'],['a','b'],['a','c'],['b','c'],['a','b','c']]
@test collect(powerset(['a', 'b', 'c'], 1)) == Any[['a'],['b'],['c'],['a','b'],['a','c'],['b','c'],['a','b','c']]
@test collect(powerset(['a', 'b', 'c'], 1, 2)) == Any[['a'],['b'],['c'],['a','b'],['a','c'],['b','c']]


"""
    KS_entropy_rate(spectrum::Vector{Float64})

Given the Lyapunov spectrum, returns the Kolmogorov-Sinai entropy rate of the
growth of an infinitessimal perturbation of a trajectory on a chaotic attractor
"""
function KS_entropy_rate(spectrum)
    return sum([val for val in spectrum if val>0.0])
end

"""
    KY_attractor_dim(spectrum::Vector{Float64})

Given the Lyapunov spectrum, returns the Kaplan-Yorke dimensionality of the
attractor. The calculation is intuitively interpretable as the maximum
dimensionality of a cross-section of a ball of points in phase space, which
is invariant in area as the ball is deformed by evolution through time
"""
function KY_attractor_dim(spectrum)
    csum = 0.0
    dim = 0
    while csum+spectrum[dim+1] >= 0
        csum += spectrum[dim+1]
        dim += 1
    end
    return dim + csum/abs(spectrum[dim+1])
end


"""
This functions are julia implementations of the functions in scikit-tda/tadasets.

Reference: https://github.com/scikit-tda/tadasets
"""

abstract type TopologicalDataType end
struct Infty <: TopologicalDataType end
struct SwissRoll <: TopologicalDataType end
struct Sphere <: TopologicalDataType
    d::Int64
end
struct Torus <: TopologicalDataType
    R::Float64
    r::Float64
end

function Sphere()
    return Sphere(2)
end
function Torus()
    return Torus(4.0, 1.0)
end

import Base.rand
"""
Infty

Sample random points on lemniscate.

Reference: https://math.stackexchange.com/a/7989/459895
"""
function rand(S::Infty, n::Int64; noise = 0.01)
    t = 2π * rand(Float64, n)
    x = cos.(t)
    y = sin.(t) .* cos.(t)
    return vcat(x',y') + noise * randn(2, n)
end

"""
Swiss Roll
Reference: http://people.cs.uchicago.edu/~dinoj/manifold/swissroll.html
"""
function rand(S::SwissRoll, n::Int64; noise = 0.01)
    t = 2π * rand(Float64, n)
    y = 2π * rand(Float64, n)
    x = t .* cos.(t)
    z = t .* sin.(t)
    return vcat(x',y',z') + noise * randn(3, n)
end

"""
d-dimensional Sphere

Reference: https://en.wikipedia.org/wiki/N-sphere#Generating_random_points
"""
function rand(S::Sphere, n::Int64; noise = 0.01)
    direction = randn(S.d, n)
    boundary = direction ./ sqrt.(sum(abs2, direction, dims = 1))
    return boundary + noise * randn(S.d, n)
end

"""
Torus
Reference: https://en.wikipedia.org/wiki/Torus#Geometry
"""
function rand(S::Torus, n::Int64; noise = 0.01)
    u = 2π * rand(Float64, n)
    v = 2π * rand(Float64, n)
    x = (S.R .+ S.r * cos.(v)) .* cos.(u)
    y = (S.R .+ S.r * cos.(v)) .* sin.(u)
    z = S.r * sin.(v)
    return vcat(x',y',z') + noise * randn(3, n)
end


@testset "factorial_ij" begin
    for i in 0:40, j in i:40
        @test SqState.factorial_ij(i, j) == factorial(big(i)) / factorial(big(j))
        @test SqState.factorial_ij(j, i) == factorial(big(i)) / factorial(big(j))
    end
end

@testset "z" begin
    x_range = -10:0.01:10
    p_range = -6:0.01:6

    for x in x_range, p in p_range
        @test SqState.z(x, p) == sqrt(2.)*(x + p*im)
    end

    xs = collect(x_range)
    ps = collect(p_range)
    @test SqState.z(xs, ps) == sqrt(2.).*(xs .+ ps' .* im)
end

@testset "α" begin
    m_range = n_range = 1:35

    for m in m_range, n in m:n_range.stop
        @test SqState.α(m, n) == max(m, n) - min(m, n)
    end

    ms = collect(m_range)
    ns = collect(n_range)
    @test SqState.α(ms, ns) == max.(ms, ns') .- min.(ms, ns')
end

@testset "gaussian_function" begin
    tol = 1e-14

    x_range = -10:0.01:10
    p_range = -6:0.01:6

    for x in x_range, p in p_range
        @test isapprox(SqState.gaussian_function(x, p), exp(-(x^2 + p^2)) / π, atol=tol)
    end

    xs = collect(x_range)
    ps = collect(p_range)
    @test isapprox(
        SqState.gaussian_function(xs, ps),
        exp.(-(xs.^2 .+ (ps').^2)) ./ π,
        atol=tol
    )
end

@testset "(-1)^i" begin
    for i in 1:50
        @test SqState.neg_one_to_power_of(i) == (-1)^i
    end
end

@testset "coefficient_of_wave_function" begin
    tol = 1e-14

    m_range = n_range = 1:35

    for m in m_range, n in n_range
        m1 = (n ≥ m) ? (-1)^m : (-1)^n
        @test isapprox(
            SqState.coefficient_of_wave_function(m, n),
            m1 * sqrt(factorial(big(min(m, n))) / factorial(big(max(m, n)))),
            atol=tol
        )
    end

    ms = collect(m_range)
    ns = collect(n_range)
    @test SqState.coefficient_of_wave_function(ms, ns) == SqState.coefficient_of_wave_function.(ms, ns')
end

@testset "z_to_power" begin
    tol = 1e-14

    m_range = n_range = 1:35
    x_range = -1:0.1:1
    p_range = -0.6:0.1:0.6

    for m in m_range, n in n_range, x in x_range, p in p_range
        z = (n >= m) ? sqrt(2.)*(x - p*im) : sqrt(2.)*(x + p*im)
        @test SqState.z_to_power(m, n, x, p) == z^(max(m, n) - min(m, n))
    end

    ms = collect(m_range)
    ns = collect(n_range)
    xs = collect(x_range)
    ps = collect(p_range)
    xs_ = reshape(collect(x_range), 1, 1, length(x_range))
    ps_ = reshape(collect(p_range), 1, 1, 1, length(p_range))
    @test SqState.z_to_power(ms, ns, xs, ps) == SqState.z_to_power.(ms, ns', xs_, ps_)
    @test SqState.z_to_power(ms, ns)(xs, ps) == SqState.z_to_power.(ms, ns', xs_, ps_)
    @test SqState.z_to_power(xs, ps)(ms, ns) == SqState.z_to_power.(ms, ns', xs_, ps_)
end


u, v, w = ℝ⁴(rand(4)), ℝ⁴(rand(4)), ℝ⁴(rand(4))
a, b = ℝ⁴(rand(4)), ℝ⁴(rand(4))
zero = ℝ⁴(0, 0, 0, 0)
α, β = rand(2)

## Abstract Vector Space Tests ##

@test isapprox(u + (v + w), (u + v) + w) # 1. associativity of addition
@test isapprox(u + v, v + u) # 2. commutativity of addition
@test isapprox(u + zero, u) # 3. the zero vector
@test isapprox(u - u, zero) # 4. the inverse element
@test isapprox(α * (u + v), α * u + α * v) # 5. distributivity Ι
@test isapprox((α + β) * u, α * u + β * u) # 6. distributivity ΙΙ
@test isapprox(α * (β * u), (α * β) * u) # 7. associativity of scalar multiplication
@test isapprox(1u, u) # 8. the unit scalar 1

## Product Space Tests ##

@test isapprox(dot(u, v), dot(v, u)) # symmetric
@test isapprox(dot(u, α * a + β * b), α * dot(u, a) + β * dot(u, b)) # linear
@test dot(u, u) ≥ 0 && isapprox(dot(u, zero), 0) # positive semidefinite
@test size(outer(u, v)) == (length(u), length(v))


using Pkg
Pkg.add("SMM")
using Optim
using HTTP
using GLM
using LinearAlgebra
using Random
using Statistics
using DataFrames
using DataFramesMeta
using CSV

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

function ols_gmm(beta, X, y)
    ssr = (y.-X*beta)'*(y.-X*beta)
    return ssr
end
g=Y-X*beta
end
beta_optim = optimize(a -> ols_gmm(a, X, y), rand(size(X,2)), LBFGS(), Optim.Options(g_tol=1e-8, iterations=100_000))
println(beta_optim.minimizer)

#Question2#part a#
X = [ones(size(df,1),1) df.age df.race.==1 df.collgrad.==1]
y = df.occupation

function mlogit(alpha, X, y)

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

    num = zeros(N,J)
    dem = zeros(N)
    for j=1:J
        num[:,j] = exp.(X*bigAlpha[:,j])
        dem .+= num[:,j]
    end

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

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

    return loglike
end

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

#partb#
unction mlogit_gmm(alpha, X, y)

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

        num = zeros(N,J)
        dem = zeros(N)
        for j=1:J
            num[:,j] = exp.(X*bigAlpha[:,j])
            dem .+= num[:,j]
        end

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

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

        return loglike
    end
    #part c#

    #we would ideally differentiate to see if the function is globally concave#

    #Question 3#
    randn()

    #Question4#
    using SMM, DataFrames
MA = SMM.parallelNormal() # Note: this line may take up to 5 minutes to execute
dc = SMM.history(MA.chains[1])
dc = dc[dc[:accepted].==true, :]
println(describe(dc))

#Question5#
function ols_smm(θ, X, y, D)
    K = size(X,2)
    N = size(y,1)
    β = θ[1:end-1]
    σ = θ[end]
    if length(β)==1
        β = β[1]
    end
    # N+1 moments in both model and data
    gmodel = zeros(N+1,D)
    # data moments are just the y vector itself
    # and the variance of the y vector
    gdata  = vcat(y,var(y))
    #### !!!!!!!!!!!!!!!!!!!!!!!!!!!!! ####
    # This is critical!                   #
    Random.seed!(1234)                    #
    # You must always use the same ε draw #
    # for every guess of θ!               #
    #### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ###
    # simulated model moments
    for d=1:D
        ε = σ*randn(N)
        ỹ = X*β .+ ε
        gmodel[1:end-1,d] = ỹ
        gmodel[  end  ,d] = var(ỹ)
    end
    # criterion function
    err = vec(gdata .- mean(gmodel; dims=2))
    # weighting matrix is the identity matrix
    # minimize weighted difference between data and moments
    J = err'*I*err
    return J
end


######################
# BASIC VARIABLES
######################

# Variable definition
x = 5
y = "3"
a,b = 3, 5
println(x," ", y," ", a," ", b) # 5 3 3 5

# Double a variable
x = 2x
println(x) # 10

# Make it exponential
x = x^2
println(x) # 100

# Array definition
arr = [1, 2, 3, 4]
println(arr) # [1, 2, 3, 4]

# Dot "." operator
arr .= arr.^2
println(arr) # [1, 4, 9, 16]

# @. macro
@. arr=arr^2
println(arr) # [1, 16, 81, 256]

# Double arr
arr = 2arr # or arr.*2
println(arr) # [2, 32, 162, 512]

# NaN
nan = NaN
println(nan == NaN) # false

######################
# COMPLEX NUMBERS
######################

# Complex number
c1 = 3+2im
c2 = 3-2im
println(c1 * c2) # 13 + 0im

# Adding real and complex number
println(x + c1) # 103+2im

# Adding complex and complex number
println(c2 + 3im) # 3+1im

# Real part
println(real(c1)) # 3

# Imaginary part
println(imag(c2)) # -2

# Complex conjugate
println(conj(c1) == c2) # true

######################
# RATIONAL NUMBERS
######################

# Define rational number
r1 = 4//5
println(r1) # 4//5

# If the numerator and denominator of a rational have common factors,
# they are reduced to lowest terms such that the denominator is non-negative

r2 = 4//8
println(r2) # 1//2

println(r1 == 8//10) # true


@testset "Expression Manipulation" begin
    a, b, c = LogicalSymbol.([:a, :b, :c])
    x, y, z = LogicalSymbol.([:x, :y, :z])

    @testset "Evaluation" begin
        st1 = a ∧ b
        @test evaluate(st1, Dict(a => true, b => true))
        @test !evaluate(st1, Dict(a => true, b => false))
        @test !evaluate(st1, Dict(a => false, b => false))

        st2 = (a ∧ b) → c
        @test evaluate(st2, Dict(a => true, b => true, c => true))
        @test evaluate(st2, Dict(a => false, b => true, c => true))
        @test evaluate(st2, Dict(a => false, b => false, c => true))
        @test !evaluate(st2, Dict(a => true, b => true, c => false))
    end

    @testset "Matching" begin
        st1 = a ∧ ¬b
        @test matches(st1, x)
        @test matches(st1, x ∧ y)
        @test !matches(st1, x ∨ y)
        @test !matches(st1, ¬x ∧ y)
        @test matches(st1, x ∧ ¬y)

        st2 = a → (b ∨ ¬c)
        @test matches(st2, x)
        @test matches(st2, x → y)
        @test matches(st2, x → (y ∨ z))
        @test matches(st2, x → (y ∨ ¬z))
        @test !matches(st2, ¬x)
        @test !matches(st2, ¬x → y)
        @test !matches(st2, x → ¬y)
        @test !matches(st2, x → (y ∧ z))

        st3 = (a ∧ b) ∨ (a ∧ b)
        @test matches(st3, x)
        @test matches(st3, x ∨ x)
        @test matches(st3, x ∨ y)
        @test matches(st3, (x ∧ y) ∨ (x ∧ y))
        @test !matches(st3, (x ∧ y) ∨ (y ∧ x))
        @test !matches(st3, ¬x)
    end

    # TODO: Rename this from replace to something more relevant to rule application
    @testset "Replacement" begin
        @test isequal(replace(a, x => ¬x), ¬a)
        @test isequal(replace(a ∧ b, x => ¬x), ¬(a ∧ b))
        @test isequal(replace(a ∧ a, (x ∧ x) => x), a)
        @test isequal(replace(a → b, (x → y) => (¬x ∨ y)), ¬a ∨ b)
        @test isequal(replace(a → ¬b, (x → y) => (¬x ∨ y)), ¬a ∨ ¬b)
        @test isequal(replace(¬(a ∨ b) ∨ ¬(a ∧ b), (¬x ∨ ¬y) => ¬(x ∧ y)), ¬((a ∨ b) ∧ (a ∧ b)))
    end
end


const cragg_levy = let res_init=zeros(5), jac_init=zeros(5,4), x_init=[1.0,2.0,2.0,2.0]

    function res(x, r)
        r[1] = (exp(x[1]) - x[2])^2
        r[2] = 10.0 * (x[2] - x[3])^3
        r[3] = (sin(x[3] - x[4]) / cos(x[3] - x[4]) )^2
        r[4] = x[1]^4
        r[5] = x[4] - 1.0
        return r
    end

    function jac(x, jac)
        jac[:] .= 0.0
        t = exp(x[1])
        jac[1, 2] = -2.0 * (t - x[2])
        jac[1, 1] = -t * jac[1, 2]
        jac[2, 2] = 30.0 * (x[2] - x[3])^2
        jac[2, 3] = -jac[2, 2]
        jac[3, 3] = 2.0*sin(x[3] - x[4]) / (cos(x[3] - x[4]))^3
        jac[3, 4] = -jac[3, 3]
        jac[4, 1] = 4.0*x[1]^3
        jac[5, 4] = 1.0
        return jac
    end

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


using Test

@testset "Galois field tests" begin
    include("galois_fields.jl")
end

@testset "Elliptic curve primitive tests" begin
    include("elliptic_curves.jl")
end

@testset "Testing NIST elliptic curves" begin
    include("curve_specs.jl")
end

@testset "Testing external fields" begin
    include("../examples/external_fields.jl")
end

@testset "Testing Specs module" begin
    include("spec.jl")
    include("primitives.jl")
    include("gbasis.jl")
    include("gecbasis.jl")
    include("conversions.jl")
    include("ecdsa/ecdsa_prime.jl")
    include("ecdsa/ecdsa_pb.jl")
    # It would be great to have a gaussian normal basis
end

@testset "Testing groups and ElGamal" begin
    include("groups.jl")
    include("elgamal.jl")
end


TOL = 0.5e-8

function bisection(f::Function, a::Real, b::Real;
                   ε::Real = TOL, iₘₐₓ::Int = 100)

    fa = f(a)
    fb = f(b)
    if fa * fb >= 0
        throw(DomainError("The signs of the interval [f($(a)), f($(b)] must
                          differ."))
    end

    if a > b # enforces that the condition a ≦ b holds
        a, b = b, a
    end

    c = (a + b) / 2
    i = 0

    while (b - a) > 2ε && n < iₘₐₓ
        c = (a + b) / 2
        fc = f(c)
        if fc == 0
            break
        elseif fc * fa < 0
            b = c
            fb = fc
        else
            a = c
            fa = fc
        end
        i += 1
    end
    c, i
end


"""
    ψ_Stommel(x, y; τ₀, β, r)

Stommel gyre streamfunction taken from equation (19.39) of Vallis (2nd edition) wher L is the
length of each side of the square domain, τ₀ is the magnitude of the wind stress, β is the
Rossby parameter, and r is the linear drag, or Rayleigh friction, acting on the vertically
integrated velocity.
"""
@inline ψ_Stommel(x, y; L, τ₀, β, r) = τ₀*π/β * (1 - x/L - exp(-x/L/ϵ(r, β, L))) * sin(π*y/L)

@inline u_Stommel(x, y; L, τ₀, β, r) = τ₀*π^2/β/L * (1 - x/L - exp(-x/L/ϵ(r, β, L))) * cos(π*y/L)
@inline v_Stommel(x, y; L, τ₀, β, r) = τ₀*π/β/L * (1 - 1/ϵ(r, β, L) * exp(-x/L/ϵ(r, β, L))) * sin(π*y/L)

@inline u_Stommel_max(; L, τ₀, β) = τ₀*π^2/β/L
@inline v_Stommel_max(; L, τ₀, β) = τ₀*π/β/L

@inline ϵ(r, β, L) = r / (β*L)


function redGauss(A::Array{Float64,2})
    # First, we make sure to convert all to a Float64 array
    # A = convert(Array{Float64,2},A)
    numRow, numCol = size(A); # Then retrieve some important variable names
    #
    for k in 1:numCol-1
        # Is the pivot zero?
        if A[k,k] == 0.0 # Yes
            nonzero = findall(x->x!==0.0, A[:,k]) # Find all non-zero elements in the current column
            selected = nonzero[1] # We then select the first non-zero element in the column
            A[k,:], A[selected,:] = A[selected,:],A[k,:] # We swap the rows
            pivot = A[k,k]; # And make a new pivot
            #
        else # Nope, everything was fine :D
            pivot = A[k,k]; #The pivot is the k,k since we want an identity matrix
        end
        #
        A[k,:] = A[k,:]./pivot #We scale the whole current row (Pivot row)
        for j in (k+1):numRow # Last, we go from the current+1 row, up until the bottom
            A[j,:] += -A[j,k].*A[k,:] # We multiply the pivot row by the negative of the current row, and add it to the current row
        end
    end
    #
    return A
end

function lgsolve(A::Array{Float64,2})
    # First, we make sure to convert all to a Float64 array
    #A = convert(Array{Float64,2},A)
    numRow, numCol = size(A); # Then retrieve some important variable names
    #x=zeros(numRow,1);
    x = Array{Float64}(undef,numRow)
    #
    for k in 1:numCol-1
        # Is the pivot zero?
        if A[k,k] == 0.0 # Yes
            nonzero = findall(x->x!==0.0, A[:,k]) # Find all non-zero elements in the current column
            selected = nonzero[1] # We then select the first non-zero element in the column
            A[k,:], A[selected,:] = A[selected,:],A[k,:] # We swap the rows
            pivot = A[k,k]; # And make a new pivot
            #
        else # Nope, everything was fine :D
            pivot = A[k,k];
        end
        #
        A[k,:] = A[k,:]./pivot #We scale the whole current row
        for j in (k+1):numRow # Last, we go from the current+1 row, up until the bottom
            A[j,:] += -A[j,k].*A[k,:] # We multiply the pivot row by the negative of the current row, and add it to the current row
        end
    end

# Backwards solution
    x[end] = A[end,end];
    for k in reverse(1:length(x)-1)
        S=0; #initialize
        for m = 1:length(x)
            S += A[k,m]*x[m];
        end
        x[k] = A[k,end] - S; # isolate
    end
    return x
end

function redGauss2(A::Array{Float64,2})
    # First, we make sure to convert all to a Float64 array
    # A = convert(Array{Float64,2},A)
    numRow, numCol = size(A); # Then retrieve some important variable names
    #
    for k in 1:numCol-1
        # Finding the maximum value to take as a pivot
        ab = abs.(A[:,k])
        pivotIndex = findall(x->x == maximum(ab),ab) #Looking for the index of the pivot

        # Taking the first maximum, just in case it repeats
        if length(pivotIndex) > 1
            pivotInd = pivotIndex[1];
        else #No type warn
            pivotInd = pivotIndex;
        end

        pivot = A[pivotInd,k];

        # Rearranging the rows
        A[k,:], A[pivotInd,:] = A[pivotInd,:],A[k,:]

        A[k,:] = A[k,:]./pivot #We scale the whole current row
        for j in (k+1):numRow # Last, we go from the current+1 row, up until the bottom
            A[j,:] += -A[j,k].*A[k,:] # We multiply the pivot row by the negative of the current row, and add it to the current row
        end
    end
    return A
end

function lgsolve2(A::Array{Float64,2})
    # First, we make sure to convert all to a Float64 array
    # A = convert(Array{Float64,2},A);
    numRow, numCol = size(A); # Then retrieve some important variable names
    x=zeros(numRow,1);
    #
    for k in 1:numCol-1
        # Finding the maximum value to take as a pivot
        ab = abs.(A[:,k])
        pivotIndex = findall(x->x == maximum(ab),ab) #Looking for the index of the pivot

        # Taking the first maximum, just in case it repeats
        if length(pivotIndex) > 1
            pivotInd = pivotIndex[1];
        else #No type warn
            pivotInd = pivotIndex;
        end

        pivot = A[pivotInd,k];

        # Rearranging the rows
        A[k,:], A[pivotInd,:] = A[pivotInd,:],A[k,:]

        A[k,:] = A[k,:]./pivot #We scale the whole current row
        for j in (k+1):numRow # Last, we go from the current+1 row, up until the bottom
            A[j,:] += -A[j,k].*A[k,:] # We multiply the pivot row by the negative of the current row, and add it to the current row
        end
    end

    # Backwards solution
    x[end] = A[end,end];
    for k in reverse(1:length(x)-1)
        S=0; #initialize
        for m = 1:length(x)
            S += A[k,m]*x[m];
        end
        x[k] = A[k,end] - S;
    end
    return x
end


"""
    Fronts.ϕ(r, t)

Evaluate the Boltzmann variable ϕ at position `r` and time `t`.

Type `\\phi<tab>` to obtain the `ϕ` symbol.

The Boltzmann variable is defined as `ϕ=r/√t` and makes the Boltzmann transformation possible.

To prevent possible name clashes, this function is not exported.

See also: [`transform`](@ref)
"""
ϕ(r, t) = r/√t

"""
    ∂ϕ_∂r(r, t)

Partial derivative of the Boltzmann variable.

Type `\\partial<tab>` to obtain the `∂` symbol; `\\phi<tab>` to obtain the `ϕ` symbol.

See also: [`ϕ`](@ref)
"""
∂ϕ_∂r(r, t) = 1/√t

"""
    ∂ϕ_∂t(r, t)

Partial derivative of the Boltzmann variable.

Type `\\partial<tab>` to obtain the `∂` symbol; `\\phi<tab>` to obtain the `ϕ` symbol.

See also: [`ϕ`](@ref)
"""
∂ϕ_∂t(r, t) = -ϕ(r,t)/2t

"""
    Fronts.r(ϕ, t)

Convert back from the Boltzmann variable to `r`.

To prevent possible name clashes, this function is not exported.

See also: [`ϕ`](@ref)
"""
r(ϕ, t) = ϕ*√t

"""
    Fronts.t(ϕ, r)

Convert back from the Boltzmann variable to `t`.

To prevent possible name clashes, this function is not exported.

See also: [`ϕ`](@ref)
"""
t(ϕ, r) = (r/ϕ)^2

"""
    transform(r, t)

Same as `ϕ(r,t)`.

See also: [`ϕ`](@ref)
"""
transform(r, t) = ϕ(r,t)

"""
    TransformedFunction

Abstract type for functions of the Boltzmann variable ϕ.

Every subtype of `TransformedFunction` gets access to the following methods:

    (::TransformedFunction)(r, t)
    d_dϕ(::TransformedFunction, r, t)
    ∂_∂r(::TransformedFunction, r, t)
    ∂_∂t(::TransformedFunction, r, t)

# Implementation

In order to access the previous methods, a type `T <: TransformedFunction` must define these methods:

    (::T)(ϕ)
    d_dϕ(::T, ϕ)
"""
abstract type TransformedFunction end

(f::TransformedFunction)(r, t) = f(ϕ(r,t))

d_dϕ(f::TransformedFunction, r, t) = d_dϕ(f, ϕ(r,t))

∂_∂r(f::TransformedFunction, r, t) = d_dϕ(f, r, t)*∂ϕ_∂r(r, t)

∂_∂t(f::TransformedFunction, r, t) = d_dϕ(f, r, t)*∂ϕ_∂t(r, t)


using Test
import DynamicPolynomials: @polyvar
# using Revise
using PolynomialBasis

function allequal(v1,v2)
    return all(v1 .≈ v2)
end

points = [-1.0,1.0]
matrix = PolynomialBasis.vandermonde_matrix(points)
testmatrix = [1.0  -1.0
              1.0   1.0]
@test allequal(matrix,testmatrix)

points = [-1.0,0.0,1.0]
matrix = PolynomialBasis.vandermonde_matrix(points)
testmatrix = [1.0  -1.0   1.0
              1.0   0.0   0.0
              1.0   1.0   1.0]
@test allequal(matrix,testmatrix)

points = [0.0]
matrix = PolynomialBasis.vandermonde_matrix(points)
testmatrix = [1.0]
@test allequal(matrix,testmatrix)

coeffs = PolynomialBasis.lagrange_polynomial_coefficients([0.0])
testcoeffs = [1.0]
@test allequal(coeffs,testcoeffs)

coeffs = PolynomialBasis.lagrange_polynomial_coefficients([-1.0,1.0])
testcoeffs = [0.5  0.5
             -0.5  0.5]
@test allequal(coeffs,testcoeffs)

coeffs = PolynomialBasis.lagrange_polynomial_coefficients([-1.0,0.0,1.0])
testcoeffs = [+0.0   1.0   0.0
              -0.5   0.0   0.5
              +0.5  -1.0   0.5]
@test allequal(coeffs,testcoeffs)

@polyvar x
coeffs = [1.0]
poly = PolynomialBasis.polynomial_from_coefficients(x,coeffs)
powers = sort!(vcat(poly.x.Z...))
testpowers = [0]
@test allequal(powers,testpowers)
@test allequal(coeffs,sort(poly.a))

coeffs = [1.0,2.0]
poly = PolynomialBasis.polynomial_from_coefficients(x,coeffs)
powers = sort!(vcat(poly.x.Z...))
testpowers = [0,1]
@test allequal(powers,testpowers)
@test allequal(coeffs,sort(poly.a))

polys = PolynomialBasis.lagrange_polynomials(x,[0.0])
@test length(polys) == 1
@test polys[1] == 1.0

polys = PolynomialBasis.lagrange_polynomials(x,[-1.0,1.0])
@test length(polys) == 2
p1 = 0.5 - 0.5x
p2 = 0.5 + 0.5x
@test allequal([p1,p2],polys)

polys = PolynomialBasis.lagrange_polynomials(x,[-1.0,0.0,1.0])
@test length(polys) == 3
p1 = -0.5x + 0.5x^2
p2 = 1.0 - x^2
p3 = 0.5x + 0.5x^2
@test allequal([p1,p2,p3],polys)


@testset "Newton" begin
    function f_1(x::Vector)
        (x[1] - 5.0)^4
    end

    function g!_1(storage::Vector, x::Vector)
        storage[1] = 4.0 * (x[1] - 5.0)^3
    end

    function h!_1(storage::Matrix, x::Vector)
        storage[1, 1] = 12.0 * (x[1] - 5.0)^2
    end
    initial_x = [0.0]

    Optim.optimize(NonDifferentiable(f_1, initial_x), [0.0], Newton())
    Optim.optimize(OnceDifferentiable(f_1, g!_1, initial_x), [0.0], Newton())

    options = Optim.Options(store_trace = false, show_trace = false,
                            extended_trace = true)
    results = Optim.optimize(f_1, g!_1, h!_1, [0.0], Newton(), options)
    @test_throws ErrorException Optim.x_trace(results)
    @test Optim.g_converged(results)
    @test norm(Optim.minimizer(results) - [5.0]) < 0.01

    eta = 0.9

    function f_2(x::Vector)
      (1.0 / 2.0) * (x[1]^2 + eta * x[2]^2)
    end

    function g!_2(storage::Vector, x::Vector)
      storage[1] = x[1]
      storage[2] = eta * x[2]
    end

    function h!_2(storage::Matrix, x::Vector)
      storage[1, 1] = 1.0
      storage[1, 2] = 0.0
      storage[2, 1] = 0.0
      storage[2, 2] = eta
    end

    results = Optim.optimize(f_2, g!_2, h!_2, [127.0, 921.0], Newton())
    @test_throws ErrorException Optim.x_trace(results)
    @test Optim.g_converged(results)
    @test norm(Optim.minimizer(results) - [0.0, 0.0]) < 0.01
    @test summary(results) == "Newton's Method"

    @testset "newton in concave region" begin
        prob=MultivariateProblems.UnconstrainedProblems.examples["Himmelblau"]
        res = optimize(MVP.objective(prob), MVP.gradient(prob), MVP.hessian(prob), [0., 0.], Newton())
        @test norm(Optim.minimizer(res) - prob.solutions) < 1e-9
    end

    @testset "Optim problems" begin
        run_optim_tests(Newton(); skip = ("Trigonometric",), show_name = debug_printing)
    end
end


using NewtonsMethod
using Test

@testset "NewtonsMethod.jl" begin
   testf(x)= (x-1)^3
    testf_prime(x)=3*(x-1)^2
    testf1(x)= exp(3*x)-1
    testf1_prime(x)=3*exp(3*x)

    #test for functions with derivative

    root1, conv, iter  = NewtonsMethod.newtonroot(testf,testf_prime; x_0 = 0.0)
    root2, conv, iter  = NewtonsMethod.newtonroot(testf1,testf1_prime; x_0 = 0.0)

    @test abs(root1 - 1.0)<1e-6
    @test abs(root2 - 0.0)<1e-6

    #do the same for functions without derivative

    root1, conv, iter  = NewtonsMethod.newtonroot(testf; x_0 = 0.0)
    root2, conv, iter  = NewtonsMethod.newtonroot(testf1; x_0 = 0.0)

    @test abs(root1 - 1.0) < 1e-6
    @test abs(root2 - 0.0) < 1e-6

    #Test if maxiter works
    root1, conv, iter  = NewtonsMethod.newtonroot(testf, testf_prime; x_0 = 0.0,  maxiter = 5)
    # if the function converged, return number of iteration, else return nothing
    @test iter == nothing ||iter <= 5
    #Test different tolerance, increasing it to 1e-10
    root1, conv, iter  = NewtonsMethod.newtonroot(testf, testf_prime; x_0=0.0,  tol = 1e-10)
    @test abs(root1-1.0)<1e-7

    #Test lack of convergence

    test_nc(x) = x^2+1

    rootnc, conv, iter  = NewtonsMethod.newtonroot(test_nc; x_0 = 1.0)
    @test rootnc==nothing

    x0 = BigFloat(0.79)
    root1, conv, iter  = NewtonsMethod.newtonroot(testf; x_0 = x0)
    root2, conv, iter  = NewtonsMethod.newtonroot(testf1; x_0 = x0)

    @test abs(root1 - 1.0)<1e-6
    @test abs(root2 - 0.0)<1e-6

end


#=
Implement integer exponentiation. That is, implement the pow(x, y) function, where x and y are integers and returns x^y.

Do this faster than the naive method of repeated multiplication.

For example, pow(2, 10) should return 1024.
=#

using Test
include("Solutions/problem61_intexponentiation.jl")

@test pow(2,10) == 1024


using DESincEig, SincFun, Base.Test

# Testing the main function SincEigen.

println("Testing Example 1")
f(x) = (35/4)./x.^2  .- 2 .+ x.^2 /16
(RESULTS, All_Abs_Error_Approx , hoptimal , n, MatrixSizes) = SincEigen( f , ones , SemiInfinite1{Float64}() , [1.5;0.03125] , [1.0;2.0] , pi/4, tol=1e-10 )
@test norm(RESULTS[1:5,3]-collect(0.0:6.0)) < sqrt(eps())

println("Testing quartic anharmonic oscillators")
# Exact values for the ground states from E. J. Weniger, Ann. Phys. 246:133--165, 1996.
V(x,β) = x.^2 .+ β.*x.^4
β = [0.2;1.0;4.0]
exact_values = [1.1182926543696632;1.392351641530291855;1.9031369454587908]

for i=1:length(β)
B = sqrt(β[i])*(1/2)^3 / 3.0
    (RESULTS, All_Abs_Error_Approx , hoptimal , n, MatrixSizes) = SincEigen( x->V(x,β[i]) , ones ,Infinite2{Float64}(), [B;B] , [3.0;3.0] , pi/6 )
    @test norm(RESULTS[1,3] - exact_values[i]) < 1e-8
end

println("Testing sextic anharmonic oscillators")

# Exact values for the ground state from Chaudhuri1991a and Tater1993, and Flessas1979.
Vsextic(x,c) = c[1]*x.^2 .+ c[2]*x.^4 .+ c[3]*x.^6
c = [1.0 -4.0 1.0 ; 4.0 -6.0 1.0 ; -7.0 0.0 1.0]
exact_values = [-2.0 ; -9.0 ; -2*sqrt(2) ]
enum = [0 , 1 , 0]
ga = 4.0
B = (1/2)^ga / ga
for i=1:size(c)[2]
    (RESULTS, All_Abs_Error_Approx , hoptimal , n, MatrixSizes) = SincEigen( x->Vsextic(x,c[i,:]) , ones ,Infinite2{Float64}(), [B;B] , [ga,ga] , pi/2ga )
    @test norm(RESULTS[enum[i]+1,3] - exact_values[i]) < 1e-8
end

#TODO add examples of SincEigenStop with converged eigenfunctions.


"""
	 exponential_search(arr::AbstractArray{T,1}, x::T) where {T <: Real}

Exponential Search in 1-D array
Time Complexity:  O(Log n)
# Exponential Search
It works in O(Log n) time
Exponential search involves two steps:
- Find range where element is present
- Do Binary Search in above found range.
### Time Complexity :
O(Log n)
Applications of Exponential Search:
Exponential Binary Search is particularly useful for unbounded searches, where size of array is infinite. Please refer Unbounded Binary Search for an example.
It works better than Binary Search for bounded arrays, and also when the element to be searched is closer to the first element.

Contributed By:- [Ash](https://github.com/ashwani-rathee)
"""
function exponential_search(arr::AbstractArray{T,1}, x::T) where {T<:Real}
    n = size(arr)[1]
    if (arr[1] == x)
        return "Element present at index 1"
    end

    i = 1
    while (i < n && arr[i] <= x)
        i = i * 2
    end
    return binary_search(arr, Int(ceil(i / 2)), min(i, n), x)
end


####
#### Unregularized NNLS problem
####

struct NNLSProblem{T, MC <: AbstractMatrix{T}, Vd <: AbstractVector{T}, W}
    C::MC
    d::Vd
    m::Int
    n::Int
    nnls_work::W
end
function NNLSProblem(C::AbstractMatrix{T}, d::AbstractVector{T}) where {T}
    m, n = size(C)
    nnls_work = NNLS.NNLSWorkspace(C, d)
    NNLSProblem(C, d, m, n, nnls_work)
end

function solve!(work::NNLSProblem, C, d)
    # Solve NNLS problem
    NNLS.load!(work.nnls_work, C, d)
    NNLS.nnls!(work.nnls_work)
    return solution(work)
end
solve!(work::NNLSProblem) = solve!(work, work.C, work.d)

solution(work::NNLSProblem) = NNLS.solution(work.nnls_work)

chi2(work::NNLSProblem) = NNLS.residualnorm(work.nnls_work)^2

"""
    lsqnonneg(C::AbstractMatrix, d::AbstractVector)

Returns the nonnegative least-squares (NNLS) solution, X, of the equation:

```math
X = \\mathrm{argmin}_{x \\ge 0} ||Cx - d||_2^2
```

# Arguments
- `C::AbstractMatrix`: Left hand side matrix acting on `x`
- `d::AbstractVector`: Right hand side vector

# Outputs
- `X::AbstractVector`: NNLS solution
"""
lsqnonneg(C, d) = lsqnonneg!(lsqnonneg_work(C, d))
lsqnonneg_work(C, d) = NNLSProblem(C, d)
lsqnonneg!(work::NNLSProblem) = solve!(work)
lsqnonneg!(work::NNLSProblem{T}, C::AbstractMatrix{T}, d::AbstractVector{T}) where {T} = solve!(work, C, d)


"""
Returns x^n mod p
"""
function exp!(x::BigInt, n::BigInt, p::BigInt = big(1000007))
    ans = 1
    x = x % p

    while n > 0
        if ((n & 1) != 0)
            ans = (ans * x) % p
        end
        x = (x * x) % p
        n = n >> 1
    end
    ans
end

exp!(x::T, n::T, p::T = 1000007) where {T <: Integer} =
exp!(big(x), big(n), big(p))

"""
Modular multiplicative inverse

a: Number whose inverse is to be found
n: modular base
"""
function inverse(a::BigInt, n::BigInt)
    g, x, _ = gcdx(a, n)

    if (g != 1)
        error("The inverse doesn't exist")
    end
    (x % n + n) % n
end

inverse(a::T, n::T) where {T <: Integer} =
inverse(big(a), big(n))

"""
Seive of Eratosthenes

Returns primes less than n
"""
function seive(n::BigInt)
    seive_list = ones(Bool, n)

    for i in 2:trunc(BigInt, sqrt(n))
        if seive_list[i] == true
           for j in (i*i):i:n
                seive_list[j] = false
           end
        end
   end
    filter(x -> seive_list[x] == true, 2:n)
end

seive(n::T) where {T <: Integer} =
seive(big(n))

"""
Euler totient function of n

phi!(n) = number of positive Numbers co-prime with n.
n: Int
"""
function phi!(n::Number)
    result = n
    p = 2

    while p * p <= n
        if (n % p == 0)
            while n % p == 0
                n /= p
            end
            result -= result / p
        end
        p += 1
    end

    if (n > 1)
        result -= result / n
    end
    Int(result)
end

"""
Returns a random Number of size bit_size bits
"""
function get_random_int(bit_size::T) where {T <: Integer}
    bit_vector = bitrand(bit_size)
    sum([(2 ^ BigInt((i - 1))) * bit for (i, bit) in enumerate(reverse(bit_vector))])
end

"""
Returns the Jacobi symbol
If b is prime, it returns the Legendre Symbol
"""
function jacobi!(a::BigInt, b::BigInt)
    if (b <= 0)
        error("b must be >= 1")
    end
    if (b & 1 == 0)
        error("b must be odd")
    end

    ans = 1
    if (a < 0)
        a = -a
        if (b % 4 == 3)
            ans = -ans
        end
    end

    while a != 0
        while a % 2 == 0
            a /= 2
            if ((b % 8) in [3, 5])
                ans = -ans
            end
        end
        a, b = b, a
        if a % 4 == 3 && b % 4 == 3
            ans = -ans
        end
        a = a % b
    end

    if (b == 1)
        return (ans)
    end
    0
end

jacobi!(a::T, b::T) where {T <: Integer} =
jacobi!(big(a), big(b))

"""
Generates a large a prime number by incremental search
"""
function generate_large_prime(bit_size::T, primality_test::Function = miller_rabin) where {T <: Integer}
    p = get_random_int(bit_size)

    if (p & 1 != 1)
        p += 1
    end
    while primality_test(p) == false
        p += 2
    end
    p
end

"""
Easier to convert Int to bytes
"""
to_bytes(x) = reinterpret(UInt8, x)


#Factorials and elementary coefficients

export
    derangement,
    partialderangement,
    factorial,
    subfactorial,
    doublefactorial,
    hyperfactorial,
    multifactorial,
    gamma,
    primorial,
    multinomial

# TODO: This should really live in Base, otherwise it's type piracy
"""
    factorial(n, k)

Compute ``n!/k!``.
"""
function Base.factorial(n::T, k::T) where T<:Integer
    if k < 0 || n < 0 || k > n
        throw(DomainError((n, k), "n and k must be nonnegative with k ≤ n"))
    end
    f = one(T)
    while n > k
        f = Base.checked_mul(f, n)
        n -= 1
    end
    return f
end
Base.factorial(n::Integer, k::Integer) = factorial(promote(n, k)...)

"""
    derangement(n)

Compute the number of permutations of `n` with no fixed points, also known as the
subfactorial. An alias `subfactorial` for this function is provided for convenience.
"""
function derangement(sn::Integer)
    n = BigInt(sn)
    return numerator(factorial(n) * sum([(-1)^k // factorial(k) for k = 0:n]))
end
const subfactorial = derangement

function doublefactorial(n::Integer)
    if n < 0
        throw(DomainError(n, "n must be nonnegative"))
    end
    z = Ref{BigInt}(0)
    ccall((:__gmpz_2fac_ui, :libgmp), Cvoid, (Ref{BigInt}, UInt), z, UInt(n))
    return z[]
end

"""
    partialderangement(n, k)

Compute the number of permutations of `n` with exactly k fixed points.
"""
function partialderangement(n::Integer, k::Integer)
    if n < 0
        throw(DomainError(n))
    end
    if k < 0 || k > n
        throw(DomainError(k))
    end
    a = BigInt(n)
    b = BigInt(k)
    return subfactorial(a - b) * binomial(a, b)
end

# Hyperfactorial
hyperfactorial(n::Integer) = prod(i->i^i, BigInt(2):n)

function multifactorial(n::Integer, m::Integer)
    if n < 0
        throw(DomainError(n, "n must be nonnegative"))
    end
    z = Ref{BigInt}(0)
    ccall((:__gmpz_mfac_uiui, :libgmp), Cvoid, (Ref{BigInt}, UInt, UInt), z, UInt(n), UInt(m))
    return z[]
end

function primorial(n::Integer)
    if n < 0
        throw(DomainError(n, "n must be nonnegative"))
    end
    z = Ref{BigInt}(0)
    ccall((:__gmpz_primorial_ui, :libgmp), Cvoid, (Ref{BigInt}, UInt), z, UInt(n))
    return z[]
end

"""
    multinomial(k...)

Multinomial coefficient where `n = sum(k)`.
"""
function multinomial(k...)
    s = 0
    result = 1
    @inbounds for i in k
        s += i
        result *= binomial(s, i)
    end
    result
end


using LinearAlgebra

function count_darts(N::Int)
  num_inside = 0
  for i=1:N
    p = rand(2)
    num_inside += norm(p) <= 1
  end
  return num_inside
end

count_darts(10000)

## Now make this work in arbitrary dimensions
using LinearAlgebra
function count_darts(N::Int, k::Int)
  num_inside = 0
  for i=1:N
    p = rand(k)
    num_inside += norm(p) <= 1
  end
  return num_inside
end
count_darts(10000, 2) # check our estimate
##
for k=3:10
  n = count_darts(10000, k)
  println(k, " -> ", n)
end
##


#=
  Bertrand's paradox

  From https://towardsdatascience.com/five-paradoxes-with-probabilities-that-will-puzzle-you-2f71201d6ee8
  """
  3. Bertrand's Box Paradox

  If you are familiar with the Montey Hall problem, this paradox is quite
  similar. In front of us there are three boxes:

    [image of three boxes]

  One box contains two silver coins, one box contains two gold coins and
  one box contains a gold- and a silver coin. We do not know which coins
  are in which box. Now, we pick a random box and blindly draw a coin from
  our box. It’s a gold coin!

  Now, the question is:

    What’s the probability that the second coin in our box is also a gold coin?

  My naive (and wrong) answer when encountering the problem for the first
  time was ½. I thought that because we drew a gold coin, our box is either
  the one with the two gold coins or the one with the mixed coins. In the
  first case, we would draw another gold coin and in the second case, we
  wouldn’t. Therefore, I presumed the probability should be ½.

    The real probability is ⅔.

  The reason for that is that the first gold coin we drew could either be
  the only gold coin in the mixed box, the first gold coin in the solely
  golden box, or the second gold coin in the solely golden box. And in
  two of these three possibilities, we will draw another gold coin.

  """

  Here we define the boxes as:
  - box1: 2 gold coins
  - box2: 1 gold and 1 silver coin
  - box3: 2 silver coins

  Distributions of variable coin1
  gold       =>    1000  (1.000000)
  Distributions of variable coin2
  gold       =>     668  (0.668000)
  silver     =>     332  (0.332000)
  Distributions of variable box
  b1         =>     668  (0.668000)
  b2         =>     332  (0.332000)

  I.e. probability that the second coin is also gold is 0.668000 (~ 2/3).

  Cf ~/blog/bertrands_paradox.blog
      ~/psi/bertrands_paradox.psi

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

# We observe that the first coin is a gold coin
@model function bertrands_paradox(coin1=1)
    # There are 3 boxes.
    b1 = 1
    b2 = 2
    b3 = 3

    # We select one of the boxes
    box ~ DiscreteUniform(1,3)

    # the coins
    gold = 1
    silver = 2

    # We draw one coin:
    coin1 ~ (box == b1) ? Categorical(simplex([2.0, 0.0])) :
            (box == b2) ? Categorical(simplex([1, 1])) :
            Categorical(simplex([0.0, 2.0]))

    # It's a gold coin.
    # true ~ Dirac(coin1 == gold)

    # What is the probability that the second coin
    # in the selected box is also a gold coin?
    coin2 ~ (box == b1) ? Categorical(simplex([1.0, 0.0])) :
            (box == b2) ? Categorical(simplex([0.0, 1.0])) :
            Categorical(simplex([0.0, 1.0]))

end

model = bertrands_paradox()
num_chns = 4

# chns = sample(model, Prior(), 10_000, num_chns)
# chns = sample(model, MH(), 10_000)
chns = sample(model, PG(5), 1_000)
# chns = sample(model, SMC(), 10_000)
# chns = sample(model, IS(), 10_000)

display(chns)

show_var_dist_pct(chns, :coin1,["gold","silver"])
show_var_dist_pct(chns, :coin2,["gold","silver"])
show_var_dist_pct(chns, :box,["b1","b2","b3"])


## Polynomial root finder for polynomials with multiple roots
##
## Based on "Computing multiple roots of inexact polynomials"
## http://www.neiu.edu/~zzeng/mathcomp/zroot.pdf
## Author: Zhonggang Zeng
## Journal: Math. Comp. 74 (2005), 869-903
##
## Zeng has a MATLAB package `multroot`, from which this name is derived.
## Basic idea is
## 1) for polynomial p we do gcd decomposition p = u * v; p' = u * w. Then roots(v) are the roots without multiplicities.
## 2) can repeat with u to get multiplicities.
##
## This is from Gauss, as explained in paper. Zeng shows how to get u,v,w when the polynomials
## are inexact due to floating point approximations or even model error. This is done in his
## algorithm II.
## 3) Zeng's algorithm I (pejroot) uses the pejorative manifold of Kahan and Gauss-Newton to
## improve the root estimates from algorithm II (roots(v)). The pejorative manifold is defined by
## the multiplicities l and is operationalized in evalG and evalJ from Zeng's paper.

using Polynomials
import Polynomials: degree

## map monic(p) to a point in C^n
## p = 1x^n + a1x^n-1 + ... + an_1 x + an -> (a1,a2,...,an)
function p2a(p::Poly)
    p = monic(p)
    rcoeffs(p)[2:end]
end

## get value of gl(z). From p16
function evalG(zs::Vector, ls::Vector)
    length(zs) == length(ls) || throw("Length mismatch")

    s = prod([poly([z])^l for (z,l) in zip(zs, ls)])  # \prod (x-z_i)^l_i
    p2a(s)
#    rcoeffs(s)[2:end]
end

## get jacobian J_l(z), p16
function evalJ(zs::Vector, ls::Vector)
    length(zs) == length(ls) || throw("Length mismatch")
    m = length(zs)

    u = prod([poly([z])^(l-1) for (z,l) in zip(zs, ls)]) ## Pi (1-z)^(l-1)

    J = zeros(eltype(zs), sum(ls), m)
    for j in 1:m
        s = -ls[j] * u
        for i in 1:m
            if i != j
                s = s * poly([zs[i]])
            end
        end
        J[:,j] = rcoeffs(s)
    end
    J
end

## Gauss-Newton iteration to solve weighted least squares problem
## G_l(z) = a, where a is related to monic version of polynomial p
## l is known multiplicity structure of polynomial p = (x-z1)^l1 * (x-z2)^l2 * ... * (x-zn)^ln
## Algorithm I, p17
function pejroot(p::Poly, z0::Vector, l::Vector{Int};
                 wts::(@compat Union{Vector, Void})=nothing, # weight vector
                 tol = 1e-8,
                 maxsteps = 100
                      )

    a = p2a(p) #rcoeffs(monic(p))[2:end] # an_1, an_2, ..., a2, a1, a0

    if wts == nothing
        wts = map(u -> min(1, 1/abs(u)), a)
    end
    W = diagm(wts)

    ## Solve WJ Δz = W(Gl(z) - a) in algorithm I
    G(z) = (evalG(z, l) - a)
    update(z, l) = z -  weighted_least_square(evalJ(z,l), G(z), wts)

    zk = copy(z0); zk1 = update(zk, l)
    deltaold = norm(zk1 - zk,2); zk = zk1

    cvg = false
    for ctr in 1:maxsteps
        zk1 = update(zk, l)
        delta = norm(zk1 - zk, 2)

        if delta > deltaold
            println("Growing delta. Best guess is being returned.")
            break
        end

        ## add extra abs(delta) < 100*eps() condition
        if delta^2 / (deltaold - delta) < tol || abs(delta) < 100*eps()
            cvg = true
            break
        end

        deltaold = delta
        zk=zk1
    end

    if !cvg println("""
Returning the initial estimates, as the
algorithm failed to improve estimates for the roots on the given
pejorative manifold.
""")
        return(z0)
    end
    return(zk1)
end

## Main interface to finding roots of polynomials with multiplicities
##
## The `multroot` function returns the roots and their multiplicities
## for `Poly` objects. It performs better than `roots` if the
## polynomial has multiplicities.
##
## julia> x = poly([0.0]);
## julia> p = (x-1)^4 * (x-2)^3 * (x-3)^3 * (x-4)l
## julia> multroot(p)
## ([1.0,2.0,3.0,4.0],[4,3,3,1])
## ## For "prettier" printing, results can be coerced to a dict
## julia> [k => v for (k,v) in zip(multroot(p)...)]
## Dict{Any,Int64} with 4 entries:
##   1.0000000000000007 => 4
##   3.000000000000018  => 3
##   1.9999999999999913 => 3
##   3.999999999999969  => 1
## ## Large order polynomials prove difficult. We can't match the claims in Zeng's paper
## ## as we don't get the pejorative manifold structure right.
## julia> p = poly([1.0:10.0]);
## julia> multroot(p) ## should be 1,2,3,4,...,10 all with multplicity 1, but
## ([1.0068,2.14161,3.63283,5.42561,7.25056,8.81228,9.98925],[1,2,1,2,2,1,1])
##
## nearby roots can be an issue
## julia> delta = 0.0001  ## delta = 0.001 works as desired.
## julia> p = (x-1 - delta)*(x-1)*(x-1 + delta)
## julia> multroot(p)
## ([0.999885,1.00006],[1,2])
function multroot(p::Poly;
                  θ::Real=1.0,   # singular threshold, 1.0 is replaced by normf(A)*eps()
                  ρ::Real=1e-10, # initial residual tolerance
                  ϕ::Real=1e2,   # residual tolerance growth factor
                  δ::Real=1e-8   # passed to solve y sigma

                  )

    degree(p) == 0 && error("Degree of `p` must be atleast 1")

    if degree(p) == 1
        return(roots(p), [1])
    end

    ## if degree(p) == 2
    ##     a,b,c = coeffs(p)
    ##     discr = b^2 - 4a*c
    ##     if discr < 0
    ##         discr = Complex(discr, 0)
    ##     end
    ##     return  ( -2c / (-b - sqrt(discr)), -2c/(-b + sqrt(discr)))
    ## end

    p = Poly(float(coeffs(p)))  # floats, not Int

    u_j, v_j, w_j, residual= agcd(p, p',  ρ = ρ)
    ρ = max(ρ, ϕ * residual)

    ## bookkeeping
    zs = roots(v_j)
    ls = ones(Int, length(zs))

    p0 = u_j

    while degree(p0) > 0
        if degree(p0) == 1
            z = roots(p0)[1]
            _, ind = findmin(abs(zs .- z))
            ls[ind] = ls[ind] + 1
            break
        end

        u_j, v_j, w_j, residual= agcd(p0, p0', ρ=ρ)

        ## need to worry about residual between
        ## u0 * v0 - monic(p0) and u0 * w0 - monic(Polynomials.polyder(p0))
        ## resiudal tolerance grows with m, here it depends on
        ## initial value and previous residual times a growth tolerance, ϕ
        ρ = max(ρ, ϕ * residual)

        ## update multiplicities
        for z in roots(v_j)
            _, ind = findmin(abs(zs .- z))
            ls[ind] = ls[ind] + 1
        end

        ## rename
        p0 = u_j
    end

    if maximum(ls) == 1
        return(zs, ls)
    else
        zs = pejroot(p, zs, ls)
        return(zs, ls)
    end
end

## Different interfaces

## can pass in vector too
multroot{T <: Real}(p::Vector{T}; kwargs...) = multroot(Poly(p); kwargs...)

## Can pass in function
function multroot(f::Function; kwargs...)
    p = Poly([0.0])
    try
        p = convert(Poly{Float64}, f)
    catch err
        error("The function does not compute a univariate polynomial")
    end
    multroot(p; kwargs...)

end

## add funciton interface to Polynomials.roots
Polynomials.roots(f::Function) = roots(convert(Poly{Float64}, f))


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

booth(x) = (x[1] + 2*x[2] - 7)^2 + (2*x[1] + x[2] -5)^2

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

function flower(x; a=1, b=1, c=4)
    return a*norm(x) + b*sin(c*atan(x[2], x[1]))
end

function michalewicz(x; m=10)
    return -sum(sin(xᵢ)*sin(i*xᵢ^2/π)^(2m) for (i, xᵢ) in enumerate(x))
end

rosenbrock(x; a=1, b=100) = (a - x[1])^2 + b*(x[2] - x[1]^2)^2

wheeler(x, a=1.5) = -exp(-(x[1]*x[2] - a)^2 - (x[2] - a)^2)

function circle(x)
    θ = x[1]
    r = 0.5 + 0.5 * (2*x[2]/(1 + x[2]^2))
    return [1-r*cos(θ); 1-r*sin(θ)]
end


using LinearAlgebra

#Givens from Problem Statement
#Shear Modulus(psi),diameter(in),length(in),torque(lb*in)
G13 = 9.8*10^6
G2 = 11.2*10^6
d13 = 1.5
d2 = 1
l13 = 2*12
l2 = 1.5*12
τ = 2400

# Polar Second Area Moment of a Cylinder (in^4)
J_cyl(d) = 0.5*pi*(d/2)^4
J13 = J_cyl(d13)
J2 = J_cyl(d2)

# Torsional Stiffness Function
k(G,J,l) = (G*J)/l

# k_local(Elastic Modulus,thickness,height,length,number of nodes,
# row/column one,row/column two, ==1 left fixed/ ==2 right fixed)
function k_local(G,J,l,i,n1,n2,fixed=0)
    k_global = zeros(i,i)
    kin = k(G,J,l)
    k_global[n1,n1] = kin
    k_global[n1,n2] = -kin
    k_global[n2,n1] = -kin
    k_global[n2,n2] = kin
    if fixed == 1
      adj = zeros(1,i)
      adj[n1] = 1
      k_global[n1,:] = adj
      k_global[:,n1] = adj
    elseif fixed == 2
      adj = zeros(1,i)
      adj[n2] = 1
      k_global[n2,:] = adj
      k_global[:,n2] = adj
    end
    return k_global
end

# Global for angular displacement calculation
k_1 = k_local(G13,J13,l13,4,1,2,1)
k_2 = k_local(G2,J2,l2,4,2,3)
k_3 = k_local(G13,J13,l13,4,3,4,2)
kG = k_1+k_2+k_3

#Global for Reaction calculation
k_11 = k_local(G13,J13,l13,4,1,2)
k_22 = k_local(G2,J2,l2,4,2,3)
k_33 = k_local(G13,J13,l13,4,3,4)
kGG = k_11+k_22+k_33

# Torque Vector
t = Float64[0 0 τ 0]
T = transpose(t)

# Angular Displacement Vector
θ = kG\T
println("Nodal Angular Displacement Vector (radians)")
display(θ)

# Moment Reaction Vector
τ_app = kGG*θ
println("Moment Reaction Vector (lb*in):")
display(τ_app)


# Fermat's primality test: (Test for a number to be prime or not, based on Fermat's little theorem)
function fermatPrimalityTest(n, iterations = 5)
    if n < 4
        return n == 2 || n == 3
    end

    for i = 1:iterations
        a = rand(2:(n - 2), 1)[1]
        if ((BigInt(a) ^ (n - 1)) % n) != 1
            return false
        end
    end
    return true
end

# Fermat's factorization: (Representation of an odd integer as the difference of two squares)
function fermatFactorization(n::Int64)
    a = ceil(sqrt(n))
    b2 = a * a - n
    b = round(sqrt(b2))
    while (b * b != b2)
        a = a + 1
        b2 = a * a - n
        b = round(sqrt(b2))
    end
    return Int64(a - b)
end


# In England the currency is made up of pound, £, and pence, p, and there are
# eight coins in general circulation:
#
# 1p, 2p, 5p, 10p, 20p, 50p, £1 (100p) and £2 (200p).
# It is possible to make £2 in the following way:
#
# 1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p
# How many different ways can £2 be made using any number of coins?

using ProjectEulerSolutions

# Recursive solution count coin combinations.  In general, the idea is to
# consider each coin type and see how many ways there are to make a sum with
# that coin and lower denominations.  Different solutions can solve the
# recursion in slightly different ways.  Memoization is used to store lower
# level computatons.

# Uses a double recursion.
function p031solution_double(units::Array{Integer}=ones(Integer, 1), value::Integer=5)::Integer
    cache = zeros(Integer, value, length(units))
    return countcoins2(cache, units, length(units), value)
end

# Uses a single recursion, makes less total recursive calls.
function p031solution_single(units::Array{Integer}=ones(Integer, 1), value::Integer=5)::Integer
    cache = zeros(Integer, value, length(units))
    return countcoins1(cache, units, length(units), value)
end

# Uses no recursion, solution from https://projecteuler.net/thread=31.
function p031solution_dp(units::Array{Integer}=ones(Integer, 1), value::Integer=5)::Integer
    ways = zeros(Integer, value)
    ways[1] = 1
    for coin in units
        for j in (coin+1):value
            ways[j] += ways[j - coin]
        end
    end
    return ways[value]
end

# Most straightforward recursive solution.
function countcoins2(cache::Array{Integer,2}, units::Array{Integer}, coin::Integer, amount::Integer)::Integer
    if amount == 0 # Coin divides equally, add 1
        return 1
    end
    if coin < 1 || amount < 0 # Coins don't divide equally
        return 0
    end
    if cache[amount, coin] > 0
        return cache[amount, coin]
    end

    # Sum of solutions without coin and with coin
    val = countcoins2(cache, units, coin - 1, amount) +
          countcoins2(cache, units, coin, amount - units[coin])
    cache[amount, coin] = val
    return val
end

# Simple refactoring, less total recursive calls.
function countcoins1(cache::Array{Integer,2}, units::Array{Integer}, coin::Integer, amount::Integer)::Integer
    if amount == 0
        return 1
    end
    if cache[amount, coin] > 0
        return cache[amount, coin]
    end
    res = 0
    for i in coin:-1:2
        if amount - units[i] >= 0
            res += countcoins1(cache, units, i, amount - units[i])
        end
    end

    # Account for smallest coin evenly dividing into amount
    if amount % units[1] == 0
        res += 1
    end

    cache[amount, coin] = res
    return res
end

p031 = Problems.Problem(Dict("Double recusion" => p031solution_double,
                             "Single recusion" => p031solution_single,
                             "Dynamic programming" => p031solution_dp))

Problems.benchmark(p031, convert(Array{Integer}, [1, 2, 5, 10, 20, 50, 100, 200]), 200)


### related to signal analysis/synthesis, geometric transforms.

function rad2freq(r,fs)
    fn = fs/2
    #r/π = x/fn, x is in Hz.
    return fn*r/π
end

function freq2rad(x,fs)
    fn = fs/2

    return x*π/fn
end

# evaluates H(e^{j*ω}).
function computeDTFTviaformula(h,ω)
    N = length(h)
    return sum( h[n+1]*exp(-im*ω*n) for n = 0:N-1 )
end
# # test code
# ω = 99
# h = randn(5)
# Hn = h[1] + h[2]*exp(-im*ω) + h[3]*exp(-im*2*ω) + h[4]*exp(-im*3*ω) + h[5]*exp(-im*4*ω)
# Ht = computeDTFTviaformula(h,ω)
#
# println("discrepancy ", abs(Hn-Ht))

# length = 2N+1
function getdiracdeltaseq(N)
    out = zeros(Float64,2*N+1)
    out[N+1] = 1.0

    return out
end

# get highpass filter from a lowpass one.
function spectralinversion(h::Vector{T}) where T
    @assert isodd(length(h))

    out = collect( -h[i] for i = 1:length(h))
    out[div(length(h),2)+1] += one(T)

    return out
end

# assume x starts at time 0.
function upsample(x::Vector{T}, M::Int)::Vector{T} where T

    out = Vector{T}(undef, length(x)*M)
    #out = Vector{T}(undef, (length(x)-1)*M +1)
    fill!(out,zero(T))

    for n = 0:length(x)-1
        out[n*M+1] = x[n+1]
    end

    return out
end

function downsample(x::Vector{T}, M::Int)::Tuple{Vector{T},Vector{T}} where T

    x_padded = copy(x)
    Q = rem(length(x),M)
    if Q != 0
        #push!(out,zero(T))
        push!(x_padded,zeros(T,M-Q)...)
    end

    N = div(length(x_padded),M)

    out = Vector{T}(undef, N)

    for n = 0:N-1
        out[n+1] = x_padded[n*M+1]
    end

    return out, x_padded
end

function linearconv(  x::Vector{T},
                h::Vector{T}) where T

    out = Vector{T}(undef, length(x)+length(h)-1)
    for i = 1:length(x)+length(h)-1

        out[i] = sum( h[k]*getfinitesignalatindex(x,i-k+1) for k = 1:length(h) )
        # i-k+1 instead of i-k because Julia is 1-indexing.
    end

    return out
end

# remove the padding due to linear convolution. There will still be a bit of
#   shift probably due to phase-shift of system. This reasoning is untested.
# g is the filter.
# y is the signal.
function forcesamesizeafterlinearconv(g::Vector{T},y::Vector{T})::Vector{T} where T

    offset1 = div(length(g)-1,2)
    offset2 = length(g)-1-offset1

    return y[offset1:end-offset2]
end

# in progress. optimized for speed. eventually package pynogram without allocating N_chs worth of output.
# ignore entries of output that require out-of-bound h.
function fastlinearconv(a::Vector{T}, b::Vector{T})::Vector{T} where T
    m = length(a)
    n = length(b)

    out = zeros(T, m+n-1)
    @inbounds for j = 1:m
        for k = 1:n
            out[j+k-1] += a[j]*b[k]
        end
    end
    return out
end

function circconv(  x::Vector{T},
                h::Vector{T}) where T

    out = Vector{T}(undef, length(x)+length(h))
    for i = 1:length(x)+length(h)

        out[i] = sum( h[k]*x[mod(i-k,length(x))+1] for k = 1:length(h) )
    end

    return out
end


# Copyright (c) 2020 Idiap Research Institute, http://www.idiap.ch/
#  Niccolò Antonello <nantonel@idiap.ch>

# define Markov model's parameters (λ)
Ns = 3          # number of states
a = [0.1; 0.3; 0.6]  # initial state probability
A = [0.1 0.9 0.0; 0.0 0.2 0.8; 0.5 0.0 0.5] # transition matrix
B = [0.8 0.1 0.1; 0.5 0.2 0.3; 0.7 0.1 0.2] # observation prob matrix
# Must be row stotastic
@test HMMGradients.is_transition_matrix(A)

Nt = 5                     # time window length
x = rand(1:Ns,5)           # observations

using Combinatorics
s_all = collect(multiset_permutations(1:Ns,Nt.*ones(Int,Ns),Nt))
@test length(unique(s_all)) == Ns^Nt
# this has all possible state sequences e.g. [1;1;1;1;1], [1;1;1;1;2] ...
# these are Ns^Nt permutations

# Probability of sequence of states
Ps = ones(Ns^Nt)
for z = 1:length(s_all)
  for t = 1:Nt
    if t == 1
      Ps[z] *= a[ s_all[z][t] ] # initial prob
    else
      Ps[z] *= A[ s_all[z][t-1], s_all[z][t] ] # trans prob
    end
  end
end
@test sum(Ps) ≈ 1 # check it's a probability

# Likelihood of x for a given sequence
Px_s = ones(length(s_all))
for z = 1:length(s_all)
  for t = 1:Nt
    Px_s[z] *= B[ x[t], s_all[z][t] ]
  end
end

P = sum(Ps .* Px_s)
y = [B[x[t],i] for t = 1:Nt, i = 1:Ns]

# testing Baum forward backward
alpha_b = Baum_forward(a,A,y)
alpha_m = Baum_forward_matrix(a,A,y)
beta_b  = Baum_backward(A,y)
beta_m = Baum_backward_matrix(A,y)
P2 = sum(alpha_m[end,:])
gamma_b = alpha_b .* beta_b ./ P2
@test norm(alpha_m-alpha_b) < 1e-7
@test norm(beta_m-beta_b) < 1e-7
@test P ≈ P2
@test all(sum(alpha_b.*beta_b,dims=2) .≈ P)

## testing forward backward with normalization
alpha, c = forward(Nt,a,A,y)
logalpha, logML = logforward(Nt,log.(a),log.(A),log.(y))
logbeta = logbackward(Nt,log.(A),log.(y))
beta = backward(Nt,A,c,y)
gamma = alpha.*beta
gamma2 = posterior(Nt,a,A,y)
loggamma = logalpha .+ logbeta .- logML*Nt
loggamma2 = logposterior(Nt,log.(a),log.(A),log.(y))
@test P ≈ sum(exp.(logalpha[end,:]))
@test nlogML(Nt,a,A,y) ≈ -1/Nt * log(P)
@test nlogML(Nt,a,A,y) ≈ nlogMLlog(Nt,log.(a),log.(A),log.(y))
@test all(sum(exp.(logalpha .+ logbeta),dims=2) .≈ P)
@test sum(log.(c)) ≈ log(sum(alpha_b[end,:]) )
@test all( sum(gamma,dims=2) .≈ 1.0 )
@test norm( gamma - gamma_b ) < 1e-7
@test norm( gamma2 - gamma_b ) < 1e-7
@test norm( gamma - exp.(loggamma) ) < 1e-7
@test norm( gamma - exp.(loggamma2) ) < 1e-7
# equivalence between baum alpha/beta and normalized ones
for t = 1:Nt
  @test norm( prod(1 ./ c[1:t]).*alpha_b[t,:] - alpha[t,:] ) < 1e-8
  @test norm( exp.(logalpha[t,:]) - (prod(c[1:t])*alpha[t,:] ) ) < 1e-8
end
for t = 1:Nt
  @test norm( prod(1 ./c[t+1:end]).*beta_b[t,:] - ( beta[t,:] ) )  < 1e-8
  @test norm( exp.(logbeta[t,:]) - (prod(c[t+1:end])*beta[t,:] ) )  < 1e-8
end

# testing with long sequence
T = Float64
Nt = 4000
Ns = 100
A = rand(T,Ns,Ns)
a = rand(T,Ns)
a ./= norm(a,1)
for i in 1:Ns A[i,:] ./= norm(A[i,:],1) end
@assert HMMGradients.is_transition_matrix(A)
y = rand(T,Nt,Ns)

alpha, c = forward(Nt,a,A,y)
logalpha, logML = logforward(Nt,log.(a),log.(A),log.(y))
logbeta = logbackward(Nt,log.(A),log.(y))
beta = backward(Nt,A,c,y)
gamma = alpha.*beta
loggamma = logalpha .+ logbeta .- logML*Nt
@test norm( gamma - exp.(loggamma) ) < 1e-5
@test nlogML(Nt,a,A,y) ≈ nlogMLlog(Nt,log.(a),log.(A),log.(y))


# Objective functions
# Author: Philipp Witte, pwitte3@gatech.edu
# Date: January 2020

export log_likelihood, ∇log_likelihood, mse, ∇mse

###################################################################################################
# Mean squared error
"""
    f = mse(X, Y)

Mean squared error between arrays/tensor X and Y

See also: [`∇mse`](@ref)
"""
mse(X, Y) = .5f0/size(X, 4)*norm(X - Y, 2)^2

"""
    ∇f = ∇mse(X, Y)

Gradient of the MSE loss with respect to input tensors X and Y.

See also: [`mse`](@ref)
"""
∇mse(X, Y) = 1f0/size(X, 4)*(X - Y)

###################################################################################################
# Log-likelihood

"""
    f = log_likelihood(X; μ=0f0, σ=1f0)

Log-likelihood of X for a Gaussian distribution with given mean μ and variance σ. All elements of X are assumed to be iid.

See also: [`∇log_likelihood`](@ref)
"""
log_likelihood(X; μ=0f0, σ=1f0) = 1f0/size(X, 4)*sum(-.5f0*((X .- μ)/σ).^2)

"""
    ∇f = ∇log_likelihood(X; μ=0f0, σ=1f0)

Gradient of the log-likelihood function with respect to the input tensor X.

See also: [`log_likelihood`](@ref)
"""
∇log_likelihood(X; μ=0f0, σ=1f0) = -1f0/size(X, 4)*(X .- μ)/σ^2


"""
    dirichlet(f1,f2,f3,f4,a,b,h)
berisi program untuk mencari solusi persamaan
differensial parsial yaitu persamaan Laplace dengan kondisi batas Dirichlet. Program
ini secara default berisi 8 masukan, yaitu fungsi nilai batas `f1`, `f2`, `f3`, dan
`f4`, nilai batas solusi `a` dan `b`, dan ukuran langkah `h`
# Example
```jl
julia> f1(x)= 20+0*x;

julia> f2(x)= 180+0*x;

julia> f3(y)= 80+0*y;

julia> f4(y)= 0*y;

julia> U = dirichlet(f1,f2,f3,f4,4,4,1)
5×5 Adjoint{Float64,Array{Float64,2}}:
  50.0   20.0      20.0      20.0     10.0
  80.0   55.7143   43.2143   27.1429   0.0
  80.0   79.6429   70.0      45.3571   0.0
  80.0  112.857   111.786    84.2857   0.0
 130.0  180.0     180.0     180.0     90.0
```
return solusi `U`.
"""
function dirichlet(f1,f2,f3,f4,a,b,h)
  maxi = 100;
  tol = 1e-7;
  n = Int(a/h)+1;
  m = Int(b/h)+1;
  U = (a.*(f1.(0).+f2.(0)).+b.*(f3.(0).+f4.(0)))./(2*a.+2*b).+ones(n,m);
  # Masalah nilai batas
  U[1:n,1]=f1.(0:h:(n-1)*h);
  U[1:n,m]=f2.(0:h:(n-1)*h);
  U[1,1:m]=f3.(0:h:(m-1)*h);
  U[n,1:m]=f4.(0:h:(m-1)*h);
  U[1,1]=(U[1,2]+U[2,1])/2;
  U[1,m]=(U[1,m-1]+U[2,m])/2;
  U[n,1]=(U[n-1,1]+U[n,2])/2;
  U[n,m]=(U[n-1,m]+U[n,m-1])/2;
  w = 4/(2+sqrt(4-(cos(pi/(n-1))+cos(pi/(m-1)))^2));
  err=1;   iter=0;
  while (err>tol)&&(iter<=maxi)
    err = 0;
    for j = 2:m-1
      for i = 2:n-1
        relx = w*(U[i,j+1]+U[i,j-1]+U[i+1,j]+U[i-1,j]-4*U[i,j])/4;
        U[i,j]=U[i,j]+relx;
        if err<=abs(relx)
          err=abs(relx);
        end
      end
    end
    iter=iter+1;
  end
  U = U';
end


"""
    regpoly(X,Y,m)

adalah fungsi untuk mencari persamaan polinomial `y=C[m]x^m + ... + C[2]x^2 + C[1]x`
dari variabel dependen `Y` dan independen `X` dengan pangkat tertinggi `m`.

# Example
```jldoctest
julia> x = [-3, 0, 2, 4];

julia> y = [ 3, 1, 1, 3];

julia> C = regpoly(x,y,2)
3-element Array{Float64,1}:
  0.17846247712019525
 -0.19249542403904824
  0.8505186089078707
```
return nilai koefisien `C = [C[m],...,C[2],C[1]]`.
"""
function regpoly(X,Y,m)
  F = zeros(length(X),m+1)
  for k = 1: m+1
    F[:,k]=X.^(k-1);
  end
  A = F'*F;
  B = F'*Y;
  C = A\B;
  C = reverse(C,dims=1)
end


"""
```
imge = imcorner(img; [method], [border], [blockSize], [k])
```
Performs corner detection, using either the Harris method or the Shi-Tomasi
method. `method` is `'harris'` (default) or `'shi-tomasi'`. `'border'` is the border
mode used in computing the gradient (default is `'replicate'`). `'blockSize'` is
the size of the box filter kernel (default of `3`). `k` is the Harris free parameter
(default of `0.04`). It is only used when method is set to `'harris'`.

"""
function imcorner(img::AbstractArray; method::AbstractString="harris", border::AbstractString="replicate", blockSize::Int=3, k::Float64=0.04)

    (grad_x,grad_y) = imgradients(img, "sobel",border);

    cov_xx = grad_x .* grad_x;
    cov_xy = grad_x .* grad_y;
    cov_yy = grad_y .* grad_y;

    box_filter_kernel = (1/(blockSize*blockSize)) * ones(blockSize,blockSize);

    cov_xx = imfilter(cov_xx,box_filter_kernel);
    cov_xy = imfilter(cov_xy,box_filter_kernel);
    cov_yy = imfilter(cov_yy,box_filter_kernel);

    if method == "harris"
        corner = (cov_xx.*cov_yy - cov_xy.*cov_xy - k*(cov_xx + cov_yy).*(cov_xx + cov_yy));
    elseif method == "shi-tomasi"
        cov_yy = 0.5*cov_yy;
        cov_xx = 0.5*cov_xx;
        corner = ((cov_xx + cov_yy) - sqrt((cov_xx - cov_yy).*(cov_xx - cov_yy) + cov_xy.*cov_xy));
    else
        error("Unknown corner method: $method")
    end

    return corner
end


test_string = "BFFFBBFRRR
FFFBBBFRRR
BBFFBBFRLL"

test_list = split(test_string,'\n')

list = readlines("day5_input.txt")

function seat_to_id(seat)
    seat_binary = seat
    for (k,v) in zip(('F', 'B', 'L', 'R'),('0', '1', '0', '1'))
        seat_binary = replace(seat_binary, k => v)
    end
    id = parse(Int, seat_binary, base=2)
    return id
end

function id_to_seat(id)
    seat_binary = bitstring(id)[end-9:end]
    seat_row = replace(replace(seat_binary[1:end-3], '1' => 'B'), '0' => 'F')
    seat_column = replace(replace(seat_binary[end-2:end], '1' => 'R'), '0' => 'L')
    seat = "B"^(7-length(seat_row)) * seat_row * 'L'^(3-length(seat_column)) * seat_column
    return seat
end

highest_id(list) = maximum(seat_to_id.(list))

highest_seat(list) = id_to_seat(highest_id(list))

println("Highest id of the test list: $(highest_id(test_list))")

println("Highest id of the problem list: $(highest_id(list))")

function myseat(list)
    sorted_list = sort(seat_to_id.(list))
    min_id = sorted_list[1]
    diff_list = sorted_list[2:end] - sorted_list[1:end-1]
    max, id_skip = findmax(diff_list)
    return min_id + id_skip
end

println("Your seat is $(myseat(list))")

# Answers:
# P1: 926
# P2: 657

function seat_to_id2(seat)
    f(x) = (x =='F' || x == 'L') ? 0 : 1
    return parse(Int, join(map(f, collect(seat))), base=2)
end

highest_id2(list) = maximum(seat_to_id2.(list))

println("Highest id of the problem list v2: $(highest_id(list))")

# method 1 is still better
#=
```julia
julia> @btime highest_id(list)
  1.158 ms (13538 allocations: 747.02 KiB)
926

julia> @btime highest_id2(list)
  1.359 ms (22844 allocations: 1.21 MiB)
926
```
 =#

seatnumber(line) = mapfoldl(c->c in "BR", (x, y)->2x+y, line, init=0)

# wow, look at the differences
#=
```julia
julia> @btime seat_to_id(line)
  1.276 μs (16 allocations: 896 bytes)
181

julia> @btime seat_to_id2(line)
  1.579 μs (27 allocations: 1.45 KiB)
181

julia> @btime seatnumber(line)
  281.837 ns (0 allocations: 0 bytes)
181
```
 =#

 # Here is a one-liner, which is actually a two-liner due to `;`,
 # that was started by [braden](bradeneliason#2230), using
 # the one-liner `seatnumber` function, and improved upon
 # after discussions with AaronRMatthis and Tom:

function seats(list)
    S=sort(mapfoldl.(∈("BR"),(x,y)->2x+y,list))
    return S[end],S[argmax(diff(S))]+1
end

nothing

# Much faster than mine:
#=
```julia
julia> @btime seats(list_day5)
  309.319 μs (14 allocations: 20.52 KiB)
(926, 657)
```
 =#


using MLDatasets, StatsBase, Measures, Plots; pyplot()

xTrain, yTrain = MLDatasets.MNIST.traindata(Float32)
xTest, yTest = MLDatasets.MNIST.testdata(Float32)
nTrain, nTest = size(xTrain)[3], size(xTest)[3]
trainData = [xTrain[:,:,k]' for k in 1:nTrain]
testData = [xTest[:,:,k]' for k in 1:nTest]
positiveTrain = trainData[yTrain .== 1]
negativeTrain = trainData[yTrain .!= 1]
testLabels = yTest .== 1

function peakProp(img)
	peakSum = 0.0
	for j in 1:28
		m = argmax(img[j,:])
		(m <=2 || m >= 26) && continue
		peakSum += sum(img[j,m-2:m+2])
	end
	peakSum/sum(img)
end
predict(img,theta) = peakProp(img) <= theta ? false : true
function F1value(theta)
	predictionOnPositive = predict.(positiveTrain,theta)
	predictionOnNegative = predict.(negativeTrain,theta)
	TP = sum(predictionOnPositive)
	FN = sum(1 .- predictionOnPositive)
	FP = sum(predictionOnNegative)
	TN = sum(1 .- predictionOnNegative)
	recall, precision = TP/(TP + FN), TP/(TP + FP)
	return 2*(precision*recall)/(precision+recall)
end

psPositive, psNegative = peakProp.(positiveTrain), peakProp.(negativeTrain)
thetaRange = 0.5:0.005:1
f1Values = F1value.(thetaRange)

bestF1, bestIndex = findmax(f1Values)
bestTheta = thetaRange[bestIndex]
println("Best theta = ", bestTheta, " with F1 value of ", bestF1)

println("On test set:")
testPredictions = predict.(testData,bestTheta)
TP, FN = sum(testPredictions[testLabels]), sum(.!testPredictions[testLabels])
FP, TN = sum(testPredictions[.!testLabels]), sum(.!testPredictions[.!testLabels])
recall, precision = TP/(TP + FN), TP/(TP + FP)
F1test = harmmean([precision,recall])
@show TP, FN, FP, TN; @show recall, precision; @show F1test

p1 = stephist(psPositive, normed = true, label="1 Digit",bins=50)
stephist!(psNegative, normed = true, xlim=(0.4,1), ylim=(0,6),bins=50,
			xlabel = "Value", ylabel = "Frequency",
			label="Non 1 Digit")
plot!([bestTheta,bestTheta],[0,5], c =:black, label = :none)
p2 = plot(thetaRange,f1Values, legend = false,
	xlabel = "Threshold", ylabel = "F1 Value")
plot!([bestTheta],[bestF1], c=:black)
plot(p1,p2,size=(800,400))


abstract type AbstractKoopmanAlgorithm end;

"""
    DMDPINV()

Approximates the Koopman operator `K` based on

```julia
K = Y / X
```

where `Y` and `X` are data matrices.

"""
mutable struct DMDPINV <: AbstractKoopmanAlgorithm end;

# Fast but more allocations
(x::DMDPINV)(X::AbstractArray, Y::AbstractArray) = Y / X

"""
    DMDSVD(rtol)

Approximates the Koopman operator `K` based on the singular value decomposition
of `X` such that:

```julia
K = Y*V*Σ*U'
```

where `Y` and `X = U*Σ*V'` are data matrices.
If `rtol` ∈ (0, 1) is given, the singular value decomposition is reduced to include only
entries bigger than `rtol*maximum(Σ)`. If `rtol` is an integer, the reduced SVD up to `rtol` is used
for computation.
"""
mutable struct DMDSVD{T} <: AbstractKoopmanAlgorithm
    rtol::T
end;

DMDSVD() = DMDSVD(0.0)

# Slower but fewer allocations
function (x::DMDSVD)(X::AbstractArray, Y::AbstractArray)
    U, S, V = svd(X)
    if typeof(x.rtol) <: Int && x.rtol < size(X, 1)
        V = V[:, x.rtol]
    elseif zero(x.rtol) < x.rtol < one(x.rtol)
        r = vec(S .> x.rtol*maximum(S))
        U = U[:, r]
        S = S[r]
        V = V[:, r]
    end

    return Y*V*Diagonal(one(eltype(X)) ./ S)*U'
end

"""
    TOTALDMD(rtol, alg)

Approximates the Koopman operator `K` with the algorithm `alg` over the rank-reduced data
matrices `Xᵣ = X Qᵣ` and `Yᵣ = Y Qᵣ`, where `Qᵣ` originates from the singular value decomposition of
the joint data `Z = [X; Y]`. Based on [this paper](http://cwrowley.princeton.edu/papers/Hemati-2017a.pdf).

If `rtol` ∈ (0, 1) is given, the singular value decomposition is reduced to include only
entries bigger than `rtol*maximum(Σ)`. If `rtol` is an integer, the reduced SVD up to `rtol` is used
for computation.
"""
mutable struct TOTALDMD{R, A} <: AbstractKoopmanAlgorithm
    rtol::R
    alg::A
end

TOTALDMD() = TOTALDMD(0.0, DMDPINV())

function (x::TOTALDMD)(X::AbstractArray, Y::AbstractArray)
    _ , S, Q = svd([X; Y])
    if typeof(x.rtol) <: Int && x.rtol < size(X, 1)
        Q = Q[:, 1:x.rtol]
    elseif zero(x.rtol) < x.rtol < one(x.rtol)
        r = vec(S .> x.rtol*maximum(S))
        Q = Q[:, r]
    end

    return x.alg(X*Q, Y*Q)
end


# Estimate derivatives numerically
# These use centered-differencing for higher accuracy
function derivative_numer{T<:Number}(func::Function, x::T, h::T)
    xp = x + h
    vp = func(xp)
    xm = x - h
    vm = func(xm)
    return (vp-vm)/(xp-xm)
end
function derivative2_numer{T<:Number}(func::Function, x::T, h::T)
    return (func(x + 2h) - 2*func(x) + func(x - 2h)) / 4h^2
end
derivative_numer{T<:Number}(func::Function, x::T, index::Int, h::T) = derivative_numer(func, x, h)  # compatibility with partial derivatives
derivative2_numer{T<:Number}(func::Function, x::T, index::Int, h::T) = derivative2_numer(func, x, h)
function derivative_numer{T<:Number}(func::Function, x::Array{T}, index::Int, h::T)
    xsave = x[index]
    xp = xsave + h
    xm = xsave - h
    x[index] = xp
    vp = func(x)
    x[index] = xm
    vm = func(x)
    x[index] = xsave
    return (vp-vm)/(xp-xm)
end
function derivative2_numer{T<:Number}(func::Function, x::Array{T}, index::(Int,Int,), h::T)
    if index[1] == index[2] # 2nd order derivative in one direction
        v0 = func(x...)
        xsave = x[index[1]]
        xp = xsave + 2h
        xm = xsave - 2h
        x[index[1]] = xp
        vp = func(x...)
        x[index[1]] = xm
        vm = func(x...)
        x[index[1]] = xsave
        deriv = (vp - 2v0 + vm) / 4h^2
    else # cross-derivative, 1st order in each direction
        xpp = [x[1]+h,x[2]+h]
        xpm = [x[1]+h,x[2]-h]
        xmp = [x[1]-h,x[2]+h]
        xmm = [x[1]-h,x[2]-h]
        deriv = (func(xpp...) - func(xpm...) - func(xmp...) + func(xmm...)) / 4h^2
    end
    return deriv
end
function derivative_numer{T<:Number}(func::Function, c::(T...,), index::Int, h::T)
    x = [c...]
    xsave = x[index]
    xp = xsave + h
    xm = xsave - h
    x[index] = xp
    vp = func(x...)
    x[index] = xm
    vm = func(x...)
    x[index] = xsave
    return (vp-vm)/(xp-xm)
end
function derivative2_numer{T<:Number}(func::Function, c::(T...,), index::(Int,Int,), h::T)
    x = [c...]
    return derivative2_numer(func, x, index, h)
end
function derivative_numer{T<:Number}(func::Function, x::T, h::Vector{T})
    d = zeros(T, length(h))
    for i = 1:length(h)
        d[i] = derivative_numer(func, x, h[i])
    end
    return d
end
function derivative2_numer{T<:Number}(func::Function, x, h::Vector{T})
    d = zeros(T, length(h))
    for i = 1:length(h)
        d[i] = derivative2_numer(func, x, h[i])
    end
    return d
end
function derivative_numer{T<:Number}(func::Function, x, index::Int, h::Vector{T})
    d = zeros(T, length(h))
    for i = 1:length(h)
        d[i] = derivative_numer(func, x, index, h[i])
    end
    return d
end
function derivative2_numer{T<:Number}(func::Function, x, index::(Int,Int,), h::Vector{T})
    d = zeros(T, length(h))
    for i = 1:length(h)
        d[i] = derivative2_numer(func, x, index, h[i])
    end
    return d
end
derivative_numer{T<:Number}(func::Function, x::T) = derivative_numer(func, x, (eps(max(abs(x),one(T))))^convert(T, 1/3))
derivative_numer{T<:Number}(func::Function, x::Array{T}, index::Int) = derivative_numer(func, x, index, (eps(max(abs(x[index]),one(T))))^convert(T, 1/3))
derivative_numer{T<:Number}(func::Function, c::(T...,), index::Int) = derivative_numer(func, c, index, (eps(max(abs(c[index]),one(T))))^convert(T, 1/3))
derivative2_numer{T<:Number}(func::Function, x::T) = derivative2_numer(func, x, (eps(max(abs(x),one(T))))^convert(T,1/3))
derivative2_numer{T<:Number}(func::Function, x::Array{T}, index::(Int,Int,)) = derivative2_numer(func, x, index, (eps(max(abs(x[index[1]]),one(T))))^convert(T, 1/3))
derivative2_numer{T<:Number}(func::Function, c::(T...,), index::(Int,Int,)) = derivative2_numer(func, c, index, (eps(max(abs(c[index[1]]),one(T))))^convert(T, 1/3))


### A Pluto.jl notebook ###
# v0.17.3

using Markdown
using InteractiveUtils

# ╔═╡ c819a820-13d1-11ec-174e-31eef4e20fc4
md"""
# Basic operators

Operators are symbols used to perform for example a basic mathematical operation. Some are very easy and straighforward, some are a little bit more complicated.

Let's start by the simple ones.

## Numeric operators

They include +, -, / and *, and are simply used as with a calculator as follows:
"""

# ╔═╡ 63b315e1-6bab-48d2-9992-6e6598bdf5fb
1 + 2

# ╔═╡ e874c232-256c-4d9f-a214-e526192be0cd
1 - 2

# ╔═╡ 4c362d08-3b79-44e3-8b48-0f9231526dca
1 * 2

# ╔═╡ f9ec2db0-d20a-4aac-ad24-66df4c7e6bde
1 / 2

# ╔═╡ 2222f963-31d7-460c-940f-4b544b3bc8a2
md"""
There is also the equal sign to assign a value to a variable:
"""

# ╔═╡ 36732368-836f-46bb-9a75-b75b10890505
a = 1

# ╔═╡ 9cb19c7e-9218-4f37-be67-c1e5d7b5a883
a

# ╔═╡ e43721b6-d642-4b6e-88ff-2bc48635effa
md"""
There's the integer division using ÷ (`\div` + tab)
"""

# ╔═╡ cc9cff27-b864-475b-b162-0503dc308432
1 ÷ 2

# ╔═╡ 98f549a9-1757-4e7c-bfac-0bf0d951b10d
md"""
To get the reminder of a division, we use the modulus, which is defined by the percent sign:
"""

# ╔═╡ 0272780b-6ace-499a-bf2b-441b9a148de4
1 % 2

# ╔═╡ 9f59bff0-bc85-4f36-b92c-d84e25cbdd9e
md"""
This is especially usefull if you want to know if the value of a variable is odd or even. To do so, you would use the remainder of the division by 2, which gives 0 for an even number, and 1 for an odd number:
"""

# ╔═╡ 7ed2ed34-f811-40c5-b6d2-17b7263f934c
a % 2

# ╔═╡ 00b8743b-8597-4fea-82d9-c191bfe73050
b = 2

# ╔═╡ 72ffc8b6-8543-42d2-b0bf-1640ccfcdea7
b % 2

# ╔═╡ 1df480ee-375f-421f-bfe6-cac883d290e3
md"""
To raise a value to the power of a number, we can use the caret:
"""

# ╔═╡ 278e7cb7-f760-4df2-8bdf-c87f1b8f5bec
2^4

# ╔═╡ 74bb4a3d-1d27-4555-8a47-98472ead2c9a
md"""
There are some short-hands if you want to update the value of a variable:
"""

# ╔═╡ ebc07294-3d9b-4973-b2b6-1afb535dc1ae
let
var = 1
var += 1
end

# ╔═╡ 73ced44d-503a-46a7-b1df-4db81e21375c
let
var = 1
var -= 1
end

# ╔═╡ 4293c2a4-cbd5-4b22-8b4a-280bbdacd1c8
let
var = 1
var *= 2
end

# ╔═╡ 1c238add-1c4f-4236-9943-6b9e7f45d8ab
let
var = 1
var /= 2
end

# ╔═╡ 6aa9bc63-bbf2-4b6e-9690-13ff4344c460
md"""
## Comparison operators

Some operators can be used to compare two values. They always return a boolean value: either `true` or `false`.

Greater than:
"""

# ╔═╡ 81ef192e-8e24-4865-8d79-06568e33390e
2 > 1

# ╔═╡ 2ee4020f-debb-4d0d-b25c-889f0f821d02
md"""
Greater or equal to:
"""

# ╔═╡ 6703e50a-18f1-4286-a5a7-2e18a28dc9ac
2 >= 1

# ╔═╡ 682bc70c-8ab9-40da-a231-df3f998d3562
md"""
The opposite:
"""

# ╔═╡ c7b5b7b7-3766-4a20-b847-f6ce2a077ace
2 < 1

# ╔═╡ 9e48d85f-61a8-4e93-9cc7-079f33359f0c
2 <= 1

# ╔═╡ daf09d9e-556d-4e2c-9e35-d78b0f61cd26
md"""
Testing equality:
"""

# ╔═╡ cce83b75-2ce6-489d-b7f6-05853fd6d056
2 == 1

# ╔═╡ 5f68213a-6d1d-4b3a-a7b3-c0abfbaa305b
md"""
!!! note
	Note that equality is done using `==`, which is not the same than `=`, used to assign a value to a variable.
"""

# ╔═╡ 9e9b02b8-f47d-494f-9332-7701903e6810
md"""
To test inequality, we use `!=`:
"""

# ╔═╡ 37d52bfb-6965-4ecd-804f-98d9f7c1b01b
2 != 1

# ╔═╡ bc6ad01a-f7ec-4538-8115-73ba67406943
md"""
## Boolean operators

There are also the logical tests using `!` ("not"), `&&` ("and") and `||` ("or") on boolean values (`true` or `false`).

### Negation

The `!` operator negates the boolean value of the statement:
"""

# ╔═╡ 83f6f3fd-ab7e-4ebb-8285-b49188a1f50b
!false

# ╔═╡ dcbbe212-23aa-4ff9-9fda-f1f0d67b3f95
!true

# ╔═╡ 8bbe84a0-93de-4bb9-8995-caed6bc920c7
md"""
So this:
"""

# ╔═╡ f0752ae9-5401-49fc-8ddc-d07302499185
2 > 1

# ╔═╡ b88e0721-cf58-4f85-b54a-4514982e2218
md"""
Is equivalent to this:
"""

# ╔═╡ 0a8e65d5-7043-4f66-8987-c2e56e09d223
!(2 <= 1)

# ╔═╡ 729ae192-df5b-47a3-a245-9d40e8014f5a
md"""

### Logical "and"

The logical "and" (`&&`) returns `true` only if the right-hand side and left-hand side values of the operator are both `true`:
"""

# ╔═╡ f032b44b-16b0-4383-8076-97d110249fb5
true && true

# ╔═╡ 4d44dd47-ff82-417a-b248-cacdac6d91d4
true && false

# ╔═╡ 0de3bcfb-f4e0-43cc-b407-ececfbb1d8bf
md"""
### Logical "or"
"""

# ╔═╡ c2b86ba3-d54e-4191-9efb-a70e0f706516
md"""
The logical "or" returns true if at least one is true:
"""

# ╔═╡ 8e7cce54-9217-4ad0-978e-67fa21cb6fd5
true || false

# ╔═╡ 4f5d628f-de89-43a0-9a50-d79d39d8fd44
false || false

# ╔═╡ f91b3d97-a611-4a5e-8cf6-3e45eb052ff1
md"""
In reality you'll use variables instead of boolean values directly. For example you could say that `c` is equal to `true` in the case both `a` and `b` are bellow 3:
"""

# ╔═╡ ff6e339d-89ba-4df9-b674-4602dd4549a9
c = a < 3 && b < 3

# ╔═╡ 2f44b8ec-3414-4903-b5a2-ea7c819b3b86
md"""
### Conditional execution
"""

# ╔═╡ a0cf3510-a2f1-4b8d-9e95-54b49f37d926
md"""
The nice thing with Julia is that it tests the conditions incrementally because we know that if the first condition is `false` in a `&&` statement, the whole statement will be false, and if the first condition is `true` in a `||` statement, the whole statement will be `true`. We can use that as a tool to conditionally execute a line of code.
"""

# ╔═╡ 98fa4586-e163-40a5-9084-c9a1cb4c350b
a == 1 && "a is equal to 1!"

# ╔═╡ 8dc5efc6-6c25-45d7-a944-6f229eef9b49
b == 1 && "b is equal to 1!"

# ╔═╡ 986244f3-b4eb-4d16-ab5f-7585a44d3784
b == 1 || "b is not equal to 1!"

# ╔═╡ 102364c0-98a9-411b-94c7-cd129c99f04a
md"""
The concept of conditional execution is very important, and we will see it in more details in a coming video.
"""

# ╔═╡ 0da85da4-18eb-431e-972a-426e73f16781
md"""
## Vectorizing operators

All operators we just saw are applied to one value at a time. But very often we want to apply an operator over a sequence of values stored in an Array for example.

To do so, we can use the vectorized version of the operators by adding a dot right before it. This is called broadcasting. We will see more details about that in another video.
"""

# ╔═╡ 5356fedc-2524-4b32-8559-a0ba5ae2f5cf
a_vec = [1, 2, 3]

# ╔═╡ 7e0b0477-e72d-4bf5-a059-63993cf0035b
md"""
For example adding a single value to a vector of values using the `+` sign will return an error:
"""

# ╔═╡ 0d905737-2738-4564-9273-139a84aa100d
a_vec + 2

# ╔═╡ 13b1edec-9ae6-4a0b-b8ed-2f63df2e6792
md"""
THe error is a `MethodError`, because there is no method implemented for the `+` function with a vector of integer and an integer.

Using the vectorized version will work perfectly:
"""

# ╔═╡ 02a4c901-8c29-42ec-8864-fe5d4c70f46b
a_vec .+ 2

# ╔═╡ 6515d475-294a-40c4-9ac7-8e3ef15e8f89
md"""
!!! note
	In practice, the vectorized version applies the `+` function sequentially over the values of the `a_vec` array, and then return the results back as an array.
"""

# ╔═╡ e087cd30-f78f-40a4-95f5-87bfcf05fbe7
md"""
There are other ways to do so, such as the for loops or array comprehension, but we will see that in another video.
"""

# ╔═╡ Cell order:
# ╟─c819a820-13d1-11ec-174e-31eef4e20fc4
# ╠═63b315e1-6bab-48d2-9992-6e6598bdf5fb
# ╠═e874c232-256c-4d9f-a214-e526192be0cd
# ╠═4c362d08-3b79-44e3-8b48-0f9231526dca
# ╠═f9ec2db0-d20a-4aac-ad24-66df4c7e6bde
# ╟─2222f963-31d7-460c-940f-4b544b3bc8a2
# ╠═36732368-836f-46bb-9a75-b75b10890505
# ╠═9cb19c7e-9218-4f37-be67-c1e5d7b5a883
# ╟─e43721b6-d642-4b6e-88ff-2bc48635effa
# ╠═cc9cff27-b864-475b-b162-0503dc308432
# ╟─98f549a9-1757-4e7c-bfac-0bf0d951b10d
# ╠═0272780b-6ace-499a-bf2b-441b9a148de4
# ╟─9f59bff0-bc85-4f36-b92c-d84e25cbdd9e
# ╠═7ed2ed34-f811-40c5-b6d2-17b7263f934c
# ╠═00b8743b-8597-4fea-82d9-c191bfe73050
# ╠═72ffc8b6-8543-42d2-b0bf-1640ccfcdea7
# ╟─1df480ee-375f-421f-bfe6-cac883d290e3
# ╠═278e7cb7-f760-4df2-8bdf-c87f1b8f5bec
# ╟─74bb4a3d-1d27-4555-8a47-98472ead2c9a
# ╠═ebc07294-3d9b-4973-b2b6-1afb535dc1ae
# ╠═73ced44d-503a-46a7-b1df-4db81e21375c
# ╠═4293c2a4-cbd5-4b22-8b4a-280bbdacd1c8
# ╠═1c238add-1c4f-4236-9943-6b9e7f45d8ab
# ╟─6aa9bc63-bbf2-4b6e-9690-13ff4344c460
# ╠═81ef192e-8e24-4865-8d79-06568e33390e
# ╟─2ee4020f-debb-4d0d-b25c-889f0f821d02
# ╠═6703e50a-18f1-4286-a5a7-2e18a28dc9ac
# ╟─682bc70c-8ab9-40da-a231-df3f998d3562
# ╠═c7b5b7b7-3766-4a20-b847-f6ce2a077ace
# ╠═9e48d85f-61a8-4e93-9cc7-079f33359f0c
# ╟─daf09d9e-556d-4e2c-9e35-d78b0f61cd26
# ╠═cce83b75-2ce6-489d-b7f6-05853fd6d056
# ╟─5f68213a-6d1d-4b3a-a7b3-c0abfbaa305b
# ╟─9e9b02b8-f47d-494f-9332-7701903e6810
# ╠═37d52bfb-6965-4ecd-804f-98d9f7c1b01b
# ╟─bc6ad01a-f7ec-4538-8115-73ba67406943
# ╠═83f6f3fd-ab7e-4ebb-8285-b49188a1f50b
# ╠═dcbbe212-23aa-4ff9-9fda-f1f0d67b3f95
# ╟─8bbe84a0-93de-4bb9-8995-caed6bc920c7
# ╠═f0752ae9-5401-49fc-8ddc-d07302499185
# ╟─b88e0721-cf58-4f85-b54a-4514982e2218
# ╠═0a8e65d5-7043-4f66-8987-c2e56e09d223
# ╟─729ae192-df5b-47a3-a245-9d40e8014f5a
# ╠═f032b44b-16b0-4383-8076-97d110249fb5
# ╠═4d44dd47-ff82-417a-b248-cacdac6d91d4
# ╟─0de3bcfb-f4e0-43cc-b407-ececfbb1d8bf
# ╟─c2b86ba3-d54e-4191-9efb-a70e0f706516
# ╠═8e7cce54-9217-4ad0-978e-67fa21cb6fd5
# ╠═4f5d628f-de89-43a0-9a50-d79d39d8fd44
# ╟─f91b3d97-a611-4a5e-8cf6-3e45eb052ff1
# ╠═ff6e339d-89ba-4df9-b674-4602dd4549a9
# ╟─2f44b8ec-3414-4903-b5a2-ea7c819b3b86
# ╟─a0cf3510-a2f1-4b8d-9e95-54b49f37d926
# ╠═98fa4586-e163-40a5-9084-c9a1cb4c350b
# ╠═8dc5efc6-6c25-45d7-a944-6f229eef9b49
# ╠═986244f3-b4eb-4d16-ab5f-7585a44d3784
# ╟─102364c0-98a9-411b-94c7-cd129c99f04a
# ╟─0da85da4-18eb-431e-972a-426e73f16781
# ╠═5356fedc-2524-4b32-8559-a0ba5ae2f5cf
# ╟─7e0b0477-e72d-4bf5-a059-63993cf0035b
# ╠═0d905737-2738-4564-9273-139a84aa100d
# ╟─13b1edec-9ae6-4a0b-b8ed-2f63df2e6792
# ╠═02a4c901-8c29-42ec-8864-fe5d4c70f46b
# ╟─6515d475-294a-40c4-9ac7-8e3ef15e8f89
# ╟─e087cd30-f78f-40a4-95f5-87bfcf05fbe7


#=
Given a multiset of integers, return whether it can be partitioned into two subsets whose sums are the same.

For example, given the multiset {15, 5, 20, 10, 35, 15, 10}, it would return true, since we can split it up into {15, 5, 10, 15, 10} and {20, 35}, which both add up to 55.

Given the multiset {15, 5, 20, 10, 35}, it would return false, since we can't split it up into two subsets that add up to the same sum.
=#

using Test
include("Solutions/problem60_partition_multiset_in_two_equal_subset.jl")

@test ispartitionable([15, 5, 20, 10, 35, 15, 10])
@test !ispartitionable([15, 5, 20, 10, 35])


"Tableau for explicit Euler method"
function getTableauExplicitEuler()
    a = zeros(Float64, 1, 1)
    b = [1.0]
    c = [0.0]
    o = 1

    TableauERK(:explicit_euler, o, a, b, c)
end

"Tableau for explicit midpoint method"
function getTableauExplicitMidpoint()
    a = [[0.0 0.0]
         [0.5 0.0]]
    b = [0.0, 1.0]
    c = [0.0, 0.5]
    o = 2

    TableauERK(:explicit_midpoint, o, a, b, c)
end

"Tableau for Heun's method"
function getTableauHeun()
    a = [[0.0 0.0]
         [1.0 0.0]]
    b = [0.5, 0.5]
    c = [0.0, 1.0]
    o = 2

    TableauERK(:heun, o, a, b, c)
end

"Tableau for Kutta's method of order three"
function getTableauKutta()
    a = [[ 0.0 0.0 0.0]
         [ 0.5 0.0 0.0]
         [-1.0 2.0 0.0]]
    b = [1/6, 4/6, 1/6]
    c = [0.0, 0.5, 1.0]
    o = 3

    TableauERK(:kutta, o, a, b, c)
end

"Tableau for explicit Runge-Kutta method of order four (1/6 rule)"
function getTableauERK4()
    a = [[0.0 0.0 0.0 0.0]
         [0.5 0.0 0.0 0.0]
         [0.0 0.5 0.0 0.0]
         [0.0 0.0 1.0 0.0]]
    b = [1/6, 1/3, 1/3, 1/6]
    c = [0.0, 0.5, 0.5, 1.0]
    o = 4

    TableauERK(:erk4, o, a, b, c)
end

"Tableau for explicit Runge-Kutta method of order four (3/8 rule)"
function getTableauERK438()
    a = [[ 0.0  0.0  0.0  0.0]
         [ 1/3  0.0  0.0  0.0]
         [-1/3  1.0  0.0  0.0]
         [ 1.0 -1.0  1.0  0.0]]
    b = [1/8, 3/8, 3/8, 1/8]
    c = [0.0, 1/3, 2/3, 1.0]
    o = 4

    TableauERK(:erk438, o, a, b, c)
end