text:
const Maybe{T} = Union{T, Nothing}

abstract type Interpretation end
struct Nonstandard <: Interpretation end
struct Standard <: Interpretation end
struct Replayed <: Interpretation end
struct Conditioned <: Interpretation end
struct Blocked <: Interpretation end
struct Deterministic <: Interpretation end
struct Proposed <: Interpretation end
struct Empirical <: Interpretation end
struct Input <: Interpretation end

const NONSTANDARD = Nonstandard()
const STANDARD = Standard()
const REPLAYED = Replayed()
const BLOCKED = Blocked()
const CONDITIONED = Conditioned()
const DETERMINISTIC = Deterministic()
const PROPOSED = Proposed()
const EMPIRICAL = Empirical()
const INPUT = Input()

abstract type Node end

@doc raw"""
    mutable struct ParametricNode{A, D, T} <: Node
        address :: A
        dist :: D
        value :: Maybe{T}
        logprob :: Float64
        logprob_sum :: Float64
        observed :: Bool
        pa :: Array{Node, 1}
        ch :: Array{Node, 1}
        interpretation :: Union{Interpretation, Vector{Interpretation}}
        last_interpretation :: Union{Interpretation, Vector{Interpretation}}
    end

A `Node` that can be used with arbitrary code for which `rand` and `Distributionss.logpdf` are defined.
"""
mutable struct ParametricNode{A, D, T} <: Node
    address :: A
    dist :: D
    value :: Maybe{T}
    logprob :: Float64
    logprob_sum :: Float64
    observed :: Bool
    pa :: Array{Node, 1}
    ch :: Array{Node, 1}
    interpretation :: Union{Interpretation, Vector{Interpretation}}
    last_interpretation :: Union{Interpretation, Vector{Interpretation}}
end

@doc raw"""
    mutable struct SampleableNode{A, T} <: Node
        address :: A
        dist :: Sampleable
        value :: Maybe{T}
        logprob :: Float64
        logprob_sum :: Float64
        observed :: Bool
        pa :: Array{Node, 1}
        ch :: Array{Node, 1}
        interpretation :: Union{Interpretation, Vector{Interpretation}}
        last_interpretation :: Union{Interpretation, Vector{Interpretation}}
    end

A `Node` that is restricted to be used with any `Sampleable` from `Distributions.jl`.
"""
mutable struct SampleableNode{A, T} <: Node
    address :: A
    dist :: Sampleable
    value :: Maybe{T}
    logprob :: Float64
    logprob_sum :: Float64
    observed :: Bool
    pa :: Array{Node, 1}
    ch :: Array{Node, 1}
    interpretation :: Union{Interpretation, Vector{Interpretation}}
    last_interpretation :: Union{Interpretation, Vector{Interpretation}}
end

@doc raw"""
    function node(T :: DataType, address :: A, dist :: D, is_obs :: Bool, i :: Interpretation) where {A, D}

Outer constructor for `Node` where no data is passed during construction.
"""
function node(T :: DataType, address :: A, dist :: D, is_obs :: Bool, i :: Interpretation) where {A, D}
    ParametricNode{A, D, T}(address, dist, nothing, 0.0, 0.0, is_obs, Array{Node, 1}(), Array{Node, 1}(), i, i)
end

@doc raw"""
    function node(T :: DataType, address :: A, dist :: D, is_obs :: Bool, i :: Interpretation) where {A, D}

Outer constructor for `Node` where no data is passed during construction.
"""
function node(T :: DataType, address :: A, dist :: D, is_obs :: Bool, i :: Interpretation) where {A, D <: Sampleable}
    SampleableNode{A, T}(address, dist, nothing, 0.0, 0.0, is_obs, Array{Node, 1}(), Array{Node, 1}(), i, i)
end

function setlp!(n::Node, lp)
    n.logprob = lp
    n.logprob_sum = lp
end

# default fallback to distributions method
logprob(d, v) = Distributions.logpdf(d, v)

@doc raw"""
    function node(value, address :: A, dist :: D, is_obs :: Bool, i :: Interpretation) where {A, D}

Outer constructor for `Node` where data is passed during construction. Data type is inferred from the passed data.
"""
function node(value, address :: A, dist :: D, is_obs :: Bool, i :: Interpretation) where {A, D}
    T = typeof(value)
    lp = logprob(dist, value)
    ParametricNode{A, D, T}(address, dist, value, lp, lp, is_obs, Array{Node, 1}(), Array{Node, 1}(), i, i)
end

@doc raw"""
    function node(value, address :: A, dist :: D, is_obs :: Bool, i :: Interpretation) where {A, D}

Outer constructor for `Node` where data is passed during construction. Data type is inferred from the passed data.
"""
function node(value, address :: A, dist :: D, is_obs :: Bool, i :: Interpretation) where {A, D <: Sampleable}
    T = typeof(value)
    lp = logprob(dist, value)
    SampleableNode{A, T}(address, dist, value, lp, lp, is_obs, Array{Node, 1}(), Array{Node, 1}(), i, i)
end

# pass through input into program without scoring it
logprob(::Input, value) = 0.0

@doc raw"""
    abstract type Trace end

Base type for all traces. `Trace`s support the following `Base` methods: `setindex!`, `getindex`,
`keys`, `values`, and `length`.
"""
abstract type Trace end

@doc raw"""
    mutable struct UntypedTrace
        trace :: OrderedDict{Any, Node}
        logprob_sum :: Float64
    end

Trace that can hold nodes with all address and value types.
"""
mutable struct UntypedTrace <: Trace
    trace :: OrderedDict{Any, Node}
    logprob_sum :: Float64
