using ADCME
using PyCall
using LinearAlgebra
using PyPlot
using Random
Random.seed!(233)

function cholesky_logdet(A)
    op_ = load_op_and_grad("./build/libCholeskyOp","cholesky_logdet")
    A = convert_to_tensor(A, dtype=Float64)
    op_(A)
end

# TODO: specify your input parameters
A = zeros(100, 9)
L = zeros(100, 6)
for i = 1:100
    a = rand(3,3)
    a = a*a'
    A[i,:] = a[:]
end
u, J = cholesky_logdet(A)
sess = Session(); init(sess)
u_ = run(sess, u)
for i = 1:100
    l = u_[i,:]
    L = [
        l[1] 0 0
        l[4]    l[2] 0
          l[5] l[6]      l[3]
    ]
    @info L*L'-reshape(A[i,:],3,3)
end


bigfib(n) = ((BigInt[1 1; 1 0])^n)[2,1]
bigfib(20)


###############################################################################
### identification tests
@inline function identification_test(pb::AbstractProblem, y, y_old, it, mem)
    id = false
    if get(mem, :id_testkind, :default) == :strongtest
        id = id_strongtest(pb::AbstractProblem, y, y_old, it, mem)
    else
        id = id_simpletest(pb::AbstractProblem, y, y_old, it, mem)
    end

    return id
end

## Simple test: checks if new y coordinates are null.
@inline function id_simpletest(pb::AbstractProblem{T}, y, y_old, it, mem) where T<:AbstractRegularizer
    supp_y = get_support(T, y)
    supp_y_old = get_support(T, y_old)
    added_coord = setdiff(supp_y, supp_y_old)

    return length(added_coord) > 0
end

## Strong test: checks if new u coordinates are within interior of (γ∂g + I)(y) for g non diff. at y
@inline function id_strongtest(pb::AbstractProblem{T}, y, y_old, it, mem) where T<:AbstractRegularizer
    reltol = get(mem, :id_reltol, 1e-3) * get(mem, :id_reltol_decay, 1)^(it-1)
    barrier = mem[:α] * pb.λ
    !haskey(mem, :cur_support) && (mem[:cur_support] = SortedSet{Int}())

    ## Compute coordinates strictly within (γ∂g + I)(y)
    strong_support = get_support(T, mem[:u], tol=barrier * (1-reltol))
    support = get_support(T, mem[:u], tol=barrier)

    exclcoords = setdiff(support, strong_support)
    # length(exclcoords)>0 && printstyled("$it \t Excluded coords: $(collect(exclcoords))\n", color=:yellow)

    added_coord = setdiff(strong_support, mem[:cur_support])

    # save new support
    mem[:cur_support] = strong_support

    return length(added_coord) > 0
end

###############################################################################
### Extrapolation functions

## No acceleration
function extra_ISTA(pb::AbstractProblem, y, y_old, it, mem)
    return y
end

## Always acceleration
function extra_FISTA(pb::AbstractProblem, y, y_old, it, mem)
    return y + (mem[:t_old]-1)/mem[:t] * (y-y_old)
end

## Conditional acceleration
function extra_CondInertia(pb::AbstractProblem{T}, y, y_old, it, mem) where T<:AbstractRegularizer
    x_next = y

    if !identification_test(pb, y, y_old, it, mem)
        x_next += (mem[:t_old]-1)/mem[:t] * (y-y_old)
    else
        println("No acceleration this step.")
    end

    return x_next
end

## Predictive conditional acceleration
# Checks wether acceleration degrades future structure wrt to non accelerated structure.
function extra_CondPredInertia(pb::AbstractProblem{T}, y, y_old, it, mem) where T<:AbstractRegularizer
    x_next_pg = y
    x_next_accel = y + (mem[:t_old]-1)/mem[:t] * (y-y_old)

    α = mem[:α]
    T_α_gradprox(pb, x) = prox_αg(pb, x - α * ∇f(pb, x), α)

    pt_pg = T_α_gradprox(pb, x_next_pg)
    pt_accel = T_α_gradprox(pb, x_next_accel)

    support_proxgrad = get_support(T, pt_pg, tol=1e-11)
    support_accel    = get_support(T, pt_accel, tol=1e-11)

    # support_proxgrad !== support_accel && (@show collect(support_proxgrad), collect(support_accel))

    nextpoint = x_next_accel
    if length(setdiff(support_proxgrad, support_accel)) > 0
        println("No acceleration this step.")
        nextpoint = x_next_pg

        # if F(pb, pt_pg) > F(pb, pt_accel)
        #     @printf( "%16e  %16e     %16e\n", F(pb, pt_pg), F(pb, pt_accel), F(pb, pt_pg) - F(pb, pt_accel))
        # end
    end

    return nextpoint
end

## Acceleration if keeps functional decrease
function extra_MFISTA(pb::AbstractProblem, y, y_old, it, mem)
    if it == 1
        mem[:z_old] = y
        mem[:evaluate_funcval] = :z_old
    end
    z_old = copy(mem[:z_old])

    z = zeros(size(y))
    if argmin([F(pb, y), F(pb, z_old)]) == 1
        z = y
    else
        z = z_old
    end

    mem[:z_old] = copy(z)
    return z + mem[:t_old]/mem[:t] * (y - z) + (mem[:t_old]-1)/mem[:t] * (z-z_old)
end

## Acceleration every other step
# Gives functional decrease every two steps.
function extra_AltIntertia(pb::AbstractProblem, y, y_old, it, mem)
    x_next = y
    if mod(it, 2) == 1
        x_next = y + (mem[:t_old]-1)/mem[:t] * (y-y_old)
    end
    return x_next
end


#
# rules-tests.jl -
#
# Test algebraic rules and simplifications.
#
module TestingLazyAlgebraRules

using LazyAlgebra
using LazyAlgebra.Foundations
import LazyAlgebra: ⋅,
    Adjoint, Inverse, InverseAdjoint, Jacobian,
    Scaled, Sum, Composition
using Test

function to_string(A::Mapping)
    io = IOBuffer()
    show(io, A)
    String(take!(io))
end

