module LDB
export
    # energy map
    EnergyMap,
    TimeFrequency,
    ProbabilityDensity,
    Signatures,
    energy_map,
    # discriminant measures
    DiscriminantMeasure,
    ProbabilityDensityDM,
    SignaturesDM,
    AsymmetricRelativeEntropy,
    SymmetricRelativeEntropy,
    HellingerDistance,
    LpDistance,
    EarthMoverDistance,
    discriminant_measure,
    # discriminant power
    DiscriminantPower,
    BasisDiscriminantMeasure,
    FishersClassSeparability,
    RobustFishersClassSeparability,
    discriminant_power,
    # local discriminant basis
    LocalDiscriminantBasis,
    fit!,
    fitdec!,
    fit_transform,
    transform,
    inverse_transform,
    change_nfeatures

using LinearAlgebra,
      Wavelets,
      Statistics
import Parameters: @with_kw
import StatsBase: mad

using ..Utils,
      ..DWT
import ..BestBasis: bestbasis_treeselection

include("ldb_energymap.jl")
include("ldb_measures.jl")

## LOCAL DISCRIMINANT BASIS
"""
    LocalDiscriminantBasis

Class type for the Local Discriminant Basis (LDB), a feature selection algorithm developed
by N. Saito and R. Coifman in "Local Discriminant Bases and Their Applications" in the
Journal of Mathematical Imaging and Vision, Vol 5, 337-358 (1995). This struct contains the
following field values:

# Parameters and Attributes:
- `wt::DiscreteWavelet`: (Default: `wavelet(WT.haar)`) A discrete wavelet for transform
  purposes.
- `max_dec_level::Union{Integer, Nothing}`: (Default: `nothing`) Max level of wavelet packet
  decomposition to be computed.
- `dm::DiscriminantMeasure`: (Default: `AsymmetricRelativeEntropy()`) the discriminant
    measure for the LDB algorithm. Supported measures are the `AsymmetricRelativeEntropy()`,
    `LpDistance()`, `SymmetricRelativeEntropy()`, and `HellingerDistance()`.
- `en::EnergyMap`: (Default: `TimeFrequency()`) the type of energy map used. Supported maps
    are `TimeFrequency()`, `ProbabilityDensity()`, and `Signatures()`.
- `dp::DiscriminantPower()`: (Default: `BasisDiscriminantMeasure()`) the measure of
    discriminant power among expansion coefficients. Supported measures are
    `BasisDiscriminantMeasure()`, `FishersClassSeparability()`, and
    `RobustFishersClassSeparability()`.
- `top_k::Union{Integer, Nothing}`: (Default: `nothing`) the top-k coefficients used in each
    node to determine the discriminant measure.
- `n_features::Union{Integer, Nothing}`: (Default: `nothing`) the dimension of output after
    undergoing feature selection and transformation.
- `sz::Union{Vector{T} where T<:Integer, Nothing}`: (Default: `nothing`) Size of signal
- `Γ::Union{AbstractArray{T} where T<:AbstractFloat, AbstractArray{NamedTuple{(:coef,
  :weight), Tuple{S₁, S₂}}} where {S₁<:Array{T} where T<:AbstractFloat, S2<:Union{T,
  Array{T}} where T<:AbstractFloat}, Nothing}`: (Default: `nothing`) computed energy map
- `DM::Union{AbstractArray{<:AbstractFloat}, Nothing}`: (Default: `nothing`) computed
  discriminant measure
- `cost::Union{AbstractVector{<:AbstractFloat}, Nothing}`: (Default: `nothing`) computed
    wavelet packet decomposition (WPD) tree cost based on the discriminant measure `DM`.
- `tree::Union{BitVector, Nothing}`: (Default: `nothing`) computed best WPD tree based on
    the discriminant measure `DM`.
- `DP::Union{AbstractVector{<:AbstractFloat}, Nothing}`: (Default: `nothing`) computed
  discriminant power
- `order::Union{AbstractVector{Integer}, Nothing}`: (Default: `nothing`) ordering of `DP` by
  descending order.
"""
@with_kw mutable struct LocalDiscriminantBasis
    # to be declared by user
    wt::DiscreteWavelet = wavelet(WT.haar)
    max_dec_level::Union{Integer, Nothing} = nothing
    dm::DiscriminantMeasure = AsymmetricRelativeEntropy()
    en::EnergyMap = TimeFrequency()
    dp::DiscriminantPower = BasisDiscriminantMeasure()
    top_k::Union{Integer, Nothing} = nothing
    n_features::Union{Integer, Nothing} = nothing
    # to be computed in fit! or fitdec!
    sz::Union{Tuple{Vararg{T,N}} where {T<:Integer,N}, Nothing} = nothing
    Γ::Union{AbstractArray{<:AbstractFloat},
             AbstractArray{NamedTuple{(:coef, :weight), Tuple{S1, S2}}} where
                {S1<:Array{T} where T<:AbstractFloat,
                 S2<:Union{AbstractFloat, Array{<:AbstractFloat}}},
             Nothing} = nothing
    DM::Union{AbstractArray{<:AbstractFloat}, Nothing} = nothing
    cost::Union{AbstractVector{<:AbstractFloat}, Nothing} = nothing
    tree::Union{BitVector, Nothing} = nothing
    DP::Union{AbstractArray{<:AbstractFloat}, Nothing} = nothing
    order::Union{AbstractVector{Integer}, Nothing} = nothing
end

"""
    fit!(f, X, y)

Fits the Local Discriminant Basis feature selection algorithm `f` onto the signals `X` with
labels `y`.

# Arguments
- `f::LocalDiscriminantBasis`: Local discriminant basis object.
- `X::AbstractArray{S} where S<:AbstractFloat`: Group of signals of the same size, arranged
  in the 2nd (1D signals) or 3rd (2D signals) dimension.
- `y::AbstractVector{T} where T`: Labels corresponding to each signal in X.

# Returns
- `f::LocalDiscriminantBasis`: The same LDB object `f` with updated fields.

# Examples
```julia
using Wavelets, WaveletsExt

X, y = generateclassdata(ClassData(:tri, 5, 5, 5))
f = LocalDiscriminantBasis()
fit!(f, X, y)
```

**See also:** [`LocalDiscriminantBasis`](@ref), [`fit_transform`](@ref),
    [`transform`](@ref), [`inverse_transform`](@ref), [`change_nfeatures`](@ref)
"""
function fit!(f::LocalDiscriminantBasis, X::AbstractArray{S}, y::AbstractVector{T}) where
             {S<:AbstractFloat, T}

    # basic summary of data
    @assert 2 ≤ ndims(X) ≤ 3
    sz = size(X)[1:end-1]
    L = maxtransformlevels(min(sz...))
    # change LocalDiscriminantBasis parameters if necessary
    f.max_dec_level = isnothing(f.max_dec_level) ? L : f.max_dec_level
    @assert 1 ≤ f.max_dec_level ≤ L

    # wavelet packet decomposition
    Xw = wpdall(X, f.wt, f.max_dec_level)

    # fit local discriminant basis
    fitdec!(f, Xw, y)
    return nothing
end

"""
    fitdec!(f, Xw, y)

Fits the Local Discriminant Basis feature selection algorithm `f` onto the decomposed
signals `Xw` with labels `y`.

# Arguments
- `f::LocalDiscriminantBasis`: Local discriminant basis object.
- `Xw::AbstractArray{S} where S<:AbstractFloat`: Group of decomposed signals of the same
  size, arranged in the 3rd (1D signals) or 4th (2D signals) dimension.
- `y::AbstractVector{T} where T`: Labels corresponding to each signal in X.

# Returns
- `f::LocalDiscriminantBasis`: The same LDB object `f` with updated fields.

# Examples
```julia
using Wavelets, WaveletsExt

X, y = generateclassdata(ClassData(:tri, 5, 5, 5))
f = LocalDiscriminantBasis()
Xw = wpdall(X, f.wt)
fitdec!(f, Xw, y)
```

**See also:** [`LocalDiscriminantBasis`](@ref), [`fit!`](@ref), [`fit_transform`](@ref),
    [`transform`](@ref), [`inverse_transform`](@ref), [`change_nfeatures`](@ref)
"""
function fitdec!(f::LocalDiscriminantBasis, Xw::AbstractArray{S}, y::AbstractVector{T}) where
                {S<:AbstractFloat, T}
    # basic summary of data
    @assert 3 ≤ ndims(Xw) ≤ 4
    c = unique(y)       # unique classes
    nc = length(c)      # number of classes
    Ny = length(y)
    f.sz = size(Xw)[1:end-2]
    L = size(Xw)[end-1]
    Nx = size(Xw)[end]
    nelem = prod(f.sz)  # total number of elements in each signal

    # change LocalDiscriminantBasis parameters if necessary
    f.top_k = isnothing(f.top_k) ? nelem : f.top_k
    f.n_features = isnothing(f.n_features) ? nelem : f.n_features
    f.max_dec_level = isnothing(f.max_dec_level) ? L-1 : f.max_dec_level

    # parameter checking
    @assert Nx == Ny
    @assert 1 ≤ f.top_k ≤ nelem
    @assert 1 ≤ f.n_features ≤ nelem
    @assert f.max_dec_level+1 == L
    @assert 1 ≤ f.max_dec_level ≤ maxtransformlevels(min(f.sz...))
    @assert nc > 1

    # --- Construct energy map for each class ---
    f.Γ = energy_map(Xw, y, f.en)

    # --- Compute discriminant measure D and obtain tree cost ---
    f.DM = discriminant_measure(f.Γ, f.dm)
    tree_type = ndims(Xw) == 3 ? :binary : :quad
    cost_len = ndims(Xw) == 3 ? gettreelength(1<<L) : gettreelength(1<<L,1<<L)
    f.cost = Vector{S}(undef, cost_len)
    for i in eachindex(f.cost)
        d = getdepth(i, tree_type)    # node level
        # Extract coefficients for current node
        if tree_type == :binary
            θ = i - 1<<d
            nθ = nodelength(f.sz..., d)         # Number of elements in current node
            rng = (θ*nθ+1):((θ+1)*nθ)
            DMθ = f.DM[rng,d+1]
        else
            rng₁ = getrowrange(f.sz[1], i)
            rng₂ = getcolrange(f.sz[2], i)
            nθ = length(rng₁) * length(rng₂)    # Number of elements in current node
            DMθ = f.DM[rng₁,rng₂,d+1] |> vec
        end
        # Compute cost
        if f.top_k < nθ
            sort!(DMθ, rev=true)
            f.cost[i] = sum(DMθ[1:f.top_k])
        else
            f.cost[i] = sum(DMθ)
        end
    end

    # --- Select best tree and best set of expansion coefficients ---
    f.tree = bestbasis_treeselection(f.cost, f.sz..., :max)
    Xc = getbasiscoefall(Xw, f.tree)

    # --- Obtain and order basis functions by power of discrimination ---
    (f.DP, f.order) = f.dp == BasisDiscriminantMeasure() ?
                      discriminant_power(f.DM, f.tree, f.dp) :
                      discriminant_power(Xc, y, f.dp)
    return nothing
end

"""
    transform(f, X)

Extract the LDB features from signals `X` based on the LDB tree constructed from previously
fitted signals.

# Arguments
- `f::LocalDiscriminantBasis`: Local discriminant basis object.
- `X::AbstractArray{S} where S<:AbstractFloat`: Group of signals of the same size, arranged
  in the 2nd (1D signals) or 3rd (2D signals) dimension.

# Returns
- `Xc::Matrix{S}`: A matrix of the extracted LDB features from `X`, where each column
  corresponds to the coefficients extracted from the corresponding signal.

# Examples
```julia
using Wavelets, WaveletsExt

X, y = generateclassdata(ClassData(:tri, 5, 5, 5))
f = LocalDiscriminantBasis()
fit!(f, X, y)
Xc = transform(f, X)
```

**See also:** [`LocalDiscriminantBasis`](@ref), [`fit!`](@ref), [`fit_transform`](@ref),
    [`inverse_transform`](@ref), [`change_nfeatures`](@ref)
"""
function transform(f::LocalDiscriminantBasis, X::AbstractArray{T}) where T
    # check necessary measurements
    sz = size(X)[1:end-1]
    N = size(X)[end]
    nelem = prod(f.sz)  # total number of elements in each signal
    @assert 2 ≤ ndims(X) ≤ 3
    @assert !isnothing(f.max_dec_level)
    @assert !isnothing(f.top_k)
    @assert !isnothing(f.n_features)
    @assert !isnothing(f.sz)
    @assert !isnothing(f.Γ)
    @assert !isnothing(f.DM)
    @assert !isnothing(f.cost)
    @assert !isnothing(f.tree)
    @assert !isnothing(f.DP)
    @assert !isnothing(f.order)
    @assert sz == f.sz
    @assert 1 ≤ f.max_dec_level ≤ maxtransformlevels(min(f.sz...))
    @assert 1 ≤ f.top_k ≤ nelem
    @assert 1 ≤ f.n_features ≤ nelem

    # wpt on X based on given f.tree
    Xw = wptall(X, f.wt, f.tree)
    # Extract best features
    Xc = Array{T,2}(undef, f.n_features, N)
    for (i, xw) in enumerate(eachslice(Xw, dims=ndims(Xw)))
        Xc[:,i] = xw[f.order[1:f.n_features]]
    end
    return Xc
end

"""
    fit_transform(f, X, y)

Fit and transform the signals `X` with labels `y` based on the LDB class `f`. This function
essentially combines both `fit!` and `transform` into one, and is the preferred method when
trying to extract the LDB coefficients of the training dataset. For the test dataset, one
needs to first run `fit!` on the training dataset before running `transform` on the test
dataset.

# Arguments
- `f::LocalDiscriminantBasis`: Local discriminant basis object.
- `Xw::AbstractArray{S} where S<:AbstractFloat`: Group of decomposed signals of the same
  size, arranged in the 3rd (1D signals) or 4th (2D signals) dimension.
- `y::AbstractVector{T} where T`: Labels corresponding to each signal in X.

# Returns
- `Xc::Matrix{S}`: A matrix of the extracted LDB features from `X`, where each column
  corresponds to the coefficients extracted from the corresponding signal.

# Examples
```julia
using Wavelets, WaveletsExt

X, y = generateclassdata(ClassData(:tri, 5, 5, 5))
f = LocalDiscriminantBasis()
Xc = fit_transform(f, X, y)
```

**See also:** [`LocalDiscriminantBasis`](@ref), [`fit!`](@ref), [`transform`](@ref),
    [`inverse_transform`](@ref), [`change_nfeatures`](@ref)
"""
function fit_transform(f::LocalDiscriminantBasis,
                       X::AbstractArray{S},
                       y::AbstractVector{T}) where {S<:AbstractFloat, T}
    # get necessary measurements
    @assert 2 ≤ ndims(X) ≤ 3
    sz = size(X)[1:end-1]
    N = size(X)[end]
    f.max_dec_level = isnothing(f.max_dec_level) ? maxtransformlevels(min(sz...)) : f.max_dec_level
    @assert 1 ≤ f.max_dec_level ≤ maxtransformlevels(min(sz...))

    # wpd on X
    Xw = wpdall(X, f.wt, f.max_dec_level)

    # fit LDB and return best features
    fitdec!(f, Xw, y)
    Xw = getbasiscoefall(Xw, f.tree)
    # Extract best features
    Xc = Array{S,2}(undef, f.n_features, N)
    for (i, xw) in enumerate(eachslice(Xw, dims=ndims(Xw)))
        Xc[:,i] = xw[f.order[1:f.n_features]]
    end
    return Xc
end

