"""
    MeshModificationModule

Module for mesh modification operations.
"""
module MeshModificationModule

using FinEtools.FTypesModule: FInt, FFlt, FCplxFlt, FFltVec, FIntVec, FFltMat, FIntMat, FMat, FVec, FDataDict
import FinEtools.FESetModule: FESet, count, boundaryconn, boundaryfe, updateconn!, connasarray, fromarray!
import FinEtools.FENodeSetModule: FENodeSet
import FinEtools.BoxModule: boundingbox, inflatebox!, intersectboxes, inbox
import FinEtools.MeshSelectionModule: connectednodes
using Base.Sort
using Base.Order
import LinearAlgebra: norm, svd, dot, eigen
import Random: randperm
using SparseArrays

"""
    interior2boundary(interiorconn::Array{Int, 2}, extractb::Array{Int, 2})

Extract the boundary connectivity from the connectivity of the interior.
"""
function interior2boundary(interiorconn::Array{Int, 2}, extractb::Array{Int, 2})
    hypf = interiorconn[:, extractb[1, :]]
    for i = 2:size(extractb, 1)
        hypf = vcat(hypf, interiorconn[:, extractb[i, :]])
    end
    return _myunique2(hypf);
end

"""
meshboundary(fes::T) where {T<:FESet}

Extract the boundary finite elements from a mesh.

Extract the finite elements of manifold dimension (n-1) from the
supplied finite element set of manifold dimension (n).
"""
function meshboundary(fes::T) where {T<:FESet}
    # Form all hyperfaces, non-duplicates are boundary cells
    hypf = boundaryconn(fes);    # get the connectivity of the boundary elements
    bdryconn = _myunique2(hypf);
    make = boundaryfe(fes);     # get the function that can make a boundary element
    return make(bdryconn);
end

function _mysortrows(A::FIntMat)
    # Sort the rows of A by sorting each column from back to front.

    m,n = size(A);

    indx =  zeros(FInt,m); sindx = zeros(FInt,m)
    for i=1:m
        indx[i]=i
    end
    nindx =  zeros(FInt,m);
    col = zeros(FInt,m)
    for c = n:-1:1
        for i=1:m
            col[i]=A[indx[i],c]
        end
        #Sorting a column vector is much faster than sorting a column matrix
        sindx=sortperm(col,alg=QuickSort);
        #sortperm!(sindx,col,alg=QuickSort); # available for 0.4, slightly faster
        #indx=indx[sindx] # saving allocations by using the below loops
        for i=1:m
            nindx[i]=indx[sindx[i]]
        end
        for i=1:m
            indx[i]=nindx[i]
        end
    end

    return A[indx,:]
end

function _mysortdim2!(A::FIntMat)
    # Sort each row  of A in ascending order.

    m,n = size(A);
    r = zeros(FInt,n)
   @inbounds for k = 1:m
        for i=1:n
            r[i]=A[k,i]
        end
        sort!(r);
        for i=1:n
            A[k,i]=r[i]
        end
    end
    return A
end

function  _myunique2(A::FIntVec)
    return _myunique2(reshape(A, length(A), 1))
end

function  _myunique2(A::FIntMat) # speeded up; now the bottleneck is _mysortrows
    #println("size(A)=$(size(A))")
    maxA=maximum(A[:])::FInt
    sA=deepcopy(A)
    #@time
    sA=_mysortdim2!(sA)::FIntMat;#this is fast
    #@time sA=sort(A,2,alg=QuickSort)::FIntMat;#this is slow
    sA= [sA broadcast(+, 1:size(A,1), maxA)]::FIntMat
    #@time
    sA =_mysortrows(sA); # this now takes the majority of time, but much less than the function below
    #@time sA  = sortrows(sA,alg=QuickSort);;#this is slow
    rix=sA[:,end];
    broadcast!(-, rix, rix, maxA)
    sA=sA[:,1:end-1];
    d=falses(size(sA,1)-1)
    for k=1:length(d)
        for m=1:size(sA,2)
            if sA[k,m]!=sA[k+1,m]
                d[k]=true;
                break;
            end
        end
    end
    #d=(sA[1:end-1,:].!=sA[2:end,:]); # element-wise comparison!
    ad=zeros(FInt,size(d,1)+1)
    ad[1]=1;
    for k=2:length(ad)
        for m=1:size(d,2)
            if d[k-1,m]!=0
                ad[k]=1;
                break;
            end
        end
    end
    #ad=map((x) -> (x?1:0),[true; any(d,2)]);
    iu=trues(length(ad))
    for k=1:(length(ad)-1)
        ad[k]=ad[k]+ad[k+1]
        iu[k]=(ad[k]>1)
    end
    ad[end]=ad[end]+1;
    iu[end]=(ad[end]>1)
    #iu =map((x) -> (x>1? true: false),(ad + [ad[2:end];1]));
    Out =A[rix[iu],:];
    return Out
end

# ### This code is correct, but very slow.
# function  _myunique1(A::FIntMat)
#     maxA=maximum(A[:])
#     sA=sort(A,2);# most time spent here
#     sA= [sA (1:size(A,1))+maxA]
#     sA  = sortrows(sA);;#and here
#     rix=sA[:,end]; rix=rix[:]-maxA;
#     sA=sA[:,1:end-1];
#     d=(sA[1:end-1,:].!=sA[2:end,:]); # element-wise comparison!
#     ad=map((x) -> (x ? 1 : 0),[true; any(d,2)]);
#     iu =map((x) -> (x>1 ? true: false),(ad + [ad[2:end];1]));
#     Out =A[rix[iu[:]],:];
#     return Out
# end

"""
    fusenodes(fens1::FENodeSet, fens2::FENodeSet, tolerance:: FFlt)

Fuse together nodes from two node sets.

Fuse two node sets. If necessary, by gluing together nodes located within
tolerance of each other. The two node sets, `fens1` and `fens2`,  are fused
together by merging the nodes that fall within a box of size `tolerance`. The
merged node set, `fens`, and the new  indexes of the nodes in the set `fens1`
are returned.

The set `fens2` will be included unchanged, in the same order,
in the node set `fens`.
The indexes of the node set `fens1` will have changed.

### Example:
After the call to this function we have
`k=new_indexes_of_fens1_nodes[j]` is the node in the node set fens which
used to be node `j` in node set `fens1`.
The finite element set connectivity that used to refer to `fens1`
needs to be updated to refer to the same nodes in  the set `fens` as
     `updateconn!(fes, new_indexes_of_fens1_nodes);`
"""
function fusenodes(fens1::FENodeSet, fens2::FENodeSet, tolerance:: FFlt)
    @assert size(fens1.xyz, 2) == size(fens2.xyz, 2)
    dim::FInt = size(fens1.xyz,2);
    nn1::FInt = count(fens1)
    nn2::FInt = count(fens2)
    xyz1 = zeros(FFlt,nn1,dim); copyto!(xyz1, fens1.xyz)#::FFltMat = copy(fens1.xyz::FFltMat)
    id1 = collect(1:nn1);
    xyz2 = zeros(FFlt,nn2,dim); copyto!(xyz2, fens2.xyz)#xyz2::FFltMat = copy(fens2.xyz::FFltMat)
    id2 = collect(1:nn2);
    # Decide which nodes should be checked for proximity
    ib::FFltVec = intersectboxes(inflatebox!(boundingbox(xyz1), tolerance), inflatebox!(boundingbox(xyz2), tolerance))
    node1in = fill(false, nn1);
    node2in = fill(false, nn2);
    if length(ib) > 0
        for i=1:nn1
            node1in[i] = inbox(ib, @view xyz1[i, :])
        end
        for i=1:nn2
            node2in[i] = inbox(ib, @view xyz2[i, :])
        end
    end
    # Mark nodes from the first array that are duplicated in the second
    if (tolerance > 0.0) # should we attempt to merge nodes?
        for i=1:nn1
            if node1in[i]
                breakoff = false
                for rx=1:nn2
                    if node2in[rx]
                        distance::FFlt= 0.0
                        for cx=1:dim
                            distance = distance + abs(xyz2[rx,cx]-xyz1[i,cx]);
                            if (distance >= tolerance) # shortcut: if the distance is already too large, stop checking
                                break
                            end
                        end
                        if (distance < tolerance)
                            id1[i] = -rx; breakoff = true;
                        end
                    end
                    if breakoff
                        break
                    end
                end
            end
        end
    end
    # Generate  fused arrays of the nodes. First copy in the nodes from the second set...
    xyzm = zeros(FFlt,nn1+nn2,dim);
    for rx = 1:nn2
        for cx = 1:dim
            xyzm[rx,cx] = xyz2[rx,cx];
        end
    end
    idm = zeros(FInt,nn1+nn2);
    for rx = 1:nn2
        idm[rx] = rx;
    end
    mid=nn2+1;
    # ...and then we add in only non-duplicated nodes from the first set
    for i=1:nn1
        if id1[i]>0
            id1[i] = mid;
            idm[mid] = mid;
            for cx = 1:dim
                xyzm[mid,cx] = xyz1[i,cx];
            end
            mid = mid+1;
        else
            id1[i] = id2[-id1[i]];
        end
    end
    nnodes = mid-1;
    xyzm = xyzm[1:nnodes,:];

    # Create the fused Node set
    fens = FENodeSet(xyzm);
    # The Node set 1 numbering will change
    new_indexes_of_fens1_nodes = id1[:];
    # The node set 2 numbering stays the same
    return fens, new_indexes_of_fens1_nodes
end

"""
    compactnodes(fens::FENodeSet, connected::Vector{Bool})

Compact the finite element node set by deleting unconnected nodes.

`fens` = array of finite element nodes
`connected` = The array element `connected[j]` is either 0 (when `j` is an
  unconnected node), or a positive number (when node `j` is connected to
  other nodes by at least one finite element)

### Output:
`fens` = new set of finite element nodes
`new_numbering`= array which tells where in the new `fens` array the
     connected nodes are (or 0 when the node was unconnected). For instance,
     node 5 was connected, and in the new array it is the third node: then
     `new_numbering[5]` is 3.

### Examples:
Let us say there are nodes not connected to any finite element that you
would like to remove from the mesh: here is how that would be
accomplished.
```
connected = findunconnnodes(fens, fes);
fens, new_numbering = compactnodes(fens, connected);
fes = renumberconn!(fes, new_numbering);
```
Finally, check that the mesh is valid:
```
validate_mesh(fens, fes);
```
"""
function compactnodes(fens::FENodeSet, connected::BitArray{1})
    @assert length(connected) == count(fens)
    new_numbering = zeros(FInt,count(fens),1);
    nxyz = deepcopy(fens.xyz);
    id=1;
    for i=1:length(connected)
        if (connected[i])
            new_numbering[i] = id;
            nxyz[id,:] = fens.xyz[i,:];
            id=id+1;
        end
    end
    #new_numbering = new_numbering[1:id-1];
    fens = FENodeSet(nxyz[1:id-1,:]);
    return fens, vec(new_numbering)
end

"""
    mergemeshes(fens1::FENodeSet, fes1::T1,
      fens2::FENodeSet, fes2::T2, tolerance::FFlt) where {T1<:FESet,T2<:FESet}

Merge together two meshes.

Merge two meshes together by gluing together nodes within tolerance. The
two meshes, `fens1`, `fes1`, and `fens2`, `fes2`, are glued together by merging
the nodes that fall within a box of size `tolerance`. If `tolerance` is set
to zero, no merging of nodes is performed; the two meshes are simply
concatenated together.

The merged node set, `fens`, and the two finite element sets with
renumbered  connectivities are returned.

Important notes: On entry into this function the connectivity of `fes1`
point into `fens1` and the connectivity of `fes2` point into `fens2`. After
this function returns the connectivity of both `fes1` and `fes2` point into
`fens`. The order of the nodes of the node set `fens1` in the resulting set
`fens` will have changed, whereas the order of the nodes of the node set
`fens2` is are guaranteed to be the same. Therefore, the connectivity of
`fes2` will in fact remain the same.
"""
function mergemeshes(fens1::FENodeSet, fes1::T1,
    fens2::FENodeSet, fes2::T2, tolerance::FFlt) where {T1<:FESet,T2<:FESet}
    # Fuse the nodes
    # @code_warntype fusenodes(fens1, fens2, tolerance);
    fens, new_indexes_of_fens1_nodes = fusenodes(fens1, fens2, tolerance);
    # Renumber the finite elements
    newfes1 = deepcopy(fes1)
    updateconn!(newfes1, new_indexes_of_fens1_nodes);
    # Note that now the connectivity of both fes1 and fes2 point into
    # fens.
    return fens, newfes1, fes2
end

"""
    mergenmeshes(meshes::Array{Tuple{FENodeSet, FESet}}, tolerance::FFlt)

Merge several meshes together.

The meshes are glued together by merging the nodes that fall within
a box of size `tolerance`. If `tolerance` is set to zero, no merging of
nodes is performed; the nodes from the meshes are simply concatenated together.

## Output
The merged node set, `fens`, and an array of finite element sets with
renumbered  connectivities are returned.
"""
function mergenmeshes(meshes::Array{Tuple{FENodeSet, FESet}}, tolerance::FFlt)
    outputfes = Array{FESet,1}()
    if (length(meshes)) == 1 # A single mesh, package output and return
        fens, fes = meshes[1];
        push!(outputfes, fes)
        return fens, outputfes
    end
    # Multiple meshes: process
    fens, fes = meshes[1];
    push!(outputfes, fes)
    for j=2:length(meshes)
        fens1, fes1 = meshes[j];
        fens, new_indexes_of_fens1_nodes = fusenodes(fens1, fens, tolerance);
        updateconn!(fes1,new_indexes_of_fens1_nodes);
        push!(outputfes, fes1)
    end
    return fens, outputfes
end