end

@doc raw"""
    mutable struct TypedTrace{A, T} <: Trace
        trace :: OrderedDict{A, SampleableNode{A, T}}
        logprob_sum :: Float64
    end

Trace that can hold nodes of the specific address (`A`) and value (`T`) types.
"""
mutable struct TypedTrace{A, T} <: Trace
    trace :: OrderedDict{A, SampleableNode{A, T}}
    logprob_sum :: Float64
end

@doc raw"""
    trace()

This is the recommended way to construct a new trace.
"""
trace() = UntypedTrace(OrderedDict{Any, Node}(), 0.0)

@doc raw"""
    trace(A, T)

This is the recommended way to construct a new typed trace.
`A` is the address type, `T` is the value type.
"""
trace(A, T) = TypedTrace(OrderedDict{A, SampleableNode{A, T}}(), 0.0)

Base.setindex!(t :: Trace, k, v) = setindex!(t.trace, k, v)
Base.getindex(t :: Trace, k) = t.trace[k]
Base.values(t :: Trace) = values(t.trace)
Base.keys(t :: Trace) = keys(t.trace)
Base.length(t :: Trace) = length(t.trace)

@doc raw"""
    function interpret_latent!(t :: Trace, i :: Interpretation)

Changes the interpretation of all latent nodes in `t` to have `interpretation == i`
"""
function interpret_latent!(t :: Trace, i :: Interpretation)
    for a in keys(t)
        if !t[a].observed && !(t[a].interpretation == CONDITIONED)
            t[a].interpretation = i
        end
    end
end

@doc raw"""
    function logprob(t :: Trace)

Computes and returns the joint log probability of the trace:

```math
\log p(t) = \sum_{a \in \text{keys}(t)}\log p(t[a])
```
"""
function logprob(t :: Trace)
    l = 0.0
    for v in values(t)
        l += v.logprob_sum
    end
    l
end

@doc raw"""
    function logprob!(t :: Trace)

Computes the joint log probability to the trace and assigns it to `t.logprob_sum`.
"""
function logprob!(t :: Trace)
    l = logprob(t)
    t.logprob_sum = l
end

@doc raw"""
    function loglikelihood(t :: Trace)

Computes and returns the log likelihood of the observed data under the model:

```math
\ell(t) =
\sum_{a:\ [a \in \text{keys}(t)] \wedge [\text{interpretation}(a) = \text{Standard}]}
\log p(t[a])
```
"""
function loglikelihood(t :: Trace)
    l = 0.0
    for v in values(t)
        if v.observed
            l += v.logprob_sum
        end
    end
    l
end

@doc raw"""
    function pa_from_trace(t :: Trace, pa)

Collects nodes in trace corresponding to an iterable of parent addresses `pa`.
"""
function pa_from_trace(t :: Trace, pa)
    this_pa = Array{Node, 1}()
    for that_address in pa
        append!(this_pa, (t[that_address],))
    end
    this_pa
end

@doc raw"""
    function connect_pa_ch!(t :: Trace, pa, a)

Connects parent and child nodes. Adds child nodes to parent's `ch` and
parent nodes to child's `pa`.
"""
function connect_pa_ch!(t :: Trace, pa, a)
    append!(t[a].pa, pa_from_trace(t, pa))
    for that_n in t[a].pa
        append!(that_n.ch, (t[a],))
    end
end

@doc raw"""
    function sample(t :: Trace, a, d, ii :: Array{Interpretation, 1}; pa = ())

Sequentially apply sample statements with interpretations as given in `ii`. This is
used to depth-first traverse the interpretation graph.
"""
function sample(t :: Trace, a, d, ii :: Vector{Interpretation}; pa = ())
    local s
    for i in ii
        s = sample(t, a, d, i; pa = pa)
    end
    s
end

@doc raw"""
    function sample(t :: Trace, a, d, i :: Nonstandard; pa = ())

Samples from distribution `d` into trace `t` at address `a`.

1. Samples a value from `d`
2. Creates a sample node
3. Adds the sample node to trace `t` at value `a`
4. Optionally adds nodes corresponding to the addresses in `pa` as parent nodes
5. Returns the sampled value
"""
function sample(t :: Trace, a, d, i :: Nonstandard; pa = ())
    s = rand(d)
    n = node(s, a, d, false, i)
    t[a] = n
    connect_pa_ch!(t, pa, a)
    s
end

@doc raw"""
    function plate(t::Trace, op::F, a, d, s::Int64, i::Nonstandard; pa = ()) where F<:Function

Plate over latent variables.
"""
function plate(t::Trace, op::F, a, d, s::Int64, i::Nonstandard; pa = ()) where F<:Function
    n = node(Vector{eltype(d)}, a, d, false, i)
    rvals = [rand(d) for _ in 1:s]
    lp = 0.0
    for r in rvals
        lp += logpdf(d, r)
    end
    setlp!(n, lp)
    n.value = rvals
    t[a] = n
    connect_pa_ch!(t, pa, a)
    rvals
end

@doc raw"""
    function sample(t :: Trace, a, d, i :: Replayed; pa = ())

Replays the sampled node through the trace.

1. If `a` is not in `t`'s address set, calls `sample(t, a, d, NONSTANDARD; pa = pa)`.
2. Creates a sample node that copies the value from the last node stored in the trace at address `a`.
3. Adds the sample node to trace `t` at value `a`
4. Optionally adds nodes corresponding to the addresses in `pa` as parent nodes
5. Resets the node's interpretation to the original interpretation
"""
function sample(t :: Trace, a, d, i :: Replayed; pa = ())
    if a in keys(t)
        last_i = t[a].last_interpretation
        n = node(t[a].value, a, d, false, i)
        t[a] = n
        connect_pa_ch!(t, pa, a)
        r = t[a].value
        t[a].interpretation = last_i
    else
        r = sample(t, a, d, NONSTANDARD; pa = pa)
    end
    r
