text:
#
#  Simple Tree container with no Key and no Rebalancing
#  To be used in RandomProjectionTree
#
#

using Printf

export
# TreeNode methods
    TreeNode,
    NullNode,
    dumpPos,
    isvoid,
    hasChildren,
    insertChildInNode,
#
#             Tree
#             depth first , left to right iterator
    Tree,
    getFirstLeftLeaf,
    getNextLeafRight,
    getNextLeafLeft,
    getDepthFirstNextRight,
    getDepthFirstNextLeft,
    getLeftSibling,
    getRightSibling

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

const debugLevel = 0

"""
# TreeNode{T,U}

FIELDS
------

a Treenode consists in

*  an array of data of generic struct T for which there must exist a Distance.
*  depth : depth in tree , root node is at depth 0.
*  rankInParent : where a node is in parent.children field
*  possibly a parent node
*  an array of children node.
*  some private data of type U for possible use by application using Tree.

CONSTRUCTORS
 -----------

1.    `function TreeNode{T,U}(parentArg::TreeNode{T,U}, dataArg::T)`.
       full constructor with parent node specified and data

2.    `function TreeNode{T,U}(dataArg::T, privatedata::U)`.
       constructor for root node (without parent).

3.    `function TreeNode{T, U}(dataArg::T)`
"""
mutable struct TreeNode{T, U}
    # fields
    data::T   # a vector of Data
    depth::Int64
    rankInParent::Int64
    parent::Union{TreeNode{T,U}, Nothing}  # at depth 0 we have a null parent
    # lateral chaining
    children::Array{TreeNode{T,U},1}
    # to store any application dependant data if necessary
    private::Union{U, Nothing}
    #
    l::Threads.ReentrantLock
    # constructor with a parent
    function TreeNode{T,U}(parentArg::TreeNode{T,U}, dataArg::T) where {T,U}
    #    @debug " TreeNode constructor with parent"
        # should check all array have the same dimension
        parent = parentArg
        depth = parent.depth+1
        rankInParent = length(parent.children)+1
        children=Array{TreeNode{T, U},1}()
        nullprivatedata = nothing
        new(dataArg, depth, 0 , parent, children, nullprivatedata, Threads.ReentrantLock() )
        #
        # we could do:
        # n.rankInParent = length(parent.children)+1
        # push!(parent.children, n)
        # n
        # This last line is necessary so that the last evaluated line is the new object
        # otherwise it is parent.children that is returned !!!
    end
    # constructor for root node of Tree with private data
    function TreeNode{T, U}(dataArg::T, privatedata::U) where{T,U}
    #    @debug " TreeNode constructor root"
        depth = 0
        rankInParent = 0
        parent = nothing
        data=dataArg
        children=Array{TreeNode{T, U},1}()
        new(data, depth,rankInParent, parent, children, privatedata , Threads.ReentrantLock())
    end
    # constructor for root node of Tree without private data
    function TreeNode{T, U}(dataArg::T) where{T,U}
    #    @debug " TreeNode constructor root"
        depth = 0
        rankInParent = 0
        parent = nothing
        data=dataArg
        children=Array{TreeNode{T, U},1}()
        nullprivatedata = nothing
        new(data, depth,rankInParent, parent, children, nullprivatedata, Threads.ReentrantLock())
    end
end

const NullNode = nothing

##############################################################################
#
#  T will most often be an Array of Float64.
#  At least it must be on object for which there is a distance
#
#  All iterations functions take a node as input and return a Nullable
#
############################################################################

"""
 Tree{T,U}  and constructors

a Tree consists in

*  a root node TreeNode{T, U}
*  a lock to serialize operations on Tree if parallel computations are done

"""
mutable struct Tree{T, U}
    #
    root::TreeNode{T, U}
    # to serialize operations on tree
    l::ReentrantLock
    # constructor
    function Tree{T,U}(dataArg::T)  where {T,U}
        root = TreeNode{T,U}(dataArg)
        new(root,ReentrantLock())
    end
end

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

"""
dump recursive posiiton

"""
function dumpPos(out::IOStream, n::TreeNode)
    if n.depth > 1
        @printf out "\n depth %d rank in Parent %d" n.depth n.rankInParent
        if n.parent !== nothing
            dumpPos(out, n.parent)
        end
    end
end

function dumpPos(n::TreeNode)
    if n.depth == 0
        @printf stdout "\n\n"
    end
    if n.depth >= 0
        @printf stdout "\n depth %d rank in Parent %d" n.depth n.rankInParent
        if n.parent !== nothing
            dumpPos(n.parent)
        end
    end
end

function isvoid(n::TreeNode)
    if length(n.data) == 0 && length(n.children) == 0
        return true
    else
        return false
    end
end

function hasChildren(n::TreeNode)
    if length(n.hasChildren) == 0
        return false
    else
        return true
    end
end

# This is useful only if we TreeNode constructor do not modify parent
# this is preferred to ensure serialization.
# But that put a lock on all insertion instead of insertion in a given node

function insertChildInNode(n::TreeNode, child::TreeNode)
    lock(n.l)
#    #
    push!(n.children,child)
    child.parent = n
    child.rankInParent = length(n.children)
    #
    unlock(n.l)
end

function getParent(n::TreeNode)
    return n.parent
end

#
#  find next to the right with first going up until a move right is possible
#  and does the right move.
#  to be abstracted in an iterator Depth first Left To Right
#

function goToNextRight(t::Tree, n::TreeNode)
    next = nothing
    #
    if debugLevel >= 1
        println("\n\n In goToNextRight :")
        dumpPos(n)
    end
    #
    node = n
    more = true
    while more
        if debugLevel >= 2
            dumpPos(node)
        end
        #
        if node.depth != 0 && node.rankInParent < length(node.parent.children)
            more = false   # we can go right
            next = node.parent.children[node.rankInParent+1]
        else
            if node.parent === nothing
                more = false
                next = nothing
            else
                node = node.parent
            end
        end
    end  # end while
    if debugLevel >= 1
        println("\n exiting  goToNextRight :")
        if next !== nothing
            dumpPos(next)
        else
            println("  goToNextRight returns null")
        end
    end
    #
    return next
end

# returns Union{TreeNode{T,U}}, Nothing}
# find next left node by going upward until we can go left.

function goToNextLeft(t::Tree, n::TreeNode)
    next = nothing
    #
    node = n
    more = true
    while more
        #
        if node.depth != 0 &&  node.rankInParent > 1
            more = false   # we can go left
            next = node.parent.children[node.rankInParent-1]
        else
            if node.parent === nothing
                more = false
                next = nothing
            else
                node = node.parent
            end
        end
    end  # end while
    #
    return next
end

"""
#   function getDepthFirstNextRight(t::Tree, n::TreeNode)

A function to iterate through the tree by depth first left to right traversal

    Returns a node or nothing if end of Tree is reached.
"""
function getDepthFirstNextRight(t::Tree, n::TreeNode)
    next = nothing
    # try to go down, if not possible go right
    if length(n.children) > 0
        next = n.children[1]
    else
        next = goToNextRight(t,n)       # we must go up until some node is not the rightmost in parent
    end
    #
    return next
end

# iteration depth first right to left

"""
#   function getDepthFirstNextLeft(t::Tree, n::TreeNode)

    A function to iterate through the tree by depth first right to left traversal

    Returns a node or nothing if end of Tree is reached.
"""
function getDepthFirstNextLeft(t::Tree, n::TreeNode)
    next = nothing
    # try to go down, if not possible go right
    if length(n.children) > 0
        next = n.children[length(n.children)]
    else
        next = goToNextLeft(t,n)       # we must go up until some node is not the rightmost in parent
    end
    #
    return next
end

"""
#   function getLeftSibling(t::Tree, n::TreeNode)

    return left sibling (left neighbour at same depth) of a node or nothing
"""
function getLeftSibling(t::Tree, n::TreeNode)
    if n.depth == 0
        return nothing
    end
    leftSibling =  goToNextLeft(t,n)
    if leftSibling === nothing
        return leftSibling
    end
    #
    # now we must go down to depth of n or until we are at a leaf
    #
    rnode = leftSibling
    if rnode.depth < n.depth
        moreDown = true
        while moreDown
            if length(rnode.children) > 0
                rnode  =  last(rnode.children)
                if rnode.depth == n.depth
                    moreDown = false
                end
            else
                moreDown = false
            end
        end
    end
    #
    if rnode.depth == n.depth
        return rnode
    else
        return nothing
    end
end

"""
#   function getRightSibling(t::Tree, n::TreeNode)

    return right sibling (right neighbour at same depth) of a node or nothing
"""
function getRightSibling(t::Tree, n::TreeNode)
    if n.depth == 0
        return nothing
    end
    rightSibling = goToNextRight(t,n)
    if rightSibling === nothing
        return rightSibling
    end
    #
    # now we must go down to depth of n or until we are at a leaf
    #
    rnode = rightSibling
    if rnode.depth < n.depth
        moreDown = true
        while moreDown
            if length(rnode.children) > 0
                rnode = first(rnode.children)
                if rnode.depth == n.depth
                    moreDown = false
                end
            else
                moreDown = false
            end
        end
    end
    #
    if rnode.depth == n.depth
        return rnode
    else
        return nothing
    end
end

# to be abstracted in a leaf  left to right iterator

"""
#    function getFirstLeftLeaf(t::Tree)

returns the left most leaf of the tree

to be chained with :
function getNextLeafRight(t::Tree, n::TreeNode)

"""
function getFirstLeftLeaf(t::Tree)
    # iterates depth first until no more children
    node = t.root
    while length(node.children) > 0
        node = node.children[1]
    end
    #
    return node
end

"""
#    function getFirstRightLeaf(t::Tree)

returns the rightmost leaf of the tree.
The call to this functio is to be chained with :
    function getNextLeafLeft(t::Tree, n::TreeNode)
"""
function getFirstRightLeaf(t::Tree)
    # iterates depth first until no more children
    node = t.root
    while length(node.children) > 0
        node = node.children[length(node.children)]
    end
    #
    return node
end

"""
#  function getNextLeafRight(t::Tree, n::TreeNode)

returns leaf or nothing

"""
function getNextLeafRight(t::Tree, n::TreeNode)
    nextleaf = nothing
    # go up until I can go right
    nextleaf = goToNextRight(t,n)
    # then go down until bottom
    while nextleaf !== nothing && length(nextleaf.children) > 0
        nextleaf = nextleaf.children[1]
    end
    #
    return nextleaf
end

"""
#  function getNextLeafLeft(t::Tree, n::TreeNode)

returns leaf or nothing
"""
function getNextLeafLeft(t::Tree, n::TreeNode)
    nextleaf = nothing
    # go up until I can go right
    nextleaf = goToNextLeft(t,n)
    # then go down until bottom
    while nextleaf !== nothing && length(nextleaf.children) > 0
        nextleaf = nextleaf.children[length(nextleaf.children)]
    end
    #
    return nextleaf
end

function deleteSubTree(t::Tree, n::TreeNode)
    parent = n.parent
    nb = length(parent.children)
    if nb > 1
        for i in n.rankInParent+1::nb
            parent.children[i-1] = parent.children[i]
            parent.children[i-1].rankInParent = i-1
        end
        resize!(parent.children, nb-1)
    else
        resize!(parent.children, 0)
    end
    finalize(n)
end


text:
platforms_and_sources = Dict(
    Platform("x86_64", "linux") => [
        ArchiveSource("https://developer.nvidia.com/compute/machine-learning/tensorrt/secure/8.0.3/tars/TensorRT-8.0.3.4.Linux.x86_64-gnu.cuda-10.2.cudnn8.2.tar.gz",
                      "2f17178307b538245fc03b04b0d2c891e36c39cc772ae1794a3fa0d9d63a583d")],
    Platform("x86_64", "windows") => [
        ArchiveSource("https://developer.nvidia.com/compute/machine-learning/tensorrt/secure/8.0.3/zip/TensorRT-8.0.3.4.Windows10.x86_64.cuda-10.2.cudnn8.2.zip",
                      "315c2bd6a2257f4fef8662d0cc4c73ae41e6641f6a3ef6227eb43b0f89abf68a")],
)

ext: jl

