"""
    r = gnrank(sys, fastrank = true, atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ )

Compute the normal rank `r` of the transfer function matrix `G(λ)` of the descriptor system `sys = (A-λE,B,C,D)`.

The normal rank of `G(λ)` is evaluated as `r = k - n`, where `k` is the normal rank of
the system matrix pencil

              | A-λE | B |
      S(λ) := |------|---|
              |  C   | D |

and `n` is the order of the system `sys` (i.e., the size of `A`).

If `fastrank = true`, the normal rank of `S(λ)` is evaluated by counting the singular values of `S(γ)` greater than `max(max(atol1,atol2), rtol*σ₁)`,
where `σ₁` is the largest singular value of `S(γ)` and `γ` is a randomly generated value.
If `fastrank = false`, the rank is evaluated as `nr + ni + nf + nl`, where `nr` and `nl` are the sums of right and left Kronecker indices,
respectively, while `ni` and `nf` are the number of infinite and finite eigenvalues, respectively. The sums `nr+ni` and
`nf+nl` are determined from an appropriate Kronecker-like form of the pencil `S(λ)`, exhibiting the spliting of the right and left structures.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively,
the absolute tolerance for the nonzero elements of `A`, `B`, `C`, `D`,
the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `B`, `C`, `D` and `E`.
The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function gnrank(sys::DescriptorStateSpace; fastrank = true, atol::Real = 0, atol1::Real = atol, atol2::Real = atol,
                rtol::Real = (max(sys.nx,sys.nu,sys.ny)*eps(real(float(one(eltype(sys.A))))))*iszero(min(atol1,atol2)))
    sys.nx == 0 && (return rank(sys.D; atol = atol1, rtol))
    return max(0,sprank(dssdata(sys)..., atol1 = atol1, atol2 = atol2, rtol = rtol, fastrank = fastrank) - sys.nx)
end
"""
    val = gzero(sys; fast = false, atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ)

Return for the descriptor system `sys = (A-λE,B,C,D)` the complex vector `val` containing the
finite and infinite Smith zeros of the system matrix pencil

               | A-λE | B |
       S(λ) := |------|---| .
               |  C   | D |

The values in `val` are called the _invariant zeros_ of the pencil `S(λ)` and are the _transmission zeros_ of the
transfer function matrix of `sys` if `A-λE` is _regular_ and the descriptor system realization
`sys = (A-λE,B,C,D)` is _irreducible_.

The computation of the zeros is performed by reducing the pencil `S(λ)` to an appropriate Kronecker-like form
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 arguements `atol1`, `atol2`  and `rtol` specify the absolute tolerance for the nonzero
elements of `A`, `B`, `C` and `D`, the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `E`, `B`, `C` and `D`, respectively.
The default relative tolerance is `n*ϵ`, where `n` is the size of `A`, and `ϵ` is the
working machine epsilon.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function gzero(SYS::DescriptorStateSpace;fast = false, atol::Real = 0, atol1::Real = atol, atol2::Real = atol,
    rtol::Real = SYS.nx*eps(real(float(one(real(eltype(SYS.A)))))*iszero(min(atol1,atol2))) )
    # pzeros([SYS.A SYS.B; SYS.C SYS.D], [SYS.E zeros(SYS.nx,SYS.nu); zeros(SYS.ny,SYS.nx+SYS.nu)]; fast = fast, atol1 = atol1,
    # atol2 = atol2, rtol = rtol )[1]
    return spzeros(dssdata(SYS)...; fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol)[1]
end
"""
    val = gpole(sys; fast = false, atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ)

Return for the descriptor system `sys = (A-λE,B,C,D)` the complex vector `val` containing
the finite and infinite zeros of the system pole pencil `P(λ) := A-λE`.
The values in `val` are the poles of the transfer function matrix of `sys`, if `A-λE` is _regular_ and the
descriptor system realization `sys = (A-λE,B,C,D)` is _irreducible_.
If the pencil `A-λE` is singular, `val` also contains `NaN` elements,
whose number is the rank deficiency of the pencil  `A-λE`.

For `E` nonsingular, `val` contains the generalized eigenvalues of the pair `(A,E)`.
For `E` singular, `val` contains the zeros of `P(λ)`, which are computed
by reducing the pencil `P(λ)` to an appropriate Kronecker-like form
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 regularity of `A-λE` is implicitly checked. If `check_reg = true`, an error message is issued if the pencil
`A-λE` is singular. If `check_reg = false` and the pencil `A-λE` is singular, then `n-r` poles are set to `NaN`, where
`n` is the system order and `r` is the normal rank of `A-λE`.

The keyword arguements `atol1`, `atol2`  and `rtol` specify the absolute tolerance for the nonzero
elements of `A`, the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A` and `E`, respectively.
The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function gpole(SYS::DescriptorStateSpace{T}; fast = false, atol::Real = 0, atol1::Real = atol, atol2::Real = atol,
               rtol::Real = SYS.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2)), check_reg = false ) where T
    T <: BlasFloat ? T1 = T : T1 = promote_type(Float64,T)
    A = copy_oftype(SYS.A,T1)
    if SYS.E == I
       return isschur(A) ? ordeigvals(A) : MatrixPencils.eigvalsnosort(A)
    else
       E = copy_oftype(SYS.E,T1)
       if norm(E,Inf) > atol2
          epsm = eps(float(one(real(T1))))
          isschur(A,E) && rcond(UpperTriangular(E)) >= SYS.nx*epsm && (return ordeigvals(A,E)[1])
          istriu(E) && rcond(UpperTriangular(E)) >= SYS.nx*epsm && (return MatrixPencils.eigvalsnosort(A,E))
          rcond(E) >= SYS.nx*epsm && (return MatrixPencils.eigvalsnosort(A,E))
       end
       # singular E
       poles, nip, krinfo = pzeros(A, E; fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol )
       check_reg && (SYS.nx == krinfo.nrank || error("the system has a singular pole pencil"))
       return [poles;NaN*ones(SYS.nx-krinfo.nrank)]
    end
end
"""
    gzeroinfo(sys; smarg, fast = false, atol = 0, atol1 = atol, atol2 = atol,
              rtol = n*ϵ, offset = sqrt(ϵ)) -> (val, info)

Return for the descriptor system `sys = (A-λE,B,C,D)` the complex vector `val` containing
the finite and infinite Smith zeros of the system matrix pencil `S(λ)`

              | A-λE | B |
       S(λ) = |------|---|
              |  C   | D |

and the named tuple `info` containing information on the Kronecker structure of the pencil `S(λ)`.
The values in `val` are called the _invariant zeros_ of the pencil `S(λ)` and are the _transmission zeros_ of the
transfer function matrix of `sys` if `A-λE` is _regular_ and the descriptor system realization
`sys = (A-λE,B,C,D)` is _irreducible_.

For stability analysis purposes, a stability margin `smarg` can be specified for the finite zeros,
in conjunction with a stability domain boundary offset `β` to numerically assess the  finite zeros
which belong to the boundary of the stability domain as follows:
in the continuous-time case, these are the finite zeros having real parts in the interval
`[smarg-β, smarg+β]`, while in the discrete-time case, these are the finite zeros having moduli in the interva
`[smarg-β, smarg+β]`. The default value of the stability margin `smarg` is `0` for a continuous-time system and
`1` for a discrete-time system.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

The named tuple `info` contains the following information:

`info.nfz` is the number of finite eigenvalues of the pencil `S(λ)` (also the number of finite zeros of `sys`);

`info.niev` is the number of infinite eigenvalues of the pencil `S(λ)`;

`info.nisev` is the number of  _simple_ infinite eigenvalues of the pencil `S(λ)`;

`info.niz` is the number of infinite zeros of the system `sys`;

`info.nfsz` is the number of finite stable zeros, i.e., the finite zeros
having real parts or moduli less than `smarg-β` for a continuous- or discrete-time system, respectively;

`info.nfsbz` is the number of finite zeros on the boundary of the
          stability domain, i.e., the finite zeros
          having real parts or moduli in the interval `[smarg-β, smarg+β]` for a continuous- or discrete-time system, respectively;

`info.nfuz` is the number of finite unstable zeros, i.e., the finite zeros
having real parts or moduli greater than `smarg+β` for a continuous- or discrete-time system, respectively;

`info.nrank` is the normal rank of the pencil `S(λ)`;

`info.miev` is an integer vector, which contains the multiplicities
          of the infinite eigenvalues of the pencil `S(λ)`
           (also the dimensions of the elementary infinite blocks in the
          Kronecker form of `S(λ)`);

`info.miz` is an integer vector, which contains the information on the
           multiplicities of the infinite zeros of `S(λ)` as follows:
           `S(λ)` has `info.mip[i]` infinite zeros of multiplicity `i`, and
             is empty if `S(λ)` has no infinite zeros;

`info.rki` is an integer vector, which contains the _right Kronecker indices_
         of the pencil `S(λ)` (empty for a regular pencil);

`info.lki` is an integer vector, which contains the _left Kronecker indices_
        of the pencil `S(λ)` (empty for a regular pencil);

`info.regular` is set to `true`,  if the pencil `S(λ)` is regular and set to
`false`, if the pencil `S(λ)` is singular;

`info.stable` is set to `true`, if the pencil `S(λ)` has only stable
                finite zeros and all its infinite zeros are
                 simple and  is set to `false` otherwise.

_Note:_ The finite zeros and the finite eigenvalues of the pencil
`S(λ)` are the same, but the multiplicities of infinite eigenvalues
   are in excess with one to the multiplicities of infinite zeros.

The computation of the zeros is performed by reducing the pencil `S(λ)` to an appropriate Kronecker-like form
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 arguements `atol1`, `atol2`  and `rtol` specify the absolute tolerance for the nonzero
elements of `A`, `B`, `C` and `D`, the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `E`, `B`, `C` and `D`, respectively.
The default relative tolerance is `n*ϵ`, where `n` is the size of `A` and `ϵ` is the
working machine epsilon.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function gzeroinfo(SYS::DescriptorStateSpace{T}; smarg::Real = SYS.Ts == 0 ? 0 : 1, fast = false,
                   atol::Real = zero(float(real(T))), atol1::Real = atol, atol2::Real = atol,
                   rtol::Real = SYS.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2)),
                   offset::Real = sqrt(eps(float(real(T)))) ) where T
    val, miz, krinfo = spzeros(dssdata(SYS)...; fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol)
    nfsz, nfsbz, nfuz = eigvals_info(val[isfinite.(val)], smarg, SYS.Ts != 0, offset)
    niev = sum(krinfo.id)
    nisev = niev == 0 ? 0 : krinfo.id[1]
    niz = sum(miz)
    nfz = krinfo.nf
    return val, (nfz = nfz, niev = niev, nisev = nisev, niz = niz, nfsz = nfsz, nfsbz = nfsbz,
                 nfuz = nfuz, nrank = krinfo.nrank, miev = krinfo.id, miz = miz,
                 rki = krinfo.rki, lki = krinfo.lki, regular = (sum(krinfo.rki) == 0 && sum(krinfo.lki) == 0),
                 stable = (niz == 0 && nfsz == nfz))
