import QuasiGeostrophy: compute
struct Wrapper{T, S} <: AbstractExpression
    data::T
    meta_data::S
end
struct WrapperMetaData{T}
    io_name::T
end

function Base.show(io::IO, field::Wrapper{T, S}) where {T <: Char, S}
    color = 230
    printstyled(io, field.data, color = color)
end
function Base.show(io::IO, field::Wrapper{T, S}) where {T, S <: WrapperMetaData}
    color = 230
    printstyled(io, field.meta_data.io_name, color = color)
end

compute(a::Wrapper) = a.data

macro wrapper(expr)
    rewritten_expr = _wrapper(expr)
    return rewritten_expr
end

function _wrapper(expr::Expr)
    symb = expr.args[1]
    val  = expr.args[2]
    if expr.head != :(=)
        println( "@wrapper macro not in proper form")
        println( "must be ")
        println( "@wrapper a=1 b=2 c=3")
        return error()
    end
    string_symb = String(symb)
    new_expr = :($(esc(symb)) =  Wrapper($val, WrapperMetaData($string_symb)))
    return new_expr
end

macro wrapper(exprs...)
    rewritten_exprs = [_wrapper(expr) for expr in exprs]
    return Expr(:block, rewritten_exprs...)
end

function rhs(qg_system)
    qg_system[1].lhs.data.data .= evaluate(qg_system[1].rhs)
    qg_system[2].lhs.data.data .= evaluate(qg_system[2].rhs)
    rhs¹ = evaluate(qg_system[3].rhs)
    rhs² = evaluate(qg_system[4].rhs)
    return [rhs¹, rhs²]
end
function evolve_system(qg_system, Δt; filter = 1)
    data = [qg_system[3].lhs.operand.data.data, qg_system[4].lhs.operand.data.data]
    olddata = copy(data)
    stage1 = rhs(qg_system)
    data .+= Δt .* stage1
    stage2 = rhs(qg_system)
    data .= olddata .+ Δt/2 .* (stage1 + stage2)
    qg_system[3].lhs.operand.data.data .= filter * data[1]
    qg_system[4].lhs.operand.data.data .= filter * data[2]
    return nothing
end
#
function cfl(ψ¹, ψ², ∂x, ∂y)
    F = ψ¹.data.metadata.transform.backward
    x,y = ψ¹.data.metadata.grid.grid
    Δx = x[2] - x[1]
    Δy = y[2] - y[1]
    u1 = F * evaluate(∂y(ψ¹))
    v1 = F * evaluate(∂x(ψ¹))
    u2 = F * evaluate(∂y(ψ²))
    v2 = F * evaluate(∂x(ψ²))
    u1 = maximum(abs.(u1))
    v1 = maximum(abs.(v1))
    u2 = maximum(abs.(u2))
    v2 = maximum(abs.(v2))
    return minimum([Δx / u1, Δx / u2, Δy / v1, Δy / v2]) # gives a Δt
end

# building some cutoff filters
function cutoff(k, cutoff)
    return k > cutoff ? 0.0 : 1.0
end


## constructors of null versions of types for passing to C
for T in (:SuperMatrix,:NCformat,:NRformat,:SCformat,:SCPformat,:NCPformat,
          :DNformat,NRformat_loc,:e_node,:ExpHeader,:LU_stack_t,
          :mem_usage_t,:GlobalLU_t)
    @eval begin
        nv{}(::Type{$T}) = apply($T,map(zero,$T.types))  # null type T value
    end
end

function nv(::Type{superlu_options_t})
    v = apply(superlu_options_t,map(zero,superlu_options_t.types))
    ccall((:set_default_options,:libsuperlu),Void,(Ptr{superlu_options_t},),&v)
    v
end

function nv(::Type{SuperLUStat_t})
    v = apply(SuperLUStat_t,map(zero,SuperLUStat_t.types))
    ccall((:StatInit,:libsuperlu),Void,(Ptr{SuperLUStat_t},),&v)
    v
end

size(A::SuperLUMat,d::Integer) = d == 1 ? A.sm.nrow : (d == 2 ? A.sm.ncol : 1)
size(A::SuperLUMat) = (A.sm.nrow,A.sm.ncol)

Stype(A::SuperMatrix) = A.Stype # storage type (compressed column, dense, etc. - see Stype_t)
Dtype(A::SuperMatrix) = A.Dtype # data type (Cdouble, complex, etc. - see Dtype_t)
Mtype(A::SuperMatrix) = A.Mtype # matrix type (general, tril, triu, sym, etc. - see Mtype_t)

for T in (:NCformat,:NRformat,:SCformat,:SCPformat,:NCPformat,:DNformat,:NRformat_loc)
    @eval begin
        countnz(A::$T) = A.nnz
    end
end

function matstore(A::SuperMatrix)
    st = Stype(A)
    st == SLU_NC && return unsafe_load(convert(Ptr{NCformat},A.Store))
    st == SLU_NCP && return unsafe_load(convert(Ptr{NCPformat},A.Store))
    st == SLU_NR && return unsafe_load(convert(Ptr{NRformat},A.Store))
    st == SLU_SC && return unsafe_load(convert(Ptr{SCformat},A.Store))
    st == SLU_SCP && return unsafe_load(convert(Ptr{SCPformat},A.Store))
    st == SLU_SR && return unsafe_load(convert(Ptr{SRformat},A.Store))
    st == SLU_DN && return unsafe_load(convert(Ptr{DNformat},A.Store))
    st == SLU_NR_loc && return unsafe_load(convert(Ptr{NRformat_loc},A.Store))
    error("Unknown Stype = $st")
end

## could cheat here because nnz is always the first member of the type
countnz(A::SuperLUMat) = A.sm.Store == zero(Ptr{Void}) ? zero(Cint) : countnz(matstore(A))

function NCMat(A::SparseMatrixCSC{Cdouble})
    m,n = map(int32,size(A));
    ## turns out that you can't pass a matrix stored in symmetric form to dgssv
    ## symA = issym(A); AA = symA ? tril(A) : A
    ## mtyp = symA ? SLU_SYL : (istril(A) ? SLU_TRL : (istriu(A) ? SLU_TRU : SLU_GE))
    nz = int32(nfilled(A)); nzval = copy(A.nzval); rv = A.rowval; cp = A.colptr
    rowind = Array(int_t,nz); colptr = Array(int_t,n+i1)
                                        # use zero-based indices
    for i in 1:length(rowind); rowind[i] = int32(rv[i]) - i1; end
    for i in 1:length(colptr); colptr[i] = int32(cp[i]) - i1; end
    cres = nv(SuperMatrix)
    ccall((:dCreate_CompCol_Matrix,:libsuperlu),Void,
          (Ptr{SuperMatrix},Cint,Cint,Cint,Ptr{Cdouble},Ptr{Cint},Ptr{Cint},Stype_t,Dtype_t,Mtype_t),
          &cres,m,n,nz,nzval,rowind,colptr,SLU_NC,SLU_D,SLU_GE)
#    ccall((:dPrint_CompCol_Matrix,:libsuperlu),Void,(Ptr{Uint8},Ptr{SuperMatrix}),"A",&cres)
    NCMat(cres,colptr,rowind,nzval)
end

function DMat(B::StridedVecOrMat{Cdouble})
    mtyp = isa(B, Vector) ? SLU_GE : (istril(B) ? SLU_TRL : (istriu(B) ? SLU_TRU : SLU_GE))
    nzval = copy(B[:])
    cres = nv(SuperMatrix)
    ccall((:dCreate_Dense_Matrix,:libsuperlu),Void,
          (Ptr{SuperMatrix},Cint,Cint,Ptr{Cdouble},Cint,Cint,Cint,Cint),
          &cres,size(B,1),size(B,2),pointer(nzval),stride(B,2),SLU_DN,SLU_D,mtyp)
#    ccall((:dPrint_Dense_Matrix,:libsuperlu),Void,(Ptr{Uint8},Ptr{SuperMatrix}),"B",&cres)
    DMat(cres,nzval)
end

## Extract compiled-in sizes from libsuperlu
function sp_ienv(i::Integer)
    0 < i < 8 || error("i must be in 1,2,...,7")
    ccall((:sp_ienv,:libsuperlu),Cint,(Cint,),i)
end

function (\)(A::NCMat,B::StridedVecOrMat)
    A.sm.Dtype == SLU_D && eltype(B) == Cdouble || error("Both A and B must be Float64 arrays")
    m,n = size(A); m == n == size(B,1) || error("Dimension mismatch")
    BB = DMat(B)
    opts = nv(superlu_options_t)
    perm_r = zeros(Cint,n)
    perm_c = zeros(Cint,n)
    sTat = nv(SuperLUStat_t)
    L = nv(SuperMatrix)
    U = nv(SuperMatrix)
    inFo = zeros(Cint,1)
    ccall((:dgssv,:libsuperlu),Void,
          (Ptr{superlu_options_t},Ptr{SuperMatrix},Ptr{Cint},Ptr{Cint},
           Ptr{SuperMatrix},Ptr{SuperMatrix},Ptr{SuperMatrix},
           Ptr{SuperLUStat_t},Ptr{Cint}),
          &opts,&A.sm,perm_c,perm_r,&L,&U,&BB.sm,&sTat,inFo)
    inFo[1] == 0 || error("dgssv returned error code $(inFo[1])")
#    ccall((:StatPrint,:libsuperlu),Void,(Ptr{SuperLUStat_t},),&sTat)
    ccall((:StatFree,:libsuperlu),Void,(Ptr{SuperLUStat_t},),&sTat)
    U.Stype == SLU_NC || error("Matrix U should be of type NCformat (Stype = 0)")
    ccall((:Destroy_CompCol_Matrix,:libsuperlu),Void,(Ptr{SuperMatrix},),&U)
    L.Stype == SLU_SC || error("Matrix L should be of type SCformat (Stype = 3)")
    ccall((:Destroy_SuperNode_Matrix,:libsuperlu),Void,(Ptr{SuperMatrix},),&L)
    reshape(BB.nzval,size(B))
end

function lufact(A::NCMat,opts::superlu_options_t)
    m,n = size(A)
    perm_c = zeros(Cint,n)
                                        # determine fill-reducing permutation
    ccall((:get_perm_c,:libsuperlu),Void,(Cint,Ptr{SuperMatrix},Ptr{Cint}),
          opts.ColPerm,&(A.sm),perm_c)
    AC = nv(SuperMatrix)
    eTree = zeros(Cint,n)
                                        # apply perm_c to create AC and form eTree
    ccall((:sp_preorder,:libsuperlu),Void,
          (Ptr{superlu_options_t},Ptr{SuperMatrix},Ptr{Cint},Ptr{Cint},Ptr{SuperMatrix}),
          &opts,&(A.sm),perm_c,eTree,&AC)
    L = nv(SuperMatrix)
    U = nv(SuperMatrix)
    perm_r = zeros(Cint,m)
    inFo = zeros(Cint,1)
    sTat = nv(SuperLUStat_t)
    ccall((:dgstrf,:libsuperlu),Void,
          (Ptr{superlu_options_t},Ptr{SuperMatrix},Cint,Cint,Ptr{Cint},
           Ptr{Void},Cint,Ptr{Cint},Ptr{Cint},Ptr{SuperMatrix},Ptr{SuperMatrix},
           Ptr{SuperLUStat_t},Ptr{Cint}),
          &opts,&(A.sm),sp_ienv(2),sp_ienv(1),eTree,
          zero(Ptr{Void}),0,perm_c,perm_r,&L,&U,&sTat,inFo)
    println("info = ",inFo[1])
    inFo[1] == 0 || error("dgstrf returned error code $(inFo[1])")
    ccall((:StatPrint,:libsuperlu),Void,(Ptr{SuperLUStat_t},),&sTat)
    ccall((:dPrint_CompCol_Matrix,:libsuperlu),Void,
          (Ptr{Uint8},Ptr{SuperMatrix}), "U", &U)
    ccall((:dPrint_SuperNode_Matrix,:libsuperlu),Void,
          (Ptr{Uint8},Ptr{SuperMatrix}), "L", &L)
    showall(perm_c)
    showall(perm_r)
end
lufact(A::NCMat) = lufact(A,nv(superlu_options_t))


include("custom_determ.jl")
include("distributions.jl")
include("choice_at.jl")
include("call_at.jl")
include("map.jl")
include("unfold.jl")
include("recurse.jl")
include("dist.jl")