end

@doc raw"""
    function plate(t::Trace, op::F, a, d, s::Int64, i::Replayed; pa = ()) where F<:Function

Plate over replayed variables. Note that this method assumes *and does not check* that the value
to be replayed `v` satisfies `length(v) == s`.
"""
function plate(t::Trace, op::F, a, d, s::Int64, i::Replayed; pa = ()) where F<:Function
    n = node(Vector{eltype(d)}, a, d, false, i)
    last_i = t[a].last_interpretation
    lp = 0.0
    for r in t[a].value
        lp += logpdf(d, r)
    end
    setlp!(n, lp)
    n.value = t[a].value
    t[a] = n
    connect_pa_ch!(t, pa, a)
    t[a].interpretation = last_i
    t[a].value
end

@doc raw"""
    function sample(t :: Trace, a, d, i :: Blocked; pa = ())

Samples from `d`, deletes the node stored at address `a` from trace `t`, and returns the
sampled value.
"""
function sample(t :: Trace, a, d, i :: Blocked; pa = ())
    s = rand(d)
    delete!(t.trace, a)
    s
end

@doc raw"""
    function plate(t::Trace, op::F, a, d, s::Int64, i::Blocked; pa = ()) where F<:Function

Plate over blocked variables.
"""
function plate(t::Trace, op::F, a, d, s::Int64, i::Blocked; pa = ()) where F<:Function
    n = node(Vector{eltype(d)}, a, d, false, i)
    rvals = [rand(d) for _ in 1:s]
    delete!(t.trace, a)
    rvals
end

@doc raw"""
    function sample(t :: Trace, a, d, params, i :: Blocked; pa = ())

Samples from `d` passing the optional arguments `params`, deletes the node stored at address `a`
from trace `t`, and returns the sampled value.
"""
function sample(t :: Trace, a, d, params, i :: Blocked; pa = ())
    s = rand(d, params...)
    delete!(t.trace, a)
    s
end

@doc raw"""
    function sample(t :: Trace, a, d, params, ii :: Array{Interpretation, 1}; pa = ())

Sequentially apply sample statements with interpretations as given in `ii`. This is
used to depth-first traverse the interpretation graph.
"""
function sample(t :: Trace, a, d, params, ii :: Array{Interpretation, 1}; pa = ())
    local s
    for i in ii
        s = sample(t, a, d, params, i; pa = pa)
    end
    s
end

@doc raw"""
    function sample(t :: Trace, a, d, params, i :: Replayed; pa = ())

Replays the sampled node through the trace.

1. If `a` is not in `t`'s address set, calls `sample(t, a, d, NONSTANDARD; pa = pa)`.
2. Creates a sample node that copies the value from the last node stored in the trace at address `a`.
3. Adds the sample node to trace `t` at value `a`
4. Optionally adds nodes corresponding to the addresses in `pa` as parent nodes
5. Resets the node's interpretation to the original interpretation
"""
function sample(t :: Trace, a, d, params, i :: Replayed; pa = ())
    sample(t, a, d, i; pa = pa)
end

@doc raw"""
    function sample(t :: Trace, a, d, params, i :: Nonstandard; pa = ())

Samples from distribution `d` into trace `t` at address `a`.

1. Samples a value from `d` passing the optional arguments `params`
2. Creates a sample node
3. Adds the sample node to trace `t` at value `a`
4. Optionally adds nodes corresponding to the addresses in `pa` as parent nodes
5. Returns the sampled value
"""
function sample(t :: Trace, a, d, params, i :: Nonstandard; pa = ())
    s = rand(d, params...)
    n = node(s, a, d, false, i)
    t[a] = n
    connect_pa_ch!(t, pa, a)
    s
end

@doc raw"""
    function sample(t :: Trace, a, d, s, i :: Standard; pa = ())

Scores an observed value `s` against the distribution `d`, storing the value in trace `t` at
address `a` and optionally adds nodes corresponding to the addresses in `pa` as parent nodes.
"""
function sample(t :: Trace, a, d, s, i :: Standard; pa = ())
    n = node(s, a, d, true, i)
    t[a] = n
    connect_pa_ch!(t, pa, a)
    s
end

@doc raw"""
    function plate(t::Trace, op::F, a, d, v::Vector{T}; pa = ()) where {T, F<:Function}

Plate over observed variables, i.e., a plated component of model likelihood. `v` is the
vector of observations, while `op` is likely `observe`.

Example usage: instead of

```
for (i, d) in enumerate(data)
    observe(t, "data $i", Normal(loc, scale), d)
end
```

we can write

```
plate(t, observe, "data", Normal(loc, scale), data)
```
"""
function plate(t::Trace, op::F, a, d, v::Vector{T}; pa = ()) where {T, F<:Function}
    n = node(typeof(v), a, d, true, STANDARD)
    lp = 0.0
    for r in v
        lp += logpdf(d, r)
    end
    setlp!(n, lp)
    n.value = v
    t[a] = n
    connect_pa_ch!(t, pa, a)
    v
end

