#####################################################################
# Base functions.
#####################################################################
function sphere(x)
  sum(x.^2)
end

# Schwefel's ellipsoid.
function ellipsoid(x)
  res = 0
  for(i in 1:length(x))
    res += sum(x[1:i])^2
  end
  res
end

function elliptic(x)
  D = length(x)
  condition = 1e+6
  coefficients = condition .^ linspace(0, 1, D)
  sum(coefficients .* x.^2)
end

function rastrigin(x)
  D = length(x)
  10 * D + sum( x.^2 ) - 10 * sum( cos( 2 * π * x ) )
end

function ackley(x)
  D = length(x)
  try
    20 - 20.*exp(-0.2.*sqrt(sum(x.^2)/D)) - exp(sum(cos(2 * π * x))/D) + e
  catch e
    # Sometimes we have gotten a DomainError from the cos function so we protect this call
    println(e)
    println("For input x = ", x)
    # Return a very large fitness value to indicate that this is NOT the solution we want.
    # TODO: Fix better way to handle this!
    9.99e100
  end
end

function schwefel1_2(x)
  D = length(x)
  partsums = zeros(D)
  partsum = 0
  for i in 1:D
    partsum += x[i]
    partsums[i] = partsum
  end
  sum(partsums.^2)
end

function rosenbrock(x)
  n = length(x)
  return( sum( 100*( x[2:n] - x[1:(n-1)].^2 ).^2 + ( x[1:(n-1)] - 1 ).^2 ) )
end

function step(x)
  sum(ceil(x + 0.5))
end

function griewank(x)
  n = length(x)
  1 + (1/4000)*sum(x.^2) - prod(cos(x ./ sqrt(1:n)))
end

function schwefel2_22(x)
  ax = abs(x)
  sum(ax) + prod(ax)
end

function schwefel2_21(x)
  maximum(abs(x))
end

# I'm unsure about this one since it does not return the expected minima at
# [1.0, 1.0].
function schwefel2_26(x)
  D = length(x)
  418.98288727243369 * D - sum(x .* sin(sqrt(abs(x))))
end

function cigar(x)
  x[1]^2 + 1e6 * sum(x[2:end].^2)
end

function cigtab(x)
  x[1]^2 + 1e8 * x[end]^2 + 1e4 * sum(x[2:(end-1)].^2)
end

# Shekel10 is a 4D, multi-minima, non-separable test problem. Our implementation
# is based on the C code in:
#   http://www.math.ntu.edu.tw/~wwang/cola_lab/test_problems/multiple_opt/multiopt_prob/Shekel10/Shekel10.c
Shekel10_A = [4 4 4 4; 1 1 1 1; 8 8 8 8; 6 6 6 6; 3 7 3 7; 2 9 2 9; 5 5 3 3; 8 1 8 1; 6 2 6 2; 7 3.6 7 3.6]
Shekel10_C = [0.1, 0.2, 0.2, 0.4, 0.4, 0.6, 0.3, 0.7, 0.5, 0.5]
function shekel(x, a, c)
  sum = 0.0
  for i in 1:length(c)
    den = 0.0
    for j in 1:size(a, 2)
      den += (x[j] - a[i,j])^2
    end
    sum = sum - 1 / (den + c[i])
  end
  return sum
end
shekel10(x) = shekel(x, Shekel10_A, Shekel10_C)

# Shekel7 is a 4D, multi-minima, non-separable test problem. Our implementation
# is based on the C code in:
#   http://www.math.ntu.edu.tw/~wwang/cola_lab/test_problems/multiple_opt/multiopt_prob/Shekel7/Shekel7.c
Shekel7_A = [4 4 4 4; 1 1 1 1; 8 8 8 8; 6 6 6 6; 3 7 3 7; 2 9 2 9; 5 5 3 3]
Shekel7_C = [0.1, 0.2, 0.2, 0.4, 0.4, 0.6, 0.3]
shekel7(x) = shekel(x, Shekel7_A, Shekel7_C)

# Shekel5 is a 4D, multi-minima, non-separable test problem. Our implementation
# is based on the C code in:
#   http://www.math.ntu.edu.tw/~wwang/cola_lab/test_problems/multiple_opt/multiopt_prob/Shekel5/Shekel5.c
Shekel5_A = [4 4 4 4; 1 1 1 1; 8 8 8 8; 6 6 6 6; 3 7 3 7]
Shekel5_C = [0.1, 0.2, 0.2, 0.4, 0.4]
shekel5(x) = shekel(x, Shekel5_A, Shekel5_C)

# Hartman 6D is a multi-minima, non-separable test problem. Our implementation is based on:
#  http://www.sfu.ca/~ssurjano/hart6.html
Hartman6_alpha = [1.0 1.2 3.0 3.2]
Hartman6_A = [10 3 17 3.50 1.7 8; 0.05 10 17 0.1 8 14; 3 3.5 1.7 10 17 8; 17 8 0.05 10 0.1 14]
Hartman6_P = 1e-4 * [1312 1696 5569 124 8283 5886; 2329 4135 8307 3736 1004 9991; 2348 1451 3522 2883 3047 6650; 4047 8828 8732 5743 1091 381]
function hartman(x, alpha, A, P)
  sum = 0.0
  for i in 1:length(alpha)
    isum = 0.0
    for j in 1:size(A, 2)
      isum += A[i,j] * (x[j] - P[i,j])^2
    end
    sum -= alpha[i] * exp(-isum)
  end
  sum
end
hartman6(x) = hartman(x, Hartman6_alpha, Hartman6_A, Hartman6_P)

# Hartman 3D is a multi-minima, non-separable test problem. Our implementation is based on:
#  http://www.sfu.ca/~ssurjano/hart3.html
# However, we get a different global minima than the one stated on that page.
Hartman3_alpha = [1.0 1.2 3.0 3.2]
Hartman3_A = [3.0 10 30; 0.1 10 35; 3.0 10 30; 0.1 10 36]
Hartman3_P = 1e-4 * [3689 1170 2673; 4699 4387 7470; 1091 8732 5547; 381 5743 8828]
hartman3(x) = hartman(x, Hartman3_alpha, Hartman3_A, Hartman3_P)
# The minima should be -3.86278 but we get a different one so use that in the problem spec:

#####################################################################
# S2 functions in addition to the base functions above. As stated
# in table II of the JADE paper: http://150.214.190.154/EAMHCO/pdf/JADE.pdf
#####################################################################

function quartic(x)
  D = length(x)
  sum( (1:D) .* x.^4 )
end

function noisy_quartic(x)
  quartic(x) + rand()
end

function s2_step(x)
  sum( ceil(x + 0.5).^2 )
end

#####################################################################
# Misc other interesting optimization functions and families.
#####################################################################

# Schwefel 2.13 is a hard one...
#function f=schwefel_213(x)
#global initial_flag
#persistent a b A alpha
#[ps,D]=size(x);
#if initial_flag==0
#    initial_flag=1;
#    load schwefel_213_data
#    if length(alpha)>=D
#        alpha=alpha(1:D);a=a(1:D,1:D);b=b(1:D,1:D);
#    else
#        alpha=-3+6*rand(1,D);
#        a=round(-100+200.*rand(D,D));
#        b=round(-100+200.*rand(D,D));
#    end
#    alpha=repmat(alpha,D,1);
#    A=sum(a.*sin(alpha)+b.*cos(alpha),2);
#end
#for i=1:ps
#    xx=repmat(x(i,:),D,1);
#    B=sum(a.*sin(xx)+b.*cos(xx),2);
#    f(i,1)=sum((A-B).^2,1);
#end

# This is a generator for the family of deceptive functions from the
# Cuccu2011 paper on novelty-based restarts. We have vectorized it to allow
# more than 1D versions. The Cuccu2011 paper uses the following values for
# (l, w) = [(5, 0),  (15, 0),  (30, 0),
#           (5, 2),  (15, 2),  (30, 2),
#           (5, 10), (15, 10), (30, 10)]
# and notes that (15, 2) and (30, 2) are the most difficult instances.
function deceptive_cuccu2011(l, w)
  (x) -> begin
    absx = abs(x)
    sumabsx = sum(absx)
    if sumabsx <= 1
      return sum(x.^2)
    elseif sumabsx >= l+1
      return sum((absx - l).^2)
    else
      return (1 - 0.5 * sum(sin( (π * w * (absx - 1)) / l ).^2))
    end
  end
end

# Deceptive/hardest instances:
deceptive_cuccu2011_15_2 = deceptive_cuccu2011(15, 2)
deceptive_cuccu2011_30_2 = deceptive_cuccu2011(30, 2)


using UncertaintyQuantification, Random
Random.seed!(2547);
N = 200
mu = [0, 0]
sig = [1 -0.55; -0.55 1]
mvnormal = MvNormal(mu, sig)

sam = zeros(200,2)
for i in 1:200 sam[i,:] = rand(mvnormal) end

Random.seed!()

function likelihood(x)
    if x[1] >1 || x[1]<-1
        rv = 0
    else
        rv = 1
        for i in 1:N
            rv = rv/(2*pi*sqrt(1-x[1]^2))*exp(-1/(2*(1-x[1]^2))*(sam[i,1]^2-2*x[1]*
            sam[i,1]*sam[i,2]+sam[i,2]^2))
        end
    end
    return rv
end

function prior(x)
    if x[1] >1 || x[1]<-1
        rv = 0
    else
        rv = 1/(1-x[1]^2)^(3/2)
    end
    return rv
end
priorsample(n) = rand(Uniform(-1,1),(n))

tmcmcsample = tmcmc(likelihood, prior, priorsample, 2000, 0.2)


using LinearAlgebra
using Plots

#%
#% The 'illustrative example' from Section 1.4, Chapter 1.
#%
#% written by John Bardsley 2016.
#%
#% Define the matrix A using the SVD
v1 = [1/sqrt(2) 1/sqrt(2)]';
v2 = [-1/sqrt(2) 1/sqrt(2)]';
s1 = 1;
s2 = 1e-2;
A  = s1*v1*v1'+s2*v2*v2';

#% Define true x and noise free b
x   = [1 1]';
b_e = A*x;

#% Create realization from the data model
nsamp  = 10000;
σ  = 0.1;
b_samp = repeat(b_e,1,nsamp)+σ*randn(2,nsamp);

#figure(1),
  scatter(b_samp[1,:],b_samp[2,:]) #,'k*')
#% Create the corresponding least squares solutions
x_LS = A\b_samp;
#figure(2)
  scatter(x_LS[1,:],x_LS[2,:]) #,'k*')

#% Check ill-posedness inequality (1.28)
ill_posed_lhs = 0.5*(1/s1+1/s2)
ill_posed_rhs = norm(x)/norm(b_e)


function draw_wireframe_sphere(pos, r; npts=11, color=(255,255,255), name=nothing, opacity=1.)
    ϕs = range(0, stop=2π, length=npts)
    θs = range(-π/2, stop=π/2, length=npts)
    # vertical
    v_xs = [[r * cos(θ) * sin(ϕ) + pos[1] for θ in θs] for ϕ in ϕs[1:end-1]]
    v_ys = [[r * cos(θ) * cos(ϕ) + pos[2] for θ in θs] for ϕ in ϕs[1:end-1]]
    v_zs = [[r * sin(θ) + pos[3] for θ in θs] for ϕ in ϕs[1:end-1]]
    # horizontal
    h_xs = [[r * cos(θ) * sin(ϕ) + pos[1] for ϕ in ϕs] for θ in θs]
    h_ys = [[r * cos(θ) * cos(ϕ) + pos[2] for ϕ in ϕs] for θ in θs]
    h_zs = [[r * sin(θ) + pos[3] for ϕ in ϕs] for θ in θs]

    hover = isnothing(name) ? "skip" : nothing

    xs, ys, zs = Float64[], Float64[], Float64[]
    for i=1:length(v_xs)
        append!(xs, v_xs[i])
        push!(xs, NaN)
        append!(ys, v_ys[i])
        push!(ys, NaN)
        append!(zs, v_zs[i])
        push!(zs, NaN)
    end
    for j=1:length(h_xs)
        append!(xs, h_xs[j])
        push!(xs, NaN)
        append!(ys, h_ys[j])
        push!(ys, NaN)
        append!(zs, h_zs[j])
        push!(zs, NaN)
    end

    return scatter3d(;x=xs, y=ys, z=zs,
                      mode="lines",
                      line=attr(color="rgb$color"),
                      opacity=opacity,
                      showlegend=false, showscale=false, name=name,
                      hovertemplate=name, hoverinfo=hover,
            )
end

draw_central_body(bd::Union{CelestialBody,Star}; kwargs...
    ) = draw_wireframe_sphere(SVector{3,Float64}(0.,0.,0.), bd.eqradius; color=bd.color, name=bd.name, kwargs...)

draw_orbiting_body(bd::CelestialBody, t; kwargs...
    ) = draw_wireframe_sphere(time_orbital_position(t, bd.orbit), bd.eqradius; color=bd.color, name=bd.name, kwargs...)

draw_soi(bd::CelestialBody, t; kwargs...
    ) = draw_wireframe_sphere(time_orbital_position(t, bd.orbit), bd.SoI; color=bd.color, name=bd.name, opacity=0.25, kwargs...)


module FreudenthalTriangulations

using LinearAlgebra
using SparseArrays

export FreudenthalTriangulation, vertices, simplex, belief_vertices, belief_simplex

# Construct the Freudenthal triangulation of the belief simplex

struct FreudenthalTriangulation
        n::Int
        m::Int
end

"""
    vertices(T::FreudenthalTriangulation)
Construct the list of Freudenthal vertices in an `n` dimensional space with grid resolution `m`.
The vertices are represented by a list of `n` dimensional vectors.
"""
function vertices(T::FreudenthalTriangulation)
    V = Vector{Int}[]
    v = Vector{Int}(undef, T.n)
    v[1] = T.m
    _vertices!(V, v, 2)
    return V
end

function _vertices!(V::Vector{Vector{Int64}}, v::Vector{Int64}, i::Int64)
    n = length(v)
    if i > n
        push!(V, copy(v))
        return
    end
    for k in 0 : v[i-1]
        v[i] = k
        _vertices!(V, v, i+1)
    end
end

"""
    _freudenthal_simplex(x::Vector{Float64})
Returns the list of vertices of the simplex of point `x` in the Freudenthal grid.
"""
function _freudenthal_simplex(x::Vector{Float64})
    n = length(x)
    V = Vector{Vector{Int}}(undef, n+1)
    V[1] = floor.(Int, x)
    d = x - V[1]
    p = sortperm(d, rev=true)
    for i in 2 : n+1
        V[i] = copy(V[i-1])
        V[i][p[i-1]] += 1
    end
    return V
end

"""
    _barycentric_coordinates(x::Vector{Float64}, V::Vector{Vector{Int64}})
Given a point `x` and its simplex `V` in the Freudenthal grid, returns the barycentric coordinates
of `x` in the grid. `V` must be in the same order as provided by the output of `freudenthal_simplex`
"""
function _barycentric_coordinates(x::Vector{Float64}, V::Vector{Vector{Int64}})
    d = x - V[1]
    p = sortperm(d, rev=true)
    n = length(x)
    λ = Vector{Float64}(undef, n+1)
    λ[n+1] = d[p[n]]
    for i in n:-1:2
        λ[i] = d[p[i-1]] - d[p[i]]
    end
    λ[1] = 1.0 - sum(λ[2:end])
    return λ
