# src/arithmetic.jl
# Arithmetic of Rationals

import Base: sign,
             signbit,
             abs,
             abs2,
             copysign,
             flipsign,
             BitSigned,
             +,
             -,
             *,
             /,
             //,
             ^,
             inv,
             fma,
             power_by_squaring,
             div,
             fld,
             cld,
             checked_mul

"""```
sign(x::AbstractRational)
    === oftype(x, sign(numerator(x)))
```
Return the sign of the rational `x`.
"""
sign(x::AbstractRational) = oftype(x, sign(numerator(x)))

"""```
signbit(x::AbstractRational)
    === signbit(numerator(x))
```
Return the sign bit of the rational `x`.
"""
signbit(x::AbstractRational) = signbit(numerator(x))

"""```
abs(x::AbstractRatio)
    === signbit(x) ? -x : x
```
Return the absolute value of the ratio `x`.
"""
abs(x::AbstractRatio) = signbit(x) ? -x : x

"""```
abs2(x::AbstractRatio)
    === x * x
```
Return the square of the absolute value of the ratio `x`.
"""
abs2(x::AbstractRatio) = x * x

"""```
copysign(x::AbstractRatio, y)
    === signbit(y) ? -abs(x) : abs(x)
```
Return the absolute value of the ratio `x` with the sign of `y`.
"""
copysign(x::AbstractRatio, y) = signbit(y) ? -abs(x) : abs(x)

"""```
flipsign(x::AbstractRatio, y)
    === signbit(y) ? -x : x
```
Return the ratio `x` with the sign flipped according to the sign of `y`.
"""
flipsign(x::AbstractRatio, y) = signbit(y) ? -x : x

"""```
+x::AbstractRatio
```
Return the ratio `x`.
"""
+(x::AbstractRatio) = build_ratio(typeof(x), +numerator(x), denominator(x))

"""```
-x::AbstractRatio
```
Return the negative of ratio `x`.
"""
-(x::AbstractRatio) = build_ratio(typeof(x), -numerator(x), denominator(x))

"""```
-x::AbstractRational{T <: BitSigned}
```
Return the negative of rational `x` with underlying bit signed integer type.
"""
function -(x::AbstractRational{T}) where {T<:BitSigned}
    if numerator(x) == typemin(T)
        throw(OverflowError("rational numerator is typemin(T)"))
    end
    return build_ratio(typeof(x), -numerator(x), denominator(x))
end

"""```
-x::AbstractRational{T <: Unsigned}
```
Return the negative of rational `x` with underlying unsigned integer type.
"""
function -(x::AbstractRational{T}) where {T<:Unsigned}
    if numerator(x) != zero(T)
        throw(OverflowError("cannot negate unsigned number"))
    end
    return x
end

"""```
x::AbstractRational * y::AbstractRational
```
Return the product of rational `x` with rational `y`.
"""
function *(x::AbstractRational, y::AbstractRational)
    xn, yd = divgcd(numerator(x), denominator(y))
    xd, yn = divgcd(denominator(x), numerator(y))
    return build_ratio(typeof(x), checked_mul(xn, yn), checked_mul(xd, yd))
end

"""```
x::AbstractRational / y
```
Divide the rational `x` by `y`.
"""
function /(x::AbstractRational, y)
    return build_ratio(typeof(x), numerator(x) * denominator(y), denominator(x) * numerator(y))
end

"""```
x / y::AbstractRational
```
Divide `x` by the rational `y`.
"""
function /(x, y::AbstractRational)
    return build_ratio(typeof(y), numerator(x) * denominator(y), denominator(x) * numerator(y))
end

"""```
x::AbstractRational // y
```
Rationally divide the rational `x` by `y`.
"""
function //(x::AbstractRational, y)
    xn, yn = divgcd(numerator(x), y)
    return xn // checked_mul(x.den, yn)
end

"""```
x // y::AbstractRational
```
Rationally divide `x` by the rational `y`.
"""
function //(x, y::AbstractRational)
    xn, yn = divgcd(x, numerator(y))
    return checked_mul(xn, denominator(y)) // yn
end

"""```
x::AbstractRational // y::AbstractRational
```
Rationally divide the rational `x` by the rational `y`.
"""
function //(x::AbstractRational, y::AbstractRational)
    xn, yn = divgcd(numerator(x), numerator(y))
    xd, yd = divgcd(denominator(x), denominator(y))
    return checked_mul(xn, yd) // checked_mul(xd, yn)
end

"""```
x::AbstractRatio ^ n::Integer
```
Exponentiate the ratio `x` by the integer `n`.
"""
function ^(x::AbstractRatio, n::Integer)
    return n >= zero(n) ? power_by_squaring(x, n) : power_by_squaring(inv(x), -n)
end

"""```
x ^ y::AbstractRatio
```
Exponentiate `x` by the ratio `y`.
"""
^(x, y::AbstractRatio) = x^(numerator(y) / denominator(y))

"""```
x::AbstractFloat ^ y::AbstractRatio
```
Exponentiate the floating point value `x` by the ratio `y`.
"""
^(x::T, y::AbstractRatio) where {T<:AbstractFloat} = x^convert(T, y)

"""```
z::Complex ^ y::AbstractRatio
```
Exponentiate the complex number `z` by the ratio `y`.
"""
^(z::Complex{T}, y::AbstractRatio) where {T} = z^convert(typeof(one(T)^y), y)

"""```
z::Complex{<:AbstractRatio} ^ n::Bool
```
Exponentiate the complex ratio `z` by the boolean `n`.
"""
^(z::Complex{<:AbstractRatio}, n::Bool) = n ? z : one(z)

"""```
z::Complex{<:AbstractRatio} ^ n::Integer
```
Exponentiate the complex ratio `z` by the integer `n`.
"""
function ^(z::Complex{<:AbstractRatio}, n::Integer)
    return n >= zero(n) ? power_by_squaring(z, n) : power_by_squaring(inv(z), -n)
end

"""```
inv(x::AbstractRatio)
    === build_ratio(typeof(x), denominator(x), numerator(x))
```
Invert the ratio `x`.
"""
inv(x::AbstractRatio) = build_ratio(typeof(x), denominator(x), numerator(x))

"""```
fma(x::AbstractRatio, y::AbstractRatio, z::AbstractRatio)
    === (x * y) + z
```
Return the fused Mulitply-Add of the ratios `x`, `y`, and `z`.
"""
fma(x::AbstractRatio, y::AbstractRatio, z::AbstractRatio) = (x * y) + z

"""```
div(x::AbstractRational, y::Integer, r::RoundingMode)
```
Integer divide the rational `x` by the integer `y` and round accoring to rounding mode `r`.
"""
function div(x::AbstractRational, y::Integer, r::RoundingMode)
    xn, yn = divgcd(numerator(x), y)
    return div(xn, checked_mul(denominator(x), yn), r)
end

"""```
div(x::Integer, y::AbstractRational, r::RoundingMode)
```
Integer divide the integer `x` by the rational `y` and round accoring to rounding mode `r`.
"""
function div(x::Integer, y::AbstractRational, r::RoundingMode)
    xn, yn = divgcd(x, numerator(y))
    return div(checked_mul(xn, denominator(y)), yn, r)
end

"""```
div(x::AbstractRational, y::AbstractRational, r::RoundingMode)
```
Integer divide the rational `x` by the rational `y` and round accoring to rounding mode `r`.
"""
function div(x::AbstractRational, y::AbstractRational, r::RoundingMode)
    xn, yn = divgcd(numerator(x), numerator(y))
    xd, yd = divgcd(denominator(x), denominator(y))
    return div(checked_mul(xn, yd), checked_mul(xd, yn), r)
end

"""```
div(x::AbstractRatio, y)
    === div(x, y, RoundToZero)
```
Integer divide the rational `x` by `y`.
"""
div(x::AbstractRatio, y) = div(x, y, RoundToZero)

"""```
fld(x::AbstractRatio, y)
    === div(x, y, RoundDown)
```
Integer divide the rational `x` by `y` and round down.
"""
fld(x::AbstractRatio, y) = div(x, y, RoundDown)

"""```
cld(x::AbstractRatio, y)
    === div(x, y, RoundUp)
```
Integer divide the rational `x` by `y` and round up.
"""
cld(x::AbstractRatio, y) = div(x, y, RoundUp)

"""```
div(x, y::AbstractRatio)
    === div(x, y, RoundToZero)
```
Integer divide `x` by the rational `y`.
"""
div(x, y::AbstractRatio) = div(x, y, RoundToZero)

"""```
fld(x, y::AbstractRatio)
    === div(x, y, RoundDown)
```
Integer divide `x` by the rational `y` and round down.
"""
fld(x, y::AbstractRatio) = div(x, y, RoundDown)

"""```
cld(x, y::AbstractRatio)
    === div(x, y, RoundUp)
```
Integer divide `x` by the rational `y` and round up.
"""
cld(x, y::AbstractRatio) = div(x, y, RoundUp)

"""```
div(x::AbstractRatio, y::AbstractRatio)
    === div(x, y, RoundToZero)
```
Integer divide the rational `x` by the rational `y`.
"""
div(x::AbstractRatio, y::AbstractRatio) = div(x, y, RoundToZero)

"""```
fld(x::AbstractRatio, y::AbstractRatio)
    === div(x, y, RoundDown)
```
Integer divide the rational `x` by the rational `y` and round down.
"""
fld(x::AbstractRatio, y::AbstractRatio) = div(x, y, RoundDown)

"""```
cld(x::AbstractRatio, y::AbstractRatio)
    === div(x, y, RoundUp)
```
Integer divide the rational `x` by the rational `y` and round up.
"""
cld(x::AbstractRatio, y::AbstractRatio) = div(x, y, RoundUp)


using SparseGrids
using Test

@testset "combvec" begin
    @testset "Two 2D vectors" begin
        a1 = [[1; 3], [2; 4]]
        a = combvec(a1)

        @test a == [[1; 2], [3; 2], [1; 4], [3; 4]]
    end

    @testset "A 2D vector and two 1D vectors" begin
        a1 = [[1; 2], [3], [4]]
        a = combvec(a1)

        @test a == [[1; 3; 4], [2; 3; 4]]
    end
end


using Profile, ProfileView

include("util.jl")

mutable struct Circle
    marbles::LinkedList{Int64}
    next_marble::Int64
end

function rotate_marbles(c::Circle, idx::Int64)
    pre = c.marbles[idx:end]
    post = c.marbles[1:idx-1]
    c.marbles = vcat(pre,post)
end

function insert_marble(circle::Circle)::Int64
    marble_to_insert = circle.next_marble
    if marble_to_insert % 23 == 0
        score = marble_to_insert + circle.marbles[-6]
        deleteat!(circle.marbles, -6)
        circshift!(circle.marbles, 6)
        #rotate_marbles(circle, mod(-5,length(circle)))
        circle.next_marble += 1
        return score
    else
        insert!(circle.marbles, 2, marble_to_insert)
        circshift!(circle.marbles, -2)
        #rotate_marbles(circle, 3)
        circle.next_marble += 1
        return 0
    end
end

function part_1(num_players::Int64, last_marble::Int64)
    a = LinkedListNode(3, nothing, nothing)
    b = LinkedListNode(0, a, nothing)
    c = LinkedListNode(2, b, nothing)
    d = LinkedListNode(1, c, nothing)
    c.next = d
    b.next = c
    a.next = b
    circle = Circle(LinkedList(a,d,4), 4)
    player_scores::Array{Int64} = zeros(num_players)
    player = 1
    while circle.next_marble <= last_marble
        if circle.next_marble % 72170 == 0
            #println(circle.next_marble/72170)
        end
        player_scores[player] += insert_marble(circle)
        player += 1
        player = (player % num_players)
        if player == 0
            player = num_players
        end
    end
    maximum(player_scores)
end

part_1(10,25)
part_1(10,1618)
part_1(13,7999)
part_1(17,1104)
part_1(470,72170)
part_2 = part_1(470,72170*100)

Profile.clear()
@profile part_1(470,72170*100)
ProfileView.view()

@time part_2 = part_1(470,72170*100)


using SellmeierFit
using Downloads
using SimpleConstants
using Test

include("read.jl")
include("model.jl")
include("fit.jl")


"""
    Geometry(a1::Array{Real,1}, a2::Array{Real,1}, ep::Array{ComplexF64,2}, mu::Array{ComplexF64,2})

2D geometry defined in real space, with lattice vectors `a1` and `a2`,
and relative permeability and permittivity `ep` and `mu`, respectively.
"""
struct Geometry
    a1::Array{Real,1}
    a2::Array{Real,1}
    ep::Array{ComplexF64,2}
    mu::Array{ComplexF64,2}
end

"""
    Geometry(epf::Function, muf::Function, a1::Array{<:Real,1}, a2::Array{<:Real,1}, d1::Real, d2::Real)

Generate geometry with permittivity `epf(x,y)` and permeability `muf(x,y)`.

The real space lattice vectors, `a1` and `a2`, define the unit cell.
The grid resolution along each lattice vector is `d1` and `d2`, respectively.
"""
function Geometry(epf::Function, muf::Function,
            a1::Array{<:Real,1}, a2::Array{<:Real,1}, d1::Real, d2::Real)
    P = ceil(Int, norm(a1)/d1)
    Q = ceil(Int, norm(a2)/d2)
    ps = range(-0.5, stop=0.5, length=P)[1:end-1]
    qs = range(-0.5, stop=0.5, length=Q)[1:end-1]
    ps = ps .+ step(ps)/2 # ensure that p and q are centered on zero
    qs = qs .+ step(qs)/2
    xs = [p*a1[1]+q*a2[1] for p in ps, q in qs]
    ys = [p*a1[2]+q*a2[2] for p in ps, q in qs]
    ep = epf.(xs, ys)
    mu = muf.(xs, ys)
    return Geometry(a1, a2, ep, mu)
