# ==============================================================================
# Check type of max-plus and type promotions
# ==============================================================================

a = MP(1.0)
@test typeof(a) == MP{Float64}

b = MP(1)
@test typeof(b) == MP{Int64}

c = MP{Float64}(1)
@test typeof(c) == MP{Float64}

A = [1 2; 3 4]
@test typeof(A) == Array{Int64,2}
@test typeof(MP(A)) == ArrMP{Int64,2}

B = [MP(1) MP(2); MP(3) MP(4)]
@test typeof(B) == ArrMP{Int64,2}

C = [MP(1) 2; 3 4]
@test typeof(C) == ArrMP{Int64,2}
@test typeof(array(C)) == Array{Int64,2}

D = [MP(1.0) 2; 3 4]
@test typeof(D) == ArrMP{Float64,2}
@test typeof(array(D)) == Array{Float64,2}

E = [MP(1) 2.0; 3 4]
@test typeof(E) == ArrMP{Int64,2}
@test typeof(array(E)) == Array{Int64,2}

F = [2 MP(1.0); 3 4]
@test typeof(F) == ArrMP{Float64,2}
@test typeof(array(F)) == Array{Float64,2}

@test typeof(array(MP(A))) == typeof(A)
@test typeof(MP(array(B))) == typeof(B)

# ==============================================================================
# Max-plus constructor
# ==============================================================================

c = MP(1.0)
@test typeof(MP(c)) == MP{Float64}
d = MP(c)
@test typeof(d) == MP{Float64}

# ==============================================================================
# Max-plus comparaisons
# ==============================================================================

# Max-plus scalars
b = MP(3.0)
@test (b == b) == true
@test (b != b) == (b ≠ b) == false
@test (b >= b) == (b ≥ b) == true
@test (b <= b) == (b ≤ b) == true
@test (b > b) == false
@test (b < b) == false

# Max-plus vs. real comparaison
@test (b == 3.0) == true
@test (b == 4.0) == false
@test (b != 4.0) == true
@test (b < 4.0) == true
@test (b <= 4.0) == true
@test (b > 4.0) == false
@test (b >= 4.0) == false
@test (4.0 > b) == true
@test (4.0 >= b) == true

# Matrix comparaison
B = [MP(1) MP(2); MP(3) MP(4)]
@test (B == B) == true
@test (B != B) == (B ≠ B) == false
@test (B .>= B) == (B .≥ B) == [true true; true true]
@test (B .<= B) == (B .≤ B) == [true true; true true]
@test (B .> B) == [false false; false false]
@test (B .< B) == [false false; false false]

# ==============================================================================
# Max-Plus zero
# ==============================================================================

@test zero(MP{Float64}) == MP(-Inf) == -Inf
@test mp0 == zero(MP{Float64})
@test zero(MP{Int64}) == -9223372036854775808
@test zero(MP{Bool}) == false
@test zero(MP(42.0)) == mp0
@test iszero(mp0) == true
@test isone(mp1) == true
@test iszero(mp1) == false
@test isone(mp0) == false

# ==============================================================================
# Max-Plus one
# ==============================================================================

@test one(MP{Float64}) == MP(0.0) == 0.0
@test mp1 == one(MP{Float64})
@test one(MP{Int64}) == 0
@test one(MP{Bool}) == false
@test one(MP(42.0)) == mp1

# ==============================================================================
# Max-plus operations and neutral elements and commutativity
# ==============================================================================

b = MP(3.0)
@test b + mp0 == mp0 + b == MP(max(3.0, -Inf)) == MP(max(-Inf, 3.0)) == b == MP(3.0)
@test b * mp1 == mp1 * b == MP(3.0 + 0.0) == MP(0.0 + 3.0) == b == MP(3.0)

@test b * b == MP(3.0 + 3.0) == MP(6.0)
@test b + b == MP(max(3.0, 3.0)) == MP(3.0)

a = MP(5.0)
b = MP(3.0)
@test a + b == MP(max(5.0, 3.0)) == b + a == MP(max(3.0, 5.0)) == MP(5.0)
@test a * b == MP(5.0 + 3.0) == b * a == MP(3.0 + 5.0) == MP(8.0)

# ==============================================================================
# Max-plus distributivity of operations
# ==============================================================================

c = MP(1.0)
@test (a + b) + c == a + (b + c) == a + b + c == MP(max(1.0, 3.0, 5.0)) == MP(5.0)
@test (a * b) * c == a * (b * c) == a * b * c == MP(1.0 + 3.0 + 5.0) == MP(9.0)

# ==============================================================================
# Max-plus distributivity
# ==============================================================================

@test (a + b) * c == MP(max(5.0, 3.0) + 1.0) == MP(6.0)
@test (a * c) + (b * c) == MP(max(5.0 + 1.0, 3.0 + 1.0)) == MP(6.0)

# ==============================================================================
# mp0, mp1 and mptop operations
# ==============================================================================

@test mp0 + mp0 == mp0
@test mp0 + mp1 == 0
@test mp0 + mptop == mptop
@test mp1 + mp0 == 0
@test mp1 + mp1 == 0
@test mp1 + mptop == mptop

@test mp0 * mp0 == mp0
@test mp0 * mp1 == mp0
#FIXME @test mp0 * mptop == mp0
@test mp1 * mp0 == mp0
@test mp1 * mp1 == 0
@test mp1 * mptop == mptop

#FIXME @test mp0 - mp0 == mp0
@test mp0 - mp1 == mp0
#FIXME @test mp1 - mp0 == 0
@test mp1 - mp1 == 0

@test mptop - mp0 == mptop
@test mptop - mp1 == mptop
@test mp0 - mptop == mp0
@test mp1 - mptop == mp0

# ==============================================================================
# Max-plus element by element operations
# ==============================================================================

A=MP([1.0 2.0; 3.0 4.0])
@test 2.0 .+ A == A .+ 2.0 == [MP(2.0) + MP(1.0) MP(2.0) + MP(2.0); MP(2.0) + MP(3.0) MP(2.0) + MP(4.0)] == MP([2.0 2.0; 3.0 4.0])
@test 2.0 .* A == A .* 2.0 == [MP(2.0) * MP(1.0) MP(2.0) * MP(2.0); MP(2.0) * MP(3.0) MP(2.0) * MP(4.0)] == MP([3.0 4.0; 5.0 6.0])

# ==============================================================================
# Max-plus other operations
# ==============================================================================

@test MP(2)^4   == MP(2 * 4) == MP(8)
@test MP(0)^0   == MP(0 * 0) == MP(0)
@test MP(2)^0   == MP(2 * 0) == MP(0)
@test MP(2)^-3  == MP(2)^(-3) == MP(2 * -3) == MP(-6)
@test MP(2)^0.5 == MP(2 * 0.5) == MP(1.0)

@test b / b == MP(3.0 - 3.0) == MP(0.0)
@test b - b == MP(3.0 - 3.0) == MP(0.0)

@test MP(3.0) / MP(5.0) == MP(3.0 - 5.0) == MP(-2.0)
@test MP(3.0) - MP(5.0) == MP(3.0 - 5.0) == MP(-2.0)

@test abs2(MP(3.0)) == MP(6.0)

# ==============================================================================
# Max-plus min
# ==============================================================================

b=MP(3.0)
@test min(b, mp0) == mp0
@test min(b, mp1) == mp1
@test min(MP(1), MP(2)) == MP(1)
@test min(MP([10 1; 10 1]), MP([4 5; 6 5])) == MP([4 1; 6 1])
@test min(mpsparse([10 1; 10 1]), mpsparse([4 5; 6 5])) == mpsparse([4 1; 6 1])

# ==============================================================================
# Julia found bugs. Check if they are really fixed.

A = mpzeros(Float64, 2,2)

# Do not insert zero() element
A[1,1] = mp0
@test A.nzval == []
@test isempty(nonzeros(A))

# Insert fake zero element
A[1,1] = MP(0.0)
@test A[1,1] == MP(0.0)
@test A.nzval == MP([0.0])
@test !isempty(nonzeros(A))

# Allow replacing a non-zero() element by a zero() element
A[1,1] = mp0
@test A[1,1] == MP(-Inf)
@test A.nzval == MP([-Inf])
@test !isempty(nonzeros(A))

# ==============================================================================
# Max-plus length() and isempty()
# ==============================================================================
# Note: ScicosLab will return isempty == false and length = 2
# Note: NSP, like Julia will return isempty == false and length = 4

A = mpzeros(Float64, 2,2)
@test length(A) == 4
@test !isempty(A)

A[1,1] = MP(0.0)
@test length(A) == 4
@test !isempty(A)

# ==============================================================================
# Max-plus between objects of different types
# ==============================================================================

b = MP(3.0)
@test b + 3.0 == 3.0 + b == b == MP(max(3.0, 3.0)) == MP(3.0)
@test b + 3   == 3   + b == b == MP(max(3.0, 3.0)) == MP(3.0)
@test b * 3.0 == 3.0 * b == MP(3.0 + 3.0) == MP(6.0)
@test b * 3   == 3   * b == MP(3.0 + 3.0) == MP(6.0)

# ==============================================================================
# Max-plus dense matrix creation
# ==============================================================================

b = MP(3.0); C = [b 4.0; 5.0 6.0]; D = MP([3.0 4.0; 5.0 6.0])
@test C == D
@test typeof(C) == ArrMP{Float64,2}

# Max-Plus matrix of max-plus ones
O = mpones(Float64, 2)
@test typeof(O) == Array{MP{Float64},1}
@test O == [mp1; mp1]

O = mpones(Float64, 2,5)
@test typeof(O) == ArrMP{Float64,2}
@test O == [mp1 mp1 mp1 mp1 mp1; mp1 mp1 mp1 mp1 mp1]

# Identity matrix
Id = mpeye(Float64, 2)
@test Id == mpeye(Float64, 2)
@test typeof(Id) == ArrMP{Float64,2}
@test Id == [mp1 mp0; mp0 mp1]

Id = mpeye(Float64, 2,5)
@test typeof(Id) == ArrMP{Float64,2}
@test Id == [mp1 mp0 mp0 mp0 mp0; mp0 mp1 mp0 mp0 mp0]

# ==============================================================================
# Max-plus / non max-plus conversion
# ==============================================================================

A=MP([1.0 2.0; 3.0 4.0])
n = 2.0
@test plustimes(MP(n)) == n
@test plustimes(mpzeros(Float64,2,2)) == ones(Float64, 2,2) .* mp0
@test array(MP(A)) == plustimes(MP(A)) == A
@test MP(array(B)) == MP(plustimes(B)) == B

# ==============================================================================
# Convert values usable for min-plus
# ==============================================================================
@test minplus(MP(1.0)) == MP(1.0)
@test minplus(mp0) == mptop
@test minplus(mptop) == mp0
@test minplus(MP([Inf 0.0; 7 -Inf])) == MP([-Inf 0.0; 7 Inf])
#FIXME @test minplus(mpsparse([Inf 0.0; 7 -Inf], preserve=true)) == mpsparse([-Inf 0.0; 7 Inf], preserve=true)
A = MP([0 3 Inf 1; 1 2 2 -Inf; -Inf Inf 1 0])
@test minplus(A) == MP([0 3 -Inf 1; 1 2 2 Inf; Inf -Inf 1 0])
@test minplus(minplus(A)) == A

# ==============================================================================
# Max-plus sparse matrix creation and conversion
# ==============================================================================

# Construct a sparse max-plus vector
V = MP(sparse([1.0, 0.0, 1.0]))
@test typeof(V) == SpvMP{Float64,Int64}
@test V.nzval == MP([1.0; 1.0])

# Construct a sparse max-plus vector of zeros
V = mpzeros(Float64, 2)
@test typeof(V) == SpvMP{Float64,Int64}
@test V.nzval == MP([])

# Construct a sparse max-plus matrix
A = MP(sparse([1, 2, 3], [1, 2, 3], [-Inf, 2, 0]))
@test typeof(A) == SpaMP{Float64,Int64}
@test A.nzval == [MP(-Inf), MP(2.0), MP(0.0)]

# Construct a sparse max-plus matrix
A = MP(sparse([1, 2, 3], [1, 2, 3], [-Inf, 2, 0]), preserve=false)
@test typeof(A) == SpaMP{Float64,Int64}
@test A.nzval == [MP(2.0)]

# Basic dense non max-plus matrix to max-plus sparse array
A = mpsparse([1.0 2.0; 3.0 4.0])
@test typeof(A) == SpaMP{Float64,Int64}
@test (A.m == 2) && (A.n == 2)
@test nonzeros(A) == MP([1.0; 3.0; 2.0; 4.0])

A = mpsparse(MP([1.0 2.0; 3.0 4.0]))
@test typeof(A) == SpaMP{Float64,Int64}
@test (A.m == 2) && (A.n == 2)
@test nonzeros(A) == MP([1.0; 3.0; 2.0; 4.0])

# 0.0 are not eliminated in the max-plus sparse array
B = mpsparse([1.0 2.0; 0.0 4.0])
@test typeof(B) == SpaMP{Float64,Int64}
@test (B.m == 2) && (B.n == 2)
@test nonzeros(B) == MP([1.0; 0.0; 2.0; 4.0])

B = mpsparse(MP([1.0 2.0; 0.0 4.0]))
@test typeof(B) == SpaMP{Float64,Int64}
@test (B.m == 2) && (B.n == 2)
@test nonzeros(B) == MP([1.0; 0.0; 2.0; 4.0])

# -Inf are eliminated in the max-plus sparse array
C = mpsparse([1.0 2.0; -Inf 4.0])
@test typeof(C) == SpaMP{Float64,Int64}
@test (C.m == 2) && (C.n == 2)
@test nonzeros(C) == MP([1.0; 2.0; 4.0])

C = mpsparse(MP([1.0 2.0; -Inf 4.0]))
@test typeof(C) == SpaMP{Float64,Int64}
@test (C.m == 2) && (C.n == 2)
@test nonzeros(C) == MP([1.0; 2.0; 4.0])

# Max-Plus matrix of zeros is already a max-plus sparse array
D = mpzeros(Float64, 3,4)
@test typeof(D) == SpaMP{Float64,Int64}
@test (D.m == 3) && (D.n == 4)
@test nonzeros(D) == []

# Convert max-plus array to max-plus sparse array
E = mpsparse(mpones(Float64, 3,4))
@test typeof(E) == SpaMP{Float64,Int64}
@test (E.m == 3) && (E.n == 4)
@test nonzeros(E) == MP(zeros(Float64, 12))

# Convert non max-plus array to max-plus sparse array
F = mpsparse(ones(Float64, 3,4))
@test typeof(F) == SpaMP{Float64,Int64}
@test (F.m == 3) && (F.n == 4)
@test nonzeros(F) == ones(12) .+ MP(1.0)

#
G = mpsparse(MP([1.0 2.0 3.0; 1.0 2.0 3.0; 0.0 2.0 0.0]))
@test typeof(G) == SpaMP{Float64,Int64}
@test (G.m == 3) && (G.n == 3)
@test nonzeros(G) == MP([1.0; 1.0; 0.0; 2.0; 2.0; 2.0; 3.0; 3.0; 0.0])