@doc raw"""
    function plate(t::Trace, op::F, a, d, v::Vector{T}, params; pa = ()) where {T, F<:Function}

Plate over observed variables with different values but identical distribution, i.e.,
``p(x|z) = \prod_n p(x_n | z_n)``.
This is as opposed to `plate(t::Trace, op::F, a, d, v::Vector{T}; pa = ())`, which is equivalent to
``p(x|z) = \prod_n p(x_n | z)``.

`params` must have the same length as `v`. Each element of `params` corresponds to a vector of that
particular component of the `params`, i.e., ``z = (z_1, ..., z_D)`` where each ``z_d`` has length ``N``,
the number of observed datapoints, and ``D`` is the cardinality of the parameterization of the
distribution.

E.g., replace
```
locs = sample(t, "locs", MvNormal(D, 1.0))
for (i, (loc, d)) in enumerate(zip(locs, data))
    observe(t, "data $i", Normal(loc, 1.0), d)
end
```
with
```
locs = sample(t, "locs", MvNormal(D, 1.0))
plate(t, observe, "data", Normal, data, (locs, ones(D)))
```
"""
function plate(t::Trace, op::F, a, d, v::Vector{T}, params; pa = ()) where {T, F<:Function}
    # accumulate logprob from values with same distribution
    lp = 0.0
    for (datapoint, param_group) in zip(v, zip(params...))
        lp += logpdf(d(param_group...), datapoint)
    end
    n = node(typeof(v), a, d, true, STANDARD)
    setlp!(n, lp)
    n.value = v
    t[a] = n
    connect_pa_ch!(t, pa, a)
    v
end

function plate(t::Trace, op::F, a, d, v::Matrix{T}, params; pa = ()) where {T, F<:Function}
    # accumulate logprob from values with same distribution
    lp = 0.0
    for (i, param_group) in enumerate(zip(params...))
        lp += logpdf(d(param_group...), vec(v[:, i]))
    end
    n = node(typeof(v), a, d, true, STANDARD)
    setlp!(n, lp)
    n.value = v
    t[a] = n
    connect_pa_ch!(t, pa, a)
    v
end

@doc raw"""
    function sample(t :: Trace, a, d, i :: Union{Standard,Conditioned}; pa = ())

Scores an observed value against the distribution `d`, storing the value in trace `t` at
address `a` and optionally adds nodes corresponding to the addresses in `pa` as parent nodes.

This method is used by the `condition` effect. It will probably not be used by most
users.
"""
function sample(t :: Trace, a, d, i :: Union{Standard,Conditioned}; pa = ())
    n = node(t[a].value, a, d, true, i)
    t[a] = n
    connect_pa_ch!(t, pa, a)
    t[a].value
end

@doc raw"""
    function plate(t::Trace, op::F, a, d, s::Int64, i::Conditioned; pa = ()) where F<:Function

Plate over conditioned variables.
"""
function plate(t::Trace, op::F, a, d, s::Int64, i::Conditioned; pa = ()) where F<:Function
    n = node(Vector{eltype(d)}, a, d, false, i)
    lp = 0.0
    for r in t[a].value
        lp += logpdf(d, r)
    end
    setlp!(n, lp)
    n.value = t[a].value
    t[a] = n
    connect_pa_ch!(t, pa, a)
    t[a].value
end

@doc raw"""
    function sample(t :: Trace, a, f, v, i :: Deterministic; pa = ())

Creates a deterministic node mapping the tuple of data `v` through function `f`,
storing the value in trace `t` at address `a`.

1. Maps tuple of data `v` through function `f`, yielding `r = f(v...)`
2. Creates a deterministic node and stores it in `t` at address `a`
3. Optionally adds nodes corresponding to the addresses in `pa` as parent nodes
4. Returns `r`
"""
function sample(t :: Trace, a, f, v, i :: Deterministic; pa = ())
    # TODO: add better testing of this, just pushed two hotfixes from applied work
    r = f(v...)
    T = typeof(r)
    n = node(T, a, f, false, i)
    n.value = r
    t[a] = n
    connect_pa_ch!(t, pa, a)
    r
end

function sample(t :: Trace, a, f, v::String, i :: Deterministic; pa = ())
    r = f(v)
    T = typeof(r)
    n = node(T, a, f, false, i)
    n.value = r
    t[a] = n
    connect_pa_ch!(t, pa, a)
    r
end

function sample(t :: Trace, a, d, i :: Input)
    n = node(d, a, INPUT, true, i)
    t[a] = n
    connect_pa_ch!(t, (), a)
    d
end

@doc raw"""
    input(t :: Trace, a, d)

Track a model input.
"""
input(t :: Trace, a, d) = sample(t, a, d, INPUT)

@doc raw"""
    function transform(t :: Trace, a, f :: F, v; pa = ()) where F <: Function

Alias for `sample(t, a, f, v, DETERMINISTIC; pa = pa)`.
"""
function transform(t :: Trace, a, f :: F, v; pa = ()) where F <: Function
    sample(t, a, f, v, DETERMINISTIC; pa = pa)
end

@doc raw"""
    function sample(t :: Trace, a, d; pa = ())

If `a` is in the set of trace addresses, calls `sample` using `t[a]`'s interpretation.
Otherwise, calls `sample` using nonstandard interpretation.
"""
function sample(t :: Trace, a, d; pa = ())
    if a in keys(t)
        sample(t, a, d, t[a].interpretation; pa = pa)
    else
        sample(t, a, d, NONSTANDARD; pa = pa)
    end
end

