"""
    loglik_obs!(d, y, μ, wt, ϕ)
Get the loglikelihood from the GLM.jl package for each observation
"""
function loglik_obs end

loglik_obs(::Bernoulli, y, μ, wt, ϕ) = wt*GLM.logpdf(Bernoulli(μ), y)
loglik_obs(::Binomial, y, μ, wt, ϕ) = GLM.logpdf(Binomial(Int(wt), μ), Int(y*wt))
loglik_obs(::Gamma, y, μ, wt, ϕ) = wt*GLM.logpdf(Gamma(inv(ϕ), μ*ϕ), y)
loglik_obs(::InverseGaussian, y, μ, wt, ϕ) = wt*GLM.logpdf(InverseGaussian(μ, inv(ϕ)), y)
loglik_obs(::Normal, y, μ, wt, ϕ) = wt*GLM.logpdf(Normal(μ, sqrt(abs(ϕ))), y)
loglik_obs(::Poisson, y, μ, wt, ϕ) = logpdf(Poisson(μ), y)

# this gets the loglikelihood from the glm.jl package for the component density
"""
    component_loglikelihood!(gc::GLMCopulaVCObs{T, D, Link})
Calculates the loglikelihood of observing `y` given mean `μ`, a distribution
`d` using the GLM.jl package.
"""
function component_loglikelihood(gc::Union{GLMCopulaVCObs{T, D, Link}, GLMCopulaARObs{T, D, Link}, GLMCopulaCSObs{T, D, Link}}) where {T <: BlasReal, D, Link}
  logl = zero(T)
    @inbounds for j in 1:gc.n
      logl += QuasiCopula.loglik_obs(gc.d, gc.y[j], gc.μ[j], gc.wt[j], 1.0)
  end
  logl
end

### loglikelihood functions
"""
    component_loglikelihood!(gc::Union{NBCopulaVCObs{T, D, Link}, NBCopulaARObs{T, D, Link}, NBCopulaCSObs{T, D, Link}}, r::T)
Calculates the loglikelihood of observing `y` given parameters for `μ` and `r` for Negative Binomial distribution using the GLM.jl package.
"""
function component_loglikelihood(gc::Union{NBCopulaVCObs{T, D, Link}, NBCopulaARObs{T, D, Link}, NBCopulaCSObs{T, D, Link}}, r::T) where {T <: BlasReal, D<:NegativeBinomial{T}, Link}
    logl = zero(T)
    @inbounds for j in 1:gc.n
        logl += logpdf(D(r, r / (gc.μ[j] + r)), gc.y[j])
    end
    logl
end

"""
    component_loglikelihood!(gc::Poisson_Bernoulli_VCObs{T, VD, VL})
Calculates the loglikelihood of observing `y` given mean `μ`, a distribution
`d` with mixed types of poisson and bernoulli distributions using the GLM.jl package.
"""
function component_loglikelihood(gc::Poisson_Bernoulli_VCObs{T, VD, VL}) where {T <: BlasReal, VD, VL}
  logl = zero(T)
    @inbounds for j in 1:gc.n
      logl += QuasiCopula.loglik_obs(gc.vecd[j], gc.y[j], gc.μ[j], gc.wt[j], 1.0)
  end
  logl
end


function mrp(q::Quaternion)
    q̄ = vector(q)
    return q̄[2:4] ./ (q̄[1] + 1.0)
end

function mrp(q::AbstractVector)
    return q[2:4] ./ (q[1] + 1.0)
end

function dmrpdq(q::AbstractVector)
    s = q[1]
    v = q[2:4]

    d1 = 1 / (s + 1)^2
    di = 1 / (s + 1)

    [-v[1] * d1  di 0.0 0.0;
     -v[2] * d1 0.0 di 0.0;
     -v[3] * d1 0.0 0.0 di]
end

function axis(q)
    m = mrp(q)
    mag = norm(m)
    if mag > 0
        # θ = 4.0 * atan(mag)
        n = m ./ mag
    else
        # θ = 0.0
        n = [1.0; 0.0; 0.0]
    end
    return n
end

function daxisdq(q::AbstractVector)
    m = mrp(q)
    dmrpdq(q) ./ norm(m) - m ./ norm(m)^2 * transpose(m ./ norm(m)) * dmrpdq(q)
end

function angle(q)
    m = mrp(q)
    mag = norm(m)
    if mag > 0
        θ = 4.0 * atan(mag)
    else
        θ = 0.0
    end
    return θ
end

function dangledq(q::AbstractVector)
    m = mrp(q)
    4.0 * 1.0 / (1.0 + norm(m)^2.0) * transpose(m ./ norm(m)) * dmrpdq(q)
end

function axis_angle(q)
    return axis(q), angle(q)
end

function rotation_vector(q)
    n, θ = axis_angle(q)
    return θ .* n
end

function drotation_vectordq(q::AbstractVector)
    θ = angle(q)
    if θ != 0.0
        a = axis(q)
        return a * dangledq(q) + θ * daxisdq(q)
    else
        [
            0.0  2.0  0.0  0.0;
            0.0  0.0  2.0  0.0;
            0.0  0.0  0.0  2.0;
        ]
    end
end

drotation_vectordq(q::Quaternion) = drotation_vectordq(vector(q))


# classification demo

# avoid unrelated libz error
# using ImageMagick
# using Makie

using KernelDensityEstimate
using Gadfly
using Distributions
using StatsBase, Random
using IteratedProbabilisticClassification
# don't mind syntax warnings, these are being removed from package dependencies over time

# modified EM classification algorithm has these major tuning parameter
global localparams = TuningParameters(
  Mfair=300,   # Cluster distribution approximation accuracy, more is better: 50 is bare minium, and computation slow above ~1000
  EMiters=40   # expectation maximization iterations to refine Cluster belief estimate classification assignments: 5 is minimum, and diminishing returns beyond ~70
)

# Example: simulated data with ground truth for reference
N1, N2 = 5000, 1000
# N1, N2 = 4000, 3000
data, groundtruth = simdata02_true(N1=N1,N2=N2)

# actual distribution of data
Gadfly.set_default_plot_size(15cm, 10cm)
# what does the data look like
Gadfly.plot(x=data.samples[1,:],y=data.samples[2,:], Geom.histogram2d)

# Ground truth distribution of underlying data clusters
# Gadfly.set_default_plot_size(15cm, 10cm)
# a generic 1D plotting tool for IteratedProbablisticClassification
plotUtil2D([2;1], groundtruth=groundtruth)

# belief from expert prediction, also used for initialization
c1_expt_pts = rand(MvNormal([5.0;5.0],[3.0 -1.0; -1.0 3.0]),100)
c1_expert = kde!(reshape([5.0,5.0],2,1), [3.0,3.0]) # Pretty much a normal distribution
c1_expert = kde!(c1_expt_pts)
c2_expert = kde!(reshape([-5.0,-6.0],2,1), [4.0,4.0]) # Pretty much a normal distribution

# cs is the main structure which classification algorithm will operate on, and modify during execution
cs = ClassificationSystem(
  2,                              # number of categories or classes available for assignment
  Dict(1=>"class1", 2=>"class2"), # names of classes
  Dict(1=>"blue", 2=>"red"),      # default plotting colors
  Dict(1=>c1_expert, 2=>c2_expert), # user forcing behaviour (expert guidance); required
  Dict(1=>deepcopy(c1_expert), 2=>deepcopy(c2_expert)), # initialize temporal prediction (forward-backward smoothing)
  Dict(1=>deepcopy(c1_expert), 2=>deepcopy(c2_expert)), # initialize current belief (0 iterations)
  rand(Categorical([0.5;0.5]),length(data.samples)) # initialize samples with random assignment, 50/50% in this 2 class case
);
println()

# expert may modify expertBelief using output from previous run of classification algorithm
# pts = getPoints(cs.currentBelief[1])
# cs.expertBelief[1] = kde!(dispersePoints(pts, MvNormal([-1.0],[1.0]) ))
# pts = getPoints(cs.currentBelief[2])
# cs.expertBelief[2] = kde!(dispersePoints(pts, MvNormal([2.0],[1.0]) ))
# println()

Gadfly.set_default_plot_size(20cm, 7cm)
# plotUtil1D(cs=cs) # if we didn't have gt
plotUtil2D(1,cs=cs)

# simulation data allows us to debug with absolute knowledge
dbg = defaultDebugResults()

# do the classification
stats = EMClassificationRun!(localparams, cs, data, debug=dbg, groundtruth=groundtruth);
println()

Gadfly.set_default_plot_size(20cm, 10cm)
plotUtil1D(cs=cs, groundtruth=groundtruth,
  drawcurrent=true,
  expertcolor=Dict(1=>"gray",2=>"gray")
)

sum(cs.assignment .== 1), sum(cs.assignment .== 2)

Gadfly.set_default_plot_size(20cm, 10cm)
plot(
layer(x=data.samples[1,cs.assignment .== 2], Geom.histogram, Theme(default_color=colorant"red")),
layer(x=data.samples[1,cs.assignment .== 1], Geom.histogram)
)

# proxy to convergence of classification algorithm, always available from stats structure
Gadfly.set_default_plot_size(20cm, 15cm)
plotEMStatus(localparams,stats)

# only available when ground truth data is available
Gadfly.set_default_plot_size(20cm, 15cm)
plotClassificationStats(localparams, dbg)

# pts = getPoints(cs.expertBelief[1])
# pts2 = dispersePoints(pts, MvNormal([0.0],[3.0]) )
# p2 = kde!(pts2)
# plotKDE([cs.expertBelief[1];p2])

#


export AbstractKernel, RBFKernel, kmat, rbf_kernel, kernel_expect

abstract type AbstractKernel end

"""
    RBFKernel(σ, matrix)

RBF Kernel with dense array as kernel matrix.
"""
struct RBFKernel <: AbstractKernel
    sigma::Float64
    matrix::Matrix{Float64}
end

"""
    kmat(k::AbstractKernel) -> Matrix

Returns Kernel Matrix.
"""
function kmat end
kmat(mbf::RBFKernel) = mbf.matrix

