#=
Binary (or 0-1) knapsack problem

    Given a knapsack of some capacity C and n objects
       with object i having weight wi and profit pi,
       the goal is to choose some subset of the objects
       that can fit in the knapsack
       (i.e. the sum of their weights is no more than C)
       while maximizing profit.

    This can be formulated as a mixed-integer program as:
        maximize x' p subject to x∈{0,1}  and  w′x <= C
        x is a vector is size n, where x_i
        is 1 if we chose to keep the object in the knapsack,
           0 otherwise.

source: https://nbviewer.jupyter.org/github/JuliaOpt/Convex.jl/blob/master/
                                    examples/binary_knapsack.ipynb
=#

# Data taken from http://people.sc.fsu.edu/~jburkardt/datasets/knapsack_01/knapsack_01.html

w = [23; 31; 29; 44; 53; 38; 63; 85; 89; 82]
C = 165
p =  [92; 57; 49; 68; 60; 43; 67; 84; 87; 72];
n = length(w)

using Convex, GLPKMathProgInterface
x = Variable(n, :Bin)
problem = maximize(dot(p, x), dot(w, x) <= C)
solve!(problem, GLPKSolverMIP())


function newtonRaphson(f::Function, fprime::Function, xi::Number, ea::Number, maxIter::Int64)
  # Estimate roots of functions using the Newton-Raphson method
  for i = 1:maxIter
    xiprev  = xi;
    xi = xi - (f(xi) / fprime(xi));

    if abs(xi - xiprev) / xi < ea
      break;
    end;
  end
  return xi;
end


# Univariate Gaussian Mixture Models

struct UnivariateGMM <: UnivariateMixture{Continuous,Normal}
    K::Int
    means::Vector{Float64}
    stds::Vector{Float64}
    prior::Categorical

    function UnivariateGMM(ms::Vector{Float64}, ss::Vector{Float64}, pri::Categorical)
        K = length(ms)
        length(ss) == K || throw(DimensionMismatch())
        ncategories(pri) == K ||
            error("The number of categories in pri should be equal to the number of components.")
        new(K, ms, ss, pri)
    end
end

@distr_support UnivariateGMM -Inf Inf

ncomponents(d::UnivariateGMM) = d.K

component(d::UnivariateGMM, k::Int) = Normal(d.means[k], d.stds[k])

probs(d::UnivariateGMM) = probs(d.prior)

mean(d::UnivariateGMM) = dot(d.means, probs(d))

rand(d::UnivariateGMM) = (k = rand(d.prior); d.means[k] + randn() * d.stds[k])

params(d::UnivariateGMM) = (d.means, d.stds, d.prior)

struct UnivariateGMMSampler <: Sampleable{Univariate,Continuous}
    means::Vector{Float64}
    stds::Vector{Float64}
    psampler::AliasTable
end

rand(s::UnivariateGMMSampler) = (k = rand(s.psampler); s.means[k] + randn() * s.stds[k])
sampler(d::UnivariateGMM) = UnivariateGMMSampler(d.means, d.stds, sampler(d.prior))


using ReachabilityAnalysis

@taylorize function lotkavolterra!(dx, x, params, t)
    local α, β, γ, δ = 1.5, 1., 3., 1.
    dx[1] = α * x[1] - β * x[1] * x[2]
    dx[2] = δ * x[1] * x[2] - γ * x[2]
    return dx
end