end

"""
    Geometry(epf::Function, muf::Function, a1::Array{<:Real,1}, a2::Array{<:Real,1}, d1::Real, d2::Real)

Generate geometry with permittivity `epf(x,y)` and permeability `muf(x,y)`.

The real space lattice vectors are assumed to have unit length and are at
angles `a1_deg` and `a2_deg` counter-clockwise from the x-axis.
The grid resolution along each lattice vector is `d1` and `d2`, respectively.
"""
function Geometry(epf::Function, muf::Function, a1_deg::Real, a2_deg::Real, d1::Real, d2::Real)
    a1 = [cosd(a1_deg), sind(a1_deg)]
    a2 = [cosd(a2_deg), sind(a2_deg)]
    return Geometry(epf, muf, a1, a2, d1, d2)
end


"""
    Bond

Struct encapsulating the interactions between two lattice sites in matrix form.
* `sites    :: Tuple{Int64, Int64}` : indices of interacting lattice sites
* `exchange :: Matrix{Float64}`     : interaction matrix
"""
struct Bond
    sites    :: Tuple{Int64, Int64}
    exchange :: Matrix{Float64}
end

# generate a bond with vanishing interactions
function get_bond_empty(
    i :: Int64,
    j :: Int64
    ) :: Bond

    b = Bond((i, j), zeros(Float64, 3, 3))

    return b
end

# modify bond matrix in place
function add_bond!(
    J :: Float64,
    b :: Bond,
    μ :: Int64,
    ν :: Int64
    ) :: Nothing

    b.exchange[μ, ν] += J

    return nothing
end

# check if bonds are equal
function are_equal(
    b1 :: Bond,
    b2 :: Bond
    )  :: Bool

    equal = norm(b1.exchange .- b2.exchange) <= 1e-8

    return equal
end


"""
Problem Statement:
In the rabbit leap problem, three east-bound rabbits stand in a line blocked by three west-bound rabbits.
They are crossing a stream with stones placed in the east west direction in a line.
There is one empty stone between them.
The rabbits can only move forward one or two steps.
They can jump over one rabbit if the need arises, but not more than that.
Are they smart enough to cross each other without having to step into the water?

While there can be many possible algos to resolve this tiny problem,
we shall start with just uninformed agents like bfs and dfs.
"""

"""
A basic setup of a state would be something like,
leftside: no of cannibals, no of missionaries
rightside: no of cannibals, no of missionaries
boat position: right or left

By reducing redundant fields, we can just represent the state with 3 fields.
Hence, I created the state like "m c b"
where,
m => no of missionaries
c => no of cannibals
b => boat(0 for left, 1 for right)
"""

initial_state = "330" # initially all missionaries and cannibals are on left side
target_state = "001" # at the end they all are at right side(as well as boat)

function all_moves(m, c, b)
    if b == '0'
        return [
            [m-1, c],
            [m-2, c],
            [m, c-1],
            [m, c-2],
            [m-1, c-1]
        ]
    else
        return [
            [m+1, c],
            [m+2, c],
            [m, c+1],
            [m, c+2],
            [m+1, c+1]
        ]
    end
end

function is_valid_move(missionary, cannibal)
    # removing bad cases given
    if missionary > 3 || cannibal > 3 || missionary < 0 || cannibal < 0
        return false
    end
    # if all missionaries on one side, they are always safe
    if missionary == 0 || missionary == 3
        return true
    end
    # making sure that missionaries are more than or equal cannibals on both sides
    return missionary >= cannibal && (3 - missionary) >= (3 - cannibal)
end

function possible_moves(state)
    m, c, b = state
    moves = filter(p->is_valid_move(parse(Int,p[1]), parse(Int, p[2])), all_moves(m,c,b))
    new_boat = b == '1' ? '0' : '1'
    return map(p->join([p[1], p[2], new_boat]), moves)
end


function calcsortkey(a, key_series, key_values)
    vector_vector_indexes = calcsortindexes(key_series, key_values)
    b = similar(a)
    j = 1
    for vector_indexes in vector_vector_indexes
        for i in vector_indexes
            b[j] = a[i]
            j+=1
        end
    end
    return b
end

function calcsortindexes(key_series, key_values)
    vector_vector_indexes=[Int[] for _ in key_values]

    for i in eachindex(key_series)
        k=indexvalue(key_series[i], key_values)
        push!(vector_vector_indexes[k], i)
    end

    return vector_vector_indexes
end

function indexvalue(v, values::Vector)
    findfirst(x->x == v, values)
end

function calcsort_numzeros(a)
    b = [@view a[:,j] for j in 1:size(a,2)]
    k_s = [length(findall(b[j] .== 0)) for j in 1:length(b)]
    b = calcsortkey(b, k_s, collect(0:size(a,1)))
    return  hcat(b...)
end


### A Pluto.jl notebook ###
# v0.12.6

using Markdown
using InteractiveUtils

# ╔═╡ eb807d0e-1dfd-11eb-155d-df6f207266b9
using Pkg, DrWatson

# ╔═╡ fa048db0-1de8-11eb-20cd-9b8d6a6fd1f1
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Random, LinearAlgebra, Plots
	using LaTeXStrings, Measures
end

# ╔═╡ f9c7005e-1dfd-11eb-08de-4d71184b2337
md"## Listing 1.14"

# ╔═╡ 9da9d70a-1dfe-11eb-054f-ffe619ac258d
Random.seed!(1974); rand()

# ╔═╡ 9daa179c-1dfe-11eb-206d-4f94676a8104
Random.seed!(1975); rand()

# ╔═╡ 9daaa4f0-1dfe-11eb-1bb8-4f632207e49b
Random.seed!(1974); rand()

# ╔═╡ 2b564d56-1de9-11eb-120b-7137b81725dd
md"## Listing 1.15"

# ╔═╡ e9553c4a-1dfd-11eb-28d8-f3f4c0297d2a
Random.seed!()

# ╔═╡ b1edef30-1dfe-11eb-26fd-9782f1f70d10
begin
	N = 10^4
	data1     = [[rand(),rand()] for _ in 1:N]
	indata   = filter((x)-> (norm(x) <= 1), data1)
	outdata  = filter((x)-> (norm(x) > 1), data1)
	piApprox = 4*length(indata)/N
	md"Pi Estimate: $(piApprox)"
end

# ╔═╡ b1ee272a-1dfe-11eb-1956-91da67abf143
begin
	scatter(first.(indata),last.(indata), c=:blue, ms=1, msw=0)
	scatter!(first.(outdata),last.(outdata), c=:red, ms=1, msw=0,
		xlims=(0,1), ylims=(0,1), legend=:none, ratio=:equal)
end

# ╔═╡ 2e3fb9ba-1de9-11eb-0835-db03a7547b7c
md"## Listing 1.16"

# ╔═╡ 3c2312fe-1dfd-11eb-1afe-11077862ebb0
begin
	a, c, m = 69069, 1, 2^32
	next(z) = (a*z + c) % m

	#N = 10^6
	data2 = Array{Float64,1}(undef, N)

	x = 808

	for i in 1:N
    	data2[i] = x/m
    	global x = next(x)
	end
end

# ╔═╡ e729992c-1e00-11eb-13dc-0b9f2d96ef0d
begin
	p1 = scatter(1:1000, data2[1:1000], xlim=(0, 1000), ylim=(0,1),
		c=:blue, m=4, msw=0, xlabel=L"n", ylabel=L"x_n")
	p2 = histogram(data2, xlim=(0,1.05), bins=50, normed=:true,
		ylims=(0,1.1), xlabel="Support", ylabel="Density")
	plot(p1, p2, size=(800, 400), legend=:none, margin = 5mm)
end

# ╔═╡ 082010d8-1de9-11eb-23df-3fd3c98c4c1d
md"## Listing 1.17"

# ╔═╡ 4441a4ba-1e01-11eb-16d8-67b2c22f7138
function path(rng, alpha, n=5000)
    x, y = 0.0, 0.0
    xDat, yDat = [], []
    for _ in 1:n
        flip = rand(rng,1:4)
        if flip == 1
            x += 1
        elseif flip == 2
            y += 1
        elseif flip == 3
            x -= (2+alpha)*rand(rng)
        elseif flip == 4
            y -= (2+alpha)*rand(rng)
        end
        push!(xDat,x)
        push!(yDat,y)
    end
    return xDat, yDat
end

# ╔═╡ 4441d4e4-1e01-11eb-201e-91694cb132aa
alphaRange = [0.2, 0.21, 0.22]

# ╔═╡ 44428c8e-1e01-11eb-0cb0-fb6fd51c9e04
default(xlabel = "x", ylabel = "y", xlims=(-150,50), ylims=(-250,50))

# ╔═╡ 4454e5fc-1e01-11eb-3d1f-bd6396d87471
begin
	p3 = plot(path(MersenneTwister(27), alphaRange[1]), c=:blue)
	plot!(path(MersenneTwister(27), alphaRange[2]), c=:red)
	plot!(path(MersenneTwister(27), alphaRange[3]), c=:green)
end;

# ╔═╡ 447a4342-1e01-11eb-22ea-a3ceff1f0df1
rng = MersenneTwister(27)

# ╔═╡ eaf93996-1e04-11eb-00d7-95f753122789
begin
	p4 = plot(path(rng, alphaRange[1]), c=:blue)
	plot!(path(rng, alphaRange[2]), c=:red)
	plot!(path(rng, alphaRange[3]), c=:green)
end;

# ╔═╡ 44a7eb76-1e01-11eb-134f-1d67bf11c36e
plot(p3, p4, size=(500, 400), legend=:none, margin=5mm)

# ╔═╡ ded51f3e-1e01-11eb-0717-af179845759e
md"## End of listing 1.14"

# ╔═╡ Cell order:
# ╟─f9c7005e-1dfd-11eb-08de-4d71184b2337
# ╠═eb807d0e-1dfd-11eb-155d-df6f207266b9
# ╠═fa048db0-1de8-11eb-20cd-9b8d6a6fd1f1
# ╠═9da9d70a-1dfe-11eb-054f-ffe619ac258d
# ╠═9daa179c-1dfe-11eb-206d-4f94676a8104
# ╠═9daaa4f0-1dfe-11eb-1bb8-4f632207e49b
# ╟─2b564d56-1de9-11eb-120b-7137b81725dd
# ╠═e9553c4a-1dfd-11eb-28d8-f3f4c0297d2a
# ╠═b1edef30-1dfe-11eb-26fd-9782f1f70d10
# ╠═b1ee272a-1dfe-11eb-1956-91da67abf143
# ╟─2e3fb9ba-1de9-11eb-0835-db03a7547b7c
# ╠═3c2312fe-1dfd-11eb-1afe-11077862ebb0
# ╠═e729992c-1e00-11eb-13dc-0b9f2d96ef0d
# ╟─082010d8-1de9-11eb-23df-3fd3c98c4c1d
# ╠═4441a4ba-1e01-11eb-16d8-67b2c22f7138
# ╠═4441d4e4-1e01-11eb-201e-91694cb132aa
# ╠═44428c8e-1e01-11eb-0cb0-fb6fd51c9e04
# ╠═4454e5fc-1e01-11eb-3d1f-bd6396d87471
# ╠═447a4342-1e01-11eb-22ea-a3ceff1f0df1
# ╠═eaf93996-1e04-11eb-00d7-95f753122789
# ╠═44a7eb76-1e01-11eb-134f-1d67bf11c36e
# ╟─ded51f3e-1e01-11eb-0717-af179845759e


function dist(a,b)
    ret = 0
    for i = 1:length(a)
        c, v = a[i], b[i]
        ret += c!=v ? 1 : 0
    end
    ret
end

a=readline()
b=readline()

sol=999999999
for i = 1:length(a)-length(b)+1
    global sol
    sa = a[i:i+length(b)-1]
    sol=min(dist(sa,b), sol)
end
println(sol)


module OneDimensionalCosineAdvectionDiffusion

using Printf, Statistics

using Oceananigans, Oceananigans.OutputWriters, Oceananigans.Grids

include("analysis.jl")

# Advection and diffusion of a cosine.
c(x, y, z, t, U, κ) = exp(-κ * t) * cos(x - U * t)

