"""
Hartree term: for a decaying potential V the energy would be

1/2 ∫ρ(x)ρ(y)V(x-y) dxdy

with the integral on x in the unit cell and of y in the whole space.
For the Coulomb potential with periodic boundary conditions, this is rather

1/2 ∫ρ(x)ρ(y) G(x-y) dx dy

where G is the Green's function of the periodic Laplacian with zero
mean (-Δ G = sum_{R} 4π δ_R, integral of G zero on a unit cell).
"""
struct Hartree
    scaling_factor::Real  # to scale by an arbitrary factor (useful for exploration)
end
Hartree(; scaling_factor=1) = Hartree(scaling_factor)
(hartree::Hartree)(basis) = TermHartree(basis, hartree.scaling_factor)

struct TermHartree <: Term
    basis::PlaneWaveBasis
    scaling_factor::Real  # scaling factor, absorbed into poisson_green_coeffs
    # Fourier coefficients of the Green's function of the periodic Poisson equation
    poisson_green_coeffs
end
function TermHartree(basis::PlaneWaveBasis{T}, scaling_factor) where T
    # Solving the Poisson equation ΔV = -4π ρ in Fourier space
    # is multiplying elementwise by 4π / |G|^2.
    poisson_green_coeffs = 4T(π) ./ [sum(abs2, basis.model.recip_lattice * G)
                                     for G in G_vectors(basis)]

    # Zero the DC component (i.e. assume a compensating charge background)
    poisson_green_coeffs[1] = 0
    TermHartree(basis, scaling_factor, scaling_factor .* poisson_green_coeffs)
end

function ene_ops(term::TermHartree, ψ, occ; ρ, kwargs...)
    basis = term.basis
    T = eltype(basis)
    pot_fourier = term.poisson_green_coeffs .* ρ.fourier
    potential = real(G_to_r(basis, pot_fourier))  # TODO optimize this
    E = real(dot(pot_fourier, ρ.fourier) / 2)

    ops = [RealSpaceMultiplication(basis, kpoint, potential) for kpoint in basis.kpoints]
    (E=E, ops=ops)
end

function compute_kernel(term::TermHartree; kwargs...)
    vc_G = term.poisson_green_coeffs
    # Note that `real` here: if omitted, will result in high-frequency noise of even FFT grids
    real(G_to_r_matrix(term.basis) * Diagonal(vec(vc_G)) * r_to_G_matrix(term.basis))
end
function apply_kernel(term::TermHartree, dρ::RealFourierArray; kwargs...)
    from_fourier(dρ.basis, term.poisson_green_coeffs .* dρ.fourier)
end


function sobolindices(
    models::Array{<:UQModel},
    inputs::Array{<:UQInput},
    output::Symbol,
    sim::AbstractMonteCarlo,
)
    sim_double_samples = @set sim.n = 2 * sim.n

    samples = sample(inputs, sim_double_samples)
    samples = samples[shuffle(1:size(samples, 1)), :]

    random_names = names(filter(i -> isa(i, RandomUQInput), inputs))

    evaluate!(models, samples)

    A = samples[1:(sim.n), :]
    B = samples[(sim.n + 1):end, :]

    fA = A[:, output]
    fB = B[:, output]

    fA .-= mean(fA)
    fB .-= mean(fB)

    VY = var([fA; fB])

    Si = zeros(length(random_names), 2)
    STi = zeros(length(random_names), 2)

    for (i, name) in enumerate(random_names)
        ABi = select(A, Not(name))
        ABi[:, name] = B[:, name]

        for m in models
            evaluate!(m, ABi)
        end

        ABi[:, output] .-= mean(ABi[:, output])

        first_order = x -> mean(fB .* (x .- fA)) / VY # Saltelli 2009
        total_effect = x -> (1 / (2 * sim.n)) * sum((fA .- x) .^ 2) / VY # Saltelli 2009

        # First order effects
        Si[i, 1] = first_order(ABi[:, output])
        bs = bootstrap(first_order, ABi[:, output], BasicSampling(1000))
        Si[i, 2] = stderror(bs)[1]

        # Total effects
        STi[i, 1] = total_effect(ABi[:, output])
        bs = bootstrap(total_effect, ABi[:, output], BasicSampling(1000))
        STi[i, 2] = stderror(bs)[1]
    end

    indices = DataFrame()
    indices.Variables = random_names
    indices.FirstOrder = Si[:, 1]
    indices.FirstOrderStdError = Si[:, 2]
    indices.TotalEffect = STi[:, 1]
    indices.TotalEffectStdError = STi[:, 2]

    return indices
end


include("7.5.1 u (a+b arcsech(c x))^n.jl")
include("7.5.2 Miscellaneous inverse hyperbolic secant.jl")

src/rules/7 Inverse hyperbolic functions/7.5 Inverse hyperbolic secant/.jl


mutable struct HTensorNode{T} <: AbstractTensor{T}
   data      :: Array{T,2}
   subshape  :: Vector{Int}
   nbasis    :: Int
   children  :: Vector{HTensorNode{T}}
   partition :: Vector{Int}
end

reshape2D(a::Array{T,1}) where {T} = reshape(a, 1, length(a))
reshape2D(a::Array{T,N}) where {T,N} = reshape(a, prod(size(a,i) for i=1:N-1), size(a,N))
coreshape(t::HTensorNode) = reshape(t.data, t.subshape..., t.nbasis)

function HTensorNode(data::Array{T,2}, children = HTensorNode{T}[]) where {T}
   nbasis = size(data,2)
   if isempty(children)
      subshape = [size(data,1)]
   else
      subshape = [child.nbasis for child in children]
      @assert size(data,1) == prod(subshape)
   end
   partition = Int[ nmode(c) for c in children ]
   HTensorNode(data, subshape, nbasis, children, partition)
end

function HTensorNode(data::Array{T,N}, children = HTensorNode{T}[]) where {T,N}
   nbasis = size(data,N)
   subshape = [size(data,i) for i=1:N-1]
   data2D = reshape2D(data)
   if !isempty(children)
      @assert length(children) == N-1
      for i=1:N-1
         @assert subshape[i] == children[i].nbasis
      end
   end
   partition = Int[ nmode(c) for c in children ]
   HTensorNode(data2D, subshape, nbasis, children, partition)
end

isleaf(t::HTensorNode)  = isempty(t.children)
nmode(t::HTensorNode)   = isleaf(t) ? length(t.subshape) : sum(nmode(c) for c in t.children)
fulldim(t::HTensorNode) = isleaf(t) ? prod(Float64, t.subshape) : prod(fulldim(c) for c in t.children)

function ncoeffs(ht::HTensorNode)
   n = length(ht.data)
   for child in ht.children
      n += ncoeffs(child)
   end
   return n
end

function shape(t::HTensorNode)
   if isleaf(t)
      return t.subshape
   else
      shp = Array{Int}(undef, sum(t.partition))
      f = 0
      for i = 1:length(t.children)
         nm = t.partition[i]
         ch = t.children[i]
         shp[f+1:f+nm] = shape(ch)
         f += nm
      end
      return shp
   end
end

function array(t::HTensorNode)
   core = coreshape(t)
   if isleaf(t)
      return core
   else
      nchildren = length(t.children)
      newshape  = Int[]
      for i in 1:nchildren
         child = t.children[i]
         charr = array(child)
         core  = tensordot(core, reshape2D(charr), i)
         append!(newshape, size(charr)[1:end-1])
      end
      reshape(core, newshape..., t.nbasis)
   end
end

function getelts(t::HTensorNode, idxs)
   core = coreshape(t)
   if isleaf(t)
      return core[idxs..., :]
   else
      f = 0
      nchildren = length(t.children)
      newshape  = Int[]
      for i in 1:nchildren
         ch_nmode = t.partition[i]
         ch_idxs  = idxs[f+1 : f+ch_nmode]
         ch_elts  = getelts(t.children[i], ch_idxs)
         core = tensordot(core, reshape2D(ch_elts), i)
         append!(newshape, size(ch_elts)[1:end-1])
         f += ch_nmode
      end
      reshape(core, newshape..., t.nbasis)
   end
end


using DataFrames
using GLM
#using Plots
using RegressionDiscontinuity
using Test

Zs = randn(1000)

ZsR = RunningVariable(Zs, cutoff = 0.0)
Zsgeg0 = RunningVariable(Zs, cutoff = 1.0)
Zsless0 = RunningVariable(Zs, cutoff = 1.0, treated = :<)
@test all(xor.(Zsgeg0.Ws + Zsless0.Ws) .== 1)

tmp_df = DataFrame(Ws = ZsR.Ws, Zs = ZsR.Zs, ZsC = ZsR.ZsC)
@test Tables.schema(tmp_df) == Tables.schema(ZsR)

Ys = rand(1000)
rdd_data = RegressionDiscontinuity.RDData(Ys, ZsR)
myfit = fit(LinearModel, @formula(Ys ~ Zs + Ws), rdd_data)


"SPH smoothing kernel."
abstract type Kernel{N} end

"Mathematical definition of a smoothing kernel."
abstract type KernelFormula{N} <: Kernel{N} end

"""
    (::Kernel)(q::Number, h::Number) -> Float

Evaluate the kernel with width `h` at relative distance `q = r/h`.
"""
function (W::Kernel{N})(q::Number, h::Number) where {N}
    @dbgassert(h > 0) && W(q) / h^N
end

"""
    ddr(W::Kernel, q::Number, h::Number) -> Float

Evaluate the derivative of the kernel with width `h` at relative distance
`q = r/h`. The derivative is with respect to `r`.
"""
function ddr(W::Kernel{N}, q::Number, h::Number) where {N}
    @dbgassert(h > 0) && ddr(W, q) / h^(N + 1)
end

"""
    ddh(W::Kernel, q::Number, h::Number) -> Float

Evaluate the derivative of the kernel with width `h` at relative distance
`q = r/h`. The derivative is with respect to `h`.
"""
function ddh(W::Kernel{N}, q::Number, h::Number) where {N}
    @dbgassert(h > 0) && ddh(W, q) / h^(N + 1)
end

"""
    get_support(W::Kernel) -> Float

Returns the relative distance `q = r/h` at which the kernel is truncated.
"""
function get_support end

"""
    nonzeroat(W::Kernel, q::Number) -> Bool

Whether the kernel is non-zero at relative distance `q = r/h`.
"""
nonzeroat(W::Kernel, q::Number) = q < get_support(W)

"""
    ddr(W::Kernel, (qᵢ, qⱼ)::Tuple{Number,Number}, (hᵢ, hⱼ)::Tuple{Number,Number})
        -> (Float, Float)

Evaluate the derivative of the kernel at the relative distances `qᵢ = rᵢ/hᵢ`
and `qⱼ = rⱼ/hⱼ`. The derivative is with respect to `r`.
"""
ddr(W::Kernel, (qᵢ, qⱼ)::Tuple{Number,Number}, (hᵢ, hⱼ)::Tuple{Number,Number}) =
    ddr(W, qᵢ, hᵢ), ddr(W, qⱼ, hⱼ)

"""
    nonzeroat(W::Kernel, q::Tuple{Number,Number}) -> Bool

Whether the kernel is non-zero at both `qᵢ` and `qⱼ`.
"""
nonzeroat(W::Kernel, (qᵢ, qⱼ)::Tuple{Number,Number}) =
    nonzeroat(W, qᵢ) && nonzeroat(W, qⱼ)

include("kernel/gaussian.jl")
include("kernel/cubic_spline.jl")
include("kernel/tabulated.jl")
include("kernel/constant_width.jl")


using MixedVolume
@static if VERSION < v"0.7.0-DEV.2005"
    using Base.Test
else
    using Test
end

@testset "CayleyConfiguration" begin
    A1 = [0 1 0; 0 0 1]
    A2 = [1 0; 0 1]

    CC = CayleyConfiguration(A1, A2)
    @test CC.A == [0 1 0 1 0; 0 0 1 0 1; 1 1 1 0 0; 0 0 0 1 1]
    @test CC.offsets == [0, 3]
    @test size(CC) == (4, 5)
end

@testset "CircuitTable" begin
    A1 = [0 0 1 1; 0 2 0 1]
    A2 = [0 0 1 2; 0 1 1 0]
    A = CayleyConfiguration(A1, A2)

    M = [(2, 3), (1, 3)]

    T = CircuitTable(A, M)
    @test T isa CircuitTable{Int}

    w = [0, 0, 0, -1, 0, -3, -4, -8]

    @test dot(T, 1, w) == 8
    @test dot(T, 2, w) == 0
    @test dot(T, 3, w) == 0
    @test dot(T, 4, w) == -1
    @test dot(T, 4, [0, 0, 0, -2, 0, -3, -4, -8]) == 2
    @test dot(T, 5, w) == 0
    @test dot(T, 7, w) == 0
end

@testset "MixedCell" begin
    A1 = [0 0 1 1; 0 2 0 1]
    A2 = [0 0 1 2; 0 1 1 0]
    A = CayleyConfiguration(A1, A2)

    M = MixedCell(A, [(2, 3), (1, 3)])
    @test M isa MixedCell{Int}
end


# MIT License: Copyright (c) 2016: Andy Ferris.
# See LICENSE.md for further licensing test

@inline function LinearAlgebra.eigen(S::SymmetricTensor{2, 1, T}) where {T}
    @inbounds Eigen(Vec{1, T}((S[1, 1],)), one(Tensor{2, 1, T}))
end

function LinearAlgebra.eigen(R::SymmetricTensor{2, 2, T′}) where T′
    S = ustrip(R)
    T = eltype(S)
    @inbounds begin
        if S[2,1] == 0 # diagonal tensor
            S11 = S[1,1]
            S22 = S[2,2]
            if S11 < S22
                λ = Vec{2}((S11, S22))
                Φ = Tensor{2,2,T}((T(1), T(0), T(0), T(1)))
            else # S22 <= S11
                λ = Vec{2}((S22, S11))
                Φ = Tensor{2,2,T}((T(0), T(1), T(1), T(0)))
            end
        else
            # eigenvalues from quadratic formula
            trS_half = tr(S) / 2
            tmp2 = trS_half * trS_half - det(S)
            tmp2 < 0 ? tmp = zero(tmp2) : tmp = sqrt(tmp2) # Numerically stable for identity matrices, etc.
            λ = Vec{2}((trS_half - tmp, trS_half + tmp))

            Φ11 = λ[1] - S[2,2]
            n1 = sqrt(Φ11 * Φ11 + S[2,1] * S[2,1])
            Φ11 = Φ11 / n1
            Φ12 = S[2,1] / n1

            Φ21 = λ[2] - S[2,2]
            n2 = sqrt(Φ21 * Φ21 + S[2,1] * S[2,1])
            Φ21 = Φ21 / n2
            Φ22 = S[2,1] / n2

            Φ = Tensor{2, 2}((Φ11, Φ12,
                              Φ21, Φ22))
        end
        return Eigen(convert(Vec{2,T′}, λ), Φ)
    end