################################################################################################
#
# demo1: Create a blank color image and display it
#
################################################################################################

# Image properties
width = cint(300)
height = cint(300)

# Color
R=cint(255)
G=cint(0)
B=cint(0)

cxx"""
void create_image_and_display(int width, int height, int B, int G, int R)
   {
   // Create a new image width x height, 8-bit unsigned RGB (BGR in OpenCV)
   cv::Mat img(width, height, CV_8UC3, cv::Scalar(B,G,R));  // Blue, Green, Red (0:255)

   // Create a new window named "Welcome"
   cv::namedWindow("Welcome", cv::WINDOW_AUTOSIZE);

   // Show the image in window
   cv::imshow("Welcome", img);

   // Writing a message to the REPL from Julia
   std::cout << "\nTo end this test, press any key" << std::endl;

   // Wait for key press
   cv::waitKey(0);

   // Destroy the window
   cv::destroyWindow("Welcome");

 }
"""

@cxx create_image_and_display(width, height, B, G, R)


module SymbolicAD

import Base.Meta: isexpr
import JuMP
import LinearAlgebra
import MathOptInterface
import SparseArrays
import Symbolics
import Test

const MOI = MathOptInterface

include("nonlinear_oracle.jl")
include("MOI_wrapper.jl")
include("jump_test_utilities.jl")
end


module TestIntroduction
include("../../../examples/introduction.jl")
end  # module


mutable struct GFSimulateState
    trace::GFTrace
    score::Float64
    visitor::AddressVisitor
    params::Dict{Symbol,Any}
end

function GFSimulateState(params::Dict{Symbol,Any})
    GFSimulateState(GFTrace(), 0., AddressVisitor(), params)
end

get_args_change(state::GFSimulateState) = nothing
get_addr_change(state::GFSimulateState, addr) = nothing
set_ret_change!(state::GFSimulateState, value) = begin end

function addr(state::GFSimulateState, dist::Distribution{T}, args, addr, arg_change) where {T}
    visit!(state.visitor, addr)
    retval::T = random(dist, args...)
    score = logpdf(dist, retval, args...)
    call = CallRecord(score, retval, args)
    state.trace = assoc_primitive_call(state.trace, addr, call)
    state.trace.has_choices = true
    state.score += score
    retval
end

function addr(state::GFSimulateState, gen::Generator{T,U}, args, addr, arg_change) where {T,U}
    visit!(state.visitor, addr)
    trace::U = simulate(gen, args)
    call::CallRecord = get_call_record(trace)
    state.trace = assoc_subtrace(state.trace, addr, trace)
    state.trace.has_choices = state.trace.has_choices || has_choices(trace)
    state.score += call.score
    call.retval::T
end

splice(state::GFSimulateState, gf::GenFunction, args::Tuple) = exec(gf, state, args)

function simulate(gen::GenFunction, args)
    state = GFSimulateState(gen.params)
    retval = exec(gen, state, args)
    # TODO add return type annotation for gen functions
    call = CallRecord{Any}(state.score, retval, args)
    state.trace.call = call
    state.trace
end


# functions to undo incycle, containRoot, gammaz
# originally in functions.jl
# Claudia March 2015

# ---------------------------------------- undo update of new hybridization --------------------------------

# function to undo updateInCycle which returns an array
# of edges/nodes changed
function undoInCycle!(edges::Array{Edge,1},nodes::Array{Node,1})
    for e in edges
        e.inCycle = -1;
    end
    for n in nodes
        n.inCycle = -1;
    end
end

# function to undo updateContainRoot (which returns an array
# of edges changed) and gives value bool, by default true
function undoContainRoot!(edges::Array{Edge,1}, bool::Bool)
    for e in edges
        e.containRoot = bool
    end
end

undoContainRoot!(edges::Vector{Edge}) = undoContainRoot!(edges,true)

# function to undo updateGammaz which returns an array
# of edges changed
# it only changes the status of istIdentifiable to true
function undoistIdentifiable!(edges::Array{Edge,1})
    for e in edges
        !e.istIdentifiable ? e.istIdentifiable = true : e.istIdentifiable = false;
    end
end

"""
    undoGammaz!(node, network)

Undo `updateGammaz!` for the 2 cases: bad diamond I,II.
`node` should be a hybrid node.
Set length to edges that were not identifiable and
change edges' `gammaz` attribute to -1.0.
Recalculate branch lengths in terms of `gammaz`.
*warning*: needs to know `incycle` attributes
"""
function undoGammaz!(node::Node, net::HybridNetwork)
    node.hybrid || error("cannot undo gammaz if starting node is not hybrid")
    if(node.isBadDiamondI)
        edge_maj, edge_min, tree_edge2 = hybridEdges(node);
        other_maj = getOtherNode(edge_maj,node);
        other_min = getOtherNode(edge_min,node);
        edgebla,tree_edge_incycle1,tree_edge = hybridEdges(other_min);
        edgebla,tree_edge_incycle2,tree_edge = hybridEdges(other_maj);
        other_min.gammaz != -1 || error("bad diamond I in node $(node.number) but no gammaz updated correctly")
        setLength!(tree_edge_incycle1,-log(1-other_min.gammaz))
        other_maj.gammaz != -1 || error("bad diamond I in node $(node.number) but no gammaz updated correctly")
        setLength!(tree_edge_incycle2,-log(1-other_maj.gammaz))
        if approxEq(other_maj.gammaz,0.0) && approxEq(other_min.gammaz,0.0)
            setGamma!(edge_maj,0.0, true) # gamma could be anything if both gammaz are 0.0, but will set to 0.0
            setLength!(edge_maj,0.0)
            setLength!(edge_min,0.0)
        else
            setGamma!(edge_maj,other_maj.gammaz / (other_maj.gammaz+other_min.gammaz), true)
        end
        other_min.gammaz = -1.0
        other_maj.gammaz = -1.0
        tree_edge_incycle1.istIdentifiable = true;
        tree_edge_incycle2.istIdentifiable = true;
        edge_maj.istIdentifiable = true;
        edge_min.istIdentifiable = true;
        node.isBadDiamondI = false
        net.numBad -= 1
    elseif(node.isBadDiamondII)
        edge_maj, edge_min, tree_edge2 = hybridEdges(node);
        tree_edge2.istIdentifiable = true
        node.isBadDiamondII = false
    elseif(node.isBadTriangle)
        edge_maj, edge_min, tree_edge2 = hybridEdges(node);
        tree_edge2.istIdentifiable = true
        node.isBadTriangle = false
    elseif(node.isVeryBadTriangle || node.isExtBadTriangle)
        node.isVeryBadTriangle = false
        node.isExtBadTriangle = false
        net.hasVeryBadTriangle = false
    else
        edge_maj, edge_min, tree_edge2 = hybridEdges(node);
        edge_maj.istIdentifiable = isEdgeIdentifiable(edge_maj)
        edge_min.istIdentifiable = isEdgeIdentifiable(edge_min)
    end
end


# This file is part of the TaylorIntegration.jl package; MIT licensed

module TaylorIntegration

using Reexport
@reexport using TaylorSeries
using LinearAlgebra
using Markdown
using Requires

export taylorinteg, lyap_taylorinteg, @taylorize

include("explicitode.jl")
include("lyapunovspectrum.jl")
include("rootfinding.jl")
include("parse_eqs.jl")

function __init__()
    @require DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" include("common.jl")
end

end #module


using SimplexTrees
using Base.Test

include("simplex_array_list.jl")
#include("simplex_tree.jl")


## 06.jl
# 集合

function ngram(input, n)
  words = []
  for i in 1:(length(input)-n+1)
    push!(words, input[i:i+n-1])
  end
  return words
end

x = ngram("paraparaparadise", 2)
y = ngram("paragraph", 2)

println("--- 和集合 ---")
println(union(x, y))
println("--- 積集合 ---")
println(intersect(x, y))
println("--- 差集合 ---")
println(setdiff(x, y))

println("\"se\" in ", x, " => ", in("se", x))
println("\"se\" in ", y, " => ", in("se", y))


import JSON

# from https://github.com/JuliaLang/JSON.jl/issues/39
# but without `flatten` or `Nullable`
function deserialize(::Type{T}, json::AbstractString) where T
    deserialize(T, JSON.parse(json))
end

function deserialize(::Type{T}, json::Dict) where T
    T([deserialize(T, f, json) for f in fieldnames(T)]...)
end

function deserialize(::Type{Tf}, field::AbstractString, json::Dict) where Tf
    deserialize(Tf, json[field])
end

function deserialize(::Type{T}, field::Symbol, json::Dict) where T
    deserialize(fieldtype(T,field), string(field), json)
end

function deserialize(::Type{T}, i::Integer) where T<:Integer
    T(i)
end

function deserialize(::Type{T}, i::AbstractString) where T<:Integer
    parse(T, i)
end

function deserialize(::Type{T}, i::AbstractFloat) where T<:AbstractFloat
    T(i)
end

function deserialize(::Type{T}, i::AbstractString) where T<:AbstractFloat
    parse(T, i)
end

# my own extension to vectors
function deserialize(::Type{Vector{T}}, A::Vector{Any}) where T
    T[deserialize(T, a) for a in A]
end

function read_instance(prefix, key::String)
    open(joinpath(prefix, "$(key).instance.json")) do f
        PipeLayout.deserialize(Instance, readstring(f))
    end
end
read_instance(key::String) = read_instance(".", key)

function read_topology(prefix, key::String)
    open(joinpath(prefix, "$(key).topology.json")) do f
        PipeLayout.deserialize(Topology, readstring(f))
    end
end
read_topology(key::String) = read_topology(".", key)


using BenchmarkTools
include("parallelGeneration.jl")

setRNGs(1)
nt = Threads.nthreads()
out = Vector{Float64}(undef, nt)

N0 = 2^25
@btime foo!($out, $N0)


# Pulse model for Bandit Arm

struct Pulse <: BanditArmBase
    step::Int64             # For tracking internally
    period::Int64           # Period of the pulse
    changePoint::Int64      # The point at which the signal goes high
    highDuration::Int64     # TIme to stay HIGH
    isRestless::Bool        # Is this a restless arm

    function Pulse( _period, _cp, _hd, _ir = true )
        new(
            0,
            _period,
            _cp,
            _hd,
            _ir
        )
    end
end

function pull!( arm::Pulse )
    arm.step    = arm.step + 1
    if arm.step > arm.period
        arm.step = 1
    end

    return ((arm.step>=arm.changePoint)&&(arm.step<=arm.changePoint+arm.highDuration)) ? 1 : 0;
end

function tick!( arm::Pulse )
    if arm.isRestless
        arm.step = arm.step + 1
        if arm.step > arm.period
            arm.step = 1
        end
    end
    nothing
end

function reset!( arm::Pulse )
    arm.step    = 0
    nothing
end


"""
    LookupArray

Types defining the behaviour of a lookup index, how it is plotted
and how [`Selector`](@ref)s like [`Between`](@ref) work.

A `LookupArray` may be [`NoLookup`](@ref) indicating that the index is just the
underlying array axis, [`Categorical`](@ref) for ordered or unordered categories,
or a [`Sampled`](@ref) index for [`Points`](@ref) or [`Intervals`](@ref).
"""
abstract type LookupArray{T,N} <: AbstractArray{T,N} end

const LookupArrayTuple = Tuple{<:LookupArray,Vararg{<:LookupArray}}

span(lookup::LookupArray) = NoSpan()
sampling(lookup::LookupArray) = NoSampling()

dims(::LookupArray) = nothing
val(l::LookupArray) = parent(l)
index(l::LookupArray) = parent(l)
locus(l::LookupArray) = Center()

Base.parent(l::LookupArray) = l.data
Base.size(l::LookupArray) = size(parent(l))
Base.axes(l::LookupArray) = axes(parent(l))
Base.first(l::LookupArray) = first(parent(l))
Base.last(l::LookupArray) = last(parent(l))
Base.firstindex(l::LookupArray) = firstindex(parent(l))
Base.lastindex(l::LookupArray) = lastindex(parent(l))
function Base.:(==)(l1::LookupArray, l2::LookupArray)
    typeof(l1) == typeof(l2) && parent(l1) == parent(l2)
end

ordered_first(l::LookupArray) = l[ordered_firstindex(l)]
ordered_last(l::LookupArray) = l[ordered_lastindex(l)]

