### A Pluto.jl notebook ###
# v0.14.1

using Markdown
using InteractiveUtils

# ╔═╡ 63ba08cc-59a8-11eb-0a0f-27efac60d779
using Pkg, DrWatson

# ╔═╡ 6db218c6-59a8-11eb-2a8b-7107354cf590
begin
	#@quickactivate "StatisticalRethinkingStan"
	using StanSample
	using StatisticalRethinking
end

# ╔═╡ 51fc19b8-59a8-11eb-2214-15aca59b807b
md" ## Clip-08-07-12s.jl"

# ╔═╡ f346a9a1-de24-40ef-bf2f-eb046ed800d5
#@quickactivate "StatisticalRethinkingStan"

# ╔═╡ 8aaa4bcc-59a8-11eb-2003-f1213b116565
begin
	df = CSV.read(sr_datadir("rugged.csv"), DataFrame)
	dropmissing!(df, :rgdppc_2000)
	dropmissing!(df, :rugged)
	df.log_gdp = log.(df[:, :rgdppc_2000])
	df.log_gdp_s = df.log_gdp / mean(df.log_gdp)
	df.rugged_s = df.rugged / maximum(df.rugged)
	df.cid = [df.cont_africa[i] == 1 ? 1 : 2 for i in 1:size(df, 1)]
	data = (N = size(df, 1), K = length(unique(df.cid)),
		G = df.log_gdp_s, R = df.rugged_s, cid=df.cid)
	PRECIS(df[:, [:log_gdp, :log_gdp_s, :rugged, :rugged_s, :cid]])
end

# ╔═╡ 4c3738f0-ba84-456a-a53f-c2690be9c077
df[1:8, [:log_gdp, :rugged, :cont_africa, :log_gdp_s, :rugged_s]]

# ╔═╡ 60053fa4-f1f9-4754-825e-170a095a284d
md"
```
8×5 DataFrame
 Row │ log_gdp  rugged   cont_africa  log_gdp_std  rugged_std
     │ Float64  Float64  Int64        Float64      Float64
─────┼────────────────────────────────────────────────────────
   1 │ 7.49261    0.858            1     1.00276    0.138342
   2 │ 6.43238    1.78             1     0.860869   0.287004
   3 │ 6.86612    0.141            1     0.918918   0.0227346
   4 │ 6.90617    0.236            1     0.924278   0.0380522
   5 │ 8.9493     0.181            1     1.19772    0.0291841
   6 │ 7.04582    0.197            1     0.942968   0.0317639
   7 │ 7.36241    0.224            1     0.985338   0.0361174
   8 │ 7.54046    0.515            1     1.00917    0.0830377
```"

# ╔═╡ e890c59c-6a68-11eb-0d33-4d52a21d0ccf
stan8_1 = "
data {
	int N;
	vector[N] G;
	vector[N] R;
}

parameters {
	real a;
	real b;
	real<lower=0> sigma;
}

transformed parameters {
	vector[N] mu;
	mu = a + b * (R - 0.125);
}

model {
	a ~ normal(1, 0.1);
	b ~ normal(0, 0.3);
	sigma ~ exponential(1);
	G ~ normal(mu, sigma);
}
generated quantities {
	vector[N] log_lik;
	for (i in 1:N)
		log_lik[i] = normal_lpdf(G[i] | mu[i], sigma);
}
";

# ╔═╡ 8e815d5c-6a66-11eb-21dc-99734c31f4e1
begin
	m8_1s = SampleModel("m8.1s", stan8_1)
	rc8_1s = stan_sample(m8_1s; data)
	if success(rc8_1s)
		post8_1s_df = read_samples(m8_1s, :dataframe)
		PRECIS(post8_1s_df[:, [:a, :b, :sigma]])
	end
end

# ╔═╡ 04a39568-6a6c-11eb-19ea-f55be3914a51
begin
	x = range(0, stop=1, length=50)
	plot(;leg=false)
	for i in 1:10:4000
		y = post8_1s_df.a[i] .+ post8_1s_df.b[i] .* x
		plot!(x, y, color=:lightgrey)
	end
	plot!(x, mean(post8_1s_df.a) .+ mean(post8_1s_df.b) .* x, color=:darkblue)
end

# ╔═╡ 7e9e3674-6aea-11eb-3206-c1f775fe2d78
stan8_2 = "
data {
	int N;
	int K;
	vector[N] G;
	vector[N] R;
	int cid[N];
}

parameters {
	vector[K] a;
	real b;
	real<lower=0> sigma;
}

transformed parameters {
	vector[N] mu;
	for (i in 1:N)
		mu[i] = a[cid[i]] + b * (R[i] - 0.215);
}

model {
	a ~ normal(1, 0.1);
	b ~ normal(0, 0.3);
	sigma ~ exponential(1);
	G ~ normal(mu, sigma);
}
generated quantities {
	vector[N] log_lik;
for (i in 1:N)
	log_lik[i] = normal_lpdf(G[i] | mu[i], sigma);
}
";

# ╔═╡ 12db71d0-6aeb-11eb-341c-e3243ba74862
begin
	m8_2s = SampleModel("m8.2s", stan8_2)
	rc8_2s = stan_sample(m8_2s; data)
	if success(rc8_2s)
		post8_2s_df = read_samples(m8_2s, :dataframe)
		PRECIS(post8_2s_df[:, [Symbol("a.1"), Symbol("a.2"), :b, :sigma]])
	end
end

# ╔═╡ 8b889740-7142-11eb-3d74-b1b9dc6f3282
plot_models([m8_1s, m8_2s], [:a, Symbol("a.1"), Symbol("a.2"), :b, :sigma])

# ╔═╡ 4a5070f8-6af4-11eb-31ca-a7a3ae504821
compare([m8_1s, m8_2s], :waic)

# ╔═╡ 4fa4b38c-7069-11eb-1202-7552c5dcd671
plot_models([m8_1s, m8_2s], :waic)

# ╔═╡ f6c60a90-6e29-11eb-1133-99ad7b37e0af
compare([m8_1s, m8_2s], :psis)

# ╔═╡ 7244381c-6e2b-11eb-2373-1d6c5d27de80
md"
```

> PSIS(m8.1)
       PSIS    lppd  penalty  std_err
1 -188.6114 94.3057 2.755778 13.35391

> PSIS(m8.2)
       PSIS     lppd  penalty  std_err
1 -252.1136 126.0568 4.272533 15.29773

> compare( m8.1 , m8.2 )
       WAIC   SE dWAIC   dSE pWAIC weight
m8.2 -252.3 15.3   0.0    NA   4.3      1
m8.1 -188.7 13.3  63.5 15.15   2.7      0

> compare( m8.1 , m8.2, func=PSIS )
Some Pareto k values are high (>0.5). Set pointwise=TRUE to inspect individual points.
       PSIS    SE dPSIS   dSE pPSIS weight
m8.2 -252.1 15.28   0.0    NA   4.3      1
m8.1 -188.7 13.40  63.4 15.16   2.7      0
```
"

# ╔═╡ dad9e286-6e2c-11eb-1e89-9f3cb4e2d06b
l, ls, pk = psisloo(m8_1s)

# ╔═╡ 133600ce-6e2d-11eb-3030-0bad514887eb
sum(ls)

# ╔═╡ 943f0ba2-6af4-11eb-2698-b3e2be715e88
begin
	nt8_2s = read_samples(m8_2s)
	hpdi(nt8_2s.a[1,:] .- nt8_2s.a[2, :])
end

# ╔═╡ 45767e2e-6a63-11eb-3e12-354a7e32a374
md" ## End of clip-08-07-12s.jl"

# ╔═╡ Cell order:
# ╟─51fc19b8-59a8-11eb-2214-15aca59b807b
# ╠═63ba08cc-59a8-11eb-0a0f-27efac60d779
# ╠═f346a9a1-de24-40ef-bf2f-eb046ed800d5
# ╠═6db218c6-59a8-11eb-2a8b-7107354cf590
# ╠═8aaa4bcc-59a8-11eb-2003-f1213b116565
# ╠═4c3738f0-ba84-456a-a53f-c2690be9c077
# ╟─60053fa4-f1f9-4754-825e-170a095a284d
# ╠═e890c59c-6a68-11eb-0d33-4d52a21d0ccf
# ╠═8e815d5c-6a66-11eb-21dc-99734c31f4e1
# ╠═04a39568-6a6c-11eb-19ea-f55be3914a51
# ╠═7e9e3674-6aea-11eb-3206-c1f775fe2d78
# ╠═12db71d0-6aeb-11eb-341c-e3243ba74862
# ╠═8b889740-7142-11eb-3d74-b1b9dc6f3282
# ╠═4a5070f8-6af4-11eb-31ca-a7a3ae504821
# ╠═4fa4b38c-7069-11eb-1202-7552c5dcd671
# ╠═f6c60a90-6e29-11eb-1133-99ad7b37e0af
# ╟─7244381c-6e2b-11eb-2373-1d6c5d27de80
# ╠═dad9e286-6e2c-11eb-1e89-9f3cb4e2d06b
# ╠═133600ce-6e2d-11eb-3030-0bad514887eb
# ╠═943f0ba2-6af4-11eb-2698-b3e2be715e88
# ╟─45767e2e-6a63-11eb-3e12-354a7e32a374


export iauHd2pa
"""
Parallactic angle for a given hour angle and declination.

This function is part of the International Astronomical Union's
SOFA (Standards of Fundamental Astronomy) software collection.

Status:  support function.

Given:
   ha     double     hour angle
   dec    double     declination
   phi    double     site latitude

Returned (function value):
         double     parallactic angle

Notes:

   1.  All the arguments are angles in radians.

   2.  The parallactic angle at a point in the sky is the position
   angle of the vertical, i.e. the angle between the directions to
   the north celestial pole and to the zenith respectively.

   3.  The result is returned in the range -pi to +pi.

   4.  At the pole itself a zero result is returned.

   5.  The latitude phi is pi/2 minus the angle between the Earth's
   rotation axis and the adopted zenith.  In many applications it
   will be sufficient to use the published geodetic latitude of the
   site.  In very precise (sub-arcsecond) applications, phi can be
   corrected for polar motion.

   6.  Should the user wish to work with respect to the astronomical
   zenith rather than the geodetic zenith, phi will need to be
   adjusted for deflection of the vertical (often tens of
   arcseconds), and the zero point of the hour angle ha will also
   be affected.

Reference:
   Smart, W.M., "Spherical Astronomy", Cambridge University Press,
   6th edition (Green, 1977), p49.

Last revision:   2017 September 12

SOFA release 2018-01-30

Copyright (C) 2018 IAU SOFA Board.  See notes at end.
"""

# double iauHd2pa (double ha, double dec, double phi)

function iauHd2pa(ha::Real, dec::Real, phi::Real)
   return ccall((:iauHd2pa, libsofa_c), Cdouble,
         (Cdouble, Cdouble, Cdouble),
          convert(Float64, ha), convert(Float64, dec),
          convert(Float64, phi))
end


struct LinearPieces
    left::Float64
    right::Float64
    slope::Float64
    intercept::Float64
end

#PiecewiseLinearActivation
struct PiecewiseLinearActivation <: ActivationFunction
    pieces::Vector{LinearPieces}
end

function (f::PiecewiseLinearActivation)(x)
    for p in f.pieces
        if p.left <= x < p.right
            return p.slope * x + p.intercept
        end
    end
end

l1 = LinearPieces(-Inf, 0.0, 0.0, 0.0)
l2 = LinearPieces(0.0, Inf, 1.0, 0.0)
ReLUact = PiecewiseLinearActivation([l1, l2])
IDact = PiecewiseLinearActivation([LinearPieces(-Inf, Inf, 1.0, 0.0)])

struct PointSearch end

# This is the main function
function solve(solver::PointSearch, problem::Problem)
    layers = problem.network.layers
    input = tovrep(problem.input)
    z1_list = Vector{Vector{Float64}}(undef, 0)
    (W, b, act) = (layers[1].weights, layers[1].bias, layers[1].activation)
    for z0 in points(input)
        z1_hat = affine_map(W, b, z0)
        z1 = act.(z1_hat)
        if is_interior_point(forward_network(solver, problem.network, z1, 1), problem.output)
            push!(z1_list, z1)
        else
            return BasicResult(:violated)
        end
    end
    for i in 1:length(layers)
        (W, b, act) = (layers[i].weights, layers[i].bias, layers[i].activation)
        v_list = vertices_list(input) #z_i-1
        zi_hat_list = affine_map.(v_list)
        zi_hat_tope = VPolytope(zi_hat_list)
        zi_list = Vector{Vector{Float64}}(undef, 0)
        inflections = get_inflection(act) #Vector
        if !isempty(inflections)
            C, d = tosimplehrep(zi_hat_tope)
            n = length(d)
            Ci = [C; zeros(Float64, (2, size(C, 2)))]
            di = [d; 0.0; 0.0]
            for j in 1:length(b)
                if j > 1
                    Ci[n+1,j-1] = 0.0
                    Ci[n+2,j-1] = 0.0
                end
                Ci[n+1,j] = 1.0
                Ci[n+2,j] = -1.0
                for k in length(inflections)
                    di[n+1] = inflections[k]
                    di[n+2] = -inflections[k]
                    intersect = HPolytope(Ci, di)
                    if !isempty(intersect)
                        v_inter = tovrep(intersect)
                        for zi_hat in points(v_inter)
                            zi = act.(zi_hat)
                            if is_interior_point(forward_network(solver, problem.network, zi, i), problem.output)
                                push!(zi_list, zi)
                            else
                                return BasicResult(:violated)
                            end
                        end
                    end
                end
            end
        end
        if i < length(layers)
            if i == 1
                append!(zi_list, z1_list)
                z1_list = 0 #free memory
            else
                append!(zi_list, act.(zi_hat_list))
            end
            input = remove_redundant_vertices(VPolytope(zi_list))
        end
    end
    return BasicResult(:holds)
end

function get_inflection(act::PiecewiseLinearActivation)
    n = length(act.pieces)
    inflections = Vector{Float64}(undef, n-1)
    for i in 1:n-1
        inflections[i] = act.pieces[i].right
    end
    return inflections
end

function get_inflection(act::ReLU)
    return [0.0]
end

function get_inflection(act::Id)
    return []
end

function forward_network(solver::PointSearch, nnet::Network, input::Vector{Float64}, last::Int)
    if last == length(nnet.layers)
        return input
    end
    reach = input
    start = last + 1
    for i in start:length(nnet.layers)
        reach = forward_layer(solver, nnet.layers[i], reach)
    end
    return reach
end

function forward_layer(solver::PointSearch, layer::Layer, input::Vector{Float64})
    return layer.activation.(affine_map(layer.weights, layer.bias, input))
end

function affine_map(W::Matrix{Float64}, b::Vector{Float64}, input::Vector{Float64})
    n = length(b)
    output = Vector{Float64}(undef, n)
    for j in 1:n
        output[j] = sum(W[j,:] .* input) + b[j]
    end
    return output
end


@coeff function G(reactor::Reactor)
  nu_0 = 1 + 1.5 * nu_T

  _gamma(value) = Gamma(nu_0) * Gamma(value) / Gamma(nu_0+value)

  cur_value = 1/nu_0 - 2*sqrt(epsilon)*_gamma(5/4) + epsilon*_gamma(3/2)

  cur_value *= (1 + nu_T) ^ 1.5

  cur_value
end


include("BPX1D.jl")