end

# Port of https://www.geometrictools.com/GTEngine/Include/Mathematics/GteSymmetricEigensolver3x3.h
# released by David Eberly, Geometric Tools, Redmond WA 98052
# under the Boost Software License, Version 1.0 (included at the end of this file)
# The original documentation states
# (see https://www.geometrictools.com/Documentation/RobustEigenSymmetric3x3.pdf )
# [This] is an implementation of Algorithm 8.2.3 (Symmetric QR Algorithm) described in
# Matrix Computations,2nd edition, by G. H. Golub and C. F. Van Loan, The Johns Hopkins
# University Press, Baltimore MD, Fourth Printing 1993. Algorithm 8.2.1 (Householder
# Tridiagonalization) is used to reduce matrix A to tridiagonal D′. Algorithm 8.2.2
# (Implicit Symmetric QR Step with Wilkinson Shift) is used for the iterative reduction
# from tridiagonal to diagonal. Numerically, we have errors E=RTAR−D. Algorithm 8.2.3
# mentions that one expects |E| is approximately μ|A|, where |M| denotes the Frobenius norm
# of M and where μ is the unit roundoff for the floating-point arithmetic: 2−23 for float,
# which is FLTEPSILON = 1.192092896e-7f, and 2−52 for double, which is
# DBLEPSILON = 2.2204460492503131e-16.
# TODO ensure right-handedness of the eigenvalue matrix
function LinearAlgebra.eigen(R::SymmetricTensor{2,3,T′}) where T′
    A = ustrip(R)
    T = eltype(A)
    function converged(aggressive, bdiag0, bdiag1, bsuper)
        if aggressive
            bsuper == 0
        else
            diag_sum = abs(bdiag0) + abs(bdiag1)
            diag_sum + bsuper == diag_sum
        end
    end

    function get_cos_sin(u::T,v::T) where {T}
        max_abs = max(abs(u), abs(v))
        if max_abs > 0
            u,v = (u,v) ./ max_abs
            len = sqrt(u^2 + v^2)
            cs, sn = (u,v) ./ len
            if cs > 0
                cs = -cs
                sn = -sn
            end
            T(cs), T(sn)
        else
            T(-1), T(0)
        end
    end

    function _sortperm3(v)
        local perm = (1,2,3)
        # unrolled bubble-sort
        (v[perm[1]] > v[perm[2]]) && (perm = (perm[2], perm[1], perm[3]))
        (v[perm[2]] > v[perm[3]]) && (perm = (perm[1], perm[3], perm[2]))
        (v[perm[1]] > v[perm[2]]) && (perm = (perm[2], perm[1], perm[3]))
        perm
    end

    # Givens reflections
    update0(Q, c::T, s::T) where T = Q ⋅ Tensor{2,3}((c, s, T(0), T(0), T(0), T(1), -s, c, T(0)))
    update1(Q, c::T, s::T) where T = Q ⋅ Tensor{2,3}((T(0), c, -s, T(1), T(0), T(0), T(0), s, c))
    # Householder reflections
    update2(Q, c::T, s::T) where T = Q ⋅ Tensor{2,3}((c, s, T(0), s, -c, T(0), T(0), T(0), T(1)))
    update3(Q, c::T, s::T) where T = Q ⋅ Tensor{2,3}((T(1), T(0), T(0), T(0), c, s, T(0), s, -c))

    is_rotation = false

    # If `aggressive` is `true`, the iterations occur until a superdiagonal
    # entry is exactly zero, otherwise they occur until it is effectively zero
    # compared to the magnitude of its diagonal neighbors. Generally the non-
    # aggressive convergence is acceptable.
    #
    # Even with `aggressive = true` this method is faster than the one it
    # replaces and in order to keep the old interface, aggressive is set to true
    aggressive = true

    # the input is symmetric, so we only consider the unique elements:
    a00, a01, a02, a11, a12, a22 = A[1,1], A[1,2], A[1,3], A[2,2], A[2,3], A[3,3]

    # Compute the Householder reflection H and B = H * A * H where b02 = 0

    c, s = get_cos_sin(a12, -a02)

    Q = Tensor{2,3}((c, s, T(0), s, -c, T(0), T(0), T(0), T(1)))

    term0 = c * a00 + s * a01
    term1 = c * a01 + s * a11
    b00 = c * term0 + s * term1
    b01 = s * term0 - c * term1
    term0 = s * a00 - c * a01
    term1 = s * a01 - c * a11
    b11 = s * term0 - c * term1
    b12 = s * a02 - c * a12
    b22 = a22

    # Givens reflections, B' = G^T * B * G, preserve tridiagonal matrices
    max_iteration = 2 * (1 + precision(T) - exponent(floatmin(T)))

    if abs(b12) <= abs(b01)
        saveB00, saveB01, saveB11 = b00, b01, b11
        for _ in 1:max_iteration
            # compute the Givens reflection
            c2, s2 = get_cos_sin((b00 - b11) / 2, b01)
            s = sqrt((1 - c2) / 2)
            c = s2 / 2s

            # update Q by the Givens reflection
            Q = update0(Q, c, s)
            is_rotation = !is_rotation

            # update B ← Q^T * B * Q, ensuring that b02 is zero and |b12| has
            # strictly decreased
            saveB00, saveB01, saveB11 = b00, b01, b11
            term0 = c * saveB00 + s * saveB01
            term1 = c * saveB01 + s * saveB11
            b00 = c * term0 + s * term1
            b11 = b22
            term0 = c * saveB01 - s * saveB00
            term1 = c * saveB11 - s * saveB01
            b22 = c * term1 - s * term0
            b01 = s * b12
            b12 = c * b12

            if converged(aggressive, b00, b11, b01)
                # compute the Householder reflection
                c2, s2 = get_cos_sin((b00 - b11) / 2, b01)
                s = sqrt((1 - c2) / 2)
                c = s2 / 2s

                # update Q by the Householder reflection
                Q = update2(Q, c, s)
                is_rotation = !is_rotation

                # update D = Q^T * B * Q
                saveB00, saveB01, saveB11 = b00, b01, b11
                term0 = c * saveB00 + s * saveB01
                term1 = c * saveB01 + s * saveB11
                b00 = c * term0 + s * term1
                term0 = s * saveB00 - c * saveB01
                term1 = s * saveB01 - c * saveB11
                b11 = s * term0 - c * term1
                break
            end
        end
    else
        saveB11, saveB12, saveB22 = b11, b12, b22
        for _ in 1:max_iteration
            # compute the Givens reflection
            c2, s2 = get_cos_sin((b22 - b11) / 2, b12)
            s = sqrt((1 - c2) / 2)
            c = s2 / 2s

            # update Q by the Givens reflection
            Q = update1(Q, c, s)
            is_rotation = !is_rotation

            # update B ← Q^T * B * Q ensuring that b02 is zero and |b12| has
            # strictly decreased.
            saveB11, saveB12, saveB22 = b11, b12, b22

            term0 = c * saveB22 + s * saveB12
            term1 = c * saveB12 + s * saveB11
            b22 = c * term0 + s * term1
            b11 = b00
            term0 = c * saveB12 - s * saveB22
            term1 = c * saveB11 - s * saveB12
            b00 = c * term1 - s * term0
            b12 = s * b01
            b01 = c * b01

            if converged(aggressive, b11, b22, b12)
                # compute the Householder reflection
                c2, s2 = get_cos_sin((b11 - b22) / 2, b12)
                s = sqrt((1 - c2) / 2)
                c = s2 / 2s

                # update Q by the Householder reflection
                Q = update3(Q, c, s)
                is_rotation = !is_rotation

                # update D = Q^T * B * Q
                saveB11, saveB12, saveB22 = b11, b12, b22
                term0 = c * saveB11 + s * saveB12
                term1 = c * saveB12 + s * saveB22
                b11 = c * term0 + s * term1
                term0 = s * saveB11 - c * saveB12
                term1 = s * saveB12 - c * saveB22
                b22 = s * term0 - c * term1
                break
            end
        end
    end
    evals = convert(Vec{3,T′}, Vec{3}((b00, b11, b22)))
    perm = _sortperm3(evals)
    evals_sorted = Vec{3}((evals[perm[1]], evals[perm[2]], evals[perm[3]]))
    Q_sorted = Tensor{2,3}((
        Q[1, perm[1]], Q[2, perm[1]], Q[3, perm[1]],
        Q[1, perm[2]], Q[2, perm[2]], Q[3, perm[2]],
        Q[1, perm[3]], Q[2, perm[3]], Q[3, perm[3]],
    ))
    return Eigen(evals_sorted, Q_sorted)
end


# test different implementations for checking if >=

using EmpiricalBayes
using Test
using Random: seed!
using Distributions
import QuadGK:quadgk

marginal_grid = collect(range(-6,stop=6,length=1001));
prior_grid = collect(range(-3,stop=3,length=121));
ds = MixingNormalConvolutionProblem(Normal, 0.2, prior_grid, marginal_grid);

true_dist = MixtureModel([ Normal(-2,.2), Normal(+2,.2)])
d_center = NormalConvolutionProblem(true_dist, marginal_grid);
f_center = BinnedMarginalDensity(d_center)

# Compare current implementation to integrating over correct domain
t = LFSRNumerator(1.0)
posterior_stats(d_center, t)

# nice!
quadgk(β ->  pdf(d_center.prior, β)*riesz_representer(t, β) , 0,20)[1]


################
## GARCH(1,1) ##
################

## X_t = mu_t + z_t*e_t
## (z_t)^2 = kappa + alpha*(X_t-1 - mu_t-1)^2 + beta*(z_t-1)^2