ordered_firstindex(l::LookupArray) = ordered_firstindex(order(l), l)
ordered_lastindex(l::LookupArray) = ordered_lastindex(order(l), l)
ordered_firstindex(o::ForwardOrdered, l::LookupArray) = firstindex(parent(l))
ordered_firstindex(o::ReverseOrdered, l::LookupArray) = lastindex(parent(l))
ordered_firstindex(o::Unordered, l::LookupArray) = firstindex(parent(l))
ordered_lastindex(o::ForwardOrdered, l::LookupArray) = lastindex(parent(l))
ordered_lastindex(o::ReverseOrdered, l::LookupArray) = firstindex(parent(l))
ordered_lastindex(o::Unordered, l::LookupArray) = lastindex(parent(l))

function Base.searchsortedfirst(lookup::LookupArray, val; lt=<)
    searchsortedfirst(parent(lookup), unwrap(val); order=ordering(order(lookup)), lt=lt)
end
function Base.searchsortedlast(lookup::LookupArray, val; lt=<)
    searchsortedlast(parent(lookup), unwrap(val); order=ordering(order(lookup)), lt=lt)
end

function Adapt.adapt_structure(to, l::LookupArray)
    rebuild(l; data=Adapt.adapt(to, parent(l)))
end

"""
    AutoLookup <: LookupArray

    AutoLookup()
    AutoLookup(index=AutoIndex(); kw...)

Automatic [`LookupArray`](@ref), the default lookup. It will be converted automatically
to another [`LookupArray`](@ref) when it is possible to detect it from the index.

Keywords will be used in the detected `LookupArray` constructor.
"""
struct AutoLookup{T,A<:AbstractVector{T},K} <: LookupArray{T,1}
    data::A
    kw::K
end
AutoLookup(index=AutoIndex(); kw...) = AutoLookup(index, kw)

order(lookup::AutoLookup) = hasproperty(lookup.kw, :order) ? lookup.kw.order : AutoOrder()
span(lookup::AutoLookup) = hasproperty(lookup.kw, :span) ? lookup.kw.span : AutoSpan()
sampling(lookup::AutoLookup) = hasproperty(lookup.kw, :sampling) ? lookup.kw.sampling : AutoSampling()
metadata(lookup::AutoLookup) = hasproperty(lookup.kw, :metadata) ? lookup.kw.metadata : NoMetadata()

Base.step(lookup::AutoLookup) = Base.step(parent(lookup))

bounds(lookup::LookupArray) = _bounds(order(lookup), lookup)

_bounds(::ForwardOrdered, l::LookupArray) = first(l), last(l)
_bounds(::ReverseOrdered, l::LookupArray) = last(l), first(l)
_bounds(::Unordered, l::LookupArray) = (nothing, nothing)

@noinline Base.step(lookup::T) where T <: LookupArray =
    error("No step provided by $T. Use a `Sampled` with `Regular`")

"""
    Aligned <: LookupArray

Abstract supertype for [`LookupArray`](@ref)s
where the index is aligned with the array axes.

This is by far the most common supertype for `LookupArray`.
"""
abstract type Aligned{T,O} <: LookupArray{T,1} end

order(lookup::Aligned) = lookup.order

"""
    NoLookup <: LookupArray

    NoLookup()

A [`LookupArray`](@ref) that is identical to the array axis.
[`Selector`](@ref)s can't be used on this lookup.

## Example

Defining a `DimArray` without passing an index
to the dimensions, it will be assigned `NoLookup`:

```jldoctest NoLookup
using DimensionalData

A = DimArray(rand(3, 3), (X, Y))
Dimensions.lookup(A)

# output

NoLookup, NoLookup
```

Which is identical to:

```jldoctest NoLookup
using .LookupArrays
A = DimArray(rand(3, 3), (X(NoLookup()), Y(NoLookup())))
Dimensions.lookup(A)

# output

NoLookup, NoLookup
```
"""
struct NoLookup{A<:AbstractVector{Int}} <: Aligned{Int,Order}
    data::A
end
NoLookup() = NoLookup(AutoIndex())

order(lookup::NoLookup) = ForwardOrdered()
span(lookup::NoLookup) = Regular(1)

Base.step(lookup::NoLookup) = 1

rebuild(l::NoLookup; data=parent(l), kw...) = NoLookup(data)

"""
    AbstractSampled <: Aligned

Abstract supertype for [`LookupArray`](@ref)s where the index is
aligned with the array, and is independent of other dimensions. [`Sampled`](@ref)
is provided by this package.

`AbstractSampled` must have  `order`, `span` and `sampling` fields,
or a `rebuild` method that accpts them as keyword arguments.
"""
abstract type AbstractSampled{T,O<:Order,Sp<:Span,Sa<:Sampling} <: Aligned{T,O} end

span(lookup::AbstractSampled) = lookup.span
sampling(lookup::AbstractSampled) = lookup.sampling
metadata(lookup::AbstractSampled) = lookup.metadata
locus(lookup::AbstractSampled) = locus(sampling(lookup))

Base.step(lookup::AbstractSampled) = step(span(lookup))

for f in (:getindex, :view, :dotview)
    @eval begin
        # Regular span may need its step size updated
        @propagate_inbounds Base.$f(l::AbstractSampled, i::AbstractRange) =
            rebuild(l; data=Base.$f(parent(l), i), span=slicespan(l, i))
    end
end

function Adapt.adapt_structure(to, l::AbstractSampled)
    rebuild(l; data=Adapt.adapt(to, parent(l)), metadata=NoMetadata())
end

# bounds
bounds(lookup::AbstractSampled) = _bounds(sampling(lookup), span(lookup), lookup)

_bounds(::Points, span, lookup::AbstractSampled) = _bounds(order(lookup), lookup)
_bounds(::Intervals, span::Irregular, lookup::AbstractSampled) = bounds(span)
_bounds(sampling::Intervals, span::Explicit, lookup::AbstractSampled) =
    _bounds(order(lookup), sampling, span, lookup)
_bounds(::ForwardOrdered, ::Intervals, span::Explicit, lookup::AbstractSampled) =
    (val(span)[1, 1], val(span)[2, end])
_bounds(::ReverseOrdered, ::Intervals, span::Explicit, lookup::AbstractSampled) =
    (val(span)[1, end], val(span)[2, 1])
_bounds(::Intervals, span::Regular, lookup::AbstractSampled) =
    _bounds(locus(lookup), order(lookup), span, lookup)
_bounds(::Start, ::ForwardOrdered, span, lookup) = first(lookup), last(lookup) + step(span)
_bounds(::Start, ::ReverseOrdered, span, lookup) = last(lookup), first(lookup) - step(span)
_bounds(::Center, ::ForwardOrdered, span, lookup) =
    first(lookup) - step(span) / 2, last(lookup) + step(span) / 2
_bounds(::Center, ::ReverseOrdered, span, lookup) =
    last(lookup) + step(span) / 2, first(lookup) - step(span) / 2
_bounds(::End, ::ForwardOrdered, span, lookup) = first(lookup) - step(span), last(lookup)
_bounds(::End, ::ReverseOrdered, span, lookup) = last(lookup) + step(span), first(lookup)

"""
    Sampled <: AbstractSampled

    Sampled(data::AbstractVector, order::Order, span::Span, sampling::Sampling, metadata)
    Sampled(; data=AutoIndex(), order=AutoOrder(), span=AutoSpan(), sampling=Points(), metadata=NoMetadata())

A concrete implementation of the [`LookupArray`](@ref)
[`AbstractSampled`](@ref). It can be used to represent
[`Points`](@ref) or [`Intervals`](@ref).

`Sampled` is capable of representing gridded data from a wide range of sources, allowing
correct `bounds` and [`Selector`](@ref)s for points or intervals of regular,
irregular, forward and reverse indexes.

On `AbstractDimArray` construction, `Sampled` lookup is assigned for all lookups of
`AbstractRange` not assigned to [`Categorical`](@ref).

## Arguments

- `data`: An `AbstractVector` of index values, matching the length of the curresponding
    array axis.
- `order`: [`Order`](@ref)) indicating the order of the index,
    [`AutoOrder`](@ref) by default, detected from the order of `data`
    to be [`ForwardOrdered`](@ref), [`ReverseOrdered`](@ref) or [`Unordered`](@ref).
    These can be provided explicitly if they are known and performance is important.
- `span`: indicates the size of intervals or distance between points, and will be set to
    [`Regular`](@ref) for `AbstractRange` and [`Irregular`](@ref) for `AbstractArray`,
    unless assigned manually.
- `sampling`: is assigned to [`Points`](@ref), unless set to [`Intervals`](@ref) manually.
    Using [`Intervals`](@ref) will change the behaviour of `bounds` and `Selectors`s
    to take account for the full size of the interval, rather than the point alone.
- `metadata`: a `Dict` or `Metadata` wrapper that holds any metadata object adding more
    information about the array axis - useful for extending DimensionalData for specific
    contexts, like geospatial data in GeoData.jl. By default it is `NoMetadata()`.

## Example

Create an array with [`Interval`] sampling, and `Regular` span for a vector with known spacing.

We set the [`Locus`](@ref) of the `Intervals` to `Start` specifying
that the index values are for the positions at the start of each interval.

```jldoctest Sampled
using DimensionalData, DimensionalData.LookupArrays

x = X(Sampled(100:-20:10; sampling=Intervals(Start())))
y = Y(Sampled([1, 4, 7, 10]; span=Regular(3), sampling=Intervals(Start())))
A = ones(x, y)

# output
5×4 DimArray{Float64,2} with dimensions:
  X Sampled 100:-20:20 ReverseOrdered Regular Intervals,
  Y Sampled Int64[1, 4, 7, 10] ForwardOrdered Regular Intervals
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0
```
"""
struct Sampled{T,A<:AbstractVector{T},O,Sp,Sa,M} <: AbstractSampled{T,O,Sp,Sa}
    data::A
    order::O
    span::Sp
    sampling::Sa
    metadata::M
end
function Sampled(
    data=AutoIndex();
    order=AutoOrder(), span=AutoSpan(),
    sampling=AutoSampling(), metadata=NoMetadata()
)
    Sampled(data, order, span, sampling, metadata)
end

function rebuild(l::Sampled;
    data=parent(l), order=order(l), span=span(l), sampling=sampling(l), metadata=metadata(l), kw...
)
    Sampled(data, order, span, sampling, metadata)
end

"""
    AbstractCategorical <: Aligned

[`LookupArray`](@ref)s where the values are categories.

[`Categorical`](@ref) is the provided concrete implementation.
but this can easily be extended - all methods are defined for `AbstractCategorical`.

All `AbstractCategorical` must provide a `rebuild`
method with `data`, `order` and `metadata` keyword arguments.
"""
abstract type AbstractCategorical{T,O} <: Aligned{T,O} end

order(lookup::AbstractCategorical) = lookup.order
metadata(lookup::AbstractCategorical) = lookup.metadata

const CategoricalEltypes = Union{AbstractChar,Symbol,AbstractString}

function Adapt.adapt_structure(to, l::AbstractCategorical)
    rebuild(l; data=Adapt.adapt(to, parent(l)), metadata=NoMetadata())
end

"""
    Categorical <: AbstractCategorical

    Categorical(o::Order)
    Categorical(; order=Unordered())

An LookupArray where the values are categories.

This will be automatically assigned if the index contains `AbstractString`,
`Symbol` or `Char`. Otherwise it can be assigned manually.

[`Order`](@ref) will be determined automatically where possible.

## Arguments

- `data`: An `AbstractVector` of index values, matching the length of the curresponding
    array axis.
- `order`: [`Order`](@ref)) indicating the order of the index,
    [`AutoOrder`](@ref) by default, detected from the order of `data`
    to be `ForwardOrdered`, `ReverseOrdered` or `Unordered`.
    Can be provided if this is known and performance is important.
- `metadata`: a `Dict` or `Metadata` wrapper that holds any metadata object adding more
    information about the array axis - useful for extending DimensionalData for specific
    contexts, like geospatial data in GeoData.jl. By default it is `NoMetadata()`.

## Example

Create an array with [`Interval`] sampling.

```jldoctest Categorical
using DimensionalData

ds = X(["one", "two", "three"]), Y([:a, :b, :c, :d])
A = DimArray(rand(3, 4), ds)
Dimensions.lookup(A)

# output

Categorical String[one, two, three] Unordered,
Categorical Symbol[a, b, c, d] ForwardOrdered
```
"""
struct Categorical{T,A<:AbstractVector{T},O<:Order,M} <: AbstractCategorical{T,O}
    data::A
    order::O
    metadata::M
