using Revise, Distributions, StatsBase, Random, BenchmarkTools, Finometrics

Random.seed!(1111)

mbsampleblockstart(N, runlength) = sample(1:(N-runlength+1))

#samplerunlength(p::Float64) = rand(Geometric(p)) + 1

#draws a non-parametric bootstrap array
function mbbootstraparray!(dat::Vector{Tdat}, sim::Vector{Tsim}; w::Int) where {Tdat, Tsim}
  simrunstart::Int = 1
  N = length(dat[1])

  #need to shift by 1 due to the way the distribution is parametrized
  while simrunstart≤N
    #draw the length of the run from the target distribution
    simrunend = min(simrunstart+w-1,N)
    runlength = simrunend - simrunstart + 1

    #draw a set of bootstrap indices
    datstart = mbsampleblockstart(N,runlength)

    #for each vector, copy the relevant indices
    for (v,b) ∈ zip(dat,sim)
      copyto!(b, simrunstart, v, datstart, runlength)
    end

    simrunstart += runlength

  end

  return nothing
end

#run the aggregation functions on the bootstrap array
function mbbootstrappass!(dat::Vector{Tv}, statfunc::Tstatfunc, sim::Vector{Tv} = deepcopy(dat);
  w::Int,
  ) where {Tv<:AbstractVector, Tstatfunc}

  @assert (length.(dat) .=== length.(sim)) |> all

  mbbootstraparray!(dat, sim; w)

  return statfunc(sim)
end

#this runs the full bootstrap
#individual passes are NOT stored
function mbbootstrap(dat::Vector{Tv}, statfunc::Tstatfunc, aggfunc::Taggfunc;
    B::Int, w::Int) where {Tv, Tstatfunc, Taggfunc}

  #pre-allocate vectors used for the bootstrap
  sims = [deepcopy(dat) for t ∈ 1:Threads.nthreads()]
  results = Dict(s => Vector{MFloat64}(undef, B) for s ∈ keys(statfunc(dat)))

  #draw a sample and compute the aggregate function, sotring the reuslts in an array
  Threads.@threads for b ∈ 1:B
    t = Threads.threadid()
    res = mbbootstrappass!(dat, statfunc, sims[t]; w)

    for s ∈ keys(res)
      @assert results[s][b] === missing
      results[s][b] = res[s]
    end
  end

  return (; Dict(s=>aggfunc(results[s]) for s ∈ keys(results))...)
end

nonparametricp(v::AbstractVector{T}) where T<:Real = sum(v .≤ zero(T))/length(v)
nonparametricp(v::AbstractVector) = (
  sum(v .|> (x)->coalesce(x≤0.0, 0.0))/sum(v .!== missing))

function testmbbootstrap(N, B; K=2)
  dat = [rand(Normal(1,1), N) |> Vector{Union{Missing, Float64}} for i ∈ 1:K]

  #a placeholder aggregation function
  statfunc(sim) = Dict{Symbol, Float64}(
    :doublemean => (sum(sim[1]) + sum(sim[2]))/(length(sim[1]) + length(sim[2])),
    :pear=>cor(sim[1],sim[2]),
    #:kend=>corkendall(sim[1] |> Vector{Float64}, sim[2] |> Vector{Float64}),
    :spear=>corspearman(sim[1] |> Vector{Float64},sim[2] |> Vector{Float64}))

  aggfunc(v) = std(skipmissing(v))
  #aggfunc = nonparametricp

  w = 5

  sim = deepcopy(dat)
  for v ∈ sim
    v .= missing
  end

  mbbootstrappass!(dat, statfunc, sim; w)
  #@code_warntype bootstrappass!(dat, statfunc, sim; p)
  for b ∈ sim
    @assert all(b .!== missing)
  end

  results = mbbootstrap(dat, statfunc, aggfunc; B, w)
  isapprox(results.doublemean, 1/(2N)^0.5, atol=1e-2) || @warn(
    "!isapprox(results.doublemean, 1/(2N)^0.5, atol=1e-2) - could be an issue if this is unexpected")

  #create an autocorrelated series
  vauto = Vector{MFloat64}(undef, N)
  v = rand(Normal(2,2), N)
  for i ∈ 1:N
    vauto[i] = mean(v[max((i-11),1):i])
  end

  for v ∈ dat
    v .+= vauto
  end

  w=7
  nwΣ = neweywestΣfunc(5)
  X::Matrix{Float64} = hcat(dat[1] .* std(dat[2]) ./ std(dat[1]), ones(N))
  lm = FMLM(X, dat[2] |> Vector{Float64})
  pearhacz = nwΣ(lm)[1,1]^0.5

  results = mbbootstrap(dat, statfunc, aggfunc;B, w)
  @info "newey HAC: $pearhacz,
    moving se: $(results.pear),
    delta: $(abs(pearhacz-results.pear))
    cor: $(lm.β[1])
    std: $(((N-2)/(1-lm.β[1]^2))^(-0.5))"

  print("Time per bootstrap:")
  @btime mbbootstrap($dat, $statfunc, $aggfunc; B=$B, w=$w)

  return nothing
end

#@time testmbbootstrap(300,10000)

###################################
#The below is a variant that uses random blocks- shouldn't make much of a difference
#choose the moving block bootstrap since it seems more accepted in literature and is less complex
#draws a single run of an array
function sbsampleblockindices(N::Int, runlength::Int)::Vector{Int}
  datrunstart = sample(1:N)
  local out::Vector{Int}
  if datrunstart + runlength - 1 ≤ N
    out = datrunstart:(datrunstart + runlength - 1) |> collect
  else
    toplength = N - datrunstart + 1
    out = [datrunstart:N; 1:(runlength-toplength);]
  end

  #@assert length(out) == runlength "runlength=$runlength but out=$out"
  return out
end

samplerunlength(p::Float64) = rand(Geometric(p)) + 1

#draws a non-parametric bootstrap array
function sbbootstraparray!(dat::Vector{Tdat}, sim::Vector{Tsim}; p::Float64) where {Tdat, Tsim}
  simrunstart::Int = 1
  N = length(dat[1])

  #need to shift by 1 due to the way the distribution is parametrized
  while simrunstart≤N
    #draw the length of the run from the target distribution
    targetrunlength = samplerunlength(p)
    simrunend = min(simrunstart+targetrunlength-1,N)
    runlength = simrunend - simrunstart + 1

    #draw a set of bootstrap indices
    inds = sbsampleblockindices(N,runlength)

    #for each vector, copy the relevant indices
    for (v,b) ∈ zip(dat,sim)
      copyto!(b, simrunstart, @view v[inds])
    end

    simrunstart += runlength

  end

  return nothing
end

#run the aggregation functions on the bootstrap array
function sbbootstrappass!(dat::Vector{Tv}, statfunc::Tstatfunc, sim::Vector{Tv} = deepcopy(dat);
  p::Float64,
  ) where {Tv<:AbstractVector, Tstatfunc}

  @assert (length.(dat) .=== length.(sim)) |> all

  sbbootstraparray!(dat, sim; p)

  return statfunc(sim)
end

#this runs the full bootstrap
#individual passes are NOT stored
function sbbootstrap(dat::Vector{Tv}, statfunc::Tstatfunc, aggfunc::Taggfunc;
    B::Int, p::Float64) where {Tv, Tstatfunc, Taggfunc}

  #pre-allocate vectors used for the bootstrap
  sims = [deepcopy(dat) for t ∈ 1:Threads.nthreads()]
  results = Dict(s => Vector{MFloat64}(undef, B) for s ∈ keys(statfunc(dat)))

  #draw a sample and compute the aggregate function, sotring the reuslts in an array
  Threads.@threads for b ∈ 1:B
    t = Threads.threadid()
    res = sbbootstrappass!(dat, statfunc, sims[t]; p)

    for s ∈ keys(res)
      @assert results[s][b] === missing
      results[s][b] = res[s]
    end
  end

  return (; Dict(s=>aggfunc(skipmissing(results[s])) for s ∈ keys(results))...)
end


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ fb195cf0-8062-11eb-3c99-fde4a4241d8b
begin
	using JLD2
	using LinearAlgebra
	using StatsBase, Statistics
	using Plots, JSServe, PlutoUI

	import WGLMakie, PyPlot
end

# ╔═╡ 949bde72-81d3-11eb-3e45-dd042b57b2bb
using LaTeXStrings

# ╔═╡ 0de243c4-8063-11eb-3580-136f64776460
Page()

# ╔═╡ 19d832ec-8063-11eb-39cf-b3f130287cc4
function makie(f::Function)
    scene = WGLMakie.Scene(resolution = (600, 400))
	f(scene)

    return scene
end

# ╔═╡ 9a89abe2-8062-11eb-2614-f7cb7a8fed05
function ingredients(path::String)
	# this is from the Julia source code (evalfile in base/loading.jl)
	# but with the modification that it returns the module instead of the last object
	name = Symbol(basename(path))
	m = Module(name)
	Core.eval(m,
        Expr(:toplevel,
             :(eval(x) = $(Expr(:core, :eval))($name, x)),
             :(include(x) = $(Expr(:top, :include))($name, x)),
             :(include(mapexpr::Function, x) = $(Expr(:top, :include))(mapexpr, $name, x)),
             :(include($path))))
	m
end

# ╔═╡ c96f10f0-8062-11eb-3e4e-2fb11d63ba86
DataIO = ingredients("../src/io.jl").DataIO

# ╔═╡ dedf4344-8137-11eb-3dd6-33edcac405d9
DataFilter = ingredients("../src/filter.jl").DataFilter

# ╔═╡ 0c8e9d0a-81cc-11eb-304f-8d4b03ed97b1
PointCloud = ingredients("../src/geo.jl").PointCloud

# ╔═╡ d3ca4d32-811e-11eb-2993-8bc55dd24808
FIGS = "/home/nolln/root/data/seqspace/eyal/figs"

# ╔═╡ 93bf354e-8124-11eb-3b5a-414a3603360f
SAVE = false

# ╔═╡ 6aa479a6-813a-11eb-0c97-353a3f53b9b2
gr(html_output_format=:png)

# ╔═╡ 1d7393d0-811f-11eb-349a-bba38050379e
SAMPLE = "sample_4"

# ╔═╡ 6fda1e64-8138-11eb-13ed-977e26b0e0cc
DATA_DIR = "/home/nolln/data/star/$SAMPLE"

# ╔═╡ ec2e7784-8062-11eb-16b4-3b009b7c4f60
RAW_INPUT = "$DATA_DIR/Solo.out/Gene/filtered/matrix.mtx"

# ╔═╡ 82119d96-8138-11eb-0138-1b6e3b959ae2
JLD_INPUT = "$DATA_DIR/glm_fit.jld2"

# ╔═╡ de1e1208-8062-11eb-0ee0-a9db551ffcd0
scrna = open(RAW_INPUT) do io
	DataIO.read_mtx(io)
end;

# ╔═╡ 84185814-806d-11eb-28f7-fd158f7638bb
μ = vec(mean(scrna,dims=2)); n = vec(sum(scrna.>0,dims=2))

# ╔═╡ d208a304-806d-11eb-15d0-ab7366d88511
begin
	PyPlot.clf()
	PyPlot.hexbin(log10.(μ), log10.(n.+1),
		gridsize=(30,30),
		cmap="inferno"
	)
	PyPlot.xlabel("log avg count/cell")
	PyPlot.ylabel("log number of expressing cells")
	if SAVE
		PyPlot.savefig("$FIGS/qc/$SAMPLE/count_vs_expressing_cells.png")
	end
	PyPlot.gcf()
end

# ╔═╡ 33a625ba-806f-11eb-1a68-bfadc0cc660b
begin
	h = histogram(log10.(μ),bins=100,legend=false)
	vline!([-3])
	xlabel!("avg log count/per cell")
	if SAVE
		savefig("$FIGS/qc/$SAMPLE/count_per_cell_histogram.png")
	end
	h
end

# ╔═╡ 597a17d0-8063-11eb-3738-3793619ecd8c
data = DataFilter.genes(scrna);

# ╔═╡ 86f746e0-8074-11eb-23b3-972e81ea370a
md"""
## Data filtering
We need to scale the data so so that highly expressed genes and lowly expressed genes are equally weighted - i.e. focus on the variation between them not overall scale.
This is clear after looking at the principal components: dominated by inter-gene variation.

Define ``x_{in}`` to be the expression count for gene i and cell n

###### Idea 1:
Naively, we just want to fit each gene to a given parameterized distribution independently.
Genes can then be re-expressed
However, we want to regress against our degrees of freedom, the total expression for each cell ``g_n \equiv \sum_i x_{in}``, to deal with systematic variations in sequencing depth.
We assume that the expression of gene i takes the form:

`` x_{in} = \text{exp}\left[\alpha_i + \beta_i \text{log}(g_n)\right] + \epsilon_{in} ``

Where ``\epsilon_{in}`` is a noise parameter with zero mean. Then, given a distribution ``\rho`` parameterized by it's mean we could then write the log likelihood as

`` \ell(\vec{\beta}_i) = \sum_n \rho(x_{in}|\mu_i = \text{exp}\left[\alpha_{i} + \beta_{i} \text{log}(g_n)\right]) ``

Thus for example, if we think the data is Poisson distributed, the above relation reduces to

`` \ell(\alpha_i, \beta_i) = \sum_n x_{in}\left(\alpha_i + \beta_i \text{log}(g_n)\right) - \text{exp}\left[\alpha_i + \beta_i \text{log}(g_n) \right] ``

Conversely, if we want to model an overdispersed counting distribution, we can turn to the negative binomial (with mean``=\mu`` and variance``=\mu + \mu^2 / \Theta``):

`` \ell(\alpha_i, \beta_i, \gamma_i) = \sum_n \text{log}(\Gamma(x_{in} +  \gamma_i)) - \text{log}(\Gamma(x_{in})) - \text{log}(\Gamma( \gamma_i)) +  \gamma_i\text{log}\left(\frac{\mu}{\mu +  \gamma_i}\right) + x_{in}\text{log}\left(\frac{\gamma_i}{\mu + \gamma_i}\right) ``

This procedure introduces ``3`` parameters per gene and thus will have to fit ``3n_g`` number of parameters in total.
Hafemeister, C. & Satija, R. (2019) claims this overfits and thus put an heurestic prior on the parameters.
Let's test this
"""

# ╔═╡ 91c38718-8138-11eb-0f18-85916a6aa8ca
@load JLD_INPUT fits

# ╔═╡ 24263ca8-80fe-11eb-0692-07698ae10d02

# ╔═╡ 21c52cf2-813b-11eb-1453-557d5b272ca1
M = vec(maximum(data,dims=2))

# ╔═╡ ba8c6388-8116-11eb-35d2-df884093be9f
begin
	E = map(fits) do r
		r.likelihood
	end
	α = map(fits) do r
		r.parameters[1]
	end
	β = map(fits) do r
		r.parameters[2]
	end
	γ = map(fits) do r
		r.parameters[3]
	end

	δα = map(fits) do r
		sqrt(abs(r.uncertainty[1]))
	end
	δβ = map(fits) do r
		sqrt(abs(r.uncertainty[2]))
	end
	δγ = map(fits) do r
		sqrt(abs(r.uncertainty[3]))
	end
	χ = vec(mean(data,dims=2))[1:length(E)]