"""
    inverse_transform(f, X)

Compute the inverse transform on the feature matrix `X` to form the original signal based on
the LDB class `f`. This function can be used to reconstruct the signals and compare the
reconstructed features between different classes.

# Arguments
- `f::LocalDiscriminantBasis`: Local discriminant basis object.
- `X::Matrix{S} where S<:AbstractFloat`: A matrix of the extracted LDB features from
  original signals, where each column corresponds to the coefficients extracted from the
  corresponding signal.

# Returns
- `Xₜ::Array{S}`: Reconstructed signals.

# Examples
```julia
X, y = generateclassdata(ClassData(:tri, 5, 5, 5))
f = LocalDiscriminantBasis()
Xc = fit_transform(f, X, y)
Xₜ = inverse_transform(f, Xc)
```

**See also:** [`LocalDiscriminantBasis`](@ref), [`fit!`](@ref), [`fit_transform`](@ref),
    [`transform`](@ref), [`change_nfeatures`](@ref)
"""
function inverse_transform(f::LocalDiscriminantBasis, X::AbstractArray{T,2}) where
                           T<:AbstractFloat
    # get necessary measurements
    @assert size(X,1) == f.n_features
    N = size(X,2)

    # insert the features x into the full matrix X padded with 0's
    Xc = zeros(T, (f.sz..., N))
    @views for (xc, x) in zip(eachslice(Xc, dims=ndims(Xc)), eachslice(X, dims=2))
        for (i,j) in enumerate(f.order[1:f.n_features])
            xc[j] = x[i]
        end
    end

    # iwpt on X
    Xₜ = iwptall(Xc, f.wt, f.tree)
    return Xₜ
end

"""
    change_nfeatures(f, x, n_features)

Change the number of features extracted from the signals from `f.n_features` to
`n_features`.

!!! warning If the input `n_features` is larger than `f.n_features`, it results in the
    regeneration of signals based on the current `f.n_features` before reselecting the
    features. This will cause additional features to be less accurate and effective. Often
    times, the "additional" features tend to be zeros.

# Arguments
- `f::LocalDiscriminantBasis`: Local discriminant basis object.
- `x::AbstractMatrix{S} where S<:AbstractFloat`: A matrix of the extracted LDB features from
  original signals, where each column corresponds to the coefficients extracted from the
  corresponding signal.
- `n_features::Integer`: Desired number of features in selected by LDB.

# Returns
- `xₜ::Matrix{S}`: Matrix of extracted LDB features, where the number of rows is now
  `n_features` instead of the previous `f.n_features`.

**See also:** [`LocalDiscriminantBasis`](@ref), [`fit!`](@ref), [`fit_transform`](@ref),
    [`transform`](@ref), [`inverse_transform`](@ref)
"""
function change_nfeatures(f::LocalDiscriminantBasis,
                          x::AbstractArray{T,2},
                          n_features::Integer) where T<:AbstractFloat

    # check measurements
    @assert !isnothing(f.n_features)
    @assert size(x,1) == f.n_features || throw(ArgumentError("f.n_features and number of rows of x do not match!"))
    @assert 1 ≤ n_features ≤ prod(f.sz)

    # change number of features
    if f.n_features ≥ n_features
        f.n_features = n_features
        xₜ = x[1:f.n_features,:]
    else
        @warn "Proposed n_features larger than currently saved n_features. Results will be less accurate since inverse_transform and transform is involved."
        X = inverse_transform(f, x)
        f.n_features = n_features
        xₜ = transform(f, X)
    end
    return xₜ
end

end # end module


using LinearAlgebraicRepresentation
using Plasm,SparseArrays
Lar = LinearAlgebraicRepresentation

# input generation
V,EV = Lar.randomcuboids(30, .35)
V = Plasm.normalize(V,flag=true)
Plasm.view(Plasm.numbering(.05)((V,[[[k] for k=1:size(V,2)], EV])))