X0 = Hyperrectangle(low=[4.8, 1.8], high=[5.2, 2.2]);
prob = @ivp(x' = lotkavolterra!(x), dim=2, x(0) ∈ X0)
sol = solve(prob, T=8.0, alg=TMJets());
solz = overapproximate(sol, Zonotope);

using Plots

plot(solz, vars=(1, 2), alpha=0.3,lw=0., xlab="x", ylab="y", label="Flowpipe", legend=:bottomright)
plot!(X0, label="X(0)")

@taylorize function f(du, u, p, t)
    du[1] = u[3] * u[1] - u[4] * (u[1] * u[2]) - u[7] * u[1]^2
    du[2] = -u[5] * u[2] + u[6] * (u[1] * u[2])

    #encode uncertain params
    du[3] = zero(u[1]) # p[1]
    du[4] = zero(u[1]) # p[2]
    du[5] = zero(u[1]) # p[3]
    du[6] = zero(u[1]) # p[4]
    du[7] = zero(u[1]) # p[5]
end

p_int = (0.99..1.01) × (0.99..1.01) × (2.99..3.01) × (0.99..1.01) × (0.099..0.101)
U0 = Singleton([1.0, 1.0]) × convert(Hyperrectangle, p_int)
prob = @ivp(u' = f(u), dim: 7, u(0) ∈ U0);

sol = solve(prob, tspan=(0.0, 10.0));
solz = overapproximate(sol, Zonotope);

plot(solz, vars=(1, 2), lw=0.3, title="Uncertain params", lab="abstol = 1e-15", xlab="u1", ylab="u2")

u0 = Singleton([1.0, 1.0])
□(ϵ) = BallInf(zeros(2), ϵ)
U0 = (u0 ⊕ □(0.05)) × convert(Hyperrectangle, p_int)

prob = @ivp(u' = f(u), dim: 7, u(0) ∈ U0)

sol = solve(prob, tspan=(0.0, 10.0), TMJets(abstol=1e-10))
solz = overapproximate(sol, Zonotope)
plot(solz, vars=(1, 2), color=:orange, lw=0.3,
     lab="eps = 0.05", title="Uncertain u0 and uncertain params",
     xlab="u1", ylab="u2")

U0 = (u0 ⊕ □(0.01)) × convert(Hyperrectangle, p_int)
prob = @ivp(u' = f(u), dim: 7, u(0) ∈ U0)

sol = solve(prob, tspan=(0.0, 10.0), TMJets(abstol=1e-10))
solz = overapproximate(sol, Zonotope)
plot!(solz, vars=(1, 2), color=:blue, lw=0.3,
  lab="eps = 0.01", title="Uncertain u0 and uncertain params",
  xlab="u1", ylab="u2")


"""
        wpm(decisionMat, weights, fns)

Apply WPM (Weighted Product Method) for a given matrix and weights.

# Arguments:
 - `decisionMat::DataFrame`: n × m matrix of objective values for n alterntives and m criteria
 - `weights::Array{Float64, 1}`: m-vector of weights that sum up to 1.0. If the sum of weights is not 1.0, it is automatically normalized.
 - `fns::Array{Function, 1}`: m-vector of functions to be applied on the columns.

# Description
wpm() applies the WPM method to rank n alterntives subject to m criteria which are supposed to be
either maximized or minimized.

# Output
- `::WPMResult`: WPMResult object that holds multiple outputs including scores, rankings, and best index.

# Examples
```julia-repl
julia> decmat = [3        12.5        2        120        14        3;
       5        15        3        110        38        4;
       3        13        2        120        19        3;
       4        14        2        100        31        4;
       3        15        1.5        125        40        4]
5×6 Array{Float64,2}:
 3.0  12.5  2.0  120.0  14.0  3.0
 5.0  15.0  3.0  110.0  38.0  4.0
 3.0  13.0  2.0  120.0  19.0  3.0
 4.0  14.0  2.0  100.0  31.0  4.0
 3.0  15.0  1.5  125.0  40.0  4.0

julia> df = makeDecisionMatrix(decmat)
5×6 DataFrame
 Row │ Crt1     Crt2     Crt3     Crt4     Crt5     Crt6
     │ Float64  Float64  Float64  Float64  Float64  Float64
─────┼──────────────────────────────────────────────────────
   1 │     3.0     12.5      2.0    120.0     14.0      3.0
   2 │     5.0     15.0      3.0    110.0     38.0      4.0
   3 │     3.0     13.0      2.0    120.0     19.0      3.0
   4 │     4.0     14.0      2.0    100.0     31.0      4.0
   5 │     3.0     15.0      1.5    125.0     40.0      4.0

julia> weights = [0.221, 0.159, 0.175, 0.127, 0.117, 0.201];

julia> fns = [maximum, minimum, minimum, maximum, minimum, maximum];

julia> result = wpm(df, weights, fns);

julia> result.scores
5-element Array{Float64,1}:
 0.7975224331331252
 0.7532541470584717
 0.7647463553356331
 0.7873956894790834
 0.7674278741781709

julia> result.bestIndex
1
```

# References
Zavadskas, E. K., Turskis, Z., Antucheviciene, J., & Zakarevicius, A. (2012). Optimization of Weighted Aggregated Sum Product Assessment. Elektronika Ir Elektrotechnika, 122(6), 3-6. https://doi.org/10.5755/j01.eee.122.6.1810
"""
function wpm(decisionMat::DataFrame, weights::Array{Float64,1}, fns::Array{Function,1})::WPMResult

    row, col = size(decisionMat)
    normalizedDecisionMat = similar(decisionMat)
    w = unitize(weights)
    colminmax = zeros(Float64, col)
    @inbounds for i in 1:col
        colminmax[i] = decisionMat[:, i] |> fns[i]
        if fns[i] == maximum
            normalizedDecisionMat[:, i] = decisionMat[:, i] ./ colminmax[i]
        elseif fns[i] == minimum
            normalizedDecisionMat[:, i] = colminmax[i] ./ decisionMat[:, i]
        end
    end
    scoreMat = similar(normalizedDecisionMat)
    for i in 1:col
        scoreMat[:, i] = normalizedDecisionMat[:, i].^w[i]
    end

    scores = zeros(Float64, row)
    for i in 1:row
        scores[i] = prod(scoreMat[i, :])
    end
    rankings = sortperm(scores)

    bestIndex = rankings |> last

    result = WPMResult(
        decisionMat,
        normalizedDecisionMat,
        w,
        scores,
        rankings,
        bestIndex
    )

    return result
end

"""
        wpm(setting)

Apply WPM (Weighted Product Method) for a given matrix and weights.

# Arguments:
 - `setting::MCDMSetting`: MCDMSetting object.

# Description
wpm() applies the WPM method to rank n alterntives subject to m criteria which are supposed to be
either maximized or minimized.

# Output
- `::WPMResult`: WPMResult object that holds multiple outputs including scores, rankings, and best index.
"""
function wpm(setting::MCDMSetting)::WPMResult
    wpm(
        setting.df,
        setting.weights,
        setting.fns
    )
end


@doc raw"""
    align(a⃗, b⃗, [w])

Solve [Wahba's problem](https://en.wikipedia.org/wiki/Wahba%27s_problem),
finding a rotation that aligns the set of points `a⃗` to a corresponding set of
points `b⃗` by minimizing the distance between the first set and the rotated
second set.

Here, `a⃗` and `b⃗` must be equally sized arrays of `QuatVec`s.  If present, `w`
must be an equally sized array of real numbers; if not, it is taken to be 1.
We define the loss function
```math
L(ℛ) ≔ Σᵢ wᵢ ‖a⃗ᵢ - ℛ b⃗ᵢ‖²
```
where ``ℛ`` is a rotation operator, and return the quaternion corresponding to
the optimal ``ℛ`` that minimizes this function.

Note that it is possible that the points do not uniquely determine a rotation —
as when one or both sets of points is rotationally symmetric.  In that case, the
loss function ``L(ℛ)`` will still be minimized and the points will still be
optimally aligned by the output quaternion, but that quaternion will not be
unique.

# Notes

In their book [_Fundamentals of Spacecraft Attitude Determination and Control_
(2014)](https://doi.org/10.1007/978-1-4939-0802-8), Markley and Crassidis say
that "Davenport’s method remains the best method for solving Wahba’s problem".
This method provides the optimal quaternion as the dominant eigenvector (the one
with the largest eigenvalue) of a certain matrix.  We start by defining the
supplementary matrix
```math
S ≔ Σᵢ wᵢ a⃗ᵢ b⃗ᵢᵀ
```
and vector
```math
s⃗ ≔ \begin{bmatrix}
S₂₃-S₃₂ \\
S₃₁-S₁₃ \\
S₁₂-S₂₁
\end{bmatrix}.
```
Then the key matrix is
```math
M ≔ \begin{bmatrix}
S + Sᵀ - (\mathrm{tr}S)\, I₃ & s⃗ \\
s⃗ᵀ & \mathrm{tr}S
\end{bmatrix}
```
It is possible for this matrix to have degenerate eigenvalues, corresponding to
cases where the points do not uniquely determine the rotation, as described
above.

"""
function align(a⃗::AbstractArray{<:QuatVec}, b⃗::AbstractArray{<:QuatVec}, w::AbstractArray{<:Real})
    # This is Eq. (5.11) from Markley and Crassidis
    S = sum(w[i] * a⃗[i].vec * b⃗[i].vec' for i in eachindex(a⃗, b⃗, w))
    return _align_Wahba(S)
end

function align(a⃗::AbstractArray{<:QuatVec}, b⃗::AbstractArray{<:QuatVec})
    # This is Eq. (5.11) from Markley and Crassidis
    S = sum(a⃗[i].vec * b⃗[i].vec' for i in eachindex(a⃗, b⃗))
    return _align_Wahba(S)
end

function _align_Wahba(S)
    # This is Eq. (5.17) from Markley and Crassidis, modified to suit our
    # conventions by flipping the sign of ``z``, and moving the final dimension
    # to the first dimension.
    M = Symmetric([
            S[1,1]+S[2,2]+S[3,3]      S[3,2]-S[2,3]         S[1,3]-S[3,1]           S[2,1]-S[1,2]
                S[3,2]-S[2,3]      S[1,1]-S[2,2]-S[3,3]     S[1,2]+S[2,1]           S[1,3]+S[3,1]
                S[1,3]-S[3,1]         S[1,2]+S[2,1]      -S[1,1]+S[2,2]-S[3,3]      S[2,3]+S[3,2]
                S[2,1]-S[1,2]         S[1,3]+S[3,1]         S[2,3]+S[3,2]       -S[1,1]-S[2,2]+S[3,3]
    ])
    # This extracts the dominant eigenvector, and interprets it as a Rotor.  In
    # particular, note that the _last_ eigenvector output by `eigen` (the 4th)
    # has the largest eigenvalue.
    return Rotor(eigen(M, 4:4).vectors[:, 1]...)
end

@doc raw"""
    align(A, B, [w])

Find a `Rotor` that aligns the set of rotors `A` to a corresponding set `B` by
minimizing the distance between the first set and the rotated second set.

Here, `A` and `B` must be equally sized arrays of `AbstractQuaternion`s.  If
present, `w` must be an equally sized array of real numbers; if not, it is
taken to be 1.  We define the loss function
```math
L(R) ≔ Σᵢ wᵢ |Aᵢ - R Bᵢ|²
```
where ``R`` is a `Rotor`, and return the quaternion corresponding to the optimal
``R`` that minimizes this function.

Note that it is possible that the input data do not uniquely determine a rotor,
which will happen when sum below is zero.  When this happens, the result will
contain `NaN`s, but no error will be raised.  When the sum is very close to —
but not exactly — zero, the accuracy of the result will be limited.  However,
the loss function will not depend strongly on the result in that case.

Be aware that this function _is_ sensitive to the signs of the input
quaternions.  See the [`unflip`](@ref) function for one way to avoid problems
related to signs.

## Notes

We can ensure that the loss function is minimized by multiplying ``R`` by an
exponential, differentiating with respect to the argument of the exponential,
and setting that argument to 0.  This derivative should be 0 at the minimum.  We
have
```math
∂ⱼ Σᵢ wᵢ |Aᵢ - \exp[vⱼ] R Bᵢ|²  →  2 ⟨ eⱼ R Σᵢ wᵢ Bᵢ Āᵢ ⟩₀
```
where → denotes taking ``vⱼ→0``, the symbol ``⟨⟩₀`` denotes taking the scalar
part, and ``eⱼ`` is the unit quaternionic vector in the ``j`` direction.  The
only way for this quantity to be zero for each choice of ``j`` is if
```math
R Σᵢ wᵢ Bᵢ Āᵢ
```
is itself a pure scalar.  This, in turn, can only happen if either (1) the sum
is 0 or (2) if ``R`` is proportional to the _conjugate_ of the sum:
```math
R ∝ Σᵢ wᵢ Aᵢ B̄ᵢ
```
Now, since we want ``R`` to be a rotor, we simply define it to be the normalized
sum.

"""
function align(A, B, w)
    Rotor(sum(w[i] * A[i] * conj(B[i]) for i in eachindex(A, B, w)))
end

function align(A, B)
    Rotor(sum(A[i] * conj(B[i]) for i in eachindex(A, B)))
end


using LinearAlgebra
using ToeplitzMatrices

function chebdif(N, M)

    # The function x,DM = chebdif(N,M) computes the differentiation
    # matrices D1, D2, ..., DM on Chebyshev nodes.
    # It is adapted from the function with the same name in dmsuite
    # developed for Matlab by JAC Weideman and SC Reddy.
    #
    # Adapted by M. Beneitez at KTH Mechanics.
    # beneitez@mech.kth.se
    #
    # Input:
    # N:  Size of the differentiation matrix.
    # M:  Number of derivates required
    # Note: 0 < M <= N-1.
    #
    # Output:
    # DM: DM[1:N,1:N,ell] contains the ell-th derivative matrix, ell=1...M
    #

    eye = Matrix{Float64}(I, N, N);        # Identity matrix.

    n1 = Int(floor(N/2)); n2  = Int(ceil(N/2));     # Indices used for flipping trick.

    k = collect(0:N-1);                        # Compute theta vector.
    th = k.*pi./(N-1)

    x = sin.(pi.*collect(N-1:-2:1-N)./(2*(N-1))); # Compute Chebyshev points.

    T = repeat(th/2,1,N);
    DX = 2*sin.(T'+T).*sin.(T'-T);          # Trigonometric identity.
    DX = [DX[1:n1,:]; -reverse(reverse(DX[1:n2,:], dims = 2), dims=1)];   # Flipping trick.
    DX[eye.==true] = ones(N,1);                      # Put 1's on the main diagonal of DX.

    C = Matrix(Toeplitz((-1).^vec(k),(-1).^vec(k)));            # C is the matrix with
    C[1,:] = C[1,:].*2; C[N,:] = C[N,:].*2;     # entries c[k]/c[j]
    C[:,1] = C[:,1]/2; C[:,N] = C[:,N]/2

    Z = 1 ./DX;                              # Z contains entries 1/(x[k]-x[j])
    Z[eye.==true] = zeros(N,1);              # with zeros on the diagonal.

    D  = eye;                                 # D contains diff(). matrices.
    DM = zeros(N,N,M);

    for ell = 1:M
        D = ell*Z.*(C.*repeat(diag(D),1,N) - D); # Off-diagonals
        D[eye.==true] = -sum(D', dims = 1);                            # Correct main diagonal of D
        DM[:,:,ell] = D;                                   # Store current D in DM
    end

    return x,DM

end


function rat(x::AbstractFloat, tol::Real=eps(x))::Rational
    p, q, pp, qq  = copysign(1,x), 0, 0, 1
    x, y = abs(x), 1.0
    r, a = modf(x)
    nt, t, tt = tol, 0.0, tol

    while r > nt        # convergents of the continued fraction: np//nq = (p*a + pp) // (q*a + qq)
        np, nq = Int(a).*(p,q) .+ (pp,qq)
        p, pp, q, qq = np, p, nq, q

        x, y = y, r     # instead of the inexact 1/r...
        a, r = divrem(x,y)

        t, tt = nt, t   # maintain x = (p + (-1)^i * r) / q
        nt = a*t+tt
    end

    i = Int(cld(x-tt,y+t))   # find optimal semiconvergent: smallest i such that x-i*y < i*t+tt
    return (i*p+pp) // (i*q+qq)
end


using LinearAlgebra

"""
Go from `X'Q*X + U'R*U`
s.t. x+ = Ax+Bu
to
`U'F*U + 2U'*G*x(0) + x(0)'H*x(0)`
and M,N, where X = M*U + N*x(0)
"""
function complact_form(A::AbstractArray{T}, B, Q, R, n) where T
    nu = size(B,2)
    nx = size(A,1)
    M = zeros(T, n*nx, n*nu)
    for i = 0:(n-1)
        AiB = A^(i)*B
        for j = 1:(n-i)
            rows = (nx*(i+j-1)+1):(nx*(i+j))
            cols = (nu*(j-1)+1):(nu*j)
            M[rows, cols] .= AiB
        end
    end
    N = vcat([A^i for i in 1:n]...)
    # Blockdiag
    Qn = vcat([[zeros(T, size(Q,1), size(Q,1)*i) Q zeros(T, size(Q,1), size(Q,1)*(n-i-1))]  for i = 0:(n-1)]...)
    Rn = vcat([[zeros(T, size(R,1), size(R,1)*i) R zeros(T, size(R,1), size(R,1)*(n-i-1))]  for i = 0:(n-1)]...)
    F = M'Qn*M + Rn
    G = M'Qn*N
    H = N'Qn*N
    return F, G, H, M, N
end
T = Float64
A = T.([0.999  -3.008  -0.113  -1.608  ;
     0      0.986   0.048   0       ;
     0      2.083   1.009   0       ;
     0      0.053   0.05    1       ])
B = T.([-0.080 -0.635  ;
     -0.029 -0.014  ;
     -0.868 -0.092  ;
     -0.022 -0.002  ])
Q = Matrix{T}(I,4,4)
R = Matrix{T}(I,2,2)
n = 10
F, G, H, M, N = complact_form(A,B,Q,R,n)

x0 = 0.2.*ones(4)

nx = size(A, 1)
nu = size(B, 2)

xl = -0.2*ones(T, nx)
xu = 0.2*ones(T, nx)

Xl = vcat([xl for i in 1:n]...)
Xu = vcat([xu for i in 1:n]...)
Ul = Xl - N*x0
Uu = Xu - N*x0

UC = M

using QPDAS
nU = nu*n
z = G*x0
@time qp = QuadraticProgram(zeros(T, 0, nU), zeros(T, 0), [UC;-UC], [Uu; -Ul], z, F, ϵ=1e-6, smartstart=true)

@time sol, val = solve!(qp)
val = val + 1/2*x0'H*x0

X = M*sol + N*x0
maximum(abs, X) - 0.2
maximum([UC;-UC]*sol - [Uu; -Ul])
1/2*sum(abs2, X) + 1/2*sum(abs2, sol)


"""
    Polynomial

one of functions mapping the set of real numbers is the algebraic polynomials, the set of functions of the form:
``p_n(x) = a_n x^n + a_{n-1} x^{n-1} + ... +a_{1}x + a_0``
- nth Larange interpolating polynomial
- Neville’s Iterated Interpolation
- Newton’s Divided-Difference Formula
- Natural Cubic Spline
- Clamped Cubic Spline
"""
module Polynomial
using SymPy
using LinearAlgebra
using Latexify

@inline function product(x::Vector)
    res = 1
    for i in 1:size(x)[1]
        res = res*x[i]
    end
    return res
end

"""
    Lagrange(X::Vector, Y::Vector; a::String="x")

use the ``(x_0, y_0), (x_1, y_1), ..., (x_n, y_n)``, to caculate a polynomial.
input X and Y, default use x to express functions.
output: function.
"""
@inline function Lagrange(X::Vector, Y::Vector; a::String="x")
    a = symbols(a)
    m = size(X)[1]
    A = ones(m) * X'
    An = A - A'
    An[An .== 0] .= 1
    up = a .- X
    upr = product(up)
    sum = 0
    for i in 1:m
        down = product(An[:,i])
        u = upr/up[i]
        sum = sum + Y[i] * (u/down)
    end
    return simplify(sum)
end

"""
    Neville(x::Vector, y::Vector, x₀::Real; tab::Bool=false)

input two vector, x and y. satisfy the function `y=f(x)`, then input x₀, which you want
to  approximate f(x₀). The default tab setting is false, just output the estimate. you can set true
to output a result table.
"""
@inline function Neville(x::Vector, y::Vector, x₀::Real; tab::Bool=false)
    n, = size(x)
    res = zeros(n, n)
    for i in 1:n
        res[i, 1] = y[i]
    end
    for i in 2:n
        for j in 2:i
            res[i,j] = ((x₀ - x[i-j+1])*res[i, j-1] - (x₀ - x[i])*res[i-1,j-1])/(x[i] - x[i-j+1])
        end
    end
    if tab
        return res
    else
        return res[n, n]
    end
end

"""
    NDDF(x::Vector, y::Vector; a::String="x", simple::Bool=true, tab::Bool=false, backward::Bool=false)

input ``x_1, x_2, x_3, ...x_n```,values ``f(x_1), f(x_2), ...,f(x_n)``, set Args `simpel = true`
output simplify ans, set tab to output a table. set `backward=true` use the Newton Backward–Difference Formula.
"""
@inline function NDDF(x::Vector, y::Vector; a::String="x", simple::Bool=true, tab=false, backward::Bool=false)
    a = symbols(a)
    n, = size(x)
    res = zeros(n, n)
    for i in 1:n
        res[i,1] = y[i]
    end
    for i in 2:n
        for j in 2:i
            res[i,j] = (res[i, j-1] - res[i-1, j-1])/(x[i] - x[i-j+1])
        end
    end
    sum = res[1,1]
    for i in 2:n
        p = 1
        for j in 1:i-1
            p = p*(a - x[j])
        end
        if backward
            sum = sum + res[n, i]*p
        else
            sum = sum + res[i, i]*p
        end
    end
    if tab
        return res
    else
        if simple
            return simplify(sum)
        else
            return sum
        end
    end
end

"""
    NCSpline( NCSpline(x::Vector, y::Vector; Latex::Bool=false, a::String="t")
input ``x_1, x_2, x_3, ...x_n```,values ``f(x_1), f(x_2), ...,f(x_n)``, set Args `Latex = true`  to
output information about the function.
"""
@inline function NCSpline(x::Vector, y::Vector; Latex::Bool=false, a::String="t")
    a = symbols(a)
    n, = size(x)
    h, α = zeros(n), zeros(n-1)

    for i in 1:n-1
        h[i] = x[i+1] - x[i]
    end
    for i in 2:n-1
        α[i] = 3/h[i] * (y[i+1] - y[i]) - 3/h[i-1] *(y[i] - y[i-1])
    end

    l = ones(n)
    μ = zeros(n)
    z = zeros(n)

    for i in 2:n-1
        l[i] = 2*(x[i+1] - x[i-1]) - h[i-1]*μ[i-1]
        μ[i] = h[i]/l[i]
        z[i] = (α[i] - h[i-1]*z[i-1])/l[i]
    end

    c, b, d = zeros(n), zeros(n), zeros(n)
    for j in reverse(1:n-1)
        c[j] = z[j] - μ[j]*c[j+1]
        b[j] = (y[j+1] - y[j])/h[j] - h[j]*(c[j+1]+2*c[j])/3
        d[j] = (c[j+1] - c[j])/(3*h[j])
    end
    A = [b[1:end-1] c[1:end-1] d[1:end-1]]
    X = []
    for i in 2:4
        for j in 1:3
            push!(X,(a - x[i-1])^(j))
        end
    end
    res = diag(A * reshape(X, (n-1,n-1))) + y[1:end-1]

    if Latex
        for i in 1:n-1
            print(res[i], ",","t ∈","[$(x[i]), $(x[i+1])]")
            print("\n")

        end
    else
        return res
    end
end

"""
    CCSpline(x::Vector, y::Vector, endpoint::Vector; Latex::Bool=false, a::String="t")

input ``x_1, x_2, x_3, ...x_n```,values ``f(x_1), f(x_2), ...,f(x_n)`` and ``f'(x_1), f'(x_n)``, set Args `Latex = true`  to
output information about the function.
"""
@inline function CCSpline(x::Vector, y::Vector, endpoint::Vector; Latex::Bool=false, a::String="t")
    n, = size(x)
    h = ones(n)
    α = ones(n)
    a = symbols(a)

    for i in 1:n-1
        h[i] = x[i+1] - x[i]
    end
    α[1] = 3(y[2] - y[1])/h[1] - 3*endpoint[1]
    α[n] = 3*endpoint[2] - 3*(y[n] - y[n-1])/h[n-1]
    for i in 2:n-1
        α[i] = 3/h[i] * (y[i+1] - y[i]) - 3/h[i-1] *(y[i] - y[i-1])
    end

    l = ones(n)
    l[1] = 2*h[1]
    μ = fill(0.5, n)
    z = fill(α[1]/l[1], n)
    for i in 2:n-1
        l[i] = 2*(x[i+1] - x[i-1]) - h[i-1]*μ[i-1]
        μ[i] = h[i]/l[i]
        z[i] = (α[i] - h[i-1]*z[i-1])/l[i]
    end
    l[n] = h[n-1]*(2 - μ[n-1])
    z[n] = (α[n] - h[n-1]*z[n-1])/l[n]
    c = fill(z[n], n)
    b, d = zeros(n), zeros(n)
    for j in reverse(1:n-1)
        c[j] = z[j] - μ[j]*c[j+1]
        b[j] = (y[j+1] - y[j])/h[j] - h[j]*(c[j+1]+2*c[j])/3
        d[j] = (c[j+1] - c[j])/(3*h[j])
    end
    A = [b[1:end-1] c[1:end-1] d[1:end-1]]
    X = []
    for i in 2:4
        for j in 1:3
            push!(X,(a - x[i-1])^(j))
        end
    end
    res = diag(A * reshape(X, (n-1,n-1))) + y[1:end-1]
    if Latex
        for i in 1:n-1
            print(res[i], ",","t ∈","[$(x[i]), $(x[i+1])]")
            print("\n")

        end
    else
        return res
    end

end

end


function Euler2()

	Fa = 1;
	Fb = 1;

	sum = 0;

	F = 0;

	while F<4e6
		F = Fa+Fb;
		Fa = Fb;
		Fb = F;

		if mod(F,2) == 0
			sum+=F
		end
	end

	return sum

end

@time Euler2()
println(Euler2())


### A Pluto.jl notebook ###
# v0.12.16

using Markdown
using InteractiveUtils

# ╔═╡ e077ab16-22e5-11eb-15ed-5f0654bf7328
using Pkg, DrWatson

# ╔═╡ e75a9a06-22e5-11eb-2e36-8d4f62b830ed
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Random, Distributions, Plots, LaTeXStrings
	Random.seed!(0)
end

# ╔═╡ ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
md"## Listing5.18"

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
begin
	prior(lam) = pdf(TriangularDist(0, 10, 3), lam)
	data = [2,1,0,0,1,0,2,2,5,2,4,0,3,2,5,0]

	like(lam) = *([pdf(Poisson(lam),x) for x in data]...)
	posteriorUpToK(lam) = like(lam)*prior(lam)
end

# ╔═╡ 2bd08a88-3afb-11eb-03e1-c3af32635947
begin
	delta = 10^-4.
	lamRange = 0:delta:10
	K = sum([posteriorUpToK(lam)*delta for lam in lamRange])
	posterior(lam) = posteriorUpToK(lam)/K
end

# ╔═╡ 2bd0c412-3afb-11eb-1e28-27323df7bbe6
begin
	bayesEstimate = sum([lam*posterior(lam)*delta for lam in lamRange])
	Text("Bayes estimate: $(bayesEstimate)")
end

# ╔═╡ 2bd13636-3afb-11eb-3c12-2d5a0c0e4b13
begin
	plot(lamRange, prior.(lamRange),
		c=:blue, label="Prior distribution")
	plot!(lamRange, posterior.(lamRange),
		c=:red, label="Posterior distribution",
		xlims=(0, 10), ylims=(0, 1.2),
		xlabel=L"\lambda",ylabel="Density")
end

# ╔═╡ 475ff888-22e6-11eb-2354-09f8f40a8e12
md"## End of listing5.18"

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═2bd08a88-3afb-11eb-03e1-c3af32635947
# ╠═2bd0c412-3afb-11eb-1e28-27323df7bbe6
# ╠═2bd13636-3afb-11eb-3c12-2d5a0c0e4b13
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


"""
**RMSProp Optimizer**
```julia
RMSprop(; η::Real=0.001, γ::Real=0.01, ϵ::Real=1e-8)
```

Algorithm

```math
\\begin{align*}
    E[g^2]_t =& \\gamma E[g^2]_{t-1}+(1-\\gamma) g_t^2\\\\
    \\Delta x_t =& \\frac{\\eta}{\\sqrt{E[g^2]_t + \\epsilon}}g_t
\\end{align*}
```
    [Reference](http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf)
"""
mutable struct RMSprop <: Optimizer
    opt_type::String
    t::Int64
    ϵ::Float64
    η::Float64
    γ::Float64
    E_g²_t::AbstractArray
end

function RMSprop(; η::Real=0.001, γ::Real=0.01, ϵ::Real=1e-8)
    @assert η > 0.0 "η must be greater than 0"
    @assert γ > 0.0 "γ must be greater than 0"
    @assert ϵ > 0.0 "ϵ must be greater than 0"

    RMSprop("RMSprop", 0, ϵ, η, γ, [])
end

params(opt::RMSprop) = "ϵ=$(opt.ϵ), η=$(opt.η), γ=$(opt.γ)"

function update(opt::RMSprop, g_t::AbstractArray{T}) where {T<:Real}
    # resize accumulated and squared updates
    if opt.t == 0
        opt.E_g²_t = zero(g_t)
    end

    # accumulate gradient
    opt.E_g²_t = opt.γ * opt.E_g²_t + (one(T) - opt.γ) * (g_t .^ 2)

    # compute update
    RMS_g_t = sqrt.(opt.E_g²_t .+ opt.ϵ)
    δ = opt.η * g_t ./ RMS_g_t

    return δ
end


using Distributions
using Gadfly
using GSL
using PMTK4Stats

αSS    = [20, 20]
βSS    = [10, 10]
αPrior = [20, 30]
βPrior = [20, 10]
M = 2
mixprior = [0.5, 0.5]
αPost      = αPrior + αSS
βPost      = βPrior + βSS

function mixBetaPost_()
    logmarglik  = lbeta(αPrior+αSS, βPrior+βSS)-lbeta(αPrior, βPrior)
    normPost, L = normalizeLogspace(logmarglik .+ log(mixprior))
    mixpost     = exp(normPost)
end

function mixBetaDemo()
    mixpost = mixBetaPost_()
    grid = 0.0001:0.01:0.9999

    function evalpdf(θ, Z, α, β)
        sum([Z[i].*pdf(Beta(α[i], β[i]), θ) for i=1:length(Z)])
    end

    prior = evalpdf(grid, mixprior, αPrior, βPrior)
    post  = evalpdf(grid, mixpost, αPost, βPost)

    lprior = layer(x=grid, y=prior, Geom.line, Theme(default_color=colorant"red", line_width=3px, line_style=Gadfly.get_stroke_vector(:dash)))
    lpost  = layer(x=grid, y=post, Geom.line, Theme(default_color=colorant"blue", line_width=3px))
    [lprior lpost]
end

function mixBetaBiased()
    mixpost = mixBetaPost_()
    pbiased = sum(mixpost.*(1-[sf_beta_inc(αPost[i], βPost[i], .5) for i=1:length(αPost)]))
    pbiasedSimple = 1-sf_beta_inc(αPost[1], βPost[1], .5)
    [pbiased, pbiasedSimple]
end


module RFIOD
export binarysearch, fixedpointiteration, staircasediagram

using Plots

"""
    binarysearch(f::Function, a::Real, b::Real, [ε::Real])

Use the binary search algorithm to find a root of `f` between `a` and `b` with maximum trucation error `ε`.

Also returns the number of iterations that the algorithm takes.

# Examples

```julia-repl
julia> binarysearch(sin,3,4)
(3.141590118408203, 17)

julia> abs(ans[1] - π) < 5e-6
true
```
"""
function binarysearch(f::Function, a::Real, b::Real, ε::Real = 5e-6)

    # Checks that the initial interval is valid
    fa, fb = f(a), f(b)
    iszero(fa) && return (a,0)
    iszero(fb) && return (b,0)
    fa * fb < 0 || error("cannot find a root, function must take opposite signs on each end of the interval")

    # Iteratively finds the halfway point of the interval, then keeps the half where the sign of f changes
    N = 0
    while abs(b-a) > 2ε
        N += 1
        m = (a+b)/2
        fm = f(m)
        iszero(fm) && return (m,N)
        (a, b, fa, fb) = ( fa * fm > 0 ? (m, b, fm, fb) : (a, m, fa, fm) )
    end
    # Returns the midpoint of the interval, which is within ε of the root
    return ((a+b)/2,N)

end

"""
    fixedpointiteration(f::Function, x₀::Real, Nₘₐₓ::Int, [ε::Real])

Use the fixed point iteration algorithm to converge on a fixed point of `f`, with starting value `x₀`.

Terminate if consecutive iterations differ by less than `ε`, or after `Nₘₐₓ` iterations.

Returns the sequence resulting from the iteration.

# Examples

```julia-repl
julia> xs = fixedpointiteration(cos, 0, 100)
92-element Vector{Float64}:
 0.0
 1.0
 [...]
 0.7390851332151607

julia> cos(xs[end])
0.7390851332151607
```
"""
function fixedpointiteration(f::Function, x₀::Real, Nₘₐₓ::Int, ε::Real = eps(Float64))

    # Sets up the initial values for the algorithm
    n = 1
    xₙ₋₁ = convert(Float64,x₀)
    xₙ = f(xₙ₋₁)
    xvalues = [xₙ₋₁, xₙ]

    # Iteratively advances the algorithm by evaluating f at the previous result and storing it
    while abs(xₙ₋₁ - xₙ) ≥ ε && n < Nₘₐₓ
        n += 1
        xₙ₋₁, xₙ = xₙ, f(xₙ)
        push!(xvalues,xₙ)
    end
    # Returns the sequence resulting from the algorithm
    return xvalues

end

"""
staircasediagram(f::Function, xvalues::Vector{Float64})

Create a staircase/cobweb diagram from `xvalues`, the results of the fixed point iteration algorithm on `f`.

# Examples

```julia-repl
julia> staircasediagram(cos, fixedpointiteration(cos, 0, 100))
```
"""
function staircasediagram(f::Function, xvalues::Vector{Float64})

    # Finds the scale at which to draw the diagram
    minx, maxx = min(xvalues...), max(xvalues...)
    δ = (maxx - minx)/100
    xticks = (minx - 10δ):δ:(maxx + 10δ)

    # Plots y = x and y = f(x), then adds in a marker for the starting point and the lines given by the sequence
    p = plot([x -> x, f], xticks, legend = false)
    scatter!(p, (xvalues[1], xvalues[1]), markercolor = :black)
    for n = 1:(length(xvalues)-1)
        plot!(p, [xvalues[n], xvalues[n], xvalues[n+1]],
            [xvalues[n], xvalues[n+1], xvalues[n+1]], linecolor = :black)
    end
    # Returns the plot
    return p

end

end


function linsolve_cr( LF::LF3dGrid, b::Array{Float64,1};
                      x0 = nothing,
                      NiterMax = 1000, TOL=5.e-10,
                      convmsg=false, showprogress=false )
  #
  Npoints = size(b)[1]
  #
  if x0 == nothing
    x = zeros(Float64,Npoints)
  else
    x = copy(x0)
  end
  #
  L_x = apply_Laplacian( LF, x )
  #
  r = b[:] - L_x[:]
  p = r[:]

  r_old = r[:]
  p_old = p[:]
  #
  L_p = apply_Laplacian( LF, p )
  x_old = x[:]
  #
  for iter = 1 : NiterMax
    #
    L_r = apply_Laplacian( LF, r )
    #
    rLr = dot( r, L_r )
    α = rLr / dot( L_p, L_p )  # ???
    #
    x_old = x[:]
    x[:] = x[:] + α * p[:]  # FIXME use x[:] to force x to be copied, not referenced
    #
    r_old = r[:]
    rLr_old = rLr
    r[:] = r[:] - α * L_p[:]
    #
    norm_res = sqrt( dot( r, r ) )
    norm_p   = sqrt( dot( p, p ) )
    Δx = abs(x-x_old)
    norm_Δx = sqrt( dot( Δx, Δx ) )
    if showprogress
      @printf("%8d %18.10e %18.10e %18.10e\n", iter, norm_res, norm_p, norm_Δx)
    end
    #
    if norm_res < TOL || norm_p < TOL || norm_Δx < TOL
      if convmsg
        @printf("#Convergence achieved in linsolve_cr: %8d iterations.\n", iter )
      end
      break
    end
    #
    L_r = apply_Laplacian( LF, r ) # calculate new L_r
    β = dot( r, L_r ) / rLr_old
    #
    p_old = p[:]
    p = r[:] + β * p[:]
    #
    L_p = L_r[:] + β * L_p[:]
  end
  #
  return x
  #
end # of function


module MonteCarlo

using Random
using Statistics

# European Option Calculation from
# https://groups.google.com/forum/?hl=en&fromgroups=#!topic/julia-dev/ImhGsqX_IHc

function perf_euro_option_devec(npaths)
    steps = 250
    r = 0.05
    sigma = .4
    T = 1
    dt = T/steps
    K = 100
    S = fill(100.0, npaths)
    t1 = (r - 0.5*sigma^2)*dt
    t2 = sigma*sqrt(dt)

    for i=1:steps
        for j=1:npaths
            S[j] *= exp(t1 + t2 * randn())
        end
    end

    return mean(exp(-r*T) .* max.(K .- S, 0))
end

function perf_euro_option_vec(npaths)
    steps = 250
    r = 0.05
    sigma = .4
    T = 1
    dt = T/steps
    K = 100
    S = fill(100.0, npaths)
    t1 = (r - 0.5*sigma^2)*dt
    t2 = sigma*sqrt(dt)
    R = Array{Float64}(undef, npaths)

    for i=1:steps
        S .*= exp.(t2 .* randn!(R) .+ t1)
    end

    return mean(exp(-r*T) .* max.(K .- S, 0))
end

end # module


# # Two-Stage Stochastic Program

# ## Introduction
# First let's consider a standard two-stage stochastic program. Such problems
# consider 1st stage variables ``x \in X \subseteq \mathbb{R}^{n_x}`` which denote
# upfront (here-and-now) decisions made before any realization of the random
# parameters ``\xi \in \mathbb{R}^{n_\xi}`` is observed, and 2nd stage variables
# ``y(\xi) \in \mathbb{R}^{n_y}`` which denote recourse (wait-and-see) decisions
# that are made in response to realizations of ``\xi``. Moreover, the objective
# seeks to optimize 1st stage costs ``f_1(x)`` and second stage costs
# ``f_2(x, y(\xi))`` which are evaluated over the uncertain domain via a risk
# measure ``R_\xi[\cdot]`` (e.g., the expectation ``\mathbb{E}_\xi[\cdot]``).
# Putting this together, we obtain the two-stage stochastic program:
# ```math
# \begin{aligned}
#     &&\min_{x, y(\xi)} &&& f_1(x) + R_\xi[f_2(x, y(\xi))] \\
#     &&\text{s.t.} &&&  g_i(x, y(\xi)) = 0, && i \in I\\
#     &&&&& h_j(x, y(\xi)) \leq 0, && j \in J\\
#     &&&&&  x \in X\\
# \end{aligned}
# ```
# where ``g_i(x, y(\xi)), \ i \in I,`` denote 2nd stage equality constraints,
# ``h_j(x, y(\xi)), \ j \in J,`` are 2nd stage inequality constraints, and ``X``
# denotes the set of feasible 1st stage decisions.

# ## Formulation

# For an example, we consider the classic farmer problem. Here the farmer must
# allocate farmland ``x_c`` for each crop ``c \in C`` with random yields per acre
# ``\xi_c`` such that he minimizes expenses (i.e., maximizes profit) while fulfilling
# contractual demand ``d_c``. If needed he can purchase crops from other farmers
# to satisfy his contracts. He can also sell extra crop yield that exceeds his
# contractual obligations. Thus, here we have 1st stage variables ``x_c`` and
# 2nd stage variables of crops sold ``w_c(\xi)`` and crops purchased ``y_c(\xi)``.
# Putting this together using the expectation ``\mathbb{E}_\xi[\cdot]`` as our
# risk measure we obtain:
# ```math
# \begin{aligned}
#     &&\underset{x, y(\xi), w(\xi)}{\text{min}} &&& \sum_{c \in C} \alpha_c x_c + \mathbb{E}_{\xi}\left[\sum_{c \in C}\beta_c y_c(\xi) - \lambda_c w_c(\xi)\right] \\
#     &&\text{s.t.} &&&  \sum_{c \in C} x_c \leq \bar{x}\\
#     &&&&& \xi_c x_c + y_c(\xi) - w_c(\xi) \geq d_c, && c \in C \\
#     &&&&& 0 \leq x_c \leq \bar{x}, && c \in C \\
#     &&&&& 0 \leq y_c(\xi) \leq \bar{y}_c, && c \in C \\
#     &&&&& 0 \leq w_c(\xi) \leq \bar{w}_c, && c \in C \\
#     &&&&& \xi_c \in \Xi_c, && c \in C
# \end{aligned}
# ```
# where ``\alpha_c`` are production costs, ``\beta_c`` are the purchase prices,
# ``\lambda_c`` are the selling prices, ``\bar{x}`` is the total acreage,
# ``\bar{y}_c`` are purchases limits, ``\bar{w}_c`` are selling limits, and
# ``\Xi_c`` are the underlying distributions.

# ## Problem Setup

# First let's import the necessary packages:
using InfiniteOpt, Distributions, Ipopt

# Next let's specify the problem data:
num_scenarios = 10 # small amount for example
C = 1:3
α = [150, 230, 260] # land cost
β = [238, 210, 0]   # purchasing cost
λ = [170, 150, 36]  # selling price
d = [200, 240, 0]   # contract demand
xbar = 500          # total land
wbar3 = 6000        # no upper bound on the other crops
ybar3 = 0           # no upper bound on the other crops
Ξ = [Uniform(0, 5), Uniform(0, 5), Uniform(10, 30)]; # the distributions

# ## Problem Definition

# Let's start by setting up the infinite model that uses Ipopt as the optimizer
# that will ultimately be used to solve the transcribed variant:
model = InfiniteModel(Ipopt.Optimizer)
set_optimizer_attribute(model, "print_level", 0);

# Now let's define the infinite parameters using [`@infinite_parameter`](@ref):
@infinite_parameter(model, ξ[c in C] ~ Ξ[c], num_supports = num_scenarios)

# Now let's define all of the decision variables using `@variables`:
@variables(model, begin
    ## 1st stage variables
    0 <= x[C] <= xbar
    ## 2nd stage variables
    0 <= y[C], Infinite(ξ)
    0 <= w[C], Infinite(ξ)
end)

# Next, the objective is defined using `@objective` and [`𝔼`](@ref):
@objective(model, Min, sum(α[c] * x[c] for c in C) +
                       𝔼(sum(β[c] * y[c] - λ[c] * w[c] for c in C), ξ))

# Finally, all we need to do is define the constraints using `@constraints`:
@constraints(model, begin
    ## capacity constraint
    sum(x[c] for c in C) <= xbar
    ## balances
    [c in C], ξ[c] * x[c] + y[c] - w[c] >= d[c]
    ## crop limits
    w[3] <= wbar3
    y[3] <= ybar3
end)

# ## Problem Solution

# With the model defined, let's optimize and get the results
optimize!(model)
x_opt = value.(x)
profit = -objective_value(model)

println("Land Allocations: ", [round(x_opt[k], digits = 2) for k in keys(x_opt)])
println("Expected Profit: \$", round(profit, digits = 2))

# We did it!

# ## CVaR Objective

# An interesting modification to the above problem would be to use a ``CVaR``
# risk measure instead of an expectation. This also can be readily achieved via
# `InfiniteOpt`. The ``CVaR`` measure is defined:
# ```math
# CVaR_\epsilon(X) = \underset{t \in \mathbb{R}}{\text{inf}}\left\{t + \frac{1}{1-\epsilon} \mathbb{E}[\text{max}(0, X - t)] \right\}
# ```
# where ``\epsilon`` is the confidence level. Inserting this into the formulation,
# we now obtain:
# ```math
# \begin{aligned}
#     &&\underset{x, y(\xi), w(\xi), t, q(\xi)}{\text{min}} &&& \sum_{c \in C} \alpha_c x_c + t + \frac{1}{1-\epsilon} \mathbb{E}_{\xi}[q(\xi)] \\
#     &&\text{s.t.} &&& \sum_{c \in C} x_c \leq \bar{x}\\
#     &&&&& \xi_c x_c + y_c(\xi) - w_c(\xi) \geq d_c, && c \in C \\
#     &&&&& 0 \leq x_c \leq \bar{x}, && c \in C \\
#     &&&&& 0 \leq y_c(\xi) \leq \bar{y}_c, && c \in C \\
#     &&&&& 0 \leq w_c(\xi) \leq \bar{w}_c, && c \in C \\
#     &&&&& \xi_c \in \Xi_c, && c \in C \\
#     &&&&& q(\xi) \geq \sum_{c \in C}\beta_c y_c(\xi) - \lambda_c w_c(\xi) - t \\
#     &&&&& q(\xi) \geq 0
# \end{aligned}
# ```
# where ``q(\xi)`` is introduced to handle the max operator. Let's update and
# resolve our `InfiniteOpt` model using ``\epsilon = 0.95``:

# Define the additional variables:
@variables(model, begin
    t
    q >= 0, Infinite(ξ)
end)

# Redefine the objective:
@objective(model, Min, sum(α[c] * x[c] for c in C) + t + 1 / (1 - 0.95) * 𝔼(q, ξ))

# Add the max constraint:
@constraint(model, q >= sum(β[c] * y[c] - λ[c] * w[c] for c in C) - t)

# Optimize and get the results
optimize!(model)
x_opt = value.(x)
y_opt = value.(y)
w_opt = value.(w)
profit = -sum(α[c] * x_opt[c] for c in C) - 1 / num_scenarios *
            sum(β[c] * y_opt[c][k] - λ[c] * w_opt[c][k] for c in C, k in 1:num_scenarios)

println("Land Allocations: ", [round(x_opt[k], digits = 2) for k in keys(x_opt)])
println("Expected Profit: \$", round(profit, digits = 2))

# That's it!

# ### Maintenance Tests
# These are here to ensure this example stays up to date.
using Test
@test termination_status(model) == MOI.LOCALLY_SOLVED
@test x_opt isa JuMPC.DenseAxisArray{<:Real}
@test profit isa Real


#! /usr/bin/julia

# Rosetta Code, Digital root/Multiplicative digital root

const bs = 10
const excnt = 5

function digitalmultroot{S<:Integer,T<:Integer}(n::S, bs::T=10)
    -1 < n && 1 < bs || throw(DomainError())
    ds = n
    pers = 0
    while bs <= ds
        ds = prod(digits(ds, bs))
        pers += 1
    end
    return (pers, ds)
end

println("Testing Multiplicative Digital Root.\n")
for i in [123321, 7739, 893, 899998]
    (pers, ds) = digitalmultroot(i, bs)
    print(@sprintf("%8d", i))
    print(" has persistence ", pers)
    println(" and digital root ", ds)
end

dmr = zeros(Int, bs, excnt)
hasroom = trues(bs)
dex = ones(Int, bs)

i = 0
while any(hasroom)
    (pers, ds) = digitalmultroot(i, bs)
    ds += 1
    if hasroom[ds]
        dmr[ds, dex[ds]] = i
        dex[ds] += 1
        if dex[ds] > excnt
            hasroom[ds] = false
        end
    end
    i += 1
end

println("\n MDR:    First ", excnt, " numbers having this MDR")
for (i, d) in enumerate(0:(bs-1))
    print(@sprintf("%4d: ", d))
    println(join([@sprintf("%6d", dmr[i, j]) for j in 1:excnt], ","))
end


# This file calculates the third application in Y.-S. Chan, A. C. Fannjiang, and G. H. Paulino,
# Integral equations with hypersingular kernels -- theory and applications to fracture mechanics,
# Int. J. Eng. Sci., 41:683--720, 2003.

using ApproxFun, SingularIntegralEquations

x = Fun(identity)
w = 1/sqrt(1-x^2)
d = domain(x)
d2 = d^2
B = [Dirichlet(d);Neumann(d)]
H2 = Hilbert(d,2)
H4 = Hilbert(d,4)
ϵ = 0.2
L = -6ϵ^2*H4[w] + H2[w]
f = -Fun(one)

uSIE = [B;L]\[[0,0],[0,0],f]
@time uSIE = [B;L]\[[0,0],[0,0],f]
println("The length of uSIE is: ",ncoefficients(uSIE))
println("The extrema of uSIE are: ",extrema(uSIE))

test0 = uSIE/(1-x^2)^2
test = Fun(x->test0(x),Ultraspherical(1,d))
temp = 0.0
for i=1:ncoefficients(test)
    temp += i*test.coefficients[i]
end
println("The normalized generalized Stress Intensity Factors are: ",3ϵ*temp)


@doc raw"""
```
t = find_threshold(Yen(), histogram, edges)
```

Computes the threshold value using Yen's maximum correlation criterion for
bilevel thresholding.

# Output

Returns a real number `t` in `edges`. The `edges` parameter represents an
`AbstractRange` which specifies the intervals associated with the histogram bins.

# Details

This algorithm uses the concept of *entropic correlation* of a gray level histogram to produce a threshold
value.

Let ``f_1, f_2, \ldots, f_I`` be the frequencies in the various bins of the
histogram and ``I`` the number of bins. With ``N = \sum_{i=1}^{I}f_i``, let
``p_i = \frac{f_i}{N}`` (``i = 1, \ldots, I``) denote the probability
distribution of gray levels. From this distribution one derives two additional
distributions. The first defined for discrete values ``1`` to ``s`` and the
other, from ``s+1`` to ``I``. These distributions are

```math
A: \frac{p_1}{P_s}, \frac{p_2}{P_s}, \ldots, \frac{p_s}{P_s}
\quad \text{and} \quad
B: \frac{p_{s+1}}{1-P_s}, \ldots, \frac{p_n}{1-P_s}
\quad \text{where} \quad
P_s = \sum_{i=1}^{s}p_i.
```
The entropic correlations associated with each distribution are

```math
C(A) = -\ln \sum_{i=1}^{s} \left( \frac{p_i}{P_s} \right)^2 \quad \text{and} \quad C(B) = -\ln \sum_{i=s+1}^{I} \left( \frac{p_i}{1 - P_s} \right)^2.
```

Combining these two entropic correlation functions we have

```math
\psi(s) = -\ln \sum_{i=1}^{s} \left( \frac{p_i}{P_s} \right)^2 -\ln \sum_{i=s+1}^{I} \left( \frac{p_i}{1 - P_s} \right)^2.
```
Finding the discrete value ``s`` which maximises the function ``\psi(s)`` produces
the sought-after threshold value (i.e. the bin which determines the threshold).

# Arguments

The function arguments are described in more detail below.

##  `histogram`

An `AbstractArray` storing the frequency distribution.

##  `edges`

An `AbstractRange` specifying how the intervals for the frequency distribution
are divided.

# Example

Compute the threshold for the "cameraman" image in the `TestImages` package.

```julia

using TestImages, ImageContrastAdjustment, HistogramThresholding

img = testimage("cameraman")
edges, counts = build_histogram(img, 256)
#=
  The `counts` array stores at index 0 the frequencies that were below the
  first bin edge. Since we are seeking a threshold over the interval
  partitioned by `edges` we need to discard the first bin in `counts`
  so that the dimensions of `edges` and `counts` match.
=#
t = find_threshold(Yen(), counts[1:end], edges)
```

# Reference

1. Yen JC, Chang FJ, Chang S (1995), “A New Criterion for Automatic Multilevel Thresholding”, IEEE Trans. on Image Processing 4 (3): 370-378, [doi:10.1109/83.366472](https://doi.org/10.1109/83.366472)
"""
function find_threshold(algorithm::Yen, histogram::AbstractArray, edges::AbstractRange)
    total = sum(histogram)
    m = length(histogram)
    p = zeros(m)

    # Calulate probability.
    for i in eachindex(p)
        p[i] = histogram[i] / total
    end

    # Setup sums.
    Pₛ = 0
    Gₛ = 0
    G′ₛ = sum(p.^2)
    maxval = typemin(Float64)
    t = firstindex(edges)
    for s in eachindex(p[1:m-1])
        # Update sums.
        Pₛ += p[s]
        Gₛ += p[s]^2
        G′ₛ -= p[s]^2

        # Calculate total correlation.
        TC = -log(Gₛ * G′ₛ) + 2 * log(Pₛ * (1 - Pₛ))
        if TC > maxval
            maxval = TC
            t = s
        end
    end

    return edges[t]
end


@doc """

Calculates dfgls point optimal unit-root test statistic of Elliott, Rothenberg, and Stock (1996) value of the largest autorregressive coefficient under the alternative hypothesis given by ρ = 1+c/T, where c=-7 if trend="constant" and c=-13.5 if trend="linear")

"""->
function dfgls_test(y::Vector{Float64}; nar::Integer=5, trend::String="constant")
  #rewrite of mw's dfgls.prc
  t = length(y)
  dftau = NaN*zeros(1, 2)

  # @ -- GLS estimate of constant term -- @
  c = -7
  rho = (1+c/t)
  yrho = [y[1]; y[2:t]-rho*y[1:t-1]] #@ Quasi Differenced Y series @
  xcrho = [1.0; (1-rho)*ones(t-1)] #@ Quasi Difference Constant @
  mu = ((xcrho'*xcrho).^(-1)).*(xcrho'yrho)

  c = -13.5
  rho = (1+c/t)
  yrho = [y[1]; y[2:t]-rho*y[1:t-1]] #@ Quasi Differenced Y series @
  xcrho = [1.0; (1-rho)*ones(t-1)] #@ Quasi Difference Constant @
  trnd = collect(1:t)
  xtrho = [trnd[1]; trnd[2:t]-rho*trnd[1:t-1]]
  x = [xcrho xtrho]
  mut = inv(x'x)*(x'yrho)
  #  @ -- detrend y -- @
  y1 = y - mu.*ones(t)                #@ GLS Demeaned @
  y2 = y - [ones(t,1) trnd]*mut        #@ GLS Detrended @
  dy1 = diff(y1)
  dy2 = diff(y2)
  #compute adf
  x1 = ar_lagmatrix(dy1, nar)
  x2 = ar_lagmatrix(dy2, nar)

  z = [y1[1:size(x1,1)] x1]
  y = z[:, 1]
  x = z[:, 2:end]
  xxi = inv(x'x)
  xy = x'y
  bet = xxi*xy
  ssr = (y'y)-(xy)'bet
  s2 = ssr/(size(y,1)-size(x,2))
  b = bet[1]
  vb = s2.*xxi[1,1]
  dfmu = (b-1)./sqrt(vb)

  z = [y1[1:size(x1,1)] x1]
  y = z[:, 1]
  x = z[:, 2:end]
  xxi = inv(x'x)
  xy = x'y
  bet = xxi*xy
  ssr = (y'y)-(xy)'bet
  s2 = ssr/(size(y,1)-size(x,2))
  b = bet[1]
  vb = s2.*xxi[1,1]
  dftau = (b-1)./sqrt(vb)
  (trend=="constant") && return dfmu[1]
  (trend=="linear") && return dftau[1]
  (trend=="both") && return dfmu[1], dftau[1]
  end


# chebyshev points and chebyshev differentiation matrix
# Ref: Spectral methods in MATLAB by Trefethen, chapters 1, 5 and 6.
function cheb_pts(n)
	return cos.((0:n)*pi/n)
end
function cheb_diff_matrix(n)
	x = cheb_pts(n)
	np = n+1
	D = zeros(np,np)
	E = view(D, reverse(1:np*np))
	D[1] = (2*n*n + 1)/6.
	E[1] = -D[1]

	nhalf = fld(np, 2)
	xint = view(x,2:n)
	D[2:n] .= (-1).^(1:n-1)./(xint .- 1)./2.0
	E[2:n] .= -D[2:n]
	D[np] = (-1)^np/2.0
	E[np] = -D[np]

	if mod(np,2) != 0
		j = nhalf + 1
		A = view(D, (j-1)*np+1:j*np)
		A .=  1.0./(x .- x[j]).*
		((-1).^((1 + j):(np + j)))
		A[j] = -x[j]/2/(1. - x[j]*x[j])
		A[1] *= 2.0
		A[np] *= 2.0
	end

	for j=2:nhalf
		A = view(D, (j-1)*np+1:j*np)
		B = view(E, (j-1)*np+1:j*np)
		A .= 1.0./(x .- x[j]).*
		((-1).^((1 + j):(np + j)))
		B .= -A
		A[1] *= 2.0
		B[1] *= 2.0
		A[j] = -x[j]/2/(1. - x[j]*x[j])
		B[j] = -A[j]
		A[np] *= 2.0
		B[np] *= 2.0
	end

	return D
end


using LinearAlgebra
using SparseArrays
using Plots

"This routine solves the heat equation using Backwards Euler in time and finite differences
in space. The output is saved as a .gif file (which should play automatically when this file is run)."

m = 100 # number of points
dt = .01 # time-step
T = 2.0

u0(x) = 0.0 # initial condition
f(x) = 5*(Float64((x > -.5) && (x <= 0.0)) - Float64((x < .5) && (x >= 0.0)))
f(x,t) = f(x)*exp(-t)
α(t) = 1.0
β(t) = sin(2*pi*t) # du/dx

# define spatial grid
x = LinRange(-1,1,m+2)
xint = x[2:end-1]
h = x[2]-x[1]
A = (1/h^2) * spdiagm(0=>2*ones(m),-1=>-ones(m-1),1=>-ones(m-1))

function F(xint,t)
    b = f.(xint,t)
    b[1] += α(t)/h^2
    b[end] += β(t)/h^2
    return b
end

u = u0.(xint)
Nsteps = ceil(Int,T/dt)
dt = T / Nsteps

interval = 2
@gif for i = 1:Nsteps
    t = (i+1)*dt
    u .= (I + dt*A)\(u + dt*F(xint,t))
    if i % interval == 0 # % = mod function
        plot(xint,u,linewidth=2,label="Solution",ylims=(-1.0,3.0))
        println("on timestep $i out of $Nsteps.")
    end
end every interval


# src/secondorder.jl contains function for solving second order ODEs
# can use either numerical integration or closed for formulas
# secondorder(a,b,c) generates the function you pass to ODE.jl RK methods
# solution(a,b,c) is the closed form solver
# ivp solves the initial value problem.
using Optim
using NLsolve

""" secondorder(α,β,γ) yields a function f
    which calculates the ode rule

        αx'' + βx' + γx = 0

    using the substitution

        y = [x; x']

        y[1]' = y[2]

        y[2]' = -α^-1 (βy[2] + γy[1])

"""
function secondorder(α, β, γ)
        return (t, y) -> [y[2]; -(β*y[2] + γ*y[1])/α]
end

function secondorder(x::Vector)
    return secondorder(x...)
end

"""descriminant(a,b,c): quadtratic equation descriminant of ax^2 + bx + c = 0
"""
descriminant(a,b,c) = √(complex(b^2-4a*c))

"""roots(a,b,c): solve the quadratic equation ax^2 + bx + c = 0 """
function roots(a,b,c)
    d = descriminant(a,b,c);
    return (-b+d)/2a, (-b-d)/2a
end

doc"""solution(a,b,c,y₀,t₀) general solution of second order linear homogenous

Arguments:
   - a,b,c::Real the coeffients of the ODE

Returns:
   - func(t, c₁, c₂): the solution at t when the coefficients are c₁, c₂
   - deriv(t, c₁, c₂): the derivative at t when the coefficients are c₁, c₂

Notes: func is derived from the textbook definition and deriv is based on
 applying calculus rules to that (a symbolic algebra tool did this).
"""
function solution(a,b,c)
    @show d = descriminant(a,b,c)
    @show r₁, r₂ = roots(a,b,c)
    if real(d) == d
        info("characteristic polynomial has real roots")
        return (t, c₁, c₂) -> c₁ * exp(r₁*t) + c₂*exp(r₂*t),
               (t, c₁, c₂) -> c₁*r₁*exp(r₁*t) + c₂*r₂*exp(r₂*t)

    end
    if imag(d) != 0.0
        info("characteristic polynomial has complex roots")
        @show α, β = -b/2a, imag(d/2a)
        func(t, c₁, c) = begin
            return (c₁*exp(α*t)*cos(β*t) + c*exp(α*t)*sin(β*t))
        end
        deriv(t,c₁,c₂) = begin
            expα = exp(α*t)
            cosβ = cos(β*t)
            sinβ = sin(β*t)
            return (-β*c₁*expα*sinβ +
                    α*c₂*expα*sinβ +
                    α*c₁*expα*cosβ +
                    β*c₂*expα*cosβ
                    )
            end
        return func, deriv
    end
end

"""ivp(a,b,c,y₀,t₀) solves second order linear homogenous initial value problem

Arguments:
   - a,b,c::Real the coeffients of the ODE
   - y₀ the initial value as a vector
   - t₀ the initial time value

Returns:
   - result : the Optim.jl result object of solving the equations
   - general: the general form solution (see `solution`)
   - deriv:   the general form derivative (see `solution`)

Note: result.zero is the coefficients to plug into the general solution
to get the correct particular solution.
"""
function ivp(a,b,c, y₀, t₀)
    x₀ = y₀[1]
    v₀ = y₀[2]
    general, deriv = solution(a,b,c)

    function f!(params, fvec)
        fvec[1] = x₀ - general(t₀, params...)
        fvec[2] = v₀ - deriv(t₀, params...)
    end
    result = nlsolve(f!,
                     [0.0,0.0],
                     autodiff=true,
                     method=:trust_region,
                     store_trace=true)
    return result, general, deriv
end


using FFTW
export Fourier, FrequencySplit

# TODO: Allow width to the removed frequencies, ie. remove
# a Gaussian around the specified frequency
# This is possibly done via a combination of the
# DSP.Filters.Bandstop and probably some windowing function from DSP.

"""
    Fourier([s, ] frequencies, x=true) <: Decomposition
Decompose a timeseries `s` into a **sum** `x + r`, by identifying specific `frequencies`
at the Fourier space and removing them from the signal.
`x` is the removed periodic component while `r` is the residual.
If a given frequency is not exactly matching the Fourier frequencies, the closest one is
removed.

**Important**: periods/frequencies are defined with respect to the `t` axis length,
the actual `t` values are not used in this method. So, frequency `1/12` (a period of `12`)
means `12` data points (whose actual value depends on `t`).

If you provide `s` the method plans the forward and inverse
Fourier transforms (so that it is efficient to re-use it for `s` of same type and length).

This method works well when a periodic signal P is superimposed on fluctuations S,
and you have a good educated guess of what frequencies compose P.
This method works well if the given signal has length multiple of the periods given.

There is arbitrarity of which part of the signal `x, r`
gets the mean value of `s`, because it is deducted for a better fit.
The argument `x=true` attributes it to `x`, use `false` for `r`.
"""
struct Fourier{F, I} <: Decomposition
    fs::Vector{Float64}
    forward::F
    inverse::I
    x::Bool
end
Fourier(fs, x::Bool=true) = Fourier(fs, nothing, nothing, x)
function Fourier(s::AbstractVector, fs::AbstractVector, x::Bool=true)
    forward = plan_rfft(s)
    inverse = plan_irfft(forward*s, length(s))
    return Fourier(fs, forward, inverse, x)
end

function decompose(t, s, method::Fourier)
    isequispaced(t) || error("Input time axis must be equispaced for method Fourier.")
    if length(s) % round(Int, maximum(1/f for f in method.fs)) ≠ 0
        @warn "The signal length is not a multiple of largest period."
    end

    m = mean(s)
    𝓕 = isnothing(method.forward) ? rfft(s .- m) : method.forward*(s .- m)
    fs = rfftfreq(length(s))
    for f in method.fs
        i = findnearest(f, fs)
        𝓕[i] = 0.0
    end
    inv_𝓕 = isnothing(method.inverse) ? irfft(𝓕, length(s)) : method.inverse*𝓕
    residual = inv_𝓕
    periodic = s .- residual
    if !method.x
        periodic .-= m
        residual .+= m
    end
    return periodic, residual
end

"""
    FrequencySplit([s, ] f::Real) <: Decomposition
Similar to the [`Fourier`](@ref) method, but now the "residual" signal is the part
of `s` with frequencies higher than `f`, while the "seasonal" part has frequencies `≤ f`.
"""
struct FrequencySplit{F, I} <: Decomposition
    f::Float64
    forward::F
    inverse::I
end
FrequencySplit(fs) = FrequencySplit(fs, nothing, nothing)
function FrequencySplit(s, fs)
    forward = plan_rfft(s)
    inverse = plan_irfft(forward*s, length(s))
    return FrequencySplit(fs, forward, inverse)
end

function decompose(t, s, method::FrequencySplit)
    isequispaced(t) || error("Input time axis must be equispaced for method FrequencySplit.")

    m = mean(s)
    𝓕 = isnothing(method.forward) ? rfft(s .- m) : method.forward*(s .- m)
    fs = rfftfreq(length(s))
    i = findlast(f -> f ≤ method.f, fs)
    𝓕[1:i+1] .= 0.0
    inv_𝓕 = isnothing(method.inverse) ? irfft(𝓕, length(s)) : method.inverse*𝓕
    residual = inv_𝓕
    periodic = s .- residual
    return periodic, residual
end


module SecantMethod

export secant_method

function secant_method(f::Function, x0::Number, x1::Number, args::Tuple=();
                       tol::AbstractFloat=1e-5, maxiter::Integer=50)
    for _ in 1:maxiter
       y1 = f(x1, args...)
       y0 = f(x0, args...)
       x = x1 - y1* (x1 - x0)/(y1 - y0)
       if abs(x - x1) < tol
          return x
       end
       x0 = x1
       x1 = x
    end
    error("Max iteration exceeded")
end

end


doc"""
    FDist(ν1,ν2)

The *F distribution* has probability density function

$f(x; \nu_1, \nu_2) = \frac{1}{x B(\nu_1/2, \nu_2/2)}
\sqrt{\frac{(\nu_1 x)^{\nu_1} \cdot \nu_2^{\nu_2}}{(\nu_1 x + \nu_2)^{\nu_1 + \nu_2}}},
\quad x>0$

It is related to the [`Chisq`](:func:`Chisq`) distribution via the property that if $X_1
\sim \operatorname{Chisq}(\nu_1)$ and $X_2 \sim \operatorname{Chisq}(\nu_2)$, then
$(X_1/\nu_1) / (X_2 / \nu_2) \sim FDist(\nu_1, \nu_2)`.

```julia
FDist(d1, d2)     # F-Distribution with parameters d1 and d2

params(d)         # Get the parameters, i.e. (d1, d2)
```

External links

* [F distribution on Wikipedia](http://en.wikipedia.org/wiki/F-distribution)
    """
immutable FDist <: ContinuousUnivariateDistribution
    ν1::Float64
    ν2::Float64

    function FDist(ν1::Real, ν2::Real)
        @check_args(FDist, ν1 > zero(ν1) && ν2 > zero(ν2))
        new(ν1, ν2)
    end
end

@distr_support FDist 0.0 Inf

#### Parameters

params(d::FDist) = (d.ν1, d.ν2)

#### Statistics

mean(d::FDist) = (ν2 = d.ν2; ν2 > 2.0 ? ν2 / (ν2 - 2.0) : NaN)

mode(d::FDist) = ((ν1, ν2) = params(d); ν1 > 2.0 ? ((ν1 - 2.0)/ν1) * (ν2 / (ν2 + 2.0)) : 0.0)

function var(d::FDist)
    (ν1, ν2) = params(d)
    ν2 > 4.0 ? 2.0 * ν2^2 * (ν1 + ν2 - 2.0) / (ν1 * (ν2 - 2.0)^2 * (ν2 - 4.0)) : NaN
end

function skewness(d::FDist)
    (ν1, ν2) = params(d)
    if ν2 > 6.0
        return (2.0 * ν1 + ν2 - 2.0) * sqrt(8.0 * (ν2 - 4.0)) / ((ν2 - 6.0) * sqrt(ν1 * (ν1 + ν2 - 2.0)))
    else
        return NaN
    end
end

function kurtosis(d::FDist)
    (ν1, ν2) = params(d)
    if ν2 > 8.0
        a = ν1 * (5. * ν2 - 22.) * (ν1 + ν2 - 2.) + (ν2 - 4.) * (ν2 - 2.)^2
        b = ν1 * (ν2 - 6.) * (ν2 - 8.) * (ν2 - 2.)
        return 12. * a / b
    else
        return NaN
    end
end

function entropy(d::FDist)
    (ν1, ν2) = params(d)
    hν1 = ν1 * 0.5
    hν2 = ν2 * 0.5
    hs = (ν1 + ν2) * 0.5
    return log(ν2 / ν1) + lgamma(hν1) + lgamma(hν2) - lgamma(hs) +
        (1.0 - hν1) * digamma(hν1) + (-1.0 - hν2) * digamma(hν2) +
        hs * digamma(hs)
end

#### Evaluation & Sampling

@_delegate_statsfuns FDist fdist ν1 ν2

rand(d::FDist) = StatsFuns.Rmath.fdistrand(d.ν1, d.ν2)


export cg,bicgstab,Lanczos

function cg{T1<:Number,T2<:Number}(A::AbstractMatrix{T1},b::Vector{T2})
	n=length(b)
	n1,n2=size(A)
	n == n1 == n2 || throw(DimensionMismatch(""))
	T=promote_type(T1,T2)
	x=zeros(T,n)
	r=b-A*x
	p=r
	rsold=dot(r,r)
	TR=typeof(real(rsold))
	for i=1:n
		Ap=A*p
		alpha=rsold/dot(p,Ap)
		x=x+alpha*p
		r=r-alpha*Ap
		rsnew=dot(r,r)
		if real(sqrt(rsnew))<10eps(TR)
			return x
		end
		p=r+rsnew/rsold*p
		rsold=rsnew
	end
	return x
end

function cg{T1<:Number,T2<:Number,T3<:Number}(A::AbstractMatrix{T1},b::Vector{T2},P::AbstractMatrix{T3})
	n=length(b)
	n1,n2=size(A)
	n3,n4=size(P)
	n == n1 == n2 == n3 == n4 || throw(DimensionMismatch(""))
	T=promote_type(T1,T2,T3)
	x=P\b
	r=b-A*x
	z=P\r
	p=z
	rsold=dot(r,z)
	TR=typeof(real(rsold))
	for i=1:n
		Ap=A*p
		alpha=rsold/dot(p,Ap)
		x=x+alpha*p
		r=r-alpha*Ap
		z=P\r
		rsnew=dot(r,z)
		if real(sqrt(rsnew))<10eps(TR)
			return x
		end
		p=z+rsnew/rsold*p
		rsold=rsnew
	end
	return x
end

function bicgstab{T1<:Number,T2<:Number}(A::AbstractMatrix{T1},b::Vector{T2})
	n=length(b)
	n1,n2=size(A)
	n == n1 == n2 || throw(DimensionMismatch(""))
	T=promote_type(T1,T2)
	x=zeros(T,n)
	r=b-A*x
	rhat = r
	ρold = one(T)
	α =one(T)
	ωold = one(T)
	v=zeros(T,n)
	p=zeros(T,n)
	TR = typeof(real(α))
	for i=1:n
		ρnew = dot(rhat,r)
		β = (ρnew/ρold)*(α/ωold)
		p = r + β*(p-ωold*v)
		v = A*p
		α = ρnew/dot(rhat,v)
		s = r - α*v
		t = A*s
		ωnew = dot(t,s)/dot(t,t)
		upd = α*p + ωnew*s
		x = x + upd
		if norm(upd)<10eps(TR)
			return x
		end
		r = s - ωnew*t
		ρold = ρnew
		ωold = ωnew
	end
	return x
end

function bicgstab{T1<:Number,T2<:Number,T3<:Number}(A::AbstractMatrix{T1},b::Vector{T2},P::AbstractMatrix{T3})
	n=length(b)
	n1,n2=size(A)
	n3,n4=size(P)
	n == n1 == n2 == n3 == n4 || throw(DimensionMismatch(""))
	T=promote_type(T1,T2,T3)
	x=P\b
	r=b-A*x
	rhat = r
	ρold = one(T)
	α =one(T)
	ωold = one(T)
	v=zeros(T,n)
	p=zeros(T,n)
	TR = typeof(real(α))
	for i=1:n
		ρnew = dot(rhat,r)
		β = (ρnew/ρold)*(α/ωold)
		p = r + β*(p-ωold*v)
		y = P\p
		v = A*y
		α = ρnew/dot(rhat,v)
		s = r - α*v
		z = P\s
		t = A*z
		zt = P\t
		ωnew = dot(zt,z)/dot(zt,zt)
		upd = α*y + ωnew*z
		x = x + upd
		if norm(upd)<10eps(TR)
			return x
		end
		r = s - ωnew*t
		ρold = ρnew
		ωold = ωnew
	end
	return x
end

function Lanczos{T<:Number}(A::AbstractMatrix{T})
	n,n1 = size(A)
	n == n1 || throw(DimensionMismatch(""))
	z = Array(T,n)
	vold = zero(T)
	vnew = [one(T),zeros(T,n-1)]
	beta = zeros(T,n)
	alpha = zeros(T,n)
	for j = 1:n-1
		w = A*vnew
		alpha[j] = dot(w,vnew)
		w = w - alpha[j]*vnew - beta[j]*vold
		beta[j+1] = norm(w)
		vold = vnew
		vnew = w/beta[j+1]
	end
	w = A*vnew
	alpha[n] = dot(w,vnew)
	@time steig!(alpha,[beta[2:end],zero(T)],z,10000)
	return sort!(alpha)
 end


"""
 Take the L2 Tikhonov regularised inverse of a matrix `m`.

 The regularisation parameter is the larger of `delta` (the optional argument that defaults
 to zero) and square root of machine epsilon. The inverse is done using an SVD.
 """
 function reginv(a::AbstractMatrix, δ = defaulttol(a))
     U, S, V = svd(a)
     Sinv = inv.(hypot.(S, δ))
     return  V * Diagonal(Sinv) * U'
 end

 function posreginv(a::AbstractMatrix, δ = defaulttol(a))
     # assumes a is positive definite (or close to)
     D, V = eigen(Hermitian(a))
     Dinv = inv.(hypot.(D, δ))
     return  V * Diagonal(Dinv) * V'
 end


export Random

"""
    Random(;args)

Randomly generated initial data, based on provided (optional arguments) :
- `L` is the typical wavelength (default is `L=1`)
- `s` is the (real) Sobolev index regularity (default is `s=∞`)
- `λ` is the length of spatial localization (default is none)

The initial data `(η,v)` are generated through randomly chosen Fourier coefficients,
multiplied with weigth `w=10^(-|k|L/(2π))` if `s=∞`, or `w=1/(1+9(|k|L/(2π))^(s+1/2))` otherwise.
If `λ` is provided, the function in spatial variables is multiplied by `exp(-|x/λ|^2)`,
and in any case normalized to have maximum absolute value 1.

"""
struct Random <: InitialData

    η
    v

    function Random(;L=1,s=Inf,λ=nothing)

        function generate( x )

            k = Mesh( x ).k
            if s == Inf
                w = 10 .^(-abs.(k*L/(2*π)))
            else
                w = 1 ./( 1 .+ 9*abs.(k*L/(2*π)).^(s+1/2) )
            end
            if λ == nothing
                φ = zero(x).+1
            else
                φ = exp.(-abs.((x/λ).^2))
            end

            θ = 2*π*rand(Float64,length(x))
            r = rand(Float64,length(x))
            Fourier = r.*exp.(-1im.*θ).*w
            Physic = real.(ifft(Fourier)).*φ
            return Physic./maximum(abs.(Physic))
        end
        η( x ) = generate( x )
        v( x ) = generate( x )

    	new( η,v )

    end

end


####### Routines related to Chebyshev interpolation or polynomials that are not about
#   evaluation nor interpolation set up.

# scale x ∈ [a,b] to [-1,1].
function scaletoChebyinterval(x::T, a::T, b::T)::T where T
    @assert a <= x <= b
    @assert b > a # disallow empty interval.

    Δ::T = b-a
    return (x-a)/Δ*convert(T,2) - one(T)
end

# scale y ∈ [-1,1] to [a,b].
function scalefromChebyinterval(y::T, a::T, b::T)::T where T
    @assert -one(T) <= y <= one(T)

    Δ::T = b-a
    return (y+1)/2*Δ + a
end

function Chebyshev2nd(x::T, L::Int)::T where T
    two = one(T) + one(T)

    out = zero(T)
    for l = 0:div(L,2)
        out += (-one(Int))^l*binomial(L-l,l)*(two*x)^(L-2*l)
    end

    return out
end

function Chebyshev1stall(x::T, L::Int)::Vector{T} where T
    @assert L >= 2

    # setup.
    two = one(T)+one(T)

    out = Vector{T}(undef,L+1)
    out[1] = one(T)
    out[2] = x
    for i = 3:length(out)
        out[i] = two*x*out[i-1] - out[i-2]
    end

    return out
end

function Chebyshev1st(x::T, L::Int)::T where T
    @assert L >= 0

    @assert isnumericallyin(x,-one(T),one(T))
    clamp(x,-one(T),one(T))

    return cos(convert(T,L)*acos(x))
end

# fill array with function
# equivalent to: collect( 𝝋[j](y[i]) for j = 1:n+1 for i = 1:m+1 ), but transposed.
function getunivariateChebyVmatrix(𝓧::Vector{T}, L::Int) where T
    p = length(𝓧)
    V = Matrix{T}(undef,L+1,p)

    for j = 1:p
        V[:,j] = Chebyshev1stall(𝓧[j],L)
    end

    return V
end

function Chebyshev1stnodes(Lp1::Int)::Vector{BigFloat}
    L = Lp1 - 1
    half = BigFloat("0.5")
    return collect( cos( (BigFloat(L-i)+half)*BigFloat(π)/BigFloat(L+1) ) for i = 0:L+1 )
end

function Chebyshev2ndnodes(L::Int)::Vector{BigFloat}
    return collect( cos( (L-i)*BigFloat(π)/BigFloat(L) ) for i = 0:L+1 )
end

function Chebyshev2ndnode(i::Int, L::Int)::BigFloat
    return cos( (L-i)*BigFloat(π)/BigFloat(L) )
end

# Colleague matrix for a L-degree Chebyshev polynomial of the mth kind.
function constructcolleaguematrix(c::Vector{T}, m::Int) where T
    @assert m == 1 || m == 2

    L = length(c) - 1

    while isnumericallyclose(c[L+1], zero(T), 1e-9) && L > 2
        L -= 1
    end

    # pre-compute.
    two = convert(T,2)
    half = one(T)/two

    C = zeros(T,L,L)
    C[2,1] = half

    # fill out the band.
    for l = 2:L-1
        C[l-1,l] = half
        C[l+1,l] = half
    end
    C[L-1,L] = half

    if m == 1
        C[1,2] = one(T)
    end

    # fill out bottom row.
    for l = 1:L
        C[L,l] = -c[l]/(two*c[L+1])
    end

    C[L,L-1] += half

    return C
end

# polynomial basis is Chebyshev polynomials of the m-th kind.
# coeffs are a_k for m = 1
# coeffs are c_k-1 = k*a_k for m = 2
function findChebyitproots(a::Vector{T}, m::Int)::Vector{T} where T
    if count(!isnumericallyclose(a[i],zero(T)) for i = 1:length(a)) < 2
        # case, a is the zero vector or a has less than 2 non-zero entries.
        #   The latter case means constant term and linear term. No local extrema.
        return Vector{T}(undef,0)
    end

    C = constructcolleaguematrix(a,m)

    return findChebyitproots(C)
end

function findChebyitproots(C::Matrix{T})::Vector{T} where T
    C_eig = LinearAlgebra.eigen(convert(Matrix{Float64},C))
    𝑧 = convert(Vector{T}, real.(C_eig.values))
    unique!(𝑧)
    filter!(xx->isnumericallyin(xx,-one(T),one(T)), 𝑧)
    clamp!(𝑧,-one(T),one(T))

    return 𝑧
end

function findChebyitpextrema(a::Vector{T}) where T
    L = length(a)-1

    c = collect( k*a[k+1] for k = 1:L)

    extrema_positions = findChebyitproots(c,2)

    return extrema_positions
end

# remove roots that do not have a zero derivative.
function findChebyitpextremawithchk(a::Vector{T}, tol::T = convert(T,0.2)) where T
    X = findChebyitpextrema(a)

    𝑑itp_X = evalunivariateChebyshevpolynomialderivative(a,X)

    remove_list = Vector{Int}(undef,0)
    for i = 1:length(X)
        if !isnumericallyclose(𝑑itp_X[i],zero(T),tol)
            push!(remove_list,i)
        end
    end

    deleteat!(X,remove_list)

    return X
end

# N is the number of samples to take between each reference position.
# 𝓧 is the set of reference positions.
function findChebyitpextremainterval(  𝑒::Function,
                                    L::Int,
                                    𝓟_in::Vector{T},
                                    tol_𝓧_spacing, # was defaulted to convert(T,1e-6/length(𝓟_in)
                                    tol_derivative_zero) where T

    ### get roots.
    sample_position_template = collect( Chebyshev2ndnode(k,L) for k = 0:L )
    out, d_set, X_set = getextremafromroots(𝑒, L, 𝓟_in,
                                            sample_position_template,
                                            tol_𝓧_spacing, tol_derivative_zero)

    # check each point in out to see if its a stationary point.
    chkfunc = xx->numericalchkextrema(𝑒, xx, tol_derivative_zero)
    filter!(chkfunc,out)

    sort!(out)
    unique!(out)

    return out, d_set, X_set
end

function discreteinnerprod(f::Function, g::Function, L::Int)

    ν = collect( Chebyshev2ndnode(k,L) for k = 0:L )

    term1 = 1/2 * f(ν[1]) * g(ν[1])

    term2 = BigFloat("0")
    for l = 1:L-1
        term2 += f(ν[l+1]) * g(ν[l+1])
    end

    term3 = 1/2 * f(ν[L+1]) * g(ν[L+1])

    return term1 + term2 + term3
end


# export  fit_cubic_spline,
#       sample_spline,
#       sample_spline_derivative,
#       sample_spline_derivative2,
#       sample_spline_speed,
#       sample_spline_theta,
#       sample_spline_curvature,
#       sample_spline_derivative_of_curvature,
#       calc_curve_length,
#       arclength,
#       calc_curve_param_given_arclen

function _integrate_simpsons(f::Function, a::Real, b::Real, n::Int)
    # integrate using Composite Simpson's rule
    # reference: https://en.wikipedia.org/wiki/Simpson%27s_rule

    @assert(n > 0) # number of intervals
    @assert(mod(n,2) == 0) # n must be even

    h = (b-a)/n
    retval = f(a) + f(b)
    flip = true
    for i = 1 : n-1
        retval += f(a+i*h) * (flip ? 4 : 2)
        flip = !flip
    end
    return h/3*retval
end

function _fit_open(pts::AbstractVector{Float64} )
    # fits the 1-D spline such that:
    #   spline goes through each point
    #   first and second derivatives match at each inner point
    #   the second derivative at the ends is zero
    # see: http://mathworld.wolfram.com/CubicSpline.html

    # this function returns a 4×(n-1) spline coefficient matrix, where n = |pts|

    n = length(pts)-1
    @assert(n > 0)

    M = spzeros(n+1,n+1)
    for i = 1 : n
        M[i,i] = 4
        M[i,i+1] = 1
        M[i+1,i] = 1
    end
    M[n+1,n+1] = 2
    M[1,1] = 2

    Y = Array{Float64}(n+1)
    for i = 1 : n+1
        ind_hi = min(i+1,n)
        ind_lo = max(1,i-1)
        Y[i] = 3*(pts[ind_hi] - pts[ind_lo])
    end

    D = M\Y

    spline_coeffs = Array{Float64}(4, n) # col is <a,b,c,d>
    spline_coeffs[1,:] = pts[1:n]
    spline_coeffs[2,:] = D[1:n]
    spline_coeffs[3,:] = 3*(pts[2:n+1] - pts[1:n]) -2*D[1:n]-D[2:n+1]
    spline_coeffs[4,:] = 2*(pts[1:n] - pts[2:n+1]) + D[1:n] + D[2:n+1]

    spline_coeffs
end
function _fit_closed(pts::AbstractVector{Float64} )
    # fits the 1-D spline such that:
    #   spline goes through each point
    #   first and second derivatives match at each inner point
    #   first the second derivative at the ends match
    # see: http://mathworld.wolfram.com/CubicSpline.html

    # this function returns a 4×n spline coefficient matrix, where n = |pts|

    n = length(pts)-1
    @assert(n > 0)

    M = spzeros(n+1,n+1)
    for i = 1 : n
        M[i,i] = 4
        M[i,i+1] = 1
        M[i+1,i] = 1
    end
    M[n+1,n+1] = 4
    M[1,n+1] = 1
    M[n+1,1] = 1

    Y = Array{Float64}(n+1)
    Y[1] = 3*(pts[2] - pts[n+1])
    for i = 2 : n
        Y[i] = 3*(pts[i+1] - pts[i-1])
    end
    Y[end] = 3*(pts[1] - pts[n])

    D = M\Y

    spline_coeffs = Array{Float64}(4, n+1) # col is <a,b,c,d>
    spline_coeffs[1,:] = pts
    spline_coeffs[2,:] = D
    spline_coeffs[3,1:n] = 3*(pts[2:n+1] - pts[1:n]) -2*D[1:n]-D[2:n+1]
    spline_coeffs[4,1:n] = 2*(pts[1:n] - pts[2:n+1]) + D[1:n] + D[2:n+1]
    spline_coeffs[3,n+1] = 3*(pts[1] - pts[n+1]) -2*D[n+1]-D[1]
    spline_coeffs[4,n+1] = 2*(pts[n+1] - pts[1]) + D[n+1] + D[1]

    spline_coeffs
end
function _fit_open(pts::Matrix{Float64}) # 2×n {x,y}

    # see http://mathworld.wolfram.com/CubicSpline.html

    d,n = size(pts)
    n -= 1

    Y = Array{Float64}(n+1)

    M = sparse(Int[], Int[], Float64[], n+1,n+1)
    for i in 1 : n
        M[i,i] = 4.0
        M[i,i+1] = 1.0
        M[i+1,i] = 1.0
    end
    M[n+1,n+1] = 2.0
    M[1,1] = 2.0

    retval = Array{Matrix{Float64}}(d)
    for k in 1 : d

        for i in 1 : n+1
            ind_hi = min(i+1,n)
            ind_lo = max(1,i-1)
            Y[i] = 3*(pts[k,ind_hi] - pts[k,ind_lo])
        end

        D = M \ Y

        spline_coeffs = Array{Float64}(4, n) # col is <a,b,c,d> for a + b⋅t + c⋅t² + d⋅t³
        spline_coeffs[1,:] = pts[k,1:n] # x₀
        spline_coeffs[2,:] = D[1:n]     # x'₀
        spline_coeffs[3,:] = 3*(pts[k,2:n+1]' - pts[k,1:n]') -2*D[1:n] - D[2:n+1] # -3x₀ + 3x₁ - 2x'₀ - x'₁
        spline_coeffs[4,:] = 2*(pts[k,1:n]' - pts[k,2:n+1]') +  D[1:n] + D[2:n+1] #  2x₀ - 2x₁ +  x'₀ + x'₁

        retval[k] = spline_coeffs
    end
    retval
end
function _fit_closed(pts::AbstractMatrix{Float64})
    d = size(pts,1)
    retval = Array{Matrix{Float64}}(d)
    for i = 1 : d
        retval[i] = _fit_closed(vec(pts[i,:]))
    end
    retval
end

function fit_cubic_spline(pts::AbstractArray{Float64}; open::Bool=true)
    if open
        return _fit_open(pts)
    else
        return _fit_closed(pts)
    end
end

function sample_spline(spline_coeffs::AbstractVector{Float64}, t::Float64)
    # here t is generally expected to be t ∈ [0,1]
    return spline_coeffs[1] + t*(spline_coeffs[2] + t*(spline_coeffs[3] + t*spline_coeffs[4]))
end
function sample_spline(spline_coeffs::AbstractMatrix{Float64}, t::Float64)
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    @assert(size(spline_coeffs, 1) == 4)
    col_ind = clamp(ceil(Int, t), 1, size(spline_coeffs,2))
    sample_spline(spline_coeffs[:,col_ind], t-col_ind+1)
end
function sample_spline(spline_coeffs::AbstractVector{Float64}, t_arr::AbstractVector{Float64})
    # here t is generally expected to be t ∈ [0,1]

    a = spline_coeffs[1]
    b = spline_coeffs[2]
    c = spline_coeffs[3]
    d = spline_coeffs[4]

    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        retval[i] = a + t*(b + t*(c + t*d))
    end
    retval
end
function sample_spline(spline_coeffs::AbstractMatrix{Float64}, t_arr::AbstractVector{Float64})
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    @assert(size(spline_coeffs, 1) == 4)
    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        col_ind = clamp(ceil(Int, t), 1, size(spline_coeffs,2))
        retval[i] = sample_spline(spline_coeffs[:,col_ind], t-col_ind+1)
    end
    retval
end

function sample_spline_derivative(spline_coeffs::AbstractVector{Float64}, t::Float64)
    # here t is generally expected to be t ∈ [0,1]
    return spline_coeffs[2] + t*(2spline_coeffs[3] + t*3spline_coeffs[4])
end
function sample_spline_derivative(spline_coeffs::AbstractMatrix{Float64}, t::Float64)
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    @assert(size(spline_coeffs, 1) == 4)
    col_ind = clamp(ceil(Int, t), 1, size(spline_coeffs,2))
    sample_spline_derivative(spline_coeffs[:,col_ind], t-col_ind+1)
end
function sample_spline_derivative(spline_coeffs::AbstractVector{Float64}, t_arr::AbstractVector{Float64})
    # here t is generally expected to be t ∈ [0,1]

    b = spline_coeffs[2]
    c = spline_coeffs[3]
    d = spline_coeffs[4]

    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        retval[i] = b + t*(2c + t*3d)
    end
    retval
end
function sample_spline_derivative(spline_coeffs::AbstractMatrix{Float64}, t_arr::AbstractVector{Float64})
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    @assert(size(spline_coeffs, 1) == 4)
    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        col_ind = clamp(ceil(Int, t), 1, size(spline_coeffs,2))
        retval[i] = sample_spline_derivative(spline_coeffs[:,col_ind], t-col_ind+1)
    end
    retval
end

function sample_spline_derivative2(spline_coeffs::AbstractVector{Float64}, t::Float64)
    # here t is generally expected to be t ∈ [0,1]
    return 2spline_coeffs[3] + t*6spline_coeffs[4]
end
function sample_spline_derivative2(spline_coeffs::AbstractMatrix{Float64}, t::Float64)
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    @assert(size(spline_coeffs, 1) == 4)
    col_ind = clamp(ceil(Int, t), 1, size(spline_coeffs,2))
    sample_spline_derivative2(spline_coeffs[:,col_ind], t-col_ind+1)
end
function sample_spline_derivative2(spline_coeffs::AbstractVector{Float64}, t_arr::AbstractVector{Float64})
    # here t is generally expected to be t ∈ [0,1]

    b = spline_coeffs[2]
    c = spline_coeffs[3]
    d = spline_coeffs[4]

    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        retval[i] = 2c + t*6d
    end
    retval
end
function sample_spline_derivative2(spline_coeffs::AbstractMatrix{Float64}, t_arr::AbstractVector{Float64})
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    @assert(size(spline_coeffs, 1) == 4)
    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        col_ind = clamp(ceil(Int, t), 1, size(spline_coeffs,2))
        retval[i] = sample_spline_derivative2(spline_coeffs[:,col_ind], t-col_ind+1)
    end
    retval
end

function sample_spline_speed(spline_coeffs_x::AbstractVector{Float64}, spline_coeffs_y::AbstractVector{Float64}, t::Float64)
    dxdt = sample_spline_derivative(spline_coeffs_x, t)
    dydt = sample_spline_derivative(spline_coeffs_y, t)
    hypot(dxdt, dydt)
end
function sample_spline_speed(spline_coeffs_x::AbstractMatrix{Float64}, spline_coeffs_y::AbstractMatrix{Float64}, t::Float64)
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x, 1) == 4)
    @assert(size(spline_coeffs_y, 1) == 4)
    @assert(n == size(spline_coeffs_y, 2))
    col_ind = clamp(ceil(Int, t), 1, n)::Int
    sample_spline_speed(spline_coeffs_x[:,col_ind], spline_coeffs_y[:,col_ind], t-col_ind+1)
end
function sample_spline_speed(spline_coeffs_x::AbstractVector{Float64}, spline_coeffs_y::AbstractVector{Float64}, t_arr::AbstractVector{Float64})
    # here t is generally expected to be t ∈ [0,1]

    bx = spline_coeffs_x[2]
    cx = spline_coeffs_x[3]
    dx = spline_coeffs_x[4]

    by = spline_coeffs_y[2]
    cy = spline_coeffs_y[3]
    dy = spline_coeffs_y[4]

    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        dxdt = bx + t*(2cx + t*3dx)
        dydt = by + t*(2cy + t*3dy)
        retval[i] = hypot(dxdt, dydt)
    end
    retval
end
function sample_spline_speed(spline_coeffs_x::AbstractMatrix{Float64}, spline_coeffs_y::AbstractMatrix{Float64}, t_arr::AbstractVector{Float64})
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.

    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x, 1) == 4)
    @assert(size(spline_coeffs_y, 1) == 4)
    @assert(n == size(spline_coeffs_y, 2))
    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        col_ind = clamp(ceil(Int, t), 1, n)
        retval[i] = sample_spline_speed(spline_coeffs_x[:,col_ind], spline_coeffs_y[:,col_ind], t-col_ind+1)
    end
    retval
end

function sample_spline_theta(spline_coeffs_x::AbstractVector{Float64}, spline_coeffs_y::AbstractVector{Float64}, t::Float64;
    stepsize=1e-4
    )

    # compute the angle from positive x-axis (counter-clockwise positive) of the curve in the positive t direction at t
    # uses an approximation via small step size instead of derivative due to zero-derivative issues
    # uses the forward derivative approximation unless it would put it out of range
    # result returned is in radians

    t_lo, t_hi = t, t+stepsize
    if t_hi > 1.0
        t_lo, t_hi = t-min(1000stepsize,0.1), t
    end

    x1 = sample_spline(spline_coeffs_x, t_lo)
    x2 = sample_spline(spline_coeffs_x, t_hi)
    y1 = sample_spline(spline_coeffs_y, t_lo)
    y2 = sample_spline(spline_coeffs_y, t_hi)

    # println("(t, lo, hi)  $t   $t_lo   $t_hi, ($(atan2(y2-y1, x2-x1)))")

    atan2(y2-y1, x2-x1)
end
function sample_spline_theta(spline_coeffs_x::AbstractMatrix{Float64}, spline_coeffs_y::AbstractMatrix{Float64}, t::Float64)
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x, 1) == 4)
    @assert(size(spline_coeffs_y, 1) == 4)
    @assert(n == size(spline_coeffs_y, 2))
    col_ind = clamp(ceil(Int, t), 1, n)
    sample_spline_theta(spline_coeffs_x[:,col_ind], spline_coeffs_y[:,col_ind], t-col_ind+1)
end
function sample_spline_theta(spline_coeffs_x::AbstractVector{Float64}, spline_coeffs_y::AbstractVector{Float64}, t_arr::AbstractVector{Float64})
    # here t is generally expected to be t ∈ [0,1]

    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        retval[i] = sample_spline_theta(spline_coeffs_x, spline_coeffs_y, t)
    end
    retval
end
function sample_spline_theta(spline_coeffs_x::AbstractMatrix{Float64}, spline_coeffs_y::AbstractMatrix{Float64}, t_arr::AbstractVector{Float64})
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.

    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x, 1) == 4)
    @assert(size(spline_coeffs_y, 1) == 4)
    @assert(n == size(spline_coeffs_y, 2))
    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        col_ind = clamp(ceil(Int, t), 1, n)
        retval[i] = sample_spline_theta(spline_coeffs_x[:,col_ind], spline_coeffs_y[:,col_ind], t-col_ind+1)
    end
    retval
end

function sample_spline_curvature(spline_coeffs_x::AbstractVector{Float64}, spline_coeffs_y::AbstractVector{Float64}, t::Float64)
    # computes the signed curvature

    dx  = sample_spline_derivative( spline_coeffs_x, t)
    dy  = sample_spline_derivative( spline_coeffs_y, t)
    ddx = sample_spline_derivative2(spline_coeffs_x, t)
    ddy = sample_spline_derivative2(spline_coeffs_y, t)

    (dx*ddy - dy*ddx)/(dx*dx + dy*dy)^1.5
end
function sample_spline_curvature(spline_coeffs_x::AbstractMatrix{Float64}, spline_coeffs_y::AbstractMatrix{Float64}, t::Float64)
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x, 1) == 4)
    @assert(size(spline_coeffs_y, 1) == 4)
    @assert(n == size(spline_coeffs_y, 2))
    col_ind = clamp(ceil(Int, t), 1, n)
    sample_spline_curvature(spline_coeffs_x[:,col_ind], spline_coeffs_y[:,col_ind], t-col_ind+1)
end
function sample_spline_curvature(spline_coeffs_x::AbstractVector{Float64}, spline_coeffs_y::AbstractVector{Float64}, t_arr::AbstractVector{Float64})
    # here t is generally expected to be t ∈ [0,1]

    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        retval[i] = sample_spline_curvature(spline_coeffs_x, spline_coeffs_y, t)
    end
    retval
end
function sample_spline_curvature(spline_coeffs_x::AbstractMatrix{Float64}, spline_coeffs_y::AbstractMatrix{Float64}, t_arr::AbstractVector{Float64})
    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.

    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x, 1) == 4)
    @assert(size(spline_coeffs_y, 1) == 4)
    @assert(n == size(spline_coeffs_y, 2))
    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        col_ind = clamp(ceil(Int, t), 1, n)
        retval[i] = sample_spline_curvature(spline_coeffs_x[:,col_ind], spline_coeffs_y[:,col_ind], t-col_ind+1)
    end
    retval
