# This file is a part of Julia. License is MIT: https://julialang.org/license

using Base.Test

@testset "Check that non-floats are correctly promoted" begin
    @test [1 0 0; 0 1 0]\[1,1] ≈ [1;1;0]
end

using Base.LinAlg: BlasComplex, BlasFloat, BlasReal

n = 10

# Split n into 2 parts for tests needing two matrices
n1 = div(n, 2)
n2 = 2*n1

srand(1234321)

@testset "Matrix condition number" begin
    ainit = rand(n,n)
    @testset "for $elty" for elty in (Float32, Float64, Complex64, Complex128)
        ainit = convert(Matrix{elty}, ainit)
        for arraytype in ("Array", "SubArray")
            if arraytype == "Array"
                a = ainit
            else
                a = view(ainit, 1:n, 1:n)
            end
            @test cond(a,1) ≈ 4.837320054554436e+02 atol=0.01
            @test cond(a,2) ≈ 1.960057871514615e+02 atol=0.01
            @test cond(a,Inf) ≈ 3.757017682707787e+02 atol=0.01
            @test cond(a[:,1:5]) ≈ 10.233059337453463 atol=0.01
            @test_throws ArgumentError cond(a,3)
        end
    end
end

areal = randn(n,n)/2
aimg  = randn(n,n)/2
a2real = randn(n,n)/2
a2img  = randn(n,n)/2
breal = randn(n,2)/2
bimg  = randn(n,2)/2