end

"""
    simplex(T::FreudenthalTriangulation, x::Vector{Float64})
Given a point `x`, returns the simplex of the point `x` and the barycentric coordinates of `x` in the grid.
"""
function simplex(T::FreudenthalTriangulation, x::Vector{Float64})
    V = _freudenthal_simplex(x)
    return V, _barycentric_coordinates(x, V)
end

"""
    _to_belief(x)
Transform a point `x` in the Freudenthal space to a point in the belief space.
"""
_to_belief(x) = (push!(x[1:end-1] - x[2:end], x[end]))./x[1]

"""
    _to_freudenthal(b, m::Int64)
Transform a point `b` in the belief space to a point in the Freudenthal space.
`m` is the resolution of the Freudenthal grid.
"""
_to_freudenthal(b, m::Int64) = [sum(b[k] for k in i : length(b))*m for i in 1 : length(b)]

"""
    belief_vertices(T::FreudenthalTriangulation)
    Converts the list of Freudenthal vertices with dimension `T.n` and granularity `T.m` into
    belief space and returns the list
"""
belief_vertices(T::FreudenthalTriangulation) = _to_belief.(vertices(T))

"""
    belief_simplex(T::FreudenthalTriangulation, b)
    Constructs the belief simplex surrounding a belief `b` and the barycentric coordinates
    corresponding to the location of the belief within the simplex
"""
function belief_simplex(T::FreudenthalTriangulation, b)
    x = _to_freudenthal(b, T.m)
    V, λ = simplex(T, x)
    B = _to_belief.(V)
    valid =  λ .> sqrt(eps())
    return B[valid], λ[valid]
end

end # module


# This file is a part of AstroLib.jl. License is MIT "Expat".
# Copyright (C) 2016 Mosè Giordano.

function _flux2mag(flux::T, zero_point::T, ABwave::T) where {T<:AbstractFloat}
    if isnan(ABwave)
        return -2.5*log10(flux) - zero_point
    else
        return -2.5*log10(flux) - 5 * log10(float(ABwave)) - 2.406
    end
end

"""
    flux2mag(flux[, zero_point, ABwave=number]) -> magnitude

### Purpose ###

Convert from flux expressed in erg/(s cm² Å) to magnitudes.

### Explanation ###

This is the reverse of `mag2flux`.

### Arguments ###

* `flux`: the flux to be converted in magnitude, expressed in
  erg/(s cm² Å).
* `zero_point`: the zero point level of the magnitude.  If not
 supplied then defaults to 21.1 (Code et al 1976).  Ignored if the `ABwave`
 keyword is supplied
* `ABwave` (optional numeric keyword): wavelength in Angstroms.
 If supplied, then returns Oke AB magnitudes (Oke & Gunn 1983, ApJ, 266, 713;
 http://adsabs.harvard.edu/abs/1983ApJ...266..713O).

### Output ###

The magnitude.

If the `ABwave` keyword is set then magnitude is given by the expression

\$\$\\text{ABmag} = -2.5\\log_{10}(f) - 5\\log_{10}(\\text{ABwave}) - 2.406\$\$

Otherwise, magnitude is given by the expression

\$\$\\text{mag} = -2.5\\log_{10}(\\text{flux}) - \\text{zero point}\$\$

### Example ###

```jldoctest
julia> using AstroLib

julia> flux2mag(5.2e-15)
14.609991640913002

julia> flux2mag(5.2e-15, 15)
20.709991640913003

julia> flux2mag(5.2e-15, ABwave=15)
27.423535345634598
```

### Notes ###

Code of this function is based on IDL Astronomy User's Library.
"""
flux2mag(flux::Real, zero_point::Real=21.1; ABwave::Real=NaN) =
    _flux2mag(promote(float(flux), float(zero_point), float(ABwave))...)


using DifferentialEquations
using Plots
gr()

function lif(u,p,t);
    gL, EL, C, Vth, I = p
    (-gL*(u-EL)+I)/C
end

function thr(u,t,integrator)
    integrator.u > integrator.p[4]
end

function reset!(integrator)
    integrator.u = integrator.p[2]
end

threshold = DiscreteCallback(thr,reset!)
current_step= PresetTimeCallback([2,15],integrator -> integrator.p[5] += 210.0)
cb = CallbackSet(current_step,threshold)

u0 = -75
tspan = (0.0, 40.0)
# p = (gL, EL, C, Vth, I)
p = [10.0, -75.0, 5.0, -55.0, 0]

prob = ODEProblem(lif, u0, tspan, p, callback=cb)

sol = solve(prob)

plot(sol)

#Izhikevichch Model
using DifferentialEquations
using Plots

function izh!(du,u,p,t);
    a, b, c, d, I = p

    du[1] = 0.04*u[1]^2+5*u[1]+140-u[2]+I
    du[2] = a*(b*u[1]-u[2])
end

function thr(u,t,integrator)
    integrator.u[1] >= 30
end

function reset!(integrator)
    integrator.u[1] = integrator.p[3]
    integrator.u[2] += integrator.p[4]
end

threshold = DiscreteCallback(thr,reset!)
current_step= PresetTimeCallback(50,integrator -> integrator.p[5] += 10)
cb = CallbackSet(current_step,threshold)

p = [0.02, 0.2, -50, 2, 0]
u0 = [-65, p[2]*-65]
tspan = (0.0, 300)

prob = ODEProblem(izh!, u0, tspan, p, callback=cb)

sol = solve(prob);
plot(sol, vars=1)

plot(sol, vars=2)

p = [0.02, 0.2, -65, 8, 0]
u0 = [-65, p[2]*-65]
tspan = (0.0, 300)

prob = ODEProblem(izh!, u0, tspan, p, callback=cb)
sol = solve(prob);
plot(sol, vars=1)

using DifferentialEquations
using Plots

# Potassium ion-channel rate functions
alpha_n(v) = (0.02 * (v - 25.0)) / (1.0 - exp((-1.0 * (v - 25.0)) / 9.0))
beta_n(v) = (-0.002 * (v - 25.0)) / (1.0 - exp((v - 25.0) / 9.0))

# Sodium ion-channel rate functions
alpha_m(v) = (0.182*(v + 35.0)) / (1.0 - exp((-1.0 * (v + 35.0)) / 9.0))
beta_m(v) = (-0.124 * (v + 35.0)) / (1.0 - exp((v + 35.0) / 9.0))

alpha_h(v) = 0.25 * exp((-1.0 * (v + 90.0)) / 12.0)
beta_h(v) = (0.25 * exp((v + 62.0) / 6.0)) / exp((v + 90.0) / 12.0)

function HH!(du,u,p,t);
    gK, gNa, gL, EK, ENa, EL, C, I = p
    v, n, m, h = u

    du[1] = (-(gK * (n^4.0) * (v - EK)) - (gNa * (m ^ 3.0) * h * (v - ENa)) - (gL * (v - EL)) + I) / C
    du[2] = (alpha_n(v) * (1.0 - n)) - (beta_n(v) * n)
    du[3] = (alpha_m(v) * (1.0 - m)) - (beta_m(v) * m)
    du[4] = (alpha_h(v) * (1.0 - h)) - (beta_h(v) * h)
end

current_step= PresetTimeCallback(100,integrator -> integrator.p[8] += 1)

# n, m & h steady-states
n_inf(v) = alpha_n(v) / (alpha_n(v) + beta_n(v))
m_inf(v) = alpha_m(v) / (alpha_m(v) + beta_m(v))
h_inf(v) = alpha_h(v) / (alpha_h(v) + beta_h(v))

p = [35.0, 40.0, 0.3, -77.0, 55.0, -65.0, 1, 0]
u0 = [-60, n_inf(-60), m_inf(-60), h_inf(-60)]
tspan = (0.0, 1000)

prob = ODEProblem(HH!, u0, tspan, p, callback=current_step)

sol = solve(prob);
plot(sol, vars=1)

plot(sol, vars=[2,3,4], tspan=(105.0,130.0))

function gSyn(max_gsyn, tau, tf, t);
    if t-tf >= 0
        return max_gsyn * exp(-(t-tf)/tau)
    else
        return 0.0
    end
end
function HH!(du,u,p,t);
    gK, gNa, gL, EK, ENa, EL, C, I, max_gSyn, ESyn, tau, tf = p
    v, n, m, h = u

    ISyn = gSyn(max_gSyn, tau, tf, t) * (v - ESyn)

    du[1] = (-(gK * (n^4.0) * (v - EK)) - (gNa * (m ^ 3.0) * h * (v - ENa)) - (gL * (v - EL)) + I - ISyn) / C
    du[2] = (alpha_n(v) * (1.0 - n)) - (beta_n(v) * n)
    du[3] = (alpha_m(v) * (1.0 - m)) - (beta_m(v) * m)
    du[4] = (alpha_h(v) * (1.0 - h)) - (beta_h(v) * h)
end

p = [35.0, 40.0, 0.3, -77.0, 55.0, -65.0, 1, 0, 0.008, 0, 20, 100]
tspan = (0.0, 200)
prob = ODEProblem(HH!, u0, tspan, p)
sol = solve(prob);
plot(sol, vars=1)

p = [35.0, 40.0, 0.3, -77.0, 55.0, -65.0, 1, 0, 0.01, 0, 20, 100]
tspan = (0.0, 200)
prob = ODEProblem(HH!, u0, tspan, p)
sol = solve(prob);
plot!(sol, vars=1)

function HH!(du,u,p,t);
    gK, gNa, gL, EK, ENa, EL, C, I, tau, tau_u, tau_R, u0, gmax, Esyn  = p
    v, n, m, h, u, R, gsyn = u

    du[1] = ((gK * (n^4.0) * (EK - v)) + (gNa * (m ^ 3.0) * h * (ENa - v)) + (gL * (EL - v)) + I + gsyn * (Esyn - v)) / C
    du[2] = (alpha_n(v) * (1.0 - n)) - (beta_n(v) * n)
    du[3] = (alpha_m(v) * (1.0 - m)) - (beta_m(v) * m)
    du[4] = (alpha_h(v) * (1.0 - h)) - (beta_h(v) * h)

    # Synaptic variables
    du[5] = -(u/tau_u)
    du[6] = (1-R)/tau_R
    du[7] = -(gsyn/tau)
end

function epsp!(integrator);
    integrator.u[5] += integrator.p[12] * (1 - integrator.u[5])
    integrator.u[7] += integrator.p[13] * integrator.u[5] * integrator.u[6]
    integrator.u[6] -= integrator.u[5] * integrator.u[6]

end

epsp_ts= PresetTimeCallback(100:100:500, epsp!)

p = [35.0, 40.0, 0.3, -77.0, 55.0, -65.0, 1, 0, 30, 1000, 50, 0.5, 0.005, 0]
u0 = [-60, n_inf(-60), m_inf(-60), h_inf(-60), 0.0, 1.0, 0.0]
tspan = (0.0, 700)
prob = ODEProblem(HH!, u0, tspan, p, callback=epsp_ts)
sol = solve(prob);
plot(sol, vars=1)

plot(sol, vars=7)

plot(sol, vars=[5,6])

epsp_ts= PresetTimeCallback(100:1000:5100, epsp!)

p = [35.0, 40.0, 0.3, -77.0, 55.0, -65.0, 1, 0, 30, 500, 50, 0.5, 0.005, 0]
u0 = [-60, n_inf(-60), m_inf(-60), h_inf(-60), 0.0, 1.0, 0.0]
tspan = (0.0, 5300)
prob = ODEProblem(HH!, u0, tspan, p, callback=epsp_ts)
sol = solve(prob);
plot(sol, vars=7)

plot(sol, vars=[5,6])

epsp_ts= PresetTimeCallback(100:100:500, epsp!)

p = [35.0, 40.0, 0.3, -77.0, 55.0, -65.0, 1, 0, 30, 100, 1000, 0.5, 0.005, 0]
u0 = [-60, n_inf(-60), m_inf(-60), h_inf(-60), 0.0, 1.0, 0.0]
tspan = (0.0, 700)
prob = ODEProblem(HH!, u0, tspan, p, callback=epsp_ts)
sol = solve(prob);
plot(sol, vars=7)

plot(sol, vars=[5,6])

using SciMLTutorials
SciMLTutorials.tutorial_footer(WEAVE_ARGS[:folder],WEAVE_ARGS[:file])


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

"""
    CubicVariogram(range=r, sill=s, nugget=n)
    CubicVariogram(ball; sill=s, nugget=n)

A cubic variogram with range `r`, sill `s` and nugget `n`.
Optionally, use a custom metric `ball`.
"""
struct CubicVariogram{V,B} <: Variogram
  sill::V
  nugget::V
  ball::B
end

CubicVariogram(ball; sill=1.0, nugget=zero(typeof(sill))) =
  CubicVariogram(sill, nugget, ball)

CubicVariogram(; range=1.0, sill=1.0, nugget=zero(typeof(sill))) =
  CubicVariogram(sill, nugget, MetricBall(range))

function (γ::CubicVariogram)(h::T) where {T}
  r = radius(γ.ball)
  s = γ.sill
  n = γ.nugget

  # constants
  c1 = T(35) / T(4)
  c2 = T(7)  / T(2)
  c3 = T(3)  / T(4)
  s1 = 7*(h/r)^2 - c1*(h/r)^3 + c2*(h/r)^5 - c3*(h/r)^7
  s2 = T(1)

  (h < r) * (s - n) * s1 +
  (h ≥ r) * (s - n) * s2 +
  (h > 0) * n
end

isstationary(::Type{<:CubicVariogram}) = true


export leaky_relu

doc"""
    leaky_relu(x::Var, alpha=0.1)

Leaky Rectified Linear Unit.

```math
f(x) =
\begin{cases}
x & x > 0 \\
\alpha x & x \leq 0
\end{cases}
```

# References
* Maas et al., ["Rectifier Nonlinearities Improve Neural Network Acoustic Models"](http://web.stanford.edu/~awni/papers/relu_hybrid_icml2013_final.pdf), ICML 2013.
"""
leaky_relu(x::Var, alpha=0.1) = Var(leaky_relu.(x.data,eltype(x)(alpha)), (leaky_relu,x,alpha))
leaky_relu(x::T, alpha::T) where T = x >= zero(T) ? x : x*alpha
leaky_relu(x::Node, alpha=0.1) = Node(leaky_relu, x, alpha)

function addgrad!(y::Var, ::typeof(leaky_relu), x::Var, alpha::Float64)
    isvoid(x.grad) && return
    ∇leaky_relu!(y.grad, x.data, x.grad, eltype(x)(alpha))
end

function ∇leaky_relu!(gy::Array{T}, x::Array{T}, gx::Array{T}, alpha::T) where T
    @inbounds for i = 1:length(x)
        gx[i] += x[i] >= zero(T) ? gy[i] : gy[i]*alpha
    end
end


function nlsolve(f, j, x, iterations = 10^5, r_norm = norm, r_abstol = sqrt(eps(eltype(x))))
    xnext = x
    r = f(x)
    for i = 1:iterations
        if r_norm(r) <= r_abstol
            return r, xnext, :success
        end
        x = copy(xnext)
        s = - j(x)\r
        xnext = x + s
        r = f(xnext)
    end
    if r_norm(r) <= r_abstol
        return r, xnext, :success
    end
    r, xnext, :failure
end


function alphaj(a::Complex128)
    return sqrt(1 - abs(a)^2)
