text:
mutable struct HalfEdge
  origin :: UInt32
  face :: UInt32
  twin :: UInt32
  next :: UInt32
  prev :: UInt32
end

struct HalfEdgeMesh{N,V,F} <: AbstractMesh{N,V,F}
  vertices :: OffsetVector{V}
  vertexhalfedge :: OffsetVector{UInt32} # contains offset into halfedges
  halfedges :: Vector{HalfEdge} # all halfedges
  faces :: Vector{UInt32}
end

function HalfEdgeMesh(vertices::Vector{V}, faces::Vector{F}) where {N,M,V<:AbstractPoint{N},F<:AbstractPolygon{M}}
  halfedgecount = length(faces) * M
  halfedges = Array{HalfEdge}(undef, halfedgecount)
  meshfaces = Array{UInt32}(undef, length(faces))
  edgedict = Dict{NTuple{2, UInt32}, UInt32}()
  vertexhalfedge = zerobased(Array{UInt32}(undef, length(vertices)))

  idx = 1
  for (faceidx, face) in enumerate(faces)
    meshfaces[faceidx] = idx
    for (S, T) in edges(face)
      if haskey(edgedict, (T, S))
        twinidx = edgedict[(T, S)]
        halfedges[idx] = HalfEdge(S, faceidx, twinidx, idx + 1, idx - 1)
        halfedges[twinidx].twin = idx
        delete!(edgedict, (T, S))
      else
        halfedges[idx] = HalfEdge(S, faceidx, 0, idx + 1, idx - 1)
        edgedict[(S, T)] = idx
        vertexhalfedge[S] = idx
      end
      idx += 1
    end
    halfedges[meshfaces[faceidx]].prev = idx - 1
    halfedges[idx - 1].next = meshfaces[faceidx]
  end

  HalfEdgeMesh{N,V,F}(zerobased(vertices), vertexhalfedge, halfedges, meshfaces)
end

function face(mesh::HalfEdgeMesh{N,V,F}, i; keephalfedges=false) where {M,F<:AbstractPolygon{M},N,V}
  idx = mesh.faces[i]
  vertices = Array{UInt32}(undef, M)
  if keephalfedges
    halfedges = Array{UInt32}(undef,M)
    @inbounds for i in 1:M
      halfedges[i] = idx
      vertices[i] = mesh.halfedges[idx].origin
      idx = mesh.halfedges[idx].next
    end
    (halfedges, F(vertices))
  else
    @inbounds for i in 1:M
      vertices[i] = mesh.halfedges[idx].origin
      idx = mesh.halfedges[idx].next
    end
    F(vertices)
  end
end
vertex(mesh::HalfEdgeMesh, i) = mesh.vertices[i]

Base.length(mesh::HalfEdgeMesh) = length(mesh.faces)
Base.size(mesh::HalfEdgeMesh) = size(mesh.faces)
Base.getindex(mesh::HalfEdgeMesh, i) = face(mesh, i)


text:
@testset "Testing Inception" begin
    batchsize = 16

    # Inference
    f, X  = nGraph.inception_v4_inference(batchsize)

    @show size(f(X...))
    for _ in 1:10
        @time f(X...)
    end

    # Training
    g, args = nGraph.inception_v4_training(batchsize)

    @show length(g.outputs)
    @show length(g.optimizer.inputs)
    @show length(g.optimizer.outputs)

    for _ in 1:5
        @time g(args...)
    end

    #@test isapprox(z, f(X))
end

ext: jl


text:
module BibParser

export parse_entry
export parse_file

include("utils.jl")

# BibTeX module
include("bibtex.jl")
import .BibTeX

# CSL-JSON module
include("csl.jl")
import .CSL

# CFF module
include("cff.jl")
import .CFF

"""
    parse_file(path::String; parser::Symbol = :BibTeX)
Parse a bibliography file. Default to BibTeX format. Other options available: CFF (CSL-JSON coming soon).
"""
parse_file(path, ::Val{:BibTeX})   = BibTeX.parse_file(path)
parse_file(path, ::Val{:CFF})      = CFF.parse_file(path)

parse_file(path, parser = :BibTeX) = parse_file(path, Val(parser))

"""
    parse_entry(entry::String; parser::Symbol = :BibTeX)
Parse a string entry. Default to BibTeX format. No other options available yet (CSL-JSON coming soon).
"""
function parse_entry(entry; parser=:BibTeX)
    return parser == :BibTeX && return BibTeX.parse_string(entry)
end

end # module