@testset "For A containing $eltya" for eltya in (Float32, Float64, Complex64, Complex128, Int)
    ainit = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(areal, aimg) : areal)
    ainit2 = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(a2real, a2img) : a2real)
    ε = εa = eps(abs(float(one(eltya))))

    apd  = ainit'*ainit # symmetric positive-definite
    @testset "Positive definiteness" begin
        @test !isposdef(ainit)
        @test isposdef(apd)
        if eltya != Int # cannot perform cholfact! for Matrix{Int}
            @test !isposdef!(copy(ainit))
            @test isposdef!(copy(apd))
        end
    end
    @testset "For b containing $eltyb" for eltyb in (Float32, Float64, Complex64, Complex128, Int)
        binit = eltyb == Int ? rand(1:5, n, 2) : convert(Matrix{eltyb}, eltyb <: Complex ? complex.(breal, bimg) : breal)
        εb = eps(abs(float(one(eltyb))))
        ε = max(εa,εb)

        for arraytype in ("Array", "SubArray")
            if arraytype == "Array"
                a = ainit
                b = binit
            else
                a = view(ainit, 1:n, 1:n)
                b = view(binit, 1:n, 1:2)
            end

            @testset "Solve square general system of equations" begin
                κ = cond(a,1)
                x = a \ b
                @test_throws DimensionMismatch b'\b
                @test_throws DimensionMismatch b\b'
                @test norm(a*x - b, 1)/norm(b) < ε*κ*n*2 # Ad hoc, revisit!
                @test zeros(eltya,n)\ones(eltya,n) ≈ (zeros(eltya,n,1)\ones(eltya,n,1))[1,1]
            end

            @testset "Test nullspace" begin
                a15null = nullspace(a[:,1:n1]')
                @test rank([a[:,1:n1] a15null]) == 10
                @test norm(a[:,1:n1]'a15null,Inf) ≈ zero(eltya) atol=300ε
                @test norm(a15null'a[:,1:n1],Inf) ≈ zero(eltya) atol=400ε
                @test size(nullspace(b), 2) == 0
                @test nullspace(zeros(eltya,n)) == eye(eltya,1)
            end
        end
    end # for eltyb

    for arraytype in ("Array", "SubArray")
        if arraytype == "Array"
            a = ainit
            a2 = ainit2
        else
            a = view(ainit, 1:n, 1:n)
            a2 = view(ainit2, 1:n, 1:n)
        end

        @testset "Test pinv" begin
            pinva15 = pinv(a[:,1:n1])
            @test a[:,1:n1]*pinva15*a[:,1:n1] ≈ a[:,1:n1]
            @test pinva15*a[:,1:n1]*pinva15 ≈ pinva15

            @test size(pinv(ones(eltya,0,0))) == (0,0)
        end

        @testset "Lyapunov/Sylvester" begin
            x = lyap(a, a2)
            @test -a2 ≈ a*x + x*a'
            x2 = sylvester(a[1:3, 1:3], a[4:n, 4:n], a2[1:3,4:n])
            @test -a2[1:3, 4:n] ≈ a[1:3, 1:3]*x2 + x2*a[4:n, 4:n]
        end

        @testset "Matrix square root" begin
            asq = sqrtm(a)
            @test asq*asq ≈ a
            asym = a'+a # symmetric indefinite
            asymsq = sqrtm(asym)
            @test asymsq*asymsq ≈ asym
        end

        @testset "Powers" begin
            if eltya <: AbstractFloat
                z = zero(eltya)
                t = convert(eltya,2)
                r = convert(eltya,2.5)
                @test a^z ≈ eye(a)
                @test a^t ≈ a^2
                @test eye(eltya,n,n)^r ≈ eye(a)
            end
        end
    end # end for loop over arraytype

    @testset "Numbers" begin
        α = rand(eltya)
        A = zeros(eltya,1,1)
        A[1,1] = α
        @test diagm(α) == A # Test behavior of `diagm` when passed a scalar
        @test expm(α) == exp(α) # `expm` should behave like `exp` with scalar argument
    end

    @testset "Factorize" begin
        d = rand(eltya,n)
        e = rand(eltya,n-1)
        e2 = rand(eltya,n-1)
        f = rand(eltya,n-2)
        A = diagm(d)
        @test factorize(A) == Diagonal(d)
        A += diagm(e,-1)
        @test factorize(A) == Bidiagonal(d,e,:L)
        A += diagm(f,-2)
        @test factorize(A) == LowerTriangular(A)
        A = diagm(d) + diagm(e,1)
        @test factorize(A) == Bidiagonal(d,e,:U)
        if eltya <: Real
            A = diagm(d) + diagm(e,1) + diagm(e,-1)
            @test full(factorize(A)) ≈ full(factorize(SymTridiagonal(d,e)))
            A = diagm(d) + diagm(e,1) + diagm(e,-1) + diagm(f,2) + diagm(f,-2)
            @test inv(factorize(A)) ≈ inv(factorize(Symmetric(A)))
        end
        A = diagm(d) + diagm(e,1) + diagm(e2,-1)
        @test full(factorize(A)) ≈ full(factorize(Tridiagonal(e2,d,e)))
        A = diagm(d) + diagm(e,1) + diagm(f,2)
        @test factorize(A) == UpperTriangular(A)
    end
end # for eltya

@testset "test triu/tril bounds checking" begin
    ainit = rand(5,7)
    for arraytype in ("Array", "SubArray")
        if arraytype == "Array"
            a = ainit
        else
            a = view(ainit, 1:size(ainit, 1), 1:size(ainit, 2))
        end
        @test_throws(ArgumentError,triu(a,8))
        @test_throws(ArgumentError,triu(a,-6))
        @test_throws(ArgumentError,tril(a,8))
        @test_throws(ArgumentError,tril(a,-6))
    end
end

@testset "Test gradient for $elty" for elty in (Int32, Int64, Float32, Float64, Complex64, Complex128)
    if elty <: Real
        x = convert(Vector{elty}, [1:3;])
        g = ones(elty, 3)
    else
        x = convert(Vector{elty}, complex.([1:3;], [1:3;]))
        g = convert(Vector{elty}, complex.(ones(3), ones(3)))
    end
    xsub = view(x, 1:size(x, 1))
    @test gradient(x) ≈ g
    @test gradient(xsub) ≈ g # Test gradient on SubArray
    @test gradient(ones(elty,1)) == zeros(elty,1)
end

@testset "Tests norms" begin
    nnorm = 10
    mmat = 10
    nmat = 8
    @testset "For $elty" for elty in (Float32, Float64, BigFloat, Complex{Float32}, Complex{Float64}, Complex{BigFloat}, Int32, Int64, BigInt)
        x = ones(elty,10)
        @testset "Vector" begin
            xs = view(x,1:2:10)
            @test norm(x, -Inf) ≈ 1
            @test norm(x, -1) ≈ 1/10
            @test norm(x, 0) ≈ 10
            @test norm(x, 1) ≈ 10
            @test norm(x, 2) ≈ sqrt(10)
            @test norm(x, 3) ≈ cbrt(10)
            @test norm(x, Inf) ≈ 1
            if elty <: Base.LinAlg.BlasFloat
                @test norm(x, 1:4) ≈ 2
                @test_throws BoundsError norm(x,-1:4)
                @test_throws BoundsError norm(x,1:11)
            end
            @test norm(xs, -Inf) ≈ 1
            @test norm(xs, -1) ≈ 1/5
            @test norm(xs, 0) ≈ 5
            @test norm(xs, 1) ≈ 5
            @test norm(xs, 2) ≈ sqrt(5)
            @test norm(xs, 3) ≈ cbrt(5)
            @test norm(xs, Inf) ≈ 1
        end

        @testset "Issue #12552:" begin
            if real(elty) <: AbstractFloat
                for p in [-Inf,-1,1,2,3,Inf]
                    @test isnan(norm(elty[0,NaN],p))
                    @test isnan(norm(elty[NaN,0],p))
                end
            end
        end

        @testset "Number" begin
            norm(x[1:1]) === norm(x[1], -Inf)
            norm(x[1:1]) === norm(x[1], 0)
            norm(x[1:1]) === norm(x[1], 1)
            norm(x[1:1]) === norm(x[1], 2)
            norm(x[1:1]) === norm(x[1], Inf)
        end

        @testset "Absolute homogeneity, triangle inequality, & vectorized versions" begin
            for i = 1:10
                xinit = elty <: Integer ? convert(Vector{elty}, rand(1:10, nnorm)) :
                        elty <: Complex ? convert(Vector{elty}, complex.(randn(nnorm), randn(nnorm))) :
                        convert(Vector{elty}, randn(nnorm))
                yinit = elty <: Integer ? convert(Vector{elty}, rand(1:10, nnorm)) :
                        elty <: Complex ? convert(Vector{elty}, complex.(randn(nnorm), randn(nnorm))) :
                        convert(Vector{elty}, randn(nnorm))
                α = elty <: Integer ? randn() :
                    elty <: Complex ? convert(elty, complex(randn(),randn())) :
                    convert(elty, randn())
                for arraytype in ("Array", "SubArray")
                    if arraytype == "Array"
                        x = xinit
                        y = yinit
                    else
                        x = view(xinit,1:2:nnorm)
                        y = view(yinit,1:2:nnorm)
                    end
                    # Absolute homogeneity
                    @test norm(α*x,-Inf) ≈ abs(α)*norm(x,-Inf)
                    @test norm(α*x,-1) ≈ abs(α)*norm(x,-1)
                    @test norm(α*x,1) ≈ abs(α)*norm(x,1)
                    @test norm(α*x) ≈ abs(α)*norm(x) # two is default
                    @test norm(α*x,3) ≈ abs(α)*norm(x,3)
                    @test norm(α*x,Inf) ≈ abs(α)*norm(x,Inf)

                    # Triangle inequality
                    @test norm(x + y,1) <= norm(x,1) + norm(y,1)
                    @test norm(x + y) <= norm(x) + norm(y) # two is default
                    @test norm(x + y,3) <= norm(x,3) + norm(y,3)
                    @test norm(x + y,Inf) <= norm(x,Inf) + norm(y,Inf)

                    # Against vectorized versions
                    @test norm(x,-Inf) ≈ minimum(abs.(x))
                    @test norm(x,-1) ≈ inv(sum(1 ./ abs.(x)))
                    @test norm(x,0) ≈ sum(x .!= 0)
                    @test norm(x,1) ≈ sum(abs.(x))
                    @test norm(x) ≈ sqrt(sum(abs2.(x)))
                    @test norm(x,3) ≈ cbrt(sum(abs.(x).^3.))
                    @test norm(x,Inf) ≈ maximum(abs.(x))
                end
            end
        end

        @testset "Matrix (Operator)" begin
            A = ones(elty,10,10)
            As = view(A,1:5,1:5)
            @test norm(A, 1) ≈ 10
            elty <: Union{BigFloat,Complex{BigFloat},BigInt} || @test norm(A, 2) ≈ 10
            @test norm(A, Inf) ≈ 10
            @test norm(As, 1) ≈ 5
            elty <: Union{BigFloat,Complex{BigFloat},BigInt} || @test norm(As, 2) ≈ 5
            @test norm(As, Inf) ≈ 5
        end

        @testset "Absolute homogeneity, triangle inequality, & vecnorm" begin
            for i = 1:10
                Ainit = elty <: Integer ? convert(Matrix{elty}, rand(1:10, mmat, nmat)) :
                        elty <: Complex ? convert(Matrix{elty}, complex.(randn(mmat, nmat), randn(mmat, nmat))) :
                        convert(Matrix{elty}, randn(mmat, nmat))
                Binit = elty <: Integer ? convert(Matrix{elty}, rand(1:10, mmat, nmat)) :
                        elty <: Complex ? convert(Matrix{elty}, complex.(randn(mmat, nmat), randn(mmat, nmat))) :
                        convert(Matrix{elty}, randn(mmat, nmat))
                α = elty <: Integer ? randn() :
                    elty <: Complex ? convert(elty, complex(randn(),randn())) :
                    convert(elty, randn())

                for arraytype in ("Array", "SubArray")
                    if arraytype == "Array"
                        A = Ainit
                        B = Binit
                    else
                        A = view(Ainit,1:nmat,1:nmat)
                        B = view(Binit,1:nmat,1:nmat)
                    end

                    # Absolute homogeneity
                    @test norm(α*A,1) ≈ abs(α)*norm(A,1)
                    elty <: Union{BigFloat,Complex{BigFloat},BigInt} || @test norm(α*A) ≈ abs(α)*norm(A) # two is default
                    @test norm(α*A,Inf) ≈ abs(α)*norm(A,Inf)

                    # Triangle inequality
                    @test norm(A + B,1) <= norm(A,1) + norm(B,1)
                    elty <: Union{BigFloat,Complex{BigFloat},BigInt} || @test norm(A + B) <= norm(A) + norm(B) # two is default
                    @test norm(A + B,Inf) <= norm(A,Inf) + norm(B,Inf)

                    # vecnorm:
                    for p = -2:3
                        @test norm(reshape(A, length(A)), p) == vecnorm(A, p)
                    end
                end
            end

            @testset "issue #10234" begin
                if elty <: AbstractFloat || elty <: Complex
                    z = zeros(elty, 100)
                    z[1] = -Inf
                    for p in [-2,-1.5,-1,-0.5,0.5,1,1.5,2,Inf]
                        @test norm(z, p) == (p < 0 ? 0 : Inf)
                        @test norm(elty[Inf],p) == Inf
                    end
                end
            end
        end
    end

    @testset "issue #10234" begin
        @test norm(Any[Inf],-2) == norm(Any[Inf],-1) == norm(Any[Inf],1) == norm(Any[Inf],1.5) == norm(Any[Inf],2) == norm(Any[Inf],Inf) == Inf
    end

    @testset "overflow/underflow in norms" begin
        @test norm(Float64[1e-300, 1], -3)*1e300 ≈ 1
        @test norm(Float64[1e300, 1], 3)*1e-300 ≈ 1
    end
end

## Issue related tests
@testset "issue #1447" begin
    A = [1.0+0.0im 0; 0 1]
    B = pinv(A)
    for i = 1:4
        @test A[i] ≈ B[i]
    end
end

@testset "issue #2246" begin
    A = [1 2 0 0; 0 1 0 0; 0 0 0 0; 0 0 0 0]
    Asq = sqrtm(A)
    @test Asq*Asq ≈ A
    A2 = view(A, 1:2, 1:2)
    A2sq = sqrtm(A2)
    @test A2sq*A2sq ≈ A2

    N = 3
    @test log(det(eye(N))) ≈ logdet(eye(N))
end

@testset "issue #2637" begin
    a = [1, 2, 3]
    b = [4, 5, 6]
    @test kron(eye(2),eye(2)) == eye(4)
    @test kron(a,b) == [4,5,6,8,10,12,12,15,18]
    @test kron(a',b') == [4 5 6 8 10 12 12 15 18]
    @test kron(a,b')  == [4 5 6; 8 10 12; 12 15 18]
    @test kron(a',b)  == [4 8 12; 5 10 15; 6 12 18]
    @test kron(a,eye(2)) == [1 0; 0 1; 2 0; 0 2; 3 0; 0 3]
    @test kron(eye(2),a) == [ 1 0; 2 0; 3 0; 0 1; 0 2; 0 3]
    @test kron(eye(2),2) == 2*eye(2)
    @test kron(3,eye(3)) == 3*eye(3)
    @test kron(a,2) == [2, 4, 6]
    @test kron(b',2) == [8 10 12]
end

@testset "issue #4796" begin
    dim=2
    S=zeros(Complex,dim,dim)
    T=zeros(Complex,dim,dim)
    T[:] = 1
    z = 2.5 + 1.5im
    S[1] = z
    @test S*T == [z z; 0 0]

    # similar issue for Array{Real}
    @test Real[1 2] * Real[1.5; 2.0] == Real[5.5]
end

@testset "Matrix exponential" begin
    @testset "Tests for $elty" for elty in (Float32, Float64, Complex64, Complex128)
        A1  = convert(Matrix{elty}, [4 2 0; 1 4 1; 1 1 4])
        eA1 = convert(Matrix{elty}, [147.866622446369 127.781085523181  127.781085523182;
                                     183.765138646367 183.765138646366  163.679601723179;
                                     71.797032399996  91.8825693231832 111.968106246371]')
        @test expm(A1) ≈ eA1

        A2  = convert(Matrix{elty},
                      [29.87942128909879    0.7815750847907159 -2.289519314033932;
                       0.7815750847907159 25.72656945571064    8.680737820540137;
                       -2.289519314033932   8.680737820540137  34.39400925519054])
        eA2 = convert(Matrix{elty},
                      [  5496313853692458.0 -18231880972009236.0 -30475770808580460.0;
                       -18231880972009252.0  60605228702221920.0 101291842930249760.0;
                       -30475770808580480.0 101291842930249728.0 169294411240851968.0])
        @test expm(A2) ≈ eA2

        A3  = convert(Matrix{elty}, [-131 19 18;-390 56 54;-387 57 52])
        eA3 = convert(Matrix{elty}, [-1.50964415879218 -5.6325707998812  -4.934938326092;
                                     0.367879439109187 1.47151775849686  1.10363831732856;
                                     0.135335281175235 0.406005843524598 0.541341126763207]')
        @test expm(A3) ≈ eA3

        A4 = convert(Matrix{elty}, [0.25 0.25; 0 0])
        eA4 = convert(Matrix{elty}, [1.2840254166877416 0.2840254166877415; 0 1])
        @test expm(A4) ≈ eA4

        A5 = convert(Matrix{elty}, [0 0.02; 0 0])
        eA5 = convert(Matrix{elty}, [1 0.02; 0 1])
        @test expm(A5) ≈ eA5

        # Hessenberg
        @test hessfact(A1)[:H] ≈ convert(Matrix{elty},
                                                 [4.000000000000000  -1.414213562373094  -1.414213562373095
                                                  -1.414213562373095   4.999999999999996  -0.000000000000000
                                                  0  -0.000000000000002   3.000000000000000])
    end

    @testset "Additional tests for $elty" for elty in (Float64, Complex{Float64})
        A4  = convert(Matrix{elty}, [1/2 1/3 1/4 1/5+eps();
                                     1/3 1/4 1/5 1/6;
                                     1/4 1/5 1/6 1/7;
                                     1/5 1/6 1/7 1/8])
        @test expm(logm(A4)) ≈ A4

        A5  = convert(Matrix{elty}, [1 1 0 1; 0 1 1 0; 0 0 1 1; 1 0 0 1])
        @test expm(logm(A5)) ≈ A5

        A6  = convert(Matrix{elty}, [-5 2 0 0 ; 1/2 -7 3 0; 0 1/3 -9 4; 0 0 1/4 -11])
        @test expm(logm(A6)) ≈ A6

        A7  = convert(Matrix{elty}, [1 0 0 1e-8; 0 1 0 0; 0 0 1 0; 0 0 0 1])
        @test expm(logm(A7)) ≈ A7
    end

    A8 = 100 * [-1+1im 0 0 1e-8; 0 1 0 0; 0 0 1 0; 0 0 0 1]
    @test expm(logm(A8)) ≈ A8
end

@testset "issue 5116" begin
    A9  = [0 10 0 0; -1 0 0 0; 0 0 0 0; -2 0 0 0]
    eA9 = [-0.999786072879326  -0.065407069689389   0.0   0.0
           0.006540706968939  -0.999786072879326   0.0   0.0
           0.0                 0.0                 1.0   0.0
           0.013081413937878  -3.999572145758650   0.0   1.0]
    @test expm(A9) ≈ eA9

    A10  = [ 0. 0. 0. 0. ; 0. 0. -im 0.; 0. im 0. 0.; 0. 0. 0. 0.]
    eA10 = [ 1.0+0.0im   0.0+0.0im                 0.0+0.0im                0.0+0.0im
            0.0+0.0im   1.543080634815244+0.0im   0.0-1.175201193643801im  0.0+0.0im
            0.0+0.0im   0.0+1.175201193643801im   1.543080634815243+0.0im  0.0+0.0im
            0.0+0.0im   0.0+0.0im                 0.0+0.0im                1.0+0.0im]
    @test expm(A10) ≈ eA10
end

@testset "Additional matrix logarithm tests" for elty in (Float64, Complex{Float64})
    A11 = convert(Matrix{elty}, [3 2; -5 -3])
    @test expm(logm(A11)) ≈ A11

    A12 = convert(Matrix{elty}, [1 -1; 1 -1])
    @test typeof(logm(A12)) == Array{Complex{Float64}, 2}

    A1  = convert(Matrix{elty}, [4 2 0; 1 4 1; 1 1 4])
    logmA1 = convert(Matrix{elty}, [1.329661349 0.5302876358 -0.06818951543;
                                    0.2310490602 1.295566591 0.2651438179;
                                    0.2310490602 0.1969543025 1.363756107])
    @test logm(A1) ≈ logmA1
    @test expm(logm(A1)) ≈ A1

    A4  = convert(Matrix{elty}, [1/2 1/3 1/4 1/5+eps();
                                 1/3 1/4 1/5 1/6;
                                 1/4 1/5 1/6 1/7;
                                 1/5 1/6 1/7 1/8])
    logmA4 = convert(Matrix{elty}, [-1.73297159 1.857349738 0.4462766564 0.2414170219;
                                    1.857349738 -5.335033737 2.994142974 0.5865285289;
                                    0.4462766564 2.994142974 -7.351095988 3.318413247;
                                    0.2414170219 0.5865285289 3.318413247 -5.444632124])
    @test logm(A4) ≈ logmA4
    @test expm(logm(A4)) ≈ A4
end

@testset "issue #7181" begin
    A = [ 1  5  9
          2  6 10
          3  7 11
          4  8 12 ]
    @test_throws ArgumentError diag(A, -5)
    @test diag(A,-4) == []
    @test diag(A,-3) == [4]
    @test diag(A,-2) == [3,8]
    @test diag(A,-1) == [2,7,12]
    @test diag(A, 0) == [1,6,11]
    @test diag(A, 1) == [5,10]
    @test diag(A, 2) == [9]
    @test diag(A, 3) == []
    @test_throws ArgumentError diag(A, 4)

    @test diag(zeros(0,0)) == []
    @test_throws ArgumentError diag(zeros(0,0),1)
    @test_throws ArgumentError diag(zeros(0,0),-1)

    @test diag(zeros(1,0)) == []
    @test diag(zeros(1,0),-1) == []
    @test_throws ArgumentError diag(zeros(1,0),1)
    @test_throws ArgumentError diag(zeros(1,0),-2)

    @test diag(zeros(0,1)) == []
    @test diag(zeros(0,1),1) == []
    @test_throws ArgumentError diag(zeros(0,1),-1)
    @test_throws ArgumentError diag(zeros(0,1),2)
end

@testset "Matrix to real power" for elty in (Float64, Complex{Float64})
# Tests proposed at Higham, Deadman: Testing Matrix Function Algorithms Using Identities, March 2014
    #Aa : only positive real eigenvalues
    Aa = convert(Matrix{elty}, [5 4 2 1; 0 1 -1 -1; -1 -1 3 0; 1 1 -1 2])

    #Ab : both positive and negative real eigenvalues
    Ab = convert(Matrix{elty}, [1 2 3; 4 7 1; 2 1 4])

    #Ac : complex eigenvalues
    Ac = convert(Matrix{elty}, [5 4 2 1;0 1 -1 -1;-1 -1 3 6;1 1 -1 5])

    #Ad : defective Matrix
    Ad = convert(Matrix{elty}, [3 1; 0 3])

    #Ah : Hermitian Matrix
    Ah = convert(Matrix{elty}, [3 1; 1 3])
    if elty <: Base.LinAlg.BlasComplex
        Ah += [0 im; -im 0]
    end

    #ADi : Diagonal Matrix
    ADi = convert(Matrix{elty}, [3 0; 0 3])
    if elty <: Base.LinAlg.BlasComplex
        ADi += [im 0; 0 im]
    end

    for A in (Aa, Ab, Ac, Ad, Ah, ADi)
        @test A^(1/2) ≈ sqrtm(A)
        @test A^(-1/2) ≈ inv(sqrtm(A))
        @test A^(3/4) ≈ sqrtm(A) * sqrtm(sqrtm(A))
        @test A^(-3/4) ≈ inv(A) * sqrtm(sqrtm(A))
        @test A^(17/8) ≈ A^2 * sqrtm(sqrtm(sqrtm(A)))
        @test A^(-17/8) ≈ inv(A^2 * sqrtm(sqrtm(sqrtm(A))))
        @test (A^0.2)^5 ≈ A
        @test (A^(2/3))*(A^(1/3)) ≈ A
        @test (A^im)^(-im) ≈ A
    end
end

@testset "issue #23366 (Int Matrix to Int power)" begin
    @testset "Tests for $elty" for elty in (Int128, Int16, Int32, Int64, Int8,
                                            UInt128, UInt16, UInt32, UInt64, UInt8,
                                            BigInt)
        @test_throws DomainError elty[1 1;1 0]^-2
        @test (@inferred elty[1 1;1 0]^2) == elty[2 1;1 1]
        I_ = elty[1 0;0 1]
        @test I_^-1 == I_
        if !(elty<:Unsigned)
            @test (@inferred (-I_)^-1) == -I_
            @test (@inferred (-I_)^-2) == I_
        end
        # make sure that type promotion for ^(::Matrix{<:Integer}, ::Integer)
        # is analogous to type promotion for ^(::Integer, ::Integer)
        # e.g. [1 1;1 0]^big(10000) should return Matrix{BigInt}, the same
        # way as 2^big(10000) returns BigInt
        for elty2 = (Int64, BigInt)
            TT = Base.promote_op(^, elty, elty2)
            @test (@inferred elty[1 1;1 0]^elty2(1))::Matrix{TT} == [1 1;1 0]
        end
    end
end

@testset "Least squares solutions" begin
    a = [ones(20) 1:20 1:20]
    b = reshape(eye(8, 5), 20, 2)
    @testset "Tests for type $elty" for elty in (Float32, Float64, Complex64, Complex128)
        a = convert(Matrix{elty}, a)
        b = convert(Matrix{elty}, b)

        # Vector rhs
        x = a[:,1:2]\b[:,1]
        @test ((a[:,1:2]*x-b[:,1])'*(a[:,1:2]*x-b[:,1]))[1] ≈ convert(elty, 2.546616541353384)

        # Matrix rhs
        x = a[:,1:2]\b
        @test det((a[:,1:2]*x-b)'*(a[:,1:2]*x-b)) ≈ convert(elty, 4.437969924812031)

        # Rank deficient
        x = a\b
        @test det((a*x-b)'*(a*x-b)) ≈ convert(elty, 4.437969924812031)

        # Underdetermined minimum norm
        x = convert(Matrix{elty}, [1 0 0; 0 1 -1]) \ convert(Vector{elty}, [1,1])
        @test x ≈ convert(Vector{elty}, [1, 0.5, -0.5])

        # symmetric, positive definite
        @test inv(convert(Matrix{elty}, [6. 2; 2 1])) ≈ convert(Matrix{elty}, [0.5 -1; -1 3])

        # symmetric, indefinite
        @test inv(convert(Matrix{elty}, [1. 2; 2 1])) ≈ convert(Matrix{elty}, [-1. 2; 2 -1]/3)
    end
end

function test_rdiv_pinv_consistency(a, b)
    @test (a*b)/b ≈ a*(b/b) ≈ (a*b)*pinv(b) ≈ a*(b*pinv(b))
    @test typeof((a*b)/b) == typeof(a*(b/b)) == typeof((a*b)*pinv(b)) == typeof(a*(b*pinv(b)))
end
function test_ldiv_pinv_consistency(a, b)
    @test a\(a*b) ≈ (a\a)*b ≈ (pinv(a)*a)*b ≈ pinv(a)*(a*b)
    @test typeof(a\(a*b)) == typeof((a\a)*b) == typeof((pinv(a)*a)*b) == typeof(pinv(a)*(a*b))
end
function test_div_pinv_consistency(a, b)
    test_rdiv_pinv_consistency(a, b)
    test_ldiv_pinv_consistency(a, b)
end

@testset "/ and \\ consistency with pinv for vectors" begin
    @testset "Tests for type $elty" for elty in (Float32, Float64, Complex64, Complex128)
        c = rand(elty, 5)
        r = rand(elty, 5)'
        cm = rand(elty, 5, 1)
        rm = rand(elty, 1, 5)
        @testset "inner prodcuts" begin
            test_div_pinv_consistency(r, c)
            test_div_pinv_consistency(rm, c)
            test_div_pinv_consistency(r, cm)
            test_div_pinv_consistency(rm, cm)
        end
        @testset "outer prodcuts" begin
            test_div_pinv_consistency(c, r)
            test_div_pinv_consistency(cm, rm)
        end
        @testset "matrix/vector" begin
            m = rand(5, 5)
            test_ldiv_pinv_consistency(m, c)
            test_rdiv_pinv_consistency(r, m)
        end
    end
end

@testset "test ops on Numbers for $elty" for elty in [Float32,Float64,Complex64,Complex128]
    a = rand(elty)
    @test expm(a) == exp(a)
    @test isposdef(one(elty))
    @test sqrtm(a) == sqrt(a)
    @test logm(a) ≈ log(a)
    @test lyap(one(elty),a) == -a/2
end

@testset "stride1" begin
    a = rand(10)
    b = view(a,2:2:10)
    @test Base.LinAlg.stride1(b) == 2
end


struct Geometric <: Distribution{Int} end

"""
    geometric(p::Real)

Sample an `Int` from the Geometric distribution with parameter `p`.
"""
const geometric = Geometric()

function Gen.logpdf(::Geometric, x::Int, p::Real)
    Distributions.logpdf(Distributions.Geometric(p), x)
end

function Gen.logpdf_grad(::Geometric, x::Int, p::Real)
    if x >= 0
        p_grad = 1/p - (1/(1-p) * x)
    else
        p_grad = 0.0
    end
    (nothing, p_grad)
end

function Gen.random(::Geometric, p::Real)
    rand(Distributions.Geometric(p))
end

is_discrete(::Geometric) = true

(::Geometric)(p) = random(Geometric(), p)

Gen.has_output_grad(::Geometric) = false
Gen.has_argument_grads(::Geometric) = (true,)

export geometric


@testset "$TEST $G" begin

triangle = random_regular_graph(3,2)
quadrangle = random_regular_graph(4,2)
pentagon = random_regular_graph(5,2)

d, λ = edit_distance(triangle, quadrangle, subst_cost=MinkowskiCost(1:3,1:4))
@test d == 1.0
@test λ == Tuple[(1,1),(2,2),(3,3),(0,4)]

d, λ = edit_distance(quadrangle, triangle, subst_cost=MinkowskiCost(1:4,1:3))
@test d == 1.0
@test λ == Tuple[(1,1),(2,2),(3,3),(4,0)]

d, λ = edit_distance(triangle, pentagon, subst_cost=MinkowskiCost(1:3,1:5))
@test d == 2.0
@test λ == Tuple[(1,1),(2,2),(3,3),(0,4),(0,5)]

d, λ = edit_distance(pentagon, triangle, subst_cost=MinkowskiCost(1:5,1:3))
@test d == 2.0
@test λ == Tuple[(1,1),(2,2),(3,3),(4,0),(5,0)]

cost = MinkowskiCost(1:3,1:3)
bcost = BoundedMinkowskiCost(1:3,1:3)
for i=1:3
  @test cost(i,i) == 0.
  @test bcost(i,i) == 2/3
end

end # testset


import MechanicalSketch
import MechanicalSketch: color_with_lumin, empty_figure, background, sethue
import MechanicalSketch: PALETTE, O, EM, FS, finish, x_y_iterators_at_pixels, latexify
import MechanicalSketch: dimension_aligned, settext, arrow, setfont
import MechanicalSketch: ComplexQuantity, generate_complex_potential_source, @import_expand, string_polar_form
import MechanicalSketch: place_image, normalize_binwise, ∙, fontface, fontsize, @layer
import MechanicalSketch: bin_bounds, BinLegend, draw_legend, StaticArrays, ColorSchemes
import ColorSchemes:     PuOr_8, ColorScheme
import StaticArrays:     SVector

using Test

let
if !@isdefined s⁻¹
    @import_expand(m,s,°)
end

BACKCOLOR = color_with_lumin(PALETTE[8], 80)
function restart()
    empty_figure(filename = joinpath(@__DIR__, "test_20.png"))
    background(BACKCOLOR)
    sethue(PALETTE[5])
end
restart()

"Source position"
p_source = (-7.5, 0.0)m |> ComplexQuantity

"Flow rate, 2d flow"
q_source = 1.0m²/s

"2d velocity potential function. Complex quantity domain, real quantity range."
ϕ_source = generate_complex_potential_source(; pos = p_source, massflowout = q_source)

physwidth = 20m
physheight = physwidth / 3
xs, ys = x_y_iterators_at_pixels(;physwidth, physheight)
A = [ϕ_source(complex(x, y)) for y in ys, x in xs] # 0.189560 seconds (1.17 M allocations: 34.138 MiB, 64.04% compilation time)

# Prepare a plot
ma = maximum(A)
mi = minimum(A)
binwidth3 = (ma - mi) / 3
legend3 = BinLegend(;maxlegend = ma, minlegend = mi, binwidth = binwidth3)
tfoo(x) = normalize_binwise(length(bin_bounds(legend3)), bin_bounds(legend3), x)

@test tfoo(mi) == 0
@test tfoo(ma) == 1
@test tfoo(mi + binwidth3) == 1 / 2
@test tfoo(mi + 2binwidth3) == 1
@test tfoo(ma) == 1
@test tfoo(mi + 0.5 * binwidth3) == 0
@test tfoo(mi + 1.5 * binwidth3) == 1 / 2
@test tfoo(mi + 2.5 * binwidth3) == 1

@test tfoo(mi + 0.6 * binwidth3) == 0.0
@test isnan(tfoo( mi + 3.001 * binwidth3))
@test isnan(tfoo(mi - 0.001 * binwidth3))
bibo = bin_bounds(legend3)
@test bibo[1] == mi
@test bibo[2] == mi + binwidth3
@test bibo[4] == ma
@test legend3(mi) != legend3.nan_color
@test legend3(ma) != legend3.nan_color
legend3.(bibo)

# Since we have values on both sides of zero, create a legend including zero.
binwidth = 0.1m²∙s⁻¹
binbounds = Tuple((-5:5)binwidth)
# Distinguishing between positive and negative is often important. We
# truncate a colorscheme to make our own.
cv(x) = get(PuOr_8, x, :clamp)
nomiddle = [[cv(x) for x in range(0, 0.3, length = 10)];
        [cv(1 - x) for x in range(0.3, 0, length = 11)]]
nomiddlescheme = ColorScheme(SVector(nomiddle...), "testschemes", "emphasize plus / minus")
legend = BinLegend(;binbounds, colorscheme = nomiddlescheme, name = :Potential)

colormat = legend.(A);
ulp, lrp = place_image(O, colormat);

# Top left of legend
legendpos = O + ((lrp[1] - ulp[1]) / 2 + EM, (ulp[2] - lrp[2]) / 2)
@layer begin
    # Font for the 'toy' text interface
    fontface("Calibri")
    fontsize(0.9FS)
    draw_legend(legendpos, legend)
end

# Add some decoration to the plot
sethue(color_with_lumin(PALETTE[5], 30))
dimension_aligned(O + (-physwidth / 2, physheight / 2), O + (physwidth / 2, physheight / 2))
dimension_aligned(O + p_source, O + (-7.5 + 1.0im)m)
dimension_aligned(O + (-physwidth / 2, - physheight / 2 ),  O +  (-physwidth / 2, physheight / 2 ))

@layer begin # Leaders
    sethue(color_with_lumin(PALETTE[5], 90))
    p0 = (-7.5 + 1.0im)m
    arrow(O + p0 + (2EM, -EM), O + p0)
    fvalue = ϕ_source(p0)
    strvalue = string(round(typeof(fvalue ), fvalue , digits = 3))
    strargument = string(p0)
    txt =  "ϕsource(" * strargument * ") = " * strvalue
    settext(txt, O + p0 + (3EM, -EM), markup=true)
    p0 = (-4.0 - 2.0im)m
    arrow(O + p0 + (2EM, -EM), O + p0)
    fvalue = ϕ_source(p0)
    strargument = string_polar_form(p0)
    strvalue = string(round(typeof(fvalue), fvalue , digits = 3))
    txt =  "ϕsource( " * strargument * " ) = " * strvalue
    settext(txt, O + p0 + (3EM, -EM), markup=true)
end

# Convert the function to latex and display
la1 = latexify(quote
    ϕ_source(r:: Length)::Potential = $q_source * log(r / oneunit(r)) / (2π)
end)
pt = O + (-14.0, -7 / 2)m
ptul, ptlr, scalefactor = place_image(pt, la1, height = 3EM)

# Following text
setfont("DejaVu Sans", FS)
str = "exists for irrotational flows only"
settext(str, ptlr + (EM, -0.5FS), markup = true)

# Convert the outer function to latex
la2 = latexify(quote
    ϕ_source(p::ComplexQuantity)::Potential = ϕ(hypot(p - $p_source))
end)
place_image(pt + (0, 5EM), la2; scalefactor)

finish()

end # let


using CartesianGP
using Base.Test

# ZERO

@test ZERO.func() == 0

# ONE

@test ONE.func() == typemax(BitString)

# AND

@test AND.func([ZERO.func(), ZERO.func()]...) == ZERO.func()
@test AND.func([ONE.func(), ZERO.func()]...) == ZERO.func()
@test AND.func([ZERO.func(), ONE.func()]...) == ZERO.func()
@test AND.func([ONE.func(), ONE.func()]...) == ONE.func()

# OR

@test OR.func([ZERO.func(), ZERO.func()]...) == ZERO.func()
@test OR.func([ONE.func(), ZERO.func()]...) == ONE.func()
@test OR.func([ZERO.func(), ONE.func()]...) == ONE.func()
@test OR.func([ONE.func(), ONE.func()]...) == ONE.func()

# XOR

@test XOR.func([ZERO.func(), ZERO.func()]...) == ZERO.func()
@test XOR.func([ONE.func(), ZERO.func()]...) == ONE.func()
@test XOR.func([ZERO.func(), ONE.func()]...) == ONE.func()
@test XOR.func([ONE.func(), ONE.func()]...) == ZERO.func()

# NOT

@test NOT.func([ZERO.func()]...) == ONE.func()
@test NOT.func([ONE.func()]...) == ZERO.func()


module Test_ldlt

include("../src/ldlt.jl")

using Test
using Random
using SparseArrays
using LinearAlgebra

@testset "ldlt" begin
    @testset "symbolic_ldlt" begin
        @testset "n = 1" begin
            Ap = [1,2]
            Ai = [1]
            Fp,Fi = symbolic_ldlt(Ap,Ai)
            @test Fp == [1,2]
            @test Fi == [1]
        end

        @testset "n = 2" begin
            @testset "A = [1 0; 0 1]" begin
                Ap = [1,2,3]
                Ai = [1,2]
                Fp,Fi = symbolic_ldlt(Ap,Ai)
                @test Fp == [1,2,3]
                @test Fi == [1,2]
            end
            @testset "A = [1 1; 1 0]" begin
                Ap = [1,3,3]
                Ai = [1,2]
                Fp,Fi = symbolic_ldlt(Ap,Ai)
                @test Fp == [1,3,4]
                @test Fi == [1,2,2]
            end
            @testset "A = [1 1; 1 1]" begin
                Ap = [1,3,4]
                Ai = [1,2,2]
                Fp,Fi = symbolic_ldlt(Ap,Ai)
                @test Fp == [1,3,4]
                @test Fi == [1,2,2]
            end
        end

        @testset "n in 3:100" begin
            @testset for seed = 1:100
                Random.seed!(seed)
                n = rand(3:100)
                A = sprand(n,n,rand())
                A = 2I + A + A'
                Ap,Ai = A.colptr,A.rowval
                Fref = lu(Matrix(A),Val(false)).L .!= 0
                Fp,Fi = symbolic_ldlt(Ap,Ai)
                F = SparseMatrixCSC(n,n,Fp,Fi,ones(Bool,length(Fi)))
                @test F == Fref
            end
        end
    end

    @testset "symbolic_ildlt_lof" begin
        @testset "c = 0" begin
            @testset for seed = 1:100
                Random.seed!(seed)
                n = rand(3:100)
                A = sprand(n,n,rand())
                A = tril(2I + A + A')
                Ap,Ai = A.colptr,A.rowval

                Fp,Fi,Fl = symbolic_ildlt_lof(Ap,Ai,0)
                @test (Fp,Fi) == (Ap,Ai)
            end
        end

        @testset "c = n" begin
            @testset for seed = 1:100
                Random.seed!(seed)
                n = rand(3:100)
                A = sprand(n,n,rand())
                A = 2I + A + A'
                Ap,Ai = A.colptr,A.rowval

                rFp,rFi = symbolic_ldlt(Ap,Ai)
                Fp,Fi,Fl = symbolic_ildlt_lof(Ap,Ai,n)
                @test (Fp,Fi) == (rFp,rFi)
            end
        end

        @testset "self-consistency" begin
            @testset for seed = 1:100
                Random.seed!(seed)
                n = rand(3:100)
                A = sprand(n,n,rand())
                A = 2I + A + A'
                Ap,Ai = A.colptr,A.rowval

                Fref = lu(Matrix(A),Val(false)).L .!= 0
                Fp,Fi,Fl = symbolic_ildlt_lof(Ap,Ai,n)
                F = SparseMatrixCSC(n,n,Fp,Fi,Fl)

                cmax = maximum(Fl)
                for c = 0:cmax
                    ref = SparseArrays.fkeep!(copy(F),(i,j,l)->(l<=c))
                    rFp,rFi,rFl = ref.colptr,ref.rowval,ref.nzval
                    iFp,iFi,iFl = symbolic_ildlt_lof(Ap,Ai,c)
                    @test (iFp,iFi,iFl) == (rFp,rFi,rFl)
                end
            end
        end
    end

    @testset "numeric_ldlt" begin
        function test(A,conj,adj)
            Ap,Ai,Av = A.colptr,A.rowval,A.nzval
            Fp,Fi = symbolic_ldlt(Ap,Ai)
            Fv = numeric_ldlt(Ap,Ai,Av,Fp,Fi; conj=conj)
            F = SparseMatrixCSC(size(A)...,Fp,Fi,Fv)

            L = tril(F,-1) + I; D = Diagonal(F);
            @test eltype(F) == eltype(A)
            @test L*D*adj(L) ≈ A
        end

        @testset for T in (Float32,Float64,ComplexF32,ComplexF64)
            @testset for (conj,adj) in ((conj,adjoint),(identity,transpose))
                @testset "n = 1" begin
                    Random.seed!(1)
                    Ap = [1,2]
                    Ai = [1]
                    Av = rand(T,1)
                    A = SparseMatrixCSC(1,1,Ap,Ai,Av)
                    A = 2I + A + adj(A)
                    test(A,conj,adj)
                end

                @testset "n = 2" begin
                    @testset "A = [1 0; 0 1]" begin
                        Random.seed!(1)
                        Ap = [1,2,3]
                        Ai = [1,2]
                        Av = rand(T,2)
                        A = SparseMatrixCSC(2,2,Ap,Ai,Av)
                        A = 2I + A + adj(A)
                        test(A,conj,adj)
                    end
                    @testset "A = [1 1; 1 0]" begin
                        Ap = [1,3,3]
                        Ai = [1,2]
                        Av = rand(T,2)
                        A = SparseMatrixCSC(2,2,Ap,Ai,Av)
                        A = 2I + A + adj(A)
                        test(A,conj,adj)
                    end
                    @testset "A = [1 1; 1 1]" begin
                        Ap = [1,3,4]
                        Ai = [1,2,2]
                        Av = rand(T,3)
                        A = SparseMatrixCSC(2,2,Ap,Ai,Av)
                        A = 2I + A + adj(A)
                        test(A,conj,adj)
                    end
                end

                @testset "n in 3:100" begin
                    @testset for seed = 1:100
                        Random.seed!(seed)
                        n = rand(3:100)
                        A = sprand(n,n,rand())
                        A = 2I + A + adj(A)
                        test(A,conj,adj)
                    end
                end
            end
        end
    end

    @testset "ildlt_tol" begin
        function test(A,conj,adj)
            A = tril(A)
            Ap,Ai,Av = A.colptr,A.rowval,A.nzval

            # Test that factorization is exact if tol = 0
            Fp,Fi,Fv = ildlt_tol(Ap,Ai,Av, 0.0; conj=conj)
            F = SparseMatrixCSC(size(A)...,Fp,Fi,Fv)
            L = tril(F,-1) + I; D = Diagonal(F);
            @test eltype(F) == eltype(A)
            @test tril(L*D*adj(L)) ≈ A

            # Test that factorization produces no fill-in for tol = Inf
            Fp,Fi,Fv = ildlt_tol(Ap,Ai,Av, Inf; conj=conj)
            @test (Fp,Fi) == (Ap,Ai)
        end

        @testset for T in (Float32,Float64,ComplexF32,ComplexF64)
            @testset for (conj,adj) in ((conj,adjoint),(identity,transpose))
                @testset "n = 1" begin
                    Random.seed!(1)
                    Ap = [1,2]
                    Ai = [1]
                    Av = rand(T,1)
                    A = SparseMatrixCSC(1,1,Ap,Ai,Av)
                    A = 2I + A + adj(A)
                    test(A,conj,adj)
                end

                @testset "n = 2" begin
                    @testset "A = [1 0; 0 1]" begin
                        Random.seed!(1)
                        Ap = [1,2,3]
                        Ai = [1,2]
                        Av = rand(T,2)
                        A = SparseMatrixCSC(2,2,Ap,Ai,Av)
                        A = 2I + A + adj(A)
                        test(A,conj,adj)
                    end
                    @testset "A = [1 1; 1 1]" begin
                        Ap = [1,3,4]
                        Ai = [1,2,2]
                        Av = rand(T,3)
                        A = SparseMatrixCSC(2,2,Ap,Ai,Av)
                        A = 2I + A + adj(A)
                        test(A,conj,adj)
                    end
                end

                @testset "n in 3:100" begin
                    @testset for seed = 1:100
                        Random.seed!(seed)
                        n = rand(3:100)
                        A = sprand(n,n,rand())
                        A = 2I + A + adj(A)
                        test(A,conj,adj)
                    end
                end
            end
        end
    end
end

end


#=

  Organize a day in Julia ConstraintSolver.jl

  Problem formulation:
  Slides on (finite domain) Constraint Logic Programming, page 38f

  Model created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#
using ConstraintSolver, JuMP
using Cbc, GLPK, Ipopt
using Printf
const CS = ConstraintSolver
include("constraints_utils.jl")

function organize_day(problem,print_solutions=true,all_solutions=true)

    cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,   "all_solutions"=> all_solutions,
                                                            # "all_optimal_solutions"=>all_solutions,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            # "traverse_strategy"=>:DFS,
                                                            # "traverse_strategy"=>:DBFS,

                                                            # "branch_split"=>:Smallest,
                                                            # "branch_split"=>:Biggest,
                                                            "branch_split"=>:InHalf,

                                                            # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
                                                            "branch_strategy" => :IMPS, # default
                                                            # "branch_strategy" => :ABS, # Activity Based Search
                                                            # "activity.decay" => 0.999, # default 0.999
                                                            # "activity.max_probes" => 10, # default, 10
                                                            # "activity.max_confidence_deviation" => 20, # default 20

                                                            # "simplify"=>false,
                                                            # "simplify"=>true, # default

                                                            "time_limit"=>6,

                                                            # "backtrack" => false, # default true
                                                            # "backtrack_sorting" => false, # default true

                                                            # "lp_optimizer" => cbc_optimizer,
                                                            # "lp_optimizer" => glpk_optimizer,
                                                            # "lp_optimizer" => ipopt_optimizer,
                                        ))
    tasks = problem[:tasks]
    durations = problem[:durations]
    precedences = problem[:precedences]
    start_time = problem[:start_time]
    end_time = problem[:end_time]

    n = length(tasks)
    @variable(model, start_time <= begins[1:n] <= end_time, Int)
    @variable(model, start_time <= ends[1:n] <= end_time, Int)

    for i in 1:n
        @constraint(model,ends[i] == begins[i] + durations[i])
    end

    no_overlap(model,begins,durations)

    # precedences
    println("precedences:$precedences")
    for (a,b) in eachrow(precedences)
        @constraint(model, ends[a] <= begins[b])
    end

    @constraint(model,begins[1] >= 11)

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    if status == MOI.OPTIMAL
        num_sols = MOI.get(model, MOI.ResultCount())
        println("num_sols:$num_sols\n")
        if print_solutions
            for sol in 1:num_sols
                println("solution #$sol")
                begins_val = convert.(Integer,JuMP.value.(begins; result=sol))
                ends_val = convert.(Integer,JuMP.value.(ends; result=sol))
                # println("begins:$begins_val ")
                # println("ends  :$ends_val ")
                for t in 1:n
                    # println("$(tasks[t]): $(begins_val[t]) .. $(ends_val[t])")
                    @printf "%s: %2d .. %2d\n" tasks[t] begins_val[t] ends_val[t]
                end
                println()

            end
        end
    else
        println("status:$status")
    end

    return status
end

# Problem instance
organize_day_problem = Dict(

    #     task id   1      2      3      4
    :tasks => ["Work","Mail","Shop","Bank"],

    # duration of the four tasks
    :durations => [4,1,2,1],

    # precedences
    # [A,B] : task A must be completed before task B
    :precedences => resize_matrix([[4,3],
                                   [2,1]]
                                  ),
    # Time limits
    :start_time => 9,
    :end_time => 17
)

@time organize_day(organize_day_problem)


"""
    PlaneWaveBasis(b1::Vector{Float64}, b2::Vector{Float64},
            ps::Vector{Int}, qs::Vector{Int})

A 2D basis of plane waves, `ks = ps*b1 + qs*b2`.
"""
struct PlaneWaveBasis
    b1::Vector{Float64}
    b2::Vector{Float64}
    ps::Vector{Int}
    qs::Vector{Int}
    kxs::Vector{Float64}
    kys::Vector{Float64}
    function PlaneWaveBasis(b1::AbstractVector{Float64}, b2::AbstractVector{Float64},
                                        ps::AbstractVector{Int}, qs::AbstractVector{Int})
        kxs = [p*b1[1]+q*b2[1] for (p,q) in zip(ps,qs)]
        kys = [p*b1[2]+q*b2[2] for (p,q) in zip(ps,qs)]
        return new(b1, b2, ps, qs, kxs, kys)
    end
end

"""
    PlaneWaveBasis(geometry::Geometry, cutoff::Int)

Approximate a basis of plane waves truncated in a circle.

The circle has a diameter of `cutoff` Brillouin zones. Increasing the `cutoff`
will increase the number of plane waves leading to a more accurate solution.
It is assumed that `norm(b1) == norm(b2)`.
"""
function PlaneWaveBasis(geometry::Geometry, cutoff::Int)
    @assert isodd(cutoff)
    b1, b2 = as_to_bs(geometry.a1, geometry.a2)
    @assert norm(b1) ≈ norm(b2) # for now
    ps, qs = Int[], Int[]
    cutoff_radius = norm(b1) * cutoff / 2
    for p in -cutoff:cutoff, q in -cutoff:cutoff
        k = p*b1 + q*b2
        if norm(k) <= cutoff_radius
            push!(ps, p)
            push!(qs, q)
        end
    end
    return PlaneWaveBasis(b1, b2, ps, qs)
end

"""
    Solver(geometry::Geometry, cutoff_b1::Int, cutoff_b2::Int)

Approximate the geometry using a basis of plane waves truncated in a rhombus.

The rhombus has lengths `cutoff_b1` and `cutoff_b2` in the `b1` and `b2`
directions, respectively.
"""
function PlaneWaveBasis(geometry::Geometry, cutoff_b1::Int, cutoff_b2::Int)
    @assert isodd(cutoff_b1)
    @assert isodd(cutoff_b2)
    P = div(cutoff_b1, 2) # rounds down
    Q = div(cutoff_b2, 2) # rounds down
    ps = [p for p in -P:P for q in -Q:Q]
    qs = [q for p in -P:P for q in -Q:Q]
    b1, b2 = as_to_bs(geometry.a1, geometry.a2)
    return PlaneWaveBasis(b1, b2, ps, qs)
end

"""
    BrillouinZoneCoordinate(p::Float64, q::Float64, label::String="")

A labelled coordinate in the Brillouin zone.

The arguments `p` and `q` are the coefficients of reciprocal lattice vectors
`b1` and `b2`. The k-space coordinate, `k = p * b1 + q * b2`, is generated by
`get_k(coord::BrillouinZoneCoordinate, basis::PlaneWaveBasis)`. For example,
`BrillouinZoneCoordinate(0.5,0)` is on the edge of the first Brillouin zone.
"""
struct BrillouinZoneCoordinate
    p::Float64
    q::Float64
    label::String
    function BrillouinZoneCoordinate(p::Real, q::Real, label::String="")
        return new(p, q, label)
    end
end

"""
    get_k(coord::BrillouinZoneCoordinate, basis::PlaneWaveBasis)

Return the k-space coordinate of the `BrillouinZoneCoordinate` in a particular
`PlaneWaveBasis`, ie `k = p*b1 + q*b2`.
"""
function get_k(coord::BrillouinZoneCoordinate, basis::PlaneWaveBasis)
    return coord.p*basis.b1 + coord.q*basis.b2
end


function ei(::Type{T}, i::Integer, n::Integer)::Vector{T} where T
    v = zeros(T, n)
    v[i] = one(T)
    return v
end
ei(i::Integer, n::Integer)::Vector{Float64} = ei(Float64, i, n)

function pivot!(M::Array{T,2}, i::Int, j::Int, Binv::Array{T, 2} = Array{T, 2}(undef, 0, 0); freelines::BitVector = trues(size(M, 1) - 1))::Matrix{T} where T
    updBinv = !iszero(length(Binv))
    m,n = size(M)
    @assert M[i, j] != 0 "pivot  illegal"
    vMi = @view M[i, :]
    updBinv && (vBinv = @view Binv[i, :])

    M[i, :] = vMi / M[i, j]
    updBinv && (Binv[i, :] = vBinv/M[i, j])

    linenottodo = findall(iszero, @view M[:, j])
    guilylines = findall(!, freelines)
    linenottodo[:] = linenottodo
    for (iter, k) in enumerate(setdiff(1:m, i, linenottodo, guilylines))
        #@show M
        #@show Binv
        M[k, :] -= M[k, j] * vMi
        (k != m) && updBinv && (Binv[iter, :] -= M[k, j] * vBinv)
    end
    return M
end

abignumber(::Type{Float64}) = reinterpret(Float64, (reinterpret(UInt64, typemax(Float64)) - UInt64(1)))/100
abignumber(::Type{Float32}) = reinterpret(Float32, (reinterpret(UInt32, typemax(Float32)) - UInt32(1)))/100
#abignumber(::Type{Float16}) = reinterpret(Float16, (reinterpret(UInt16, typemax(Float16)) - UInt16(1)))/100
abignumber(::Type{Rational{Int128}}) = div(typemax(Int128), Int128(1000))//Int128(1)
abignumber(::Type{Rational{Int64}}) = div(typemax(Int64), Int64(1000))//Int64(1)
abignumber(::Type{Rational{Int32}}) = div(typemax(Int32), Int32(1000))//Int32(1)

tol(::Type{T}) where T <: AbstractFloat = 10*eps(T)
tol(::Type{T}) where T <: Rational = T(0)

function isacanonicalcol(v::AbstractVector)::Bool
    m = length(v)
    return (sum(iszero, v) == (m - 1)) && (sum(isone, v) == 1)
end
function iscanonical(A::AbstractMatrix)::Tuple{Bool, Vector{Int}}
    m, n = size(A)
    basis = zeros(Int, m)
    for i in 1:n
        Ai = @view A[:, i]
        if isacanonicalcol(Ai)
            idx = findfirst(Bool.(Ai))
            basis[idx] = i
        end
    end
    return all(!iszero, basis), basis
end


using NumberUnions

if VERSION >= v"0.7-"
    using Test
else
    using Base.Test
end

@test Float64 <: IntFloat64
@test Int64 <: IntFloat64
@test !(Float64 <: IntFloat32)

@test SysInt >: Int16
@test Integer32 == Union{Int32, UInt32}

@test bytes2Int( sizeof(Int16) ) === Int16
@test bytes2UInt( sizeof(UInt32) ) === UInt32
@test bytes2Float( sizeof(Float64) ) === Float64


### A Pluto.jl notebook ###
# v0.14.0

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 1f65fc80-a83b-11eb-2583-89bff1ea8372
html"""

Section 3.3

 Why we can't predict the weather

Lecture Video

"""

# ╔═╡ 88b46d2e-220e-11eb-0f7f-b3f523f0214e
begin
    import Pkg
    Pkg.activate(mktempdir())

    Pkg.add([
        Pkg.PackageSpec(name="Plots", version="1"),
        Pkg.PackageSpec(name="PlutoUI", version="0.7"),
        Pkg.PackageSpec(name="LaTeXStrings", version="1"),
        Pkg.PackageSpec(name="Roots", version="1"),
		Pkg.PackageSpec(name="DifferentialEquations", version="6"),
    ])

    using Plots, PlutoUI, LaTeXStrings, Roots, DifferentialEquations
	using LinearAlgebra
end

# ╔═╡ f9dbf2e8-574d-4846-af48-f7e5a82a1afc
TableOfContents(aside=true)

# ╔═╡ a2b2eae2-762e-41c3-a546-0caedc79db7d
md"""
# Why we can predict the climate but can't predict the weather
"""

# ╔═╡ 91b34e59-e4d2-45b7-9e2b-1b95748e5d6a
md"""
## Weather vs. climate
"""

# ╔═╡ 22bddfca-fd1c-4dc7-85f3-f864c3b53407
md"""
Will it rain tomorrow? We should be able to predict that using observations about the current state of the atmosphere and mathematical models in the form of partial differential equations that predict how the wind will move the air around, what the temperature will be, and how much water can precipitate out.

What about predicting rain next week? Next year?

We know from experience that we can only predict the weather in the short term, up to a week or so (and still only with a certain probability). Predicting even one month out doesn't seem possible.

On the other hand, we can talk about the *climate* and how it behaves on much longer time scales, e.g. "the average temperature in in Boston in April is 55°F". The short-term weather has a significant random (or apparently random) component, but becomes much more predictable when we look at statistical properties like averages and standard deviations; this is then called **climate**.
"""

# ╔═╡ 6f554dc0-220c-11eb-341f-1b66de841c86
md"""
# Nonlinear dynamics: stability and bifurcations
"""

# ╔═╡ 2c6f4b31-2f08-4e24-8e31-ad80d2700fa8
md"""
In this notebook we will see that the simplest possible model of the weather, a set of 3 coupled, nonlinear ODEs known as the **Lorenz equations**, can have unpredictable behaviour, known as
**(deterministic) chaos**. We will build up to that by looking at two simpler models of dynamics, in 1D and 2D.
"""

# ╔═╡ 822c015a-8bb3-4432-a2d4-2e2ba4f906a6
md"""
## Reminder: Ordinary Differential Equations (ODEs)
"""

# ╔═╡ 816de40c-220c-11eb-2d3a-23e6267cd529
md"""
Recall that we are looking at **differential equations**, with the goal of modelling the evolution in time of the climate.

The simplest such models are **ordinary differential equations** (ODEs), where one or a few continuous variables evolve continuously in time, with a model that specifies their instantaneous rate of change as a function of their current values.

The general form of an ODE (that does not depend explicitly on time, i.e. that is **autonomous**), is

$$\frac{dx(t)}{dt} = f(x(t)),$$

or

$$\dot{x}(t) = f(x(t)),$$

with an initial condition $x(t=0) = x_0$.

Here $\dot{x}(t)$ denotes the derivative of the function $t \mapsto x(t)$ at time $t$.
"""

# ╔═╡ d21f6358-220c-11eb-00b8-03fe0746fdc9
md"""
We have seen that the simplest numerical method to solve such an equation is the **(forward) Euler method**, in which we convert this equation into an explicit time-stepping routine: we take a small time step of length $h$ and approximate the derivative as

$$\frac{dx(t)}{dt} \simeq \frac{x(t + h) - x(t)}{h},$$

giving the numerical method

$$x_{n+1} = x_{n} + h f(x_n),$$

where $x_n$ is the approximation of the true solution $x(t_n)$ at the $n$th time step $t_n$.

"""

# ╔═╡ 735bb47e-220d-11eb-1ba6-8d591a935857
md"""
# 1D: Modelling bacterial growth
"""

# ╔═╡ 79b148b6-220d-11eb-2785-25d05068aeed
md"""
Let's use this to simulate a simple nonlinear ODE describing the dynamics of a population of bacteria. The bacteria reproduce at a rate $\lambda$ *provided* that there is sufficient food, in which case we would have $\dot{x} = \lambda x$. But the need for enough food will put a *limit* on the sustainable population to a value $K$, sometimes called the **carrying capacity**.

The simplest model for the combined effect of growth and saturation is as follows:

$$\dot{x} = \lambda \, x \, (K - x).$$

When $x$ is close to $0$, the growth rate is $\lambda$, but that rate decreases as $x$ increases.

(This is sometimes called the [**logistic**](https://en.wikipedia.org/wiki/Logistic_function#Logistic_differential_equation) differential equation, although that name does not seem particularly helpful.)
"""

# ╔═╡ d43a869e-220d-11eb-009c-a119de57e7da
md"""
Our goal is to use computational thinking, but we will actually not be interested so much in the exact dynamics in time, but rather in the **qualitative** features of the behaviour of the system. For example, at long times (formally $t \to \infty$) does the population get arbitrarily large? Or does it, for example, oscillate around a particular value? Or does it converge to a particular size? This forms the subject of **nonlinear dynamics** or **dynamical systems** theory.
"""

# ╔═╡ 088eabb2-220e-11eb-1ac0-df419b87f39a
md"""
Let's simulate the system using the Euler method to try to guess the answer to this question. Note that there are many much more sophisticated methods for solving ODEs collected in the DifferentialEquations.jl package. We should never use the Euler method in practice, but should use a tried and tested library instead, and algorithms that provide much better accuracy in the solutions, if we are interested in faithful numerical results.
"""

# ╔═╡ a44cc180-22d4-11eb-2129-211d024c4921
md"""
It's useful to rescale the variables to the simplest form:

$$\dot{x} = x \, (1 - x).$$

[We can do so by defining new adimensional space and time variables $x'$ and $t'$ as follows: $x = K \, x'$, giving $K \dot{x'} = \lambda K \, x' (K - K x')$, and then $t' = \lambda K \, t$, giving $dx' / dt' = x' (1 - x')$.]
"""

# ╔═╡ 57566646-1b4c-4098-abf7-eb242d01ca81
md"""
We define a function representing the right-hand side of the ODE:
"""

# ╔═╡ 6aa1d2f4-220e-11eb-06e0-c346f74aa018
logistic(x) = x * (1 - x)

# ╔═╡ b6d0dd18-22d4-11eb-2083-0b02de030579
md"""
Let's simulate this with the Euler method and plot the trajectory $x(t)$ as a function of time $t$:
"""

# ╔═╡ 6e90ea26-220e-11eb-0c65-bf52b3d2e195
results = euler(logistic, 0.5, 0.01, 20.0)

# ╔═╡ 2d1c6abf-3b15-4638-88c5-89b5d0585c98
md"""
Normally we would *not* choose the Euler method to calculate actual trajectories, but it is enough for our purposes of understanding the *qualitative* behaviour of the equations.
"""

# ╔═╡ f055be76-22d4-11eb-268a-bf70c7d8f1a1
md"""
We see that for this particular initial condition, the solution seems to settle down to a fixed value after some time, and then remains at that value thereafter.

Such a value is called a **fixed point**, a **stationary point**, or a **steady state** of the ODE.
"""

# ╔═╡ 96e22792-220e-11eb-2729-63964507b5f2
begin
	plot(results.ts, results.xs, size=(400, 300), leg=false, xlabel=L"t", ylabel=L"x(t)", lw=3)
	scatter!([(results.ts[1], results.xs[1])])
	ylims!(0.4, 1.1)
end

# ╔═╡ 90ccb392-2216-11eb-1fd8-83b7d7c16b54
md"""
## Qualitative behaviour: Fixed points and their stability
"""

# ╔═╡ 0bf302de-4a0f-4341-b010-d3606e237acc
# gr(fmt=:png, dpi=150, size=(300, 200))
# gr(fmt=:svg)

gr(fmt=:png, dpi=300, size=(400, 300))

# ╔═╡ bef9c2e4-220e-11eb-24d8-bd618d2985ea
md"""
 Let's see what happens for other initial conditions:
"""

# ╔═╡ 17317eb8-22d5-11eb-0f46-2bf1bddd36bb
md"""
x₀ = $(@bind x0 Slider(-0.9:0.001:3.0, default=0.5, show_value=true))
"""

# ╔═╡ 2300b29a-22d5-11eb-3c99-bdec0e5a2685
let
	p = plot(xlabel=L"t", ylabel=L"x(t)",  leg=false, ylim=(-1, 2))

	results = euler(logistic, x0, 0.01, 5.0)

	plot!(results.ts, results.xs, alpha=1, lw=3)
	scatter!([0.0], [x0])

	hline!([0.0], ls=:dash)
	# as_svg(p)
end

# ╔═╡ ae862e10-22d5-11eb-3d75-1d748cf86944
md"""
To get an overview of the behaviour we can draw all the results on a single graph:
"""

# ╔═╡ c5bf15b1-a540-478b-acec-1aa47aad13d1
let
	p = plot(xlabel=L"t", ylabel=L"x(t)", leg=false, ylim=(-1, 2))

	# for x0 in -0.5:0.05:2.0
#for x0 in 0.0:0.1:2.0
	for x0 in -0.5:0.1:2.0
	results = euler(logistic, x0, 0.05, 5.0)

# 	for x0 in -0.5:0.05:2.0
# 		results = euler(logistic, x0, 0.01, 10.0)

		plot!(p, results.ts, results.xs, alpha=0.8, lw=1, arrow=true)
	end

	md"""
	#### Trajectories for $ẋ = x(1-x)$

	$p

	"""
end

# ╔═╡ 446c564e-220f-11eb-191a-6b419e790f3f
md"""
We see that all the curves starting near to $x_0=1.0$ seem to converge to 1 at long times. If the system starts *exactly* at 0 then it stays there forever. However, if it starts close to 0, on either side, then it moves *away* from 0 (on that same side of 0) -- starting from a negative value $x$ becomes ever more negative. (Even though negative populations have no meaning in the original interpretation as the dynamics of a population, we can still ask study the dynamics of the equation with negative initial conditions, since it may model other systems too.)

The special values $x^*_1=1$ and $x^*_2=0$ are called **stationary points** or **fixed points** of the differential equation. If we start at $x^*_i$, then the derivative there is $f'(x^*_i) = 0$, and hence we cannot move away from $x^*_i$! The fixed points can be found as zeros or **roots** of the function $f$, i.e. values $x^*$ such that $f(x^*) = 0$.
"""

# ╔═╡ 38894c32-2210-11eb-26ff-d9796edd7871
md"""
We see, though, that the two types of fixed points are **qualitatively different**: trajectories that start close to $x^*_1 = 1$ move *towards* $x^*_1$, whereas trajectories that start close to $x^*_2 = 0$ move *away* from it. We say that $x^*_1$ is a **stable fixed point** and $x^*_2$ is an **unstable fixed point**.

In general it is not possible to find analytical formulas for the position and stability of fixed points; instead, we can use numerical **root-finding algorithms**, for example the Newton method. There are various Julia packages with implementations of these algorithms; we will use `Roots.jl`, which works well for scalar-valued functions.
"""

# ╔═╡ a196a20a-2216-11eb-11f9-6789bb2a3ece
md"""
## State space: Vector field and phase portrait
"""

# ╔═╡ c15a53e8-2216-11eb-1460-8510aea0f570
md"""
If we want to find the whole trajectory for a given initial condition then we need to solve the equations, either numerically or analytically.

However, we may want less information about the system, for example the **long-time** or **asymptotic** dynamics. It turns out that we can obtain some information about that *without* explicitly solving the ODE! This is the **qualitative approach** to studying nonlinear systems.

Instead of drawing trajectories $x(t)$ as a function of time $t$, as we did above, let's use a different graphical representation, where we draw **state space** or **phase space**: This is the set ("space") of all possible values of the dependent variables ("states"). For the above ODE there is only a single dependent variable, $x$, so the state space is the real line, $\mathbb{R}$.

At each possible value of $x$, the ODE gives us information about the rate of change of $x(t)$ at that point. Let's draw an **arrow** at that point, pointing in the direction that a particle placed at that point would move: to the right if $\dot{x} > 0$ and to the left if $\dot{x} < 0$.
"""

# ╔═╡ 9a833edc-2217-11eb-0701-99862b410bfa
vector_field(logistic)

# ╔═╡ 42c28b16-2218-11eb-304a-e534353fa12b
md"""
This vector field indeed gives us a *qualitative* picture of the dynamics. It does not tell us how fast the dynamics will occur in each region, but it indicates what the *tendency* is. We have coded the fixed points according to their stability; this may be calculated using the derivative evaluated at the fixed point, $f'(x^*)$, since this derivative controls the behaviour of nearby initial conditions $x^* + \delta x$.
The unstable fixed point is shown as a green square, and the stable fixed point as a grey circle; we will use this convention throughout the notebook.
"""

# ╔═╡ e6ed3366-2246-11eb-3a86-f375162c746d
md"""
## Bifurcations
"""

# ╔═╡ ec3c5d9c-2246-11eb-1ec5-e3d9c8a7fa23
md"""
Now suppose that there is a **parameter** $\mu$ in the system that can be varied. For each value of $\mu$ we have a *different* ODE

$$\dot{x} = f_\mu(x).$$

For example,
$$\dot{x} = \mu + x^2.$$

Let's draw the state space for each different value of $\mu$:
"""

# ╔═╡ 3fa21738-2247-11eb-09d5-4dcf0e7377fc
g(μ, x) = μ + x^2

# ╔═╡ 19eb987e-2248-11eb-2982-1930b5840aed
md"""
μ = $(@bind λ Slider(-1.0:0.05:1, show_value=true))
"""

# ╔═╡ 25578fa6-2248-11eb-2838-57fbfb928fc4
begin
	vector_field(x -> g(λ, x))
end

# ╔═╡ 49ec803a-22d7-11eb-3f04-db8f8c119a7d
md"""
Now let's collect all the vector fields into a single plot. The horizontal axis now represents the different possible values of the parameter $\mu$:
"""

# ╔═╡ b9ee4db4-22d7-11eb-38ef-511e30df16cc
bifurcation_diagram(g)

# ╔═╡ 27190ac0-230b-11eb-3367-af2bbbf57e5e
md"""
We see that at the **critical value** $\mu_c = 0$ there is a **qualitative change in behaviour** of the system: for $\mu_c < 0$ there are two fixed points, whereas for $\mu_c > 0$ there are no fixed points at all. Such a qualitative change is called a **bifurcation**. In this particular case the two fixed points collide in a **saddle--node** or **fold** bifurcation.
"""

# ╔═╡ 48ef665e-22d0-11eb-2c72-b722fafd296e
md"""
# 1D: Bistability and hysteresis
"""

# ╔═╡ 51eeb46c-22d0-11eb-1643-c92e725d9da0
md"""
Now let's look at the dynamics of the following system:

$$\dot{x} = \mu + x - x^3.$$
"""

# ╔═╡ 44c1738a-2247-11eb-3232-f30b46fd7ce6
h(μ, x) = μ + x - x^3

# ╔═╡ 7a94642e-39d8-480d-9516-d11e6c1b078b
md"""
Let's plot the bifurcation diagram again:
"""

# ╔═╡ f040d1c4-2247-11eb-0bf0-090e90a86a85
bifurcation_diagram(h)

# ╔═╡ a56ad0bc-22d7-11eb-0418-d36f3ef14109
md"""
We see that there is a range of values of $\mu$ for which there are *three coexisting fixed points*, two stable and one unstable. Since there are two stable fixed points in which the system can remain, we say that the system is **bistable**.
"""

# ╔═╡ 5b9c66ce-224b-11eb-02a4-b9a5a173ee6a
md"""
Now that we understand what the plots mean and the dynamics, let's plot just the fixed points $x^*(\mu)$ as a function of $\mu$. Such a plot is called a **bifurcation diagram**:
"""

# ╔═╡ 0c3c88fe-2249-11eb-03af-bf9c393fadd4
fixed_points(h)

# ╔═╡ aa81663c-230b-11eb-3c0e-39759f36deb6
md"""
The pieces of curve are called **branches**.
"""

# ╔═╡ 55d4d188-2249-11eb-0c47-dfddce9b2027
md"""
## Hysteresis
"""

# ╔═╡ 5ac33518-2249-11eb-2d57-7de185d43e5f
md"""
Suppose we now think about slowly varying the parameter $\mu$. If we change the parameter $\mu$ by a little, the system is no longer at a fixed point, since the position of the fixed point moves when $\mu$ changes. However, the system will then **relax**: it will follow the dynamics at the new value of $\mu$, and will rapidly converge to the new fixed point nearby for that new value of $\mu$.

For example, starting at $\mu=-2$, the system will stay on the lower black (stable) **branch** until $\mu=0.4$ or so. At that point, two fixed points collide and annihilate each other! After that there is no longer a fixed point nearby. However, there is another fixed point much further up that will now attract all trajectories, so the system rapidly transitions to that fixed point.

Now suppose we decrease the parameter again. The system will now track the *upper* branch until $\mu=-0.4$ or so, when again it will jump back down.

For each parameter value $\mu$ in the interval $[-0.4, 0.4]$ there is **bistability**, i.e. **coexistence** of *two* fixed points with the same value of $\mu$ (together with a third, unstable fixed point that is not observable).

The fact that the system tracks different stable branches depending on where we started, i.e. on the history of the dynamics, is known as **hysteresis**.
"""

# ╔═╡ 4c73705e-230c-11eb-3c90-b14536d78808
md"""
Hysteretic behaviour like this is found in many scientific and engineering contexts,  including switches in biology, for example genetic switches, and in the historical dynamics of the earth's climate.
"""

# ╔═╡ 2ced9e26-22d0-11eb-34a5-bf80822f5c43
md"""
## Slow--fast systems
"""

# ╔═╡ 3213d406-22d0-11eb-0e89-71b42ea5cada
md"""
What are we actually doing when we let the parameter $\mu$ vary? Effectively we now have a system of *two* coupled ODEs, for example

$$\dot{x} = \mu + x - x^3;$$
$$\dot{\mu} = \epsilon,$$

where $\mu$ varies at a slow speed $\epsilon$. On a time scale much shorter than $1 / \epsilon$, the dynamics of $x$ "does not know" that $\mu$ is changing, so it will converge to a fixed point $x^*(\mu)$ for the current value of $\mu$. [An associated term is **adiabatic approximation**.] However, in fact $\mu$ does gradually change, so the value of $x$ will effectively "slide along" the curve $x(t) \simeq x^*(\mu(t))$, tracking the curve of fixed points as $\mu$ changes.

Once $\mu$ reaches a critical value $\mu_c$, however, there is no longer a nearby fixed point to move to, and the vector field will move the system to rapidly transition to the far away alternative fixed point.

If we now reverse the dynamics of $\mu$, we slide back along the upper branch.
"""

# ╔═╡ bcce1286-2213-4f25-a947-7a429bd96594
md"""
# 2D: Oscillations in chemical reactions -- the Brusselator model
"""

# ╔═╡ 77ae220e-08bd-4808-b1f5-f80397afe9bc
md"""
Bifurcations are not restricted to 1D systems; indeed, the dynamics can be much richer in higher dimensions.

For example, let's look at the [Brusselator model](https://en.wikipedia.org/wiki/Brusselator). This models a chemical reaction that **oscillates**, known as a [chemical clock](https://en.wikipedia.org/wiki/Chemical_clock):
"""

# ╔═╡ c48b8097-d81e-43a1-a61f-b2d01edbc8d5
md"""
$$\begin{aligned}
\dot{x} &= a + x^2 y - bx - x \\
\dot{y} &= b x - x^2 y
\end{aligned}$$
"""

# ╔═╡ 6b857cb0-182f-4451-9f5a-e170dae185c1
function brusselator(xx, p, t)
	x, y = xx
	a, b = p

	return [a + x^2 * y - b*x - x,
			b * x - x^2 * y]
end

# ╔═╡ e2953d11-6880-45cb-8000-3f6903a85c7d
begin
	u0 = [1, 1]
	tspan = (0.0, 50.0)
	params = [a, b]
end

# ╔═╡ cbb9e351-b496-45cc-8816-519ada1dc5d5
begin
	prob = ODEProblem(brusselator, u0, tspan, params)
	soln = solve(prob)
end;

# ╔═╡ 77e5ff47-1e95-4552-a3d2-2fdf7bbea7cb
md"""
a = $(@bind a Slider(0.0:0.1:5.0, show_value=true, default=1.0))

b = $(@bind b Slider(0.0:0.1:5.0, show_value=true, default=1.5))
"""

# ╔═╡ 92aca04d-376f-4ac7-b4c3-5b0652974157
gr(dpi=300)

# ╔═╡ 34556546-0cab-43e2-bc2c-f7487288392c
let
	tspan = (0.0, 10.0)
	params = [a, b]

	p1 = plot(leg=false, background_color_inside=:black)

	for x in 0:1.0:5
		for y in 0:1.0:5
			u0 = [x, y]

			prob = ODEProblem(brusselator, u0, tspan, params)
			soln = solve(prob)

			plot!(p1, soln, vars=(1, 2), xlims=(0, 5), ylims=(0, 5), ratio=1, lw=1.5)
#	p2 = plot(soln)

#	plot(p1, p2, ylims=(0, 5))
		end
	end

	# plot direction field:
	xs = Float64[]
	ys = Float64[]

	for x in 0:0.1:5
		for y in 0:0.1:5
			v = brusselator([x, y], params, 0)
			v ./= (norm(v) * 30)
			# plot!([x, x + v[1]], [y, y + v[2]], alpha=0.5, c=:gray)

			push!(xs, x - v[1], x + v[1], NaN)
			push!(ys, y - v[2], y + v[2], NaN)

		end
	end

	plot!(xs, ys, alpha=0.7, c=:gray)

	# as_svg(p1)

	md"""
	#### Trajectories in state space for the Brusselator model

	$(p1)

	"""

end

# ╔═╡ c90e3885-33bd-40d2-8a64-946fd7676656
md"""
We see that there is a critical value of $b$ above which the fixed point becomes unstable and gives rise to an attracting periodic orbit. This is a [**Hopf bifurcation**](https://en.wikipedia.org/wiki/Hopf_bifurcation).
"""

# ╔═╡ e01fa79b-e92b-4b77-8234-108839ed2598
md"""
# 3D: Chaos in the Lorenz equations
"""

# ╔═╡ 1d91975b-2644-41da-a2a4-667ae6ed5ca1
md"""
The [Lorenz equations](https://en.wikipedia.org/wiki/Lorenz_system) form a (very) simplified model of convection in a layer of fluid representing the atmosphere, first investigated in a famous paper by Edward Lorenz, published in 1963, about work done at MIT.  They represent a pioneering numerical investigation of **chaotic behaviour**.

The Lorenz equations are a set of three coupled ODEs. They have an apparently simple form, with only two nonlinear terms:
"""

# ╔═╡ d99002e1-02ca-45bd-ba26-581f7f1b4fe8
md"""
$$\begin{align}
\dot{x} &= \sigma (y - x) \\[6pt]
\dot{y} &= x (\rho - z) - y \\[6pt]
\dot{z} &= x y - \beta z
\end{align}$$
"""

# ╔═╡ 64642233-3331-40ea-929b-be5daa933393
md"""
Nonetheless, we will see that they can exhibit remarkably complex behaviour. Indeed, most nonlinear ODEs with at least three variables can be expected to exhibit similarly complicated behaviour.
"""

# ╔═╡ 2228c7f6-8692-487e-bf8a-6600e7e93a06
md"""
Let's solve the system using `DifferentialEquations.jl`. We will take the classical parameter values $\sigma = 10$ and $\beta = 8/3$, but we will allow $\rho$ to vary; its classical value is $28$.
"""

# ╔═╡ cfa7b192-f1a0-4cd1-ae34-cec07e7b47f6
function lorenz(u, p, t)
	x, y, z = u
	σ, ρ, β = p

	dx = σ * (y - x)
	dy = x * (ρ - z) - y
	dz = x * y - β * z

	return [dx, dy, dz]
end

# ╔═╡ 2e61c129-7fd8-46b6-8480-40f0244aab47
begin
	lorenz_prob = ODEProblem(lorenz, [0.01, 0.01, 0.01], (0.0, 100.0), lorenz_params)

	lorenz_soln = solve(lorenz_prob)
end;

# ╔═╡ 58dbd153-09e2-4a38-94c1-9f69acf515ae
lorenz_params = (σ = 10.0, ρ = ρ, β = 8/3)

# ╔═╡ e068c809-34c6-4723-8ad5-a55918cfed87
md"""
ρ = $(@bind ρ Slider(0.0:0.1:100.0, show_value=true, default=10.0))
"""

# ╔═╡ 5ffeba9b-8487-4be1-bb64-8d2330824ee5
plot(lorenz_soln, vars=(1, 2, 3), xlabel="x", ylabel="y", zlabel="z", xlims=(-25, 25), ylims=(-25, 25), zlims=(0, 60))

# ╔═╡ a7f067e4-d088-400b-bba4-d58f9cfb87a8
md"""
As $\rho$ increases we see a sequence of bifurcations. Above a critical value, trajectories converge to a fractal **strange attractor**, on which the dynamics is **chaotic**.
"""

# ╔═╡ cc7ee950-6b86-4d43-8524-86254978bd1b
md"""
Deterministic chaos occurs when nearby initial conditions separate exponentially fast in state space. This has been given the name **butterly effect**: the perturbation to the atmosphere's state caused by a butterfly flapping its wings could end up being magnified to modify the direction in which a tornado moves.

We can see this in a simple way by perturbing the initial condition slightly and calculating the distance between the two systems as a function of $t$:
"""

# ╔═╡ aebdd083-4f70-4490-9949-8fe5c1cb2e1e
begin
	ϵ = 1e-10

	lorenz_prob2 = ODEProblem(lorenz, [0.01 + ϵ, 0.01 + ϵ, 0.01 + ϵ], (0.0, 50.0), lorenz_params)

	lorenz_soln2 = solve(lorenz_prob2)
end;

# ╔═╡ f44c9f92-aacd-4556-b494-8ee874387e5d
begin
	ts = 0:0.01:50
	distances = [norm(lorenz_soln2(t) - lorenz_soln(t)) for t in ts]
end

# ╔═╡ c2615e16-93d1-4db0-af18-0c1a758cd58a
plot(ts, distances, yscale=:log10, label="distance", xlabel="t", leg=:topleft)

# ╔═╡ 0b1b4e0e-d146-4c94-bfee-3d0a50323d0b
md"""
We indeed see a significant window over which the distance grows exponentially (straight line on the semi-logarithmic graph) for large enough values of $\rho$, before it saturates at a value of the order of the diameter of the attractor.

The rate of the exponential growth is known as the **Lyapunov exponent**; there are better ways to measure it more accurately.
"""

# ╔═╡ b8423240-1761-490d-a856-f722714cc763
md"""
Since the Lorenz equations model the atmosphere, we could expect that the dynamics of the atmosphere is at least as complicated.
"""

# ╔═╡ 8eb1c83e-18f2-497a-94fd-a4abe11beed3
md"""
We can also plot the $x$ coordinate of each trajectory for comparison:
"""

# ╔═╡ 3188c2ca-ab8f-45a9-bcbc-10159659a9d4
begin
	plot(lorenz_soln, vars=1, label="original", size=(500, 300), leg=:topleft)
	plot!(lorenz_soln2, vars=1, label="perturbed")
	ylabel!("x(t)")
end

# ╔═╡ 75f3d63d-aa5d-46ce-824c-72153623d2c5
md"""
At long enough times the trajectories separate and behave very differently.
"""

# ╔═╡ 019be011-51ed-4997-8e8e-1035c109efe7
md"""
Now let's look at the "climate" in this model, namely the average value of each coordinate over a long time:
"""

# ╔═╡ edd470f6-828e-47f4-b2d1-cb16f6103158
mean(v) = sum(v) / length(v)

# ╔═╡ 5c27ade2-5983-4332-9097-20efb20504bc
begin
	T = 1000.0

	lorenz_prob3 = ODEProblem(lorenz, [1 + ϵ, 1 + ϵ, 1 + ϵ], (0.0, T), lorenz_params)

	lorenz_soln3 = solve(lorenz_prob3)

	lorenz_prob4 = ODEProblem(lorenz, [1, 1, 1], (0.0, T), lorenz_params)

	lorenz_soln4 = solve(lorenz_prob4)
end;

# ╔═╡ 043c35e5-bf4b-445a-b746-9030b96033ca
mean(abs.(lorenz_soln3(t)) for t in T/2:T)

# ╔═╡ d9b34169-85a2-4f97-a7e3-a2ed67ee205a
mean(abs.(lorenz_soln4(t)) for t in T/2:T)

# ╔═╡ 02f295ba-6bb1-41ca-9581-af33766241a4
md"""
We see that the average mean value of each component is approximately the same, even though the individual trajectories are wildly different. This is an example of how statistical properties can be the same, even if individual behaviour is very different, and motivates the idea that climate -- i.e. "average weather" -- can be stable, even if day-to-day variations differ a lot.
"""

# ╔═╡ d0918f0c-22d4-11eb-383a-553e33114c52
md"""
# Function library
"""

# ╔═╡ 319c2ba8-220e-11eb-1ee0-4dd51ff4cd25
euler_step(f, x, h) = x + h * f(x)

# ╔═╡ 42de6dd6-220e-11eb-2a48-a3c69b10a03b
function euler(f, x0, h, t_final)
	ts = [0.0]
	xs = [x0]

	x = x0
	t = 0.0

	while t < t_final
		x = euler_step(f, x, h)
		t += h

		push!(xs, x)
		push!(ts, t)
	end

	return (ts=ts, xs=xs)  # a named tuple
end

# ╔═╡ 546168ce-2218-11eb-198b-1da9f8a9a242
derivative(f, x, h=0.001) = ( f(x + h) - f(x - h) ) / (2h)

# ╔═╡ 46f1e3a2-2217-11eb-184f-f5649b892004
"Draw 1D vector field using centred arrows"
function horiz_vector_field(f)

	xlo, xhi = -2, 2.5
	arrow_size = 0.07
	tol=1e-5  # tolerance to check for fixed point

	p = plot(size=(400, 100), xlim=(xlo, xhi), ylim=(-0.5, 0.5), leg=false, yticks=[])

	for x in xlo:0.2:xhi
		d = f(x)  # derivative

		if d > tol
			plot!([x-arrow_size, x+arrow_size], [0, 0], arrow=true, c=:blue, alpha=0.5, lw=1.5)

		elseif d < -tol
			plot!([x+arrow_size, x-arrow_size], [0, 0], arrow=true, c=:red, alpha=0.5, lw=1.5)

		end

	end

	roots = find_zeros(f, -10, 10)

	for root in roots
		stability = sign(derivative(f, root))

		if stability > 0
			scatter!([root], [0], c=:green, alpha=0.5, m=:square, ms=5)  # unstable
		else
			scatter!([root], [0], c=:black, alpha=0.5, ms=6)  # stable
		end

	end

	p
end

# ╔═╡ 2b1b49b0-2247-11eb-1d25-57f3b4f46d04
"Draw vertical vector field of 1D ODE on plot p with parameter μ"
function vector_field!(p, μ, f)

	xlo, xhi = -2, 2
	arrow_size = 0.07
	tol=1e-5  # tolerance to check for fixed point

	for x in xlo:0.2:xhi
		d = f(x)  # derivative

		if d > tol

			plot!([μ, μ], [x-arrow_size, x+arrow_size], arrow=(3.0, 2.0), arrowstyle=:triangle, c=:blue, alpha=0.4, lw=1.5)

		elseif d < -tol
			plot!([μ, μ], [x+arrow_size, x-arrow_size], arrow=true, c=:red, alpha=0.4, lw=1.5)
		end

	end

	roots = find_zeros(f, -10, 10)

	for root in roots
		stability = sign(derivative(f, root))

		if stability > 0
			scatter!([μ], [root], c=:green, alpha=0.5, m=:square, ms=2)  # unstable
		else
			scatter!([μ], [root], c=:black, alpha=0.5, ms=3)  # stable
		end

	end

	p
end

# ╔═╡ d4fa7bec-2371-11eb-3e9f-69d552546331
function vector_field(f)
	p = plot(leg=false, xticks=[], xrange=(-1, 1), size=(100, 200))
	vector_field!(p, 0, f)
end

# ╔═╡ e55d2780-2247-11eb-01e0-fbdc94bba264
function bifurcation_diagram(h)
	p = plot(leg=false, ratio=1)

	for μ in -2:0.15:2
		vector_field!(p, μ, x -> h(μ, x))
	end

	xlabel!("μ")
	ylabel!("fixed points and dynamics with given μ")

	return p
end

# ╔═╡ e27dace4-2248-11eb-2ae1-953639d8c944
function fixed_points(f)
	p = plot(leg=false, ratio=1)

	for μ in -2:0.01:2

		roots = find_zeros(x -> f(μ, x), -10, 10)

		for root in roots
			stability = sign(derivative(x -> f(μ, x), root))

			if stability > 0
				scatter!([μ], [root], c=:green, alpha=0.5, m=:square)  # unstable
			else
				scatter!([μ], [root], c=:black, alpha=0.5)  # stable
			end
		end

	end

	xlabel!(L"\mu")
	ylabel!(L"x^*(\mu)")

	return p
end

# ╔═╡ Cell order:
# ╟─1f65fc80-a83b-11eb-2583-89bff1ea8372
# ╠═88b46d2e-220e-11eb-0f7f-b3f523f0214e
# ╠═f9dbf2e8-574d-4846-af48-f7e5a82a1afc
# ╟─a2b2eae2-762e-41c3-a546-0caedc79db7d
# ╟─91b34e59-e4d2-45b7-9e2b-1b95748e5d6a
# ╟─22bddfca-fd1c-4dc7-85f3-f864c3b53407
# ╟─6f554dc0-220c-11eb-341f-1b66de841c86
# ╟─2c6f4b31-2f08-4e24-8e31-ad80d2700fa8
# ╟─822c015a-8bb3-4432-a2d4-2e2ba4f906a6
# ╟─816de40c-220c-11eb-2d3a-23e6267cd529
# ╟─d21f6358-220c-11eb-00b8-03fe0746fdc9
# ╟─735bb47e-220d-11eb-1ba6-8d591a935857
# ╟─79b148b6-220d-11eb-2785-25d05068aeed
# ╟─d43a869e-220d-11eb-009c-a119de57e7da
# ╟─088eabb2-220e-11eb-1ac0-df419b87f39a
# ╟─a44cc180-22d4-11eb-2129-211d024c4921
# ╟─57566646-1b4c-4098-abf7-eb242d01ca81
# ╠═6aa1d2f4-220e-11eb-06e0-c346f74aa018
# ╟─b6d0dd18-22d4-11eb-2083-0b02de030579
# ╠═6e90ea26-220e-11eb-0c65-bf52b3d2e195
# ╟─2d1c6abf-3b15-4638-88c5-89b5d0585c98
# ╟─f055be76-22d4-11eb-268a-bf70c7d8f1a1
# ╟─96e22792-220e-11eb-2729-63964507b5f2
# ╟─90ccb392-2216-11eb-1fd8-83b7d7c16b54
# ╠═0bf302de-4a0f-4341-b010-d3606e237acc
# ╟─bef9c2e4-220e-11eb-24d8-bd618d2985ea
# ╟─17317eb8-22d5-11eb-0f46-2bf1bddd36bb
# ╟─2300b29a-22d5-11eb-3c99-bdec0e5a2685
# ╟─ae862e10-22d5-11eb-3d75-1d748cf86944
# ╟─c5bf15b1-a540-478b-acec-1aa47aad13d1
# ╟─446c564e-220f-11eb-191a-6b419e790f3f
# ╟─38894c32-2210-11eb-26ff-d9796edd7871
# ╟─a196a20a-2216-11eb-11f9-6789bb2a3ece
# ╟─c15a53e8-2216-11eb-1460-8510aea0f570
# ╠═9a833edc-2217-11eb-0701-99862b410bfa
# ╟─42c28b16-2218-11eb-304a-e534353fa12b
# ╟─e6ed3366-2246-11eb-3a86-f375162c746d
# ╟─ec3c5d9c-2246-11eb-1ec5-e3d9c8a7fa23
# ╠═3fa21738-2247-11eb-09d5-4dcf0e7377fc
# ╟─19eb987e-2248-11eb-2982-1930b5840aed
# ╠═25578fa6-2248-11eb-2838-57fbfb928fc4
# ╟─49ec803a-22d7-11eb-3f04-db8f8c119a7d
# ╠═b9ee4db4-22d7-11eb-38ef-511e30df16cc
# ╟─27190ac0-230b-11eb-3367-af2bbbf57e5e
# ╟─48ef665e-22d0-11eb-2c72-b722fafd296e
# ╟─51eeb46c-22d0-11eb-1643-c92e725d9da0
# ╠═44c1738a-2247-11eb-3232-f30b46fd7ce6
# ╟─7a94642e-39d8-480d-9516-d11e6c1b078b
# ╠═f040d1c4-2247-11eb-0bf0-090e90a86a85
# ╟─a56ad0bc-22d7-11eb-0418-d36f3ef14109
# ╟─5b9c66ce-224b-11eb-02a4-b9a5a173ee6a
# ╠═0c3c88fe-2249-11eb-03af-bf9c393fadd4
# ╟─aa81663c-230b-11eb-3c0e-39759f36deb6
# ╟─55d4d188-2249-11eb-0c47-dfddce9b2027
# ╟─5ac33518-2249-11eb-2d57-7de185d43e5f
# ╟─4c73705e-230c-11eb-3c90-b14536d78808
# ╟─2ced9e26-22d0-11eb-34a5-bf80822f5c43
# ╟─3213d406-22d0-11eb-0e89-71b42ea5cada
# ╟─bcce1286-2213-4f25-a947-7a429bd96594
# ╟─77ae220e-08bd-4808-b1f5-f80397afe9bc
# ╟─c48b8097-d81e-43a1-a61f-b2d01edbc8d5
# ╠═6b857cb0-182f-4451-9f5a-e170dae185c1
# ╠═e2953d11-6880-45cb-8000-3f6903a85c7d
# ╠═cbb9e351-b496-45cc-8816-519ada1dc5d5
# ╟─77e5ff47-1e95-4552-a3d2-2fdf7bbea7cb
# ╠═92aca04d-376f-4ac7-b4c3-5b0652974157
# ╠═34556546-0cab-43e2-bc2c-f7487288392c
# ╟─c90e3885-33bd-40d2-8a64-946fd7676656
# ╟─e01fa79b-e92b-4b77-8234-108839ed2598
# ╟─1d91975b-2644-41da-a2a4-667ae6ed5ca1
# ╟─d99002e1-02ca-45bd-ba26-581f7f1b4fe8
# ╟─64642233-3331-40ea-929b-be5daa933393
# ╟─2228c7f6-8692-487e-bf8a-6600e7e93a06
# ╠═cfa7b192-f1a0-4cd1-ae34-cec07e7b47f6
# ╠═2e61c129-7fd8-46b6-8480-40f0244aab47
# ╠═58dbd153-09e2-4a38-94c1-9f69acf515ae
# ╟─e068c809-34c6-4723-8ad5-a55918cfed87
# ╠═5ffeba9b-8487-4be1-bb64-8d2330824ee5
# ╟─a7f067e4-d088-400b-bba4-d58f9cfb87a8
# ╟─cc7ee950-6b86-4d43-8524-86254978bd1b
# ╠═aebdd083-4f70-4490-9949-8fe5c1cb2e1e
# ╠═f44c9f92-aacd-4556-b494-8ee874387e5d
# ╠═c2615e16-93d1-4db0-af18-0c1a758cd58a
# ╟─0b1b4e0e-d146-4c94-bfee-3d0a50323d0b
# ╟─b8423240-1761-490d-a856-f722714cc763
# ╟─8eb1c83e-18f2-497a-94fd-a4abe11beed3
# ╟─3188c2ca-ab8f-45a9-bcbc-10159659a9d4
# ╟─75f3d63d-aa5d-46ce-824c-72153623d2c5
# ╟─019be011-51ed-4997-8e8e-1035c109efe7
# ╠═edd470f6-828e-47f4-b2d1-cb16f6103158
# ╠═5c27ade2-5983-4332-9097-20efb20504bc
# ╠═043c35e5-bf4b-445a-b746-9030b96033ca
# ╠═d9b34169-85a2-4f97-a7e3-a2ed67ee205a
# ╟─02f295ba-6bb1-41ca-9581-af33766241a4
# ╟─d0918f0c-22d4-11eb-383a-553e33114c52
# ╠═319c2ba8-220e-11eb-1ee0-4dd51ff4cd25
# ╠═42de6dd6-220e-11eb-2a48-a3c69b10a03b
# ╠═546168ce-2218-11eb-198b-1da9f8a9a242
# ╠═46f1e3a2-2217-11eb-184f-f5649b892004
# ╠═2b1b49b0-2247-11eb-1d25-57f3b4f46d04
# ╠═d4fa7bec-2371-11eb-3e9f-69d552546331
# ╠═e55d2780-2247-11eb-01e0-fbdc94bba264
# ╟─e27dace4-2248-11eb-2ae1-953639d8c944