function run_test(; Nx, Δt, stop_iteration, U = 1, κ = 1e-4,
                  architecture = CPU(), topo = (Periodic, Periodic, Bounded))

    #####
    ##### Test c and v-advection
    #####

    grid = RegularCartesianGrid(size=(Nx, 1, 1), x=(0, 2π), y=(0, 1), z=(0, 1), topology=topo)

    model = IncompressibleModel(architecture = architecture,
                                        grid = grid,
                                    coriolis = nothing,
                                    buoyancy = nothing,
                                     tracers = :c,
                                     closure = ConstantIsotropicDiffusivity(ν=κ, κ=κ))

    set!(model, u = U,
                v = (x, y, z) -> c(x, y, z, 0, U, κ),
                c = (x, y, z) -> c(x, y, z, 0, U, κ))

    simulation = Simulation(model, Δt=Δt, stop_iteration=stop_iteration, progress_frequency=stop_iteration)

    println("Running 1D in x cosine advection diffusion test for v and c with Nx = $Nx and Δt = $Δt...")
    run!(simulation)

    x = xnodes(model.tracers.c)
    c_analytical = c.(x, 0, 0, model.clock.time, U, κ)

    # Calculate errors
    cx_simulation = model.tracers.c
    cx_simulation = interior(cx_simulation)[:, 1, 1]
    cx_errors = compute_error(cx_simulation, c_analytical)

    v_simulation = model.velocities.v
    v_simulation = interior(v_simulation)[:, 1, 1]
    v_errors = compute_error(v_simulation, c_analytical)

    #####
    ##### Test u-advection
    #####

    ygrid = RegularCartesianGrid(size=(1, Nx, 1), x=(0, 1), y=(0, 2π), z=(0, 1), topology=topo)

    model = IncompressibleModel(architecture = architecture,
                                        grid = ygrid,
                                    coriolis = nothing,
                                    buoyancy = nothing,
                                     tracers = :c,
                                     closure = ConstantIsotropicDiffusivity(ν=κ, κ=κ))

    set!(model, v = U,
                u = (x, y, z) -> c(y, x, z, 0, U, κ),
                c = (x, y, z) -> c(y, x, z, 0, U, κ))

    simulation = Simulation(model, Δt=Δt, stop_iteration=stop_iteration, progress_frequency=stop_iteration)

    println("Running 1D in y cosine advection diffusion test for u and c with Ny = $Nx and Δt = $Δt...")
    run!(simulation)

    # Calculate errors
    u_simulation = model.velocities.u
    u_simulation = interior(u_simulation)[1, :, 1]
    u_errors = compute_error(u_simulation, c_analytical)

    # Calculate errors
    cy_simulation = model.tracers.c
    cy_simulation = interior(cy_simulation)[1, :, 1]
    cy_errors = compute_error(cy_simulation, c_analytical)

    return (

            cx = (simulation = cx_simulation,
                  analytical = c_analytical,
                          L₁ = cx_errors.L₁,
                          L∞ = cx_errors.L∞),

            cy = (simulation = cy_simulation,
                  analytical = c_analytical,
                          L₁ = cy_errors.L₁,
                          L∞ = cy_errors.L∞),

            v = (simulation = v_simulation,
                 analytical = c_analytical, # same solution as c.
                         L₁ = v_errors.L₁,
                         L∞ = v_errors.L∞),

            u = (simulation = u_simulation,
                 analytical = c_analytical, # same solution as c.
                         L₁ = u_errors.L₁,
                         L∞ = u_errors.L∞),

            grid = grid

            )
end

end # module


export SolverLevMarq

struct SolverLevMarq{T<:Real} <: AbstractNextBasisSolver
  maxiter::Integer
  xtol::T
  gtol::T
  ftol::T
  λinit::T
  λinc::T
  λdec::T
  min_step_quality::T
  good_step_quality::T
  λmax::T
  λmin::T
  diagmin::T
end

function SolverLevMarq(;
  maxiter::Integer=100,
  xtol::T=1e-8,
  gtol::T=1e-8,
  ftol::T=1e-5,
  λinit::T=1.0,
  λinc::T=10.,
  λdec::T=10.,
  min_step_quality::T=1e-3,
  good_step_quality::T=.75,
  λmax::T = 1e12,
  λmin::T = 1e-12,
  diagmin::T = 1e-6
  ) where T
  @assert 0 ≤ min_step_quality < good_step_quality ≤ 1
  SolverLevMarq{T}(maxiter, xtol, gtol, ftol,
                λinit, λinc, λdec,
                min_step_quality, good_step_quality,
                λmax, λmin, diagmin)
end

function (self::SolverLevMarq{T})(prob::Problem{L, B, CS},
                                  r::Vector{T},
                                  uinit::Vector{T};
                                  useprojections::Bool=true,
                                  savetrace::Bool=false) where {T, L, B, CS}
  u = copy(uinit)
  λ = self.λinit
  xconverged, gconverged, λconverged, fconverged = false, false, false, false
  Δu = zeros(u)
  tr = DataFrame(i=Int[], sse=T[], prsse=T[], trsse=T[], ρ=T[],
                 λ=Int[], lo=String[], ri=String[],
                 normΔu=T[], normg=T[], expdecr=T[]
                 )  # TODO: Time consuming
  local F::Vector{T}, J::Matrix{T}

  for iter in 1:self.maxiter
    F, J = valueandjacobian(prob, u)          # TODO: Time consuming
    F -= r
    sse = sum(abs2, F)
    JJ = J'*J
    D = Diagonal(JJ)                # TODO: Time consuming
    D[0 .< D .< self.diagmin] = self.diagmin  # TODO: -do-
    Jf = -J'*F                      # TODO: transp is Time consuming
                                    # This is also the neg gradient
    normg = norm(Jf, Inf)
    gconverged = normg < self.gtol
    gconverged && break

    accept_step = false
    while !(accept_step || λconverged || fconverged)
      JJD = JJ + λ*D                # TODO: Time consuming
      Δu = JJD\Jf
      if useprojections
        Δu = projectΩ(prob, u+Δu)-u
      end

      # Try this step and compare with linear prediction
      pred_sse = sum(abs2, J*Δu+F)
      trialf = predictor(prob, u+Δu) - r    # TODO: Time consuming   #X
      trial_sse = sum(abs2, trialf)
      expd_decr = 1 - pred_sse/sse          # Positive
      fconverged = expd_decr < self.ftol

      ρ = (sse-trial_sse)/(sse-pred_sse)
      lower_trust = ρ ≤  self.min_step_quality
      raise_trust = ρ > self.good_step_quality

      normΔu = norm(Δu)
      logrelλ = round(Int, log(self.λinc, λ))
      trentry = [iter, sse, pred_sse, trial_sse, ρ,
                 logrelλ,
                 lower_trust ? "↑" : " ",
                 raise_trust ? "↓" : " ",
                 normΔu, normg, expd_decr]
      push!(tr, trentry)

      if lower_trust
        if λ==self.λmax
          λconverged = true
        else
          λ = min(λ*self.λinc, self.λmax)
        end
      else        #Accept this step
        accept_step = true
        u += Δu
        if raise_trust
          λ = max(λ/self.λdec, self.λmin)
        end
      end
    end   #Inner Loop over λ

    xconverged = norm(Δu) < self.xtol
    (xconverged || gconverged || λconverged || fconverged) && break
  end    #Outer Loop over Δu

  savetrace && print(tr)

  u, sum(abs2, predictor(prob, u)-r)  #X
end


@testset "$(rpad("Deterministic equations",80))" begin

    ################################################################################
    # Test ODE: Ordinary Differential Equation
    ################################################################################

    function f_ode(t, x, f)
        f[1] = x[1]
    end

    ode  = ODE(eltype(q₀), 1, 1, 1, f_ode, t₀, q₀)
    ode1 = ODE(f_ode, t₀, q₀)
    ode2 = ODE(f_ode, q₀)

    @test ode == ode1
    @test ode == ode2

    @test hash(ode1) == hash(ode2)

    @test ode == similar(ode, t₀, q₀)
    @test ode == similar(ode, q₀)

    ################################################################################
    # Test PODE: Partitioned Ordinary Differential Equation
    ################################################################################

    function v_pode(t, q, p, v)
        v[1] = q[1]
    end

    function f_pode(t, q, p, f)
        f[1] = 2p[1]
    end

    pode  = PODE(eltype(q₀), 1, 1, 1, v_pode, f_pode, t₀, q₀, p₀)
    pode1 = PODE(v_pode, f_pode, t₀, q₀, p₀)
    pode2 = PODE(v_pode, f_pode, q₀, p₀)

    @test pode == pode1
    @test pode == pode2

    @test hash(pode1) == hash(pode2)

    @test pode == similar(pode, t₀, q₀, p₀)
    @test pode == similar(pode, q₀, p₀)

    ################################################################################
    # Test IODE: Implicit Ordinary Differential Equation
    ################################################################################

    function iode_ϑ(t, q, v, p)
        p[1] = v[1]
    end

    function iode_f(t, q, v, f)
        f[1] = sin(q[1])
    end

    function iode_g(t, q, λ, g)
        g[1] = λ[1]
    end

    function iode_v(t, q, p, v)
        v[1] = p[1]
    end

    iode  = IODE(eltype(q₀), 1, 1, 1, iode_ϑ, iode_f, iode_g, t₀, q₀, p₀, λ₀; v=iode_v)
    iode1 = IODE(iode_ϑ, iode_f, iode_g, t₀, q₀, p₀, λ₀; v=iode_v)
    iode2 = IODE(iode_ϑ, iode_f, iode_g, t₀, q₀, p₀; v=iode_v)
    iode3 = IODE(iode_ϑ, iode_f, iode_g, q₀, p₀; v=iode_v)

    @test iode == iode1
    @test iode == iode2
    @test iode == iode3

    @test hash(iode1) == hash(iode2)

    @test iode == similar(iode, t₀, q₀, p₀, λ₀)
    @test iode == similar(iode, t₀, q₀, p₀)
    @test iode == similar(iode, q₀, p₀)

    ################################################################################
    # Test VODE: Variational Ordinary Differential Equation
    ################################################################################

    vode  = VODE(eltype(q₀), 1, 1, 1, iode_ϑ, iode_f, iode_g, t₀, q₀, p₀, λ₀; v=iode_v)
    vode1 = VODE(iode_ϑ, iode_f, iode_g, t₀, q₀, p₀, λ₀; v=iode_v)
    vode2 = VODE(iode_ϑ, iode_f, iode_g, t₀, q₀, p₀; v=iode_v)
    vode3 = VODE(iode_ϑ, iode_f, iode_g, q₀, p₀; v=iode_v)

    @test vode == vode1
    @test vode == vode2
    @test vode == vode3

    @test hash(vode1) == hash(vode2)

    @test vode == similar(vode, t₀, q₀, p₀, λ₀)
    @test vode == similar(vode, t₀, q₀, p₀)
    @test vode == similar(vode, q₀, p₀)

    ################################################################################
    # Test DAE: Differential Algebraic Equation
    ################################################################################

    function v_dae(t, x, v)
        v[1] = x[1]
        v[2] = x[2]
    end

    function u_dae(t, x, λ, u)
        u[1] = +λ[1]
        u[2] = -λ[1]
    end

    function ϕ_dae(t, x, λ, ϕ)
        ϕ[1] = x[2] - x[1]
    end

    dae  = DAE(eltype(q₀), 1, 2, 1, 1, v_dae, u_dae, ϕ_dae, t₀, x₀, λ₀)
    dae1 = DAE(v_dae, u_dae, ϕ_dae, t₀, x₀, λ₀)
    dae2 = DAE(v_dae, u_dae, ϕ_dae, x₀, λ₀)

    @test dae == dae1
    @test dae == dae2

    @test hash(dae1) == hash(dae2)

    @test dae == similar(dae, t₀, x₀, λ₀)
    @test dae == similar(dae, x₀, λ₀)

    ################################################################################
    # Test PDAE: Partitioned Differential Algebraic Equation
    ################################################################################

    function pdae_v(t, q, p, v)
        v[1] = q[1]
    end

    function pdae_f(t, q, p, f)
        f[1] = p[1]
    end

    function pdae_p(t, q, v, p)
        p[1] = v[1]
    end

    function pdae_u(t, q, p, λ, u)
        u[1] = λ[1]
    end

    function pdae_g(t, q, p, λ, g)
        g[1] = λ[1]
    end

    function pdae_ϕ(t, q, p, λ, ϕ)
        ϕ[1] = p[1] - q[1]
    end

    pdae  = PDAE(eltype(q₀), 1, 1, 1, 1, pdae_v, pdae_f, pdae_u, pdae_g, pdae_ϕ, t₀, q₀, p₀, λ₀)
    pdae1 = PDAE(pdae_v, pdae_f, pdae_u, pdae_g, pdae_ϕ, t₀, q₀, p₀, λ₀)
    pdae2 = PDAE(pdae_v, pdae_f, pdae_u, pdae_g, pdae_ϕ, q₀, p₀, λ₀)

    @test pdae == pdae1
    @test pdae == pdae2

    @test hash(pdae1) == hash(pdae2)

    @test pdae == similar(pdae, t₀, q₀, p₀, λ₀)
    @test pdae == similar(pdae, t₀, q₀, p₀)
    @test pdae == similar(pdae, q₀, p₀)

    idae  = IDAE(eltype(q₀), 1, 1, 1, 1, pdae_p, pdae_f, pdae_u, pdae_g, pdae_ϕ, t₀, q₀, p₀, λ₀)
    idae1 = IDAE(pdae_p, pdae_f, pdae_u, pdae_g, pdae_ϕ, t₀, q₀, p₀, λ₀)
    idae2 = IDAE(pdae_p, pdae_f, pdae_u, pdae_g, pdae_ϕ, q₀, p₀, λ₀)

    @test idae == idae1
    @test idae == idae2

    @test hash(idae1) == hash(idae2)

    @test idae == similar(idae, t₀, q₀, p₀, λ₀)
    @test idae == similar(idae, t₀, q₀, p₀)
    @test idae == similar(idae, q₀, p₀)

    ################################################################################
    # Test VDAE: Variational Differential Algebraic Equation
    ################################################################################

    function vdae_ψ(t, q, p, λ, μ, ψ)
        ψ[1] = μ[1] - λ[1]
    end

    vdae  = VDAE(eltype(q₀), 1, 1, 1, 1, iode_ϑ, iode_f, iode_g, iode_g, pdae_ϕ, vdae_ψ, t₀, q₀, p₀, λ₀, λ₀; v=iode_v)
    vdae1 = VDAE(iode_ϑ, iode_f, iode_g, iode_g, pdae_ϕ, vdae_ψ, t₀, q₀, p₀, λ₀, λ₀; v=iode_v)
    vdae2 = VDAE(iode_ϑ, iode_f, iode_g, iode_g, pdae_ϕ, vdae_ψ, t₀, q₀, p₀, λ₀; v=iode_v)
    vdae3 = VDAE(iode_ϑ, iode_f, iode_g, iode_g, pdae_ϕ, vdae_ψ, t₀, q₀, p₀; v=iode_v)
    vdae4 = VDAE(iode_ϑ, iode_f, iode_g, iode_g, pdae_ϕ, vdae_ψ, q₀, p₀; v=iode_v)

    @test vdae == vdae1
    @test vdae == vdae2
    @test vdae == vdae3
    @test vdae == vdae4

    @test hash(vdae1) == hash(vdae2)
    @test hash(vdae3) == hash(vdae4)

    @test vdae == similar(vdae, t₀, q₀, p₀, λ₀, λ₀)
    @test vdae == similar(vdae, t₀, q₀, p₀, λ₀)
    @test vdae == similar(vdae, t₀, q₀, p₀)
    @test vdae == similar(vdae, q₀, p₀)

    ################################################################################
    # Test SODE: Split Ordinary Differential Equation
    ################################################################################

    function f_sode_1(t, x, f)
        f[1] = x[1]
    end

    function f_sode_2(t, x, f)
        f[1] = x[1]^2
    end

    f_sode = (f_sode_1, f_sode_2)

    sode  = SODE(eltype(q₀), 1, 1, 1, f_sode, t₀, q₀)
    sode1 = SODE(f_sode, t₀, q₀)
    sode2 = SODE(f_sode, q₀)

    @test sode == sode1
    @test sode == sode2

    @test hash(sode1) == hash(sode2)

    @test sode == similar(sode, t₀, q₀)
    @test sode == similar(sode, q₀)

