using JuMP, Gurobi, Cbc
import XLSX
using BenchmarkTools

#### read data ####
#xf = XLSX.readxlsx("../data/part1/data_numP10_numC10_09_05_13_29.xlsx")
xf = XLSX.readxlsx("../data/data1.xlsx")

l = xf["General!B1"]     # work day duration
N = xf["General!B3"] + 1  # number of clients + 1
M = xf["General!B2"]  # number of providers
# + 1 is for including the headquarter (HS14)

# Clients
s = xf["Clients!A2:A$N"]   # client service start times
d = vcat([0], xf["Clients!B2:B$N"])   # durations for clients, set d[1] = 0 for HS14

# Providers
w = xf["Providers!B2:B$(M+1)"]  # start time of providers
f = xf["Providers!A2:A$(M+1)"]  # hiring cost of providers

# make separate s vector for each provider, inserting into the first element the provider's work start time
s_test = ones(M, N)
for k in 1:M
    global s_test[k,:] = vcat([w[k]], s)
end
s = s_test

# Distances
t = xf["Distances"][:]  # 2-d array of travel times
##########

model = Model();
set_optimizer(model, Gurobi.Optimizer);
set_time_limit_sec(model, 300.0);

#set_optimizer(model, Gurobi.Optimizer);
# set 5 mintues time limit

#(13)
@variable(model, x[1:N, 1:N, 1:M], Bin)

# subtour constraint variables using u
@variable(model, 1 <= u[1:N, 1:M] <= N-1)

######################## routing constraints ######################

# (2) each client served once by one provider
@constraint(model, served[j in 2:N], sum(x[i,j,k] for i in 1:N for k in 1:M) == 1)

# (3) route continuity
@constraint(model, continuity[p in 2:N, k in 1:M], sum(x[i,p,k] for i in 1:N) == sum(x[p,j,k] for j in 1:N) )

# (4) not all providers need to leave 14HS (and start working)
@constraint(model, leave_depot[k in 1:M], sum(x[1,j,k] for j in 2:N) <= 1)

# (7) at least 1 provider must leave 14HS and start working to serve clients
@constraint(model, sum(x[1,j,k] for j in 2:N for k in 1:M) >= 1)

#(9) subtour elimination for each provider
for k in 1:M
    for i in 2:N
        for j in 2:N
            @constraint(model, u[i,k] - u[j,k] + (N-1) * x[i,j,k] <= (N-1) - 1 )
        end
    end
end

######################## end routing constraints ######################

######################## time constraints ######################

# (10) initial client service times
@constraint(model, start[j in 2:N, k in 1:M], x[1,j,k]*(s[k,1] + t[1,j] + d[1]) <= s[k,j])

# (11) in between start and end of work, service times
@constraint(model, bet[i in 2:N, j in 2:N, k in 1:M], x[i,j,k]*(s[k,i] + t[i,j] + d[i]) <= s[k,j])

# (12) come back to 14HS by hour l.
@constraint(model, endofwork[i in 2:N, k in 1:M], x[i,1,k]*(s[k,i] + t[i,1] + d[i]) <= l)

######################## end time constraints ######################

# newer objective to minimise total cost based on hours of work.
### Note. the max() function inside the objective can only be solved by Gurobi solver (Cbc cannot solve)
@objective(model, Min, sum(x[1,j,k]*f[k]*(sum(x[p,q,k]*(t[p,q] + d[p] + max(0, s[k,q] - s[k,p] - t[p,q] - d[p]) ) for p in 1:N for q in 1:N)) for j in 2:N for k in 1:M))

# original non-hourly objective for part 1
#@objective(model, Min, sum(f[k]*x[1,j,k] for j in 1:N for k in 1:M))

optimize!(model)

#@btime optimize!(model)

# see which providers are chosen to provide service.
for k in 1:M
    if sum(value.(x[1,:,k])) == 1
        println("Provider $k provides service.")
        println("Provider $k costs $(f[k]) per hour.")
        # come back hr
        for i in 2:N
            if value.(x[i,1,k]) == 1
                println("Provider $k comes back to 14HS at $(t[i,1] + s[k,i] + d[i])hr. \n")
                break # added
            end
        end
    end
end

# Given N: number of clients+1, and provider number k, find the path
### So if you found that providers 2, 3, 4 and 5 are chosen to work, run get_path(N, 2), get_path(N, 3) etc.
function get_path(N, k)
    path = []
    for j in 2:N
        if value.(x[1,j,k]) == 1
            println("\nstart of path")
            println(1, " => ")
            path = vcat(path, [1])
            println(j, " => ")
            path = vcat(path, [j])
            i = j
            while value.(x[i,1,k]) == 0
                for m in 2:N
                    if value.(x[i,m,k]) == 1
                        println(m, " => ")
                        path = vcat(path, [m])
                        i = m
                        break
                    end
                end
            end
            println(1)
            path = vcat(path, [1])
            println("end of path.... or in client index below (not location)")
        end
    end

    for j in 2:N
        if value.(x[1,j,k]) == 1
            println("\nstart of path")
            println(0, " => ")
            println(j-1, " => ")
            i = j
            while value.(x[i,1,k]) == 0
                for m in 2:N
                    if value.(x[i,m,k]) == 1
                        println(m-1, " => ")
                        i = m
                        break
                    end
                end
            end
            println(0)
            println("end of path (in client index)")
        end
    end
    return path
end

k = 2
path = get_path(N, k)

function overview(path, k)
    println("$(s[k,1])hr: Becomes available, leaves to location $(path[2]).")
    for i in 2:length(path)-1
        println("$(s[k,path[i-1]]+t[path[i-1], path[i]] + d[path[i-1]])hr: Arrived at location $(path[i]).")
        if s[k,path[i-1]]+t[path[i-1], path[i]] + d[path[i-1]] != s[k,path[i]]
            println("   Arrived early... Wait until service start time.")
        end
        println("$(s[k,path[i]])hr: Serve for $(d[path[i]]) hrs.")
        println("$(s[k,path[i]]+d[path[i]])hr: Finished service. Leave to location $(path[i+1]).")
    end
    println("$(s[k,path[length(path)-1]] + d[path[length(path)-1]] + t[path[length(path)-1], 1])hr: Arrived at location 1 (14HS).\n")
end

# print the overview of the path for provider k, where 'path' is found above using get_path(N,k)
path = [1 7 1]
k = 2
overview(path, k)

# find the number of worked hours = provider come back time (to 14HS) -  provider start time (w_k)
worked_hr = s[k,path[length(path)-1]] + d[path[length(path)-1]] + t[path[length(path)-1], 1] - s[k,1]
println("Provider $k worked for $worked_hr hours.")


@testset "sequences.jl" begin

    @testset "PGSE" begin
        δ, Δ = 500.0, 2000.0
        f = PGSE(δ, Δ)
        @test f.δ ≈ δ
        @test f.Δ ≈ Δ
        @test f(-100.0) ≈ 0.0
        @test f(prevfloat(0.0)) ≈ 0.0
        @test f(0.0) ≈ 1.0
        @test f(prevfloat(δ)) ≈ 1.0
        @test f(δ) ≈ 0.0
        @test f(nextfloat(δ)) ≈ 0.0
        @test f(prevfloat(Δ)) ≈ 0.0
        @test f(Δ) ≈ -1.0
        @test f(nextfloat(Δ)) ≈ -1.0
        @test f(Δ + δ + 100.0) ≈ 0.0

        # Integral
        T = LinRange(-δ, Δ + 2δ, 10)
        for t ∈ T
            i = intervals(f)
            i = i[0.0 .< i .< t]
            @test integral(f, t) ≈ quadgk(f, 0.0, i..., t)[1] rtol = 1e-8
        end

        # B-value
        b = quadgk(t -> integral(f, t)^2, 0.0, Δ + δ)[1]
        @test int_F²(f) ≈ b rtol = 1e-8
    end

    @testset "DoublePGSE" begin
        δ, Δ, p = 500.0, 2000.0, 1000.0
        tmid = p + δ + Δ
        f = DoublePGSE(δ, Δ, p)

        # Properties
        @test f.δ ≈ δ
        @test f.Δ ≈ Δ
        @test f.tpause ≈ p

        # Function call
        @test f(-100.0) ≈ 0.0
        @test f(prevfloat(0.0)) ≈ 0.0
        @test f(0.0) ≈ 1.0
        @test f(nextfloat(0.0)) ≈ 1.0
        @test f(prevfloat(δ)) ≈ 1.0
        @test f(δ) ≈ 0.0
        @test f(nextfloat(δ)) ≈ 0.0
        @test f(prevfloat(Δ)) ≈ 0.0
        @test f(Δ) ≈ -1.0
        @test f(nextfloat(Δ)) ≈ -1.0
        @test f(prevfloat(Δ + δ)) ≈ -1.0
        @test f(nextfloat(Δ + δ)) ≈ 0.0
        @test f(-prevfloat(tmid)) ≈ 0.0
        @test f(tmid) ≈ 1.0
        @test f(nextfloat(tmid)) ≈ 1.0
        @test f(prevfloat(tmid + δ)) ≈ 1.0
        @test f(nextfloat(tmid + δ)) ≈ 0.0
        @test f(tmid + δ) ≈ 0.0
        @test f(prevfloat(tmid + Δ)) ≈ 0.0
        @test f(tmid + Δ) ≈ -1.0
        @test f(nextfloat(tmid + Δ)) ≈ -1.0
        @test f(prevfloat(tmid + Δ + δ)) ≈ -1.0
        @test f(tmid + Δ + δ) ≈ 0.0
        @test f(nextfloat(tmid + Δ + δ)) ≈ 0.0
        @test f(tmid + Δ + δ + 100.0) ≈ 0.0

        # Integral
        T = LinRange(-δ, p + 2Δ + 3δ, 10)
        for t ∈ T
            i = intervals(f)
            i = i[0.0 .< i .< t]
            @test integral(f, t) ≈ quadgk(f, 0.0, i..., t)[1] rtol = 1e-8
        end

        # B-value
        b = quadgk(t -> integral(f, t)^2, 0.0, tmid + Δ + δ)[1]
        @test int_F²(f) ≈ b rtol = 1e-8
    end

    @testset "CosOGSE" begin

    end

    @testset "SinOGSE" begin

    end

    @testset "UnknownSequence" begin
        struct UnknownSequence{T} <: TimeProfile{T} end

        f = UnknownSequence{Float64}()

        @test_throws Exception f(0.0)
        @test_throws Exception integral(f, 0.0)
        @test_throws Exception int_F²(f)
        @test_throws Exception intervals(f)
        @test_throws Exception echotime(f)
        @test_throws Exception get_interval(f, 0.0)
        @test_throws Exception constant_intervals(f)
        @test_throws Exception isconstant(f, 0.0)
    end