"""
    mergenodes(fens::FENodeSet, fes::FESet, tolerance::FFlt)

Merge together  nodes of a single node set.

Merge by gluing together nodes from a single node set located within
tolerance of each other. The nodes are glued together by merging the
nodes that fall within a box of size `tolerance`. The merged node
set, `fens`, and the finite element set, `fes`, with renumbered  connectivities
are returned.

Warning: This tends to be an expensive operation!
"""
function mergenodes(fens::FENodeSet, fes::FESet, tolerance::FFlt)
    maxnn = count(fens) + 1
    xyz1 = fens.xyz;
    dim  = size(xyz1,2);
    id1 = collect(1:count(fens));
    d = zeros(size(xyz1,1));
    # Mark nodes from the array that are duplicated
    for i = 1:count(fens)
        if (id1[i] > 0) # This node has not yet been marked for merging
            XYZ = reshape(xyz1[i,:], 1, dim);
            copyto!(d, sum(abs.(xyz1 .- XYZ), dims = 2)); #find the distances along  coordinate directions
            minn = maxnn
            @inbounds for jx = 1:length(d)
                if d[jx] < tolerance
                    minn = min(jx, minn);
                    id1[jx] = -minn;
                    id1[minn] = minn;
                end
            end
        end
    end
    # Generate  merged arrays of the nodes
    xyzm = zeros(FFlt,count(fens),dim);
    mid = 1;
    for i = 1:count(fens) # and then we pick only non-duplicated fens1
        if id1[i] > 0 # this node is the master
            id1[i] = mid;
            xyzm[mid,:] = xyz1[i,:];
            mid = mid+1;
        else # this node is the slave
            id1[i] = id1[-id1[i]];
        end
    end
    nnodes = mid-1;
    xyzm = xyzm[1:nnodes,:];
    # Renumber the cells
    conns = connasarray(fes);
    for i = 1:size(conns,1)
        conns[i,:] = id1[conns[i,:]];
    end
    fes = fromarray!(fes, conns)

    fens = FENodeSet(xyzm[1:nnodes,:]);

    return fens,fes
end

"""
    mergenodes(fens::FENodeSet, fes::FESet, tolerance::FFlt, candidates::FIntVec)

Merge together  nodes of a single node set.

Similar to `mergenodes(fens::FENodeSet, fes::FESet, tolerance::FFlt)`, but only
the candidate nodes are considered for merging. This can potentially speed up the operation by orders of magnitude.
"""
function mergenodes(fens::FENodeSet, fes::FESet, tolerance::FFlt, candidates::FIntVec)
    maxnn = count(fens) + 1
    xyz1 = fens.xyz;
    dim  = size(xyz1,2);
    id1 = collect(1:count(fens));
    d = fill(100.0*tolerance, size(xyz1, 1))
    # Mark nodes from the array that are duplicated
    for ic = 1:length(candidates)
        i = candidates[ic]
        if (id1[i] > 0) # This node has not yet been marked for merging
            XYZ = xyz1[i,:];
            minn = maxnn
            for kx = candidates
                d[kx] = sum(abs.(xyz1[kx,:] .- XYZ))
            end
            @inbounds for jx = candidates
                if d[jx] < tolerance
                    minn = min(jx, minn);
                    id1[jx] = -minn;
                    id1[minn] = minn;
                end
            end
        end
    end
    # Generate  merged arrays of the nodes
    xyzm = zeros(FFlt,count(fens),dim);
    mid = 1;
    for i = 1:count(fens) # and then we pick only non-duplicated fens1
        if id1[i] > 0 # this node is the master
            id1[i] = mid;
            xyzm[mid,:] = xyz1[i,:];
            mid = mid+1;
        else # this node is the slave
            id1[i] = id1[-id1[i]];
        end
    end
    nnodes = mid-1;
    xyzm = xyzm[1:nnodes,:];
    # Renumber the cells
    conns = connasarray(fes);
    for i = 1:count(fes)
        conn = conns[i,:];
        conns[i,:] = id1[conn];
    end
    fes = fromarray!(fes, conns);

    fens = FENodeSet(xyzm[1:nnodes,:]);

    return fens,fes
end

"""
    renumberconn!(fes::FESet, new_numbering::FIntVec)

Renumber the nodes in the connectivity of the finite elements based on a new
numbering for the nodes.

`fes` =finite element set
`new_numbering` = new serial numbers for the nodes.  The connectivity
          should be changed as `conn[j]` --> `new_numbering[conn[j]]`

Let us say there are nodes not connected to any finite element that you would
like to remove from the mesh: here is how that would be accomplished.
```
connected = findunconnnodes(fens, fes);
fens, new_numbering = compactfens(fens, connected);
fes = renumberconn!(fes, new_numbering);
```
Finally, check that the mesh is valid:
```julia
validate_mesh(fens, fes);
```
"""
function renumberconn!(fes::FESet, new_numbering::FIntVec)
    conn = connasarray(fes)
    for i=1:size(conn,1)
        c = conn[i,:];
        conn[i,:] = new_numbering[c];
    end
    return fromarray!(fes, conn)
end

"""
    vsmoothing(v::FFltMat, t::FIntMat; options...)

Internal routine for mesh smoothing.

Keyword options:
`method` = :taubin (default) or :laplace
`fixedv` = Boolean array, one entry per vertex: is the vertex immovable (true)
    or movable  (false)
`npass` = number of passes (default 2)
"""
function vsmoothing(v::FFltMat, t::FIntMat; kwargs...)
    fixedv = falses(size(v,1))
    npass = 2;
    method =:taubin;
    for apair in pairs(kwargs)
        sy, val = apair
        if sy==:method
            method = val
        elseif sy==:fixedv
            fixedv .= val
        elseif sy==:npass
            npass = val
        end
    end

    nv = deepcopy(v)
    # find neighbors for the given connections
    vneigh =  vertexneighbors(t,size(v,1));
    # Smoothing considering all connections through the volume
    if (method == :taubin)
        nv =  smoothertaubin(v,vneigh,fixedv,npass,0.5,-0.5);
    elseif (method == :laplace)
        nv =  smootherlaplace(v,vneigh,fixedv,npass,0.5,-0.5);
    end
    # return new vertex locations
    return nv
end

"""
    meshsmoothing(fens::FENodeSet, fes::T; options...) where {T<:FESet}

General smoothing of meshes.

## Keyword options:
`method` = :taubin (default) or :laplace
`fixedv` = Boolean array, one entry per vertex: is the vertex immovable (true)
    or movable  (false)
`npass` = number of passes (default 2)

## Return
The modified  node set.
"""
function meshsmoothing(fens::FENodeSet, fes::T; options...) where {T<:FESet}
    v = deepcopy(fens.xyz)
    v = vsmoothing(v, connasarray(fes); options...)
    copyto!(fens.xyz, v)
    return fens
end

function  smoothertaubin(vinp::FFltMat, vneigh::Array{FIntVec,1}, fixedv::T, npass::FInt, lambda::FFlt, mu::FFlt) where {T}
    v=deepcopy(vinp);
    nv=deepcopy(vinp);
    for I= 1:npass
        o=randperm(length(vneigh));
        damping_factor=lambda;
        for k= 1:length(vneigh)
            r=o[k];
            n=vneigh[r];
            if (length(n)>1) && (!fixedv[r])
                ln1 = (length(n)-1)
                nv[r,:] .= (1-damping_factor)*vec(v[r,:]) + damping_factor*(vec(sum(v[n,:], dims = 1)) - vec(v[r,:]))/ln1;
            end
        end
        v=deepcopy(nv);
        damping_factor=mu;
        for k= 1:length(vneigh)
            r=o[k];
            n=vneigh[r];
            if (length(n)>1) && (!fixedv[r])
                ln1 = (length(n)-1)
                nv[r,:] .= (1-damping_factor)*vec(v[r,:]) + damping_factor*(vec(sum(v[n,:], dims = 1)) - vec(v[r,:]))/ln1;
            end
        end
        v=deepcopy(nv);
    end
    return nv
end

function   smootherlaplace(vinp::FFltMat, vneigh::Array{FIntVec,1}, fixedv::T, npass::FInt, lambda::FFlt,mu::FFlt) where {T}
    v=deepcopy(vinp);
    nv=deepcopy(vinp);
    damping_factor=lambda;
    for I= 1:npass
        o=randperm(length(vneigh));
        for k= 1:length(vneigh)
            r=o[k];
            n=vneigh[r];
            if (length(n)>1) && (!fixedv[r])
                ln1 = (length(n)-1)
                nv[r,:] = (1-damping_factor)*vec(v[r,:]) + damping_factor*(vec(sum(v[n,:], dims = 1))-vec(v[r,:]))/ln1;
            end
        end
        v=deepcopy(nv);
    end
    return nv
end

"""
    vertexneighbors(conn::FIntMat, nvertices::FInt)

Find the node neighbors in the mesh.

Return an array of integer vectors, element I holds an array of numbers of nodes
which are connected to node I (including node I).
"""
function vertexneighbors(conn::FIntMat, nvertices::FInt)
    vn = FIntVec[]; sizehint!(vn, nvertices)
    for I= 1:nvertices
        push!(vn, FInt[]);          # preallocate
    end
    for I= 1:size(conn,1)
        for r= 1:size(conn,2)
            append!(vn[conn[I,r]],vec(conn[I,:]));
        end
    end
    for I= 1:length(vn)
        vn[I]=unique(vn[I]);
    end
    return vn
end

"""
    mirrormesh(fens::FENodeSet, fes::T, Normal::FFltVec,
      Point::FFltVec; kwargs...) where {T<:FESet}

Mirror a 2-D mesh in a plane given by its normal and one point.

Warning: The code to relies on the numbering of the cells: to reverse the
orientation of the mirrored cells, the connectivity is listed in reverse
order.   If the mirrored cells do not follow this rule (for instance hexahedra
for quadrilaterals), their areas/volumes will come out negative. In such a
case the renumbering function of the connectivity needs to be supplied.

For instance: H8 elements require  the renumbering function to be supplied as
fens1,gcells1 = mirror_mesh(fens, gcells,...
          [-1,0,0], [0,0,0], @(c)c([1, 4, 3, 2, 5, 8, 7, 6]));
"""
function mirrormesh(fens::FENodeSet, fes::T, Normal::FFltVec,
    Point::FFltVec; kwargs...) where {T<:FESet}
    # Treat optional arguments.
    # Simply switch the order of nodes.  Works for simplexes...
    renumb(conn) = conn[end:-1:1];
    for apair in pairs(kwargs)
        sy, val = apair
        if sy == :renumb
            renumb = val
        end
    end
    # Make sure we're using a unit normal
    Normal = Normal/norm(Normal);
    Normal = vec(Normal)
    # The point needs to be a row  matrix
    Point = vec(Point)

    fens1 = deepcopy(fens); # the mirrored mesh nodes
    for i = 1:count(fens1)
        a = fens1.xyz[i,:]
        d = dot(vec(a-Point), Normal);
        fens1.xyz[i,:] = a-2*d*Normal;
    end
    # Reconnect the cells
    fes1=deepcopy(fes);
    conn = connasarray(fes1)
    for i=1:size(conn, 1)
        conn[i,:]=renumb(conn[i,:]);
    end
    return fens1, fromarray!(fes1, conn)
end

function _nodepartitioning3(fens::FENodeSet, nincluded::Vector{Bool}, npartitions::Int = 2)
    function inertialcutpartitioning!(partitioning, parts, X)
        nspdim = 3
        StaticMoments = fill(zero(FFlt), nspdim, length(parts));
        npart = fill(0, length(parts))
        for spdim = 1:nspdim
            @inbounds for j = 1:size(X, 1)
                if nincluded[j] # Is the node to be included in the partitioning?
                    jp = partitioning[j]
                    StaticMoments[spdim, jp] += X[j, spdim]
                    npart[jp] += 1 # count the nodes in the current partition
                end
            end
        end
        CG = fill(zero(FFlt), nspdim, length(parts));
        for p = parts
            npart[p] = Int(npart[p] / nspdim)
            CG[:, p] = StaticMoments[:, p] / npart[p] # center of gravity of each partition
        end
        MatrixMomentOfInertia = fill(zero(FFlt), nspdim, nspdim, length(parts))
        @inbounds for j = 1:size(X, 1)
            if nincluded[j] # Is the node to be included in the partitioning?
                jp = partitioning[j]
                xj, yj, zj = X[j, 1] - CG[1, jp], X[j, 2] - CG[2, jp], X[j, 3] - CG[3, jp]
                MatrixMomentOfInertia[1, 1, jp] += yj^2 + zj^2
                MatrixMomentOfInertia[2, 2, jp] += xj^2 + zj^2
                MatrixMomentOfInertia[3, 3, jp] += yj^2 + xj^2
                MatrixMomentOfInertia[1, 2, jp] -= xj * yj
                MatrixMomentOfInertia[1, 3, jp] -= xj * zj
                MatrixMomentOfInertia[2, 3, jp] -= yj * zj
            end
        end
        for p = parts
            MatrixMomentOfInertia[2, 1, p] = MatrixMomentOfInertia[1, 2, p]
            MatrixMomentOfInertia[3, 1, p] = MatrixMomentOfInertia[3, 1, p]
            MatrixMomentOfInertia[3, 2, p] = MatrixMomentOfInertia[3, 2, p]
        end
        longdir = fill(zero(FFlt), nspdim, length(parts))
        for p = parts
            F = eigen(MatrixMomentOfInertia[:, :, p])
            six = sortperm(F.values)
            longdir[:, p] = F.vectors[:, six[1]]
        end
        toggle = fill(one(FFlt), length(parts));
        @inbounds for j = 1:size(X, 1)
            if nincluded[j] # Is the node to be included in the partitioning?
                jp = partitioning[j]
                vx, vy, vz = longdir[:, jp]
                xj, yj, zj = X[j, 1] - CG[1, jp], X[j, 2] - CG[2, jp], X[j, 3] - CG[3, jp]
                d = xj * vx + yj * vy + zj * vz
                c = 0
                if d < 0.0
                    c = 1
                elseif d > 0.0
                    c = 0
                else # disambiguate d[ixxxx] == 0.0
                    c = (toggle[jp] > 0) ? 1 : 0
                    toggle[jp] = -toggle[jp]
                end
                partitioning[j] = 2 * jp - c
            end
        end
    end

    nlevels = Int(round(ceil(log(npartitions)/log(2))))
    partitioning = fill(1, count(fens))  # start with nodes assigned to partition 1
    for level = 0:1:(nlevels - 1)
        inertialcutpartitioning!(partitioning, collect(1:2^level), fens.xyz)
    end
    return partitioning
