"""Compute the entropy of the specified dataset."""
function entropy(dataset::AbstractArray{T, 2}, target::Integer,
                c::Integer=2) where {T}
    class = dataset[:, target]
    n = length(class)
    uniques = unique(class)

    return -mapreduce(u -> begin
        subset = dataset[class .== u, :]
        p = size(subset, 1) / n
        return p * log2(p)
    end, +, uniques) / log2(c)
end

entropy(dataset::AbstractArray{T, 2}, target::String,
        header::AbstractArray{String}, c::Integer=2) where {T} =
    entropy(dataset, something(findfirst(isequal(target), header), 0), c)

"""Compute the information gain of the specified attribute."""
function information_gain(dataset::AbstractArray{T, 2}, attribute::Integer,
                        target::Integer, c::Integer=2) where {T}
    n = size(dataset, 1)
    attr = dataset[:, attribute]
    uniques = unique(attr)

    attr_sub_entropy = mapreduce(u -> begin
        subset = dataset[attr .== u, :]
        return size(subset, 1) / n * entropy(subset, target, c)
    end, +, uniques)

    return entropy(dataset, target, c) - attr_sub_entropy
end

information_gain(dataset::AbstractArray{T, 2}, attribute::String,
                target::String, header::AbstractArray{String},
                c::Integer=2) where {T} =
    information_gain(dataset,
            something(findfirst(isequal(attribute), header), 0),
            something(findfirst(isequal(target), header), 0), c)

information_gain(dataset::AbstractArray{T, 2}, attribute::String,
                target::Integer, header::AbstractArray{String},
                c::Integer=2) where {T} =
    information_gain(dataset,
            something(findfirst(isequal(attribute), header), 0), target, c)

information_gain(dataset::AbstractArray{T, 2}, attribute::Integer,
                target::String, header::AbstractArray{String},
                c::Integer=2) where {T} =
    information_gain(dataset, attribute,
            something(findfirst(isequal(target), header), 0), c)


immutable Binomial <: DiscreteUnivariateDistribution
    size::Int
    prob::Float64
    function Binomial(n::Real, p::Real)
    	if n <= 0
	    	error("size must be positive")
	    else
	    	if 0.0 <= p <= 1.0
	    		new(int(n), float64(p))
	    	else
	    		error("prob must be in [0, 1]")
			end
	    end
	end
end

Binomial(size::Integer) = Binomial(size, 0.5)
Binomial() = Binomial(1, 0.5)

@_jl_dist_2p Binomial binom

entropy(d::Binomial) = d.size * (-xlogx(1.0 - d.prob) - xlogx(d.prob))

insupport(d::Binomial, x::Number) = isinteger(x) && 0 <= x <= d.size

kurtosis(d::Binomial) = (1.0 - 2.0 * d.prob * (1.0 - d.prob)) / var(d)

mean(d::Binomial) = d.size * d.prob

median(d::Binomial) = iround(d.size * d.prob)

# TODO: May need to subtract 1 sometimes
modes(d::Binomial) = iround((d.size + 1.0) * d.prob)

function mgf(d::Binomial, t::Real)
	n, p = d.size, d.prob
	return (1.0 - p + p * exp(t))^n
end

function cf(d::Binomial, t::Real)
	n, p = d.size, d.prob
	return (1.0 - p + p * exp(im * t))^n
end

modes(d::Binomial) = iround([d.size * d.prob])

# TODO: rand() is totally screwed up

skewness(d::Binomial) = (1.0 - 2.0 * d.prob) / std(d)

var(d::Binomial) = d.size * d.prob * (1.0 - d.prob)


using Plots
using Distributions
using Random
gr(size=(300,300))

function ellipse!(μ,Σ;kw...)
    for i=1:3
        plot!([tuple((i*sqrt(Σ)*[cos(t),sin(t)]+μ)...) for t in range(0,stop=2π,length=100)];
              seriestype=:shape,linealpha=0.5,linecolor=:gray,fillalpha=[0.4,0.2,0.1][i],kw...)
    end
    current()
end

function mixtureplot(X₁,X₂,μ₀,Σ₀,μ₁,Σ₁,Π)
    scatter(X₁,X₂;fillalpha=0.5,markerstrokewidth=0.5,
        marker_z=Π,mc=ColorGradient([:lightblue,:orange]),
        colorbar=:false)
    ellipse!(μ₀,Σ₀,fillcolor=:lightblue)
    ellipse!(μ₁,Σ₁,fillcolor=:orange)
    plot!(;bg=:transparent,xlims=(-4.5,8.5),ylims=(-3.5,10.5),
        leg=false,ticks=:none,ratio=:equal)
end

function scalein(x)
    (x-1/2)^101/(0.5^101)
end

Random.seed!(123);
n = 100
α = 0.4
𝒩₀ = MvNormal([1,1],[2.0 1.0; 1.0 2.0])
𝒩₁ = MvNormal([3.0,7.0],[1.5 0; 0 0.5])
X₁ = zeros(n)
X₂ = zeros(n)
Z = zeros(Bool,n)
for i=1:n
    Z[i] = rand(Bernoulli(α))
    X₁[i],X₂[i] = Z[i] ? rand(𝒩₁) : rand(𝒩₀)
end

α = 0.6
μ₀ = [3.0,3.0]
μ₁ = [1.0,6.0]
Σ₀ = 1.0*Matrix(I, 2, 2)
Σ₁ = 1.0*Matrix(I, 2, 2)
mixtureplot(X₁,X₂,μ₀,Σ₀,μ₁,Σ₁)

Π = [α*pdf(MvNormal(μ₁,Σ₁),[x₁,x₂]) /
       ((1-α)*pdf(MvNormal(μ₀,Σ₀),[x₁,x₂]) +
        α*pdf(MvNormal(μ₁,Σ₁),[x₁,x₂])) for (x₁,x₂) in zip(X₁,X₂)];