end

function sample_spline_derivative_of_curvature(spline_coeffs_x::AbstractVector{Float64}, spline_coeffs_y::AbstractVector{Float64}, t::Float64;
    stepsize=1e-4
    )

    # computes the derivative of the signed curvature

    t_lo, t_hi = t, t+stepsize
    if t_hi > 1.0
        t_lo, t_hi = t-stepsize, t
    end

    κ_hi = sample_spline_curvature(spline_coeffs_x, spline_coeffs_y, t_hi)
    κ_lo = sample_spline_curvature(spline_coeffs_x, spline_coeffs_y, t_lo)

    (κ_hi - κ_lo) / stepsize
end
function sample_spline_derivative_of_curvature(spline_coeffs_x::AbstractMatrix{Float64}, spline_coeffs_y::AbstractMatrix{Float64}, t::Float64;
    stepsize=1e-4
    )

    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.
    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x, 1) == 4)
    @assert(size(spline_coeffs_y, 1) == 4)
    @assert(n == size(spline_coeffs_y, 2))
    col_ind = clamp(ceil(Int, t), 1, n)
    sample_spline_derivative_of_curvature(spline_coeffs_x[:,col_ind], spline_coeffs_y[:,col_ind], t-col_ind+1, stepsize=stepsize)
end
function sample_spline_derivative_of_curvature(spline_coeffs_x::AbstractVector{Float64}, spline_coeffs_y::AbstractVector{Float64}, t_arr::AbstractVector{Float64};
    stepsize=1e-4
    )

    # here t is generally expected to be t ∈ [0,1]

    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        retval[i] = sample_spline_derivative_of_curvature(spline_coeffs_x, spline_coeffs_y, t, stepsize=stepsize)
    end
    retval