end

function _nodepartitioning2(fens::FENodeSet, nincluded::Vector{Bool}, npartitions::Int = 2)
    function inertialcutpartitioning!(partitions, parts, X)
        nspdim = 2
        StaticMoments = fill(zero(FFlt), nspdim, length(parts));
        npart = fill(0, length(parts))
        for spdim = 1:nspdim
            @inbounds for j = 1:size(X, 1)
                if nincluded[j] # Is the node to be included in the partitioning?
                    jp = partitions[j]
                    StaticMoments[spdim, jp] += X[j, spdim]
                    npart[jp] += 1 # count the nodes in the current partition
                end
            end
        end
        CG = fill(zero(FFlt), nspdim, length(parts));
        for p = parts
            npart[p] = Int(npart[p] / nspdim)
            CG[:, p] = StaticMoments[:, p] / npart[p] # center of gravity of each partition
        end
        MatrixMomentOfInertia = fill(zero(FFlt), nspdim, nspdim, length(parts))
        @inbounds for j = 1:size(X, 1)
            if nincluded[j] # Is the node to be included in the partitioning?
                jp = partitions[j]
                xj, yj = X[j, 1] - CG[1, jp], X[j, 2] - CG[2, jp]
                MatrixMomentOfInertia[1, 1, jp] += yj^2
                MatrixMomentOfInertia[2, 2, jp] += xj^2
                MatrixMomentOfInertia[1, 2, jp] -= xj * yj
            end
        end
        for p = parts
            MatrixMomentOfInertia[2, 1, p] = MatrixMomentOfInertia[1, 2, p]
        end
        longdir = fill(zero(FFlt), nspdim, length(parts))
        for p = parts
            F = eigen(MatrixMomentOfInertia[:, :, p])
            six = sortperm(F.values)
            longdir[:, p] = F.vectors[:, six[1]]
        end
        toggle = fill(one(FFlt), length(parts));
        @inbounds for j = 1:size(X, 1)
            if nincluded[j] # Is the node to be included in the partitioning?
                jp = partitions[j]
                vx, vy = longdir[:, jp]
                xj, yj = X[j, 1] - CG[1, jp], X[j, 2] - CG[2, jp]
                d = xj * vx + yj * vy
                c = 0
                if d < 0.0
                    c = 1
                elseif d > 0.0
                    c = 0
                else # disambiguate d[ixxxx] == 0.0
                    c = (toggle[jp] > 0) ? 1 : 0
                    toggle[jp] = -toggle[jp]
                end
                partitions[j] = 2 * jp - c
            end
        end
    end

    nlevels = Int(round(ceil(log(npartitions)/log(2))))
    partitions = fill(1, count(fens))  # start with nodes assigned to partition 1
    for level = 0:1:(nlevels - 1)
        inertialcutpartitioning!(partitions, collect(1:2^level), fens.xyz)
    end
    return partitions
end

"""
    nodepartitioning(fens::FENodeSet, nincluded::Vector{Bool}, npartitions)

Compute the inertial-cut partitioning of the nodes.

`nincluded` = Boolean array: is the node to be included in the partitioning or
    not?
`npartitions` = number of partitions, but note that the actual number of
    partitions is going to be a power of two.

The partitioning can be visualized for instance as:
```julia
partitioning = nodepartitioning(fens, npartitions)
partitionnumbers = unique(partitioning)
for gp = partitionnumbers
  groupnodes = findall(k -> k == gp, partitioning)
  File =  "partition-nodes-Dollar(gp).vtk"
  vtkexportmesh(File, fens, FESetP1(reshape(groupnodes, length(groupnodes), 1)))
end
File =  "partition-mesh.vtk"
vtkexportmesh(File, fens, fes)
@async run(`"paraview.exe" DollarFile`)
```
"""
function nodepartitioning(fens::FENodeSet, nincluded::Vector{Bool}, npartitions::Int = 2)
    @assert npartitions >= 2
    if size(fens.xyz, 2) == 3
        return _nodepartitioning3(fens, nincluded, npartitions)
    elseif size(fens.xyz, 2) == 2
        return _nodepartitioning2(fens, nincluded, npartitions)
    else
        @warn "Not implemented for 1D"
    end
end

"""
    nodepartitioning(fens::FENodeSet, npartitions)

Compute the inertial-cut partitioning of the nodes.

`npartitions` = number of partitions, but note that the actual number of
partitions is going to be a power of two.

In this variant all the nodes are to be included in the partitioning.

The partitioning can be visualized for instance as:
```julia
partitioning = nodepartitioning(fens, npartitions)
partitionnumbers = unique(partitioning)
for gp = partitionnumbers
  groupnodes = findall(k -> k == gp, partitioning)
  File =  "partition-nodes-Dollar(gp).vtk"
  vtkexportmesh(File, fens, FESetP1(reshape(groupnodes, length(groupnodes), 1)))
end
File =  "partition-mesh.vtk"
vtkexportmesh(File, fens, fes)
@async run(`"paraview.exe" DollarFile`)
```
"""
function nodepartitioning(fens::FENodeSet, npartitions::Int = 2)
    @assert npartitions >= 2
    nincluded = fill(true, count(fens)) # The default is all nodes are included in the partitioning.
    if size(fens.xyz, 2) == 3
        return _nodepartitioning3(fens, nincluded, npartitions)
    elseif size(fens.xyz, 2) == 2
        return _nodepartitioning2(fens, nincluded, npartitions)
    else
        @warn "Not implemented for 1D"
    end
end

"""
    nodepartitioning(fens::FENodeSet, fesarr, npartitions::Vector{Int})

Compute the inertial-cut partitioning of the nodes.

`fesarr` = array of finite element sets that represent regions
`npartitions` = array of the number of partitions in each region. However
note that the actual number of partitions is going to be a power of two.

The partitioning itself is carried out by `nodepartitioning()` with
a list of nodes to be included in the partitioning. For each region I
the nodes included in the partitioning are those connected to
the elements of that region, but not to elements that belong to
any of the previous regions, 1…I-1.
"""
function nodepartitioning(fens::FENodeSet, fesarr, npartitions::Vector{Int})
    @assert length(fesarr) == length(npartitions)
    # Partitioning of all the nodes
    partitioning = fill(0, count(fens))
    # Find the partitioning of the nodes in FESet 1
    nincludedp = fill(false, count(fens))
    for i = connectednodes(fesarr[1]) # For nodes connected by region 1
        nincludedp[i] = true
    end
    partitioning1 = nodepartitioning(fens, nincludedp, npartitions[1])
    totnpartitions = maximum(partitioning1)
    # Transfer the partitioning of region 1 into the overall partitioning
    partitioning[nincludedp] = partitioning1[nincludedp]
    for i = 2:length(npartitions)
        # Find the partitioning of the nodes in FESet i, but not in the preceding sets
        nincluded = fill(false, count(fens))
        for j = connectednodes(fesarr[i]) # For nodes connected by region i
            nincluded[j] = !nincludedp[j] # Not included previously
        end
        partitioning1 = nodepartitioning(fens, nincluded, npartitions[i])
        np = maximum(partitioning1) # Number of partitions for region i
        partitioning1 = partitioning1 .+ totnpartitions # shift by the number of partitions in previous regions
        totnpartitions = totnpartitions + np # Increment the number of partitions
        partitioning[nincluded] = partitioning1[nincluded] # Update overall partitioning
        @. nincludedp = nincluded | nincludedp  # Update the list of nodes that have already been included
    end
    return partitioning
end

"""
    adjgraph(conn, nfens)

Compute the adjacency graph from the array of connectivities of the elements
in the mesh.

# Examples

```
conn = [9 1 8 4;
       1 3 2 8;
       8 2 7 5;
       2 6 7 7];
nfens = 9;
adjgraph(conn, nfens)

```
should produce
```
9-element Array{Array{Int64,1},1}:
 [9, 8, 4, 3, 2]
 [1, 3, 8, 7, 5, 6]
 [1, 2, 8]
 [9, 1, 8]
 [8, 2, 7]
 [2, 7]
 [8, 2, 5, 6]
 [9, 1, 4, 3, 2, 7, 5]
 [1, 8, 4]
 ```
"""
function adjgraph(conn, nfens)
    neighbors = fill(Int[], nfens)
    for i = 1:length(neighbors)
    	neighbors[i] = Int[]
    end
    for k = 1:size(conn, 1)
    	for node1 = conn[k, :]
    		for node2 = conn[k, :]
    			if node1 != node2
    				push!(neighbors[node1], node2)
    			end
    		end
        end
    end
    for i = 1:length(neighbors)
    	neighbors[i] = unique(neighbors[i])
    end
    return neighbors
end

"""
    _coldeg(A::SparseMatrixCSC, j::Int)

This function has been modified from source of
(https://github.com/rleegates/CuthillMcKee.jl/blob/master/src/CuthillMcKee.jl)

`A` is assumed to be symmetric.
"""
function _coldeg(A::SparseMatrixCSC, j::Int)
	cptr = A.colptr
	return cptr[j+1]-cptr[j]
end

"""
    adjgraph(A)

Compute the adjacency graph from a sparse matrix. This function has been
modified from source of
(https://github.com/rleegates/CuthillMcKee.jl/blob/master/src/CuthillMcKee.jl)

`A` is assumed to be symmetric.
"""
function adjgraph(A::SparseMatrixCSC)
	cptr = A.colptr
	rval = A.rowval
	ncols = length(cptr)-1
	neighbors = Vector{Vector{Int}}(undef, ncols)
	for i = 1:length(neighbors)
		neighbors[i] = Int[]
	end
	sbyf = let A = A
		j->_coldeg(A, j)
	end
	for j = 1:ncols
		strt = cptr[j]
		jdeg = _coldeg(A, j)
		jadj = Vector{Int}(undef, jdeg)
		for i = 1:jdeg
			jadj[i] = rval[strt+i-1]
		end
		sort!(jadj, by=sbyf)
		neighbors[j] = jadj
	end
	return neighbors
end

"""
    nodedegrees(adjgr::Vector{Vector{Int}})

Compute the degrees of the nodes in the adjacency graph.

conn = [9 1 8 4;
       1 3 2 8;
       8 2 7 5;
       2 6 7 7];
nfens = 9;
adjgr = adjgraph(conn, nfens)
nodedegrees(adjgr)

julia> degrees = node_degrees(adjgr)
9-element Array{Int64,1}:
 5
 6
 3
 3
 3
 2
 4
 7
 3
"""
function nodedegrees(adjgr::Vector{Vector{Int}})
    degrees = fill(0, length(adjgr))
    for k = 1:length(adjgr)
        degrees[k] = length(adjgr[k])
    end
    return degrees
end

function _findP(inR, degrees)
	# println("In _findP")
	P = 0
	mindegree = length(inR)
	@inbounds for i = 1:length(inR)
		if !inR[i]
			return i
			# if degrees[i] < mindegree
			# 	P = i
			# 	mindegree = degrees[i]
			# end
		end
	end
	return P
end

"""
    revcm(adjgr::Vector{Vector{Int}}, degrees::Vector{Int})

Reverse Cuthill-McKee node-renumbering algorithm.
"""
function revcm(adjgr::Vector{Vector{Int}}, degrees::Vector{Int})
	@assert length(adjgr) == length(degrees)
	# Initialization
	n = length(adjgr)
	ndegperm = sortperm(degrees) # sorted nodal degrees
	inR = fill(false, n) # Is a node in the result list?
	R = Int64[]
	sizehint!(R, n)
	Q = Int64[] # Node queue
	sizehint!(Q, n)
	while true
		P = 0 # Find the next node to start from
		while !isempty(ndegperm)
			i = popfirst!(ndegperm)
			if !inR[i]
				P = i
				break
			end
		end
		if P == 0
			break # That was the last node
		end
		# Now we have a node to start from: put it into the result list
		push!(R, P); inR[P] = true
		empty!(Q) # empty the queue
		append!(Q, adjgr[P]) # put the adjacent nodes into the queue
		while length(Q) >= 1
			C = popfirst!(Q) # child to put into the result list
			if !inR[C]
				push!(R, C); inR[C] = true
			end
			for i in adjgr[C] # at all adjacent nodes into the queue
				if !inR[i]
					push!(Q, i)
				end
			end
		end
    end
    return reverse(R) # reverse the result list
end

end


using Dates
using BenchmarkTools

include("../Fastback.jl");

# w/ delayed orders min     = 14.4 ms
# direct placement          = 5.4 ms
@benchmark begin
    AAPL = Instrument("AAPL");
    dt = DateTime(2018, 1, 2, 9, 30, 0)
    acc = Account(100_000.0)
    for i = 1:100_000
        nbbo = NBBO(dt, 100.0 + i / 1000, 102 + i / 1000)
        execute_order!(acc, OpenOrder(AAPL, 100), nbbo)
        update_account!(acc, AAPL, NBBO(dt, 100, 102))
        execute_order!(acc, CloseOrder(acc.open_positions[1]), nbbo)
        update_account!(acc, AAPL, NBBO(dt, 100, 102))
    end
end evals=3 samples=40

# w/ delayed orders min     = 2.1 ms
# direct placement          = 1.1 ms
@benchmark begin
    AAPL = Instrument("AAPL");
    dt = DateTime(2018, 1, 2, 9, 30, 0)
    acc = Account(100_000.0)
    for i = 1:100_000
        nbbo = NBBO(dt, 100.0 + i / 1000, 102 + i / 1000)
        if i == 1
            execute_order!(acc, OpenOrder(AAPL, 100), nbbo)
        end
        update_account!(acc, AAPL, nbbo)
    end
end evals=3 samples=40


# interfacing with NNlib.jl

import NNlib: stride, padding, dilation, flipkernel, spatial_dims, kernel_size,
              conv!, ∇conv_filter!, ∇conv_data!,
              maxpool!, meanpool!, ∇maxpool!, ∇meanpool!,
              softmax, softmax!, ∇softmax, ∇softmax!,
              logsoftmax, logsoftmax!, ∇logsoftmax, ∇logsoftmax!

import DataStructures: DefaultDict

const CUDNNFloat = Union{Float16,Float32,Float64}