end


using AdvancedHMC.Adaptation: WelfordVar, NaiveVar, WelfordCov, NaiveCov, add_sample!, get_var, get_cov, reset!
using Test, LinearAlgebra, Distributions

# Check that the estimated variance is approximately correct.
@testset "Online v.s. naive v.s. true var/cov estimation" begin
    D = 10
    n_samples = 100_000

    var_welford = WelfordVar(D)
    var_naive = NaiveVar()
    var_estimators = [var_welford, var_naive]
    cov_welford = WelfordCov(D)
    cov_naive = NaiveCov()
    cov_estimators = [cov_welford, cov_naive]
    estimators = [var_estimators..., cov_estimators...]

    for dist in [MvNormal(zeros(D), ones(D)), Dirichlet(D, 1)]
        for _ = 1:n_samples
            s = rand(dist)
            for estimator in estimators
                add_sample!(estimator, s)
            end
        end

        @test get_var(var_welford) ≈ get_var(var_naive) atol=0.1D
        for estimator in var_estimators
            @test get_var(estimator) ≈ var(dist) atol=0.1D
        end

        @test get_cov(cov_welford) ≈ get_cov(cov_naive) atol=0.1D^2
        for estimator in cov_estimators
            @test get_cov(estimator) ≈ cov(dist) atol=0.1D^2
        end

        for estimator in estimators
            reset!(estimator)
        end
    end
end


###################################################################################################
# CONSTANTS
###################################################################################################
const LOG2 = log(2)                         # Ln(2)
const XCAL1 = [-1,1]                        # 𝒳 with N=1
const XCAL2 = [[-1,-1],[-1,1],[1,-1],[1,1]] # 𝒳 with N=2
###################################################################################################
# STRUCTS
###################################################################################################
mutable struct IntBitVec <: AbstractVector{Bool}
    data::UInt64
end
Base.size(::IntBitVec) = (64,)
@inline function Base.getindex(v::IntBitVec,i::Int)
   @boundscheck checkbounds(v, i)
   v.data & (1 << (i-1)) != 0
end
###################################################################################################
# USED FUNCTIONS
###################################################################################################
"""
`comp_ex([N1,...,NK],[α1,...,αK],β)`

Function that computes the mean methylation vector E[X] assuming the Ising model for a methylation
state vector of size `[N1,...,NK]` and parameters `[α1,...,αK]` and `β`.

# Examples
```julia-repl
julia> CpelAsm.comp_ex([4],[0.0],0.0)
4-element Array{Float64,1}:
 0.0
 0.0
 0.0
 0.0
```
"""
function comp_ex(n::Vector{Int64},a::Vector{Float64},b::Float64)::Vector{Float64}

    # Loop over all positions
    x = zeros(Int64,sum(n))
    y = zeros(Float64,sum(n))
    @inbounds for j in 1:sum(n)
        x[j] = 1
        y[j] = comp_lkhd(x,n,a,b)
        x[j] = 0
    end

    # Return
    return 2*y.-1

end # end comp_ex
"""
`comp_exx([N1,...,NK],[α1,...,αK],β;r=1)`

Function that computes mean methylation vector E[X_{i}X_{i+r}}] assuming the Ising model for a
methylation state vector of size `[N1,...,NK]` and parameters `[α1,...,αK]` and `β`.

# Examples
```julia-repl
julia> CpelAsm.comp_exx([4],[0.0],0.0)
3-element Array{Float64,1}:
 0.0
 2.220446049250313e-16
 0.0
```
"""
function comp_exx(n::Vector{Int64},a::Vector{Float64},b::Float64;r::Int64=1)::Vector{Float64}

    # Loop over all positions
    x = zeros(Int64,sum(n))
    y = zeros(Float64,sum(n)-r)
    @inbounds for j in 1:(sum(n)-r)
        x[j] = x[j+r] = 1
        y[j] = comp_lkhd(x,n,a,b) + comp_lkhd(-x,n,a,b)
        x[j] = x[j+r] = 0
    end

    # Return
    return 2*y.-1

end # end comp_exx
"""
    `comp_mml(Z,EX)`

Function that computes mean methylation level (MML) given at the CpG sites marked by binary vector
Z and given first order moment E[X] vector. This function is only used when not all CpG sites are
homozygous since it can compute MML over a subset of CpG sites.

# Examples
```julia-repl
julia> ex=CpelAsm.comp_ex([4],[0.0],0.0);
julia> CpelAsm.comp_mml(trues(4),ex)
0.5
```
"""
function comp_mml(z::BitArray{1},ex::Vector{Float64})::Float64

    # Return
    return abs(round(0.5/length(ex[z])*sum(ex[z])+0.5;digits=8))

end # end comp_mml
"""
    `comp_mml_∇([N1,...,NK],∇logZ)`

Function that computes mean methylation level (MML) over the entire haplotype. This function takes
advantage of the fact that E[S(X)]=Adot(θ), and it's much faster than `comp_mml`. This function is
only used when all CpG sites are homozygous.

# Examples
```julia-repl
julia> n=[1,1,1]; θ=[1.0,-1.0,1.0,0.0];
julia> ∇logZ = CpelAsm.get_grad_logZ(n,θ);
julia> CpelAsm.comp_mml_∇(n,∇logZ)
0.62693236
```
"""
function comp_mml_∇(n::Vector{Int64},∇logZ::Vector{Float64})::Float64

    # Return
    return abs(round(0.5*(1.0+1.0/sum(n)*sum(∇logZ[1:length(n)]));digits=8))

end # end comp_mml_∇
"""
    `comp_exlng(Z,[N1,...,NK],[α1,...,αK],β)`

Function that returns the third summand in the normalized methylation entropy (NME) defined
for the homozygous CpG sites, with positions determined by binary vector Z, assuming an
allele-specific vector with [N1,...,NK] CpG sites, with parameters [α1,...,αK] and β.

# Examples
```julia-repl
julia> n=[10]; z=trues(10); z[5]=false; α=[0.0]; β=0.0;
julia> CpelAsm.comp_exlng(z,n,α,β)
0.6931471805599452
```
"""
function comp_exlng(z::BitArray{1},n::Vector{Int64},a::Vector{Float64},b::Float64)::Float64

    # Find changes to/from 0 in x vector
    hetst = findall(isequal(-1),abs.(z[2:end]) - abs.(z[1:(end-1)])) .+ 1
    hetend = findall(isequal(1),abs.(z[2:end]) - abs.(z[1:(end-1)])) .+ 1

    # Determine whether it starts/finishes with true/false
    z[1]==false && pushfirst!(hetst,1)
    z[end]==false && push!(hetend,sum(n)+1)

    # Find subregion label for each CpG site
    subid = vcat([i*ones(Int64,n[i]) for i in 1:length(n)]...)

    # Loop over heterozygous stretches
    y = 0.0
    @inbounds for i=1:length(hetst)

        # Choose 𝒳 depending on boundary or not
        bound = hetst[i]==1 || hetend[i]==sum(n)+1 ? true : false
        xcal = bound ? XCAL1 : XCAL2

        # Figure out b (block IDs of heterozygous) and r (α indices)
        bid = subid[hetst[i]:(hetend[i]-1)]
        nhet = [count(x->x==id,bid) for id in unique(bid)]

        # Compute expectation
        for x in xcal

            # Find αp1 and αp2
            xaug = zeros(Int64,sum(n))
            if bound
                # If heterozygous CpG on boundary of X
                hetst[i]==1 ? xaug[hetend[i]]+=x : xaug[hetst[i]-1]+=x
                ap1 = hetst[i]==1 ? a[1] : 2.0*x*b+a[subid[hetst[i]]]
                ap2 = hetend[i]==sum(n)+1 ? a[end] : 2.0*x*b+a[subid[hetend[i]-1]]
            else
                # If heterozygous CpG not on boundary of X
                xaug[[hetst[i]-1,hetend[i]]]+=x
                ap1 = 2.0*x[1]*b+a[subid[hetst[i]]]
                ap2 = 2.0*x[2]*b+a[subid[hetend[i]-1]]
            end

            # Add contribution to total sum of expectations
            y += log(comp_g(nhet,a[unique(bid)],b,ap1,ap2))*comp_lkhd(xaug,n,a,b)

        end

    end

    # Return sum of all expectations
    return y

end # end comp_exlng
"""
    `comp_nme(Z,[N1,...,NK],[α1,...,αK],β,EX,EXX)`

Function that computes normalized methylation entropy (NME) over the CpG sites determined by binary
vector Z. This is done by assuming an Ising model for the allele-specific methylation state vector
of size `[N1,...,NK]`, parameters `[α1,...,αK]` and `β`. This function is used when not all CpG
sites are homozygous since it can compute NME over a subset of CpG sites.

# Examples
```julia-repl
julia> n=[10]; z=trues(sum(n)); z[5]=false; α=[0.0]; β=0.0;
julia> CpelAsm.comp_nme(z,n,α,β,CpelAsm.comp_ex(n,α,β),CpelAsm.comp_exx(n,α,β))
1.0
```
"""
function comp_nme(z::BitArray{1},n::Vector{Int64},a::Vector{Float64},b::Float64,ex::Vector{Float64},
                  exx::Vector{Float64})::Float64

    # Define output quantity
    h = 0.0

    # Vector of α for homozygous
    avec = vcat([a[i]*ones(Float64,n[i]) for i in 1:length(n)]...)[z]

    # 1st term (log partition function)
    h += log(comp_Z(n,a,b))

    # 2nd term (average potential energy over homozygous CpG sites)
    h -= avec'*ex[z]+b*sum(exx[z[1:(end-1)].*z[2:end]])

    # 3rd term if necessary
    h -= all(z) ? 0.0 : comp_exlng(z,n,a,b)

    # Return nme
    return abs(round(h/(sum(z)*LOG2);digits=8))