end
function sample_spline_derivative_of_curvature(spline_coeffs_x::AbstractMatrix{Float64}, spline_coeffs_y::AbstractMatrix{Float64}, t_arr::AbstractVector{Float64};
    stepsize=1e-4
    )

    # for t ∈ (-∞,1] we use spline_coeffs[:,1]
    # for t ∈ [1,2] we use spline_coeffs[:,2]
    # etc.

    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x, 1) == 4)
    @assert(size(spline_coeffs_y, 1) == 4)
    @assert(n == size(spline_coeffs_y, 2))
    retval = Array{Float64}(length(t_arr))
    for (i,t) in enumerate(t_arr)
        col_ind = clamp(ceil(Int, t), 1, n)
        retval[i] = sample_spline_derivative_of_curvature(spline_coeffs_x[:,col_ind], spline_coeffs_y[:,col_ind], t-col_ind+1, stepsize=stepsize)
    end
    retval
end

function calc_curve_length(spline_coeffs_x::AbstractVector{Float64}, spline_coeffs_y::AbstractVector{Float64};
    n_intervals::Int = 100
    )

    # integrate using Simpson's rule
    # _integrate_simpsons(t->sample_spline_speed(spline_coeffs_x, spline_coeffs_y, t), 0.0, 1.0, n_intervals)

    a = 0.0
    b = 1.0
    n = n_intervals

    h = (b-a)/n
    retval = sample_spline_speed(spline_coeffs_x, spline_coeffs_y, a) + sample_spline_speed(spline_coeffs_x, spline_coeffs_y, b)
    flip = true
    for i = 1 : n-1
        retval += sample_spline_speed(spline_coeffs_x, spline_coeffs_y, a+i*h) * (flip ? 4 : 2)
        flip = !flip
    end
    return h/3*retval