# max-plus sparse array to max-plus dense array
Z = dense(mpzeros(Float64, 2,2))
@test typeof(Z) == ArrMP{Float64,2}
@test Z == [mp0 mp0; mp0 mp0]

# max-plus sparse array to max-plus dense array
Z = full(mpzeros(Float64, 2,2))
@test typeof(Z) == ArrMP{Float64,2}
@test Z == [mp0 mp0; mp0 mp0]

# max-plus sparse array to non max-plus dense array
Z = array(mpzeros(Float64, 2,2))
@test typeof(Z) == SparseMatrixCSC{Float64,Int64}
@test Z == [-Inf -Inf; -Inf -Inf]

# ==============================================================================
# Bug with Julia with SparseMatrixCSC and operator== which confused zero() and 0.0

A = MP(sparse([1, 2], [1, 2], [0.0, 0.0]))
B = mpzeros(Float64, 2,2)
@test A.nzval == MP([0.0, 0.0])
@test (A == B) == false

AA = sparse([1, 2], [1, 2], [mp0, mp0])
BB = mpzeros(Float64, 2,2)
@test AA.nzval == MP([-Inf, -Inf])
@test (AA == BB) == true

# ==============================================================================
# Max-plus operations with matrices
# ==============================================================================

A = MP([4 3; 7 -Inf])
@test A^0 == mpeye(Float64, 2,2)
@test A * A == A^2 == MP([10.0 7.0; 11.0 10.0])
@test A * A * A == A^3 == MP([14.0 13.0; 17.0 14.0])
# TODO A^-1

C = [MP(3.0) 4.0; 5.0 6.0]
D = MP([1.0 2; 3 4])
@test C + D == D + C == MP([3.0 4.0; 5.0 6.0]) == C

# Trace
# TODO shall we forbid trace of non squared matrices ?
@test mptrace([]) == mp0
@test mptrace(mpeye(Float64, 2,2)) == tr(mpeye(Float64, 2,2)) == 0.0
@test mptrace(mpeye(Float64, 2,5)) == 0.0
@test mptrace(mpzeros(Float64, 2,2)) == mptrace(full(mpzeros(Float64, 2,2))) == tr(mpzeros(Float64, 2,2)) == mp0
@test mptrace(mpzeros(Float64, 2,5)) == mptrace(full(mpzeros(Float64, 2,5))) == mp0
@test mptrace([1.0 2.0; 3.0 4.0]) == tr(MP([1.0 2.0; 3.0 4.0])) == MP(1.0) + MP(4.0) == MP(4.0)
@test mptrace(sparse([1.0 2.0; 3.0 4.0])) == mptrace(mpsparse([1.0 2.0; 3.0 4.0])) == MP(4.0)

# ==============================================================================
# Max-plus norm
# ==============================================================================

@test mpnorm(MP([1 20 2;30 400 4;4 50 10])) == MP(400 - 1) == MP(399)
@test mpnorm(mpsparse([1 20 2;30 400 4;4 50 10])) == MP(400 - 1) == MP(399)
@test mpnorm([mp0 1; 10 mp0]) == MP(10.0) - mp0 == mptop

# ==============================================================================
# Max-plus star
# ==============================================================================
@test mpstar(MP(1.0)) == mpstar(1.0) == mptop
@test mpstar(MP(-1.0)) == mpstar(-1.0) == mp1
@test mpstar(MP([1.0 2; 3 4])) == mpstar([1.0 2; 3 4]) == [mptop mptop; mptop mptop]
A=MP([-3.0 -2; -1 0]); B = mpstar(A)
@test B == mpeye(Float64, 2,2) + A
@test B == B + A^2
@test B == B + A^3

@test mpstar(mpeye(Float64, 2,2)) == mpeye(Float64, 2,2)

#TODO sparse
#sA = mpsparse(A); sB = mpstar(sA)
#@test B == mpeye(Float64, 2,2) + A
#@test B == B + A^2
#@test B == B + A^3

# ==============================================================================
# TODO @test C / C == MP([0.0 -2.0; 2.0 0.0])
# ==============================================================================

# ==============================================================================
# Max-plus display
# ==============================================================================
using Suppressor

mp_change_display(0)
result = @capture_out show(stdout, mp0)
@test result == "-Inf"
result = @capture_out show(stdout, mp1)
@test result == "0.0"
result = @capture_out show(stdout, MP(4.0))
@test result == "4.0"
result = @capture_out show(stdout, mpzero(Int64))
@test result == "-9223372036854775808"

mp_change_display(1)
result = @capture_out show(stdout, mp0)
@test result == "."
result = @capture_out show(stdout, mp1)
@test result == "0"
result = @capture_out show(stdout, MP(4.0))
@test result == "4"
result = @capture_out show(stdout, mpzero(Int64))
@test result == "."

mp_change_display(2)
result = @capture_out show(stdout, mp0)
@test result == "."
result = @capture_out show(stdout, mp1)
@test result == "e"
result = @capture_out show(stdout, MP(4.0))
@test result == "4"
result = @capture_out show(stdout, mpzero(Int64))
@test result == "."

mp_change_display(3)
result = @capture_out show(stdout, mp0)
@test result == "ε"
result = @capture_out show(stdout, mp1)
@test result == "0"
result = @capture_out show(stdout, MP(4))
@test result == "4"
result = @capture_out show(stdout, mpzero(Int64))
@test result == "ε"

mp_change_display(4)
result = @capture_out show(stdout, mp0)
@test result == "ε"
result = @capture_out show(stdout, mp1)
@test result == "e"
result = @capture_out show(stdout, MP(4.0))
@test result == "4"
result = @capture_out show(stdout, mpzero(Int64))
@test result == "ε"

A = MP([4.5 0.0; 7.0 -Inf])
result = @capture_out show(stdout, A)
@test result == "MP{Float64}[4.5 e; 7 ε]"
result = @capture_out LaTeX(stdout, A)
@test result == "\\left[\n\\begin{array}{*{20}c}\n4.5 & e \\\\\n7 & \\varepsilon \\\\\n\\end{array}\n\\right]\n"
mp_change_display(0)
result = @capture_out LaTeX(stdout, A)
@test result == "\\left[\n\\begin{array}{*{20}c}\n4.5 & 0 \\\\\n7 & -\\infty \\\\\n\\end{array}\n\\right]\n"


#=

    This is a port of the SPPL model random-sequence.pynb
    """
    In this notebook, we will implement and query the following probabilistic model of a random sequence in SPPL.

    For i=0, X[0]  is a standard normal.
    For i>0
        if  X[i−1] is negative, then  X[i] is a standard normal
        otherwise, let  W[i] be the outcome of a fair coin:
            if  W[i]=0 , then  X[i]=0.5∗(X[i−1])2+X[i−1]
            othewrise(sic!),  X[i] is a standard normal.

    """

    The SPPL model give the following (exact) probabilities
    (adjusted for Python's 0 base):

    * Pr(X[1]>0)		    = 0.50
    * Pr(X[1]>0 | X[2]>0)	= 0.647
    * Pr(X[1]>0 | X[3]>0)	= 0.544

    This model:

    We want the probability of X[1] > 0 given by:
    * No observation:
           t    mean: 0.5046   std: 0.5000

    * We observe X[2] > 0
           t    mean:0.6420    std:0.4797

    * We observe X[3] > 0
           t    mean: 0.5402   std: 0.4984

=#

using Turing, StatsPlots, Distributions, StatsBase
using CSV
include("jl_utils.jl")

@model function random_sequence()
    X = tzeros(3)
    W = tzeros(3)

    X[1] ~ Normal()
    for i in 2:3
        if X[i-1] > 0
            X[i] ~ Normal(0, 1)
            W[i] ~ Dirac(0.0)
        else
            W[i] ~ Bernoulli(0.5)
            if W[i] == 0
                X[i] ~ Dirac( 0.5*X[i-1]^2 + X[i-1])  # Dirac((X[i-1]^2 / 2 + X[i-1])
            else
                X[i] ~ Normal(0, 1)
            end
        end
    end

    # Observations:
    true ~ Dirac(X[2] > 0)
    # true ~ Dirac(X[3] > 0)

    # What is the probability that X[1] > 0?
    t ~ Dirac(X[1] > 0)
end

model = random_sequence()

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

# chns = sample(model, HMC(0.1,6), 1_000)
# chns = sample(model, NUTS(), 1_000)

display(chns[[:t]])
display(chns)


using Hydrogen
using AtomicLevels
using Test

# The Lyman and Balmer series radial dipole moments are given by
# Eq. (63.4)

lyman²(n) = 2^8*n^7*(n-1)^(2n-5)/((n+1)^(2n+5))

balmer_s²(n) = 2^17*n^7*(n^2-1)*(n-2)^(2n-6)/((n+2)^(2n+6))

function balmer_p²(n,ℓ)
    if ℓ == 0
        2^15*n^9*(n-2)^(2n-6)/(3*(n+2)^(2n+6))
    elseif ℓ == 2
        2^19*n^9*(n^2-1)*(n-2)^(2n-7)/(3*(n+2)^(2n+7))
    end
end

function lyman_balmer²(o, n, ℓ)
    # Eq. (BS63.5)
    n == o.n && return (3/2*n*√(n^2-max(ℓ,o.ℓ)^2))^2
    n = float(n)
    if o == o"1s"
        lyman²(n)
    elseif o == o"2s"
        balmer_s²(n)
    elseif o == o"2p"
        balmer_p²(n, ℓ)
    end
end

@testset "Hydrogen.jl" begin
    @testset "Factorial ratios" begin
        for a = 1:6
            for b = 1:6
                @test Hydrogen.factorial_ratio(a,b) == factorial(a)/factorial(b)
            end
        end
    end

    @testset "Radial dipole moments" begin
        # We cannot compare with Table 13 of Bethe and Salpeter 1977,
        # since it provides so few figures and some of the values
        # appear to be wrong. Instead, we compare with the specific
        # formulas for the Lyman and Balmer series.

        for (j,(o,ℓ′)) in enumerate([(o"1s",1), (o"2s",1), (o"2p",0), (o"2p",2)])
            for n′ = 1+ℓ′:8
                @test radial_dipole_moment(o, Orbital(n′, ℓ′))^2 ≈
                    lyman_balmer²(o, n′, ℓ′)
            end
        end
    end

    @testset "Orbitals" begin
        # Error conditions:
        @test_throws ArgumentError non_relativistic_orbital(0, 0)
        @test_throws ArgumentError non_relativistic_orbital(-1, 0)
        @test_throws ArgumentError non_relativistic_orbital(1, 1)
        @test_throws ArgumentError non_relativistic_orbital(2, -1)
        @test_throws MethodError non_relativistic_orbital(2.5, -1)
        # Check values:
        P10 = non_relativistic_orbital(1, 0)
        @test P10(0) ≈ 0.0
    end
end


#
#--------------------------------------#
export linearFEM
#--------------------------------------#
"""
 1D linear FEM operators in (0,1)
"""
function linearFEM(N)

	n  = N + 1
	dz = 1/N
    z  = linspace(0,1,n)

    dn = ones(n)
    dN = ones(N)

    A = Tridiagonal(-1*dN,2*dn ,-1*dN) / dz # == D'*B*D
    B = Tridiagonal(1dN/6,2dn/3,1dN/6) * dz
    C = Tridiagonal(-dN/2,0dn  ,dN/2)        # == B * D
    D = Tridiagonal( 0dN ,-1dn ,1dN)   / dz  # attempt second order?

    A[1,1]     = 0.5 * A[1,1]
    A[end,end] = 0.5 * A[end,end]

    B[1,1]     = 0.5 * B[1,1]
    B[end,end] = 0.5 * B[end,end]

    w = B * ones(n)

return z,w,A,B,C,D
end


"""
    MIPVerify(optimizer)

MIPVerify computes maximum allowable disturbance using mixed integer linear programming.

# Problem requirement
1. Network: any depth, ReLU activation
2. Input: hyperrectangle
3. Output: halfspace

# Return
`AdversarialResult`

# Method
MILP encoding. Use presolve to compute a tight node-wise bounds first.
Default `optimizer` is `GLPKSolverMIP()`.

# Property
Sound and complete.

# Reference

V. Tjeng, K. Xiao, and R. Tedrake,
["Evaluating Robustness of Neural Networks with Mixed Integer Programming,"
*ArXiv Preprint ArXiv:1711.07356*, 2017.](https://arxiv.org/abs/1711.07356)

[https://github.com/vtjeng/MIPVerify.jl](https://github.com/vtjeng/MIPVerify.jl)
"""
@with_kw struct MIPVerify{O<:AbstractMathProgSolver}
    optimizer::O
end

function solve(solver::MIPVerify, problem::Problem)
    model = Model(solver = solver.optimizer)
    neurons = init_neurons(model, problem.network)
    deltas = init_deltas(model, problem.network)
    add_complementary_set_constraint!(model, problem.output, last(neurons))
    bounds = get_bounds(problem)
    encode_mip_constraint!(model, problem.network, bounds, neurons, deltas)
    o = max_disturbance!(model, first(neurons) - problem.input.center)
    status = solve(model, suppress_warnings = true)
    if status == :Infeasible
        return AdversarialResult(:SAT)
    end
    if getvalue(o) >= minimum(problem.input.radius)
        return AdversarialResult(:SAT)
    else
        return AdversarialResult(:UNSAT, getvalue(o))
    end
end


# Solve Project Euler problem 96 (sudoku) to test ExactCovers.
#
# The idea here is to reduce sudoku to the exact cover problem and then use
# ExactCovers to solve that. Because we need to solve multiple sudokus, we
# costruct a general sudoku matrix and then, for each sudoku,
# 1. cover the parts of the matrix that correspond to givens,
# 2. find an exact cover for the partially covered matrix, and finally
# 3. uncover in reverse order.
# This is more efficient (and also easier to write down) than creating a
# specialized matrix for each sudoku.
#
# Each row (subset) in the sudoku matrix corresponds to placing a number into a
# square. The columns (elements) correspond to the individual constraints that
# each placement fulfills. These are:
# * The corresponding square is filled.
# * The corresponding row has the placed number.
# * The corresponding column has the placed number.
# * The corresponding box (3x3-subgrid) has the placed number.
#
# Because the sudoku rules dictate that each of these constraints must be
# satisfied exactly once, an exact cover for that matrix is a solution to
# sudoku. Preselecting (covering) rows (subsets) now makes sure that we are
# actually solving the given sudoku instance, instead of an empty grid.

"Creates the `CoverSet` that represents an empty sudoku grid."
function make_sudoku_matrix()
  box_for(row, column) = (div(row - 1, 3) + 1, div(column - 1, 3) + 1)

  constraints_for(row, column, number) = Set(Any[
    (:square_is_filled, row, column),
    (:row_has, row, number),
    (:column_has, column, number),
    (:box_has, box_for(row, column), number)
  ])

  CoverMatrix(Dict([
    (row, column, number) => constraints_for(row, column, number) for
      row in 1:9,
      column in 1:9,
      number in 1:9
  ]))
end

"""
Preselects the given placements from `sudoku` in `matrix`.
"""
function cover_givens!(sudoku, matrix)
  for row in 1:9, column in 1:9
    if sudoku[row, column] > 0
      cover_subset!(matrix, (row, column, sudoku[row, column]))
    end
  end
end

"""
Reverses the preselection from `sudoku`, so that `matrix` is restored to a
pristine sudoku matrix.
"""
function uncover_givens!(sudoku, matrix)
  for row in 9:-1:1, column in 9:-1:1
    if sudoku[row, column] > 0
      uncover_subset!(matrix, (row, column, sudoku[row, column]))
    end
  end
end

"""
Solves `sudoku`, assuming `matrix` is a sudoku matrix representing an empty
grid. `matrix` will be restored to that state when the call returns.
"""
function solve(sudoku, matrix)
  result = copy(sudoku)
  cover_givens!(sudoku, matrix)
  for entry in find_exact_cover(matrix)
    row, column, number = entry
    result[row, column] = number
  end
  uncover_givens!(sudoku, matrix)

  result
end

"""
Reads the source file containing the sudoku instances to solve.
"""
sudoku_producer() = @task let
  file = open(joinpath(dirname(@__FILE__), "data", "sudokus.txt"))
  while !eof(file)
    readline(file)
    sudoku = Array(Int8, 9, 9)
    for row in 1:9
        sudoku[row, :] = [c - Int8('0') for c in read(file, Int8, 9)]
        readline(file)
    end

    produce(sudoku)
  end
end

# Actually solve Project Euler problem 96. This means solving the sudokus,
# extracting the first three boxes of each, interpreting them as 3-digit
# numbers and adding them up.
let
  result = 0

  matrix = make_sudoku_matrix()
  for sudoku in sudoku_producer()
    sudoku = solve(sudoku, matrix)
    result += 100sudoku[1, 1] + 10sudoku[1, 2] + sudoku[1, 3]
  end

  @test result == 24702
end


#    Problem : GROUP A
#    *********
#    Another function with nontrivial groups and repetitious elements.
#
#    Original SIF Source:
#    N. Gould, private communication.
#
#    Implementation translated from Source:
#    http://eprints.tsu.ge/234/14/Tests%20collection-K-F.pdf
#
#    COSINE.SIF classification OUR2-AN-V-0
#
#    Number of variables (variable)

f = (x) -> begin
    fx = 0.0
    for i in 1:lastindex(x)-1
        α = -0.5x[i+1]+x[i]^2
        fx += cos(α)
    end
    return fx
end

g! = (g, x) -> begin
    for i in 1:lastindex(x)-1
        α = -0.5x[i+1]+x[i]^2
        g[i] -= 2.0sin(α)x[i]
        g[i+1] += 0.5sin(α)
    end
    return g
end

fg! = (g, x) -> begin
    fx = 0.0
    for i in 1:lastindex(x)-1
        α = -0.5x[i+1]+x[i]^2
        fx += cos(α)
        g[i] -= 2.0sin(α)*x[i]
        g[i+1] += 0.5sin(α)
    end
    return fx, g
end

init = (n::Int=10000) -> begin
    n < 2 && @warn("COSINE: number of variables must be ≥ 2")
    return n, ones(n)
end

TestSet["COSINE"] = UncProgram("COSINE", f, g!, fg!, init)


export rref, rref!, solve, solve_augmented

"""
`swap_rows!(A,i,j)` swaps rows `i` and `j` in the matrix `A`.
"""
function swap_rows!(A::Array{T,2}, i::Int, j::Int) where T
  if i==j
    return nothing
  end
  A[ [i,j], :] = A[ [j,i], :]
  return nothing
end

"""
`add_row_to_row!(A,i,j)` adds row `i` to row `j` in the matrix `A`.
"""
function add_row_to_row!(A::Array{T,2},i::Int,j::Int) where T<:Number
  A[j,:] += A[i,:]
  return nothing
end

# The following functions developed by Tara Abrishami

"""
`rref!(A)` overwrites `A` with its row reduced echelon form.
"""
function rref!(A::Array{GF2,2})
  r, c = size(A)
  s = 0
  for x in 1:r
    b = false
    while !b && x + s <= c
      if A[x, x+s] == 1
        break
      elseif A[x, x + s] == 0
        for y in x:r
          if A[y, x + s] == 1
            swap_rows!(A, y, x)
            b = true
            break
          end
        end
      end
      if !b
        s = s + 1
      end
    end
    for m in 1:r
      if x + s <= c && m != x && A[m, x+s] == 1
        add_row_to_row!(A, x, m)
      end
    end
  end
end

"""
`rref(A)` returns the row reduced echelon form of `A`.
"""
function rref(A::Array{GF2,2})
  AA = copy(A)
  rref!(AA)
  return AA
end

"""
`solve(A,b)` returns a solution `x` to the linear system
`A*x == b` or throws an error if no solution can be found.
"""
function solve(A::Array{GF2, 2}, b::Array{GF2, 1})
  r, c = size(A)
  if r != size(b)[1]
    error("Dimensionally incorrect input")
  end
  C = [A b]
  return solve_augmented(C)
end

# returns a single solution to the system with matrix C1
function solve_augmented(C1::Array{GF2, 2})
  r, c = size(C1)
  D = copy(C1)
  rref!(D)
  x = 0
  for a in 1:r
   in = true
   for b in 1:c-1
     if D[a, b] != 0
       in = false
     end
   end
   if in && D[a, c] != 0
     error("Inconsistent system")
   end
  end
  ret = zeros(GF2, c-1)
  for p in 1:r
    if D[p, c] == 1
      for n in 1:c-1
        if D[p, n] == 1
          ret[n] = 1
          break
        end
      end
    end
  end
  return ret
end

import Base.inv

function inv(A::Array{GF2,2})
  n,m = size(A)
  if n!= m
    error("Cannot invert a matrix that isn't square.")
  end
  if det(A)==0
    error("Cannot invert a singular matrix.")
  end
  In = Matrix{GF2}(I,n,n)

  AB = [A  In]
  rref!(AB)

  B = AB[:,n+1:end]
  return B
end

function LinearAlgebra.nullspace(A::Array{GF2, 2})
  r, c = size(A)
  M = rref(A)
  ret = zeros(GF2, c)
  s = 0
  x = 1
  left = false
  while x <= c
    if x > r
      left = true
      break
    end
    if x + s > c
      break
    end
    if M[x, x+s] == 1
      x = x + 1
      continue
    else
      p = zeros(GF2, c)
      p[x + s] = 1
      for t in 1:r
        if M[t, x + s] == 1
          for q in 1:c
            if M[t, q] == 1
              p[q] = 1
              break
            end
          end
        end
      end
      s = s + 1
      ret = hcat(ret, p)
    end
  end
  if left
    while x + s <= c
      p = zeros(GF2, c)
      p[x + s] = 1
      for t in 1:r
        if M[t, x + s] == 1
          for q in 1:c
            if M[t, q] == 1
              p[q] = 1
              break
            end
          end
        end
      end
      s = s + 1
      ret = hcat(ret, p)
    end
  end
  ret = ret[:, 1:size(ret,2) .!= 1]
  return ret
end

export solve_all
"""
`solve_all(A,b)` returns a solution to `A*x==b` together with
a basis for the nullspace of `A`.
"""
function solve_all(A::Array{GF2, 2}, b::Array{GF2, 1})
  return solve(A, b), nullspace(A)
end

export nullity
"""
`nullity(A)` returns the dimension of the nullspace of `A`.
"""
function nullity(A::Array{GF2,2})
  NS = nullspace(A)
  (x,n) = size(NS)
  return n
end

function LinearAlgebra.rank(A::Array{GF2,2})
  r,c = size(A)
  n = nullity(A)
  return c-n
end


# # [How to port rules generated by SparseADRules to Zygote](@id lowrank_svd)
#
# In this demo we'll show how to insert SparseADRules's sparse gradient implementation to boost Zygote's gradient in lowrank svd.
using Zygote
using SparseArrays, LinearAlgebra
using SparseADRules:low_rank_svd
import FiniteDifferences
using BenchmarkTools
using ChainRulesCore, ChainRulesTestUtils
include(joinpath(@__DIR__, "..", "test", "testutils.jl"))

function svd_loss(A::AbstractSparseMatrix{T}, z::Vector{T}, r::Int) where T
    U, S, Vt = low_rank_svd(A, r)
    residual_mat = U * Diagonal(S) * Vt - U * Diagonal(z) * Vt
    return tr(residual_mat'*residual_mat) # square of Frobenius norm
end

# generate test data
r = 10
A = sprand(100, r, 0.2) * Diagonal(rand(r)) * sprand(r, 50, 0.2)
z = rand(r)

@btime grad_A = Zygote.gradient(A -> svd_loss(A, z, r), $A)
grad_A = Zygote.gradient(A -> svd_loss(A, z, r), A)[1]

grad_fA = FiniteDifferences.grad(FiniteDifferences.central_fdm(5, 1), A -> svd_loss(A, z, r), A)[1]

using Test
@testset "test AD on lowrank svd" begin
    @test grad_A ≈ grad_fA
end


# ---
# title: 224. Basic Calculator
# id: problem224
# author: Qling
# date: 2021-03-10
# difficulty: Hard
# categories: Math, Stack
# link: <https://leetcode.com/problems/basic-calculator/description/>
# hidden: true
# ---
#
# Implement a basic calculator to evaluate a simple expression string.
#
# The expression string may contain open `(` and closing parentheses `)`, the
# plus `+` or minus sign `-`, **non-negative** integers and empty spaces ` `.
#
# **Example 1:**
#
#
#
#     Input: "1 + 1"
#     Output: 2
#
#
# **Example 2:**
#
#
#
#     Input: " 2-1 + 2 "
#     Output: 3
#
# **Example 3:**
#
#
#
#     Input: "(1+(4+5+2)-3)+(6+8)"
#     Output: 23
#
# **Note:**
#
#   * You may assume that the given expression is always valid.
#   * **Do not** use the `eval` built-in library function.
#
#
## @lc code=start
using LeetCode

function calculate(s::String)::Int
    res, num, sign, stk = 0, 0, 1, []
    for c in s
        if isdigit(c)
            num = 10 * num + parse(Int, c)
        elseif c in ['-', '+']
            res += num * sign
            num = 0
            sign = (c == '-') ? -1 : 1
        elseif c == '('
            push!(stk, res)
            push!(stk, sign)
            res, sign = 0, 1
        elseif c == ')'
            res += num * sign
            res *= pop!(stk)
            res += pop!(stk)
            num = 0
        end
    end

    return res + num * sign
end
## @lc code=end


using Plots
using BenchmarkTools

function vfsolvex(vnew, kgrid, tolerance, imax, σ)
    β = 0.9

    v = vnew .+ 2*tolerance
    cartesianindex = Array{CartesianIndex{2}, length(v)}
    i = 1

    c = kgrid .- kgrid'
    c[c .< 0] .= 0
    u = (c .^ (1-σ) .- 1) ./ (1-σ)
    u[kgrid .- kgrid' .< 0] .= -Inf

    while maximum(abs.(v - vnew)) > tolerance && i <= imax
        v = vnew;

        (vnew, cartesianindex) = findmax(u .+ β*v', dims = 2);
        i += 1;
    end
    kprimeindex = getindex.(cartesianindex, 2)
    return (v = vnew, kprime = kgrid[kprimeindex], kprimeindex = kprimeindex)
end

kupper = 5
klower = 0.01
n = 1000
kgrid = collect(range(klower, stop = kupper, length = n))
(v, kprime, kprimeindex) = vfsolvex(zeros(n), kgrid, 0.001, 1000, 1.5);

scatter(kgrid, v, label = "v")
scatter(kgrid, kprime, label = "k'")

function policy(x)
    e=getindex(findall(kgrid .== x),1)
    return kprime[e]
end

T = 100
kpath = zeros(T)
k = kgrid[1000]
for i in 1:T
    k = policy(k)
    kpath[i] = k
end
time = collect(range(1, T, length = T))
scatter(time, kpath, label = "kpath")


ENV["GKSwstype"] = "100" #src

# # Getting Started with Data Science in Julia
#
# > [A]lways bite the bullet with regard to understanding the basics of the data first before you do anything else...
# >
# > \- Caitlin Smallwood, Vice President of Science and Algorithms at Netflix

println("Hello, Data Science World!")

# # What does your data look like?
#
# Before you can solve complex problems with data, you should have a firm grasp on what your data looks like.  Are your variables continuous or categorical?  What do the distributions look like?  Are there missing observations?  Are variables correlated?  All of these questions arise in the data science workflow, so it's helpful to know the answers from the start of your analysis.

# Let's start with loading the [**`Statistics`**](https://docs.julialang.org/en/latest/stdlib/Statistics/) package, which provides basic statistical operations like means, variances, etc.

using Statistics

# You can examine the items that a package exports with the `names` function.

names(Statistics)

# To start, let's try some of the functions from **`Statistics`** on simulated data.

x = randn(100, 3)

mean(x, dims=1)  # calculate over first dimension (column means)

#-

cor(x)  # correlation matrix

# For a more realistic example, let's load some data from the **`RDatasets`** package, which has a large collection of datasets that get loaded as a `DataFrame`.

# The [`"iris"` dataset](https://en.wikipedia.org/wiki/Iris_flower_data_set) is a collection of measurements for three different species of the iris flower: Iris Setosa, Iris Virginica, and Iris Versicolor (shown below).  The measurements consist of length and width of the petal and sepal (part underneath the flower).

#

using RDatasets

iris = dataset("datasets", "iris")

# DataFrames are discussed in another course module.  For now, we'll just use the fact that data vectors from the DataFrame can be selected with `mydf.mycol`:

iris.SepalLength

# What is the average sepal width across all three species?

mean(iris.SepalWidth)

# Maximum and minimum?

@show minimum(iris.SepalWidth)
@show maximum(iris.SepalWidth)
extrema(iris.SepalWidth)

# Is petal width correlated with petal length?

cor(iris.PetalWidth, iris.PetalLength)

# While we could examine each column separately, a much quicker way to summarize our variables is with the `describe` function:

describe(iris)

# # Random Sampling
#
#
#
# [Random Sampling](https://en.wikipedia.org/wiki/Sampling_(statistics)) plays an integral part in many data science tasks, such as:
#
# - Splitting data into multiple datasets for [cross validation](https://en.wikipedia.org/wiki/Cross-validation_(statistics)).
# - Subsampling a large dataset to something more manageable.
# - Running statistical simulations.
# - [Statistical Bootstrap](https://en.wikipedia.org/wiki/Bootstrapping_(statistics))
#
# The **`StatsBase`** package has the `sample` function to factilitate sampling [with or without replacement](https://en.wikipedia.org/wiki/Sampling_(statistics)#Replacement_of_selected_units).
# Imagine you had a bag with 20 numbers in it.
# Sampling with replacement means that you put the number you drew back into the bag before the next draw.
# Just like rolling a dice, each draw is _independent_ and a given number might get drawn again.
# On the other hand, if you were to _not_ put the drawn number back in the bag, that would be representative of sampling without replacement.
# Each draw is _dependent_ upon all previous draws as those numbers are no longer available for selection.
# For bootstrapping and statistical simulations, it's typical to use sampling with replacement.
# While the two behave nearly the same if the sample is a very small fraction of the overall population, it becomes important to sample without replacement if the sample is large or if using uneven importance weights.

using StatsBase

y1 = sample(1:20, 20, replace=true)  # replaces units after being selected

#-

y2 = sample(1:20, 20, replace=false)  # DOES NOT replace units after being selected

# Next we'll use the [`countmap`](http://juliastats.github.io/StatsBase.jl/latest/search.html?q=countmap) function to get the count of each unique value in our samples.  Notice that when we do sampling *without* replacement, the values will only appear one time.  When sampling *with* replacement, any given selected item is then placed back into the pool of possible items to select from.

countmap(y1)

#-

countmap(y2)

# This is just a small sample (pun intended) of the features in **`StatsBase`**.  For a more complete look of what is possible, check out the [documentation](http://juliastats.github.io/StatsBase.jl/latest/index.html).

# # Parametric Distributions
#
# The [**`Distributions`**](https://github.com/JuliaStats/Distributions.jl) package provides an interface for working with probability distributions.  The full documentation is [here](https://juliastats.github.io/Distributions.jl/stable/).

# Here we'll also load the **`StatsPlots`** package (more on this in the next course module) to visualize the probability distributions.

using Distributions, StatsPlots

plot(Normal(), label = "Normal(0, 1)")

#-

plot!(Gamma(5, 1), label = "Gamma(5, 1)")

# The package defines a number of functions that together create a consistent "grammar" for discussing distributions:
#
# - Probability density function: `pdf`
# - Cumulative distribution function: `cdf`
# - The inverse cumulative distribution function with the standard `quantile` statistical function
# - etc.

d = Normal()

pdf(d, 0), cdf(d, 0), mean(d), var(d), quantile(d, .5), mode(d)

#-

d = Gamma(5, 1)

pdf(d, 0), cdf(d, 0), mean(d), var(d), quantile(d, .5), mode(d)

# As an example, let's write a function that uses [Newton's Method](https://en.wikipedia.org/wiki/Newton%27s_method) that can find a given quantile for any continuous univariate distribution.  Newton's method attempts to find the root for a function $f$ by performing iterations of the form:
#
# $$\theta^{(t)} = \theta^{(t-1)} - \frac{f(\theta^{(t-1)})}{f'(\theta^{(t-1)})}.$$
#
# For quantiles, we want to find the root of the function $F(\theta) - q$ where $F$ is the cumulative density function of the distribtuion and $q \in (0, 1)$.  Using **`Distributions`**, this looks something like

function myquantile(d::Distribution, q::Number)
    θ = mean(d)
    for i in 1:20
        θ -= (cdf(d, θ) - q) / pdf(d, θ)  # θ = θ - (F(θ) - q) / F'(θ)
    end
    θ
end

# Does our `myquantile` function work as expected?  Let's try out our function on several distributions.

d = Normal()
myquantile(d, .5), quantile(d, .5)

#-

d = Gamma(4,3)
myquantile(d, .7), quantile(d, .7)

# The above example shows off the power of generic functions.  Instead of hard-coding the distribution (as would be necessary in R), we can write functions in terms of an arbitrary distribution (without extra effort).  This gives us a lot of flexibility for tasks such as writing [Gibbs Samplers](https://en.wikipedia.org/wiki/Gibbs_sampling) that can swap out distributions with ease.


#script for the 2D Linear elasticity problem
#plate with a hole under tension at infinity benchmark
include("../src/Solver2D.jl")
include("../src/SplinePlotting.jl")

#Material properties
Emod = 1e5
nu = 0.3
Cmat = Emod/(1-nu^2)*[1  nu  0; nu  1  0; 0  0  (1-nu)/2]
material = Material2D(Emod, nu, Cmat)

#define the domain geometry
R = 1.
L = 4.
center = [0., 0.]
sang = π/2
eang = π

bottom_crv =  nrbcirc(R, center, sang, eang)
bottom_crv = nrbreverse(bottom_crv)
right_seg = nrbline([0, R], [0, L])
left_seg = nrbline([-R, 0], [-L, 0])
up_seg = nrbline([-L, 0], [-L, L], [0, L])
nrb = nrbcoons(bottom_crv, up_seg, left_seg, right_seg)

#degree elevate and refine
degP = [3, 3]
num_subdiv = [10, 20]
nrb = nrbdegelev(nrb, degP.+1-nrb.order)
_, _, new_knots = kntrefine(nrb.knots, num_subdiv, degP, degP.-1)
nrb = nrbkntins(nrb, new_knots)
# Plots.surface(reuse = false)
# plt = nrbctrlplot(nrb)
# display(plt)

#symmetry Dirichlet B.C., u_y = 0 for y=0 and u_x=0 for x=0
u_bound_dir_symy0(x,y) = [undef, 0]
u_bound_dir_symx0(x,y) = [0, undef]
# traction at infinity
tx = 10

#define the exact solution
function exact_disp(x,y)
    cart2pol(x, y) = hypot(x, y), atan(y, x)
    r, th = cart2pol(x, y)
    ux = (1+nu)/Emod*tx*(1/(1+nu)*r*cos(th)+2*R^2/((1+nu)*r)*cos(th)+R^2/(2*r)*cos(3*th)-R^4/(2*r^3)*cos(3*th))
    uy = (1+nu)/Emod*tx*(-nu/(1+nu)*r*sin(th)-(1-nu)*R^2/((1+nu)*r)*sin(th)+
            R^2/(2*r)*sin(3*th)-R^4/(2*r^3)*sin(3*th))
    return [ux, uy]
end

function exact_stress(x,y)
    cart2pol(x, y) = hypot(x, y), atan(y, x)
    r, th = cart2pol(x, y)
    sigma_rr = tx/2*(1-R^2/r^2)+tx/2*(1-4*R^2/r^2+3*R^4/r^4)*cos(2*th)
    sigma_tt = tx/2*(1+R^2/r^2)-tx/2*(1+3*R^4/r^4)*cos(2*th)
    sigma_rt = -tx/2*(1+2*R^2/r^2-3*R^4/r^4)*sin(2*th)

    A = [cos(th)^2 sin(th)^2 2*sin(th)*cos(th); sin(th)^2 cos(th)^2 -2*sin(th)*cos(th);
        -sin(th)*cos(th) sin(th)*cos(th) cos(th)^2-sin(th)^2]
    stress = A\[sigma_rr;sigma_tt;sigma_rt]
    return stress
end
"""
Define the Neumann boundary condition derived from the exact stresses
"""
function u_bound_neu(x,y)
    tol_eq=1e-10
    ex_stress = exact_stress(x,y)
    if abs(x+L)<tol_eq
        #on the boundary x=-L the outer normal is (-1, 0)
        nx = -1
        ny = 0
    elseif abs(y-L)<tol_eq
        #on the boundary y=L, the outer normal is (0, 1)
        nx = 0
        ny = 1
    else
        error("Point not on the Neumann boundary")
    end
    tx = nx*ex_stress[1]+ny*ex_stress[3]
    ty = nx*ex_stress[3]+ny*ex_stress[2]
    return tx, ty
end

bound_left = Boundary2D("Dirichlet", "Left", u_bound_dir_symy0)
bound_right = Boundary2D("Dirichlet", "Right", u_bound_dir_symx0)
bound_up = Boundary2D("Neumann", "Up", u_bound_neu)
bound_all = [bound_left, bound_right, bound_up]

#generate the NURBS mesh
IGAmesh = genMesh(nrb)

gauss_rule = [genGaussLegendre(degP[1]+1), genGaussLegendre(degP[2]+1)]
stiff = assemble_stiff_elast2D(IGAmesh, Cmat, gauss_rule)
#Assume no body forces
rhs = zeros(2*IGAmesh.numBasis)
bcdof_all, elem_all = classifyBoundary2D(IGAmesh)
lhs, rhs = applyBCnurbsElast(IGAmesh, bound_all, stiff, rhs, bcdof_all, elem_all, gauss_rule)
println("Solving linear system with $(length(rhs)) DOFs")
@time sol0 = lhs\rhs
@time plotSolElast(IGAmesh, Cmat, sol0, "PlateHoleC0")
@time plotSolErrorElast(IGAmesh, Cmat, sol0, exact_disp, exact_stress, "PlateHoleC0Err")
@time relL2Err, relH1Err = compErrorNormElast(IGAmesh, Cmat, sol0, exact_disp, exact_stress, gauss_rule)
println("Relative L2-norm error is $relL2Err")
println("Relative energy-norm error is $relH1Err")


"""
    Newton{M}

`Newton` dispatches to the Newton direction for `LineSearch` algorithm and uses the Hessian
in `TrustRegion` models.

# Fields
- `modify::M`: the Hessian modification method to enforce positive definiteness
"""
struct Newton{M}
    modify::M
end

Newton() = Newton(MultipleIdentity())

order(::Newton) = SecondOrder()

function approx_hessian(n::Newton, state)
    ∇²f = state.∇²f
    isposdef(∇²f) && return ∇²f
    return n.modify(∇²f)
end

function direction(n::Newton, state)
    ∇f = state.∇f
    ∇²f = approx_hessian(n::Newton, state)
    return -inv(∇²f) * ∇f
end

"""
    MultipleIdentity{B, S}

Modify the Hessian by adding some multiples, τ, of the identity matrix, as described in
Nocedal and Wright's Numerical Optimization.

# Fields
- `β::B`: constant to compute appropriate τ
- `scale::S`: scale factor to expand τ
"""
struct MultipleIdentity{B, S}
    β::B
    scale::S

    function MultipleIdentity(β, ρ)
        β > 0 || error("β must be positive")
        ρ > 1 || error("ρ must be larger than 1")
        new{typeof(β), typeof(ρ)}(β, ρ)
    end
end

"""
    MultipleIdentity(; beta=1e-3, scale=2.0)

Initiate `MultipleIdentity` method to modify the Hessian.
"""
MultipleIdentity(; beta=1e-3, scale=2.0) = MultipleIdentity(beta, scale)

function (mi::MultipleIdentity)(∇²f::Matrix{T}) where T
    β = T(mi.β)
    scale = T(mi.scale)
    τ = -minimum(diag(∇²f)) + β
    h = similar(∇²f)
    while true
        h .= ∇²f + τ*I
        isposdef(h) && return h
        τ = max(scale*τ, β)
    end
end


#
# Quadratic fit
#

struct Quadratic
  a :: Float64
  b :: Float64
  c :: Float64
  R :: Float64
  x :: Vector{Float64}
  y :: Vector{Float64}
  ypred :: Vector{Float64}
  residues :: Vector{Float64}
end

"""
`fitquad(x,y)` or `fitquadratic(x,y)`

Obtains the quadratic fit: ``y = a*x^2 + b*x + c``

Optional lower and upper bounds for a and b can be provided using, for example:

```
fitquad(x,y, lower(b=0.), upper(a=5.,b=7.) )
```
and the intercept `c` can be fixed with

```
fitquad(x,y, c=3.)
```

# Examples
```jldoctest
julia>  x = sort(rand(10)); y = x.^2 .+ rand(10);

julia> fit = fitquad(x,y)

 ------------------- Quadratic Fit -------------

 Equation: y = ax^2 + bx + c

 With: a = 1.9829681649993036
       b = -1.24215737650827
       c = 0.9410816080128867

 Pearson correlation coefficient, R = 0.8452759310204063
 Average square residue = 0.039620067833833005

 Predicted Y: ypred = [0.778952191090992, 0.7759243614999851...
 residues = [0.0550252612868799, -0.15207394277809727...

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

```
"""
function fitquadratic(X :: AbstractArray{<:Real}, Y :: AbstractArray{<:Real};
                      l :: lower = lower(), u :: upper = upper(), c = nothing,
                      options :: Options = Options())
  # Check data
  X, Y = checkdata(X,Y,options)
  # Set bounds
  vars = [ VarType(:a,Number,1),
           VarType(:b,Number,1),
           VarType(:c,Nothing,1) ]
  lower, upper = setbounds(vars,l,u)
  if c == nothing
    # Set model
    @. model(x,p) = p[1]*x^2 + p[2]*x + p[3]
    # Fit
    fit = find_best_fit(model, X, Y, length(vars), options, lower, upper)
    # Analyze results and return
    R = pearson(X,Y,model,fit)
    x, y, ypred = finexy(X,options.fine,model,fit)
    return Quadratic(fit.param...,R,x,y,ypred,fit.resid)
  else
    lower = lower[1:length(vars)-1]
    upper = upper[1:length(vars)-1]
    # Set model
    @. model_const(x,p) = p[1]*x^2 + p[2]*x + c
    # Fit
    fit = find_best_fit(model_const, X, Y, length(vars)-1, options, lower, upper)
    # Analyze results and return
    R = pearson(X,Y,model_const,fit)
    x, y, ypred = finexy(X,options.fine,model_const,fit)
    return Quadratic(fit.param...,c,R,x,y,ypred,fit.resid)
  end
end
fitquad = fitquadratic

function Base.show( io :: IO, fit :: Quadratic )
  println("")
  println(" ------------------- Quadratic Fit ------------- ")
  println("")
  println(" Equation: y = ax^2 + bx + c ")
  println("")
  println(" With: a = ", fit.a)
  println("       b = ", fit.b)
  println("       c = ", fit.c)
  println("")
  println(" Pearson correlation coefficient, R = ", fit.R)
  println(" Average square residue = ", mean(fit.residues.^2))
  println("")
  println(" Predicted Y: ypred = [",fit.ypred[1],", ",fit.ypred[2],"...")
  println(" residues = [", fit.residues[1],", ",fit.residues[2],"...")
  println("")
  println(" ----------------------------------------------- ")
end

export fitquad, fitquadratic


# v0.6

using Distributions

function eqdist(data::Vector{T}, α::Float64=0.05)::Bool where T <: Real
    if ! (0 ≤ α ≤ 1); error("α must be in [0, 1]") end
    exp = mean(data)
    chisqval = sum((x - exp) ^ 2 for x in data) / exp
    pval = ccdf(Chisq(2), chisqval)
    return pval > α
end

data1 = [199809, 200665, 199607, 200270, 199649]
data2 = [522573, 244456, 139979,  71531,  21461]

for data in (data1, data2)
    println("Data:\n$data")
    println("Hypothesis test: the original population is ", (eqdist(data) ? "" : "not "), "uniform.\n")
end


abstract type AbstractLQR <: AbstractController end

"""
Infinite-horizon continuous-time linear quadratic regulator (LQR).
"""
struct LQR <: AbstractLQR
    A
    B
    Q
    R
end

function RunningCost(lqr::LQR)
    @unpack Q, R = lqr
    return function (x, u)
        x'*Q*x + u'*R*u
    end
end

function ARE_solution(lqr::LQR)
    @unpack A, B, Q, R = lqr
    P, _, _ = MatrixEquations.arec(A, B*inv(R)*B', Q)
    P
end

function optimal_gain(lqr::LQR)
    @unpack B, R = lqr
    P = ARE_solution(lqr)
    K = inv(R) * B' * P
end

"""
Minimise J = ∫ (x' Q x + u' R u) from 0 to ∞
"""
function Command(lqr::LQR)
    K = optimal_gain(lqr)
    return function (x)
        -K*x
    end
end

function solutions(lqr::LQR)
    P = ARE_solution(lqr)
    K = optimal_gain(lqr)
    optimal_controller = Command(lqr)
    P, K, optimal_controller
end


module Day5
export day5

function parseLine(line)
    for r in ("F"=>"0", "B"=>"1", "L"=>"0", "R"=>"1")
        line = replace(line, r)
    end
    parse(Int, line; base = 2)
end

function readInput(inputfile="inputs/input05.txt")
    [parseLine(line) for line in eachline(inputfile)]
end

function day5(inputfile="inputs/input05.txt")
    values = readInput(inputfile)
    (maximum(values), day5Part2(values))
end

function day5Part2(values)
    sort!(values)
    for (a,b) in zip(values, @view values[2:end])
        if b-a == 2
            return a+1
        end
    end
end

end #module


##### MEAN-VARIANCE OPTIMIZATION
#This is not supposed to produce great results, since mean and covariance
#is supposed to be very exact before good weights are found
using JuMP
using Gurobi
using StatsBase
using DataFrames
using CSV
include("SupportFunction.jl")
include("DataLoad.jl")
println("Leeeeroooy Jenkins")
#Esben's path
path = "$(homedir())/Documents/GitHub/Thesis/Data/MonthlyReturns"

mainData = loadIndexDataNoDurLOGReturn(path)

mainDataArr = Array(mainData)

startRow = 1
endRow = 100
trainingObservations = 100
mainXarr = mainDataArr[startRow:endRow,1:10]
indexes = size(mainXarr)[2]

#### WITH RISK AVERSION

nRows = size(mainDataArr)[1]
nCols = size(mainDataArr)[2]

##LOOP TO TEST IT
portfolioMVTotal = []
portfolioMVTotal = push!(portfolioMVTotal, 1)
portfolioMVIndividual = []
portfolioMVIndividual = push!(portfolioMVIndividual, 0)
wMVtracker = zeros((nRows-trainingObservations-1),10)

w1N = repeat([0.1], outer = 10)
portfolio1NTotal = []
portfolio1NTotal = push!(portfolio1NTotal, 1)
portfolio1NIndividual = []
portfolio1NIndividual = push!(portfolio1NIndividual, 0)

for rStart = 1:(nRows-trainingObservations-1)
    startRow = rStart
    endRow = (rStart-1)+trainingObservations
    mainXarr = mainDataArr[startRow:endRow,1:10]
    indexes = size(mainXarr)[2]

    #creating the covariance matrix of the training data
    Sigma = cov(mainXarr)

    #creating the mean of the returns for SAA (forecasts should be used instead)
    Mu = mean(mainXarr, 1)'

    #setting risk aversion.
    gamma = 10

    M = JuMP.Model(solver = GurobiSolver(OutputFlag = 0))
    @variables M begin
            w[1:indexes]
    end
    @objective(M,Min, gamma*w'*Sigma*w-(Mu'*w)[1]) #efficient frontier
    @constraint(M, 0 .<= w)
    @constraint(M, sum(w[i] for i=1:indexes) == 1)

    solve(M)
    wStar = getvalue(w)
    for i = 1:length(wStar)
        if wStar[i] >=0.5
            println("Index $i is chosen")
        end
    end
    wMVtracker[rStart,:] = wStar

    #creating the forecastRow in arithmetic returns (not log returns)
    #then we can multiply and sum across the row
    forecastRow = exp(mainDataArr[(endRow+1):(endRow+1),1:10])

    periodReturn = Array(forecastRow*wStar)[1]
    currentLength = length(portfolioMVTotal)
    portfolioMVTotal = push!(portfolioMVTotal, portfolioMVTotal[currentLength]*periodReturn)
    portfolioMVIndividual = push!(portfolioMVIndividual, periodReturn)

    period1NReturn = Array(forecastRow*w1N)[1]
    current1NLength = length(portfolio1NTotal)
    portfolio1NTotal = push!(portfolio1NTotal, portfolio1NTotal[current1NLength]*period1NReturn)
    portfolio1NIndividual = push!(portfolio1NIndividual, period1NReturn)
end

#Checking the total return of the portfolio by checking last index
portfolioMVTotal[length(portfolioMVTotal)]
portfolio1NTotal[length(portfolio1NTotal)]

portfolioMVTotal = convert(Array{Float64,1}, portfolioMVTotal)
portfolioMVIndividual = convert(Array{Float64,1}, portfolioMVIndividual)
(mean(portfolioMVIndividual)-1)*100
std(portfolioMVIndividual)
SharpeRatioMV = (mean(portfolioMVIndividual)-1)/std(portfolioMVIndividual)

portfolio1NTotal = convert(Array{Float64,1}, portfolio1NTotal)
portfolio1NIndividual = convert(Array{Float64,1}, portfolio1NIndividual)
(mean(portfolio1NIndividual)-1)*100
std(portfolio1NIndividual)
SharpeRatio1N = (mean(portfolio1NIndividual)-1)/std(portfolio1NIndividual)

combinedPortfolios = hcat(portfolioMVTotal, portfolio1NTotal, portfolioMVIndividual, portfolio1NIndividual)
writedlm("MV100obsG10versus1N.csv", combinedPortfolios,",")

writedlm("MV100obsPortfolioWeightsG10.csv", wMVtracker,",")

function meanVariancePPDOptimization(fullX, startRow, endRow, gammaValue, meanForecasts)
    mainXarr = fullX[startRow:endRow, 1:10]
    w1N = repeat([0.1], outer = 10)

    #creating the covariance matrix of the training data
    Sigma = cov(mainXarr)

    #using the PPD mean forecasts
    Mu = meanForecasts

    #setting risk aversion.
    gamma = gammaValue

    M = JuMP.Model(solver = GurobiSolver(OutputFlag = 0))
    @variables M begin
            w[1:indexes]
    end
    @objective(M,Min, gamma*w'*Sigma*w-(Mu'*w)[1]) #efficient frontier
    @constraint(M, 0 .<= w)
    @constraint(M, sum(w[i] for i=1:indexes) == 1)

    solve(M)
    wStar = getvalue(w)

    #creating the forecastRow in arithmetic returns (not log returns)
    #then we can multiply and sum across the row
    forecastRow = exp(fullX[(endRow+1):(endRow+1),1:10])

    periodReturn = Array(forecastRow*wStar)[1]
    period1NReturn = Array(forecastRow*w1N)[1]

    return periodReturn, period1NReturn, wStar
end

"""
Keeps track of returns for both the proposed method and 1/N.
Needs the overall array as well as the two next returns to push
"""

function trackPortfolioReturns(ReturnOverall, individualReturn, newPeriodReturn, Return1NOverall, Individual1NReturn, period1NReturn)
    currentLength = length(ReturnOverall)
    ReturnOverall = push!(ReturnOverall, ReturnOverall[currentLength]*newPeriodReturn)
    individualReturn = push!(individualReturn, newPeriodReturn)

    current1NLength = length(Return1NOverall)
    Return1NOverall = push!(Return1NOverall, Return1NOverall[current1NLength]*period1NReturn)
    Individual1NReturn = push!(Individual1NReturn, period1NReturn)
end

function trackPortfolioWeights(weights, periodWeights, iteration)
    weights[iteration, 1:10] = periodWeights
end

"""
Utilizing LASSO to create a classification; 1, it goes up, 0, it goes down
The portfolio is then a 1/K, where K is the amount of indexes predicted to go up
No weighting between them or variance optimization, hence quite basic
"""
function ClassificationPortfolio(fullX, firstRow, indicators)

end

####SOCP FORM OF MEAN-VARIANCE
using JuMP
using Gurobi
using StatsBase
using DataFrames
using CSV
include("SupportFunction.jl")
include("DataLoad.jl")
println("Leeeeroooy Jenkins")
#Esben's path
path = "$(homedir())/Documents/GitHub/Thesis/Data/MonthlyReturns"

mainData = loadIndexDataNoDurLOGReturn(path)

mainDataArr = Array(mainData)
indexes = 10

#### WITH RISK AVERSION

nRows = size(mainDataArr)[1]
nCols = size(mainDataArr)[2]

##LOOP TO TEST IT
portfolioMVTotal = []
portfolioMVTotal = push!(portfolioMVTotal, 1)
portfolioMVIndividual = []
portfolioMVIndividual = push!(portfolioMVIndividual, 0)
wMVtracker = zeros((nRows-trainingObservations-1),10)

w1N = repeat([0.1], outer = 10)
portfolio1NTotal = []
portfolio1NTotal = push!(portfolio1NTotal, 1)
portfolio1NIndividual = []
portfolio1NIndividual = push!(portfolio1NIndividual, 0)

for rStart = 1:(nRows-trainingObservations-1)
    startRow = rStart
    endRow = (rStart-1)+trainingObservations
    mainXarr = mainDataArr[startRow:endRow,1:10]
    indexes = size(mainXarr)[2]

    #creating the covariance matrix of the training data
    Sigma = cov(mainXarr)
    #A=U^(T)U where U is upper triangular with real positive diagonal entries
    F = lufact(Sigma)
    U = F[:U]  #Cholesky factorization of Sigma
    #creating the mean of the returns for SAA (forecasts should be used instead)
    Mu = mean(mainXarr, 1)'

    #setting risk aversion.
    gamma = 1000

    M = JuMP.Model(solver = GurobiSolver(OutputFlag = 0))
    @variables M begin
            w[1:indexes]
            u[1:indexes]
            z
            y
    end

    @objective(M,Min, gamma*y - (Mu'*w)[1])
    @constraint(M, 0 .<= w)
    @constraint(M, sum(w[i] for i=1:indexes) == 1)
    @constraint(M, norm([2*U'*w;y-1]) <= y+1)
    solve(M)
    wStar = getvalue(w)

    wMVtracker[rStart,:] = wStar

    #creating the forecastRow in arithmetic returns (not log returns)
    #then we can multiply and sum across the row
    forecastRow = (exp10(mainDataArr[(endRow+1):(endRow+1),1:10])-1)*100

    periodReturn = Array(forecastRow*wStar)[1]
    currentLength = length(portfolioMVTotal)
    portfolioMVTotal = push!(portfolioMVTotal, portfolioMVTotal[currentLength]*periodReturn)
    portfolioMVIndividual = push!(portfolioMVIndividual, periodReturn)

    period1NReturn = Array(forecastRow*w1N)[1]
    current1NLength = length(portfolio1NTotal)
    portfolio1NTotal = push!(portfolio1NTotal, portfolio1NTotal[current1NLength]*period1NReturn)
    portfolio1NIndividual = push!(portfolio1NIndividual, period1NReturn)
end

#Checking the total return of the portfolio by checking last index
portfolioMVTotal[length(portfolioMVTotal)]
portfolio1NTotal[length(portfolio1NTotal)]

portfolioMVTotal = convert(Array{Float64,1}, portfolioMVTotal)
portfolioMVIndividual = convert(Array{Float64,1}, portfolioMVIndividual)
(mean(portfolioMVIndividual)-1)*100
std(portfolioMVIndividual)
SharpeRatioMV = (mean(portfolioMVIndividual)-1)/std(portfolioMVIndividual)

portfolio1NTotal = convert(Array{Float64,1}, portfolio1NTotal)
portfolio1NIndividual = convert(Array{Float64,1}, portfolio1NIndividual)
(mean(portfolio1NIndividual)-1)*100
std(portfolio1NIndividual)
SharpeRatio1N = (mean(portfolio1NIndividual)-1)/std(portfolio1NIndividual)

writedlm("MVWeights2.csv", wMVtracker,",")
2.98


#****************************************************************#
# VeysiADN 17 Apr 2021
# Advanced Robotics Homework 4 Codes
#****************************************************************#
using LinearAlgebra
const lAlgebra = LinearAlgebra
#****************************************************************#
# Returns normalized version of given matrix/array
function Normalize(N)
    return N/lAlgebra.norm(N)
end
#****************************************************************#
# Returns 4x4 twist matrix ‘se3mat’ (an element of Lie Algebra se3)
# from given twist vector (= 6x1 vector) ‘xi
function VecToSe3( xi )
    w_skew = [ 0   -xi[3]   xi[2];
              xi[3]   0    -xi[1];
             -xi[2]  xi[1]    0 ]
    return vcat(hcat(w_skew,xi[4:6]), zeros(1, 4))
end
#****************************************************************#
# Returns twist vector ‘xi’ from given twist matrix ‘se3mat’
function Se3ToVec( se3mat )
    omg = [se3mat[3, 2], se3mat[1, 3],se3mat[2, 1]]
    v = se3mat[1:3, 4]
    return vcat(omg,v)
end
#****************************************************************#
# Calculates and return rotation matrix ‘R’ by
# evaluating matrix exponential of 3x3 skew symmetric matrix ‘so3mat’

function MatrixExp3(so3mat)
        omg = [so3mat[3, 2], so3mat[1, 3],so3mat[2, 1]]
        theta = lAlgebra.norm(omg)
        omg_n = so3mat / theta
        return lAlgebra.I + sin(theta) * omg_n + (1 - cos(theta)) * omg_n * omg_n
end

#****************************************************************#
# Calculates and return transformation matrix ‘g’ by
# evaluating matrix exponential of 4x4 twist matrix ‘se3mat’
function MatrixExp6( se3mat )
    omg = [se3mat[3, 2], se3mat[1, 3],se3mat[2, 1]]
    theta = lAlgebra.norm(omg)
    omg_n = se3mat[1:3, 1:3] / theta
    temp  = hcat(MatrixExp3(se3mat[1:3, 1:3]),
                         (lAlgebra.I * theta +
                          (1 - cos(theta)) * omg_n +
                          (theta - sin(theta)) * omg_n * omg_n) *
                         se3mat[1:3, 4] / theta)
    return    vcat(temp,[0 0 0 1])
end


"""
    AffineDistribution(μ, σ, ρ)

A shifted and scaled (affinely transformed) version of `ρ`.

If ``Z`` is a random variable with distribution `ρ`, then `AffineDistribution(μ, σ, ρ)` is
the distribution of the random variable
```math
X = μ + σ Z
```

If `ρ` is a discrete univariate distribution, the probability mass function of the
transformed distribution is given by
```math
P(X = x) = P\\left(Z = \\frac{x-μ}{σ} \\right).
```

If `ρ` is a continuous univariate distribution with probability density function ``f_Z``,
the probability density function of the transformed distribution is given by
```math
f_X(x) = \\frac{1}{|σ|} f_Z\\left( \\frac{x-μ}{σ} \\right).
```

We recommend against using the `AffineDistribution` constructor directly. Instead, use
`+`, `-`, `*`, and `/`. These are optimized for specific distributions and will fall back
on `AffineDistribution` only when they need to.

Affine transformations of discrete variables are easily affected by rounding errors. If you
are getting incorrect results, try using exact `Rational` types instead of floats.

```julia
d = μ + σ * ρ       # Create location-scale transformed distribution
params(d)           # Get the parameters, i.e. (μ, σ, ρ)
```
"""
struct AffineDistribution{T<:Real, S<:ValueSupport, D<:UnivariateDistribution{S}} <: UnivariateDistribution{S}
    μ::T
    σ::T
    ρ::D
    function AffineDistribution{T,S,D}(μ::T, σ::T, ρ::D; check_args=true) where {T<:Real, S<:ValueSupport, D<:UnivariateDistribution{S}}
        check_args && @check_args(AffineDistribution, σ > zero(σ))
        new{T, S, D}(μ, σ, ρ)
    end
end

function AffineDistribution(μ::T, σ::T, ρ::UnivariateDistribution; check_args::Bool=true) where {T<:Real}
    _T = promote_type(eltype(ρ), T)
    D = typeof(ρ)
    S = value_support(D)
    return AffineDistribution{_T,S,D}(_T(μ), _T(σ), ρ; check_args=check_args)
end

function AffineDistribution(μ::Real, σ::Real, ρ::UnivariateDistribution; check_args::Bool=true)
    return AffineDistribution(promote(μ, σ)..., ρ; check_args=check_args)
end

# aliases
const LocationScale{T,S,D} = AffineDistribution{T,S,D}
function LocationScale(μ::Real, σ::Real, ρ::UnivariateDistribution; check_args::Bool=true)
    Base.depwarn("`LocationScale` is deprecated, use `AffineDistribution` instead", :LocationScale)
    if check_args && σ ≤ 0  # preparation for future PR where I remove σ > 0 check
        throw(ArgumentError("σ must be strictly positive."))
    end
    return AffineDistribution(μ, σ, ρ; check_args=false)
end

const ContinuousAffineDistribution{T<:Real,D<:ContinuousUnivariateDistribution} = AffineDistribution{T,Continuous,D}
const DiscreteAffineDistribution{T<:Real,D<:DiscreteUnivariateDistribution} = AffineDistribution{T,Discrete,D}

Base.eltype(::Type{<:AffineDistribution{T}}) where T = T

minimum(d::AffineDistribution) = d.μ + d.σ * minimum(d.ρ)
maximum(d::AffineDistribution) = d.μ + d.σ * maximum(d.ρ)
support(d::AffineDistribution) = affinedistribution_support(d.μ, d.σ, support(d.ρ))
function affinedistribution_support(μ::Real, σ::Real, support::RealInterval)
    return RealInterval(μ + σ * support.lb, μ + σ * support.ub)
end
affinedistribution_support(μ::Real, σ::Real, support) = μ .+ σ .* support

AffineDistribution(μ::Real, σ::Real, d::AffineDistribution) = AffineDistribution(μ + d.μ * σ, σ * d.σ, d.ρ)

#### Conversions

convert(::Type{AffineDistribution{T}}, μ::Real, σ::Real, ρ::D) where {T<:Real, D<:UnivariateDistribution} = AffineDistribution(T(μ),T(σ),ρ)
convert(::Type{AffineDistribution{T}}, d::AffineDistribution{S}) where {T<:Real, S<:Real} = AffineDistribution(T(d.μ),T(d.σ),d.ρ, check_args=false)

#### Parameters

location(d::AffineDistribution) = d.μ
scale(d::AffineDistribution) = d.σ
params(d::AffineDistribution) = (d.μ,d.σ,d.ρ)
partype(::AffineDistribution{T}) where {T} = T

#### Statistics

mean(d::AffineDistribution) = d.μ + d.σ * mean(d.ρ)
median(d::AffineDistribution) = d.μ + d.σ * median(d.ρ)
mode(d::AffineDistribution) = d.μ + d.σ * mode(d.ρ)
modes(d::AffineDistribution) = d.μ .+ d.σ .* modes(d.ρ)

var(d::AffineDistribution) = d.σ^2 * var(d.ρ)
std(d::AffineDistribution) = d.σ * std(d.ρ)
skewness(d::AffineDistribution) = skewness(d.ρ)
kurtosis(d::AffineDistribution) = kurtosis(d.ρ)

isplatykurtic(d::AffineDistribution) = isplatykurtic(d.ρ)
isleptokurtic(d::AffineDistribution) = isleptokurtic(d.ρ)
ismesokurtic(d::AffineDistribution) = ismesokurtic(d.ρ)

entropy(d::ContinuousAffineDistribution) = entropy(d.ρ) + log(d.σ)
entropy(d::DiscreteAffineDistribution) = entropy(d.ρ)

mgf(d::AffineDistribution,t::Real) = exp(d.μ*t) * mgf(d.ρ,d.σ*t)

#### Evaluation & Sampling

pdf(d::ContinuousAffineDistribution, x::Real) = pdf(d.ρ,(x-d.μ)/d.σ) / d.σ
pdf(d::DiscreteAffineDistribution, x::Real) = pdf(d.ρ,(x-d.μ)/d.σ)

logpdf(d::ContinuousAffineDistribution,x::Real) = logpdf(d.ρ,(x-d.μ)/d.σ) - log(d.σ)
logpdf(d::DiscreteAffineDistribution, x::Real) = logpdf(d.ρ,(x-d.μ)/d.σ)

for f in (:cdf, :ccdf, :logcdf, :logccdf)
    @eval $f(d::AffineDistribution, x::Real) = $f(d.ρ, (x - d.μ) / d.σ)
end

quantile(d::AffineDistribution,q::Real) = d.μ + d.σ * quantile(d.ρ,q)

rand(rng::AbstractRNG, d::AffineDistribution) = d.μ + d.σ * rand(rng, d.ρ)
cf(d::AffineDistribution, t::Real) = cf(d.ρ,t*d.σ) * exp(1im*t*d.μ)
gradlogpdf(d::ContinuousAffineDistribution, x::Real) = gradlogpdf(d.ρ,(x-d.μ)/d.σ) / d.σ

#### Syntactic sugar for simple transforms of distributions, e.g., d + x, d - x, and so on

Base.:+(d::UnivariateDistribution, x::Real) = AffineDistribution(x, one(x), d)
Base.:+(x::Real, d::UnivariateDistribution) = d + x
Base.:*(x::Real, d::UnivariateDistribution) = AffineDistribution(zero(x), x, d)
Base.:*(d::UnivariateDistribution, x::Real) = x * d
Base.:-(d::UnivariateDistribution, x::Real) = d + -x
Base.:/(d::UnivariateDistribution, x::Real) = inv(x) * d


using ForwardDiff, BenchmarkTools, StaticArrays

# Using StaticArrays #
#--------------------#

@inline eom(u) = @SVector [3.8*u[1]*(1-u[1])-0.05*(u[2]+0.35)*(1-2*u[3]),
0.1*((u[2]+0.35)*(1-2*u[3])-1 )*(1-1.9*u[1]),
3.78*u[3]*(1-u[3])+0.2*u[2]]

@inline function manual_jacobian(x)
    return @SMatrix [3.8*(1 - 2x[1]) -0.05*(1-2x[3]) 0.1*(x[2] + 0.35);
    -0.19((x[2] + 0.35)*(1-2x[3]) - 1)  0.1*(1-2x[3])*(1-1.9x[1])  -0.2*(x[2] + 0.35)*(1-1.9x[1]);
    0.0  0.2  3.78(1-2x[3]) ]
end

@inline function manual_jacobian!(J, x)
  J[1,1] = 3.8*(1 - 2x[1]); J[1,2] = -0.05*(1-2x[3]); J[1,3] = 0.1*(x[2] + 0.35);
  J[2,1] = 0.19((x[2] + 0.35)*(1-2x[3]) - 1); J[2,2] = 0.1*(1-2x[3])*(1-1.9x[1])
  J[2,3] = -0.2*(x[2] + 0.35)*(1-1.9x[1])
  J[3,1] = 0.0; J[3,2] = 0.2; J[3,3] = 3.78(1-2x[3])
  return J
end

@inline forward_jacobian(u) = ForwardDiff.jacobian(eom, u)

u = SVector{3}(rand(3)); J = rand(3,3)

# sanity check
@assert manual_jacobian(u) == forward_jacobian(u)

println("---- eom(::SVector) benchmark results ----")
display(@benchmark eom($u))

println("---- manual_jacobian(::SVector) benchmark results ----")
display(@benchmark manual_jacobian($u))

println("---- forward_jacobian(::SVector) benchmark results ----")
display(@benchmark forward_jacobian($u))

# sanity check
@assert manual_jacobian(u) == forward_jacobian(u)

@inline forward_jacobian!(J, u) = ForwardDiff.jacobian!(J, eom, u)

println("---- manual_jacobian_inplace(::SVector) benchmark results ----")
display(@benchmark manual_jacobian!($J, $u))

println("---- forward_jacobian_inplace(::SVector) benchmark results ----")
display(@benchmark forward_jacobian!($J, $u))

# Using Base Arrays #
#-------------------#

@inline function eom!(du, x)
  x1, x2, x3 = x[1], x[2], x[3]
  du[1] = 3.8*x1*(1-x1)-0.05*(x2+0.35)*(1-2*x3)
  du[2] = 0.1*((x2+0.35)*(1-2*x3)-1 )*(1-1.9*x1)
  du[3] = 3.78*x3*(1-x3)+0.2*x2
end

@inline function manual_jacobian!(J, x)
  J[1,1] = 3.8*(1 - 2x[1]); J[1,2] = -0.05*(1-2x[3]); J[1,3] = 0.1*(x[2] + 0.35);
  J[2,1] = 0.19((x[2] + 0.35)*(1-2x[3]) - 1); J[2,2] = 0.1*(1-2x[3])*(1-1.9x[1])
  J[2,3] = -0.2*(x[2] + 0.35)*(1-1.9x[1])
  J[3,1] = 0.0; J[3,2] = 0.2; J[3,3] = 3.78(1-2x[3])
  return J
end

@inline forward_jacobian!(J, du, u, cfg) = ForwardDiff.jacobian!(J, eom!, du, u, cfg)

u = rand(3)
du = zeros(3)
J = zeros(3, 3)
cfg = ForwardDiff.JacobianConfig(eom!, du, u)

println("---- eom!(::Vector, ::Vector) benchmark results ----")
display(@benchmark eom!($du, $u))

println("---- manual_jacobian!(::Matrix, ::Vector) benchmark results ----")
display(@benchmark manual_jacobian!($J, $u))

println("---- forward_jacobian!(::Matrix, ::Vector, ::Vector, ::JacobianConfig) benchmark results ----")
display(@benchmark forward_jacobian!($J, $du, $u, $cfg))


#! /usr/bin/julia

# Rosetta Code, Amicable pairs

function pcontrib{T<:Integer}(p::T, a::T)
    n = one(T)
    pcon = one(T)
    for i in 1:a
        n *= p
        pcon += n
    end
    return pcon
end

function divisorsum{T<:Integer}(n::T)
    dsum = one(T)
    for (p, a) in factor(n)
        dsum *= pcontrib(p, a)
    end
    dsum -= n
end

function aliquotclassifier{T<:Integer}(n::T)
    a = T[n]
    b = divisorsum(a[end])
    len = 1
    while len < 17 && !(b in a) && 0 < b && b < 2^47+1
        push!(a, b)
        b = divisorsum(a[end])
        len += 1
    end
    if b in a
        1 < len || return ("Perfect", a)
        if b == a[1]
            2 < len || return ("Amicable", a)
            return ("Sociable", a)
        elseif b == a[end]
            return ("Aspiring", a)
        else
            return ("Cyclic", push!(a, b))
        end
    end
    push!(a, b)
    b != 0 || return ("Terminating", a)
    return ("Non-terminating", a)
end

println("Classification Tests:")
tests = [1:12, 28, 496, 220, 1184, 12496, 1264460, 790, 909, 562, 1064, 1488]
for i in tests
    (class, a) = aliquotclassifier(i)
    println(@sprintf("%8d => ", i), @sprintf("%16s, ", class), a)
end


using Plots, Distributions, LaTeXStrings, Statistics, StatsPlots, JLD, ProgressBars, StatsBase

function Metropolis(p :: Function, steps :: Integer, dx :: Real, X₀ :: Real)
    success=0
    x= X₀
    xList=Float64[i for i in 1:steps]
    n=0
    for n in 1:steps
        xList[n]=x
        y= x + rand(Uniform(-dx, dx))
        if rand()< p(y)/p(x)
            x=y
            success+=1
        end
    end
    return xList, success/steps
end

#histogram
xList, successful_steps=Metropolis(x -> ℯ^(-x^2/2), 10^5, 3 , 0.0)
save("../../chapter8/8.1/HistoData.jld","xList", xList, "SuccessfulSteps", successful_steps)
histogram(xList, normalize=true, alpha=0.7, label="Normal line")
plot!(Normal(0,1), ylabel="Probability Density", xlabel="x",
    title=L"Guassian\ Distribution\ for\ 100000\ steps\ and\ a_r=%$successful_steps", label="Metropolis Series", dpi=600)
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter8\\HistoPlot.png")
#acceptence rate per unit length
N=100
stepsList= range(0.1, 50, length=N)
aᵣData=[]
for n in ProgressBar(1:N)
    xList, successful_steps=Metropolis(x -> ℯ^(-x^2/2), 10^5, stepsList[n] , 0.0)
    push!(aᵣData, successful_steps)
end
plot(stepsList,aᵣData , xlabel=L"dx", ylabel=L"a_r(dx)", title="Acceptance per Length of Steps", label=nothing, dpi=600)
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter8\\acceptance_ratio.png")
#C(j)_j plot
stepsList=[15.9, 7.95, 5.27, 3.88, 2.94, 2.2, 1.57, 1.03, 0.5]
aᵣList=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
CⱼList=[]
for n in ProgressBar(1:9)
    xList, successful_steps=Metropolis(x -> ℯ^(-x^2/2), 10^5, stepsList[n] , 0.0)
    push!(CⱼList, autocor(xList))
end
save("../../chapter8/8.1/CjList.jld","CjList",CⱼList)
selectedNums=[1, 3, 6, 9 ]

plot()
for i in selectedNums
    plot!(1:length(CⱼList[i]), CⱼList[i], linewidth=3, label=L"a_r\ =\ %$(aᵣList[i])")
end
plot!(ylabe="Cⱼ", xlabel="j", title=L"C_j \_\ j", dpi=600)
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter8\\Cj_j.png")
#length correlation
logCl=[]
ξList=Float64[]
x=[i for i in 1:10]
for i in CⱼList
    push!(logCl,log.(i[1:10]))
end
hcat(x, ones(10))
for y in logCl
    ξ⁽⁻¹⁾, Const= hcat(x, ones(10)) \ y
    ξ= -1/ξ⁽⁻¹⁾
    push!(ξList, ξ)
end
plot(aᵣList, ξList, title="Correlation Length", xlabel=L"a_r", ylabel=L"\xi", label=nothing)
scatter!(aᵣList, ξList, dpi=600, label=nothing)
save("../../chapter8/8.1/xiList.jld","ξList",ξList)
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter8\\correlationLength.png")


# ---
# title: 492. Construct the Rectangle
# id: problem492
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Math
# link: <https://leetcode.com/problems/construct-the-rectangle/description/>
# hidden: true
# ---
#
# A web developer needs to know how to design a web page's size. So, given a
# specific rectangular web page's area, your job by now is to design a
# rectangular web page, whose length L and width W satisfy the following
# requirements:
#
#   1. The area of the rectangular web page you designed must equal to the given target area.
#   2. The width `W` should not be larger than the length `L`, which means `L >= W`.
#   3. The difference between length `L` and width `W` should be as small as possible.
#
# Return _an array`[L, W]` where `L` and `W` are the length and width of the web
# page you designed in sequence._
#
#
#
# **Example 1:**
#
#
#
#     Input: area = 4
#     Output: [2,2]
#     Explanation: The target area is 4, and all the possible ways to construct it are [1,4], [2,2], [4,1].
#     But according to requirement 2, [1,4] is illegal; according to requirement 3,  [4,1] is not optimal compared to [2,2]. So the length L is 2, and the width W is 2.
#
#
# **Example 2:**
#
#
#
#     Input: area = 37
#     Output: [37,1]
#
#
# **Example 3:**
#
#
#
#     Input: area = 122122
#     Output: [427,286]
#
#
#
#
# **Constraints:**
#
#   * `1 <= area <= 107`
#
#
## @lc code=start
using LeetCode

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


# # Group Isomorphism

# ## Set up Basic Functions

using TightBindingLattice
using Formatting
function display_matrix(io::IO, matrix::AbstractMatrix; prefix::AbstractString="")
    width = ceil(Int, maximum(length("$item") for item in matrix)/4)*4
    for row in eachrow(matrix)
        for (icol, col) in enumerate(row)
            print(io, icol == 1 ? prefix : " ")
            printfmt(io, "{:>$(width)s}", "$col")
        end
        println(io)
    end
end

# ## Generate Two Isomorphic Groups

group1 = FiniteGroup([
    1 2 3 4;
    2 1 4 3;
    3 4 2 1;
    4 3 1 2;
]);
group2 = let mtab1 = group1.multiplication_table,
             mtab2 = zeros(Int, (4, 4)),
             mapping = [1, 3, 2, 4]
             for i in 1:4, j in 1:4
                 mtab2[mapping[i], mapping[j]] = mapping[mtab1[i,j]]
             end
             FiniteGroup(mtab2)
         end;

# ## Group Multiplication Tables

println("Multiplication Table of G₁")
println("--------------------------")
display_matrix(stdout, group_multiplication_table(group1))
println()

println("Multiplication Table of G₂")
println("--------------------------")
display_matrix(stdout, group_multiplication_table(group2))
println()

# ## Group Isomorphism

println("Group Isomorphism  ϕ: G₁ → G₂")
println("-----------------------------")
ϕ = group_isomorphism(group1, group2)
for g in 1:group_order(group1)
    println("  ϕ($g) = $(ϕ[g])")
end
println()

mtab2 = zeros(Int, (group_order(group1), group_order(group1)))
for g in 1:group_order(group1), h in 1:group_order(group1)
    mtab2[ϕ[g], ϕ[h]] = ϕ[ group_product(group1, g, h) ]
end

println("Multiplication Table of ϕ(G₁)")
println("-----------------------------")
display_matrix(stdout, mtab2)
println()
println("  ϕ(g)⋅ϕ(h) = ϕ(g⋅h)")
println("  ϕ(G₁) ≡ G₂ should hold.")


# # Usage example:
# npts = 10
# rho = 0.9
# sig = 0.01
# gridpts, transition = rouwenhorst(npts, rho, sig)

function RouwenNormalize!(A)
    A[2:end-1, :] ./= 2
    nothing
end

function rouwenupdate!(Πold, Πnew, p, q)
    Nold = size(Πold, 1)
    Πnew[1:Nold, 1:Nold] = p*Πold
    Πnew[1:Nold, 2:(Nold+1)] .+= (1-p)*Πold
    Πnew[2:(Nold+1), 1:Nold] .+= (1-q)*Πold
    Πnew[2:(Nold+1), 2:(Nold+1)] .+= q*Πold
    nothing
end

function rouwenmat(N, p, q)
    initmat = [p 1-p;1-q q]
    if N==2
        ret = initmat
    else
        currmat = initmat
        for n = 3:N
            nextmat = fill(0., (n, n))
            rouwenupdate!(currmat, nextmat, p, q)
            currmat = nextmat
            RouwenNormalize!(currmat)
        end
        ret = currmat
    end
    return(ret)
end

function rouwenhorst(npts, ρ, σ)
    # Discretizes
    # y′ = ρ y +  σ ϵ

    ω = σ/sqrt(1-ρ^2) # Long run std dev
    # println("Long run variance: $ω")
    q = (1+ρ)/2

    points = range(-ω*sqrt(npts - 1),
                   ω*sqrt(npts-1),
                   length=npts)

    # points = GenGrid(uniform(npts, bds))
    Π = rouwenmat(npts, q, q)
    return points, Π
end


using HypothesisTests, Distributions, Test, Random
using HypothesisTests: default_tail
using StableRNGs

@testset "Anderson-Darling" begin
@testset "One sample test" begin
    n = 1000
    rng = StableRNG(1984948)

    d = Normal(1000, 100)
    x = rand(rng, d, n)
    t = OneSampleADTest(x, d)
    @test t.A² ≈ 1.2960 atol=0.1^4
    @test pvalue(t) ≈ 0.2336 atol=0.1^4
    @test default_tail(t) == :right

    d = DoubleExponential()
    x = rand(rng, d, n)
    t = OneSampleADTest(x, Normal(mean(d), std(d)))
    @test t.A² ≈ 11.0704 atol=0.1^4
    @test pvalue(t) ≈ 0.0 atol=0.1^4
    t = OneSampleADTest(x, d)
    @test t.A² ≈ 0.8968 atol=0.1^4
    @test pvalue(t) ≈ 0.4162 atol=0.1^4

    d = Cauchy()
    x = rand(rng, Cauchy(), n)
    t = OneSampleADTest(x, Normal())
    @test pvalue(t) ≈ 0.0 atol=0.1^4
    t = OneSampleADTest(x, d)
    @test pvalue(t) ≈ 0.9640 atol=0.1^4

    d = LogNormal()
    x = rand(rng, d, n)
    t = OneSampleADTest(x, Normal(mean(d), std(d)))
    @test pvalue(t) ≈ 0.0 atol=0.1^4
    t = OneSampleADTest(x, d)
    @test pvalue(t) ≈ 0.9123 atol=0.1^4

    d = Uniform(-pi, 2pi)
    x = rand(rng, d, n)
    t = OneSampleADTest(x, d)
    @test pvalue(t) ≈ 0.2337 atol=0.1^4

    x = rand(rng, Uniform(0, 1.8), n)
    t = OneSampleADTest(x, Uniform())
    @test pvalue(t) ≈ 0.0 atol=0.1^4

    x = rand(rng, Exponential(), n)
    t = OneSampleADTest(x, Exponential())
    @test pvalue(t) ≈ 0.8579 atol=0.1^4
end

@testset "k-sample test" begin
    samples = Any[
        [38.7, 41.5, 43.8, 44.5, 45.5, 46.0, 47.7, 58.0],
        [39.2, 39.3, 39.7, 41.4, 41.8, 42.9, 43.3, 45.8],
        [34.0, 35.0, 39.0, 40.0, 43.0, 43.0, 44.0, 45.0],
        [34.0, 34.8, 34.8, 35.4, 37.2, 37.8, 41.2, 42.8]
    ]

    t = KSampleADTest(samples...)
    @test t.A²k ≈ 8.3926 atol=0.1^4
    @test t.σ ≈ 1.2038 atol=0.1^4
    @test pvalue(t) ≈ 0.0022 atol=0.1^4
    @test default_tail(t) == :right

    ts = KSampleADTest(samples..., nsim = 20000);
    @test pvalue(ts) ≈ 0.00155 atol=0.1^3

    t = KSampleADTest(samples..., modified = false)
    @test t.A²k ≈ 8.3559 atol=0.1^4
    @test t.σ ≈ 1.2038 atol=0.1^4
    @test pvalue(t) ≈ 0.0023 atol=0.1^4

    ts = KSampleADTest(samples..., modified = false, nsim = 20000);
    @test pvalue(ts) ≈ 0.00150 atol=0.1^3
end

@testset "more tests" begin
    rng =  StableRNG(31412455)
    samples = Any[rand(rng, Normal(), 50), rand(rng, Normal(0.5), 30)]
    t = KSampleADTest(samples...)
    @test pvalue(t) < 0.05

    samples = Any[rand(rng, Normal(), 50), rand(rng, Normal(), 30), rand(rng, Normal(), 20)]
    t = KSampleADTest(samples...)
    @test pvalue(t) > 0.05

    @test pvalue(OneSampleADTest(vcat(rand(rng, Normal(),500), rand(rng, Beta(2,2),500)), Beta(2,2))) ≈ 0.0 atol=0.1^4

    n = 1000
    x = rand(rng, Exponential(), n)
    @test pvalue(KSampleADTest(rand(rng, 1000), randn(rng, 1000))) ≈ 0.0 atol=eps()
    @test pvalue(KSampleADTest(x,x,x,x,x,x)) ≈ 1.0
end

end


### A Pluto.jl notebook ###
# v0.12.17

using Markdown
using InteractiveUtils

# ╔═╡ 42338250-4387-11eb-1d21-a3b1adbf202c
begin
	using Pkg
    Pkg.activate(pwd())
end

# ╔═╡ 593f78f0-4387-11eb-3175-93d23999fd88
begin
	using CSV
	using Statistics
	using DataFrames
end

# ╔═╡ 9d6fccb0-4395-11eb-34d8-8de61157070c
using Colors

# ╔═╡ 1d3942f0-4387-11eb-05e8-85639ea3579a
md"""
# Data Mining
## Assignment 3 - Correlation Matrix
### Aadam - CS1945
"""

# ╔═╡ b3196bd0-43a3-11eb-3831-65d049f3abef
md"""
# Data Transformation
Reads `iris` data from [http://archive.ics.uci.edu/ml/datasets/Iris](http://archive.ics.uci.edu/ml/datasets/Iris), transforms it, and saves the transformed data into `data/iris_transformed.txt` file.
"""

# ╔═╡ bd551d90-4387-11eb-1e63-a5993648b680
function transform(csv_path)
	df = CSV.read(csv_path, DataFrame, datarow=1) # read from CSV
	insertcols!(df, 1, :row => axes(df, 1) ) # add column with row numbers

	# change categorical values to numeric
	df.Column5 = replace.(df.Column5, Ref("Iris-setosa" => 1))
	df.Column5 = replace.(df.Column5, Ref("Iris-versicolor" => 2))
	df.Column5 = replace.(df.Column5, Ref("Iris-virginica" => 3))
	df[!,:Column5] = parse.(Int,df[!, :Column5])

	# add data size info
	open("data/iris_transformed.txt", "w") do io
		println(io, size(df)[1])
		println(io, size(df)[2])
		println(io, "")
	end

	# append CSV data to file
	CSV.write("data/iris_transformed.txt", df, header=false, append=true)
	df
end

# ╔═╡ 4f544fa0-438c-11eb-1c71-f10ea0b7747c
df = transform("data/iris.data")[:, 2:end-1]

# ╔═╡ afbd4320-4390-11eb-1e7a-df636c495cba
md"""
# Calculate Correlation Matrix
The correlation matrix will be a NXN matrix (where N is number of records in your input dataset) containing Pearson’s correlation coefficient between each of the row in data matrix. Pearson’s correlation coefficient formula:

$\frac{{}\sum_{i=1}^{n} (x_i - \overline{x})(y_i - \overline{y})}
{\sqrt{\sum_{i=1}^{n} (x_i - \overline{x})^2(y_i - \overline{y})^2}}$
"""

# ╔═╡ eaa97310-438f-11eb-2ad5-d141a81d9791
function cor_col(x::AbstractVector, mx, y::AbstractVector, my)
    n = length(x)

	# Initialize the accumulators
	xx = zero(sqrt(abs2(one(x[1]))))
	yy = zero(sqrt(abs2(one(y[1]))))
	xy = zero(x[1] * y[1]')

	for i in eachindex(x, y)
		xi = x[i] - mx
		yi = y[i] - my
		xx += abs2(xi)
		yy += abs2(yi)
		xy += xi * yi'
	end
    return clamp(xy / max(xx, yy) / sqrt(min(xx, yy) / max(xx, yy)), -1, 1)
end

# ╔═╡ 578bf662-43a4-11eb-3d40-0b491ad520b2
function cor(X::AbstractMatrix)
	n = size(X)[1]
	cormat = zeros(n, n)

	for i in 1:n
		for j in i:n
			cormat[i, j] = cor_col(X[i, :], mean(X[i, :]), X[j, :], mean(X[j, :]))
			cormat[j, i] = cormat[i, j]
		end
	end
	cormat
end

# ╔═╡ 0eb51410-438d-11eb-0a4b-a59b660f63dd
cor_matrix = cor(Matrix(df))

# ╔═╡ 0ecb0d10-438d-11eb-2b61-a783a5176442
md"""
# Discretize
Calculate median/mean of each column of the correlation matrix and set all the values in that column that are above the calculated median/mean to 1 and rest to 0.
"""

# ╔═╡ 0ee10610-438d-11eb-193c-3d806714919a
cor_mean = mean(cor_matrix, dims=1)

# ╔═╡ 0ef59f80-438d-11eb-15f0-6986acb67f16
cor_median = median(cor_matrix, dims=1)

# ╔═╡ 0b236830-4395-11eb-129b-65553ddfc548
cor_mean_matrix = zeros(size(cor_matrix));

# ╔═╡ 16ab1130-4395-11eb-3d46-ad660d6c74c6
cor_median_matrix = zeros(size(cor_matrix));

# ╔═╡ 0f0d1f20-438d-11eb-27ef-b9a25e3b9436
for i in 1:size(cor_matrix)[1]
	cor_mean_matrix[:, i] = cor_matrix[:, i] .> cor_mean[i]
	cor_median_matrix[:, i] = cor_matrix[:, i] .> cor_median[i]
end

# ╔═╡ c0379bb0-4395-11eb-269b-19b8db40b0d5
md"""
# Visualize
Convert the discretized matrix into bitmap.
"""

# ╔═╡ 1257b542-4398-11eb-3aec-d9c38c46ccf3
md"""
### Median Discretized Correlation Matrix
"""

# ╔═╡ 9d8884d2-4395-11eb-294b-5d0b022f06e5
Gray.(cor_median_matrix)

# ╔═╡ 23e78ec2-4398-11eb-35d0-0b6de4d620de
md"""
### Mean Discretized Correlation Matrix
"""

# ╔═╡ f627fab0-4397-11eb-00e1-0f3bc6802852
Gray.(cor_mean_matrix)

# ╔═╡ Cell order:
# ╟─1d3942f0-4387-11eb-05e8-85639ea3579a
# ╟─42338250-4387-11eb-1d21-a3b1adbf202c
# ╠═593f78f0-4387-11eb-3175-93d23999fd88
# ╟─b3196bd0-43a3-11eb-3831-65d049f3abef
# ╠═bd551d90-4387-11eb-1e63-a5993648b680
# ╠═4f544fa0-438c-11eb-1c71-f10ea0b7747c
# ╟─afbd4320-4390-11eb-1e7a-df636c495cba
# ╠═eaa97310-438f-11eb-2ad5-d141a81d9791
# ╠═578bf662-43a4-11eb-3d40-0b491ad520b2
# ╠═0eb51410-438d-11eb-0a4b-a59b660f63dd
# ╟─0ecb0d10-438d-11eb-2b61-a783a5176442
# ╠═0ee10610-438d-11eb-193c-3d806714919a
# ╠═0ef59f80-438d-11eb-15f0-6986acb67f16
# ╠═0b236830-4395-11eb-129b-65553ddfc548
# ╠═16ab1130-4395-11eb-3d46-ad660d6c74c6
# ╠═0f0d1f20-438d-11eb-27ef-b9a25e3b9436
# ╟─c0379bb0-4395-11eb-269b-19b8db40b0d5
# ╠═9d6fccb0-4395-11eb-34d8-8de61157070c
# ╟─1257b542-4398-11eb-3aec-d9c38c46ccf3
# ╠═9d8884d2-4395-11eb-294b-5d0b022f06e5
# ╟─23e78ec2-4398-11eb-35d0-0b6de4d620de
# ╠═f627fab0-4397-11eb-00e1-0f3bc6802852


# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.8.0-DEV
#     language: julia
#     name: julia-1.8
# ---

# %%
VERSION

# %%
using Plots
default(size=(400, 250))

using Random
Random.seed!(4649373)

# %%
x = range(0, 1; length=10)

f(x) = sinpi(2x)
noise = 0.2randn(length(x))
t = f.(x) + noise

xs = range(extrema(x)...; length=1000)
plot(xs, f.(xs); label="", xlabel="x", ylabel="t")
scatter!(x, t; label="", color=3)

# %%
PP = []
for d in (0, 1, 3, 9)
    X = x.^(0:d)'
    w = X\t
    g(x) = evalpoly(x, w)
    P = plot(xs, f.(xs); label="")
    plot!(xs, g.(xs); label="degree $d")
    scatter!(x, t; label="")
    push!(PP, P)
end
plot(PP...; size=(800, 500))

# %%
PP = []
for d in (0, 1, 3, 9)
    X = x.^(0:d)'
    w = X'X\X't
    g(x) = evalpoly(x, w)
    P = plot(xs, f.(xs); label="")
    plot!(xs, g.(xs); label="degree $d")
    scatter!(x, t; label="")
    push!(PP, P)
end
plot(PP...; size=(800, 500))

# %%
using SymPy
n = 5
x = collect(symbols("x1:$(n+1)", real=true))

# %%
t = collect(symbols("t1:$(n+1)", real=true))

# %%
d = 3
X = x.^(0:d)'

# %%
A = X'X

# %%
T = X't

# %%


# ---
# title: 67. Add Binary
# id: problem67
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Math, String
# link: <https://leetcode.com/problems/add-binary/description/>
# hidden: true
# ---
#
# Given two binary strings `a` and `b`, return _their sum as a binary string_.
#
#
#
# **Example 1:**
#
#
#
#     Input: a = "11", b = "1"
#     Output: "100"
#
#
# **Example 2:**
#
#
#
#     Input: a = "1010", b = "1011"
#     Output: "10101"
#
#
#
#
# **Constraints:**
#
#   * `1 <= a.length, b.length <= 104`
#   * `a` and `b` consist only of `'0'` or `'1'` characters.
#   * Each string does not contain leading zeros except for the zero itself.
#
#
## @lc code=start
using LeetCode

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


### A Pluto.jl notebook ###
# v0.17.1

using Markdown
using InteractiveUtils

# ╔═╡ ca32437b-65ce-401b-93fc-1f3b7011ae88
begin
    import Pkg
    # activate the shared project environment
    Pkg.activate(Base.current_project())
    using Omega, Distributions, UnicodePlots
end

# ╔═╡ daa9fd86-5a36-4aa2-842e-477cdb19b8d5
md"# Probability Dilution
Probability dilution is refers to scenarios where one can increase one's confidence by increasing the noisyness of one's measurement device.

Here we'll use an example on microcrip certification to demonstrate."

# ╔═╡ 62257a75-2949-4afc-8543-fe3fa4073be5
md"## Example: Chip certification

Imagine you're a microchip certification engineer.  You probe microchips coming off the supply line to see if they are good or bad.  A microchip is bad if its power pin (which is the first pin) has a zero voltage.  If any of the other pins have zero voltage it's salvageable, and we'll say it's a good pin.  For simplicitly, we assume that only one (and exactly one) pin is faulty."

# ╔═╡ 53572568-9d02-465a-baba-1c624d79d4b5
md"We'll express our prior belief over which pin is faulty using a discrete uniform distribution over the 10 pins:"

# ╔═╡ f0f80b1a-d090-11eb-2b0d-d11fd8196311
#using Omega, Distributions, UnicodePlots

# ╔═╡ 4936c45a-a5bf-49cb-bfdc-c05883d5982b
faulty_pin = @~ DiscreteUniform(1, 10)

# ╔═╡ 57c308fa-a2e7-49fb-b52d-41346ee7ea0d
md"The probe tells us which pin is faulty.  Unfortunately, the probe is a little error prone.  Under normal operation it gives back the true answer, but with some probability it just gives back a random answer between 1 and 10"

# ╔═╡ d3af8df6-5375-4a52-9432-41ef6227af0c
error_prob = Variable(ω -> 0.01) # constant random variable

# ╔═╡ b0769639-7b6a-4d95-bc8d-bdedd5d27c4f
error_occurred = @~ Bernoulli(error_prob)

# ╔═╡ 8e2fb0c5-ca50-4bcb-85bf-d78c06ea5653
alternative_pin = @~ DiscreteUniform(1, 10)

# ╔═╡ ad3ec928-dc20-4987-aaed-78e8c4141d3e
function measure(ω)
	if error_occurred(ω)
		alternative_pin(ω)
	else
		faulty_pin(ω)
	end
end

# ╔═╡ 87199546-b427-4908-9531-28d40995efa8
md"As described above, a chip is bad if the faulty_pin is pin 1"

# ╔═╡ 4b8285ca-0fa8-4c38-a2a9-870169a2c1d7
bad_chip = faulty_pin ==ₚ 1

# ╔═╡ 021546fa-267d-4789-b6a5-b69d7eea01e6
md"We'll define a function `prob` to estimate the probability that an event is true:"

# ╔═╡ 0ed2799b-0e1b-434a-85be-99a3869a89c7
prob(x; n = 1000) = mean(randsample(x, n))

# ╔═╡ 505f497c-2852-44f2-bf17-8bfb12886554
md"The probability that it's a bad chip is:"

# ╔═╡ fc03484c-206d-4c3d-90f9-773e3b9b0a90
prob(bad_chip)

# ╔═╡ 413d05ce-ed65-406e-be97-32a2bad22d92
md"Now suppose we use our probe and observe a measurement of 1"

# ╔═╡ ff0aced3-466f-44e8-8d84-9521fca7c7c4
evidence = measure ==ₚ 1;

# ╔═╡ 233e3ee2-e56c-4c4d-a53d-83c144c303d0
md"Given the evidence, we can look at our conditional belief over which pin is faulty:"

# ╔═╡ 5774b63a-d492-490e-862a-0c3645722885
histogram(randsample(cnd(faulty_pin, evidence), 1000))

# ╔═╡ c6d93cdd-a2bd-4e5b-b835-1509cc582978
md"And compute the conditional probability that it is indeed a bad chip (faulty pin == 1)"

# ╔═╡ 32b3da83-0fb4-4a99-9742-46f9ad13eb8b
conditional_bad_chip = cnd(bad_chip, evidence);

# ╔═╡ 5ddfb321-d696-4853-bb9a-f3b8593f074d
md"Given the evidence, the conditional probability the chip is bad is high, at:"

# ╔═╡ ff904fa3-898b-4c35-a1c4-8a08ca5f82df
prob_bad = prob(conditional_bad_chip)

# ╔═╡ ae28acc2-fb0f-4189-bdaa-574a3e090f75
md"The certification handbook says a chip can be ceritifed if there's less than a 20% chance that it is faulty"

# ╔═╡ dbe58fac-bc39-4ee1-8930-6bb4e8453317
can_certify(p) = p < 0.3

# ╔═╡ 03ac41b7-10fd-4466-976d-f2815fe655a2
md"Clearly, we cannot certify this chip:"

# ╔═╡ 6b9ac9fa-56c3-453f-aba7-69cd193d91c3
can_certify(prob_bad)

# ╔═╡ 9ef8cafd-49e5-439a-9723-479babd7e6d7
md"### A Noisier Probe
\"Not to worry!\" your colleague, Sneaky Simon says.  \"Just use the bad probe.\""

# ╔═╡ 8c128635-e46d-418e-8c51-d74fc55973e4
md"We'll use `intervene` in Omega to construct a different model where the error rate is different"

# ╔═╡ aa15703f-6137-4d67-bb25-bec070d3d193
error_prob_noisy = 0.9

# ╔═╡ da0608ac-94bd-4ba1-81be-1cde40bd4cae
conditional_bad_chip_noisy_probe = intervene(conditional_bad_chip, error_prob => error_prob_noisy);

# ╔═╡ 828161b0-c32e-4ae8-9206-1f7ce95dfda4
prob_bad_noisy = prob(conditional_bad_chip_noisy_probe)

# ╔═╡ 1e1a3659-0a82-46c1-9b6e-0f8de142f9be
md"Job done?"

# ╔═╡ b6470b88-3ac0-4ccf-81df-c6f80e409a64
can_certify(prob_bad_noisy)

# ╔═╡ abe64bc4-cdef-4cb5-80cc-2e99e792ecab
md"Apparently, Sneaky Simon was right.  Using the bad probe has allowed us to certify the chip.  Is this right?  If not, what has gone wrong?"

# ╔═╡ 672b6bfa-54ce-4b12-80f0-ee930a777cbc
probwow = pw(prob, rid(conditional_bad_chip_noisy_probe, (faulty_pin, alternative_pin)))

# ╔═╡ 36a6cb8a-f81e-4e05-9dd0-a475e88ab62c
# histogram(randsample(probwow, 100))

# ╔═╡ Cell order:
# ╟─daa9fd86-5a36-4aa2-842e-477cdb19b8d5
# ╟─62257a75-2949-4afc-8543-fe3fa4073be5
# ╟─53572568-9d02-465a-baba-1c624d79d4b5
# ╠═f0f80b1a-d090-11eb-2b0d-d11fd8196311
# ╠═ca32437b-65ce-401b-93fc-1f3b7011ae88
# ╠═4936c45a-a5bf-49cb-bfdc-c05883d5982b
# ╟─57c308fa-a2e7-49fb-b52d-41346ee7ea0d
# ╠═d3af8df6-5375-4a52-9432-41ef6227af0c
# ╠═b0769639-7b6a-4d95-bc8d-bdedd5d27c4f
# ╠═8e2fb0c5-ca50-4bcb-85bf-d78c06ea5653
# ╠═ad3ec928-dc20-4987-aaed-78e8c4141d3e
# ╟─87199546-b427-4908-9531-28d40995efa8
# ╠═4b8285ca-0fa8-4c38-a2a9-870169a2c1d7
# ╟─021546fa-267d-4789-b6a5-b69d7eea01e6
# ╠═0ed2799b-0e1b-434a-85be-99a3869a89c7
# ╟─505f497c-2852-44f2-bf17-8bfb12886554
# ╠═fc03484c-206d-4c3d-90f9-773e3b9b0a90
# ╟─413d05ce-ed65-406e-be97-32a2bad22d92
# ╠═ff0aced3-466f-44e8-8d84-9521fca7c7c4
# ╟─233e3ee2-e56c-4c4d-a53d-83c144c303d0
# ╠═5774b63a-d492-490e-862a-0c3645722885
# ╟─c6d93cdd-a2bd-4e5b-b835-1509cc582978
# ╠═32b3da83-0fb4-4a99-9742-46f9ad13eb8b
# ╟─5ddfb321-d696-4853-bb9a-f3b8593f074d
# ╠═ff904fa3-898b-4c35-a1c4-8a08ca5f82df
# ╟─ae28acc2-fb0f-4189-bdaa-574a3e090f75
# ╠═dbe58fac-bc39-4ee1-8930-6bb4e8453317
# ╟─03ac41b7-10fd-4466-976d-f2815fe655a2
# ╠═6b9ac9fa-56c3-453f-aba7-69cd193d91c3
# ╟─9ef8cafd-49e5-439a-9723-479babd7e6d7
# ╟─8c128635-e46d-418e-8c51-d74fc55973e4
# ╠═aa15703f-6137-4d67-bb25-bec070d3d193
# ╠═da0608ac-94bd-4ba1-81be-1cde40bd4cae
# ╠═828161b0-c32e-4ae8-9206-1f7ce95dfda4
# ╟─1e1a3659-0a82-46c1-9b6e-0f8de142f9be
# ╠═b6470b88-3ac0-4ccf-81df-c6f80e409a64
# ╟─abe64bc4-cdef-4cb5-80cc-2e99e792ecab
# ╠═672b6bfa-54ce-4b12-80f0-ee930a777cbc
# ╠═36a6cb8a-f81e-4e05-9dd0-a475e88ab62c


using GR, LinearAlgebra, LowRankApprox, Formatting, ImageFiltering, Images, Plots, ImageMagick, Colors, TestImages, ImageView, ImageTransformations

I=load("rgb.jpg")

G=Gray.(I)
# size(G)

A=imresize(G, ratio=1/5)
# rank(A), size(A), eltype(A)

(p,q)=size(A)
B=Array{Float64}(A)
U, S, V=psvd(B)
B010=sum(S[n]*U[1:p,n]*(V[1:q,n])' for n=1:10);
B030=sum(S[n]*U[1:p,n]*(V[1:q,n])' for n=1:30);
B050=sum(S[n]*U[1:p,n]*(V[1:q,n])' for n=1:50);
B070=sum(S[n]*U[1:p,n]*(V[1:q,n])' for n=1:70);

plot(Gray.(B),
    xaxis=false,
    xticks=false,
    yaxis=false,
    yticks=false,
    grid=false,
    title="B=B(365) 365 by 548 full rank matrix")
savefig("grayscale_svd_365.png")

plot(Gray.(B010),
    xaxis=false,
    xticks=false,
    yaxis=false,
    yticks=false,
    grid=false,
    title="B(10) rank 10 approximation")
savefig("grayscale_svd_010.png")

plot(Gray.(B030),
    xaxis=false,
    xticks=false,
    yaxis=false,
    yticks=false,
    grid=false,
    title="B(30) rank 30 approximation")
savefig("grayscale_svd_030.png")

plot(Gray.(B050),
    xaxis=false,
    xticks=false,
    yaxis=false,
    yticks=false,
    grid=false,
    title="B(50) rank 50 approximation")
savefig("grayscale_svd_050.png")

plot(Gray.(B070),
    xaxis=false,
    xticks=false,
    yaxis=false,
    yticks=false,
    grid=false,
    title="B(70) rank 70 approximation")
savefig("grayscale_svd_070.png")