text:
testOnAllBackends("AFArray") do af
    println("\tconstruct")

    arr = [1.0f0, 2.0f0]
    afArr = array(af, arr)
    @test [2, 1, 1, 1] == dims(afArr)
    @test (2,) == size(afArr)
    @test host(afArr) == arr
    @test dType(afArr) == f32
    @test numdims(afArr) == length(size(afArr))

    arr = [1.0f0 2.0f0]
    afArr = array(af, arr)
    @test [1, 2, 1, 1] == dims(afArr)
    @test (1,2) == size(afArr)
    @test host(afArr) == arr
    @test dType(afArr) == f32
    @test numdims(afArr) == length(size(afArr))

    afArr = array(af, Float32, 3)
    @test [3, 1, 1, 1] == dims(afArr)
    @test (3,) == size(afArr)
    arr = host(afArr)
    @test typeof(arr) == Array{Float32, 1}
    @test (3,) == size(arr)
    @test dType(afArr) == f32
    @test numdims(afArr) == length(size(afArr))

    afArr = array(af, Float32, 1, 3)
    @test [1, 3, 1, 1] == dims(afArr)
    @test (1, 3) == size(afArr)
    arr = host(afArr)
    @test typeof(arr) == Array{Float32, 2}
    @test (1, 3) == size(arr)
    @test dType(afArr) == f32
    @test numdims(afArr) == length(size(afArr))

    arr = [1.0f0, 2.0f0]
    afArr = array(af, arr, 1, 2)
    @test [1, 2, 1, 1] == dims(afArr)
    @test (1, 2) == size(afArr)
    @test host(afArr) == [1.0f0 2.0f0]
    @test dType(afArr) == f32
    @test numdims(afArr) == length(size(afArr))

    arr = [one(Int32) zero(Int32)]
    afArr = array(af, arr, 2)
    @test [2, 1, 1, 1] == dims(afArr)
    @test (2, ) == size(afArr)
    @test host(afArr) == [one(Int32), zero(Int32)]
    @test dType(afArr) == s32
    @test numdims(afArr) == length(size(afArr))

    println("\tindex get")
    idx = AF.ArrayIndex(afArr)
    aPtr = AF.ptr(idx)
    @test aPtr == afArr.ptr
    afArr = AF.AFArray{getBackend(af)}(af, aPtr, false)
    @test [2, 1, 1, 1] == dims(afArr)
    @test (2, ) == size(afArr)
    @test host(afArr) == [one(Int32), zero(Int32)]
    @test dType(afArr) == s32
    @test numdims(afArr) == length(size(afArr))

    afArr = array(af, [1, 2, 3, 4, 5])
    indexed = afArr[seq(af, 1)]
    @test host(indexed) == [2]
    indexed = afArr[3]
    @test host(indexed) == [3]
    indexed = afArr[2:4]
    @test host(indexed) == [2, 3, 4]

    afArr = array(af, [[1,2,3,4] [5,6,7,8] [9,10,11,12] [13, 14, 15, 16]])
    indexed = afArr[3:5]
    @test host(indexed) == [3, 4, 5]
    indexed = afArr[1:3, 2:3]
    @test host(indexed) == [[5, 6, 7] [9, 10, 11]]
    indexed = afArr[-1, 3:4]
    @test host(indexed) == [12 16]
    # span
    indexed = afArr[span(af)]
    @test host(indexed) == [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
    indexed = afArr[2,:]
    @test host(indexed) == [2 6 10 14]

    afArr = array(af, [1, 2, 3, 4, 5])
    afIdx = array(af, [1.0f0, 0.0f0, 2.0f0])
    indexed = afArr[afIdx]
    @test host(indexed) == [2, 1, 3]

    afArr = array(af, [[1,2,3,4] [5,6,7,8] [9,10,11,12] [13, 14, 15, 16]])
    afIdx = array(af, [[1.0f0, 0.0f0, 2.0f0] [5.0f0, 10.0f0, 2.0f0]])
    indexed = afArr[afIdx]
    @test host(indexed) == [2,1,3,6,11,3]

    # Col, Row
    afArr = array(af, [[0,1,2] [3,4,5] [6,7,8]])

    @test host(afArr[row(af, 0)]) == [0 3 6]
    @test host(afArr[row(af, 2)]) == [2 5 8]

    @test host(afArr[rows(af, 0, 1)]) == [[0,1] [3,4] [6,7]]

    @test host(afArr[col(af, 0)]) == [0,1,2]
    @test host(afArr[col(af, 2)]) == [6,7,8]

    @test host(afArr[cols(af, 1,2)]) == [[3,4,5] [6,7,8]]

    println("\tindex assign")
    afArr = array(af, [1,2,3,4])
    afArr[:] = 5
    @test host(afArr) == [5,5,5,5]

    afArr = array(af, [1,2,3,4])
    afArr[2:3] = 5.5f0
    @test host(afArr) == [1,5,5,4]

    afArr = array(af, [1,2,3,4])
    afArr[1:2] = array(af, [10.1f0, 11.1f0])
    @test host(afArr) == [10,11,3,4]

    @test_throws AFErrorException afArr[1:2] = array(af, [10.1f0, 11.1f0, 12.2f0])

    afArr = array(af, [[1,2,3,4] [5,6,7,8] [9,10,11,12] [13, 14, 15, 16]])
    afArr[:, 3:4] = array(af, [[1,2,3,4] [5,6,7,8]])
    @test host(afArr) == [[1,2,3,4] [5,6,7,8] [1,2,3,4] [5,6,7,8]]

    afArr[3:4, :] = 1.1f0
    @test host(afArr) == [[1,2,1,1] [5,6,1,1] [1,2,1,1] [5,6,1,1]]

    afArr = array(af, [[1,2,3,4] [5,6,7,8] [9,10,11,12] [13, 14, 15, 16]])
    afArr[array(af, [10, 11, 12, 13, 14, 15])] = array(af, [55, 66, 77, 88, 99, 100])
    @test host(afArr) == [[1,2,3,4] [5,6,7,8] [9,10,55,66] [77, 88, 99, 100]]
    afArr[array(af, [[1, 2, 3] [4, 5, 6]])] = array(af, [55, 66, 77, 88, 99, 100])
    @test host(afArr) == [[1,55,66,77] [88,99,100,8] [9,10,55,66] [77, 88, 99, 100]]

    @test_throws AFErrorException afArr[array(af, [[1, 2, 3] [4, 5, 6]])] = array(af, [[55, 66, 77] [88, 99, 100]])

    afArr = array(af, [[1,2,3,4] [5,6,7,8] [9,10,11,12] [13, 14, 15, 16]])
    afArr[array(af, [[1, 2, 3] [4, 5, 6]])] = -1
    @test host(afArr) == [[1,-1,-1,-1] [-1,-1,-1,8] [9,10,11,12] [13, 14, 15, 16]]

    println("\tCOW")
    arr1 = array(af, [1, 2, 3])
    arr2 = arr1[]

    @test host(arr1) == [1, 2, 3]
    @test host(arr2) == [1, 2, 3]

    arr1[1] = 5

    @test host(arr1) == [5, 2, 3]
    @test host(arr2) == [1, 2, 3]

    println("\tempty")
    arr1[] = array(af)

    @test dType(arr1) == 0
    @test dims(arr1) == [0, 0, 0, 0]
    @test size(arr1) == ()
    @test isEmpty(arr1)
    @test host(arr2) == [1, 2, 3]
    @test_throws ErrorException host(arr1)
end

max_stars_repo_name: unbornchikken/julia-ml-proto

max_issues_repo_name: unbornchikken/julia-ml-proto

max_forks_repo_name: unbornchikken/julia-ml-proto


text:
struct Improper

   i :: Int64
   j :: Int64
   k :: Int64
   l :: Int64

   mult :: Int64
   kpsi :: Vector{Float64}
   psi0 :: Vector{Float64}

end


text:
println("------------------------------------")
println("Fusion Trees")
println("------------------------------------")
ti = time()
@testset TimedTestSet "Fusion trees for sector $G" for G in (ℤ₂, ℤ₃, ℤ₄, U₁, CU₁, SU₂, FibonacciAnyon, ℤ₃ × ℤ₄, U₁ × SU₂, SU₂ × SU₂, ℤ₂ × FibonacciAnyon × FibonacciAnyon)
    N = 5
    out = ntuple(n->randsector(G), StaticLength(N))
    isdual = ntuple(n->rand(Bool), StaticLength(N))
    in = rand(collect(⊗(out...)))
    numtrees = count(n->true, fusiontrees(out, in, isdual))
    while !(0 < numtrees < 30)
        out = ntuple(n->randsector(G), StaticLength(N))
        in = rand(collect(⊗(out...)))
        numtrees = count(n->true, fusiontrees(out, in, isdual))
    end
    it = @inferred fusiontrees(out, in, isdual)
    f = @inferred first(it)
    @testset "Fusion tree $G: printing" begin
        @test eval(Meta.parse(sprint(show,f))) == f
    end
    @testset "Fusion tree $G: braiding" begin
        for in = ⊗(out...)
            for f in fusiontrees(out, in, isdual)
                d1 = @inferred TK.artin_braid(f, 2)
                d2 = empty(d1)
                for (f1, coeff1) in d1
                    for (f2,coeff2) in TK.artin_braid(f1, 2; inv = true)
                        d2[f2] = get(d2, f2, zero(coeff1)) + coeff2*coeff1
                    end
                end
                for (f2, coeff2) in d2
                    if f2 == f
                        @test coeff2 ≈ 1
                    else
                        @test isapprox(coeff2, 0; atol = 10*eps())
                    end
                end
            end
        end

        f = rand(collect(it))
        d1 = TK.artin_braid(f, 2)
        d2 = empty(d1)
        for (f1, coeff1) in d1
            for (f2,coeff2) in TK.artin_braid(f1, 3)
                d2[f2] = get(d2, f2, zero(coeff1)) + coeff2*coeff1
            end
        end
        d1 = d2
        d2 = empty(d1)
        for (f1, coeff1) in d1
            for (f2,coeff2) in TK.artin_braid(f1, 3; inv = true)
                d2[f2] = get(d2, f2, zero(coeff1)) + coeff2*coeff1
            end
        end
        d1 = d2
        d2 = empty(d1)
        for (f1, coeff1) in d1
            for (f2,coeff2) in TK.artin_braid(f1, 2; inv = true)
                d2[f2] = get(d2, f2, zero(coeff1)) + coeff2*coeff1
            end
        end
        d1 = d2
        for (f1, coeff1) in d1
            if f1 == f
                @test coeff1 ≈ 1
            else
                @test isapprox(coeff1, 0; atol = 10*eps())
            end
        end
    end
    @testset "Fusion tree $G: braiding and permuting" begin
        p = tuple(randperm(N)...,)
        ip = invperm(p)

        levels = ntuple(identity, N)
        d = @inferred TK.braid(f, levels, p)
        d2 = Dict{typeof(f), valtype(d)}()
        levels2 = p
        for (f2, coeff) in d
            for (f1,coeff2) in TK.braid(f2, levels2, ip)
                d2[f1] = get(d2, f1, zero(coeff)) + coeff2*coeff
            end
        end
        for (f1, coeff2) in d2
            if f1 == f
                @test coeff2 ≈ 1
            else
                @test isapprox(coeff2, 0; atol = 10*eps())
            end
        end

        if (BraidingStyle(G) isa Bosonic) && hasfusiontensor(G)
            Af = convert(Array, f)
            Afp = permutedims(Af, (p..., N+1))
            Afp2 = zero(Afp)
            for (f1, coeff) in d
                Afp2 .+= coeff .* convert(Array, f1)
            end
            @test Afp ≈ Afp2
        end
    end
    @testset "Fusion tree $G: insertat" begin
        N = 4
        out2 = ntuple(n->randsector(G), StaticLength(N))
        in2 = rand(collect(⊗(out2...)))
        isdual2 = ntuple(n->rand(Bool), StaticLength(N))
        f2 = rand(collect(fusiontrees(out2, in2, isdual2)))
        for i = 1:N
            out1 = ntuple(n->randsector(G), StaticLength(N))
            out1 = Base.setindex(out1, in2, i)
            in1 = rand(collect(⊗(out1...)))
            isdual1 = ntuple(n->rand(Bool), StaticLength(N))
            isdual1 = Base.setindex(isdual1, false, i)
            f1 = rand(collect(fusiontrees(out1, in1, isdual1)))

            trees = @inferred TK.insertat(f1, i, f2)
            @test norm(values(trees)) ≈ 1

            f1a, f1b = TK.split(f1, StaticLength(i))
            @test length(TK.insertat(f1b, 1, f1a)) == 1
            @test first(TK.insertat(f1b, 1, f1a)) == (f1 => 1)

            levels = ntuple(identity, N)
            gen = Base.Generator(TK.braid(f1, levels, (i, (1:i-1)..., (i+1:N)...))) do (t, c)
                (t′, c′) = first(TK.insertat(t, 1, f2))
                @test c′ == one(c′)
                return t′ => c
            end
            trees2 = Dict(gen)
            trees3 = empty(trees2)
            p = ((N+1:N+i-1)..., (1:N)..., (N+i:2N-1)...)
            levels = ((i:N+i-1)..., (1:i-1)..., (i+N:2N-1)...)
            for (t,coeff) in trees2
                for (t′,coeff′) in TK.braid(t, levels, p)
                    trees3[t′] = get(trees3, t′, zero(coeff′)) + coeff*coeff′
                end
            end
            for (t, coeff) in trees3
                @test get(trees, t, zero(coeff)) ≈ coeff atol = 1e-12
            end

            if (BraidingStyle(G) isa Bosonic) && hasfusiontensor(G)
                Af1 = convert(Array, f1)
                Af2 = convert(Array, f2)
                Af = TensorOperations.tensorcontract(Af1, [1:i-1; -1; N-1 .+ (i+1:N+1)],
                                                     Af2, [i-1 .+ (1:N); -1], 1:2N)
                Af′ = zero(Af)
                for (f, coeff) in trees
                    Af′ .+= coeff .* convert(Array, f)
                end
                @test Af ≈ Af′
            end
        end
    end
    @testset "Fusion tree $G: merging" begin
        N = 3
        out1 = ntuple(n->randsector(G), StaticLength(N-1))
        in1 = rand(collect(⊗(out1...)))
        f1 = rand(collect(fusiontrees((out1..., dual(in1)), one(in1))))
        out2 = ntuple(n->randsector(G), StaticLength(N))
        in2 = rand(collect(⊗(out2...)))
        f2 = rand(collect(fusiontrees(out2, in2)))
        trees1 = @inferred TK.merge(f1, f2, first(f1.coupled ⊗ f2.coupled))
        @test sum(abs2(coeff)*dim(c) for c in f1.coupled ⊗ f2.coupled
                    for (f,coeff) in TK.merge(f1, f2, c)) ≈ dim(f1.coupled)*dim(f2.coupled)

        # test merge and braid interplay
        trees2 = TK.merge(f2, f1, first(f1.coupled ⊗ f2.coupled))
        perm = ( (N.+(1:N))... , (1:N)...)
        levels = ntuple(identity, 2*N)
        trees3 = Dict{keytype(trees1), complex(valtype(trees1))}()
        for (t, coeff) in trees1
            for (t′, coeff′) in TK.braid(t, levels, perm)
                trees3[t′] = get(trees3, t′, zero(coeff′)) + coeff*coeff′
            end
        end
        for (t, coeff) in trees3
            @test isapprox(coeff, get(trees2, t, zero(coeff)); atol = 10*eps())
        end

        if (BraidingStyle(G) isa Bosonic) && hasfusiontensor(G)
            Af1 = convert(Array, f1)
            Af2 = convert(Array, f2)
            for c in f1.coupled ⊗ f2.coupled
                Af0 = convert(Array, FusionTree((f1.coupled, f2.coupled), c, (false, false), ()))
                _Af = TensorOperations.tensorcontract(Af1, [1:N;-1],
                                                        Af0, [-1;N+1;N+2], 1:N+2)
                Af = TensorOperations.tensorcontract(Af2, [N .+ (1:N); -1],
                                                        _Af, [1:N; -1; 2N+1], 1:2N+1)
                Af′ = zero(Af)
                for (f, coeff) in trees1
                    if f.coupled == c
                        Af′ .+= coeff .* convert(Array, f)
                    end
                end
                @test Af ≈ Af′
            end
        end
    end

    if G <: ProductSector
        N = 3
    else
        N = 4
    end
    out = ntuple(n->randsector(G), StaticLength(N))
    numtrees = count(n->true, fusiontrees((out..., map(dual, out)...)))
    while !(0 < numtrees < 100)
        out = ntuple(n->randsector(G), StaticLength(N))
        numtrees = count(n->true, fusiontrees((out..., map(dual, out)...)))
    end
    incoming = rand(collect(⊗(out...)))
    f1 = rand(collect(fusiontrees(out, incoming, ntuple(n->rand(Bool), N))))
    f2 = rand(collect(fusiontrees(out[randperm(N)], incoming, ntuple(n->rand(Bool), N))))

    @testset "Double fusion tree $G: repartioning" begin
        for n = 0:2*N
            d = @inferred TK.repartition(f1, f2, StaticLength(n))
            @test dim(incoming) ≈ sum(abs2(coef)*dim(f1.coupled) for ((f1,f2), coef) in d)
            d2 = Dict{typeof((f1,f2)), valtype(d)}()
            for ((f1′,f2′),coeff) in d
                for ((f1′′,f2′′),coeff2) in TK.repartition(f1′,f2′, StaticLength(N))
                    d2[(f1′′,f2′′)] = get(d2, (f1′′,f2′′), zero(coeff)) + coeff2*coeff
                end
            end
            for ((f1′,f2′), coeff2) in d2
                if f1 == f1′ && f2 == f2′
                    @test coeff2 ≈ 1
                    if !(coeff2 ≈ 1)
                        @show f1, f2, n
                    end
                else
                    @test isapprox(coeff2, 0; atol = 10*eps())
                end
            end
            if (BraidingStyle(G) isa Bosonic) && hasfusiontensor(G)
                Af1 = convert(Array, f1)
                Af2 = permutedims(convert(Array, f2), [N:-1:1; N+1])
                sz1 = size(Af1)
                sz2 = size(Af2)
                d1 = prod(sz1[1:end-1])
                d2 = prod(sz2[1:end-1])
                dc = sz1[end]
                A = reshape(reshape(Af1, (d1, dc)) * reshape(Af2, (d2, dc))',
                            (sz1[1:end-1]..., sz2[1:end-1]...))
                A2 = zero(A)
                for ((f1′,f2′), coeff) in d
                    Af1′ = convert(Array, f1′)
                    Af2′ = permutedims(convert(Array, f2′), [(2N-n):-1:1; 2N-n+1])
                    sz1′ = size(Af1′)
                    sz2′ = size(Af2′)
                    d1′ = prod(sz1′[1:end-1])
                    d2′ = prod(sz2′[1:end-1])
                    dc′ = sz1′[end]
                    A2 += coeff*reshape(reshape(Af1′,(d1′,dc′)) * reshape(Af2′,(d2′,dc′))',
                                        (sz1′[1:end-1]..., sz2′[1:end-1]...))
                end
                @test A ≈ A2
            end
        end
    end
    @testset "Double fusion tree $G: permutation" begin
        if BraidingStyle(G) isa SymmetricBraiding
            for n = 0:2N
                p = (randperm(2*N)...,)
                p1, p2 = p[1:n], p[n+1:2N]
                ip = invperm(p)
                ip1, ip2 = ip[1:N], ip[N+1:2N]

                d = @inferred TensorKit.permute(f1, f2, p1, p2)
                @test dim(incoming) ≈ sum(abs2(coef)*dim(f1.coupled) for ((f1,f2), coef) in d)
                d2 = Dict{typeof((f1,f2)), valtype(d)}()
                for ((f1′,f2′), coeff) in d
                    d′ = TensorKit.permute(f1′,f2′, ip1, ip2)
                    for ((f1′′,f2′′), coeff2) in d′
                        d2[(f1′′,f2′′)] = get(d2, (f1′′,f2′′), zero(coeff)) + coeff2*coeff
                    end
                end
                for ((f1′,f2′), coeff2) in d2
                    if f1 == f1′ && f2 == f2′
                        @test coeff2 ≈ 1
                        if !(coeff2 ≈ 1)
                            @show f1, f2, p
                        end
                    else
                        @test abs(coeff2) < 10*eps()
                    end
                end
                if (BraidingStyle(G) isa Bosonic) && hasfusiontensor(G)
                    Af1 = convert(Array, f1)
                    Af2 = convert(Array, f2)
                    sz1 = size(Af1)
                    sz2 = size(Af2)
                    d1 = prod(sz1[1:end-1])
                    d2 = prod(sz2[1:end-1])
                    dc = sz1[end]
                    A = reshape(reshape(Af1, (d1, dc)) * reshape(Af2, (d2, dc))',
                                (sz1[1:end-1]..., sz2[1:end-1]...))
                    Ap = permutedims(A, (p1..., p2...));
                    A2 = zero(Ap)
                    for ((f1′,f2′), coeff) in d
                        Af1′ = convert(Array, f1′)
                        Af2′ = convert(Array, f2′)
                        sz1′ = size(Af1′)
                        sz2′ = size(Af2′)
                        d1′ = prod(sz1′[1:end-1])
                        d2′ = prod(sz2′[1:end-1])
                        dc′ = sz1′[end]
                        A2 += coeff*reshape(reshape(Af1′,(d1′,dc′)) *
                                            reshape(Af2′,(d2′,dc′))',
                                            (sz1′[1:end-1]..., sz2′[1:end-1]...))
                    end
                    @test Ap ≈ A2
                end
            end
        end
    end
end
tf = time()
printstyled("Finished fusion tree tests in ",
            string(round(tf-ti; sigdigits=3)),
            " seconds."; bold = true, color = Base.info_color())
println()


text:
struct DiscreteExplicitPOMDP{S,A,O,OF,RF,D} <: POMDP{S,A,O}
    s::Vector{S}
    a::Vector{A}
    o::Vector{O}
    tds::Dict{Tuple{S,A}, SparseCat{Vector{S}, Vector{Float64}}}
    ods::Dict{Tuple{A,S}, SparseCat{Vector{O}, Vector{Float64}}}
    ofun::OF
    r::RF
    smap::Dict{S,Int}
    amap::Dict{A,Int}
    omap::Dict{O,Int}
    discount::Float64
    initial::D
    terminals::Set{S}
end

struct DiscreteExplicitMDP{S,A,RF,D} <: MDP{S,A}
    s::Vector{S}
    a::Vector{A}
    tds::Dict{Tuple{S,A}, SparseCat{Vector{S}, Vector{Float64}}}
    r::RF
    smap::Dict{S,Int}
    amap::Dict{A,Int}
    discount::Float64
    initial::D
    terminals::Set{S}
end

const DEP{S,A,O} = DiscreteExplicitPOMDP
const DE{S,A} = Union{DiscreteExplicitPOMDP{S,A},DiscreteExplicitMDP{S,A}}

POMDPs.discount(m::DE) = m.discount
POMDPs.states(m::DE) = m.s
POMDPs.actions(m::DE) = m.a
POMDPs.stateindex(m::DE, s) = m.smap[s]
POMDPs.actionindex(m::DE, a) = m.amap[a]

POMDPs.observations(m::DEP) = m.o
POMDPs.obsindex(m::DEP, o) = m.omap[o]
POMDPModelTools.obs_weight(m::DEP, a, sp, o) = m.ofun(a, sp, o)

POMDPs.transition(m::DE, s, a) = m.tds[s,a]
POMDPs.observation(m::DEP, a, sp) = m.ods[a,sp]
POMDPs.reward(m::DE, s, a) = m.r(s, a)

POMDPs.initialstate(m::DE) = m.initial

POMDPs.isterminal(m::DE,s) = s in m.terminals

POMDPModelTools.ordered_states(m::DE) = m.s
POMDPModelTools.ordered_actions(m::DE) = m.a
POMDPModelTools.ordered_observations(m::DEP) = m.o

"""
    DiscreteExplicitPOMDP(S,A,O,T,Z,R,γ,[b₀],[terminals=Set()])

Create a POMDP defined by the tuple (S,A,O,T,Z,R,γ).

# Arguments

## Required
- `S`,`A`,`O`: State, action, and observation spaces (typically `Vector`s)
- `T::Function`: Transition probability distribution function; ``T(s,a,s')`` is the probability of transitioning to state ``s'`` from state ``s`` after taking action ``a``.
- `Z::Function`: Observation probability distribution function; ``O(a, s', o)`` is the probability of receiving observation ``o`` when state ``s'`` is reached after action ``a``.
- `R::Function`: Reward function; ``R(s,a)`` is the reward for taking action ``a`` in state ``s``.
- `γ::Float64`: Discount factor.

## Optional
- `b₀=Uniform(S)`: Initial belief/state distribution (See `POMDPModelTools.Deterministic` and `POMDPModelTools.SparseCat` for other options).

## Keyword
- `terminals=Set()`: Set of terminal states. Once a terminal state is reached, no more actions can be taken or reward received.
"""
function DiscreteExplicitPOMDP(s, a, o, t, z, r, discount, b0=Uniform(s); terminals=Set())
    ss = vec(collect(s))
    as = vec(collect(a))
    os = vec(collect(o))
    ST = eltype(ss)
    AT = eltype(as)
    OT = eltype(os)

    tds = filltds(t, ss, as)

    # TODO parallelize?
    ods = Dict{Tuple{AT, ST}, SparseCat{Vector{OT}, Vector{Float64}}}()
    for u in as
        for xp in ss
            ys = OT[]
            ps = Float64[]
            for y in os
                p = z(u, xp, y)
                if p > 0.0
                    push!(ys, y)
                    push!(ps, p)
                end
            end
            ods[u,xp] = SparseCat(ys, ps)
        end
    end

    m = DiscreteExplicitPOMDP(
        ss, as, os,
        tds, ods,
        z, r,
        Dict(ss[i]=>i for i in 1:length(ss)),
        Dict(as[i]=>i for i in 1:length(as)),
        Dict(os[i]=>i for i in 1:length(os)),
        discount, b0, convert(Set{eltype(ss)}, terminals)
    )

    @assert has_consistent_distributions(m)

    return m
end

"""
    DiscreteExplicitMDP(S,A,T,R,γ,[p₀],[terminals=Set()])

Create an MDP defined by the tuple (S,A,T,R,γ).

# Arguments

## Required
- `S`,`A`: State and action spaces (typically `Vector`s)
- `T::Function`: Transition probability distribution function; ``T(s,a,s')`` is the probability of transitioning to state ``s'`` from state ``s`` after taking action ``a``.
- `R::Function`: Reward function; ``R(s,a)`` is the reward for taking action ``a`` in state ``s``.
- `γ::Float64`: Discount factor.

## Optional
- `p₀=Uniform(S)`: Initial state distribution (See `POMDPModelTools.Deterministic` and `POMDPModelTools.SparseCat` for other options).

## Keyword
- `terminals=Set()`: Set of terminal states. Once a terminal state is reached, no more actions can be taken or reward received.
"""
function DiscreteExplicitMDP(s, a, t, r, discount, p0=Uniform(s); terminals=Set())
    ss = vec(collect(s))
    as = vec(collect(a))

    tds = filltds(t, ss, as)

    m = DiscreteExplicitMDP(
        ss, as, tds, r,
        Dict(ss[i]=>i for i in 1:length(ss)),
        Dict(as[i]=>i for i in 1:length(as)),
        discount, p0, convert(Set{eltype(ss)}, terminals)
    )

    @assert has_consistent_distributions(m)

    return m
end

function filltds(t, ss, as)
    ST = eltype(ss)
    AT = eltype(as)
    tds = Dict{Tuple{ST, AT}, SparseCat{Vector{ST}, Vector{Float64}}}()
    # TODO parallelize?
    for x in ss
        for u in as
            xps = ST[]
            ps = Float64[]
            for xp in ss
                p = t(x, u, xp)
                if p > 0.0
                    push!(xps, xp)
                    push!(ps, p)
                end
            end
            tds[x,u] = SparseCat(xps, ps)
        end
    end
    return tds
end

POMDPs.convert_s(::Type{V}, s, m::DE) where V<:AbstractVector = convert_to_vec(V, s, m.smap)
POMDPs.convert_a(::Type{V}, a, m::DE) where V<:AbstractVector = convert_to_vec(V, a, m.amap)
POMDPs.convert_o(::Type{V}, o, m::DEP) where V<:AbstractVector = convert_to_vec(V, o, m.omap)

POMDPs.convert_s(::Type{S}, v::AbstractArray{N}, m::DE{S}) where {S,N<:Number} = convert_from_vec(S, v, m.s)
POMDPs.convert_a(::Type{A}, v::AbstractArray{N}, m::DE{<:Any,A}) where {A,N<:Number} = convert_from_vec(A, v, m.a)
POMDPs.convert_o(::Type{O}, v::AbstractArray{N}, m::DEP{<:Any,<:Any,O}) where {O,N<:Number} = convert_from_vec(O, v, m.o)

# if states are numbers, try to preserve
POMDPs.convert_s(::Type{V}, s::Number, m::DE) where V<:AbstractVector{N} where N<:Number = convert(V, [s])
POMDPs.convert_a(::Type{V}, a::Number, m::DE) where V<:AbstractVector{N} where N<:Number = convert(V, [a])
POMDPs.convert_o(::Type{V}, o::Number, m::DEP) where V<:AbstractVector{N} where N<:Number = convert(V, [o])

POMDPs.convert_s(::Type{N}, v::AbstractVector{F}, m::DE) where {N<:Number, F<:Number} = convert(N, first(v))
POMDPs.convert_a(::Type{N}, v::AbstractVector{F}, m::DE) where {N<:Number, F<:Number} = convert(N, first(v))
POMDPs.convert_o(::Type{N}, v::AbstractVector{F}, m::DEP) where {N<:Number, F<:Number} = convert(N, first(v))

# if states are vectors, try to preserve
POMDPs.convert_s(T::Type{A1}, s::A2, m::DE) where {A1<:AbstractVector, A2<:AbstractVector} = convert(T, s)
POMDPs.convert_a(T::Type{A1}, a::A2, m::DE) where {A1<:AbstractVector, A2<:AbstractVector} = convert(T, a)
POMDPs.convert_o(T::Type{A1}, o::A2, m::DEP) where {A1<:AbstractVector, A2<:AbstractVector} = convert(T, o)

# for things that aren't numbers
convert_to_vec(V, x, map) = convert(V, [map[x]])
convert_from_vec(T, v, space) = convert(T, space[convert(Integer, first(v))])


text:
#!/usr/local/bin/julia

function binary_search(array::Array{Int64, 1}, key::Int64)
    bottom, top = 1, length(array)
    while bottom <= top
        middle = floor(Int64, (top + bottom)/2)
        if array[middle] < key
            bottom = middle + 1
        elseif array[middle] > key
            top = middle - 1
        else
            return true
        end
    end
    return false
end

# Sample run
# julia> @timev binary_search([1:10000000...], 1000011)
#   1.017336 seconds (10.00 M allocations: 310.125 MiB, 77.09% gc time)
# elapsed time (ns): 1017336317
# gc time (ns):      784260295
# bytes allocated:   325189632
# pool allocs:       9999500
# non-pool GC allocs:9
# malloc() calls:    2
# realloc() calls:   6
# GC pauses:         4
# full collections:  2
# true


text:
module PosNegReindexTests

using Test
using Gridap.Arrays
using Gridap.TensorValues

for indices in ([1,3,-1,2,-2], PosNegPartition([1,4,2],5))

  values_pos = Float64[40,30,10]
  values_neg = -Float64[40,30]
  c = lazy_map(PosNegReindex(values_pos,values_neg),indices)
  r = [  i>0 ? values_pos[i] : values_neg[-i] for i in indices ]
  test_array(c,r)

  subindices = [1,4,2]
  d = lazy_map(Reindex(c),subindices)
  @test d === values_pos

  subindices = [3,5]
  d = lazy_map(Reindex(c),subindices)
  @test d === values_neg

  subindices = [4,2]
  d = lazy_map(Reindex(c),subindices)
  r = [  i>0 ? values_pos[i] : values_neg[-i] for i in indices[subindices] ]
  test_array(d,r)

  subindices = [5,]
  d = lazy_map(Reindex(c),subindices)
  r = [  i>0 ? values_pos[i] : values_neg[-i] for i in indices[subindices] ]
  test_array(d,r)

  subindices = [1,5,3,4]
  d = lazy_map(Reindex(c),subindices)
  r = [  i>0 ? values_pos[i] : values_neg[-i] for i in indices[subindices] ]
  test_array(d,r)
  #print_op_tree(d)

end

# Combination with Broadcasting

p = 1
data = [2,3,-1,3,4,4,3,2,5,4,-3,4]
ptrs = [1,4,4,7,13]
gid_to_val_pos = [p, 2*p, 3*p, -p, p]
gid_to_val_neg = [3*p, -p, p]
lid_to_gid = Table(data,ptrs)
ca = lazy_map(Broadcasting(PosNegReindex(gid_to_val_pos,gid_to_val_neg)),lid_to_gid)
a = Vector{Int}[[2, 3, 3], [], [3, -1, -1], [3, 2, 1, -1, 1, -1]]
test_array( ca, a )

i_to_v=[[1, 2], [5, 6], [1, 5], [2, 6], [2, 3], [6, 7], [3, 7], [3, 4], [7, 8], [4, 8], [9, 10], [5, 9], [6, 10], [10, 11], [7, 11], [11, 12], [8, 12], [13, 14], [9, 13], [10, 14], [14, 15], [11, 15], [15, 16], [12, 16]]
j_to_i=Int[]
lid_to_gid=lazy_map(Reindex(i_to_v),j_to_i)
gid_to_val=VectorValue{2,Float64}[(0.0, 0.25), (0.25, 0.25), (0.5, 0.25), (0.75, 0.25), (0.0, 0.5), (0.25, 0.5), (0.5, 0.5), (0.75, 0.5), (0.0, 0.75), (0.25, 0.75), (0.5, 0.75), (0.75, 0.75), (0.0, 1.0), (0.25, 1.0), (0.5, 1.0), (0.75, 1.0)]
f2=lazy_map(Broadcasting(PosNegReindex(gid_to_val,gid_to_val)),lid_to_gid)

# 0-length cases

test_array(f2,Vector{VectorValue{2,Float64}}[])

indices = Int[]
values_pos = Float64[]
values_neg = Float64[]
c = lazy_map(PosNegReindex(values_pos,values_neg),indices)
test_array(c,Float64[])

# If we work with PosNegReindex return also PosNegReindex

for indices in ([1,3,-2,2,-1], PosNegPartition([1,4,2],5), lazy_map(PosNegReindex([1,2,3],[-1,-2]),[1,3,-2,2,-1]))

  a_pos = Float64[40,30,10]
  a_neg = -Float64[40,30]
  a = lazy_map(PosNegReindex(a_pos,a_neg),indices)

  b_pos = Float64[43,50,60]
  b_neg = -Float64[41,30]
  b = lazy_map(PosNegReindex(b_pos,b_neg),indices)

  f_pos = fill(+,3)
  f_neg = fill(-,2)
  f = lazy_map(PosNegReindex(f_pos,f_neg),Function,indices)
  c = lazy_map(evaluate,f,a,b)
  #print_op_tree(c)
  test_array(c,collect(c))
  c_pos = c.maps.value.values_pos
  c_neg = c.maps.value.values_neg
  test_array(c_pos,a_pos+b_pos)
  test_array(c_neg,a_neg-b_neg)

  a_pos = Float64[40,30,10]
  a_neg = -Int[40,30]
  a = lazy_map(PosNegReindex(a_pos,a_neg),Number,indices)
  fun(i) = Float64(i) + 3
  c = lazy_map(fun,a)
  #print_op_tree(c)
  test_array(c,collect(c))
  c_pos = c.maps.value.values_pos
  c_neg = c.maps.value.values_neg
  test_array(c_pos,fun.(a_pos))
  test_array(c_neg,fun.(a_neg))

end

a_i = PosNegPartition([1,],4)
a_pos = Float64[40]
a_neg = -Float64[40,30,20]
a = lazy_map(PosNegReindex(a_pos,a_neg),a_i)

b_i = PosNegPartition([3,2,4],4)
b_pos = Float64[50,60,30]
b_neg = -Float64[41]
b = lazy_map(PosNegReindex(b_pos,b_neg),b_i)

c = lazy_map(+,a,b)
test_array(c,map(+,a,b))

## Testing some cases where PosNegReindex can be type-instable
#
#indices = [1,3,-2,2,-1]
#values_pos = Int[40,30,10]
#values_neg = -Float64[40,30]
#c = lazy_map(PosNegReindex(values_pos,values_neg),indices)
#d = lazy_map(i->Float64(i),c)
#display(d)
#
#d = lazy_map(*,c,ones(Float64,size(c)))
#display(d)
#
#indices = [1,1,-1,1,-1]
#values_pos = [+]
#values_neg = [-]
#c = lazy_map(PosNegReindex(values_pos,values_neg),indices)
#d = lazy_map(evaluate, c, rand(size(c)), rand(size(c)))
#display(d)

end # module


text:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Tests related to the text backend.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

data = Any[1    false      1.0     0x01 ;
           2     true      2.0     0x02 ;
           3    false      3.0     0x03 ;
           4     true      4.0     0x04 ;
           5    false      5.0     0x05 ;
           6     true      6.0     0x06 ;]

# Default
# ==============================================================================

@testset "Default" begin

    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Col. 1
Col. 2
Col. 3
Col. 4

1
false
1.0
1

2
true
2.0
2

3
false
3.0
3

4
true
4.0
4

5
false
5.0
5

6
true
6.0
6

"""
    result = sprint((io,data)->pretty_table(io,data,backend = :html), data)
    @test result == expected
end

# Alignments
# ==============================================================================

@testset "Alignments" begin
    # Left
    # ==========================================================================
    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Col. 1
Col. 2
Col. 3
Col. 4

1
false
1.0
1

2
true
2.0
2

3
false
3.0
3

4
true
4.0
4

5
false
5.0
5

6
true
6.0
6

"""
    result = sprint((io, data)->pretty_table(io, data; alignment = :l,
                                             backend = :html), data)
    @test result == expected

    # Center
    # ==========================================================================
    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Col. 1
Col. 2
Col. 3
Col. 4

1
false
1.0
1

2
true
2.0
2

3
false
3.0
3

4
true
4.0
4

5
false
5.0
5

6
true
6.0
6

"""
    result = sprint((io, data)->pretty_table(io, data; alignment = :c,
                                             backend = :html), data)
    @test result == expected

    # Per column configuration
    # ==========================================================================

    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Col. 1
Col. 2
Col. 3
Col. 4

1
false
1.0
1

2
true
2.0
2

3
false
3.0
3

4
true
4.0
4

5
false
5.0
5

6
true
6.0
6

"""
    result = sprint((io, data)->pretty_table(io, data;
                                             alignment = [:l,:r,:c,:r],
                                             backend = :html), data)
    @test result == expected

    # Cell override
    # ==========================================================================

    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Col. 1
Col. 2
Col. 3
Col. 4

1
false
1.0
1

2
true
2.0
2

3
false
3.0
3

4
true
4.0
4

5
false
5.0
5

6
true
6.0
6

"""
    result = sprint((io, data)->pretty_table(io, data;
                                             alignment = [:l,:r,:c,:r],
                                             backend = :html,
                                             cell_alignment =
                                                Dict( (3,1) => :r,
                                                      (3,2) => :l,
                                                      (1,4) => :l,
                                                      (3,4) => :c,
                                                      (4,4) => :c,
                                                      (6,4) => :l )), data)
    @test result == expected
end

# Filters
# ==============================================================================

@testset "Filters" begin
    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Row
Col. 1
Col. 3

2
2
2.0

4
4
4.0

6
6
6.0

"""

    result = sprint((io,data)->pretty_table(io, data;
                                            backend = :html,
                                            filters_row = ( (data,i) -> i%2 == 0,),
                                            filters_col = ( (data,i) -> i%2 == 1,),
                                            formatter = ft_printf("%.3",3),
                                            show_row_number = true), data)
    @test result == expected

    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Row
Col. 1
Col. 3

2
2
2.0

4
4
4.0

6
6
6.0

"""

    result = sprint((io,data)->pretty_table(io, data;
                                            backend = :html,
                                            filters_row = ( (data,i) -> i%2 == 0,),
                                            filters_col = ( (data,i) -> i%2 == 1,),
                                            formatter = ft_printf("%.3",3),
                                            show_row_number = true,
                                            alignment = [:c,:l,:l,:c]), data)
    @test result == expected
end

# Formatter
# ==============================================================================

@testset "Formatter" begin
    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Col. 1
Col. 2
Col. 3
Col. 4

1
false
1
1

0
true
0
0

3
false
3
3

0
true
0
0

5
false
5
5

0
true
0
0

"""
    formatter = Dict(0 => (v,i) -> isodd(i) ? i : 0,
                     2 => (v,i) -> v)
    result = sprint((io, data)->pretty_table(io, data; backend = :html,
                                             formatter = formatter), data)
    @test result == expected
end

# Show row number
# ==============================================================================

@testset "Show row number" begin
    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Row
Col. 1
Col. 2
Col. 3
Col. 4

1
1
false
1.0
1

2
2
true
2.0
2

3
3
false
3.0
3

4
4
true
4.0
4

5
5
false
5.0
5

6
6
true
6.0
6

"""
    result = sprint((io, data)->pretty_table(io, data;
                                             alignment       = [:l,:r,:c,:r],
                                             backend         = :html,
                                             show_row_number = true), data)
    @test result == expected
end

# Sub-headers
# ==============================================================================

# Hiding header and sub-header
# ==============================================================================

# Print vectors
# ==============================================================================

@testset "Print vectors" begin

    vec = 0:1:10

    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Col. 1

0

1

2

3

4

5

6

7

8

9

10

"""

    result = sprint((io,data)->pretty_table(io, data, backend = :html), vec)
    @test result == expected

    expected = """
<!DOCTYPE html>

<meta charset="UTF-8">

Row
Col. 1

1
0

2
1

3
2

4
3

5
4

6
5

7
6

8
7

9
8

10
9

11
10

"""

    result = sprint((io, vec)->pretty_table(io, vec; alignment = :c,
                                            backend = :html,
                                            show_row_number = true), vec)
    @test result == expected

    # TODO: test sub-headers.
end

# Dictionaries
# ==============================================================================

# Helpers
# ==============================================================================


text:
using Bokeh
# by default autoopen is false (since v0.0.2) so to cause plots to show
# when plot is called we switch it on
# this can also be achieved by passing autoopen=true to plot
autoopen(true)

# for consistency we set the name of the html file to generate in this example
# if you dont set this the default name "bokeh_plot.html" is used
plotfile("legend.html")

y = collect(1:10).^2
# autoopen is set to true so `plot` will try to open a browser to show the plot
plot(y, legends=["This is the Legend"])  # , legendsgo=:top_left


text:
module Aheui

export 아희
export 스택, 큐, 통로, 순방향, 역방향
export 없음, 끝냄
export 덧셈, 곱셈, 뺄셈, 나눗셈, 나머지
export 집어넣기, 뽑기, 중복, 바꿔치기
export 선택, 이동, 비교, 조건
export 가자, 종성획수

abstract type 저장공간 end
abstract type 커서타입 end

mutable struct 진행방향
  어느쪽::Function
end

mutable struct 스택 <: 저장공간
  공간::Vector
  출력::Vector
  방향::진행방향
end
struct 큐 <: 저장공간
  공간::Vector
  출력::Vector
  방향::진행방향
end
struct 통로 <: 저장공간
  공간::Vector
  출력::Vector
  방향::진행방향
end

순방향 = 진행방향(+)
역방향 = 진행방향(-)

없음(저장::저장공간, _) = nothing
끝냄(저장::저장공간, _) = isempty(저장.공간) ? 0 : 뽑(저장)
덧셈(저장::저장공간, _) = 집어넣기(저장, +(뽑(저장), 뽑(저장)))
곱셈(저장::저장공간, _) = 집어넣기(저장, *(뽑(저장), 뽑(저장)))
뺄셈(저장::저장공간, _) = 집어넣기(저장, -뽑(저장)+뽑(저장))
나눗셈(저장::저장공간, _) = 집어넣기(저장, \(뽑(저장), 뽑(저장)))
나머지(저장::저장공간, _) = 집어넣기(저장, ((a,b)->%(b,a))(뽑(저장), 뽑(저장)))

뽑(저장::저장공간) = pop!(저장.공간)

function 뽑기(저장::저장공간, 받침::Symbol)
  if isempty(저장.공간)
    nothing
  else
    값 = 뽑(저장)
    출력(저장, :ㅎ==받침 ? Char(값) : 값)
  end
end
뽑기(저장::저장공간, ::Union{Int,Float64}) = isempty(저장.공간) ? nothing : 출력(저장, 뽑(저장))
뽑기(저장::저장공간) = 뽑기(저장, 0)

function 출력(저장::저장공간, 값)
  push!(저장.출력, 값)
  값
end

집어넣기(저장::저장공간, ::Symbol) = nothing
집어넣기(저장::저장공간, 값::Union{Int,Float64}) = push!(저장.공간, 값)

중복(저장::저장공간, _) = 집어넣기(저장, last(저장.공간))

바꿔치기(저장::저장공간, _) = 저장.공간[end], 저장.공간[end-1] = 저장.공간[end-1], 저장.공간[end]
선택(공간들::Dict, 공간키) = 공간들[공간키]
function 이동(저장::저장공간, 공간들::Dict, 공간키)
  공간 = 선택(공간들, 공간키)
  집어넣기(공간, 뽑기(저장))
  저장
end
function 비교(저장::저장공간)
  값 = 뽑기(저장) >= 뽑기(저장) ? 1 : 0
  집어넣기(저장, 값)
end
function 조건(저장::저장공간)
  저장.방향 = 0 == 뽑기(저장) ? 역방향 : 순방향
  저장
end
가자(f, a...) = f(a...)

struct 그냥내비둠 <: 커서타입
end
struct 칸이동 <: 커서타입
  얼만큼::Int
  어느쪽::Function
end

mutable struct 커서움직임
  이전위치
  위치
  방향::Function
end

내비둠 = 그냥내비둠()
한칸 = 칸이동(1, identity)
두칸 = 칸이동(2, identity)
import Base: +, -
+(칸::칸이동) = 칸이동(칸.얼만큼, +)
-(칸::칸이동) = 칸이동(칸.얼만큼, -)

function 커서이동(상하::칸이동, 좌우::그냥내비둠)
  function 어디로(저장::저장공간, 커서::커서움직임)
    행값,렬값 = 커서.위치
    커서.이전위치 = [행값 렬값]
    커서.위치 = [상하.어느쪽(행값, 상하.얼만큼) 렬값]
    저장.방향.어느쪽 = 상하.어느쪽
    커서
  end
  어디로
end
function 커서이동(상하::그냥내비둠, 좌우::칸이동)
  function 어디로(저장::저장공간, 커서::커서움직임)
    행값,렬값 = 커서.위치
    커서.이전위치 = [행값 렬값]
    커서.위치 = [행값 좌우.어느쪽(렬값, 좌우.얼만큼)]
    저장.방향.어느쪽 = 좌우.어느쪽
    커서
  end
  어디로
end
기능없음(저장::저장공간, 커서::커서움직임) = 커서

const 닿소리표 = [없음 없음 나눗셈 덧셈 곱셈 나머지 뽑기 집어넣기 중복 선택 이동 없음 비교 없음 조건 없음 뺄셈 바꿔치기 끝냄]
const 홀소리표 = [
  커서이동(내비둠, +한칸)
  기능없음
  커서이동(내비둠, +두칸)
  기능없음
  커서이동(내비둠, -한칸)
  기능없음
  커서이동(내비둠, -두칸)
  기능없음
  커서이동(-한칸, 내비둠)
  기능없음
  기능없음
  기능없음
  커서이동(-두칸, 내비둠)
  커서이동(+한칸, 내비둠)
  기능없음
  기능없음
  기능없음
  커서이동(+두칸, 내비둠)
  기능없음 # ㅡ  FIXME: 방향 바꾸기 처리 추가
  기능없음 # ㅢ
  기능없음 # ㅣ
]
const 종성획수표 = [0 2 4 4 2 5 5 3 5 7 9 9 7 9 9 8 4 4 6 2 4 :ㅇ 3 4 3 4 4 :ㅎ]
const 유니코드_가 = 0xAC00
const 유니코드_히흫 = 0xD7A3
const 초성오프셋 = 21 * 28
const 중성오프셋 = 28

function 기능(저장::저장공간, 소리::Char, 커서::커서움직임)
  값 = Int(소리)
  if 값 >= 유니코드_가 && 값 <= 유니코드_히흫
    값 = 값 - 유니코드_가
    초성인덱스 = trunc(Int, 값 / 초성오프셋) + 1
    값 = 값 % 초성오프셋
    중성인덱스 = trunc(Int, 값 / 중성오프셋) + 1
    종성인덱스 = trunc(Int, 값 % 중성오프셋) + 1
    닿소리표[초성인덱스], 홀소리표[중성인덱스], 종성획수표[종성인덱스]
  else
  end
end

function 아희(입력::String)
  여러줄 = split(입력, "\n")
  행,렬 = length(여러줄), maximum(map(length, 여러줄))
  격자 = Array{Char}(undef, 행, 렬)
  for (행값,줄) in enumerate(여러줄)
    for (렬값,글자) in enumerate(줄)
      격자[행값,렬값] = 글자
    end
  end
  아희(격자)
end

function 아희(입력::Array{Char,2})
  isempty(입력) && return ""
  공간들 = Dict(:큐=>큐([], [], 순방향), :스택=>스택([], [], 순방향), :통로=>통로([], [], 순방향))
  저장 = 가자(선택, 공간들, :스택)
  커서 = 커서움직임([0 0], [1 1], 기능없음)
  while true
    소리 = 입력[커서.위치...]
    초,중,종 = 기능(저장, 소리, 커서)
    if 끝냄==초
      break
    else
      가자(초, 저장, 종)
      커서 = 중(저장, 커서)
    end
  end
  join(저장.출력)
end

end


text:
using DifferentialEquations
using ForwardDiff
using DiffEqBase: ODEProblem, SDEProblem, RODEProblem, DEIntegrator
using OrdinaryDiffEq: ODEIntegrator
using StochasticDiffEq: SDEIntegrator

""" Continuous-time problem. """
const CTProblem = Union{ODEProblem, SDEProblem, RODEProblem}
# TODO: maybe use Abstract*Problem; but note that
# AbstractDiscreteProblem <: AbstractODEProblem

function get_integrator(prob::CTProblem; save_everystep=false,
                        alg = nothing,
                        kwargs...)
    if alg === nothing
        alg, extra_kwargs = default_algorithm(prob; kwargs...)
    else
        extra_kwargs = []
    end
    return init(prob, alg; kwargs..., extra_kwargs...,
                save_everystep=save_everystep)
end
# See:
# - [[../../OrdinaryDiffEq/src/solve.jl::function init\b]]
# - http://docs.juliadiffeq.org/latest/basics/common_solver_opts.html

@inline function assert_success(integrator::DEIntegrator)
    if ! (integrator.sol.retcode in (:Default, :Success))
        throw(IntegratorError(integrator.sol.retcode))
    end
end
# http://docs.juliadiffeq.org/latest/basics/solution.html#Return-Codes-(RetCodes)-1

const ContinuousLEProblem = LEProblem{ODEProblem}

"""
    ContinuousLEProblem(phase_dynamics, u0 [, p];
                        t_attr=<number>, <keyword arguments>)

This is a short-hand notation for:

```julia
LEProblem(ODEProblem(phase_dynamics, u0 [, p]), t_attr)
```

For the list of usable keyword arguments, see [`LEProblem`](@ref).
"""
ContinuousLEProblem(phase_dynamics, u0, p=nothing;
                    tspan=(0.0, 100.0), kwargs...) =
    LEProblem(ODEProblem(phase_dynamics, u0, tspan, p);
              kwargs...)

init_phase_state(integrator::DEIntegrator) = integrator.sol.prob.u0[:, 1]
init_tangent_state(integrator::DEIntegrator) = integrator.sol.prob.u0[:, 2:end]

current_state(integrator::DEIntegrator) = integrator.u