# breusch_godfrey.jl
# Breusch-Godfrey test for autocorrelation
#
# Copyright (C) 2017   Benjamin Born
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

export BreuschGodfreyTest

struct BreuschGodfreyTest <: HypothesisTest
    n::Int              # number of observations
    lag::Int            # number of lags in test statistic
    BG::Float64         # test statistic
end

"""
    BreuschGodfreyTest(X, e, lag, start0 = true)

Compute the Breusch-Godfrey test for serial correlation in the residuals
of a regression model.

`X` is the matrix of regressors from the original model and `e` the vector of residuals.
`lag` determines the number of lagged residuals included in the auxiliary regression.
Set `start0` to specify how the starting values for the lagged residuals are handled.
`start0 = true` (default) sets them to zero (as in Godfrey, 1978); `start0 = false`
uses the first `lag` residuals as starting values, i.e. shortening the sample by `lag`.

# External links

  * [Breusch-Godfrey test on Wikipedia](https://en.wikipedia.org/wiki/Breusch–Godfrey_test)
"""
function BreuschGodfreyTest(xmat::AbstractArray{T}, e::AbstractVector{T},
                            lag::Int, start0::Bool=true) where T<:Real
    n = size(e,1)
    elag = zeros(Float64,n,lag)
    for ii = 1:lag  # construct lagged residuals
        elag[ii+1:end,ii] = e[1:end-ii]
    end

    offset = start0 ? 0 : lag

    regmat = [xmat[offset+1:end,:] elag[offset+1:end,:]]
    regcoeff = regmat\e[offset+1:end]
    resid = e[offset+1:end] - regmat*regcoeff

    rsq = 1 - dot(resid,resid)/dot(e[offset+1:end],e[offset+1:end]) # uncentered R^2
    BG = (n-offset)*rsq
    BreuschGodfreyTest(n-offset,lag,BG)
end

testname(::BreuschGodfreyTest) = "Breusch-Godfrey autocorrelation test"
population_param_of_interest(x::BreuschGodfreyTest) =
    ("coefficients on lagged residuals up to lag p", "all zero", NaN)
default_tail(test::BreuschGodfreyTest) = :right

function show_params(io::IO, x::BreuschGodfreyTest, ident)
    println(io, ident, "number of observations:         ", x.n)
    println(io, ident, "number of lags:                 ", x.lag)
    println(io, ident, "T*R^2 statistic:                ", x.BG)
end

pvalue(x::BreuschGodfreyTest) = pvalue(Chisq(x.lag), x.BG; tail=:right)


# # 2D forced-dissipative turbulence
#
#md # This example can be run online via [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/generated/twodnavierstokes_stochasticforcing.ipynb).
#md # Also, it can be viewed as a Jupyter notebook via [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/generated/twodnavierstokes_stochasticforcing.ipynb).
#
# A simulation of forced-dissipative two-dimensional turbulence. We solve the
# two-dimensional vorticity equation with stochastic excitation and dissipation in
# the form of linear drag and hyperviscosity.

using FourierFlows, Printf, Plots

using FourierFlows: parsevalsum
using Random: seed!
using FFTW: irfft

import GeophysicalFlows.TwoDNavierStokes
import GeophysicalFlows.TwoDNavierStokes: energy, enstrophy

# ## Choosing a device: CPU or GPU

dev = CPU()    # Device (CPU/GPU)
nothing # hide

# ## Numerical, domain, and simulation parameters
#
# First, we pick some numerical and physical parameters for our model.

 n, L  = 256, 2π             # grid resolution and domain length
 ν, nν = 2e-7, 2             # hyperviscosity coefficient and hyperviscosity order
 μ, nμ = 1e-1, 0             # linear drag coefficient
    dt = 0.005               # timestep
nsteps = 4000                # total number of steps
 nsubs = 20                  # number of steps between each plot
nothing # hide

# ## Forcing

# We force the vorticity equation with stochastic excitation that is delta-correlated
# in time and while spatially homogeneously and isotropically correlated. The forcing
# has a spectrum with power in a ring in wavenumber space of radious $k_f$ and
# width $\delta k_f$, and it injects energy per unit area and per unit time equal
# to $\varepsilon$.

forcing_wavenumber = 14.0    # the central forcing wavenumber for a spectrum that is a ring in wavenumber space
forcing_bandwidth  = 1.5     # the width of the forcing spectrum
ε = 0.1                      # energy input rate by the forcing

grid = TwoDGrid(dev, n, L)

K = @. sqrt(grid.Krsq)
forcing_spectrum = @. exp(-(K - forcing_wavenumber)^2 / (2 * forcing_bandwidth^2))
forcing_spectrum[K .< ( 2 * 2π/L)] .= 0 # no power at low wavenumbers
forcing_spectrum[K .> (20 * 2π/L)] .= 0 # no power at high wavenumbers
ε0 = parsevalsum(forcing_spectrum .* grid.invKrsq / 2, grid) / (grid.Lx * grid.Ly)
@. forcing_spectrum *= ε/ε0             # normalize forcing to inject energy at rate ε

seed!(1234)
nothing # hide

# Next we construct function `calcF!` that computes a forcing realization every timestep
function calcF!(Fh, sol, t, clock, vars, params, grid)
  ξ = ArrayType(dev)(exp.(2π * im * rand(eltype(grid), size(sol))) / sqrt(clock.dt))
  ξ[1, 1] = 0
  @. Fh = ξ * sqrt(forcing_spectrum)
  nothing
end
nothing # hide

# ## Problem setup
# We initialize a `Problem` by providing a set of keyword arguments. The
# `stepper` keyword defines the time-stepper to be used.
prob = TwoDNavierStokes.Problem(dev; nx=n, Lx=L, ν=ν, nν=nν, μ=μ, nμ=nμ, dt=dt, stepper="ETDRK4",
                                calcF=calcF!, stochastic=true)
nothing # hide

# Define some shortcuts for convenience.
sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid

x, y = grid.x, grid.y
nothing # hide

# First let's see how a forcing realization looks like. Function `calcF!()` computes
# the forcing in Fourier space and saves it into variable `vars.Fh`, so we first need to
# go back to physical space.
calcF!(vars.Fh, sol, 0.0, clock, vars, params, grid)

heatmap(x, y, irfft(vars.Fh, grid.nx),
     aspectratio = 1,
               c = :balance,
            clim = (-200, 200),
           xlims = (-L/2, L/2),
           ylims = (-L/2, L/2),
          xticks = -3:3,
          yticks = -3:3,
          xlabel = "x",
          ylabel = "y",
           title = "a forcing realization",
      framestyle = :box)

# ## Setting initial conditions

# Our initial condition is a fluid at rest.
TwoDNavierStokes.set_zeta!(prob, zeros(grid.nx, grid.ny))

# ## Diagnostics

# Create Diagnostics; the diagnostics are aimed to probe the energy budget.
E  = Diagnostic(energy,                prob, nsteps=nsteps) # energy
Z  = Diagnostic(enstrophy,             prob, nsteps=nsteps) # enstrophy
diags = [E, Z] # a list of Diagnostics passed to `stepforward!` will  be updated every timestep.
nothing # hide

# ## Visualizing the simulation

# We initialize a plot with the vorticity field and the time-series of
# energy and enstrophy diagnostics. To plot energy and enstrophy on the same
# axes we scale enstrophy with $k_f^2$.

p1 = heatmap(x, y, vars.zeta,
         aspectratio = 1,
                   c = :balance,
                clim = (-40, 40),
               xlims = (-L/2, L/2),
               ylims = (-L/2, L/2),
              xticks = -3:3,
              yticks = -3:3,
              xlabel = "x",
              ylabel = "y",
               title = "vorticity, t="*@sprintf("%.2f", clock.t),
          framestyle = :box)

p2 = plot(2, # this means "a plot with two series"
               label = ["energy E(t)" "enstrophy Z(t) / k_f²"],
              legend = :right,
           linewidth = 2,
               alpha = 0.7,
              xlabel = "μ t",
               xlims = (0, 1.1 * μ * nsteps * dt),
               ylims = (0, 0.55))

l = @layout Plots.grid(1, 2)
p = plot(p1, p2, layout = l, size = (900, 420))

# ## Time-stepping the `Problem` forward

# Finally, we time-step the `Problem` forward in time.

startwalltime = time()
anim = @animate for j = 0:Int(nsteps/nsubs)

  log = @sprintf("step: %04d, t: %d, E: %.4f, Z: %.4f, walltime: %.2f min",
      clock.step, clock.t, E.data[E.i], Z.data[Z.i], (time()-startwalltime)/60)

  if j%(1000/nsubs)==0; println(log) end

  p[1][1][:z] = vars.zeta
  p[1][:title] = "vorticity, μ t = "*@sprintf("%.2f", μ * clock.t)
  push!(p[2][1], μ * E.t[E.i], E.data[E.i])
  push!(p[2][2], μ * Z.t[Z.i], Z.data[Z.i] / forcing_wavenumber^2)

  stepforward!(prob, diags, nsubs)
  TwoDNavierStokes.updatevars!(prob)

end

mp4(anim, "twodturb_forced.mp4", fps=18)


using MathMLRepr
using Mxma

#m = MExpr(:(f(x)=x^2+1))
#m = MExpr(:(a=1; b=x^2+2x+1))
#r = mcall1(m)
#println(r)

# Julia expressions => mstr
#mcall(:(b=x^2+2x+1))
#m1 = MExpr("sprint(b)\$")
#m1 = MExpr("sprint(b);")
#m1 = MExpr("a: 1\$ c: 2\$ print(b)\$")
#m1 = MExpr("sprint(b)")
#m1 = MExpr("a: matrix([1,2],[3,4])")
#mcall(MExpr(:(A = [1 2; 3 4])))
#mcall(MExpr(:(A = [1/2, 2/3, 3/4, 4/5])))
#mcall(MExpr(:(A = 34//87)))
#m1 = MExpr("sprint(A)\$")
#r = mcall(m1)
#println(r)

# Julia types => mstr
#A = rand(1:9, 2, 4)
#A = [1//2, 3//4, 5//6]
#A = rand(1:9, 3, 3); A = convert.(Rational, A)
#A = [:π :(x^2); :(x+1) :(2x)]

#s = @mxset(A)
# Some examples
#s = @mxc f = x^2+2x+1
#println(s)
#r = @mxc factor(f)
#println(r)
#r = @mxc expand((x+1)*(x+2))
#println(r)
#A = rand(1:9, 2, 4)
#r = @mxset A
#println(r)
#r = @mxc 2*[1,2,3,4]
#println(r)

# latex output
#r = @mxc f = x^2 + 3x + 2
#r = @mxc factor(f)
#io = IOBuffer()
#show(io, MIME("text/mathml+xml"), r)
#s = String(take!(io))
#println(s)

# Issue
#@mxc begin a=1;b=2 end
#r = @mx b=[1/2 2/3; 3/4 4/5]
#r = @mx b=[1 2; 3 4]
#r = @mx "cauchy_matrix([a,b,c])"
#io = IOBuffer()
#show(io, MIME("text/mathml+xml"), r)
#s = String(take!(io))
#println(s)
#println(typeof(r))
#println(length(Base.Multimedia.displays))
#display(r);println()
#display([r, r])
#display("text/plain", r); println()

# convert MExpr to LATEX
s = @mx fex=t^(1/2)*e^(-a*t/4)
t = convert(LATEX, s)
println(s)
println(t)


function BFS(G::DictGraph, start::Int)::SortedDict
    bsfDict = SortedDict(i => [-1, typemax(Int), -1] for i in keys(G.relation))
    bsfDict[start] = [0, 0, -1]

    𝑄 = Array{Int, 1}()

    push!(𝑄, start)

    while !isempty(𝑄)
        𝑢 = pop!(𝑄)
        for 𝑣 in G[𝑢]
            if bsfDict[𝑣[1]][1] == -1
                bsfDict[𝑣[1]][1] = 0
                bsfDict[𝑣[1]][2] = bsfDict[𝑢][2] + 1
                bsfDict[𝑣[1]][3] = 𝑢
                push!(𝑄, 𝑣.u)
            end
        end
        bsfDict[𝑢][1] = 1
    end

    return bsfDict
end

function BFSv2(G::DictGraph, start::Int)::SortedDict
    bsfDict = SortedDict(i => [-1, typemax(Int), -1] for i in keys(G.relation))
    bsfDict[start] = [0, 0, -1]

    𝑄 = Array{Int, 1}()

    push!(𝑄, start)

    while !isempty(𝑄)                                       #while the stack is not empty
        𝑢 = pop!(𝑄)

        for 𝑣 in G[𝑢]                              #for each vertex v that belongs to G.Adj[u]
            if bsfDict[𝑣[1]][2] == typemax(Int)
                bsfDict[𝑣[1]][1] = 0
                bsfDict[𝑣[1]][2] = bsfDict[𝑢][2] + 1
                bsfDict[𝑣[1]][3] = 𝑢
                push!(𝑄, 𝑣[1])
            end
        end

        bsfDict[𝑢][1] = 1
    end
end


