text:
module UtilitiesTests

using Test

import Documenter

module UnitTests
    module SubModule end

    # Does `submodules` collect *all* the submodules?
    module A
        module B
            module C
                module D end
            end
        end
    end

    mutable struct T end
    mutable struct S{T} end

    "Documenter unit tests."
    Base.length(::T) = 1

    f(x) = x

    const pi = 3.0
end

module OuterModule
module InnerModule
import ..OuterModule
export OuterModule
end
end

@testset "Utilities" begin
    let doc = @doc(length)
        a = Documenter.Utilities.filterdocs(doc, Set{Module}())
        b = Documenter.Utilities.filterdocs(doc, Set{Module}([UnitTests]))
        c = Documenter.Utilities.filterdocs(doc, Set{Module}([Base]))
        d = Documenter.Utilities.filterdocs(doc, Set{Module}([UtilitiesTests]))

        @test a !== nothing
        @test a === doc
        @test b !== nothing
        @test contains(stringmime("text/plain", b), "Documenter unit tests.")
        @test c !== nothing
        @test !contains(stringmime("text/plain", c), "Documenter unit tests.")
        @test d === nothing
    end

    # Documenter.Utilities.issubmodule
    @test Documenter.Utilities.issubmodule(Main, Main) === true
    @test Documenter.Utilities.issubmodule(UnitTests, UnitTests) === true
    @test Documenter.Utilities.issubmodule(UnitTests.SubModule, Main) === true
    @test Documenter.Utilities.issubmodule(UnitTests.SubModule, UnitTests) === true
    @test Documenter.Utilities.issubmodule(UnitTests.SubModule, Base) === false
    @test Documenter.Utilities.issubmodule(UnitTests, UnitTests.SubModule) === false

    @test UnitTests.A in Documenter.Utilities.submodules(UnitTests.A)
    @test UnitTests.A.B in Documenter.Utilities.submodules(UnitTests.A)
    @test UnitTests.A.B.C in Documenter.Utilities.submodules(UnitTests.A)
    @test UnitTests.A.B.C.D in Documenter.Utilities.submodules(UnitTests.A)
    @test OuterModule in Documenter.Utilities.submodules(OuterModule)
    @test OuterModule.InnerModule in Documenter.Utilities.submodules(OuterModule)
    @test length(Documenter.Utilities.submodules(OuterModule)) == 2

    @test Documenter.Utilities.isabsurl("file.md") === false
    @test Documenter.Utilities.isabsurl("../file.md") === false
    @test Documenter.Utilities.isabsurl(".") === false
    @test Documenter.Utilities.isabsurl("https://example.org/file.md") === true
    @test Documenter.Utilities.isabsurl("http://example.org") === true
    @test Documenter.Utilities.isabsurl("ftp://user:pw@example.org") === true
    @test Documenter.Utilities.isabsurl("/fs/absolute/path") === false

    @test Documenter.Utilities.doccat(UnitTests) == "Module"
    @test Documenter.Utilities.doccat(UnitTests.T) == "Type"
    @test Documenter.Utilities.doccat(UnitTests.S) == "Type"
    @test Documenter.Utilities.doccat(UnitTests.f) == "Function"
    @test Documenter.Utilities.doccat(UnitTests.pi) == "Constant"

    # repo type
    @test Documenter.Utilities.repo_host_from_url("https://bitbucket.org/somerepo") == Documenter.Utilities.RepoBitbucket
    @test Documenter.Utilities.repo_host_from_url("https://www.bitbucket.org/somerepo") == Documenter.Utilities.RepoBitbucket
    @test Documenter.Utilities.repo_host_from_url("http://bitbucket.org/somethingelse") == Documenter.Utilities.RepoBitbucket
    @test Documenter.Utilities.repo_host_from_url("http://github.com/Whatever") == Documenter.Utilities.RepoGithub
    @test Documenter.Utilities.repo_host_from_url("https://github.com/Whatever") == Documenter.Utilities.RepoGithub
    @test Documenter.Utilities.repo_host_from_url("https://www.github.com/Whatever") == Documenter.Utilities.RepoGithub
    @test Documenter.Utilities.repo_host_from_url("https://gitlab.com/Whatever") == Documenter.Utilities.RepoGitlab

    # line range
    let
        repo_type = Documenter.Utilities.RepoGithub
        line_range = 2:5
        expected_string = "L2-L5"

        formatting = Documenter.Utilities.LineRangeFormatting(repo_type)
        @test Documenter.Utilities.format_line(line_range, formatting) == expected_string
    end

    let
        repo_type = Documenter.Utilities.RepoBitbucket
        line_range = 2:5
        expected_string = "2:5"

        formatting = Documenter.Utilities.LineRangeFormatting(repo_type)
        @test Documenter.Utilities.format_line(line_range, formatting) == expected_string
    end

    import Documenter.Documents: Document, Page, Globals
    let page = Page("source", "build", [], ObjectIdDict(), Globals()), doc = Document()
        code = """
        x += 3
        γγγ_γγγ
        γγγ
        """
        exprs = Documenter.Utilities.parseblock(code, doc, page)

        @test isa(exprs, Vector)
        @test length(exprs) === 3

        @test isa(exprs[1][1], Expr)
        @test exprs[1][1].head === :+=
        @test exprs[1][2] == "x += 3\n"

        @test exprs[2][2] == "γγγ_γγγ\n"

        @test exprs[3][1] === :γγγ
        @test exprs[3][2] == "γγγ\n"
    end
end

end


text:
# Linear covariance function

include("lin_iso.jl")
include("lin_ard.jl")

@doc """
# Description
Constructors for linear kernel

# See also
LinIso, LinArd
""" ->
Lin(ll::Float64) = LinIso(ll)
Lin(ll::Vector{Float64}) = LinArd(ll)

ext: jl


text:
@testset "statistics.jl" begin
    # taken from StatsBase.jl
    @test LatSpec.autocor(1.0:5.0) ≈ [1.0, 0.4, -0.1, -0.4, -0.4]
    # uncorrelated random numbers should give a autocorrelation time of one
    @test LatSpec.autotimeexp(rand(100)) ≈ 1
    # TODO: we still need a test for autocorrelated data
end


text:
using Flux
using Flux: logitcrossentropy, throttle
using Flux.Data: Tree, children, isleaf
using Parameters: @with_kw
include("data.jl")

@with_kw mutable struct Args
    lr::Float64 = 1e-3    # Learning rate
    N::Int = 300
    throttle::Int = 10    # Throttle timeout
end

function train(; kws...)
    # Initialize HyperParameters
    args = Args(; kws...)
    # load data
    @info("Loading Data...")
    train_data = getdata(args)

    @info("Constructing model....")
    embedding = param(randn(Float32, N, length(alphabet)))

    W = Dense(2*N, N, tanh)
    combine(a, b) = W([a; b])

    sentiment = Chain(Dense(N, 5))

    function forward(tree)
      if isleaf(tree)
        token, sent = tree.value
        phrase = embedding * token
        phrase, logitcrossentropy(sentiment(phrase), sent)
      else
        _, sent = tree.value
        c1, l1 = forward(tree[1], embedding)
        c2, l2 = forward(tree[2], embedding)
        phrase = combine(c1, c2)
        phrase, l1 + l2 + logitcrossentropy(sentiment(phrase), sent)
      end
    end

    loss(tree) = forward(tree)[2]

    opt = ADAM(args.lr)
    ps = params(embedding, W, sentiment)
    evalcb = () -> @show loss(train[1])
    @info("Training Model...")
    Flux.train!(loss, ps, zip(train_data), opt,cb = throttle(evalcb, args.throttle))
end

cd(@__DIR__)
train()

max_stars_repo_name: thomascherickal/model-zoo

max_issues_repo_name: thomascherickal/model-zoo

max_forks_repo_name: thomascherickal/model-zoo


text:
"""
     NamedTupleTools

This module provides some useful NamedTuple tooling.

see [`namedtuple`](@ref), [`isprototype`](@ref),
    [`fieldnames`](@ref), [`fieldtypes`](@ref), [`fieldvalues`](@ref)
    [`delete`](@ref), [`merge`](@ref)
"""
module NamedTupleTools

export @namedtuple,
       namedtuple, isprototype,
       propertynames, fieldnames, fieldvalues, fieldtypes,
       merge,
       split,
       delete,
       select,
       ntfromstruct, structfromnt,
       @structfromnt

import Base: propertynames, fieldnames, valtype, values, merge, split

if isdefined(Base, :fieldtypes)
     import Base: fieldtypes
else
     export fieldtypes
end

# length(T), length(x::T) without type piracy
lengthof(::Type{T}) where {T<:NamedTuple} = length(T.parameters[1])
lengthof(::Type{T}) where {N,T<:NamedTuple{N}} = length(N)
lengthof(::Type{NTuple{N,S}}) where {N,S} = N
lengthof(x::NTuple{N,S}) where {N,S} = N

# accept comma delimited values
NamedTuple{T}(xs...) where {T} = NamedTuple{T}(xs)

propertynames(nt::NamedTuple{N,T}) where {N,T} = N   # correct
fieldnames(nt::NamedTuple{N,T}) where {N,T} = N      # deprecate

"""
    fieldvalues

obtain values assigned to fields of a struct type
(in field order)
"""
function fieldvalues(x::T) where {T}
     !isstructtype(T) && throw(ArgumentError("$(T) is not a struct type"))

     return ((getfield(x, name) for name in fieldnames(T))...,)
end

"""
    fieldtypes( namedtuple )
    fieldtypes( typeof(namedtuple) )

Retrieve the values' types as a tuple.

see: [`valtype`](@ref)
"""
fieldtypes(x::T) where {N,S, T<:NamedTuple{N,S}} = Tuple(T.parameters[2].parameters)

fieldtypes(::Type{T}) where {N, S<:Tuple, T<:Union{NamedTuple{N},NamedTuple{N,S}}} =
       typeof(T) === UnionAll ? Tuple((NTuple{lengthof(N),Any}).parameters) :
                                Tuple(T.parameters[2].parameters)

"""
    valtype( namedtuple )

Retrieve the values' types as a typeof(tuple).

see: [`fieldtypes`](@ref)
"""
valtype(x::T) where {N,S, T<:NamedTuple{N,S}} = T.parameters[2]

valtype(::Type{T}) where {N, S<:Tuple, T<:Union{NamedTuple{N},NamedTuple{N,S}}} =
    typeof(T) === UnionAll ? NTuple{lengthof(N),Any} : T.parameters[2]

"""
    untuple( Tuple{_} )

Retrieve the types that are internal to the `Tuple` as a (_).
"""
untuple(::Type{T}) where {T<:Tuple} = (T.parameters...,)

"""
    retuple( (_) )

Generate a `Tuple` with the given internal types as a `Tuple{_}`.
"""
retuple(x::Tuple) = Tuple{x...,}

namedtuple(x::DataType) = ntfromstruct(x)

function ntfromstruct(x::T) where {T}
     !isstructtype(T) && throw(ArgumentError("$(T) is not a struct type"))
     names = fieldnames(T)
     values = fieldvalues(x)
     return NamedTuple{names}(values)
end

# an instance of type S, a Struct
function structfromnt(::Type{S}, x::NT) where {S, N, T, NT<:NamedTuple{N,T}}
     names = N
     values = fieldvalues(x)
     if fieldnames(S) != names
          throw(ErrorException("fields in ($S) do not match ($x)"))
     end
     return S(values...,)
end

# the Struct itself
function structfromnt(structname::Union{Symbol, String}, nt::NamedTuple{N,T}) where {N,T}
    sname = Symbol(structname)
    names = N
    types = untuple(T)
    tostruct = Meta.parse(NamedTupleTools.struct_from(sname, names, types))
    eval(tostruct) # generate Struct
    return nothing
end

macro structfromnt(sname, nt)
    :( eval(structfromnt($(esc(sname)), $(esc(nt)))) )
end

# Expr part from Fredrik Ekre
struct_from(structname, names, types) =
	"Expr(:struct,
		false,
		Expr(:curly,
			 :$structname
		),
		Expr(:block,
			map((x,y) -> Expr(:(::), x, y), $names, $types)...
		)
	)"

structfrom(structname, names, types) = eval(eval(Meta.parse(struct_from(structname, names, types))))

"""
    namedtuple(namesforvalues, valuesfornames)
"""

# from kristoffer.carlsson

@inline function namedtuple(namesforvalues::NTuple{N,Symbol}, valuesfornames) where {N}
    N == length(valuesfornames) || throw(ErrorException("lengths must match"))
    return (; zip(namesforvalues, valuesfornames)...,)
end