@doc raw"""
    function sample(t :: Trace, a, d, params; pa = ())

If `a` is in the set of trace addresses, calls `sample` using `t[a]`'s interpretation.
Otherwise, calls `sample` using nonstandard interpretation.
"""
function sample(t :: Trace, a, d, params; pa = ())
    if a in keys(t)
        sample(t, a, d, params, t[a].interpretation; pa = pa)
    else
        sample(t, a, d, params, NONSTANDARD; pa = pa)
    end
end

@doc raw"""
    function observe(t :: Trace, a, d, s; pa = ())

If `d` is not `nothing` an alias for calling `sample` with standard interpretation.
Otherwise, an alias for calling `sample` with nonstandard interpretation.
"""
function observe(t :: Trace, a, d, s; pa = ())
    if s === nothing
        sample(t, a, d, NONSTANDARD; pa = pa)
    else
        sample(t, a, d, s, STANDARD; pa = pa)
    end
end

# maybe Proposed will have meaning later, stub out just in case
# e.g., right now new trace is made in mh, but is this always true?

sample(t :: Trace, a, d, i :: Proposed) = sample(t, a, d, NONSTANDARD)

@doc raw"""
    propose(t :: Trace, a, d)

Propose a value for the address `a` in trace `t` from the distribution `d`.
"""
propose(t :: Trace, a, d) = sample(t, a, d, PROPOSED)

@doc raw"""
    function plate(t::Trace, op::F, a, d, s::Int64; pa = ()) where F<:Function

Sample or observe a vector of random variables at a single site instead of multiple.
This can speed up inference since the number of sites in the model will no longer scale
with dataset size (though numerical value computation is still linear in dataset size).

Example usage: instead of

```
vals = [sample(t, "val $i", Geometric()) for i in 1:N]
```

we can write

```
vals = plate(t, sample, "val", Geometric(), N)
```

Mathematically, this is equivalent to the product ``p(z) = \prod_n p(z_n)``
and treating it as the single object ``p(z)`` instead of the ``N`` objects ``p(z_n)``.
"""
function plate(t::Trace, op::F, a, d, s::Int64; pa = ()) where F<:Function
    if a in keys(t)
        plate(t, op, a, d, s, t[a].interpretation; pa = pa)
    else
        plate(t, op, a, d, s, NONSTANDARD; pa = pa)
    end
end
plate(t::Trace, op::F, a, d, v::Vector{Nothing}; pa = ()) where F <: Function = plate(t, op, a, d, length(v); pa = pa)
export plate

function Base.show(io::IO, t::Trace)
    s = "\nTrace with $(length(t)) nodes:"
    for node in values(t)
        s *= "\n$(node.address) = ($(node.value), $(node.dist), $(node.logprob_sum), $(node.interpretation))"
    end
    print(io, s)
end

export Node, ParametricNode, SampleableNode, node
export Trace, UntypedTrace, TypedTrace, trace, logprob, logprob!, sample, observe, input, loglikelihood
export node_info, graph, transform
export Interpretation
export Nonstandard, Standard, Replayed, Conditioned, Deterministic, Input, Empirical
export NONSTANDARD, STANDARD, REPLAYED, CONDITIONED, DETERMINISTIC, INPUT, EMPIRICAL


text:
using DispatcherCache: node_hash, source_hash, arg_hash, dep_hash, __hash

@testset "Hashing" begin
    # Low level
    v = 1
    @test __hash(v) == string(hash(hash(typeof(v)) + hash(v)), base=16)

    # Generate some functions and ops
    foo(x, y) = x + y
    bar(x) = x
    another_foo(x, y) = begin
        # some comment
        x+y
    end
    yet_another_foo(x,y) = x + y + 1 - 1

    bar1 = @op bar(10)
    foo1 = @op foo(bar1, 10)
    foo2 = @op another_foo(1, 2)
    foo3 = @op yet_another_foo(1, 2)

    # Source
    @test source_hash(foo1) == source_hash(foo2)
    @test source_hash(foo1) != source_hash(foo3)

    # Arguments
    @test arg_hash(foo2) == arg_hash(foo3)
    @test arg_hash(foo1) == arg_hash(bar1)
    @test arg_hash(bar1) == __hash(hash(nothing) + hash(10) + hash(Int))

    # Dependencies
    k2h = Dict{Op, String}()
    hash_bar1, _ = node_hash(bar1, k2h)
    @test dep_hash(foo1, k2h) == __hash(__hash(nothing) * hash_bar1)

    # Entire node
    h_src = source_hash(foo1)
    h_arg = arg_hash(foo1)
    h_dep = dep_hash(foo1, k2h)
    @test node_hash(foo1, k2h) ==
        (__hash(join(h_src, h_arg, h_dep)),
         Dict("code" => h_src, "args" => h_arg, "deps" => h_dep))
end

ext: jl


text:
#!/usr/bin/env julia

module ResPlots

using CSV, DataFrames, Gadfly

export makeboxplots, makegroupedboxplots

function makegroupedboxplots()
    data = CSV.read("results.csv")
    set_default_plot_size(20cm, 20cm)
    p_all_d = plot(data, ygroup="img_shape", x="d_norm", y="FID", color="g_norm", Geom.subplot_grid(Geom.bar(position=:stack)))
    p_all_g = plot(data, ygroup="img_shape", x="d_norm", y="FID", color="g_norm", Geom.subplot_grid(Geom.bar(position=:stack)))
    vstack(p_all_d, p_all_g)
end