"""
    rbf_kernel(basis, σ::Real) -> RBFKernel

Returns RBF Kernel Matrix.
"""
function rbf_kernel(basis, σ::Real)
    dx2 = (basis .- basis').^2
    RBFKernel(σ, exp.(-1/2σ * dx2))
end

"""
    kernel_expect(kernel::AbstractKernel, px::Vector{Float64}, py::Vector{Float64}) -> Float64

Returns the expectation value of kernel on specific distributions.
"""
kernel_expect(kernel::AbstractKernel, px::Vector, py::Vector=px) = px' * kmat(kernel) * py


using SparseCCSA

# Simple problem:
# maximize x1^2 * x_2
# subject to x_1 <= 3
# and x_1 <= -2
# and x_2 <= 4

function f_and_grad(x)
    fx = [x[1]^2 ^ x[2], x[1] - 3, x[1] + 4, x[2] - 4]
    gradx = [2 * x[1] 1; 1 0; 1 0; 0 1]
    fx, gradx
end

st = CCSAState(
          2,
          3,
          [0,0],
          [100,100],
          f_and_grad,
          # the stuff below shouldn't matter
          # later we will ensure that the user does not have to create these, but don't worry about it for now.
          ones(m+1)
          ones(n),
          zeros(n),
          zeros(m+1),
          zeros(m+1,n),

          zeros(n),
          zeros(n),
          zeros(n),
          zeros(n),
          zeros(n),
          zeros(m+1)
         )

optimize(st) # get answer


########
# Math #
########

@inline invlogit(x::T) where T<:Real = one(T) / (one(T) + exp(-x))
@inline logit(x::T) where T<:Real = log(x / (one(T) - x))

# More stable, faster version of rand(Categorical)
function randcat(p::Vector{Float64})
  # if(any(p .< 0)) error("Negative probabilities not allowed"); end
  r, s = rand(), one(Int)
  for j = 1:length(p)
    r -= p[j]
    if(r <= 0.0) s = j; break; end
  end

  s
end

struct NotImplementedException <: Exception end

# Numerically stable sum of values represented in log domain.
logsum(xs::Vector{T}) where T<:Real = begin
  largest = maximum(xs)
  ys = map(x -> exp.(x - largest), xs)

  log(sum(ys)) + largest
end

# KL-divergence
kl(p::Normal, q::Normal) = (log(q.σ / p.σ) + (p.σ^2 + (p.μ - q.μ)^2) / (2 * q.σ^2) - 0.5)

align_internal!(x,n) = begin
  m = length(x)
  resize!(x, n)
  x[m+1:end] .= zero(eltype(x))
end

align(x,y) = begin
  if length(x) < length(y)
    align_internal!(x, length(y))
  elseif length(x) > length(y)
    align_internal!(y, length(x))
  end

  (x,y)
end

#######
# I/O #
#######

macro sym_str(var)
  var_str = string(var)
  :(Symbol($var_str))
end

##########
# Helper #
##########

auto_tune_chunk_size!(mf::Function, rep_num=10) = begin
  dim = length(mf().vals)
  if dim > 8
    n = 1
    sz_cand = Int[]
    while dim / n > 8
      push!(sz_cand, ceil(Int, dim / n))
      n += 1
    end
    filter!(sz -> 8 < sz <= 50, sz_cand)
    sz_num = length(sz_cand)
    prof_log = Vector{Float64}(sz_num)
    for i = 1:sz_num
      println("[Turing] profiling chunk size = $(sz_cand[i])")
      setchunksize(sz_cand[i])
      prof_log[i] = @elapsed for _ = 1:rep_num mf() end
    end
    minval, minidx = findmin(prof_log)
    println("[Turing] final chunk size chosen = $(sz_cand[minidx])")
    setchunksize(sz_cand[minidx])
  else
    setchunksize(8)
  end
end


@defcomp climatesf6cycle begin
    # Global SF6 emissions in kt of SF6
    globsf6 = Parameter(index=[time])

    #Atmospheric SF6 concentrations
    acsf6 = Variable(index=[time])

    #SF6 pre industrial
    sf6pre = Parameter(default = 0.04)

    #SF6 decay
    sf6decay = Variable()

    lifesf6 = Parameter(default = 3200.0)

    function run_timestep(p, v, d, t)

        if is_first(t)
            v.sf6decay = 1.0 / p.lifesf6

            v.acsf6[t] = p.sf6pre
        else
            # Calculate SF6 concentrations
            v.acsf6[t] = p.sf6pre + (v.acsf6[t - 1] - p.sf6pre) * (1 - v.sf6decay) + p.globsf6[t] / 25.1

            if v.acsf6[t] < 0
                error("sf6 atmospheric concentration out of range")
            end
        end
    end
end


# ### axis range

#md # ![FILE_NAME.png](images/FILE_NAME.png)

plot(sin, 0, 2π, xlims=(-10, 10), ylims=(-2,2))


using Test
@time @testset "Linear deformation 1" begin include("test_linear_defor_1.jl") end
@time @testset "Linear deformation 2" begin include("test_linear_defor_2.jl") end
@time @testset "Linear deformation 3" begin include("test_linear_defor_3.jl") end
@time @testset "Linear deformation 4" begin include("test_linear_defor_4.jl") end
@time @testset "Linear deformation 5" begin include("test_linear_defor_5.jl") end
@time @testset "Linear deformation 6" begin include("test_linear_defor_6.jl") end
@time @testset "Linear deformation 7" begin include("test_linear_defor_7.jl") end
@time @testset "Linear deformation 8" begin include("test_linear_defor_8.jl") end
@time @testset "Linear deformation 9" begin include("test_linear_defor_9.jl") end
@time @testset "Linear deformation 10" begin include("test_linear_defor_10.jl") end
true


### A Pluto.jl notebook ###
# v0.16.1

using Markdown
using InteractiveUtils

# ╔═╡ 7bba9ce0-1efe-11ec-2557-b9c38d2fbc38
md"""
# Chapter 17
#### Nicetak
 $(import Dates; Dates.format(Dates.today(), Dates.DateFormat("U d, Y")))
"""

# ╔═╡ d7594aeb-a318-4cf1-9ee0-e22512aa4681
md"""
## 17.1
ハミルトンサイクル問題がNPに属することを示してください.
"""

# ╔═╡ 645f0723-2329-4f5b-ae46-f1709b86ac38
md"""
**解答**

ハミルトンサイクルの解の候補 $p$ が存在するとして, これがハミルトンサイクルであることを確かめるには

- すべての頂点を通ること
- 合法的な経路であること

を確かめる必要がある. 解候補が$|V|$より長いときは, 自明にハミルトンサイクルでないことが確かめられるので,　たかだか $|V|$ の長さの解候補を見れば良い. このとき, 解候補の順番にたどっていけば, すべての頂点を通ることは自明に確かめられる. 合法的な経路であることは, 解候補の示すある頂点に対して, 次の頂点への辺があることを確かめればよいので, 一つの頂点に対して最大で $|V|$ の長さを見ればよいので, $O(|V|^2)$.

"""

# ╔═╡ 7063faec-9747-443a-8339-dd792109ccb5
md"""
## 17.2
グラフ $G = (V, E)$ と正の整数 $k$ があたえられたときに, $G$ がサイズ $k$ 以上の完全グラフを部分グラフとして含むかどうかを判定する問題を**クリーク問題**とよびます. クリーク問題がNPに属することを示してください.
"""

# ╔═╡ 6bae3ae3-6488-43ae-9548-d9e6a74647ff
md"""
**解答**

解候補は部分グラフである必要があるため, 入力は多項式長である必要があり, これは多項式時間で判定できる.
今, 完全グラフの解候補 $G' = (V', E')$ が与えられているとする. これが部分グラフであることを確かめるためには, 各 $e \in E'$ が $E$ に含まれていることを確かめればよいので, $O(|E|^2)$.
また, これが完全グラフであることを確かめるためには, すべての頂点がすべての他頂点に対して辺を持つことを確かめればよい. 一つの頂点に対して最大で $|V| - 1$ の辺が $E'$ に含まれているか確かめればよいので, $O(|V|^2|E|)$.
"""

# ╔═╡ d7cf1c75-c8de-4f91-9eb4-2c43c0a6df4b
md"""
## 17.3
安定集合問題がNP完全であることを利用して, クリーク問題もNP完全であることを示してください.
"""

# ╔═╡ 67708d53-4285-4025-9ac3-aea1902cafe6
md"""
**解答**

グラフ $G$ の頂点の部分集合 $S$ が安定集合であることは, 補グラフ $\bar{G}$ において, $S$ が完全グラフであることと同値である. したがって, サイズ $k$ 以上の安定集合が存在することと, 補グラフ $\bar{G}$ においてサイズ $k$ 以上の完全グラフが存在することは同値である. 補グラフの構成は自明に多項式時間 $O(|V|^2)$ で可能なので, クリーク問題は安定集合問題に多項式還元される.
"""

# ╔═╡ Cell order:
# ╟─7bba9ce0-1efe-11ec-2557-b9c38d2fbc38
# ╟─d7594aeb-a318-4cf1-9ee0-e22512aa4681
# ╟─645f0723-2329-4f5b-ae46-f1709b86ac38
# ╟─7063faec-9747-443a-8339-dd792109ccb5
# ╟─6bae3ae3-6488-43ae-9548-d9e6a74647ff
# ╟─d7cf1c75-c8de-4f91-9eb4-2c43c0a6df4b
# ╟─67708d53-4285-4025-9ac3-aea1902cafe6


#=
[Intermediate] rearranged magic squares

Description

An NxN magic square is an NxN grid of the numbers 1 through N^2 such that each row,
column, and major diagonal adds up to M = N(N^2+1)/2. See this week's Easy problem
for an example.
You will be given an NxN grid that is not a magic square, but whose rows can be
rearranged to form a magic square. In this case, rearranging the rows means to
put the rows (horizontal lines of numbers) in a different order, but within each
row the numbers stay the same. So for instance, the top row can be swapped with
the second row, but the numbers within each row cannot be moved to a different
position horizontally, and the numbers that are on the same row as each other
to begin with must remain on the same row as each other.
Write a function to find a magic square formed by rearranging the rows of the
given grid.
There is more than one correct solution. Format your grid however you like. You
can parse the program's input to get the grid, but you don't have to.

Example

15 14  1  4        12  6  9  7
12  6  9  7   =>    2 11  8 13
 2 11  8 13        15 14  1  4
 5  3 16 10         5  3 16 10

Inputs

Challenge inputs

Any technique is going to eventually run too slowly when the grid size gets too
large, but you should be able to handle 8x8 in a reasonable amount of time (less
than a few minutes). If you want more of a challenge, see how many of the example
inputs you can solve.
I've had pretty good success with just randomly rearranging the rows and checking
the result. Of course, you can use a "smarter" technique if you want, as long as
it works!

Optional bonus

(Warning: hard for 12x12 or larger!) Given a grid whose rows can be rearranged to
form a magic square, give the number of different ways this can be done. That is,
how many of the N! orderings of the rows will result in a magic square?
If you take on this challenge, include the result you get for as many of the
challenge input grids as you can, along with your code.

( source: https://redd.it/4dmm44 )
=#

#   exit at EOF ( in Linux: Ctrl + D and Windows: Ctrl + Z )
read_matrix( data::DataType=Any, input::IO=STDIN, spacing::Char=' ' ) = readdlm(
input, spacing, data, header=false, skipblanks=true, ignore_invalid_chars=true,
quotes=false, comments=false )

magic_N( N::Int ) = convert( Int, N*( N^2+1 )/2 )

function swap_grid_line!( grid::Matrix{ Int }, i::Integer, j::Integer )
    #   @inbounds allows the operation without bounding checking -- great time
    #   saving
    @inbounds for k in 1:size( grid, 2 )
        grid[ i, k ], grid[ j, k ] = grid[ j, k ], grid[ i, k ]
    end
end

#=
    This function basically does the interpolation of a determinated line and see
    all the combinations, if the combination is valid or not
=#
function __magic_square!( grid::Matrix{ Int }, index::Int, magic::Int )
    for i in 1:size( grid, 1 ), j in 1:size( grid, 1 )
        if i != index && j != index
            swap_grid_line!( grid, i, j )
            trace( grid ) == magic ? break : continue
        end
    end
end

#=
    This function call __magic_square! each time with each line of the grid to
    check up all the combinations -- when the solutions is found returns the
    the grid
=#
function magic_square!( grid::Matrix{ Int } )
    local magic::Int = magic_N( size( grid, 1 ) )

    for i in 1:size( grid, 1 )
        trace( grid ) == magic ? break : __magic_square!( grid, i, magic )
    end

    return grid
end

function main( )
    local matrix::Matrix{ Int } = read_matrix( Int )
    println( @time magic_square!( matrix ) )
    println( "The magic number is ", magic_N( size( matrix, 1 ) ), " and your diagonal adds up to ", trace( matrix ) )
end

main( )


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

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

# ╔═╡ ad471560-28e9-11eb-16b5-f7025684bc5f
using SymEngine

# ╔═╡ 3f007f22-4148-11eb-1c8b-9b6e06ccc541
using OrdinaryDiffEq

# ╔═╡ 42931bc0-4148-11eb-26b9-e5fee42c7580
using Plots

# ╔═╡ 470b0f50-4148-11eb-3e5a-810c88e0c029
using LaTeXStrings

# ╔═╡ 30db9be0-5fda-11eb-3899-278be0c6f536
using PlutoUI

# ╔═╡ 80213b30-28e7-11eb-388d-5501de9711e7
md"""# Tema 6 Backstepping

## Ejemplo final de la teoría, estabilizar:

$$\dot x_1=x_1^2-x_1^3+x_2$$
$$\dot x_2=x_3$$
$$\dot x_3 =u$$

# Plan de acción
* Con $u$ controlo $x_3$
* Con $x_3$ controlo $x_2$
* Con $x_2$ controlo $x_1$

Pero el plan se ejecuta al revés, por eso se llama back-stepping ☺

"""

# ╔═╡ b5d53800-28ea-11eb-1858-4360a7d9fafc
md"De nuevo usamos la función de tema 4"

# ╔═╡ bd1a0730-28ea-11eb-2355-c1d9285733c5
begin
	function derivada(funcion,variables,derivadas)
		d=0
		for i=1:length(variables)
			d=d+diff(funcion,variables[i])*derivadas[i]
		end
		return d
	end

	function derivada(f,x,dx,n) #multiple distpach
		d=f
		for i=1:n
			d=derivada(d,x,dx)
		end
		return d
	end
end

# ╔═╡ 2890d210-28ea-11eb-2f16-23af078db24a
x1,x2,x3,u=symbols("x1,x2,x3,u");

# ╔═╡ e0c749a0-28e9-11eb-2a9d-3b5f6566605a
begin
	dx1=x1^2-x1^3+x2
    dx2=x3
	dx3=u
end;

# ╔═╡ 4cce2a0e-28ea-11eb-26c6-672aeb7bfdf9
md"## Paso 1, controlar $x_1$"

# ╔═╡ 646d6000-28ea-11eb-342d-b1c902896547
V1=x1^2/2

# ╔═╡ 224b2df0-28eb-11eb-2a45-c9eb740f149c
dV1=expand(derivada(V1,[x1],[dx1]))

# ╔═╡ 2d6d8fe0-c925-11eb-3a32-2fd273a45a10
md"Vamos a *tantear* un poco para entender cómo funciona esto, para ello vamos a jugar con $x_2$ a ver si conseguimos que V disminuya como nosotros queremos"

# ╔═╡ 4684dc70-28eb-11eb-1fde-2575c48354e8
md"""De forma directa $x_2$ como control poríamos eliminar el término **malo** y dejar uno cuadrático bueno bastaría con hacer que $x_2x_1 + x_1^3=-k_1x_1^2 \to x_2=-k_1x_1 - x_1^2$"""

# ╔═╡ d589c21e-5fd5-11eb-3c70-535e951c59cf
k1=symbols("k1");

# ╔═╡ 2c671c60-c925-11eb-303d-b398113bdb9b
expand(subs(dV1,x2=>-k1*x1-x1^2))

# ╔═╡ 01b23ad0-c926-11eb-2dc3-f143c1d2b6f4
md"Vamos a guardar lo que hemos encontrado en una funciónde control $\phi_1$"

# ╔═╡ 5159e1d0-28ec-11eb-14f4-99de193e35a1
ϕ1=-k1*x1-x1^2  #el  valor deseado de x2

# ╔═╡ bbd3f930-4181-11eb-1007-7fecf56f39aa
md"""Veamos que no nos hemos equivocado"""

# ╔═╡ 220f19e0-28ec-11eb-057e-0b369e37930c
expand(subs(dV1,x2=>ϕ1)) #si pudiera hacer esto realmente ya habría terminado

# ╔═╡ 883fd650-28ec-11eb-0ba3-014589882c54
md"Esto estaría guay, pero no puedo hacer que $x_2$ valga lo que yo quiera (todavía) así que defino un *error* $z_1=x_2-\phi_1$ y continuo, lo que tengo en realidad es que $x_2=\phi_1+z_1$"

# ╔═╡ c5680400-418b-11eb-32bb-ad1a2f2077ed
z1=symbols("z1");

# ╔═╡ 95969500-418d-11eb-0132-fd7f0ed24652
md"De modo que al derivar lo que realmente sale es"

# ╔═╡ 5f805e82-418b-11eb-05a4-dba1827925ec
expand(subs(dV1,x2=>ϕ1+z1))

# ╔═╡ 5eadc9c0-c926-11eb-2e4b-cb01e83f5e4f
md"""Resulta que el término $x_1z_1$ es **malo**. Para *cargármelo* necesito añadir a $V$ algo que al derivar produzca $z_1\cdot$"algo que pueda contolar" ... eso nos lleva al paso 2"""

# ╔═╡ 42851430-28ed-11eb-2823-a5014cb3eda0
md"""## Paso 2, controlar $x_1$ y $z_1$
Hemos reducido el problema de controlar $x_1$ a hacer $z_1=0$
¿Qué hemos ganado?
Paciencia, la idea es que nos estamos acercando a $u$
"¿Cómo me *desago* del término con $z_1$?
## Ampliemos nuestro V con $\frac{z_1^2}{2}$"""

# ╔═╡ 984ebfb0-28ed-11eb-2cee-fdbf3d493de8
V2=V1+z1^2/2

# ╔═╡ a5582280-28f4-11eb-2c05-51acb08f478b
md"Y derivemos a ver que sale, para ello primero calculo $\dot z=\dot x_2 - \dot \phi_1$"

# ╔═╡ 559eb340-28f3-11eb-3720-059fec2196ee
begin
	dϕ1=symbols("dϕ1")
	dz1=dx2-dϕ1
end;

# ╔═╡ 03ef90e0-4190-11eb-1f86-9f2bf2582d96
md"**Truco**: para no arrastrar *chorizos* creamos una variale que sea la derivada como haríamos **a mano**, luego ya calcularemos la verdadera derivada al final"

# ╔═╡ 7d9e7930-28ed-11eb-20fd-970954cf18b7
let
	dV2=derivada(V2,[x1 z1],[dx1 dz1])
	dV2=subs(dV2,x2=>z1+ϕ1) #La sustiucion es para hacer aparecer z1
	dV2=expand(dV2)
end

# ╔═╡ fb177d00-5fd7-11eb-0636-2f7f9dbd5598
k2=symbols("k2");

# ╔═╡ 2e63aed0-28f7-11eb-27a6-cbf8cb83c09c
ϕ2=-x1 -k2*z1 +dϕ1

# ╔═╡ da519e60-28f6-11eb-3598-c12f14f24180
md"""queremos hacer que $x_1z_1 + x_3z_1 - z_1 \dot ϕ_1 = -k_2z_1^2$, despejamos

$x_1 + x_3 - \dot ϕ_1 = -k_2z_1$

$x_3 = -k_2z_1 -x_1 + \dot ϕ_1$

Y hacemos $ϕ_2=-k_2z_1 -x_1 + \dot ϕ_1$

**Nota:** esto se podría despejar usando un paquete CAS más potente (Symbolics por ejemplo) pero no merece la pena, hacerlo a mano ayuda a *entender* lo que estamos haciendo. El CAS lo usaremos solamente para verificar los cálculos (que per se ya es muy útil)
"""

# ╔═╡ 0dc9ed20-4192-11eb-1039-93d7bfd5d09f
md"veamos que funciona"

# ╔═╡ ebea590e-4182-11eb-2a0b-a9161adc7f97
let
	dV2=derivada(V2,[x1 z1],[dx1 dz1])
	dV2=subs(dV2,x2=>z1+ϕ1)
	dV2=subs(dV2,x3=>ϕ2) #lo nuevo (si pudiese hacerlo claro)
	dV2=expand(dV2)
end

# ╔═╡ a58593c0-28f7-11eb-1674-51101547d596
md"""de nuevo lo mismo **no puedo** hacer eso así que
ahora:
$z_2=x_3-\phi_2$"""

# ╔═╡ 3094ca2e-28f8-11eb-2cc0-a1d2cebc1da6
z2=symbols("z2")

# ╔═╡ 3dbf9b10-4192-11eb-04e5-1f29ccd9da2a
let
	dV2=derivada(V2,[x1 z1],[dx1 dz1])
	dV2=subs(dV2,x2=>z1+ϕ1)
	dV2=subs(dV2,x3=>z2+ϕ2) #esto es lo que SI puedo hacer
	dV2=expand(dV2)
end

# ╔═╡ 8a020db0-c927-11eb-09b9-45afce72c1c8
md"""Al igual que antes queremos *cargarnos* $z_2z_1$ para ello hecesitamos un término adicional que de $z_2\cdot$"cosas" y a estas alturas os imaginaréis ya lo que vamos a poner ;)"""

# ╔═╡ fb7102a0-28f8-11eb-131d-c1792c1aaa27
md"""
## Paso 3, controlar $x_1$, $z_1$ y $z_2$
Hemos llegado a dónde queríamos por fin va a salir $u$
## Ampliemos nuestro V otra vez"""

# ╔═╡ 28437790-28f9-11eb-18f1-11364b9029ee
V3=V2+z2^2/2

# ╔═╡ 9f114912-28f9-11eb-17f1-7fa461ff3371
begin
	dϕ2=symbols("dϕ2")
	dz2=dx3-dϕ2
end;

# ╔═╡ 24332a00-28fa-11eb-2a88-8b5e6f124e6d
md"Aparece $u$ ¡Yupi!"

# ╔═╡ 85a6c900-28f9-11eb-229f-f78f9cd042e4
let
	dV3=derivada(V3,[x1 z1 z2],[dx1 dz1 dz2])
	dV3=subs(dV3,x2=>z1+ϕ1)
	dV3=subs(dV3,x3=>z2+ϕ2)
	dV3=expand(dV3)
end

# ╔═╡ c9b94770-28fa-11eb-25ba-2384e1b7672a
md"""queremos hacer que $uz_2 - z_2 \dot ϕ_2 + z_2z_1 = -k_3z_2^2$

$u - \dot ϕ_2 + z_1 = -k_3z_2$

$u =  \dot ϕ_2 - z_1 -k_3z_2$

Ahora vamos a ver que funciona:"""

# ╔═╡ db3aa380-5fd8-11eb-16fc-6b7ef56e8daa
k3=symbols("k3");

# ╔═╡ f8482150-4183-11eb-05f2-599741c800de
let
	dV3=derivada(V3,[x1 z1 z2],[dx1 dz1 dz2])
	dV3=subs(dV3,x2=>z1+ϕ1)
	dV3=subs(dV3,x3=>z2+ϕ2)
	dV3=subs(dV3,u=>dϕ2-z1-k3*z2)
	dV3=expand(dV3)
end

# ╔═╡ f16d5e90-4183-11eb-16dc-8191ac698ce2
md"""

## Ya tenemos ley de control, ahora hay que implementarla

"""

# ╔═╡ d4991de0-4147-11eb-067b-4f9c143fb673
function control(x,p,t)
	#completemos esto interactivamente
    x1,x2,x3=x
	k1,k2,k3=p

	ϕ1=-k1*x1 - x1^2

	dϕ1=(-k1 - 2*x1)*(x2 + x1^2 - x1^3)

	z1=x2-ϕ1

	ϕ2= dϕ1 - x1 - k2*z1

	z2=x3-ϕ2

	dϕ2= -k2*(-dϕ1 + x3) + (-k1 - 2*x1)*x3 + (x2 + x1^2 - x1^3)*((-k1 - 2*x1)*(2*x1 - 3*x1^2) - 2*(x2 + x1^2 - x1^3)) - (x2 + x1^2 - x1^3)
	#si se hace a mano se puede sustituir todavía menos por ejemplo podemos darnos cuenta de que el término repetido x2 + x1^2 - x1^3 es dx2 y calcularlo una sola vez...

	u=dϕ2-z1-k3*z2

	#bonus para ver que funciona
	V=(x1^2 + z1^2 + z2^2)/2

	return (u,V)
end

# ╔═╡ c259a7d0-28fb-11eb-3cc6-ab3e44ea196c
function derivadas(x,p,t)
	x1,x2,x3=x
	u=control(x,p,t)[1]
	dx1=x1^2-x1^3+x2
    dx2=x3
	dx3=u
	dx=[dx1 dx2 dx3]
end

# ╔═╡ 4704bc10-c963-11eb-24d9-2dd79b9b5f33
#=prueba para ver que devuele números y por lo tanto hemos definido todo
si hemos dejado algo sin definir apareceran las variables simbólicas=#
control([1 2 3],[1 1 1],0)

# ╔═╡ f78bff70-4192-11eb-2f14-05fbee335f4f
md"""ha llegado el momento de hacer los cálculos"""

# ╔═╡ 19ce9560-c939-11eb-0678-6956501cddf9
ϕ1

# ╔═╡ 11015170-c939-11eb-3145-1ff24e53a7f8
dϕ1_b=derivada(ϕ1,[x1],[dx1])

# ╔═╡ b941ace2-c939-11eb-1414-a3befce0a650
ddϕ1=derivada(dϕ1_b,[x1 x2],[dx1 dx2])

# ╔═╡ 83fcb3a0-c938-11eb-1051-a93ef03e0639
ϕ2

# ╔═╡ 70c05310-c96e-11eb-26c8-334d2b13e99b

# ╔═╡ e2c93200-c938-11eb-2d02-752060d9d7d9
dϕ2_b=derivada(ϕ2,[x1 z1 dϕ1],[dx1 dz1 ddϕ1])

# ╔═╡ 4031d6c0-4196-11eb-275f-cda9a32cce36
md"## Vamos a ver cómo queda el *chorizo*"

# ╔═╡ 6ff23950-4195-11eb-250b-898515a5c031
chorizo_u=expand(control([x1 x2 x3],[k1,k2,k3],0)[1])

# ╔═╡ 9e20ff40-c963-11eb-16e2-cd955911eb41
md"## Bonus, depuración:
Para comprobar los cálculos los haremos de otra forma y veremos si concuerdan, para ello trabajaremos directamente con las x, primero ponemos $V$ en términos de $x_1$, $x_2$ y $x_3$"

# ╔═╡ ae7ec160-c963-11eb-07ae-2b1bb1cd78d4
begin
	V_x=V3
	V_x=subs(V_x,z1=>x2-ϕ1)
	V_x=subs(V_x,z2=>x3-ϕ2)
	V_x=subs(V_x,dϕ1=>dϕ1_b)
	V_x=subs(V_x,z1=>x2-ϕ1)
	#El último parece redundante pero hay que insistir hasta que no queden zs
	#Algunas sustituciones hacen aparecer z1 de nuevo
end

# ╔═╡ 242cce30-c968-11eb-2c4b-4f2162e95f54
md"ahora derivamos y substituimos el conrtol quedando todo en términos de las x"

# ╔═╡ ae663050-c963-11eb-24d7-d3dcd3055b4b
begin
	dV_x=derivada(V_x,[x1 x2 x3],[dx1 dx2 dx3])
	dV_x=subs(dV_x,u=>chorizo_u)
end

# ╔═╡ 4ad29ab2-c968-11eb-26c6-f90433e890b1
md"la idea es comparar con lo que esperamos que salga"

# ╔═╡ ae4fe930-c963-11eb-30eb-f16f9e561190
begin
	dV_teórica=-k1*x1^2 - k2*z1^2 - k3*z2^2 - x1^4  #esto dice la teoría
	#para comparar lo ponemos en términos de las x solamente
	dV_teórica=subs(dV_teórica,z1=>x2-ϕ1)
	dV_teórica=subs(dV_teórica,z2=>x3-ϕ2)
	dV_teórica=subs(dV_teórica,dϕ1=>dϕ1_b)
	dV_teórica=subs(dV_teórica,z1=>x2-ϕ1)  #es necesario ya que vuelve a aparecer z1
end

# ╔═╡ cc2f0f30-c968-11eb-0814-bbe5107bdd3b
md"Si sale 0 es que lo hemos hecho bien"

# ╔═╡ ee6c43b0-c963-11eb-13f9-c51ef0a7f9a2
expand(dV_teórica-dV_x)

# ╔═╡ 9e7a974e-c96b-11eb-3a2b-759bf3f51058
md"# Y ahora a simular"

# ╔═╡ 49b79790-5fda-11eb-2917-4b055386f9db
k_1= @bind k_1 Slider(0:0.01:5,default=1,show_value=true)

# ╔═╡ 8ac8b250-5fda-11eb-2acd-4f317055f0cf
k_2= @bind k_2 Slider(0:0.01:5,default=1,show_value=true)

# ╔═╡ 8fbb0600-5fda-11eb-2f43-196140b76f69
k_3= @bind k_3 Slider(0:0.01:5,default=1,show_value=true)

# ╔═╡ fc5e8b30-4147-11eb-30d3-4bef0bac9ca6
begin
	x0 = [1 0 0]
	parametros=[k_1 k_2 k_3]
	tspan = (0.0,10.0)
	prob = ODEProblem(derivadas,x0,tspan,parametros);
	sol = solve(prob,Tsit5());

	Xs=sol.u
	ts=sol.t

	Vs=[]
	Us=[]

	for i=1:length(ts)
		u,V=control(Xs[i],parametros,ts[i])
		push!(Vs,V)
		push!(Us,u)
	end

	# Salida
	fig1=plot(sol,vars=(0,1), xaxis=L"t",yaxis=L"y(t)",label=L"y(t)")

	#Estados
	fig2=plot(sol, xaxis="t",yaxis=L"x(t)", label=:none)

	#Señal de control
	fig3=plot(ts,Us,xaxis="t",yaxis=L"u(t)", label=:none)
	xlims!(tspan)

	#V
	fig4=plot(ts,Vs,xaxis="t",yaxis=L"V(t)", label=:none)
	xlims!(tspan)

	l = @layout [a ; b ; c; d]
	plot(fig1,fig2,fig3,fig4, layout=l)
end

# ╔═╡ Cell order:
# ╟─80213b30-28e7-11eb-388d-5501de9711e7
# ╠═ad471560-28e9-11eb-16b5-f7025684bc5f
# ╟─b5d53800-28ea-11eb-1858-4360a7d9fafc
# ╟─bd1a0730-28ea-11eb-2355-c1d9285733c5
# ╠═2890d210-28ea-11eb-2f16-23af078db24a
# ╠═e0c749a0-28e9-11eb-2a9d-3b5f6566605a
# ╟─4cce2a0e-28ea-11eb-26c6-672aeb7bfdf9
# ╠═646d6000-28ea-11eb-342d-b1c902896547
# ╠═224b2df0-28eb-11eb-2a45-c9eb740f149c
# ╟─2d6d8fe0-c925-11eb-3a32-2fd273a45a10
# ╠═2c671c60-c925-11eb-303d-b398113bdb9b
# ╟─4684dc70-28eb-11eb-1fde-2575c48354e8
# ╠═d589c21e-5fd5-11eb-3c70-535e951c59cf
# ╠═01b23ad0-c926-11eb-2dc3-f143c1d2b6f4
# ╠═5159e1d0-28ec-11eb-14f4-99de193e35a1
# ╟─bbd3f930-4181-11eb-1007-7fecf56f39aa
# ╠═220f19e0-28ec-11eb-057e-0b369e37930c
# ╟─883fd650-28ec-11eb-0ba3-014589882c54
# ╠═c5680400-418b-11eb-32bb-ad1a2f2077ed
# ╟─95969500-418d-11eb-0132-fd7f0ed24652
# ╠═5f805e82-418b-11eb-05a4-dba1827925ec
# ╟─5eadc9c0-c926-11eb-2e4b-cb01e83f5e4f
# ╟─42851430-28ed-11eb-2823-a5014cb3eda0
# ╠═984ebfb0-28ed-11eb-2cee-fdbf3d493de8
# ╟─a5582280-28f4-11eb-2c05-51acb08f478b
# ╠═559eb340-28f3-11eb-3720-059fec2196ee
# ╟─03ef90e0-4190-11eb-1f86-9f2bf2582d96
# ╠═7d9e7930-28ed-11eb-20fd-970954cf18b7
# ╠═fb177d00-5fd7-11eb-0636-2f7f9dbd5598
# ╠═2e63aed0-28f7-11eb-27a6-cbf8cb83c09c
# ╟─da519e60-28f6-11eb-3598-c12f14f24180
# ╟─0dc9ed20-4192-11eb-1039-93d7bfd5d09f
# ╠═ebea590e-4182-11eb-2a0b-a9161adc7f97
# ╟─a58593c0-28f7-11eb-1674-51101547d596
# ╠═3094ca2e-28f8-11eb-2cc0-a1d2cebc1da6
# ╠═3dbf9b10-4192-11eb-04e5-1f29ccd9da2a
# ╟─8a020db0-c927-11eb-09b9-45afce72c1c8
# ╟─fb7102a0-28f8-11eb-131d-c1792c1aaa27
# ╠═28437790-28f9-11eb-18f1-11364b9029ee
# ╠═9f114912-28f9-11eb-17f1-7fa461ff3371
# ╟─24332a00-28fa-11eb-2a88-8b5e6f124e6d
# ╠═85a6c900-28f9-11eb-229f-f78f9cd042e4
# ╟─c9b94770-28fa-11eb-25ba-2384e1b7672a
# ╠═db3aa380-5fd8-11eb-16fc-6b7ef56e8daa
# ╠═f8482150-4183-11eb-05f2-599741c800de
# ╟─f16d5e90-4183-11eb-16dc-8191ac698ce2
# ╠═c259a7d0-28fb-11eb-3cc6-ab3e44ea196c
# ╠═d4991de0-4147-11eb-067b-4f9c143fb673
# ╠═4704bc10-c963-11eb-24d9-2dd79b9b5f33
# ╟─f78bff70-4192-11eb-2f14-05fbee335f4f
# ╠═19ce9560-c939-11eb-0678-6956501cddf9
# ╠═11015170-c939-11eb-3145-1ff24e53a7f8
# ╠═b941ace2-c939-11eb-1414-a3befce0a650
# ╠═83fcb3a0-c938-11eb-1051-a93ef03e0639
# ╠═70c05310-c96e-11eb-26c8-334d2b13e99b
# ╠═e2c93200-c938-11eb-2d02-752060d9d7d9
# ╟─4031d6c0-4196-11eb-275f-cda9a32cce36
# ╠═6ff23950-4195-11eb-250b-898515a5c031
# ╟─9e20ff40-c963-11eb-16e2-cd955911eb41
# ╠═ae7ec160-c963-11eb-07ae-2b1bb1cd78d4
# ╟─242cce30-c968-11eb-2c4b-4f2162e95f54
# ╠═ae663050-c963-11eb-24d7-d3dcd3055b4b
# ╟─4ad29ab2-c968-11eb-26c6-f90433e890b1
# ╠═ae4fe930-c963-11eb-30eb-f16f9e561190
# ╟─cc2f0f30-c968-11eb-0814-bbe5107bdd3b
# ╠═ee6c43b0-c963-11eb-13f9-c51ef0a7f9a2
# ╟─9e7a974e-c96b-11eb-3a2b-759bf3f51058
# ╠═3f007f22-4148-11eb-1c8b-9b6e06ccc541
# ╠═42931bc0-4148-11eb-26b9-e5fee42c7580
# ╠═470b0f50-4148-11eb-3e5a-810c88e0c029
# ╠═30db9be0-5fda-11eb-3899-278be0c6f536
# ╟─49b79790-5fda-11eb-2917-4b055386f9db
# ╟─8ac8b250-5fda-11eb-2acd-4f317055f0cf
# ╟─8fbb0600-5fda-11eb-2f43-196140b76f69
# ╟─fc5e8b30-4147-11eb-30d3-4bef0bac9ca6


using MathematicalSystems, LazySets, TypedPolynomials
using SparseArrays, LinearAlgebra
using Test

# fix namespace conflicts with LazySets
using MathematicalSystems: LinearMap, AffineMap, ResetMap

include("continuous.jl")
include("discrete.jl")
include("identity.jl")
include("inputs.jl")
include("maps.jl")
include("outputs.jl")
include("macros.jl")
include("successor.jl")
include("utilities.jl")


export DiGraph, plot
using LightGraphs
using TikzGraphs

import LightGraphs: DiGraph
function DiGraph(vtree::Vtree)
    N = num_nodes(vtree)
    g = DiGraph(N)
    nv = num_variables(vtree)
    id = nv
    dict = Dict{PlainVtreeInnerNode, Int}()
    if id < N
        dict[vtree] = (id += 1)
    end

    convert_rec(v::PlainVtreeLeafNode) = ()
    convert_rec(v::PlainVtreeInnerNode) = begin
        foreach(children(v)) do c
            c_id = isleaf(c) ? variable(c) : dict[c] = (id += 1)
            add_edge!(g, dict[v], c_id)
            convert_rec(c)
        end
    end

    convert_rec(vtree)
    @assert id == N
    label = [["$i" for i in 1 : nv];fill(".", nv - 1)]
    g, label
end

function DiGraph(lc::LogicCircuit; on_edge=noop, on_var=noop)
    nv = num_variables(lc)
    nn = num_nodes(lc)
    g = DiGraph(nn)
    id = 0
    dict = Dict{LogicCircuit, Int}()
    label = Vector{String}(undef, nn)
    dict[lc] = (id += 1)

    add_label!(g, dict, c::LogicCircuit) = begin
        label[dict[c]] =
        if isliteralgate(c) "$(literal(c))"
        elseif istrue(c)  "T"
        elseif isfalse(c)  "F"
        elseif is⋁gate(c) "⋁"
        else "⋀"
        end
    end

    if on_var == noop
        on_var = add_label!
    end

    foreach_down(lc) do n
        on_var(g, dict, n)
        if has_children(n)
            foreach(children(n)) do c
                c_id = haskey(dict, c) ? dict[c] : dict[c] = (id += 1)
                add_edge!(g, dict[n], c_id)
                on_edge(g, dict, n, c, dict[n], c_id)
            end
        end
    end
    g, label
end

import TikzGraphs: plot
plot(vtree::Vtree) = TikzGraphs.plot(DiGraph(vtree)...)
plot(lc::LogicCircuit) = TikzGraphs.plot(DiGraph(lc)...)


#cgrad(:default)[1]

```
function cMap(x::Number,cSeq::Array)
    n = length(cSeq)
    if x>1 || x<0
        error("x should be normalized to 0-1")
    end
    ind = Int(floor(x*(n-1)))+Int(1)
    if ind ==0 # out of bound caused by 0-eps()
        ind=1
    end
    cSeq[ind]
end
```
"cMap(x::Float,cSeq::Array) Map a value from 0 to one to a color, given a color sequence"
function cMap(x::Number,cSeq)
    cMap(x,cSeq.colors)
end

#cgrad(:default).colors

"cMap_def default c map"
function cMap_def(x::Number)
    cMap(x,cgrad(:default))
end

function cNormalize(x::Union{Array,UnitRange})
    xMin = minimum(x)
    xDiff = maximum(x) - xMin
    [(x[i]-xMin)/xDiff for i in 1:length(x)]
end

"qcMap_def(x) x is an arraym, quick map from a numeric array to a color array"
function qcMap_def(x)
    cMap_def.(cNormalize(x))
end

#qcMap_def(0:.05:1)

"plotInit"
function plotInit()
    return Plots.plot()
end

"plotTrajectory!(x,y,z) for x,y,z as single vector, plotTrajectory(x,y) for 2 vectors, plotTrajectory(x) is a time plot if x is the only input and ncol(x) is 1, else treated as a data-frame"
function plotTrajectory!(p,x::Union{Array,UnitRange},y::Union{Array,UnitRange},z::Union{Array,UnitRange};xlab = "x",ylab = "y",zlab = "z",map_t = true,t = Missing, log_t = false,markeralpha = .5)
    if (length(x)!=length(y)) || (length(y)!=length(z))
        error("Length differ")
    end
    if map_t
        if t==Missing
            t = 1:length(x)
        end
        if log_t == true
            t = log.(t)
        end
        return plot!(p,x,y,z;color = qcMap_def(t),markeralpha = markeralpha)
    end
    if length(t) != length(x)
        error("Check length of t")
    end
    return plot!(p,x,y,z;markeralpha = markeralpha)
end

function plotTrajectory!(p,x::Union{Array,UnitRange},y::Union{Array,UnitRange};xlab = "x",ylab = "y",map_t = true,t = Missing, log_t = false,markeralpha = .5)
    if (length(x)!=length(y))
        error("Length differ")
    end
    if map_t

        if t==Missing

            t = 1:length(x)
        end
        if log_t == true
            t = log.(t)
        end
        if length(t) != length(x)
            error("Check length of t")
        end
        return plot!(p,x,y;color = qcMap_def(t),markeralpha = markeralpha)
    end
    return plot!(p,x,y;markeralpha = markeralpha)
end

function plotTrajectory!(p,x::Union{Array,UnitRange};xlab = "t",ylab = "x",map_t = true,t = Missing,log_t = false,markeralpha = .5)

    if t==Missing           #  need t anyway

        t = 1:length(x)
    end
    if log_t == true
        t = log.(t)
    end
    if map_t
        return plot!(p,x;color = qcMap_def(t),markeralpha = markeralpha)
    end
    return plot!(p,t,x;markeralpha = markeralpha)
end

# plotScatter

"plotScatter!(x,y,z) for x,y,z as single vector, plotScatter(x,y) for 2 vectors, plotScatter(x) is a time plot if x is the only input and ncol(x) is 1, else treated as a data-frame"
function plotScatter!(p,x::Union{Array,UnitRange},y::Union{Array,UnitRange},z::Union{Array,UnitRange};xlab = "x",ylab = "y",zlab = "z",map_t = false,t = Missing,
     log_t = false,alpha = .5)
    if (length(x)!=length(y)) || (length(y)!=length(z))
        error("Length differ")
    end
    if map_t
        if t==Missing
            t = 1:length(x)
        end
        if log_t == true
            t = log.(t)
        end
        return Plots.scatter!(p,x,y,z;color = qcMap_def(t),alpha = alpha)
    end
    if length(t) != length(x)
        error("Check length of t")
    end
    return Plots.scatter!(p,x,y,z;alpha = alpha)
end

function plotScatter!(p,x::Union{Array,UnitRange},y::Union{Array,UnitRange};xlab = "x",ylab = "y",map_t = false,t = Missing, log_t = false,alpha = .5)
    if (length(x)!=length(y))
        error("Length differ")
    end
    if map_t

        if t==Missing

            t = 1:length(x)
        end
        if log_t == true
            t = log.(t)
        end
        if length(t) != length(x)
            error("Check length of t")
        end
        return Plots.scatter!(p,x,y;color = qcMap_def(t),alpha = alpha)
    end
    return Plots.scatter!(p,x,y;alpha = alpha)
end

function plotScatter!(p,x::Union{Array,UnitRange};xlab = "t",ylab = "x",map_t = false,t = Missing,log_t = false,alpha = .5)

    if t==Missing           #  need t anyway

        t = 1:length(x)
    end
    if log_t == true
        t = log.(t)
    end
    if map_t
        return Plots.scatter!(p,x;color = qcMap_def(t),alpha = alpha)
    end
    return Plots.scatter!(p,t,x;alpha = alpha)
end

"plotTrace!(x,y,z) similar to plotScatter, plotTrajectory with predefined plts"
function plotTrace!(p,x,y,z;t = Missing,log_t = false)
    plotTrajectory!(p,x,y,z;t=t,log_t = log_t)
    plotScatter!(p,x,y,z;t=t,log_t = log_t)
end

function plotTrace!(p,x,y;t = Missing,log_t = false)
    plotTrajectory!(p,x,y;t=t,log_t = log_t)
    plotScatter!(p,x,y;t=t,log_t = log_t)
end

function plotTrace!(p,x;t = Missing,log_t = false)
    plotTrajectory!(p,x;t=t,log_t = log_t)
    plotScatter!(p,x;t=t,log_t = log_t)
end


using JLD
include("src/solver.jl")
k(x, y)=0.6 * sin(2 * pi * x) + 0.2 * sin(4 * pi * y) + 0.1 * sin(10 * pi * x + 8 * pi * y)
N_mesh=512
arr1=[]
arr2=[]
for N_mesh in [256, 512, 1024, 2048, 4096]
    println(N_mesh)
    x=linspace(0,1 - 1 / N_mesh,N_mesh)
    y=linspace(0,1 - 1 / N_mesh,N_mesh)
    sol=zeros(N_mesh,N_mesh)
    for i=1:N_mesh
        for j=1:N_mesh
            sol[i,j]=k(x[i],y[j])
        end
    end
    f=get_f(sol,1.)
    u,err1=solver(f,1.,1e-6)
    push!(arr1,err1)
    u,err2=multigrid_solver(f,1.,1e-6)
    push!(arr2,err2)
end
brr1=[]
brr2=[]

for N_mesh in [256, 512, 1024, 2048, 4096]
    println(N_mesh)
    x=linspace(0,1 - 1 / N_mesh,N_mesh)
    y=linspace(0,1 - 1 / N_mesh,N_mesh)
    sol=zeros(N_mesh,N_mesh)
    for i=1:N_mesh
        for j=1:N_mesh
            sol[i,j]=k(x[i],y[j])
        end
    end
    f=get_f(sol,1.)
    push!(brr1,@elapsed solver(f,1.,1e-6))
    push!(brr2,@elapsed multigrid_solver(f,1.,1e-6))
end

save("plotdata.jld","arr1",arr1,"arr2",arr2,"brr1",brr1,"brr2",brr2)


# # Saving and loading a mesh

# In this tutorial, we will learn
#
#    -  How to save a mesh to a file.
#    -  How to load a mesh from a file.
#    -  How to find out the number of elements and their type.
#    -  How to find out the characteristics of the shape (shape descriptor).

# We will generate the triangular mesh inside a rectangular block.
# The block will have the dimensions shown below:
a, b = 1.0, 1.0
# The triangles will be generated in a regular pattern, with the number of
# edges per side of the block given as
na, nb = 2, 3

# The mesh will be generated by the package `MeshSteward`. We choose an
# orientation of the diagonals (in other words the pattern in which the block is
# tiled) as labeled `:b`.
using MeshSteward: T3block
conn = T3block(a, b, na, nb, :b);

# The variable `conn` is an incidence relation. This incidence relation will
# become the base relation of the mesh when it gets attached to the mesh.
using MeshSteward: Mesh, attach!
m = Mesh()
attach!(m, conn);

# The embedding space in which the mesh lives is two-dimensional. We can verify
# that by
using MeshSteward: nspacedims
@show nspacedims(m)

# We can export the mesh into a set of files that are referred to as the MESH
# format. This will actually store the mesh as three separate files. The file
# `"Unit-square-mesh.mesh"` is the directory of the bundle of the mesh files,
# so to speak.
using MeshSteward: save
save(m, "Unit-square-mesh")
# Note the trio of files `"Unit-square-mesh.mesh"` (mesh  "directory"), and
# `"Unit-square-mesh-conn.dat"` (connectivity) and
# `"Unit-square-mesh-xyz.dat"` (locations of vertices).
readdir(".")

# We will now load the mesh we saved just now into another mesh that we create
# specifically for the purpose of comparing the two meshes.
using MeshSteward: load
m2 = load(Mesh(), "Unit-square-mesh");

# Now we compare the number of  elements  stored in those two meshes. First the
# vertices (those are the shapes on the right of the incidence relation).
using MeshCore: nshapes
using MeshSteward: baseincrel
@show nshapes(baseincrel(m).right) == nshapes(baseincrel(m2).right)

# Now  we compare the number of the triangular elements in those two meshes:
@show nshapes(baseincrel(m).left) == nshapes(baseincrel(m2).left)

# What is the name of the shape descriptor of the shape collection on the left
# (that is what we usually consider the "elements" in a finite element mesh) of
# the base incidence relation of the mesh?
using MeshCore: shapedesc
@show shapedesc(baseincrel(m).left).name

# We would expect it to be the name of a three-node triangle, "T3".

# Are the shape descriptors of the shape collection on the left the same for the
# two meshes?
@show shapedesc(baseincrel(m).left) == shapedesc(baseincrel(m2).left)

# What is the name of the shape descriptor of the shape collection on the right
# (that is what we usually consider the "nodes" in a finite element mesh) of
# the base incidence relation of the mesh?
using MeshCore: shapedesc
@show shapedesc(baseincrel(m).right).name

# P1 is the shape descriptor of a point shape. So vertices (nodes) are points.

# Are the shape descriptors of the shape collection on the right the same for the
# two meshes?
@show shapedesc(baseincrel(m).right) == shapedesc(baseincrel(m2).right)


using FFTW
using DifferentialEquations
using Plots

nu = 0.001 # Diffusion constant

# Define spatial domain
L = 20 # Length of domain
N = 1000 # Number of discretization points
dx = L/N
x = -L/2:dx:L/2-dx # Define x domain

# Define discrete wavenumbers
kappa = (2pi/L)*(-N/2:N/2-1)
kappa = fftshift(kappa) # Re-order fft wavenumbers

# Initial condition
u0 = sech.(x)

# Simulate PDE in spatial domain
f(u, p, t) = real.(-u.*ifft(im*kappa.*fft(u)) + nu*ifft(-(kappa.^2).*fft(u)))
prob = ODEProblem(f, u0, (0.0,2.5))
u = solve(prob)

# Plot solution in time
p1 = surface(hcat(u.u...))


export salt_pepper, salt_pepper_chn

salt_or_pepper(salt_prob, salt, pepper) = rand() < salt_prob ? salt : pepper

 # function for a raw value
function f_sp(prob, salt_prob, salt, pepper)
    return x -> rand() < prob ? salt_or_pepper(salt_prob, salt, pepper) : x
end

@inline function comb_sp(x, n)
    return n
end

function salt_pepper(X, prob=0.1; salt_prob=0.5, salt=1, pepper=0)
    return salt_pepper!(copy(X), prob, salt_prob=salt_prob, salt=salt, pepper=pepper)
end

 # for arbitrary array, RGB and Gray images
function salt_pepper!(X, prob=0.1; salt_prob=0.5, salt=1, pepper=0)

    # type conversion of salt & pepper makes it faster
    a = X[1]
    salt = convert(eltype(a), salt)
    pepper = convert(eltype(a), pepper)

    return apply_noise!(comb_sp, f_sp(prob, salt_prob, salt, pepper), X, false)
end

"""
    salt_pepper(X; salt_prob=0.5, salt=1.0, pepper=0.0[, prob=0.1])

Returns array `X` affected by salt and pepper noise.
`X` can be an array or an RGB or Gray image
`prob` is a optional argument for the probability that a pixel will be affected by the noise.
`salt_prob` is a keyword argument representing the probability for salt noise.
The probability for pepper noise is therefore 1-`salt_prob`.
`salt` is a keyword argument for specifying the value of salt noise.
`pepper` is a keyword argument for specifying the value of pepper noise.
"""
salt_pepper

function salt_pepper_chn(X, prob=0.1; salt_prob=0.5, salt=1.0, pepper=0.0)
    return salt_pepper_chn!(copy(X), prob, salt_prob=salt_prob, salt=salt, pepper=pepper)
end

function salt_pepper_chn!(X, prob=0.1; salt_prob=0.5, salt=1.0, pepper=0.0)
    return apply_noise_chn!(comb_sp, f_sp(prob, salt_prob, salt, pepper), X, false)
end

"""
    salt_pepper_chn(X; salt_prob=0.5, salt=1.0, pepper=0.0[, prob=0.1])

Returns a RGB Image `X` affected by salt and pepper noise.
When a salt or pepper occurs, it is applied to all channels of the RGB making a real salt
and pepper on the whole image.
`prob` is a optional argument for the probability that a pixel will be affected by the noise.
`salt_prob` is a keyword argument representing the probability for salt noise.
The probability for pepper noise is therefore 1-`salt_prob`.
`salt` is a keyword argument for specifying the value of salt noise.
`pepper` is a keyword argument for specifying the value of pepper noise.
"""
salt_pepper_chn


@testset "triangle" begin
    r = 2  # radius
    b = regpoly(3, r)  # regular triangle inscribed in circle of radius r (triangel side = √3r/2)

    va = [0,r]  # apex
    vl = [-√3r/2,-r/2]  # left vertex
    vr = [√3r/2,-r/2]  # right vertex
    vs = (va, vl, vr)

    hb = [0,-r/2]  # midpoint of base
    hr = [√3r/4,r/4]  # midpoint of right side
    hl = [-√3r/4,r/4]  # midpoint of left side

    @test b == deepcopy(b)
    @test hash(b) == hash(deepcopy(b))
    @test [0,0] ∈ b  # center
    @test (s = one⁻; s.*va ∈ b && s.*vl ∈ b && s.*vr ∈ b)  # inside apex, left, right
    @test (s = one⁺; s.*va ∉ b && s.*vl ∉ b && s.*vr ∉ b)  # outside apex, left, right

    @test (xn = surfpt_nearby([0,0],b); xn≈(hb,normalize(hb)) || xn≈(hr,normalize(hr)) || xn≈(hl,normalize(hl)))  # handle point at center properly

    @test all([(p₀ = hb; p = s.*p₀; surfpt_nearby(p,b) ≈ (p₀, normalize(p₀))) for s = (one⁻⁻,1,one⁺⁺)])  # around base
    @test all([(p₀ = hr; p = s.*p₀; surfpt_nearby(p,b) ≈ (p₀, normalize(p₀))) for s = (one⁻⁻,1,one⁺⁺)])  # around right side
    @test all([(p₀ = hl; p = s.*p₀; surfpt_nearby(p,b) ≈ (p₀, normalize(p₀))) for s = (one⁻⁻,1,one⁺⁺)])  # around left side

    @test all([(p = one⁺⁺.*p₀; surfpt_nearby(p,b) ≈ (p₀, normalize(p₀))) for p₀ = vs])  # outside corners
    @test all([(p = one⁺⁺.*[p₀[1]*0.98, p₀[2]*1.02]; surfpt_nearby(p,b) ≈ (p₀, normalize(p-p₀))) for p₀ = vs])  # outside corners
    @test all([(p = p₀; surfpt_nearby(p,b) ≈ (p₀, normalize(p₀))) for p₀ = vs])  # at corners

    @test bounds(b) ≈ (vl, [vr[1],va[2]])
    @test checkbounds(b)

    @test (∆ = rand(2); translate(b,∆) ≈ regpoly(3,r,π/2,∆))

    @test b ≈ Polygon([va vl vr]')
    @test Polygon([va vl vr]') == Polygon([va vr vl]') == Polygon([vl va vr]') == Polygon([vl vr va]') == Polygon([vr va vl]') == Polygon([vr vl va]')
    @test b ≈ isosceles((vl,vr), 1.5r)
end  # @testset "triangle"

@testset "triangular prism" begin
    r = 1
    h2 = 1.1
    c = Prism([0,0,0], regpoly(3,r), 2h2)

    va = [0,r]  # apex
    vl = [-√3r/2,-r/2]  # left vertex
    vr = [√3r/2,-r/2]  # right vertex
    vs = (va, vl, vr)

    hb = [0,-r/2]  # midpoint of base line of base triangle
    hr = [√3r/4,r/4]  # midpoint of right side of base triangle
    hl = [-√3r/4,r/4]  # midpoint of left side of base triangle
    hs = (hb, hr, hl)

    @test c == deepcopy(c)
    @test hash(c) == hash(deepcopy(c))
    @test [0,0,0] ∈ c  # center
    @test all([(s = one⁻; [s.*va;t*h2] ∈ c && [s.*vl;t*h2] ∈ c && [s.*vr;t*h2] ∈ c) for t = (-one⁻,0,one⁻)]) # inside apex, left, right
    @test all([(s = one⁺; [s.*va;t*h2] ∉ c && [s.*vl;t*h2] ∉ c && [s.*vr;t*h2] ∉ c) for t = (-one⁺,0,one⁺)]) # inside apex, left, right

    @test (xn = surfpt_nearby([0,0,0],c); xn≈([hb;0],normalize([hb;0])) || xn≈([hr;0],normalize([hr;0])) || xn≈([hl;0],normalize([hl;0])))  # handle point at center properly

    @test all([(p = one⁺⁺.*[p₀;q₀]; surfpt_nearby(p,c) ≈ ([p₀;q₀], normalize(p - [p₀;q₀]))) for p₀ = vs, q₀ = (-h2,h2)])  # outside corners

    # Below, s = one⁻ is excluded because it is an inside point, for which nout is calculated differently.
    @test all([(p = s.*[p₀;q₀]; surfpt_nearby(p,c) ≈ ([p₀;q₀], normalize([normalize(p₀);q₀/abs(q₀)]))) for p₀ = vs, q₀ = (-h2,h2), s = (1,one⁺)])  # around corners
    @test all([(p = s.*[p₀;q₀]; surfpt_nearby(p,c) ≈ ([p₀;q₀], normalize([normalize(p₀);0]))) for p₀ = vs, q₀ = (-h2/2,0,h2/2), s = (1,one⁺)])  # around vertical edges

    @test all([(p = [0.5.*p₀;s*q₀]; surfpt_nearby(p,c) ≈ ([0.5.*p₀;q₀], [0,0,sign(q₀)])) for p₀ = vs, q₀ = (-h2,h2), s = (one⁻⁻,one⁻,1,one⁺,one⁺⁺)])  # around bases
    @test all([(p = [s.*p₀;q₀]; surfpt_nearby(p,c) ≈ ([p₀;q₀], [normalize(p₀);0])) for p₀ = hs, q₀ = (-h2/2,0,h2/2), s = (one⁻⁻,one⁻,1,one⁺,one⁺⁺)])  # around sides

    @test bounds(c) ≈ ([vl;-h2],[vr[1],va[2],h2])
    @test checkbounds(c)

    @test (∆ = rand(3); translate(c,∆) ≈ Prism([0,0,0].+∆, regpoly(3,r), 2h2))
end  # @testset "triangular prism"

@testset "triangular prism, rotated" begin
    ax1 = normalize([1,0,-1])
    ax2 = [0,1,0]
    ax3 = normalize([1,0,1])  # ax3 = ax1 × ax2
    ax = [ax1 ax2 ax3]

    r = 1
    h2 = 1.1
    c = Prism([0,0,0], regpoly(3,r), 2h2, ax)

    va = [0,r]  # apex
    vl = [-√3r/2,-r/2]  # left vertex
    vr = [√3r/2,-r/2]  # right vertex
    vs = (va, vl, vr)

    hb = [0,-r/2]  # midpoint of base line of base triangle
    hr = [√3r/4,r/4]  # midpoint of right side of base triangle
    hl = [-√3r/4,r/4]  # midpoint of left side of base triangle
    hs = (hb, hr, hl)

    @test c == deepcopy(c)
    @test hash(c) == hash(deepcopy(c))
    @test [0,0,0] ∈ c  # center
    @test all([(s = one⁻; ax*[s.*va;t*h2] ∈ c && ax*[s.*vl;t*h2] ∈ c && ax*[s.*vr;t*h2] ∈ c) for t = (-one⁻,0,one⁻)]) # inside apex, left, right
    @test all([(s = one⁺; ax*[s.*va;t*h2] ∉ c && ax*[s.*vl;t*h2] ∉ c && ax*[s.*vr;t*h2] ∉ c) for t = (-one⁺,0,one⁺)]) # inside apex, left, right

    @test (xn = surfpt_nearby([0,0,0],c); xn≈(ax*[hb;0],ax*normalize([hb;0])) || xn≈(ax*[hr;0],ax*normalize([hr;0])) || xn≈(ax*[hl;0],ax*normalize([hl;0])))  # handle point at center properly

    @test all([(p = one⁺⁺.*[p₀;q₀]; surfpt_nearby(ax*p,c) ≈ (ax*[p₀;q₀], ax*normalize(p - [p₀;q₀]))) for p₀ = vs, q₀ = (-h2,h2)])  # outside corners

    # Below, s = one⁻ is excluded because it is an inside point, for which nout is calculated differently.
    @test all([(p = s.*[p₀;q₀]; surfpt_nearby(ax*p,c) ≈ (ax*[p₀;q₀], ax*normalize([normalize(p₀);q₀/abs(q₀)]))) for p₀ = vs, q₀ = (-h2,h2), s = (1,one⁺)])  # around corners
    @test all([(p = s.*[p₀;q₀]; surfpt_nearby(ax*p,c) ≈ (ax*[p₀;q₀], ax*normalize([normalize(p₀);0]))) for p₀ = vs, q₀ = (-h2/2,0,h2/2), s = (1,one⁺)])  # around vertical edges

    @test all([(p = [0.5.*p₀;s*q₀]; surfpt_nearby(ax*p,c) ≈ (ax*[0.5.*p₀;q₀], ax*[0,0,sign(q₀)])) for p₀ = vs, q₀ = (-h2,h2), s = (one⁻⁻,one⁻,1,one⁺,one⁺⁺)])  # around bases
    @test all([(p = [s.*p₀;q₀]; surfpt_nearby(ax*p,c) ≈ (ax*[p₀;q₀], ax*[normalize(p₀);0])) for p₀ = hs, q₀ = (-h2/2,0,h2/2), s = (one⁻⁻,one⁻,1,one⁺,one⁺⁺)])  # around sides

    @test (vtot = [[vl;h2] [vl;-h2] [vr;h2] [vr;-h2] [va;h2] [va;-h2]]; bounds(c) ≈ (minimum(ax*vtot, dims=2)[:,1], maximum(ax*vtot, dims=2)[:,1]))
    @test checkbounds(c)

    @test (∆ = rand(3); translate(c,∆) ≈ Prism([0,0,0].+∆, regpoly(3,r), 2h2, ax))
end  # @testset "triangular prism, rotated"


export Frobenius

#Frobenius matrices or Gaussian elimination matrices
#
#Matrices of the form
#[ 1 0 ...     0 ]
#[ 0 1 ...     0 ]
#[ .........     ]
#[ ... 1 ...     ]
#[ ... c1 1 ...  ]
#[ ... c2 0 1 ...]
#[ ............. ]
#[ ... ck ...   1]
#
#i.e. an identity matrix with nonzero subdiagonal elements along a single
#column.
#
#In this implementation, the subdiagonal of the nonzero column is stored as a
#dense vector, so that the size can be inferred automatically as j+k where j is
#the index of the column and k is the number of subdiagonal elements.

immutable Frobenius{T} <: AbstractArray{T, 2}
    colidx :: Int
    c :: Vector{T}
end

#Basic property computations
size(F::Frobenius, r::Int) = (r==1 || r==2) ? F.colidx + length(F.c) :
    throw(ArgumentError("Frobenius matrix is of rank 2"))

function size(F::Frobenius)
    n = F.colidx + length(F.c)
    n, n
end

#XXX Inefficient but works
getindex(F::Frobenius, i, j) = getindex(full(F), i, j)
isassigned(F::Frobenius, i, j) = isassigned(full(F), i, j)

function full{T}(F::Frobenius{T})
    M = eye(T, size(F, 1))
    M[F.colidx+1:end, F.colidx] = F.c
    M
end

#Linear algebra stuff
function A_mul_B!{T}(F::Frobenius{T}, b::Vector{T})
    (n = size(F, 2)) == length(b) || throw(DimensionMismatch("$n $(length(b))"))
    for i=F.colidx+1:n
        b[i] += F.c[i-F.colidx] * b[F.colidx]
    end
    b
end
*{T}(F::Frobenius{T}, b::Vector{T}) = A_mul_B!(F, copy(b))

function *{T}(F::Frobenius{T}, G::Frobenius{T})
    (n = size(F, 2)) == size(G, 2) || throw(DimensionMismatch(""))
    if F.colidx == G.colidx #Answer is still expressible as a Frobenius
        return Frobenius(F.colidx, F.c + G.c)
    else
        M = full(F)
        M[G.colidx+1:end, G.colidx] = F.colidx < G.colidx ? G.c :
            Frobenius(F.colidx-G.colidx, F.c) * G.c
        return M
    end
end

inv(F::Frobenius) = Frobenius(F.colidx, -F.c)


using Test
using TestSetExtensions
using LinearAlgebra
using Random
using Qaintessent

##==----------------------------------------------------------------------------------------------------------------------

@testset ExtendedTestSet "statevector" begin

    @testset "test statevector int constructor" begin
        N = rand(1:10)
        s = Statevector(N)
        @test s.N == N
        @test s.state == zeros(ComplexF64, 2^N)
    end

    @testset "test statevector vector constructor" begin
        N = rand(1:10)
        ψ = randn(ComplexF64, 2^N)
        s = Statevector(ψ)
        @test s.N == N
        @test s.state == ψ
    end

    @testset "test statevector indexing" begin
        N = rand(1:10)
        ψ = randn(ComplexF64, 2^N)
        s = Statevector(ψ)
        r = rand(1:2^N)
        @test s[r] == ψ[r]
    end

    @testset "test statevector attributes" begin
        N = rand(1:10)
        ψ = randn(ComplexF64, 2^N)
        s = Statevector(ψ)
        @test size(s) == size(ψ)
        @test length(s) == length(ψ)
        @test IndexStyle(typeof(s)) == IndexLinear()
    end
end


"""
$(SIGNATURES)

Compute `log(sum(exp, X))` in a numerically stable way that avoids intermediate over- and
underflow.

`X` should be an iterator of real or complex numbers. The result is computed using a single
pass over the data.

# References

[Sebastian Nowozin: Streaming Log-sum-exp Computation](http://www.nowozin.net/sebastian/blog/streaming-log-sum-exp-computation.html)
"""
logsumexp(X) = _logsumexp_onepass(X)

"""
$(SIGNATURES)

Compute `log.(sum(exp.(X); dims=dims))` in a numerically stable way that avoids
intermediate over- and underflow.

The result is computed using a single pass over the data.

# References

[Sebastian Nowozin: Streaming Log-sum-exp Computation](http://www.nowozin.net/sebastian/blog/streaming-log-sum-exp-computation.html)
"""
logsumexp(X::AbstractArray{<:Number}; dims=:) = _logsumexp(X, dims)

_logsumexp(X::AbstractArray{<:Number}, ::Colon) = _logsumexp_onepass(X)
function _logsumexp(X::AbstractArray{<:Number}, dims)
    # Do not use log(zero(eltype(X))) directly to avoid issues with ForwardDiff (#82)
    FT = float(eltype(X))
    xmax_r = reduce(_logsumexp_onepass_op, X; dims=dims, init=(FT(-Inf), zero(FT)))
    return @. first(xmax_r) + log1p(last(xmax_r))
end

function _logsumexp_onepass(X)
    # fallback for empty collections
    isempty(X) && return log(sum(X))
    return _logsumexp_onepass_result(_logsumexp_onepass_reduce(X, Base.IteratorEltype(X)))
end

# function barrier for reductions with single element and without initial element
_logsumexp_onepass_result(x) = float(x)
_logsumexp_onepass_result((xmax, r)::Tuple) = xmax + log1p(r)

# iterables with known element type
function _logsumexp_onepass_reduce(X, ::Base.HasEltype)
    # do not perform type computations if element type is abstract
    T = eltype(X)
    isconcretetype(T) || return _logsumexp_onepass_reduce(X, Base.EltypeUnknown())

    FT = float(T)
    return reduce(_logsumexp_onepass_op, X; init=(FT(-Inf), zero(FT)))
end

# iterables without known element type
_logsumexp_onepass_reduce(X, ::Base.EltypeUnknown) = reduce(_logsumexp_onepass_op, X)

## Reductions for one-pass algorithm: avoid expensive multiplications if numbers are reduced

# reduce two numbers
function _logsumexp_onepass_op(x1::T, x2::T) where {T<:Number}
    xmax, a = if x1 == x2
        # handle `x1 = x2 = ±Inf` correctly
        x2, zero(x1 - x2)
    elseif isnan(x1) || isnan(x2)
        # ensure that `NaN` is propagated correctly for complex numbers
        z = oftype(x1, NaN)
        z, exp(z)
    elseif real(x1) > real(x2)
        x1, x2 - x1
    else
        x2, x1 - x2
    end
    r = exp(a)
    return xmax, r
end
_logsumexp_onepass_op(x1::Number, x2::Number) = _logsumexp_onepass_op(promote(x1, x2)...)

# reduce a number and a partial sum
_logsumexp_onepass_op(x::Number, (xmax, r)::Tuple{<:Number,<:Number}) =
    _logsumexp_onepass_op(x, xmax, r)
_logsumexp_onepass_op((xmax, r)::Tuple{<:Number,<:Number}, x::Number) =
    _logsumexp_onepass_op(x, xmax, r)
_logsumexp_onepass_op(x::Number, xmax::Number, r::Number) =
    _logsumexp_onepass_op(promote(x, xmax)..., r)
function _logsumexp_onepass_op(x::T, xmax::T, r::Number) where {T<:Number}
    _xmax, _r = if x == xmax
        # handle `x = xmax = ±Inf` correctly
        xmax, r + exp(zero(x - xmax))
    elseif isnan(x) || isnan(xmax)
        # ensure that `NaN` is propagated correctly for complex numbers
        z = oftype(x, NaN)
        z, r + exp(z)
    elseif real(x) > real(xmax)
        x, (r + one(r)) * exp(xmax - x)
    else
        xmax, r + exp(x - xmax)
    end
    return _xmax, _r
end

# reduce two partial sums
function _logsumexp_onepass_op(
    (xmax1, r1)::Tuple{<:Number,<:Number}, (xmax2, r2)::Tuple{<:Number,<:Number}
)
    return _logsumexp_onepass_op(xmax1, xmax2, r1, r2)
end
function _logsumexp_onepass_op(xmax1::Number, xmax2::Number, r1::Number, r2::Number)
    return _logsumexp_onepass_op(promote(xmax1, xmax2)..., promote(r1, r2)...)
end
function _logsumexp_onepass_op(xmax1::T, xmax2::T, r1::R, r2::R) where {T<:Number,R<:Number}
    xmax, r = if xmax1 == xmax2
        # handle `xmax1 = xmax2 = ±Inf` correctly
        xmax2, r2 + (r1 + one(r1)) * exp(zero(xmax1 - xmax2))
    elseif isnan(xmax1) || isnan(xmax2)
        # ensure that `NaN` is propagated correctly for complex numbers
        z = oftype(xmax1, NaN)
        z, r1 + exp(z)
    elseif real(xmax1) > real(xmax2)
        xmax1, r1 + (r2 + one(r2)) * exp(xmax2 - xmax1)
    else
        xmax2, r2 + (r1 + one(r1)) * exp(xmax1 - xmax2)
    end
    return xmax, r
end


using LinearAlgebraicRepresentation
Lar = LinearAlgebraicRepresentation
Lara = Lar.Arrangement

#--------------------------------------------------------------------------------------------------------------------------------
#                               PIPELINE PART 1
#--------------------------------------------------------------------------------------------------------------------------------

"""
    frag_edge_channel(in_chan, out_chan, V, EV, bigPI)

Utility function for parallel edge fragmentation.

This function handles the edge fragmentation in the first part of arrangement's algorithmic pipeline
(see also [`Lar.Arrangement.planar_arrangement_1`](@ref)) during multiprocessing computation.
In order to do so it needs two `Distributed.RemoteChannel`s, one with the inputs and one for outputs.

See also: [`Lar.Arrangement.frag_edge`](@ref)
"""
function frag_edge_channel( in_chan::Distributed.RemoteChannel{Channel{Int64}},
                            out_chan::Distributed.RemoteChannel{Channel{Tuple}},
                            V::Lar.Points,
                            EV::Lar.ChainOp,
                            bigPI::Array{Array{Int64,1},1})
    run_loop = true
    while run_loop
        edgenum = take!(in_chan)
        if edgenum != -1
            put!(out_chan, (edgenum, Lar.Arrangement.frag_edge(V, EV, edgenum, bigPI)))
        else
            run_loop = false
        end
    end
end

"""
	frag_edge(V, EV, edge_idx, bigPI)

Splits the `edge_idx`-th edge in `EV`.

This method splits the `edge_idx`-th edge in `EV` into several parts by confronting it
with the others that intersect its bounding box (see also [`Lar.Arrangement.intersect_edges`](@ref)).

The method returns a set of the new vertices that the segment is made of (with redundancies) and
the associated cochain (with no redundancies).

See also:
 - [`Lar.Arrangement.planar_arrangement_1`](@ref)
 - [`Lar.Arrangement.frag_edge_channel`](@ref)

---

# Examples
```jldoctest
julia> V = [1.0 0.0; 0.0 1.0; 0.0 0.5; 0.5 1.0; 1.0 1.0]; # By Rows!

julia> EV = [[1, 2], [2, 5], [3, 4], [4, 5]];

julia> copEV = Lar.coboundary_0(EV::Lar.Cells);

julia> bigPI = Lar.spaceindex((convert(Lar.Points, V'), EV));

julia> Lara.frag_edge(V, copEV, 1, bigPI)[1]
5×2 Array{Float64,2}:
 1.0   0.0
 0.0   1.0
 1.0   0.0
 0.25  0.75
 0.0   1.0

julia> Lara.frag_edge(V, copEV, 1, bigPI)[2]
2×5 SparseMatrixCSC{Int8,Int64} with 4 stored entries:
  [1, 1]  =  1
  [2, 2]  =  1
  [1, 4]  =  1
  [2, 4]  =  1
```
"""
function frag_edge(V::Lar.Points, EV::Lar.ChainOp, edge_idx::Int, bigPI::Array{Array{Int64,1},1})::Tuple{Lar.Points, Lar.ChainOp}
    alphas = Dict{Float64, Int}()
    edge = EV[edge_idx, :]
    verts = V[edge.nzind, :]
    for i in bigPI[edge_idx]
        if i != edge_idx && edge_idx in bigPI[i]    #<------------------------- Could be usefull?
            intersection = Lar.Arrangement.intersect_edges(V, edge, EV[i, :])
            for (point, alpha) in intersection
                verts = [verts; point]
                alphas[alpha] = size(verts, 1)
            end
        end
    end
    alphas[0.0], alphas[1.0] = [1, 2]
    alphas_keys = sort(collect(keys(alphas)))
    edge_num = length(alphas_keys)-1
    verts_num = size(verts, 1)
    ev = SparseArrays.spzeros(Int8, edge_num, verts_num)
    for i in 1:edge_num
        ev[i, alphas[alphas_keys[i]]] = 1
        ev[i, alphas[alphas_keys[i+1]]] = 1
    end
    return verts, ev
end

"""
	intersect_edges(V, edge1, edge2)

Finds the intersection point (if there exists) between the two given edges.

This method compute the points where `edge2` intersect `edge1`.
If they are collinear only the vertices of `edge2` are considered (see the second example).

See also: [`Lar.Arrangement.frag_edge`](@ref)

---

# Examples
```jldoctest
# Cross
julia> V = [1.0 0.0; 0.0 1.0; 0.0 0.5; 0.5 1.0]; # By Rows!

julia> EV = [[1, 2], [3, 4]];

julia> copEV = Lar.coboundary_0(EV::Lar.Cells);

julia> Lara.intersect_edges(V, copEV[1, :], copEV[2, :])
1-element Array{Tuple{Array{T,2} where T,Float64},1}:
 ([0.25 0.75], 0.75)
```

```jldoctest
# Collinear
julia> V = [1.0 0.0; 0.0 1.0; 0.75 0.25; 0.5 0.5]; # By Rows!

julia> EV = [[1, 2], [3, 4]];

julia> copEV = Lar.coboundary_0(EV::Lar.Cells);

julia> Lar.Arrangement.intersect_edges(V, copEV[1, :], copEV[2, :])
2-element Array{Tuple{Array{T,2} where T,Float64},1}:
 ([0.75 0.25], 0.25)
 ([0.5 0.5], 0.5)

julia> Lar.Arrangement.intersect_edges(V, copEV[2, :], copEV[1, :])
0-element Array{Tuple{Array{T,2} where T,Float64},1}
```
"""
function intersect_edges(V::Lar.Points, edge1::Lar.Cell, edge2::Lar.Cell)::Array{Tuple{Lar.Points, Float64}, 1}
    err = 10e-8

    x1, y1, x2, y2 = vcat(map(c->V[c, :], edge1.nzind)...)
    x3, y3, x4, y4 = vcat(map(c->V[c, :], edge2.nzind)...)
    ret = Array{Tuple{Lar.Points, Float64}, 1}()

    v1 = [x2-x1, y2-y1];
    v2 = [x4-x3, y4-y3];
    v3 = [x3-x1, y3-y1];

    ang1 = Lar.dot(Lar.normalize(v1), Lar.normalize(v2))
    ang2 = Lar.dot(Lar.normalize(v1), Lar.normalize(v3))

    parallel = 1-err < abs(ang1) < 1+err
    colinear = parallel && (1-err < abs(ang2) < 1+err || -err < Lar.norm(v3) < err)

    if colinear
        o = [x1 y1]
        v = [x2 y2] - o
        alpha = 1/Lar.dot(v,v')
        ps = [x3 y3; x4 y4]
        for i in 1:2
            a = alpha*Lar.dot(v',(reshape(ps[i, :], 1, 2)-o))
            if 0 < a < 1
                push!(ret, (ps[i:i, :], a))
            end
        end

    elseif !parallel
        denom = (v2[2])*(v1[1]) - (v2[1])*(v1[2])
        a = ((v2[1])*(-v3[2]) - (v2[2])*(-v3[1])) / denom
        b = ((v1[1])*(-v3[2]) - (v1[2])*(-v3[1])) / denom

        if -err < a < 1+err && -err <= b <= 1+err
            p = [(x1 + a*(x2-x1))  (y1 + a*(y2-y1))]
            push!(ret, (p, a))
        end
    end

    return ret
end

"""
    merge_vertices!(V, EV[, edge_map[, err]])

Compact the vertices closer than `err` in a single one.

This method check one at time each vertex ``v`` in `V` and identifies each other vertex within `err` with ``v`` itself.
The cochain `EV` is coherently modified (multiple edges between two vertices are not allowed).
If an `edge_map` is given in input (this could be usefull during the planar arrangements), then also
the map is coherently modified and given back in output.

See also: [`Lar.Arrangement.planar_arrangement_1`](@ref)

---

# Examples
```jldoctest
# Collinear
julia> V = [0.5 0.5; 0.0 0.0; 0.5 0.5; 1.0 1.0; 0.5 0.5; 1.0 1.0]; # By Rows!

julia> EV = [[1, 4], [3, 2], [5, 6], [1, 6], [5, 3]];

julia> copEV = Lar.coboundary_0(EV::Lar.Cells);

julia> Lara.merge_vertices!(V, copEV)[1]
3×2 Array{Float64,2}:
 0.5  0.5
 0.0  0.0
 1.0  1.0

julia> Lara.merge_vertices!(V, copEV)[2]
2×3 SparseArrays.SparseMatrixCSC{Int8,Int64} with 4 stored entries:
  [1, 1]  =  1
  [2, 1]  =  1
  [2, 2]  =  1
  [1, 3]  =  1
```
"""
function merge_vertices!(V::Lar.Points, EV::Lar.ChainOp, edge_map::Array{Array{Int64,1},1}=[[-1]], err=1e-4)
    vertsnum = size(V, 1)
    edgenum = size(EV, 1)
    newverts = zeros(Int, vertsnum)
    # KDTree constructor needs an explicit array of Float64
    V = Array{Float64,2}(V)
    kdtree = KDTree(permutedims(V))

    todelete = []

    i = 1
    for vi in 1:vertsnum
        if !(vi in todelete)
            nearvs = Lar.inrange(kdtree, V[vi, :], err)

            newverts[nearvs] .= i

            nearvs = setdiff(nearvs, vi)
            todelete = union(todelete, nearvs)

            i = i + 1
        end
    end

    nV = V[setdiff(collect(1:vertsnum), todelete), :]

    edges = Array{Tuple{Int, Int}, 1}(undef, edgenum)
    oedges = Array{Tuple{Int, Int}, 1}(undef, edgenum)

    for ei in 1:edgenum
        v1, v2 = EV[ei, :].nzind

        edges[ei] = Tuple{Int, Int}(sort([newverts[v1], newverts[v2]]))
        oedges[ei] = Tuple{Int, Int}(sort([v1, v2]))

    end
    nedges = union(edges)
    nedges = filter(t->t[1]!=t[2], nedges)

    nedgenum = length(nedges)
    nEV = spzeros(Int8, nedgenum, size(nV, 1))

    etuple2idx = Dict{Tuple{Int, Int}, Int}()

    for ei in 1:nedgenum
        nEV[ei, collect(nedges[ei])] .= 1
        etuple2idx[nedges[ei]] = ei
    end

    if edge_map != [[-1]]
        for i in 1:length(edge_map)
            row = edge_map[i]
            row = map(x->edges[x], row)
            row = filter(t->t[1]!=t[2], row)
            row = map(x->etuple2idx[x], row)
            edge_map[i] = row
        end
        return Lar.Points(nV), nEV, edge_map
    end

    return Lar.Points(nV), nEV
end

#--------------------------------------------------------------------------------------------------------------------------------
#                                       PIPELINE BICONNECTED COMPONENTS
#--------------------------------------------------------------------------------------------------------------------------------

"""
    biconnected_components(EV)

Compute sets of 2-cells on the same 3-cells biconnected components.

The method evaluate the 2-cells ``σ_i`` wich lies on the same 3-cells biconnected
component ``χ_j`` and gives back an array that contains an array of ``σ_i`` for each ``j``.
Do note that if the cochain EV contains more copies of the same 2-cell then
it will be considered like a 3-cell.

Intermediate part of Planar Arrangement's algorithmic pipeline.

See also: [`Lar.planar_arrangement`](@ref) for the complete pipeline.

---

# Examples
```jldoctest
julia> copEV = SparseArrays.sparse(Array{Int8, 2}([
    [1 1 0 0 0 0] #1 -> 1,2  |
    [1 0 1 0 0 0] #2 -> 1,3  |
    [1 0 0 1 0 0] #3 -> 1,4   |
    [1 0 0 0 1 0] #4 -> 1,5   |
    [1 0 0 0 0 1] #5 -> 1,6
    [0 1 1 0 0 0] #6 -> 2,3  |
    [0 0 0 1 1 0] #7 -> 4,5   |
    ]));
julia> Lara.biconnected_components(copEV)
2-element Array{Array{Int64,1},1}:
 [2, 6, 1]
 [4, 7, 3]
```

```jldoctest
julia> copEV = SparseArrays.sparse(Array{Int8, 2}([
    [1 1 0] #1 -> 1,2  |
    [1 1 0] #2 -> 1,2  |
    [1 0 1] #3 -> 1,2
    ]));
julia> Lara.biconnected_components(copEV)
1-element Array{Array{Int64,1},1}:
 [2, 1]
```
"""
function biconnected_components(EV::Lar.ChainOp)::Array{Array{Int, 1}, 1}
    ps = Array{Tuple{Int, Int, Int}, 1}()
    es = Array{Tuple{Int, Int}, 1}()
    todel = Array{Int, 1}()
    visited = Array{Int, 1}()
    bicon_comps = Array{Array{Int, 1}, 1}()
    hivtx = 1

    function an_edge(point)
        edges = setdiff(EV[:, point].nzind, todel)
        if length(edges) == 0
            edges = [false]
        end
        edges[1]
    end

    function get_head(edge, tail)
        setdiff(EV[edge, :].nzind, [tail])[1]
    end

    function v_to_vi(v)
        i = findfirst(t->t[1]==v, ps)
        # seems findfirst changed from 0 to Nothing
        if typeof(i) == Nothing
            return false
        elseif i == 0
            return false
        else
            return ps[i][2]
        end
    end

    push!(ps, (1,1,1))
    push!(visited, 1)
    exit = false
    while !exit
        edge = an_edge(ps[end][1])
        if edge != false
            tail = ps[end][2]
            head = get_head(edge, ps[end][1])
            hi = v_to_vi(head)
            if hi == false
                hivtx += 1
                push!(ps, (head, hivtx, ps[end][2]))
                push!(visited, head)
            else
                if hi < ps[end][3]
                    ps[end] = (ps[end][1], ps[end][2], hi)
                end
            end
            push!(es, (edge, tail))
            push!(todel, edge)
        else
            if length(ps) == 1
                found = false
                pop!(ps)
                for i in 1:size(EV,2)
                    if !(i in visited)
                        hivtx = 1
                        push!(ps, (i, hivtx, 1))
                        push!(visited, i)
                        found = true
                        break
                    end
                end
                if !found
                    exit = true
                end

            else
                if ps[end][3] == ps[end-1][2]
                    edges = Array{Int, 1}()
                    while true
                        edge, tail = pop!(es)
                        push!(edges, edge)
                        if tail == ps[end][3]
                            if length(edges) > 1
                                push!(bicon_comps, edges)
                            end
                            break
                        end
                    end

                else
                    if ps[end-1][3] > ps[end][3]
                        ps[end-1] = (ps[end-1][1], ps[end-1][2], ps[end][3])
                    end
                end
                pop!(ps)
            end
        end
    end
    bicon_comps = sort(bicon_comps, lt=(x,y)->length(x)>length(y))
    return bicon_comps
end

#--------------------------------------------------------------------------------------------------------------------------------
#                                       PIPELINE PART 2
#--------------------------------------------------------------------------------------------------------------------------------

"""
    get_external_cycle(V, EV, FE)

Evaluates the index of the external 3D-cell.

This method looks for and retrieve the external cycle of the model `(V, EV, FE)`.
If the cell does not exist then it return a `Nothing` element.

See also: [`Lar.Arrangement.componentgraph`](@ref).

---

# Examples
```jldoctest
# ``K^4`` graph
julia> V = [0.0 0.0; 4.0 0.0; 2.0 3.0; 2.0 1.5];
julia> copEV = SparseArrays.sparse(Array{Int8, 2}([
		[1 1 0 0] #1 -> 1,2
		[0 1 1 0] #2 -> 2,3
		[1 0 1 0] #3 -> 3,1
		[1 0 0 1] #4 -> 1,2
		[0 1 0 1] #5 -> 2,3
		[0 0 1 1] #6 -> 3,1
	]));
julia> copFE = SparseArrays.sparse(Array{Int8, 2}([
		[1 0 0 1 1 0] #1 -> 1,4,5
		[0 1 0 0 1 1] #2 -> 2,5,6
		[0 0 1 1 0 1] #3 -> 3,4,6
		[1 1 1 0 0 0] #4 -> 1,2,3 External
	]));

julia> Lara.get_external_cycle(V, copEV, copFE)
4

julia> typeof(Lara.get_external_cycle(V, copEV, copFE[1:3,:]))
Nothing
```
"""
function get_external_cycle(V::Lar.Points, EV::Lar.ChainOp, FE::Lar.ChainOp)
    FV = abs.(FE)*EV
    vs = sparsevec(mapslices(sum, abs.(EV), dims=1)').nzind
    minv_x1 = maxv_x1 = minv_x2 = maxv_x2 = pop!(vs)
    for i in vs
        if V[i, 1] > V[maxv_x1, 1]
            maxv_x1 = i
        elseif V[i, 1] < V[minv_x1, 1]
            minv_x1 = i
        end
        if V[i, 2] > V[maxv_x2, 2]
            maxv_x2 = i
        elseif V[i, 2] < V[minv_x2, 2]
            minv_x2 = i
        end
    end
    cells = intersect(
        FV[:, minv_x1].nzind,
        FV[:, maxv_x1].nzind,
        FV[:, minv_x2].nzind,
        FV[:, maxv_x2].nzind
    )
    if length(cells) == 1
        return cells[1]
    else
        for c in cells
            if Lar.face_area(V, EV, FE[c, :]) < 0
                return c
            end
        end
    end
end

"""
    pre_containment_test(bboxes)

Generate the containment graph associated to `bboxes`.

The function evaluate a `SparseArrays{Int8, n, n}` PRE-containmnet graph in the sense
that it stores `1` when the bbox associated to the `i`-th component contains entirely
the bbox associated to the `j`-th one. (where `n = length(bboxes)`)

See also:
  - [`Lar.Arrangement.componentgraph`](@ref).
  - [`Lar.bbox_contains`](@ref).

---

# Examples
```jldoctest
# Planar Tiles
julia> bboxes = [
		([0.0 0.0], [1.0 1.0])
		([0.0 0.0], [0.4 0.4])
		([0.6 0.0], [1.0 0.4])
		([0.0 0.6], [0.4 1.0])
		([0.6 0.6], [1.0 1.0])
		([2.0 3.0], [2.0 3.0]) # no intersection
	];

julia> Lara.pre_containment_test(bboxes)
5×5 SparseMatrixCSC{Int8,Int64} with 4 stored entries:
  [2, 1]  =  1
  [3, 1]  =  1
  [4, 1]  =  1
  [5, 1]  =  1
```
"""
function pre_containment_test(bboxes)
    n = length(bboxes)
    containment_graph = SparseArrays.spzeros(Int8, n, n)

    for i in 1:n
        for j in 1:n
            if i != j && Lar.bbox_contains(bboxes[j], bboxes[i])
                containment_graph[i, j] = 1
            end
        end
    end

    return containment_graph
end

"""
    prune_containment_graph(n, V, EVs, shells, graph)

Prunes the containment `graph` from the non-included faces.

This method prunes the containment graph eliminating the included bouning boxes
that not corresponds to included faces.

Do note that this method expects to work on biconnectet clusters of faces.

See also:
  - [`Lar.Arrangement.componentgraph`](@ref).
  - [`Lar.point_in_face`](@ref).
```
"""
function prune_containment_graph(n, V, EVs, shells, graph)

    for i in 1:n
        an_edge = shells[i].nzind[1]
        origin_index = EVs[i][an_edge, :].nzind[1]
        origin = V[origin_index, :]

        for j in 1:n
            if i != j
                if graph[i, j] == 1
                    shell_edge_indexes = shells[j].nzind
                    ev = EVs[j][shell_edge_indexes, :]

                    if !Lar.point_in_face(origin, V, ev)
                        graph[i, j] = 0
                    end
                end
             end
         end

     end
     return graph
end

"""
    transitive_reduction!(graph)

Prune the redundancies in containment `graph`.

The ``m``-layer containment `graph` is reduced to the 1-layer containment `graph`
where only the first level nested components are considered.

See also: [`Lar.Arrangement.componentgraph`](@ref).

---

# Examples
```jldoctest
julia> containment = [
		0 0 0 0 0 0
		0 0 0 0 0 0
		1 0 0 0 0 0
		1 0 0 0 0 0
		1 0 1 0 0 0
		1 0 1 0 1 0
	];

julia> transitive_reduction!(containment)

julia> containment
6×6 Array{Int64,2}:
 0  0  0  0  0  0
 0  0  0  0  0  0
 1  0  0  0  0  0
 1  0  0  0  0  0
 0  0  1  0  0  0
 0  0  0  0  1  0
```
"""
function transitive_reduction!(graph)
    n = size(graph, 1)
    for j in 1:n
        for i in 1:n
            if graph[i, j] > 0
                for k in 1:n
                    if graph[j, k] > 0
                        graph[i, k] = 0
                    end
                end
            end
        end
    end
end

"""
    cell_merging(n, containment_graph, V, EVs, boundaries, shells, shell_bboxes)

Cells composing for the Topological Gift Wrapping algorithm.

This is the online part of the TGW algorithm.

See also: [`Lar.Arrangement.planar_arrangement_2`](@ref).
"""
function cell_merging(n, containment_graph, V, EVs, boundaries, shells, shell_bboxes)
    function bboxes(V::Lar.Points, indexes::Lar.ChainOp)
        boxes = Array{Tuple{Any, Any}}(undef, indexes.n)
        for i in 1:indexes.n
            v_inds = indexes[:, i].nzind
            boxes[i] = Lar.bbox(V[v_inds, :])
        end
        boxes
    end
    # initialization
    sums = Array{Tuple{Int, Int, Int}}(undef, 0);
	# assembling child components with father components
    for father in 1:n
        if sum(containment_graph[:, father]) > 0
            father_bboxes = bboxes(V, abs.(EVs[father]')*abs.(boundaries[father]'))
            for child in 1:n
                if containment_graph[child, father] > 0
                    child_bbox = shell_bboxes[child]
                    for b in 1:length(father_bboxes)
                        if Lar.bbox_contains(father_bboxes[b], child_bbox)
                            push!(sums, (father, b, child))
                            break
                        end
                    end
                end
            end
        end
    end
    # offset assembly initialization
    EV = vcat(EVs...)
    edgenum = size(EV, 1)
    facenum = sum(map(x->size(x,1), boundaries))
    FE = spzeros(Int8, facenum, edgenum)
    shells2 = spzeros(Int8, length(shells), edgenum)
    r_offsets = [1]
    c_offset = 1
    # submatrices construction
    for i in 1:n
        min_row = r_offsets[end]
        max_row = r_offsets[end] + size(boundaries[i], 1) - 1
        min_col = c_offset
        max_col = c_offset + size(boundaries[i], 2) - 1
        FE[min_row:max_row, min_col:max_col] = boundaries[i]
        shells2[i, min_col:max_col] = shells[i]
        push!(r_offsets, max_row + 1)
        c_offset = max_col + 1
    end
    # offsetting assembly of component submatrices
    for (f, r, c) in sums
        FE[r_offsets[f]+r-1, :] += shells2[c, :]
    end

    return EV, FE
end

"""
    componentgraph(V, copEV, bicon_comps)

Topological Gift Wrapping algorithm on 2D skeletons.

This is the offline part of the TGW algorithm. It takes in input a model and its
biconnected components mapping and evaluates usefull informations:
 1. Number of biconnected components.
 2. Component Graph of the biconnected structure.
 3. The 1-cells structure (UNMODIFIED). <----------------------------- Could be removed?
 4. Association between non-dangling 2-cells and their orientation (for each component).
 5. Association between 3-cells and 2-cells (with orientation, for each component).
 6. Association between 3-cells and their orientation (for each component).
 7. Shell bounding boxes of the components.

See also: [`Lar.Arrangement.planar_arrangement_2`](@ref) for the TGW.

---

# Examples
```jldoctest
# Papillon
julia> V = [0.0 0.0; 0.0 3.0; 2.0 1.5; 4.0 0.0; 4.0 3.0];

julia> copEV = SparseArrays.sparse(Array{Int8, 2}([
		[1 1 0 0 0] #1 -> 1,2
		[0 1 1 0 0] #2 -> 2,3
		[1 0 1 0 0] #3 -> 3,1
		[0 0 1 1 0] #4 -> 3,4
		[0 0 0 1 1] #5 -> 4,5
		[0 0 1 0 1] #6 -> 3,5
	]));

julia> copFE = SparseArrays.sparse(Array{Int8, 2}([
		[1 1 1 0 0 0] #1 -> 1,2,3
		[0 0 0 1 1 1] #2 -> 4,5,6
		[1 1 1 1 1 1] #3 -> 1,2,3,4,5,6 External
	]));

julia> bicon_comps = [[1, 2, 3], [4, 5, 6]];

julia> Lara.componentgraph(V, copEV, bicon_comps)[1]
2

julia> Lara.componentgraph(V, copEV, bicon_comps)[2]
2×2 SparseMatrixCSC{Int8,Int64} with 0 stored entries

julia> Lara.componentgraph(V, copEV, bicon_comps)[4]
2-element Array{SparseMatrixCSC{Int8,Int64},1}:

  [1, 1]  =  -1
  [3, 1]  =  -1
  [1, 2]  =  1
  [2, 2]  =  -1
  [2, 3]  =  1
  [3, 3]  =  1

  [1, 3]  =  -1
  [3, 3]  =  -1
  [1, 4]  =  1
  [2, 4]  =  -1
  [2, 5]  =  1
  [3, 5]  =  1

julia> Lara.componentgraph(V, copEV, bicon_comps)[5]
2-element Array{SparseMatrixCSC{Int8,Int64},1}:

  [1, 1]  =  -1
  [1, 2]  =  -1
  [1, 3]  =  1

  [1, 1]  =  1
  [1, 2]  =  1
  [1, 3]  =  -1

julia> Lara.componentgraph(V, copEV, bicon_comps)[6]
2-element Array{SparseVector{Int8,Int64},1}:
   [1]  =  1
  [2]  =  1
  [3]  =  -1
   [1]  =  -1
  [2]  =  -1
  [3]  =  1

julia> Lara.componentgraph(V, copEV, bicon_comps)[7]
2-element Array{Any,1}:
 ([0.0 0.0], [2.0 3.0])
 ([2.0 0.0], [4.0 3.0])
```
"""
function componentgraph(V, copEV, bicon_comps)

	# arrangement of isolated components
	n = size(bicon_comps, 1)
	shells = Array{Lar.Chain, 1}(undef, n)
	boundaries = Array{Lar.ChainOp, 1}(undef, n)
	EVs = Array{Lar.ChainOp, 1}(undef, n)
	# for each component
	for p=1:n
		ev = copEV[sort(bicon_comps[p]), :]
		# computation of 2-cells
		fe = Lar.Arrangement.minimal_2cycles(V, ev)
		# exterior cycle
		global shell_num = Lar.Arrangement.get_external_cycle(V, ev, fe)
		# decompose each fe (co-boundary local to component)
		EVs[p] = ev
		global tokeep = setdiff(1:fe.m, shell_num)
		boundaries[p] = fe[tokeep, :]
		shells[p] = fe[shell_num, :]
	end

#	@show shell_num;
#	@show [SparseArrays.findnz(EVs[k]) for k=1:length(EVs)];
#	@show tokeep;
#	@show [SparseArrays.findnz(boundaries[k]) for k=1:length(boundaries)];
#	@show [SparseArrays.findnz(shells[k]) for k=1:length(shells)];

	# computation of bounding boxes of isolated components
	shell_bboxes = []
	for i in 1:n
		vs_indexes = (abs.(EVs[i]')*abs.(shells[i])).nzind
		@show vs_indexes
		push!(shell_bboxes, Lar.bbox(V[vs_indexes, :]))
		@show shell_bboxes
	end
	# computation and reduction of containment graph
	containment_graph = Lar.Arrangement.pre_containment_test(shell_bboxes)
	@show 1,Matrix(containment_graph)
	containment_graph = Lar.Arrangement.prune_containment_graph(n, V, EVs, shells, containment_graph)
	@show 2,Matrix(containment_graph)
	Lar.Arrangement.transitive_reduction!(containment_graph)
	@show 3,Matrix(containment_graph)
	return n, containment_graph, V, EVs, boundaries, shells, shell_bboxes
end

"""
    cleandecomposition(V, copEV, sigma)

!!! NOT WORKING

This function clears the model `(V, copEV)` from all edges outside ``σ``.

This function takes a model `(V, copEV)` and a `Lar.Chain` ``σ`` and
gives back the dropped model and and a vector `todel` of the 2-cells to drop.

---

# Examples
```jldoctest
# Nested Triangles
julia> V = [
		0.0 0.0; 2.0 0.0; 4.0 0.0;
		1.0 1.5; 3.0 1.5; 2.0 3.0;
		2.0 -3.; -2. 3.0; 6.0 3.0
	];

julia> copEV = SparseArrays.sparse(Array{Int8, 2}([
    [1 0 1 0 0 0 0 0 0] #1 -> 1,3
    [1 0 0 0 0 1 0 0 0] #2 -> 1,6
    [0 0 1 0 0 1 0 0 0] #3 -> 3,6
    [0 1 0 1 0 0 0 0 0] #4 -> 2,4
    [0 1 0 0 1 0 0 0 0] #5 -> 2,5
    [0 0 0 1 1 0 0 0 0] #6 -> 4,5
    [0 0 0 0 0 0 1 1 0] #7 -> 7,8
    [0 0 0 0 0 0 1 0 1] #8 -> 7,9
    [0 0 0 0 0 0 0 1 1] #9 -> 8,9
    ]));

julia> σ = SparseArrays.sparse([0; 0; 0; 1; 1; 1; 0; 0; 0]);

todel, V, copEV = Lara.cleandecomposition(V, copEV, convert(Lar.Chain, σ))

Plasm.view(convert(Lar.Points, V'), Lar.cop2lar(copEV));
```
"""
function cleandecomposition(V::Lar.Points, copEV::Lar.ChainOp, sigma::Lar.Chain)
    # Deletes edges outside sigma area
	todel = []
	new_edges = []
	map(i->new_edges=union(new_edges, edge_map[i]), sigma.nzind) # ???
	ev = copEV[new_edges, :]
	for e in 1:copEV.m
		if !(e in new_edges)  # ???  remove if ???

			vidxs = copEV[e, :].nzind
			v1, v2 = map(i->V[vidxs[i], :], [1,2])
			centroid = .5*(v1 + v2)

			if ! Lar.point_in_face(centroid, V, ev)
				push!(todel, e)
			end
		end
	end
	for i in reverse(todel)
		for row in edge_map

			filter!(x->x!=i, row)

			for j in 1:length(row)
				if row[j] > i
					row[j] -= 1
				end
			end
		end
	end
	V, copEV = Lar.delete_edges(todel, V, copEV)

    # biconnected components
    bicon_comps = Lar.Arrangement.biconnected_components(copEV) # -> arrays of edge indices
    @show bicon_comps,0
    if isempty(bicon_comps)
        println("No biconnected components found.")
        if (return_edge_map)
            return (nothing, nothing, nothing, nothing)
        else
            return (nothing, nothing, nothing)
        end
    end

	#remove dangling edges
    edges = sort(union(bicon_comps...))
    todel = sort(setdiff(collect(1:size(copEV,1)), edges))
    for i in reverse(todel)
        for row in edge_map

            filter!(x->x!=i, row)

            for j in 1:length(row)
                if row[j] > i
                    row[j] -= 1
                end
            end
        end
    end
    return todel, V, copEV
end

#--------------------------------------------------------------------------------------------------------------------------------
#								MAIN PIPELINE
#--------------------------------------------------------------------------------------------------------------------------------

"""
	function planar_arrangement_1(V, copEV[, sigma[, return_edge_map[, multiproc]]])

First part of arrangement's algorithmic pipeline.

This function computes the pairwise intersection between each edge of a given 2D cellular complex 1-skeleton.
The computation is speeded up via the evaluation of the Spatial Index. See [`Lar.spaceindex`](@ref).

See also: [`Lar.planar_arrangement`](@ref) for the complete pipeline.

---

# Examples
```jldoctest
julia> EV = [[1, 2], [3, 4], [1, 3], [2, 4], [5, 6], [7, 8], [5, 7], [6, 8]];

julia> V = [
           0.0 0.5 0.0 0.5 0.3 1.0 0.3 1.0;
           0.0 0.0 1.0 1.0 0.5 0.5 1.0 1.0
       ];

julia> W = convert(Lar.Points, V'); # Infering type for W = V'

julia> cop_EV = Lar.coboundary_0(EV::Lar.Cells);

julia> W1, copEV1 = Lar.planar_arrangement_1(W::Lar.Points, cop_EV::Lar.ChainOp)
([0.0 0.0; 0.5 0.0; … ; 1.0 0.5; 1.0 1.0],
  [1 ,  1]  =  1
  [4 ,  1]  =  1
  [1 ,  2]  =  1
  ⋮
  [11,  8]  =  1
  [9 ,  9]  =  1
  [11,  9]  =  1)

julia> EV1 = Lar.cop2lar(copEV1);

julia> V1 = convert(Lar.Points, W1');

julia> Plasm.view(Plasm.numbering(0.1)((V,[[[k] for k=1:size(V,2)], EV])));

julia> Plasm.view(Plasm.numbering(0.1)((V1,[[[k] for k=1:size(V1,2)], EV1])));
```
"""
function planar_arrangement_1(V::Lar.Points, copEV::Lar.ChainOp,
		sigma::Lar.Chain=SparseArrays.spzeros(Int8, 0),
		return_edge_map::Bool=false,
		multiproc::Bool=false)

	# data structures initialization
	edgenum = size(copEV, 1)
	edge_map = Array{Array{Int, 1}, 1}(undef,edgenum)
	rV = Lar.Points(zeros(0, 2))
	rEV = SparseArrays.spzeros(Int8, 0, 0)
	finalcells_num = 0

	# spaceindex computation
	model = (convert(Lar.Points,V'),Lar.cop2lar(copEV))
	bigPI = Lar.spaceindex(model::Lar.LAR)

	# multiprocessing of edge fragmentation
	if (multiproc == true)
		in_chan = Distributed.RemoteChannel(()->Channel{Int64}(0))
		out_chan = Distributed.RemoteChannel(()->Channel{Tuple}(0))
		ordered_dict = SortedDict{Int64,Tuple}()
		@async begin
			for i in 1:edgenum
				put!(in_chan,i)
			end
			for p in distributed.workers()
				put!(in_chan,-1)
			end
		end
		for p in distributed.workers()
			@async Base.remote_do(frag_edge_channel, p, in_chan, out_chan, V, copEV, bigPI)
		end
		for i in 1:edgenum
			frag_done_job = take!(out_chan)
			ordered_dict[frag_done_job[1]] = frag_done_job[2]
		end
		for (dkey, dval) in ordered_dict
			i = dkey
			v, ev = dval
			newedges_nums = map(x->x+finalcells_num, collect(1:size(ev, 1)))
			edge_map[i] = newedges_nums
			finalcells_num += size(ev, 1)
			rV, rEV = Lar.skel_merge(rV, rEV, v, ev)
		end
	else
	# sequential (iterative) processing of edge fragmentation
		for i in 1:edgenum
			v, ev = Lar.Arrangement.frag_edge(V, copEV, i, bigPI)
			newedges_nums = map(x->x+finalcells_num, collect(1:size(ev, 1)))
			edge_map[i] = newedges_nums
			finalcells_num += size(ev, 1)
			rV, rEV = Lar.skel_merge(rV, rEV, v, ev) # block diagonal ...
		end
	end
	# merging of close vertices and edges (2D congruence)
	V, copEV = rV, rEV
    if return_edge_map
        V, copEV, edge_map = Lar.Arrangement.merge_vertices!(V, copEV, edge_map)
        return V, copEV, edge_map
    end
	V, copEV = Lar.Arrangement.merge_vertices!(V, copEV, edge_map)
	return V, copEV
end

"""
	function planar_arrangement_2(V, copEV, bicon_comps
		[, sigma[, return_edge_map[, multiproc]]]
	)

Second part of arrangement's algorithmic pipeline.

This function is the complete Topological Gift Wrapping (TGW) algorithm that is firstly
locally used in order to decompose the 2-cells and then globally to generate the 3-cells
of the arrangement of the ambient space ``E^3``.

During this process each dangling 2-cell is removed.
Do note that the isolated 1-cells are not removed by this procedure.

See also: [`Lar.planar_arrangement`](@ref) for the complete pipeline.

---

# Examples
```jldoctest
# Triforce
julia> V = [0.0 0.0; 2.0 0.0; 4.0 0.0; 1.0 1.5; 3.0 1.5; 2.0 3.0; 3.0 3.0];
julia> W = convert(Lar.Points, V');
julia> EV = SparseArrays.sparse(Array{Int8, 2}([
    [1 1 0 0 0 0 0] #1 -> 1,2
    [0 1 1 0 0 0 0] #2 -> 2,3
    [1 0 0 1 0 0 0] #3 -> 1,4
    [0 0 0 1 0 1 0] #4 -> 4,6
    [0 0 1 0 1 0 0] #5 -> 3,5
    [0 0 0 0 1 1 0] #6 -> 5,6
    [0 1 0 1 0 0 0] #7 -> 2,4
    [0 1 0 0 1 0 0] #8 -> 2,5
    [0 0 0 1 1 0 0] #9 -> 4,5
    [0 0 0 0 0 1 1]
    ]));

julia> Plasm.view(Plasm.numbering(0.5)(
		(W,[[[k] for k=1:size(W,2)], Lar.cop2lar(EV)])
	));

julia> bicon_comps = Lar.Arrangement.biconnected_components(EV);

julia> V, EV, FE = Lar.Arrangement.planar_arrangement_2(V, EV, bicon_comps);

julia> Plasm.view( Plasm.numbering1(0.5)((V, EV, FE)) );
```
"""
function planar_arrangement_2(V::Lar.Points, copEV::Lar.ChainOp, bicon_comps::Array{Array{Int, 1}, 1},
		sigma::Lar.Chain=spzeros(Int8, 0),
		return_edge_map::Bool=false,
		multiproc::Bool=false)

	# Topological Gift Wrapping
	n, containment_graph, V, EVs, boundaries, shells, shell_bboxes =
		Lar.Arrangement.componentgraph(V, copEV, bicon_comps)
	@show containment_graph
	# only in the context of 3D arrangement
	if sigma.n > 0 ## number of columns of sigma > 0
		todel, V, copEV = Lar.Arrangement.cleandecomposition(V, copEV, sigma)
		V, copEV = Lar.delete_edges(todel, V, copEV) # <----------------------non mi è chiaro perchè non è dentro la fun
	end
	# final shell poset aggregation and FE output
	copEV, FE = Lar.Arrangement.cell_merging(
		n, containment_graph, V, EVs, boundaries, shells, shell_bboxes)
	if (return_edge_map)
		return V, copEV, FE, edge_map
	end
	return V, copEV, FE
end

"""
	planar_arrangement(V::Points, copEV::ChainOp
		[, sigma::Chain[, return_edge_map::Bool[, multiproc::Bool]]]
	)

Compute the arrangement on the given cellular complex 1-skeleton in 2D.
Whole arrangement's algorithmic pipeline.

A cellular complex is arranged when the intersection of every possible pair of cell
of the complex is empty and the union of all the cells is the whole Euclidean space.
The method with no additional arguments specified gives back the full arranged complex
`V`, `EV` and `FE`.

See also:
  - [`Lar.Arrangement.planar_arrangement_1`](@ref)
  - [`Lar.Arrangement.biconnected_components`](@ref)
  - [`Lar.Arrangement.planar_arrangement_2`](@ref)

## Additional arguments:
  - `sigma::Chain`: if specified, `planar_arrangement` will delete from
    the output every edge and face outside this cell. Defaults to an empty cell.
  - `return_edge_map::Bool`: makes the function return also an `edge_map` which
    maps the edges of the imput to the one of the output. Defaults to `false`.
  - `multiproc::Bool`: Runs the computation in parallel mode. Defaults to `false`.

Many examples could be found in Lar Documentation and `examples` directory.
"""
function planar_arrangement( V::Lar.Points, copEV::Lar.ChainOp,
		sigma::Lar.Chain=spzeros(Int8, 0),
		return_edge_map::Bool=false,
		multiproc::Bool=false)

	# edge subdivision
	V, copEV = Lar.Arrangement.planar_arrangement_1(V::Lar.Points, copEV::Lar.ChainOp)
	# biconnected components
	bicon_comps = Lar.Arrangement.biconnected_components(copEV::Lar.ChainOp)
	# 2-complex and containment graph
	V, copEV, copFE = Lar.Arrangement.planar_arrangement_2(V::Lar.Points, copEV::Lar.ChainOp, bicon_comps)
	return V, copEV, copFE
end


"""
    internal_M(time, mu, timesteps)

Return `(numberOfSteps, kout)` where `numberOfSteps` is the required number of
internal simulation steps so that the probability of having a noise jump in a
timestep is `jumpProb`. `kout` tells how many internal time steps are there
between timesteps with output.

`timesteps` = number of output steps.
"""
function internal_M(time, mu, timesteps)
    temp=convert(Int64,cld(time,jumpProb/mu )+1) # minimum number of internal steps

    if time/(temp-1)>0.1
        temp=ceil(Int,time/0.1+1)
    end
    if temp<=timesteps                            # if temp<timesteps then I simply use the number of output steps
        kout=1
        return timesteps, kout
    else

    # otherwise we choose the smallest multiple of (timesteps-1) bigger than the required number of internal steps -1 :
    # numberOfSteps=kout*(timesteps-1)+1

        kout=cld((temp-1),(timesteps-1))
        # in finding kout we have to use (temp-1) and (timesteps-1) because the time step=1 is fixed for both
        # internal and output steps.

        return ((timesteps-1)*kout+1),kout

    end
end

internal_dt(time,mu,timesteps)=time/(internal_M(time,mu,timesteps)[1]-1) # unit of time given the number of internal steps

rescaledMu(time,mu,timesteps)=dt(time,mu,timesteps)*mu

"""
    psi = superposition_state(N::Integer)

Return the superposition state for the `N`-dimensional Hilbert space. `psi` is
a `Array{Complex{Float64}}`.

```jldoctest
julia> superposition_state(4)
4-element Array{Complex{Float64},1}:
 0.5 + 0.0im
 0.5 + 0.0im
 0.5 + 0.0im
 0.5 + 0.0im
```
"""
function superposition_state(N::Integer)
    return ones(ComplexF64, N) ./ sqrt(N)
end