@inline function namedtuple(namesforvalues::Vector{Symbol}, valuesfornames)
    length(namesforvalues) == length(valuesfornames) || throw(ErrorException("lengths must match"))
     return (; zip(namesforvalues, valuesfornames)...,)
end

namedtuple(namesforvalues::Vector{S}, valuesfornames) where {N,S<:AbstractString} =
    namedtuple(Symbol.(namesforvalues), valuesfornames)

namedtuple(namesforvalues::NTuple{N,S}, valuesfornames) where {N,S<:AbstractString} =
    namedtuple(Symbol.(namesforvalues), valuesfornames)

"""
    namedtuple(  name1, name2, ..  )
    namedtuple( (name1, name2, ..) )
    namedtuple(  namedtuple )

Generate a NamedTuple prototype by specifying or obtaining the fieldnames.
The prototype is applied to fieldvalues, giving a completed NamedTuple.

# Example

julia> ntprototype = namedtuple( :a, :b, :c )

NamedTuple{(:a, :b, :c),T} where T<:Tuple

julia> nt123 = ntprototype(1, 2, 3)

(a = 1, b = 2, c = 3)

julia> ntAb3 = ntprototype("A", "b", 3)

(a = "A", b = "b", c = 3)

see: [`isprototype`](@ref)
"""
namedtuple(names::NTuple{N,Symbol}) where {N} = NamedTuple{names}
namedtuple(names::Vararg{Symbol}) = NamedTuple{names}
namedtuple(names::NTuple{N,String}) where {N}  = namedtuple(Symbol.(names))
namedtuple(names::Vararg{String}) = namedtuple(Symbol.(names))
namedtuple(names::T) where {T<:AbstractVector{Symbol}} = namedtuple(names...,)
namedtuple(names::T) where {T<:AbstractVector{String}} = namedtuple(Symbol.(names))

namedtuple(nt::T) where {N,V,T<:NamedTuple{N,V}} = NamedTuple{N}
# for speed
namedtuple(nm1::T) where T<:Symbol = NamedTuple{(nm1,)}
namedtuple(nm1::T, nm2::T) where T<:Symbol = NamedTuple{(nm1,nm2)}
namedtuple(nm1::T, nm2::T, nm3::T) where T<:Symbol = NamedTuple{(nm1,nm2,nm3)}
namedtuple(nm1::T, nm2::T, nm3::T, nm4::T) where T<:Symbol = NamedTuple{(nm1,nm2,nm3,nm4)}
namedtuple(nm1::T, nm2::T, nm3::T, nm4::T, nm5::T) where T<:Symbol =
    NamedTuple{(nm1,nm2,nm3,nm4,nm5)}
namedtuple(nm1::T, nm2::T, nm3::T, nm4::T, nm5::T, nm6::T) where T<:Symbol =
    NamedTuple{(nm1,nm2,nm3,nm4,nm5,nm6)}
namedtuple(nm1::T, nm2::T, nm3::T, nm4::T, nm5::T, nm6::T, nm7::T) where T<:Symbol =
    NamedTuple{(nm1,nm2,nm3,nm4,nm5,nm6,nm7)}
namedtuple(nm1::T, nm2::T, nm3::T, nm4::T, nm5::T, nm6::T, nm7::T, nm8::T) where T<:Symbol =
    NamedTuple{(nm1,nm2,nm3,nm4,nm5,nm6,nm7,nm8)}
namedtuple(nm1::T, nm2::T, nm3::T, nm4::T, nm5::T, nm6::T, nm7::T, nm8::T, nm9::T) where T<:Symbol =
    NamedTuple{(nm1,nm2,nm3,nm4,nm5,nm6,nm7,nm8,nm9)}
namedtuple(nm1::T, nm2::T, nm3::T, nm4::T, nm5::T, nm6::T, nm7::T, nm8::T, nm9::T, nm10::T) where T<:Symbol =
    NamedTuple{(nm1,nm2,nm3,nm4,nm5,nm6,nm7,nm8,nm9,nm10)}

"""
    isprototype( ntprototype )
    isprototype( namedtuple  )

Predicate that identifies NamedTuple prototypes.

see: [`namedtuple`](@ref)
"""
isprototype(::Type{T}) where {T<:NamedTuple} = eltype(T) === Any
isprototype(nt::T) where {T<:NamedTuple} = false
isprototype(::Type{UnionAll}) = false

"""
   delete(namedtuple, symbol(s)|Tuple)
   delete(ntprototype, symbol(s)|Tuple)

Generate a namedtuple [ntprototype] from the first arg omitting fields present in the second arg.

see: [`merge`](@ref)
"""
delete(a::NamedTuple, b::Symbol) = Base.structdiff(a, namedtuple(b))
delete(a::NamedTuple, b::NTuple{N,Symbol}) where {N} = Base.structdiff(a, namedtuple(b))
delete(a::NamedTuple, bs::Vararg{Symbol}) = Base.structdiff(a, namedtuple(bs))

delete(::Type{T}, b::Symbol) where {S,T<:NamedTuple{S}} = namedtuple((Base.setdiff(S,(b,))...,))
delete(::Type{T}, b::NTuple{N,Symbol}) where {S,N,T<:NamedTuple{S}} = namedtuple((Base.setdiff(S,b)...,))
delete(::Type{T}, bs::Vararg{Symbol}) where {S,N,T<:NamedTuple{S}} = namedtuple((Base.setdiff(S,bs)...,))

"""
   select(namedtuple, symbol(s)|Tuple)
   select(ntprototype, symbol(s)|Tuple)

Generate a namedtuple [ntprototype] from the first arg, including only fields present in the second arg.

see: [`merge`](@ref)
"""
select(nt::NamedTuple, k::Symbol) = nt[k]
select(nt::NamedTuple, k::NamedTuple) = select(nt, keys(k))
select(nt::NamedTuple, ks) = namedtuple(ks)(((nt[k] for k in ks)...,))

"""
    merge(namedtuple1, namedtuple2)
    merge(nt1, nt2, nt3, ..)

Generate a namedtuple with all fieldnames and values of namedtuple2
    and every fieldname of namedtuple1 that does not occur in namedtuple2
    along with their values.

see: [`delete!`](@ref)
"""
merge(::Type{T1}, ::Type{T2}) where {N1,N2,T1<:NamedTuple{N1},T2<:NamedTuple{N2}} =
    namedtuple((unique((N1..., N2...,))...,))
merge(::Type{T1}, ::Type{T2}, ::Type{T3}) where {N1,N2,N3,T1<:NamedTuple{N1},T2<:NamedTuple{N2},T3<:NamedTuple{N3}} =
    namedtuple((unique((N1..., N2..., N3...,))...,))
merge(::Type{T1}, ::Type{T2}, ::Type{T3}, ::Type{T4}) where {N1,N2,N3,N4,T1<:NamedTuple{N1},T2<:NamedTuple{N2},T3<:NamedTuple{N3},T4<:NamedTuple{N4}} =
    namedtuple((unique((N1..., N2..., N3..., N4...,))...,))
merge(::Type{T1}, ::Type{T2}, ::Type{T3}, ::Type{T4}, ::Type{T5}) where {N1,N2,N3,N4,N5,T1<:NamedTuple{N1},T2<:NamedTuple{N2},T3<:NamedTuple{N3},T4<:NamedTuple{N4},T5<:NamedTuple{N5}} =
    namedtuple((unique((N1..., N2..., N3..., N4..., N5...,))...,))
merge(::Type{T1}, ::Type{T2}, ::Type{T3}, ::Type{T4}, ::Type{T5}, ::Type{T6}) where {N1,N2,N3,N4,N5,N6,T1<:NamedTuple{N1},T2<:NamedTuple{N2},T3<:NamedTuple{N3},T4<:NamedTuple{N4},T5<:NamedTuple{N5},T6<:NamedTuple{N6}} =
    namedtuple((unique((N1..., N2..., N3..., N4..., N5..., N6...,))...,))
merge(::Type{T1}, ::Type{T2}, ::Type{T3}, ::Type{T4}, ::Type{T5}, ::Type{T6}, ::Type{T7}) where {N1,N2,N3,N4,N5,N6,N7,T1<:NamedTuple{N1},T2<:NamedTuple{N2},T3<:NamedTuple{N3},T4<:NamedTuple{N4},T5<:NamedTuple{N5},T6<:NamedTuple{N6},T7<:NamedTuple{N7}} =
    namedtuple((unique((N1..., N2..., N3..., N4..., N5..., N6...,N7...))...,))

# merge(nt1::T1, nt2::T2) where {T1<:NamedTuple, T2<:NamedTuple} is already defined

merge(a::NamedTuple{an}, b::NamedTuple{bn}, c::NamedTuple{cn}) where {an, bn, cn} =
    reduce(merge,(a, b, c))
merge(a::NamedTuple{an}, b::NamedTuple{bn}, c::NamedTuple{cn}, d::NamedTuple{dn}) where {an, bn, cn, dn} =
    reduce(merge,(a, b, c, d))
merge(a::NamedTuple{an}, b::NamedTuple{bn}, c::NamedTuple{cn}, d::NamedTuple{dn}, e::NamedTuple{en}) where {an, bn, cn, dn, en} =
    reduce(merge,(a, b, c, d, e))
merge(a::NamedTuple{an}, b::NamedTuple{bn}, c::NamedTuple{cn}, d::NamedTuple{dn}, e::NamedTuple{en}, f::NamedTuple{fn}) where {an, bn, cn, dn, en, fn} =
    reduce(merge,(a, b, c, d, e, f))
merge(a::NamedTuple{an}, b::NamedTuple{bn}, c::NamedTuple{cn}, d::NamedTuple{dn}, e::NamedTuple{en}, f::NamedTuple{fn}, g::NamedTuple{gn}) where {an, bn, cn, dn, en, fn, gn} =
    reduce(merge,(a, b, c, d, e, f, g))

"""
    split(namedtuple, symbol(s)|Tuple)

Generate two namedtuples, the first with only the fields in the second arg, the
second with all but the fields in the second arg, such that
`merge(split(nt, ks)...) == nt` when `ks` contains the first fields of `nt`.
"""
split(nt::NamedTuple, ks::Symbol) = split(nt, (ks,))
split(nt::NamedTuple, ks) = select(nt, ks), delete(nt, ks)

#=  interconvert: NamedTuple <--> Dict =#

uniontype(nt::NamedTuple) = Union{typeof.(values(nt))...,}

"""
    gather_(x::Iterable)

Collect the elements of x into a Tuple, in their iterated order.
"""
@inline gather_(x::T) where {T} = (collect(x)...,)

namedtuple(d::T) where {T<:Dict{Symbol,V}} where {V} =
    NamedTuple{gather_(keys(d)), NTuple{length(d), V}}(gather_(values(d)))
namedtuple(d::T) where {T<:Dict{S,V}} where {S<:AbstractString, V} =
    NamedTuple{Symbol.(gather_(keys(d))), NTuple{length(d), V}}(gather_(values(d)))

# use: dict = convert(Dict, nt)
#=
   for Dict{Symbol,Any}:
   Base.convert(::Type{Dict}, x::NT) where {N, NT<:NamedTuple{N}} =
       Dict([sym=>val for (sym,val) in zip(fieldnames(x), fieldvalues(x))])
=#
Base.convert(::Type{Dict}, x::NT) where {N, NT<:NamedTuple{N}} =
    Dict{Symbol, uniontype(x)}([sym=>val for (sym,val) in zip(fieldnames(x), fieldvalues(x))])

dictionary(nt::NamedTuple) = convert(Dict, nt) # deprecated

# from PR by pdeffebach (Vector of Pairs becomes NamedTuple)
namedtuple(v::Vector{<:Pair{<:Symbol}}) = namedtuple([p[1] for p in v]...)([p[2] for p in v]...)
# with names as strings
namedtuple(v::Vector{<:Pair{<:String}}) = namedtuple([p[1] for p in v]...)([p[2] for p in v]...)

# NamedTuple becomes a Vector of Pairs
Base.convert(::Type{Vector{Pair}}, nt::NamedTuple) =  map(kv->Pair(first(kv), last(kv)), zip(keys(nt), values(nt)))

# from Sebastian Pfitzner (on Slack)
macro namedtuple(vars...)
   args = Any[]
   for v in vars
       if Meta.isexpr(v, :(=)) || Meta.isexpr(v, :...)
           push!(args, esc(v))
       else
           push!(args, Expr(:(=), esc(v), esc(v)))
       end
   end
   expr = Expr(:tuple, Expr(:parameters, args...))
   return expr
end

end # module NamedTupleTools