α = sum(Π)/n
μ₀ = [(1 .- Π) ⋅ X₁, (1 .- Π) ⋅ X₂] / sum(1 .- Π)
μ₁ = [Π ⋅ X₁, Π ⋅ X₂] / sum(Π)
Σ₀ = Matrix(Hermitian(sum((1-π)*([x₁,x₂] - μ₀) * ([x₁,x₂] - μ₀)' for (x₁,x₂,π) in zip(X₁,X₂,Π))/sum(1 .- Π)))
Σ₁ = Matrix(Hermitian(sum(π*([x₁,x₂] - μ₁) * ([x₁,x₂] - μ₁)' for (x₁,x₂,π) in zip(X₁,X₂,Π))/sum(Π)))
Π = [α*pdf(MvNormal(μ₁,Σ₁),[x₁,x₂]) /
       ((1-α)*pdf(MvNormal(μ₀,Σ₀),[x₁,x₂]) +
        α*pdf(MvNormal(μ₁,Σ₁),[x₁,x₂])) for (x₁,x₂) in zip(X₁,X₂)];

mixtureplot(X₁,X₂,μ₀,Σ₀,μ₁,Σ₁,Π)


using OrdinaryDiffEq
using StaticArrays
using Plots

"""
    Duffing

A Duffing equation with periodic forcing of the form

```math
mx″ + cx′ + kx + μx³ = F₀cos(ωt)
```
"""
Base.@kwdef mutable struct Duffing
    m::Float64 = 1.0
    c::Float64 = 0.01
    k::Float64 = 2.0
    μ::Float64 = 1
    F₀::Float64 = 0.05
    ω::Float64 = 1.0
end

"""
    rhs_dt(u, p::Duffing, t)

Return the right-hand side of the Duffing.
"""
function rhs_dt(u, p::Duffing, t)
    x = u[1]
    x′ = u[2]
    f = p.F₀ * cos(p.ω * t)
    # Use a static vector (SVector) for speed; it's not necessary for any other reason
    return SVector(x′, (f - p.c * x′ - p.k * x - p.μ * x^3) / p.m)
end

"""
    integrate(u, p::Duffing, t)

Return a solution of the Duffing equation over the time interval `t` starting at the initial
condition `u` with the parameters of the Duffing equation taken from `p`.
"""
function integrate(u, p::Duffing, t)
    # Explicitly convert everything to Float64 in case integer values are used
    # Use SVectors for speed (not necessary)
    prob = ODEProblem(rhs_dt, SVector{2, Float64}(u), Float64.(t), p)
    return solve(prob, Tsit5())
end

"""
    example()

Run an example Duffing equation simulation and plot the results.
"""
function example()
    p = Duffing()
    sol = integrate([0, 0], p, (0, 100))
    plot(sol.t, sol[1,:])
end

# Code to benchmark the timings

# using BenchmarkTools
# p = Duffing()
# @benchmark integrate($([0, 0]), $p, (0, 100))

# Output on my laptop

# BenchmarkTools.Trial:
#   memory estimate:  60.34 KiB
#   allocs estimate:  462
#   --------------
#   minimum time:     73.100 μs (0.00% GC)
#   median time:      75.700 μs (0.00% GC)
#   mean time:        90.955 μs (3.73% GC)
#   maximum time:     3.807 ms (96.36% GC)
#   --------------
#   samples:          10000
#   evals/sample:     1


function BondNSxPs(m,b0,b1,b2,tau,b3=0.0,tau2=1.0)
#BondNSxPs    Extended Nelson and Siegel (1987) spot rate, forward rate, and discount function
#
#
#
#
#  Usage:    (s,f,d) = BondNSxPs(m,b0,b1,b2,tau[,b3[,tau2]])
#
#  Input:    m         NxK matrix  times to maturity in years (don't use less than 1e-8 or so)
#            b0        scalar, beta0 parameter in NS
#            b1        scalar, beta1 parameter in NS
#            b2        scalar, beta2 parameter in NS
#            tau       scalar, tau   parameter in NS
#            b3        (optional) scalar, beta2 parameter in extended NS [0]
#            tau2      (optional) scalar, tau2  parameter in extended NS [1]
#
#  Output:   s         NxK matrix,  spot rate (continously compounded)
#            f         NxK matrix,  forward rate (continously compounded)
#            d         NxK matrix,  discount function
#
#
#
#  Note:     For more details, see
#            (a) Svensson (1995), "Estimating Forward Interest Rates with
#                the Extended Nelson & Siegel Method," Quarterly Review,
#                Sveriges Riksbank, 1995:3, 13-26.
#            (b) Soderlind and Svensson (1997), "New Techniques to Extract
#                Market Expectations from Financial Instruments,"
#                Journal of Monetary Economics 40, 383-429.
#
#
#
#  Paul.Soderlind@unisg.ch, April 2002, to Julia 2015
#----------------------------------------------------------------------------*/

                                                 #forward rate
  f =  b0 .+ b1*exp.(-m/tau) + b2*(m/tau).*exp.(-m/tau) + b3*(m/tau2).*exp.(-m/tau2)

  s =  b0 .+ b1* (1 .- exp.(-m/tau)) ./(m/tau) +
             b2*((1 .- exp.(-m/tau)) ./(m/tau)  - exp.(-m/tau)) +
             b3*((1 .- exp.(-m/tau2))./(m/tau2) - exp.(-m/tau2))        #spot rate

  d = exp.(-s.*m)             #discount function

  return s,f,d

end
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
function BondNSxEstPs(par0,Q,tm,c,s0,ytmLoss=0,weight=1.0)
#BondNSxEstPs  Estimates parameters in (extended) Nelson-Siegel yield curve model by
#              non-linear least squares (minimizing squared differences between
#              actual and fitted bond prices or yield to maturities).
#
#
#
#  Usage:    NSb = BondNSxEstPs(par0,Q,tm,c,s0[,ytmLoss[,weight]])
#
#
#  Input:    par0       4x1 or 6x1 vector, initial guess of paremeters
#                         if 4x1: standard NS with par0 = [b0,b1,b2,tau]
#                         if 6x1: extended NS with par0 = [b0,b1,b2,tau,b3,tau2]
#            Q          nx1 vector, data on bond prices, eg. 1.01
#            tm         nx1 vector, data on time to maturity (in years), eg. 2.54
#            c          nx1 vector, data on bond coupons, eg. 0.06
#            s0         scalar, restricted value of short rate:
#                       if [] no restriction, else b0 + b1 = s0 is imposed
#            ytmLoss    scalar, scalar, 0: mimimize squared price errors;
#                                       1: minimize squared ytm errors (default 0)
#            weight     scalar or nx1 vector, weights in the loss function
#
#  Output:   NSb        4x1 or 6x1 vector, estimated parameters
#
#
#
#  Note:     For more details, see
#            (a) Svensson (1995), "Estimating Forward Interest Rates with
#                the Extended Neolson & Siegel Method," Quarterly Review,
#                Sveriges Riksbank, 1995:3, 13-26.
#            (b) Soderlind and Svensson (1997), "New Techniques to Extract
#                Market Expectations from Financial Instruments,"
#                Journal of Monetary Economics 40, 383-429.
#
#
#  Uses:  Optim
#
#
#
#  Paul.Soderlind@unisg.ch, April 2002, to Julia Nov 2015
#------------------------------------------------------------------------------

  Qtc = [Q tm c]

  if in(length(par0),[4,6]) && !isempty(s0)
      par0 = deleteat!(par0,2)   #b1 = s0 - b0 is imposed by BondNSxLossPs
  end

  Sol = optimize(b->BondNSxLossPs(b,Qtc,s0,ytmLoss,weight),par0)
  NSb = Optim.minimizer(Sol)
  if !Optim.converged(Sol)
    println("no convergence")
    return
  end

  if length(NSb) == 3
    NSb[[1,3]] = abs.(NSb[[1,3]])
    NSb        = [NSb[1]; s0-NSb[1]; NSb[2:3]]
  elseif length(NSb) == 4
    NSb[[1,4]] = abs.(NSb[[1,4]])
  elseif length(NSb) == 5
    NSb[[1,3,5]] = abs.(NSb[[1,3,5]])
    NSb = [NSb[1]; s0-NSb[1]; NSb[2:5]]
  elseif length(NSb) == 6
    NSb[[1,4,6]] = abs.(NSb[[1,4,6]])
  end

  return NSb

end
#----------------------------------------------------------------------------

#------------------------------------------------------------------------------
function BondNSxLossPs(b,Qtc,s0,ytmLoss=0,weight=1.0)
#BondNSxLossPs    Defines loss function for bond prices in extended Nelson-Siegel model.
#                 Used for estimation of the parameters in the model by minimizing
#                 Loss (squared price deviations, possibly with weights).
#
#
#  Usage:    Loss = BondNSxLossPs(b,Qtc,s0,ytmLoss,weight)    or
#                 = BondNSxLossPs(b,Qtc,s0,ytmLoss)           or
#                 = BondNSxLossPs(b,Qtc,s0)
#
#  Input:    b         3x1, 4x1, 5x1, or 6x1 vector with parameters in Nelson-Siegel model
#                        if 3x1: [b0,b2,tau], NS with restriction that b1 = g_s0 - b0
#                        if 4x1: [b0,b1,b2,tau], NS without restrictions
#                        if 5x1: [b0,b2,tau,b3,tau2], extended NS with restriction that b1 = g_s0 - b0
#                        if 6x1: [b0,b1,b2,tau,b3,tau2], extended NS without restrictions
#            Qtc       nx3 matrix, data on [bond prices,time to maturity,coupons]. Use ytm instead
#                      of bond prices if ytmLoss==1
#            s0        scalar, restricted value of short rate
#            ytmLoss   scalar, 0: mimimize squared price errors;
#                      1: minimize squared ytm errors (default 0)
#            weight    scalar or nx1 vector, weights in the loss function
#
#  Output:   Loss    scalar, sum of squared differences between implied and actual bond
#                    prices (or yields)
#
#  Calls on: BondNSxPs, BondYieldToMatPs (if ytmLoss==1)
#
#------------------------------------------------------------------------------

  (Q,tm,c) = [Qtc[:,i] for i=1:3]       #data on bond prices, time to maturity, coupons
  n        = length(c)           #number of bonds

  if length(b) == 3             #standard Nelson-Siegel with restriction b1 = s0-b0
    (b0,b1,b2,tau,b3,tau2) = (abs(b[1]),s0-abs(b[1]),b[2],abs(b[3]),0.0,1.0)
  elseif length(b) == 4         #standard Nelson-Siegel
   (b0,b1,b2,tau,b3,tau2)  = (abs(b[1]),b[2],b[3],abs(b[4]),0.0,1.0)
  elseif length(b) == 5         #extended Nelson-Siegel with restriction b1 = s0 - b0
    (b0,b1,b2,tau,b3,tau2) = (abs(b[1]),s0-abs(b[1]),b[2],abs(b[3]),b[4],abs(b[5]))
  elseif length(b) == 6         #extended Nelson-Siegel
   (b0,b1,b2,tau,b3,tau2)  = (abs(b[1]),b[2],b[3],abs(b[4]),b[5],abs(b[6]))
  end

  QNS = fill(NaN,n)
  for i = 1:n                            #loop over bonds
    ti     = filter(z->z>0,mod(tm[i],1):tm[i])      #time to coupon payments, >0 only
    d      = BondNSxPs(ti,b0,b1,b2,tau,b3,tau2)[3]  #NSx: spot, forward, discount fn
    QNS[i] = sum(d.*c[i]) + d[end]                  #fitted bond price
    if ytmLoss == 1
      QNS[i] = BondYieldToMatPs(QNS[i],c[i],ti,1,1,0.05,1e-7)  #fitted ytm
    end
  end
  Loss = 1.0 + 100*sum( weight.*(QNS - Q).^2 )  #weighted sum of squared deviations of fitted from actual

  return Loss

end
#------------------------------------------------------------------------------


"""
    natural_eigdist(𝚽, 𝛌, Q; α = 1.0, T = :Inf,
                    input_format = :zero_measures, distance = :DAG,
                    edge_weight = 1, edge_length = 1)

compute natural distances between graph Laplacian eigenvectors.

# Input Arguments
- `𝚽::Matrix{Float64}`: matrix of (weighted) graph Laplacian eigenvectors.
- `𝛌::Vector{Float64}`: vector of eigenvalues.
- `Q::Matrix{Float64}`: unweighted incidence matrix of the graph.
- `α::Float64`: ROT parameter. (default: `1.0`)
- `T::Any`: TSD parameter, i.e., the stopping time T in K_functional (default: `:Inf`)
- `input_format::Symbol`: options: `:zero_measures`, `:pmf1` and `:pmf2` (default: `:zero_measures`)
- `distance::Symbol`: options: `:ROT`, `:HAD`, `:DAG` and `:TSD` (default: `:DAG`)
- `edg_length::Any`: vector of edge lengths (default: 1 represents unweighted graphs)
- `edge_weight::Any`: the weights vector, which stores the affinity weight of
    each edge (default: `1` represents unweighted graphs).

# Output Argument
- `dis::Matrix{Float64}`: the distance matrix, dis[i,j] = d(𝜙ᵢ₋₁, 𝜙ⱼ₋₁).

"""
function natural_eigdist(𝚽, 𝛌, Q; α = 1.0, T = :Inf,
                         input_format = :zero_measures, distance = :DAG,
                         edge_weight = 1, edge_length = 1)
    N = size(Q, 1)
    P = deepcopy(𝚽)
    if input_format == :zero_measures
        P[:, 1] .= 0
    elseif input_format == :pmf1
        P = P.^2
    elseif input_format == :pmf2
        P = exp.(P) ./ sum(exp.(P), dims = 1)
    else
        @error("input_format does not support $(input_format)!")
        return
    end

    if distance == :ROT
        D = eigROT_Distance(P, Q; edge_length = edge_length, α = α)
    elseif distance == :HAD
        D = eigHAD_Distance(𝚽, 𝛌)
    elseif distance == :DAG
        D = eigDAG_Distance(𝚽, Q, N; edge_weight = edge_weight)
    elseif distance == :TSD
        tL = Q * Q'
        t𝛌, t𝚽 = eigen(Matrix(tL))
        D = eigTSD_Distance(P, t𝚽, t𝛌, Q; length = edge_length, T = T)
    else
        error("distance does not support $(distance)!")
        return
    end

    return D
end


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

"""
    euclidsq(x, y)

Return the Euclidean distance between two indexable objects.
"""
euclidsq(x, y) = sum((x[i] - y[i])^2 for i in eachindex(x))

# Support matrix data in a GPU and AD compatible way

"""
    euclidsq(X::T, Y::T) where {T<:AbstractMatrix}

Return the Euclidean distance between columns in two matrices.
"""
function euclidsq(X::T, Y::T) where {T<:AbstractMatrix}
    XiXj = transpose(X) * Y
    x² = sum(X .^ 2; dims=1)
    y² = sum(Y .^ 2; dims=1)
    transpose(x²) .+ y² - 2XiXj
end

"""
    euclidsq(X::T) where {T<:AbstractMatrix}

Effective version of `euclidsq(X, X)`.
"""
function euclidsq(X::T) where {T<:AbstractMatrix}
    XiXj = transpose(X) * X
    x² = sum(X .^ 2; dims=1)
    transpose(x²) .+ x² - 2XiXj
end

"""
    gaussian_gramian(xs, ys, σ=1)

Gramian matrix for samples `xs` and `ys` using a Gaussian kernel
kernel with bandwidth `σ`.
"""
gaussian_gramian(xs; kwargs...) = gaussian_gramian(xs, xs; kwargs...)
gaussian_gramian(xs, ys; σ=1) =
  [exp(-euclidsq(x, y) / 2σ^2) for x in xs, y in ys]

function gaussian_gramian(X::T, Y::T; σ=1) where {T<:AbstractMatrix}
    gaussian_gramian(euclidsq(X, Y), σ)
end

gaussian_gramian(esq, σ::AbstractFloat) = exp.(-esq ./ 2σ^2)

"""
    safe_diagm(mat, a)

Generate a squared matrix whose diagonal is `a` that is
compatible to perform addition on `mat`. It hebaves
differently based on `mat` is on CPU or GPU.

It is compatible with
- CuArrays.jl (see lib/cuarrays.jl)
- Zygote.jl (see lib/zygote.jl)
"""
safe_diagm(mat, a) = a * I


# These methods are used solely to help the auto-arima algorithm
# Kwiatkowski, D.;
# Phillips, P. C. B.;
# Schmidt, P.;
# Shin, Y. (1992)
# "Testing the null hypothesis
#  of stationarity against the
#  alternative of a unit root"

function calc_lag_kpss(lags::Bool, n::Int)
    return lags ? trunc(Int, 4*(n/100)^0.25) : trunc(Int, 12*(n/100)^0.25)
end

function σ²_estimator(l::Int, n::Int, ϵ::Vector{Fl}) where Fl
    return ((1/n)*sum(ϵ[t]^2 for t in 1:n)) + ((2/n)*sum((1-(s/(l + 1))) * (sum(ϵ[t]*ϵ[t - s] for t in s + 1:n)) for s in 1:l))
end

function p_value_weighted_average(η::Fl, p1::Fl, cv1::Fl,
                                  p2::Fl, cv2::Fl) where Fl
    return ((p1*(cv1 - cv2)) - ((cv1 - η)*(p1 - p2)))/(cv1 - cv2)
end

function p_value_from_η(η::Fl, crit_vals::Vector{Fl}) where Fl
    if η >= crit_vals[1]
        p_value = 0.01
    elseif η < crit_vals[1] && η >= crit_vals[2]
        p_value = p_value_weighted_average(η, 0.01, crit_vals[1], 0.025, crit_vals[2])
    elseif η < crit_vals[2] && η >= crit_vals[3]
        p_value = p_value_weighted_average(η, 0.025, crit_vals[2], 0.05, crit_vals[3])
    elseif η < crit_vals[3] && η <= crit_vals[4]
        p_value = p_value_weighted_average(η, 0.05, crit_vals[3], 0.1, crit_vals[4])
    else
        p_value = 0.1
    end
    return p_value
end

function calc_η_level(y::Vector{Fl}, lags::Bool) where Fl
    n = length(y)
    ϵ = y .- (mean(y))
    l = calc_lag_kpss(lags, n)
    η = (1/n^2)*sum(sum((((sum(ϵ[i] for i in 1:t))^2)/σ²_estimator(l, n, ϵ)) for t in 1:n))
    return η
end

function kpss_test(y::Vector{Fl}; lags::Bool = true) where Fl
    η = calc_η_level(y, lags)
    crit_vals = [0.739, 0.574, 0.463, 0.347]
    p_value = p_value_from_η(η, crit_vals)
    return p_value
end


module Mandelbrot
export mandelbrot_image, mandelbrot

using Base.Threads

function mandelbrot(c::Complex, max_iter::Int)
    z = 0.0 + 0.0im
    for i in 1:max_iter
        z = z^2 + c
        if abs(z) > 2
            return max_iter - i
        end
    end
    return 0
end

function mandelbrot_image(width, height, max_iter = 1000; rmin = -2, rmax = 1, imin = -1, imax = 1)
    A = [
        Complex((j/width * (rmax - rmin) + rmin), (i/height * (imax - imin) + imin))
        for i in 1:height, j in 1:width
    ]

    set = Array{Int}(undef, height, width)

    @time @inbounds @threads for i in 1:height
        for j in 1:width
            set[i, j] = mandelbrot(A[i, j], max_iter)
        end
    end

    return set
end

end


firstPayment = 3000
i = 0.12
m = 12

# (A/P, i, N)
function capitalRecoveryFactor(i, N)
	(i * (1 + i)^N) / ((i+1)^N - 1)
end

# Find: How much the guy paid the student
P = firstPayment / (1 + i/m)^(m * 1/12)
A = P * capitalRecoveryFactor(i/m, 12)
C = A * 1 / capitalRecoveryFactor(i/m, 5) + A + 1000

println("Guy paid: ", C)


function hermite_polynomial_coefficient_matrix(left,right)
    [1.0   left   left^2   left^3
     0.0   1.0   2.0*left  3.0*(left^2)
     1.0   right  right^2  right^3
     0.0   1.0   2.0*right 3.0*right^2]
end

function hermite_polynomial_coefficients(left,right)
    M = hermite_polynomial_coefficient_matrix(left,right)
    coeffs = M\I
end

function hermite_polynomials(x,left,right)
    coeffs = hermite_polynomial_coefficients(left,right)
    order,numbasis = size(coeffs)
    return [polynomial_from_coefficients(x,coeffs[:,i]) for i = 1:numbasis]
end


"""
FLOYDWARSHALL
-------------
    compute all shortest paths using the Floyd-Warshall algorithm.

    (D,P) = floydwarshall(A) returns the shortest distance matrix between all pairs
    of nodes in the graph A in matrix D.  If A has a negative weight cycle, then this
    algorithm will throw an error. P is the matrix of predecessors.

Functions
---------
- (D,P) = floydwarshall(A::MatrixNetwork)
- (D,P) = floydwarshall{T}(A::SparseMatrixCSC{T,Int64})

Example
-------
~~~
A = load_matrix_network("all_shortest_paths_example")
(D,P) = floydwarshall(A)
~~~
"""
:floydwarshall

## setup functions:

function floydwarshall_phase1(A::MatrixNetwork)
    (nzi,nzj,nzv) = csr_to_sparse(A.rp,A.ci,A.vals,A.n)
    return (nzi,nzj,nzv,A.n)
end

function floydwarshall_phase1{T}(A::SparseMatrixCSC{T,Int64})
    (ri,ci,ai) = findnz(A)
    return (ri,ci,ai,A.n)
end

function floydwarshall_phase2{T}(ri::Vector{Int64},ci::Vector{Int64},ai::Vector{T},n::Int64)

    nz = length(ai)
    D = Inf*ones(Int64,n,n)

    #TODO: check: always compute P or give the option of just computing D?

    P = zeros(Int64,n,n)
    # initialize the distance and predecessor matrix
    for ei = 1:nz
        i = ri[ei]
        j = ci[ei]
        v = ai[ei]
        if v < D[i,j]
            D[i,j] = v
            P[i,j] = i
        end
    end

    ids = sub2ind((n,n),1:n,1:n)
    D[ids] = 0 # set diagonal to 0

    for k=1:n
        for i=1:n
            for j=1:n
                if D[i,k]+D[k,j] < D[i,j]
                    D[i,j] = D[i,k]+D[k,j]
                    P[i,j] = P[k,j]
                end
            end
        end
    end

    if any(diag(D).<0)
        warn("floydwarshall:negativeCycle","negative weight cycle detected")
    end

    return (D,P)
end

## floyd warshall
function floydwarshall(A::MatrixNetwork)
    (nzi,nzj,nzv,n) = floydwarshall_phase1(A)
    (D,P) = floydwarshall_phase2(nzi,nzj,nzv,n)
    return (D,P)
end

function floydwarshall{T}(A::SparseMatrixCSC{T,Int64})
    (nzi,nzj,nzv,n) = floydwarshall_phase1(A)
    (D,P) = floydwarshall_phase2(nzi,nzj,nzv,n)
    return (D,P)
end


using Turing, Distributions, DifferentialEquations

# Import MCMCChain, Plots, and StatsPlots for visualizations and diagnostics.
using MCMCChains, Plots, StatsPlots

# Set a seed for reproducibility.
using Random
Random.seed!(14);

function lotka_volterra(du,u,p,t)
  x, y = u
  α, β, γ, δ  = p
  du[1] = (α - β*y)x # dx =
  du[2] = (δ*x - γ)y # dy =
end
p = [1.5, 1.0, 3.0, 1.0]
u0 = [1.0,1.0]
prob1 = ODEProblem(lotka_volterra,u0,(0.0,10.0),p)
sol = solve(prob1,Tsit5())
plot(sol)

sol1 = solve(prob1,Tsit5(),saveat=0.1)
odedata = Array(sol1) + 0.8 * randn(size(Array(sol1)))
plot(sol1, alpha = 0.3, legend = false); scatter!(sol1.t, odedata')

Turing.setadbackend(:forwarddiff)

@model function fitlv(data, prob1)
    σ ~ InverseGamma(2, 3) # ~ is the tilde character
    α ~ truncated(Normal(1.5,0.5),0.5,2.5)
    β ~ truncated(Normal(1.2,0.5),0,2)
    γ ~ truncated(Normal(3.0,0.5),1,4)
    δ ~ truncated(Normal(1.0,0.5),0,2)

    p = [α,β,γ,δ]
    prob = remake(prob1, p=p)
    predicted = solve(prob,Tsit5(),saveat=0.1)

    for i = 1:length(predicted)
        data[:,i] ~ MvNormal(predicted[i], σ)
    end
end

model = fitlv(odedata, prob1)

# This next command runs 3 independent chains without using multithreading.
chain = mapreduce(c -> sample(model, NUTS(.65),1000), chainscat, 1:3)

plot(chain)

pl = scatter(sol1.t, odedata');

chain_array = Array(chain)
for k in 1:300
    resol = solve(remake(prob1,p=chain_array[rand(1:1500), 1:4]),Tsit5(),saveat=0.1)
    plot!(resol, alpha=0.1, color = "#BBBBBB", legend = false)
end
# display(pl)
plot!(sol1, w=1, legend = false)

@model function fitlv2(data, prob1) # data should be a Vector
    σ ~ InverseGamma(2, 3) # ~ is the tilde character
    α ~ truncated(Normal(1.5,0.5),0.5,2.5)
    β ~ truncated(Normal(1.2,0.5),0,2)
    γ ~ truncated(Normal(3.0,0.5),1,4)
    δ ~ truncated(Normal(1.0,0.5),0,2)

    p = [α,β,γ,δ]
    prob = remake(prob1, p=p)
    predicted = solve(prob,Tsit5(),saveat=0.1)

    for i = 1:length(predicted)
        data[i] ~ Normal(predicted[i][2], σ) # predicted[i][2] is the data for y - a scalar, so we use Normal instead of MvNormal
    end
end

model2 = fitlv2(odedata[2,:], prob1)

Threads.nthreads()

# This next command runs 3 independent chains with multithreading.
chain2 = sample(model2, NUTS(.45), MCMCThreads(), 5000, 3, progress=false)

pl = scatter(sol1.t, odedata');
chain_array2 = Array(chain2)
for k in 1:300
    resol = solve(remake(prob1,p=chain_array2[rand(1:12000), 1:4]),Tsit5(),saveat=0.1)
    # Note that due to a bug in AxisArray, the variables from the chain will be returned always in
    # the order it is stored in the array, not by the specified order in the call - :α, :β, :γ, :δ
    plot!(resol, alpha=0.1, color = "#BBBBBB", legend = false)
end
#display(pl)
plot!(sol1, w=1, legend = false)

function delay_lotka_volterra(du, u, h, p, t)
   x, y = u
   α, β, γ, δ = p
   du[1] = α * h(p, t-1; idxs=1) - β * x * y
   du[2] = -γ * y + δ * x * y
   return
end

p = (1.5,1.0,3.0,1.0)
u0 = [1.0; 1.0]
tspan = (0.0,10.0)
h(p, t; idxs::Int) = 1.0
prob1 = DDEProblem(delay_lotka_volterra,u0,h,tspan,p)

sol = solve(prob1,saveat=0.1)
ddedata = Array(sol)
ddedata = ddedata + 0.5 * randn(size(ddedata))

scatter(sol.t, ddedata'); plot!(sol)

Turing.setadbackend(:forwarddiff)
@model function fitlv(data, prob1)

    σ ~ InverseGamma(2, 3)
    α ~ Truncated(Normal(1.5,0.5),0.5,2.5)
    β ~ Truncated(Normal(1.2,0.5),0,2)
    γ ~ Truncated(Normal(3.0,0.5),1,4)
    δ ~ Truncated(Normal(1.0,0.5),0,2)

    p = [α,β,γ,δ]

    #prob = DDEProblem(delay_lotka_volterra,u0,_h,tspan,p)
    prob = remake(prob1, p=p)
    predicted = solve(prob,saveat=0.1)
    for i = 1:length(predicted)
        data[:,i] ~ MvNormal(predicted[i], σ)
    end
end;
model = fitlv(ddedata, prob1)

chain = sample(model, NUTS(.65), MCMCThreads(), 300, 3, progress=true)
plot(chain)

chain

pl = scatter(sol.t, ddedata')
chain_array = Array(chain)
for k in 1:100
    resol = solve(remake(prob1,p=chain_array[rand(1:450),1:4]),Tsit5(),saveat=0.1)
    # Note that due to a bug in AxisArray, the variables from the chain will be returned always in
    # the order it is stored in the array, not by the specified order in the call - :α, :β, :γ, :δ

    plot!(resol, alpha=0.1, color = "#BBBBBB", legend = false)
end
#display(pl)
plot!(sol)

using Zygote, DiffEqSensitivity
Turing.setadbackend(:zygote)
prob1 = ODEProblem(lotka_volterra,u0,(0.0,10.0),p)

@model function fitlv(data, prob)
    σ ~ InverseGamma(2, 3)
    α ~ truncated(Normal(1.5,0.5),0.5,2.5)
    β ~ truncated(Normal(1.2,0.5),0,2)
    γ ~ truncated(Normal(3.0,0.5),1,4)
    δ ~ truncated(Normal(1.0,0.5),0,2)
    p = [α,β,γ,δ]
    prob = remake(prob, p=p)

    predicted = solve(prob,saveat=0.1)
    for i = 1:length(predicted)
        data[:,i] ~ MvNormal(predicted[i], σ)
    end
end;
model = fitlv(odedata, prob1)
chain = sample(model, NUTS(.65),1000)

@model function fitlv(data, prob)
    σ ~ InverseGamma(2, 3)
    α ~ truncated(Normal(1.5,0.5),0.5,2.5)
    β ~ truncated(Normal(1.2,0.5),0,2)
    γ ~ truncated(Normal(3.0,0.5),1,4)
    δ ~ truncated(Normal(1.0,0.5),0,2)
    p = [α,β,γ,δ]
    prob = remake(prob, p=p)
    predicted = solve(prob,saveat=0.1,sensealg=InterpolatingAdjoint(autojacvec=ReverseDiffVJP(true)))
    for i = 1:length(predicted)
        data[:,i] ~ MvNormal(predicted[i], σ)
    end
end;
model = fitlv(odedata, prob1)
@time chain = sample(model, NUTS(.65),1000)

u0 = [1.0,1.0]
tspan = (0.0,10.0)
function multiplicative_noise!(du,u,p,t)
  x,y = u
  du[1] = p[5]*x
  du[2] = p[6]*y
end
p = [1.5,1.0,3.0,1.0,0.1,0.1]

function lotka_volterra!(du,u,p,t)
  x,y = u
  α,β,γ,δ = p
  du[1] = dx = α*x - β*x*y
  du[2] = dy = δ*x*y - γ*y
end

prob_sde = SDEProblem(lotka_volterra!,multiplicative_noise!,u0,tspan,p)

ensembleprob = EnsembleProblem(prob_sde)
@time data = solve(ensembleprob,SOSRI(),saveat=0.1,trajectories=1000)
plot(EnsembleSummary(data))

Turing.setadbackend(:forwarddiff)
@model function fitlv(data, prob)
    σ ~ InverseGamma(2,3)
    α ~ truncated(Normal(1.3,0.5),0.5,2.5)
    β ~ truncated(Normal(1.2,0.25),0.5,2)
    γ ~ truncated(Normal(3.2,0.25),2.2,4.0)
    δ ~ truncated(Normal(1.2,0.25),0.5,2.0)
    ϕ1 ~ truncated(Normal(0.12,0.3),0.05,0.25)
    ϕ2 ~ truncated(Normal(0.12,0.3),0.05,0.25)
    p = [α,β,γ,δ,ϕ1,ϕ2]
    prob = remake(prob, p=p)
    predicted = solve(prob,SOSRI(),saveat=0.1)

    if predicted.retcode != :Success
        Turing.acclogp!(_varinfo, -Inf)
    end
    for j in 1:length(data)
        for i = 1:length(predicted)
            data[j][i] ~ MvNormal(predicted[i],σ)
        end
    end
end;

model = fitlv(data, prob_sde)
chain = sample(model, NUTS(0.25), 5000, init_theta = [1.5,1.3,1.2,2.7,1.2,0.12,0.12])
plot(chain)

if isdefined(Main, :TuringTutorials)
    Main.TuringTutorials.tutorial_footer(WEAVE_ARGS[:folder], WEAVE_ARGS[:file])
end


# rmsd100
# root mean-squared difference between two arrays in %

export rmsd100

using LinearAlgebra: norm

"""
    rmsd = rmsd100(x, y ; mask)

Compute 100 * RMSD (root mean squared difference) between `x` and `y` within domain mask.

in
- `x` : array
- `y` : another array of same size

option:
- `mask::Array{Bool}` : domain over which to compute the RMSE; default `trues(size(x))`

out
- rmsd : rmsd of `x` vs `y` within `mask` in %
"""
function rmsd100(x::AbstractArray{<:Number}, y::AbstractArray{<:Number}
        ; mask::AbstractArray{Bool} = trues(size(x)))
    return 100 * norm(x[mask] - y[mask]) / sqrt(sum(mask))
end


# This file is part of the IntervalArithmetic.jl package; MIT licensed

"""
    setdiff(x::Interval, y::Interval)

Calculate the set difference `x ∖ y`, i.e. the set of values
that are inside the interval `x` but not inside `y`.

Returns an array of intervals.
The array may:

- be empty if `x ⊆ y`;
- contain a single interval, if `y` overlaps `x`
- contain two intervals, if `y` is strictly contained within `x`.
"""
function setdiff(x::Interval{T}, y::Interval{T}) where T
    intersection = x ∩ y

    isempty(intersection) && return [x]
    intersection == x && return [emptyinterval(T)]

    if x.lo == intersection.lo
        if isfinite(intersection.hi)
            return [unsafe_interval(intersection.hi, x.hi)]
        else
            return [emptyinterval(T)]
        end
    end
    if x.hi == intersection.hi
        if isfinite(intersection.lo)
            return [unsafe_interval(x.lo, intersection.lo)]
        else
            return [emptyinterval(T)]
        end
    end

    return [unsafe_interval(x.lo, y.lo),
            unsafe_interval(y.hi, x.hi)]
end
setdiff(x::ValidInterval, ::ValidInterval) = [x]

"""
    distance(a::Interval, b::Interval)

Hausdorff distance between two intervals.
"""
distance(a::Interval, b::Interval) = max(abs(a.lo-b.lo), abs(a.hi-b.hi))

const where_bisect = 0.49609375

"""
    bisect(X::Interval, α=0.49609375)

Split the interval `X` at position α; α=0.5 corresponds to the midpoint.
Returns a tuple of the new intervals.
"""
function bisect(X::Interval, α=where_bisect)
    @assert 0 ≤ α ≤ 1

    m = mid(X, α)

    return (unsafe_interval(X.lo, m), unsafe_interval(m, X.hi))
end

"""
    mid(a::Interval, α=0.5)

Find an intermediate point at a relative position `α`` in the interval `a`.
The default is the true midpoint at `α = 0.5`.

Assumes 0 ≤ α ≤ 1.

Warning: if the parameter `α = 0.5` is explicitly set, the behavior differs
from the default case if the provided `Interval` is not finite, since when
`α` is provided `mid` simply replaces `+Inf` (respectively `-Inf`) by `prevfloat(+Inf)`
(respecively `nextfloat(-Inf)`) for the computation of the intermediate point.
"""
function mid(a::Interval{T}, α) where T

    lo = (a.lo == -Inf ? nextfloat(T(-Inf)) : a.lo)
    hi = (a.hi == +Inf ? prevfloat(T(+Inf)) : a.hi)

    β = T(α)

    midpoint = β * (hi - lo) + lo
    isfinite(midpoint) && return midpoint
    #= Fallback in case of overflow: hi - lo == +Inf.
       This case can not be the default one as it does not pass several
       IEEE1788-2015 tests for small floats.
    =#
    return (1 - β) * lo + β * hi
end

mid(::EmptyInterval{T}, α) where T = T(NaN)


function piecewise(x)
	IfElse.ifelse(x[1] < 0.5, 0.0,
		IfElse.ifelse(x[1] < 2.0, m_ss * x[1] - 0.5 * m_ss,
			-0.250 * m_ss * (x[1] - 2.0) + 1.5 * m_ss))
end

function d_piecewise(x)
	IfElse.ifelse(x[1] < 0.5, 0.0,
		IfElse.ifelse(x[1] < 2.0, m_ss,
			-0.250 * m_ss))
end

function mirror_and_repeat(x; l::T = 5.0) where {T}
	# l = length of the pattern  ---^_/^-
	# we repeat it this way ---^_/^--^\_^------^_/^--^\_^---
	# section ≡ even -> normal pattern
	# section ≡ odd -> mirrored pattern
	section = floor(x / l)
	x_rel = x % l
	mir = (section % 2) == 1
	if !mir
		x_mir = x_rel
	elseif mir # mirroring
		x_mir = l - x_rel
	end
	mult = mir == 1 ? -1 : 1
	return x_mir, mult
end

# smooth kinks w/ cubic polynomial
poly(a, z) = a[4] + a[3] * z + a[2] * z^2.0 + a[1] * z^3.0
d_poly(a, z) = a[3] + 2.0 * a[2] * z + 3.0 * a[1] * z^2.0

function generate_piecewise_terrain(mss; repeat::Bool = false)
	# piece 1
	m1 = 0.0
	x1 = 0.4
	y1 = m1 * x1

	m2 = m_ss
	x2 = 0.6
	y2 = m2 * 0.1

	A1 = [x1^3.0 x1^2.0 x1 1.0;
	      x2^3.0 x2^2.0 x2 1.0;
		  3.0 * x1^2.0 2.0 * x1 1.0 0.0;
		  3.0 * x2^2.0 2.0 * x2 1.0 0.0]
	b1 = [y1; y2; m1; m2]

	a1 = A1 \ b1

	@assert isapprox(poly(a1, x1) - y1, 0.0, atol = 1.0e-8)
	@assert isapprox(poly(a1, x2) - y2, 0.0, atol = 1.0e-8)

	# piece 2
	m1 = m_ss
	x1 = 1.4
	y1 = m_ss * x1

	m2 = -0.250 * m_ss
	x2 = 1.6
	y2 = m_ss * 1.5 + m2 * 0.1

	A2 = [x1^3.0 x1^2.0 x1 1.0;
	      x2^3.0 x2^2.0 x2 1.0;
		  3.0 * x1^2.0 2.0 * x1 1.0 0.0;
		  3.0 * x2^2.0 2.0 * x2 1.0 0.0]
	b2 = [y1; y2; m1; m2]

	a2 = A2 \ b2

	@assert isapprox(poly(a2, x1) - y1, 0.0, atol = 1.0e-8)
	@assert isapprox(poly(a2, x2) - y2, 0.0, atol = 1.0e-8)

	if !repeat
		function piecewise_smoothed(x)
			IfElse.ifelse(x[1] < 0.4, 0.0,
				IfElse.ifelse(x[1] < 0.6, poly(a1, x[1]),
					IfElse.ifelse(x[1] < 1.9, m_ss * x[1] - 0.5 * m_ss,
						IfElse.ifelse(x[1] < 2.1, poly(a2, x[1] - 0.5),
						-0.250 * m_ss * (x[1] - 2.0) + 1.5 * m_ss))))
		end

		function d_piecewise_smoothed(x)
			IfElse.ifelse(x[1] < 0.4, 0.0,
				IfElse.ifelse(x[1] < 0.6, d_poly(a1, x[1]),
					IfElse.ifelse(x[1] < 1.9, m_ss,
						IfElse.ifelse(x[1] < 2.1, d_poly(a2, x[1] - 0.5),
						-0.250 * m_ss))))
		end
		return piecewise_smoothed, d_piecewise_smoothed
	elseif repeat
		@show "repp"
		function piecewise_repeat_smoothed(x)
			x_mir, mult = mirror_and_repeat(x[1]; l = 5.0)
			IfElse.ifelse(x_mir < 0.4, mult * 0.0,
				IfElse.ifelse(x_mir < 0.6, mult * poly(a1, x_mir),
					IfElse.ifelse(x_mir < 1.9, mult * (m_ss * x_mir - 0.5 * m_ss),
						IfElse.ifelse(x_mir < 2.1, mult * poly(a2, x_mir - 0.5),
						mult * (-0.250 * m_ss * (x_mir - 2.0) + 1.5 * m_ss)))))
		end

		function d_piecewise_repeat_smoothed(x)
			x_mir, mult = mirror_and_repeat(x[1]; l = 5.0)
			IfElse.ifelse(x_mir < 0.4, 0.0,
				IfElse.ifelse(x_mir < 0.6, d_poly(a1, x_mir),
					IfElse.ifelse(x_mir < 1.9, m_ss,
						IfElse.ifelse(x_mir < 2.1, d_poly(a2, x_mir - 0.5),
						-0.250 * m_ss))))
		end
		return piecewise_repeat_smoothed, d_piecewise_repeat_smoothed
	end
end

# x = range(-1.0, stop = 4.0, length = 1000)
# plot(x, piecewise.(x))#, aspect_ratio = :equal)
# plot!(x, piecewise_smoothed.(x))#, aspect_ratio = :equal)
#
# plot(x, d_piecewise.(x), aspect_ratio = :equal)
# plot!(x, d_piecewise_smoothed.(x), aspect_ratio = :equal)

m_ss = tan(deg2rad(10.0)) # 10 degree slope
p1, dp1 = generate_piecewise_terrain(m_ss, repeat = false)
piecewise1_2D_lc = Environment{R2, LinearizedCone}(p1, dp1)
plot(-1:0.01:15, piecewise1_2D_lc.surf.(-1:0.01:15))
plot!(-1:0.01:15, piecewise1_2D_lc.surf_grad.(-1:0.01:15))

m_ss = tan(deg2rad(-15.0)) # 10 degree slope
p2, dp2 = generate_piecewise_terrain(m_ss)
piecewise2_2D_lc = Environment{R2, LinearizedCone}(p2, dp2)
# plot(-1:0.01:15, piecewise2_2D_lc.surf.(-1:0.01:15))
# plot!(-1:0.01:15, piecewise2_2D_lc.surf_grad.(-1:0.01:15))

# vis = Visualizer()
# open(vis)
# plot_surface!(vis, piecewise1_2D_lc, n = 100)
# plot_surface!(vis, piecewise2_2D_lc, n = 100)


# borrowed from KernelDensity.jl
# https://github.com/JuliaStats/KernelDensity.jl/blob/master/src/univariate.jl

"""
optimize(f, x_lower, x_upper; iterations=1000, rel_tol=nothing, abs_tol=nothing)

Minimize the function `f` in the interval `x_lower..x_upper`, using the
[golden-section search](https://en.wikipedia.org/wiki/Golden-section_search).
Return an approximate minimum `x̃` or error if such approximate minimum cannot be found.

This algorithm assumes that `-f` is unimodal on the interval `x_lower..x_upper`,
that is to say, there exists a unique `x` in `x_lower..x_upper` such that `f` is
decreasing on `x_lower..x` and increasing on `x..x_upper`.

`rel_tol` and `abs_tol` determine the relative and absolute tolerance, that is
to say, the returned value `x̃` should differ from the actual minimum `x` at most
`abs_tol + rel_tol * abs(x̃)`.
If not manually specified, `rel_tol` and `abs_tol` default to `sqrt(eps(T))` and
`eps(T)` respectively, where `T` is the floating point type of `x_lower` and `x_upper`.

`iterations` determines the maximum number of iterations allowed before convergence.

This is a private, unexported function, used internally to select the optimal bandwidth
automatically.
"""
function optimize(f, x_lower, x_upper; iterations=1000, rel_tol=nothing, abs_tol=nothing)

if x_lower > x_upper
    error("x_lower must be less than x_upper")
end

T = promote_type(typeof(x_lower/1), typeof(x_upper/1))
rtol = something(rel_tol, sqrt(eps(T)))
atol = something(abs_tol, eps(T))

function midpoint_and_convergence(lower, upper)
    midpoint = (lower + upper) / 2
    tol = atol + rtol * midpoint
    midpoint, (upper - lower) <= 2tol
end

invphi::T = 0.5 * (sqrt(5) - 1)
invphisq::T = 0.5 * (3 - sqrt(5))

a::T, b::T = x_lower, x_upper
h = b - a
c = a + invphisq * h
d = a + invphi * h

fc, fd = f(c), f(d)

for _ in 1:iterations
    h *= invphi
    if fc < fd
        m, converged = midpoint_and_convergence(a, d)
        converged && return m
        b = d
        d, fd = c, fc
        c = a + invphisq * h
        fc = f(c)
    else
        m, converged = midpoint_and_convergence(c, b)
        converged && return m
        a = c
        c, fc = d, fd
        d = a + invphi * h
        fd = f(d)
    end
end

error("Reached maximum number of iterations without convergence.")
end


# 145 is a curious number, as 1! + 4! + 5! = 1 + 24 + 120 = 145.
#
# Find the sum of all numbers which are equal to the sum of the factorial of
# their digits.
#
# Note: as 1! = 1 and 2! = 2 are not sums they are not included.

using ProjectEulerSolutions

# Precompute one digit factorials, use mapreduce to quickly calculate sums of
# factorials of digits.
function p034solution()::Integer
    facs = map(factorial, 0:9) # precompute digit factorials
    sums = 0
    for i in 11:99999 # Arbitrary upper bound?
        if mapreduce(x -> facs[x + 1], +, digits(i)) == i
            sums += i
        end
    end
    return sums
end

p034 = Problems.Problem(p034solution)

Problems.benchmark(p034)


using LinearAlgebra
"""
    Compute Lyapunov Exponents and Covariant
    Lyapunov vectors.
    Reference: Ginelli 2013
	Inputs:
		DTu: dxdxm m-length timeseries of jacobian matrices
		du: tangent subspace dimension that the computed CLVs must span.
	Outputs:
		lyap_exps: the first du Lyapunov exponents
		Q: dxduxm m-length timeseries of the first du CLVs

"""
function clvs(DTu::Array{Float64,3},du::Int64)
    d = size(DTu)[1]
    m = size(DTu)[3]
    lyap_exps = zeros(du)
    R = zeros(du,du,m)
    Q = zeros(d,du,m)
    A = qr!(rand(d,du))
	Q[:,:,1] = Array(A.Q)
	R[:,:,1] = A.R
    for i=2:m
        Q[:,:,i] = DTu[:,:,i-1]*Q[:,:,i-1]
        A = qr!(Q[:,:,i])
		Q[:,:,i] = Array(A.Q)
		R[:,:,i] = A.R
        lyap_exps .+= log.(abs.(diag(R[:,:,i])))./m
    end
    C = zeros(du,du,m)
    C[:,:,end] = diagm(ones(du))
    for i=reverse(1:m-1)
        C[:,:,i] = R[:,:,i+1]\C[:,:,i+1]
        [normalize!(view(C,:,j,i)) for j = 1:du]
        Q[:,:,i] = Q[:,:,i]*C[:,:,i]
    end
    return lyap_exps, Q
end


"""
    FourierDerivativeOperator{T}

A derivative operator on a periodic grid with real scalar type `T` computing the
first derivative using a spectral Fourier expansion via real discrete Fourier
transforms.
"""
struct FourierDerivativeOperator{T<:Real, Grid, RFFT, BRFFT} <: AbstractPeriodicDerivativeOperator{T}
    jac::T
    Δx::T
    grid_compute::Grid   # N-1 nodes, including the left and excluding the right boundary
    grid_evaluate::Grid #  N  nodes, including both boundaries
    tmp::Vector{Complex{T}}
    rfft_plan::RFFT
    brfft_plan::BRFFT

    function FourierDerivativeOperator(jac::T, Δx::T, grid_compute::Grid, grid_evaluate::Grid,
                                        tmp::Vector{Complex{T}}, rfft_plan::RFFT, brfft_plan::BRFFT) where {T<:Real, Grid, RFFT, BRFFT}
        @argcheck length(brfft_plan) == length(tmp) DimensionMismatch
        @argcheck length(brfft_plan) == (length(rfft_plan)÷2)+1 DimensionMismatch
        @argcheck length(grid_compute) == length(rfft_plan) DimensionMismatch
        @argcheck length(grid_compute) == length(grid_evaluate)-1 DimensionMismatch
        @argcheck first(grid_compute) == first(grid_evaluate)
        @argcheck step(grid_compute) ≈ step(grid_evaluate)
        @argcheck last(grid_compute) < last(grid_evaluate)

        new{T, Grid, RFFT, BRFFT}(jac, Δx, grid_compute, grid_evaluate, tmp, rfft_plan, brfft_plan)
    end
end

"""
    FourierDerivativeOperator(xmin::T, xmax::T, N::Integer) where {T<:Real}

Construct the `FourierDerivativeOperator` on a uniform grid between `xmin` and
`xmax` using `N` nodes and `N÷2+1` complex Fourier modes.
"""
function FourierDerivativeOperator(xmin::T, xmax::T, N::Integer) where {T<:Real}
    @argcheck N >= 1

    jac = 2*T(π) / (xmax - xmin) / N # / N because of brfft instead of BRFFT
    Δx = (xmax - xmin) / N
    grid_evaluate = range(xmin, stop=xmax, length=N+1) # two boundary nodes
    grid_compute = range(xmin, stop=grid_evaluate[end-1], length=N)
    u = zero.(grid_compute)
    rfft_plan = plan_rfft(u)
    uhat = rfft_plan*u
    brfft_plan = plan_brfft(uhat, N)

    FourierDerivativeOperator(jac, Δx, grid_compute, grid_evaluate, uhat, rfft_plan, brfft_plan)
end

"""
    fourier_derivative_operator(xmin::Real, xmax::Real, N::Integer)
    fourier_derivative_operator(; xmin::Real, xmax::Real, N::Integer)

Construct the `FourierDerivativeOperator` on a uniform grid between `xmin` and
`xmax` using `N` nodes and `N÷2+1` complex Fourier modes.
"""
function fourier_derivative_operator(xmin::Real, xmax::Real, N::Integer)
    FourierDerivativeOperator(promote(xmin, xmax)..., N)
end

function fourier_derivative_operator(; xmin::Real, xmax::Real, N::Integer)
    FourierDerivativeOperator(promote(xmin, xmax)..., N)
end

derivative_order(D::FourierDerivativeOperator) = 1
LinearAlgebra.issymmetric(D::FourierDerivativeOperator) = false

function Base.show(io::IO, D::FourierDerivativeOperator)
    if get(io, :compact, false)
        print(io, "Periodic 1st derivative Fourier operator")
    else
        grid = D.grid_evaluate
        print(io, "Periodic 1st derivative Fourier operator {T=", eltype(D), "} \n")
        print(io, "on a grid in [", first(grid), ", ", last(grid),
                    "] using ", length(D.rfft_plan), " nodes and ",
                    length(D.brfft_plan), " modes")
    end
end

function mul!(dest::AbstractVector{T}, D::FourierDerivativeOperator, u::AbstractVector{T}) where {T}
    @unpack jac, tmp, rfft_plan, brfft_plan = D
    N, _ = size(D)
    @boundscheck begin
        @argcheck N == length(u)
        @argcheck N == length(dest)
    end

    mul!(tmp, rfft_plan, u)
    @inbounds @simd for j in Base.OneTo(length(tmp)-1)
        tmp[j] *= (j-1)*im * jac
    end
    # see e.g. Steven G. Johnson (2011) Notes on FFT based differentiation
    if iseven(N)
        @inbounds tmp[end] = zero(eltype(tmp))
    else
        @inbounds tmp[end] *= (length(tmp)-1)*im * jac
    end
    mul!(dest, brfft_plan, tmp)
end

# TODO there is no 5 argument mul! in FFTW.jl...
# function mul!(dest::AbstractVector{T}, D::FourierDerivativeOperator, u::AbstractVector{T}, α, β) where {T}
#     @unpack jac, tmp, rfft_plan, brfft_plan = D
#     N, _ = size(D)
#     @boundscheck begin
#         @argcheck N == length(u)
#         @argcheck N == length(dest)
#     end

#     mul!(tmp, rfft_plan, u)
#     @inbounds @simd for j in Base.OneTo(length(tmp)-1)
#         tmp[j] *= (j-1)*im * jac
#     end
#     if iseven(N)
#         @inbounds tmp[end] = zero(eltype(tmp))
#     else
#         @inbounds tmp[end] *= (length(tmp)-1)*im * jac
#     end
#     mul!(dest, brfft_plan, tmp, α, β)
# end

function integrate(func, u::AbstractVector, D::FourierDerivativeOperator)
    @boundscheck begin
        length(u) == length(grid(D))
    end
    @unpack Δx = D

    @inbounds res = sum(func, u)

    Δx * res
end

function mass_matrix(D::FourierDerivativeOperator)
    @unpack Δx = D

    Δx * I
end

"""
    fourier_derivative_matrix(N, xmin::Real=0.0, xmax::Real=2π)

Compute the Fourier derivative matrix with respect to the corresponding nodal
basis using `N` nodes, see
Kopriva (2009) Implementing Spectral Methods for PDEs, Algorithm 18.
"""
function fourier_derivative_matrix(N, xmin::Real=0.0, xmax::Real=2π)
    T = promote_type(typeof(xmin), typeof(xmax))
    jac_2 = T(π) / (xmax - xmin)
    D = Array{T}(undef, N, N)
    @inbounds for j in 1:N, i in 1:N
        j == i && continue
        D[i,j] = (-1)^(i+j) * cot((i-j)*T(π)/N) * jac_2
        D[i,i] -= D[i,j]
    end
    D
end

function _coef_end(coef, D1::FourierDerivativeOperator{T}) where {T}
    if isodd(size(D1, 1))
        return coef
    else
        return ntuple(i -> isodd(i) ? coef[i] : zero(T), length(coef))
    end
end
# fallback for periodic FD operators etc.
_coef_end(coef, D1) = coef

struct FourierPolynomialDerivativeOperator{T<:Real, Grid, RFFT, BRFFT, N} <: AbstractPeriodicDerivativeOperator{T}
    D1::FourierDerivativeOperator{T,Grid,RFFT,BRFFT}
    coef::NTuple{N,T}
    coef_end::NTuple{N,T}

    function FourierPolynomialDerivativeOperator(D1::FourierDerivativeOperator{T,Grid,RFFT,BRFFT}, coef::NTuple{N,T}) where {T<:Real, Grid, RFFT, BRFFT, N}
        coef_end = _coef_end(coef, D1)
        new{T,Grid,RFFT,BRFFT,N}(D1, coef, coef_end)
    end
end

function FourierPolynomialDerivativeOperator(D1::FourierDerivativeOperator)
    T = eltype(D1)
    FourierPolynomialDerivativeOperator(D1, (zero(T), one(T)))
end

Base.size(poly::FourierPolynomialDerivativeOperator) = size(poly.D1)
function LinearAlgebra.issymmetric(poly::FourierPolynomialDerivativeOperator)
    @unpack coef = poly
    all(iszero, coef[idx] for idx in eachindex(coef) if iseven(idx))
end
grid(poly::FourierPolynomialDerivativeOperator) = grid(poly.D1)

function Base.show(io::IO, poly::FourierPolynomialDerivativeOperator)
    if get(io, :compact, false)
        print(io, "Fourier polynomial")
    else
        print(io, "Fourier polynomial with coefficients\n")
        print(io, poly.coef)
        print(io, "\nof the operator:\n")
        print(io, poly.D1)
    end
end

function Base.:*(D1::FourierDerivativeOperator, D2::FourierDerivativeOperator)
    T = eltype(D1)
    @argcheck T == eltype(D2) ArgumentError
    @argcheck D1.jac == D2.jac ArgumentError
    @argcheck D1.Δx == D2.Δx ArgumentError
    @argcheck D1.grid_compute == D2.grid_compute DimensionMismatch
    @argcheck D1.grid_evaluate == D2.grid_evaluate DimensionMismatch

    FourierPolynomialDerivativeOperator(D1, (zero(T), zero(T), one(T)))
end

function Base.literal_pow(::typeof(^), D1::FourierDerivativeOperator, ::Val{P}) where {P}
    T = eltype(D1)
    coef = Base.setindex( ntuple(_->zero(T), Val{P+1}()), one(T), P+1)
    FourierPolynomialDerivativeOperator(D1, coef)
end

function Base.:*(factor::Union{Real,Integer}, poly::FourierPolynomialDerivativeOperator)
    @unpack coef = poly
    for idx in 1:length(coef)
        coef = Base.setindex(coef, factor*coef[idx], idx)
    end

    FourierPolynomialDerivativeOperator(poly.D1, coef)
end

function Base.:*(poly::FourierPolynomialDerivativeOperator, factor::Union{Real,Integer})
    factor * poly
end

function Base.:*(D::FourierDerivativeOperator, factor::Union{Real,Integer})
    FourierPolynomialDerivativeOperator(D) * factor
end

function Base.:*(factor::Union{Real,Integer}, D::FourierDerivativeOperator)
    D * factor
end

function Base.:*(poly::FourierPolynomialDerivativeOperator, scaling::UniformScaling)
    scaling.λ * poly
end

function Base.:*(scaling::UniformScaling, poly::FourierPolynomialDerivativeOperator)
    poly * scaling
end

function Base.:*(D::FourierDerivativeOperator, scaling::UniformScaling)
    scaling * FourierPolynomialDerivativeOperator(D)
end

function Base.:*(scaling::UniformScaling, D::FourierDerivativeOperator)
    FourierPolynomialDerivativeOperator(D) * scaling
end

function Base.:*(poly1::FourierPolynomialDerivativeOperator, poly2::FourierPolynomialDerivativeOperator)
    T = eltype(poly1.D1)
    @argcheck T == eltype(poly2.D1) ArgumentError
    @argcheck poly1.D1.jac == poly2.D1.jac ArgumentError
    @argcheck poly1.D1.Δx == poly2.D1.Δx ArgumentError
    @argcheck poly1.D1.grid_compute == poly2.D1.grid_compute DimensionMismatch
    @argcheck poly1.D1.grid_evaluate == poly2.D1.grid_evaluate DimensionMismatch

    coef = mul_poly(poly1.coef, poly2.coef)
    FourierPolynomialDerivativeOperator(poly1.D1, coef)
end

function Base.:*(poly1::FourierPolynomialDerivativeOperator, D2::FourierDerivativeOperator)
    poly1 * FourierPolynomialDerivativeOperator(D2)
end

function Base.:*(D1::FourierDerivativeOperator, poly2::FourierPolynomialDerivativeOperator)
    FourierPolynomialDerivativeOperator(D1) * poly2
end

function Base.:+(poly1::FourierPolynomialDerivativeOperator, poly2::FourierPolynomialDerivativeOperator)
    T = eltype(poly1.D1)
    @argcheck T == eltype(poly2.D1) ArgumentError
    @argcheck poly1.D1.jac == poly2.D1.jac ArgumentError
    @argcheck poly1.D1.Δx == poly2.D1.Δx ArgumentError
    @argcheck poly1.D1.grid_compute == poly2.D1.grid_compute DimensionMismatch
    @argcheck poly1.D1.grid_evaluate == poly2.D1.grid_evaluate DimensionMismatch

    coef = add_poly(poly1.coef, poly2.coef)
    FourierPolynomialDerivativeOperator(poly1.D1, coef)
end

function Base.:+(D1::FourierDerivativeOperator, poly2::FourierPolynomialDerivativeOperator)
    FourierPolynomialDerivativeOperator(D1) + poly2
end

function Base.:+(poly1::FourierPolynomialDerivativeOperator, D2::FourierDerivativeOperator)
    poly1 + FourierPolynomialDerivativeOperator(D2)
end

function Base.:+(poly::FourierPolynomialDerivativeOperator, scaling::UniformScaling)
    @unpack coef = poly
    coef = Base.setindex(coef, coef[1] + scaling.λ, 1)

    FourierPolynomialDerivativeOperator(poly.D1, coef)
end

function Base.:+(scaling::UniformScaling, poly::FourierPolynomialDerivativeOperator)
    poly + scaling
end

function Base.:+(D::FourierDerivativeOperator, scaling::UniformScaling)
    FourierPolynomialDerivativeOperator(D) + scaling
end

function Base.:+(scaling::UniformScaling, D::FourierDerivativeOperator)
    D + scaling
end

function Base.:-(poly1::FourierPolynomialDerivativeOperator, poly2::FourierPolynomialDerivativeOperator)
    T = eltype(poly1.D1)
    @argcheck T == eltype(poly2.D1) ArgumentError
    @argcheck poly1.D1.jac == poly2.D1.jac ArgumentError
    @argcheck poly1.D1.Δx == poly2.D1.Δx ArgumentError
    @argcheck poly1.D1.grid_compute == poly2.D1.grid_compute DimensionMismatch
    @argcheck poly1.D1.grid_evaluate == poly2.D1.grid_evaluate DimensionMismatch

    coef = subtract_poly(poly1.coef, poly2.coef)
    FourierPolynomialDerivativeOperator(poly1.D1, coef)
end

function Base.:-(D1::FourierDerivativeOperator, poly2::FourierPolynomialDerivativeOperator)
    FourierPolynomialDerivativeOperator(D1) - poly2
end

function Base.:-(poly1::FourierPolynomialDerivativeOperator, D2::FourierDerivativeOperator)
    poly1 - FourierPolynomialDerivativeOperator(D2)
end

function Base.:-(poly::FourierPolynomialDerivativeOperator, scaling::UniformScaling)
    @unpack coef = poly
    coef = Base.setindex(coef, coef[1] - scaling.λ, 1)

    FourierPolynomialDerivativeOperator(poly.D1, coef)
end

function Base.:-(scaling::UniformScaling, poly::FourierPolynomialDerivativeOperator)
    @unpack coef = poly
    coef = Base.setindex(coef, scaling.λ - coef[1], 1)
    for idx in 2:length(coef)
        coef = Base.setindex(coef, -coef[idx], idx)
    end

    FourierPolynomialDerivativeOperator(poly.D1, coef)
end

function Base.:-(D::FourierDerivativeOperator, scaling::UniformScaling)
    FourierPolynomialDerivativeOperator(D) - scaling
end

function Base.:-(scaling::UniformScaling, D::FourierDerivativeOperator)
    scaling - D
end

function mul!(dest::AbstractVector{T}, poly::FourierPolynomialDerivativeOperator, u::AbstractVector{T}) where {T}
    @unpack D1, coef, coef_end = poly
    @unpack jac, tmp, rfft_plan, brfft_plan = D1
    N, _ = size(D1)
    @boundscheck begin
        @argcheck N == length(u)
        @argcheck N == length(dest)
    end

    mul!(tmp, rfft_plan, u)
    @inbounds @simd for j in Base.OneTo(length(tmp)-1)
        # *N ) / N: brfft instead of irfft
        tmp[j] *= evalpoly((j-1)*im * jac*N, coef) / N
    end
    # see e.g. Steven G. Johnson (2011) Notes on FFT based differentiation
    @inbounds tmp[end] *= evalpoly((length(tmp)-1)*im * jac*N, coef_end) / N
    mul!(dest, brfft_plan, tmp)
end

function LinearAlgebra.ldiv!(dest::AbstractVector{T}, rat::FourierPolynomialDerivativeOperator, u::AbstractVector{T}) where {T}
    @unpack D1, coef, coef_end = rat
    @unpack jac, tmp, rfft_plan, brfft_plan = D1
    N, _ = size(D1)
    @boundscheck begin
        @argcheck N == length(u)
        @argcheck N == length(dest)
    end

    mul!(tmp, rfft_plan, u)
    @inbounds @simd for j in Base.OneTo(length(tmp)-1)
        # *N ) / N: brfft instead of irfft
        tmp[j] /= (evalpoly((j-1)*im * jac*N, coef) * N)
    end
    # see e.g. Steven G. Johnson (2011) Notes on FFT based differentiation
    @inbounds tmp[end] /= (evalpoly((length(tmp)-1)*im * jac*N, coef_end) * N)
    mul!(dest, brfft_plan, tmp)
end

struct FourierRationalDerivativeOperator{T<:Real, Grid, RFFT, BRFFT, Nnum, Nden} <: AbstractPeriodicDerivativeOperator{T}
    D1::FourierDerivativeOperator{T,Grid,RFFT,BRFFT}
    num_coef::NTuple{Nnum,T}
    num_coef_end::NTuple{Nnum,T}
    den_coef::NTuple{Nden,T}
    den_coef_end::NTuple{Nden,T}

    function FourierRationalDerivativeOperator(D1::FourierDerivativeOperator{T,Grid,RFFT,BRFFT}, num_coef::NTuple{Nnum,T}, den_coef::NTuple{Nden,T}) where {T<:Real, Grid, RFFT, BRFFT, Nnum, Nden}
        num_coef_end = _coef_end(num_coef, D1)
        den_coef_end = _coef_end(den_coef, D1)
        new{T,Grid,RFFT,BRFFT,Nnum,Nden}(D1, num_coef, num_coef_end, den_coef, den_coef_end)
    end
end

Base.size(rat::FourierRationalDerivativeOperator) = size(rat.D1)
grid(rat::FourierRationalDerivativeOperator) = grid(rat.D1)

function Base.show(io::IO, rat::FourierRationalDerivativeOperator)
    if get(io, :compact, false)
        print(io, "Rational Fourier operator")
    else
        print(io, "Rational Fourier operator with coefficients\n")
        print(io, rat.num_coef)
        print(io, "\nand\n")
        print(io, rat.den_coef)
        print(io, "\nof the operator:\n")
        print(io, rat.D1)
    end
end

function LinearAlgebra.issymmetric(rat::FourierRationalDerivativeOperator)
    @unpack num_coef, den_coef = rat
    num_is_even = all(iszero, num_coef[idx] for idx in eachindex(num_coef) if iseven(idx))
    den_is_even = all(iszero, den_coef[idx] for idx in eachindex(den_coef) if iseven(idx))

    num_is_even == den_is_even
end

function FourierRationalDerivativeOperator(num::FourierPolynomialDerivativeOperator)
    T = eltype(num)
    FourierRationalDerivativeOperator(num.D1, num.coef, (one(T),))
end

function FourierRationalDerivativeOperator(D::FourierDerivativeOperator)
    FourierRationalDerivativeOperator(FourierPolynomialDerivativeOperator(D))
end

function Base.:/(num::FourierPolynomialDerivativeOperator, den::FourierPolynomialDerivativeOperator)
    @argcheck num.D1.jac == den.D1.jac ArgumentError
    @argcheck num.D1.Δx == den.D1.Δx ArgumentError
    @argcheck num.D1.grid_compute == den.D1.grid_compute DimensionMismatch
    @argcheck num.D1.grid_evaluate == den.D1.grid_evaluate DimensionMismatch

    FourierRationalDerivativeOperator(num.D1, num.coef, den.coef)
end

function Base.:/(num::FourierDerivativeOperator, den::FourierPolynomialDerivativeOperator)
    FourierPolynomialDerivativeOperator(num) / den
end

function Base.:/(num::FourierPolynomialDerivativeOperator, den::FourierDerivativeOperator)
    num / FourierPolynomialDerivativeOperator(den)
end

function Base.inv(rat::FourierRationalDerivativeOperator)
    FourierRationalDerivativeOperator(rat.D1, rat.den_coef, rat.num_coef)
end

function Base.inv(den::Union{FourierDerivativeOperator,FourierPolynomialDerivativeOperator})
    inv(FourierRationalDerivativeOperator(den))
end

function Base.:+(rat1::FourierRationalDerivativeOperator, rat2::FourierRationalDerivativeOperator)
    T = eltype(rat1)
    @argcheck T == eltype(rat2) ArgumentError
    @argcheck rat1.D1.jac == rat2.D1.jac ArgumentError
    @argcheck rat1.D1.Δx == rat2.D1.Δx ArgumentError
    @argcheck rat1.D1.grid_compute == rat2.D1.grid_compute DimensionMismatch
    @argcheck rat1.D1.grid_evaluate == rat2.D1.grid_evaluate DimensionMismatch

    num_coef = add_poly(mul_poly(rat1.num_coef, rat2.den_coef), mul_poly(rat1.den_coef, rat2.num_coef))
    den_coef = mul_poly(rat1.den_coef, rat2.den_coef)
    FourierRationalDerivativeOperator(rat1.D1, num_coef, den_coef)
end

function Base.:+(rat1::FourierRationalDerivativeOperator, rat2::Union{FourierDerivativeOperator,FourierPolynomialDerivativeOperator})
    rat1 + FourierRationalDerivativeOperator(rat2)
end

function Base.:+(rat1::Union{FourierDerivativeOperator,FourierPolynomialDerivativeOperator}, rat2::FourierRationalDerivativeOperator)
    FourierRationalDerivativeOperator(rat1) + rat2
end

function Base.:-(rat1::FourierRationalDerivativeOperator, rat2::FourierRationalDerivativeOperator)
    T = eltype(rat1)
    @argcheck T == eltype(rat2) ArgumentError
    @argcheck rat1.D1.jac == rat2.D1.jac ArgumentError
    @argcheck rat1.D1.Δx == rat2.D1.Δx ArgumentError
    @argcheck rat1.D1.grid_compute == rat2.D1.grid_compute DimensionMismatch
    @argcheck rat1.D1.grid_evaluate == rat2.D1.grid_evaluate DimensionMismatch

    num_coef = subtract_poly(mul_poly(rat1.num_coef, rat2.den_coef), mul_poly(rat1.den_coef, rat2.num_coef))
    den_coef = mul_poly(rat1.den_coef, rat2.den_coef)
    FourierRationalDerivativeOperator(rat1.D1, num_coef, den_coef)
end

function Base.:-(rat1::FourierRationalDerivativeOperator, rat2::Union{FourierDerivativeOperator,FourierPolynomialDerivativeOperator})
    rat1 - FourierRationalDerivativeOperator(rat2)
end

function Base.:-(rat1::Union{FourierDerivativeOperator,FourierPolynomialDerivativeOperator}, rat2::FourierRationalDerivativeOperator)
    FourierRationalDerivativeOperator(rat1) - rat2
end

function Base.:*(rat1::FourierRationalDerivativeOperator, rat2::FourierRationalDerivativeOperator)
    T = eltype(rat1)
    @argcheck T == eltype(rat2) ArgumentError
    @argcheck rat1.D1.jac == rat2.D1.jac ArgumentError
    @argcheck rat1.D1.Δx == rat2.D1.Δx ArgumentError
    @argcheck rat1.D1.grid_compute == rat2.D1.grid_compute DimensionMismatch
    @argcheck rat1.D1.grid_evaluate == rat2.D1.grid_evaluate DimensionMismatch

    num_coef = mul_poly(rat1.num_coef, rat2.num_coef)
    den_coef = mul_poly(rat1.den_coef, rat2.den_coef)
    FourierRationalDerivativeOperator(rat1.D1, num_coef, den_coef)
end

function Base.:*(rat1::FourierRationalDerivativeOperator, rat2::Union{FourierDerivativeOperator,FourierPolynomialDerivativeOperator})
    rat1 * FourierRationalDerivativeOperator(rat2)
end

function Base.:*(rat1::Union{FourierDerivativeOperator,FourierPolynomialDerivativeOperator}, rat2::FourierRationalDerivativeOperator)
    FourierRationalDerivativeOperator(rat1) * rat2
end

function Base.:/(rat1::FourierRationalDerivativeOperator, rat2::FourierRationalDerivativeOperator)
    T = eltype(rat1)
    @argcheck T == eltype(rat2) ArgumentError
    @argcheck rat1.D1.jac == rat2.D1.jac ArgumentError
    @argcheck rat1.D1.Δx == rat2.D1.Δx ArgumentError
    @argcheck rat1.D1.grid_compute == rat2.D1.grid_compute DimensionMismatch
    @argcheck rat1.D1.grid_evaluate == rat2.D1.grid_evaluate DimensionMismatch

    num_coef = mul_poly(rat1.num_coef, rat2.den_coef)
    den_coef = mul_poly(rat1.den_coef, rat2.num_coef)
    FourierRationalDerivativeOperator(rat1.D1, num_coef, den_coef)
end

function Base.:/(rat1::FourierRationalDerivativeOperator, rat2::Union{FourierDerivativeOperator,FourierPolynomialDerivativeOperator})
    rat1 / FourierRationalDerivativeOperator(rat2)
end

function Base.:/(rat1::Union{FourierDerivativeOperator,FourierPolynomialDerivativeOperator}, rat2::FourierRationalDerivativeOperator)
    FourierRationalDerivativeOperator(rat1) / rat2
end

function mul!(dest::AbstractVector{T}, rat::FourierRationalDerivativeOperator, u::AbstractVector{T}) where {T}
    @unpack D1, num_coef, num_coef_end, den_coef, den_coef_end = rat
    @unpack jac, tmp, rfft_plan, brfft_plan = D1
    N, _ = size(D1)
    @boundscheck begin
        @argcheck N == length(u)
        @argcheck N == length(dest)
    end

    mul!(tmp, rfft_plan, u)
    @inbounds @simd for j in Base.OneTo(length(tmp)-1)
        # *N ) / N: brfft instead of irfft
        tmp[j] *= evalpoly((j-1)*im * jac*N, num_coef) / (N * evalpoly((j-1)*im * jac*N, den_coef))
    end
    # see e.g. Steven G. Johnson (2011) Notes on FFT based differentiation
    @inbounds tmp[end] *= evalpoly((length(tmp)-1)*im * jac*N, num_coef_end) / (N * evalpoly((length(tmp)-1)*im * jac*N, den_coef_end))
    mul!(dest, brfft_plan, tmp)
end

function LinearAlgebra.ldiv!(dest::AbstractVector{T}, rat::FourierRationalDerivativeOperator, u::AbstractVector{T}) where {T}
    @unpack D1, num_coef, num_coef_end, den_coef, den_coef_end = rat
    @unpack jac, tmp, rfft_plan, brfft_plan = D1
    N, _ = size(D1)
    @boundscheck begin
        @argcheck N == length(u)
        @argcheck N == length(dest)
    end

    mul!(tmp, rfft_plan, u)
    @inbounds @simd for j in Base.OneTo(length(tmp)-1)
        # *N ) / N: brfft instead of irfft
        tmp[j] *= evalpoly((j-1)*im * jac*N, den_coef) / (N * evalpoly((j-1)*im * jac*N, num_coef))
    end
    # see e.g. Steven G. Johnson (2011) Notes on FFT based differentiation
    @inbounds tmp[end] *= evalpoly((N-1)*im * jac*N, den_coef_end) / (N * evalpoly((N-1)*im * jac*N, num_coef_end))
    mul!(dest, brfft_plan, tmp)
end

function Base.:\(rat::Union{FourierRationalDerivativeOperator,FourierPolynomialDerivativeOperator}, u::AbstractVector{T}) where {T}
    dest = similar(u)
    ldiv!(dest, rat, u)
end

struct PeriodicDerivativeOperatorQuotient{T<:Real, numDtype<:Union{PeriodicDerivativeOperator{T},FourierDerivativeOperator{T}}, denDtype<:Union{PeriodicDerivativeOperator{T},FourierDerivativeOperator{T}}, Nnum, Nden, RFFT, IRFFT} <: AbstractPeriodicDerivativeOperator{T}
    num_D::numDtype
    den_D::denDtype
    num_coef::NTuple{Nnum,T}
    den_coef::NTuple{Nden,T}
    num_coef_end::NTuple{Nnum,T}
    den_coef_end::NTuple{Nden,T}
    tmp::Vector{Complex{T}}
    num_eigval::Vector{Complex{T}}
    den_eigval::Vector{Complex{T}}
    rfft_plan::RFFT
    irfft_plan::IRFFT

    function PeriodicDerivativeOperatorQuotient(num_D::numDtype, den_D::denDtype, num_coef::NTuple{Nnum,T}, den_coef::NTuple{Nden,T}, tmp::Vector{Complex{T}}, num_eigval::Vector{Complex{T}}, den_eigval::Vector{Complex{T}}, rfft_plan::RFFT, irfft_plan::IRFFT) where {T<:Real, numDtype<:Union{PeriodicDerivativeOperator{T},FourierDerivativeOperator{T}}, denDtype<:Union{PeriodicDerivativeOperator{T},FourierDerivativeOperator{T}}, Nnum, Nden, RFFT, IRFFT}
        @argcheck length(irfft_plan) == length(tmp) DimensionMismatch
        @argcheck length(irfft_plan) == length(num_eigval) DimensionMismatch
        @argcheck length(irfft_plan) == length(den_eigval) DimensionMismatch
        @argcheck length(irfft_plan) == (length(rfft_plan)÷2)+1 DimensionMismatch
        @argcheck length(grid(num_D)) == length(rfft_plan) DimensionMismatch
        @argcheck length(grid(den_D)) == length(rfft_plan) DimensionMismatch

        num_coef_end = _coef_end(num_coef, num_D)
        den_coef_end = _coef_end(den_coef, den_D)

        new{T,numDtype,denDtype,Nnum,Nden,RFFT,IRFFT}(num_D, den_D, num_coef, den_coef, num_coef_end, den_coef_end, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan)
    end
end

Base.size(quot::PeriodicDerivativeOperatorQuotient) = size(quot.num_D)
grid(quot::PeriodicDerivativeOperatorQuotient) = grid(quot.num_D)

function Base.show(io::IO, quot::PeriodicDerivativeOperatorQuotient)
    if get(io, :compact, false)
        print(io, "Rational operator (", quot.num_D, ", ", quot.den_D, ")")
    else
        print(io, "Quotient of the polynomial with coefficients\n")
        print(io, quot.num_coef)
        print(io, "\nof the operator:\n")
        print(io, quot.num_D)
        print(io, "and the polynomial with coefficients\n")
        print(io, quot.den_coef)
        print(io, "\nof the operator:\n")
        print(io, quot.den_D)
    end
end

function _eigvals!(eigval::Vector{Complex{T}}, D::FourierDerivativeOperator{T}) where {T<:Real}
    N = length(grid(D))
    jac = D.jac * N
    @inbounds for idx in 1:(length(eigval))
        eigval[idx] = (idx-1)*im * jac
    end

    eigval
end

function Base.://(num::FourierDerivativeOperator, den::PeriodicRationalDerivativeOperator)
    T = eltype(num)
    @argcheck T == eltype(den) ArgumentError
    @argcheck grid(num) == grid(den) ArgumentError
    @argcheck den.den_coef == (one(T),) ArgumentError

    @unpack tmp, rfft_plan = num
    irfft_plan = plan_irfft(tmp, length(grid(num)))

    num_eigval = similar(tmp)
    _eigvals!(num_eigval, num)

    den_eigval = similar(tmp)
    _eigvals!(den_eigval, den.D)

    PeriodicDerivativeOperatorQuotient(num, den.D, (zero(T), one(T)), den.num_coef, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan)
end

function Base.://(num::FourierPolynomialDerivativeOperator, den::PeriodicRationalDerivativeOperator)
    T = eltype(num)
    @argcheck T == eltype(den) ArgumentError
    @argcheck grid(num) == grid(den) ArgumentError
    @argcheck den.den_coef == (one(T),) ArgumentError

    @unpack tmp, rfft_plan = num.D1
    irfft_plan = plan_irfft(tmp, length(grid(num)))

    num_eigval = similar(tmp)
    _eigvals!(num_eigval, num.D1)

    den_eigval = similar(tmp)
    _eigvals!(den_eigval, den.D)

    PeriodicDerivativeOperatorQuotient(num.D1, den.D, num.coef, den.num_coef, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan)
end

function Base.://(num::PeriodicDerivativeOperator, den::PeriodicRationalDerivativeOperator)
    T = eltype(num)
    @argcheck T == eltype(den) ArgumentError
    @argcheck grid(num) == grid(den) ArgumentError
    @argcheck den.den_coef == (one(T),) ArgumentError

    x = grid(num)
    u = zero.(x)
    rfft_plan = plan_rfft(u)
    tmp = rfft_plan * u
    irfft_plan = plan_irfft(tmp, length(grid(num)))

    num_eigval = similar(tmp)
    _eigvals!(num_eigval, num)

    den_eigval = similar(tmp)
    _eigvals!(den_eigval, den.D)

    PeriodicDerivativeOperatorQuotient(num, den.D, (zero(T), one(T)), den.num_coef, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan)
end

function Base.://(num::PeriodicRationalDerivativeOperator, den::PeriodicRationalDerivativeOperator)
    T = eltype(num)
    @argcheck T == eltype(den) ArgumentError
    @argcheck grid(num) == grid(den) ArgumentError
    @argcheck num.den_coef == (one(T),) ArgumentError
    @argcheck den.den_coef == (one(T),) ArgumentError

    x = grid(num)
    u = zero.(x)
    rfft_plan = plan_rfft(u)
    tmp = rfft_plan * u
    irfft_plan = plan_irfft(tmp, length(grid(num)))

    num_eigval = similar(tmp)
    _eigvals!(num_eigval, num.D)

    den_eigval = similar(tmp)
    _eigvals!(den_eigval, den.D)

    PeriodicDerivativeOperatorQuotient(num.D, den.D, num.num_coef, den.num_coef, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan)
end

function Base.://(num::FourierDerivativeOperator, den::FourierPolynomialDerivativeOperator)
    T = eltype(num)
    @argcheck T == eltype(den) ArgumentError
    @argcheck grid(num) == grid(den) ArgumentError

    @unpack tmp, rfft_plan = den.D1
    irfft_plan = plan_irfft(tmp, length(grid(num)))

    num_eigval = similar(tmp)
    _eigvals!(num_eigval, num)

    den_eigval = similar(tmp)
    _eigvals!(den_eigval, den.D1)

    PeriodicDerivativeOperatorQuotient(num, den.D1, (zero(T), one(T)), den.coef, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan)
end

function Base.://(num::FourierPolynomialDerivativeOperator, den::FourierPolynomialDerivativeOperator)
    T = eltype(num)
    @argcheck T == eltype(den) ArgumentError
    @argcheck grid(num) == grid(den) ArgumentError

    @unpack tmp, rfft_plan = den.D1
    irfft_plan = plan_irfft(tmp, length(grid(num)))

    num_eigval = similar(tmp)
    _eigvals!(num_eigval, num.D1)

    den_eigval = similar(tmp)
    _eigvals!(den_eigval, den.D1)

    PeriodicDerivativeOperatorQuotient(num.D1, den.D1, num.coef, den.coef, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan)
end

function Base.://(num::PeriodicDerivativeOperator, den::FourierPolynomialDerivativeOperator)
    T = eltype(num)
    @argcheck T == eltype(den) ArgumentError
    @argcheck grid(num) == grid(den) ArgumentError

    @unpack tmp, rfft_plan = den.D1
    irfft_plan = plan_irfft(tmp, length(grid(num)))

    num_eigval = similar(tmp)
    _eigvals!(num_eigval, num)

    den_eigval = similar(tmp)
    _eigvals!(den_eigval, den.D1)

    PeriodicDerivativeOperatorQuotient(num, den.D1, (zero(T), one(T)), den.coef, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan)
end

function Base.://(num::PeriodicRationalDerivativeOperator, den::FourierPolynomialDerivativeOperator)
    T = eltype(num)
    @argcheck T == eltype(den) ArgumentError
    @argcheck grid(num) == grid(den) ArgumentError
    @argcheck num.den_coef == (one(T),) ArgumentError

    @unpack tmp, rfft_plan = den.D1
    irfft_plan = plan_irfft(tmp, length(grid(num)))

    num_eigval = similar(tmp)
    _eigvals!(num_eigval, num.D)

    den_eigval = similar(tmp)
    _eigvals!(den_eigval, den.D1)

    PeriodicDerivativeOperatorQuotient(num.D, den.D1, num.num_coef, den.coef, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan)
end

function mul!(dest::AbstractVector{T}, quot::PeriodicDerivativeOperatorQuotient, u::AbstractVector{T}) where {T}
    @unpack num_D, num_coef, den_coef, num_coef_end, den_coef_end, tmp, num_eigval, den_eigval, rfft_plan, irfft_plan = quot
    N, _ = size(num_D)
    @boundscheck begin
        @argcheck N == length(u)
        @argcheck N == length(dest)
    end

    mul!(tmp, rfft_plan, u)
    @inbounds @simd for j in Base.OneTo(length(tmp)-1)
        tmp[j] *= evalpoly(num_eigval[j], num_coef) / evalpoly(den_eigval[j], den_coef)
    end
    tmp[end] *= evalpoly(num_eigval[end], num_coef_end) / evalpoly(den_eigval[end], den_coef_end)
    mul!(dest, irfft_plan, tmp)
end

"""
    ConstantFilter(D::FourierDerivativeOperator, filter)

Create a modal filter with constant parameters adapted to the Fourier
derivative operator `D` with parameters given by the filter function `filter`.
"""
function ConstantFilter(D::FourierDerivativeOperator{T}, filter) where {T}
    Np1 = length(D.brfft_plan)
    coefficients = Array{T}(undef, Np1)
    set_filter_coefficients!(coefficients, filter)
    tmp = copy(D.tmp)
    modal2nodal = plan_irfft(D.tmp, length(D.rfft_plan))
    nodal2modal = D.rfft_plan
    ConstantFilter(coefficients, nodal2modal, modal2nodal, tmp, filter)
end

abstract type AbstractFourierViscosity{T} <: AbstractPeriodicDerivativeOperator{T} end

@inline source_of_coefficients(Di::AbstractFourierViscosity) = (Di.source_of_coefficients)

LinearAlgebra.issymmetric(Di::AbstractFourierViscosity) = true
grid(Di::AbstractFourierViscosity) = grid(Di.D)

function mul!(dest::AbstractVector{T}, Di::AbstractFourierViscosity{T}, u::AbstractVector{T}) where {T}
    @unpack coefficients, D = Di
    @unpack tmp, rfft_plan, brfft_plan = D
    N = size(D, 1)
    @boundscheck begin
        @argcheck N == length(u)
        @argcheck N == length(dest)
        @argcheck length(tmp) == length(coefficients)
    end

    mul!(tmp, rfft_plan, u)
    @inbounds @simd for j in Base.OneTo(length(tmp))
        tmp[j] *= coefficients[j]
    end
    mul!(dest, brfft_plan, tmp)

    nothing
end

"""
    FourierConstantViscosity

Fourier viscosity operator with constant coefficients for the periodic 1st
derivative Fourier operator.
"""
struct FourierConstantViscosity{T<:Real, Grid, RFFT, BRFFT} <: AbstractFourierViscosity{T}
    coefficients::Vector{T}
    D::FourierDerivativeOperator{T,Grid,RFFT,BRFFT}
    parameters
    source_of_coefficients

    function FourierConstantViscosity(D::FourierDerivativeOperator{T,Grid,RFFT,BRFFT}, parameters, source_of_coefficients) where {T<:Real, Grid, RFFT, BRFFT}
        # precompute coefficients
        N = size(D,1)
        jac = N * D.jac # *N: brfft instead of irfft
        coefficients = Vector{T}(undef, length(D.brfft_plan))
        set_filter_coefficients!(coefficients, jac, N, parameters, source_of_coefficients)
        new{T,Grid,RFFT,BRFFT}(coefficients, D, parameters, source_of_coefficients)
    end
end

function Base.show(io::IO, Di::FourierConstantViscosity)
    if get(io, :compact, false)
        print(io, "Fourier viscosity operator with constant coefficients of ")
    else
        grid = Di.D.grid_evaluate
        print(io, "Fourier viscosity operator with constant coefficients for the periodic 1st\n")
        print(io, "derivative Fourier operator {T=", eltype(Di), "} on a grid in [",
                first(grid), ", ", last(grid), "]\n")
        print(io, "using ", length(Di.D.rfft_plan), " nodes and ",
                length(Di.D.brfft_plan), " modes with coefficients of ")
    end
    print(io, Di.source_of_coefficients)
end

function dissipation_operator(source_of_coefficients, D::FourierDerivativeOperator; kwargs...)
    parameters = get_parameters(source_of_coefficients, D; kwargs...)
    FourierConstantViscosity(D, parameters, source_of_coefficients)
end

"""
    Tadmor1989()

Coefficients of the Fourier spectral viscosity given in
- Tadmor (1989)
  Convergence of Spectral Methods for Nonlinear Conservation Laws.
  SIAM Journal on Numerical Analysis 26.1, pp. 30-44.
"""
struct Tadmor1989 <: SourceOfCoefficients end

function Base.show(io::IO, source::Tadmor1989)
    if get(io, :compact, false)
        summary(io, source)
    else
        print(io,
            "Tadmor (1989) \n",
            "  Convergence of Spectral Methods for Nonlinear Conservation Laws. \n",
            "  SIAM Journal on Numerical Analysis 26.1, pp. 30-44. \n")
    end
end

function set_filter_coefficients!(coefficients::AbstractVector{T},
                                  jac::T, N::Int,
                                  parameters, source::Tadmor1989) where {T<:Real}
    @unpack strength, cutoff = parameters
    @argcheck cutoff >= 1
    fill!(coefficients, zero(T))
    jac = jac^2 / N # ^2: 2nd derivative; /N: brfft instead of irfft
    @simd for j in cutoff:length(coefficients)
        @inbounds coefficients[j] = -strength * (j-1)^2 * jac
    end
end

"""
    MadayTadmor1989()

Coefficients of the Fourier spectral viscosity given in
- Maday, Tadmor (1989)
  Analysis of the Spectral Vanishing Viscosity Method for Periodic Conservation
    Laws.
  SIAM Journal on Numerical Analysis 26.4, pp. 854-870.
"""
struct MadayTadmor1989 <: SourceOfCoefficients end

function Base.show(io::IO, source::MadayTadmor1989)
    if get(io, :compact, false)
        summary(io, source)
    else
        print(io,
            "Maday, Tadmor (1989) \n",
            "  Analysis of the Spectral Vanishing Viscosity Method for Periodic Conservation\n",
            "    Laws. \n",
            "  SIAM Journal on Numerical Analysis 26.4, pp. 854-870.")
    end
end

function set_filter_coefficients!(coefficients::AbstractVector{T},
                                  jac::T, N::Int,
                                  parameters, source::MadayTadmor1989) where {T<:Real}
    @unpack strength, cutoff = parameters
    @argcheck cutoff >= 1
    fill!(coefficients, zero(T))
    jac = jac^2 / N # ^2: 2nd derivative; /N: brfft instead of irfft
    @inbounds @simd for j in cutoff:min(2cutoff,length(coefficients))
        coefficients[j] = -strength * (j-1)^2 * jac * (j-cutoff)/cutoff
    end
    @inbounds @simd for j in 2cutoff:length(coefficients)
        coefficients[j] = -strength * (j-1)^2 * jac
    end
end

"""
    TadmorWaagan2012Standard()

Coefficients of the Fourier spectral viscosity given in
- Tadmor, Waagan (2012)
  Adaptive Spectral Viscosity for Hyperbolic Conservation Laws.
  SIAM Journal on Scientific Computing 34.2, pp. A993-A1009.
"""
struct TadmorWaagan2012Standard <: SourceOfCoefficients end

function Base.show(io::IO, source::TadmorWaagan2012Standard)
    if get(io, :compact, false)
        summary(io, source)
    else
        print(io,
            "Tadmor, Waagan (2012) \n",
            "  Adaptive Spectral Viscosity for Hyperbolic Conservation Laws. \n",
            "  SIAM Journal on Scientific Computing 34.2, pp. A993-A1009.")
    end
end

function set_filter_coefficients!(coefficients::AbstractVector{T},
                                  jac::T, N::Int,
                                  parameters, source::TadmorWaagan2012Standard) where {T<:Real}
    @unpack strength, cutoff = parameters
    @argcheck cutoff >= 1
    fill!(coefficients, zero(T))
    jac = jac^2 / N # ^2: 2nd derivative; /N: brfft instead of irfft
    @inbounds @simd for j in cutoff+1:length(coefficients)
        coefficients[j] = -strength * (j-1)^2 * jac * exp(-((length(coefficients)-j)/(j-cutoff))^2)
    end
end

"""
    TadmorWaagan2012Convergent()

Coefficients of the Fourier spectral viscosity given in
- Tadmor, Waagan (2012)
  Adaptive Spectral Viscosity for Hyperbolic Conservation Laws.
  SIAM Journal on Scientific Computing 34.2, pp. A993-A1009.

See also
- Schochet (1990)
  The Rate of Convergence of Spectral-Viscosity Methods for Periodic Scalar
    Conservation Laws.
  SIAM Journal on Numerical Analysis 27.5, pp. 1142-1159.
"""
struct TadmorWaagan2012Convergent <: SourceOfCoefficients end

function Base.show(io::IO, source::TadmorWaagan2012Convergent)
    if get(io, :compact, false)
        summary(io, source)
    else
        print(io,
            "Tadmor, Waagan (2012) \n",
            "  Adaptive Spectral Viscosity for Hyperbolic Conservation Laws. \n",
            "  SIAM Journal on Scientific Computing 34.2, pp. A993-A1009.")
    end
end

function set_filter_coefficients!(coefficients::AbstractVector{T},
                                  jac::T, N::Int,
                                  parameters, source::TadmorWaagan2012Convergent) where {T<:Real}
    @unpack strength, cutoff = parameters
    @argcheck cutoff >= 1
    fill!(coefficients, zero(T))
    jac = jac^2 / N # ^2: 2nd derivative; /N: brfft instead of irfft
    @inbounds @simd for j in cutoff:min(2cutoff,length(coefficients))
        coefficients[j] = -strength * (j-1)^2 * jac * exp(-((2cutoff-j)/(j-cutoff))^2)
    end
    @inbounds @simd for j in 2cutoff:length(coefficients)
        coefficients[j] = -strength * (j-1)^2 * jac
    end
end

const FourierSpectralViscosityCoefficients = Union{Tadmor1989,MadayTadmor1989,TadmorWaagan2012Standard,TadmorWaagan2012Convergent}

function get_parameters(source_of_coefficients::FourierSpectralViscosityCoefficients,
                        D::FourierDerivativeOperator;
                        strength::Real=eltype(D)(1)/size(D,2),
                        cutoff::Int=1+round(Int, sqrt(size(D,2))), #+1: 1 based indexing
                        kwargs...)
    @argcheck cutoff >= 1

    Dict(:strength=>strength, :cutoff=>cutoff)
end

"""
    Tadmor1993()

Coefficients of the Fourier super spectral viscosity given in
- Tadmor (1993)
  Super Viscosity and Spectral Approximations of Nonlinear Conservation Laws.
  Numerical Methods for Fluid Dynamics IV, pp. 69-82.
"""
struct Tadmor1993 <: SourceOfCoefficients end

function Base.show(io::IO, source::Tadmor1993)
    if get(io, :compact, false)
        summary(io, source)
    else
        print(io,
            "Tadmor (1993) \n",
            "  Super Viscosity and Spectral Approximations of Nonlinear Conservation Laws. \n",
            "  Numerical Methods for Fluid Dynamics IV, pp. 69-82.")
    end
end

function set_filter_coefficients!(coefficients::AbstractVector{T},
                                  jac::T, N::Int,
                                  parameters, source::Tadmor1993) where {T<:Real}
    @unpack order, strength, cutoff = parameters
    @argcheck order >= 1
    @argcheck cutoff >= 1
    fill!(coefficients, zero(T))
    jac = jac^2order / N # ^2order: order-th derivative; /N: brfft instead of irfft
    @inbounds @simd for j in cutoff:length(coefficients)
        coefficients[j] = -strength * (j-1)^2order * jac * (1 - (cutoff/j)^2order)
    end
end

const FourierSuperSpectralViscosityCoefficients = Tadmor1993

function get_parameters(source::FourierSuperSpectralViscosityCoefficients,
                        D::FourierDerivativeOperator;
                        order::Int=1,
                        strength=eltype(D)(1)/size(D,2)^(2order-1),
                        cutoff::Int=1+round(Int, size(D,2)^(1-1/2order)), #+1: 1 based indexing
                        kwargs...)
    @argcheck order >= 1
    @argcheck cutoff >= 1

    Dict(:order=>order, :strength=>strength, :cutoff=>cutoff)
end


function derivative{T<:FloatingPoint}(f::Function,x::Array{T,1})

    n = length(f(x))
    m = length(x)

	e  = eps(T)^(1/3)*Base.maxabs([x,one(T)])
    dh = eye(m)*e
	deriv = Array(T,n,m)

    for i = 1:m
	    f1 = f(x+2*dh[:,i])
	    f2 = f(x+dh[:,i])
	    f3 = f(x-dh[:,i])
	    f4 = f(x-2*dh[:,i])
        deriv[:,i] = (-f1+8*f2-8*f3+f4)/(12*e)
    end

    return deriv

end


"""
FBeam!(FV, U, bdata)
Function evaluation for PTC example.
F(u) = -u'' - lambda sin(u)
"""
function FBeam!(FV, U, bdata)
    D2 = bdata.D2
    lambda = bdata.lambda
    su = lambda * sin.(U)
    FV .= (D2 * U - su)
end

"""
BeamJ!(FP,FV,U,bdata)

Jacobian for the beam problem
F(u) = -u'' - lambda sin(u)
so
F'(u) w  = D2 w - lambda cos(u) w
"""
function BeamJ!(FP, FV, U, bdata)
    D2 = bdata.D2
    lambda = bdata.lambda
    cu = lambda * cos.(U)
    n = length(U)
    zr = zeros(n - 1)
    FP .= D2 - Tridiagonal(zr, cu, zr)
end

"""
BeamtdJ!(FP, FV, U, bdata)

Jacobian evaluation for the time-dependent beam problem.
If F^n(w) = w - u_n + dt F(w) = 0 then
F^n(w)' = I + dt F'(w)
"""
function BeamtdJ!(FP, FV, U, bdata)
    FP .= BeamJ!(FP, FV, U, bdata)
    dt = bdata.dt
    FP .= I + dt * FP
end

"""
FBeamtd!(FV, U, bdata)

Function evaluation for the time-dependent beam problem.
The implicit Euler step for u_t = - F(u) is
u_{n+1} = u_n - dt F(u_{n+1})
so the nonlinear equation is
F^n(w) = w - u_n + dt F(w) = 0
"""
function FBeamtd!(FV, U, bdata)
    un = bdata.UN
    dt = bdata.dt
    FV .= FBeam!(FV, U, bdata)
    dU = U - un
    FV .= dU + dt * FV
    #axpby!(1.0,dU,dt,FV)
end

"""
beaminit(n,dt,lambda=20.0)

Set up the beam problem with n interior grid points.

dt is only needed for the temporal integration examples
"""

function beaminit(n, dt, lambda = 20.0)
    #
    # deltaval is a place to store the current pseudo-time step. I need this
    # for preconditioning. You MUST have this in your pdata because ptcsoli
    # writes to it:  pdata.deltaval[1]=delta
    # So it has to be there in exactly this way.
    #
    deltaval = zeros(1)
    D2 = Lap1d(n)
    dx = 1.0 / (n + 1)
    x = collect(dx:dx:1.0-dx)
    UN = zeros(size(x))
    bdata = (D2 = D2, x = x, dx = dx, dt = dt, lambda = lambda, UN = UN, deltaval = deltaval)
end

"""
Lap1d(n)

returns -d^2/dx^2 on [0,1] zero BC
"""
function Lap1d(n)
    dx = 1 / (n + 1)
    d = 2.0 * ones(n)
    sup = -ones(n - 1)
    slo = -ones(n - 1)
    D2 = Tridiagonal(slo, d, sup)
    D2 = D2 / (dx * dx)
    return D2
end


#!/usr/bin/env julia

module PE11

using DelimitedFiles

const buffer=IOBuffer(
"08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08
49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00
81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65
52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91
22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80
24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50
32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70
67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21
24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72
21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95
78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92
16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57
86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58
19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40
04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66
88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69
04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36
20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16
20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54
01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48"
);
const data=readdlm(buffer,' ',Int);

end

export pe11

function pe11(data::Matrix{<:Integer}=PE11.data)
    n=4
    productline(dx::Int,dy::Int)=[prod(data[y+i*dy,x+i*dx] for i in 0:n-1) for y in 1:size(data,1), x in 1:size(data,2) if checkbounds(Bool,data,y+(n-1)*dy,x+(n-1)*dx)]
    cat(productline(1,0),productline(0,1),productline(1,1),productline(1,-1),dims=1)|>maximum
end

if !haskey(ENV,"PROJECT_EULER_WITHOUT_RESULT")
    pe11()|>print
end


using StatisticalRethinking, Optim
#gr(size=(600,600));

p_grid = range(0, step=0.001, stop=1);

prior = ones(length(p_grid));

likelihood = [pdf(Binomial(9, p), 6) for p in p_grid];

posterior = likelihood .* prior;

posterior = posterior / sum(posterior);

N = 10000
samples = sample(p_grid, Weights(posterior), N);

chn = MCMCChains.Chains(reshape(samples, N, 1, 1), ["toss"]);

MCMCChains.describe(chn)

plot(chn)

x0 = [0.5]
lower = [0.0]
upper = [1.0]

function loglik(x)
  ll = 0.0
  ll += log.(pdf.(Beta(1, 1), x[1]))
  ll += sum(log.(pdf.(Binomial(9, x[1]), repeat([6], 1))))
  -ll
end

(qmap, opt) = quap(loglik, x0, lower, upper)

opt

quapfit = [qmap[1], std(samples, mean=qmap[1])]

p = Vector{Plots.Plot{Plots.GRBackend}}(undef, 4)
p[1] = scatter(1:length(p_grid), samples, markersize = 2, ylim=(0.0, 1.3), lab="Draws")

w = 6
n = 9
x = 0:0.01:1
p[2] = plot( x, pdf.(Beta( w+1 , n-w+1 ) , x ), lab="Conjugate solution")
density!(p[2], samples, lab="Sample density")

p[3] = plot( x, pdf.(Beta( w+1 , n-w+1 ) , x ), lab="Conjugate solution")
plot!( p[3], x, pdf.(Normal( quapfit[1], quapfit[2] ) , x ), lab="Quap approximation")

w = 6; n = 9; x = 0:0.01:1
p[4] = plot( x, pdf.(Beta( w+1 , n-w+1 ) , x ), lab="Conjugate solution")
plot!(p[4], x, pdf.(Normal( 0.67 , 0.16 ) , x ), lab="Normal approximation")
plot(p..., layout=(2, 2))

# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl