# This file is a part of SimilaritySearch.jl

export JaccardDistance, DiceDistance, IntersectionDissimilarity, CosineDistanceSet
import Distances: evaluate
using Base.Order

"""
    JaccardDistance()

The Jaccard distance is defined as

```math
J(u, v) = \\frac{|u \\cap v|}{|u \\cup v|}
```
"""
struct JaccardDistance <: SemiMetric end

"""
    DiceDistance()

The Dice distance is defined as

```math
D(u, v) = \\frac{2 |u \\cap v|}{|u| + |v|}
```
"""
struct DiceDistance <: SemiMetric end

"""
    IntersectionDissimilarity()

The intersection dissimilarity uses the size of the intersection as a mesuare of similarity as follows:

```math
I(u, v) = 1 - \\frac{|u \\cap v|}{\\max \\{|u|, |v|\\}}
```
"""
struct IntersectionDissimilarity <: SemiMetric end

"""
    CosineDistanceSet()

The cosine distance for very sparse binary vectors represented as
sorted lists of positive integers where ones occur.
"""
struct CosineDistanceSet <: SemiMetric end

"""
    intersectionsize(a, b, o=Forward)

Computes the size the intersections of `a` and `b`, specified as ordered sequences.
"""
function intersectionsize(a, b, o=Forward)
    len_a::Int = length(a)
    len_b::Int = length(b)
    ia::Int = ib::Int = 1
    intersection_size::Int = 0
    @inbounds while ia <= len_a && ib <= len_b
        if lt(o, a[ia], b[ib])
            ia += 1
        elseif lt(o, b[ib], a[ia])
            ib += 1
        else
            ia += 1
            ib += 1
            intersection_size += 1
        end
    end

    intersection_size
end

"""
    unionsize(a, b, isize)

Computes the size of the union of `a` and `b` that have an intersection size `isize`
"""
function unionsize(a, b, isize)
    length(a) + length(b) - isize
end

"""
    evaluate(::JaccardDistance, a, b)

Computes the Jaccard's distance of `a` and `b` both sets specified as
sorted vectors.
"""
function evaluate(::JaccardDistance, a, b)
    isize = intersectionsize(a, b)
    1.0 - isize / unionsize(a, b, isize)
end

"""
    evaluate(::DiceDistance, a, b)

Computes the Dice's distance of `a` and `b` both sets specified as
sorted vectors.
"""
function evaluate(::DiceDistance, a, b)
    i = intersectionsize(a, b)
    1.0 - 2 * i / (length(a) + length(b))
end

"""
    evaluate(::IntersectionDissimilarity, a, b)

Uses the intersection as a distance function (non-metric)
"""
function evaluate(::IntersectionDissimilarity, a, b)
    i = intersectionsize(a, b)
    return 1.0 - i / max(length(a), length(b))
end

"""
    evaluate(::CosineDistanceSet, a, b)

Computes the cosine distance where `a` and `b` are sorted lists of integers (emulating binary sparse vectores)
"""
function evaluate(::CosineDistanceSet, U, V)
    1 - intersectionsize(U, V) / (sqrt(length(U)) * sqrt(length(V)))
end


#### Cauchy matrix

export Cauchy

struct Cauchy{T} <: AbstractMatrix{T}
    x::Vector{T} #
    y::Vector{T} #
end # immutable

function Cauchy(k::Number)
         Cauchy(collect(1:k),collect(1:k))
end

function Cauchy(x::Vector)
         Cauchy(x,x)
end

# Define its size

size(A::Cauchy, dim::Integer) = length(A.x)
size(A::Cauchy)= size(A,1), size(A,1)

# Index into a Cauchy
function getindex(A::Cauchy,i::Integer,j::Integer)
    return 1.0/(A.x[i]+A.y[j])
end # getindex

# Dense version of Cauchy
Matrix(A::Cauchy) = [A[i,j] for i=1:size(A,1), j=1:size(A,2)]


using Distributions

function betarnd(a,b,n,m)
	d = Beta(a,b)
	r = rand(d,int(n*m))
	reshape(r, n,m)
end