"""
    filmpressure!(pressure, height, γ, θ, n, m, hmin, hcrit)

Calculation of the capillary pressure which is given by `` p = - γ∇²h+ Π(h) ``.

# Arguments

- `pressure :: Array{Number,2}`: Array that store the result of the compuation
- `height :: Array{Number,2}`: Height field ``h(\\mathbf{x},t)``
- `γ <: Number`: Forcing strenght due to surface tension
- `θ <: Number`: Equilibrium contact angle
- `n :: Int`: Larger power law exponent for `` Π(h) ``
- `m :: Int`: Smaller power law exponent for `` Π(h) ``
- `hmin <: Number`: Parameter of `` Π(h) ``, in fact `` Π(hmin) = 0 ``
- `hcrit <: Number`: Numerical stabilizer for case `` h(\\mathbf{x},t) \\ll hmin ``

# Mathematics

The capillary pressure ``p_{\\text{cap}}`` is the centeral angle to match our model with the thin film equation.
It consists of two parts, first being the laplace pressure `` \\nabla^2 h `` and second being the derivative of the disjoining pontential `` \\Pi(h) ``

`` p_{\\text{cap}} = -\\gamma \\nabla^2 h + \\Pi(h). ``

For the laplacian term we use the same nine point discretization as in `Swlabe.∇²f!`.
`` \\Pi(h) `` on the other hand is given by

`` \\Pi(h) = \\kappa(\\theta)f(h), ``

where `` \\kappa(\\theta) `` is simply a measure for the **Hamaker constant** and given as

`` \\kappa(\\theta) = \\gamma(1- \\cos(\\theta))\\frac{(n-1)(m-1)}{(n-m)h_{\\text{min}}}.``

For `` f(h) `` one can use various forms, a very common however is the power law given by

`` f(h) = \\bigg[\\bigg(\\frac{h_{\\text{min}}}{h}\\bigg)^n - \\bigg(\\frac{h_{\\text{min}}}{h}\\bigg)^m\\bigg]. ``

# Examples

```jldoctest
julia> using Swalbe, Test

julia> h = reshape(collect(1.0:25.0),5,5) # A dummy height field
5×5 Matrix{Float64}:
 1.0   6.0  11.0  16.0  21.0
 2.0   7.0  12.0  17.0  22.0
 3.0   8.0  13.0  18.0  23.0
 4.0   9.0  14.0  19.0  24.0
 5.0  10.0  15.0  20.0  25.0

julia> pressure = zeros(5,5); θ = 0.0; # Fully wetting substrate

julia> Swalbe.filmpressure!(pressure, h, θ) # default γ = 0.01

julia> result = [30.0 5.0 5.0 5.0 -20;
                 25.0 0.0 0.0 0.0 -25.0;
                 25.0 0.0 0.0 0.0 -25.0;
                 25.0 0.0 0.0 0.0 -25.0;
                 20.0 -5.0 -5.0 -5.0 -30.0];

julia> for i in eachindex(result)
           @test result[i] .≈ -100 .* pressure[i] atol=1e-12
       end
```

# References

- [Peschka et al.](https://www.pnas.org/content/116/19/9275)
- [Craster and Matar](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.81.1131)
- [Derjaguin and Churaev](https://www.sciencedirect.com/science/article/abs/pii/0021979778900565)

"""
function filmpressure!(output, f, γ, θ, n, m, hmin, hcrit)
    # Straight elements j+1, i+1, i-1, j-1
    hip = circshift(f, (1,0))
    hjp = circshift(f, (0,1))
    him = circshift(f, (-1,0))
    hjm = circshift(f, (0,-1))
    # Diagonal elements
    hipjp = circshift(f, (1,1))
    himjp = circshift(f, (-1,1))
    himjm = circshift(f, (-1,-1))
    hipjm = circshift(f, (1,-1))
    # Disjoining pressure part
    κ = (1 .- cospi.(θ)) .* (n - 1) .* (m - 1) ./ ((n - m) * hmin)

    output .= -γ .* ((2/3 .* (hjp .+ hip .+ him .+ hjm)
                   .+ 1/6 .* (hipjp .+ himjp .+ himjm .+ hipjm)
                   .- 10/3 .* f) .+ κ .* (power_broad.(hmin./(f .+ hcrit), n)
                                       .- power_broad.(hmin./(f .+ hcrit), m)))
    return nothing
end

function filmpressure!(output, f, dgrad, γ, θ, n, m, hmin, hcrit)
    hip, hjp, him, hjm, hipjp, himjp, himjm, hipjm = viewneighbors(dgrad)
    # Straight elements j+1, i+1, i-1, j-1
    circshift!(hip, f, (1,0))
    circshift!(hjp, f, (0,1))
    circshift!(him, f, (-1,0))
    circshift!(hjm, f, (0,-1))
    # Diagonal elements
    circshift!(hipjp, f, (1,1))
    circshift!(himjp, f, (-1,1))
    circshift!(himjm, f, (-1,-1))
    circshift!(hipjm, f, (1,-1))
    #=
    Disjoining pressure part:
    1. Constant part due to angle, n, m, hmin
    2. Part due to the powerlaw
    =#
    output .= -γ .* ((1 .- cospi.(θ)) .* (n - 1) .* (m - 1) ./ ((n - m) * hmin)
                 .* (power_broad.(hmin./(f .+ hcrit), n)
                  .- power_broad.(hmin./(f .+ hcrit), m)) )

    output .-= γ .* (2/3 .* (hjp .+ hip .+ him .+ hjm)
                   .+ 1/6 .* (hipjp .+ himjp .+ himjm .+ hipjm)
                   .- 10/3 .* f)
    return nothing
end
# Standard usage parameters
function filmpressure!(output, f, θ)
    # Straight elements j+1, i+1, i-1, j-1
    hip = circshift(f, (1,0))
    hjp = circshift(f, (0,1))
    him = circshift(f, (-1,0))
    hjm = circshift(f, (0,-1))
    # Diagonal elements
    hipjp = circshift(f, (1,1))
    himjp = circshift(f, (-1,1))
    himjm = circshift(f, (-1,-1))
    hipjm = circshift(f, (1,-1))
    # Disjoining pressure part
    κ = (1 .- cospi.(θ)) .* 16/0.6

    output .= -0.01 .* ((2/3 .* (hjp .+ hip .+ him .+ hjm)
                   .+ 1/6 .* (hipjp .+ himjp .+ himjm .+ hipjm)
                   .- 10/3 .* f) .+ κ .* (power_broad.(0.1./(f .+ 0.05), 9)
                                       .- power_broad.(0.1./(f .+ 0.05), 3)))
    return nothing
end
# One dim implementation
function filmpressure!(output::Vector, f, dgrad, γ, θ, n, m, hmin, hcrit)
    hip, him = viewneighbors_1D(dgrad)
    # Straight elements j+1, i+1, i-1, j-1
    circshift!(hip, f, 1)
    circshift!(him, f, -1)
    #=
    Disjoining pressure part:
    1. Constant part due to angle, n, m, hmin
    2. Part due to the powerlaw
    =#
    output .= -γ .* ((1 .- cospi.(θ)) .* (n - 1) .* (m - 1) ./ ((n - m) * hmin)
                 .* (power_broad.(hmin./(f .+ hcrit), n)
                  .- power_broad.(hmin./(f .+ hcrit), m)) )

    output .-= γ .* (hip .- 2 .* f .+ him)
    return nothing
end

function filmpressure!(output::Vector, f, dgrad, rho, γ, θ, n, m, hmin, hcrit; Gamma=0.0)
    hip, him = viewneighbors_1D(dgrad)
    # Straight elements j+1, i+1, i-1, j-1
    circshift!(hip, f, 1)
    circshift!(him, f, -1)
    #=
    Disjoining pressure part:
    1. Constant part due to angle, n, m, hmin
    2. Part due to the powerlaw
    =#
    output .= - (γ .+ Gamma .* rho) .* ((1 .- cospi.(θ)) .* (n - 1) .* (m - 1) ./ ((n - m) * hmin)
                 .* (power_broad.(hmin./(f .+ hcrit), n)
                  .- power_broad.(hmin./(f .+ hcrit), m)) )

    output .-= (γ .+ Gamma .* rho) .* (hip .- 2 .* f .+ him)
    return nothing
end

"""
    power_broad(arg, n)

Computes `arg` to the power `n`.

Actually this is useful because the `^` operator is much slower.
Same thing I learned about the `pow` function in **C**, * yes it does what you want, but it is slow as fuck *.

# Examples
```jldoctest
julia> using Swalbe, Test

julia> Swalbe.power_broad(3, 3)
27

julia> Swalbe.power_broad.([2.0 5.0 6.0], 2) # Use the broadcasting operator `.`
1×3 Matrix{Float64}:
 4.0  25.0  36.0

```

See also: [`filmpressure`](@ref)
"""
function power_broad(arg::Float64, n::Int)
    temp = 1.0
    for i = 1:n
        temp *= arg
    end
    return temp
end

function power_broad(arg::Float32, n::Int)
    temp = 1.0f0
    for i = 1:n
        temp *= arg
    end
    return temp
end

function power_broad(arg::Int, n::Int)
    temp = 1
    for i = 1:n
        temp *= arg
    end
    return temp
end


#!/usr/bin/env julia
# -*- coding: utf-8 -*-
# File   : sphero_cylinder_approx.jl
# License: MIT
# Author : Andrei Leonard Nicusan <a.l.nicusan@bham.ac.uk>
# Date   : 21.05.2021

using GLMakie
using Polyhedra
using ColorSchemes
using LinearAlgebra

import CDDLib
lib = CDDLib.Library()

# Return points on a sphere, generated with the method of Markus Deserno from
# https://www.cmu.edu/biolphys/deserno/pdf/sphere_equi.pdf
function half_sphere(;centre = [0. 0. 0.], radius = 1., n = 64)
    a = 4π * radius^2 / 2n
    d = √a

    mθ = floor(π / d)

    dθ = π / mθ
    dϕ = a / dθ

    points = zeros(n, 3)

    # Half sphere without middle circumference
    nc = 0

    for m in 0:mθ / 2 - 2
        θ = π * (m + 0.5) / mθ
        mϕ = floor(2π * sin(θ) / dϕ)
        for p in 0:mϕ - 1
            ϕ = 2π * p / mϕ
            nc += 1

            points[nc, 1] = centre[1] + radius * sin(θ) * cos(ϕ)
            points[nc, 2] = centre[2] + radius * sin(θ) * sin(ϕ)
            points[nc, 3] = centre[3] + radius * cos(θ)
        end
    end

    # Add remaining points on the middle circumference
    inc = 2π / (n - nc)
    for i in 0:n - nc - 1
        nc += 1

        θ = π / 2
        ϕ = i * inc

        points[nc, 1] = centre[1] + radius * sin(θ) * cos(ϕ)
        points[nc, 2] = centre[2] + radius * sin(θ) * sin(ϕ)
        points[nc, 3] = centre[3] + 0.
    end

    points
end

# Construct and return the rotation matrix that rotates a unit vector a onto unit vector b, taken
# from https://math.stackexchange.com/questions/180418
function reorient(a, b)
    v = vec(a) × vec(b)
    vx = [
        0 -v[3] v[2]
        v[3] 0 -v[1]
        -v[2] v[1] 0
    ]

    I(3) + vx + vx * vx ./ (1 + a ⋅ b)
end

function sphero_cylinder(;c1 = [0. -1. -1.], c2 = [4. 4. 0.], r1 = 1., r2 = 1., n = 128)
    hs1 = half_sphere(n = n ÷ 2)
    hs2 = half_sphere(n = n ÷ 2)

    # Re-orient the half-spheres
    v1 = (c1 - c2)
    v1 ./= norm(v1)
    rot1 = reorient([0 0 1], v1)

    for i in 1:size(hs1)[1]
        hs1[i, :] .= rot1 * hs1[i, :]
    end

    v2 = -v1
    rot2 = reorient([0 0 1], v2)

    for i in 1:size(hs2)[1]
        hs2[i, :] .= rot2 * hs2[i, :]
    end

    hs1 = (hs1 .+ c1) .* r1
    hs2 = (hs2 .+ c2) .* r2

    [hs1 ; hs2]
end

# Points on a half-sphere
sphere_points = half_sphere()
sphere_poly = polyhedron(vrep(sphere_points), lib)

# Write the half-sphere vertices to a file
open("half_sphere.csv", "w") do io
    write(io, "Points on a half-sphere polyhedral approximation with 64 vertices\n")
    for i in 1:size(sphere_points)[1]
        for j in 1:size(sphere_points)[2]
            write(io, "$(sphere_points[i, j])\n")
        end
    end
end

# Points on a sphero-cylinder
sc_points = sphero_cylinder()
sc_poly = polyhedron(vrep(sc_points), lib)

# Plot the half-sphere and a sphero-cylinder
cm = ColorSchemes.magma
GLMakie.mesh(Polyhedra.Mesh{3}(sc_poly), color = get(cm, 0.5))
GLMakie.wireframe!(Polyhedra.Mesh{3}(sc_poly))

current_figure()


module Test_meutil

using LinearAlgebra
using MatrixEquations
using Test

n = 500
ar, ur, = schur(rand(n,n))
cr = rand(n,n)
Qr = cr'*cr

@testset "Matrix Equations Utilities" begin
x = copy(Qr); @time utqu!(x,ur);
@time y = ur'*Qr*ur;
@test norm(x-y) < sqrt(eps(1.))