end
function Categorical(data=AutoIndex(); order=AutoOrder(), metadata=NoMetadata())
    Categorical(data, order, metadata)
end

function rebuild(l::Categorical;
    data=parent(l), order=order(l), metadata=metadata(l), kw...
)
    Categorical(data, order, metadata)
end

"""
    Unaligned <: LookupArray

Abstract supertype for [`LookupArray`](@ref) where the index is not aligned to the grid.

Indexing an [`Unaligned`](@ref) with [`Selector`](@ref)s must provide all
other [`Unaligned`](@ref) dimensions.
"""
abstract type Unaligned{T,N} <: LookupArray{T,N} end

"""
    Transformed <: Unaligned

    Transformed(f, dim::Dimension; metadata=NoMetadata())

[`LookupArray`](@ref) that uses an affine transformation to convert
dimensions from `dims(lookup)` to `dims(array)`. This can be useful
when the dimensions are e.g. rotated from a more commonly used axis.

Any function can be used to do the transformation, but transformations
from CoordinateTransformations.jl may be useful.

## Arguments

- `f`: transformation function
- `dim`: a dimension to transform to.

## Keyword Arguments

- `metdata`:

## Example

```jldoctest
using DimensionalData, DimensionalData.LookupArrays, CoordinateTransformations

m = LinearMap([0.5 0.0; 0.0 0.5])
A = [1 2  3  4
     5 6  7  8
     9 10 11 12];
da = DimArray(A, (t1=Transformed(m, X), t2=Transformed(m, Y)))

da[X(At(6)), Y(At(2))]

# output
9
```
"""
struct Transformed{T,A<:AbstractVector{T},F,D,M} <: Unaligned{T,1}
    data::A
    f::F
    dim::D
    metadata::M
end
function Transformed(f, dim; metadata=NoMetadata())
    Transformed(AutoIndex(), f, basetypeof(dim)(), metadata)
end

function rebuild(l::Transformed;
    data=data(l), f=f(l), dim=dim(l), metadata=metadata(l)
)
    Transformed(data, f, dim, metadata)
end

f(lookup::Transformed) = lookup.f
dim(lookup::Transformed) = lookup.dim

transformfunc(lookup::Transformed) = f(lookup)
transformdim(x) = nothing
transformdim(lookup::Transformed) = lookup.dim
transformdim(::Type{<:Transformed{<:Any,<:Any,<:Any,D}}) where D = D

Base.:(==)(l1::Transformed, l2::Transformed) = typeof(l1) == typeof(l2) && f(l1) == f(l2)

# TODO Transformed bounds

# Shared methods

slicespan(l::LookupArray, i::Colon) = span(l)
slicespan(l::LookupArray, i) = _slicespan(span(l), l, i)

_slicespan(span::Regular, l::LookupArray, i::Int) = span
_slicespan(span::Regular, l::LookupArray, i::AbstractRange) = Regular(step(l) * step(i))
_slicespan(span::Regular, l::LookupArray, i::AbstractArray) = _slicespan(Irregular(bounds(l)), l, i)
_slicespan(span::Explicit, l::LookupArray, i::Int) = Explicit(val(span)[:, i])
_slicespan(span::Explicit, l::LookupArray, i::AbstractArray) = Explicit(val(span)[:, i])
_slicespan(span::Irregular, l::LookupArray, i::StandardIndices) =
    _slicespan(sampling(l), span, l, i)
_slicespan(::Points, span::Irregular, l::LookupArray, i::StandardIndices) = span
_slicespan(::Intervals, span::Irregular, l::LookupArray, i::StandardIndices) =
    Irregular(_maybeflipbounds(l, _slicebounds(locus(l), span, l, i)))

function _slicebounds(locus::Start, span::Irregular, l::LookupArray, i::StandardIndices)
    l[first(i)], last(i) >= lastindex(l) ? _maybeflipbounds(l, bounds(span))[2] : l[last(i) + 1]
end
function _slicebounds(locus::End, span::Irregular, l::LookupArray, i::StandardIndices)
    first(i) <= firstindex(l) ? _maybeflipbounds(l, bounds(span))[1] : l[first(i) - 1], l[last(i)]
end
function _slicebounds(locus::Center, span::Irregular, l::LookupArray, i::StandardIndices)
    first(i) <= firstindex(l) ? _maybeflipbounds(l, bounds(span))[1] : (l[first(i) - 1] + l[first(i)]) / 2,
    last(i)  >= lastindex(l)  ? _maybeflipbounds(l, bounds(span))[2] : (l[last(i) + 1]  + l[last(i)]) / 2
end
# Have to special-case date/time so we work with seconds and add to the original
function _slicebounds(locus::Center, span::Irregular, l::LookupArray{T}, i::StandardIndices) where T<:Dates.AbstractTime
    op = T === Date ? div : /
    frst = if first(i) <= firstindex(l)
        _maybeflipbounds(l, bounds(span))[1]
    else
        if isrev(order(l))
            op(l[first(i)] - l[first(i) - 1], 2) + l[first(i) - 1]
        else
            op(l[first(i) - 1] - l[first(i)], 2) + l[first(i)]
        end
    end
    lst = if last(i) >= lastindex(l)
        _maybeflipbounds(l, bounds(span))[2]
    else
        if isrev(order(l))
            op(l[last(i)] - l[last(i) + 1], 2) + l[last(i) + 1]
        else
            op(l[last(i) + 1] - l[last(i)], 2) + l[last(i)]
        end
    end
    return (frst, lst)
end

# reducing methods
@inline reducelookup(lookup::NoLookup) = NoLookup(OneTo(1))
# TODO what should this do?
@inline reducelookup(lookup::Unaligned) = NoLookup(OneTo(1))
# Categories are combined.
@inline reducelookup(lookup::Categorical{<:AbstractString}) =
    rebuild(lookup; data=["combined"])
@inline reducelookup(lookup::Categorical) = rebuild(lookup; data=[:combined])
# Sampled is resampled
@inline reducelookup(lookup::AbstractSampled) = _reducelookup(span(lookup), lookup)

@inline _reducelookup(::Irregular, lookup::AbstractSampled) = begin
    rebuild(lookup; data=_reduceindex(lookup), order=ForwardOrdered())
end
@inline _reducelookup(span::Regular, lookup::AbstractSampled) = begin
    newstep = step(span) * length(lookup)
    newindex = _reduceindex(lookup, newstep)
    # Make sure the step type matches the new index eltype
    newstep = convert(promote_type(eltype(newindex), typeof(newstep)), newstep)
    newspan = Regular(newstep)
    rebuild(lookup; data=newindex, order=ForwardOrdered(), span=newspan)
end
@inline _reducelookup(
    span::Regular{<:Dates.CompoundPeriod}, lookup::AbstractSampled
) = begin
    newstep = Dates.CompoundPeriod(step(span).periods .* length(lookup))
    # We don't pass the step here - the range doesn't work with CompoundPeriod
    newindex = _reduceindex(lookup)
    # Make sure the step type matches the new index eltype
    newspan = Regular(newstep)
    rebuild(lookup; data=newindex, order=ForwardOrdered(), span=newspan)
end
@inline _reducelookup(span::Explicit, lookup::AbstractSampled) = begin
    bnds = val(span)
    newstep = bnds[2] - bnds[1]
    newindex = _reduceindex(lookup, newstep)
    # Make sure the step type matches the new index eltype
    newstep = convert(promote_type(eltype(newindex), typeof(newstep)), newstep)
    newspan = Explicit(reshape([bnds[1, 1]; bnds[2, end]], 2, 1))
    newlookup = rebuild(lookup; data=newindex, order=ForwardOrdered(), span=newspan)
end
# Get the index value at the reduced locus.
# This is the start, center or end point of the whole index.
@inline _reduceindex(lookup::LookupArray, step=nothing) = _reduceindex(locus(lookup), lookup, step)
@inline _reduceindex(locus::Start, lookup::LookupArray, step) = _mayberange(first(lookup), step)
@inline _reduceindex(locus::End, lookup::LookupArray, step) = _mayberange(last(lookup), step)
@inline _reduceindex(locus::Center, lookup::LookupArray, step) = begin
    index = parent(lookup)
    len = length(index)
    newval = centerval(index, len)
    _mayberange(newval, step)
end
# Ranges with a known step always return a range
_mayberange(x, step) = x:step:x
# Arrays return a vector
_mayberange(x, step::Nothing) = [x]

@inline centerval(index::AbstractArray{<:Number}, len) = (first(index) + last(index)) / 2
@inline function centerval(index::AbstractArray{<:DateTime}, len)
    f = first(index)
    l = last(index)
    if f <= l
        return (l - f) / 2 + first(index)
    else
        return (f - l) / 2 + last(index)
    end
end
@inline centerval(index::AbstractArray, len) = index[len ÷ 2 + 1]

ordering(::ForwardOrdered) = Base.Order.ForwardOrdering()
ordering(::ReverseOrdered) = Base.Order.ReverseOrdering()


"""
	grdfill(cmd0::String="", arg1=nothing, kwargs...)

Reads a grid that presumably has unfilled holes that the user wants to fill in some fashion.
Holes are identified by NaN values but this criteria can be changed.

Full option list at [`grdfill`]($(GMTdoc)grdfill.html)

Parameters
----------

- **A** | **algo** :: [Type => Str]		``Arg = mode[arg]``

    Specify the hole-filling algorithm to use. Choose from c for constant fill and append the constant value,
    n for nearest neighbor (and optionally append a search radius in pixels).
    ($(GMTdoc)grdfill.html#a)
- **G** | **outgrid** :: [Type => Str]

    Output grid file name. Note that this is optional and to be used only when saving
    the result directly on disk. Otherwise, just use the G = grdfill(....) form.
    ($(GMTdoc)grdfill.html#g)
- **L** | **list** :: [Type => Str]	``Arg = [p]``

    Just list the rectangular subregions west east south north of each hole. No grid fill takes place and
    **outgrid** is ignored. Optionally, append **p** to instead write closed polygons for all subregions.
- **N** | **nodata** :: [Type => Str]	``Arg = nodata``

    Sets the node value that identifies a point as a member of a hole [Default is NaN].
    ($(GMTdoc)grdfill.html#n)
- $(GMT.opt_R)
- $(GMT.opt_V)
- $(GMT.opt_f)
"""
function grdfill(cmd0::String="", arg1=nothing; kwargs...)

	length(kwargs) == 0 && return monolitic("grdfill", cmd0, arg1)

	d = KW(kwargs)
	cmd = parse_common_opts(d, "", [:R :V_params :f :params])
	cmd = parse_these_opts(cmd, d, [[:A :algo], [:G :outgrid], [:L :list], [:N :nodata]])

	common_grd(d, cmd0, cmd, "grdfill ", arg1)		# Finish build cmd and run it
end

# ---------------------------------------------------------------------------------------------------
grdfill(arg1, cmd0::String=""; kw...) = grdfill(cmd0, arg1; kw...)


# Types for Julia implementation of pseudolikelihood estimation (Stage2)
# Claudia August 2014
# in julia: include("types.jl")
#
# Types: Edge, Node, HybridNetwork
################################################################

# procedure to create hybrid network:
# 1) create edges defined as hybrid or not
# 2) create nodes with such edges
# 3) setNode! to add nodes into edges
# 4) create hybrid network
# 5) updateGammaz!

# -------------- EDGE -------------------------#
"""
 `ANode`

Abstract node. An object of type [`Edge`](@ref) has a `node` attribute,
which is an vector of 2 `ANode` objects.
The object of type [`Node`](@ref) is an `ANode`, and has an `edge` attribute,
which is vector of `Edge` objects.
"""
abstract type ANode end