text:
# # Mesh grid for mode finding - Part 2
#
# In [Mesh grid for mode finding - Part 1](@ref) we used
# [`LongwaveModePropagator.trianglemesh`](@ref) to
# initialize the GRPF grid for mode finding several different ionospheres and transmit
# frequencies.
# In part 2 we'll look at where `grpf` can fail for certain scenarios.
#
# Naturally we want to be able to use the coarsest initial mesh grid as possible
# because the reflection coefficient has to be integrated through the ionosphere
# for every node of the grid. This step is by far the most computationally
# expensive operation of all of LongwaveModePropagator.
#
# First, let's load the packages needed in this example.

using Plots
using Plots.Measures
using RootsAndPoles

using LongwaveModePropagator
using LongwaveModePropagator: QE, ME, solvemodalequation, trianglemesh, defaultmesh

# In the LongwaveModePropagator tests suite, the `segmented_scenario` is known to
# miss roots if we use the same `trianglemesh` parameters (with `Δr = deg2rad(0.5)`)
# as in part 1 of the example.
#
# Here we define the `HomogeneousWaveguide` from the second half of the
# `segmented_scenario` known to have the missing roots.

frequency = Frequency(24e3)
electrons = Species(QE, ME, z->waitprofile(z, 80, 0.45), electroncollisionfrequency)
waveguide = HomogeneousWaveguide(BField(50e-6, π/2, 0), electrons, Ground(15, 0.001))
me = PhysicalModeEquation(frequency, waveguide)
title = "24 kHz\nh′: 80, β: 0.45"
nothing  #hide

# First, let's simply compute the mode equation on a fine grid.

Δr = 0.2
x = 30:Δr:90
y = -10:Δr:0
mesh = x .+ 1im*y';

# As in part 1, we also define a function to compute the modal equation phase.

function modeequationphase(me, mesh)
    phase = Vector{Float64}(undef, length(mesh))
    Threads.@threads for i in eachindex(mesh)
        f = solvemodalequation(deg2rad(mesh[i]), me)
        phase[i] = rad2deg(angle(f))
    end
    return phase
end

phase = modeequationphase(me, mesh);

heatmap(x, y, reshape(phase, length(x), length(y))';
        color=:twilight, clims=(-180, 180),
        xlims=(30, 90), ylims=(-10, 0),
        xlabel="real(θ)", ylabel="imag(θ)",
        title=title,
        right_margin=2mm)
#md savefig("meshgrid2_20knight.png"); nothing # hide
#md # ![](meshgrid2_20knight.png)

#
# Let's run the `grpf` with `Δr = 0.5`.

zbl = deg2rad(complex(30.0, -10.0))
ztr = deg2rad(complex(89.9, 0.0))
Δr = deg2rad(0.5)

mesh = trianglemesh(zbl, ztr, Δr)

params = LMPParams().grpfparams
roots, poles, quads, phasediffs, tess, g2f = grpf(θ->solvemodalequation(θ, me),
                                                  mesh, PlotData(), params);
z, edgecolors = getplotdata(tess, quads, phasediffs, g2f)

zdeg = rad2deg.(z)
rootsdeg = rad2deg.(roots)
polesdeg = rad2deg.(poles)

twilightquads = [
    colorant"#9E3D36",
    colorant"#C99478",
    colorant"#7599C2",
    colorant"#5C389E",
    colorant"#404040",
    RGB(0.0, 0.0, 0.0)
]

img = plot(real(zdeg), imag(zdeg); group=edgecolors, palette=twilightquads, linewidth=1.5,
           xlims=(30, 90), ylims=(-10, 0),
           xlabel="real(θ)", ylabel="imag(θ)", legend=false,
           title=title);
plot!(img, real(rootsdeg), imag(rootsdeg); color="red",
      seriestype=:scatter, markersize=5);
plot!(img, real(polesdeg), imag(polesdeg); color="red",
      seriestype=:scatter, markershape=:utriangle, markersize=5)
#md savefig(img, "meshgrid2_20knightmesh.png"); nothing # hide
#md # ![](meshgrid2_20knightmesh.png)

# Upon inspection, it is clear that there is a root/pole visible at the upper right
# corner of the domain in the fine mesh above that is not identified by `grpf`.
# In fact, the algorithm did not even try to refine the mesh in this region.
# That is because the root and pole pair are too closely spaced for the algorithm
# to know they are there.
# The discretized Cauchy
# [argument principle](https://en.wikipedia.org/wiki/Argument_principle) (DCAP),
# upon which GRPF is based, can only identify the presence of roots and poles if
# there are an unequal number of them.
# If the complex phase change around a tessellation contour is equal to 0, there may
# be no roots or poles in the contoured region, or there may be an equal number of
# roots and poles.
#
# This run identified 20 roots and 19 poles.
# Let's run `grpf` again with a finer resolution of `Δr = 0.2`.

Δr = deg2rad(0.2)

mesh = trianglemesh(zbl, ztr, Δr)

roots, poles, quads, phasediffs, tess, g2f = grpf(θ->solvemodalequation(θ, me),
                                                  mesh, PlotData(), params);
z, edgecolors = getplotdata(tess, quads, phasediffs, g2f)

zdeg = rad2deg.(z)
rootsdeg = rad2deg.(roots)
polesdeg = rad2deg.(poles)

img = plot(real(zdeg), imag(zdeg); group=edgecolors, palette=twilightquads, linewidth=1.5,
           xlims=(30, 90), ylims=(-10, 0),
           xlabel="real(θ)", ylabel="imag(θ)", legend=false,
           title=title);
plot!(img, real(rootsdeg), imag(rootsdeg); color="red",
      seriestype=:scatter, markersize=5);
plot!(img, real(polesdeg), imag(polesdeg); color="red",
      seriestype=:scatter, markershape=:utriangle, markersize=5)
#md savefig(img, "meshgrid2_20knightfinemesh.png"); nothing # hide
#md # ![](meshgrid2_20knightfinemesh.png)

# This higher resolution initial grid has identified 22 roots and 21 poles.
# Zooming in on the upper right region, we can see that the previously
# missing roots and poles are very closely spaced.

img = plot(real(zdeg), imag(zdeg); group=edgecolors, palette=twilightquads, linewidth=1.5,
           xlims=(80, 90), ylims=(-2, 0),
           xlabel="real(θ)", ylabel="imag(θ)", legend=false,
           title=title);
plot!(img, real(rootsdeg), imag(rootsdeg); color="red",
      seriestype=:scatter, markersize=5);
plot!(img, real(polesdeg), imag(polesdeg); color="red",
      seriestype=:scatter, markershape=:utriangle, markersize=5)
#md savefig(img, "meshgrid2_20knightfinemeshzoom.png"); nothing # hide
#md # ![](meshgrid2_20knightfinemeshzoom.png)

#
# Roots are frequently located closely to poles in the upper right of the domain for
# a variety of ionospheres. To ensure they are captured,
# [`LongwaveModePropagator.defaultmesh`](@ref)
# uses a spacing of `Δr = deg2rad(0.5)` for most of the mesh,
# but a spacing of `Δr = deg2rad(0.1)` for the region with real greater
# than 75° and imaginary greater than -1.5°.

mesh = defaultmesh(frequency)
meshdeg = rad2deg.(mesh)

img = plot(real(meshdeg), imag(meshdeg); seriestype=:scatter,
           xlabel="real(θ)", ylabel="imag(θ)",
           size=(450,375), legend=false);
plot!(img, [30, 90], [0, 0]; color="red");
plot!(img, [80, 90], [-10, 0]; color="red")
#md savefig(img, "meshgrid2_defaultmesh.png"); nothing # hide
#md # ![](meshgrid2_defaultmesh.png)


text:
mutable struct TermSum <: AbstractVector{Term}
    #=
    Sum of terms. An operator that is a sum of Majorana products with coefficients can be
    represented by this type.
    =#

    terms::Vector{Term}

end

# interface with abstract array
size(ts::TermSum) = size(ts.terms)

getindex(ts::TermSum, i) = getindex(ts.terms, i)

setindex!(ts::TermSum, t::Term, i) = setindex!(ts.terms, t, i)

function show(io::IO, ts::TermSum)
    #=
    Formatted printing.
    =#

    print(io, ts[1], "\n")
    for t in ts[2:end]
        print(io, "+ ", t, "\n")
    end

end

empty!(ts::TermSum) = empty!(ts.terms)

push!(ts::TermSum, tms...) = push!(ts.terms, tms...)

function zero!(ts::TermSum)
    #=
    Set to canonical zero.
    =#

    empty!(ts)
    push!(ts, zero(Term))

    nothing

end

function simplify!(ts::TermSum)
    #=
    Simplify the sum of terms.
    =#

    # put all terms into simplified form
    simplify!.(ts)

    # if coefficients can be added together, do so
    newterms = _compresssum(ts)

    # find the zeros
    zs = iszero!.(newterms)

    if all(zs)
        # set to canonical zero
        zero!(ts)
    else
        # remove the zero coefficents
        deleteat!(newterms, zs)

        # replace terms with simplified version
        ts.terms = newterms
    end

    nothing

end

function drop!(ts::TermSum, f::Function)
    #=
    Drop terms for which a function returns true.
    =#

    deleteat!(ts.terms, f.(ts))

    nothing

end

function *(ts1::TermSum, ts2::TermSum)
    #=
    Multiply two sums of terms.
    =#

    newterms = [t1 * t2 for t1 in ts1, t2 in ts2 if true] # a 1d array
    newts = TermSum(newterms)

    # simplify the result
    simplify!(newts)

    newts

end

function commutator(ts1::TermSum, ts2::TermSum)
    #=
    Commutator of term sums.
    =#

    newterms = [commutator(t1, t2) for t1 in ts1, t2 in ts2 if true] # a 1d array
    newtermsum = TermSum(newterms)

    # simplify the result
    simplify!(newtermsum)

    newtermsum

end

function +(ts1::TermSum, ts2::TermSum)
    #=
    Add term sums.
    =#

    newts = TermSum([ts1.terms; ts2.terms])

    # simplify the result
    simplify!(newts)

    newts

end

max_stars_repo_name: aormorningstar/SymbolicMajoranas

max_issues_repo_name: aormorningstar/SymbolicMajoranas

max_forks_repo_name: aormorningstar/SymbolicMajoranas


text:
using Turing: WelfordCovar, NaiveCovar, add_sample!, get_covar, reset!
using Test, LinearAlgebra

let
    D = 1000
    wc = WelfordCovar(0, zeros(D), zeros(D,D))
    add_sample!(wc, randn(D))
    reset!(wc)

    # Check that reseting zeros everything.
    @test wc.n === 0
    @test wc.μ == zeros(D)
    @test wc.M == zeros(D,D)

    # Ensure that asking for the variance doesn't mutate the WelfordVar.
    add_sample!(wc, randn(D))
    add_sample!(wc, randn(D))
    μ, M = deepcopy(wc.μ), deepcopy(wc.M)

    @test wc.μ == μ
    @test wc.M == M
end

# Check that the estimated variance is approximately correct.
let
    D = 10
    wc = WelfordCovar(0, zeros(D), zeros(D,D))

    for _ = 1:10000
        s = randn(D)
        add_sample!(wc, s)
    end

    covar = get_covar(wc)

    @test covar ≈ LinearAlgebra.diagm(0 => ones(D)) atol=0.2
end


text:
"""Convenience type for passing around a sequence and its per-base
error probabilities.

"""
mutable struct RifrafSequence
    seq::DNASeq
    est_n_errors::Float64
    error_log_p::Vector{LogProb}
    match_scores::Vector{Score}
    mismatch_scores::Vector{Score}
    ins_scores::Vector{Score}
    del_scores::Vector{Score}
    codon_ins_scores::Vector{Score}
    codon_del_scores::Vector{Score}
    bandwidth::Int
    bandwidth_fixed::Bool
end