end # end comp_nme
"""
    `comp_nme_∇([N1,...,NK],θ,∇logZ)`

Function that computes normalized methylation entropy (NME) using the ∇logZ information. This
function can only be used in `comp_tnull` since it does not allow computation on a subset of CpG
sites. This function is only used when all CpG sites are homozygous.

# Examples
```julia-repl
julia> n=[4]; α=[0.0]; β=0.0; θ=vcat([α,β]...); ∇logZ=CpelAsm.get_grad_logZ(n,θ);
julia> CpelAsm.comp_nme_∇(n,θ,∇logZ)
1.0
```
"""
function comp_nme_∇(n::Vector{Int64},θ::Vector{Float64},∇logZ::Vector{Float64})::Float64

    # Return
    return abs(round(1.0/(sum(n)*LOG2)*(log(comp_Z(n,θ[1:(end-1)],θ[end]))-θ'*∇logZ);digits=8))

end # end comp_nme_∇
"""
    `gen_x_mc(N,α,β)`

Function that generates a methylation vector from an Ising model with `[N1,...,NK]`, and parameters
`[α1,...,αK]` and `β`.

# Examples
```julia-repl
julia> Random.seed!(1234);
julia> CpelAsm.gen_x_mc([5],[0.0],0.0)
5-element Array{Int64,1}:
  1
  1
  1
 -1
  1
```
"""
function gen_x_mc(n::Vector{Int64},a::Vector{Float64},b::Float64)::Vector{Int64}

    # Find subregion label for each CpG site
    subid = vcat([i*ones(Int64,n[i]) for i in 1:length(n)]...)

    # Sample first CpG site
    x = Vector{Int64}()
    p = comp_lkhd(pushfirst!(zeros(Int64,sum(n)-1),1),n,a,b)
    push!(x,sample(XCAL1,Weights([1-p,p])))
    sum(n)>1 || return x

    # Sequentially sample from Markov chain
    @inbounds for i=2:sum(n)

        # Add boundary function g() if necessary
        expaux = exp(a[subid[i]]+b*x[i-1])
        if i < sum(n)
            ap1p = 2.0*b + a[subid[i]]
            ap1m = -2.0*b + a[subid[i]]
            n_miss = [count(x->x==sr,subid[(i+1):sum(n)]) for sr in unique(subid[(i+1):sum(n)])]
            gp = comp_g(n_miss,a[unique(subid[(i+1):sum(n)])],b,ap1p,a[end])
            gm = comp_g(n_miss,a[unique(subid[(i+1):sum(n)])],b,ap1m,a[end])
            p = gp*expaux/(gp*expaux+gm/expaux)
        else
            p = expaux/(expaux+1.0/expaux)
        end

        # Add i-th CpG site
        push!(x,sample(XCAL1,Weights([1-p,p])))

    end

    # Return methylation vector
    return x

end # end gen_x_mc
"""
    `comp_nme_mix_mc(Z1,Z2,N1,N2,theta1,theta2)`

Function that estimates the entropy for the Ising mixture model using Monte Carlo. This is done
assuming an Ising model for the allele-specific methylation state vector of size `[N1,...,NK]`,
parameters `[α1,...,αK]` and `β`, for each allele. The homozygous part of each model is determined
by binary vector Z (i.e., via Hadamard product Z*X, where * is the Hadamard product).

# Examples
```julia-repl
julia> Random.seed!(1234);
julia> α1=[0.0]; β1=0.0; α2=[1.0]; β2=1.0;
julia> n1=[10]; n2=[10]; z1=trues(sum(n1)); z2=trues(sum(n2));
julia> CpelAsm.comp_nme_mix_mc(z1,z2,n1,n2,vcat(α1,β1),vcat(α2,β2))
0.6278665195419888
```
"""
function comp_nme_mix_mc(z1::BitArray{1},z2::BitArray{1},n1::Vector{Int64},n2::Vector{Int64},
                         t1::Vector{Float64},t2::Vector{Float64};L::Int64=1000)::Float64

    # For loop to sample from mixture
    h = 0.0
    @inbounds for i=1:L

        # Sample allele
        y = sample(1:2,Weights([0.5,0.5]))

        # Sample x given allele
        x = y==1 ? gen_x_mc(n1,t1[1:(end-1)],t1[end]) : gen_x_mc(n2,t2[1:(end-1)],t2[end])
        x0 = y==1 ? x[z1] : x[z2]

        # Add contribution
        x1 = zeros(Int64,sum(n1))
        x2 = zeros(Int64,sum(n2))
        x1[z1] = x0
        x2[z2] = x0
        h += log(comp_lkhd(x1,n1,t1[1:(end-1)],t1[end])+comp_lkhd(x2,n2,t2[1:(end-1)],t2[end]))

    end

    # Return
    return min(1.0,max(0.0,1.0/sum(z1)*(1.0-h/(L*LOG2))))

end # end comp_nme_mix_mc
"""
    `comp_nme_mix_exact(Z1,Z2,N1,N2,theta1,theta2)`

Function that exactly computes the NME for the Ising mixture model. This is done by assuming an
Ising model for the allele-specific methylation state vector of size `[N1,...,NK]`, parameters
`[α1,...,αK]` and `β`. The homozygous part of the allele-specific vector is determined by binary
vector Z (i.e., via Hadamard product Z*X, where * is the Hadamard product). This is done by
traversing 𝒳h.

# Examples
```julia-repl
julia> n1=[10]; n2=[10];
julia> α1=[0.0]; β1=0.0; α2=[1.0]; β2=1.0;
julia> z1=trues(sum(n1)); z2=trues(sum(n2));
julia> CpelAsm.comp_nme_mix_exact(z1,z2,n1,n2,vcat(α1,β1),vcat(α2,β2))
0.6239489595165986
```
"""
function comp_nme_mix_exact(z1::BitArray{1},z2::BitArray{1},n1::Vector{Int64},n2::Vector{Int64},
                            t1::Vector{Float64},t2::Vector{Float64})::Float64

    # Loop over 𝒳h
    h = 0.0
    n = sum(z1)
    w1 = zeros(Int64,sum(n1))
    w2 = zeros(Int64,sum(n2))
    @inbounds for i=1:(2^n)
        z = IntBitVec(i)[1:n]
        x = -ones(Int64,n)
        x[z] .= 1
        w1[z1] =+ x
        w2[z2] =+ x
        lkhd1 = comp_lkhd(w1,n1,t1[1:(end-1)],t1[end])
        lkhd2 = comp_lkhd(w2,n2,t2[1:(end-1)],t2[end])
        h += (lkhd1+lkhd2) * log(lkhd1+lkhd2)
        w1[z1] =- x
        w2[z2] =- x
    end

    # Return
    return min(1.0,max(0.0,1.0/n*(1.0-0.5*h/LOG2)))

end # end comp_nme_mix_exact
"""
    `comp_uc(Z1,Z2,N1,N2,theta1,theta2,h1,h2)`

Function that exactly computes UC over the homozygous CpG sites. This is done by assuming an Ising
model for the allele-specific methylation state vector of size `[N1,...,NK]`, parameters
`[α1,...,αK]` and `β`. The homozygous part of the allele-specific vector is determined by binary
vector Z (i.e., via Hadamard product Z*X, where * is the Hadamard product).

# Examples
```julia-repl
julia> n1=[20]; n2=[20];
julia> z1=trues(sum(n1)); z2=trues(sum(n2));
julia> α1=[2.5]; α2=[2.5]; β1=0.0; β2=0.0;
julia> h1=CpelAsm.comp_nme(z1,n1,α1,β1,CpelAsm.comp_ex(n1,α1,β1),CpelAsm.comp_exx(n1,α1,β1));
julia> h2=CpelAsm.comp_nme(z2,n2,α2,β2,CpelAsm.comp_ex(n2,α2,β2),CpelAsm.comp_exx(n2,α2,β2));
julia> CpelAsm.comp_uc(z1,z2,n1,n2,vcat(α1,β1),vcat(α2,β2),h1,h2)
0.63304398
```
"""
function comp_uc(z1::BitArray{1},z2::BitArray{1},n1::Vector{Int64},n2::Vector{Int64},
                  t1::Vector{Float64},t2::Vector{Float64},h1::Float64,h2::Float64)::Float64

    # Compute h(x)
    h = sum(z1)<17 ? comp_nme_mix_exact(z1,z2,n1,n2,t1,t2) : comp_nme_mix_mc(z1,z2,n1,n2,t1,t2)
        # print_log("h: $(h)")

    # If H(X)≈0 then UC=0
    uc = isapprox(h,0.0,atol=1e-5) ? 0.0 : min(1.0,max(0.0,1.0-0.5*(h1+h2)/h))

    # Return
    return round(uc;digits=8)

end # end comp_uc
###################################################################################################
# DEV FUNCTIONS
###################################################################################################
"""
    `comp_mmlv([N1,...,NK],∇logZ)`

Function that computes the mean methylation level vector (MMLV) for each subregion. Reporting
output for subregion might be more informative than just an overall mean. This function takes
advantage of the fact that E[S(X)]=Adot(θ), which is much faster than computing first moment
and correlations as currently done.

# Examples
```julia-repl
julia> n=[1,1,1]; θ=[1.0,-1.0,1.0,0.0];
julia> ∇logZ = CpelAsm.get_grad_logZ(n,θ);
julia> CpelAsm.comp_mmlv(n,∇logZ)
3-element Array{Float64,1}:
 0.88079708
 0.11920292
 0.88079708
```
"""
function comp_mmlv(n::Vector{Int64},∇logZ::Vector{Float64})::Vector{Float64}

    # Return
    return abs.(round.(0.5.*(1.0./n.*∇logZ[1:length(n)].+1.0);digits=8))

end # end comp_mmlv
"""
    `comp_corr(EX,EXX)`

Function that returns the correlation vector between consecutive CpG sites given the vector of
first order moments E[X] and second order moments E[XX].

# Examples
```julia-repl
julia> CpelAsm.comp_corr(CpelAsm.comp_ex([4],[0.0],0.0),CpelAsm.comp_exx([4],[0.0],0.0))
3-element Array{Float64,1}:
 0.0
 0.0
 0.0
```
"""
function comp_corr(ex::Vector{Float64},exx::Vector{Float64})::Vector{Float64}

    # Initialize vector of 1's
    ov = ones(Float64,length(ex)-1)

    # Return ρ
    return round.((exx.-ex[1:(end-1)].*ex[2:end]) ./ sqrt.((ov.-ex[1:(end-1)].^2) .*
    (ov.-ex[2:end].^2));digits=8)

end # end comp_corr
"""
    `comp_cov([N1,...,NK],[α1,...,αK],β,EX,EXX)`

Function that returns the covariance matrix of a methylation vector given the `[N1,...,NK]`,
`[α1,...,αK]`, `β`, E[X], and E[XX].

# Examples
```julia-repl
julia> n=[4]; α=[1.0]; β=1.0;
julia> ex = CpelAsm.comp_ex(n,α,β);
julia> exx = CpelAsm.comp_exx(n,α,β);
julia> CpelAsm.comp_cov(n,α,β,ex,exx)
4×4 Array{Float64,2}:
 0.419974     0.0          4.44089e-16  3.33067e-16
 0.0          0.419974     1.11022e-16  4.44089e-16
 4.44089e-16  1.11022e-16  0.419974     0.0
 3.33067e-16  4.44089e-16  0.0          0.419974
```
"""
function comp_cov(n::Vector{Int64},a::Vector{Float64},b::Float64,ex::Vector{Float64},
                  exx::Vector{Float64})::Array{Float64,2}

    # Initialize matrix
    ntot = sum(n)
    cov = zeros(Float64,ntot,ntot)

    # Add first subdiagonal E[X_{i}X_{i+1}]
    cov[2:(ntot+1):((ntot-1)*ntot)] = exx

    # Add covariance terms E[X_{i}X_{i+k-1}]
    @inbounds for k=3:ntot
        cov[k:(ntot+1):((ntot-k+1)*ntot)] = comp_exx(n,a,b;r=k-1)
    end

    # Symmetrize
    cov += transpose(cov)

    # Substract E[X_i]E[X_j]
    cov -= ex * ex'

    # Add 1's to diagonal of matrix
    cov[1:(ntot+1):ntot^2] += ones(Float64,ntot)

    # Return Σ
    return cov

end # end comp_cov
"""
    `comp_evec(Σ)`

Function that returns the eigenvector associated with the largest eigenvalue of the covariance
matrix Σ. The components of the vector with the largest magnitude highlight the CpG sites in the
region with the largest amount of variability.

# Examples
```julia-repl
julia> n=[4]; α=[1.0]; β=1.0;
julia> ex = CpelAsm.comp_ex(n,α,β);
julia> exx = CpelAsm.comp_exx(n,α,β);
julia> Σ = CpelAsm.comp_cov(n,α,β,ex,exx);
julia> CpelAsm.comp_evec(Σ)
4-element Array{Float64,1}:
 0.6867257391267225
 0.16854601514378278
 0.16854601514379314
 0.6867257391267322
```
"""
function comp_evec(cov::Array{Float64,2})::Vector{Float64}

    # Return evec
    return abs.(eigvecs(cov)[:,size(cov)[1]])

end # end comp_evec
###################################################################################################
# VERIFICATION FUNCTIONS (NOT USED)
###################################################################################################
"""
    `comp_nme_xcal(Z,[N1,...,NK],[α1,...,αK],β,EX,EXX)`

Function that computes normalized methylation entropy (NME) over the homozygous CpG sites. This is
done by assuming an Ising model for the allele-specific methylation state vector of size
`[N1,...,NK]`, parameters `[α1,...,αK]` and `β`. The homozygous part of the allele-specific vector
is determined by binary vector Z (i.e., via Hadamard product Z*X, where * is the Hadamard product).

# Examples
```julia-repl
julia> n=[10]; z=trues(sum(n)); α=[0.0]; β=0.0;
julia> CpelAsm.comp_nme_xcal(z,n,α,β)
1.0
```
"""
function comp_nme_xcal(z::BitArray{1},n::Vector{Int64},a::Vector{Float64},b::Float64)::Float64

    # Loop over 𝒳h
    h = 0.0
    w = zeros(Int64,sum(n))
    hom_ind = findall(y->y==true,z)
    xcal = generate_xcal(length(hom_ind))
    @inbounds for x in xcal
        w[hom_ind] =+ x
        lkhd = comp_lkhd(w,n,a,b)
        h += lkhd * log(lkhd)
        w[hom_ind] =- x
    end

    # Return
    return round(-h/(sum(z)*LOG2);digits=8)

end # end comp_nme_xcal
"""
    `comp_uc_xcal(Z1,Z2,N1,N2,theta1,theta2)`

Function that exactly computes the UC over the homozygous CpG sites. This is done by assuming an
Ising model for the allele-specific methylation state vector of size `[N1,...,NK]`, parameters
`[α1,...,αK]` and `β`. The homozygous part of the allele-specific vector is determined by binary
vector Z (i.e., via Hadamard product Z*X, where * is the Hadamard product).

# Examples
```julia-repl
julia> n1=[10]; n2=[10];
julia> z1=trues(sum(n1)); z2=trues(sum(n2));
julia> θ1=[2.0,0.0]; θ2=[-2.0,0.0];
julia> CpelAsm.comp_uc_xcal(z1,z2,n1,n2,θ1,θ2)
0.43482166257188215
```
"""
function comp_uc_xcal(z1::BitArray{1},z2::BitArray{1},n1::Vector{Int64},n2::Vector{Int64},
                      theta1::Vector{Float64},theta2::Vector{Float64})::Float64

    # Loop over 𝒳h
    num = 0.0
    den = 0.0
    n = sum(z1)
    w1 = zeros(Int64,sum(n1))
    w2 = zeros(Int64,sum(n2))
    hom_ind_1 = findall(y->y==true,z1)
    hom_ind_2 = findall(y->y==true,z2)
    @inbounds for i=1:(2^n)
        z = IntBitVec(i)[1:n]
        x = -ones(Int64,n)
        x[z] .= 1
        w1[hom_ind_1] =+ x
        w2[hom_ind_2] =+ x
        lkhd1 = comp_lkhd(w1,n1,theta1[1:(end-1)],theta1[end])
        lkhd2 = comp_lkhd(w2,n2,theta2[1:(end-1)],theta2[end])
        lkhd0 = 0.5*(lkhd1+lkhd2)
        den += lkhd0 * log(lkhd0)
        num += lkhd1 * log(lkhd1) + lkhd2 * log(lkhd2)
        w1[hom_ind_1] =- x
        w2[hom_ind_2] =- x
    end

    # Return
    return 1-0.5*num/den

end # end comp_uc_xcal
###################################################################################################
# COMPETING MODELS
###################################################################################################
"""
    `comp_lkhd_bin(XOBS,θ)`

Function that returns likelihood vector of XOBS, potentially partial, given parameter vector θ for
the non-parametric independent model (binomial).

"""
function comp_lkhd_bin(xobs::Array{Vector{Int64},1},θ::Vector{Float64})::Vector{Float64}

    # Return evec
    return [prod(θ.^(x.==1) .* (ones(length(xobs[1]))-θ).^(x.==-1)) for x in xobs]

end # end comp_lkhd_bin
"""
    `comp_lkhd_mult(XOBS,θ)`

Function that returns likelihood vector of XOBS, assuming full data, given parameter vector θ for
the non-parametric dependent model (multinomial).

"""
function comp_lkhd_mult(xobs::Array{Vector{Int64},1},θ::Vector{Float64})::Vector{Float64}

    # Generate 𝒳
    xcal = generate_xcal(length(xobs[1]))

    # Return evec
    return θ[[findfirst(y->y==x,xcal) for x in xobs]]

end # end comp_lkhd_mult


# SPDX-License-Identifier: MIT

const PdfIndex = Union{Int,Nothing}
const Label = Union{AbstractString,Nothing}

mutable struct State{T<:Semifield}
    id::Int
    initweight::T
    finalweight::T
    pdfindex::PdfIndex
    label::Label
end

isinit(s::State{T}) where T = s.initweight ≠ zero(T)
isfinal(s::State{T}) where T = s.finalweight ≠ zero(T)
islabeled(s::State) = ! isnothing(s.label)
isemitting(s::State)  = ! isnothing(s.pdfindex)
setinit!(s::State{T}, weight::T = one(T)) where T = s.initweight = weight
setfinal!(s::State{T}, weight::T = one(T)) where T = s.finalweight = weight

mutable struct Link{T<:Semifield}
    dest::State
    weight::T
end

"""
    struct FSM{T<:Semifield}
        states # vector of states
        links # Dict state -> vector of links
    end

Probabilistic finite state machine.
"""
struct FSM{T<:Semifield}
    states::Vector{State{T}}
    links::Dict{State, Vector{Link{T}}}
end
FSM{T}() where T = FSM{T}(State{T}[], Dict{State, Vector{Link{T}}}())
FSM() = FSM{LogSemifield{Float64}}()
fsmtype(::FSM{T}) where T<:Semifield = T

states(fsm::FSM) = fsm.states
links(fsm::FSM{T}, state::State{T}) where T = get(fsm.links, state, Link{T}[])

function addstate!(fsm::FSM{T}; initweight = zero(T), finalweight = zero(T),
                   pdfindex = nothing, label = nothing) where T
    s = State(length(fsm.states)+1, initweight, finalweight, pdfindex, label)
    push!(fsm.states, s)
    s
end

function link!(fsm::FSM{T}, src::State{T}, dest::State{T}, weight::T = one(T)) where T
    list = get(fsm.links, src, Link{T}[])
    link = Link{T}(dest, weight)
    push!(list, link)
    fsm.links[src] = list
    link
end

function Base.show(io, ::MIME"image/svg+xml", fsm::FSM)
    dotpath, dotfile = mktemp()
    svgpath, svgfile = mktemp()

    write(dotfile, "Digraph {\n")
    write(dotfile, "rankdir=LR;")

    for s in states(fsm)
        name = "$(s.id)"
        label = islabeled(s) ? "$(s.label)" : "ϵ"
        label *= isemitting(s) ? ":$(s.pdfindex)" : ":ϵ"
        if s.initweight ≠ zero(typeof(s.initweight))
            weight = round(convert(Float64, s.initweight), digits = 3)
            label *= "/$(weight)"
        end
        if s.finalweight ≠ zero(typeof(s.finalweight))
            weight = round(convert(Float64, s.finalweight), digits = 3)
            label *= "/$(weight)"
        end
        attrs = "shape=" * (isfinal(s) ? "doublecircle" : "circle")
        attrs *= " penwidth=" * (isinit(s) ? "2" : "1")
        attrs *= " label=\"" * label * "\""
        attrs *= " style=filled fillcolor=" * (isemitting(s) ? "lightblue" : "none")
        write(dotfile, "$name [ $attrs ];\n")
    end

    for src in states(fsm)
        for link in links(fsm, src)
            weight = round(convert(Float64, link.weight), digits = 3)
            srcname = "$(src.id)"
            destname = "$(link.dest.id)"
            write(dotfile, "$srcname -> $destname [ label=\"$(weight)\" ];\n")
        end
    end
    write(dotfile, "}\n")
    close(dotfile)
    run(`dot -Tsvg $(dotpath) -o $(svgpath)`)

    xml = read(svgfile, String)
    write(io, xml)

    close(svgfile)

    rm(dotpath)
    rm(svgpath)
end

#======================================================================
FSM operations
======================================================================#

"""
    union(fsm1, fsm2, ...)

Merge all the fsms into a single one.
"""
function Base.union(fsm1::FSM{T}, fsm2::FSM{T}) where T
    allstates = union(states(fsm1), states(fsm2))
    newfsm = FSM{T}()

    smap = Dict()
    for state in allstates
        smap[state] = addstate!(newfsm, label = state.label,
                                pdfindex = state.pdfindex,
                                initweight = state.initweight,
                                finalweight = state.finalweight)
    end

    for src in states(fsm1)
        for link in links(fsm1, src)
            link!(newfsm, smap[src], smap[link.dest], link.weight)
        end
    end

    for src in states(fsm2)
        for link in links(fsm2, src)
            link!(newfsm, smap[src], smap[link.dest], link.weight)
        end
    end

    newfsm
end
Base.union(f::FSM{T}, o::FSM{T}...) where T = foldl(union, o, init = f)

"""
    renormalize!(fsm)

Ensure the that all the weights of all the outgoing arcs leaving a
state sum up to 1.
"""
function renormalize!(fsm::FSM{T}) where T
    total = zero(T)
    for s in filter(isinit, states(fsm)) total += s.initweight end
    for s in filter(isinit, states(fsm)) s.initweight /= total end

    for src in states(fsm)
        total = src.finalweight
        for link in links(fsm, src) total += link.weight end
        for link in links(fsm, src) link.weight /= total end
        src.finalweight /= total
    end

    fsm
end

"""
    replace(fsm, subfsms, delim = "!")

Replace the state in `fsm` wiht a sub-fsm from `subfsms`. The pairing
is done with the last tone of `label` of the state, i.e. the state
with label `a!b!c` will be replaced by `subfsms[c]`. States that don't
have matching labels are left untouched.
"""
function Base.replace(fsm::FSM{T}, subfsms::Dict, delim = "!") where T
    newfsm = FSM{T}()

    matchlabel = label -> isnothing(label) ? nothing : split(label, delim)[end]

    smap_in = Dict()
    smap_out = Dict()
    for s in states(fsm)
        if matchlabel(s.label) in keys(subfsms)
            smap = Dict()
            for cs in states(subfsms[matchlabel(s.label)])
                label = "$(s.label)$(delim)$(cs.label)"
                ns = addstate!(newfsm, pdfindex = cs.pdfindex, label = label,
                               initweight = s.initweight * cs.initweight,
                               finalweight = s.finalweight * cs.finalweight)
                smap[cs] = ns

                if isinit(cs) smap_in[s] = ns end
                if isfinal(cs) smap_out[s] = ns end
            end

            for cs in states(subfsms[matchlabel(s.label)])
                for link in links(subfsms[matchlabel(s.label)], cs)
                    link!(newfsm, smap[cs], smap[link.dest], link.weight)
                end
            end

        else
            ns = addstate!(newfsm, pdfindex = s.pdfindex, label = s.label,
                           initweight = s.initweight, finalweight = s.finalweight)
            smap_in[s] = ns
            smap_out[s] = ns
        end
    end

    for osrc in states(fsm)
        weight = one(T)
        if matchlabel(osrc.label) in keys(subfsms)
            finals = filter(isfinal, states(subfsms[matchlabel(osrc.label)]))
            weight = sum(map(s->s.finalweight, finals))
        end
        for link in links(fsm, osrc)
            src = smap_out[osrc]
            dest = smap_in[link.dest]
            link!(newfsm, src, dest, link.weight * weight)
        end
    end

    newfsm
end

function _unique_labels(statelist, T, step; init = true)
    labels = Dict()
    for (s, w) in statelist
        lstates, iw, fw, tw = get(labels, (s.label, step), (Set(), zero(T), zero(T), zero(T)))
        push!(lstates, s)
        labels[(s.label, step)] = (lstates, iw+s.initweight, fw+s.finalweight, tw+w)
    end

    # Inverse the map so that the set of states is the key.
    retval = Dict()
    for (key, value) in labels
        retval[value[1]] = (key[1], value[2], value[3], value[4], init)
    end
    retval
end

"""
    determinize(fsm)

Determinize the FSM w.r.t. the state labels.
"""
function determinize(fsm::FSM{T}) where T
    newfsm = FSM{T}()
    smap = Dict()
    newlinks = Dict()

    initstates = [(s, zero(T)) for s in filter(isinit, collect(states(fsm)))]
    queue = _unique_labels(initstates, T, 0, init = true)
    while ! isempty(queue)
        key, value = pop!(queue)
        lstates = key
        label, iw, fw, tw, init = value
        step = 0

        if key ∉ keys(smap)
            if init
                s = addstate!(newfsm, label = label, initweight = iw, finalweight = fw)
            else
                s = addstate!(newfsm, label = label, finalweight = fw)
            end
            smap[key] = s
        end

        nextstates = []
        for ls in lstates
            for link in links(fsm, ls)
                push!(nextstates, (link.dest, link.weight))
            end
        end

        nextlabels = _unique_labels(nextstates, T, step+1, init = false)
        for (key2, value2) in nextlabels
            w = get(newlinks, (key,key2), zero(T))
            newlinks[(key,key2)] = w+value2[end]
        end
        queue = merge(queue, nextlabels)
    end

    for (key, value) in newlinks
        src = smap[key[1]]
        dest = smap[key[2]]
        weight = value
        link!(newfsm, src, dest, weight)
    end

    newfsm
end

"""
    transpose(fsm)

Reverse the direction of the arcs.
"""
function Base.transpose(fsm::FSM{T}) where T
    newfsm = FSM{T}()
    smap = Dict()
    for s in states(fsm)
        ns = addstate!(newfsm, label = s.label, initweight = s.finalweight,
                       finalweight = s.initweight, pdfindex = s.pdfindex)
        smap[s] = ns
    end

    for src in states(fsm)
        for link in links(fsm, src)
            link!(newfsm, smap[link.dest], smap[src], link.weight)
        end
    end

    newfsm
end

"""
    minimize(fsm)

Return a minimal equivalent fsm.
"""
minimize(fsm::FSM{T}) where T = (transpose ∘ determinize ∘ transpose ∘ determinize)(fsm)

"""
	eps_closure!(fsm, state, closure; [weight=1], [visited=[]])

Find eps closure from `state` in `fsm`.
"""
function eps_closure!(
        fsm::FSM{T}, state::State, closure::Vector;
        weight::T=one(T), visited::Vector{State} = State[]
) where T <: Semifield

    if state in visited
        return closure
    end
	push!(visited, state)

    for l in links(fsm, state)
        if isemitting(l.dest)
            push!(closure, (l.dest, l.weight * weight))
        else
            eps_closure!(fsm, l.dest, closure; weight=l.weight * weight, visited=visited)
        end
    end
    return closure
end

"""
	remove_eps(fsm)

Removes non-emitting states from `fsm`.
"""
function remove_eps(fsm::FSM{T}) where T <: Semifield
    nfsm = FSM{T}()
    smap = Dict{State, State}()
    eps_states = []
    eps_closures = Dict{State, Vector}()
    for s in states(fsm)
        if isemitting(s)
            smap[s] = addstate!(nfsm;
                initweight=s.initweight, finalweight=s.finalweight,
                pdfindex=s.pdfindex, label=s.label)
        else
            push!(eps_states, s)
        end
    end

    for eps in eps_states
        closure = eps_closure!(fsm, eps, [])
        eps_closures[eps] = unique!(closure)
    end

    for s in states(fsm)
        for l in links(fsm, s)
            if isemitting(s) && isemitting(l.dest)
                link!(nfsm, smap[s], smap[l.dest], l.weight)
            elseif isemitting(s)
                for (ns, w) in eps_closures[l.dest]
                    link!(nfsm, smap[s], smap[ns], l.weight * w)
                end
            end
        end
    end
    return nfsm
end


using NonUniformRandomVariateGeneration
using Distributions
using BenchmarkTools
using Random
using StatsBase

function benchNative1(n::Int64, ps::Vector{Float64}, N::Int64)
  v::Vector{Int64} = Vector{Int64}(undef, n)
  for i in 1:N
    sampleCategoricalSorted!(v, ps)
  end
end

function benchNative2(n::Int64, ps::Vector{Float64}, N::Int64)
  v::Vector{Int64} = Vector{Int64}(undef, n)
  scratch1::Vector{Float64} = Vector{Float64}(undef, n)
  scratch2::Vector{Float64} = Vector{Float64}(undef, length(ps))
  for i in 1:N
    sampleCategoricalSorted!(v, ps, scratch1, scratch2)
  end
  return v
end

function benchDistributions(n::Int64, ps::Vector{Float64}, N::Int64)
  multinom = Distributions.Multinomial(n, ps)
  v::Vector{Int64} = Vector{Int64}(undef, n)
  a = Vector{Int64}(undef, length(ps))
  for i in 1:length(ps)
    a[i] = i
  end
  ws = StatsBase.weights(ps)
  for i in 1:N
    Distributions.sample!(a, ws, v)
  end
  return v
end

n = 10000
m = 1000
ps = exp.(randexp(m))
ps ./= sum(ps)
N = 1024

@btime benchNative1(n, ps, N)
@btime benchNative2(n, ps, N)
@btime benchDistributions(n, ps, N)


### SIMULATION FUNCTIONS ###

"""
    simulate_step(filter::AbstractFilter, x::AbstractVector, u::AbstractVector, rng::AbstractRNG=Random.GLOBAL_RNG)

Run a step of simulation starting at state x, taking action u, and using the
motion and measurement equations specified by the filter.
"""
function simulate_step(filter::AbstractFilter, x::AbstractVector{<:Number},
                        u::AbstractVector{<:Number}, rng::AbstractRNG=Random.GLOBAL_RNG)

    xn = predict(filter.d, x, u, rng)

    yn = measure(filter.o, xn, u, rng)

    return xn, yn
end

"""
    simulation(filter::AbstractFilter, b0::GaussianBelief,
                action_sequence::Vector{AbstractVector}})

Run a simulation to get positions and measurements. Samples starting point from
GaussianBelief b0, the runs action_sequence with additive gaussian noise all
specified by AbstractFilter filter to return a simulated state and measurement
history.
"""
function simulation(filter::AbstractFilter, b0::GaussianBelief,
                    action_sequence::Vector{<:AbstractArray},
                    rng::AbstractRNG = Random.GLOBAL_RNG)

    # make initial state
    s0 = rand(rng, b0)

    # simulate action sequence
    state_history = [s0]
    measurement_history = Vector{AbstractVector{typeof(s0[1])}}()
    for u in action_sequence
        xn, yn = simulate_step(filter, state_history[end], u)
        push!(state_history, xn)
        push!(measurement_history, yn)
    end

    ## TODO: Is it worth separating out start state from state_history
    return state_history, measurement_history
end

"""
    run_filter(filter::AbstractFilter, b0::GaussianBelief, action_history::Vector{AbstractVector},
            measurement_history::Vector{AbstractVector})

Given an initial belief b0, matched-size arrays for action and measurement
histories and a filter, update the beliefs using the filter, and return a
vector of all beliefs.
"""
function run_filter(filter::AbstractFilter, b0::GaussianBelief, action_history::Vector{A},
            measurement_history::Vector{B}) where {A<:AbstractVector, B<:AbstractVector}

        # assert matching action and measurement sizes
        @assert length(action_history) == length(measurement_history)

        # initialize belief vector
        beliefs = [b0]

        # iterate through and update beliefs
        for (u, y) in zip(action_history, measurement_history)
            bn = update(filter, beliefs[end], u, y)
            push!(beliefs, bn)
        end

        return beliefs
end

"""
    unpack(belief_history::Vector{<:GaussianBelief};
        dims::Vector{Int}=[])

Given a history of beliefs, return an unpacked (time steps, state dim)-sized array of
predicted means and a (time steps, state dim, state dim)-sized array of
covariances. One can optionally specify dimensions indices dims to output
reduced state information.
"""
function unpack(belief_history::Vector{<:GaussianBelief};
    dims::Vector{Int}=Vector{Int}())

    # set default to condense all dimensions
    if length(dims) == 0
        dims = collect(1:length(belief_history[1].μ))
    end

    # set output sizes
    μ = zeros(typeof(belief_history[1].μ[1]),
        length(belief_history), length(dims))
    Σ = zeros(typeof(belief_history[1].μ[1]),
        length(belief_history), length(dims), length(dims))

    # iterate over belief_history and place elements appropriately
    for (i, belief) in enumerate(belief_history)
        μ[i,:] = belief.μ[dims]
        Σ[i,:,:] = belief.Σ[dims,dims]
    end

    return μ, Σ
end
### TODO: add in-place update!, predict!, and measure! functions


module NonlinearOperatorsTests

using Test
using Gridap.Algebra
using Gridap.Algebra: NonlinearOperatorMock

op = NonlinearOperatorMock()

x = [1.0, 3.0]
b = [0.0,0.0]
A = [-1.0 0.0; 0.0 1.0]

test_nonlinear_operator(op,x,b,jac=A)

@test residual(op,x) ≈ b
@test jacobian(op,x) ≈ A
b1, A1 = residual_and_jacobian(op,x)
@test b1 ≈ b
@test A1 ≈ A

end # module


let
    # before testing the model, check whether missing/null data is rejected
    tdata = [rand(1:10, 5)..., missing]
    @test_throws ErrorException("Data contains `missing` values and a fit cannot be performed") LsqFit.check_data_health(tdata, tdata)
    tdata = [rand(1:10, 5)..., Inf]
    @test_throws ErrorException("Data contains `Inf` or `NaN` values and a fit cannot be performed") LsqFit.check_data_health(tdata, tdata)
    tdata = [rand(1:10, 5)..., NaN]
    @test_throws ErrorException("Data contains `Inf` or `NaN` values and a fit cannot be performed") LsqFit.check_data_health(tdata, tdata)

    # fitting noisy data to an exponential model
    # TODO: Change to `.-x` when 0.5 support is dropped
    model(x, p) = p[1] .* exp.(-x .* p[2])

    # some example data
    Random.seed!(12345)
    xdata = range(0, stop=10, length=20)
    ydata = model(xdata, [1.0, 2.0]) + 0.01*randn(length(xdata))
    p0 = [0.5, 0.5]

    for ad in (:finite, :forward, :forwarddiff)
        fit = curve_fit(model, xdata, ydata, p0; autodiff = ad)
        @assert norm(fit.param - [1.0, 2.0]) < 0.05
        @test fit.converged

        # can also get error estimates on the fit parameters
        errors = margin_error(fit, 0.1)
        @assert norm(errors - [0.017, 0.075]) < 0.01
    end
    # if your model is differentiable, it can be faster and/or more accurate
    # to supply your own jacobian instead of using the finite difference
    function jacobian_model(x,p)
        J = Array{Float64}(undef, length(x), length(p))
        J[:,1] = exp.(-x.*p[2])     #dmodel/dp[1]
        J[:,2] = -x.*p[1].*J[:,1]           #dmodel/dp[2]
        J
    end
    jacobian_fit = curve_fit(model, jacobian_model, xdata, ydata, p0)
    @assert norm(jacobian_fit.param - [1.0, 2.0]) < 0.05
    @test jacobian_fit.converged

    # some example data
    yvars = 1e-6*rand(length(xdata))
    ydata = model(xdata, [1.0, 2.0]) + sqrt.(yvars) .* randn(length(xdata))

    fit = curve_fit(model, xdata, ydata, 1 ./ yvars, [0.5, 0.5])
    println("norm(fit.param - [1.0, 2.0]) < 0.05 ? ", norm(fit.param - [1.0, 2.0]))
    @assert norm(fit.param - [1.0, 2.0]) < 0.05
    @test fit.converged

    # can also get error estimates on the fit parameters
    errors = margin_error(fit, 0.1)
    println("norm(errors - [0.017, 0.075]) < 0.1 ?", norm(errors - [0.017, 0.075]))
    @assert norm(errors - [0.017, 0.075]) < 0.1

    # test with user-supplied jacobian and weights
    fit = curve_fit(model, jacobian_model, xdata, ydata, 1 ./ yvars, [0.5, 0.5])
    println("norm(fit.param - [1.0, 2.0]) < 0.05 ? ", norm(fit.param - [1.0, 2.0]))
    @assert norm(fit.param - [1.0, 2.0]) < 0.05
    @test fit.converged

    curve_fit(model, jacobian_model, xdata, ydata, 1 ./ yvars, [0.5, 0.5]; tau=0.0001)
end


#function _create_mesh_from_elements(is3D::Bool,
#                                    name::String,
#                                    points::Vector{Point{3,T}},
#                                    element_vecs::Vector{Vector{UInt64}},
#                                    element_sets::Dict{String, BitSet}) where {T}
#    # Determine element lengths
#    element_lengths = Int64[]
#    for element in element_vecs
#        l = length(element)
#        if l ∉ element_lengths
#            push!(element_lengths, l)
#        end
#    end
#    sort!(element_lengths)
#    U = _select_UInt_type(length(points))
#    if !is3D # is2D
#        K = 2
#        # Verify all points are approximately the same z-coordinate
#        if any(i->ϵ_Point < abs(points[1][3] - points[i][3]), 2:length(points))
#            error("Points of 2D mesh do not lie in the same plane")
#        end
#        points2D = convert.(Point{2,T}, points) # convert to 2D
#        if all(x->x < 6, element_lengths) # Linear mesh
#            P = 1
#        else
#            P = 2
#        end
#        polytopes = [ Polytope{K,P,length(elem),U}(elem) for elem in element_vecs ]
#        # Verify convexity
#        return PolytopeVertexMesh(name, points2D, polytopes, element_sets)
#    else
#        K = 3
#        if all(x->x < 10, element_lengths) # Linear mesh
#            P = 1
#        else # Quadratic Mesh
#            P = 2
#        end
#        polytopes = [ Polytope{K,P,length(elem),U}(elem) for elem in element_vecs ]
#        # Verify convexity
#        return PolytopeVertexMesh(name, points, polytopes, element_sets)
#    end
#    error("Invalid mesh type")
#end


content = readlines("input.txt")

drawingNums = split(popfirst!(content), ',')

# 2:6, 8:12, 14:18, 20:24

# Have to use a regex instead of just ' ' because there are multiple space characters
# Matrix(hcat(x...)') to convert to a matrix (transpose to get the correct orientation)

Base.adjoint(x::SubString) = x

boards = [Matrix(hcat(split.(content[x], r"\s+", keepempty=false)...)') for x in [range(i, stop=i+4) for i in 2:6:length(content) if i+4 <= length(content)]]

marker = "x"

markedString = repeat(marker, 5)

function checkBoard(board::Matrix)
    for row in eachrow(board)
        if join(row) == markedString
            return true
        end
    end

    for col in eachcol(board)
        if join(col) == markedString
            return true
        end
    end

    return false
end

boardScore(board, num) = sum([parse(Int, x) for x in board if x != marker]) * parse(Int, num)

function draw()

    for num in drawingNums
        for board in boards, i in 1:length(board)
            if board[i] == num
                board[i] = marker
                if checkBoard(board)
                    return boardScore(board, num)
                end
            end
        end
    end

end

draw()

#! Part 2

# Reset board
boards = [Matrix(hcat(split.(content[x], r"\s+", keepempty=false)...)') for x in [range(i, stop=i+4) for i in 2:6:length(content) if i+4 <= length(content)]]

function drawpt2()
    local trackedDrawingNum
    local winningBoardIndices::Vector{Int} = []
    for num in drawingNums
        for (boardIndex, board) in enumerate(boards), i in 1:length(board)
            if board[i] == num
                board[i] = marker
                if checkBoard(board) && !(boardIndex in winningBoardIndices)
                    push!(winningBoardIndices, boardIndex)
                    trackedDrawingNum = num
                    if length(winningBoardIndices) == length(boards)
                        return boardScore(boards[last(winningBoardIndices)], trackedDrawingNum)
                    end
                end
            end
        end
    end
end

drawpt2()


#! /usr/bin/julia

# Rosetta Code, Set consolidation

function consolidate{T}(a::Array{Set{T},1})
    1 < length(a) || return a
    b = copy(a)
    c = Set{T}[]
    while 1 < length(b)
        x = shift!(b)
        cme = true
        for (i, y) in enumerate(b)
            !isempty(intersect(x, y)) || continue
            cme = false
            b[i] = union(x, y)
            break
        end
        !cme || push!(c, x)
    end
    push!(c, b[1])
    return c
end

p = Set(["A", "B"])
q = Set(["C", "D"])
r = Set(["B", "D"])
s = Set(["H", "I", "K"])
t = Set(["F", "G", "H"])

println("p = ", p)
println("q = ", q)
println("r = ", r)
println("s = ", s)
println("t = ", t)

println("consolidate([p, q]) =\n    ", consolidate([p, q]))
println("consolidate([p, r]) =\n    ", consolidate([p, r]))
println("consolidate([p, q, r]) =\n    ", consolidate([p, q, r]))
println("consolidate([p, q, r, s, t]) =\n    ",
        consolidate([p, q, r, s, t]))


## Load Packages
using Omega
using StatsBase
using Random
using Plots
using Distributions

function one_simulation_prey(rng, n, transitions)

    """
    Simulates one step of gillespie for prey.
    """

    #t = t_list[n](rng)
    hazard_result = hazards_list[n](rng)
    prey_val = prey_list[n](rng)
    labels = ["spawn_prey", "pred_dies","prey2pred"]
    transition = transitions[labels[hazard_result]]
    new_prey = prey_val + transition[1]

    # Enforce only positive integers
    max(1, new_prey)

end

function one_simulation_pred(rng, n, transitions)

    """
    Simulates one step of gillespie for pred.
    """

    #t = t_list[n](rng)
    hazard_result = hazards_list[n](rng)
    pred_val = pred_list[n](rng)
    labels = ["spawn_prey", "pred_dies","prey2pred"]
    transition = transitions[labels[hazard_result]]
    new_pred = pred_val + transition[2]

    # Enforce only positive integers
    max(1, new_pred)

end

function get_hazards(rng, n)
    """
    Compute the hazard function given the current states.  Note that in this
        model the function depends only on state, not time, though this is not
        the case in general. "spawn_prey" represents the event of a prey being born,
        "prey2pred" represents a predator consuming a new prey and consequently spawning
        a new predator, "pred_dies" represents the death of a predator.

    args:
        ecology(dict): a dictionary containing species counts.
        theta(dict): A dictionary where keys represent events (reactions) and
            values are the location hyperparameters for rate parameter constants
            corresponding to those events.
    """

    ecology = Dict("prey" => prey_list[n](rng), "pred" => pred_list[n](rng))

    hazards = Dict(
        "spawn_prey" => theta(rng)["spawn_prey"] * ecology["prey"],
        "prey2pred" => theta(rng)["prey2pred"] * ecology["prey"] * ecology["pred"],
        "pred_dies" => theta(rng)["pred_dies"] * ecology["pred"]
        )

    vals = collect(values(hazards))
    sum_vals = sum(vals)
    prob_vals = vals/sum_vals
    categorical(rng, prob_vals)
end

function generate_rates(rng)

    Dict("spawn_prey" => spawn_prey(rng),
         "prey2pred" => prey2pred(rng),
         "pred_dies" => pred_dies(rng))

end

## Initialize paramters
Pre = [[1, 0], [1, 1], [0, 1]]
Post = [[2, 0], [0, 2], [0, 0]]
transition_mat = Post - Pre
transitions = Dict("spawn_prey" => transition_mat[1,],
                    "prey2pred" => transition_mat[2,],
                    "pred_dies" => transition_mat[3,])
#t0=0.0

# Initiate starting values randomely
prey_init = normal(10., .001)
pred_init = normal(10., .001)

# Random variables for rates
spawn_prey = normal(1.5, .01)
prey2pred = normal(1.0, .0001)
pred_dies = normal(3.0, .0075)

# prey_init = normal(50., .001)
# pred_init = normal(100., .001)
#
# # Random variables for rates
# spawn_prey = normal(.9, .01)
# prey2pred = normal(.004, .0001)
# pred_dies = normal(.4, .0075)

## Generate Model
hazards_list = Any[]
prey_list = Any[]
pred_list = Any[]
Random.seed!(1234)
theta = ciid(generate_rates)
push!(prey_list, prey_init)
push!(pred_list, pred_init)
N = 1000

for f in 2:N
    last = f - 1
    hazards_temp = ciid(get_hazards, last) # individual step
    prey_temp = ciid(one_simulation_prey, last, transitions) # individual step
    pred_temp = ciid(one_simulation_pred, last, transitions) # individual step
    push!(hazards_list, hazards_temp)
    push!(prey_list, prey_temp)
    push!(pred_list, pred_temp)
end

random_var_tuple = (Tuple(x for x in hazards_list)...,
                Tuple(x for x in prey_list)...,
                Tuple(x for x in pred_list)...,
                Tuple(Any[spawn_prey,prey2pred,pred_dies,theta])...)

## Sample
Random.seed!(1234)
samples = rand(random_var_tuple,
                1, alg = RejectionSample)

# extract run results and plot
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals,samples[1][N+x])
    push!(pred_vals,samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Omega Simulation",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Predators"],
        lw = 1.25)

a = shuffle(prey_vals)
b = sortperm(a)
condition_index = b[490:499]

## Sample with conditional
hazards_list = Any[]
prey_list = Any[]
pred_list = Any[]
theta = ciid(generate_rates)
push!(prey_list, prey_init)
push!(pred_list, pred_init)
N = 500
for f in 2:N
    last = f - 1
    hazards_temp = ciid(get_hazards, last) # individual step
    if f in 150:151
        prey_temp_ = ciid(one_simulation_prey, last, transitions) # individual step
        prey_temp = cond(prey_temp_, prey_temp_ ==ₛ 5)
    else
        prey_temp = ciid(one_simulation_prey, last, transitions) # individual step
    end
    #if f in 500:750
    #    pred_temp_ = ciid(one_simulation_pred, last, transitions) # individual step
    #    pred_temp = cond(pred_temp_, pred_temp_ < 120)
    #else
    pred_temp = ciid(one_simulation_pred, last, transitions) # individual step
    #end
    push!(hazards_list, hazards_temp)
    push!(prey_list, prey_temp)
    push!(pred_list, pred_temp)
end

random_var_tuple = (Tuple(x for x in hazards_list)...,
                Tuple(x for x in prey_list)...,
                Tuple(x for x in pred_list)...,
                Tuple(Any[spawn_prey,prey2pred,pred_dies,theta])...)

Random.seed!(1234)
samples = rand(random_var_tuple,
                1, alg = SSMH)

# extract run results and plot
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals,samples[1][N+x])
    push!(pred_vals,samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Prey <55 at T=2500 Simulation",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Pred"],
        lw = 1.25)

# Histogram to test that conditional worked
compile = []
for x in 1:50
    push!(compile, samples[x][7500])
end
histogram(compile, bins = 10,
        title = "Prey at T=2500 (<55 Conditional)")
## Counterfactual
Random.seed!(1234)
samples = rand(random_var_tuple, 1, alg = RejectionSample)
# extract run results and plot
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals,samples[1][N+x])
    push!(pred_vals,samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Simulation",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Pred"],
        lw = 1.25)

replace_index = 16
new_prey = replace(prey_list[replace_index],
                    prey_list[replace_index - 1] => 10.0)
prey_list_replace = prey_list
prey_list_replace[replace_index] = new_prey

random_var_tuple_int = (Tuple(x for x in hazards_list)...,
                Tuple(x for x in prey_list_replace)...,
                Tuple(x for x in pred_list)...,
                Tuple(Any[spawn_prey,prey2pred,pred_dies,theta])...)
Random.seed!(1234)
adj_samples = rand(random_var_tuple_int, 1, alg = RejectionSample)

# extract run results and plot
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals,adj_samples[1][N+x])
    push!(pred_vals,adj_samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Intervention Initial Prey=150",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Predators"],
        lw = 1.25)