end
"""
    gpoleinfo(sys; smarg, fast = false, atol = 0, atol1 = atol, atol2 = atol,
              rtol = n*ϵ, offset = sqrt(ϵ)) -> (val, info)

Return for the descriptor system `sys = (A-λE,B,C,D)` the complex vector `val` containing
the finite and infinite zeros of the system pole pencil `P(λ) := A-λE` and the named tuple `info` containing information on
the eigenvalue structure of the pole pencil `P(λ)`. The values in `val` are the _poles_ of the
transfer function matrix of `sys`, if `A-λE` is _regular_ and the
descriptor system realization `sys = (A-λE,B,C,D)` is _irreducible_.
If the pencil `A-λE` is singular, `val` also contains `NaN` elements,
whose number is the rank deficiency of the pencil  `A-λE`.

For stability analysis purposes, a stability margin `smarg` can be specified for the finite eigenvalues,
in conjunction with a stability domain boundary offset `β` to numerically assess the  finite eigenvalues
which belong to the boundary of the stability domain as follows:
in the continuous-time case, these are the finite eigenvalues having real parts in the interval
`[smarg-β, smarg+β]`, while in the discrete-time case, these are the finite eigenvalues having moduli in the interval
`[smarg-β, smarg+β]`. The default value of the stability margin `smarg` is `0` for a continuous-time system and
`1` for a discrete-time system.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

The named tuple `info` contains the following information:

`info.nfev` is the number of finite eigenvalues of the pencil `A-λE` (also the number of finite poles of `sys`);

`info.niev` is the number of infinite eigenvalues of the pencil `A-λE`;

`info.nisev` is the number of _simple_ infinite eigenvalues of the pencil `A-λE` (also known as non-dynamic modes);

`info.nip` is the number of infinite poles of the system `sys`;

`info.nfsev` is the number of finite stable eigenvalues, i.e., the finite eigenvalues
having real parts or moduli less than `smarg-β` for a continuous- or discrete-time system, respectively;

`info.nfsbev` is the number of finite eigenvalues on the boundary of the
          stability domain, i.e., the finite eigenvalues
          having real parts or moduli in the interval `[smarg-β, smarg+β]` for a continuous- or discrete-time system, respectively;

`info.nfuev` is the number of finite unstable eigenvalues, i.e., the finite eigenvalues
having real parts or moduli greater than `smarg+β` for a continuous- or discrete-time system, respectively;

`info.nhev` is the number of _hidden_ eigenvalues set to `NaN`
         (can be nonzero only if the pencil `A-λE` is singular);

`info.nrank` is the normal rank of the pencil `A-λE`;

`info.miev` is an integer vector, which contains the multiplicities
          of the infinite eigenvalues of the pencil `A-λE` as follows:
          the `i`-th element `info.miev[i]` is the order of an infinite elementary divisor
          (i.e., the multiplicity of an infinite eigenvalue) and
          the number of infinite poles is the sum of the components of `info.miev`;

`info.mip` is an integer vector, which contains the information on the
           multiplicities of the infinite zeros of `A-λE` as follows:
           the `i`-th element `info.mip[i]` is equal to `k-1`, where `k` is the order of an infinite elementary
             divisor with `k > 0` and the number of infinite poles is the sum of the components of `info.mip`;

`info.rki` is an integer vector, which contains the _right Kronecker indices_
           of the pencil `A-λE` (empty for a regular pencil);

`info.lki` is an integer vector, which contains the _left Kronecker indices_
           of the pencil `A-λE` (empty for a regular pencil);

`info.regular` is set to `true`,  if the pencil `A-λE` is regular and set to
`false`, if the pencil `A-λE` is singular;

`info.proper` is set to `true`, if the pencil `A-λE` is regular and all its infinite
                 eigenvalues are simple (has only non-dynamic modes), or
                 is set to `false`, if the pencil `A-λE` is singular or has higher order infinite eigenvalues;

`info.stable` is set to `true`, if the pencil `A-λE` is regular, has only stable
                finite eigenvalues and all its infinite eigenvalues are
                 simple (has only non-dynamic modes), and  is set to `false` otherwise.

_Note:_ The finite poles and the finite eigenvalues of the pencil `P(λ)` are the same,
but the multiplicities of infinite eigenvalues of `P(λ)` are in excess with one to the multiplicities of infinite poles.

For the reduction of the pencil `P(λ)` to an appropriate Kronecker-like form
orthonal similarity transformations are performed, which involve 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 arguements `atol1`, `atol2`  and `rtol` specify the absolute tolerance for the nonzero
elements of `A`, the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A` and `E`, respectively.
The default relative tolerance is `n*ϵ`, where `n` is the size of `P(λ)`, and `ϵ` is the
working machine epsilon.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function gpoleinfo(SYS::DescriptorStateSpace{T}; smarg::Real = SYS.Ts == 0 ? 0 : 1, fast = false,
                   atol::Real = zero(float(real(T))), atol1::Real = atol, atol2::Real = atol,
                   rtol::Real = SYS.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2)),
                   offset::Real = sqrt(eps(float(real(T))))) where T
    disc = (SYS.Ts != 0)
    n = SYS.nx
    T <: BlasFloat ? T1 = T : T1 = promote_type(Float64,T)
    A = copy_oftype(SYS.A,T1)
    if SYS.E == I
       isschur(A) ? val = ordeigvals(A) : val = MatrixPencils.eigvalsnosort(A)
       nfsev, nfsbev, nfuev = eigvals_info(val, smarg, disc, offset)
       return val, (nfev = n, niev = 0, nisev = 0, nip = 0, nfsev = nfsev, nfsbev = nfsbev,
                     nfuev = nfuev, nhev = 0, nrank = n, miev = Int[], mip = Int[],
                     rki = Int[], lki = Int[], regular = true, proper = true, stable = (nfsev == n))
    else
       krinfo = nothing
       E = copy_oftype(SYS.E,T1)
       if norm(E,Inf) > atol2
          epsm = eps(float(one(real(T))))
          if isschur(A,E) && rcond(UpperTriangular(E)) >= n*epsm
             val = ordeigvals(A,E)[1]
          elseif istriu(E) && rcond(UpperTriangular(E)) >= n*epsm
             val = MatrixPencils.eigvalsnosort(A,E)
          elseif rcond(E) >= n*epsm
             val = MatrixPencils.eigvalsnosort(A,E)
          else
             # singular E
             val, mip, krinfo = pzeros(A, E; fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol )
          end
       else
          val, mip, krinfo = pzeros(A, E; fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol )
       end
       nfsev, nfsbev, nfuev = eigvals_info(val[isfinite.(val)], smarg, disc, offset)
       isnothing(krinfo) &&
           (return val, (nfev = n, niev = 0, nisev = 0, nip = 0, nfsev = nfsev, nfsbev = nfsbev,
                        nfuev = nfuev, nhev = 0, nrank = n, miev = Int[], mip = Int[],
                        rki = Int[], lki = Int[], regular = true, proper = true, stable = (nfsev == n)))
       nhev = n - krinfo.nrank
       nhev > 0 && (@warn "The system has a singular pole pencil")
       val = [val;NaN*ones(nhev)]
       niev = sum(krinfo.id)
       nip = sum(mip)
       nfev = n-niev
       return val, (nfev = nfev, niev = niev, nisev = count(krinfo.id .== 1), nip = nip, nfsev = nfsev, nfsbev = nfsbev,
                    nfuev = nfuev, nhev = nhev, nrank = krinfo.nrank, miev = krinfo.id, mip = mip,
                    rki = krinfo.rki, lki = krinfo.lki, regular = (nhev == 0), proper = (nip == 0),
                    stable = (nip == 0 && nfsev == nfev))
    end
end
function eigvals_info(val::AbstractVector, smarg::Real, disc::Bool, offset::Real)
    if disc
       nf = count(abs.(val) .< smarg-offset)
       nu = count(abs.(val) .> smarg+offset)
    else
       nf = count(real.(val) .< smarg-offset)
       nu = count(real.(val) .> smarg+offset)
    end
    return nf, length(val)-nf-nu, nu
end
"""
    isregular(sys; atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ)

Return `true` if the descriptor system `sys = (A-λE,B,C,D)` has a regular pole pencil `A-λE` and `false` otherwise.

To test whether the pencil `A-λE` is regular (i.e., `det(A-λE) ̸≡ 0`),
the underlying computational procedure reduces the pencil `A-λE` to an appropriate Kronecker-like form,
which provides information on the rank of `A-λE`.

The keyword arguements `atol1`, `atol2` and `rtol` specify the absolute tolerance for the nonzero
elements of `A`, the absolute tolerance for the nonzero elements of `E`, and the relative tolerance
for the nonzero elements of `A` and `E`, respectively.
The default relative tolerance is `n*ϵ`, where `n` is the size of  `A`, and `ϵ` is the
working machine epsilon.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function isregular(SYS::DescriptorStateSpace{T}; atol::Real = zero(float(real(T))), atol1::Real = atol, atol2::Real = atol,
    rtol::Real = SYS.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2))) where T
    SYS.E == I && (return true)
    epsm = eps(float(one(real(T))))
    istriu(SYS.E) && rcond(UpperTriangular(SYS.E)) > SYS.nx*epsm && (return true)
    rcond(SYS.E) > SYS.nx*epsm && (return true)
    return MatrixPencils.isregular(SYS.A, SYS.E, atol1 = atol1, atol2 = atol2, rtol = rtol )
end
"""
    isproper(sys; atol = 0, atol1 = atol, atol2 = atol, rtol = = n*ϵ, fast = true)

Return `true` if the transfer function matrix `G(λ)` of the descriptor system `sys = (A-λE,B,C,D)` is proper
and `false` otherwise.

For a descriptor system realization `sys = (A-λE,B,C,D)` without uncontrollable and unobservable infinite eigenvalues,
it is checked that the pencil `A-λE` has no infinite eigenvalues or, if infinite eigenvalues exist,
all infinite eigenvalues are simple. If the original descriptor realization has uncontrollable or
unobservable infinite eigenvalues, these are elliminated using orthogonal pencil reduction algorithms.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively,
the absolute tolerance for the nonzero elements of `A`, `B`, `C`, `D`,
the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `B`, `C`, `D` and `E`.
The default relative tolerance is `n*ϵ`, where `n` is the order of `A` and `ϵ` is the working machine epsilon.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function isproper(SYS::DescriptorStateSpace{T}; fast::Bool = true, atol::Real = zero(float(real(T))), atol1::Real = atol, atol2::Real = atol,
    rtol::Real = SYS.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2))) where T
    (SYS.E == I || SYS.nx == 0)   && (return true)
    epsm = eps(float(one(real(T))))
    istriu(SYS.E) && rcond(UpperTriangular(SYS.E)) > SYS.nx*epsm &&
                     (return true)
    rcond(SYS.E) > SYS.nx*epsm && (return true)
    # check regularity for singular E
    MatrixPencils.isregular(SYS.A, SYS.E, atol1 = atol1, atol2 = atol2, rtol = rtol ) || (return false)
    # compute a realizations without uncontrollable and unobservable infinite eigenvalues
    A, E, = lsminreal2(SYS.A, SYS.E, SYS.B, SYS.C, SYS.D;
             fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol, infinite = true,
             contr = true, obs = true, noseig = false)
    krinfo = pkstruct(A, E; fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol )
    return (isempty(krinfo.id) || maximum(krinfo.id) == 1)
end
"""
    isstable(sys[, smarg]; fast = true, atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ, offset = sqrt(ϵ))

Return `true` if the descriptor system `sys = (A-λE,B,C,D)` has only stable poles and `false` otherwise.

It is checked that the pole pencil `P(λ) := A-λE` has no infinite eigenvalues or, if infinite eigenvalues exist,
all infinite eigenvalues are simple, and additionally the real parts of all finite eigenvalues  are
less than `smarg-β` for a continuous-time system or
have moduli less than `smarg-β` for a discrete-time system, where `smarg` is the stability margin and
`β` is the stability domain boundary offset.
The default value of the stability margin `smarg` is `0` for a continuous-time system and
`1` for a discrete-time system.
The offset  `β` to be used to numerically assess the stability of eigenvalues
can be specified via the keyword parameter `offset = β`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

For `E` singular, the computation of the poles is performed by reducing the pencil `P(λ)` to an appropriate Kronecker-like form
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 `atol1`, `atol2`, and `rtol`, specify, respectively,
the absolute tolerance for the nonzero elements of `A`, `B`, `C`, `D`,
the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `B`, `C`, `D` and `E`.
The default relative tolerance is `n*ϵ`, where `n` is the order of `A` and `ϵ` is the working machine epsilon.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function isstable(SYS::DescriptorStateSpace{T}, smarg::Real = SYS.Ts == 0 ? 0 : 1;
                  fast = false, atol::Real = 0, atol1::Real = atol, atol2::Real = atol,
                  rtol::Real = SYS.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2)),
                  offset::Real = sqrt(eps(float(real(T))))) where T
    disc = (SYS.Ts != 0)
    β = abs(offset);
    if SYS.E == I
       isschur(SYS.A) ? poles = ordeigvals(SYS.A) : poles = eigvals(SYS.A)
    else
       poles = gpole(SYS; fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol)
       (any(isinf.(poles)) || any(isnan.(poles)))  && (return false)
    end
    return disc ? all(abs.(poles) .< smarg-β) : all(real.(poles) .< smarg-β)