function RifrafSequence(seq::DNASeq, error_log_p::Vector{LogProb},
                        bandwidth::Int, scores::Scores)
    if bandwidth < 1
        error("bandwidth must be positive")
    end

    if length(seq) != length(error_log_p)
        error("length mismatch")
    end
    if length(seq) == 0
        return RifrafSequence()
    end
    if minimum(error_log_p) == -Inf
        error("a log error probability is negative infinity")
    end
    if maximum(error_log_p) > 0.0
        bad_value = maximum(error_log_p)
        error("a log error probability is > 0: $bad_value")
    end
    # all scores are symmetric on the ends so there are fewer
    # branches in the alignment code
    match_scores = Vector{Score}(length(error_log_p))
    mismatch_scores = Vector{Score}(length(error_log_p))
    ins_scores = Vector{Score}(length(error_log_p))
    del_scores = Vector{Score}(length(error_log_p) + 1)

    match_scores[:] = log10.(1.0 - exp10.(error_log_p))
    mismatch_scores[:] = error_log_p + scores.mismatch
    ins_scores[:] = error_log_p + scores.insertion

    del_scores[1] = error_log_p[1] + scores.deletion
    del_scores[end] = error_log_p[end] + scores.deletion
    for i = 1:(length(error_log_p) - 1)
        del_scores[i + 1] = max(error_log_p[i], error_log_p[i + 1]) + scores.deletion
    end

    codon_ins_scores = Vector{Score}()
    codon_del_scores = Vector{Score}()
    if scores.codon_insertion > -Inf
        codon_ins_scores = Vector{Score}(length(error_log_p) - 2)
        for i = 2:(length(error_log_p) - 1)
            codon_ins_scores[i - 1] = max(error_log_p[i - 1],
                                          error_log_p[i],
                                          error_log_p[i + 1]) + scores.codon_insertion
        end
    end
    if scores.codon_deletion > -Inf
        codon_del_scores = Vector{Score}(length(error_log_p) + 1)
        codon_del_scores[1] = error_log_p[1] + scores.codon_deletion
        codon_del_scores[end] = error_log_p[end] + scores.codon_deletion
        for i = 1:(length(error_log_p) - 1)
            codon_del_scores[i + 1] = max(error_log_p[i], error_log_p[i + 1]) + scores.codon_deletion
        end
    end

    n_errors = sum(exp10.(error_log_p))

    return RifrafSequence(seq, n_errors, error_log_p,
                          match_scores, mismatch_scores,
                          ins_scores, del_scores,
                          codon_ins_scores, codon_del_scores,
                          bandwidth, false)
end

"""Given PHRED scores instead of log error rates."""
function RifrafSequence(seq::DNASeq, phreds::Vector{Int8}, bandwidth::Int, scores::Scores)
    error_log_p = phred_to_log_p(phreds)
    return RifrafSequence(seq, error_log_p, bandwidth, scores)
end

"""Update scores for the given sequence."""
function RifrafSequence(seq::RifrafSequence, scores::Scores)
    result = RifrafSequence(seq.seq, seq.error_log_p, seq.bandwidth, scores)
    result.bandwidth_fixed = seq.bandwidth_fixed
    return result
end

"""Empty sequence"""
function RifrafSequence()
    RifrafSequence(DNASeq(), 0.0, LogProb[], Score[], Score[], Score[],
                   Score[], Score[], Score[], 0, false)
end

do_codon_ins(s::RifrafSequence) = length(s.codon_ins_scores) > 0
do_codon_del(s::RifrafSequence) = length(s.codon_del_scores) > 0
do_codon_moves(s::RifrafSequence) = do_codon_ins(s) || do_codon_del(s)

function Base.length(s::RifrafSequence)
    return length(s.seq)
end


text:
Pkg.clone("https://github.com/paulmthompson/SpikeSorting.jl")
#Pkg.add("Gtk")
#Pkg.checkout("Gtk")
#Pkg.checkout("DistributedArrays")


text:
# from nose.tools import *
# from bibpy import *
# import glob
using Test
using Citations
using Glob

function test_parsing()
    for example in glob("*.bib")
        parse_bib(example)
    end
end

function parse_bib(f)
    contents = readlines(open(f, "r"))
    data = clear_comments(contents)
    bib = Parser(data)
#     # bib.parse()
#     # data = bib.json()
end

test_parsing()


text:
sphere(x, y) = x^2 + y^2
matyas(x, y) = 0.26 .* (x^2 + y^2) - 0.48 .* x .* y
goldstein(x, y) = (1 + (x + y + 1)^2 .* (19 - 14 .* x + 3 .* x^2 - 14 .* y + 6 .* x .* y + 3 .* y^2)) .*
    (30 + (2 .* x - 3 .* y)^2 .* (18 - 32 .* x + 12 .* x^2 + 48 .* y - 36 .* x .* y + 27 .* y^2))

function check(f, ans)
    x = Var(1)
    y = Var(1)
    z = f(x, y)
    gradient!(z)
    (x.grad.data, y.grad.data) == ans
end

@test check(sphere, ([2.0], [2.0]))
@test check(matyas, ([0.040000000000000036], [0.040000000000000036]))
@test check(goldstein, ([-5376.0], [8064.0]))


text:
function gctest(manager)
    Local = CachedArrays.Local
    Remote = CachedArrays.Remote
    @show manager

    len = 1024
    B = rand(Float32, len)
    A = CachedArray{Float32}(undef, manager, len; status = CachedArrays.ReadWrite())

    @test isa(A, CachedArray)
    # Test assignment works
    A .= B
    @test A == B
    @test length(A) == len

    @test CachedArrays.pool(A) == Local

    # Make sure the global manager is updated correctly.
    @test inlocal(manager, A)
    @test !inremote(manager, A)

    # Evict this object.
    CachedArrays.evict!(A)

    # Make sure our query functions work
    @test CachedArrays.pool(A) == Remote
    @test A == B

    # Make sure the cache gets updated.
    @test !inlocal(manager, A)

    @test inremote(manager, A)

    # Now prefetch the array back
    CachedArrays.prefetch!(A)
    @test CachedArrays.pool(A) == Local
    @test A == B

    # The cache maanger should now have this array stored at both locations.
    @test inlocal(manager, A)
    @test inremote(manager, A; primary_only = false)
    return nothing
end

@testset "Testing Array Correctness" begin
    # Grab ahold of the default manager so we can make sure it gets updated correctly.
    GC.gc(true)
    manager = CachedArrays.CacheManager(
        CachedArrays.AlignedAllocator(),
        CachedArrays.AlignedAllocator();
        localsize = 1_000_000_000,
        remotesize = 1_000_000_000,
        minallocation = 12,
    )

    # Run the GC test, then run garbage collection.
    gctest(manager)
    @test CachedArrays.check(manager)
    GC.gc(true)
    @test CachedArrays.check(manager)

    # Wrap this body of code in a "let" block to ensure that GC will clean up any temporary
    # arrays created in the body.
    let
        Local, Remote = CachedArrays.Local, CachedArrays.Remote
        @test length(CachedArrays.visible_ids(manager, Local; primary_only = true)) == 0
        @test length(CachedArrays.visible_ids(manager, Remote; primary_only = true)) == 0
        # Okay, now start doing some operations on arrays.
        len = 2_000_000
        A = CachedArray{Float32}(undef, manager, len; status = CachedArrays.ReadWrite())
        B = CachedArray{Float32}(undef, manager, len; status = CachedArrays.ReadWrite())

        vA = rand(Float32, len)
        vB = rand(Float32, len)

        A .= vA
        B .= vB

        @test inlocal(manager, A)
        @test inlocal(manager, B)
        #@test CachedArrays.getsize(manager.remotemap) == 0

        # Do a broadcasting add - make sure the newly created object is a CachedArray
        # and that it gets registered with the cache.
        C = A .+ B
        @test isa(C, CachedArray)
        @test length(CachedArrays.visible_ids(manager, Local; primary_only = true)) == 3

        @test inlocal(manager, A)
        @test inlocal(manager, B)
        @test inlocal(manager, C)

        # Now do some timing to make sure the overhead of CachedArrays isn't stupid.
        f!(C, A, B) = C .= A .+ B
        f!(C, A, B)
        @test (@allocated f!(C, A, B)) == 0
    end
end

@testset "Testing Cleanup" begin
    @test CachedArrays.gc_managers() == 1
end


text:
module Tomography

using Plots
using FFTW
using Interpolations

include("phantom.jl")
include("Radon.jl")
include("wave_forward.jl")
include("utils.jl")

#Radon Transform
export phantom

using Reexport

@reexport using
    .Radon
#PAT(PhotoAcoustic Tomography)
#export wave_forward

end # module


text:
###############
# FiniteBasis #
###############
    # A FiniteBasis repesents the tensor product
    # of an arbitrary number of fock bases, each of which is truncated
    # at a known, finite number of states. For example, imagine the
    # following fock bases and their tensor product:
    #
    # F_4 => {|0>, |1>, |2>, |3>}
    # F_3 => {|0>, |1>, |2>}
    # F_2 => {|0>, |1>}
    #
    # F_4 x F_3 x F_2 => {|0,0,0>, |0,0,1>, |0,1,0>, |0,1,1>...|3,2,1>}
    #
    # Note that the subscript number refers to basis *length*, not max
    # excitation level.
    #
    # This tensor product basis, F_4 x F_3 x F_2, could be represented
    # by calling FiniteBasis(4, 3, 2).

    immutable FiniteBasis{S} <: AbstractFiniteBasis{S}
        lens::@compat(Tuple{Vararg{Int}})
        FiniteBasis(lens::@compat(Tuple{Vararg{Int}})) = new(lens)
        FiniteBasis(lens::Int...) = new(lens)
    end

    FiniteBasis{S<:AbstractStructure}(lens::@compat(Tuple{Vararg{Int}}), ::Type{S}=Orthonormal) = FiniteBasis{S}(lens)
    FiniteBasis(lens::Int...) = FiniteBasis(lens)

    Base.convert{A,B}(::Type{FiniteBasis{A}}, f::FiniteBasis{B}) = FiniteBasis(f.lens, A)

    ######################
    # Property Functions #
    ######################
    structure{S}(::Type{FiniteBasis{S}}) = S

    Base.length(basis::FiniteBasis) = prod(basis.lens)
    Base.size(basis::FiniteBasis) = basis.lens
    Base.size(basis::FiniteBasis, i) = basis.lens[i]

    nfactors(basis::FiniteBasis) = length(basis.lens)
    checkcoeffs(coeffs, dim::Int, basis::FiniteBasis) = size(coeffs, dim) == length(basis)

    ##########################
    # Mathematical Functions #
    ##########################
    tensor{S}(a::FiniteBasis{S}, b::FiniteBasis{S}) = FiniteBasis(tuple(a.lens..., b.lens...), S)

    ######################
    # Printing Functions #
    ######################
    Base.repr(f::FiniteBasis) = "$(typeof(f))$(size(f))"
    Base.show(io::IO, f::FiniteBasis) = print(io, repr(f))

export FiniteBasis,
    structure,
    tensor,
    checkcoeffs


text:
#
# This file is part of the DiscreteEvents.jl Julia package, MIT license
#
# Paul Bayer, 2020
#
# This is a Julia package for discrete event simulation
#

import Base.invokelatest

function event! end

# Function barrier for functions and expressions as well for arguments and
# keywords of a `fun`. It allows Expr, Symbol and `fun` as arguments for `fun`.
_evaluate(y) = y    # catchall, gives back the argument
function _evaluate(y::T) where {T<:Function}
    # try
    #     y()
    # catch exc
    #     if exc isa MethodError
    #         invokelatest(y)
    #     else
    #         rethrow(exc)
    #     end
    # end
    if (threadid() == 1) # || (parentmodule(y) != Main)
        y()
    else
        invokelatest(y)
    end
end
_evaluate(y::T) where {T<:Tuple{Vararg{<:Any}}} = _evaluate.(y)
_evaluate(kw::T) where {T<:Iterators.Pairs} = (; zip(keys(kw), map(i->_evaluate(i), values(kw)) )...)
function _evaluate(y::T) where {T<:Union{Symbol,Expr}}  # Symbol,Expr: eval
    try
        ret = Core.eval(Main,y)
        @warn "Evaluating expressions is slow, use functions instead" maxlog=1
        return ret
    catch
        return y
    end
end

# Function barrier for executing `fun`s.
_invoke(f::F, ::Nothing, ::Nothing) where {F<:Function} = f()
_invoke(f::F, arg::T, ::Nothing) where {F<:Function,T<:Tuple{Vararg{<:Any}}} = f(_evaluate.(arg)...)
_invoke(f::F, ::Nothing, kw) where {F<:Function} = f(; _evaluate(kw)...)
_invoke(f::F, arg::T, kw) where {F<:Function,T<:Tuple{Vararg{<:Any}}} = f(_evaluate.(arg)...; _evaluate(kw)...)
_invoke(f::typeof(event!), arg::T, ::Nothing) where {T<:Tuple{Vararg{<:Any}}} = f(arg...)
_invoke(f::typeof(event!), ::Nothing, kw) = f(; kw...)
_invoke(f::typeof(event!), arg::T, kw) where {T<:Tuple{Vararg{<:Any}}} = f(arg..., kw...)