type GARCH_1_1{T} <: AbstrUnivarModel
    μ::Float64
    κ::Float64
    α::Float64
    β::Float64
    distr::T

    function GARCH_1_1(μ::Float64, κ::Float64,
                       α::Float64, β::Float64,
                       d::T)
        if !isa(d, Union(Normal, Econometrics.TDist))
            error("The innovation distribution must be either Normal
                       or TDist.")
        end
        if κ <= 0
            error("κ parameter must be larger than 0")
        end
        if α < 0
            error("α parameter must be larger than 0")
        end
        if β < 0
            error("β parameter must be larger than 0")
        end
        if α + β >= 1
            error("α and β together must be smaller than 1 for stationarity")
        end
        return new(μ, κ, α, β, d)
    end
end

function GARCH_1_1{T}(μ::Float64, κ::Float64,
                      α::Float64, β::Float64,
                      d::T)
    return GARCH_1_1{T}(μ, κ, α, β, d)
end

function GARCH_1_1{T}(params::Array{Float64, 1}, d::T)
    return GARCH_1_1{T}(params..., d)
end

type GARCH_1_1_Fit{T, T2} <: AbstrUnivarModel
    model::GARCH_1_1{T}
    data::T2
    sigmas::T2
    nllh::Float64
    # data, sigmas either Array{Float64, 1} or Timematr / Timenum
end

function GARCH_1_1_Fit{T, T2}(mod::GARCH_1_1{T},
                              data::T2, sigmas::T2,
                              nllh::Float64)
    return GARCH_1_1_Fit{T, T2}(mod, data, sigmas, nllh)
end


function hqrt(t, b)
#
#  transformation using the t from hessqr
#
   z = copy(b)
   k = min(size(t,2), size(z,1)-1)
   for j = 1:k
       T = [t[1,j] t[2,j]; -conj(t[2,j]) conj(t[1,j])]
       z[j:j+1,1:end] = T * z[j:j+1,1:end]
   end
   z
end

function hqrt1(t::AbstractArray{T,2}, n::Int=1) where T
#
#  transformation using the t from hessqr on unit vector e(n)
#
   k = size(t,2)
   z = zeros(T, k+1)
   z[n] = one(T)
   for j = n:k
	   zj = z[j]
	   z[j] *= t[1,j]
	   z[j+1] = t[2,j] * zj
   end
   z
end


using Test
using LinearAlgebra
using Ferrite
using Ferrite: cellid, getcoordinates, CellIterator

using TopOpt
using TopOpt.TopOptProblems: boundingbox, nnodespercell, getgeomorder, getmetadata, getdh, getE, getdim
using TopOpt.TrussTopOptProblems: getA, default_quad_order
# if get(ENV, "CI", nothing) != "true"
#     import Makie
#     using TopOpt.TrussTopOptProblems.TrussVisualization: visualize
# end

include("utils.jl")

problem_json = ["mgz_truss1.json", "mgz_truss2.json", "mgz_truss3.json"]
u_solutions = [
    1e-3 * vcat([2.41, 0.72], zeros(2*2)),
    1e-3 * vcat([0.1783, 2.7222, -0.4863], zeros(4*3)),
    1e-3 * vcat([0.871, 1.244], [0,0], [-0.193, 0]),
]
ins_dir = joinpath(@__DIR__, "instances", "fea_examples");

@testset "Truss problem solve - $(problem_json[i])" for i in 1:length(problem_json)
    # i = 3
    file_name = problem_json[i]
    problem_file = joinpath(ins_dir, file_name)

    node_points, elements, mats, crosssecs, fixities, load_cases = parse_truss_json(problem_file);
    ndim, nnodes, ncells = length(node_points[1]), length(node_points), length(elements)
    loads = load_cases["0"]

    problem = TrussProblem(Val{:Linear}, node_points, elements, loads, fixities, mats, crosssecs);

    @test getdim(problem) == ndim
    @test Ferrite.getncells(problem) == ncells
    @test getE(problem) == [m.E for m in mats]
    @test problem.black == problem.white == falses(ncells)
    @test problem.force == loads
    @test problem.varind == 1:ncells
    grid = problem.ch.dh.grid
    @test length(grid.cells) == ncells

    @test getgeomorder(problem) == 1
    @test nnodespercell(problem) == 2

    quad_order=default_quad_order(problem)
    elementinfo = ElementFEAInfo(problem, quad_order, Val{:Static});
    As = getA(problem)
    Es = getE(problem)
    for cell in CellIterator(getdh(problem))
        cellidx = cellid(cell)
        coords = getcoordinates(cell)
        L = norm(coords[1] - coords[2])
        A = As[cellidx]
        E = Es[cellidx]
        @test elementinfo.cellvolumes[cellidx] ≈ L * A

        Γ = global2local_transf_matrix(coords...)
        Ke_m = (A*E/L)*Γ'*[1 -1; -1 1]*Γ
        Ke = elementinfo.Kes[cellidx]
        @test Ke_m ≈ Ke
    end

    solver = FEASolver(Displacement, Direct, problem)
    solver()

    # if get(ENV, "CI", nothing) != "true"
    #     ## TODO plot analysis result with
    #     fig = visualize(problem, solver.u)
    #     Makie.display(fig)
    # end

    # we use kN for force and m for length
    # thus, pressure/modulus is in kN/m
    # the textbook uses a quite rough rounding scheme...
    # 0.3 mm error
    to_K_full = solver.globalinfo.K.data
    @assert norm(solver.u - u_solutions[i]) < 3e-4

end # end test set


immutable NoncentralBeta <: ContinuousUnivariateDistribution
    α::Float64
    β::Float64
    λ::Float64

    function NoncentralBeta(α::Real, β::Real, λ::Real)
    	@check_args(NoncentralBeta, α > zero(α) && β > zero(β))
        @check_args(NoncentralBeta, λ >= zero(λ))
    	new(α, β, λ)
    end
end

@distr_support NoncentralBeta 0.0 1.0

### Parameters

params(d::NoncentralBeta) = (d.α, d.β, d.λ)

### Evaluation & Sampling

# TODO: add mean and var

@_delegate_statsfuns NoncentralBeta nbeta α β λ

function rand(d::NoncentralBeta)
    a = rand(NoncentralChisq(2.0 * d.α, d.β))
    b = rand(Chisq(2.0 * d.β))
    a / (a + b)
end


Random.seed!(0)

# facts("qraddrow") do

    m, n = 3, 3
    A = randn(m,m)
    Q, R = qr(A)
    for i in 1:100
        a = randn(m)'
        global A = [A; a]
        global R = qraddrow(R, a)
        @assert norm( R'R - A'*A ) <  1e-5
    end

# end


module QuantumRelay

using SymPy
using JuMP
using GLPK
using IterTools
using LLLplus
using GSL
using PyPlot
using Distributions
using LinearAlgebra
using SmithNormalForm

export
	qrelay_op,
	op_mat,
	scan_maker,
        nonneg,
	QRelaySampler

include("utility.jl")
include("operateurs.jl")
include("scan.jl")
include("distributions.jl")

struct QRelaySampler
	prob::Function #return the probablility
	psetproposal::Function #return next combination of sample

    #input parameters:
    #mat: the matrix p_ij in the note (10)
    #coef: the coefficient c in the note (10)
    #omega: the coefficient A in the note (10)
    #pdet0: the probability of detection for each detector
	function QRelaySampler(mat::Array{T, 2}, coef, omega, pdet0) where T <: Int
		F=smith(mat)
                U=F.S
                V=F.T
                S=diagm(F)
        #the PolyLib returns P=USV. Inverse the matrices so Ui/Vi is the same as U/V in the note (18)
		Ui = inv(U)
		Vi = inv(V)
		s = diag(S)
		r = count(!iszero, s)
		s0 = s[1:r]
		@assert s0 == ones(r)
		ui1 = Ui[1:r, :]
		ui2 = Ui[r+1:end, :]
		vi1 = Vi[:, 1:r]
		vi2 = Vi[:, r+1:end]
		vi2 = lll(vi2)[1] #Lenstra–Lenstra–Lovász lattice basis reduction
		T0 = vi1*ui1
		ui2oc = orthocomp(ui2) #orhogonal complement

		setc, scan = scan_maker(vi2) #make the scanner for the algorithm1 in the note

        #compute the probability for an ideal system
        #na: the photon numbers in a output mode
		function prob(na)
		    @assert count(!iszero, ui2*na) == 0
		    b = T0*na
		    setc(-b)
		    total = 0.0
		    for x in Channel(scan)
		        nab = vi2*x + b #the photon numbers for each item in the sum in the note (10)
		        total += prod([c.^complex(n)/factorial(n) for (c, n) in zip(coef, nab)])
		    end
		    return abs2.(total*omega)
		end

        #compute the probability of detection
        #q: the number of photons detectors report
        #na: the number of photons arrived at detector
        #mask: if there is no detector in this channel, mask=0
		function prob(q, na, mask)
		    q0 = round.(Int, q.>0)
		    m0 = round.(Int, mask)
		    return prod((q0 + (ones(length(q0))-2q0).*pdet0(na)).^m0)
		end

		psetproposal(x::Vector) = QuantumRelay.OrthoNNDist(x, ui2oc)

		new(prob, psetproposal)
	end
end

end


using SparseArrays
using LinearAlgebra
using Plots

function invvekit(A, y, m, e)
    l=[0];
    # Berechnung gemäß Algorithmus (Arbeitsblatt)
    Q,R,p = lu(A-m*Diagonal(ones(size(A))))
    while true
        y_h=R\(Q' * y) #TODO: geht nicht immer, warum
        l_h = 1/(dot(y_h,y)) + m
        # Närung Eigenwert zu Liste Eigenwert hinzufügen
        l=[l;l_h]
        # neuen Wert Eigenvektor setzen
        y=y_h/norm(y_h)
        # Abbruchkriterium prüfen
        if (norm(A*y-l_h*y)<e)
            # Liste Eigenwert und Eigenvektor zurückgeben
            return l,y;
        end
    end
end


module TestDet

import ConicHulls.Dets

for n=1:5
    for k=1:100
        A = rand(-8:8, (n,n))
        d0 = iround(det(A))
        d1 = Dets.det([A[:,k] for k=1:n]...)
        d2 = dot(Dets.incomplete_det([A[:,k] for k=1:(n-1)]...), A[:,end])

        @assert d1 == d0
        @assert d2 == d0
    end
end

end # module


using Gnuplot, Colors, ColorSchemes
# function definitions
χ0(xs, Ωs) = (Ωs/π)^(1/4) .* exp.(- Ωs*xs.^2.0./2)
χ1(xs, Ωs) = (4Ωs^3/π)^(1/4) .* xs .* exp.(- Ωs*xs.^2.0./2)
function χn(x; m = 2, t0 = 3, ω₀ = 0.57, Eα0 = -0.9)
    nr = m - 1
    T0 = 41.34*t0
    Ωs = T0^2/(4*log(2))
    x₀ = Eα0 .+ 2ω₀
    xs = (x .- x₀)
    a, b = χ1(xs, Ωs), χ0(xs, Ωs)
    if m<0
        return println("m must be greater than zero")
    elseif m==0
        return b
    elseif  m==1
        return a
    else
        for n in 1:nr
            a, b = √(2Ωs/(n+1)) .* xs .* a .-  √(n/(n+1)) .* b, a
        end
    end
    return a
end
# actual code for plotting...
bgcp1 = "set object rectangle from screen 0,0 to screen 1,1"
bgcp2 = " behind fillcolor rgb '#212946' fillstyle solid noborder"
bgcolor = bgcp1*bgcp2
x = LinRange(0,1,1000)
cmap = get(colorschemes[:matter], LinRange(0,1,30))
@gp(x, χn(x; ω₀ = 0.77), "w l lc '#$(hex(cmap[1]))'",
    "set key off", "set xrange [0.4:0.74]", bgcolor,
    "unset ytics; unset xtics; unset border")
for n in 0:15
    @gp :- x χn(x; m = n, ω₀ = 0.77)  "w l lw 0.5 lc '#$(hex(cmap[n+2]))'" :-
    # glow effect
    if n<7
        for i  in 1:8-n
            @gp :- x χn(x; m = n, ω₀ = 0.77) "w l lw $(0.5 + 1.05*i) lc '#F5$(hex(cmap[n+2]))' t ''"
            @gp :- x χn(x; m = n, ω₀ = 0.77) "w l lw $(0.5 + 1.05*i) lc '#F5$(hex(cmap[n+2]))' t ''" :-
        end
    else
        for i  in 1:3
            @gp :- x χn(x; m = n, ω₀ = 0.77) "w l lw $(0.5 + 1.05*i) lc '#F5$(hex(cmap[n+2]))' t ''"
            @gp :- x χn(x; m = n, ω₀ = 0.77) "w l lw $(0.5 + 1.05*i) lc '#F5$(hex(cmap[n+2]))' t ''" :-
        end

    end
end
@gp
save(term="pngcairo size 1200,400", output="bannerGlow.png")
#save(term="pdf size 7,1.8", output="bannerGlow.pdf") # hide


using RayTracingWeekend

using BenchmarkTools
using Images
using LinearAlgebra
using InteractiveUtils
using StaticArrays
using Test

"Modified from BenchmarkTools @ballocated. Returns number of heap allocations, instead of bytes."
macro ballocs(args...)
    return esc(quote
        $BenchmarkTools.allocs($BenchmarkTools.minimum($BenchmarkTools.@benchmark $(args...)))
    end)
end

"Test passes if `args...` causes no heap allocations."
macro test_no_allocs(args...)
    return esc(quote
        # limit number of samples to 5... since the variance is extremely low
        @test (@ballocs $(args...) samples=3) == 0
    end)
end

@testset "Dep_Images" begin # dependencies on Images.jl
    img = rand(4, 3)
    img_rgb = rand(RGB, 4, 4)

    ex1 = [RGB(1,0,0) RGB(0,1,0) RGB(0,0,1);
        RGB(1,1,0) RGB(1,1,1) RGB(0,0,0)]

    ex2 = zeros(RGB, 2, 3)
    ex2[1,1] = RGB(1,0,0)
    ex2

    # The "Hello World" of graphics
    @fastmath function gradient(nx::Int, ny::Int)
        img = zeros(RGB, ny, nx)
        @inbounds for j in 1:nx
            for i in 1:ny # Julia is column-major, i.e. iterate 1 column at a time
                x = j; y = (ny-i);
                r = x/nx
                g = y/ny
                b = 0.2
                img[i,j] = RGB(r,g,b)
            end
        end
        img
    end

    gradient(20,10)
end

""" Temporary function to shoot rays through each pixel. Later replaced by `render`

    Args:
        scene: a function that takes a ray, returns the color of any object it hit
"""
function shoot_rays1(nx::Int, ny::Int, scene, ::Type{T}) where T
    lower_left_corner = SA[-2,-1,-1]
    horizontal = SA[4,0,0]
    vertical = SA[T(0),T(2),T(0)]
    origin = SA[T(0),T(0),T(0)]

    img = zeros(RGB{T}, ny, nx)
    Threads.@threads for j in 1:nx
        @inbounds for i in 1:ny # Julia is column-major, i.e. iterate 1 column at a time
            u = T(j/nx)
            v = T((ny-i)/ny) # Y-up!
            ray = Ray(origin, normalize(lower_left_corner + u*horizontal + v*vertical))
            #r = x/nx
            #g = y/ny
            #b = 0.2
            img[i,j] = rgb(scene(ray))
        end
    end
    img
end

# @fastmath speeds up a lot!
@inline @fastmath function hit_sphere1(center::Vec3{T}, radius::T, r::Ray{T}) where T
    oc = r.origin - center
    #a = r.dir ⋅ r.dir # unnecessary since `r.dir` is normalized
    a = 1
    b = 2oc ⋅ r.dir
    c = (oc ⋅ oc) - radius*radius
    discriminant = b*b - 4a*c
    discriminant > 0
end

@inline function sphere_scene1(r::Ray{T}) where T
    if hit_sphere1(SA[T(0), T(0), T(-1)], T(0.5), r) # sphere of radius 0.5 centered at z=-1
        return SA[T(1),T(0),T(0)] # red
    else
        skycolor(r)
    end
end

@inline function hit_sphere2(center::Vec3{T}, radius::T, r::Ray{T}) where T
    oc = r.origin - center
    #a = r.dir ⋅ r.dir # unnecessary since `r.dir` is normalized
    a = 1
    b = 2oc ⋅ r.dir
    c = (oc ⋅ oc) - radius^2
    discriminant = b*b - 4a*c
    if discriminant < 0
        return -1
    else
        return (-b - sqrt(discriminant)) / 2a
    end
end

@inline function sphere_scene2(r::Ray{T}) where T
    sphere_center = SA[T(0),T(0),T(-1)]
    t = hit_sphere2(sphere_center, T(0.5), r) # sphere of radius 0.5 centered at z=-1
    if t > T(0)
        n⃗ = normalize(point(r, t) - sphere_center) # normal vector. typed n\vec
        return T(0.5)*n⃗ + SA[T(0.5),T(0.5),T(0.5)] # remap normal to rgb
    else
        skycolor(r)
    end
end

@testset "RayTracingWeekend.jl" begin
    t_col = SA[0.4, 0.5, 0.1] # test color

    # Verify that no heap allocations occur for low-level functions
    @test_no_allocs squared_length($t_col) # 1.162 ns

    @test !near_zero(t_col)
    @test_no_allocs near_zero($t_col) # 1.382 ns

    #@show @btime rgb($t_col) # 1.382 ns
    @test rgb(t_col) == RGB{Float64}(0.4,0.5,0.1)
    @test_no_allocs rgb($t_col)

    _origin = SA[0.0,0.0,0.0]
    _v3_minusY = SA[0.0,-1.0,0.0]
    _t_ray1 = Ray(_origin, _v3_minusY)

    # Float32: 6.161 ns (0 allocations: 0 bytes)
    # Float64: 6.900 ns (0 allocations: 0 bytes)
    @test_no_allocs Ray($_origin, $_v3_minusY)
    @test_no_allocs point($_t_ray1, 0.5) # 1.412 ns
    @test_no_allocs skycolor($_t_ray1) # 1.402 ns
    @test_no_allocs rgb(skycolor($_t_ray1)) # 1.412 ns
    @test_no_allocs trand()
    @test_no_allocs random_between(50.0, 100.0) # 2.695 ns
    @test_no_allocs random_vec3(-1.0,1.0)
    @test_no_allocs random_vec2(-1.0f0,1.0f0)

    # REMEMBER: the times are somewhat random! e.g. use best timing of 5!
    @test_no_allocs random_vec3_in_sphere(Float64) # 19.716 ns
    @test_no_allocs random_vec3_on_sphere(Float64)
    @test_no_allocs random_vec2_in_disk(Float64)

    @benchmark random_vec2_in_disk(Float64) samples=3

    # varies depending on number of threads, maybe mem alignment?
    @test (@ballocs shoot_rays1(20, 10, $skycolor, $Float32) samples=3) < 85 # typically 81-83

    shoot_rays1(40, 20, sphere_scene1, Float32)

    @test (@ballocs shoot_rays1(40, 20, sphere_scene1, Float32) samples=3) < 85

    # # claforte: this got significantly worse after Vec3{T} was integrated...
    # # WAS:
    # # Float32: 262.286 μs (2 allocations: 234.45 KiB)
    # # NOW:
    # # Float32: 290.470 μs (2 allocations: 234.45 KiB)
    # # Float64: 351.164 μs (2 allocations: 468.83 KiB)
    # # Above was all using 1 single thread. With 16 threads:
    # #   64.612 μs (83 allocations: 241.75 KiB)
    # # Eliminate unnecessary r.dir ⋅ r.dir:
    # #   61.586 μs (82 allocations: 241.72 KiB)
    # #print("@btime main(200,100,sphere_scene2, Float32):")
    shoot_rays1(200,100,sphere_scene2, Float32)

    @assert RayTracingWeekend.reflect(SA[0.6,-0.8,0.0], SA[0.0,1.0,0.0]) == SA[0.6,0.8,0.0]

    default_camera(SA[0f0,0f0,0f0]) # Float32 camera

    get_ray(default_camera(SA[0f0,0f0,0f0]), 0.0f0, 0.0f0)

    @test_no_allocs get_ray(default_camera(SA[0f0,0f0,0f0]), 0.0f0, 0.0f0)

    ELEM_TYPE = Float64 # Float32/Float64

    t_default_cam = default_camera(SA{ELEM_TYPE}[0,0,0])

    # Using @paulmelis' style of hit(): @inbounds for i in eachindex(hittables) and Union{HitRecord, Nothing}
    #   951.447 μs (65574 allocations: 5.12 MiB)
    render(scene_2_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 16) # 16 samples

    # # Using @paulmelis' style of hit(): @inbounds for i in eachindex(hittables) and Union{HitRecord, Nothing}
    # #   101.161 μs (4314 allocations: 459.41 KiB)
    # render(scene_2_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 1) # 1 sample

    # #render(scene_4_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 16)

    # # unchanged angle
    # @assert refract((@SVector[0.6,-0.8,0]), (@SVector[0.0,1.0,0.0]), 1.0) == @SVector[0.6,-0.8,0.0]

    # # wider angle
    # t_refract_widerθ = refract(@SVector[0.6,-0.8,0.0], @SVector[0.0,1.0,0.0], 2.0)
    # @assert isapprox(t_refract_widerθ, @SVector[0.87519,-0.483779,0.0]; atol=1e-3)

    # # narrower angle
    # t_refract_narrowerθ = refract(@SVector[0.6,-0.8,0.0], @SVector[0.0,1.0,0.0], 0.5)
    # @assert isapprox(t_refract_narrowerθ, @SVector[0.3,-0.953939,0.0]; atol=1e-3)

    # #render(scene_diel_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 16)
    # #render(scene_diel_spheres(), default_camera(), 320, 32)

    # # Hollow Glass sphere using a negative radius
    # #render(scene_diel_spheres(-0.5; elem_type=ELEM_TYPE), t_default_cam, 96, 16)

    # #render(scene_diel_spheres(; elem_type=ELEM_TYPE), default_camera((SA{ELEM_TYPE}[-2,2,1]), (SA{ELEM_TYPE}[0,0,-1]),
    # #																 (SA{ELEM_TYPE}[0,1,0]), ELEM_TYPE(20)), 96, 16)

    # #render(scene_blue_red_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 16)

    # t_cam1 = default_camera([13,2,3], [0,0,0], [0,1,0], 20, 16/9, 0.1, 10.0; elem_type=ELEM_TYPE)

    # # took ~20s (really rough timing) in REPL, before optimization
    # # after optimization:
    # #   880.997 ms (48801164 allocations: 847.10 MiB)
    # # after switching to Float32 + reducing allocations using rand_vec3!():
    # #    79.574 ms (  542467 allocations:  14.93 MiB)
    # # after optimizing skycolor, rand*, probably more stuff I forgot...
    # #    38.242 ms (  235752 allocations:   6.37 MiB)
    # # after removing all remaining Color, Vec3, replacing them with @SVector[]...
    # #    26.790 ms (   91486 allocations: 2.28 MiB)
    # # Using convert(Float32, ...) instead of MyFloat(...):
    # #    25.856 ms (   70650 allocations: 1.96 MiB)
    # # Don't specify return value of Option{HitRecord} in hit()
    # # Don't initialize unnecessary elements in HitRecord():
    # #    38.961 ms (   70681 allocations: 1.96 MiB) # WORSE, probably because we're writing a lot more to stack...?
    # # Replace MyFloat by Float32:
    # #    36.726 ms (13889 allocations: 724.09 KiB)
    # # @inline lots of stuff:
    # #    14.690 ms (13652 allocations: 712.98 KiB)
    # # rand(Float32) to avoid Float64s:
    # #    14.659 ms (13670 allocations: 713.84 KiB)
    # # Re-measured:
    # #    14.069 ms (13677 allocations: 714.22 KiB)
    # # After parameterized Vec3{T}:
    # # Float32: 14.422 ms (26376 allocations: 896.52 KiB) # claforte: why are there 2X the allocations as previously?
    # # Float64: 12.772 ms (12868 allocations: 1.08 MiB) (10% speed-up! Thanks @woclass!)
    # # rand() using MersenneTwister _rng w/ Float64:
    # #    14.092 ms (12769 allocations: 1.07 MiB) (SLOWER?!)
    # # rand() using Xoroshiro128Plus _rng w/ Float64:
    # #    12.581 ms (12943 allocations: 1.08 MiB)
    # # Above was all using 1 single thread. With 16 threads:
    # #     1.789 ms (12926 allocations: 1.08 MiB) (WOW!)
    # # Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
    # # Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
    # #     2.168 ms (13791 allocations: 1.15 MiB)
    # # Using  bunch of @inbounds, @simd in low-level functions
    # #     2.076 ms (13861 allocations: 1.15 MiB)
    # # Lots of optimizations, up to `Using non-mutable HitRecord, Union{HitRecordMissing}, ismissing():`
    # #     2.042 ms (14825 allocations: 1.23 MiB)
    # #@btime render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 96, 1)

    # # took 5020s in Pluto.jl, before optimizations!
    # # after lots of optimizations, up to switching to Float32 + reducing allocations using rand_vec3!():
    # #   10.862032 seconds (70.09 M allocations: 1.858 GiB, 1.53% gc time)
    # # after optimizing skycolor, rand*, probably more stuff I forgot...
    # #    4.926 s (25,694,163 allocations: 660.06 MiB)
    # # after removing all remaining Color, Vec3, replacing them with @SVector[]...
    # #    3.541 s (9074843 allocations: 195.22 MiB)
    # # Using convert(Float32, ...) instead of MyFloat(...):
    # #    3.222 s (2055106 allocations: 88.53 MiB)
    # # Don't specify return value of Option{HitRecord} in hit()
    # # Don't initialize unnecessary elements in HitRecord():
    # #    5.016 s (2056817 allocations: 88.61 MiB) #  WORSE, probably because we're writing a lot more to stack...?
    # # Replace MyFloat by Float32:
    # #    5.185 s (1819234 allocations: 83.55 MiB) # Increase of 1% is probably noise
    # # Remove normalize() in reflect() (that function assumes the inputs are normalized)
    # #    5.040 s (1832052 allocations: 84.13 MiB)
    # # @inline lots of stuff:
    # #    2.110 s (1823044 allocations: 83.72 MiB)
    # # rand(Float32) to avoid Float64s:
    # #    2.063 s (1777985 allocations: 81.66 MiB)
    # # @woclass's "Use alias instead of new struct", i.e. `const HittableList = Vector{Hittable}`:
    # #    1.934 s (1796954 allocations: 82.53 MiB)
    # # @woclass's Vec3{T} with T=Float64: (7.8% speed-up!)
    # #    1.800 s (1711061 allocations: 131.03 MiB)
    # # rand() using Xoroshiro128Plus _rng w/ Float64:
    # #    1.808 s (1690104 allocations: 129.43 MiB) (i.e. rand is not a bottleneck?)
    # # Above was all using 1 single thread. With 16 threads:
    # #  265.331 ms (1645402 allocations: 126.02 MiB) (WOW!)
    # # Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
    # # Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
    # #  308.217 ms (1830162 allocations: 140.12 MiB)
    # # Using @inbounds, @simd in low-level functions:
    # #  302.952 ms (1892513 allocations: 144.88 MiB)
    # # Convert Camera and every Material structs to non-mutable:
    # #  301.042 ms (1849711 allocations: 141.61 MiB)  (i.e. - unchanged)
    # # Adapt @Christ_Foster's Base.getproperty w/ @inline @inbounds:
    # #  292.603 ms (1856398 allocations: 142.12 MiB) (ran multiple times, seems like real, 3-5% speed-up)
    # # Eliminate the off-by-half-a-pixel offset:
    # #  286.873 ms (1811412 allocations: 138.69 MiB) (ran multiple times, seems like ~2.5% speed-up)
    # # Fixed, per-thread RNGs with fixed seeds
    # #  286.575 ms (1884433 allocations: 144.26 MiB) (i.e. maybe a tiny bit faster considering this fixed seed has more allocations?)
    # # Make HitRecord non-mutable:
    # #   29.733 s (937962909 allocations: 69.88 GiB) (WTF!)
    # # Lots of optimizations, up to `Using non-mutable HitRecord, Union{HitRecordMissing}, ismissing():`
    # #  306.011 ms (1884433 allocations: 144.26 MiB) (Still slower... Hum)
    # # Using @paulmelis' style of hit(): @inbounds for i in eachindex(hittables) and Union{HitRecord, Nothing}
    # #  304.877 ms (1884433 allocations: 144.26 MiB)
    # # Extract the scene creation from the render() call:
    # #  300.344 ms (1883484 allocations: 144.21 MiB)
    # print("render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 200, 32):")
    # reseed!()
    # _scene_random_spheres = scene_random_spheres(; elem_type=ELEM_TYPE)
    # @btime render($_scene_random_spheres, $t_cam1, 200, 32)

    # # After some optimization, took ~5.6 hours:
    # #   20171.646846 seconds (94.73 G allocations: 2.496 TiB, 1.06% gc time)
    # # ... however the image looked weird... too blurry
    # # After removing all remaining Color, Vec3, replacing them with @SVector[]... took ~3.7 hours:
    # #   13326.770907 seconds (29.82 G allocations: 714.941 GiB, 0.36% gc time)
    # # Using convert(Float32, ...) instead of MyFloat(...):
    # # Don't specify return value of Option{HitRecord} in hit()
    # # Don't initialize unnecessary elements in HitRecord():
    # # Took ~4.1 hours:
    # #   14723.339976 seconds (5.45 G allocations: 243.044 GiB, 0.11% gc time) # WORSE, probably because we're writing a lot more to stack...?
    # # Replace MyFloat by Float32:
    # # Lots of other optimizations including @inline lots of stuff:
    # #    6018.101653 seconds (5.39 G allocations: 241.139 GiB, 0.21% gc time) (1.67 hour)
    # # @woclass's rand(Float32) to avoid Float64s: (expected to provide 2.2% speed up)
    # # @woclass's "Use alias instead of new struct", i.e. `const HittableList = Vector{Hittable}`
    # # @woclass's Vec3{T} with T=Float64: (7.8% speed-up!):
    # #    5268.175362 seconds (4.79 G allocations: 357.005 GiB, 0.47% gc time) (1.46 hours)
    # # Above was all using 1 single thread. With 16 threads: (~20 minutes)
    # #    1210.363539 seconds (4.94 G allocations: 368.435 GiB, 10.08% gc time)
    # # Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
    # # Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
    # #    1298.522674 seconds (5.43 G allocations: 404.519 GiB, 10.18% gc time)
    # # Using @inbounds, @simd in low-level functions:
    # #    1314.510565 seconds (5.53 G allocations: 411.753 GiB, 10.21% gc time) # NOTE: difference due to randomness?
    # # Adapt @Christ_Foster's Base.getproperty w/ @inline @inbounds: (expect 3-5% speed-up)
    # # Eliminate the off-by-half-a-pixel offset: (expect ~2.5% speed-up)
    # # Fixed, per-thread RNGs with fixed seeds (expecting no noticeable change in speed)
    # #  Using 16 threads: (21m22s)
    # #    1282.437499 seconds (5.53 G allocations: 411.742 GiB, 10.08% gc time) (i.e. 2.5% speed-up... currently GC- and memory-bound?)
    # #  Using 14 threads: (21m45s)
    # #    1305.767627 seconds (5.53 G allocations: 411.741 GiB, 9.97% gc time)
    # #print("@time render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 1920, 1000):")
    # #@time render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 1920, 1000)

    # t_cam2 = default_camera([3,3,2], [0,0,-1], [0,1,0], 20, 16/9, 2.0, norm([3,3,2]-[0,0,-1]);
    #                         elem_type=ELEM_TYPE)

    # # Before optimization:
    # #  5.993 s  (193097930 allocations: 11.92 GiB)
    # # after disabling: `Base.getproperty(vec::SVector{3}, sym::Symbol)`
    # #  1.001 s  ( 17406437 allocations: 425.87 MiB)
    # # after forcing Ray and point() to use Float64 instead of AbstractFloat:
    # #  397.905 ms (6269207 allocations: 201.30 MiB)
    # # after forcing use of Float32 instead of Float64:
    # #  487.680 ms (7128113 allocations: 196.89 MiB) # More allocations... something is causing them...
    # # after optimizing rand_vec3!/rand_vec2! to minimize allocations:
    # #  423.468 ms (6075725 allocations: 158.92 MiB)
    # # after optimizing skycolor, rand*, probably more stuff I forgot...
    # #  217.853 ms (2942272 allocations: 74.82 MiB)
    # # after removing all remaining Color, Vec3, replacing them with @SVector[]...
    # #   56.778 ms (1009344 allocations: 20.56 MiB)
    # # Using convert(Float32, ...) instead of MyFloat(...):
    # #   23.870 ms (210890 allocations: 8.37 MiB)
    # # Replace MyFloat by Float32:
    # #   22.390 ms (153918 allocations: 7.11 MiB)
    # # Various other changes, e.g. remove unnecessary normalize
    # #   20.241 ms (153792 allocations: 7.10 MiB)
    # # @inline lots of stuff:
    # #   18.065 ms (153849 allocations: 7.10 MiB)
    # # rand(Float32) to avoid Float64s:
    # #   16.035 ms (153777 allocations: 7.10 MiB)
    # # After @woclass's Vec3{T} with T=Float64:
    # #   16.822 ms (153591 allocations: 11.84 MiB)
    # # rand() using Xoroshiro128Plus _rng w/ Float64:
    # #   13.469 ms (153487 allocations: 11.83 MiB)
    # # Above was all using 1 single thread. With 16 threads:
    # #    6.537 ms (153599 allocations: 11.84 MiB)
    # # Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
    # # Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
    # #    6.766 ms (161000 allocations: 12.40 MiB)
    # # @inbounds and @simd in low-level functions
    # #    6.519 ms (160609 allocations: 12.37 MiB)
    # #render(scene_diel_spheres(; elem_type=ELEM_TYPE), t_cam2, 96, 16)

    # using Profile
    # render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 16, 1)
    # Profile.clear_malloc_data()
    # render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 17, 13)

end # @testset


export RadialTrajectory, radialNodes, radialDensity

"""
    RadialTrajectory(numProfiles, numSamplingPerProfile
                  ; TE::Float64=0.0
                  , AQ::Float64=1.e-3
                  , angleOffset= :equispaced
                  , kargs...)

returns a 2d radial trajectory.

# Arguments
* `numProfiles::Int64`            - number of profiles
* `numSamplingPerProfile::Int64`  - number of sampling points per profile
* (`TE::Float64=0.0`)             - echo time in s
* (`AQ::Float64=1.e-3`)           - readout duration in s (per profile)
* (`angleOffset= :equispaced`)    - spacing of profile angles (`:equispaced` sampling, `:golden` angle sampling or `:random` sampling)
"""
function RadialTrajectory(::Type{T}, numProfiles, numSamplingPerProfile
                  ; TE::T=0.0
                  , AQ::T=1.e-3
                  , angleOffset= :equispaced
                  , kargs...) where T
  nodes = radialNodes(T, numProfiles, numSamplingPerProfile; angleOffset=angleOffset)
  times = readoutTimes(T, numProfiles, numSamplingPerProfile; TE=TE, AQ=AQ)
  return  Trajectory("Radial", nodes, times, TE, AQ, numProfiles, numSamplingPerProfile, 1, false, true)
end

function radialNodes(::Type{T}, numProfiles::Int64
                          , numSamplingPerProfile::Int64
                          ; angleOffset= :equispaced
                          , kargs...) where T
  nodes = zeros(T, 2, numSamplingPerProfile, numProfiles)
  if angleOffset == :golden
    angles = [i*getGoldenAngleRad()  for i=0:numProfiles-1 ]
  elseif angleOffset == :random
    angles = sort(pi .* rand(numProfiles))
  elseif angleOffset == :equispaced
    angles = collect(pi.*(0:numProfiles-1)/numProfiles)
  end

  pos = collect((0:numSamplingPerProfile-1)/numSamplingPerProfile .- 0.5)
  for l = 1:numProfiles
    for k = 1:numSamplingPerProfile
      nodes[1,k,l] = (-1)^l * pos[k]*cos(angles[l])
      nodes[2,k,l] = (-1)^l * pos[k]*sin(angles[l])
    end
  end
  return reshape(nodes, 2, numSamplingPerProfile*numProfiles)
end

function radialDensity(::Type{T}, numProfiles::Int64, numSamplingPerProfile::Int64) where T
  density = zeros(T, numSamplingPerProfile, numProfiles)
  pos = collect((0:numSamplingPerProfile-1)/numSamplingPerProfile .- 0.5)
  for l = 1:numProfiles
    for k = 1:numSamplingPerProfile
      density[k,l] = abs(pos[k])
    end
  end
  return vec(density)
end


# The SteinGaussianUnboundedPowerKernel has base kernel of the form:
#
# k(x, y) = ((1 + gamma*||x||^alpha)*(1 + gamma*||y||^alpha)) *
#   exp(-1/(2*beta)*||x-y||^2)
#
# where ||.|| is the L2 norm. This is gauranteed to enforce
# that k is a universal kernel that vanishes at infinity.

mutable struct SteinGaussianUnboundedPowerKernel <: SteinGaussianWeightedKernel
    # the alpha parameter
    alpha::Float64
    # the beta parameter
    beta::Float64
    # the gamma parameter
    gamma::Float64
end

# have default parameters of alpha = 2.5,  beta = 1.0, gamma = 1.0
SteinGaussianUnboundedPowerKernel() = SteinGaussianUnboundedPowerKernel(2.5, 1.0, 1.0)

SteinGaussianUnboundedPowerKernel(alpha::Float64) = SteinGaussianUnboundedPowerKernel(alpha, 1.0, 1.0)

SteinGaussianUnboundedPowerKernel(alpha::Int) = SteinGaussianUnboundedPowerKernel(float(alpha), 1.0, 1.0)

# The term (1 + gamma*||z||^alpha)
function _Qz(ker::SteinGaussianUnboundedPowerKernel, z::Array{Float64, 1})
    1.0 + ker.gamma * norm(z,2)^ker.alpha
end

# The term ((1 + gamma*||x||^alpha)*(1 + gamma*||y||^alpha))^-1
function Q(ker::SteinGaussianUnboundedPowerKernel, x::Array{Float64, 1}, y::Array{Float64, 1})
    _Qz(ker, x) * _Qz(ker, y)
end

function dxj_Q(ker::SteinGaussianUnboundedPowerKernel, x::Array{Float64,1}, y::Array{Float64,1}, j::Int64)
    fy = _Qz(ker, y)
    mx = norm(x,2)^(ker.alpha - 2)
    mx * ker.gamma * ker.alpha * fy * x[j]
end

function dxjyj_Q(ker::SteinGaussianUnboundedPowerKernel, x::Array{Float64,1}, y::Array{Float64,1}, j::Int64)
    mx = norm(x,2)^(ker.alpha - 2)
    my = norm(y,2)^(ker.alpha - 2)

    ker.gamma^2 * ker.alpha^2 * mx * my * x[j] * y[j]
end


#=
Written by Rasmus Kyng.

Given an adjacency matrix mat
where A[i,j] != 0 indicates a directed edge from i to j,
compute a topological sorting of the vertices, s.t.

Please check that the edge direction agrees with what you want before usage!
=#

function toposort{Tv,Ti}(mat::SparseMatrixCSC{Tv,Ti})
    # each COL i gives the outgoing edges of vertex i
    n = mat.n
    colptr::Array{Ti,1} = mat.colptr
    rowval::Array{Ti,1} = mat.rowval # the row index corresponding to a given colptr entry
    popSorted::Array{Ti,1} = zeros(Ti,n);
    seen::Array{Bool,1} = falses(n);
    popCount::Ti = 0
    s = DataStructures.Stack(Tuple{Ti,Ti})
    for i = 1:n
        if seen[i]
            continue
        end
        seen[i] = true
        DataStructures.push!(s,(i,colptr[i]))
        while !isempty(s)
            vertNbrTuple = DataStructures.pop!(s)
            v = vertNbrTuple[1]
            edgeNum = vertNbrTuple[2]
            if edgeNum < colptr[v+1]
                DataStructures.push!(s,(v,edgeNum+1))
                nbr = rowval[edgeNum]
                if !seen[nbr]
                    seen[nbr] = true
                    DataStructures.push!(s,(nbr,colptr[nbr]))
                end
            else
                popCount += 1
                popSorted[popCount] = v
            end
        end
    end
    return popSorted
end

"""The signed edge-vertex adjacency matrix"""
function dirEdgeVertexMat(A::SparseMatrixCSC)
    (ai,aj) = findnz(A)
    m = length(ai)
    n = size(A)[1]
    return sparse(collect(1:m),ai,1.0,m,n) - sparse(collect(1:m),aj,1.0,m,n)
end


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

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

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

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

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

function (γ::GaussianVariogram)(h)
  # add small eps to nugget
  # for numerical stability
  r = radius(γ.ball)
  s = γ.sill
  n = γ.nugget + typeof(s)(1e-6)
  (s - n) * (1 - exp(-3(h/r)^2)) + (h > 0) * n
end

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


using Random; Random.seed!(1238)
using OrdinaryDiffEq
using Statistics
using ForwardDiff, Calculus
using DiffEqSensitivity
using Test
using Zygote

@testset "LSS" begin
  @testset "Lorentz single parameter" begin
    function lorenz!(du,u,p,t)
      du[1] = 10*(u[2]-u[1])
      du[2] = u[1]*(p[1]-u[3]) - u[2]
      du[3] = u[1]*u[2] - (8//3)*u[3]
    end

    p = [28.0]
    tspan_init = (0.0,30.0)
    tspan_attractor = (30.0,50.0)
    u0 = rand(3)
    prob_init = ODEProblem(lorenz!,u0,tspan_init,p)
    sol_init = solve(prob_init,Tsit5())
    prob_attractor = ODEProblem(lorenz!,sol_init[end],tspan_attractor,p)
    sol_attractor = solve(prob_attractor,Vern9(),abstol=1e-14,reltol=1e-14)

    g(u,p,t) = u[end]
    function dg(out,u,p,t,i)
      fill!(out, zero(eltype(u)))
      out[end] = -one(eltype(u))
    end
    lss_problem1 = ForwardLSSProblem(sol_attractor, ForwardLSS(), g)
    lss_problem1a = ForwardLSSProblem(sol_attractor, ForwardLSS(), nothing, dg)
    lss_problem2 = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=DiffEqSensitivity.Cos2Windowing()), g)
    lss_problem2a = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=DiffEqSensitivity.Cos2Windowing()), nothing, dg)
    lss_problem3 = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=10), g)
    lss_problem3a = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=10), g, dg) #ForwardLSS with time dilation requires knowledge of g

    adjointlss_problem = AdjointLSSProblem(sol_attractor, AdjointLSS(alpha=10.0), g)
    adjointlss_problem_a = AdjointLSSProblem(sol_attractor, AdjointLSS(alpha=10.0), g, dg)

    res1 = shadow_forward(lss_problem1)
    res1a = shadow_forward(lss_problem1a)
    res2 = shadow_forward(lss_problem2)
    res2a = shadow_forward(lss_problem2a)
    res3 = shadow_forward(lss_problem3)
    res3a = shadow_forward(lss_problem3a)

    res4 = shadow_adjoint(adjointlss_problem)
    res4a = shadow_adjoint(adjointlss_problem_a)

    @test res1[1] ≈ 1 atol=1e-1
    @test res2[1] ≈ 1 atol=1e-1
    @test res3[1] ≈ 1 atol=5e-2

    @test res1 ≈ res1a atol=1e-10
    @test res2 ≈ res2a atol=1e-10
    @test res3 ≈ res3a atol=1e-10
    @test res3 ≈ res4 atol=1e-10
    @test res3 ≈ res4a atol=1e-10

    # fixed saveat to compare with concrete solve
    sol_attractor2 = solve(prob_attractor,Vern9(),abstol=1e-14,reltol=1e-14, saveat=0.01)
    lss_problem1 = ForwardLSSProblem(sol_attractor2, ForwardLSS(), g)
    lss_problem1a = ForwardLSSProblem(sol_attractor2, ForwardLSS(), nothing, dg)
    lss_problem2 = ForwardLSSProblem(sol_attractor2, ForwardLSS(alpha=DiffEqSensitivity.Cos2Windowing()), g)
    lss_problem2a = ForwardLSSProblem(sol_attractor2, ForwardLSS(alpha=DiffEqSensitivity.Cos2Windowing()), nothing, dg)
    lss_problem3 = ForwardLSSProblem(sol_attractor2, ForwardLSS(alpha=10), g)
    lss_problem3a = ForwardLSSProblem(sol_attractor2, ForwardLSS(alpha=10), g, dg) #ForwardLSS with time dilation requires knowledge of g

    adjointlss_problem = AdjointLSSProblem(sol_attractor2, AdjointLSS(alpha=10.0), g)
    adjointlss_problem_a = AdjointLSSProblem(sol_attractor2, AdjointLSS(alpha=10.0), g, dg)

    res1 = shadow_forward(lss_problem1)
    res1a = shadow_forward(lss_problem1a)
    res2 = shadow_forward(lss_problem2)
    res2a = shadow_forward(lss_problem2a)
    res3 = shadow_forward(lss_problem3)
    res3a = shadow_forward(lss_problem3a)

    res4 = shadow_adjoint(adjointlss_problem)
    res4a = shadow_adjoint(adjointlss_problem_a)

    @test res1[1] ≈ 1 atol=5e-2
    @test res2[1] ≈ 1 atol=5e-2
    @test res3[1] ≈ 1 atol=5e-2

    @test res1 ≈ res1a atol=1e-10
    @test res2 ≈ res2a atol=1e-10
    @test res3 ≈ res3a atol=1e-10
    @test res3 ≈ res4 atol=1e-10
    @test res3 ≈ res4a atol=1e-10

    function G(p; sensealg=ForwardLSS(), dt=0.01, g=nothing)
      _prob = remake(prob_attractor,p=p)
      _sol = solve(_prob,Vern9(),abstol=1e-14,reltol=1e-14,saveat=dt,sensealg=sensealg, g=g)
      sum(getindex.(_sol.u,3))
    end

    dp1 = Zygote.gradient((p)->G(p),p)
    @test res1 ≈ dp1[1] atol=1e-10

    dp1 = Zygote.gradient((p)->G(p, sensealg=ForwardLSS(alpha=DiffEqSensitivity.Cos2Windowing())),p)
    @test res2 ≈ dp1[1] atol=1e-10

    dp1 = Zygote.gradient((p)->G(p, sensealg=ForwardLSS(alpha=10), g=g),p)
    @test res3 ≈ dp1[1] atol=1e-10

    dp1 = Zygote.gradient((p)->G(p, sensealg=AdjointLSS(alpha=10.0), g=g),p)
    @test res4 ≈ dp1[1] atol=1e-10

    @show res1[1] res2[1] res3[1]
  end

  @testset "Lorentz" begin
    function lorenz!(du,u,p,t)
      du[1] = p[1]*(u[2]-u[1])
      du[2] = u[1]*(p[2]-u[3]) - u[2]
      du[3] = u[1]*u[2] - p[3]*u[3]
    end

    p = [10.0, 28.0, 8/3]

    tspan_init = (0.0,30.0)
    tspan_attractor = (30.0,50.0)
    u0 = rand(3)
    prob_init = ODEProblem(lorenz!,u0,tspan_init,p)
    sol_init = solve(prob_init,Tsit5())
    prob_attractor = ODEProblem(lorenz!,sol_init[end],tspan_attractor,p)
    sol_attractor = solve(prob_attractor,Vern9(),abstol=1e-14,reltol=1e-14)

    g(u,p,t) = u[end] + sum(p)
    function dgu(out,u,p,t,i)
      fill!(out, zero(eltype(u)))
      out[end] = -one(eltype(u))
    end
    function dgp(out,u,p,t,i)
      fill!(out, -one(eltype(p)))
    end

    lss_problem = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=10), g)
    lss_problem_a = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=10), g, (dgu,dgp))
    adjointlss_problem = AdjointLSSProblem(sol_attractor, AdjointLSS(alpha=10.0), g)
    adjointlss_problem_a = AdjointLSSProblem(sol_attractor, AdjointLSS(alpha=10.0), g, (dgu,dgp))

    resfw = shadow_forward(lss_problem)
    resfw_a = shadow_forward(lss_problem_a)
    resadj = shadow_adjoint(adjointlss_problem)
    resadj_a = shadow_adjoint(adjointlss_problem_a)

    @test resfw ≈ resadj rtol=1e-10
    @test resfw ≈ resfw_a rtol=1e-10
    @test resfw ≈ resadj_a rtol=1e-10

    sol_attractor2 = solve(prob_attractor,Vern9(),abstol=1e-14,reltol=1e-14, saveat=0.01)
    lss_problem = ForwardLSSProblem(sol_attractor2, ForwardLSS(alpha=10), g)
    resfw = shadow_forward(lss_problem)

    function G(p; sensealg=ForwardLSS(), dt=0.01, g=nothing)
      _prob = remake(prob_attractor,p=p)
      _sol = solve(_prob,Vern9(),abstol=1e-14,reltol=1e-14,saveat=dt,sensealg=sensealg, g=g)
      sum(getindex.(_sol.u,3)) + sum(p)
    end

    dp1 = Zygote.gradient((p)->G(p, sensealg=ForwardLSS(alpha=10), g=g),p)
    @test resfw ≈ dp1[1] atol=1e-10

    dp1 = Zygote.gradient((p)->G(p, sensealg=AdjointLSS(alpha=10.0), g=g),p)
    @test resfw ≈ dp1[1] atol=1e-10

    @show resfw
  end

  @testset "T0skip and T1skip" begin
    function lorenz!(du,u,p,t)
      du[1] = p[1]*(u[2]-u[1])
      du[2] = u[1]*(p[2]-u[3]) - u[2]
      du[3] = u[1]*u[2] - p[3]*u[3]
    end

    p = [10.0, 28.0, 8/3]

    tspan_init = (0.0,30.0)
    tspan_attractor = (30.0,50.0)
    u0 = rand(3)
    prob_init = ODEProblem(lorenz!,u0,tspan_init,p)
    sol_init = solve(prob_init,Tsit5())
    prob_attractor = ODEProblem(lorenz!,sol_init[end],tspan_attractor,p)
    sol_attractor = solve(prob_attractor,Vern9(),abstol=1e-14,reltol=1e-14, saveat=0.01)

    g(u,p,t) = u[end]^2/2 + sum(p)
    function dgu(out,u,p,t,i)
      fill!(out, zero(eltype(u)))
      out[end] = -u[end]
    end
    function dgp(out,u,p,t,i)
      fill!(out, -one(eltype(p)))
    end

    function G(p; sensealg=ForwardLSS(), dt=0.01, g=nothing, t0skip=0.0, t1skip=0.0)
      _prob = remake(prob_attractor,p=p)
      _sol = solve(_prob,Vern9(),abstol=1e-14,reltol=1e-14,saveat=dt,sensealg=sensealg, g=g, t0skip=t0skip, t1skip=t1skip)
      sum(getindex.(_sol.u,3).^2)/2 + sum(p)
    end

    ## ForwardLSS

    lss_problem = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=10), g)
    resfw = shadow_forward(lss_problem)

    res = deepcopy(resfw)

    dp1 = Zygote.gradient((p)->G(p, sensealg=ForwardLSS(alpha=10), g=g),p)
    @test res ≈ dp1[1] atol=1e-10

    resfw = shadow_forward(lss_problem; t0skip=10.0, t1skip=5.0)
    resskip = deepcopy(resfw)

    dp1 = Zygote.gradient((p)->G(p, sensealg=ForwardLSS(alpha=10), g=g, t0skip=10.0, t1skip=5.0),p)
    @test resskip ≈ dp1[1] atol=1e-10

    @show res resskip

    ## ForwardLSS with dgdu and dgdp

    lss_problem = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=10), g, (dgu,dgp))
    res2 = shadow_forward(lss_problem)
    @test res ≈ res2 atol=1e-10
    res2 = shadow_forward(lss_problem; t0skip=10.0, t1skip=5.0)
    @test resskip ≈ res2 atol=1e-10

    ## AdjointLSS

    lss_problem = AdjointLSSProblem(sol_attractor, AdjointLSS(alpha=10.0), g)
    res2 = shadow_adjoint(lss_problem)
    @test res ≈ res2 atol=1e-10
    res2 = shadow_adjoint(lss_problem; t0skip=10.0, t1skip=5.0)
    @test_broken resskip ≈ res2 atol=1e-10

    dp1 = Zygote.gradient((p)->G(p, sensealg=AdjointLSS(alpha=10.0), g=g),p)
    @test res ≈ dp1[1] atol=1e-10

    dp1 = Zygote.gradient((p)->G(p, sensealg=AdjointLSS(alpha=10), g=g, t0skip=10.0, t1skip=5.0),p)
    @test res2 ≈ dp1[1] atol=1e-10

    ## AdjointLSS with dgdu and dgd

    lss_problem = AdjointLSSProblem(sol_attractor, AdjointLSS(alpha=10.0), g, (dgu,dgp))
    res2 = shadow_adjoint(lss_problem)
    @test res ≈ res2 atol=1e-10
    res2 = shadow_adjoint(lss_problem; t0skip=10.0, t1skip=5.0)
    @test_broken resskip ≈ res2 atol=1e-10
  end