end
function calc_curve_length(
    spline_coeffs_x::AbstractMatrix{Float64},
    spline_coeffs_y::AbstractMatrix{Float64};
    n_intervals_per_segment::Int = 100
    )

    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_y, 2) == n)
    @assert(size(spline_coeffs_x, 1) == size(spline_coeffs_y, 1) == 4)

    len = 0.0
    for i = 1 : n
        len += calc_curve_length(spline_coeffs_x[:,i], spline_coeffs_y[:,i], n_intervals = n_intervals_per_segment)
    end
    len
end

function arclength(
    spline_coeffs_x::AbstractVector{Float64},
    spline_coeffs_y::AbstractVector{Float64},
    t_min::Real = 0.0,
    t_max::Real = 1.0,
    n_intervals::Int = 100
    )

    if isapprox(t_min, t_max)
        return 0.0
    end

    # _integrate_simpsons(t->sample_spline_speed(spline_coeffs_x, spline_coeffs_y, t), t_min, t_max, n_intervals)

    a = t_min
    b = t_max
    n = n_intervals

    h = (b-a)/n
    retval = sample_spline_speed(spline_coeffs_x, spline_coeffs_y, a) + sample_spline_speed(spline_coeffs_x, spline_coeffs_y, b)
    flip = true
    for i = 1 : n-1
        retval += sample_spline_speed(spline_coeffs_x, spline_coeffs_y, a+i*h) * (flip ? 4 : 2)
        flip = !flip
    end
    return h/3*retval