## Int fix test
replace_index = 16
test = replace(prey_list[replace_index],
                    prey_list[replace_index - 1] => normal(30,1))

Random.seed!(1234)
samples = rand(random_var_tuple, 1, alg = RejectionSample)
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals,samples[1][N+x])
    push!(pred_vals,samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Intervention Prey T10=300",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Pred"],
        lw = 1.25)

## Intervene on initial value
prey_init_new = replace(prey_init, prey_init => 150.)
prey_list_replace = prey_list
prey_list_replace[1] = prey_init_new
random_var_tuple_int = (Tuple(x for x in hazards_list)...,
                Tuple(x for x in prey_list_replace)...,
                Tuple(x for x in pred_list)...,
                Tuple(Any[spawn_prey,prey2pred,pred_dies,theta])...)
Random.seed!(1234)
adj_samples = rand(random_var_tuple_int, 1, alg = RejectionSample)

# extract run results and plot
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals,adj_samples[1][N+x])
    push!(pred_vals,adj_samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Intervention Initial Prey=150",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Predators"],
        lw = 1.25)

## Recreate Plots
hazards_list = Any[]
prey_list = Any[]
pred_list = Any[]
Random.seed!(2)
theta = ciid(generate_rates)
push!(prey_list, prey_init)
push!(pred_list, pred_init)
N = 200