"""
    Edge(number)

Data structure for an edge and its various attributes. Most notably:
- `number` (integer): serves as unique identifier;
  remains unchanged when the network is modified,
  with a nearest neighbor interchange for example
- `node`: a vector of [`Node`]s, normally just 2 of them
- `isChild1` (boolean): `true` if `node[1]` is the child node of the edge,
  false if `node[1]` is the parent node of the edge
- `length`: branch length
- `hybrid` (boolean): whether the edge is a tree edge or a hybrid edge
  (in which case `isChild1` is important, even if the network is semi-directed)
- `gamma`: proportion of genetic material inherited by the child node via the edge;
  1.0 for a tree edge
- `isMajor` (boolean): whether the edge is the major path to the child node;
  `true` for tree edges, since a tree edge is the only path to its child node;
  normally true if `gamma>0.5`.

and other fields, used very internally
"""
mutable struct Edge
    number::Int
    length::Float64 #default 1.0
    hybrid::Bool
    y::Float64 # exp(-t), cannot set in constructor for congruence
    z::Float64 # 1-y , cannot set in constructor for congruence
    gamma::Float64 # set to 1.0 for tree edges, hybrid?gamma:1.0
    node::Array{ANode,1}
    isChild1::Bool # used for hybrid edges to set the direction (default true)
    isMajor::Bool  # major edge treated as tree edge for network traversal
                   # true if gamma>.5, or if it is the original tree edge
    inCycle::Int # = Hybrid node number if this edge is part of a cycle created by such hybrid node
    # -1 if not part of cycle. used to add new hybrid edge. updated after edge is part of a network
    containRoot::Bool # true if this edge can contain a root given the direction of hybrid edges
                      # used to add new hybrid edge. updated after edge is part of a network
    istIdentifiable::Bool # true if the parameter t (length) for this edge is identifiable as part of a network
                          # updated after part of a network
    fromBadDiamondI::Bool # true if the edge came from deleting a bad diamond I hybridization
    # inner constructors: ensure congruence among (length, y, z) and (gamma, hybrid, isMajor), and size(node)=2
    Edge(number::Int) = new(number,1.0,false,exp(-1.0),1-exp(-1.0),1.,[],true,true,-1,true,true,false)
    Edge(number::Int, length::Float64) = new(number,length,false,exp(-length),1-exp(-length),1.,[],true,true,-1,true,true,false)
    Edge(number::Int, length::Float64,hybrid::Bool,gamma::Float64) =
        new(number,length,hybrid,exp(-length),1-exp(-length), hybrid ? gamma : 1.,[],true, !hybrid || gamma>0.5 ,-1,!hybrid,true,false)
    Edge(number::Int, length::Float64,hybrid::Bool,gamma::Float64,isMajor::Bool) =
        new(number,length,hybrid,exp(-length),1-exp(-length), hybrid ? gamma : 1.,[],true,isMajor,-1,!hybrid,true,false)
    function Edge(number::Int, length::Float64,hybrid::Bool,gamma::Float64,node::Array{ANode,1})
        size(node,1) != 2 ?
        error("vector of nodes must have exactly 2 values") :
        new(number,length,hybrid,exp(-length),1-exp(-length),
            hybrid ? gamma : 1., node,true, !hybrid || gamma>0.5,
            -1,!hybrid,true,false)
    end
    function Edge(number::Int, length::Float64,hybrid::Bool,gamma::Float64,node::Array{ANode,1},isChild1::Bool, inCycle::Int, containRoot::Bool, istIdentifiable::Bool)
        size(node,1) != 2 ?
        error("vector of nodes must have exactly 2 values") :
        new(number,length,hybrid,exp(-length),1-exp(-length),
            hybrid ? gamma : 1., node,isChild1, !hybrid || gamma>0.5,
            inCycle,containRoot,istIdentifiable,false)
    end
end

# warning: gammaz, inCycle, isBadTriangle/Diamond updated until the node is part of a network
"""
    Node(number, leaf)

Data structure for an edge and its various attributes. Most notably:

- `number` (integer): serves as unique identifier;
  remains unchanged when the network is modified,
  with a nearest neighbor interchange for example
- `leaf` (boolean): whether the node is a leaf (with data typically) or an
  internal node (no data typically)
- `name` (string): taxon name for leaves; internal node may or may not have a name
- `edge`: vector of [`Edge`]s that the node is attached to;
  1 if the node is a leaf, 2 if the node is the root, 3 otherwise, and
  potentially more if the node has a polytomy
- `hybrid` (boolean): whether the node is a hybrid node (with 2 or more parents)
  or a tree node (with a single parent)

Other more internal attributes include:

- `isBadDiamondI` and `isBadDiamondII` (booleans): whether the node is a
  hybrid node where the reticulation forms a cycle of 4 nodes (diamond),
  and where both parents of the hybrid nodes are connected to a leaf.
  In a bad diamond of type I, the hybrid node itself is also connected
  to a leaf but the common neighbor of the 2 hybrid's parents is not connected
  to a leaf.
  In a bad diamond of type II, the hybrid node has an internal node as child,
  and the common neighbor of the 2 hybrid's parents is connected to a leaf.
- `isBadTriangle`, `isVeryBadTriangle` and `isExtBadTriangle` (booleans):
  true if the reticulation forms a cycle of 3 nodes (triangle) and
  depending on the number of leaves attached these 3 nodes. The triangle means
  that the 2 parents of the hybrid node are directly related:
  one is the child of the other. `isBadTriangle` is true if the triangle is
  "good", as per Solís-Lemus & Ané (2016), that is, if all 3 nodes in the cycle
  are not connected to any leaves (the reticulation is detectable from quartet
  concordance factors, even though all branch lengths are not identifiable).
  `isVeryBadTriangle` is true if 2 (or all) of the 3 nodes are connected to a
  leaf, in which case the reticulation is undetectable from unrooted gene tree
  topologies (thus it's best to exclude these reticulations from a search).
  `isBadTriangle` is true if exactly 1 of the 3 nodes is connected to a leaf.

For details see Solís-Lemus & Ané (2016, doi:10.1371/journal.pgen.1005896)
"""
mutable struct Node <: ANode
    number::Int
    leaf::Bool
    hybrid::Bool
    gammaz::Float64  # notes file for explanation. gammaz if tree node, gamma2z if hybrid node.
                     # updated after node is part of network with updateGammaz!
    edge::Array{Edge,1}
    hasHybEdge::Bool #is there a hybrid edge in edge? only needed when hybrid=false (tree node)
    isBadDiamondI::Bool # for hybrid node, is it bad diamond case I, update in updateGammaz!
    isBadDiamondII::Bool # for hybrid node, is it bad diamond case II, update in updateGammaz!
    isExtBadTriangle::Bool # for hybrid node, is it extremely bad triangle, udpate in updateGammaz!
    isVeryBadTriangle::Bool # for hybrid node, is it very bad triangle, udpate in updateGammaz!
    isBadTriangle::Bool # for hybrid node, is it very bad triangle, udpate in updateGammaz!
    inCycle::Int # = hybrid node if this node is part of a cycle created by such hybrid node, -1 if not part of cycle
    prev::Union{Nothing,ANode} # previous node in cycle, used in updateInCycle. set to "nothing" to begin with
    k::Int # num nodes in cycle, only stored in hybrid node, updated after node becomes part of network
           # default -1
    typeHyb::Int8 # type of hybridization (1,2,3,4, or 5), needed for quartet network only. default -1
    name::AbstractString
    clade::Vector{Int64} #Set to [1] initially to indicate that all start in main clade. outgroup added with addclades!
    # inner constructor: set hasHybEdge depending on edge
    Node() = new(-1,false,false,-1.,Edge[],false,false,false,false,false,false,-1,nothing,-1,-1,"",[1])
    Node(number::Int, leaf::Bool) = new(number,leaf,false,-1.,[],false,false,false,false,false,false,-1.,nothing,-1,-1,"",[1])
    Node(number::Int, leaf::Bool, hybrid::Bool) = new(number,leaf,hybrid,-1.,[],hybrid,false,false,false,false,false,-1.,nothing,-1,-1,"", [1])
    Node(number::Int, leaf::Bool, hybrid::Bool, edge::Array{Edge,1})=new(number,leaf,hybrid,-1.,edge,!all((e->!e.hybrid),edge),false,false,false,false,false,-1.,nothing,-1,-1,"", [1])
    Node(number::Int, leaf::Bool, hybrid::Bool,gammaz::Float64, edge::Array{Edge,1}) = new(number,leaf,hybrid,gammaz,edge,!all((e->!e.hybrid), edge),false,false,false,false,false,-1.,nothing,-1,-1,"", [1])
end

# partition type
mutable struct Partition
    cycle::Vector{Int} #hybrid node number for cycle (or cycles)
    edges::Vector{Edge} #edges in partition
end

abstract type Network end

# warning: no attempt to make sure the direction of edges matches with the root
# warning: no check if it is network or tree, node array can have no hybrids
# warning: nodes and edges need to be defined and linked before adding to a network
"""
    HybridNetwork

Subtype of abstract `Network` type.
Explicit network or tree with the following attributes:

- numTaxa
- numNodes (total number of nodes)
- numEdges
- numHybrids (number of hybrid nodes)
- edge (array of Edges)
- node (array of Nodes)
- root (index of root in vector 'node'. May be artificial, for printing and traversal purposes only.)
- hybrid (array of Nodes: those are are hybrid nodes)
- leaf (array of Nodes: those that are leaves)
- loglik (negative log pseudolik after estimation)
- isRooted (true or false)
"""
mutable struct HybridNetwork <: Network
    numTaxa::Int  # cannot set in constructor for congruence
    numNodes::Int
    numEdges::Int
    node::Array{Node,1}
    edge::Array{Edge,1}
    root::Int # node[root] is the root node, default 1
    names::Array{String,1} # translate table for taxon names --but also includes hybrid names...
    hybrid::Array{Node,1} # array of hybrid nodes in network
    numHybrids::Int # number of hybrid nodes
    cladewiseorder_nodeIndex::Vector{Int} # index in 'node' for "cladewise" preorder in main tree
    visited::Array{Bool,1} # reusable array of booleans
    edges_changed::Array{Edge,1} # reusable array of edges
    nodes_changed::Array{Node,1} # reusable array of nodes. used for preorder traversal
    leaf::Array{Node,1} # array of leaves
    ht::Vector{Float64} # vector of parameters to optimize
    numht::Vector{Int} # vector of number of the hybrid nodes and edges in ht e.g. [3,6,8,...], 2 hybrid nodes 3,6, and edge 8 is the 1st identifiable
    numBad::Int # number of bad diamond I hybrid nodes, set as 0
    hasVeryBadTriangle::Bool # true if the network has extremely/very bad triangles that should be ignored
    index::Vector{Int} #index in net.edge, net.node of elements in net.ht to make updating easy
    loglik::Float64 # value of the min -loglik after optBL
    blacklist::Vector{Int} # reusable array of integers, used in afterOptBL
    partition::Vector{Partition} # to choose edges from a partition only to avoid intersecting cycles
    cleaned::Bool # attribute to know if the network has been cleaned after readm default false
    isRooted::Bool # to know if network is rooted, e.g. after directEdges! (which updates isChild1 of each edge)
    # inner constructor
    function HybridNetwork(node::Array{Node,1},edge::Array{Edge,1})
        hybrid=Node[];
        leaf=Node[];
        for n in node
            if n.hybrid push!(hybrid,n); end
            if n.leaf   push!(leaf,  n); end
        end
        new(size(leaf,1),size(node,1),size(edge,1),node,edge,1,[],hybrid,size(hybrid,1), #numTaxa,...,numHybrids
            [],[],[],[],leaf,[],[], #cladewiseorder,...,numht
            0,false,[],0,[],[],false,false) #numBad...
    end
    function HybridNetwork(node::Array{Node,1},edge::Array{Edge,1},root::Int)
        hybrid=Node[];
        leaf=Node[];
        for n in node
            if n.hybrid push!(hybrid,n); end
            if n.leaf   push!(leaf,  n); end
        end
        new(size(leaf,1),size(node,1),size(edge,1),node,edge,root,[],hybrid,size(hybrid,1), #numTaxa,...,numHybrids
            [],[],[],[],leaf,[],[], #cladewiseorder,...,numht
            0,false,[],0,[],[],false,false) #numBad...
    end
    HybridNetwork() = new(0,0,0,[],[],0,[],[],0, # numTaxa ... numHybrid
                          [],[],[],[],[],[],[], # cladewiseorder...
                          0,false,[],0,[],[],false,false); # numBad ...
end

# type created from a HybridNetwork only to extract a given quartet
"""
    QuartetNetwork(net::HybridNetwork)

Subtype of `Network` abstract type.
need documentation!
"""
mutable struct QuartetNetwork <: Network
    numTaxa::Int
    numNodes::Int
    numEdges::Int
    node::Array{Node,1}
    edge::Array{Edge,1}
    hybrid::Array{Node,1} # array of hybrid nodes in network
    leaf::Array{Node,1} # array of leaves
    numHybrids::Int # number of hybrid nodes
    hasEdge::Array{Bool,1} # array of boolean with all the original identifiable edges of HybridNetwork and gammas (net.ht)
    quartetTaxon::Array{String,1} # the quartet taxa in the order it represents. Points to same array as its Quartet.taxon
    which::Int8 # 0 it tree quartet, 1 is equivalent to tree quartet and 2 if two minor CF different, default -1
    typeHyb::Array{Int8,1} #array with the type of hybridization of each hybrid node in the quartet
    t1::Float64 # length of internal edge, used when qnet.which=1, default = -1
    names::Array{String,1} # taxon and node names, same order as in network.node
    split::Array{Int8,1} # split that denotes to which side each leaf is from the split, i.e. [1,2,2,1] means that leaf1 and 4 are on the same side of the split, default -1,-1,-1,-1
    formula::Array{Int8,1} # array for qnet.which=1 that indicates if the expCf is major (1) or minor (2) at qnet.expCF[i] depending on qnet.formula[i], default -1,-1,-1
    expCF::Array{Float64,1} # three expected CF in order 12|34, 13|24, 14|23 (matching obsCF from qnet.quartet), default [0,0,0]
    indexht::Vector{Int} # index in net.ht for each edge in qnet.ht
    changed::Bool # true if the expCF would be changed with the current parameters in the optimization, to recalculate, default true
    index::Vector{Int} # index in qnet.edge (qnet.node for gammaz) of the members in qnet.indexht to know how to find quickly in qnet
    # inner constructor
    function QuartetNetwork(net::HybridNetwork)
        net2 = deepcopy(net); #fixit: maybe we dont need deepcopy of all, maybe only arrays
        new(net2.numTaxa,net2.numNodes,net2.numEdges,net2.node,net2.edge,net2.hybrid,net2.leaf,net2.numHybrids, [true for e in net2.edge],[],-1,[], -1.,net2.names,Int8[-1,-1,-1,-1],Int8[-1,-1,-1],[0,0,0],[],true,[])
        #new(sum([n.leaf?1:0 for n in net.node]),size(net.node,1),size(net.edge,1),copy(net.node),copy(net.edge),copy(net.hybrid),size(net.hybrid,1), [true for e in net2.edge],[],-1,[],-1.,net2.names,[-1,-1,-1,-1],[-1,-1,-1],[],true,[])
    end
    function QuartetNetwork(net::HybridNetwork,quartet::Array{String,1})
        net2 = deepcopy(net);
        new(net2.numTaxa,net2.numNodes,net2.numEdges,net2.node,net2.edge,net2.hybrid,net2.leaf,net2.numHybrids, [true for e in net2.edge],quartet,-1,[],-1.,net2.names,Int8[-1,-1,-1,-1],Int8[-1,-1,-1],[0,0,0],[],true,[])
    end
    QuartetNetwork() = new(0,0,0,[],[],[],[],0,[],[],-1,[],-1.0,[],[],[],[],[],true,[])
end

abstract type AQuartet end

"""
    Quartet

type that saves the information on a given 4-taxon subset. It contains the following attributes:

- number: integer
- taxon: vector of taxon names, like t1 t2 t3 t4
- obsCF: vector of observed CF, in order 12|34, 13|24, 14|23
- logPseudoLik
- ngenes: number of gene trees used to compute the observed CF; -1.0 if unknown
- qnet: [`QuartetNetwork`](@ref), which saves the expCF after snaq estimation to
  emphasize that the expCF depend on a specific network, not the data

see also: [`QuartetT`](@ref) for quartet with data of user-defined type `T`,
using a mapping between quartet indices and quartet taxa.
"""
mutable struct Quartet <: AQuartet
    number::Int
    taxon::Array{String,1} # taxa 1234. qnet.quartetTaxon points to the same array.
    obsCF::Array{Float64,1} # three observed CF in order 12|34, 13|24, 14|23
    qnet::QuartetNetwork # quartet network for the current network (want to keep as if private attribute)
    logPseudoLik::Float64 # log pseudolik value for the quartet. 0.0 by default
    ngenes::Float64 # number of gene trees used to compute the obsCV, default -1.; Float in case ngenes is average
    # inner constructor: to guarantee obsCF are only three and add up to 1
    function Quartet(number::Integer,t1::AbstractString,t2::AbstractString,t3::AbstractString,t4::AbstractString,obsCF::Array{Float64,1})
        size(obsCF,1) != 3 ? error("observed CF vector should have size 3, not $(size(obsCF,1))") : nothing
        0.99 < sum(obsCF) < 1.02 || @warn "observed CF should add up to 1, not $(sum(obsCF))"
        new(number,[t1,t2,t3,t4],obsCF,QuartetNetwork(),0.0,-1.0);
    end
    function Quartet(number::Integer,t1::Array{String,1},obsCF::Array{Float64,1})
        size(obsCF,1) != 3 ? error("observed CF vector should have size 3, not $(size(obsCF,1))") : nothing
        0.99< sum(obsCF) < 1.02 || @warn "observed CF should add up to 1, not $(sum(obsCF))"
        size(t1,1) != 4 ? error("array of taxa should have size 4, not $(size(t1,1))") : nothing
        0.0 <= obsCF[1] <= 1.0 || error("obsCF must be between (0,1), but it is $(obsCF[1]) for $(t1)")
        0.0 <= obsCF[2] <= 1.0 || error("obsCF must be between (0,1), but it is $(obsCF[2]) for $(t1)")
        0.0 <= obsCF[3] <= 1.0 || error("obsCF must be between (0,1), but it is $(obsCF[3]) for $(t1)")
        new(number,t1,obsCF,QuartetNetwork(),0.0,-1.0);
    end
    Quartet() = new(0,[],[],QuartetNetwork(),0.0,-1.0)
end

"""
QuartetT{T}

Generic type for 4-taxon sets. Fields:
- `number`: rank of the 4-taxon set
- `taxonnumber`: static vector of 4 integers, assumed to be distinct and sorted
- `data`: object of type `T`

For easier look-up, a unique mapping is used between the rank (`number`) of a
4-taxon set and its 4 taxa (see [`quartetrank`](@ref) and [`nchoose1234`](@ref)):

rank-1 = (t1-1) choose 1 + (t2-1) choose 2 + (t3-1) choose 3 + (t4-1) choose 4

# examples

```jldoctest
julia> nCk = PhyloNetworks.nchoose1234(5)
6×4 Array{Int64,2}:
 0   0   0  0
 1   0   0  0
 2   1   0  0
 3   3   1  0
 4   6   4  1
 5  10  10  5

julia> PhyloNetworks.QuartetT(1,3,4,6, [.92,.04,.04, 100], nCk)
4-taxon set number 8; taxon numbers: 1,3,4,6
data: [0.92, 0.04, 0.04, 100.0]
```
"""
struct QuartetT{T} <: AQuartet where T
    number::Int
    taxonnumber::StaticArrays.SVector{4,Int}
    data::T
end
function Base.show(io::IO, obj::QuartetT{T}) where T
    disp = "4-taxon set number $(obj.number); taxon numbers: "
    disp *= join(obj.taxonnumber,",")
    disp *= "\ndata: "
    print(io, disp)
    print(io, obj.data)
end
function QuartetT(tn1::Int,tn2::Int,tn3::Int,tn4::Int, data::T, nCk::Matrix, checksorted=true::Bool) where T
    if checksorted
        (tn1<tn2 && tn2<tn3 && tn3<tn4) || error("taxon numbers must be sorted")
    end
    QuartetT{T}(quartetrank(tn1,tn2,tn3,tn4,nCk), SVector(tn1,tn2,tn3,tn4), data)
end

"""
    quartetrank(t1,t2,t3,t4, nCk::Matrix)
    quartetrank([t1,t2,t3,t4], nCk)

Return the rank of a four-taxon set with taxon numbers `t1,t2,t3,t4`,
assuming that `ti`s are positive integers such that t1<t2, t2<t3 and t3<t4
(assumptions not checked!).
`nCk` should be a matrix of "n choose k" binomial coefficients:
see [`nchoose1234`](@ref).

# examples

```jldoctest
julia> nCk = PhyloNetworks.nchoose1234(5)
6×4 Array{Int64,2}:
 0   0   0  0
 1   0   0  0
 2   1   0  0
 3   3   1  0
 4   6   4  1
 5  10  10  5

julia> PhyloNetworks.quartetrank([1,2,3,4], nCk)
1

julia> PhyloNetworks.quartetrank([3,4,5,6], nCk)
15
```
"""
@inline function quartetrank(tnum::AbstractVector, nCk::Matrix)
    quartetrank(tnum..., nCk)
end
@inline function quartetrank(t1::Int, t2::Int, t3::Int, t4::Int, nCk::Matrix)
    # rank-1 = t1-1 choose 1 + t2-1 choose 2 + t3-1 choose 3 + t4-1 choose 4
    return nCk[t1,1] + nCk[t2,2] + nCk[t3,3] + nCk[t4,4] + 1
end

"""
    nchoose1234(nmax)

`nmax+1 x 4` matrix containing the binomial coefficient
"n choose k" in row `n+1` and column `k`. In other words,
`M[i,k]` gives "i-1 choose k". It is useful to store these
values and look them up to rank (a large number of) 4-taxon sets:
see [`quartetrank`](@ref).
"""
function nchoose1234(nmax::Int)
    # compute nC1, nC2, nC3, nC4 for n in [0, nmax]: used for ranking quartets
    M = Matrix{Int}(undef, nmax+1, 4)
    for i in 1:(nmax+1)
        M[i,1] = i-1 # n choose 1 = n. row i is for n=i-1
    end
    M[1,2:4] .= 0 # 0 choose 2,3,4 = 0
    for i in 2:(nmax+1)
        for k in 2:4 # to choose k items in 1..n: the largest could be n, else <= n-1
            M[i,k] = M[i-1,k-1] + M[i-1,k]
        end
    end
    return M
end

# Data on quartet concordance factors -------

"""
    DataCF

type that contains the following attributes:

- quartet (vector of Quartets)
- numQuartets
- tree (vector of trees: empty if a table of CF was input instead of list of trees)
- numTrees (-1 if a table CF was input instead of list of trees)
- repSpecies (taxon names that were repeated in table of CF or input gene trees: used inside snaq for multiple alleles case)

The list of Quartet may be accessed with the attribute .quartet.
If the input was a list of trees, the HybridNetwork's can be accessed with the attribute .tree.
For example, if the DataCF object is named d, d.quartet[1] will show the first quartet
and d.tree[1] will print the first input tree.
"""
mutable struct DataCF # fixit
    quartet::Array{Quartet,1} # array of quartets read from CF output table or list of quartets in file
    numQuartets::Integer # number of quartets
    tree::Vector{HybridNetwork} #array of input gene trees
    numTrees::Integer # number of gene trees
    repSpecies::Vector{String} #repeated species in the case of multiple alleles
    DataCF(quartet::Array{Quartet,1}) = new(quartet,length(quartet),[],-1,[])
    DataCF(quartet::Array{Quartet,1},trees::Vector{HybridNetwork}) = new(quartet,length(quartet),trees,length(trees),[])
    DataCF() = new([],0,[],-1,[])
end

# aux type for the updateBL function
mutable struct EdgeParts
    edgenum::Int
    part1::Vector{Node}
    part2::Vector{Node}
    part3::Vector{Node}
    part4::Vector{Node}
end

struct RootMismatch <: Exception
    msg::String
end
RootMismatch() = RootMismatch("")
Base.showerror(io::IO, e::RootMismatch) = print(io, "RootMismatch: ", e.msg);


function GlobalPressureTemperatureModel(arg0::jdouble, arg1::jdouble, arg2::Frame)
    return GlobalPressureTemperatureModel((jdouble, jdouble, Frame), arg0, arg1, arg2)
end

function GlobalPressureTemperatureModel(arg0::jdouble, arg1::jdouble, arg2::Frame, arg3::DataContext)
    return GlobalPressureTemperatureModel((jdouble, jdouble, Frame, DataContext), arg0, arg1, arg2, arg3)
end

function get_pressure(obj::GlobalPressureTemperatureModel)
    return jcall(obj, "getPressure", jdouble, ())
end

function get_temperature(obj::GlobalPressureTemperatureModel)
    return jcall(obj, "getTemperature", jdouble, ())
end

function weather_parameters(obj::GlobalPressureTemperatureModel, arg0::jdouble, arg1::AbsoluteDate)
    return jcall(obj, "weatherParameters", void, (jdouble, AbsoluteDate), arg0, arg1)
end


module TabularTMazeExperiment

import Flux: Descent, ADAM
import Random

using GVFHordes
using Curiosity
using MinimalRLCore
using SparseArrays
using ProgressMeter

const TTMU = Curiosity.TabularTMazeUtils
const BU = Curiosity.BaselineUtils
const SRCU = Curiosity.SRCreationUtils

default_args() =
    Dict(
        # Behaviour Items
        # "behaviour_eta" => 0.50,
        "behaviour_gamma" => 0.9,
        "behaviour_learner" => "Q",
        "behaviour_update" => "TabularRoundRobin",
        "behaviour_trace" => "AccumulatingTraces",
        "behaviour_opt" => "Auto",
        "behaviour_lambda" => 0.9,
        "behaviour_alpha_init" => 0.1,
        "exploration_param" => 0.1,
        "exploration_strategy" => "epsilon_greedy",
        "ϵ_range" => (0.4,0.1),
        "decay_period" => 1000,
        "warmup_steps" => 100,
        "behaviour_w_init" => 10.0,

        # Demon Attributes
        "demon_alpha_init" => 0.5,
        # "demon_eta" => 0.25,
        "demon_discounts" => 0.9,
        "demon_learner" => "SR",
        "demon_update" => "TB",
        "demon_interest_set" => "ttmaze",
        "demon_policy_type" => "greedy_to_cumulant",
        "demon_opt" => "Auto",
        "demon_lambda" => 0.9,
        "demon_trace"=> "AccumulatingTraces",
        "demon_beta_m" => 0.9,
        "demon_beta_v" => 0.99,

        #Shared Demon and Behaviour Attributes
        "eta" =>0.2,

        # Environment Config
        "constant_target"=> (-10,10),
        "cumulant_schedule" => "DrifterDistractor",
        "distractor" => (1.0, 5.0),
        "drifter" => (1.0, sqrt(0.01)),
        "exploring_starts" => true,
        "env_step_penalty" => 0.0,

        # Agent and Logger
        "horde_type" => "regular",
        "intrinsic_reward" => "weight_change",
        "logger_keys" => [LoggerKey.TTMAZE_ERROR, LoggerKey.TTMAZE_UNIFORM_ERROR,
                            LoggerKey.TTMAZE_OLD_ERROR, LoggerKey.TTMAZE_ROUNDROBIN_ERROR, LoggerKey.GOAL_VISITATION,
                            LoggerKey.EPISODE_LENGTH,
                            LoggerKey.TTMAZE_DIRECT_ERROR],
        "save_dir" => "TabularTMazeExperiment",
        "seed" => 1,
        "steps" => 5000,
        "use_external_reward" => true,
        "logger_interval" => 100,
        "random_first_action" => true,

    )

function construct_agent(parsed)

    feature_size = 21
    action_space = 4
    observation_size = 5
    demon_alpha_init = parsed["demon_alpha_init"]
    demon_learner = parsed["demon_learner"]
    demon_lu = parsed["demon_update"]

    behaviour_learner = parsed["behaviour_learner"]
    behaviour_lu = parsed["behaviour_update"]
    behaviour_discount = parsed["behaviour_gamma"]

    intrinsic_reward_type = parsed["intrinsic_reward"]
    behaviour_trace = parsed["behaviour_trace"]
    use_external_reward = parsed["use_external_reward"]

    #Create state constructor
    function state_constructor(observation, feature_size)
        s = spzeros(feature_size)
        s[convert(Int64,observation[1])] = 1
        return s
    end
    state_constructor_func = (obs) -> state_constructor(obs, feature_size)

    function compressed_state_constructor(obs)
        s = spzeros(4)
        if obs[1] < 5
            s[1] = 1
        elseif obs[1] < 8
            s[2] = 1
        elseif obs[1] < 15
            s[3] = 1
        elseif obs[1] < 17
            s[2] = 1
        else
            s[4] = 1
        end
        return s
    end

    behaviour_feature_projector = Curiosity.FeatureSubset(TTMU.StateActionFeatures(feature_size,action_space),1:1)
    demon_feature_projector = behaviour_feature_projector

    demons = get_horde(parsed, length(demon_feature_projector), action_space, demon_feature_projector)

    exploration_strategy = Curiosity.get_exploration_strategy(parsed, 1:action_space)

    demon_learner = Curiosity.get_linear_learner(parsed,
                                                 feature_size,
                                                 action_space,
                                                 demons,
                                                 "demon",
                                                 demon_feature_projector)

    # TODO: Behaviour horde needs access to the behaviour learner to condition the behaviour policy
    # BUT behaviour learner needs access the horde to know things like how many demons there are.
    behaviour_num_tasks = 1
    num_SFs = 4
    num_demons = if parsed["behaviour_learner"] ∈ ["GPI"]
        num_SFs * feature_size * action_space + 1
    elseif parsed["behaviour_learner"] ∈ ["Q", "FollowDemon", "RandomDemons"]
        1
    else
        println("Invalid behaviour learner. Num demons is not defined")
    end

    behaviour_learner = if parsed["behaviour_learner"] == "FollowDemon"
        demon_i = 3
        action_set = 1:action_space
        drifter_demon = GVF(GVFParamFuncs.FeatureCumulant(demon_i+1),
                            GVFParamFuncs.StateTerminationDiscount(parsed["demon_discounts"], TTMU.pseudoterm),
                            GVFParamFuncs.FunctionalPolicy((;kwargs...) -> TTMU.demon_target_policy(demon_i;kwargs...)))
        BU.FollowDemon(drifter_demon,action_set)
    elseif parsed["behaviour_learner"] == "RandomDemons"
        action_set = 1:action_space
        BU.RandomDemons(demons, action_set)
    else
            Curiosity.get_linear_learner(parsed,
                        feature_size,
                        action_space,
                        num_demons,
                        behaviour_num_tasks,
                        "behaviour",
                        behaviour_feature_projector)
    end

    behaviour_gvf = if behaviour_learner isa GPI
        #behaviour discount for immediate reward predictor for GPI should always be 0.
        TTMU.make_behaviour_gvf(0.0, state_constructor_func, behaviour_learner, exploration_strategy)
    elseif behaviour_learner isa QLearner
        TTMU.make_behaviour_gvf(behaviour_discount, state_constructor_func, behaviour_learner, exploration_strategy)
    elseif behaviour_learner isa BU.FollowDemon || behaviour_learner isa BU.RandomDemons
        TTMU.make_behaviour_gvf(behaviour_discount, state_constructor_func, behaviour_learner, exploration_strategy)
    else
        throw(ArgumentError("What other type of behaviour learner??"))
    end
    behaviour_demons = if behaviour_learner isa GPI
        SF_discounts = [GVFParamFuncs.StateTerminationDiscount(behaviour_discount, TTMU.pseudoterm) for i in 1:4]
        SF_policies = [GVFParamFuncs.FunctionalPolicy((;kwargs...) ->TTMU.demon_target_policy(i;kwargs...)) for i in 1:4]
        SF_horde = SRCU.create_SF_horde(SF_policies,SF_discounts,behaviour_feature_projector,1:4)

        pred_horde = Horde([behaviour_gvf])

        Curiosity.GVFSRHordes.SRHorde(pred_horde, SF_horde, num_SFs, behaviour_feature_projector)
    elseif behaviour_learner isa QLearner
        Horde([behaviour_gvf])
    elseif behaviour_learner isa BU.FollowDemon || behaviour_learner isa BU.RandomDemons
        Horde([behaviour_gvf])
    else
        throw(ArgumentError("goes with which horde? " ))
    end

    random_first_action = parsed["random_first_action"]
    if behaviour_learner isa TabularRoundRobin
        if random_first_action == false
            @warn "Round Robin with random first action is recommended"
        end
    elseif random_first_action == true
        @warn "Random first action is enabled"
    end

    Agent(demons,
          feature_size,
          behaviour_learner,
          behaviour_demons,
          behaviour_discount,
          demon_learner,
          observation_size,
          action_space,
          intrinsic_reward_type,
          (obs) -> state_constructor(obs, feature_size),
          use_external_reward,
          exploration_strategy,
          random_first_action)
end

function get_horde(parsed, feature_size, action_space, projected_feature_constructor)

    discount = parsed["demon_discounts"]
    pseudoterm = TTMU.pseudoterm
    num_actions = TTMU.NUM_ACTIONS
    num_demons = TTMU.NUM_DEMONS

    #TODO: Sort out the if-else block so that demon_policy_type and horde_type is not blocking eachother.
    horde = if parsed["demon_policy_type"] == "greedy_to_cumulant" && parsed["demon_learner"] != "SR"
        Horde([GVF(GVFParamFuncs.FeatureCumulant(i+1), GVFParamFuncs.StateTerminationDiscount(discount, pseudoterm), GVFParamFuncs.FunctionalPolicy((;kwargs...) -> TTMU.demon_target_policy(i;kwargs...))) for i in 1:num_demons])
    elseif parsed["demon_policy_type"] == "greedy_to_cumulant" && parsed["demon_learner"] == "SR"
        Horde([GVF(GVFParamFuncs.FeatureCumulant(i+1), GVFParamFuncs.ConstantDiscount(0.0), GVFParamFuncs.FunctionalPolicy((;kwargs...) -> TTMU.demon_target_policy(i;kwargs...))) for i in 1:num_demons])
    elseif parsed["demon_policy_type"] == "random"
        Horde([GVF(GVFParamFuncs.FeatureCumulant(i+1), GVFParamFuncs.StateTerminationDiscount(discount, pseudoterm), GVFParamFuncs.RandomPolicy(fill(1/num_actions,num_actions))) for i in 1:num_demons])
    else
        throw(ArgumentError("Not a valid policy type for demons"))
    end

    if parsed["demon_learner"] == "SR"
        num_SFs = 4
        SF_discounts = [GVFParamFuncs.StateTerminationDiscount(discount, TTMU.pseudoterm) for i in 1:4]
        SF_policies = [GVFParamFuncs.FunctionalPolicy((;kwargs...) ->TTMU.demon_target_policy(i;kwargs...)) for i in 1:4]
        SF_horde = SRCU.create_SF_horde(SF_policies,SF_discounts,projected_feature_constructor,1:4)

        horde = Curiosity.GVFSRHordes.SRHorde(horde, SF_horde, num_SFs, projected_feature_constructor)
    end

    return horde
end

function main_experiment(parsed=default_args(); progress=false, working=false)

    num_steps = parsed["steps"]
    Random.seed!(parsed["seed"])

    cumulant_schedule = TTMU.get_cumulant_schedule(parsed)
    exploring_starts = parsed["exploring_starts"]
    env = TabularTMaze(exploring_starts, cumulant_schedule)

    if "eta" in keys(parsed)
        prefixes = ["behaviour","demon"]
        for prefix in prefixes
            parsed[join([prefix, "eta"], "_")] = parsed["eta"]
        end
    end
    if "alpha_init" in keys(parsed)
        prefixes = ["behaviour","demon"]
        for prefix in prefixes
            parsed[join([prefix, "alpha_init"], "_")] = parsed["alpha_init"]
        end
    end

    agent = construct_agent(parsed)

    goal_visitations = zeros(4)

    logger_init_dict = Dict(
        LoggerInitKey.TOTAL_STEPS => num_steps,
        LoggerInitKey.INTERVAL => parsed["logger_interval"],
        LoggerInitKey.ENV => "tabular_tmaze"
    )

    Curiosity.experiment_wrapper(parsed, logger_init_dict, working) do parsed, logger
        eps = 1
        max_num_steps = num_steps
        steps = Int[]

        if progress
            p = Progress(max_num_steps)
        end

        logger_start!(logger, env, agent)
        while sum(steps) < max_num_steps
            cur_step = 0
            max_episode_steps = max_num_steps - sum(steps)
            tr, stp =
                run_episode!(env, agent, max_episode_steps) do (s, a, s_next, r, t)
                    #This is a callback for every timestep where logger can go
                    # agent is accesible in this scope

                    if t == true && working==true && t
                        goals = s_next[2:end]
                        f = findfirst(!iszero, goals)
                        goal_visitations[f] += 1
                    end

                    logger_step!(logger, env, agent, s, a, s_next, r, t)
                    cur_step+=1
                end
                logger_episode_end!(logger)
            push!(steps, stp)
            eps += 1

            if progress
                ProgressMeter.update!(p, sum(steps))
            end
        end
        if working == true
            println(goal_visitations)
        end
    end