function makeboxplots()
    data = CSV.read("results.csv")
    set_default_plot_size(20cm, 20cm)
    p_all_d = stackedboxplot(data, :d_norm, :FID, :g_norm)
    p_all_g = stackedboxplot(data, :g_norm, :FID, :d_norm)
    p_64_d  = stackedboxplot(data[data[:img_shape] .== 64,  :], :d_norm, :FID, :g_norm)
    p_64_g  = stackedboxplot(data[data[:img_shape] .== 64,  :], :g_norm, :FID, :d_norm)
    p_128_d = stackedboxplot(data[data[:img_shape] .== 128, :], :d_norm, :FID, :g_norm)
    p_128_g = stackedboxplot(data[data[:img_shape] .== 128, :], :g_norm, :FID, :d_norm)
    pgrid = gridstack([p_all_d p_all_g; p_64_d p_64_g; p_128_d p_128_g])
    out = SVG("boxplot.svg", 20cm, 20cm)
    draw(out, pgrid)
    return pgrid

    # this is bugged
    #= p_all_stack = title(hstack(p_all_d, p_all_g), "all") =#
    #= p_64_stack  = title(hstack(p_64_d, p_64_g),   "64") =#
    #= p_128_stack = title(hstack(p_128_d, p_128_g), "128") =#
    #= vstack(p_all_stack, p_64_stack, p_128_stack) =#
end

function stackedboxplot(data, x, y, c)
    plot(data, x=x, y=y, color=c, Geom.bar(position=:stack))
end

end

max_stars_repo_name: janEbert/GANerator

max_issues_repo_name: janEbert/GANerator

max_forks_repo_name: janEbert/GANerator


text:
module _DispEn
export DispEn
using Clustering: kmeans, assignments
using Statistics: std, mean
using StatsFuns: normcdf
    """
        Dispx, RDE = DispEn(Sig)

    Returns the dispersion entropy (`Dispx`) and the reverse dispersion entropy
    (`RDE`) estimated from the data sequence (`Sig`) using the default parameters:
    embedding dimension = 2, time delay = 1, symbols = 3, logarithm = natural,
    data transform = normalised cumulative density function (ncdf)

        Dispx, RDE = DispEn(Sig::AbstractArray{T,1} where T<:Real; c::Int=3, m::Int=2, tau::Int=1, Typex::String="ncdf", Logx::Real=exp(1), Fluct::Bool=false, Norm::Bool=false, rho::Real=1)

    Returns the dispersion entropy (`Dispx`) and the reverse dispersion entropy (`RDE`)
    estimated from the data sequence (`Sig`) using the specified 'keyword' arguments:

    # Arguments:
    `m`     - Embedding Dimension, a positive integer\n
    `tau`   - Time Delay, a positive integer\n
    `c`     - Number of symbols, an integer > 1\n
    `Typex` - Type of data-to-symbolic sequence transform, one of the following:
              {`"linear", "kmeans" ,"ncdf", "finesort", "equal"`}\n
              See the EntropyHub guide for more info on these transforms.\n
    `Logx`  - Logarithm base, a positive scalar\n
    `Fluct` - When Fluct == true, DispEn returns the fluctuation-based
              Dispersion entropy.   [default: false]\n
    `Norm`  - Normalisation of Dispx and RDE value:
              [false]  no normalisation - default
              [true]   normalises w.r.t number of possible dispersion patterns
                       (c^m  or (2c -1)^m-1 if Fluct == true).\n
    `rho`   - *If Typex == 'finesort', rho is the tuning parameter* (default: 1)\n

    # See also `PermEn`, `SyDyEn`, `MSEn`

    # References:
        [1] Mostafa Rostaghi and Hamed Azami,
            "Dispersion entropy: A measure for time-series analysis."
            IEEE Signal Processing Letters
            23.5 (2016): 610-614.

        [2] Hamed Azami and Javier Escudero,
            "Amplitude-and fluctuation-based dispersion entropy."
            Entropy
            20.3 (2018): 210.

        [3] Li Yuxing, Xiang Gao and Long Wang,
            "Reverse dispersion entropy: A new complexity measure for
            sensor signal."
            Sensors
            19.23 (2019): 5203.

        [4] Wenlong Fu, et al.,
            "Fault diagnosis for rolling bearings based on fine-sorted
            dispersion entropy and SVM optimized with mutation SCA-PSO."
            Entropy
            21.4 (2019): 404.

    """
    function DispEn(Sig::AbstractArray{T,1} where T<:Real; c::Int=3, m::Int=2, tau::Int=1, Typex::String="ncdf",
        Logx::Real=exp(1), Fluct::Bool=false, Norm::Bool=false, rho::Real=1)

    N = size(Sig)[1]
    (N > 10) ? nothing :  error("Sig:   must be a numeric vector")
    (c > 1) ? nothing :   error("c:     must be an integer > 1")
    (m > 0) ? nothing :   error("m:     must be an integer > 0")
    (tau > 0) ? nothing : error("tau:   must be an integer > 0")
    (lowercase(Typex) in ["linear", "kmeans", "ncdf", "finesort","equal"]) ? nothing :
        error("Typex:    must be one of the following strings - 'linear','kmeans','ncdf','finesort','equal'")
    (Logx>0) ? nothing :  error("Logx:  must be a positive number > 0")
    (rho>=0) ? nothing :  error("rho:   must be a positive scalar.")

    if lowercase(Typex) == "linear"
        Edges = range(minimum(Sig),maximum(Sig),length=c+1)
        Zi = map(x -> sum(Edges[1:c].<=x), Sig)

    elseif lowercase(Typex) == "kmeans"
        Temp = kmeans(transpose(Sig), c; maxiter=200)
        Zx = assignments(Temp)
        Clux = Temp.centers
        xx = sortperm(Clux[:]);   Zi = zeros(N)
        for k = 1:c
            Zi[Zx.==xx[k]] .= k;
        end

    elseif lowercase(Typex) == "ncdf"
        Zx = normcdf.(mean(Sig),std(Sig,corrected=false),Sig);
        #= Zi= map(x -> searchsortedfirst(range(0,1,length=c+1),x), Zx) .- 1
        Zi[Zi.==0] .= 1 =#
        Zi = map(x -> sum(range(0,1,length=c+1)[1:c].<=x), Zx)

    elseif lowercase(Typex)  == "finesort"
        Zx = normcdf.(mean(Sig),std(Sig,corrected=false),Sig)
        Zi = map(x -> sum(range(0,1,length=c+1)[1:c].<=x), Zx)
        Ym = zeros(N-(m-1)*tau, m)
        for n = 1:m
            Ym[:,n] = Zx[1+(n-1)*tau:N-((m-n)*tau)]
        end
        Yi = floor.(maximum(abs.(diff(Ym,dims=2)),dims=2)./(rho*std(abs.(diff(Sig)),corrected=false)))

    elseif lowercase(Typex) == "equal"
        ix = sortperm(Sig);
        xx = Int.(round.(range(0,N,length=c+1)))
        Zi = zeros(N)
        for k = 1:c
            Zi[ix[xx[k]+1:xx[k+1]]] .= k
        end
    end

    Zm = zeros(N-(m-1)*tau, m)
    for n = 1:m
        Zm[:,n] = Zi[1+(n-1)*tau:N-((m-n)*tau)]
    end

    (lowercase(Typex) == "finesort") ? Zm = hcat(Zm, Yi) : nothing
    if Fluct
        Zm = diff(Zm,dims=2)
        (m < 2) ? @warn(["Fluctuation-based Dispersion Entropy is undefined for m = 1. "...
                "An embedding dimension (m) > 1 should be used."]) : nothing
    end

    T = unique(Zm,dims=1)
    Nx = size(T)[1]
    Counter = zeros(Nx)
    for n = 1:Nx
        Counter[n] = sum(all(Zm .- transpose(T[n,:]) .==0, dims=2))
    end

    Ppi = Counter[Counter.!= 0]/size(Zm)[1]

    if Fluct
        RDE = sum((Ppi .- (1/((2*c - 1)^(m-1)))).^2)
    else
        RDE = sum((Ppi .- (1/(c^m))).^2)
    end
    #RDE = sum(Ppi.^2) - (1/Nx)

    if round(sum(Ppi)) != 1
        @warn("Potential Error calculating probabilities")
    end

    Dispx = -sum(Ppi.*log.(Logx, Ppi))
    if Norm
        #Dispx = Dispx/log(Logx, Nx)
        #RDE = RDE/(1 - (1/Nx))
        if Fluct
            Dispx = Dispx/(log(Logx, (2*c - 1)^(m-1)))
            RDE = RDE/(1 - (1/((2*c - 1)^(m-1))))
        else
            Dispx = Dispx/(log(Logx, c^m))
            RDE = RDE/(1 - (1/(c^m)))
        end
    end

    return Dispx, RDE
    end