function randVonMisesFisher(m, n, kappa, mu = None)
	#% randVonMISESFISHERM Random number generation from von Mises Fisher
	#% distribution.
	#% X = randVonMisesFisherm(m, n, kappa) returns n samples of random unit
	#% directions in m dimensional space, with concentration parameter kappa,
	#% and the direction parameter mu = e_m
	#% X = randVonMisesFisherm(m, n, kappa, mu) with direction parameter mu
	#% (m-dimensional column unit vector)
	#%
	#% Sungkyu Jung, Feb 3, 2010.
	#
	# converted into Julia, Daniel Perry, April 2015,
	#
	# see http://www.stat.pitt.edu/sungkyu/MiscPage.html
	#     http://www.stat.pitt.edu/sungkyu/software/randVonMisesFisherm.zip
	#     http://www.stat.pitt.edu/sungkyu/software/randVonMisesFisher3.pdf

	if mu == None
		muflag = false;
	else
		muflag = true;
	end

	if m < 2;
			println("Message from randVonMisesFisherm.m: dimension m must be > 2")
			println("Message from randVonMisesFisherm.m: Set m to be 2")
			m = 2;
	end

	if kappa < 0;
			println("Message from randVonMisesFisherm.m: kappa must be >= 0");
			println("Message from randVonMisesFisherm.m: Set kappa to be 0");
			kappa = 0;
	end

	#%
	#% the following algorithm is following the modified Ulrich's algorithm
	#% discussed by Andrew T.A. Wood in "SIMULATION OF THE VON MISES FISHER
	#% DISTRIBUTION", COMMUN. STATIST 23(1), 1994.

	#% step 0 : initialize
	b = (-2*kappa + sqrt(4*kappa^2 + (m-1)^2))/(m-1);
	x0 = (1-b)/(1+b);
	c = kappa*x0 + (m-1)*log(1-x0^2);

	#% step 1 & step 2
	nnow = n;
	w = [];
	#%cnt = 0;
	while true
			ntrial = int(max(round(nnow*1.2),nnow+10))
			Z = betarnd((m-1)/2,(m-1)/2,ntrial,1);
			U = rand(ntrial,1);
			W = (1-(1+b)*Z)./(1-(1-b)*Z);

			indicator = (kappa*W + (m-1)*log(1-x0*W) - c) .>= log(U)
			if sum(indicator) >= nnow
					w1 = W[find(indicator)]
					w = [w ;w1[1:nnow]];
					break;
			else
					w = [w ; W[find(indicator)]];
					nnow = nnow-sum(indicator);
					#%cnt = cnt+1;disp(['retrial' num2str(cnt) '.' num2str(sum(indicator))]);
			end
	end

	#% step 3
	V = UNIFORMdirections(m-1,n);
	X = [repmat(sqrt(1-w'.^2),m-1,1).*V ;w'];

	if muflag
			mu = mu / norm(mu);
			X = rotMat(mu)'*X;
	end
	return X
end

function UNIFORMdirections(m,n)
	#% generate n uniformly distributed m dim'l random directions
	#% Using the logic: "directions of Normal distribution are uniform on sphere"

	V = zeros(m,n);
	nr = randn(m,n); #%Normal random
	for i=1:n
			while true
					ni=(nr[:,i]'*nr[:,i])[1]; #% length of ith vector
					#% exclude too small values to avoid numerical discretization
					if ni < 1e-10
							#% so repeat random generation
							nr[:,i]=randn(m,1);
					else
							 V[:,i]=nr[:,i]/sqrt(ni);
							break;
					end
			end
	end

	return V
end

function rotMat(b, a = None, alpha = None)
	#% ROTMAT returns a rotation matrix that rotates unit vector b to a
	#%
	#%   rot = rotMat(b) returns a d x d rotation matrix that rotate
	#%   unit vector b to the north pole (0,0,...,0,1)
	#%
	#%   rot = rotMat(b,a ) returns a d x d rotation matrix that rotate
	#%   unit vector b to a
	#%
	#%   rot = rotMat(b,a,alpha) returns a d x d rotation matrix that rotate
	#%   unit vector b towards a by alpha (in radian)
	#%
	#%    See also .
	#
	#% Last updated Nov 7, 2009
	#% Sungkyu Jung

	s1 = size(b,1)
	s2 = size(b,2)
	d = max(s1,s2);
	b= b/norm(b);

	if s1<=s2
		b = b'
	end

	if a == None
			a = [zeros(d-1,1); 1];
	end
	if alpha == None
			alpha = acos((a'*b)[1]);
	end

	if abs((a'*b)[1] - 1) < 1e-15
		rot = eye(d)
		return rot
	end
	if abs((a'*b)[1] + 1) < 1e-15
		rot = -eye(d)
		return rot
	end

	c = b - a * (a'*b); c = c / norm(c);
	A = a*c' - c*a' ;

	rot = eye(d) + sin(alpha)*A + (cos(alpha) - 1)*(a*a' +c*c');
end


# ---
# title: 382. Linked List Random Node
# id: problem382
# author: zhwang
# date: 2022-02-15
# difficulty: Medium
# categories: Reservoir Sampling
# link: <https://leetcode.com/problems/linked-list-random-node/description/>
# hidden: true
# ---
#
# Given a singly linked list, return a random node's value from the linked list.
# Each node must have the **same probability** of being chosen.
#
# **Follow up:**
# What if the linked list is extremely large and its length is unknown to you?
# Could you solve this efficiently without using extra space?
#
# **Example:**
#
#
#
#     // Init a singly linked list [1,2,3].
#     ListNode head = new ListNode(1);
#     head.next = new ListNode(2);
#     head.next.next = new ListNode(3);
#     Solution solution = new Solution(head);
#
#     // getRandom() should return either 1, 2, or 3 randomly. Each element should have equal probability of returning.
#     solution.getRandom();
#
#
#
## @lc code=start
using LeetCode

function get_random(head::ListNode)::Int
    node, i, res = head, 1, 0
    while !isnothing(node)
        if rand(1:i) == 1  ## 1/i prob for value change
            res = node.val
        end
        node = node.next
        i += 1
    end
    return res
end
## @lc code=end


#!/usr/bin/julia

function base10ToAny(in_dig,base)
    if base >= 21
        println("Warning: There're not special characters for digits in base 21 or more")
    end
    digits_b10=[]
    q=in_dig
    while true
        if q % base <= 9
            push!(digits_b10,q % base)
        elseif q % base == 10
            push!(digits_b10,'A')
        elseif q % base == 11
            push!(digits_b10,'B')
        elseif q % base == 12
            push!(digits_b10,'C')
        elseif q % base == 13
            push!(digits_b10,'D')
        elseif q % base == 14
            push!(digits_b10,'E')
        elseif q % base == 15
            push!(digits_b10,'F')
        elseif q % base == 16
            push!(digits_b10,'G')
        elseif q % base == 17
            push!(digits_b10,'H')
        elseif q % base == 18
            push!(digits_b10,'I')
        elseif q % base == 19
            push!(digits_b10,'J')
        else
            r=(q % base)
            push!(digits_b10,"($r)")
        end
        q=div(q,base)
        if q == 0
            break
        end
    end
    l=length(digits_b10)
    to_ret=[]
    for idx in 1:l
        push!(to_ret,pop!(digits_b10))
    end
    if base <= 10
        return parse(Int,join(to_ret))
    else
        return join(to_ret)
    end
end

function anyBaseTo10(iterDig,base)
    if base >= 21
        println("Error: Only works for a base 20 or less")
        return
    end
    if !isa(iterDig,AbstractString)
        println("Error: Input n should be a string")
        return
    end
    summation=0
    l=sizeof(iterDig)
    for idx in 1:l
        digit = iterDig[ l - (idx-1)]
        if digit == 'A'
            digit = 10
        elseif digit == 'B'
            digit = 11
        elseif digit == 'C'
            digit = 12
        elseif digit == 'D'
            digit = 13
        elseif digit == 'E'
            digit = 14
        elseif digit == 'F'
            digit = 15
        elseif digit == 'G'
            digit = 16
        elseif digit == 'H'
            digit = 17
        elseif digit == 'I'
            digit = 18
        elseif digit == 'J'
            digit = 19
        else
            digit = parse(Int64, digit)
        end
        summation = digit*(base^(idx-1)) + summation
    end
    return summation
end

function binAddition(in_bin_1,in_bin_2)
    in_bin_1=base10ToAny(in_bin_1,2)
    in_bin_2=base10ToAny(in_bin_2,2)
    ib1=string(in_bin_1)
    ib2=string(in_bin_2)
    l1=sizeof(ib1)
    l2=sizeof(ib2)
    if l1 > l2
        AddZeros = l1 - l2
        ib2=("0" ^ AddZeros) * ib2
        larger=l1
    elseif l2 > l1
        AddZeros = l2 - l1
        ib1=("0" ^ AddZeros) * ib1
        larger=l2
    else
        larger=l1
    end
    c=0
    all_s=[]
    for idx in 1:larger
        dig1= parse(Int64, ib1[ larger - (idx-1) ])
        dig2= parse(Int64, ib2[ larger - (idx-1) ])
        d=div( dig1 + dig2 + c, 2)
        s=dig1 + dig2 + c - 2d
        c=d
        push!(all_s,s)
    end
    push!(all_s,c)
    justZero = true
    catResult=[]
    for idx in 1:length(all_s)
        if length(all_s) == 2 && join(all_s) == "00"
            return 0
        end
        if justZero && all_s[ length(all_s) - (idx -1 ) ] == 0
            continue
        end
        if all_s[ length(all_s) - (idx -1 ) ]  == 1
            justZero = false
        end
        push!(catResult,all_s[ length(all_s) - (idx -1 ) ])
    end
    return anyBaseTo10(join(catResult),2)
end

function binMultiplication(a,b)
    a=base10ToAny(a,2)
    b=base10ToAny(b,2)
    a=string(a)
    b=string(b)
    la=sizeof(a)
    lb=sizeof(b)
    if la > lb
        AddZeros = la - lb
        b=("0" ^ AddZeros) * b
        larger=la
    elseif lb > la
        AddZeros = lb - la
        a=("0" ^ AddZeros) * a
        larger=lb
    else
        larger=la
    end
    c_partials=[]
    for idx in 1:larger
        if b[larger - (idx -1)] == '1'
            c = a * ("0" ^ (idx - 1))
            push!(c_partials,c)
        else
            c = "0"
            push!(c_partials,c)
        end
    end
    p = 0
    for partial in c_partials
        partial = parse(Int64, partial)
        p = binAddition(p,anyBaseTo10(string(partial),2))
    end
    return p
end

# Division Algorithm
# a = d(q) + r
function intDivision(a::Int,d::Int)
    q = 0
    r = abs(a)
    while r >= d
        r = r - d
        q = q + 1
    end
    if a < 0 && r > 0
        r = d - r
        q = -(q + 1)
    end
    return [q,r]
end

# Modular Exponentiation
# x = b ^ n mod m
function modExp(b::Int, n::Int, m::Int)
    n=base10ToAny(n,2) #convert to binary
    x=1
    power=mod(b,m)
    ns=string(n)
    for idx in 1:sizeof(ns)
        if ns[sizeof(ns) - (idx-1)] == '1'
            x = mod(x * power,m)
        end
        power = mod((power * power),m)
    end
    return x
end

install.packages("plotly")
# The Euclidean Algorithm
function gcd(a::Int,b::Int)
    x=a
    y=b
    while y != 0
        r = mod(x,y)
        x = y
        y = r
    end
    return x
end

function sieveEratosthenes(n::Int) # accepts one integer argument
    isprime = ones(Bool, n) # n-element vector of true-s
    isprime[1] = false
    # 1 is not a prime
    for i in 2:round(Int, sqrt(n)) # loop integers from 2 to sqrt(n)
        if isprime[i]
            # conditional evaluation
            for j in (i*i):i:n # sequence with step i
            isprime[j] = false
            end
        end
    end
    return filter(x -> isprime[x], 1:n) # filter using anonymous function
end


# # Implementation of the real cubic root finding method described in:
# # 92.34 The cubic equation – a new look at the irreducible case
# # Author(s): I. J. ZUCKER
# # Source: The Mathematical Gazette , July 2008, Vol. 92, No. 524 (July 2008), pp. 264-268
# # Published by: The Mathematical Association of America
# # Stable URL: https://www.jstor.org/stable/27821778

"""
Return all the real roots of a cubic equation represented as a
vector of real coefficients for terms of increasing degree.
Note that any higher order coefficients (degree >= 4) are ignored.

Implementation of the real cubic root finding method of Zucker (2008).
"""
function solve_real_cubic_roots(polycoeff::Vector{T};
    warn_scaling::Bool=true,
    leading_tol::Float64=CUBIC_ATOL,
    coeff_tol::Float64=1.0/CUBIC_ATOL) where {T<:Real}

    Rts = Vector{Float64}() # object to return with real roots inside.

    if length(polycoeff) < 4
        @error "There are too few coefficients given."
        return Rts
    end

    if warn_scaling && isapprox(polycoeff[4], zero(T), atol=leading_tol)
        @warn "The leading cubic coefficient is approximately zero. Returning empty array."
        ## TODO?: fall back to solving a quadratic instead
        return Rts
    end

    (e, d, c) = polycoeff[1:3]./polycoeff[4]

    if warn_scaling && poor_conditioning((e,d,c), coeff_tol)
        @warn "Large coefficients in scaled polynomial. Poor conditioning may occur."
    end

    # We reduce f(x) = e + d*x + c*x^2 + x^3 via x == t - c/3 to
    # the standard transformed form f'(t) = 2q + 3p*t + t^3 :
    p = d/3 - c^2/9
    q = (c^3/27) - (c*d/6) + e/2
    Disc = q^2 + p^3  # cubic discriminant

    if Disc >= 0  # one real root: encompasses the case where p >= 0.
        u = cbrt(-q + sqrt(Disc))
        v = cbrt(-q - sqrt(Disc))

        r1 = u + v            # transformed real cubic root
        push!(Rts, r1 - c/3)  # original cubic root
    elseif Disc < 0  # three real roots : we necessarily have p < 0, hence 0 < s < 1
        h = q/p
        s = Disc/p^3
        w = sqrt(s)

        r_hyp = [
                   2*h*HgF(1//3, 2//3, 1//2, s),
             (-2//3)*h*HgF(2//3, 4//3, 3//2, 1//2*(1 + w)),
             (-2//3)*h*HgF(2//3, 4//3, 3//2, 1//2*(1 - w)),
        ]

        append!(Rts, r_hyp .- c/3)
    else
        error("""
        The computed discriminant is not comparable to 0;
        it may have a value of NaN or Inf due to numerical overflow.
        """)
    end

    return Rts
end

function solve_all_cubic_roots(polycoeff::Vector{T};
    warn_scaling::Bool=true,
    leading_tol::Float64=CUBIC_ATOL,
    coeff_tol::Float64=1.0/CUBIC_ATOL) where {T<:Real}

    Rts = Vector{Complex{Float64}}()

    real_roots = solve_real_cubic_roots(polycoeff; warn_scaling, leading_tol, coeff_tol)
    append!(Rts, real_roots)

    if length(real_roots) == 3
        return Rts
    elseif length(real_roots) == 1
        r = real_roots[1]
        c0, c1, c2 = polycoeff[1:3]./polycoeff[4]
        # If we know the one real root r of c0 + c1*z + c2*z^2 + z^3 = 0,
        # then we can solve a quadratic -c0/r + (c2 + r)*x + x^2 = 0 (or multiply through by r)
        # to obtain s1 and s2, the complex conjugate roots, since
        # c0 = -r*s1*s2 and c2 = -(r + s1 + s2),
        # and we obtain the sum and product of s1 and s2:
        complex_roots = solve_quadratic_roots([-c0, r*(r + c2), r]) # return type Vector{Complex{Float64}}
        Rts = append!(Rts, complex_roots)
        return Rts
    else
        error("Incorrect number of cubic roots returned: $(Rts).")
        return Rts
    end

end


using LightGraphs
using LinearAlgebra
using SparseArrays
using IterativeSolvers
using Plots
using GraphPlot
using ColorSchemes

function network_automorphism_classes(g::SimpleGraph{<:Integer}; tol=1e-4)
    r = lobpcg(float(adjacency_matrix((g))), true, 1, tol = 1e-10)
    x = copy(r.X[:,1]) # get the dominant eigenvector
    p = sortperm(x)
    x .= x[p]
    k = 1
    classes = [[p[1]]]
    for i in 2:length(x)
        if abs(x[i]-x[i-1]) < tol
            push!(classes[k], p[i])
        else
            k += 1
            push!(classes, [p[i]])
        end
    end
    return classes, r.X[:,1]
end

A = [0 1 1 0 0 0 0 0;
     1 0 1 0 0 0 0 0;
     1 1 0 1 1 1 0 0;
     0 0 1 0 0 0 1 1;
     0 0 1 0 0 0 1 1;
     0 0 1 0 0 0 1 1;
     0 0 0 1 1 1 0 0;
     0 0 0 1 1 1 0 0
    ]

g = SimpleGraph(A)
gplot(g)

cs = network_automorphism_classes(g)

function pairpermutation_matrix(n,i,j)
    P = spzeros(Int64, n,n)
    for k in 1:n
        if k != i || k != j
            P[k,k] = 1
        end
    end
    P[i,j] = 1
    P[j,i] = 1
    return P
end

function checkclasses(g::SimpleGraph{<:Integer}, cs)
    A = adjacency_matrix(g)
    N = nv(g)
    for c in cs
        n = length(c)
        if n > 1
            for i in 2:n # NB incorrect test
                P = pairpermutation_matrix(N,c[1],c[i])
                if A != P*A*P
                    return false
                end
            end
        end
    end
    return true
end

checkclasses(g, cs)

g = graphfamous("karate")
cs, v = network_automorphism_classes(g)
classes = filter(c->length(c)>1, cs)
allclasses = vcat(classes...)
ps = [(k,v[k]) for k in allclasses]
colors = ColorSchemes.Set1_4.colors;
nodecols = fill(colorant"lightgray", nv(g));
for i in 1:length(classes), v in classes[i]
    nodecols[v] = colors[i];
end
gplot(g, nodelabel=1:34, nodefillc = nodecols)

A = float(adjacency_matrix(g))
λ, V = eigen(Array(A))
[V[allclasses,i] for i in 1:34 if any(abs.(V[:,i]) .< 1e-6) ]


# # Nonnegative over a variety

#md # [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/generated/Getting started/circle.ipynb)
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/generated/Getting started/circle.ipynb)

# The polynomial ``1 - y^2`` is nonnegative for all ``y`` in the unit circle.
# This can be verified using Sum-of-Squares.

using Test #src
using DynamicPolynomials
using SumOfSquares
@polyvar x y
S = @set x^2 + y^2 == 1

# We need to pick an SDP solver, see [here](https://jump.dev/JuMP.jl/v0.23.0/installation/#Supported-solvers) for a list of the available choices.
# The domain over which the nonnegativity of ``1 - y^2`` should be certified
# is specified through the `domain` keyword argument.

import CSDP
model = SOSModel(CSDP.Optimizer)
set_silent(model)
con_ref = @constraint(model, 1 - y^2 >= 0, domain = S)
optimize!(model)

# We can see that the model was feasible:

@test JuMP.termination_status(model) == MOI.OPTIMAL #src
@test JuMP.primal_status(model) == MOI.FEASIBLE_POINT #src
solution_summary(model)

# The certificate can be obtained as follows:

dec = sos_decomposition(con_ref, 1e-6) #src
@test length(dec) == 1 #src
@test first(dec) ≈ x rtol = 1e-6 #src
sos_decomposition(con_ref, 1e-6)

# It returns ``x^2`` which is a valid certificate as:
# $$ 1 - y^2 \equiv x^2 \pmod{x^2 + y^2 - 1} $$


using GaussQuadrature
using PyPlot

x = collect(linspace(0, 1, 201))
const n = 5

a, b = shifted_legendre_coefs(Float64, n)
p = orthonormal_poly(x, a, b)
grid(true)

figure(1)
plot(x, p)
const r = 0
α, β = logweight_coefs(Float64, n+1, r)
q = orthonormal_poly(x, α[1:n], β)
title("Orthonormal shifted Legendre polynomials")

figure(2)
plot(x, q)
grid(true)
s = "Orthonormal polymials with weight \$\\log x^{-1}\$"
title(latexstring(s))


#= Install the primes package: (if unavailable)
import Pkg; Pkg.add("Primes") =#
using Primes

# Public key: (e,n)
struct PublicKey
    n::Integer
    e::Integer
end

# Private key: (d,n)
struct PrivateKey
    n::Integer
    d::Integer
end

function keyGenerator(p::Integer, q::Integer, e::Integer)
    # Basic corner cases:
    isprime(p) || error("p must be a prime number")
    isprime(q) || error("q must be a prime number")
    e > 0 || error("e must be positive")
    # Computing Euler's totient:
    phi = (p - 1) * (q - 1)
    # Computing greatest common divisor and Bézout coefficients (i.e. u and v, such that u*e + u*phi = gcd(e,phi)):
    (g, u, v) = gcdx(e,phi)
    # If gcd(e,phi)!=1, they aren't coprime:
    g == 1 || error("phi(n) and e must be coprime")
    # Computing private exponent d based on (+ve)/(-ve) value of u through a ternary operator:
    u < 0 ? d = (u % phi) + phi : d = u % phi
    # Computing n as the product of the two entered primes and returning the computed public and private key components:
    n = p * q
    return PublicKey(n, e), PrivateKey(n, d)
end

# Encryption function:
function encrypt(m::Integer, k::PublicKey)
    0 <= m || error("m must be non-negative")
    m < k.n || error("m is too large")
    return powermod(m, k.e, k.n)
end

# Decryption function:
function decrypt(c::Integer, k::PrivateKey)
    0 <= c || error("c must be non-negative")
    c < k.n || error("c is too large")
    return powermod(c, k.d, k.n)
end

print("Enter a prime number: ")
p = parse(Int, readline())
print("Enter another prime number: ")
q = parse(Int, readline())
print("Enter public key exponent: ")
e = parse(Int, readline())
(publicKey, privateKey) = keyGenerator(p, q, e)
println("Public key (n,e) = ($(publicKey.n),$(publicKey.e)); (to be shared)")
println("Private key (n,d) = ($(privateKey.n),$(privateKey.d)); (to be kept with oneself)")
println("Enter an integer to encrypt:")
m = parse(Int, readline())
println("Original integer: $m")
c = encrypt(m, publicKey)
println("Encrypted integer: $c")
println("Decrypted integer: $(decrypt(c,privateKey))")


function lagrange_interpolant(R, xs, i)
    n = length(xs)
    x = AbstractAlgebra.gen(R)

    f = R(prod(
        j == i ? 1 : (x - xs[j])
        for j in 1:n
    ))

    c = prod(
        j == i ? 1 : (xs[i] - xs[j])
        for j in 1:n
    )

    AbstractAlgebra.map_coefficients(t -> t // c, f)
end

# univariate
# returns :: AbstractAlgebra.Generic.Poly
function interpolate_polynomial(R, xs, ys)
    f = sum(
        ys[i] * lagrange_interpolant(R, xs, i)
        for i in 1:length(xs)
    )
end

"""
    For given polynomial g and m computes the rational fraction r//t,
    s.t. r//t approximates g modulo m

        r == t*g (mod m)

    and
        deg(r) + deg(t) <= deg(m)

    Throws if ??? TODO
"""
function polynomial_reconstruction(g, m)
    # The function implements Algorithm 12.9 from
    #   "Modern Computer Algebra", second edition,
    #   Joachim von zur Gathen, Jürgen Gerhard

    n = AbstractAlgebra.degree(m)
    k = div(n, 2)
    FF = AbstractAlgebra.parent(g)

    U = (FF(1), FF(0), m)
    V = (FF(0), FF(1), g)
    while AbstractAlgebra.degree(V[3]) >= k
        q = div(U[3], V[3])
        T = U .- q .* V
        U = V
        V = T
    end

    r, t = V[3], V[2]

    if gcd(r, t) == FF(1) && AbstractAlgebra.degree(t) <= n - k
        return r // t
    end

    throw(DomainError(
        g, "reconstruction of $g (mod $m) does not exist"
    ))
end

"""
    Interpolates rational function in the given ring R with
    the given interpolation points xs and ys by
    interpolating as a polynomial first
    and reconstructing into a polynomial fraction after
"""
function interpolate_rational_function(R, xs, ys)
    # The function implements algorithm derived from Corollary 5.20 of
    #   "Modern Computer Algebra", second edition,
    #   Joachim von zur Gathen, Jürgen Gerhard

    x = AbstractAlgebra.gen(R)
    g = Nemo.interpolate(R, xs, ys)

    # TODO: this computation can be done faster
    m = prod(x - xs[i] for i in 1:length(xs))

    polynomial_reconstruction(g, m)
end

# TODO: why is it still here?
function interpolate_multivariate_rational_function(MR, R, xs, ys)

    f = interpolate_rational_function(R, xs, ys)

    f
end

"""
    Returns reversed vector of base-adic representation of n
"""
function decompose_by_degrees(n, base, len)
    powers = [base^(i-1) for i in 1:len]
    exps = zeros(Int, len)
    for i in 1:len
        exps[len - i + 1] = div(n, powers[len - i + 1])
        n = n - exps[len - i + 1] * powers[len - i + 1]
    end
    exps
end

# TODO : not used
function random_linear_shift(ground_ring, n)
    functors = []
    inverses = []

    for i in 1:n
        a, b = ground_ring(rand(1:10000)), ground_ring(rand(1:10000))
        f  = ( x -> x + b )
        fi = ( x -> x - b )

        push!(functors, f)
        push!(inverses, fi)
    end

    return functors, inverses
end

"""
    Given a callable functor f tries to find degrees of univariate polynomials
    A, B approximating f as a rational function f = A // B
"""
# TODO: not used
function predict_degrees(f)
    # staring from small amount of points..
    n = 8

    # = deg(A), deg(B) initially
    predicted_degrees = (n, n)

    polyring, = AbstractAlgebra.PolynomialRing(ground, "u")

    # "safety condition" to assume f is interpolated correctly indeed
    # failes on first iteration
    while 4*(sum(predicted_degrees) + 2) > n
        xs = [ rand(f) for _ in 1:n  ]
        ys = f.(xs)

        interpolated = interpolate_rational_function(polyring, xs, ys)

        predicted_degrees = map(degree, (numerator(interpolated), denominator(interpolated)))

        @debug "for $n points degrees are " predicted_degrees

        n = n * 2
    end

    predicted_degrees
end

"""
    Same as the function above, key difference is that here
    f returns an array of results on each evaluation
"""
# TODO: not used
function simultaneous_predict_degrees(f)
    # staring from small amount of points..
    n = 8

    polyring = base_ring(f)
    nfrom, nto = arity(f)

    predicted_degrees = [ (n, n) for _ in 1:nto ]
    already_interpolated = 0

    while already_interpolated < nto

        4*(sum(values(predicted_degrees)) + 2) > n
        xs = [ rand(f) for _ in 1:n  ]
        ys = f.(xs)

        for (j, deg) in enumerate(predicted_degrees)
            if 4*(sum(deg) + 2) < n
                continue
            end
            yparticular = [y[j] for y in ys]
            interpolated = interpolate_rational_function(polyring, xs, yparticular)

            predicted_degrees[j] = map(degree, (numerator(interpolated), denominator(interpolated)))

        end

        @debug "for $n points degrees are " predicted_degrees

        n = n * 2
    end

    predicted_degrees
end

"""

"""
# TODO: not used
function backward_kronecker(
                     f,
                     target_ring,
                     maxexp) where {T}

    polybuilder = MPolyBuildCtx(target_ring)
    nvariables = length(gens(target_ring))

    # TODO: fix 0:degree(f)
    for (e, c) in zip(0:degree(f), coefficients(f))
        if iszero(c)
            continue
        end
        push_term!(
            polybuilder,
            c,
            decompose_by_degrees(e, maxexp, nvariables)
        )
    end

    finish(polybuilder)
end

function decompose_by_good_degrees(e, prods, n)
    ans = zeros(Int, n)

    for i in 1:n
        ans[n - i + 1] = div(e, prods[n - i + 1])
        e = mod(e, prods[n - i + 1])
    end

    ans
end

function backward_good_kronecker(
                     f,
                     target_ring,
                     exps) where {T}

    prods = deepcopy(exps)
    prods[1] = 1
    for i in 2:length(prods)
        prods[i] = prods[i - 1] * (exps[i - 1] + 1)
    end

    polybuilder = MPolyBuildCtx(target_ring)
    nvariables = length(gens(target_ring))

    for (e, c) in zip(0:degree(f), coefficients(f))
        if iszero(c)
            continue
        end
        push_term!(
            polybuilder,
            c,
            decompose_by_good_degrees(e, prods, nvariables)
        )
    end

    ans = finish(polybuilder)
    ans
end

function backward_kronecker(
                  f::AbstractAlgebra.Generic.Frac{T},
                  target_ring,
                  maxexp) where {T}

    backward_kronecker(numerator(f), target_ring, maxexp) //
        backward_kronecker(denominator(f), target_ring, maxexp)
end

function backward_good_kronecker(
                  f::AbstractAlgebra.Generic.Frac{T},
                  target_ring,
                  maxexp) where {T}

    backward_good_kronecker(numerator(f), target_ring, maxexp) //
        backward_good_kronecker(denominator(f), target_ring, maxexp)
end

function generate_kronecker_points(ground, maxexp, nvariables)
    npoints = (maxexp^nvariables + 2) * 2
    xs = [rand(ground) for _ in 1:npoints]
    points = [
        [ ground(j)^i for i in [ (maxexp)^k for k in 0:(nvariables - 1) ] ]
          for j in xs
    ]
    xs, points
end

function generate_good_kronecker_points(ground, exps, nvariables)
    npoints = prod( map(x -> x + 1, exps) ) * 2

    prods = deepcopy(exps)
    # univariate case? =(
    prods[1] = 1
    for i in 2:length(prods)
        prods[i] = prods[i - 1] * (exps[i - 1] + 1)
    end

    xs = Set([rand(ground) for _ in 1:npoints])
    while length(xs) < npoints
        xs = union!(xs, Set([rand(ground) for _ in 1:(npoints-length(xs))]))
    end
    xs = collect(xs)

    points = [
        [ p^prods[k] for k in 1:nvariables ]
        for p in xs
    ]

    xs, points
end


function colNullities(P::AbstractArray, tol=0)
    A, B = toPencil(P)
    return colNullities(A, B, tol)
end

"""
    e, d, Ar, Br = colNullities(A, B, tol=0)
computes the column nullities of the pencil Bx-A.
e = column nullities
d =  nullities of infinite eigenvalue
x*Br - Ar = reduced pencil containing only finite eigenvalues and row indices
"""
function colNullities(A::AbstractArray{T, N}, B::AbstractArray{T, N}, tol=0) where {T<:Number, N}

    r = rank(B, atol=tol)
    s = size(B, 2) - r
    blocks  = [s]
    ranks = Int[]

    while s != 0
        F = svd(B, full=true);
        B = B*F.V
        A = A*F.V

        F = svd(A[:, r+1:end], full=true)
        Ua = F.U
        ra = rank(A[:, r+1:end], atol=tol)
        push!(ranks, ra)
        P = Matrix(I, size(Ua))
        P = P[[ra+1:end; 1:ra], :]
        A = P*Ua'*A
        B = P*Ua'*B

        A = A[1:end-ra, 1:r]
        B = B[1:end-ra, 1:r]
        r = rank(B, atol=tol)
        s = size(B, 2) - r
        push!(blocks, s)
    end
    e = blocks[1:end-1] - ranks
    d = ranks - blocks[2:end]

    removeTailZeroes!(e)
    removeTailZeroes!(d)
    iszero(d) && (d = Int[])
    iszero(e) && (e = Int[])
    return e, d, A, B
end

function rowNullities(P::AbstractArray, tol=0)
    A, B = toPencil(P)
    return rowNullities(A, B, tol)
end

"""
    e, d, Ar, Br = colNullities(A, B, tol=0)
computes the row nullities of the pencil Bx-A.
e = row nullities
d =  nullities of infinite eigenvalue
x*Br - Ar = reduced pencil containing only finite eigenvalues and column indices
"""
function rowNullities(A::AbstractArray{T, N}, B::AbstractArray{T, N}, tol=0) where {T<:Number, N}

    r = rank(B, atol=tol)
    s = size(B, 1) - r
    blocks = [s]
    ranks = Int[]
    while s != 0
        F = svd(B, full=true)
        Ub = F.U
        P = Matrix(I, size(Ub))
        P = P[[r+1:end; 1:r], :]
        B = P*Ub'*B
        A = P*Ub'*A
        F = svd(A[1:s, :], full=true)
        ra = rank(A[1:s, :], atol=tol)
        push!(ranks, ra)
        A = A*F.V
        B = B*F.V
        A = A[s+1:end, ra+1:end]
        B = B[s+1:end, ra+1:end]
        r = rank(B, atol=tol)
        s = size(B, 1) - r
        push!(blocks, s)
    end

    e = blocks[1:end-1] - ranks
    d = ranks - blocks[2:end]

    removeTailZeroes!(e)
    removeTailZeroes!(d)
    iszero(d) && (d = Int[])
    iszero(e) && (e = Int[])
    return e, d, A, B
end

function eigNullities(λ::Number, A::AbstractArray{T, N}, B::AbstractArray{T, N}, tol=0) where {T<:Number, N}
    A = A - λ*B
    r = rank(A, atol=tol)
    s = size(A, 2) - r

    blocks  = [s]

    while s != 0
        F = svd(A, full=true);
        B = B*F.V
        A = A*F.V

        F = svd(B[:, r+1:end], full=true)
        Ua = F.U

        P = Matrix(I, size(Ua))
        P = P[[s+1:end; 1:s], :]
        A = P*Ua'*A
        B = P*Ua'*B

        A = A[1:r, 1:r]
        B = B[1:r, 1:r]
        r = rank(A, atol=tol)
        s = size(A, 2) - r
        push!(blocks, s)
    end
    e = blocks[1:end-1] - blocks[2:end]

    return e
end

function eigNullities(λ::Number, P::AbstractArray, tol=0)
    _, _, Ar, Br = colNullities(P, tol)
    _, _, Ar, Br = rowNullities(Ar, Br, tol)
    return eigNullities(λ, Ar, Br, tol)
end


## B-splines, recursive definition
B0(u::Vector{Float64}, i::Integer) = x::Real -> (u[i] <= x < u[i+1]) ? float(1) : float(0) ## non-continuous
B(p::Integer,u::Vector{Float64},i::Integer) = (p==0) ? B0(u,i) : x -> float(((x-u[i])/(u[i+p]-u[i]))*B(p-1,u,i)(x) + ((u[i+p+1]-x)/(u[i+p+1]-u[i+1]))*B(p-1,u,i+1)(x))

totalNumNodes(numNodes::Integer, p::Integer) = numNodes + 2p ## adding p nodes on each side
numOfBasisElements(numNodes::Integer,p::Integer) = numNodes + p - 1 ## number of non-zero spline functions

## returns an index-function of basis-functions, which takes as input i in 1:k
## creates evenly-spaced nodes
function splineBasisFuns(p::Integer, range::(Real,Real), numNodes::Integer)
    intervalLen = (range[2]-range[1])/(numNodes-1)
    u = linspace(range[1] - p*intervalLen, range[2]+ p*intervalLen, totalNumNodes(numNodes,p))
    i -> B(p,u,i)
end

## B-splines with pre-specific nodes
## WARNING: all functions in this basis will be 0 at the leftmost and rightmost nodes, and the same is true of their first p-1 derivatives.
function splineBasisFuns(p::Integer, nodes::Vector{Float64})
    i::Integer -> B(p,nodes,i)
end

cubicSplineBasisFuns(range::(Real,Real), numNodes::Integer) = splineBasisFuns(3, range, numNodes)
quadraticSplineBasisFuns(range::(Real,Real), numNodes::Integer) = splineBasisFuns(2, range, numNodes)
linearSplineBasisFuns(range::(Real,Real), numNodes::Integer) = splineBasisFuns(1, range, numNodes)

## BFE "Basis Function Expansion"

## evenly-spaced nodes
function splineBFE(p::Integer, x::Vector{Float64}, range::(Real,Real), numNodes::Integer)
    funs = splineBasisFuns(p, range, numNodes)
    num = numOfBasisElements(numNodes,p)
    Float64[funs(i)(xj) for xj in x, i in 1:num]
end

## pre-specified nodes
function splineBFE(p::Integer, x::Vector{Float64}, nodes::Vector{Float64})
    funs = splineBasisFuns(p, nodes)
    n = length(nodes) - p - 1
    Float64[funs(i)(xj) for xj in x, i in 1:n]
end

cubicSplineBFE(x::Vector{Float64}, range::(Real,Real), numNodes::Integer) =
    splineBFE(3, x, range, numNodes)
quadraticSplineBFE(x::Vector{Float64}, range::(Real,Real), numNodes::Integer) =
    splineBFE(2, x, range, numNodes)
linearSplineBFE(x::Vector{Float64}, range::(Real,Real), numNodes::Integer) =
    splineBFE(1, x, range, numNodes)

cubicSplineBFE(x::Vector{Float64}, nodes::Vector{Float64}) = splineBFE(3, x, nodes)
quadraticSplineBFE(x::Vector{Float64}, nodes::Vector{Float64}) = splineBFE(2, x, nodes)
linearSplineBFE(x::Vector{Float64}, nodes::Vector{Float64}) = splineBFE(1, x, nodes)


using TensorToolbox
using LinearAlgebra
using Arpack
#using TensorDecompositions

"""
hyper parameters max_iter and tol are decieded by sklearn
"""
function lranmf_mu(X, r ; max_iter=200, tol = 1.0E-3, verbose = false)
    # lranmf_mu is effective when r << min(n,m)
    # input is a onnegative matrix
    # proposed by Guoxu Zhou in 2012
    # https://ieeexplore.ieee.org/document/6166354

    n, m = size(X)
    epsilon = 0.000001

    # See step1 in section II-B in the paper
    if r == min(n, m)
        svd_X = svd(X)
    else
        svd_X = svds(X; nsv=r, ritzvec=true)[1]
    end
    Achil = svd_X.U * diagm(svd_X.S)
    Bchil = svd_X.V

    # See step2 in section II-B in the paper
    A = rand(n, r)
    B = rand(m, r)
    cost_at_init = norm(X - A*B')
    previous_cost = cost_at_init
    for iter = 1:max_iter
        B .= B .* ( max.( Bchil*(Achil' * A), epsilon ) ) ./ ( B*(A'*A) )
        A .= A .* ( max.( Achil*(Bchil' * B), epsilon ) ) ./ ( A*(B'*B) )

        if tol > 0 && iter % 10 == 0
            cost = norm(X - A*B')
            if verbose
                println("iter: $iter cost: $cost")
            end
            if (previous_cost - cost) / cost_at_init < tol
                break
            end
            previous_cost = cost
        end
    end

    # A * B' is rank-r matrix
    return A, B'
end

function lraSNTD(Y, reqrank)
    # Sequential nonnegative Tucker based on lraNMF
    # input is a nonnegative tensor
    # proposed by Guoxu Zhou in 2012
    # https://ieeexplore.ieee.org/document/6166354

    N = ndims(Y)
    input_tensor_shape = size(Y)

    # See section IV-2) in the paper
    # get non-negative factors
    A = []
    for n=1:N
        # Yn is Matricization of tensor Y by mode n
        Yn = tenmat(Y, n)
        An, _ = lranmf_mu(Yn, reqrank[n])
        push!(A, An)
        # ttm(Y,An,n) is mode-n product of tensor X and matrix An
        Y = ttm(Y, pinv(An), n)
    end

    # reproduce
    Y = ttm(Y, A[1], 1)
    for n=2:N
        Y = ttm(Y, A[n], n)
    end

    return Y
end


using NumericalMethodsforEngineers

A = [10. 1. -5.; -20. 3. 20.; 5. 3. 5.]
b = [1., 2., 6.]
n = size(A, 1)
x = 0.0

# Convert to upper triang;e
for k in 1:n-1
  if abs(A[k, k]) > 1.0e-6
    for i in k+1:n
      x = A[i, k] / A[k, k]
      A[i, k] = 0.0
      for j in k+1:n
        A[i, j] -= A[k, j] * x
      end
      b[i] -= b[k] * x
    end
  else
    println("Zero pivot found in row $k")
  end
end

println("Modified Matrix")
A |> display
println()

for i in n:-1:1
  x = b[i]
  if i < n
    for j in i+1:n
      x -= A[i, j] * b[j]
    end
  end
  b[i] = x / A[i, i]
end

println("Solution vector:")
b |> display
d = copy(b)

# Restore A and b
A = [10. 1. -5.; -20. 3. 20.; 5. 3. 5.]
b = [1., 2., 6.]

c = A\b

println()
@test round(c, 14) == d
@test round(A * c, 14) == b


using ReverseDiffOverload

const v1 = randn(2)
const v2 = randn(2)
const v3 = randn(2)
const M  = randn(2, 2)

import ReverseDiffOverload.testdiff
testdiff(f::String, x) = begin
    println(f)
    testdiff(eval(parse("x -> $f")), x)
end
testdiff(f::String, x, y) = begin
    println(f)
    testdiff(eval(parse("(x, y) -> $f")), x, y)
end
#dot
testdiff("dot(v1, x)", v2)
testdiff("dot(x, y)", [1., 2.], [3., 4.])

#+
testdiff("x+y", 1., 2.)
testdiff("dot(v1, x+y)", v2, v3)
testdiff("dot(v1, x+y)", 1., v2)

#-
testdiff("x+(-y)", 1., 2.)
testdiff("x-y", 1., 2.)
testdiff("dot(v1, x-y)", v2, v3)
testdiff("dot(v1, x-y)", 1., v2)
testdiff("dot(v1, -x)", v2)

#*
testdiff("x*y", 3., 4.)
testdiff("dot(v1, x'*y)", M, v2)
testdiff("sum(x*y)", v1', M)
testdiff("dot(v1, x*y)", M, v2)
testdiff("dot(v1, x*y)", randn(), v2)

#/
testdiff("dot(v1, x\\y)", M, v2)
testdiff("dot(v1, vec(x/y))", v2', M)
testdiff("x+x*x*exp(x)", 2.)

#.^
testdiff("x .^ y", 2.5, 3.5)
testdiff("x .^ y", 2.5, -3.5)
testdiff("dot(v1, x .* y)", v2, v3)
testdiff("dot(v1, x .^ y)", [1., 2.], [3., 4.])

#'
testdiff("first(y'*x*y)", [1. 2;3 4], [5.,6]'')
#matrix funcs
testdiff("det(x)", [1. 2;3 4])
testdiff("trace(x)", [1. 2;3 4])
testdiff("det(inv(x))", [1. 2;3 4])
testdiff("sum(x)", [1., 2., 3.])
testdiff("dot([1.1, 2.1], vec(x))", [5. 6])

testdiff("dot(v1, exp(x))", v2)
testdiff("dot(v1, sin(x))", v2)
testdiff("dot(v1, cos(x))", v2)

testdiff("sum(exp(x.*v1))", v2)

testdiff("sum(x./y)", v2, v3)

testdiff("sum(rectlin(M*x))", v1)
testdiff("logΓ(x)", 3)
testdiff("logΓ(x)", 3)
testdiff("getindex(x, 2)", ones(3))
testdiff("first(v1'*sum(x, 2))", randn(2,2))
testdiff("first(sum(x, 1)*v1)", randn(2,2))


struct PolyharmonicSpline
    dim::Int64
    order::Int64
    coeff::Vector{Float64}
    centers::Array{Float64,2}
    error::Float64
end

function polyharmonicK(r,K)
    if iseven(K)
        iszero(r) && return zero(r)
        if r >= 1.0
            return (r^K)*log(r)
        elseif 0.0 < r < 1.0
            return (r.^(K-1))*log(r.^r)
        elseif iszero(r) # Needed for autodiff to work at zero
            return zero(r)
        end
    else
        return r^K
    end
end

function PolyharmonicSpline(K::Int64, centers::Array{Float64,2}, values::Array{Float64}; s = 0.0)
    m,n = size(centers)
    m != length(values) && throw(DimensionMismatch())

    M = zeros(m,m)
    N = zeros(m,n+1)

    for i=1:m
        N[i,1] = 1
        N[i,2:end] = centers[i,:]
        for j=1:m
            M[i,j] = polyharmonicK(norm(centers[i,:] .- centers[j,:]),K)
        end
    end
    M = M + s*I
    L = vcat(hcat(M,N),hcat(N', zeros(n+1,n+1)))

    w = pinv(L)*vcat(values,zeros(n+1))

    ivalues = zeros(m)
    for i=1:m
        tmp = 0.0
        for j=1:m
            tmp = tmp + w[j]*polyharmonicK(norm(centers[i,:] .- centers[j,:]),K)
        end
        tmp = tmp + w[m+1]
        for j=2:n+1
            tmp = tmp + w[m+j]*centers[i,j-1]
        end
        ivalues[i] = tmp
    end
    error = norm(values .- ivalues)

    return PolyharmonicSpline(n,K,w,centers,error)
end

function PolyharmonicSpline(K::Int64, centers::Vector{Float64},values::Vector{Float64};s = 0.0)
    PolyharmonicSpline(K,reshape(centers,length(centers),1),values,s=s)
end

function (S::PolyharmonicSpline)(x::T...) where T <: Real
    n = length(x)
    n != S.dim && throw(DimensionMismatch("$n != $(S.dim)"))

    v = 0.0
    l = length(S.coeff)-(n+1)
    for j=1:l
        v = v + S.coeff[j]*polyharmonicK(norm(x .- S.centers[j,:]), S.order)
    end

    v = v + S.coeff[l+1]
    for j=2:n+1
        v = v + S.coeff[l+j]*x[j-1]
    end

    return v
end


# [Martinsson & Tropp, Algorithm 7]
function rangefinder(A::AbstractMatrix{T}, r::Int; q::Int=0) where {T <: Number}
    m, n = size(A)
    if q == 0
        Ω = 1/sqrt(n) * randn(n, r)
        Y = A * Ω
    else
        Y = 1/sqrt(m) * randn(m, r)
        for i in 1:q
            Y = Array(qr(Y).Q)
            Y = A*A'*Y
        end
    end

    return Array(qr(Y).Q)
end


# sin (θ) cos (θ)

#Makie
using CairoMakie
let
    x = -2π:0.1:2π
    fig = Figure(resolution = (600, 400), backgroundcolor = :honeydew,
          font = "CMU Serif")
    ax = Axis(fig[1, 1], xlabel = L"f(x)", ylabel = L"x",
            xlabelsize = 22, ylabelsize = 22)
    lines!(x, cos.(x); linewidth = 3, linestyle = ".-", label = L"cos θ",
      color = :paleturquoise4)
    lines!(x, sin.(x); linewidth = 3, linestyle = :dot, label = L"sin θ",
      color = :springgreen)
    axislegend(; merge = true, framecolor = :mediumpurple, position = :lt)
    display(fig)
end;

#Jl Plots
p = Plots.plot([sin], zeros(0), xlims = (0, 2π), ylims = (-1, 1), c=[:black, :magenta], marker=:circle, linewidth= 2, label = "sin θ")
p = Plots.plot!([cos], zeros(0), xlims = (0, 2π), ylims = (-1, 1), c=[:cyan, :black], marker=:hexagon, linewidth= 2,  label = "cos θ")
for x = range(0, stop = 2π, length = 20)
    push!(p, x, Float64[sin(x), cos(x)])
end


####
# Some Closed-Form Formulae and Helpers
####
#returns max coeff_dev for dist S, M
comp_Sc(S, M) = (S + M - 1)/M
max_dev(S, M) = M * (S - 1) / (S + M - 1)
function max_cv(S, M)
	Sc = comp_Sc(S, M)
	M * sqrt((Sc - 1))
end

#Returns the minimum B
function min_gm(S, M, mu)
	Sc = comp_Sc(S, M)
	out = (Sc - 1)/Sc * log(1 - M) + 1/Sc * log(M * (S - 1) + 1)
	mu * exp(out)
end

#helper function for a robust (indefinite) quadratic
#ax^2 + bx +c >= 0 for all x in [l, u]
#adds everything to model m
function _addRQ!(m, a, b, c, l, u)
    y1 = @variable(m)  #theta
    @constraint(m, y1 >= 0)
    y = @variable(m)    #t
    @constraint(m, y >= 0)
    t2 = @variable(m)
    @constraint(m, 4 * y1 * y >= t2^2)
    @constraint(m, t2 == b - (l + u) * (y1 - a))
    @constraint(m, y1 - a >= 0)
    @constraint(m, c + l * u * (y1 - a) - y >= 0)
end

#conjecture that maximal CV occurs for uniform distribution among unimodals
#Not formally proven
function max_std_cv_unimodal_guess(mu, S, M, isSym)
    #convert to standardized distribution
    c = mu * (1 - M)
    Sc = vopp.comp_Sc(S, M)

    #adjust for symmetry if needed
    if isSym
        if Sc > 2
            Sc = 2
        end
        if Sc < 2
            lb = 2 - Sc
        else
            lb = 0
        end
    end

    return 2 / sqrt(12) * (Sc - lb) / (Sc + lb)
end

#for the standardized distribution
#document indexes 0 to N
function geom_price_ladder(S, delta)
    N = ceil(Int, 1 + log(S/delta) / log(1 + delta))
    ps = zeros(N + 1)
    ps[0 + 1] = 0.
    ps[1 + 1] = delta
    ps[N + 1] = S
    for i = 2:N-1
        ps[i + 1] = ps[i - 1 + 1] * (1 + delta)
    end
    ps
end

vopp_ub_scale(S, M) = -lambertw(-M / exp(1) / (S + M - 1))

#assumes standardized
function tight_dist_ub_scale(x, S)
    alpha = 1/vopp_ub_scale(S, 1)
    if 0 <= x <= alpha
        return 1.
    elseif x <= S
        return alpha / x
    else
        return 0.
    end
    return -1.
end

#Approximately computes the minimum and maximal valid q for distn
#assumes it is standardized
function min_max_q_IC_uni(S, mode, v0, supp_size=100)

    function G(p, m, t)
        if p > max(m, t)
            return 0.
        elseif p < min(m, t)
            return 1.
        elseif m == t
            return m >= p ? 1. : 0.
        end
        return (max(m, t) - p) / abs(m-t)
    end

    m = Model(solver=GurobiSolver(OutputFlag=false))

    t_grid = range(0, stop=S, length=supp_size)

    @variable(m, ws[1:supp_size] >= 0)
    @constraint(m, sum(ws) == 1)
    @constraint(m, sum(ws[i] * (mode + t_grid[i])/2 for i = 1:supp_size) == 1)
    @objective(m, Min, sum(ws[i] * G(v0, mode, t_grid[i]) for i = 1:supp_size))

    status = solve(m)
    @assert status == :Optimal
    minq = getobjectivevalue(m)

    @objective(m, Max, sum(ws[i] * G(v0, mode, t_grid[i]) for i = 1:supp_size))

    status = solve(m)
    @assert status == :Optimal
    maxq = getobjectivevalue(m)
    return minq, maxq
end

function min_max_q_IC_uni(S, M, mu, mode, phat, supp_size=100)
    c = mu * (1 - M)
    Sc = vopp.comp_Sc(S, M)
    mode_c = (mode - c) / (mu - c)
    v0 = (phat - 1)/ M + 1
    return min_max_q_IC_uni(Sc, mode_c, v0, supp_size)

end


import GreyDecision.GreyNumbers: GreyNumber
import GreyDecision.Utility: makeminmax
import LinearAlgebra: det

@testset "Transpose" begin
    x = [
        GreyNumber(5.0) GreyNumber(3.0) GreyNumber(-2.0);
        GreyNumber(-8.0) GreyNumber(6.0) GreyNumber(1.0);
        GreyNumber(10.0) GreyNumber(7.0) GreyNumber(13.0)
    ]
    xt = transpose(x)

    @test size(xt) == size(x)
    @test xt[1, 2] == x[2, 1]
    @test xt[1, 3] == x[3, 1]
    @test xt[1, 1] == x[1, 1]
    @test xt[2, 2] == x[2, 2]
    @test xt[3, 3] == x[3, 3]
end

@testset "det(x)" begin
    x = [
        GreyNumber(5.0) GreyNumber(3.0) GreyNumber(-2.0);
        GreyNumber(-8.0) GreyNumber(6.0) GreyNumber(1.0);
        GreyNumber(10.0) GreyNumber(7.0) GreyNumber(13.0)
    ]
    y = [
        5.0 3.0 -2.0;
        -8.0 6.0 1.0;
        10.0 7.0 13.0
    ]
    xdet = det(transpose(x) * x)
    ydet = det(transpose(y) * y)

    @test ydet == xdet.a
    @test ydet == xdet.b
end

@testset "inv(x)" begin
    atol = 0.001
     x = [
        GreyNumber(5.0) GreyNumber(3.0) GreyNumber(-2.0);
        GreyNumber(-8.0) GreyNumber(6.0) GreyNumber(1.0);
        GreyNumber(10.0) GreyNumber(7.0) GreyNumber(13.0)
    ]
    xtx = transpose(x) * x
    invxyx = inv(xtx)
    result = invxyx * xtx

    @test isapprox(result[1,1].a, 1.0, atol = atol)
    @test isapprox(result[2,2].a, 1.0, atol = atol)
    @test isapprox(result[3,3].a, 1.0, atol = atol)
    @test isapprox(result[1,2].a, 0.0, atol = atol)
    @test isapprox(result[1,3].a, 0.0, atol = atol)
    @test isapprox(result[2,1].a, 0.0, atol = atol)
    @test isapprox(result[2,3].a, 0.0, atol = atol)
    @test isapprox(result[3,1].a, 0.0, atol = atol)
    @test isapprox(result[3,2].a, 0.0, atol = atol)

end


"Log PDF of Generalized student-t Distribution."
function tlogpdf(x, df, mean, sigma)

   function tdist_consts(df, sigma)
       hdf = 0.5 * df
       shdfhdim = hdf + 0.5
       v = lgamma(hdf + 1/2) - lgamma(hdf) - 0.5*log(df) - 0.5*log(pi) - log(sigma)
       return (shdfhdim, v)
   end

    shdfhdim, v = tdist_consts(df, sigma)

    xx = x .- mean
    xx = (xx ./ sqrt(sigma)).^2

    p = 1/df * xx

    return v - log((1 + p).^((df+1) / 2))
end


#--------------------------------------------------------------------
# Spacetime Discretization methods in Julia
# Soham 05-2019
# Chebyshev Polynomials: Gauss Lobatto grid
# See Boyd F.8
#--------------------------------------------------------------------

export collocation, derivative, integral

function naturalcollocation(S::ChebyshevGL{Tag, N, T}, i::Int)::T where {Tag, N, T}
    @assert i <= N
    return  cospi(T(i-1)/T(N-1))
end

function naturalderivative(S::ChebyshevGL{Tag, N, T}, i::Int, j::Int)::T where {Tag, N, T}
    @assert i <= N && j <= N
	if i==j==1
        return  (2T(N-1)^2 + 1)/T(6)
	elseif i==j==N
        return -(2T(N-1)^2 + 1)/T(6)
	elseif i==j
        return - naturalcollocation(S,j)/(2(1-naturalcollocation(S,j)^2))
	else
		ci = (i == 1 || i == N) ?  2 : 1
		cj = (j == 1 || j == N) ?  2 : 1
		s  = (i + j) % 2 != 0   ? -1 : 1
        return (T(ci)/T(cj))*(T(s)/(naturalcollocation(S, i) - naturalcollocation(S,j)))
	end
end

function naturalintegral(S::ChebyshevGL{Tag, N, T}, i::Int)::T where {Tag, N, T}
    @assert i <= N
    W = T(0)
	for j in 1:N
        w = (j == 1 ? 1 : (j-1)%2 == 0 ? T(2)/T((1-(j-1)^2)) : 0)
        l = (i == 1 || i == N ? (T(1)/T(N-1))*cospi(T(i-1)*T(j-1)/T(N-1)) : (T(2)/T(N-1))*cospi(T(i-1)*T(j-1)/T(N-1)))
        W = W + w*l
	end
	return W
end

function collocation(space::S, i::Int)::T where {S <: Cardinal{Tag, N, T}} where {Tag, N, T}
    return naturalcollocation(space,i)*T(space.max - space.min)/2 + T(space.max + space.min)/2
end

function derivative(space::S, i::Int, j::Int)::T where {S <: Cardinal{Tag, N, T}} where {Tag, N, T}
    return naturalderivative(space,i,j)*(2/T(space.max - space.min))
end

function integral(space::S, i::Int)::T where {S <: Cardinal{Tag, N, T}} where {Tag, N, T}
    return naturalintegral(space,i)*T(space.max - space.min)/T(2)
end


"""
    regression(data::Dataset, labels::Labels; kwargs...)

perform learning of logistic regression classifier on given `data` and correct `labels`

outputs vector `w` which detrmines a hyperplane optimally separating the data samples

- `kwargs` can contain `epsilon` to specify accuracy of optimization method and initial `step` size
"""
function regression(data::Dataset, labels::Labels; kwargs...)
    trainX = data()
    trainy = labels()

    w = gd(trainX, trainy; kwargs...)

    return w
end

"""
    classify(data::Dataset, w::Vector{<:Real})

classify the `data` using separating hyperplane determined by `w`
"""
function classify(data::Dataset, w::Vector{<:Real})
    X = data()
    _, n = size(X)
    return Labels(data, [dot(X[:, i], w) >= 0 ? 1 : -1 for i in 1:n])
end

"""
    computeE(X::Matrix{<:Real}, k::Vector{<:Integer}, w::Vector{<:Real})

returns energy of hyperplane `w` with respect to data `X` and labels `k`; this objective is to be minimized
"""
function computeE(X::Matrix{<:Real}, k::Vector{<:Integer}, w::Vector{<:Real})
    N = length(k)
    return sum([log(1 + exp(-k[i] * dot(X[:, i], w))) for i in 1:N]) ./ N
end

"""
    gradE(X::Matrix{<:Real}, k::Vector{<:Integer}, w::Vector{<:Real})

returns gradient of energy with respect to vector w
"""
function gradE(X::Matrix{<:Real}, k::Vector{<:Integer}, w::Vector{<:Real})
    N = length(k)
    return - sum([(k[i] * X[:, i]) / (1 + exp(k[i] * dot(X[:, i], w))) for i in 1:N]; dims=1)[1] ./ N
end

"""
    l2norm(x)

computes l2-norm of given vector `x`
"""
function l2norm(x)
    return sqrt.(dot(x, x))
end

"""
    gd(X::Matrix{<:Real}, k::Vector{<:Integer}; epsilon=1e-4, step=1.0)

performs gradient descent method to find optimal separating hyperplane `w` on data `X` and labels `k`

keyword arguments can be used to alter the optimization:
- `epsilon` to specify accuracy/terminal condition
- `step` to adjust initial step size
"""
function gd(X::Matrix{<:Real}, k::Vector{<:Integer}; epsilon=1e-4, step=1.0)
    dim, _ = size(X)

    w = zeros(Float64, dim)
    lastw = fill(Inf64, dim)
    E = Inf64

    while l2norm(w - lastw) > epsilon
        grad = gradE(X, k, w)
        neww = w - step * grad
        newE = computeE(X, k, neww)

        if newE < E
            step *= 2
            E = newE
            lastw = w
            w = neww
        else
            step /= 2
        end

    end

    return w
end


"""
    GammaExponential(lnℓ::AbstractFloat, lnγ::AbstractFloat)
    <: RadialKernel{Euclidean}

The gamma exponential kernel with length scale
``\\exp(\\gamma\\ln\\ell) = \\ell^\\gamma > 0``.

```math
k_{\\gamma,\\ell}(x, y) =
k_{\\gamma,\\ell}(\\lVert x - y\\rVert_2) =
\\exp\\bigg\\lbrace -
\\frac{\\lVert x - y\\rVert_2^\\gamma}{\\ell^\\gamma}\\bigg\\rbrace
```
"""
mutable struct GammaExponential{F<:AbstractFloat} <: RadialKernel{Euclidean}
    dist::Euclidean
    lnℓ::F
    lnγ::F

    ℓᵞ::F
    γ::F
    function GammaExponential(
        dist::Euclidean,
        lnℓ::AbstractFloat,
        lnγ::AbstractFloat
    )
        lnℓ, lnγ = promote(lnℓ, lnγ)
        γ = exp(lnγ)
        new{typeof(lnℓ)}(dist, lnℓ, lnγ, exp(γ * lnℓ), γ)
    end
end

GammaExponential(lnℓ::AbstractFloat, lnγ::AbstractFloat) =
    GammaExponential(Euclidean(), lnℓ, lnγ)

@inline (k::GammaExponential)(τ::AbstractFloat) = exp(-τ^k.γ / k.ℓᵞ)

numparams(::GammaExponential) = (1, 1)
paramtypes(::GammaExponential{F}) where F = (F, F)
params(k::GammaExponential) = (lnℓ = k.lnℓ, lnγ = k.lnγ)
function setparams!(k::GammaExponential{F}, lnℓ::F, lnγ::F) where F
    lnℓ, lnγ = promote(lnℓ, lnγ)
    γ = exp(lnγ)
    k.lnℓ = lnℓ
    k.lnγ = lnγ
    k.ℓᵞ = exp(γ * lnℓ)
    k.γ = γ
end


"""
        greedy_star2_coloring

Find a coloring of a given input graph such that
no two vertices connected by an edge have the same
color using greedy approach. The number of colors
used may be equal or greater than the chromatic
number `χ(G)` of the graph.

A star coloring is a special type of distance - 1  coloring,
For a coloring to be called a star coloring, it must satisfy
two conditions:

1. every pair of adjacent vertices receives distinct  colors
(a distance-1 coloring)

2. For any vertex v, any color that leads to a two-colored path
involving v and three other vertices  is  impermissible  for  v.
In other words, every path on four vertices uses at least three
colors.

Reference: Gebremedhin AH, Manne F, Pothen A. **What color is your Jacobian? Graph coloring for computing derivatives.** SIAM review. 2005;47(4):629-705.

TODO: add text explaining the difference between star1 and star2
"""
function color_graph(g::Graphs.AbstractGraph, ::GreedyStar2Color)
    v = nv(g)
    colorvec = zeros(Int, v)

    forbidden_colors = zeros(Int, v+1)

    for vertex_i = vertices(g)
        for w in inneighbors(g, vertex_i)
            if colorvec[w] != 0
                forbidden_colors[colorvec[w]] = vertex_i
            end

            for x in inneighbors(g, w)
                if colorvec[x] != 0
                    if colorvec[w] == 0
                        forbidden_colors[colorvec[x]] = vertex_i
                    else
                        if colorvec[x] < colorvec[w]
                            forbidden_colors[colorvec[x]] = vertex_i
                        end
                    end
                end
            end
        end
        colorvec[vertex_i] = find_min_color(forbidden_colors, vertex_i)
    end
    return colorvec
end


using DataStructures

"""
    stack_to_rational(st::Stack)

Take a stack consisting of the convergents of the continued
fraction expansion of a number and return the rational approximation.

Note: This function makes a deep copy of the given stack.

Ex.
    [3, 7, 15] ---> 333/106

julia> st = Stack(Int)
DataStructures.Stack{Int64}(Deque [Int64[]])

julia> push!(st, 3)
DataStructures.Stack{Int64}(Deque [[3]])

julia> push!(st, 7)
DataStructures.Stack{Int64}(Deque [[3,7]])

julia> push!(st, 15)
DataStructures.Stack{Int64}(Deque [[3,7,15]])

julia> stack_to_rational(st)
333//106
"""
function stack_to_rational(st)
    tmp = deepcopy(st)
    x = pop!(tmp)

    while (length(tmp) > 0)
        x = pop!(tmp) + 1 // x
    end

    return x
end

"""
    diophantine(x [, ε = 10.0-5])

Return a rational approximation of x with an error tolerance of ε (default value of 10^-5).

Using notation from Bremner (pg 146).

julia> diophantine(float(π))
355//113
"""
function diophantine(x, ε = 10.0^-5)
    st = Stack(Int)

    # step i=2
    γ = x
    qi = floor(Int, γ)
    push!(st, qi)

    # step i=3,4,...
    while (abs(stack_to_rational(st) - x) > ε)
        γ = 1 / (γ - qi)
        qi = floor(Int, γ)
        push!(st, qi)
    end

    return stack_to_rational(st)
end


include("randExp.jl")

# we write an improoved function that uses Monte Carlo with exponential importance sampling
# to estimate the volume of the unit hypersphere in d dimensions.
# We choose our points x with independent Cartesian components and prob density exp(-lam.xi) / [ lam*((1-exp(-lam)) ].
# We achieve this by taking xi = (1/lam) log (1/y) where y is chosen uniformly in (exp(-lam),1)
function sphereVolMCImpExp(d, n, lam)
	count = 0.0
	for i = 1:n

		# we generate x according to our distribution
		x = randExp(d, lam)

		# we calculate the squared norm of x and check if x is inside the hypersphere
		norm2 = x' * x
		if norm2 < 1

			# we compute the density at x
			rho = 1
			for i = 1:d

				# note that y(i) = exp(-lam * x(i))
				rho *= exp(-lam*x[i]) * lam / (1-exp(-lam))
			end
			count += 1/rho
		end
	end
	vol =  2^d * count / n
	return vol
end


"""
	stationarydist(m::MarkovProcess)

Phân phối dừng của quá trình Markov
"""
function stationarydist(m::MarkovProcess)
	G = m.G
	A = vcat(transpose(G)[1:end-1, :], ones(1, size(G, 1)))
	B = vcat(zeros(size(G, 1) - 1), 1)
	inv(A) * B
end

"""
	imbeddedchain(m::MarkovProcess)

Xích Markov nhúng của một quá trình Markov
"""
function imbeddedchain(m::MarkovProcess)
	λ = -diag(m.G)
	p = m.G ./ λ - I
	p[diagind(p)] .= 0
	p[diagind(p)] = 1 .- sum(p; dims=2)
	MarkovChain(p)
end

"""
	transition_matrix(m::MarkovProcess, t)

Tạo ma trận chuyển tại thời điểm t
"""
function transition_matrix(m::MarkovProcess, t)
	D, Q = eigen(m.G)
	Q * diagm(exp.(D * t)) * inv(Q)
end

function Base.show(io::IO, m::MarkovProcess)
	display("Quá trình Markov với ma trận sinh")
	display(m.G)
end


module Mel

"""
    mel(frequency::Number)

Transform the input to mel scale
"""
mel(frequency::Number)::Number = 1127*log(1 + frequency/700)

"""
    imel(mel::Number)

Transform the input back from mel scale
"""
imel(mel::Number)::Number = 700*(exp(mel/1127) - 1)

"""
    melspace(start::Number, length::Int, stop::Number)::Array

Return an array of mel spaced points
"""
melspace(start::Number, length::Int, stop::Number)::Array =
	imel.(range(mel(start), length=length, mel(stop)))

"""
    mffilters(noffilters::Int, filterlength::Int, sr::Real) -> (windows, starts)

Project a filterbank composed by triangular and even-spaced filters in the mel
scale.

The first element of the tuple is an array containing arrays, each one filled
with the non-zero part of a filter from a filterbank. The second element is an
array containing the start of the non-zero part of each filter. The return can
be used to create a [`Filterbank`](@ref) defined in the Filterbank module.

**Parameters:**

`noffilters`: The number of filters in the filterbank.

`filterlenght`: The length of the filters, should match the lenght of the lenght
of the signal to which the filterbank will be applied.

`sr`: The sampling rate to be used, should match the original sampling rate of
the sequence.

See also: [`Filterbank`](@ref)
"""
function mffilters(noffilters::Int, filterlength::Int, sr::Real)

	melpoints = ceil.(Int, melspace(1, noffilters + 2, sr)*filterlength/sr)

	return (map(m->	begin

		start, apex, stop = melpoints[m:m + 2]
		w = zeros(stop - start)

		for k in 1:(apex - start)
			w[k] = k/(apex - start)
		end

		for k in apex:(stop - 1)
			w[k - start + 1] = (stop - k)/(stop - apex)
		end

		return 2w/(stop - start)	#Normalized so that the triangle has area 1

	end, 1:noffilters),
	melpoints[1:end - 2])

end

export mel, imel, melspace, mffilters
end  # module Mel


using DynamicHMCModels

Random.seed!(1)

cd(@__DIR__)
include("simulateLR.jl")

 struct RegressionProb
    x::Array{Float64,2}
    y::Array{Float64,1}
    Nd::Int64
    Nc::Int64
end

function (problem::RegressionProb)(θ)
    @unpack x,y,Nd,Nc = problem   # extract the data
    @unpack B0,B,sigma = θ
    μ = B0 .+x*B
    sum(logpdf.(Normal.(μ,sigma),y)) + logpdf(Normal(0,10),B0) +
    loglikelihood(Normal(0,10),B) + logpdf(Truncated(Cauchy(0,5),0,Inf),sigma)
end

# Define problem with data and inits.

x, y, Nd, Nc = simulateLR()
p = RegressionProb(x, y, Nd, Nc)
p((B0 = 0.0, B = fill(0.0, Nc), sigma = 1.0))

# Write a function to return properly dimensioned transformation.

problem_transformation(p::RegressionProb) =
    as((B0=asℝ, B=as(Array, asℝ, Nc), sigma = asℝ₊))

# Use Flux for the gradient.

P = TransformedLogDensity(problem_transformation(p), p)
∇P = ADgradient(:ForwardDiff, P)

#import Zygote
#∇P = ADgradient(:Zygote, P)

# Sample from the posterior.

chain, NUTS_tuned = NUTS_init_tune_mcmc(∇P, 4000);

# Undo the transformation to obtain the posterior from the chain.

posterior = TransformVariables.transform.(Ref(problem_transformation(p)), get_position.(chain));

chns = nptochain(posterior,NUTS_tuned)


using Interpolations
using QuadGK
using Jacobi
using Plots

function polynomial_decomposition(f, xs, ops; w=one, rtol=1e-8)
    x1, x2 = minimum(xs), maximum(xs)
    ys = f.(xs)
    ℑf = interpolate((xs,), ys, Gridded(Linear()))

    numerators = [quadgk(x -> ℑf(x) * op(x) * w(x), x1, x2, rtol=rtol)[1] for op in ops]
    denominators = [quadgk(x -> op(x)^2 * w(x), x1, x2, rtol=rtol)[1] for op in ops]
    cs = numerators ./ denominators

    N = length(ops)
    f′(x) = sum(cs[n] * ops[n](x) for n in 1:N)

    return cs, f′
end

@info "Decomposing into Fourier series..."

xs = range(-π, π, length=101)
f(x) = x/π + 2exp(-3(x+2)^2) -  3exp(-6(x-1)^2)

cs_even, ℑf_even = Dict(), Dict()
cs_odd,  ℑf_odd  = Dict(), Dict()
anim = @animate for N in 1:16
    ops_cos = [x -> cos(n*x) for n in 0:N]
    ops_sin = [x -> sin(n*x) for n in 1:N]
    cs_even[N], ℑf_even[N] = polynomial_decomposition(f, xs, ops_cos)
    cs_odd[N],  ℑf_odd[N]  = polynomial_decomposition(f, xs, ops_sin)
    plot(xs, f.(xs), label="", linewidth=2, xlim=(-π, π), title="Fourier series decomposition (N=$N)")
    plot!(xs, ℑf_even[N].(xs) .+ ℑf_odd[N].(xs), label="", linewidth=2)
end
mp4(anim, "fourier_decomposition.mp4", fps=5)
gif(anim, "fourier_decomposition.gif", fps=5)

@info "Decomposing into Legendre series..."

xs = range(-1, 1, length=101)
f(x) = x + 2exp(-2π*(x+2/π)^2) -  3exp(-6π*(x-1/π)^2)

cs, ℑf = Dict(), Dict()
anim = @animate for N in 0:32
    ops = [x -> legendre(x, n) for n in 0:N]
    cs[N], ℑf[N] = polynomial_decomposition(f, xs, ops)
    plot(xs, f.(xs), label="", linewidth=2, xlim=(-1, 1), title="Legendre series decomposition (N=$N)")
    plot!(xs, ℑf[N].(xs), label="", linewidth=2)
end
mp4(anim, "legendre_decomposition.mp4", fps=5)
mp4(anim, "legendre_decomposition.gif", fps=5)

@info "Decomposing into Chebyshev series..."

cs, ℑf = Dict(), Dict()
anim = @animate for N in 0:16
    w(x) = 1 / √(1 - x^2)
    ops = [x -> chebyshev(x, n) for n in 0:N]
    cs[N], ℑf[N] = polynomial_decomposition(f, xs, ops, w=w, rtol=1e-5)
    plot(xs, f.(xs), label="", linewidth=2, xlim=(-1, 1), title="Chebyshev series decomposition (N=$N)")
    plot!(xs, ℑf[N].(xs), label="", linewidth=2)
end
mp4(anim, "chebyshev_decomposition.mp4", fps=5)
mp4(anim, "chebyshev_decomposition.gif", fps=5)


# Get the mean approximation error in random interior points.
function abserror(f::Function, F::Expansion; vals = 200)
    rgrid = randomgrid(support(F),vals)
    @debug "Random evaluation in abserror"
    Fval = F(rgrid)
    fval = sample(rgrid,f,codomaintype(F))
    sum(abs.(Fval-fval))/vals
end

# Get the max approximation error in random interior points
function maxerror(f::Function, F::Expansion; vals = 200, options...)
    rgrid = randomgrid(support(F),vals)
    @debug "Random evaluation in maxerror"
    Fval = F(rgrid; options...)
    fval = sample(rgrid,f,codomaintype(F))
    maximum(abs.(Fval-fval))
end

using QuadGK

function L2error(f::Function, F::Expansion{S,T}; rtol = eps(real(T)), atol = eps(real(T)), options...) where {S,T}
    I = QuadGK.quadgk(x->abs(F(x)-f(x))^2, infimum(support(dictionary(F))), supremum(support(dictionary(F))), rtol=rtol, atol=atol)
    @assert I[2] < 100max(rtol*I[1],atol)
    sqrt(I[1])
end

function residual(f::Function, F::Expansion; residualtype = :l2, options...)
    A, B = discretization(f, dictionary(F); options...)
    R = A*coefficients(F)-B
    if residualtype == :l2
        norm(R)
    elseif residualtype == :relativel2
        norm(R)/norm(B)
    elseif residualtype == :maxnorm
        maximum(abs.(R))
    else
        error("Unknown residualtype: $residualtype")
    end
end


# Solve Poisson's equation -(uxx + uyy) = f, bnd cnds u(x,y) = g(x,y)
# on a square grid using the finite difference method.
#
# UC Berkeley Math 228B, Per-Olof Persson <persson@berkeley.edu>

using Pkg
Pkg.add("Laplacians")
using SparseArrays, PyPlot, Laplacians

"""
    A, b, x, y = assemblePoisson(n, f, g)

Assemble linear system Au = b for Poisson's equation using finite differences.
Grid size (n+1) x (n+1), right hand side function f(x,y), Dirichlet boundary
conditions g(x,y).
"""

function D2x(f,x,y,h)
  return (f(x-h,y)-2*f(x,y)+f(x+h,y))/h^2
end
function D2y(f,x,y,h)
  return (f(x,y-h)-2*f(x,y)+f(x,y+h))/h^2
end

function z(f,x,y,h)
  return f(x,y) + h^2*(D2x(f,x,y,h)+D2y(f,x,y,h))/12
end

function assemblePoisson(n, f, g)
    h = 1.0 / n #on a grid of 1x1, figure out index spacing
    N = (n+1)^2 #size of all elements, including boundary conditions
    x = h * (0:n) #x coords of the index points
    y = x #equate 2 axis

    umap = reshape(1:N, n+1, n+1)     # Index mapping from 2D grid to vector
    A = Tuple{Int64,Int64,Float64}[]  # Array of matrix elements (row,col,value)
    b = zeros(N)

    # Main loop, insert stencil in matrix for each node point
    for j = 1:n+1
        for i = 1:n+1
            row = umap[i,j]
            if i == 1 || i == n+1 || j == 1 || j == n+1
                # Dirichlet boundary condition, u = g
                push!(A, (row, row, 1.0))
                b[row] = g(x[i],y[j])
            else
                # Interior nodes, 5-point stencil
                push!(A, (row, row, 20.0))
                push!(A, (row, umap[i+1,j], -4.0))
                push!(A, (row, umap[i-1,j], -4.0))
                push!(A, (row, umap[i,j+1], -4.0))
                push!(A, (row, umap[i,j-1], -4.0))

                push!(A, (row, umap[i+1,j+1], -1.0))
                push!(A, (row, umap[i-1,j+1], -1.0))
                push!(A, (row, umap[i+1,j-1], -1.0))
                push!(A, (row, umap[i-1,j-1], -1.0))
                b[row] = z(f, x[i], y[j], h) * 6*h^2
            end
        end
    end

    # Create CSC sparse matrix from matrix elements
    A = sparse((x->x[1]).(A), (x->x[2]).(A), (x->x[3]).(A), N, N)

    return A, b, x, y
end

function testPoisson(n=40)
    uexact(x,y) = exp(-(4(x - 0.3)^2 + 9(y - 0.6)^2)) #truesoln
    f(x,y) = uexact(x,y) * (26 - (18y - 10.8)^2 - (8x - 2.4)^2) #f_ij

    A, b, x, y = assemblePoisson(n, f, uexact)

    # Solve + reshape for plotting
    u = reshape(A \ b, n+1, n+1)

    # Plotting
    clf()
    contour(x, y, u, 10, colors="k")
    contourf(x, y, u, 10)
    axis("equal")
    colorbar()

    # Compute error in max-norm
    u0 = uexact.(x, y') #solving for lte
    error = maximum(abs.(u - u0))
end


using FastGaussQuadrature
import GSL

"""
    U = upsampling_matrix(n, m)

um = U*un upsamples a function u(x) from n to m GL nodes on [-1,1] \\
Can be used to create a downsampling matrix as well
"""
function upsampling_matrix(n, m)
    L = legendre_matrix(n)
    B = Array{Float64}(m, n)
    glpoints, glweights = gausslegendre(m)
    for i=1:m
        P = GSL.sf_legendre_Pl_array(n-1, glpoints[i])
        for j=1:n
            B[i,j] = P[j]
        end
    end
    return B*L
end

"""
    L = legendre_matrix(order)

L computes Legendre expansion coefficients for a function
defined at the Gauss-Legendre quadrature nodes on [-1, 1] \\
``c = L*f  ->  c_l = (2l+1)/2 \\sum_n P_l(x_n) f_n w_n``
"""
function legendre_matrix(n)
    glpoints, glweights = gausslegendre(n)
    L = Array{Float64}(n, n)
    for i=1:n
        P = GSL.sf_legendre_Pl_array(n-1, glpoints[i])
        for j=1:n
            l = j-1
            L[j, i] = P[j]*glweights[i]*(2*l+1)/2
        end
    end
    return L
end

"""
    D = legendre_diff_matrix(order)

D approximates the derivative of a function defined at
the Gauss-Legendre quadrature nodes on [-1, 1] \\
``D*f =~ f'``
"""
function legendre_diff_matrix(n)
    glpoints, glweights = gausslegendre(n)
    dPt = Array{Float64}(n, n)
    for i=1:n
        _, dP = legendre_Pl_deriv_array(n-1, glpoints[i])
        for j=1:n
            dPt[i, j] = dP[j]
        end
    end
    L = legendre_matrix(n)
    return dPt*L
end

"""
P, D = legendre_Pl_deriv_array(n, x) \\
Recurrence relation used: [http://dlmf.nist.gov/18.9]

As fast as the GSL implementation, but also takes x outside [-1, 1].
    """
function legendre_Pl_deriv_array(n, x)
    P = Array{Complex{Float64}}(n+1)
    D = Array{Complex{Float64}}(n+1)
    P[1] = 1.0 # l=0
    D[1] = 0.0
    P[2] = x # l=1
    D[2] = 1.0
    for l=1:n-1
        # Compute l+1
        P[l+1+1] = ( (2*l+1)*x * P[l+1] - l*P[l-1+1] ) / (l+1)
        D[l+1+1] = ( (2*l+1)*(P[l+1] + x * D[l+1]) - l*D[l-1+1] ) / (l+1)
    end
    P, D
end

"""
    t, relres, iter = newton_legendre(c, zr, t0, maxiter, tol)

Find root t of Legendre expansion c such that
``\\sum_n c_n*P_n(t) = zr``
"""
function newton_legendre(c, zr, t0, maxiter, tol)
    t = t0
    iter = 0
    dt = Inf
    lmax = length(c)-1
    relres = tol
    for iter=1:maxiter
        P, D = legendre_Pl_deriv_array(lmax, t)
        f = sum(P.*c) - zr
        fp = sum(D.*c)
        dt = -f / fp
        t = t + dt
        relres = abs(dt/t)
        if relres < tol
            break
        end
    end
    return t, relres, iter
end


module Newton

# Write your package code here.
using Statistics, LinearAlgebra, ForwardDiff

function newtonroot(f,f′;x₀,tol=1E-7,maxiter=1000)
    println("the first dispach")
    x=zeros(maxiter+1)
    x_old=x₀
    i=1
    norm_dif=Inf
    while i <= maxiter &&  norm_dif>=tol
        x_new=x_old - f(x_old)/f′(x_old)
        i+=1
        norm_dif=norm(x_new-x_old)
        x_old=x_new
    end
    if i == maxiter+1
        return nothing
    else
        return (iteration=i, norm_dif=norm_dif, x=x_old)
    end
end

function newtonroot(f;x₀,tol = 1E-7,maxiter = 1000)
    println("the second dispach")
    x=zeros(maxiter+1)
    x_old=x₀
    i=1
    norm_dif=Inf
    f_prime(x)=ForwardDiff.derivative(f,x)
    while i <= maxiter &&  norm_dif>=tol
        x_new=x_old - f(x_old)/f_prime(x_old)
        i+=1
        norm_dif=norm(x_new-x_old)
        x_old=x_new
    end
    if i == maxiter+1
        return nothing
    else
        return (iteration=i, norm_dif=norm_dif, x=x_old)
    end
end

export newtonroot
end


function euclidean_distance(set1::Array, set2::Array)

    @assert length(set1) == length(set2) "Sets don't have the same length"

    sum = zero(eltype(set1))

    for i in 1:length(set1)
            sum += (set2[i] - set1[i])^2
    end

    return sqrt(sum)

end


using LinearAlgebra

# k_local(Elastic Modulus,thickness,height,length,number of nodes,row/column one,row/column two, ==1 adjusts for fixed node)

#creates local stiffness matrix and maps to global
function k_local(E,t,h,l,i,n1,n2,fixed=0)
    k_global = zeros(i,i)
    A = t*h
    k = ((E*A)/l)
    k_global[n1,n1] = k
    k_global[n1,n2] = -k
    k_global[n2,n1] = -k
    k_global[n2,n2] = k
    if fixed == 1
      adj = zeros(1,i)
      adj[n1] = 1
      k_global[n1,:] = adj
      k_global[:,n1] = adj
    end
    return k_global
end

# constants from problem statement
E = 28*10^3
t = 1/8
h16 = 4
h2345 = 2.5/4
l = 2
l2345 = 8

# combining all globally mapped matrices
kG = k_local(E,t,h16,l,4,1,2,1)+
     k_local(E,t,h2345,l2345,4,2,3)+
     k_local(E,t,h2345,l2345,4,2,3)+
     k_local(E,t,h2345,l2345,4,2,3)+
     k_local(E,t,h2345,l2345,4,2,3)+
     k_local(E,t,h16,l,4,3,4)

# Force Vector
f = Float64[0 0 0 500]
F = transpose(f)

# Displacement vector
u = kG\F
println("Nodal Displacement Vector (inches)")
display(u)

# Strain Function
ϵ(x2,x1,l) = (x2 - x1)/l

#Strain Vector
ϵM = [ϵ(u[2],u[1],l)
      ϵ(u[3],u[2],l2345)
      ϵ(u[3],u[2],l2345)
      ϵ(u[3],u[2],l2345)
      ϵ(u[3],u[2],l2345)
      ϵ(u[4],u[3],l)]

# Stress Function
σ(E,ϵ) = E*ϵ

# Stress Vector
σM = [σ(E,ϵM[1])
      σ(E,ϵM[2])
      σ(E,ϵM[3])
      σ(E,ϵM[4])
      σ(E,ϵM[5])
      σ(E,ϵM[6])]
println("Element Stress Vector (psi)")
display(σM)


# calculate probability by pdf
# sensor_data_200.txt
# plot as 2d-contour

module ContourPdf200
    using DataFrames, CSV, Statistics, LinearAlgebra
    using Base.Iterators, Plots
    pyplot()

    function pdf(x, mu, cov_mat)
        # determinant
        det_mat = det(cov_mat)

        diff_x = x - mu
        return exp(-((diff_x'/cov_mat)*diff_x)/2)/(2*pi*sqrt(det_mat))
    end

    function main(is_test=false)
        # input
        data_path = joinpath(split(@__FILE__, "src")[1], "data/sensor_data_200.txt")
        df = CSV.read(data_path, DataFrame,
                      header=["date", "time", "ir", "lidar"],
                      delim=' ')

        # variance
        var_ir = var(df.ir)
        var_lidar = var(df.lidar)

        #covariance
        diff_ir = df.ir .- mean(df.ir)
        diff_lidar = df.lidar .- mean(df.lidar)
        a = diff_ir .* diff_lidar
        covar = cov(df.ir, df.lidar, corrected=false)

        # covariance matrix
        cov_mat = [var_ir covar; covar var_lidar]

        # mean(mu)
        mu = [mean(df.ir); mean(df.lidar)]

        # plot contour
        vx = 280:340
        vy = 190:230
        z = [pdf([x; y], mu, cov_mat) for x in vx, y in vy]
        contour(vx, vy, z', label="contour", c=:haline, aspect_ratio=:equal)

        if is_test == false
            save_path = joinpath(split(@__FILE__, "src")[1], "src/prob_stats/multi_dim_gauss_dist/contour_pdf/contour_pdf_200.png")
            savefig(save_path)
        end
    end
end


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

using Markdown
using InteractiveUtils

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

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

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

# ╔═╡ 6ffea8a0-1ea1-11eb-1991-61c054bcac44
begin
	A = Set(['a','e','i','o','u'])
	B = Set(['x','y','z'])
	omega = 'a':'z'

	N = 10^6
end;

# ╔═╡ 70179b62-1ea1-11eb-13ce-7fb4d54bad4b
begin
	df = DataFrame()
	local mcEst1, mcEst2
	for _ in 1:5
    	mcEst1 = sum([in(sample(omega),A) || in(sample(omega),B) for _ in 1:N])/N
    	mcEst2 = sum([in(sample(omega),union(A,B)) for _ in 1:N])/N
		append!(df, DataFrame(:mcEst1 => mcEst1, :mcEst2 => mcEst2))
	end
	df
end

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

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


export merge_sort

function merge_sort(array::Vector)
    if length(array) > 1
        divided = divide(array)
        merged1 = merge_sort(divided[1])
        merged2 = merge_sort(divided[2])
        merged = merge(merged1, merged2)

        return merged
    end

    return array
end

function divide(array::Vector)
    split = trunc(Int64, length(array)/2)
    arr1 = array[1:split]
    arr2 = array[split+1:end]

    return (arr1, arr2)
end

function merge(arr1::Vector, arr2::Vector)
    merged = []

    while !(isempty(arr1) || isempty(arr2))
        if arr1[1] <= arr2[1]
            push!(merged, popfirst!(arr1))
        else
            push!(merged, popfirst!(arr2))
        end
    end

    if !isempty(arr1)
        append!(merged, arr1)
    end
    if !isempty(arr2)
        append!(merged, arr2)
    end

    return merged
end


# # Ordinary random walk

# Here is the prototypical application. We repeatedly perform an ordinary random walk with a fixed
# number of steps on $\mathbb{Z}$, collecting statistics on the final position.
# We call each walk, or trajectory, a *sample*. An ensemble of samples is a *trial*.

# We begin by loading packages that we will use.

using RandomWalks
using EmpiricalCDFs: EmpiricalCDF, get_data
using Statistics

# First, we show an uncommented version of the code that specifies and runs the experiment.
# Note that it is very compact!

trial_loop = SampleLoopActor(10^6, ECDFActor(get_x, EmpiricalCDF()));
trial!(WalkPlan(WalkF(), StepLimitActor(10^3)), trial_loop);

# In the following we go through the same code step-by-step.

# ### Generating the samples and the trial

# A sample is a single trajectory, or walk. We will take `nsamples` samples.
nsamples = 10^6;

# Choose the number of steps per walk.
nsteps = 10^3;

# Create an emtpy empirical cumulative distribution function for floating point data.
ecdf = EmpiricalCDF()

# A trial is a collection of samples (walks) from which we collect statistics.
# The following instance of a `SampleLoopActor` specifies that we collect `nsamples` samples and that we
# build an empirical CDF of the final position (on the x-axis) of the walker after each walk. Below, we will
# use a one-dimensional walk, so `get_x` returns the position.
trial_loop = SampleLoopActor(nsamples, ECDFActor(get_x, ecdf));

# We used two `AbstractActors` above. `Actors` are treated (for the most part)
# in a uniform way within a sample, and in (a different)
# uniform way within a trial. This gives flexibility in designing experiments.

# We create a `WalkPlan` and run a trial of `nsamples` samples.
# The `WalkPlan` consists of  two parts;
# The first, a walk specification `WalkF()`, which is the default one-dimension walk on the integers.
# The second an action `StepLimitActor(nsteps)` to take after each step.
# `StepLimitActor(nsteps)` stops the walk if the number of steps exceeds `nsteps`.
# The `WalkPlan` specifies the sample and how it is collected.

# The `Actor` `trial_loop` was described above. It describes how to take several samples and actions to take
# afer each sample.
# In this case, which statistics to collect.
# So, the following line builds an ECDF of the final position of a one-dimensional walk after `nsteps` steps.
@time trial!(WalkPlan(WalkF(), StepLimitActor(nsteps)), trial_loop);

# We rescale the data so that the expected standard deviation is equal to `1`.
x = get_data(ecdf);
x ./= sqrt(nsteps);

# Test that the standard deviation and mean are as expected

## The mean should be near 0 and the standard deviation near 1
@show mean(ecdf), std(ecdf);

# We test the above results with a generous tolerance.
tolerance = 1e-3;

isapprox(std(ecdf), 1; atol = tolerance)
#-
isapprox(mean(ecdf), 0; atol = tolerance)

# We approximate the probability density function with a normalized `Histogram`
# and see that it looks reasonable.
using StatsBase: Histogram, fit, normalize
h = normalize(fit(Histogram, get_data(ecdf), -5.5:1.0:5.5));

println(h.weights);


using DifferentialEquations
using SimpleDiffEq
using DiffEqSensitivity
using Random
using Distributions
using DiffEqParamEstim
using Plots

function sir_ode!(du,u,p,t)
    (S,I,R,C) = u
    (β,c,γ) = p
    N = S+I+R
    infection = β*c*I/N*S
    recovery = γ*I
    @inbounds begin
        du[1] = -infection
        du[2] = infection - recovery
        du[3] = recovery
        du[4] = infection
    end
    nothing
end;

δt = 1.0
tmax = 40.0
tspan = (0.0,tmax)
obstimes = 1.0:1.0:tmax;

u0 = [990.0,10.0,0.0,0.0]; # S,I.R,Y

p = [0.05,10.0,0.25]; # β,c,γ

prob_ode = ODEProblem(sir_ode!,u0,tspan,p)
sol_ode = solve(prob_ode,Tsit5(),saveat=δt);

out = Array(sol_ode)
C = out[4,:];

X = C[2:end] .- C[1:(end-1)];

Random.seed!(1234);

Y = rand.(Poisson.(X));

using Optim

function ss1(β)
    prob = remake(prob_ode,u0=[990.0,10.0,0.0,0.0],p=[β,10.0,0.25])
    sol = solve(prob,Tsit5(),saveat=δt)
    out = Array(sol)
    C = out[4,:]
    X = C[2:end] .- C[1:(end-1)]
    nonpos = sum(X .<= 0)
    if nonpos > 0
        return Inf
    end
    return(sum((X .- Y) .^2))
end;

function nll1(β)
    prob = remake(prob_ode,u0=[990.0,10.0,0.0,0.0],p=[β,10.0,0.25])
    sol = solve(prob,Tsit5(),saveat=δt)
    out = Array(sol)
    C = out[4,:]
    X = C[2:end] .- C[1:(end-1)]
    nonpos = sum(X .<= 0)
    if nonpos > 0
        return Inf
    end
    -sum(logpdf.(Poisson.(X),Y))
end;

lower1 = 0.0
upper1 = 1.0
initial_x1 = 0.1;

opt1_ss = Optim.optimize(ss1,lower1,upper1)

opt1_nll = Optim.optimize(nll1,lower1,upper1)

function ss2(x)
    (i0,β) = x
    I = i0*1000.0
    prob = remake(prob_ode,u0=[1000.0-I,I,0.0,0.0],p=[β,10.0,0.25])
    sol = solve(prob,Tsit5(),saveat=δt)
    out = Array(sol)
    C = out[4,:]
    X = C[2:end] .- C[1:(end-1)]
    nonpos = sum(X .<= 0)
    if nonpos > 0
        return Inf
    end
    return(sum((X .- Y) .^2))
end;

function nll2(x)
    (i0,β) = x
    I = i0*1000.0
    prob = remake(prob_ode,u0=[1000.0-I,I,0.0,0.0],p=[β,10.0,0.25])
    sol = solve(prob,Tsit5(),saveat=δt)
    out = Array(sol)
    C = out[4,:]
    X = C[2:end] .- C[1:(end-1)]
    nonpos = sum(X .<= 0)
    if nonpos > 0
        return Inf
    end
    -sum(logpdf.(Poisson.(X),Y))
end;

lower2 = [0.0,0.0]
upper2 = [1.0,1.0]
initial_x2 = [0.01,0.1];

opt2_ss = Optim.optimize(ss2,lower2,upper2,initial_x2)

opt2_nll = Optim.optimize(nll2,lower2,upper2,initial_x2)

function loss_function(sol)
    out = Array(sol)
    C = out[4,:]
    X = C[2:end] .- C[1:(end-1)]
    nonpos = sum(X .<= 0)
    if nonpos > 0
        return Inf
    end
    -sum(logpdf.(Poisson.(X),Y))
end;

prob_generator = (prob,q) -> remake(prob,
    u0=[1000.0-(q[1]*1000),q[1]*1000,0.0,0.0],
    p=[q[2],10.0,0.25]);

cost_function = build_loss_objective(prob_ode,
    Tsit5(),
    loss_function,
    saveat=δt,
    prob_generator = prob_generator,
    maxiters=100,
    verbose=false);

opt_pe1 = Optim.optimize(cost_function,lower2,upper2,initial_x2)

using NLopt
opt = Opt(:LD_MMA, 2)
opt.lower_bounds = lower2
opt.upper_bounds = upper2
opt.min_objective = cost_function
opt.maxeval = 10000
(minf,minx,ret) = NLopt.optimize(opt,initial_x2)

using BlackBoxOptim
bound1 = Tuple{Float64, Float64}[(0.0,1.0),(0.0, 1.0)]
result = bboptimize(cost_function;SearchRange = bound1, MaxSteps = 1e4)