"""
    fun(f::Function, args...; kwargs...)

Save a function `f` and its arguments in a closure for later execution.

# Arguments
The arguments `args...` and keyword arguments `kwargs...` to fun are passed to `f` at
execution but may change their values between beeing captured in `fun` and `f`s later
execution. If `f` needs their current values at execution time there are two possibilities:
1. `fun` can take `fun`s, function closures, symbols or expressions at the place of values
    or variable arguments. They are evaluated at event time just before being passed to f.
    There is one exception: if `f` is an `event!`, its arguments are passed on unevaluated.
2.  A mutable type argument (Array, struct ...) is always current. You can
    also change its content from within a function.

!!! warning "Evaluating symbols and expressions is slow"
    Symbols and Expr should be avoided in time critical parts of applications. You will
    get a one time warning if you use that feature. They can be replaced easily by
    `fun`s or function closures. They are evaluated at global scope in Module `Main` only.
    Other modules using `DiscreteEvents.jl` cannot use this feature and have to use
    functions.

# Examples
```jldoctest; filter = r".*fclosure.jl:[0-9]+"
julia> using DiscreteEvents

julia> g(x; y=1) = x+y
g (generic function with 1 method)

julia> x = 1
1

julia> gg = fun(g, :x, y=2);   # we pass x as a symbol to fun

julia> x += 1   # a becomes 2
2

julia> gg()     # at execution g gets a current x and gives a warning
┌ Warning: Evaluating expressions is slow, use functions instead
└ @ DiscreteEvents ~/.julia/dev/DiscreteEvents/src/fclosure.jl:37
4

julia> hh = fun(g, fun(()->x), y=3);   # reference x with an anonymous fun

julia> x += 1   # x becomes 3
3

julia> hh()     # at execution g gets again a current x
6

julia> ii = fun(g, ()->x, y=4);  # reference x with an anonymous function

julia> x += 1   # x becomes 4
4

julia> ii()     # ok, g gets an updated x
8
```
"""
@inline function fun(f::F, args::Vararg{Any, N}; kwargs...) where {F<:Function,N}
    args = ifelse(isempty(args), nothing, args)
    kwargs = ifelse(isempty(kwargs), nothing, kwargs)
    () -> _invoke(f, args, kwargs)
end


text:
using .HDF5
import Libdl

export PHDF5Driver

"""
    PHDF5Driver(; fcpl, fapl)

Parallel HDF5 driver using the HDF5.jl package.

HDF5 file creation and file access property lists may be specified via the
`fcpl` and `fapl` keyword arguments respectively.

Note that the MPIO file access property list does not need to be set, as this is
done automatically by this driver when the file is opened.
"""
struct PHDF5Driver <: ParallelIODriver
    fcpl :: HDF5.Properties
    fapl :: HDF5.Properties
    function PHDF5Driver(;
            fcpl = HDF5.DEFAULT_PROPERTIES,
            fapl = HDF5.DEFAULT_PROPERTIES,
        )
        check_hdf5_parallel()
        if fcpl == HDF5.DEFAULT_PROPERTIES
            fcpl = create_property(HDF5.H5P_FILE_CREATE)
        end
        if fapl == HDF5.DEFAULT_PROPERTIES
            fapl = create_property(HDF5.H5P_FILE_ACCESS)
            # This is the default in HDF5.jl -- makes sense due to GC
            fapl[:fclose_degree] = HDF5.H5F_CLOSE_STRONG
        end
        new(fcpl, fapl)
    end
end

const HDF5FileOrGroup = Union{HDF5.File, HDF5.Group}

const H5MPIHandle = let csize = sizeof(MPI.MPI_Comm)
    @assert csize in (4, 8)
    csize === 4 ? HDF5.Hmpih32 : HDF5.Hmpih64
end

mpi_to_h5_handle(obj::Union{MPI.Comm, MPI.Info}) =
    reinterpret(H5MPIHandle, obj.val)

function h5_to_mpi_handle(
        ::Type{T}, h::H5MPIHandle) where {T <: Union{MPI.Comm, MPI.Info}}
    T(reinterpret(MPI.MPI_Comm, h))
end

const _HAS_PARALLEL_HDF5 = Libdl.dlopen(HDF5.libhdf5) do lib
    Libdl.dlsym(lib, :H5Pget_fapl_mpio, throw_error=false) !== nothing
end

"""
    hdf5_has_parallel() -> Bool

Returns `true` if the loaded HDF5 libraries support MPI-IO.
"""
hdf5_has_parallel() = _HAS_PARALLEL_HDF5

function check_hdf5_parallel()
    hdf5_has_parallel() && return
    error(
        "HDF5.jl has no parallel support." *
        " Make sure that you're using MPI-enabled HDF5 libraries, and that" *
        " MPI was loaded before HDF5." *
        " See https://jipolanco.github.io/PencilArrays.jl/latest/PencilIO/#Setting-up-Parallel-HDF5-1" *
        " for details."
    )
end

function keywords_to_h5open(; kws...)
    flags, other_kws = keywords_to_open(; kws...)
    (
        flags.read,
        flags.write,
        flags.create,
        flags.truncate,
        flags.append,
    ), other_kws
end

"""
    open([f::Function], driver::PHDF5Driver, filename, comm::MPI.Comm; keywords...)

Open parallel file using the Parallel HDF5 driver.

See [`open(::ParallelIODriver)`](@ref) for common options for all drivers.

Driver-specific options may be passed via the `driver` argument. See
[`PHDF5Driver`](@ref) for details.
"""
function Base.open(::PHDF5Driver) end

function Base.open(D::PHDF5Driver, filename::AbstractString, comm::MPI.Comm; kw...)
    mode_args, other_kws = keywords_to_h5open(; kw...)
    info = MPI.Info(other_kws...)
    fcpl = D.fcpl
    fapl = D.fapl
    fapl[:fapl_mpio] = mpi_to_h5_handle.((comm, info))
    swmr = false

    # The code below is adapted from h5open in HDF5.jl v0.15
    # TODO propose alternative h5open for HDF5.jl, taking keyword arguments `read`, `write`, ...
    # Then we wouldn't need to copy code from HDF5.jl...
    rd, wr, cr, tr, ff = mode_args
    if ff && !wr
        error("HDF5 does not support appending without writing")
    end

    fid = if cr && (tr || !isfile(filename))
        flag = swmr ? HDF5.H5F_ACC_TRUNC | HDF5.H5F_ACC_SWMR_WRITE : HDF5.H5F_ACC_TRUNC
        HDF5.h5f_create(filename, flag, fcpl, fapl)
    else
        HDF5.ishdf5(filename) ||
            error("unable to determine if $filename is accessible in the HDF5 format (file may not exist)")
        if wr
            flag = swmr ? HDF5.H5F_ACC_RDWR | HDF5.H5F_ACC_SWMR_WRITE : HDF5.H5F_ACC_RDWR
        else
            flag = swmr ? HDF5.H5F_ACC_RDONLY | HDF5.H5F_ACC_SWMR_READ : HDF5.H5F_ACC_RDONLY
        end
        HDF5.h5f_open(filename, flag, fapl)
    end

    close(fapl)
    fcpl != HDF5.DEFAULT_PROPERTIES && close(fcpl)

    HDF5.File(fid, filename)
end

"""
    setindex!(
        g::Union{HDF5.File,HDF5.Group}, x::MaybePencilArrayCollection,
        name::AbstractString; chunks = false, collective = true, prop_lists...,
    )

Write [`PencilArray`](@ref) or [`PencilArrayCollection`](@ref) to parallel HDF5
file.

For performance reasons, the memory layout of the data is conserved. In other
words, if the dimensions of a `PencilArray` are permuted in memory, then the
data is written in permuted form.

In the case of a `PencilArrayCollection`, each array of the collection is written
as a single component of a higher-dimension dataset.

# Optional arguments

- if `chunks = true`, data is written in chunks, with roughly one chunk
  per MPI process. This may (or may not) improve performance in parallel
  filesystems.

- if `collective = true`, the dataset is written collectivelly. This is
  usually recommended for performance.

- additional property lists may be specified by key-value pairs in
  `prop_lists`, following the [HDF5.jl
  syntax](https://juliaio.github.io/HDF5.jl/stable/#Passing-parameters).
  These property lists take precedence over keyword arguments.
  For instance, if the `dxpl_mpio = HDF5.H5FD_MPIO_COLLECTIVE` option is passed,
  then the value of the `collective` argument is ignored.

# Property lists

Property lists are passed to
[`h5d_create`](https://portal.hdfgroup.org/display/HDF5/H5D_CREATE2)
and [`h5d_write`](https://portal.hdfgroup.org/display/HDF5/H5D_WRITE).
The following property types are recognised:
- [link creation properties](https://portal.hdfgroup.org/display/HDF5/Attribute+and+Link+Creation+Properties),
- [dataset creation properties](https://portal.hdfgroup.org/display/HDF5/Dataset+Creation+Properties),
- [dataset access properties](https://portal.hdfgroup.org/display/HDF5/Dataset+Access+Properties),
- [dataset transfer properties](https://portal.hdfgroup.org/display/HDF5/Dataset+Transfer+Properties).

# Example

Open a parallel HDF5 file and write some `PencilArray`s to the file:

```julia
pencil = Pencil(#= ... =#)
u = PencilArray{Float64}(undef, pencil)
v = similar(u)

# [fill the arrays with interesting values...]

comm = get_comm(u)

open(PHDF5Driver(), "filename.h5", comm, write=true) do ff
    ff["u", chunks=true] = u
    ff["uv"] = (u, v)  # this is a two-component PencilArrayCollection (assuming equal dimensions of `u` and `v`)
end
```

"""
function Base.setindex!(
        g::HDF5FileOrGroup, x::MaybePencilArrayCollection,
        name::AbstractString; chunks=false, collective=true, prop_pairs...,
    )
    to = timer(pencil(x))

    @timeit_debug to "Write HDF5" begin

    check_phdf5_file(g, x)

    # Add extra property lists if required by keyword args.
    props = Dict{Symbol,Any}(pairs(prop_pairs))

    if chunks && !haskey(prop_pairs, :chunk)
        chunk = h5_chunk_size(x, MemoryOrder())
        props[:chunk] = chunk
    end

    if collective && !haskey(prop_pairs, :dxpl_mpio)
        props[:dxpl_mpio] = HDF5.H5FD_MPIO_COLLECTIVE
    end

    dims_global = h5_dataspace_dims(x)
    @timeit_debug to "create dataset" dset =
        create_dataset(g, name, h5_datatype(x), dataspace(dims_global); props...)
    inds = range_local(x, MemoryOrder())
    @timeit_debug to "write data" to_hdf5(dset, x, inds)
    @timeit_debug to "write metadata" write_metadata(dset, x)

    end

    x
end

# Write metadata as HDF5 attributes attached to a dataset.
# Note that this is a collective operation (all processes must call this).
function write_metadata(dset::HDF5.Dataset, x)
    meta = metadata(x)
    for (name, val) in pairs(meta)
        dset[string(name)] = to_hdf5(val)
    end
    dset
end

to_hdf5(val) = val
to_hdf5(val::Tuple{}) = false  # empty tuple
to_hdf5(val::Tuple) = SVector(val)
to_hdf5(::Nothing) = false

"""
    read!(g::Union{HDF5File,HDF5Group}, x::MaybePencilArrayCollection,
          name::AbstractString; collective=true, prop_lists...)

Read [`PencilArray`](@ref) or [`PencilArrayCollection`](@ref) from parallel HDF5
file.

See [`setindex!`](@ref) for details on optional arguments.

# Property lists

Property lists are passed to
[`h5d_open`](https://portal.hdfgroup.org/display/HDF5/H5D_OPEN2)
and [`h5d_read`](https://portal.hdfgroup.org/display/HDF5/H5D_READ).
The following property types are recognised:
- [dataset access properties](https://portal.hdfgroup.org/display/HDF5/Dataset+Access+Properties),
- [dataset transfer properties](https://portal.hdfgroup.org/display/HDF5/Dataset+Transfer+Properties).

# Example

Open a parallel HDF5 file and read some `PencilArray`s:

```julia
pencil = Pencil(#= ... =#)
u = PencilArray{Float64}(undef, pencil)
v = similar(u)

comm = get_comm(u)
info = MPI.Info()

open(PHDF5Driver(), "filename.h5", comm, read=true) do ff
    read!(ff, u, "u")
    read!(ff, (u, v), "uv")
end
```
"""
function Base.read!(g::HDF5FileOrGroup, x::MaybePencilArrayCollection,
                    name::AbstractString; collective=true, prop_pairs...)
    to = timer(pencil(x))

    @timeit_debug to "Read HDF5" begin

    dapl = create_property(HDF5.H5P_DATASET_ACCESS; prop_pairs...)
    dxpl = create_property(HDF5.H5P_DATASET_XFER; prop_pairs...)

    # Add extra property lists if required by keyword args.
    if collective && !haskey(prop_pairs, :dxpl_mpio)
        HDF5.h5p_set_dxpl_mpio(dxpl.id, HDF5.H5FD_MPIO_COLLECTIVE)
    end

    dims_global = h5_dataspace_dims(x)
    @timeit_debug to "open dataset" dset = open_dataset(g, string(name), dapl, dxpl)
    check_phdf5_file(parent(dset), x)

    if dims_global != size(dset)
        throw(DimensionMismatch(
            "incompatible dimensions of HDF5 dataset and PencilArray"))
    end

    inds = range_local(x, MemoryOrder())
    @timeit_debug to "read data" from_hdf5!(dset, x, inds)

    end

    x