function Schwarz_matrix_2D(A::SparseMatrixCSC{Float64, Int64}, L::Integer, BCs::AbstractArray{String, 1})
    B = spdiagm(0 => diag(A).^-1)
    for l=1:(L-1)
        ψ_x, χ_x = BPX_weights(L, l, BCs[1:2])
        P_x = BPX_layer_half_matrix(L, ψ_x, χ_x, BCs[1:2])
        ψ_y, χ_y = BPX_weights(L, l, BCs[3:4])
        P_y = BPX_layer_half_matrix(L, ψ_y, χ_y, BCs[3:4])
        P = kron(P_y, P_x)
        if l == 1
            A_P = sparse(inv(Array(transpose(P)*A*P + 1e-6*I)))
        else
            A_P = spdiagm(0 => diag(transpose(P)*A*P).^-1)
        end
        B += P*A_P*transpose(P)
    end
    return B
end

function BPX_matrix_2D(L::Integer, BCs::AbstractArray{String, 1})
    N = 2^L+1
    h = 1/(N-1)
    DOF_x, DOF_y = N - 2 + sum(BCs[1:2] .== "N"), N - 2 + sum(BCs[3:4] .== "N")
    B = spdiagm(0 => ones(DOF_x*DOF_y))
    for l=1:(L-1)
        ψ_x, χ_x = BPX_weights(L, l, BCs[1:2])
        P_x = BPX_layer_half_matrix(L, ψ_x, χ_x, BCs[1:2])
        ψ_y, χ_y = BPX_weights(L, l, BCs[3:4])
        P_y = BPX_layer_half_matrix(L, ψ_y, χ_y, BCs[3:4])
        P = kron(P_y, P_x)
        # B += (P*transpose(P))*h
        N_ = 2^l+1
        h_ = 1/(N_-1)
        B += (P*transpose(P))*(h/h_)
    end
    return B
end


# ---
# title: Amplitude swapping
# id: demo_swapphase
# cover: assets/swapphase.gif
# author: Peter Kovesi
# date: 2018-10-26
# ---

# A demonstration of the importance of phase information in images. Given two
# images`swapphase()` takes their Fourier transforms and constructs two new, synthetic,
# images formed from the swapped phase and amplitude imformation.  In general it is the
# phase information that dominates.  However, for textures where the amplitude spectra can
# be concentrated in a limited set of locations, the reverse can apply.

# See [Oppenheim and Lim's paper "The importance of phase in signals". Proceedings of the
# IEEE. Volume: 69 , Issue: 5 , May 1981](https://ieeexplore.ieee.org/document/1456290)

using TestImages
using Images
using ImagePhaseCongruency

img1 = centered(Float64.(Gray.(restrict(testimage("lighthouse")))))[-127:128, -127:128]
img2 = restrict(Float64.(testimage("mandril_gray")))[1:256, 1:256]

(newimg1, newimg2) = swapphase(img1, img2)

mosaic(Gray.(img1), newimg1, img2, newimg2; nrow=2)
# Bottom 1) left: phase of lighthouse, amplitude of Mandrill 2) right: amplitude of lighthouse, phase of Mandrill

# save cover image #src
isdir("assets") || mkdir("assets") #src
save(joinpath("assets", "swapphase.gif"), Images.gif([img1, newimg1]); fps=1) #src


### A Pluto.jl notebook ###
# v0.12.7

using Markdown
using InteractiveUtils

# ╔═╡ 6fd616f6-1ea1-11eb-3814-8bfb4a096c49
using Pkg, DrWatson

# ╔═╡ 6ffe2628-1ea1-11eb-24ea-57f985146a72
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Distributions, Random, StatsBase, DataFrames, Plots
	Random.seed!(1)
end;

# ╔═╡ ed174bc4-1ea0-11eb-1e2f-a32874cec549
md"## Listing 3.20"

# ╔═╡ 70179b62-1ea1-11eb-13ce-7fb4d54bad4b
dists = [
    Uniform(10,20),
    Exponential(3.5),
    Gamma(0.5,7),
    Beta(10,0.5),
    Weibull(10,0.5),
    Normal(20,3.5),
    Rayleigh(2.4),
    Cauchy(20,3.5)]

# ╔═╡ 863fa0fe-2164-11eb-221c-fbdef5124dc6
begin
	df = DataFrame()
	df.distributions = dists
	df.parameters = params.(dists)
	df.support = ((d)->(minimum(d),maximum(d))).(dists)
	df
end

# ╔═╡ 70182cd8-1ea1-11eb-094d-8d0d49cf15f3
md"## End of listing 3.20"

# ╔═╡ Cell order:
# ╟─ed174bc4-1ea0-11eb-1e2f-a32874cec549
# ╠═6fd616f6-1ea1-11eb-3814-8bfb4a096c49
# ╠═6ffe2628-1ea1-11eb-24ea-57f985146a72
# ╠═70179b62-1ea1-11eb-13ce-7fb4d54bad4b
# ╠═863fa0fe-2164-11eb-221c-fbdef5124dc6
# ╟─70182cd8-1ea1-11eb-094d-8d0d49cf15f3


#    Copyright (c) 2021 J.A. Duffek
#    Copyright (c) 2000 D.M. Spink
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, see <http://www.gnu.org/licenses/>.

using Plots;

"""
    demorevolve()

Demonstration of surface construction by revolving a profile curve.

# Examples:
```julia
julia> demorevolve()
```
"""
function demorevolve()
# Construct a test profile in the x-z plane
pnts = [3.0 5.5 5.5 1.5 1.5 4.0 4.5;
        0.0 0.0 0.0 0.0 0.0 0.0 0.0;
        0.5 1.5 4.5 3.0 7.5 6.0 8.5];
crv = nrbmak(pnts,vec([0 0 0 1/4 1/2 3/4 3/4 1 1 1]));

# rotate and vectrans by some arbitrary amounts.
xx = vecrotz(deg2rad(25)) * vecroty(deg2rad(15)) * vecrotx(deg2rad(20));
nrb = nrbtform(crv,vectrans([5.0;5.0])*xx);

# define axes of rotation
pnt = [5.0;5.0;0.0];
ver = xx * [0.0;0.0;1.0;1.0];
srf = nrbrevolve(nrb,ver[1:3],pnt);

# make and draw nurbs curve
p = nrbeval(srf,collect.([range(0.0,1.0,length=20),range(0.0,1.0,length=20)]));
Plots.pyplot();
plot(p[1,:,:], p[2,:,:], p[3,:,:],
     st = :surface,
     cbar = false,
     legend = false,
     camera = (-30, 30),
     title = "Construct of a 3D surface by revolution of a curve.",
     c=:copper);
# TODO: come back later as pyplot backend does not has light/shading yet
#       furthermore grid lines should be off and interpolated shading should be
#       on
end # demorevolve


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.6.2
#     language: julia
#     name: julia-1.6
# ---

# %%
function repeatlength(A, i)
    k = 1
    @inbounds while i + k ≤ lastindex(A) && isequal(A[i+k], A[i])
        k += 1
    end
    k
end

"""
    RepVal(A)
Iterator generating the all (repeat length, value)-tuples in `A`.
"""
struct RepVal{T} A::T end
Base.IteratorSize(::Type{RepVal{T}}) where T = Base.SizeUnknown()
Base.eltype(x::RepVal) = Tuple{Int, eltype(x.A)} # (rep.len., value)

Base.iterate(x::RepVal) = iterate(x, firstindex(x.A))
function Base.iterate(x::RepVal, i::Int)
    i > lastindex(x.A) && return nothing
    k = repeatlength(x.A, i)
    (k, x.A[i]), i + k
end

maxrep_maxval(A) = maximum(RepVal(A))
negrep((k, v)) = (-k, v)
maxrep_minval(A) = negrep(minimum(negrep, RepVal(A)))

@doc RepVal

# %%
A = [1, 2, 3, 3, 3, 1, 1, 1, NaN, NaN, NaN, 2, 2, 3]
@show A
RepVal(A) |> collect

# %%
maxrep_maxval(A)

# %%
maxrep_minval(A)

# %%
@code_warntype iterate(RepVal(A), 1)

# %% [markdown]
# https://discourse.julialang.org/t/minimum-mode-problem-minimum-number-maximum-repetition/66749/2

# %%
function findn(x,y=zeros(Int,maximum(x)))
    m = 0
    fill!(y,0)
    @inbounds for i in x
        y[i] += 1
        m = max(y[i],m)
    end
    return findfirst(isequal(m),y)
end

# %%

# %%
using StatsBase, DataStructures

function findn_jling(x)
    cm = sort!(OrderedDict(countmap(x)); byvalue = true)
    last(cm.keys), last(cm.vals)
end

# %%

# %%
function minMode(c)
    minVal = -1
    i = 0

    i = i+1
    for row in eachrow(c)
    row = collect(row)
    temp = filter(x -> x!=0, row)
    count = counter(temp)
    sortedCollection = sort(collect(count), by=x->x[2], rev=true)
    minVal = sortedCollection[1][1]
    repetitions = sortedCollection[1][2]
    for (key, value) in sortedCollection
        if(value == repetitions)
            if(key < minVal)
                minVal = key
            end
        end
        end
    end

    return minVal
end

# %%
using StatsBase
swap_negval((val, rep)) = (rep, -val)
inv_swap_negval((rep, val)) = (-val, rep)
minmode(X) = inv_swap_negval(maximum(swap_negval, countmap(X)))k

# %%
X = rand(1:10000:10^8, 10^6)
X'

# %%
using BenchmarkTools

# %%
@btime maxrep_minval($X)

# %%
@btime findn($X)

# %%
@btime minMode($X)

# %%
@btime minmode($X)

# %%
@btime findn_jling($X)

# %%


### A Pluto.jl notebook ###
# v0.16.1

using Markdown
using InteractiveUtils

# ╔═╡ 7f005754-26bb-11ec-2d0c-2dfd971bc6e2
md"""
## B-1 Graph Coloring
**Def.**$(HTML("<br>"))
Given an undirected graph ``G = (V, E)``, a _**``k``-coloring**_ is a function ``c: V \to \{0,1,\ldots,k-1\}`` s.t. ``c(u) \ne c(v)`` for every edge ``\{u, v\} \in E``.

**Rmk.**
01. The map coloring problem that many of us have heard of is a concret question of which the above definition forms an abstraction: The countries are the vertices. Those which share borders constitute the edges in ``E``.
02. For any graphs with finite vertices, there always exists a ``k``-coloring, e.g. simply taking ``k = |V|``, coloring each vertex with a different color will do. As a result, we are usually interested in discussing ``k``-coloring of a graph with ``k`` as small as possible.

"""

# ╔═╡ Cell order:
# ╠═7f005754-26bb-11ec-2d0c-2dfd971bc6e2


### The function eps() outputs the machine epsilon of the argument.
### Argument currently set to 1e-14.
### Resulting EPSILON is approx. 1.6e-30.
const EPSILON = eps(1e-14)

### EPSILON32 is 1f-30.
const EPSILON32 = "0.000000000000000000000000000001f"

### Euler–Mascheroni constant.
### γ = 0.5772156649015...
import Base.MathConstants.eulergamma

### Numerical approximation to the digamma function.
### Based on eq. (12), without looking at the accompanying source
### code, of: K. S. Kölbig, "Programs for computing the logarithm of
### the gamma function, and the digamma function, for complex
### argument," Computer Phys. Commun. vol. 4, pp. 221–226 (1972).
const DIGAMMA_c =
"""
inline float
digamma(float x)

		{
		float p = 0.0f;

		if (x < 7)
		{
			int n = 7 - floor(x);
			for (int v=1; v < n; v++)
				p -= 1 / (x + v);

			p -= 1 / x;
			x += n;
		}

		float t = 1 / x;
		p += log(x) - 0.5f * t;
		t *= t;
		p -= t * 0.08333333333333333f
				- 0.008333333333333333f * t
				+ 0.003968253968253968f * t*t
				- 0.004166666666666667f * t*t*t
				+ 0.007575757575757576f * t*t*t*t
				- 0.021092796092796094f * t*t*t*t*t
				+ 0.08333333333333333f * t*t*t*t*t*t
				- 0.4432598039215686f * t*t*t*t*t*t*t;

		return p;
		}
		"""

### Parallel Gauss-Jordan elimination (reduced row echelon form) algorithm for solving the linear system Ax=b.
### Partial pivoting unnecessary for symmetric positive-definite matrices. Ref,
### "Accuracy and Stability of Numerical Algorithms" Higham, 2002.
const LINSOLVE_c =
"""
inline void
linsolve(long K, long z, local float *A, local float *b)

		{
		for (long j=0; j<K-1; j++)
		{
			if ((j <= z) && (z < K-1))
			{
				float c = -A[K * j + (z + 1)] / A[K * j + j];

			    for (long l=j+1; l<K; l++)
			        A[K * l + (z + 1)] += c * A[K * l + j];

			    b[z + 1] += c * b[j];
			}
			barrier(CLK_LOCAL_MEM_FENCE);
		}

		for (long j=K-1; j>0; j--)
		{
			if (z < j)
				b[z] -= b[j] * A[K * j + z] / A[K * j + j];

			barrier(CLK_LOCAL_MEM_FENCE);
		}

		b[z] *= 1 / A[K * z + z];
		}
		"""

"Type alias for a vector of vectors."
VectorList{T} = Vector{Vector{T}}

"Type alias for a vector of matrices."
MatrixList{T} = Vector{Matrix{T}}

"Prevent overflow to ±Inf."
finite(x::Union{AbstractFloat, Array{<:AbstractFloat}}) = sign.(x) .* min.(abs.(x), floatmax.(x))

### LogSumExp function.
### Overflow safe.
import Distributions.logsumexp

function additive_logistic(x::Matrix{<:Real}; dims::Integer)
	"Additive logistic function of a real-valued matrix over the given dimension."
	"Overflow safe."

	if dims in [1,2]
		x = exp.(x .- maximum(x, dims=dims))
		x = x ./ sum(x, dims=dims)
	end

	return x
end

function additive_logistic(x::Vector{<:Real})
	"Additive logistic function of a real-valued vector."
	"Overflow safe."

	x = exp.(x .- maximum(x))
	x = x / sum(x)

	return x
end

function additive_logistic(x::Matrix{<:Real})
	"Additive logistic function of a real-valued matrix."
	"Overflow safe."

	x = exp.(x .- maximum(x))
	x = x / sum(x)

	return x
end

function isstochastic(P::Matrix{<:Real}; dims::Integer)
	"Check to see if X is a stochastic matrix."
	"if dims = 1, check for left stochastic matrix."
	"if dims = 2, check for right stochastic matrix."

	if dims == 1
		x = all([isprobvec(P[:,j]) for j in 1:size(P, 2)])
	elseif dims == 2
		x = all([isprobvec(P[i,:]) for i in 1:size(P, 1)])
	else
		x = P
	end

	return x
end

### Keep until pull request is merged.
import Distributions.xlogy
import Distributions.binomlogpdf
Distributions.xlogy(x::T, y::T) where {T<:Real} = x != zero(T) ? x * log(y) : zero(log(y))
Distributions.binomlogpdf(n::Real, p::Real, k::Real) = (isinteger(k) & (zero(k) <= k <= n)) ? convert(typeof(float(p)), loggamma(n + 1) - loggamma(k + 1) - loggamma(n - k + 1) + xlogy(k, p) + xlogy(n - k, 1 - p)) : convert(typeof(float(p)), -Inf)

### Keep until pull request is merged.
function Distributions.entropy(d::Dirichlet)
    α = d.alpha
    α0 = d.alpha0
    k = length(α)

    if k == 1
    	en = 0.0

    else
	    en = d.lmnB + (α0 - k) * digamma(α0)
	    for j in 1:k
	        @inbounds αj = α[j]
	        en -= (αj - 1.0) * digamma(αj)
	    end
	end

    return en