end
"""
    ghanorm(sys, fast = true, atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (hanorm, hs)

Compute for a proper and stable descriptor system `sys = (A-λE,B,C,D)` with the transfer function
matrix `G(λ)`, the Hankel norm `hanorm =` ``\\small ||G(\\lambda)||_H`` and the vector of Hankel singular values `hs` of the system.

For a proper system with `E` singular, the uncontrollable infinite eigenvalues of the pair `(A,E)` and
the non-dynamic modes are elliminated using minimal realization techniques.
The rank determinations in the performed reductions
are based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

   The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively,
the absolute tolerance for the nonzero elements of `A`, `B`, `C`, `D`,
the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `B`, `C`, `D` and `E`.
The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the order of the system `sys`. The keyword argument `atol` can be used
to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function ghanorm(sys::DescriptorStateSpace{T}; fast::Bool = true,
                 atol::Real = zero(float(real(T))), atol1::Real = atol, atol2::Real = atol,
                 rtol::Real = sys.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2)))  where T

    T1 = T <: BlasFloat ? T : promote_type(Float64,T)
    n = size(sys.A,1)

    n == 0 && (return zero(real(T1)), zeros(real(T1),0))

    s2eps = sqrt(eps(real(T1)))
    disc = !iszero(sys.Ts)

    if  sys.E == I
        # for a non-dynamic system, we set the Hankel norm to zero,
        # but the Hankel singular values are empty
        size(sys.A,1) == 0 && (return zero(real(T1)), zeros(real(T1),0))
        # reduce the system to Schur coordinate form
        SF = schur(sys.A)
        # check stability
        ((disc && maximum(abs.(SF.values)) >= 1-s2eps) || (!disc && maximum(real(SF.values)) >= -s2eps)) &&
              error("The system sys is unstable")
        S = plyaps(SF.T, SF.Z'*sys.B; disc = disc)
        R = plyaps(SF.T', (sys.C*SF.Z)'; disc = disc)
        hs = svdvals(R*S)
     else
        # eliminate non-dynamic modes if possible
        if rcond(sys.E) < n*eps(float(real(T1)))
           # sys = gss2ss(sys,s2eps,'triu');
           sys = gminreal(sys, fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol);
           rcond(sys.E) < n*eps(float(real(T1))) && error("The system SYS is not proper")
        end
        # for a non-dynamic system, we set the Hankel norm to zero,
        # but the Hankel singular values are empty
        size(sys.A,1) == 0 && (return zero(real(T1)), zeros(real(T1),0))
        # reduce the system to generalized Schur coordinate form
        SF = schur(sys.A,sys.E)
        ((disc && maximum(abs.(SF.values)) >= 1-s2eps) || (!disc && maximum(real(SF.values)) >= -s2eps)) &&
              error("The system sys is unstable")
        S = plyaps(SF.S, SF.T, SF.Q'*sys.B; disc = disc)
        R = plyaps(SF.S', SF.T', (sys.C*SF.Z)'; disc = disc)
        hs = svdvals(R*UpperTriangular(SF.T)*S)
    end

    return hs[1], hs
    # end GHANORM
end
"""
    gh2norm(sys, fast = true, offset = sqrt(ϵ), atol = 0, atol1 = atol, atol2 = atol, atolinf = atol, rtol = n*ϵ)

Compute for a descriptor system `sys = (A-λE,B,C,D)` the `H2` norm of its transfer function  matrix `G(λ)`.
The `H2` norm is infinite, if `sys` has unstable poles, or, for a continuous-time, the system has nonzero gain at infinity.
To check the stability, the eigenvalues of the _pole pencil_ `A-λE` must have real parts less
than `-β` for a continuous-time system or
have moduli less than `1-β` for a discrete-time system, where `β` is the stability domain boundary offset.
The offset  `β` to be used can be specified via the keyword parameter `offset = β`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

For a continuous-time system `sys` with `E` singular, a reduced order realization is determined first, without
uncontrollable and unobservable nonzero finite and infinite eigenvalues of the corresponding pole pencil.
The rank determinations in the performed reductions
are based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively,
the absolute tolerance for the nonzero elements of `A`, `B`, `C`, `D`,
the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `B`, `C`, `D` and `E`.
The keyword argument `atolinf` is the absolute tolerance for the gain of `G(λ)` at `λ = ∞`.
The used default value is `atolinf = 0`.
The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the order of the system `sys`. The keyword argument `atol` can be used
to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function gh2norm(sys::DescriptorStateSpace{T}; fast::Bool = true, offset::Real = sqrt(eps(float(real(T)))),
                 atol::Real = zero(float(real(T))), atol1::Real = atol, atol2::Real = atol, atolinf::Real = atol,
                 rtol::Real = sys.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2)))  where T
    return gl2norm(sys; h2norm = true, fast = fast, offset = offset, atol1 = atol1, atol2 = atol2, atolinf = atolinf, rtol = rtol)