for f in 2:N
    last = f - 1
    hazards_temp = ciid(get_hazards, last) # individual step
    prey_temp = ciid(one_simulation_prey, last, transitions) # individual step
    pred_temp = ciid(one_simulation_pred, last, transitions) # individual step
    push!(hazards_list, hazards_temp)
    push!(prey_list, prey_temp)
    push!(pred_list, pred_temp)
end

random_var_tuple = (Tuple(x for x in hazards_list)...,
                Tuple(x for x in prey_list)...,
                Tuple(x for x in pred_list)...,
                Tuple(Any[spawn_prey,prey2pred,pred_dies,theta])...)

## A
Random.seed!(22)
samples = rand(random_var_tuple,
                1, alg = RejectionSample)

# extract run results and plot
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals,samples[1][N+x])
    push!(pred_vals,samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Omega Simulation",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Predators"],
        lw = 1.25)

## B
condition_index = [27, 75, 122, 187] #27

hazards_list = Any[]
prey_list = Any[]
pred_list = Any[]
Random.seed!(2)
theta = ciid(generate_rates)
push!(prey_list, prey_init)
push!(pred_list, pred_init)
N = 200
for f in 2:N
    last = f - 1
    hazards_temp = ciid(get_hazards, last) # individual step
    if f in condition_index
        prey_temp_ = ciid(one_simulation_prey, last, transitions) # individual step
        prey_temp = cond(prey_temp_, prey_temp_ >= 6.)
    else
        prey_temp = ciid(one_simulation_prey, last, transitions) # individual step
    end
    pred_temp = ciid(one_simulation_pred, last, transitions) # individual step
    push!(hazards_list, hazards_temp)
    push!(prey_list, prey_temp)
    push!(pred_list, pred_temp)
