function _check_dimension(v::AbstractVector, s)
    length(v) != MOI.dimension(s) && throw(DimensionMismatch("Mismatch between value and set"))
    return nothing
end

function distance_to_set(::DefaultDistance, v::AbstractVector{T}, s::MOI.Reals) where {T <: Real}
    _check_dimension(v, s)
    return zero(T)
end

function distance_to_set(::NormedEpigraphDistance{p}, v::AbstractVector{<:Real}, s::MOI.Zeros) where {p}
    _check_dimension(v, s)
    return LinearAlgebra.norm(v, p)
end

function distance_to_set(::DefaultDistance, v::AbstractVector{<:Real}, s::MOI.Zeros)
    return distance_to_set(NormedEpigraphDistance{2}(), v, s)
end

function distance_to_set(::NormedEpigraphDistance{p}, v::AbstractVector{<:Real}, s::MOI.Nonnegatives) where {p}
    _check_dimension(v, s)
    return LinearAlgebra.norm((ifelse(vi < 0, -vi, zero(vi)) for vi in v), p)
end

function distance_to_set(::DefaultDistance, v::AbstractVector{<:Real}, s::MOI.Nonnegatives)
    return distance_to_set(NormedEpigraphDistance{2}(), v, s)
end

function distance_to_set(::NormedEpigraphDistance{p}, v::AbstractVector{<:Real}, s::MOI.Nonpositives) where {p}
    _check_dimension(v, s)
    return LinearAlgebra.norm((ifelse(vi > 0, vi, zero(vi)) for vi in v), p)
end

function distance_to_set(::DefaultDistance, v::AbstractVector{<:Real}, s::MOI.Nonpositives)
    _check_dimension(v, s)
    return distance_to_set(NormedEpigraphDistance{2}(), v, s)
end

distance_to_set(::EpigraphViolationDistance, v::Real, s::MOI.LessThan) = max(v - s.upper, zero(v))
distance_to_set(::EpigraphViolationDistance, v::Real, s::MOI.GreaterThan) = max(s.lower - v, zero(v))
distance_to_set(::EpigraphViolationDistance, v::Real, s::MOI.EqualTo) = abs(v - s.value)

distance_to_set(::DefaultDistance, v, s::Union{MOI.LessThan, MOI.GreaterThan, MOI.EqualTo}) = distance_to_set(EpigraphViolationDistance(), v, s)

distance_to_set(::DefaultDistance, v::T, s::MOI.Interval) where {T <: Real} = max(s.lower - v, v - s.upper, zero(T))

function distance_to_set(::EpigraphViolationDistance, v::AbstractVector{<:Real}, s::MOI.NormInfinityCone)
    _check_dimension(v, s)
    t = v[1]
    xs = @view(v[2:end])
    result = maximum(abs, xs) - t
    return max(result, zero(result))
end

function distance_to_set(::EpigraphViolationDistance, v::AbstractVector{<:Real}, s::MOI.NormOneCone)
    _check_dimension(v, s)
    t = v[1]
    xs = @view(v[2:end])
    result = sum(abs, xs) - t
    return max(result, zero(result))
end

function distance_to_set(::EpigraphViolationDistance, v::AbstractVector{<:Real}, s::MOI.SecondOrderCone)
    _check_dimension(v, s)
    t = v[1]
    xs = @view(v[2:end])
    result = LinearAlgebra.norm2(xs) - t
    return max(result, zero(result))
end

"""
    distance_to_set(::NormedEpigraphDistance{p}, v::AbstractVector{<:Real}, s::MOI.SecondOrderCone)

Composition of the projection with the p-norm.
"""
function distance_to_set(::NormedEpigraphDistance{p}, v::AbstractVector{<:Real}, s::MOI.SecondOrderCone) where {p}
    _check_dimension(v, s)
    t = v[1]
    xs = @view(v[2:end])
    nx = LinearAlgebra.norm(xs, p)
    if nx <= t
        return zero(eltype(nx))
    end
    if nx <= -t
        return sqrt(t^2 + nx^2)
    end
    return (nx - t) / √2
end

function distance_to_set(::DefaultDistance, v, s::MOI.SecondOrderCone)
    return distance_to_set(NormedEpigraphDistance{2}(), v, s)
end

function distance_to_set(::DefaultDistance, v, s::Union{MOI.NormInfinityCone, MOI.NormOneCone})
    return distance_to_set(EpigraphViolationDistance(), v, s)
end

function distance_to_set(::NormedEpigraphDistance{p}, v::AbstractVector{<:Real}, s::MOI.RotatedSecondOrderCone) where {p}
    _check_dimension(v, s)
    t = v[1]
    u = v[2]
    xs = @view(v[3:end])
    return LinearAlgebra.norm(
        (max(-t, zero(t)), max(-u, zero(u)), max(LinearAlgebra.dot(xs,xs) - 2 * t * u)),
        p,
    )
end

function distance_to_set(::DefaultDistance, v::AbstractVector{<:Real}, s::MOI.RotatedSecondOrderCone)
    return distance_to_set(NormedEpigraphDistance{2}(), v, s)
end

function distance_to_set(::DefaultDistance, v::AbstractVector{<:Real}, s::MOI.GeometricMeanCone)
    _check_dimension(v, s)
    t = v[1]
    xs = @view(v[2:end])
    n = MOI.dimension(s) - 1
    xresult = LinearAlgebra.norm2(
        max.(-xs, zero(eltype(xs)))
    )
    # early returning if there exists x[i] < 0 to avoid complex sqrt
    if xresult > 0
        return xresult
    end
    result = t - prod(xs)^(inv(n))
    return max(result, zero(result))
end

function distance_to_set(::NormedEpigraphDistance{p}, v::AbstractVector{<:Real}, s::MOI.ExponentialCone) where {p}
    _check_dimension(v, s)
    x = v[1]
    y = v[2]
    z = v[3]

    if x <= 0 && isapprox(y, 0, atol=1e-10) && z >= 0
        return zero(eltype(v))
    end

    result = y * exp(x/y) - z
    return LinearAlgebra.norm(
        (max(-y, zero(result)), max(result, zero(result))),
        p,
    )
end

function distance_to_set(::DefaultDistance, v::AbstractVector{<:Real}, s::MOI.ExponentialCone)
    return distance_to_set(NormedEpigraphDistance{2}(), v, s)
end

function distance_to_set(::NormedEpigraphDistance{p}, vs::AbstractVector{<:Real}, s::MOI.DualExponentialCone) where {p}
    _check_dimension(vs, s)
    u = vs[1]
    v = vs[2]
    w = vs[3]

    if isapprox(u, 0, atol=1e-10) && v >= 0 && w >= 0
        return zero(eltype(vs))
    end

    result = -u*exp(v/u) - ℯ * w
    return LinearAlgebra.norm(
        (max(u, zero(result)), max(result, zero(result))),
        p,
    )
end

function distance_to_set(::DefaultDistance, vs::AbstractVector{<:Real}, s::MOI.DualExponentialCone)
    return distance_to_set(NormedEpigraphDistance{2}(), vs, s)
end

function distance_to_set(::DefaultDistance, v::AbstractVector{<:Real}, s::MOI.PowerCone)
    _check_dimension(v, s)
    x = v[1]
    y = v[2]
    z = v[3]
    e = s.exponent
    # early return to avoid complex exponent results
    if x < 0 || y < 0
       return LinearAlgebra.norm2(
            (max(-x, zero(x)), max(-y, zero(x)))
        )
    end
    result = abs(z) - x^e * y^(1-e)
    return max(result, zero(result))
end

function distance_to_set(::NormedEpigraphDistance{p}, vs::AbstractVector{<:Real}, s::MOI.DualPowerCone) where {p}
    _check_dimension(vs, s)
    u = vs[1]
    v = vs[2]
    w = vs[3]
    e = s.exponent
    ce = 1-e
    result = abs(w) - (u/e)^e * (v/ce)^ce
    return LinearAlgebra.norm(
        (max(-u, zero(result)), max(-v, zero(result)), max(result, zero(result))),
        p,
    )
end

function distance_to_set(::DefaultDistance, vs::AbstractVector{<:Real}, s::MOI.DualPowerCone)
    return distance_to_set(NormedEpigraphDistance{2}(), vs, s)
end

function distance_to_set(::NormedEpigraphDistance{p}, v::AbstractVector{<:Real}, set::MOI.RelativeEntropyCone) where {p}
    _check_dimension(v, s)
    n = (dimension(set)-1) ÷ 2
    u = v[1]
    v = @view(v[2:(n+1)])
    w = @view(v[(n+2):end])
    s = sum(w[i] * log(w[i]/v[i]) for i in eachindex(w))
    result = s - u
    return LinearAlgebra.norm(
        push!(
            max.(@view(v[2:end]), zero(result)),
            max(result, zero(result)),
        ),
        p,
    )
end

function distance_to_set(::DefaultDistance, v::AbstractVector{<:Real}, set::MOI.RelativeEntropyCone)
    return distance_to_set(NormedEpigraphDistance{2}(), v, set)
end

function distance_to_set(::EpigraphViolationDistance, v::AbstractVector{<:Real}, s::MOI.NormSpectralCone)
    _check_dimension(v, s)
    t = v[1]
    m = reshape(@view(v[2:end]), (s.row_dim, s.column_dim))
    s1 = LinearAlgebra.svd(m).S[1]
    result = s1 - t
    return max(result, zero(result))
end

function distance_to_set(::EpigraphViolationDistance, v::AbstractVector{<:Real}, s::MOI.NormNuclearCone)
    _check_dimension(v, s)
    t = v[1]
    m = reshape(@view(v[2:end]), (s.row_dim, s.column_dim))
    s1 = sum(LinearAlgebra.svd(m).S)
    result = s1 - t
    return max(result, zero(result))
end

function distance_to_set(::DefaultDistance, v, s::Union{MOI.NormSpectralCone, MOI.NormNuclearCone})
    return distance_to_set(EpigraphViolationDistance(), v, s)
end

function distance_to_set(::DefaultDistance, v::T, ::MOI.ZeroOne) where {T <: Real}
    return min(abs(v - zero(T)), abs(v - one(T)))
end

function distance_to_set(::DefaultDistance, v::Real, ::MOI.Integer)
    return min(abs(v - floor(v)), abs(v - ceil(v)))
end

# return the element-wise distance to zero, with the greatest element to 0
function distance_to_set(::DefaultDistance, v::AbstractVector{T}, ::MOI.SOS1) where {T <: Real}
    _check_dimension(v, s)
    _, i = findmax(abs.(v))
    return LinearAlgebra.norm2([v[j] for j in eachindex(v) if j != i])
end

# takes in input [z, f(x)]
function distance_to_set(::DefaultDistance, v::AbstractVector{T}, s::MOI.IndicatorSet{A}) where {A, T <: Real}
    _check_dimension(v, s)
    z = v[1]
    # inactive constraint
    if A === MOI.ACTIVATE_ON_ONE && isapprox(z, 0) || A === MOI.ACTIVATE_ON_ZERO && isapprox(z, 1)
        return zeros(T, 2)
    end
    return LinearAlgebra.norm2(
        (distance_to_set(d, z, MOI.ZeroOne()), distance_to_set(v[2], s.set))
    )
end