# Since CUDNN does not support 1D convolution, Conv in Flux will give a CUDNNError if the size is 1-dimensional.
fix1d(x) = x
fix1d(x::DenseCuArray{T, 3}) where T = reshape(x, size(x, 1), 1, size(x, 2), size(x, 3))
fix1d(cdims::DenseConvDims{1,K,C_in,C_out,S,P,D,F}) where {K,C_in,C_out,S,P,D,F} =
  DenseConvDims{2,(K...,1),C_in,C_out,(S...,1),(P...,0,0),(D...,1),F}((cdims.I...,1))
fix1d(pdims::PoolDims{1,K,S,P,D}) where {K,S,P,D,F} =
  PoolDims{2,(K...,1),(S...,1),(P...,0,0),(D...,1)}((pdims.I..., 1), pdims.C_in)

# We have to reshape the CuArray/PoolDims/DenseConvDims to 4D before feeding to CUDNN.
reshape4D(x::AbstractVector) = reshape(x, 1, 1, length(x), 1)
reshape4D(x::AbstractMatrix) = reshape(x, 1, 1, size(x)...)
reshape4D(x::AbstractArray{T,3}) where T = reshape(x, size(x, 1), 1, size(x, 2), size(x, 3))
reshape4D(x::AbstractArray{T}) where T = x

workspacesize(x) = min(Mem.info()[1] ÷ 16, sizeof(x) * 2)

function perfChoose(perfResults, returnedAlgoCount)::UInt32
  if perfResults[1].status != 0
    return 0
  else
    (best_algo,best_time,best_memory) = (perfResults[1].algo,perfResults[1].time,perfResults[1].memory)
    for i = 2:returnedAlgoCount
      if perfResults[i].status == 0 && perfResults[i].memory < best_memory && perfResults[i].time < best_time * 1.1
        (best_algo,best_memory) = (perfResults[i].algo,perfResults[i].memory)
      end
    end
    return best_algo
  end
end

# Softmax

# in-place for x or dy
softmax(x::DenseCuArray{T}; dims=1) where T<:CUDNNFloat =
  softmax!(x, x, dims=dims)

∇softmax(dy::DenseCuArray{T}, x::DenseCuArray{T}; dims=1) where T<:CUDNNFloat =
  ∇softmax!(dy, dy, x, dims=dims)

logsoftmax(x::DenseCuArray{T}; dims=1) where T<:CUDNNFloat =
  logsoftmax!(x, x, dims=dims)

∇logsoftmax(dy::DenseCuArray{T}, x::DenseCuArray{T}; dims=1) where T<:CUDNNFloat =
  ∇logsoftmax!(dy, dy, x, dims=dims)

function softmax!(y::DenseCuArray{T}, x::DenseCuArray{T}; dims=1) where T<:CUDNNFloat
  cudnnSoftmaxForward(reshape4D(x), reshape4D(y),
                      algo=CUDNN_SOFTMAX_FAST, mode=cudnnSoftmaxMode_t(dims-1))
  return y
end

function ∇softmax!(dx::DenseCuArray{T}, dy::DenseCuArray{T}, x::DenseCuArray{T}; dims=1) where T<:CUDNNFloat
  y = softmax(x, dims=dims)
  cudnnSoftmaxBackward(reshape4D(y), reshape4D(dy), reshape4D(dx),
                       algo=CUDNN_SOFTMAX_FAST, mode=cudnnSoftmaxMode_t(dims-1))
  return dx
end

function logsoftmax!(y::DenseCuArray{T}, x::DenseCuArray{T}; dims=1) where T<:CUDNNFloat
  cudnnSoftmaxForward(reshape4D(x), reshape4D(y),
                      algo=CUDNN_SOFTMAX_LOG, mode=cudnnSoftmaxMode_t(dims-1))
  return y
end

function ∇logsoftmax!(dx::DenseCuArray{T}, dy::DenseCuArray{T}, x::DenseCuArray{T}; dims=1) where T<:CUDNNFloat
  y = logsoftmax(x, dims=dims)
  cudnnSoftmaxBackward(reshape4D(y), reshape4D(dy), reshape4D(dx),
                       algo=CUDNN_SOFTMAX_LOG, mode=cudnnSoftmaxMode_t(dims-1))
  return dx
end

# Convolution

const conv_forward_algos = DefaultDict{Tuple, Int32}(Int32(-1))
function conv!(y::DenseCuArray{T}, x::DenseCuArray{T}, w::DenseCuArray{T}, cdims::DenseConvDims;
               alpha=1, algo=-1) where T<:CUDNNFloat
  if version() < v"6"
    all(x -> x == 1, dilation(cdims)) || error("Only dilation = 1 is supported in cuDNN version < 6")
  end

  if algo < 0
    global conv_forward_algos
    key = (T, strides(x), strides(w), strides(y), cdims, size(x)[end])
    algo = conv_forward_algos[key]
    if algo < 0 # not in conv_forward_algos
      # algo = UInt32(cudnnGetConvolutionForwardAlgorithm(fix1d(y), fix1d(x), fix1d(w), fix1d(cdims), preference=2, workspacesize=workspacesize(x)) # will be removed in cuDNN 8
      # returnedAlgoCount, perfResults = cudnnGetConvolutionForwardAlgorithm_v7(fix1d(y), fix1d(x), fix1d(w), fix1d(cdims))
      # returnedAlgoCount, perfResults = cudnnFindConvolutionForwardAlgorithm(fix1d(y), fix1d(x), fix1d(w), fix1d(cdims))
      returnedAlgoCount, perfResults = cudnnFindConvolutionForwardAlgorithmEx(fix1d(y), fix1d(x), fix1d(w), fix1d(cdims), workspacesize=workspacesize(x))
      algo = perfChoose(perfResults, returnedAlgoCount)
      conv_forward_algos[key] = algo
    end
  end

  cudnnConvolutionForward(fix1d(y), fix1d(x), fix1d(w), fix1d(cdims), alpha=alpha, algo=algo)
  return y
end

if isdefined(NNlib, :conv_bias_act!)
function NNlib.conv_bias_act!(y::DenseCuArray{T}, x::DenseCuArray{T}, w::DenseCuArray{T}, cdims::DenseConvDims, b::DenseCuArray{T}, σ=identity;
                              z::DenseCuArray{T}=y, alpha1=1, alpha2=0, algo=-1) where T<:CUDNNFloat
  if version() < v"6"
    all(x -> x == 1, dilation(cdims)) || error("Only dilation = 1 is supported in cuDNN version < 6")
  end

  if algo < 0
    global conv_forward_algos
    key = (T, strides(x), strides(w), strides(y), cdims, size(x)[end])
    algo = conv_forward_algos[key]
    if algo < 0 # not in conv_forward_algos
      # algo = UInt32(cudnnGetConvolutionForwardAlgorithm(fix1d(y), fix1d(x), fix1d(w), fix1d(cdims), preference=2, workspacesize=workspacesize(x))) # will be removed in cuDNN 8
      # returnedAlgoCount, perfResults = cudnnGetConvolutionForwardAlgorithm_v7(fix1d(y), fix1d(x), fix1d(w), fix1d(cdims))
      # returnedAlgoCount, perfResults = cudnnFindConvolutionForwardAlgorithm(fix1d(y), fix1d(x), fix1d(w), fix1d(cdims))
      returnedAlgoCount, perfResults = cudnnFindConvolutionForwardAlgorithmEx(fix1d(y), fix1d(x), fix1d(w), fix1d(cdims), workspacesize=workspacesize(x))
      algo = perfChoose(perfResults, returnedAlgoCount)
      conv_forward_algos[key] = algo
    end
  end

  # only relu and identity are supported
  if σ == NNlib.relu # always merge convolutions, bias, and relu, even when bias is turned off
    cudnnConvolutionBiasActivationForward(fix1d(y), fix1d(x), fix1d(w), fix1d(z), fix1d(b),
                                          fix1d(cdims), algo=algo, alpha1=alpha1, alpha2=alpha2,
                                          activationMode=CUDNN_ACTIVATION_RELU, activationCoeff=0.0)
  elseif algo == 1 && b != nothing # only merge convolution and bias if the fastest algorithm is also the only supported algorithm and the bias is not turned off
    # algo must be CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM (1) when activationMode equals CUDNN_ACTIVATION_IDENTITY
    cudnnConvolutionBiasActivationForward(fix1d(y), fix1d(x), fix1d(w), fix1d(z), fix1d(b),
                                          fix1d(cdims), algo=algo, alpha1=alpha1, alpha2=alpha2,
                                          activationMode=CUDNN_ACTIVATION_IDENTITY, activationCoeff=0.0)
    σ.(y)
  else # fallback
    if b == nothing # bias is turned off
      σ.(conv!(y, x, w, cdims, alpha=alpha1, algo=algo))
    else # bias is turned on
      σ.(add_bias(conv!(y, x, w, cdims, alpha=alpha1, algo=algo), b))
    end
  end

  return y
end
end

const conv_data_algos = DefaultDict{Tuple, Int32}(Int32(-1))
function ∇conv_data!(dx::DenseCuArray{T}, dy::DenseCuArray{T}, w::DenseCuArray{T},
                     cdims::DenseConvDims; alpha=1, algo=-1) where T<:CUDNNFloat
  if version() < v"6"
    all(x -> x == 1, dilation(cdims)) || error("Only dilation = 1 is supported in cuDNN version < 6")
  end

  if algo < 0
    global conv_data_algos
    key = (T, strides(dx), strides(w), strides(dy), cdims, size(dx)[end])
    algo = conv_data_algos[key]
    if algo < 0 # not in conv_data_algos
      # algo = UInt32(cudnnGetConvolutionBackwardDataAlgorithm(fix1d(dx), fix1d(w), fix1d(dy), fix1d(cdims), preference=2, workspacesize=workspacesize(dx))) # will be removed in cuDNN 8
      # returnedAlgoCount, perfResults = cudnnGetConvolutionBackwardDataAlgorithm_v7(fix1d(dx), fix1d(w), fix1d(dy), fix1d(cdims))
      # returnedAlgoCount, perfResults = cudnnFindConvolutionBackwardDataAlgorithm(fix1d(dx), fix1d(w), fix1d(dy), fix1d(cdims))
      returnedAlgoCount, perfResults = cudnnFindConvolutionBackwardDataAlgorithmEx(fix1d(dx), fix1d(w), fix1d(dy), fix1d(cdims), workspacesize=workspacesize(dx))
      algo = perfChoose(perfResults, returnedAlgoCount)
      conv_data_algos[key] = algo
    end
  end

  cudnnConvolutionBackwardData(fix1d(dx), fix1d(w), fix1d(dy), fix1d(cdims), alpha=alpha, algo=algo)
  return dx
end

const conv_filter_algos = DefaultDict{Tuple, Int32}(Int32(-1))
function ∇conv_filter!(dw::DenseCuArray{T}, x::DenseCuArray{T}, dy::DenseCuArray{T},
                       cdims::DenseConvDims; alpha=1, algo=-1) where T<:CUDNNFloat
  if version() < v"6"
    all(x -> x == 1, dilation(cdims)) || error("Only dilation = 1 is supported in cuDNN version < 6")
  end

  if algo < 0
    global conv_filter_algos
    # (type, batchsize, conv descriptor)
    key = (T, strides(x), strides(dw), strides(dy), cdims, size(x)[end])
    algo = conv_filter_algos[key]
    if algo < 0 # not in conv_filter_algos
      # algo = UInt32(cudnnGetConvolutionBackwardFilterAlgorithm(fix1d(dw), fix1d(x), fix1d(dy), fix1d(cdims), preference=2, workspacesize=workspacesize(x))) # will be removed in cuDNN 8
      # returnedAlgoCount, perfResults = cudnnGetConvolutionBackwardFilterAlgorithm_v7(fix1d(dw), fix1d(x), fix1d(dy), fix1d(cdims))
      # returnedAlgoCount, perfResults = cudnnFindConvolutionBackwardFilterAlgorithm(fix1d(dw), fix1d(x), fix1d(dy), fix1d(cdims))
      returnedAlgoCount, perfResults = cudnnFindConvolutionBackwardFilterAlgorithmEx(fix1d(dw), fix1d(x), fix1d(dy), fix1d(cdims), workspacesize=workspacesize(x))
      algo = perfChoose(perfResults, returnedAlgoCount)
      conv_filter_algos[key] = algo
    end
  end

  cudnnConvolutionBackwardFilter(fix1d(dw), fix1d(x), fix1d(dy), fix1d(cdims), alpha=alpha, algo=algo)
  return dw
end

# Bias

# in-place for x (add b to x)
add_bias(x::DenseCuArray{T}, b::DenseCuArray{T})  where {T<:CUDNNFloat} =
  (cudnnAddTensor(reshape4D(x), reshape4D(b)); return x)

∇conv_bias!(db::DenseCuArray{T}, dy::DenseCuArray{T}; alpha=1, beta=0) where T<:CUDNNFloat =
  (cudnnConvolutionBackwardBias(fix1d(db), fix1d(dy), alpha=alpha, beta=beta); return db)

# Pooling

maxpool!(y::DenseCuArray{T}, x::DenseCuArray{T}, pdims::PoolDims) where T<:CUDNNFloat =
  (cudnnPoolingForward(fix1d(y), fix1d(x), fix1d(pdims); mode=0); return y)

∇maxpool!(dx::DenseCuArray{T}, dy::DenseCuArray{T}, y::DenseCuArray{T}, x::DenseCuArray{T}, pdims::PoolDims) where T<:CUDNNFloat =
  (cudnnPoolingBackward(fix1d(dx), fix1d(dy), fix1d(x), fix1d(y), fix1d(pdims), mode=0); return dx)

meanpool!(y::DenseCuArray{T}, x::DenseCuArray{T}, pdims::PoolDims) where T<:CUDNNFloat =
  (cudnnPoolingForward(fix1d(y), fix1d(x), fix1d(pdims), mode=1); return y)

∇meanpool!(dx::DenseCuArray{T}, dy::DenseCuArray{T}, y::DenseCuArray{T}, x::DenseCuArray{T}, pdims::PoolDims) where T<:CUDNNFloat =
  (cudnnPoolingBackward(fix1d(dx), fix1d(dy), fix1d(x), fix1d(y), fix1d(pdims), mode=1); return dx)