end


struct Quintic{T} <: SPHKernel
    n_neighbours::Int64
    norm_1D::T
    norm_2D::T
    norm_3D::T
end

"""
    Quintic(T::DataType=Float64, n_neighbours::Integer=64)

Set up a `Quintic` kernel for a given DataType `T`.
"""
Quintic(T::DataType=Float64, n_neighbours::Integer=216) = Quintic{T}(n_neighbours, 243.0/40.0, 15309.0/(478.0*π), 2187.0/(40.0*pi))

"""
    kernel_value_1D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

Evaluate quintic spline at position ``u = \\frac{x}{h}``.
"""
function kernel_value_1D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

    n = kernel.norm_1D * h_inv

    if u < 1/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        u_m13 = T(1/3) - u
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 -
                6u_m23*u_m23*u_m23*u_m23*u_m23 +
                15u_m13*u_m13*u_m13*u_m13*u_m13 ) * n |> T
    elseif u < 2/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 -
                6u_m23*u_m23*u_m23*u_m23*u_m23 ) * n |> T
    elseif u < 1
        u_m1  = 1 - u
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 ) * n |> T
    else
        return 0.0 |> T
    end

end

"""
    kernel_deriv_1D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

Evaluate the derivative of the Quintic spline at position ``u = \\frac{x}{h}``.
"""
function kernel_deriv_1D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

    n = kernel.norm_1D * h_inv^2

    if u < 1/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        u_m13 = T(1/3) - u
        return ( -5u_m1*u_m1*u_m1*u_m1 +
                 30u_m23*u_m23*u_m23*u_m23  -
                 75u_m13*u_m13*u_m13*u_m13 ) * n |> T
    elseif u < 2/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        return ( -5u_m1*u_m1*u_m1*u_m1 +
                 30u_m23*u_m23*u_m23*u_m23 - 75 ) * n |> T
    elseif u < 1
        u_m1 =  1 - u
        return -5u_m1*u_m1*u_m1*u_m1 * n |> T
    else
        return 0.0 |> T
    end

end

"""
    bias_correction_1D(kernel::Quintic{T}, density::Real, m::Real, h_inv::Real) where T

Does not do anything for the BSplines. Implemented for stability.
"""
function bias_correction_1D(kernel::Quintic{T}, density::Real, m::Real, h_inv::Real) where T
    return density
end

"""
    kernel_value_2D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

Evaluate quintic spline at position ``u = \\frac{x}{h}``.
"""
function kernel_value_2D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

    n = kernel.norm_2D * h_inv^2

    if u < 1/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        u_m13 = T(1/3) - u
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 -
                6u_m23*u_m23*u_m23*u_m23*u_m23 +
               15u_m13*u_m13*u_m13*u_m13*u_m13 ) * n |> T
    elseif u < 2/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 -
                6u_m23*u_m23*u_m23*u_m23*u_m23 ) * n |> T
    elseif u < 1
        u_m1  = 1 - u
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 ) * n |> T
    else
        return 0.0 |> T
    end

end

"""
    kernel_deriv_2D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

Evaluate the derivative of the Quintic spline at position ``u = \\frac{x}{h}``.
"""
function kernel_deriv_2D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

    n = kernel.norm_2D * h_inv^3

    if u < 1/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        u_m13 = T(1/3) - u
        return ( -5u_m1*u_m1*u_m1*u_m1 +
                 30u_m23*u_m23*u_m23*u_m23 -
                 75u_m13*u_m13*u_m13*u_m13 ) * n |> T
    elseif u < 2/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        return ( -5u_m1*u_m1*u_m1*u_m1 +
                 30u_m23*u_m23*u_m23*u_m23 - 75 ) * n |> T
    elseif u < 1
        u_m1  = 1 - u
        return -5u_m1*u_m1*u_m1*u_m1 * n |> T
    else
        return 0.0 |> T
    end

end

"""
    bias_correction_2D(kernel::Quintic{T}, density::Real, m::Real, h_inv::Real) where T

Does not do anything for the BSplines. Implemented for stability.
"""
function bias_correction_2D(kernel::Quintic{T}, density::Real, m::Real, h_inv::Real) where T
    return density
end

"""
    kernel_value_3D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

Evaluate quintic spline at position ``u = \\frac{x}{h}``.
"""
function kernel_value_3D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

    n = kernel.norm_3D * h_inv^3

    if u < 1/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        u_m13 = T(1/3) - u
        return (  u_m1*u_m1*u_m1*u_m1*u_m1 -
                 6u_m23*u_m23*u_m23*u_m23*u_m23 +
                15u_m13*u_m13*u_m13*u_m13*u_m13 ) * n |> T
    elseif u < 2/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 -
                6u_m23*u_m23*u_m23*u_m23*u_m23 ) * n |> T
    elseif u < 1
        u_m1 = 1 - u
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 ) * n |> T
    else
        return 0.0 |> T
    end

end

"""
    kernel_deriv_3D(kernel::Quintic, u::Real, h_inv::Real)

Evaluate the derivative of the Quintic spline at position ``u = \\frac{x}{h}``.
"""
function kernel_deriv_3D(kernel::Quintic{T}, u::Real, h_inv::Real) where T

    n = kernel.norm_3D * h_inv^4

    if u < 1/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        u_m13 = T(1/3) - u
        return ( -5u_m1*u_m1*u_m1*u_m1 +
                30u_m23*u_m23*u_m23*u_m23 -
                75u_m13*u_m13*u_m13*u_m13 ) * n |> T
    elseif u < 2/3
        u_m1  = 1 - u
        u_m23 = T(2/3) - u
        return ( -5u_m1*u_m1*u_m1*u_m1 +
                 30u_m23*u_m23*u_m23*u_m23 - 75 ) * n |> T
    elseif u < 1
        u_m1  = 1 - u
        return ( -5u_m1*u_m1*u_m1*u_m1 ) * n |> T
    else
        return 0.0 |> T
    end

end

"""
    bias_correction_3D(kernel::Quintic{T}, density::Real, m::Real, h_inv::Real) where T

Does not do anything for the BSplines. Implemented for stability.
"""
function bias_correction_3D(kernel::Quintic{T}, density::Real, m::Real, h_inv::Real) where T
    return density |> T
end


"""
    ZeroInflatedPoisson(λ, p)
A *Zero-Inflated Poisson distribution* is a mixture distribution in which data arise from two processes. The first process is is a Poisson distribution, with mean λ, that descibes the number of independent events occurring within a unit time interval:
```math
P(X = k) = (1 - p) \\frac{\\lambda^k}{k!} e^{-\\lambda}, \\quad \\text{ for } k = 0,1,2,\\ldots.
```
Zeros may arise from this process, an additional Bernoulli process, where the probability of observing an excess zero is given as p:
```math
P(X = 0) = p + (1 - p) e^{-\\lambda}
```
As p approaches 0, the distribution tends toward Poisson(λ).
```julia
ZeroInflatedPoisson()        # Zero-Inflated Poisson distribution with rate parameter 1, and probability of observing a zero 0.5
ZeroInflatedPoisson(λ)       # ZeroInflatedPoisson distribution with rate parameter λ, and probability of observing a zero 0.5
params(d)                    # Get the parameters, i.e. (λ, p)
mean(d)                      # Get the mean of the mixture distribution
var(d)                       # Get the variance of the mixture distribution
```
External links:
* [Zero-inflated Poisson Regression on UCLA IDRE Statistical Consulting](https://stats.idre.ucla.edu/stata/dae/zero-inflated-poisson-regression/)
* [Zero-inflated model on Wikipedia](https://en.wikipedia.org/wiki/Zero-inflated_model)
* McElreath, R. (2020). Statistical Rethinking: A Bayesian Course with Examples in R and Stan (2nd ed.). Chapman and Hall/CRC. https://doi.org/10.1201/9780429029608

"""
struct ZeroInflatedPoisson{T<:Real} <: DiscreteUnivariateDistribution
  λ::T
  p::T

  function ZeroInflatedPoisson{T}(λ::T, p::T) where {T <: Real}
      return new{T}(λ, p)
    end
end

function ZeroInflatedPoisson(λ::T, p::T; check_args = true) where {T <: Real}
  if check_args
    @check_args(Poisson, λ >= zero(λ))
    @check_args(ZeroInflatedPoisson, zero(p) <= p <= one(p))
  end
  return ZeroInflatedPoisson{T}(λ, p)
end

ZeroInflatedPoisson(λ::Real, p::Real) = ZeroInflatedPoisson(promote(λ, p)...)
ZeroInflatedPoisson(λ::Integer, p::Integer) = ZeroInflatedPoisson(float(λ), float(p))
ZeroInflatedPoisson(λ::Real) = ZeroInflatedPoisson(λ, 0.0)
ZeroInflatedPoisson() = ZeroInflatedPoisson(1.0, 0.0, check_args = false)

@distr_support ZeroInflatedPoisson 0 (d.λ == zero(typeof(d.λ)) ? 0 : Inf)

### Statistics

mean(d::ZeroInflatedPoisson) = (1 - d.p) * d.λ

var(d::ZeroInflatedPoisson) = d.λ * (1 - d.p) * (1 + d.p * d.λ)

#### Conversions

function convert(::Type{ZeroInflatedPoisson{T}}, λ::Real, p::Real) where {T<:Real}
  return ZeroInflatedPoisson(T(λ), T(p))
end

function convert(::Type{ZeroInflatedPoisson{T}}, d::ZeroInflatedPoisson{S}) where {T <: Real, S <: Real}
  return ZeroInflatedPoisson(T(d.λ), T(d.p), check_args = false)
end

#### Parameters

params(d::ZeroInflatedPoisson) = (d.λ, d.p,)
partype(::ZeroInflatedPoisson{T}) where {T} = T

rate(d::ZeroInflatedPoisson) = d.λ
excessprob(d::ZeroInflatedPoisson) = d.p

#### Evaluation

function logpdf(d::ZeroInflatedPoisson, y::Real)
    lp = if iszero(y)
        logaddexp(log(d.p), log1p(-d.p) - d.λ)
    else
        log1p(-d.p) + logpdf(Poisson(d.λ), y)
    end
    return lp
end

function cdf(d::ZeroInflatedPoisson, x::Real)
  pd = Poisson(d.λ)

  deflat_limit = -1.0 / expm1(d.λ)

  if x < 0
    out = 0.0
  elseif d.p < deflat_limit
    out = NaN
  else
    out = d.p + (1 - d.p) * cdf(pd, x)
  end
  return out
end

# quantile
function quantile(d::ZeroInflatedPoisson, q::Real)

  deflat_limit = -1.0 / expm1(d.λ)

  if (q <= d.p)
    out = 0
  elseif (d.p < deflat_limit)
    out = convert(Int64, NaN)
  elseif (d.p < q) & (deflat_limit <= d.p) & (q < 1.0)
    qp = (q - d.p) / (1.0 - d.p)
    pd = Poisson(d.λ)
    out = quantile(pd, qp) # handles d.p == 1 as InexactError(Inf)
  end
  return out
end

#### Fitting

struct ZeroInflatedPoissonStats <: SufficientStats
  sx::Float64   # (weighted) sum of x
  p0::Float64   # observed proportion of zeros
  tw::Float64   # total sample weight
end

suffstats(::Type{<:ZeroInflatedPoisson}, x::AbstractArray{T}) where {T<:Integer} = ZeroInflatedPoissonStats(
    sum(x),
    mean(iszero, x),
    length(x)
  )

# weighted
function suffstats(::Type{<:ZeroInflatedPoisson}, x::AbstractArray{T}, w::AbstractArray{Float64}) where T<:Integer
    n = length(x)
    n == length(w) || throw(DimensionMismatch("Inconsistent array lengths."))
    sx = 0.
    tw = 0.
    p0 = 0.
    for i = 1 : n
        @inbounds wi = w[i]
        @inbounds sx += x[i] * wi
        tw += wi
        @inbounds p0i = (x[i] == 0) * wi
        p0 += p0i
    end
    return ZeroInflatedPoissonStats(sx, p0, tw)
end

function fit_mle(::Type{<:ZeroInflatedPoisson}, ss::ZeroInflatedPoissonStats)
  m = ss.sx / ss.tw
  s = m / (1 - ss.p0)

  λhat = lambertw(-s * exp(-s)) + s
  phat = 1 - (m / λhat)

  return ZeroInflatedPoisson(λhat, phat)
end

function fit_mle(::Type{<:ZeroInflatedPoisson}, x::AbstractArray{T}) where T<:Real
  pstat = suffstats(ZeroInflatedPoisson, x)
  return fit_mle(ZeroInflatedPoisson, pstat)
end

function fit_mle(::Type{<:ZeroInflatedPoisson}, x::AbstractArray{T}, w::AbstractArray{Float64}) where T<:Real
  pstat = suffstats(ZeroInflatedPoisson, x, w)
  return fit_mle(ZeroInflatedPoisson, pstat)
end


# Methods for porting Indicators.jl functions to TS objects from Temporal.jl package

###### run.jl ######
mode{V}(x::TS{V}) = mode(x.values)
runmean{V,T}(x::TS{V,T}, n::Int64=10, cumulative::Bool=true) = ts(runmean(x.values, n, cumulative), x.index, :RunMean)
runsum{V,T}(x::TS{V,T}, n::Int64=10, cumulative::Bool=true) = ts(runsum(x.values, n, cumulative), x.index, :RunSum)
runmad{V,T}(x::TS{V,T}, n::Int64=10, cumulative::Bool=true) = ts(runmad(x.values, n, cumulative), x.index, :RunMAD)
runvar{V,T}(x::TS{V,T}, n::Int64=10, cumulative::Bool=true) = ts(runvar(x.values, n, cumulative), x.index, :RunVar)
runmax{V,T}(x::TS{V,T}, n::Int64=10, cumulative::Bool=true) = ts(runmax(x.values, n, cumulative), x.index, :RunMax)
runmin{V,T}(x::TS{V,T}, n::Int64=10, cumulative::Bool=true) = ts(runmin(x.values, n, cumulative), x.index, :RunMin)
runsd{V,T}(x::TS{V,T}, n::Int64=10, cumulative::Bool=true) = ts(runsd(x.values, n, cumulative), x.index, :RunSD)
wilder_sum{V,T}(x::TS{V,T}, n::Int64=10) = ts(runsum(x.values, n), x.index, :WilderSum)
function runcov{V,T}(x::TS{V,T}, y::TS{V,T}, n::Int64=10, cumulative::Bool=true)
    @assert size(x,2) == 1 || size(y,2) == 1 "Arguments x and y must both be univariate (have only one column)."
    z = [x y]
    return ts(runcov(z[:,1], z[:,2], n, cumulative), x.index, :RunCov)
end
function runcor{V,T}(x::TS{V,T}, y::TS{V,T}, n::Int64=10, cumulative::Bool=true)
    @assert size(x,2) == 1 || size(y,2) == 1 "Arguments x and y must both be univariate (have only one column)."
    z = [x y]
    ts(runcor(z[:,1], z[:,2], n, cumulative), x.index, :RunCor)
end