end

# ╔═╡ d7070fe2-8116-11eb-35c0-496f1cad3008
begin
	l1 = scatter(χ,E,
		yscale=:log,
		xscale=:log,
		alpha=0.1,
		legend=false,
		xlabel="average count/cell",
		ylabel="average negative log likelihood /cell"
	)
	if SAVE
		savefig("$FIGS/qc/$SAMPLE/count_per_cell_vs_likelihood.png")
	end
	l1
end

# ╔═╡ 2f377f1c-8119-11eb-10a8-71fc4394927c
begin
	l2 = scatter(χ, α, marker_z=log10.(δα),
		xscale=:log10,
		alpha=0.1,
		label="",
		xlabel="average count/cell",
		ylabel="estimated α",
		colorbar_title="log uncertainty"
	)
	if SAVE
		savefig("$FIGS/qc/$SAMPLE/count_per_cell_vs_α.png")
	end
	l2
end

# ╔═╡ f9824c96-8119-11eb-3adb-c3cbc2986947
begin
	l3 = scatter(χ, β,
		marker_z=log10.(δβ),
		alpha=0.1,
		xscale=:log10,
		label="",
		ylims=(-5,5),
		xlabel="average count/cell",
		ylabel="estimated β",
		colorbar_title="log uncertainty"
	)
	if SAVE
		savefig("$FIGS/qc/$SAMPLE/count_per_cell_vs_β.png")
	end
	l3
end

# ╔═╡ 08a8018e-811a-11eb-0ce4-6711063fce7d
begin
	l4 = scatter(χ, γ,
		marker_z=log10.(δγ),
		label="",
		alpha=0.1,
		yscale=:log10,
		xscale=:log10,
		xlabel="average count/cell",
		ylabel="estimated γ",
		colorbar_title="log uncertainty"
	)
	if SAVE
		savefig("$FIGS/qc/$SAMPLE/count_per_cell_vs_γ.png")
	end
	l4
end

# ╔═╡ c81b6f9a-813a-11eb-00d1-1d92878cb882
begin
	PyPlot.clf()
	PyPlot.hexbin(log10.(χ), log10.(γ),
		gridsize=(40,40),
		cmap="inferno"
	)
	PyPlot.xlabel("log avg count/cell")
	PyPlot.ylabel("log estimated γ")
	if SAVE
		PyPlot.savefig("$FIGS/qc/$SAMPLE/count_per_cell_vs_γ_hexbin.png")
	end
	PyPlot.gcf()
end

# ╔═╡ 0e619b6e-813b-11eb-188c-b7ad7b1c6e3a
begin
	l5 = scatter(χ, M,
		marker_z=log10.(γ),
		label="",
		alpha=0.1,
		yscale=:log10,
		xscale=:log10,
		xlabel="average count/cell",
		ylabel="max count/cell",
		colorbar_title="log uncertainty"
	)
	if SAVE
		savefig("$FIGS/qc/$SAMPLE/max_count_per_cell_vs_γ.png")
	end
	l5
end

# ╔═╡ dfe3851e-8064-11eb-049a-4dfed6e9ff3c
function null(data)
	ñ = zeros(size(data))
	for i in 1:size(data,1)
		sample!(data[i,:], view(ñ,i,:))
	end
	return ñ
end