# Activation

# in-place for x
Base.broadcasted(::typeof(NNlib.σ), x::DenseCuArray{T}) where {T<:CUDNNFloat} =
  (cudnnActivationForward(reshape4D(x), mode=CUDNN_ACTIVATION_SIGMOID, coeff=0.0); return x)

Base.broadcasted(::typeof(NNlib.relu), x::DenseCuArray{T}) where {T<:CUDNNFloat} =
  (cudnnActivationForward(reshape4D(x), mode=CUDNN_ACTIVATION_RELU, coeff=0.0); return x)

Base.broadcasted(::typeof(NNlib.tanh), x::DenseCuArray{T}) where {T<:CUDNNFloat} =
  (cudnnActivationForward(reshape4D(x), mode=CUDNN_ACTIVATION_TANH, coeff=0.0); return x)

Base.broadcasted(::typeof(NNlib.relu6), x::DenseCuArray{T}) where {T<:CUDNNFloat} =
  (cudnnActivationForward(reshape4D(x), mode=CUDNN_ACTIVATION_CLIPPED_RELU, coeff=6.0); return x)

Base.broadcasted(::typeof(NNlib.elu), x::DenseCuArray{T}) where {T<:CUDNNFloat} =
  (cudnnActivationForward(reshape4D(x), mode=CUDNN_ACTIVATION_ELU, coeff=1.0); return x)

# CUDNN_ACTIVATION_IDENTITY does not work with cudnnActivationForward
Base.broadcasted(::typeof(NNlib.identity), x::DenseCuArray{T}) where {T<:CUDNNFloat} = x

Base.broadcasted(::typeof(NNlib.leakyrelu), x::DenseCuArray{T}, a=T(0.01)) where {T<:CUDNNFloat} =
  (cudnnOpTensor(CUDNN_OP_TENSOR_MAX, reshape4D(x), reshape4D(x), reshape4D(x), alpha1=a); return x)


#=
This file is auto-generated. Do not edit.
=#

#! format: off

"""
    mutable struct MultiStartCost <: OperationalCost
        variable::VariableCost
        no_load::Float64
        fixed::Float64
        start_up::NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}
        shut_down::Float64
    end

Data Structure Operational Cost Data which includes fixed, variable cost, multiple start up cost and stop costs.

# Arguments
- `variable::VariableCost`: variable cost
- `no_load::Float64`: no load cost
- `fixed::Float64`: fixed cost
- `start_up::NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}`: start-up cost
- `shut_down::Float64`: shut-down cost, validation range: `(0, nothing)`, action if invalid: `warn`
"""
mutable struct MultiStartCost <: OperationalCost
    "variable cost"
    variable::VariableCost
    "no load cost"
    no_load::Float64
    "fixed cost"
    fixed::Float64
    "start-up cost"
    start_up::NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}
    "shut-down cost"
    shut_down::Float64
end

function MultiStartCost(; variable, no_load, fixed, start_up, shut_down, )
    MultiStartCost(variable, no_load, fixed, start_up, shut_down, )
end

# Constructor for demo purposes; non-functional.
function MultiStartCost(::Nothing)
    MultiStartCost(;
        variable=VariableCost((0.0, 0.0)),
        no_load=0.0,
        fixed=0.0,
        start_up=(hot = START_COST, warm = START_COST, cold = START_COST),
        shut_down=0.0,
    )
end

"""Get [`MultiStartCost`](@ref) `variable`."""
get_variable(value::MultiStartCost) = value.variable
"""Get [`MultiStartCost`](@ref) `no_load`."""
get_no_load(value::MultiStartCost) = value.no_load
"""Get [`MultiStartCost`](@ref) `fixed`."""
get_fixed(value::MultiStartCost) = value.fixed
"""Get [`MultiStartCost`](@ref) `start_up`."""
get_start_up(value::MultiStartCost) = value.start_up
"""Get [`MultiStartCost`](@ref) `shut_down`."""
get_shut_down(value::MultiStartCost) = value.shut_down

"""Set [`MultiStartCost`](@ref) `variable`."""
set_variable!(value::MultiStartCost, val) = value.variable = val
"""Set [`MultiStartCost`](@ref) `no_load`."""
set_no_load!(value::MultiStartCost, val) = value.no_load = val
"""Set [`MultiStartCost`](@ref) `fixed`."""
set_fixed!(value::MultiStartCost, val) = value.fixed = val
"""Set [`MultiStartCost`](@ref) `start_up`."""
set_start_up!(value::MultiStartCost, val) = value.start_up = val
"""Set [`MultiStartCost`](@ref) `shut_down`."""
set_shut_down!(value::MultiStartCost, val) = value.shut_down = val


include("run_pd_hyak.jl")
datafile = "../../data/T1_Spitzer_data.jld2"
foutput = "T1_pd_MCMC_nw50_ns50_limbprior_001.jld2"
numwalkers = 50
burnin = 1
thinning = 1
astep = 2.0
nsteps = 50
run_pd(datafile,foutput,numwalkers,burnin,thinning,astep,nsteps;nout=10000,limbprior=true)


@testset "entities" begin
    # g, train_X, train_y = traindata(Entities(), :bgs)
    # @test typeof(g) == Dict{Any,Any}
    # @test typeof(train_X) == SparseMatrixCSC{Float32,Int64}
    # @test size(train_X) == (140, 1433)
    # @test typeof(train_y) == SparseMatrixCSC{Int32,Int64}
    # @test size(train_y) == (140, 7)

    # g, test_X, test_y = testdata(Entities(), :bgs)
    # @test typeof(g) == Dict{Any,Any}
    # @test typeof(test_X) == SparseMatrixCSC{Float32,Int64}
    # @test size(test_X) == (1000, 1433)
    # @test typeof(test_y) == SparseMatrixCSC{Int32,Int64}
    # @test size(test_y) == (1000, 7)
end


#####
##### Profiling batched inference
#####

using AlphaZero
using Setfield
import CUDA

using Plots

# using Revise; Revise.includet("scripts/profile/inference.jl")
# profile_inference(on_gpu=true, batch_size=128, num_filters=128)
# With 128 filters: 27μs per sample
# With  64 filters: 17μs per sample

"""
Estimate inference time in μs/sample.
"""
function profile_inference(
    exp::Experiment = Examples.experiments["connect-four"];
    on_gpu=true,
    nrep=100,
    batch_size=128,
    num_filters=64)

  params = exp.netparams
  params = @set params.num_filters = num_filters
  net = exp.mknet(exp.gspec, params)
  net = Network.copy(net; on_gpu, test_mode=true)
  state = GI.current_state(GI.init(exp.gspec))
  batch = [state for _ in 1:batch_size]
  Network.evaluate_batch(net, batch) # To compile everything
  GC.gc(true)
  info = CUDA.@timed for _ in 1:nrep Network.evaluate_batch(net, batch) end
  return info.time / nrep / batch_size * 1_000_000
end

"""
Plot the inference time speedup (in μs/sample) as a function of batch-size.
"""
function plot_inference_speedup(
  file::String,
  exp::Experiment = Examples.experiments["connect-four"];
  on_gpu=true,
  num_filters=64)

  N = 8
  batch_sizes = [2^i for i in 0:N]
  ts = [
    profile_inference(exp;
      nrep=10,
      num_filters, batch_size, on_gpu)
    for batch_size in batch_sizes ]

  xticks = (0:N, map(string, batch_sizes))
  plot(0:N, ts[1] ./ ts,
    title="Inference Speedup (baseline: $(Int(ceil(ts[1]))) μs)",
    ylabel="Speedup",
    xlabel="Batch size",
    ylims=(0, Inf),
    legend=:none,
    xticks=xticks)
  savefig(file)
end

function all_plots()
  plot_inference_speedup("inference-gpu-64.png",  on_gpu=true,  num_filters=64)
  plot_inference_speedup("inference-cpu-64.png",  on_gpu=false, num_filters=64)
  plot_inference_speedup("inference-gpu-128.png", on_gpu=true,  num_filters=128)
  plot_inference_speedup("inference-cpu-128.png", on_gpu=false, num_filters=128)
end


for s in enumerate((:vertextype, :facetype, :normaltype,
                       :texturecoordinatetype, :colortype))
    @eval begin
        $(s[2]){T<:HomogenousMesh}(t::Type{T}) = t.parameters[$(s[1])]
        $(s[2])(mesh::HomogenousMesh) = $(s[2])(typeof(mesh))
    end
end

hasvertices(msh) = vertextype(msh) != Void
hasfaces(msh) = facetype(msh) != Void
hasnormals(msh) = normaltype(msh) != Void
hastexturecoordinates(msh) = texturecoordinatetype(msh) != Void
hascolors(msh) = colortype(msh) != Void

vertices(msh) = msh.vertices
faces(msh) = msh.faces
normals(msh) = msh.normals
texturecoordinates(msh) = msh.texturecoordinates
colors(msh) = msh.color

# Bad, bad name! But it's a little tricky to filter out faces and verts from the attributes, after get_attribute
function attributes_noVF{T<:AbstractMesh}(m::T)
    fielddict = Dict{Symbol, Any}(map(fieldnames(T)[3:end]) do field
        field => getfield(m, field)
    end)
    return filter(fielddict) do key,val
        val != nothing && val != Void[]
    end
end
#Gets all non Void attributes from a mesh in form of a Dict fieldname => value
function attributes(m::AbstractMesh)
    filter((key,val) -> (val != nothing && val != Void[]), all_attributes(m))
end
#Gets all non Void attributes types from a mesh type fieldname => ValueType
function attributes{M <: HMesh}(m::Type{M})
    filter((key,val) -> (val != Void && val != Vector{Void}), all_attributes(M))
end

function all_attributes{M <: HMesh}(m::Type{M})
    Dict{Symbol, Any}(map(field -> (field => fieldtype(M, field)), fieldnames(M)))
end
function all_attributes{M <: HMesh}(m::M)
    Dict{Symbol, Any}(map(field -> (field => getfield(m, field)),  fieldnames(M)))
end

# Needed to not get into an stack overflow
convert{M <: AbstractMesh}(::Type{M}, mesh::AbstractGeometry) = M(mesh)
@compat (::Type{HM1}){HM1 <: AbstractMesh}(mesh::HM1) = mesh

"""
Uses decompose to get all the converted attributes from the meshtype and
creates a new mesh with the desired attributes from the converted attributs
Getindex can be defined for any arbitrary geometric type or exotic mesh type.
This way, we can make sure, that you can convert most of the meshes from one type to the other
with minimal code.
"""
@compat function (::Type{HM1}){HM1 <: AbstractMesh}(primitive::Union{AbstractMesh, GeometryPrimitive})
    result = Dict{Symbol, Any}()
    for (field, target_type) in zip(fieldnames(HM1), HM1.parameters)
        if target_type != Void
            if field == :attribute_id
                if !isa(primitive, HomogenousMesh)
                    error("Primitive $primitive doesn't hold attribute indexes")
                end
                result[field] = primitive.attribute_id
            else
                result[field] = decompose(target_type, primitive)
            end
        end
    end
    HM1(result)
end

isvoid{T}(::Type{T}) = false
isvoid(::Type{Void}) = true
isvoid{T}(::Type{Vector{T}}) = isvoid(T)
@compat function (::Type{HM1}){HM1 <: HomogenousMesh}(primitive::HomogenousMesh)
    fnames = fieldnames(HM1)
    args = ntuple(nfields(HM1)) do i
        field, target_type = fnames[i], fieldtype(HM1, i)
        soure_type = fieldtype(typeof(primitive), i)
        isa(HM1.parameters[i], TypeVar) && return getfield(primitive, field) # target is not defined
        if !isvoid(target_type) && isvoid(soure_type) # target not there yet, maybe we can decompose though (e.g. normals)
            return decompose(HM1.parameters[i], primitive)
        elseif isvoid(target_type)
            return target_type()
        else
            return convert(target_type, getfield(primitive, field))
        end
    end
    HM1(args...)
end

#Should be:
#function call{M <: HMesh, VT <: Point, FT <: Face}(::Type{M}, vertices::Vector{VT}, faces::Vector{FT})
# Haven't gotten around to implement the types correctly with abstract types in FixedSizeArrays
@compat function (::Type{M}){M <: HMesh, VT, FT <: Face}(
        vertices::Vector{Point{3, VT}}, faces::Vector{FT}
    )
    msh = PlainMesh{VT, FT}(vertices=vertices, faces=faces)
    convert(M, msh)
end
get_default(x::Union{Type, TypeVar}) = nothing
get_default{X <: Array}(x::Type{X}) = Void[]

"""
generic constructor for abstract HomogenousMesh, infering the types from the keywords (which have to match the field names)
some problems with the dispatch forced me to use this method name... need to further investigate this
"""
function homogenousmesh(attribs::Dict{Symbol, Any})
    newfields = []
    for name in fieldnames(HMesh)
        push!(newfields, get(attribs, name, get_default(fieldtype(HMesh, name))))
    end
    HomogenousMesh(newfields...)
end

"""
Creates a mesh from keyword arguments, which have to match the field types of the given concrete mesh
"""
@compat (::Type{M}){M <: HMesh}(; kw_args...) = M(Dict{Symbol, Any}(kw_args))

"""
Creates a new mesh from a dict of `fieldname => value` and converts the types to the given meshtype
"""
@compat function (::Type{M}){M <: HMesh}(attribs::Dict{Symbol, Any})
    newfields = map(zip(fieldnames(HomogenousMesh), M.parameters)) do field_target_type
        field, target_type = field_target_type
        default = fieldtype(HomogenousMesh, field) <: Vector ? Void[] : nothing
        get(attribs, field, default)
    end
    M(HomogenousMesh(newfields...))
end

"""
Creates a new mesh from an old one, with changed attributes given by the keyword arguments
"""
@compat function (::Type{M}){M <: HMesh}(mesh::AbstractMesh, attributes::Dict{Symbol, Any})
    newfields = map(fieldnames(HomogenousMesh)) do field
        get(attributes, field, getfield(mesh, field))
    end
    HomogenousMesh(newfields...)