##### ma.jl ######
sma{V,T}(x::TS{V,T}, n::Int64=10) = ts(sma(x.values, n), x.index, :SMA)
hma{V,T}(x::TS{V,T}, n::Int64=10) = ts(hma(x.values, n), x.index, :HMA)
mma{V,T}(x::TS{V,T}, n::Int64=10) = ts(mma(x.values, n), x.index, :MMA)
swma{V,T}(x::TS{V,T}, n::Int64=10) = ts(swma(x.values, n), x.index, :SWMA)
kama{V,T}(x::TS{V,T}, n::Int64=10, nfast::Float64=0.6667, nslow::Float64=0.0645) = ts(kama(x.values, n, nfast, nslow), x.index, :KAMA)
alma{V,T}(x::TS{V,T}, n::Int64=9, offset::Float64=0.85, sigma::Float64=6.0) = ts(kama(x.values, n, offset, sigma), x.index, :ALMA)
trima{V,T}(x::TS{V,T}, n::Int64=10; args...) = ts(trima(x.values, n; args...), x.index, :TRIMA)
wma{V,T}(x::TS{V,T}, n::Int64=10; args...) = ts(wma(x.values, n; args...), x.index, :WMA)
ema{V,T}(x::TS{V,T}, n::Int64=10; args...) = ts(ema(x.values, n; args...), x.index, :EMA)
dema{V,T}(x::TS{V,T}, n::Int64=10; args...) = ts(dema(x.values, n; args...), x.index, :DEMA)
tema{V,T}(x::TS{V,T}, n::Int64=10; args...) = ts(tema(x.values, n; args...), x.index, :TEMA)
mama{V,T}(x::TS{V,T}, fastlimit::Float64=0.5, slowlimit::Float64=0.05) = ts(mama(x.values, fastlimit, slowlimit), x.index, [:MAMA, :FAMA])

##### reg.jl ######
mlr_beta{V,T}(y::TS{V,T}, n::Int64=10) = ts(mlr_beta(y.values, n), y.index, :Beta)
mlr_slope{V,T}(y::TS{V,T}, n::Int64=10) = ts(mlr_slope(y.values, n), y.index, :Slope)
mlr_intercept{V,T}(y::TS{V,T}, n::Int64=10) = ts(mlr_intercept(y.values, n), y.index, :Intercept)
mlr{V,T}(y::TS{V,T}, n::Int64=10) = ts(mlr(y.values, n), y.index, :MLR)
mlr_se{V,T}(y::TS{V,T}, n::Int64=10) = ts(mlr_se(y.values, n), y.index, :StdErr)
mlr_ub{V,T}(y::TS{V,T}, n::Int64=10, se::Float64=2.0) = ts(mlr_ub(y.values, n, se), y.index, :UB)
mlr_lb{V,T}(y::TS{V,T}, n::Int64=10, se::Float64=2.0) = ts(mlr_lb(y.values, n, se), y.index, :LB)
mlr_bands{V,T}(y::TS{V,T}, n::Int64=10, se::Float64=2.0) = ts(mlr_bands(y.values, n, se), y.index, [:LB, :MLR, :UB])
mlr_rsq{V,T}(y::TS{V,T}, n::Int64=10) = ts(mlr_rsq(y.values, n), y.index, :RSquared)

##### mom.jl ######
momentum{V,T}(x::TS{V,T}, n::Int64=1) = ts(momentum(x.values, n), x.index, :Momentum)
roc{V,T}(x::TS{V,T}, n::Int64=1) = ts(roc(x.values, n), x.index, :ROC)
macd{V,T}(x::TS{V,T}, nfast::Int64=12, nslow::Int64=26, nsig::Int64=9; args...) = ts(macd(x.values, nfast, nslow, nsig; args...), x.index, [:MACD, :Signal, :Histogram])
rsi{V,T}(x::TS{V,T}, n::Int64=10; args...) = ts(rsi(x.values, n; args...), x.index, :RSI)
adx{V,T}(hlc::TS{V,T}, n::Int64=10; args...) = ts(adx(hlc.values, n; args...), hlc.index, [:DiPlus, :DiMinus, :ADX])
psar{V,T}(x::TS{V,T}, af::Float64=0.02, af_max::Float64=0.2, af_min::Float64=0.02) = ts(psar(x.values, af, af_max, af_min), x.index, :SMA)
kst{V,T}(x::TS{V,T}, nroc::Vector{Int64}=[10,15,20,30], navg::Vector{Int64}=[10,10,10,15]; args...) = ts(kst(x.values, nroc, navg; args...), x.index, :KST)
wpr{V,T}(hlc::TS{V,T}, n::Int64=14) = ts(wpr(hlc.values, n), hlc.index, :WPR)
cci{V,T}(hlc::TS{V,T}, n::Int64=20, c::Float64=0.015; args...) = ts(cci(hlc.values, n, c; args...), hlc.index, :WPR)
stoch{V,T}(hlc::TS{V,T}, nK::Int64=14, nD::Int64=3; args...) = ts(stoch(hlc.values, nK, nD; args...), hlc.index, [:Stochastic, :Signal])
smi{V,T}(hlc::TS{V,T}, n::Int64=13, nFast::Int64=2, nSlow::Int64=25, nSig::Int64=9; args...) = ts(smi(hlc.values, n, nFast, nSlow, nSig; args...), hlc.index, [:SMI, :Signal])

##### vol.jl ######
bbands{V,T}(x::TS{V,T}, n::Int64=10, sigma::Float64=2.0; args...) = ts(bbands(x.values, n, sigma; args...), x.index, [:LB, :MA, :UB])
tr{V,T}(x::TS{V,T}, n::Int64=2) = ts(tr(x.values, n), x.index, :TR)
atr{V,T}(x::TS{V,T}, n::Int64=14) = ts(atr(x.values, n), x.index, :ATR)
keltner{V,T}(hlc::TS{V,T}, nema::Int64=20, natr::Int64=10, mult::Int64=2) = ts(keltner(hlc.values, nema, natr, mult), hlc.index, [:LB, :MA, :UB])

##### trendy.jl #####
maxima{V,T}(x::TS{V,T}; args...) = ts(maxima(x.values; args...), x.index, :Maxima)
minima{V,T}(x::TS{V,T}; args...) = ts(minima(x.values; args...), x.index, :Minima)
support{V,T}(x::TS{V,T}; args...) = ts(support(x.values; args...), x.index, :Support)
resistance{V,T}(x::TS{V,T}; args...) = ts(resistance(x.values; args...), x.index, :Resistance)


using DifferentialEquations
using Distributions

function getsolution(sol, times)
  #function to get species abundance from ODE solution
  if VERSION < v"0.6-"
    x1 = map(x -> x[1], sol(times))
    y1 = map(x -> x[2], sol(times))
  else
    x1 = map(x -> x[1], sol(times).u)
    y1 = map(x -> x[2], sol(times).u) #Need .u syntax as of 0.6
  end
  return x1, y1
end

srand(1)
println("Test ABC SMC with ODE model")

f = @ode_def LV begin
  dx = a*x - b*x*y
  dy = b*x*y - y
end a=>1.0 b=>1.0

inputa = 0.8
inputb = 2.2
g = LV(a = inputa, b = inputb)
x0 = [1.0; 0.5]
tspan = (0.0, 15.0)
prob = ODEProblem(g, x0, tspan)
sol = solve(prob)

#generate target data
times = 1.0:1.0:15.0
x, y = getsolution(sol, times)
x += rand(Normal(0.0, 0.25^2), length(times))
y += rand(Normal(0.0, 0.25^2), length(times))
targetdata = [x, y]

function simLV(params, constants, targetdata)
  a = params[1]
  b = params[2]
  x0 = [1.0; 0.5]
  tspan = (0.0, 15.0)
  times = 1.0:1.0:15.0
  h = LV(a = a, b = b)
  prob = ODEProblem(h, x0, tspan)
  sol = solve(prob)
  x1, y1 = getsolution(sol, times)
  d = sum((x1 .- targetdata[1]).^2 + (y1 .- targetdata[2]).^2)
  return d, sol
end

setup = ABCSMC(simLV,
  2,
  0.1,
  Prior([Uniform(0.0, 5.0), Uniform(0.0, 5.0)]),
  maxiterations = 10^6,
  convergence = 0.05,
  nparticles = 500
  )
@time ressmc = runabc(setup, targetdata, verbose = false, progress = false);

println("\t Check parameters are inferred correctly for Lotka Volterra model")
@test isapprox(mean(ressmc.parameters, weights(ressmc.weights), 1)[1], inputa, rtol = 0.05)
@test isapprox(mean(ressmc.parameters, weights(ressmc.weights), 1)[2], inputb, rtol = 0.05)


__precompile__()
module gauss_quadrature
using Dierckx
export  do_quad, delta_measure,Gauss_Hermite, Gauss_Hermite_per,QUAD, Golub_Welsch!,periodic_simplify!,simplify!,sortquad!,quad_to_pdf
##########################
function init(fcn, opt)
  #=
  =#
end
#######################
type QUAD
  length
  X
  w
end
#######################
function do_quad(test,quad::QUAD)
  # evaluate test fn wrt to quad
  #
  sum=0.0
  for i=1:quad.length
    sum += test(quad.X[i]) * quad.w[i]
  end
  return sum
end
##############################################
function lanczos(N,xw)
  # Adapted from Gautschi's OPQ Matlab routines
  Ncap=size(xw,1)
  if N<=0 || N>Ncap
      print("N:",N,"; Ncap:",Ncap)
     error("N out of range")
  end
  p0=xw[:,1]; p1=zeros(Ncap,1); p1[1]=xw[1,2]
  for n=1:Ncap-1
    pn=xw[n+1,2]; gam=1; sig=0; t=0; xlam=xw[n+1,1]
    for k=1:n+1
      rho=p1[k]+pn; tmp=gam*rho; tsig=sig

      if (rho)<=0
        gam=1; sig=0
      else
        gam=p1[k]/rho
        sig=pn/rho
      end
      tk=sig*(p0[k]-xlam)-gam*t
      p0[k]=p0[k]-(tk-t); t=tk
      if (sig)<=0
        pn=tsig*p1[k]
      else
        pn=(t^2)/sig
      end
      tsig=sig; p1[k]=tmp
    end
  end
  ab=[p0[1:N] p1[1:N]]
  return ab