end

@testset "NILSS" begin
  @testset "Lorentz single parameter" begin
    function lorenz!(du,u,p,t)
      du[1] = 10*(u[2]-u[1])
      du[2] = u[1]*(p[1]-u[3]) - u[2]
      du[3] = u[1]*u[2] - (8//3)*u[3]
    end

    p = [28.0]
    tspan_init = (0.0,100.0)
    tspan_attractor = (100.0,120.0)
    u0 = rand(3)
    prob_init = ODEProblem(lorenz!,u0,tspan_init,p)
    sol_init = solve(prob_init,Tsit5())
    prob_attractor = ODEProblem(lorenz!,sol_init[end],tspan_attractor,p)

    g(u,p,t) = u[end]
    function dg(out,u,p,t,i)
      fill!(out, zero(eltype(u)))
      out[end] = -one(eltype(u))
    end

    nseg = 50 # number of segments on time interval
    nstep = 2001 # number of steps on each segment

    # fix seed here for res1==res2 check, otherwise hom. tangent
    # are initialized randomly
    Random.seed!(1234)
    nilss_prob1 = NILSSProblem(prob_attractor, NILSS(nseg, nstep), g)
    res1 = DiffEqSensitivity.shadow_forward(nilss_prob1,Tsit5())

    Random.seed!(1234)
    nilss_prob2 = NILSSProblem(prob_attractor, NILSS(nseg, nstep), g, dg)
    res2 = DiffEqSensitivity.shadow_forward(nilss_prob2,Tsit5())

    @test res1[1] ≈ 1 atol=5e-2
    @test res2[1] ≈ 1 atol=5e-2
    @test res1 ≈ res2 atol=1e-10

    function G(p; dt=nilss_prob1.dtsave)
      _prob = remake(prob_attractor,p=p)
      _sol = solve(_prob,Tsit5(),saveat=dt,sensealg=NILSS(nseg, nstep), g=g)
      sum(getindex.(_sol.u,3))
    end

    Random.seed!(1234)
    dp1 = Zygote.gradient((p)->G(p),p)
    @test res1 ≈ dp1[1] atol=1e-10
  end
end

@testset "NILSAS" begin
  @testset "nilsas_min function" begin
    u0 = rand(3)
    M = 2
    nseg = 2
    numparams = 1
    quadcache = DiffEqSensitivity.QuadratureCache(u0, M, nseg, numparams)

    C = quadcache.C
    C[:,:,1] .= [
                1. 0.
                0. 1.]
    C[:,:,2] .= [
                4. 0.
                0. 1.]

    dwv = quadcache.dwv
    dwv[:,1] .= [1., 0.]
    dwv[:,2] .= [1., 4.]

    dwf = quadcache.dwf
    dwf[:,1] .= [1., 1.]
    dwf[:,2] .= [3., 1.]

    dvf = quadcache.dvf
    dvf[1] = 1.
    dvf[2] = 2.

    R = quadcache.R
    R[:,:,1] .= [
                Inf Inf
                Inf Inf]
    R[:,:,2] .= [
                1. 1.
                0. 2.]

    b = quadcache.b
    b[:,1] = [Inf, Inf]
    b[:,2] = [0., 1.]

    @test DiffEqSensitivity.nilsas_min(quadcache) ≈ [-1. 0.
                                                     -1. -1.]
  end
  @testset "Lorenz" begin
    function lorenz!(du,u,p,t)
      du[1] = p[1]*(u[2]-u[1])
      du[2] = u[1]*(p[2]-u[3]) - u[2]
      du[3] = u[1]*u[2] - p[3]*u[3]
      return nothing
    end

    u0_trans = rand(3)
    p = [10.0, 28.0, 8/3]

    # parameter passing to NILSAS
    M = 2
    nseg = 40
    nstep = 101

    tspan_transient = (0.0,30.0)
    prob_transient = ODEProblem(lorenz!,u0_trans,tspan_transient,p)
    sol_transient = solve(prob_transient, Tsit5())

    u0 = sol_transient.u[end]

    tspan_attractor = (0.0,40.0)
    prob_attractor = ODEProblem(lorenz!,u0,tspan_attractor,p)
    sol_attractor = solve(prob_attractor,Vern9(),abstol=1e-14,reltol=1e-14,saveat=0.01)

    g(u,p,t) = u[end]
    function dg(out,u,p,t,i)
      fill!(out, zero(eltype(u)))
      out[end] = -one(eltype(u))
    end

    lss_problem = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=10), g, dg)
    resfw = shadow_forward(lss_problem)

    @info resfw

    nilsas_prob = NILSASProblem(sol_attractor, NILSAS(nseg,nstep,M), g)
    res = shadow_adjoint(nilsas_prob, Tsit5())

    @info res

    @test resfw ≈ res atol=1e-1

    nilsas_prob = NILSASProblem(sol_attractor, NILSAS(nseg,nstep,M), g, dg)
    res = shadow_adjoint(nilsas_prob, Tsit5())

    @info res

    @test resfw ≈ res atol=1e-1
  end

  @testset "Lorenz parameter-dependent loss function" begin
    function lorenz!(du,u,p,t)
      du[1] = p[1]*(u[2]-u[1])
      du[2] = u[1]*(p[2]-u[3]) - u[2]
      du[3] = u[1]*u[2] - p[3]*u[3]
      return nothing
    end

    u0_trans = rand(3)
    p = [10.0, 28.0, 8/3]

    # parameter passing to NILSAS
    M = 2
    nseg = 100
    nstep = 101

    tspan_transient = (0.0,100.0)
    prob_transient = ODEProblem(lorenz!,u0_trans,tspan_transient,p)
    sol_transient = solve(prob_transient, Tsit5())

    u0 = sol_transient.u[end]

    tspan_attractor = (0.0,50.0)
    prob_attractor = ODEProblem(lorenz!,u0,tspan_attractor,p)
    sol_attractor = solve(prob_attractor,Vern9(),abstol=1e-14,reltol=1e-14,saveat=0.01)

    g(u,p,t) = u[end]^2/2 + sum(p)
    function dgu(out,u,p,t,i)
      fill!(out, zero(eltype(u)))
      out[end] = -u[end]
    end
    function dgp(out,u,p,t,i)
      fill!(out, -one(eltype(p)))
    end

    lss_problem = ForwardLSSProblem(sol_attractor, ForwardLSS(alpha=10), g, (dgu,dgp))
    resfw = shadow_forward(lss_problem)

    @info resfw

    nilsas_prob = NILSASProblem(sol_attractor, NILSAS(nseg,nstep,M), g)
    res = shadow_adjoint(nilsas_prob, Tsit5())

    @info res

    @test resfw ≈ res rtol=1e-1

    nilsas_prob = NILSASProblem(sol_attractor, NILSAS(nseg,nstep,M), g, (dgu,dgp))
    res = shadow_adjoint(nilsas_prob, Tsit5())

    @info res

    @test resfw ≈ res rtol=1e-1
  end