end
"""
Creates a new mesh from an old one, with a new constant attribute (like a color)
"""
@compat function (::Type{HM}){HM <: HMesh, ConstAttrib}(mesh::AbstractMesh, constattrib::ConstAttrib)
    result = Dict{Symbol, Any}()
    for (field, target_type) in zip(fieldnames(HM), HM.parameters)
        if target_type <: ConstAttrib
            result[field] = constattrib
        elseif target_type != Void
            result[field] = decompose(target_type, mesh)
        end
    end
    HM(result)
end
function add_attribute(m::AbstractMesh, attribute)
    attribs = attributes(m) # get all attribute values as a Dict fieldname => value
    attribs[:color] = attribute # color will probably be renamed to attribute. not sure yet...
    homogenousmesh(attribs)
end

"""
Creates a new mesh from a tuple of a geometry type and a constant attribute
"""
@compat function (::Type{HM}){HM <: HMesh, ConstAttrib, P<:AbstractGeometry}(x::Tuple{P, ConstAttrib})
    any, const_attribute = x
    add_attribute(HM(any), const_attribute)
end

merge{M <: AbstractMesh}(m::Vector{M}) = merge(m...)

"""
Merges an arbitrary mesh. This function probably doesn't work for all types of meshes
parameters:
`m1` first mesh
`meshes...` other meshes
"""
function merge{M <: AbstractMesh}(m1::M, meshes::M...)
    v = copy(m1.vertices)
    f = copy(m1.faces)
    attribs = deepcopy(attributes_noVF(m1))
    for mesh in meshes
        append!(f, mesh.faces + length(v))
        append!(v, mesh.vertices)
        for (v1, v2) in zip(values(attribs), values(attributes_noVF(mesh)))
            append!(v1, v2)
        end
    end
    attribs[:vertices]  = v
    attribs[:faces]     = f
    return M(attribs)
end

"""
A mesh with one constant attribute can be merged as an attribute mesh.
Possible attributes are FSArrays
"""
function merge{_1, _2, _3, _4, ConstAttrib <: Colorant, _5, _6}(
        m1::HMesh{_1, _2, _3, _4, ConstAttrib, _5, _6},
        meshes::HMesh{_1, _2, _3, _4, ConstAttrib, _5, _6}...
    )
    vertices     = copy(m1.vertices)
    faces        = copy(m1.faces)
    attribs      = Dict{Symbol, Any}(filter((k,v) -> k != :color, attributes_noVF(m1)))
    attribs      = Dict{Symbol, Any}([(k, copy(v)) for (k,v) in attribs])
    color_attrib = RGBA{U8}[RGBA{U8}(m1.color)]
    index        = Float32[length(color_attrib)-1 for i=1:length(m1.vertices)]
    for mesh in meshes
        append!(faces, mesh.faces + length(vertices))
        append!(vertices, mesh.vertices)
        attribsb = attributes_noVF(mesh)
        for (k,v) in attribsb
            k != :color && append!(attribs[k], copy(v))
        end
        push!(color_attrib, mesh.color)
        append!(index, Float32[length(color_attrib)-1 for i=1:length(mesh.vertices)])
    end
    delete!(attribs, :color)
    attribs[:vertices]      = vertices
    attribs[:faces]         = faces
    attribs[:attributes]    = color_attrib
    attribs[:attribute_id]  = index
    return HMesh{_1, _2, _3, _4, Void, typeof(color_attrib), eltype(index)}(attribs)
end

# Fast but slightly ugly way to implement mesh multiplication
# This should probably go into FixedSizeArrays.jl, Vector{FSA} * FSA
immutable MeshMulFunctor{T}
    matrix::Mat{4,4,T}
end
@compat (m::MeshMulFunctor{T}){T}(vert) = Vec{3, T}(m.matrix*Vec{4, T}(vert..., 1))
function *{T}(m::Mat{4,4,T}, mesh::AbstractMesh)
    msh = deepcopy(mesh)
    map!(MeshMulFunctor(m), msh.vertices)
    msh
end


"""
Base class for `RTree` node (`Branch` or `Leaf`).
"""
abstract type Node{T,N,V} end

dimtype(::Type{<:Node{T}}) where T = T
dimtype(node::Node) = dimtype(typeof(node))
Base.ndims(::Type{<:Node{<:Any, N}}) where N = N
Base.ndims(node::Node) = ndims(typeof(node))
mbrtype(::Type{<:Node{T,N}}) where {T,N} = Rect{T,N}
mbrtype(node::Node) = mbrtype(typeof(node))
mbr(node::Node) = node.mbr
parent(node::Node) = node.parent
hasparent(node::Node) = node.parent !== nothing
children(node::Node) = node.children
@inline Base.isempty(node::Node) = isempty(children(node))
@inline Base.length(node::Node) = length(children(node))
@inline Base.getindex(node::Node, i::Integer) = getindex(children(node), i)

# `node` position among its siblings in `children` vector of the parent or nothing
pos_in_parent(node::Node) =
    hasparent(node) ? findfirst(c -> c === node, children(parent(node))) : nothing

# update mbr to match the combined MBR of its children
syncmbr!(node::Node) =
    node.mbr = !isempty(node) ? mapreduce(mbr, combine, node.children) : empty(mbrtype(node))

# check whether type E could be used as the data element type in R-tree
# FIXME also need to check whether E could be converted to V of Node
check_eltype_rtree(::Type{E}, ::Type{N}) where {N<:Node, E} =
    check_hasmbr(mbrtype(N), E)

check_eltype_rtree(el::Any, node::Node) =
    check_eltype_rtree(typeof(el), typeof(node))

# equality check for R-tree elements (by MBR and, optionally, ID)
isequal_rtree(el::Any, reg::Region, id::Any = nothing) =
    isequal_rtree(idtrait(typeof(el)), el, reg, id)

isequal_rtree(::Type{<:HasID}, el::Any, reg::Region, key::Any) =
    isequal(id(el), convert(idtype(idtrait(typeof(el))), key)) && (mbr(el) == reg)

# if the tree has IDs but `nothing` supplied, skip ID check
isequal_rtree(::Type{<:HasID}, el::Any, reg::Region, ::Nothing = nothing) =
    (mbr(el) == reg)

isequal_rtree(::Type{HasNoID}, el::Any, reg::Region, ::Nothing = nothing) =
    (mbr(el) == reg)

"""
R-Tree leaf (level 1 node).
Its children are data elements of type `V`.
"""
mutable struct Leaf{T,N,V} <: Node{T,N,V}
    parent::Union{Node{T,N,V}, Nothing} # actually Branch{T,N,V}, but cannot do without forward decl
    mbr::Rect{T,N}
    children::Vector{V}

    function Leaf{T,N,V}(parent::Union{Nothing, Node{T,N,V}},
                         ::Type{V}) where {T,N,V}
        new{T,N,V}(parent, empty(Rect{T,N}), Vector{V}())
    end
end

level(node::Leaf) = 1 # always

"""
R-Tree node for levels above 1 (non-`Leaf`).
"""
mutable struct Branch{T,N,V} <: Node{T,N,V}
    parent::Union{Branch{T,N,V}, Nothing}
    level::Int  # level (thickness) in the R-tree (0=leaves)
    mbr::Rect{T,N}
    children::Union{Vector{Branch{T,N,V}},Vector{Leaf{T,N,V}}}

    function Branch{T,N,V}(parent::Union{Nothing, Branch{T,N,V}}, ::Type{C}) where {T,N,V, C<:Node{T,N,V}}
        new{T,N,V}(parent, 0, empty(Rect{T,N}), Vector{C}())
    end
end

level(node::Branch) = node.level

function Base.setindex!(node::Branch, child::Node, i::Integer)
    (level(node) == level(child) + 1) ||
        throw(SpatialIndexException("Parent ($(level(node)) and child ($(level(child))) levels don't match"))
    setindex!(children(node), child, i)
    child.parent = node
end


function accept(obj::AmbiguityAcceptance, arg0::Vector{IntegerLeastSquareSolution})
    return jcall(obj, "accept", IntegerLeastSquareSolution, (Vector{IntegerLeastSquareSolution},), arg0)
end

function number_of_candidates(obj::AmbiguityAcceptance)
    return jcall(obj, "numberOfCandidates", jint, ())
end


module TypeDef
export datType, dimType, envType, labType, matType, setType, simType, tabType,
  equilType, indxType, maskType, parType

struct datType
  region  ::Vector{Int64}
  qtr     ::Vector{Int64}
  network ::Vector{Int64}
  brand   ::Vector{Int64}
  prepost ::Vector{Int64}
  product ::Vector{Int64}
  ms      ::Vector{Float64}
  price   ::Vector{Float64}
  δ       ::Vector{Float64}
  X       ::Matrix{Float64}
  Xᵢ      ::Matrix{Float64}
  ξ       ::Vector{Float64}
end

struct dimType
  region  ::Int64
  qtr     ::Int64
  network ::Int64
  brand   ::Int64
  prepost ::Int64
  product ::Int64
  person  ::Int64
  market  ::Int64
  obs     ::Int64
  prodt   ::Vector{Int64}
  mno     ::Int64
  mvno    ::Int64
  θ₂ ::Int64
  θ₁ ::Int64
  θ  ::Int64
  Xᵢ ::Int64
  Dᵢ ::Int64
  νᵢ ::Int64
  Rᵢ ::Int64
end

struct envType
  model     ::Int64
  mcid      ::Int64
  nSim      ::Int64
  ver       ::String
  pathOut   ::String
  pathImp   ::String
  fileOutp  ::String
  fileMcost ::String
  fileBase  ::String
  fileLog   ::String
  tol       ::Float64
  b1        ::Int64
  b2        ::Int64
  mc_mvno   ::Int64
end

struct labType
  network::Vector{String}
  prepost::Vector{String}
  product::Vector{String}
  prodIdx::Vector{Int64}
end

struct matType # input arrays for all markets
  y     ::Matrix{Float64}
  ν     ::Matrix{Float64}
  pop   ::Vector{Float64}
  ms    ::Matrix{Float64}
  p     ::Matrix{Float64}
  μ̃     ::Array{Float64,3}
  mvno  ::BitArray{2}
  mvnp  ::BitArray{2}
  own   ::BitArray{2}
  host  ::BitArray{2}
  active::BitArray{2}
  firm  ::Matrix{Int64}
end

struct parType
  θ₂     ::Vector{Float64}
  θ₁     ::Vector{Float64}
  αᵢ     ::Matrix{Float64}
  δ      ::Vector{Float64}
  indxeθ ::Vector{Int64}
end

struct setType
  region  ::Vector{Int64}
  qtr     ::Vector{Int64}
  product ::Matrix{Int64}
end

struct simType # minimal arrays for computation
  s      ::Vector{Float64}
  p      ::Vector{Float64}
  num    ::Vector{Float64}
  D      ::Vector{Float64}
  ms     ::Matrix{Float64}
  ∂s∂p   ::Array{Float64,2}
  ∂S∂p   ::Array{Float64,2}
  ∂D∂p   ::Array{Float64,2}
  μ̃      ::Array{Float64,3}
  ∂²S∂p² ::Array{Float64,3}
  ∂²D∂p² ::Array{Float64,3}
end

struct tabType
  retail   ::Array{Float64,2} # Retail price-markup table
  whsale   ::Array{Float64,2} # Wholesale price-markup table
  mcost    ::Array{Matrix{Float64},1}
  payoff   ::Array{Float64,3}
  Πperiod  ::Array{Float64,4} # Period profit under obs equil
  t9       ::Array{Float64,3}
  t9last   ::Array{Float64,4}
  t9ns     ::Array{Float64,4}
  fb       ::Array{Float64,3}
  fblast   ::Array{Float64,4}
  fbns     ::Array{Float64,4}
  bound    ::Array{Float64,3}
  divRatio ::Array{Float64,3}
  elasR    ::Array{Float64,3}
  elasW    ::Array{Float64,3}
  Πchannel ::Array{Float64,3}
  csplus   ::Array{Float64,2}
  tsplus   ::Array{Float64,3}
  Δprice   ::Array{Float64,3}
  lerner   ::Array{Float64,2}
  ebitda   ::Array{Float64,2}
end

struct equilType
  pdline ::BitArray{1}
  price  ::Matrix{Float64}
  mcost  ::Array{Matrix{Float64},1}
  wprice ::Array{Float64,2}
  markup ::Array{Float64,3}
  profit ::Array{Float64,3}
  csplus ::Array{Float64,1}
  demand ::Array{Float64,2}
  fail   ::BitArray{1}
end

struct indxType
  market   ::Vector{Int64}
  active   ::BitArray{2}
  anyActive::BitArray{1}
  mno      ::BitArray{1} # MNOs active in any of the markets
  mvno     ::BitArray{1} # MVNOs active in any of the markets
  mvnp     ::BitArray{2}
end

struct maskType
  mno  ::BitArray{1}
  mvno ::BitArray{1}
  free ::BitArray{1}
  Djpi ::BitArray{2}
end

end


struct StringStrategy <: SearchStrategy
    alphabet::Vector{Char}
    minsize::UInt32
    maxsize::UInt32

    StringStrategy(alphabet, minsize, maxsize) = new(alphabet, minsize, maxsize)
end

function draw(strategy::StringStrategy)
    length = rand(strategy.minsize:strategy.maxsize)
    return string(rand(strategy.alphabet, length)...)
end

Base.eltype(::StringStrategy) = String

strings(; minsize=0, maxsize=5000) = strings(:alpha; minsize=minsize, maxsize=maxsize)
strings(sym::Symbol; minsize=0, maxsize=5000) = strings(Val(sym); minsize=minsize, maxsize=maxsize)
strings(::Val{:alpha}; minsize=0, maxsize=5000) = StringStrategy(collect(Iterators.flatten(('A':'Z', 'a':'z'))), minsize, maxsize)
strings(::Val{:alphanum}; minsize=0, maxsize=5000) = StringStrategy(collect(Iterators.flatten(('0':'9', 'A':'Z', 'a':'z'))), minsize, maxsize)
strings(alphabet; minsize=0, maxsize=5000) = StringStrategy(alphabet, minsize, maxsize)

struct SymbolStrategy <: SearchStrategy
    alphabet::Vector{Char}
    minsize::UInt32
    maxsize::UInt32

    SymbolStrategy(alphabet, minsize, maxsize) = new(alphabet, minsize, maxsize)