@testset "Algebraic rules" begin
    include("common.jl")
    identical = LazyAlgebra.identical
    Composition = LazyAlgebra.Composition
    dims = (3,4,5)

    M = SymbolicMapping(:M)
    Q = SymbolicMapping(:Q)
    R = SymbolicMapping(:R)
    A = SymbolicLinearMapping(:A)
    B = SymbolicLinearMapping(:B)
    C = SymbolicLinearMapping(:C)
    D = SymbolicLinearMapping(:D)

    # Test properties.
    @test M !== R
    @test A !== B
    @test identical(A, B) == false
    let E = A
        @test identical(A, E) == true
    end
    @test is_linear(M) == false
    @test is_linear(A) == true
    @test is_linear(A + B) == true
    @test is_linear(A') == true
    @test is_linear(inv(A)) == true
    @test is_linear(inv(M)) == false
    @test is_linear(A + B + C) == true
    @test is_linear(A' + B + C) == true
    @test is_linear(A + B' + C) == true
    @test is_linear(A + B + C') == true
    @test is_linear(M + B + C) == false
    @test is_linear(A + M + C) == false
    @test is_linear(A + B + M) == false
    @test is_linear(A*B*C) == true
    @test is_linear(A'*B*C) == true
    @test is_linear(A*B'*C) == true
    @test is_linear(A*B*C') == true
    @test is_linear(M*B*C) == false
    @test is_linear(A*M*C) == false
    @test is_linear(A*B*M) == false
    @test is_endomorphism(M) == false
    @test is_endomorphism(A) == false
    @test is_selfadjoint(M) == false
    @test is_selfadjoint(A) == false
    @test is_selfadjoint(A'*A) == true # FIXME: was broken
    @test is_selfadjoint(A*A') == true # FIXME: was broken
    @test_broken is_selfadjoint(B'*A*A'*B) == true

    # Test identity.
    @test Id === LazyAlgebra.Id
    @test Id' === Id
    @test inv(Id) === Id
    @test 1Id === Id
    @test Id*M*Id === M
    @test Id*Id === Id
    @test Id*Id*Id === Id
    @test Id\Id === Id
    @test Id/Id === Id
    @test Id + Id === 2Id
    @test Id - Id === 0Id
    @test Id + 2Id === 3Id
    @test 2Id + 3Id === 5Id
    @test -4Id + (Id + 2Id) === -Id
    @test Id + Id - 2Id === 0Id
    @test 2Id - (Id + Id) === 0Id
    @test inv(3Id) === (1/3)*Id
    @test SelfAdjointType(Id) === SelfAdjoint()
    @test MorphismType(Id) === Endomorphism()
    @test DiagonalType(Id) === DiagonalMapping()
    for T in (Float32, Float64)
        atol, rtol = zero(T), sqrt(eps(T))
        x = randn(T, dims)
        y = randn(T, dims)
        @test Id*x === x # test same object
        for P in (Direct, Adjoint, Inverse, InverseAdjoint)
            @test apply(P,Id,x) === x # test same object
            test_api(P, Id, x, y)
        end
    end

    # Neutral elements.
    @test isone(Id)
    @test iszero(Id) == false
    @test iszero(A - A)
    @test iszero(-M + M)
    @test one(A) === Id
    @test one(M) === Id
    @test zero(A) === 0*A
    @test zero(M) === 0*M

    # Basic methods for sums and compositions of mappings.
    let A1 = A + B + M, A2 = C*B*M*Q
        @test eltype(A1) <: Mapping
        @test eltype(A2) <: Mapping
        @test Tuple(A1) === terms(A1)
        @test Tuple(A2) === terms(A2)
        @test length(A1) == 3
        @test length(A2) == 4
        @test firstindex(A1) == 1
        @test firstindex(A2) == 1
        @test lastindex(A1) == length(A1)
        @test lastindex(A2) == length(A2)
        @test first(A1) === A1[1]
        @test first(A2) === A2[1]
        @test last(A1) === A1[end]
        @test last(A2) === A2[end]
    end

    @test B + A === A + B
    @test B + A + Q === Q + A + B
    @test B + (M + A + Q) === A + B + M + Q
    @test (-1)*A === -A
    @test -(-A) === A
    @test 2\A === (1/2)*A
    @test A + A === 2A
    @test A + B - A === B
    @test 2R + A - Q + B - 2A + Q + 1R === 3R + B - A
    # FIXME: for the following to work, we must impose the value of the
    #        first multiplier of a sum
    #@test A + B*(M - Q) + A - 2B*(Q - M) === 2*A + 3*B*(M - Q)
    @test A + B*(M - Q) + 3A - 3B*(M - Q) === 4*A - 2*B*(M - Q)
    @test A*2M === 2*(A*M)
    @test A*2M === 2*(A*M)
    @test 3A*2M === 6A*M
    @test 3R*2M !== 6R*M

    # Test adjoint and Jacobian.
    x = nothing
    @test (A*A')' === A*A'
    for X in (A*A', A'*A, B'*A'*A*B, B'*A*A'*B)
        @test X' === X
    end
    @test A' === Adjoint(A)
    @test A' isa Adjoint
    @test A'' === (A')' === A
    @test adjoint(A) === A'
    @test jacobian(A,x) === A
    @test ∇(A,x) === A
    @test (3A)' === 3*(A')
    @test (A + 2B)' - A' === 2*B'
    @test_throws ArgumentError Jacobian(A,x)
    @test_throws ArgumentError M'
    @test_throws ArgumentError adjoint(M)
    @test_throws ArgumentError Adjoint(M)
    @test jacobian(M,x) isa Jacobian
    @test ∇(M,x) === jacobian(M,x)
    @test ∇(3M,x) === 3*∇(M,x)
    @test ∇(M,x) + ∇(2M,x) === 3∇(M,x)

    # Inverse.
    @test inv(M) === Id/M
    @test inv(inv(M)) === M
    @test Id/A === inv(A)
    @test Id\A === A
    @test M/Q === M*inv(Q)
    @test M\Q === inv(M)*Q
    @test inv(2M) === inv(M)*(2\Id)
    @test inv(2M) === inv(M)*((1/2)*Id)
    @test inv(2A) === 2\inv(A)
    @test inv(2A) === (1/2)*inv(A)
    @test inv(A*B) === inv(B)*inv(A)
    @test A/3B === 3\A/B
    @test 2/A === 2*inv(A)
    @test 4A/4B === A/B
    @test 4A\4B === A\B
    @test inv(A*M*B*Q) === inv(Q)*inv(B)*inv(M)*inv(A)
    @test inv(M)*M === Id
    @test M*inv(M) === Id
    let D = M*Q*(A - B)
        @test inv(D)*D === Id
        @test D*inv(D) === Id
    end
    let D = A + 2B - C
        @test inv(D)*D === Id
        @test D*inv(D) === Id
    end
    @test inv(M*Q*(A - B)) === inv(A - B)*inv(Q)*inv(M)
    @test inv(A*3M) === inv(M)*(3\inv(A))
    @test inv(A*3B) === 3\inv(A*B) === 3\inv(B)*inv(A)
    @test inv(A*B*3M) === inv(M)*(3\inv(B))*inv(A)
    @test inv(A*B*3C) === 3\inv(A*B*C) === 3\inv(C)*inv(B)*inv(A)

    # Inverse-adjoint.
    @test inv(A)' === inv(A')
    @test inv(A')*A' === Id
    @test A'*inv(A') === Id
    let E = inv(A'), F = inv(A)'
        @test inv(E) === A'
        @test inv(F) === A'
    end

    # Test aliases for composition.
    @test isa(M*R, Composition)
    @test M⋅R === M*R
    @test M∘R === M*R

    # Test associativity of sum and composition.
    @test (M + R) + Q === M + R + Q
    @test M + (R + Q) === (M + R) + Q
    @test (M*R)*Q === M*R*Q
    @test M*(R*Q) === (M*R)*Q

    # Test adjoint of sums and compositions.
    @test (A*B)' === (B')*(A') === B'*A'
    @test (A'*B)' === B'*A
    @test (A*B')' === B*A'
    @test (A*B*C)' === C'*B'*A'
    @test (A'*B*C)' === C'*B'*A
    @test (A*B'*C)' === C'*B*A'
    @test (A*B*C')' === C*B'*A'
    @test (A + B)' === A' + B'
    @test (A' + B)' === A + B'
    @test (A + B')' === A' + B
    @test (A' + B + C)' === A + B' + C'
    @test (A + B' + C)' === A' + B + C'
    @test (A + B + C')' === A' + B' + C

    # Test inverse of sums and compositions.

    # Test unary plus and negation.
    @test +M === M
    @test -(-M) === M
    @test -M === (-1)*M
    @test 2A + A === 3A
    @test 10M - 3M === 7M
    @test M + A + M === A + 2M

    # Test forbidden calls to constructors because they should yield an
    # instance of a different type if simplification rules were applied.
    @test_throws ArgumentError Adjoint(Id)
    @test_throws ArgumentError Adjoint(A')
    @test_throws ArgumentError Adjoint(inv(A))
    @test_throws ArgumentError Adjoint(inv(A'))
    @test_throws ArgumentError Adjoint(3A)
    @test_throws ArgumentError Adjoint(A + B)
    @test_throws ArgumentError Adjoint(A*B)

    @test_throws ArgumentError Inverse(Id)
    @test_throws ArgumentError Inverse(A')
    @test_throws ArgumentError Inverse(inv(A))
    @test_throws ArgumentError Inverse(inv(A'))
    @test_throws ArgumentError Inverse(3A)
    @test_throws ArgumentError Inverse(A*B)

    @test_throws ArgumentError InverseAdjoint(Id)
    @test_throws ArgumentError InverseAdjoint(A')
    @test_throws ArgumentError InverseAdjoint(inv(A))
    @test_throws ArgumentError InverseAdjoint(inv(A'))
    @test_throws ArgumentError InverseAdjoint(3A)
    @test_throws ArgumentError InverseAdjoint(A*B)

    @test_throws ArgumentError Jacobian(3M,x)

    @test_throws ArgumentError Scaled(2,3M)

    # Check that sums and compositions must have at least 2 terms
    @test_throws ArgumentError Sum()
    @test_throws ArgumentError Sum(A)
    @test Sum(A,B) isa Sum
    @test_throws ArgumentError Composition()
    @test_throws ArgumentError Composition(A)
    @test Composition(A,B) isa Composition

    # Test the `show` method.
    @test to_string(A) == "A"
    @test to_string(A') == "A'"
    @test to_string(A + A) == "2⋅A"
    @test to_string(A' + A') == "2⋅A'"
    @test to_string(Id/A) == "inv(A)"
    @test to_string(Id/(A + B)) == "inv($(to_string(A + B)))"
    @test to_string(M) == "M"
    @test to_string(M + M) == "2⋅M"
    @test to_string(∇(M,x)) == "∇(M,x)"
    @test to_string(∇(M,x) + ∇(M,x)) == "2⋅∇(M,x)"
end # testset
nothing

end # module


for eps in [0.1, 0.05, 0.02, 0.01]
    n = ceil(1/eps^2)
    println("For epsilon = $eps set n = $n")
end


 #initialize queens variables and domains

    columns = [1, 2, 3, 4, 5, 6, 7, 8]
   rows = Dict(col => columns for col in columns)
   println("defining columns and rows")

#-----------------------------------------------------------------

mutable struct QueensConstraint
    variables::Vector{Int}

   function QueensConstraint(columns::Vector{Int})
   println("creating constraint")
        variables = columns
        return new(variables)
    end
end

#-----------------------------------------------------------------

#checks if the constraint is satisfied returns boolean
function satisfied(cons::QueensConstraint,assignment)
println("check if satisfied")
     for (q1c,q1r) in assignment # q1c = queen 1 column, q1r = queen 1 row
        for q2c in range(q1c + 1, stop=length(cons.variables)+1) # q2c = queen 2 column
            if q2c in keys(assignment)
                q2r = assignment[q2c] # q2r = queen 2 row

                if q1r == q2r # same row?
                  return false
                end

                if abs(q1r-q2r) == abs(q1c-q2c)  # same diagonal?
                 return false
                end
            end
        end
    end

   return true # no conflict
end

#-----------------------------------------------------------------

#csp struct for
struct CSP

    #the problem's fields
    variables::Vector
    domains::Dict{Int, Vector}
    constraints::Dict{Int, Vector{}}

    #constructor
    function CSP(vars, doms, cons=Dict())
    println("creating csp")
        variables = vars
        domains = doms
        constraints = cons

        for var in vars
            constraints[var] = Vector()
            if (!haskey(domains, var))
                error("Every variable should have a domain assigned to it.")
            end
        end
        return new(variables, domains, constraints)
    end
end

#-----------------------------------------------------------------

function add_constraint(csp:: CSP, constraint::QueensConstraint)

    for var in constraint.variables
    println("adding cons")
        if (!(var in csp.variables))
            error("Variable in constraint not in CSP")
        else
            push!(csp.constraints[var], constraint)
        end
    end
end

#-----------------------------------------------------------------

function consistent(csp:: CSP, variable, assignment)::Bool
println("check consistency")
    for constraint in csp.constraints[variable]
        if (!satisfied(constraint, assignment))
            return false
        end
        return true
    end
end

#-----------------------------------------------------------------

function backtracking_search(csp:: CSP,  assignment=Dict(), path=Dict())::Union{Dict,Nothing}
println("back tracking")
     # assignment is complete if every variable is assigned (our base case)
     if length(assignment) == length(csp.variables)
        return assignment
     end

    unassigned = [v for v in csp.variables if!(v in keys(assignment))]
#=
    # get all variables without assignments
    unassigned::Vector{Int} = []
    for v in csp.variables
        if (!haskey(assignment, v))
            push!(unassigned, v)
        end
    end

=#
    # get the every possible domain value of the first unassigned variable
    first = unassigned[1]
    # println(first)
    # pretty_print(csp.domains)
    for value in csp.domains[first]
        local_assignment = deepcopy(assignment)
        local_assignment[first] = value

        # if we're still consistent, we recurse (continue)
        if consistent(csp, first, local_assignment)
            # forward checking, prune future assignments that will be inconsistent
            for un in unassigned
                ass = deepcopy(local_assignment)
                for (i, val) in enumerate(csp.domains[un])
                    ass[un] = val
                    if un != first
                        if(!consistent(csp, un, ass))
                            deleteat!(csp.domains[un], i)
                        end
                    end
                end
            end

            path[first] = csp.domains
            # println("reduced")
            #out(csp.domains)
            result = backtracking_search(csp, local_assignment)

            #backtrack if nothing is found
            if result !== nothing
                out(path)
                return result
            end
        end
    end
    return nothing
end

#-----------------------------------------------------------------

function out(d::Dict, pre=1)

    for (k,v) in d
        if typeof(v) <: Dict
            s = "$(repr(k)) => "
            println(join(fill(" ", pre)) * s)
            out(v, pre+1+length(s))
        else
            println(join(fill(" ", pre)) * "$(repr(k)) => $(repr(v))")
        end
    end
    nothing
end
#-----------------------------------------------------------------

csp = CSP(columns,rows)
add_constraint(csp, QueensConstraint(columns))
solution = backtracking_search(csp)

if solution != Nothing
    print("solution is: ", solution)
else
    println("No solution found!")
end


# Mishchenko2019.jl - reproducing Mishchenko et al. 2019 PRL, with the Feynman
# variational polaron technique

println("Loading PolaronMobility...")
using PolaronMobility
println("Loading Plots for plotting...")
using Plots
gr(size=(500,375))

using Printf
import QuadGK.quadgk # one ring to integrate them all...

#function ImX(nurange,v,w,βred,α,ω,mb)

function savefigs(name)
    println("Saving PNG")
    savefig("$(name).png")
    println("Saving PDF")
    savefig("$(name).pdf")
# high res conversion of vector PDF -> suitable, anti-aliased, PNG for slides
    println("Convert PDF to high res PNG")
    run(`convert -density 300 -resize 1600x $(name).pdf $(name).pdf.png`)
end

# Specify problem
βred=5 # temperature, in thermodynamic Beta units
mb=0.12*9.1093837015E-31
ω=1
const eV = const q = const ElectronVolt = 1.602176487e-19;      # kg m2 / s2
const Boltzmann = const kB =  1.3806504e-23;                  # kg m2 / K s2
const hbar = const ħ = 1.05457162825e-34;          # kg m2 / s

function FHIP1962_Fig2()
    # FHIP1962 reproduction
    # Fig 2 FHIP1962
    α=5
    #in [6] #1:1:10
    v,w = feynmanvw(α)
    println("α: $(α) v: $(v) w: $(w)")

    nu=3.5:0.2:22
    println("Integrating ImX for nu=$nu range...")
    s=ImX(nu, v, w, βred,α,ω,mb)
    plot( s.nu,s.ImX,label="ImX",
             markersize=3,marker=:downtriangle, xlab="nu (units Omega)",ylab="ImX")
    savefigs("FHIP1962_Fig2")
end

function FHIP1962_Fig3()
    # Fig 3 FHIP1962
    α=7
    #in [6] #1:1:10
    v,w = feynmanvw(α)
    println("α: $(α) v: $(v) w: $(w)")

    nu=3.5:0.2:28
    println("Integrating ImX for nu=$nu range...")
    s=ImX(nu, v, w, βred,α,ω,mb)
    plot( s.nu,s.ImX,label="ImX",
             markersize=3,marker=:downtriangle, xlab="nu (units Omega)",ylab="ImX")
end

# Athernmal Feynman variational technique, to compare best to the DiagMC
# results, though nb. they have some kind of T-dep in the diagrams

function Mishchenko_FigureFour()
    # Specify problem
    #mb=1
    βred=8 # temperature, in thermodynamic Beta units
    ω=1

    nurange=0.0:0.05:20
    Trange=[8,4,2,1,0.5,0.25,0.125]

    #using Distributed
    #addprocs(6)
    #@distributed

    for α in [1,2,3,4,5,6,7,8,9,10,12,15,20] #1:1:10
       # athermal action
        v,w = feynmanvw(α)
        plot(xlab="nu (units Omega) alpha=$(α)",ylab="Mob", ylims=(0,1.0E12))

        for T in Trange
            βred=1/T
            @time s=ImX(nurange, v, w, βred,α,ω,mb)
            plot!( s.nu, s.μ, label="T=$(T)")
        end

        savefigs("Mishchenko-Fig4-AthermalAction_alpha_$(α)")

        plot(xlab="nu (units Omega) alpha=$(α)",ylab="Mob", ylims=(0,3.0E12))

        for T in Trange
            βred=1/T
            # Osaka finite temperature action
            v,w=feynmanvw(α, βred, verbose=true) # temperature dependent Action

            @time s=ImX(nurange, v, w, βred,α,ω,mb)
            plot!( s.nu, s.μ, label="T=$(T) v=$(round(v,sigdigits=2)) w=$(round(w,sigdigits=2))")
        end

        savefigs("Mishchenko-Fig4-OsakaFiniteTemperatureAction_alpha_$(α)")
    end
end
#wait()

function HellwarthMobility(v,w,βred, α, ω)
# Hellwarth1999 - directly do contour integration in Feynman1962, for
        # finite temperature DC mobility
        # Hellwarth1999 Eqn (2) and (1) - These are going back to the general
        # (pre low-T limit) formulas in Feynman1962.  to evaluate these, you
        # need to do the explicit contour integration to get the polaron
        # self-energy
        R=(v^2-w^2)/(w^2*v) # inline, page 300 just after Eqn (2)

        b=R*βred/sinh(βred*v/2) # Feynman1962 version; page 1010, Eqn (47b)

        a=sqrt( (βred/2)^2 + R*βred*coth(βred*v/2))
        k(u,a,b,v) = (u^2+a^2-b*cos(v*u))^(-3/2)*cos(u) # integrand in (2)
        K=quadgk(u->k(u,a,b,v),0,Inf)[1] # numerical quadrature integration of (2)

        #Right-hand-side of Eqn 1 in Hellwarth 1999 // Eqn (4) in Baggio1997
        RHS= α/(3*sqrt(π)) * βred^(5/2) / sinh(βred/2) * (v^3/w^3) * K
        #μ=RHS^-1 * (q)/(ω*mb)
        #@printf("\n\tμ(Hellwarth1999)= %f m^2/Vs \t= %.2f cm^2/Vs",μ,μ*100^2)
        RHS^-1
end

function Mischenko_MobilityComparison(α; Trange=0.1:0.01:25)
    println("Mishchenko Feynman athermal Hellwarth explicit integration mobility with α= $(α)")
    βred=8
    ω=1 #(50*kB)/ħ # in artifical units of T
    fout=open("MishchenkoMobility_$(α).dat","w")
    @printf(fout,"# T v w mob(SI)")

    for T in Trange
        β=ω/T #  ħ*ω/(kB*T)

        # Athermal variational
        v,w = feynmanvw(α)
        μ=HellwarthMobility(v,w, β, α, ω)
        @printf(fout,"%f    %f %f %f ",T,v,w,μ)

        # Thermal theory
        v,w = feynmanvw(α, β)
        μ=HellwarthMobility(v,w, β, α, ω)
        @printf(fout,"%f %f %f ",v,w,μ)
        @printf(fout,"\n")
    end
    close(fout)
end

for α in [2.4, 4, 6, 8, 10, 12, 14] # Mishchenko2019 Fig 2 and 3
    Mischenko_MobilityComparison(α)
end

println("That's me!")


@testset "996.number-of-squareful-arrays.jl" begin
    @test num_squareful_perms([1,17,8]) == 2
    @test num_squareful_perms([2, 2, 2]) == 1
    @test num_squareful_perms([2, 2, 7]) == 3
end


# # Solving the Huggett model

using Test #jl
using Aiyagari
using Optim, QuantEcon, Parameters

PKG_HOME = joinpath(dirname(pathof(Aiyagari)), "..")

# ## Setting up the state space

a_grid = LinRange(-2, 5, 20)

endo = EndogenousStateSpace((a=a_grid,))

z_grid = [0.5; 1.0; 1.5]
z_prob = [0.7 0.15 0.15;
          0.2 0.6 0.2;
          0.15 0.15 0.7]
z_MC = MarkovChain(z_prob, z_grid, :z)

exo = ExogenousStateSpace([z_MC]);

# ## Define the household's problem in the Huggett model

u(c) = c > 0 ? log(c) : 10000 * c - 10000 * one(c)

function get_c(choices, states, aggregate_state)
  @unpack a_next = choices
  @unpack a, z = states
  @unpack r = aggregate_state

  c = a + z - a_next/(1+r)
end

function objective(choices, states, aggregate_state, 𝔼V, params)
  @unpack a_next = choices
  @unpack β = params

  c = get_c(choices, states, aggregate_state)

  u(c) + β * 𝔼V(a_next)
end

function Aiyagari.get_optimum(states, agg_state, 𝔼V, params, endo, hh::Consumer)
  a_grid = endo.grids.a

  a_min, a_max = extrema(a_grid)

  res = Optim.optimize(a_next -> - objective((a_next=a_next,), states, agg_state, 𝔼V, params), a_min, a_max)

  a_next = Optim.minimizer(res)
  val    = - Optim.minimum(res)
  conv   = Optim.converged(res)

  pol = a_next
  pol_full = (a_next=a_next, c=get_c((a_next=a_next, ), states, agg_state))

  (pol=pol, pol_full=pol_full, val=val, conv=conv)
end

mutable struct HuggettAS{T1,T2} <: AggregateState
  r::T1
  dist::T2 # the distribution over idiosynchratic states
end

function HuggettAS(r, a_grid, z_MC)
  dist_proto = zeros((length(a_grid), length(z_MC.state_values)))
  HuggettAS(r, dist_proto)
end;

# ## Initialize the aggregate state and solve the model

agg_state = HuggettAS(0.05, a_grid, z_MC)
param = (β = 0.9, )

#using BenchmarkTools
@unpack val, policy, policies_full = solve_bellman(endo, exo, agg_state, param, Consumer(), rtol=√eps())
#22 ms 176 itr

using DelimitedFiles #jl
using DelimitedFiles #src
#writedlm(joinpath(PKG_HOME, "test/matrices", "huggett_value.txt"), val) #src
value_test = readdlm(joinpath(PKG_HOME, "test/matrices", "huggett_value.txt")) #jl

@test all(val .≈ value_test) #jl

a_min, a_max = a_grid[[1;end]]
@test all(a_min .< policy .< a_max) #jl

using Plots #md
plot(a_grid, val) #md
#- #md
plot(a_grid, policies_full.a_next) #md
#- #md
plot(a_grid, policies_full.c) #md

#using BenchmarkTools #src

# let value = value, policy=policy, z_mc = z_MC #src
#   lin_ind = LinearIndices(size(value)) #src
#  #src
#   ngp_exo = length(z_mc.state_values) #src
#   n = length(policy) #src
#   len = n * ngp_exo * 2 #src
#  #src
#   I = zeros(Int, len) #src
#   J = zeros(Int, len) #src
#   V = zeros(len) #src
#  #src
#   @btime Aiyagari.controlled_markov_chain!($I, $J, $V, $lin_ind, $z_mc, #src $a_grid, $policy)
# end #src

dist = stationary_distribution(endo, exo, policy)
#926 μs

#writedlm("test/matrices/huggett_dist.txt", dist) #src
dist_test = readdlm(joinpath(PKG_HOME, "test/matrices", "huggett_dist.txt")) #jl
@test maximum(abs, dist .- dist_test) < 1e-14 #jl

# ## Looking at the equilibrium

function excess_demand(r)
  agg_state = HuggettAS(r, a_grid, z_MC)
  @unpack val, policy, policies_full = solve_bellman(endo, exo, agg_state, param, Consumer(), rtol=√eps())

  dist = stationary_distribution(endo, exo, policy)

  sum(dist .* policies_full.a_next)
end

r_vec = 0.07:0.005:0.11 #md

plot(r_vec, excess_demand.(r_vec)) #md

# ## Finite Horizon: Solving the OLG version of the model

function Aiyagari.get_optimum_last(states, agg_state, params, endo, hh::Consumer)
  a_grid = endo.grids.a

  a_next = 0.0
  val    = objective((a_next=a_next,), states, agg_state, x -> 0, params)
  conv   = true

  pol = a_next
  pol_full = (a_next=a_next, c=get_c((a_next=a_next, ), states, agg_state))

  (pol=pol, pol_full=pol_full, val=val, conv=conv)
end

age_grid = 25:65

@unpack val, policy, policies_full = solve_bellman(endo, exo, agg_state, param, Consumer(), age_grid)

using Plots, Colors #md
#md
plt = plot(legend=false) #md
 ran = 1:length(age_grid)- 9 #md
 col = range(colorant"deepskyblue", stop=colorant"navyblue", length=length(ran)) #md
 map(ran) do i #md
   plot!(plt, a_grid, val[:,:,i], color=col[i]) #md
 end #md
 plt #md


using Pkg
Pkg.activate("c:/git/JLBoost")
@time using DataFrames
@time using JDF
@time using JLBoost, LossFunctions

###############################################################################
# fitting fm
###############################################################################
if !isdir("c:/data/GiveMeSomeCredit/cs-training.jdf")
	using CSV
	a = CSV.read("c:/data/GiveMeSomeCredit/cs-training.csv", missingstrings=["NA"])
	rename!(a, Symbol("")=>:column1)
	create_missing!(a, :MonthlyIncome)
	create_missing!(a, :NumberOfDependents)
	type_compress!(a, compress_float=true)
	savejdf("c:/data/GiveMeSomeCredit/cs-training.jdf", a)
end

a = loadjdf("c:/data/GiveMeSomeCredit/cs-training.jdf")

using Calculus

softmax(w) = 1/(1 + exp(-w))
logloss(y) = w -> logloss(y, w)
logloss(y, w) = -(y*log( 1/(1 + exp(-w))) + (1-y)*log(1- 1/(1 + exp(-w))))

t = a.SeriousDlqin2yrs
ws = fill(0.0,nrow(a))

using CuArrays
ct = cu(t)
cws = cu(ws)

logloss.(ct, cws)

ct .+ cws

using BenchmarkTools
@benchmark Calculus.derivative.(logloss.($t), $ws)

using LossFunctions: deriv
loss = LogitLogLoss()
@benchmark deriv(loss, $t, $ws)

using ForwardDiff
@benchmark ForwardDiff.derivative.(logloss.($t), $ws)

using ForwardDiff

d = transpose(hcat(t, ws))
@benchmark ForwardDiff.gradient.(x->logloss(x...), eachcol(d))

using Zygote: @adjoint, gradient

@adjoint logloss(y, w) = logloss(y, w), Δ -> (Δ*-(log(1/(1+exp(-w))) - (log(1 - 1/(1+exp(-w))))), Δ*( -1/(1+exp(w)) - y + 1))

@time gradient(logloss, 1, 0.5)

f(t, ws) = gradient(logloss, t, ws)[2]

@benchmark f.(t, ws)

# Winner is LossFunctions

using CuArrays
ct = cu(t)
cws = cu(ws)

g(ct, cws) = deriv(LogitLogloss(), ct, cws)

@benchmark g.(loss, ct, cws)

#####################################################################

using Zygote: @adjoint, gradient
gg(ct, cws) = gradient(logloss, ct, cws)[2]
@time gg.(ct, cws)

logloss.(ct, cws)

|
d1(y,w) = @. -1 / (1 + exp(w)) - y + 1

using BenchmarkTools
CuArrays.allowscalar(false)
@benchmark d1(ct, cws)


immutable NaiveNeighborTree{T <: Real} <: NearestNeighborTree
	X::Matrix{T}
	metric::Metric
end

function NaiveNeighborTree{T <: Real}(X::Matrix{T})
	return NaiveNeighborTree(X, Euclidean())
end

function nearest{T <: Real}(t::NaiveNeighborTree,
                            v::Vector{T},
	                        k::Integer = 1,
	                        exclude::Integer = -1)
	n = size(t.X, 2)

	if k >= n
		error("k cannot be larger than the size of the full data set")
	end

	pq = PriorityQueue{Int, Float64}(Base.Order.Reverse)
	items = 0

	for i in 1:n
		if i == exclude
			continue
		end
		d = evaluate(t.metric, v, t.X[:, i])
		if items < k
			items += 1
			enqueue!(pq, i, d)
		else
			i_max, d_max = peek(pq)
			if d < d_max
				dequeue!(pq)
				enqueue!(pq, i, d)
			end
		end
	end

	return collect(keys(pq)), collect(values(pq))
end

function inball{T <: Real}(t::NaiveNeighborTree,
	                       v::Vector{T},
	                       r::Real,
	                       exclude::Integer = -1)
	is, ds = Int[], Float64[]

	n = size(t.X, 2)

	for i in 1:n
		if i == exclude
			continue
		end
		d = evaluate(t.metric, v, t.X[:, i])
		if d < r
			push!(is, i)
			push!(ds, d)
		end
	end

	return is, ds
end


### Following R. Nelsen 'An Introduction to Copulas', Springer Science & Business Media, 1999 - 216,
### for bivariate Archimedean copulas `C(u₁,u₂)` data can be generated as follow:
### * draw `u₁ = rand()`,
### * define `w = ∂C(u₁, u₂)\∂u₁` and inverse `u₂ = f(w, u₁)`,
### * draw  `w = rand()`
### * return a pair u₁, u₂.

### This method is applied here for Clayton, Frank and Ali-Mikhail-Haq copula. If we use
### this method recursively, we can get `n`-variate data with uniform marginals on
### `[0,1]`, where each neighbour pair
### of marginals `uᵢ uⱼ` for `j = i+1` are draw form a bivariate subcopula with
### parameter `θᵢ`, the only condition for `θᵢ`
### is such as for a corresponding bivariate copula.

"""
  rand2cop(u1::Vector{Float64}, θ::Union{Int, Float64}, copula::String)

Returns vector of data generated using copula::String given vector of uniformly
distributed u1 and copula parameter θ.
"""
function rand2cop(u1::Vector{Float64}, θ::Union{Int, Float64}, copula::String)
  w = rand(length(u1))
  copula in ["clayton", "amh", "frank"] || throw(AssertionError("$(copula) copula is not supported"))
  if copula == "clayton"
    return (u1.^(-θ).*(w.^(-θ/(1+θ)) .-1) .+1).^(-1/θ)
  elseif copula == "frank"
    return -1/θ*log.(1 .+(w*(1-exp(-θ)))./(w.*(exp.(-θ*u1) .-1) .-exp.(-θ*u1)))
  elseif copula == "amh"
    a = 1 .-u1
    b = 1 .-θ .*(1 .+2 .*a .*w)+2*θ^2*a.^2 .*w
    c = 1 .-θ .*(2 .-4*w .+4 .*a .*w)+θ.^2 .*(1 .-4 .*a .*w+4 .*a.^2 .*w)
    return 2*w .*(a .*θ .-1).^2 ./(b+sqrt.(c))
  end
end

"""
  chaincopulagen(t::Int, θ::Union{Vector{Float64}, Vector{Int}}, copula::String;
                                              rev::Bool = false, cor::String = "")

Returns: t x n Matrix{Float}, t realisations of n variate data, where n = length(θ)+1.
To generate data uses chain of bivariate Archimedean one parameter copulas.

Following copula families are supported: clayton, frank and amh -- Ali-Mikhail-Haq.

If rev == true, reverse the copula output i.e. u → 1-u (we call it reversed copula).
It cor == pearson, kendall, uses correlation coeficient as a parameter

```jldoctest
julia> Random.seed!(43);

julia> chaincopulagen(10, [4., 11.], "frank")
10×3 Array{Float64,2}:
 0.180975  0.386303   0.879254
 0.775377  0.247895   0.144803
 0.888934  0.426854   0.772457
 0.924876  0.395564   0.223155
 0.408278  0.139002   0.142997
 0.912603  0.901252   0.949828
 0.828727  0.0295759  0.0897796
 0.400537  0.0337673  0.27872
 0.429437  0.462771   0.425435
 0.955881  0.953623   0.969038
```
"""
VFI = Union{Vector{Float64}, Vector{Int}}
chaincopulagen(t::Int, θ::VFI, copula::String; rev::Bool = false, cor::String = "") =
chaincopulagen(t, θ, [fill(copula, length(θ))...] ; rev = rev, cor= cor)

function chaincopulagen(t::Int, θ::VFI, copula::Vector{String}; rev::Bool = false, cor::String = "")
  if ((cor == "Spearman") | (cor == "Kendall"))
    θ = map(i -> usebivρ(θ[i], copula[i], cor), 1:length(θ))
  else
    map(i -> testbivθ(θ[i], copula[i]), 1:length(θ))
  end
  u = rand(t,1)
  for i in 1:length(θ)
    u = hcat(u, rand2cop(u[:, i], θ[i], copula[i]))
  end
  rev ? 1 .-u : u
end

"""
  testbivθ(θ::Union{Float64}, copula::String)

Tests bivariate copula parameter

clayton bivariate sub-copulas with parameters (θᵢ ≥ -1) ^ ∧ (θᵢ ≠ 0).
amh -- Ali-Mikhail-Haq bivariate sub-copulas with parameters -1 ≥ θᵢ ≥ 1
frank bivariate sub-copulas with parameters (θᵢ ≠ 0)
"""
function testbivθ(θ::Union{Float64, Int}, copula::String)
  !(0. in θ)|(copula == "amh") || throw(DomainError("not supported for θ = 0"))
  if copula == "clayton"
    θ >= -1 || throw(DomainError("not supported for θ < -1"))
  elseif copula == "amh"
    -1 <= θ <= 1|| throw(DomainError("amh biv. copula supported only for -1 ≤ θ ≤ 1"))
  end
  Nothing
end

"""
Returns Float64, a copula parameter given a pearson or kendall correlation

For clayton or frank copula correlation fulfulling (-1 > ρᵢ > 1) ∧ (ρᵢ ≠ 0)
For amh copula pearson correlation fulfilling -0.2816 > ρᵢ >= .5. while kendall -0.18 < τ < 1/3
"""
function usebivρ(ρ::Float64, copula::String, cor::String)
  if copula == "amh"
      -0.2816 < ρ <= 0.5 || throw(DomainError("correlation coeficiant must fulfill -0.2816 < ρ <= 0.5"))
    if cor == "Kendall"
      -0.18 < ρ < 1/3 || throw(DomainError("correlation coeficiant must fulfill -0.2816 < ρ <= 1/3"))
    end
  else
    -1 < ρ < 1 || throw(DomainError("correlation coeficiant must fulfill -1 < ρ < 1"))
    !(0. in ρ) || throw(DomainError("not supported for ρ = 0"))
  end
  (cor == "Kendall") ? τ2θ(ρ, copula) : ρ2θ(ρ, copula)
end

# chain frechet copulas

"""
  chainfrechetcopulagen(t::Int, α::Vector{Float64}, β::Vector{Float64} = zeros(α))

Retenares data from a chain of bivariate two parameter frechet copuls with parameters
vectors α and β, such that ∀ᵢ 0 α[i] + β[i] ≤1 α[i] > 0, and β[i] > 0 |α| = |β|

```jldoctest
julia> Random.seed!(43)

julia> julia> chainfrechetcopulagen(10, [0.6, 0.4], [0.3, 0.5])
10×3 Array{Float64,2}:
 0.996764  0.996764  0.996764
 0.204033  0.795967  0.204033
 0.979901  0.979901  0.0200985
 0.120669  0.879331  0.120669
 0.453027  0.453027  0.453027
 0.800909  0.199091  0.800909
 0.54892   0.54892   0.54892
 0.933832  0.933832  0.0661679
 0.396943  0.396943  0.396943
 0.804096  0.851275  0.955881
```
"""
function chainfrechetcopulagen(t::Int, α::Vector{Float64}, β::Vector{Float64} = zero(α))
  length(α) == length(β) || throw(AssertionError("different lengths of parameters"))
  minimum(α) >= 0 || throw(DomainError("negative α parameter"))
  minimum(β) >= 0 || throw(DomainError("negative β parameter"))
  maximum(α+β) <= 1 || throw(DomainError("α[i] + β[i] > 0"))
  fncopulagen(α, β, rand(t, length(α)+1))
end

"""

  fncopulagen(α::Vector{Float64}, β::Vector{Float64}, u::Matrix{Float64})

```jldoctest

julia> fncopulagen(2, [0.2, 0.4], [0.1, 0.1], [0.2 0.4 0.6; 0.3 0.5 0.7])
2×3 Array{Float64,2}:
 0.6  0.4  0.2
 0.7  0.5  0.3

```
"""
function fncopulagen(α::Vector{Float64}, β::Vector{Float64}, u::Matrix{Float64})
  p = invperm(sortperm(u[:,1]))
  u = u[:,end:-1:1]
  lx = floor.(Int, size(u,1).*α)
  li = floor.(Int, size(u,1).*β) + lx
  for j in 1:size(u, 2)-1
    u[p[1:lx[j]],j+1] = u[p[1:lx[j]], j]
    r = p[lx[j]+1:li[j]]
    u[r,j+1] = 1 .-u[r,j]
  end
  u
end


using .RationalFunctionFields: groebner_ideal, generators_to_ideal, GroebnerEvaluator,
                         discover_groebner_structure, discover_groebner_degrees, saturate,
                         naive_groebner_ideal, obtain_generators, aa_ideal_to_singular,
                         RationalFunctionField, contains_randomized, check_ideal_inclusion

logger = Logging.SimpleLogger(stderr, Logging.Debug)
Logging.global_logger(logger)

@testset "Preliminary test" begin

    R, (x1, x2) = AA.PolynomialRing(Sing.QQ, ["x1", "x2"])

    set = [
           x1 // (x1*x2)
           1 // (x1*x2)
   ]

    # generators_to_ideal test
   I, yoverx, basepolyring, nvariables, ground, ystrings, Q = generators_to_ideal(set)

    y1, y2 = AA.gens(yoverx)
    x1, x2 = AA.gens(basepolyring)

    @test Q == x1*x2
    # x1*y1*y2 + x1*x2*y1, -y1*y2 + x1*x2
    @test I == [-x1*y1*y2 + x1*x2*y1, -y1*y2 + x1*x2]
    @test ground == Sing.QQ
    @test basepolyring == R
    @test AA.base_ring(yoverx) == basepolyring

    # saturate test
    It, t = saturate(I, Q)

    f = last(It)
    @test t == last(AA.gens(AA.parent(f)))
    @test f == 1 - x1*x2*t

end

# essentially a sanity check
@testset "Naive generation tests" begin

    R, (x1, x2) = AA.PolynomialRing(Sing.QQ, ["x1", "x2"])

    set = [
           (x1^2 + x2^2) // 1,
           (x1*x2) // 1
   ]

    gb = naive_groebner_ideal(set)
    coeffs = obtain_generators(gb)

    @test Set(coeffs) == Set([x1*x2, -x1*x2, -x1^2 - x2^2, R(1)])

end

@testset "Groebner structure tests" begin

    @info "Groebner structure tests"

    # ogo, chto-to novoe
    FF = Sing.QQ

    R, (x1, x2) = AA.PolynomialRing(FF, ["x1", "x2"])

    # true generating coeffs here:
    # -1, x1*x2, -1, -1, x1^2 + x2^2, x1*x2, 1, -x1^2 - x2^2
    set = [
           (x1^2 + x2^2) // 1,
           (x1*x2) // 1
    ]

    I, yoverx, basepolyring, nvariables, ground, ystrings, Q = generators_to_ideal(set)
    eval_ring, = Sing.PolynomialRing(FF, ystrings)
    G = GroebnerEvaluator(I, eval_ring, R, ground)
    npolys, ncoeffs = discover_groebner_structure(G)

    @test npolys == 3 && sum(ncoeffs) == 8

    # over GF
    FF = Sing.N_ZpField(2^31-1)

    R, (x1, x2) = AA.PolynomialRing(FF, ["x1", "x2"])
    set = [
           (x1^2 + x2^2) // 1,
           (x1*x2) // 1
    ]
    I, yoverx, basepolyring, nvariables, ground, ystrings, Q = generators_to_ideal(set)
    eval_ring, = Sing.PolynomialRing(FF, ystrings)
    G = GroebnerEvaluator(I, eval_ring, R, ground)

    npolys, ncoeffs = discover_groebner_structure(G)

    @test npolys == 3 && sum(ncoeffs) == 8

    R, (x, y, z) = AA.PolynomialRing(Sing.QQ, ["x", "y", "z"])
    gens = [ (x^2 - z^2) // (x + y), x*y // (x + y), 1//z, z^2 // 1 ]

    I, yoverx, basepolyring, nvariables, ground, ystrings, Q = generators_to_ideal(gens)
    eval_ring, = Sing.PolynomialRing(Sing.QQ, ystrings)
    G = GroebnerEvaluator(I, eval_ring, R, ground)

    npolys, ncoeffs = discover_groebner_structure(G)

    @test npolys == 4
    @test ncoeffs == [2, 2, 4, 5]

end

@testset "Groebner degree prediction tests" begin
    @info "Groebner degree prediction tests"

    # now to the degree prediction tests

    FF = Sing.QQ

    R, (x1, x2) = AA.PolynomialRing(FF, ["x1", "x2"])

    set1 = [
        (x1^2 + x2^2) // 1,
        (x1*x2) // 1
    ]

    for (i, set) in enumerate([ set1 ])
        I, yoverx, basepolyring, nvariables, ground, ystrings, Q = generators_to_ideal(set)
        It, t = saturate(I, Q)
        eval_ring, = Sing.PolynomialRing(FF, [ystrings..., "t"])
        coeff_ring = basepolyring

        G = GroebnerEvaluator(It, eval_ring, coeff_ring, ground)

        predicted_degrees = discover_groebner_degrees(G)

        gb = naive_groebner_ideal(set)
        coeffs = obtain_generators(gb)
        true_degrees = [
            maximum([ AA.degree(f, var) for f in coeffs ])
            for var in AA.gens(coeff_ring)
        ]

        @test predicted_degrees == true_degrees

    end

end

function test_groebner_inclusion(set, ismodular)
    newset = groebner_ideal(set, modular=ismodular)

    @test check_ideal_inclusion(newset, set)

end

function test_field_inclusion(set, ismodular)
    FF = RationalFunctionField(set)

    for x in set
        @test contains_randomized(FF, x)
    end
end

@testset "Groebner modular and rational are same tests" begin
    FF = Sing.QQ
    R, (x1, x2) = AA.PolynomialRing(FF, ["x1", "x2"])
    set1 = [
           (x1 + x2) // 1,
           x1*x2 // 1
    ]
    set2 = [
            (x1*x2) // (x1 + 1),
            x2 // 1
    ]

    for set in [set1, set2]
        rational_gb = groebner_ideal(set, modular=false)
        finite_gb = groebner_ideal(set, modular=true)

        @test rational_gb == finite_gb
    end

end

@testset "Groebner main tests" begin

    @info "Groebner main tests"

    FF = Sing.QQ
    R, (x1, x2) = AA.PolynomialRing(FF, ["x1", "x2"])
    set1 = [
           (x1 + x2) // 1,
           x1*x2 // 1
    ]

    set2 = [
            (x1^4 + x2^4) // 1,
            (x1^3 + x2^3) // 1,
            (x1^2 + x2^2) // 1
    ]

    for set in [set1, set2]
        for ifmodular in [true, false]
            test_groebner_inclusion(set, ifmodular)
            test_field_inclusion(set, ifmodular)
        end
    end

    R, (x1, x2, x3) = AA.PolynomialRing(FF, ["x1", "x2", "x3"])
    # syzygy computation error inside Singular ?
    set3 = [
            (x1 + x2 + x3) // 1,
            (x3 + 2) // (x1 + 1)
    ]

    set4 = [
            (x1*x2) // (1),
            1 // (x1 + x2 + x3),
            (2x2 + 3) // 1
    ]
    set5 = [
            (x1 + x2)^2 // 1,
            (x1 + x3)^2 // 1,
            (x2 + x3)^2 // 1
    ]

    # we want only modular here since computations over QQ
    # are hard here
    for (i, set) in enumerate([set3, set4, set5])
        for ifmodular in [true]
            test_groebner_inclusion(set, ifmodular)
            test_field_inclusion(set, ifmodular)
        end
    end

end


# ic.jl
# Needed to initialize a problem.
# TODO: doc these
function ICConstant(mesh::AbstractMesh, sbp::AbstractOperator, eqn::AdvectionData, opts,
                    u0::AbstractArray)

  for i=1:length(u0)
    u0[i] =  2
  end

  return nothing
end

@doc """
### AdvectionEquationMod.ICx5plusy5

Computes the initial conditions for the state variable
    u = x^5 + y^5 + z^5; z is omitted it 2d

**Inputs**

*  `mesh` : AbstractMesh type
*  `sbp`  : Summation-by-parts operator
*  `eqn`  : Advection equation object
*  `opts` : Options dictionary
*  `u0`   : Array that stores inital state variables

**Outputs**

*  None

"""->

function ICx5plusy5(mesh::AbstractMesh{Tmsh},
sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol, Tres, 2},
opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol, Tres}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      x = mesh.coords[1,j,i]
      y = mesh.coords[2,j,i]
      u0[dofnums_j] = x^5 + y^5
    end
  end

  return nothing
end # end function ICx5plusy5

function ICx5plusy5(mesh::AbstractMesh{Tmsh},
sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol, Tres, 3},
opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol, Tres}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      x = mesh.coords[1,j,i]
      y = mesh.coords[2,j,i]
      u0[dofnums_j] = x^5 + y^5 + z^5
    end
  end

  return nothing
end # end function ICx5plusy5

@doc """
### AdvectionEquationMod.ICexp_xplusy

Computes the initial conditions for the state variable
    u = exp(x + y + z), z is omitted in 2d

**Inputs**

*  `mesh` : AbstractMesh type
*  `sbp`  : Summation-by-parts operator
*  `eqn`  : Advection equation object
*  `opts` : Options dictionary
*  `u0`   : Array that stores inital state variables

**Outputs**

*  None

"""->

function ICexp_xplusy(mesh::AbstractMesh{Tmsh},
                    sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol, Tres, 2},
                    opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol, Tres}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      x = mesh.coords[1,j,i]
      y = mesh.coords[2,j,i]
      u0[dofnums_j] = exp(x + y)
    end
  end

  return nothing
end # end function exp_xplusy

function ICexp_xplusy(mesh::AbstractMesh{Tmsh},
                    sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol, Tres, 3},
                    opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol, Tres}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      x = mesh.coords[1,j,i]
      y = mesh.coords[2,j,i]
          z = mesh.coords[3,j,i]
      u0[dofnums_j] = exp(x + y + z)
    end
  end

  return nothing
end # end function exp_xplusy

function ICsinwave(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_sinwave(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICsinwavey(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_sinwavey(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICsinwavey_pert(mesh::AbstractMesh{Tmsh},
                    sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
                    opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_sinwavey_pert(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICsinwavexy(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  n = 1  # number of sin waves
  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = mesh.dofs[1, j, i]
      coords = sview(mesh.coords, :, j, i)
      u0[dofnums_j] = sin(2*pi*n*coords[1]) + sin(2*pi*n*coords[2])
      if mesh.dim == 3
        u0[dofnums_j] += sin(2*pi*n*coords[3])
      end
    end
  end

  return nothing
end

function ICsinwave_ampl(mesh::AbstractMesh{Tmsh},
                    sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
                    opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_sinwave_ampl(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function ICsinwave_ampl

function ICmms1(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_mms1(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICx4(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_x4(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICp0(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
  	for j = 1:mesh.numNodesPerElement
  	  dofnums_j = sview(mesh.dofs, :, j, i)
  	  u0[dofnums_j] = calc_p0(eqn.params, mesh.coords[:, j, i], eqn.t)
  	end
  end

  return nothing
end # end function exp_xplusy

function ICp1(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_p1(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICp2(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_p2(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICp3(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_p3(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICp4(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_p4(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICp5(mesh::AbstractMesh{Tmsh},
  sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
  opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_p5(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end # end function exp_xplusy

function ICexp5xplus4yplus2(mesh::AbstractMesh{Tmsh},
          sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
          opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_exp5xplus4yplus2(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end

function ICexp5xplusy(mesh::AbstractMesh{Tmsh},
          sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
          opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_exp5xplusy(eqn.params, mesh.coords[:, j, i],
                                      eqn.t)
    end
  end

  return nothing
end

function ICexp3xplusy(mesh::AbstractMesh{Tmsh},
          sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
          opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_exp3xplusy(eqn.params, mesh.coords[:, j, i],
                                      eqn.t)
    end
  end

  return nothing
end

function ICexp2xplus2y(mesh::AbstractMesh{Tmsh},
          sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
          opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_exp2xplus2y(eqn.params, mesh.coords[:, j, i],
                                       eqn.t)
    end
  end

  return nothing
end

function ICexp_xy(mesh::AbstractMesh{Tmsh},
          sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
          opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_exp_xy(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end

function ICxplusy(mesh::AbstractMesh{Tmsh},
          sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
          opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_xplusy(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end

function ICunsteadymms(mesh::AbstractMesh{Tmsh},
          sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
          opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_unsteadymms(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end

function ICunsteadypoly(mesh::AbstractMesh{Tmsh},
          sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
          opts, u0::AbstractArray{Tsol}) where {Tmsh, Tsbp, Tsol}

  for i = 1:mesh.numEl
    for j = 1:mesh.numNodesPerElement
      dofnums_j = sview(mesh.dofs, :, j, i)
      u0[dofnums_j] = calc_unsteadypoly(eqn.params, mesh.coords[:, j, i], eqn.t)
    end
  end

  return nothing
end

@doc """
### AdvectionEquationMod.ICFile

This function reads a vector from a file on disk and set the solution to it.
The vector must contain the same number of entries as there are degrees of
freedom in the mesh.

This function is useful for things like warm-starting the solver.
In this case, the file should be the output of `writeSolutionFiles`. The degree
of freedom number must be the same for both simulations for this to work (the
file contains no degree of freedom number information).

**Inputs**

*  `mesh`
*  `sbp`
*  `eqn`
*  `opts`

**Inputs/Outputs**

*  `u0`: vector to populate with the solution

  Aliasing restrictions: none.

"""->
function ICFile(mesh::AbstractMesh{Tmsh},
                sbp::AbstractOperator{Tsbp}, eqn::AdvectionData{Tsol},
                opts, u0::AbstractVector{Tsol}) where {Tmsh, Tsbp, Tsol}
# populate u0 with initial values from a disk file
# the file name comes from opts["ICfname"]

  readSolutionFiles(mesh, sbp, eqn, opts, opts["ICfname"], u0)
end

"""
  Dictionary that maps IC names to functions.  Every new IC should be added
  to the list
"""
global const ICDict = Dict{Any, Function}(
"ICConstant" => ICConstant,
"ICx5plusy5" => ICx5plusy5,
"ICexp_xplusy" => ICexp_xplusy,
"ICsinwave" => ICsinwave,
"ICsinwavey" => ICsinwavey,
"ICsinwavey_pert" => ICsinwavey_pert,
"ICsinwavexy" => ICsinwavexy,
"ICsinwave_ampl" => ICsinwave_ampl,
"ICmms1" => ICmms1,
"ICx4" => ICx4,
"ICp0" => ICp0,
"ICp1" => ICp1,
"ICp2" => ICp2,
"ICp3" => ICp3,
"ICp4" => ICp4,
"ICp5" => ICp5,
"ICexp5xplus4yplus2" => ICexp5xplus4yplus2,
"ICexp5xplusy" => ICexp5xplusy,
"ICexp3xplusy" => ICexp3xplusy,
"ICexp2xplus2y" => ICexp2xplus2y,
"ICexp_xy" => ICexp_xy,
"ICxplusy" => ICxplusy,
"ICunsteadymms" => ICunsteadymms,
"ICunsteadypoly" => ICunsteadypoly,
"ICFile" => ICFile,
)


module Day22

export day22

function parseinput(filename)
    deck = Dict{Int,Vector{Int}}()
    player = 0
    for line in readlines(filename)
        if line == ""
            continue
        elseif length(line) >= 6
            player = parse(Int, line[8])
        else
            push!(get!(deck, player, Int[]), parse(Int, line))
        end
    end
    deck
end

function day22(::Val{:a}, filename)
    deck = parseinput(filename)
    looser = maximum(deck[1]) > maximum(deck[2]) ? 2 : 1
    winner = maximum(deck[1]) > maximum(deck[2]) ? 1 : 2
    round = 1
    while !isempty(deck[looser])
        a = deck[1][1]
        deck[1] = deck[1][2:end]
        b = deck[2][1]
        deck[2] = deck[2][2:end]
        if a > b
            push!(deck[1],a)
            push!(deck[1],b)
        else
            push!(deck[2],b)
            push!(deck[2],a)
        end
        round += 1
    end
    foldl((acc, (i,x)) -> acc + i*x, enumerate(reverse(deck[winner])), init = 0)
end

function play(deck)
    seen_decks = Set{Tuple{Int,Vector{Int}}}()
    newdeck = Dict{Int,Vector{Int}}()
    winner = 0
    while length(deck[1]) != 0 && length(deck[2]) != 0

        if (1,deck[1]) in seen_decks || (2,deck[2]) in seen_decks
            return (1,deck)
        else
            a = deck[1][1]
            b = deck[2][1]
            if length(deck[1][2:end]) >= a && length(deck[2][2:end]) >= b
                newdeck[1] = copy(deck[1][2:1+a])
                newdeck[2] = copy(deck[2][2:1+b])
                (winner, ds) = play(newdeck)
            else
                winner = a > b ? 1 : 2
            end
            if winner == 1
                push!(deck[1],a)
                push!(deck[1],b)
            else
                push!(deck[2],b)
                push!(deck[2],a)

            end
            push!(seen_decks, (1, deck[1]))
            push!(seen_decks, (2, deck[2]))

            deck[1] = deck[1][2:end]
            deck[2] = deck[2][2:end]

        end
    end
    return (winner, deck)
end

function day22(::Val{:b}, filename)
    deck = parseinput(filename)

    (winner, deck) = play(deck)

    display(deck)

    foldl((acc, (i,x)) -> acc + i*x, enumerate(reverse(deck[winner])), init = 0)
end

end


attacks(x1, y1, x2, y2) = (x1 == x2 || y1 == y2 || x1 - y1 == x2 - y2 || x1 + y1 == x2 + y2)

function queens(N, xs...)
    lxs = length(xs)
    c = N - lxs
    c == 0 && return reverse(xs)
    for i=1:N
        any(1:lxs) do j
            attacks(xs[j], N-j+1, i, c)
        end && continue
        v = queens(N, xs..., i)
        v !== nothing && return v
    end
    return nothing
end

using WebIO

N, W = 8, 30
allqueens = queens(N)
println(allqueens)

using Blink
w = Window()
body!(w, dom"div"(
    dom"canvas.#ChessBoard[width=$(N*W), height=$(N*W)]"(),
    dom"script"(
        """
            function draw_queen(ctx, sx, sy){
                ctx.fillStyle = "#C0C0C0";
                ctx.fillText("Q", sx+$W/5, sy+4*$W/5);
                ctx.fillStyle = "#000000";
            }

            function draw_board(ctx, queens){
                n = queens.length
                ctx.fillStyle = "#FFFFFF";
                ctx.fillRect(0, 0, n*$W, n*$W);
                ctx.fillStyle = "#000000";
                sx = 0; sy = 0;

                for (i=n-1; i >= 0; i--){
                    for (j=0; j < n; j++){
                        if ((i+j)%2 == 0)
                            ctx.fillRect(sx, sy, $W, $W);
                        if (j == queens[i] - 1)
                            draw_queen(ctx, sx, sy)
                        sx += $W;
                    }
                    sy += $W; sx = 0;
                }
            }

            var c = document.getElementById("ChessBoard");
            var ctx = c.getContext("2d");
            ctx.font = "$(4*div(W,5))px sans-serif";
            draw_board(ctx, $([allqueens...]));
        """
    )
))


export draw_basin!, basins_of_attraction, automatic_Δt_basins

"""
    basins_of_attraction(grid::Tuple, ds::DynamicalSystem; kwargs...) -> basins, attractors
Compute an estimate of the basins of attraction of a dynamical system `ds` on
a partitioning of the state space given by `grid`. The method has been
inspired by the 2D grid approach developed by Nusse & Yorke [^Yorke1997].
It works _without_ knowledge of where attractors are; it identifies them automatically.

The dynamical system can be:
* An actual `DiscreteDynamicalSystem` or `ContinuousDynamicalSystem`.
* A Poincaré map of `ContinuousDynamicalSystem`: [`poincaremap`](@ref).
* A stroboscopic map, i.e. a periodically forced `ContinuousDynamicalSystem` (see examples
  for this particular application).

`grid` is a tuple of ranges defining the grid of initial conditions, for example
`grid = (xg, yg)` where `xg = yg = range(-5, 5; length = 100)`. The grid is not necessarilly
of the same dimension as the state space, attractors can be found in lower dimensional
projections.

The output `basins` is an integer-valued array on the `grid`, with its entries labelling
which basin of attraction the given grid point belongs to.
The output `attractors` is a dictionary whose keys correspond to the attractor number and
the values contains the points of the attractors found.
Notice that for some attractors this list may be incomplete.

See also [`match_attractors!`](@ref), [`basin_fractions`](@ref), [`tipping_probabilities`](@ref).

[^Yorke1997]: H. E. Nusse and J. A. Yorke, Dynamics: numerical explorations Ch. 7, Springer, New York, 1997

## Keyword Arguments
* `Δt`: Approximate time step of the integrator, which is `1` for discrete systems.
  For continuous systems, an automatic value is calculated using [`automatic_Δt_basins`](@ref).
  See that function for more info.
* `T` : Period of the stroboscopic map, in case of a continuous dynamical system with periodic
  time forcing. This argument is incompatible with `Δt`.
* `idxs = 1:length(grid)`: This vector selects the variables of the system that will define the
  subspace the dynamics will be projected into.
* `complete_state = zeros(D-Dg)`: This argument allows setting the _remaining_ variables
  of the dynamical system state on each initial condition `u`, with `Dg` the dimension
  of the grid. It can be either a vector of length `D-Dg`, or a function `f(y)` that
  returns a vector of length `D-Dg` given the _projected_ initial condition on the grid `y`.
* `diffeq = NamedTuple()`: Keyword arguments propagated to [`integrator`](@ref). Only
  useful for continuous systems. It is **strongly recommended** to choose high accuracy
  solvers for this application, e.g. `diffeq = (alg = Vern9(), reltol = 1e-9, abstol = 1e-9)`.
* `mx_chk_att = 2`: A parameter that sets the maximum checks of consecutives hits of an attractor
  before deciding the basin of the initial condition.
* `mx_chk_hit_bas = 10` : Maximum check of consecutive visits of the same basin of attraction.
  This number can be increased for higher accuracy.
* `mx_chk_fnd_att = 100` : Maximum check of unnumbered cell before considering we have an attractor.
  This number can be increased for higher accuracy.
* `mx_chk_loc_att = 100` : Maximum check of consecutive cells marked as an attractor before considering
  that we have all the available pieces of the attractor.
* `mx_chk_lost` : Maximum check of iterations outside the defined grid before we consider the orbit
  lost outside. This number can be increased for higher accuracy. It defaults to `20` if no
  attractors are given (see discussion on refining basins), and to `1000` if attractors are given.
* `horizon_limit = 1e6` : If the norm of the integrator state reaches this limit we consider that the
  orbit diverges.
* `show_progress = true` : By default a progress bar is shown using ProgressMeter.jl.
* `attractors, ε`: See discussion on refining basins below.

## Description
`basins` has the following organization:
* The basins are coded in sequential order from 1 up to the number of attractors.
* If the trajectory diverges or converges to an attractor outside the defined grid it is
  numbered `-1`

`attractors` has the following organization:
* The keys of the dictionary correspond to the number of the attractor.
* The value associated to a key is a [`Dataset`](@ref) with the *guessed* location of the
  attractor on the state space.

The method starts by picking the first available initial condition on the grid not yet
numbered. The dynamical system is then iterated until one of the following conditions happens:
1. The trajectory hits a known attractor already numbered `mx_chk_att` consecutive times: the
   initial condition is numbered with the corresponding number.
1. The trajectory spends `mx_chk_lost` steps outside the defiend grid: the initial
   condition is set to -1.
1. The trajectory hits a known basin `mx_chk_hit_bas` times in a row: the initial condition
   belongs to that basin and is numbered accordingly.
1. The trajectory hits `mx_chk_fnd_att` times in a row an unnumbered cell: it is considered
   an attractor and is labelled with a new number.

Regarding performace, this method is at worst as fast as tracking the attractors.
In most cases there is a signicative improvement in speed.

Notice that in the case we have to project the dynamics on a lower dimensional space,
there are edge cases where the system may have two attractors
that are close on the projected space but are far apart in another dimension. They could
be collapsed or confused into the same attractor. This is a drawback of this method.

## Refining basins of attraction
Sometimes one would like to be able to refine the found basins of attraction by recomputing
`basins_of_attraction` on a smaller, and more fine-grained, `grid`. If however this
new `grid` does not contain the attractors, `basins_of_attraction` would (by default)
attribute the value `-1` to all grid points. For these cases, an extra search clause can
be provided by setting the keywords `attractors, ε`. The `attractors` is a dictionary
mapping attractor IDs to `Dataset`s (i.e., the same as the return value of
`basins_of_attraction`). The algorithm checks at each step whether the system state is
`ε`-close (Euclidean norm) to any of the given attractors, and if so it attributes the stating grid point
to the basin of the close attractor. By default `ε` is equal to the mean grid spacing.

A word of advice while using this method: in order to work properly, `ε` should be
about the size of a grid cell that has been used to compute the given `attractors`. It is
recomended to keep the same step size (i.e., use the same integrator) since it may have an
influence in some cases. This algorithm is usually slower than the method with the
attractors on the grid.
"""
function basins_of_attraction(grid::Tuple, ds;
        Δt=nothing, T=nothing, idxs = 1:length(grid),
        complete_state = zeros(eltype(get_state(ds)), length(get_state(ds)) - length(grid)),
        diffeq = NamedTuple(), kwargs...
        # `kwargs` tunes the basin finding algorithm, e.g. `mx_chk_att`.
        # these keywords are actually expanded in `_identify_basin_of_cell!`
    )

    @assert length(idxs) == length(grid)
    integ = ds isa PoincareMap ? ds : integrator(ds; diffeq)
    idxs = SVector(idxs...)

    D = length(get_state(integ))
    if complete_state isa AbstractVector && (length(complete_state) ≠ D-length(idxs))
        error("Vector `complete_state` must have length D-Dg!")
    end
    # Check logic for automatic Δt computation:
    fixed_solver = haskey(diffeq, :dt) && haskey(diffeq, :adaptive)
    if ds isa ContinuousDynamicalSystem && isnothing(Δt) && isnothing(T) && !fixed_solver
        Δt = automatic_Δt_basins(ds, grid; idxs, complete_state, diffeq)
        if get(kwargs, :show_progress, true)
            @info "Automatic Δt estimation yielded Δt = $(Δt)"
        end
    end
    return basins_of_attraction(grid, integ, Δt, T, idxs, complete_state, fixed_solver; kwargs...)
end

function basins_of_attraction(
        grid, integ, Δt, T, idxs::SVector, complete_state, fixed_solver; kwargs...
    )

    complete_and_reinit! = CompleteAndReinit(complete_state, idxs, length(get_state(integ)))
    get_projected_state = (integ) -> view(get_state(integ), idxs)
    MDI = DynamicalSystemsBase.MinimalDiscreteIntegrator
    if !isnothing(T)
        iter_f! = (integ) -> step!(integ, T, true)
    elseif (integ isa PoincareMap) || (integ isa MDI) || fixed_solver
        iter_f! = step!
    else # generic case
        iter_f! = (integ) -> step!(integ, Δt) # we don't have to step _exactly_ `Δt` here
    end
    bsn_nfo = draw_basin!(
        grid, integ, iter_f!, complete_and_reinit!, get_projected_state; kwargs...
    )
    return bsn_nfo.basin, bsn_nfo.attractors
end

# Estimate Δt
using LinearAlgebra

"""
    automatic_Δt_basins(ds, grid; kwargs...) → Δt
Calculate an optimal `Δt` value for [`basins_of_attraction`](@ref).
This is done by evaluating the dynamic rule `f` (vector field) at `N` randomly chosen
points of the grid. The average `f` is then compared with the diagonal length of a grid
cell and their ratio provides `Δt`.

Keywords `idxs, complete_state, diffeq` are exactly as in [`basins_of_attraction`](@ref),
and the keyword `N` is `5000` by default.

Notice that `Δt` should not be too small which happens typically if the grid resolution
is high. It is okay for [`basins_of_attraction`](@ref) if the trajectory skips a few cells.
But if `Δt` is too small the default values for all other keywords such
as `mx_chk_hit_bas` need to be increased drastically.

Also, `Δt` that is smaller than the internal step size of the integrator will cause
a performance drop.
"""
function automatic_Δt_basins(ds, grid;
        idxs = 1:length(grid), N = 5000, diffeq = NamedTuple(),
        complete_state = zeros(eltype(get_state(ds)), length(get_state(ds)) - length(grid))
    )

    if ds isa Union{PoincareMap, DiscreteDynamicalSystem}
        return 1
    end
    integ = integrator(ds; diffeq...)
    complete_and_reinit! = CompleteAndReinit(complete_state, idxs, length(get_state(integ)))

    steps = step.(grid)
    s = sqrt(sum(x^2 for x in steps)) # diagonal length of a cell
    indices = CartesianIndices(length.(grid))
    random_points = [generate_ic_on_grid(grid, ind) for ind in rand(indices, N)]
    dudt = 0.0
    udummy = copy(integ.u)
    for p in random_points
        complete_and_reinit!(integ, p)
        deriv = if integ.u isa SVector
            integ.f(integ.u, integ.p, 0.0)
        else
            integ.f(udummy, integ.u, integ.p, 0.0)
            udummy
        end
        dudt += norm(deriv)
    end
    return Δt = 10*s*N/dudt
end


using BlackBoxOptim

# Another problem from Hans W. Borchers, constrained 15-points:
function points15(x)
    d = 2.0
    for i = 1:14, j = (i+1):15
        s = (x[i]-x[j])^2 + (x[i+15]-x[j+15])^2 + (x[i+30]-x[j+30])^2
        s = sqrt(s)
        if s < d
          d = s
        end
    end
    return -d
end

Dims = 15 * 3

# The best known minimum is around -0.62 but we do not clearly know if there are better
# ones so we aim somewhat lower...
prob = BlackBoxOptim.minimization_problem(points15, "Constrained points 15",
        (0.0, 1.0), 45, -0.64)

# This is a hard problem so run for some time...
MaxMinutes = 0.50

# To run XNES et al on it we need to add a penalty for going outside the (0,1) box.
function penalty(x, range)
  lower = x .< range[1]
  higher = x .> range[2]
  100 * (norm(range[1] - x[lower]) + norm(x[higher] - range[2]))
end

penalized_points15(x) = points15(x) + penalty(x, (0.0, 1.0))

# Run XNES
result = bboptimize(penalized_points15; SearchRange = (0.0, 1.0), NumDimensions = Dims,
  MaxTime = MaxMinutes * 60, Method = :xnes)
best_xnes = best_candidate(result)

# Run DXNES
result = bboptimize(penalized_points15; SearchRange = (0.0, 1.0), NumDimensions = Dims,
  MaxTime = MaxMinutes * 60, Method = :dxnes)
best_dxnes = best_candidate(result)

# Run the default (Adaptive DE) method for same amount of time on penalized function
result = bboptimize(penalized_points15; SearchRange = (0.0, 1.0), NumDimensions = Dims,
  MaxTime = MaxMinutes * 60)
best_de_pen = best_candidate(result)

# But DE need no penalization since it respects search range bounds:
result = bboptimize(points15; SearchRange = (0.0, 1.0), NumDimensions = Dims,
  MaxTime = MaxMinutes * 60)
best_de = best_candidate(result)

# Run several methods several times:
BlackBoxOptim.repeated_bboptimize(2, prob, Dims, [
  :generating_set_search,
  :adaptive_de_rand_1_bin_radiuslimited,
  :random_search,
  ],
  MaxMinutes * 60)

println("xNES points15 fitness = ", points15(best_xnes))
println("dxNES points15 fitness = ", points15(best_dxnes))
println("Adaptive DE (default, penalized) points15 fitness = ", points15(best_de_pen))
println("Adaptive DE (default) points15 fitness = ", points15(best_de))


using Test
using NumberIntervals

const a = NumberInterval(-1, 0)
const b = NumberInterval(-0.5, 0.5)
const c = NumberInterval(0.5, 2)
const d = NumberInterval(0.25, 0.8)
const z = zero(NumberInterval)
const e = NumberInterval(Inf, -Inf)

@testset "number comparison" begin
    @test a < c
    @test c > a
    @test (a < b) |> ismissing
    @test (c > b) |> ismissing
    @test !(c < a)
    @test !(a > c)
    @test z == z
    @test z != c
    @test (a == b) |> ismissing
    @test (b != c) |> ismissing
    @test b <= c
end
@testset "testing for zero" begin
    @test !iszero(c)
    @test iszero(z)
    @test iszero(a) |> ismissing
    @test iszero(b) |> ismissing
end
@testset "test sign" begin
    @test signbit(c) == false
    @test signbit(-a) == false
    @test signbit(-c) == true
    @test signbit(a) |> ismissing
    @test sign(c) == 1
    @test sign(z) == 0
    @test sign(-c) == -1
    @test sign(b) |> ismissing
end
@testset "isinteger" begin
    @test isinteger(z)
    @test isinteger(NumberInterval(4))
    @test !isinteger(NumberInterval(4.5))
    @test isinteger(c) |> ismissing
    @test !isinteger(d)
end
@testset "isfinite" begin
    @test isfinite(a)
    @test isfinite(b)
    @test isfinite(c)
    @test isfinite(z)
    @test isfinite(NumberInterval(0., Inf))
    @test isfinite(e) |> ismissing
end
@testset "IndeterminateException" begin
    @test_throws IndeterminateException throw(IndeterminateException())
end
@testset "constructor" begin
    @test NumberInterval(a) === a
    @test NumberInterval{Float64}(a) === a
    @test NumberInterval{Float32}(4) isa NumberInterval{Float32}
    @test real(a) === a
    @test_throws ErrorException NumberInterval(2., 1.)
    @test isnan(NumberInterval(NaN))
    @test_throws ErrorException NumberInterval(Inf)
    @test_throws ErrorException NumberInterval(-Inf)
end
@testset "promotion" begin
    @test promote_rule(NumberInterval{Float32}, Float64) ==
        NumberInterval{Float64}
end


using Voronoi
using Base.Test

points = Point[]
segments = Segment[Segment(Point(0, 0), Point(0, 1)),
                   Segment(Point(0, 1), Point(1, 1)),
                   Segment(Point(1, 1), Point(1, 0)),
                   Segment(Point(1, 0), Point(0, 0))]

vertices, edges =  get_edges(points, segments)

@test length(vertices) == 5
@test length(filter(e -> e.is_primary == true, edges)) == 4

segments = Segment[Segment(Point(0, 0), Point(0, 2)),
                   Segment(Point(0, 2), Point(1, 2)),
                   Segment(Point(1, 2), Point(1, 0)),
                   Segment(Point(1, 0), Point(0, 0))]

vertices, edges =  get_edges(points, segments)

@test length(vertices) == 6
@test length(filter(e -> e.is_primary == true, edges)) == 5


using CompScienceMeshes
using Test

fn = joinpath(dirname(@__FILE__),"FPLZX.msh")
m = CompScienceMeshes.load_gid_mesh(fn)

@test numcells(m) == 896
@test numvertices(m) == 450
@test vertices(m,1) ≈ [0,0,7]


# MIT license
# Copyright (c) Microsoft Corporation. All rights reserved.
# See LICENSE in the project root for full license information.

@testset "Repeat" begin
    function hex3RGB()
        clusterelements = SVector((0,0),(-1,0),(-1,1))
        colors = [colorant"red",colorant"green",colorant"blue"]
        names = ["R","G","B"]
        eltlattice = Repeat.HexBasis1()
        clusterbasis = Repeat.LatticeBasis(( -1,2),(2,-1))
        lattice = Repeat.LatticeCluster(clusterbasis,eltlattice,clusterelements)
        properties =  DataFrame(Color = colors, Name = names)
        return Repeat.ClusterWithProperties(lattice,properties)
    end

    #spherepoint tests
    @test isapprox(Repeat.Lenslets.spherepoint(1,π/2,0.0), [0.0,1.0,0.0])
    @test isapprox(Repeat.Lenslets.spherepoint(1,0.0,π/2), [1.0,0.0,0.0])
    @test isapprox(Repeat.Lenslets.spherepoint(1,0,0.0), [0.0,0.0,1.0])
    @test isapprox(Repeat.Lenslets.spherepoint(1,0.0,π/4), [sqrt(2)/2,0.0,sqrt(2)/2])

    """ Create a LatticeCluser with three elements at (0,0),(-1,0),(-1,1) coordinates in the HexBasis1 lattice"""
    function hex3cluster()
        clusterelts = SVector((0,0),(-1,0),(-1,1))
        eltlattice = Repeat.HexBasis1()
        clusterbasis = Repeat.LatticeBasis(( -1,2),(2,-1))
        return Repeat.LatticeCluster(clusterbasis,eltlattice,clusterelts)
    end

    @test [-1 2;2 -1] == Repeat.basismatrix(Repeat.clusterbasis(hex3RGB()))

    function basistest(a::Repeat.AbstractLatticeCluster)
        return Repeat.clusterbasis(a)
    end

    @test basistest(hex3cluster()) == basistest(hex3RGB())

    #LatticeCluster testset
    cluster = Repeat.Lenslets.hex9()

    for iter in 1:100
        (i,j) = rand.((1:1000,1:1000))
        coords,tileindex = Repeat.cluster_coordinates_from_tile_coordinates(cluster,i,j)
        reconstructed = Repeat.tilecoordinates(cluster,coords...,tileindex)
        @test all((i,j) .== reconstructed)
    end

    #verify that the 0,0 cluster is correct
    for (index,element) in pairs(Repeat.clusterelements(cluster))
        coords, tileindex = Repeat.cluster_coordinates_from_tile_coordinates(cluster, element...)
        @test all(coords .== 0)
        @test tileindex == index
    end
end


using ReachabilityAnalysis, Plots

@taylorize function duffing!(du, u, p, t)
    local α = -1.0
    local β = 1.0
    local δ = 0.3
    local γ = 0.37

    x, v = u
    f = γ * cos(ω * t)

    du[1] = u[2]
    du[2] = - α*x - δ*v - β*x^3 + f
end

ω = 1.2
T = 2*pi / ω
X0 = Singleton([1.0, 0.0]) ⊕ BallInf(zeros(2), 0.1)
prob = @ivp(x' = duffing!(x), x(0) ∈ X0, dim=2);

sol = solve(prob, tspan=(0.0, 20*T), alg=TMJets21a());

plot(sol, vars=(0, 1), xlab="t", ylab="x", lw=0.2, color=:blue)

plot(sol, vars=(0, 2), xlab="t", ylab="v", lw=0.2, color=:blue)

plot(sol, vars=(1, 2), xlab="x", ylab="v", lw=0.5, color=:red)


"""
    evalpoly1x2r

Evaluate one polynomial at `x` given packed coefficients `poly` using
Horner's method with two SIMD lanes.
"""
@generated function evalpoly1x2r(x::T, poly::NTuple{N, Vec{2, T}}) where {N, T<:Real}
    insns = [:(accum = muladd(x2vec, accum, poly[$i])) for i in N-1:-1:1]
    Expr(:block,
        quote
            x2 = x*x
            x2vec = Vec{2, T}((x2, x2))
            accum = poly[N]
        end,
        insns...,
        quote
            accum[1] + accum[2]*x
        end
    )
end

"""
    packpoly1x2r

Pack a tuple of polynomial coefficients into 2-wide SIMD vectors
"""
function packpoly1x2r(p::NTuple{N, T}) where {N, T<:Real}
    parr = collect(p)
    # pad with zeros so length is multiple of 2
    addl = 1 - (N + 1) % 2
    for i = 1:addl
        push!(parr, zero(T))
    end

    m = (N + 1) ÷ 2
    packedarr = Array{Vec{2, T}, 1}(undef, m)
    for i = 1:m
        ofs = (i-1)*2
        packedarr[i] = Vec{2, T}((parr[1+ofs], parr[2+ofs]))
    end
    return tuple(packedarr...)
end

"""
    evalpoly1x4r

Evaluate one polynomial at `x` given packed coefficients `poly` using
Horner's method with four SIMD lanes.
"""
@generated function evalpoly1x4r(x::T, poly::NTuple{N, Vec{4, T}}) where {N, T<:Real}
    insns = [:(accum = muladd(x4vec, accum, poly[$i])) for i in N-1:-1:1]
    Expr(:block,
        quote
            x2 = x*x
            x4 = x2*x2
            x4vec = Vec{4, T}((x4, x4, x4, x4))
            accum = poly[N]
        end,
        insns...,
        quote
            accum[1] + accum[2]*x + accum[3]*x2 + accum[4]*x2*x
        end
    )
end

"""
    packpoly1x4r

Pack a tuple of polynomial coefficients into 4-wide SIMD vectors
"""
function packpoly1x4r(p::NTuple{N, T}) where {N, T<:Real}
    parr = collect(p)
    # pad with zeros so length is multiple of 4
    addl = 3 - (N + 3) % 4
    for i = 1:addl
        push!(parr, zero(T))
    end

    m = (N + 3) ÷ 4
    packedarr = Array{Vec{4, T}, 1}(undef, m)
    for i = 1:m
        ofs = (i-1)*4
        packedarr[i] = Vec{4, T}((parr[1+ofs], parr[2+ofs], parr[3+ofs], parr[4+ofs]))
    end
    return tuple(packedarr...)
end


#=
--------------------------------------------------------------------------------------------------------------------------------
Subsampling: Jackknife
--------------------------------------------------------------------------------------------------------------------------------
=#

"""
    block_jackknife(Y::Union{FloatMatrix, JMatrix{Float64}}, subsample::Float64)

Generate block jackknife (Kunsch, 1989) samples. This implementation is described in Pellegrino (2020).

This technique subsamples a time series dataset by removing, in turn, all the blocks of consecutive observations with a given size.

# Arguments
- `Y`: Observed measurements (`nxT`), where `n` and `T` are the number of series and observations.
- `subsample`: Block size as a percentage of number of observed periods. It is bounded between 0 and 1.

# References
Kunsch (1989) and Pellegrino (2020).
"""
function block_jackknife(Y::Union{FloatMatrix, JMatrix{Float64}}, subsample::Float64)

    # Check inputs
    check_bounds(subsample, 0, 1);

    # Dimensions
    n, T = size(Y);

    # Block size
    block_size = Int64(ceil(subsample*T));
    if block_size == 0
        error("subsample is too small!");
    end

    # Number of block jackknifes samples - as in Kunsch (1989)
    samples = T-block_size+1;

    # Initialise jackknife_data
    jackknife_data = JArray{Float64,3}(undef, n, T, samples);

    # Loop over j=1, ..., samples
    for j=1:samples

        # Index of missings
        ind_j = collect(j:j+block_size-1);

        # Block jackknife data
        jackknife_data[:, :, j] = Y;
        jackknife_data[:, ind_j, j] .= missing;
    end

    # Return jackknife_data
    return jackknife_data;
end

"""
    optimal_d(n::Int64, T::Int64)

Select the optimal value for d. See artificial_jackknife (...) for more details on d.

# Arguments
- `n`: Number of series
- `T`: Number of observations
"""
function optimal_d(n::Int64, T::Int64)
    objfun_array = zeros(n*T);

    for d=1:fld(n*T,2)
        objfun_array[d] = objfun_optimal_d(n, T, d);
    end

    return argmax(objfun_array);
end

"""
    objfun_optimal_d(n::Int64, T::Int64, d::Int64)

Objective function to select the optimal value for d.

# Arguments
- `n`: Number of series
- `T`: Number of observations
- `d`: Candidate d
"""
function objfun_optimal_d(n::Int64, T::Int64, d::Int64)
    fun = no_combinations(n*T, d) - (d>=n).*no_combinations(n*T-n, d-n)*T;

    for i=2:fld(d, n)
        fun -= (-1^(i-1))*no_combinations(T, i)*no_combinations(n*T-i*n, d-i*n);
    end

    return fun;
end

"""
    artificial_jackknife(Y::Union{FloatMatrix, JMatrix{Float64}}, subsample::Float64, max_samples::Int64, seed::Int64=1)

Generate artificial jackknife samples as in Pellegrino (2020).

The artificial delete-d jackknife is an extension of the delete-d jackknife for dependent data problems.
- This technique replaces the actual data removal step with a fictitious deletion, which consists of imposing `d`-dimensional (artificial) patterns of missing observations to the data.
- This approach does not alter the data order nor destroy the correlation structure.

# Arguments
- `Y`: Observed measurements (`nxT`), where `n` and `T` are the number of series and observations.
- `subsample`: `d` as a percentage of the original sample size. It is bounded between 0 and 1.
- `max_samples`: If `C(n*T,d)` is large, artificial_jackknife would generate `max_samples` jackknife samples.
- `seed`: Random seed (default: 1).

# References
Pellegrino (2020).
"""
function artificial_jackknife(Y::Union{FloatMatrix, JMatrix{Float64}}, subsample::Float64, max_samples::Int64, seed::Int64=1)

    # Dimensions
    n, T = size(Y);
    nT = n*T;

    # Check inputs
    check_bounds(subsample, 0, 1);

    # Set d using subsample
    d = Int64(ceil(subsample*nT));

    # Error management
    if d == 0
        error("subsample is too small!");
    end

    # Warning
    if subsample > 0.5
        @warn "this algorithm might be unstable for `subsample` larger than 0.5!";
    end

    # Get vec(Y)
    vec_Y = Y[:] |> JVector{Float64};

    # Initialise loop (controls)
    C_nT_d = no_combinations(nT, d);
    samples = convert(Int64, min(C_nT_d, max_samples));
    zeros_vec = zeros(d);

    # Initialise loop (output)
    ind_missings = Array{Int64}(zeros(d, samples));
    jackknife_data = JArray{Float64,3}(undef, n, T, samples);

    # Set `rng`
    rng = StableRNG(seed);

    # Loop over j=1, ..., samples
    for j=1:samples

        # First draw
        if j == 1
            if samples == C_nT_d
                ind_missings[:,j] = rand_without_replacement(rng, nT, d);
            else
                ind_missings[:,j] = rand_without_replacement(rng, n, T, d);
            end

        # Iterates until ind_missings[:,j] is neither a vector of zeros, nor already included in ind_missings
        else
            while ind_missings[:,j] == zeros_vec || is_vector_in_matrix(ind_missings[:,j], ind_missings[:, 1:j-1])
                if samples == C_nT_d
                    ind_missings[:,j] = rand_without_replacement(rng, nT, d);
                else
                    ind_missings[:,j] = rand_without_replacement(rng, n, T, d);
                end
            end
        end

        # Add (artificial) missing observations
        vec_Y_j = copy(vec_Y);
        vec_Y_j[ind_missings[:,j]] .= missing;

        # Store data
        jackknife_data[:, :, j] = reshape(vec_Y_j, n, T);
    end

    # Return jackknife_data
    return jackknife_data;
end

#=
--------------------------------------------------------------------------------------------------------------------------------
Subsampling: Bootstrap
--------------------------------------------------------------------------------------------------------------------------------
=#

"""
    moving_block_bootstrap(Y::Union{FloatMatrix, JMatrix{Float64}}, subsample::Float64, samples::Int64, seed::Int64=1)

Generate moving block bootstrap samples.

The moving block bootstrap randomly subsamples a time series into ordered and overlapped blocks of consecutive observations.

# Arguments
- `Y`: Observed measurements (`nxT`), where `n` and `T` are the number of series and observations.
- `subsample`: Block size as a percentage of number of observed periods. It is bounded between 0 and 1.
- `samples`: Number of bootstrap samples.
- `seed`: Random seed (default: 1).

# References
Kunsch (1989) and Liu and Singh (1992).
"""
function moving_block_bootstrap(Y::Union{FloatMatrix, JMatrix{Float64}}, subsample::Float64, samples::Int64, seed::Int64=1)

    # Check inputs
    check_bounds(subsample, 0, 1);

    # Dimensions
    n, T = size(Y);

    # Block size
    block_size = Int64(ceil(subsample*T));
    if block_size == 0
        error("subsample is too small!");
    end

    # Initialise bootstrap_data
    bootstrap_data = JArray{Float64,3}(undef, n, block_size, samples);

    # Set `rng`
    rng = StableRNG(seed);

    # Loop over j=1, ..., samples
    for j=1:samples

        # Starting point for the moving block
        ind_j = rand(rng, 1:T-block_size+1);

        # Bootstrap data
        bootstrap_data[:, :, j] .= Y[:, ind_j:ind_j+block_size-1];
    end

    # Return bootstrap_data
    return bootstrap_data;
end

"""
    stationary_block_bootstrap(Y::Union{FloatMatrix, JMatrix{Float64}}, subsample::Float64, samples::Int64, seed::Int64=1)

Generate stationary block bootstrap samples.

The stationary bootstrap is similar to the block bootstrap proposed in independently in Kunsch (1989) and Liu and Singh (1992).

There are two main differences:
- The blocks have random length
- In order to achieve stationarity, the stationary (block) bootstrap "wraps" the data around in a "circle" so that the first observation follows the last.

Note: Block size is exponentially distributed with mean `Int64(ceil(subsample*T))`.

# Arguments
- `Y`: Observed measurements (`nxT`), where `n` and `T` are the number of series and observations.
- `subsample`: Block size as a percentage of number of observed periods. It is bounded between 0 and 1.
- `samples`: Number of bootstrap samples.
- `seed`: Random seed (default: 1).

# References
Politis and Romano (1994).
"""
function stationary_block_bootstrap(Y::Union{FloatMatrix, JMatrix{Float64}}, subsample::Float64, samples::Int64, seed::Int64=1)

    # Check inputs
    check_bounds(subsample, 0, 1);

    # Dimensions
    n, T = size(Y);

    # Block length is exponentially distributed with mean
    avg_block_size = Int64(ceil(subsample*T));
    if avg_block_size == 0
        error("subsample is too small!");
    end

    # Initialise bootstrap_data
    bootstrap_data = JArray{Float64,3}(undef, n, T, samples);

    # Set `rng`
    rng = StableRNG(seed);

    # Loop over j=1, ..., samples
    for j=1:samples

        # Merge multiple blocks of random size
        ind_j    = zeros(T) |> Array{Int64,1};
        ind_j[1] = rand(rng, 1:T);

        # Loop over t=2,...,T
        for t=2:T

            # Let ind_j[t] be picked at random
            if rand(rng) < 1/avg_block_size;
                ind_j[t] = rand(rng, 1:T);

            # Let ind_j[t] be ind_j[t-1] + 1
            else
                if ind_j[t-1] == T
                    ind_j[t] = 1;
                else
                    ind_j[t] = ind_j[t-1] + 1;
                end
            end
        end

        # Generate j-th bootstrap sample
        bootstrap_data[:, :, j] .= Y[:, ind_j];
    end

    # Return bootstrap_data
    return bootstrap_data;
end


function f!(resid, x)
    for i in eachindex(x)
        resid[i] = sin(x[i]) + x[i]^3
    end
end
x = ones(5)
@test Sundials.kinsol(f!, x, linear_solver = :Band, jac_upper = 0, jac_lower = 0) ==
      Sundials.kinsol(f!, x)

function f_iip(du, u, p, t)
    @. du = sin(u) + u^3
end
function f_iip(du, u, p)
    @. du = sin(u) + u^3
end
function f_oop(u, p, t)
    return @. sin(u) + u^3
end
function f_oop(u, p)
    return @. sin(u) + u^3
end

#Testing KINSOL()
u0 = ones(5)
prob_iip = SteadyStateProblem(f_iip, u0)
prob_oop = SteadyStateProblem(f_oop, u0)
@test solve(prob_iip, KINSOL()) == solve(prob_iip, KINSOL(linear_solver=:Band, jac_upper=0, jac_lower=0))
@test solve(prob_oop, KINSOL()) == solve(prob_oop, KINSOL(linear_solver=:Band, jac_upper=0, jac_lower=0))
prob_iip = NonlinearProblem(f_iip, u0)
prob_oop = NonlinearProblem(f_oop, u0)
@test solve(prob_iip, KINSOL()) == solve(prob_iip, KINSOL(linear_solver=:Band, jac_upper=0, jac_lower=0))
@test solve(prob_oop, KINSOL()) == solve(prob_oop, KINSOL(linear_solver=:Band, jac_upper=0, jac_lower=0))


using Zygote
using LinearAlgebra

@Zygote.nograd StopFunction
@Zygote.nograd _initializect_square

@Zygote.adjoint function IPEPS{LT,T,N,AT}(bulk) where {LT,T,N,AT}
    IPEPS{LT,T,N,AT}(bulk), dy -> (dy.bulk,)
end

@Zygote.adjoint function CTMRGRuntime{LT}(bulk::AT,
        corner::AbstractArray{T}, edge::AbstractArray{T}) where {LT<:AbstractLattice,T,N,AT<:AbstractArray{T,N}}
        return CTMRGRuntime{LT}(bulk,corner,edge), dy->(dy.bulk, dy.corner, dy.edge)
end

# patch since it's currently broken otherwise
@Zygote.adjoint function Base.typed_hvcat(::Type{T}, rows::Tuple{Vararg{Int}}, xs::S...) where {T,S}
  Base.typed_hvcat(T,rows, xs...), ȳ -> (nothing, nothing, permutedims(ȳ)...)
end

# improves performance compared to default implementation, also avoids errors
# with some complex arrays
@Zygote.adjoint function LinearAlgebra.norm(A::AbstractArray, p::Real = 2)
    n = norm(A,p)
    back(Δ) = let n = n
                    (Δ .* A ./ (n + eps(0f0)),)
                end
    return n, back
end

@doc raw"
    num_grad(f, K::Real; [δ = 1e-5])

return the numerical gradient of `f` at `K` calculated with
`(f(K+δ/2) - f(K-δ/2))/δ`

# example

```jldoctest; setup = :(using TensorNetworkAD)
julia> TensorNetworkAD.num_grad(x -> x * x, 3) ≈ 6
true
```
"
num_grad(f, K::Real; δ::Real = 1e-5) = (f(K+δ/2) - f(K-δ/2))/δ

@doc raw"
    num_grad(f, K::AbstractArray; [δ = 1e-5])

return the numerical gradient of `f` for each element of `K`.

# example

```jldoctest; setup = :(using TensorNetworkAD, LinearAlgebra)
julia> TensorNetworkAD.num_grad(tr, rand(2,2)) ≈ I
true
```
"
function num_grad(f, a::AbstractArray; δ::Real = 1e-5)
    map(CartesianIndices(a)) do i
        foo = x -> (ac = copy(a); ac[i] = x; f(ac))
        num_grad(foo, a[i], δ = δ)
    end
end


using DataFrames
using DatasToolbox

const NROWS = 5*10^6

df = DataFrame(A=rand(NROWS), B=rand(1:100, NROWS))

const M = 0.5

# testfunc(a, b) = a < M && b % 3 == 0
# testfunc(a) = a < M

# @time ocdf = DatasToolbox.constrain_OLD(df, [:A, :B], testfunc)
# @time cdf = constrain(df, [:A, :B], testfunc)

dict = Dict()
expr = :((:A .< M) && (:B % 3 .== 0))
DatasToolbox._checkConstraintExpr!(expr, dict)

mac = macroexpand(:(@constrain(df, (:A .< M) && (:B % 3 .== 0))))

@time cdf = @constrain(df, (:A .< M) && (:B % 3 .== 0))

@time ccdf = constrain(df, A=(a -> a < M), B=(b -> b % 3 == 0))


function TriSquareMesh(
    m::Int,
    n::Int = m,
)::Tuple{Array{Float64,2},Array{Int,2}}

    p = [[x; y] for x = 0.0:1.0/Float64(m):1.0, y = 0.0:1.0/Float64(n):1.0][:]
    t = [
        [
            [i + (j - 1) * (m + 1) i + (j - 1) * (m + 1) + 1 i + j * (m + 1)],
            [i + (j - 1) * (m + 1) + 1 i + j * (m + 1) + 1 i + j * (m + 1)],
        ] for i = 1:m, j = 1:n
    ]

    return hcat(p...), hcat(hcat(t'...)...)
end

function QuadSquareMesh(
    m::Int,
    n::Int = m,
)::Tuple{Array{Float64,2},Array{Int,2}}

    p = [[x; y] for x = 0.0:1.0/Float64(m):1.0, y = 0.0:1.0/Float64(n):1.0][:]
    t = [ [i + (j - 1) * (m + 1)    i + (j - 1) * (m + 1) + 1  i + j * (m + 1) + 1   i + j * (m + 1)]
          for j = 1:m, i = 1:n ]

    return hcat(p...), hcat(t'...)
end

function SquareMesh(
    m::Int,
    n::Int,
    elemtype::Int,
)::Tuple{Array{Float64,2},Array{Int,2}}

    if (elemtype==0)
        p, t = TriSquareMesh(m,n)
    else
        p, t = QuadSquareMesh(m,n)
    end

    return p, t
end


"""
# Differencing
```julia
diff(ts::TS, periods::Int = 1)
```

Return the discrete difference of successive row elements.
Default is the element in the next row. `periods` defines the number
of rows to be shifted over. The skipped rows are rendered as `missing`.

`diff` returns an error if column type does not have the method `-`.

# Examples
```jldoctest; setup = :(using TSx, DataFrames, Dates, Random, Statistics)
julia> using Random, Statistics;

julia> random(x) = rand(MersenneTwister(123), x);

julia> dates = collect(Date(2017,1,1):Day(1):Date(2018,3,10));

julia> ts = TS(random(length(dates)), dates);
julia> ts[1:10]
(10 x 1) TS with Date Index

 Index       x1
 Date        Float64
───────────────────────
 2017-01-01  0.768448
 2017-01-02  0.940515
 2017-01-03  0.673959
 2017-01-04  0.395453
 2017-01-05  0.313244
 2017-01-06  0.662555
 2017-01-07  0.586022
 2017-01-08  0.0521332
 2017-01-09  0.26864
 2017-01-10  0.108871

julia> diff(ts)[1:10]        # difference over successive rows
(10 x 1) TS with Date Index

 Index       x1
 Date        Float64?
─────────────────────────────
 2017-01-01  missing
 2017-01-02        0.172067
 2017-01-03       -0.266556
 2017-01-04       -0.278506
 2017-01-05       -0.0822092
 2017-01-06        0.349311
 2017-01-07       -0.0765327
 2017-01-08       -0.533889
 2017-01-09        0.216506
 2017-01-10       -0.159769

julia> diff(ts, 3)[1:10]     # difference over the third row
(10 x 1) TS with Date Index

 Index       x1
 Date        Float64?
─────────────────────────────
 2017-01-01  missing
 2017-01-02  missing
 2017-01-03  missing
 2017-01-04       -0.372995
 2017-01-05       -0.627271
 2017-01-06       -0.0114039
 2017-01-07        0.190569
 2017-01-08       -0.261111
 2017-01-09       -0.393915
 2017-01-10       -0.477151

```
"""

# Diff
function diff(ts::TS, periods::Int = 1)
    if periods <= 0
        error("periods must be a postive int")
    end
    ddf = ts.coredata[:, Not(:Index)] .- TSx.lag(ts, periods).coredata[:, Not(:Index)]
    insertcols!(ddf, 1, "Index" => ts.coredata[:, :Index])
    TS(ddf, :Index)
end