end
function arclength(
    spline_coeffs_x::AbstractMatrix{Float64},
    spline_coeffs_y::AbstractMatrix{Float64},
    t_min::Real = 0.0,
    t_max::Real = size(spline_coeffs_x, 2),
    n_intervals_per_segment::Int = 100
    )

    n = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_y, 2) == n)
    @assert(size(spline_coeffs_x, 1) == size(spline_coeffs_y, 1) == 4)

    if isapprox(t_min, t_max)
        return 0.0
    end

    # println("tmin/tmax: $t_min / $t_max")

    len = 0.0
    for i = floor(Int, t_min) : min(floor(Int, t_max), n-1)
        t_lo, t_hi = float(i), i+1.0

        spline_ind = i+1
        t_in_min = max(t_lo, t_min) - t_lo
        t_in_max = min(t_hi, t_max) - t_lo
        # println("($i) t_lo: $t_lo, t_hi: $t_hi, : $t_in_min → $t_in_max")
        len += arclength(spline_coeffs_x[:,spline_ind], spline_coeffs_y[:,spline_ind], t_in_min, t_in_max, n_intervals_per_segment)
    end
    # println("len: ", len)
    len
end

function calc_curve_param_given_arclen(
    spline_coeffs_x :: AbstractVector{Float64},
    spline_coeffs_y :: AbstractVector{Float64},
    s :: Float64;
    max_iterations :: Int=100,
    curve_length :: Float64 = calc_curve_length(spline_coeffs_x, spline_coeffs_y),
    epsilon::Float64 = 1e-4 # tolerance required before termination
    )

    # finds t such that p(t) is a distance s from start of curve
    # returns t=0 if s ≤ 0.0 and t=1 if s > L
    if s ≤ 0.0
        return 0.0
    elseif s ≥ curve_length
        return 1.0
    end

    t = s/curve_length
    lo, hi = 0.0, 1.0

    # @printf("%10s %10s %10s %10s %10s %10s\n", "iter", "lo", "hi", "t", "s", "F")
    # println("-"^65)

    for iter = 1 : max_iterations
        F = arclength(spline_coeffs_x, spline_coeffs_y, 0.0, t) - s

        # @printf("%10d %10.5f %10.5f %10.5f %10.5f %10.5f\n", iter-1, lo, hi, t, s, F)

        if abs(F) < epsilon
            # |F(t)| is close enough to zero, report it
            return t
        end

        DF = sample_spline_speed(spline_coeffs_x, spline_coeffs_y, t)
        tCandidate = t - F/DF
        if F > 0
            hi = t
            t = tCandidate ≤ lo ? 0.5*(lo+hi) : tCandidate
        else
            lo = t
            t = tCandidate ≥ hi ? 0.5*(lo+hi) : tCandidate
        end
    end

    # @printf("%10d %10.5f %10.5f %10.5f %10.5f %10s\n", max_iterations, lo, hi, t, s, "-")

    t