end

function dictionaryElement(z::Complex128,a::Complex128)
    return alphaj(a)/(z-a)
end

function blaschkeElement(z::Complex128,a::Complex128)
    return (1-conj(a)*z)/(z-a)
end

function invBlaschkeElement(z::Complex128,a::Complex128)
    return (z-a)/(1-conj(a)*z)
end

function blaschkeProd(z::Complex128,aj::Vector{Complex128})
    r = one(z)
    for i=1:length(aj)
        r *= blaschkeElement(z,aj[i])
    end
    return r
end

function blaschkeProdExceptQ(z::Complex128,aj::Vector{Complex128},q::Int)
    r = one(z)
    for i=1:length(aj)
        if i!=q
            r *= blaschkeElement(z,aj[i])
        end
    end
    return r
end

function impulseTakenakaMalmquist(n,aj::Vector{Complex128})
    if n<=zero(n)
        return zero(Complex128)
    end
    temp1 = zero(Complex128)
    temp2 = zero(Complex128)
    for i=1:(length(aj)-1)
        temp1 += aj[i]^(n-1)*dictionaryElement(aj[i],aj[end])*blaschkeProdExceptQ(aj[i],aj[1:(length(aj)-1)],i)*(1-abs(aj[i])^2)
    end
    temp2 += aj[end]^(n-1)*alphaj(aj[end])*blaschkeProd(aj[end],aj[1:(length(aj)-1)])
    return temp1 + temp2
end


# coding: utf-8

# In[1]:

#global list is the key
#otherwise we will lose the list throughout iterations
global factors=[]

# In[2]:

function factorization(num)

    #negative and float should be excluded
    if (num<0) || !(isinteger(num))

        error("negative or float is not allowed")

    end

    #if n is smaller than 4
    #prime number it is
    if num>4

        #exclude 1 and itself
        #the largest factor of n can not exceed the half of n
        #because 2 is the smallest factor
        #the range of factors we are gonna try starts from 2 to fld(num,2)+1
        #int function to solve the odd number problem
        for i in 2:(fld(num,2)+1)

            #the factorization process
            if num%i==0

                push!(factors,i)

                #return is crucial
                #if the number is not a prime number
                #it will stop function from appending non-prime factors
                #the next few lines will be ignored
                return factorization(Int32(num/i))

            end

        end

    end

    #append the last factor
    #it could be n itself if n is a prime number
    #in that case there is only one element in the list
    #or it could be the last indivisible factor of n which is also a prime number
    push!(factors,num)

    if length(factors)==1

        println(num," is a prime number")
        empty!(factors)

    end

end

# In[3]:

factorization(71392643)

# In[4]:

print(factors)


using EngEconomics

# Given
# 12 months
# month1 - 15000 cost
# g = 5% per month
# grant is received in 6 monthy installments starting at month 2
# i = 12%

n = 1
m = 12
i = 0.12
nc = n * m
ic = i / m
Ac = 15000
g = 0.05
F = Ac * presentWorthConversionFactorGeometric(g, ic, nc) * compoundAmountFactor(ic, nc)
Ag = F * capitalRecoveryFactor(ic, 6)