max_stars_repo_name: cmhamel/Yggdrasil

max_issues_repo_name: cmhamel/Yggdrasil

max_forks_repo_name: cmhamel/Yggdrasil


text:
using Reexport

export @dependencies

push!(LOAD_PATH, joinpath("app", "helpers"))

macro dependencies()
  :(using Genie, Helpers, Renderer, Cache, Router, Util, Logger, Macros, Flax)
end

max_stars_repo_name: anirudhmurali/hello-julia-genie

max_issues_repo_name: anirudhmurali/hello-julia-genie

max_forks_repo_name: anirudhmurali/hello-julia-genie


text:
using Base.Test

rows(M) =  [view(M, x, :) for x in 1:size(M)[1]]
columns(M) = [view(M, :, x) for x in 1:size(M)[2]]

A = [1 2; 3 4; 5 6]

@test [[1, 2], [3, 4], [5, 6]] == rows(A)
@test [1, 2] == rows(A)[1]
@test [[1, 2], [3, 4]] == rows(A)[1:2]
@test [[1, 2], [3, 4], [5, 6]] == rows(A)[1:end]
@test [[1, 2], [3, 4], [5, 6]] == [row for row in rows(A)]

@test [[1; 3; 5], [2; 4; 6]] == columns(A)
@test [1; 3; 5] == columns(A)[1]
@test [[1; 3; 5], [2; 4; 6]] == columns(A)[1:end]
@test [[1; 3; 5], [2; 4; 6]] == [col for col in columns(A)]

B = ['1' '2'; '3' '4'; '5' '6'; '7' '8']
@test [['1', '2'], ['3', '4'], ['5', '6'], ['7', '8']] == rows(B)

C = ["1" "2"; "3" "4"; "5" "6"; "7" "8"]
@test [["1", "2"], ["3", "4"], ["5", "6"], ["7", "8"]] == rows(C)


text:
module SIMDTest

using Test
using ForwardDiff: Dual, valtype
using InteractiveUtils: code_llvm
using StaticArrays: SVector

const DUALS = (Dual(1., 2., 3., 4.),
               Dual(1., 2., 3., 4., 5.),
               Dual(Dual(1., 2.), Dual(3., 4.)))

function simd_sum(x::Vector{T}) where T
    s = zero(T)
    @simd for i in eachindex(x)
        @inbounds s = s + x[i]
    end
    return s
end

@testset "SIMD $D" for D in map(typeof, DUALS)
    plus_bitcode = sprint(io -> code_llvm(io, +, (D, D)))
    @test occursin("fadd <4 x double>", plus_bitcode)

    minus_bitcode = sprint(io -> code_llvm(io, -, (D, D)))
    @test occursin("fsub <4 x double>", minus_bitcode)

    times_bitcode = sprint(io -> code_llvm(io, *, (D, D)))
    @test occursin(r"fadd \<.*?x double\>", times_bitcode)
    @test occursin(r"fmul \<.*?x double\>", times_bitcode)

    div_bitcode = sprint(io -> code_llvm(io, /, (D, D)))
    @test occursin(r"fadd \<.*?x double\>", div_bitcode)
    @test occursin(r"fmul \<.*?x double\>", div_bitcode)

    pow_bitcode = sprint(io -> code_llvm(io, ^, (D, Int)))
    @test occursin(r"fmul \<.*?x double\>", pow_bitcode)

    exp_bitcode = sprint(io -> code_llvm(io, ^, (D, D)))
    @test occursin(r"fadd \<.*?x double\>", exp_bitcode)
    if !(valtype(D) <: Dual)
        # see https://github.com/JuliaDiff/ForwardDiff.jl/issues/167
        @test occursin(r"fmul \<.*?x double\>", exp_bitcode)

        # see https://github.com/JuliaDiff/ForwardDiff.jl/pull/201
        sum_bitcode = sprint(io -> code_llvm(io, simd_sum, (Vector{D},)))
        @test occursin(r"fadd (fast |)\<.*?x double\>", sum_bitcode)
    end
end

# `pow2dot` is chosen so that `@code_llvm pow2dot(SVector(1:1.0:4...))`
# generates code with SIMD instructions.
# See:
# https://github.com/JuliaDiff/ForwardDiff.jl/pull/332
# https://github.com/JuliaDiff/ForwardDiff.jl/pull/331#issuecomment-406107260
@inline pow2(x) = x^2
pow2dot(xs) = pow2.(xs)

# Nested dual such as `Dual(Dual(1., 2.), Dual(3., 4.))` only produces
# "fmul <2 x double>" so it is excluded from the following test.
const POW_DUALS = (Dual(1., 2.),
                   Dual(1., 2., 3.),
                   Dual(1., 2., 3., 4.),
                   Dual(1., 2., 3., 4., 5.))

@testset "SIMD square of $D" for D in map(typeof, POW_DUALS)
    pow_bitcode = sprint(io -> code_llvm(io, pow2dot, (SVector{4, D},)))
    @test occursin(r"(.*fmul \<4 x double\>){2}"s, pow_bitcode)
    # "{2}" is for asserting that fmul has to appear at least twice:
    # once for `.value` and once for `.partials`.
end

end # module


text:
################################################################
# Integration with UncertainData.jl (with constraints)
################################################################

include("validate_constraints.jl")

"""
    causality(source, target, test::CausalityTest, resampling::ConstrainedResampling{N})

Test for a causal influence from `source` to `target` using the provided causality `test`.
Both `source` and `target` might be uncertain data, which is resampled according to the
`resampling` scheme.

!!! note

    This method uses the machinery from
    [`UncertainData.jl`](https://github.com/kahaaga/UncertainData.jl) to constrain the
    furnishing distributions of the uncertain data during resampling, which offers complete
    control over the resampling procedure. The use the causality tests with uncertain data,
    first run `using UncertainData` in the Julia console.

## Arguments

- **`source`**: The source. May be a real-valued vector, a `Vector{AbstractUncertainValue}`,
    or a `AbstractUncertainValueDataset`.

- **`target`**: The target. May be a real-valued vector, a `Vector{AbstractUncertainValue}`,
    or a `AbstractUncertainValueDataset`.

- **`resampling`**: A [`ConstrainedResampling`](@ref) instance. For one time series,
    provide the `ConstrainedResampling` constructor with either a single sampling constraint
    or a vector of sampling constraints. For two time series, provide the
    `ConstrainedResampling` constructor with a tuple, where each element in the tuple
    is either a single sampling constraint or a vector of sampling constraints. The
    sampling constraints comprising the first element of the tuple is then mapped to the
    uncertain values in `source`, and the second tuple is mapped to the elements of `target`.

- **`test`**: An instance of a [causality test](@ref causality_tests).

## Sampling constraints

See [`UncertainData.jl`](https://github.com/kahaaga/UncertainData.jl) for a list of possible
constraints and how to construct them. The examples below show a few alternatives.
See also [`ConstrainedResampling`](@ref).

## Examples

```julia
# Vectors of uncertain values. Also, convert to uncertain datasets
uvals_x = [UncertainValue(Normal, rand(Normal(0, 5)), abs(rand(Normal(0, 3)))) for i = 1:100]
uvals_y = [UncertainValue(Normal, rand(Normal(0, 5)), abs(rand(Normal(0, 3)))) for i = 1:100];
uvx = UncertainValueDataset(uvals_x)
uvy = UncertainValueDataset(uvals_y)

# Draw a single realisation of `uvx` and a single realisation of `uvy`
x, y = resample(uvx), resample(uvy)

# A transfer entropy test using the [`TransferOperatorGrid`](@ref) estimator.
test_tog = TransferOperatorGridTest(binning = RectangularBinning(5), ηs = 1)

##############################
# Perform causality tests
##############################
# No constraints are needed, because no data are uncertain
causality(x, y, test_tog)

# Applying the same constraint to all elements of the uncertain vectors
# ---------------------------------------------------------------------
onevar_constraints = ConstrainedResampling(TruncateStd(1))
twovar_constraints = ConstrainedResampling(TruncateStd(2), TruncateStd(1))

# Only one variable is constrained, so provide only one set of constraints
causality(x, uvy, test_tog, onevar_constraints)
causality(uvx, y, test_tog, onevar_constraints)
causality(x, uvals_y, test_tog, onevar_constraints)
causality(uvals_x, y, test_tog, onevar_constraints)

# Both variables are constrained, so provide two sets of constraints
causality(uvx, uvy, test_tog, twovar_constraints)
causality(uvals_x, uvals_y, test_tog, twovar_constraints)

# Applying different constraints on each element of the uncertain vectors
# -----------------------------------------------------------------------
constraints_x = [TruncateStd(1 + rand(Uniform(-0.5, 0.5))) for uv in uvals_x]
constraints_y = [TruncateStd(1 + rand(Uniform(-0.2, 0.2))) for uv in uvals_y]

# Constraints for x alone, constraints for y alone
crx = ConstrainedResampling(constraints_x)
cry = ConstrainedResampling(constraints_y)

# Compute causality statistic between one real-valued time series and an
# uncertain time series. Resample the uncertain values according to the
# `ConstrainedResampling`s we just defined.
causality(x, uvy, test_tog, cry)
causality(uvx, y, test_tog, crx)
causality(x, uvals_y, test_tog, cry)
causality(uvals_x, y, test_tog, crx)

# Compute causality statistic between two uncertain time series.
# We define separately constraints for y and y
crxy = ConstrainedResampling(constraints_x, constraints_y)
causality(uvx, uvy, test_tog, crxy)
causality(uvals_x, uvals_y, test_tog, crxy)
```
"""
function causality(source, target, test, resampling::ConstrainedResampling) end

function causality(source, target, test::TT, resampling::ConstrainedResampling{N}) where {N, TT <: CausalityTest}

    validate_constraints(source, target, resampling)

    causality(source, target, test)
end

function causality(source::UVT1, target::UVT2, test::TT, resampling::ConstrainedResampling{N}) where {
            UVT1<:Union{AbstractUncertainValueDataset, Vector{<:AbstractUncertainValue}},
            UVT2<:Union{AbstractUncertainValueDataset, Vector{<:AbstractUncertainValue}},
            N,
            TT <: CausalityTest}

    validate_constraints(source, target, resampling)

    causality(resample(source, resampling[1]), resample(target, resampling[2]), test)
end

function causality(source, target::UVT, test::TT, resampling::ConstrainedResampling{N}) where {
            UVT <: Union{AbstractUncertainValueDataset, Vector{<:AbstractUncertainValue}},
            N,
            TT <: CausalityTest}

    validate_constraints(source, target, resampling)

    causality(source, resample(target, resampling[1]), test)
end

function causality(source::UVT, target, test::TT, resampling::ConstrainedResampling{N}) where {
            UVT <: Union{AbstractUncertainValueDataset, Vector{<:AbstractUncertainValue}},
            N,
            TT <: CausalityTest}

    validate_constraints(source, target, resampling)

    causality(resample(source, resampling[1]), target, test)
end


text:
module test_h3_api_misc

using Test
using H3.API # hexAreaKm2 hexAreaM2 edgeLengthKm edgeLengthM numHexagons getRes0Indexes res0IndexCount

@test hexAreaKm2(1) == 607220.9782

@test hexAreaM2(1) == 6.07221e11

@test edgeLengthKm(0) == 1107.712591
@test edgeLengthKm(1) == 418.6760055

@test edgeLengthM(0) == 1107712.591
@test edgeLengthM(1) == 418676.0055

@test numHexagons(0) == 122
@test numHexagons(1) == 842
@test numHexagons(5) == 2_016_842
@test numHexagons(15) == 569_707_381_193_162

@test res0IndexCount() == 122

res0indexes = getRes0Indexes()
@test length(res0indexes) == 122

h = 0x08001fffffffffff
@test first(res0indexes) == h
@test h3GetResolution(h) == 0
@test geoToH3(h3ToGeo(h), 0) == h
faceijk = h3ToFaceIjk(h)
@test faceijk == FaceIJK(1, CoordIJK(1, 0, 0))
@test h3ToLocalIjk(h, h) == CoordIJK(0, 0, 0)
using H3.Lib
@test Lib.h3GetBaseCell(h) == 0