end


struct Dirichlet <: Distribution{Vector{Float64}} end

"""
    dirichlet(alpha::AbstractArray{U}) where {U<:Real}

Samples a `Vector{Float64}` value from a dirichlet
"""
const dirichlet = Dirichlet()

function logpdf(::Dirichlet, x::AbstractArray{T}, alpha::AbstractArray{U}) where {T <: Real, U <: Real}
    if !(isapprox(sum(x),1) & all(x .>= 0) & all(x .<= 1))
        -Inf
    elseif all(alpha .> 0.)
        Distributions.logpdf(Distributions.Dirichlet(alpha), x)
    else
        -Inf
    end
end

function logpdf_grad(::Dirichlet, x::AbstractArray{T}, alpha::AbstractArray{U}) where {T <: Real, U <: Real}
    if (isapprox(sum(x),1) & all(x .>= 0) & all(x .<= 1))
        deriv_x = sum((alpha.-1) ./ x)
        deriv_alpha = log.(x) .+ digamma(sum(alpha)) .- digamma.(alpha)
    else
        error("x has to be a simplex: $x")
    end
    Tuple(vcat([deriv_x, deriv_alpha]...))
end

function random(::Dirichlet, alpha::AbstractVector{U}) where {U <: Real}
    """ sample data from Dirichlet distribution with parameter vector alpha
    """
    rand(Distributions.Dirichlet(alpha))