end
function calc_curve_param_given_arclen(
    spline_coeffs_x :: AbstractMatrix{Float64},
    spline_coeffs_y :: AbstractMatrix{Float64},
    s :: Float64;
    max_iterations :: Int=100,
    n_integration_intervals :: Int=100, # must be multiple of 2
    curve_length :: Float64 = calc_curve_length(spline_coeffs_x, spline_coeffs_y),
    epsilon::Float64 = 1e-4 # tolerance required before termination
    )

    # finds t such that p(t) is a distance s from start of curve
    # returns t=0 if s ≤ 0.0 and t=t_max if s > L

    n_segments = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x,1) == size(spline_coeffs_y,1) == 4)
    @assert(size(spline_coeffs_y,2) == n_segments)

    if s ≤ 0.0
        return 0.0
    elseif s ≥ curve_length
        return float(n_segments)
    end

    t = s/curve_length
    lo, hi = 0.0, float(n_segments)

    # @printf("%10s %10s %10s %10s %10s %10s\n", "iter", "lo", "hi", "t", "s", "F")
    # println("-"^65)

    for iter = 1 : max_iterations
        F = arclength(spline_coeffs_x, spline_coeffs_y, 0.0, t, n_integration_intervals) - s

        # @printf("%10d %10.5f %10.5f %10.5f %10.5f %10.5f\n", iter-1, lo, hi, t, s, F)

        if abs(F) < epsilon
            return t
        end

        DF = sample_spline_speed(spline_coeffs_x, spline_coeffs_y, t)
        tCandidate = t - F/DF
        if F > 0
            hi = t
            t = tCandidate ≤ lo ? 0.5*(lo+hi) : tCandidate
        else
            lo = t
            t = tCandidate ≥ hi ? 0.5*(lo+hi) : tCandidate
        end
    end

    # @printf("%10d %10.5f %10.5f %10.5f %10.5f %10s\n", max_iterations, lo, hi, t, s, "-")

    t