end
"""
    gl2norm(sys, h2norm = false, fast = true, offset = sqrt(ϵ), atol = 0, atol1 = atol, atol2 = atol, atol3 = atol, atolinf = atol, rtol = n*ϵ)

Compute for a descriptor system `sys = (A-λE,B,C,D)` the `L2` norm of its transfer function  matrix `G(λ)`.
The `L2` norm is infinite if the _pole pencil_ `A-λE` has
zeros (i.e., poles) on the stability domain boundary, i.e., on the extended imaginary axis, in the continuous-time case,
or on the unit circle, in the discrete-time case.
The `L2` norm is also infinite for a continuous-time system having a gain at infinity greater than `atolinf`.

To check the lack of poles on the stability domain boundary, the eigenvalues of the pencil `A-λE`
must not have real parts in the interval `[-β,β]` for a continuous-time system or
must not have moduli in the interval `[1-β,1+β]` for a discrete-time system, where `β` is the stability domain boundary offset.
The offset  `β` to be used can be specified via the keyword parameter `offset = β`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

If `h2norm = true`, the `H2` norm is computed.
The `H2` norm is infinite if the _pole pencil_ `A-λE` has unstable zeros (i.e., unstable poles), or
for a continuous-time system having a gain at infinity greater than `atolinf`.
To check the stability, the eigenvalues of the pencil `A-λE` must have real parts less than `-β` for a continuous-time system or
have moduli less than `1-β` for a discrete-time system.

For a continuous-time system `sys` with `E` singular, a reduced order realization is determined first, without
uncontrollable and unobservable nonzero finite and infinite eigenvalues of the corresponding pole pencil.
The rank determinations in the performed reductions
are based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively,
the absolute tolerance for the nonzero elements of `A`, `B`, `C`, `D`,
the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `B`, `C`, `D` and `E`.
The keyword argument `atol3` specifies the absolute tolerance for the nonzero elements of `B`
and is only used if `h2norm = false` for controllability tests of unstable eigenvalues.
The keyword argument `atolinf` is the absolute tolerance for the gain of `G(λ)` at  `λ = ∞`.
The used default value is `atolinf = 0`.
The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the order of the system `sys`. The keyword argument `atol` can be used
to simultaneously set `atol1 = atol`, `atol2 = atol` and `atol3 = atol`.
"""
function gl2norm(sys::DescriptorStateSpace{T}; h2norm::Bool = false, fast::Bool = true,
                 offset::Real = sqrt(eps(float(real(T)))), atol::Real = zero(float(real(T))), atol1::Real = atol, atol2::Real = atol, atol3::Real = atol,
                 atolinf::Real = atol, rtol::Real = sys.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2)))  where T

    T1 = T <: BlasFloat ? T : promote_type(Float64,T)
    disc = !iszero(sys.Ts)

    if  sys.E == I
        # quick return for a non-dynamic system or continuous-time system with nonzero D
        size(sys.A,1) == 0 && (return disc ? norm(sys.D) : (norm(sys.D,Inf) <= atolinf ? zero(real(T1)) : Inf))
        disc || norm(sys.D,Inf) <= atolinf || (return Inf)
        if h2norm
            # compute the H2-norm
            # reduce the system to Schur coordinate form
            SF = schur(sys.A)
            # check stability
            ((disc && maximum(abs.(SF.values)) >= 1-offset) || (!disc && maximum(real(SF.values)) >= -offset)) && (return Inf)
            R = plyaps(SF.T', (sys.C*SF.Z)'; disc = disc)
            return disc ? norm([R*(SF.Z'*sys.B); sys.D]) : norm(R*(SF.Z'*sys.B))
        else
            # compute the L2-norm
            try
              sys = grcfid(sys, offset = offset, atol1 = atol1, atol3 = atol3, rtol = rtol)[1]
              R = plyaps(sys.A', sys.C'; disc = disc)
              return disc ? norm([R*sys.B; sys.D]) : norm(R*sys.B)
            catch
              return Inf
            end
        end
     else
        n = sys.nx
        # eliminate uncontrollable and unobservable infinite eigenvalues and non-dynamic modes if possible
        if rcond(sys.E) < n*eps(float(real(T1)))
           sys = gir(sys, finite = false, noseig = true, fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol)
           # check properness for a continuous-time system
           disc || rcond(sys.E) >= n*eps(float(real(T1))) || (return Inf)
        end
        size(sys.A,1) == 0 && (return disc ? norm(sys.D) : (norm(sys.D,Inf) <= atolinf ? zero(real(T1)) : Inf))
        disc || norm(sys.D) <= atolinf || (return Inf)
        if h2norm
            # compute the H2-norm
            # reduce the system to generalized Schur coordinate form
            SF = schur(sys.A,sys.E)
            # check stability
            ((disc && maximum(abs.(SF.values)) >= 1-offset) || (!disc && maximum(real(SF.values)) >= -offset)) && (return Inf)
            R = plyaps(SF.S', SF.T', (sys.C*SF.Z)'; disc = disc)
            return disc ? norm([R*(SF.Q'*sys.B); sys.D]) : norm(R*(SF.Q'*sys.B))
        else
            # compute the L2-norm
            try
              sys = glcfid(sys, offset = offset, mininf = true, atol1 = atol1, atol2 = atol2, atol3 = atol3, rtol = rtol)[1]
              R = plyaps(sys.A', sys.E', sys.C'; disc = disc)
              return disc ? norm([R*sys.B; sys.D]) : norm(R*sys.B)
            catch
              return Inf
            end
        end
    end

    # end GL2NORM
end
"""
    ghinfnorm(sys, rtolinf = 0.001, fast = true, offset = sqrt(ϵ), atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (hinfnorm, fpeak)

Compute for a descriptor system `sys = (A-λE,B,C,D)` with the transfer function  matrix `G(λ)`
the `H∞` norm `hinfnorm` (i.e.,  the peak gain of `G(λ)`) and
the corresponding peak frequency `fpeak`, where the peak gain is achieved.
The `H∞` norm is infinite if the _pole pencil_ `A-λE` has unstable zeros (i.e., `sys` has unstable poles).
To check the stability, the eigenvalues of the pencil `A-λE` must have real parts less than `-β` for a continuous-time system or
have moduli less than `1-β` for a discrete-time system, where `β` is the stability domain boundary offset.
The offset  `β` to be used can be specified via the keyword parameter `offset = β`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

The keyword argument `rtolinf` specifies the relative accuracy for the computed infinity norm.
The  default value used for `rtolinf` is `0.001`.

For a continuous-time system `sys` with `E` singular, a reduced order realization is determined first, without
uncontrollable and unobservable nonzero finite and infinite eigenvalues of the corresponding pole pencil.
The rank determinations in the performed reductions
are based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, the absolute tolerance for the
nonzero elements of matrices `A`, `B`, `C`, `D`, the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `B`, `C`, `D` and `E`.
The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the order of the system `sys`.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function ghinfnorm(sys::DescriptorStateSpace{T}; rtolinf::Real = float(real(T))(0.001), fast::Bool = true, offset::Real = sqrt(eps(float(real(T)))),
                   atol::Real = zero(float(real(T))), atol1::Real = atol, atol2::Real = atol,
                   rtol::Real = (size(sys.A,1)*eps(real(float(one(T)))))*iszero(min(atol1,atol2)))  where T
    return glinfnorm(sys; hinfnorm = true, rtolinf = rtolinf, fast = fast, offset = offset, atol1 = atol1, atol2 = atol2, rtol = rtol)

end
"""
    glinfnorm(sys, hinfnorm = false, rtolinf = 0.001, fast = true, offset = sqrt(ϵ), atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (linfnorm, fpeak)

Compute for a descriptor system `sys = (A-λE,B,C,D)` with the transfer function  matrix `G(λ)`
the `L∞` norm `linfnorm` (i.e.,  the peak gain of `G(λ)`) and
the corresponding peak frequency `fpeak`, where the peak gain is achieved.
The `L∞` norm is infinite if the _pole pencil_ `A-λE` has
zeros (i.e., poles) on the stability domain boundary, i.e., on the extended imaginary axis, in the continuous-time case,
or on the unit circle, in the discrete-time case.
To check the lack of poles on the stability domain boundary, the eigenvalues of the pencil `A-λE`
must not have real parts in the interval `[-β,β]` for a continuous-time system or
must not have moduli within the interval `[1-β,1+β]` for a discrete-time system, where `β` is the stability domain boundary offset.
The offset  `β` to be used can be specified via the keyword parameter `offset = β`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

The keyword argument `rtolinf` specifies the relative accuracy for the computed infinity norm.
The  default value used for `rtolinf` is `0.001`.

If `hinfnorm = true`, the `H∞` norm is computed. In this case, the stability of the zeros of `A-λE` is additionally checked and
the `H∞` norm is infinite for an unstable system.
To check the stability, the eigenvalues of the pencil `A-λE` must have real parts less than `-β` for a continuous-time system or
have moduli less than `1-β` for a discrete-time system.

For a continuous-time system `sys` with `E` singular, a reduced order realization is determined first, without
uncontrollable and unobservable nonzero finite and infinite eigenvalues of the corresponding pole pencil.
The rank determinations in the performed reductions
are based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, the absolute tolerance for the
nonzero elements of matrices `A`, `B`, `C`, `D`, the absolute tolerance for the nonzero elements of `E`,
and the relative tolerance for the nonzero elements of `A`, `B`, `C`, `D` and `E`.
The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the order of the system `sys`.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol` and `atol2 = atol`.
"""
function glinfnorm(sys::DescriptorStateSpace{T}; hinfnorm::Bool = false, rtolinf::Real = float(real(T))(0.001), fast::Bool = true,
                   offset::Real = sqrt(eps(float(real(T)))), atol::Real = zero(float(real(T))), atol1::Real = atol, atol2::Real = atol,
                   rtol::Real = sys.nx*eps(real(float(one(T))))*iszero(min(atol1,atol2)))  where T

    T1 = T <: BlasFloat ? T : promote_type(Float64,T)
    ZERO = real(T1)(0)

    # detect zero case
    # iszero(sys, atol1 = atol1, atol2 = atol2, rtol = rtol) && (return ZERO, ZERO)

    # quick exit for zero dimensions
    (sys.nu == 0 || sys.ny == 0) && (return ZERO, ZERO)

    # quick exit in constant case
    sys.nx == 0 && (return opnorm(sys.D), ZERO)

    β = abs(offset)
    Ts = abs(sys.Ts)
    disc = !iszero(Ts)
    complx = T1 <: Complex

    # eliminate simple infinite eigenvalues in the continuous-time case with singular E
    if disc || sys.E == I || rcond(sys.E) >= sys.nx*eps(float(real(T1)))
       A, E, B, C, D = dssdata(T1,sys)
    else
       A, E, B, C, D = dssdata(gir(sys, fast = fast, finite = false, noseig = true, atol1 = atol1, atol2 = atol2, rtol = rtol))
    end

    n = size(A,1)

    # quick exit in constant case
    n == 0 && (return opnorm(D), real(T1)(0))

    # check properness in continuous-time case
    disc || E == I || rcond(E) >= n*eps(float(real(T1))) || (return Inf, Inf)

    # check for poles on the boundary of the stability domain
    E == I ? ft = eigvals(A) : ft = eigvals(A,E); ft = ft[isfinite.(ft)]
    if disc
        hinfnorm && any(abs.(ft) .> 1-β) && (return Inf, NaN)
        for i = 1:length(ft)
            abs(ft[i]) >= 1-β && abs(ft[i]) <= 1+β && (return Inf, complx ? imag(log(complex(ft[i]))/Ts) : abs(log(complex(ft[i]))/Ts))
        end
    else
        hinfnorm && any(real.(ft) .> -β) && (return Inf, NaN)
        for i = 1:length(ft)
            real(ft[i]) >= -β && real(ft[i]) <= β && (return Inf, complx ? imag(ft[i]) : abs(imag(ft[i])))
        end
    end

    # compute L∞-norm according to system type
    disc ? (return norminfd(A, E, B, C, D, ft, Ts, rtolinf)) :
           (return norminfc(A, E, B, C, D, ft, rtolinf))
    # end GLINFNORM
end
function norminfc(a, e, b, c, d, ft0, tol)

   T = eltype(a)
   TR = real(T)
   ny, nu = size(d)
   min(ny, nu) == 0 && (return TR(0), TR(0))

   # Continuous-time L∞ norm computation
   # It is assumed that A-λE has no eigenvalues on the extended imaginary axis

   # Tolerance for jw-axis mode detection
   compl = T <: Complex
   epsm = eps(TR)
   toljw1 = 100 * epsm;       # for simple roots
   toljw2 = 10 * sqrt(epsm);  # for double root

   # Problem dimensions
   nx = size(a,1)
   desc = e != I
   # reduce to complex Hessenberg form
   ac, ec, bc, cc, dc = chess(a, e, b, c, d)

   # Build a new vector TESTFRQ of test frequencies containing the peaking
   # frequency for each mode (or an approximation thereof for non-resonant modes).
   # Add frequency w = 0 and set GMIN = || D || and FPEAK to infinity
   # ar2 = abs.(real(ft0));  # magnitudes of real parts of test frequencies
   w0 = abs.(ft0);         # fundamental frequencies

   #  ikeep = (imag.(ft0) .>= 0) .& ( w0 .> 0)
   #  offset2 = max.(0.25,max.(1 .- 2 .*(ar2[ikeep]./w0[ikeep]).^2))
   #  temp = w0[ikeep].*sqrt.(offset2)
   #  compl ? testfrq = [-temp; [0]; temp] : testfrq = [[0]; temp]
   compl ? testfrq = [-w0; [0]; w0] : testfrq = [[0]; w0]

   gmin = opnorm(d)
   fpeak = Inf

   # Compute lower estimate GMIN as max. gain over the selected frequencies
   for i = 1:length(testfrq)
      w = testfrq[i];
      bct = copy(bc)
      desc ? ldiv!(UpperHessenberg(ac-(im*w)*ec),bct) : ldiv!(ac,bct,shift = -im*w)
      gw = opnorm(dc-cc*bct)
      gw > gmin && (gmin = gw;  fpeak = w)
   end
   gmin == 0 && (return TR(0), TR(0))

   # modified gamma iterations (Bruinsma-Steinbuch algorithm) start:
   iter = 1;
   while iter < 30
      # Test if G = (1+TOL)*GMIN qualifies as upper bound
      g = (1+tol) * gmin;
      # Compute finite eigenvalues of Hamiltonian pencil
      # deflate nu+ny simple infinite eigenvalues
      h1 = [g*I d; d' g*I; zeros(T,nx,ny) b ; c' zeros(T,nx,nu)]
      h2 = [c zeros(T,ny,nx) ; zeros(T,nu,nx) -b'; a zeros(T,nx,nx) ; zeros(T,nx,nx) -a']
      j2 = [zeros(T,ny+nu,2*nx); e zeros(T,nx,nx) ; zeros(T,nx,nx) e']
      _, tau = LinearAlgebra.LAPACK.geqrf!(h1)
      compl ? tran = 'C' : tran = 'T'
      LinearAlgebra.LAPACK.ormqr!('L',tran,h1,tau,h2)
      LinearAlgebra.LAPACK.ormqr!('L',tran,h1,tau,j2)
      i2 = ny+nu+1:ny+nu+2*nx
      heigs = eigvals!(view(h2,i2,:),view(j2,i2,:))

      mag = abs.(heigs);
      # Detect jw-axis modes.  Test is based on a round-off level of
      # eps*rho(H) resulting in worst-case
      # perturbations of order sqrt(eps*rho(H)) on the real part
      # of poles of multiplicity two (typical as g->norm(sys,inf))
      #jweig = heigs(abs(real(heigs)) < toljw2*(1 .+ mag)+toljw1*max(mag));
      jweig = heigs[abs.(real(heigs)) .< toljw2*(1 .+ mag) .+ toljw1*mag];

      # Compute frequencies where gain G is attained and
      # generate new test frequencies
      ws = imag(jweig);
      #ws = unique(max.(epsm,ws[ws.> 0]))
      compl ? ws = unique(sort(ws)) : ws = unique(sort(max.(epsm,ws[ws.> 0])))
      lws0 = length(ws);
      if lws0 == 0
         # No jw-axis eigenvalues for G = GMIN*(1+TOL): we're done
         return gmin, fpeak
      else
         lws0 == 1 && (ws = [ws;ws]) # correct pairing
         lws = length(ws);
      end

      # Form the vector of mid-points and compute
      # gain at new test frequencies
      gmin0 = gmin;   # save current lower bound
      #ws = sqrt.(ws[1:lws-1].*ws[2:lws])
      #ws = (ws[1:lws-1].+ ws[2:lws])/2
      # Compute lower estimate GMIN as max. gain over the selected frequencies
      for i = 1:lws-1
          w = (ws[i]+ws[i+1])/2
          bct = copy(bc)
          desc ? ldiv!(UpperHessenberg(ac-(im*w)*ec),bct) : ldiv!(ac,bct,shift = -im*w)
          gw = opnorm(dc-cc*bct)
          gw > gmin && (gmin = gw;  fpeak = w)
      end

      # If lower bound has not improved, exit (safeguard against undetected
      # jw-axis modes of Hamiltonian matrix)
      (lws0 < 2 || gmin < gmin0*(1+tol/10)) && (return gmin, fpeak)
      iter += 1
   end #while
end
function norminfd(a, e, b, c, d, ft0, Ts, tol)

   T = eltype(a)
   TR = real(T)
   ny, nu = size(d)
   min(ny, nu) == 0 && (return TR(0), TR(0))

   # Discrete-time L∞ norm computation
   # It is assumed that A-λE has no eigenvalues on the unit circle

   # Tolerance for detection of unit circle modes
   compl = T <: Complex
   epsm = eps(TR)
   toluc1 = 100 * epsm       # for simple roots
   toluc2 = 10 * sqrt(epsm)  # for double root

   # Problem dimensions
   nx = size(a,1);
   ny, nu = size(d)
   desc = e != I
   # reduce to complex Hessenberg form
   ac, ec, bc, cc, dc = chess(a, e, b, c, d)

   # Build a new vector TESTFRQ of test frequencies containing the peaking
   # frequency for each mode (or an approximation thereof for non-resonant modes).
   sr = log.(complex(ft0[(ft0 .!= 0) .& (abs.(ft0) .<= pi/Ts)]));   # equivalent jw-axis modes
   #sr = ft0[(ft0 .!= 0) .& (abs.(ft0) .<= pi/Ts)];   # equivalent jw-axis modes
   # asr2 = abs.(real(sr))   # magnitude of real parts
   w0 = abs.(sr);           # fundamental frequencies

   # ikeep = (imag.(sr) .>= 0) .& ( w0 .> 0)
   # testfrq = w0[ikeep].*sqrt.(max.(0.25,1 .- 2 .*(asr2[ikeep]./w0[ikeep]).^2))
   compl ? testfrq = [-w0; [0]; w0] : testfrq = [[0]; w0]

   # Back to unit circle, and add z = exp(0) and z = exp(pi)
   testz = [exp.(im*testfrq); [-1] ]

   gmin = 0
   fpeak = 0

   # Compute lower estimate GMIN as max. gain over the selected frequencies
   for i = 1:length(testz)
      z = testz[i]
      bct = copy(bc)
      desc ? ldiv!(UpperHessenberg(ac-z*ec),bct) : ldiv!(ac,bct,shift = -z)
      gw = opnorm(dc-cc*bct)
      gw > gmin && (gmin = gw;  compl ? fpeak = angle(z) : fpeak = abs(angle(z)))
   end
   gmin == 0 && (return TR(0), TR(0))

   # Modified gamma iterations (Bruinsma-Steinbuch algorithm) starts:
   iter = 1;
   while iter < 30
      # Test if G = (1+TOL)*GMIN qualifies as upper bound
      g = (1+tol) * gmin;
      # Compute the finite eigenvalues of the symplectic pencil
      # deflate nu+ny simple infinite eigenvalues
      h1 = [a zeros(T,nx,nx+ny) b; zeros(T,nx,nx) e' zeros(nx,ny+nu)]
      h2 = [c zeros(T,ny,nx) g*I d; zeros(T,nu,nx) b' d' g*I]
      j1 = [e zeros(T,nx,nx+ny+nu); zeros(T,nx,nx) a' c' zeros(T,nx,nu) ]
      _, tau = LinearAlgebra.LAPACK.gerqf!(h2)
      compl ? tran = 'C' : tran = 'T'
      LinearAlgebra.LAPACK.ormrq!('R',tran,h2,tau,h1)
      LinearAlgebra.LAPACK.ormrq!('R',tran,h2,tau,j1)
      i1 = 1:(2*nx)
      heigs = eigvals!(view(h1,:,i1),view(j1,:,i1))
      heigs =  heigs[abs.(heigs) .< 1/toluc2]

      # Detect unit-circle eigenvalues
      mag = abs.(heigs)
      uceig = heigs[abs.(1 .- mag) .< toluc2 .+ toluc1*mag]

      # Compute frequencies where gain G is attained and
      # generate new test frequencies
      ang = sort(angle.(uceig));
      ang = compl ? unique(ang) : unique(max.(epsm,ang[ang .> 0]))
      lan0 = length(ang);
      if lan0 == 0
         # No unit-circle eigenvalues for G = GMIN*(1+TOL): we're done
         return gmin, fpeak/Ts
       else
         lan0 == 1 && (ang = [ang;ang])   # correct pairing
         lan = length(ang)
      end

      # Form the vector of mid-points and compute
      # gain at new test frequencies
      gmin0 = gmin;   # save current lower bound
      #testz = exp.(im*((ang[1:lan-1]+ang[2:lan])/2))
      # Compute lower estimate GMIN as max. gain over the selected frequencies
      for i = 1:lan-1
         #z = testz[i]
         z = exp(im*((ang[i]+ang[i+1])/2))
         bct = copy(bc)
         desc ? ldiv!(UpperHessenberg(ac-z*ec),bct) : ldiv!(ac,bct,shift = -z)
         gw = opnorm(dc-cc*bct)
         gw > gmin && (gmin = gw;  compl ? fpeak = angle(z) : fpeak = abs(angle(z)))
      end

      # If lower bound has not improved, exit (safeguard against undetected
      # unit-circle eigenvalues).
      (lan0 < 2 || gmin < gmin0*(1+tol/10)) && (return gmin, fpeak/Ts)
      iter += 1
   end
end

"""
    gnugap(sys1, sys2; freq = ω, rtolinf = 0.00001, fast = true, offset = sqrt(ϵ),
           atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (nugapdist, fpeak)

Compute the ν-gap distance `nugapdist` between two descriptor systems `sys1 = (A1-λE1,B1,C1,D1)` and
`sys2 = (A2-λE2,B2,C2,D2)` and the corresponding frequency `fpeak` (in rad/TimeUnit), where the ν-gap
distance achieves its peak value.

If `freq = missing`, the resulting `nugapdist` satisfies `0 <= nugapdist <= 1`.
The value `nugapdist = 1` results, if the winding number is different of zero in which case `fpeak = []`.

If `freq = ω`, where `ω` is a given vector of real frequency values, the resulting `nugapdist` is a vector
of pointwise ν-gap distances of the dimension of `ω`, whose components satisfies `0 <= maximum(nugapdist) <= 1`.
In this case, `fpeak` is the frequency for which the pointwise distance achieves its peak value.
All components of `nugapdist` are set to 1 if the winding number is different of zero in which case `fpeak = []`.

The stability boundary offset,  `β`, to be used to assess the finite zeros which belong to the
boundary of the stability domain can be specified via the keyword parameter `offset = β`.
Accordingly, for a continuous-time system, these are the finite zeros having
real parts within the interval `[-β,β]`, while for a discrete-time system,
these are the finite zeros having moduli within the interval `[1-β,1+β]`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

Pencil reduction algorithms are employed to compute range and coimage spaces
which perform 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 `atol1`, `atol2` and `rtol`, specify, respectively,
the absolute tolerance for the nonzero elements of `A1`, `A2`, `B1`, `B2`, `C1`, `C2`, `D1` and `D2`,
the absolute tolerance for the nonzero elements of `E1` and `E2`,
and the relative tolerance for the nonzero elements of all above matrices.
The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the maximum of the orders of the systems `sys1` and `sys2`.
The keyword argument `atol` can be used to simultaneously set `atol1 = atol`, `atol2 = atol`.

The keyword argument `rtolinf` specifies the relative accuracy to be used
to compute the ν-gap as the infinity norm of the relevant system according to [1].
The default value used for `rtolinf` is `0.00001`.

_Method:_ The evaluation of ν-gap uses the definition proposed in [1],
extended to generalized LTI (descriptor) systems. The computation of winding number
is based on enhancements covering zeros on the boundary of the
stability domain and infinite zeros.

_References:_

[1] G. Vinnicombe. Uncertainty and feedback: H∞ loop-shaping and the ν-gap metric.
    Imperial College Press, London, 2001.
"""
function gnugap(sys1::DescriptorStateSpace{T1},sys2::DescriptorStateSpace{T2}; freq::Union{AbstractVector{<:Real},Real,Missing} = missing,
         fast::Bool = true, offset::Real = sqrt(eps(float(real(T1)))), rtolinf::Real = float(real(T1))(0.00001),
         atol::Real = zero(real(T1)), atol1::Real = atol, atol2::Real = atol,
         rtol::Real = max(sys1.nx,sys2.nx)*eps(real(float(one(T1))))*iszero(min(atol1,atol2)))  where {T1,T2}
   T = promote_type(T1,T2)
   T <: BlasFloat || (T = promote_type(T,Float64))
   ONE = one(real(T))

   Ts = promote_Ts(sys1.Ts,sys2.Ts)
   disc = (Ts != 0)

   ismissing(freq) ? nf = 0 : nf = length(freq)

   p, m = size(sys1)
   (p,m) == size(sys2) || error("The systems sys1 and sys2 must have the same number of inputs and outputs")

   # compute the normalized coprime factorizations R1 = [N1;M1] and R2 = [N2;M2]
   R1 = grange([sys1;I], zeros = "none", inner = true, atol1 = atol1, atol2 = atol2, rtol = rtol,
               offset = offset, fast = fast)[1];
   R2 = grange([sys2;I], zeros = "none", inner = true, atol1 = atol1, atol2 = atol2, rtol = rtol,
                offset = offset, fast = fast)[1];

   # check conditions on det(N2'*N1+M2'*M1) = det(R2'*R1)
   syst = gir(R2'*R1, atol1 = atol1, atol2 = atol2, rtol = rtol, fast = fast);
   infoz = gzeroinfo(syst, offset = offset, atol1 = atol1, atol2 = atol2, rtol = rtol, fast = fast)[2];
   # check invertibility and presence of zeros on the boundary of stability domain
   if infoz.nrank != order(syst)+m || infoz.nfsbz > 0
      return nf == 0 ? ONE : ones(nf), Float64[]
   end

   # evaluate winding number
   infop = gpoleinfo(syst,offset = offset, atol1 = atol1, atol2 = atol2, rtol = rtol, fast = fast)[2];
   wno = infoz.nfuz - infop.nfuev + infoz.niz - infop.nip
   # check condition on winding number
   if wno != 0
      # nonzero winding number
      return nf == 0 ? ONE : ones(nf), Float64[]
   end

   # compute the normalized left coprime factorization L1 = [ N1t M1t]
   L1 = gcrange([sys1 I], zeros = "none", coinner = true, atol1 = atol1, atol2 = atol2, rtol = rtol,
                offset = offset, fast = fast)[1];
   # compute the underlying system to compute the nu-gap distance
   # using the definition of Vinnicombe
   syst = L1*[zeros(T,m,p) -I; I zeros(T,p,m)]*R2
   if ismissing(freq)
      # compute the ν-gap using the definition of Vinnicombe
      nugapdist, fpeak = ghinfnorm(syst; rtolinf = rtolinf, fast = fast, offset = offset,
                                  atol1 = atol1, atol2 = atol2, rtol = rtol)
   else
      H = freqresp(syst,freq); nugapdist = zeros(nf)
      tmax = opnorm(H[:,:,1]); fpeak = freq[1]
      nugapdist[1] = tmax
      for i = 2:nf
          temp = opnorm(H[:,:,i])
          nugapdist[i] = temp;
          if tmax < temp
             tmax = temp; fpeak = freq[i]
          end
      end
   end
   return nugapdist, fpeak
end


using LinearAlgebra

export translate, scale, rotate

import Base: position

"""
    translate(v::Vec2D) -> Matrix
Creates a 3x3 matrix that would translate points
in the `v` direction.
"""
function translate(v::Union{Vec2D, Point})
    A = zeros(3, 3) + I
	A[1, 3] = v.x
	A[2, 3] = v.y

    A
end

"""
    scale(x, y) -> Matrix
Create 3x3 matrix for scaling in object. If you multiply
this with a collection of points making up a shape, it
has the effect of scaling that shape.
"""
function scale(x::Number, y::Number)
    A = zeros(3, 3)
    A[1, 1] = x
    A[2, 2] = y
    A[3, 3] = 0.0
    A
end

"""
    rotate(angle) -> Matrix
Create a 3x3 matrix that can be used to rotate a shape
made up of multiple points along its axis. Each point
will have to be a column in matrix.
"""
function rotate(angle::Number)
    A = zeros(3, 3) + I
    sine   = sin(angle)
    cosine = cos(angle)

	A[1, 1] = cosine
	A[1, 2] = -sine
	A[2, 1] = sine
	A[2, 2] = cosine

    A
end


function score(x, y)
	distance = sqrt(x^2 + y^2)
	inner_radius = 1
	middle_radius = 5
	outer_radius = 10
	if distance <= inner_radius
		return 10
	elseif distance <= middle_radius
		return 5
	elseif distance <= outer_radius
		return 1
	else
		return 0
    end
end


# # Foundations of Bilevel Programming: Example 5
# This example is from the book _Foundations of Bilevel Programming_ by Stephan
# Dempe, Chapter 8.1, Page 255. [url](https://www.springer.com/gp/book/9781402006319)

# Here, only the second level is described

# Model of the problem
# First level
# ```math
# \min 0,\\
# ```
# Second level
# ```math
# \min x,\\
# \notag s.t.\\
# x+y \leq 2,\\
# x-y \leq 2,\\
# -4x+5y \leq 10,\\
# -4x-5y \leq 10,\\
# ```

using BilevelJuMP
using Ipopt
using JuMP
using Test

model = BilevelModel(Ipopt.Optimizer, mode = BilevelJuMP.ProductMode(1e-9))

# Global variables
atol = 1e-3

# First we need to create all of the variables in the upper and lower problems:

# Upper level variables
@variable(Upper(model), y, start = 0)

#Lower level variables
@variable(Lower(model), x, start = 0)

# Then we can add the objective and constraints of the upper problem:

# Upper level objecive function
@objective(Upper(model), Min, 0 * y + 0)

# Followed by the objective and constraints of the lower problem:

# Lower objective function
@objective(Lower(model), Min, x)

# Lower constraints
@constraint(Lower(model), x + y <= 2)
@constraint(Lower(model), x - y <= 2)
@constraint(Lower(model), -4x + 5y <= 10)
@constraint(Lower(model), -4x - 5y <= 10)

# Initial Starting conditions  #src

# Now we can solve the problem and verify the solution again that reported by
# Dempe.

optimize!(model)
primal_status(model)
termination_status(model)


const woods = let res_init=zeros(7), jac_init=zeros(7,4), x_init=[-3.0, -1.0, -3.0, -1.0]

    function res(x, r)
        r[1] = 10.0 * (x[2] - x[1]^2 )
        r[2] = 1.0 - x[1]
        r[3] = sqrt(90.0) * (x[4] - x[3]^2 )
        r[4] = 1.0 - x[3]
        r[5] = sqrt(9.9) * (x[2] + x[4] - 2.0)
        t = sqrt(0.2)
        r[6] = t * (x[2] - 1.0)
        r[7] = t * (x[4] - 1.0)
        return r
    end

    function jac(x, jac)
        jac[:] .= 0.0
        jac[1, 1] = -20x[1]
        jac[1, 2] = 10.0
        jac[2, 1] = -1.0
        jac[3, 4] = sqrt(90.0)
        jac[3, 3] = -2x[3] * jac[3, 4]
        jac[4, 3] = -1.0
        jac[5, 2] = sqrt(9.9)
        jac[5, 4] = jac[5, 2]
        jac[6, 2] = sqrt(0.2)
        jac[7, 4] = jac[6, 2]
        return jac
    end

    f(;scale=1, verbose=false, print_steps=false) = testone("woods", res, jac,
                                                   res_init, jac_init, x_init;
                                                   scale=scale, verbose=verbose,
                                                   print_steps=print_steps)
end


"""
    mcse(x::AbstractVector{<:Real}; method::Symbol=:imse, kwargs...)

Compute the Monte Carlo standard error (MCSE) of samples `x`.
The optional argument `method` describes how the errors are estimated. Possible options are:

- `:bm` for batch means [^Glynn1991]
- `:imse` initial monotone sequence estimator [^Geyer1992]
- `:ipse` initial positive sequence estimator [^Geyer1992]

[^Glynn1991]: Glynn, P. W., & Whitt, W. (1991). Estimating the asymptotic variance with batch means. Operations Research Letters, 10(8), 431-435.

[^Geyer1992]: Geyer, C. J. (1992). Practical Markov Chain Monte Carlo. Statistical Science, 473-483.
"""
function mcse(x::AbstractVector{<:Real}; method::Symbol=:imse, kwargs...)
    return if method === :bm
        mcse_bm(x; kwargs...)
    elseif method === :imse
        mcse_imse(x)
    elseif method === :ipse
        mcse_ipse(x)
    else
        throw(ArgumentError("unsupported MCSE method $method"))
    end
end

function mcse_bm(x::AbstractVector{<:Real}; size::Int=floor(Int, sqrt(length(x))))
    n = length(x)
    m = min(div(n, 2), size)
    m == size || @warn "batch size was reduced to $m"
    mcse = StatsBase.sem(Statistics.mean(@view(x[(i + 1):(i + m)])) for i in 0:m:(n - m))
    return mcse
end

function mcse_imse(x::AbstractVector{<:Real})
    n = length(x)
    lags = [0, 1]
    ghat = StatsBase.autocov(x, lags)
    Ghat = sum(ghat)
    @inbounds value = Ghat + ghat[2]
    @inbounds for i in 2:2:(n - 2)
        lags[1] = i
        lags[2] = i + 1
        StatsBase.autocov!(ghat, x, lags)
        Ghat = min(Ghat, sum(ghat))
        Ghat > 0 || break
        value += 2 * Ghat
    end

    mcse = sqrt(value / n)

    return mcse
end

function mcse_ipse(x::AbstractVector{<:Real})
    n = length(x)
    lags = [0, 1]
    ghat = StatsBase.autocov(x, lags)
    @inbounds value = ghat[1] + 2 * ghat[2]
    @inbounds for i in 2:2:(n - 2)
        lags[1] = i
        lags[2] = i + 1
        StatsBase.autocov!(ghat, x, lags)
        Ghat = sum(ghat)
        Ghat > 0 || break
        value += 2 * Ghat
    end

    mcse = sqrt(value / n)

    return mcse
end


function normAm(A,m)::Real
    #NORMAM   Estimate of 1-norm of power of matrix.
    #   NORMAM(A,m) estimates norm(A^m,1).
    #   If A has nonnegative elements the estimate is exact.
    #   [C,MV] = NORMAM(A,m) returns the estimate C and the number MV of
    #   matrix-vector products computed involving A or A^*.

    #   Reference: A. H. Al-Mohy and N. J. Higham, A New Scaling and Squaring
    #   Algorithm for the Matrix Exponential, SIAM J. Matrix Anal. Appl. 31(3):
    #   970-989, 2009.

    #   Awad H. Al-Mohy and Nicholas J. Higham, September 7, 2010.

    t = 2; # Number of columns used by NORMEST1.

    n = size(A, 1);
    if hasmethod(opnorm, Tuple{typeof(A), typeof(1)})
        if eltype(A) <: Real
            if sum(A .< 0) == 0 # for positive matrices only
                e = ones(n,1)
                f = similar(e)
                for j=1:m
                    mul!(f, A, e)
                    copyto!(e, f)
                end
                return norm(e, Inf)
            end
        end
    end
    return norm1est(A, m, t)
end


## discretize!

"""
    discretize!(a::AbstractArray{<:Real}, n::Integer=30)

Sort `a`, divide its values into (roughly) `n` groups corresponding to
equal-length intervals, and replace each value in a group by the `mean`
of this group. This can be useful to transform samples from a continuous
distribution in order to plot an histogram.

# Examples
```julia-repl
julia> pmf(discretize!(randn(1000), 10))
pmf for [-3.2977741401356235, -2.804784333001702, -2.804784333001702, -2.804784333001702, -2.0873488466856744, -2.0873488466856744, -2.0873488466856744, -2.0873488466856744, -2.0873488466856744, -2.0873488466856744  …  2.0792314833569865, 2.0792314833569865, 2.0792314833569865, 2.0792314833569865, 2.0792314833569865, 2.9112746357518313, 2.9112746357518313, 2.9112746357518313, 2.9112746357518313, 2.9112746357518313] with support of length 10:
  -3.29777  => 0.001
  -2.80478  => 0.003
  -2.08735  => 0.026
  -1.36503  => 0.104
  -0.679956 => 0.237
  0.0211377 => 0.273
  0.748744  => 0.232
  1.46951   => 0.101
  2.07923   => 0.018
  2.91127   => 0.005
```
"""
function discretize!(a::AbstractArray{<:Real}, n::Integer=30)
    require_one_based_indexing(a)
    length(a) < 2n && return a
    n = min(n, length(a) ÷ 2)
    sort!(a)
    w = (a[end] - a[1]) / n
    i = 1
    for x in LinRange(a[1], a[end], n)
        j = searchsortedlast(a, x)
        y = mean(view(a, i:j))
        a[i:j] .= y
        i = j+1
    end
    a
end


function DE_mutation(population, i::Int, F::Float64, strategy::Symbol=:rand1, best_ind=0)

    N = length(population)
    # select participats
    r1 = rand(1:N)
    while r1 == i
        r1 = rand(1:N)
    end

    r2 = rand(1:N)
    while r2 == i || r1 == r2
        r2 = rand(1:N)
    end

    r3 = rand(1:N)
    while r3 == i || r3 == r1 || r3 == r2
        r3 = rand(1:N)
    end

    x = population[i].x
    a = population[r1].x
    b = population[r2].x
    c = population[r3].x

    # strategy is selected here
    if strategy == :rand1
        # DE/rand/1
        u = a + F * (b - c)
    elseif strategy == :best1
        # DE/best/1
        xBest = population[argbest(population)].x
        u = xBest + F * (b - c)
    elseif strategy == :rand2
        # DE/rand/2

        r4 = rand(1:N)
        while r4 == i || r4 == r1 || r4 == r2 || r4 == r3
            r4 = rand(1:N)
        end

        r5 = rand(1:N)
        while r5 == i || r5 == r1 || r5 == r2 || r5 == r3 || r5 == r4
            r5 = rand(1:N)
        end

        d = population[r4].x
        ee = population[r5].x

        u = ee + F * (a - b + c - d)
    elseif strategy == :randToBest1
        # DE/rand-to-best/1
        xBest = population[argbest(population)].x
        u = x + F * (xBest - x + a - b)
    elseif strategy == :best2
        # DE/best/2
        r4 = rand(1:N)
        best_ind = argbest(population)
        xBest = population[best_ind].x
        while r4 == i || r4 == r1 || r4 == r2 || r4 == r3 || r4 == best_ind
            r4 = rand(1:N)
        end
        d = population[r4].x
        u = xBest + F * (a - b + c - d)
    else
        error("Unknown strategy " * string(strategy))
    end

    return u
end

function DE_crossover(x, u, CR)
    D = length(x)
    # binomial crossover
    v = zeros(D)
    j_rand = rand(1:D)

    # binomial crossover
    for j = 1:D
        if rand() < CR || j == j_rand
            v[j] = u[j]
        else
            v[j] = x[j]
        end
    end

    return v
end


using NNlib: logsoftmax, logσ

# Cost functions

mse(ŷ, y) = sum((ŷ .- y).^2)/length(y)

function crossentropy(ŷ::AbstractVecOrMat, y::AbstractVecOrMat; weight = 1)
  @fix -sum(y .* log.(ŷ) .* weight) / size(y, 2)
end

@deprecate logloss(x, y) crossentropy(x, y)

function logitcrossentropy(logŷ::AbstractVecOrMat, y::AbstractVecOrMat; weight = 1)
  return -sum(y .* logsoftmax(logŷ) .* weight) / size(y, 2)
end

"""
    binarycrossentropy(ŷ, y)

Return `-y*log(ŷ) - (1-y)*log(1-ŷ)`.

    julia> binarycrossentropy.(σ.([-1.1491, 0.8619, 0.3127]), [1, 1, 0.])
    3-element Array{Float64,1}:
    1.4244
    0.352317
    0.86167
"""
binarycrossentropy(ŷ, y) = -y*log(ŷ) - (1 - y)*log(1 - ŷ)

"""
    logitbinarycrossentropy(logŷ, y)

`logitbinarycrossentropy(logŷ, y)` is mathematically equivalent to `binarycrossentropy(σ(logŷ), y)`
but it is more numerically stable.

    julia> logitbinarycrossentropy.([-1.1491, 0.8619, 0.3127], [1, 1, 0.])
    3-element Array{Float64,1}:
     1.4244
     0.352317
     0.86167
"""
logitbinarycrossentropy(logŷ, y) = (1 - y)*logŷ - logσ(logŷ)

"""
    normalise(x::AbstractVecOrMat)

Normalise each column of `x` to mean 0 and standard deviation 1.
"""
function normalise(x::AbstractVecOrMat)
  μ′ = mean(x, 1)
  σ′ = std(x, 1, mean = μ′)
  return (x .- μ′) ./ σ′
end


function ahp_RI(n::Int64)::Float64
    # First index is n = 3
    # RI[3] = 0.58
    # RI[4] = 0.90
    ris = [0.58, 0.90, 1.12, 1.24, 1.32,
          1.41, 1.45, 1.49, 1.51, 1.53,
          1.56, 1.57, 1.59]
    if n < 3
        return 0
    elseif n <= 15
        return ris[n - 2]
    else
        return ris |> last
    end

end

function ahp_consistency(comparisonMatrix::DataFrame)::AHPConsistencyResult

    n, m = size(comparisonMatrix)

    csums = colsums(comparisonMatrix)
    normalizedComparisonMatrix = zeros(Float64, n, n)

    for i in 1:n
        for j in 1:n
            normalizedComparisonMatrix[i, j] = comparisonMatrix[i, j] / csums[j]
        end
    end

    priority_vector = rowmeans(normalizedComparisonMatrix)
    mcomparisonMatrix = convert(Array{Float64,2}, comparisonMatrix)

    consistency_vector = mcomparisonMatrix * priority_vector

    pc_matrix = consistency_vector ./ priority_vector

    lambda_max = sum(pc_matrix) / n

    CI  = (lambda_max - n) / (n - 1)
    ri  = ahp_RI(n)
    CR  = CI / ri

    isConsistent = (CR < 0.1)

    result = AHPConsistencyResult(
        comparisonMatrix,
        makeDecisionMatrix(normalizedComparisonMatrix),
        consistency_vector,
        priority_vector,
        pc_matrix,
        lambda_max,
        CI,
        ri,
        CR,
        isConsistent
    )

    return result

end

function ahp(comparisonMatrixList::Array{DataFrame,1}, criteriaComparisonMatrix::DataFrame)::AHPResult

    result_list = map(ahp_consistency, comparisonMatrixList)

    n = length(result_list)

    ncriteria, _ = size(criteriaComparisonMatrix)

    ncandidates, _ = size(comparisonMatrixList[1])

    decision_matrix = zeros(Float64, ncandidates, ncriteria)

    @inbounds for i in 1:n
        decision_matrix[:,i] = result_list[i].priority
    end

    criteria_consistency = ahp_consistency(criteriaComparisonMatrix)

    weights = criteria_consistency.priority

    decision_matrix_df = makeDecisionMatrix(decision_matrix)
    ordering_result =  decision_matrix * weights

    bestIndex = sortperm(ordering_result) |> last

    result = AHPResult(
        comparisonMatrixList,
        criteriaComparisonMatrix,
        criteria_consistency,
        decision_matrix_df,
        ordering_result,
        weights,
        bestIndex
    )

    return result
end


#######################################################
# Constrained to unconstrained distribution bijectors #
#######################################################
struct TruncatedBijector{T} <: Bijector{0}
    lb::T
    ub::T
end

function (b::TruncatedBijector)(x::Real)
    a, b = b.lb, b.ub
    lowerbounded, upperbounded = isfinite(a), isfinite(b)
    if lowerbounded && upperbounded
        return StatsFuns.logit((x - a) / (b - a))
    elseif lowerbounded
        return log(x - a)
    elseif upperbounded
        return log(b - x)
    else
        return x
    end
end

function (b::TruncatedBijector)(x::AbstractVector{<:Real})
    a, b = b.lb, b.ub
    lowerbounded, upperbounded = isfinite(a), isfinite(b)
    if lowerbounded && upperbounded
        return @. StatsFuns.logit((x - a) / (b - a))
    elseif lowerbounded
        return log.(x - a)
    elseif upperbounded
        return log.(b - x)
    else
        return x
    end
end

function (ib::Inversed{<:TruncatedBijector})(y::Real)
    a, b = ib.orig.lb, ib.orig.ub
    lowerbounded, upperbounded = isfinite(a), isfinite(b)
    if lowerbounded && upperbounded
        return (b - a) * StatsFuns.logistic(y) + a
    elseif lowerbounded
        return exp(y) + a
    elseif upperbounded
        return b - exp(y)
    else
        return y
    end
end

function (ib::Inversed{<:TruncatedBijector})(y::AbstractVector{<:Real})
    a, b = ib.orig.lb, ib.orig.ub
    lowerbounded, upperbounded = isfinite(a), isfinite(b)
    if lowerbounded && upperbounded
        return @. (b - a) * StatsFuns.logistic(y) + a
    elseif lowerbounded
        return @. exp(y) + a
    elseif upperbounded
        return @. b - exp(y)
    else
        return y
    end
end

function logabsdetjac(b::TruncatedBijector, x::Real)
    a, b = b.lb, b.ub
    lowerbounded, upperbounded = isfinite(a), isfinite(b)
    if lowerbounded && upperbounded
        return - log((x - a) * (b - x) / (b - a))
    elseif lowerbounded
        return - log(x - a)
    elseif upperbounded
        return - log(b - x)
    else
        return zero(x)
    end
end

function logabsdetjac(b::TruncatedBijector, x::AbstractVector{<:Real})
    a, b = b.lb, b.ub
    lowerbounded, upperbounded = isfinite(a), isfinite(b)
    if lowerbounded && upperbounded
        return @. - log((x - a) * (b - x) / (b - a))
    elseif lowerbounded
        return @. - log(x - a)
    elseif upperbounded
        return @. - log(b - x)
    else
        return zero(x)
    end
end


function gausschebyshev(n::Int, kind::Int=1)
    # GAUSS-CHEBYSHEV NODES AND WEIGHTS.

    # Use known explicit formulas. Complexity O(n).
    if kind == 1
        # Gauss-ChebyshevT quadrature, i.e., w(x) = 1/sqrt(1-x^2)
        [cos((2 * k - 1) * π / (2 * n)) for k = n:-1:1], fill(π / n, n)
    elseif kind == 2
        # Gauss-ChebyshevU quadrature, i.e., w(x) = sqrt(1-x^2)
        ([cos(k * π / (n + 1)) for k = n:-1:1],
         [π/(n + 1) * sin(k / (n + 1) * π)^2 for k = n:-1:1])
    elseif kind == 3
        # Gauss-ChebyshevV quadrature, i.e., w(x) = sqrt((1+x)/(1-x))
        ([cos((k - .5) * π / (n + .5)) for k = n:-1:1],
         [2π / (n + .5) * cos((k - .5) * π / (2 * (n + .5)))^2 for k = n:-1:1])
    elseif kind == 4
        # Gauss-ChebyshevW quadrature, i.e., w(x) = sqrt((1-x)/(1+x))
        ([cos(k * π / (n + .5)) for k = n:-1:1],
         [2π / (n + .5) * sin(k * π / (2 * (n + .5)))^2 for k = n:-1:1])
    else
        throw(ArgumentError("Chebyshev kind should be 1, 2, 3, or 4"))
    end
end


"""
Shapey value using Harsanyi dividends
"""
function fₛⁱ(G::Game, i::Int)
    ∑(Δₕ(G, T) / length(T) for T in subsets(G.N) if i ∈ T)
end

"""
Shapey value using permutations
"""
function fₛ(G::SimpleGame)
    N = length(G.N)
    Nσ = factorial(N)

    shapley = zeros(N)

    for n in 1:Nσ
        σ = nthperm(G.N, n)

        for (i, s) in enumerate(σ)
            shapley[s] += G.v(σ[1:i]) - G.v(σ[1:i - 1])
        end

    end

    return shapley ./ Nσ
end
fₛ(G::GraphGame) = fₛ(graphtoMyerson(G))

"""
Myerson solution, it is equivalent to the Shapley value of the Myerson game.
"""
μ(G::SimpleGame, L::Array{NTuple{2,Int}}) = fₛ(GraphGame(G.N, G.v, L))


""" Computes divided differences of tabulated data n points x and
    for each of the n points, up to m derivatives  dy
"""
function DividedDifference2(x,dy)  # generate array of divided differences
    n, m = size(dy)                # n data points, m derivatives (0 to m-1)
    dd = zeros(typeof(x[1]),n*m,n*m)               # matrix of divided differences
    z = zeros(typeof(x[1]),n*m)
    k=1;
    for i = 1:n                    # n data points
        for j = 1:m                # m derivatives (0 to m-1) at each point
            k = (i-1)*m + j        # row index
            z[k] = x[i]
            dd[k,1] = dy[i,1]      # 0th divided difference in first column
            for l = 2:k            # column index for the remaining columns
                if dd[k,l-1] == dd[k-1,l-1]  # left and top-left neighbors are repeated
                    if l<=m
                        dd[k,l] = dy[i,l] / factorial(l-1)
                    else dd[k,l] = big(0.0)
                    end
                else
                    dd[k,l] = (dd[k,l-1] - dd[k-1,l-1]) / (z[k]-z[k-l+1])
                end
            end
        end
    end
    return dd
end


"""
OLSFn

Run the (first-stage) time-series regressions for the asset pricing model to obtain the alpha and betas for each test assets

INPUT
`Y': T x N matrix of dependent varaible
`X': T x (K + 1) matrix with K explanatory variables and one intercept term
`cov_method': method for calculating the variance-covariance matrix of the error terms:
            - time iid: error terms are iid across time but cross-sectionally correlated
            - iid: error terms are iiid both across time and assets
            - NW: Newey-West covariance matrix with m lags
            - HH: Hansen-Hodrick covariane matrix with m lags
`m': number of lags for the Newey-West or Hansen-Hodrick covariance matrix

OUTPUT
`alpha': N x 1 vector of alphas of the test assets
`beta': N x K matrix of the betas of the test assets. There are N test assets and K factors
`resid': T x N matrix of the pricing errors
`cov_resid': N x N matrix of covariance matrix of the pricing errors
"""

function OLSFn(Y, X, cov_method = "time iid", m = 0)
    beta = (X' * X) \ (X' * Y); # (K+1) x N
    resid = Y - X * beta;
    alpha = beta[1, :]; # alphas N x 1
    beta = beta[2:end, :]'; # betas N x K

    # Method of estimating the covariance matrix of residuals for OLS
    if cov_method == "time iid" # iid across time but cross-sectionally correlated
        cov_resid = cov(resid);
    elseif cov_method == "iid" # iid
        cov_resid = cov(resid);
        cov_resid = diagm(diag(cov_resid));
    elseif cov_method == "NW" # Newey-West
        cov_resid = cov(resid);
        for i = 1:m
            w = 1 - i / (m + 1);
            cov_resid = cov_resid + 2 * w * (1 / (T - 1 - i)) * resid[1:end - i, :]' * resid[1 + i:end, :];
        end
    elseif cov_method == "HH" # Hansen-Hodrick
        cov_resid = cov(resid)
        for i = 1:m
            cov_resid = cov_resid + (2 / (T - 1 - i)) * resid[1:end - i, :]' * resid[1 + i:end, :];
        end
    end

    return (alpha, beta, resid, cov_resid)
end


using LinearAlgebra, Statistics, StatsBase, Plots; pyplot()

n, N = 5, 10^6
P = diagm(-1 => fill(1/3,n-1),
           0 => fill(1/3,n),
           1 => fill(1/3,n-1))
P[1,n], P[n,1] = 1/3, 1/3

A = UpperTriangular(ones(n,n))
C = P*A

function f1(x,u)
    for xNew in 1:n
        if u <= C[x+1,xNew]
            return xNew-1
        end
    end
end

f2(x,xi) = mod(x + xi , n)

function countTau(f,rnd)
    t = 0
    visits = fill(false,n)
    state = 0
    while sum(visits) < n
        state = f(state,rnd())
        visits[state+1] |= true
        t += 1
    end
    return t-1
end

data1 = [countTau(f1,rand) for _ in 1:N]
data2 = [countTau(f2,()->rand([-1,0,1]) ) for _ in 1:N]
est1, est2 = mean(data1), mean(data2)
c1, c2 = counts(data1)/N,counts(data2)/N
println("Estimated mean value of tau using f1: ",est1)
println("Estimated mean value of tau using f2: ",est2)
println("\nThe matrix P:", P)
scatter(4:33,c1[1:30],
	c=:blue, ms=5, msw=0,
	label="Transition probability matrix")
scatter!(4:33,c2[1:30],
	c=:red, ms=5, msw=0, shape=:cross,
	label="Stochastic recursive formula", xlabel="Time", ylabel="Probability")


export iCycle, iPath, iGrid, iKnight, iCube, iShift

"""
`iCycle(n::Int, simple::Bool=true)` creates an implicit graph that is
a cycle graph with `n` vertices `1` through `n`. When `simple`
is `true`, the graph is undirected; when `false` it's a directed
cycle `1 → 2 → 3 → ⋯ → n → 1`.
"""
function iCycle(n::Int, simple::Bool = true)::ImplicitGraph{Int}
    if n < 3
        error("Number of vertices must be at least 3")
    end

    function N(v::Int)
        a = mod1(v + 1, n)
        b = mod1(v - 1, n)
        if simple
            return [b, a]
        end
        return [a]
    end

    has_vertex(v::Int) = 1 <= v <= n

    return ImplicitGraph{Int}(has_vertex, N)
end

"""
`iPath(simple::Bool=true)` creates an implicit graph that is
a path graph on the integers. If `simple` is `true` vertex `v` is
has an edge to `v-1` and `v+1`; otherwise, `v` has an edge only to
`v+1`.
"""
function iPath(simple::Bool = true)::ImplicitGraph{Int}
    yes(v::Int)::Bool = true

    function N(v::Int)::Vector{Int}
        if simple
            return [v - 1, v + 1]
        else
            return [v + 1]
        end
    end
    return ImplicitGraph{Int}(yes, N)
end

"""
`iGrid()` returns an infinite two-dimensional grid graph. Vertices are
of type `Tuple{Int,Int}`.
"""
function iGrid()::ImplicitGraph{Tuple{Int,Int}}
    yes(v::Tuple{Int,Int})::Bool = true
    function N(v::Tuple{Int,Int})::Vector{Tuple{Int,Int}}
        a, b = v
        return [(a, b - 1), (a, b + 1), (a - 1, b), (a + 1, b)]
    end
    return ImplicitGraph{Tuple{Int,Int}}(yes, N)
end

"""
`iKnight()` returns the Knight's move graph on an infinite
chessboard.
"""
function iKnight()::ImplicitGraph{Tuple{Int,Int}}
    yes(v::Tuple{Int,Int})::Bool = true
    function N(v::Tuple{Int,Int})::Vector{Tuple{Int,Int}}
        a, b = v
        neigh = [
            (a + 1, b + 2),
            (a + 1, b - 2),
            (a + 2, b + 1),
            (a + 2, b - 1),
            (a - 1, b + 2),
            (a - 1, b - 2),
            (a - 2, b + 1),
            (a - 2, b - 1),
        ]
        return neigh
    end
    return ImplicitGraph{Tuple{Int,Int}}(yes, N)
end

"""
`iCube(d::Int)` creates an (implict) `d`-dimensional cube graph.
"""
function iCube(d::Int)::ImplicitGraph{String}
    if d < 1
        error("Dimension must be positive")
    end

    function dvec_check(s::String)::Bool
        if length(s) != d
            return false
        end
        for i = 1:d
            if s[i] ∉ "01"
                return false
            end
        end
        return true
    end

    function N(v::String)
        result = Vector{String}(undef, d)
        for i = 1:d
            head = v[1:i-1]
            c = v[i] == '0' ? "1" : "0"
            tail = v[i+1:end]
            result[i] = head * c * tail
        end
        return result
    end

    return ImplicitGraph{String}(dvec_check, N)
end

using IterTools
"""
`iShift(alphabet,n)` creates an implicit shift digraph whose vertices
are `n`-tuples of elements of `alphabet`.
"""
function iShift(alphabet, n::Int)::ImplicitGraph
    elts = collect(distinct(alphabet))
    T = eltype(elts)

    function has_vertex(v)::Bool
        for i = 1:n
            if v[i] ∉ elts
                return false
            end
        end
        return true
    end

    function N(v)
        base = v[2:end]
        result = [(base..., j) for j in elts]
        return result
    end

    return ImplicitGraph{NTuple{n,T}}(has_vertex, N)
end


### wiener-time-series.jl
#
# Copyright (C) 2016 Mosè Giordano.
#
# Maintainer: Mosè Giordano <mose AT gnu DOT org>
# Keywords: deconvolution, signal processing
#
# This file is a part of Deconvolution.jl.
#
# License is MIT "Expat".
#
### Commentary:
#
# This file produces an example image used in Deconvolution.jl documentation.
#
### Code:

using Random

Random.seed!(7)

using LombScargle, Deconvolution, Plots, Statistics, FFTW
t = range(0, stop=10, length=1000) # observation times
x = sinpi.(t) .* cos.(5t) - 1.5cospi.(t) .* sin.(2t) # the original signal

# Gaussian blurring kernel
kernel = exp.( - 10 .* (t .- 5).^2)
kernel ./= sum(kernel) # normalize kernel to sum of 1
kernel = ifftshift(kernel) # move center to index pos 1

n = rand(length(x)) # noise to be added
noise = 3 .* (n .- mean(n))
y = x + noise # observed noisy signal
# blurred and noise signal
y_blurred = real(ifft(fft(kernel) .* fft(x))) + noise

# Lomb-Scargle periodogram
p = lombscargle(t, y, maximum_frequency=2, samples_per_peak=10)
plot(freqpower(p)...)

m1 = LombScargle.model(t, y, findmaxfreq(p, [0, 0.5])[1]) # first model
m2 = LombScargle.model(t, y, findmaxfreq(p, [0.5, 1])[1]) # second model
m3 = LombScargle.model(t, y, findmaxfreq(p, [1, 1.5])[1]) # third model

signal = m1 + m2 + m3
polished = wiener(y, signal, noise)
deblurred = wiener(y_blurred, signal, noise, kernel)

# Plots
plot(t, x, size=(900, 600), label="Original signal", linewidth=2)
plot!(t, y, label="Observed signal")
savefig("time-series-observed.png")

plot(t, x, size=(900, 600), label="Original signal", linewidth=2)
plot!(t, y_blurred, label="Blurred signal")
savefig("time-series-observed-blurred.png")

plot(t, x, size=(900, 600), label="Original signal", linewidth=2)
plot!(t, polished, label="Recovered with Wiener")
plot!(t, signal, label="Lomb–Scargle model")
savefig("time-series-recovered.png")

plot(t, x, size=(900, 600), label="Original signal", linewidth=2)
plot!(t, deblurred, label="Deblurred with Wiener")
plot!(t, signal, label="Lomb–Scargle model")
savefig("time-series-deblurred.png")


#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Functions to convert anomalies related to the orbit.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# References
#
#   [1] Vallado, D. A (2013). Fundamentals of Astrodynamics and Applications.
#       Microcosm Press, Hawthorn, CA, USA.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

export M_to_E, M_to_f
export E_to_f, E_to_M
export f_to_E, f_to_M

################################################################################
#                              From Mean Anomaly
################################################################################

"""
    M_to_E(e::T1, M::T2; kwargs...) where {T1, T2}

Compute the eccentric anomaly (0,2π) \\[rad] given the eccentricity `e` and the
mean anomaly `M` [rad].

This function uses the Newton-Raphson algorithm to solve the Kepler's equation.

# Keywords

- `tol::Union{Nothing, Number}`: Tolerance to accept the solution from
    Newton-Raphson algorithm. If `tol` is `nothing`, then it will be
    `eps(T)`, where `T` is a floating-point type obtained from the promotion of
    `T1` and `T2`. (**Default** = `nothing`)
- `max_iterations::Number`: Maximum number of iterations allowed for the
    Newton-Raphson algorithm. If it is lower than 1, then it is set to 10.
    (**Default** = 10)
"""
@inline function M_to_E(
    e::T1,
    M::T2;
    max_iterations::Integer = 10,
    tol::Union{Nothing, Number} = nothing
) where {T1, T2}
    T = float(promote_type(T1, T2))

    # Compute the eccentric anomaly using the Newton-Raphson method.
    # ==============================================================

    # Make sure that M is in the interval [0,2π].
    M = mod(M, T(2π))

    # Initial guess.
    #
    # See [1, p. 75].
    E = (M > π) ? M - e : M + e

    sin_E, cos_E = sincos(E)

    # Check the tolerance.
    if tol === nothing
        δ = eps(T)
    else
        δ = T(tol)
    end

    # Check the maximum number of iterations.
    if max_iterations < 1
        max_iterations = 10
    end

    # Newton-Raphson iterations.
    for i in 1:max_iterations
        abs(E - e*sin_E - M) ≤ δ && break
        E = E - (E - e * sin_E - M) / (1 - e * cos_E)
        sin_E, cos_E = sincos(E)
    end

    # Return the eccentric anomaly in the interval [0, 2π].
    return mod(E, T(2π))
end

"""
    M_to_f(e::T1, M::T2; kwargs...) where {T1, T2}

Compute the true anomaly (0,2π) \\[rad] given the eccentricity `e` and the mean
anomaly `M` [rad].

This function uses the Newton-Raphson algorithm to solve the Kepler's equation.

# Keywords

- `tol::Union{Nothing, Number}`: Tolerance to accept the solution from
    Newton-Raphson algorithm. If `tol` is `nothing`, then it will be
    `eps(T)`, where `T` is a floating-point type obtained from the promotion of
    `T1` and `T2`. (**Default** = `nothing`)
- `max_iterations::Number`: Maximum number of iterations allowed for the
    Newton-Raphson algorithm. If it is lower than 1, then it is set to 10.
    (**Default** = 10)
"""
@inline function M_to_f(e::T1, M::T2; kwargs...) where {T1, T2}
    # Compute the eccentric anomaly.
    E = M_to_E(e, M; kwargs...)

    # Compute the true anomaly in the interval [0,2π].
    return E_to_f(e, E)
end

################################################################################
#                            From Eccentric Anomaly
################################################################################

"""
    E_to_f(e::T1, E::T2) where {T1, T2}

Compute the true anomaly (0,2π) \\[rad] given the eccentricity `e` and the
eccentric anomaly `E` [rad].
"""
@inline function E_to_f(e::T1, E::T2) where {T1, T2}
    T = float(promote_type(T1, T2))

    sin_Eo2, cos_Eo2 = sincos(E / 2)

    # Compute the true anomaly in the interval [0, 2*π].
    return mod(2atan(sqrt(1 + e) * sin_Eo2, sqrt(1 - e) * cos_Eo2), T(2π))
end

"""
    E_to_M(e::T1, E::T2) where {T1, T2}

Compute the mean anomaly (0,2π) \\[rad] given the eccentricity `e` and the
eccentric anomaly `E` [rad].
"""
@inline function E_to_M(e::T1, E::T2) where {T1, T2}
    T = float(promote_type(T1, T2))
    return mod(E - e * sin(E), T(2π))
end

################################################################################
#                              From True Anomaly
################################################################################

"""
    f_to_E(e::T1, f::T2) where {T1, T2}

Compute the eccentric anomaly (0,2π) \\[rad] given the eccentricity `e` and
the true anomaly `f` [rad].
"""
@inline function f_to_E(e::T1, f::T2) where {T1, T2}
    T = float(promote_type(T1, T2))
    sin_fo2, cos_fo2 = sincos(f / 2)
    return mod(2atan(sqrt(1 - e) * sin_fo2, sqrt(1 + e) * cos_fo2), T(2π) )
end

"""
    f_to_M(e::T1, f::T2) where {T1, T2}

Compute the mean anomaly (0,2π) \\[rad] given the eccentricity `e` and the
true anomaly `f` [rad].
"""
@inline function f_to_M(e::T1, f::T2) where {T1, T2}
    # Compute the eccentric anomaly.
    E = f_to_E(e, f)

    # Compute the true anomaly in the interval [0, 2π].
    return E_to_M(e, E)
end


# One dimensional wave equation
using ConservationLawsDiffEq
using OrdinaryDiffEq
const CL = ConservationLawsDiffEq

const CFL = 0.45
const Tend = 1.0
const cc = 1.0

Jf(u::AbstractVector) = [0.0 cc;cc 0.0]
f(u::AbstractVector) = [0.0 cc;cc 0.0]*u
f0(x) = [sin(4*π*x),0.0]

Nflux(ϕl::AbstractVector, ϕr::AbstractVector) = 0.5*(f(ϕl)+f(ϕr))
exact_sol(x, t::Float64) = [0.5*(sin(4*π*(-t+x))+sin(4*π*(t+x))),
0.5*(sin(4*π*(-t+x))-sin(4*π*(t+x)))]

# Now discretizate the domain
mesh = Uniform1DFVMesh(50, [-1.0, 1.0])

function get_problem(alg, mesh)
  #Compute discrete initial data
  u0 = getInitialState(mesh,f0,use_threads = true)

  # Now get a explicit semidiscretization (discrete in space) du_h(t)/dt = f_h(u_h(t))
  f_h = getSemiDiscretization(f,alg,mesh,[Periodic()]; Df = Jf, use_threads = false,numvars = 2)

  #Setup ODE problem for a time interval = [0.0,1.0]
  ode_prob = ODEProblem(f_h,u0,(0.0,Tend))

  #Setup callback in order to fix CFL constant value
  cb = getCFLCallback(f_h, CFL)

  #Estimate an initial dt
  dt = update_dt!(u0, f_h, CFL)
  return ode_prob, cb, dt
end

ode_prob, cb, dt = get_problem(FVSKTScheme(), mesh)
sol = solve(ode_prob,SSPRK22(); dt = dt, callback = cb)

ode_prob, cb, dt = get_problem(FVTecnoScheme(Nflux;order=3), mesh)
sol2 = solve(ode_prob,SSPRK22(); dt = dt, callback = cb)

ode_prob, cb, dt = get_problem(FVCompWENOScheme(), mesh)
sol3 = solve(ode_prob,SSPRK22(); dt = dt, callback = cb)

ode_prob, cb, dt = get_problem(FVCompMWENOScheme(), mesh)
sol4 = solve(ode_prob,SSPRK22(); dt = dt, callback = cb)

ode_prob, cb, dt = get_problem(FVSpecMWENOScheme(), mesh)
sol5 = solve(ode_prob,SSPRK22(); dt = dt, callback = cb)

#Estimate errors
u1_h = fv_solution(sol, mesh; vars = 2)
u2_h = fv_solution(sol2, mesh; vars = 2)
u3_h = fv_solution(sol3, mesh; vars = 2)
u4_h = fv_solution(sol4, mesh; vars = 2)
u5_h = fv_solution(sol5, mesh; vars = 2)
get_L1_error(exact_sol,u1_h)
get_L1_error(exact_sol,u2_h)
get_L1_error(exact_sol,u3_h)
get_L1_error(exact_sol,u4_h)
get_L1_error(exact_sol,u5_h)

#Plot
using Plots
plot(u1_h, vars=1, lab="KT y",line = (:dot,2))
plot!(u2_h, vars=1,lab="Tecno y",line=(:dot,3))
plot!(u3_h, vars=1,lab="Comp WENO y",line=(:dot,3))
plot!(u4_h, vars=1,lab="Comp MWENO y",line=(:dot,3))
plot!(u5_h, vars=1,lab="Spec MWENO y",line=(:dot,3))
plot!(CL.cell_centers(mesh), [exact_sol(x,Tend)[1] for x in CL.cell_centers(mesh)],lab="Ref y")


"""
Features extraction from returns proposed by Betina and al.
"""
#KLT stands for the long-term period
#KST stands for the short-term period
#ϱ is the number of standard deviations above the means
function feature_indexes_sgns(asset_rtn_sgn, kst, klt, kmom, ϱ=2)
    #current time
    t = size(asset_rtn_sgn, 1)
    # sig_1 and sig_2 are
    # short-term and long-term simple moving averages
    sig_1 = (1 / kst) * sum(asset_rtn_sgn[t - d] for d in 0:(kst - 1))
    sig_2 = (1 / klt) * sum(asset_rtn_sgn[t - d] for d in 0:(klt - 1))

    # sig_3 and sig_4 are exponential moving average (short and long)
    sig_3 =
        (1.0 / sum(exp(-d / kst) for d in 1:kst)) *
        sum(exp(-d / kst) * asset_rtn_sgn[t - d] for d in 0:(kst - 1))
    sig_4 =
        (1.0 / sum(exp(-d / klt) for d in 1:klt)) *
        sum(exp(-d / klt) * asset_rtn_sgn[t - d] for d in 0:(klt - 1))
    # RSI
    R_pos = 0
    R_neg = 0
    num_pos = 0
    num_neg = 0
    for i in (t - kmom + 1):t
        if (asset_rtn_sgn[i] > 0)
            R_pos = R_pos + asset_rtn_sgn[i]
            num_pos = num_pos + 1
        else
            R_neg = R_neg + asset_rtn_sgn[i]
            num_neg = num_neg + 1
        end
    end
    med_pos = R_pos / num_pos
    if num_neg == 0
        med_neg = 1
    else
        med_neg = abs(R_neg / num_neg)
    end
    sig_5 = med_pos / med_neg
    # estimated shor-t and long-t standard deviations
    σ_st_6 = sqrt((1.0 / kst) * sum((asset_rtn_sgn[t - d] - sig_1)^2 for d in 0:(kst - 1)))
    σ_lt_7 = sqrt((1.0 / klt) * sum((asset_rtn_sgn[t - d] - sig_2)^2 for d in 0:(klt - 1)))
    # sig_6 and sig_7 are

    if sig_1 <= 0
        sig_6 = sig_1 - ϱ * σ_st_6
    else
        sig_6 = sig_1 + ϱ * σ_st_6
    end
    if sig_2 <= 0
        sig_7 = sig_2 - ϱ * σ_lt_7
    else
        sig_7 = sig_2 + ϱ * σ_lt_7
    end
    σ_st_8 = sqrt((1.0 / kst) * sum((asset_rtn_sgn[t - d] - sig_3)^2 for d in 0:(kst - 1)))
    σ_lt_9 = sqrt((1.0 / klt) * sum((asset_rtn_sgn[t - d] - sig_4)^2 for d in 0:(klt - 1)))
    if sig_3 <= 0
        sig_8 = sig_3 - ϱ * σ_st_8
    else
        sig_8 = sig_3 + ϱ * σ_st_8
    end
    if sig_4 <= 0
        sig_9 = sig_4 - ϱ * σ_lt_9
    else
        sig_9 = sig_4 + ϱ * σ_lt_9
    end

    return [sig_1; sig_2; sig_3; sig_4; sig_5; sig_6; sig_7; sig_8; sig_9]
end

"""
Mixed signals predictor proposed by Betina and al. Univariate
"""
function mixed_signals_predict_return(asset_rtn_sgn, num_t, kst_a, klt_a, kmom, ϱ=2)
    numD = size(asset_rtn_sgn, 1)
    num_train = min(numD - 1, num_t)

    ## optimize features weights (least squares based on past returns)
    # signal features extraction train (from klt+1 to klt+num_train)
    #kst = max(min(numD-num_train-10,kst_a),0)
    #klt = max(min(numD-num_train-10,klt_a),0)
    sig_features = zeros(num_train, 9) #each line is the i-element's featurs
    for i in 1:num_train
        #kst = max(min(numD-i-2,kst_a),0)
        klt = max(min(numD - i - 2, klt_a), 0)
        kst = Int64(floor(klt / 2.0))
        sig_features[num_train + 1 - i, :] = feature_indexes_sgns(
            asset_rtn_sgn[1:(numD - i)], kst, klt, kmom
        )
    end
    # optimize weights (least squares - pseudo inverse)
    try
        weights_opt = \(
            sig_features'sig_features,
            sig_features'asset_rtn_sgn[(numD - num_train + 1):numD],
        )
    catch
        weights_opt = pinv(sig_features, 1E-07) * asset_rtn_sgn[(numD - num_train + 1):numD]
    end
    #  normalize features weights
    weights_opt = weights_opt ./ sum(weights_opt)
    ## extract features current return
    kst = min(numD, kst_a)
    klt = min(numD, klt_a)
    features = feature_indexes_sgns(asset_rtn_sgn[1:numD], kst, klt, kmom)
    ## extrapolate return

    return (features'weights_opt)[1]
end