function distance_to_set(::NormedEpigraphDistance{p}, v::AbstractVector{T}, s::MOI.PositiveSemidefiniteConeTriangle) where {p, T <: Real}
    X = unvec_symm(v, s.side_dimension)
    λ, U = LinearAlgebra.eigen(X)
    Tp = eltype(λ)
    λm = -min.(λ, zero(Tp))
    vdist = vec_symm(U * LinearAlgebra.Diagonal(λm) * U')
    return LinearAlgebra.norm(vdist, p)
end

function distance_to_set(::DefaultDistance, v::AbstractVector{T}, s::MOI.PositiveSemidefiniteConeTriangle) where {T <: Real}
    return distance_to_set(NormedEpigraphDistance{2}(), v, s)
end


begin

    N = 100

    A = rand(N)

    #stack up some functions
    @et res_et = exp(cos(sin(A)))
    res_native = exp(cos(sin(A)))

    @test res_et == res_native
end


using JuliaSetPkg
using Base.Test

x=Array(linspace(-1,1,200))
y=Array(linspace(-1,1,200))
R = z -> (z^2 + -0.4+0.6*im )

#test size
A=juliaSet(R,x,y)
nx,ny=size(A)
@test length(x)==nx
@test length(y)==ny

#test fixed point
R = z -> z
A=juliaSet(R,x,y,100)
@test A == zeros(200,200)+101


# This version is focused in Julia 1.0 version
module Liga
	# Dependencies
	import Base.show
	import Base.reverse
	import Base.convert
	#   import OhMyREPL
	using SparseArrays,LinearAlgebra
	using OhMyREPL
	# New definitions
	export  BasisBlade,MultiVector,
	    GAAbstractType,Blade,
	    layout,layout_info,
	    tree,buildtree,
	    grade,grademinus,
	    gradeplus,gradeprojection,
	    bbgeometric,bbinner,bbouter,
	    geometric,scalar,inner,
	    outer,mvsum,mvdiff,
	    reverse,conjugate,⋅,
	    dual,inverse,OperationTable,
	    embedding,copy,isequal,==,
	    multivector,≈,magnitude,blade

	# Main files
	# Objects.jl produces two objects: BasisBlade and MultiVectors
	include("Objects.jl")
	# Layout.jl produces the enviroment to work. In current version
	# three enviroments are suported: GA, Conformal and Projetive
	include("Layout.jl")
	# OperationalTables.jl produces arrays of BasisBlade geometric,
	# inner and outer products. This is an important function because
	# this is the optimization of engine of Liga
	include("OperationalTables.jl")
	# LigaFunctions.jl contains all implemented functions like MultiVector
	# geometric product.
	include("Functions.jl")
	# LigaShow.jl contains some modifications to print BasisBlade and
	# MultiVectors.
	include("LigaShow.jl")
end


module Irr
    export sum_fracts

    function sum_fracts(l::Array{Array{Int,1},1})
        result = sum( x -> x[1]//x[2], l )
        if denominator(result) == 1 || numerator(result) == 0
            numerator(result)
        else
            [numerator(result),denominator(result)]
        end
    end

    sum_fracts(l::Any) = nothing

end


function calculate_transcription_kinetics(t::Float64, x::Array{Float64,1}, problem::Dict{String,Any})::Float64

    # initailzie -
    transcription_rate = 0.0

    # alias -
    mRNA = x[1]
    G = x[2]
    σ70 = x[3]

    # get parameters from problem dictionary -
    KX = problem["transcription_saturation_constant"]
    tau_X = problem["transcription_time_constant"]
    Vmax_X = problem["maximum_transcription_velocity"]

    # TODO: compute the transcription rate -
    transcription_rate = Vmax_X * (G/((tau_X*KX + (tau_X+1)*G)))

    # return -
    return transcription_rate
end

function calculate_mRNA_degradation_kinetics(t::Float64, x::Array{Float64,1}, problem::Dict{String,Any})::Float64

    # alias -
    mRNA = x[1]
    G = x[2]
    σ70 = x[3]

    # get the mRNA degradation constant -
    mRNA_degradation_constant = problem["mRNA_degradation_constant"]

    # return -
    return (mRNA_degradation_constant*mRNA)
end


#############################################
###MatrixEquivalent of the trace operation###
#############################################
"""
    buildTraceMatrixRight(maxLen; typed=Float64)

Create the matrix that takes the trace on the right-most site of a DM of length maxLen
typed is the type of the data store in the matrix: for rational arythmetic, choose SymPy.Sym
"""
function buildTraceMatrixRight(maxLen; typed=Float64)
    trmat=spzeros(typed, 2^(2*maxLen), 2^(2*maxLen+2))
    for x in 1:2^maxLen
        for y=1:2^maxLen
            trmat[x+(y-1)*2^maxLen, x+(y-1)*2^(maxLen+1)]=1
            trmat[x+(y-1)*2^maxLen, (x+2^maxLen)+(y-1+2^maxLen)*2^(maxLen+1)]=1
        end
    end
    return trmat
end
"""
    buildTraceMatrixLeft(maxLen; typed=Float64)

create the matrix that takes the trace on the left-most site of a DM of length maxLen
typed is the type of the data store in the matrix: for rational arythmetic, choose SymPy.Sym
"""
function buildTraceMatrixLeft(maxLen; typed=Float64)
    trmat=spzeros(typed, 2^(2*maxLen), 2^(2*maxLen+2))
    for x in 1:2^maxLen
        for y=1:2^maxLen
            trmat[x+(y-1)*2^maxLen, 2*(x-1)+2*(y-1)*2^(maxLen+1)+1]=1
            trmat[x+(y-1)*2^maxLen, 1+2*(x-1)+(2*(y-1)+1)*2^(maxLen+1)+1]=1
        end
    end
    return trmat
end

"""
    buildTraceMatrix(maxLen)

create the matrix that takes the trace of a DM of length maxLen
"""
function buildTraceMatrix(maxLen)
    trmat=spzeros(1, 2^(2*maxLen+2))
    for x in 1:2^(maxLen+1)
        trmat[x+2^(maxLen+1)*(x-1)]=1
    end
    return trmat
end

"""
    initializeTraceMatrices(maxLen)

Build a complete table of Traces
Traces[i, j] takes (i-1) the trace on the left, and (j-1) the trace on the right
"""
function initializeTraceMatrices(maxLen)
    TrLeft=buildTraceMatrixLeft(2);
    #global const pseudoIdentity=sparse(triming.(MatJoin*invMatJoin));
    Traces=Array{typeof(TrLeft)}(maxLen, maxLen)
    Traces[1, 1]=speye(2^(2*maxLen), 2^(2*maxLen));
    for j=2:maxLen
        Traces[j, 1]=transpose(buildTraceMatrixLeft(maxLen-j+1)*transpose(Traces[j-1, 1]))
    end
    for j=1:maxLen-1
        for k=2:maxLen+1-j
            Traces[j, k]=transpose(buildTraceMatrixRight(maxLen-j-k+2)*transpose(Traces[j, k-1]))
        end
    end
    return Traces
end
##########################################
###Building and reducing density matrix###
##########################################
"""
    buildReducedDensityMatrix(rho, site, Traces)

Compute the single-site density matrix of rho on site site.
Convention site=0 is the leftmost site.
Traces is the set of Trace matrices previously computed
"""
@inline function buildReducedDensityMatrix(rho, site, Traces)
    return transpose(At_mul_B(rho, Traces[1+site, end-site]))
end

"""
    buildReducedDensityMatrix(rho, site, nb, Traces)

Compute the reduced density matrix of rho on sites site:site+nb-1.
Convention site=0 is the leftmost site.
Traces is the set of Trace matrices previously computed
"""
@inline function buildReducedDensityMatrix(rho, site, nb, Traces)    #Local densityMatrix for sites site:site+nb-1
    len=getLength(length(rho));
    if len==nb
        return rho
    end
    if nb==1
        return buildReducedDensityMatrix(rho, site, Traces)
    end
    return transpose(At_mul_B(rho, Traces[1+site, len-nb-site+1]))
end

"""
    reduceDensityMatrixLeft(rho, Traces)

Takes the left trace of rho
Traces is the set of Trace matrices previously computed
"""
@inline function reduceDensityMatrixLeft(rho, Traces)
    return transpose(At_mul_B(rho, Traces[2, 1]))
end

"""
    reduceDensityMatrixLeft!(redrho, rho, Traces)

Takes the left trace of rho to redrho
Traces is the set of Trace matrices previously computed
"""
@inline function reduceDensityMatrixLeft!(redrho, rho, Traces)    #Local densityMatrix for site site
    redrho.=transpose(At_mul_B(rho, Traces[2, 1]))
end

"""
    reduceDensityMatrixRight(rho, Traces)

Takes the right trace of rho
Traces is the set of Trace matrices previously computed
"""
@inline function reduceDensityMatrixRight(rho, Traces)
    return transpose(At_mul_B(rho, Traces[1, 2]))
end

"""
    reduceDensityMatrixRight!(redrho, rho, Traces)

Takes the right trace of rho to redrho
Traces is the set of Trace matrices previously computed
"""
@inline function reduceDensityMatrixRight!(redrho, rho, Traces)   #Local densityMatrix for site site
    redrho.=transpose(At_mul_B(rho, Traces[1, 2]))
end

####################
###Reimplementation of kron###
####################
"""
    leftFusionIdentity(rho)

Compute I2⊗rho in vector form
Note that it would correspond to kron(rho, I2) for matrices
"""
function leftFusionIdentity(rho)
        len=getLength(length(rho));
        stepp=2^len
        extrho=zeros(Complex128, 2^(2*len+2))
        offset=0; offset2=0
        for j=1:stepp
            for k=1:stepp
                @inbounds extrho[offset2+2*k-1]=0.5*rho[offset+k]
                @inbounds extrho[offset2+2*stepp+2*k]=0.5*rho[offset+k]
            end
            offset+=stepp
            offset2+=4*stepp
        end
        return extrho
end

"""
    leftFusionIdentity(extrho, rho)

Compute extrho=I2⊗rho in vector form
Note that it would correspond to kron(rho, I2) for matrices
"""
function leftFusionIdentity!(extrho, rho)
        len=getLength(length(rho));
        @simd for i =1:length(extrho)
            @inbounds extrho[i]=zero(Complex128)
        end
        stepp=2^len
        offset=0; offset2=0
        for j=1:stepp
            for k=1:stepp
                @inbounds extrho[offset2+2*k-1]=0.5*rho[offset+k]
                @inbounds extrho[offset2+2*stepp+2*k]=0.5*rho[offset+k]
            end
            offset+=stepp
            offset2+=4*stepp
        end
end

"""
    leftFusion(smallrho, rho)

Compute smallrho⊗rho in vector form
Note that it would correspond to kron(rho, smallrho) for matrices
"""
function leftFusion(smallrho, rho)
        len=getLength(length(rho));
        extrho=Array{Complex128}(2^(2*len+2))
        stepp=2^len
        offset=0; offset2=0
        for j=1:stepp
            for k=1:stepp
                @inbounds extrho[offset2+2*k-1]=smallrho[1]*rho[offset+k]
                @inbounds extrho[offset2+2*k]=smallrho[2]*rho[offset+k]
                @inbounds extrho[offset2+2*stepp+2*k-1]=smallrho[3]*rho[offset+k]
                @inbounds extrho[offset2+2*stepp+2*k]=smallrho[4]*rho[offset+k]
            end
            offset+=stepp
            offset2+=4*stepp
        end
        return extrho
end

"""
    leftFusion!(extrho, smallrho, rho)

Compute extrho=smallrho⊗rho in vector form
Note that it would correspond to kron(rho, smallrho) for matrices
"""
function leftFusion!(extrho, smallrho, rho)
        len=getLength(length(rho));
        stepp=2^len
        offset=0; offset2=0
        for j=1:stepp
            for k=1:stepp
                @inbounds extrho[offset2+2*k-1]=smallrho[1]*rho[offset+k]
                @inbounds extrho[offset2+2*k]=smallrho[2]*rho[offset+k]
                @inbounds extrho[offset2+2*stepp+2*k-1]=smallrho[3]*rho[offset+k]
                @inbounds extrho[offset2+2*stepp+2*k]=smallrho[4]*rho[offset+k]
            end
            offset+=stepp
            offset2+=4*stepp
        end
end

"""
    rightFusionIdentity(rho)

Compute rho⊗I2 in vector form
Note that it would correspond to kron(I2, rho) for matrices
"""
function rightFusionIdentity(rho)
        len=getLength(length(rho));
        stepp=2^len; temp=2^(2*len+1)+stepp
        extrho=zeros(Complex128, 2^(2*len+2))
        offset=0; offset2=0
        for j=1:stepp
            for k=1:stepp
                @inbounds extrho[offset2+k]=0.5*rho[offset+k]
                @inbounds extrho[offset2+k+temp]=0.5*rho[offset+k]
            end
            offset+=stepp
            offset2+=2*stepp
        end
        return extrho
end

"""
    rightFusionIdentity!(extrho, rho)

Compute extrho=rho⊗I2 in vector form
Note that it would correspond to kron(I2, rho) for matrices
"""
function rightFusionIdentity!(extrho, rho)
        len=getLength(length(rho));
        stepp=2^len; temp=2^(2*len+1)+stepp
        @simd for i=1:length(extrho)
            @inbounds extrho[i]=zero(Complex128)
        end
        offset=0; offset2=0
        for j=1:stepp
            for k=1:stepp
                @inbounds extrho[offset2+k]=0.5*rho[offset+k]
                @inbounds extrho[offset2+k+temp]=0.5*rho[offset+k]
            end
            offset+=stepp
            offset2+=2*stepp
        end
end

"""
    rightFusion(rho, smallrho)

Compute rho⊗smallrho in vector form
Note that it would correspond to kron(smallrho, rho) for matrices
"""
function rightFusion(rho, smallrho)
    len=getLength(length(rho));
    stepp=2^len; temp=2^(2*len+1)
    extrho=Array{Complex128}(2^(2*len+2))
    offset=0; offset2=0
    for j=1:stepp
        for k=1:stepp
            @inbounds extrho[offset2+k]=smallrho[1]*rho[offset+k]
            @inbounds extrho[offset2+k+stepp]=smallrho[2]*rho[offset+k]
            @inbounds extrho[offset2+k+temp]=smallrho[3]*rho[offset+k]
            @inbounds extrho[offset2+k+temp+stepp]=smallrho[4]*rho[offset+k]
        end
        offset+=stepp
        offset2+=2*stepp
    end
    return extrho
end

"""
    rightFusion!(extrho, rho, smallrho);

Compute extrho=rho⊗smallrho in vector form
Note that it would correspond to kron(smallrho, rho) for matrices
"""
function rightFusion!(extrho, rho, smallrho)
    len=getLength(length(rho));
    stepp=2^len; temp=2^(2*len+1)
    offset=0; offset2=0
    for j=1:stepp
        for k=1:stepp
            @inbounds extrho[offset2+k]=smallrho[1]*rho[offset+k]
            @inbounds extrho[offset2+k+stepp]=smallrho[2]*rho[offset+k]
            @inbounds extrho[offset2+k+temp]=smallrho[3]*rho[offset+k]
            @inbounds extrho[offset2+k+temp+stepp]=smallrho[4]*rho[offset+k]
        end
        offset+=stepp
        offset2+=2*stepp
    end
end

########################################################################################
###Build from an operator acting on a DM the corresponding superoperator for a vector###
########################################################################################
"""
    buildSuperOperatorLeft(operator)

Return the sparse matrix representing operator acting on the left on a DM written as a vector
"""
function buildSuperOperatorLeft(operator)
    return kron(speye(typeof(operator[1]), size(operator)[1]), operator)''
end

"""
    buildSuperOperatorRight(operator)

Return the sparse matrix representing operator acting on the right on a DM written as a vector
"""
function buildSuperOperatorRight(operator)
    return kron(transpose(operator), speye(typeof(operator[1]), size(operator)[1]))''
end

"""
    buildSuperCommutator(operator)

Return the sparse matrix representing [operator, ρ] for ρ written as a vector
"""
function buildSuperCommutator(operator)
    return (buildSuperOperatorLeft(operator)-buildSuperOperatorRight(operator))''
end


using ITensors

"""
    trg(T::ITensor; χmax::Int, nsteps::Int) -> κ, T

Perform the TRG algorithm on the partition function composed of the ITensor T.

The indices of T must obey come in pairs `(sₕ => sₕ')` and  `(sᵥ => sᵥ').

χmax is the maximum renormalized bond dimension.

nsteps are the number of renormalization steps performed.

The outputs are κ, the partition function per site, and the final renormalized
ITensor T.
"""
function trg(T::ITensor;
             χmax::Int,
             nsteps::Int,
             cutoff = 0.0,
             svd_alg = "divide_and_conquer")
  sₕ, sᵥ = filterinds(T; plev = 0)
  @assert hassameinds((sₕ, sₕ', sᵥ, sᵥ'), T)

  # Keep track of the partition function per site
  κ = 1.0
  for n in 1:nsteps
    Fₕ, Fₕ′ = factorize(T, (sₕ', sᵥ');
                       ortho = "none",
                       maxdim = χmax,
                       cutoff = cutoff,
                       tags = tags(sₕ),
                       svd_alg = svd_alg)

    s̃ₕ = commonind(Fₕ, Fₕ′)
    Fₕ′ *= δ(dag(s̃ₕ), s̃ₕ')

    Fᵥ, Fᵥ′ = factorize(T, (sₕ, sᵥ');
                       ortho = "none",
                       maxdim = χmax,
                       cutoff = cutoff,
                       tags = tags(sᵥ),
                       svd_alg = svd_alg)

    s̃ᵥ = commonind(Fᵥ, Fᵥ′)
    Fᵥ′ *=  δ(dag(s̃ᵥ), s̃ᵥ')

    T = (Fₕ * δ(dag(sₕ'), sₕ)) *
        (Fᵥ * δ(dag(sᵥ'), sᵥ)) *
        (Fₕ′ * δ(dag(sₕ), sₕ')) *
        (Fᵥ′ * δ(dag(sᵥ), sᵥ'))

    sₕ, sᵥ = s̃ₕ, s̃ᵥ

    trT = abs((T * δ(sₕ, sₕ') * δ(sᵥ, sᵥ'))[])
    T = T / trT
    κ *= trT ^ (1 / 2 ^ n)
  end
  return κ, T
end


# Subspace iterations

function sub_iterations(X,
                        Y,
                        q::Int)

    for i = 1:q
          Y = X * qr(X' * qr(Y).Q).Q
    end

    return Y
end

function sub_iterations!(X,
                         Y,
                         q::Int)

    for i = 1:q
          Y = X * qr(X' * qr(Y).Q).Q
    end
end


"""
A complete statistical model for one river, with three-stage modelling: yearly average, seasonal averages, daily averages.
"""
immutable Adam2015RiverStatisticalModel
  year::GeneralizedExtremeValue # Annual average; Weibull when xi = -1.
  season_wet::GeneralizedExtremeValue # Difference between seasonal and annual averages, normalised wrt annual average.
  season_dry::GeneralizedExtremeValue
  daily_wet::GeneralizedExtremeValue
  daily_dry::GeneralizedExtremeValue

  seasons_separator::SeasonsSeparator
end

getYearlyAverage(m::Adam2015RiverStatisticalModel) = m.year
function getSeasonAverageDelta(m::Adam2015RiverStatisticalModel, season::Symbol)
  if season == :Wet
    return m.season_wet
  elseif season == :Dry
    return m.season_dry
  else
    error("Unknown season: " * string(season))
  end
end
function getSeasonDailyDelta(m::Adam2015RiverStatisticalModel, season::Symbol)
  if season == :Wet
    return m.daily_wet
  elseif season == :Dry
    return m.daily_dry
  else
    error("Unknown season: " * string(season))
  end
end
getSeasonSeparator(m::Adam2015RiverStatisticalModel) = m.seasons_separator

"""
Based on the data contained in `TS` (one time series per year!), fit a statistical model.

  * `TS`: array of time series (one series per year) to fit the statistical model on.
  * `verbosity`: level of output.
    * `0`: no output.
    * `1`: summary output
    * `2`: detailed output of this algorithm's details
    * `3`: detailed output of this algorithm's and its sub-algorithms' details
    * `4`: including solver output

TODO: implement flooding.
"""
function fit_Adam2015(TS::Array{TimeSeries.TimeArray{Float64, 1, DateTime, Array{Float64, 1}}, 1};
                      seasonsSeparator::SeasonsSeparator=HydrologicalWesternEurope,
                      verbosity::Int=1)
  ## Separate every year in two seasons: dry and wet.
  sep_ts = [separate(ts, seasonsSeparator, guaranteeTimeStep=true) for ts in TS]
  wet_TS = [ts[:Wet] for ts in sep_ts]
  dry_TS = [ts[:Dry] for ts in sep_ts]

  # Compute a few useful averages within those seasons.
  ann_avg = [mean(values(ts)) for ts in TS]
  wet_avg = [mean(values(ts)) for ts in wet_TS]
  dry_avg = [mean(values(ts)) for ts in dry_TS]

  if verbosity >= 1
    println(" == ")
    println(" ==== Statistical model fitting on ", length(TS), " years")

    if verbosity >= 2
      println(" ====== Length of wet season: ", length(wet_TS[1]))
      println(" ====== Length of dry season: ", length(dry_TS[1]))
    end
  end

  ## First deal with annual average.
  if verbosity >= 1
    println(" ==== Fitting a generalised extreme values model on the annual average.")
  end

  d_year_params = fit_mle_optim(ExtremeValueDistributions.GeneralizedExtremeValue, ann_avg, [0.5, 0.5, -1], verbose=(verbosity >= 3))
  d_year = Distributions.GeneralizedExtremeValue(d_year_params[1], exp(d_year_params[2]), d_year_params[3]) # ~ <Q>_Y

  if verbosity >= 2
    println(" ====== Annual average: generalised extreme values.")
    println(" ======    Parameters: μ = ", d_year.μ, "; σ = ", d_year.σ, "; ξ = ", d_year.ξ, ".")
  end

  ## Second with the seasonal variations with respect to the annual averages.
  if verbosity >= 1
    println(" ==== Fitting generalised extreme values models on the seasonal averages.")
  end

  wet_deltas = (wet_avg .- ann_avg) ./ ann_avg
  d_season_wet_params = fit_mle_optim(ExtremeValueDistributions.GeneralizedExtremeValue, wet_deltas, [0.5, 0.5, 0.5], verbose=(verbosity >= 3))
  d_season_wet = Distributions.GeneralizedExtremeValue(d_season_wet_params[1], exp(d_season_wet_params[2]), d_season_wet_params[3]) # ~ \Delta Q_S^W

  if verbosity >= 2
    println(" ====== Wet season average: generalised extreme values.")
    println(" ======    Parameters: μ = ", d_season_wet.μ, "; σ = ", d_season_wet.σ, "; ξ = ", d_season_wet.ξ, ".")
  end

  dry_deltas = (dry_avg .- ann_avg) ./ ann_avg
  d_season_dry_params = fit_mle_optim(ExtremeValueDistributions.GeneralizedExtremeValue, dry_deltas, [0.5, 0.5, 0.5], verbose=(verbosity >= 3))
  d_season_dry = Distributions.GeneralizedExtremeValue(d_season_dry_params[1], exp(d_season_dry_params[2]), d_season_dry_params[3]) # ~ \Delta Q_S^D

  if verbosity >= 2
    println(" ====== Dry season average: generalised extreme values.")
    println(" ======    Parameters: μ = ", d_season_dry.μ, "; σ = ", d_season_dry.σ, "; ξ = ", d_season_dry.ξ, ".")
  end

  ## Finally with the daily variations within each season.
  # Here, working with matrices of values, as each day is considered (and not a single point per year).
  if verbosity >= 1
    println(" ==== Fitting generalised extreme values models on the daily inflow.")
  end

  wet_daily_deltas = vcat([(values(wet_TS[i]) - wet_avg[i]) / wet_avg[i] for i in 1:length(wet_TS)]...)
  d_daily_wet_params = fit_mle_optim(ExtremeValueDistributions.GeneralizedExtremeValue, wet_daily_deltas, [0.5, 0.5, 0.5], verbose=(verbosity >= 3))
  d_daily_wet = Distributions.GeneralizedExtremeValue(d_daily_wet_params[1], exp(d_daily_wet_params[2]), d_daily_wet_params[3]) # ~ \Delta Q_d^W

  if verbosity >= 2
    println(" ====== Daily variations during the wet season: generalised extreme values.")
    println(" ======    Parameters: μ = ", d_daily_wet.μ, "; σ = ", d_daily_wet.σ, "; ξ = ", d_daily_wet.ξ, ".")
  end

  dry_daily_deltas = vcat([(values(dry_TS[i]) - dry_avg[i]) / dry_avg[i] for i in 1:length(dry_TS)]...)
  d_daily_dry_params = fit_mle_optim(ExtremeValueDistributions.GeneralizedExtremeValue, dry_daily_deltas, [0.5, 0.5, 0.5], verbose=(verbosity >= 3))
  d_daily_dry = Distributions.GeneralizedExtremeValue(d_daily_dry_params[1], exp(d_daily_dry_params[2]), d_daily_dry_params[3]) # ~ \Delta Q_d^D

  if verbosity >= 2
    println(" ====== Daily variations during the wet season: generalised extreme values.")
    println(" ======    Parameters: μ = ", d_daily_dry.μ, "; σ = ", d_daily_dry.σ, "; ξ = ", d_daily_dry.ξ, ".")
  end

  ## Done. Prepare the output data structure.
  if verbosity >= 1
    println(" ==== Model fitted. ")
    println(" == ")
  end

  return Adam2015RiverStatisticalModel(d_year, d_season_wet, d_season_dry, d_daily_wet, d_daily_dry, seasonsSeparator)
end

function fit_Adam2015(r::River; kwargs...)
  if haskey(r.ext, "adam2015")
    return r.ext["adam2015"]
  else
    m = fit_Adam2015(r.scenarios; kwargs...)
    r.ext["adam2015"] = m
    return m
  end
end

"""
Prints a report analysing the statistical model with observations made on the raw data.
"""
function printReport(r::River, stat::Adam2015RiverStatisticalModel)
  sc = r.scenarios
  wet = separate(sc, stat.seasons_separator, :Wet)
  dry = separate(sc, stat.seasons_separator, :Dry)
  wet_avg = Float64[mean(values(ts)) for ts in wet]
  dry_avg = Float64[mean(values(ts)) for ts in dry]

  ymean_sc = Float64[mean(values(ts)) for ts in sc]
  wet_smean_sc = (wet_avg .- ymean_sc) ./ ymean_sc
  dry_smean_sc = (dry_avg .- ymean_sc) ./ ymean_sc
  wet_dmean_sc = vcat([(values(wet[i]) .- wet_avg[i]) ./ wet_avg[i] for i in 1:length(wet)]...)
  dry_dmean_sc = vcat([(values(dry[i]) .- dry_avg[i]) ./ dry_avg[i] for i in 1:length(dry)]...)

  # Printing helpers.
  n2s = n -> @sprintf("%7.3f", n)
  function printReport_distr(title::AbstractString, distr::ContinuousUnivariateDistribution, data::Array{Float64, 1})
    # Expected output:
	# 	 ++++ Yearly averages: mean 0.511 vs 0.509
	# 	 ++++              variance 0.014 vs 0.014
	# 	 ++++              kurtosis 0.192 vs 1.689
	#                      ^^^^ (4 characters)
	#         ^^^^^^^^^^^^^^^   length(title), followed by " :"

    title_blank = " " ^ (length(title) - 4)

    println(" ++++ " * title       *     ": mean " * n2s(mean(distr))      * " vs " * n2s(mean(data)))
    println(" ++++ " * title_blank * "  variance " * n2s(var(distr))       * " vs " * n2s(var(data)))
    println(" ++++ " * title_blank * "  skewness " * n2s(skewness(distr))  * " vs " * n2s(skewness(data)))
    println(" ++++ " * title_blank * "  kurtosis " * n2s(kurtosis(distr))  * " vs " * n2s(kurtosis(data)))
  end

  # Validity report.
  println(" ++ Validity report for the statistical model (model vs reality): ")
  printReport_distr("Yearly averages",   stat.year,       ymean_sc)
  printReport_distr("Wet season deltas", stat.season_wet, wet_smean_sc)
  printReport_distr("Dry season deltas", stat.season_dry, dry_smean_sc)
  printReport_distr("Wet daily deltas",  stat.daily_wet,  wet_dmean_sc)
  printReport_distr("Dry daily deltas",  stat.daily_dry,  dry_dmean_sc)

  # Covariance report.
  corr_adj  = c -> (abs(c) < 0.3) ? "weak" : (abs(c) < 0.8) ? "moderate" : "strong"
  corr_sign = c -> (c <= 0.0) ? "negative" : "positive"

  pearson_ymean_wetsmean = cor(ymean_sc, wet_smean_sc)
  pearson_ymean_drysmean = cor(ymean_sc, dry_smean_sc)

  println(" ++ ")
  println(" ++ Covariance report for the statistical model: ")
  println(" ++++ Pearson's coefficient between yearly average and wet season average: " * n2s(pearson_ymean_wetsmean))
  println(" ++++      " * corr_adj(pearson_ymean_wetsmean) * " " * corr_sign(pearson_ymean_wetsmean) * " correlation")
  println(" ++++ Pearson's coefficient between yearly average and dry season average: " * n2s(pearson_ymean_drysmean))
  println(" ++++      " * corr_adj(pearson_ymean_drysmean) * " " * corr_sign(pearson_ymean_drysmean) * " correlation")
end

# Base functions: from the statistical model to actual scenarios.
# Those functions allow scalar *and* vector arguments! (Only useful for Qd, however.)
# 213 days in wet season:     Date(2016, April, 30) - Date(2015, October, 1) + Day(1)
# 152 left in dry season.
adam2015_wet_season_value(QY, QS) = QY * (1 + QS)
adam2015_dry_season_value(QY, QS) = QY * (1 - QS * 213 / 152)
adam2015_wet_day_value(QY, QS, Qd) = QY * (1 + QS) * (1 + Qd)
adam2015_dry_day_value(QY, QS, Qd) = QY * (1 - QS * 213 / 152) * (1 + Qd)

"""
Based on the given statistical `model`, generate one yearly scenarios, with a value each `period`.
This scenario starts at the beginning of the wet season (as per the seasons in `model`), and occur in the year
`currentYear`.
"""
function generateScenarios_Adam2015(model::Adam2015RiverStatisticalModel, period::Period, currentYear::Int; kwargs...)
  # Pick a year average and a season average at random. The wet average has a high likelihood of being negative,
  # so generate until something positive is generated.
  year = rand(getYearlyAverage(model))

  # dry_season = rand(getSeasonAverageDelta(model, :Dry)) # Not fitted, using a fraction of the other season.
  wet_season = rand(getSeasonAverageDelta(model, :Wet))
  while adam2015_wet_season_value(year, wet_season) <= 0.0
    wet_season = rand(getSeasonAverageDelta(model, :Wet))
  end

  # Then work on days.
  bs, es = seasonBoundariesGuarantee(getSeasonSeparator(model), period, currentYear)
  n_wet = length(bs[:Wet] : period : es[:Wet])
  n_dry = length(bs[:Dry] : period : es[:Dry])

  gen_wet = adam2015_wet_day_value(year, wet_season, rand(getSeasonDailyDelta(model, :Wet), n_wet))
  gen_dry = adam2015_dry_day_value(year, wet_season, rand(getSeasonDailyDelta(model, :Dry), n_dry))

  gen = vcat(gen_wet, gen_dry)
  gen[gen .<= 0] = 0.
  return (collect(bs[:Wet] : period : es[:Dry]), gen)
end

generateScenarios_Adam2015(r::River, period::Period, currentYear::Int; kwargs...) =
  generateScenarios_Adam2015(fit_Adam2015(r; kwargs...), period, currentYear)

"""
Computes confidence intervals based on the statistical model.

Assumption: all three stages of probability distributions are not correlated.
This assumption seems mostly valid, according to a few correlation tests.
"""
function computeCI_Adam2015(r::River, model::Adam2015RiverStatisticalModel, level::Float64=.95)
  TS = r.scenarios

  ## Start by computing the actual values to this confidence level using quantiles.
  q_low = (1 - level) / 2
  q_high = 1 - q_low

  # First the annual confidence interval.
  year_low = quantile(model.year, q_low)
  year_mean = mean(model.year)
  year_high = quantile(model.year, q_high)

  # Then the same for the seasonal average normalised deltas.
  wet_season_low = quantile(model.season_wet, q_low)
  wet_season_mean = mean(model.season_wet)
  wet_season_high = quantile(model.season_wet, q_high)

  dry_season_low = quantile(model.season_dry, q_low)
  dry_season_mean = mean(model.season_dry)
  dry_season_high = quantile(model.season_dry, q_high)

  # And finally for the seasonal daily normalised deltas.
  wet_daily_low = quantile(model.daily_wet, q_low)
  wet_daily_mean = mean(model.daily_wet)
  wet_daily_high = quantile(model.daily_wet, q_high)

  dry_daily_low = quantile(model.daily_dry, q_low)
  dry_daily_mean = mean(model.daily_dry)
  dry_daily_high = quantile(model.daily_dry, q_high)

  ## Reconstruct the complete confidence interval as the sum of the previous random variables.
  # Here is used the hypothesis of absence of correlation.
  # Technique:
  #   - start from the annual average bound
  #   - transform one season's distribution based on this to get the seasonal average's bound
  #   - sum those bounds to get the total bound for seasonal variations
  # On average, the daily variations are zero: they are not taken into account when computing the mean.
  # However, these have a larger impact on the bounds of the confidence interval.
  dwet_low  = adam2015_wet_day_value(year_low,  wet_season_low,  wet_daily_low)
  dwet_high = adam2015_wet_day_value(year_high, wet_season_high, wet_daily_high)
  ddry_low  = adam2015_dry_day_value(year_low,  dry_season_low,  dry_daily_low)
  ddry_high = adam2015_dry_day_value(year_high, dry_season_high, dry_daily_high)

  # NOTE: dry_season seems to be replaced by wet_season in Adam's code!?
  dwet_mean = adam2015_wet_season_value(year_mean, wet_season_mean)
  ddry_mean = adam2015_dry_season_value(year_mean, wet_season_mean)

  ## Prepare the outputs, respecting the two seasons.
  wet_begin = begins(model.seasons_separator, :Wet, 1)
  wet_end = ends(model.seasons_separator, :Dry, 1)
  dry_begin = begins(model.seasons_separator, :Dry, 1)
  dry_end = ends(model.seasons_separator, :Dry, 1)
  period = getPeriod(r)

  # The length of the reconstructed series must match that of the time series!
  while length(vcat(wet_begin:period:wet_end, dry_begin:period:dry_end)) > length(timestamp(TS[1]))
    wet_begin += period
  end
  if length(vcat(wet_begin:period:wet_end, dry_begin:period:dry_end)) < length(timestamp(TS[1]))
    error("Reconstructed period too small (" * length(vcat(wet_begin:period:wet_end, dry_begin:period:dry_end)) * ") " *
          "with respect to existing time series (" * length(timestamp(TS[1])) * ").")
  end

  means   = vcat([dwet_mean for d in wet_begin:period:wet_end], [ddry_mean for d in dry_begin:period:dry_end])
  ci_low  = vcat([dwet_low  for d in wet_begin:period:wet_end], [ddry_low  for d in dry_begin:period:dry_end])
  ci_high = vcat([dwet_high for d in wet_begin:period:wet_end], [ddry_high for d in dry_begin:period:dry_end])

  means[means .<= 0.0] = 0.0
  ci_low[ci_low .<= 0.0] = 0.0
  ci_high[ci_high .<= 0.0] = 0.0

  ts_means   = TimeArray(timestamp(TS[1]), means,   colnames(TS[1]), meta(TS[1]))
  ts_ci_low  = TimeArray(timestamp(TS[1]), ci_low,  colnames(TS[1]), meta(TS[1]))
  ts_ci_high = TimeArray(timestamp(TS[1]), ci_high, colnames(TS[1]), meta(TS[1]))

  return ts_means, ts_ci_low, ts_ci_high
end

computeCI_Adam2015(r::River, level::Float64=.95; kwargs...) =
  computeCI_Adam2015(r, fit_Adam2015(r; kwargs...), level)


using Impero, Test

@wrapper u=1 σ=1
∂x = Operator(nothing, DerivativeMetaData(nothing, "x"))
∂y = Operator(nothing, DerivativeMetaData(nothing, "y"))
∂z = Operator(nothing, DerivativeMetaData(nothing, "z"))
∂t = Operator(nothing, DerivativeMetaData(nothing, "t"))
@testset "Impero Equation Specification" begin
    equ = @to_equation σ=u
    @test equ.rhs == u
    @test equ.lhs == σ

    @pde_system pde_system = [
        σ = ∂x(u),
        ∂t(u) = -∂x(u * u - ∂x(σ)),
    ]
    @test pde_system[1].lhs == σ
    @test pde_system[2].lhs == ∂t(u)
    @test pde_system[1].rhs == ∂x(u)
    @test pde_system[2].rhs == -∂x(u * u - ∂x(σ))
end

@testset "Impero Equation Modification" begin
    @pde_system pde_system = [
        σ = ∂x(u),
        ∂t(u)= ∂x(∂x(σ)),
    ]
    pde_system[2].rhs += -∂x(u*u)
    rhs = (∂x(∂x(σ))+-(∂x((u*u))))
    @test pde_system[2].rhs == rhs
end

@testset "Impero Equation Augmentation" begin
    @pde_system pde_system = [
        σ = ∂x(u),
        ∂t(u)= ∂x(∂x(σ)),
    ]
    push!(pde_system, @to_equation σ=u)
    @test pde_system[3].lhs == σ
    @test pde_system[3].rhs == u
end


"""
$(SIGNATURES)

Return whether `point` is in `hull`.
"""
function Base.in(point::PointLike, hull::ConvexHull)
    op = orientation_comparator(hull)
    vertices = hull.vertices
    n = length(vertices)
    @inbounds begin
        if n === 0
            return false
        elseif n === 1
            return point == hull.vertices[1]
        elseif n === 2
            p′ = point - vertices[1]
            δ = vertices[2] - vertices[1]
            cross2(p′, δ) == 0 && 0 <= p′ ⋅ δ <= δ ⋅ δ
        else
            δ = vertices[1] - vertices[n]
            for i in Base.OneTo(n - 1)
                op(cross2(point - vertices[i], δ), 0) && return false
                δ = vertices[i + 1] - vertices[i]
            end
            return !op(cross2(point - vertices[n], δ), 0)
        end
    end
end


using Test
using ITensors

using ITensors: ∑, ∏

@testset "Simple trotterization" begin
  H = ∑{Op}() + ("X", 1) + ("Y", 1)

  s = siteinds("Qubit", 1)

  for nsteps in [10, 100, 1000]
    expHᵉˣᵃᶜᵗ = ITensor(exp(H), s)
    @test expHᵉˣᵃᶜᵗ ≈ ITensor(exp(H; alg=Trotter{1}(nsteps)), s) rtol = 1 / nsteps
    @test expHᵉˣᵃᶜᵗ ≈ ITensor(exp(H; alg=Trotter{2}(nsteps)), s) rtol = (1 / nsteps)^2
    @test expHᵉˣᵃᶜᵗ ≈ ITensor(exp(H; alg=Trotter{4}(nsteps)), s) rtol = (1 / nsteps)^2
    @test expHᵉˣᵃᶜᵗ ≈ ITensor(exp(H; alg=Trotter{8}(nsteps)), s) rtol = (1 / nsteps)^2

    # Convert to ITensors
    t = 1.0
    Uᵉˣᵃᶜᵗ = ∏([ITensor(exp(im * t * H), s)])
    U = ∏{ITensor}(exp(im * t * H; alg=Trotter{2}(nsteps)), s)
    ψ₀ = onehot(s .=> "0")
    Uᵉˣᵃᶜᵗψ₀ = Uᵉˣᵃᶜᵗ(ψ₀)
    Uψ₀ = U(ψ₀)
    @test Uᵉˣᵃᶜᵗψ₀ ≈ Uψ₀ rtol = (1 / nsteps)^2
  end
end

function heisenberg(N)
  os = Sum{Op}()
  for j in 1:(N - 1)
    os += "Sz", j, "Sz", j + 1
    os += 0.5, "S+", j, "S-", j + 1
    os += 0.5, "S-", j, "S+", j + 1
  end
  return os
end

@testset "Heisenberg Trotter" begin
  N = 4
  ℋ = heisenberg(N)
  s = siteinds("S=1/2", N)
  ψ₀ = MPS(s, n -> isodd(n) ? "↑" : "↓")
  t = 1.0
  for nsteps in [10, 100]
    for order in [1, 2, 4]
      𝒰 = exp(im * t * ℋ; alg=Trotter{order}(nsteps))
      U = ∏{ITensor}(𝒰, s)
      H = ITensor(ℋ, s)
      Uʳᵉᶠψ₀ = replaceprime(exp(im * t * H) * prod(ψ₀), 1 => 0)
      atol = max(1e-6, 1 / nsteps^order)
      @test prod(U(ψ₀)) ≈ Uʳᵉᶠψ₀ atol = atol
    end
  end
end


using Roots #use: pkg.add("Roots") or in package mode: add Roots
rhoW = 1000 #Density of water in kg/m^3
rhoPVC = 1380 #Density of PVC in kg/m^3
Cd = 0.7 #Drag coefficient
Vs = (4/3)*pi #Coefficient for volume of sphere
g = 9.81 #Acceleration by gravity on Earth
V_dot = 3.2e-3 #Volumetric flow rate
A_in = (pi/4)*0.15^2 #Area of inlet
A_out = (pi/4)*0.35^2 #Area of outlet
f(r) = (sqrt((2*(rhoPVC-rhoW)*g*(Vs*r^3))/
(Cd*rhoW*(pi*r^2))))*(A_in-(pi*r^2))-V_dot
    #assumes ball diameter is closer to inlet
f2(r)= (sqrt((2*(rhoPVC-rhoW)*g*(Vs*r^3))/
(Cd*rhoW*(pi*r^2))))*(A_out-(pi*r^2))-V_dot
    #assumes ball diameter is closer to outlet
a = 2*fzero(f,0.07) #reference Roots in Julia documentation for usage of fzero
display(a) #lower bound result
b = 2*fzero(f2,0.17)
display(b) #upper bound result


"""
Implementations of pure, extended and unscented Kalman filters.

Algorithms based on Ch.5 of Bayesian Filtering & Smoothing (Särkka, 2014)

Wouter Kouw
04-06-2020
"""

using Distributions
using Random

include("util.jl")

function kalman_filter(observations,
                       transition_matrix,
                       emission_matrix,
                       process_noise,
                       measurement_noise,
                       state0)
    """
    Kalman filter (Th. 4.2)

    This filter is built for a linear Gaussian dynamical system with known
    transition coefficients, process and measurement noise.
    """

    # Dimensionality
    Dx = size(process_noise,1)
    Dy = size(measurement_noise,1)

    # Recast process noise to matrix
    if Dx == 1
        if typeof(process_noise) != Array{Float64,2}
            process_noise = reshape([process_noise], 1, 1)
        end
        if typeof(measurement_noise) != Array{Float64,2}
            measurement_noise = reshape([measurement_noise], 1, 1)
        end
    end

    # Time horizon
    time_horizon = length(observations)

    # Initialize estimate arrays
    mx = zeros(Dx, time_horizon)
    Px = zeros(Dx, Dx, time_horizon)

    # Initial state prior
    m_0, P_0 = state0

    # Start previous state variable
    m_tmin = m_0
    P_tmin = P_0

    for t = 1:time_horizon

        # Prediction step
        m_t_pred = transition_matrix*m_tmin
        P_t_pred = transition_matrix*P_tmin*transition_matrix' .+ process_noise

        # Update step
        v_t = observations[:,t] .- emission_matrix*m_t_pred
        S_t = emission_matrix*P_t_pred*emission_matrix' .+ measurement_noise
        K_t = P_t_pred*emission_matrix'*inv(S_t)
        m_t = m_t_pred .+ K_t*v_t
        P_t = P_t_pred .- K_t*S_t*K_t'

        # Store estimates
        mx[:,t] = m_t
        Px[:,:,t] = P_t

        # Update previous state variable
        m_tmin = m_t
        P_tmin = P_t
    end
    return mx, Px
end

function extended_kalman_filter(observations::Array{Float64,2},
                                transition_function::Function,
                                emission_function::Function,
                                process_noise::Array{Float64,2},
                                measurement_noise::Array{Float64,2},
                                state0::Tuple{Array{Float64,1}, Array{Float64,2}};
                                second_order=false)
    """
    Extended Kalman filter for additive noise case
        First-order Taylor approximation = Alg 5.4 (Särkkä - BF&S)
        Second-order Taylor approxumation = Alg 5.6 (Särkkä - BF&S)

    This filter is built for a linear Gaussian dynamical system with known
    transition coefficients, process and measurement noise.
    """

    # Dimensionality
    Dx = size(process_noise,1)
    Dy = size(measurement_noise,1)

    # Recast process noise to matrix
    if Dx == 1
        if typeof(process_noise) != Array{Float64,2}
            process_noise = reshape([process_noise], 1, 1)
        end
        if typeof(measurement_noise) != Array{Float64,2}
            measurement_noise = reshape([measurement_noise], 1, 1)
        end
    end

    # Time horizon
    time_horizon = length(observations)

    # Initialize estimate arrays
    mx = zeros(Dx, time_horizon)
    Px = zeros(Dx, Dx, time_horizon)

    # Initial state prior
    m_0, P_0 = state0

    # Start previous state variable
    m_tmin = m_0
    P_tmin = P_0

    for t = 1:time_horizon

        "Prediction step"

        # Compute Jacobian evaluated at previous mean
        Fx = Jacobian(transition_function, m_tmin)

        if second_order
            # Compute per-element Hessian evaluated at previous mean
            Fxx = Hessian(transition_function, m_tmin)
        end

        # Compute predicted mean
        m_t_pred = transition_function(m_tmin)

        # Add second-order term from Taylor approx
        if second_order
            nn = length(m_t_pred)
            e = zeros(nn,)
            for i = 1:nn
                e[i] = tr(Fxx[i,:,:]*P_tmin)
            end
            m_t_pred += 1/2. *e
        end

        # Compute predicted covariance
        P_t_pred = Fx*P_tmin*Fx' + process_noise

        # Add second-order term from Taylor approx
        if second_order
            nn = length(m_t_pred)
            E = zeros(nn,nn)
            for i = 1:nn
                for j = 1:nn
                    E[i,j] = tr(Fxx[i,:,:]*P_tmin*Fxx[j,:,:]*P_tmin)
                end
            end
            P_t_pred += 1/2. *E
        end

        "Update step"

        # Compute Jacobian evaluated at predicted mean
        Hx = Jacobian(emission_function, m_t_pred)

        if second_order
            # Compute per-element Hessian evaluated at predicted mean
            Hxx = Hessian(emission_function, m_t_pred)
        end

        # Update equations
        v_t = observations[:,t] .- emission_function(m_t_pred)
        S_t = Hx*P_t_pred*Hx' .+ measurement_noise

        if second_order
            nn = length(m_t_pred)
            e = zeros(nn,)
            for i = 1:nn
                e[i] = tr(Hxx[i,:,:]*P_t_pred)
            end
            v_t += -1/2. *e

            E = zeros(nn,nn)
            for i = 1:nn
                for j = 1:nn
                    E[i,j] = tr(Hxx[i,:,:]*P_t_pred*Hxx[j,:,:]*P_t_pred)
                end
            end
            S_t += -1/2. *E
        end

        K_t = P_t_pred*Hx'*inv(S_t)
        m_t = m_t_pred .+ K_t*v_t
        P_t = P_t_pred .- K_t*S_t*K_t'

        # Store estimates
        mx[:,t] = m_t
        Px[:,:,t] = P_t

        # Update previous state variable
        m_tmin = m_t
        P_tmin = P_t
    end
    return mx, Px
end

function unscented_kalman_filter(observations::Array{Float64,2},
                                 transition_function::Function,
                                 emission_function::Function,
                                 process_noise::Array{Float64,2},
                                 measurement_noise::Array{Float64,2},
                                 state0::Tuple{Array{Float64,1}, Array{Float64,2}};
                                 α=1.1, β=2., κ=.1)
    """
    Unscented Kalman filter with additive noise (Alg. 5.14)

    This filter is built for a linear Gaussian dynamical system with unknown
    transition function, but with known process and measurement noise.
    """

    # Dimensionality
    Dx = size(process_noise,1)
    Dy = size(measurement_noise,1)

    # Recast process noise to matrix
    if Dx == 1
        if typeof(process_noise) != Array{Float64,2}
            process_noise = reshape([process_noise], 1, 1)
        end
        if typeof(measurement_noise) != Array{Float64,2}
            measurement_noise = reshape([measurement_noise], 1, 1)
        end
    end

    # Time horizon
    time_horizon = length(observations)

    # Set algorithm parameters
    λ = α^2*(Dx + κ) - Dx

    if λ < 0
        error("Unsuitable algorithm parameters chosen; λ is negative.")
    end

    # Compute constant weights
    weights_m = [[λ/(Dx + λ)]; repeat( [1/(2*(Dx + λ))], 2*Dx)]
    weights_c = [[λ/(Dx + λ) + (1-α^2+β)]; repeat( [1/(2*(Dx + λ))], 2*Dx)]

    if sum(weights_m) != 1.0
        error("Unsuitable algorithm parameters chosen; first-order weights do not sum to 1.")
    end

    # Initialize estimate arrays
    mx = zeros(Dx, time_horizon)
    Px = zeros(Dx, Dx, time_horizon)

    # Initial state prior
    m_0, P_0 = state0

    # Start previous state variable
    m_tmin = m_0
    P_tmin = P_0

    for t = 1:time_horizon

        "Prediction step"

        # Square root of previous covariance matrix
        sP_tmin = sqrt(P_tmin)

        # Initialize sigma point array
        ξ = zeros(Dx, 2*Dx+1)

        # Center point
        ξ[:, 1] = m_tmin

        # Positive and negative spread points
        for i = 1:Dx
            ξ[:, 1+i] = m_tmin + sqrt(Dx + λ)*sP_tmin[:,i]
            ξ[:, 1+Dx+i] = m_tmin - sqrt(Dx + λ)*sP_tmin[:,i]
        end

        # Propagate sigma points through transition function
        ζ = transition_function.(ξ)

        # Compute predicted mean
        m_t_pred = zeros(Dx,)
        for i=1:2Dx+1
            m_t_pred += weights_m[i] .* ζ[:,i]
        end

        # Compute predicted covariance
        P_t_pred = zeros(Dx,Dx)
        for i = 1:2*Dx+1
            P_t_pred += weights_c[i]*(ζ[:,i] .- m_t_pred)*(ζ[:,i] .- m_t_pred)' .+ process_noise
        end

        "Update step"

        # Square root of previous covariance matrix
        sP_t_pred = sqrt(P_t_pred)

        # Initialize sigma point array
        ξ_ = zeros(Dx, 2*Dx+1)

        # Center point
        ξ_[:, 1] = m_t_pred

        # Positive and negative spread points
        for i = 1:Dx
            ξ_[:, 1+i] = m_t_pred + sqrt(Dx + λ)*sP_t_pred[:,i]
            ξ_[:, 1+Dx+i] = m_t_pred - sqrt(Dx + λ)*sP_t_pred[:,i]
        end

        # Propagate sigma points through emission function
        ζ_ = emission_function.(ξ_)

        # Compute mean of inverted likelihood
        μ_t = zeros(Dx,)
        for i = 1:2*Dx+1
            μ_t += weights_m[i] * ζ_[:,i]
        end

        # Compute covariance and cross-covariance of inverted likelihood
        S_t = zeros(Dx,Dx)
        C_t = zeros(Dx,Dx)
        for i = 1:2*Dx+1
            S_t += weights_c[i]*(ζ_[:,i] - μ_t)*(ζ_[:,i] - μ_t)' .+ measurement_noise
            C_t += weights_c[i]*(ξ_[:,i] - m_t_pred)*(ζ_[:,i] - μ_t)'
        end

        # Compute Kalman gain
        K_t = C_t*inv(S_t)

        # Compute filtered state parameters
        m_t = m_t_pred .+ K_t*(observations[:,t] - μ_t)
        P_t = P_t_pred .- K_t*S_t*K_t'

        # Store estimates
        mx[:,t] = m_t
        Px[:,:,t] = P_t

        # Update previous state variable
        m_tmin = m_t
        P_tmin = P_t
    end
    return mx, Px
end


export measure, dirac
export variables, monomials, moments

# If a monomial is not in x, it does not mean that the moment is zero, it means that it is unknown/undefined
struct Measure{T, MT <: AbstractMonomial, MVT <: AbstractVector{MT}} <: AbstractMeasure{T}
    a::Vector{T}
    x::MVT

    function Measure{T, MT, MVT}(a::Vector{T}, x::MVT) where {T, MT, MVT}
        @assert length(a) == length(x)
        new(a, x)
    end
end
function Measure(a::AbstractVector{T}, x::AbstractVector{TT}; kws...) where {T, TT <: AbstractTermLike}
    # cannot use `monovec(a, x)` as it would sum the entries
    # corresponding to the same monomial.
    if length(a) != length(x)
        throw(ArgumentError("There should be as many coefficient than monomials"))
    end
    σ, X = sortmonovec(x)
    b = a[σ]
    if length(x) > length(X)
        rev = Dict(X[j] => j for j in eachindex(σ))
        for i in eachindex(x)
            j = rev[x[i]]
            if i != σ[j]
                if !isapprox(b[j], a[i]; kws...)
                    error("The monomial `$(x[i])` is occurs twice with different values: `$(a[i])` and `$(b[j])`")
                end
            end
        end
    end
    return Measure{T, monomialtype(TT), typeof(X)}(b, X)
end

"""
    measure(a::AbstractVector{T}, X::AbstractVector{<:AbstractMonomial}; rtol=Base.rtoldefault(T), atol=zero(T))

Creates a measure with moments `moment(a[i], X[i])` for each `i`.
An error is thrown if there exists `i` and `j` such that `X[i] == X[j]` but
`!isapprox(a[i], a[j]; rtol=rtol, atol=atol)`.
"""
measure(a, X; kws...) = Measure(a, X; kws...)
measure(a, basis::MB.MonomialBasis; kws...) = measure(a, basis.monomials; kws...)

"""
    variables(μ::AbstractMeasureLike)

Returns the variables of `μ` in decreasing order. Just like in MultivariatePolynomials, it could contain variables of zero degree in every monomial.
"""
MP.variables(μ::Measure) = variables(μ.x)

"""
    monomials(μ::AbstractMeasureLike)

Returns an iterator over the monomials of `μ` sorted in the decreasing order.
"""
MP.monomials(μ::Measure) = μ.x

"""
    moments(μ::AbstractMeasureLike)

Returns an iterator over the moments of `μ` sorted in decreasing order of monomial.
"""
moments(μ::Measure) = map((α, x) -> moment(α, x), μ.a, μ.x)

Base.:(*)(α, μ::Measure) = measure(α * μ.a, μ.x)
Base.:(*)(μ::Measure, α) = measure(μ.a * α, μ.x)
Base.:(-)(μ::Measure) = measure(-μ.a, μ.x)
function Base.:(+)(μ::Measure, ν::Measure)
    @assert μ.x == ν.x
    measure(μ.a + ν.a, μ.x)
end

"""
    dirac(X::AbstractVector{<:AbstractMoment}, s::AbstractSubstitution...)

Creates the dirac measure by evaluating the moments of `X` using `s`.

## Examples

Calling `dirac([x*y, x*y^2], x=>3, y=>2)` should the measure with moment `x*y` of value `6` and moment `x*y^2` of value `12`.
"""
function dirac(x::AbstractVector{MT}, s::MP.AbstractSubstitution...) where {MT <: AbstractMonomial}
    Measure([m(s...) for m in x], x)
end


using MatrixPolynomials, LinearAlgebra
using Test

x = variable("x")

@testset "column nullities" begin
    J1 = [x -1]
    e, d, Ar, Br = colNullities(J1)
    @test e == [0, 1]
    @test colIndices(J1) == [1]
    @test isempty(d)
    @test isempty(Ar)
    @test isempty(Br)

    J2 = [x -1 0;
          0  x -1]

    e, d, Ar, Br = colNullities(J2)
    @test e == [0, 0, 1]
    @test colIndices(J2) == [2]
    @test isempty(d)
    @test isempty(Ar)
    @test isempty(Br)

    J3 = [zeros(2, 2) J2]
    e, d, Ar, Br = colNullities(J3)
    @test e == [2, 0, 1]
    @test colIndices(J3) == [0, 0, 2]
    @test isempty(d)
    @test isempty(Ar)
    @test isempty(Br)

    J = [x]
    e, d, Ar, Br = colNullities(J)
    @test isempty(e)
    @test isempty(d)
    @test Ar == hcat([0])
    @test Br == hcat([1])

    J = [-1]
    e, d, Ar, Br = colNullities(J)
    @test isempty(e)
    @test d == [1]
    @test isempty(Ar)
    @test isempty(Br)

    J = [x -1 0 0 0;
        0 0 -1 0 0;
        0 0 0 x-1 0]

    e, d, Ar, Br = colNullities(J)
    @test e == [1, 1]
    @test colIndices(J) == [0, 1]
    @test d == [1]

    J = [x -1 0 0 0 0 0;
         0 0 x -1 0 0 0
         0 0 0 0 -1 0 0;
         0 0 0 0 0 x-1 0]

    e, d, Ar, Br = colNullities(J)
    @test e == [1, 2]
    @test colIndices(J) == [0, 1, 1]
    @test d == [1]

    J = [x -1 0 0 0 0  0 0  0 0 0;
         0 0 x -1 0 0  0 0  0 0 0;
         0 0 0 0 -1 0  0 0  0 0 0;
         0 0 0 0 0 x-1 0 0  0 0 0;
         0 0 0 0 0 0   0 0 -1 0 0;
         0 0 0 0 0 0   0 0  x 0 0;
         0 0 0 0 0 0   0 0  0 -1 0
         0 0 0 0 0 0   0 0  0  x -1]

    e, d, Ar, Br = colNullities(J)
    @test e == [2, 2]
    @test colIndices(J) == [0, 0, 1, 1]
    @test d == [1, 1]
end

@testset "row nullities" begin
    J1 = [-1;x]
    e, d, Ar, Br = rowNullities(J1)
    @test e == [0, 1]
    @test rowIndices(J1) == [1]
    @test isempty(d)
    @test isempty(Ar)
    @test isempty(Br)

    J2 = [-1 0;
        x -1;
        0 x]

    e, d, Ar, Br = rowNullities(J2)
    @test e == [0, 0, 1]
    @test rowIndices(J2) == [2]
    @test isempty(d)
    @test isempty(Ar)
    @test isempty(Br)

    J3 = [zeros(2, 2); J2]
    e, d, Ar, Br = rowNullities(J3)
    @test e == [2, 0, 1]
    @test rowIndices(J3) == [0, 0, 2]
    @test isempty(d)
    @test isempty(Ar)
    @test isempty(Br)

    J = [x]
    e, d, Ar, Br = rowNullities(J)
    @test isempty(e)
    @test isempty(d)
    @test Ar == hcat([0])
    @test Br == hcat([1])

    J = [-1]
    e, d, Ar, Br = rowNullities(J)
    @test isempty(e)
    @test d == [1]
    @test isempty(Ar)
    @test isempty(Br)

    J = [-1 0 0;
        x 0 0;
        0 -1 0;
        0 0 x-1;
        0 0 0]

    e, d, Ar, Br = rowNullities(J)
    @test e == [1, 1]
    @test rowIndices(J) == [0, 1]
    @test d == [1]

    J = [-1 0 0 0;
        x 0 0 0;
        0 -1 0 0;
        0 x 0 0;
        0 0 -1 0;
        0 0 0 0]

    e, d, Ar, Br = rowNullities(J)
    @test e == [1, 2]
    @test rowIndices(J) == [0, 1, 1]
    @test d == [1]

    J = [-1 0 0 0 0;
          x 0 0 0 0;
          0 -1 0 0 0;
          0 x 0 0 0;
          0 0 -1 0 0;
          0 0 0 -1 0;
          0 0 0 x -1;
          0 0 0 0 0]

    e, d, Ar, Br = rowNullities(J)
    @test e == [1, 2]
    @test rowIndices(J) == [0, 1, 1]
    @test d == [1, 1]
end

@testset "eigenvalues computations" begin
    J = [x-1 0;
         0   x-2]
    @test eig(J) ≈ [1, 2]
    @test isempty(infMultiplicities(J))

    A = [x^2 1 0; 0 0 0;0 0 x]
    @test eig(A) ≈ [0, Inf]
    @test infMultiplicities(A) == [1]

    A = [1 0 0;0 x 0;0 0 x^2-x]
    @test eig(A) ≈ [0, 0, 1, Inf, Inf, Inf]
    @test infMultiplicities(A) == [1, 2]

    J = [x -1 0 0 0 0  0 0  0 0 0;
    0 0 x -1 0 0  0 0  0 0 0;
    0 0 0 0 -1 0  0 0  0 0 0;
    0 0 0 0 0 x-1 0 0  0 0 0;
    0 0 0 0 0 0   0 0 -1 0 0;
    0 0 0 0 0 0   0 0  x 0 0;
    0 0 0 0 0 0   0 0  0 -1 0
    0 0 0 0 0 0   0 0  0  x -1]
    @test eig(J) ≈ [1, Inf, Inf, Inf]
    @test infMultiplicities(J) == [1, 2]

    A = [x-1.1 0 0
         0    -1 0
         0     x -1]
    @test eig(A) ≈ [1.1, Inf, Inf]
    @test infMultiplicities(A) == [2]

    A = zeros(Pol{Int}, (14, 16))
    A[1,3:4] = [x -1]
    A[2:3, 5:7] = [x -1 0;0 x -1]
    A[5:8, 8:10] = [-1 0 0;x -1 0;0 x -1;0 0 x]
    A[9, 11] = -1
    A[10:11, 12:13] = [-1 0;x -1]
    A[12, 14] = x-2
    A[13:14, 15:16] = [x-3 0;-1 x-3]

    @test eig(A) ≈ [2, 3, 3, Inf, Inf, Inf]
    @test colIndices(A) == [0, 0, 1, 2]
    @test rowIndices(A) == [0, 3]
    @test infMultiplicities(A) == [1, 2]

    F = svd(rand(size(A)...), full=true)
    A1 = F.U*A*F.Vt
    @test eig(A1, 1e-6) ≈ [2, 3, 3, Inf, Inf, Inf]
end


using ITensors, Test

a = [-0.1, -0.12]
@test ITensors.truncate!(a) == (0., 0.)
@test length(a) == 1
a = [0.1, 0.01, 1e-13]
@test ITensors.truncate!(a,absoluteCutoff=true,cutoff=1e-5) == (1e-13, (0.01 + 1e-13)/2)
@test length(a) == 2

i = Index(2,"i")
j = Index(2,"j")
A = randomITensor(i,j)
@test_throws ArgumentError factorize(A, i, dir="fakedir")

@testset "Spectrum" begin
  i = Index(100,"i")
  j = Index(100,"j")

  U,S,V = svd(rand(100,100))
  S ./= norm(S)
  A = ITensor(U*diagm(0=>S)*V', i,j)

  spec = svd(A,i).spec

  @test eigs(spec) ≈ S .^2
  @test truncerror(spec) == 0.0

  spec = svd(A,i; maxdim=length(S)-3).spec
  @test truncerror(spec) ≈ sum(S[end-2:end].^2)

end


# [[file:../fresa.org::testmultiobjective][testmultiobjective]]
using Fresa
nx = 2
# specify the domain for the search, x ∈ [0,10]ⁿ
domain = Fresa.Domain(x -> zeros(length(x)), x -> ones(length(x)))
# initial point in domain
x = rand(nx)
# objective function
f = x -> ( [sin(x[1]-x[2]); cos(x[1]+x[2])], 0)
# create the initial population consisting of this single point
p0 = [Fresa.createpoint(x,f)]
# now invoke Fresa to solve the problem
pareto, population = Fresa.solve(f, p0, domain;
                                 #fitnesstype = :hadamard,
                                 #fitnesstype = :borda,
                                 fitnesstype = :nondominated,
                                 ngen=200,
                                 npop=20,
                                 plotvectors=true,
                                 tolerance=0.01)

println("**** Pareto front:")
println("#+plot: ind:2 deps:(3) with:points")
println(population[pareto])
#using BenchmarkTools
#@benchmark
# testmultiobjective ends here


include("EllCrv/EllCrv.jl")
include("EllCrv/Misc.jl")
include("EllCrv/QQ.jl")
include("EllCrv/Finite.jl")
include("EllCrv/Heights.jl")
include("EllCrv/MordellWeilRank.jl")
include("EllCrv/Isogeny.jl")


""" Diagrams of a given shape.
"""
module ShapeDiagrams
export Span, Cospan, left, right, apex, base

using ...Theories: dom, codom

""" Span of morphisms in a category.
"""
struct Span{Left,Right}
  left::Left
  right::Right

  function Span(left::Left, right::Right; strict::Bool=true) where {Left,Right}
    if strict && dom(left) != dom(right)
      error("Domains of legs in span do not match: $left vs $right")
    end
    new{Left,Right}(left, right)
  end
end

apex(span::Span) = dom(span.left) # == dom(span.right)
left(span::Span) = span.left
right(span::Span) = span.right

""" Cospan of morphisms in a category.
"""
struct Cospan{Left,Right}
  left::Left
  right::Right

  function Cospan(left::Left, right::Right; strict::Bool=true) where {Left,Right}
    if strict && codom(left) != codom(right)
      error("Codomains of legs in cospan do not match: $left vs $right")
    end
    new{Left,Right}(left, right)
  end
end

base(cospan::Cospan) = codom(cospan.left) # == codom(cospan.right)
left(cospan::Cospan) = cospan.left
right(cospan::Cospan) = cospan.right

end


using LinearAlgebra
using LogExpFunctions
using Statistics
using Tullio

"""
    gpd_fit(
        sample::AbstractVector{T<:Real},
        r_eff::T = 1;
        wip::Bool=true,
        min_grid_pts::Integer=30,
        sort_sample::Bool=false
    ) -> (ξ::T, σ::T)

Return a named list of estimates for the parameters ξ (shape) and σ (scale) of the
generalized Pareto distribution (GPD), assuming the location parameter is 0.

# Arguments

  - `sample::AbstractVector`: A numeric vector. The sample from which to estimate
    the parameters.
  - `wip::Bool = true`: Logical indicating whether to adjust ξ based on a weakly informative
    Gaussian prior centered on 0.5. Defaults to `true`.
  - `min_grid_pts::Integer = 30`: The minimum number of grid points used in the fitting
    algorithm. The actual number used is `min_grid_pts + ⌊sqrt(length(sample))⌋`.

# Note

Estimation method taken from Zhang, J. and Stephens, M.A. (2009). The parameter ξ is the
negative of k.
"""
function gpd_fit(
    sample::AbstractVector{T},
    r_eff::T=1;
    wip::Bool=true,
    min_grid_pts::Integer=30,
    sort_sample::Bool=false,
) where T<:Real

    len = length(sample)
    # sample must be sorted, but we can skip if sample is already sorted
    if sort_sample
        sample = sort(sample; alg=QuickSort)
    end

    grid_size = min_grid_pts + isqrt(len)  # isqrt = floor sqrt
    n_0 = 10  # determines how strongly to nudge ξ towards .5
    x_star = inv(3 * sample[(len + 2) ÷ 4])  # magic number. ¯\_(ツ)_/¯
    invmax = inv(sample[len])

    # build pointwise estimates of ξ and θ at each grid point
    θ_hats = similar(sample, grid_size)
    @fastmath @. θ_hats = invmax + (1 - sqrt((grid_size + 1) / $(1:grid_size))) * x_star
    @tullio threads=false ξ_hats[i] := log1p(-θ_hats[i] * sample[j])
    ξ_hats /= len

    log_like = ξ_hats  # Reuse preallocated array
    # Calculate profile log-likelihood at each estimate:
    @tullio threads=false ξ_hats[i] =
        len * (log(-θ_hats[i] / ξ_hats[i]) - ξ_hats[i] - 1)
    # Calculate weights from log-likelihood:
    weights = log_like  # Reuse preallocated array
    log_norm = logsumexp(log_like)
    @tullio threads=false log_like[x] = exp(log_like[x] - log_norm)
    # Take weighted mean:
    @tullio threads=false θ_hat := weights[x] * θ_hats[x]
    @tullio threads=false ξ := log1p(-θ_hat * sample[i])
    ξ /= len
    σ::T = -ξ / θ_hat

    # Drag towards .5 to reduce variance for small len
    if wip
        @fastmath ξ = (r_eff * ξ * len + 0.5 * n_0) / (r_eff * len + n_0)
    end

    return ξ, σ

end

"""
    gpd_quantile(p::T, k::T, sigma::T) where {T<:Real} -> T

Compute the `p` quantile of the Generalized Pareto Distribution (GPD).

# Arguments

  - `p`: A scalar between 0 and 1.
  - `ξ`: A scalar shape parameter.
  - `σ`: A scalar scale parameter.

# Returns

A quantile of the Generalized Pareto Distribution.
"""
function gpd_quantile(p, ξ::T, sigma::T) where {T <: Real}
    return sigma * expm1(-ξ * log1p(-p)) / ξ
end


##############################################################################
##
## Combination behaves like [A B C ...] without forming it
##
##############################################################################

struct Combination{N}
    A::NTuple{N, Matrix{Float64}}
    cumlength::Vector{Int}
end

function Combination(A::Matrix{Float64}...)
    Combination(A, cumsum([size(x, 2) for x in A]))
end

function size(c::Combination, i)
    if i == 1
        size(c.A[1], 1)
    elseif i == 2
        c.cumlength[end]
    end
end

function view(c::Combination, ::Colon, j)
    index = searchsortedfirst(c.cumlength, j)
    newj = j
    if index > 1
        newj = j - c.cumlength[index-1]
    end
    view(c.A[index], :, newj)
end

##############################################################################
##
## Crossprod computes [A B C ...]' [A B C ...] without forming it
##
##############################################################################

# Construct [A B C]'[A B C] without generating [A B C]
function crossprod(c::Combination{N}) where {N}
    out = Array{Float64}(undef, size(c, 2), size(c, 2))
    for j in 1:size(c, 2)
        viewj = view(c, :, j)
        for i in j:size(c, 2)
            out[i, j] = dot(viewj, view(c, :, i))
        end
    end
    # make symmetric
    for j in 1:size(c, 2), i in 1:(j-1)
        out[i, j] = out[j, i]
    end
    return out
end
crossprod(A::Matrix{Float64}) = A'A
crossprod(A::Matrix{Float64}...) = crossprod(Combination(A...))

##############################################################################
##
## Returns base of [A B C ...]
##
## TODO: You could protect against roundoff error by using a controlled sum algorithm (similar to sum_kbn) to compute elements of X'X, then converting to BigFloat before factoring.
##
##
##############################################################################

# rank(A) == rank(A'A)
function basecol(X::Matrix{Float64}...; factorization = :Cholesky)
    cholm = cholesky!(Symmetric(crossprod(X...)), Val(true); tol = -1, check = false)
    r = 0
    if size(cholm, 1) > 0
        r = sum(diag(cholm.factors) .> size(X[1],1)^2 * eps())
        # used to be r = rank(cholm) but does not work wiht very high regressors at the same time as intercept
    end
    invpermute!(1:size(cholm, 1) .<= r, cholm.piv)
end

function getcols(X::Matrix{Float64},  basecolX::BitArray{1})
    sum(basecolX) == size(X, 2) ? X : X[:, basecolX]
end


using VectorSphericalWaves
using ProfileView
using Zygote

m, n, kr_r, kr_i, θ, ϕ = 6, 10, 1e5, 0, 0.2, 0.3
kr = complex(kr_r, kr_i)

θ = 0.4
@profiler for i=1:1e6; VectorSphericalWaves.πₘₙ(m, n, θ); end

@time for i=1:1e5; VectorSphericalWaves.πₘₙ(m, n, θ); end

Zygote.gradient(VectorSphericalWaves.πₘₙ, m, n, θ)

@time for i=1:1e2; N_mn_wave(m, n, complex(kr_r, kr_i), θ, ϕ; kind="regular"); end

using VectorSphericalWaves
m, n, kr_r, kr_i, θ, ϕ = 6, 10, 1e5, 0, 0.2, 0.3 ; kr = complex(kr_r, kr_i); θ = rand(1000);
N_mn_wave(m, n, kr, θ[1], ϕ; kind="regular")
M_mn_wave(m, n, kr, θ[1], ϕ; kind="regular")
@time for n = 1 : 6
    for m = -n:n
        N_mn_wave.(m, n, kr, θ, ϕ; kind="regular")
        M_mn_wave.(m, n, kr, θ, ϕ; kind="regular")
        N_mn_wave.(m, n, kr, θ, ϕ; kind="irregular")
        M_mn_wave.(m, n, kr, θ, ϕ; kind="irregular")
    end
end


@testsection "Tens" begin
    sq2 = √Sym(2)
    v = Sym[0 1 1; 1 0 1; 1 1 0]
    b = Basis(v)
    bn = normalize(b)
    for i ∈ 1:3
        @eval $(Symbol("v$i")) = symbols($"v$i", real = true)
    end
    V = Tens(Tensor{1,3}(i -> eval(Symbol("v$i"))))
    @test simplify(components(V, b, (:cont,))) ==
          [(-v1 + v2 + v3) / 2, (v1 - v2 + v3) / 2, (v1 + v2 - v3) / 2]
    @test simplify(components(V, b, (:cov,))) == [v2 + v3, v1 + v3, v1 + v2]
    @test simplify(components(V, bn, (:cov,))) ==
          [sq2 * (v2 + v3) / 2, sq2 * (v1 + v3) / 2, sq2 * (v1 + v2) / 2]

    for i ∈ 1:3, j ∈ 1:3
        @eval $(Symbol("t$i$j")) = symbols($"t$i$j", real = true)
    end
    T = Tens(Tensor{2,3}((i, j) -> eval(Symbol("t$i$j"))))
    @test simplify(components(T, b, (:cov, :cov))) == [
        t22+t23+t32+t33 t21+t23+t31+t33 t21+t22+t31+t32
        t12+t13+t32+t33 t11+t13+t31+t33 t11+t12+t31+t32
        t12+t13+t22+t23 t11+t13+t21+t23 t11+t12+t21+t22
    ]
    @test simplify(components(T, b, (:cont, :cov))) ==
          [
        -t12-t13+t22+t23+t32+t33 -t11-t13+t21+t23+t31+t33 -t11-t12+t21+t22+t31+t32
        t12+t13-t22-t23+t32+t33 t11+t13-t21-t23+t31+t33 t11+t12-t21-t22+t31+t32
        t12+t13+t22+t23-t32-t33 t11+t13+t21+t23-t31-t33 t11+t12+t21+t22-t31-t32
    ] / 2

    for i ∈ 1:3
        @eval $(Symbol("a$i")) = symbols($"a$i", real = true)
        @eval $(Symbol("b$i")) = symbols($"b$i", real = true)
    end
    a = Tens(Vec{3}((i,) -> eval(Symbol("a$i"))))
    b = Tens(Vec{3}((i,) -> eval(Symbol("b$i"))))
    @test a ⊗ b == Sym[a1*b1 a1*b2 a1*b3; a2*b1 a2*b2 a2*b3; a3*b1 a3*b2 a3*b3]
    @test a ⊗ˢ b == Sym[
        a1*b1 a1*b2/2+a2*b1/2 a1*b3/2+a3*b1/2
        a1*b2/2+a2*b1/2 a2*b2 a2*b3/2+a3*b2/2
        a1*b3/2+a3*b1/2 a2*b3/2+a3*b2/2 a3*b3
    ]

    θ, ϕ, ψ = symbols("θ, ϕ, ψ", real = true)
    R = rot3(θ, ϕ, ψ)
    Λ = [symbols("λ$i", positive = true) for i ∈ 1:3]
    bg = Basis(R .* Λ')
    bo = Basis(R)
    𝕀, 𝕁, 𝕂 = ISO()
    α, β = symbols("α β", real = true)
    𝕋 = α * 𝕁 + β * 𝕂
    @test change_tens(𝕋, bg) == 𝕋
    @test change_tens(𝕋, bo) == 𝕋
    A = components(𝕋, bg)
    𝕋₂ = Tens(A, bg)
    @test 𝕋₂ == 𝕋

end


using Gadfly
using ProgressMeter

# Creates an array of length N where each entry is an independent realization
# of a complex Gaussian with Ez = Ezz = 0 and Ezz^* = 1.
function zGaussian(N=1)
   x, y = rand(N), rand(N)
   sqrt(-log(x)) .* exp(2im * pi * y)
end

function getRHS(nwells, T, KdJ)
   # Diagonal entries (0 for the action)
   center = (nwells + 1) / 2
   diags = push!(.5 * KdJ * ([1:nwells] - center).^2, 0)
   prop = -1im * diagm(diags)
   # Coupling between the wells
   coupls = push!(ones(nwells - 1) * T, 0)
   prop += 1im * (diagm(coupls, 1) + diagm(coupls, -1))

   (t, y) -> prop * y
end

function integrate_rk4(rhs, times, y0)
   y = zeros(typeof(y0[0]), (length(y0), length(times)))

   y[:, 1] = y0
   yrk = zeros(typeof(y0[0]), (length(y0), 4))

   for (i, t) in enumerate(times[2:end])
      dt = t - times[i]
      yrk[:, 1] = dt * rhs(t, y[:, i])
      yrk[:, 2] = dt * rhs(t + dt/2, y[:, i] + .5 * yrk[:, 1])
      yrk[:, 3] = dt * rhs(t + dt/2, y[:, i] + .5 * yrk[:, 2])
      yrk[:, 4] = dt * rhs(t + dt, y[:, i] + yrk[:, 3])

      y[:, i+1] = y[:, i] + 1/6 * (yrk[:, 1] + 2*yrk[:, 2] + 2*yrk[:, 3] + yrk[:, 4])
   end

   y
end

function main(timesteps, dt, nwells, realizations; T=2.0, KdJ=2.0)
   println("GO!")
   shift = convert(Int, floor((nwells - 1) // 2 % 2))
   even_w = filter(n -> isodd(n - shift), [1:nwells])
   odd_w = filter(n -> iseven(n - shift), [1:nwells])
   t = [1:timesteps] * dt
   rhs = getRHS(nwells, T, KdJ)
   norm = zeros(Complex{Float64}, timesteps)
   prog = Progress(realizations, 1)

   for n in [1:realizations]
      z1 = integrate_rk4(rhs, t, push!(zGaussian(nwells), 0))
      z2 = integrate_rk4(rhs, t, push!(zGaussian(nwells), 0))

      prefactor = prod(conj(z1[even_w, 1]) .* z2[even_w, 1])
      # Refactor as dot product
      sum_even = reshape(sum(z1[even_w, :] .* conj(z2[even_w, :]), 1), timesteps)
      sum_odd = reshape(sum(z1[odd_w, :] .* conj(z2[odd_w, :]), 1), timesteps)

      norm += prefactor * exp(sum_even + sum_odd) / realizations
      next!(prog)
   end

   println("DOne")
   t, norm
end

t, norm = main(100, 0.01, 5, 80000)
plot(x=t, y=real(norm), Geom.line)


"""
   Module for performing Sinc interpolation
From
https://github.com/christiangil/GP-Research/blob/master/julia/src/interpolation_functions.jl
https://github.com/christiangil/GP-Research/blob/master/julia/src/general_functions.jl

Authors: Joe Ninan (original)
         Christian Gilbertson (Converted to Julia and optimized)
         Eric Ford (Further adapted/optimizations)
"""
module SincInterpolation

using Base.Math
using DSP
using Interpolations
using RvSpectMLBase
#import ..RvSpectML: searchsortednearest

export spectra_interpolate

include("low_level.jl")

include("convenience.jl")
export interp_chunk_to_shifted_grid_sinc, interp_chunk_to_shifted_grid_sinc!
export interp_chunk_to_grid_sinc, interp_chunk_to_grid_sinc!

end # module


"""
    pspole(psys::PeriodicStateSpace{PM}[,K]; kwargs...) -> val

Return for the periodic system `psys = (A(t),B(t),C(t),D(t))` the complex vector `val` containing
the characteristic exponents of the periodic matrix `A(t)` (also called the _poles_ of the system `psys`).

Depending on the underlying periodic matrix type `PM`, the optional argument `K` and keyword arguments `kwargs` may have the following values:

- if `PM = PeriodicFunctionMatrix`, or `PM = PeriodicSymbolicMatrix`, or `PM = PeriodicTimeSeriesMatrix`, then `K` is the number of factors used to express the monodromy matrix of `A(t)` (default: `K = 1`)  and `kwargs` are the keyword arguments of  [`pseig(::PeriodicFunctionMatrix)`](@ref);

- if `PM = HarmonicArray`, then `K` is the number of harmonic components used to represent the Fourier series of `A(t)` (default: `K = max(10,nh-1)`, `nh` is the number of harmonics terms of `A(t)`)  and `kwargs` are the keyword arguments of  [`psceig(::HarmonicArray)`](@ref);

- if `PM = FourierFunctionMatrix`, then `K` is the number of harmonic components used to represent the Fourier series of `A(t)` (default: `K = max(10,nh-1)`, `nh` is the number of harmonics terms of `A(t)`)  and `kwargs` are the keyword arguments of  [`psceig(::FourierFunctionMatrix)`](@ref);

- if `PM = PeriodicMatrix` or `PM = PeriodicArray`, then `K` is the starting sample time (default: `K = 1`)  and `kwargs` are the keyword arguments of  [`psceig(::PeriodicMatrix)`](@ref);
"""
function pspole(psys::PeriodicStateSpace, K::Int; kwargs...)
    return psceig(psys.A,K; kwargs...)
end
pspole(psys::PeriodicStateSpace; kwargs...) = psceig(psys.A; kwargs...)
"""
    pszero(psys::PeriodicStateSpace{HarmonicArray}[, N]; P, atol, rtol, fast) -> val
    pszero(psys::PeriodicStateSpace{PeriodicFunctionMatrix}[, N]; P, atol, rtol, fast) -> val
    pszero(psys::PeriodicStateSpace{PeriodicSymbolicMatrix}[, N]; P, atol, rtol, fast) -> val

Compute the finite and infinite zeros of a continuous-time periodic system `psys = (A(t), B(t), C(t), D(t))` in `val`,
where the periodic system matrices `A(t)`, `B(t)`, `C(t)`, and `D(t)` are in a _Harmonic Array_, or
_Periodic Function Matrix_, or _Periodic Symbolic Matrix_ representation
(the last two representation are automatically converted to a _Harmonic Array_ representation).
`N` is the number of selected harmonic components in the Fourier series of the system matrices (default: `N = max(20,nh-1)`,
where `nh` is the maximum number of harmonics terms) and the keyword parameter `P` is the number of full periods
to be considered (default: `P = 1`) to build
a frequency-lifted LTI representation based on truncated block Toeplitz matrices (see [`ps2fls`](@ref)).

The computation of the zeros of the _complex_ lifted system is performed by reducing the corresponding system pencil
to an appropriate Kronecker-like form which exhibits the finite and infinite eigenvalues.
The reduction is performed using orthonal similarity transformations and involves rank decisions based on rank revealing QR-decompositions with column pivoting,
if `fast = true`, or, the more reliable, SVD-decompositions, if `fast = false`. For a system `psys` of period `T`,
the finite zeros are determined as those eigenvalues which have imaginary parts in the interval `[-ω/2, ω/2]`, where `ω = 2π/(P*T)`.
To eliminate possible spurious finite eigenvalues, the intersection of two finite eigenvalue sets is computed
for two lifted systems obtained for `N` and `N+2` harmonic components.
The infinite zeros are determined as the infinite zeros of the LTI system `(A(ti), B(ti), C(ti), D(ti))`
resulting for a random time value `ti`. _Warning:_ While this evaluation of the number of infinite zeros mostly
provides the correct result, there is no theoretical assessment of this approach (counterexamples are welcome!).

The keyword arguments `atol`  and `rtol` specify the absolute and relative tolerances for the nonzero
elements of the underlying lifted system pencil, respectively.
The default relative tolerance is `n*ϵ`, where `n` is the size of the smallest dimension of the pencil, and `ϵ` is the
working machine epsilon.
"""
function pszero(psys::PeriodicStateSpace{<: HarmonicArray}, N::Union{Int,Missing} = missing; P::Int= 1, fast::Bool = true, atol::Real = 0, rtol::Real = 0)
    ismissing(N) && (N = max(20, max(size(psys.A.values,3),size(psys.B.values,3),size(psys.C.values,3),size(psys.D.values,3))-1))
    (N == 0 || islti(psys) ) && (return spzeros(dssdata(psaverage(psys))...; fast, atol1 = atol, atol2 = atol, rtol)[1])
    period = psys.A.period
    ωhp2 = pi/P/period
    n = size(psys.A,1)
    T = promote_type(Float64, eltype(psys.A))
    # employ heuristics to determine fix finite zeros by comparing two sets of computed zeros
    z = spzeros(dssdata(ps2fls(psys, N; P))...; fast, atol1 = atol, atol2 = atol, rtol)[1]
    zf = z[isfinite.(z)]
    ind = sortperm(imag(zf),by=abs);
    nf = count(abs.(imag(zf[ind[1:min(4*n,length(ind))]])) .<=  ωhp2*(1+sqrt(eps(T))))
    zf = zf[ind[1:nf]]
    z2 = spzeros(dssdata(ps2fls(psys, N+2; P))...; fast, atol1 = atol, atol2 = atol, rtol)[1]
    zf2 = z2[isfinite.(z2)]
    ind = sortperm(imag(zf2),by=abs);
    nf2 = count(abs.(imag(zf2[ind[1:min(4*n,length(ind))]])) .<=  ωhp2*(1+sqrt(eps(T))))
    zf2 = zf2[ind[1:nf2]]
    σf = Complex{T}[]
    nf < nf2 || ((zf, zf2) = (zf2, zf))
    atol > 0 || (norms = max(norm(psys.A.values,Inf),norm(psys.B.values,Inf),norm(psys.C.values,Inf),norm(psys.D.values,Inf)))
    tol = atol > 0 ? atol : (rtol > 0 ? rtol*norms : sqrt(eps(T))*norms)
    for i = 1:min(nf,nf2)
        minimum(abs.(zf2 .- zf[i])) < tol  && push!(σf,zf[i])
    end
    isreal(σf) && (σf = real(σf))
    if any(isinf.(z))
       # Conjecture: The number of infinite zeros is the same as that of the time-evaluated system!
       zm = spzeros(dssdata(psteval(psys, period*rand()))...; fast, atol1 = atol, atol2 = atol, rtol)[1]
       return [σf; zm[isinf.(zm)]]
    else
       return σf
    end
end
"""
    pszero(psys::PeriodicStateSpece{FourierFunctionMatrix}[, N]; P, atol, rtol, fast) -> val

Compute the finite and infinite zeros of a continuous-time periodic system `psys = (Af(t), Bf(t), Cf(t), Df(t))` in `val`,
where the periodic system matrices `Af(t)`, `Bf(t)`, `Cf(t)`, and `Df(t)` are in a _Fourier Function Matrix_ representation.
`N` is the number of selected harmonic components in the Fourier series of the system matrices (default: `N = max(20,nh-1)`,
where `nh` is the maximum number of harmonics terms) and the keyword parameter `P` is the number of full periods
to be considered (default: `P = 1`) to build
a frequency-lifted LTI representation based on truncated block Toeplitz matrices (see [`ps2frls`](@ref)).

The computation of the zeros of the _real_ lifted system is performed by reducing the corresponding system pencil
to an appropriate Kronecker-like form which exhibits the finite and infinite eigenvalues.
The reduction is performed using orthonal similarity transformations and involves rank decisions based on rank revealing QR-decompositions with column pivoting,
if `fast = true`, or, the more reliable, SVD-decompositions, if `fast = false`. For a system `psys` of period `T`,
the finite zeros are determined as those eigenvalues which have imaginary parts in the interval `[-ω/2, ω/2]`, where `ω = 2π/(P*T)`.
To eliminate possible spurious finite eigenvalues, the intersection of two finite eigenvalue sets is computed
for two lifted systems obtained for `N` and `N+2` harmonic components.
The infinite zeros are determined as the infinite zeros of the LTI system `(Af(ti), Bf(ti), Cf(ti), Df(ti))`
resulting for a random time value `ti`. _Warning:_ While this evaluation of the number of infinite zeros mostly
provides the correct result, there is no theoretical assessment of this approach (counterexamples are welcome!).

The keyword arguments `atol`  and `rtol` specify the absolute and relative tolerances for the nonzero
elements of the underlying lifted system pencil, respectively.
The default relative tolerance is `n*ϵ`, where `n` is the size of the smallest dimension of the pencil, and `ϵ` is the
working machine epsilon.
"""
function pszero(psys::PeriodicStateSpace{<: FourierFunctionMatrix}, N::Union{Int,Missing} = missing; P::Int= 1, fast::Bool = true, atol::Real = 0, rtol::Real = 0)
    ismissing(N) && (N = max(20, maximum(ncoefficients.(Matrix(psys.A.M))), maximum(ncoefficients.(Matrix(psys.B.M))),
                                   maximum(ncoefficients.(Matrix(psys.C.M))), maximum(ncoefficients.(Matrix(psys.A.M)))))
    (N == 0 || islti(psys) ) && (return spzeros(dssdata(psaverage(psys))...; fast, atol1 = atol, atol2 = atol, rtol)[1])

    # employ heuristics to determine fix finite zeros by comparing two sets of computed zeros
    z = spzeros(dssdata(ps2frls(psys, N; P))...; fast, atol1 = atol, atol2 = atol, rtol)[1]

    period = psys.A.period
    ωhp2 = pi/P/period
    n = size(psys.A,1)
    T = promote_type(Float64, eltype(psys.A))
    zf = z[isfinite.(z)]
    ind = sortperm(imag(zf),by=abs);
    nf = count(abs.(imag(zf[ind[1:min(4*n,length(ind))]])) .<=  ωhp2*(1+sqrt(eps(T))))
    zf = zf[ind[1:nf]]

    z2 = spzeros(dssdata(ps2frls(psys, N+2; P))...; fast, atol1 = atol, atol2 = atol, rtol)[1]
    zf2 = z2[isfinite.(z2)]
    ind = sortperm(imag(zf2),by=abs);
    nf2 = count(abs.(imag(zf2[ind[1:min(4*n,length(ind))]])) .<=  ωhp2*(1+sqrt(eps(T))))
    zf2 = zf2[ind[1:nf2]]
    σf = Complex{T}[]
    nf < nf2 || ((zf, zf2) = (zf2, zf))
    atol > 0 || (norms = max(norm(coefficients(psys.A.M),Inf),norm(coefficients(psys.B.M),Inf),norm(coefficients(psys.C.M),Inf),norm(coefficients(psys.D.M),Inf)))
    tol = atol > 0 ? atol : (rtol > 0 ? rtol*norms : sqrt(eps(T))*norms)
    for i = 1:min(nf,nf2)
        minimum(abs.(zf2 .- zf[i])) < tol  && push!(σf,zf[i])
    end
    isreal(σf) && (σf = real(σf))

    if any(isinf.(z))
       # Conjecture: The number of infinite zeros is the same as that of the time-evaluated system!
       zm = spzeros(dssdata(psteval(psys, period*rand()))...; fast, atol1 = atol, atol2 = atol, rtol)[1]
       zf = [σf; zm[isinf.(zm)]]
    end
    nz = length(zf)
    nz > n && (@warn "$(nz-n) spurious finite zero(s) present")
    return zf
end
"""
     pszero(psys::PeriodicStateSpace{PeriodicMatrix}[, K]; atol, rtol, fast) -> val
     pszero(psys::PeriodicStateSpace{PeriodicArray}[, K]; atol, rtol, fast) -> val

Compute the finite and infinite zeros of a discrete-time periodic system `psys = (A(t), B(t), C(t), D(t))` in `val`,
where the periodic system matrices `A(t)`, `B(t)`, `C(t)`, and `D(t)` are in either _Periodic Matrix_ or _Periodic Array_ representation.
The optional argument `K` specifies a desired time to start the sequence of periodic matrices (default: `K = 1`).

The computation of zeros relies on the _fast_ structure exploiting reduction of singular periodic pencils as described in [1],
which separates a matrix pencil `M-λN` which contains the infinite and finite zeros
and the left and right Kronecker indices.
The reduction is performed using orthonal similarity transformations and involves rank decisions based
on rank revealing QR-decompositions with column pivoting,
if `fast = true`, or, the more reliable, SVD-decompositions, if `fast = false`.

The keyword arguments `atol` and `rtol` specify the absolute and relative tolerances for the nonzero
elements of the periodic system matrices, respectively.
The default relative tolerance is `n*ϵ`, where `n` is proportional with the largest dimension of the periodic matrices,
and `ϵ` is the working machine epsilon.

_References_

[1] A. Varga and P. Van Dooren. Computing the zeros of periodic descriptor systems.
    Systems & Control Letters 50:371–381, 2003.
"""
function pszero(psys::PeriodicStateSpace{<: AbstractPeriodicArray{:d,T}}, K::Int = 1; fast::Bool = true, atol::Real = 0, rtol::Real = 0) where {T}
    islti(psys)  && (return spzeros(psys.A.M[1], I, psys.B.M[1], psys.C.M[1], psys.D.M[1]; fast, atol1 = atol, atol2 = atol, rtol)[1])

    (na, nb, nc, nd) = (psys.A.dperiod, psys.B.dperiod, psys.C.dperiod, psys.D.dperiod)
    N = na*psys.A.nperiod
    p, m = size(psys)
    ndx, nx = size(psys.A)
    patype = length(nx) == 1
    si = [getpm(psys.A,K,na) getpm(psys.B,K,nb); getpm(psys.C,K,nc) getpm(psys.D,K,nd)]
    ndxi = ndx[patype ? 1 : mod(K-1,na)+1]
    nxi1 = nx[patype ? 1 : mod(K,na)+1]
    ti = [ -I zeros(T,ndxi,m); zeros(T,p,nxi1+m)]
    tolr = atol
    atol == 0 && (sc = sum(nx.+p)* eps(float(T)))
    n1 = size(si,2)
    for i = K:K+N-3
        m1 = size(si,1)
        si1 = [getpm(psys.A,i+1,na) getpm(psys.B,i+1,nb); getpm(psys.C,i+1,nc) getpm(psys.D,i+1,nd)]
        mi1 = size(si1,1)
        ndxi1 = mi1-p
        nxi2 = nx[patype ? 1 : mod(i+1,na)+1]
        ti1 = [ -I zeros(T,ndxi1,m); zeros(T,p,nxi2+m)]

        F = qr([ ti; si1 ], ColumnNorm())
        nr = minimum(size(F.R))
        # compute rank of r
        ss = abs.(diag(F.R[1:nr,1:nr]))
        atol == 0 && ( tolr = sc * maximum(ss))
        rankr = count(ss .> tolr)
        si = F.Q'*[si; zeros(T,mi1,n1)]; si=si[rankr+1:end,:]
        ti = F.Q'*[ zeros(T,m1,nxi2+m); ti1]; ti = ti[rankr+1:end,:]
    end
    sn = [getpm(psys.A,K+N-1,na) getpm(psys.B,K+N-1,nb); getpm(psys.C,K+N-1,nc) getpm(psys.D,K+N-1,nd)]
    ndxi = ndx[patype ? 1 : mod(K+N-2,na)+1]
    nxi1 = nx[patype ? 1 : mod(K+N-1,na)+1]
    tn = [ I zeros(T,ndxi,m); zeros(T,p,nxi1+m)]
    a = [ zeros(T,size(tn)...) sn; si ti]
    e = [ tn zeros(T,size(sn)...); zeros(T,size(si)...) zeros(T,size(ti)...)]
    return complex(pzeros(a, e; fast, atol1 = atol, atol2 = atol, rtol)[1]).^(1/N)
end
function pszero(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix, PeriodicSymbolicMatrix, PeriodicTimeSeriesMatrix}}, K::Union{Int,Missing} = missing; kwargs...)
    return pszero(convert(PeriodicStateSpace{HarmonicArray},psys), K; kwargs...)
end
"""
     isstable(psys[, K = 1]; smarg = 1, fast = false, offset = sqrt(ϵ), kwargs...) -> Bool

Return `true` if the continuous-time periodic system `psys` has only stable characteristic multipliers and `false` otherwise.

To assess the stability, the absolute values of the characteristic multipliers (i.e., the eigenvalues of the monodromy matrix)
must be less than `smarg-β`, where `smarg` is the discrete-time stability margin (default: `smarg = 1`)  and
`β` is an offset specified via the keyword parameter `offset = β` to be used to numerically assess the stability
of eigenvalues. The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

The monodromy matrix is determined as a product `K` state transition matrices (default: `K = 1`)
computed by integrating numerically a homogeneous linear ODE with periodic coefficients
(see function [`monodromy`](@ref) for options which can be specified via the keyword arguments `kwargs`).
If `fast = false` (default) then the characteristic multipliers are computed using an approach
based on the periodic Schur decomposition [1], while if `fast = true`
the structure exploiting reduction [2] of an appropriate lifted pencil is employed.
This later option may occasionally lead to inaccurate results for large number of matrices.

_References_

[1] A. Bojanczyk, G. Golub, and P. Van Dooren,
    The periodic Schur decomposition. Algorithms and applications, Proc. SPIE 1996.

[2] A. Varga & P. Van Dooren. Computing the zeros of periodic descriptor systems.
    Systems and Control Letters, 50:371-381, 2003.
"""
function isstable(psys::PeriodicStateSpace{<: AbstractPeriodicArray{:c,T}}, K::Int = 1; smarg::Real = 1,
                  fast::Bool = false, offset::Real = sqrt(eps(float(real(T)))), kwargs...) where T
    ev = pseig(monodromy(convert(PeriodicFunctionMatrix,psys.A), K; kwargs...); fast)
    return all(abs.(ev) .< smarg-abs(offset))
end
"""
    isstable(psys; smarg = 1, fast = false, offset = sqrt(ϵ)) -> Bool

Return `true` if the discrete-time periodic system `psys` has only stable characteristic multipliers and `false` otherwise.

To assess the stability, the absolute values of the characteristic multipliers (i.e., the eigenvalues of the monodromy matrix)
must be less than `smarg-β`, where `smarg` is the discrete-time stability margin (default: `smarg = 1`)  and
`β` is an offset specified via the keyword parameter `offset = β` to be used to numerically assess the stability
of eigenvalues. The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

If `fast = false` (default) then the characteristic multipliers are computed using an approach
based on the periodic Schur decomposition [1], while if `fast = true`
the structure exploiting reduction [2] of an appropriate lifted pencil is employed.
This later option may occasionally lead to inaccurate results for large number of matrices.

_References_

[1] A. Bojanczyk, G. Golub, and P. Van Dooren,
    The periodic Schur decomposition. Algorithms and applications, Proc. SPIE 1996.

[2] A. Varga & P. Van Dooren. Computing the zeros of periodic descriptor systems.
    Systems and Control Letters, 50:371-381, 2003.
"""
function isstable(psys::PeriodicStateSpace{<: AbstractPeriodicArray{:d,T}}; smarg::Real = 1, fast::Bool = false,
                  offset::Real = sqrt(eps(float(real(T))))) where T
    ev = pseig(psys.A; fast)
    return all(abs.(ev) .< smarg-abs(offset))
end


# This file contains the two PV cell models (SDM and DDM)
# and functions to compute the SSE as well as the RMSE.

const k = 1.3806503e-23     # Boltamann constant
const q = 1.60217646e-19    # electron charge

"""
    Single diode model with five parameters

V, I are the input voltage and current.

Ir:  photocurrent generated by the solar cell (A)
I0: the reverse saturation current of the diode (μA)
n: diode ideality factor
Rs: series resistance (Ω)
Rp: parallel resistance (Ω)
"""
function model_sdm(V, I, Iph, I0, n, Rs, Rp, T)
    tmp = V+ I*Rs
    return Iph - I0*1e-6*(exp(q*tmp/(n*k*T)) - 1) - tmp/Rp
end

"""
    Double diode model with seven parameters

V, I are the input voltage and current.

Iph:  photocurrent generated by the solar cell (A)
I01, I02: the reverse saturation current of the diode (μA)
n1, n2: diode ideality factor
Rs: series resistance (Ω)
Rp: parallel resistance (Ω)
"""
function model_ddm(V, I, Iph, I01, I02, n1, n2, Rs, Rp, T)
    tmp = V+ I*Rs
    c = k*T/q
    return Iph - I01*1e-6*(exp(tmp/(n1*c)) - 1) - I02*1e-6*(exp(tmp/(n2*c)) - 1) - tmp/Rp
end

"""
	Compute the sum of squared error (SSE)

data: a matrix of V-I data, each row denoting a (V, I) point
model: a function for the SDM or the DDM, whose input is (V, I) and output is the estimated current
"""
function calculate_sse(data, model::Function)
    sse = 0.0
    for (Vm, Im) in eachrow(data)
        Ic = model(Vm, Im)
        sse += (Im - Ic)^2
    end
    return sse
end

"""
	Compute the root mean square error (RMSE)

data: a matrix of V-I data, each row denoting a (V, I) point
model: a function for the SDM or the DDM, whose input is (V, I) and output is the estimated current
"""
function calculate_rmse(data, model::Function)
    sse = calculate_sse(data, model)
    return sqrt(sse / size(data, 1))
end

"""
    Compute the root mean square error (RMSE)

data: a matrix of V-I data
θ: parameter vector. Depending on the length of θ, either SDM or DDM will be used.
T: temperature in Calvin (K)
"""
function calculate_rmse(data, θ, T)
    model = length(θ) == 5 ? model_sdm : model_ddm
    return calculate_rmse(data, (V, I)->model(V, I, θ..., T))
end

"""
	Compute the mean bias error (MBE)

data: a matrix of V-I data, each row denoting a (V, I) point
model: a function for the SDM or the DDM, whose input is (V, I) and output is the estimated current
"""
function calculate_mbe(data, model::Function)
    mbe = 0.0
    for (Vm, Im) in eachrow(data)
        Ic = model(Vm, Im)
        mbe += (Im - Ic)
    end
    return mbe / size(data, 1)
end

"""
	Compute the mean absolute percentage error (MAPE).

See https://en.wikipedia.org/wiki/Mean_absolute_percentage_error for a definition.

data: a matrix of V-I data, each row denoting a (V, I) point
model: a function for the SDM or the DDM, whose input is (V, I) and output is the estimated current
"""
function calculate_mape(data, model::Function)
    mape = 0.0
    for (Vm, Im) in eachrow(data)
        Ic = model(Vm, Im)
        d = abs((Im - Ic) / Im)
        # @show d
        mape += d
    end
    return mape / size(data, 1) * 100
end

"""
	Compute the mean absolute error (MAE)

data: a matrix of V-I data, each row denoting a (V, I) point
model: a function for the SDM or the DDM, whose input is (V, I) and output is the estimated current
"""
function calculate_mae(data, model::Function)
    mae = 0.0
    for (Vm, Im) in eachrow(data)
        Ic = model(Vm, Im)
        mae += abs(Im - Ic)
    end
    return mae / size(data, 1)
end


using MAGMA

using Test, LinearAlgebra

@testset "random singular precision matrices" begin

    matrixToTest = rand(Cfloat, 2, 2)

    right_answer = svd(matrixToTest).S
    S = right_answer

    jobu = 'A'
    jobvt = 'A'

    ldu=2
    ldvt=2
    lwork=400
    success=magmaInit()

    result = gesvd!(jobu,jobvt,matrixToTest)

    s = result[2]

    magmaFinalize()

    diff = S .- s
    error_value = norm(diff)

    @test error_value < 1e-7

    if error_value >= 1e-7
        println("Unfortunately, the test failed.")
        println("Here is some maybe useful information:")
        println("the element_type is ", typeof(matrixToTest))
        println("the right answer = ", S)
        println("However, MAGMA got the answer = ", s)
        println("The info returned by MAGMA is: ", info[1])
    end
end