end

#= for n = 1:m
    Zm[:,n] = Zi[1+(n-1)*tau:N-((m-n)*tau)]
    T[:,n] = repeat(1:c,inner=(c^(n-1),1),outer=(c^(m-n),1))
end

if lowercase(Typex) == "finesort"
    Zm = hcat(Zm, Yi)
    temp = sort(unique(Yi))
    T = repeat(T,outer=(length(temp),1))
    T = hcat(T,repeat(temp,inner=Nx))
    Counter = repeat(Counter,outer=length(Yi))
    Nx = length(Counter)
end

if Fluct
    Zm = diff(Zm,dims=2)
    T  = unique(Zm,dims=1)
    Nx = size(T,1)
    Counter = zeros(Nx)
end =#

"""
Copyright 2021 Matthew W. Flood, EntropyHub

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

For Terms of Use see https://github.com/MattWillFlood/EntropyHub
"""


text:
using MultipleViewGeometry, Test, LinearAlgebra

# In the case of calibrated cameras one can assume that
# 𝐊₁ = 𝐊₂ = 𝐈, 𝐭₁ = [0,0,0],  𝐑₁ = 𝐈 ,  𝐑₂ = 𝐑 and 𝐭 = -𝐑𝐭₂ so that the
# direct nRt representation 𝐇 = -d𝐑 + 𝐭𝐧' holds.
# We will verify that our general function for constructing a homography
# produces the same result as the direct nRt representation for calibrated
# cameras.
𝐊₁ = Matrix{Float64}(I, 3, 3)
𝐑₁ = Matrix{Float64}(I, 3, 3)
𝐭₁ = [0.0, 0.0, 0.0]
𝐊₂ = Matrix{Float64}(I, 3, 3)
𝐑₂ = Matrix{Float64}(I, 3, 3)
𝐭₂ = [2.0, 2.0, 2.0]
𝐧 = [1.0, 0.0, 0.0]
d = 10

𝐑 = 𝐑₂
𝐭 = -𝐑*𝐭₂
𝐇₀ = -d*𝐑 + 𝐭*𝐧'

𝐇 = construct(HomographyMatrix(),𝐊₁,𝐑₁,𝐭₁,𝐊₂,𝐑₂,𝐭₂,𝐧,d)

# Homographies are equivalent up to scale and sign so we need to normalise them
# before comparing them.
𝐇₀ = 𝐇₀ / norm(𝐇₀)
𝐇₀ = 𝐇₀ / sign(𝐇₀[3,3])