h = last(res0indexes)
@test h == 0x080f3fffffffffff
@test h3GetResolution(h) == 0
@test geoToH3(h3ToGeo(h), 0) == 0x080f3fffffffffff
faceijk = h3ToFaceIjk(h)
@test h3ToFaceIjk(h) == FaceIJK(18, CoordIJK(1, 0, 0))

@test h3ToFaceIjk.(res0indexes[1:10]) == [FaceIJK(1, CoordIJK(1, 0, 0)),
                                          FaceIJK(2, CoordIJK(1, 1, 0)),
                                          FaceIJK(1, CoordIJK(0, 0, 0)),
                                          FaceIJK(2, CoordIJK(1, 0, 0)),
                                          FaceIJK(0, CoordIJK(2, 0, 0)),
                                          FaceIJK(1, CoordIJK(1, 1, 0)),
                                          FaceIJK(1, CoordIJK(0, 0, 1)),
                                          FaceIJK(2, CoordIJK(0, 0, 0)),
                                          FaceIJK(0, CoordIJK(1, 0, 0)),
                                          FaceIJK(2, CoordIJK(0, 1, 0))]

@test h3ToFaceIjk.(res0indexes[end-10:end]) == [FaceIJK(18, CoordIJK(0, 0, 1)),
                                                FaceIJK(19, CoordIJK(0, 0, 1)),
                                                FaceIJK(17, CoordIJK(1, 0, 0)),
                                                FaceIJK(19, CoordIJK(0, 0, 0)),
                                                FaceIJK(18, CoordIJK(0, 1, 0)),
                                                FaceIJK(18, CoordIJK(1, 0, 1)),
                                                FaceIJK(19, CoordIJK(2, 0, 0)),
                                                FaceIJK(19, CoordIJK(1, 0, 0)),
                                                FaceIJK(18, CoordIJK(0, 0, 0)),
                                                FaceIJK(19, CoordIJK(1, 0, 1)),
                                                FaceIJK(18, CoordIJK(1, 0, 0))]

end # module test_h3_api_misc


text:
export DMRGOptions, DMRGResults, storeDMRGResult, runDMRG

struct DMRGOptions
    num_states::Int64
    sweeps::Sweeps
    overlap_penalty::Float64
    quiet::Bool

    DMRGOptions(num_states::Int64 = 1, sweeps::Sweeps = getDefaultSweeps(), overlap_penalty::Float64 = 10.0, quiet::Bool = false) = new(num_states, sweeps, overlap_penalty, quiet)
end

struct DMRGResults
    states::Vector{MPS}
    overlaps::Array{ComplexF64,2}
    observables::Vector{Dict{String,Tuple{ComplexF64,ComplexF64,ComplexF64}}}
    localOperators::Vector{Dict{String,Vector{ComplexF64}}}
    correlationFunctions::Vector{Dict{String,Matrix{ComplexF64}}}
end

function getDefaultSweeps()::Sweeps
    sweeps = Sweeps(8)
    setmaxdim!(sweeps, 50, 80, 100, 150, 200, 250, 300, 500)
    setmindim!(sweeps, 1, 1, 1, 1, 1, 1, 1, 1)
    setcutoff!(sweeps, 1e-6, 1e-8, 1e-10, 1e-12, 1e-12, 1e-12, 0.0, 0.0)
    setnoise!(sweeps, 1e-7, 1e-8, 1e-9, 1e-10, 1e-11, 1e-12, 0.0, 0.0)
    return sweeps
end

function storeDMRGResult(file::String, result::DMRGResults)
    @info "Store DMRG results …"
    mkpath(dirname(file))
    fptr = HDF5.h5open(file, "w")

    # store states
    for (i, state) in enumerate(result.states)
        HDF5.write(fptr, "psi_" * string(i), state)
    end

    # store observables
    for (i, observables) in enumerate(result.observables)
        grp_state = HDF5.create_group(fptr, string("observables_", string(i)))
        for (name, values) in observables
            grp_observable = HDF5.create_group(grp_state, string(name))
            HDF5.write(grp_observable, "value", values[1])
            HDF5.write(grp_observable, "squared", values[2])
            HDF5.write(grp_observable, "variance", values[3])
        end
    end

    # store local operators
    for (i, localOperators) in enumerate(result.localOperators)
        grp_state = HDF5.create_group(fptr, string("local_operators_", string(i)))
        for (name, values) in localOperators
            write(grp_state, name, values)
        end
    end

    # store correlation functions operators
    for (i, correlationFunctions) in enumerate(result.correlationFunctions)
        grp_state = HDF5.create_group(fptr, string("correlation_functions_", string(i)))
        for (name, values) in correlationFunctions
            write(grp_state, name, values)
        end
    end

    # store overlaps
    HDF5.write(fptr, "overlaps", result.overlaps)

    HDF5.close(fptr)
end

function runDMRG(model::Model, options::DMRGOptions)::DMRGResults
    @info "Building model …"
    sites = model.sites
    hamiltonian = getHamiltonian(model)
    psi0 = randomMPS(sites)

    results = DMRGResults(
        MPS[],
        zeros(ComplexF64, options.num_states, options.num_states),
        Dict{String,Tuple{ComplexF64,ComplexF64,ComplexF64}}[],
        Dict{String,Vector{ComplexF64}}[],
        Dict{String,Matrix{ComplexF64}}[]
    )

    for j = 1:options.num_states
        @info "Running DMRG to compute state $j …"
        if length(results.states) == 0
            _, psi = dmrg(hamiltonian, psi0, options.sweeps; outputlevel = options.quiet ? 0 : 1)
            push!(results.states, psi)
            psi0 = psi
        else
            _, psi = dmrg(hamiltonian, result.states, psi0, options.sweeps; options.overlap_penalty, outputlevel = options.quiet ? 0 : 1)
            push!(result.states, psi)
            psi0 = psi
        end
    end

    for (i, state) in enumerate(results.states)
        @info "Computing observables for state $i …"
        push!(results.observables, Dict{String,Tuple{ComplexF64,ComplexF64,ComplexF64}}())
        for observable in getObservables(model)
            results.observables[i][observable.name] = expect(state, observable)
        end

        @info "Computing local operators for state $i …"
        push!(results.localOperators, Dict{String,Vector{ComplexF64}}())
        for localOperator in getLocalOperators(model)
            results.localOperators[i][localOperator.name] = expect(state, localOperator)
        end

        @info "Computing correlation functions for state $i …"
        push!(results.correlationFunctions, Dict{String,Matrix{ComplexF64}}())
        for correlationFunction in getCorrelationFunctions(model)
            results.correlationFunctions[i][correlationFunction.name] = expect(state, correlationFunction)
        end
    end

    @info "Computing overlaps between states …"
    for i = 1:options.num_states
        for j = 1:options.num_states
            results.overlaps[i, j] = inner(results.states[i], results.states[j])
        end
    end
    return results
end


text:
### A Pluto.jl notebook ###
# v0.17.4

using Markdown
using InteractiveUtils

# ╔═╡ 57ed2d88-970d-4b8d-9f36-7115f50622de
begin
	using LinearAlgebra # for the norm and dot function
	using ForwardDiff # for automatic differentiation
end

# ╔═╡ 3b36fb00-833f-11ec-0d4d-451fa6f0aaee
md"""
# Programming tapas (I)

These are a few, hopefully small, tasks to practice Julia.

- **Gradient decent**
- Array interface
"""

# ╔═╡ 0efbf532-66cb-41fd-98a5-6df89e364b84
md"""## Gradient decent with backtracking line search

Write a function `gradient_decent(f, x0, α, β)` which implements the gradient decent with backtracking line search.

You can follow the notation form this slides (or your own).

The pseudo algorithms are:
"""

# ╔═╡ 67ab9ac3-93e0-4d9a-84aa-131aaf543198
md"""
```julia
Gradient decent:
given a starting point x_0

x = x_0
repeat until stopping criterium
	Compute decent direction Δx = ∇f(x).
 	Line search: Choose a step size t > 0.
    Update: x = x + t * Δx

 	Stopping criterium: ‖ ∇f(x) ‖² < ε
end
```
"""

# ╔═╡ a5e2ee29-8de1-41be-8b05-8427b1a65bce
md"""
```julia
Backtracking line search:
given x, f, Δx, α, β

t = 1
repeat until f(x + t Δx) < f(x) + α t dot( ∇f(x), Δx )
 	Udate: t = β t
end

```

Source: [https://web.stanford.edu/class/ee364a/lectures/unconstrained.pdf](https://web.stanford.edu/class/ee364a/lectures/unconstrained.pdf)
"""

# ╔═╡ 304398ad-f680-4f8c-8a76-5768c0fbfa5e

# ╔═╡ 2c48c94e-7d31-450c-b3c7-0194894c38b7
function linesearch(f, Δx, x, α, β; t_min = 1e-5)
	t = 1
	fx = f(x)
	m = norm(Δx)
	while t > t_min
		if f(x .+ t*Δx) <= fx + α * t * m^2
			return t
		end
		t = t*β
		cond || return t
	end

	return NaN
end

# ╔═╡ ae9e20b2-4f88-4c28-8856-ce61368445a3
function gradientdecent(f, x0, α, β, ε; max_steps = 1e4)
	x = copy(x0)
	i = 0
	∇f = similar(x0)

	@assert β < 1
	@assert α < 0.5

	while i < max_steps
		ForwardDiff.gradient!(∇f, f, x)
		Δx = -∇f
		if norm(∇f)^2 < ε
			return x
		end

		t = linesearch(f, Δx, x, α, β)
		x .+= t * Δx

		i += 1
	end

	return NaN
end

# ╔═╡ e331926f-6e11-434c-a7d3-02d0e9f0f269
begin
	f(x) = exp(x[1]+3x[2]-0.1) + exp(x[1]-3x[2]-0.1) + exp(-x[1]-0.1)
	α = 0.25
	β = 0.5
	ε = 1e-8
	x0 = [0.0, 0.0]

	gradientdecent(f, x0, α, β, ε)
end

# ╔═╡ 7a5cfa1b-9b47-4741-8656-210a3515b6f3

# ╔═╡ 81361eb6-092c-4cd1-96a0-eb9e167dfd3f

# ╔═╡ b04a090b-e9d7-4032-b956-1238fac26f94
md"## Array interpolation"

# ╔═╡ 3413f532-50ec-47f5-9547-4e9474be174e
md"""
We want to take regular vectors, but have an automated way to compute in between values with the syntax
```julia
x = [5, 7, 5]
x[1.5] == 6.0  # linear interpolation between 5 and 7
```

Implement
`Base.getindex(x::Vector{Float64}, p::Float64) = `
accordingly.
"""

# ╔═╡ 641f85f8-368a-45eb-9912-284eca29d83b
x = [5.0, 7.0, 5.0]

# ╔═╡ 9f62d306-25f2-4b8f-a691-7a0fa3f4aa03
# x[1.5]

# ╔═╡ 61eaf1c1-f033-4e36-8171-19ff6621cc82

# ╔═╡ 84c50e3b-b836-477f-81bb-121a2e5d055f
md"
## Merge sort

Implement (any variant) of merge sort.

You can for example try this strategy
```
MergeSort(arr[], l,  r)
If r > l
     1. Find the middle point to divide the array into two halves:
             middle m = l+ (r-l)/2
     2. Call mergeSort for first half:
             Call mergeSort(arr, l, m)
     3. Call mergeSort for second half:
             Call mergeSort(arr, m+1, r)
     4. Merge the two halves sorted in step 2 and 3:
             Call merge(arr, l, m, r)
```
Source: https://www.geeksforgeeks.org/merge-sort/
"

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[compat]
ForwardDiff = "~0.10.25"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.1"
manifest_format = "2.0"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "f9982ef575e19b0e5c7a98c6e75ee496c0f73a93"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.12.0"

[[deps.ChangesOfVariables]]
deps = ["ChainRulesCore", "LinearAlgebra", "Test"]
git-tree-sha1 = "bf98fa45a0a4cee295de98d4c1462be26345b9a1"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.2"