end

random_var_tuple = (Tuple(x for x in hazards_list)...,
                Tuple(x for x in prey_list)...,
                Tuple(x for x in pred_list)...,
                Tuple(Any[spawn_prey,prey2pred,pred_dies,theta])...)

Random.seed!(22)
samples = rand(random_var_tuple,
                1, alg = RejectionSample)

# extract run results and plot
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals, samples[1][N+x])
    push!(pred_vals, samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Condition On Too Many Prey",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Pred"],
        lw = 1.25)

## C/E
replace_index = 15
new_prey = replace(prey_list[replace_index],
                    prey_list[replace_index - 1] => 10.0)
prey_list_replace = prey_list
prey_list_replace[replace_index] = new_prey
random_var_tuple_int = (Tuple(x for x in hazards_list)...,
                Tuple(x for x in prey_list_replace)...,
                Tuple(x for x in pred_list)...,#pred_list_replace
                Tuple(Any[spawn_prey,prey2pred,pred_dies,theta])...)

# extract run results and plot
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals,adj_samples[1][N+x])
    push!(pred_vals,adj_samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Pred=6 at t=15",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Predators"],
        lw = 1.25)

function increase_prey()

    Random.seed!(rand(1:10000))
    samples = rand(random_var_tuple, 1, alg = RejectionSample)
    adj_samples = rand(random_var_tuple_int, 1, alg = RejectionSample)

    # extract run results and plot
    prey_norm_vals = []
    prey_adj_vals = []
    for x in 1:(N-1)
        push!(prey_norm_vals,samples[1][N+x])
        push!(prey_adj_vals,adj_samples[1][(N*2)+x])
    end

    return sum(prey_adj_vals) - sum(prey_norm_vals)
end

check = [increase_prey() for x=1:100]

histogram(check, bins = 15,
        title = "blah")

##D/F
replace_index = 15
new_pred = replace(pred_list[replace_index],
                    pred_list[replace_index - 1] => 12.0)
pred_list_replace = pred_list
pred_list_replace[replace_index] = new_pred
random_var_tuple_int = (Tuple(x for x in hazards_list)...,
                Tuple(x for x in prey_list)...,
                Tuple(x for x in pred_list_replace)...,#pred_list_replace
                Tuple(Any[spawn_prey,prey2pred,pred_dies,theta])...)

Random.seed!(22)
adj_samples = rand(random_var_tuple_int, 1, alg = RejectionSample)

# extract run results and plot
prey_vals = []
pred_vals = []
for x in 1:(N-1)
    push!(prey_vals,adj_samples[1][N+x])
    push!(pred_vals,adj_samples[1][(N*2)+x])
end

plot(hcat(prey_vals,pred_vals),
        title = "Pred=6 at t=15",
        xlabel = "Time",
        ylabel = "Quantity",
        label = ["Prey" "Predators"],
        lw = 1.25)

function increase_pred()

    samples = rand(random_var_tuple, 1, alg = RejectionSample)
    adj_samples = rand(random_var_tuple_int, 1, alg = RejectionSample)

    # extract run results and plot
    prey_norm_vals = []
    prey_adj_vals = []
    for x in 1:(N-1)
        push!(prey_norm_vals,samples[1][N+x])
        push!(prey_adj_vals,adj_samples[1][(N*2)+x])
    end

    return sum(prey_adj_vals) - sum(prey_norm_vals)
end

check = [increase_pred() for x=1:10]

histogram(check,
        title = "blah")


export HopfieldNetwork

type HopfieldNetwork{T<:Number}
	state::Array{T,1}
	weight::Array{T,2}
	n::Integer
	typ::Type
	function HopfieldNetwork(n::Integer)
		state = zeros(T,n)
		Weight = zeros(T,(n,n))
		new(state,Weight,n,T)
	end
end

function Base.show(io::IO, net::HopfieldNetwork)
	@printf io "HopfieldNetwork(type: %s, dim: %d)" string(net.typ) net.n
end