@testset "Monodromy" begin
    function toric_ed(A)
        d, n = size(A)
        @var t[1:d] y[1:n] u[1:n]

        φ = map(j -> prod(i -> t[i]^A[i, j], 1:d), 1:n)
        Dφ = [differentiate(φ[i], t[j]) for i = 1:n, j = 1:d]

        System([φ + y - u; Dφ' * y], parameters = u)
    end

    @testset "monodromy_solve" begin
        F = toric_ed([3 2 1 0; 0 1 2 3])

        result = monodromy_solve(
            F,
            target_solutions_count = 21,
            max_loops_no_progress = 20,
            threading = false,
        )
        @test is_success(result)
        @test length(solutions(result)) == 21
        @test isempty(multiplicities(solutions(result)))
        @test isempty(sprint(show, result)) == false

        # test seed
        result2 = monodromy_solve(
            F,
            target_solutions_count = 21,
            max_loops_no_progress = 20,
            threading = false,
            seed = result.seed,
        )
        @test result2.statistics.tracked_loops[] == result.statistics.tracked_loops[]

        result = monodromy_solve(
            F,
            target_solutions_count = 21,
            max_loops_no_progress = 20,
            threading = true,
        )
        @test is_success(result)
        @test length(solutions(result)) == 21

        # test that timeout works
        result_timeout = monodromy_solve(F, target_solutions_count = 21, timeout = 1e-12)
        @test length(solutions(result_timeout)) < 21

        # test input of length > 1
        x₀, p₀ = find_start_pair(F)
        result = monodromy_solve(
            F,
            [x₀ for _ = 1:30],
            p₀,
            target_solutions_count = 21,
            max_loops_no_progress = 20,
        )
        @test length(solutions(result)) == 21

        # test with false input
        @test_logs (:warn, "None of the provided solutions is a valid start solution.") (
            result = monodromy_solve(F, [rand(6) for _ = 1:10], p₀)
        )
        @test result.returncode == :invalid_startvalue
        result =
            monodromy_solve(F.expressions, [x₀, rand(6)], p₀, parameters = F.parameters)
        @test length(solutions(result)) == 21

        # different distance function
        result = monodromy_solve(F, x₀, p₀, distance = (x, y) -> 0.0)
        @test length(solutions(result)) == 1

        # Test stop heuristic with no target solutions count
        result = monodromy_solve(F, x₀, p₀)
        @test is_heuristic_stop(result)

        roots_of_unity(s) = begin
            t = cis(π * 2 / 3)
            t² = t * t
            (vcat(t * s[1], t * s[2], s[3:end]), vcat(t² * s[1], t² * s[2], s[3:end]))
        end
        result = monodromy_solve(
            F,
            x₀,
            p₀,
            target_solutions_count = 21,
            max_loops_no_progress = 100,
            equivalence_classes = false,
            group_action = roots_of_unity,
        )
        @test length(solutions(result)) == 21

        # equivalence classes
        result = monodromy_solve(
            F,
            x₀,
            p₀,
            equivalence_classes = true,
            target_solutions_count = 7,
            max_loops_no_progress = 20,
            group_actions = roots_of_unity,
        )
        @test nresults(result) == 7
        # Test that equivalence classes are on by default if we supply a group action
        result = monodromy_solve(
            F,
            x₀,
            p₀,
            group_action = roots_of_unity,
            max_loops_no_progress = 20,
        )
        @test nsolutions(result) == 7

        # AbstractSystem as input
        result = monodromy_solve(
            InterpretedSystem(F),
            group_action = roots_of_unity,
            target_solutions_count = 7,
            max_loops_no_progress = 20,
        )
        @test nsolutions(result) == 7

        # test reuse strategies
        result = monodromy_solve(
            F,
            x₀,
            p₀,
            group_action = roots_of_unity,
            target_solutions_count = 7,
            reuse_loops = :all,
            max_loops_no_progress = 200,
        )
        @test nresults(result) == 7

        result = monodromy_solve(
            F,
            x₀,
            p₀,
            group_action = roots_of_unity,
            target_solutions_count = 7,
            reuse_loops = :random,
            max_loops_no_progress = 200,
        )
        @test nresults(result) == 7

        result = monodromy_solve(
            F,
            x₀,
            p₀,
            group_action = roots_of_unity,
            target_solutions_count = 7,
            reuse_loops = :none,
            max_loops_no_progress = 200,
        )
        @test nresults(result) == 7
    end

    @testset "Method of moments" begin
        f = moments3()
        S₃ = SymmetricGroup(3)
        relabeling(v) = map(p -> [v[1:3][p]..., v[4:6][p]..., v[7:9][p]...], S₃)

        R = monodromy_solve(
            f;
            group_action = relabeling,
            show_progress = false,
            max_loops_no_progress = 1,
        )
        @test nsolutions(R) ≤ 225

        for threading in [true, false]
            R = monodromy_solve(
                f;
                group_action = relabeling,
                show_progress = false,
                max_loops_no_progress = 20,
                target_solutions_count = 225,
                threading = false,
            )
            @test nsolutions(R) == 225
        end
    end

    @testset "Projective + Group Actions" begin
        @var a[1:2] x[1:2] s[1:2] z m[0:5]

        f0 = a[1] + a[2]
        f1 = a[1] * x[1] + a[2] * x[2]
        f2 = a[1] * (x[1]^2 + s[1] * z) + a[2] * (x[2]^2 + s[2] * z)
        f3 = a[1] * (x[1]^3 + 3 * s[1] * x[1] * z) + a[2] * (x[2]^3 + 3 * s[2] * x[2] * z)
        f4 =
            a[1] * (x[1]^4 + 6 * s[1] * x[1]^2 * z + 3 * s[1]^2 * z^2) +
            a[2] * (x[2]^4 + 6 * s[2] * x[2]^2 * z + 3 * s[2]^2 * z^2)
        f5 =
            a[1] * (x[1]^5 + 10 * s[1] * x[1]^3 * z + 15 * x[1] * s[1]^2 * z^2) +
            a[2] * (x[2]^5 + 10 * s[2] * x[2]^3 * z + 15 * x[2] * s[2]^2 * z^2)

        M2 = System(
            [f0, f1, f2, f3, f4, f5] - m .* z .^ (1:6),
            variables = [a; x; s; z],
            parameters = m,
        )

        relabeling = let S₂ = SymmetricGroup(2)
            v -> map(p -> [v[1:2][p]..., v[3:4][p]..., v[5:6][p]..., v[7]], S₂)
        end

        R = monodromy_solve(
            M2;
            group_action = relabeling,
            show_progress = false,
            threading = false,
            max_loops_no_progress = 10,
        )

        @test nsolutions(R) == 9
    end

    @testset "permutations" begin
        @var x[1:2] a b c
        p₁ = x - [2; 0]
        p₂ = x - [-2; 0]
        c₁ = p₁[1]^2 + p₁[2]^2 - 1
        c₂ = p₂[1]^2 + p₂[2]^2 - 1

        F = System([c₁ * c₂; a * x[1] + b * x[2] - c]; parameters = [a, b, c])
        S = monodromy_solve(
            F,
            [[1.0, 0.0]],
            [1, 1, 1],
            permutations = true,
            max_loops_no_progress = 20,
        )
        A = permutations(S)
        B = permutations(S, reduced = false)

        @test size(A) == (2, 2)
        @test A == [1 2; 2 1] || A == [2 1; 1 2]
        @test size(B, 1) == 2
        @test size(B, 2) > 2

        start_sols = solve(F; target_parameters = [1, 1, 1], start_system = :total_degree)
        S = monodromy_solve(F, solutions(start_sols), [1, 1, 1]; permutations = true)
        C = permutations(S)
        @test size(C, 1) == 4
    end

    @testset "Linear subspaces" begin
        @var x[1:4]
        f1 = rand_poly(x, 6)
        F = System([f1], x)
        res = monodromy_solve(F; dim = 3, threading = false)
        @test nsolutions(res) == 6
        @test trace(res) < 1e-6
        @test is_success(res)

        res = monodromy_solve(F; dim = 3, trace_test = false, threading = false)
        @test nsolutions(res) == 6
        @test is_heuristic_stop(res)

        res = monodromy_solve(
            F;
            dim = 3,
            trace_test = true,
            trace_test_tol = 1e-50,
            threading = false,
        )
        @test nsolutions(res) == 6
        @test is_heuristic_stop(res)

        @var x[1:4]
        f1 = rand_poly(x, 6; homogeneous = true)
        F = System([f1], x)
        res = monodromy_solve(F; dim = 2, compile = false)
        @test nsolutions(res) == 6
        @test trace(res) < 1e-6
        @test is_success(res)

        F = System([f1, rand_poly(x, 3), rand_poly(x, 4)], x)
        res = monodromy_solve(F; codim = 3, compile = false)
        @test nsolutions(res) == 72
        @test trace(res) < 1e-6
        @test is_success(res)
    end

    @testset "Monodromy rational functions" begin
        @var x y
        @var u[1:4]
        f1 = u[1] / x^2 + u[2]
        f2 = u[3] / y^2 + u[4]
        F = System([f1, f2], variables = [x, y], parameters = u[1:4])
        r1 = monodromy_solve(
            F,
            compile = true,
            target_solutions_count = 4,
            max_loops_no_progress = 100,
        )
        r2 = monodromy_solve(
            F,
            compile = false,
            target_solutions_count = 4,
            max_loops_no_progress = 100,
        )
        @test nsolutions(r1) == nsolutions(r2) == 4

        @var A1[1:3, 1:4] A2[1:3, 1:4]
        @var x[1:3] u1[1:2] u2[1:2]
        y1 = A1 * [x; 1]
        y2 = A2 * [x; 1]
        f = sum((u1 - y1[1:2] ./ y1[3]) .^ 2) + sum((u2 - y2[1:2] ./ y2[3]) .^ 2)
        F = System(
            differentiate(f, x),
            variables = x,
            parameters = [u1; u2; vec(A1); vec(A2)],
        )
        r = monodromy_solve(
            F,
            compile = false,
            max_loops_no_progress = 100,
            target_solutions_count = 6,
        )
        @test nsolutions(r) == 6
    end

    @testset "parameter homotopy with monodromy result" begin
        F = toric_ed([3 2 1 0; 0 1 2 3])
        mres = monodromy_solve(
            F,
            target_solutions_count = 21,
            max_loops_no_progress = 20,
            threading = false,
        )
        @test nsolutions(solve(F, mres, target_parameters = randn(ComplexF64, 4))) == 21
    end

    @testset "Verify solution_completeness" begin
        @var x y a b c
        f = x^2 + y^2 - 1
        l = a * x + b * y + c
        sys = System([f, l]; parameters = [a, b, c])
        res = solve(sys, [-0.6 - 0.8im, -1.2 + 0.4im]; target_parameters = [1, 2, 3])
        @test verify_solution_completeness(sys, solutions(res), [1, 2, 3])
        @test verify_solution_completeness(
            sys,
            solutions(res),
            [1, 2, 3],
            show_progress = false,
        )
        @test verify_solution_completeness(
            sys,
            solutions(res),
            [1, 2, 3],
            monodromy_options = (compile = false,),
            parameter_homotopy_options = (compile = false,),
            show_progress = false,
        )
        @test false == verify_solution_completeness(
            sys,
            solutions(res)[1:1],
            [1, 2, 3],
            monodromy_options = (compile = false,),
            parameter_homotopy_options = (compile = false,),
            show_progress = true,
        )
        @test false == verify_solution_completeness(
            sys,
            solutions(res),
            [1, 2, 3],
            monodromy_options = (compile = false,),
            parameter_homotopy_options = (compile = false,),
            show_progress = false,
            trace_tol = 1e-60,
        )
    end
end


using Tensorial
using BenchmarkTools
using InteractiveUtils # for `versioninfo`
using Markdown

const run_array = true

const suite = BenchmarkGroup()
suite["Array"] = BenchmarkGroup()
suite["Tensor"] = BenchmarkGroup()

const tensors = let
    dict = Dict{Tuple{Space, DataType}, Tensor}()
    for S in (Space(3),
              Space(3,3),
              Space(Symmetry(3,3)),
              Space(3,3,3),
              Space(Symmetry(3,3,3)),
              Space(3,3,3,3),
              Space(Symmetry(3,3),Symmetry(3,3)))
        for T in (Float32, Float64)
            dict[(S, T)] = rand(Tensorial.tensortype(S){T})
        end
    end
    dict
end

include("ops.jl")

tune!(suite)
const results = run(suite)

let path = "../docs/src/Benchmarks.md"

    path = joinpath(dirname(@__FILE__), path)

    open(path, "w") do file

        function printheader(head)
            println(file, "| **$(head)** | | | |")
        end
        function printrow(op, tt, ta)
            pretty = (t) -> BenchmarkTools.prettytime(BenchmarkTools.time(minimum(t)))
            speedup = (ta, tt) -> round(10*BenchmarkTools.time(minimum(ta))/BenchmarkTools.time(minimum(tt)))/10
            println(file, "| `$(op)` | $(pretty(tt)) | $(pretty(ta)) | ×$(speedup(ta, tt)) |")
        end

        print(file,
              """
              # Benchmarks

              ```julia
              a = rand(Vec{3})
              A = rand(SecondOrderTensor{3})
              S = rand(SymmetricSecondOrderTensor{3})
              B = rand(Tensor{Tuple{3,3,3}})
              AA = rand(FourthOrderTensor{3})
              SS = rand(SymmetricFourthOrderTensor{3})
              ```

              | Operation  | `Tensor` | `Array` | speed-up |
              |:-----------|---------:|--------:|---------:|
              """)

        printheader("Single contraction")
        printrow("a ⋅ a", results["Tensor"]["Space(3) ⋅ Space(3)"],
                          results["Array" ]["Space(3) ⋅ Space(3)"])
        printrow("A ⋅ a", results["Tensor"]["Space(3,3) ⋅ Space(3)"],
                          results["Array" ]["Space(3,3) ⋅ Space(3)"])
        printrow("S ⋅ a", results["Tensor"]["Space(Symmetry(3,3)) ⋅ Space(3)"],
                          results["Array" ]["Space(Symmetry(3,3)) ⋅ Space(3)"])

        printheader("Double contraction")
        printrow("A ⊡ A", results["Tensor"]["Space(3,3) ⊡ Space(3,3)"],
                          results["Array" ]["Space(3,3) ⊡ Space(3,3)"])
        printrow("S ⊡ S", results["Tensor"]["Space(Symmetry(3,3)) ⊡ Space(Symmetry(3,3))"],
                          results["Array" ]["Space(Symmetry(3,3)) ⊡ Space(Symmetry(3,3))"])
        printrow("B ⊡ A", results["Tensor"]["Space(3,3,3) ⊡ Space(3,3)"],
                          results["Array" ]["Space(3,3,3) ⊡ Space(3,3)"])
        printrow("AA ⊡ A", results["Tensor"]["Space(3,3,3,3) ⊡ Space(3,3)"],
                           results["Array" ]["Space(3,3,3,3) ⊡ Space(3,3)"])
        printrow("SS ⊡ S", results["Tensor"]["Space(Symmetry(3,3),Symmetry(3,3)) ⊡ Space(Symmetry(3,3))"],
                           results["Array" ]["Space(Symmetry(3,3),Symmetry(3,3)) ⊡ Space(Symmetry(3,3))"])

        printheader("Tensor product")
        printrow("a ⊗ a", results["Tensor"]["Space(3) ⊗ Space(3)"],
                          results["Array" ]["Space(3) ⊗ Space(3)"])

        printheader("Cross product")
        printrow("a × a", results["Tensor"]["Space(3) ⊗ Space(3)"],
                          results["Array" ]["Space(3) ⊗ Space(3)"])

        printheader("Determinant")
        printrow("det(A)", results["Tensor"]["det(Space(3,3))"],
                           results["Array" ]["det(Space(3,3))"])
        printrow("det(S)", results["Tensor"]["det(Space(Symmetry(3,3)))"],
                           results["Array" ]["det(Space(Symmetry(3,3)))"])

        printheader("Inverse")
        printrow("inv(A)", results["Tensor"]["inv(Space(3,3))"],
                           results["Array" ]["inv(Space(3,3))"])
        printrow("inv(S)", results["Tensor"]["inv(Space(Symmetry(3,3)))"],
                           results["Array" ]["inv(Space(Symmetry(3,3)))"])
        printrow("inv(AA)", results["Tensor"]["inv(Space(3,3,3,3))"],
                            results["Array" ]["inv(Space(3,3,3,3))"])
        printrow("inv(SS)", results["Tensor"]["inv(Space(Symmetry(3,3),Symmetry(3,3)))"],
                            results["Array" ]["inv(Space(Symmetry(3,3),Symmetry(3,3)))"])

        # print versioninfo
        print(file,
              """

              The benchmarks are generated by
              [`runbenchmarks.jl`](https://github.com/KeitaNakamura/Tensorial.jl/blob/master/benchmark/runbenchmarks.jl)
              on the following system:

              ```julia
              julia> versioninfo()
              $((x = IOBuffer(); versioninfo(x); String(take!(x))))
              """)
        println(file, "```")
    end

    # display results
    open(path, "r") do file
        display(Markdown.parse(file))
    end
end


# order 2
h = [5//12, 7//6, 11//12, 1, 1]
e = [1//1, 0, 0, 0, 0]
H = Diagonal(h)

q1 = [0 7//12 -1//12 0 0]
q2 = [-q1[2] 0 7//12 0 0]
q3 = [-q1[3] -q2[3] 0 1//2 0]
q4 = [0 0 -1//2 0 1//2]
q5 = [0 0 0 -1//2 0]
Q = vcat(q1, q2, q3, q4, q5)

D1 = H \ (Q - 1//2*e*e'); display(D1)

x=1//1:length(h)
ks = 0:5
for k in ks
    u=x.^k
    println("k = ", k)
    println(D1*u - k*x.^(k-1))
end

# order 4
h = [511//1600, 3971//2880, 929//1440, 587//480, 2659//2880, 14551//14400, 1, 1, 1]
e = [1//1, 0, 0, 0, 0, 0, 0, 0, 0]
H = Diagonal(h)

q1 = [0 436061//680400 -312391//5443200 -100109//907200 54107//5443200 263//15552 0 0 0]
q2 = [-q1[2] 0 238831//544320 290357//1088640 -125//5184 -220357//5443200 0 0 0]
q3 = [-q1[3] -q2[3] 0 3467//7776 -74827//1088640 3859//907200 0 0 0]
q4 = [-q1[4] -q2[4] -q3[4] 0 342799//544320 -149959//5443200 0 0 0]
q5 = [-q1[5] -q2[5] -q3[5] -q4[5] 0 428789//680400 -1//12 0 0]
q6 = [-q1[6] -q2[6] -q3[6] -q4[6] -q5[6] 0 2//3 -1//12 0]
q7 = [0 0 0 0 1//12 -2//3 0 2//3 -1//12]
q8 = [0 0 0 0 0 1//12 -2//3 0 2//3]
q9 = [0 0 0 0 0 0 1//12 -2//3 0]
Q = vcat(q1, q2, q3, q4, q5, q6, q7, q8, q9)

D1 = H \ (Q - 1//2*e*e'); display(D1)

x=1//1:length(h)
ks = 0:5
for k in ks
    u=x.^k
    println("k = ", k)
    println(D1*u - k*x.^(k-1))
end

# order 6
h = [7497391//25401600, 1106227//725760, 105317//403200, 260179//145152, 303631//725760,
    513973//403200, 671171//725760, 25631999//25401600, 1, 1, 1, 1]
e = [1//1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
H = Diagonal(h)

q1 = [0 14085349//21168000 -205189//8064000 -7583609//38102400 -89573//4233600 9//80 -39279943//1524096000 -13//2000 0 0 0 0]
q2 = [-q1[2] 0 56626691//304819200 97579603//152409600 1214239//16934400 -246914711//762048000 23380423//304819200 3//200 0 0 0 0]
q3 = [-q1[3] -q2[3] 0 22302157//101606400 -6048877//60963840 9737351//169344000 -508309//25401600 780937//304819200 0 0 0 0]
q4 = [-q1[4] -q2[4] -q3[4] 0 8120941//30481920 3467971//6350400 -42123859//304819200 -2075677//152409600 0 0 0 0]
q5 = [-q1[5] -q2[5] -q3[5] -q4[5] 0 29205107//152409600 15263287//304819200 -81127//3386880 0 0 0 0]
q6 = [-q1[6] -q2[6] -q3[6] -q4[6] -q5[6] 0 321139631//508032000 -49594423//762048000 1//60 0 0 0]
q7 = [-q1[7] -q2[7] -q3[7] -q4[7] -q5[7] -q6[7] 0 30841499//43545600 -3//20 1//60 0 0]
q8 = [-q1[8] -q2[8] -q3[8] -q4[8] -q5[8] -q6[8] -q7[8] 0 3//4 -3//20 1//60 0]
q9 = [0 0 0 0 0 -1//60 3//20 -3//4 0 3//4 -3//20 1//60]
q10 = [0 0 0 0 0 0 -1//60 3//20 -3//4 0 3//4 -3//20]
q11 = [0 0 0 0 0 0 0 -1//60 3//20 -3//4 0 3//4]
q12 = [0 0 0 0 0 0 0 0 -1//60 3//20 -3//4 0]
Q = vcat(q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12)

D1 = H \ (Q - 1//2*e*e'); display(D1)

x=1//1:length(h)
ks = 0:7
for k in ks
    u=x.^k
    println("k = ", k)
    println(D1*u - k*x.^(k-1))
end


using Distributions, CmdStan, Random, Parameters, StatsFuns
ProjDir = @__DIR__
cd(ProjDir)

stream = open("Logit_Normal.stan","r")
Model = read(stream,String)
close(stream)
seed = 343

stanmodel = Stanmodel(Sample(save_warmup=false, num_warmup=1000,
  num_samples=1000, thin=1), nchains=1, name="Logit_Normal", model=Model,
  printsummary=false, output_format=:mcmcchains, random=CmdStan.Random(seed))

Nsub = 3000
N = 400
ps = logistic.(rand(Normal(-2, 0.8), Nsub))
y = rand.(Binomial.(N, ps))

stanInput = Dict("N"=>N, "Nsub"=>Nsub, "y"=>y)
@time rc, chain, cnames = stan(stanmodel, stanInput, ProjDir)


# Routines for 2D grids

function Vandermonde(basis, quadrature)
    V = zeros(length(quadrature.locations), basis.order)
    for j=1:basis.order
        V[:,j] = basisFunction(basis, quadrature.locations, j)
    end
end

"Compute scaled warp function at order N based on rout
interpolation nodes"
function Warpfactor(basis, quadrature, rout)
    N = len(quadrature.locations)

    # Equidistant distribution
    eq = equidistant(N)

    # Compute V based on equidistant nodes
    Veq = Vandermonde(basis, eq)

    # Evaluate basis function at rout
    Pmat = zeros(basis.order, length(rout))
    for i=1:basis.order
        Pmat[i,:] = basisFunction(basis, rout, i)
    end
    Lmat = Veq' / Pmat

    # Compute warp factor
    warp = Lmat' * (quadrature.locations - req)

    # Scale factor

end

"Compute (x,y) nodes in equilateral triangle for polynomial
of order N"
function Nodes2D(N::Int)
    # Optimum correction to warp factor, to minimise
    # Lebesque constant for interpolant
    alpopt = [0.0000, 0.0000, 1.4152, 0.1001, 0.2751,
              0.9800, 1.0999, 1.2832, 1.3648, 1.4773,
              1.4959, 1.5743, 1.5770, 1.6223, 0.6258]

    # Set optimised parameter, alpha, depending on order
    alpha = N < 16 ? alpopt[N] : 5.0 / 3

    # Total number of nodes
    Np = (N + 1) * (N + 2) / 2

    # Create equidistributed nodes on equilateral triangle
    L1 = zeros(Np, 1)
    L3 = zeros(Np, 1)
    sk = 1
    for n = 1:N+1
        for m = 1:N+2-n
            L1[sk] = (n - 1) / N
            L3[sk] = (m - 1) / N
            sk += 1
        end
    end
    L2 = 1.0 - L1 - L3
    x = -L2 + L3
    y = (-L2 - L3 + 2 * L1)/sqrt(3.0)

    # Compute blending function at each node for each edge
    blend1 = 4 * L2 * L3
    blend2 = 4 * L1 * L3
    blend3 = 4 * L1 * L2

    # Amount of warp for each node, for each edge
    warpf1 = Warpfactor(N, L3 - L2)
    warpf2 = Warpfactor(N, L1 - L3)
    warpf3 = Warpfactor(N, L2 - L1)

end


x = randn(500)
hist(x; nbins=50, scaling="pdf")
plot!(x -> exp(-x^2/2)/sqrt(2π), -3, 3)


export sigmoid

"""
    Sigmoid <: Func
"""
@func mutable struct Sigmoid end

forward(f::Sigmoid, x) = @. tanh(x * 0.5) * 0.5 + 0.5

function backward(self, gy)
    y = self.outputs[1].value
    return gy .* y .* (1 - y)
end

sigmoid(x) = Sigmoid()(x)


using Printf
using DataFrames
include(joinpath("src", "data_loader.jl"))
include(joinpath("src", "model.jl"))

df = data_loader.read_csv("data_proj_414.csv")

train_df, test_df = data_loader.data_split(df)
#fruit_number, centers, esigma = model.fit(df)
fruit_number, centers, esigma = model.fit(train_df)
@printf("fruit number:\t%d\n", floor(fruit_number))
@printf("Sample tree number:\t%d\n", size(centers)[2])
@printf("Total tree number:\t%d\n", size(centers)[2]*3)
loc_arr = [train_df.X train_df.Y]'
train_pred_fruit = model.predict(loc_arr, fruit_number, centers, esigma)
train_mse = model.mse(train_pred_fruit, train_df.Close)
train_acc_soft = model.accuarcy_soft(train_pred_fruit, train_df.Close)*100
@printf("train mse:\t%2.4f\n", train_mse)
@printf("train soft accuarcy:\t%2.2f%%\n", train_acc_soft)
println()
loc_arr = [test_df.X test_df.Y]'
test_pred_fruit = model.predict(loc_arr, fruit_number, centers, esigma)
test_mse = model.mse(test_pred_fruit, test_df.Close)
test_acc_soft = model.accuarcy_soft(test_pred_fruit, test_df.Close)*100
@printf("test mse:\t%2.4f\n", test_mse)
@printf("test soft accuarcy:\t%2.2f%%\n", test_acc_soft)
train_df[:predict_close] =  train_pred_fruit
train_df[:diff_close] = map((x, y) -> abs((y-x)),
train_df[:, :predict_close], train_df[:, :Close])
data_loader.save_csv(train_df, "predict.csv")
center_df = DataFrame(X = centers[1, :], Y = centers[2, :])
data_loader.save_csv(center_df, "centers.csv")

# println(summary(df))
# println(summary(train_df))
# println(summary(test_df)

# main()


#=
Given a function that generates perfectly random numbers between 1 and k (inclusive), where k is an input, write a function that shuffles a deck of cards represented as an array using only swaps.

It should run in O(N) time.

Hint: Make sure each one of the 52! permutations of the deck is equally likely.
=#

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

@test sort(shuffle(collect(1:52))) == collect(1:52)


### A Pluto.jl notebook ###
# v0.11.12

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

# ╔═╡ ae24c8b2-f60b-11ea-2c7a-03857d1217b2
using Pkg

# ╔═╡ bc14fc1a-f60b-11ea-207a-91b967f28076
begin
	pkg"add Colors ColorSchemes Images ImageMagick PlutoUI Suppressor InteractiveUtils"
	using Colors, ColorSchemes, Images, ImageMagick
	using Suppressor, InteractiveUtils, PlutoUI
end

# ╔═╡ 0feb5674-f5d5-11ea-0714-7379a7d381a3
using LinearAlgebra

# ╔═╡ 20125236-f5d6-11ea-3877-6b332497be62
using SparseArrays

# ╔═╡ 0f6ecba6-f5da-11ea-22c2-2929e562f413
using Statistics

# ╔═╡ b0ba5b8c-f5d1-11ea-1304-3f0e47f935fe
md"# What do data structures, machine learning and the singular-value decomposition have in common?"

# ╔═╡ ffa95430-f5d1-11ea-3cb7-5bb8d8f13701
md"""Blurry Fons magic: Exploiting Structure!

ADD A HINT BOX HERE WITH THE ANSWER "STRUCTURE" """

# ╔═╡ 261c4df2-f5d2-11ea-2c72-7d4b09c46098
md"One-hot vectors:  Numbers and images

0s and 1s and colors"

# ╔═╡ b5177f70-f60b-11ea-14a9-f5a574cc5185
Pkg.activate(mktempdir())

# ╔═╡ ef8f44b2-f5fc-11ea-1e4d-bd873cd39d6c
@bind nn Slider(1:20, show_value=true)

# ╔═╡ fd9211c0-f5fc-11ea-1745-7f2dae88af9e
@bind kk Slider(1:nn, default=nn, show_value=true)

# ╔═╡ 3a1ed5b8-f5fd-11ea-2ecd-b9d08349651f
colors = [colorant"black", colorant"red"]

# ╔═╡ 77ae1146-f5d2-11ea-1226-27d15c90a8df
begin
	v = fill(1, nn)
	v[kk] = 2

	colors[v]'
end

# ╔═╡ 676f6e3a-f5fd-11ea-3552-e7e7c6382276
v

# ╔═╡ 3592648a-f5fd-11ea-2ed0-a1d4a4c14d07

# ╔═╡ 7ffa02f6-f5d2-11ea-1b41-15c21f92e9ee
Int.((1:10) .== 3)

# ╔═╡ 21a15906-f5d3-11ea-3a71-f53eabc31acc
md"Colors and sliders"

# ╔═╡ 3c5f0ba8-f5d3-11ea-22d3-4f7c513144bc
md"Data structure for a one-hot vector"

# ╔═╡ 4d2db24e-f5d4-11ea-10d4-b126f643abee

# ╔═╡ 4624cd26-f5d3-11ea-1cf8-7d6555eae5fa
struct OneHot <: AbstractVector{Int}
	n::Int
	k::Int
end

# ╔═╡ 397ac764-f5fe-11ea-20cc-8d7cab19d410
Base.size(x::OneHot) = (x.n, )

# ╔═╡ 82c7046c-f5d3-11ea-04e2-ef7c0f4db5da
Base.getindex(x::OneHot, i::Int) = Int(x.k == i)

# ╔═╡ 7a98f708-f5d3-11ea-2301-cde71ca469ff
x = OneHot(5, 3)

# ╔═╡ 60214c1a-f5fe-11ea-2d08-c59715bcedd0
x[1:2]

# ╔═╡ cf77d83c-f5fd-11ea-136e-8951de64e05e
show_vector(x::OneHot) = colors[x .+ 1]'

# ╔═╡ 5813e1b2-f5ff-11ea-2849-a1def74fc065
begin
	imshow(M) = get.(Ref(ColorSchemes.rainbow), M ./ maximum(M))
	imshow(x::AbstractVector) = imshow(x')
end

# ╔═╡ e51d4ef6-f5fd-11ea-28f1-a1616e17f715
imshow(rand(5, 5))

# ╔═╡ 982590d4-f5ff-11ea-3802-73292c75ad6c
imshow(x)

# ╔═╡ c3f70e18-f5ff-11ea-35aa-31b22ca506b8
get.([ColorSchemes.rainbow], x)'

# ╔═╡ c200deb0-f5fd-11ea-32e1-d96596b16ebc
x[3]

# ╔═╡ a5989a76-f5d3-11ea-3fe5-5f9959199fe8
typeof(x)

# ╔═╡ b0bd6c6a-f5d3-11ea-00ee-b7c155f23481
x[3:5]

# ╔═╡ bd94f82c-f5d3-11ea-2a1e-77ddaadbfeb9
collect(x)

# ╔═╡ 8d2c6910-f5d4-11ea-1928-1baf09815687
md"""How much "information" (numbers) do you need to represent a one-hot vector? Is it $n$ or is it two?
"""

# ╔═╡ 81c35324-f5d4-11ea-2338-9f982d38732c
md"# Diagonal matrices"

# ╔═╡ 2cfda0dc-f5d5-11ea-16c4-b5a33b90e37f
md"As you might see in high school:"

# ╔═╡ 150432d4-f5d5-11ea-32b2-19a2a91d9637
M = [5 0 0
	 0 6 0
	 0 0 -10]

# ╔═╡ 21328d1c-f5d5-11ea-288e-4171ad35326d
Diagonal(M)

# ╔═╡ d8f36278-f5d5-11ea-3338-8573ce40e65e
md"How much information do you need for a diagonal matrix?"

# ╔═╡ e90c55fc-f5d5-11ea-10f1-470ff772985d
md"""We should always look for *structure* where it exists"""

# ╔═╡ 19775c3c-f5d6-11ea-15c2-89618e654a1e
md"## Sparse matrices"

# ╔═╡ 232d1dcc-f5d6-11ea-2710-658c75b9c7a4
sparse(M)

# ╔═╡ 2b2feb9c-f5d6-11ea-191c-df20360b12a9
M2 = spzeros(10^6, 10^6)

# ╔═╡ 50c74f6c-f5d6-11ea-29f1-5de9997d5d9f
M2[1, 2] = 1

# ╔═╡ 5de72b7c-f5d6-11ea-1b6f-35b830b5fb34
M3 = sparse([1, 2, 10^6], [4, 10, 10^6], [7, 8, 9])

# ╔═╡ 2fd7e52e-f5d7-11ea-3b5a-1f338e2451e0
M4 = [1 0 2 0 10; 0 3 4 0 9; 0 0 0 5 8; 0 0 0 0 7] .* 0

# ╔═╡ aa09c008-f5d8-11ea-1bdc-b51ee6eb2478
sparse(M4)

# ╔═╡ d941cd66-f5d8-11ea-26ff-47ba7779ab20
md"Sparse matrix dictionaries? Before CSC"

# ╔═╡ 62a6ec62-f5d9-11ea-071e-ed33c5dea0cd
md"## Rand: Where is the structure?"

# ╔═╡ 67274c3c-f5d9-11ea-3475-c9d228e3bd5a
# join(rand("ACGT", 100))

vv = rand(1:9, 1000000)

# ╔═╡ b6c7a918-f600-11ea-18ff-6521507358c6
md"Mention lossless compression e.g. run-length encoding"

# ╔═╡ 765c6552-f5d9-11ea-29d3-bfe7b4b04612
md"""Some might guess that there is "no structure"

Take mean and standard deviation -- some would say that's the structure

"""

# ╔═╡ 126fb3ea-f5da-11ea-2f7d-0b3259a296ce
mean(vv), std(vv), 5, sqrt(10 * 2/3)

# ╔═╡ 5f79e8f4-f5da-11ea-2b55-ef344b8a3ba2
var(vv)

# ╔═╡ 9b9e2c2a-f5da-11ea-369b-b513b196515b
md"Statisticians (and professors who've just graded exams) might say that under certain circumstances the mean and the variance give you the necessary structure, and the rest can be thrown away"

# ╔═╡ e68b98ea-f5da-11ea-1a9d-db45e4f80241
m = sum(vv) / length(vv)  # mean

# ╔═╡ f20ccac4-f5da-11ea-0e69-413b5e49f423
σ² = sum( (vv .- m) .^ 2 ) / (length(vv) - 1)

# ╔═╡ 0bc792e8-f5db-11ea-0b7a-1502ddc8008e
var(vv)

# ╔═╡ 12a2e96c-f5db-11ea-1c3e-494ae7446886
σ = sqrt(σ²)

# ╔═╡ 22487ce2-f5db-11ea-32e9-6f70ab2c0353
std(vv)

# ╔═╡ 389ae62e-f5db-11ea-1557-c3adbbee0e5c
md"Sometimes the summary statistics are all you want. (But sometimes not)"

# ╔═╡ 0c2b6408-f5d9-11ea-2b7f-7fece2eecc1f
md"## Multiplication tables"

# ╔═╡ 542a9556-f5db-11ea-0375-99f52416f6e4
md"How do you make a multiplication table?"

# ╔═╡ 165788b2-f601-11ea-3e69-cdbbb6558e54
md"Do you recognise this?"

# ╔═╡ 22941bb8-f601-11ea-1d6e-0d955297bc2e
rand(3) .* rand(4)'

# ╔═╡ 2f75df7e-f601-11ea-2fc2-aff4f335af33
imshow(rand(3) .* rand(4)')

# ╔═╡ 53e6b612-f601-11ea-05a9-5395e69b3c41
svd(rand(3) .* rand(4)')

# ╔═╡ 3e919766-f601-11ea-0485-05f45484bf8d
md"It's not easy to see the structure!"

# ╔═╡ 1052993e-f601-11ea-2c55-0d67e31b670e
M5 =
[1  2  3   4
 2  4  6   8
 3  6  9  12
]

# ╔═╡ 68190822-f5db-11ea-117f-d10a161208c3
md"Comprehension:"

# ╔═╡ 71b44874-f5db-11ea-1f67-47bad9295e03
md"Slider"

# ╔═╡ 6c51eddc-f5db-11ea-1235-332cdbb072fa
[i * j for i in 1:3, j in 1:4]

# ╔═╡ 86fb49ee-f5db-11ea-3bfa-c95c3b8775a3
md"Explain '"

# ╔═╡ 173cfab4-f5d9-11ea-0c7c-bf8b0888f6e7
(1:3) .* (1:4)'

# ╔═╡ 8bab3e36-f5db-11ea-187a-f31fa8cf357d
reshape([1, 2, 3], 3, 1)

# ╔═╡ e64291e8-f5db-11ea-0cab-8567b781408f
[1
 2
 3]

# ╔═╡ 0111a124-f5dc-11ea-0904-fdd88d7acac4
MM = rand(3, 3)

# ╔═╡ 06406158-f5dc-11ea-02b1-2519a0176993
MM[:, 1]

# ╔═╡ 12051f06-f5dc-11ea-2f6c-0fdc50eeff01
vvv = [1, 2, 3]

# ╔═╡ 195854e6-f5dc-11ea-114b-2333f87173f7
a = reshape([1, 2, 3], 3, 1)

# ╔═╡ 768116f6-f5dc-11ea-1cfe-b3016c574725
b = reshape([4, 5, 6], 3, 1)

# ╔═╡ 9050426e-f5dc-11ea-373c-65456732bd34
a * b'

# ╔═╡ 965bd07e-f5dc-11ea-2e85-d34996cf2fae
v4 = [1, 2, 3]

# ╔═╡ a9daf4cc-f5dc-11ea-270b-2566f89f168c
v4 * v4'

# ╔═╡ dc0c8b72-f5dc-11ea-3e6f-0f43cbf58f56
v4 .* v4'

# ╔═╡ 0ed78e76-f5dd-11ea-2ad8-a35a69c0ef9a
v4

# ╔═╡ 1648a0fa-f5dd-11ea-0292-495207e83de9
[v4 v4]

# ╔═╡ 1e9cefea-f5dd-11ea-3e5f-a189fd41c42e
[1*v4 5*v4 6*v4]

# ╔═╡ 24664f20-f5dd-11ea-2a69-cd3e0ebd5c39
v4 * [1, 5, 6]'

# ╔═╡ 43a4920c-f5dd-11ea-1ab1-0b3d673c0f1e
v4 .* [1, 5, 6]'

# ╔═╡ 5d767290-f5dd-11ea-2189-81198fd216ce
outer(v, w) = [x * y for x in v, y in w]  # just a multiplication table

# ╔═╡ 5a493052-f601-11ea-2f5f-f940412905f2
begin
	v6 = rand(3)
	w6 = rand(4)

	U6, Σ6, V6 = svd( outer(v6, w6) )
end

# ╔═╡ 8633afb2-f601-11ea-206b-e9c4b9621c2a
outer(U6[:,1], V6[:, 1]) .* Σ6[1]

# ╔═╡ 9918c4fa-f601-11ea-3bf1-3506dcb437f7
outer(v6, w6)

# ╔═╡ 6aae805e-f5dd-11ea-108c-733daae313dc
outer(v4, [1, 5, 6])

# ╔═╡ 9a023cf8-f5dd-11ea-3016-f95d433e6df0
outer(1:10, 1:10)  # works with things other than Vectors

# ╔═╡ b4c82246-f5dd-11ea-068f-2f63a5a382e2
md"Did you memorize this in third grade?"

# ╔═╡ d1f87b22-f5dd-11ea-3bc3-471d5b3a5202
md"Slider: 12 is the standard in the UK"

# ╔═╡ d790281e-f5dd-11ea-0d1c-f57da5018a6b
md"How much information do I need to store a multiplication table?"

# ╔═╡ d1578d4c-f601-11ea-2983-27dc131d39b8
md"### Scaled multiplication table"

# ╔═╡ d9556f32-f601-11ea-3dd8-1bc876b7b719
10 .* outer(1:10, 1:10)

# ╔═╡ e36e4ec2-f5dd-11ea-34ea-1bcf5fd7c16d
md"In the context of 1:n times 1:n, just one number n is needed.

But given arbitrary vectors v and w,  we need to store the whole of v and w"

# ╔═╡ 52e857ca-f5de-11ea-14bb-bdc0ac24ab90
md"Toeplitz in hw"

# ╔═╡ 98f08990-f5de-11ea-1f56-1f2d73649773
"Add mult tables to make flag"

# ╔═╡ 21bbb60a-f5df-11ea-2c1b-dd716a657df8
cs = distinguishable_colors(100)

# ╔═╡ a5d637ea-f5de-11ea-3b70-877e876bc9c9
flag = outer([1, 1, 1, 2, 2, 2, 1, 1, 1], ones(Int, 9))

# ╔═╡ 2668e100-f5df-11ea-12b0-073a578a5edb
cs[flag]

# ╔═╡ e8d727f2-f5de-11ea-1456-f72602e81e0d
cs[flag + flag']

# ╔═╡ 4c80c786-f5df-11ea-31ec-318439349648
cs[outer(rand(1:10, 3), rand(1:10, 5))]

# ╔═╡ 8d2bae22-f5df-11ea-10d3-859f4c3aa6c7
Gray.((100 .- outer(1:10, 1:10)) ./ 100)

# ╔═╡ e23debc8-f5df-11ea-2c1e-b58a64f9acd3
ColorSchemes.rainbow[floor.(Int, (1:10, 1:10) ./ 10)]

# ╔═╡ 70b8918e-f5e0-11ea-3c86-6fa72df5a28e
ColorSchemes.rainbow.colors[floor.(Int, outer(1:10, 1:10) ./ 10)]

# ╔═╡ a0934122-f5e0-11ea-1f3b-ab0021ac6906
ColorSchemes.rainbow.colors[ceil.(Int, outer(1:10, 1:10) ./ 10)]

# ╔═╡ b9381b8a-f5e0-11ea-1f84-e39325203038

# ╔═╡ 4cf96558-f5e0-11ea-19be-db4c59a41120
ColorSchemes.rainbow

# ╔═╡ 11de523c-f5e0-11ea-2f3d-c981c1b6a1fe
outer(1:10, 1:10) ./ 100

# ╔═╡ fb0c6c7e-f5df-11ea-38d0-2d98c9dc232f

# ╔═╡ ebd72fb8-f5e0-11ea-0630-573337dff753
md"## SVD"

# ╔═╡ f00d1eaa-f5e0-11ea-21df-d9cf6f7af9b9
md"Grab the rank-1 approx of an image.

First multiplication table"

# ╔═╡ b6478e1a-f5f6-11ea-3b92-6d4f067285f4
url = "https://arbordayblog.org/wp-content/uploads/2018/06/oak-tree-sunset-iStock-477164218.jpg"

# ╔═╡ d4a049a2-f5f8-11ea-2f34-4bc0e3a5954a
download(url, "tree.jpg")

# ╔═╡ f2c11f88-f5f8-11ea-3e02-c1d4fa22031e
begin
	image = load("tree.jpg")
	image = image[1:5:end, 1:5:end]
end

# ╔═╡ f7e38aaa-f5f8-11ea-002f-09dd1fa21181
reds = [Float64(c.r) for c in image]

# ╔═╡ 29062f7a-f5f9-11ea-2682-1374e7694e32
picture = Float64.(channelview(image))

# ╔═╡ 6156fd1e-f5f9-11ea-06a9-211c7ab813a4
pr, pg, pb = eachslice(picture, dims=1)

# ╔═╡ a9766e68-f5f9-11ea-0019-6f9d02050521
[RGB.(pr, 0, 0) RGB.(0, pg, 0) RGB.(0, 0, pb)]

# ╔═╡ fee66076-f5f9-11ea-2316-abc57b62a57c
RGB.(image

# ╔═╡ 6532b388-f5f9-11ea-2ae2-f9b12e441bb3
pr

# ╔═╡ 0c0ee362-f5f9-11ea-0f75-2d2810c88d65
begin
	Ur, Σr, Vr = svd(pr)
	Ug, Σg, Vg = svd(pg)
	Ub, Σb, Vb = svd(pb)
end

# ╔═╡ 3c28c4c2-f5fa-11ea-1947-9dfe91ea1535
RGB.(sum(outer(Ur[:,i], Vr[:,i]) .* Σr[i] for i in 1:5), 0, 0)

# ╔═╡ f56f40e4-f5fa-11ea-3a99-156565445c2e
@bind n Slider(1:100, show_value=true)

# ╔═╡ 7ba6e6a6-f5fa-11ea-2bcd-616d5a3c898b
RGB.(sum(outer(Ur[:,i], Vr[:,i]) .* Σr[i] for i in 1:n),
	sum(outer(Ug[:,i], Vg[:,i]) .* Σg[i] for i in 1:n),
	sum(outer(Ub[:,i], Vb[:,i]) .* Σb[i] for i in 1:n))

# ╔═╡ 8a22387e-f5fb-11ea-249b-435af5c0a6b6

# ╔═╡ 8df84fcc-f5d5-11ea-312f-bf2a3b3ce2ce
md"## Appendix"

# ╔═╡ 91980bcc-f5d5-11ea-211f-e9a08ff0fb19
function with_terminal(f)
	local spam_out, spam_err
	@color_output false begin
		spam_out = @capture_out begin
			spam_err = @capture_err begin
				f()
			end
		end
	end
	spam_out, spam_err

	HTML("""

$(Markdown.htmlesc(spam_out))

	""")
end

# ╔═╡ 466901ea-f5d5-11ea-1db5-abf82c96eabf
with_terminal() do
	dump(M)
end

# ╔═╡ b38c4aae-f5d5-11ea-39b6-7b0c7d529019
with_terminal() do
	dump(Diagonal(M))
end

# ╔═╡ 8b60629e-f5d6-11ea-27c8-d934460d3a57
with_terminal() do
	dump(M3)
end

# ╔═╡ cde79f38-f5d6-11ea-3297-0b5b240f7b9e
with_terminal() do
	dump(sparse(M4))
end

# ╔═╡ 4f8684ea-f5fb-11ea-07be-11d8046f35df
with_terminal() do
	@time @inbounds RGB.(sum(outer(Ur[:,i], Vr[:,i]) .* Σr[i] for i in 1:20),
	sum(outer(Ug[:,i], Vg[:,i]) .* Σg[i] for i in 1:20),
	sum(outer(Ub[:,i], Vb[:,i]) .* Σb[i] for i in 1:20));
end

# ╔═╡ Cell order:
# ╟─b0ba5b8c-f5d1-11ea-1304-3f0e47f935fe
# ╟─ffa95430-f5d1-11ea-3cb7-5bb8d8f13701
# ╟─261c4df2-f5d2-11ea-2c72-7d4b09c46098
# ╠═ae24c8b2-f60b-11ea-2c7a-03857d1217b2
# ╠═b5177f70-f60b-11ea-14a9-f5a574cc5185
# ╠═bc14fc1a-f60b-11ea-207a-91b967f28076
# ╠═ef8f44b2-f5fc-11ea-1e4d-bd873cd39d6c
# ╠═fd9211c0-f5fc-11ea-1745-7f2dae88af9e
# ╠═3a1ed5b8-f5fd-11ea-2ecd-b9d08349651f
# ╠═676f6e3a-f5fd-11ea-3552-e7e7c6382276
# ╠═77ae1146-f5d2-11ea-1226-27d15c90a8df
# ╠═3592648a-f5fd-11ea-2ed0-a1d4a4c14d07
# ╠═7ffa02f6-f5d2-11ea-1b41-15c21f92e9ee
# ╟─21a15906-f5d3-11ea-3a71-f53eabc31acc
# ╟─3c5f0ba8-f5d3-11ea-22d3-4f7c513144bc
# ╠═4d2db24e-f5d4-11ea-10d4-b126f643abee
# ╠═4624cd26-f5d3-11ea-1cf8-7d6555eae5fa
# ╠═397ac764-f5fe-11ea-20cc-8d7cab19d410
# ╠═82c7046c-f5d3-11ea-04e2-ef7c0f4db5da
# ╠═60214c1a-f5fe-11ea-2d08-c59715bcedd0
# ╠═7a98f708-f5d3-11ea-2301-cde71ca469ff
# ╠═cf77d83c-f5fd-11ea-136e-8951de64e05e
# ╠═5813e1b2-f5ff-11ea-2849-a1def74fc065
# ╠═e51d4ef6-f5fd-11ea-28f1-a1616e17f715
# ╠═982590d4-f5ff-11ea-3802-73292c75ad6c
# ╠═c3f70e18-f5ff-11ea-35aa-31b22ca506b8
# ╠═c200deb0-f5fd-11ea-32e1-d96596b16ebc
# ╠═a5989a76-f5d3-11ea-3fe5-5f9959199fe8
# ╠═b0bd6c6a-f5d3-11ea-00ee-b7c155f23481
# ╠═bd94f82c-f5d3-11ea-2a1e-77ddaadbfeb9
# ╠═8d2c6910-f5d4-11ea-1928-1baf09815687
# ╟─81c35324-f5d4-11ea-2338-9f982d38732c
# ╟─2cfda0dc-f5d5-11ea-16c4-b5a33b90e37f
# ╠═0feb5674-f5d5-11ea-0714-7379a7d381a3
# ╠═150432d4-f5d5-11ea-32b2-19a2a91d9637
# ╠═21328d1c-f5d5-11ea-288e-4171ad35326d
# ╠═466901ea-f5d5-11ea-1db5-abf82c96eabf
# ╠═b38c4aae-f5d5-11ea-39b6-7b0c7d529019
# ╟─d8f36278-f5d5-11ea-3338-8573ce40e65e
# ╟─e90c55fc-f5d5-11ea-10f1-470ff772985d
# ╠═19775c3c-f5d6-11ea-15c2-89618e654a1e
# ╠═20125236-f5d6-11ea-3877-6b332497be62
# ╠═232d1dcc-f5d6-11ea-2710-658c75b9c7a4
# ╠═2b2feb9c-f5d6-11ea-191c-df20360b12a9
# ╠═50c74f6c-f5d6-11ea-29f1-5de9997d5d9f
# ╠═5de72b7c-f5d6-11ea-1b6f-35b830b5fb34
# ╠═8b60629e-f5d6-11ea-27c8-d934460d3a57
# ╠═2fd7e52e-f5d7-11ea-3b5a-1f338e2451e0
# ╠═cde79f38-f5d6-11ea-3297-0b5b240f7b9e
# ╠═aa09c008-f5d8-11ea-1bdc-b51ee6eb2478
# ╟─d941cd66-f5d8-11ea-26ff-47ba7779ab20
# ╟─62a6ec62-f5d9-11ea-071e-ed33c5dea0cd
# ╠═67274c3c-f5d9-11ea-3475-c9d228e3bd5a
# ╟─b6c7a918-f600-11ea-18ff-6521507358c6
# ╟─765c6552-f5d9-11ea-29d3-bfe7b4b04612
# ╠═0f6ecba6-f5da-11ea-22c2-2929e562f413
# ╠═126fb3ea-f5da-11ea-2f7d-0b3259a296ce
# ╠═5f79e8f4-f5da-11ea-2b55-ef344b8a3ba2
# ╟─9b9e2c2a-f5da-11ea-369b-b513b196515b
# ╠═e68b98ea-f5da-11ea-1a9d-db45e4f80241
# ╠═f20ccac4-f5da-11ea-0e69-413b5e49f423
# ╠═0bc792e8-f5db-11ea-0b7a-1502ddc8008e
# ╠═12a2e96c-f5db-11ea-1c3e-494ae7446886
# ╠═22487ce2-f5db-11ea-32e9-6f70ab2c0353
# ╠═389ae62e-f5db-11ea-1557-c3adbbee0e5c
# ╟─0c2b6408-f5d9-11ea-2b7f-7fece2eecc1f
# ╟─542a9556-f5db-11ea-0375-99f52416f6e4
# ╠═165788b2-f601-11ea-3e69-cdbbb6558e54
# ╠═22941bb8-f601-11ea-1d6e-0d955297bc2e
# ╠═2f75df7e-f601-11ea-2fc2-aff4f335af33
# ╠═53e6b612-f601-11ea-05a9-5395e69b3c41
# ╠═5a493052-f601-11ea-2f5f-f940412905f2
# ╠═8633afb2-f601-11ea-206b-e9c4b9621c2a
# ╠═9918c4fa-f601-11ea-3bf1-3506dcb437f7
# ╟─3e919766-f601-11ea-0485-05f45484bf8d
# ╠═1052993e-f601-11ea-2c55-0d67e31b670e
# ╠═68190822-f5db-11ea-117f-d10a161208c3
# ╠═71b44874-f5db-11ea-1f67-47bad9295e03
# ╠═6c51eddc-f5db-11ea-1235-332cdbb072fa
# ╠═86fb49ee-f5db-11ea-3bfa-c95c3b8775a3
# ╠═173cfab4-f5d9-11ea-0c7c-bf8b0888f6e7
# ╠═8bab3e36-f5db-11ea-187a-f31fa8cf357d
# ╠═e64291e8-f5db-11ea-0cab-8567b781408f
# ╠═0111a124-f5dc-11ea-0904-fdd88d7acac4
# ╠═06406158-f5dc-11ea-02b1-2519a0176993
# ╠═12051f06-f5dc-11ea-2f6c-0fdc50eeff01
# ╠═195854e6-f5dc-11ea-114b-2333f87173f7
# ╠═768116f6-f5dc-11ea-1cfe-b3016c574725
# ╠═9050426e-f5dc-11ea-373c-65456732bd34
# ╠═965bd07e-f5dc-11ea-2e85-d34996cf2fae
# ╠═a9daf4cc-f5dc-11ea-270b-2566f89f168c
# ╠═dc0c8b72-f5dc-11ea-3e6f-0f43cbf58f56
# ╠═0ed78e76-f5dd-11ea-2ad8-a35a69c0ef9a
# ╠═1648a0fa-f5dd-11ea-0292-495207e83de9
# ╠═1e9cefea-f5dd-11ea-3e5f-a189fd41c42e
# ╠═24664f20-f5dd-11ea-2a69-cd3e0ebd5c39
# ╠═43a4920c-f5dd-11ea-1ab1-0b3d673c0f1e
# ╠═5d767290-f5dd-11ea-2189-81198fd216ce
# ╠═6aae805e-f5dd-11ea-108c-733daae313dc
# ╠═9a023cf8-f5dd-11ea-3016-f95d433e6df0
# ╠═b4c82246-f5dd-11ea-068f-2f63a5a382e2
# ╠═d1f87b22-f5dd-11ea-3bc3-471d5b3a5202
# ╠═d790281e-f5dd-11ea-0d1c-f57da5018a6b
# ╠═d1578d4c-f601-11ea-2983-27dc131d39b8
# ╠═d9556f32-f601-11ea-3dd8-1bc876b7b719
# ╠═e36e4ec2-f5dd-11ea-34ea-1bcf5fd7c16d
# ╠═52e857ca-f5de-11ea-14bb-bdc0ac24ab90
# ╠═98f08990-f5de-11ea-1f56-1f2d73649773
# ╠═21bbb60a-f5df-11ea-2c1b-dd716a657df8
# ╠═a5d637ea-f5de-11ea-3b70-877e876bc9c9
# ╠═2668e100-f5df-11ea-12b0-073a578a5edb
# ╠═e8d727f2-f5de-11ea-1456-f72602e81e0d
# ╠═4c80c786-f5df-11ea-31ec-318439349648
# ╠═8d2bae22-f5df-11ea-10d3-859f4c3aa6c7
# ╠═e23debc8-f5df-11ea-2c1e-b58a64f9acd3
# ╠═70b8918e-f5e0-11ea-3c86-6fa72df5a28e
# ╠═a0934122-f5e0-11ea-1f3b-ab0021ac6906
# ╠═b9381b8a-f5e0-11ea-1f84-e39325203038
# ╠═4cf96558-f5e0-11ea-19be-db4c59a41120
# ╠═11de523c-f5e0-11ea-2f3d-c981c1b6a1fe
# ╠═fb0c6c7e-f5df-11ea-38d0-2d98c9dc232f
# ╠═ebd72fb8-f5e0-11ea-0630-573337dff753
# ╠═f00d1eaa-f5e0-11ea-21df-d9cf6f7af9b9
# ╠═b6478e1a-f5f6-11ea-3b92-6d4f067285f4
# ╠═d4a049a2-f5f8-11ea-2f34-4bc0e3a5954a
# ╠═f2c11f88-f5f8-11ea-3e02-c1d4fa22031e
# ╠═f7e38aaa-f5f8-11ea-002f-09dd1fa21181
# ╠═29062f7a-f5f9-11ea-2682-1374e7694e32
# ╠═6156fd1e-f5f9-11ea-06a9-211c7ab813a4
# ╠═a9766e68-f5f9-11ea-0019-6f9d02050521
# ╠═fee66076-f5f9-11ea-2316-abc57b62a57c
# ╠═6532b388-f5f9-11ea-2ae2-f9b12e441bb3
# ╠═0c0ee362-f5f9-11ea-0f75-2d2810c88d65
# ╠═3c28c4c2-f5fa-11ea-1947-9dfe91ea1535
# ╠═f56f40e4-f5fa-11ea-3a99-156565445c2e
# ╠═7ba6e6a6-f5fa-11ea-2bcd-616d5a3c898b
# ╠═8a22387e-f5fb-11ea-249b-435af5c0a6b6
# ╠═4f8684ea-f5fb-11ea-07be-11d8046f35df
# ╠═8df84fcc-f5d5-11ea-312f-bf2a3b3ce2ce
# ╠═91980bcc-f5d5-11ea-211f-e9a08ff0fb19

lecture_notebooks/taking advantage of structure.jl


#=
rabin_karp_string_search:
- Julia version: 1.5.0
- Author: shisa
- Date: 2020-09-20
=#

module RabinKarpSearch
    # parameters
    const cardinal = 101

    function get_hash(s, len)
        hash_value = 0
        pow_num = len
        for i in 1:len
            pow_num -= 1
            ascii_num = Int(s[i])
            hash_value += ascii_num * (cardinal^pow_num)
        end
        return hash_value
    end

    function rabin_karp_search(text, pattern)
        println("")
        println("Search $(pattern) from $(text)")

        len_txt = length(text)
        len_pat = length(pattern)

        # convert from string to hash value
        hash_txt = get_hash(text[1:len_pat], len_pat)
        hash_pat = get_hash(pattern[1:len_pat], len_pat)

        # search
        for i in 1:(len_txt)
            if hash_txt == hash_pat
                if text[i:i+len_pat-1] == pattern
                    println("Found at index:$(i)~$(i+len_pat-1)")
                    return i
                end
            end
            # prevent bounds error
            if (i + len_pat) > len_txt
                break
            end
            # calculate next hash value
            hash_txt = hash_txt - (Int(text[i]) * (cardinal^(len_pat-1)))
            hash_txt *= cardinal
            hash_txt = hash_txt + (Int(text[i+len_pat]) * (cardinal^0))
        end

        println("Pattern not found")
        return -1
    end

    function main()
        @time println(rabin_karp_search("Hello sunshine", "sun"))
        @time println(rabin_karp_search("GEEKS FOR GEEKS", "GEEKS"))
        @time println(rabin_karp_search("aaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbb"))
    end
end

if abspath(PROGRAM_FILE) == @__FILE__
    using .RabinKarpSearch
    RabinKarpSearch.main()
end


# Sum of the largest k components

"""
  SumLargest(k::Integer=1, λ::Real=1.0)

Returns the function `g(x) = λ⋅sum(x_[1], ..., x_[k])`, for an integer k ⩾ 1 and `λ ⩾ 0`.
"""

# TODO: SumLargest(r) is (the postcomposition of) the conjugate of
#   (1) ind{0 <= x <= 1 : sum(x) = r},
# where instead IndSimplex(r) corresponds to
#   (2) ind{0 <= x : sum(x) = r}.
# Therefore SumLargest(r) now is only correct when r = 1. To make SumLargest correct
# we should extend IndSimplex to allow for that additional bound in its definition,
# by adding a second argument to the constructor. Then in the following line we
# should replace IndSimplex(k) with IndSimplex(k, 1.0).
# Note that (1) is proper only if x ∈ Rⁿ for n ⩾ r.
SumLargest{I <: Integer, R <: Real}(k::I=1, lambda::R=1.0) = Postcompose(Conjugate(IndSimplex(k)), lambda)

function (f::Conjugate{IndSimplex{I}}){I <: Integer, S <: Real}(x::AbstractArray{S})
  if f.f.a == 1
    return maximum(x)
  end
  v = zero(S)
  if ndims(x) == 1
    p = selectperm(x, 1:f.f.a, rev=true)
    v = sum(x[p])
  else
    p = selectperm(x[:], 1:f.f.a, rev=true)
    v = sum(x[p])
  end
  return v
end

fun_name{I <: Integer, R <: Real}(f::Postcompose{Conjugate{IndSimplex{I}}, R}) = "sum of k largest components"
fun_expr{I <: Integer, R <: Real}(f::Postcompose{Conjugate{IndSimplex{I}}, R}) = "x ↦ λ⋅sum(x_[1], ..., x_[k])"
fun_params{I <: Integer, R <: Real}(f::Postcompose{Conjugate{IndSimplex{I}}, R}) = "k = $(f.f.f.a), λ = $(f.a)"


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

@inline function RandomWalker2D(TotalSteps, R₀)
    choices = ((1,0),(0,1),(-1,0),(0,-1))
    TotalWalks = rand(choices,TotalSteps)
    return reduce(.+,TotalWalks) .+ R₀
end

#=
function PlotHeatmap(Data, RWNumbers) #useless piece of shit
    Tuples = collect(Tuple(Data[i,:]) for i ∈ 1:RWNumbers)
    maxindx = max(getindex.(Tuples, 1)...,getindex.(Tuples, 2)...)
    minindx = min(getindex.(Tuples, 1)...,getindex.(Tuples, 2)...)
    delta = maxindx - minindx + 1
    Surf = zeros(Int,delta,delta)

    for tup in Tuples
        Surf[(tup .- (minindx - 1))...] += 1
    end
    return heatmap(Surf)
end
=#

TStepsList = [10,100,1000]
RWNumbersList = [10000,100000,1000000]
R₀ = [0,0]
Data = []
for TotalSteps in TStepsList
    RWNumbers = TotalSteps * 1000
    TotalWalkers = zeros(Int,RWNumbers,2)
    for i ∈ ProgressBar(1:RWNumbers)
        TotalWalkers[i,:] = RandomWalker2D(TotalSteps,R₀)
    end
    push!(Data, copy(TotalWalkers))
end

cd(dirname(@__FILE__))
save("../../Data/Q1/Q1-2DHist.jld", "Data", Data)
# Data = load("../../Data/Q1/Q1-2DHist.jld")["Data"]
#Plot section

scatter(RgList.^2)

# PlotHeatmap(Data, RWNumbers)
dimnum = 3
P1 = histogram2d(collect(Tuple(Data[dimnum][i,:]) for i ∈ 1:RWNumbersList[dimnum]),
    nbin = 200,
    xaxis = nothing,
    yaxis = nothing,
    framestyle = :box
    )
P2 = histogram(Data[dimnum][:,2], orientation = :horizontal, xticks = [0,3000], bins = 150,
    ylabel = L"Y", flip = true,framestyle = :box, c = :purple)
P3 = histogram(Data[dimnum][:,1], flip = true, yticks = [0,3000], bins = 150,
    xlabel = L"X", framestyle = :box, c = :purple)
P4 = plot(legend=false,grid=false,
    foreground_color_subplot=:white,background=:white,framestyle = :box)

P21 = plot(P2,P1,layout = @layout[a{0.1w} grid(1,1, heights = [0.95])], legend = nothing)

P22 = plot(P4,P3,P4,layout = @layout[a{0.08w} grid(1,1) a{0.02w}], legend = nothing)

plot(P21,P22,layout = grid(2,1, heights = [0.85,0.15]),
    plot_title = L"Distribution\ of\ %$(RWNumbersList[dimnum])\ RW\ in\ %$(TStepsList[dimnum])\ steps")

savefig("../../Figs/Q1/Q1-2DHist$dimnum.pdf")


export Point, Arc, Line, Arcs, Polygon, issimple, iscomplex, boundingbox,
isconcave, isconvex, convexhull

"""
    Point(ϕ::Float64, λ::Float64)

Point type with latitude `ϕ` [deg] and longitude `λ` [deg]
"""
struct Point{T<:Float64}
    ϕ::T
    λ::T
end

(-)(x::Point, y::Point) = Point(x.ϕ - y.ϕ, x.λ - y.λ)
(rad2deg)(x::Point) = Point(rad2deg(x.ϕ), rad2deg(x.λ))
(deg2rad)(x::Point) = Point(deg2rad(x.ϕ), deg2rad(x.λ))

"""
    Arc(point₁::Point, point₂::Point)

Arc type with start position `point₁` [deg] and end position `point₂`
[deg]. The Arc is defined along the shortest great circle distance between
the two points.
"""
struct Arc{T<:Float64}
    point₁::Point{T}
    point₂::Point{T}
end

"""
    Line(point::Point, azimuth::Float64)

Line type with `point` [deg] and `azimuth` [deg]. The line type of a great
circle line that completely circles the sphere.
"""
struct Line{T<:Float64}
    point::Point{T}
    azimuth::Float64
end

"""
    Arcs(points::Vector{Point})

Arcs type with a Vector of points [deg]. A minimum of two points are
necessary for the continuous string of line sections.
"""
struct Arcs{T<:Float64}
    points::Vector{Point{T}}
    function Arcs(points::Vector{Point{T}}) where T<:Float64
        if length(points) > 1
            new{T}(points)
        else
            throw(BoundsError("Invalid Arcs: # Points < 2"))
        end
    end
end

#TODO Ensure that the inside_point is not on any line crossing adjacent points of the polygon
"""
    Polygon(inside_point::Point, points::Vector{Point})

Polygon type with points [deg]. A minimum of three different points are
necessary for a polygon. Also an inside point must be given.
"""
struct Polygon{T<:Float64}
    inside_point::Point{T}
    points::Vector{Point{T}}
    function Polygon(inside_point::Point, points::Vector{Point{T}}) where T<:Float64
        if points[1] != points[end]
            points = vcat(points, points[1])
        end
        if length(points) > 3
            new{T}(inside_point, points)
        else
            throw(BoundsError("Invalid Polygon: # individual Points < 3"))
        end
    end
end

"""
    Polygon(inside_point::Point, arcs::Arcs)

Generate Polygon type based on Arcs. Also an inside point must be given.
"""
Polygon(inside_point::Point, arcs::Arcs) = Polygon(inside_point,
arcs.points)

"""
    issimple(polygon::Polygon)

True if the polygon is 'simple' meaning that it does not self-intersect.
"""
function issimple(polygon::Polygon)
    return !isselfintersecting(polygon)
end

"""
    iscomplex(polygon::Polygon)

True if the polygon is 'complex' meaning that it self-intersects.
"""
iscomplex(polygon::Polygon) = !issimple(polygon)

"""
    isconcave(polygon::Polygon)

Tests if a polygon is concave.

This is true when the polygon is simple and not convex.
"""
isconcave(polygon::Polygon) = issimple(polygon) && !isconvex(polygon)

"""
    isconvex(polygon::Polygon)

Tests if a polygon is convex.

The test is setup by checking exhaustively all possible arcs between all
points of the polygon to intersect with the polygon. If false the polygon
is convex.
"""
function isconvex(polygon::Polygon)
    for (id,pt1) in enumerate(polygon.points[1:end-2])
        for pt2 in polygon.points[id+1:end]
            if length(intersection_points(Arc(pt1,pt2), polygon)) > 2
                return false
            end
        end
    end
    return true
end

"""
    boundingbox(arcs::Arcs)

    Output: [[ϕ_south,ϕ_north],[λ_west,λ_east]]

Generate bounding box values from the given arcs. All arcs stay
within the lat/lon-limits of the bounding box.

Source: Chamberlain & Duquette - Some Algorithms for Polygons on a Sphere
"""
function boundingbox(arcs::Arcs)
    λw = minimum([point.λ for point in arcs.points])
    λe = maximum([point.λ for point in arcs.points])
    pt1 = arcs.points[1]
    ϕmax = -90.0
    ϕmin = 90.0
    for pt2 in arcs.points[2:end]
        ϕmax = max(ϕmax, highest_latitude_point(pt1, pt2).ϕ)
        ϕmin = min(ϕmin, lowest_latitude_point(pt1, pt2).ϕ)
        pt1=pt2
    end
    return [[ϕmin,ϕmax],[λw,λe]]
end

"""
    boundingbox(polygon::Polygon)

    Output: [[ϕ_south,ϕ_north],[λ_west,λ_east]]

Generate bounding box values from the given polygon. This bounding box can be
used for quick point-in-polygon tests. Any arc of the polygon stays within
the lat/lon-limits of the bounding box.

Source: Chamberlain & Duquette - Some Algorithms for Polygons on a Sphere
"""
boundingbox(polygon::Polygon) = boundingbox(Arcs(polygon.points))

"""
    convexhull(polygon::Polygon)

    output: Polygon

Generate a convex hull for the polygon.

Source: stackoverflow.com/questions/9678624/convex-hull-of-longitude-latitude-points-on-the-surface-of-a-sphere
"""
function convexhull(polygon::Polygon)
    n_pts = length(polygon.points) - 1

    inside_point = point_cartesian(polygon.inside_point)
    points = point_cartesian.(polygon.points[1:end-1])
    # move points that are on a great circle arc section to inside_point
    for idA in 1:n_pts
        for idB in 1:n_pts
            if idA == idB
                #
            else
                for id in 1:n_pts
                    if id ≠ idA && id ≠ idB && ison(polygon.points[id], Arc(polygon.points[idA],
                        polygon.points[idB]))
                        points[id] = inside_point
                    end
                end
            end
        end
    end

    # add great circle lines that have all points on or inside the convex hull
    hull_points = []
    for (idA, pointA) in enumerate(points)
        for (idB, pointB) in enumerate(points)
            if pointA == pointB
                #
            else
                great_circle = cross(pointA, pointB)
                tst = map(x -> dot(great_circle, x), points)
                tst[idA] = 0.0
                tst[idB] = 0.0
                if all(tst.≥0.0)
                    append!(hull_points, [[idA, idB]])
                end
            end
        end
    end

    # create convex hull by sequencing points
    convex_set = [hull_points[1][1], hull_points[1][2]]
    for _ in 1:length(hull_points)-1
        id = findfirst(x -> x[1] == convex_set[end], hull_points)
        push!(convex_set, hull_points[id][2])
    end

    return Polygon(polygon.inside_point, polygon.points[convex_set])
end


function root_sum_of_squares(signals::AbstractArray{<: Number}, dim::Val{N}=Val(1)) where N
	result = dropdims(sum(abs2, signals; dims=N); dims=N)
	@. result = sqrt(result)
end

function roemer(
	signals::AbstractArray{<: Number},
	sensitivity::AbstractArray{<: Number},
	dim::Val{N}=Val(1)
) where N
	dropdims(
		sum(conj.(sensitivity) .* signals; dims=N) ./ sum(abs2, sensitivity; dims=N);
		dims=N
	)
end

"""
	plan_sensitivities(sensitivities::Union{AbstractMatrix{<: Number}, AbstractArray{<: Number, 3}}) = S

sensitivities[spatial dimensions..., channels]
shape = (spatial dimensions, other dimension)

"""
function plan_sensitivities(
	sensitivities::AbstractArray{<: Number, N},
	shape::NTuple{N, <: Integer}
) where N

	# Get dimensions
	# TODO: check spatial dims
	other_dims = shape[N]
	shape = shape[1:N-1]
	shape_s = size(sensitivities)
	@assert shape == shape_s[1:N-1]
	channels = shape_s[N]
	spatial_dimensions = prod(shape)
	input_dimension = spatial_dimensions * other_dims
	output_dimension = input_dimension * channels

	# Reshape
	sensitivities = reshape(sensitivities, spatial_dimensions, channels, 1)
	conj_sensitivities = conj.(sensitivities)

	S = LinearMap{ComplexF64}(
		x::AbstractVector{<: Complex} -> begin
			Sx = sensitivities .* reshape(x, spatial_dimensions, 1, other_dims)
			vec(Sx)
		end,
		y::AbstractVector{<: Complex} -> begin
			y = reshape(y, spatial_dimensions, channels, other_dims)
			SHy = sum(conj_sensitivities .* y; dims=2)
			vec(SHy)
		end,
		output_dimension, input_dimension
	)
	return S
end


export Addition, +, -

"""
Description:

    An addition constraint factor node.

    f(out,in1,in2) = δ(in1 + in2 - out)

Interfaces:

    1. out
    2. in1
    3. in2

Construction:

    Addition(out, in1, in2, id=:some_id)
"""
mutable struct Addition <: DeltaFactor
    id::Symbol
    interfaces::Vector{Interface}
    i::Dict{Symbol,Interface}

    function Addition(out, in1, in2; id=generateId(Addition))
        @ensureVariables(out, in1, in2)
        self = new(id, Array{Interface}(undef, 3), Dict{Int,Interface}())
        addNode!(currentGraph(), self)
        self.i[:out] = self.interfaces[1] = associate!(Interface(self), out)
        self.i[:in1] = self.interfaces[2] = associate!(Interface(self), in1)
        self.i[:in2] = self.interfaces[3] = associate!(Interface(self), in2)

        return self
    end
end

slug(::Type{Addition}) = "+"

function +(in1::Variable, in2::Variable)
    out = Variable()
    Addition(out, in1, in2)
    return out
end

function +(in1::Variable, in2)
    @ensureVariables(in2)
    in1 + in2
end

+(in1, in2::Variable) = +(in2, in1)

"""
    -(in1::Variable, in2::Variable)

A subtraction constraint based on the addition factor node.

"""
function -(in1::Variable, in2::Variable)
    out = Variable()
    Addition(in1, out, in2)
    return out
end

function -(in1::Variable, in2)
    @ensureVariables(in2)
    in1 - in2
end

function -(in1, in2::Variable)
    @ensureVariables(in1)
    in1 - in2
end


# Pseudocode: http://www.stat.rice.edu/%7Escottdw/stat550/HW/hw4/c05.pdf

type MVAsh{F1 <: Function, F2 <: Function, R1 <: Range, R2 <: Range} <: AbstractAsh
    kernelx::F1
    kernely::F2
    rngx::R1
    rngy::R2
    mx::Int64
    my::Int64
    v::Matrix{Int}
    z::Matrix{Float64}
    nobs::Int64
end

function MVAsh(
        kernelx::Function, kernely::Function, rngx::Range, rngy::Range,
        mx::Int64, my::Int64, v::Matrix{Int}, z::Matrix{Float64}, nobs::Int64
        )
    @assert kernelx(0.1) > 0.0 && kernelx(-0.1) > 0.0 "kernelx must always be positive"
    @assert kernely(0.1) > 0.0 && kernely(-0.1) > 0.0 "kernely must always be positive"
    @assert length(rngx) > 1 "Need at least two bins"
    @assert length(rngy) > 1 "Need at least two bins"
    @assert length(rngx) == size(v, 2) == size(z, 2)
    @assert length(rngy) == size(v, 1) == size(z, 1)
    @assert mx > 0 "Smoothing parameter mx must be positive"
    @assert my > 0 "Smoothing parameter my must be positive"
    MVAsh{typeof(kernelx), typeof(kernely), typeof(rngx), typeof(rngy)}(
        kernelx, kernely, rngx, rngy, mx, my, v, z, nobs
    )
end
function Base.show(io::IO, o::MVAsh)
    println(io, typeof(o))
    println(io, "  > X")
    println(io, "    - m      : ", o.mx)
    println(io, "    - kernel : ", o.kernelx)
    println(io, "    - edges  : ", o.rngx)
    println(io, "  > Y")
    println(io, "    - m      : ", o.my)
    println(io, "    - kernel : ", o.kernely)
    println(io, "    - edges  : ", o.rngy)
end

function ash(
        x::Vector, y::Vector,
        rngx::Range = extendrange(x, .4, 100), rngy::Range = extendrange(y, .4, 100);
        mx::Int = 5, my::Int = 5,
        kernelx::Function = Kernels.biweight, kernely::Function = Kernels.biweight,
        warnout::Bool = true
    )
    ncol, nrow = length(rngx), length(rngy)
    o = MVAsh(
        kernelx, kernely, rngx, rngy, mx, my,
        zeros(Int64, nrow, ncol), zeros(nrow, ncol), 0
    )
    update!(o, x, y)
    ash!(o; warnout = warnout)
 end

function update!(o::MVAsh, x::AbstractVector, y::AbstractVector)
    n = length(x)
    @assert n == length(y)
    δx, δy = step(o.rngx), step(o.rngy)
    ax, ay = o.rngx[1], o.rngy[1]
    nbinx, nbiny = length(o.rngx), length(o.rngy)

    for i in 1:n
        kx = floor(Int, (x[i] - ax) / δx + 1.5)
        ky = floor(Int, (y[i] - ay) / δy + 1.5)
        if 1 <= kx <= nbinx && 1 <= ky <= nbiny
            o.v[ky, kx] += 1
        end
    end
    o.nobs += n
    o
end

function ash!(o::MVAsh;
        mx::Int64 = o.mx, my::Int64 = o.my,
        kernelx::Function = o.kernelx, kernely::Function = o.kernely,
        warnout::Bool = true
    )
    o.mx, o.my, o.kernelx, o.kernely = mx, my, kernelx, kernely
    δx, δy = step(o.rngx), step(o.rngy)
    nbinx, nbiny = length(o.rngx), length(o.rngy)

    w = zeros(2 * my - 1, 2 * mx - 1)
    for i = (1 - my):(my - 1), j = (1 - mx):(mx - 1)
        w[i + my, j + mx] = kernely(i / my) * kernelx(i / mx)
    end

    v, z = o.v, o.z
    for k = 1:nbiny
       for l = 1:nbinx
           if v[k, l] == 0
                l += 1
            else
                for i = max(1, k - my + 1):min(nbiny, k + my - 1)
                    for j = max(1, l - mx + 1):min(nbinx, l + mx - 1)
                        z[i, j] += v[k, l] * w[my + i - k, mx + j - l]
                    end
                end
            end
        end
    end

    # make density integrate to 1
    denom = 1.0 / (sum(o.z) * δx * δy)
    for i in eachindex(z)
        @inbounds z[i] *= denom
    end

    if warnout
        any(z[end, :] .!= 0)    && warn("nonzero density outside bounds at minimum(rngy)")
        any(z[1, :] .!= 0)      && warn("nonzero density outside bounds at maximum(rngy)")
        any(z[:, 1] .!= 0)      && warn("nonzero density outside bounds at minimum(rngx)")
        any(z[:, end] .!= 0)    && warn("nonzero density outside bounds at maximum(rngx)")
    end
    o
end

xyz(o::MVAsh) = (collect(o.rngx), collect(o.rngy), copy(o.z))
function Base.mean(o::MVAsh)
    meanx = mean(o.rngx, StatsBase.WeightVec(vec(sum(o.z, 1))))
    meany = mean(o.rngy, StatsBase.WeightVec(vec(sum(o.z, 2))))
    [meanx; meany]
end
function Base.var(o::MVAsh)
    varx = var(o.rngx, StatsBase.WeightVec(vec(sum(o.z, 1))))
    vary = var(o.rngy, StatsBase.WeightVec(vec(sum(o.z, 2))))
    [varx; vary]
end
Base.std(o::MVAsh) = sqrt(var(o))
function StatsBase.fit!(o::MVAsh, x::AbstractVector, y::AbstractVector; kw...)
    update!(o, x, y)
    ash!(o; kw...)
end

@recipe function f(o::MVAsh)
    seriestype --> :heatmap
    o.rngx, o.rngy, o.z
end


using LinearAlgebra
using CompScienceMeshes
using SauterSchwab3D

const pI   = point(6,5,3)
const pII  = point(5,2,3)
const pIII = point(7,1,0)
const pIV  = point(3,4,0)

const qII  = point(3,8,4)
const qIII = point(7,5,6)
const qIV  = point(9,8,5)

const P = simplex(pI,pII,pIII,pIV)
const Q = simplex(pI,qII,qIII,qIV)

const sing = SauterSchwab3D.singularity_detection(P,Q)

Accuracy2 = 16
cv_ref = CommonVertex6D(sing,SauterSchwab3D._legendre(Accuracy2,0.0,1.0))

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

function INTEGRAND(u,v)
   n1 = neighborhood(P,u)
   n2 = neighborhood(Q,v)
   x = cartesian(n1)
   y = cartesian(n2)
   output = integrand(x,y)*jacobian(n1)*jacobian(n2)
   return(output)
end

print("Ref: ")
ref = sauterschwab_parameterized(INTEGRAND, cv_ref)
println(ref)
println()

res_tp =[]
res_sp =[]
res_gm = []
n1 = []
n2 = []
n3 = []

#Grauss tensor product
for i in 2:1:14
   Accuracy = i
   cv = CommonVertex6D(sing,SauterSchwab3D._legendre(Accuracy,0.0,1.0))

   int_tp = sauterschwab_parameterized(INTEGRAND, cv)

   num_pts = 2*length(cv.qps)^6
   push!(n1,num_pts)

   push!(res_tp,int_tp)
end

#ShunnHam simplex product
for i in 2:1:7
   Accuracy = i
   cv_s = CommonVertex6D_S(sing,SauterSchwab3D._shunnham3D(Accuracy))

   int_sp = sauterschwab_parameterized(INTEGRAND, cv_s)

   println(length(cv_s.qps))
   num_pts = 2*length(cv_s.qps)^2
   push!(n2,num_pts)
   push!(res_sp,int_sp)
end

#GrundmannMoeller simplex product
for i in 2:1:12
   Accuracy = i
   cv_gm = CommonVertex6D_S(sing,SauterSchwab3D._grundmannMoeller3D(2*Accuracy-1))

   int_gm = sauterschwab_parameterized(INTEGRAND, cv_gm)

   println(length(cv_gm.qps))
   num_pts = 2*length(cv_gm.qps)^2
   push!(n3,num_pts)
   push!(res_gm,int_gm)
end

err_tp = norm.(res_tp.-ref)/norm(ref)
err_sp = norm.(res_sp.-ref)/norm(ref)
err_gm = norm.(res_gm.-ref)/norm(ref)

println(err_tp)
println(err_sp)
println(err_gm)

using Plots
plot( yaxis=:log, xaxis=:log, fontfamily="Times")
plot!(n1,err_tp, label="Gauss Tensor-Product",markershape=:circle)
plot!(n2,err_sp, label="Simplex Tensor-Product",markershape=:rect)
#plot!(n3,err_gm, label="Simplex-Product GM",markershape=:x)
plot!(xlims=(1e1,10^6),ylims=(1e-7,1))
plot!(xlabel="#Quad. points/Func. evals.", ylabel="Rel. Error.", title="Common Vertex 6D",legend=:bottomleft)

using BenchmarkTools

ref = 0.003477333267758975 - 0.5997621570461863im

cv = CommonVertex6D(sing,SauterSchwab3D._legendre(6,0.0,1.0))

int_tp = sauterschwab_parameterized(INTEGRAND, cv)

num_pts = 2*length(cv.qps)^6
println(num_pts)
err_tp = norm.(int_tp.-ref)/norm(ref)
println(err_tp)
@time for i in 1:100 sauterschwab_parameterized(INTEGRAND, cv) end

cv_s = CommonVertex6D_S(sing,SauterSchwab3D._shunnham3D(6))

int_sp = sauterschwab_parameterized(INTEGRAND, cv_s)

num_pts = num_pts = 2*length(cv_s.qps)^2
println(num_pts)
err_sp = norm.(int_sp.-ref)/norm(ref)
println(err_sp)
@time for i in 1:100 sauterschwab_parameterized(INTEGRAND, cv_s) end

#=
cv_gm = CommonVertex6D_S(SauterSchwab3D._grundmannMoeller3D(2*6-1))

int_gm = sauterschwab_parameterized(INTEGRAND, cv_gm)

num_pts = 2*length(cv_gm.qps)^2
println(num_pts)
err_gm = norm.(int_gm.-ref)/norm(ref)
println(err_gm)
=#


"""
    CauchyEndgame(x::AbstractVector; samples_per_loop = 8, max_winding_number = 12)

The Cauchy endgame tries to to predict the value a solution path `x(t)` at `0` , i.e. `x(0)`,
by using a generalization of [Cauchy's integral formula]. This is derived in [^MSW90].
The general idea is that we make loops around the origin until we have again the start point.
The number of loops is referred to as the *winding* or *cycle number*.

[^MSW90]: Morgan, A.P., Sommese, A.J. & Wampler, C.W. Numer. Math. (1990) 58: 669. https://doi.org/10.1007/BF01385648
[Cauchy's integral formula]: https://en.wikipedia.org/wiki/Cauchy%27s_integral_formula
"""
struct CauchyEndgame{AV<:AbstractVector}
    base_point::AV
    samples_per_loop::Int
    max_winding_number::Int

    function CauchyEndgame(
        x::AV;
        samples_per_loop::Int = 8,
        max_winding_number::Int = 12,
    ) where {AV<:AbstractVector}
        new{AV}(copy(x), samples_per_loop, max_winding_number)
    end
end

"""
    CauchyEndgameResult

An enum indicating the result of the [`predict!`](@ref) computation.

# Cases
* `CAUCHY_SUCCESS`: The endgame was successfull.
* `CAUCHY_TERMINATED_MAX_WINDING_NUMBER`: The endgame was terminated since the winding
  number is larger than the provided threshold.
* `CAUCHY_TERMINATED_ACCURACY_LIMIT`: The endgame was terminated since during the loop
  tracking the achievable accuracy was no more sufficient.
* `CAUCHY_TERMINATED`: The endgame was terminated due to some other error in the path
  tracking.
"""
@enum CauchyEndgameResult begin
    CAUCHY_SUCCESS
    CAUCHY_TERMINATED_MAX_WINDING_NUMBER
    CAUCHY_TERMINATED_ACCURACY_LIMIT
    CAUCHY_TERMINATED_MAX_ITERS
    CAUCHY_TERMINATED
end

"""
    predict!(p, tracker::CoreTracker, ::CauchyEndgame)

Try to predict the value of `x(0)` using the [`CauchyEndgame`](@ref).
For this we track the polygon defined by ``te^{i2πk/n}`` until we end again at ``x``.
Here ``n`` is the number of samples we take per loop, `samples_per_loop`.
The computation gives up if we have a winding number larger than `max_winding_number`.
It returns a tuple denoting the success ([`CauchyEndgameResult`](@ref)) the computed
winding number `m::Int` and th expected accuracy of the solution.

[Cauchy's integral formula]: https://en.wikipedia.org/wiki/Cauchy%27s_integral_formula
"""
function predict!(p, tracker::CoreTracker, cauchy::CauchyEndgame)
    @unpack base_point, samples_per_loop, max_winding_number = cauchy

    tracker.options.logarithmic_time_scale || throw(ArgumentError("The `CoreTracker` needs to use a logarithmic time scale for the cauchy endgame."))

    s = current_t(tracker)
    s_target = tracker.state.segment.target

    # store the current value in p
    base_point .= current_x(tracker)
    p .= 0.0
    x = current_x(tracker)

    # during the loop we fix the affine patch
    fix_patch!(tracker)

    m = k = 1
    Δθ = 2π / samples_per_loop
    result = CAUCHY_TERMINATED_MAX_WINDING_NUMBER
    # The accuracy of the Cauchy endgame depends on the minimal accuracy
    # of a sample point. If all sample points are computed with an accuracy τ
    # we can expect τ as an error of the solution
    max_acc = tracker.state.accuracy
    while m ≤ max_winding_number
        θⱼ = 0.0
        for j = 1:samples_per_loop
            θⱼ₋₁ = θⱼ
            θⱼ += Δθ

            retcode = track!(tracker, x, s + im * θⱼ₋₁, s + im * θⱼ; loop = true)
            max_acc = max(max_acc, tracker.state.accuracy)

            if !is_success(retcode)
                init!(tracker, base_point, s, s_target; keep_steps = true)
                if retcode == CoreTrackerStatus.terminated_accuracy_limit
                    return CAUCHY_TERMINATED_ACCURACY_LIMIT, m, max_acc
                elseif retcode == CoreTrackerStatus.terminated_maximal_iterations
                    return CAUCHY_TERMINATED_MAX_ITERS, m, max_acc
                else
                    return CAUCHY_TERMINATED, m, max_acc
                end
            end

            p .+= x
        end
        # Check that loop is closed
        if norm(tracker)(base_point, x) < 4 * tracker.options.accuracy
            p ./= m .* samples_per_loop
            result = CAUCHY_SUCCESS
            break
        end

        m += 1
    end

    # we have to undo the fixing of the patch
    unfix_patch!(tracker)
    init!(tracker, base_point, s, s_target; loop = true, keep_steps = true)

    result, m, max_acc
end

fix_patch!(tracker::CoreTracker) = tracker.options.update_patch = false
unfix_patch!(tracker::CoreTracker) = tracker.options.update_patch = true


# # @__NAME__

# PREAMBLE

# PKG_SETUP

# ## Setup

using Pkg # hide
Pkg.activate(@__DIR__) # hide
Pkg.instantiate() # hide
using RigidBodyDynamics
using StaticArrays
using SymPy

# ## Create symbolic parameters
# * Masses: $m_1, m_2$
# * Mass moments of inertia (about center of mass): $I_1, I_2$
# * Link lengths: $l_1, l_2$
# * Center of mass locations (w.r.t. preceding joint axis): $c_1, c_2$
# * Gravitational acceleration: $g$

inertias = @syms m_1 m_2 I_1 I_2 positive = true
lengths = @syms l_1 l_2 c_1 c_2 real = true
gravitational_acceleration = @syms g real = true
params = [inertias..., lengths..., gravitational_acceleration...]
transpose(params)

# ## Create double pendulum `Mechanism`
# A `Mechanism` contains the joint layout and inertia parameters, but no state information.

T = Sym # the 'scalar type' of the Mechanism we'll construct
axis = SVector(zero(T), one(T), zero(T)) # axis of rotation for each of the joints
double_pendulum = Mechanism(RigidBody{T}("world"); gravity = SVector(zero(T), zero(T), g))
world = root_body(double_pendulum) # the fixed 'world' rigid body

# Attach the first (upper) link to the world via a revolute joint named 'shoulder'
inertia1 = SpatialInertia(CartesianFrame3D("upper_link"),
    moment=I_1 * axis * transpose(axis),
    com=SVector(zero(T), zero(T), c_1),
    mass=m_1)
body1 = RigidBody(inertia1)
joint1 = Joint("shoulder", Revolute(axis))
joint1_to_world = one(Transform3D{T}, frame_before(joint1), default_frame(world));
attach!(double_pendulum, world, body1, joint1,
    joint_pose = joint1_to_world);

# Attach the second (lower) link to the world via a revolute joint named 'elbow'
inertia2 = SpatialInertia(CartesianFrame3D("lower_link"),
    moment=I_2 * axis * transpose(axis),
    com=SVector(zero(T), zero(T), c_2),
    mass=m_2)
body2 = RigidBody(inertia2)
joint2 = Joint("elbow", Revolute(axis))
joint2_to_body1 = Transform3D(
    frame_before(joint2), default_frame(body1), SVector(zero(T), zero(T), l_1))
attach!(double_pendulum, body1, body2, joint2,
    joint_pose = joint2_to_body1)

# ## Create `MechanismState` associated with the double pendulum `Mechanism`
# A `MechanismState` stores all state-dependent information associated with a `Mechanism`.

x = MechanismState(double_pendulum);

# Set the joint configuration vector of the MechanismState to a new vector of symbolic variables
q = configuration(x)
for i in eachindex(q)
    q[i] = symbols("q_$i", real = true)
end

# Set the joint velocity vector of the MechanismState to a new vector of symbolic variables
v = velocity(x)
for i in eachindex(v)
    v[i] = symbols("v_$i", real = true)
end

# ## Compute dynamical quantities in symbolic form

# Mass matrix
simplify.(mass_matrix(x))

# Kinetic energy
simplify(kinetic_energy(x))

# Potential energy
simplify(gravitational_potential_energy(x))

examples/6. Symbolics using SymPy/6. Symbolics using SymPy.jl


@testset "set operations on sphere" begin
    @testset "complement" begin
        sphere1 = ImplicitSphere(SVector(-1.0, -1.0, -1.0), 1.0)
        sp1node = CSGNode(sphere1, [])
        compnode = CSGNode(CSGBuilding.complement, [sp1node])

        @test isapprox(value(evaluate(compnode, SVector(-1.0, -1.0, -1.0))), 1)
        @test isapprox(value(evaluate(compnode, SVector(-1, -1, 0))), 0)
    end

    @testset "intersection" begin
        sphere1 = ImplicitSphere(v0, 1.0)
        sphere2 = ImplicitSphere(v0, 5.0)
        sp1node = CSGNode(sphere1, [])
        sp2node = CSGNode(sphere2, [])

        node1 = CSGNode(CSGBuilding.intersection, [sp1node, sp2node])

        @test value(evaluate(node1, v0)) < 0
        @test value(evaluate(node1, SVector(100,0,0))) > 0
    end

    @testset "subtraction" begin
        sphere1 = ImplicitSphere(v0, 1.0)
        sphere2 = ImplicitSphere(v0, 5.0)
        sp1node = CSGNode(sphere1, [])
        sp2node = CSGNode(sphere2, [])

        node1 = CSGNode(CSGBuilding.subtraction, [sp1node, sp2node])

        @test value(evaluate(node1, v0)) > 0
        @test value(evaluate(node1, SVector(100,0,0))) > 0
    end
end

@testset "unit cube" begin
    n1 = SVector(1,0,0.0);
    n2 = SVector(0,1,0.0);
    n3 = SVector(0,0,1.0);

    pl1 = ImplicitPlane(n1, n1)
    pln1 = CSGNode(pl1, [])

    pl2 = ImplicitPlane(n2, n2)
    pln2 = CSGNode(pl2, [])

    pl3 = ImplicitPlane(n3, n3)
    pln3 = CSGNode(pl3, [])

    pl4 = ImplicitPlane(-n1, -n1)
    pln4 = CSGNode(pl4, [])

    pl5 = ImplicitPlane(-n2, -n2)
    pln5 = CSGNode(pl5, [])

    pl6= ImplicitPlane(-n3, -n3)
    pln6 = CSGNode(pl6, [])

    tr1 = CSGNode(CSGBuilding.intersection, [pln1, pln2])
    tr2 = CSGNode(CSGBuilding.intersection, [pln3, pln4])
    tr3 = CSGNode(CSGBuilding.intersection, [tr1, tr2])
    tr4 = CSGNode(CSGBuilding.intersection, [tr3, pln5])
    cubetree = CSGNode(CSGBuilding.intersection, [tr4, pln6])

    unitps = [n1, n2, n3, -n1, -n2, -n3]

    @testset "on surface" begin
        for n in unitps
            @test isapprox(value(evaluate(cubetree, n)), 0)
        end
    end

    @testset "over surface" begin
        for n in unitps
            newp = 1.0001*n
            @test value(evaluate(cubetree, newp)) > 0
        end
    end

    @testset "under surface" begin
        for n in unitps
            newp = 0.9999*n
            @test value(evaluate(cubetree, newp)) < 0
        end
    end

    Random.seed!(1234)

    @testset "inside the cube" begin
        for i in 1:10
            @test value(evaluate(cubetree, rand(SVector{3}))) < 0
        end
    end

    @testset "outside the cube" begin
        for i in 1:10
            direction = 1.0001*sqrt(3)*normalize(rand(SVector{3}))
            @test value(evaluate(cubetree, direction)) > 0
        end
    end

end


"""
    struct ScaledMeasure{R,M} <: AbstractMeasure
        logscale :: R
        base :: M
    end

"""
struct ScaledMeasure{R,M} <: AbstractMeasure
    logscale :: R
    base :: M
end

function logdensity(sm::ScaledMeasure{R,M}, x::X) where {X, R, M <: AbstractMeasure}
    logdensity(sm.base, x) + sm.logscale
end

function Base.:*(k::Number, m::AbstractMeasure)
    if iszero(k)
        return TrivialMeasure
    else
        return ScaledMeasure{typeof(k), typeof(m)}(log(k),m)
    end
end

Base.:*(m::AbstractMeasure, k::Real) = k * m

≪(::M, ::ScaledMeasure{R,M}) where {R,M} = true
≪(::ScaledMeasure{R,M}, ::M) where {R,M} = true

basemeasure(μ::ScaledMeasure{R,M}) where {R,M} = μ.base

sampletype(μ:: ScaledMeasure) = sampletype(μ.base)


using Grid

function perlin_linearinterp(r::Array{Float64,2},m::Int,n::Int)

	# assuming a y,x array/matrix
	s = size(r);

	xc = 1:1:s[2];
	yc = 1:1:s[1];

	zr = CoordInterpGrid((yc,xc), r, BCnil, InterpLinear);

	zr[linspace(1,s[1],m),linspace(1,s[2],n)];
end

function perlin_noise(m::Int,n::Int)
	r = rand(m,n);

	size_series = [48,24,12,6,3];
	power_series = [1.0,0.5,0.25,0.125,0.0625];

	z = zeros(size(r));

	for k=1:length(size_series)
		z0 = perlin_linearinterp(
			r[1:round(Int,(m/size_series[k])),
			1:round(Int,(n/size_series[k]))],
			m,n);
		z = z + (z0 * power_series[k]);
	end
	z
end

#using Winston
#z = perlin_noise(300,300);
#imagesc(z,(minimum(z),maximum(z)));

# julia> z1 = map((x) -> clamp(x,0.5,0.9),z);
# julia> imagesc(z1,(minimum(z1),maximum(z1)))


module day18

function evalnext(s; addfirst=false)
    while true
        m = match(r"\((\d+[\+\*])+\d+\)", s)
        if isnothing(m) break end
        val = evalnext(m.match[2:end-1]; addfirst)
        s = replace(s, m.match => "$val")
    end

    if addfirst
        while true
            m = match(r"\d+\+\d+", s)
            if isnothing(m) break end
            val = +(parse.(Int64, split(m.match, '+'))...)
            s = replace(s, m.match => "$val")
        end
    end

    lhs = match(r"\d+", s).match
    current = parse(Int64, lhs)
    if length(lhs) == length(s) return current end
    s = s[length(lhs)+1:end]
    while true
        op = Dict('+' => +, '*' => *)[s[1]]
        rhs = match(r"\d+", s[2:end]).match
        current = op(current, parse(Int64, rhs))
        if length(rhs) + 1 == length(s) return current end
        s = s[length(rhs)+2:end]
    end
end

equations = map(x -> replace(x, " "=>""), readlines("day18_input.txt"))
println("Part 1: sum = $(sum(evalnext.(equations)))")

# Part 2
println("Part 2: sum = $(sum(evalnext.(equations; addfirst=true)))")

end


"""
    NormInfinityBridge{T}

The `NormInfinityCone` is representable with LP constraints, since
``t \\ge \\max_i \\lvert x_i \\rvert`` if and only if
``t \\ge x_i`` and ``t \\ge -x_i`` for all ``i``.
"""
struct NormInfinityBridge{T, F, G} <: SetMapBridge{T, MOI.Nonnegatives, MOI.NormInfinityCone, F, G}
    constraint::CI{F, MOI.Nonnegatives}
end
function concrete_bridge_type(::Type{<:NormInfinityBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormInfinityCone}) where T
    F = MOIU.promote_operation(+, T, G, G)
    return NormInfinityBridge{T, F, G}
end

function map_set(::Type{<:NormInfinityBridge}, set::MOI.NormInfinityCone)
    return MOI.Nonnegatives(2 * (MOI.dimension(set) - 1))
end
function inverse_map_set(::Type{<:NormInfinityBridge}, set::MOI.Nonnegatives)
    return MOI.NormInfinityCone(div(MOI.dimension(set), 2) + 1)
end

function map_function(::Type{<:NormInfinityBridge{T}}, func) where T
    scalars = MOIU.eachscalar(func)
    t = scalars[1]
    lb = scalars[2:end]
    ub = MOIU.operate(-, T, lb)
    f_new = MOIU.operate(vcat, T, ub, lb)
    for i in 1:(2 * (length(scalars) - 1))
        MOIU.operate_output_index!(+, T, i, f_new, t)
    end
    return f_new
end
function inverse_map_function(::Type{<:NormInfinityBridge{T}}, func) where T
    scalars = MOIU.eachscalar(func)
    t = MOIU.operate!(/, T, sum(scalars), T(length(scalars)))
    d = div(length(scalars), 2)
    x = MOIU.operate!(/, T, MOIU.operate!(-, T, scalars[(d + 1):end], scalars[1:d]), T(2))
    return MOIU.operate(vcat, T, t, x)
end
# Given a_i is dual on t - x_i >= 0 and b_i is dual on t + x_i >= 0,
# the dual on (t, x) in NormInfinityCone is (u, v) in NormOneCone, where
# v_i = -a_i + b_i and u = sum(a) + sum(b).
function adjoint_map_function(::Type{<:NormInfinityBridge}, func)
    scalars = MOIU.eachscalar(func)
    t = sum(scalars)
    d = div(length(scalars), 2)
    x = (scalars[(d + 1):end] - scalars[1:d])
    return vcat(t, x)
end
function inverse_adjoint_map_function(::Type{<:NormInfinityBridge{T}}, func::AbstractVector{T}) where T
    # This is used by `MOI.ConstraintDualStart`.
    # The result should belong to `MOI.Nonnegatives` and the sum of the elements should
    # be `t`.
    # Only one of dual of the upper and lower bound should be active so one of the two
    # duals is zero. We know which one since they should be nonnegative.
    # Then if `t = sum abs(x_i)`, we will indeed have only one of them being zero.
    t = func[1]
    y = func[2:end]
    lb = [x > 0 ?  x : zero(x) for x in y]
    ub = [x < 0 ? -x : zero(x) for x in y]
    x = [ub; lb]
    return x .+ (t - sum(x)) / length(x)
end

"""
    NormOneBridge{T}

The `NormOneCone` is representable with LP constraints, since
``t \\ge \\sum_i \\lvert x_i \\rvert`` if and only if there exists a vector y such that
``t \\ge \\sum_i y_i`` and ``y_i \\ge x_i``, ``y_i \\ge -x_i`` for all ``i``.
"""
struct NormOneBridge{T, F, G, H} <: AbstractBridge
    y::Vector{MOI.VariableIndex}
    ge_index::CI{F, MOI.GreaterThan{T}}
    nn_index::CI{G, MOI.Nonnegatives}
end
function bridge_constraint(::Type{NormOneBridge{T, F, G, H}}, model::MOI.ModelLike, f::H, s::MOI.NormOneCone) where {T, F, G, H}
    f_scalars = MOIU.eachscalar(f)
    d = MOI.dimension(s)
    y = MOI.add_variables(model, d - 1)
    ge_index = MOIU.normalize_and_add_constraint(model, MOIU.operate(-, T, f_scalars[1], MOIU.operate(sum, T, y)), MOI.GreaterThan(zero(T)), allow_modify_function=true)
    lb = f_scalars[2:d]
    ub = MOIU.operate(-, T, lb)
    lb = MOIU.operate!(+, T, lb, MOI.VectorOfVariables(y))
    ub = MOIU.operate!(+, T, ub, MOI.VectorOfVariables(y))
    f_new = MOIU.operate(vcat, T, ub, lb)
    nn_index = MOI.add_constraint(model, f_new, MOI.Nonnegatives(2d - 2))
    return NormOneBridge{T, F, G, H}(y, ge_index, nn_index)
end

MOI.supports_constraint(::Type{NormOneBridge{T}}, ::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormOneCone}) where T = true
MOIB.added_constrained_variable_types(::Type{<:NormOneBridge}) = Tuple{DataType}[]
MOIB.added_constraint_types(::Type{NormOneBridge{T, F, G, H}}) where {T, F, G, H} = [(F, MOI.GreaterThan{T}), (G, MOI.Nonnegatives)]
function concrete_bridge_type(::Type{<:NormOneBridge{T}}, H::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormOneCone}) where T
    S = MOIU.scalar_type(H)
    F = MOIU.promote_operation(+, T, S, S)
    G = MOIU.promote_operation(+, T, H, H)
    return NormOneBridge{T, F, G, H}
end

# Attributes, Bridge acting as a model
MOI.get(b::NormOneBridge, ::MOI.NumberOfVariables) = length(b.y)
MOI.get(b::NormOneBridge, ::MOI.ListOfVariableIndices) = b.y
MOI.get(b::NormOneBridge{T, F, G, H}, ::MOI.NumberOfConstraints{F, MOI.GreaterThan{T}}) where {T, F, G, H} = 1
MOI.get(b::NormOneBridge{T, F, G, H}, ::MOI.NumberOfConstraints{G, MOI.Nonnegatives}) where {T, F, G, H} = 1
MOI.get(b::NormOneBridge{T, F, G, H}, ::MOI.ListOfConstraintIndices{F, MOI.GreaterThan{T}}) where {T, F, G, H} = [b.ge_index]
MOI.get(b::NormOneBridge{T, F, G, H}, ::MOI.ListOfConstraintIndices{G, MOI.Nonnegatives}) where {T, F, G, H} = [b.nn_index]

# References
function MOI.delete(model::MOI.ModelLike, c::NormOneBridge)
    MOI.delete(model, c.nn_index)
    MOI.delete(model, c.ge_index)
    MOI.delete(model, c.y)
end

# Attributes, Bridge acting as a constraint
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintFunction, c::NormOneBridge{T, F, G, H}) where {T, F, G, H}
    ge_func = MOI.get(model, MOI.ConstraintFunction(), c.ge_index)
    nn_func = MOIU.eachscalar(MOI.get(model, MOI.ConstraintFunction(), c.nn_index))
    t = MOIU.operate!(+, T, ge_func, MOIU.operate!(/, T, sum(nn_func), T(2)))
    d = div(length(nn_func), 2)
    x = MOIU.operate!(/, T, MOIU.operate!(-, T, nn_func[(d + 1):end], nn_func[1:d]), T(2))
    return MOIU.convert_approx(H, MOIU.remove_variable(MOIU.operate(vcat, T, t, x), c.y))
end
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet, c::NormOneBridge)
    dim = 1 + div(MOI.dimension(MOI.get(model, MOI.ConstraintSet(), c.nn_index)), 2)
    return MOI.NormOneCone(dim)
end

function MOI.supports(
    ::MOI.ModelLike,
    ::Union{MOI.ConstraintPrimalStart, MOI.ConstraintDualStart},
    ::Type{<:NormOneBridge})

    return true
end
function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintPrimalStart,
                 bridge::NormOneBridge{T}, value) where T
    x_value = value[1 .+ (1:length(bridge.y))]
    y_value = abs.(x_value)
    for i in eachindex(bridge.y)
        MOI.set(model, MOI.VariablePrimalStart(), bridge.y[i], y_value[i])
    end
    MOI.set(model, attr, bridge.nn_index, [y_value - x_value; y_value + x_value])
    MOI.set(model, attr, bridge.ge_index, value[1] - reduce(+, y_value, init=zero(T)))
    return
end
function MOI.get(model::MOI.ModelLike,
                 attr::Union{MOI.ConstraintPrimal, MOI.ConstraintPrimalStart},
                 bridge::NormOneBridge)
    ge_primal = MOI.get(model, attr, bridge.ge_index)
    nn_primal = MOI.get(model, attr, bridge.nn_index)
    t = ge_primal + sum(nn_primal) / 2
    d = length(bridge.y)
    x = (nn_primal[(d + 1):end] - nn_primal[1:d]) / 2
    return vcat(t, x)
end
# Given a_i is dual on y_i - x_i >= 0 and b_i is dual on y_i + x_i >= 0 and c is dual on t - sum(y) >= 0,
# the dual on (t, x) in NormOneCone is (u, v) in NormInfinityCone, where
# v_i = -a_i + b_i and u = c.
function MOI.get(model::MOI.ModelLike,
                 attr::Union{MOI.ConstraintDual, MOI.ConstraintDualStart},
                 bridge::NormOneBridge)
    t = MOI.get(model, attr, bridge.ge_index)
    nn_dual = MOI.get(model, attr, bridge.nn_index)
    d = length(bridge.y)
    x = nn_dual[(d + 1):end] - nn_dual[1:d]
    return vcat(t, x)
end
# value[1 + i] = nn_dual[d + i] - nn_dual[i]
# and `nn_dual` is nonnegative. By complementarity slackness, only one of each
# `nn_dual` can be nonzero (except if `x = 0`) so we can set
# depending on the sense of `value[1 + i]`.
function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintDualStart,
                 bridge::NormOneBridge, value)
    t = MOI.set(model, MOI.ConstraintDualStart(), bridge.ge_index, value[1])
    d = length(bridge.y)
    nn_dual = zeros(eltype(value), 2d)
    for i in eachindex(bridge.y)
        v = value[1 + i]
        if v < 0
            nn_dual[i] = -v
        else
            nn_dual[d + i] = v
        end
    end
    MOI.set(model, MOI.ConstraintDualStart(), bridge.nn_index, nn_dual)
    return
end


#!/usr/bin/env julia

using Luxor, Colors

function drawbbox(apoly)
    gsave()
    setline(0.3)
    setdash("dotted")
    box(polybbox(apoly), :stroke)
    grestore()
end

function drawpoly(p, x, y, counter)
    gsave()
    translate(x, y)
    sethue("purple")
    psort = polysortbyangle(p)
    drawbbox(psort)
    poly(psort, close=true, :stroke)

    # label points
    for (n, pt) in enumerate(psort)
        circle(pt, 1, :fill)
        text(string(n), pt.x, pt.y)
    end

    sethue("green")
    cp = polycentroid(psort)
    circle(cp, 2, :fill)
    text(string(counter), cp.x, cp.y)

    # highlight cases where centroid isn't inside poly. Might be an error...
    if ! isinside(cp, psort)
        gsave()
        setdash("dotted")
        setopacity(0.2)
        sethue("red")
        circle(cp, 100, :fillstroke)
        grestore()
    end

    grestore()
end

function polycentroidsort(width, height)
    pagetiles = Tiler(width, height, 5, 5, margin=50)
    tilesize = pagetiles.tilewidth/2
    for (pos, n) in pagetiles
      if rand(Bool)
        p = randompointarray(rand(-tilesize:-tilesize), rand(-tilesize:-tilesize), rand(tilesize:tilesize), rand(tilesize:tilesize), rand(5:12))
      else
        p = ngon(0, 0, tilesize, rand(3:12), vertices=true)
      end
      drawpoly(p, pos.x, pos.y, n)
    end
end

width, height = 3000, 3000
fname = "/tmp/polycentroidsort.pdf"
Drawing(width, height, fname)
fontsize(20)
origin()
polycentroidsort(width, height)
finish()
println("finished test: output in $(fname)")


using DataStructures

slurp_grid(filename) = parse.(Int8, permutedims(hcat(collect.(readlines(filename))...)))

get_neighbors(idx) = [idx + CartesianIndex(Δx, Δy) for Δx = -1:1 for Δy in -1:1 if !(Δx == Δy == 0)]

function in_bounds(grid, idx)
    y_max, x_max = size(grid)
    y, x = Tuple(idx)
    1 <= y <= y_max && 1 <= x <= x_max
end

get_neighbors(grid, idx) = [idx for idx in get_neighbors(idx) if in_bounds(grid, idx)]

get_neighbors(CartesianIndex(1, 1))

function step(grid, num_flashes)
    flashed = []
    to_increment = []
    grid = (i -> i + 1).(grid)
    for idx in CartesianIndices(grid)
        if grid[idx] > 9
            push!(flashed, idx)
            for idx_ in get_neighbors(grid, idx)
                if idx_ ∉ flashed
                    push!(to_increment, idx_)
                end
            end
        end
    end
    while !isempty(to_increment)
        next = pop!(to_increment)
        grid[next] += 1
        if grid[next] > 9 && next ∉ flashed
            push!(flashed, next)
            for idx in get_neighbors(grid, next)
                if idx ∉ flashed
                    push!(to_increment, idx)
                end
            end
        end
    end
    grid = (i -> i > 9 ? 0 : i).(grid)
    grid, num_flashes + length(flashed)
end

function solve1(filename)
    grid = slurp_grid(filename)
    num_flashes = 0
    for _ = 1:100
        grid, num_flashes = step(grid, num_flashes)
    end
    num_flashes
end

solve1("11eg.txt") == 1656
solve1("11.txt")

function step(grid)
    flashed = []
    to_increment = []
    grid = (i -> i + 1).(grid)
    for idx in CartesianIndices(grid)
        if grid[idx] > 9
            push!(flashed, idx)
            for idx_ in get_neighbors(grid, idx)
                if idx_ ∉ flashed
                    push!(to_increment, idx_)
                end
            end
        end
    end
    while !isempty(to_increment)
        next = pop!(to_increment)
        grid[next] += 1
        if grid[next] > 9 && next ∉ flashed
            push!(flashed, next)
            for idx in get_neighbors(grid, next)
                if idx ∉ flashed
                    push!(to_increment, idx)
                end
            end
        end
    end
    (i -> i > 9 ? 0 : i).(grid)
end

function solve2(filename)
    grid = slurp_grid(filename)
    count = 0
    while !all(==(0), grid)
        grid = step(grid)
        count += 1
    end
    count
end

solve2("11eg.txt")
solve2("11.txt")


using OrdinaryDiffEq, DiffEqCallbacks, Test

f2(u,p,t) = 1.01*u
u0=1/2
tspan = (0.0,5.0)
prob = ODEProblem(f2,u0,tspan)

sol = solve(prob,Tsit5())
@test maximum(diff(sol.t)) > 0.46

dtFE(u,p,t) = 1/20
cb = StepsizeLimiter(dtFE)
sol = solve(prob,Tsit5(),callback=cb)
@test maximum(diff(sol.t)) < 0.046001

cb = StepsizeLimiter(dtFE;safety_factor=1)
sol = solve(prob,Tsit5(),callback=cb)
@test maximum(diff(sol.t)) < 0.050001

cb = StepsizeLimiter(dtFE;safety_factor=1)
sol = solve(prob,Tsit5(),callback=cb,adaptive=false,dt=1/10)
@test maximum(diff(sol.t)) < 0.050001

dtFE(u,p,t) = t < 1 ? 1/20 : 1/10
cb = StepsizeLimiter(dtFE;safety_factor=1)
sol = solve(prob,Tsit5(),callback=cb,adaptive=false,dt=1/10)
@test maximum(diff(sol.t)) > 0.050001
@test maximum(diff(sol.t)) < 0.100001

sol = solve(prob,Tsit5(),callback=cb,adaptive=false,dt=1/20)
@test maximum(diff(sol.t)) < 0.050001

dtFE(u,p,t) = t + 0.2
cb = StepsizeLimiter(dtFE;safety_factor=1,max_step=true)
sol = solve(prob,Tsit5(),callback=cb,adaptive=false,dt=1/20)
@test diff(sol.t) ≈ [0.2,0.4,0.8,1.6,2.0]


using StatsBase

for path in ["94.jl.85.out", "94.jl.90.out"]
    X = readcsv(path)
    N = size(X,1)

    C = corspearman(map(Float64,X[:,3]), map(Float64, X[:,4]))
    println("Spearman correlation for $path: $C")

    v1 = map(Float64,X[:,3])
    v2 = map(Float64,X[:,4])
    r1 = similar(v1)
    r2 = similar(v2)

    r1[sortperm(v1)] = 1:N
    r2[sortperm(v2)] = 1:N

    C = 1 - 6*sum((r1 - r2).*(r1 - r2)) / (N^3-N)
    println("(manual computation) Spearman correlation for $path: $C")
end


lines = readlines("d07.in")

baggraph = Dict{String,Vector{Tuple{Int,String}}}()

function invertgraph(baggraph)
    invertedgraph = Dict{String,Set{String}}()
    for (outerbag, innerbags) in baggraph
        for (_, innerbag) in innerbags
            push!(invertedgraph, innerbag => union!(get(invertedgraph, innerbag, Set{String}()), [outerbag]))
        end
    end
    return invertedgraph
end

function containingbags(invertedgraph, desiredbag)
    bags = Set{String}()
    visit = Set([desiredbag])
    while !isempty(visit)
        bag = pop!(visit)
        if bag in keys(invertedgraph)
            outers = invertedgraph[bag]
            union!(bags, outers)
            union!(visit, outers)
            pop!(invertedgraph, bag)
        end
    end
    return bags
end

function countbags(baggraph, desiredbag)
    innerbags = baggraph[desiredbag]
    if isempty(innerbags)
        return 1
    else
        return 1 + sum(n * countbags(baggraph, innerbag) for (n, innerbag) in innerbags)
    end
end

for line in lines
    m = match(r"^([\w ]+) bags contain ", line)
    outerbag = m.captures[1]
    innerbags = eachmatch(r"(\d+) ([\w ]+) bags?[,.]", line[length(m.match)+1:end])
    push!(baggraph, outerbag => [Tuple([parse(Int, i.captures[1]), i.captures[2]]) for i in innerbags])
end

desiredbag = "shiny gold"

P1 = length(containingbags(invertgraph(baggraph),desiredbag))
P2 = countbags(baggraph, desiredbag) - 1

println("P1: ", P1)
println("P2: ", P2)