end


    """
    `RacahAlgebra.specialValue(w3j::RacahAlgebra.W3j)`
        ... attempts to find a special value for the Wigner 3j symbol w3j.
            A (istrue, rex)::Tuple{Bool, RacahExpression} is returned, and where istrue determined of whether a
            special value is returned in rex. For istrue = false, rex has no meaning.
    """
    function specialValue(w3j::RacahAlgebra.W3j)
        deltas = Kronecker[];    triangles = Triangle[];   integrals = Integral[]
        w3js = W3j[];   w6js = W6j[];   w9js = W9j[];   ylms = Ylm[];    djpqs = Djpq[]

        rexList = RacahAlgebra.symmetricForms(w3j)
        for  rex in rexList
            ww = rex.w3js[1]
            #
            #  Rule:        ( j1  j2  j3 )        J/2 [(J-2*j1)!(J-2*j2)!(J-2*j3)!] 1/2                   (J/2)!
            #  -----        (            )  = (-1)    [---------------------------]       -----------------------------------
            #               (  0   0   0 )            [          (J+1)!           ]       (J/2 - j1)! (J/2 - j2)! (J/2 - j3)!
            #
            #               with  J = j1+j2+j3  is  even; the 3j symbol is zero if J is odd.
            #
            specialW3j = W3j(ww.ja, ww.jb, ww.jc, 0, 0, 0);     J = ww.ja + ww.jb + ww.jc
            if  ww == specialW3j
                push!( deltas, Kronecker(J, Basic(:even)) )
                wa = RacahExpression( rex.summations, integrals, rex.phase + J/2,
                        rex.weight * sqrt( factorial(J - 2*ww.ja) * factorial(J - 2*ww.jb) * factorial(J - 2*ww.jb) / factorial(J+1) ) *
                                      factorial(J/2) / ( factorial(J/2-ww.ja) * factorial(J/2-ww.jb) * factorial(J/2-ww.jc) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:        ( j   j   0 )           j-m     -1/2
            #  -----        (           )   =   (-1)      [j]
            #               ( m  -m   0 )
            #
            specialW3j = W3j(ww.ja, ww.ja, 0, ww.ma, -ww.ma, 0)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.ja - ww.ma, rex.weight / sqrt(2*ww.ja+1),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:        ( j   j-1/2   1/2 )           j-m-1   (   j - m   ) 1/2
            #  -----        (                 )   =   (-1)        (-----------)
            #               ( m  -m-1/2   1/2 )                   ( 2j (2j+1) )
            #
            specialW3j = W3j(ww.ja, ww.ja-1//2, 1//2, ww.ma, -ww.ma-1//2, 1//2)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.ja - ww.ma - 1,
                                      rex.weight * sqrt( (ww.ja - ww.ma)/(2*ww.ja * (2*ww.ja+1)) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:        ( j+1    j    1 )           j-m-1  (    (j-m)(j-m+1)    ) 1/2
            #  -----        (               )   =   (-1)       (--------------------)
            #               (  m   -m-1   1 )                  ( (2j+3)(2j+2)(2j+1) )
            #
            specialW3j = W3j(ww.jb+1, ww.jb, 1, ww.ma, -ww.ma-1, 1)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma - 1,
                                      rex.weight * sqrt( (ww.jb - ww.ma)*(ww.jb - ww.ma + 1)/(2*ww.jb+3) * (2*ww.jb+2) * (2*ww.jb+1) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:        ( j+1   j   1 )           j-m-1  (  2(j+m+1)(j-m+1)   ) 1/2
            #  -----        (             )   =   (-1)       (--------------------)
            #               (  m   -m   0 )                  ( (2j+3)(2j+2)(2j+1) )
            #
            specialW3j = W3j(ww.jb+1, ww.jb, 1, ww.ma, -ww.ma, 0)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma - 1,
                        rex.weight * sqrt( 2*(ww.jb + ww.ma + 1)*(ww.jb - ww.ma + 1)/(2*ww.jb+3) * (2*ww.jb+2) * (2*ww.jb+1) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:        ( j    j     1 )           j-m  (  2*(j-m)(j+m+1)  ) 1/2
            #  -----        (              )   =   (-1)     (------------------)
            #               ( m   -m-1   1 )                ( (2j+2)(2j+1)(2j) )
            #
            specialW3j = W3j(ww.ja, ww.ja, 1, ww.ma, -ww.ma-1, 1)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.ja - ww.ma,
                        rex.weight * sqrt( 2*(ww.ja - ww.ma)*(ww.ja + ww.ma + 1)/(2*ww.ja+2) * (2*ww.ja+1) * (2*ww.ja) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:        ( j   j   1 )           j-m            2 m
            #  -----        (           )   =   (-1)      -----------------------
            #               ( m  -m   0 )                 ((2j+2)(2j+1)(2j))^(1/2)
            #
            specialW3j = W3j(ww.ja, ww.ja, 1, ww.ma, -ww.ma, 0)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.ja - ww.ma,
                                      rex.weight * 2*ww.ma / sqrt( (2*ww.ja+2) * (2*ww.ja+1) * (2*ww.ja) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 1      ( j+3/2    j    3/2 )           j-m+1/2   ( (j-m-1/2)(j-m+1/2)(j-m+3/2) ) 1/2
            #  -------      (                   )   =   (-1)          (-----------------------------)
            #               (   m   -m-3/2  3/2 )                     (   (2j+4)(2j+3)(2j+2)(2j+1)  )
            #
            specialW3j = W3j(ww.jb+3//2, ww.jb, 3//2, ww.ma, -ww.ma-3//2, 3//2)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma + 1//2,
                                      rex.weight * sqrt( ( (ww.jb-ww.ma-1//2) * (ww.jb-ww.ma+1//2) * (ww.jb-ww.ma-3//2) )/
                                                         ( (2*ww.jb+4) * (2*ww.jb+3) * (2*ww.jb+2) * (2*ww.jb+1) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 2      ( j+3/2    j    3/2 )           j-m+1/2 ( 3*(j-m+1/2)(j-m+3/2)(j+m+3/2) ) 1/2
            #  -------      (                   )   =   (-1)        (-------------------------------)
            #               (   m   -m-1/2  1/2 )                   (    (2j+4)(2j+3)(2j+2)(2j+1)   )
            #
            specialW3j = W3j(ww.jb+3//2, ww.jb, 3//2, ww.ma, -ww.ma-1//2, 1//2)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma + 1//2,
                                      rex.weight * sqrt( ( 3*(ww.jb-ww.ma+1//2) * (ww.jb-ww.ma+3//2) * (ww.jb+ww.ma+3//2) )/
                                                         ( (2*ww.jb+4) * (2*ww.jb+3) * (2*ww.jb+2) * (2*ww.jb+1) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 4      ( j+1/2    j    3/2 )            j-m-1/2           (        j-m+1/2         ) 1/2
            #  -------      (                   )    =   (-1)      (j+3*m+3/2) (------------------------)
            #               (   m   -m-1/2  1/2 )                              ( (2j+3)(2j+2)(2j+1)(2j) )
            #
            #
            specialW3j = W3j(ww.jb+1//2, ww.jb, 3//2, ww.ma, -ww.ma-1//2, 1//2)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma - 1//2,
                                      rex.weight * (ww.jb + 3*ww.ma + 3//2) *
                                      sqrt( (ww.jb-ww.ma+1//2) / ((2*ww.jb+3) * (2*ww.jb+2) * (2*ww.jb+1) * 2*ww.jb) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 5      ( j+2    j   2 )           j-m  (   (j-m-1)(j-m)(j-m+1)(j-m+2)   ) 1/2
            #  -------      (              )   =   (-1)     (--------------------------------)
            #               ( m    -m-2  2 )                ( (2j+5)(2j+4)(2j+3)(2j+2)(2j+1) )
            #
            specialW3j = W3j(ww.jb+2, ww.jb, 2, ww.ma, -ww.ma-2, 2)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma,
                                      rex.weight * sqrt( (ww.jb-ww.ma-1) * (ww.jb-ww.ma) * (ww.jb-ww.ma+1) *(ww.jb-ww.ma-2) /
                                                         ((2*ww.jb+5) * (2*ww.jb+4) * (2*ww.jb+3) * (2*ww.jb+2)  * (2*ww.jb+1) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 7      ( j+2   j   2 )          j-m  ( 6 (j+m+2)(j+m+1)(j-m+2)(j-m+1) ) 1/2
            #  -------      (             )  =   (-1)     (--------------------------------)
            #               ( m    -m   0 )               ( (2j+5)(2j+4)(2j+3)(2j+2)(2j+1) )
            #
            specialW3j = W3j(ww.jb+2, ww.jb, 2, ww.ma, -ww.ma, 0)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma,
                                      rex.weight * sqrt( 6* (ww.jb+ww.ma+2) * (ww.jb+ww.ma+1) * (ww.jb-ww.ma+2) * (ww.jb-ww.ma+1) /
                                                         ((2*ww.jb+5) * (2*ww.jb+4) * (2*ww.jb+3) * (2*ww.jb+2)  * (2*ww.jb+1) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 8      ( j+1    j    2 )         j-m+1    (  (j-m-1)(j-m)(j-m+1)(j+m+2)  ) 1/2
            #  -------      (               ) =   (-1)      2* (------------------------------)
            #               ( m    -m-2   2 )                  ( (2j+4)(2j+3)(2j+2)(2j+1)(2j) )
            #
            specialW3j = W3j(ww.jb+1, ww.jb, 2, ww.ma, -ww.ma-2, 2)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma + 1,
                                      rex.weight * 2* sqrt( (ww.jb-ww.ma-1) * (ww.jb-ww.ma) * (ww.jb-ww.ma+1) * (ww.jb-ww.ma+2) /
                                                            ((2*ww.jb+4) * (2*ww.jb+3) * (2*ww.jb+2) * (2*ww.jb+1)  * 2*ww.jb ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 9      ( j+1    j    2 )         j-m+1            (        (j-m+1)(j-m)          ) 1/2
            #  -------      (               ) =   (-1)     2*(j+2*m+2) (------------------------------)
            #               ( m    -m-1   1 )                          ( (2j+4)(2j+3)(2j+2)(2j+1)(2j) )
            #
            specialW3j = W3j(ww.jb+1, ww.jb, 2, ww.ma, -ww.ma-1, 1)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma + 1,
                                      rex.weight * 2* (ww.jb+2*ww.ma+2) * sqrt( (ww.jb-ww.ma-1) * (ww.jb-ww.ma) /
                                                           ((2*ww.jb+4) * (2*ww.jb+3) * (2*ww.jb+2) * (2*ww.jb+1)  * 2*ww.jb ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 10     ( j+1   j   2 )          j-m+1     (       6 (j+m+1)(j-m+1)       ) 1/2
            #  --------     (             )  =   (-1)      2*m (------------------------------)
            #               ( m    -m   0 )                    ( (2j+4)(2j+3)(2j+2)(2j+1)(2j) )
            #
            specialW3j = W3j(ww.jb+1, ww.jb, 2, ww.ma, -ww.ma, 0)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma + 1,
                                      rex.weight * 2*ww.ma * sqrt( 6* (ww.jb-ww.ma+1) * (ww.jb-ww.ma+1) /
                                                           ((2*ww.jb+4) * (2*ww.jb+3) * (2*ww.jb+2) * (2*ww.jb+1)  * 2*ww.jb ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 13     ( j   j   2 )          j-m         2 (3*m*m - j(j+1))
            #  --------     (           )  =   (-1)     ------------------------------------
            #               ( m  -m   0 )               ((2j+3)(2j+2)(2j+1)(2j)(2j-1))^(1/2)
            #
            specialW3j = W3j(ww.jb, ww.jb, 2, ww.ma, -ww.ma, 0)
            if  ww == specialW3j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.jb - ww.ma,
                                      rex.weight * 2* (3*ww.ma*ww.ma - ww.jb*(ww.jb+1)) /
                                                   sqrt( (2*ww.jb+3) * (2*ww.jb+2) * (2*ww.jb+1)  * 2*ww.jb * (2*ww.jb-1) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
        end

        return( (false, RacahExpression() ) )
    end

    """
    `RacahAlgebra.specialValue(w6j::RacahAlgebra.W6j)`
        ... attempts to find a special value for the Wigner 6-j symbol w6j.
            A (istrue, rex)::Tuple{Bool, RacahExpression} is returned, and where istrue determined of whether a
            special value is returned in rex. For istrue = false, rex has no meaning.
    """
    function specialValue(w6j::RacahAlgebra.W6j)
        deltas = Kronecker[];    triangles = Triangle[];   integrals = Integral[]
        w3js = W3j[];   w6js = W6j[];   w9js = W9j[];   ylms = Ylm[];    djpqs = Djpq[]

        rexList = RacahAlgebra.symmetricForms(w6j)
        for  rex in rexList
            ww = rex.w6js[1]
            #
            #                                            j1+j2+j3
            #  Rule:         ( j1   j2   j3 )        (-1)
            #  -----        {(              )}   =  --------------  t(j1,j2,j3)  d(j1,l2) d(j2,l1)
            #                ( l1   l2   0  )                   1/2
            #                                         [ j1, j2 ]
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, ww.d, ww.e, 0)
            if  ww == specialW6j
                push!( deltas, Kronecker(ww.a, ww.e) )
                push!( deltas, Kronecker(ww.b, ww.d) )
                push!( triangles, Triangle(ww.a, ww.b, ww.c) )
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.a + ww.b + ww.c,
                                      rex.weight / sqrt( (2*ww.a+1)*(2*ww.b+1)), deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:         ( j1     j2      j3   )            j1+j2+j3    (     (j1+j3-j2)(j1+j2-j3+1)     ) 1/2
            #  -----        {(                     )}   =   (-1)            (--------------------------------)
            #                ( 1/2  j3-1/2  j2+1/2 )                        ( (2*j2+1)(2*j2+2)(2*j3)(2*j3+1) )
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 1//2, ww.c -1//2,  ww.b +1//2)
            if  ww == specialW6j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.a + ww.b + ww.c,
                                      rex.weight * sqrt( (ww.a+ww.c-ww.b)*(ww.a+ww.b-ww.c+1) /
                                                         (2*ww.b+1)*(2*ww.b+2)*2*ww.c*(2*ww.c+1) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:         ( j1     j2      j3   )            j1+j2+j3+1   (     (j1+j2+j3+2)(j2+j3-j1+1)     ) 1/2
            #  -----        {(                     )}   =   (-1)             (----------------------------------)
            #                ( 1/2  j3+1/2  j2+1/2 )                         ( (2*j2+1)(2*j2+2)(2*j3+1)(2*j3+2) )
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 1//2, ww.c +1//2,  ww.b +1//2)
            if  ww == specialW6j
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.a + ww.b + ww.c + 1,
                                      rex.weight * sqrt( (ww.a+ww.b+ww.c+2)*(ww.b+ww.c-ww.a+1) /
                                                         (2*ww.b+1)*(2*ww.b+2)*(2*ww.c+1)*(2*ww.c+2) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:         ( j1   j2    j3  )            S       (           S(S+1)(S-2*j1)(S-2*j1-1)           ) 1/2
            #  -----        {(                )}   =   (-1)        (----------------------------------------------)
            #                (  1  j3-1  j2-1 )                    ( (2*j2-1)(2*j2)(2*j2+1)(2*j3-1)(2*j3)(2*j3+1) )
            #
            #                with  S = j1+j2+j3
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 1, ww.c -1,  ww.b -1)
            if  ww == specialW6j
                S = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + S,
                                      rex.weight * sqrt( S*(S+1)*(S-2*ww.a)*(S-2*ww.a -1) /
                                                         (2*ww.b-1)* 2*ww.b * (2*ww.b+1)* (2*ww.c-1)* 2*ww.c * (2*ww.c+1) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:         ( j1   j2   j3  )            S   (      2(S+1)(S-2*j1)(S-2*j2)(S-2*j3+1)        ) 1/2
            #  -----        {(               )}   =   (-1)    (----------------------------------------------)
            #                (  1  j3-1  j2  )                ( (2*j2)(2*j2+1)(2*j2+2)(2*j3-1)(2*j3)(2*j3+1) )
            #
            #                with  S = j1+j2+j3
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 1, ww.c -1,  ww.b)
            if  ww == specialW6j
                S = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + S,
                                      rex.weight * sqrt( 2*(S+1)*(S-2*ww.a)*(S-2*ww.b)*(S-2*ww.c +1) /
                                                         2*ww.b * (2*ww.b+1) * (2*ww.b+2) * (2*ww.c-1) * 2*ww.c * (2*ww.c+1) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:         ( j1   j2   j3    )            S   (     (S-2*j2-1)(S-2*j2)(S-2*j3+1)(S-2*j3+2)     ) 1/2
            #  -----        {(                 )}   =   (-1)    (------------------------------------------------)
            #                (  1  j3-1  j2+1  )                ( (2*j2+1)(2*j2+2)(2*j2+3)(2*j3-1)(2*j3)(2*j3+1) )
            #
            #                with  S = j1+j2+j3
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 1, ww.c -1,  ww.b +1)
            if  ww == specialW6j
                S = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + S,
                                      rex.weight * sqrt( (S-2*ww.b-1)*(S-2*ww.b)*(S-2*ww.c +1)*(S-2*ww.c +2) /
                                                         (2*ww.b+1) * (2*ww.b+2) * (2*ww.b+3) * (2*ww.c-1) * 2*ww.c * (2*ww.c+1) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:         ( j1  j2  j3  )            S+1         2 [ j2(j2+1) + j3(j3+1) -  j1(j1+1) ]
            #  -----        {(             )}   =   (-1)       ----------------------------------------------  1/2
            #                (  1  j3  j2  )                   ( (2*j2)(2*j2+1)(2*j2+2)(2*j3)(2*j3+1)(2*j3+2) )
            #
            #                with  S = j1+j2+j3
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 1, ww.c,  ww.b)
            if  ww == specialW6j
                S = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + S + 1,
                                      rex.weight * 2 * ( ww.b*(ww.b+1) + ww.c*(ww.c+1) - ww.a*(ww.a+1) ) /
                                                       sqrt( 2*ww.b * (2*ww.b+1) * (2*ww.b+2) * 2*ww.c * (2*ww.c+1) * (2*ww.c+2) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 1       (  a     b      c   )            s   (         (s-1)s(s+1)(s-2*a-2)(s-2*a-1)(s-2*a)         ) 1/2
            #  -----        {(                   )}   =   (-1)    (------------------------------------------------------)
            #                ( 3/2  c-3/2  b-3/2 )                ( (2*b-2)(2*b-1)(2*b)(2*b+1)(2*c-2)(2*c-1)(2*c)(2*c+1) )
            #
            #                with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 3//2, ww.c-3//2,  ww.b-3//2)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * sqrt( (s-1)* s * (s+1) * (s-2*ww.a-2) * (s-2*ww.a-1) * (s-2*ww.a)/
                                                         (2*ww.b-2)*(2*ww.b-1)*2*ww.b*(2*ww.b+1) * (2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 2       (  a     b      c   )            s   (       3*s(s+1)(s-2*a-1)(s-2*a)(s-2*b)(s-2*c+1)       ) 1/2
            #  -----        {(                   )}   =   (-1)    (------------------------------------------------------)
            #                ( 3/2  c-3/2  b-1/2 )                ( (2*b-1)(2*b)(2*b+1)(2*b+2)(2*c-2)(2*c-1)(2*c)(2*c+1) )
            #
            #                with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 3//2, ww.c-3//2,  ww.b-1//2)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * sqrt( 3*s * (s+1) * (s-2*ww.a-1) * (s-2*ww.a) * (s-2*ww.b) * (s-2*ww.c+1)/
                                                         ((2*ww.b-1)*2*ww.b*(2*ww.b+1)*(2*ww.b+2) * (2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1)) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 3       (  a     b      c   )            s   (   3*(s+1)(s-2*a)(s-2*b-1)(s-2*b)(s-2*c+1)(s-2*c+2)   ) 1/2
            #  -----        {(                   )}   =   (-1)    (------------------------------------------------------)
            #                ( 3/2  c-3/2  b+1/2 )                ( (2*b)(2*b+1)(2*b+2)(2*b+3)(2*c-2)(2*c-1)(2*c)(2*c+1) )
            #
            #                with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 3//2, ww.c-3//2,  ww.b+1//2)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * sqrt( 3* (s+1) * (s-2*ww.a) * (s-2*ww.b-1) * (s-2*ww.b) * (s-2*ww.c+1) * (s-2*ww.c+2)/
                                                         (2*ww.b *(2*ww.b+1)*(2*ww.b+2)*(2*ww.b+3) * (2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1)) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 4       (  a     b      c   )            s    (  (s-2*b-2)(s-2*b-1)(s-2*b)(s-2*c+1)(s-2*c+2)(s-2*c+3)  ) 1/2
            #  -----        {(                   )}   =   (-1)     (--------------------------------------------------------)
            #                ( 3/2  c-3/2  b+3/2 )                 ( (2*b+1)(2*b+2)(2*b+3)(2*b+4)(2*c-2)(2*c-1)(2*c)(2*c+1) )
            #
            #                with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 3//2, ww.c-3//2,  ww.b+3//2)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * sqrt( (s-2*ww.b-2) * (s-2*ww.b-1) * (s-2*ww.b) * (s-2*ww.c+1) * (s-2*ww.c+2) * (s-2*ww.c+3)/
                                                         ((2*ww.b+1)*(2*ww.b+2)*(2*ww.b+3)*(2*ww.b+4) * (2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1)) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule: 5       (  a     b      c   )            s                                      (                     (s+1)(s-2*a)                     ) 1/2
            #  -----        {(                   )}   =  (-1)   [2*(s-2*b)(s-2*c) - (s+2)(s-2*a-1)]  (------------------------------------------------------)
            #                ( 3/2  c-1/2  b-1/2 )                                                   ( (2*b-1)(2*b)(2*b+1)(2*b+2)(2*c-1)(2*c)(2*c+1)(2*c+2) )
            #
            #      with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 3//2, ww.c-1//2,  ww.b-1//2)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * (2*(s-2*ww.b)*(s-2*ww.c) - (s+2)*(s-2*ww.a-1)) *   sqrt( (s+1)*(s-2*ww.a) /
                                                         ((2*ww.b-1)*2*ww.b*(2*ww.b+1)*(2*ww.b+2) * (2*ww.c-1)*2*ww.c*(2*ww.c+1)*(2*ww.c+1)) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 6       (  a     b      c   )            s                                     (                   (s-2*b)(s-2*c+1)                   ) 1/2
            #   -----        {(                   )}  =  (-1)  [ (s-2*b-1)(s-2*c) - 2*(s+2)(s-2*a)]  (------------------------------------------------------)
            #                 ( 3/2  c-1/2  b+1/2 )                                                  ( (2*b)(2*b+1)(2*b+2)(2*b+3)(2*c-1)(2*c)(2*c+1)(2*c+2) )
            #
            #                 with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 3//2, ww.c-1//2,  ww.b+1//2)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * ((s-2*ww.b-1)*(s-2*ww.c) - 2*(s+2)*(s-2*ww.a)) *   sqrt( (s-2*ww.b) * (s-2*ww.c + 1) /
                                                    (2*ww.b * (2*ww.b+1)*(2*ww.b+2)*(2*ww.b+3) * (2*ww.c-1)*2*ww.c*(2*ww.c+1)*(2*ww.c+2)) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 7       ( a   b    c  )            s   (                  (s-2)(s-1)s(s+1)(s-2*a-3)(s-2*a-2)(s-2*a-1)(s-2*a) ) 1/2
            #   -----        {(             )}   =   (-1)    (---------------------------------------------------------------------)
            #                 ( 2  c-2  b-2 )                ( (2*b-3)(2*b-2)(2*b-1)(2*b)(2*b+1)(2*c-3)(2*c-2)(2*c-1)(2*c)(2*c+1)  )
            #
            #                 with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 2, ww.c-2,  ww.b-2)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * sqrt( (s-2) * (s-1) *s* (s-2*ww.a-3)* (s-2*ww.a-2)* (s-2*ww.a-1)* (s-2*ww.a) /
                            ((2*ww.b-3)*(2*ww.b-2)*(2*ww.b-1)*2*ww.b*(2*ww.b+1) * (2*ww.c-3)*(2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1)) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 8       ( a   b    c  )            s     (     (s-1)s(s+1)(s-2*a-2)(s-2*a-1)(s-2*a)(s-2*b)(s-2*c+1)            ) 1/2
            #   -----        {(             )}   =   (-1)    2 (---------------------------------------------------------------------)
            #                 ( 2  c-2  b-1 )                  ( (2*b-2)(2*b-1)(2*b)(2*b+1)(2*b+2)(2*c-3)(2*c-2)(2*c-1)(2*c)(2*c+1)  )
            #
            #                 with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 2, ww.c-2,  ww.b-1)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * 2 + sqrt( (s-1)*s*(s+1) * (s-2*ww.a-2) * (s-2*ww.a-1) * (s-2*ww.a) * (s-2*ww.b) * (s-2*ww.c+1)/
                            ((2*ww.b-2)*(2*ww.b-1)*2*ww.b*(2*ww.b+1)*(2*ww.b+2) * (2*ww.c-3)*(2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1)) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 9       ( a   b    c  )            s  (       6*s*(s+1)(s-2*a-1)(s-2*b-1)(s-2*a)(s-2*b)(s-2*c+1)(s-2*c+2)  ) 1/2
            #   -----        {(             )}   =   (-1)   (--------------------------------------------------------------------)
            #                 ( 2  c-2   b  )               ( (2*b-1)(2*b)(2*b+1)(2*b+2)(2*b+3)(2*c-3)(2*c-2)(2*c-1)(2*c)(2*c+1) )
            #
            #                 with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 2, ww.c-2,  ww.b)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * sqrt( 6*s * (s+1)*(s-2*ww.a-1)*(s-2*ww.b-1)*(s-2*ww.a)*(s-2*ww.b)*(s-2*ww.c+1)*(s-2*ww.c+2)/
                            ((2*ww.b-1)*2*ww.b*(2*ww.b+1)*(2*ww.b+2)*(2*ww.b+3) * (2*ww.c-3)*(2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1)) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 10      ( a   b    c  )            s    (    (s+1)(s-2*a)(s-2*b-2)(s-2*b-1)(s-2*b)(s-2*c+1)(s-2*c+2)(s-2*c+3) ) 1/2
            #   -----        {(             )}   =   (-1)   2 (---------------------------------------------------------------------)
            #                 ( 2  c-2  b+1 )                 ( (2*b)(2*b+1)(2*b+2)(2*b+3)(2*b+4)(2*c-3)(2*c-2)(2*c-1)(2*c)(2*c+1)  )
            #
            #                 with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 2, ww.c-2,  ww.b+1)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * 2 * sqrt( (s+1)*(s-2*ww.a)*(s-2*ww.b-2)*(s-2*ww.b-1)*(s-2*ww.b)*(s-2*ww.c+1)*(s-2*ww.c+2)*(s-2*ww.c+3) /
                                      ( 2*ww.b*(2*ww.b+1)*(2*ww.b+2)*(2*ww.b+3)*(2*ww.b+4) * (2*ww.c-3)*(2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 11      ( a   b    c  )            s   (  (s-2*b-3)(s-2*b-2)(s-2*b-1)(s-2*b)(s-2*c+1)(s-2*c+2)(s-2*c+3)(s-2*c+4) ) 1/2
            #   -----        {(             )}   =   (-1)    (-------------------------------------------------------------------------)
            #                 ( 2  c-2  b+2 )                ( (2*b+1)(2*b+2)(2*b+3)(2*b+4)(2*b+5)(2*c-3)(2*c-2)(2*c-1)(2*c)(2*c+1)    )
            #
            #                 with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 2, ww.c-2,  ww.b+2)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * sqrt( (s-2*ww.b-3)*(s-2*ww.b-2)*(s-2*ww.b-1)*(s-2*ww.b) * (s-2*ww.c+1)*(s-2*ww.c+2)*(s-2*ww.c+3)*(s-2*ww.c+4) /
                                      ( (2*ww.b+1)*(2*ww.b+2)*(2*ww.b+3)*(2*ww.b+4)*(2*ww.b+5) * (2*ww.c-3)*(2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 12      ( a   b    c  )        s                                    (                           s*(s+1)(s-2*a-1)(s-2*a)                  ) 1/2
            #   -----        {(             )} = (-1)   4 [ (a+b)(a-b+1) - (c-1)(c-b+1)]  (--------------------------------------------------------------------)
            #                 ( 2  c-1  b-1 )                                             ( (2*b-2)(2*b-1)(2*b)(2*b+1)(2*b+2)(2*c-2)(2*c-1)(2*c)(2*c+1)(2*c+2) )
            #
            #      with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 2, ww.c-1,  ww.b-1)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * 4 * ( (ww.a+ww.b)*(ww.a-ww.b+1) - (ww.c-1)*(ww.c-ww.b+1) ) *
                                      sqrt( s*(s+1) * (s-2*ww.a-1)*(s-2*ww.a) /
                                          ( (2*ww.b-2)*(2*ww.b-1)*2*ww.b*(2*ww.b+1)*(2*ww.b+2) * (2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1)*(2*ww.c+2) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 13      ( a   b    c  )        s                                (                    6*(s+1)(s-2*a)(s-2*b)(s-2*c+1)                  ) 1/2
            #   -----        {(             )} = (-1)   2 [ (a+b+1)(a-b) - c*c  + 1 ] (--------------------------------------------------------------------)
            #                 ( 2  c-1   b  )                                         ( (2*b-1)(2*b)(2*b+1)(2*b+2)(2*b+3)(2*c-2)(2*c-1)(2*c)(2*c+1)(2*c+2) )
            #
            #                 with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 2, ww.c-1, ww.b)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * 2 * ( (ww.a+ww.b+1)*(ww.a-ww.b) - ww.c*ww.c + 1 ) *
                                      sqrt(  6*(s+1) * (s-2*ww.a) * (s-2*ww.b) * (s-2*ww.c+1) /
                                           ( (2*ww.b-1)*2*ww.b*(2*ww.b+1)*(2*ww.b+2)*(2*ww.b+3) * (2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1)*(2*ww.c+2) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 14      ( a   b    c  )        s                                     (                  (s-2*b-1)(s-2*b)(s-2*c+1)(s-2*c+2)                ) 1/2
            #   -----        {(             )} = (-1)  4 [ (a+b+2)(a-b-1) - (c-1)(b+c+2) ] (--------------------------------------------------------------------)
            #                 ( 2  c-1  b+1 )                                              ( (2*b)(2*b+1)(2*b+2)(2*b+3)(2*b+4)(2*c-2)(2*c-1)(2*c)(2*c+1)(2*c+2) )
            #
            #                 with  s = a + b + c
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 2, ww.c-1,  ww.b+1)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * 4 * ( (ww.a+ww.b+2)*(ww.a-ww.b-1) - (ww.c-1)*(ww.b+ww.c+2) ) *
                                      sqrt( (s-2*ww.b-1)*(s-2*ww.b) * (s-2*ww.c+1)*(s-2*ww.c+2) /
                                          ( 2*ww.b*(2*ww.b+1)*(2*ww.b+2)*(2*ww.b+3)*(2*ww.b+4) * (2*ww.c-2)*(2*ww.c-1)*2*ww.c*(2*ww.c+1)*(2*ww.c+2) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #   Rule: 15      ( a   b   c )        s                                     (                                                                    ) 1/2
            #   -----        {(           )} = (-1)  2 [ 3*X(X-1) - 4*b*(b+1)*c*(c+1) ]  (--------------------------------------------------------------------)
            #                 ( 2   c   b )                                              ( (2*b-1)(2*b)(2*b+1)(2*b+2)(2*b+3)(2*c-1)(2*c)(2*c+1)(2*c+2)(2*c+3) )
            #
            #                 with  s = a + b + c   and   X = b*(b+1) + c*(c+1) - a*(a+1)
            #
            specialW6j = W6j( ww.a, ww.b, ww.c, 2, ww.c,  ww.b)
            if  ww == specialW6j
                s = ww.a + ww.b + ww.c;     X = ww.b*(ww.b+1) + ww.c*(ww.c+1) - ww.a*(ww.a+1)
                wa = RacahExpression( rex.summations, integrals, rex.phase + s,
                                      rex.weight * 2 * ( 3*X*(X-1) - 4*ww.b*(ww.b+1)*ww.c*(ww.c+1) ) *
                                      sqrt( 1 / ( (2*ww.b-1)*2*ww.b*(2*ww.b+1)*(2*ww.b+2)*(2*ww.b+3) * (2*ww.c-1)*2*ww.c*(2*ww.c+1)*(2*ww.c+2)*(2*ww.c+3) ) ),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
        end

        return( (false, RacahExpression() ) )
    end

    """
    `RacahAlgebra.specialValue(w9j::RacahAlgebra.W9j)`
        ... attempts to find a special value for the Wigner 9-j symbol w9j.
            A (istrue, rex)::Tuple{Bool, RacahExpression} is returned, and where istrue determined of whether a
            special value is returned in rex. For istrue = false, rex has no meaning.
    """
    function specialValue(w9j::RacahAlgebra.W9j)
        deltas = Kronecker[];    triangles = Triangle[];   integrals = Integral[]
        w3js= W3j[];   w6js = W6j[];   w9js = W9j[];   ylms = Ylm[];    djpqs = Djpq[]

        rexList = RacahAlgebra.symmetricForms(w9j)
        for  rex in rexList
            ww = rex.w9js[1]
            #
            #  Rule:         ( ja   jb   0 )
            #  -----         (             )         D(ja,jc,je)
            #               {( jc   jd   0 )}   =  -------------- 1/2  delta(a,b)  delta(c,d)  delta(e,f)
            #                (             )       [ ja, jc, je ]
            #                ( je   jf   0 )
            #
            specialW9j = W9j( ww.a, ww.b, 0, ww.c, ww.d, 0, ww.e, ww.f, 0)
            if  ww == specialW9j
                push!( deltas, Kronecker(ww.a, ww.b) )
                push!( deltas, Kronecker(ww.c, ww.d) )
                push!( deltas, Kronecker(ww.e, ww.f) )
                push!( triangles, Kronecker(ww.a, ww.c, ww.e) )
                wa = RacahExpression( rex.summations, integrals, rex.phase, rex.weight / sqrt( (2*ww.a+1)*(2*ww.c+1)*(2*ww.c+1)),
                                      deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
            #
            #  Rule:         ( ja   jb   jc )           jb+jc+je+jf
            #  -----         (              )       (-1)                   ( ja   jb   jc )
            #               {( jd   je   jf )}   =  --------------- 1/2   {(              )}
            #                (              )          [ jc, jg ]          ( je   jd   jg )
            #                ( jg   jh   0  )
            #
            specialW9j = W9j( ww.a, ww.b, ww.c, ww.d, ww.e, ww.f, ww.g, ww.h, 0)
            if  ww == specialW9j
                push!( w6js, W6j(ww.a, ww.b, ww.c, ww.e, ww.d, ww.g) )
                wa = RacahExpression( rex.summations, integrals, rex.phase + ww.b + ww.c + ww.e + ww.f,
                                      rex.weight / sqrt( (2*ww.c+1)*(2*ww.g+1)), deltas, triangles, w3js, w6js, w9js, ylms, djpqs )
                return( (true, wa) )
            end
        end

        return( (false, RacahExpression() ) )
    end


import FrankWolfe
import LinearAlgebra
import Random

using Test

n = Int(1e3)
k = 10000

s = rand(1:100)
@info "Seed: $s"

Random.seed!(s)

xpi = rand(n);
total = sum(xpi);
const xp = xpi ./ total;

f(x) = LinearAlgebra.norm(x - xp)^2
function grad!(storage, x)
    @. storage = 2 * (x - xp)
    return nothing
end

lmo = FrankWolfe.UnitSimplexOracle(1.0)
x0 = FrankWolfe.compute_extreme_point(lmo, rand(n))

@time xblas, _ = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Shortstep(),
    L=2,
    emphasis=FrankWolfe.blas,
    verbose=false,
    trajectory=false,
    momentum=0.9,
)

@time xmem, _ = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Shortstep(),
    L=2,
    emphasis=FrankWolfe.memory,
    verbose=true,
    trajectory=true,
    momentum=0.9,
)

@test f(xblas) ≈ f(xmem)


@testset "Minimal models of elliptic curves" begin

  @testset "Minimal model (Laska-Kraus-Connell" begin
    E = EllipticCurve([1,2,3,4,5])
    EE = @inferred laska_kraus_connell(E)
    @test a_invars(EE) == (1, -1, 0, 4, 3)

    E = EllipticCurve([625, -15625, 19531250, -2929687500, -34332275390625])
    EE = @inferred laska_kraus_connell(E)
    @test a_invars(EE) == (1, -1, 0, 4, 3)
  end

  @testset "Tates algorithm" begin
    E = EllipticCurve([625, -15625, 19531250, -2929687500, -34332275390625])
    EE = @inferred tates_algorithm_global(E)
    @test a_invars(EE) == (1, -1, 0, 4, 3)

    E = EllipticCurve([1,2,3,4,5])
    EE = @inferred tates_algorithm_global(E)
    @test a_invars(EE) == (1, -1, 0, 4, 3)

    #  25350.a1
    E = EllipticCurve([1, 1, 0, 40050, 7557750])
    Ep, K, f, c = tates_algorithm_local(E, 2)
    @test a_invars(tidy_model(Ep)) == a_invars(E)
    @test K == "I1"
    @test f == 1
    @test c == 1

    Ep, K, f, c = tates_algorithm_local(E, 3)
    @test a_invars(tidy_model(Ep)) == a_invars(E)
    @test K == "I2"
    @test f == 1
    @test c == 2

    Ep, K, f, c = tates_algorithm_local(E, 5)
    @test a_invars(tidy_model(Ep)) == a_invars(E)
    @test K == "III*"
    @test f == 2
    @test c == 2

    Ep, K, f, c = tates_algorithm_local(E, 13)
    @test a_invars(tidy_model(Ep)) == a_invars(E)
    @test K == "IV*"
    @test f == 2
    @test c == 1

    # 150.a1
    E = EllipticCurve([1, 1, 0, -20700, 1134000])
    Ep, K, f, c = tates_algorithm_local(E, 2)
    @test a_invars(tidy_model(Ep)) == a_invars(E)
    @test K == "I5"
    @test f == 1
    @test c == 1

    Ep, K, f, c = tates_algorithm_local(E, 3)
    @test a_invars(tidy_model(Ep)) == a_invars(E)
    @test K == "I10"
    @test f == 1
    @test c == 2

    Ep, K, f, c = tates_algorithm_local(E, 5)
    @test a_invars(tidy_model(Ep)) == a_invars(E)
    @test K == "III*"
    @test f == 2
    @test c == 2
  end

  @testset "Tates algorithm over number fields" begin
    #100.1-b2
    Rx, x = PolynomialRing(QQ, "x")
    L, a = number_field(x^2-x-1)
    E = EllipticCurve(L, [1, 1, 1, -3, 1])
    F, phi = transform_rstu(E,[12, -1, 1+2*a, 3+a])
    F, phi = integral_model(F)
    OL = ring_of_integers(L)
    P = 2*OL
    Ep, K, f, c, s = tates_algorithm_local(F, P)
    @test K == "I5"
    @test f == 1
    @test c == 5
    @test s == true
    @test valuation(discriminant(E),P) == valuation(discriminant(Ep),P)

    P = numerator(ideal(OL, -2*a+1))

    Ep, K, f, c, s = tates_algorithm_local(Ep, P)
    @test K == "IV"
    @test f == 2
    @test c == 3
    @test s == true

    @test valuation(discriminant(E),P) == valuation(discriminant(Ep),P)

    #49.1-CMa1
    L, a = number_field(x^2-x+1)
    E = EllipticCurve(L, [0, 1+a, a, a, 0])
    F, phi = transform_rstu(E,[4, -1+a, 7, a-4])
    F, phi = integral_model(F)
    OL = ring_of_integers(L)

    P = numerator(ideal(OL, -3*a+1))

    Ep, K, f, c, s = tates_algorithm_local(F, P)
    @test K == "II"
    @test f == 2
    @test c == 1
    @test s == true

    @test valuation(discriminant(E),P) == valuation(discriminant(Ep),P)
    #673.1-a1
    E = EllipticCurve(L, [0, a, a, -1+a, 0])
    F, phi = transform_rstu(E,[a, 0, -3+a, 7])
    F, phi = integral_model(F)
    P = numerator(ideal(OL, 29*a-8))
    Ep, K, f, c, s = tates_algorithm_local(F, P)
    @test K == "I1"
    @test f == 1
    @test c == 1
    @test s == false

    @test valuation(discriminant(E),P) == valuation(discriminant(Ep),P)
    #121.1-c3
    L, a = number_field(x^5 - x^4 - 4*x^3 + 3*x^2 + 3*x - 1)
    E = EllipticCurve(L, [0, a-1, a^3+a^2-2*a-1, -2*a-1, -a^4-a^3+a^2-a-2])
    F, phi = transform_rstu(E,[5, -1+a^2, a^3, 2*a-a^4])
    F, phi = integral_model(F)
    OL = ring_of_integers(L)
    P = numerator(ideal(OL, a^2+a-2))
    Ep, K, f, c, s = tates_algorithm_local(F, P)
    @test K == "I5*"
    @test f == 2
    @test c == 2
    @test s == true

    @test valuation(discriminant(E),P) == valuation(discriminant(Ep),P)
  end
end


module VectorSphericalHarmonics

using OffsetArrays
using StaticArrays
using LinearAlgebra
using SphericalHarmonics
using SphericalHarmonics: Pole, NorthPole, SouthPole
import SphericalHarmonics: eltypeY, eltypeP, getY
using SphericalHarmonicModes
using SphericalHarmonicArrays

export vshbasis
export vshbasis!
export genspharm
export genspharm!
export PB
export Irreducible
export Hansen
export SphericalCovariant
export Polar
export HelicityCovariant
export Cartesian

"""
    AbstractVSH

Abstract supertype of vector spherical harmonics
"""
abstract type AbstractVSH end
@doc raw"""
    Irreducible <: AbstractVSH

Vector spherical harmonics that are eigenfunctions of irreducible representationa of total, orbital as well as spin angular momenta.
They may be constructed by coupling scalar spherical harmonics ``Y_{L m}\left(\hat{n}\right)`` with the spherical basis
vectors ``\chi_\mu`` as

```math
\mathbf{Y}_{J M}^L\left(\hat{n}\right) = \sum_{m \mu} C^{J M}_{L m 1 \mu} Y_{L m}\left(\hat{n}\right) \chi_\mu,
```
"""
struct Irreducible <: AbstractVSH end

@doc raw"""
    Hansen <: AbstractVSH

Hansen vector spherical harmonics ``\mathbf{H}_{J M}^{(\lambda)}\left(\hat{n}\right)`` that are related to the [`Irreducible`](@ref)
harmonics through

```math
\begin{aligned}
\mathbf{H}_{JM}^{\left(-1\right)}\left(\hat{n}\right) & =-\sqrt{\frac{J+1}{2J+1}}\mathbf{Y}_{JM}^{J+1}\left(\hat{n}\right)+\sqrt{\frac{J}{2J+1}}\mathbf{Y}_{JM}^{J-1}\left(\hat{n}\right),\\
\mathbf{H}_{JM}^{\left(0\right)}\left(\hat{n}\right) & =\mathbf{Y}_{JM}^{J}\left(\hat{n}\right),\\
\mathbf{H}_{JM}^{\left(1\right)}\left(\hat{n}\right) & =\sqrt{\frac{J}{2J+1}}\mathbf{Y}_{JM}^{J+1}\left(\hat{n}\right)+\sqrt{\frac{J+1}{2J+1}}\mathbf{Y}_{JM}^{J-1}\left(\hat{n}\right),
\end{aligned}
```
"""
struct Hansen <:AbstractVSH end

@doc raw"""
    PB <: AbstractVSH

Phinney-Burridge vector spherical harmonics ``\mathbf{P}_{J M}^\gamma\left(\hat{n}\right)`` that are related to the [`Hansen`](@ref)
harmonics through

```math
\begin{aligned}
\mathbf{P}_{JM}^{+1}\left(\hat{n}\right) & =\frac{1}{\sqrt{2}}\left(\mathbf{H}_{JM}^{\left(1\right)}\left(\hat{n}\right)-\mathbf{H}_{JM}^{\left(0\right)}\left(\hat{n}\right)\right),\\
\mathbf{P}_{JM}^{0}\left(\hat{n}\right) & =\mathbf{H}_{JM}^{\left(-1\right)}\left(\hat{n}\right),\\
\mathbf{P}_{JM}^{-1}\left(\hat{n}\right) & =\frac{1}{\sqrt{2}}\left(\mathbf{H}_{JM}^{\left(1\right)}\left(\hat{n}\right)+\mathbf{H}_{JM}^{\left(0\right)}\left(\hat{n}\right)\right).
\end{aligned}
```
"""
struct PB <: AbstractVSH end
Base.broadcastable(YT::AbstractVSH) = Ref(YT)

include("basis.jl")

struct SHCache{T}
    S :: T
end

@doc raw"""
    cache([T::Type = Float64], θ, ϕ, jmax)

Pre-allocate a set of scalar spherical harmonics ``Y_{j m}(\theta,\phi)`` that go into evaluating the vector harmonics.
The cutoff `jmax` corresponds to the maximum degree of vector harmonics that we seek to evaluate.

The type `T` sets the precision used to evaluate the harmonics.
The spherical harmonics evaluated will be of type `Complex{T}`.
"""
cache(jmax) = cache(Float64, jmax)
cache(T, jmax) = SHCache(SphericalHarmonics.cache(T, jmax + 1))
cache(θ, ϕ, jmax) = cache(Float64, θ, ϕ, jmax)
function cache(T, θ, ϕ, jmax)
    S = SphericalHarmonics.cache(T, jmax + 1)
    shcache!(S, θ, ϕ)
    return SHCache(S)
end

@doc raw"""
    cache!(S, θ, ϕ, [jmax])

Update a pre-allocated set of scalar spherical harmonics ``Y_{j m}(\theta,\phi)``.
The cutoff `jmax` corresponds to the maximum degree of vector harmonics that we seek to evaluate.
If this is not provided, the cutoff angular degree in `S` will remain unchanged.
"""
cache!(S::SHCache, θ, ϕ, jmax...) = (shcache!(S.S, θ, ϕ, jmax...); return S)
function shcache!(S, θ, ϕ, jmax)
    computePlmcostheta!(S, θ, jmax + 1)
    computeYlm!(S, θ, ϕ, jmax + 1)
    return S
end
function shcache!(S, θ, ϕ)
    computePlmcostheta!(S, θ)
    computeYlm!(S, θ, ϕ)
    return S
end

getY(S::SHCache) = getY(S.S)
eltypeY(S::SHCache) = eltypeY(S.S)
eltypeP(S::SHCache) = eltypeP(S.S)

struct VSHCache{VSHVEC <: AbstractVector, SHC <: SHCache}
    Y :: VSHVEC
    S :: SHC
end

eltypeY(V::VSHCache{VSHVEC}) where {VSHVEC} = eltype(VSHVEC)
getY(V::VSHCache) = V.Y
_modes(V::VSHCache) = only(SphericalHarmonicArrays.shmodes(getY(V)))

function VSHCache(T::Type, YT::AbstractVSH, B::Basis, θ, ϕ, modes::Union{ML,LM})
    V = VSHCache(T, YT, B, modes)
    vshbasis!(V, YT, B, modes, θ, ϕ)
    return V
end
function VSHCache(T::Type, θ, ϕ, modes::Union{LM, ML})
    V = VSHCache(T, modes)
    genspharm!(V, modes, θ, ϕ)
    return V
end

function VSHCache(T::Type, YT::AbstractVSH, B::Basis, modes::Union{ML,LM})
    jmax = maximum(l_range(modes))
    S = cache(T, jmax)
    el = vshbasis(YT, B, first(modes)..., 0.0, 0.0, S)
    el_zero = _zero(el)
    v = [_copy(el_zero) for _ in 1:length(modes)]
    Y = SHArray(v, modes)
    VSHCache(Y, S)
end
function VSHCache(T::Type, modes::Union{LM, ML})
    jmax = maximum(l_range(modes))
    S = cache(T, jmax)
    el = genspharm(first(modes)..., 0.0, 0.0, S)
    el_zero = _zero(el)
    v = [_copy(el_zero) for _ in 1:length(modes)]
    Y = SHArray(v, modes)
    VSHCache(Y, S)
end
VSHCache(modes::Union{LM, ML}) = VSHCache(Float64, modes)
VSHCache(YT::AbstractVSH, args...) = VSHCache(Float64, YT, args...)

_maybewrapoffset(v, ::Union{Cartesian, Polar}) = v
_maybewrapoffset(v, B::Basis) = OffsetArray(v, _basisinds(B))

_vectorinds(::Irreducible, j) = j .+ (-1:1)
_vectorinds(::Hansen, j) = -1:1
_vectorinds(::PB, j) = -1:1

"""
    vshbasis(Y::AbstractVSH, B::Basis, j::Integer, m::Integer, n::Integer, θ, ϕ, [S = VectorSphericalHarmonics.cache(θ, ϕ, j)])

Evaluate the components of the vector spherical harmonics ``\\mathbf{Y}_{j m}^n(θ, ϕ)`` in the basis `B`.
A pre-allocated array of scalar spherical harmonics `S` may be passed as the final argument. This may be generated
using [`VectorSphericalHarmonics.cache`](@ref).
"""
function vshbasis(YT::AbstractVSH, B::Basis, j, m, n, θ, ϕ, S::Union{Nothing, SHCache} = nothing)
    Y = vshbasis(YT, B, j, m, θ, ϕ, S)
    vs = SVector{3}((Y[i, n] for i in UnitRange(axes(Y,1))))
    _maybewrapoffset(vs, B)
end

_PBHelicitycheck(Y, B, M) = M
_PBHelicitycheck(::PB, ::HelicityCovariant, M) = Diagonal(M[SVector{3}(diagind(M))])

"""
    vshbasis(Y::AbstractVSH, B::Basis, j::Integer, m::Integer, θ, ϕ, [S = VectorSphericalHarmonics.cache(θ, ϕ, j)])

Evaluate a set of vector spherical harmonics ``\\mathbf{Y}_{j m}^\\alpha(θ, ϕ)`` for valid values of ``\\alpha``,
and return their components in the basis `B`.
A pre-allocated array of scalar spherical harmonics `S` may be passed as the final argument. This may be generated
using [`VectorSphericalHarmonics.cache`](@ref).
"""
function vshbasis(Y::AbstractVSH, B::Basis, j::Integer, m::Integer, θ, ϕ, S::Union{Nothing, SHCache} = nothing)
    M = _vshbasis_angle(Y, B, j, m, θ, ϕ, S)
    M2 = _PBHelicitycheck(Y, B, M)
    OffsetArray(M2, _basisinds(B), _vectorinds(Y, j))
end

_computeS(::Nothing, θ, ϕ, j) = cache(θ, ϕ, j)
_computeS(S::SHCache, θ, ϕ, j) = S

function _vshbasis_angle(Y::AbstractVSH, B::Basis, j, m, θ, ϕ, S)
    if θ == 0
        return _vshbasis_angle(Y, B, j, m, NorthPole(), ϕ, S)
    elseif θ == pi
        return _vshbasis_angle(Y, B, j, m, SouthPole(), ϕ, S)
    else
        return _vshbasis(Y, B, j, m, θ, ϕ, _computeS(S, θ, ϕ, j))
    end
end

# VSH at poles are zero except for M = 0, ±1
function _vshbasis_angle(Y::AbstractVSH, B::Basis, j, m, θ::Pole, ϕ, S)
    if abs(m) > 1
        T = S === nothing ? ComplexF64 : eltypeY(S)
        M = SMatrix{3,3,T}(ntuple(x -> zero(T), Val(9)))
    else
        M = _vshbasis(Y, B, j, m, θ, ϕ, _computeS(S, θ, ϕ, j))
    end
    return M
end

function _vshbasis(::PB, B, j, m, θ, ϕ, S)
    H = vshbasis(Hansen(), B, j, m, θ, ϕ, S)
    Hp_static = SMatrix{3,3}(parent(H))
    Hm1 = Hp_static[:, 1]
    H0  = Hp_static[:, 2]
    Hp1 = Hp_static[:, 3]

    T = eltypeP(S)
    Ym1 = 1/√T(2) * (Hp1 + H0)
    Y0 = Hm1
    Yp1 = 1/√T(2) * (Hp1 - H0)

    SMatrix{3,3}((Ym1..., Y0..., Yp1...))
end

function __vshbasis(::Hansen, B, j, m, θ, ϕ, S)
    Y = vshbasis(Irreducible(), B, j, m, θ, ϕ, S)
    Yp_static = SMatrix{3,3}(parent(Y))
    Yjm1 = Yp_static[:, 1]
    Yj  = Yp_static[:, 2]
    Yjp1 = Yp_static[:, 3]

    T = eltypeP(S)
    Hp1 = √(T(j)/(2j+1)) * Yjp1 + √(T(j+1)/(2j+1)) * Yjm1
    H0 = Yj
    Hm1 = -√(T(j+1)/(2j+1)) * Yjp1 + √(T(j)/(2j+1)) * Yjm1

    M = SMatrix{3,3}((Hm1..., H0..., Hp1...))
    OffsetArray(M, _basisinds(B), _vectorinds(Hansen(), j))
end
function _vshbasis(::Hansen, B, j, m, θ, ϕ, S)
    M = __vshbasis(Hansen(), B, j, m, θ, ϕ, S)
    parent(M)
end
function _vshbasis(::Hansen, B::Polar, j, m, θ, ϕ, S::SHCache)
    HO = __vshbasis(Hansen(), B, j, m, θ, ϕ, S)
    Hm1 = SVector{3}(HO[1,-1], zero(eltype(HO)), zero(eltype(HO)))
    H0 = SVector{3}(zero(eltype(HO)), HO[2,0], HO[3,0])
    Hp1 = SVector{3}(zero(eltype(HO)), HO[2,1], HO[3,1])
    SMatrix{3,3}(Hm1..., H0..., Hp1...)
end
function _vshbasis(::Hansen, B::HelicityCovariant, j, m, θ, ϕ, S::SHCache)
    HO = __vshbasis(Hansen(), B, j, m, θ, ϕ, S)
    Hm1 = SVector{3}(zero(eltype(HO)), HO[0,-1], zero(eltype(HO)))
    H0 = SVector{3}(HO[-1,0], zero(eltype(HO)), HO[1,0])
    Hp1 = SVector{3}(HO[-1,1], zero(eltype(HO)), HO[1,1])
    SMatrix{3,3}(Hm1..., H0..., Hp1...)
end

function _vshbasis(::Irreducible, B::SphericalCovariant, j, m, θ, ϕ, S::SHCache)
    Y = getY(S)
    TC = eltype(Y)
    T = eltypeP(S)
    M = MMatrix{3, 3, TC}(ntuple(x -> zero(TC), Val(9)))
    Mof = OffsetArray(M, _basisinds(B), j .+ (-1:1))
    jmm = j-m
    jmmp1 = jmm+1
    jpm = j+m
    jpmp1 = jpm+1
    jp12jp3 = (j+1)*(2j+3)

    Mof[+1, j + 1] = √(T(jmmp1 * (jmmp1 + 1))/2jp12jp3)     * Y[(j+1, m-1)]
    Mof[ 0, j + 1] = -√(T(jmmp1 * jpmp1) / jp12jp3)         * Y[(j+1, m)]
    Mof[-1, j + 1] = √(T(jpmp1 * (jpmp1 + 1)) /2jp12jp3)    * Y[(j+1, m+1)]

    if j > 0
        jjp1 = j*(j+1)
        if m - 1 >= -j
            Mof[+1, j] = -√(T(jpm * jmmp1)/2jjp1) * Y[(j, m-1)]
        end
        Mof[0, j] = m * √(T(1)/jjp1) * Y[(j, m)]
        if m + 1 <= j
            Mof[-1, j] = √(T(jmm * jpmp1)/2jjp1) * Y[(j, m+1)]
        end

        j2jm1 = j*(2j-1)
        if m - 1 >= -(j - 1)
            Mof[+1, j - 1] = √(T(jpm * (jpm - 1))/2j2jm1) * Y[(j-1, m-1)]
        end
        if -(j - 1) <= m <= (j - 1)
            Mof[0, j - 1] = √(T(jmm * jpm)/j2jm1) * Y[(j-1, m)]
        end
        if m + 1 <= j - 1
            Mof[-1, j - 1] = √(T(jmm * (jmm - 1))/2j2jm1) * Y[(j-1, m+1)]
        end
    end

    SMatrix(M)
end
function _vshbasis(::Irreducible, B::Basis, j, m, θ, ϕ, S::SHCache)
    Y = _vshbasis(Irreducible(), SphericalCovariant(), j, m, θ, ϕ, S)
    C = basisconversionmatrix(SphericalCovariant(), B, θ, ϕ)
    C * Y
end

_neg1pow(m) = isodd(m) ? -1 : 1

_zero(Y) = oftype(Y, zero(Y))
_zero(Y::OffsetArray) = OffsetArray(_zero(parent(Y)), axes(Y))

_copy(A) = copy(A)
_copy(A::OffsetArray) = OffsetArray(_copy(parent(A)), axes(A))
_copy(D::Diagonal) = Diagonal(_copy(diag(D)))

"""
    vshbasis(Y::AbstractVSH, B::Basis, modes::Union{SphericalHarmonicModes.LM, SphericalHarmonicModes.ML}, θ, ϕ, [S = maximum(SphericalHarmonicModes.l_range(modes))])

Evaluate a set of vector spherical harmonics ``\\mathbf{Y}_{j m}^\\alpha(θ, ϕ)`` for valid values of ``\\alpha``
for all `(j,m)` in `modes`, and return their components in the basis `B`.
A pre-allocated array of scalar spherical harmonics `S` may be passed as the final argument. This may be generated
using [`VectorSphericalHarmonics.cache`](@ref).
"""
function vshbasis(Y::AbstractVSH, B::Basis, modes::Union{ML,LM}, θ, ϕ, S::Union{Nothing, SHCache} = nothing)
    if !((θ == 0 || θ == pi) && minimum(abs, m_range(modes)) > 1)
        S2 = _computeS(S, θ, ϕ, maximum(l_range(modes)))
    else
        S2 = nothing
    end
    j, m = first(modes)
    el = vshbasis(Y, B, j, m, θ, ϕ, S2)
    el_zero = _zero(el)
    v = [_copy(el_zero) for _ in 1:length(modes)]
    vshbasis!(v, Y, B, modes, θ, ϕ, S2)
    SHArray(v, modes)
end

_commonphase(::Irreducible, j, m) = _neg1pow(j + m + 1)
_commonphase(::Hansen, j, m) = _neg1pow(m + 1)
_commonphase(::PB, j, m) = _neg1pow(m)

_maybereversebasis(Y, ::AbstractVSH, ::Union{Cartesian, Polar}, YM) = Y
_maybereversebasis(Y, ::AbstractVSH, ::Union{SphericalCovariant, HelicityCovariant}, YM) = oftype(Y, _reverse!(Y, YM, dims = 1))
_maybereversebasis(Y, ::PB, ::Union{Cartesian, Polar}, YM) = oftype(Y, _reverse!(Y, YM, dims = 2))
_maybereversebasis(Y, ::PB, ::SphericalCovariant, YM) = oftype(Y, _reverse!(Y, YM))
_maybereversebasis(Y::Diagonal, ::PB, ::HelicityCovariant, YM) = Diagonal(oftype(parent(Y), _reverse!(parent(Y), parent(YM))))

# Use efficient reverse(::StaticArray) defined by StaticArrays if possible
_reverse!(Y::AbstractVector, ::Any; dims = :) = reverse(Y)
_reverse!(Y, YM; dims = :) = _reverse!(Y, YM, dims)
_reverse!(Y, ::Any, ::Colon) = reverse(Y)
function _reverse!(Y, YM, dims)
    copyto!(YM, Y)
    reverse!(YM, dims = dims)
    return YM
end

_basisconjphase(::Union{SphericalCovariant, HelicityCovariant}) = SVector{3}(-1,1,-1)
_basisconjphase(::Any) = 1

function _vectorindsphase(::Union{Irreducible, Hansen}, Y)
    ax = SVector{3}(axes(Y,2)...)
    map(_neg1pow, ax)'
end
_vectorindsphase(::Any, Y) = 1

_multiplyphase(A, B) = A .* B
_multiplyphase(A::Diagonal{<:Any, <:SVector}, B) = Diagonal(SMatrix{3,3}(A) .* B)

function _conjphase(YT, B, Y, j, m, scratchM = Y)
    phase = _basisconjphase(B) .* _vectorindsphase(YT, Y) * _commonphase(YT, j, m)
    Y2 =  _multiplyphase(_maybereversebasis(conj(parent(Y)), YT, B, scratchM), phase)
    OffsetArray(Y2, axes(Y))
end

function vshbasis!(A::AbstractVector, YT::AbstractVSH, B::Basis, modes::Union{ML,LM}, θ, ϕ, S::Union{Nothing, SHCache} = nothing)
    if θ == 0
        _vshbasis!(A, YT, B, modes, NorthPole(), ϕ, S)
    elseif θ == pi
        _vshbasis!(A, YT, B, modes, SouthPole(), ϕ, S)
    else
        _vshbasis!(A, YT, B, modes, θ, ϕ, S)
    end
    return A
end

function _vshbasis!(A::AbstractVector, YT::AbstractVSH, B::Basis, modes::Union{ML,LM}, θ, ϕ, S::Union{Nothing, SHCache} = nothing)
    if !(θ isa Pole && minimum(abs, m_range(modes)) > 1)
        S2 = _computeS(S, θ, ϕ, maximum(l_range(modes)))
        scratchM = similar(parent(first(A)))
    end
    for (ind, (j,m)) in zip(eachindex(A), modes)
        if θ isa Pole && abs(m) > 1
            A[ind] = OffsetArray(zero(A[ind]), _basisinds(B), _vectorinds(YT, j))
            continue
        end
        if (j,-m) in modes && modeindex(modes, j, -m) < ind
            Y1 = A[modeindex(modes, j, -m)]
            A[ind] = _conjphase(YT, B, Y1, j, m, scratchM)
        else
            A[ind] = vshbasis(YT, B, j, m, θ, ϕ, S2)
        end
    end
    return A
end

function vshbasis!(V::VSHCache, YT::AbstractVSH, B::Basis, θ, ϕ)
    vshbasis!(V, YT, B, _modes(V), θ, ϕ)
    return getY(V)
end

function vshbasis!(V::VSHCache, YT::AbstractVSH, B::Basis, modes::Union{LM, ML}, θ, ϕ)
    Y = getY(V)
    modes_Vorder = SphericalHarmonicModes.ofordering(_modes(V), modes)
    if !((θ == 0 || θ == pi) && minimum(abs, m_range(modes_Vorder)) > 1)
        cache!(V.S, θ, ϕ, maximum(l_range(modes)))
    end
    vshbasis!(parent(Y), YT, B, modes_Vorder, θ, ϕ, V.S)
    return Y
end

"""
    genspharm(j, m, θ, ϕ, [S = VectorSphericalHarmonics.cache(θ, ϕ, j)])

Evaluate the diagonal elements of the Phinney-Burridge ([`PB`](@ref)) vector spherical harmonics
in the [`HelicityCovariant`](@ref) basis for one mode `(j,m)`. Returns an array `Y` whose elements
`Y[n]` are given by

```math
Y_{jm}^N(\\theta,\\phi) = \\left[\\mathbf{P}_{j m}^N(θ, ϕ)\\right]^N
```

These components are referred to as "generalized spherical harmonics" by Dahlen & Tromp (1998),
which is the nomenclature used here.

A pre-allocated array of scalar spherical harmonics `S` may be passed as the final argument. This may be generated
using [`VectorSphericalHarmonics.cache`](@ref).
"""
function genspharm(j, m, θ, ϕ, S::Union{Nothing, SHCache} = nothing)
    Y = vshbasis(PB(), HelicityCovariant(), j, m, θ, ϕ, S)
    M = diag(parent(Y))
    OffsetArray(M, _basisinds(HelicityCovariant()))
end

"""
    genspharm(modes::Union{SphericalHarmonicModes.LM, SphericalHarmonicModes.ML}, θ, ϕ, [S = maximum(SphericalHarmonicModes.l_range(modes))])

Evaluate the diagonal elements of the Phinney-Burridge ([`PB`](@ref)) vector spherical harmonics
in the [`HelicityCovariant`](@ref) basis for all modes `(j,m)` in `modes`.

A pre-allocated array of scalar spherical harmonics `S` may be passed as the final argument. This may be generated
using [`VectorSphericalHarmonics.cache`](@ref).
"""
function genspharm(modes::Union{ML,LM}, θ, ϕ, S::Union{Nothing, SHCache} = nothing)
    if !((θ == 0 || θ == pi) && minimum(abs, m_range(modes)) > 1)
        S2 = _computeS(S, θ, ϕ, maximum(l_range(modes)))
    else
        S2 = nothing
    end
    j, m = first(modes)
    el = genspharm(j, m, θ, ϕ, S2)
    el_zero = _zero(el)
    v = [_copy(el_zero) for _ in 1:length(modes)]
    genspharm!(v, modes, θ, ϕ, S2)
    SHArray(v, modes)
end

function genspharm!(A::AbstractVector, modes::Union{ML,LM}, θ, ϕ, S::Union{Nothing, SHCache} = nothing)
    if θ == 0
        _genspharm!(A, modes, NorthPole(), ϕ, S)
    elseif θ == pi
        _genspharm!(A, modes, SouthPole(), ϕ, S)
    else
        _genspharm!(A, modes, θ, ϕ, S)
    end
    return A
end

function _genspharm!(A::AbstractVector, modes::Union{ML,LM}, θ, ϕ, S::Union{Nothing, SHCache} = nothing)
    if !(θ isa Pole && minimum(abs, m_range(modes)) > 1)
        S2 = _computeS(S, θ, ϕ, maximum(l_range(modes)))
    end
    for (ind, (j,m)) in zip(eachindex(A), modes)
        if θ isa Pole && abs(m) > 1
            A[ind] = OffsetArray(zero(A[ind]), _basisinds(HelicityCovariant()))
            continue
        end
        if (j,-m) in modes && modeindex(modes, j, -m) < ind
            Y = A[modeindex(modes, j, -m)]
            A[ind] = _conjphase(PB(), HelicityCovariant(), Y, j, m)
        else
            A[ind] = genspharm(j, m, θ, ϕ, S2)
        end
    end
    return A
end

function genspharm!(V::VSHCache, θ, ϕ)
    genspharm!(V, _modes(V), θ, ϕ)
    return getY(V)
end

function genspharm!(V::VSHCache, modes::Union{LM, ML}, θ, ϕ)
    modes_Vorder = SphericalHarmonicModes.ofordering(_modes(V), modes)
    if !((θ == 0 || θ == pi) && minimum(abs, m_range(modes_Vorder)) > 1)
        cache!(V.S, θ, ϕ, maximum(l_range(modes)))
    end
    genspharm!(parent(getY(V)), modes_Vorder, θ, ϕ, V.S)
    return getY(V)
end

include("precompile.jl")

end


include("../../src/SFFM.jl")
using LinearAlgebra, Plots#, SFFM

## define the model(s)
include("exampleModelDef.jl")

## section 4.3: the marginal stationary distribution of X
## mesh
Δtemp = 0.4
Nodes = collect(approxBounds[1, 1]:Δtemp:approxBounds[1, 2])
nBases = 3
Basis = "lagrange"
mesh = SFFM.DGMesh(
    approxModel,
    Nodes,
    nBases,
    Basis = Basis,
)
let
    ## analytic version for comparison
    # construction
    Ψₓ = SFFM.PsiFunX( approxModel)
    ξₓ = SFFM.MakeXiX( approxModel, Ψₓ)
    pₓ, πₓ, Πₓ, Kₓ = SFFM.StationaryDistributionX( approxModel, Ψₓ, ξₓ)

    c = 0
    for nBases in 1:2:3
        mesh = SFFM.DGMesh(
            approxModel,
            Nodes,
            nBases,
            Basis=Basis,
        )
        frapmesh = SFFM.FRAPMesh(
            approxModel,
            Nodes,
            nBases,
        )

        # evaluate the distribution
        analyticX = (
            pm = [pₓ[:];0;0],
            distribution = Πₓ(mesh.Nodes[2:end]) - Πₓ(mesh.Nodes[1:end-1]),
            x = (mesh.Nodes[1:end-1]+mesh.Nodes[2:end])./2,
            SFFM.SFFMProbability,
        )

        # plot it
        q = SFFM.plot(
            approxModel,
            mesh = mesh,
            dist = analyticX,
            color = :red,
            label = "Analytic",
            marker = :x,
            seriestype = :line,
            jitter = 0.5,
        )

        # now DG it
        c = c+1

        # compute the marginal via DG
        All = SFFM.MakeAll( approxModel, mesh, approxType = "interpolation")
        Ψ = SFFM.PsiFun(All.D)

        # construct FRAP matrices
        # me = SFFM.MakeME(SFFM.CMEParams[nBases], mean = SFFM.Δ(mesh)[1])
        B = SFFM.MakeFullGenerator( approxModel, frapmesh)
        D = SFFM.MakeD( mesh, B, All.R)
        Ψme = SFFM.PsiFun( D)

        # the distribution of X when Y first returns to 0
        ξ = SFFM.MakeXi( All.B, Ψ)
        ξme = SFFM.MakeXi( B, Ψme)

        marginalX, p, K = SFFM.MakeLimitDistMatrices(
            All.B,
            All.D,
            All.R.RDict,
            Ψ,
            ξ,
            mesh,
            approxModel,
        )
        marginalXme, pme, Kme = SFFM.MakeLimitDistMatrices(
            B,
            D,
            All.R.RDict,
            Ψme,
            ξme,
            mesh,
            approxModel,
        )
        # convert marginalX to a distribution for plotting
        Dist = SFFM.Coeffs2Dist(
            approxModel,
            mesh,
            marginalX,
            SFFM.SFFMProbability,
        )
        Distme = SFFM.Coeffs2Dist(
            approxModel,
            mesh,
            marginalXme,
            SFFM.SFFMProbability,
        )
        # plot it
        q = SFFM.plot!(
            q,
            approxModel,
            mesh,
            Dist,
            color = :green,
            label = "DG: N_k = "*string(nBases),
            seriestype = :line,
            jitter = 0.5,
        )
        q = SFFM.plot!(
            q,
            approxModel,
            mesh,
            Distme,
            color = :blue,
            label = "ME: N_k = "*string(nBases),
            seriestype = :line,
            jitter = 0.5,
        )

        q = plot!(windowsize=(600,600))
        titles = ["Phase 11" "Phase 10" "Phase 01" "Phase 00"]
        for sp in 1:4
            q = plot!(
                subplot = sp,
                xlims = (-0.5,5),
                title = titles[sp],
                xlabel = "x",
                ylabel = "Density / Probability",
            )
        end
        display(q)
    end
    # savefig(pwd()*"/examples/paperNumerics/dump/marginalStationaryDistX.png")
end


using Base.Test
using QuantumOptics

N = 3
Ncutoff = 2
T = [0.:0.1:1.;]

Ω = [0 2 3;
     2 0 1;
     3 1 0]

ω = [1., 1.2, 1.5]

basis_fock = FockBasis(Ncutoff)
basis = tensor([basis_fock for i=1:N]...)

a = destroy(basis_fock)
at = create(basis_fock)
I = identity(basis_fock)

rho0 = cumulantexpansion.ProductDensityOperator([coherentstate(basis_fock, i%Ncutoff) for i=1:N]...)

J = LazyTensor[]
Jdagger = LazyTensor[]

# Interaction picture
Hrot = LazyTensor[]
for i=1:N, j=1:N
    if i==j
        continue
    end
    h = LazyTensor(basis, [i,j], [a, at], Ω[i,j])
    push!(Hrot, h)
end
Hrot = LazySum(Hrot...)

# Schroedinger picture
function f(t, rho)
    H = LazyTensor[LazyTensor(basis, i, at*a, ω[i]) for i=1:N]
    for i=1:N, j=1:N
        if i==j
            continue
        end
        h = LazyTensor(basis, [i,j], [a, at], exp(1im*(ω[i]-ω[j])*t)*Ω[i,j])
        push!(H, h)
    end
    LazySum(H...), J, Jdagger
end

tout, rho_rot_t = cumulantexpansion.master(T, rho0, Hrot, J)
tout, rho_t = cumulantexpansion.master_timedependent(T, rho0, f)

for (i, t) in enumerate(tout)
    R = prod([embed(basis, i, expm(1im*ω[i]*t*full(at*a))) for i=1:N])
    rho_rot = rho_rot_t[i]
    rho = rho_t[i]
    @test tracedistance(full(rho_rot), full(R)*full(rho)*dagger(full(R))) < 1e-5
end


function get_cell_dof_basis(
  model::DiscreteModel,
  cell_reffe::AbstractArray{<:GenericRefFE{Nedelec}},
  ::CurlConformity)

  cell_dofs = lazy_map(get_dof_basis,cell_reffe)
  cell_ownids = lazy_map(get_face_own_dofs,cell_reffe)
  cell_map = get_cell_map(Triangulation(model))
  cell_Jt = lazy_map(Broadcasting(∇),cell_map)
  cell_x = lazy_map(get_nodes,cell_dofs)
  cell_Jtx  = lazy_map(evaluate,cell_Jt,cell_x)
  k = TransformNedelecDofBasis()
  lazy_map(k,cell_dofs,cell_Jtx,cell_ownids)
end

struct TransformNedelecDofBasis <: Map end

function return_cache(::TransformNedelecDofBasis,dofs,Jtx,ownids)
  # Assumes the same element type in all the mesh
  deepcopy(dofs)
end

function evaluate!(cache,::TransformNedelecDofBasis,dofs,Jtx,ownids)
  basis = cache # Assumes the same element type in all the mesh
  for face in 1:length(ownids)
    face_dofs_ids = ownids[face]
    face_point_ids = dofs.face_nodes[face]
    face_moments_out = basis.face_moments[face]
    face_moments_in = dofs.face_moments[face]
    for p in 1:length(face_point_ids)
      F = transpose(Jtx[p])
      for i in 1:length(face_dofs_ids)
        face_moments_out[p,i] = F⋅face_moments_in[p,i]
      end
    end
  end
  basis
end

function get_cell_shapefuns(
  model::DiscreteModel,
  cell_reffe::AbstractArray{<:GenericRefFE{Nedelec}},
  ::CurlConformity)

  cell_reffe_shapefuns = lazy_map(get_shapefuns,cell_reffe)
  cell_map = get_cell_map(Triangulation(model))
  k = ReferenceFEs.CoVariantPiolaMap()
  lazy_map(k,cell_reffe_shapefuns,cell_map)
end


module Curls

export Curl

using ...Common
using SparseArrays

struct Curl{N,T}
	curlB::SparseMatrixCSC{T,Int}
	curlE::SparseMatrixCSC{T,Int}
end

function Curl(sim::Simulation{1})

	∂ₓE = E_derivative(length(sim),sim.dx)
	posE = map(p->p.x,sim.x)
	PE = invperm(sortperm(posE))

	∂ₓB = B_derivative(length(sim),sim.dx)
	posB = expand_B_pos(posE,sim.dx)
	PB = invperm(sortperm(posB))

	permute!(∂ₓE,PB,PE) # not sure why this is the correct permutation
	permute!(∂ₓB,PE,PB) # but it works

	I3 = sparse([3,2],[2,3],[1,-1],3,3)
	curlB = kron(I3,∂ₓB)
	curlE = kron(I3,∂ₓE)

	return Curl{1,Float64}(curlB,curlE)
end

function Curl(sim::Simulation{2}) end

function Curl(sim::Simulation{3}) end

function B_derivative(n::Int,dx::Float64)
	rowsm = 1:n
	rowsp = 1:n
	colsm = 1:n
	colsp = 2:n+1
	vals = ones(n)
	return sparse(vcat(rowsm,rowsp),vcat(colsm,colsp),vcat(-vals,vals)/dx,n,n+1)
end

function E_derivative(n::Int,dx::Float64)
	rowsm = 2:n
	rowsp = 2:n
	colsm = 1:n-1
	colsp = 2:n
	vals = ones(n-1)
	return sparse(vcat([1,n+1],rowsm,rowsp),vcat([1,n],colsm,colsp),vcat([2,-2],-vals,vals)/dx,n+1,n)
end

expand_B_pos(sim::Simulation{1}) = expand_B_pos(map(p->p.x,sim.x),sim.dx)
expand_B_pos(posE::Vector,dx::Float64) = vcat(posE.-dx/2,maximum(posE)+dx/2)

end # module


using Distributions
import CovSel
import HD

# simple model that differes in only one edge
srand(123)

p = 20
Sigmax = eye(p,p)
Sigmay = zeros(p,p)
rho = 0.7
for i=1:p
    for j=1:p
        Sigmay[i,j]=rho^abs(i-j)
    end
end
sqmy = sqrtm(Sigmay)
n = 1000
X = randn(n,p)
Y = randn(n,p) * sqmy;
hSx = cov(X)
hSy = cov(Y)

tmp = ones(p,p)

# λ = 0.2
λ = rand(Uniform(0.15, 0.2))
λvec = λ*ones(div(p*(p+1), 2))

reload("HD")
reload("CovSel")

@time o1 = CovSel.Alt.differencePrecisionNaive(hSx, hSy, λ, tmp)
@time o2 = CovSel.Alt.differencePrecisionActiveShooting(hSx, hSy, λ, tmp)
@time o3 = CovSel.differencePrecisionActiveShooting(hSx, hSy, λvec)

@show maximum(abs.(o1 - o2))
@show maximum(abs.(CovSel.vec2tril(o3, p) - tril(o1)))

@show trace(hSx * o1 * hSy * o1) / 2 - trace(o1*(hSy-hSx))
o3 = CovSel.tril2symmetric(CovSel.vec2tril(o3, p))
@show trace(hSx * o3 * hSy * o3) / 2 - trace(o3*(hSy-hSx))

# non_zero_set = find( abs(precM) .> 1e-4 )
# @time CovSel.covsel_refit!(X, Z, U, S, non_zero_set)
#
# # find connected components
# import Images
# nonZero = zeros(S)
# for i in eachindex(S)
#   if abs(S[i]) > λ
#     nonZero[ i ] = 1
#   end
# end
# labels = Images.label_components(nonZero)
#
# function collect_groups(labels)
#     groups = [Int[] for i = 1:maximum(labels)]
#     for (i,l) in enumerate(labels)
#         if l != 0
#             push!(groups[l], i)
#         end
#     end
#     groups
# end
#
# groups = collect_groups(labels)
# S[ groups[1] ]
#
# S[1:2, 1:2]


######################################################################
# infotheoretic.jl
# has things like mutual information and stuff
######################################################################

# sum over all possible jammer locations
function p_obs(m::SearchDomain, xv::Float64, yv::Float64, o::Obs)
	prob = 0.0
	for xj = 1:m.num_cells
		for yj = 1:m.num_cells
			prob += m.b[xj,yj] * O(m, xv, yv, (xj-1,yj-1), o)
		end
	end
	return prob
end

# computes mutual information for a specific vehicle location
function mutual_information(m::SearchDomain, xv::Float64, yv::Float64)
	H_o = 0.0
	H_o_t = 0.0
	for o = 0:35
		po = p_obs(m, xv, yv, o)
		if po > 0.0
			H_o -= po * log(po)
		end

		# sum over theta
		for xj = 1:m.num_cells
			for yj = 1:m.num_cells
				pot = O(m, xv, yv, (xj-1,yj-1), o)
				if pot > 0.0
					H_o_t -= pot * m.b[xj,yj] * log(pot)
				end
			end
		end
	end
	return H_o - H_o_t
end

# computes mutual information for all locations
function mutual_information(m::SearchDomain)
	mut_info = zeros(m.num_cells, m.num_cells)
	for xv = 1:m.num_cells
		for yv = 1:m.num_cells
			mut_info[xv,yv] = mutual_information(m, xv-1.0, yv-1.0)
		end
	end
	return mut_info
end


using Knet, CUDArt, CUSPARSE
using Knet: loss, back

m = 10
n = 1000
dy = sparse(rand(1:m,n), 1:n, 1.0, m, n)
y = rand(m,n)
dx = similar(y)

for (yT,dyT) in ((Array,Array),
                 (Array,SparseMatrixCSC),
                 (CudaArray,CudaArray),
                 (CudaArray,CudaSparseMatrixCSC))
    display((:softloss, loss(SoftLoss(), convert(dyT, dy); y=convert(yT, y)), yT, dyT))
end

for (yT,dyT) in ((Array,Array),
                 (Array,SparseMatrixCSC),
                 (CudaArray,CudaArray),
                 (CudaArray,CudaSparseMatrixCSC))
    display((:softlossback, vecnorm(back(SoftLoss(), convert(dyT, dy); y=convert(yT, y), dx=convert(yT, dx))), yT, dyT))
end

for (yT,dyT) in ((Array,Array), (CudaArray,CudaArray))
    display((:quadloss, loss(QuadLoss(), convert(dyT, dy); y=convert(yT, y)), yT, dyT))
end

for (yT,dyT) in ((Array,Array), (CudaArray,CudaArray))
    display((:quadlossback, vecnorm(back(QuadLoss(), convert(dyT, dy); y=convert(yT, y), dx=convert(yT, dx))), yT, dyT))
end


abstract type GANLoss end

# Loss that includes the generator calls out to loss that just takes in generated values
Lᴰ(t::GANLoss, G, D, z, x; wD = 1f0, yG = (), yD = ()) = Lᴰ(t, D, x, G(z, yG...), wD = wD, yG = yG, yD = yD)

## Binary Cross Entropy Loss
struct GAN_BCELoss <: GANLoss end
const LBCE = Flux.Losses.logitbinarycrossentropy
Lᴰ(t::GAN_BCELoss, D, x, xtilde; wD = 1f0, yG = (), yD = ()) = LBCE(D(vcat(x, yD...)), wD) + LBCE(D(vcat(xtilde, yG...)), 0f0)
Lᴳ(t::GAN_BCELoss, G, D, z; yG = ()) = LBCE(D(vcat(G(vcat(z, yG...)), yG...)), 1f0)

## Least Squares Loss
struct GAN_LSLoss <: GANLoss end
Lᴰ(t::GAN_LSLoss, D, x, xtilde; wD = 1f0, yG = (), yD = ()) = Flux.mse(sigmoid.(D(vcat(x, yD...))), wD) + Flux.mse(sigmoid.(D(vcat(xtilde, yG...))), 0f0)
Lᴳ(t::GAN_LSLoss, G, D, z; yG = ()) = Flux.mse(D(vcat(G(vcat(z, yG...)), yG...)), 1f0)

## Hinge Loss
struct GAN_HingeLoss <: GANLoss end
Lᴰ(t::GAN_HingeLoss, D, x, xtilde; wD = 1f0, yG = (), yD = ()) = mean(relu.(1f0 .+ (-2f0 .* wD .+ 1f0) .* D(vcat(x, yD...)))) + mean(relu.(1f0 .+ D(vcat(xtilde, yG...))))
Lᴳ(t::GAN_HingeLoss, G, D, z; yG = ()) = -mean(D(vcat(G(vcat(z, yG...)), yG...)))

## WLoss
struct GAN_WLoss <: GANLoss end
Lᴰ(t::GAN_WLoss, D, x, xtilde; wD = 1f0, yG = (), yD = ()) = mean(D(vcat(xtilde, yG...))) -  mean((wD .* 2f0 .- 1f0) .* D(vcat(x, yD...)))
Lᴳ(t::GAN_WLoss, G, D, z; yG = ()) = -mean(D(vcat(G(vcat(z, yG...)), yG...)))

## WGAN-GP
@with_kw struct GAN_WLossGP <: GANLoss
	λ::Float32 = 10f0 # gp parameter
	noise_range::Float32 = 1f0 # Range of the ϵ parameter that picks a point to evalutate the gradient (smaller is closer to "real" data)
end

function Lᴰ(t::GAN_WLossGP, D, x, xtilde; wD = 1f0, yG = (), yD = ())
	loss = mean(D(vcat(xtilde, yG...))) -  mean((wD .* 2f0 .- 1f0) .* D(vcat(x, yD...)))

	# Compute gradient penalty
	loss += (length(yG) == 1) ?  t.λ*gradient_penalty(D, vcat(x, yD...), vcat(xtilde, yG...)) : t.λ*gradient_penalty(D, x, xtilde)
	loss
end

Lᴳ(t::GAN_WLossGP, G, D, z; yG = ()) = -mean(D(vcat(G(vcat(z, yG...)), yG...)))

GANLosses = Dict("BCE" => GAN_BCELoss(), "LS" => GAN_LSLoss(), "Hinge" => GAN_HingeLoss(), "W" => GAN_WLoss(), "WGP" => GAN_WLossGP())


##### Diffusive fluxes
@inline diffusive_flux_x(i, j, k, grid, κˣ::Number, c) = κˣ * Ax(i, j, k, grid) * ∂x⁰(i, j, k, grid, c)
@inline diffusive_flux_y(i, j, k, grid, κʸ::Number, c) = κʸ * Ay(i, j, k, grid) * ∂y⁰(i, j, k, grid, c)
@inline diffusive_flux_z(i, j, k, grid, κᶻ::Number, c) = κᶻ * Az(i, j, k, grid) * ∂z⁰(i, j, k, grid, c)

##### Laplacian diffusion operator
@inline function κ∇²(i, j, k, grid, κˣ, κʸ, κᶻ, c)
    return 1/volume(i, j, k, grid) * (δx⁺(i, j, k, grid, diffusive_flux_x, κˣ, c) +
                       δy⁺(i, j, k, grid, diffusive_flux_y, κʸ, c) +
                       δz⁺(i, j, k, grid, diffusive_flux_z, κᶻ, c))
end

##### calculate the tendency by diffusion for tracer c
@kernel function calc_diffusion!(Gc, grid, κˣ, κʸ, κᶻ, c, ΔT)
    i, j, k = @index(Global, NTuple)
    ### offset index for halo points
    ii = i + grid.Hx
    jj = j + grid.Hy
    kk = k + grid.Hz
    @inbounds Gc[ii, jj, kk] = Gc[ii, jj, kk] + κ∇²(ii, jj, kk, grid, κˣ, κʸ, κᶻ, c) * ΔT
end

function nut_diffusion!(Gcs, arch::Architecture, g, nutrients, κˣ, κʸ, κᶻ, ΔT)
    calc_diffusion_kernel! = calc_diffusion!(device(arch), (16,16), (g.Nx, g.Ny, g.Nz))
    barrier = Event(device(arch))

    events=[]
    for name in nut_names
        event = calc_diffusion_kernel!(Gcs[name].data, g, κˣ, κʸ, κᶻ, nutrients[name].data, ΔT, dependencies=barrier)
        push!(events, event)
    end

    wait(device(arch), MultiEvent(Tuple(events)))

    return nothing
end
nut_diffusion!(Gcs, arch::Architecture, g, nutrients, κ, ΔT) =
    nut_diffusion!(Gcs, arch::Architecture, g, nutrients, κ, κ, κ, ΔT)


function coef_H_uniform!(r::Integer, N::Integer, M::Integer,
                         store::Store{T}, version=2) where T <: AbstractFloat
    H = OffsetVector{Matrix{T}}(undef, 0:N-1)
    for ℓbar = 0:N-1
        H[ℓbar] = Array{T}(undef, r, r)
    end
    coef_H_uniform!(H, M, store, version)
    return H
end

function coef_H_uniform!(H::OffsetVector{Matrix{T}}, M::Integer,
                         store::Store{T}, version=2) where T <: AbstractFloat
    N = length(H)
    coef_H0_uniform!(H[0], store)
    if N ≥ 2
        coef_H1_uniform!(H[1], M, store)
    end
    if N ≥ 3
        if version == 1
            coef_H_uniform_ver1!(H, 2:N-1, M, store)
        elseif version == 2
            coef_H_uniform_ver2!(H, 2:N-1, M, store)
        else
            throw(ArgumentError("version must be 1 or 2"))
        end
    end
end

# H^0
function coef_H0_uniform!(r::Integer, store::Store{T}) where T <: AbstractFloat
    H0 =Array{T}(undef, r, r)
    coef_H0_uniform!(H0, store)
    return H0
end

function coef_H0_uniform!(H0::Matrix{T},
                          store::Store{T}) where T <: AbstractFloat
    r = size(H0, 1)
    α = store.α
    @argcheck r ≤ store.rmax
    @argcheck size(H0, 2) == r
    y, wy = rules(store.jacobi5)
    z, wz = rules(store.legendre)
    Φ  = view(store.C, 1:2r)
    for j = 1:r, i = 1:r
        Ψ  = view(store.Ψ, 1:j)
        dΨ = view(store.dΨ, 1:i)
        My = ceil(Integer, (i+j-1)/2)
        Mz = ceil(Integer, (i+j)/2)
        for my = 1:My
            Φ[my] = zero(T)
            y_ = y[My][my]
            for mz = 1:Mz
                z_ = z[Mz][mz]
                τ = ( y_ - z_ * y_ + 1 + z_ ) / 2
                σ = τ - 1 - y_
                legendre_polys!(Ψ, σ)
                deriv_legendre_polys!(dΨ, τ)
                Φ[my] += wz[Mz][mz] * Ψ[j] * dΨ[i]
            end
            Φ[my] /= 2
        end
        s = zero(T)
        for my = 1:My
            s += wy[My][my] * Φ[my]
        end
        H0[i,j] = s
    end
    σ, w = rules(store.jacobi1)
    c = 1 / ( 2^α * Γ(α) )
    for j = 1:r
        Ψ = view(store.Ψ, 1:j)
        M = ceil(Integer, j/2)
        s = zero(T)
        for m = 1:M
            σ_ = σ[M][m]
            legendre_polys!(Ψ, σ_)
            s += w[M][m] * Ψ[j]
        end
        for i = 1:r
            H0[i,j] = c * ( s - H0[i,j] )
        end
    end
end

# H^1
function coef_H1_uniform!(r::Integer, M::Integer,
                          store::Store{T}) where T <: AbstractFloat
    H1 = Array{T}(undef, r, r)
    coef_H1_uniform!(H1, M, store)
    return H1
end

function coef_H1_uniform!(H1::Matrix{T}, M::Integer,
                          store::Store{T}) where T <: AbstractFloat
    r = size(H1, 1)
    α = store.α
    @argcheck size(H1, 2) == r
    @argcheck r ≤ store.rmax

    σ, wσ = rule(store.legendre[M])
    Ψ = view(store.Ψ, 1:r, 1:M)
    legendre_polys!(Ψ, σ)
    A = view(store.A, 1:r)
    c = 2^(1-α)
    for j = 1:r
        s = zero(T)
        for m = 1:M
            s += wσ[m] * ( 3 - σ[m] )^(α-1) * Ψ[j,m]
        end
        A[j] = c * s
    end

    Mσ = ceil(Integer, r/2)
    σ, wσ = rule(store.jacobi1[Mσ])
    Ψ = view(store.Ψ, 1:r, 1:Mσ)
    legendre_polys!(Ψ, σ)
    B = view(store.B, 1:r)
    for j = 1:r
        s = zero(T)
        for m = 1:Mσ
            s+= wσ[m] * Ψ[j,m]
        end
        B[j] = 2^(1-α) * s
    end

    Mτ = max(1, ceil(Integer, r-1))
    Mσ = Mτ
    Mz = M
    z, wz = rule(store.unitlegendre[Mz])
    σ, wσ = rule(store.jacobi3[Mσ])
    τ, wτ = rule(store.jacobi4[Mτ])
    dΨ = view(store.dΨ, 1:r, 1:Mτ)
    deriv_legendre_polys!(dΨ, τ)
    C = view(store.C, 1:r, 1:r)
    for j = 1:r
        Ψ = view(store.Ψ, 1:j)
        for i = 1:r
            outer = zero(T)
            for mτ = 1:Mτ
                τ_ = τ[mτ]
                inner = zero(T)
                for mz = 1:Mz
                    z_ = z[mz]
                    legendre_polys!(Ψ, 1 - z_*(1+τ_))
                    inner += wz[mz] * (1+z_)^(α-1) * Ψ[j]
                end
                outer += wτ[mτ] * dΨ[i,mτ] * inner
            end
            C[i,j] = 2^(1-α) * outer
        end
    end
    Ψ = view(store.Ψ, 1:r, 1:Mσ)
    legendre_polys!(Ψ, σ)
    for j = 1:r
        for i = 1:r
            dΨ = view(store.dΨ, 1:i)
            outer = zero(T)
            for mσ = 1:Mσ
                σ_ = σ[mσ]
                inner = zero(T)
                for mz = 1:Mz
                    z_ = z[mz]
                    deriv_legendre_polys!(dΨ, z_*(1-σ_)-1)
                    inner += wz[mz] * (z_+1)^(α-1) * dΨ[i]
                end
                outer += wσ[mσ] * Ψ[j,mσ] * inner
            end
            C[i,j] += 2^(1-α) * outer
        end
    end

    c = 1 / ( 2Γ(α) )
    for j = 1:r
        pow = one(T)
        for i = 1:r
            pow = -pow
            H1[i,j] = c * ( A[j] + pow * B[j] - C[i,j] )
        end
    end
end

# H^ℓbar for ℓbar in rng, first version (rng must not include ℓbar ≤ 1)
function coef_H_uniform_ver1!(H::OffsetVector{Matrix{T}}, rng::UnitRange,
                              M::Integer, store::Store{T}
                             ) where T <: AbstractFloat
    @argcheck rng.start ≥ 2
    r = size(H[rng.start], 1)
    A = view(store.A, 1:r)
    B = view(store.B, 1:r)
    C = view(store.C, 1:r, 1:r)

    σ, wσ = rule(store.legendre[M])
    τ, wτ = σ, wσ
    Ψ  = view(store.Ψ, 1:r, 1:M)
    dΨ = view(store.dΨ, 1:r, 1:M)
    legendre_polys!(Ψ, σ)
    deriv_legendre_polys!(dΨ, τ)
    α = store.α
    c = 1 / ( 2Γ(α) )
    for ℓbar in rng

        for j = 1:r
            s = zero(T)
            for m = 1:M
                Δ = ( 1 - σ[m] ) / (2ℓbar)
                s += wσ[m] * ( 1 + Δ )^(α-1) * Ψ[j,m]
            end
            A[j] = s
        end

        for j = 1:r
            s = zero(T)
            for m = 1:M
                 Δ = ( 1 + σ[m] ) / ( 2ℓbar )
                 s += wσ[m] * (1-Δ)^(α-1) * Ψ[j,m]
            end
            B[j] = s
        end

        τ, wτ = σ, wσ
        for j = 1:r, i = 1:r
            outer = zero(T)
            for mτ = 1:M
                inner = zero(T)
                for mσ = 1:M
                    Δ = ( τ[mτ] - σ[mσ] ) / ( 2ℓbar )
                    inner += wσ[mσ] * (1+Δ)^(α-1) * Ψ[j,mσ]
                end
                outer += wτ[mτ] * dΨ[i,mτ] * inner
            end
            C[i,j] = outer
        end

        for j = 1:r
            pow = one(T)
            for i = 1:r
                pow = -pow
                H[ℓbar][i,j] = c * ℓbar^(α-1) * ( A[j] + pow * B[j] - C[i,j] )
            end
        end
    end
end

# H^ℓbar for ℓbar in rng, second version (rng must not include ℓbar ≤ 1)
function coef_H_uniform_ver2!(H::OffsetVector{Matrix{T}}, rng::UnitRange,
                              M::Integer, store::Store{T}
                             ) where T <: AbstractFloat
    @argcheck rng.start ≥ 2
    r = size(H[rng.start], 1)
    σ, wσ = rule(store.legendre[M])
    τ, wτ = σ, wσ
    Ψ  = view(store.Ψ, 1:r, 1:M)
    legendre_polys!(Ψ, σ)
    α = store.α
    c = - (1-α) / ( 4Γ(α) )
    for ℓbar in rng
        for j = 1:r, i = 1:r
            outer = zero(T)
            for mτ = 1:M
                inner = zero(T)
                for mσ = 1:M
                    Δ = ( τ[mτ] - σ[mσ] ) / ( 2ℓbar )
                    inner += wσ[mσ] * (1+Δ)^(α-2) * Ψ[j,mσ]
                end
                outer += wτ[mτ] * Ψ[i,mτ] * inner
            end
            H[ℓbar][i,j] = c * ℓbar^(α-2) * outer
        end
    end
end

"""
    coef_H(r)

Returns diagonal matrix `H` for the classical case `α=1`.
"""
function coef_H(::Type{T}, r::Integer) where T <: AbstractFloat
    d = T[ one(T) / (2j+1) for j = 0:r-1 ]
    return LinearAlgebra.Diagonal(d)
end


using Statistics
using ProgressMeter
using MATLAB
include("atn_module.jl")
N = 10_000          # number of nodes
m = 5               # number of active links per active node
mu = 0.1            # probability I -> S
tmax = 3000         # maximum duration of the simulation
Ni = Int(0.01 * N)  # initial number of infects
gamma = -2.22       # exponent of the power law
NTrials = 5         # number of trials for each value of lambda
tolerance = 1e-10   # termination tolerance
TWindow = 500      # time window for evaluating steady state
TMinConvergence = 500

LambdaMin = 0.0
LambdaMax = 1.0
LambdaStep = 0.05
Lambda = LambdaMin:LambdaStep:LambdaMax
LambdaSize = length(Lambda)

EtaMin = 0.0
EtaMax = 15
EtaStep = 0.5
Eta = EtaMin:EtaStep:EtaMax
EtaSize = length(Eta)
IT = zeros(EtaSize, LambdaSize)
@showprogress for (i, lambda) in enumerate(Lambda)
    for (j, eta) in enumerate(Eta)
        ITrend = time_series_eta(Ni, N, m, eta, EtaMax, gamma, lambda, mu, tolerance,
                                 TWindow, TMinConvergence, tmax)
        IT[j, i] = ITrend[end]
    end
end
# mat"plot($ITrend)"


function kendall(; x0 = 5, birth_rate = 2.0, death_rate = 1.0, immgr_rate = 0.5)
  m = Network("Kendall's Process")

  m <= Species("X", x0)

  m <= Reaction("birth",       birth_rate, "X --> X + X")
  m <= Reaction("death",       death_rate, "X --> 0")
  m <= Reaction("immigration", immgr_rate, "0 --> X")

  return m
end