# subdivision of input edges
W = convert(Lar.Points, V')
cop_EV = Lar.coboundary_0(EV::Lar.Cells)
cop_EW = convert(Lar.ChainOp, cop_EV)
V, copEV = Lar.planar_arrangement_1(W::Lar.Points, cop_EW::Lar.ChainOp)

# compute containment graph of components
bicon_comps = Lar.Arrangement.biconnected_components(copEV)

# visualization of component graphs
EW = Lar.cop2lar(copEV)
W = convert(Lar.Points, V')
hpcs = [ Plasm.lar2hpc(W,[EW[e] for e in comp]) for comp in bicon_comps ]
Plasm.view([ Plasm.color(Plasm.colorkey[(k%12)==0 ? 12 : k%12])(hpcs[k]) for k=1:(length(hpcs)) ])

# computation of 2-cells and 2-boundary
V, copEV, copFE = Lar.planar_arrangement_2(V, copEV, bicon_comps)

# visualization of numbered arrangement
Plasm.view( Plasm.numbering1(0.05)((V, copEV, copFE)) )

# final solid visualization
triangulated_faces = Lar.triangulate2D(V, [copEV, copFE])
V = convert(Lar.Points, V')
FVs = convert(Array{Lar.Cells}, triangulated_faces)
Plasm.viewcolor(V::Lar.Points, FVs::Array{Lar.Cells})

# polygonal face boundaries
EVs = Lar.FV2EVs(copEV, copFE)
EVs = convert(Array{Array{Array{Int64,1},1},1}, EVs)
Plasm.viewcolor(V::Lar.Points, EVs::Array{Lar.Cells})

# exploded polygons
model = V,EVs
Plasm.view(Plasm.lar_exploded(model)(1.2,1.2,1.2))


using Bridge, StaticArrays
using Test, Statistics, LinearAlgebra, Random
SV2 = SVector{2,Float64}

@test let # functions callable
    tt = range(0, stop=1, length=100)

    sample(tt, Wiener{SV2}())
    sample!(SamplePath{SV2}(collect(tt), zeros(SV2, 100)), Wiener{SV2}())
    sample(tt, WienerBridge{SV2}(1.0, SVector(2.0, -2.0)))
    sample!(SamplePath{SV2}(collect(tt), zeros(SV2, 100)), WienerBridge{SV2}(1.0, SVector(2.0, -2.0)))
    solve(Euler(), SVector(1.1, 1.0), sample(tt, Wiener{SV2}()), Wiener{SV2}())

    sample(tt, Wiener())
    sample(tt, WienerBridge(1.0, 1.0))
    true
end

# tests with alpha 0.01
# test fail 1% of the time

r = 2.576
n = 1000

# varmu(x, mu) = dot(x-mu, x-mu)/length(x)
# var0(x) = dot(x, x)/length(x)

#testing mean and variance of Brownian motion sampled at few points
X = [sample(range(0.0, stop=2.0, length=5), Wiener()).yy[end] for i in 1:n]

@test abs(mean(X)) < r * sqrt(2/n)
# see above, fails 1% of the time
# if you want to check: 99% ≈ mean([abs(mean([sample(linspace(0.0, 2.0, 5), Wiener()).yy[end] for i in 1:n]))< r*sqrt(2/n) for k in 1:10000])

chiupper = 1118.95 #upper 0.005 percentile for n = 1000
chilower = 888.56 #lower 0.005 percentile for n = 1000
@test chiupper > 1000.0 * var(X)/2 > chilower

# mean and covariance of vector brownian motion

@test norm(SVector(5.0,2.0) - mean([sample(range(0.0, stop=2.0, length=5),Wiener{SV2}(),SVector(5.0,2.0)).yy[end] for i = 1:div(n,2)])) < r *sqrt(2*2.82 / n)
@test norm(2I - cov(Bridge.mat([sample(range(0.0, stop=2.0, length=5),Wiener{SV2}(),SVector(5.0,2.0)).yy[end] for i = 1:1000]), dims=2, corrected=true))  < 0.3

@test Bridge.a(0.0, 0.0, Wiener()) == inv(Bridge.Γ(0.0, 0.0, Wiener())) == Bridge.σ(0.0, 0.0, Wiener())
@test Bridge.a(0.0, 0.0, WienerBridge(1.0, 0.0)) == inv(Bridge.Γ(0.0, 0.0, WienerBridge(1.0, 0.0))) ==
    Bridge.σ(0.0, 0.0, WienerBridge(1.0, 0.0))

@test mean(transitionprob(0.0, 0.0, 2.0, Wiener())) == 0
@test var(transitionprob(0.0, 0.0, 2.0, Wiener())) ≈ 2.0


##############################################################################
#
# test_3d_vortex_particles.jl
#
# Check 3D vortex particles behave as expected.
#
# Copyright 2020 HJA Bird
#
# 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.
##############################################################################

function two_particles_basic_case_3d()
	@testset "3D 2 particle interaction" begin
		pl0 = [0,0,0]
		plz = [0,0,1]
		ply = [0,1,0]
		plx = [1,0,0]
		pvx = [1,0,0]
		pvy = [0,1,0]
		pvz = [0,0,1]

		# Singular kernel ---------------------------------------------------------------------------------------
		kernel = singular_regularisation()
		@test isapprox([0,-1,0]./(4*pi), particle_induced_velocity(pl0, pvx, plz, kernel, 1.); rtol=1e-5)
		@test isapprox([-1,0,0]./(4*pi), particle_induced_velocity(plz, pvy, pl0, kernel, 1.); rtol=1e-5)
		@test isapprox([0,0,0]./(4*pi), particle_induced_velocity(pl0, pvx, pl0, kernel, 1.); rtol=1e-5)
		@test isapprox([0,0,0]./(4*pi), particle_induced_velocity(plz, pvy, plz, kernel, 1.); rtol=1e-5)
		@test isapprox([0,0,0]./(4*pi), particle_induced_velocity(pl0, pvz, plz, kernel, 1.); rtol=1e-5)
		@test isapprox([0,0,0]./(4*pi), particle_induced_velocity(plz, pvz, pl0, kernel, 1.); rtol=1e-5)
		@test isapprox([0,-0.25,0]./(4*pi), particle_induced_velocity(pl0, pvx, 2*plz, kernel, 1.); rtol=1e-5)
		@test isapprox([-0.25,0,0]./(4*pi), particle_induced_velocity(2*plz, pvy, pl0, kernel, 1.); rtol=1e-5)
		@test isapprox([0,-2,0]./(4*pi), particle_induced_velocity(pl0, 2*pvx, plz, kernel, 1.); rtol=1e-5)
		@test isapprox([-2,0,0]./(4*pi), particle_induced_velocity(plz, 2*pvy, pl0, kernel, 1.); rtol=1e-5)
		@test isapprox([0,0,1]./(4*pi), particle_induced_velocity(pl0, pvx, ply, kernel, 1.); rtol=1e-5)
		@test isapprox([0,0,-1]./(4*pi), particle_induced_velocity(ply, pvx, pl0, kernel, 1.); rtol=1e-5)
		# Check these don't fail.
		kernel = gaussian_regularisation()
		kernel = planetary_regularisation()
		kernel = winckelmans_regularisation()
		# Check visc dvort.
		kernel = gaussian_regularisation()
		gaussr1 = sqrt(2/pi) * exp(-1/2)
		gaussr2 = sqrt(2/pi) * exp(-4/2)
		@test isapprox((pvz-pvy)*2*gaussr1,
			particle_visc_induced_dvort(ply, pvz, 1, pl0, pvy, 1, kernel, 1., 1.); rtol=1e-5)
		@test isapprox((pvx-pvy)*2*gaussr1,
			particle_visc_induced_dvort(plx, pvx, 1, pl0, pvy, 1, kernel, 1., 1.); rtol=1e-5)
		@test isapprox((pvz-pvz)*2*gaussr1,
			particle_visc_induced_dvort(ply, pvz, 1, pl0, pvz, 1, kernel, 1., 1.); rtol=1e-5)
		@test isapprox((pvz-pvy)*2*gaussr2,
			particle_visc_induced_dvort(2*ply, pvz, 1, pl0, pvy, 1, kernel, 1., 1.); rtol=1e-5)
		@test isapprox((pvx-pvy)*2*gaussr2,
			particle_visc_induced_dvort(2*plx, pvx, 1, pl0, pvy, 1, kernel, 1., 1.); rtol=1e-5)
		@test isapprox((pvz-pvz)*2*gaussr2,
			particle_visc_induced_dvort(2*ply, pvz, 1, pl0, pvz, 1, kernel, 1., 1.); rtol=1e-5)
		@test isapprox((pvz-pvy)*gaussr1,
			particle_visc_induced_dvort(ply, pvz, 0.5, pl0, pvy, 0.5, kernel, 1., 1.); rtol=1e-5)
		@test isapprox((pvz-pvy)*gaussr1,
			particle_visc_induced_dvort(ply, 0.5*pvz, 0.5, pl0, pvy, 1.0, kernel, 1., 1.); rtol=1e-5)
		@test isapprox((pvz-pvy)*4*gaussr1,
			particle_visc_induced_dvort(ply, pvz, 1, pl0, pvy, 1, kernel, 1., 2.); rtol=1e-5)
		@test isapprox((pvz-pvy)*8*gaussr2,
			particle_visc_induced_dvort(ply, pvz, 1, pl0, pvy, 1, kernel, 0.5, 1.); rtol=1e-5)
	end
end

function test_correct_output_matrix_dims_3d_vortex_particles()
	@testset "3D particle correct output dimensions" begin
		n1 = 31
		n2 = 47
		kernel = singular_regularisation()
		@test (3,) == size(particle_induced_velocity(rand(3), rand(3), rand(3), kernel, 1.))
		@test (3,) == size(particle_induced_velocity(rand(n1, 3), rand(n1, 3), rand(3), kernel, 1.))
		@test (n1, 3) == size(particle_induced_velocity(rand(n1, 3), rand(n1, 3), rand(n1, 3), kernel, 1.))
		@test (n2, 3) == size(particle_induced_velocity(rand(n1, 3), rand(n1, 3), rand(n2, 3), kernel, 1.))
		@test (n1, 3) == size(particle_induced_velocity(rand(3), rand(3), rand(n1, 3), kernel, 1.))
		@test (3,) == size(particle_induced_dvort(rand(3), rand(3), rand(3), rand(3), kernel, 1.))
		@test (3,) == size(particle_induced_dvort(rand(n1, 3), rand(n1, 3), rand(3), rand(3), kernel, 1.))
		@test (n1, 3) == size(particle_induced_dvort(rand(n1, 3), rand(n1, 3), rand(n1, 3), rand(n1, 3), kernel, 1.))
		@test (n2, 3) == size(particle_induced_dvort(rand(n1, 3), rand(n1, 3), rand(n2, 3), rand(n2, 3), kernel, 1.))
		@test (n1, 3) == size(particle_induced_dvort(rand(3), rand(3), rand(n1, 3), rand(n1, 3), kernel, 1.))
		kernel = gaussian_regularisation()
		@test (3,) == size(particle_visc_induced_dvort(rand(3), rand(3), rand(), rand(3), rand(3), rand(), kernel, 1., 1))
		@test (3,) == size(particle_visc_induced_dvort(rand(n1, 3), rand(n1, 3), rand(n1), rand(3), rand(3), rand(), kernel, 1., 1))
		@test (n1, 3) == size(particle_visc_induced_dvort(rand(n1, 3), rand(n1, 3), rand(n1), rand(n1, 3), rand(n1, 3), rand(n1), kernel, 1., 1))
		@test (n2, 3) == size(particle_visc_induced_dvort(rand(n1, 3), rand(n1, 3), rand(n1), rand(n2, 3), rand(n2, 3), rand(n2), kernel, 1., 1))
		@test (n1, 3) == size(particle_visc_induced_dvort(rand(3), rand(3), rand(), rand(n1, 3), rand(n1, 3), rand(n1), kernel, 1., 1))
	end
end

test_correct_output_matrix_dims_3d_vortex_particles()
two_particles_basic_case_3d()


module Operators

"""
* `n` : input vector length

# Keyword Arguments

* `np` : pad length +ve
* `nn` : pad length -ve
* `ntimes` : number of times `n+np+nn` block is repeated
"""
function pad(T::Type, n; np=0, nn=0, ntimes=1 )
	nout=(n+np+nn)*ntimes; # total length
	neach=n+np+nn # each block length
	nout=neach*ntimes # total length
	A=spzeros(T, nout, n)
	# need first nin elements
	for it in 1:ntimes
		for i in nn+1:n+nn
			A[i+(it-1)*neach,i-nn]=one(T)
		end
	end
	return A
end

function splitpad(T::Type, n; nfrac=1, np=0, nn=0)
	(mod(n, nfrac) ≠ 0) && error("invalid nfrac")
	nb=div(n, nfrac)
	neach=nb+np+nn
	nout=neach*nfrac
	A=spzeros(T, nout, n)
	for ii in 1:nfrac
		iii=(ii-1)*neach
		for i in 1:nb
			A[i+nn+iii,i+(ii-1)*nb]=one(T)
		end
	end
	return A
end

function splittrun(T::Type, n; np=0, nn=0, nfrac=1)
	return transpose(splitpad(T,n,np=np, nn=nn, nfrac=nfrac))
end

function blkdiag(T::Type, Ain, ntimes)
	n,m=size(Ain)
	A=spzeros(T, n*ntimes, m*ntimes)

	for i in 1:ntimes
		Av=view(A,1+n*(i-1):n*i,1+m*(i-1):m*i)
		for ii in eachindex(Ain)
			Av[ii]=Ain[ii]
		end
	end
	return A
end

end


export SpinHalfSite,
       spinHalfSites

struct SpinHalfSite <: Site
    s::Index
    SpinHalfSite(I::Index) = new(I)
end
SpinHalfSite(n::Int) = SpinHalfSite(Index(2, "Site,S=1/2,n=$n"))

function spinHalfSites(N::Int;kwargs...)::SiteSet
  sites = SiteSet(N)
  for n=1:N
    set(sites,n,SpinHalfSite(n))
  end
  return sites
end

function state(site::SpinHalfSite,
               st::String)::IndexVal
  if st == "Up" || st == "↑"
    return site.s(1)
  elseif st == "Dn" || st == "↓"
    return site.s(2)
  else
    throw(ArgumentError("State string \"$st\" not recognized for SpinHalfSite"))
  end
  return site.s(1)
end

function operator(site::SpinHalfSite,
                  opname::AbstractString)::ITensor
    s = site.s
    sP = prime(site.s)
    Up = s(1)
    UpP = sP(1)
    Dn = s(2)
    DnP = sP(2)

    Op = ITensor(dag(s), s')

    if opname == "S⁺" || opname == "Splus" || opname == "S+"
        Op[Dn, UpP] = 1.
    elseif opname == "S⁻" || opname == "Sminus" || opname == "S-"
        Op[Up, DnP] = 1.
    elseif opname == "Sˣ" || opname == "Sx"
        Op[Up, DnP] = 0.5
        Op[Dn, UpP] = 0.5
    elseif opname == "iSʸ" || opname == "iSy"
        Op[Up, DnP] = -0.5
        Op[Dn, UpP] = 0.5
    elseif opname == "Sʸ" || opname == "Sy"
        Op = complex(Op)
        Op[Up, DnP] = 0.5*im
        Op[Dn, UpP] = -0.5*im
    elseif opname == "Sᶻ" || opname == "Sz"
        Op[Up, UpP] = 0.5
        Op[Dn, DnP] = -0.5
    elseif opname == "projUp"
        Op[Up, UpP] = 1.
    elseif opname == "projDn"
        Op[Dn, DnP] = 1.
    elseif opname == "Up" || opname == "↑"
        pU = ITensor(s)
        pU[Up] = 1.
        return pU
    elseif opname == "Dn" || opname == "↓"
        pD = ITensor(s)
        pD[Dn] = 1.
        return pD
    else
        throw(ArgumentError("Operator name '$opname' not recognized for SpinHalfSite"))
    end
    return Op
end


"""
    struct ChebyshevNodes{T} <: AbstractIntervalGrid{T}

A grid with chebyshev nodes on [-1,1].

# Example
```jldocs
julia> ChebyshevExtremae(4)
4-element ChebyshevExtremae{Float64}:
  1.0
  0.5000000000000001
 -0.4999999999999998
 -1.0
```
"""
struct ChebyshevTNodes{T} <: AbstractIntervalGrid{T}
    n   ::  Int
end

const ChebyshevNodes = ChebyshevTNodes
const ChebyshevPoints = ChebyshevTNodes

size(g::ChebyshevTNodes) = (g.n,)

unsafe_grid_getindex(g::ChebyshevTNodes{T}, i::Int) where {T} = T(-1)*cos((i-T(1)/2) * T(pi) / (g.n) )

"""
    struct ChebyshevExtremae{T} <: AbstractIntervalGrid{T}

A grid with chebyshev extrema on [-1,1].

# Example
```jldocs
julia> ChebyshevExtremae(4)
4-element ChebyshevExtremae{Float64}:
  1.0
  0.5000000000000001
 -0.4999999999999998
 -1.0
```
"""
struct ChebyshevExtremae{T} <: AbstractIntervalGrid{T}
    n   ::  Int
end

const ChebyshevPointsOfTheSecondKind = ChebyshevExtremae

size(g::ChebyshevExtremae) = (g.n,)

# TODO: flip the values so that they are sorted
unsafe_grid_getindex(g::ChebyshevExtremae{T}, i::Int) where {T} = i == 0 ? T(0) : cos((i-1)*T(pi) / (g.n-1) )

struct ChebyshevUNodes{T} <: AbstractIntervalGrid{T}
    n   :: Int
end

size(g::ChebyshevUNodes) = (g.n,)

unsafe_grid_getindex(nodes::ChebyshevUNodes{T}, i::Int) where T = cos((nodes.n + 1 - i) * convert(T,π) / (nodes.n + 1))

struct LegendreNodes{T} <: AbstractIntervalGrid{T}
    nodes   :: Vector{T}
end

size(grid::LegendreNodes) = size(grid.nodes)
unsafe_grid_getindex(grid::LegendreNodes, i::Int) = @inbounds getindex(grid.nodes, i)

struct LaguerreNodes{T} <: AbstractIntervalGrid{T}
    α       :: T
    nodes   :: Vector{T}
end

size(grid::LaguerreNodes) = size(grid.nodes)
unsafe_grid_getindex(grid::LaguerreNodes, i::Int) = @inbounds getindex(grid.nodes, i)

struct HermiteNodes{T} <: AbstractIntervalGrid{T}
    nodes   :: Vector{T}
end

size(grid::HermiteNodes) = size(grid.nodes)
unsafe_grid_getindex(grid::HermiteNodes, i::Int) = @inbounds getindex(grid.nodes, i)

struct JacobiNodes{T} <: AbstractIntervalGrid{T}
    α   ::  T
    β   ::  T
    nodes   ::Vector
end

size(grid::JacobiNodes) = size(grid.nodes)
unsafe_grid_getindex(grid::JacobiNodes, i::Int) = @inbounds getindex(grid.nodes, i)

"A vector that aliases another vector but has its own type."
abstract type VectorAlias{T} <: AbstractVector{T} end
@propagate_inbounds getindex(vector::VectorAlias, i::Int) = getindex(vector.vector, i)
size(vector::VectorAlias) = size(vector.vector)

"A function vector is a lazy vector with entries defined by a function."
abstract type FunctionVector{T} <: AbstractVector{T} end
size(vector::FunctionVector) = (vector.n,)

@propagate_inbounds function getindex(vector::FunctionVector, i::Int)
    @boundscheck (1 <= i <= length(vector)) || throw(BoundsError())
    funvector_getindex(vector, i)
end

struct ChebyshevTWeights{T,F} <: VectorAlias{T}
    vector  ::  F
end

# The Chebyshev weights are constant
function ChebyshevTWeights{T}(n) where {T}
    vector = Fill(convert(T,π) / n, n)
    ChebyshevTWeights{T,typeof(vector)}(vector)
end

struct ChebyshevUWeights{T} <: FunctionVector{T}
    n   :: Int
end

funvector_getindex(weights::ChebyshevUWeights{T}, i::Int) where {T} =
    convert(T,π)/(weights.n + 1) * sin(convert(T,weights.n + 1 -i) / (weights.n + 1) * convert(T,π))^2

gausschebyshev(::Type{T}, n::Int) where T =
    ChebyshevTNodes{T}(n), ChebyshevTWeights{T}(n)
gausschebyshev(n::Int) = gausschebyshev(Float64, n)

gausschebyshevu(::Type{T}, n::Int) where T =
    ChebyshevUNodes{T}(n), ChebyshevUWeights{T}(n)
gausschebyshevu(n::Int) = gausschebyshevu(Float64,n)

struct LegendreWeights{T} <: VectorAlias{T}
    vector  ::  Vector{T}
end

function gausslegendre(::Type{Float64}, n::Int)
    x,w = FastGaussQuadrature.gausslegendre(n)
    LegendreNodes(x), LegendreWeights(w)
end
function gausslegendre(::Type{T}, n::Int) where T
    x,w = GaussQuadrature.legendre(T, n)
    LegendreNodes(x), LegendreWeights(w)
end
gausslegendre(n::Int) = gausslegendre(Float64, n)

struct LaguerreWeights{T} <: VectorAlias{T}
    α       ::  T
    vector  ::  Vector{T}
end

function gausslaguerre(::Type{Float64}, n::Int, α::Float64)
    x,w = FastGaussQuadrature.gausslaguerre(n, α)
    LaguerreNodes(α, x), LaguerreWeights(α, w)
end
function gausslaguerre(::Type{T}, n::Int, α::T) where T
    x,w = GaussQuadrature.laguerre(n, α)
    LaguerreNodes(α, x), LaguerreWeights(α, w)
end
gausslaguerre(n::Int, α::T) where T = gausslaguerre(T, n, α)

struct HermiteWeights{T} <: VectorAlias{T}
    vector  ::  Vector{T}
end
function gausshermite(::Type{Float64}, n::Int)
    x,w = FastGaussQuadrature.gausshermite(n)
    HermiteNodes(x), HermiteWeights(w)
end
function gausshermite(::Type{T}, n::Int) where T
    x,w = GaussQuadrature.hermite(T, n)
    HermiteNodes(x), HermiteWeights(w)
end
gausshermite(n::Int) = gausshermite(Float64, n)

struct JacobiWeights{T} <: VectorAlias{T}
    α       ::  T
    β       ::  T
    vector  ::  Vector{T}
end
function gaussjacobi(::Type{Float64}, n::Int, α::Float64, β::Float64)
    x,w = FastGaussQuadrature.gaussjacobi(n, α, β)
    JacobiNodes(α, β, x), JacobiWeights(α, β, w)
end
function gaussjacobi(::Type{T}, n::Int, α::T, β::T) where T
    x,w = GaussQuadrature.jacobi(n, α, β)
    JacobiNodes(α, β, x), JacobiWeights(α, β, w)
end
gaussjacobi(n::Int, α::T, β::T) where T = gaussjacobi(T, n, α, β)

covering(::ChebyshevTNodes{T}) where T = ChebyshevInterval{T}()
name(g::ChebyshevTNodes) = "ChebyshevT nodes"

name(g::ChebyshevExtremae) = "Chebyshev extremae"
covering(::ChebyshevExtremae{T}) where T = ChebyshevInterval{T}()

name(g::ChebyshevUNodes) = "ChebyshevU nodes"
covering(::ChebyshevUNodes{T}) where T = ChebyshevInterval{T}()

name(g::LegendreNodes) = "Legendre nodes"
covering(::LegendreNodes{T}) where T = ChebyshevInterval{T}()
LegendreNodes{T}(n::Int) where T = gausslegendre(T, n)[1]

name(g::LaguerreNodes) = "Laguerre nodes α=$(g.α)"
covering(::LaguerreNodes{T}) where T = HalfLine{T}()
similargrid(grid::LaguerreNodes, T, n::Int) = LaguerreNodes{T}(n, T(grid.α))
LaguerreNodes(n::Int, α::T) where T = gausslaguerre(T, n, α)[1]
LaguerreNodes{T}(n::Int, α::T) where T = gausslaguerre(T, n, α)[1]

name(g::HermiteNodes) = "Hermite nodes"
covering(::HermiteNodes{T}) where T = DomainSets.FullSpace{T}()
HermiteNodes{T}(n::Int) where T = gausshermite(T, n)[1]

name(g::JacobiNodes) = "Jacobi nodes α=$(g.α), β=$(g.β)"
covering(::JacobiNodes{T}) where T = ChebyshevInterval{T}()
similargrid(grid::JacobiNodes, T, n::Int) = JacobiNodes{T}(n, T(grid.α), T(grid.β))
JacobiNodes{T}(n::Int, α::T, β::T) where T = gaussjacobi(T, n, α, β)[1]
JacobiNodes(n::Int, α::T, β::T) where T = gaussjacobi(T, n, α, β)[1]

# Grids with fixed support and one variable
for GRID in (:ChebyshevNodes, :ChebyshevExtremae, :ChebyshevUNodes, :LegendreNodes, :HermiteNodes)
    @eval similargrid(g::$GRID, ::Type{T}, n::Int) where {T} = $GRID{T}(n)
    @eval $GRID(n::Int) = $GRID{Float64}(n)
    # @eval $GRID(n::Int, d::AbstractInterval) =
        # $GRID(n, endpoints(d)...)
    # @eval $GRID(n::Int, a, b) = rescale($GRID{typeof((b-a)/n)}(n), a, b)
end


module Spring #correspond à un namespace
    using Plots

    export SpringParam, InitParam, AnimParam
    export animate_spring

    toto(x) = cos(x)

    function gogo()
        x=[Float64(i) for i ∈ 1:60]
        y=toto.(x)
        plot(x,y)
    end

    Base.@kwdef struct SpringParam
        ls::Float64
        ms::Float64
        ks::Float64
        ns::Int
    end
    #A quoi sert Base.@kwdef? A rentrer des champs nommés
    getvalue(p::SpringParam) = p.ls,p.ms,p.ks,p.ns

    Base.@kwdef struct InitParam
        λ::Float64
        shift::Float64
        pos::Float64
    end
    getvalue(p::InitParam) = p.λ,p.shift,p.pos
    #meme nom de methode mais argument specialise

    Base.@kwdef struct AnimParam
        δt::Float64
        nδt::Int
        nδtperframe::Int
    end
    getvalue(p::AnimParam) = p.δt,p.nδt,p.nδtperframe

    steady_position(i,ls) = (i-1)*ls

    function initial_position(i,ls,λ,shift,pos)
        xs=steady_position(i,ls)
        dx=xs-pos
        xs-λ*dx*shift*exp(-0.5*dx^2/λ^2)
    end

    function update_force!(fx,xc,ks) # ! est une convention de nommage en julia
                                     # pour les fonctions dont on modifie les arguments
                                     # placés en premieres positions
        ns=length(xc)
        for i ∈ 2:ns-1
            fx[i] = -ks*(2xc[i]-xc[i-1]-xc[i+1])
        end
    end

    function update_position!(xt,xc,xp,fx,δt,ms)
        coef=δt^2/ms
        @. xt = 2*xc - xp + fx*coef
    end

    function advance_nδtpf(xc,xp,xt,fx,sp,ap)
        ls,ms,ks,ns=getvalue(sp)
        δt,nδt,nδtperframe=getvalue(ap)

        for _ ∈ 1:nδtperframe #boucle sans indice
            update_force!(fx,xc,ks)
            update_position!(xt,xc,xp,fx,δt,ms)
            xc,xp,xt=xt,xc,xp
        end
        xc,xp,xt
    end

    function animate_spring(sp,ip,ap)
        ls,ms,ks,ns=getvalue(sp)
        λ,shift,pos=getvalue(ip)
        δt,nδt,nδtperframe=getvalue(ap)

        xs=[steady_position(i,ls) for i ∈ 1:ns] #array comprehension
        xc=[initial_position(i,ls,λ,shift,pos) for i ∈ 1:ns]

        dc=zero(xc) #tableau de la meme forme et meme type que xc avec des 0
        #for i ∈ 1:ns
        #    dc[i]=xc[i]-xs[i]
        #end
        #version broadcastée, pas de difference de rapidité en julia
        #dc.=xc.-xs
        #autre notation pour ne pas mettre des . partout
        #@. dc=xc-xs

        fx=zero(xc)
        #update_force!(fx,xc,ks)

        xt=zero(xc)
        xp=copy(xc) #sinon ca pointe juste, on change de nom
        #example tester a=rand(2), b=rand(3), a,b=b,a
        nf=nδt÷nδtperframe
        t=0.0
        anim = @animate for i ∈ 1:nf #creation d'une animation
            xc,xp,xt=advance_nδtpf(xc,xp,xt,fx,sp,ap)
            @. dc = xc - xs
            t+=nδtperframe*δt
            plot(xs,dc,ylims=(-shift,shift),title="t=$t")
        end
        gif(anim,"toto.gif",fps=15)

        #plot(xs,fx)
        #plot(xs,dc)

        #imid=ns÷2 #division entiere \div
        #xs[imid],xc[imid],dc[imid]
        #sp,ip,ap #retour de la fonction main
    end

    # si on laisse main ici il faut l'appeler avec Spring.main() dans le REPL
    # ou l'exporter du module pour s'affranchir du Spring.
    # Mieux faire un fichier main.jl à la racine puis dans le REPL include("main.jl")
    #function main()
    #    sp=Spring.SpringParam(ls=0.1,ms=1,ks=1,ns=400)
    #    ip=InitParam(λ=1.0,shift=1.5,pos=sp.ls*sp.ns/2)
    #    ap=AnimParam(δt=1.0,nδt=1000,nδtperframe=10)

    #end

end # module


# ---
# title: "`opening`"
# id: op_opening
# cover: assets/opening.png
# ---

# `opening` operator is defined as `dilate(erode(img))`. Intuitively, opening operation
# fills the white holes in the image.

using ImageMorphology
using TestImages
using ImageBase
using ImageShow

img = restrict(testimage_dip3e("fig0940")) # rice
img01 = @. Gray(img > 0.5)
mosaic(img, img01; nrow=1)

#-

out1 = opening(img) # default: all spatial dimensions, r=1, a box-shape SE
out2 = opening(img; dims=(2,)) # only apply to the second dimension
out3 = opening(img; r=5) # half-size r=5

## also to the binary version
out1_01 = opening(img01)
out2_01 = opening(img01; dims=(2,))
out3_01 = opening(img01; r=5)

mosaic(out1, out2, out3, out1_01, out2_01, out3_01; nrow=2, rowmajor=true)

# ## See also

# [`closing`](@ref op_closing) is the dual operator of `opening` in the following sense:

complement.(opening(img)) == closing(complement.(img))

# For a comprehensive and more accurate documentation, please check the [`opening`](@ref)
# reference page.

# save cover image #src
using FileIO #src
mkpath("assets") #src
img = Gray.(restrict(testimage("blobs"))) #src
save("assets/opening.png", opening(img; r=2)) #src


#=
Created on Sunday 19 April 2020
Last update: Sunday 16 April 2020

@author: Michiel Stock
michielfmstock@gmail.com

Utilities for the TSP problem.
=#

module TSP

import STMO:dist
using STMO
using Plots: plot!, scatter, scatter!, plot

export TravelingSalesmanProblem, cities, computecost, coordinates
export plot_cities, plot_cities!, plot_tour, plot_tour!
export swap!, deltaswapcost, deltaflipcost, flip!
export totoro_tsp, got_coords

struct TravelingSalesmanProblem{Tc}
    coordinates::Matrix{Tc}
    distance::Matrix{Float64}
end

TravelingSalesmanProblem(coordinates) = TravelingSalesmanProblem(coordinates,
                                dist(coordinates, coordinates))

"""Returns the number of cities."""
Base.length(tsp::TravelingSalesmanProblem) = size(tsp.coordinates, 1)

"""Returns the coordinates of the cities."""
coordinates(tsp::TravelingSalesmanProblem) = tsp.coordinates

"""Returns the cities"""
cities(tsp::TravelingSalesmanProblem) = collect(1:length(tsp))

"""Returns the distance matrix of the cities."""
dist(tsp::TravelingSalesmanProblem) = tsp.distance

"""Returns the distance (or cost) of going from city `ci` to city `cj`"""
dist(tsp::TravelingSalesmanProblem, ci, cj) = dist(tsp)[ci,cj]

Base.isvalid(tsp::TravelingSalesmanProblem, tour) = length(tour) == length(tsp) &&
    Set(tour) == Set(cities(tsp))

"""
    computecost(tsp::TravelingSalesmanProblem, tour)

Computes the cost of travessing a tour.
"""
function computecost(tsp::TravelingSalesmanProblem, tour)
    !isvalid(tsp, tour) && throw(AssertionError("invalid tour provided"))
    c = 0.0
    for (i, j) in zip(tour[1:end-1], tour[2:end])
        c += dist(tsp, i, j)
    end
    # complete tour
    c += dist(tsp, tour[end], tour[1])
    return c
end

split_coord(X) = X[:,1], X[:,2]

plot_cities(tsp::TravelingSalesmanProblem; kwargs...) = scatter(split_coord(coordinates(tsp))...,
                color=myblue, markersize=1, label="", aspect_ratio=:equal; kwargs...)

plot_cities!(tsp::TravelingSalesmanProblem; kwargs...) = scatter!(split_coord(coordinates(tsp))...,
                color=myblue, markersize=1, label=""; kwargs...)

coords_tour(tsp, tour) = [coordinates(tsp)[tour,:];coordinates(tsp)[[tour[1]],:]]

plot_tour(tsp::TravelingSalesmanProblem, tour; kwargs...) = plot(
                split_coord(coords_tour(tsp, tour))...,
                color=myred, label="", aspect_ratio=:equal; kwargs...)

plot_tour!(tsp::TravelingSalesmanProblem, tour; kwargs...) = plot!(
                split_coord(coords_tour(tsp, tour))...,
                color=myred, label=""; kwargs...)

"""
    deltaswapcost(tsp, tour, i, j)

Compute the change in tour cost if the cities at positions `i` and `j` are
swapped.
"""
function deltaswapcost(tsp, tour, i, j)
    n = length(tsp)
    i == j && return 0.0
    # put in order
    i, j = i < j ? (i, j) : (j, i)
    # choose indices respecting cyclic  invariance
    i₋₁ = i == 1 ? n : i - 1
    i₊₁ = i == n ? 1 : i + 1
    j₋₁ = j == 1 ? n : j - 1
    j₊₁ = j == n ? 1 : j + 1
    ci₋₁, ci, ci₊₁, cj₋₁, cj, cj₊₁ = tour[[i₋₁, i, i₊₁, j₋₁, j, j₊₁]]
    if j - i == 1  # i and j are neighbors
        Δc = ((dist(tsp, ci₋₁, cj) + dist(tsp, ci, cj₊₁)) -
                    (dist(tsp, ci₋₁, ci) + dist(tsp, cj, cj₊₁)))
    elseif (i==1 && j==n)
        Δc = ((dist(tsp, tour[end-1], tour[1]) + dist(tsp, tour[end], tour[2])) -
                    (dist(tsp, tour[end-1], tour[end]) + dist(tsp, tour[1], tour[2])))
    else
        Δc = ((dist(tsp, ci₋₁, cj) + dist(tsp, cj, ci₊₁) +
                    dist(tsp, cj₋₁, ci) + dist(tsp, ci, cj₊₁)) -
                    (dist(tsp, ci₋₁, ci) + dist(tsp, ci, ci₊₁) +
                    dist(tsp, cj₋₁, cj) + dist(tsp, cj, cj₊₁)))
    end
    return Δc
end

"""
    swap!(tour, i, j)

Swaps the cities at positions `i` and `j` in `tour`.
"""
function swap!(tour, i, j)
    tour[i], tour[j] = tour[j], tour[i]
    return tour
end

"""
    deltaflipcost(tsp, tour, i, j)

Compute the change in tour cost if the subtour between the `i`th and `j`th city
are flipped.
"""
function deltaflipcost(tsp, tour, i, j)
    i == j && return 0.0
    n = length(tsp)
    # put in order
    i, j = i < j ? (i, j) : (j, i)
    (i, j) == (1, n) && return 0.0
    # choose indices respecting cyclic  invariance
    i₋₁ = i == 1 ? n : i - 1
    i₊₁ = i == n ? 1 : i + 1
    j₋₁ = j == 1 ? n : j - 1
    j₊₁ = j == n ? 1 : j + 1
    ci₋₁, ci, ci₊₁, cj₋₁, cj, cj₊₁ = tour[[i₋₁, i, i₊₁, j₋₁, j, j₊₁]]
    Δc = - (dist(tsp, ci₋₁, ci) + dist(tsp, cj, cj₊₁))
    Δc += dist(tsp, ci₋₁, cj) + dist(tsp, ci, cj₊₁)
    return Δc
end

function flip!(tour, i, j)
    i, j = i < j ? (i, j) : (j, i)
    reverse!(@view tour[i:j])
    return tour
end

# example network
include("targ.jl")
include("totoro.jl")

got_tsp() = TravelingSalesmanProblem(got_coords)
totoro_tsp() = TravelingSalesmanProblem(totoro_coords)

end  # module TSP


"""
    Model for testing steady state solver with linear growth variables.
"""
module E6

using ModelBaseEcon

model = Model()

@parameters model begin
    p_dlp = 0.0050000000000000
    p_dly = 0.0045000000000000
end

@variables model begin
    dlp; dly; dlyn; lp; ly; lyn
end

@shocks model begin
    dlp_shk; dly_shk
end

@autoexogenize model begin
    ly = dly_shk
    lp = dlp_shk
end

@equations model begin
    dly[t]=(1-0.2-0.2)*p_dly+0.2*dly[t-1]+0.2*dly[t+1]+dly_shk[t]
    dlp[t]=(1-0.5)*p_dlp+0.1*dlp[t-2]+0.1*dlp[t-1]+0.1*dlp[t+1]+0.1*dlp[t+2]+0.1*dlp[t+3]+dlp_shk[t]
    dlyn[t]=dly[t]+dlp[t]
    ly[t]=ly[t-1]+dly[t]
    lp[t]=lp[t-1]+dlp[t]
    lyn[t]=lyn[t-1]+dlyn[t]
 end

@initialize model

end


function compute()::Int
    numerators_product = 1
    denominators_product = 1
    for i ∈ 1:9
        for j ∈ 1:i - 1
            for k ∈ 1:j - 1
                if (k * 10 + i) * j == (i * 10 + j) * k
                    numerators_product *= k
                    denominators_product *= j
                end
            end
        end
    end
    return(denominators_product ÷ gcd(numerators_product, denominators_product))
end


using Nemo
using Hecke
using ToyFHE
using Primes
using Test
using GaloisFields

const ℤ = ZZ
const ℤx, x = ZZ["x"]
const ℤpx, xp = ResidueRing(ZZ, 2)["xp"]
ℛ = ResidueRing(ℤpx, cyclotomic(7, x))

plain = PolyCRTEncoding(zero(ℛ))
β = GaloisFields.gen(eltype(plain))

plain[1] = β+1
plain[2] = β^2+1

p = convert(ℛ, plain)
enc = PolyCRTEncoding(p)
@test enc[1] == β+1
@test enc[2] == β^2+1


module ft2d
using SpecialFunctions
export rectft,circft,ellipft,recip2real,recipvec2real,real2recip

function rectft(frax,fray,dnx,dny)
    #rectangle transforms to sinc
    return frax*fray*sinc.(frax*dnx).*sinc.(fray*dny)
end

function ellipft(frax,fray,dnx,dny)
    #argument of the bessel function
    radix=.5*sqrt.((dnx*frax).^2+(dny*fray).^2)
    result=besselj.(1,2*pi*radix)./radix
    #asymptotic value for going towards 0
    result[radix.==0].=pi
    #scale
    return result*frax*fray/4
end

function circft(fra,dnx,dny)
    return ellipft(fra,fra,dnx,dny)
end

function real2recip(dnx,dny,f,x,y)
    F=zeros(size(dnx))*1im
    for i=1:size(dnx,1)
        for j=1:size(dnx,2)
            F[i,j]=size(x,1)^-1*size(x,2)^-1*sum(f.*exp.(-1im*dnx[i,j]*x*2*pi-1im*dny[i,j]*y*2*pi))
        end
    end
    return F
end
function recip2real(dnx,dny,F,x,y)
    f=zeros(size(x))*1im
    for i=1:size(x,1)
        for j=1:size(y,2)
            f[i,j]=size(dnx,1)^-1*sum(F.*exp.(1im*x[i,j]*dnx*2*pi+1im*y[i,j]*dny*2*pi))
        end
    end
    return f
end

function recipvec2real(nx,ny,F,x,y)
    f=zeros(size(x))*1im
    for i=1:size(x,1)
        for j=1:size(y,2)
            f[i,j]=sum(F.*exp.(1im*x[i,j]*nx*2*pi+1im*y[i,j]*ny*2*pi))
        end
    end
    return f
end
end


#
#  NLS: Non Linear Schrödinger
#

function NLSHam(u,p)

    N = 5

    @inbounds begin
    q = view(u,1:N)
    p = view(u,N+1:2*N)

    H1=zero(eltype(u))
    H2=zero(eltype(u))

    for i in 1:N
        H1+=1/4*(q[i]^2+p[i]^2)^2
    end

    for i in 2:N
        H2+=(p[i-1]^2*p[i]^2+q[i-1]^2*q[i]^2-q[i-1]^2*p[i]^2-p[i-1]^2*q[i]^2+
             4*p[i-1]*p[i]*q[i-1]*q[i])
    end

    return(H1-H2)
    end

end

function NLSODE!(du,u,p,t)

    N = 5

    q = view(u,1:N)
    p = view(u,N+1:2*N)

    du[1]=p[1]*(q[1]^2+p[1]^2)-(2*p[1]*p[2]^2-2*p[1]*q[2]^2+4*p[2]*q[1]*q[2])
    du[N+1]=-q[1]*(q[1]^2+p[1]^2)+(2*q[1]*q[2]^2-2*q[1]*p[2]^2+4*p[1]*p[2]*q[2])

    for i in 2:N-1

        du[i]=p[i]*(q[i]^2+p[i]^2)-(2*p[i-1]^2*p[i]-2*q[i-1]^2*p[i]+4*p[i-1]*q[i-1]*q[i])-(2*p[i]*p[i+1]^2-2*p[i]*q[i+1]^2+4*p[i+1]*q[i]*q[i+1])

        du[i+N]=-q[i]*(q[i]^2+p[i]^2)+(2*q[i-1]^2*q[i]-2*p[i-1]^2*q[i]+4*p[i-1]*p[i]*q[i-1])+(2*q[i]*q[i+1]^2-2*q[i]*p[i+1]^2+4*p[i]*p[i+1]*q[i+1])

   end

   du[N]=p[N]*(q[N]^2+p[N]^2)-(2*p[N-1]^2*p[N]-2*q[N-1]^2*p[N]+4*p[N-1]*q[N-1]*q[N])
   du[2*N]=-q[N]*(q[N]^2+p[N]^2)+(2*q[N-1]^2*q[N]-2*p[N-1]^2*q[N]+4*p[N-1]*p[N]*q[N-1])

end


# Defining resolution
dx, dy = 0.5, 0.5

include.(["Geometry.jl", "Math.jl"])
using Plots

# Problem definition
k(_, y) = y >= 0.5 ? 2 : 1
g(x, _) = cbrt(x^2)

shape = [(-1, -1), (1, -1), (1, 0), (0, 0), (0, 1), (-1, 1)]
Γ = [g, 1-g, 0, 0, g, -1-g]
Γn, Γd = prepareEdge(shape, Γ)

# Base functions
e(x₀, y₀) = (x, y) -> (x₀ - dx, y₀ - dy) < (x, y) < (x₀ + dx, y₀ + dy) ? (1 - abs(x - x₀) / dx) * (1 - abs(y - y₀) / dy) : 0

L(v) = ∑([∫dl(k * gᵢ * v, Γᵢ) for (gᵢ, Γᵢ) in Γn])
B(u, v) = ∫∫dS(k * (∂x(u) * ∂x(v) + ∂y(u) * ∂y(v)), (-1, -1), (1, 1))

eᵢ = [e(x, y) for x ∈ -1:dx:1, y ∈ -1:dy:1 if inpolygon((x, y), shape) != 0 && !onEdge((x, y), Γd)]
wᵢ = [B(u, v) for u ∈ eᵢ, v ∈ eᵢ] \ [L(v) for v ∈ eᵢ]

f(x, y) = ∑(wᵢ .* [e(x, y) for e ∈ eᵢ])

# Drawing plot
X, Y = -1:0.01:1, -1:0.01:1
Z = [f(x, y) for y ∈ Y, x ∈ X]
heatmap(X, Y, Z, aspect_ratio = 1)


# Can we approximate non-linear limb-darkening with
# polynomial?

include("/Users/ericagol/Computer/Julia/regress.jl")

nmu = 1000; mu = linspace(0,1,nmu)
fnon = sqrt.(mu).^3-mu
using PyPlot
for i=1:20
  fn = zeros(i,nmu)
  fn[1,:] = 1.0
  for j=2:i
    fn[j,:] =mu.^(j-1)
  end
  coeff,cov = regress(fn,fnon,ones(nmu))
  fmod = zeros(nmu)
  for j=1:i
    fmod += coeff[j]*fn[j,:]
  end
  clf()
  plot(mu,fnon+mu)
  plot(mu,fmod+mu)
  plot(mu,fmod-fnon)
  println("i: ",i," scatter: ",std(fmod-fnon)," max: ",maximum(abs,fmod-fnon))
  println("minimum: ",minimum(fmod+mu)," maximum: ",maximum(fmod+mu))
  read(STDIN,Char)
end


using LazySets, MathematicalPredicates

function linearSwitching_specification()
    # initial set in mode 1
    X0_m1 = Singleton([3.1, 4.0, 0.0, 0.0, 0.0])
    X0 = [(1, X0_m1)]

    # safety property: x1 > -1.2
    border = HalfSpace([1.0, 0.0, 0.0, 0.0, 0.0], -1.2)  # x1 ≤ -1.2
    property = is_disjoint_from(border)

    # time horizon: 1 time unit
    time_horizon = 1.0

    # specification
    O = Dict(:T => time_horizon, :property => property)

    return X0, O
end


mutable struct Lorentz
    σ::Float64
    ρ::Float64
    β::Float64
    x::Float64
    y::Float64
    z::Float64
end

function step!(l::Lorentz, dt::Float64)
    dx = l.σ*(l.y - l.x)
    dy = l.x*(l.ρ - l.z) - l.y
    dz = l.x*l.y - l.β*l.z
    l.x += dt*dx
    l.y += dt*dy
    l.z += dt*dz
end

attractor = Lorentz(10.0,28.0,8/3,1,1,1)

using Plots

default(legendfontsize=16, dpi=150)
fig = plot3d(1,xlim=(-20.0,20.0),ylim=(-20.0,20.0),zlim=(0,50.0),
            title="Now this is the nice Lorentz",color="purple",
            marker=2,linestyle=:dot,linewidth=0.5,markerstrokewidth=0.0,
            legend=false)

@gif for i = 1:1500
    step!(attractor,0.01)
    push!(fig,attractor.x,attractor.y,attractor.z)
end every 10


export lyapunovs, lyapunov
#######################################################################################
#                                      Discrete                                       #
#######################################################################################
"""
```julia
lyapunovs(ds::DynamicalSystem, N; kwargs...) -> [λ1, λ2, ..., λD]
```
Calculate the spectrum of lyapunov exponents [1] of `ds` by applying the
QR-decomposition method `N` times (see method "H2" of [2]).
Returns a vector with the *final*
values of the lyapunov exponents in descending order.
# Keyword Arguments:
* `Ttr` : Extra "transient" time to evolve the system before application of the
  algorithm. Should be `Int` for discrete systems. Defaults are
  system type dependent.
* `dt = 1.0` : (only for continuous) Time of individual evolutions
  between sucessive orthonormalization steps.
* `diff_eq_kwargs = Dict()` : (only for continuous)
  Keyword arguments passed into the solvers of the
  `DifferentialEquations` package (see `evolve` or `timeseries` for more info).

[1] : A. M. Lyapunov, *The General Problem of the Stability of Motion*,
Taylor & Francis (1992)

[2] : K. Geist *et al*, Progr. Theor. Phys. **83**, pp 875 (1990)
"""
function lyapunovs(ds::DiscreteDS, N::Real; Ttr::Real = 100)

  u = deepcopy(ds.state)
  D = length(u)
  eom = ds.eom
  jac = ds.jacob
  # Transient iterations
  for i in 1:Ttr
    u = eom(u)
  end

  # Initialization
  λ = zeros(eltype(u), D)
  Q = eye(eltype(u), D)
  K = copy(Q)
  # Main algorithm
  for i in 1:N
    u = eom(u)
    A_mul_B!(K, jac(u), Q)

    Q, R = qr(K)
    for i in 1:D
      λ[i] += log(abs(R[i,i]))
    end
  end
  λ./N
end# this works. add tests and good to go.

"""
```julia
lyapunov(ds::DynamicalSystem, Τ; kwargs...) -> λ
```
Calculate the maximum lyapunov exponent using a method due to Benettin [1], which simply
evolves two neighboring trajectories while constantly rescaling one of the two.
`T`  denotes the total time of evolution (should be `Int` for discrete systems).

# Keyword Arguments:
* `Ttr` : Extra "transient" time to evolve the system before application of the
  algorithm. Should be `Int` for discrete systems. Defaults are
  system type dependent.
* `d0 = 1e-9` : Initial & rescaling distance between two neighboring trajectories.
* `threshold = 10^3*d0` : Threshold to rescale the test trajectory.
* `diff_eq_kwargs = Dict()` : (only for continuous)
  Keyword arguments passed into the solvers of the
  `DifferentialEquations` package (see `evolve` or `timeseries` for more info).
* `dt = 0.1` : (only for continuous) Time of evolution between each check of
  distance e0xceeding the `threshold`.

*Warning*: Default values have been choosen to give accurate & fast results for
maximum lyapunov exponent expected between 0.1 to 1.0. Be sure to adjust
them properly for your system. Specifically for the continuous systems,
be sure that `exp(λ*dt) < threshold/d0`.

[1] : G. Benettin *et al.*, Phys. Rev. A **14**, pp 2338 (1976)
"""
function lyapunov(ds::DiscreteDS, N::Real = 100000; Ttr::Int = 100,
  d0=1e-9*one(eltype(ds.state)), threshold=10^4*d0)

  threshold <= d0 && throw(ArgumentError("Threshold must be bigger than d0!"))
  eom = ds.eom
  st1 = deepcopy(ds.state)

  # transient
  for i in 1:Ttr
    st1 = eom(st1)
  end

  st2 = st1 + d0
  dist = d0
  λ = zero(eltype(st1))
  i = 0
  while i < N
    #evolve until rescaling:
    while dist < threshold
      st1 = eom(st1)
      st2 = eom(st2)
      # if hasnan
      dist = norm(st1 - st2)
      i+=1
      i>=N && break
    end
    a = dist/d0
    λ += log(a)
    #rescale:
    st2 = st1 + (st2 - st1)/a
    dist = d0
  end
  λ /= i
end

function lyapunovs(ds::DiscreteDS1D, N::Real = 10000; Ttr = 100)

  eom = ds.eom
  der = ds.deriv
  x = deepcopy(ds.state)

  #transient
  for i in 1:Ttr
    x = eom(x)
  end

  λ = log(abs(der(x)))
  for i in 1:N
    x = eom(x)
    λ += log(abs(der(x)))
  end
  λ/N
end
lyapunov(ds::DiscreteDS1D, N::Int=10000; Ttr::Int = 100) = lyapunovs(ds, N, Ttr=Ttr)

#######################################################################################
#                                    Continuous                                       #
#######################################################################################
function lyapunov(ds::ContinuousDS, T::Real = 10000.0; Ttr = 10.0,
  d0=1e-9*one(eltype(ds.state)), threshold=10^4*d0, dt = 0.1,
  diff_eq_kwargs = Dict())

  const dict = Dict()

  threshold <= d0 && throw(ArgumentError("Threshold must be bigger than d0!"))

  # Transient
  if Ttr != 0
    evolve!(ds, Ttr; diff_eq_kwargs = diff_eq_kwargs)
  end
  # initialize:
  st1 = ds.state
  st2 = st1 + d0
  prob1 = ODEProblem(ds, dt)
  prob2 = ODEProblem(ds, dt)
  prob2.u0 = st2
  dist = d0
  λ = zero(eltype(st1))
  t = zero(T)
  i = 0
  # start evolution and rescaling:
  while t < T
    #evolve until rescaling:
    while dist < threshold
      # evolve
      if diff_eq_kwargs == dict
        st1 = evolve!(prob1)
        st2 = evolve!(prob2)
      else
        st1 = evolve!(prob1, diff_eq_kwargs)
        st2 = evolve!(prob2, diff_eq_kwargs)
      end
      dist = norm(st1 - st2)
      t += dt
      i += 1
      t ≥ T && break
    end
    # add computed scale to accumulator:
    a = dist/d0
    if a > 1e^4 && i <= 1
      warnstr = "Distance between test and original trajectory exceeded threshold "
      warnstr*= "after just 1 evolution step. "
      warnstr*= "Please decrease `dt`, increase `threshold` or decrease `d0`."
      warn(warnstr)
      errorstr = "Parameters choosen for `lyapunov` with "
      errorstr*= "`ContinuousDS` are not accurate."
      throw(ArgumentError(errorstr))
    end
    λ += log(a); i+= 1
    #rescale:
    st2 = st1 + (st2 - st1)/a
    prob2.u0 = st2
    dist = d0; i = 0
  end
  λ /= t
end

function lyapunovs(ds::ContinuousDS, N::Real;
  Ttr::Real = 1.0, diff_eq_kwargs::Dict = Dict(), dt::Real = 1.0)

  D = dimension(ds)
  T = eltype(ds.state)
  jac = ds.jacob
  const dict = Dict()
  # Transient
  if Ttr != 0
    evolve!(ds, Ttr; diff_eq_kwargs = diff_eq_kwargs)
  end
  # Initialization
  tbprob = tangentbundle_setup(ds, dt)
  λ = zeros(T, D)
  Q = eye(eltype(ds.state), D)

  # Main algorithm
  for i in 1:N
    tbprob.u0[:, 2:end] .= Q #update tangent dynamics state
    # evolve system and tangent dynamics:
    if diff_eq_kwargs == dict
      evolve!(tbprob)
    else
      evolve!(tbprob, diff_eq_kwargs)
    end

    # Perform QR:
    Q, R = qr(view(tbprob.u0, :, 2:D+1))
    for j in 1:D
      λ[j] += log(abs(R[j,j]))
    end
  end
  λ./(N*dt) #return spectrum
end


function rotate_matrix!(mat :: Array{Int32, 2})
  N :: Int = size(mat, 1)
  for row = 1:int(N / 2)
    for col = row:(N - row)
      mat[row, col], mat[col, N - row + 1], mat[N - row + 1, N - col + 1], mat[N - col + 1, row] =
      mat[N - col + 1, row], mat[row, col], mat[col, N - row + 1], mat[N - row + 1, N - col + 1]
    end
  end
end

# Time  : O(n^2)
# Space : O(1)

using Base: Test

mat = Array{Int32, 2}([[1 2 3];[4 5 6];[7 8 9]])
rotate_matrix!(mat)
@test mat == Array{Int32, 2}([[7 4 1];[8 5 2];[9 6 3]])
rotate_matrix!(mat)
@test mat == Array{Int32, 2}([[9 8 7];[6 5 4];[3 2 1]])


@testset "normal sampler" begin

	cc = dummyChoiceContext()

	@testset "getparams, numparams, paramranges, sample" begin

		@testset "using default construction" begin

		    s = DataGenerators.NormalSampler()

		    @testset "numparams and paramranges" begin
		        @test DataGenerators.numparams(s) == 2
		        prs = DataGenerators.paramranges(s)
		        @test typeof(prs) <: Vector{Tuple{Float64,Float64}}
		        @test prs == [(-realmax(Float64), realmax(Float64)), (0.0, realmax(Float64))]
			end

		    @testset "default params" begin
		        @test DataGenerators.getparams(s) == [0.0, 1.0]
		    end

		    @mtestset "default sampling" reps=Main.REPS alpha=Main.ALPHA begin
		        x, trace = DataGenerators.sample(s, (0,1), cc)
		        @test typeof(x) <: Float64
				@mtest_that_sometimes x < -1.0
				@mtest_that_sometimes -1.0 <= x < 0.0
				@mtest_that_sometimes 0.0 < x <= 1.0
				@mtest_that_sometimes 1.0 < x
		        @mtest_distributed_as Normal(0.0,1.0) x
			end

		end

		@testset "using non-default construction" begin

		    s = DataGenerators.NormalSampler([-949.88, 123.4])
		    @test DataGenerators.getparams(s) == [-949.88, 123.4]

		    @mtestset "consistent with normal" reps=Main.REPS alpha=Main.ALPHA begin
		    	x, trace = DataGenerators.sample(s, (0,1), cc)
		        @mtest_distributed_as Normal(-949.88,123.4) x
		    end

		end

	end

	@testset "setparams" begin

	    s = DataGenerators.NormalSampler()
	    prs = DataGenerators.paramranges(s)
	    midparams = map(pr->robustmidpoint(pr[1],pr[2]), prs)

	    @testset "valid parameters $params" for params in [[0.8, 19.3], [87.4, 0.0],]
		# note special case of sigma = 0

	        DataGenerators.setparams!(s, params)

			@mtestset "consistent with normal" reps=Main.REPS alpha=Main.ALPHA begin
	        	x, trace = DataGenerators.sample(s, (0,1), cc)
				mu = params[1]
				sigma = params[2] == 0.0 ? eps(params[2]) : params[2]
		        @mtest_distributed_as Normal(mu,sigma) x
			end

	    end

	    @testset "boundary values for parameter index $pidx" for pidx in 1:length(prs)

	        pr = prs[pidx]
	        params = copy(midparams)

			@testset "bound index $bidx" for bidx in 1:2

	            params[pidx] = pr[bidx]
	            DataGenerators.setparams!(s, params)

				@mtestset "consistent with normal" reps=Main.REPS alpha=Main.ALPHA begin
	            	x, trace = DataGenerators.sample(s, (0,1), cc)
					mu = params[1]
					sigma = params[2] == 0.0 ? eps(params[2]) : params[2]
			        @mtest_distributed_as Normal(mu,sigma) x
				end

				@testset "range check exception" begin
		            params[pidx] = bidx == 1 ? prevfloat(pr[bidx]) : nextfloat(pr[bidx])
		            @test_throws ErrorException DataGenerators.setparams!(s, params)
				end

			end

	    end

	    @testset "wrong number of parameters" begin

	        @test_throws ErrorException DataGenerators.setparams!(s, midparams[1:end-1])
	        @test_throws ErrorException DataGenerators.setparams!(s, [midparams; 0.5])

	    end

	end

	@testset "estimateparams" begin

	    otherparams = [155.6, 13.7]

	    @testset "from parameters $params" for params in [[-56.0, 18.9], [3.44, 0.0],]
		# note special case of sigma = 0

	        s1 = DataGenerators.NormalSampler(params)
	        s2 = DataGenerators.NormalSampler(otherparams)
	        traces = map(1:100) do i
	            x, trace = DataGenerators.sample(s1, (0,1), cc)
	            trace
	        end
	        estimateparams!(s2, traces)

			@mtestset "consistent with normal" reps=Main.REPS alpha=Main.ALPHA begin
	        	x, trace = DataGenerators.sample(s2, (0,1), cc)
				if params[2] > 0.0
					@mtest_distributed_as Normal(params[1],params[2]) x
				else
					@test isapprox(x, params[1])
					# necessary since above distributed_as fails when sigma=0.0 owing to rounding errors
				end
			end

	    end

	    @testset "too few traces" begin

	        params = [21.0, 0.7]
	        s1 = DataGenerators.NormalSampler(params)
	        s2 = DataGenerators.NormalSampler(otherparams)
	        traces = map(1:1) do i
	            x, trace = DataGenerators.sample(s1, (0,1), cc)
	            trace
	        end

			@mtestset "consistent with normal" reps=Main.REPS alpha=Main.ALPHA begin
	        	x, trace = DataGenerators.sample(s2, (0,1), cc)
		        @mtest_distributed_as Normal(otherparams[1],otherparams[2]) x
			end

	    end

	end

end


### A Pluto.jl notebook ###
# v0.12.18

using Markdown
using InteractiveUtils

# ╔═╡ 60941eaa-1aea-11eb-1277-97b991548781
using Revise,PlutoUI,ExtendableGrids

# ╔═╡ df8aefc0-5273-11eb-1a67-3552732deae0
using Plots

# ╔═╡ 08fa72d6-5274-11eb-03bd-ef885fdd216d
P=Plots

# ╔═╡ e5cec5c8-5273-11eb-0e0b-03960befcc71
X=collect(0:0.5:10)

# ╔═╡ f088d080-5273-11eb-2085-e5d4734512ff
g1=simplexgrid(X)

# ╔═╡ ff091d22-5273-11eb-2645-89630b592fa0
gridplot(g1,Plotter=P,resolution=(500,100))

# ╔═╡ 2dd685fe-5274-11eb-173c-6f2ccaf41aae
gridplot(g1,map(x->sin(x),g1),Plotter=P)

# ╔═╡ 8d1857ae-5274-11eb-265d-09604d9dba94
g2=simplexgrid(X,X)

# ╔═╡ 9526886c-5274-11eb-33a6-d95a16547472
gridplot(g2,Plotter=P)

# ╔═╡ ae81ae16-5274-11eb-36e6-a1dd77e51abb
gridplot(g2,map((x,y)->(sin(x)*exp(-(y-5)^2)),g2),Plotter=P,colormap=:hot)

# ╔═╡ eb380f5a-5274-11eb-2a7a-87c75706a2ae
p=GridPlotContext(Plotter=P,layout=(1,2),fignum=2,resolution=(800,400))

# ╔═╡ 0114cb24-5275-11eb-3013-0f3b170f25de
begin
	gridplot!(p[1,1],g1,map(x->sin(x),g1),title="1D")
	gridplot!(p[1,2],g2,map((x,y)->(sin(x)*exp(-(y-5)^2)),g2),colormap=:hot,title="2D")
	reveal(p)
end

# ╔═╡ Cell order:
# ╠═60941eaa-1aea-11eb-1277-97b991548781
# ╠═df8aefc0-5273-11eb-1a67-3552732deae0
# ╠═08fa72d6-5274-11eb-03bd-ef885fdd216d
# ╠═e5cec5c8-5273-11eb-0e0b-03960befcc71
# ╠═f088d080-5273-11eb-2085-e5d4734512ff
# ╠═ff091d22-5273-11eb-2645-89630b592fa0
# ╠═2dd685fe-5274-11eb-173c-6f2ccaf41aae
# ╠═8d1857ae-5274-11eb-265d-09604d9dba94
# ╠═9526886c-5274-11eb-33a6-d95a16547472
# ╠═ae81ae16-5274-11eb-36e6-a1dd77e51abb
# ╠═eb380f5a-5274-11eb-2a7a-87c75706a2ae
# ╠═0114cb24-5275-11eb-3013-0f3b170f25de


using LinearOperators
using MatrixDepot
using KrylovMethods
using Base.Test

println("=== Testing CLGS ===")

# test with sparse matrix
A   = sprandn(100,10,.2)
Af = (x,flag) -> ((flag=='F') ? A*x : A'*x)
rhs = randn(100)

# test early stopping behaviour
tt = cgls(Af,rhs,tol=1e-20,maxIter=2,out=2,storeInterm=true)
@test tt[2]==-1

# test behaviour for zero rhs
tt = cgls(Af,zeros(size(A,1)),tol=1e-20,maxIter=2,out=2)
@test tt[2]==-9
@test all(tt[1].==0)

xgt = full(A)\rhs
xt  = cgls(LinearOperator(A),rhs,tol=1e-20)
xt2 = cgls(Af,rhs,tol=1e-20,maxIter=100,x=randn(size(xt[1])))
Xt  = cgls(A,rhs,tol=1e-20,maxIter=100,storeInterm=true,out=1)

@test norm(xgt-xt[1])/norm(xgt) < 1e-6
@test norm(xgt-xt2[1])/norm(xgt) < 1e-6
@test norm(Xt[1][:,end]-xt[1])/norm(xt[1]) < 1e-15

# test with dense hilbert matrix
A   = matrixdepot("hilb",10,3)
rhs = randn(size(A,1))
xgt = A\rhs

x1  = cgls(LinearOperator(A),rhs,tol=1e-10,maxIter=10)
x2  = cgls(A,rhs,tol=1e-10,maxIter=10)

@test norm(xgt-x1[1])/norm(xgt) < 1e-6
@test norm(xgt-x2[1])/norm(xgt) < 1e-6
@test norm(x1[1]-x2[1])/norm(x1[1]) < 1e-12

println("=== CGLS: All tests passed. ===")


# This file is part of IntegerSequences.
# Copyright Peter Luschny. License is MIT.

(@__DIR__) ∉ LOAD_PATH && push!(LOAD_PATH, (@__DIR__))

module Abundant
using NumberTheory, IterTools, Counts

export ModuleAbundant
export isAbundant, is005101, I005101, F005101, L005101, V005101

"""
``n`` is an abundant number if ``σ(n) > 2n``. An abundant number is a number for which the sum of its proper divisors is greater than the number itself.

* isAbundant, is005101, I005101, F005101, L005101, V005101.
"""
const ModuleAbundant = ""

"""
Is ``n`` an abundant number, i.e. is ``σ(n) > 2n`` ?
"""
isAbundant(n) = σ(n) > 2n

"""
Is ``n`` a term of sequence A005101?
"""
is005101(n) = isAbundant(n)

"""
Iterate over the first ``n`` abundant numbers.
"""
I005101(n) = takeFirst(isAbundant, n)

"""
Iterate over the abundant numbers which do not exceed ``n (1 ≤ i ≤ n)``.
"""
F005101(n) = Iterators.filter(isAbundant, 1:n)

"""
Return a list of the  first ``n`` abundant numbers.
"""
L005101(n) = collect(I005101(n))

"""
Return the value of the ``n``-th abundant number.
"""
V005101(n) = nth(I005101(n), n)

#START-TEST-########################################################

using Test, SeqTests, SeqUtils

function test()
    @testset "Abundant" begin
        @test isAbundant(100800) == true
        @test isAbundant(2402400) == true
        @test isAbundant(49008960) == true

        if is_oeis_installed()
            SeqTest([L005101], 'L')
        end
    end
end

function demo()
    println(V005101(15))
    println(L005101(15))

    for a in I005101(15) print(a, ", ") end; println("...")
    for a in F005101(40) print(a, ", ") end; println("...")

    for n in 40:50 println(n, " ↦ ", isAbundant(n)) end
    for n in 1:6   println(n, " ↦ ", L005101(n)) end

    for a in I005101(1000)
        isodd(a) && print(a, " ")
    end
    println()
end

"""
L005101(5000)
    0.018731 seconds (40.38 k allocations: 759.328 KiB)
"""
function perf()
    GC.gc()
    @time L005101(5000)
end

function main()
    test()
    demo()
    perf()
end

main()

end # module

#=
Test Summary: | Pass  Total
Abundant      |    3      3

72

[12, 18, 20, 24, 30, 36, 40, 42, 48, 54, 56, 60, 66, 70, 72]
12, 18, 20, 24, 30, 36, 40, 42, 48, 54, 56, 60, 66, 70, 72, ...
12, 18, 20, 24, 30, 36, 40, ...

40 ↦ true
41 ↦ false
42 ↦ true
43 ↦ false
44 ↦ false
45 ↦ false
46 ↦ false
47 ↦ false
48 ↦ true
49 ↦ false
50 ↦ false

1 ↦ [12]
2 ↦ [12, 18]
3 ↦ [12, 18, 20]
4 ↦ [12, 18, 20, 24]
5 ↦ [12, 18, 20, 24, 30]
6 ↦ [12, 18, 20, 24, 30, 36]

945 1575 2205 2835 3465

0.018731 seconds (40.38 k allocations: 759.328 KiB)
=#


"""
Returns the boundingbox of a patch in terms of its center and halfsize.

    function boundingbox{U,D,C,N,T}(p::Simplex{U,D,C,N,T}) -> center, halfsize
"""
function boundingbox(p::Simplex{U,D,C,N,T}) where {U,D,C,N,T}

    # ll = minimum(p.vertices)
    # ur = maximum(p.vertices)

    ll = first(p.vertices); for v in p.vertices; ll = min.(v, ll); end
    ur = first(p.vertices); for v in p.vertices; ll = max.(v, ll); end

    center = (ll + ur) / 2
    halfsize = maximum(ur - center)

    return center, halfsize
end

function boundingbox(V)

    # ll = minimum(V)
    # ur = maximum(V)

    ll = first(V); for v ∈ V; ll = min.(v, ll); end
    ur = first(V); for v ∈ V; ur = max.(v, ur); end

    c = (ll + ur) / 2
    s = maximum(ur - c)

    return c, s
end

boundingbox(p::SVector{N,T}) where {N,T<:Number} = p, zero(eltype(p))


### A Pluto.jl notebook ###
# v0.12.11

using Markdown
using InteractiveUtils

# ╔═╡ 206337dc-f928-11ea-04ac-278c8fc8079d
using Pkg, DrWatson

# ╔═╡ 20646558-f928-11ea-3351-bf12703651b2
begin
	using StatisticalRethinking
end

# ╔═╡ 0e0d2086-f926-11ea-0470-451b528c9d02
md"## Clip-02-01-02t.jl"

# ╔═╡ 2069098c-f928-11ea-1008-d75cba2186b4
md"## snippet 2.1"

# ╔═╡ 20699ba4-f928-11ea-3f37-8359c913281d
begin
	ways = [0, 3, 8, 9, 0]
	ways = ways / sum(ways)
end

# ╔═╡ 20762c66-f928-11ea-32ec-53742fbaff9b
md"## snippet 2.2"

# ╔═╡ 207be6ea-f928-11ea-32df-cd2ed1f344d1
md"##### With Distributions.jl working with distributions is a little different from R. Instead of having `rbinom`, `dbinom` and `pbinom` we just got the `Binomial` distribution which to work with."

# ╔═╡ aa0f1104-f928-11ea-0830-cbd5ac5bcb63
d = Binomial(9, 0.5)             # Binomial distribution

# ╔═╡ aa0f4d9a-f928-11ea-038e-c31458a61022
d.n                              # Number of trials parameter

# ╔═╡ aa0fd1cc-f928-11ea-1da8-c5f3559d2d0a
rand(d)                          # singe random draw

# ╔═╡ aa1b33a8-f928-11ea-328e-bf25c307896b
rand(d, 10)                      # 10 random draws

# ╔═╡ aa1bc3c0-f928-11ea-2dff-950e37ca54d3
pdf(d, 6)                        # probability density of getting a 6

# ╔═╡ aa261070-f928-11ea-3502-83a01308f7bd
cdf(d, 6)                        # cumulative probability of getting 6

# ╔═╡ aa271b96-f928-11ea-162b-390eddab0c11
pdf(Binomial(9, 0.5), 6)         # probability density of getting a 6

# ╔═╡ 9e7664be-f928-11ea-20b2-653cf2037050
md"## End of clip-02-01-02t.jl"

# ╔═╡ Cell order:
# ╟─0e0d2086-f926-11ea-0470-451b528c9d02
# ╠═206337dc-f928-11ea-04ac-278c8fc8079d
# ╠═20646558-f928-11ea-3351-bf12703651b2
# ╟─2069098c-f928-11ea-1008-d75cba2186b4
# ╠═20699ba4-f928-11ea-3f37-8359c913281d
# ╟─20762c66-f928-11ea-32ec-53742fbaff9b
# ╟─207be6ea-f928-11ea-32df-cd2ed1f344d1
# ╠═aa0f1104-f928-11ea-0830-cbd5ac5bcb63
# ╠═aa0f4d9a-f928-11ea-038e-c31458a61022
# ╠═aa0fd1cc-f928-11ea-1da8-c5f3559d2d0a
# ╠═aa1b33a8-f928-11ea-328e-bf25c307896b
# ╠═aa1bc3c0-f928-11ea-2dff-950e37ca54d3
# ╠═aa261070-f928-11ea-3502-83a01308f7bd
# ╠═aa271b96-f928-11ea-162b-390eddab0c11
# ╟─9e7664be-f928-11ea-20b2-653cf2037050


push!( LOAD_PATH, "../" )
import InformationInequalities  # Import the module to  have access to functions

"""
List all conditional Mutual Information expressions for a given number `n` of random variables. Conditional Entropies are of the form `I(X;Y|Z)` aka Mutual information between `X` and `Y` given `Z`.
```julia-repl
julia> ConditionalMutualInformationList(2,"🍉")
["I(🍉1;🍉2)" "I(🍉2;🍉1)"]
julia> ConditionalMutualInformationList(3)
["I(X1;X2)","I(X1;X2|X3)","I(X1;X3)","I(X1;X3|X2)","I(X2;X1)","I(X2;X1|X3)","I(X2;X3)","I(X2;X3|X1)","I(X3;X1)","I(X3;X1|X2)","I(X3;X2)","I(X3;X2|X1)"]
```
"""
function ConditionalMutualInformationList(m::Int64=2,RV::AbstractString="X")
	y=  [("$(RV)" .* string(i) * ";" * "$(RV)" .* string(j) * "|" * find_subset(m,i,j,RV)[k]) for i=1:m for j =setdiff(1:m,i) for k=1:Int64(exp2(m-2)) ]
	y0=replace.(y,"|" => "| ")
	y1=replace.(y0,"| $(RV)" => "|$(RV)")
	y2=replace.(y1,"| " => "")
	yy = "I(" .* y2 .* ")"
	return yy[:]
end


using Test, Fracciones

@testset "Constructores" begin
    @test Fraccion(true, 0x0f) === Fraccion{UInt8}(1, 15) # comprobar tipos
    @test Fraccion(0x00, 5) === Fraccion(0, 1) # ok aunque 0x00 == typemin(UInt8)
    @test Fraccion(2, 0) === Fraccion(1, 0)
    @test Fraccion(-2, 0) === Fraccion(-1, 0)
    # inválidos
    @test_throws ArgumentError Fraccion(0, 0)
    @test_throws ArgumentError Fraccion(Int8(-128), Int8(5))
    @test_throws ArgumentError Fraccion(Int8(5), Int8(-128))
    # otros constructores
    @test Fraccion{Int}(1.0, 2.0) === Fraccion(1,2)
    @test Fraccion{UInt8}(1, 2) === Fraccion(0x01, 0x02)
    @test Fraccion{UInt8}(Fraccion(1,2)) === Fraccion(0x01, 0x02)
    @test Fraccion(0) === Fraccion(0,1)
    @test Fraccion(2) === Fraccion(2,1)
    @test Fraccion{UInt8}(2.0) === Fraccion(0x02,0x01)
    @test Fraccion(2.0) === Fraccion(2,1)
    @test Fraccion(Inf) === Fraccion(1,0)
    @test Fraccion(-Inf) === Fraccion(-1,0)
    @test Fraccion(1,2) === Fraccion(Fraccion(1,2))
end

@testset "Conversiones" begin
    @test Int(Fraccion(4,2)) === 2
    @test float(Fraccion(1,4)) == 0.25
    @test Float64[Fraccion(1,2), Fraccion(1,4)] == [0.5, 0.25]
end

@testset "Signos" begin
    @test Fraccion(5, -2) == Fraccion(-5, 2) == -Fraccion(5,2)
    @test numerador(Fraccion(5,-2)) < 0
    @test denominador(Fraccion(5,-2)) > 0
end

@testset "Propiedades y generadores" begin
    @test numerador(Fraccion(2,4)) == 1
    @test denominador(Fraccion(2,4)) == 2
    @test numerador(Fraccion(3,0)) == 1
    @test reciproco(Fraccion(2, 6)) == 3
    @test reciproco(3) == Fraccion(1, 3)
    @test sign(Fraccion(1,2)) == 1
    @test sign(Fraccion(-1,2)) == -1
    @test sign(Fraccion(0)) == 0
    @test abs(Fraccion(-1,2)) == Fraccion(1,2)
    @test one(Fraccion{UInt8}) === Fraccion(0x01)
    @test one(Fraccion) === Fraccion(1)
    @test zero(Fraccion{UInt8}) === Fraccion(0x00)
    @test zero(Fraccion) === Fraccion(0)
    @test zeros(Fraccion, 2) == Fraccion[0, 0]
    @test ones(Fraccion, 2) == Fraccion.([1, 1])
    @test typemin(Fraccion{Int}) === Fraccion(-1,0)
    @test typemin(Fraccion{UInt8}) === Fraccion(0x00)
    @test typemin(Fraccion{Bool}) === Fraccion(false)
    @test typemax(Fraccion{Int}) === Fraccion(1,0)
    @test typemax(Fraccion{UInt8}) === Fraccion(0x01,0x00)
    @test typemax(Fraccion{Bool}) === Fraccion(true,false)
end

@testset "Operaciones algebraicas" begin
    @test Fraccion(1, 12) + Fraccion(5, 3) == Fraccion(7, 4)
    @test Fraccion(2, 3) - Fraccion(1, 5) == Fraccion(7, 15)
    # con infinitos
    @test Fraccion(1, 0) + Fraccion(1,2) === Fraccion(1, 0)
    @test Fraccion(1, 0) + 3 == Fraccion(1, 0)
    @test Fraccion(1, 3) * Fraccion(3, 4) == Fraccion(1, 4)
    @test Fraccion(1, 2) / Fraccion(3, 4) == Fraccion(2, 3)
    @test_throws ArgumentError Fraccion(0, 1) * Fraccion(-1, 0)

    @test Fraccion(2,3)^-2 == Fraccion(9,4)
    @test Fraccion(2,3)^3 == Fraccion(8,27)
    @test Fraccion(1,3)^0 == 1
    # combinar con otros tipos de números
    @test Fraccion(1,3) + 2 == 2 + Fraccion(1,3) == Fraccion(7,3)
    @test Fraccion(1,3) + 0 == Fraccion(1,3)
    @test 2 - Fraccion(1,0) == Fraccion(-1,0)
    @test Fraccion(1,3) * 2 == 2 * Fraccion(1,3) == Fraccion(2,3)
    @test Fraccion(1,3) * 0 == Fraccion(0,1)
    @test 2 * Fraccion(1,0) == Fraccion(1,0)
    @test Fraccion(1,3) / 2 == Fraccion(1,6)
    @test 2 / Fraccion(1,3) == Fraccion(6)
    @test Fraccion(1,6) / 0 == Fraccion(1,0)
    @test 0 / Fraccion(1,0) == Fraccion(0,1)
end

@testset "Comparaciones" begin
    for (menor, mayor) = (
        (Fraccion(1,4), Fraccion(1,2)), (0.25, Fraccion(1,2)), (Fraccion(1,4), 0.5),
        (Fraccion(0), Fraccion(1,2)), (0, Fraccion(1,2)), (Fraccion(0), 0.5),
        (Fraccion(-1,4), Fraccion(0)), (-0.25, Fraccion(0)), (Fraccion(-1,4), 0),
        (Fraccion(0), Fraccion(1,0)), (0, Fraccion(1,0)), (Fraccion(0), Inf),
        (Fraccion(-1,0), Fraccion(0)), (-Inf, Fraccion(0)), (Fraccion(-1,0), 0),
        (Fraccion(-1,0), Fraccion(1,0)), (-Inf, Fraccion(1,0)), (Fraccion(-1,0), Inf)
    )
        @test menor < mayor
        @test -menor > -mayor
        @test menor ≤ mayor
        @test mayor ≥ menor
        @test menor == menor
        @test mayor == mayor
    end
end

@testset "Función y macro `fraccion`" begin
    @test fraccion(3, Fraccion(5,2)) == Fraccion(6,5)
    @test fraccion(Fraccion(5,2), 3) == Fraccion(5,6)
    @test (@fraccion 3/4) == Fraccion(3, 4)
    @test (@fraccion (3 + (4/3))/5^2) == Fraccion(13, 75) == 13/75
end


export softmax,∂softmax

function softmax(rᵢ) #for prediction
  Z=sum(exp(rᵢ),1)
  rₒ=exp(rᵢ)./Z
end
function ∂softmax(rᵢ,rₒ)
  ∂ij=Diagm(rₒ) - rₒ⊗rₒ  # ∂rₒᵢ/∂rᵢⱼ
end


using Test

using EquationsOfState.FiniteStrains

@testset "Test data from Pymatgen" begin
    volumes = [
        25.987454833,
        26.9045702104,
        27.8430241908,
        28.8029649591,
        29.7848370694,
        30.7887887064,
        31.814968055,
        32.8638196693,
        33.9353435494,
        35.0299842495,
        36.1477417695,
        37.2892088485,
        38.4543854865,
        39.6437162376,
        40.857201102,
        42.095136449,
        43.3579668329,
        44.6456922537,
        45.9587572656,
        47.2973100535,
        48.6614988019,
        50.0517680652,
        51.4682660281,
        52.9112890601,
        54.3808371612,
        55.8775030703,
        57.4014349722,
        58.9526328669,
    ]
    v0 = 40.98926572870838
    @test map(v -> getstrain(EulerianStrain(), v0, v), volumes) ≈ [
        0.17749734613767998,
        0.16201229839015363,
        0.14705196468522874,
        0.13259433633439055,
        0.11861428460608225,
        0.10509239492761757,
        0.09201008130906252,
        0.07934607736917354,
        0.06708557020358508,
        0.0552093893237503,
        0.043704151717372075,
        0.032551138219835574,
        0.021738453213956288,
        0.011250631487461304,
        0.0010768691828133559,
        -0.00879571243518995,
        -0.01838039465214164,
        -0.02768646727024454,
        -0.03672601609939291,
        -0.04550846145018128,
        -0.05404287277132974,
        -0.06233970600633609,
        -0.07040712280723271,
        -0.07825377702272912,
        -0.08588638996615916,
        -0.09331442415000685,
        -0.10054463011940301,
        -0.1075828678792159,
    ]
    @test [getstrain(LagrangianStrain(), v0, v) for v in volumes] ≈ [
        -0.13099486451833953,
        -0.12236351105872695,
        -0.11363226812607319,
        -0.10480202613156259,
        -0.09587095509895383,
        -0.08683995684674645,
        -0.07770989398154193,
        -0.06847899767396914,
        -0.059149424468252,
        -0.04971943052962041,
        -0.04019111457888069,
        -0.030561514081675456,
        -0.020832711371037982,
        -0.011003048988641961,
        -0.0010745548727579268,
        0.008953212194864557,
        0.0190818588446644,
        0.029309415622954416,
        0.039637468728734304,
        0.050065246093897176,
        0.06059200478213067,
        0.0712192845248587,
        0.08194633401200402,
        0.09277353626347284,
        0.10369906697722475,
        0.11472551485871496,
        0.125852144820906,
        0.13707718021610538,
    ]
    @test [getstrain(NaturalStrain(), v0, v) for v in volumes] ≈ [
        -0.15189876859201823,
        -0.14033801748544264,
        -0.1289092546069999,
        -0.1176106297063706,
        -0.10643692669611844,
        -0.09538653341846574,
        -0.08445778277754971,
        -0.07364595812672871,
        -0.06295105569265277,
        -0.05236861111679098,
        -0.04189858087249365,
        -0.03153541438302606,
        -0.02127915820568489,
        -0.011125922058677679,
        -0.0010757111979426712,
        0.008873996435816848,
        0.018726748238692272,
        0.028482534552264627,
        0.0381447307354657,
        0.04771440101503085,
        0.05719263915631435,
        0.06658253672817539,
        0.07588506772815591,
        0.0851021663162455,
        0.09423387093583208,
        0.10328387622610548,
        0.11225302719931324,
        0.1211413559225737,
    ]
    @test [getstrain(InfinitesimalStrain(), v0, v) for v in volumes] ≈ [
        -0.1640423928171002,
        -0.1506626772344306,
        -0.13758688871244362,
        -0.12480606002491879,
        -0.1123077673073063,
        -0.10008399218206754,
        -0.0881269055666829,
        -0.07642563827621052,
        -0.06497471350599215,
        -0.05376410009427657,
        -0.04278871466598844,
        -0.0320379239348092,
        -0.021507173948334346,
        -0.011188045308548222,
        -0.0010762899827498895,
        0.00883473873948748,
        0.01855249213433896,
        0.02808073099690478,
        0.03742638317829727,
        0.046593960004638224,
        0.05558787891231487,
        0.06441430751249311,
        0.07307726622682587,
        0.08158155182153393,
        0.08993010154841308,
        0.09812908257335062,
        0.10618193139700183,
        0.11409127770318894,
    ]
end


@testset "PorousModels" begin

    @testset "BrooksAndCorey" begin
        # HF135 membrane, Brooks and Corey model
        # Data from Buser (PhD thesis, 2016)
        # http://hdl.handle.net/1773/38064
        θr = 1.07e-6
        θs = 0.936
        k = 5.50e-13  # m**2
        α = 1/1.96  # 1/m
        n = 0.669

        bc = BrooksAndCorey(n=n, α=α, k=k, θr=θr, θs=θs)

        htest = -10.0

        @test @inferred hθ(bc, @inferred θh(bc, htest)) ≈ htest
        @test @inferred Ch(bc, htest) ≈ derivative(h -> θh(bc, h), htest)
        @test @inferred Cθ(bc, @inferred θh(bc, htest)) ≈ @inferred Ch(bc, htest)
        @test @inferred Kθ(bc, @inferred θh(bc, htest)) ≈ @inferred Kh(bc, htest)

        θtest = 0.5

        @test Dθ(bc, θtest) ≈ 1.8877259646141242e-06

        @test @inferred θh(bc, @inferred hθ(bc, θtest)) ≈ θtest
        @test @inferred Ch(bc, @inferred hθ(bc, θtest)) ≈ @inferred Cθ(bc, θtest)
        @test @inferred Kh(bc, @inferred hθ(bc, θtest)) ≈ @inferred Kθ(bc, θtest)
        @test @inferred Dθ(bc, θtest) ≈ @inferred Kθ(bc, θtest)/@inferred Cθ(bc, θtest)
    end

    @testset "VanGenuchten" begin
        # HF135 membrane, Van Genuchten model
        # Data from Buser (PhD thesis, 2016)
        # http://hdl.handle.net/1773/38064
        θr = 0.0473
        θs = 0.945
        k = 5.50e-13  # m**2
        α = 0.2555  # 1/m
        n = 2.3521

        vg = VanGenuchten(n=n, α=α, k=k, θr=θr, θs=θs)

        htest = -10.0

        @test @inferred Ch(vg, htest) ≈ 0.02896477570729651
        @test Kh(vg, htest) ≈ 9.430485870291618e-9

        @test @inferred hθ(vg, @inferred θh(vg, htest)) ≈ htest
        @test @inferred Ch(vg, htest) ≈ derivative(h -> θh(vg, h), htest)
        @test @inferred Cθ(vg, @inferred θh(vg, htest)) ≈ @inferred Ch(vg, htest)
        @test @inferred Kθ(vg, @inferred θh(vg, htest)) ≈ @inferred Kh(vg, htest)

        θtest = 0.5

        @test Dθ(vg, θtest) ≈ 1.769723354269708e-6

        @test @inferred θh(vg, @inferred hθ(vg, θtest)) ≈ θtest
        @test @inferred Ch(vg, @inferred hθ(vg, θtest)) ≈ @inferred Cθ(vg, θtest)
        @test @inferred Kh(vg, @inferred hθ(vg, θtest)) ≈ @inferred Kθ(vg, θtest)
        @test @inferred Dθ(vg, θtest) ≈ @inferred Kθ(vg, θtest)/@inferred Cθ(vg, θtest)
    end
end


using Test
using HMatrices
using LinearAlgebra
using Random
using StaticArrays

using HMatrices: RkMatrix

include(joinpath(HMatrices.PROJECT_ROOT,"test","testutils.jl"))

Random.seed!(1)

m = 5000
T = Float64
X    = points_on_sphere(m)
Y    = X

K = laplace_matrix(X,X)

splitter  = CardinalitySplitter(nmax=50)
Xclt      = ClusterTree(X,splitter)
Yclt      = ClusterTree(Y,splitter)
adm = StrongAdmissibilityStd(3)
comp = PartialACA(;atol=1e-10);
H = assemble_hmat(K,Xclt,Yclt;adm,comp,threads=false,distributed=false)
H_full      = Matrix(H)

##
hlu   = lu(H;atol=1e-5)
y     = rand(m)
M     = Matrix(K)
exact = M\y
approx = hlu\y
@test norm(exact - approx,Inf) < 1e-6


using Optim
using Calculus

struct CaseModelFitSEI3R <: CaseModelFitResult
    ρ::Float64
    E0::Float64
    info::Matrix{Float64}
    inputs::NamedTuple
    fit::Optim.MultivariateOptimizationResults
end

logit(x::Float64) = log(x/(1-x))
invlogit(x::Float64) = exp(x)/(1+exp(x))

function poissonLogLik( x::Vector{Int64}, λ::Vector{Float64})
    return sum(poissonLogLik.(x,λ))
end

function poissonLogLik(x::Int64,λ::Float64)
    return logpdf(Poisson(λ),x)
end

"""
function fitCaseModel(cases::Vector{Int64},N::Int64,
    ρ::Float64,ϕ0::Float64,E::Float64,d0::SEI3R)
"""
function fitCaseModel(cases::Vector{Int64},N::Int64,
    ρ::Float64,ϕ0::Float64,E::Float64,d0::SEI3R)

    d = deepcopy(d0)

    ntime = length(cases)
    # dvec = fill(d0,ntime)

    function f(param::Vector{Float64})
        d.β[1] = invlogit(param[2] + logit(d0.β[1]) )
        s = estimatedStates(ntime,N,cases[1],exp(param[3]),
                            invlogit(param[1]),d)
        caseIntensity = s.I1*invlogit(param[1])
        # println(caseIntensity)
        return -poissonLogLik(diff(cases),caseIntensity[2:end])
        # return caseIntensity
    end

    fit = optimize(f,[logit(ρ),logit(ϕ0),log(E)],NelderMead())
    d.β[1] = invlogit(fit.minimizer[2] + logit(d0.β[1]) )
    info = Calculus.hessian(f,fit.minimizer)
    return (cases=cases,N=N,ρ=invlogit(fit.minimizer[1]),
            E0=exp(fit.minimizer[3]),d=d,info=info,fit=fit)
end

###########
"""
function fitCaseModel(cases::Vector{Int64},x::Matrix{Float64},
    N::Int64,ρ::Float64,ϕ0::Float64,E::Float64,d0::SEI3R)

Fit a model to confirmed cases where the β1 transmission parameter is allowed to depend on covariates x.  The dependence is through a logit link function.
"""
function fitCaseModel(cases::Vector{Int64},x::Matrix{Float64},
    N::Int64,ρ::Float64,E::Float64,d0::SEI3R)

    d = deepcopy(d0)
    nc = size(x,2)

    ntime = length(cases)
    # dvec = fill(d0,ntime)

    function f(param::Vector{Float64})
        ϕ = hcat(param[3:end])
        betachange = (x*ϕ)[:,1]
        s = estimatedStates(N,cases[1],exp(param[2]),
                            invlogit(param[1]),betachange,d)
        caseIntensity = s.I1*invlogit(param[1])
        # println(caseIntensity)
        return -poissonLogLik(diff(cases),caseIntensity[2:end])
        # return caseIntensity
    end

    fit = optimize(f,[logit(ρ);log(E);zeros(nc)],NelderMead())
    # d.β[1] = invlogit(fit.minimizer[2] + logit(d0.β[1]))
    info = Calculus.hessian(f,fit.minimizer)
    return (cases=cases,N=N,ρ=invlogit(fit.minimizer[1]),
            E0=exp(fit.minimizer[3]),d=d0,info=info,fit=fit)
end

"""
predictCases(fit,ntime)

Predict number of cases using a fit object.
"""
function predictCases(fit,ntime::Int64)
    s = estimatedStates(ntime,fit.N,fit.cases[1],fit.E0,fit.ρ,fit.d)
    return cumsum(s.I1*fit.ρ)
end

function predictCases(fit,x::Matrix{Float64})
    ϕ = hcat(fit.fit.minimizer[3:end])
    betachange = (x*ϕ)[:,1]
    s = estimatedStates(fit.N,fit.cases[1],fit.E0,fit.ρ,
        betachange,fit.d)
    return cumsum(s.I1*fit.ρ)
end


@doc raw"""
    Hyperbolic{N} <: AbstractEmbeddedManifold{DefaultEmbeddingType}

The hyperbolic space $ℍ^n$ represented by $n+1$-Tuples, i.e. embedded in the
[`Lorentz`](@ref)ian manifold equipped with the [`MinkowskiMetric`](@ref)
$⟨\cdot,\cdot⟩_{\mathrm{M}}$. The space is defined as

```math
ℍ^n = \Bigl\{p ∈ ℝ^{n+1} : ⟨p,p⟩_{\mathrm{M}}= -p_{n+1}^2
  + \displaystyle\sum_{k=1}^n p_k^2 = -1, p_{n+1} > 0\Bigr\},.
```

The tangent space $T_p ℍ^n$ is given by

````math
T_p ℍ^n := \bigl\{
X ∈ ℝ^{n+1} : ⟨p,X⟩_{\mathrm{M}} = 0
\bigr\}.
````
Note that while the [`MinkowskiMetric`](@ref) renders the [`Lorentz`](@ref) manifold (only)
pseudo-Riemannian, on the tangent bundle of the Hyperbolic space it induces a Riemannian
metric. The corresponding sectional curvature is $-1$.

# Constructor

    Hyperbolic(n)

Generate the $ℍ^{n} ⊂ ℝ^{n+1}$
"""
struct Hyperbolic{N} <: AbstractEmbeddedManifold{DefaultIsometricEmbeddingType} end

Hyperbolic(n::Int) = Hyperbolic{n}()

"""
    check_manifold_point(M::Hyperbolic, p; kwargs...)

Check whether `p` is a valid point on the [`Hyperbolic`](@ref) `M`, i.e. is a `p` vector with
inner product in the embedding of -1, see [`MinkowskiMetric`](@ref).
The tolerance for the last test can be set using the `kwargs...`.
"""
function check_manifold_point(M::Hyperbolic, p; kwargs...)
    mpv = invoke(check_manifold_point, Tuple{supertype(typeof(M)), typeof(p)}, M, p; kwargs...)
    mpv === nothing || return mpv
    if !isapprox(minkowski_metric(p, p), -1.0; kwargs...)
        return DomainError(
            minkowski_metric(p, p),
            "The point $(p) does not lie on $(M) since its Minkowski inner product is not -1.",
        )
    end
    return nothing
end

"""
    check_tangent_vector(M::Hyperbolic, p, X; check_base_point = true, kwargs... )

Check whether `X` is a tangent vector to `p` on the [`Hyperbolic`](@ref) `M`, i.e.
after [`check_manifold_point`](@ref)`(M,p)`, `X` has to be of the same dimension as `p`
and orthogonal to `p` with respect to the inner product from the embedding, see
[`MinkowskiMetric`](@ref). The optional parameter `check_base_point` indicates whether to
call [`check_manifold_point`](@ref)  for `p`. The tolerance for the last test can be set
using the `kwargs...`.
"""
function check_tangent_vector(M::Hyperbolic, p, X; check_base_point = true, kwargs...)
    if check_base_point
        mpe = check_manifold_point(M, p; kwargs...)
        mpe === nothing || return mpe
    end
    mpv = invoke(
        check_tangent_vector,
        Tuple{supertype(typeof(M)), typeof(p), typeof(X)},
        M,
        p,
        X;
        check_base_point = false, # already checked above
        kwargs...
    )
    mpv === nothing || return mpv
    if !isapprox(minkowski_metric(p, X), 0.0; kwargs...)
        return DomainError(
            abs(minkowski_metric(p, X)),
            "The vector $(X) is not a tangent vector to $(p) on $(M), since it is not orthogonal (with respect to the Minkowski inner product) in the embedding.",
        )
    end
    return nothing
end

decorated_manifold(M::Hyperbolic{N}) where {N} = Lorentz(N+1, MinkowskiMetric())

default_metric_dispatch(::Hyperbolic, ::MinkowskiMetric) = Val(true)

@doc raw"""
    distance(M::Hyperbolic, p, q)

Compute the distance on the [`Hyperbolic`](@ref) `M`, which reads

````math
d_{ℍ^n}(p,q) = \operatorname{acosh}( - ⟨p, q⟩_{\mathrm{M}}),
````

where $⟨\cdot,\cdot⟩_{\mathrm{M}}$ denotes the [`MinkowskiMetric`](@ref) on the embedding,
the [`Lorentz`](@ref)ian manifold.
"""
distance(M::Hyperbolic, p, q) = acosh(max(-minkowski_metric(p, q), 1.0))

embed!(::Hyperbolic, q, p) = (q .= p)

embed!(::Hyperbolic, Y, p, X) = (Y .= X)

@doc raw"""
    exp(M::Hyperbolic, p, X)

Compute the exponential map on the [`Hyperbolic`](@ref) space $ℍ^n$ emanating
from `p` towards `X`. The formula reads

````math
\exp_p X = \cosh(\sqrt{⟨X,X⟩_{\mathrm{M}}})p
+ \sinh(\sqrt{⟨X,X⟩_{\mathrm{M}}})\frac{X}{\sqrt{⟨X,X⟩_{\mathrm{M}}}},
````

where $⟨\cdot,\cdot⟩_{\mathrm{M}}$ denotes the [`MinkowskiMetric`](@ref) on the embedding,
the [`Lorentz`](@ref)ian manifold.
"""
exp(::Hyperbolic, ::Any...)

function exp!(M::Hyperbolic, q, p, X)
    vn = sqrt(max(inner(M, p, X, X), 0.0))
    vn < eps(eltype(p)) && return copyto!(q, p)
    return copyto!(q, cosh(vn) * p + sinh(vn) / vn * X)
end

@doc raw"""
    injectivity_radius(M::Hyperbolic)
    injectivity_radius(M::Hyperbolic, p)

Return the injectivity radius on the [`Hyperbolic`](@ref), which is $∞$.
"""
injectivity_radius(H::Hyperbolic) = Inf
injectivity_radius(H::Hyperbolic, ::ExponentialRetraction) = Inf
injectivity_radius(H::Hyperbolic, ::Any) = Inf
injectivity_radius(H::Hyperbolic, ::Any, ::ExponentialRetraction) = Inf
eval(quote
    @invoke_maker 1 Manifold injectivity_radius(M::Hyperbolic, rm::AbstractRetractionMethod)
end)

@doc raw"""
    log(M::Hyperbolic, p, q)

Compute the logarithmic map on the [`Hyperbolic`](@ref) space $ℍ^n$, the tangent
vector representing the [`geodesic`](@ref) starting from `p`
reaches `q` after time 1. The formula reads for $p ≠ q$

```math
\log_p q = d_{ℍ^n}(p,q)
\frac{q-⟨p,q⟩_{\mathrm{M}} p}{\lVert q-⟨p,q⟩_{\mathrm{M}} p \rVert_2},
```

where $⟨\cdot,\cdot⟩_{\mathrm{M}}$ denotes the [`MinkowskiMetric`](@ref) on the embedding,
the [`Lorentz`](@ref)ian manifold. For $p=q$ the logarihmic map is equal to the zero vector.
"""
log(::Hyperbolic, ::Any...)

function log!(M::Hyperbolic, X, p, q)
    scp = minkowski_metric(p, q)
    w = q + scp * p
    wn = sqrt(max(scp .^ 2 - 1, 0.0))
    wn < eps(eltype(p)) && return zero_tangent_vector!(M, X, p)
    X .= acosh(max(1.0, -scp)) / wn .* w
    return X
end

@doc raw"""
    manifold_dimension(H::Hyperbolic)

Return the dimension of the hyperbolic space manifold $ℍ^n$, i.e. $\dim(ℍ^n) = n$.
"""
manifold_dimension(::Hyperbolic{N}) where {N} = N

"""
    mean(
        M::Hyperbolic,
        x::AbstractVector,
        [w::AbstractWeights,]
        method = CyclicProximalPointEstimationMethod();
        kwargs...,
    )

Compute the Riemannian [`mean`](@ref mean(M::Manifold, args...)) of `x` on the
[`Hyperbolic`](@ref) space using [`CyclicProximalPointEstimation`](@ref).
"""
mean(::Hyperbolic, ::Any...)

function mean!(M::Hyperbolic, p, x::AbstractVector, w::AbstractVector; kwargs...)
    return mean!(M, p, x, w, CyclicProximalPointEstimation(); kwargs...)
end

@doc raw"""
    project(M::Hyperbolic, p, X)

Perform an orthogonal projection with respect to the Minkowski inner product of `X` onto
the tangent space at `p` of the [`Hyperbolic`](@ref) space `M`.

The formula reads
````math
Y = X + ⟨p,X⟩_{\mathrm{M}} p,
````
where $⟨\cdot, \cdot⟩_{\mathrm{M}}$ denotes the [`MinkowskiMetric`](@ref) on the embedding,
the [`Lorentz`](@ref)ian manifold.
"""
project(::Hyperbolic, ::Any, ::Any)

project!(M::Hyperbolic, Y, p, X) = (Y .= X .+ minkowski_metric(p, X) .* p)

show(io::IO, ::Hyperbolic{N}) where {N} = print(io, "Hyperbolic($(N))")

@doc raw"""
    vector_transport_to(M::Hyperbolic, p, X, q, ::ParallelTransport)

Compute the paralllel transport of the `X` from the tangent space at `p` on the
[`Hyperbolic`](@ref) space $ℍ^n$ to the tangent at `q` along the [`geodesic`](@ref)
connecting `p` and `q`. The formula reads

````math
\mathcal P_{q←p}X = X - \frac{⟨\log_p q,X⟩_p}{d^2_{ℍ^n}(p,q)}
\bigl(\log_p q + \log_qp \bigr),
````
where $⟨\cdot,\cdot⟩_p$ denotes the inner product in the tangent space at `p`.
"""
vector_transport_to(::Hyperbolic, ::Any, ::Any, ::Any, ::ParallelTransport)

function vector_transport_to!(M::Hyperbolic, Y, p, X, q, ::ParallelTransport)
    w = log(M, p, q)
    wn = norm(M, p, w)
    wn < eps(eltype(p + q)) && return copyto!(Y, X)
    return copyto!(Y, X - (inner(M, p, w, X) * (w + log(M, q, p)) / wn^2))
end


#L2HingeLoss

using LaTeXStrings, LossFunctions, Measures, Latexify

Plots.plot(L2HingeLoss(), c=:olive, leg=:topright, linewidth=2, annotation=(1, 6, L"L\left( a \right) = max\{0,1, - a \}^2", 18))


using Test
using EFIT

g = readg(EFIT.test_gfile)

lt, ut = triangularity(g)
@test lt == 0.059614676027860296
@test ut == 0.05822145848512557

@test ellipticity(g) == 1.475784591289634
@test major_radius(g) == 1.648852555
@test minor_radius(g) == 0.633397135
@test aspect_ratio(g) == 2.6031891587258285


x = range(0, stop=π, length=100)
y1 = 0
y2 = @. cos(5x)*exp(-x)
plot(x, y2, lw=0.05)
fill_between!(x, y1, y2, fill="aliceblue")
hline(y1)
xlim(0, π)


# fbp2_window.jl

export fbp2_window

using FFTW: fftshift

"""
    window = fbp2_window(n, window)

Create an apodizing window of length `n` and fftshift it
"""
function fbp2_window(n::Int,window::Symbol)

    # boxcar?
    if window === :none || window === :ramp
        window = ones(n)
#=
    elseif window === :boxcar
        #cut = sscanf(window, 'boxcar,%g');
        window = my_boxcar(n, cut)

    elseif window === :hamming
        #cut = sscanf(window, 'hamming,%g');
        window = my_hamming(n, cut)

    elseif window === :hanning
        #cut = sscanf(window, 'hanning,%g');
        window = my_hann(n, cut)
=#
    elseif window === :hann
        window = my_hann(n, 1.0)
    elseif window === :hann50
        window = my_hann(n, 0.5)
    elseif window === :hann75
        window = my_hann(n, 0.75)
    elseif window === :hann80
        window = my_hann(n, 0.80)

    else
        throw("unknown window $window")
    end

    return fftshift(window)
end

window(n::Int, window::AbstractVector{<:Real}) = fftshift(window)

function my_boxcar(n::Int, cutoff::Real)
    w = round(cutoff * n)
    ii = (0:n-1) .- n/2
    return (abs.(ii) .< w/2)
end

function my_hann(n::Int, cutoff::Real)
    w = round(cutoff * n)
    ii = (0:n-1) .- n/2
    return 0.5 * (1 .+ cos.(2*pi*ii/w)) .* (abs.(ii) .< w/2)
end

function my_hamming(n::Int, cutoff::Real)
    w = round(cutoff * n)
    ii = (0:n-1) .- n/2
    return (0.54 .+ 0.46 * cos.(2*pi*ii/w)) .* (abs.(ii) .< w/2)
end