# ╔═╡ 2d358d88-814b-11eb-0c11-23089a86b856
norm = vcat((fit.residuals' for (i,fit) ∈ enumerate(fits) if M[i] > 1 )...)

# ╔═╡ 18a61724-8156-11eb-07db-3de6b3104a1c
function gene_names()
	names = open("$DATA_DIR/Solo.out/Gene/filtered/features.tsv") do io
		[split(line)[2] for line in eachline(io)]
	end
	# NOTE: many details here depend strongly on how we filter...
	#		should just make scrna matrix into a struct so names are always bound
	@assert length(names) == size(scrna,1)

	μ 	  = vec(mean(scrna,dims=2))
	names = names[μ .> 1e-3]
	names = names[M .> 1]

	@assert length(names) == size(norm,1)

	return names
end

# ╔═╡ 8d4fa794-814b-11eb-2a06-1ba01e048084
F = svd(norm);

# ╔═╡ ded15dc2-814b-11eb-35d5-63bf4d616d34
F̃ = svd(null(norm));

# ╔═╡ d7a04a06-814b-11eb-0f00-49c8745fc905
begin
	p1 = plot(F.S/sum(F.S), xscale=:log10, yscale=:log10)
	plot!(F̃.S/sum(F̃.S), xscale=:log10, yscale=:log10)
	xlabel!("principal component")
	ylabel!("singular value")
	if SAVE
		savefig("$FIGS/geo/$SAMPLE/residual_matrix_singular_values.png")
	end
	p1
end

# ╔═╡ 8b400948-814c-11eb-059d-f1fa06164139
ipr(ψ) = sum(ψ.^4, dims=1) ./ (sum(ψ.^2, dims=1)).^2

# ╔═╡ cb23cd18-814c-11eb-3314-674bcab45578
ψ = ipr(F.U)

# ╔═╡ 075494be-81d6-11eb-123e-d3f005c9cb0d
localized_state, localized_size = getindex(argmin(1 ./ ψ),2), minimum(1 ./ ψ)

# ╔═╡ d50cb378-814c-11eb-1863-db97f0895d64
begin
	p2 = plot(1 ./ ψ', yscale=:log10, xscale=:log10)
	xlabel!("component")
	ylabel!("participation ratio")
	if SAVE
		savefig("$FIGS/geo/$SAMPLE/residual_matrix_participation_ratio.png")
	end
	p2
end

# ╔═╡ eb4592e0-8156-11eb-2564-49ede281d83a
names = gene_names()

# ╔═╡ 36a6f78a-81dc-11eb-187f-e91cdaa1f611
localized_genes = names[sortperm(F.U[:,localized_state].^2; rev=true)]

# ╔═╡ fff6ffa0-8156-11eb-0a6b-195479c7b49b
names[argmax(abs.(F.U[:,22]))]

# ╔═╡ 0a30bc3e-814d-11eb-3a4c-77164eb08043
begin
	plot(abs.(F.U[:,1]),yscale=:log10,alpha=.5)
	plot!(abs.(F.U[:,20]),alpha=.5)
end

# ╔═╡ 22a82bd4-81cb-11eb-0c85-af09449b8b9e
dₑ = 50; Ñ = F.U[:,1:dₑ] * Diagonal(F.S[1:dₑ]) * F.Vt[1:dₑ,:]

# ╔═╡ acc97a86-81df-11eb-39bd-6b7c3e599e1c
dd = 500; approx = F.U[:,1:dd] * Diagonal(F.S[1:dd]) * F.Vt[1:dd,:]

# ╔═╡ 6e7c5dc4-81de-11eb-1e29-6d94b51d76b8
let p
	p = plot(sqrt.(cumsum(F.S.^2)/sum(F.S.^2)), linewidth=2)
	xlabel!("rank approximation")
	ylabel!("correlation with original")

	if SAVE
		savefig("$FIGS/geo/$SAMPLE/svd_reduction_accuracy.png")
	end
	p
end

# ╔═╡ 40885c20-81dd-11eb-1b6d-733ca67aa312
ind_sample = sample(CartesianIndices(norm), 2000)

# ╔═╡ 574a4dd8-81dd-11eb-16a9-d9066f374f9d
scatter(approx[ind_sample], norm[ind_sample])

# ╔═╡ 0823bfd8-81cd-11eb-144d-9bb76ef0ea65
g̃ = PointCloud.geodesics(Ñ,12)

# ╔═╡ 69e3f8f8-81f7-11eb-3cc7-a7ac4ca669a5
if SAVE
	@save "$DATA_DIR/geodesic_k12.jld2" Ñ g̃
end

# ╔═╡ 4d0509e8-81cf-11eb-1e7b-8d8ad2ecf672
PyPlot.clf(); PyPlot.matshow(g̃); PyPlot.gcf()

# ╔═╡ ff7617a2-81cf-11eb-1c8f-c5dbacc6d897
ρ, Rs = PointCloud.scaling(g̃, 1000)

# ╔═╡ dce339b0-81d2-11eb-1f94-6906505d3346
begin
	p3 = plot(Rs, ρ', alpha=0.03, color=:cyan3, label="")
	plot!(Rs, mean(ρ, dims=1)', linewidth=5, alpha=1, color=:cyan3, label="mean")
	plot!(Rs, 1e-3*Rs.^3, linestyle=:dashdot, linewidth=2, alpha=1, color=:coral3, label=L"\alpha R^3")
	plot!(Rs, 5e-2*Rs.^2, linestyle=:dashdot, linewidth=2, alpha=1, color=:darkgoldenrod1, label=L"\alpha R^2")

	xaxis!("radius", :log10, (10, 400))
	yaxis!("number of points", :log10, (10, 3200))

	if SAVE
		savefig("$FIGS/geo/$SAMPLE/ball_scaling.png")
	end
	p3
end

# ╔═╡ 4e9e490e-81d4-11eb-2948-6f1b312907fc
ξ = PointCloud.mds(g̃.^2, 10)

# ╔═╡ 708ef216-81d4-11eb-3673-e394d0fa118f
begin

residual = zeros(size(ξ,2))
for d ∈ 1:size(ξ,2)
	D̄ = .√PointCloud.distance²(ξ[:,1:d]')

	residual[d] = cor(
		PointCloud.upper_tri(D̄),
		PointCloud.upper_tri(g̃)
	)
end

end;

# ╔═╡ 97052e74-81d4-11eb-115f-0b6b19bea33a
begin
	p4 = plot(residual, linewidth=2, label="")
	xlabel!("dimension")
	ylabel!("correlation of distance matrices")
	if SAVE
		savefig("$FIGS/geo/$SAMPLE/isomap_distance_correlation.png")
	end
	p4
end

# ╔═╡ eb79c294-81d4-11eb-2cd6-633742a3ca0e
gene_index = Dict(g => i for (i,g) in enumerate(names))

# ╔═╡ 91e387a0-81da-11eb-1c9f-857e7079fe59
@bind THETA PlutoUI.Slider(range(0,180,length=100))

# ╔═╡ 9bb40ce6-81da-11eb-3814-670fc5029879
@bind PHI PlutoUI.Slider(range(0,360,length=100))

# ╔═╡ 449c5422-81e9-11eb-293d-2b3f8db9e449
GENE = "SOX11"

# ╔═╡ a9989792-81d9-11eb-2123-632f4c14bcd0
begin
	PyPlot.clf()
	PyPlot.scatter3D(ξ[:,1], ξ[:,2], ξ[:,3],
		c=vec(Ñ[gene_index[GENE],:]),
		cmap="inferno",
	)
	PyPlot.view_init(THETA,PHI)
	if false
		PyPlot.savefig("$FIGS/geo/$SAMPLE/isomap_coordinates_$GENE.png")
	end
	PyPlot.gcf()
end

# ╔═╡ 736505e4-81d6-11eb-0ed5-db36e1f6a6ec
#=WGLMakie.scatter(ξ[:,1], ξ[:,2], ξ[:,3],
	color=vec(Ñ[gene_index["PAX6"],:]),
	markersize=5
)=#

# ╔═╡ Cell order:
# ╠═fb195cf0-8062-11eb-3c99-fde4a4241d8b
# ╠═949bde72-81d3-11eb-3e45-dd042b57b2bb
# ╠═0de243c4-8063-11eb-3580-136f64776460
# ╠═19d832ec-8063-11eb-39cf-b3f130287cc4
# ╠═9a89abe2-8062-11eb-2614-f7cb7a8fed05
# ╠═c96f10f0-8062-11eb-3e4e-2fb11d63ba86
# ╠═dedf4344-8137-11eb-3dd6-33edcac405d9
# ╠═0c8e9d0a-81cc-11eb-304f-8d4b03ed97b1
# ╠═d3ca4d32-811e-11eb-2993-8bc55dd24808
# ╠═93bf354e-8124-11eb-3b5a-414a3603360f
# ╠═6aa479a6-813a-11eb-0c97-353a3f53b9b2
# ╠═1d7393d0-811f-11eb-349a-bba38050379e
# ╟─6fda1e64-8138-11eb-13ed-977e26b0e0cc
# ╟─ec2e7784-8062-11eb-16b4-3b009b7c4f60
# ╟─82119d96-8138-11eb-0138-1b6e3b959ae2
# ╟─de1e1208-8062-11eb-0ee0-a9db551ffcd0
# ╠═84185814-806d-11eb-28f7-fd158f7638bb
# ╟─d208a304-806d-11eb-15d0-ab7366d88511
# ╟─33a625ba-806f-11eb-1a68-bfadc0cc660b
# ╠═597a17d0-8063-11eb-3738-3793619ecd8c
# ╠═18a61724-8156-11eb-07db-3de6b3104a1c
# ╟─86f746e0-8074-11eb-23b3-972e81ea370a
# ╠═91c38718-8138-11eb-0f18-85916a6aa8ca
# ╟─24263ca8-80fe-11eb-0692-07698ae10d02
# ╟─21c52cf2-813b-11eb-1453-557d5b272ca1
# ╟─ba8c6388-8116-11eb-35d2-df884093be9f
# ╟─d7070fe2-8116-11eb-35c0-496f1cad3008
# ╟─2f377f1c-8119-11eb-10a8-71fc4394927c
# ╟─f9824c96-8119-11eb-3adb-c3cbc2986947
# ╟─08a8018e-811a-11eb-0ce4-6711063fce7d
# ╟─c81b6f9a-813a-11eb-00d1-1d92878cb882
# ╟─0e619b6e-813b-11eb-188c-b7ad7b1c6e3a
# ╠═dfe3851e-8064-11eb-049a-4dfed6e9ff3c
# ╠═2d358d88-814b-11eb-0c11-23089a86b856
# ╠═8d4fa794-814b-11eb-2a06-1ba01e048084
# ╠═ded15dc2-814b-11eb-35d5-63bf4d616d34
# ╠═d7a04a06-814b-11eb-0f00-49c8745fc905
# ╠═8b400948-814c-11eb-059d-f1fa06164139
# ╠═cb23cd18-814c-11eb-3314-674bcab45578
# ╠═075494be-81d6-11eb-123e-d3f005c9cb0d
# ╠═36a6f78a-81dc-11eb-187f-e91cdaa1f611
# ╠═d50cb378-814c-11eb-1863-db97f0895d64
# ╟─eb4592e0-8156-11eb-2564-49ede281d83a
# ╠═fff6ffa0-8156-11eb-0a6b-195479c7b49b
# ╠═0a30bc3e-814d-11eb-3a4c-77164eb08043
# ╠═22a82bd4-81cb-11eb-0c85-af09449b8b9e
# ╠═acc97a86-81df-11eb-39bd-6b7c3e599e1c
# ╠═6e7c5dc4-81de-11eb-1e29-6d94b51d76b8
# ╠═40885c20-81dd-11eb-1b6d-733ca67aa312
# ╠═574a4dd8-81dd-11eb-16a9-d9066f374f9d
# ╠═0823bfd8-81cd-11eb-144d-9bb76ef0ea65
# ╠═69e3f8f8-81f7-11eb-3cc7-a7ac4ca669a5
# ╠═4d0509e8-81cf-11eb-1e7b-8d8ad2ecf672
# ╠═ff7617a2-81cf-11eb-1c8f-c5dbacc6d897
# ╠═dce339b0-81d2-11eb-1f94-6906505d3346
# ╠═4e9e490e-81d4-11eb-2948-6f1b312907fc
# ╠═708ef216-81d4-11eb-3673-e394d0fa118f
# ╠═97052e74-81d4-11eb-115f-0b6b19bea33a
# ╠═eb79c294-81d4-11eb-2cd6-633742a3ca0e
# ╠═91e387a0-81da-11eb-1c9f-857e7079fe59
# ╠═9bb40ce6-81da-11eb-3814-670fc5029879
# ╠═449c5422-81e9-11eb-293d-2b3f8db9e449
# ╠═a9989792-81d9-11eb-2123-632f4c14bcd0
# ╠═736505e4-81d6-11eb-0ed5-db36e1f6a6ec


"""
$(TYPEDEF)
`ADMM` is an implementation of Lasso using the alternating direction methods of multipliers and
loosely based on [this implementation](https://web.stanford.edu/~boyd/papers/admm/lasso/lasso.html).
It solves the following problem
```math
\\argmin_{x} \\frac{1}{2} \\| Ax-b\\|_2 + \\lambda \\|x\\|_1
```
# Fields
$(FIELDS)
# Example
```julia
opt = ADMM()
opt = ADMM(1e-1, 2.0)
```
"""
mutable struct ADMM{T, R} <: AbstractOptimizer{T}
    """Sparsity threshold"""
    λ::T
    """Augmented Lagrangian parameter"""
    ρ::R

    function ADMM(threshold::T = 1e-1, ρ::R = 1.0) where {T, R}
        @assert all(threshold .> zero(eltype(threshold))) "Threshold must be positive definite"
        @assert zero(R) < ρ "Augemented lagrangian parameter should be positive definite"
        return new{T, R}(threshold, ρ)
    end
end

Base.summary(::ADMM) = "ADMM"

function (opt::ADMM{T,H})(X, A, Y, λ::U = first(opt.λ);
    maxiter::Int64 = maximum(size(A)), abstol::U = eps(eltype(T)), progress = nothing, kwargs...)  where {T, H, U}

    n, m = size(A)

    ρ = opt.ρ

    x_i = zero(X)
    u = zero(X)
    z = zero(X)

    x_i .= X

    P = A'A .+ Diagonal(ρ .* ones(eltype(X),m))
    P = cholesky!(P)
    c = A'*Y

    R = SoftThreshold()

    iters = 0
    converged = false

    xzero = zero(eltype(X))
    obj = xzero
    sparsity = xzero
    conv_measure = xzero

    _progress = isa(progress, Progress)
    initial_prog = _progress ? progress.counter : 0

    @views while (iters < maxiter) && !converged
        iters += 1

        #ldiv!(z, P, c .+ ρ .* (z .- u))
        z .= P \ (c .+ ρ .* (z .- u))
        R(X, z .+ u, λ ./ ρ)
        u .= u .+ z .- X

        conv_measure = norm(x_i .- X, 2)

        if _progress
            obj = norm(Y .- A*X, 2)
            sparsity = norm(X, 0, λ)

            ProgressMeter.next!(
            progress;
            showvalues = [
                (:Threshold, λ), (:Objective, obj), (:Sparsity, sparsity),
                (:Convergence, conv_measure)
            ]
            )
        end

        if conv_measure < abstol
            converged = true

            if _progress

                ProgressMeter.update!(
                progress,
                initial_prog + maxiter
                )
            end

        else
            x_i .= X
        end
    end
    @views clip_by_threshold!(X, λ)
    return
end


import SpecialFunctions: gamma

abstract type AbstractWindResourceModel end

"""
    DiscritizedWindResource(wind_directions, wind_speeds, wind_probabilities, measurement_heights, air_density, ti_model, wind_shear_model)

    Struct defining a wind resource

# Arguments
- `wind_directions::Array{Float}(Nstates)`: an array of wind directions corresponding to each wind farm state
- `wind_speeds::Array{Float}(Nstates)`: an array of wind speeds corresponding to each wind farm state
- `wind_probabilities::Array{Float}(Nstates)`: an array of probabilities corresponding to each wind farm state
- `measurement_heights::Array{Float}(Nstates)`: an array of measurement heights corresponding to each wind farm state
- `air_density::Float`: the air density
- `ambient_ti::Array{Float}`: an array of the ambient turbulence intensity for each wind direction
- `wind_shear_model::Array{AbstractWindShearModel}(1)`: contains a struct defining the desired turbulence intensity model
"""
struct DiscretizedWindResource{AF, TF, ASM} <: AbstractWindResourceModel

    wind_directions::AF
    wind_speeds::AF
    wind_probabilities::AF
    measurement_heights::AF
    air_density::TF
    ambient_tis::AF
    wind_shear_model::ASM

end

function _dist_weibull(x,L;k=2.0)
    if L < 0.0001
        L = 0.0001
    end
    return (k/L)*(x/L)^(k-1)*exp(-(x/L)^k)
end

function setup_weibull_distribution(wind_directions,wind_probabilities,wind_speeds,nspeeds)
        if nspeeds == 1
            return wind_directions, wind_probabilities, wind_speeds
        end

        ndirections = length(wind_directions)
        dirs = zeros(ndirections*nspeeds)
        freqs = zeros(ndirections*nspeeds)
        speeds = zeros(ndirections*nspeeds)

        #direction loops
        k = 1
        for i = 1:ndirections
            for j = 1:nspeeds
                dirs[k] = wind_directions[i]
                k += 1
            end
        end

        #speed and frequency loops
        m = 1
        for i = 1:ndirections
            avg_speed = wind_speeds[i]
            speed_dist = range(25.0/(2.0*nspeeds)+0.001,stop=25.0-25.0/(2.0*nspeeds),length=nspeeds)
            dspeed = speed_dist[2]-speed_dist[1]
            num_int = 1000

            for j = 1:nspeeds
                speed_int = range(speed_dist[j]-dspeed/2.0,stop=speed_dist[j]+dspeed/2.0,length=num_int)
                k = 2.0
                scale = avg_speed/(gamma(1.0+1.0/k))
                freq_int = _dist_weibull.(speed_int,scale)

                speed_freq = trapz(speed_int,freq_int)
                speeds[m] = speed_dist[j]
                freqs[m] = speed_freq*wind_probabilities[i]
                m += 1
            end
        end

        return dirs, freqs, speeds
end


using Indicators
using PyPlot
using Base.Dates

# Generate some toy sample data
srand(1)
n = 250
op = 100.0 + cumsum(randn(n))
hi = op + rand(n)
lo = op - rand(n)
cl = 100.0 + cumsum(randn(n))
for i = 1:n
	if cl[i] > hi[i]
		cl[i] = hi[i]
	elseif cl[i] < lo[i]
		cl[i] = lo[i]
	end
end
ohlc = [op hi lo cl]
hlc = [hi lo cl]
hl = [hi lo]
t = collect(today():Day(1):today()+Day(n-1))

# Overlays
subplot(411)
plot(t, cl, lw=2, c="k", label="Random Walk")
grid(ls="-", c=[0.8,0.8,0.8])
plot(t, sma(cl,n=40), c=[1,0.5,0], label="SMA (40)")
plot(t, ema(cl,n=10), c=[0,1,1], label="EMA (10)")
plot(t, wma(cl,n=20), c=[1,0,1], label="WMA (20)")
plot(t, psar(hl),   "bo", label="Parabolic SAR")
legend(loc="best", frameon=false)

# MACD
subplot(412)
plot(t, macd(cl)[:,1], label="MACD", c=[1,0.5,1])
plot(t, macd(cl)[:,2], label="Signal", c=[0.5,0.25,0.5])
bar(t, macd(cl)[:,3], align="center", label="Histogram", color=[0,0.5,0.5], alpha=0.25)
plot([t[1],t[end]], [0,0], ls="--", c=[0.5,0.5,0.5])
grid(ls="-", c=[0.8,0.8,0.8])
legend(loc="best", frameon=false)

# RSI
subplot(413)
plot(t, rsi(cl), c=[0.5,0.5,0], label="RSI")
grid(ls="-", c=[0.8,0.8,0.8])
plot([t[1],t[end]], [30,30], c="g")
plot([t[1],t[end]], [70,70], c="r")
legend(loc="best", frameon=false)

# ADX
subplot(414)
plot(t, adx(hlc)[:,1], "g-", label="DI+")
plot(t, adx(hlc)[:,2], "r-", label="DI-")
plot(t, adx(hlc)[:,3], c=[0,0,1], lw=2, label="ADX")
grid(ls="-", c=[0.8,0.8,0.8])
legend(loc="best", frameon=false)

tight_layout()


## We need to use a library for Symbolic calculations, a very common one is SymPy from the python
## language. Luckily Julia has excellent support for it.
using Parameters
using DifferentialEquations
using ForwardDiff
using SymPy

#jl Remove the follow `#jl` to use this file standalone
include("cr_model.jl")

# We want to deal with all of these as symbolic variables, which are not the default type in Julia,
# unlike Mathematica.
@vars R C
@vars r k e a h m
# We need to make a symbolic parameter list, as `CRPar` is numeric
spar = Dict(
    :r => r,
    :k => k,
    :a => a,
    :h => h,
    :e => e,
    :m => m)

# we need to make symbolic versions of the model equations. We do this by calling the function with
# the symbolic parameters. the last parameter could be anythign as the time (`t`) argument is not
# used. I have set it to `NaN` which is a name for not a number.

## CR exp resource, type I consumer
f1, f2 = cr_expI_model([R, C], spar, NaN)
#-
SymPy.solve(f1, R)
#-
SymPy.solve(f1, C)
#-
SymPy.solve(f2, R)
#-
SymPy.solve(f2, C)
#-

## CR log resource, type I consumer
g1, g2 = cr_logI_model([R, C], spar, NaN)
#-
SymPy.solve(g1, R)
#-
SymPy.solve(g1, C)
#-
SymPy.solve(g2, R)
#-
SymPy.solve(g2, C)

## CR log resource, type II consumer
l1, l2 = cr_logII_model([R, C], spar, NaN)
#-
SymPy.solve(l1, R)
#-
SymPy.solve(l1, C)
#-
SymPy.solve(l2, R)
#-
SymPy.solve(l2, C)

## CR exp resource, type II consumer
n1, n2 = cr_expII_model([R, C], spar, NaN)
#-
SymPy.solve(n1, R)
#-
SymPy.solve(n1, C)
#-
SymPy.solve(n2, R)
#-
SymPy.solve(n2, C)


"""
    abstract type OliverProblem{T,N,M} end

Abstract type for constructing a boundary value problem from a recurrence
relation. The functions for coefficients and the right side dispatch.

`N` and `M` correspond to the total number of specified initial boundary conditions,
and the number specified on the end of the recurrence, respectively.
"""
abstract type OliverProblem{T,N,M} end

"""
    assembleP{T}(OP::OliverProblem{M,N}, a, b) where {M, N}

Generic description of the ``\\mathbf{P}`` matrix within the linear system of
Oliver's method. This in equation 3a of Oliver 1968.
"""
function assembleP(Tmat, OP::OliverProblem{T,N,M}, a, b) where {T,N,M}
    Psize = b - N - a
    𝐏 = BandedMatrix{Tmat}(undef, (Psize, Psize), (N-M,M))
    for row in 1:Psize
        for col in max(1, row-N+M):min(row + M, Psize)
            Δm = col - row
            𝐏[row, col] = OliverP(OP, M - Δm, a + row)
        end
    end
    return 𝐏
end
assembleP(OP::OliverProblem{T,N,M}, a, b) where {T,N,M} = assembleP(T,OP,a,b)

"""
    assembleρ{T}(OP::OliverProblem{M,N}, a, b) where {M, N}

Generic description of the ``\\vec{\\rho}`` vector within the linear system of
Oliver's method. This in equation 3b of Oliver 1968.
"""
function assembleρ(Tmat, OP::OliverProblem{T,N,M}, a, b, YBC) where {T,N,M}
    Psize = b - N - a
    ρ = zeros(Tmat, Psize)

    for row in 1:(N-M)
        Σ = zero(T)
        for s in 1:(N - M - row + 1)
            # every row, increase by 1 the sum UB, YBC arg, P arg
            Σ = Σ + OliverP(OP, N - s + 1, a + row) * YBC[a + s + row - 1]
        end
        ρ[row] = OliverR(OP, a + row) - Σ
    end

    rₛ, rₑ = (b - N - M + 1 - a), (b - N - a)
    for row in rₛ:rₑ
        row_from_end = rₑ - row + 1
        Σ = zero(T)
        for s in 0:(M - row_from_end)
            # every row, decrease by 1 the sum UB, YBC arg, P arg
            Σ = Σ + OliverP(OP, s, b - N - row_from_end + 1) *
                YBC[b - s - row_from_end + 1]
        end
        ρ[row] = OliverR(OP, a+row) - Σ
    end

    return ρ
end
assembleρ(OP::OliverProblem{T}, a, b, YBC) where T =
    assembleρ(T, OP, a, b, YBC)

"""
	OliverP(OP::OliverProblem, s, i)

Computes ``P_s(i)``, the coefficient ``s`` at solution index ``i``.
"""
function OliverP(OP::OliverProblem, s, i)
    throw(ErrorException("You need to implement an OliverP for this problem."))
end

"""
	OliverR(OP::OliverProblem, i)

Computes ``R(i)``, the right side of the recurrence relation.
"""
function OliverR(OP::OliverProblem, i)
    throw(ErrorException("You need to implement an OliverR for this problem."))
end


using Turing
using Test

obs = [0, 1, 0, 1, 1, 1, 1, 1, 1, 1]

@model newinterface(obs) = begin
  p ~ Beta(2,2)
  for i = 1:length(obs)
    obs[i] ~ Bernoulli(p)
  end
  p
end

# Turing.TURING[:modelex]

# newinterface(data)
#
# ga = VarInfo()
# sampler = HMCSampler{HMC}(HMC(100, 1.5, 3))
# ga = newinterface(data, ga, sampler)
# newinterface
#
# chain = sample(newinterface, HMC(100, 1.5, 3))

chain = sample(newinterface(obs), HMC(100, 0.75, 3, :p, :x); chunk_size=2)


"""
    BetaPrime(α,β)

The *Beta prime distribution* has probability density function

```math
f(x; \\alpha, \\beta) = \\frac{1}{B(\\alpha, \\beta)}
x^{\\alpha - 1} (1 + x)^{- (\\alpha + \\beta)}, \\quad x > 0
```

The Beta prime distribution is related to the [`Beta`](@ref) distribution via the
relation ship that if ``X \\sim \\operatorname{Beta}(\\alpha, \\beta)`` then ``\\frac{X}{1 - X}
\\sim \\operatorname{BetaPrime}(\\alpha, \\beta)``

```julia
BetaPrime()        # equivalent to BetaPrime(1, 1)
BetaPrime(a)       # equivalent to BetaPrime(a, a)
BetaPrime(a, b)    # Beta prime distribution with shape parameters a and b

params(d)          # Get the parameters, i.e. (a, b)
```

External links

* [Beta prime distribution on Wikipedia](http://en.wikipedia.org/wiki/Beta_prime_distribution)

"""
struct BetaPrime{T<:Real} <: ContinuousUnivariateDistribution
    α::T
    β::T
    BetaPrime{T}(α::T, β::T) where {T} = new{T}(α, β)
end

function BetaPrime(α::T, β::T; check_args=true) where {T<:Real}
    check_args && @check_args(BetaPrime, α > zero(α) && β > zero(β))
    return BetaPrime{T}(α, β)
end

BetaPrime(α::Real, β::Real) = BetaPrime(promote(α, β)...)
BetaPrime(α::Integer, β::Integer) = BetaPrime(float(α), float(β))
BetaPrime(α::Real) = BetaPrime(α, α)
BetaPrime() = BetaPrime(1.0, 1.0, check_args=false)

@distr_support BetaPrime 0.0 Inf

#### Conversions
function convert(::Type{BetaPrime{T}}, α::Real, β::Real) where T<:Real
    BetaPrime(T(α), T(β))
end
function convert(::Type{BetaPrime{T}}, d::BetaPrime{S}) where {T <: Real, S <: Real}
    BetaPrime(T(d.α), T(d.β), check_args=false)
end

#### Parameters

params(d::BetaPrime) = (d.α, d.β)
@inline partype(d::BetaPrime{T}) where {T<:Real} = T

#### Statistics

function mean(d::BetaPrime{T}) where T<:Real
    ((α, β) = params(d); β > 1 ? α / (β - 1) : T(NaN))
end

function mode(d::BetaPrime{T}) where T<:Real
    ((α, β) = params(d); α > 1 ? (α - 1) / (β + 1) : zero(T))
end

function var(d::BetaPrime{T}) where T<:Real
    (α, β) = params(d)
    β > 2 ? α * (α + β - 1) / ((β - 2) * (β - 1)^2) : T(NaN)
end

function skewness(d::BetaPrime{T}) where T<:Real
    (α, β) = params(d)
    if β > 3
        s = α + β - 1
        2(α + s) / (β - 3) * sqrt((β - 2) / (α * s))
    else
        return T(NaN)
    end
end

#### Evaluation

function logpdf(d::BetaPrime, x::Real)
    α, β = params(d)
    _x = max(0, x)
    z = xlogy(α - 1, _x) - (α + β) * log1p(_x) - logbeta(α, β)
    return x < 0 ? oftype(z, -Inf) : z
end

function zval(::BetaPrime, x::Real)
    y = max(x, 0)
    z = y / (1 + y)
    # map `Inf` to `Inf` (otherwise it returns `NaN`)
    return isinf(x) && x > 0 ? oftype(z, Inf) : z
end
xval(::BetaPrime, z::Real) = z / (1 - z)

for f in (:cdf, :ccdf, :logcdf, :logccdf)
    @eval $f(d::BetaPrime, x::Real) = $f(Beta(d.α, d.β; check_args=false), zval(d, x))
end

for f in (:quantile, :cquantile, :invlogcdf, :invlogccdf)
    @eval $f(d::BetaPrime, p::Real) = xval(d, $f(Beta(d.α, d.β; check_args=false), p))
end

#### Sampling

function rand(rng::AbstractRNG, d::BetaPrime)
    (α, β) = params(d)
    rand(rng, Gamma(α)) / rand(rng, Gamma(β))
end


function anneal_linear(pctg, from, to)
    return (1 - pctg) * from + pctg * to
end

function anneal_cosine(pctg, from, to)
    return to + (from - to)/2 * (cos(pi*pctg) + 1)
end

anneal_const(pctg, from, to) = pctg == 1 ? to : from

function anneal_exp(pctg, from, to)
    from != 0 || throw(DomainError("`from` must not be 0"))
    return from * (to/from)^pctg
end


# # Time-Varying Bayesian Linear Regression

using AbstractGPs
using ColorTypes
using FixedPointNumbers
using Plots
using Random
using Stheno

# ## Define and inspect our model

#=
g1 and g2 are time-varying basis functions. In a real application, these might be specified
by data.

w1 and w2 are the time-varying basis functions for these bases, specified by slowly
varying GPs.

f is the prediction of the regressor.

y is the addition of f and rough temporally-correlated "noise".
=#
f = @gppp let
    g1 = x->x / 4
    g2 = cos
    w1 = stretch(GP(SEKernel()), 0.2)
    w2 = stretch(GP(SEKernel()), 1)
    f = g1 * w1 + g2 * w2
    y = f + 0.3 * GP(Matern12Kernel())
end;

# Sample from the prior from plotting and for conditioning.
rng, N, Nplot, S = MersenneTwister(123456), 250, 500, 100;
x = GPPPInput(:y, sort(rand(rng, N) * 10));
ŷ = rand(rng, f(x));

# Compute the posterior.
f′ = posterior(f(x), ŷ);

# Sample from the posterior and write to file.
xp_ = range(-2.5, stop=12.5, length=Nplot);
xp_w1 = GPPPInput(:w1, xp_);
xp_w2 = GPPPInput(:w2, xp_);
xp_y = GPPPInput(:y, xp_);

# Generate joint posterior samples.
xp = BlockData(xp_w1, xp_w2, xp_y);
f′_xp = rand(rng, f′(xp, 1e-9), S);
w1′s, w2′s, y′s = split(xp, f′_xp);

# ## Plot results

gr();
posterior_plot = plot(
    legend=:topleft,
    legendfont=Plots.Font(
        "sans-serif",
        10,
        :hcenter,
        :vcenter,
        0.0,
        RGB{Normed{UInt8, 8}}(0.0,0.0,0.0)
    ),
    background_color_legend=RGBA(1, 1, 1, 0),
    foreground_color_legend=RGBA(1, 1, 1, 0),
);

# Plot posterior over w1.
plot!(posterior_plot, xp_, f′(xp_w1); color=:green, label="w1");
plot!(posterior_plot, xp_, w1′s; color=:green, label="", linewidth=1, alpha=0.2);

# Plot posterior over w2.
plot!(posterior_plot, xp_, f′(xp_w2); color=:magenta, label="w2");
plot!(posterior_plot, xp_, w2′s; color=:magenta, label="", linewidth=1, alpha=0.2);

# Plot x1 and x2
plot!(posterior_plot, xp_, (x->x / 4).(xp_);
    linecolor=:black,
    linewidth=1.0,
    label="x / 4",
);
plot!(posterior_plot, xp_, cos.(xp_);
    linecolor=:black,
    linewidth=1.0,
    linestyle=:dash,
    label="cos",
);

# Plot samples against which we're regressing.
scatter!(posterior_plot, x.x, ŷ;
    markercolor=:red,
    markershape=:circle,
    markerstrokewidth=0.0,
    markersize=4,
    markeralpha=0.7,
    label="",
);

# Plot posterior over `y`.
plot!(posterior_plot, xp_, f′(xp_y); color=:blue, label="y");
plot!(posterior_plot, xp_, y′s; color=:blue, label="", linewidth=1, alpha=0.2);

posterior_plot


using Plots
using StatsBase

N = 10000
recover_day = 10
days = 360

persons_state = zeros(Int32, N)
infected = sample(1:N,1)

for i in infected
    persons_state[i] = 1
end

R0=2
γ=1/recover_day
β=γ*R0/(N-1)

result = []
for d in 1:days

    # 感染の判定
    new_infected = []
    for i in 1:length(persons_state)
        if (persons_state[i] == 1) && i ∉ new_infected
            for j in 1:length(persons_state)
                if persons_state[j] == 0 && j != i
                    b = rand()
                    if b < β
                        persons_state[j] = 1
                        push!(new_infected,j)
                    end
                end
            end
        end
    end

    # 回復の判定
    for i in 1:length(persons_state)
        if persons_state[i] == 1
            r = rand()
            if r <= γ
                persons_state[i] = 2
            end
        end
    end

    # 人数を数える
    S = 0
    I = 0
    R = 0
    for i in 1:length(persons_state)
        if persons_state[i] == 0
            S = S + 1
        elseif  persons_state[i] == 1
            I = I + 1
        else
            R = R + 1
        end
    end

    SIR=(d, [S,I,R])

    println("$SIR")

    push!(result, SIR)
end

plot_graph = plot(map(t->t[1][1], result),map(t->t[2][1], result),
                  title="SIR model/Random Contact R0=$R0 γ=$γ",
                  label="Suscepible", color=:blue, legend=:left, size=(1200, 900))
plot!(plot_graph, map(t->t[1][1], result),map(t->t[2][2], result), label="Infectious", color=:red)
plot!(plot_graph, map(t->t[1][1], result),map(t->t[2][3], result), label="Removed", color=:green)

png(plot_graph, "sir_randomcontact.png")


using BenchmarkTools
include("ZZDiffusionBridge.jl")

#comment out runall() in example_sin.jl
include("./examples/example_sin.jl")

# see notes section: tune velocities
# the prefactor (β + 1.0) is just made to make a
# bit more comparable the experiments

function tune_velocity( L::Int64, β = 0.0)
    θ = fill(0.0, 2<<L - 1)
    k = 1
    for i in 0:L
        for j in 0:2^i-1
            θ[k] =  (3*β + 0.5)*2.0^(-i*β)
            k += 1
        end
    end
    return(θ)
end

#just little differences from the original zz_sampler (src/zz_sampler).
function zz_sampler(S::System, X::AbstractModel, T::Float64, L::Int64, u::Float64, v::Float64, clock::Float64, τ0::Float64 , n0::Int64)
    t = 0.0
    Ξ = Skeleton[]
    while t < clock
        τ0 , n0 = findmin(S.τ)
        S.ξ .+=  S.θ*τ0
        t += τ0
        if acc_rej(n0, S, X, sampling_scheme(X), u, v, t)
            S.θ[n0] = -S.θ[n0]
            push!(Ξ, (Skeleton(copy(S.ξ), t)))
            update_events!(n0, τ0, S, X, sampling_scheme(X), dependence_strucute(X), true, u, v, t)
        else
            update_events!(n0, τ0, S, X, sampling_scheme(X), dependence_strucute(X), false, u, v, t)
        end
    end
    Ξ, τ0, n0
end

# the the average of the path over one realization.
# since the path is piecewise linear. We take the midpoint.
function piec_lin_integration(y, L, T)
    N = 2<<L
    somma = 0.5*(y[1] + y[end]) + sum(y[2:(end-1)])
    return somma*T/N
end

#given an array of skeleton, compute the sample avergae
# `sampling _frequency`. Discretization given the continuous path
# L truncation, u, v initial and final points
function compute_average(X::Array{Skeleton,1}, sampling_frequncy::Float64, u, v, L, T)
    dt = 0.5:sampling_frequncy:T-0.5 #if you start at 0.0 and finish at T the function Interpolate gives errors
    M = 0
    count = 0
    for i in dt
        ξ = FindCoordinates(X, i).ξ
        ξfe = fs_expansion(ξ, u, v, L, T)
        path = piec_lin_integration(ξfe, L, T)
        M = M + path
        count += 1
    end
    return M/count
end

# experiment: timing each ZigZag of length `clock`. repeat it
# `rep` times. return the an array with the single repretition and the time
# β determines the decay of the velocities
# L truncation level

function vel_experiment(β, L, clock, rep)
    α = 0.5
    T = 200.0
    u = -3π
    v = 3π
    τ0 = 0.0
    n0 = 0
    ξ = fill(0.0, 2<<L - 1)
    θ = tune_velocity(L, β)
    S = System(L, T, ξ, θ)
    X = SinSDE(α)
    Y = []
    TIME = []
    tot_time = 0
    for i in 1:rep
        (Ξ, τ0, n0), time = @timed  zz_sampler(S, X, T, L, u, v, clock, τ0, n0)
        #S::System, X::AbstractModel, T::Float64, L::Int64, u, v, clock, τ0 , n0)
        tot_time += time
        push!(Y, copy(Ξ) )
        push!(TIME, tot_time )
    end
    return Y, TIME
end

function runall_velocity()
    rep = 20
    clock = 100
    Y = fill(0.0,(6,rep))
    Time = fill(0.0,(6,rep))
    row = 1
    L = 10
    p = plot()
    for β in 0.0:0.1:0.5
        X, time = vel_experiment(β, L, 100.0, rep)
        ave = cumsum(compute_average.(X, 1.0, -3π,  3π, L, T))
        Y[row,:] =  [ave[i]/i for i in 1:rep]
        Time[row, :] = time
        plot!(p, Time[row,:], abs.(Y[row,:]), label =("beta =  $β"))
        row += 1
    end
    display(p)
    return Y, Time
end

X, T = runall_velocity()


import Base: isprime

## http://docs.sympy.org/dev/modules/ntheory.html?highlight=ntheory#sympy.ntheory.factor_
ntheory_sympy_methods = (
                         :sieve,
                         :prime, :primepi,
                         :isprime, # import
                         :nextprime, :prevprime, :primorial,
                         :trailing,
                         :multiplicity, :perfect_power,
                         :pollard_rho, :pollard_pm1,
                         :factorint, :primefactors, :divisors, :divisor_count,
                         :totient,
                         :binomial_coefficients, :binomial_coefficients_list,
                         :multinomial_coefficients,
                         :npartitions,
                         :n_order, :is_primitive_root, :is_quad_residue,
                         :legendre_symbol, :jacobi_symbol
                         )

"""

[primerange](http://docs.sympy.org/dev/modules/ntheory.html): Generate all prime numbers in the range [a, b).

This is a replication of `primes` to which we dispatch.

"""
function primerange(a::Sym,b)
    a = max(2,convert(Int, a))
    b = max(a+1, convert(Int, b))
    primes(a,b)
end
export primerange


using MPFR_wrap
using Test
using Base.MPFR

@testset "assignment" begin
    prec, atol = 512, 1e-100
    c = BigFloat(precision=prec)

    @test set!(c, BigFloat("3", precision=prec)) == BigFloat("3", precision=prec)
    @test set!(c, UInt32(3)) == BigFloat("3", precision=prec)
    @test set!(c, Int32(-3)) == BigFloat("-3", precision=prec)
    @test set!(c, -3.0) == BigFloat("-3", precision=prec)
end

@testset "basic arithmetic functions" begin
    prec, atol = 512, 1e-100
    c = BigFloat("1.0", precision=prec)

    add!(c, BigFloat("1.2", precision=prec), BigFloat("1.3", precision=prec))
    @test isapprox(c, BigFloat("2.5", precision=prec), atol=atol)
    add!(c, BigFloat("1.2", precision=prec), UInt32(2))
    @test isapprox(c, BigFloat("3.2", precision=prec), atol=atol)
    add!(c, BigFloat("1.2", precision=prec), Int32(-2))
    @test isapprox(c, BigFloat("-0.8", precision=prec), atol=atol)
    add!(c, BigFloat("1.2", precision=prec), -2.0)
    @test isapprox(c, BigFloat("-0.8", precision=prec), atol=atol)
    add!(c, BigFloat("1.2", precision=prec), BigInt(-4))
    @test isapprox(c, BigFloat("-2.8", precision=prec), atol=atol)

    sub!(c, BigFloat("1.2", precision=prec), BigFloat("1.3", precision=prec))
    @test isapprox(c, BigFloat("-0.1", precision=prec), atol=atol)
    sub!(c, BigFloat("1.2", precision=prec), UInt32(2))
    @test isapprox(c, BigFloat("-0.8", precision=prec), atol=atol)
    sub!(c, BigFloat("1.2", precision=prec), Int32(-2))
    @test isapprox(c, BigFloat("3.2", precision=prec), atol=atol)
    sub!(c, BigFloat("1.2", precision=prec), -2.0)
    @test isapprox(c, BigFloat("3.2", precision=prec), atol=atol)
    sub!(c, BigFloat("1.2", precision=prec), BigInt(-4))
    @test isapprox(c, BigFloat("5.2", precision=prec), atol=atol)
    sub!(c, UInt32(2), BigFloat("1.2", precision=prec))
    @test isapprox(c, BigFloat("0.8", precision=prec), atol=atol)
    sub!(c, Int32(-2), BigFloat("1.2", precision=prec))
    @test isapprox(c, BigFloat("-3.2", precision=prec), atol=atol)
    sub!(c, -2.0, BigFloat("1.2", precision=prec))
    @test isapprox(c, BigFloat("-3.2", precision=prec), atol=atol)
    sub!(c, BigInt(-4), BigFloat("1.2", precision=prec))
    @test isapprox(c, BigFloat("-5.2", precision=prec), atol=atol)

    sqr!(c, BigFloat("2", precision=prec))
    @test isapprox(c, BigFloat("4", precision=prec), atol=atol)

    sqrt!(c, BigFloat("4", precision=prec))
    @test isapprox(c, BigFloat("2", precision=prec), atol=atol)
    @test_throws DomainError sqrt!(c, BigFloat("-4", precision=prec))

    rec_sqrt!(c, BigFloat("4", precision=prec))
    @test isapprox(c, BigFloat("0.5", precision=prec), atol=atol)

    cbrt!(c, BigFloat("-27", precision=prec))
    @test isapprox(c, BigFloat("-3", precision=prec), atol=atol)

    rootn!(c, BigFloat("81", precision=prec), UInt32(4))
    @test isapprox(c, BigFloat("3", precision=prec), atol=atol)
    @test_throws DomainError rootn!(c, BigFloat("-4"), UInt32(4))

    pow!(c, BigFloat("3", precision=prec), BigFloat("3", precision=prec))
    @test isapprox(c, BigFloat("27", precision=prec), atol=atol)
    pow!(c, BigFloat("3", precision=prec), UInt32(3))
    @test isapprox(c, BigFloat("27", precision=prec), atol=atol)
    pow!(c, BigFloat("3", precision=prec), Int32(3))
    @test isapprox(c, BigFloat("27", precision=prec), atol=atol)
    pow!(c, BigFloat("3", precision=prec), BigInt(3))
    @test isapprox(c, BigFloat("27", precision=prec), atol=atol)
    pow!(c, UInt32(3), UInt32(3))
    @test isapprox(c, BigFloat("27", precision=prec), atol=atol)
    pow!(c, UInt32(3), BigFloat("3", precision=prec))
    @test isapprox(c, BigFloat("27", precision=prec), atol=atol)

    neg!(c, BigFloat("-27", precision=prec))
    @test isapprox(c, BigFloat("27", precision=prec), atol=atol)

    abs!(c, BigFloat("-27", precision=prec))
    @test isapprox(c, BigFloat("27", precision=prec), atol=atol)

    dim!(c, BigFloat("27", precision=prec), BigFloat("20", precision=prec))
    @test isapprox(c, BigFloat("7", precision=prec), atol=atol)
    @test_throws DomainError dim!(c, BigFloat("27"), BigFloat(NaN))

    mul_2!(c, BigFloat("3", precision=prec), UInt32(2))
    @test isapprox(c, BigFloat("12", precision=prec), atol=atol)
    mul_2!(c, BigFloat("3", precision=prec), Int32(2))
    @test isapprox(c, BigFloat("12", precision=prec), atol=atol)

    div_2!(c, BigFloat("12", precision=prec), UInt32(2))
    @test isapprox(c, BigFloat("3", precision=prec), atol=atol)
    div_2!(c, BigFloat("12", precision=prec), Int32(2))
    @test isapprox(c, BigFloat("3", precision=prec), atol=atol)
end

@testset "comparison" begin
    @test cmp_2exp(BigFloat("8"), UInt32(2), 4) < 0
    @test cmp_2exp(BigFloat("8"), Int32(2), 2) == 0

    @test number_p(BigFloat(3))

    @test !regular_p(BigFloat(NaN))

    @test unordered_p(BigFloat(3.0), BigFloat(NaN))
end

@testset "special functions" begin
    prec, atol = 512, 1e-100
    c, d = BigFloat(precision=prec), BigFloat(precision=prec)

    log!(c, BigFloat(ℯ, precision=prec))
    @test isapprox(c, BigFloat("1.0", precision=prec), atol=atol)

    y0!(c, BigFloat("1.0", precision=64))
    @test isapprox(c, BigFloat("0.088256964215676957"), atol=1e-15)

    y1!(c, BigFloat("1.0", precision=64))
    @test isapprox(c, BigFloat("-0.781212821300288716"), atol=1e-15)

    @test isapprox(j0!(c, BigFloat("1.0", precision=prec)),
                   jn!(d, Int32(0), BigFloat("1.0", precision=prec)), atol=atol)

    @test isapprox(y0!(c, BigFloat("1.0", precision=prec)),
                   yn!(d, Int32(0), BigFloat("1.0", precision=prec)), atol=atol)

    fma!(c, BigFloat("3", precision=prec), BigFloat("2", precision=prec),
         BigFloat("1", precision=prec))
    @test isapprox(c, BigFloat("7", precision=prec), atol=atol)

    hypot!(c, BigFloat("3", precision=prec), BigFloat("4", precision=prec))
    @test isapprox(c, BigFloat("5", precision=prec), atol=atol)
end

@testset "integer and reminder functions" begin
    prec, atol = 512, 1e-100
    c, d = BigFloat(precision=prec), BigFloat(precision=prec)

    ceil!(c, BigFloat("3.4", precision=prec))
    @test isapprox(c, BigFloat("4.0", precision=prec), atol=atol)

    floor!(c, BigFloat("3.4", precision=prec))
    @test isapprox(c, BigFloat("3.0", precision=prec), atol=atol)

    round!(c, BigFloat("3.4", precision=prec))
    @test isapprox(c, BigFloat(3.0, precision=prec), atol=atol)

    roundeven!(c, BigFloat(3.4, precision=prec))
    @test isapprox(c, BigFloat(3.0, precision=prec), atol=atol)

    rint!(c, BigFloat("3.4", precision=prec), Base.MPFR.MPFRRoundUp)
    @test isapprox(c, BigFloat("4.0", precision=prec), atol=atol)

    frac!(c, BigFloat("3.4", precision=prec), Base.MPFR.MPFRRoundUp)
    @test isapprox(c, BigFloat("0.4", precision=prec), atol=atol)

    modf!(c, d, BigFloat("3.4", precision=prec), Base.MPFR.MPFRRoundUp)
    @test isapprox(c, BigFloat("3.0", precision=prec), atol=atol)
    @test isapprox(d, BigFloat("0.4", precision=prec), atol=atol)

    @test integer_p(BigFloat("4.0"))
end


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    Chain(p1, p2, ..., pn)

A polygonal chain from a sequence of points `p1`, `p2`, ..., `pn`.
See https://en.wikipedia.org/wiki/Polygonal_chain.
"""
struct Chain{Dim,T,V<:AbstractVector{Point{Dim,T}}} <: Polytope{1,Dim,T}
  vertices::V
end

# specialize Polytope outer constructor to use standard vector
Chain(vertices::Vararg{<:Point}) = Chain(collect(vertices))

Chain(vertices::CircularVector) =
  Chain([collect(vertices); vertices[begin]])

nvertices(c::Chain) = length(c.vertices) - isclosed(c)

"""
    vertices(chain)

Return the vertices of the `chain`. In the case that the
chain is closed, the returned vector is circular and can
be indexed with arbitrarily negative or positive indices.
"""
function vertices(c::Chain)
  if isclosed(c)
    vs = @view c.vertices[begin:end-1]
    CircularVector(vs)
  else
    c.vertices
  end
end

"""
    segments(chain)

Return the segments linking consecutive points of the `chain`.
"""
function segments(c::Chain)
  vs = c.vertices
  n = length(vs)
  (Segment(view(vs, [i,i+1])) for i in 1:n-1)
end

"""
    isclosed(chain)

Tells whether or not the chain is closed.

A closed chain is also known as a ring.
"""
isclosed(c::Chain) = first(c.vertices) == last(c.vertices)

"""
   issimple(chain)

Tells whether or not the `chain` is simple.

A chain is simple when all its segments only
intersect at end points.
"""
function issimple(c::Chain)
  vs = c.vertices
  ss = collect(segments(c))
  for i in 1:length(ss)
    for j in i+1:length(ss)
      I = intersecttype(ss[i], ss[j])
      if !(I isa CornerTouchingSegments || I isa NonIntersectingSegments)
        return false
      end
    end
  end
  true
end

"""
    windingnumber(point, chain)

Winding number of `point` with respect to the `chain`.

## References

* Balbes, R. and Siegel, J. 1990. [A robust method for calculating
  the simplicity and orientation of planar polygons]
  (https://www.sciencedirect.com/science/article/abs/pii/0167839691900198)
"""
function windingnumber(p::Point, c::Chain)
  vₒ, vs = p, c.vertices
  sum(∠(vs[i], vₒ, vs[i+1]) for i in 1:length(vs)-1)
end

abstract type OrientationMethod end

struct WindingOrientation <: OrientationMethod end

struct TriangleOrientation <: OrientationMethod end

"""
    orientation(chain, [method])

Returns the orientation of the `chain` as either
counter-clockwise (CCW) or clockwise (CW).

Optionally, specify the orientation `method`:

* `WindingOrientation()` - Balbes, R. and Siegel, J. 1990.
* `TriangleOrientation()` - Held, M. 1998.

Default method is `WindingOrientation()`.

## References

* Balbes, R. and Siegel, J. 1990. [A robust method for calculating
  the simplicity and orientation of planar polygons]
  (https://www.sciencedirect.com/science/article/abs/pii/0167839691900198)
* Held, M. 1998. [FIST: Fast Industrial-Strength Triangulation of Polygons]
  (https://link.springer.com/article/10.1007/s00453-001-0028-4)
"""
orientation(c::Chain) = orientation(c, WindingOrientation())

function orientation(c::Chain{Dim,T}, ::WindingOrientation) where {Dim,T}
  # pick any segment
  x1, x2 = c.vertices[1:2]
  x̄ = centroid(Segment(x1, x2))
  w = windingnumber(x̄, c) - ∠(x1, x̄, x2)
  isapprox(w, T(π), atol=atol(T)) ? :CCW : :CW
end

function orientation(c::Chain{Dim,T}, ::TriangleOrientation) where {Dim,T}
  v = vertices(c)
  Δ(i) = signarea(v[1], v[i], v[i+1])
  a = mapreduce(Δ, +, 2:length(v)-1)
  a ≥ zero(T) ? :CCW : :CW
end

"""
    unique(chain)

Return a new `chain` without duplicate vertices.
"""
Base.unique(c::Chain) = unique!(deepcopy(c))

"""
    unique!(chain)

Remove duplicate vertices in the `chain`.
"""
function Base.unique!(c::Chain)
  # sort vertices lexicographically
  verts = c.vertices
  perms = sortperm(coordinates.(verts))

  # remove true duplicates
  keep = Int[]
  sorted = @view verts[perms]
  for i in 1:length(sorted)-1
    if sorted[i] != sorted[i+1]
      # save index in the original vector
      push!(keep, perms[i])
    end
  end
  push!(keep, last(perms))

  # preserve chain order
  sort!(keep)

  # update vertices in place
  copy!(verts, verts[keep])

  c
end

"""
    close!(chain)

Close the `chain`, i.e. repeat the first vertex
at the end of the vertex list.
"""
function close!(c::Chain)
  push!(c.vertices, first(c.vertices))
  c
end

"""
    open!(chain)

Open the `chain`, i.e. remove the last vertex.
"""
function open!(c::Chain)
  pop!(c.vertices)
  c
end

"""
    reverse!(chain)

Reverse the `chain` vertices in place.
"""
function Base.reverse!(c::Chain)
  reverse!(c.vertices)
  c
end

"""
    reverse(chain)

Reverse the `chain` vertices.
"""
Base.reverse(c::Chain) = reverse!(deepcopy(c))

"""
    angles(chain)

Return angles `∠(vᵢ-₁, vᵢ, vᵢ+₁)` at all vertices
`vᵢ` of the `chain`. If the chain is open, the first
and last vertices have no angles. Positive angles
represent a CCW rotation whereas negative angles
represent a CW rotation. In either case, the
absolute value of the angles returned is never
greater than `π`.
"""
function angles(c::Chain)
  θs = map(2:length(c.vertices)-1) do i
    p1 = c.vertices[i-1]
    p2 = c.vertices[i  ]
    p3 = c.vertices[i+1]
    ∠(p1, p2, p3)
  end

  if isclosed(c)
    p1 = c.vertices[end-1]
    p2 = c.vertices[1]
    p3 = c.vertices[2]
    pushfirst!(θs, ∠(p1, p2, p3))
  end

  θs
end

"""
    innerangles(chain)

Return inner angles of the *closed* `chain`. Inner
angles are always positive, and unlike `angles`
they can be greater than `π`.
"""
function innerangles(c::Chain{Dim,T}) where {Dim,T}
  @assert isclosed(c) "Inner angles only defined for closed chains"

  # correct sign of angles in case orientation is CW
  θs = orientation(c) == :CW ? -angles(c) : angles(c)

  [θ > 0 ? 2*T(π) - θ : -θ for θ in θs]
end

Base.view(c::Chain, inds) = Chain(view(vertices(c), inds))

function Base.show(io::IO, c::Chain)
  N = length(c.vertices)
  print(io, "$N-chain")
end

function Base.show(io::IO, ::MIME"text/plain", c::Chain{Dim,T}) where {Dim,T}
  v = c.vertices
  N = length(v)
  I, J = N > 10 ? (5, N-4) : (N, N+1)
  lines = [["  └─$(v[i])" for i in 1:I]
           (N > 10 ? ["  ⋮"] : [])
           ["  └─$(v[i])" for i in J:N]]
  println(io, "$N-chain{$Dim,$T}")
  print(io, join(lines, "\n"))
end


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 9c5d8972-7485-11eb-25f7-155fbbd40d98
using Revise

# ╔═╡ 2c24aaca-7483-11eb-05a9-6f847b7d09b7
using Distributions

# ╔═╡ 42bb4640-7483-11eb-0661-677c524c62b0
using Plots

# ╔═╡ eeb0be1c-7483-11eb-341f-b1996e9a53a6
using OmegaMH

# ╔═╡ 9d2f2714-749b-11eb-1403-e3a045df62ca
using ReplicaExchange

# ╔═╡ ebd11c9a-7484-11eb-2941-c78e3e22d260
using Random

# ╔═╡ 0464d3ec-7489-11eb-1081-d761307263db
using Pkg; Pkg.add("PyPlot")

# ╔═╡ 94d6f4e6-74ac-11eb-0871-a31b2df5526d
using PlutoUI

# ╔═╡ 128974e2-7483-11eb-03a0-15b2abc75d66
md"# MCMC Test"

# ╔═╡ 318e4a98-7483-11eb-1916-c9630ecf07e2
md"First, let's setup our target density"

# ╔═╡ 27ced392-7483-11eb-3d36-7b8277daadff
dist = MvNormal([0.0, 0.0], [1.0, 1.0])

# ╔═╡ 7e4e66ce-7483-11eb-238e-d15279086f3b
target_density(x, y) = pdf(dist, [x, y])

# ╔═╡ 9a7dc4f2-7483-11eb-36be-251be000793e
target_density(0.2, 0.4)

# ╔═╡ 68100d38-7483-11eb-07d5-45547ad45143
Plots.surface(-4:0.1:4, -4:0.1:4, target_density, c = :blues)

# ╔═╡ f744509a-7483-11eb-3168-9bff07e7b245
md"Now let's sample from the distribution using Metropolis Hastings";

# ╔═╡ 09fe4874-7484-11eb-28be-9f5df0586c87
state_init = rand(2)

# ╔═╡ 20537202-7484-11eb-1d70-a1bcbaa3c265
target_logdensity(state) = logpdf(dist, state);

# ╔═╡ 3b0ac92e-7484-11eb-2e90-cf94eb2e9731
num_samples = 100001;

# ╔═╡ 4343d3b0-7484-11eb-388b-576f50c83335
function propose_and_logratio(rng, state)
	prop_dist = MvNormal(state, [0.1, 0.1])
	sample = rand(rng, prop_dist)
	(sample, 0)
end

# ╔═╡ e84726c8-7484-11eb-1512-c5cee451507a
rng = Random.MersenneTwister(0)

# ╔═╡ f8e56030-7484-11eb-2bb6-afa2f952b085
samples = OmegaMH.mh(rng, target_logdensity, num_samples, state_init, propose_and_logratio)

# ╔═╡ 747691fe-7487-11eb-292e-2975ef75e7f0
Xs = [i[1] for i in samples]

# ╔═╡ 814cf4ae-7487-11eb-1b18-c11f124dd18a
Ys = [i[2] for i in samples]

# ╔═╡ ff22c6e2-7487-11eb-3bbe-db691feb921b
Plots.histogram2d(Xs, Ys, nbins = 100)

# ╔═╡ 22183a3e-7487-11eb-192c-4370f0b71496
md"Now let's create an example that breaks MH"

# ╔═╡ 30131820-7487-11eb-11de-9561b77139e8
dist_hard = MixtureModel([MvNormal([-2.0, -2.0], [0.5, 0.5]),
		                  MvNormal([2.0, 2.0], [1.5, 0.1])],
						[0.5, 0.5])

# ╔═╡ b9c69864-7487-11eb-3942-e146b6ae5ea4
Plots.surface(-4:0.1:4, -4:0.1:4, (x, y) -> pdf(dist_hard, [x, y]), c = :blues)

# ╔═╡ 1c2dedee-7489-11eb-3a2c-519ab256505f
state_init_hard = [-1.2, -1.3]

# ╔═╡ b0030bf6-74a0-11eb-14c8-4f09bcb0327c
target_density_hard(state) = pdf(dist_hard, state);

# ╔═╡ d51078da-7488-11eb-334f-c1a0ce33384c
target_logdensity_hard(state) = logpdf(dist_hard, state);

# ╔═╡ ff1e6132-7488-11eb-3a82-d1e9fa247b42
samples_hard = OmegaMH.mh(rng,
						  target_logdensity_hard,
						  num_samples,
						  state_init,
						  propose_and_logratio)

# ╔═╡ 519dc07e-7489-11eb-295b-3769fa285c29
Xs_hard = [i[1] for i in samples_hard]

# ╔═╡ 5a8e043c-7489-11eb-23a5-9ba3521900bd
Ys_hard = [i[2] for i in samples_hard]

# ╔═╡ 4d3c151c-7489-11eb-2352-65f743d197d0
Plots.histogram2d(Xs_hard, Ys_hard, nbins = 100)

# ╔═╡ dd7f08f4-748a-11eb-15ae-1f79ca5c1a0c
md"Unfortunately, this doesn;t work in this new space; the MCMC chain fails to jump from mode to mode"

# ╔═╡ 92926c20-749c-11eb-37f7-e7ec05a5ee41
# relax(density, temp) = state -> 1 - exp(- density(state)/temp)
relax(density, temp) = state -> density(state)^(1/temp)

# ╔═╡ 98c5076a-74a1-11eb-0f34-99ffa441dee6
logrelax(logdensity, temp) = state -> (1/temp) * logdensity(state)

# ╔═╡ d1910068-74a0-11eb-21e6-e1d2410c1646
target_density_hard_rlx = relax(target_density_hard, 2.0)

# ╔═╡ a2b3e42c-74a0-11eb-1678-7148d7776b6e
Plots.surface(-4:0.1:4, -4:0.1:4, (x, y) -> target_density_hard_rlx([x,y]), c = :blues)

# ╔═╡ cd57df24-74a2-11eb-166f-31b691e72e12
temps = [1.0, 2.0, 20.0, 200.0]

# ╔═╡ e5df8ac4-74a2-11eb-3a36-31d7ac95fba8
function simulate_n(temp, state, samples_per_swap)
	relaxed_logdensity = logrelax(target_logdensity_hard, temp)
	OmegaMH.mh(rng,
			   relaxed_logdensity,
			   samples_per_swap,
			   state,
			   propose_and_logratio)
end

# ╔═╡ b75ae81e-74a3-11eb-2ac0-efd1a5c21f21
simulate_1(temp, state, samples_per_swap) =
	simulate_n(temp, state, samples_per_swap)[end]

# ╔═╡ e208a218-74a3-11eb-28ac-0dec5c478e15
function evaluate(temp, state)
	relaxed_logdensity = logrelax(target_logdensity_hard, temp)
	relaxed_logdensity(state)
end

# ╔═╡ ee2bb440-748a-11eb-046b-e5a4741ffe99
samples_hard_wow = re!(rng, temps, 1, 10000, [deepcopy(state_init) for i = 1:length(temps)], simulate_n, simulate_1, evaluate)

# ╔═╡ 59576824-74b3-11eb-1a98-d773404b49e0
unpack(s) = [i[1] for i in s], [i[2] for i in s]

# ╔═╡ b870ea20-74a4-11eb-2ab7-6972d5d2a105
Plots.histogram2d(unpack(samples_hard_wow)..., nbins = 100)

# ╔═╡ 25be5166-74a8-11eb-1255-bf738e624e97
Plots.contour(-4:0.1:4, -4:0.1:4, (x, y) -> target_density_hard_rlx([x,y]), c = :blues)

# ╔═╡ de1d4ab8-74a7-11eb-16a1-c728c0e288b8
function mcmc_dynamics(pdfs, traces, lbub, xrng = -4:0.1:4, yrng = -4:0.1:4)
	# plt = Plots.contoour(pdfs[1])
	plots = []
	for i = 1:length(pdfs)
		plt = Plots.contour(xrng, yrng,
							  (x, y) -> pdfs[i]([x, y]),
							   c = :blues,
							   aspect_ratio = 1.0)
		Xs = [j[1] for j in traces[i]]
	    Ys = [j[2] for j in traces[i]]
		plot!(plt, Xs[lbub], Ys[lbub])
			# , alpha = range(0.0; stop = 1.0, length = length(lbub)))
		push!(plots, plt)
	end
	Plots.plot(plots..., layour = (4, 1))
end

# ╔═╡ 745f702a-74a8-11eb-1ada-cf83599e0b17
pdfs = [relax(target_density_hard, temp) for temp in temps]

# ╔═╡ 0603ea0c-74ad-11eb-29ef-d58f5a17aba4
swap_every = 10

# ╔═╡ 0a36105a-74ad-11eb-27c8-ed6919e3c5c8
samples_per_swap = 10000

# ╔═╡ 17ace2ae-74ad-11eb-34be-3be5f9eb6f28
niters = swap_every * samples_per_swap

# ╔═╡ b82be6b2-74a8-11eb-1df2-9d26ac88ccbb
traces = re_all!(rng, temps, swap_every, samples_per_swap, [deepcopy(rand(dist_hard)) for i = 1:length(temps)], simulate_n, simulate_1, evaluate)

# ╔═╡ 4eb016a8-74b3-11eb-0fb3-e78e6117666b
Plots.histogram2d(unpack(traces[1])..., nbins = 100)

# ╔═╡ 9e771a08-74ac-11eb-1260-ad7e2af51b1b
@bind lb Slider(1:niters)

# ╔═╡ b8ac427c-74ac-11eb-0eca-a7234b0ab59a
@bind ub Slider(lb:niters)

# ╔═╡ 98226b20-74a8-11eb-28d4-c5d50971fcd5
mcmc_dynamics(pdfs, traces, lb:ub)

# ╔═╡ bd699f26-74ac-11eb-019e-bb6ef2c119d2

# ╔═╡ Cell order:
# ╠═9c5d8972-7485-11eb-25f7-155fbbd40d98
# ╟─128974e2-7483-11eb-03a0-15b2abc75d66
# ╠═2c24aaca-7483-11eb-05a9-6f847b7d09b7
# ╠═42bb4640-7483-11eb-0661-677c524c62b0
# ╠═eeb0be1c-7483-11eb-341f-b1996e9a53a6
# ╠═9d2f2714-749b-11eb-1403-e3a045df62ca
# ╠═ebd11c9a-7484-11eb-2941-c78e3e22d260
# ╠═0464d3ec-7489-11eb-1081-d761307263db
# ╠═94d6f4e6-74ac-11eb-0871-a31b2df5526d
# ╟─318e4a98-7483-11eb-1916-c9630ecf07e2
# ╠═27ced392-7483-11eb-3d36-7b8277daadff
# ╠═7e4e66ce-7483-11eb-238e-d15279086f3b
# ╠═9a7dc4f2-7483-11eb-36be-251be000793e
# ╠═68100d38-7483-11eb-07d5-45547ad45143
# ╠═f744509a-7483-11eb-3168-9bff07e7b245
# ╠═09fe4874-7484-11eb-28be-9f5df0586c87
# ╠═20537202-7484-11eb-1d70-a1bcbaa3c265
# ╠═3b0ac92e-7484-11eb-2e90-cf94eb2e9731
# ╠═4343d3b0-7484-11eb-388b-576f50c83335
# ╠═e84726c8-7484-11eb-1512-c5cee451507a
# ╠═f8e56030-7484-11eb-2bb6-afa2f952b085
# ╠═747691fe-7487-11eb-292e-2975ef75e7f0
# ╠═814cf4ae-7487-11eb-1b18-c11f124dd18a
# ╠═ff22c6e2-7487-11eb-3bbe-db691feb921b
# ╠═22183a3e-7487-11eb-192c-4370f0b71496
# ╠═30131820-7487-11eb-11de-9561b77139e8
# ╠═b9c69864-7487-11eb-3942-e146b6ae5ea4
# ╠═1c2dedee-7489-11eb-3a2c-519ab256505f
# ╠═b0030bf6-74a0-11eb-14c8-4f09bcb0327c
# ╠═d51078da-7488-11eb-334f-c1a0ce33384c
# ╠═ff1e6132-7488-11eb-3a82-d1e9fa247b42
# ╠═519dc07e-7489-11eb-295b-3769fa285c29
# ╠═5a8e043c-7489-11eb-23a5-9ba3521900bd
# ╠═4d3c151c-7489-11eb-2352-65f743d197d0
# ╠═dd7f08f4-748a-11eb-15ae-1f79ca5c1a0c
# ╠═92926c20-749c-11eb-37f7-e7ec05a5ee41
# ╠═98c5076a-74a1-11eb-0f34-99ffa441dee6
# ╠═d1910068-74a0-11eb-21e6-e1d2410c1646
# ╠═a2b3e42c-74a0-11eb-1678-7148d7776b6e
# ╠═cd57df24-74a2-11eb-166f-31b691e72e12
# ╠═e5df8ac4-74a2-11eb-3a36-31d7ac95fba8
# ╠═b75ae81e-74a3-11eb-2ac0-efd1a5c21f21
# ╠═e208a218-74a3-11eb-28ac-0dec5c478e15
# ╠═ee2bb440-748a-11eb-046b-e5a4741ffe99
# ╠═59576824-74b3-11eb-1a98-d773404b49e0
# ╠═b870ea20-74a4-11eb-2ab7-6972d5d2a105
# ╠═25be5166-74a8-11eb-1255-bf738e624e97
# ╠═de1d4ab8-74a7-11eb-16a1-c728c0e288b8
# ╠═745f702a-74a8-11eb-1ada-cf83599e0b17
# ╠═0603ea0c-74ad-11eb-29ef-d58f5a17aba4
# ╠═0a36105a-74ad-11eb-27c8-ed6919e3c5c8
# ╠═17ace2ae-74ad-11eb-34be-3be5f9eb6f28
# ╠═b82be6b2-74a8-11eb-1df2-9d26ac88ccbb
# ╠═4eb016a8-74b3-11eb-0fb3-e78e6117666b
# ╠═9e771a08-74ac-11eb-1260-ad7e2af51b1b
# ╠═b8ac427c-74ac-11eb-0eca-a7234b0ab59a
# ╠═98226b20-74a8-11eb-28d4-c5d50971fcd5
# ╠═bd699f26-74ac-11eb-019e-bb6ef2c119d2


using Test
using SymDiff

@test simplify(:((x+0)/(y+0))) == :(x/y)


using(DelimitedFiles)

input = DelimitedFiles.readdlm("Day 5/input_day5.txt")

# part 1

function passport(x)

parsed_binary = reduce(replace, ["B"=>1, "F"=>0, "L"=>0, "R"=>1], init=x)
row = parse(Int, parsed_binary[1:7], base = 2)
column = parse(Int, parsed_binary[8:10], base = 2)
seatid = row * 8 + column
return seatid

end

@time begin

seatids = passport.(input) #using . to vectorize bc julia is op

maximum(seatids)

end

# part 2

@time begin

seat_range = minimum(seatids):maximum(seatids)

seat_diff = setdiff(seat_range, seatids)

end


@testset "Triangle.jl Interface" begin
    @testset "Basic Triangulation" begin

        @testset "basic_triangulation_vertices" begin
            points = Array{Float64,2}([0. 0.; 1. 0.; 0. 1.])
            @test Triangle.basic_triangulation_vertices(points)[1] == points
        end

        @testset "basic_triangulation with point map" begin
            points = Array{Float64,2}([0. 0.; 1. 0.; 0. 1.])
            points_map = [1, 2, 3]
            @test Triangle.basic_triangulation(points,points_map)[1] == points_map
        end

        @testset "basic_triangulation_vertices with point map" begin
            points = Array{Float64,2}([0. 0.; 1. 0.; 0. 1.])
            points_map = [1, 2, 3]
            @test Triangle.basic_triangulation_vertices(points,points_map)[1] == points
        end

        # Triforce
        @testset "basic_triangulation with unordered point map" begin
            points = Array{Float64,2}([0. 0.; 4. 0.; 2. 3.; 8. 0.; 6. 3.; 4. 6.])
            points_map = [1, 2, 4, 3, 5, 6]
            result_tri = [[1, 2, 4],[4, 2, 5],[5, 6, 4],[3, 5, 2]]
            @test Triangle.basic_triangulation(points,points_map) == result_tri
        end

        # Tetris L (no boundary)
        @testset "basic_triangulation on L tetris piece" begin
            points = [0. 0.; 0. 3.; 1. 3.; 1. 1.; 2. 1.; 2. 0.]
            points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
            triangles = Triangle.basic_triangulation(points,points_map)
            @test length(triangles) == 5
        end

        @testset "basic_triangulation_vertices with point map on L tetris piece" begin
            points = [0. 0.; 0. 3.; 1. 3.; 1. 1.; 2. 1.; 2. 0.]
            points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
            triangles = Triangle.basic_triangulation_vertices(points,points_map)
            @test length(triangles) == 5
        end

         # Tetris L (all boundary)
        @testset "constrained_triangulation" begin
            points = [0. 0.; 0. 3.; 1. 3.; 1. 1.; 2. 1.; 2. 0.]
            points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
            edges_list = Array{Int64,2}([1 2; 2 3; 3 4; 4 5; 5 6; 6 1])
            triangles = Triangle.constrained_triangulation(points,points_map,edges_list)
            @test length(triangles) == 4
        end

        @testset "constrained_triangulation_vertices" begin
            points = [0. 0.; 0. 3.; 1. 3.; 1. 1.; 2. 1.; 2. 0.]
            points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
            edges_list = Array{Int64,2}([1 2; 2 3; 3 4; 4 5; 5 6; 6 1])
            triangles = Triangle.constrained_triangulation_vertices(points,points_map,edges_list)
            @test length(triangles) == 4
        end

        # Tetris L (custom boundary)
        @testset "constrained_triangulation with boundary" begin
            points = [0. 0.; 0. 3.; 1. 3.; 1. 1.; 2. 1.; 2. 0.]
            points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
            edges_list = Array{Int64,2}([1 2; 2 3; 3 4; 4 5; 5 6; 6 1])
            edge_boundary = [false, false, true, true, false, false]
            triangles = Triangle.constrained_triangulation(points,points_map,edges_list,edge_boundary)
            @test length(triangles) == 4
        end

        @testset "constrained_triangulation_vertices with boundary" begin
            points = [0. 0.; 0. 3.; 1. 3.; 1. 1.; 2. 1.; 2. 0.]
            points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
            edges_list = Array{Int64,2}([1 2; 2 3; 3 4; 4 5; 5 6; 6 1])
            edge_boundary = [false, false, true, true, false, false]
            triangles = Triangle.constrained_triangulation_vertices(points,points_map,edges_list,edge_boundary)
            @test length(triangles) == 4
        end

        # Triforce (without center triangle)
        @testset "constrained_triangulation with boundary and holes" begin
            points = [0. 0.; 4. 0.; 2. 3.; 8. 0.; 6. 3.; 4. 6.]
            points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
            edges_list = Array{Int64,2}([1 2; 2 3; 3 1; 2 4; 4 5; 5 2; 3 5; 5 6; 6 3])
            edge_boundary = [false,true,false,false,false,true,true,false,false]
            holes_list = [4. 2.]
            triangles = Triangle.constrained_triangulation(points,points_map,edges_list,edge_boundary,holes_list)
            @test length(triangles) == 3
        end

        @testset "constrained_triangulation_vertices with boundary and holes" begin
            points = [0. 0.; 4. 0.; 2. 3.; 8. 0.; 6. 3.; 4. 6.]
            points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
            edges_list = Array{Int64,2}([1 2; 2 3; 3 1; 2 4; 4 5; 5 2; 3 5; 5 6; 6 3])
            edge_boundary = [false,true,false,false,false,true,true,false,false]
            holes_list = [4. 2.]
            triangles = Triangle.constrained_triangulation_vertices(points,points_map,edges_list,edge_boundary,holes_list)
            @test length(triangles) == 3
        end
    end
end


"""
    ConfMat(labels)
    ConfMat(labels, y, ŷ)

# Example
```julia
julia> labels = [:dog, :cat]
2-element Vector{Symbol}:
 :dog
 :cat

 julia> y = rand(labels, 100);

 julia> ŷ = rand(labels, 100);

 julia> cm = ConfMat(labels, y, ŷ)
 ConfMat{2,Symbol}
 Actual class ╲ Predicted class │ :dog  :cat
 ───────────────────────────────┼───────────
 :dog                           │   30    24
 :cat                           │   21    25

```
"""
struct ConfMat{N, T}
    labels
    counts
end

function ConfMat(labels::Vector, counts::NamedMatrix)
    N = length(labels)
    T = eltype(labels)
    ConfMat{N, T}(labels, counts)
end

function ConfMat(labels::Vector)
    #labels = vec(labels)
    n = length(labels)
    c = zeros(Int, n, n)
    c = NamedArray(c, (labels, labels), ("Actual class", "Predicted class"))
    ConfMat(labels, c)
end

function ConfMat(labels::T, y::T, ŷ::T) where {E, T <: Vector{E}}
    cm = ConfMat(labels)
    update!(cm, y, ŷ)
end

function update!(cm::ConfMat{2,}, y::OneHotMatrix, ŷ::AbstractMatrix;
                 threshold = 0.5)
    y = onecold(y, cm.labels)
    y = labidx(y, cm.labels)
    ŷ = (@. Int(ŷ > threshold) + 1) |> vec
    _update!(cm, y, ŷ)
end

function update!(cm::ConfMat{N,}, y::OneHotMatrix, ŷ::AbstractMatrix) where N
    y = onecold(y, cm.labels)
    ŷ = onecold(ŷ, cm.labels)
    update!(cm, y, ŷ)
end

function update!(cm::ConfMat{N, T}, a::Vector{T}, p::Vector{T}) where {N, T}
    a = labidx(a, cm.labels)
    p = labidx(p, cm.labels)
    _update!(cm, a, p)
end

function _update!(cm::ConfMat, y::Vector{Int}, ŷ::Vector{Int})
    length(ŷ) == length(y) || throw(DimensionMismatch(
        "Number of actual values and number of predicted values mismatch."))
    for i in eachindex(ŷ)
        @inbounds r = y[i]
        @inbounds c = ŷ[i]
        @inbounds cm.counts[r, c] += 1
    end
    cm
end

"""
    labidx(v, l)

"""
function labidx(v, l)
    i = indexin(v, l)
    all(.!isnothing.(i)) || throw(ArgumentError(
        "Unknown labels in supplied data."))
    Vector{Int}(i)
end

summary(cm::ConfMat{N, T}) where {N, T} =
    string("ConfMat{", N, ",", T, "}")

Base.print(cm::ConfMat) = print(cm.counts)

function Base.show(io::IO, cm::ConfMat)
    println(io, summary(cm))
    #show(io, cm.labels)
    tmp = IOBuffer()
    show(tmp, cm.counts)
    #println(io)
    sh = String(take!(tmp))
    for l ∈ split(sh, "\n")[2:end]
        println(io, l)
    end
end


# a simple implementation avoiding vectors should make it simpler to port to Python and Julia
function CrossProduct(v, i, j, result)
    @inbounds result[1] = v[i][2]*v[j][3]-v[i][3]*v[j][2]
	@inbounds result[2] = v[i][3]*v[j][1]-v[i][1]*v[j][3]
    @inbounds result[3] = v[i][1]*v[j][2]-v[i][2]*v[j][1]
	@inbounds result[4] = 0
    return 0
end

function Thrust(particleArray)
    entries = length(particleArray)
    PartVector = MVector{4, Float64}(0., 0., 0., 0.)
    RefVector = MVector{4, Float64}(0., 0., 0., 0.)
    SumVector = MVector{4, Float64}(0., 0., 0., 0.)
    maxVal = 0.0

    if (entries<2)
        println(stderr, "Thrust : Not enough entries")
        return -1.0
    end
    # start calcualting the thrust
    @inbounds for i in 1:entries
        SumVector += particleArray[i]
    end

    # now do the thrust
    @inbounds for i = 1:entries-1
        @inbounds for j = i+1:entries
            CrossProduct(particleArray, i, j, RefVector)
			RefVector ./= sqrt(sum(RefVector.^2))
			PartVector .*= 0
            # Add all momenta with sign; two choices for each reference particle.
            @inbounds for k = 1:entries
                if k != i && k != j
                    dotProduct = particleArray[k] ⋅ RefVector
					#print(sign(dotProduct), "\t", particleArray[k], "\t", PartVector)
					#println(sign(dotProduct) * particleArray[k])
                    PartVector .+= sign(dotProduct) * particleArray[k]
                end
            end
            c1 = 0
            c2 = 0
            for l = 1:4
                if l == 0
                    c1 = 1
                    c2 = 1
                elseif l == 1
                    c1 = 1
                    c2 = -1
                elseif l == 2
                    c1 = -1
                    c2 = 1
                else
                    c1 = -1
                    c2 = -1
                end

				tempVal = 0.0
                @inbounds for n=1:3 # only copy three of the four as we overwrite the fourth element again ...
                    tempVal += (PartVector[n] + c1*particleArray[i][n] + c2*particleArray[j][n])^2
                end
                tempVal = sqrt(tempVal)
                if (tempVal > maxVal)
                    maxVal = tempVal
                end
            end
        end
    end
    return maxVal / SumVector[4]
end


using CompactBasisFunctions
using Test

include("vandermonde_tests.jl")
include("basis_tests.jl")
include("bernstein_tests.jl")
include("chebyshev_tests.jl")
include("lagrange_tests.jl")
include("legendre_tests.jl")


##### Alias Table #####

immutable AliasTable <: Sampleable{Univariate,Discrete}
    accept::Vector{Float64}
    alias::Vector{Int}
    isampler::RandIntSampler
end
numcategories(s::AliasTable) = length(s.accept)

function make_alias_table!(a::AbstractVector{Float64})
    # input probabilities via a, which is then
    # overriden by acceptance probabilities
    # and used internally by the AliasTable instance
    #
    n = length(a)
    for i=1:n
        @inbounds a[i] *= n
    end
    alias = Array(Int,n)
    larges = Int[]
    smalls = Int[]

    for i = 1:n
        acci = a[i]
        if acci > 1.0
            push!(larges,i)
        elseif acci < 1.0
            push!(smalls,i)
        end
    end

    while !isempty(larges) && !isempty(smalls)
        s = pop!(smalls)
        l = pop!(larges)
        @inbounds alias[s] = l
        @inbounds a[l] = (a[l] - 1.0) + a[s]
        if a[l] > 1
            push!(larges,l)
        else
            push!(smalls,l)
        end
    end

    # this loop should be redundant, except for rounding
    for s in smalls
        @inbounds a[s] = 1.0
    end
    AliasTable(a, alias, RandIntSampler(n))
end

function AliasTable{T<:Real}(probs::AbstractVector{T})
    n = length(probs)
    n > 0 || error("The input probability vector is empty.")
    a = Array(Float64, n)
    for i = 1:n
        @inbounds a[i] = probs[i]
    end
    make_alias_table!(a)
end

rand(s::AliasTable) =
    (i = rand(s.isampler); u = rand(); @inbounds r = u < s.accept[i] ? i : s.alias[i]; r)

show(io::IO, s::AliasTable) = @printf(io, "AliasTable with %d entries", numcategories(s))


# File to check and compare qdldl routine against built-in cholesky routine
include("qdldl.jl")

using QDLDL, Base.Test

nDense = 1000
nSparse = 1000
numProblems = nDense + nSparse

rng = Random.MersenneTwister(131123)

function generateSparsePosDefMatrix(rng,n::Int64,density::Float64)
      X = sprand(rng,n,n,density)
      X = 0.5(X+X')
      # make random matrix pos def
      X = X+(2*n+1)*speye(n);
      return X
  end

function generatePosDefMatrix(rng,n::Int64)
      X = rand(rng,n,n)
      Q, R = qr(X)
      eigs = rand(rng,n).*(5 .-0.1) .+ 0.1
      X = Q*Matrix(Diagonal(eigs))*Q'
      X = 0.5*(X+X')
      return X
  end

solveTimesQDLDL = zeros(numProblems)
solveTimesCHOL = zeros(numProblems)

@testset "QDLDL routine for random problems" begin

  for iii=1:numProblems

    dim = rand(rng,50:1:200);

    if iii <=nSparse
      density=rand(rng,0.1:0.1:0.4)
      A = generateSparsePosDefMatrix(rng,dim,density)
    else
      A = sparse(generatePosDefMatrix(rng,dim))
    end
    xtrue = rand(rng,dim)
    btrue = A*xtrue

    # solve with QDLDL
    tic()
    F = QDLDL.qdldl(A)
    x_qdldl = solve(F,btrue)
    solveTimesQDLDL[iii] = toq()

    # solve with cholesky
    tic()
    W = cholfact(A)
    x_chol = W\btrue
    solveTimesCHOL[iii] = toq()

    # check results
    @test norm(xtrue-x_qdldl,Inf) < 1e-9
    @test norm(x_chol-x_qdldl,Inf) < 1e-9

  end
end

# print mean solve times for both routines
println("\n\n$(nSparse) SPARSE PROBLEMS\n"*"-"^30*"\nMean solve time QLDL routine: \t$(mean(solveTimesQDLDL[1:nSparse])*1000)ms,\nMean solve Cholesky routine: \t$(mean(solveTimesCHOL[1:nSparse])*1000)ms.\n"*"-"^30)
println("\n$(nDense) DENSE PROBLEMS\n"*"-"^30*"\nMean solve time QLDL routine: \t$(mean(solveTimesQDLDL[nSparse+1:end])*1000)ms,\nMean solve Cholesky routine: \t$(mean(solveTimesCHOL[nSparse+1:end])*1000)ms.\n"*"-"^30)


using ADCME
using PyCall
using LinearAlgebra
using PyPlot
using Random
Random.seed!(233)

add_source = load_op_and_grad("../build/libADSeismic","add_source", multiple=true)
################## End Load Operator ##################
# tf.debugging.set_log_device_placement(true)
# TODO: specify your input parameters
nx = 20
ny = 20
sigmaxx = constant(ones((nx+2)*(ny+2)))
sigmayy = constant(ones((nx+2)*(ny+2)))
sigmaxy = constant(ones((nx+2)*(ny+2)))
vx = constant(ones((nx+2)*(ny+2)))
vy = constant(ones((nx+2)*(ny+2)))
srci = constant(rand(1:5, 100))
srcj = constant(rand(1:5, 100))
srctype = constant(rand(0:4,100))
srcv = constant(rand(100))
u = add_source(sigmaxx,sigmayy,sigmaxy,vx,vy,srci,srcj,srctype,constant(nx),constant(ny),srcv)
# error()
sess = Session()
init(sess)
run(sess, u)
# error()

# TODO: change your test parameter to `m`
# gradient check -- v
function scalar_function(m)
    c = sum(m^2)
    return sum(sum.(add_source(sigmaxx + c,sigmaxy + c,sigmayy + c,vx + c,vy + c,srci,srcj,srctype,constant(nx),constant(ny),m).^2))
end

# TODO: change `m_` and `v_` to appropriate values
m_ = constant(rand(100))
v_ = rand(100)
y_ = scalar_function(m_)
dy_ = gradients(y_, m_)
ms_ = Array{Any}(undef, 5)
ys_ = Array{Any}(undef, 5)
s_ = Array{Any}(undef, 5)
w_ = Array{Any}(undef, 5)
gs_ =  @. 1 / 10^(1:5)

for i = 1:5
    g_ = gs_[i]
    ms_[i] = m_ + g_*v_
    ys_[i] = scalar_function(ms_[i])
    s_[i] = ys_[i] - y_
    w_[i] = s_[i] - g_*sum(v_.*dy_)
end

sess = Session()
init(sess)
sval_ = run(sess, s_)
wval_ = run(sess, w_)
close("all")
loglog(gs_, abs.(sval_), "*-", label="finite difference")
loglog(gs_, abs.(wval_), "+-", label="automatic differentiation")
loglog(gs_, gs_.^2 * 0.5*abs(wval_[1])/gs_[1]^2, "--",label="\$\\mathcal{O}(\\gamma^2)\$")
loglog(gs_, gs_ * 0.5*abs(sval_[1])/gs_[1], "--",label="\$\\mathcal{O}(\\gamma)\$")

plt.gca().invert_xaxis()
legend()
xlabel("\$\\gamma\$")
ylabel("Error")
savefig("gradtest.png")


using MicrostructureNoise, Distributions

using Random, LinearAlgebra

Random.seed!(10)
η = 0.01

n = 1000
tt = 2*[0.0; sort(rand(n-1)); 1.0]
T = tt[end]
σ(t) = 1.0 + sin(t)
# ad hoc euler
x = [0.0; cumsum(σ.(tt[2:end]).*randn(n).*sqrt.(diff(tt)))]
y = x + sqrt(η)*randn(n+1)
prior = MicrostructureNoise.Prior(
N = 20,

α1 = 0.0,
β1 = 0.0,

αη = 0.3,
βη = 0.3,

Πα = LogNormal(1., 0.5),
μ0 = 0.0,
C0 = 5.0
)

α = 0.3
σα = 0.1
td, θs, ηs, αs, p = MicrostructureNoise.MCMC(prior, tt, y, α, σα, 1500)

@test_throws DimensionMismatch MicrostructureNoise.MCMC(prior, tt, y[2:end], α, σα, 10)

@testset "skipfirst" begin
    @test_logs (:info, "skip observation y[1] at t[1] (skipfirst == true)") MicrostructureNoise.MCMC(prior, tt, y, α, σα, 10; skipfirst = true)
    @test_throws DimensionMismatch MicrostructureNoise.MCMC(prior, tt, y[3:end], α, σα, 10; skipfirst = true)

    Random.seed!(10)
    p1 = MicrostructureNoise.MCMC(prior, tt, [y[2];y[2:end]], α, σα, 10; skipfirst = true)
    Random.seed!(10)
    p2 = MicrostructureNoise.MCMC(prior, tt, y[2:end], α, σα, 10; skipfirst = true)
    @test p1[1] == p2[1]
    @test p1[2][end] == p2[2][end]
end

posterior = MicrostructureNoise.posterior_volatility(td, θs)

# plot(MicrostructureNoise.piecewise(posterior.post_t[1:end], posterior.post_mean_root[1:end])...)
# plot!(tt,1+sin.(tt), ylim=(0,2))

# Test posterior mean versus truth

@test abs(mean(ηs)-η) < 0.2*η
@test norm(diff(td).*(mean(θs, dims=2) - σ.(td[1:end-1]).^2)) < 1


using DataFrames, GoogleCharts, Dates
render = identity

year_sales_expenses = DataFrame(
    Year     = map(string, 2004:2007),
    Sales    = [1000, 1170, 660, 1030],
    Expenses = [400, 460,1120, 540]
)

## Area Chart

area_data = year_sales_expenses

options = [
           :title => "Company Performance",
           :hAxis => [:title => "Year",  :titleTextStyle => [:color => "red"]]
           ];
chart = area_chart(area_data, options)
render(chart)
## or
area_chart(area_data, title="Company Performance", haxis=[:title => "Year",  :titleTextStyle => [:color => "red"]])

## Bar Chart
bar_data = area_data
chart = bar_chart(bar_data, options)
render(chart)

## Bubble Chart

bubble_data = DataFrame(
    ID              = ["CAN","DEU","DNK"],
    Life_Expectancy = [80.66, 79.84, 78.6],
    Fertility_Rate  = [1.67, 1.36, 1.84],
    Region          = ["North America", "Europe", "Europe"],
    Population      = [33739900, 81902307, 5523095]
)

options = [
           :title => "Correlation between life expectancy, fertility rate and population of some world countries (2010)",
           :hAxis => [:title => "Life Expectancy"],
           :vAxis => [:title => "Fertility Rate"],
           :bubble=> [:textStyle => [:fontSize => 11]]
           ];

chart = bubble_chart(bubble_data, options)
render(chart)
## or
bubble_chart(bubble_data,
           title = "Correlation between life expectancy, fertility rate and population of some world countries (2010)",
           hAxis = [:title => "Life Expectancy"],
           vAxis = [:title => "Fertility Rate"],
           bubble= [:textStyle => [:fontSize => 11]]
           )

## candlestick_chart,
candle_data = DataFrame(
    days    = ["Mon", "Tue", "Wed", "Thu", "Fri"],
    low     = [ 20, 28, 38, 45, 50],
    opening = [ 31, 50, 55, 77, 80],
    closing = [ 50, 55, 77, 80, 50],
    high    = [ 77, 77, 96, 90, 90]
)

chart = candlestick_chart(candle_data, Dict())
render(chart)

## column_chart,
column_data = year_sales_expenses
options = [:title => "Company performance",
           :hAxis => [:title=> "Year", :titleTextStyle=> [:color => "red"]]]

chart = column_chart(column_data, options)
render(chart)
## combo_chart,

combo_data = DataFrame(
    Month = ["2004/05","2005/06","2006/07","2007/08","2008/09"],
    Bolivia = [165, 135, 157, 139, 136],
    Madagascar = [938, 1120, 1167, 1110, 691],
    PapauNewGuinea = [522, 599, 587, 615, 629],
    Rwand = [998, 1268, 807, 968, 1026],
    Average = [614.6, 682, 623, 609.4, 569.6]
)

options = [
           :title => "Monthly Coffee Production by Country",
           :vAxis=> [:title=> "Cups"],
           :hAxis=> [:title=> "Month"],
           :seriesType=> "bars",
           :series=> ["{}","{}","{}","{}",[:type=> "line"]]
        ];
chart = combo_chart(combo_data, options)
render(chart)
## gauge_chart,
 gauge_data = DataFrame(
     Label=["Memory", "CPU", "Network"],
     Value = [80, 55, 68]
     )

options = [
           :width=> 400, :height=> 120,
           :redFrom=> 90, :redTo=> 100,
           :yellowFrom=>75, :yellowTo=> 90,
           :minorTicks=> 5
        ]
chart = gauge_chart(gauge_data, options)
render(chart)
## geo_chart,

geo_data = DataFrame(
    Country = ["Germany", "United States", "Brazil", "Canada", "France", "RU"],
    Popularity = [200, 300, 400, 500, 600, 700]
)
options = Dict()
chart = geo_chart(geo_data, options)
render(chart)

## Line Chart
line_data = area_data
options = [:title => "Company Performance"]
chart = line_chart(line_data, options)
render(chart)

## pie_chart,
pie_data = DataFrame(
    Task = ["Work", "Eat", "Commute", "Watch TV", "Sleep"],
    HoursPerDay = [11,2,2,2,7]
)
options = [:title => "My Daily Activities"]
chart = pie_chart(pie_data, options)
render(chart)

## Scatter Chart

scatter_data = DataFrame(
    Age = [8,4,11,4,3,6.5],
    Weight = [12, 5.5, 14, 5, 3.5, 7]
)
options = [:title => "Age vs. Weight comparison",
           :hAxis =>  [:title => "Age", :minValue => 0, :maxValue => 15],
           :vAxis => [:title => "Weight", :minValue => 0, :maxValue => 15]
]

chart = scatter_chart(scatter_data, options)
render(chart)

## stepped_area__chart,

stepped_area_data = DataFrame(
                              Director = ["Alfred Hitchcock (1935)", "Ralph Thomas (1959)","Don Sharp (1978)", "James Hawes (2008)"],
                              RottenTomatoes = [8.4, 6.9, 6.5, 4.4],
                              IMDB = [7.9, 6.5, 6.4, 6.2]
                              )
options = [:title=>"The decline of 'The 39 Steps'", :vAxis => [:title=>"Accumulated Rating"], :isStacked => true]
chart = stepped_area_chart(stepped_area_data, options)
render(chart)
## table_chart,
### XXX Need a way to do value/format for Salary, but perhaps this is best done in julia?
table_data = DataFrame(
    Name = ["Mike", "Jim", "Alice", "Bob"],
    Salary = [10000, 8000, 12500, 7000],
    FullTimeEmployee = [true, false, true, true]
)
options = [:showRowNumber => true]
chart = table_chart(table_data, options)
render(chart)

## tree_chart,
tree_data = DataFrame(
                      Location = ["Global", "America","Europe","Asia","Australia","Africa","Brazil","USA","Mexico","Canada","France","Germany","Sweden","Italy","UK","China","Japan","India","Laos","Mongolia","Israel","Iran","Pakistan","Egypt","S. Africa","Sudan","Congo","Zair"],
                      Parent = [nothing, "Global","Global","Global","Global","Global","America","America","America","America","Europe","Europe","Europe","Europe","Europe","Asia","Asia","Asia","Asia","Asia","Asia","Asia","Asia","Africa","Africa","Africa","Africa","Africa"],
                      MarketTradeVolume_Size = [0,0,0,0,0,0,11,52,24,16,42,31,22,17,21,36,20,40,4,1,12,18,11,21,30,12,10,8],
                      MarketIncreaseDecrease_color = [0,0,0,0,0,0,10,31,12,-23,-11,-2,-13,4,-5,4,-12,63,34,-5,24,13,-52,0,43,2,12,10]
)
options = [
          :minColor=> "#f00",
          :midColor=> "#ddd",
          :maxColor=> "#0d0",
          :headerHeight=> 15,
          :fontColor=> "black",
          :showScale=> true
           ]
chart = tree_chart(tree_data, options)
render(chart)

## annotated_time_line,
## extra hassle to get ymd into data frame
annotated_data = DataFrame()
annotated_data[:Year] = [Date(2008, 1, i) for i in 1:6]

tmp = DataFrame(
                SoldPencils = [30000, 14045, 55022,75284, 41476, 33322],
                Title1 = [nothing,nothing,nothing,nothing,"bought pens",nothing],
                Text1 = [nothing,nothing,nothing,nothing,"bought 200k pens",nothing],
                SoldPens = [40675, 20374,50766, 14334, 66467, 39643],
                title2 = [nothing,nothing,nothing,"Out of Stock",nothing, nothing],
                text2 = [nothing,nothing,nothing,"Ran out of stock on pens at 4pm",nothing, nothing]
                )
annotated_data = vcat(annotated_data, tmp)
options = [:displayAnnotations=>true]
chart = annotated_time_line(annotated_data, options)
render(chart)

## intensity_map,

## Motion chart: Doesn't render locally without some adjustments...
## http://helpx.adobe.com/flash-player/kb/communicate-internet-flash-content.html
d = DataFrame()
d[:Fruit] = ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"]
j1, j2 = Date(1988, 1, 1), Date(1989, 1, 1)
d[:Date] = [j1, j1, j1, j2, j2, j2]
d[:Sales] = [1000, 1150, 300, 1200, 750, 788]
d[:Expenses] = [300, 200, 250, 400, 150, 617]
e,w = "East", "West"
d[:Location] = [e, w, w, e, w, w]

chart = motion_chart(d, nothing)
render(chart)

## org_chart,

org_data = DataFrame(
    Name = ["Mike", "Jim", "Alice", "Bob", "Carol"],
    Manager = ["", "Mike", "Mike", "Jim", "Bob"],
    Tooltip = ["The presidient", "VP", "", "Bob Sponge", ""]
)

options = [:allowHtml=>true]
chart = org_chart(org_data, options)
render(chart)

## image_spark_line ## Deprecated by google
spark_data = DataFrame(
    Revenue = [435, 438, 512, 460, 491, 387, 552,511, 505, 509],
    Licenses = [132, 131, 137, 142, 140, 139, 147, 146, 151, 149],
)

options = [:width=> 120, :height=> 40, :showAxisLines=> false,  :showValueLabels=> false, :labelPosition=> "left"]

chart = image_spark_line(spark_data, options)
render(chart)

## Our plot interface
plot(sin, 0, 2pi)
plot([sin, u -> cos(u) > 0 ? 0.0 : NaN], 0, 2pi, [:lineWidth=>5,
	                                        :title=>"A function and where its derivative is positive",
						:vAxis=>[:minValue => -1.2, :maxValue => 1.2]
						])
plot([sin, u -> cos(u) > 0 ? sin(u) : NaN], 0, 2pi, [:lineWidth=>5,
	                                        :title=>"A function and where its derivative is positive",
						:vAxis=>[:minValue => -1.2, :maxValue => 1.2]
						])

## stupid boxplot interface
x = randn(20)
y = rand(20)
boxplot(x)
## might swap x and y order
boxplot([:x => x, :y=>y])
## a bit better
d = DataFrame(data=[x, y],
              nms=reshape([repmat(["x"], length(x),1), repmat(["y"], length(y),1)], length([x,y]))
              )
gp = groupby(d, :nms)
boxplot(gp)

## histogram
histogram(x)
histogram(x, n=50)

## From the README

## parametric plots
plot((x -> sin(2x), cos), 0, 2pi)

## scatter plots
x = linspace(0, 1., 20)
y = rand(20)
scatter(x, y)

using RDatasets
mtcars = dataset("datasets", "mtcars")
scatter(:WT, :MPG, mtcars)

iris = dataset("datasets", "iris")
d=iris[:, [2,3,5]]          ## in the order  "x, y, grouping factor"
gp = groupby(d, :Species)
scatter(gp)

## surface plots (YMMV)
#surfaceplot((x,y) -> x^2 + y^2, linspace(0,1,20), linspace(0,2,20))