end

is_discrete(::Dirichlet) = false
(::Dirichlet)(alpha) = random(Dirichlet(), alpha)

has_output_grad(::Dirichlet) = true
# has_argument_grads(::Dirichlet) = (true, true)

export dirichlet


using Test
using LinearAlgebra: norm
using NonNegLeastSquares: nonneg_lsq
using SparseArrays

#test specific
using Random
using PyCall
const pyopt = pyimport_conda("scipy.optimize", "scipy")

function test_case1()
    A = [ 0.53879488  0.65816267
          0.12873446  0.98669198
          0.24555042  0.00598804
          0.80491791  0.32793762 ]

    b = [0.888, 0.562, 0.255, 0.077]
    x = [0.15512102, 0.69328985] # approx solution from scipy
    return A, b, x
end

function test_case2()
    A = [ -0.24  -0.82   1.35   0.36   0.35
          -0.53  -0.20  -0.76   0.98  -0.54
           0.22   1.25  -1.60  -1.37  -1.94
          -0.51  -0.56  -0.08   0.96   0.46
           0.48  -2.25   0.38   0.06  -1.29 ]
    b = [-1.6, 0.19, 0.17, 0.31, -1.27]
    x = [2.2010416, 1.19009924, 0.0, 1.55001345, 0.0]
    return A, b, x
end

function test_case3() # non-float
    A = ones(Int, 4, 3)
    b = 2*ones(Int, 4)
    x = 2*ones(Int, 3)
    return A, b, x
end

function test_algorithm(fh::Function, ε::Real=1e-5)
    # Solve A*x = b for x, subject to x >=0
    A, b, x = test_case1()
    @test norm(fh(A,b) - x) < ε

    A, b, x = test_case2()
    @test norm(fh(A,b) - x) < ε

    # Test a bunch of random cases
    for i = 1:100
        m,n = rand(1:10),rand(1:10)
        A3 = randn(m,n)
        b3 = randn(m)
        x3,resid = pyopt.nnls(A3,b3)
        if resid > ε
            @test norm(fh(A3,b3) - x3) < ε
        else
            @test norm(A3*fh(A3,b3) - b3) < ε
        end
    end
end

nnls(A,b) = nonneg_lsq(A, b; alg=:nnls)
nnls_gram(A,b) = nonneg_lsq(A'*A, A'*b; alg=:nnls, gram=true)
fnnls(A,b) = nonneg_lsq(A, b; alg=:fnnls)
fnnls_gram(A,b) = nonneg_lsq(A'*A, A'*b; alg=:fnnls, gram=true)
pivot(A,b) = nonneg_lsq(A, b; alg=:pivot)
pivot_comb(A,b) = nonneg_lsq(A, b; alg=:pivot, variant=:comb)
pivot_cache(A,b) = nonneg_lsq(A, b; alg=:pivot, variant=:cache)