end
######################################
function gauss(N,ab)
  # Adapted from Gautschi's OPQ Matlab routines
  N0=size(ab,1)
  if N0<N
    error("input array ab too short")
  end
  J=zeros(N,N)
  for n=1:N
     J[n,n]=ab[n,1]
  end
  for n=2:N
    J[n,n-1]=sqrt(ab[n,2])
    J[n-1,n]=J[n,n-1]
  end
  D,V=eig(J)
  #V=V(:,I);
  xw=[D ab[1,2]*V[1,:]'.^2]
  return xw
end
########################################
function Golub_Welsch!(quad::QUAD, m)
  # Compute m-point GQ
  # No change if input quad has too few points
  if quad.length> m
    m=round(Int16,m)
    ab=lanczos(m, [quad.X quad.w])
    xw=gauss(m,ab)
    quad.length=m
    quad.X=xw[:,1]
    quad.w=xw[:,2]
  end
end
###########################
function sortquad!(quad::QUAD)
  # sort quad points
  perm=sortperm(quad.X)
  quad.X=quad.X[perm]
  quad.w=quad.w[perm]
end
###########################
function simplify!(quad::QUAD, m, cutoff)
# Return reduced quadrature
# Group points outside interval [cutoff[1],cutoff[2]]
# Apply m-point GQ on interior
# No action if input quad less than m
  if quad.length > m
     ind=find(x->(x<=cutoff[1]), quad.X) # small nodes
     m1=length(ind); w1=sum(quad.w[ind])
     ind=find(x->(x>=cutoff[2]), quad.X) # big nodes
     m2=length(ind); w2=sum(quad.w[ind])
     if (quad.length-m1-m2>m)
       ind=find(y->(y>cutoff[1] && y<cutoff[2]), quad.X) # interior nodes
       ab=lanczos(m, [quad.X[ind] quad.w[ind]])
       xw=gauss(m,ab)
       quad.length=m+2
       quad.X=[cutoff[1]; xw[:,1]; cutoff[2]]
       quad.w=[w1;        xw[:,2]; w2] # update quad
     end
  end
end
#################################################
function partition_simplify!(quad::QUAD, m, d, cutoff)
# Apply periodic conditions and find m-point GQ
# on d sub-intervals.
  if quad.length > m
   ind=find(x->(x<cutoff[1]), quad.X) # small nodes
   m1=length(ind); w1=sum(quad.w[ind])
   ind=find(x->(x>cutoff[2]), quad.X) # big nodes
   m2=length(ind); w2=sum(quad.w[ind])
   if (quad.length-m1-m2>m)
    partition=collect(linspace(cutoff[1], cutoff[2], d+1))
    x=[]; w=[];
    for i=1:d
      inds=find(y->(y>partition[i] && y<=partition[i+1]), quad.X)
      quad1=QUAD(length(inds),quad.X[inds],quad.w[inds])
      Golub_Welsch!(quad1, max(5,m*min(1,10*sum(quad1.w) ) ) )
      x=[x; quad1.X]
      w=[w; quad1.w]
    end
    x=[cutoff[1]; x; cutoff[2]]
    w=[w1; w; w2]
    quad.length=length(x)
    quad.X=x
    quad.w=w
  end
 end
end
#####
function l1(q1::QUAD, q2::QUAD)
  mysum=0.
  i1=2; i2=2; c1=cumsum(q1.w); c2=cumsum(q2.w)
  if (q1.X[i1] < q2.X[i2]) # find smallest point
    b=q1.X[i1];  i1=i1+1
  else
    b=q2.X[i2];  i2=i2+1
  end
  while (i1<=q1.length && i2<=q2.length)
    if (q1.X[i1]<q2.X[i2]) # q1 is next smallest
      delta=q1.X[i1]-b; # work out delta X
      # interpolate on q2
      if (q2.X[i2]>q2.X[i2-1])
        lambda=(q1.X[i1]-q2.X[i2-1])/(q2.X[i2]-q2.X[i2-1])
        cinterp=lambda*c2[i2]+(1-lambda)*c2[i2-1]#
        c_delta=abs(cinterp-c1[i1]);
      else
        c_delta=0
      end
      # record current x
      b=q1.X[i1]; i1+=1
    else
      delta=q2.X[i2]-b; # work out delta X
      # interpolate on q2
      if q1.X[i1-1]<q1.X[i1]
        lambda=(q2.X[i2]-q1.X[i1-1])/(q1.X[i1]-q1.X[i1-1])
        cinterp=lambda*c1[i1]+(1-lambda)*c1[i1-1]
        c_delta=abs(cinterp-c2[i2])
      else
        c_delta=0
      end
      # record current x
      b=q2.X[i2]; i2+=1
    end
    mysum+=c_delta*delta
  end
  if (i1<q1.length)
    mysum+=sum(abs(c1[i1:end-1]).*(q1.w[i1+1:end]-q1.w[i1:end-1]))
  end
  if (i2<q2.length)
    mysum+=sum(abs(c2[i2:end-1]).*(q2.w[i2+1:end]-q2.w[i2:end-1]))
  end
  return mysum
end
#

#####
function l1cdf(q1::QUAD, cdf)
  mysum=0.
  c1=cumsum(q1.w)
  for i=2:q1.length,
    delta1=abs(c1[i-1]-cdf(q1.X[i-1] ))
    delta2=abs(c1[i]  -cdf(q1.X[i]) )
    mysum+= (q1.X[i]-q1.X[i-1])*(delta1+delta2)/2
  end
  return mysum
end
#################################################
function periodic_simplify!(quad::QUAD, m, length_scale)
# Apply periodic conditions and find m-point GQ
  quad.X=mod(quad.X, length_scale)
  Golub_Welsch!(quad, m)
end
#################################################
function partition_periodic_simplify!(quad::QUAD, m, d, length_scale)
# Apply periodic conditions and find m-point GQ
# on d sub-intervals.
  theta=mod(quad.X, length_scale)
  partition=collect(linspace(-1e-6, length_scale, d+1))
  x=[]; w=[];
  for i=1:d
    inds=find(y->(y>partition[i] && y<=partition[i+1]), theta)
    quad1=QUAD(length(inds),theta[inds],quad.w[inds])
    Golub_Welsch!(quad1, m)
    x=[x; quad1.X]
    w=[w; quad1.w]
  end
  quad.length=length(x)
  quad.X=x
  quad.w=w
end
###########################
function Gauss_Hermite(mu_sig_sq, n)
  # Returns m-point Gauss-Hermite quadrature rule
  # for N(m,s2), for mu_sig_sq=[m,s2]
  #
  # using Hermite recursion rule for w(x)=e^(-x^2)
  # and change of variable
  #
  println("GH: [mean,variance]=",mu_sig_sq)
  v =sqrt((1:n-1)/2.0);
  J=diagm(v,1)+diagm(v,-1)
  D,V=eig(J)
  xw=[D sqrt(pi)*V[1,:]'.^2]
  # change of variable
  f=x->mu_sig_sq[1]+x*sqrt(2*mu_sig_sq[2])
  weight_scale=sqrt(pi)
  quad=QUAD(n,f(xw[:,1]),xw[:,2]/weight_scale)
  #
  #
  println(quad.length,"-pt Gauss-Hermite rule.")
  sortquad!(quad)
  return quad
end
###########################
function Gauss_Hermite_per(mu_sig_sq, m, length_scale)
  # Returns m-point Gauss-Hermite quadrature rule
  # for N(m,s2), for mu_sig_sq=[m,s2]
  #
  # using Hermite recursion rule for w(x)=e^(-x^2)
  # and change of variable
  #
  # Apply periodic conditions on domain with length_scale length
  println("GH: [mean,variance]=",mu_sig_sq)
  nn=10*m
  v =sqrt((1:nn-1)/2.0);
  J=diagm(v,1)+diagm(v,-1)
  D,V=eig(J)
  xw=[D sqrt(pi)*V[1,:]'.^2]
  # change of variable
  f=x->mu_sig_sq[1]+x*sqrt(2*mu_sig_sq[2])
  weight_scale=sqrt(pi)
  quad=QUAD(nn,f(xw[:,1]),xw[:,2]/weight_scale)
  #
  periodic_simplify!(quad, m, length_scale)
  #
  println(quad.length,"-pt Gauss-Hermite rule.")
  sortquad!(quad)
  return quad
end
################################
function Gauss_Hermite_sech(mu_nu, n)
  # Returns m-point Gauss-Hermite quadrature rule
  # for sech^2( (x-mu)/(4 nu))/(8 nu)
  # for mu_nu=[mu,nu]
  # via Gauss-Hermite
  #
  nu=mu_nu[2]
  mu=mu_nu[1]
  s=nu*sqrt(8)
  mu_sig_sq=[mu, s^2]
  quad=Gauss_Hermite(mu_sig_sq, n)
  c=4*sqrt(pi*nu^2);
  helper=z-> c/(4*nu* (exp(-0.25*z/nu)+exp(0.25*z/nu))*exp(-(0.25*z/nu)^2) )
  for i=1:quad.length
    quad.w[i]=quad.w[i]*helper(quad.X[i]-mu)
  end
  quad.w=quad.w/sum(quad.w)
  return quad
end
#############################
function delta_measure(x)
  return  QUAD(1,x,1)
end
######################
function quad_to_pdf(quad::QUAD,n,s_in)
  sortquad!(quad)
  cdf_all=cumsum(quad.w)
  kk=3
  cdf_all=[zeros(kk);cdf_all;ones(kk)]
  eps=1e-2
  X=[quad.X[1]+linspace(-kk*eps,-eps,kk);quad.X;quad.X[end]+linspace(eps,kk*eps,kk)]
  n=min(quad.length,n)
  k=3
  range=collect(linspace(quad.X[1],quad.X[end],n))
  #print(length(X)," ", length(cdf_all), " ", length(range))
  #spl=Spline1D(X,cdf_all,range;w=ones(length(X)), k=k, bc="nearest")
  spl=Spline1D(X,cdf_all;w=ones(length(X)), k=k, bc="nearest",s=s_in)
  pdf=derivative(spl,range)
  cdf=spl(range)
  return range,pdf,cdf,X,cdf_all
end

######################
function quad_to_pdf(quad::QUAD)
  sortquad!(quad)
  cdf_all=cumsum(quad.w)
  kk=3
  cdf_all=[zeros(kk);cdf_all;ones(kk)]
  eps=1e-2
  X=[quad.X[1]+linspace(-kk*eps,-eps,kk);quad.X;quad.X[end]+linspace(eps,kk*eps,kk)]
  n=min(quad.length,n)
  k=3
  range=collect(linspace(quad.X[1],quad.X[end],n))
  #print(length(X)," ", length(cdf_all), " ", length(range))
  #spl=Spline1D(X,cdf_all,range;w=ones(length(X)), k=k, bc="nearest")
  spl=Spline1D(X,cdf_all)
  pdf=derivative(spl,range)
  cdf=spl(range)
  return range,pdf,cdf,X,cdf_all
end
#
end # module defn


CI = CartesianIndex

function parse_data(l)
    m = match(r".*x=(-?\d+)\.\.(-?\d+), y=(-?\d+)\.\.(-?\d+)", l)
    x1, x2, y1, y2 = parse.(Int, m.captures)
    CI(x1, y1), CI(x2, y2)
end

const p1, p2 = parse_data(readline())

function step(pos, vel)
    pos = pos .+ vel
    vel = (sign(vel[1]) * (abs(vel[1]) - 1) , vel[2]-1)
    pos, vel
end

function step_until(vel, limit=500)
    pos = (0, 0)
    for i in 1:limit
        pos, vel = step(pos, vel)
        is_inside(pos) && return 1
        if pos[2] < p1.I[2] || pos[1] > p2.I[1]
            break
        end
    end
    0
end

is_inside(pos) = CI(pos...) ∈ p1:p2

# Part One
let n = abs(p1.I[2])-1
    n*(n+1)÷2
end |> println

# Part Two
sum(step_until((velx, vely)) for velx in 1:300, vely in -200:300) |> println


function [D,J,JInv,X]=JacobiSphere(ksi,F,Grid)
Rad=Grid.Rad;
X1=Grid.Nodes(F.N(1)).P(1)...
  +(Grid.Nodes(F.N(2)).P(1)-Grid.Nodes(F.N(1)).P(1))*ksi(1)...
  +(Grid.Nodes(F.N(4)).P(1)-Grid.Nodes(F.N(1)).P(1))*ksi(2)...
  +(Grid.Nodes(F.N(3)).P(1)-Grid.Nodes(F.N(4)).P(1)-Grid.Nodes(F.N(2)).P(1)+Grid.Nodes(F.N(1)).P(1))*ksi(1)*ksi(2);
X2=Grid.Nodes(F.N(1)).P(2)...
  +(Grid.Nodes(F.N(2)).P(2)-Grid.Nodes(F.N(1)).P(2))*ksi(1)...
  +(Grid.Nodes(F.N(4)).P(2)-Grid.Nodes(F.N(1)).P(2))*ksi(2)...
  +(Grid.Nodes(F.N(3)).P(2)-Grid.Nodes(F.N(4)).P(2)-Grid.Nodes(F.N(2)).P(2)+Grid.Nodes(F.N(1)).P(2))*ksi(1)*ksi(2);
X3=Grid.Nodes(F.N(1)).P(3)...
  +(Grid.Nodes(F.N(2)).P(3)-Grid.Nodes(F.N(1)).P(3))*ksi(1)...
  +(Grid.Nodes(F.N(4)).P(3)-Grid.Nodes(F.N(1)).P(3))*ksi(2)...
  +(Grid.Nodes(F.N(3)).P(3)-Grid.Nodes(F.N(4)).P(3)-Grid.Nodes(F.N(2)).P(3)+Grid.Nodes(F.N(1)).P(3))*ksi(1)*ksi(2);
f=Rad*(X1^2+X2^2+X3^2)^(-3/2);

dx1dX1=f*(X2^2+X3^2);
dx1dX2=-f*X1*X2;
dx1dX3=-f*X1*X3;
dx2dX1=dx1dX2;
dx2dX2=f*(X1^2+X3^2);
dx2dX3=-f*X2*X3;
dx3dX1=dx1dX3;
dx3dX2=dx2dX3;
dx3dX3=f*(X1^2+X2^2);

% dx1dX1=1;
% dx1dX2=-0;
% dx1dX3=0;
% dx2dX1=0;
% dx2dX2=1;
% dx2dX3=0;
% dx3dX1=0;
% dx3dX2=0;
% dx3dX3=1;

dX1dksi1=(Grid.Nodes(F.N(2)).P(1)-Grid.Nodes(F.N(1)).P(1))...
  +(Grid.Nodes(F.N(3)).P(1)-Grid.Nodes(F.N(4)).P(1)...
  -Grid.Nodes(F.N(2)).P(1)+Grid.Nodes(F.N(1)).P(1))*ksi(2);
dX1dksi2=(Grid.Nodes(F.N(4)).P(1)-Grid.Nodes(F.N(1)).P(1))...
  +(Grid.Nodes(F.N(3)).P(1)-Grid.Nodes(F.N(4)).P(1)...
  -Grid.Nodes(F.N(2)).P(1)+Grid.Nodes(F.N(1)).P(1))*ksi(1);
dX2dksi1=(Grid.Nodes(F.N(2)).P(2)-Grid.Nodes(F.N(1)).P(2))...
  +(Grid.Nodes(F.N(3)).P(2)-Grid.Nodes(F.N(4)).P(2)...
  -Grid.Nodes(F.N(2)).P(2)+Grid.Nodes(F.N(1)).P(2))*ksi(2);
dX2dksi2=(Grid.Nodes(F.N(4)).P(2)-Grid.Nodes(F.N(1)).P(2))...
  +(Grid.Nodes(F.N(3)).P(2)-Grid.Nodes(F.N(4)).P(2)...
  -Grid.Nodes(F.N(2)).P(2)+Grid.Nodes(F.N(1)).P(2))*ksi(1);
dX3dksi1=(Grid.Nodes(F.N(2)).P(3)-Grid.Nodes(F.N(1)).P(3))...
  +(Grid.Nodes(F.N(3)).P(3)-Grid.Nodes(F.N(4)).P(3)...
  -Grid.Nodes(F.N(2)).P(3)+Grid.Nodes(F.N(1)).P(3))*ksi(2);
dX3dksi2=(Grid.Nodes(F.N(4)).P(3)-Grid.Nodes(F.N(1)).P(3))...
  +(Grid.Nodes(F.N(3)).P(3)-Grid.Nodes(F.N(4)).P(3)...
  -Grid.Nodes(F.N(2)).P(3)+Grid.Nodes(F.N(1)).P(3))*ksi(1);

J=zeros(3,2);

J(1,1)=dx1dX1*dX1dksi1+dx1dX2*dX2dksi1+dx1dX3*dX3dksi1;
J(2,1)=dx2dX1*dX1dksi1+dx2dX2*dX2dksi1+dx2dX3*dX3dksi1;
J(3,1)=dx3dX1*dX1dksi1+dx3dX2*dX2dksi1+dx3dX3*dX3dksi1;
J(1,2)=dx1dX1*dX1dksi2+dx1dX2*dX2dksi2+dx1dX3*dX3dksi2;
J(2,2)=dx2dX1*dX1dksi2+dx2dX2*dX2dksi2+dx2dX3*dX3dksi2;
J(3,2)=dx3dX1*dX1dksi2+dx3dX2*dX2dksi2+dx3dX3*dX3dksi2;

D=norm(cross(J(:,1),J(:,2)),2);
if nargout > 2
  X=[X1 X2 X3]*(Rad/sqrt((X1^2+X2^2+X3^2)));   X=[X1 X2 X3];
end
JInv=inv(J'*J)*J'*sqrt(det(J'*J));
end


###
#   An implementation of Transitional Markov Chain Monte Carlo in Julia
#
#            Institute for Risk and Uncertainty, Uni of Liverpool
#
#                       Authors: Ander Gray, Adolphus Lye
#
#                       Email: Ander.Gray@liverpool.ac.uk,
#                              Adolphus.Lye@liverpool.ac.uk
#
#
#   This Transitional MCMC algorithm is inspirted by OpenCOSSAN:
#                   https://github.com/cossan-working-group/OpenCossan
#
#
#   Algorithm originally proposed by:
#           J. Ching, and Y. Chen (2007). Transitional Markov Chain Monte Carlo method
#           for Bayesian model updating, Model class selection, and Model averaging.
#           Journal of Engineering Mechanics, 133(7), 816-832.
#           doi:10.1061/(asce)0733-9399(2007)133:7(816)
#
###

using TimerOutputs
const to = TimerOutput()

function tmcmc(
    log_fD_T::Function,
    log_fT::Function,
    sample_fT::Function,
    Nsamples::Integer,
    burnin::Integer=20,
    thin::Integer=3,
    beta2::Float64=0.01)

    j1 = 0;                     # Iteration number
    βj = 0;                     # Tempering parameter
    θ_j = sample_fT(Nsamples);  # Samples of prior
    Lp_j = zeros(Nsamples, 1);   # Log liklihood of first iteration

    Log_ev = 0                  # Log Evidence

    Ndims = size(θ_j, 2)         # Number of dimensions (input)

    covariance_method = LinearShrinkage(DiagonalUnitVariance(), :lw) # * DiagonalUnitVariance is always SPD!

    @timeit_debug to "Main while loop" while βj < 1

        j1 = j1 + 1

        @debug "Beginning iteration $j1"

        ###
        # Parallel evaluation of the likelihood
        ###

        @debug "Computing likelihood with $(nworkers()) workers..."

        Lp_j = pmap(log_fD_T, eachrow(θ_j))
        Lp_j = reduce(vcat, Lp_j)
        Lp_adjust = maximum(Lp_j)

        ###
        #   Computing new βj
        ###
        @debug "Computing β_j and weights..."

        @timeit_debug to "Compute β and weights " βj1, w_j = _beta_and_weights(βj, Lp_j .- Lp_adjust)

        @info "β_$(j1) = $(βj1)"

        @timeit_debug to "Log evidence" Log_ev = log(mean(w_j)) + (βj1 - βj) * Lp_adjust + Log_ev   # Log evidence in current iteration

        prop = mu -> proprnd(mu, Σ_j, log_fT) # Anonymous function for proposal
        target = x -> log_fD_T(x) .* βj1 .+ log_fT(x) # Anonymous function for transitional distribution

        # (Normalized) Weighted resampling of θj (indices with replacement)
        @timeit_debug to "Normalised weights" wn_j = w_j ./ sum(w_j); # normalize weights
        @timeit_debug to "Compute indices" indices = sample(1:Nsamples, Weights(wn_j), Nsamples, replace=true)
        @timeit_debug to "Update θ_j1" θ_j1 = θ_j[indices, :]

        # Estimate covariance matrix
        @timeit_debug to "Compute covariance" Σ_j = beta2 * cov(covariance_method, θ_j1)

        @debug "Markov chains with $(nworkers()) workers..."

        # pmap is a parallel map
        @timeit_debug to "Run markov chains" θ_j1 = pmap(x -> metropolis_hastings_simple(target, prop, x, 1, burnin, thin)[1], eachrow(θ_j1))
        θ_j1 = reduce(vcat, θ_j1)

        βj = βj1
        θ_j = θ_j1
    end
    return θ_j, Log_ev
end

"""
    _beta_and_weights(β, likelihood)

Compute the next value for `β` and the nominal weights `w` using bisection.
"""
function _beta_and_weights(β::Real, adjusted_likelihood::AbstractVector{<:Real})
    low = β
    high = 2

    local x, w # Declare variables so they are visible outside the loop

    while (high - low) / ((high + low) / 2) > 1e-6
        x = (high + low) / 2
        w = exp.((x .- β) .* adjusted_likelihood)

        if std(w) / mean(w) > 1
            high = x
        else
            low = x
        end
    end

    return min(1, x), w
end

function proprnd(mu::AbstractVector, covMat::AbstractMatrix, prior::Function)
    samp = rand(MvNormal(mu, covMat), 1)
    while isinf(prior(samp))
        samp = rand(MvNormal(mu, covMat), 1)
    end
    return samp[:]
end


using Base.Test

using CompScienceMeshes
using BEAST

const e0 = Point(0.0,0.0,0.0)
const e1 = Point(1.0,0.0,0.0)
const e2 = Point(0.0,1.0,0.0)
const e3 = Point(0.0,0.0,1.0)

p = patch(
    [
        Point(0.0,0.0,0.0),
        Point(1.0,0.0,0.0)
    ],
    Val{1}
)

q = patch(
    [
        Point(0.0,0.0,0.0),
        Point(0.5,0.0,0.0)
    ],
    Val{1}
)

f = LagrangeRefSpace{Float64,1}()
x = neighborhood(p, [0.0])
v = f(x)
@test v == Vec(0.0, 1.0)

Q = restrict(f, p, q)
@test Q == [
    1.0 0.5
    0.0 0.5
]

# Test restriction of RT elements
ni, no = 6, 7;
ui = transpose([triangleGaussA[ni] triangleGaussB[ni] ]);
uo = transpose([triangleGaussA[no] triangleGaussB[no] ]);
wi = triangleGaussW[ni];
wo = triangleGaussW[no];
universe = Universe(1.0, ui, wi, uo, wo);

p = patch([2*e0,2*e1,2*e2], Val{2})
x = neighborhood(p,[0.5, 0.5])
ϕ = RTRefSpace{Float64}()
v = ϕ(x)

Q = restrict(ϕ, p, p)
@test Q == eye(3)

q = patch([e0+e1, 2*e1, e1+e2], Val{2})
Q = restrict(ϕ, p, q)
@test Q == [
    2 -1 0
    0  1 0
    0 -1 2] // 4


# Functions for negative binomial outcome model in GBM.
#
# Copyright (c) 2020: Jeffrey W. Miller.
# This file is released under the MIT "Expat" License.

module Outcome_NB

export compute_MuWE!, compute_MuWE, loglikelihood, compute_logdispersion_derivatives, update_logdispersions

using Statistics: mean
using SpecialFunctions: logabsgamma, digamma, trigamma
lgamma_(x) = logabsgamma(x)[1]

# Compute Mu, W, and E
function compute_MuWE!(Mu,W,E,Y,logMu,r)
    Mu .= exp.(logMu)
    W .= (r.*Mu)./(r.+Mu)
    E .= (Y - Mu).*(W./Mu)
end

function compute_MuWE(Y,logMu,r)
    I,J = size(Y)
    Mu = zeros(I,J)
    W = zeros(I,J)
    E = zeros(I,J)
    compute_MuWE!(Mu,W,E,Y,logMu,r)
    return Mu,W,E
end

# log-likelihood of the Negative-Binomial distribution with mean Mu and inverse-dispersion r, on data Y.
# loglikelihood(Y,Mu,r) = sum(lgamma_.(r.+Y) .- lgamma_.(Y.+1) .- lgamma_.(r) .+ r.*log.(r) .+ Y.*log.(Mu) .- (r.+Y).*log.(r.+Mu))
# loglikelihood(Y,Mu,r) = sum(lgamma_.(r.+Y) .- lgamma_.(r) .- lgamma_.(Y.+1) .- Y.*log1p.(r./Mu) .- r.*log1p.(Mu./r))
function loglikelihood(Y,Mu,r)
    I,J = size(Y)
    ll = 0.0
    for i = 1:I
        for j = 1:J
            ll += lgamma_(r[i,j]+Y[i,j]) - lgamma_(r[i,j]) - lgamma_(Y[i,j]+1) - Y[i,j]*log1p(r[i,j]/Mu[i,j]) - r[i,j]*log1p(Mu[i,j]/r[i,j])
        end
    end
    return ll
end

digamma_delta(y,r) = ((r > 10^8) ? log1p(y/r) : digamma(y+r)-digamma(r))
trigamma_delta(y,r) = ((r > 10^8) ? -(y/r)/(y+r) : trigamma(y+r)-trigamma(r))

function compute_logdispersion_derivatives(Y,Mu,r)
    I,J = size(Y)
    D1 = zeros(I,J)
    D2 = zeros(I,J)
    for i = 1:I, j = 1:J
        D1[i,j] = -r[i,j]*(digamma_delta(Y[i,j],r[i,j]) - log1p(Mu[i,j]/r[i,j]) - (Y[i,j]-Mu[i,j])/(r[i,j]+Mu[i,j]))

        if isinf(Mu[i,j]^2)
            D2[i,j] = -D1[i,j] + (r[i,j]^2)*trigamma_delta(Y[i,j],r[i,j]) + r[i,j]
        else
            D2[i,j] = -D1[i,j] + (r[i,j]^2)*trigamma_delta(Y[i,j],r[i,j]) + (Y[i,j] + Mu[i,j]^2/r[i,j]) / (1.0 + Mu[i,j]/r[i,j])^2
        end
    end
    return D1,D2
end

function update_logdispersions(S,T,omega,Y,Mu,r,p,max_step,b_S,b_T,update_S,update_T,verbose)
    I,J = size(Y)

    if update_S
    # Update S
    D1,D2 = compute_logdispersion_derivatives(Y,Mu,r)
    g = vec(sum(D1; dims=2)) .- p.lambda_S*(S .- p.mu_S)
    h = vec(sum(D2; dims=2)) .- p.lambda_S
    delta = (g./h).*(h.<0) .+ (-g).*(h.>=0)
    S = S - delta.*min.(b_S./abs.(delta), 1)
    if any(abs.(delta) .> b_S) && verbose; println(" max step size enforced in S[i] update for one or more i."); end
    b_S = [(abs(delta[i]) > b_S[i] ?  b_S[i]/2 : max_step) for i = 1:I]
    shift = log(mean(exp.(S)))
    S = S .- shift  # Project onto constrained space
    omega = omega + shift  # Compensate to preserve likelihood
    r = exp.(-(S.+T'.+omega))
    end

    if update_T
    # Update T
    D1,D2 = compute_logdispersion_derivatives(Y,Mu,r)
    g = vec(sum(D1; dims=1)) .- p.lambda_T*(T .- p.mu_T)
    h = vec(sum(D2; dims=1)) .- p.lambda_T
    delta = (g./h).*(h.<0) .+ (-g).*(h.>=0)
    T = T - delta.*min.(b_T./abs.(delta), 1)
    if any(abs.(delta) .> b_T) && verbose; println(" max step size enforced in T[j] update for one or more j."); end
    b_T = [(abs(delta[j]) > b_T[j] ?  b_T[j]/2 : max_step) for j = 1:J]
    shift = log(mean(exp.(T)))
    T = T .- shift  # Project onto constrained space
    omega = omega + shift  # Compensate to preserve likelihood
    r = exp.(-(S.+T'.+omega))
    end

    return S,T,omega,r,b_S,b_T
end

end # module Outcome_NB


using IterTools
using LinearAlgebra
using Distributions
using OrdinaryDiffEq

export get_max_num, master_operator, steady_state_masterOP, dynamics_masterOP,
		dynamics_masterOP_diag

"""
	get_max_num(x0::Vector{Float64}, precision::Float64)

	Calculates the vector of maximum number of species allowed where the
	Poisson distribution is below a certain probability (precision)

	Mean of Poisson distribution for each species is the initial condition x0

	Returns a vector of max_num

"""
function get_max_num(x0::Vector{Float64}, precision::Float64)

	num_species = length(x0)
	max_num = zeros(Int, num_species)
	for i in 1:num_species
		x,n = 1,0
		while abs(x) > precision
			x = 1-cdf(Poisson(x0[i]), n)
			n += 1
		end
		max_num[i] = n
	end
	return max_num

end
"""
	master_operator(p::Parameters, max_num; α=1.0)

	Calculates the master operator for the reaction network
	where the state space will be truncated at a maximum number
	of species `max_num`

	Input
	p		Parameters struct for the reaction network
	max_num	number of species where the state space is truncated
	α		Plefka expansion parameter, default is α=1.0

	Returns the master operator and state space in a tuple

"""
function master_operator(p::Parameters, max_num::Vector{Int}; α=1.0)

	num_species = length(p.k[1])	# number of species
	params = p.k	# reaction rates
	s_i = p.s_i		# Stoichiometric products
	r_i = p.r_i		# Stoichiometric reactants

	# state space
	x = [collect(0:max_num[j]) for j in 1:num_species]
	state_space = reduce(vcat, collect(Iterators.product(x...)))

	# master operator
	len_state_space = prod(max_num .+ 1)
	master = zeros(len_state_space, len_state_space)

	for s in state_space

		# get index from s (assume only one appearence)
		idx_s = findfirst(x->x==s, state_space)

		for j in 1:num_species

			# Annihiliation
			t = collect(s)
			t[j] += 1
			t = tuple(t...)
			if t[j] <= max_num[j]
				idx_t = findfirst(x->x==t, state_space)		# index of t
				master[idx_s, idx_t] += params[2][j]*state_space[idx_t][j]
			end

			# Creation
			t = collect(s)
			t[j] -= 1
			t = tuple(t...)
			if t[j] >= 0
				idx_t = findfirst(x->x==t, state_space) 	# index of t
				master[idx_s, idx_t] += params[1][j]
			end

		end

		# Interaction
		for β in 1:length(params[3])

			new_state = tuple([(s[m] - s_i[β,m] + r_i[β,m]) for m in 1:num_species]...)

			if all(state_space[1] .<= new_state) && all(state_space[end] .>= new_state)
				idx = findfirst(x->x==new_state, state_space)
				r = α*params[3][β]
				for n in 1:num_species
					for p in 0:r_i[β,n]-1
						r *= (state_space[idx][n] - p)
					end
				end
				master[idx_s, idx] += r
			end

		end

	end

	# sum along columns must be zero
	master[diagind(master)] = -sum(master, dims=1)

	return (master, state_space)

end

function master_operator(p::Parameters, max_num::Int; α=1.0)
	max_num_vec = max_num .* ones(Int, length(p.k[1]))
	return master_operator(p::Parameters, max_num_vec; α=α)
end

"""
	steady_state_masterOP(master, state_space)

	Calculates the steady state of the master operator and state space

	Returns the steady state

"""
function steady_state_masterOP(master, state_space, E::Eigen)

	num_species = length(state_space[1])	# number of species

	# index of largest eigenvalue
	max_eig_idx = argmax(real.(E.values))

	# sum of eigenvectors of the highest eigenvalue
	sum_eigvecs = sum(abs.(E.vectors[:,max_eig_idx]))

	x0 = zeros(num_species)		# returned steady state

	for i in 1:length(state_space)
		for j in 1:num_species
			x0[j] += state_space[i][j] * abs(E.vectors[i,max_eig_idx]) / sum_eigvecs
		end
	end

	return x0

end

function steady_state_masterOP(master, state_space)
	E = eigen(master, sortby=nothing)		# eigenvalues and eigenvectors
	return steady_state_masterOP(master, state_space, E)
end

"""
	calc_mean_masterOP(p, state_space)

	Calculates the mean concentration with the probability vector `p`
	and state space

	Returns the calculated mean

"""
function calc_mean_masterOP(p::Vector{Float64}, state_space)

	num_species = length(state_space[1])	# number of species
	x = zeros(num_species)					# returned mean

	# sum of probabilities, should be == 1
	sum_p = sum(abs.(p))

	for i in 1:length(state_space)
		for j in 1:num_species
			x[j] += state_space[i][j] * abs(p[i]) / sum_p
		end
	end

	return x

end

"""
	initial_distr_state_space(state_space, x0)

	Calculates the initial Poisson distribution with means `x0`
	for the different species.

	Returns the initial probability distribution vector

"""
function initial_distr_state_space(state_space, x0::Vector{Float64})

	num_species = length(state_space[1])	# number of species
	p0 = ones(length(state_space))			# initial prob. distr.

	for i in 1:length(state_space)
		for j in 1:num_species
			p0[i] *= pdf(Poisson(x0[j]), state_space[i][j])
		end
	end

	return p0./sum(p0)

end

"""
	dynamics_masterOP(master, state_space, tspan, x0, method)

	Runs the dynamics for the master operator using an euler step integrator.

	Input
	master		master operator
	state_space	truncated state space
	tspan		time grid
	x0			initial condition for copy numbers
	method		integration algorithm. If not specified: default is Tsit5()

	You can find a list of available solvers at https://diffeq.sciml.ai/stable/solvers/ode_solve/

	Returns a Result struct with time_grid and mean copy numbers

"""
function dynamics_masterOP(master, state_space, tspan::Vector{Float64}, x0::Vector{Float64}, method; rtn_prob=false)

	# Differential equation
	function f!(du,u,M,t)
		du .= M*u
	end

	# Solve the differential equation
	p0 = initial_distr_state_space(state_space, x0)
    P = ode_solver(f!, master, tspan, p0, method; savedt=true)

    # Convert probability distributions to mean copy numbers
	y = zeros(length(x0), length(tspan))
	for t in 1:length(tspan)
		y[:,t] = calc_mean_masterOP(P.data[:,t], state_space)
	end

	if rtn_prob
		return (P.data, Result(tspan, y))
	else
		return Result(tspan, y)
	end
end

function dynamics_masterOP(master, state_space, tspan::Vector{Float64}, x0::Vector{Float64})
	println("No integration algorithm specified! Using Tsit5() as default!")
	dynamics_masterOP(master, state_space, tspan, x0, Tsit5)
end

"""
	dynamics_masterOP_diag(master, state_space, E::Eigen, tspan, x0)

	Runs the dynamics for the master operator by diagonalizing the master
	operator and directly calculates the matrix exponential.

	Solves the master equation   dP/dt = M*P   by the solution

		P(t) = exp(M*t) * P0

	Input
	master		master operator
	state_space	truncated state space
	E			Eigen values and vectors, calculate by `eigen(master)`
	tspan		time grid
	x0			initial condition for copy numbers

	Returns a Result struct with time_grid and mean copy numbers

"""
function dynamics_masterOP_diag(master, state_space, E::Eigen, tspan, x0; rtn_prob=false)

	er = E.vectors
	el = inv(er)
	d = E.values #real.(E.values)

	p0 = initial_distr_state_space(state_space, x0)
	y = zeros(length(x0), length(tspan))
	p = zeros(length(state_space), length(tspan))

	elp0 = el*p0

	for j in 1:length(tspan)
		for i in 1:length(state_space)
			@inbounds p[i,j] = abs(sum(er[i,:].*exp.(d*tspan[j]).*elp0))
		end
		p[:,j] ./= sum(p[:,j])
		y[:,j] = CRN.calc_mean_masterOP(p[:,j], state_space)
	end

	if rtn_prob
		return (p, Result(tspan, y))
	else
		return Result(tspan, y)
	end

end

function dynamics_masterOP_diag(master, state_space, tspan, x0)
	E = eigen(master, sortby=nothing)		# eigenvalues and eigenvectors
	return dynamics_masterOP_diag(master, state_space, E, tspan, x0)
end


@defstruct WassersteinLossLayer Layer (
  name :: AbstractString = "wasserstein-loss",
  (M :: Matrix = [], !isempty(M)),
  (lambda :: AbstractFloat = 0, lambda > 0),
  (sinkhorn_iter :: Int = 50, sinkhorn_iter > 0),
  (bottoms :: Vector{Symbol} = Symbol[], length(bottoms) == 2)
)
@characterize_layer(WassersteinLossLayer,
  has_loss  => true,
  can_do_bp => true,
  is_sink   => true,
)

type WassersteinLossLayerState{T} <: LayerState
  layer :: WassersteinLossLayer
  loss  :: T

  K     :: Blob
  KM    :: Blob
  alpha :: Blob
  u     :: Blob
  tmps  :: Vector{Blob}
end

function setup(backend::Backend, layer::WassersteinLossLayer, inputs::Vector{Blob}, diffs::Vector{Blob})
  data_type = eltype(inputs[1])

  pred  = inputs[1]
  label = inputs[2]
  @assert size(layer.M, 1) == get_fea_size(pred)
  @assert size(layer.M, 2) == get_fea_size(label)

  K = convert(Array{data_type}, exp(-layer.lambda * layer.M))
  KM = K .* convert(Array{data_type}, layer.M)
  alpha  = make_blob(backend, zeros(data_type, get_fea_size(pred), get_num(pred)))
  u = make_blob(backend, zeros(data_type, get_fea_size(pred), get_num(pred)))
  state = WassersteinLossLayerState(layer, zero(data_type),
      make_blob(backend, K), make_blob(backend, KM), alpha, u, Blob[])
  return state
end

function shutdown(backend::Backend, state::WassersteinLossLayerState)
  destroy(state.K)
  destroy(state.KM)
  destroy(state.alpha)
  destroy(state.u)
  map(destroy, state.tmps)
end

function sinkhorn(backend::CPUBackend, state::WassersteinLossLayerState, inputs::Vector{Blob})
  pred  = inputs[1]
  label = inputs[2]

  data_type = eltype(pred)
  pred_size = get_fea_size(pred)
  pred_num  = get_num(pred)

  # init as uniform distribution
  copy!(state.u, ones(data_type, pred_size, pred_num) / pred_size);
  u = state.u.data

  a = reshape(pred.data, pred_size, pred_num)
  b = reshape(label.data, get_fea_size(label), pred_num)
  K = state.K.data

  for iter = 1:state.layer.sinkhorn_iter
    u = a ./ (K * (b./(u'*K)'))
  end

  # compute objective function
  v = b ./ (K'*u)
  state.loss = sum(u .* ((state.KM.data)*v)) / pred_num

  # compute gradient
  alpha = log(u) / convert(data_type, state.layer.lambda) / pred_num
  copy!(state.alpha, alpha)
end

function forward(backend::Backend, state::WassersteinLossLayerState, inputs::Vector{Blob})
  sinkhorn(backend, state, inputs)
end

function backward(backend::Backend, state::WassersteinLossLayerState, inputs::Vector{Blob}, diffs::Vector{Blob})
  diff = diffs[1]
  if !isa(diff, NullBlob)
    copy!(diff, state.alpha)
  end
end


#Basic Example of IRLS in a noiseless situation

using PyPlot
using CompressedSensing

srand(0)

#Signal Dimensions
n=1000
#measurement dimension
m=400
#Approximate Sparsity
k=100

#Create a random sparse signal
Signal = zeros(n)
Signal[int(rand(k)*(n-1)+1)] = randn(k)

#Create a random measurement matrix
MM = randn(m,n)

#Measure the signal using the measurement matrix
Measurement = MM*Signal

#Reconstruct using IRLS
Reconstruction = IRLS(MM,Measurement,verbose=true)

#plot the results
bar(1:length(Signal),Signal,width=.01) #easiest method for plotting thin lines
SigHandle = plt.Line2D([], [],label="Signal",color="black") #by using bar() plot above, the legend
						#gets all funky, so to fix it make a handle that contains the correct
						#information
ReconPlot=scatter(1:length(Reconstruction),Reconstruction,label="IRLS Reconstruction")
xlim(0,length(Signal))
legend(loc=1,handles=[SigHandle,ReconPlot])
savefig("Example_1_Fig_1.png")


using MultivariateStats
using Base.Test

srand(34568)

## data
n = 10
d = 5
X = randn(d, n)

# step-by-step kernel centralization
for K in [
    reshape(1.:12., 3, 4),
    reshape(1.:9., 3, 3),
    reshape(1.:12., 4, 3),
    rand(n,d),
    rand(d,d),
    rand(d,n) ]

    x, y = size(K)
    I1 = ones(x,x)/x
    I2 = ones(y,y)/y
    Z = K - I1*K - K*I2 + I1*K*I2

    KC = fit(MultivariateStats.KernelCenter, K)
    @test all(isapprox.(Z, MultivariateStats.transform!(KC, copy(K))))
end

# kernel calculations
K = MultivariateStats.pairwise((x,y)->norm(x-y), X, X[:,1:2])
@test size(K) == (n, 2)
@test K[1,1] == 0
@test K[3,2] == norm(X[:,3] - X[:,2])

K = MultivariateStats.pairwise((x,y)->norm(x-y), X[:,1:3], X)
@test size(K) == (3, n)
@test K[1,1] == 0
@test K[3,2] == norm(X[:,2] - X[:,3])

K = similar(X, n, n)
MultivariateStats.pairwise!(K, (x,y)->norm(x-y), X)
@test size(K) == (n, n)
@test K[1,1] == 0
@test K[2,1] == norm(X[:,2] - X[:,1])

KC = fit(MultivariateStats.KernelCenter, K)
Iₙ = ones(n,n)/n
@test MultivariateStats.transform!(KC, copy(K)) ≈ K - Iₙ*K - K*Iₙ + Iₙ*K*Iₙ

K = MultivariateStats.pairwise((x,y)->norm(x-y), X, X[:,1])
@test size(K) == (n, 1)
@test K[1,1] == 0
@test K[2,1] == norm(X[:,2] - X[:,1])

KC = fit(MultivariateStats.KernelCenter, K)
@test all(isapprox.(MultivariateStats.transform!(KC, copy(K)), 0.0, atol=10e-7))

## check different parameters
X = randn(d, n)
M = fit(KernelPCA, X, maxoutdim=d)
M2 = fit(PCA, X, method=:cov, pratio=1.0)
@test indim(M) == d
@test outdim(M) == d
@test abs.(transform(M, X)) ≈ abs.(transform(M2, X))
@test abs.(transform(M, X[:,1])) ≈ abs.(transform(M2, X[:,1]))

M = fit(KernelPCA, X, maxoutdim=3, solver=:eigs)
M2 = fit(PCA, X, method=:cov, maxoutdim=3)
@test indim(M) == d
@test outdim(M) == 3
@test abs.(transform(M, X)) ≈ abs.(transform(M2, X))
@test abs.(transform(M, X[:,1])) ≈ abs.(transform(M2, X[:,1]))

# issue #44
Y = randn(d, 2*n)
@test size(transform(M, Y)) == size(transform(M2, Y))

# reconstruction
@test_throws ArgumentError reconstruct(M, X)
M = fit(KernelPCA, X, inverse=true)
@test all(isapprox.(reconstruct(M, transform(M, X)), X, atol=0.75))

# use rbf kernel
γ = 10.
rbf=(x,y)->exp(-γ*norm(x-y)^2.0)
M = fit(KernelPCA, X, kernel=rbf)
@test indim(M) == d
@test outdim(M) == d

# use precomputed kernel
K = MultivariateStats.pairwise((x,y)->x'*y, X)
@test_throws AssertionError fit(KernelPCA, rand(1,10), kernel=nothing) # symmetric kernel
M = fit(KernelPCA, K, maxoutdim = 5, kernel=nothing, inverse=true) # use precomputed kernel
M2 = fit(PCA, X, method=:cov, pratio=1.0)
@test_throws ArgumentError reconstruct(M, X) # no reconstruction for precomputed kernel
@test abs.(transform(M)) ≈ abs.(transform(M2, X))

@test_throws ArgumentError fit(KernelPCA, rand(1,10), kernel=1)

# fit a Float32 matrix
X = randn(Float32, d, n)
M = fit(KernelPCA, X)
@test indim(M) == d
@test outdim(M) == d
@test eltype(transform(M, X[:,1])) == Float32

## fit a sparse matrix
X = sprandn(100d, n, 0.6)
M = fit(KernelPCA, X, maxoutdim=3, solver=:eigs)
@test indim(M) == 100d
@test outdim(M) == 3


"""
    legendre(n, x)

Compute the Legendre polynomial of order n at point x.

The Legendre polynomials are a special case of the Jacobi polynomials with
α, β = 0.  The implementation here reflects that.
"""
function legendre(n, x)
    return Jacobi.jacobi(n, 0, 0, x)
end


# TODO: Remove when done with this.

import Pkg; Pkg.activate("../")
using CytofDiffDensity; const cdd = CytofDiffDensity
using Distributions
using MCMC
using StatsPlots
import Random

function compute_post_density(chain::AbstractVector, ygrid)
  function compute_pdf(c::NamedTuple)
    comps = SkewT.(c.mu, c.sigma, c.nu, c.phi)
    return pdf.(MixtureModel(comps, c.eta), ygrid)
  end
  return compute_pdf.(chain)
end

# Normal Ordered Prior.
K = 3
m1 = MixtureModel([Normal(-2, .3), Normal(0, .2), Normal(1, .4)], [.3, .4, .3])
y = rand(m1, 1000)
histogram(y, label=nothing, bins=100)
onm_prior = cdd.make_ordered_prior(y, K)
model1= MixSkewT(y, K, mu=onm_prior, tdist=false, skew=false)
spl = make_sampler(model1)
chain, metrics = mcmc(spl, 5000, nburn=100, thin=1)
@assert isapprox(mean.(m1.components), mean(getindex.(chain, :mu)), atol=0.2)
@assert isapprox(std.(m1.components), mean(getindex.(chain, :sigma)), atol=0.1)
plot(hcat(getindex.(chain, :mu)...)')

# SkewT Ordered Prior.
K = 3
Random.seed!(1)
m2 = MixtureModel([SkewT(-5, 1, 10, -7),
                   SkewT(0, 2, 10, -12),
                   SkewT(3, 3, 10, -10)], [.3, .4, .3])
y = rand(m2, 10000)
histogram(y, bins=100, label=nothing)

Random.seed!(0)
# onm_prior = cdd.make_ordered_prior(y, K, s=(maximum(y)-minimum(y))/2K)
onm_prior = cdd.make_ordered_prior(y, K)
model2 = MixSkewT(y, K, mu=onm_prior, skew=true, tdist=true)
spl = make_sampler(model2)
nburn, nsamps, thin = (3000, 1000, 1)
callback = make_callback(model2, nburn=nburn, nsamps=nsamps, thin=thin)
chain, metrics = mcmc(spl, nsamps, nburn=nburn, thin=thin, callback=callback)
plot(metrics[:loglike])

mean(getindex.(chain, :mu))
mean(getindex.(chain, :eta))
plot(hcat(getindex.(chain, :mu)...)', label=nothing)
hline!(getfield.(m2.components, :loc), label=nothing, ls=:dot, color=:black)
plot(hcat(getindex.(chain, :sigma)...)')
hline!(getfield.(m2.components, :scale), label=nothing, ls=:dot, color=:black)
plot(hcat(getindex.(chain, :nu)...)')
hline!(getfield.(m2.components, :df), label=nothing, ls=:dot, color=:black)
plot(hcat(getindex.(chain, :phi)...)')
hline!(getfield.(m2.components, :skew), label=nothing, ls=:dot, color=:black)
plot(hcat(getindex.(chain, :eta)...)')
hline!(m2.prior.p, label=nothing, ls=:dot, color=:black)

begin
  ygrid = cdd.make_ygrid(y)
  dens = compute_post_density(chain, ygrid)
  plot(ygrid, pdf.(m2, ygrid), label=nothing, color=:black)
  dens_lower = vec(MCMC.quantiles(hcat(dens...), 0.025, dims=2))
  dens_upper = vec(MCMC.quantiles(hcat(dens...), 0.975, dims=2))
  plot!(ygrid, dens_lower, fillrange=dens_upper, label=nothing, alpha=0.3)
  histogram!(y, normalize=true, la=0, alpha=.3)
end


# Implement GreedyMotifSearch
using DataStructures

const NUCLEOTIDES = ('A', 'C', 'G', 'T')

struct ProfileColumn
    A::Float64
    C::Float64
    G::Float64
    T::Float64
end

function Base.argmax(column::ProfileColumn)
    ((getfield(column, symbol), String(symbol)) for symbol in fieldnames(ProfileColumn)) |> maximum |> last
end

convert_to_intlist(line) = map(x -> parse(Int, x), split(line))

calc_hamming_distance(s1, s2) = count(((c1, c2),) -> c1 != c2, zip(s1, s2))

function create_profile_matrix_from_motifs(motifs)
    nr_motifs = length(motifs)
    profile_matrix = ProfileColumn[]
    for column in zip(motifs...)
        counts = counter(column)
        vals = (get(counts, nucleotide, 0) / nr_motifs for nucleotide in NUCLEOTIDES)
        push!(profile_matrix, ProfileColumn(vals...))
    end
    profile_matrix
end

function calc_profile_matrix_score(motifs)
    profile_matrix = create_profile_matrix_from_motifs(motifs)
    consensus = join(map(argmax, profile_matrix))
    foldl((score, motif) -> score + calc_hamming_distance(motif, consensus), motifs; init=0)
end

calc_kmer_probability(kmer, profile_matrix) =
    foldl((p, (nucleotide, column)) -> p * getfield(column, Symbol(nucleotide)), zip(kmer, profile_matrix); init=1.0)

function profile_most_probable_kmer(text, profile_matrix, k)
    max_probability = 0.0
    most_probable_kmer = text[1:k]
    for ix in 1:(length(text)-k+1)
        kmer = text[ix:ix+k-1]
        p = calc_kmer_probability(kmer, profile_matrix)
        if p > max_probability
            max_probability = p
            most_probable_kmer = kmer
        end
    end
    most_probable_kmer
end

function greedy_motif_search(texts, k)
    best_motifs = map(text -> text[1:k], texts)
    best_score = calc_profile_matrix_score(best_motifs)
    for ix in 1:(length(texts[1])-k+1)
        kmer = texts[1][ix:ix+k-1]
        motifs = [kmer]
        for text in texts[2:end]
            profile = create_profile_matrix_from_motifs(motifs)
            motif = profile_most_probable_kmer(text, profile, k)
            push!(motifs, motif)
        end
        score = calc_profile_matrix_score(motifs)
        if score < best_score
            best_score = score
            best_motifs = motifs
        end
    end
    best_motifs
end

function main()
    k, t = convert_to_intlist(readline())
    texts = map(_ -> readline(), 1:t)
    result = greedy_motif_search(texts, k)
    foreach(println, result)
end

main()


"""
    GaussianRewards(mf::Tuple{Vararg{<:Function}}; <keyword arguments>)

Construct a callable object to sample gaussian rewards.

# Arguments
- `mf::Tuple{Vararg{<:Function}}`: A Tuple of functions which take a 1-dimensional input and
    output the (scalar) mean reward for the corresponding action.
- `σ::Float64`: The standard deviation of the gaussian noise applied to each reward.
"""
struct GaussianRewards{T<:Tuple{Vararg{<:Function}}} <: AbstractRewardSampler
    mf::T
    σ::Float64

    function GaussianRewards(mf::T; σ::Float64=1.0) where {T<:Tuple{Vararg{<:Function}}}
        return new{T}(mf, σ)
    end
end

function (sampler::GaussianRewards)(X::AbstractMatrix, a::AbstractVector{<:Int})
    n = size(X, 2)
    r = zeros(1, n)
    for i in 1:n
        r[i] = sampler.mf[a[i]](X[:, i])
    end
    r += rand(Normal(0, sampler.σ), 1, n)
    return r
end


module FinSets
export FinOrd, FinOrdFunction, force, terminal, product, equalizer, pullback,
  initial, coproduct, coequalizer, pushout

using AutoHashEquals
using DataStructures: IntDisjointSets, union!, find_root

using ...GAT
using ...Theories: Category
using ..ShapeDiagrams
import ...Theories: dom, codom, id, compose, ⋅, ∘

# Data types
############

""" Finite ordinal (natural number).

An object in the category of finite ordinals, which is the skeleton of the
category of finite sets.
"""
@auto_hash_equals struct FinOrd{T<:Integer}
  n::T
end

""" Function between sets in the form of finite ordinals.

A morphism in the category of finite ordinals, which is the skeleton of the
category of finite sets.

In this data structure, the field `func` representing the function can have any
Julia type `T`, provided that `FinOrdFunction{T}` is callable. Usually, this
object will be an ordinary Julia function or an abstract vector. In the latter
case, the function (1↦1, 2↦3, 3↦2, 4↦3) is represented by the vector [1,3,2,3].
"""
@auto_hash_equals struct FinOrdFunction{F,T<:Integer}
  func::F
  dom::T
  codom::T
end
FinOrdFunction(f::AbstractVector) = FinOrdFunction(f, maximum(f))
FinOrdFunction(f::AbstractVector, codom::Integer) =
  FinOrdFunction(f, length(f), codom)

# Function objects are callable.
(f::FinOrdFunction)(i::Integer) = f.func(i)
(f::FinOrdFunction{<:AbstractVector})(i::Integer) = f.func[i]
(f::FinOrdFunction{typeof(id)})(i::Integer) = i

""" Force evaluation of function, yielding the vector representation.
"""
force(f::FinOrdFunction) = FinOrdFunction(map(f, 1:f.dom), f.codom)
force(f::FinOrdFunction{<:AbstractVector}) = f

# Category of finite ordinals
#############################

@instance Category(FinOrd, FinOrdFunction) begin
  dom(f::FinOrdFunction) = FinOrd(f.dom)
  codom(f::FinOrdFunction) = FinOrd(f.codom)

  id(A::FinOrd) = FinOrdFunction(id, A.n, A.n)

  function compose(f::FinOrdFunction, g::FinOrdFunction)
    @assert f.codom == g.dom
    FinOrdFunction(compose_functions(f.func, g.func), f.dom, g.codom)
  end
end

compose_functions(f,g) = as_function(g) ∘ as_function(f)
compose_functions(::typeof(id), g) = g
compose_functions(f, ::typeof(id)) = f
compose_functions(::typeof(id), ::typeof(id)) = id
compose_functions(f::AbstractVector, g::AbstractVector) = g[f]

as_function(f) = f
as_function(f::AbstractVector) = x -> f[x]

# Limits
########

terminal(::Type{FinOrd}) = FinOrd(1)
terminal(::Type{FinOrd{T}}) where T = FinOrd(one(T))

function product(A::FinOrd, B::FinOrd)
  m, n = A.n, B.n
  indices = CartesianIndices((m, n))
  π1 = FinOrdFunction(i -> indices[i][1], m*n, m)
  π2 = FinOrdFunction(i -> indices[i][2], m*n, n)
  Span(π1, π2)
end

function equalizer(f::FinOrdFunction, g::FinOrdFunction)
  @assert dom(f) == dom(g) && codom(f) == codom(g)
  m = f.dom
  FinOrdFunction(filter(i -> f(i) == g(i), 1:m), m)
end

""" Pullback of cospan of functions between finite ordinals.

TODO: This logic is completely generic. Make it independent of FinOrd.
"""
function pullback(cospan::Cospan{<:FinOrdFunction,<:FinOrdFunction})
  f, g = left(cospan), right(cospan)
  prod = product(dom(f), dom(g))
  π1, π2 = left(prod), right(prod)
  eq = equalizer(π1⋅f, π2⋅g)
  Span(eq⋅π1, eq⋅π2)
end

# Colimits
##########

initial(::Type{FinOrd}) = FinOrd(0)
initial(::Type{FinOrd{T}}) where T = FinOrd(zero(T))

function coproduct(A::FinOrd, B::FinOrd)
  m, n = A.n, B.n
  ι1 = FinOrdFunction(1:m, m, m+n)
  ι2 = FinOrdFunction(m+1:m+n, n, m+n)
  Cospan(ι1, ι2)
end

function coequalizer(f::FinOrdFunction, g::FinOrdFunction)
  @assert dom(f) == dom(g) && codom(f) == codom(g)
  m, n = f.dom, f.codom
  sets = IntDisjointSets(n)
  for i in 1:m
    union!(sets, f(i), g(i))
  end
  h = [ find_root(sets, i) for i in 1:n ]
  roots = unique!(sort(h))
  FinOrdFunction([ searchsortedfirst(roots, r) for r in h], length(roots))
end

""" Pushout of span of functions between finite ordinals.

TODO: This logic is completely generic. Make it independent of FinOrd.
"""
function pushout(span::Span{<:FinOrdFunction,<:FinOrdFunction})
  f, g = left(span), right(span)
  coprod = coproduct(codom(f), codom(g))
  ι1, ι2 = left(coprod), right(coprod)
  coeq = coequalizer(f⋅ι1, g⋅ι2)
  Cospan(ι1⋅coeq, ι2⋅coeq)
end

end


using Distributions
using ForwardDiff
using DiffableDistributions
using Base.Test

function f1(x)
    d = Normal(x[1], x[2])
    mean(d) / std(d)
end
function f2(x)
    d = Normal(x[1], 3.5)
    mean(d) / std(d)
end
function f3(x)
    d = Normal(1.1, x[1])
    mean(d) / std(d)
end

grad1(x) = [1/x[2], -x[1]/x[2]^2]
grad2(x) = [1/3.5]
grad3(x) = [-1.1/x^2]

agrad1 = ForwardDiff.gradient(f1)
agrad2 = ForwardDiff.gradient(f2)
agrad3 = ForwardDiff.gradient(f3)

pts = Vector{Float64}[
    [1., 2.],
    [1.15, 0.75],
    [0.5, 3.75]
]
for p in pts
    @assert grad1(p) ≈ agrad1(p)
    @assert grad2(p[1]) ≈ agrad2([p[1]])
    @assert grad3(p[2]) ≈ agrad3([p[2]])
end


# Test file for 5-d Chebyshev polynomials

using ChebyshevApprox

n1 = 10
n2 = 15
n3 = 7
n4 = 16
n5 = 31

dom_1 = [2.0, -3.0]
dom_2 = [1.5,  0.5]
dom_3 = [0.5, -0.5]
dom_4 = [-0.66, -1.79]
dom_5 = [1.0, -1.0]
dom = [dom_1 dom_2 dom_3 dom_4 dom_5]

nodesr_1  = chebyshev_nodes(n1,dom_1)
nodesr_2  = chebyshev_nodes(n2,dom_2)
nodesr_3  = chebyshev_nodes(n3,dom_3)
nodesr_4  = chebyshev_nodes(n4,dom_4)
nodesr_5  = chebyshev_nodes(n5,dom_5)
nodese_1  = chebyshev_extrema(n1,dom_1)
nodese_2  = chebyshev_extrema(n2,dom_2)
nodese_3  = chebyshev_extrema(n3,dom_3)
nodese_4  = chebyshev_extrema(n4,dom_4)
nodese_5  = chebyshev_extrema(n5,dom_5)
nodesex_1 = chebyshev_extended(n1,dom_1)
nodesex_2 = chebyshev_extended(n2,dom_2)
nodesex_3 = chebyshev_extended(n3,dom_3)
nodesex_4 = chebyshev_extended(n4,dom_4)
nodesex_5 = chebyshev_extended(n5,dom_5)
nodesv_1  = vertesi_nodes(n1,dom_1)
nodesv_2  = vertesi_nodes(n2,dom_2)
nodesv_3  = vertesi_nodes(n3,dom_3)
nodesv_4  = vertesi_nodes(n4,dom_4)
nodesv_5  = vertesi_nodes(n5,dom_5)

yr  = zeros(n1,n2,n3,n4,n5)
ye  = zeros(n1,n2,n3,n4,n5)
yex = zeros(n1,n2,n3,n4,n5)
yv  = zeros(n1,n2,n3,n4,n5)

for i = 1:n1
  for j = 1:n2
    for k = 1:n3
      for l = 1:n4
        for m = 1:n5

          yr[i,j,k,l,m]  = (nodesr_1[i]+4.0)^0.5+nodesr_1[i]*sqrt(nodesr_2[j])+exp(nodesr_3[k])*nodesr_4[l]-(1.0+nodesr_5[m])^2
          ye[i,j,k,l,m]  = (nodese_1[i]+4.0)^0.5+nodese_1[i]*sqrt(nodese_2[j])+exp(nodese_3[k])*nodese_4[l]-(1.0+nodese_5[m])^2
          yex[i,j,k,l,m] = (nodesex_1[i]+4.0)^0.5+nodesex_1[i]*sqrt(nodesex_2[j])+exp(nodesex_3[k])*nodesex_4[l]-(1.0+nodesex_5[m])^2
          yv[i,j,k,l,m]  = (nodesv_1[i]+4.0)^0.5+nodesv_1[i]*sqrt(nodesv_2[j])+exp(nodesv_3[k])*nodesv_4[l]-(1.0+nodesv_5[m])^2

        end
      end
    end
  end
end

order_tensor = [6, 6, 6, 6, 6]
order_complete = 6

wr_tensor    = chebyshev_weights_threaded(yr,[nodesr_1,nodesr_2,nodesr_3,nodesr_4,nodesr_5],order_tensor,dom)
wr_complete  = chebyshev_weights_threaded(yr,(nodesr_1,nodesr_2,nodesr_3,nodesr_4,nodesr_5),order_complete,dom)
we_tensor    = chebyshev_weights_extrema_threaded(ye,(nodese_1,nodese_2,nodese_3,nodese_4,nodese_5),order_tensor,dom)
we_complete  = chebyshev_weights_extrema_threaded(ye,(nodese_1,nodese_2,nodese_3,nodese_4,nodese_5),order_complete,dom)
wex_tensor   = chebyshev_weights_extended_threaded(yex,(nodesex_1,nodesex_2,nodesex_3,nodesex_4,nodesex_5),order_tensor,dom)
wex_complete = chebyshev_weights_extended_threaded(yex,(nodesex_1,nodesex_2,nodesex_3,nodesex_4,nodesex_5),order_complete,dom)
wv_tensor    = chebyshev_weights_vertesi_threaded(yv,(nodesv_1,nodesv_2,nodesv_3,nodesv_4,nodesv_5),order_tensor,dom)
wv_complete  = chebyshev_weights_vertesi_threaded(yv,(nodesv_1,nodesv_2,nodesv_3,nodesv_4,nodesv_5),order_complete,dom)

point = [1.748, 0.753, 0.119, -0.947, -0.23]

yr_tensor    = chebyshev_evaluate(wr_tensor,point,order_tensor,dom)
yr_complete  = chebyshev_evaluate(wr_complete,point,order_complete,dom)
ye_tensor    = chebyshev_evaluate(we_tensor,point,order_tensor,dom)
ye_complete  = chebyshev_evaluate(we_complete,point,order_complete,dom)
yex_tensor   = chebyshev_evaluate(wex_tensor,point,order_tensor,dom)
yex_complete = chebyshev_evaluate(wex_complete,point,order_complete,dom)
yv_tensor    = chebyshev_evaluate(wv_tensor,point,order_tensor,dom)
yv_complete  = chebyshev_evaluate(wv_complete,point,order_complete,dom)

y_actual = (point[1]+4.0)^0.5+point[1]*sqrt(point[2])+exp(point[3])*point[4]-(1.0+point[5])^2

println([y_actual yr_tensor yr_complete ye_tensor ye_complete yex_tensor yex_complete yv_tensor yv_complete])

yr_grad_tensor       = chebyshev_gradient(wr_tensor,point,order_tensor,dom)
yr_grad_complete     = chebyshev_gradient(wr_complete,point,order_complete,dom)
yr_deriv_tensor_1    = chebyshev_derivative(wr_tensor,point,1,order_tensor,dom)
yr_deriv_complete_1  = chebyshev_derivative(wr_tensor,point,1,order_complete,dom)
ye_grad_tensor       = chebyshev_gradient(we_tensor,point,order_tensor,dom)
ye_grad_complete     = chebyshev_gradient(we_complete,point,order_complete,dom)
ye_deriv_tensor_1    = chebyshev_derivative(we_tensor,point,1,order_tensor,dom)
ye_deriv_complete_1  = chebyshev_derivative(we_complete,point,1,order_complete,dom)
yex_grad_tensor      = chebyshev_gradient(wex_tensor,point,order_tensor,dom)
yex_grad_complete    = chebyshev_gradient(wex_complete,point,order_complete,dom)
yex_deriv_tensor_1   = chebyshev_derivative(wex_tensor,point,1,order_tensor,dom)
yex_deriv_complete_1 = chebyshev_derivative(wex_complete,point,1,order_complete,dom)
yv_grad_tensor       = chebyshev_gradient(wv_tensor,point,order_tensor,dom)
yv_grad_complete     = chebyshev_gradient(wv_complete,point,order_complete,dom)
yv_deriv_tensor_1    = chebyshev_derivative(wv_tensor,point,1,order_tensor,dom)
yv_deriv_complete_1  = chebyshev_derivative(wv_complete,point,1,order_complete,dom)

println(yr_grad_tensor, yr_grad_complete, yr_deriv_tensor_1, yr_deriv_complete_1)
println(ye_grad_tensor, ye_grad_complete, ye_deriv_tensor_1, ye_deriv_complete_1)
println(yex_grad_tensor, yex_grad_complete, yex_deriv_tensor_1, yex_deriv_complete_1)
println(yv_grad_tensor, yv_grad_complete, yv_deriv_tensor_1, yv_deriv_complete_1)

# Test the structures

chebpoly = ChebPoly(wr_complete,order_complete,dom)
chebyshev_evaluate(chebpoly,point)

chebr  = ChebInterpRoots(yr,(nodesr_1,nodesr_2,nodesr_3,nodesr_4,nodesr_5),order_complete,dom)
chebe  = ChebInterpExtrema(ye,(nodese_1,nodese_2,nodese_3,nodese_4,nodese_5),order_complete,dom)
chebex = ChebInterpExtended(yex,(nodesex_1,nodesex_2,nodesex_3,nodesex_4,nodesex_5),order_complete,dom)
chebv  = ChebInterpVertesi(yv,(nodesv_1,nodesv_2,nodesv_3,nodesv_4,nodesv_5),order_complete,dom)
cheb_interp(chebr,point)
cheb_interp(chebe,point)
cheb_interp(chebex,point)
cheb_interp(chebv,point)

chebr_fn  = cheb_interp(chebr)
chebe_fn  = cheb_interp(chebe)
chebex_fn = cheb_interp(chebex)
chebv_fn  = cheb_interp(chebv)

chebr_fn(point)
chebe_fn(point)
chebex_fn(point)
chebv_fn(point)