end

function check_phdf5_file(g, x)
    check_hdf5_parallel()

    plist_id = HDF5.h5f_get_access_plist(HDF5.file(g))
    plist = HDF5.Properties(plist_id, HDF5.H5P_FILE_ACCESS)

    # Get HDF5 ids of MPIO driver and of the actual driver being used.
    driver_mpio = ccall((:H5FD_mpio_init, HDF5.libhdf5), HDF5.hid_t, ())
    driver = HDF5.h5p_get_driver(plist)
    if driver !== driver_mpio
        error("HDF5 file was not opened with the MPIO driver")
    end

    comm, info = _h5p_get_fapl_mpio(plist)
    if isdefined(MPI, :Comm_compare)  # requires recent MPI.jl
        if MPI.Comm_compare(comm, get_comm(x)) ∉ (MPI.IDENT, MPI.CONGRUENT)
            throw(ArgumentError(
                "incompatible MPI communicators of HDF5 file and PencilArray"
            ))
        end
    end

    close(plist)
    nothing
end

to_hdf5(dset, x::PencilArray, inds) = dset[inds...] = parent(x)

function from_hdf5!(dset, x::PencilArray, inds)
    u = parent(x)

    if stride(u, 1) != 1
        u .= dset[inds...]  # short and easy version (but allocates!)
        return x
    end

    # The following is adapted from one of the _getindex() in HDF5.jl.
    HDF5Scalar = HDF5.ScalarType
    T = eltype(x)
    if !(T <: Union{HDF5Scalar, Complex{<:HDF5Scalar}})
        error("Dataset indexing (hyperslab) is available only for bits types")
    end

    dsel_id = HDF5.hyperslab(dset, inds...)
    memtype = HDF5.datatype(u)
    memspace = HDF5.dataspace(u)

    try
        # This only works for stride-1 arrays.
        HDF5.h5d_read(dset.id, memtype.id, memspace.id, dsel_id, dset.xfer, u)
    finally
        close(memtype)
        close(memspace)
        HDF5.h5s_close(dsel_id)
    end

    x
end

# Define variants for collections.
for func in (:from_hdf5!, :to_hdf5)
    @eval function $func(dset, col::PencilArrayCollection, inds_in)
        for I in CartesianIndices(collection_size(col))
            inds = (inds_in..., Tuple(I)...)
            $func(dset, col[I], inds)
        end
    end
end

h5_datatype(x::PencilArray) = datatype(eltype(x))
h5_datatype(x::PencilArrayCollection) = h5_datatype(first(x))

h5_dataspace_dims(x::PencilArray) = size_global(x, MemoryOrder())
h5_dataspace_dims(x::PencilArrayCollection) =
    (h5_dataspace_dims(first(x))..., collection_size(x)...)

function h5_chunk_size(x::PencilArray, order = MemoryOrder())
    # Determine chunk size for writing to HDF5 dataset.
    # The idea is that each process writes to a single separate chunk of the
    # dataset, of size `dims_local`.
    # This only works if the data is ideally balanced among processes, i.e. if
    # the local dimensions of the dataset are the same for all processes.
    dims_local = size_local(x, order)

    # In the general case that the data is not well balanced, we take the
    # minimum size along each dimension.
    chunk = MPI.Allreduce(collect(dims_local), min, get_comm(x))

    N = ndims(x)
    @assert length(chunk) == N
    ntuple(d -> chunk[d], Val(N))
end

h5_chunk_size(x::PencilArrayCollection, args...) =
    (h5_chunk_size(first(x), args...)..., collection_size(x)...)

function _h5p_get_fapl_mpio(fapl_id)
    h5comm, h5info = HDF5.h5p_get_fapl_mpio(fapl_id, H5MPIHandle)
    comm = h5_to_mpi_handle(MPI.Comm, h5comm)
    info = h5_to_mpi_handle(MPI.Info, h5info)
    comm, info
end


text:
using Documenter, Psycho, DataFrames, GLM

makedocs(
    format = :html,
    sitename = "Psycho.jl",
    authors="Dominique Makowski",
    analytics="UA-97457476-4",
    modules = [Psycho, DataFrames, GLM],
    strict = false,
    pages = [
         "index.md",
         "man/tutorials.md",
         "man/API.md",
         "man/about.md"
    ]
)

deploydocs(
    repo = "github.com/neuropsychology/Psycho.jl.git",
    target = "build",
    julia  = "1.0",
    osname = "linux",
    deps   = nothing,
    make   = nothing
)


text:
using ITensors

abstract type AbstractLattice end

struct IrregularLattice <: AbstractLattice end

struct HyperCubic{N} <: AbstractLattice
  dims::NTuple{N,Int}
end

struct ITensorNetwork{T,L}
  data::Dict{T,ITensor} # Map tensor labels to tensors
  tags::Dict{T,TagSet} # Map tensor labels to tags that categorize the tensors, such as boundary
  lattice::L
  out_boundary::Dict{T,T} # Represent links across boundaries
  in_boundary::Dict{T,T} # Represent links across boundaries
end

ts = ("i", "j", "k", "l")
ds = (2, 2, 2, 2)
i, j, k, l = Index.(ds, ts)
A, B, C, D = ITensor.(((i, j), (j, k), (k, l), (l, i)))

tn = ITensorNetwork(["A" => A, "B" => B, "C" => C, "D" => D])


text:
module RunTests

using Test

@testset "IndexSets" begin include("IndexSetsTests.jl") end

@testset "SparseUtils" begin include("SparseUtilsTests.jl") end

@testset "Sequential" begin include("sequential/runtests.jl") end

@testset "MPI" begin include("mpi/runtests.jl") end

end # module


text:
### Definition of Cluster Trees
# This was mostly copied over from the AbstractTrees example, credits are due
#
# Copied from AbstractTrees.jl Jan. 2021

if !isdefined(@__MODULE__, :BinaryNode)
  include("binarytree.jl")
end

const ClusterTree = BinaryNode{UnitRange{Int}}

## Functionality for cluster trees
# Create a simple cluster tree based on
bisection_cluster(n::Int, opts::HssOptions=HssOptions(); args...) = bisection_cluster(1:n, opts; args...)
function bisection_cluster(range::UnitRange{Int}, opts::HssOptions=HssOptions(); args...)
  opts = copy(opts; args...)
  chkopts!(opts)
  length(range) ≤ 0 && throw(ArgumentError("Index range must be larger or equal to 0"))
  _bisection_cluster(range, opts.leafsize)
end
function bisection_cluster(n::Tuple{Int,Int}, opts::HssOptions=HssOptions(); args...)
  opts = copy(opts; args...)
  chkopts!(opts)
  1 ≤ n[1] ≤ n[2] || throw(ArgumentError("Indices must be ordered and bigger than 1"))
  ClusterTree(1:n[2], _bisection_cluster(1:n[1], opts.leafsize), _bisection_cluster(n[1]+1:n[2], opts.leafsize))
end
function _bisection_cluster(range::UnitRange{Int}, leafsize::Int)
  node = ClusterTree(range)
  if length(range) > leafsize
    n = convert(Int, ceil(size(range,1)/2))
    node.left = _bisection_cluster(range[1:n], leafsize)
    node.right = _bisection_cluster(range[n+1:end], leafsize)
  end
  return node
end


text:
using ModelingStructs
using ForwardDiff
using Test

MS = ModelingStructs

include("test_utils.jl")
include("test_MStruct.jl")

max_stars_repo_name: jonniedie/NamedViewVectors


text:
abstract type JuliaConTalkType end
struct LightningTalk <: JuliaConTalkType end
struct SponsorTalk <: JuliaConTalkType end
struct Talk <: JuliaConTalkType end
struct Keynote <: JuliaConTalkType end
struct BoF <: JuliaConTalkType end
struct Minisymposium <: JuliaConTalkType end
struct Workshop <: JuliaConTalkType end
struct Experience <: JuliaConTalkType end
struct VirtualPoster <: JuliaConTalkType end

function json2df(conf)
    df = DataFrame(
        start = ZonedDateTime[],
        duration = CompoundPeriod[],
        title = String[],
        speaker = Vector{String}[],
        type = JuliaConTalkType[],
        url = String[],
        track = String[],
    )

    for day in conf["days"]
        date = Date(day["date"])
        for (track, talks) in day["rooms"]
            for talk in talks
                # parse duration
                tmp = split(talk["duration"], ':')
                dur = Hour(tmp[1]) + Minute(tmp[2])

                d = Dict(
                    :start => ZonedDateTime(DateTime(date, Time(talk["start"])), JULIACON_TIMEZONE),
                    :duration => dur,
                    :title => talk["title"],
                    :speaker => String[p["public_name"] for p in talk["persons"]],
                    :type => talktype_from_str(talk["type"]),
                    :url => talk["url"],
                    :track => track,
                )
                push!(df, d)
            end
        end
    end

    return df
end

function talktype_from_str(str)
    if str == "Talk"
        return Talk()
    elseif str == "Lightning talk"
        return LightningTalk()
    elseif str == "Sponsor Talk"
        return SponsorTalk()
    elseif str == "Keynote"
        return Keynote()
    elseif str == "Birds of Feather" || str == "BoF (45 mins)"
        return BoF()
    elseif str == "Minisymposium"
        return Minisymposium()
    elseif contains(str, "Workshop")
        return Workshop()
    elseif contains(str, "Experience")
        return Experience()
    elseif contains(str, "Virtual Poster")
        return VirtualPoster()
    else
        error("Unknown JuliaCon talk type \"$str\".")
    end
end

string(x::LightningTalk) = "Lightning Talk"
string(x::SponsorTalk) = "Sponsor Talk"
string(x::Talk) = "Talk"
string(x::Keynote) = "Keynote"
string(x::BoF) = "Birds of Feather"
string(x::Minisymposium) = "Minisymposium"
string(x::Workshop) = "Workshop"
string(x::Experience) = "Experience"
string(x::VirtualPoster) = "Virtual Poster"

abbrev(::Type{LightningTalk}) = "L"
abbrev(::Type{SponsorTalk}) = "S"
abbrev(::Type{Talk}) = "T"
abbrev(::Type{Keynote}) = "K"
abbrev(::Type{BoF}) = "BoF"
abbrev(::Type{Minisymposium}) = "M"
abbrev(::Type{Workshop}) = "W"
abbrev(::Type{Experience}) = "E"
abbrev(::Type{VirtualPoster}) = "P"

abbrev(x::JuliaConTalkType) = abbrev(typeof(x))

is_schedule_json_available() = isfile(joinpath(CACHE_DIR, "schedule.json"))

"""
Get the conference schedule as a DataFrame.
On first call, the schedule is downloaded from Pretalx and cached for further usage.
"""
function get_conference_schedule()
    isassigned(jcon) || update_schedule()
    return jcon[]
end

"""
    update_schedule(; verbose=false, ignore_timeout=false)

Explicitly trigger a schedule update according to the specified CACHE_MODE.
"""
function update_schedule(; verbose=false, notimeout=false)
    local file
    to = TimerOutput()
    verbose && @info "Cache mode: $CACHE_MODE"
    usecache = CACHE_MODE != :NEVER
    download_dir = usecache ? CACHE_DIR : mktempdir()

    if CACHE_MODE != :ALWAYS
        verbose && @info "Downloading $(default_json_url()) to $download_dir"
        if usecache && !isdir(CACHE_DIR)
            verbose && @info "Cache directory $CACHE_DIR created."
            mkpath(CACHE_DIR)
        end

        timeout =
            (usecache && is_schedule_json_available()) ? (!notimeout ? TIMEOUT : Inf) : Inf
        verbose && @info "Timeout set to $timeout seconds."

        try
            @timeit to "download" file = download(
                default_json_url(), joinpath(download_dir, "schedule.json.tmp"); timeout
            )
            file = mv(
                joinpath(download_dir, "schedule.json.tmp"),
                joinpath(download_dir, "schedule.json");
                force=true,
            )
        catch err
            if usecache
                @warn "Download failed or timed out. Falling back to cached schedule (might be stale). " *
                      "You can try forcing matters with JuliaCon.update_schedule(notimeout=true) or " *
                      "skipping the update altogether via JuliaCon.set_cachemode(:ALWAYS)."
                file = joinpath(CACHE_DIR, "schedule.json")
            else
                error(
                    "Download failed. Not using the cache due to CACHE_MODE = $CACHE_MODE."
                )
            end
        end
    else
        verbose && @info "Loading cached schedule.json"
        is_schedule_json_available() || error(
            "Can't find cached schedule.json. Not downloading due to CACHE_MODE = $CACHE_MODE.",
        )
        file = joinpath(CACHE_DIR, "schedule.json")
    end

    @timeit to "parse2json" data = JSON.parsefile(file)
    @timeit to "json2df" jcon[] = json2df(data["schedule"]["conference"])

    verbose && @info string("Timings:\n", to)
    return nothing