algs = [nnls, nnls_gram, fnnls, fnnls_gram, pivot, pivot_comb, pivot_cache]
errs = fill(1e-5, length(algs))

for (f, ε) in zip(algs, errs)
    print("testing ")
    @show f
    test_algorithm(f, ε)
    println("done")
end

#= non-float test fails, so revisit later
@testset "pivot_cache-non-float" begin
    A, b, x = test_case3()
    xi = pivot_cache(A, b)
    xf = pivot_cache(Float32.(A), Float32.(b))
    @test xi ≈ xf
end
=#

@testset "comb" begin
    A, b, x = test_case2()
    x0 = pivot_comb(A, b)
    P! = falses(size(A,2),1)
    x1 = nonneg_lsq(A, b; alg=:pivot, variant=:comb, P! = P!)
    @test x0 == x1
    @test P! != falses(size(A,2),1)
    @test all(x0[(!).(P!)] .== 0)
end

@testset "NNLS" begin include("nnls_test.jl") end
@testset "FNNLS" begin include("fnnls_test.jl") end
@testset "Pivot" begin include("pivot_test.jl") end
@testset "Sparse" begin include("sparse_test.jl") end


function compute_edge_faces_ring(faces)
    """
        compute_edge_face_ring - compute faces adjacent to each edge

          e2f = compute_edge_face_ring(faces);

          e2f(i,j) and e2f(j,i) are the number of the two faces adjacent to
          edge (i,j).

          Copyright (c) 2007 Gabriel Peyre
    """
    n = maximum(faces)
    m = size(faces,2)

    i = [faces[1,:] faces[2,:] faces[3,:]]
    j = [faces[2,:] faces[3,:] faces[1,:]]
    s = [1:m 1:m 1:m];

    # first without duplicate
    tmp = unique(i + (maximum(i) + 1)*j) # unique
    I = indexin(tmp, i + (maximum(i) + 1)*j)

    # remaining items
    J = setdiff(1:length(s), I);

    # flip the duplicates
    i1 = [i[I]; j[J]]
    j1 = [j[I]; i[J]]
    s  = [s[I]; s[J]]

    # remove doublons
    tmp = unique(i1 + (maximum(i1) + 1)*j1)
    I = indexin(tmp, i1 + (maximum(i1) + 1)*j1)
    i1 = i1[I]
    j1 = j1[I]
    s  = s[I]
    A = sparse(i1,j1,s,n,n)

    # add missing points
    I = findall(A'.!=0)
    I = I[A[I].==0]
    A[I]=-1

    return A
end;


# SPDX-License-Identifier: X11
# 2020-11-23
# Determinant (100pt)

a,b = parse.(Int, (split ∘ readline)())
c,d = parse.(Int, (split ∘ readline)())

println(a*d - b*c)


#=
Filename: aiyagari_compute_equilibrium.jl
Author: Victoria Gregory
Date: 8/30/2016

Draws the aggregate supply and demand curves for
the Aiyagari model.
=#

using QuantEcon
include("aiyagari_household.jl")
using Plots
pyplot()

# Firms' parameters
A = 1
N = 1
alpha = 0.33
beta = 0.96
delta = 0.05

"""
Compute wage rate given an interest rate, r
"""
function r_to_w(r::Float64)
    return A * (1 - alpha) * (A * alpha / (r + delta)) ^ (alpha / (1 - alpha))
end

"""
Inverse demand curve for capital. The interest rate
associated with a given demand for capital K.
"""
function rd(K::Float64)
    return A * alpha * (N / K) ^ (1 - alpha) - delta
end

"""
Map prices to the induced level of capital stock.

##### Arguments
- `am::Household` : Household instance for problem we want to solve
- `r::Float64` : interest rate

##### Returns
- The implied level of aggregate capital
"""
function prices_to_capital_stock(am::Household, r::Float64)

    # Set up problem
    w = r_to_w(r)
    setup_R!(am, r, w)
    aiyagari_ddp = DiscreteDP(am.R, am.Q, am.beta)

    # Compute the optimal policy
    results = solve(aiyagari_ddp, PFI)

    # Compute the stationary distribution
    stationary_probs = stationary_distributions(results.mc)[:, 1][1]

    # Return K
    return sum(am.s_vals[:, 1] .* stationary_probs)
end

# Create an instance of Household
z_chain = MarkovChain([0.67 0.33; 0.33 0.67], [0.5, 1.5])
am = Household(z_chain=z_chain, beta=beta, a_max=20.0)

# Create a grid of r values at which to compute demand and supply of capital
num_points = 20
r_vals = linspace(0.02, 1/beta - 1, num_points)

# Compute supply of capital
k_vals = Array(Float64, num_points, 1)
for i in 1:num_points
    k_vals[i] = prices_to_capital_stock(am, r_vals[i])
end

# Plot against demand for capital by firms
demand = [rd(k) for k in k_vals]
labels =  ["demand for capital"; "supply of capital"]
plot(k_vals, [demand r_vals], label=labels', lw=2, alpha=0.6)
plot!(xlabel="capital", ylabel="interest rate")


using DualNumbers, HyperDualNumbers, FinancialFFT, FinancialMonteCarlo, BenchmarkTools

A = 600.0;
N = 18;

method_cm = CarrMadanMethod(A, N);
S0 = 100.0;
# S0 = dual(100.0, 1.0);
K = 100.0;
# K = dual(100.0, 1.0);
# r = hyper(0.02, 1.0, 1.0, 0.0);
# r = dual(0.02, 1.0);
r = 0.02;
T = 1.0;
# T = dual(1.0, 1.0);
# d = hyper(0.0, 0.0, 1.0, 0.0);
d = 0.01
# d = 0.0;
# sigma = dual(0.2, 1.0);
sigma = hyper(0.2, 1.0, 1.0, 0.0);
# sigma = 0.2;
lam = 15.0;
mu1 = 0.03;
sigma1 = 0.02;
zero_rate = ZeroRate(r);

# Model = MertonProcess(sigma, lam, mu1, sigma1, Underlying(S0, d));
Model = BlackScholesProcess(sigma, Underlying(S0, d));

EUData = EuropeanOption(T, K)
Nsim = 100_000;
Nstep = 30;
mc = MonteCarloConfiguration(Nsim, Nstep, FinancialMonteCarlo.AntitheticMC());

@btime pricer(Model, zero_rate, method_cm, EUData);
method_lewis = LewisMethod(700.0, 200000);
@btime pricer(Model, zero_rate, method_lewis, EUData);
# @btime pricer(Model, zero_rate, mc, EUData);
method_cm_lewis = CarrMadanLewisMethod(A, N);
@btime pricer(Model, zero_rate, method_cm_lewis, EUData);

nothing


## This file provides a wrapper to Convex.jl for solving nonnegative
## least-squares problems. In general, the active-set methods seem
## to outperform Convex.jl, but this function can be used to directly
## compare the two approaches.

using Convex
using SCS
using ECOS

"""
x = convex_nnls(A, b, solver; kwargs...)

Solves the nonnegative least-squares problem using Convex.jl and the solver
specified by "solver".
"""
function convex_nnls(
	A::Matrix{Float64},
	B::Matrix{Float64};
	variant::Symbol = :SCS,
	kwargs...)

	# Primal optimization variables
	X = Variable(size(A,2),size(B,2))

	# declare problem
	problem  = minimize(sumsquares(B - (A*X)), [X >= 0])

	# solve problem
	if variant == :SCS
		solve!(problem,SCSSolver(kwargs))
	elseif variant == :ECOS
		solve!(problem,ECOSSolver(kwargs))
	else
		error("Specified solver :",variant," not recognized")
	end

	# check solution
	if problem.status != :Optimal
		warn("Solving for H problem status is :",problem.status)
	end

	# return solution
	return X.value
end


#by Lazaro Alonso
using CairoMakie
CairoMakie.activate!()
let
    x(t; v₀ = 270,θ = 60) = v₀*t*cos(θ*π/180)
    y(t; v₀ = 270,θ = 60, g = 9.8) = v₀*t*sin(θ*π/180) - g*t^2/2
    t = 0:0.5:50
    xcoord = x.(t)
    ycoord = y.(t)

    xnode = Node(xcoord[1:2])
    ynode = Node(ycoord[1:2])

    xnodePt = Node([xcoord[2]])
    ynodePt = Node([ycoord[2]])
    # first frame, initial plot
    fig, ax = lines(xnode, ynode, color = :black, linewidth = 2,
        figure = (resolution=(700,450),), axis = (xlabel = "x", ylabel = "y",))
    scatter!(xnodePt, ynodePt, color = :red, markersize = 15)
    # the animation is done by updating the nodes values
    record(fig,joinpath(@__DIR__, "output", "animLinePoint.mp4")) do io
        for i in 3:length(t)
            push!(xnode[], xcoord[i])
            push!(ynode[], ycoord[i])
            xnodePt[] = [xcoord[i]]
            ynodePt[] = [ycoord[i]]
            xnode[] = xnode[] # trigger all updates for the new frame
            autolimits!(ax)
            ax.title = "t = $(t[i])"
            ylims!(ax, -1000, 3000)
            recordframe!(io)  # record a new frame
        end
    end
end

using Pkg # HIDE
Pkg.status("CairoMakie") # HIDE


### A Pluto.jl notebook ###
# v0.11.4

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ e5e0a0da-d45c-11ea-1042-e9b5d0654d4f
md"Fix the value of `c` below to make it `c = a * b`"

# ╔═╡ 4dff4b5e-d461-11ea-29c8-d548fdb5f08b
md"Edit the equation below to calculate the number of pizzas to order using the variables above for **people**, **avg**, and **slices**:"

# ╔═╡ f907e46a-d471-11ea-07e5-f30e2aab3d08
md"""The diameter of a pizza is often stated on a menu so let's define a **formula** to calculate the area of a pizza given the diameter **d**.

We do this by writing a formula like this: `area(d) = pi * (d/2)^2`

Let's write that below:
"""

# ╔═╡ d9575e9c-d472-11ea-1eda-2d335d039f28
md"""Now we have a function called **area** that we can pass any diameter and it will return the area of a pizza (or circle), let's try that with the pizza from before with `area(2*r)` to get the area of the pizza:
"""

# ╔═╡ edb95b14-d473-11ea-3a5a-77382d31f941
md"""## Finding the best pizza deal

Let's see if a larger pizza is a better value by calculating the price per area.  There are 4 sizes: small, medium, large, extra large with the following prices:

Size     | Diameter (inches) | Price ($)
:------- | :---------------: | --------:
small    | 9  | 13.10
medium   | 13 | 20.95
large    | 15 | 24.90
XL       | 17 | 30.95

### 1. How many small pizzas is the same as one XL pizza?

Edit the expression below:
"""

# ╔═╡ 5b07b8fe-d475-11ea-01aa-6b88d6ed8a05
md"""### 2. Calculate the cost per area of each pizza:
"""

# ╔═╡ a42e4eb0-d474-11ea-316a-3d864451bc01
md"Which size of pizza is the best deal?  Write your answer below and assign it to the variable **best_value**."

# ╔═╡ cb419286-d4ff-11ea-1d7f-af5c8574b775
md"""### 3. Is this a good deal?

San Marinos has a special **\"Buy two medium pizzas and save \$5\"**.  Is this a better deal than buying a extra-large pizza?

Calculate the total cost of two medium pizzas deal (saving \$5):"""

# ╔═╡ 0d76d97c-d500-11ea-2433-e96c6fc43b05
md"Calculate the total area of two medium pizzas:"

# ╔═╡ 20a1e9cc-d500-11ea-3d9b-279c71bc20f1
md"Now calculate cost per area by taking the total cost of two medium pizzas and divide by the total area:"

# ╔═╡ 57f024ae-d500-11ea-1cc4-ed28348fdf93
md"""Is it a better deal to get two medium pizzas for \$5 off or to just buy an extra-large?"""

# ╔═╡ 180c8fdc-d503-11ea-04ca-bf2c07fd1c17
md"""### 4. Advanced Problem

A new worker at a pizza shop was getting paid for cutting pizza into pieces.  The pieces of pizza could be any size.  Calculate the maximum number of pieces the worker could make with two cuts of the pizza."""

# ╔═╡ 92b4a012-d503-11ea-15a2-1f3a446d3284
md"Now what about 3 cuts across the pizza?  What is the maximum number of pieces that can be made with **3 cuts**?"

# ╔═╡ 2eb9a560-d507-11ea-3b8b-9d06678fe131
md"Now, how many pieces can be made with **4 cuts**?"

# ╔═╡ d1e3dec0-d507-11ea-1213-d37a9325ee2f
md"Are you starting to see a pattern?  Can you figure out a formula for how many pieces of pizza can be made with \"n\" cuts?  Make a table and fill in the number of pieces for a number of cuts and see if you can find the pattern:

Cuts | Pieces
:--- | ------:
0    |   1
1    |   2
2    |   4
3    |
4    |
"

# ╔═╡ 97bfd13c-dcc2-11ea-0067-ad8c2c6517fc
md"To get an extra hint, figure out how many slices we can get from **5 cuts**:"

# ╔═╡ e0cb2822-dcc2-11ea-2c85-5748bfe526dc
md"Have you found the pattern? Write down the formula below:"

# ╔═╡ 03249876-d508-11ea-16bb-fd5afed37a1f
md"""##### Let's test your formula!"""

# ╔═╡ 14158eb0-d45c-11ea-088f-330e45412320
a = 2

# ╔═╡ 2ed4bb92-d45c-11ea-0b31-2d8e32ce7b44
b = 6

# ╔═╡ 03664f5c-d45c-11ea-21b6-91cd647a07aa
md"# Mathematics in Julia 🍕
This is an introduction to programming.  Let's get started!

## Let's make a calculator!

First let's do some simple math with setting **a = $a**, **b = $b** and **c = a * b**.  What will **c** equal?

Type in the cells (with the coloured background) below and press **`Shift-Enter`** or the click the right-arrow button (▶) to the right to execute the cell after changing the values."

# ╔═╡ 30f0f882-d45c-11ea-2adc-7d84ecf8a7a6
c = 10

# ╔═╡ 262b312a-d460-11ea-26c5-df30459effc5
people = 10

# ╔═╡ 2ea7f162-d460-11ea-0e8e-25340e2e64da
avg = 2.5

# ╔═╡ 3da812c6-d460-11ea-0170-79fbb6a4347c
slices = 8

# ╔═╡ a38cb92e-d45e-11ea-2959-05be909befb2
md"""### Now you have a calculator!

You did multiplication above.  Here's how you do other mathematical operations:

Operation | Type This
:------------ | :-------------:
add | +
subtract | -
multiply | *
divide | /
power | ^

### Pizza Slices

Let's try this out on a problem.  Let's say you want to order pizzas for $people people (**people = $people**) and each person wants $avg slices on average (**avg = $avg**).  A pizza has $slices slices per pizza (**slices = $slices**).  How many pizzas should you order (**pizzas = ?**)?  So we have the following

Meaning | Variable
:------ | :--------:
Number of people | people
Average number of slices each person eats | avg
Number of slices on a piece of pizza | slices

"""

# ╔═╡ 444e2fa4-d460-11ea-12aa-57e0576c2d66
pizzas = 1

# ╔═╡ 3c12f2b4-d471-11ea-2d37-539f061f7cf2
r = 6

# ╔═╡ d9c31dfa-d470-11ea-23b2-838975b71f7c
md"""## Writing your own math functions

The area of a pizza is ``A = \pi r^2``.  Lets try calculating the area of a pizza that has a radius of $r inches (**r = $6**).  Type **pi** to get the value of ``\pi`` and **r^2** to get the radius squared.
"""

# ╔═╡ 50f0f6d6-d471-11ea-304e-8f72e7ef9d7e
A =  r^2

# ╔═╡ cb36a9ee-d472-11ea-1835-bf7963137e18
area(d) = pi * (d / 2)^2

# ╔═╡ 04b010c0-d473-11ea-1767-136c7e26e122
A2 = area(r)

# ╔═╡ 637c26fa-d475-11ea-2c5b-2b0f4775b119
smalls_in_xl = 1

# ╔═╡ 3823d09e-d474-11ea-194e-59b5805f303b
small = 13.10 / area(9)

# ╔═╡ 76c11174-d474-11ea-29c5-81856d47cf74
medium = 20.95 / area(13)

# ╔═╡ 8b12d200-d474-11ea-3035-01eccf39f917
large = 24.90 / area(15)

# ╔═╡ 962e6b86-d474-11ea-11a6-a1d11e33ae42
xl = 30.95 / area(17)

# ╔═╡ 16ec3f32-d4ff-11ea-20e2-5bc6dd5db083
best_value = small

# ╔═╡ f147b6cc-d4ff-11ea-05ad-6f5b441e5d1b
two_medium_cost = 20.95 * 1 - 0

# ╔═╡ 19eb2a82-d500-11ea-3782-596adc689382
two_medium_area = 1 * area(13)

# ╔═╡ 70e85498-d500-11ea-35af-474574f5c011
two_medium_deal = 1

# ╔═╡ 6494e270-d503-11ea-38a7-df96e7f0a241
cuts2 = 1

# ╔═╡ a05aae8e-d506-11ea-190f-57e9ce53b8b9
cuts3 = 1

# ╔═╡ 5a8ede88-d507-11ea-30d9-c99a67243781
cuts4 = 1

# ╔═╡ bae0cb62-dcc2-11ea-0667-512e1c407d40
cuts5 = 1

# ╔═╡ f5f89724-d507-11ea-0a93-6d904f36bbe4
function pieces(n)
	return n
end

# ╔═╡ bd9f3d24-d509-11ea-165d-3d465a0b4542
md"""Move the slider to change the number of cuts:

$(@bind n html"<input type=range max=50>")"""

# ╔═╡ b8644fb0-daa6-11ea-1e94-9bf46e7b0fad
hint(text) = Markdown.MD(Markdown.Admonition("hint", "Hint", [text]));

# ╔═╡ 8700d986-d475-11ea-0d0e-790448cf92ba
let
	ans = (pi * (17 / 2)^2) / (pi * (9 / 2)^2)
	if smalls_in_xl == 1
		hint(md"""The diameter of the XL pizza is 17 inches while the diameter of the small pizza is 9 inches.  Use the **area()** function from before to find the area of each and divide them.""")
	elseif smalls_in_xl < ans - 4 * eps(ans)
		md"""Keep trying, your answer is too low."""
	elseif smalls_in_xl > ans + 4 * eps(ans)
		md"""Keep trying, your answer is too high."""
	else
		md"""**Great!** You got it right. Let's move on."""
	end
end

# ╔═╡ 2814a1d4-dcc0-11ea-3d42-f52765e478fe
hint(md"For each extra cut, start out with the solution for the previous number. When you add one extra cut, how many new slices do you get?")

# ╔═╡ 48647ab2-daa5-11ea-0494-ef87be7cbf7c
hint(md"A new cut will create the maximum number of _new slices_ if it intersects all previous cuts.")

# ╔═╡ 8cada086-daa5-11ea-220c-0f660938b604
if cuts5 == 5 + 4 + 3 + 2 + 1 + 1
	hint(md"To get the maximum number of pieces with 5 cuts it will be ``5 + 4 + 3 + 2 + 1``, plus 1 extra for the original pizza with 0 cuts. To find the formula of a sequence of numbers group them like so: ``5 + (4 + 1) + (3 + 2) = 3 * 5``.")
else
	md""
end

# ╔═╡ 4119d19e-dcbc-11ea-3ec8-271e88e1afca
almost(text) = Markdown.MD(Markdown.Admonition("warning", "Almost there!", [text]));

# ╔═╡ 921bba30-dcbc-11ea-13c3-87554722da8a
keep_working(text=md"The answer is not quite right.") = Markdown.MD(Markdown.Admonition("danger", "Keep working on it!", [text]));

# ╔═╡ 5a6d1a8e-dcbc-11ea-272a-6f769c8d309c
correct(text=md"Great! You got the right answer! Let's move on to the next section.") = Markdown.MD(Markdown.Admonition("correct", "Got it!", [text]));

# ╔═╡ 33b1975c-d45c-11ea-035f-ab76e46a31ed
if c == a * b
	correct(md"""**Great!** The value of c = $c.  So you now have a simple computer!

	Now go back above and change the value of **a = $a** to **a = $(a + 3)** and press **`Shift-Enter`**.
	What is the new value of **c**?  Notice how all the values get updated in this notebook!
	""")
else
	keep_working()
end

# ╔═╡ f26d50da-d46b-11ea-0c2d-77ca13532b3d
if pizzas == people * avg / slices
	almost(md"Yes that is right! But we should round $pizzas up to an integer, otherwise the restaurant will be confused.

Try `ceil(...)`!")
elseif pizzas == ceil(people * avg / slices)
	correct(md"Yes that is right, that's a lot of pizza! Excellent, you figured out we need to round up the number of pizzas!")
else
	keep_working()
end

# ╔═╡ 5c4a5f22-d471-11ea-260f-9338d8bfa2d6
if A != pi * r^2
	keep_working(md"Let's fix the above cell before we move on!  Find the formula to calculate the area using **pi** and **r**.")
else
	correct(md"""**Great!**  You figured it out.  Keep going.""")
end

# ╔═╡ a07e5c3e-d476-11ea-308c-718f8f128334
if A2 != pi * (12 / 2)^2
	hint(md"Keep trying to get the right answer.
	**Hint**: you need to multiply the radius by 2 to convert it into the diameter.")
else
	correct()
end

# ╔═╡ 1ba2c208-d4ff-11ea-0a8e-e75bf7e1c3e6
if !isapprox(best_value, xl)
	hint(md"No need to copy these digits yourself - what should we assign to **best_value**?")
else
	correct()
end

# ╔═╡ 6ae748b2-d503-11ea-1c51-6b2df24fd212
if cuts2 != 4
	hint(md"The cuts must go all the way across the pizza!")
else
	correct(md"Awesome!")
end

# ╔═╡ a679bddc-d506-11ea-143a-6d4dcd70e918
if cuts3 == 6
	almost(md"""Close but not quite. Who said that pizza slices need to look like pizza slices?""")
elseif cuts3 == 7
	correct(md"You got it right.  Now for something harder...")
else
	hint(md"Try drawing it out on a piece of paper.")
end

# ╔═╡ 5df7eefc-d507-11ea-0d1f-45b224a04774
if cuts4 == 11
	correct(md"That was a tough question.  How did you figure it out?  You tried hard.")
elseif cuts4 < 10
	hint(md"Draw it out on a piece of paper.  You can make more pieces with 4 cuts.")
elseif cuts4 < 11
	hint(md"Getting close but you can make more pieces with 4 cuts.")
else
	hint(md"That is too high. Only straight lines!")
end

# ╔═╡ e80986c6-d509-11ea-12e3-f79a54b5ab31
if pieces(n) ==  n * (n + 1) / 2 + 1
	md"""_Testing..._

	**For $n cuts, you predict $(pieces(n)) pieces.**

	$(correct(md"Well done!"))"""
else
	md"""_Testing..._

	**For $n cuts, you predict $(pieces(n)) pieces.**

	$(keep_working(md"The answer should be $(Int(n*(n+1)/2+1))."))"""
end

# ╔═╡ Cell order:
# ╟─03664f5c-d45c-11ea-21b6-91cd647a07aa
# ╠═14158eb0-d45c-11ea-088f-330e45412320
# ╠═2ed4bb92-d45c-11ea-0b31-2d8e32ce7b44
# ╟─e5e0a0da-d45c-11ea-1042-e9b5d0654d4f
# ╠═30f0f882-d45c-11ea-2adc-7d84ecf8a7a6
# ╟─33b1975c-d45c-11ea-035f-ab76e46a31ed
# ╟─a38cb92e-d45e-11ea-2959-05be909befb2
# ╠═262b312a-d460-11ea-26c5-df30459effc5
# ╠═2ea7f162-d460-11ea-0e8e-25340e2e64da
# ╠═3da812c6-d460-11ea-0170-79fbb6a4347c
# ╟─4dff4b5e-d461-11ea-29c8-d548fdb5f08b
# ╠═444e2fa4-d460-11ea-12aa-57e0576c2d66
# ╟─f26d50da-d46b-11ea-0c2d-77ca13532b3d
# ╟─d9c31dfa-d470-11ea-23b2-838975b71f7c
# ╠═3c12f2b4-d471-11ea-2d37-539f061f7cf2
# ╠═50f0f6d6-d471-11ea-304e-8f72e7ef9d7e
# ╟─5c4a5f22-d471-11ea-260f-9338d8bfa2d6
# ╟─f907e46a-d471-11ea-07e5-f30e2aab3d08
# ╠═cb36a9ee-d472-11ea-1835-bf7963137e18
# ╟─d9575e9c-d472-11ea-1eda-2d335d039f28
# ╠═04b010c0-d473-11ea-1767-136c7e26e122
# ╟─a07e5c3e-d476-11ea-308c-718f8f128334
# ╟─edb95b14-d473-11ea-3a5a-77382d31f941
# ╠═637c26fa-d475-11ea-2c5b-2b0f4775b119
# ╟─8700d986-d475-11ea-0d0e-790448cf92ba
# ╟─5b07b8fe-d475-11ea-01aa-6b88d6ed8a05
# ╠═3823d09e-d474-11ea-194e-59b5805f303b
# ╠═76c11174-d474-11ea-29c5-81856d47cf74
# ╠═8b12d200-d474-11ea-3035-01eccf39f917
# ╠═962e6b86-d474-11ea-11a6-a1d11e33ae42
# ╟─a42e4eb0-d474-11ea-316a-3d864451bc01
# ╠═16ec3f32-d4ff-11ea-20e2-5bc6dd5db083
# ╟─1ba2c208-d4ff-11ea-0a8e-e75bf7e1c3e6
# ╟─cb419286-d4ff-11ea-1d7f-af5c8574b775
# ╠═f147b6cc-d4ff-11ea-05ad-6f5b441e5d1b
# ╟─0d76d97c-d500-11ea-2433-e96c6fc43b05
# ╠═19eb2a82-d500-11ea-3782-596adc689382
# ╟─20a1e9cc-d500-11ea-3d9b-279c71bc20f1
# ╠═70e85498-d500-11ea-35af-474574f5c011
# ╟─57f024ae-d500-11ea-1cc4-ed28348fdf93
# ╟─180c8fdc-d503-11ea-04ca-bf2c07fd1c17
# ╠═6494e270-d503-11ea-38a7-df96e7f0a241
# ╟─6ae748b2-d503-11ea-1c51-6b2df24fd212
# ╟─92b4a012-d503-11ea-15a2-1f3a446d3284
# ╠═a05aae8e-d506-11ea-190f-57e9ce53b8b9
# ╟─a679bddc-d506-11ea-143a-6d4dcd70e918
# ╟─2eb9a560-d507-11ea-3b8b-9d06678fe131
# ╠═5a8ede88-d507-11ea-30d9-c99a67243781
# ╟─5df7eefc-d507-11ea-0d1f-45b224a04774
# ╟─d1e3dec0-d507-11ea-1213-d37a9325ee2f
# ╟─2814a1d4-dcc0-11ea-3d42-f52765e478fe
# ╟─48647ab2-daa5-11ea-0494-ef87be7cbf7c
# ╟─97bfd13c-dcc2-11ea-0067-ad8c2c6517fc
# ╠═bae0cb62-dcc2-11ea-0667-512e1c407d40
# ╟─8cada086-daa5-11ea-220c-0f660938b604
# ╟─e0cb2822-dcc2-11ea-2c85-5748bfe526dc
# ╠═f5f89724-d507-11ea-0a93-6d904f36bbe4
# ╟─03249876-d508-11ea-16bb-fd5afed37a1f
# ╟─bd9f3d24-d509-11ea-165d-3d465a0b4542
# ╟─e80986c6-d509-11ea-12e3-f79a54b5ab31
# ╟─b8644fb0-daa6-11ea-1e94-9bf46e7b0fad
# ╟─4119d19e-dcbc-11ea-3ec8-271e88e1afca
# ╟─921bba30-dcbc-11ea-13c3-87554722da8a
# ╟─5a6d1a8e-dcbc-11ea-272a-6f769c8d309c


using StochasticDiffEq, Zygote
using DiffEqSensitivity, Test, ForwardDiff

abstol = 1e-12
reltol = 1e-12
savingtimes = 0.5

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

  function dW!(du, u, p, t)
    du[1] = 0.1u[1]
    du[2] = 0.1u[2]
  end

  u0 = [1.0, 1.0]
  tspan = (0.0, 10.0)
  p = [2.2, 1.0, 2.0, 0.4]
  prob_sde = SDEProblem(dt!, dW!, u0, tspan, p)

  condition(u, t, integrator) = integrator.t > 9.0 #some condition
  function affect!(integrator)
    #println("Callback")  #some callback
  end
  cb = DiscreteCallback(condition, affect!, save_positions=(false, false))

  function predict_sde(p)
    return Array(solve(prob_sde, EM(), p=p, saveat=savingtimes, sensealg=ForwardDiffSensitivity(), dt=0.001, callback=cb))
  end

  loss_sde(p) = sum(abs2, x - 1 for x in predict_sde(p))

  loss_sde(p)
  @time dp = gradient(p) do p
    loss_sde(p)
  end

  @test !iszero(dp[1])
end

@testset "SDEs" begin
  println("SDEs")
  test_SDE_callbacks()
end