end

function draw(strategy::SymbolStrategy)
    length = rand(strategy.minsize:strategy.maxsize)
    return Symbol(rand(strategy.alphabet, length)...)
end

Base.eltype(::SymbolStrategy) = Symbol

symbols(; minsize=0, maxsize=5000) = symbols(:alpha; minsize=minsize, maxsize=maxsize)
symbols(sym::Symbol; minsize=0, maxsize=5000) = symbols(Val(sym); minsize=minsize, maxsize=maxsize)
symbols(::Val{:alpha}; minsize=0, maxsize=5000) = SymbolStrategy(collect(Iterators.flatten(('A':'Z', 'a':'z'))), minsize, maxsize)
symbols(::Val{:alphanum}; minsize=0, maxsize=5000) = SymbolStrategy(collect(Iterators.flatten(('0':'9', 'A':'Z', 'a':'z'))), minsize, maxsize)
symbols(alphabet; minsize=0, maxsize=5000) = SymbolStrategy(alphabet, minsize, maxsize)


#############################################################################
# ECOS.jl
# Wrapper around the ECOS solver https://github.com/ifa-ethz/ecos
# See http://github.com/jump-dev/ECOS.jl
#############################################################################
# ECOS.jl
# Contains the wrapper itself
#############################################################################

module ECOS

import ECOS_jll

using CEnum

include("gen/libecos_common.jl")
include("gen/libecos_api.jl")

"""
    unsafe_add_settings(p::Ptr{pwork}, options::Dict{Symbol})

Add settings to the ECOS model.
"""
function unsafe_add_settings(p::Ptr{pwork}, options::Dict{Symbol})
    problem = unsafe_load(p)::pwork
    stgs = unsafe_load(problem.stgs)::settings
    new_stgs = settings(
        [get(options, k, getfield(stgs, k)) for k in fieldnames(settings)]...,
    )
    unsafe_store!(problem.stgs, new_stgs)
    return
end

include("MOI_wrapper/MOI_wrapper.jl")

function __init__()
    global ecos = ECOS_jll.libecos
    libecos_version = VersionNumber(unsafe_string(ECOS_ver()))
    if libecos_version != v"2.0.8"
        error(
            "Current ECOS version installed is $(libecos_version), but we " *
            "require version 2.0.8",
        )
    end
    return
end

end # module


using FileIO

struct LaxHeader
    version::UInt32
end

function Base.read(io::IO, ::Type{LaxHeader})
    header = LaxHeader(
        read(io, UInt32),
        )
end

struct LaxQuadtreeHeader
    ssignature::UInt32  # LASS
    stype::UInt32
    qsignature::UInt32  # LASQ
    version::UInt32
    levels::UInt32
    level_index::UInt32
    implicit_levels::UInt32
    min_x::Float64
    max_x::Float64
    min_y::Float64
    max_y::Float64
end

function Base.read(io::IO, ::Type{LaxQuadtreeHeader})
    header = LaxQuadtreeHeader(
        read(io, UInt32),
        read(io, UInt32),
        read(io, UInt32),
        read(io, UInt32),
        read(io, UInt32),
        read(io, UInt32),
        read(io, UInt32),
        Float64(read(io, Float32)),
        Float64(read(io, Float32)),
        Float64(read(io, Float32)),
        Float64(read(io, Float32))
    )
end

struct LaxIntervalHeader
    signature::UInt32
    version::UInt32
    number_cells::UInt32
end

function Base.read(io::IO, ::Type{LaxIntervalHeader})
    header = LaxIntervalHeader(
        read(io, UInt32),
        read(io, UInt32),
        read(io, UInt32),
    )
end

struct LaxIntervalCell
    cell_index::Int32
    number_intervals::UInt32
    number_points::UInt32
end

function Base.read(io::IO, ::Type{LaxIntervalCell})
    header = LaxIntervalCell(
        read(io, UInt32),
        read(io, UInt32),
        read(io, UInt32)
    )
end

struct LaxIntervalCellInterval
    _start::UInt32
    _end::UInt32
end

function Base.read(io::IO, ::Type{UnitRange{T}}) where T <: Integer
    range = UnitRange(
        Int64(read(io, UInt32))+1,  # .lax uses 0 based indices
        Int64(read(io, UInt32))+1   # Int32 could theoretically overflow
    )
end

function load(f::File{format"LAX"})
    open(f) do s
        skipmagic(s)
        load(s)
    end
end

function load(s::Stream{format"LAX"})
    # magic bytes are skipped
    header = read(s, LaxHeader)

    # read quadtree
    qheader = read(s, LaxQuadtreeHeader)
    # if not, qsignature and version are missing
    # but we ignore this other lax type for now
    qheader.level_index != 0 && error("Level index other than 0 is not supported yet.")

    # create quadtree
    qt = quadtree(qheader)

    # read cell header
    iheader = read(s, LaxIntervalHeader)
    ncells = iheader.number_cells

    # total number of points
    total_points = 0

    for i = 1:ncells
        # Read cell and its intervals
        qcell = read(s, LaxIntervalCell)
        total_points += qcell.number_points
        intervals = Vector{UnitRange{Integer}}(undef, qcell.number_intervals)

        for j = 1:qcell.number_intervals
            intervals[j] = read(s, UnitRange{Integer})
        end

        # Create cell in qt with intervals
        quadtree!(qt, qcell.cell_index, intervals)

    end

    # assert we are at end of file
    @assert eof(s)

    @info("Processed $(s.filename) with $(total_points) points.")

    return qt
end


##
##  Java Information Dynamics Toolkit (JIDT)
##  Copyright (C) 2012, Joseph T. Lizier
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see <http://www.gnu.org/licenses/>.
##

# = Example 1 - Transfer entropy on binary data =

# Simple transfer entropy (TE) calculation on binary data using the discrete TE calculator:

# Import the JavaCall package:
using JavaCall;

# Change location of jar to match yours:
jarLocation = "../../infodynamics.jar";
# Start the JVM supplying classpath and heap size
#  (increase memory here if you get crashes due to not enough space)
JavaCall.init(["-Djava.class.path=$(jarLocation)", "-Xmx128M"]);

# Generate some random binary data.
sourceArray=rand(0:1, 100);
destArray = [0; sourceArray[1:99]];
sourceArray2=rand(0:1, 100);

# Create a TE calculator and run it:
teClass = @jimport infodynamics.measures.discrete.TransferEntropyCalculatorDiscrete;
teCalc=teClass((jint, jint), 2, 1);
jcall(teCalc, "initialise", Void, ()); # This is how to indicate a void return type and arguments

# We can pass simple arrays of ints directly in:
jcall(teCalc, "addObservations", Void, (Array{jint,1}, Array{jint,1}),
				     sourceArray, destArray);
result = jcall(teCalc, "computeAverageLocalOfObservations", jdouble, ());
@printf("For copied source, result should be close to 1 bit : %.4f\n", result);

jcall(teCalc, "initialise", Void, ());
jcall(teCalc, "addObservations", Void, (Array{jint,1}, Array{jint,1}),
				     sourceArray2, destArray);
result2 = jcall(teCalc, "computeAverageLocalOfObservations", jdouble, ());
@printf("For random source, result should be close to 0 bits: %.4f\n", result2);


export init_random_pars!

"""
    build_rng_generator_T(T::abstractArray, seed)

builds an RNG generator for the type of T.
    Defaults to MersenneTWister.
"""
function build_rng_generator_T(arrT::Array, seed)
    return MersenneTwister(seed)
end

"""
    init_random_pars!([rng=GLOBAL_RNG], net; sigma=0.01 )

Initializes all weights of the neural network to a random distribution with
variance `sigma`.
"""
init_random_pars!(net::NeuralNetwork, args...; kwargs...) = init_random_pars!(GLOBAL_RNG, net, args...; kwargs...)
function init_random_pars!(rng::AbstractRNG, net; sigma=0.01)
    for f=trainable(net)
        init_random_pars!(rng, f; sigma=sigma)
    end
    return net
end

function init_random_pars!(rng::AbstractRNG, f::AbstractArray; sigma=0.01)
    randn!(rng, f)
    f .*= sqrt(sigma)

    return f
end


"""
    struct DirichletFESpace <: SingleFieldFESpace
      space::SingleFieldFESpace
    end
"""
struct DirichletFESpace{S<:SingleFieldFESpace} <: SingleFieldFESpace
  space::S
  function DirichletFESpace(space::SingleFieldFESpace)
    new{typeof(space)}(space)
  end
end

ConstraintStyle(::Type{DirichletFESpace{B}}) where B = ConstraintStyle(B)

CellField(t::DirichletFESpace,cell_vals) = CellField(t.space,cell_vals)

get_cell_isconstrained(f::DirichletFESpace) = get_cell_isconstrained(f.space)

get_cell_constraints(f::DirichletFESpace) = get_cell_constraints(f.space)

function get_free_dof_ids(f::DirichletFESpace)
  get_dirichlet_dof_ids(f.space)
end

function get_vector_type(f::DirichletFESpace)
  get_vector_type(f.space)
end

function get_cell_dof_ids(f::DirichletFESpace)
  lazy_map(Broadcasting(-),get_cell_dof_ids(f.space))
end

function get_dirichlet_dof_ids(f::DirichletFESpace)
  get_free_dof_ids(f.space)
end

function num_dirichlet_tags(f::DirichletFESpace)
  1
end

function get_dirichlet_dof_tag(f::DirichletFESpace)
  ones(Int8,num_dirichlet_dofs(f))
end

function scatter_free_and_dirichlet_values(f::DirichletFESpace,fv,dv)
  scatter_free_and_dirichlet_values(f.space,dv,fv)
end

function gather_free_and_dirichlet_values!(fv,dv,f::DirichletFESpace,cv)
  gather_free_and_dirichlet_values!(dv,fv,f.space,cv)
  (fv,dv)
end

function TrialFESpace(f::DirichletFESpace)
  U = TrialFESpace(f.space)
  DirichletFESpace(U)
end

function get_cell_shapefuns(f::DirichletFESpace)
  get_cell_shapefuns(f.space)
end

function get_cell_shapefuns_trial(f::DirichletFESpace)
  get_cell_shapefuns_trial(f.space)
end

function get_cell_dof_basis(f::DirichletFESpace)
  get_cell_dof_basis(f.space)
end

function get_triangulation(f::DirichletFESpace)
  get_triangulation(f.space)
end


function initialize_outer!(
    device_states,
    static::PSY.StaticInjection,
    dynamic_device::PSY.DynamicInverter{
        C,
        PSY.OuterControl{PSY.VirtualInertia, PSY.ReactivePowerDroop},
        IC,
        DC,
        P,
        F,
    },
) where {
    C <: PSY.Converter,
    IC <: PSY.InnerControl,
    DC <: PSY.DCSource,
    P <: PSY.FrequencyEstimator,
    F <: PSY.Filter,
}

    #Obtain external states inputs for component
    external_ix = get_input_port_ix(
        dynamic_device,
        PSY.OuterControl{PSY.VirtualInertia, PSY.ReactivePowerDroop},
    )
    Vr_filter = device_states[external_ix[1]]
    Vi_filter = device_states[external_ix[2]]
    Ir_filter = device_states[external_ix[3]]
    Ii_filter = device_states[external_ix[4]]

    Vr_cnv = get_inner_vars(dynamic_device)[Vr_cnv_var]
    Vi_cnv = get_inner_vars(dynamic_device)[Vi_cnv_var]
    θ0_oc = angle(Vr_cnv + 1im * Vi_cnv)

    #Obtain additional expressions
    p_elec_out = Ir_filter * Vr_filter + Ii_filter * Vi_filter
    q_elec_out = -Ii_filter * Vr_filter + Ir_filter * Vi_filter

    #Update inner_vars
    get_inner_vars(dynamic_device)[P_ES_var] = p_elec_out
    #Update states
    outer_ix = get_local_state_ix(
        dynamic_device,
        PSY.OuterControl{PSY.VirtualInertia, PSY.ReactivePowerDroop},
    )
    outer_states = @view device_states[outer_ix]
    outer_states[1] = PSY.get_ω_ref(dynamic_device) #ω
    outer_states[2] = θ0_oc #θ_oc
    outer_states[3] = q_elec_out #qm

    #Update inner vars
    get_inner_vars(dynamic_device)[θ_oc_var] = θ0_oc
    get_inner_vars(dynamic_device)[ω_oc_var] = PSY.get_ω_ref(dynamic_device)
    #Update Q_ref. Initialization assumes q_ref = q_elec_out of PF solution
    PSY.get_ext(dynamic_device)[CONTROL_REFS][P_ref_index] = p_elec_out
    PSY.set_P_ref!(PSY.get_active_power(PSY.get_outer_control(dynamic_device)), p_elec_out)
    PSY.get_ext(dynamic_device)[CONTROL_REFS][Q_ref_index] = q_elec_out
end

function initialize_outer!(
    device_states,
    static::PSY.StaticInjection,
    dynamic_device::PSY.DynamicInverter{
        C,
        PSY.OuterControl{PSY.ActivePowerDroop, PSY.ReactivePowerDroop},
        IC,
        DC,
        P,
        F,
    },
) where {
    C <: PSY.Converter,
    IC <: PSY.InnerControl,
    DC <: PSY.DCSource,
    P <: PSY.FrequencyEstimator,
    F <: PSY.Filter,
}

    #Obtain external states inputs for component
    external_ix = get_input_port_ix(
        dynamic_device,
        PSY.OuterControl{PSY.ActivePowerDroop, PSY.ReactivePowerDroop},
    )
    Vr_filter = device_states[external_ix[1]]
    Vi_filter = device_states[external_ix[2]]
    Ir_filter = device_states[external_ix[3]]
    Ii_filter = device_states[external_ix[4]]

    Vr_cnv = get_inner_vars(dynamic_device)[Vr_cnv_var]
    Vi_cnv = get_inner_vars(dynamic_device)[Vi_cnv_var]
    θ0_oc = angle(Vr_cnv + 1im * Vi_cnv)

    #Obtain additional expressions
    p_elec_out = Ir_filter * Vr_filter + Ii_filter * Vi_filter
    q_elec_out = -Ii_filter * Vr_filter + Ir_filter * Vi_filter

    #Update inner_vars
    get_inner_vars(dynamic_device)[P_ES_var] = p_elec_out
    #Update states
    outer_ix = get_local_state_ix(
        dynamic_device,
        PSY.OuterControl{PSY.ActivePowerDroop, PSY.ReactivePowerDroop},
    )
    outer_states = @view device_states[outer_ix]
    outer_states[1] = θ0_oc #θ_oc
    outer_states[2] = p_elec_out #pm
    outer_states[3] = q_elec_out #qm

    #Update inner vars
    get_inner_vars(dynamic_device)[θ_oc_var] = θ0_oc
    get_inner_vars(dynamic_device)[ω_oc_var] = PSY.get_ω_ref(dynamic_device)
    #Update Q_ref. Initialization assumes q_ref = q_elec_out of PF solution
    PSY.get_ext(dynamic_device)[CONTROL_REFS][P_ref_index] = p_elec_out
    PSY.set_P_ref!(PSY.get_active_power(PSY.get_outer_control(dynamic_device)), p_elec_out)
    PSY.get_ext(dynamic_device)[CONTROL_REFS][Q_ref_index] = q_elec_out