end

function get_running_talks(; now=default_now())
    jcon = get_conference_schedule()

    running_talks = filter(jcon; view=true) do talk
        start_time = talk.start
        end_time = start_time + talk.duration
        return start_time <= astimezone(now, JULIACON_TIMEZONE) < end_time
    end
    return running_talks
end

function _print_running_talks(running_talks; now=default_now())
    nrow(running_talks) > 0 || return nothing
    # println()
    # println(Dates.format(default_now(), "HH:MM dd-mm-YYYY"))
    for talk in eachrow(running_talks)
        println()
        printstyled(talk.track; bold=true, color=_track2color(talk.track))
        println()
        println("\t", talk.title, " (", string(talk.type), ")")
        println("\t", "├─ ", _speakers2str(talk.speaker))
        println("\t", "└─ ", talk.url)
    end
    println("\n")
    println("(Full schedule: https://pretalx.com/juliacon2021/schedule)")
    return nothing
end

function _track2color(track::String)
    if track == "Red"
        return :red
    elseif track == "Green"
        return :green
    elseif track == "Purple"
        return :magenta
    elseif track == "Blue"
        return :blue
    else
        return :default
    end
end

function now(::Val{:text}; now)
    running_talks = get_running_talks(; now=now)
    str = ""
    if !isnothing(running_talks)
        for talk in eachrow(running_talks)
            str *= """
            $(talk.track)
            \t$(talk.title) ($(string(talk.type)))
            \t├─ $(JuliaCon._speakers2str(talk.speaker))
            \t└─ $(talk.url)
            """
        end
    end
    str *= "\n(Full schedule: https://pretalx.com/juliacon2021/schedule)"
    return str
end

function now(::Val{:terminal}; now)
    running_talks = get_running_talks(; now=now)
    _print_running_talks(running_talks; now=now)
    return nothing
end

# A dispatcher for the `now` methods. Default to terminal output.
now(; now=default_now(), output=:terminal) = JuliaCon.now(Val(output); now=now)

function _speakers2str(speaker::Vector{String})
    if length(speaker) <= 3
        return join(speaker, ", ")
    else
        return string(join(speaker[1:3], ", "), " et al.")
    end
end

function _get_today_tables(;
    now=default_now(), track=nothing, terminal_links=TERMINAL_LINKS, highlighting=true, text_highlighting=false
)
    jcon = get_conference_schedule()

    today_start_utc = ZonedDateTime(DateTime(Date(now), Time("00:00")), JULIACON_TIMEZONE)
    today_end_utc = ZonedDateTime(DateTime(Date(now) + Day(1), Time("00:00")), JULIACON_TIMEZONE)

    talks_today = filter(jcon; view=true) do talk
        # talk is in the requested track (default: any)
        if !isnothing(track) && talk.track != track
            return false
        end

        # talk starts "today" (local time)
        return today_start_utc <= talk.start < today_end_utc
    end

    # no talks today -> exit
    nrow(talks_today) > 0 || return (nothing, nothing, nothing)

    # create talk tables
    tracks = String[]
    tables = Matrix{Union{String,URLTextCell}}[]
    highlighters = Union{Nothing,Highlighter}[]

    # for each track
    for track_grp in groupby(talks_today, :track)
        # build talk-data matrix
        data = Matrix{Union{String,URLTextCell}}(undef, nrow(track_grp), 4)
        for (i, talk) in enumerate(eachrow(track_grp))
            data[i, 1] = Dates.format(astimezone(talk.start, timezone(now)), "HH:MM")
            data[i, 2] = terminal_links ? URLTextCell(talk.title, talk.url) : talk.title
            data[i, 3] = JuliaCon.abbrev(talk.type)
            data[i, 4] = JuliaCon._speakers2str(talk.speaker)
        end

        h_running = Highlighter((data, m, n) -> false, crayon"yellow")
        if highlighting
            for (i, talk) in enumerate(eachrow(track_grp))
                start_time = talk.start
                end_time = talk.start + talk.duration
                if start_time <= astimezone(now, JULIACON_TIMEZONE) < end_time
                    if text_highlighting
                        if data[i, 2] isa URLTextCell
                            data[i, 2].x = string("> ", data[i, 2].x)
                        else
                            data[i, 2] = string("> ", data[i, 2])
                        end
                    end

                    h_running = Highlighter((data, m, n) -> m == i, crayon"yellow")
                end
            end
        end

        push!(tracks, first(track_grp).track)
        push!(tables, data)
        push!(highlighters, h_running)
    end

    @assert length(tables) == length(highlighters) == length(tracks)
    return (tracks, tables, highlighters)
end

# A dispatcher for the `today` methods. Defaults to terminal output.
function today(;
    now=default_now(),
    track=nothing,
    terminal_links=TERMINAL_LINKS,
    output=:terminal, # can take the :text value to output a Vector{String}
    highlighting=true
)
    return today(Val(output); now, track, terminal_links, highlighting)
end

function today(::Val{:terminal}; now, track, terminal_links, highlighting=true)
    tracks, tables, highlighters = _get_today_tables(; now, track, terminal_links, highlighting)
    isnothing(tables) && return nothing

    header = (["Time", "Title", "Type", "Speaker"],)
    header_crayon = crayon"dark_gray bold"
    border_crayon = crayon"dark_gray"
    h_times = Highlighter((data, i, j) -> j == 1, crayon"white bold")

    println()
    println(Dates.format(TimeZones.Date(now), "E d U Y"))

    for j in eachindex(tracks)
        track = tracks[j]
        data = tables[j]
        h_running = highlighters[j]
        println()
        pretty_table(
            data;
            title=track,
            title_crayon=Crayon(; foreground=_track2color(track), bold=true),
            header=header,
            header_crayon=header_crayon,
            border_crayon=border_crayon,
            highlighters=(h_times, h_running),
            tf=tf_unicode_rounded,
            alignment=[:c, :l, :c, :l],
        )
    end

    println()
    if highlighting
        printstyled("Currently running talks are highlighted in ")
        printstyled("yellow"; color=:yellow)
        printstyled(".")
        println()
        println()
    end
    print(abbrev(Talk), " = Talk, ")
    print(abbrev(LightningTalk), " = Lightning Talk, ")
    print(abbrev(SponsorTalk), " = Sponsor Talk, ")
    println(abbrev(Keynote), " = Keynote, ")
    print(abbrev(Workshop), " = Workshop, ")
    print(abbrev(Minisymposium), " = Minisymposium, ")
    println(abbrev(BoF), " = Birds of Feather, ")
    print(abbrev(Experience), " = Experience, ")
    println(abbrev(VirtualPoster), " = Virtual Poster")
    println()
    println("Check out https://pretalx.com/juliacon2021/schedule for more information.")
    return nothing
end

function today(::Val{:text}; now, track, terminal_links, highlighting=true)
    tracks, tables, highlighters = _get_today_tables(; now, track, terminal_links, highlighting, text_highlighting=highlighting)
    isnothing(tables) && return nothing

    header = (["Time", "Title", "Type", "Speaker"],)
    header_crayon = crayon"dark_gray bold"
    border_crayon = crayon"dark_gray"
    h_times = Highlighter((data, i, j) -> j == 1, crayon"white bold")

    strings = Vector{String}()
    push!(strings, string(Dates.format(TimeZones.Date(now), "E d U Y")))
    for j in eachindex(tracks)
        track = tracks[j]
        data = tables[j]
        h_running = highlighters[j]
        str = pretty_table(
            String,
            data;
            title=track,
            title_crayon=Crayon(; foreground=_track2color(track), bold=true),
            header=header,
            header_crayon=header_crayon,
            border_crayon=border_crayon,
            highlighters=(h_times, h_running),
            tf=tf_unicode_rounded,
            alignment=[:c, :l, :c, :l],
        )
        push!(strings, str)
    end

    legend = if highlighting
        """
        Currently running talks are prefixed by a '>'.

        """
    else
        ""
    end

    legend *= """
    $(JuliaCon.abbrev(JuliaCon.Talk)) = Talk, $(JuliaCon.abbrev(JuliaCon.LightningTalk)) = Lightning Talk, $(JuliaCon.abbrev(JuliaCon.SponsorTalk)) = Sponsor Talk, $(JuliaCon.abbrev(JuliaCon.Keynote)) = Keynote,
    $(JuliaCon.abbrev(JuliaCon.Workshop)) = Workshop, $(JuliaCon.abbrev(JuliaCon.Minisymposium)) = Minisymposium, $(JuliaCon.abbrev(JuliaCon.BoF)) = Birds of Feather,
    $(JuliaCon.abbrev(JuliaCon.Experience)) = Experience, $(JuliaCon.abbrev(JuliaCon.VirtualPoster)) = Virtual Poster

    Check out https://pretalx.com/juliacon2021/schedule for more information.
    """
    push!(strings, legend)
    return strings
end

function tomorrow(;
    now=default_now(),
    track=nothing,
    terminal_links=TERMINAL_LINKS,
    output=:terminal, # can take the :text value to output a Vector{String}
)
    return today(Val(output); now = now + Dates.Day(1), track, terminal_links, highlighting = false)
end


text:
import DataFrames: AbstractDataFrame

using DataFrames: rename!

export cleannames!, cleanname, renamedups!

"""
    cleannames!(df::DataFrame)

Uses R's `janitor::clean_names` to clean the names
"""
const ALLOWED_CHARS = vcat(vcat(vcat(Char.(-32+97:-32+97+25), Char.(97:97+25)), '_'), Char.(48:57))

renamedups!(n::AbstractVector{Symbol}) = begin
    # are the uniques?
    d = Dict{Symbol, Bool}()
    for (i, n1) in enumerate(n)
        if haskey(d, n1)
            n[i] = Symbol(string(n[i])*"_1")
            d[n[i]] = true
        else
            d[n1] = true
        end
    end
    n
end

cleanname(s) = begin
    ss = string(s)
    res = join([c in ALLOWED_CHARS ? c : '_' for c in ss])

    if res[1] in vcat(Char.(48:57))
        res = "x" * res
    end
    Symbol(res)
end

function cleannames!(df::AbstractDataFrame)
    n = names(df)
    cn = cleanname.(n)
    cn = renamedups!(cn)

    for p in Pair.(n, cn)
        rename!(df, p)
    end
    df
end

max_stars_repo_name: xiaodaigh/DataConvenience

max_issues_repo_name: xiaodaigh/DataConvenience

max_forks_repo_name: xiaodaigh/DataConvenience


text:
module BenchKNNGraphs

using NearestNeighborDescent.KNNGraphs
using LightGraphs
using Distances
using BenchmarkTools
using Random

#function graph_has_edges(graph, _edges)
#    for e in _edges
#        has_edge(graph, e)
#    end
#end

suite = BenchmarkGroup()

knn_graph_types = [HeapKNNGraph, LockHeapKNNGraph]
num_points = [500]
dims = [50]
num_neighbs = [15]
metric = Euclidean

for graph in knn_graph_types
    for np in num_points
        for d in dims
            Random.seed!(0)
            data = [2 .* rand(d) .- 1 for _ in 1:np] # uniform dist in [-1, 1]
            for k in num_neighbs
                if k ≥ np
                    continue
                end
                prepath = ["N="*string(np), "D="*string(d), "K="*string(k), string(graph)]
                postpath = [string(metric)]
                suite[vcat(prepath, ["construction"], postpath, )] =
                    @benchmarkable $(graph)($(data), $k, $(metric()))
            end
        end
    end
end

end # module

BenchKNNGraphs.suite


text:
import FFTW
import JSON
import ImageMorphology
import LightGraphs

using DataFrames
using SimpleWeightedGraphs
using SparseArrays
using StatsBase: countmap

using LightGraphs: src, dst

function grid_borders_per_label(grid_labels::Matrix{<:Unsigned})
    d_cols = [-1 0 1 0]
    d_rows = [0 1 0 -1]
    borders_per_label = [Array{UInt32, 1}[] for i in 1:maximum(grid_labels)]

    for row in 1:size(grid_labels, 1)
        for col in 1:size(grid_labels, 2)
            cur_label = grid_labels[row, col];
            if cur_label == 0
                continue
            end

            for (d_row, d_col) in zip(d_rows, d_cols)
                n_row = row + d_row
                n_col = col + d_col

                if (n_row < 1) || (n_col < 1) || (n_row > size(grid_labels, 1)) || (n_col > size(grid_labels, 2)) ||
                        (grid_labels[n_row, n_col] != cur_label)
                    push!(borders_per_label[cur_label], [col, row])
                    break
                end
            end
        end
    end

    return borders_per_label