𝐇 = 𝐇 / norm(𝐇)
𝐇 = 𝐇 / sign(𝐇[3,3])

@test all(𝐇 .≈ 𝐇₀)


text:
# Chunking Generator
# ==================

struct ChunkingGenerator{I,F}
    scheduler::Scheduler

    iter::I

    func::F

    # maximum chunk size
    maxchunksize::Int

    # number of chunks in a chunking buffer
    nchunks::Int
end

function ChunkingGenerator(scheduler, iter, func)
    return ChunkingGenerator(
        scheduler, iter, func,
        scheduler.config.chunksize, 8)
end

function Base.iteratorsize(::Type{ChunkingGenerator{I,F}}) where {I,F}
    return Base.iteratorsize(I)
end

function Base.iteratoreltype(::Type{ChunkingGenerator{I,F}}) where {I,F}
    return Base.EltypeUnknown()
end

function Base.size(gen::ChunkingGenerator)
    return size(gen.iter)
end

mutable struct ChunkingGeneratorState{S}
    state::S
    done::Bool
    chunk::Vector
    remotecalls::Vector{RemoteCall}
end

function Base.start(gen::ChunkingGenerator)
    return ChunkingGeneratorState(start(gen.iter), false, [], RemoteCall[])
end

function Base.done(gen::ChunkingGenerator, state)
    while !state.done && length(state.remotecalls) < gen.nchunks
        chunk = eltype(gen.iter)[]
        while length(chunk) < gen.maxchunksize && !done(gen.iter, state.state)
            item, state.state = next(gen.iter, state.state)
            push!(chunk, item)
        end
        if isempty(chunk)
            state.done = true
        else
            # Assign `gen.func` to a variable so as not to serialize `gen`.
            f = gen.func
            rc = call(gen.scheduler, c -> map(f, c), chunk)
            push!(state.remotecalls, rc)
        end
    end
    return isempty(state.chunk) && isempty(state.remotecalls)
end

function Base.next(gen::ChunkingGenerator, state)
    if isempty(state.chunk)
        state.chunk = fetch(shift!(state.remotecalls))
    end
    item = shift!(state.chunk)
    return item, state
end


text:
function SeisSemblance(in,param=Dict())

	dt = get(param,"dt",0.002)
	dx = get(param,"dx",10)
	nt,nx = size(in)
	offset = get(param,"offset",[0:1:nx-1]*dx)
	vmin = get(param,"vmin",1500)
	vmax = get(param,"vmax",3500)
	nv = get(param,"nv",500)
	v = linspace(vmin,vmax,nv)
	tau = [0:4:nt-1]*dt
	ntau = length(tau)
	L = get(param,"L",5)

	S = zeros(ntau,nv)
	for it = 1:ntau
		for iv = 1:nv
			t = sqrt( tau[it]^2 + (offset/v[iv]).^2 )
			s = zeros(2*L+1,nx)
			for ig = -L:L
				ts = t + (ig-1)*dt
				for ix = 1:nx
					is = ts[ix]/dt+1
					i1 = floor(is)
					i2 = i1 + 1
					if (i1>=1 && i2<=nt)
						a = is-i1
						s[ig+L+1,ix] = (1.-a)*in[i1,ix] + a*in[i2,ix]   #Grab sample with Linear interpolation
					end
				end
			end
			#s = s.*H
			s1  = sum( (sum(s,2)).^2)
			s2  = sum( sum(s.^2))
			S[it,iv] = abs(s1-s2)
		end
	end
	S = S/maximum(S[:])

	return S,tau,v
end


text:
function solve()
    n, d = [parse(Int, x) for x in split(readline())]
    ret = 0
    for _=1:n
        x, y = [parse(Int, x) for x in split(readline())]
        if sqrt(x*x + y*y) ≤ d
            ret += 1
        end
    end
    ret
end

println(solve())

max_stars_repo_name: aishikawa/atcoder-julia

max_issues_repo_name: aishikawa/atcoder-julia

max_forks_repo_name: aishikawa/atcoder-julia


text:
using Documenter, CImGui

makedocs(
    modules = [CImGui],
    clean = false,
    sitename = "CImGui.jl",
    linkcheck = !("skiplinks" in ARGS),
    pages = [
        "Introduction" => "index.md",
        "API Reference" => "api.md",
    ],
)

deploydocs(
    repo = "github.com/Gnimuc/CImGui.jl.git",
)


text:
using Documenter, ISA

makedocs(;
    modules=[ISA],
    format=Documenter.HTML(),
    pages=[
        "Home" => "index.md",
        "ISA Basics" =>
                    ["Cannonical Triplets and Component Sets"   => "basics/triplets.md",
                     "AM--FM Components"     => "basics/components.md",
                     "AM--FM Models"         => "basics/models.md",
                     "Instantaneous Spectra" => "basics/spectra.md",
                     "Numerical Components"     => "basics/componentsNumerical.md",],
        "Decomposition" => "decomposition/EMD.md",
        "Distributions" => "distributions/STFT.md",
        "Citation" => "cite.md",
    ],
    repo="https://github.com/ssandova/ISA.jl/blob/{commit}{path}#L{line}",
    sitename="ISA.jl",
    authors="Steven Sandoval",
    assets=String[],
)

#deploydocs(;
#    repo="github.com/ssandova/ISA.jl",
#)

max_stars_repo_name: kricheso/ISA

max_issues_repo_name: kricheso/ISA

max_forks_repo_name: kricheso/ISA