[[deps.CommonSubexpressions]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
version = "0.3.0"

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "44c37b4636bc54afac5c574d2d02b625349d6582"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.41.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[deps.DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.3"

[[deps.DiffRules]]
deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "84083a5136b6abf426174a58325ffd159dd6d94f"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.9.1"

[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"

[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[deps.ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "1bd6fc0c344fc0cbee1f42f8d2e7ec8253dda2d2"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "0.10.25"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.2"

[[deps.IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "abc9885a7ca2052a736a600f7fa66209f96506e1"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.4.1"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.LogExpFunctions]]
deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "e5718a00af0ab9756305a0392832c8952c7426c1"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.6"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.9"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[deps.NaNMath]]
git-tree-sha1 = "b086b7ea07f8e38cf122f5016af580881ac914fe"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.7"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"

[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "2cf929d64681236a2e074ffafb8d568733d2e6af"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.3"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "e6bf188613555c78062842777b116905a9f9dd49"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "2.1.0"

[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "2884859916598f974858ff01df7dfc6c708dd895"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.3.3"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─3b36fb00-833f-11ec-0d4d-451fa6f0aaee
# ╟─0efbf532-66cb-41fd-98a5-6df89e364b84
# ╟─67ab9ac3-93e0-4d9a-84aa-131aaf543198
# ╟─a5e2ee29-8de1-41be-8b05-8427b1a65bce
# ╟─304398ad-f680-4f8c-8a76-5768c0fbfa5e
# ╠═57ed2d88-970d-4b8d-9f36-7115f50622de
# ╠═2c48c94e-7d31-450c-b3c7-0194894c38b7
# ╠═ae9e20b2-4f88-4c28-8856-ce61368445a3
# ╠═e331926f-6e11-434c-a7d3-02d0e9f0f269
# ╟─7a5cfa1b-9b47-4741-8656-210a3515b6f3
# ╟─81361eb6-092c-4cd1-96a0-eb9e167dfd3f
# ╟─b04a090b-e9d7-4032-b956-1238fac26f94
# ╟─3413f532-50ec-47f5-9547-4e9474be174e
# ╠═641f85f8-368a-45eb-9912-284eca29d83b
# ╠═9f62d306-25f2-4b8f-a691-7a0fa3f4aa03
# ╟─61eaf1c1-f033-4e36-8171-19ff6621cc82
# ╟─84c50e3b-b836-477f-81bb-121a2e5d055f
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002

max_stars_repo_name: SteffenPL/Julia-for-mathematicians

max_issues_repo_name: SteffenPL/Julia-for-mathematicians

max_forks_repo_name: SteffenPL/Julia-for-mathematicians


text:
struct Sequence{Outer<:Reagent,Inner<:Reagent} <: Reagent
    outer::Outer
    inner::Inner
end

hascas(r::Sequence) = hascas(r.outer) || hascas(r.inner)
maysync(r::Sequence) = maysync(r.outer) || maysync(r.inner)

then(r::Sequence, actr::Reactable) = then(r.outer, then(r.inner, actr))
then(r::Reagent, actr::Reactable) = Reactor(r, actr)
then(::Identity, actr::Reactable) = actr

Reagents.Reagent(::Commit) = Identity()
Reagents.Reagent(actr::Reactor) = actr.reagent ⨟ Reagent(actr.continuation)

hascas(::Identity) = false

struct Choice{R1<:Reagent,R2<:Reagent} <: Reagent
    r1::R1
    r2::R2
end

hascas(r::Choice) = hascas(r.r1) || hascas(r.r2)
maysync(r::Choice) = maysync(r.r1) || maysync(r.r2)

_maysync(r::Reagent) = maysync(then(r, Commit()))
_hascas(r::Reagent) = hascas(then(r, Commit()))

function tryreact!(actr::Reactor{<:Choice}, a, rx::Reaction, offer::Union{Offer,Nothing})
    (; r1, r2) = actr.reagent
    rx = @set rx.restart_on_failure = true
    ans1 = tryreact!(then(r1, actr.continuation), a, rx, offer)
    @trace(
        label = :tried_choice1,
        offerid = offerid(offer),
        taskid = objectid(current_task()),
        ans = ans1,
        a,
        rx,
        offer,
        maysync_r1 = _maysync(r1),
        hascas_r2 = _hascas(r2),
    )
    ans1 isa Failure || return ans1
    if offer === nothing
        if _maysync(r1) && _hascas(r2)
            if ans1 isa NeedNack
                runpostcommithooks(ans1, nothing)
            end
            # If the first branch may synchronize, and the second branch has a
            # CAS, we need to simultaneously rescind the offer *and* commit the
            # CASes.
            @trace(
                label = :block_choice,
                offerid = offerid(offer),
                taskid = objectid(current_task()),
                ans = ans1,
                a,
                rx,
                offer,
            )
            return Block()
        end
    end
    ans2 = tryreact!(then(r2, actr.continuation), a, rx, offer)
    @trace(
        label = :tried_choice2,
        offerid = offerid(offer),
        taskid = objectid(current_task()),
        ans = ans2,
        a,
        rx,
        offer,
    )
    if ans1 isa NeedNack
        if ans2 isa NeedNack
            return NeedNack(combine(ans1.postcommithooks, ans2.postcommithooks))
        elseif ans2 isa Block
            return ans1
        else
            runpostcommithooks(ans1, nothing)
            return ans2
        end
    elseif ans1 isa Retry && ans2 isa Failure
        if ans2 isa NeedNack
            runpostcommithooks(ans2, nothing)
        end
        return Retry()
    else
        return ans2
    end
end

struct ZipSource{R<:Reagent} <: Reagent
    reagent::R
end

hascas(r::ZipSource) = hascas(r.reagent)
maysync(r::ZipSource) = maysync(r.reagent)

function tryreact!(actr::Reactor{<:ZipSource}, a, rx::Reaction, offer::Union{Offer,Nothing})
    (; reagent) = actr.reagent
    return tryreact!(then(reagent ⨟ Map(b -> (a, b)), actr.continuation), a, rx, offer)
end

struct Both{R1<:Reagent,R2<:Reagent} <: Reagent
    r1::R1
    r2::R2
end

hascas(r::Both) = hascas(r.r1) || hascas(r.r2)

struct Branch{Label,T}
    value::T
    label::Val{Label}
end

Branch{Label}(value) where {Label} = Branch(value, Val(Label))

function then(r::Both, actr::Reactable)
    (; r1, r2) = r
    function continuation(br)
        x, y = br.value
        if br isa Branch{1}
            return Return(x) ⨟ r2 ⨟ Map(z -> (y, z))
        else
            br::Branch{2}
            return Return(x) ⨟ r1 ⨟ Map(z -> (z, y))
        end
    end
    try1 = ZipSource(r1) ⨟ Map(Branch{1})
    try2 = ZipSource(r2) ⨟ Map(Branch{2})
    return then((try1 | try2) ⨟ Computed(continuation), actr)
end
# TODO: maybe directly define `tryreact`?
# Note: Using `Choice` inside `Both` so that *both* `r1` and `r2` can trigger
# the reaction.

Base.:∘(inner::Reagent, outer::Sequence) = (inner ∘ outer.inner) ∘ outer.outer
Base.:∘(inner::Reagent, outer::Reagent) = Sequence(outer, inner)

# `|` could be a bit misleading since `Choice` is rather `xor`
Base.:|(r1::Reagent, r2::Reagent) = Choice(r1, r2)
Base.:&(r1::Reagent, r2::Reagent) = Both(r1, r2)

#=
Base.:+(r1::Reagent, r2::Reagent) = Choice(r1, r2)
Base.:>>(r1::Reagent, r2::Reagent) = r2 ∘ r1
Base.:*(r1::Reagent, r2::Reagent) = Both(r1, r2)
=#

# Implementation strategy of `WithNack`
#
# `tryreact!(::Reactor{<:WithNack}, ...)` creates the post-command hook that
# needs to be fired when other branch is chosen.  This is propagated upwards by
# returned value `NeedNack` which is similar to `Block`.   In
# `tryreact!(::Reactor{<:Choice}, ...)`, this post-commit hook is merged into
# `rx::Reaction` used for the reaction of the "else" branch.

struct NeedNack <: Reagents.Failure
    postcommithooks::ImmutableList{Any}
end

const SomehowBlocked = Union{NeedNack,Block}

withnackhook(::Block, @nospecialize(f)) = NeedNack(pushfirst(nothing, f))
withnackhook(ans::NeedNack, @nospecialize(f)) = NeedNack(pushfirst(ans.postcommithooks, f))

# Same as `Computed`:
hascas(::WithNack) = true  # maybe
maysync(::WithNack) = true  # maybe

# TODO: Alternative strategy: Disable NACK in this branch via post-commit hook;
# trigger non-disabled NACKs while "bubbling" up.
function tryreact!(actr::Reactor{<:WithNack}, a, rx::Reaction, offer::Union{Offer,Nothing})
    if offer === nothing
        # `WithNack` is likely to invoke a costly function to setup some kind of
        # RPC.  Let's require `offer` to be instantiated so that it can be
        # registered in the communications in this branch.  This is an
        # optimization but the test relies on this behavior.
        return Block()
    end
    (; f) = actr.reagent
    iscancelled = Reagents.Ref{Union{Nothing,Bool}}(nothing)
    send, receive = Reagents.channel(Nothing)
    function cancel!(_)
        if cas!(iscancelled, nothing, true).success
            while Reagents.trysync!(send) !== nothing
            end
        end
    end
    function disable!(_)
        cas!(iscancelled, nothing, false)
    end
    nack = receive | (Read(iscancelled) ⨟ Map(x -> x === true ? nothing : Block()))
    y = f(nack)::Union{Failure,Reagent}
    if y isa Failure
        cancel!(nothing)
        return y
    end
    ans = tryreact!(then(y, actr.continuation), a, withpostcommit(rx, disable!), offer)
    if ans isa SomehowBlocked
        # `cancel!` will be registered into the else branch(es) of `Choice`
        return withnackhook(ans, cancel!)
    elseif ans isa Failure  # i.e., Retry
        cancel!(nothing)
        return ans
    else
        return ans
    end
end

struct UntilBegin{R<:Reactor} <: Reagent
    loop::R
end

hascas(r::UntilBegin) = hascas(r.loop)

struct UntilEnd{F,R<:Reactable} <: Reagent
    f::F
    continuation::R  # used only for `hascas`
end

hascas(r::UntilEnd) = hascas(r.continuation)

struct UntilBreak{T,Rx<:Reaction}
    value::T
    rx::Rx
end

then(r::Until, actr::Reactable) =
    then(UntilBegin(then(r.reagent, then(UntilEnd(r.f, actr), Commit()))), actr)

function tryreact!(
    actr::Reactor{<:UntilBegin},
    a,
    rx::Reaction,
    offer::Union{Offer,Nothing},
)
    (; loop) = actr.reagent
    while true
        ans = tryreact!(loop, a, Reaction(), nothing)
        if ans isa UntilBreak
            return tryreact!(actr.continuation, ans.value, combine(rx, ans.rx), offer)
        elseif ans isa SomehowBlocked
            error("`Until(f, reagent)` does not support blocking `reagent`")
        elseif ans isa Failure
            GC.safepoint()
            # TODO: backoff
        else
            ans::Nothing
        end
    end
end

function tryreact!(actr::Reactor{<:UntilEnd}, a, rx::Reaction, offer::Union{Offer,Nothing})
    (; f) = actr.reagent
    b = f(a)
    if b === nothing
        # Commit the reaction
        return tryreact!(actr.continuation, nothing, rx, offer)
    else
        return UntilBreak(b, rx)
    end
end


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

mutable struct VaultCertificate <: SwaggerModel
    certificateUrl::Any # spec type: Union{ Nothing, String } # spec name: certificateUrl
    certificateStore::Any # spec type: Union{ Nothing, String } # spec name: certificateStore

    function VaultCertificate(;certificateUrl=nothing, certificateStore=nothing)
        o = new()
        validate_property(VaultCertificate, Symbol("certificateUrl"), certificateUrl)
        setfield!(o, Symbol("certificateUrl"), certificateUrl)
        validate_property(VaultCertificate, Symbol("certificateStore"), certificateStore)
        setfield!(o, Symbol("certificateStore"), certificateStore)
        o
    end
end # type VaultCertificate

const _property_map_VaultCertificate = Dict{Symbol,Symbol}(Symbol("certificateUrl")=>Symbol("certificateUrl"), Symbol("certificateStore")=>Symbol("certificateStore"))
const _property_types_VaultCertificate = Dict{Symbol,String}(Symbol("certificateUrl")=>"String", Symbol("certificateStore")=>"String")
Base.propertynames(::Type{ VaultCertificate }) = collect(keys(_property_map_VaultCertificate))
Swagger.property_type(::Type{ VaultCertificate }, name::Symbol) = Union{Nothing,eval(Meta.parse(_property_types_VaultCertificate[name]))}
Swagger.field_name(::Type{ VaultCertificate }, property_name::Symbol) =  _property_map_VaultCertificate[property_name]

function check_required(o::VaultCertificate)
    true
end

function validate_property(::Type{ VaultCertificate }, name::Symbol, val)
end


text:
"""

An array type to store elements that have only a few choices.
That is, it is a pooled array.
Use `enumeration` to create an `EnumerationArray`.

"""
immutable EnumerationArray{T,N,V,R<:Integer} <: AbstractArray{Nullable{T},N}
  elems::V
  # element 0 in the `pool` member variable means `NA`.
  pool::Vector{T}
  # it can be confusing if T is Integer, if we do not combine elems and pool as a tuple.
  EnumerationArray{V,T}(elems_pool::Tuple{V, Vector{T}}) where {V,T} = new{T,ndims(elems_pool[1]),V,eltype(elems_pool[1])}(elems_pool[1], elems_pool[2])
end

EnumerationArray{T,V<:AbstractArray}(elems_pool::Tuple{V,Vector{T}}) = EnumerationArray{typeof(elems_pool[1]),T}((elems_pool[1], elems_pool[2]))
#EnumerationArray{R<:Integer,N,T}(elems::AbstractArray{R,N}, pool::Vector{T}) = EnumerationArray{T,N,typeof(elems),R}(elems, pool)
EnumerationArray{T,N,V,R<:Integer}(arr::EnumerationArray{T,N,V,R}, poolorder::AbstractVector{T}) = begin
  zeroR = zero(R)
  oneR = one(R)
  counter = R(length(poolorder))
  newpool = Array(T, length(arr.pool))
  ordermap = Array(R, length(arr.pool))
  copy!(newpool, poolorder)
  arrpool = arr.pool
  for i in eachindex(arrpool)
    if arr.pool[i] in poolorder
      ordermap[i] = R(findfirst(poolorder, arrpool[i]))
    else
      counter += oneR
      ordermap[i] = counter
      newpool[counter] = arrpool[i]
    end
  end
  newelems = map(i->ordermap[i], arr.elems)
  EnumerationArray{T,N,V,R}((newelems, newpool))
end
EnumerationArray{R<:Integer,T,N}(::Type{R}, arr::AbstractArray{Nullable{T},N}) = EnumerationArray(R, arr, T[])
EnumerationArray{R<:Integer,T,N}(::Type{R}, arr::AbstractArray{Nullable{T},N}, poolorder::AbstractVector{T}) = begin
  d = Dict{T,R}()
  zeroR = zero(R)
  oneR = one(R)
  counter = oneR
  for p in poolorder
    d[p] = counter
    counter += oneR
  end
  pool = Array{T}(length(poolorder))
  copy!(pool, poolorder)
  elems = map(arr) do elem
    if isnull(elem)
      zeroR
    elseif haskey(d, elem.value)
      d[elem.value]
    else
      d[elem.value] = counter
      push!(pool, elem.value)
      tempcounter = counter
      counter += oneR
      tempcounter
    end
  end
  EnumerationArray{typeof(elems),T}((elems, pool))
end
EnumerationArray{T,N}(arr::AbstractArray{Nullable{T},N}) = EnumerationArray(Int, arr)
EnumerationArray{T,N}(arr::AbstractArray{Nullable{T},N}, poolorder::AbstractVector{T}) = EnumerationArray(Int, arr, poolorder)
Base.size(arr::EnumerationArray) = size(arr.elems)
Base.getindex{T,N,V,R}(arr::EnumerationArray{T,N,V,R}, indices...) = begin
  elem = arr.elems[indices...]
  map_nullable(elem, arr.pool)
end
map_nullable{T,N,R}(elems::AbstractArray{R,N}, pool::Vector{T}) = EnumerationArray((elems, pool))
map_nullable{T,R}(elem::R, pool::Vector{T}) = elem == zero(R) ? Nullable{T}() : Nullable(pool[elem])

getindexvalue{T,N,V,R}(arr::EnumerationArray{T,N,V,R}, indices...) = arr.elems[indices...]::R

Base.eltype{T,N,V,R}(::Type{EnumerationArray{T,N,V,R}}) = Nullable{T}
Base.length(arr::EnumerationArray) = length(arr.elems)
Base.endof(arr::EnumerationArray) = length(arr)
Base.setindex!{T,N,V,R}(arr::EnumerationArray{T,N,V,R}, v::Nullable{T}, indices...) = begin
  if isnull(v)
    setindex!(arr.elems, zero(R), indices...)
    arr
  else
    # note a T value that is not one of the pool values will be assigned to 0.
    # that is, it is treated as null.
    setindex!(arr.elems, convert(R, findfirst(arr.pool, v.value)), indices...)
    arr
  end
end
Base.setindex!{T,N,V,R}(arr::EnumerationArray{T,N,V,R}, v::Integer, indices...) = begin
  setindex!(arr.elems, convert(R,v), indices...)
  arr
end
Base.copy!(tgt::EnumerationArray, src::EnumerationArray) = begin
  copy!(tgt.elems, src.elems)
  copy!(tgt.pool, src.pool)
end
Base.copy(arr::EnumerationArray) = EnumerationArray((copy(arr.elems), copy(arr.pool)))

Base.similar(arr::EnumerationArray) = EnumerationArray((similar(arr.elems),arr.pool))
Base.similar{T,N,V,R,M}(arr::EnumerationArray{T,N,V,R}, dims::NTuple{M,Int}) = EnumerationArray((similar(arr.elems, dims),arr.pool))
Base.similar{T,N,V,R}(arr::EnumerationArray{T,N,V,R}, dims::Int...) = EnumerationArray((similar(arr.elems, dims...),arr.pool))
Base.similar{T,N,V,R,U,M}(arr::EnumerationArray{T,N,V,R}, ::Type{U}, dims::NTuple{M,Int}) = similar(arr.elems, U, dims)
Base.similar{T,N,V,R,U}(arr::EnumerationArray{T,N,V,R}, ::Type{U}, dims::Int...) = similar(arr.elems, U, dims...)
Base.IndexStyle{T,N,V,R}(::Type{EnumerationArray{T,N,V,R}}) = Base.IndexStyle(V)
Base.reshape(arr::EnumerationArray, args::Tuple{Vararg{Int}}) = EnumerationArray((reshape(arr.elems, args), arr.pool))
Base.reshape(arr::EnumerationArray, args::Int...) = EnumerationArray((reshape(arr.elems, args...), arr.pool))
Base.transpose(arr::EnumerationArray, args...) = EnumerationArray((transpose(arr.elems, args...), arr.pool))
Base.permutedims(arr::EnumerationArray, args...) = EnumerationArray((permutedims(arr.elems, args...), arr.pool))
Base.repeat(arr::EnumerationArray, args...;kwargs...) = EnumerationArray((repeat(arr.elems, args...;kwargs...), arr.pool))
Base.repmat{T,V,R}(arr::EnumerationArray{T,1,V,R}, n::Int) = EnumerationArray((repmat(arr.elems, n), arr.pool))
Base.repmat{T,V,R}(arr::EnumerationArray{T,2,V,R}, n::Int) = EnumerationArray((repmat(arr.elems, n), arr.pool))
Base.repmat{T,V,R}(arr::EnumerationArray{T,1,V,R}, m::Int, n::Int) = EnumerationArray((repmat(arr.elems, m, n), arr.pool))
Base.repmat{T,V,R}(arr::EnumerationArray{T,2,V,R}, m::Int, n::Int) = EnumerationArray((repmat(arr.elems, m, n), arr.pool))
Base.sort(arr::EnumerationArray, args...) = EnumerationArray((sort(arr.elems, args...), arr.pool))
Base.sort!(arr::EnumerationArray, args...) = (sort!(arr.elems, args...); arr)
Base.fill!{T,N,V,R}(arr::EnumerationArray{T,N,V,R}, elem::Integer) = fill!(arr.elems, convert(R,elem))
Base.fill!{T,N,V,R}(arr::EnumerationArray{T,N,V,R}, elem::Nullable{T}) = begin
  if isnull(elem)
    fill!(arr.elems, zero(R))
  else
    order = convert(R, findfirst(arr.pool, elem.value))
    fill!(arr.elems, order)
  end
end

Base.cat(dim::Int, arr1::EnumerationArray, arrs::EnumerationArray...) = begin
  pool = arr1.pool
  for arr in arrs
    if pool != arr.pool
      throw(ArgumentError("cannot concatenate EnumerationArrays: the pools are different."))
    end
  end
  EnumerationArray((cat(dim, arr1.elems, map(x->x.elems, arrs)...), pool))
end
Base.vcat(arrs::EnumerationArray...) = cat(1, arrs...)
Base.hcat(arrs::EnumerationArray...) = cat(2, arrs...)
#Base.sub(arr::EnumerationArray, args::Union{Base.Colon,Int,AbstractVector}...) = EnumerationArray((view(arr.elems, args...), arr.pool))
Base.view(arr::EnumerationArray, args::Union{Base.Colon,Int,AbstractVector}...) = EnumerationArray((view(arr.elems, args...), arr.pool))
Base.reverse(arr::EnumerationArray, args...) = EnumerationArray((reverse(arr.elems, args...), arr.pool))

"""

`enumeration(arr [, poolorder])`

Create an `EnumerationArray`.

##### Arguments

* `arr`: an input array of `Nullable` element type. It is assumed that there are only a few possible values in `arr` and each value is converted into an integer when creating an `EnumerationArray`.
* `poolorder`: a vector to fix some of the integer values in the mapping from the values in `arr` to integers. If there are `n` elements in `poolorder`, those `n` elements in `arr` will be assigned 1...`n` when creating an `EnumerationArray`. All the others are assigned integers in order of their appearance.

##### Examples

```julia
julia> enumeration([:A,:A,:B,:B,:C])
5-element DataCubes.EnumerationArray{Symbol,1,DataCubes.AbstractArrayWrapper{Int64,1,Array{Int64,1}},Int64}:
 Nullable(:A)
 Nullable(:A)
 Nullable(:B)
 Nullable(:B)
 Nullable(:C)

julia> enumeration([:A,:A,:B,:B,:C]).pool
3-element Array{Symbol,1}:
 :A
 :B
 :C

julia> enumeration([:A,:A,:B,:B,:C]).elems
5-element DataCubes.AbstractArrayWrapper{Int64,1,Array{Int64,1}}:
 1
 1
 2
 2
 3

julia> enumeration([:A,:A,:B,:B,:C], [:C,:B])
5-element DataCubes.EnumerationArray{Symbol,1,DataCubes.AbstractArrayWrapper{Int64,1,Array{Int64,1}},Int64}:
 Nullable(:A)
 Nullable(:A)
 Nullable(:B)
 Nullable(:B)
 Nullable(:C)

julia> enumeration([:A,:A,:B,:B,:C], [:C,:B]).pool
3-element Array{Symbol,1}:
 :C
 :B
 :A

julia> enumeration([:A,:A,:B,:B,:C], [:C,:B]).elems
5-element DataCubes.AbstractArrayWrapper{Int64,1,Array{Int64,1}}:
 3
 3
 2
 2
 1
```

"""
function enumeration end

enumeration{T}(arr::AbstractArray{Nullable{T}}) = EnumerationArray(arr)
enumeration(arr::AbstractArray{Nullable}) = throw(ArgumentError("the input array needs to be typed."))
enumeration{T}(arr::AbstractArray{T}) = EnumerationArray(nalift(arr))
enumeration{T}(arr::AbstractArray{Nullable{T}}, poolorder::Vector{T}) = EnumerationArray(arr, poolorder)
enumeration{T}(arr::AbstractArray{Nullable}, poolorder::Vector{T}) = throw(ArgumentError("the input array needs to be typed.")) #: $(arr), $(eltype(arr)), $(poolorder), $(eltype(poolorder))."))
enumeration(arr::AbstractArray, poolorder::Vector) = EnumerationArray(nalift(arr), poolorder)
enumeration(arr::AbstractArray, poolorder) = EnumerationArray(nalift(arr), collect(poolorder))

"""

`@enumeration(arr [, poolorder])`

Create an `EnumerationArray`. Similar to the `enumeration` function, but you can type in a null element using `NA`.

##### Arguments

* `arr`: an input array of `Nullable` element type. It is assumed that there are only a few possible values in `arr` and each value is converted into an integer when creating an `EnumerationArray`. `NA` is translated into a null element of appropriate type.
* `poolorder`: a vector to fix some of the integer values in the mapping from the values in `arr` to integers. If there are `n` elements in `poolorder`, those `n` elements in `arr` will be assigned 1...`n` when creating an `EnumerationArray`. All the others are assigned integers in order of their appearance.

##### Examples

```julia
julia> @enumeration([:A,:A,:B,NA,NA])
5-element DataCubes.EnumerationArray{Symbol,1,DataCubes.AbstractArrayWrapper{Int64,1,Array{Int64,1}},Int64}:
 Nullable(:A)
 Nullable(:A)
 Nullable(:B)
 Nullable{Symbol}()
 Nullable{Symbol}()

julia> @enumeration([:A,:A,:B,NA,NA]).pool
2-element Array{Symbol,1}:
 :A
 :B

julia> @enumeration([:A,:A,:B,NA,NA]).elems
5-element DataCubes.AbstractArrayWrapper{Int64,1,Array{Int64,1}}:
 1
 1
 2
 0
 0

julia> @enumeration([:A,:A,:B,NA,NA], [:B,:A])
5-element DataCubes.EnumerationArray{Symbol,1,DataCubes.AbstractArrayWrapper{Int64,1,Array{Int64,1}},Int64}:
 Nullable(:A)
 Nullable(:A)
 Nullable(:B)
 Nullable{Symbol}()
 Nullable{Symbol}()

julia> @enumeration([:A,:A,:B,NA,NA], [:B,:A]).pool
2-element Array{Symbol,1}:
 :B
 :A

julia> @enumeration([:A,:A,:B,NA,NA], [:B,:A]).elems
5-element DataCubes.AbstractArrayWrapper{Int64,1,Array{Int64,1}}:
 2
 2
 1
 0
 0
```

"""

macro enumeration(args...)
  if length(args) == 1
    quote
      enumeration(@nalift($(esc(args[1]))))
    end
  elseif length(args) == 2
    quote
      enumeration(@nalift($(esc(args[1]))), $(esc(args[2])))
    end
  else
    error("cannot figure out what to do in this case yet: ", args...)
  end
end


text:
abstract type ExecutionEnvironment end

struct NoExecutionEnvironment <: ExecutionEnvironment end
beforescenario(::NoExecutionEnvironment, ::StepDefinitionContext, ::Gherkin.Scenario) = nothing
afterscenario(::NoExecutionEnvironment, ::StepDefinitionContext, ::Gherkin.Scenario) = nothing

module GlobalExecEnv
    envs = Dict{Symbol, Function}()

    function clear()
        global envs
        envs = Dict{Symbol, Function}()
    end
end

macro beforescenario(ex::Expr)
    envdefinition = :( (context, scenario) -> $ex )
    quote
        GlobalExecEnv.envs[:beforescenario] = $(esc(envdefinition))
    end
end

macro afterscenario(ex::Expr)
    envdefinition = :( (context, scenario) -> $ex )
    quote
        GlobalExecEnv.envs[:afterscenario] = $(esc(envdefinition))
    end
end

struct FromSourceExecutionEnvironment <: ExecutionEnvironment
    envdefinitions::Dict{Symbol, Function}

    function FromSourceExecutionEnvironment(source::String)
        include_string(Main, source)

        this = new(GlobalExecEnv.envs)
        GlobalExecEnv.clear()
        this
    end
end

function invokeenvironmentmethod(
        executionenv::FromSourceExecutionEnvironment,
        context::StepDefinitionContext,
        scenario::Gherkin.Scenario,
        methodsym::Symbol)

    if haskey(executionenv.envdefinitions, methodsym)
        method = executionenv.envdefinitions[methodsym]
        Base.invokelatest(method, context, scenario)
    end
end

beforescenario(executionenv::FromSourceExecutionEnvironment,
               context::StepDefinitionContext,
               scenario::Gherkin.Scenario) = invokeenvironmentmethod(executionenv, context, scenario, :beforescenario)

afterscenario(executionenv::FromSourceExecutionEnvironment,
              context::StepDefinitionContext,
              scenario::Gherkin.Scenario) = invokeenvironmentmethod(executionenv, context, scenario, :afterscenario)


text:
#=

JuliaBolt is ported from Neobolt: Neo4j Bolt connector for Python
https://github.com/neo4j-drivers/neobolt

Original notices:

# Copyright (c) 2002-2018 "Neo4j,"
# Neo4j Sweden AB [http://neo4j.com]
#
# This file is part of Neo4j.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

=#

const NULL_ = 0xC0
const FALSE = 0xC2
const TRUE = 0xC3

const PACKED_UINT_8 = [struct_pack(value) for value in 0x00:0xff]
const PACKED_UINT_16 = [struct_pack(value) for value in 0x0000:0xffff]

const INT64_HI = (2^63)-1
const INT64_LO = -(2^63)

#####################################
#               Packer              #
#####################################

mutable struct Packer
    stream
    supports_bytes::Bool

    Packer(stream) = new(stream, false)
end

# Packer -------------------------- #

function pack_raw(p::Packer, data::AbstractArray{UInt8})
    write(p, data)
end

# Packer -------------------------- #

function pack(p::Packer, value::Nothing)
    write(p, [0xC0]) # NULL
end

# Packer -------------------------- #

function pack(p::Packer, value::Bool)
    if value
        write(p, [0xC3]) # TRUE
    else
        write(p, [0xC2]) # FALSE
    end
end

# Packer -------------------------- #

function pack(p::Packer, value::Float64)
    write(p, [0xC1])
    write(p, struct_pack(value))
end

# Packer -------------------------- #

function pack(p::Packer, value::Integer)
    if -16 <= value < 128
        write(p, PACKED_UINT_8[mod(value, 256)+1]) #index
    elseif -128 <= value < -16
        write(p, [0xC8])
        write(p, PACKED_UINT_8[mod(value, 256)+1]) #index
    elseif -32768 <= value < 32768
        write(p, [0xC9])
        write(p, PACKED_UINT_16[mod(value, 65536)+1]) #index
    elseif -2147483648 <= value < 2147483648
        write(p, [0xCA])
        write(p, struct_pack(Int32(value)))
    elseif INT64_LO <= value < INT64_HI
        write(p, [0xCB])
        write(p, struct_pack(Int64(value)))
    else
        throw(ErrorException("Integer $(value) out of range"))
    end
end

# Packer -------------------------- #

function pack(p::Packer, value::String)
    data = Array{UInt8}(value)
    pack_string_header(p, length(data))
    pack_raw(p, data)
end

# Packer -------------------------- #

function pack(p::Packer, value::Array{UInt8})
    pack_bytes_header(p, length(value))
    pack_raw(p, value)
end

# Packer -------------------------- #

function pack(p::Packer, value::AbstractArray)
    pack_list_header(p, length(value))
    for item in value
        pack(p, item)
    end
end

# Packer -------------------------- #

function pack(p::Packer, value::AbstractDict)
    pack_map_header(p, length(value))
    for (k, v) in value
        pack(p, k)
        pack(p, v)
    end
end

# Packer -------------------------- #

function pack(p::Packer, value::Structure)
    pack_struct(p, value.tag, value.fields)
end

# Packer -------------------------- #

function pack_bytes_header(p::Packer, size::Integer)
    if !p.supports_bytes
        throw(ErrorException("This PackSteam channel does not support BYTES (consider upgrading to Neo4j 3.2+)"))
    end

    if size < 0x100
        write(p, [0xCC])
        write(p, PACKED_UINT_8[size+1]) #index
    elseif size < 0x10000
        write(p, [0xCD])
        write(p, PACKED_UINT_16[size+1])  #index
    elseif size < 0x100000000
        write(p, [0xCE])
        write(p, struct_pack(UInt32(size)))
    else
        throw(ErrorException("Bytes header size out of range"))
    end
end

# Packer -------------------------- #

function pack_string_header(p::Packer, size::Integer)
    if size == 0x00
        write(p, [0x80])
    elseif size == 0x01
        write(p, [0x81])
    elseif size == 0x02
        write(p, [0x82])
    elseif size == 0x03
        write(p, [0x83])
    elseif size == 0x04
        write(p, [0x84])
    elseif size == 0x05
        write(p, [0x85])
    elseif size == 0x06
        write(p, [0x86])
    elseif size == 0x07
        write(p, [0x87])
    elseif size == 0x08
        write(p, [0x88])
    elseif size == 0x09
        write(p, [0x89])
    elseif size == 0x0A
        write(p, [0x8A])
    elseif size == 0x0B
        write(p, [0x8B])
    elseif size == 0x0C
        write(p, [0x8C])
    elseif size == 0x0D
        write(p, [0x8D])
    elseif size == 0x0E
        write(p, [0x8E])
    elseif size == 0x0F
        write(p, [0x8F])
    elseif size < 0x100
        write(p, [0xD0])
        write(p, PACKED_UINT_8[size+1]) #index
    elseif size < 0x10000
        write(p, [0xD1])
        write(p, PACKED_UINT_16[size+1]) #index
    elseif size < 0x100000000
        write(p, [0xD2])
        write(p, struct_pack(UInt32(size)))
    else
        throw(ErrorException("String header size out of range"))
    end
end

# Packer -------------------------- #

function pack_list_header(p::Packer, size::Integer)
    if size == 0x00
        write(p, [0x90])
    elseif size == 0x01
        write(p, [0x91])
    elseif size == 0x02
        write(p, [0x92])
    elseif size == 0x03
        write(p, [0x93])
    elseif size == 0x04
        write(p, [0x94])
    elseif size == 0x05
        write(p, [0x95])
    elseif size == 0x06
        write(p, [0x96])
    elseif size == 0x07
        write(p, [0x97])
    elseif size == 0x08
        write(p, [0x98])
    elseif size == 0x09
        write(p, [0x99])
    elseif size == 0x0A
        write(p, [0x9A])
    elseif size == 0x0B
        write(p, [0x9B])
    elseif size == 0x0C
        write(p, [0x9C])
    elseif size == 0x0D
        write(p, [0x9D])
    elseif size == 0x0E
        write(p, [0x9E])
    elseif size == 0x0F
        write(p, [0x9F])
    elseif size < 0x100
        write(p, [0xD4])
        write(p, PACKED_UINT_8[size+1]) #index
    elseif size < 0x10000
        write(p, [0xD5])
        write(p, PACKED_UINT_16[size+1]) #index
    elseif size < 0x100000000
        write(p, [0xD6])
        write(p, struct_pack(UInt32(size)))
    else
        throw(ErrorException("List header size out of range"))
    end
end

# Packer -------------------------- #

function pack_list_stream_header(p::Packer)
    write(p, [0xD7])
end

# Packer -------------------------- #

function pack_map_header(p::Packer, size::Integer)
    if size == 0x00
        write(p, [0xA0])
    elseif size == 0x01
        write(p, [0xA1])
    elseif size == 0x02
        write(p, [0xA2])
    elseif size == 0x03
        write(p, [0xA3])
    elseif size == 0x04
        write(p, [0xA4])
    elseif size == 0x05
        write(p, [0xA5])
    elseif size == 0x06
        write(p, [0xA6])
    elseif size == 0x07
        write(p, [0xA7])
    elseif size == 0x08
        write(p, [0xA8])
    elseif size == 0x09
        write(p, [0xA9])
    elseif size == 0x0A
        write(p, [0xAA])
    elseif size == 0x0B
        write(p, [0xAB])
    elseif size == 0x0C
        write(p, [0xAC])
    elseif size == 0x0D
        write(p, [0xAD])
    elseif size == 0x0E
        write(p, [0xAE])
    elseif size == 0x0F
        write(p, [0xAF])
    elseif size < 0x100
        write(p, [0xD8])
        write(p, PACKED_UINT_8[size+1]) #index
    elseif size < 0x10000
        write(p, [0xD9])
        write(p, PACKED_UINT_16[size+1]) #index
    elseif size < 0x100000000
        write(p, [0xDA])
        write(p, struct_pack(UInt32(size)))
    else
        throw(ErrorException("Map header size out of range"))
    end
end

# Packer -------------------------- #

function pack_map_stream_header(p::Packer)
    write(p, [0xDB])
end

# Packer -------------------------- #

function pack_struct(p::Packer, signiture::UInt8, fields::Array)
    size = length(fields)
    if size == 0x00
        write(p, [0xB0])
    elseif size == 0x01
        write(p, [0xB1])
    elseif size == 0x02
        write(p, [0xB2])
    elseif size == 0x03
        write(p, [0xB3])
    elseif size == 0x04
        write(p, [0xB4])
    elseif size == 0x05
        write(p, [0xB5])
    elseif size == 0x06
        write(p, [0xB6])
    elseif size == 0x07
        write(p, [0xB7])
    elseif size == 0x08
        write(p, [0xB8])
    elseif size == 0x09
        write(p, [0xB9])
    elseif size == 0x0A
        write(p, [0xBA])
    elseif size == 0x0B
        write(p, [0xBB])
    elseif size == 0x0C
        write(p, [0xBC])
    elseif size == 0x0D
        write(p, [0xBD])
    elseif size == 0x0E
        write(p, [0xBE])
    elseif size == 0x0F
        write(p, [0xBF])
    elseif size < 0x100
        write(p, [0xDC])
        write(p, PACKED_UINT_8[size+1]) #index
    elseif size < 0x10000
        write(p, [0xDD])
        write(p, PACKED_UINT_16[size+1]) #index
    else
        throw(ErrorException("String header size out of range"))
    end

    write(p, [signiture])

    for field in fields
        pack(p, field)
    end
end

# Packer -------------------------- #

function pack_end_of_stream(p::Packer)
    write(p, [0xDF])
end

# Packer -------------------------- #

function write(p::Packer, b::AbstractArray{UInt8})
    Base.write(p.stream, b)
end

# Packer -------------------------- #
# # # # # # # # # # # # # # # # # # #

max_stars_repo_name: virtualgraham/JuliaBolt

max_issues_repo_name: virtualgraham/JuliaBolt

max_forks_repo_name: virtualgraham/JuliaBolt


text:
module JulzDummy

  using Julz

  cd("$(dirname(@__FILE__))/..") do
    include("../config/bootload.jl")
  end

  function main()
    load_input("input.jl", true)

    open("output.jl", "w") do cur_file
      write(cur_file, "output = []")
    end

    println("done.")
  end

end


text:
abstract type VLAbstractAgentModel end
abstract type VLAbstractGameWorld end
abstract type VLAbstractOrderModel end

# setup enum for actions and orders -
@enum ActionType BUY = 1 SELL = -1 HOLD = 0       # what actions are we going to take?
@enum OrderType CANCEL = 0 MARKET = 1 LIMIT = 2   # what are the different types of orders?

# setup agent type -
mutable struct VLAgentModel <: VLAbstractAgentModel

    # agent data -
    agent_id::UUID
    agent_position_array::Array{Int64,2}
    agent_wealth_array::Array{Float64,1}
    agent_trade_logic::Function
    agent_update_logic::Function
    agent_parameters::Dict{String,Any}

    # default inner constructor -
    VLAgentModel() = new()
end

mutable struct VLGameWorld <: VLAbstractGameWorld

    # game world data -
    asset_price_array::Array{Float64,2}
    liquidity_parameter_array::Array{Float64,1}
    agent_array::Array{VLAgentModel,1}
    number_of_iterations::Int64
    exchange_logic::Function

    # constructor -
    VLGameWorld() = new()
end

struct VLOrderModel <: VLAbstractOrderModel

    # data -
    asset_index::Int64
    action::Int64
    order_type::Int64
    quantity::Int64
    price::Float64
    agent_id::UUID
    order_id::UUID

    # constructor -
    VLOrderModel(asset_index, action, order_type, quantity, price, agent_id) = new(asset_index, action, order_type,
        quantity, price, agent_id, uuid4())
end

struct VLMarketSimulationResult

    agent_array::Array{VLAgentModel,1}
    asset_price_array::Array{Float64,2}

    # constructor -
    VLMarketSimulationResult(agents, price_array) = new(agents, price_array)
end


text:
# A small library to make development with custom types in the REPL and in IJulia easier.
# Normally, a type once defined in Main (the scope of the REPL or IJulia Notebook) cannot be redefined,
# while a type defined in a module may be redefined by calling `include` on that module's source code.
#
# It can be inconvenient to split your work between two separate editor contexts, especially if your
# testing code that depends upon some prepared data objects. Instead of splitting the code into multiple
# files for testing, this module provides a macro, `@asinclude`, which allows you to wrap a quoted block
# of code, dynamically converting it into a module that is reloaded on macro evaluation, and re-introduces
# the newly redefined symbols back into Main's scope. This lets you redefine types trivially without any
# other changes to your environment.
#
# Warning
# The macro writes the contents of each quoted block to a Julia source code file with the same name as
# the module, which contains the generated code. This code is generated from the AST tokens produced by
# `quote`, which may or may not be as clean as hand-written code. Bear this in mind in case the module name
# collides with another file in your working directory.
module asinclude

# This function checks a given line of pseudo-source code for the presence
# of string-ified Expr tokens, and attempts to translate them back into text.
#
# It uses a dictionary to choose which function to use to resolve the detected
# special form. See `specialform_handler` for more details. If an encountered
# special form does not have a rule in `specialform_handler`, an error will
# be thrown.
#
# This is called by @excisecode.
function fixspecialform(line)
    transform_line = lstrip(line)
    # If the first character of the stripped line is a $, the line contains
    # a special form
    if transform_line[1] == '\$'
        tokens = split(line, ":")[2:end]
        # cleantokens is used to find the right handler, but just in case
        # it deletes something important, pass the raw string to the handler
        # so that no information is lost.
        cleantokens = map(x -> replace(x, r",|\s|\)|(#.*$)", ""), tokens)
        line = specialform_handler[cleantokens[1]](tokens...)
    end
    return line
end

# To add support for a new special form, add a new entry to this dictionary
# with the name of the special form token as the key and the value a function
# which takes a splat of argument tokens/strings, that returns a single string
# representing the generated code as output.
#
# This is used by the `fixspecialform` function.
specialform_handler = {
    "import" => (args...) -> begin
    args = map(x -> replace(x, r",|\s|\)|(#.*$)", ""), args)
    args = filter(x->length(x)>0, [args...])
    # import statements are a single, fully qualified name
    return (args[1] * " " * join(args[2:end], '.'))
end,
    "export" => (args...) -> begin
    args = map(x -> replace(x, r",|\s|\)|(#.*$)", ""), args)
    args = filter(x->length(x)>0, [args...])
    # export statements may contain several comma delimited names
    return (args[1] * " " * join(args[2:end], ','))
end,
    "toplevel" => (args...) -> begin
    args = map(x -> replace(x, r",|\s|\)|(#.*$)", ""), args)
    # Accumulate completed expressions
    entries = String[]
    # Accumulate tokens for the current special form
    currententry = String[]
    # Several special forms are present in the same array, but each new expression
    # begins with a token containing the $ function, so use it as a delimiter token
    # to post-process the last special form and start the next one
    for token in args[3:end]
        if contains(token, "\$")
            push!(entries, specialform_handler[currententry[1]](currententry...))
            currententry = String[]
            continue
        end
        push!(currententry, token)
    end
    push!(entries, specialform_handler[currententry[1]](currententry...))
    # Must return a single string
    return join(entries, "\n")
end
}

# This macro is repsonsible for pulling the raw partially parsed tokens from the
# quote block and removing the wrapping quote tokens from the chunk. The tokens still contain
# partially parsed statements that will need to be converted back into text
# resembling the original special syntax for statements like import and export.
#
# Each line is passed to the function `fixspecialform` which inteprets the partially parsed tokens. If
# there is not a special form handler found in the dictionary `specialform_handler`, then an error will
# be thrown.
#
# This is called by @asmodule
macro excisecode(snippet)
    lines = eval(:(split(string($snippet), '\n')))
    nlines = length(lines)
    body = lines[2:nlines-1]
    fixbody = [fixspecialform(line) for line in body]
    return fixbody
end

# This macro drives @excisecode, and provides simple wrapping of the transformed source in
# a module block named after $name. This code should be directly executable using include_string
# but lacks some of the introspection-derived import features of @asinclude. @asinclude calls this.
macro asmodule(snippet, name)
    contents = eval(:(@excisecode($snippet)))
    wrapped = append!(append!(["module " * name * "\n"], contents), [" \nend"])
    wrapped = join(wrapped, "\n")
    return wrapped
end

# The main entry point into this macro collection. Wrapped around a quted block of code,
# this macro produces a text file containing the equivalent source file. The file is named after the
# module name given as a parameter to thte macro.
#
# This new module will be loaded using `include` and all exported names will be read, and statements
# assigning them to the global environment will be added. The module will then be included **AGAIN**.
# Pending rewriting this last step to use something akin to `include_string`, make sure your module level
# code is re-entrant.
macro asinclude(name, snippet)
    contents = eval(:(@asmodule($snippet, $name)))
    fname = name * ".jl"
    stream = open(fname, "w")
    write(stream, contents)
    close(stream)
    # Parse/compile the module so that it can be imported,
    # and more importantly so that it overwrites previous definitions
    include(fname)
    stream = open(fname, "a")
    # During testing, a data attribute appeared, which caused issues. This is
    # likely an artefact of the testing process, but a blacklist for names to ignore makes
    # sense to include.
    blacklist = Set([:data])
    # Import the new module so that it can be introspected.
    # In order to import a module from its string name, first
    # convert the string to a symbol and manually construct the
    # parsed import statement
    eval(Expr(:import, symbol(name)))
    # Then, retrieve the module object by evaluating it's symbol,
    # again from the string name
    inclterms = (names(eval(symbol(name))))

    # inclterms does not support the Iterable protocol, so an
    # index for-loop is needed.
    n = length(inclterms)
    for i in range(1,n)
        # locname is the local name in the module to export to global scope
        locname = string(inclterms[i])
        if !in(blacklist, locname)
            write(stream, "\n$locname = $name.$locname\n")
        end
    end
    close(stream)
    # Reload the module again to execute the new global-scope statements.
    # This may be better replaced with a call to `include_string`
    include(fname)
end

end


text:
module Git

import Git_jll

export git

include("git_function.jl")

end # module


text:
"""
    mutable struct Network <: ANetwork
        ne::Int
        edge_index_range::Int
        out_edges::Vector{Vector{Pair{Int,Int}}}  #unordered adjlist
        epos::Vector{Pair{Int,Int}}    # position of the edge in out_edges
        free_indexes::Vector{Int}       # indexes of deleted edges to be used up
                                        # for new edges to avoid very large
                                        # indexes, and unnecessary property map
                                        # memory used
        props::PropertyStore
    end

A type representing a directed graph with indexed edges.

    Network(n=0)

Construct a `Network` with `n` vertices and no edges.

    Network(adjmx::AbstractMatrix; selfedges=true, upper=false)

Construct a `Network` from the adjacency matrix `adjmx`, placing an edge in
correspondence to each nonzero element of `adjmx`.
If `selfedges=false` the diagonal elements of `adjmx` are ignored.
If `upper=true` only the upper triangular part of `adjmx` is considered.
"""
mutable struct Network <: ANetwork
    ne::Int
    edge_index_range::Int
    out_edges::Vector{Vector{Pair{Int,Int}}}  #unordered adjlist
    epos::Vector{Pair{Int,Int}}    # position of the edge in out_edges
    free_indexes::Vector{Int}       # indexes of deleted edges to be used up
                                    # for new edges to avoid very large
                                    # indexes, and unnecessary property map
                                    # memory use

    props::PropertyStore
end

"""
    mutable struct DiNetwork <: ADiNetwork
        ne::Int
        edge_index_range::Int
        out_edges::Vector{Vector{Pair{Int,Int}}}  #unordered out_adjlist
        in_edges::Vector{Vector{Pair{Int,Int}}}  #unordered in_adjlist

        epos::Vector{Pair{Int,Int}}    # position of the edge in out_edges
                                        # the first in the pair is the vertex
                                        # with lower index

        free_indexes::Vector{Int}       # indexes of deleted edges to be used up
                                        # for new edges to avoid very large
                                        # indexes, and unnecessary property map
                                        # memory use
        props::PropertyStore
    end

A type representing an directed graph with indexed edges.

    DiNetwork(n=0)

Construct a `DiNetwork` with `n` vertices and no edges.

    DiNetwork(adjmx::AbstractMatrix; selfedges=true)

Construct a `DiNetwork` from the adjacency matrix `adjmx`.
If `selfedges=false` the diagonal elements of `adjmx` are ignored.
"""
mutable struct DiNetwork <: ADiNetwork
    ne::Int
    edge_index_range::Int
    out_edges::Vector{Vector{Pair{Int,Int}}}  #unordered out_adjlist
    in_edges::Vector{Vector{Pair{Int,Int}}}  #unordered in_adjlist
    epos::Vector{Pair{Int,Int}}    # position of the edge in out_edges
    free_indexes::Vector{Int}       # indexes of deleted edges to be used up
                                    # for new edges to avoid very large
                                    # indexes, and unnecessary property map
                                    # memory use

    props::PropertyStore
end

const NetOrDiNet = Union{Network, DiNetwork}

edgetype(::Type{G}) where {G<:NetOrDiNet} = IndexedEdge
graphtype(::Type{DiNetwork}) = Network
digraphtype(::Type{Network}) = DiNetwork
vertextype(::Type{G}) where {G<:NetOrDiNet} = Int

#### GRAPH CONSTRUCTORS
function DiNetwork(n::Integer = 0)
    out_edges = [Vector{Pair{Int,Int}}() for _=1:n]
    in_edges = [Vector{Pair{Int,Int}}() for _=1:n]

    epos = Vector{Pair{Int,Int}}()
    free_indexes = Vector{Int}()
    return DiNetwork(0, 0, out_edges, in_edges, epos, free_indexes, PropertyStore())
end

nv(g::NetOrDiNet) = length(g.out_edges)
ne(g::NetOrDiNet) = g.ne

function add_vertex!(g::DiNetwork)
    push!(g.in_edges, Vector{Pair{Int,Int}}())
    push!(g.out_edges, Vector{Pair{Int,Int}}())
    return nv(g)
end

function add_edge!(g::DiNetwork, u::Integer, v::Integer)
    (u in vertices(g) && v in vertices(g)) || return (false, IndexedEdge(u,v,-1))
    has_edge(g, u, v) && return (false, IndexedEdge(u,v,-1))
    if isempty(g.free_indexes)
        g.edge_index_range += 1
        idx = g.edge_index_range
    else
        idx = pop!(g.free_indexes)
    end
    oes = g.out_edges[u]
    ies = g.in_edges[v]
    push!(oes, Pair(v, idx))
    push!(ies, Pair(u, idx))
    g.ne += 1

    length(g.epos) < idx && resize!(g.epos, idx)
    g.epos[idx] = Pair(length(oes), length(ies))

    return (true, IndexedEdge(u,v,idx))
end

rem_edge!(g::NetOrDiNet, s::Integer, t::Integer) = rem_edge!(g, edge(g, s, t))

function rem_edge!(g::DiNetwork, e::IndexedEdge)
    s = e.src
    t = e.dst
    idx = e.idx
    idx <= 0 && return false
    oes = g.out_edges[s]
    ies = g.in_edges[t]

    idx > length(g.epos) && return false
    length(oes) == 0 && return false
    p1 = g.epos[idx].first
    p1 < 0 && return false
    back = last(oes)
    p1 = g.epos[idx].first
    p2 = g.epos[back.second].second
    g.epos[back.second] = Pair(p1, p2)
    oes[p1] = back
    pop!(oes)

    back = last(ies)
    p1 = g.epos[back.second].first
    p2 = g.epos[idx].second
    g.epos[back.second] = Pair(p1, p2)
    ies[p2] = back
    pop!(ies)

    g.epos[idx] = Pair(-1,-1)

    g.ne -= 1
    push!(g.free_indexes, idx)
    return true
end

# TODO can be improved (see graph/digraph)
function edge(g::NetOrDiNet, i::Integer, j::Integer)
    (i > nv(g) || j > nv(g)) && return IndexedEdge(i, j, -1)
    oes = g.out_edges[i]
    pos = findfirst(e->e.first==j, oes)
    if pos !== nothing
        return IndexedEdge(i, j, oes[pos].second)
    else
        return IndexedEdge(i, j, -1)
    end
end

function out_edges(g::NetOrDiNet, i::Integer)
    oes = g.out_edges[i]
    return (IndexedEdge(i, j, idx) for (j, idx) in oes)
end

function out_neighbors(g::NetOrDiNet, i::Integer)
    oes = g.out_edges[i]
    return (j for (j, idx) in oes)
end

function in_edges(g::DiNetwork, i::Integer)
    ies = g.in_edges[i]
    return (IndexedEdge(j, i, idx) for (j, idx) in ies)
end

function in_neighbors(g::DiNetwork, i::Integer)
    ies = g.in_edges[i]
    return (j for (j, idx) in ies)
end

pop_vertex!(g::Network) = (clean_vertex!(g, nv(g)); pop!(g.out_edges); nv(g)+1)
pop_vertex!(g::DiNetwork) = (clean_vertex!(g, nv(g)); pop!(g.out_edges);
                          pop!(g.in_edges); nv(g)+1)

function reverse!(g::DiNetwork)
    g.out_edges, g.in_edges = g.in_edges, g.out_edges
    for i=1:length(g.epos)
        g.epos[i] = reverse(g.epos[i])
    end
    return g
end

## GRAPH

function Network(n::Integer = 0)
    out_edges = [Vector{Pair{Int,Int}}() for _=1:n]
    epos = Vector{Pair{Int,Int}}()
    free_indexes = Vector{Int}()
    return Network(0, 0, out_edges, epos, free_indexes, PropertyStore())
end

function add_vertex!(g::Network)
    push!(g.out_edges, Vector{Pair{Int,Int}}())
    return nv(g)
end

function add_edge!(g::Network, u::Integer, v::Integer)
    u, v = u <= v ? (u, v) : (v, u)
    (u in vertices(g) && v in vertices(g)) || return (false, IndexedEdge(u,v,-1))
    has_edge(g, u, v) && return (false, IndexedEdge(u,v,-1)) # could be removed for multigraphs

    if isempty(g.free_indexes)
        g.edge_index_range += 1
        idx = g.edge_index_range
    else
        idx = pop!(g.free_indexes)
    end
    oes = g.out_edges[u]
    ies = g.out_edges[v]
    push!(oes, Pair(v, idx))
    if u != v
        push!(ies, Pair(u, idx))
    end
    g.ne += 1

    length(g.epos) < idx && resize!(g.epos, idx)
    g.epos[idx] = Pair(length(oes), length(ies))

    return (true, IndexedEdge(u,v,idx))
end

function rem_edge!(g::Network, e::IndexedEdge)
    s = e.src
    t = e.dst
    if s > t
        s,t = t,s
    end
    idx = e.idx
    idx <= 0 && return false
    oes = g.out_edges[s]
    ies = g.out_edges[t]
    idx > length(g.epos) && return false
    length(oes) == 0 && return false
    p1 = g.epos[idx].first
    p1 < 0 && return false

    back = last(oes)
    if back.first > s
        p2 = g.epos[back.second].second
        g.epos[back.second] = Pair(p1 , p2)
    elseif back.first == s #fix self-edges
        g.epos[back.second] = Pair(p1, p1)
    else
        p2 = g.epos[back.second].first
        g.epos[back.second] = Pair(p2 , p1)
    end
    oes[p1] = back
    pop!(oes)

    if s != t
        back = last(ies)
        p1 = g.epos[idx].second
        if back.first > t
            p2 = g.epos[back.second].second
            g.epos[back.second] = Pair(p1 , p2)
        elseif back.first == t #fix self-edges
            g.epos[back.second] = Pair(p1 , p1)
        else
            p2 = g.epos[back.second].first
            g.epos[back.second] = Pair(p2 , p1)
        end
        ies[p1] = back
        pop!(ies)
    end

    g.epos[idx] = Pair(-1,-1)

    g.ne -= 1
    push!(g.free_indexes, idx)
    return true
end

function in_edges(g::Network, i::Integer)
    ies = g.out_edges[i]
    return (IndexedEdge(j, i, idx) for (j, idx) in ies)
end

function swap_vertices!(g::Network, u::Integer, v::Integer)
    if u != v
        #TODO copying to avoid problems with self edges
        # maybe can copy only one of the two
        neigu = deepcopy(g.out_edges[u])
        neigv = deepcopy(g.out_edges[v])

        for (k,p) in enumerate(neigu)
            j, idx = p
            kj = j <= u ? g.epos[idx].first : g.epos[idx].second
            g.out_edges[j][kj] = Pair(v, idx)
            g.epos[idx] = j <= v ? Pair(kj, k) : Pair(k, kj)
        end

        for (k,p) in enumerate(neigv)
            j, idx = p
            kj = j <= v ? g.epos[idx].first : g.epos[idx].second
            g.out_edges[j][kj] = Pair(u, idx)
            g.epos[idx] = j <= u ? Pair(kj, k) : Pair(k, kj)
        end

        g.out_edges[u], g.out_edges[v] = g.out_edges[v], g.out_edges[u]

        swap_vertices!(g.props, u, v)
    end
end

function swap_vertices!(g::DiNetwork, u::Integer, v::Integer)
    if u != v
        #TODO copying to avoid problems with self edges
        # maybe can copy only one of the two
        neigu = deepcopy(g.out_edges[u])
        neigv = deepcopy(g.out_edges[v])
        neiguin = deepcopy(g.in_edges[u])
        neigvin = deepcopy(g.in_edges[v])

        for (k,p) in enumerate(neigu)
            j, idx = p
            kj = g.epos[idx].second
            g.in_edges[j][kj] = Pair(v, idx)
        end

        for (k,p) in enumerate(neigv)
            j, idx = p
            kj = g.epos[idx].second
            g.in_edges[j][kj] = Pair(u, idx)
        end

        for (k,p) in enumerate(neiguin)
            j, idx = p
            kj = g.epos[idx].first
            g.out_edges[j][kj] = Pair(v, idx)
        end

        for (k,p) in enumerate(neigvin)
            j, idx = p
            kj = g.epos[idx].first
            g.out_edges[j][kj] = Pair(u, idx)
        end

        g.out_edges[u], g.out_edges[v] = g.out_edges[v], g.out_edges[u]
        g.in_edges[u], g.in_edges[v] = g.in_edges[v], g.in_edges[u]

        swap_vertices!(g.props, u, v)
    end
end

# function test_consistency(g::Network)
#     for i=1:nv(g)
#         for (k, p) in  enumerate(g.out_edges[i])
#             j = p.first
#             id = p.second
#             if i < j
#                 @assert g.epos[id].first == k "$id $i $j $(g.epos[id]) $(g.out_edges[i])"
#             else
#                 @assert g.epos[id].second == k
#             end
#             @assert findfirst(e->e.first==i, g.out_edges[j]) > 0
#         end
#     end
# end
#
# function test_consistency(g::DiNetwork)
#     for i=1:nv(g)
#         for (k, p) in  enumerate(g.out_edges[i])
#             j = p.first
#             id = p.second
#             @assert g.epos[id].first == k "$id $i $j $(g.epos[id]) $(g.out_edges[i])"
#             posin = findfirst(e->e.first==i, g.in_edges[j])
#             @assert posin > 0
#             @assert g.in_edges[j][posin].second == id
#         end
#     end
# end