###########################################################################################################
###########################################################################################################
###########################################################################################################
###########################################################################################################
###########################################################################################################
###########################################################################################################
#                                         RADIAL COEFFICIENTS                                             #
###########################################################################################################
###########################################################################################################
###########################################################################################################
###########################################################################################################
###########################################################################################################
###########################################################################################################
export SlaterON, SlaterSF, SlaterSA, SlaterSB, SlaterSN, SlaterSX
###########################################################################################################
########################################## SLATER TYPE ORBITALS ###########################################
SlaterON(n::arb, ζ::arb) = ((2*ζ)^(n + 1//2))//Sqrt(Gamma(2*n + One(n)))
SlaterON(n::Real, ζ::Real) = SlaterON(RF(n), RF(ζ))

function SlaterON(n1::arb, n2::arb, ρ::arb, τ::arb)
    res1 = ((ρ*(One(τ)+τ))^(n1 + 1//2)) * ((ρ*(One(τ)-τ))^(n2 + 1//2))
    res2 = Sqrt(Gamma(2*n1 + One(n1)) * Gamma(2*n2 + One(n2)))
    res1 // res2
end
SlaterON(n1::Real, n2::Real, ρ::Real, τ::Real) = SlaterON(RF(n1), RF(n2), RF(ρ), RF(τ))
###########################################################################################################
###########################################################################################################
###########################################################################################################
#                                     SLATER TYPE SPINOR ORBITALS                                         #
###########################################################################################################
###########################################################################################################
###########################################################################################################
########################################## NORMALIZATION FACTOR ###########################################
################# Relativistic normalization factor N_{n\kappa}
SlaterSF(n::arb, κ::Int) = ifelse(κ < 0, Abs(κ), Sqrt(κ^2 + (2*n + 1)))
SlaterSF(n::Real, κ::Int) = SlaterSF(RF(n), κ)
###########################################################################################################
############################################## COEFFICIENT A ##############################################
################# Normalization coeeficients so called A^{\beta}_{n\kappa}
SlaterSA(β::Int, n::arb, κ::Int) = ifelse(κ < 0, (β*κ)//(2*n), (β//2)*(SlaterSF(n, κ)-κ) - (RF(1)//RF(2)))
SlaterSA(β::Int, n::Real, κ::Int) = SlaterSA(β, RF(n), RF(κ))
###########################################################################################################
############################################## COEFFICIENT B ##############################################
################# Normalization coeeficients so called B^{\beta}_{n\kappa}
SlaterSB(β::Int, n::arb, κ::Int) = ifelse(κ < 0, Zero(RF), -β*((SlaterSF(n, κ) - κ)//(2*n + 1)))
SlaterSB(β::Int, n::Real, κ::Int) = SlaterSB(β, RF(n), RF(κ))
###########################################################################################################
############################################## COEFFICIENT N ##############################################
################# 1/Sqrt(N) part, the dominator is used for both wave and charge density
function SlaterSN(β::Int, n::arb, κ::Int)
    res1 = SlaterSA(β, n, κ) * SlaterSA(β, n, κ) * Gamma(2*n + RF(1))
    res2 = SlaterSA(β, n, κ) * SlaterSB(β, n, κ) * Gamma(2*n + RF(2))
    res3 = SlaterSB(β, n, κ) * SlaterSB(β, n, κ) * (Gamma(2*n + RF(3)) // 4)
    res1 + res2 + res3
end
SlaterSN(β::Int, n::Real, κ::Int) = SlaterSN(β, RF(n), κ)

SlaterSN(β::Int, n::arb, κ::Int, ζ::arb) = ((2*ζ)^(n + 1//2))//Sqrt(SlaterSN(β, n, κ))
SlaterSN(β::Int, n::Real, κ::Int, ζ::Real) = SlaterSN(β, RF(n), κ, RF(ζ))

function SlaterON(β1::Int, n1::arb, κ1::Int, β2::Int, n2::arb, κ2::Int, ρ::arb, τ::arb)
    res1 = ((ρ*(One(τ)+τ))^(n1 + 1//2)) * ((ρ*(One(τ)-τ))^(n2 + 1//2))
    res2 = Sqrt(SlaterSN(β1, n1, κ1) * SlaterSN(β2, n2, κ2))
    res1 // res2
end
SlaterSN(β1::Int, n1::Real, κ1::Int, β2::Int, n2::Real, κ2::Int, ρ::Real, τ::Real) =
SlaterSN(β1, RF(n1), κ1, β2, RF(n2), κ2, RF(ρ), RF(τ))

function SlaterSX(β1::Int, n1::arb, κ1::Int, ζ1::arb, β2::Int, n2::arb, κ2::Int, ζ2::arb)
    a1 = SlaterSA(β1, n1, κ1)
    a2 = SlaterSA(β2, n2, κ2)
    b1 = SlaterSB(β1, n1, κ1)
    b2 = SlaterSB(β2, n2, κ2)

    x1 = ζ1 * ζ1 * a1 * a2
    x2 = ζ1 * ζ2 * b1 * a2
    x3 = ζ1 * ζ2 * a1 * b2
    x4 = ζ2 * ζ2 * b1 * b2

    x = [x1, x2, x3, x4]
    x = transpose(x)
    return x
end
SlaterSX(β1::Int, n1::Real, κ1::Int, ζ1::Real, β2::Int, n2::Real, κ2::Int, ζ2::Real) =
SlaterSX(β1, RF(n1), κ1, RF(ζ1), β2, RF(n2), κ2, RF(ζ2))


function primitiven{T<:Integer}(m::T)
    1 < m || return T[]
    m != 2 || return T[1]
    !isprime(m) || return T[2:2:m-1]
    rp = trues(m-1)
    if isodd(m)
        rp[1:2:m-1] = false
    end
    for p in keys(factor(m))
        rp[p:p:m-1] = false
    end
    T[1:m-1][rp]
end

function pythagoreantripcount{T<:Integer}(plim::T)
    primcnt = 0
    fullcnt = 0
    11 < plim || return (primcnt, fullcnt)
    for m in 2:plim
        p = 2m^2
        p+2m <= plim || break
        for n in primitiven(m)
            q = p + 2m*n
            q <= plim || break
            primcnt += 1
            fullcnt += div(plim, q)
        end
    end
    return (primcnt, fullcnt)
end

println("Counting Pythagorian Triplets within perimeter limits:")
println("    Limit          All   Primitive")
for om in 1:10
    (pcnt, fcnt) = pythagoreantripcount(10^om)
    println(@sprintf "    10^%02d  %11d   %9d" om fcnt pcnt)
end


function lorentz_likelihood_hermitian_band_save(p,y::Vector{Float64},aex,al_small,indx,logdeta)
# Computes the likelihood of a sum of Exponential/Cosine kernels (which have a
# Lorentzian power spectrum) utilizing the approach of Ambikasaran (2015)
#   Numer. Linear Algebra Appl. 2015; 22:1102-1114 DOI: 10.1002/nla
# Uses (2.30) from Rasmussen & Williams (2006) to compute the log likelihood.
# The form of the kernel is:
#
#   K(t_i,t_j) = w_i \delta_{ij} + \sum_{k=1}^{p} \alpha_k \exp{-\beta_k |t_i-t_j|}
#
# This is represented by the matrix A_{ij} = K(t_i,t_j).
# The log likelihood is given by:
# ln(L) = -1/2 y^T A^{-1} y - 1/2 |det(A)|
# Note: if a \beta_k is complex, then its complex conjugate *must* be included as well.
#
# First, define band-diagonal matrix for kernel:
# [x_i,{r_{i,1},...,r_{i,p}},{l_{i,1},...,l_{i,p}}]

#tic()
n = length(y)
# There are p+1 sub-diagonals, p+1 super-diagonals + diagonal
# for a total of 2*p+3 non-zero diagonals:
nex = (2p+1)*n-2p
bex = zeros(Complex{Float64},nex)
for i=1:n
# Compute actual indices:
  irow =(i-1)*(1+2p)+1
  bex[irow] = y[i]
# Diagonal noise:
  for j=1:p
    if i < n
      bex[irow+  j] = 0.0
      bex[irow+p+j] = 0.0
    end
  end
end

# Specify the number of bands below & above the diagonal:
m1 = p+1
m2 = p+1
# Solve the equation A^{-1} y = b using band-diagonal LU back-substitution on
# the extended equations: A_{ex}^{-1} y_{ex} = b_{ex}:
banbks(aex,nex,m1,m2,al_small,indx,bex)
# Now select solution to compute the log likelihood:
# The equation A^{-1} y = b has been solved in bex (the extended vector).
# So, I need to pick out the b portion from bex, and take the dot product
# with y (which is the residuals of the data minus model, which is correlated
# noise that we are modeling with the multi-Lorentzian covariance function):
log_like = 0.0
for i=1:n
  log_like += real(bex[(i-1)*(2p+1)+1])*y[i]
end
# Convert this to log likelihood:
log_like = -0.5*log_like
return log_like
end


module PhysicalOceanography

export f, β

Ω = 7.2921 * 10^(-5) #rad/s
R = 6.3781 * 10^(6) #m

"""
    f(φ)

Coriolis Parameter
"""
f(φ) = 2Ω * sind(φ)

"""
    β(φ)

Rossby Parameter
"""
β(φ) = 2Ω / R * cosd(φ)

end


"""
    eom_anishchenko1(u, p, t)

Equations of motion for the system defined by eq. 13 in [1], which can
give strange, nonchaotic attractors.

## Equations of motion

The equations of motion are

```math
\\begin{aligned}
dx &= \\alpha (1-s \\cos (2 \\pi \\phi )) \\cdot x(1-x) \\\\
dϕ &= (\\phi + \\omega ) \\mod{1}
\\end{aligned}
```

## References

1. Anishchenko, Vadim S., and Galina I. Strelkova. "Irregular attractors."
    Discrete dynamics in Nature and Society 2.1 (1998): 53-72.
"""
function eom_anishchenko1(u, p, t)
    x, ϕ = (u...,)
    α, s, ω = (p...,)
    dx = α*(1 - s*cos(2*pi*ϕ))*x*(1 - x)
    dϕ = (ϕ + ω) % 1

    return SVector{2}(dx, dϕ)
end

function anishchenko1(u₀, α, s, ω)
    p = [α, s, ω]
    DiscreteDynamicalSystem(eom_anishchenko1, u₀, p)
end

"""
    anishchenko1(;u₀ = rand(2), α =3.277, s=0.1, ω=0.5*(sqrt(5)-1))

Initialise the system defined by eq. 13 in [1], which can give strange,
nonchaotic attractors.

## Equations of motion

The equations of motion are

```math
\\begin{aligned}
dx &= \\alpha (1-s \\cos (2 \\pi \\phi )) \\cdot x(1-x) \\\\
dϕ &= (\\phi + \\omega ) \\mod{1}
\\end{aligned}
```

## References

1. Anishchenko, Vadim S., and Galina I. Strelkova. "Irregular attractors."
    Discrete dynamics in Nature and Society 2.1 (1998): 53-72.
"""
anishchenko1(;u₀ = rand(2), α =3.277, s=0.1, ω=0.5*(sqrt(5)-1)) =
    anishchenko1(u₀, α, s, ω)


module mlmetrics
using  Statistics

"""
    struct mse{T<:AbstractFloat}
        y::Vector
        ŷ::Vector
    end
    The mean squared error is the sum of the squared differences between the
    predicted and observed values.

    struct mae{T<:AbstractFloat}
        y::Vector
        ŷ::Vector
    end
    The mean absolute error is the sum of the absolute differences between the
    predicted and observed values.

    struct rmse{T<:AbstractFloat}
        y::Vector
        ŷ::Vector
    end
    The root mean squared error is the square root of the mean squared error.

    struct rmae{T<:AbstractFloat}
        y::Vector
        ŷ::Vector
    end
    The root mean absolute error is the square root of the mean absolute error.

    struct rmsle{T<:AbstractFloat}
        y::Vector
        ŷ::Vector
    end
    The root mean squared log error is the square root of the mean squared log error.

    struct r2score{T<:AbstractFloat}
        y::Vector
        ŷ::Vector
    end
    The R2 is a statistic that will give some information about the goodness of fit of a model.
"""

struct mse{T<:AbstractFloat}
    y::Vector
    ŷ::Vector

    function mse(y, ŷ)
        return mean((y - ŷ).^2)
    end
end

struct mae{T<:AbstractFloat}
    y::Vector
    ŷ::Vector

    function mae(y, ŷ)
        return mean(abs.(y - ŷ))
    end
end

struct rmse{T<:AbstractFloat}
    y::Vector
    ŷ::Vector

    function rmse(y, ŷ)
        return sqrt(mse(y, ŷ))
    end
end

struct rmae{T<:AbstractFloat}
    y::Vector
    ŷ::Vector

    function rmae(y, ŷ)
        return sqrt(mae(y, ŷ))
    end
end

struct rmsle{T<:AbstractFloat}
    y::Vector
    ŷ::Vector

    function rmsle(y, ŷ)
        return sqrt(mean((log.(y .+ 1) - log.(ŷ .+ 1)).^2))
    end
end

struct r2score{T<:AbstractFloat}
    y::Vector
    ŷ::Vector

    function r2score(y, ŷ)
        squares = sum(y.^2)
        residuals = sum(y - ŷ).^2
        return 1 - residuals/squares
    end
end

struct binarycrossentropy{T<:AbstractFloat}
    y::Vector
    ŷ::Vector

    function binarycrossentropy(y::Vector, ŷ::Vector)
        ε = 1e-10
        return -mean(y.*log2.(ŷ .+ ε) + (1 .- y).*log2.(1 .- ŷ .+ ε))
    end
end

struct accuracy{T<:AbstractFloat}
    y_true::AbstractArray
    y_predict::AbstractArray

    function accuracy(y_true::AbstractArray, y_predict::AbstractArray)
        return sum(y_true.==y_predict)/length(y_true)
    end

end

struct precision{T<:AbstractFloat}
    y_true::AbstractArray
    y_predict::AbstractArray

    function precision(y_true::AbstractArray, y_predict::AbstractArray)
        true_positives = sum(y_true.== 1 .== y_predict)
        true_negatives = sum(y_true.== 0 .== y_predict)
        false_positives = sum(y_true.!= 1 .== y_predict)
        false_negatives = sum(y_true.!= 0 .== y_predict)

        return true_positives/(true_positives + false_positives)
    end

end

struct recall{T<:AbstractFloat}
    y_true::AbstractArray
    y_predict::AbstractArray

    function recall(y_true::AbstractArray, y_predict::AbstractArray)
        true_positives = sum(y_true.== 1 .== y_predict)
        true_negatives = sum(y_true.== 0 .== y_predict)
        false_positives = sum(y_true.!= 1 .== y_predict)
        false_negatives = sum(y_true.!= 0 .== y_predict)

        return true_positives/(true_positives + false_negatives)
    end

end

struct f1_score{T<:AbstractFloat}
    y_true::AbstractArray
    y_predict::AbstractArray

    function f1_score(y_true::AbstractArray, y_predict::AbstractArray)
        num =  2 * precision(y_true, y_predict) * recall(y_true, y_predict)
        den = precision(y_true, y_predict) + recall(y_true, y_predict)
        return num/den
    end

end

end


using EngEconomics, Roots

# Given
# a = parasailing
# b = kayaking
aInit = -100000
aAnnual = 15000
aN = 15

bInit = -10000
bAnnual = 2000
bN = 15
MARR = 0.10

N = aN

# Determine the better alternative
ΔInit = aInit - bInit
ΔAnnual = aAnnual - bAnnual
ΔIRR(x) = ΔInit + ΔAnnual * seriesPresentAmountFactor(x, N)
ΔIRR_ans = find_zero(ΔIRR, 0.05)

if ΔIRR_ans > MARR
	println("Choose Parasailing")
else
	println("Choose Kayaking")
end


# stoichiometric matrix: fluxes (in order) are v1-v6 (v6 is reverse of v5), b1-b20
S = [-1 0 0 1 2 -2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; #citrulline
     -1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0;  #aspartate
     1 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; #arginosuccinate
     0 1 0 0 0 0 0 0 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; #fumarate
     0 1 -1 0 -2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; #arginine
     0 0 1 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0;  #ornithine
     0 0 1 0 0 0 0 0 0 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; #urea
     0 0 0 -1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; #carbamoyl phosphate
     -1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; #ATP
     1 0 0 0 0 0 0 0 0 0 0 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0; #AMP
     1 0 0 0 0 0 0 0 0 0 0 0 -1 0 0 0 0 0 0 0 0 0 0 0 0 0; #diphosphate
     0 0 0 1 0 0 0 0 0 0 0 0 0 -1 0 0 0 0 0 0 0 0 0 0 0 0; #phosphate
     0 0 0 0 -3 3 0 0 0 0 0 0 0 0 1 -1 0 0 0 0 0 0 0 0 0 0; #NADPH
     0 0 0 0 -3 3 0 0 0 0 0 0 0 0 0 0 1 -1 0 0 0 0 0 0 0 0; #H+
     0 0 0 0 -4 4 0 0 0 0 0 0 0 0 0 0 0 0 1 -1 0 0 0 0 0 0; #O2
     0 0 0 0 2 -2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 -1 0 0 0 0; #NO2
     0 0 0 0 3 -3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 -1 0 0; #NADP+
     0 0 -1 0 4 -4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 -1]; #H2O

#atom matrix: C H N O P S
A = [6 13 3 3 0 0; #citrulline
    4 7 1 4 0 0;  #aspartate
    10 18 4 6 0 0; #arginosuccinate
    4 4 0 4 0 0; #fumarate
    6 14 4 2 0 0; #arginine
    5 12 2 2 0 0; #ornithine
    1 4 2 1 0 0; #urea
    1 4 1 5 1 0; #carbamoyl phosphate
    10 16 5 13 3 0; #ATP
    10 14 5 7 1 0;  #AMP
    0 4 0 7 2 0;  #diphosphate
    0 3 0 4 1 0;  #phosphate
    21 30 7 17 3 0; #NADPH
    0 1 0 0 0 0; #H+
    0 0 0 2 0 0; #O2
    0 0 1 1 0 0; #NO
    21 29 7 17 3 0; #NADP+
    0 2 0 1 0 0] #H2O

e = transpose(A)*S

#look at only the balances for v1-v6, species are not balanced over boundaries
print(e[:,1:6])


using Knet, ParticleSwarmOptimization
include("Args.jl")

hosaki(x) = (1 - 8*x[1] + 7*(x[1]^2) - 7/3*(x[1]^3) + (1/4)*(x[1]^4))*(x[2]^2)*exp(-1 * x[2])
#x* = (4,2)
#f(x*) = -2.3458
#julia hosaki.jl --num 10 --epoch 100

function main()
    gpu(false)
    args = parse_commandline()

    problem = RegularProblem(hosaki, 2, [0.0,0.0], [5.0,6.0])
    gbest = pso(problem, args["num"], args["epoch"], args["phi1"], args["phi2"], args["w"], args["vmin"], args["vmax"])

    println("\nBest: $(gbest.pbest_fitness), $(gbest.x)")
end

main()


# ---
# title: 241. Different Ways to Add Parentheses
# id: problem241
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Divide and Conquer
# link: <https://leetcode.com/problems/different-ways-to-add-parentheses/description/>
# hidden: true
# ---
#
# Given a string of numbers and operators, return all possible results from
# computing all the different possible ways to group numbers and operators. The
# valid operators are `+`, `-` and `*`.
#
# **Example 1:**
#
#
#
#     Input: "2-1-1"
#     Output: [0, 2]
#     Explanation:
#     ((2-1)-1) = 0
#     (2-(1-1)) = 2
#
# **Example 2:**
#
#
#
#     Input:"2*3-4*5"
#     Output: [-34, -14, -10, -10, 10]
#     Explanation: (2*(3-(4*5))) = -34
#     ((2*3)-(4*5)) = -14
#     ((2*(3-4))*5) = -10
#     (2*((3-4)*5)) = -10
#     (((2*3)-4)*5) = 10 ****
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


function getwraparoundDFTfreqs(N::Int, fs::T, ν_begin::T) where T

    U0 = getDFTfreqrange(N, fs)
    out, inds = wrapfreqrange(U0, ν_begin, fs)

    return U0, out, inds
end

# Assumes U0 is sorted in ascending order.
function wrapfreqrange(U0, ν_begin::T, fs::T) where T <: Real

    N = length(U0)
    ind = findfirst(xx->(xx>ν_begin), U0)

    # TODO handle these exceptions with more grace.
    @assert typeof(ind) == Int
    @assert ind <= N

    out = Vector{T}(undef, N)
    #out[1:ind] = U0[1:ind] .+ fs
    #out[ind+1:end] = U0[ind+1:end]

    M = N-ind
    out[1:M] = U0[ind+1:end]
    out[M+1:end] = U0[1:ind] .+ fs

    inds = collect(1:N)
    inds[1:M] = collect(ind+1:N)
    inds[M+1:end] = collect(1:ind)

    return out, inds
end

function gettimerange(N::Int, fs::T) where T
    Ts::T = 1/fs

    return zero(T):Ts:(N-1)*Ts
 end

 function getDFTfreqrange(N::Int, fs::T)::LinRange{T} where T
     a = zero(T)
     b = fs-fs/N

     return LinRange(a, b, N)
 end

 # case 1D.
function computeDTFTch3eq29(h, u::T, Λ)::Complex{T} where T <: Real

    # debug_array = zeros(Complex{T}, length(Λ))
    # debug_array2 = zeros(Complex{T}, length(Λ))
    # fs = 1/(Λ[2]-Λ[1])

    running_sum = zero(T)
    for i = 1:length(Λ)
        x = Λ[i]

        running_sum += h[i]*exp(-im*2*π*u*x)

        # # debug.
        # debug_array[i] = h[i]*exp(-im*2*π*u*x)
        #
        # #debug_array2[i] = h[i]*exp(-im*2*π*u*x +im*35*2*pi)
        # debug_array2[i] = h[i]*exp(-im*2*π*(u+3*fs)*x)
    end

    #println("debug: norm(debug_array -debug_array2 ) = ", norm(debug_array -debug_array2 ) )
    return running_sum
end


# # Differentiating a QP wrt a single variable

#md # [![](https://img.shields.io/badge/show-github-579ACA.svg)](@__REPO_ROOT_URL__/docs/src/examples/matrix-inversion-manual.jl)

# Consider the quadratic program

# ```math
# \begin{split}
# \begin{array} {ll}
# \mbox{minimize} & \frac{1}{2} x^T Q x + q^T x \\
# \mbox{subject to} & G x \leq h, x \in \mathcal{R}^2 \\
# \end{array}
# \end{split}
# ```

# where `Q`, `q`, `G` are fixed and `h` is the single parameter.

# In this example, we'll try to differentiate the QP wrt `h`, by finding its
# jacobian by hand (using Eqn (6) of [QPTH article](https://arxiv.org/pdf/1703.00443.pdf))
# and compare the results:
# - Manual compuation
# - Using JuMP and DiffOpt

# Assuming
# ```
# Q = [[4, 1], [1, 2]]
# q = [1, 1]
# G = [1, 1]
# ```
# and begining with a starting value of `h=-1`

# few values just for reference

# | variable | optimal value | note |
# |----|------|-----|
# | x* | [-0.25; -0.75] | Primal optimal |
# | 𝜆∗ | -0.75 | Dual optimal |

# ## Finding Jacobian using matrix inversion
# Lets formulate Eqn (6) of [QPTH article](https://arxiv.org/pdf/1703.00443.pdf) for our QP. If we assume `h` as the only parameter and `Q`,`q`,`G` as fixed problem data - also note that our QP doesn't involves `Ax=b` constraint - then Eqn (6) reduces to
# ```math
# \begin{gather}
#  \begin{bmatrix}
#      Q & g^T \\
#      \lambda^* G & G x^* - h
#  \end{bmatrix}
#  \begin{bmatrix}
#      dx \\
#      d \lambda
#  \end{bmatrix}
#  =
#   \begin{bmatrix}
#    0 \\
#    \lambda^* dh
#    \end{bmatrix}
# \end{gather}
# ```

# Now to find the jacobians $$ \frac{\partial x}{\partial h}, \frac{\partial \lambda}{\partial h}$$
# we substitute `dh = I = [1]` and plug in values of `Q`,`q`,`G` to get
# ```math
# \begin{gather}
#  \begin{bmatrix}
#      4 & 1 & 1 \\
#      1 & 2 & 1 \\
#      -0.75 & -0.75 & 0
#  \end{bmatrix}
#  \begin{bmatrix}
#      \frac{\partial x_1}{\partial h} \\
#      \frac{\partial x_2}{\partial h} \\
#      \frac{\partial \lambda}{\partial h}
#  \end{bmatrix}
#  =
#   \begin{bmatrix}
#    0 \\
#    0 \\
#    -0.75
#    \end{bmatrix}
# \end{gather}
# ```

# Upon solving using matrix inversion, the jacobian is
# ```math
# \frac{\partial x_1}{\partial h} = 0.25, \frac{\partial x_2}{\partial h} = 0.75, \frac{\partial \lambda}{\partial h} = -1.75
# ```

# ## Finding Jacobian using JuMP and DiffOpt

using JuMP
import DiffOpt
import Ipopt

n = 2 # variable dimension
m = 1; # no of inequality constraints

Q = [4. 1.;1. 2.]
q = [1.; 1.]
G = [1. 1.;]
h = [-1.;]   # initial values set

# Initialize empty model

model = Model(() -> DiffOpt.diff_optimizer(Ipopt.Optimizer))
set_silent(model)

# Add the variables

@variable(model, x[1:2])

# Add the constraints.

@constraint(
    model,
    cons[j in 1:1],
    sum(G[j, i] * x[i] for i in 1:2)  <= h[j]
);

@objective(
    model,
    Min,
    1/2 * sum(Q[j, i] * x[i] *x[j] for i in 1:2, j in 1:2) +
    sum(q[i] * x[i] for i in 1:2)
)

# Solve problem

optimize!(model)

# primal solution

value.(x)

# dual solution

dual.(cons)

# set sentivitity

MOI.set(
    model,
    DiffOpt.ForwardInConstraint(),
    cons[1],
    0.0 * index(x[1]) - 1.0,  # to indicate the direction vector to get directional derivatives
)

# Note that `0.0 * index(x[1])` is used to make its type `typeof(0.0 * index(x[1]) - 1.0) <: MOI.AbstractScalarFunction`.
# To indicate different direction to get directional derivative, users should replace `0.0 * index(x[1]) - 1.0` as the form of `dG*x - dh`, where `dG` and `dh` correspond to the elements of direction vectors along `G` and `h` axes, respectively.

# Compute derivatives

DiffOpt.forward(model)

# Query derivative

dx = MOI.get.(
    model,
    DiffOpt.ForwardOutVariablePrimal(),
    x,
)

using Test                                  #src
@test dx ≈ [0.25 ,0.75] atol=1e-4 rtol=1e-4 #src


@inline function two_sum(a::T, b::T) where T<:AbstractFloat
    ab =  a + b
    B  = ab - a
    ε  = (a - (ab - B)) + (b - B)
    return ab, ε
end

@inline function two_diff(a::T, b::T) where T<:AbstractFloat
    ab =  a - b
    B  = ab - a
    ε  = (a - (ab - B)) + (b - B)
    return ab, ε
end

"""
    fast_two_sum(x, y)::(a, b)

{ ab  =  a + b,  ε  = (a - ab) + b }

__unchecked precondition__: abs(a) >= abs(b).
"""
@inline function fast_two_sum(a::T, b::T) where T<:AbstractFloat
     ab =  a + b
     B  = ab - a
     ε  =  b - B
     return ab, ε
end

"""
    fast_two_diff(x, y)::(a, b)

{ ab  =  a - b,  ε  = (a - ab) + b }

__unchecked precondition__: abs(a) >= abs(b).
"""
@inline function fast_two_diff(a::T, b::T) where T<:AbstractFloat
     ab =  a - b
     B  = ab - a
     ε  =  B - b
     return ab, ε
end

@inline function two_prod(a::T, b::T) where T<:AbstractFloat
    ab =  a * b
    ε  = fma(a, b, -ab)
    return ab, ε
end

#=
   Separate an IEEEFloat (fp) into two parts, (hi, lo), assuring
      `fp === hi + lo`, `hi === fp - lo`, `lo === fp - hi`, and
      `ulp(abs(hi)) > ufp(abs(lo))`.

      ulp, ufp are acronyms for u[nit in the] {l[ast], f[irst]} p[lace]

      ulp(fp) = eps(fp)/2
      ufp(fp) = ldexp(0.5, exponent(fp)

   ref
       A floating-point technique for extending the available precision.
       T. J. Dekker.
       Numer. Math., 18:224–242, 1971
=#

splitter(::Type{Float64}) =
    Float64(one(Int64) + one(Int64) << (cld(precision(Float64),2)-1))
splitter(::Type{Float32}) =
    Float32(one(Int32) + one(Int32) << (cld(precision(Float32),2)-1))
splitter(::Type{Float16}) =
    Float16(one(Int16) + one(Int16) << (cld(precision(Float16),2)-1))

splitmax(::Type{Float64}) = realmax(Float64) / splitter(Float64)
splitmax(::Type{Float32}) = realmax(Float32) / splitter(Float32)
splitmax(::Type{Float16}) = realmax(Float16) / splitter(Float16)

# Veldkamp splitting of a floating point value
@inline function splitting(x::T) where T<:AbstractFloat
    (!isfinite(x) || abs(x) > splitmax(T)) && throw(OverflowError("$x overflows"))
    z   = x * splitter(T)
    zₕᵢ = z - (z - x)
    zₗₒ = x - zₕᵢ
    return zₕᵢ, zₗₒ
end

# Rump splitting
#    In extractscalar, a floating-point number fp is split relative to p2,
#    a fixed power of 2.
function extractscalar(fp, p2=2^27)
    hi = (p2 + fp) - p2
    lo = fp - hi
    return hi, lo
end

#=
    splitfp(fp) =?= extractscalar(2^(?+exponent(fp)), fp)

=#

for (U,F) in ((:UInt64, :Float64), (:UInt32, :Float32), (:UInt16, :Float16))
  @eval begin
    @inline function ufp(x::$F)
        u = reinterpret($U, x)
        u = (u >> (precision($F)-1)) << (precision($F)-1)
        return reinterpret($F, u)
    end
  end
end

const Float64ulp = inv(ldexp(1.0, precision(Float64)))
const Float32ulp = inv(ldexp(1.0, precision(Float32)))
const Float16ulp = inv(ldexp(1.0, precision(Float16)))

@inline ulp(x::Float64) = ufp(x) * Float64ulp
@inline ulp(x::Float32) = ufp(x) * Float32ulp
@inline ulp(x::Float16) = ufp(x) * Float16ulp

const Float64eps = inv(ldexp(1.0, precision(Float64)-1))
const Float32eps = inv(ldexp(1.0, precision(Float32)-1))
const Float16eps = inv(ldexp(1.0, precision(Float16)-1))

@inline epsi(x::Float64) = ufp(x) * Float64eps
@inline epsi(x::Float32) = ufp(x) * Float32eps
@inline epsi(x::Float16) = ufp(x) * Float16eps


function fastModularExponentiation(m, e, n::Integer)
    n > 0  || error("n must be positive")
    e >= 0 || error("e must be non-negative")
    r = 1
    while e > 0
        if (e & 1) > 0
            r = (r*m)%n
        end
        e >>= 1
        m = (m^2)%n
    end
    return r>0 ? r : r+n
end


# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.10.3
#   kernelspec:
#     display_name: Julia 1.6.3
#     language: julia
#     name: julia-1.6
# ---

# %% [markdown]
# https://twitter.com/genkuroki/status/1459220790444920833

# %%
using Distributions, StatsPlots

# %%
mixnormal = MixtureModel([Normal(0, 1), Normal(1, 2)], [1/3, 2/3])

# %%
X = rand(mixnormal, 10^5)
f(x) = (1/3)*pdf(Normal(0, 1), x) + (2/3)*pdf(Normal(1, 2), x)

histogram(X; norm=true, alpha=0.3, label="sample of mixnormal")
plot!(f; label="(1/3)N(x|0,1)+(2/3)N(x|1,2))", lw=2)

# %%


"""
    eom_var1(x, p, n) -> Function

Equations of motion for a vector autoregressive system where X₁ → X₂ → X₃.
"""
function eom_var1(x, p, n)
    σ₁, σ₂, σ₃ = p[1], p[2], p[3]
    x₁, x₂, x₃ = x[1], x[2], x[3]
    θ = rand(Normal(0, σ₁))
    η = rand(Normal(0, σ₂))
    ϵ = rand(Normal(0, σ₃))

    dx₁ = θ
    dx₂ = x₁ * η
    dx₃ = 0.5*x₃ * x₂ + ϵ
    return SVector{3}(dx₁, dx₂, dx₃)
end

function var1(uᵢ, σ₁, σ₂, σ₃)
    p = [σ₁, σ₂, σ₃]
    DiscreteDynamicalSystem(eom_var1, uᵢ, p)
end

"""
    var1(x, p, n) -> DiscreteDynamicalSystem

Initialise a discrete vector autoregressive system where X₁ → X₂ → X₃.
"""
var1(;uᵢ = rand(3), σ₁ = 1.0, σ₂ = 0.2, σ₃ = 0.3) = var1(uᵢ, σ₁, σ₂, σ₃)


# Correlation Functions
# #+HTML: Source
# #+HTML:

# [[file:~/.julia/dev/MatrixProductStates/README.org::*Correlation%20Functions][Correlation Functions:1]]
"""
    two_point_correlator((i, op_i)::Pair{Int, Matrix}, (j, op_j)::Pair{Int, Matrix}, L)

Create an MPO on `L` sites (with bond dimension 1) representing identity operators everywhere except
sites `i` and `j` where `op_i` and `op_j` are inserted instead. ie.

    𝟙 ⊗ 𝟙 ⊗ ... ⊗ op_i ⊗ 𝟙 ⊗ ... ⊗ op_j ⊗ 𝟙 ⊗ ... ⊗ 𝟙

example: spin-spin correlation function

we can construct ⟨σᶻᵢσᶻⱼ⟩ on a 12 site lattice as
    σᶻ = [1 0; 0 -1]
    two_point_correlator(i=>σᶻ, j=>σᶻ, 12)
"""
function two_point_correlator((i, op_i), (j, op_j), L)
    d = size(op_i)[1]
    @assert (size(op_i) == (d, d)) && (size(op_j) == (d, d))
    @assert i in 1:L
    @assert j in 1:L
    id = diagm(0 => ones(Complex{Float64}, d))

    op_i_tnsr = reshape(convert(Matrix{Complex{Float64}}, op_i), 1, 1, d, d)
    op_j_tnsr = reshape(convert(Matrix{Complex{Float64}}, op_j), 1, 1, d, d)
    id_tnsr   = reshape(id, 1, 1, d, d)

    tensors = map(1:L) do l
        O_tnsr = (l == i ? op_i_tnsr :
                  l == j ? op_j_tnsr :
                  id_tnsr)
    end
    MPO{L,Complex{Float64}}(tensors)
end
# Correlation Functions:1 ends here


# ## Contour plot
# ### Contour plot

#md # ![FILE_NAME.png](images/FILE_NAME.png)

f(x,y) = x^2 + y^2
x = repeat(range(0, stop=2, length=50), 1, 50)
y = repeat(range(0, stop=2, length=50), 1, 50)'

contour( f.(x, y) )


function lin_interp(grid, vals)
    function func(x::Real)
        if x <= grid[1]
            index_1 = 1
            index_2 = 2
        elseif x >= grid[length(grid)]
            index_1 = length(grid) - 1
            index_2 = length(grid)
        else
            index_1 = searchsortedfirst(grid, x)
            index_2 = searchsortedlast(grid, x)
        end
        x_1 = grid[index_1]
        x_2 = grid[index_2]
        y_1 = vals[index_1]
        y_2 = vals[index_2]

        y = ((y_2- y_1)/(x_2 - x_1))*(x - x_1) + y_1
        return y
    end

    function func{T<:Real}(x::AbstractVector{T})
        y = zeros(length(x))
        for i in 1:length(x)
            y[i] = func(x[i])
        end
        return y
    end

    return func
end


# .................... cubic b-spline functions ...............................

# n / 0 == 0
#
function zero_divide(a, b)
    x = a / b
    return ifelse(isfinite(x), x, 0)
end

# uniform-knot p:degree n:number_of_points
#
function knot(p, n)
    base = [ifelse(k <= p, 0.0, 1.0) for k in (0:p + n)]
    if n - p == 1
        return base
    else
        for k in 0:n-p
            base[p + k + 1] = k / (n - p)
        end
    end
    return base
end

# b-spline basis p:degree i:number u:parameter k:knot
#
function bbasis(p, i, u, k)
    if p == 0
        return ifelse(k[i] <= u <= k[i+1], 1, 0)
    else
        m0 = zero_divide(u - k[i], k[i+p] - k[i])
        m1 = zero_divide(k[i+p+1] - u, k[i+p+1] - k[i+1])
        return m0 * bbasis(p-1, i, u, k) + m1 * bbasis(p-1, i+1, u, k)
    end
end

# b-spline basis first-derivative
#
function bbasis_fd(p, i, u, k)
    return zero_divide(p, k[i+p] - k[i]) * bbasis(p-1, i, u, k) -
        zero_divide(p, k[i+p+1] - k[i+1]) * bbasis(p-1, i+1, u, k)
end

# b-spline curve, points: as columns, t: 1d vector
#
function bcurve(points, u, bas=bbasis)
    knt = Ref(knot(3, size(points, 2)))
    tmp = zeros(size(points, 1), length(u), size(points, 2))
    for n in axes(points, 2)
        tmp[:,:,n] = points[:,n] .* bas.(3, n, u, knt)'
    end
    return dropdims(sum(tmp, dims=3), dims=3)
end

# b-spline surface, points: size(cp_u, cp_v, 3), uv: size(nu, nv, 2)
#
function bsurface(points, uv, bu=bbasis, bv=bbasis)
    ku, kv = knot.(3, size(points))
    uv_tmp = zeros(size(uv, 1), size(uv, 2), size(points, 3))

    for I in CartesianIndices(uv[:,:,1])
        for J in CartesianIndices(points[:,:,1])
            uv_tmp[I,:] += points[J,:] .*
                bu(3, J[1], uv[I, 1], ku) .*
                bv(3, J[2], uv[I, 2], kv)
        end
    end
    return uv_tmp
end

function mgrid(nu, nv, a=0:1, b=0:1)
    uv = zeros(nu, nv, 2)
    uv[:,:,1] .= first(a) : ((last(a)-first(a)) / (nu-1)) : last(a)
    uv[:,:,2] .= (first(b) : ((last(b)-first(b)) / (nv-1)) : last(b))'
    return uv
end

function rnd_grid(x, y)
    uv = mgrid(x, y, 0:x, 0:y)
    az = rand(Float32, x, y)
    return cat(uv, az, dims = 3)
end


"""
    SqExponentialKernel()

Squared exponential kernel.

# Definition

For inputs ``x, x' \\in \\mathbb{R}^d``, the squared exponential kernel is defined as
```math
k(x, x') = \\exp\\bigg(- \\frac{\\|x - x'\\|_2^2}{2}\\bigg).
```

See also: [`GammaExponentialKernel`](@ref)
"""
struct SqExponentialKernel <: SimpleKernel end

kappa(::SqExponentialKernel, d²::Real) = exp(-d² / 2)

metric(::SqExponentialKernel) = SqEuclidean()

iskroncompatible(::SqExponentialKernel) = true

Base.show(io::IO, ::SqExponentialKernel) = print(io, "Squared Exponential Kernel")

## Aliases ##

"""
    RBFKernel()

Alias of [`SqExponentialKernel`](@ref).
"""
const RBFKernel = SqExponentialKernel

"""
    GaussianKernel()

Alias of [`SqExponentialKernel`](@ref).
"""
const GaussianKernel = SqExponentialKernel

"""
    SEKernel()

Alias of [`SqExponentialKernel`](@ref).
"""
const SEKernel = SqExponentialKernel

"""
    ExponentialKernel()

Exponential kernel.

# Definition

For inputs ``x, x' \\in \\mathbb{R}^d``, the exponential kernel is defined as
```math
k(x, x') = \\exp\\big(- \\|x - x'\\|_2\\big).
```

See also: [`GammaExponentialKernel`](@ref)
"""
struct ExponentialKernel <: SimpleKernel end

kappa(::ExponentialKernel, d::Real) = exp(-d)

metric(::ExponentialKernel) = Euclidean()

iskroncompatible(::ExponentialKernel) = true

Base.show(io::IO, ::ExponentialKernel) = print(io, "Exponential Kernel")

## Aliases ##

"""
    LaplacianKernel()

Alias of [`ExponentialKernel`](@ref).
"""
const LaplacianKernel = ExponentialKernel

"""
    Matern12Kernel()

Alias of [`ExponentialKernel`](@ref).
"""
const Matern12Kernel = ExponentialKernel

"""
    GammaExponentialKernel(; γ::Real=2.0)

γ-exponential kernel with parameter `γ`.

# Definition

For inputs ``x, x' \\in \\mathbb{R}^d``, the γ-exponential kernel[^RW] with parameter
``\\gamma \\in (0, 2]`` is defined as
```math
k(x, x'; \\gamma) = \\exp\\big(- \\|x - x'\\|_2^{\\gamma}\\big).
```

See also: [`ExponentialKernel`](@ref), [`SqExponentialKernel`](@ref)

[^RW]: C. E. Rasmussen & C. K. I. Williams (2006). Gaussian Processes for Machine Learning.
"""
struct GammaExponentialKernel{Tγ<:Real} <: SimpleKernel
    γ::Vector{Tγ}
    function GammaExponentialKernel(; gamma::Real=2.0, γ::Real=gamma)
        @check_args(GammaExponentialKernel, γ, zero(γ) < γ ≤ 2, "γ ∈ (0, 2]")
        return new{typeof(γ)}([γ])
    end
end

@functor GammaExponentialKernel

kappa(κ::GammaExponentialKernel, d::Real) = exp(-d^first(κ.γ))

metric(::GammaExponentialKernel) = Euclidean()

iskroncompatible(::GammaExponentialKernel) = true

function Base.show(io::IO, κ::GammaExponentialKernel)
    return print(io, "Gamma Exponential Kernel (γ = ", first(κ.γ), ")")
end


"""
    Link

An abstract type whose subtypes determine methods for [`linkfun`](@ref), [`linkinv`](@ref),
[`mueta`](@ref), and [`inverselink`](@ref).
"""
abstract type Link end

# Make links broadcast like a scalar
Base.Broadcast.broadcastable(l::Link) = Ref(l)

"""
    Link01

An abstract subtype of [`Link`](@ref) which are links defined on (0, 1)
"""
abstract type Link01 <: Link end

"""
    CauchitLink

A [`Link01`](@ref) corresponding to the standard Cauchy distribution,
[`Distributions.Cauchy`](https://juliastats.github.io/Distributions.jl/stable/univariate.html#Distributions.Cauchy).
"""
mutable struct CauchitLink <: Link01 end

"""
    CloglogLink

A [`Link01`](@ref) corresponding to the extreme value (or log-Wiebull) distribution.  The
link is the complementary log-log transformation, `log(1 - log(-μ))`.
"""
mutable struct CloglogLink  <: Link01 end

"""
    IdentityLink

The canonical [`Link`](@ref) for the `Normal` distribution, defined as `η = μ`.
"""
mutable struct IdentityLink <: Link end

"""
    InverseLink

The canonical [`Link`](@ref) for [`Distributions.Gamma`](https://juliastats.github.io/Distributions.jl/stable/univariate.html#Distributions.Gamma) distribution, defined as `η = inv(μ)`.
"""
mutable struct InverseLink  <: Link end

"""
    InverseSquareLink

The canonical [`Link`](@ref) for [`Distributions.InverseGaussian`](https://juliastats.github.io/Distributions.jl/stable/univariate.html#Distributions.InverseGaussian) distribution, defined as `η = inv(abs2(μ))`.
"""
mutable struct InverseSquareLink  <: Link end

"""
    LogitLink

The canonical [`Link01`](@ref) for [`Distributions.Bernoulli`](https://juliastats.github.io/Distributions.jl/stable/univariate.html#Distributions.Bernoulli) and [`Distributions.Binomial`](https://juliastats.github.io/Distributions.jl/stable/univariate.html#Distributions.Binomial).
The inverse link, [`linkinv`](@ref), is the c.d.f. of the standard logistic distribution,
[`Distributions.Logistic`](https://juliastats.github.io/Distributions.jl/stable/univariate.html#Distributions.Logistic).
"""
mutable struct LogitLink <: Link01 end

"""
    LogLink

The canonical [`Link`](@ref) for [`Distributions.Poisson`](https://juliastats.github.io/Distributions.jl/stable/univariate.html#Distributions.Poisson), defined as `η = log(μ)`.
"""
mutable struct LogLink <: Link end

"""
    NegativeBinomialLink

The canonical [`Link`](@ref) for [`Distributions.NegativeBinomial`](https://juliastats.github.io/Distributions.jl/stable/univariate.html#Distributions.NegativeBinomial) distribution, defined as `η = log(μ/(μ+θ))`.
The shape parameter θ has to be fixed for the distribution to belong to the exponential family.
"""
mutable struct NegativeBinomialLink  <: Link
    θ::Float64
end

"""
    ProbitLink

A [`Link01`](@ref) whose [`linkinv`](@ref) is the c.d.f. of the standard normal
distribution, [`Distributions.Normal()`](https://juliastats.github.io/Distributions.jl/stable/univariate.html#Distributions.Normal).
"""
mutable struct ProbitLink <: Link01 end

"""
    SqrtLink

A [`Link`](@ref) defined as `η = √μ`
"""
mutable struct SqrtLink <: Link end

"""
    linkfun(L::Link, μ)

Return `η`, the value of the linear predictor for link `L` at mean `μ`.

# Examples
```jldoctest
julia> μ = inv(10):inv(5):1
0.1:0.2:0.9

julia> show(linkfun.(LogitLink(), μ))
[-2.19722, -0.847298, 0.0, 0.847298, 2.19722]

```
"""
function linkfun end

"""
    linkinv(L::Link, η)

Return `μ`, the mean value, for link `L` at linear predictor value `η`.

# Examples
```jldoctest
julia> μ = 0.1:0.2:1
0.1:0.2:0.9

julia> η = logit.(μ);

julia> linkinv.(LogitLink(), η) ≈ μ
true
```
"""
function linkinv end

"""
    mueta(L::Link, η)

Return the derivative of [`linkinv`](@ref), `dμ/dη`, for link `L` at linear predictor value `η`.

# Examples
```jldoctest
julia> mueta(LogitLink(), 0.0)
0.25

julia> mueta(CloglogLink(), 0.0) ≈ 0.36787944117144233
true

julia> mueta(LogLink(), 2.0) ≈ 7.38905609893065
true
```
"""
function mueta end

"""
    inverselink(L::Link, η)

Return a 3-tuple of the inverse link, the derivative of the inverse link, and when appropriate, the variance function `μ*(1 - μ)`.

The variance function is returned as NaN unless the range of μ is (0, 1)

# Examples
```jldoctest
julia> inverselink(LogitLink(), 0.0)
(0.5, 0.25, 0.25)

julia> μ, oneminusμ, variance = inverselink(CloglogLink(), 0.0);

julia> μ + oneminusμ ≈ 1
true

julia> μ*(1 - μ) ≈ variance
true

julia> isnan(last(inverselink(LogLink(), 2.0)))
true
```
"""
function inverselink end

"""
    canonicallink(D::Distribution)

Return the canonical link for distribution `D`, which must be in the exponential family.

# Examples
```jldoctest
julia> canonicallink(Bernoulli())
LogitLink()
```
"""
function canonicallink end

linkfun(::CauchitLink, μ) = tan(pi * (μ - oftype(μ, 1/2)))
linkinv(::CauchitLink, η) = oftype(η, 1/2) + atan(η) / pi
mueta(::CauchitLink, η) = one(η) / (pi * (one(η) + abs2(η)))
function inverselink(::CauchitLink, η)
    μlower = atan(-abs(η)) / π
    μlower += oftype(μlower, 1/2)
    η > 0 ? 1 - μlower : μlower, inv(π * (1 + abs2(η))), μlower * (1 - μlower)
end

linkfun(::CloglogLink, μ) = log(-log1p(-μ))
function linkinv(::CloglogLink, η::T) where T<:Real
    clamp(-expm1(-exp(η)), eps(T), one(T) - eps(T))
end
function mueta(::CloglogLink, η::T) where T<:Real
    max(eps(T), exp(η) * exp(-exp(η)))
end
function inverselink(::CloglogLink, η)
    expη = exp(η)
    μ = -expm1(-expη)
    omμ = exp(-expη)   # the complement, 1 - μ
    μ, max(floatmin(μ), expη * omμ), max(floatmin(μ), μ * omμ)
end

linkfun(::IdentityLink, μ) = μ
linkinv(::IdentityLink, η) = η
mueta(::IdentityLink, η) = one(η)
inverselink(::IdentityLink, η) = η, one(η), oftype(η, NaN)

linkfun(::InverseLink, μ) = inv(μ)
linkinv(::InverseLink, η) = inv(η)
mueta(::InverseLink, η) = -inv(abs2(η))
function inverselink(::InverseLink, η)
    μ = inv(η)
    μ, -abs2(μ), oftype(μ, NaN)
end

linkfun(::InverseSquareLink, μ) = inv(abs2(μ))
linkinv(::InverseSquareLink, η) = inv(sqrt(η))
mueta(::InverseSquareLink, η) = -inv(2η*sqrt(η))
function inverselink(::InverseSquareLink, η)
    μ = inv(sqrt(η))
    μ, -μ / (2η), oftype(μ, NaN)
end

linkfun(::LogitLink, μ) = logit(μ)
linkinv(::LogitLink, η) = logistic(η)
function mueta(::LogitLink, η)
    expabs = exp(-abs(η))
    denom = 1 + expabs
    (expabs / denom) / denom
end
function inverselink(::LogitLink, η)
    expabs = exp(-abs(η))
    opexpabs = 1 + expabs
    deriv = (expabs / opexpabs) / opexpabs
    η ≤ 0 ? expabs / opexpabs : inv(opexpabs), deriv, deriv
end

linkfun(::LogLink, μ) = log(μ)
linkinv(::LogLink, η) = exp(η)
mueta(::LogLink, η) = exp(η)
function inverselink(::LogLink, η)
    μ = exp(η)
    μ, μ, oftype(μ, NaN)
end

linkfun(nbl::NegativeBinomialLink, μ) = log(μ / (μ + nbl.θ))
linkinv(nbl::NegativeBinomialLink, η) = ℯ^η * nbl.θ / (1-ℯ^η)
mueta(nbl::NegativeBinomialLink, η) = ℯ^η * nbl.θ / (1-ℯ^η)
function inverselink(nbl::NegativeBinomialLink, η)
    μ = ℯ^η * nbl.θ / (1-ℯ^η)
    deriv = μ * (1 + μ / nbl.θ)
    μ, deriv, oftype(μ, NaN)
end

linkfun(::ProbitLink, μ) = -sqrt2 * erfcinv(2μ)
linkinv(::ProbitLink, η) = erfc(-η / sqrt2) / 2
mueta(::ProbitLink, η) = exp(-abs2(η) / 2) / sqrt2π
function inverselink(::ProbitLink, η)
    μlower = erfc(abs(η) / sqrt2) / 2
    μupper = 1 - μlower
    η < 0 ? μlower : μupper, exp(-abs2(η) / 2 ) / sqrt2π, μlower * μupper
end

linkfun(::SqrtLink, μ) = sqrt(μ)
linkinv(::SqrtLink, η) = abs2(η)
mueta(::SqrtLink, η) = 2η
inverselink(::SqrtLink, η) = abs2(η), 2η, oftype(η, NaN)

canonicallink(::Bernoulli) = LogitLink()
canonicallink(::Binomial) = LogitLink()
canonicallink(::Gamma) = InverseLink()
canonicallink(::InverseGaussian) = InverseSquareLink()
canonicallink(d::NegativeBinomial) = NegativeBinomialLink(d.r)
canonicallink(::Normal) = IdentityLink()
canonicallink(::Poisson) = LogLink()

"""
    glmvar(D::Distribution, μ)

Return the value of the variance function for `D` at `μ`

The variance of `D` at `μ` is the product of the dispersion parameter, ϕ, which does not
depend on `μ` and the value of `glmvar`.  In other words `glmvar` returns the factor of the
variance that depends on `μ`.

# Examples
```jldoctest
julia> μ = 1/6:1/3:1;

julia> glmvar.(Normal(), μ)    # constant for Normal()
3-element Array{Float64,1}:
 1.0
 1.0
 1.0

julia> glmvar.(Bernoulli(), μ) ≈ μ .* (1 .- μ)
true

julia> glmvar.(Poisson(), μ) == μ
true
```
"""
function glmvar end

glmvar(::Union{Bernoulli,Binomial}, μ) = μ * (1 - μ)
glmvar(::Gamma, μ) = abs2(μ)
glmvar(::InverseGaussian, μ) = μ^3
glmvar(d::NegativeBinomial, μ) = μ * (1 + μ/d.r)
glmvar(::Normal, μ) = one(μ)
glmvar(::Poisson, μ) = μ

"""
    mustart(D::Distribution, y, wt)

Return a starting value for μ.

For some distributions it is appropriate to set `μ = y` to initialize the IRLS algorithm but
for others, notably the Bernoulli, the values of `y` are not allowed as values of `μ` and
must be modified.

# Examples
```jldoctest
julia> mustart(Bernoulli(), 0.0, 1) ≈ 1/4
true

julia> mustart(Bernoulli(), 1.0, 1) ≈ 3/4
true

julia> mustart(Binomial(), 0.0, 10) ≈ 1/22
true

julia> mustart(Normal(), 0.0, 1) ≈ 0
true
```
"""
function mustart end

mustart(::Bernoulli, y, wt) = (y + oftype(y, 1/2)) / 2
mustart(::Binomial, y, wt) = (wt * y + oftype(y, 1/2)) / (wt + one(y))
function mustart(::Union{Gamma, InverseGaussian}, y, wt)
    fy = float(y)
    iszero(y) ? oftype(y, 1/10) : fy
end
function mustart(::NegativeBinomial, y, wt)
    fy = float(y)
    iszero(y) ? fy + oftype(fy, 1/6) : fy
end
mustart(::Normal, y, wt) = y
function mustart(::Poisson, y, wt)
    fy = float(y)
    fy + oftype(fy, 1/10)
end

"""
    devresid(D, y, μ)

Return the squared deviance residual of `μ` from `y` for distribution `D`

The deviance of a GLM can be evaluated as the sum of the squared deviance residuals.  This
is the principal use for these values.  The actual deviance residual, say for plotting, is
the signed square root of this value
```julia
sign(y - μ) * sqrt(devresid(D, y, μ))
```

# Examples
```jldoctest
julia> devresid(Normal(), 0, 0.25) ≈ abs2(0.25)
true

julia> devresid(Bernoulli(), 1, 0.75) ≈ -2*log(0.75)
true

julia> devresid(Bernoulli(), 0, 0.25) ≈ -2*log1p(-0.25)
true
```
"""
function devresid end

function devresid(::Bernoulli, y, μ)
    if y == 1
        return -2 * log(μ)
    elseif y == 0
        return -2 * log1p(-μ)
    end
    throw(ArgumentError("y should be 0 or 1 (got $y)"))
end
function devresid(::Binomial, y, μ)
    if y == 1
        return -2 * log(μ)
    elseif y == 0
        return -2 * log1p(-μ)
    else
        return 2 * (y * (log(y) - log(μ)) + (1 - y)*(log1p(-y) - log1p(-μ)))
    end
end
devresid(::Gamma, y, μ) = -2 * (log(y / μ) - (y - μ) / μ)
devresid(::InverseGaussian, y, μ) = abs2(y - μ) / (y * abs2(μ))
function devresid(d::NegativeBinomial, y, μ)
    θ = d.r
    v = 2 * (xlogy(y, y / μ) + xlogy(y + θ, (μ + θ)/(y + θ)))
    return μ == 0 ? oftype(v, NaN) : v
end
devresid(::Normal, y, μ) = abs2(y - μ)
devresid(::Poisson, y, μ) = 2 * (xlogy(y, y / μ) - (y - μ))

"""
    dispersion_parameter(D)  # not exported

Does distribution `D` have a separate dispersion parameter, ϕ?

Returns `false` for the `Bernoulli`, `Binomial` and `Poisson` distributions, `true` otherwise.

# Examples
```jldoctest
julia> show(GLM.dispersion_parameter(Normal()))
true
julia> show(GLM.dispersion_parameter(Bernoulli()))
false
```
"""
dispersion_parameter(D) = true
dispersion_parameter(::Union{Bernoulli, Binomial, Poisson}) = false

"""
    loglik_obs(D, y, μ, wt, ϕ)  # not exported

Returns `wt * logpdf(D(μ, ϕ), y)` where the parameters of `D` are derived from `μ` and `ϕ`.

The `wt` argument is a multiplier of the result except in the case of the `Binomial` where
`wt` is the number of trials and `μ` is the proportion of successes.

The loglikelihood of a fitted model is the sum of these values over all the observations.
"""
function loglik_obs end

loglik_obs(::Bernoulli, y, μ, wt, ϕ) = wt*logpdf(Bernoulli(μ), y)
loglik_obs(::Binomial, y, μ, wt, ϕ) = logpdf(Binomial(Int(wt), μ), Int(y*wt))
loglik_obs(::Gamma, y, μ, wt, ϕ) = wt*logpdf(Gamma(inv(ϕ), μ*ϕ), y)
loglik_obs(::InverseGaussian, y, μ, wt, ϕ) = wt*logpdf(InverseGaussian(μ, inv(ϕ)), y)
loglik_obs(::Normal, y, μ, wt, ϕ) = wt*logpdf(Normal(μ, sqrt(ϕ)), y)
loglik_obs(::Poisson, y, μ, wt, ϕ) = wt*logpdf(Poisson(μ), y)
# We use the following parameterization for the Negative Binomial distribution:
#    (Γ(θ+y) / (Γ(θ) * y!)) * μ^y * θ^θ / (μ+θ)^{θ+y}
# The parameterization of NegativeBinomial(r=θ, p) in Distributions.jl is
#    Γ(θ+y) / (y! * Γ(θ)) * p^θ(1-p)^y
# Hence, p = θ/(μ+θ)
loglik_obs(d::NegativeBinomial, y, μ, wt, ϕ) = wt*logpdf(NegativeBinomial(d.r, d.r/(μ+d.r)), y)


"""
     enkf(state_ens, d_matrix, hx_matrix)

Return updated states
"""
function enkf(state_ens, d_matrix, hx_matrix)

  # Variables (Mandel)

  X  = state_ens;
  D  = d_matrix;
  HX = hx_matrix;

  # Subtract ensemble mean (Mandel)

  (n, N) = size(X);
  (m, N) = size(D);

  A    = X - 1 / N * (X * ones(Float64, N, 1)) * ones(Float64, 1, N);
  HA   = HX - 1 / N * (HX * ones(Float64, N, 1)) * ones(Float64, 1, N);
  Dtmp = D - 1 / N * (D * ones(Float64, N, 1)) * ones(Float64, 1, N);

  # Observation error variance (Mandel-theoretic, Evensen-sample)

  R_sample = Dtmp * Dtmp' / (N-1);

  # Variance of predicted observations (DeChant and Mandel)

  C_YY = 1 / (N-1) * HA * HA';

  # Covariance between states ensemble and predicted observations (DeChant and Mandel)

  C_XY = 1 / (N-1) * A * HA';

  # Compute kalman gain (DeChant)

  K = C_XY / (C_YY + R_sample);

  if any(isnan(K))
    println("R_sample = $R_sample")
    println("C_YY = $C_YY")
    println("C_XY = $C_XY")
    error("Nans in Kalman gain")
  end

  # Update states (DeChant and Mandel)

  Xhat = X + K*(D-HX);

  return(Xhat);

end

"""
     enkf!(state_ens, d_matrix, hx_matrix)

Update states in place
"""
function enkf!(state_ens, d_matrix, hx_matrix)

  # Variables (Mandel)

  X  = state_ens;
  D  = d_matrix;
  HX = hx_matrix;

  # Subtract ensemble mean (Mandel)

  (n, N) = size(X);
  (m, N) = size(D);

  A    = X - 1 / N * (X * ones(Float64, N, 1)) * ones(Float64, 1, N);
  HA   = HX - 1 / N * (HX * ones(Float64, N, 1)) * ones(Float64, 1, N);
  Dtmp = D - 1 / N * (D * ones(Float64, N, 1)) * ones(Float64, 1, N);

  # Observation error variance (Mandel-theoretic, Evensen-sample)

  R_sample = Dtmp * Dtmp' / (N-1);

  # Variance of predicted observations (DeChant and Mandel)

  C_YY = 1 / (N-1) * HA * HA';

  # Covariance between states ensemble and predicted observations (DeChant and Mandel)

  C_XY = 1 / (N-1) * A * HA';

  # Compute kalman gain (DeChant)

  K = C_XY / (C_YY + R_sample);

  if any(isnan(K))
    println("R_sample = $R_sample")
    println("C_YY = $C_YY")
    println("C_XY = $C_XY")
    error("Nans in Kalman gain")
  end

  # Update states (DeChant and Mandel)

  X = X + K*(D-HX);

  nothing

end


#all kernel functions are normalized such that their support radius is h

@fastmath function pos(x::Float64)::Float64
	return (x > 0.0 ? x : 0.0)
end

"""
    spline23(h::Float64, r::Float64)::Float64

Returns ``w(r)``, the value of a 2d cubic spline ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function spline23(h::Float64, r::Float64)::Float64
	x = r/h
	if (x < 0.5)
		# 40/7pi = 1.8189136353359467
		return 1.8189136353359467*(1.0 - 6.0*x^2 + 6.0*x^3)/h^2
	elseif (x < 1.0)
		# 80/7pi = 3.6378272706718935
		return 3.6378272706718935*(1.0 - x)^3/h^2
	end
	return 0.0
end

"""
    Dspline23(h::Float64, r::Float64)::Float64

Returns ``\\frac{\\text{d}w}{\\text{d}r}(r)``, the first derivative of a 2d cubic spline ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function Dspline23(h::Float64, r::Float64)::Float64
	x = r/h
	if (x < 0.5)
		# 240/7pi = 10.91348181201568
		return -10.91348181201568*(2.0*x - 3.0*x^2)/h^3
	elseif (x < 1.0)
		return -10.91348181201568*(1.0 - x)^2/h^3
	end
	return 0.0
end

"""
    rDspline23(h::Float64, r::Float64)::Float64

Returns ``\\frac{1}{r}\\,\\frac{\\text{d}w}{\\text{d}r}(r)``, the reduced first derivative of a 2d cubic spline ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function rDspline23(h::Float64, r::Float64)::Float64
	x = r/h
	if (x < 0.5)
		# 240/7pi = 10.91348181201568
		return -10.91348181201568*(2.0 - 3.0*x)/h^4
	elseif (x < 1.0)
		return -10.91348181201568*(1.0 - x)^2/(x*h^4)
	end
	return 0.0
end

"""
    spline24(h::Float64, r::Float64)::Float64

Returns ``w(r)``, the value of a 2d quartic spline ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function spline24(h::Float64, r::Float64)::Float64
	x = r/h
	return 6.222175110452539*(pos(1.0 - x)^4 - 5*pos(0.6 - x)^4 + 10*pos(0.2 - x)^4)/h^2
end

"""
    Dspline24(h::Float64, r::Float64)::Float64

Returns ``\\frac{\\text{d}w}{\\text{d}r}(r)``, the first derivative of a 2d quartic spline ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function Dspline24(h::Float64, r::Float64)::Float64
	x = r/h
	return -24.888700441810155*(pos(1.0 - x)^3 - 5*pos(0.6 - x)^3 + 10*pos(0.2 - x)^3)/h^3
end

"""
    rDspline24(h::Float64, r::Float64)::Float64

Returns ``\\frac{1}{r}\\,\\frac{\\text{d}w}{\\text{d}r}(r)``, the reduced first derivative of a 2d quartic spline ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function rDspline24(h::Float64, r::Float64)::Float64
	x = r/h
	if x > 0.2
		return -24.888700441810155*(pos(1.0 - x)^3 - 5*pos(0.6 - x)^3)/(x*h^4)
	end
	return -24.888700441810155*(1.2 - 6.0*x^2)/h^4
end

"""
    wendland2(h::Float64, r::Float64)::Float64

Returns ``w(r)``, the value of a 2d quintic Wendland kernel ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function wendland2(h::Float64, r::Float64)::Float64
	x = r/h
	if (x > 1.0)
		return 0.0
	end
	# 7/pi ≐ 2.228169203286535
	return 2.228169203286535*((1.0 - x)^4)*(1.0 + 4.0*x)/h^2
end

"""
    Dwendland2(h::Float64, r::Float64)::Float64

Returns ``\\frac{\\text{d}w}{\\text{d}r}(r)``, the first derivative of a 2d quintic Wendland kernel ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function Dwendland2(h::Float64, r::Float64)::Float64
	x = r/h
	if (x > 1.0)
		return 0.0
	end
	# 140/pi ≐ 44.563384065730695
	return -44.563384065730695*x*((1.0 - x)^3)/h^3
end

"""
    rDwendland2(h::Float64, r::Float64)::Float64

Returns ``\\frac{1}{r}\\,\\frac{\\text{d}w}{\\text{d}r}(r)``, the reduced first derivative of a 2d quintic Wendland kernel ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function rDwendland2(h::Float64, r::Float64)::Float64
	x = r/h
	if (x > 1.0)
		return 0.0
	end
	# 140/pi ≐ 44.563384065730695
	return -44.563384065730695*((1.0 - x)^3)/h^4
end

"""
    wendland3(h::Float64, r::Float64)::Float64

Returns ``w(r)``, the value of a 3d quintic Wendland kernel ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function wendland3(h::Float64, r::Float64)::Float64
	x = r/h
	if (x > 1.0)
		return 0.0
	end
	# 21/2pi ≐ 3.3422538049298023
	return 3.3422538049298023*((1.0 - x)^4)*(1.0 + 4.0*x)/h^3
end

"""
    Dwendland3(h::Float64, r::Float64)::Float64

Returns ``\\frac{\\text{d}w}{\\text{d}r}(r)``, the first derivative of a 3d quintic Wendland kernel ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function Dwendland3(h::Float64, r::Float64)::Float64
	x = r/h
	if (x > 1.0)
		return 0.0
	end
	# 210/pi ≐ 66.84507609859604
	return -66.84507609859604*x*((1.0 - x)^3)/h^4
end

"""
    rDwendland3(h::Float64, r::Float64)::Float64

Returns ``\\frac{1}{r}\\,\\frac{\\text{d}w}{\\text{d}r}(r)``, the reduced first derivative of a 3d quintic Wendland kernel ``w`` with support radius `h`.
Integrates to unity.

"""
@fastmath function rDwendland3(h::Float64, r::Float64)::Float64
	x = r/h
	if (x > 1.0)
		return 0.0
	end
	# 210/pi ≐ 66.84507609859604
	return -66.84507609859604*((1.0 - x)^3)/h^5
end

@fastmath function DDwendland3(h::Float64, r::Float64)::Float64
	x = r/h
	if (x > 1.0)
		return 0.0
	end
	# 210/pi ≐ 66.84507609859604
	return -66.84507609859604*((1.0 - 4.0*x)*(1.0 - x)^2)/h^5
end

@fastmath function spiky3(h::Float64, r::Float64)::Float64
	x = r/h
	#15/2pi = 2.3873241463784303
	return 2.3873241463784303*(1.0 - x)^2/h^3
end

@fastmath function Dspiky3(h::Float64, r::Float64)::Float64
	x = r/h
	#15/pi = 4.7746482927568605
	return -(x < 1.0)*2.3873241463784303*(1.0 - x)/h^4
end

@fastmath function rDspiky3(h::Float64, r::Float64)::Float64
	x = r/h
	#15/pi = 4.7746482927568605
	return -(x < 1.0)*2.3873241463784303*(1.0 - x)/((x + 1e-6)*h^5)
end

@fastmath function DDspiky3(h::Float64, r::Float64)::Float64
	x = r/h
	#15/pi = 4.7746482927568605
	return (x < 1.0)*2.3873241463784303/h^5
end


using Combinatorics

function findMinWrapDist(f_true,f_hat)
# Assign each of the ground truth frequencies to closest
# (squared error) canditate of estimated frequencies.
# Metric is the wrap-aroud distance on the torus $\mathds{T}=[0,1]$.
#
# Inputs:
# f_true: ground truth frequencies
# f_hat: estimated frequencies
#
# Output:
# dists: wrap-around distances of the frequencies in f_true and f_hat

K = length(f_true)
WrapDist(x,y) = min(abs(x-y),abs(x-y+1))

dists = Array{Float64,1}(undef,K)
for ii = 1:K
    dists[ii] = minimum(WrapDist.(f_true[ii],f_hat))
end

return dists
end


# The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.
#
# Find the sum of all the primes below two million.

using ProjectEulerSolutions

# Run Sieve of Eratosthenes to get primes and sum them.
function p010solution(n::Integer=3)::Integer
    return sum(sieve_eratosthenes(n))
end

p010 = Problems.Problem(p010solution)

Problems.benchmark(p010, 1_999_999)


export Associative, Nonassociative, associativity

"""
Trait to indicate that a binary operation • is associative over set S. Only
methods of • with the signature •(x::S, y::S) are to be considered.

That is to say, ``\forall x, y, z \in S. (x \bullet y) \bullet z = x \bullet (y
\bullet z)``. Note that in practice, this is not so absolute. Minor deviations
such as `Float64` arithmetic not being perfectly associative are allowed for
practical reasons.
"""
abstract Associative

"""
Trait to indicate that a binary operation • is not necessarily associative over
set S. Despite the name, this trait does not imply the existence of a
counterexample to associativity.
"""
abstract Nonassociative

"""
Return the associativity trait of the set S under the binary operation •.
"""
associativity(::Type, ::Function) = Nonassociative

# complex numbers are associative under + and *
# note that this is not in general true for arbitrary Numbers
associativity{N<:AbstractComplex}(::Type{N}, ::typeof(+)) = Associative
associativity{N<:AbstractComplex}(::Type{N}, ::typeof(*)) = Associative

# Strings are associative under *
associativity(::Type{String}, ::typeof(*)) = Associative


"""
    NormalCanon(η, λ)

Canonical parametrisation of the Normal distribution with canonical parameters `η` and `λ`.

The two *canonical parameters* of a normal distribution ``\\mathcal{N}(\\mu, \\sigma^2)`` with mean ``\\mu`` and
standard deviation ``\\sigma`` are ``\\eta = \\sigma^{-2} \\mu`` and ``\\lambda = \\sigma^{-2}``.
"""
struct NormalCanon{T<:Real} <: ContinuousUnivariateDistribution
    η::T       # σ^(-2) * μ
    λ::T       # σ^(-2)
    μ::T       # μ

    function NormalCanon{T}(η, λ; check_args::Bool=true) where T
        check_args && @check_args(NormalCanon, λ > zero(λ))
        new{T}(η, λ, η / λ)
    end
end

NormalCanon(η::T, λ::T; check_args::Bool=true) where {T<:Real} = NormalCanon{typeof(η/λ)}(η, λ; check_args=check_args)
NormalCanon(η::Real, λ::Real; check_args::Bool=true) = NormalCanon(promote(η, λ)...; check_args=check_args)
NormalCanon(η::Integer, λ::Integer; check_args::Bool=true) = NormalCanon(float(η), float(λ); check_args=check_args)
NormalCanon() = NormalCanon{Float64}(0.0, 1.0; check_args=false)

@distr_support NormalCanon -Inf Inf

#### Type Conversions
convert(::Type{NormalCanon{T}}, η::S, λ::S) where {T <: Real, S <: Real} = NormalCanon(T(η), T(λ))
convert(::Type{NormalCanon{T}}, d::NormalCanon{S}) where {T <: Real, S <: Real} = NormalCanon(T(d.η), T(d.λ); check_args=false)

## conversion between Normal and NormalCanon

convert(::Type{Normal}, d::NormalCanon) = Normal(d.μ, 1 / sqrt(d.λ))
convert(::Type{NormalCanon}, d::Normal) = (λ = 1 / d.σ^2; NormalCanon(λ * d.μ, λ))
meanform(d::NormalCanon) = convert(Normal, d)
canonform(d::Normal) = convert(NormalCanon, d)

#### Parameters

params(d::NormalCanon) = (d.η, d.λ)
@inline partype(d::NormalCanon{T}) where {T<:Real} = T

#### Statistics

mean(d::NormalCanon) = d.μ
median(d::NormalCanon) = mean(d)
mode(d::NormalCanon) = mean(d)

skewness(d::NormalCanon{T}) where {T<:Real} = zero(T)
kurtosis(d::NormalCanon{T}) where {T<:Real} = zero(T)

var(d::NormalCanon) = 1 / d.λ
std(d::NormalCanon) = sqrt(var(d))

entropy(d::NormalCanon) = (-log(d.λ) + log2π + 1) / 2

location(d::NormalCanon) = mean(d)
scale(d::NormalCanon) = std(d)

#### Evaluation

pdf(d::NormalCanon, x::Real) = (sqrt(d.λ) / sqrt2π) * exp(-d.λ * abs2(x - d.μ)/2)
logpdf(d::NormalCanon, x::Real) = (log(d.λ) - log2π - d.λ * abs2(x - d.μ))/2

zval(d::NormalCanon, x::Real) = (x - d.μ) * sqrt(d.λ)
xval(d::NormalCanon, z::Real) = d.μ + z / sqrt(d.λ)

cdf(d::NormalCanon, x::Real) = normcdf(zval(d,x))
ccdf(d::NormalCanon, x::Real) = normccdf(zval(d,x))
logcdf(d::NormalCanon, x::Real) = normlogcdf(zval(d,x))
logccdf(d::NormalCanon, x::Real) = normlogccdf(zval(d,x))

quantile(d::NormalCanon, p::Real) = xval(d, norminvcdf(p))
cquantile(d::NormalCanon, p::Real) = xval(d, norminvccdf(p))
invlogcdf(d::NormalCanon, lp::Real) = xval(d, norminvlogcdf(lp))
invlogccdf(d::NormalCanon, lp::Real) = xval(d, norminvlogccdf(lp))

#### Sampling

rand(rng::AbstractRNG, cf::NormalCanon) = cf.μ + randn(rng) / sqrt(cf.λ)

#### Affine transformations

function Base.:+(d::NormalCanon, c::Real)
    η, λ = params(d)
    return NormalCanon(η + c * λ, λ)
end
Base.:*(c::Real, d::NormalCanon) = NormalCanon(d.η / c, d.λ / c^2)


module Metrics
    using ..Representation
    function calc_population_fitness!(population::Representation.Population,
                                    true_values::Array{Float64, 1},
                                    error_function, extra_params=nothing)
        for memeber in population.members
            pred_values = [kernels_to_values(x, memeber.gauss_kernels) for x in true_values]
            if extra_params != nothing && error_function == weighted_mean_abs_error
                (error, hit_ratio) = error_function(true_values,
                                        pred_values,
                                        extra_params["threshold"],
                                        extra_params["lower_weight"],
                                        extra_params["upper_weight"])
            else
                (error, hit_ratio) = error_function(true_values, pred_values)
            end
            memeber.fitness = error
            memeber.hit_ratio = hit_ratio
        end
    end

    function mean_sqr_error(true_values::Array{Float64, 1}, pred_values::Array{Float64, 1})
        error = sum((true_values - pred_values).^2)/length(true_values)
        hit_ratio = sum(true_values .== pred_values)
        return error, hit_ratio
    end

    function mean_absolute_error(true_values::Array{Float64, 1}, pred_values::Array{Float64, 1})
        error = sum(abs.(true_values - pred_values))/length(true_values)
        hit_ratio = sum(true_values .== pred_values)
        return error, hit_ratio
    end

    function weighted_mean_abs_error(true_values::Array{Float64, 1},
                                    pred_values::Array{Float64, 1},
                                    threshold::Float64,
                                    lower_weight::Float64,
                                    upper_weight::Float64)

        abs_error = abs.(true_values - pred_values)
        mask = [apply_threshold(value, threshold,
                        lower_weight, upper_weight) for value in abs_error]
        hit_ratio = sum(mask .== 1.0)
        return sum(abs_error.*mask)/length(true_values), hit_ratio

    end

    function apply_threshold(value::Float64, threshold::Float64,
                                lower_weight::Float64, upper_weight::Float64)
        return value <= threshold ? lower_weight : upper_weight
    end

    function kernels_to_values(x::Float64,
                                kernels::Array{Tuple{Float64, Float64, Float64}, 1})
        sum = Float32(0.0)
        for kernel in kernels
            (weight, c, γ) = kernel
            sum += weight * exp(-γ*(c - x)^2)
        end
        return sum
    end

end


# Gaussian models

### For prior IsoNormalCanon

function logpar(d::IsoNormalCanon)
    q = length(d)
    h = d.h
    σ2 = inv(d.J.value)
    (q * log(2π * σ2) + σ2 * vecnorm(h)^2) * 0.5
end

immutable IsoNormalCanonSuffStats
    h::Vector{Float64}  # add to h
    κ::Float64          # add to κ
end

length(ss::IsoNormalCanonSuffStats) = length(ss.h)

function posterior(d::IsoNormalCanon, ss::IsoNormalCanonSuffStats)
    length(d) == length(ss) || throw(DimensionMismatch())
    MvNormalCanon(d.h + ss.h, d.J.value + ss.κ)
end

##
# Gaussian likelihood model
#
#  x ~ N(θ, σ^2)
#
immutable IsoGaussModel <: LikelihoodModel
    σ::Float64    # the standard dev of observations
end

withparams(g::IsoGaussModel, μ::Vector{Float64}) =
    MvNormal(μ, g.σ)

function suffstats(g::IsoGaussModel, X::AbstractMatrix, inds::AbstractVector{Int})
    d, n = size(X)
    h = zeros(d)
    c = 1.0 / g.σ^2
    for i in inds
        x = view(X, :, i)
        axpy!(c, x, h)
    end
    IsoNormalCanonSuffStats(h, n * c)
end

suffstats(g::IsoGaussModel, X::AbstractMatrix) = suffstats(g, X, 1:size(X,2))


using Distributions
using ReverseDiffOverload
import Distributions.logpdf

test(dist, x, params...) = begin
    @assert isapprox(logpdf(dist, x, params...), logpdf(dist(params...), x))
    if dist <: DiscreteDistribution
        testdiff((params...) -> logpdf(dist, x, params...), params...)
    else
        testdiff((x, params...) -> logpdf(dist, x, params...), x, params...)
    end
end

#Normal
valid(::Type{Normal}, x, μ, σ) = begin
    @assert all(σ .> 0)
end
#Differentiable versions.
logpdf(::Type{Normal}, x, μ, σ) = begin
    #valid(Normal, x, μ, σ)
    σ2 = σ.*σ
    diff = x-μ
    -(log(2*pi*σ2) + diff.*diff ./ σ2)/2
end
test(Normal, 1, 2, 3)

#MvNormal
valid(::Type{MvNormal}, x::Vector, μ::Vector, Σ::Matrix) = begin
    @assert isposdef(Σ)
end
#Differentiable versions.
logpdf(::Type{MvNormal}, x::Vector, μ::Vector, Σ::Matrix) = begin
    #valid(MvNormal, x, μ, Σ)
    diff = x-μ
    -(length(x)*log(2*pi) + logdet(Σ) + (diff'/Σ)*x)/2
end
#test(MvNormal, [1., 2], [3., 4], eye(2) + ones(2,2))

#Gamma
valid(::Type{Gamma}, x, shape, scale) = begin
    @assert all(x .>= 0)
    @assert all(shape .>= 0)
    @assert all(scale .>= 0)
end
logpdf(::Type{Gamma}, x, shape, scale) = begin
    #valid(Gamma, x, shape, scale)
    -logΓ(shape) - shape.*log(scale) + (shape-1).*log(x) - x./scale
end
test(Gamma, 1, 2, 3)

#InverseGamma
valid(::Type{InverseGamma}, x, shape, scale) = begin
    @assert all(x .>= 0)
    @assert all(shape .>= 0)
    @assert all(scale .>= 0)
end
logpdf(::Type{InverseGamma}, x, shape, scale) = begin
    #valid(InverseGamma, x, shape, scale)
    -logΓ(shape) + shape.*log(scale) - (shape+1).*log(x) - scale./x
end
test(InverseGamma, 1, 2, 3)

#Bernoulli
valid(::Type{Bernoulli}, x, p) = begin
    @assert all(0 .<= p .<= 1)
    @assert all((x .== 1)|(x .== 0))
end
logpdf(::Type{Bernoulli}, x, p) = begin
    #valid(Bernoulli, x, p)
    x*log(p) + (1-x)*log(1-p)
end
test(Bernoulli, 1., 0.9)
test(Bernoulli, 0., 0.9)

#Categorical
valid(::Type{Categorical}, x, ps::Vector{Float64}) = begin
    @assert isapprox(sum(ps), 1)
    @assert all(0 .<= ps .<= 1)
end
logpdf(::Type{Categorical}, x, ps) = begin
    #valid(Categorical, x, ps)
    log(ps[x])
end
test(Categorical, 2, [0.3, 0.4, 0.3])

#Beta
valid(::Type{Beta}, x, α, β) = begin
    @assert all(α .> 0)
    @assert all(β .> 0)
    @assert all(0 .<= x .<= 1)
end
logB(α, β) = logΓ(α) + logΓ(β) - logΓ(α + β)
logpdf(::Type{Beta}, x, α, β) = begin
    #valid(Beta, x, α, β)
    (α-1).*log(x) + (β-1).*log(1-x) - logB(α, β)
end
test(Beta, 0.6, 1.3, 1.5)

#Dirichlet
valid(::Type{Dirichlet}, x, α::Vector{Float64}) = begin
    @assert all(0 .<= x)
    @assert all(0 .<= α)
end
logB(α) = sum(logΓ(α)) - logΓ(sum(α))
logpdf(::Type{Dirichlet}, x, α) = begin
    #valid(Dirichlet, x, α)
    sum((α-1).*log(x)) - logB(α)
end
test(Dirichlet, [1.2, 1.6, 1.3], [1.3, 1.6, 1.3])

#Poisson
valid(::Type{Poisson}, k, λ) = begin
    @assert all(k .>= 0)
    @assert all(λ .>= 0)
end
logpdf(::Type{Poisson}, k, λ) = begin
    #valid(Poisson, k, λ)
    k*log(λ) - log(factorial(k)) - λ
end
test(Poisson, 3, 5)

#Non-differentiable fallbacks from Distributions.jl
logpdf{D <: Distribution}(d::Type{D}, x, params...) =
    logpdf(d(params...), x)


abstract Type1FuzzySet <: AbstractFuzzySet
export Type1FuzzySet

#####################
abstract Membershipfunction <: Type1FuzzySet
export Membershipfunction

import Base: getindex

getindex(mf::Membershipfunction, range) = [mf[value]::Float64 for value in range]
getindex(mf::Membershipfunction, values...) = [mf[value]::Float64 for value in values]

export getindex

#####################
# Membership Functions
"Triangle Membership Function"
immutable TriangleMF <: Membershipfunction
	a::Float64
	b::Float64
	c::Float64

	TriangleMF(a,b,c) = a <= b <= c ? new(a,b,c) : error("Indices out of order")
end
export TriangleMF

import Base: getindex
function getindex(trimf::TriangleMF, value::Real)
	(value <= trimf.a || trimf.c <= value) && return zero(Float64)
	(value == trimf.b) && return one(Float64)

	if trimf.a < value < trimf.b
		slope = (1.0 - 0.0)/(trimf.b - trimf.a)
		return slope * (value - trimf.a)
	else
		slope = (0.0 - 1.0)/(trimf.c - trimf.b)
		return (slope * (value - trimf.b) + 1.0)
	end
end
export getindex

"Trapezoid Membership Function"
immutable TrapezoidMF <: Membershipfunction
	a::Float64
	b::Float64
	c::Float64
	d::Float64
end
export TrapezoidMF

import Base: getindex
function getindex(trapmf::TrapezoidMF, value::Real)
	(value <= trapmf.a || trapmf.d <= value) && return zero(Float64)
	(trapmf.b <= value <= trapmf.c) && return one(Float64)

	if trapmf.a < value < trapmf.b
		slope = (1 - 0)/(trapmf.b - trapmf.a)
		return slope * (value - trapmf.a)
	else
		slope = (0 - 1)/(trapmf.d - trapmf.c)
		return slope * (value - trapmf.c) + 1
	end
end
export getindex

"Generic Membership Function"
immutable GenericMF <: Membershipfunction
	mf::Function
end
export GenericMF

import Base: getindex
function getindex(gmf::GenericMF, value::Real)
	gmf.mf(value)
end
export getindex

immutable GaussMF <: Membershipfunction
	mu::Float64
	sigma::Float64
end
export GaussMF
immutable GaussMF <: Membershipfunction
	mu::Float64
	sigma::Float64
end
export GaussMF

import Base: getindex
function getindex(gmf::GaussMF, value::Real)
	GenericMF(x -> exp(-(x-gmf.mu).^2/(2.*gmf.sigma.^2)))[value]
end
export getindex

#####################


module problem112

description = """
Bouncy numbers

Working from left-to-right if no digit is exceeded by the digit to its left it is called an increasing number; for example, 134468.
Similarly if no digit is exceeded by the digit to its right it is called a decreasing number; for example, 66420.
We shall call a positive integer that is neither increasing nor decreasing a "bouncy" number; for example, 155349.
Clearly there cannot be any bouncy numbers below one-hundred, but just over half of the numbers below one-thousand (525) are bouncy. In fact, the least number for which the proportion of bouncy numbers first reaches 50% is 538.
Surprisingly, bouncy numbers become more and more common and by the time we reach 21780 the proportion of bouncy numbers is equal to 90%.

Find the least number for which the proportion of bouncy numbers is exactly 99%.
"""

function increasing(n :: Integer)
  lastDigit = 9
  while n > 0
    d = n % 10
    if d > lastDigit
      return false
    end
    lastDigit = d;
    n = div(n, 10)
  end
  return true
end

function decreasing(n :: Integer)
  lastDigit = 0
  while n > 0
    d = n % 10
    if d < lastDigit
      return false
    end
    lastDigit = d;
    n = div(n, 10)
  end
  return true
end

function bouncy(n :: Integer)
  return !(increasing(n) || decreasing(n))
end

function bouncySearch(percentage :: Integer)
  n = 1
  b = 0
  while b*100 != n*percentage
    n += 1
    b += bouncy(n) ? 1 : 0
  end
  return n
end

using Base.Test

@test bouncySearch(90) == 21780

end


"""
High-order singular value decomposition (HO-SVD).
"""
function hosvd{T,N}(tensor::StridedArray{T,N}, core_dims::NTuple{N, Int};
                    pad_zeros::Bool=false, compute_error::Bool=false)
    pad_zeros || _check_tensor(tensor, core_dims)

    factors = map(1:N) do i
        X = _col_unfold(tensor, i)
        f = eigs(X'X, nev=core_dims[i])[2]
        if pad_zeros && size(f, 2) < core_dims[i] # fill missing factors with zeros
            f = hcat(f, zeros(T, size(tensor, i), core_dims[i]-size(f, 2)))
        end
        mapslices(_check_sign, f, 1)
    end

    res = Tucker((factors...), tensorcontractmatrices(tensor, factors))
    if compute_error
        _set_rel_residue(res, tensor)
    end
    return res
end

hosvd{T,N}(tensor::StridedArray{T,N}, r::Int; compute_error::Bool=false) =
    hosvd(tensor, (fill(r, N)...); compute_error=compute_error)