end

function border_mst(border_points::Array{<:Vector{<:Real},1}; min_edge_length::Float64=0.1, max_edge_length::Float64=1.5)
    leafsize = min(size(border_points, 1), 10) # Some performance feature?
    tree = KDTree(Matrix{Float64}(hcat(border_points...)), leafsize=leafsize);
    src_verts, dst_verts, weights = Int[], Int[], Float64[]
    edges = Set{Tuple{Int, Int}}()

    n_neighbs = min(length(border_points), 5)
    for (i, p) in enumerate(border_points)
        inds, dists = knn(tree, p, n_neighbs, true) # 5 to get border for sure
        filt_mask = (dists .> min_edge_length) .& (dists .< max_edge_length)

        for (ind, dist) in zip(inds[filt_mask], dists[filt_mask])
            edge = sort([i, ind])
            edge = (edge...,)
            if !(edge in edges)
                push!(src_verts, edge[1])
                push!(dst_verts, edge[2])
                push!(weights, dist)
                push!(edges, edge)
            end
        end
    end

    adj_mtx = sparse(src_verts, dst_verts, weights, size(border_points, 1), size(border_points, 1));
    return LightGraphs.kruskal_mst(SimpleWeightedGraph(adj_mtx + adj_mtx'));
end

function find_longest_paths(edges::Array{<:LightGraphs.AbstractEdge, 1})::Array{Vector{Int}, 1}
    if length(edges) == 0
        return []
    end

    vert_counts = StatsBase.countmap(vcat(map(x -> [src(x), dst(x)], edges)...));
    n_verts = maximum(keys(vert_counts));
    leafs = collect(keys(vert_counts))[collect(values(vert_counts)) .== 1];

    adj_mtx = sparse(src.(edges), dst.(edges), weight.(edges), n_verts, n_verts)
    g_inc = SimpleWeightedGraph(adj_mtx + adj_mtx')

    conn_comps = LightGraphs.connected_components(g_inc);
    longest_paths = Array{Int, 1}[]
    for vert_inds in conn_comps
        if size(vert_inds, 1) < 3
            continue
        end

        comp_leafs = intersect(vert_inds, leafs)
        max_dist, max_source, max_target = -1, -1, -1
        paths = []
        for src_id in comp_leafs
            paths = LightGraphs.dijkstra_shortest_paths(g_inc, src_id);
            cur_target = comp_leafs[findmax(paths.dists[comp_leafs])[2]];
            if isinf(paths.dists[cur_target])
                @warn "Infinite distance for verteces $src_id and $cur_target"
                continue
            end

            if paths.dists[cur_target] > max_dist
                max_dist = paths.dists[cur_target]
                max_target = cur_target
                max_source = src_id
            end
        end

        if max_source < 0 | max_target < 0
            @warn "Source/target vertex id is 0"
            continue
        end

        cur_target = max_target
        path = Int[cur_target]
        paths = LightGraphs.dijkstra_shortest_paths(g_inc, max_source);
        if isinf(paths.dists[max_target])
            @warn "Infinite distance for verteces $src_id and $cur_target"
            continue
        end
        while cur_target != max_source
            cur_target = paths.parents[cur_target]
            push!(path, cur_target)
            if size(path, 1) > 2 * paths.dists[max_target]
                @warn "Can't build path"
                break
            end
        end
        push!(longest_paths, path)
    end

    return longest_paths
end

function order_points_to_polygon(vert_inds::Vector{Int}, border_coords::Matrix{T} where T <: Real; max_dev::T2 where T2 <: Real=10.0)
    polygon_inds = Vector{Int}[]
    while !isempty(vert_inds)
        ci = vert_inds[1]
        cur_ids = [ci]
        kdt = KDTree(Float64.(border_coords[:, vert_inds]));
        while true
            nn_ids = setdiff(vert_inds[inrange(kdt, Float64.(border_coords[:, ci]), max_dev)], cur_ids)
            if length(nn_ids) == 0
                break
            end

            nn_ids = nn_ids[sortperm(vec(sum((border_coords[:, ci] .- border_coords[:, nn_ids]) .^ 2, dims=1)))]

            ci = nn_ids[1]
            push!(cur_ids, ci)
        end

        push!(polygon_inds, cur_ids)
        vert_inds = setdiff(vert_inds, cur_ids)
    end

    return polygon_inds
end

function find_grid_point_labels_kde(pos_data::Matrix{Float64}, cell_labels::Vector{Int}, min_x::Union{Vector{Float64}, Tuple{Float64, Float64}},
        max_x::Union{Vector{Float64}, Tuple{Float64, Float64}}; grid_step::Float64, bandwidth::Float64, dens_threshold::Float64=1e-5, min_molecules_per_cell::Int=3,
        verbose::Bool=false)::Matrix{<:Unsigned}  where T <: Real
    coords_per_label = [pos_data[:, ids] for ids in split_ids(cell_labels .+ 1)];
    filt_mask = (size.(coords_per_label, 2) .>= min_molecules_per_cell)
    coords_per_label = coords_per_label[filt_mask];
    cell_labels = (0:maximum(cell_labels))[filt_mask]

    if length(coords_per_label) == 0
        return Matrix{Int}(undef,0,0)
    end

    FFTW.set_num_threads(1); # see https://github.com/JuliaStats/KernelDensity.jl/issues/80

    xw, yw = ceil.(Int, (max_x .- min_x) ./ grid_step)
    label_mat = nothing
    if length(coords_per_label) < (2 ^ 16) - 1
        label_mat = zeros(UInt16, yw, xw)
    else
        label_mat = zeros(UInt32, yw, xw)
    end
    dens_mat = zeros(Float16, yw, xw)

    p = verbose ? Progress(length(coords_per_label)) : nothing
    for (ci, lab) in zip(1:length(coords_per_label), cell_labels)
        c_bandwidth = (ci == 1) ? bandwidth / 1.5 : bandwidth
        coords = coords_per_label[ci]
        sxi = max(floor(Int, (minimum(coords[1,:]) - min_x[1] - 3 * c_bandwidth) / grid_step), 1);
        ex = maximum(coords[1,:]) + 3 * c_bandwidth;

        syi = max(floor(Int, (minimum(coords[2,:]) - min_x[2] - 3 * c_bandwidth) / grid_step), 1);
        ey = maximum(coords[2,:]) + 3 * c_bandwidth;

        cxs = (sxi * grid_step + min_x[1]):grid_step:ex;
        cys = (syi * grid_step + min_x[2]):grid_step:ey;

        dens = kde((coords[2,:], coords[1,:]), (cys, cxs), bandwidth=(c_bandwidth, c_bandwidth)).density

        for dyi in 1:length(cys)
            yi = dyi + syi
            if yi > size(dens_mat, 1)
                break
            end

            for dxi in 1:length(cxs)
                xi = dxi + sxi
                if xi > size(dens_mat, 2)
                    break
                end
                d = dens[dyi, dxi] * size(coords, 2)
                if (d > dens_threshold) && (d > dens_mat[yi, xi])
                    dens_mat[yi, xi] = d
                    label_mat[yi, xi] = lab
                end
            end
        end

        if verbose
            next!(p)
        end
    end

    return ImageMorphology.closing(label_mat)
end

function extract_polygons_from_label_grid(grid_labels::Matrix{<:Unsigned}; min_border_length::Int=3, shape_method::Symbol=:path, max_dev::Float64=10.0,
        exclude_labels::Vector{Int}=Int[], offset::Vector{Float64}=[0.0, 0.0], grid_step::Float64=1.0)::Array{Matrix{Float64}, 1}
    borders_per_label = grid_borders_per_label(grid_labels);
    if !isempty(exclude_labels)
        borders_per_label = borders_per_label[setdiff(1:length(borders_per_label), exclude_labels)]
    end

    borders_per_label = borders_per_label[length.(borders_per_label) .>= min_border_length]

    paths = nothing
    edges_per_label = border_mst.(borders_per_label)
    if shape_method == :path
        paths = find_longest_paths.(edges_per_label);
    elseif shape_method == :order
        conn_comps = [LightGraphs.connected_components(LightGraphs.SimpleGraphFromIterator(LightGraphs.SimpleGraphs.SimpleEdge.(src.(edges), dst.(edges)))) for edges in edges_per_label]
        paths = [vcat(order_points_to_polygon.(cc, Ref(hcat(borders...)), max_dev=max_dev)...) for (borders, cc) in zip(borders_per_label, conn_comps)];
    else
        error("Unknown shape_method: $shape_method")
    end

    paths = [ps[length.(ps) .> min_border_length] for ps in paths]
    polygons = vcat([[borders[p] for p in cur_paths] for (borders, cur_paths) in zip(borders_per_label, paths)]...);
    polygons = [copy((hcat(coords...) .* grid_step .+ offset)') for coords in polygons]
    return [vcat(cp, cp[1,:]') for cp in polygons]
end

boundary_polygons(bm_data::BmmData; kwargs...) = boundary_polygons(bm_data.x, bm_data.assignment; kwargs...)
boundary_polygons(spatial_df::DataFrame, args...; kwargs...) =
    boundary_polygons(position_data(spatial_df), args...; kwargs...)

function boundary_polygons(pos_data::Matrix{Float64}, cell_labels::Vector{Int}; min_x::Union{Array{Float64}, Nothing}=nothing, max_x::Union{Array{Float64}, Nothing}=nothing,
                           grid_step::Float64=5.0, min_border_length::Int=3, shape_method::Symbol=:path, max_dev::Float64=10.0,
                           bandwidth::Float64=(grid_step / 2), exclude_labels::Vector{Int}=Int[], kwargs...)::Array{Matrix{Float64}, 1}
    if min_x === nothing
        min_x = vec(mapslices(minimum, pos_data, dims=2))
    end

    if max_x === nothing
        max_x = vec(mapslices(maximum, pos_data, dims=2))
    end

    grid_labels_plane = find_grid_point_labels_kde(pos_data, cell_labels, min_x, max_x; grid_step=grid_step, bandwidth=bandwidth, kwargs...)
    GC.gc()

    if length(grid_labels_plane) == 0
        return Matrix{Float64}[]
    end

    return extract_polygons_from_label_grid(grid_labels_plane; min_border_length=min_border_length, shape_method=shape_method, max_dev=max_dev,
        exclude_labels=exclude_labels, offset=min_x, grid_step=grid_step)
end

function save_polygons_to_geojson(polygons::Array{Array{T,2},1} where T<:Number, path::Union{String, Nothing}=nothing)
    geoms = [Dict("type" => "Polygon", "coordinates" => [collect.(eachrow(p))]) for p in polygons]
    geo_json_dict = Dict("type" => "GeometryCollection", "geometries" => geoms);
    if path == nothing
        return geo_json_dict
    end

    open(path, "w") do f
        print(f, JSON.json(geo_json_dict))
    end
end

max_stars_repo_name: YinuoJin/Baysor

max_issues_repo_name: YinuoJin/Baysor

max_forks_repo_name: YinuoJin/Baysor


text:
# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder, Pkg

name = "boostpython"
version = v"1.76.0"

# Collection of sources required to complete build
sources = [
    GitSource("https://github.com/boostorg/python.git", "68fa9dccdec75d59ac28d3ff98d70c659e12326d")
]

# Bash recipe for building across all platforms
script = raw"""
cd $WORKSPACE/srcdir/python
c++ -fPIC -o $libdir/libboost_python38.$dlext -O3 -shared -L$libdir -lpython3.8 -I$includedir -I$includedir/python3.8 $(find src ! -path src/numpy/\* -name \*.cpp)
cp -r include/* $includedir
install_license LICENSE_1_0.txt
"""

# No Windows support in Python_jll currently
platforms = filter(!Sys.iswindows, supported_platforms())
platforms = expand_cxxstring_abis(platforms)

# The products that we will ensure are always built
products = [
    LibraryProduct("libboost_python38", :libboost_python)
]

# Dependencies that must be installed before this package can be built
dependencies = [
    Dependency(PackageSpec(name="boost_jll", uuid="28df3c45-c428-5900-9ff8-a3135698ca75"); compat="=1.76.0")
    Dependency(PackageSpec(name="Python_jll"), v"3.8.1"; compat="~3.8")
]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; julia_compat="1.6")

max_stars_repo_name: sharanry/Yggdrasil

max_issues_repo_name: sharanry/Yggdrasil

max_forks_repo_name: sharanry/Yggdrasil