end
function calc_curve_param_given_arclen(
    spline_coeffs_x :: AbstractVector{Float64},
    spline_coeffs_y :: AbstractVector{Float64},
    s_arr :: AbstractVector{Float64}; # assumes s_arr is sorted
    max_iterations :: Int=100,
    curve_length :: Float64 = calc_curve_length(spline_coeffs_x, spline_coeffs_y),
    epsilon::Float64 = 1e-4 # tolerance required before termination
    )

    n = length(s_arr)
    t_arr = Array{Float64}(n)

    s = s_arr[1]
    t = s/curve_length
    if s ≤ 0.0
        t = 0.0
    elseif s ≥ curve_length
        t = 1.0
    end

    lo =  0.0

    for (i,s) in enumerate(s_arr)

        if s ≤ 0.0
            t = 0.0
            t_arr[i], lo = t, t
            continue
        elseif s ≥ curve_length
            t = 1.0
            t_arr[i], lo = t, t
            continue
        end

        hi = 1.0
        for iter = 1 : max_iterations
            F = arclength(spline_coeffs_x, spline_coeffs_y, 0.0, t) - s

            if abs(F) < epsilon
                t_arr[i], lo = t, t
                continue
            end

            DF = sample_spline_speed(spline_coeffs_x, spline_coeffs_y, t)
            tCandidate = t - F/DF
            if F > 0
                hi = t
                t = tCandidate ≤ lo ? 0.5*(lo+hi) : tCandidate
            else
                lo = t
                t = tCandidate ≥ hi ? 0.5*(lo+hi) : tCandidate
            end
        end

        t_arr[i], lo = t, t
    end

    t_arr
end
function calc_curve_param_given_arclen(
    spline_coeffs_x :: AbstractMatrix{Float64},
    spline_coeffs_y :: AbstractMatrix{Float64},
    s_arr :: AbstractVector{Float64}; # assumes s_arr is sorted
    max_iterations :: Int = 50,
    curve_length :: Float64 = calc_curve_length(spline_coeffs_x, spline_coeffs_y),
    epsilon::Float64 = 1e-4, # tolerance required before termination
    n_intervals_in_arclen::Int = 100
    )

    n_segments = size(spline_coeffs_x, 2)
    @assert(size(spline_coeffs_x,1) == size(spline_coeffs_y,1) == 4)
    @assert(size(spline_coeffs_y,2) == n_segments)

    n = length(s_arr)
    t_arr = Array{Float64}(n)

    s = s_arr[1]
    t = s/curve_length
    if s ≤ 0.0
        t = 0.0
    elseif s ≥ curve_length
        return float(n_segments)
    end

    lo =  0.0
    # println("L: ", curve_length)
    # println("s_max: ", s_arr[end])
    for (i,s) in enumerate(s_arr)

        # println("\ns: ", s)

        if s ≤ 0.0
            t = 0.0
            t_arr[i] = lo = t
            continue
        elseif s ≥ curve_length
            t = float(n_segments)
            t_arr[i] = lo = t
            continue
        end

        hi = float(n_segments)
        for iter = 1 : max_iterations
            F = arclength(spline_coeffs_x, spline_coeffs_y, 0.0, t, n_intervals_in_arclen) - s

            if abs(F) < epsilon
                break
            end

            DF = sample_spline_speed(spline_coeffs_x, spline_coeffs_y, t)
            tCandidate = t - F/DF
            if F > 0
                hi = t
                t = tCandidate ≤ lo ? 0.5*(lo+hi) : tCandidate
            else
                lo = t
                t = tCandidate ≥ hi ? 0.5*(lo+hi) : tCandidate
            end
        end

        t_arr[i] = lo = t
    end

    t_arr
end


## Exercise 4-8
## Enter the code in this chapter in a notebook.

## 1. Draw a stack diagram that shows the state of the program while executing circle(🐢, radius).
## You can do the arithmetic by hand or add print statements to the code.
println("Ans 1: ")

println(" turtle --> Turtle")
println("     turtle --> Turtle")
println("     radius --> 100")
println("     circumference --> 628.318...")
println("     n --> 212")
println("     len --> 2")
println("         turtle --> Turtle")
println("         nsides --> 212")
println("         len --> 2")
println("         i --> 1:212")
println(" return")

## 2. The version of arc in Refactoring is not very accurate because the linear approximation of the circle
## is always outside the true circle. As a result, the turtle ends up a few pixels away from the correct destination.
## My solution shows a way to reduce the effect of this error. Read the code and see if it makes sense to you.
## If you draw a diagram, you might see how it works.
using ThinkJulia

function polyline(t, n, len, angle)
    for i in 1:n
        forward(t, len)
        turn(t, -angle)
    end
end

"""
arc(t, r, angle)

Draws an arc with the given radius and angle:

    t: turtle
    r: radius
    angle: angle subtended by the arc, in degrees
"""
function arc(t, r, angle)
    arc_len = 2 * π * r * abs(angle) / 360
    n = trunc(arc_len / 4) + 3
    step_len = arc_len / n
    step_angle = angle / n

    # making a slight left turn before starting reduces
    # the error caused by the linear approximation of the arc
    turn(t, -step_angle/2)
    polyline(t, n, step_len, step_angle)
    turn(t, step_angle/2)
end

println("Ans 2: ")

@svg begin
    turtle = Turtle()
    # forward(turtle, 30)  # for checking the position of the turtle
    arc(turtle, 100, 125)
end

##

println("End.")


using ModelingToolkit
using DifferentialEquations

@parameters t α
@variables x(t) y(t)
D = Differential(t)

eqs = [D(x) ~ y * x,
       D(y) ~ -α * x]

@named sys = ODESystem(eqs)

u0 = [ x => 1.0, y => 2.0]
tspan = (0.0, 1.0)
p = [α => 1.0]
prob = ODEProblem(sys, u0, tspan, p)
sol = solve(prob)

#=
using SymbolicUtils
r1 = @rule (~y)*(~x) => (~y)^2
r1(eqs[1].rhs)
=#


#
# Computes the density function of a list of values
#

function density(v;nbins=nothing,step=nothing,steptype="absolute",vmin=nothing,vmax=nothing)

  ndata = length(v)

  if vmin == nothing
    vmin = minimum(v)
  end
  if vmax == nothing
    vmax = maximum(v)
  end
  if nbins == nothing
    nbins = 100
  end
  if step == nothing
    step = (vmax - vmin)/nbins
  else
    # By default, the step size is absolute
    if steptype == "relative"
      step = step*(vmax-vmin)/nbins
    elseif steptype != "absolute"
      error(" steptype must be \"relative\" or \"absolute\"")
    end
  end

  x = Vector{Float64}(undef,nbins)
  df = Vector{Float64}(undef,nbins)

  binstep = (vmax - vmin)/nbins
  for i in 1:nbins
    x[i] = vmin + (i-1)*binstep + binstep/2
    nv = 0
    for j in 1:ndata
      if ( v[j] > x[i] - step/2 ) && ( v[j] <= x[i] + step/2 )
        nv = nv + 1
      end
    end
    binsize = min(vmax,x[i]+step/2) - max(vmin,x[i]-step/2)
    df[i] = nv/(binsize*ndata)
  end

  return x, df

end