end

function initialize_outer!(
    device_states,
    static::PSY.StaticInjection,
    dynamic_device::PSY.DynamicInverter{
        C,
        PSY.OuterControl{PSY.ActivePowerPI, PSY.ReactivePowerPI},
        IC,
        DC,
        P,
        F,
    },
) where {
    C <: PSY.Converter,
    IC <: PSY.InnerControl,
    DC <: PSY.DCSource,
    P <: PSY.FrequencyEstimator,
    F <: PSY.Filter,
}

    #Obtain external states inputs for component
    external_ix = get_input_port_ix(
        dynamic_device,
        PSY.OuterControl{PSY.ActivePowerPI, PSY.ReactivePowerPI},
    )
    Vr_filter = device_states[external_ix[1]]
    Vi_filter = device_states[external_ix[2]]
    Ir_filter = device_states[external_ix[3]]
    Ii_filter = device_states[external_ix[4]]
    Ir_cnv = device_states[external_ix[5]]
    Ii_cnv = device_states[external_ix[6]]

    #Obtain additional expressions
    θ0_oc = get_inner_vars(dynamic_device)[θ_freq_estimator_var]
    I_dq_cnv = ri_dq(θ0_oc + pi / 2) * [Ir_cnv; Ii_cnv]
    p_elec_out = Ir_filter * Vr_filter + Ii_filter * Vi_filter
    q_elec_out = -Ii_filter * Vr_filter + Ir_filter * Vi_filter

    #Get Outer Controller parameters
    outer_control = PSY.get_outer_control(dynamic_device)
    active_power_control = PSY.get_active_power(outer_control)
    Ki_p = PSY.get_Ki_p(active_power_control) #Integral Gain
    reactive_power_control = PSY.get_reactive_power(outer_control)
    Ki_q = PSY.get_Ki_q(reactive_power_control) #Integral Gain

    #Update inner_vars
    get_inner_vars(dynamic_device)[P_ES_var] = p_elec_out
    #Update states
    outer_ix = get_local_state_ix(
        dynamic_device,
        PSY.OuterControl{PSY.ActivePowerPI, PSY.ReactivePowerPI},
    )
    outer_states = @view device_states[outer_ix]
    outer_states[1] = I_dq_cnv[q] / Ki_p #σp_oc
    outer_states[2] = p_elec_out #p_oc
    outer_states[3] = I_dq_cnv[d] / Ki_q #σq_oc
    outer_states[4] = q_elec_out #q_oc

    #Update inner vars
    get_inner_vars(dynamic_device)[θ_oc_var] = θ0_oc
    get_inner_vars(dynamic_device)[ω_oc_var] = PSY.get_ω_ref(dynamic_device)
    get_inner_vars(dynamic_device)[Id_oc_var] = I_dq_cnv[d]
    get_inner_vars(dynamic_device)[Iq_oc_var] = I_dq_cnv[q]
    #Update Q_ref. Initialization assumes q_ref = q_elec_out from PF solution
    PSY.get_ext(dynamic_device)[CONTROL_REFS][P_ref_index] = p_elec_out
    PSY.set_P_ref!(PSY.get_active_power(PSY.get_outer_control(dynamic_device)), p_elec_out)
    PSY.get_ext(dynamic_device)[CONTROL_REFS][Q_ref_index] = q_elec_out
    PSY.set_Q_ref!(
        PSY.get_reactive_power(PSY.get_outer_control(dynamic_device)),
        q_elec_out,
    )
end


include("iPredict.jl/src/iPredict.jl")
using .iPredict
using DataFrames
using Plots, Plots.Measures
using JLD2
using DelimitedFiles
using StatsBase

#Initialise variables
GeneIsoformRelationships = "data/gencode_human_gene_isoform_relationships.txt"
NumSimulations = 100
NumIsoformsToSimulate = 4
filteringThreshold = 4
alpha_vals = [0.45, 0.7, 0.95, 1.2, 1.45]
beta_vals = reverse(alpha_vals)
scnorm_counts = ["H1_24_scnorm_counts.txt"]#,
"H1_96_scnorm_counts.txt",
"H9_24_scnorm_counts.txt",
"H9_96_scnorm_counts.txt"]
pFP = 0.01
pFN = 0.04
isoChoiceModels = ["Random", "UniformObserved", "CellVariable"]

for countsMatrix in scnorm_counts

    output = "figures/figure2/" * countsMatrix
    data_out = "figures/figure2_data/" * countsMatrix

    for model in isoChoiceModels

        for i in 1:length(alpha_vals)

            output = "figures/figure2/" * model * string(alpha_vals[i]) * "_" *
            string(beta_vals[i]) * "_" * countsMatrix
            data_out = "figures/figure2_data/" * model * string(alpha_vals[i]) * "_" *
            string(beta_vals[i]) * "_" * countsMatrix

            out = globalPredict("data/" * countsMatrix, GeneIsoformRelationships,
            NumSimulations, output, NumIsoformsToSimulate, filteringThreshold,
            false, alpha_vals[i], beta_vals[i], pFN, pFP, "none", model,
            data_out)
        end
    end
end

function make_hist(path, titleArg)
    Arr = readdlm(path)

    if (titleArg == "3 Isoforms" &&
        path == "figures/figure2_data/H1_96_scnorm_counts.txt_3_isoforms_array.txt")
        #Untransformed plot
        p1 = histogram(Arr,
        title = titleArg,
        xlims = (0, 1.2),
        ylims = (0, 5),
        ylabel = "Probability Density",
        normalize=:pdf,
        bins = 100,
        axis = (font(100)), titlefontsize=100, yticks = 0:5:10,
        margin = 25mm)

    elseif titleArg == "Real" #|| titleArg == "alpha=0.45, beta=1.45"
        p1 = histogram(Arr,
        title = titleArg,
        xlims = (0, 1.2),
        ylims = (0, 5),
        normalize=:pdf,
        xlabel = "Mean No. Isoforms\nper Gene per Cell",
        bins = 100,
        axis = (font(100)), titlefontsize=100, yticks = 0:5:10,
        margin = 25mm)

    else
        #Untransformed plot
        p1 = histogram(Arr,
        title = titleArg,
        xlims = (0, 1.2),
        ylims = (0, 5),
        normalize=:pdf,
        bins = 100,
        axis = (font(100)), titlefontsize=100, yticks = 0:5:10,
        margin = 25mm)
    end
    vline!([mean(Arr)], color = :black, linewidth = 8)
    return p1
end

v0 =  make_hist("figures/figure2_data/Random0.45_1.45_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=0.45, beta=1.45")
v1 =  make_hist("figures/figure2_data/Random0.7_1.2_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=0.7, beta=1.2")
v2 =  make_hist("figures/figure2_data/Random0.95_0.95_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=0.95, beta=0.95")
v3 =  make_hist("figures/figure2_data/Random1.2_0.7_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=1.2, beta=0.7")
v4 =  make_hist("figures/figure2_data/Random1.45_0.45_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=1.45, beta=0.45")

pD = plot(v4, v3, v2, v1, v0,
       layout = (5,1), legend = false, size = (250*8, 600*8),
       axis = (font(180)), titlefontsize=160, margin = 30mm)

savefig(pD, "figures/figure2/Random_figure2D.png")

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

v0 =  make_hist("figures/figure2_data/UniformObserved0.45_1.45_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=0.45, beta=1.45")
v1 =  make_hist("figures/figure2_data/UniformObserved0.7_1.2_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=0.7, beta=1.2")
v2 =  make_hist("figures/figure2_data/UniformObserved0.95_0.95_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=0.95, beta=0.95")
v3 =  make_hist("figures/figure2_data/UniformObserved1.2_0.7_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=1.2, beta=0.7")
v4 =  make_hist("figures/figure2_data/UniformObserved1.45_0.45_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=1.45, beta=0.45")

pD = plot(v4, v3, v2, v1, v0,
       layout = (5,1), legend = false, size = (250*8, 600*8),
       axis = (font(180)), titlefontsize=160, margin = 30mm)

savefig(pD, "figures/figure2/UniformObserved_figure2D.png")

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

v0 =  make_hist("figures/figure2_data/CellVariable0.45_1.45_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=0.45, beta=1.45")
v1 =  make_hist("figures/figure2_data/CellVariable0.7_1.2_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=0.7, beta=1.2")
v2 =  make_hist("figures/figure2_data/CellVariable0.95_0.95_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=0.95, beta=0.95")
v3 =  make_hist("figures/figure2_data/CellVariable1.2_0.7_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=1.2, beta=0.7")
v4 =  make_hist("figures/figure2_data/CellVariable1.45_0.45_H1_24_scnorm_counts.txt_1_isoforms_array.txt", "alpha=1.45, beta=0.45")

pD = plot(v4, v3, v2, v1, v0,
       layout = (5,1), legend = false, size = (250*8, 600*8),
       axis = (font(180)), titlefontsize=160, margin = 30mm)

savefig(pD, "figures/figure2/CellVariable_figure2D.png")

####################################################
# OVERLAP
####################################################

function make_hist(path, titleArg)
    Arr = readdlm(path)

    if (titleArg == "3 Isoforms" &&
        path == "figures/figure2_data/H1_96_scnorm_counts.txt_3_isoforms_array.txt")
        #Untransformed plot
        p1 = histogram(Arr,
        title = titleArg,
        color = palette(:default)[2],
        xlims = (0, 1.2),
        ylims = (0, 5),
        ylabel = "Probability Density",
        normalize=:pdf,
        bins = 100,
        axis = (font(100)), titlefontsize=100, yticks = 0:5:10,
        margin = 25mm)

    elseif titleArg == "Real" #|| titleArg == "alpha=0.45, beta=1.45"
        p1 = histogram(Arr,
        title = titleArg,
        color = palette(:default)[2],
        xlims = (0, 1.2),
        ylims = (0, 5),
        normalize=:pdf,
        xlabel = "Mean No. Isoforms\nper Gene per Cell",
        bins = 100,
        axis = (font(100)), titlefontsize=100, yticks = 0:5:10,
        margin = 25mm)

    else
        #Untransformed plot
        p1 = histogram(Arr,
        title = titleArg,
        color = palette(:default)[2],
        xlims = (0, 1.2),
        ylims = (0, 5),
        normalize=:pdf,
        bins = 100,
        axis = (font(100)), titlefontsize=100, yticks = 0:5:10,
        margin = 25mm)
    end
    vline!([mean(Arr)], color = :black, linewidth = 8)
    return p1
end

v0 =  make_hist("figures/figure2_data/Random0.45_1.45_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=0.45, beta=1.45")
v1 =  make_hist("figures/figure2_data/Random0.7_1.2_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=0.7, beta=1.2")
v2 =  make_hist("figures/figure2_data/Random0.95_0.95_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=0.95, beta=0.95")
v3 =  make_hist("figures/figure2_data/Random1.2_0.7_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=1.2, beta=0.7")
v4 =  make_hist("figures/figure2_data/Random1.45_0.45_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=1.45, beta=0.45")

pD = plot(v4, v3, v2, v1, v0,
       layout = (5,1), legend = false, size = (250*8, 600*8),
       axis = (font(180)), titlefontsize=160, margin = 30mm)

savefig(pD, "figures/figure2/Random_figure2D_overlap.png")

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

v0 =  make_hist("figures/figure2_data/UniformObserved0.45_1.45_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=0.45, beta=1.45")
v1 =  make_hist("figures/figure2_data/UniformObserved0.7_1.2_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=0.7, beta=1.2")
v2 =  make_hist("figures/figure2_data/UniformObserved0.95_0.95_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=0.95, beta=0.95")
v3 =  make_hist("figures/figure2_data/UniformObserved1.2_0.7_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=1.2, beta=0.7")
v4 =  make_hist("figures/figure2_data/UniformObserved1.45_0.45_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=1.45, beta=0.45")

pD = plot(v4, v3, v2, v1, v0,
       layout = (5,1), legend = false, size = (250*8, 600*8),
       axis = (font(180)), titlefontsize=160, margin = 30mm)

savefig(pD, "figures/figure2/UniformObserved_figure2D_overlap.png")

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

v0 =  make_hist("figures/figure2_data/CellVariable0.45_1.45_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=0.45, beta=1.45")
v1 =  make_hist("figures/figure2_data/CellVariable0.7_1.2_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=0.7, beta=1.2")
v2 =  make_hist("figures/figure2_data/CellVariable0.95_0.95_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=0.95, beta=0.95")
v3 =  make_hist("figures/figure2_data/CellVariable1.2_0.7_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=1.2, beta=0.7")
v4 =  make_hist("figures/figure2_data/CellVariable1.45_0.45_H1_24_scnorm_counts.txt_1_isoforms_overlap_array.txt", "alpha=1.45, beta=0.45")

pD = plot(v4, v3, v2, v1, v0,
       layout = (5,1), legend = false, size = (250*8, 600*8),
       axis = (font(180)), titlefontsize=160, margin = 30mm)

savefig(pD, "figures/figure2/CellVariable_figure2D_overlap.png")