end

end


using MultiFloats: Float64x3
using StaticArrays: SVector
using UUIDs: UUID

push!(LOAD_PATH, @__DIR__)
using PCREO
using PointGroups

@assert isdir(ENV["PCREO_DATABASE_DIRECTORY"])

lsdir(path...) = filter(!startswith('.'),
    readdir(joinpath(path...); sort=false))

function build_pcreo_cache(n_min::Int, n_max::Int)
    result = Dict{Int,Vector{Tuple{Float64x3,UUID}}}()
    println("Building PCREO cache...")
    flush(stdout)
    for num_dir in lsdir(ENV["PCREO_DATABASE_DIRECTORY"])
        num_points = parse(Int, num_dir)
        @assert !haskey(result, num_points)
        if n_min <= num_points <= n_max
            println("Scanning directory $num_dir...")
            flush(stdout)
            entry_list = Tuple{Float64x3,UUID}[]
            for entry_name in lsdir(ENV["PCREO_DATABASE_DIRECTORY"], num_dir)
                reference_name = "PCREO-03-$num_dir-$entry_name.csv"
                reference = PCREORecord(joinpath(
                    ENV["PCREO_DATABASE_DIRECTORY"], num_dir,
                    entry_name, reference_name))
                push!(entry_list, (reference.energy, UUID(entry_name)))
            end
            sort!(entry_list)
            result[num_points] = entry_list
        end
    end
    println("Finished building PCREO cache.")
    flush(stdout)
    return result
end

const PCREO_CACHE = build_pcreo_cache(parse(Int, ARGS[1]), parse(Int, ARGS[2]))

const UUID_REGEX = Regex("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-" *
                         "[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")

function to_point_vector(points::AbstractMatrix{T}) where {T}
    dimension, num_points = size(points)
    @assert dimension == 3
    return [SVector{3,T}(view(points, :, i)) for i = 1 : num_points]
end

function add_to_database!(filepath::String)
    filename = basename(filepath)
    uuid = UUID(match(UUID_REGEX, filename).match)
    record = PCREORecord(filepath)
    dim_str = lpad(record.dimension, 2, '0')
    num_str = lpad(record.num_points, 8, '0')
    num_dir = joinpath(ENV["PCREO_DATABASE_DIRECTORY"], num_str)
    if !isdir(num_dir)
        mkdir(num_dir)
    end
    if !haskey(PCREO_CACHE, record.num_points)
        PCREO_CACHE[record.num_points] = Tuple{Float64x3,UUID}[]
    end
    for (reference_energy, reference_uuid) in PCREO_CACHE[record.num_points]
        if isapprox(record.energy, reference_energy, rtol=Float64x3(2^-80))
            entry_dir = joinpath(num_dir, string(reference_uuid))
            reference = PCREORecord(joinpath(entry_dir,
                "PCREO-$dim_str-$num_str-$reference_uuid.csv"))
            @assert reference.energy == reference_energy
            if isometric(to_point_vector(record.points),
                         to_point_vector(reference.points), 2^-40)
                newpath = joinpath(entry_dir, filename)
                mv(filepath, newpath)
                return newpath
            end
        end
    end
    push!(PCREO_CACHE[record.num_points], (record.energy, uuid))
    sort!(PCREO_CACHE[record.num_points])
    new_dir = joinpath(num_dir, string(uuid))
    mkdir(new_dir)
    newpath = joinpath(new_dir, filename)
    mv(filepath, newpath)
    return newpath
end

function in_range(n_min, n_max)
    return filename -> n_min <= parse(Int, filename[10:17]) <= n_max
end

function main(n_min::Int, n_max::Int)
    filenames = filter(in_range(n_min, n_max),
                filter(startswith("PCREO-03-"),
                readdir("/Users/dzhang314/pcreo-output-rice")))
    for filename in filenames
        src = joinpath("/Users/dzhang314/pcreo-output-rice", filename)
        try
            dst = add_to_database!(src)
            println(src, " => ", dst)
            flush(stdout)
        catch e
            if e isa AssertionError
                println(filename, ": ", e)
                flush(stdout)
            else
                rethrow(e)
            end
        end
    end
end

main(parse(Int, ARGS[1]), parse(Int, ARGS[2]))


# * Solver trace columns
# ** Energy

mutable struct EnergyColumn{T<:AbstractFloat} <: TraceColumn
    E::T
    fmt::FormatExpr
    header::String
    eV::Bool
end

function EnergyColumn(E::T, eV::Bool=true, header="Energy") where T
    fmt = if eV
        FormatExpr("{1:+10.5f} Ha = {2:+10.5f} eV")
    else
        FormatExpr("{1:+10.5f} Ha")
    end
    EnergyColumn(E, fmt,
                 rpad(header, 1+length(eV ? format(fmt, E, E) : format(fmt, E))), eV)
end

(e::EnergyColumn)(::Integer) =
    e.eV ? (e.E,27.211e.E) : (e.E,)

# ** Virial theorem

mutable struct VirialColumn{T<:AbstractFloat} <: TraceColumn
    V::T
    fmt::FormatExpr
    lc::LinearColorant{T}
    header::String
end

function VirialColumn(V::T) where T
    fmt = FormatExpr("{1:.5f} ({2:s}{3:.2f}×10{4:3s}$(crayon"reset"))")
    lc = LinearColorant(one(T),zero(T),SolverTraces.red_green_scale())
    VirialColumn(V, fmt, lc,
                 rpad("⟨V̂⟩/⟨T̂⟩ ⟨V̂⟩/⟨T̂⟩ + 2", 1+length(format(fmt, V, "", base_exp(abs(V + 2.0))...))))
end

function (v::VirialColumn{T})(::Integer) where T
    Vb,Ve = base_exp(abs(v.V + 2.0))
    (v.V, v.lc(100abs.(log10(2.0)-log10(abs(v.V)))/log10(2.0)),
     Vb,to_superscript(Ve))
end

# ** Relaxation

mutable struct RelaxationColumn{T<:AbstractFloat} <: TraceColumn
    ω::T
    fmt::FormatExpr
    header::String
end

function RelaxationColumn(ω::T) where T
    header = "1-ω"
    fmt = FormatExpr("{1:.2f}×10{2:3s}")
    RelaxationColumn(ω, fmt,
                     rpad(header, 1+length(format(fmt, base_exp(abs(1.0 - ω))...))))
end

function (r::RelaxationColumn{T})(::Integer) where T
    ωb,ωe = base_exp(abs(1.0 - r.ω))
    (ωb,to_superscript(ωe))
end

# ** Flags

mutable struct FlagsColumn <: TraceColumn
    val::Vector{String}
    fmt::FormatExpr
    header::String
end

function FlagsColumn()
    header = "Flags"
    FlagsColumn(String[], FormatExpr("{1:5s}"), "Flags")
end

(flags::FlagsColumn)(::Integer) = (join(flags.val, ""),)

# * Setup solver trace
function setup_solver_trace(verbosity, max_iter, tol, ω, num_printouts;
                            tol_header="Tolerance")
    if verbosity > 1
        tolerance = Tolerance(tol, tol_header, print_target=false)

        columns = (CurrentStep(max_iter,
                               lc=LinearColorant(max_iter,1,SolverTraces.red_green_scale()),
                               header="Iteration"),
                   tolerance)

        relaxation = if !iszero(ω)
            columns = (columns..., RelaxationColumn(ω))
            last(columns)
        else
            nothing
        end

        eng = EnergyColumn(0.0),EnergyColumn(0.0,false,"⟨T̂⟩"),EnergyColumn(0.0,false, "⟨V̂⟩")
        virial = VirialColumn(-2.0)
        flags = FlagsColumn()

        columns = (columns..., eng..., virial, flags)

        trace = SolverTrace(max_iter,
                            columns...;
                            progress_meter=false,
                            num_printouts=num_printouts)

        trace,tolerance,relaxation,eng,virial,flags
    else
        nothing,nothing,nothing,nothing,nothing,nothing
    end
end


col_1_to_30 = [ -2.5   0.5           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   3.0       -2.0       1.0         0.0   0.0   0.0   0.0     0.0   0.0    0.0
1.0  -3.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   3.0       -2.0       1.0         0.0   0.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0          -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   2.0        0.0       0.0         0.0   0.0   0.0   0.0     0.0   0.0    0.5
0.0   0.0           0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        2.0       0.0         0.0   0.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       2.0         0.0   0.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         2.0   0.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   2.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   2.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0   2.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0   0.0     2.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0   0.0     0.0   2.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   2.0        0.0       0.0         0.0   0.0   0.0   0.0     0.0   0.0    0.5
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        2.0       0.0         0.0   0.0   0.0   0.0     0.0   0.0    0.0
-0.0  -0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       2.0         0.0   0.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         2.0   0.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   2.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   2.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0   2.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0   0.0        0.0       0.0         0.0   0.0   0.0   0.0     2.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -2.5   0.0        0.0       0.0         0.0   0.0   0.0   0.0     0.0   2.0    0.0
0.0   0.0           1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -6.0        6.66667  -2.66667     0.0   0.0   0.0   0.0     0.0   0.0    1.0
0.0   0.0           0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0  -0.666667  -2.0       0.666667    0.0   0.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.666667  -2.66667  -4.0         6.0  -4.0   2.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0       -14.0  20.0  -8.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0        -2.0  -2.0   2.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         2.0  -8.0  -8.0  18.0   -12.0   6.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0  -3.5     2.5  -1.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0  -0.25   -2.0   0.25   0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0   0.25   -1.0  -2.75   0.0
0.0   6.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   2.0        0.0       0.0         0.0   0.0   0.0   0.0     0.0   0.0   -6.0
0.0   2.498e-16     0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        2.0       0.0         0.0   0.0   0.0   0.0     0.0   0.0    1.0
-0.0  -1.66533e-15   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       2.0         0.0   0.0   0.0   0.0     0.0   0.0   -4.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         2.0   0.0   0.0   0.0     0.0   0.0    9.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   2.0   0.0   0.0     0.0   0.0    3.747e-16
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   2.0   0.0     0.0   0.0   -2.498e-15
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0   2.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0   0.0     2.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   1.0   0.0        0.0       0.0         0.0   0.0   0.0   0.0     0.0   2.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0   0.0     0.0   0.0    0.0
0.0   0.0           0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0   0.0        0.0       0.0         0.0   0.0   0.0   0.0     0.0   0.0    0.0]

col_31_to_40 = [   0.0           0.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.5           0.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.5            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.5            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.5            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.5           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.5     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     0.5    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     0.0    0.5     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.5           0.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.5            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.5            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.5            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.5           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.5     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     0.5    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     0.0    0.5     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
1.0           0.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           1.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            1.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            1.0            0.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            1.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           1.0     0.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     1.0    0.0     0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.0     0.0    1.0     0.0   1.5
-4.0           1.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
-3.0          -1.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
10.0          -9.0            0.0            0.0            0.0           0.0     0.0    0.0     0.0   0.0
-18.0          27.0          -12.0          -12.0            3.0           0.0     0.0    0.0     0.0   0.0
-7.49401e-16   1.1241e-15     3.0           -3.0           -3.0           0.0     0.0    0.0     0.0   0.0
4.996e-15    -7.49401e-15  -12.0           30.0          -21.0           0.0     0.0    0.0     0.0   0.0
0.0           0.0            1.125         -2.25           3.375        -4.125  -1.5    0.375   0.0   0.0
0.0           0.0            4.68375e-17   -9.36751e-17    1.40513e-16   0.375  -3.0   -0.375   0.0   0.0
0.0           0.0           -3.1225e-16     6.245e-16     -9.36751e-16  -1.5     3.75  -5.25    0.0   0.0
0.0           0.0            0.0            0.0            0.0           0.25   -0.5    0.75   -2.5   2.0
0.0           0.0            0.0            0.0            0.0           0.5    -1.0    1.5     1.0  -2.0]

B_data = [col_1_to_30 col_31_to_40]