x = copy(Qr); @time utqu!(x,ur');
@time y = ur*Qr*ur';
@test norm(x-y) < sqrt(eps(1.))

@time x = utqu(Qr,ur);
@time y = ur'*Qr*ur;
@test norm(x-y) < sqrt(eps(1.))

@time x = utqu(Qr,ur');
@time y = ur*Qr*ur';
@test norm(x-y) < sqrt(eps(1.))

@time x = vec2triu(triu2vec(Qr, rowwise = false, her = false),rowwise = false, her = false)
@test x == triu(Qr)

@time x = vec2triu(triu2vec(Qr, rowwise = false, her = true),rowwise = false, her = true)
@test x == Qr

@time x = vec2triu(triu2vec(Qr, rowwise = true, her = false),rowwise = true, her = false)
@test x == triu(Qr)

@time x = vec2triu(triu2vec(Qr, rowwise = true, her = true),rowwise = true, her = true)
@test x == Qr

@test isschur(rand(2,3)) == false &&
      isschur(rand(1,1)) == true &&
      isschur(rand(3,3)) == false &&
      isschur(rand(3,3),rand(2,2)) == false

 u = [1 2;3 4]
 q = [1 3; 3 1]

try
    utqu(q,u)
    utqu(q//2,u)
    utqu(q//2,u//2)
    utqu!(q,u)
    utqu!(q//2,u)
    utqu(q//2,u//2)
   @test true
catch
    @test false
end

try
    utqu!(q,u//2)
    @test false
catch
    @test true
end

try
    utqu!(rand(3,3),rand(3,3))
    @test false
catch
    @test true
end

try
    utqu!(Symmetric(rand(3,3)),rand(2,2))
    @test false
catch
    @test true
end

try
    utqu(rand(3,3),rand(3,3))
    @test false
catch
    @test true
end

try
    utqu(Symmetric(rand(3,3)),rand(2,2))
    @test false
catch
    @test true
end

try
    utqu(Symmetric(rand(3,3)),rand(2,2)')
    @test false
catch
    @test true
end

try
    qrupdate!(triu(rand(4,4)), rand(2,3))
    @test false
catch
    @test true
end

try
    rqupdate!(triu(rand(4,4)), rand(2,3))
    @test false
catch
    @test true
end

try
    triu2vec(rand(2,2), her = true)
    @test false
catch
    @test true
end

end

end


using RDatasets
using GLM
using ANOVA

iris = dataset("datasets","iris")
mdl = lm(@formula(SepalLength ~ Species), iris)
anova(mdl)


function samplesystem(;name, τ=3.0)
    @variables t
    D = Differential(t)
    sts = @variables x(t) RHS(t)  # RHS is observed
    ps = @parameters τ=τ       # parameters
    ODESystem([ RHS  ~ (1 - x)/τ, D(x) ~ RHS ], t, sts, ps; name)
end


function matrixfma(output, mtx, input::AbstractVector, bias, isize, osize)
  for idx = 1:osize
    @inbounds output[idx] = bias[idx]
    @inbounds for jdx = 1:isize
      output[idx] += mtx[idx, jdx] * input[jdx]
    end
  end
end

function matrixfma(output, mtx, input::AbstractMatrix, bias, isize, osize)
  for mdx = 1:size(input, 2)
    for idx = 1:osize
      @inbounds output[idx, mdx] = bias[idx]
      @inbounds for jdx = 1:isize
        output[idx, mdx] += mtx[idx, jdx] * input[jdx, mdx]
      end
    end
  end
end

function evaluate!{F, i, o, tf}(output::AbstractArray{F}, fcl::FullyConnectedLayer{F, i, o, tf}, input::AbstractArray)
  matrixfma(output, fcl.transition, input, fcl.bias, i, o)
  for idx = 1:o
    output[idx] = tf(output[idx])
  end
end


function deep_nnm(tmp_weights::Array{Float64,1},tmp_xx::Array{Float64,2},nodes::Array{Int64,2},obs::Int64,vars::Int64,vars_depe::Int64)

    n_bias=sum(nodes)+vars_depe
    w_bias=(tmp_weights[end-n_bias+1:end])
    w1=(tmp_weights[1:vars*nodes[1]])
    w11=reshape(w1,vars,nodes[1])
    a_sig=sigm1.(tmp_xx*w11+ones(obs,nodes[1]).*w_bias[1:nodes[1]]')

    ind=length(w1)
    for ii=2:length(nodes)
        w1=(tmp_weights[ind+1:ind+nodes[ii-1]*nodes[ii]])
        ind+=length(w1)
        w11=reshape(w1,nodes[ii-1],nodes[ii])
        a_sig=sigm1.(a_sig*w11+ones(obs,nodes[ii]).*w_bias[sum(nodes[1:ii-1])+1:sum(nodes[1:ii])]')
    end

    w1=(tmp_weights[ind+1:end-n_bias])
    w11=reshape(w1,nodes[end],vars_depe)
    out=(a_sig*w11+ones(obs,vars_depe).*w_bias[end-vars_depe+1:end]')

    return out

end

#


using PDMats
import Statistics: mean, var
using LinearAlgebra

"""
   `calc_periodogram(times, y_obs, σ_ys; options...)`
Computes a periodogram for times, observations and uncertainties.

Inputs:
- times: obseration times
- y_obs: observed values at each time
- σ_ys:  measurement uncertainty at each time (may be either a Vector or positive definite matrix)
Optional parameters:

- period_min (2.0)
- period_max (4*span of times)
- num_periods (4000)

Output (as NamedTuple):
- period_grid: orbital periods searched
- periodogram: χ² for best fit given each orbital period
- period_best_fit: period searched resulting to minimum χ²
- coeff_best_fit: best-fit coefficients for period_best_fit
- phase_best_fit: orbital phase for period_best_fit at time = 0
- predict: predictions of best-fit model at each time
- rms: root mean square of predictions (of best-fit model) minus observations
"""
function calc_periodogram  end

function calc_periodogram(t::Vector, y_obs::Vector, σ_ys::Vector; kwargs...)
 	calc_periodogram(t, y_obs, PDiagMat(σ_ys.^2); kwargs...)
end

function calc_periodogram(t::Vector, y_obs::Vector, covar_mat::MT;
	    		period_min::Real = 2.0, period_max::Real = 4*(maximum(t)-minimum(t)), num_periods::Integer = 4000) where {
						T3<:Real, MT<:AbstractPDMat{T3} }
	period_grid =  1.0 ./ range(1.0/period_max, stop=1.0/period_min, length=num_periods)
	periodogram = map(p->-0.5*calc_χ²_general_linear_least_squares(calc_design_matrix_circ(p,t),covar_mat,y_obs),period_grid)
	period_fit = period_grid[argmax(periodogram)]
	design_matrix_fit = calc_design_matrix_circ(period_fit,t)
	coeff_fit = fit_general_linear_least_squares(design_matrix_fit,covar_mat,y_obs)
	phase_fit = atan(coeff_fit[1],coeff_fit[2])
	pred = design_matrix_fit * coeff_fit
	rms = sqrt(mean((y_obs.-pred).^2))
	return (;period_grid=period_grid, periodogram=periodogram, period_best_fit = period_fit, coeff_best_fit=coeff_fit, phase_best_fit=phase_fit, predict=pred, rms=rms )
end

# Internal routines called by calc_periodogram[]
function calc_design_matrix_circ!(result::AM, period::Real, times::AV) where { R1<:Real, AM<:AbstractMatrix{R1}, AV<:AbstractVector{R1} }
	n = length(times)
	@assert size(result,1) == n
	@assert 2<= size(result,2) <=3
	for i in 1:n
		( result[i,1], result[i,2] ) = sincos(2π/period .* times[i])
	end
	if size(result,2) == 3
		result[:,3] .= one(eltype(result))
	end
	return result
end

function calc_design_matrix_circ(period::Real, times::AV) where { R1<:Real, AV<:AbstractVector{R1} }
	n = length(times)
	dm = Array{promote_type(typeof(period),eltype(times))}(undef,n,3)
	calc_design_matrix_circ!(dm,period,times)
	return dm
end

function fit_general_linear_least_squares( design_mat::ADM, covar_mat::APD, obs::AA ) where { ADM<:AbstractMatrix, APD<:AbstractPDMat, AA<:AbstractArray }
	Xt_inv_covar_X = Xt_invA_X(covar_mat,design_mat)
	X_inv_covar_y =  design_mat' * (covar_mat \ obs)
	AB_hat = Xt_inv_covar_X \ X_inv_covar_y                            # standard GLS
end

function predict_general_linear_least_squares( design_mat::ADM, covar_mat::APD, obs::AA ) where { ADM<:AbstractMatrix, APD<:AbstractPDMat, AA<:AbstractArray }
	param = fit_general_linear_least_squares(design_mat,covar_mat,obs)
	design_mat * param
end

function calc_χ²_general_linear_least_squares( design_mat::ADM, covar_mat::APD, obs::AA ) where { ADM<:AbstractMatrix, APD<:AbstractPDMat, AA<:AbstractArray }
	pred = predict_general_linear_least_squares(design_mat,covar_mat,obs)
	invquad(covar_mat,obs-pred)
end


import Multirate: PFB, taps2pfb

# Interpolator FIR kernel
type Channelizer{Th,Tx}
    pfb::PFB{Th}
    h::Vector{Th}
    Nchannels::Int
    tapsPer𝜙::Int
    history::Array{Array{Tx}}
end

function Channelizer{Th}( Tx, h::Vector{Th}, Nchannels::Integer )
    pfb       = taps2pfb( h, Nchannels )
    Nchannels = size( pfb )[2]
    tapsPer𝜙  = size( pfb )[1]
    Channelizer{Th, Tx}( pfb, h, Nchannels, tapsPer𝜙, [] )
end

function Channelizer( Th, Tx, Nchannels::Integer, tapsPer𝜙 = 20 )
    hLen = tapsPer𝜙 * Nchannels
    h    = firdes( hLen, 0.45/Nchannels, kaiser ) .* Nchannels
    Channelizer( Tx, Array{Th}(h), Nchannels )
end

function filt!{Tb,Th,Tx}( buffer::Matrix{Tb}, kernel::Channelizer{Th}, x::AbstractVector{Tx} )
    Nchannels         = kernel.Nchannels
    pfb               = kernel.pfb
    tapsPer𝜙          = kernel.tapsPer𝜙
    xLen              = length( x )
    (bufLen,bufWidth) = size( buffer )
    fftBuffer         = Array{Tb}( Nchannels )

    @assert xLen   % Nchannels == 0
    @assert bufLen * bufWidth  == xLen
    @assert Tb                 == promote_type(Th,Tx)

    xPartitioned = Array{Vector{Tx}}( Nchannels )

    for channel in 1:Nchannels
        xIdxStart = Nchannels-channel+1
        xPartitioned[channel] = x[xIdxStart:Nchannels:end]
    end

    if kernel.history == []
        kernel.history = [ zeros(Tx, tapsPer𝜙-1) for i in 1:Nchannels ]
    end

    @simd for xIdx in 1:bufLen
        @simd for 𝜙Idx in Nchannels:-1:1
            if xIdx < tapsPer𝜙
                @inbounds fftBuffer[𝜙Idx] = unsafedot( pfb, 𝜙Idx, kernel.history[𝜙Idx], xPartitioned[𝜙Idx], xIdx )
            else
                @inbounds fftBuffer[𝜙Idx] = unsafedot( pfb, 𝜙Idx, xPartitioned[𝜙Idx], xIdx )
            end
        end

        @inbounds buffer[xIdx,:] = fftshift(ifft(fftBuffer))
    end

    # set history for next call
    @simd for 𝜙Idx in 1:Nchannels
        @inbounds kernel.history[𝜙Idx] = shiftin!( kernel.history[𝜙Idx], xPartitioned[𝜙Idx] )
    end

    return buffer
end

function filt{Th,Tx}( kernel::Channelizer{Th,Tx}, x::AbstractVector{Tx} )
    xLen   = length( x )

    @assert xLen % kernel.Nchannels == 0

    buffer = Array{promote_type(Th,Tx)}( Int(xLen/kernel.Nchannels), kernel.Nchannels )
    filt!( buffer, kernel, x )
    return buffer
end


using Catch22
using Clustering
using CSV
using Plots
using StatsBase
using UrlDownload

pyplot()
X = urldownload("https://ndownloader.figshare.com/files/24950795", true, format=:CSV, header=false, delim=",", type=Float64, silencewarnings=true) |> Array; # Ignore the warnings

nomissing = F -> F[.!ismissing.(Array(F))]
X = [nomissing(collect(x)) for x in X];

F = hcat([catch22(Float64.(x)) for x in X]...); # May take a minute
Df = 1.0.-abs.(StatsBase.corspearman(F'))
idxs = Clustering.hclust(Df; linkage=:average, branchorder=:optimal).order

p = plot(Df[idxs, idxs], seriestype = :heatmap, aspect_ratio=:equal, xaxis=nothing)

plot!(yticks=(1:size(Df, 1), replace.(string.(Catch22.featurenames[idxs]), '_'=>"\\_")), size=(800, 400), xlims=[0.5, size(Df, 1)+0.5], ylims=[0.5, size(Df, 1)+0.5], box=:on, colorbar_title="1-|ρ|", clims=(0.0, 1.0))


# Is left
# ---------------------------------------------------------------------------------------------
# If the point is left of the line segment in the 2D plane.
#
# The segment's direction is from 𝘅₁ to 𝘅₂. Let 𝘂 = 𝘅₂ - 𝘅₁ and 𝘃 = 𝗽 - 𝘅₁
# We may determine if the angle θ between the point and segment is in [0, π] based on the
# sign of 𝘂 × 𝘃, since 𝘂 × 𝘃 = ‖𝘂‖‖𝘃‖sin(θ).
#   𝗽    ^
#   ^   /
# 𝘃 |  / 𝘂
#   | /
#   o
# We allow points on the line (𝘂 × 𝘃 = 0) to be left, since this test is primarily
# used to determine if a point is inside a polygon. A mesh is supposed to partition
# its domain, so if we do not allow points on the line, there will exist points in the
# mesh which will not be in any face.
@inline function isleft(p::Point2D, l::LineSegment2D)
    return 0 ≤ l.𝘂 × (p - l.𝘅₁)
end

# Is left
# ---------------------------------------------------------------------------------------------
# If the point is left of the quadratic segment in the 2D plane.
#   𝗽    ^
#   ^   /
# 𝘃 |  / 𝘂
#   | /
#   o
# If the segment is straight, we can perform the isleft check with the straight
# line from the  segment's start point to the segment's end point.
# If this condition isn't met, the segment's curvature must be accounted for.
# We find the point on the curve q that is nearest point to the point of interest.
# Call this point q_near. We then perform the isleft check with the tangent vector
# of q at q_near and the vector from q_near to p, p - q_near.
function isleft(p::Point2D, q::QuadraticSegment2D)
    if isstraight(q)
        𝘃₁ = q.𝘅₂ - q.𝘅₁
        𝘃₂ = p - q.𝘅₁
        return 𝘃₁ × 𝘃₂ > 0
    else
        # See nearest_point for an explanation of the math.
        # When the cubic has 3 real roots, the point must be inside the
        # curve of the segment. Meaning:
        #   If the segment curves left, the point is right.
        #   If the segment curves right, the point is left.
        # This way we save substantial time by bypassing the complex number arithmetic
        𝘂 = q.𝘂
        𝘃 = q.𝘃
        𝘄 = p - q.𝘅₁
        # f′(r) = ar³ + br² + cr + d = 0
        a = 4(𝘂 ⋅ 𝘂)
        b = 6(𝘂 ⋅ 𝘃)
        c = 2((𝘃 ⋅ 𝘃) - 2(𝘂 ⋅𝘄))
        d = -2(𝘃 ⋅ 𝘄)
        # Lagrange's method
        e₁ = s₀ = -b/a
        e₂ = c/a
        e₃ = -d/a
        A = 2e₁^3 - 9e₁*e₂ + 27e₃
        B = e₁^2 - 3e₂
        if A^2 - 4B^3 > 0 # one real root
            s₁ = ∛((A + √(A^2 - 4B^3))/2)
            if s₁ == 0
                s₂ = s₁
            else
                s₂ = B/s₁
            end
            r = (s₀ + s₁ + s₂)/3
            𝘃₁ = 𝗗(q, r)
            𝘃₂ = p - q(r)
            return 𝘃₁ × 𝘃₂ > 0
        else # three real roots
            return (q.𝘅₂ - q.𝘅₁) × (q.𝘅₃ - q.𝘅₁) < 0
        end
    end
end

# Nearest point
# ---------------------------------------------------------------------------------------------
# Find the point on 𝗾(r) closest to the point of interest 𝘆.
#
# Note: r ∈ [0, 1] is not necessarily true for this function, since it finds the minimizer
# of the function 𝗾(r), ∀r ∈ ℝ
# Find r which minimizes ‖𝘆 - 𝗾(r)‖, where 𝗾(r) = r²𝘂 + r𝘃 + 𝘅₁.
# This r also minimizes ‖𝘆 - 𝗾(r)‖²
# It can be shown that this is equivalent to finding the minimum of the quartic function
# ‖𝘆 - 𝗾(r)‖² = f(r) = a₄r⁴ + a₃r³ + a₂r² + a₁r + a₀
# The minimum of f(r) occurs when f′(r) = ar³ + br² + cr + d = 0, where
# 𝘄 = 𝘆 - 𝘅₁, a = 4(𝘂 ⋅ 𝘂), b = 6(𝘂 ⋅ 𝘃), c = 2[(𝘃 ⋅ 𝘃) - 2(𝘂 ⋅𝘄)], d = -2(𝘃 ⋅ 𝘄)
# Lagrange's method (https://en.wikipedia.org/wiki/Cubic_equation#Lagrange's_method)
# is used to find the roots.
function nearest_point(p::Point{Dim,T}, q::QuadraticSegment) where {Dim,T}
    𝘂 = q.𝘂
    𝘃 = q.𝘃
    𝘄 = p - q.𝘅₁
    # f′(r) = ar³ + br² + cr + d = 0
    a = 4(𝘂 ⋅ 𝘂)
    b = 6(𝘂 ⋅ 𝘃)
    c = 2((𝘃 ⋅ 𝘃) - 2(𝘂 ⋅𝘄))
    d = -2(𝘃 ⋅ 𝘄)
    # Lagrange's method
    e₁ = s₀ = -b/a
    e₂ = c/a
    e₃ = -d/a
    A = 2e₁^3 - 9e₁*e₂ + 27e₃
    B = e₁^2 - 3e₂
    if A^2 - 4B^3 > 0 # one real root
        s₁ = ∛((A + √(A^2 - 4B^3))/2)
        if s₁ == 0
            s₂ = s₁
        else
            s₂ = B/s₁
        end
        r = (s₀ + s₁ + s₂)/3
        return r, q(r)
    else # three real roots
        # Complex cube root
        t₁ = exp(log((A + √(complex(A^2 - 4B^3)))/2)/3)
        if t₁ == 0
            t₂ = t₁
        else
            t₂ = B/t₁
        end
        ζ₁ = Complex{T}(-1/2, √3/2)
        ζ₂ = conj(ζ₁)
        dist_min = typemax(T)
        r_near = zero(T)
        p_near = nan(Point{Dim,T})
        # Use the real part of each root
        for rᵢ in (real((s₀ +    t₁ +    t₂)/3),
                   real((s₀ + ζ₂*t₁ + ζ₁*t₂)/3),
                   real((s₀ + ζ₁*t₁ + ζ₂*t₂)/3))
            pᵢ = q(rᵢ)
            dist = distance²(pᵢ, p)
            if dist < dist_min
                dist_min = dist
                r_near = rᵢ
                p_near = pᵢ
            end
        end
        return r_near, p_near
    end
end

# Hyperplane
# ---------------------------------------------------------------------------------------------
Base.in(p::Point, plane::Hyperplane) = p.coord ⋅ plane.𝗻 ≈ plane.d
in_halfspace(p::Point, plane::Hyperplane) = p.coord ⋅ plane.𝗻 - plane.d ≥ 0

# Given a point p and line l that lie in the plane. Check that the point is left of the line
function isleft(p::Point3D, l::LineSegment3D, plane::Hyperplane3D)
    # Since p and l ∈ plane, l.𝘂 × (p - l.𝘅₁) must either by in the exact same direction
    # as plane.𝗻 or the exact opposite direction. If the direction is the same, the point
    # is left of the line.
    return 0 ≤ (l.𝘂 × (p - l.𝘅₁)) ⋅ plane.𝗻
end

# AABox
# ---------------------------------------------------------------------------------------------
@inline Base.in(p::Point2D, aab::AABox2D) = aab.xmin ≤ p[1] ≤ aab.xmax &&
                                            aab.ymin ≤ p[2] ≤ aab.ymax
@inline Base.in(p::Point3D, aab::AABox3D) = aab.xmin ≤ p[1] ≤ aab.xmax &&
                                            aab.ymin ≤ p[2] ≤ aab.ymax &&
                                            aab.zmin ≤ p[3] ≤ aab.zmax
# Point inside polygon
# ---------------------------------------------------------------------------------------------
# Test if a point is in a polygon for 2D points/polygons
function Base.in(p::Point2D, poly::Polygon{N, 2}) where {N}
    # Test if the point is to the left of each edge.
    for i ∈ 1:N-1
        isleft(p, LineSegment2D(poly[i], poly[i + 1])) || return false
    end
    return isleft(p, LineSegment2D(poly[N], poly[1]))
end
# Not necessarily planar
#function Base.in(p::Point3D, poly::Polygon{N, 3}) where {N}
#    # Check if the point is even in the same plane as the polygon
#    plane = Hyperplane(poly[1], poly[2], poly[3])
#    p ∈ plane || return false
#    # Test that the point is to the left of each edge, oriented to the plane
#    for i = 1:N-1
#        isleft(p, LineSegment3D(poly[i], poly[i + 1]), plane) || return false
#    end
#    return isleft(p, LineSegment3D(poly[N], poly[1]), plane)
#end
#

# Point inside triangle
# ---------------------------------------------------------------------------------------------
function Base.in(p::Point3D, tri::Triangle3D)
    # P ∈ ABC iff the surface normals of CCW triangles PAB, PBC, & PCA are equal.
    𝗮 = tri[1] - p
    𝗯 = tri[2] - p
    𝗰 = tri[3] - p
    𝗻₁= 𝗮 × 𝗯
    𝗻₂= 𝗯 × 𝗰
    d₁₂ = 𝗻₁ ⋅ 𝗻₂
    # Test the normals point the same direction relative to each other
    # and that surface normals are equivalent using 𝗻̂ ⋅ 𝗻̂ = 1
    # d₁₂ > 0 is redundant if the point is in the triangle, but it is a very
    # fast check that the point is in the plane of the triangle.
    (d₁₂ > 0 && d₁₂ ≈ norm(𝗻₁)*norm(𝗻₂)) || return false
    # We need only check the direction of the norm of the last triangle to
    # prove that the point is in the triangle
    return 𝗻₂ ⋅(𝗰 × 𝗮) > 0
end

# This performs much better than the default routine, which is logically equivalent.
# Better simd this way? Chaining isleft doesn't have the same performance improvement for
# triangles.
function Base.in(p::Point2D, quad::Quadrilateral2D)
    return isleft(p, LineSegment2D(quad[1], quad[2])) &&
           isleft(p, LineSegment2D(quad[2], quad[3])) &&
           isleft(p, LineSegment2D(quad[3], quad[4])) &&
           isleft(p, LineSegment2D(quad[4], quad[1]))
end

# Test if a 2D point is in a 2D quadratic polygon
function Base.in(p::Point2D, poly::QuadraticPolygon{N, 2, T}) where {N, T}
    # Test if the point is to the left of each edge.
    bool = true
    M = N ÷ 2
    for i ∈ 1:M-1
        if !isleft(p, QuadraticSegment2D(poly[i], poly[i + 1], poly[i + M]))
            return false
        end
    end
    return isleft(p, QuadraticSegment2D(poly[M], poly[1], poly[N]))
end


# solve the ground state of Lieb-Liniger model
# also demonstrates the usage of preconditioners in Optim.jl

using TensorKit
using TensorOperations
using TensorKitAD
using KrylovKit
using Zygote
using KrylovKit
using LinearAlgebra
using Plots
using Optim
using ChainRules
using ChainRulesCore
using Printf

using Revise
using statmech_tm_solver

function Base.show(io::IO, t::OptimizationState)
    @printf io "%4d   %14.14e   %14.14e\n" t.iteration t.value t.g_norm
end
#########################################################

# parameters
c = 2
μ = 1
L = 50
d = 1
nsteps1 = 200 # ordinary LBFGS steps
nsteps2 = 1000 # preconditioned LBFGS steps

# cost function (energy) and its gradient
function f(ψ_arr::Array{ComplexF64, 3})
    ψ = convert_to_cmps(ψ_arr)
    return energy_lieb_liniger(ψ, c, L, μ)
end

function g!(ψg_arr::Array{ComplexF64, 3}, ψ_arr::Array{ComplexF64, 3})
    copyto!(ψg_arr, f'(ψ_arr))
end

# construct the preconditioner
function _precondprep!(P::preconditioner, ψ_arr::Array{ComplexF64, 3})
    ψ = convert_to_cmps(ψ_arr)
    ψgrad = convert_to_tensormap(f'(ψ_arr), 2)

    P1 = preconditioner(ψ, ψgrad, L; gauge=:periodic)
    P.map = P1.map
    P.invmap = P1.invmap
    P.proj = P1.proj
end

ψm = cmps(rand, 2, d)
#ψm = quickload("lieb_liniger_c$(c)_mu$(μ)_L$(L)_chi4") |> convert_to_cmps

for χ in [2; 4; 8; 12; 16; 20]
    global ψm

    # important: random init or start with existing results
    #ψ0 = expand(ψm, χ, 0.01)
    ψ0 = quickload("lieb_liniger_c$(c)_mu$(μ)_L$(L)_chi$(χ)") |> convert_to_cmps

    ψ_arr0 = convert_to_array(ψ0)
    E0 = energy_lieb_liniger(ψ0, c, L, μ)

    res = optimize(f, g!, ψ_arr0, LBFGS(), Optim.Options(show_trace=true, iterations=nsteps1))
    ψ_arr = Optim.minimizer(res);
    ψm = convert_to_cmps(ψ_arr);
    ψm_arr = convert_to_tensormap(f'(ψ_arr), 2);

    P0 = preconditioner(ψm, ψm_arr, L);
    res = optimize(f, g!, ψ_arr, LBFGS(P = P0, precondprep=_precondprep!), Optim.Options(show_trace=true, iterations=nsteps2))
    ψm = convert_to_cmps(Optim.minimizer(res));

    # calculate observables. total, kinetic and potential energies
    E = energy_lieb_liniger(ψm, c, L, μ)
    env = finite_env(K_mat(ψm, ψm), L)
    env = permute(env, (2, 3), (4, 1))

    op_ρ = particle_density(ψm)
    op_k = kinetic(ψm)
    ρ_value = tr(env * op_ρ)
    k_value = tr(env * op_k)

    @show χ, E, ρ_value, k_value

    quicksave("lieb_liniger_c$(c)_mu$(μ)_L$(L)_chi$(χ)", convert_to_tensormap(ψm))
end


erf1arg = [
(:erf, :(exp(-abs2(x))*2/sqrt(pi)), (-Inf,Inf)),     # \frac{2}{\sqrt{\pi}} \int_0^x e^{-t^2} dt
(:erfc, :(-exp(-abs2(x))*2/sqrt(pi)), (-Inf,Inf)),   # 1-erf(x)
(:erfcx, :(2y.*x-2/sqrt(pi)), (-Inf,Inf)),           # erfc(x)*exp(x^2)
(:erfi, :(exp(abs2(x))*2/sqrt(pi)), (-Inf,Inf)),     # -i*erf(ix)
(:dawson, :(-2y.*x+1), (-Inf,Inf)),                  # \frac{\sqrt{\pi}}{2} e^{-x^2} erfi(x).
(:erfinv, :(exp(abs2(y))*sqrt(pi)/2), (-1,1)),       # erf(erfinv(x)) = x
(:erfcinv, :(-exp(abs2(y))*sqrt(pi)/2), (0,2)),      # erfc(erfcinv(x)) = x
]

for (f,g,r) in erf1arg
    @eval @primitive $f(x),dy,y  (dy.*($g))
    addtest1(f,r)
end


using DifferentialEquations, Test

f = function (du,u,p,t)
  du[1] = 2 - 2u[1]
  du[2] = u[1] - 4u[2]
end
u0 = zeros(2)
prob = SteadyStateProblem(f,u0)

sol = solve(prob)

@test typeof(sol.alg)<: SSRootfind


module ParticleMovement

  #=
      This is a simple little program that I've written in many languages. It's an
      exercise to learn a new programming language, and tooling in it for doing simple
      image manipulation, and simple simulations.

      The topic is a simple mechanics simulation: Particles with speed are placed in
      a plane and then made to collide with each others, walls etc.  The simulation
      steps ahead a single time step at a time.

      Physical accuracy is not an objective, but it  is "physics like" in that it
      at least makes an attempt to preserve energy, momentum etc., but apart from that
      it's made for me learning julia, not calculating anything of physical importance.

      I've tested this in Julia 1.3.1 on macOS 10.15.2. It mostly works for me :-)
  =#

  using Test
  using Plots

  using Images, TestImages, Colors, ImageView, GtkReactive
  import Base.+
  import Base.-
  import Base.*
  import Base./
  import Base.==
  import Base.≈
  import Base.show

  export ParticleState, Point, collide

  struct Point
	     x::Float64
	     y::Float64
  end

  Base.show(p::Point) =
          print("{", p.x, ",", p.y, "}")

  (==)(a::Point,  b::Point)   = a.x == b.x && a.y == b.y
  (≈)(a::Point,   b::Point)   = isapprox(a.x, b.x, atol=1e-8) && isapprox(a.y, b.y, atol=1e-8)
  (*)(a::Number,  b::Point)   = Point(a * b.x, a * b.y)
  (*)(a::Point,   b::Number)  = b * a
  (+)(a::Point,   b::Point)   = Point(a.x + b.x, a.y + b.y)
  (-)(a::Point,   b::Point)   = Point(a.x - b.x, a.y - b.y)
  (/)(a::Point,   n::Number)  = Point(a.x/n , a.y/n)

  struct Line
  	 p1::Point
	 p2::Point
	 a::Float64
	 b::Float64
	 c::Float64
	 direction::Point
  end

  points_on_different_sides_of_line(l::Line, a::Point, b::Point) =
     return ((l.p1.y - l.p2.y) * (a.x - l.p1.x)  + (l.p2.x - l.p1.x) * (a.y - l.p1.y)) *
            ((l.p1.y - l.p2.y) * (b.x - l.p1.x) +  (l.p2.x - l.p1.x) * (b.y - l.p1.y)) < 0

  struct ParticleState
      id::     Int64
      mass::   Float64
      pos::    Point
      speed::  Point
  end

 SAMPLE_BARRIER = Line(Point(0.0, 150), Point(100.0, 150), -150.0, 0.0, 1.0, Point(1,0))

 function reflect_through_line(l::Line, p::Point)
    divisor = l.a^2 + l.b^2
    prex = p.x * (l.a^2 - l.b^2) - 2*l.b*(l.a * p.y + l.c)
    prey = p.y * (l.b^2 - l.a^2) - 2*l.a*(l.b * p.x + l.c)
    return Point(prex/divisor, prey/divisor)
 end

 inner_product(v1::Point, v2::Point) = v1.x*v2.x + v1.y*v2.y

 vector_length(p::Point) = p.x^2 + p.y^2

 angleBetweenVectors(v1::Point, v2::Point) =
     acos(inner_product(v1, v2)/(vector_length(v1) * vector_length(v2)))

 function reflect_through_line(l::Line, p::ParticleState)::ParticleState
   newPos   = reflect_through_line(l, p.pos)
   angle    = angleBetweenVectors(l.direction, p.speed)
   newSpeed = rotate(p.speed, 2*angle)
   return ParticleState(p.id, p.mass, newPos, newSpeed)
 end

 Base.show(z::ParticleState) =
          print("Particle (mass=", z , ", pos=", z.pos, ", speed = ", z.speed, ")")

  (==)(a::ParticleState, b::ParticleState) = a.mass == b.mass && a.pos == b.pos && a.speed == b.speed

  as_complex(p::Point)              = p.x + 1im * p.y
  as_point(c::Complex)     = Point(real(c), imag(c))
  rotate(p::Point, angle::Number) = as_point(as_complex(p) * exp(1im * angle))

  coll(a::ParticleState, b::ParticleState)  =
 	          a.speed * (2*a.mass / (a.mass + b.mass)) - b.speed * ((a.mass - b.mass)/(a.mass + b.mass))

  # A momentum-preserving collision. Assuming that the
  # particles are of the same size and are exacly touching
  # when the method is being applied.  They won't move, but they will
  # get new speeds.
  function collide(a::ParticleState, b::ParticleState)
  	   newVb = coll(a, b)
   	   newVa = coll(b, a)
	   return ParticleState(a.id, a.mass, a.pos, newVa), ParticleState(b.id, b.mass, b.pos, newVb)
  end

  random_point(dx::Float64, dy::Float64) =
       Point(rand()*dx - dx/2,
	     rand()*dy - dy/2)

  random_direction() =
       rotate(Point(0.0, 1.0), rand()*2*π)

   # Make an (eventually) random particle.
   random_particle(id::Int64, mass::Float64, speed::Float64, dx::Float64, dy::Float64) =
	    ParticleState(id, mass, random_point(dx, dy), random_direction()*speed)

   random_ensemble(mass::Float64, speed::Float64, dx::Float64, dy::Float64, n::Int64) =
	   Set{ParticleState}([random_particle(id, mass, speed, dx, dy) for id in 1:n])

   function pointToImageCoord(pos::Point, xdim::Int64, ydim::Int64, maxx::Float64, maxy::Float64)::Tuple{Int64, Int64}
      x = 1 + xdim/2  + xdim * (pos.x / maxx)
      y = 1 + ydim/2  + ydim * (pos.y / maxy)
      return trunc(Int, x), trunc(Int, y)
   end

   function paint_dot(
   	    img,
   	    maxx::Float64, maxy::Float64, pos::Point)
           xdim, ydim = size(img)
	   x, y = pointToImageCoord(pos, xdim, ydim, maxx, maxy)
  	   if x > 0 && x <= xdim && y > 0 && y <= ydim
  	     img[x, y] = 0 # Black
	   end
   end

   function paint_points!(target, maxx::Float64, maxy::Float64, points)
       foreach(p -> paint_dot(target, maxx, maxy,  p), points)
   end

   function points_on_line(l::Line, pointsOnLine::Int)
      p1 = l.p1
      delta = (l.p2 - l.p1) / pointsOnLine
      return [p1 + delta * i for i in 1:pointsOnLine]
   end

   function img_of_line!(target, maxx::Float64, maxy::Float64, l::Line)
      paint_points!(target, maxx, maxy, points_on_line(l,200))

   function  basic_movement(p::ParticleState)
      pPrime =  ParticleState(p.id, p.mass, p.pos + p.speed, p.speed)

      # Handling reflections via a line bar
      if points_on_different_sides_of_line(SAMPLE_BARRIER, p.pos, pPrime.pos)
         pPrime = reflect_through_line(SAMPLE_BARRIER, pPrime)
      end

      return pPrime
   end

   function handle_collisions_between_particles(particles::Set{ParticleState},  xdim::Int64, ydim::Int64, maxx::Float64, maxy::Float64)::Set{ParticleState}
       result = []
       for (key, partition) in partition(particles, xdim, ydim, maxx, maxy)
           prev = missing
	   for p in partition
	      if prev === missing
                 prev = p
               else
                 a, prev = collide(prev, p)
                 push!(result, a)
               end
           end
           push!(result, prev)
       end
       return Set{ParticleState}(result)
   end

   function partition(particles::Set{ParticleState}, xdim::Int64, ydim::Int64, maxx::Float64, maxy::Float64)::Dict{Tuple{Int64,Int64}, Set{ParticleState}}
      dict = Dict{Tuple{Int64, Int64}, Set{ParticleState}}()
      for p in particles
          coord = pointToImageCoord(p.pos, xdim, ydim, maxx, maxy)
          if (haskey(dict,coord))
             push!(dict[coord], p)
          else
             dict[coord] = Set{ParticleState}([p])
          end
      end
      return dict
   end

  progress(particles::Set{ParticleState}, xdim::Int64, ydim::Int64, maxx::Float64, maxy::Float64) =
        handle_collisions_between_particles(Set{ParticleState}([basic_movement(p) for p in particles]),
                         xdim, ydim, maxx, maxy)

   function movie(frames::Int64, particles::Int64)
         state = ParticleMovement.random_ensemble(1.0, 1.0,  100.0, 100.0,  particles)
         xdim = 1000
         ydim = 1000
         maxx = 1000.
         maxy = 1000.
         result = ones(Gray{N0f8},1000, 1000, frames) # One == white
         for i in 1:frames
           println("Generating frame ", i , "/", frames)
           slice = view(result, :, :, i)
	   img_of_line!(slice, maxx, maxy, SAMPLE_BARRIER)
           img_of_particles!(slice, state, maxx, maxy)
           state = progress(state, xdim, ydim, maxx, maxy)
         end
         return result
   end

   # TODO: Uncomment this and make mpeg movies
   # using Images

   # function writevideo(fname, imgstack::Array{<:Color,3};
   #                   overwrite=true, fps=30::UInt, options=``)
   #     ow = overwrite ? `-y` : `-n`
   #     h, w, nframes = size(imgstack)

   #     open(`ffmpeg
   #           -loglevel warning
   #           $ow
   #           -f rawvideo
   #           -pix_fmt rgb24
   #           -s:v $(h)x$(w)
   #           -r $fps
   #           -i pipe:0
   #           $options
   #           -vf "transpose=0"
   #           -pix_fmt yuv420p
   #           $fname`, "w") do out
   #       for i = 1:nframes
   #           write(out, convert.(RGB{N0f8}, clamp01.(imgstack[:,:,i])))
   #       endfpp= ParticleMovement.movie(5)
   #     end
   # end

   # function writevideo(fname, imgstack::Array{<:Gray,3};
   #                   overwrite=true, fps=30::Int, options=``)
   #     ow = overwrite ? `-y` : `-n`
   #     h, w, nframes = size(imgstack)

   #     open(`ffmpeg
   #           -loglevel warning
   #           $ow
   #           -f rawvideo
   #           -pix_fmt rgb24
   #           -s:v $(h)x$(w)
   #           -r $fps
   #           -i pipe:0
   #           $options
   #           -vf "transpose=0"
   #           -pix_fmt yuv420p
   #           $fname`, "w") do out
   #       for i = 1:nframes
   #           write(out, convert.(RGB{N0f8}, clamp01.(imgstack[:,:,i])))
   #       end
   #     end
   # end

   ## TODO:
   ##   o  Make an animation by using the https://github.com/JuliaImages/ImageView.jl
   ##        And the mridata in there as a template.  Make a forward/backward scrollable 2D
   ##        movie-like dataset.
   ##   o  Make it run in a workbook
   ##   o  Make it nice

   run_smoketest(frames::Int=200, particles::Int=10000) = imshow(ParticleMovement.movie(frames, particles))

   ## Unit tests
 @testset "Unit tests for particle movements" begin

   @testset "Basic arithmetic operations on points" begin
      @test Point(0.,0.) == Point(0.,0.)
      @test Point(0.,0.) != Point(1.0, 0.)
      @test Point(0.,0.) != Point(0.0, 1.)
      @test Point(0.0,1.0) ≈ rotate(Point(1.0, 0.0), π/2)
   end

   @testset "Generation of random ensembles" begin
     sizeOfEnsemble = 3
     @test sizeOfEnsemble == length(random_ensemble(1.0, 1.0,  1.0, 1.0, sizeOfEnsemble))
   end

   @testset "Collission management" begin
       # Test that the handling of collissions don't change the number of
       # particles in the ensemble
       sizeOfEnsemble = 3
       @test sizeOfEnsemble == length(handle_collisions_between_particles(random_ensemble(1.0, 1.0,  1.0, 1.0, sizeOfEnsemble), 1000, 1000, 1000., 1000.))
       # TBD:
       #   * Testing collissions between individual particles.
       #   * Testing collisions between particle and line.
   end

   @testset "Simulation progression" begin
      # Test that progressing the simulation isn't changing the number
      # of particles in the ensemble
      @test 3 == length(progress(random_ensemble(1.0, 1.0,  1.0, 1.0, 3), 1000, 1000, 1000., 1000.))
   end

  end
end

   # TODO:
   #  - Add collisions with curves.   Do it like this:
   #     - Did the particle cross the curve?, if so then
   #         - Calculate a perfectly elastic collision between the curve and the particle,
   #               modifying the movement
   #  - Draw the barriers particles can collide against
   #  - https://github.com/jrevels/YASGuide


using BenchmarkTools
using Printf

# Get the dimension from the command line.
n, = size(ARGS)
if n < 1
    println("Usage: copyMatrix.jl dim")
    println("       --->      Please specify the dimension.")
    exit()
end

dimension = parse(Int, ARGS[1])

"""
    maxtrixcopy(A)

Perform the copy operations on matrix A with the given dimensions.
"""
function matrixcopy(A)
    N = size(A, 1)
    for j = 1:N, i = 1:N
        A[i, j, 1] = A[i, j, 2]
        A[i, j, 3] = A[i, j, 1]
        A[i, j, 2] = A[i, j, 3]
    end
end

println("-------------------------------")
println(@sprintf "Copy of matrix (loop) %d" dimension)
println("-------------------------------")

A = randn(dimension, dimension, 3)

@btime matrixcopy(A)

println(" ")

println("--------------------------")
println(@sprintf "Vectorized Copy of matrix %d" dimension)
println("--------------------------")

A = randn(dimension, dimension, 3)

@btime begin
    A[:, :, 1] = A[:, :, 2]
    A[:, :, 3] = A[:, :, 1]
    A[:, :, 2] = A[:, :, 3]
end

println(" ")


### A Pluto.jl notebook ###
# v0.17.0

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

# ╔═╡ 60941eaa-1aea-11eb-1277-97b991548781
begin
    using PlutoUI
	using ExtendableGrids
	using PlutoVista
	using GridVisualize
	using VoronoiFVM
	default_plotter!(PlutoVista)
end

# ╔═╡ 556480e0-94f1-4e47-be9a-3e1e0e99555c
TableOfContents(title="")

# ╔═╡ 5e13b3db-570c-4159-939a-7e2268f0a102
md"""
# Some problems with Voronoi FVM

Draft. J. Fuhrmann, Oct. 29. 2021

"""

# ╔═╡ fae47c55-eef8-4428-bb5f-45824978753d
md"""
## Transient problem

This problem was suggested by R. Eymard.
"""

# ╔═╡ 8ba2300c-17ff-44e1-b33a-c5bdf1ce12fe
md"""
Regard the following problem coupling Darcy's equation with Fick's law and transport:
"""

# ╔═╡ 51c9517c-8797-4406-b053-301694fb0484
md"""
```math
  \begin{aligned}
    \vec v &= k \nabla p \\
    \nabla \cdot \vec v &= 0\\
    \partial_t (\phi c) - \nabla \cdot (D\nabla c + c \vec v) &= 0
  \end{aligned}
```
"""

# ╔═╡ 99341e32-9c78-4e31-bec0-d1ffbc85ec32
md"""
The domain is described by the following discretization grid:
"""

# ╔═╡ cd013964-f329-4d2c-ae4b-305093f0ac56
md"""
### Results

In the calculations, we ramp up the inlet concentration and  measure  the amount  of solute flowing  through the outlet - the breaktrough curve.
"""

# ╔═╡ afccbadb-2ca8-4c3e-8c6d-c78df59d8d7e
nref=1

# ╔═╡ dd9f8d38-8812-40ba-88c8-f873ec7d6121
tend=100

# ╔═╡ 5f6ac608-b1a0-450e-910e-d7d8ea2ffae0
ε_fix=1.0e-4

# ╔═╡ 5b60c7d4-7bdb-4989-b055-6695b9fdeedc
md"""
Here, we plot the solutions for the `grid_n` case and the `grid_f` case.
"""

# ╔═╡ f6abea66-1e42-4201-8433-5d092989749d
begin
	vis_n=GridVisualizer(dim=2,resolution=(210,150))
	vis_f=GridVisualizer(dim=2,resolution=(210,150))
	(vis_n,vis_f)
end

# ╔═╡ 98ae56dd-d42d-4a93-bb0b-5956b6e981a3
md"""
Time: $(@bind t Slider(1:tend/100:tend,show_value=true,default=tend*0.1))
"""

# ╔═╡ 99c3b54b-d458-482e-8aa0-d2c2b51fdf25
md"""
## Reaction-Diffusion problem

Here we solve the following problem:

```math
    -\nabla D \nabla u + R u = 0
```
where D is large in the high permeability region and small otherwise. R is a constant.

"""

# ╔═╡ eef85cd7-eba4-4c10-9e1d-38411179314d
md"""
### Results
"""

# ╔═╡ fcd066f1-bcd8-4479-a4e4-7b8c235336c4
md"""
## Discussion

### Transient case
As there will be nearly no flow
in  y-direction, we should  get the  very same  results in  all four
cases for small permeability values in the low permeability region.

In the `grid_n` case,  the heterogeneous control volumina  ovrestimate the storage
capacity which shows itself  in a underestimation  of the
transferred solute.

With  the high  permeability contrast,  the results  for heterogeneous
domain should be essentially equal to those for 1D domain.
 However,   with  a   coarse  resolution   in
y-direction, we see large  differences in the transient behaviour of
the breaktrough curve compared to the 1D case.
The introduction of a thin  protection layer leads  to  reasonable   results.

Alternatively, the porosity of the low permeability region can be modified.
Arguably, this is the case in practice, see e.g.
[Ackerer et al, Transport in Porous Media35:345–373, 1999](https://link.springer.com/content/pdf/10.1023/A:1006564309167.pdf)
(eq. 7).

### Reaction diffusion case
In this case, we look at a homogeneous reaction in a domain divided
into a high and low diffusion region. With high contrast in the diffusion
coefficients, the reasonable assumption is that the reaction takes place only
in the high diffusion region, and the un-consumed share of species leaves at the outlet.

In this case we observe a similar related problem which can be fixed by adding a thin layer
of control volumes at the boundary. No problem occurs if the reaction rate at in the low diffusion
region is zero.

### Conclusion

Here, we indeed observe problem with the Voronoi approach: care must be taken to handle the case
of hetero interfaces in connection with transient processes and/or homogeneous reactions.
In these cases it should be analyzed if the problem occurs, and why, and it appears, that the discussion
should not be had without reference to the correct physical models. A remedy based on meshing
is available at least for straight interfaces.

### Opinion

With standard ways of using finite elements, the issue described here will occur in a similar way, so
the discussion is indeed with the alternative "cell centered" finite volume approach which places interfaces
at the boundaries of the control volumes rather than along the edges of a underlying triangulation.

#### Drawbacks of two point flux Voronoi methods based on simplicial meshes (as tested here):
- Anisotropic diffusion is only correct with aligned meshes
- Reliance on boundary conforming Delaunay property of the underyling mesh, thus narrowing the available meshing strategies
- The issue described  in the present notebook. However, in both cases discussed here, IMHO it might  "go  away"  depending on the correct physics.
  There should be more discussions with relevant application problems at hand.

#### Advantages (compared to the cell centered approach placing collocation points away from interfaces)
- Availability of P1 interpolant on simplices for visualization, interpolation, coupling etc.
- Mesh generators tend to place interfaces at triangle edges.
- Dirichlet BC can be applied exactly
- There is a straighforward way to link interface processes with bulk processes, e.g. an adsorption reaction is easily described by a reaction term at the boundary which involves interface and bulk value available at the same mesh node.

"""

# ╔═╡ c9d92201-813c-499b-b863-b138c30eb634
md"""
## Appendix
"""

# ╔═╡ a372ac90-c871-4dc0-a44b-a5bddef71823
md"""
### Domain data
"""

# ╔═╡ 124b2a0a-ef19-453e-9e3a-5b5ce7db5fac
md"""
Sizes:
"""

# ╔═╡ 1ad18670-e7cb-4f7a-be0f-3db98cdeb6a4
begin
L=10   # length of the high perm layer
W=0.5  # width of high perm layer
Wlow=2 # width of adjacent low perm layers
end;

# ╔═╡ cc325b2c-6174-4b8d-8e39-202ac68b5705
md"""
In the center of the domain, we assume a layer with high permeability.

As  boundary  conditions for the pressure ``p`` we choose  fixed pressure values at  the left
and right boundaries of the  domain, triggering a constant pressure gradient throughout the domain.

At the inlet of the high  permeability layer, we set ``c=1``, and at the
outlet, we set ``c=0``.

The high permeability layer has length `L`=$( L) and width `W`= $(W).

We solve the time dependent problem on three types of  rectangular grids with the same
resolution in   $x$ direction and different variants to to handle the  high permeability
layer.

- `grid_n` - a "naive" grid which just resolves the permeability layer and the surrounding material with equally spaced (in y direction) grids
- `grid_1` - a 1D grid  of the high permeability layer. With high permeability contrast, the solution of the 2D case at y=0 should conincide with the 1D solution
- `grid_f` - a "fixed" 2D grid which resolves the permeability layer and the surrounding material with equally spaced (in y direction) grids and "protection layers" of width `ε_fix`=$(ε_fix)  correcting the size of high permeability control volumes

"""

# ╔═╡ 47bc8e6a-e296-42c9-bfc5-967edfb0feb7
md"""
Boundary conditions:
"""

# ╔═╡ d1d5bad2-d282-4e7d-adb9-baf21f58155e
begin
Γ_top=3
Γ_bot=1
Γ_left=4
Γ_right=2
Γ_in=5
Γ_out=2
end;

# ╔═╡ 9d736062-6821-46d9-9e49-34b43b78e814
begin
    Ω_low=1
    Ω_high=2
end;

# ╔═╡ 83b9931f-9020-4400-8aeb-31ad391184db
function grid_2d(;nref=0,ε_fix=0.0)
    nx=10*2^nref
    ny=1*2^nref
    nylow=3*2^nref
    xc=linspace(0,L,nx+1)
    y0=linspace(-W/2,W/2,ny+1)
    if ε_fix>0.0
        yfix=[W/2,W/2+ε_fix]
	ytop=glue(yfix,linspace(yfix[end],Wlow,nylow+1))
    else
        ytop=linspace(W/2,Wlow,nylow+1)
    end
    yc=glue(-reverse(ytop),glue(y0,ytop))
    grid=simplexgrid(xc,yc)
    cellmask!(grid, [0,-W/2],[L,W/2],Ω_high)
    bfacemask!(grid, [0,-W/2],[0,W/2],Γ_in)
    bfacemask!(grid, [L,-W/2],[L,W/2],Γ_out)
end

# ╔═╡ 46a0f078-4165-4e37-9e69-e69af8584f6e
gridplot(grid_2d(),resolution=(400,300))

# ╔═╡ 3f693666-4026-4c01-a7aa-8c7dcbc32372
gridplot(grid_2d(;ε_fix=1.0e-1),resolution=(400,300))

# ╔═╡ c402f03c-746a-45b8-aaac-902a2f196094
function grid_1d(;nref=0)
    nx=10*2^nref
    xc=linspace(0,L,nx+1)
    grid=simplexgrid(xc)
    cellmask!(grid, [0],[L],Ω_high)
    bfacemask!(grid, [0],[0],Γ_in)
    bfacemask!(grid, [L],[L],Γ_out)
    grid
end

# ╔═╡ e33bf4b7-7b9a-4c5c-be4f-874af949c5ef
md"""
### Solvers
"""

# ╔═╡ a63a655c-e48b-4969-9409-31cd3db3bdaa
md"""
Pressure index in solution
"""

# ╔═╡ d7009231-4b43-44bf-96ba-9a203c0b5f5a
ip=1;

# ╔═╡ 26965e38-91cd-4022-bdff-4c503f724bfe
md"""
Concentration index in solution
"""

# ╔═╡ c904c921-fa10-43eb-bd46-b2869fa7f431
ic=2;

# ╔═╡ b143c846-2294-47f7-a2d1-8a6eabe942a3
md"""
Generate breaktrough courve from transient solution
"""

# ╔═╡ 92e4e4ab-3485-4cb9-9b41-e702a211a477
function breakthrough(sys,tf,sol)
	of=similar(sol.t)
	t=sol.t
	of[1]=0
	for i=2:length(sol.t)
	 of[i]=-integrate(sys,tf,sol[i],sol[i-1],t[i]-t[i-1])[ic]
	end
	of
end

# ╔═╡ 3df8bace-b4f1-4052-84f7-dff21d3a35f0
md"""
Transient solver:
"""

# ╔═╡ e866db69-9388-4691-99f7-879cf0658418
function trsolve(grid;
	κ=[1.0e-3,5],
	D=[1.0e-12,1.0e-12],
	Δp=1,
	ϕ=[1,1],
	tend=100)

    function flux(y,u,edge)
        y[ip]=κ[edge.region]*(u[ip,1]-u[ip,2])
	bp,bm=fbernoulli_pm(y[ip]/D[edge.region])
        y[ic]=D[edge.region]*(bm*u[ic,1]-bp*u[ic,2])
    end

    function stor(y,u,node)
        y[ip]=0
        y[ic]=ϕ[node.region]*u[ic]
    end
    physics=VoronoiFVM.Physics(flux=flux,storage=stor)
    sys=VoronoiFVM.System(grid,physics)
    enable_species!(sys,ip,[1,2])
    enable_species!(sys,ic,[1,2])
	dim=dim_space(grid)

	for ibc ∈ (dim == 1 ? [Γ_in] : [Γ_left,Γ_in])
		boundary_dirichlet!(sys,ip,ibc,Δp)
    end

	for ibc ∈ (dim == 1 ? [Γ_out] : [Γ_right,Γ_out])
      boundary_dirichlet!(sys,ip,ibc,0)
    end

    boundary_dirichlet!(sys,ic,Γ_in,0)
    boundary_dirichlet!(sys,ic,Γ_out,0)

    control=VoronoiFVM.NewtonControl()
    control.Δt=1.0e-4
    control.Δt_min=1.0e-6
    control.verbose=false

    inival=VoronoiFVM.solve(unknowns(sys,inival=0),sys;control=control)
    factory=VoronoiFVM.TestFunctionFactory(sys)
    tfc=testfunction(factory,[Γ_in,Γ_left,Γ_top,Γ_bot],[Γ_out])

    function pre(sol,t)
	cin=min(1.0,1.0e3*t)
	boundary_dirichlet!(sys,ic,Γ_in,cin)
    end

    sol=VoronoiFVM.solve(inival,sys,[0,tend]; pre=pre,control=control)

    bt=breakthrough(sys,tfc,sol)
    if dim==1
		bt=bt*W
	end

    grid,sol,bt
end

# ╔═╡ cd88123a-b042-43e2-99b9-ec925a8794ed
grid_n,sol_n,bt_n=trsolve(grid_2d(nref=nref),tend=tend);

# ╔═╡ 61ed054e-7116-4a51-9d13-e07279acb2a4
scalarplot!(vis_n,grid_n,sol_n(t)[ic,:],levels=0:0.2:1,resolution=(400,150),show=true)

# ╔═╡ b0ad0adf-6f6c-4fb3-b58e-e05cc8c0c796
grid_1,sol_1,bt_1=trsolve(grid_1d(nref=nref),tend=tend);

# ╔═╡ e36d2aef-1b5a-45a7-9289-8d1e544bcedd
scalarplot(grid_1,sol_1(t)[ic,:],levels=0:0.2:1,resolution=(500,150),
xlabel="x",ylabel="c",title="1D calculation, t=$t")

# ╔═╡ 76b77ec0-27b0-4a02-9ae4-43d756eb09dd
grid_f,sol_f,bt_f=trsolve(grid_2d(nref=nref,ε_fix=ε_fix),tend=tend);

# ╔═╡ d1ce7f69-3703-4cb2-9bf3-4c5efccb479e
scalarplot!(vis_f,grid_f,sol_f(t)[ic,:],levels=0:0.2:1,resolution=(400,150),show=true)

# ╔═╡ 904b36f0-10b4-4db6-9252-21668305de9c
grid_ϕ,sol_ϕ,bt_ϕ=trsolve(grid_2d(nref=nref), ϕ=[1.0e-3,1],tend=tend);

# ╔═╡ ce49bb25-b2d0-4d17-a8fe-d7b62e9b20be
let
    p=PlutoVistaPlot(resolution=(500,200),xlabel="t",ylabel="outflow",
                     legend=:rb,
                     title="Breakthrough Curves")
    plot!(p, sol_n.t,bt_n,label="naive grid")
    plot!(p, sol_1.t,bt_1,label="1D grid",markertype=:x)
    plot!(p, sol_f.t,bt_f,label="grid with fix",markertype=:circle)
    plot!(p, sol_ϕ.t,bt_ϕ,label="modified ϕ",markertype=:cross)
end

# ╔═╡ 78d92b4a-bdb1-4117-ab9c-b422eac403b1
md"""
Reaction-Diffusion solver
"""

# ╔═╡ bb3a50ed-32e7-4305-87d8-4093c054a4d2
function rdsolve(grid;D=[1.0e-12,1.0],R=[1,0.1])

    function flux(y,u,edge)
        y[1]=D[edge.region]*(u[1,1]-u[1,2])
    end

	function rea(y,u,node)
        y[1]=R[node.region]*u[1]
    end

    physics=VoronoiFVM.Physics(flux=flux,reaction=rea)
    sys=VoronoiFVM.System(grid,physics)
    enable_species!(sys,1,[1,2])
  	dim=dim_space(grid)

    boundary_dirichlet!(sys,1,Γ_in,1)
    boundary_dirichlet!(sys,1,Γ_out,0)

    sol=VoronoiFVM.solve(unknowns(sys,inival=0),sys)
    factory=VoronoiFVM.TestFunctionFactory(sys)
    tf=testfunction(factory,[Γ_in,Γ_left,Γ_top,Γ_bot],[Γ_out])
   	of=integrate(sys,tf,sol)
	    fac=1.0
	if dim==1
		fac=W
	end
    grid,sol[1,:],of[1]*fac

end

# ╔═╡ 2f560406-d169-4027-9cfe-7689494edf45
rdgrid_1,rdsol_1,of_1=rdsolve(grid_1d(nref=nref))

# ╔═╡ 34228382-4b1f-4897-afdd-19db7d5a7c59
scalarplot(rdgrid_1,rdsol_1,resolution=(300,200))

# ╔═╡ a6714eac-9e7e-4bdb-beb7-aca354664ad6
rdgrid_n,rdsol_n,of_n=rdsolve(grid_2d(nref=nref))

# ╔═╡ 20d7624b-f43c-4ac2-bad3-383a9e4e1b42
 rdgrid_f,rdsol_f,of_f=rdsolve(grid_2d(nref=nref,ε_fix=ε_fix))

# ╔═╡ 6a6d0e94-8f0d-4119-945c-dd48ec0798fd
scalarplot(rdgrid_n,rdsol_n,resolution=(210,200)),
scalarplot(rdgrid_f,rdsol_f,resolution=(210,200))

# ╔═╡ c0fc1f71-52ba-41a9-92d1-74e82ac7826c
 rdgrid_r,rdsol_r,of_r=rdsolve(grid_2d(nref=nref),R=[0,0.1])

# ╔═╡ c08e86f6-b5c2-4762-af23-382b1b153f45
md"""
We measure the outflow at the outlet. As a result, we obtain:
   - 1D case: $(of_1)
   - 2D case, naive grid: $(of_n)
   - 2D case, grid with "protective layer": $(of_f)
   - 2D case, naive grid, "modified" R: $(of_r)

"""

# ╔═╡ 0cc1c511-f351-421f-991a-a27f26a8db4f
  html"<hr>"

# ╔═╡ 99c8458a-a584-4825-a983-ae1a05e50000
md"""
"Un-markdown" the next cell for debugging/developing
"""

# ╔═╡ b6b826a1-b52f-41d3-8feb-b6464f76352e
md"""
  begin
    using Pkg
    Pkg.activate(".testenv")
    Pkg.add("Revise")
    using Revise
    Pkg.develop("VoronoiFVM")
    Pkg.add(["PlutoVista", "PlutoUI","GridVisualize","ExtendableGrids"])
end
""";

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
ExtendableGrids = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
GridVisualize = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
PlutoVista = "646e1f28-b900-46d7-9d87-d554eb38a413"
VoronoiFVM = "82b139dc-5afc-11e9-35da-9b9bdfd336f3"

[compat]
ExtendableGrids = "~0.8.7"
GridVisualize = "~0.3.9"
PlutoUI = "~0.7.16"
PlutoVista = "~0.8.7"
VoronoiFVM = "~0.13.2"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.0-rc2"
manifest_format = "2.0"

[[deps.AbstractTrees]]
git-tree-sha1 = "03e0550477d86222521d254b741d470ba17ea0b5"
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
version = "0.3.4"

[[deps.Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[deps.ArnoldiMethod]]
deps = ["LinearAlgebra", "Random", "StaticArrays"]
git-tree-sha1 = "f87e559f87a45bece9c9ed97458d3afe98b1ebb9"
uuid = "ec485272-7323-5ecc-a04f-4719b315124d"
version = "0.1.0"

[[deps.ArrayInterface]]
deps = ["Compat", "IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"]
git-tree-sha1 = "d9352737cef8525944bf9ef34392d756321cbd54"
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
version = "3.1.38"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.Cassette]]
git-tree-sha1 = "6ce3cd755d4130d43bab24ea5181e77b89b51839"
uuid = "7057c7e9-c182-5462-911a-8362d720325c"
version = "0.3.9"

[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "3533f5a691e60601fe60c90d8bc47a27aa2907ec"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.11.0"

[[deps.ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "a851fec56cb73cfdf43762999ec72eff5b86882a"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.15.0"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[deps.Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[deps.CommonSubexpressions]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
version = "0.3.0"

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "dce3e3fea680869eaa0b774b2e8343e9ff442313"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.40.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[deps.DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[deps.DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.3"

[[deps.DiffRules]]
deps = ["NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "7220bc21c33e990c14f4a9a319b1d242ebc5b269"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.3.1"

[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"

[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[deps.EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[deps.ElasticArrays]]
deps = ["Adapt"]
git-tree-sha1 = "a0fcc1bb3c9ceaf07e1d0529c9806ce94be6adf9"
uuid = "fdbdab4c-e67f-52f5-8c3f-e7b388dad3d4"
version = "1.2.9"

[[deps.ExtendableGrids]]
deps = ["AbstractTrees", "Dates", "DocStringExtensions", "ElasticArrays", "InteractiveUtils", "LinearAlgebra", "Printf", "Random", "SparseArrays", "Test"]
git-tree-sha1 = "1e8e50f054057f23e908fbd6935766dca6293cc2"
uuid = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
version = "0.8.7"

[[deps.ExtendableSparse]]
deps = ["DocStringExtensions", "LinearAlgebra", "Printf", "Requires", "SparseArrays", "SuiteSparse", "Test"]
git-tree-sha1 = "0341e41e45e6c5e46be89c33543082a0a867707c"
uuid = "95c220a8-a1cf-11e9-0c77-dbfce5f500b3"
version = "0.6.5"

[[deps.FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "2db648b6712831ecb333eae76dbfd1c156ca13bb"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.11.2"

[[deps.FillArrays]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "8756f9935b7ccc9064c6eef0bff0ad643df733a3"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "0.12.7"

[[deps.FiniteDiff]]
deps = ["ArrayInterface", "LinearAlgebra", "Requires", "SparseArrays", "StaticArrays"]
git-tree-sha1 = "8b3c09b56acaf3c0e581c66638b85c8650ee9dca"
uuid = "6a86dc24-6348-571c-b903-95158fe2bd41"
version = "2.8.1"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "63777916efbcb0ab6173d09a658fb7f2783de485"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "0.10.21"

[[deps.GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "58bcdf5ebc057b085e58d95c138725628dd7453c"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.1"

[[deps.GridVisualize]]
deps = ["ColorSchemes", "Colors", "DocStringExtensions", "ElasticArrays", "ExtendableGrids", "GeometryBasics", "LinearAlgebra", "Observables", "OrderedCollections", "PkgVersion", "Printf", "Requires", "StaticArrays"]
git-tree-sha1 = "925ba2f11df005d894b113292d32fca9afe3f8c8"
uuid = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
version = "0.3.9"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
git-tree-sha1 = "5efcf53d798efede8fee5b2c8b09284be359bf24"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.2"

[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[deps.IfElse]]
git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1"
uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
version = "0.1.1"

[[deps.Inflate]]
git-tree-sha1 = "f5fc07d4e706b84f72d54eedcc1c13d92fb0871c"
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
version = "0.1.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "f0c6489b12d28fb4c2103073ec7452f3423bd308"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.1"

[[deps.IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[deps.IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[deps.IterativeSolvers]]
deps = ["LinearAlgebra", "Printf", "Random", "RecipesBase", "SparseArrays"]
git-tree-sha1 = "1a8c6237e78b714e901e406c096fc8a65528af7d"
uuid = "42fd0dbc-a981-5370-80f2-aaf504508153"
version = "0.9.1"

[[deps.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[deps.JLD2]]
deps = ["DataStructures", "FileIO", "MacroTools", "Mmap", "Pkg", "Printf", "Reexport", "TranscodingStreams", "UUIDs"]
git-tree-sha1 = "46b7834ec8165c541b0b5d1c8ba63ec940723ffb"
uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
version = "0.4.15"

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LightGraphs]]
deps = ["ArnoldiMethod", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"]
git-tree-sha1 = "432428df5f360964040ed60418dd5601ecd240b6"
uuid = "093fc24a-ae57-5d10-9952-331d41423f4d"
version = "1.3.5"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.LogExpFunctions]]
deps = ["ChainRulesCore", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "6193c3815f13ba1b78a51ce391db8be016ae9214"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.4"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.9"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[deps.NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.Observables]]
git-tree-sha1 = "fe29afdef3d0c4a8286128d4e45cc50621b1e43d"
uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
version = "0.4.0"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"

[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[deps.OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "d911b6a12ba974dabe2291c6d450094a7226b372"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.1.1"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[deps.PkgVersion]]
deps = ["Pkg"]
git-tree-sha1 = "a7a7e1a88853564e551e4eba8650f8c38df79b37"
uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
version = "0.1.1"

[[deps.PlutoUI]]
deps = ["Base64", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "4c8a7d080daca18545c56f1cac28710c362478f3"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.16"

[[deps.PlutoVista]]
deps = ["ColorSchemes", "Colors", "DocStringExtensions", "GridVisualize", "UUIDs"]
git-tree-sha1 = "b99d4e38e7dba4535cee937e0444aed5912245d0"
uuid = "646e1f28-b900-46d7-9d87-d554eb38a413"
version = "0.8.7"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.RecipesBase]]
git-tree-sha1 = "44a75aa7a527910ee3d1751d1f0e4148698add9e"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.2"

[[deps.RecursiveArrayTools]]
deps = ["ArrayInterface", "ChainRulesCore", "DocStringExtensions", "FillArrays", "LinearAlgebra", "RecipesBase", "Requires", "StaticArrays", "Statistics", "ZygoteRules"]
git-tree-sha1 = "c944fa4adbb47be43376359811c0a14757bdc8a8"
uuid = "731186ca-8d62-57ce-b412-fbd966d074cd"
version = "2.20.0"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[deps.SimpleTraits]]
deps = ["InteractiveUtils", "MacroTools"]
git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
version = "0.9.4"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.SparseDiffTools]]
deps = ["Adapt", "ArrayInterface", "Compat", "DataStructures", "FiniteDiff", "ForwardDiff", "LightGraphs", "LinearAlgebra", "Requires", "SparseArrays", "StaticArrays", "VertexSafeGraphs"]
git-tree-sha1 = "999444c932d3bd32e7cb5a349e219bec9cfa2bd5"
uuid = "47a9eef4-7e08-11e9-0b38-333d64bd3804"
version = "1.17.1"

[[deps.SparsityDetection]]
deps = ["Cassette", "DocStringExtensions", "LinearAlgebra", "SparseArrays", "SpecialFunctions"]
git-tree-sha1 = "9e182a311d169cb9fe0c6501aa252983215fe692"
uuid = "684fba80-ace3-11e9-3d08-3bc7ed6f96df"
version = "0.3.4"

[[deps.SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "f0bccf98e16759818ffc5d97ac3ebf87eb950150"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.8.1"

[[deps.Static]]
deps = ["IfElse"]
git-tree-sha1 = "e7bc80dc93f50857a5d1e3c8121495852f407e6a"
uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
version = "0.4.0"

[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3c76dde64d03699e074ac02eb2e8ba8254d428da"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.13"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "2ce41e0d042c60ecd131e9fb7154a3bfadbf50d3"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.3"

[[deps.SuiteSparse]]
deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "fed34d0e71b91734bf0a7e10eb1bb05296ddbcd0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.0"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.VertexSafeGraphs]]
deps = ["LightGraphs"]
git-tree-sha1 = "b9b450c99a3ca1cc1c6836f560d8d887bcbe356e"
uuid = "19fa3120-7c27-5ec5-8db8-b0b0aa330d6f"
version = "0.1.2"

[[deps.VoronoiFVM]]
deps = ["DiffResults", "DocStringExtensions", "ExtendableGrids", "ExtendableSparse", "ForwardDiff", "GridVisualize", "IterativeSolvers", "JLD2", "LinearAlgebra", "Printf", "RecursiveArrayTools", "SparseArrays", "SparseDiffTools", "SparsityDetection", "StaticArrays", "SuiteSparse", "Test"]
git-tree-sha1 = "65f1d72aa575d3d6c348fcfc0d0d905fdbd7f5c3"
uuid = "82b139dc-5afc-11e9-35da-9b9bdfd336f3"
version = "0.13.2"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[deps.ZygoteRules]]
deps = ["MacroTools"]
git-tree-sha1 = "8c1a8e4dfacb1fd631745552c8db35d0deb09ea0"
uuid = "700de1a5-db45-46bc-99cf-38207098b444"
version = "0.2.2"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╠═60941eaa-1aea-11eb-1277-97b991548781
# ╠═556480e0-94f1-4e47-be9a-3e1e0e99555c
# ╟─5e13b3db-570c-4159-939a-7e2268f0a102
# ╟─fae47c55-eef8-4428-bb5f-45824978753d
# ╟─8ba2300c-17ff-44e1-b33a-c5bdf1ce12fe
# ╟─51c9517c-8797-4406-b053-301694fb0484
# ╟─99341e32-9c78-4e31-bec0-d1ffbc85ec32
# ╟─46a0f078-4165-4e37-9e69-e69af8584f6e
# ╟─cc325b2c-6174-4b8d-8e39-202ac68b5705
# ╠═3f693666-4026-4c01-a7aa-8c7dcbc32372
# ╟─cd013964-f329-4d2c-ae4b-305093f0ac56
# ╠═afccbadb-2ca8-4c3e-8c6d-c78df59d8d7e
# ╠═dd9f8d38-8812-40ba-88c8-f873ec7d6121
# ╠═5f6ac608-b1a0-450e-910e-d7d8ea2ffae0
# ╠═cd88123a-b042-43e2-99b9-ec925a8794ed
# ╠═b0ad0adf-6f6c-4fb3-b58e-e05cc8c0c796
# ╠═76b77ec0-27b0-4a02-9ae4-43d756eb09dd
# ╠═904b36f0-10b4-4db6-9252-21668305de9c
# ╟─ce49bb25-b2d0-4d17-a8fe-d7b62e9b20be
# ╟─5b60c7d4-7bdb-4989-b055-6695b9fdeedc
# ╟─f6abea66-1e42-4201-8433-5d092989749d
# ╟─e36d2aef-1b5a-45a7-9289-8d1e544bcedd
# ╟─98ae56dd-d42d-4a93-bb0b-5956b6e981a3
# ╟─61ed054e-7116-4a51-9d13-e07279acb2a4
# ╟─d1ce7f69-3703-4cb2-9bf3-4c5efccb479e
# ╟─99c3b54b-d458-482e-8aa0-d2c2b51fdf25
# ╟─eef85cd7-eba4-4c10-9e1d-38411179314d
# ╠═2f560406-d169-4027-9cfe-7689494edf45
# ╠═a6714eac-9e7e-4bdb-beb7-aca354664ad6
# ╠═20d7624b-f43c-4ac2-bad3-383a9e4e1b42
# ╠═c0fc1f71-52ba-41a9-92d1-74e82ac7826c
# ╟─c08e86f6-b5c2-4762-af23-382b1b153f45
# ╠═34228382-4b1f-4897-afdd-19db7d5a7c59
# ╟─6a6d0e94-8f0d-4119-945c-dd48ec0798fd
# ╟─fcd066f1-bcd8-4479-a4e4-7b8c235336c4
# ╟─c9d92201-813c-499b-b863-b138c30eb634
# ╟─a372ac90-c871-4dc0-a44b-a5bddef71823
# ╟─124b2a0a-ef19-453e-9e3a-5b5ce7db5fac
# ╠═1ad18670-e7cb-4f7a-be0f-3db98cdeb6a4
# ╟─47bc8e6a-e296-42c9-bfc5-967edfb0feb7
# ╠═d1d5bad2-d282-4e7d-adb9-baf21f58155e
# ╠═9d736062-6821-46d9-9e49-34b43b78e814
# ╠═83b9931f-9020-4400-8aeb-31ad391184db
# ╠═c402f03c-746a-45b8-aaac-902a2f196094
# ╟─e33bf4b7-7b9a-4c5c-be4f-874af949c5ef
# ╟─a63a655c-e48b-4969-9409-31cd3db3bdaa
# ╠═d7009231-4b43-44bf-96ba-9a203c0b5f5a
# ╟─26965e38-91cd-4022-bdff-4c503f724bfe
# ╠═c904c921-fa10-43eb-bd46-b2869fa7f431
# ╟─b143c846-2294-47f7-a2d1-8a6eabe942a3
# ╠═92e4e4ab-3485-4cb9-9b41-e702a211a477
# ╟─3df8bace-b4f1-4052-84f7-dff21d3a35f0
# ╠═e866db69-9388-4691-99f7-879cf0658418
# ╟─78d92b4a-bdb1-4117-ab9c-b422eac403b1
# ╠═bb3a50ed-32e7-4305-87d8-4093c054a4d2
# ╟─0cc1c511-f351-421f-991a-a27f26a8db4f
# ╟─99c8458a-a584-4825-a983-ae1a05e50000
# ╠═b6b826a1-b52f-41d3-8feb-b6464f76352e
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


using MLBase
using Base.Test

## Kfold

ss = collect(Kfold(12, 3))
@test length(ss) == 3
for i = 1:3
	@test isa(ss[i], Vector{Int})
	@test issorted(ss[i])
end
x = vcat(ss...)
@test sort(x) == [1:12]

## LOOCV

ss = collect(LOOCV(4))
@test length(ss) == 4

@test ss[1] == [2, 3, 4]
@test ss[2] == [1, 3, 4]
@test ss[3] == [1, 2, 4]
@test ss[4] == [1, 2, 3]

## RandomSub

# temporary disable until StatsBase passes travis

# ss = collect(RandomSub(10, 5, 6))
# @test length(ss) == 6
# for i = 1:6
# 	@test length(ss[i]) == 5
# 	@test 1 <= minimum(ss[i]) <= maximum(ss[i]) <= 10
# 	@test length(unique(ss[i])) == 5
# end


"""
Package: ForecastPlots

    pacf(x,
         type = "step-real",
         lag = Integer(ceil(10*log10(length(x)))),
         alpha = (0.95,0.99);
         plot = true)

Compute the partial auto-correlation for an univariate series with an optional plot.

There are two versions; the "step" version estimates the auto-regressive parameters of an increasing model, the "real" version estimates the actual partial auto-correlation by eliminating the linear information provided by the lags. When using the default type "stepwise-real" both versions are calculated.

The distribution used to estimate the confidence intervals is an approximation of a Fisher Transformation via a Normal Distribution. There is a plot recipe for the returned object.

# Arguments
- `x`: Vector of data.
- `type` = Valid values are "stepwise", "real" and "stepwise-real".
- `lag`: Maximum number of lags.
- `alpha`: A tuple with two thresholds (t1,t2) with t1 <= t2 to plot confidence intervals. The default values are 0.95 and 0.99.
- `plot`: When true displays a plot with the results.

# Returns
A CF object

# Examples
```julia-repl
x = rand(100);
pacf(x);
pacf(x; plot=false)
20×2 Matrix{Float64}:
 -0.0475437   -0.101204
  0.0407006    0.058935
 -0.0747269    0.0366026
```
"""
function pacf(x::AbstractVector{<:Real};
              type::String = "stepwise-real",
              lag::Integer = Integer(ceil(10*log10(length(x)))),
              alpha::Tuple{Real,Real} = (0.95,0.99),
              plot::Bool = true,
              kw...)

    N = length(x)
    @assert type in  ["stepwise","real","stepwise-real"] "The options for `type` are `stepwise`, `real` and `stepwise-real`"
    @assert 1 <= lag <= N-4
    @assert length(alpha) == 2
    @assert 0.0 < alpha[1] < alpha[2] < 1.0

    # Confidence Intervals
    function fse(v::Real)::AbstractFloat
        1/sqrt(v-3)
    end

    a1 = alpha[1]
    a2 = alpha[2]
    z1 = quantile(Normal(), a1 + (1-a1)/2)
    ci1 = z1*fse(N-lag)
    z2 = quantile(Normal(), a2 + (1-a2)/2)
    ci2 = z2*fse(N-lag)
    ci = (ci1,ci2)

    call = "pacf(x"*
        "; type=\""*type*
        "\", lag="*string(lag)*
        ", alpha="*string(alpha)*")"

    if (type::String == "stepwise")
        pac = pacf_stepwise(x; lag, alpha)
        plot && display(cf_gr((cres=pac, N=N, type="pacf_stepwise", lag, alpha, ci, auto=true, kw...)))
        return pac
    end

    if (type::String == "real")
        pac = pacf_real(x; lag, alpha)
        plot && display(cf_gr((cres=pac, N=N, type="pacf_real", lag, alpha, ci, auto=true, kw...)))
        return pac
    end

    if type::String == "stepwise-real"
        pacs = pacf_stepwise(x; lag, alpha)
        pacr = pacf_real(x; lag, alpha)
        plot && display(cf_gr((cres=hcat(pacs, pacr), N=N, type="pacf_stepwise-real", lag, alpha, ci, auto=true, kw...)))
        return hcat(pacs, pacr)
    end

end

function pacf_lag(x::AbstractVector{<:Real};
                   lag::Integer = Integer(ceil(10*log10(length(x)))),
                   alpha::Tuple{Real,Real} = (0.95,0.99))

    R = vcat(cov(x,x),acf(x; type = "cov", lag, alpha, plot=false))
    p = length(R)-1

    A = zeros(p,p)
    for i in p:-1:1
        A[i,i:end] = R[1:p-i+1]
        A[i:end,i] = R[1:p-i+1]
    end
    b = R[2:end]

    lambda = 10^-36
    (A'*A+lambda*I(p))\(A'*b)

end

function pacf_stepwise(x::AbstractVector{<:Real};
                   lag::Integer = Integer(ceil(10*log10(length(x)))),
                   alpha::Tuple{Real,Real} = (0.95,0.99))

    #TODO optimize with Levinson-Durbin algorithm
    pac = []
    for i in 1:lag
        push!(pac, pacf_lag(x; lag = i)[end])
    end
    pac

end

function pacf_real(x::AbstractVector{<:Real};
             lag::Integer = Integer(ceil(10*log10(length(x)))),
             alpha::Tuple{Real,Real} = (0.95,0.99))

    # prevent singularities
    lambda = 10^-36

    N = length(x)
    A = zeros(N-lag,lag+1)
    for i in 1:(N-lag)
        A[i,:] = x[i:i+lag]
    end
    bt1 = A[:,1]
    A = A[:,2:end]
    A = hcat(A,repeat([1.0],N-lag))

    pacf_res = []
    for i in 2:lag+1
        A1i = drop(A, c=i-1)
        lsq = (A1i'*A1i+lambda*I(lag))\(A1i'*bt1)
        r1 = round.(bt1.-A1i*lsq, digits=10)

        bt0 = A[:,i-1]
        lsq = (A1i'*A1i+lambda*I(lag))\(A1i'*bt0)
        r0 = round.(bt0.-A1i*lsq, digits=10)
        push!(pacf_res,cor(r0,r1))

    end

    pacf_res
end

"""
Package: ForecastPlots

    drop(M;r,c)

Drop rows and columns from a matrix.
"""
function drop(M::AbstractMatrix; r=[], c=[])
    s = size(M)
    dr = collect(1:s[1])
    dc = collect(1:s[2])
    splice!(dr,r)
    splice!(dc,c)
    M[dr,dc]
end